docs
Loading...
Searching...
No Matches
StyleValue.h
Go to the documentation of this file.
1///
2/// Copyright (C) 2026 Ultralight, Inc. All rights reserved.
3/// A license is required for commercial use. https://ultralig.ht
4///
5#pragma once
6// X11's headers define None as a macro, which would break CssUnit::None below.
7#pragma push_macro("None")
8#undef None
9
10#include <Ultralight/CAPI/CAPI_DOMElement.h>
11#include <Ultralight/detail/Css.h>
12
13#include <limits>
14#include <string_view>
15
16namespace ultralight {
17namespace dom {
18
19///
20/// The unit of a StyleValue (the same values as the C API's ULDOMStyleUnit).
21///
22enum class StyleUnit : unsigned {
23 Number = 0, ///< A unitless number (CSS `<number>`).
24 Percent, ///< `%`
25 Px, ///< `px`
26 Em, ///< `em`
27 Rem, ///< `rem`
28 Vw, ///< `vw`
29 Vh, ///< `vh`
30 Deg, ///< `deg`
31 Ms, ///< `ms`
32 S, ///< `s`
33 Empty, ///< No number (an Empty StyleValue; writing it removes the property).
34};
35
36static_assert(static_cast<unsigned>(StyleUnit::Number) == kULDOMStyleUnit_Number
37 && static_cast<unsigned>(StyleUnit::Percent) == kULDOMStyleUnit_Percent
38 && static_cast<unsigned>(StyleUnit::Px) == kULDOMStyleUnit_Px
39 && static_cast<unsigned>(StyleUnit::Em) == kULDOMStyleUnit_Em
40 && static_cast<unsigned>(StyleUnit::Rem) == kULDOMStyleUnit_Rem
41 && static_cast<unsigned>(StyleUnit::Vw) == kULDOMStyleUnit_Vw
42 && static_cast<unsigned>(StyleUnit::Vh) == kULDOMStyleUnit_Vh
43 && static_cast<unsigned>(StyleUnit::Deg) == kULDOMStyleUnit_Deg
44 && static_cast<unsigned>(StyleUnit::Ms) == kULDOMStyleUnit_Ms
45 && static_cast<unsigned>(StyleUnit::S) == kULDOMStyleUnit_S
46 && static_cast<unsigned>(StyleUnit::Empty) == kULDOMStyleUnit_Empty,
47 "dom::StyleUnit must mirror ULDOMStyleUnit");
48
49/// \cond INTERNAL
50namespace detail {
51
52// The StyleUnit for a parsed CSS unit. Returns false for a real CSS unit that has no
53// StyleUnit (pt, cm, fr, ...).
54constexpr bool StyleUnitOf(ultralight::detail::CssUnit css_unit, StyleUnit* out) {
55 using ultralight::detail::CssUnit;
56 switch (css_unit) {
57 case CssUnit::None: *out = StyleUnit::Number; return true;
58 case CssUnit::Percent: *out = StyleUnit::Percent; return true;
59 case CssUnit::Px: *out = StyleUnit::Px; return true;
60 case CssUnit::Em: *out = StyleUnit::Em; return true;
61 case CssUnit::Rem: *out = StyleUnit::Rem; return true;
62 case CssUnit::Vw: *out = StyleUnit::Vw; return true;
63 case CssUnit::Vh: *out = StyleUnit::Vh; return true;
64 case CssUnit::Deg: *out = StyleUnit::Deg; return true;
65 case CssUnit::Ms: *out = StyleUnit::Ms; return true;
66 case CssUnit::S: *out = StyleUnit::S; return true;
67 default: return false;
68 }
69}
70
71// The CSS text of a unit ("" for a unitless number and for Empty).
72constexpr const char* StyleUnitText(StyleUnit unit) {
73 switch (unit) {
74 case StyleUnit::Percent: return "%";
75 case StyleUnit::Px: return "px";
76 case StyleUnit::Em: return "em";
77 case StyleUnit::Rem: return "rem";
78 case StyleUnit::Vw: return "vw";
79 case StyleUnit::Vh: return "vh";
80 case StyleUnit::Deg: return "deg";
81 case StyleUnit::Ms: return "ms";
82 case StyleUnit::S: return "s";
83 case StyleUnit::Number:
84 case StyleUnit::Empty: return "";
85 }
86 return "";
87}
88
89} // namespace detail
90/// \endcond
91
92///
93/// A numeric CSS value and its unit.
94///
95/// dom::StyleValue pairs a number with a CSS unit for styling page elements from native code. It
96/// lets you write numeric values directly to an element's inline style without formatting strings,
97/// and read numeric properties back from inline or computed styles.
98///
99/// You can update a health bar with computed values:
100///
101/// ```
102/// dom::Element bar = document.getElementById("health-bar");
103/// bar.style.width = dom::StyleValue::Pct(health * 100);
104/// bar.style.left = dom::StyleValue::Px(x);
105/// bar.style.opacity = 0.8; // a bare number has no unit
106/// ```
107///
108/// The library still parses the value as CSS when applying it to the element.
109///
110/// ## Reading Styles and Value States
111///
112/// Calling AsStyleValue() on an inline or computed style parses the property into a
113/// dom::StyleValue:
114///
115/// ```
116/// dom::StyleValue width = dom::getComputedStyle(bar).width.AsStyleValue();
117/// if (width)
118/// PlaceMinimap(width.value); // 300 when the width is "300px"
119/// ```
120///
121/// A dom::StyleValue is always in one of these states:
122///
123/// - **A number holds a finite value and a StyleUnit.** Writing it sets the property.
124/// - **Empty holds no value.** Writing it removes the inline property.
125/// - **Invalid represents text that doesn't convert to a number (eg, `auto`) or a number that isn't
126/// finite.** Writing it does nothing and keeps the existing style.
127///
128/// Checking a dom::StyleValue with `if (value)` evaluates to true only when it holds a number.
129///
130/// You can copy an inline style directly between elements by assigning what you read:
131///
132/// ```
133/// icon.style.width = badge.style.width.AsStyleValue(); // removes icon's width
134/// // if badge has none
135/// ```
136///
137/// ## Unsupported Units and Custom Properties
138///
139/// - **Properties ignore unsupported units.** Assigning a unit that a property doesn't accept (such
140/// as pixels for `opacity` or a unitless number for `width`) does nothing and keeps the previous
141/// value. When DOM diagnostics are on, the library logs a warning for each ignored write.
142/// - **Custom properties require text.** Assigning a dom::StyleValue to a custom property
143/// (`--name`) does nothing, so assign text like `"8px"` instead.
144///
145/// @see dom::Element::style, dom::getComputedStyle(), dom::StyleUnit, dom::CssValue
146///
148 double value = 0; ///< The number (only meaningful when this holds one).
149 StyleUnit unit = StyleUnit::Empty; ///< The number's unit (StyleUnit::Empty when it's Empty).
150
151 ///
152 /// Create an Empty StyleValue (it holds nothing, and writing it removes the property).
153 ///
154 constexpr StyleValue() = default;
155
156 ///
157 /// Create a unitless number (CSS `<number>`). A bare number converts to this implicitly.
158 ///
159 /// @param value The number.
160 ///
161 constexpr StyleValue(double value) : value(value), unit(StyleUnit::Number) {}
162
163 ///
164 /// Create a number with a unit. The unit factories (eg, Px()) are shorter.
165 ///
166 /// @param value The number.
167 ///
168 /// @param unit The number's unit.
169 ///
170 constexpr StyleValue(double value, StyleUnit unit) : value(value), unit(unit) {}
171
172 ///
173 /// Parse CSS text into a StyleValue.
174 ///
175 /// The text must be a single number with no unit or a StyleUnit unit. Style members and reads
176 /// by name have AsStyleValue(), which does the same.
177 ///
178 /// ```
179 /// StyleValue::Parse("300px"); // 300, StyleUnit::Px
180 /// StyleValue::Parse("0.5"); // 0.5, StyleUnit::Number
181 /// StyleValue::Parse(""); // Empty
182 /// StyleValue::Parse("auto"); // Invalid (a keyword)
183 /// StyleValue::Parse("12pt"); // Invalid (pt isn't a StyleUnit)
184 /// ```
185 ///
186 /// @param text The CSS text.
187 ///
188 /// @return Returns the number with its unit, an Empty StyleValue when `text` is empty, or an
189 /// Invalid one when `text` doesn't convert.
190 ///
191 static constexpr StyleValue Parse(std::string_view text) {
192 if (text.empty())
193 return StyleValue();
194 StyleUnit parsed_unit = StyleUnit::Number;
195 if (ultralight::detail::IsLoneNumericToken(text.data(), text.size())) {
196 const ultralight::detail::CssNumber num
197 = ultralight::detail::ParseCssNumber(text.data(), text.size());
198 if (num.status == ultralight::detail::CssStatus::Ok
199 && detail::StyleUnitOf(num.unit, &parsed_unit))
200 return StyleValue(num.value, parsed_unit);
201 }
202 return StyleValue(std::numeric_limits<double>::quiet_NaN());
203 }
204
205 ///
206 /// Whether or not this StyleValue is Empty (it holds nothing).
207 ///
208 constexpr bool IsEmpty() const { return unit == StyleUnit::Empty; }
209
210 ///
211 /// Whether or not this StyleValue is Invalid (it came from text that doesn't convert, or its
212 /// value isn't finite).
213 ///
214 constexpr bool IsInvalid() const { return !IsEmpty() && !IsFinite(value); }
215
216 ///
217 /// Whether or not this StyleValue holds a number (it's neither Empty nor Invalid).
218 ///
219 constexpr explicit operator bool() const { return !IsEmpty() && IsFinite(value); }
220
221 ///
222 /// Create a unitless number (CSS `<number>`).
223 ///
224 /// @param v The number.
225 ///
226 static constexpr StyleValue Number(double v) { return { v, StyleUnit::Number }; }
227
228 ///
229 /// Create a length in CSS pixels (`px`).
230 ///
231 /// @param v The number of pixels.
232 ///
233 static constexpr StyleValue Px(double v) { return { v, StyleUnit::Px }; }
234
235 ///
236 /// Create a percentage (`%`).
237 ///
238 /// @param v The percentage (50 means 50%).
239 ///
240 static constexpr StyleValue Pct(double v) { return { v, StyleUnit::Percent }; }
241
242 ///
243 /// Create a multiple of the element's font size (`em`).
244 ///
245 /// @param v The multiple.
246 ///
247 static constexpr StyleValue Em(double v) { return { v, StyleUnit::Em }; }
248
249 ///
250 /// Create a multiple of the root element's font size (`rem`).
251 ///
252 /// @param v The multiple.
253 ///
254 static constexpr StyleValue Rem(double v) { return { v, StyleUnit::Rem }; }
255
256 ///
257 /// Create a percentage of the viewport width (`vw`).
258 ///
259 /// @param v The percentage (100 is the full width).
260 ///
261 static constexpr StyleValue Vw(double v) { return { v, StyleUnit::Vw }; }
262
263 ///
264 /// Create a percentage of the viewport height (`vh`).
265 ///
266 /// @param v The percentage (100 is the full height).
267 ///
268 static constexpr StyleValue Vh(double v) { return { v, StyleUnit::Vh }; }
269
270 ///
271 /// Create an angle in degrees (`deg`).
272 ///
273 /// @param v The number of degrees.
274 ///
275 static constexpr StyleValue Deg(double v) { return { v, StyleUnit::Deg }; }
276
277 ///
278 /// Create a time in milliseconds (`ms`).
279 ///
280 /// @param v The number of milliseconds.
281 ///
282 static constexpr StyleValue Ms(double v) { return { v, StyleUnit::Ms }; }
283
284 ///
285 /// Create a time in seconds (`s`).
286 ///
287 /// @param v The number of seconds.
288 ///
289 static constexpr StyleValue S(double v) { return { v, StyleUnit::S }; }
290
291 private:
292 static constexpr bool IsFinite(double v) {
293 return v >= -std::numeric_limits<double>::max() && v <= std::numeric_limits<double>::max();
294 }
295};
296
297///
298/// Compile-time wrapper for CSS string literals assigned to element styles.
299///
300/// CssValue wraps CSS string literals assigned to inline styles, which the library parses when
301/// written. It checks the literal at compile time to catch mistyped numbers and units before your
302/// code runs.
303///
304/// String literals convert to CssValue on their own when assigned to style properties:
305///
306/// ```
307/// hud.style.width = "50%"; // a number: checked when you compile
308/// hud.style.margin = "0 auto"; // other CSS: parsed when it's written
309/// hud.style.fontSize = "12pt"; // a unit StyleValue doesn't have: parsed when it's written
310/// // hud.style.width = "50pxx"; // a typo: compile error
311/// ```
312///
313/// ## Strings Built at Run Time
314///
315/// Style properties provide a separate overload for dynamic values, parsing the CSS text when it's
316/// written.
317///
318/// Wrap a char array filled at run time in std::string_view so the compiler doesn't treat the
319/// buffer as a string literal and fail to compile:
320///
321/// ```
322/// char text[16];
323/// std::snprintf(text, sizeof(text), "%dpx", bar_width);
324/// hud.style.width = std::string_view(text);
325/// ```
326///
327/// @note On older MSVC toolsets (before Visual Studio 2026), a mistyped literal causes a compile
328/// error only in a constant expression. In ordinary style writes, it's ignored when written.
329///
330/// @see dom::StyleValue, dom::Element::style
331///
332class CssValue {
333 public:
334 ///
335 /// How the literal is written-- either parsed as a number at compile time (Numeric) or passed
336 /// to the library's CSS parser as text (Passthrough).
337 ///
338 enum class Kind : uint8_t { Passthrough = 0, Numeric };
339
340 ///
341 /// Capture a string literal and parse it at compile time.
342 ///
343 /// @param s The string literal.
344 ///
345 template <size_t N>
346 UL_CSS_CONSTEVAL CssValue(const char (&s)[N]) : text_(s), length_(N - 1) {
347 if (!ultralight::detail::IsLoneNumericToken(s, N - 1))
348 return;
349 const ultralight::detail::CssNumber num = ultralight::detail::ParseCssNumber(s, N - 1);
350 if (num.status != ultralight::detail::CssStatus::Ok) {
351 // A lone numeric token that fails strict parsing is a typo, not a compound value.
352 // On the UL_CSS_CONSTEVAL fallback arm it degrades to passthrough at runtime,
353 // where the engine's CSS parser rejects it with the web's own silence.
354 if (std::is_constant_evaluated())
355 ultralight::detail::RaiseCssError(num.status);
356 return;
357 }
358 // A real CSS unit with no StyleUnit (pt, cm, fr, ...) is valid CSS, carried as
359 // passthrough text.
360 StyleUnit style_unit = StyleUnit::Number;
361 if (detail::StyleUnitOf(num.unit, &style_unit))
362 SetNumeric(num.value, style_unit);
363 }
364
365 ///
366 /// Get how the literal is written (see Kind).
367 ///
368 constexpr Kind kind() const { return kind_; }
369
370 ///
371 /// Get the parsed number (only meaningful when kind() is Kind::Numeric).
372 ///
373 constexpr double value() const { return value_; }
374
375 ///
376 /// Get the parsed unit (only meaningful when kind() is Kind::Numeric).
377 ///
378 constexpr StyleUnit unit() const { return unit_; }
379
380 ///
381 /// Get the literal's text as written (null-terminated).
382 ///
383 constexpr const char* text() const { return text_; }
384
385 ///
386 /// Get the literal's length in bytes.
387 ///
388 constexpr size_t length() const { return length_; }
389
390 private:
391 constexpr void SetNumeric(double value, StyleUnit unit) {
392 kind_ = Kind::Numeric;
393 value_ = value;
394 unit_ = unit;
395 }
396
397 const char* text_ = nullptr;
398 size_t length_ = 0;
399 double value_ = 0.0;
401 Kind kind_ = Kind::Passthrough;
402};
403
404} // namespace dom
405} // namespace ultralight
406
407#pragma pop_macro("None")
constexpr const char * text() const
Get the literal's text as written (null-terminated).
Definition StyleValue.h:383
constexpr double value() const
Get the parsed number (only meaningful when kind() is Kind::Numeric).
Definition StyleValue.h:373
constexpr Kind kind() const
Get how the literal is written (see Kind).
Definition StyleValue.h:368
constexpr size_t length() const
Get the literal's length in bytes.
Definition StyleValue.h:388
Kind
How the literal is written– either parsed as a number at compile time (Numeric) or passed to the libr...
Definition StyleValue.h:338
@ Numeric
Definition StyleValue.h:338
@ Passthrough
Definition StyleValue.h:338
UL_CSS_CONSTEVAL CssValue(const char(&s)[N])
Capture a string literal and parse it at compile time.
Definition StyleValue.h:346
constexpr StyleUnit unit() const
Get the parsed unit (only meaningful when kind() is Kind::Numeric).
Definition StyleValue.h:378
Direct C++ access to modify page elements and handle events.
StyleUnit
The unit of a StyleValue (the same values as the C API's ULDOMStyleUnit).
Definition StyleValue.h:22
@ S
s
Definition StyleValue.h:32
@ Em
em
Definition StyleValue.h:26
@ Ms
ms
Definition StyleValue.h:31
@ Vw
vw
Definition StyleValue.h:28
@ Percent
%
Definition StyleValue.h:24
@ Number
A unitless number (CSS <number>).
Definition StyleValue.h:23
@ Deg
deg
Definition StyleValue.h:30
@ Empty
No number (an Empty StyleValue; writing it removes the property).
Definition StyleValue.h:33
@ Rem
rem
Definition StyleValue.h:27
@ Vh
vh
Definition StyleValue.h:29
@ Px
px
Definition StyleValue.h:25
Root namespace for every public Ultralight type, function, and enumeration.
@ Numeric
Definition Editor.h:113
static constexpr StyleValue Vw(double v)
Create a percentage of the viewport width (vw).
Definition StyleValue.h:261
static constexpr StyleValue Ms(double v)
Create a time in milliseconds (ms).
Definition StyleValue.h:282
static constexpr StyleValue Rem(double v)
Create a multiple of the root element's font size (rem).
Definition StyleValue.h:254
constexpr StyleValue(double value)
Create a unitless number (CSS <number>).
Definition StyleValue.h:161
constexpr bool IsEmpty() const
Whether or not this StyleValue is Empty (it holds nothing).
Definition StyleValue.h:208
constexpr bool IsInvalid() const
Whether or not this StyleValue is Invalid (it came from text that doesn't convert,...
Definition StyleValue.h:214
static constexpr StyleValue Em(double v)
Create a multiple of the element's font size (em).
Definition StyleValue.h:247
static constexpr StyleValue S(double v)
Create a time in seconds (s).
Definition StyleValue.h:289
constexpr StyleValue(double value, StyleUnit unit)
Create a number with a unit.
Definition StyleValue.h:170
constexpr StyleValue()=default
Create an Empty StyleValue (it holds nothing, and writing it removes the property).
StyleUnit unit
The number's unit (StyleUnit::Empty when it's Empty).
Definition StyleValue.h:149
static constexpr StyleValue Number(double v)
Create a unitless number (CSS <number>).
Definition StyleValue.h:226
static constexpr StyleValue Deg(double v)
Create an angle in degrees (deg).
Definition StyleValue.h:275
static constexpr StyleValue Pct(double v)
Create a percentage (%).
Definition StyleValue.h:240
static constexpr StyleValue Vh(double v)
Create a percentage of the viewport height (vh).
Definition StyleValue.h:268
static constexpr StyleValue Px(double v)
Create a length in CSS pixels (px).
Definition StyleValue.h:233
static constexpr StyleValue Parse(std::string_view text)
Parse CSS text into a StyleValue.
Definition StyleValue.h:191
double value
The number (only meaningful when this holds one).
Definition StyleValue.h:148