From 51bf3268ddf5db1bdd61e59bfb4a30f0463a4bfb Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 10 Oct 2020 22:35:24 +0200 Subject: [PATCH] =?UTF-8?q?Refactor=20text=20state=20=F0=9F=86=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/eval/mod.rs | 37 ++++---- src/eval/state.rs | 198 +++++++++++++++++++---------------------- src/geom/linear.rs | 7 +- src/layout/text.rs | 4 +- src/library/font.rs | 18 ++-- src/library/spacing.rs | 2 +- 6 files changed, 128 insertions(+), 138 deletions(-) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index fc8bbbd6b..313dddc49 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -179,7 +179,8 @@ impl EvalContext { /// Start a paragraph group based on the active text state. pub fn start_par_group(&mut self) { let dirs = self.state.dirs; - let line_spacing = self.state.text.line_spacing(); + let em = self.state.font.font_size(); + let line_spacing = self.state.par.line_spacing.eval(em); let aligns = self.state.aligns; self.start_group((dirs, line_spacing, aligns)); } @@ -204,13 +205,13 @@ impl EvalContext { /// Construct a text node from the given string based on the active text /// state. pub fn make_text_node(&self, text: String) -> Text { - let mut variant = self.state.text.variant; + let mut variant = self.state.font.variant; - if self.state.text.strong { + if self.state.font.strong { variant.weight = variant.weight.thicken(300); } - if self.state.text.emph { + if self.state.font.emph { variant.style = match variant.style { FontStyle::Normal => FontStyle::Italic, FontStyle::Italic => FontStyle::Normal, @@ -221,8 +222,8 @@ impl EvalContext { Text { text, dir: self.state.dirs.cross, - size: self.state.text.font_size(), - fallback: Rc::clone(&self.state.text.fallback), + size: self.state.font.font_size(), + families: Rc::clone(&self.state.font.families), variant, aligns: self.state.aligns, } @@ -256,8 +257,9 @@ impl Eval for SynNode { fn eval(&self, ctx: &mut EvalContext) -> Self::Output { match self { SynNode::Space => { + let em = ctx.state.font.font_size(); ctx.push(Spacing { - amount: ctx.state.text.word_spacing(), + amount: ctx.state.par.word_spacing.eval(em), softness: Softness::Soft, }); } @@ -274,19 +276,20 @@ impl Eval for SynNode { SynNode::Parbreak => { ctx.end_par_group(); + let em = ctx.state.font.font_size(); ctx.push(Spacing { - amount: ctx.state.text.par_spacing(), + amount: ctx.state.par.par_spacing.eval(em), softness: Softness::Soft, }); ctx.start_par_group(); } SynNode::Emph => { - ctx.state.text.emph ^= true; + ctx.state.font.emph ^= true; } SynNode::Strong => { - ctx.state.text.strong ^= true; + ctx.state.font.strong ^= true; } SynNode::Heading(heading) => { @@ -311,8 +314,8 @@ impl Eval for NodeHeading { fn eval(&self, ctx: &mut EvalContext) -> Self::Output { let prev = ctx.state.clone(); let upscale = 1.5 - 0.1 * self.level.v as f64; - ctx.state.text.font_size.scale *= upscale; - ctx.state.text.strong = true; + ctx.state.font.scale *= upscale; + ctx.state.font.strong = true; self.contents.eval(ctx); @@ -324,10 +327,10 @@ impl Eval for NodeRaw { type Output = (); fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - let prev = Rc::clone(&ctx.state.text.fallback); - let fallback = Rc::make_mut(&mut ctx.state.text.fallback); - fallback.list.insert(0, "monospace".to_string()); - fallback.flatten(); + let prev = Rc::clone(&ctx.state.font.families); + let families = Rc::make_mut(&mut ctx.state.font.families); + families.list.insert(0, "monospace".to_string()); + families.flatten(); let mut children = vec![]; for line in &self.lines { @@ -341,7 +344,7 @@ impl Eval for NodeRaw { expand: Spec::new(false, false), }); - ctx.state.text.fallback = prev; + ctx.state.font.families = prev; } } diff --git a/src/eval/state.rs b/src/eval/state.rs index 3ae6b4142..9c5476d91 100644 --- a/src/eval/state.rs +++ b/src/eval/state.rs @@ -13,10 +13,12 @@ use crate::paper::{Paper, PaperClass, PAPER_A4}; pub struct State { /// The scope that contains function definitions. pub scope: Scope, - /// The text state. - pub text: TextState, /// The page state. pub page: PageState, + /// The paragraph state. + pub par: ParState, + /// The font state. + pub font: FontState, /// The active layouting directions. pub dirs: Gen, /// The active alignments. @@ -27,118 +29,16 @@ impl Default for State { fn default() -> Self { Self { scope: crate::library::_std(), - text: TextState::default(), page: PageState::default(), + par: ParState::default(), + font: FontState::default(), dirs: Gen::new(Dir::TTB, Dir::LTR), aligns: Gen::new(Align::Start, Align::Start), } } } -/// Defines which fonts to use and how to space text. -#[derive(Debug, Clone, PartialEq)] -pub struct TextState { - /// A tree of font family names and generic class names. - pub fallback: Rc, - /// The selected font variant. - 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, - /// The font size. - pub font_size: FontSize, - /// The word spacing (relative to the the font size). - pub word_spacing: Linear, - /// The line spacing (relative to the the font size). - pub line_spacing: Linear, - /// The paragraphs spacing (relative to the the font size). - pub par_spacing: Linear, -} - -impl TextState { - /// The absolute font size. - pub fn font_size(&self) -> Length { - self.font_size.eval() - } - - /// The absolute word spacing. - pub fn word_spacing(&self) -> Length { - self.word_spacing.eval(self.font_size()) - } - - /// The absolute line spacing. - pub fn line_spacing(&self) -> Length { - self.line_spacing.eval(self.font_size()) - } - - /// The absolute paragraph spacing. - pub fn par_spacing(&self) -> Length { - self.par_spacing.eval(self.font_size()) - } -} - -impl Default for TextState { - fn default() -> Self { - Self { - fallback: Rc::new(fallback! { - list: ["sans-serif"], - classes: { - "serif" => ["source serif pro", "noto serif"], - "sans-serif" => ["source sans pro", "noto sans"], - "monospace" => ["source code pro", "noto sans mono"], - "math" => ["latin modern math", "serif"], - }, - base: [ - "source sans pro", "noto sans", "segoe ui emoji", - "noto emoji", "latin modern math", - ], - }), - variant: FontVariant { - style: FontStyle::Normal, - weight: FontWeight::REGULAR, - stretch: FontStretch::Normal, - }, - strong: false, - emph: false, - font_size: FontSize::abs(Length::pt(11.0)), - word_spacing: Relative::new(0.25).into(), - line_spacing: Relative::new(0.2).into(), - par_spacing: Relative::new(0.5).into(), - } - } -} - -/// The font size, defined by base and scale. -#[derive(Debug, Clone, PartialEq)] -pub struct FontSize { - /// The base font size, updated whenever the font size is set absolutely. - pub base: Length, - /// The scale to apply on the base font size, updated when the font size - /// is set relatively. - pub scale: Linear, -} - -impl FontSize { - /// Create a new font size. - pub fn new(base: Length, scale: impl Into) -> Self { - Self { base, scale: scale.into() } - } - - /// Create a new font size with the given `base` and a scale of `1.0`. - pub fn abs(base: Length) -> Self { - Self::new(base, Relative::ONE) - } - - /// Compute the absolute font size. - pub fn eval(&self) -> Length { - self.scale.eval(self.base) - } -} - -/// Defines the size and margins of a page. +/// Defines page properties. #[derive(Debug, Copy, Clone, PartialEq)] pub struct PageState { /// The class of this page. @@ -177,3 +77,87 @@ impl Default for PageState { Self::new(PAPER_A4) } } + +/// Defines paragraph properties. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ParState { + /// The spacing between words (dependent on scaled font size). + 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, +} + +impl Default for ParState { + fn default() -> Self { + Self { + word_spacing: Relative::new(0.25).into(), + line_spacing: Relative::new(0.2).into(), + par_spacing: Relative::new(0.5).into(), + } + } +} + +/// Defines font properties. +#[derive(Debug, Clone, PartialEq)] +pub struct FontState { + /// A tree of font family names and generic class names. + pub families: Rc, + /// The selected font variant. + pub variant: FontVariant, + /// The font size. + pub size: Length, + /// The linear to apply on the base font size. + pub scale: Linear, + /// 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, +} + +impl FontState { + /// The absolute font size. + pub fn font_size(&self) -> Length { + self.scale.eval(self.size) + } +} + +impl Default for FontState { + fn default() -> Self { + Self { + families: Rc::new(default_font_families()), + variant: FontVariant { + style: FontStyle::Normal, + weight: FontWeight::REGULAR, + stretch: FontStretch::Normal, + }, + size: Length::pt(11.0), + scale: Linear::ONE, + strong: false, + emph: false, + } + } +} + +/// The default tree of font fallbacks. +fn default_font_families() -> FallbackTree { + fallback! { + list: ["sans-serif"], + classes: { + "serif" => ["source serif pro", "noto serif"], + "sans-serif" => ["source sans pro", "noto sans"], + "monospace" => ["source code pro", "noto sans mono"], + "math" => ["latin modern math", "serif"], + }, + base: [ + "source sans pro", + "noto sans", + "segoe ui emoji", + "noto emoji", + "latin modern math", + ], + } +} diff --git a/src/geom/linear.rs b/src/geom/linear.rs index 2567d264c..d9860d43b 100644 --- a/src/geom/linear.rs +++ b/src/geom/linear.rs @@ -11,7 +11,10 @@ pub struct Linear { impl Linear { /// The zero linear. - pub const ZERO: Linear = Linear { rel: Relative::ZERO, abs: Length::ZERO }; + pub const ZERO: Self = Self { rel: Relative::ZERO, abs: Length::ZERO }; + + /// The linear with a relative part of `100%` and no absolute part. + pub const ONE: Self = Self { rel: Relative::ONE, abs: Length::ZERO }; /// Create a new linear. pub fn new(rel: Relative, abs: Length) -> Self { @@ -24,7 +27,7 @@ impl Linear { self.rel.eval(one) + self.abs } - /// Whether this linear's relative component is zero. + /// Whether this linear's relative part is zero. pub fn is_absolute(self) -> bool { self.rel == Relative::ZERO } diff --git a/src/layout/text.rs b/src/layout/text.rs index fafc1b14e..7ba4be210 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -12,7 +12,7 @@ pub struct Text { pub text: String, pub size: Length, pub dir: Dir, - pub fallback: Rc, + pub families: Rc, pub variant: FontVariant, pub aligns: Gen, } @@ -30,7 +30,7 @@ impl Layout for Text { self.size, self.dir, &mut loader, - &self.fallback, + &self.families, self.variant, ) .await; diff --git a/src/library/font.rs b/src/library/font.rs index e6d9cd127..7460b21b6 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -58,10 +58,10 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value { if let Some(linear) = args.find::() { if linear.is_absolute() { - ctx.state.text.font_size.base = linear.abs; - ctx.state.text.font_size.scale = Relative::ONE.into(); + ctx.state.font.size = linear.abs; + ctx.state.font.scale = Relative::ONE.into(); } else { - ctx.state.text.font_size.scale = linear; + ctx.state.font.scale = linear; } } @@ -69,20 +69,20 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value { let list: Vec<_> = args.find_all::().map(|s| s.to_lowercase()).collect(); if !list.is_empty() { - Rc::make_mut(&mut ctx.state.text.fallback).list = list; + Rc::make_mut(&mut ctx.state.font.families).list = list; needs_flattening = true; } if let Some(style) = args.get::<_, FontStyle>(ctx, "style") { - ctx.state.text.variant.style = style; + ctx.state.font.variant.style = style; } if let Some(weight) = args.get::<_, FontWeight>(ctx, "weight") { - ctx.state.text.variant.weight = weight; + ctx.state.font.variant.weight = weight; } if let Some(stretch) = args.get::<_, FontStretch>(ctx, "stretch") { - ctx.state.text.variant.stretch = stretch; + ctx.state.font.variant.stretch = stretch; } for (class, dict) in args.find_all_str::>() { @@ -91,14 +91,14 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value { .map(|s| s.to_lowercase()) .collect(); - Rc::make_mut(&mut ctx.state.text.fallback).update_class_list(class, fallback); + Rc::make_mut(&mut ctx.state.font.families).update_class_list(class, fallback); needs_flattening = true; } args.done(ctx); if needs_flattening { - Rc::make_mut(&mut ctx.state.text.fallback).flatten(); + Rc::make_mut(&mut ctx.state.font.families).flatten(); } if let Some(body) = body { diff --git a/src/library/spacing.rs b/src/library/spacing.rs index a2b21e93c..765153810 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -24,7 +24,7 @@ fn spacing(mut args: Args, ctx: &mut EvalContext, axis: SpecAxis) -> Value { args.done(ctx); if let Some(linear) = spacing { - let amount = linear.eval(ctx.state.text.font_size()); + let amount = linear.eval(ctx.state.font.font_size()); let spacing = Spacing { amount, softness: Softness::Hard }; if ctx.state.dirs.main.axis() == axis { ctx.end_par_group();