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 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 }