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}; /// The execution state. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct State { /// The direction for text and other inline objects. pub dir: Dir, /// The current alignments of layouts in their parents. pub aligns: Gen, /// The current page settings. pub page: PageState, /// The current text settings. pub text: Rc, } impl State { /// Access the `text` state mutably. pub fn text_mut(&mut self) -> &mut TextState { Rc::make_mut(&mut self.text) } } impl Default for State { fn default() -> Self { Self { dir: Dir::LTR, aligns: Gen::splat(Align::Start), page: PageState::default(), text: Rc::new(TextState::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 text properties. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct TextState { /// A list of font families with generic class definitions (the final /// family list also depends on `monospace`). pub families: Rc, /// The selected font variant (the final variant also depends on `strong` /// and `emph`). pub variant: FontVariant, /// Whether the strong toggle is active or inactive. This determines /// whether the next `*` adds or removes font weight. pub strong: bool, /// Whether the emphasis toggle is active or inactive. This determines /// whether the next `_` makes italic or non-italic. pub emph: bool, /// Whether the monospace toggle is active or inactive. pub monospace: bool, /// The font size. pub size: Length, /// The spacing between words (dependent on scaled font size). // TODO: Don't ignore this. pub word_spacing: Linear, /// The spacing between lines (dependent on scaled font size). pub line_spacing: Linear, /// The spacing between paragraphs (dependent on scaled font size). pub par_spacing: Linear, /// 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, /// 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 TextState { /// Access the `families` list mutably. pub fn families_mut(&mut self) -> &mut FamilyList { Rc::make_mut(&mut self.families) } /// The resolved family iterator. pub fn families(&self) -> impl Iterator + Clone { let head = if self.monospace { self.families.monospace.as_slice() } else { &[] }; head.iter().map(String::as_str).chain(self.families.iter()) } /// 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 word spacing. pub fn word_spacing(&self) -> Length { self.word_spacing.resolve(self.size) } /// The resolved line spacing. pub fn line_spacing(&self) -> Length { self.line_spacing.resolve(self.size) } /// The resolved paragraph spacing. pub fn par_spacing(&self) -> Length { self.par_spacing.resolve(self.size) } } impl Default for TextState { fn default() -> Self { Self { families: Rc::new(FamilyList::default()), variant: FontVariant { style: FontStyle::Normal, weight: FontWeight::REGULAR, stretch: FontStretch::NORMAL, }, strong: false, emph: false, monospace: false, size: Length::pt(11.0), word_spacing: Relative::new(0.25).into(), line_spacing: Relative::new(0.5).into(), par_spacing: Relative::new(1.0).into(), 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 FamilyList { /// The user-defined list of font families. pub list: Vec, /// Definition of serif font families. pub serif: Vec, /// Definition of sans-serif font families. pub sans_serif: Vec, /// Definition of monospace font families used for raw text. pub monospace: Vec, /// Base fonts that are tried if the list has no match. pub base: Vec, } impl FamilyList { /// Flat iterator over this map's family names. pub fn iter(&self) -> impl Iterator + Clone { self.list .iter() .flat_map(move |family: &FontFamily| { match family { FontFamily::Named(name) => std::slice::from_ref(name), FontFamily::Serif => &self.serif, FontFamily::SansSerif => &self.sans_serif, FontFamily::Monospace => &self.monospace, } }) .chain(&self.base) .map(String::as_str) } } impl Default for FamilyList { fn default() -> Self { Self { list: vec![FontFamily::Serif], serif: vec!["eb garamond".into()], sans_serif: vec!["pt sans".into()], monospace: vec!["inconsolata".into()], base: vec!["twitter color emoji".into(), "latin modern math".into()], } } } /// Describes 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 stroke. Calling functions should attempt to /// read this value from the appropriate font tables if this is `None`. pub thickness: Option, /// Position of the line relative to the baseline. Calling functions should /// attempt to read this value from the appropriate font tables if this is /// `None`. pub offset: Option, /// Amount that the line will be longer or shorter than its associated text. pub extent: Linear, }