Skip to main content

fleuron/style/properties/
computed.rs

1//! What one node computes to: every property resolved, ready for
2//! the box tree to ask.
3
4use std::collections::BTreeMap;
5
6use serde::Serialize;
7
8use crate::fonts::{FeatureSetting, GenericFamily};
9use crate::lines::HangingPunctuation;
10
11use super::Declaration;
12use super::background::{
13    Background, BackgroundPosition, BackgroundSize, SizeSource, no_background,
14};
15use super::counter::{Content, ListStyleType, StringSet};
16use super::edges::{Border, BorderRadius, CornerRadius, Edges};
17use super::exclusion::{Coord, Inset, Position, ShapeOutside, ShapePoint, ShapeSource, WrapFlow};
18use super::value::{
19    BorderCollapse, BoxDecorationBreak, Break, Color, ColumnSpan, DecorationLine, DecorationStyle,
20    Family, FontStyle, FontVariantAlternates, FontVariantCaps, FontVariantLigatures,
21    FontVariantNumeric, Hyphens, Length, NORMAL_LINE_HEIGHT, TextAlign, TextDecoration,
22    TextJustify, TextTransform, Width,
23};
24
25/// One node's resolved style: what every downstream pass reads.
26#[derive(Debug, Clone, PartialEq, Serialize)]
27pub struct ComputedStyle {
28    /// The face this style shapes with, resolved against the
29    /// registry. Family, slope and weight chose it; layout only needs
30    /// the answer.
31    pub font_id: u16,
32    /// The families asked for, in order, kept for diagnostics and for
33    /// the snapshot the style tree is reviewed through.
34    pub font_family: Vec<Family>,
35    /// Font size in points.
36    pub font_size: f32,
37    /// Upright or italic.
38    pub font_style: FontStyle,
39    /// Weight on the CSS 1–1000 scale.
40    pub font_weight: u16,
41    /// What text and rules are painted in.
42    pub color: Color,
43    /// Line height as a unitless multiple of the font size.
44    pub line_height: f32,
45    /// Extra advance after every glyph, in points, from
46    /// `letter-spacing`.
47    pub letter_spacing: f32,
48    /// Which capitals the run is drawn with.
49    pub font_variant_caps: FontVariantCaps,
50    /// The features the sheet asked the face for, from
51    /// `font-feature-settings`. They are asked for after the ones the
52    /// `font-variant` longhands name, so a tag in both is set here.
53    #[serde(skip_serializing_if = "Vec::is_empty")]
54    pub font_feature_settings: Vec<FeatureSetting>,
55    /// Which ligatures the run is set with.
56    #[serde(skip_serializing_if = "default_ligatures")]
57    pub font_variant_ligatures: FontVariantLigatures,
58    /// Which figures the run is set with.
59    #[serde(skip_serializing_if = "default_numeric")]
60    pub font_variant_numeric: FontVariantNumeric,
61    /// Which alternate glyphs the run is set with.
62    #[serde(skip_serializing_if = "default_alternates")]
63    pub font_variant_alternates: FontVariantAlternates,
64    /// What the run's letters are transformed to before shaping.
65    pub text_transform: TextTransform,
66    /// Which rules are drawn across the run, from
67    /// `text-decoration-line`.
68    #[serde(skip_serializing_if = "undecorated")]
69    pub text_decoration_line: DecorationLine,
70    /// What those rules are painted in, from
71    /// `text-decoration-color`. `None` is the colour of the text.
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub text_decoration_color: Option<Color>,
74    /// How each rule is drawn, from `text-decoration-style`.
75    #[serde(skip_serializing_if = "solid")]
76    pub text_decoration_style: DecorationStyle,
77    /// How thick each rule is, in points, from
78    /// `text-decoration-thickness`. `None` is the thickness the face
79    /// declares.
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub text_decoration_thickness: Option<f32>,
82    /// How lines fill the measure.
83    pub text_align: TextAlign,
84    /// What justification opens up to fill it.
85    pub text_justify: TextJustify,
86    /// Which marks may hang past the measure.
87    pub hanging_punctuation: HangingPunctuation,
88    /// First-line indent in points.
89    pub text_indent: f32,
90    /// Whether words may break at syllable boundaries.
91    pub hyphens: Hyphens,
92    /// Lines that must be left at the bottom of a fragment.
93    pub orphans: u16,
94    /// Lines that must be moved to the top of the next one.
95    pub widows: u16,
96    /// The named page this element's pages take, from `page`.
97    pub page: Option<String>,
98    /// What the element paints in place of children it has none of:
99    /// the ornament a thematic break is set with.
100    pub content: Content,
101    /// The named strings this element sets when the flow reaches it,
102    /// from `string-set`. This is where a running head's text comes
103    /// from.
104    #[serde(skip_serializing_if = "Vec::is_empty")]
105    pub string_set: Vec<StringSet>,
106    /// The folio the page this element opens takes, from
107    /// `counter-reset: page`.
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub counter_reset: Option<u32>,
110    /// The number the first note under this element takes, from
111    /// `counter-reset: note`. On the footnote area it restarts the
112    /// numbering on every page.
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub note_reset: Option<u32>,
115    /// Lines an initial letter is sunk over, from `initial-letter`.
116    /// Fewer than two is no drop cap.
117    pub initial_letter: u16,
118    /// Whether the element sits in the flow or against the page.
119    #[serde(skip_serializing_if = "in_flow")]
120    pub position: Position,
121    /// What the insets say, for an element that is against the page.
122    #[serde(skip_serializing_if = "unplaced")]
123    pub inset: Edges<Inset>,
124    /// Which layer the block paints in, from `z-index`. Higher paints
125    /// later, and `auto` is layer 0.
126    #[serde(skip_serializing_if = "ground_layer")]
127    pub z_index: i32,
128    /// How much of what the block paints shows, from `opacity`: 1 is
129    /// all of it and 0 is none. The blocks inside it show that much
130    /// of their own.
131    #[serde(skip_serializing_if = "opaque")]
132    pub opacity: f32,
133    /// Which side of this element the prose sets on, from `wrap-flow`.
134    #[serde(skip_serializing_if = "wraps_nothing")]
135    pub wrap_flow: WrapFlow,
136    /// The contour the prose sets around, from `shape-outside`.
137    #[serde(skip_serializing_if = "wraps_its_box")]
138    pub shape_outside: ShapeOutside,
139    /// How far the prose keeps off that contour, in points, from
140    /// `shape-margin`.
141    #[serde(skip_serializing_if = "no_shape_margin")]
142    pub shape_margin: f32,
143    /// Margins in points.
144    pub margin: Edges,
145    /// Padding in points, between the border and the content.
146    #[serde(skip_serializing_if = "no_padding")]
147    pub padding: Edges,
148    /// The four border edges.
149    #[serde(skip_serializing_if = "no_border")]
150    pub border: Edges<Border>,
151    /// How far each corner of the border box is rounded, from
152    /// `border-radius`. The background and the border follow the
153    /// corners.
154    #[serde(skip_serializing_if = "square")]
155    pub border_radius: BorderRadius,
156    /// What is painted behind the block: a tint, and an image over
157    /// it.
158    #[serde(skip_serializing_if = "no_background")]
159    pub background: Background,
160    /// What the decoration does where a page break splits the block.
161    #[serde(skip_serializing_if = "sliced")]
162    pub box_decoration_break: BoxDecorationBreak,
163    /// The width the box is asked to take, from `width`. A table reads
164    /// it off the cells of its first row to size its columns.
165    #[serde(skip_serializing_if = "auto_width")]
166    pub width: Width,
167    /// The height the content box is asked to take, from `height`.
168    #[serde(skip_serializing_if = "auto_width")]
169    pub height: Width,
170    /// The least height the content box takes, from `min-height`.
171    #[serde(skip_serializing_if = "auto_width")]
172    pub min_height: Width,
173    /// The most width an image takes, from `max-width`. `Auto` is
174    /// `none`.
175    #[serde(skip_serializing_if = "auto_width")]
176    pub max_width: Width,
177    /// The most height an image takes, from `max-height`. `Auto` is
178    /// `none`.
179    #[serde(skip_serializing_if = "auto_width")]
180    pub max_height: Width,
181    /// Whether a table's cells share their borders, from
182    /// `border-collapse`.
183    #[serde(skip_serializing_if = "separate")]
184    pub border_collapse: BorderCollapse,
185    /// The marker a list item is set with, from `list-style-type`.
186    #[serde(skip_serializing_if = "disc")]
187    pub list_style_type: ListStyleType,
188    /// Where a page break falls before this element.
189    pub break_before: Break,
190    /// Where one falls after it.
191    pub break_after: Break,
192    /// Whether this element may be split across pages.
193    pub break_inside: Break,
194    /// Whether this element is set across every column of a divided
195    /// page, from `column-span`.
196    #[serde(skip_serializing_if = "one_column")]
197    pub column_span: ColumnSpan,
198    /// The custom properties in force, by name, each with every
199    /// `var()` in its value already replaced. A child inherits them.
200    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
201    pub custom: BTreeMap<String, String>,
202}
203
204impl ComputedStyle {
205    /// The initial value of every property: what the root computes to
206    /// before any rule matches it.
207    pub fn initial() -> ComputedStyle {
208        ComputedStyle {
209            font_id: 0,
210            font_family: vec![Family::Generic(GenericFamily::Serif)],
211            font_size: 12.0,
212            font_style: FontStyle::Normal,
213            font_weight: 400,
214            color: Color::BLACK,
215            line_height: NORMAL_LINE_HEIGHT,
216            letter_spacing: 0.0,
217            font_variant_caps: FontVariantCaps::Normal,
218            font_feature_settings: Vec::new(),
219            font_variant_ligatures: FontVariantLigatures::NORMAL,
220            font_variant_numeric: FontVariantNumeric::NORMAL,
221            font_variant_alternates: FontVariantAlternates::Normal,
222            text_transform: TextTransform::None,
223            text_decoration_line: DecorationLine::NONE,
224            text_decoration_color: None,
225            text_decoration_style: DecorationStyle::Solid,
226            text_decoration_thickness: None,
227            text_align: TextAlign::Left,
228            text_justify: TextJustify::InterWord,
229            hanging_punctuation: HangingPunctuation::NONE,
230            text_indent: 0.0,
231            hyphens: Hyphens::None,
232            orphans: 2,
233            widows: 2,
234            page: None,
235            content: Content::None,
236            string_set: Vec::new(),
237            counter_reset: None,
238            note_reset: None,
239            initial_letter: 0,
240            position: Position::Static,
241            inset: Edges::all(Inset::Auto),
242            z_index: 0,
243            opacity: 1.0,
244            wrap_flow: WrapFlow::Auto,
245            shape_outside: ShapeOutside::None,
246            shape_margin: 0.0,
247            margin: Edges::all(0.0),
248            padding: Edges::all(0.0),
249            border: Edges::all(Border::NONE),
250            border_radius: BorderRadius::SQUARE,
251            background: Background::NONE,
252            box_decoration_break: BoxDecorationBreak::Slice,
253            width: Width::Auto,
254            height: Width::Auto,
255            min_height: Width::Auto,
256            max_width: Width::Auto,
257            max_height: Width::Auto,
258            border_collapse: BorderCollapse::Separate,
259            list_style_type: ListStyleType::Disc,
260            break_before: Break::Auto,
261            break_after: Break::Auto,
262            break_inside: Break::Auto,
263            column_span: ColumnSpan::None,
264            custom: BTreeMap::new(),
265        }
266    }
267
268    /// A child's starting point: inherited properties as they stand,
269    /// the rest back at their initial values.
270    pub fn inherit(&self) -> ComputedStyle {
271        ComputedStyle {
272            margin: Edges::all(0.0),
273            padding: Edges::all(0.0),
274            border: Edges::all(Border::NONE),
275            border_radius: BorderRadius::SQUARE,
276            background: Background::NONE,
277            box_decoration_break: BoxDecorationBreak::Slice,
278            width: Width::Auto,
279            height: Width::Auto,
280            min_height: Width::Auto,
281            max_width: Width::Auto,
282            max_height: Width::Auto,
283            content: Content::None,
284            string_set: Vec::new(),
285            counter_reset: None,
286            note_reset: None,
287            initial_letter: 0,
288            position: Position::Static,
289            inset: Edges::all(Inset::Auto),
290            z_index: 0,
291            opacity: 1.0,
292            wrap_flow: WrapFlow::Auto,
293            shape_outside: ShapeOutside::None,
294            shape_margin: 0.0,
295            break_before: Break::Auto,
296            break_after: Break::Auto,
297            break_inside: Break::Auto,
298            column_span: ColumnSpan::None,
299            ..self.clone()
300        }
301    }
302
303    /// Applies one declaration. `parent_size` is what `em` and
304    /// percentages in `font-size` measure against; `root_size` is
305    /// what `rem` does.
306    pub fn apply(&mut self, declaration: &Declaration, parent_size: f32, root_size: f32) {
307        match declaration {
308            Declaration::FontFamily(families) => self.font_family = families.clone(),
309            Declaration::FontSize(length) => {
310                self.font_size = length.to_points(parent_size, root_size).max(0.0)
311            }
312            Declaration::FontStyle(style) => self.font_style = *style,
313            Declaration::FontWeight(weight) => self.font_weight = *weight,
314            Declaration::Color(color) => self.color = *color,
315            Declaration::LineHeight(line_height) => {
316                self.line_height = line_height.to_multiple(self.font_size, root_size)
317            }
318            Declaration::LetterSpacing(length) => {
319                self.letter_spacing = length.to_points(self.font_size, root_size)
320            }
321            Declaration::FontVariantCaps(caps) => self.font_variant_caps = *caps,
322            Declaration::FontFeatureSettings(settings) => {
323                self.font_feature_settings = settings.clone()
324            }
325            Declaration::FontVariantLigatures(ligatures) => {
326                self.font_variant_ligatures = *ligatures
327            }
328            Declaration::FontVariantNumeric(numeric) => self.font_variant_numeric = *numeric,
329            Declaration::FontVariantAlternates(alternates) => {
330                self.font_variant_alternates = *alternates
331            }
332            Declaration::TextTransform(transform) => self.text_transform = *transform,
333            Declaration::TextDecorationLine(line) => self.text_decoration_line = *line,
334            Declaration::TextDecorationColor(color) => self.text_decoration_color = *color,
335            Declaration::TextDecorationStyle(style) => self.text_decoration_style = *style,
336            Declaration::TextDecorationThickness(length) => {
337                self.text_decoration_thickness =
338                    length.map(|length| length.to_points(self.font_size, root_size).max(0.0))
339            }
340            Declaration::TextAlign(align) => self.text_align = *align,
341            Declaration::TextJustify(justify) => self.text_justify = *justify,
342            Declaration::HangingPunctuation(hanging) => self.hanging_punctuation = *hanging,
343            Declaration::TextIndent(length) => {
344                self.text_indent = length.to_points(self.font_size, root_size)
345            }
346            Declaration::Hyphens(hyphens) => self.hyphens = *hyphens,
347            Declaration::Orphans(lines) => self.orphans = *lines,
348            Declaration::Widows(lines) => self.widows = *lines,
349            Declaration::Page(name) => self.page = name.clone(),
350            Declaration::Content(content) => self.content = content.clone(),
351            Declaration::StringSet(sets) => self.string_set = sets.clone(),
352            Declaration::CounterReset(counters) => {
353                self.counter_reset = counters.page;
354                self.note_reset = counters.note;
355            }
356            Declaration::InitialLetter(lines) => self.initial_letter = *lines,
357            Declaration::Position(position) => self.position = *position,
358            Declaration::Inset(edge, length) => {
359                *self.inset.edge(*edge) = match length {
360                    None => Inset::Auto,
361                    Some(Length::Percent(percent)) => Inset::Percent(*percent),
362                    Some(length) => Inset::Points(length.to_points(self.font_size, root_size)),
363                }
364            }
365            Declaration::ZIndex(layer) => self.z_index = *layer,
366            Declaration::Opacity(opacity) => self.opacity = *opacity,
367            Declaration::WrapFlow(wrap) => self.wrap_flow = *wrap,
368            Declaration::ShapeOutside(shape) => {
369                self.shape_outside = match shape {
370                    ShapeSource::None => ShapeOutside::None,
371                    ShapeSource::Auto => ShapeOutside::Auto,
372                    ShapeSource::Polygon(points) => ShapeOutside::Polygon(
373                        points
374                            .iter()
375                            .map(|(x, y)| ShapePoint {
376                                x: Coord::of(*x, self.font_size, root_size),
377                                y: Coord::of(*y, self.font_size, root_size),
378                            })
379                            .collect(),
380                    ),
381                }
382            }
383            Declaration::ShapeMargin(length) => {
384                self.shape_margin = length.to_points(self.font_size, root_size).max(0.0)
385            }
386            Declaration::Margin(edge, length) => {
387                *self.margin.edge(*edge) = length.to_points(self.font_size, root_size)
388            }
389            Declaration::Padding(edge, length) => {
390                *self.padding.edge(*edge) = length.to_points(self.font_size, root_size).max(0.0)
391            }
392            Declaration::BorderStyle(edge, style) => self.border.edge(*edge).style = *style,
393            Declaration::BorderWidth(edge, length) => {
394                self.border.edge(*edge).width = length.to_points(self.font_size, root_size).max(0.0)
395            }
396            Declaration::BorderColor(edge, color) => self.border.edge(*edge).color = *color,
397            Declaration::BorderRadius(corner, x, y) => {
398                *self.border_radius.corner(*corner) = CornerRadius {
399                    x: Coord::of(*x, self.font_size, root_size),
400                    y: Coord::of(*y, self.font_size, root_size),
401                }
402            }
403            Declaration::BackgroundColor(color) => self.background.color = *color,
404            Declaration::BackgroundImage(url) => self.background.image = url.clone(),
405            Declaration::BackgroundRepeat(repeat) => self.background.repeat = *repeat,
406            Declaration::BackgroundSize(size) => {
407                self.background.size = computed_size(*size, self.font_size, root_size)
408            }
409            Declaration::BackgroundPosition(x, y) => {
410                self.background.position = BackgroundPosition {
411                    x: Coord::of(*x, self.font_size, root_size),
412                    y: Coord::of(*y, self.font_size, root_size),
413                }
414            }
415            Declaration::BoxDecorationBreak(value) => self.box_decoration_break = *value,
416            Declaration::Width(width) => self.width = self.size(*width, root_size),
417            Declaration::Height(height) => self.height = self.size(*height, root_size),
418            Declaration::MinHeight(height) => self.min_height = self.size(*height, root_size),
419            Declaration::MaxWidth(width) => self.max_width = self.size(*width, root_size),
420            Declaration::MaxHeight(height) => self.max_height = self.size(*height, root_size),
421            Declaration::BorderCollapse(value) => self.border_collapse = *value,
422            Declaration::ListStyleType(value) => self.list_style_type = *value,
423            Declaration::BreakBefore(value) => self.break_before = *value,
424            Declaration::BreakAfter(value) => self.break_after = *value,
425            Declaration::BreakInside(value) => self.break_inside = *value,
426            Declaration::ColumnSpan(value) => self.column_span = *value,
427            // The cascade resolves both before anything is applied.
428            Declaration::Custom(_) | Declaration::Pending(_) => {}
429        }
430    }
431
432    /// Sets the longhand `like` names back to what `base` holds for
433    /// it. `base` is the style before any declaration applied, so a
434    /// property that inherits takes its parent's value and one that
435    /// does not takes its initial value.
436    pub(crate) fn reset(&mut self, like: &Declaration, base: &ComputedStyle) {
437        match like {
438            Declaration::FontFamily(_) => self.font_family = base.font_family.clone(),
439            Declaration::FontSize(_) => self.font_size = base.font_size,
440            Declaration::FontStyle(_) => self.font_style = base.font_style,
441            Declaration::FontWeight(_) => self.font_weight = base.font_weight,
442            Declaration::Color(_) => self.color = base.color,
443            Declaration::LineHeight(_) => self.line_height = base.line_height,
444            Declaration::LetterSpacing(_) => self.letter_spacing = base.letter_spacing,
445            Declaration::FontVariantCaps(_) => self.font_variant_caps = base.font_variant_caps,
446            Declaration::FontFeatureSettings(_) => {
447                self.font_feature_settings = base.font_feature_settings.clone()
448            }
449            Declaration::FontVariantLigatures(_) => {
450                self.font_variant_ligatures = base.font_variant_ligatures
451            }
452            Declaration::FontVariantNumeric(_) => {
453                self.font_variant_numeric = base.font_variant_numeric
454            }
455            Declaration::FontVariantAlternates(_) => {
456                self.font_variant_alternates = base.font_variant_alternates
457            }
458            Declaration::TextTransform(_) => self.text_transform = base.text_transform,
459            Declaration::TextDecorationLine(_) => {
460                self.text_decoration_line = base.text_decoration_line
461            }
462            Declaration::TextDecorationColor(_) => {
463                self.text_decoration_color = base.text_decoration_color
464            }
465            Declaration::TextDecorationStyle(_) => {
466                self.text_decoration_style = base.text_decoration_style
467            }
468            Declaration::TextDecorationThickness(_) => {
469                self.text_decoration_thickness = base.text_decoration_thickness
470            }
471            Declaration::TextAlign(_) => self.text_align = base.text_align,
472            Declaration::TextJustify(_) => self.text_justify = base.text_justify,
473            Declaration::HangingPunctuation(_) => {
474                self.hanging_punctuation = base.hanging_punctuation
475            }
476            Declaration::TextIndent(_) => self.text_indent = base.text_indent,
477            Declaration::Hyphens(_) => self.hyphens = base.hyphens,
478            Declaration::Orphans(_) => self.orphans = base.orphans,
479            Declaration::Widows(_) => self.widows = base.widows,
480            Declaration::Page(_) => self.page = base.page.clone(),
481            Declaration::Content(_) => self.content = base.content.clone(),
482            Declaration::StringSet(_) => self.string_set = base.string_set.clone(),
483            Declaration::CounterReset(_) => {
484                self.counter_reset = base.counter_reset;
485                self.note_reset = base.note_reset;
486            }
487            Declaration::InitialLetter(_) => self.initial_letter = base.initial_letter,
488            Declaration::Position(_) => self.position = base.position,
489            Declaration::Inset(edge, _) => *self.inset.edge(*edge) = base.inset.get(*edge),
490            Declaration::ZIndex(_) => self.z_index = base.z_index,
491            Declaration::Opacity(_) => self.opacity = base.opacity,
492            Declaration::WrapFlow(_) => self.wrap_flow = base.wrap_flow,
493            Declaration::ShapeOutside(_) => self.shape_outside = base.shape_outside.clone(),
494            Declaration::ShapeMargin(_) => self.shape_margin = base.shape_margin,
495            Declaration::Margin(edge, _) => *self.margin.edge(*edge) = base.margin.get(*edge),
496            Declaration::Padding(edge, _) => *self.padding.edge(*edge) = base.padding.get(*edge),
497            Declaration::BorderStyle(edge, _) => {
498                self.border.edge(*edge).style = base.border.get(*edge).style
499            }
500            Declaration::BorderWidth(edge, _) => {
501                self.border.edge(*edge).width = base.border.get(*edge).width
502            }
503            Declaration::BorderColor(edge, _) => {
504                self.border.edge(*edge).color = base.border.get(*edge).color
505            }
506            Declaration::BorderRadius(corner, ..) => {
507                *self.border_radius.corner(*corner) = base.border_radius.get(*corner)
508            }
509            Declaration::BackgroundColor(_) => self.background.color = base.background.color,
510            Declaration::BackgroundImage(_) => {
511                self.background.image = base.background.image.clone()
512            }
513            Declaration::BackgroundRepeat(_) => self.background.repeat = base.background.repeat,
514            Declaration::BackgroundSize(_) => self.background.size = base.background.size,
515            Declaration::BackgroundPosition(..) => {
516                self.background.position = base.background.position
517            }
518            Declaration::BoxDecorationBreak(_) => {
519                self.box_decoration_break = base.box_decoration_break
520            }
521            Declaration::Width(_) => self.width = base.width,
522            Declaration::Height(_) => self.height = base.height,
523            Declaration::MinHeight(_) => self.min_height = base.min_height,
524            Declaration::MaxWidth(_) => self.max_width = base.max_width,
525            Declaration::MaxHeight(_) => self.max_height = base.max_height,
526            Declaration::BorderCollapse(_) => self.border_collapse = base.border_collapse,
527            Declaration::ListStyleType(_) => self.list_style_type = base.list_style_type,
528            Declaration::BreakBefore(_) => self.break_before = base.break_before,
529            Declaration::BreakAfter(_) => self.break_after = base.break_after,
530            Declaration::BreakInside(_) => self.break_inside = base.break_inside,
531            Declaration::ColumnSpan(_) => self.column_span = base.column_span,
532            Declaration::Custom(_) | Declaration::Pending(_) => {}
533        }
534    }
535
536    /// What a written size computes to. A percentage stays one,
537    /// because the box it measures against is sized in the layout
538    /// pass.
539    fn size(&self, written: Option<Length>, root_size: f32) -> Width {
540        match written {
541            None => Width::Auto,
542            Some(Length::Percent(percent)) => Width::Percent(percent.max(0.0)),
543            Some(length) => Width::Points(length.to_points(self.font_size, root_size).max(0.0)),
544        }
545    }
546
547    /// Whether the block asks for a height of its own content box.
548    pub fn sized(&self) -> bool {
549        self.height != Width::Auto || self.min_height != Width::Auto
550    }
551
552    /// What this style, computed for `::first-line`, changes about
553    /// the element it was computed beside.
554    pub fn first_line_over(&self, element: &ComputedStyle) -> crate::lines::FirstLine {
555        fn set<T>(differs: bool, value: T) -> Option<T> {
556            differs.then_some(value)
557        }
558        let face = crate::lines::Face {
559            family: set(self.font_family != element.font_family, self.font_id),
560            italic: set(
561                self.font_style != element.font_style,
562                self.font_style == FontStyle::Italic,
563            ),
564            weight: set(self.font_weight != element.font_weight, self.font_weight),
565        };
566        crate::lines::FirstLine {
567            face: (face != crate::lines::Face::default()).then_some(face),
568            size: set(self.font_size != element.font_size, self.font_size),
569            letter_spacing: set(
570                self.letter_spacing != element.letter_spacing,
571                self.letter_spacing,
572            ),
573            caps: set(
574                self.font_variant_caps != element.font_variant_caps,
575                self.font_variant_caps,
576            ),
577            transform: set(
578                self.text_transform != element.text_transform,
579                self.text_transform,
580            ),
581            color: set(self.color != element.color, self.color),
582            decoration: set(self.decoration() != element.decoration(), self.decoration()),
583        }
584    }
585
586    /// This block's border box inside `measure`, laid out at `x` from
587    /// the enclosing content box's leading edge: `(leading edge,
588    /// width)`, with only the margins taken off.
589    pub fn border_box(&self, x: f32, measure: f32) -> (f32, f32) {
590        (
591            x + self.margin.left,
592            (measure - self.margin.inline()).max(0.0),
593        )
594    }
595
596    /// Whether prose is set around this element rather than over it:
597    /// the sheet has taken it out of the flow, and it excludes a side.
598    ///
599    /// This is the one place a contour is read, so it is also what
600    /// decides whether one is traced. An element still in the flow
601    /// takes a band of its own and nothing sets beside it, so a
602    /// contour on it would answer a question nobody asks.
603    pub fn excludes(&self) -> bool {
604        self.position == Position::Absolute && self.wrap_flow != WrapFlow::Auto
605    }
606
607    /// This block's content box inside `measure`: `(leading edge, the
608    /// measure its lines break to)`. Margin, border and padding come
609    /// off both edges, and the leading edge moves in by what they
610    /// take on the left.
611    pub fn content_box(&self, x: f32, measure: f32) -> (f32, f32) {
612        let border = self.border.widths();
613        let leading = self.margin.left + border.left + self.padding.left;
614        let trailing = self.margin.right + border.right + self.padding.right;
615        (x + leading, (measure - leading - trailing).max(0.0))
616    }
617
618    /// The box this style paints around the runs of an inline
619    /// element, and `None` where it paints none and takes no width.
620    ///
621    /// Padding counts even where nothing is painted over it: it is
622    /// width, and the line breaks against it.
623    pub fn inline_box(&self) -> Option<crate::lines::InlineBox> {
624        let padding = self.padding;
625        let border = self.border.widths();
626        let paints = self.background.paints() || self.border.paints();
627        let spaced = padding != Edges::all(0.0);
628        (paints || spaced).then_some(crate::lines::InlineBox {
629            padding,
630            border,
631            cloned: self.box_decoration_break == BoxDecorationBreak::Clone,
632        })
633    }
634
635    /// Everything line layout needs from a style.
636    pub fn paragraph(&self) -> crate::lines::ParagraphStyle {
637        crate::lines::ParagraphStyle {
638            font_id: self.font_id,
639            size: self.font_size,
640            color: self.color,
641            line_height: self.line_height,
642            letter_spacing: self.letter_spacing,
643            caps: self.font_variant_caps,
644            features: self.features(),
645            transform: self.text_transform,
646            decoration: self.decoration(),
647        }
648    }
649
650    /// Every feature this style asks the face for, in the order the
651    /// shaper reads them: what the `font-variant` longhands name,
652    /// then what `font-feature-settings` spells out. A tag in both
653    /// takes the value written last, which is the one the sheet
654    /// spelled.
655    pub fn features(&self) -> Vec<FeatureSetting> {
656        let mut settings = self.font_variant_ligatures.settings();
657        settings.extend(self.font_variant_numeric.settings());
658        settings.extend(self.font_variant_alternates.settings());
659        settings.extend(self.font_feature_settings.iter().copied());
660        settings
661    }
662
663    /// What is drawn across this style's runs, the four longhands
664    /// gathered into one value.
665    pub fn decoration(&self) -> TextDecoration {
666        TextDecoration {
667            line: self.text_decoration_line,
668            color: self.text_decoration_color,
669            style: self.text_decoration_style,
670            thickness: self.text_decoration_thickness,
671        }
672    }
673}
674
675/// What the cascade makes of a written `background-size`: `em` and
676/// `rem` against the font size in force, a percentage kept as one,
677/// because the box it measures against is sized in the layout pass.
678pub(crate) fn computed_size(size: SizeSource, font_size: f32, root_size: f32) -> BackgroundSize {
679    let axis =
680        |length: Option<Length>| length.map(|length| Coord::of(length, font_size, root_size));
681    match size {
682        SizeSource::Auto => BackgroundSize::Auto,
683        SizeSource::Cover => BackgroundSize::Cover,
684        SizeSource::Contain => BackgroundSize::Contain,
685        SizeSource::Fixed(width, height) => BackgroundSize::Fixed {
686            width: axis(width),
687            height: axis(height),
688        },
689    }
690}
691
692fn default_ligatures(ligatures: &FontVariantLigatures) -> bool {
693    *ligatures == FontVariantLigatures::NORMAL
694}
695
696fn default_numeric(numeric: &FontVariantNumeric) -> bool {
697    *numeric == FontVariantNumeric::NORMAL
698}
699
700fn default_alternates(alternates: &FontVariantAlternates) -> bool {
701    *alternates == FontVariantAlternates::Normal
702}
703
704fn in_flow(position: &Position) -> bool {
705    *position == Position::Static
706}
707
708fn unplaced(inset: &Edges<Inset>) -> bool {
709    *inset == Edges::all(Inset::Auto)
710}
711
712fn ground_layer(layer: &i32) -> bool {
713    *layer == 0
714}
715
716fn opaque(opacity: &f32) -> bool {
717    *opacity == 1.0
718}
719
720fn wraps_nothing(wrap: &WrapFlow) -> bool {
721    *wrap == WrapFlow::Auto
722}
723
724fn no_padding(padding: &Edges) -> bool {
725    *padding == Edges::all(0.0)
726}
727
728fn no_border(border: &Edges<Border>) -> bool {
729    *border == Edges::all(Border::NONE)
730}
731
732fn square(radius: &BorderRadius) -> bool {
733    *radius == BorderRadius::SQUARE
734}
735
736fn sliced(value: &BoxDecorationBreak) -> bool {
737    *value == BoxDecorationBreak::Slice
738}
739
740fn undecorated(line: &DecorationLine) -> bool {
741    !line.draws()
742}
743
744fn solid(style: &DecorationStyle) -> bool {
745    *style == DecorationStyle::Solid
746}
747
748fn auto_width(width: &Width) -> bool {
749    *width == Width::Auto
750}
751
752fn separate(collapse: &BorderCollapse) -> bool {
753    *collapse == BorderCollapse::Separate
754}
755
756fn disc(marker: &ListStyleType) -> bool {
757    *marker == ListStyleType::Disc
758}
759
760fn one_column(span: &ColumnSpan) -> bool {
761    *span == ColumnSpan::None
762}
763
764fn no_shape_margin(margin: &f32) -> bool {
765    *margin == 0.0
766}
767
768fn wraps_its_box(shape: &ShapeOutside) -> bool {
769    *shape == ShapeOutside::None
770}
771
772#[cfg(test)]
773#[cfg(test)]
774mod tests {
775    use super::*;
776    use crate::style::properties::{
777        Coord, CounterStyle, Declaration, Length, ShapeOutside, ShapeSource,
778    };
779
780    /// Part: `ComputedStyle` carries the custom properties in force,
781    /// and a child inherits them.
782    #[test]
783    fn a_child_inherits_the_custom_properties() {
784        assert!(ComputedStyle::initial().custom.is_empty());
785        let mut style = ComputedStyle::initial();
786        style.custom.insert("--accent".into(), "#d6075e".into());
787        assert_eq!(style.inherit().custom, style.custom);
788    }
789
790    /// Part: `ComputedStyle` carries the properties of an inline
791    /// box. A style that paints nothing and takes no width has none,
792    /// and padding alone is enough for one, because padding is width.
793    #[test]
794    fn an_inline_box_is_the_padding_border_and_background_of_a_style() {
795        let initial = ComputedStyle::initial();
796        assert_eq!(initial.inline_box(), None);
797
798        let mut padded = initial.clone();
799        padded.padding = Edges::all(4.0);
800        let box_ = padded.inline_box().expect("padding alone makes a box");
801        assert_eq!((box_.leading(), box_.above()), (4.0, 4.0));
802        assert!(!box_.cloned);
803
804        let mut tinted = initial.clone();
805        tinted.background.color = Some(Color::BLACK);
806        assert!(tinted.inline_box().is_some());
807
808        let mut ruled = initial.clone();
809        ruled.border = Edges::all(Border {
810            style: super::super::edges::BorderStyle::Solid,
811            width: 1.0,
812            color: None,
813        });
814        ruled.box_decoration_break = BoxDecorationBreak::Clone;
815        let box_ = ruled.inline_box().expect("a border makes a box");
816        assert_eq!(box_.border, Edges::all(1.0));
817        assert!(box_.cloned);
818    }
819
820    /// Folios spell out in every style the subset supports, and a
821    /// value a style has no spelling for falls back to decimal.
822    #[test]
823    fn counter_styles_spell_their_values() {
824        let spell = |style: CounterStyle, values: [u32; 4]| {
825            values.map(|value| style.format(value)).join(" ")
826        };
827        assert_eq!(spell(CounterStyle::Decimal, [1, 4, 9, 40]), "1 4 9 40");
828        assert_eq!(spell(CounterStyle::LowerRoman, [1, 4, 9, 40]), "i iv ix xl");
829        assert_eq!(spell(CounterStyle::UpperRoman, [1, 4, 9, 40]), "I IV IX XL");
830        assert_eq!(spell(CounterStyle::LowerAlpha, [1, 4, 26, 27]), "a d z aa");
831        assert_eq!(spell(CounterStyle::UpperAlpha, [1, 4, 26, 27]), "A D Z AA");
832        assert_eq!(CounterStyle::LowerRoman.format(0), "0");
833        assert_eq!(CounterStyle::LowerRoman.format(4000), "4000");
834        assert_eq!(CounterStyle::LowerAlpha.format(0), "0");
835    }
836
837    /// A polygon point written as a length computes to points against
838    /// the font size in force. One written as a percentage stays a
839    /// percentage, because the box it measures against is the image's
840    /// and the image is sized in the layout pass.
841    #[test]
842    fn a_polygon_keeps_its_percentages_and_computes_its_lengths() {
843        let mut style = ComputedStyle::initial();
844        style.font_size = 10.0;
845        style.apply(
846            &Declaration::ShapeOutside(ShapeSource::Polygon(vec![
847                (Length::Em(2.0), Length::Percent(50.0)),
848                (Length::Points(3.0), Length::Rem(1.0)),
849                (Length::Percent(100.0), Length::Percent(100.0)),
850            ])),
851            10.0,
852            16.0,
853        );
854        let ShapeOutside::Polygon(points) = &style.shape_outside else {
855            panic!(
856                "the polygon did not reach the style: {:?}",
857                style.shape_outside
858            );
859        };
860        assert_eq!(points[0].x, Coord::Points(20.0));
861        assert_eq!(points[0].y, Coord::Percent(50.0));
862        assert_eq!(points[1].x, Coord::Points(3.0));
863        assert_eq!(points[1].y, Coord::Points(16.0));
864        assert_eq!(points[2].x.to_points(80.0), 80.0);
865        assert_eq!(points[2].y.to_points(40.0), 40.0);
866
867        style.apply(&Declaration::ShapeMargin(Length::Em(1.5)), 10.0, 16.0);
868        assert_eq!(style.shape_margin, 15.0);
869    }
870
871    /// Part: `height` and `min-height` compute as lengths and
872    /// percentages. A length in `em` resolves against the font size in
873    /// force, and a percentage stays a percentage until layout
874    /// resolves it against the content box.
875    #[test]
876    fn height_and_min_height_compute_as_lengths_and_percentages() {
877        let mut style = ComputedStyle::initial();
878        style.font_size = 10.0;
879        assert!(!style.sized());
880        style.apply(&Declaration::Height(Some(Length::Em(3.0))), 10.0, 16.0);
881        style.apply(
882            &Declaration::MinHeight(Some(Length::Percent(25.0))),
883            10.0,
884            16.0,
885        );
886        assert_eq!(style.height, Width::Points(30.0));
887        assert_eq!(style.min_height, Width::Percent(25.0));
888        assert_eq!(style.min_height.resolve(540.0), Some(135.0));
889        assert!(style.sized());
890        assert_eq!(style.inherit().height, Width::Auto);
891        assert_eq!(style.inherit().min_height, Width::Auto);
892        style.apply(&Declaration::Height(None), 10.0, 16.0);
893        assert_eq!(style.height, Width::Auto);
894    }
895
896    /// Part: `width`, `height`, `max-width` and `max-height` compute
897    /// as lengths and percentages, and a child does not inherit them.
898    #[test]
899    fn image_sizes_compute_as_lengths_and_percentages_and_do_not_inherit() {
900        let mut style = ComputedStyle::initial();
901        style.font_size = 10.0;
902        style.apply(&Declaration::Width(Some(Length::Points(200.0))), 10.0, 16.0);
903        style.apply(
904            &Declaration::Height(Some(Length::Percent(50.0))),
905            10.0,
906            16.0,
907        );
908        style.apply(&Declaration::MaxWidth(Some(Length::Em(12.0))), 10.0, 16.0);
909        style.apply(
910            &Declaration::MaxHeight(Some(Length::Percent(40.0))),
911            10.0,
912            16.0,
913        );
914        assert_eq!(style.width, Width::Points(200.0));
915        assert_eq!(style.height, Width::Percent(50.0));
916        assert_eq!(style.max_width, Width::Points(120.0));
917        assert_eq!(style.max_height, Width::Percent(40.0));
918
919        let child = style.inherit();
920        assert_eq!(
921            [child.width, child.height, child.max_width, child.max_height],
922            [Width::Auto; 4]
923        );
924
925        style.apply(&Declaration::MaxWidth(None), 10.0, 16.0);
926        assert_eq!(style.max_width, Width::Auto);
927    }
928
929    /// Part: `position: relative` computes, and an inset written as a
930    /// percentage stays a percentage until layout resolves it against
931    /// the page area.
932    /// `left` outranks `right` and `top` outranks `bottom`.
933    #[test]
934    fn insets_keep_their_percentages_and_offset_a_relative_box() {
935        use crate::style::properties::{Edge, Inset, Position};
936        let mut style = ComputedStyle::initial();
937        style.font_size = 10.0;
938        style.apply(&Declaration::Position(Position::Relative), 10.0, 16.0);
939        style.apply(
940            &Declaration::Inset(Edge::Top, Some(Length::Em(-1.2))),
941            10.0,
942            16.0,
943        );
944        style.apply(
945            &Declaration::Inset(Edge::Bottom, Some(Length::Points(40.0))),
946            10.0,
947            16.0,
948        );
949        style.apply(
950            &Declaration::Inset(Edge::Right, Some(Length::Percent(10.0))),
951            10.0,
952            16.0,
953        );
954        assert_eq!(style.position, Position::Relative);
955        assert_eq!(style.inset.top, Inset::Points(-12.0));
956        assert_eq!(style.inset.right, Inset::Percent(10.0));
957        assert_eq!(style.inset.right.resolve(300.0), Some(30.0));
958        assert_eq!(style.inset.left.resolve(300.0), None);
959        assert_eq!(style.inset.offset((300.0, 500.0)), (-30.0, -12.0));
960    }
961}