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 }