View Javadoc
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 }