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