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