From 73615f7e3ce23f2ea656d04ea9f96184f5ebdc0a Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 24 Mar 2021 17:12:34 +0100 Subject: =?UTF-8?q?Text=20shaping=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Shapes text with rustybuzz - Font fallback with family list - Tofus are shown in the first font Co-Authored-By: Martin --- src/exec/context.rs | 56 ++++++++++++--------------------------- src/exec/state.rs | 76 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 77 insertions(+), 55 deletions(-) (limited to 'src/exec') diff --git a/src/exec/context.rs b/src/exec/context.rs index 6ba5b25f..b6a67a2e 100644 --- a/src/exec/context.rs +++ b/src/exec/context.rs @@ -1,7 +1,4 @@ use std::mem; -use std::rc::Rc; - -use fontdock::FontStyle; use super::{Exec, FontFamily, State}; use crate::diag::{Diag, DiagSet, Pass}; @@ -87,7 +84,7 @@ impl<'a> ExecContext<'a> { /// Push a word space into the active paragraph. pub fn push_space(&mut self) { - let em = self.state.font.font_size(); + let em = self.state.font.resolve_size(); self.push(SpacingNode { amount: self.state.par.word_spacing.resolve(em), softness: 1, @@ -103,19 +100,19 @@ impl<'a> ExecContext<'a> { while let Some(c) = scanner.eat_merging_crlf() { if is_newline(c) { - self.push(self.make_text_node(mem::take(&mut line))); + self.push(TextNode::new(mem::take(&mut line), &self.state)); self.push_linebreak(); } else { line.push(c); } } - self.push(self.make_text_node(line)); + self.push(TextNode::new(line, &self.state)); } /// Apply a forced line break. pub fn push_linebreak(&mut self) { - let em = self.state.font.font_size(); + let em = self.state.font.resolve_size(); self.push_into_stack(SpacingNode { amount: self.state.par.leading.resolve(em), softness: 2, @@ -124,7 +121,7 @@ impl<'a> ExecContext<'a> { /// Apply a forced paragraph break. pub fn push_parbreak(&mut self) { - let em = self.state.font.font_size(); + let em = self.state.font.resolve_size(); self.push_into_stack(SpacingNode { amount: self.state.par.spacing.resolve(em), softness: 1, @@ -154,36 +151,6 @@ impl<'a> ExecContext<'a> { result } - /// Construct a text node from the given string based on the active text - /// state. - pub fn make_text_node(&self, text: String) -> TextNode { - let mut variant = self.state.font.variant; - - if self.state.font.strong { - variant.weight = variant.weight.thicken(300); - } - - if self.state.font.emph { - variant.style = match variant.style { - FontStyle::Normal => FontStyle::Italic, - FontStyle::Italic => FontStyle::Normal, - FontStyle::Oblique => FontStyle::Normal, - } - } - - TextNode { - text, - dir: self.state.dirs.cross, - aligns: self.state.aligns, - families: Rc::clone(&self.state.font.families), - variant, - font_size: self.state.font.font_size(), - top_edge: self.state.font.top_edge, - bottom_edge: self.state.font.bottom_edge, - color: self.state.font.color, - } - } - /// Finish the active paragraph. fn finish_par(&mut self) { let mut par = mem::replace(&mut self.par, ParNode::new(&self.state)); @@ -292,7 +259,7 @@ impl StackNode { impl ParNode { fn new(state: &State) -> Self { - let em = state.font.font_size(); + let em = state.font.resolve_size(); Self { dirs: state.dirs, aligns: state.aligns, @@ -301,3 +268,14 @@ impl ParNode { } } } + +impl TextNode { + fn new(text: String, state: &State) -> Self { + Self { + text, + dir: state.dirs.cross, + aligns: state.aligns, + props: state.font.resolve_props(), + } + } +} diff --git a/src/exec/state.rs b/src/exec/state.rs index 0322c437..7957f312 100644 --- a/src/exec/state.rs +++ b/src/exec/state.rs @@ -4,8 +4,9 @@ use std::rc::Rc; use fontdock::{FontStretch, FontStyle, FontVariant, FontWeight}; use crate::color::{Color, RgbaColor}; +use crate::font::VerticalFontMetric; use crate::geom::*; -use crate::layout::{Fill, VerticalFontMetric}; +use crate::layout::Fill; use crate::paper::{Paper, PaperClass, PAPER_A4}; /// The evaluation state. @@ -100,7 +101,7 @@ impl Default for ParState { #[derive(Debug, Clone, PartialEq)] pub struct FontState { /// A list of font families with generic class definitions. - pub families: Rc, + pub families: Rc, /// The selected font variant. pub variant: FontVariant, /// The font size. @@ -111,32 +112,58 @@ pub struct FontState { pub top_edge: VerticalFontMetric, /// The bottom end of the text bounding box. pub bottom_edge: VerticalFontMetric, + /// The glyph fill color / texture. + pub color: Fill, /// 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 glyph fill color / texture. - pub color: Fill, } impl FontState { - /// Access the `families` mutably. - pub fn families_mut(&mut self) -> &mut FamilyMap { - Rc::make_mut(&mut self.families) + /// The resolved font size. + pub fn resolve_size(&self) -> Length { + self.scale.resolve(self.size) } - /// The absolute font size. - pub fn font_size(&self) -> Length { - self.scale.resolve(self.size) + /// Resolve font properties. + pub fn resolve_props(&self) -> FontProps { + 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, + } + } + + FontProps { + families: Rc::clone(&self.families), + variant, + size: self.resolve_size(), + top_edge: self.top_edge, + bottom_edge: self.bottom_edge, + color: self.color, + } + } + + /// Access the `families` mutably. + pub fn families_mut(&mut self) -> &mut FamilyList { + Rc::make_mut(&mut self.families) } } impl Default for FontState { fn default() -> Self { Self { - families: Rc::new(FamilyMap::default()), + families: Rc::new(FamilyList::default()), variant: FontVariant { style: FontStyle::Normal, weight: FontWeight::REGULAR, @@ -146,16 +173,33 @@ impl Default for FontState { top_edge: VerticalFontMetric::CapHeight, bottom_edge: VerticalFontMetric::Baseline, scale: Linear::ONE, + color: Fill::Color(Color::Rgba(RgbaColor::BLACK)), strong: false, emph: false, - color: Fill::Color(Color::Rgba(RgbaColor::BLACK)), } } } +/// Properties used for font selection and layout. +#[derive(Debug, Clone, PartialEq)] +pub struct FontProps { + /// The list of font families to use for shaping. + pub families: Rc, + /// Which variant of the font to use. + pub variant: FontVariant, + /// The font size. + pub size: Length, + /// What line to consider the top edge of text. + pub top_edge: VerticalFontMetric, + /// What line to consider the bottom edge of text. + pub bottom_edge: VerticalFontMetric, + /// The color of the text. + pub color: Fill, +} + /// Font family definitions. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub struct FamilyMap { +pub struct FamilyList { /// The user-defined list of font families. pub list: Vec, /// Definition of serif font families. @@ -168,9 +212,9 @@ pub struct FamilyMap { pub base: Vec, } -impl FamilyMap { +impl FamilyList { /// Flat iterator over this map's family names. - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator + Clone { self.list .iter() .flat_map(move |family: &FontFamily| { @@ -186,7 +230,7 @@ impl FamilyMap { } } -impl Default for FamilyMap { +impl Default for FamilyList { fn default() -> Self { Self { list: vec![FontFamily::Serif], -- cgit v1.2.3