1 /*
2 * Copyright (c) 2002-2026 Gargoyle Software Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 * https://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package org.htmlunit.html;
16
17 import java.io.IOException;
18 import java.util.Arrays;
19 import java.util.Map;
20
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.htmlunit.SgmlPage;
24 import org.htmlunit.javascript.host.event.Event;
25 import org.htmlunit.util.StringUtils;
26 import org.htmlunit.util.geometry.Circle2D;
27 import org.htmlunit.util.geometry.Polygon2D;
28 import org.htmlunit.util.geometry.Rectangle2D;
29 import org.htmlunit.util.geometry.Shape2D;
30
31 /**
32 * Wrapper for the HTML element "area".
33 *
34 * @author Mike Bowler
35 * @author David K. Taylor
36 * @author Christian Sell
37 * @author Marc Guillemot
38 * @author Ahmed Ashour
39 * @author Frank Danek
40 * @author Ronald Brill
41 */
42 public class HtmlArea extends HtmlElement implements HyperlinkElement {
43 private static final Log LOG = LogFactory.getLog(HtmlArea.class);
44
45 /** The HTML tag represented by this element. */
46 public static final String TAG_NAME = "area";
47
48 private static final String SHAPE_RECT = "rect";
49 private static final String SHAPE_CIRCLE = "circle";
50 private static final String SHAPE_POLY = "poly";
51
52 /**
53 * Creates a new instance.
54 *
55 * @param qualifiedName the qualified name of the element type to instantiate
56 * @param page the page that contains this element
57 * @param attributes the initial attributes
58 */
59 HtmlArea(final String qualifiedName, final SgmlPage page,
60 final Map<String, DomAttr> attributes) {
61 super(qualifiedName, page, attributes);
62 }
63
64 /**
65 * {@inheritDoc}
66 */
67 @Override
68 protected boolean doClickStateUpdate(final boolean shiftKey, final boolean ctrlKey) throws IOException {
69 HyperlinkElementSupport.doClickStateUpdate(this, shiftKey, ctrlKey, "");
70 return false;
71 }
72
73 /**
74 * Returns the value of the attribute {@code shape}. Refer to the
75 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
76 * documentation for details on the use of this attribute.
77 *
78 * @return the value of the attribute {@code shape} or an empty string if that attribute isn't defined
79 */
80 public final String getShapeAttribute() {
81 return getAttributeDirect("shape");
82 }
83
84 /**
85 * Returns the value of the attribute {@code coords}. Refer to the
86 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
87 * documentation for details on the use of this attribute.
88 *
89 * @return the value of the attribute {@code coords} or an empty string if that attribute isn't defined
90 */
91 public final String getCoordsAttribute() {
92 return getAttributeDirect("coords");
93 }
94
95 /**
96 * Returns the value of the attribute {@code href}. Refer to the
97 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
98 * documentation for details on the use of this attribute.
99 *
100 * @return the value of the attribute {@code href} or an empty string if that attribute isn't defined
101 */
102 @Override
103 public final String getHrefAttribute() {
104 return getAttributeDirect("href");
105 }
106
107 /**
108 * Returns the value of the attribute {@code nohref}. Refer to the
109 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
110 * documentation for details on the use of this attribute.
111 *
112 * @return the value of the attribute {@code nohref} or an empty string if that attribute isn't defined
113 */
114 public final String getNoHrefAttribute() {
115 return getAttributeDirect("nohref");
116 }
117
118 /**
119 * Returns the value of the attribute {@code alt}. Refer to the
120 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
121 * documentation for details on the use of this attribute.
122 *
123 * @return the value of the attribute {@code alt} or an empty string if that attribute isn't defined
124 */
125 public final String getAltAttribute() {
126 return getAttributeDirect("alt");
127 }
128
129 /**
130 * Returns the value of the attribute {@code tabindex}. Refer to the
131 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
132 * documentation for details on the use of this attribute.
133 *
134 * @return the value of the attribute {@code tabindex} or an empty string if that attribute isn't defined
135 */
136 public final String getTabIndexAttribute() {
137 return getAttributeDirect("tabindex");
138 }
139
140 /**
141 * Returns the value of the attribute {@code accesskey}. Refer to the
142 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
143 * documentation for details on the use of this attribute.
144 *
145 * @return the value of the attribute {@code accesskey} or an empty string if that attribute isn't defined
146 */
147 public final String getAccessKeyAttribute() {
148 return getAttributeDirect("accesskey");
149 }
150
151 /**
152 * Returns the value of the attribute {@code onfocus}. Refer to the
153 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
154 * documentation for details on the use of this attribute.
155 *
156 * @return the value of the attribute {@code onfocus} or an empty string if that attribute isn't defined
157 */
158 public final String getOnFocusAttribute() {
159 return getAttributeDirect("onfocus");
160 }
161
162 /**
163 * Returns the value of the attribute {@code onblur}. Refer to the
164 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
165 * documentation for details on the use of this attribute.
166 *
167 * @return the value of the attribute {@code onblur} or an empty string if that attribute isn't defined
168 */
169 public final String getOnBlurAttribute() {
170 return getAttributeDirect("onblur");
171 }
172
173 /**
174 * Returns the value of the attribute {@code target}. Refer to the
175 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
176 * documentation for details on the use of this attribute.
177 *
178 * @return the value of the attribute {@code target} or an empty string if that attribute isn't defined
179 */
180 @Override
181 public final String getTargetAttribute() {
182 return getAttributeDirect("target");
183 }
184
185 /**
186 * Returns the value of the attribute {@code rel}. Refer to the
187 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
188 * documentation for details on the use of this attribute.
189 *
190 * @return the value of the attribute {@code rel} or an empty string if that attribute isn't defined
191 */
192 @Override
193 public final String getRelAttribute() {
194 return getAttributeDirect("rel");
195 }
196
197 /**
198 * Returns the value of the attribute {@code ping} or an empty string if that attribute isn't defined.
199 *
200 * @return the value of the attribute {@code ping} or an empty string if that attribute isn't defined
201 */
202 @Override
203 public final String getPingAttribute() {
204 return getAttributeDirect("ping");
205 }
206
207 /**
208 * Returns the value of the attribute {@code download} or an empty string if that attribute isn't defined.
209 *
210 * @return the value of the attribute {@code download} or an empty string if that attribute isn't defined
211 */
212 @Override
213 public final String getDownloadAttribute() {
214 return getAttributeDirect("download");
215 }
216
217 /**
218 * Indicates if this area contains the specified point.
219 * @param x the x coordinate of the point
220 * @param y the y coordinate of the point
221 * @return {@code true} if the point is contained in this area
222 */
223 boolean containsPoint(final int x, final int y) {
224 final String shape = StringUtils.defaultIfEmptyOrNull(getShapeAttribute(), SHAPE_RECT);
225
226 if ("default".equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
227 return true;
228 }
229
230 if (SHAPE_RECT.equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
231 final Shape2D rectangle = parseRect();
232 return rectangle.contains(x, y);
233 }
234
235 if (SHAPE_CIRCLE.equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
236 final Shape2D circle = parseCircle();
237 return circle.contains(x, y);
238 }
239
240 if (SHAPE_POLY.equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
241 final Shape2D path = parsePoly();
242 return path.contains(x, y);
243 }
244
245 return false;
246 }
247
248 /**
249 * {@inheritDoc}
250 */
251 @Override
252 public DisplayStyle getDefaultStyleDisplay() {
253 return DisplayStyle.NONE;
254 }
255
256 /**
257 * {@inheritDoc}
258 */
259 @Override
260 public boolean isDisplayed() {
261 final DomNode parent = getParentNode();
262 if (parent instanceof HtmlMap && parent.isDisplayed()) {
263 return !isEmpty();
264 }
265 return false;
266 }
267
268 private Rectangle2D parseRect() {
269 // browsers seem to support comma and blank
270 final String[] coords = StringUtils.splitAtCommaOrBlank(getCoordsAttribute());
271
272 double leftX = 0;
273 double topY = 0;
274 double rightX = 0;
275 double bottomY = 0;
276
277 try {
278 if (coords.length > 0) {
279 leftX = Double.parseDouble(coords[0].trim());
280 }
281 if (coords.length > 1) {
282 topY = Double.parseDouble(coords[1].trim());
283 }
284 if (coords.length > 2) {
285 rightX = Double.parseDouble(coords[2].trim());
286 }
287 if (coords.length > 3) {
288 bottomY = Double.parseDouble(coords[3].trim());
289 }
290 }
291 catch (final NumberFormatException e) {
292 if (LOG.isWarnEnabled()) {
293 LOG.warn("Invalid rect coords '" + Arrays.toString(coords) + "'", e);
294 }
295 }
296
297 return new Rectangle2D(leftX, topY, rightX, bottomY);
298 }
299
300 private Circle2D parseCircle() {
301 // browsers seem to support comma and blank
302 final String[] coords = StringUtils.splitAtCommaOrBlank(getCoordsAttribute());
303
304 double centerX = 0;
305 double centerY = 0;
306 double radius = 0;
307
308 try {
309 if (coords.length > 0) {
310 centerX = Double.parseDouble(coords[0].trim());
311 }
312 if (coords.length > 1) {
313 centerY = Double.parseDouble(coords[1].trim());
314 }
315 if (coords.length > 2) {
316 radius = Double.parseDouble(coords[2].trim());
317 }
318
319 }
320 catch (final NumberFormatException e) {
321 if (LOG.isWarnEnabled()) {
322 LOG.warn("Invalid circle coords '" + Arrays.toString(coords) + "'", e);
323 }
324 }
325
326 return new Circle2D(centerX, centerY, radius);
327 }
328
329 private Shape2D parsePoly() {
330 // browsers seem to support comma and blank
331 final String[] coords = StringUtils.splitAtCommaOrBlank(getCoordsAttribute());
332
333 try {
334 if (coords.length > 1) {
335 double x = Double.parseDouble(coords[0].trim());
336 double y = Double.parseDouble(coords[1].trim());
337
338 final Polygon2D path = new Polygon2D(x, y);
339
340 for (int i = 2; i + 1 < coords.length; i += 2) {
341 x = Double.parseDouble(coords[i].trim());
342 y = Double.parseDouble(coords[i + 1].trim());
343
344 path.lineTo(x, y);
345 }
346 return path;
347 }
348 }
349 catch (final NumberFormatException e) {
350 if (LOG.isWarnEnabled()) {
351 LOG.warn("Invalid poly coords '" + Arrays.toString(coords) + "'", e);
352 }
353 }
354
355 return new Rectangle2D(0, 0, 0, 0);
356 }
357
358 /**
359 * {@inheritDoc}
360 */
361 @Override
362 public boolean handles(final Event event) {
363 if (Event.TYPE_BLUR.equals(event.getType()) || Event.TYPE_FOCUS.equals(event.getType())) {
364 return true;
365 }
366 return super.handles(event);
367 }
368
369 private boolean isEmpty() {
370 final String shape = StringUtils.defaultIfEmptyOrNull(getShapeAttribute(), SHAPE_RECT);
371
372 if ("default".equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
373 return false;
374 }
375
376 if (SHAPE_RECT.equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
377 final Shape2D rectangle = parseRect();
378 return rectangle.isEmpty();
379 }
380
381 if (SHAPE_CIRCLE.equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
382 final Shape2D circle = parseCircle();
383 return circle.isEmpty();
384 }
385
386 if (SHAPE_POLY.equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
387 final Shape2D path = parsePoly();
388 return path.isEmpty();
389 }
390
391 return false;
392 }
393 }