Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
4 * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
5 * Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
6 *
7 * SPDX-License-Identifier: BSD-2-Clause
8 */
9
10#include <AK/ByteBuffer.h>
11#include <LibGfx/Palette.h>
12#include <LibWeb/CSS/Serialize.h>
13#include <LibWeb/CSS/StyleValue.h>
14#include <LibWeb/DOM/Document.h>
15#include <LibWeb/HTML/BrowsingContext.h>
16#include <LibWeb/Loader/LoadRequest.h>
17#include <LibWeb/Loader/ResourceLoader.h>
18#include <LibWeb/Page/Page.h>
19#include <LibWeb/Painting/GradientPainting.h>
20#include <LibWeb/Platform/Timer.h>
21
22namespace Web::CSS {
23
24StyleValue::StyleValue(Type type)
25 : m_type(type)
26{
27}
28
29AbstractImageStyleValue const& StyleValue::as_abstract_image() const
30{
31 VERIFY(is_abstract_image());
32 return static_cast<AbstractImageStyleValue const&>(*this);
33}
34
35AngleStyleValue const& StyleValue::as_angle() const
36{
37 VERIFY(is_angle());
38 return static_cast<AngleStyleValue const&>(*this);
39}
40
41BackgroundStyleValue const& StyleValue::as_background() const
42{
43 VERIFY(is_background());
44 return static_cast<BackgroundStyleValue const&>(*this);
45}
46
47BackgroundRepeatStyleValue const& StyleValue::as_background_repeat() const
48{
49 VERIFY(is_background_repeat());
50 return static_cast<BackgroundRepeatStyleValue const&>(*this);
51}
52
53BackgroundSizeStyleValue const& StyleValue::as_background_size() const
54{
55 VERIFY(is_background_size());
56 return static_cast<BackgroundSizeStyleValue const&>(*this);
57}
58
59BorderStyleValue const& StyleValue::as_border() const
60{
61 VERIFY(is_border());
62 return static_cast<BorderStyleValue const&>(*this);
63}
64
65BorderRadiusStyleValue const& StyleValue::as_border_radius() const
66{
67 VERIFY(is_border_radius());
68 return static_cast<BorderRadiusStyleValue const&>(*this);
69}
70
71BorderRadiusShorthandStyleValue const& StyleValue::as_border_radius_shorthand() const
72{
73 VERIFY(is_border_radius_shorthand());
74 return static_cast<BorderRadiusShorthandStyleValue const&>(*this);
75}
76
77ShadowStyleValue const& StyleValue::as_shadow() const
78{
79 VERIFY(is_shadow());
80 return static_cast<ShadowStyleValue const&>(*this);
81}
82
83CalculatedStyleValue const& StyleValue::as_calculated() const
84{
85 VERIFY(is_calculated());
86 return static_cast<CalculatedStyleValue const&>(*this);
87}
88
89ColorStyleValue const& StyleValue::as_color() const
90{
91 VERIFY(is_color());
92 return static_cast<ColorStyleValue const&>(*this);
93}
94
95ConicGradientStyleValue const& StyleValue::as_conic_gradient() const
96{
97 VERIFY(is_conic_gradient());
98 return static_cast<ConicGradientStyleValue const&>(*this);
99}
100
101ContentStyleValue const& StyleValue::as_content() const
102{
103 VERIFY(is_content());
104 return static_cast<ContentStyleValue const&>(*this);
105}
106
107FilterValueListStyleValue const& StyleValue::as_filter_value_list() const
108{
109 VERIFY(is_filter_value_list());
110 return static_cast<FilterValueListStyleValue const&>(*this);
111}
112
113FlexStyleValue const& StyleValue::as_flex() const
114{
115 VERIFY(is_flex());
116 return static_cast<FlexStyleValue const&>(*this);
117}
118
119FlexFlowStyleValue const& StyleValue::as_flex_flow() const
120{
121 VERIFY(is_flex_flow());
122 return static_cast<FlexFlowStyleValue const&>(*this);
123}
124
125FontStyleValue const& StyleValue::as_font() const
126{
127 VERIFY(is_font());
128 return static_cast<FontStyleValue const&>(*this);
129}
130
131FrequencyStyleValue const& StyleValue::as_frequency() const
132{
133 VERIFY(is_frequency());
134 return static_cast<FrequencyStyleValue const&>(*this);
135}
136
137GridTrackPlacementShorthandStyleValue const& StyleValue::as_grid_track_placement_shorthand() const
138{
139 VERIFY(is_grid_track_placement_shorthand());
140 return static_cast<GridTrackPlacementShorthandStyleValue const&>(*this);
141}
142
143GridAreaShorthandStyleValue const& StyleValue::as_grid_area_shorthand() const
144{
145 VERIFY(is_grid_area_shorthand());
146 return static_cast<GridAreaShorthandStyleValue const&>(*this);
147}
148
149GridTemplateAreaStyleValue const& StyleValue::as_grid_template_area() const
150{
151 VERIFY(is_grid_template_area());
152 return static_cast<GridTemplateAreaStyleValue const&>(*this);
153}
154
155GridTrackPlacementStyleValue const& StyleValue::as_grid_track_placement() const
156{
157 VERIFY(is_grid_track_placement());
158 return static_cast<GridTrackPlacementStyleValue const&>(*this);
159}
160
161IdentifierStyleValue const& StyleValue::as_identifier() const
162{
163 VERIFY(is_identifier());
164 return static_cast<IdentifierStyleValue const&>(*this);
165}
166
167ImageStyleValue const& StyleValue::as_image() const
168{
169 VERIFY(is_image());
170 return static_cast<ImageStyleValue const&>(*this);
171}
172
173InheritStyleValue const& StyleValue::as_inherit() const
174{
175 VERIFY(is_inherit());
176 return static_cast<InheritStyleValue const&>(*this);
177}
178
179InitialStyleValue const& StyleValue::as_initial() const
180{
181 VERIFY(is_initial());
182 return static_cast<InitialStyleValue const&>(*this);
183}
184
185LengthStyleValue const& StyleValue::as_length() const
186{
187 VERIFY(is_length());
188 return static_cast<LengthStyleValue const&>(*this);
189}
190
191GridTrackSizeStyleValue const& StyleValue::as_grid_track_size_list() const
192{
193 VERIFY(is_grid_track_size_list());
194 return static_cast<GridTrackSizeStyleValue const&>(*this);
195}
196
197LinearGradientStyleValue const& StyleValue::as_linear_gradient() const
198{
199 VERIFY(is_linear_gradient());
200 return static_cast<LinearGradientStyleValue const&>(*this);
201}
202
203ListStyleStyleValue const& StyleValue::as_list_style() const
204{
205 VERIFY(is_list_style());
206 return static_cast<ListStyleStyleValue const&>(*this);
207}
208
209NumericStyleValue const& StyleValue::as_numeric() const
210{
211 VERIFY(is_numeric());
212 return static_cast<NumericStyleValue const&>(*this);
213}
214
215OverflowStyleValue const& StyleValue::as_overflow() const
216{
217 VERIFY(is_overflow());
218 return static_cast<OverflowStyleValue const&>(*this);
219}
220
221PercentageStyleValue const& StyleValue::as_percentage() const
222{
223 VERIFY(is_percentage());
224 return static_cast<PercentageStyleValue const&>(*this);
225}
226
227PositionStyleValue const& StyleValue::as_position() const
228{
229 VERIFY(is_position());
230 return static_cast<PositionStyleValue const&>(*this);
231}
232
233RadialGradientStyleValue const& StyleValue::as_radial_gradient() const
234{
235 VERIFY(is_radial_gradient());
236 return static_cast<RadialGradientStyleValue const&>(*this);
237}
238
239RectStyleValue const& StyleValue::as_rect() const
240{
241 VERIFY(is_rect());
242 return static_cast<RectStyleValue const&>(*this);
243}
244
245ResolutionStyleValue const& StyleValue::as_resolution() const
246{
247 VERIFY(is_resolution());
248 return static_cast<ResolutionStyleValue const&>(*this);
249}
250
251StringStyleValue const& StyleValue::as_string() const
252{
253 VERIFY(is_string());
254 return static_cast<StringStyleValue const&>(*this);
255}
256
257TextDecorationStyleValue const& StyleValue::as_text_decoration() const
258{
259 VERIFY(is_text_decoration());
260 return static_cast<TextDecorationStyleValue const&>(*this);
261}
262
263TimeStyleValue const& StyleValue::as_time() const
264{
265 VERIFY(is_time());
266 return static_cast<TimeStyleValue const&>(*this);
267}
268
269TransformationStyleValue const& StyleValue::as_transformation() const
270{
271 VERIFY(is_transformation());
272 return static_cast<TransformationStyleValue const&>(*this);
273}
274
275UnresolvedStyleValue const& StyleValue::as_unresolved() const
276{
277 VERIFY(is_unresolved());
278 return static_cast<UnresolvedStyleValue const&>(*this);
279}
280
281UnsetStyleValue const& StyleValue::as_unset() const
282{
283 VERIFY(is_unset());
284 return static_cast<UnsetStyleValue const&>(*this);
285}
286
287StyleValueList const& StyleValue::as_value_list() const
288{
289 VERIFY(is_value_list());
290 return static_cast<StyleValueList const&>(*this);
291}
292
293BackgroundStyleValue::BackgroundStyleValue(
294 ValueComparingNonnullRefPtr<StyleValue const> color,
295 ValueComparingNonnullRefPtr<StyleValue const> image,
296 ValueComparingNonnullRefPtr<StyleValue const> position,
297 ValueComparingNonnullRefPtr<StyleValue const> size,
298 ValueComparingNonnullRefPtr<StyleValue const> repeat,
299 ValueComparingNonnullRefPtr<StyleValue const> attachment,
300 ValueComparingNonnullRefPtr<StyleValue const> origin,
301 ValueComparingNonnullRefPtr<StyleValue const> clip)
302 : StyleValueWithDefaultOperators(Type::Background)
303 , m_properties {
304 .color = move(color),
305 .image = move(image),
306 .position = move(position),
307 .size = move(size),
308 .repeat = move(repeat),
309 .attachment = move(attachment),
310 .origin = move(origin),
311 .clip = move(clip),
312 .layer_count = 0
313 }
314{
315 auto layer_count = [](auto style_value) -> size_t {
316 if (style_value->is_value_list())
317 return style_value->as_value_list().size();
318 else
319 return 1;
320 };
321
322 m_properties.layer_count = max(layer_count(m_properties.image), layer_count(m_properties.position));
323 m_properties.layer_count = max(m_properties.layer_count, layer_count(m_properties.size));
324 m_properties.layer_count = max(m_properties.layer_count, layer_count(m_properties.repeat));
325 m_properties.layer_count = max(m_properties.layer_count, layer_count(m_properties.attachment));
326 m_properties.layer_count = max(m_properties.layer_count, layer_count(m_properties.origin));
327 m_properties.layer_count = max(m_properties.layer_count, layer_count(m_properties.clip));
328
329 VERIFY(!m_properties.color->is_value_list());
330}
331
332ErrorOr<String> BackgroundStyleValue::to_string() const
333{
334 if (m_properties.layer_count == 1) {
335 return String::formatted("{} {} {} {} {} {} {} {}", TRY(m_properties.color->to_string()), TRY(m_properties.image->to_string()), TRY(m_properties.position->to_string()), TRY(m_properties.size->to_string()), TRY(m_properties.repeat->to_string()), TRY(m_properties.attachment->to_string()), TRY(m_properties.origin->to_string()), TRY(m_properties.clip->to_string()));
336 }
337
338 auto get_layer_value_string = [](ValueComparingNonnullRefPtr<StyleValue const> const& style_value, size_t index) {
339 if (style_value->is_value_list())
340 return style_value->as_value_list().value_at(index, true)->to_string();
341 return style_value->to_string();
342 };
343
344 StringBuilder builder;
345 for (size_t i = 0; i < m_properties.layer_count; i++) {
346 if (i)
347 TRY(builder.try_append(", "sv));
348 if (i == m_properties.layer_count - 1)
349 TRY(builder.try_appendff("{} ", TRY(m_properties.color->to_string())));
350 TRY(builder.try_appendff("{} {} {} {} {} {} {}", TRY(get_layer_value_string(m_properties.image, i)), TRY(get_layer_value_string(m_properties.position, i)), TRY(get_layer_value_string(m_properties.size, i)), TRY(get_layer_value_string(m_properties.repeat, i)), TRY(get_layer_value_string(m_properties.attachment, i)), TRY(get_layer_value_string(m_properties.origin, i)), TRY(get_layer_value_string(m_properties.clip, i))));
351 }
352
353 return builder.to_string();
354}
355
356ErrorOr<String> BackgroundRepeatStyleValue::to_string() const
357{
358 return String::formatted("{} {}", CSS::to_string(m_properties.repeat_x), CSS::to_string(m_properties.repeat_y));
359}
360
361ErrorOr<String> BackgroundSizeStyleValue::to_string() const
362{
363 return String::formatted("{} {}", TRY(m_properties.size_x.to_string()), TRY(m_properties.size_y.to_string()));
364}
365
366ErrorOr<String> BorderStyleValue::to_string() const
367{
368 return String::formatted("{} {} {}", TRY(m_properties.border_width->to_string()), TRY(m_properties.border_style->to_string()), TRY(m_properties.border_color->to_string()));
369}
370
371ErrorOr<String> BorderRadiusStyleValue::to_string() const
372{
373 if (m_properties.horizontal_radius == m_properties.vertical_radius)
374 return m_properties.horizontal_radius.to_string();
375 return String::formatted("{} / {}", TRY(m_properties.horizontal_radius.to_string()), TRY(m_properties.vertical_radius.to_string()));
376}
377
378ErrorOr<String> BorderRadiusShorthandStyleValue::to_string() const
379{
380 return String::formatted("{} {} {} {} / {} {} {} {}", TRY(m_properties.top_left->horizontal_radius().to_string()), TRY(m_properties.top_right->horizontal_radius().to_string()), TRY(m_properties.bottom_right->horizontal_radius().to_string()), TRY(m_properties.bottom_left->horizontal_radius().to_string()), TRY(m_properties.top_left->vertical_radius().to_string()), TRY(m_properties.top_right->vertical_radius().to_string()), TRY(m_properties.bottom_right->vertical_radius().to_string()), TRY(m_properties.bottom_left->vertical_radius().to_string()));
381}
382
383void CalculatedStyleValue::CalculationResult::add(CalculationResult const& other, Layout::Node const* layout_node, PercentageBasis const& percentage_basis)
384{
385 add_or_subtract_internal(SumOperation::Add, other, layout_node, percentage_basis);
386}
387
388void CalculatedStyleValue::CalculationResult::subtract(CalculationResult const& other, Layout::Node const* layout_node, PercentageBasis const& percentage_basis)
389{
390 add_or_subtract_internal(SumOperation::Subtract, other, layout_node, percentage_basis);
391}
392
393void CalculatedStyleValue::CalculationResult::add_or_subtract_internal(SumOperation op, CalculationResult const& other, Layout::Node const* layout_node, PercentageBasis const& percentage_basis)
394{
395 // We know from validation when resolving the type, that "both sides have the same type, or that one side is a <number> and the other is an <integer>".
396 // Though, having the same type may mean that one side is a <dimension> and the other a <percentage>.
397 // Note: This is almost identical to ::add()
398
399 m_value.visit(
400 [&](Number const& number) {
401 auto other_number = other.m_value.get<Number>();
402 if (op == SumOperation::Add) {
403 m_value = number + other_number;
404 } else {
405 m_value = number - other_number;
406 }
407 },
408 [&](Angle const& angle) {
409 auto this_degrees = angle.to_degrees();
410 if (other.m_value.has<Angle>()) {
411 auto other_degrees = other.m_value.get<Angle>().to_degrees();
412 if (op == SumOperation::Add)
413 m_value = Angle::make_degrees(this_degrees + other_degrees);
414 else
415 m_value = Angle::make_degrees(this_degrees - other_degrees);
416 } else {
417 VERIFY(percentage_basis.has<Angle>());
418
419 auto other_degrees = percentage_basis.get<Angle>().percentage_of(other.m_value.get<Percentage>()).to_degrees();
420 if (op == SumOperation::Add)
421 m_value = Angle::make_degrees(this_degrees + other_degrees);
422 else
423 m_value = Angle::make_degrees(this_degrees - other_degrees);
424 }
425 },
426 [&](Frequency const& frequency) {
427 auto this_hertz = frequency.to_hertz();
428 if (other.m_value.has<Frequency>()) {
429 auto other_hertz = other.m_value.get<Frequency>().to_hertz();
430 if (op == SumOperation::Add)
431 m_value = Frequency::make_hertz(this_hertz + other_hertz);
432 else
433 m_value = Frequency::make_hertz(this_hertz - other_hertz);
434 } else {
435 VERIFY(percentage_basis.has<Frequency>());
436
437 auto other_hertz = percentage_basis.get<Frequency>().percentage_of(other.m_value.get<Percentage>()).to_hertz();
438 if (op == SumOperation::Add)
439 m_value = Frequency::make_hertz(this_hertz + other_hertz);
440 else
441 m_value = Frequency::make_hertz(this_hertz - other_hertz);
442 }
443 },
444 [&](Length const& length) {
445 auto this_px = length.to_px(*layout_node);
446 if (other.m_value.has<Length>()) {
447 auto other_px = other.m_value.get<Length>().to_px(*layout_node);
448 if (op == SumOperation::Add)
449 m_value = Length::make_px(this_px + other_px);
450 else
451 m_value = Length::make_px(this_px - other_px);
452 } else {
453 VERIFY(percentage_basis.has<Length>());
454
455 auto other_px = percentage_basis.get<Length>().percentage_of(other.m_value.get<Percentage>()).to_px(*layout_node);
456 if (op == SumOperation::Add)
457 m_value = Length::make_px(this_px + other_px);
458 else
459 m_value = Length::make_px(this_px - other_px);
460 }
461 },
462 [&](Time const& time) {
463 auto this_seconds = time.to_seconds();
464 if (other.m_value.has<Time>()) {
465 auto other_seconds = other.m_value.get<Time>().to_seconds();
466 if (op == SumOperation::Add)
467 m_value = Time::make_seconds(this_seconds + other_seconds);
468 else
469 m_value = Time::make_seconds(this_seconds - other_seconds);
470 } else {
471 VERIFY(percentage_basis.has<Time>());
472
473 auto other_seconds = percentage_basis.get<Time>().percentage_of(other.m_value.get<Percentage>()).to_seconds();
474 if (op == SumOperation::Add)
475 m_value = Time::make_seconds(this_seconds + other_seconds);
476 else
477 m_value = Time::make_seconds(this_seconds - other_seconds);
478 }
479 },
480 [&](Percentage const& percentage) {
481 if (other.m_value.has<Percentage>()) {
482 if (op == SumOperation::Add)
483 m_value = Percentage { percentage.value() + other.m_value.get<Percentage>().value() };
484 else
485 m_value = Percentage { percentage.value() - other.m_value.get<Percentage>().value() };
486 return;
487 }
488
489 // Other side isn't a percentage, so the easiest way to handle it without duplicating all the logic, is just to swap `this` and `other`.
490 CalculationResult new_value = other;
491 if (op == SumOperation::Add) {
492 new_value.add(*this, layout_node, percentage_basis);
493 } else {
494 // Turn 'this - other' into '-other + this', as 'A + B == B + A', but 'A - B != B - A'
495 new_value.multiply_by({ Number { Number::Type::Integer, -1.0f } }, layout_node);
496 new_value.add(*this, layout_node, percentage_basis);
497 }
498
499 *this = new_value;
500 });
501}
502
503void CalculatedStyleValue::CalculationResult::multiply_by(CalculationResult const& other, Layout::Node const* layout_node)
504{
505 // We know from validation when resolving the type, that at least one side must be a <number> or <integer>.
506 // Both of these are represented as a float.
507 VERIFY(m_value.has<Number>() || other.m_value.has<Number>());
508 bool other_is_number = other.m_value.has<Number>();
509
510 m_value.visit(
511 [&](Number const& number) {
512 if (other_is_number) {
513 m_value = number * other.m_value.get<Number>();
514 } else {
515 // Avoid duplicating all the logic by swapping `this` and `other`.
516 CalculationResult new_value = other;
517 new_value.multiply_by(*this, layout_node);
518 *this = new_value;
519 }
520 },
521 [&](Angle const& angle) {
522 m_value = Angle::make_degrees(angle.to_degrees() * other.m_value.get<Number>().value());
523 },
524 [&](Frequency const& frequency) {
525 m_value = Frequency::make_hertz(frequency.to_hertz() * other.m_value.get<Number>().value());
526 },
527 [&](Length const& length) {
528 VERIFY(layout_node);
529 m_value = Length::make_px(length.to_px(*layout_node) * other.m_value.get<Number>().value());
530 },
531 [&](Time const& time) {
532 m_value = Time::make_seconds(time.to_seconds() * other.m_value.get<Number>().value());
533 },
534 [&](Percentage const& percentage) {
535 m_value = Percentage { percentage.value() * other.m_value.get<Number>().value() };
536 });
537}
538
539void CalculatedStyleValue::CalculationResult::divide_by(CalculationResult const& other, Layout::Node const* layout_node)
540{
541 // We know from validation when resolving the type, that `other` must be a <number> or <integer>.
542 // Both of these are represented as a Number.
543 auto denominator = other.m_value.get<Number>().value();
544 // FIXME: Dividing by 0 is invalid, and should be caught during parsing.
545 VERIFY(denominator != 0.0f);
546
547 m_value.visit(
548 [&](Number const& number) {
549 m_value = Number {
550 Number::Type::Number,
551 number.value() / denominator
552 };
553 },
554 [&](Angle const& angle) {
555 m_value = Angle::make_degrees(angle.to_degrees() / denominator);
556 },
557 [&](Frequency const& frequency) {
558 m_value = Frequency::make_hertz(frequency.to_hertz() / denominator);
559 },
560 [&](Length const& length) {
561 VERIFY(layout_node);
562 m_value = Length::make_px(length.to_px(*layout_node) / denominator);
563 },
564 [&](Time const& time) {
565 m_value = Time::make_seconds(time.to_seconds() / denominator);
566 },
567 [&](Percentage const& percentage) {
568 m_value = Percentage { percentage.value() / denominator };
569 });
570}
571
572ErrorOr<String> CalculatedStyleValue::to_string() const
573{
574 return String::formatted("calc({})", TRY(m_expression->to_string()));
575}
576
577bool CalculatedStyleValue::equals(StyleValue const& other) const
578{
579 if (type() != other.type())
580 return false;
581 // This is a case where comparing the strings actually makes sense.
582 return to_string().release_value_but_fixme_should_propagate_errors() == other.to_string().release_value_but_fixme_should_propagate_errors();
583}
584
585ErrorOr<String> CalculatedStyleValue::CalcNumberValue::to_string() const
586{
587 return value.visit(
588 [](Number const& number) -> ErrorOr<String> { return String::number(number.value()); },
589 [](NonnullOwnPtr<CalcNumberSum> const& sum) -> ErrorOr<String> { return String::formatted("({})", TRY(sum->to_string())); });
590}
591
592ErrorOr<String> CalculatedStyleValue::CalcValue::to_string() const
593{
594 return value.visit(
595 [](Number const& number) -> ErrorOr<String> { return String::number(number.value()); },
596 [](NonnullOwnPtr<CalcSum> const& sum) -> ErrorOr<String> { return String::formatted("({})", TRY(sum->to_string())); },
597 [](auto const& v) -> ErrorOr<String> { return v.to_string(); });
598}
599
600ErrorOr<String> CalculatedStyleValue::CalcSum::to_string() const
601{
602 StringBuilder builder;
603 TRY(builder.try_append(TRY(first_calc_product->to_string())));
604 for (auto const& item : zero_or_more_additional_calc_products)
605 TRY(builder.try_append(TRY(item->to_string())));
606 return builder.to_string();
607}
608
609ErrorOr<String> CalculatedStyleValue::CalcNumberSum::to_string() const
610{
611 StringBuilder builder;
612 TRY(builder.try_append(TRY(first_calc_number_product->to_string())));
613 for (auto const& item : zero_or_more_additional_calc_number_products)
614 TRY(builder.try_append(TRY(item->to_string())));
615 return builder.to_string();
616}
617
618ErrorOr<String> CalculatedStyleValue::CalcProduct::to_string() const
619{
620 StringBuilder builder;
621 TRY(builder.try_append(TRY(first_calc_value.to_string())));
622 for (auto const& item : zero_or_more_additional_calc_values)
623 TRY(builder.try_append(TRY(item->to_string())));
624 return builder.to_string();
625}
626
627ErrorOr<String> CalculatedStyleValue::CalcSumPartWithOperator::to_string() const
628{
629 return String::formatted(" {} {}", op == SumOperation::Add ? "+"sv : "-"sv, TRY(value->to_string()));
630}
631
632ErrorOr<String> CalculatedStyleValue::CalcProductPartWithOperator::to_string() const
633{
634 auto value_string = TRY(value.visit(
635 [](CalcValue const& v) { return v.to_string(); },
636 [](CalcNumberValue const& v) { return v.to_string(); }));
637 return String::formatted(" {} {}", op == ProductOperation::Multiply ? "*"sv : "/"sv, value_string);
638}
639
640ErrorOr<String> CalculatedStyleValue::CalcNumberProduct::to_string() const
641{
642 StringBuilder builder;
643 TRY(builder.try_append(TRY(first_calc_number_value.to_string())));
644 for (auto const& item : zero_or_more_additional_calc_number_values)
645 TRY(builder.try_append(TRY(item->to_string())));
646 return builder.to_string();
647}
648
649ErrorOr<String> CalculatedStyleValue::CalcNumberProductPartWithOperator::to_string() const
650{
651 return String::formatted(" {} {}", op == ProductOperation::Multiply ? "*"sv : "/"sv, TRY(value.to_string()));
652}
653
654ErrorOr<String> CalculatedStyleValue::CalcNumberSumPartWithOperator::to_string() const
655{
656 return String::formatted(" {} {}", op == SumOperation::Add ? "+"sv : "-"sv, TRY(value->to_string()));
657}
658
659Optional<Angle> CalculatedStyleValue::resolve_angle() const
660{
661 auto result = m_expression->resolve(nullptr, {});
662
663 if (result.value().has<Angle>())
664 return result.value().get<Angle>();
665 return {};
666}
667
668Optional<Angle> CalculatedStyleValue::resolve_angle_percentage(Angle const& percentage_basis) const
669{
670 auto result = m_expression->resolve(nullptr, percentage_basis);
671
672 return result.value().visit(
673 [&](Angle const& angle) -> Optional<Angle> {
674 return angle;
675 },
676 [&](Percentage const& percentage) -> Optional<Angle> {
677 return percentage_basis.percentage_of(percentage);
678 },
679 [&](auto const&) -> Optional<Angle> {
680 return {};
681 });
682}
683
684Optional<Frequency> CalculatedStyleValue::resolve_frequency() const
685{
686 auto result = m_expression->resolve(nullptr, {});
687
688 if (result.value().has<Frequency>())
689 return result.value().get<Frequency>();
690 return {};
691}
692
693Optional<Frequency> CalculatedStyleValue::resolve_frequency_percentage(Frequency const& percentage_basis) const
694{
695 auto result = m_expression->resolve(nullptr, percentage_basis);
696
697 return result.value().visit(
698 [&](Frequency const& frequency) -> Optional<Frequency> {
699 return frequency;
700 },
701 [&](Percentage const& percentage) -> Optional<Frequency> {
702 return percentage_basis.percentage_of(percentage);
703 },
704 [&](auto const&) -> Optional<Frequency> {
705 return {};
706 });
707}
708
709Optional<Length> CalculatedStyleValue::resolve_length(Layout::Node const& layout_node) const
710{
711 auto result = m_expression->resolve(&layout_node, {});
712
713 if (result.value().has<Length>())
714 return result.value().get<Length>();
715 return {};
716}
717
718Optional<Length> CalculatedStyleValue::resolve_length_percentage(Layout::Node const& layout_node, Length const& percentage_basis) const
719{
720 auto result = m_expression->resolve(&layout_node, percentage_basis);
721
722 return result.value().visit(
723 [&](Length const& length) -> Optional<Length> {
724 return length;
725 },
726 [&](Percentage const& percentage) -> Optional<Length> {
727 return percentage_basis.percentage_of(percentage);
728 },
729 [&](auto const&) -> Optional<Length> {
730 return {};
731 });
732}
733
734Optional<Percentage> CalculatedStyleValue::resolve_percentage() const
735{
736 auto result = m_expression->resolve(nullptr, {});
737 if (result.value().has<Percentage>())
738 return result.value().get<Percentage>();
739 return {};
740}
741
742Optional<Time> CalculatedStyleValue::resolve_time() const
743{
744 auto result = m_expression->resolve(nullptr, {});
745
746 if (result.value().has<Time>())
747 return result.value().get<Time>();
748 return {};
749}
750
751Optional<Time> CalculatedStyleValue::resolve_time_percentage(Time const& percentage_basis) const
752{
753 auto result = m_expression->resolve(nullptr, percentage_basis);
754
755 return result.value().visit(
756 [&](Time const& time) -> Optional<Time> {
757 return time;
758 },
759 [&](auto const&) -> Optional<Time> {
760 return {};
761 });
762}
763
764Optional<float> CalculatedStyleValue::resolve_number()
765{
766 auto result = m_expression->resolve(nullptr, {});
767 if (result.value().has<Number>())
768 return result.value().get<Number>().value();
769 return {};
770}
771
772Optional<i64> CalculatedStyleValue::resolve_integer()
773{
774 auto result = m_expression->resolve(nullptr, {});
775 if (result.value().has<Number>())
776 return result.value().get<Number>().integer_value();
777 return {};
778}
779
780static bool is_number(CalculatedStyleValue::ResolvedType type)
781{
782 return type == CalculatedStyleValue::ResolvedType::Number || type == CalculatedStyleValue::ResolvedType::Integer;
783}
784
785static bool is_dimension(CalculatedStyleValue::ResolvedType type)
786{
787 return type != CalculatedStyleValue::ResolvedType::Number
788 && type != CalculatedStyleValue::ResolvedType::Integer
789 && type != CalculatedStyleValue::ResolvedType::Percentage;
790}
791
792template<typename SumWithOperator>
793static Optional<CalculatedStyleValue::ResolvedType> resolve_sum_type(CalculatedStyleValue::ResolvedType first_type, Vector<NonnullOwnPtr<SumWithOperator>> const& zero_or_more_additional_products)
794{
795 auto type = first_type;
796
797 for (auto const& product : zero_or_more_additional_products) {
798 auto maybe_product_type = product->resolved_type();
799 if (!maybe_product_type.has_value())
800 return {};
801 auto product_type = maybe_product_type.value();
802
803 // At + or -, check that both sides have the same type, or that one side is a <number> and the other is an <integer>.
804 // If both sides are the same type, resolve to that type.
805 if (product_type == type)
806 continue;
807
808 // If one side is a <number> and the other is an <integer>, resolve to <number>.
809 if (is_number(type) && is_number(product_type)) {
810 type = CalculatedStyleValue::ResolvedType::Number;
811 continue;
812 }
813
814 // FIXME: calc() handles <percentage> by allowing them to pretend to be whatever <dimension> type is allowed at this location.
815 // Since we can't easily check what that type is, we just allow <percentage> to combine with any other <dimension> type.
816 if (type == CalculatedStyleValue::ResolvedType::Percentage && is_dimension(product_type)) {
817 type = product_type;
818 continue;
819 }
820 if (is_dimension(type) && product_type == CalculatedStyleValue::ResolvedType::Percentage)
821 continue;
822
823 return {};
824 }
825 return type;
826}
827
828Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcSum::resolved_type() const
829{
830 auto maybe_type = first_calc_product->resolved_type();
831 if (!maybe_type.has_value())
832 return {};
833 auto type = maybe_type.value();
834 return resolve_sum_type(type, zero_or_more_additional_calc_products);
835}
836
837// https://www.w3.org/TR/CSS2/visufx.html#value-def-shape
838Gfx::FloatRect EdgeRect::resolved(Layout::Node const& layout_node, Gfx::FloatRect border_box) const
839{
840 // In CSS 2.1, the only valid <shape> value is: rect(<top>, <right>, <bottom>, <left>) where
841 // <top> and <bottom> specify offsets from the top border edge of the box, and <right>, and
842 // <left> specify offsets from the left border edge of the box.
843
844 // The value 'auto' means that a given edge of the clipping region will be the same as the edge
845 // of the element's generated border box (i.e., 'auto' means the same as '0' for <top> and
846 // <left>, the same as the used value of the height plus the sum of vertical padding and border
847 // widths for <bottom>, and the same as the used value of the width plus the sum of the
848 // horizontal padding and border widths for <right>, such that four 'auto' values result in the
849 // clipping region being the same as the element's border box).
850 auto left = border_box.left() + (left_edge.is_auto() ? 0 : left_edge.to_px(layout_node)).value();
851 auto top = border_box.top() + (top_edge.is_auto() ? 0 : top_edge.to_px(layout_node)).value();
852 auto right = border_box.left() + (right_edge.is_auto() ? border_box.width() : right_edge.to_px(layout_node)).value();
853 auto bottom = border_box.top() + (bottom_edge.is_auto() ? border_box.height() : bottom_edge.to_px(layout_node)).value();
854 return Gfx::FloatRect {
855 left,
856 top,
857 right - left,
858 bottom - top
859 };
860}
861
862Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcNumberSum::resolved_type() const
863{
864 auto maybe_type = first_calc_number_product->resolved_type();
865 if (!maybe_type.has_value())
866 return {};
867 auto type = maybe_type.value();
868 return resolve_sum_type(type, zero_or_more_additional_calc_number_products);
869}
870
871template<typename ProductWithOperator>
872static Optional<CalculatedStyleValue::ResolvedType> resolve_product_type(CalculatedStyleValue::ResolvedType first_type, Vector<NonnullOwnPtr<ProductWithOperator>> const& zero_or_more_additional_values)
873{
874 auto type = first_type;
875
876 for (auto const& value : zero_or_more_additional_values) {
877 auto maybe_value_type = value->resolved_type();
878 if (!maybe_value_type.has_value())
879 return {};
880 auto value_type = maybe_value_type.value();
881
882 if (value->op == CalculatedStyleValue::ProductOperation::Multiply) {
883 // At *, check that at least one side is <number>.
884 if (!(is_number(type) || is_number(value_type)))
885 return {};
886 // If both sides are <integer>, resolve to <integer>.
887 if (type == CalculatedStyleValue::ResolvedType::Integer && value_type == CalculatedStyleValue::ResolvedType::Integer) {
888 type = CalculatedStyleValue::ResolvedType::Integer;
889 } else {
890 // Otherwise, resolve to the type of the other side.
891 if (is_number(type))
892 type = value_type;
893 }
894
895 continue;
896 } else {
897 VERIFY(value->op == CalculatedStyleValue::ProductOperation::Divide);
898 // At /, check that the right side is <number>.
899 if (!is_number(value_type))
900 return {};
901 // If the left side is <integer>, resolve to <number>.
902 if (type == CalculatedStyleValue::ResolvedType::Integer) {
903 type = CalculatedStyleValue::ResolvedType::Number;
904 } else {
905 // Otherwise, resolve to the type of the left side.
906 }
907
908 // FIXME: Division by zero makes the whole calc() expression invalid.
909 }
910 }
911 return type;
912}
913
914Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcProduct::resolved_type() const
915{
916 auto maybe_type = first_calc_value.resolved_type();
917 if (!maybe_type.has_value())
918 return {};
919 auto type = maybe_type.value();
920 return resolve_product_type(type, zero_or_more_additional_calc_values);
921}
922
923Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcSumPartWithOperator::resolved_type() const
924{
925 return value->resolved_type();
926}
927
928Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcNumberProduct::resolved_type() const
929{
930 auto maybe_type = first_calc_number_value.resolved_type();
931 if (!maybe_type.has_value())
932 return {};
933 auto type = maybe_type.value();
934 return resolve_product_type(type, zero_or_more_additional_calc_number_values);
935}
936
937Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcNumberProductPartWithOperator::resolved_type() const
938{
939 return value.resolved_type();
940}
941
942Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcNumberSumPartWithOperator::resolved_type() const
943{
944 return value->resolved_type();
945}
946
947Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcProductPartWithOperator::resolved_type() const
948{
949 return value.visit(
950 [](CalcValue const& calc_value) {
951 return calc_value.resolved_type();
952 },
953 [](CalcNumberValue const& calc_number_value) {
954 return calc_number_value.resolved_type();
955 });
956}
957
958Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcValue::resolved_type() const
959{
960 return value.visit(
961 [](Number const& number) -> Optional<CalculatedStyleValue::ResolvedType> {
962 return { number.is_integer() ? ResolvedType::Integer : ResolvedType::Number };
963 },
964 [](Angle const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Angle }; },
965 [](Frequency const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Frequency }; },
966 [](Length const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Length }; },
967 [](Percentage const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Percentage }; },
968 [](Time const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Time }; },
969 [](NonnullOwnPtr<CalcSum> const& sum) { return sum->resolved_type(); });
970}
971
972Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcNumberValue::resolved_type() const
973{
974 return value.visit(
975 [](Number const& number) -> Optional<CalculatedStyleValue::ResolvedType> {
976 return { number.is_integer() ? ResolvedType::Integer : ResolvedType::Number };
977 },
978 [](NonnullOwnPtr<CalcNumberSum> const& sum) { return sum->resolved_type(); });
979}
980
981CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcNumberValue::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
982{
983 return value.visit(
984 [&](Number const& number) -> CalculatedStyleValue::CalculationResult {
985 return CalculatedStyleValue::CalculationResult { number };
986 },
987 [&](NonnullOwnPtr<CalcNumberSum> const& sum) -> CalculatedStyleValue::CalculationResult {
988 return sum->resolve(layout_node, percentage_basis);
989 });
990}
991
992CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcValue::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
993{
994 return value.visit(
995 [&](NonnullOwnPtr<CalcSum> const& sum) -> CalculatedStyleValue::CalculationResult {
996 return sum->resolve(layout_node, percentage_basis);
997 },
998 [&](auto const& v) -> CalculatedStyleValue::CalculationResult {
999 return CalculatedStyleValue::CalculationResult { v };
1000 });
1001}
1002
1003CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcSum::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
1004{
1005 auto value = first_calc_product->resolve(layout_node, percentage_basis);
1006
1007 for (auto& additional_product : zero_or_more_additional_calc_products) {
1008 auto additional_value = additional_product->resolve(layout_node, percentage_basis);
1009
1010 if (additional_product->op == CalculatedStyleValue::SumOperation::Add)
1011 value.add(additional_value, layout_node, percentage_basis);
1012 else if (additional_product->op == CalculatedStyleValue::SumOperation::Subtract)
1013 value.subtract(additional_value, layout_node, percentage_basis);
1014 else
1015 VERIFY_NOT_REACHED();
1016 }
1017
1018 return value;
1019}
1020
1021CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcNumberSum::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
1022{
1023 auto value = first_calc_number_product->resolve(layout_node, percentage_basis);
1024
1025 for (auto& additional_product : zero_or_more_additional_calc_number_products) {
1026 auto additional_value = additional_product->resolve(layout_node, percentage_basis);
1027
1028 if (additional_product->op == CSS::CalculatedStyleValue::SumOperation::Add)
1029 value.add(additional_value, layout_node, percentage_basis);
1030 else if (additional_product->op == CalculatedStyleValue::SumOperation::Subtract)
1031 value.subtract(additional_value, layout_node, percentage_basis);
1032 else
1033 VERIFY_NOT_REACHED();
1034 }
1035
1036 return value;
1037}
1038
1039CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcProduct::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
1040{
1041 auto value = first_calc_value.resolve(layout_node, percentage_basis);
1042
1043 for (auto& additional_value : zero_or_more_additional_calc_values) {
1044 additional_value->value.visit(
1045 [&](CalculatedStyleValue::CalcValue const& calc_value) {
1046 VERIFY(additional_value->op == CalculatedStyleValue::ProductOperation::Multiply);
1047 auto resolved_value = calc_value.resolve(layout_node, percentage_basis);
1048 value.multiply_by(resolved_value, layout_node);
1049 },
1050 [&](CalculatedStyleValue::CalcNumberValue const& calc_number_value) {
1051 VERIFY(additional_value->op == CalculatedStyleValue::ProductOperation::Divide);
1052 auto resolved_calc_number_value = calc_number_value.resolve(layout_node, percentage_basis);
1053 // FIXME: Checking for division by 0 should happen during parsing.
1054 VERIFY(resolved_calc_number_value.value().get<Number>().value() != 0.0f);
1055 value.divide_by(resolved_calc_number_value, layout_node);
1056 });
1057 }
1058
1059 return value;
1060}
1061
1062CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcNumberProduct::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
1063{
1064 auto value = first_calc_number_value.resolve(layout_node, percentage_basis);
1065
1066 for (auto& additional_number_value : zero_or_more_additional_calc_number_values) {
1067 auto additional_value = additional_number_value->resolve(layout_node, percentage_basis);
1068
1069 if (additional_number_value->op == CalculatedStyleValue::ProductOperation::Multiply)
1070 value.multiply_by(additional_value, layout_node);
1071 else if (additional_number_value->op == CalculatedStyleValue::ProductOperation::Divide)
1072 value.divide_by(additional_value, layout_node);
1073 else
1074 VERIFY_NOT_REACHED();
1075 }
1076
1077 return value;
1078}
1079
1080CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcProductPartWithOperator::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
1081{
1082 return value.visit(
1083 [&](CalcValue const& calc_value) {
1084 return calc_value.resolve(layout_node, percentage_basis);
1085 },
1086 [&](CalcNumberValue const& calc_number_value) {
1087 return calc_number_value.resolve(layout_node, percentage_basis);
1088 });
1089}
1090
1091CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcSumPartWithOperator::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
1092{
1093 return value->resolve(layout_node, percentage_basis);
1094}
1095
1096CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcNumberProductPartWithOperator::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
1097{
1098 return value.resolve(layout_node, percentage_basis);
1099}
1100
1101CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcNumberSumPartWithOperator::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const
1102{
1103 return value->resolve(layout_node, percentage_basis);
1104}
1105
1106ErrorOr<String> ColorStyleValue::to_string() const
1107{
1108 return serialize_a_srgb_value(m_color);
1109}
1110
1111ErrorOr<String> ContentStyleValue::to_string() const
1112{
1113 if (has_alt_text())
1114 return String::formatted("{} / {}", TRY(m_properties.content->to_string()), TRY(m_properties.alt_text->to_string()));
1115 return m_properties.content->to_string();
1116}
1117
1118float Filter::Blur::resolved_radius(Layout::Node const& node) const
1119{
1120 // Default value when omitted is 0px.
1121 auto sigma = 0;
1122 if (radius.has_value())
1123 sigma = radius->resolved(node).to_px(node).value();
1124 // Note: The radius/sigma of the blur needs to be doubled for LibGfx's blur functions.
1125 return sigma * 2;
1126}
1127
1128Filter::DropShadow::Resolved Filter::DropShadow::resolved(Layout::Node const& node) const
1129{
1130 // The default value for omitted values is missing length values set to 0
1131 // and the missing used color is taken from the color property.
1132 return Resolved {
1133 offset_x.resolved(node).to_px(node).value(),
1134 offset_y.resolved(node).to_px(node).value(),
1135 radius.has_value() ? radius->resolved(node).to_px(node).value() : 0.0f,
1136 color.has_value() ? *color : node.computed_values().color()
1137 };
1138}
1139
1140float Filter::HueRotate::angle_degrees() const
1141{
1142 // Default value when omitted is 0deg.
1143 if (!angle.has_value())
1144 return 0.0f;
1145 return angle->visit([&](Angle const& angle) { return angle.to_degrees(); }, [&](auto) { return 0.0f; });
1146}
1147
1148float Filter::Color::resolved_amount() const
1149{
1150 if (amount.has_value()) {
1151 if (amount->is_percentage())
1152 return amount->percentage().as_fraction();
1153 return amount->number().value();
1154 }
1155 // All color filters (brightness, sepia, etc) have a default amount of 1.
1156 return 1.0f;
1157}
1158
1159ErrorOr<String> FilterValueListStyleValue::to_string() const
1160{
1161 StringBuilder builder {};
1162 bool first = true;
1163 for (auto& filter_function : filter_value_list()) {
1164 if (!first)
1165 TRY(builder.try_append(' '));
1166 TRY(filter_function.visit(
1167 [&](Filter::Blur const& blur) -> ErrorOr<void> {
1168 TRY(builder.try_append("blur("sv));
1169 if (blur.radius.has_value())
1170 TRY(builder.try_append(TRY(blur.radius->to_string())));
1171 return {};
1172 },
1173 [&](Filter::DropShadow const& drop_shadow) -> ErrorOr<void> {
1174 TRY(builder.try_appendff("drop-shadow({} {}"sv,
1175 drop_shadow.offset_x, drop_shadow.offset_y));
1176 if (drop_shadow.radius.has_value())
1177 TRY(builder.try_appendff(" {}", TRY(drop_shadow.radius->to_string())));
1178 if (drop_shadow.color.has_value()) {
1179 TRY(builder.try_append(' '));
1180 TRY(serialize_a_srgb_value(builder, *drop_shadow.color));
1181 }
1182 return {};
1183 },
1184 [&](Filter::HueRotate const& hue_rotate) -> ErrorOr<void> {
1185 TRY(builder.try_append("hue-rotate("sv));
1186 if (hue_rotate.angle.has_value()) {
1187 TRY(hue_rotate.angle->visit(
1188 [&](Angle const& angle) -> ErrorOr<void> {
1189 return builder.try_append(TRY(angle.to_string()));
1190 },
1191 [&](auto&) -> ErrorOr<void> {
1192 return builder.try_append('0');
1193 }));
1194 }
1195 return {};
1196 },
1197 [&](Filter::Color const& color) -> ErrorOr<void> {
1198 TRY(builder.try_appendff("{}(",
1199 [&] {
1200 switch (color.operation) {
1201 case Filter::Color::Operation::Brightness:
1202 return "brightness"sv;
1203 case Filter::Color::Operation::Contrast:
1204 return "contrast"sv;
1205 case Filter::Color::Operation::Grayscale:
1206 return "grayscale"sv;
1207 case Filter::Color::Operation::Invert:
1208 return "invert"sv;
1209 case Filter::Color::Operation::Opacity:
1210 return "opacity"sv;
1211 case Filter::Color::Operation::Saturate:
1212 return "saturate"sv;
1213 case Filter::Color::Operation::Sepia:
1214 return "sepia"sv;
1215 default:
1216 VERIFY_NOT_REACHED();
1217 }
1218 }()));
1219 if (color.amount.has_value())
1220 TRY(builder.try_append(TRY(color.amount->to_string())));
1221 return {};
1222 }));
1223 TRY(builder.try_append(')'));
1224 first = false;
1225 }
1226 return builder.to_string();
1227}
1228
1229ErrorOr<String> FlexStyleValue::to_string() const
1230{
1231 return String::formatted("{} {} {}", TRY(m_properties.grow->to_string()), TRY(m_properties.shrink->to_string()), TRY(m_properties.basis->to_string()));
1232}
1233
1234ErrorOr<String> FlexFlowStyleValue::to_string() const
1235{
1236 return String::formatted("{} {}", TRY(m_properties.flex_direction->to_string()), TRY(m_properties.flex_wrap->to_string()));
1237}
1238
1239ErrorOr<String> FontStyleValue::to_string() const
1240{
1241 return String::formatted("{} {} {} / {} {}", TRY(m_properties.font_style->to_string()), TRY(m_properties.font_weight->to_string()), TRY(m_properties.font_size->to_string()), TRY(m_properties.line_height->to_string()), TRY(m_properties.font_families->to_string()));
1242}
1243
1244ErrorOr<String> GridTrackPlacementShorthandStyleValue::to_string() const
1245{
1246 if (m_properties.end->grid_track_placement().is_auto())
1247 return String::formatted("{}", TRY(m_properties.start->grid_track_placement().to_string()));
1248 return String::formatted("{} / {}", TRY(m_properties.start->grid_track_placement().to_string()), TRY(m_properties.end->grid_track_placement().to_string()));
1249}
1250
1251ErrorOr<String> GridAreaShorthandStyleValue::to_string() const
1252{
1253 StringBuilder builder;
1254 if (!m_properties.row_start->as_grid_track_placement().grid_track_placement().is_auto())
1255 TRY(builder.try_appendff("{}", TRY(m_properties.row_start->as_grid_track_placement().grid_track_placement().to_string())));
1256 if (!m_properties.column_start->as_grid_track_placement().grid_track_placement().is_auto())
1257 TRY(builder.try_appendff(" / {}", TRY(m_properties.column_start->as_grid_track_placement().grid_track_placement().to_string())));
1258 if (!m_properties.row_end->as_grid_track_placement().grid_track_placement().is_auto())
1259 TRY(builder.try_appendff(" / {}", TRY(m_properties.row_end->as_grid_track_placement().grid_track_placement().to_string())));
1260 if (!m_properties.column_end->as_grid_track_placement().grid_track_placement().is_auto())
1261 TRY(builder.try_appendff(" / {}", TRY(m_properties.column_end->as_grid_track_placement().grid_track_placement().to_string())));
1262 return builder.to_string();
1263}
1264
1265ErrorOr<String> GridTrackPlacementStyleValue::to_string() const
1266{
1267 return m_grid_track_placement.to_string();
1268}
1269
1270ErrorOr<String> GridTemplateAreaStyleValue::to_string() const
1271{
1272 StringBuilder builder;
1273 for (size_t y = 0; y < m_grid_template_area.size(); ++y) {
1274 for (size_t x = 0; x < m_grid_template_area[y].size(); ++x) {
1275 TRY(builder.try_appendff("{}", m_grid_template_area[y][x]));
1276 if (x < m_grid_template_area[y].size() - 1)
1277 TRY(builder.try_append(" "sv));
1278 }
1279 if (y < m_grid_template_area.size() - 1)
1280 TRY(builder.try_append(", "sv));
1281 }
1282 return builder.to_string();
1283}
1284
1285ErrorOr<String> GridTrackSizeStyleValue::to_string() const
1286{
1287 return m_grid_track_size_list.to_string();
1288}
1289
1290ErrorOr<String> IdentifierStyleValue::to_string() const
1291{
1292 return String::from_utf8(CSS::string_from_value_id(m_id));
1293}
1294
1295bool IdentifierStyleValue::has_color() const
1296{
1297 switch (m_id) {
1298 case ValueID::Currentcolor:
1299 case ValueID::LibwebLink:
1300 case ValueID::LibwebPaletteActiveLink:
1301 case ValueID::LibwebPaletteActiveWindowBorder1:
1302 case ValueID::LibwebPaletteActiveWindowBorder2:
1303 case ValueID::LibwebPaletteActiveWindowTitle:
1304 case ValueID::LibwebPaletteBase:
1305 case ValueID::LibwebPaletteBaseText:
1306 case ValueID::LibwebPaletteButton:
1307 case ValueID::LibwebPaletteButtonText:
1308 case ValueID::LibwebPaletteDesktopBackground:
1309 case ValueID::LibwebPaletteFocusOutline:
1310 case ValueID::LibwebPaletteHighlightWindowBorder1:
1311 case ValueID::LibwebPaletteHighlightWindowBorder2:
1312 case ValueID::LibwebPaletteHighlightWindowTitle:
1313 case ValueID::LibwebPaletteHoverHighlight:
1314 case ValueID::LibwebPaletteInactiveSelection:
1315 case ValueID::LibwebPaletteInactiveSelectionText:
1316 case ValueID::LibwebPaletteInactiveWindowBorder1:
1317 case ValueID::LibwebPaletteInactiveWindowBorder2:
1318 case ValueID::LibwebPaletteInactiveWindowTitle:
1319 case ValueID::LibwebPaletteLink:
1320 case ValueID::LibwebPaletteMenuBase:
1321 case ValueID::LibwebPaletteMenuBaseText:
1322 case ValueID::LibwebPaletteMenuSelection:
1323 case ValueID::LibwebPaletteMenuSelectionText:
1324 case ValueID::LibwebPaletteMenuStripe:
1325 case ValueID::LibwebPaletteMovingWindowBorder1:
1326 case ValueID::LibwebPaletteMovingWindowBorder2:
1327 case ValueID::LibwebPaletteMovingWindowTitle:
1328 case ValueID::LibwebPaletteRubberBandBorder:
1329 case ValueID::LibwebPaletteRubberBandFill:
1330 case ValueID::LibwebPaletteRuler:
1331 case ValueID::LibwebPaletteRulerActiveText:
1332 case ValueID::LibwebPaletteRulerBorder:
1333 case ValueID::LibwebPaletteRulerInactiveText:
1334 case ValueID::LibwebPaletteSelection:
1335 case ValueID::LibwebPaletteSelectionText:
1336 case ValueID::LibwebPaletteSyntaxComment:
1337 case ValueID::LibwebPaletteSyntaxControlKeyword:
1338 case ValueID::LibwebPaletteSyntaxIdentifier:
1339 case ValueID::LibwebPaletteSyntaxKeyword:
1340 case ValueID::LibwebPaletteSyntaxNumber:
1341 case ValueID::LibwebPaletteSyntaxOperator:
1342 case ValueID::LibwebPaletteSyntaxPreprocessorStatement:
1343 case ValueID::LibwebPaletteSyntaxPreprocessorValue:
1344 case ValueID::LibwebPaletteSyntaxPunctuation:
1345 case ValueID::LibwebPaletteSyntaxString:
1346 case ValueID::LibwebPaletteSyntaxType:
1347 case ValueID::LibwebPaletteTextCursor:
1348 case ValueID::LibwebPaletteThreedHighlight:
1349 case ValueID::LibwebPaletteThreedShadow1:
1350 case ValueID::LibwebPaletteThreedShadow2:
1351 case ValueID::LibwebPaletteVisitedLink:
1352 case ValueID::LibwebPaletteWindow:
1353 case ValueID::LibwebPaletteWindowText:
1354 return true;
1355 default:
1356 return false;
1357 }
1358}
1359
1360Color IdentifierStyleValue::to_color(Layout::NodeWithStyle const& node) const
1361{
1362 if (id() == CSS::ValueID::Currentcolor) {
1363 if (!node.has_style())
1364 return Color::Black;
1365 return node.computed_values().color();
1366 }
1367
1368 auto& document = node.document();
1369 if (id() == CSS::ValueID::LibwebLink)
1370 return document.link_color();
1371
1372 if (!document.page())
1373 return {};
1374
1375 auto palette = document.page()->palette();
1376 switch (id()) {
1377 case CSS::ValueID::LibwebPaletteDesktopBackground:
1378 return palette.color(ColorRole::DesktopBackground);
1379 case CSS::ValueID::LibwebPaletteActiveWindowBorder1:
1380 return palette.color(ColorRole::ActiveWindowBorder1);
1381 case CSS::ValueID::LibwebPaletteActiveWindowBorder2:
1382 return palette.color(ColorRole::ActiveWindowBorder2);
1383 case CSS::ValueID::LibwebPaletteActiveWindowTitle:
1384 return palette.color(ColorRole::ActiveWindowTitle);
1385 case CSS::ValueID::LibwebPaletteInactiveWindowBorder1:
1386 return palette.color(ColorRole::InactiveWindowBorder1);
1387 case CSS::ValueID::LibwebPaletteInactiveWindowBorder2:
1388 return palette.color(ColorRole::InactiveWindowBorder2);
1389 case CSS::ValueID::LibwebPaletteInactiveWindowTitle:
1390 return palette.color(ColorRole::InactiveWindowTitle);
1391 case CSS::ValueID::LibwebPaletteMovingWindowBorder1:
1392 return palette.color(ColorRole::MovingWindowBorder1);
1393 case CSS::ValueID::LibwebPaletteMovingWindowBorder2:
1394 return palette.color(ColorRole::MovingWindowBorder2);
1395 case CSS::ValueID::LibwebPaletteMovingWindowTitle:
1396 return palette.color(ColorRole::MovingWindowTitle);
1397 case CSS::ValueID::LibwebPaletteHighlightWindowBorder1:
1398 return palette.color(ColorRole::HighlightWindowBorder1);
1399 case CSS::ValueID::LibwebPaletteHighlightWindowBorder2:
1400 return palette.color(ColorRole::HighlightWindowBorder2);
1401 case CSS::ValueID::LibwebPaletteHighlightWindowTitle:
1402 return palette.color(ColorRole::HighlightWindowTitle);
1403 case CSS::ValueID::LibwebPaletteMenuStripe:
1404 return palette.color(ColorRole::MenuStripe);
1405 case CSS::ValueID::LibwebPaletteMenuBase:
1406 return palette.color(ColorRole::MenuBase);
1407 case CSS::ValueID::LibwebPaletteMenuBaseText:
1408 return palette.color(ColorRole::MenuBaseText);
1409 case CSS::ValueID::LibwebPaletteMenuSelection:
1410 return palette.color(ColorRole::MenuSelection);
1411 case CSS::ValueID::LibwebPaletteMenuSelectionText:
1412 return palette.color(ColorRole::MenuSelectionText);
1413 case CSS::ValueID::LibwebPaletteWindow:
1414 return palette.color(ColorRole::Window);
1415 case CSS::ValueID::LibwebPaletteWindowText:
1416 return palette.color(ColorRole::WindowText);
1417 case CSS::ValueID::LibwebPaletteButton:
1418 return palette.color(ColorRole::Button);
1419 case CSS::ValueID::LibwebPaletteButtonText:
1420 return palette.color(ColorRole::ButtonText);
1421 case CSS::ValueID::LibwebPaletteBase:
1422 return palette.color(ColorRole::Base);
1423 case CSS::ValueID::LibwebPaletteBaseText:
1424 return palette.color(ColorRole::BaseText);
1425 case CSS::ValueID::LibwebPaletteThreedHighlight:
1426 return palette.color(ColorRole::ThreedHighlight);
1427 case CSS::ValueID::LibwebPaletteThreedShadow1:
1428 return palette.color(ColorRole::ThreedShadow1);
1429 case CSS::ValueID::LibwebPaletteThreedShadow2:
1430 return palette.color(ColorRole::ThreedShadow2);
1431 case CSS::ValueID::LibwebPaletteHoverHighlight:
1432 return palette.color(ColorRole::HoverHighlight);
1433 case CSS::ValueID::LibwebPaletteSelection:
1434 return palette.color(ColorRole::Selection);
1435 case CSS::ValueID::LibwebPaletteSelectionText:
1436 return palette.color(ColorRole::SelectionText);
1437 case CSS::ValueID::LibwebPaletteInactiveSelection:
1438 return palette.color(ColorRole::InactiveSelection);
1439 case CSS::ValueID::LibwebPaletteInactiveSelectionText:
1440 return palette.color(ColorRole::InactiveSelectionText);
1441 case CSS::ValueID::LibwebPaletteRubberBandFill:
1442 return palette.color(ColorRole::RubberBandFill);
1443 case CSS::ValueID::LibwebPaletteRubberBandBorder:
1444 return palette.color(ColorRole::RubberBandBorder);
1445 case CSS::ValueID::LibwebPaletteLink:
1446 return palette.color(ColorRole::Link);
1447 case CSS::ValueID::LibwebPaletteActiveLink:
1448 return palette.color(ColorRole::ActiveLink);
1449 case CSS::ValueID::LibwebPaletteVisitedLink:
1450 return palette.color(ColorRole::VisitedLink);
1451 case CSS::ValueID::LibwebPaletteRuler:
1452 return palette.color(ColorRole::Ruler);
1453 case CSS::ValueID::LibwebPaletteRulerBorder:
1454 return palette.color(ColorRole::RulerBorder);
1455 case CSS::ValueID::LibwebPaletteRulerActiveText:
1456 return palette.color(ColorRole::RulerActiveText);
1457 case CSS::ValueID::LibwebPaletteRulerInactiveText:
1458 return palette.color(ColorRole::RulerInactiveText);
1459 case CSS::ValueID::LibwebPaletteTextCursor:
1460 return palette.color(ColorRole::TextCursor);
1461 case CSS::ValueID::LibwebPaletteFocusOutline:
1462 return palette.color(ColorRole::FocusOutline);
1463 case CSS::ValueID::LibwebPaletteSyntaxComment:
1464 return palette.color(ColorRole::SyntaxComment);
1465 case CSS::ValueID::LibwebPaletteSyntaxNumber:
1466 return palette.color(ColorRole::SyntaxNumber);
1467 case CSS::ValueID::LibwebPaletteSyntaxString:
1468 return palette.color(ColorRole::SyntaxString);
1469 case CSS::ValueID::LibwebPaletteSyntaxType:
1470 return palette.color(ColorRole::SyntaxType);
1471 case CSS::ValueID::LibwebPaletteSyntaxPunctuation:
1472 return palette.color(ColorRole::SyntaxPunctuation);
1473 case CSS::ValueID::LibwebPaletteSyntaxOperator:
1474 return palette.color(ColorRole::SyntaxOperator);
1475 case CSS::ValueID::LibwebPaletteSyntaxKeyword:
1476 return palette.color(ColorRole::SyntaxKeyword);
1477 case CSS::ValueID::LibwebPaletteSyntaxControlKeyword:
1478 return palette.color(ColorRole::SyntaxControlKeyword);
1479 case CSS::ValueID::LibwebPaletteSyntaxIdentifier:
1480 return palette.color(ColorRole::SyntaxIdentifier);
1481 case CSS::ValueID::LibwebPaletteSyntaxPreprocessorStatement:
1482 return palette.color(ColorRole::SyntaxPreprocessorStatement);
1483 case CSS::ValueID::LibwebPaletteSyntaxPreprocessorValue:
1484 return palette.color(ColorRole::SyntaxPreprocessorValue);
1485 default:
1486 return {};
1487 }
1488}
1489
1490ImageStyleValue::ImageStyleValue(AK::URL const& url)
1491 : AbstractImageStyleValue(Type::Image)
1492 , m_url(url)
1493{
1494}
1495
1496void ImageStyleValue::load_any_resources(DOM::Document& document)
1497{
1498 if (resource())
1499 return;
1500
1501 m_document = &document;
1502 auto request = LoadRequest::create_for_url_on_page(m_url, document.page());
1503 set_resource(ResourceLoader::the().load_resource(Resource::Type::Image, request));
1504}
1505
1506void ImageStyleValue::resource_did_load()
1507{
1508 if (!m_document)
1509 return;
1510 // FIXME: Do less than a full repaint if possible?
1511 if (m_document && m_document->browsing_context())
1512 m_document->browsing_context()->set_needs_display();
1513
1514 if (resource()->is_animated() && resource()->frame_count() > 1) {
1515 m_timer = Platform::Timer::create();
1516 m_timer->set_interval(resource()->frame_duration(0));
1517 m_timer->on_timeout = [this] { animate(); };
1518 m_timer->start();
1519 }
1520}
1521
1522void ImageStyleValue::animate()
1523{
1524 m_current_frame_index = (m_current_frame_index + 1) % resource()->frame_count();
1525 auto current_frame_duration = resource()->frame_duration(m_current_frame_index);
1526
1527 if (current_frame_duration != m_timer->interval())
1528 m_timer->restart(current_frame_duration);
1529
1530 if (m_current_frame_index == resource()->frame_count() - 1) {
1531 ++m_loops_completed;
1532 if (m_loops_completed > 0 && m_loops_completed == resource()->loop_count())
1533 m_timer->stop();
1534 }
1535
1536 if (on_animate)
1537 on_animate();
1538}
1539
1540Gfx::Bitmap const* ImageStyleValue::bitmap(size_t frame_index) const
1541{
1542 if (!resource())
1543 return nullptr;
1544 return resource()->bitmap(frame_index);
1545}
1546
1547ErrorOr<String> ImageStyleValue::to_string() const
1548{
1549 return serialize_a_url(m_url.to_deprecated_string());
1550}
1551
1552bool ImageStyleValue::equals(StyleValue const& other) const
1553{
1554 if (type() != other.type())
1555 return false;
1556 return m_url == other.as_image().m_url;
1557}
1558
1559Optional<CSSPixels> ImageStyleValue::natural_width() const
1560{
1561 if (auto* b = bitmap(0); b != nullptr)
1562 return b->width();
1563 return {};
1564}
1565
1566Optional<CSSPixels> ImageStyleValue::natural_height() const
1567{
1568 if (auto* b = bitmap(0); b != nullptr)
1569 return b->height();
1570 return {};
1571}
1572
1573void ImageStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering) const
1574{
1575 if (auto* b = bitmap(m_current_frame_index); b != nullptr)
1576 context.painter().draw_scaled_bitmap(dest_rect.to_type<int>(), *b, bitmap(0)->rect(), 1.0f, to_gfx_scaling_mode(image_rendering));
1577}
1578
1579static ErrorOr<void> serialize_color_stop_list(StringBuilder& builder, auto const& color_stop_list)
1580{
1581 bool first = true;
1582 for (auto const& element : color_stop_list) {
1583 if (!first)
1584 TRY(builder.try_append(", "sv));
1585
1586 if (element.transition_hint.has_value())
1587 TRY(builder.try_appendff("{}, "sv, TRY(element.transition_hint->value.to_string())));
1588
1589 TRY(serialize_a_srgb_value(builder, element.color_stop.color));
1590 for (auto position : Array { &element.color_stop.position, &element.color_stop.second_position }) {
1591 if (position->has_value())
1592 TRY(builder.try_appendff(" {}"sv, TRY((*position)->to_string())));
1593 }
1594 first = false;
1595 }
1596 return {};
1597}
1598
1599ErrorOr<String> LinearGradientStyleValue::to_string() const
1600{
1601 StringBuilder builder;
1602 auto side_or_corner_to_string = [](SideOrCorner value) {
1603 switch (value) {
1604 case SideOrCorner::Top:
1605 return "top"sv;
1606 case SideOrCorner::Bottom:
1607 return "bottom"sv;
1608 case SideOrCorner::Left:
1609 return "left"sv;
1610 case SideOrCorner::Right:
1611 return "right"sv;
1612 case SideOrCorner::TopLeft:
1613 return "top left"sv;
1614 case SideOrCorner::TopRight:
1615 return "top right"sv;
1616 case SideOrCorner::BottomLeft:
1617 return "bottom left"sv;
1618 case SideOrCorner::BottomRight:
1619 return "bottom right"sv;
1620 default:
1621 VERIFY_NOT_REACHED();
1622 }
1623 };
1624
1625 if (m_properties.gradient_type == GradientType::WebKit)
1626 TRY(builder.try_append("-webkit-"sv));
1627 if (is_repeating())
1628 TRY(builder.try_append("repeating-"sv));
1629 TRY(builder.try_append("linear-gradient("sv));
1630 TRY(m_properties.direction.visit(
1631 [&](SideOrCorner side_or_corner) -> ErrorOr<void> {
1632 return builder.try_appendff("{}{}, "sv, m_properties.gradient_type == GradientType::Standard ? "to "sv : ""sv, side_or_corner_to_string(side_or_corner));
1633 },
1634 [&](Angle const& angle) -> ErrorOr<void> {
1635 return builder.try_appendff("{}, "sv, TRY(angle.to_string()));
1636 }));
1637
1638 TRY(serialize_color_stop_list(builder, m_properties.color_stop_list));
1639 TRY(builder.try_append(")"sv));
1640 return builder.to_string();
1641}
1642
1643bool LinearGradientStyleValue::equals(StyleValue const& other_) const
1644{
1645 if (type() != other_.type())
1646 return false;
1647 auto& other = other_.as_linear_gradient();
1648 return m_properties == other.m_properties;
1649}
1650
1651float LinearGradientStyleValue::angle_degrees(CSSPixelSize gradient_size) const
1652{
1653 auto corner_angle_degrees = [&] {
1654 return static_cast<float>(atan2(gradient_size.height().value(), gradient_size.width().value())) * 180 / AK::Pi<float>;
1655 };
1656 return m_properties.direction.visit(
1657 [&](SideOrCorner side_or_corner) {
1658 auto angle = [&] {
1659 switch (side_or_corner) {
1660 case SideOrCorner::Top:
1661 return 0.0f;
1662 case SideOrCorner::Bottom:
1663 return 180.0f;
1664 case SideOrCorner::Left:
1665 return 270.0f;
1666 case SideOrCorner::Right:
1667 return 90.0f;
1668 case SideOrCorner::TopRight:
1669 return corner_angle_degrees();
1670 case SideOrCorner::BottomLeft:
1671 return corner_angle_degrees() + 180.0f;
1672 case SideOrCorner::TopLeft:
1673 return -corner_angle_degrees();
1674 case SideOrCorner::BottomRight:
1675 return -(corner_angle_degrees() + 180.0f);
1676 default:
1677 VERIFY_NOT_REACHED();
1678 }
1679 }();
1680 // Note: For unknowable reasons the angles are opposite on the -webkit- version
1681 if (m_properties.gradient_type == GradientType::WebKit)
1682 return angle + 180.0f;
1683 return angle;
1684 },
1685 [&](Angle const& angle) {
1686 return angle.to_degrees();
1687 });
1688}
1689
1690void LinearGradientStyleValue::resolve_for_size(Layout::Node const& node, CSSPixelSize size) const
1691{
1692 if (m_resolved.has_value() && m_resolved->size == size)
1693 return;
1694 m_resolved = ResolvedData { Painting::resolve_linear_gradient_data(node, size, *this), size };
1695}
1696
1697void LinearGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering) const
1698{
1699 VERIFY(m_resolved.has_value());
1700 Painting::paint_linear_gradient(context, dest_rect, m_resolved->data);
1701}
1702
1703CSSPixelPoint PositionValue::resolved(Layout::Node const& node, CSSPixelRect const& rect) const
1704{
1705 // Note: A preset + a none default x/y_relative_to is impossible in the syntax (and makes little sense)
1706 CSSPixels x = horizontal_position.visit(
1707 [&](HorizontalPreset preset) -> CSSPixels {
1708 return rect.width() * [&] {
1709 switch (preset) {
1710 case HorizontalPreset::Left:
1711 return 0.0f;
1712 case HorizontalPreset::Center:
1713 return 0.5f;
1714 case HorizontalPreset::Right:
1715 return 1.0f;
1716 default:
1717 VERIFY_NOT_REACHED();
1718 }
1719 }();
1720 },
1721 [&](LengthPercentage length_percentage) -> CSSPixels {
1722 return length_percentage.resolved(node, Length::make_px(rect.width())).to_px(node);
1723 });
1724 CSSPixels y = vertical_position.visit(
1725 [&](VerticalPreset preset) -> CSSPixels {
1726 return rect.height() * [&] {
1727 switch (preset) {
1728 case VerticalPreset::Top:
1729 return 0.0f;
1730 case VerticalPreset::Center:
1731 return 0.5f;
1732 case VerticalPreset::Bottom:
1733 return 1.0f;
1734 default:
1735 VERIFY_NOT_REACHED();
1736 }
1737 }();
1738 },
1739 [&](LengthPercentage length_percentage) -> CSSPixels {
1740 return length_percentage.resolved(node, Length::make_px(rect.height())).to_px(node);
1741 });
1742 if (x_relative_to == HorizontalEdge::Right)
1743 x = rect.width() - x;
1744 if (y_relative_to == VerticalEdge::Bottom)
1745 y = rect.height() - y;
1746 return CSSPixelPoint { rect.x() + x, rect.y() + y };
1747}
1748
1749ErrorOr<void> PositionValue::serialize(StringBuilder& builder) const
1750{
1751 // Note: This means our serialization with simplify any with explicit edges that are just `top left`.
1752 bool has_relative_edges = x_relative_to == HorizontalEdge::Right || y_relative_to == VerticalEdge::Bottom;
1753 if (has_relative_edges)
1754 TRY(builder.try_append(x_relative_to == HorizontalEdge::Left ? "left "sv : "right "sv));
1755 TRY(horizontal_position.visit(
1756 [&](HorizontalPreset preset) -> ErrorOr<void> {
1757 return builder.try_append([&] {
1758 switch (preset) {
1759 case HorizontalPreset::Left:
1760 return "left"sv;
1761 case HorizontalPreset::Center:
1762 return "center"sv;
1763 case HorizontalPreset::Right:
1764 return "right"sv;
1765 default:
1766 VERIFY_NOT_REACHED();
1767 }
1768 }());
1769 },
1770 [&](LengthPercentage length_percentage) -> ErrorOr<void> {
1771 return builder.try_appendff(TRY(length_percentage.to_string()));
1772 }));
1773 TRY(builder.try_append(' '));
1774 if (has_relative_edges)
1775 TRY(builder.try_append(y_relative_to == VerticalEdge::Top ? "top "sv : "bottom "sv));
1776 TRY(vertical_position.visit(
1777 [&](VerticalPreset preset) -> ErrorOr<void> {
1778 return builder.try_append([&] {
1779 switch (preset) {
1780 case VerticalPreset::Top:
1781 return "top"sv;
1782 case VerticalPreset::Center:
1783 return "center"sv;
1784 case VerticalPreset::Bottom:
1785 return "bottom"sv;
1786 default:
1787 VERIFY_NOT_REACHED();
1788 }
1789 }());
1790 },
1791 [&](LengthPercentage length_percentage) -> ErrorOr<void> {
1792 return builder.try_append(TRY(length_percentage.to_string()));
1793 }));
1794 return {};
1795}
1796
1797ErrorOr<String> RadialGradientStyleValue::to_string() const
1798{
1799 StringBuilder builder;
1800 if (is_repeating())
1801 TRY(builder.try_append("repeating-"sv));
1802 TRY(builder.try_appendff("radial-gradient({} "sv,
1803 m_properties.ending_shape == EndingShape::Circle ? "circle"sv : "ellipse"sv));
1804
1805 TRY(m_properties.size.visit(
1806 [&](Extent extent) -> ErrorOr<void> {
1807 return builder.try_append([&] {
1808 switch (extent) {
1809 case Extent::ClosestCorner:
1810 return "closest-corner"sv;
1811 case Extent::ClosestSide:
1812 return "closest-side"sv;
1813 case Extent::FarthestCorner:
1814 return "farthest-corner"sv;
1815 case Extent::FarthestSide:
1816 return "farthest-side"sv;
1817 default:
1818 VERIFY_NOT_REACHED();
1819 }
1820 }());
1821 },
1822 [&](CircleSize const& circle_size) -> ErrorOr<void> {
1823 return builder.try_append(TRY(circle_size.radius.to_string()));
1824 },
1825 [&](EllipseSize const& ellipse_size) -> ErrorOr<void> {
1826 return builder.try_appendff("{} {}", TRY(ellipse_size.radius_a.to_string()), TRY(ellipse_size.radius_b.to_string()));
1827 }));
1828
1829 if (m_properties.position != PositionValue::center()) {
1830 TRY(builder.try_appendff(" at "sv));
1831 TRY(m_properties.position.serialize(builder));
1832 }
1833
1834 TRY(builder.try_append(", "sv));
1835 TRY(serialize_color_stop_list(builder, m_properties.color_stop_list));
1836 TRY(builder.try_append(')'));
1837 return builder.to_string();
1838}
1839
1840Gfx::FloatSize RadialGradientStyleValue::resolve_size(Layout::Node const& node, Gfx::FloatPoint center, Gfx::FloatRect const& size) const
1841{
1842 auto const side_shape = [&](auto distance_function) {
1843 auto const distance_from = [&](float v, float a, float b, auto distance_function) {
1844 return distance_function(fabs(a - v), fabs(b - v));
1845 };
1846 auto x_dist = distance_from(center.x(), size.left(), size.right(), distance_function);
1847 auto y_dist = distance_from(center.y(), size.top(), size.bottom(), distance_function);
1848 if (m_properties.ending_shape == EndingShape::Circle) {
1849 auto dist = distance_function(x_dist, y_dist);
1850 return Gfx::FloatSize { dist, dist };
1851 } else {
1852 return Gfx::FloatSize { x_dist, y_dist };
1853 }
1854 };
1855
1856 auto const closest_side_shape = [&] {
1857 return side_shape(AK::min<float>);
1858 };
1859
1860 auto const farthest_side_shape = [&] {
1861 return side_shape(AK::max<float>);
1862 };
1863
1864 auto const corner_distance = [&](auto distance_compare, Gfx::FloatPoint& corner) {
1865 auto top_left_distance = size.top_left().distance_from(center);
1866 auto top_right_distance = size.top_right().distance_from(center);
1867 auto bottom_right_distance = size.bottom_right().distance_from(center);
1868 auto bottom_left_distance = size.bottom_left().distance_from(center);
1869 auto distance = top_left_distance;
1870 if (distance_compare(top_right_distance, distance)) {
1871 corner = size.top_right();
1872 distance = top_right_distance;
1873 }
1874 if (distance_compare(bottom_right_distance, distance)) {
1875 corner = size.top_right();
1876 distance = bottom_right_distance;
1877 }
1878 if (distance_compare(bottom_left_distance, distance)) {
1879 corner = size.top_right();
1880 distance = bottom_left_distance;
1881 }
1882 return distance;
1883 };
1884
1885 auto const closest_corner_distance = [&](Gfx::FloatPoint& corner) {
1886 return corner_distance([](float a, float b) { return a < b; }, corner);
1887 };
1888
1889 auto const farthest_corner_distance = [&](Gfx::FloatPoint& corner) {
1890 return corner_distance([](float a, float b) { return a > b; }, corner);
1891 };
1892
1893 auto const corner_shape = [&](auto corner_distance, auto get_shape) {
1894 Gfx::FloatPoint corner {};
1895 auto distance = corner_distance(corner);
1896 if (m_properties.ending_shape == EndingShape::Ellipse) {
1897 auto shape = get_shape();
1898 auto aspect_ratio = shape.width() / shape.height();
1899 auto p = corner - center;
1900 auto radius_a = AK::sqrt(p.y() * p.y() * aspect_ratio * aspect_ratio + p.x() * p.x());
1901 auto radius_b = radius_a / aspect_ratio;
1902 return Gfx::FloatSize { radius_a, radius_b };
1903 }
1904 return Gfx::FloatSize { distance, distance };
1905 };
1906
1907 // https://w3c.github.io/csswg-drafts/css-images/#radial-gradient-syntax
1908 auto resolved_size = m_properties.size.visit(
1909 [&](Extent extent) {
1910 switch (extent) {
1911 case Extent::ClosestSide:
1912 // The ending shape is sized so that it exactly meets the side of the gradient box closest to the gradient’s center.
1913 // If the shape is an ellipse, it exactly meets the closest side in each dimension.
1914 return closest_side_shape();
1915 case Extent::ClosestCorner:
1916 // The ending shape is sized so that it passes through the corner of the gradient box closest to the gradient’s center.
1917 // If the shape is an ellipse, the ending shape is given the same aspect-ratio it would have if closest-side were specified
1918 return corner_shape(closest_corner_distance, closest_side_shape);
1919 case Extent::FarthestCorner:
1920 // Same as closest-corner, except the ending shape is sized based on the farthest corner.
1921 // If the shape is an ellipse, the ending shape is given the same aspect ratio it would have if farthest-side were specified.
1922 return corner_shape(farthest_corner_distance, farthest_side_shape);
1923 case Extent::FarthestSide:
1924 // Same as closest-side, except the ending shape is sized based on the farthest side(s).
1925 return farthest_side_shape();
1926 default:
1927 VERIFY_NOT_REACHED();
1928 }
1929 },
1930 [&](CircleSize const& circle_size) {
1931 auto radius = circle_size.radius.to_px(node);
1932 return Gfx::FloatSize { radius, radius };
1933 },
1934 [&](EllipseSize const& ellipse_size) {
1935 auto radius_a = ellipse_size.radius_a.resolved(node, CSS::Length::make_px(size.width())).to_px(node);
1936 auto radius_b = ellipse_size.radius_b.resolved(node, CSS::Length::make_px(size.height())).to_px(node);
1937 return Gfx::FloatSize { radius_a, radius_b };
1938 });
1939
1940 // Handle degenerate cases
1941 // https://w3c.github.io/csswg-drafts/css-images/#degenerate-radials
1942
1943 constexpr auto arbitrary_small_number = 1e-10;
1944 constexpr auto arbitrary_large_number = 1e10;
1945
1946 // If the ending shape is a circle with zero radius:
1947 if (m_properties.ending_shape == EndingShape::Circle && resolved_size.is_empty()) {
1948 // Render as if the ending shape was a circle whose radius was an arbitrary very small number greater than zero.
1949 // This will make the gradient continue to look like a circle.
1950 return Gfx::FloatSize { arbitrary_small_number, arbitrary_small_number };
1951 }
1952 // If the ending shape has zero width (regardless of the height):
1953 if (resolved_size.width() <= 0) {
1954 // Render as if the ending shape was an ellipse whose height was an arbitrary very large number
1955 // and whose width was an arbitrary very small number greater than zero.
1956 // This will make the gradient look similar to a horizontal linear gradient that is mirrored across the center of the ellipse.
1957 // It also means that all color-stop positions specified with a percentage resolve to 0px.
1958 return Gfx::FloatSize { arbitrary_small_number, arbitrary_large_number };
1959 }
1960 // Otherwise, if the ending shape has zero height:
1961 if (resolved_size.height() <= 0) {
1962 // Render as if the ending shape was an ellipse whose width was an arbitrary very large number and whose height
1963 // was an arbitrary very small number greater than zero. This will make the gradient look like a solid-color image equal
1964 // to the color of the last color-stop, or equal to the average color of the gradient if it’s repeating.
1965 return Gfx::FloatSize { arbitrary_large_number, arbitrary_small_number };
1966 }
1967 return resolved_size;
1968}
1969
1970void RadialGradientStyleValue::resolve_for_size(Layout::Node const& node, CSSPixelSize paint_size) const
1971{
1972 CSSPixelRect gradient_box { { 0, 0 }, paint_size };
1973 auto center = m_properties.position.resolved(node, gradient_box).to_type<float>();
1974 auto gradient_size = resolve_size(node, center, gradient_box.to_type<float>());
1975 if (m_resolved.has_value() && m_resolved->gradient_size == gradient_size)
1976 return;
1977 m_resolved = ResolvedData {
1978 Painting::resolve_radial_gradient_data(node, gradient_size.to_type<CSSPixels>(), *this),
1979 gradient_size,
1980 center,
1981 };
1982}
1983
1984bool RadialGradientStyleValue::equals(StyleValue const& other) const
1985{
1986 if (type() != other.type())
1987 return false;
1988 auto& other_gradient = other.as_radial_gradient();
1989 return m_properties == other_gradient.m_properties;
1990}
1991
1992void RadialGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering) const
1993{
1994 VERIFY(m_resolved.has_value());
1995 Painting::paint_radial_gradient(context, dest_rect, m_resolved->data,
1996 context.rounded_device_point(m_resolved->center.to_type<CSSPixels>()),
1997 context.rounded_device_size(m_resolved->gradient_size.to_type<CSSPixels>()));
1998}
1999
2000ErrorOr<String> ConicGradientStyleValue::to_string() const
2001{
2002 StringBuilder builder;
2003 if (is_repeating())
2004 TRY(builder.try_append("repeating-"sv));
2005 TRY(builder.try_append("conic-gradient("sv));
2006 bool has_from_angle = false;
2007 bool has_at_position = false;
2008 if ((has_from_angle = m_properties.from_angle.to_degrees() != 0))
2009 TRY(builder.try_appendff("from {}", TRY(m_properties.from_angle.to_string())));
2010 if ((has_at_position = m_properties.position != PositionValue::center())) {
2011 if (has_from_angle)
2012 TRY(builder.try_append(' '));
2013 TRY(builder.try_appendff("at "sv));
2014 TRY(m_properties.position.serialize(builder));
2015 }
2016 if (has_from_angle || has_at_position)
2017 TRY(builder.try_append(", "sv));
2018 TRY(serialize_color_stop_list(builder, m_properties.color_stop_list));
2019 TRY(builder.try_append(')'));
2020 return builder.to_string();
2021}
2022
2023void ConicGradientStyleValue::resolve_for_size(Layout::Node const& node, CSSPixelSize size) const
2024{
2025 if (!m_resolved.has_value())
2026 m_resolved = ResolvedData { Painting::resolve_conic_gradient_data(node, *this), {} };
2027 m_resolved->position = m_properties.position.resolved(node, CSSPixelRect { { 0, 0 }, size });
2028}
2029
2030void ConicGradientStyleValue::paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering) const
2031{
2032 VERIFY(m_resolved.has_value());
2033 Painting::paint_conic_gradient(context, dest_rect, m_resolved->data, context.rounded_device_point(m_resolved->position));
2034}
2035
2036bool ConicGradientStyleValue::equals(StyleValue const& other) const
2037{
2038 if (type() != other.type())
2039 return false;
2040 auto& other_gradient = other.as_conic_gradient();
2041 return m_properties == other_gradient.m_properties;
2042}
2043
2044float ConicGradientStyleValue::angle_degrees() const
2045{
2046 return m_properties.from_angle.to_degrees();
2047}
2048
2049ErrorOr<String> ListStyleStyleValue::to_string() const
2050{
2051 return String::formatted("{} {} {}", TRY(m_properties.position->to_string()), TRY(m_properties.image->to_string()), TRY(m_properties.style_type->to_string()));
2052}
2053
2054ErrorOr<String> NumericStyleValue::to_string() const
2055{
2056 return m_value.visit(
2057 [](auto value) {
2058 return String::formatted("{}", value);
2059 });
2060}
2061
2062ErrorOr<String> OverflowStyleValue::to_string() const
2063{
2064 return String::formatted("{} {}", TRY(m_properties.overflow_x->to_string()), TRY(m_properties.overflow_y->to_string()));
2065}
2066
2067ErrorOr<String> PercentageStyleValue::to_string() const
2068{
2069 return m_percentage.to_string();
2070}
2071
2072ErrorOr<String> PositionStyleValue::to_string() const
2073{
2074 auto to_string = [](PositionEdge edge) {
2075 switch (edge) {
2076 case PositionEdge::Left:
2077 return "left";
2078 case PositionEdge::Right:
2079 return "right";
2080 case PositionEdge::Top:
2081 return "top";
2082 case PositionEdge::Bottom:
2083 return "bottom";
2084 }
2085 VERIFY_NOT_REACHED();
2086 };
2087
2088 return String::formatted("{} {} {} {}", to_string(m_properties.edge_x), TRY(m_properties.offset_x.to_string()), to_string(m_properties.edge_y), TRY(m_properties.offset_y.to_string()));
2089}
2090
2091ErrorOr<String> RectStyleValue::to_string() const
2092{
2093 return String::formatted("rect({} {} {} {})", m_rect.top_edge, m_rect.right_edge, m_rect.bottom_edge, m_rect.left_edge);
2094}
2095
2096ErrorOr<String> ShadowStyleValue::to_string() const
2097{
2098 StringBuilder builder;
2099 TRY(builder.try_appendff("{} {} {} {} {}", m_properties.color.to_deprecated_string(), TRY(m_properties.offset_x.to_string()), TRY(m_properties.offset_y.to_string()), TRY(m_properties.blur_radius.to_string()), TRY(m_properties.spread_distance.to_string())));
2100 if (m_properties.placement == ShadowPlacement::Inner)
2101 TRY(builder.try_append(" inset"sv));
2102 return builder.to_string();
2103}
2104
2105ErrorOr<String> TextDecorationStyleValue::to_string() const
2106{
2107 return String::formatted("{} {} {} {}", TRY(m_properties.line->to_string()), TRY(m_properties.thickness->to_string()), TRY(m_properties.style->to_string()), TRY(m_properties.color->to_string()));
2108}
2109
2110ErrorOr<String> TransformationStyleValue::to_string() const
2111{
2112 StringBuilder builder;
2113 TRY(builder.try_append(CSS::to_string(m_properties.transform_function)));
2114 TRY(builder.try_append('('));
2115 for (size_t i = 0; i < m_properties.values.size(); ++i) {
2116 TRY(builder.try_append(TRY(m_properties.values[i]->to_string())));
2117 if (i != m_properties.values.size() - 1)
2118 TRY(builder.try_append(", "sv));
2119 }
2120 TRY(builder.try_append(')'));
2121
2122 return builder.to_string();
2123}
2124
2125bool TransformationStyleValue::Properties::operator==(Properties const& other) const
2126{
2127 return transform_function == other.transform_function && values.span() == other.values.span();
2128}
2129
2130ErrorOr<String> UnresolvedStyleValue::to_string() const
2131{
2132 StringBuilder builder;
2133 for (auto& value : m_values)
2134 TRY(builder.try_append(TRY(value.to_string())));
2135 return builder.to_string();
2136}
2137
2138bool UnresolvedStyleValue::equals(StyleValue const& other) const
2139{
2140 if (type() != other.type())
2141 return false;
2142 // This is a case where comparing the strings actually makes sense.
2143 return to_string().release_value_but_fixme_should_propagate_errors() == other.to_string().release_value_but_fixme_should_propagate_errors();
2144}
2145
2146bool StyleValueList::Properties::operator==(Properties const& other) const
2147{
2148 return separator == other.separator && values.span() == other.values.span();
2149}
2150
2151ErrorOr<String> StyleValueList::to_string() const
2152{
2153 auto separator = ""sv;
2154 switch (m_properties.separator) {
2155 case Separator::Space:
2156 separator = " "sv;
2157 break;
2158 case Separator::Comma:
2159 separator = ", "sv;
2160 break;
2161 default:
2162 VERIFY_NOT_REACHED();
2163 }
2164
2165 StringBuilder builder;
2166 for (size_t i = 0; i < m_properties.values.size(); ++i) {
2167 TRY(builder.try_append(TRY(m_properties.values[i]->to_string())));
2168 if (i != m_properties.values.size() - 1)
2169 TRY(builder.try_append(separator));
2170 }
2171 return builder.to_string();
2172}
2173
2174ValueComparingNonnullRefPtr<ColorStyleValue> ColorStyleValue::create(Color color)
2175{
2176 if (color.value() == 0) {
2177 static auto transparent = adopt_ref(*new ColorStyleValue(color));
2178 return transparent;
2179 }
2180
2181 if (color == Color::from_rgb(0x000000)) {
2182 static auto black = adopt_ref(*new ColorStyleValue(color));
2183 return black;
2184 }
2185
2186 if (color == Color::from_rgb(0xffffff)) {
2187 static auto white = adopt_ref(*new ColorStyleValue(color));
2188 return white;
2189 }
2190
2191 return adopt_ref(*new ColorStyleValue(color));
2192}
2193
2194ValueComparingNonnullRefPtr<GridTemplateAreaStyleValue> GridTemplateAreaStyleValue::create(Vector<Vector<String>> grid_template_area)
2195{
2196 return adopt_ref(*new GridTemplateAreaStyleValue(grid_template_area));
2197}
2198
2199ValueComparingNonnullRefPtr<GridTrackPlacementStyleValue> GridTrackPlacementStyleValue::create(CSS::GridTrackPlacement grid_track_placement)
2200{
2201 return adopt_ref(*new GridTrackPlacementStyleValue(grid_track_placement));
2202}
2203
2204ValueComparingNonnullRefPtr<GridTrackSizeStyleValue> GridTrackSizeStyleValue::create(CSS::GridTrackSizeList grid_track_size_list)
2205{
2206 return adopt_ref(*new GridTrackSizeStyleValue(grid_track_size_list));
2207}
2208
2209ValueComparingNonnullRefPtr<GridTrackSizeStyleValue> GridTrackSizeStyleValue::make_auto()
2210{
2211 return adopt_ref(*new GridTrackSizeStyleValue(CSS::GridTrackSizeList()));
2212}
2213
2214ValueComparingNonnullRefPtr<RectStyleValue> RectStyleValue::create(EdgeRect rect)
2215{
2216 return adopt_ref(*new RectStyleValue(rect));
2217}
2218
2219ValueComparingNonnullRefPtr<LengthStyleValue> LengthStyleValue::create(Length const& length)
2220{
2221 if (length.is_auto()) {
2222 static auto value = adopt_ref(*new LengthStyleValue(CSS::Length::make_auto()));
2223 return value;
2224 }
2225 if (length.is_px()) {
2226 if (length.raw_value() == 0) {
2227 static auto value = adopt_ref(*new LengthStyleValue(CSS::Length::make_px(0)));
2228 return value;
2229 }
2230 if (length.raw_value() == 1) {
2231 static auto value = adopt_ref(*new LengthStyleValue(CSS::Length::make_px(1)));
2232 return value;
2233 }
2234 }
2235 return adopt_ref(*new LengthStyleValue(length));
2236}
2237
2238static Optional<CSS::Length> absolutized_length(CSS::Length const& length, CSSPixelRect const& viewport_rect, Gfx::FontPixelMetrics const& font_metrics, CSSPixels font_size, CSSPixels root_font_size)
2239{
2240 if (length.is_px())
2241 return {};
2242 if (length.is_absolute() || length.is_relative()) {
2243 auto px = length.to_px(viewport_rect, font_metrics, font_size, root_font_size);
2244 return CSS::Length::make_px(px);
2245 }
2246 return {};
2247}
2248
2249ValueComparingNonnullRefPtr<StyleValue const> StyleValue::absolutized(CSSPixelRect const&, Gfx::FontPixelMetrics const&, CSSPixels, CSSPixels) const
2250{
2251 return *this;
2252}
2253
2254ValueComparingNonnullRefPtr<StyleValue const> LengthStyleValue::absolutized(CSSPixelRect const& viewport_rect, Gfx::FontPixelMetrics const& font_metrics, CSSPixels font_size, CSSPixels root_font_size) const
2255{
2256 if (auto length = absolutized_length(m_length, viewport_rect, font_metrics, font_size, root_font_size); length.has_value())
2257 return LengthStyleValue::create(length.release_value());
2258 return *this;
2259}
2260
2261ValueComparingNonnullRefPtr<StyleValue const> ShadowStyleValue::absolutized(CSSPixelRect const& viewport_rect, Gfx::FontPixelMetrics const& font_metrics, CSSPixels font_size, CSSPixels root_font_size) const
2262{
2263 auto absolutized_offset_x = absolutized_length(m_properties.offset_x, viewport_rect, font_metrics, font_size, root_font_size).value_or(m_properties.offset_x);
2264 auto absolutized_offset_y = absolutized_length(m_properties.offset_y, viewport_rect, font_metrics, font_size, root_font_size).value_or(m_properties.offset_y);
2265 auto absolutized_blur_radius = absolutized_length(m_properties.blur_radius, viewport_rect, font_metrics, font_size, root_font_size).value_or(m_properties.blur_radius);
2266 auto absolutized_spread_distance = absolutized_length(m_properties.spread_distance, viewport_rect, font_metrics, font_size, root_font_size).value_or(m_properties.spread_distance);
2267 return ShadowStyleValue::create(m_properties.color, absolutized_offset_x, absolutized_offset_y, absolutized_blur_radius, absolutized_spread_distance, m_properties.placement);
2268}
2269
2270ValueComparingNonnullRefPtr<StyleValue const> BorderRadiusStyleValue::absolutized(CSSPixelRect const& viewport_rect, Gfx::FontPixelMetrics const& font_metrics, CSSPixels font_size, CSSPixels root_font_size) const
2271{
2272 if (m_properties.horizontal_radius.is_percentage() && m_properties.vertical_radius.is_percentage())
2273 return *this;
2274 auto absolutized_horizontal_radius = m_properties.horizontal_radius;
2275 auto absolutized_vertical_radius = m_properties.vertical_radius;
2276 if (!m_properties.horizontal_radius.is_percentage())
2277 absolutized_horizontal_radius = absolutized_length(m_properties.horizontal_radius.length(), viewport_rect, font_metrics, font_size, root_font_size).value_or(m_properties.horizontal_radius.length());
2278 if (!m_properties.vertical_radius.is_percentage())
2279 absolutized_vertical_radius = absolutized_length(m_properties.vertical_radius.length(), viewport_rect, font_metrics, font_size, root_font_size).value_or(m_properties.vertical_radius.length());
2280 return BorderRadiusStyleValue::create(absolutized_horizontal_radius, absolutized_vertical_radius);
2281}
2282
2283bool CalculatedStyleValue::contains_percentage() const
2284{
2285 return m_expression->contains_percentage();
2286}
2287
2288bool CalculatedStyleValue::CalcSum::contains_percentage() const
2289{
2290 if (first_calc_product->contains_percentage())
2291 return true;
2292 for (auto& part : zero_or_more_additional_calc_products) {
2293 if (part->contains_percentage())
2294 return true;
2295 }
2296 return false;
2297}
2298
2299bool CalculatedStyleValue::CalcSumPartWithOperator::contains_percentage() const
2300{
2301 return value->contains_percentage();
2302}
2303
2304bool CalculatedStyleValue::CalcProduct::contains_percentage() const
2305{
2306 if (first_calc_value.contains_percentage())
2307 return true;
2308 for (auto& part : zero_or_more_additional_calc_values) {
2309 if (part->contains_percentage())
2310 return true;
2311 }
2312 return false;
2313}
2314
2315bool CalculatedStyleValue::CalcProductPartWithOperator::contains_percentage() const
2316{
2317 return value.visit(
2318 [](CalcValue const& value) { return value.contains_percentage(); },
2319 [](CalcNumberValue const&) { return false; });
2320}
2321
2322bool CalculatedStyleValue::CalcValue::contains_percentage() const
2323{
2324 return value.visit(
2325 [](Percentage const&) { return true; },
2326 [](NonnullOwnPtr<CalcSum> const& sum) { return sum->contains_percentage(); },
2327 [](auto const&) { return false; });
2328}
2329
2330bool calculated_style_value_contains_percentage(CalculatedStyleValue const& value)
2331{
2332 return value.contains_percentage();
2333}
2334
2335}