View Javadoc
1   /*
2    * Copyright (c) 2002-2025 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.css;
16  
17  import static org.htmlunit.css.CssStyleSheet.AUTO;
18  
19  import java.util.regex.Pattern;
20  
21  import org.apache.commons.lang3.math.NumberUtils;
22  import org.htmlunit.html.DomElement;
23  import org.htmlunit.html.DomNode;
24  import org.htmlunit.html.HtmlCanvas;
25  import org.htmlunit.html.HtmlHtml;
26  
27  /**
28   * Utilities for css value handling.
29   *
30   * @author Ronald Brill
31   */
32  public final class CssPixelValueConverter {
33  
34      private static final Pattern TO_FLOAT_PATTERN = Pattern.compile("(\\d+(?:\\.\\d+)?).*");
35  
36      /**
37       * Disallow instantiation of this class.
38       */
39      private CssPixelValueConverter() {
40          // Empty.
41      }
42  
43      /**
44       * Converts the specified length CSS attribute value into an integer number of pixels. If the
45       * specified CSS attribute value is a percentage, this method uses the specified value object
46       * to recursively retrieve the base (parent) CSS attribute value.
47       * @param element the element for which the CSS attribute value is to be retrieved
48       * @param value the CSS attribute value which is to be retrieved
49       * @return the integer number of pixels corresponding to the specified length CSS attribute value
50       * @see #pixelValue(String)
51       */
52      public static int pixelValue(final DomElement element, final CssValue value) {
53          final ComputedCssStyleDeclaration style =
54                  element.getPage().getEnclosingWindow().getComputedStyle(element, null);
55          final String s = value.get(style);
56          return pixelValue(element, s, value, false);
57      }
58  
59      /**
60       * Returns the specified length CSS attribute value value as a pixel length value.
61       * If the specified CSS attribute value is a percentage, this method
62       * uses the specified value object to recursively retrieve the base (parent) CSS attribute value.
63       * @param element the element for which the CSS attribute value is to be retrieved
64       * @param value the CSS attribute value which is to be retrieved
65       * @return the specified length CSS attribute value as a pixel length value
66       * @see #pixelValue(DomElement, CssValue)
67       */
68      public static String pixelString(final DomElement element, final CssValue value) {
69          final ComputedCssStyleDeclaration style =
70                  element.getPage().getEnclosingWindow().getComputedStyle(element, null);
71          final String styleValue = value.get(style);
72          if (styleValue.endsWith("px")) {
73              return styleValue;
74          }
75          return pixelValue(element, styleValue, value, false) + "px";
76      }
77  
78      /**
79       * Converts the specified length string value into an integer number of pixels. This method does
80       * <b>NOT</b> handle percentages correctly; use {@link #pixelString(DomElement, CssValue)} if you
81       * need percentage support).
82       * @param value the length string value to convert to an integer number of pixels
83       * @return the integer number of pixels corresponding to the specified length string value
84       * @see <a href="http://htmlhelp.com/reference/css/units.html">CSS Units</a>
85       * @see #pixelString(DomElement, CssValue)
86       */
87      public static int pixelValue(final String value) {
88          float i = NumberUtils.toFloat(TO_FLOAT_PATTERN.matcher(value).replaceAll("$1"), 0);
89          if (value.length() < 2) {
90              return Math.round(i);
91          }
92          if (value.endsWith("px")) {
93              return Math.round(i);
94          }
95  
96          if (value.endsWith("em")) {
97              i = i * 16;
98          }
99          else if (value.endsWith("%")) {
100             i = i * 16 / 100;
101         }
102         else if (value.endsWith("ex")) {
103             i = i * 10;
104         }
105         else if (value.endsWith("in")) {
106             i = i * 150;
107         }
108         else if (value.endsWith("cm")) {
109             i = i * 50;
110         }
111         else if (value.endsWith("mm")) {
112             i = i * 5;
113         }
114         else if (value.endsWith("pt")) {
115             i = i * 2;
116         }
117         else if (value.endsWith("pc")) {
118             i = i * 24;
119         }
120         return Math.round(i);
121     }
122 
123     private static int pixelValue(final DomElement element,
124             final String styleValue, final CssValue value, final boolean percentMode) {
125         if (styleValue.endsWith("%") || (styleValue.isEmpty() && element instanceof HtmlHtml)) {
126             final float i = NumberUtils.toFloat(TO_FLOAT_PATTERN.matcher(styleValue).replaceAll("$1"), 100);
127 
128             final DomNode parent = element.getParentNode();
129             final int absoluteValue;
130             if (parent instanceof DomElement) {
131                 final DomElement parentElem = (DomElement) parent;
132                 final ComputedCssStyleDeclaration style =
133                         parentElem.getPage().getEnclosingWindow().getComputedStyle(parentElem, null);
134                 final String parentStyleValue = value.get(style);
135                 absoluteValue = pixelValue((DomElement) parent, parentStyleValue, value, true);
136             }
137             else {
138                 absoluteValue = value.getWindowDefaultValue();
139             }
140             return  Math.round((i / 100f) * absoluteValue);
141         }
142         if (AUTO.equals(styleValue)) {
143             return value.getDefaultValue();
144         }
145         if (styleValue.isEmpty()) {
146             if (element instanceof HtmlCanvas) {
147                 return value.getWindowDefaultValue();
148             }
149 
150             // if the call was originated from a percent value we have to go up until
151             // we can provide some kind of base value for percent calculation
152             if (percentMode) {
153                 final DomNode parent = element.getParentNode();
154                 if (parent == null || parent instanceof HtmlHtml) {
155                     return value.getWindowDefaultValue();
156                 }
157                 final DomElement parentElem = (DomElement) parent;
158                 final ComputedCssStyleDeclaration style =
159                         parentElem.getPage().getEnclosingWindow().getComputedStyle(parentElem, null);
160                 final String parentStyleValue = value.get(style);
161                 return pixelValue(parentElem, parentStyleValue, value, true);
162             }
163 
164             return 0;
165         }
166         return pixelValue(styleValue);
167     }
168 
169     /**
170      * Encapsulates the retrieval of a style attribute, given a DOM element from which to retrieve it.
171      */
172     public abstract static class CssValue {
173         private final int defaultValue_;
174         private final int windowDefaultValue_;
175 
176         /**
177          * C'tor.
178          * @param defaultValue the default value
179          * @param windowDefaultValue the default value for the window
180          */
181         public CssValue(final int defaultValue, final int windowDefaultValue) {
182             defaultValue_ = defaultValue;
183             windowDefaultValue_ = windowDefaultValue;
184         }
185 
186         /**
187          * Gets the default value.
188          * @return the default value
189          */
190         public int getDefaultValue() {
191             return defaultValue_;
192         }
193 
194         /**
195          * Gets the default size for the window.
196          * @return the default value for the window
197          */
198         public int getWindowDefaultValue() {
199             return windowDefaultValue_;
200         }
201 
202         /**
203          * Returns the CSS attribute value from the specified computed style.
204          * @param style the computed style from which to retrieve the CSS attribute value
205          * @return the CSS attribute value from the specified computed style
206          */
207         public abstract String get(ComputedCssStyleDeclaration style);
208     }
209 }