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