//! Style properties. mod paper; pub use paper::*; use std::rc::Rc; use crate::font::{ FontFamily, FontStretch, FontStyle, FontVariant, FontWeight, VerticalFontMetric, }; use crate::geom::*; /// Defines a set of properties a template can be instantiated with. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Style { /// The direction for text and other inline objects. pub dir: Dir, /// The alignments of layouts in their parents. pub aligns: Gen, /// The page settings. pub page: Rc, /// The paragraph settings. pub par: Rc, /// The current text settings. pub text: Rc, } impl Style { /// Access the `page` style mutably. pub fn page_mut(&mut self) -> &mut PageStyle { Rc::make_mut(&mut self.page) } /// Access the `par` style mutably. pub fn par_mut(&mut self) -> &mut ParStyle { Rc::make_mut(&mut self.par) } /// Access the `text` style mutably. pub fn text_mut(&mut self) -> &mut TextStyle { Rc::make_mut(&mut self.text) } /// The resolved line spacing. pub fn leading(&self) -> Length { self.par.leading.resolve(self.text.size) } /// The resolved paragraph spacing. pub fn par_spacing(&self) -> Length { self.par.spacing.resolve(self.text.size) } } impl Default for Style { fn default() -> Self { Self { dir: Dir::LTR, aligns: Gen::splat(Align::Start), page: Rc::new(PageStyle::default()), par: Rc::new(ParStyle::default()), text: Rc::new(TextStyle::default()), } } } /// Defines style properties of pages. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct PageStyle { /// The class of this page. pub class: PaperClass, /// The width and height of the page. pub size: Size, /// The amount of white space on each side of the page. If a side is set to /// `None`, the default for the paper class is used. pub margins: Sides>, } impl PageStyle { /// The resolved margins. pub fn margins(&self) -> Sides { let default = self.class.default_margins(); Sides { left: self.margins.left.unwrap_or(default.left), top: self.margins.top.unwrap_or(default.top), right: self.margins.right.unwrap_or(default.right), bottom: self.margins.bottom.unwrap_or(default.bottom), } } } impl Default for PageStyle { fn default() -> Self { let paper = Paper::A4; Self { class: paper.class(), size: paper.size(), margins: Sides::splat(None), } } } /// Defines style properties of paragraphs. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct ParStyle { /// The spacing between paragraphs (dependent on scaled font size). pub spacing: Linear, /// The spacing between lines (dependent on scaled font size). pub leading: Linear, } impl Default for ParStyle { fn default() -> Self { Self { spacing: Relative::new(1.2).into(), leading: Relative::new(0.65).into(), } } } /// Defines style properties of text. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct TextStyle { /// The font size. pub size: Length, /// The selected font variant (the final variant also depends on `strong` /// and `emph`). pub variant: FontVariant, /// The top end of the text bounding box. pub top_edge: VerticalFontMetric, /// The bottom end of the text bounding box. pub bottom_edge: VerticalFontMetric, /// Glyph color. pub fill: Paint, /// A list of font families with generic class definitions (the final /// family list also depends on `monospace`). pub families: Rc, /// Whether 300 extra font weight should be added to what is defined by the /// `variant`. pub strong: bool, /// Whether the the font style defined by the `variant` should be inverted. pub emph: bool, /// Whether a monospace font should be preferred. pub monospace: bool, /// Whether font fallback to a base list should occur. pub fallback: bool, } impl TextStyle { /// The resolved variant with `strong` and `emph` factored in. pub fn variant(&self) -> FontVariant { let mut variant = self.variant; if self.strong { variant.weight = variant.weight.thicken(300); } if self.emph { variant.style = match variant.style { FontStyle::Normal => FontStyle::Italic, FontStyle::Italic => FontStyle::Normal, FontStyle::Oblique => FontStyle::Normal, } } variant } /// The resolved family iterator. pub fn families(&self) -> impl Iterator + Clone { let head = if self.monospace { self.families.monospace.as_slice() } else { &[] }; let core = self.families.list.iter().flat_map(move |family| { match family { FontFamily::Named(name) => std::slice::from_ref(name), FontFamily::Serif => &self.families.serif, FontFamily::SansSerif => &self.families.sans_serif, FontFamily::Monospace => &self.families.monospace, } }); let tail = if self.fallback { self.families.base.as_slice() } else { &[] }; head.iter().chain(core).chain(tail).map(String::as_str) } /// Access the `families` style mutably. pub fn families_mut(&mut self) -> &mut FamilyStyle { Rc::make_mut(&mut self.families) } } impl Default for TextStyle { fn default() -> Self { Self { size: Length::pt(11.0), variant: FontVariant { style: FontStyle::Normal, weight: FontWeight::REGULAR, stretch: FontStretch::NORMAL, }, top_edge: VerticalFontMetric::CapHeight, bottom_edge: VerticalFontMetric::Baseline, fill: Paint::Color(Color::Rgba(RgbaColor::BLACK)), families: Rc::new(FamilyStyle::default()), strong: false, emph: false, monospace: false, fallback: true, } } } /// Font list with family definitions. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct FamilyStyle { /// The user-defined list of font families. pub list: Rc>, /// Definition of serif font families. pub serif: Rc>, /// Definition of sans-serif font families. pub sans_serif: Rc>, /// Definition of monospace font families used for raw text. pub monospace: Rc>, /// Base fonts that are tried as last resort. pub base: Rc>, } impl Default for FamilyStyle { fn default() -> Self { Self { list: Rc::new(vec![FontFamily::SansSerif]), serif: Rc::new(vec!["ibm plex serif".into()]), sans_serif: Rc::new(vec!["ibm plex sans".into()]), monospace: Rc::new(vec!["ibm plex mono".into()]), base: Rc::new(vec![ "ibm plex sans".into(), "latin modern math".into(), "twitter color emoji".into(), ]), } } }