1use 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#[derive(Debug, Clone, PartialEq, Serialize)]
27pub struct ComputedStyle {
28 pub font_id: u16,
32 pub font_family: Vec<Family>,
35 pub font_size: f32,
37 pub font_style: FontStyle,
39 pub font_weight: u16,
41 pub color: Color,
43 pub line_height: f32,
45 pub letter_spacing: f32,
48 pub font_variant_caps: FontVariantCaps,
50 #[serde(skip_serializing_if = "Vec::is_empty")]
54 pub font_feature_settings: Vec<FeatureSetting>,
55 #[serde(skip_serializing_if = "default_ligatures")]
57 pub font_variant_ligatures: FontVariantLigatures,
58 #[serde(skip_serializing_if = "default_numeric")]
60 pub font_variant_numeric: FontVariantNumeric,
61 #[serde(skip_serializing_if = "default_alternates")]
63 pub font_variant_alternates: FontVariantAlternates,
64 pub text_transform: TextTransform,
66 #[serde(skip_serializing_if = "undecorated")]
69 pub text_decoration_line: DecorationLine,
70 #[serde(skip_serializing_if = "Option::is_none")]
73 pub text_decoration_color: Option<Color>,
74 #[serde(skip_serializing_if = "solid")]
76 pub text_decoration_style: DecorationStyle,
77 #[serde(skip_serializing_if = "Option::is_none")]
81 pub text_decoration_thickness: Option<f32>,
82 pub text_align: TextAlign,
84 pub text_justify: TextJustify,
86 pub hanging_punctuation: HangingPunctuation,
88 pub text_indent: f32,
90 pub hyphens: Hyphens,
92 pub orphans: u16,
94 pub widows: u16,
96 pub page: Option<String>,
98 pub content: Content,
101 #[serde(skip_serializing_if = "Vec::is_empty")]
105 pub string_set: Vec<StringSet>,
106 #[serde(skip_serializing_if = "Option::is_none")]
109 pub counter_reset: Option<u32>,
110 #[serde(skip_serializing_if = "Option::is_none")]
114 pub note_reset: Option<u32>,
115 pub initial_letter: u16,
118 #[serde(skip_serializing_if = "in_flow")]
120 pub position: Position,
121 #[serde(skip_serializing_if = "unplaced")]
123 pub inset: Edges<Inset>,
124 #[serde(skip_serializing_if = "ground_layer")]
127 pub z_index: i32,
128 #[serde(skip_serializing_if = "opaque")]
132 pub opacity: f32,
133 #[serde(skip_serializing_if = "wraps_nothing")]
135 pub wrap_flow: WrapFlow,
136 #[serde(skip_serializing_if = "wraps_its_box")]
138 pub shape_outside: ShapeOutside,
139 #[serde(skip_serializing_if = "no_shape_margin")]
142 pub shape_margin: f32,
143 pub margin: Edges,
145 #[serde(skip_serializing_if = "no_padding")]
147 pub padding: Edges,
148 #[serde(skip_serializing_if = "no_border")]
150 pub border: Edges<Border>,
151 #[serde(skip_serializing_if = "square")]
155 pub border_radius: BorderRadius,
156 #[serde(skip_serializing_if = "no_background")]
159 pub background: Background,
160 #[serde(skip_serializing_if = "sliced")]
162 pub box_decoration_break: BoxDecorationBreak,
163 #[serde(skip_serializing_if = "auto_width")]
166 pub width: Width,
167 #[serde(skip_serializing_if = "auto_width")]
169 pub height: Width,
170 #[serde(skip_serializing_if = "auto_width")]
172 pub min_height: Width,
173 #[serde(skip_serializing_if = "auto_width")]
176 pub max_width: Width,
177 #[serde(skip_serializing_if = "auto_width")]
180 pub max_height: Width,
181 #[serde(skip_serializing_if = "separate")]
184 pub border_collapse: BorderCollapse,
185 #[serde(skip_serializing_if = "disc")]
187 pub list_style_type: ListStyleType,
188 pub break_before: Break,
190 pub break_after: Break,
192 pub break_inside: Break,
194 #[serde(skip_serializing_if = "one_column")]
197 pub column_span: ColumnSpan,
198 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
201 pub custom: BTreeMap<String, String>,
202}
203
204impl ComputedStyle {
205 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 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 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 Declaration::Custom(_) | Declaration::Pending(_) => {}
429 }
430 }
431
432 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 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 pub fn sized(&self) -> bool {
549 self.height != Width::Auto || self.min_height != Width::Auto
550 }
551
552 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 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 pub fn excludes(&self) -> bool {
604 self.position == Position::Absolute && self.wrap_flow != WrapFlow::Auto
605 }
606
607 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 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 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 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 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
675pub(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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}