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.css;
16  
17  import static org.htmlunit.css.CssStyleSheet.AUTO;
18  
19  import java.util.regex.Pattern;
20  
21  import org.htmlunit.html.DomElement;
22  import org.htmlunit.html.DomNode;
23  import org.htmlunit.html.HtmlCanvas;
24  import org.htmlunit.html.HtmlHtml;
25  import org.htmlunit.util.StringUtils;
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 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 = StringUtils.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 * 8;
104         }
105         else if (value.endsWith("cm")) {
106             i = i * 38;
107         }
108         else if (value.endsWith("mm")) {
109             i = i * 4;
110         }
111         else if (value.endsWith("pt")) {
112             i = i * 2;
113         }
114         else if (value.endsWith("pc")) {
115             i = i * 24;
116         }
117         else if (value.endsWith("ch")) {
118             i = i * 8;
119         }
120         else if (value.endsWith("vh")
121                 || value.endsWith("vmin")) {
122             // this matches also
123             // "dvh" "dvmin" "lvh" "lvmin" "svh" "svmin"
124             i = i * 6;
125         }
126         else if (value.endsWith("vw")
127                 || value.endsWith("vmax")) {
128             // this matches also
129             // "dvw" "dvmax" "lvw" "lvmax" "svw" "svmax"
130             i = i * 12;
131         }
132         // placed at the end to handle min before
133         else if (value.endsWith("in")) {
134             i = i * 150;
135         }
136         return Math.round(i);
137     }
138 
139     private static int pixelValue(final DomElement element,
140             final String styleValue, final CssValue value, final boolean percentMode) {
141         if (styleValue.endsWith("%") || (styleValue.isEmpty() && element instanceof HtmlHtml)) {
142             final float i = StringUtils.toFloat(TO_FLOAT_PATTERN.matcher(styleValue).replaceAll("$1"), 100);
143 
144             final DomNode parent = element.getParentNode();
145             final int absoluteValue;
146             if (parent instanceof DomElement parentElem) {
147                 final ComputedCssStyleDeclaration style =
148                         parentElem.getPage().getEnclosingWindow().getComputedStyle(parentElem, null);
149                 final String parentStyleValue = value.get(style);
150                 absoluteValue = pixelValue(parentElem, parentStyleValue, value, true);
151             }
152             else {
153                 absoluteValue = value.getWindowDefaultValue();
154             }
155             return  Math.round((i / 100f) * absoluteValue);
156         }
157         if (AUTO.equals(styleValue)) {
158             return value.getDefaultValue();
159         }
160         if (styleValue.isEmpty()) {
161             if (element instanceof HtmlCanvas) {
162                 return value.getWindowDefaultValue();
163             }
164 
165             // if the call was originated from a percent value we have to go up until
166             // we can provide some kind of base value for percent calculation
167             if (percentMode) {
168                 final DomNode parent = element.getParentNode();
169                 if (parent == null || parent instanceof HtmlHtml) {
170                     return value.getWindowDefaultValue();
171                 }
172                 final DomElement parentElem = (DomElement) parent;
173                 final ComputedCssStyleDeclaration style =
174                         parentElem.getPage().getEnclosingWindow().getComputedStyle(parentElem, null);
175                 final String parentStyleValue = value.get(style);
176                 return pixelValue(parentElem, parentStyleValue, value, true);
177             }
178 
179             return 0;
180         }
181         return pixelValue(styleValue);
182     }
183 
184     /**
185      * Encapsulates the retrieval of a style attribute, given a DOM element from which to retrieve it.
186      */
187     public abstract static class CssValue {
188         private final int defaultValue_;
189         private final int windowDefaultValue_;
190 
191         /**
192          * C'tor.
193          * @param defaultValue the default value
194          * @param windowDefaultValue the default value for the window
195          */
196         public CssValue(final int defaultValue, final int windowDefaultValue) {
197             defaultValue_ = defaultValue;
198             windowDefaultValue_ = windowDefaultValue;
199         }
200 
201         /**
202          * Gets the default value.
203          * @return the default value
204          */
205         public int getDefaultValue() {
206             return defaultValue_;
207         }
208 
209         /**
210          * Gets the default size for the window.
211          * @return the default value for the window
212          */
213         public int getWindowDefaultValue() {
214             return windowDefaultValue_;
215         }
216 
217         /**
218          * Returns the CSS attribute value from the specified computed style.
219          * @param style the computed style from which to retrieve the CSS attribute value
220          * @return the CSS attribute value from the specified computed style
221          */
222         public abstract String get(ComputedCssStyleDeclaration style);
223     }
224 }