use std::rc::Rc; use crate::color::{Color, RgbaColor}; use crate::font::{ FontFamily, FontStretch, FontStyle, FontVariant, FontWeight, VerticalFontMetric, }; use crate::geom::*; use crate::layout::Paint; use crate::paper::{PaperClass, PAPER_A4}; /// Defines an set of properties a template can be instantiated with. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct State { /// The direction for text and other inline objects. pub dirs: Gen, /// The alignments of layouts in their parents. pub aligns: Gen, /// The page settings. pub page: Rc, /// The paragraph settings. pub par: Rc, /// The font settings. pub font: Rc, } impl State { /// Access the `page` state mutably. pub fn page_mut(&mut self) -> &mut PageState { Rc::make_mut(&mut self.page) } /// Access the `par` state mutably. pub fn par_mut(&mut self) -> &mut ParState { Rc::make_mut(&mut self.par) } /// Access the `font` state mutably. pub fn font_mut(&mut self) -> &mut FontState { Rc::make_mut(&mut self.font) } /// The resolved line spacing. pub fn line_spacing(&self) -> Length { self.par.line_spacing.resolve(self.font.size) } /// The resolved paragraph spacing. pub fn par_spacing(&self) -> Length { self.par.par_spacing.resolve(self.font.size) } } impl Default for State { fn default() -> Self { Self { dirs: Gen::new(Dir::LTR, Dir::TTB), aligns: Gen::splat(Align::Start), page: Rc::new(PageState::default()), par: Rc::new(ParState::default()), font: Rc::new(FontState::default()), } } } /// Defines page properties. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct PageState { /// 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 PageState { /// 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 PageState { fn default() -> Self { let paper = PAPER_A4; Self { class: paper.class(), size: paper.size(), margins: Sides::splat(None), } } } /// Defines paragraph properties. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct ParState { /// The spacing between paragraphs (dependent on scaled font size). pub par_spacing: Linear, /// The spacing between lines (dependent on scaled font size). pub line_spacing: Linear, } impl Default for ParState { fn default() -> Self { Self { par_spacing: Relative::new(1.0).into(), line_spacing: Relative::new(0.5).into(), } } } /// Defines font properties. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct FontState { /// 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, /// 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, /// The specifications for a strikethrough line, if any. pub strikethrough: Option>, /// The specifications for a underline, if any. pub underline: Option>, /// The specifications for a overline line, if any. pub overline: Option>, } impl FontState { /// 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 = self .monospace .then(|| self.families.monospace.as_slice()) .unwrap_or_default(); 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, } }); head.iter() .chain(core) .chain(self.families.base.iter()) .map(String::as_str) } /// Access the `families` state mutably. pub fn families_mut(&mut self) -> &mut FamilyState { Rc::make_mut(&mut self.families) } } impl Default for FontState { fn default() -> Self { Self { families: Rc::new(FamilyState::default()), variant: FontVariant { style: FontStyle::Normal, weight: FontWeight::REGULAR, stretch: FontStretch::NORMAL, }, strong: false, emph: false, monospace: false, size: Length::pt(11.0), top_edge: VerticalFontMetric::CapHeight, bottom_edge: VerticalFontMetric::Baseline, fill: Paint::Color(Color::Rgba(RgbaColor::BLACK)), strikethrough: None, underline: None, overline: None, } } } /// Font family definitions. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct FamilyState { /// 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 FamilyState { fn default() -> Self { Self { list: Rc::new(vec![FontFamily::Serif]), serif: Rc::new(vec!["eb garamond".into()]), sans_serif: Rc::new(vec!["pt sans".into()]), monospace: Rc::new(vec!["inconsolata".into()]), base: Rc::new(vec![ "twitter color emoji".into(), "latin modern math".into(), ]), } } } /// Defines a line that is positioned over, under or on top of text. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LineState { /// Stroke color of the line, defaults to the text color if `None`. pub stroke: Option, /// Thickness of the line's strokes (dependent on scaled font size), read /// from the font tables if `None`. pub thickness: Option, /// Position of the line relative to the baseline (dependent on scaled font /// size), read from the font tables if `None`. pub offset: Option, /// Amount that the line will be longer or shorter than its associated text /// (dependent on scaled font size). pub extent: Linear, }