Coverage Report

Created: 2025-09-05 06:52

/src/serenity/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org>
3
 * Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <AK/TypeCasts.h>
9
#include <LibCore/DirIterator.h>
10
#include <LibWeb/CSS/Clip.h>
11
#include <LibWeb/CSS/StyleProperties.h>
12
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
13
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
14
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
15
#include <LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h>
16
#include <LibWeb/CSS/StyleValues/CounterStyleValue.h>
17
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
18
#include <LibWeb/CSS/StyleValues/GridAutoFlowStyleValue.h>
19
#include <LibWeb/CSS/StyleValues/GridTemplateAreaStyleValue.h>
20
#include <LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h>
21
#include <LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h>
22
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
23
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
24
#include <LibWeb/CSS/StyleValues/MathDepthStyleValue.h>
25
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
26
#include <LibWeb/CSS/StyleValues/OpenTypeTaggedStyleValue.h>
27
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
28
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
29
#include <LibWeb/CSS/StyleValues/RectStyleValue.h>
30
#include <LibWeb/CSS/StyleValues/RotationStyleValue.h>
31
#include <LibWeb/CSS/StyleValues/ScrollbarGutterStyleValue.h>
32
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
33
#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
34
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
35
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
36
#include <LibWeb/Layout/BlockContainer.h>
37
#include <LibWeb/Layout/Node.h>
38
#include <LibWeb/Platform/FontPlugin.h>
39
40
namespace Web::CSS {
41
42
NonnullRefPtr<StyleProperties::Data> StyleProperties::Data::clone() const
43
0
{
44
0
    auto clone = adopt_ref(*new StyleProperties::Data);
45
0
    clone->m_animation_name_source = m_animation_name_source;
46
0
    clone->m_transition_property_source = m_transition_property_source;
47
0
    clone->m_property_values = m_property_values;
48
0
    clone->m_property_important = m_property_important;
49
0
    clone->m_property_inherited = m_property_inherited;
50
0
    clone->m_animated_property_values = m_animated_property_values;
51
0
    clone->m_math_depth = m_math_depth;
52
0
    clone->m_font_list = m_font_list;
53
0
    clone->m_line_height = m_line_height;
54
0
    return clone;
55
0
}
56
57
NonnullRefPtr<StyleProperties> StyleProperties::clone() const
58
0
{
59
0
    auto cloned = adopt_ref(*new StyleProperties);
60
0
    cloned->m_data = m_data;
61
0
    return cloned;
62
0
}
63
64
bool StyleProperties::is_property_important(CSS::PropertyID property_id) const
65
0
{
66
0
    size_t n = to_underlying(property_id);
67
0
    return m_data->m_property_important[n / 8] & (1 << (n % 8));
68
0
}
69
70
void StyleProperties::set_property_important(CSS::PropertyID property_id, Important important)
71
0
{
72
0
    size_t n = to_underlying(property_id);
73
0
    if (important == Important::Yes)
74
0
        m_data->m_property_important[n / 8] |= (1 << (n % 8));
75
0
    else
76
0
        m_data->m_property_important[n / 8] &= ~(1 << (n % 8));
77
0
}
78
79
bool StyleProperties::is_property_inherited(CSS::PropertyID property_id) const
80
0
{
81
0
    size_t n = to_underlying(property_id);
82
0
    return m_data->m_property_inherited[n / 8] & (1 << (n % 8));
83
0
}
84
85
void StyleProperties::set_property_inherited(CSS::PropertyID property_id, Inherited inherited)
86
0
{
87
0
    size_t n = to_underlying(property_id);
88
0
    if (inherited == Inherited::Yes)
89
0
        m_data->m_property_inherited[n / 8] |= (1 << (n % 8));
90
0
    else
91
0
        m_data->m_property_inherited[n / 8] &= ~(1 << (n % 8));
92
0
}
93
94
void StyleProperties::set_property(CSS::PropertyID id, NonnullRefPtr<CSSStyleValue const> value, Inherited inherited, Important important)
95
0
{
96
0
    m_data->m_property_values[to_underlying(id)] = move(value);
97
0
    set_property_important(id, important);
98
0
    set_property_inherited(id, inherited);
99
0
}
100
101
void StyleProperties::revert_property(CSS::PropertyID id, StyleProperties const& style_for_revert)
102
0
{
103
0
    m_data->m_property_values[to_underlying(id)] = style_for_revert.m_data->m_property_values[to_underlying(id)];
104
0
    set_property_important(id, style_for_revert.is_property_important(id) ? Important::Yes : Important::No);
105
0
    set_property_inherited(id, style_for_revert.is_property_inherited(id) ? Inherited::Yes : Inherited::No);
106
0
}
107
108
void StyleProperties::set_animated_property(CSS::PropertyID id, NonnullRefPtr<CSSStyleValue const> value)
109
0
{
110
0
    m_data->m_animated_property_values.set(id, move(value));
111
0
}
112
113
void StyleProperties::reset_animated_properties()
114
0
{
115
0
    m_data->m_animated_property_values.clear();
116
0
}
117
118
NonnullRefPtr<CSSStyleValue const> StyleProperties::property(CSS::PropertyID property_id, WithAnimationsApplied return_animated_value) const
119
0
{
120
0
    if (return_animated_value == WithAnimationsApplied::Yes) {
121
0
        if (auto animated_value = m_data->m_animated_property_values.get(property_id).value_or(nullptr))
122
0
            return *animated_value;
123
0
    }
124
125
    // By the time we call this method, all properties have values assigned.
126
0
    return *m_data->m_property_values[to_underlying(property_id)];
127
0
}
128
129
RefPtr<CSSStyleValue const> StyleProperties::maybe_null_property(CSS::PropertyID property_id) const
130
0
{
131
0
    if (auto animated_value = m_data->m_animated_property_values.get(property_id).value_or(nullptr))
132
0
        return *animated_value;
133
0
    return m_data->m_property_values[to_underlying(property_id)];
134
0
}
135
136
CSS::Size StyleProperties::size_value(CSS::PropertyID id) const
137
0
{
138
0
    auto value = property(id);
139
0
    if (value->is_keyword()) {
140
0
        switch (value->to_keyword()) {
141
0
        case Keyword::Auto:
142
0
            return CSS::Size::make_auto();
143
0
        case Keyword::MinContent:
144
0
            return CSS::Size::make_min_content();
145
0
        case Keyword::MaxContent:
146
0
            return CSS::Size::make_max_content();
147
0
        case Keyword::FitContent:
148
0
            return CSS::Size::make_fit_content();
149
0
        case Keyword::None:
150
0
            return CSS::Size::make_none();
151
0
        default:
152
0
            VERIFY_NOT_REACHED();
153
0
        }
154
0
    }
155
156
0
    if (value->is_math())
157
0
        return CSS::Size::make_calculated(const_cast<CSSMathValue&>(value->as_math()));
158
159
0
    if (value->is_percentage())
160
0
        return CSS::Size::make_percentage(value->as_percentage().percentage());
161
162
0
    if (value->is_length()) {
163
0
        auto length = value->as_length().length();
164
0
        if (length.is_auto())
165
0
            return CSS::Size::make_auto();
166
0
        return CSS::Size::make_length(length);
167
0
    }
168
169
    // FIXME: Support `fit-content(<length>)`
170
0
    dbgln("FIXME: Unsupported size value: `{}`, treating as `auto`", value->to_string());
171
0
    return CSS::Size::make_auto();
172
0
}
173
174
LengthPercentage StyleProperties::length_percentage_or_fallback(CSS::PropertyID id, LengthPercentage const& fallback) const
175
0
{
176
0
    return length_percentage(id).value_or(fallback);
177
0
}
178
179
Optional<LengthPercentage> StyleProperties::length_percentage(CSS::PropertyID id) const
180
0
{
181
0
    auto value = property(id);
182
183
0
    if (value->is_math())
184
0
        return LengthPercentage { const_cast<CSSMathValue&>(value->as_math()) };
185
186
0
    if (value->is_percentage())
187
0
        return value->as_percentage().percentage();
188
189
0
    if (value->is_length())
190
0
        return value->as_length().length();
191
192
0
    if (value->has_auto())
193
0
        return LengthPercentage { Length::make_auto() };
194
195
0
    return {};
196
0
}
197
198
LengthBox StyleProperties::length_box(CSS::PropertyID left_id, CSS::PropertyID top_id, CSS::PropertyID right_id, CSS::PropertyID bottom_id, const CSS::Length& default_value) const
199
0
{
200
0
    LengthBox box;
201
0
    box.left() = length_percentage_or_fallback(left_id, default_value);
202
0
    box.top() = length_percentage_or_fallback(top_id, default_value);
203
0
    box.right() = length_percentage_or_fallback(right_id, default_value);
204
0
    box.bottom() = length_percentage_or_fallback(bottom_id, default_value);
205
0
    return box;
206
0
}
207
208
Color StyleProperties::color_or_fallback(CSS::PropertyID id, Layout::NodeWithStyle const& node, Color fallback) const
209
0
{
210
0
    auto value = property(id);
211
0
    if (!value->has_color())
212
0
        return fallback;
213
0
    return value->to_color(node);
214
0
}
215
216
NonnullRefPtr<Gfx::Font const> StyleProperties::font_fallback(bool monospace, bool bold)
217
0
{
218
0
    if (monospace && bold)
219
0
        return Platform::FontPlugin::the().default_fixed_width_font().bold_variant();
220
221
0
    if (monospace)
222
0
        return Platform::FontPlugin::the().default_fixed_width_font();
223
224
0
    if (bold)
225
0
        return Platform::FontPlugin::the().default_font().bold_variant();
226
227
0
    return Platform::FontPlugin::the().default_font();
228
0
}
229
230
CSSPixels StyleProperties::compute_line_height(CSSPixelRect const& viewport_rect, Length::FontMetrics const& font_metrics, Length::FontMetrics const& root_font_metrics) const
231
0
{
232
0
    auto line_height = property(CSS::PropertyID::LineHeight);
233
234
0
    if (line_height->is_keyword() && line_height->to_keyword() == Keyword::Normal)
235
0
        return font_metrics.line_height;
236
237
0
    if (line_height->is_length()) {
238
0
        auto line_height_length = line_height->as_length().length();
239
0
        if (!line_height_length.is_auto())
240
0
            return line_height_length.to_px(viewport_rect, font_metrics, root_font_metrics);
241
0
    }
242
243
0
    if (line_height->is_number())
244
0
        return Length(line_height->as_number().number(), Length::Type::Em).to_px(viewport_rect, font_metrics, root_font_metrics);
245
246
0
    if (line_height->is_percentage()) {
247
        // Percentages are relative to 1em. https://www.w3.org/TR/css-inline-3/#valdef-line-height-percentage
248
0
        auto& percentage = line_height->as_percentage().percentage();
249
0
        return Length(percentage.as_fraction(), Length::Type::Em).to_px(viewport_rect, font_metrics, root_font_metrics);
250
0
    }
251
252
0
    if (line_height->is_math()) {
253
0
        if (line_height->as_math().resolves_to_number()) {
254
0
            auto resolved = line_height->as_math().resolve_number();
255
0
            if (!resolved.has_value()) {
256
0
                dbgln("FIXME: Failed to resolve calc() line-height (number): {}", line_height->as_math().to_string());
257
0
                return CSSPixels::nearest_value_for(m_data->m_font_list->first().pixel_metrics().line_spacing());
258
0
            }
259
0
            return Length(resolved.value(), Length::Type::Em).to_px(viewport_rect, font_metrics, root_font_metrics);
260
0
        }
261
262
0
        auto resolved = line_height->as_math().resolve_length(Length::ResolutionContext { viewport_rect, font_metrics, root_font_metrics });
263
0
        if (!resolved.has_value()) {
264
0
            dbgln("FIXME: Failed to resolve calc() line-height: {}", line_height->as_math().to_string());
265
0
            return CSSPixels::nearest_value_for(m_data->m_font_list->first().pixel_metrics().line_spacing());
266
0
        }
267
0
        return resolved->to_px(viewport_rect, font_metrics, root_font_metrics);
268
0
    }
269
270
0
    return font_metrics.line_height;
271
0
}
272
273
Optional<int> StyleProperties::z_index() const
274
0
{
275
0
    auto value = property(CSS::PropertyID::ZIndex);
276
0
    if (value->has_auto())
277
0
        return {};
278
0
    if (value->is_integer()) {
279
        // Clamp z-index to the range of a signed 32-bit integer for consistency with other engines.
280
0
        auto integer = value->as_integer().integer();
281
0
        if (integer >= NumericLimits<int>::max())
282
0
            return NumericLimits<int>::max();
283
0
        if (integer <= NumericLimits<int>::min())
284
0
            return NumericLimits<int>::min();
285
0
        return static_cast<int>(integer);
286
0
    }
287
0
    return {};
288
0
}
289
290
float StyleProperties::resolve_opacity_value(CSSStyleValue const& value)
291
0
{
292
0
    float unclamped_opacity = 1.0f;
293
294
0
    if (value.is_number()) {
295
0
        unclamped_opacity = value.as_number().number();
296
0
    } else if (value.is_math()) {
297
0
        auto& calculated = value.as_math();
298
0
        if (calculated.resolves_to_percentage()) {
299
0
            auto maybe_percentage = value.as_math().resolve_percentage();
300
0
            if (maybe_percentage.has_value())
301
0
                unclamped_opacity = maybe_percentage->as_fraction();
302
0
            else
303
0
                dbgln("Unable to resolve calc() as opacity (percentage): {}", value.to_string());
304
0
        } else if (calculated.resolves_to_number()) {
305
0
            auto maybe_number = const_cast<CSSMathValue&>(value.as_math()).resolve_number();
306
0
            if (maybe_number.has_value())
307
0
                unclamped_opacity = maybe_number.value();
308
0
            else
309
0
                dbgln("Unable to resolve calc() as opacity (number): {}", value.to_string());
310
0
        }
311
0
    } else if (value.is_percentage()) {
312
0
        unclamped_opacity = value.as_percentage().percentage().as_fraction();
313
0
    }
314
315
0
    return clamp(unclamped_opacity, 0.0f, 1.0f);
316
0
}
317
318
float StyleProperties::opacity() const
319
0
{
320
0
    auto value = property(CSS::PropertyID::Opacity);
321
0
    return resolve_opacity_value(*value);
322
0
}
323
324
float StyleProperties::fill_opacity() const
325
0
{
326
0
    auto value = property(CSS::PropertyID::FillOpacity);
327
0
    return resolve_opacity_value(*value);
328
0
}
329
330
Optional<CSS::StrokeLinecap> StyleProperties::stroke_linecap() const
331
0
{
332
0
    auto value = property(CSS::PropertyID::StrokeLinecap);
333
0
    return keyword_to_stroke_linecap(value->to_keyword());
334
0
}
335
336
Optional<CSS::StrokeLinejoin> StyleProperties::stroke_linejoin() const
337
0
{
338
0
    auto value = property(CSS::PropertyID::StrokeLinejoin);
339
0
    return keyword_to_stroke_linejoin(value->to_keyword());
340
0
}
341
342
NumberOrCalculated StyleProperties::stroke_miterlimit() const
343
0
{
344
0
    auto value = property(CSS::PropertyID::StrokeMiterlimit);
345
346
0
    if (value->is_math()) {
347
0
        auto const& math_value = value->as_math();
348
0
        VERIFY(math_value.resolves_to_number());
349
0
        return NumberOrCalculated { math_value };
350
0
    }
351
352
0
    return NumberOrCalculated { value->as_number().number() };
353
0
}
354
355
float StyleProperties::stroke_opacity() const
356
0
{
357
0
    auto value = property(CSS::PropertyID::StrokeOpacity);
358
0
    return resolve_opacity_value(*value);
359
0
}
360
361
float StyleProperties::stop_opacity() const
362
0
{
363
0
    auto value = property(CSS::PropertyID::StopOpacity);
364
0
    return resolve_opacity_value(*value);
365
0
}
366
367
Optional<CSS::FillRule> StyleProperties::fill_rule() const
368
0
{
369
0
    auto value = property(CSS::PropertyID::FillRule);
370
0
    return keyword_to_fill_rule(value->to_keyword());
371
0
}
372
373
Optional<CSS::ClipRule> StyleProperties::clip_rule() const
374
0
{
375
0
    auto value = property(CSS::PropertyID::ClipRule);
376
0
    return keyword_to_fill_rule(value->to_keyword());
377
0
}
378
379
Optional<CSS::FlexDirection> StyleProperties::flex_direction() const
380
0
{
381
0
    auto value = property(CSS::PropertyID::FlexDirection);
382
0
    return keyword_to_flex_direction(value->to_keyword());
383
0
}
384
385
Optional<CSS::FlexWrap> StyleProperties::flex_wrap() const
386
0
{
387
0
    auto value = property(CSS::PropertyID::FlexWrap);
388
0
    return keyword_to_flex_wrap(value->to_keyword());
389
0
}
390
391
Optional<CSS::FlexBasis> StyleProperties::flex_basis() const
392
0
{
393
0
    auto value = property(CSS::PropertyID::FlexBasis);
394
395
0
    if (value->is_keyword() && value->to_keyword() == CSS::Keyword::Content)
396
0
        return CSS::FlexBasisContent {};
397
398
0
    return size_value(CSS::PropertyID::FlexBasis);
399
0
}
400
401
float StyleProperties::flex_grow() const
402
0
{
403
0
    auto value = property(CSS::PropertyID::FlexGrow);
404
0
    if (!value->is_number())
405
0
        return 0;
406
0
    return value->as_number().number();
407
0
}
408
409
float StyleProperties::flex_shrink() const
410
0
{
411
0
    auto value = property(CSS::PropertyID::FlexShrink);
412
0
    if (!value->is_number())
413
0
        return 1;
414
0
    return value->as_number().number();
415
0
}
416
417
int StyleProperties::order() const
418
0
{
419
0
    auto value = property(CSS::PropertyID::Order);
420
0
    if (!value->is_integer())
421
0
        return 0;
422
0
    return value->as_integer().integer();
423
0
}
424
425
Optional<CSS::ImageRendering> StyleProperties::image_rendering() const
426
0
{
427
0
    auto value = property(CSS::PropertyID::ImageRendering);
428
0
    return keyword_to_image_rendering(value->to_keyword());
429
0
}
430
431
CSS::Length StyleProperties::border_spacing_horizontal(Layout::Node const& layout_node) const
432
0
{
433
0
    auto value = property(CSS::PropertyID::BorderSpacing);
434
0
    if (value->is_length())
435
0
        return value->as_length().length();
436
0
    if (value->is_math())
437
0
        return value->as_math().resolve_length(layout_node).value_or(CSS::Length(0, CSS::Length::Type::Px));
438
0
    auto const& list = value->as_value_list();
439
0
    return list.value_at(0, false)->as_length().length();
440
0
}
441
442
CSS::Length StyleProperties::border_spacing_vertical(Layout::Node const& layout_node) const
443
0
{
444
0
    auto value = property(CSS::PropertyID::BorderSpacing);
445
0
    if (value->is_length())
446
0
        return value->as_length().length();
447
0
    if (value->is_math())
448
0
        return value->as_math().resolve_length(layout_node).value_or(CSS::Length(0, CSS::Length::Type::Px));
449
0
    auto const& list = value->as_value_list();
450
0
    return list.value_at(1, false)->as_length().length();
451
0
}
452
453
Optional<CSS::CaptionSide> StyleProperties::caption_side() const
454
0
{
455
0
    auto value = property(CSS::PropertyID::CaptionSide);
456
0
    return keyword_to_caption_side(value->to_keyword());
457
0
}
458
459
CSS::Clip StyleProperties::clip() const
460
0
{
461
0
    auto value = property(CSS::PropertyID::Clip);
462
0
    if (!value->is_rect())
463
0
        return CSS::Clip::make_auto();
464
0
    return CSS::Clip(value->as_rect().rect());
465
0
}
466
467
Optional<CSS::JustifyContent> StyleProperties::justify_content() const
468
0
{
469
0
    auto value = property(CSS::PropertyID::JustifyContent);
470
0
    return keyword_to_justify_content(value->to_keyword());
471
0
}
472
473
Optional<CSS::JustifyItems> StyleProperties::justify_items() const
474
0
{
475
0
    auto value = property(CSS::PropertyID::JustifyItems);
476
0
    return keyword_to_justify_items(value->to_keyword());
477
0
}
478
479
Optional<CSS::JustifySelf> StyleProperties::justify_self() const
480
0
{
481
0
    auto value = property(CSS::PropertyID::JustifySelf);
482
0
    return keyword_to_justify_self(value->to_keyword());
483
0
}
484
485
Vector<CSS::Transformation> StyleProperties::transformations_for_style_value(CSSStyleValue const& value)
486
0
{
487
0
    if (value.is_keyword() && value.to_keyword() == CSS::Keyword::None)
488
0
        return {};
489
490
0
    if (!value.is_value_list())
491
0
        return {};
492
493
0
    auto& list = value.as_value_list();
494
495
0
    Vector<CSS::Transformation> transformations;
496
497
0
    for (auto& it : list.values()) {
498
0
        if (!it->is_transformation())
499
0
            return {};
500
0
        auto& transformation_style_value = it->as_transformation();
501
0
        auto function = transformation_style_value.transform_function();
502
0
        auto function_metadata = transform_function_metadata(function);
503
0
        Vector<TransformValue> values;
504
0
        size_t argument_index = 0;
505
0
        for (auto& transformation_value : transformation_style_value.values()) {
506
0
            if (transformation_value->is_math()) {
507
0
                auto& calculated = transformation_value->as_math();
508
0
                if (calculated.resolves_to_length_percentage()) {
509
0
                    values.append(CSS::LengthPercentage { calculated });
510
0
                } else if (calculated.resolves_to_percentage()) {
511
                    // FIXME: Maybe transform this for loop to always check the metadata for the correct types
512
0
                    if (function_metadata.parameters[argument_index].type == TransformFunctionParameterType::NumberPercentage) {
513
0
                        values.append(NumberPercentage { calculated.resolve_percentage().value() });
514
0
                    } else {
515
0
                        values.append(LengthPercentage { calculated.resolve_percentage().value() });
516
0
                    }
517
0
                } else if (calculated.resolves_to_number()) {
518
0
                    values.append({ Number(Number::Type::Number, calculated.resolve_number().value()) });
519
0
                } else if (calculated.resolves_to_angle()) {
520
0
                    values.append({ calculated.resolve_angle().value() });
521
0
                } else {
522
0
                    dbgln("FIXME: Unsupported calc value in transform! {}", calculated.to_string());
523
0
                }
524
0
            } else if (transformation_value->is_length()) {
525
0
                values.append({ transformation_value->as_length().length() });
526
0
            } else if (transformation_value->is_percentage()) {
527
0
                if (function_metadata.parameters[argument_index].type == TransformFunctionParameterType::NumberPercentage) {
528
0
                    values.append(NumberPercentage { transformation_value->as_percentage().percentage() });
529
0
                } else {
530
0
                    values.append(LengthPercentage { transformation_value->as_percentage().percentage() });
531
0
                }
532
0
            } else if (transformation_value->is_number()) {
533
0
                values.append({ Number(Number::Type::Number, transformation_value->as_number().number()) });
534
0
            } else if (transformation_value->is_angle()) {
535
0
                values.append({ transformation_value->as_angle().angle() });
536
0
            } else {
537
0
                dbgln("FIXME: Unsupported value in transform! {}", transformation_value->to_string());
538
0
            }
539
0
            argument_index++;
540
0
        }
541
0
        transformations.empend(function, move(values));
542
0
    }
543
0
    return transformations;
544
0
}
545
546
Vector<CSS::Transformation> StyleProperties::transformations() const
547
0
{
548
0
    return transformations_for_style_value(property(CSS::PropertyID::Transform));
549
0
}
550
551
Optional<CSS::Transformation> StyleProperties::rotate(Layout::Node const& layout_node) const
552
0
{
553
0
    auto value = property(CSS::PropertyID::Rotate);
554
0
    if (!value->is_rotation())
555
0
        return {};
556
0
    auto& rotation = value->as_rotation();
557
558
0
    auto resolve_angle = [&layout_node](CSSStyleValue const& value) -> Optional<Angle> {
559
0
        if (value.is_angle())
560
0
            return value.as_angle().angle();
561
0
        if (value.is_math() && value.as_math().resolves_to_angle())
562
0
            return value.as_math().resolve_angle(layout_node);
563
0
        return {};
564
0
    };
565
566
0
    auto resolve_number = [&](CSSStyleValue const& value) -> Optional<double> {
567
0
        if (value.is_number())
568
0
            return value.as_number().number();
569
0
        if (value.is_math() && value.as_math().resolves_to_number())
570
0
            return value.as_math().resolve_number();
571
0
        return {};
572
0
    };
573
574
0
    auto x = resolve_number(rotation.rotation_x()).value_or(0);
575
0
    auto y = resolve_number(rotation.rotation_y()).value_or(0);
576
0
    auto z = resolve_number(rotation.rotation_z()).value_or(0);
577
0
    auto angle = resolve_angle(rotation.angle()).value_or(Angle::make_degrees(0));
578
579
0
    Vector<TransformValue> values;
580
0
    values.append({ Number(Number::Type::Number, x) });
581
0
    values.append({ Number(Number::Type::Number, y) });
582
0
    values.append({ Number(Number::Type::Number, z) });
583
0
    values.append({ angle });
584
585
0
    return CSS::Transformation(CSS::TransformFunction::Rotate3d, move(values));
586
0
}
587
588
static Optional<LengthPercentage> length_percentage_for_style_value(CSSStyleValue const& value)
589
0
{
590
0
    if (value.is_length())
591
0
        return value.as_length().length();
592
0
    if (value.is_percentage())
593
0
        return value.as_percentage().percentage();
594
0
    if (value.is_math())
595
0
        return LengthPercentage { const_cast<CSSMathValue&>(value.as_math()) };
596
0
    return {};
597
0
}
598
599
Optional<CSS::TransformBox> StyleProperties::transform_box() const
600
0
{
601
0
    auto value = property(CSS::PropertyID::TransformBox);
602
0
    return keyword_to_transform_box(value->to_keyword());
603
0
}
604
605
CSS::TransformOrigin StyleProperties::transform_origin() const
606
0
{
607
0
    auto value = property(CSS::PropertyID::TransformOrigin);
608
0
    if (!value->is_value_list() || value->as_value_list().size() != 2)
609
0
        return {};
610
0
    auto const& list = value->as_value_list();
611
0
    auto x_value = length_percentage_for_style_value(list.values()[0]);
612
0
    auto y_value = length_percentage_for_style_value(list.values()[1]);
613
0
    if (!x_value.has_value() || !y_value.has_value()) {
614
0
        return {};
615
0
    }
616
0
    return { x_value.value(), y_value.value() };
617
0
}
618
619
Optional<Color> StyleProperties::accent_color(Layout::NodeWithStyle const& node) const
620
0
{
621
0
    auto value = property(CSS::PropertyID::AccentColor);
622
0
    if (value->has_color())
623
0
        return value->to_color(node);
624
0
    return {};
625
0
}
626
627
Optional<CSS::AlignContent> StyleProperties::align_content() const
628
0
{
629
0
    auto value = property(CSS::PropertyID::AlignContent);
630
0
    return keyword_to_align_content(value->to_keyword());
631
0
}
632
633
Optional<CSS::AlignItems> StyleProperties::align_items() const
634
0
{
635
0
    auto value = property(CSS::PropertyID::AlignItems);
636
0
    return keyword_to_align_items(value->to_keyword());
637
0
}
638
639
Optional<CSS::AlignSelf> StyleProperties::align_self() const
640
0
{
641
0
    auto value = property(CSS::PropertyID::AlignSelf);
642
0
    return keyword_to_align_self(value->to_keyword());
643
0
}
644
645
Optional<CSS::Appearance> StyleProperties::appearance() const
646
0
{
647
0
    auto value = property(CSS::PropertyID::Appearance);
648
0
    auto appearance = keyword_to_appearance(value->to_keyword());
649
0
    if (appearance.has_value()) {
650
0
        switch (*appearance) {
651
        // Note: All these compatibility values can be treated as 'auto'
652
0
        case CSS::Appearance::Textfield:
653
0
        case CSS::Appearance::MenulistButton:
654
0
        case CSS::Appearance::Searchfield:
655
0
        case CSS::Appearance::Textarea:
656
0
        case CSS::Appearance::PushButton:
657
0
        case CSS::Appearance::SliderHorizontal:
658
0
        case CSS::Appearance::Checkbox:
659
0
        case CSS::Appearance::Radio:
660
0
        case CSS::Appearance::SquareButton:
661
0
        case CSS::Appearance::Menulist:
662
0
        case CSS::Appearance::Listbox:
663
0
        case CSS::Appearance::Meter:
664
0
        case CSS::Appearance::ProgressBar:
665
0
        case CSS::Appearance::Button:
666
0
            appearance = CSS::Appearance::Auto;
667
0
            break;
668
0
        default:
669
0
            break;
670
0
        }
671
0
    }
672
0
    return appearance;
673
0
}
674
675
CSS::Filter StyleProperties::backdrop_filter() const
676
0
{
677
0
    auto value = property(CSS::PropertyID::BackdropFilter);
678
0
    if (value->is_filter_value_list())
679
0
        return Filter(value->as_filter_value_list());
680
0
    return Filter::make_none();
681
0
}
682
683
CSS::Filter StyleProperties::filter() const
684
0
{
685
0
    auto value = property(CSS::PropertyID::Filter);
686
0
    if (value->is_filter_value_list())
687
0
        return Filter(value->as_filter_value_list());
688
0
    return Filter::make_none();
689
0
}
690
691
Optional<CSS::Positioning> StyleProperties::position() const
692
0
{
693
0
    auto value = property(CSS::PropertyID::Position);
694
0
    return keyword_to_positioning(value->to_keyword());
695
0
}
696
697
bool StyleProperties::operator==(StyleProperties const& other) const
698
0
{
699
0
    if (m_data->m_property_values.size() != other.m_data->m_property_values.size())
700
0
        return false;
701
702
0
    for (size_t i = 0; i < m_data->m_property_values.size(); ++i) {
703
0
        auto const& my_style = m_data->m_property_values[i];
704
0
        auto const& other_style = other.m_data->m_property_values[i];
705
0
        if (!my_style) {
706
0
            if (other_style)
707
0
                return false;
708
0
            continue;
709
0
        }
710
0
        if (!other_style)
711
0
            return false;
712
0
        auto const& my_value = *my_style;
713
0
        auto const& other_value = *other_style;
714
0
        if (my_value.type() != other_value.type())
715
0
            return false;
716
0
        if (my_value != other_value)
717
0
            return false;
718
0
    }
719
720
0
    return true;
721
0
}
722
723
Optional<CSS::TextAnchor> StyleProperties::text_anchor() const
724
0
{
725
0
    auto value = property(CSS::PropertyID::TextAnchor);
726
0
    return keyword_to_text_anchor(value->to_keyword());
727
0
}
728
729
Optional<CSS::TextAlign> StyleProperties::text_align() const
730
0
{
731
0
    auto value = property(CSS::PropertyID::TextAlign);
732
0
    return keyword_to_text_align(value->to_keyword());
733
0
}
734
735
Optional<CSS::TextJustify> StyleProperties::text_justify() const
736
0
{
737
0
    auto value = property(CSS::PropertyID::TextJustify);
738
0
    return keyword_to_text_justify(value->to_keyword());
739
0
}
740
741
Optional<CSS::TextOverflow> StyleProperties::text_overflow() const
742
0
{
743
0
    auto value = property(CSS::PropertyID::TextOverflow);
744
0
    return keyword_to_text_overflow(value->to_keyword());
745
0
}
746
747
Optional<CSS::PointerEvents> StyleProperties::pointer_events() const
748
0
{
749
0
    auto value = property(CSS::PropertyID::PointerEvents);
750
0
    return keyword_to_pointer_events(value->to_keyword());
751
0
}
752
753
Variant<LengthOrCalculated, NumberOrCalculated> StyleProperties::tab_size() const
754
0
{
755
0
    auto value = property(CSS::PropertyID::TabSize);
756
0
    if (value->is_math()) {
757
0
        auto const& math_value = value->as_math();
758
0
        if (math_value.resolves_to_length()) {
759
0
            return LengthOrCalculated { math_value };
760
0
        }
761
0
        if (math_value.resolves_to_number()) {
762
0
            return NumberOrCalculated { math_value };
763
0
        }
764
0
    }
765
766
0
    if (value->is_length())
767
0
        return LengthOrCalculated { value->as_length().length() };
768
769
0
    return NumberOrCalculated { value->as_number().number() };
770
0
}
771
772
Optional<CSS::WordBreak> StyleProperties::word_break() const
773
0
{
774
0
    auto value = property(CSS::PropertyID::WordBreak);
775
0
    return keyword_to_word_break(value->to_keyword());
776
0
}
777
778
Optional<CSS::LengthOrCalculated> StyleProperties::word_spacing() const
779
0
{
780
0
    auto value = property(CSS::PropertyID::WordSpacing);
781
0
    if (value->is_math()) {
782
0
        auto& math_value = value->as_math();
783
0
        if (math_value.resolves_to_length()) {
784
0
            return LengthOrCalculated { math_value };
785
0
        }
786
0
    }
787
788
0
    if (value->is_length())
789
0
        return LengthOrCalculated { value->as_length().length() };
790
791
0
    return {};
792
0
}
793
794
Optional<CSS::WhiteSpace> StyleProperties::white_space() const
795
0
{
796
0
    auto value = property(CSS::PropertyID::WhiteSpace);
797
0
    return keyword_to_white_space(value->to_keyword());
798
0
}
799
800
Optional<LengthOrCalculated> StyleProperties::letter_spacing() const
801
0
{
802
0
    auto value = property(CSS::PropertyID::LetterSpacing);
803
0
    if (value->is_math()) {
804
0
        auto const& math_value = value->as_math();
805
0
        if (math_value.resolves_to_length()) {
806
0
            return LengthOrCalculated { math_value };
807
0
        }
808
0
    }
809
810
0
    if (value->is_length())
811
0
        return LengthOrCalculated { value->as_length().length() };
812
813
0
    return {};
814
0
}
815
816
Optional<CSS::LineStyle> StyleProperties::line_style(CSS::PropertyID property_id) const
817
0
{
818
0
    auto value = property(property_id);
819
0
    return keyword_to_line_style(value->to_keyword());
820
0
}
821
822
Optional<CSS::OutlineStyle> StyleProperties::outline_style() const
823
0
{
824
0
    auto value = property(CSS::PropertyID::OutlineStyle);
825
0
    return keyword_to_outline_style(value->to_keyword());
826
0
}
827
828
Optional<CSS::Float> StyleProperties::float_() const
829
0
{
830
0
    auto value = property(CSS::PropertyID::Float);
831
0
    return keyword_to_float(value->to_keyword());
832
0
}
833
834
Optional<CSS::Clear> StyleProperties::clear() const
835
0
{
836
0
    auto value = property(CSS::PropertyID::Clear);
837
0
    return keyword_to_clear(value->to_keyword());
838
0
}
839
840
Optional<CSS::ColumnSpan> StyleProperties::column_span() const
841
0
{
842
0
    auto value = property(CSS::PropertyID::ColumnSpan);
843
0
    return keyword_to_column_span(value->to_keyword());
844
0
}
845
846
StyleProperties::ContentDataAndQuoteNestingLevel StyleProperties::content(DOM::Element& element, u32 initial_quote_nesting_level) const
847
0
{
848
0
    auto value = property(CSS::PropertyID::Content);
849
0
    auto quotes_data = quotes();
850
851
0
    auto quote_nesting_level = initial_quote_nesting_level;
852
853
0
    auto get_quote_string = [&](bool open, auto depth) {
854
0
        switch (quotes_data.type) {
855
0
        case QuotesData::Type::None:
856
0
            return FlyString {};
857
0
        case QuotesData::Type::Auto:
858
            // FIXME: "A typographically appropriate used value for quotes is automatically chosen by the UA
859
            //        based on the content language of the element and/or its parent."
860
0
            if (open)
861
0
                return depth == 0 ? "“"_fly_string : "‘"_fly_string;
862
0
            return depth == 0 ? "”"_fly_string : "’"_fly_string;
863
0
        case QuotesData::Type::Specified:
864
            // If the depth is greater than the number of pairs, the last pair is repeated.
865
0
            auto& level = quotes_data.strings[min(depth, quotes_data.strings.size() - 1)];
866
0
            return open ? level[0] : level[1];
867
0
        }
868
0
        VERIFY_NOT_REACHED();
869
0
    };
870
871
0
    if (value->is_content()) {
872
0
        auto& content_style_value = value->as_content();
873
874
0
        CSS::ContentData content_data;
875
876
        // FIXME: The content is a list of things: strings, identifiers or functions that return strings, and images.
877
        //        So it can't always be represented as a single String, but may have to be multiple boxes.
878
        //        For now, we'll just assume strings since that is easiest.
879
0
        StringBuilder builder;
880
0
        for (auto const& item : content_style_value.content().values()) {
881
0
            if (item->is_string()) {
882
0
                builder.append(item->as_string().string_value());
883
0
            } else if (item->is_keyword()) {
884
0
                switch (item->to_keyword()) {
885
0
                case Keyword::OpenQuote:
886
0
                    builder.append(get_quote_string(true, quote_nesting_level++));
887
0
                    break;
888
0
                case Keyword::CloseQuote:
889
                    // A 'close-quote' or 'no-close-quote' that would make the depth negative is in error and is ignored
890
                    // (at rendering time): the depth stays at 0 and no quote mark is rendered (although the rest of the
891
                    // 'content' property's value is still inserted).
892
                    // - https://www.w3.org/TR/CSS21/generate.html#quotes-insert
893
                    // (This is missing from the CONTENT-3 spec.)
894
0
                    if (quote_nesting_level > 0)
895
0
                        builder.append(get_quote_string(false, --quote_nesting_level));
896
0
                    break;
897
0
                case Keyword::NoOpenQuote:
898
0
                    quote_nesting_level++;
899
0
                    break;
900
0
                case Keyword::NoCloseQuote:
901
                    // NOTE: See CloseQuote
902
0
                    if (quote_nesting_level > 0)
903
0
                        quote_nesting_level--;
904
0
                    break;
905
0
                default:
906
0
                    dbgln("`{}` is not supported in `content` (yet?)", item->to_string());
907
0
                    break;
908
0
                }
909
0
            } else if (item->is_counter()) {
910
0
                builder.append(item->as_counter().resolve(element));
911
0
            } else {
912
                // TODO: Implement images, and other things.
913
0
                dbgln("`{}` is not supported in `content` (yet?)", item->to_string());
914
0
            }
915
0
        }
916
0
        content_data.type = ContentData::Type::String;
917
0
        content_data.data = MUST(builder.to_string());
918
919
0
        if (content_style_value.has_alt_text()) {
920
0
            StringBuilder alt_text_builder;
921
0
            for (auto const& item : content_style_value.alt_text()->values()) {
922
0
                if (item->is_string()) {
923
0
                    alt_text_builder.append(item->as_string().string_value());
924
0
                } else if (item->is_counter()) {
925
0
                    alt_text_builder.append(item->as_counter().resolve(element));
926
0
                } else {
927
0
                    dbgln("`{}` is not supported in `content` alt-text (yet?)", item->to_string());
928
0
                }
929
0
            }
930
0
            content_data.alt_text = MUST(alt_text_builder.to_string());
931
0
        }
932
933
0
        return { content_data, quote_nesting_level };
934
0
    }
935
936
0
    switch (value->to_keyword()) {
937
0
    case Keyword::None:
938
0
        return { { ContentData::Type::None }, quote_nesting_level };
939
0
    case Keyword::Normal:
940
0
        return { { ContentData::Type::Normal }, quote_nesting_level };
941
0
    default:
942
0
        break;
943
0
    }
944
945
0
    return { {}, quote_nesting_level };
946
0
}
947
948
Optional<CSS::ContentVisibility> StyleProperties::content_visibility() const
949
0
{
950
0
    auto value = property(CSS::PropertyID::ContentVisibility);
951
0
    return keyword_to_content_visibility(value->to_keyword());
952
0
}
953
954
Optional<CSS::Cursor> StyleProperties::cursor() const
955
0
{
956
0
    auto value = property(CSS::PropertyID::Cursor);
957
0
    return keyword_to_cursor(value->to_keyword());
958
0
}
959
960
Optional<CSS::Visibility> StyleProperties::visibility() const
961
0
{
962
0
    auto value = property(CSS::PropertyID::Visibility);
963
0
    if (!value->is_keyword())
964
0
        return {};
965
0
    return keyword_to_visibility(value->to_keyword());
966
0
}
967
968
Display StyleProperties::display() const
969
0
{
970
0
    auto value = property(PropertyID::Display);
971
0
    if (value->is_display()) {
972
0
        return value->as_display().display();
973
0
    }
974
0
    return Display::from_short(Display::Short::Inline);
975
0
}
976
977
Vector<CSS::TextDecorationLine> StyleProperties::text_decoration_line() const
978
0
{
979
0
    auto value = property(CSS::PropertyID::TextDecorationLine);
980
981
0
    if (value->is_value_list()) {
982
0
        Vector<CSS::TextDecorationLine> lines;
983
0
        auto& values = value->as_value_list().values();
984
0
        for (auto const& item : values) {
985
0
            lines.append(keyword_to_text_decoration_line(item->to_keyword()).value());
986
0
        }
987
0
        return lines;
988
0
    }
989
990
0
    if (value->is_keyword() && value->to_keyword() == Keyword::None)
991
0
        return {};
992
993
0
    dbgln("FIXME: Unsupported value for text-decoration-line: {}", value->to_string());
994
0
    return {};
995
0
}
996
997
Optional<CSS::TextDecorationStyle> StyleProperties::text_decoration_style() const
998
0
{
999
0
    auto value = property(CSS::PropertyID::TextDecorationStyle);
1000
0
    return keyword_to_text_decoration_style(value->to_keyword());
1001
0
}
1002
1003
Optional<CSS::TextTransform> StyleProperties::text_transform() const
1004
0
{
1005
0
    auto value = property(CSS::PropertyID::TextTransform);
1006
0
    return keyword_to_text_transform(value->to_keyword());
1007
0
}
1008
1009
Optional<CSS::ListStyleType> StyleProperties::list_style_type() const
1010
0
{
1011
0
    auto value = property(CSS::PropertyID::ListStyleType);
1012
0
    return keyword_to_list_style_type(value->to_keyword());
1013
0
}
1014
1015
Optional<CSS::ListStylePosition> StyleProperties::list_style_position() const
1016
0
{
1017
0
    auto value = property(CSS::PropertyID::ListStylePosition);
1018
0
    return keyword_to_list_style_position(value->to_keyword());
1019
0
}
1020
1021
Optional<CSS::Overflow> StyleProperties::overflow_x() const
1022
0
{
1023
0
    return overflow(CSS::PropertyID::OverflowX);
1024
0
}
1025
1026
Optional<CSS::Overflow> StyleProperties::overflow_y() const
1027
0
{
1028
0
    return overflow(CSS::PropertyID::OverflowY);
1029
0
}
1030
1031
Optional<CSS::Overflow> StyleProperties::overflow(CSS::PropertyID property_id) const
1032
0
{
1033
0
    auto value = property(property_id);
1034
0
    return keyword_to_overflow(value->to_keyword());
1035
0
}
1036
1037
Vector<ShadowData> StyleProperties::shadow(PropertyID property_id, Layout::Node const& layout_node) const
1038
0
{
1039
0
    auto value = property(property_id);
1040
1041
0
    auto resolve_to_length = [&layout_node](NonnullRefPtr<CSSStyleValue const> const& value) -> Optional<Length> {
1042
0
        if (value->is_length())
1043
0
            return value->as_length().length();
1044
0
        if (value->is_math())
1045
0
            return value->as_math().resolve_length(layout_node);
1046
0
        return {};
1047
0
    };
1048
1049
0
    auto make_shadow_data = [resolve_to_length, &layout_node](ShadowStyleValue const& value) -> Optional<ShadowData> {
1050
0
        auto maybe_offset_x = resolve_to_length(value.offset_x());
1051
0
        if (!maybe_offset_x.has_value())
1052
0
            return {};
1053
0
        auto maybe_offset_y = resolve_to_length(value.offset_y());
1054
0
        if (!maybe_offset_y.has_value())
1055
0
            return {};
1056
0
        auto maybe_blur_radius = resolve_to_length(value.blur_radius());
1057
0
        if (!maybe_blur_radius.has_value())
1058
0
            return {};
1059
0
        auto maybe_spread_distance = resolve_to_length(value.spread_distance());
1060
0
        if (!maybe_spread_distance.has_value())
1061
0
            return {};
1062
0
        return ShadowData {
1063
0
            value.color()->to_color(verify_cast<Layout::NodeWithStyle>(layout_node)),
1064
0
            maybe_offset_x.release_value(),
1065
0
            maybe_offset_y.release_value(),
1066
0
            maybe_blur_radius.release_value(),
1067
0
            maybe_spread_distance.release_value(),
1068
0
            value.placement()
1069
0
        };
1070
0
    };
1071
1072
0
    if (value->is_value_list()) {
1073
0
        auto const& value_list = value->as_value_list();
1074
1075
0
        Vector<ShadowData> shadow_data;
1076
0
        shadow_data.ensure_capacity(value_list.size());
1077
0
        for (auto const& layer_value : value_list.values()) {
1078
0
            auto maybe_shadow_data = make_shadow_data(layer_value->as_shadow());
1079
0
            if (!maybe_shadow_data.has_value())
1080
0
                return {};
1081
0
            shadow_data.append(maybe_shadow_data.release_value());
1082
0
        }
1083
1084
0
        return shadow_data;
1085
0
    }
1086
1087
0
    if (value->is_shadow()) {
1088
0
        auto maybe_shadow_data = make_shadow_data(value->as_shadow());
1089
0
        if (!maybe_shadow_data.has_value())
1090
0
            return {};
1091
0
        return { maybe_shadow_data.release_value() };
1092
0
    }
1093
1094
0
    return {};
1095
0
}
1096
1097
Vector<ShadowData> StyleProperties::box_shadow(Layout::Node const& layout_node) const
1098
0
{
1099
0
    return shadow(PropertyID::BoxShadow, layout_node);
1100
0
}
1101
1102
Vector<ShadowData> StyleProperties::text_shadow(Layout::Node const& layout_node) const
1103
0
{
1104
0
    return shadow(PropertyID::TextShadow, layout_node);
1105
0
}
1106
1107
Optional<CSS::BoxSizing> StyleProperties::box_sizing() const
1108
0
{
1109
0
    auto value = property(CSS::PropertyID::BoxSizing);
1110
0
    return keyword_to_box_sizing(value->to_keyword());
1111
0
}
1112
1113
Variant<CSS::VerticalAlign, CSS::LengthPercentage> StyleProperties::vertical_align() const
1114
0
{
1115
0
    auto value = property(CSS::PropertyID::VerticalAlign);
1116
1117
0
    if (value->is_keyword())
1118
0
        return keyword_to_vertical_align(value->to_keyword()).release_value();
1119
1120
0
    if (value->is_length())
1121
0
        return CSS::LengthPercentage(value->as_length().length());
1122
1123
0
    if (value->is_percentage())
1124
0
        return CSS::LengthPercentage(value->as_percentage().percentage());
1125
1126
0
    if (value->is_math())
1127
0
        return LengthPercentage { const_cast<CSSMathValue&>(value->as_math()) };
1128
1129
0
    VERIFY_NOT_REACHED();
1130
0
}
1131
1132
Optional<CSS::FontVariant> StyleProperties::font_variant() const
1133
0
{
1134
0
    auto value = property(CSS::PropertyID::FontVariant);
1135
0
    return keyword_to_font_variant(value->to_keyword());
1136
0
}
1137
1138
Optional<FlyString> StyleProperties::font_language_override() const
1139
0
{
1140
0
    auto value = property(CSS::PropertyID::FontLanguageOverride);
1141
0
    if (value->is_string())
1142
0
        return value->as_string().string_value();
1143
0
    return {};
1144
0
}
1145
1146
Optional<HashMap<FlyString, IntegerOrCalculated>> StyleProperties::font_feature_settings() const
1147
0
{
1148
0
    auto value = property(PropertyID::FontFeatureSettings);
1149
1150
0
    if (value->is_keyword())
1151
0
        return {}; // normal
1152
1153
0
    if (value->is_value_list()) {
1154
0
        auto const& feature_tags = value->as_value_list().values();
1155
0
        HashMap<FlyString, IntegerOrCalculated> result;
1156
0
        result.ensure_capacity(feature_tags.size());
1157
0
        for (auto const& tag_value : feature_tags) {
1158
0
            auto const& feature_tag = tag_value->as_open_type_tagged();
1159
1160
0
            if (feature_tag.value()->is_integer()) {
1161
0
                result.set(feature_tag.tag(), feature_tag.value()->as_integer().value());
1162
0
            } else {
1163
0
                VERIFY(feature_tag.value()->is_math());
1164
0
                result.set(feature_tag.tag(), IntegerOrCalculated { feature_tag.value()->as_math() });
1165
0
            }
1166
0
        }
1167
0
        return result;
1168
0
    }
1169
1170
0
    return {};
1171
0
}
1172
1173
Optional<HashMap<FlyString, NumberOrCalculated>> StyleProperties::font_variation_settings() const
1174
0
{
1175
0
    auto value = property(CSS::PropertyID::FontVariationSettings);
1176
1177
0
    if (value->is_keyword())
1178
0
        return {}; // normal
1179
1180
0
    if (value->is_value_list()) {
1181
0
        auto const& axis_tags = value->as_value_list().values();
1182
0
        HashMap<FlyString, NumberOrCalculated> result;
1183
0
        result.ensure_capacity(axis_tags.size());
1184
0
        for (auto const& tag_value : axis_tags) {
1185
0
            auto const& axis_tag = tag_value->as_open_type_tagged();
1186
1187
0
            if (axis_tag.value()->is_number()) {
1188
0
                result.set(axis_tag.tag(), axis_tag.value()->as_number().value());
1189
0
            } else {
1190
0
                VERIFY(axis_tag.value()->is_math());
1191
0
                result.set(axis_tag.tag(), NumberOrCalculated { axis_tag.value()->as_math() });
1192
0
            }
1193
0
        }
1194
0
        return result;
1195
0
    }
1196
1197
0
    return {};
1198
0
}
1199
1200
CSS::GridTrackSizeList StyleProperties::grid_auto_columns() const
1201
0
{
1202
0
    auto value = property(CSS::PropertyID::GridAutoColumns);
1203
0
    return value->as_grid_track_size_list().grid_track_size_list();
1204
0
}
1205
1206
CSS::GridTrackSizeList StyleProperties::grid_auto_rows() const
1207
0
{
1208
0
    auto value = property(CSS::PropertyID::GridAutoRows);
1209
0
    return value->as_grid_track_size_list().grid_track_size_list();
1210
0
}
1211
1212
CSS::GridTrackSizeList StyleProperties::grid_template_columns() const
1213
0
{
1214
0
    auto value = property(CSS::PropertyID::GridTemplateColumns);
1215
0
    return value->as_grid_track_size_list().grid_track_size_list();
1216
0
}
1217
1218
CSS::GridTrackSizeList StyleProperties::grid_template_rows() const
1219
0
{
1220
0
    auto value = property(CSS::PropertyID::GridTemplateRows);
1221
0
    return value->as_grid_track_size_list().grid_track_size_list();
1222
0
}
1223
1224
CSS::GridAutoFlow StyleProperties::grid_auto_flow() const
1225
0
{
1226
0
    auto value = property(CSS::PropertyID::GridAutoFlow);
1227
0
    if (!value->is_grid_auto_flow())
1228
0
        return CSS::GridAutoFlow {};
1229
0
    auto& grid_auto_flow_value = value->as_grid_auto_flow();
1230
0
    return CSS::GridAutoFlow { .row = grid_auto_flow_value.is_row(), .dense = grid_auto_flow_value.is_dense() };
1231
0
}
1232
1233
CSS::GridTrackPlacement StyleProperties::grid_column_end() const
1234
0
{
1235
0
    auto value = property(CSS::PropertyID::GridColumnEnd);
1236
0
    return value->as_grid_track_placement().grid_track_placement();
1237
0
}
1238
1239
CSS::GridTrackPlacement StyleProperties::grid_column_start() const
1240
0
{
1241
0
    auto value = property(CSS::PropertyID::GridColumnStart);
1242
0
    return value->as_grid_track_placement().grid_track_placement();
1243
0
}
1244
1245
CSS::GridTrackPlacement StyleProperties::grid_row_end() const
1246
0
{
1247
0
    auto value = property(CSS::PropertyID::GridRowEnd);
1248
0
    return value->as_grid_track_placement().grid_track_placement();
1249
0
}
1250
1251
CSS::GridTrackPlacement StyleProperties::grid_row_start() const
1252
0
{
1253
0
    auto value = property(CSS::PropertyID::GridRowStart);
1254
0
    return value->as_grid_track_placement().grid_track_placement();
1255
0
}
1256
1257
Optional<CSS::BorderCollapse> StyleProperties::border_collapse() const
1258
0
{
1259
0
    auto value = property(CSS::PropertyID::BorderCollapse);
1260
0
    return keyword_to_border_collapse(value->to_keyword());
1261
0
}
1262
1263
Vector<Vector<String>> StyleProperties::grid_template_areas() const
1264
0
{
1265
0
    auto value = property(CSS::PropertyID::GridTemplateAreas);
1266
0
    return value->as_grid_template_area().grid_template_area();
1267
0
}
1268
1269
Optional<CSS::ObjectFit> StyleProperties::object_fit() const
1270
0
{
1271
0
    auto value = property(CSS::PropertyID::ObjectFit);
1272
0
    return keyword_to_object_fit(value->to_keyword());
1273
0
}
1274
1275
CSS::ObjectPosition StyleProperties::object_position() const
1276
0
{
1277
0
    auto value = property(CSS::PropertyID::ObjectPosition);
1278
0
    auto const& position = value->as_position();
1279
0
    CSS::ObjectPosition object_position;
1280
0
    auto const& edge_x = position.edge_x();
1281
0
    auto const& edge_y = position.edge_y();
1282
0
    if (edge_x->is_edge()) {
1283
0
        auto const& edge = edge_x->as_edge();
1284
0
        object_position.edge_x = edge.edge();
1285
0
        object_position.offset_x = edge.offset();
1286
0
    }
1287
0
    if (edge_y->is_edge()) {
1288
0
        auto const& edge = edge_y->as_edge();
1289
0
        object_position.edge_y = edge.edge();
1290
0
        object_position.offset_y = edge.offset();
1291
0
    }
1292
0
    return object_position;
1293
0
}
1294
1295
Optional<CSS::TableLayout> StyleProperties::table_layout() const
1296
0
{
1297
0
    auto value = property(CSS::PropertyID::TableLayout);
1298
0
    return keyword_to_table_layout(value->to_keyword());
1299
0
}
1300
1301
Optional<CSS::Direction> StyleProperties::direction() const
1302
0
{
1303
0
    auto value = property(CSS::PropertyID::Direction);
1304
0
    return keyword_to_direction(value->to_keyword());
1305
0
}
1306
1307
Optional<CSS::UnicodeBidi> StyleProperties::unicode_bidi() const
1308
0
{
1309
0
    auto value = property(CSS::PropertyID::UnicodeBidi);
1310
0
    return keyword_to_unicode_bidi(value->to_keyword());
1311
0
}
1312
1313
Optional<CSS::WritingMode> StyleProperties::writing_mode() const
1314
0
{
1315
0
    auto value = property(CSS::PropertyID::WritingMode);
1316
0
    return keyword_to_writing_mode(value->to_keyword());
1317
0
}
1318
1319
Optional<CSS::MaskType> StyleProperties::mask_type() const
1320
0
{
1321
0
    auto value = property(CSS::PropertyID::MaskType);
1322
0
    return keyword_to_mask_type(value->to_keyword());
1323
0
}
1324
1325
Color StyleProperties::stop_color() const
1326
0
{
1327
0
    auto value = property(CSS::PropertyID::StopColor);
1328
0
    if (value->is_keyword()) {
1329
        // Workaround lack of layout node to resolve current color.
1330
0
        auto& keyword = value->as_keyword();
1331
0
        if (keyword.keyword() == CSS::Keyword::Currentcolor)
1332
0
            value = property(CSS::PropertyID::Color);
1333
0
    }
1334
0
    if (value->has_color()) {
1335
        // FIXME: This is used by the SVGStopElement, which does not participate in layout,
1336
        // so can't pass a layout node (so can't resolve some colors, e.g. palette ones)
1337
0
        return value->to_color({});
1338
0
    }
1339
0
    return Color::Black;
1340
0
}
1341
1342
void StyleProperties::set_math_depth(int math_depth)
1343
0
{
1344
0
    m_data->m_math_depth = math_depth;
1345
    // Make our children inherit our computed value, not our specified value.
1346
0
    set_property(PropertyID::MathDepth, MathDepthStyleValue::create_integer(IntegerStyleValue::create(math_depth)));
1347
0
}
1348
1349
QuotesData StyleProperties::quotes() const
1350
0
{
1351
0
    auto value = property(CSS::PropertyID::Quotes);
1352
0
    if (value->is_keyword()) {
1353
0
        switch (value->to_keyword()) {
1354
0
        case Keyword::Auto:
1355
0
            return QuotesData { .type = QuotesData::Type::Auto };
1356
0
        case Keyword::None:
1357
0
            return QuotesData { .type = QuotesData::Type::None };
1358
0
        default:
1359
0
            break;
1360
0
        }
1361
0
    }
1362
0
    if (value->is_value_list()) {
1363
0
        auto& value_list = value->as_value_list();
1364
0
        QuotesData quotes_data { .type = QuotesData::Type::Specified };
1365
0
        VERIFY(value_list.size() % 2 == 0);
1366
0
        for (auto i = 0u; i < value_list.size(); i += 2) {
1367
0
            quotes_data.strings.empend(
1368
0
                value_list.value_at(i, false)->as_string().string_value(),
1369
0
                value_list.value_at(i + 1, false)->as_string().string_value());
1370
0
        }
1371
0
        return quotes_data;
1372
0
    }
1373
1374
0
    return InitialValues::quotes();
1375
0
}
1376
1377
Vector<CounterData> StyleProperties::counter_data(PropertyID property_id) const
1378
0
{
1379
0
    auto value = property(property_id);
1380
1381
0
    if (value->is_counter_definitions()) {
1382
0
        auto& counter_definitions = value->as_counter_definitions().counter_definitions();
1383
0
        Vector<CounterData> result;
1384
0
        for (auto& counter : counter_definitions) {
1385
0
            CounterData data {
1386
0
                .name = counter.name,
1387
0
                .is_reversed = counter.is_reversed,
1388
0
                .value = {},
1389
0
            };
1390
0
            if (counter.value) {
1391
0
                if (counter.value->is_integer()) {
1392
0
                    data.value = AK::clamp_to<i32>(counter.value->as_integer().integer());
1393
0
                } else if (counter.value->is_math()) {
1394
0
                    auto maybe_int = counter.value->as_math().resolve_integer();
1395
0
                    if (maybe_int.has_value())
1396
0
                        data.value = AK::clamp_to<i32>(*maybe_int);
1397
0
                } else {
1398
0
                    dbgln("Unimplemented type for {} integer value: '{}'", string_from_property_id(property_id), counter.value->to_string());
1399
0
                }
1400
0
            }
1401
0
            result.append(move(data));
1402
0
        }
1403
0
        return result;
1404
0
    }
1405
1406
0
    if (value->to_keyword() == Keyword::None)
1407
0
        return {};
1408
1409
0
    dbgln("Unhandled type for {} value: '{}'", string_from_property_id(property_id), value->to_string());
1410
0
    return {};
1411
0
}
1412
1413
Optional<CSS::ScrollbarWidth> StyleProperties::scrollbar_width() const
1414
0
{
1415
0
    auto value = property(CSS::PropertyID::ScrollbarWidth);
1416
0
    return keyword_to_scrollbar_width(value->to_keyword());
1417
0
}
1418
1419
}