diff --git a/src/exec/context.rs b/src/exec/context.rs index 925fd7de5..13e53b091 100644 --- a/src/exec/context.rs +++ b/src/exec/context.rs @@ -1,7 +1,7 @@ use std::mem; use std::rc::Rc; -use super::{Exec, ExecWithMap, FontFamily, State}; +use super::{Exec, ExecWithMap, State}; use crate::diag::{Diag, DiagSet, Pass}; use crate::eco::EcoString; use crate::eval::{ExprMap, Template}; @@ -44,15 +44,6 @@ impl ExecContext { self.diags.insert(diag); } - /// Set the font to monospace. - pub fn set_monospace(&mut self) { - self.state - .font_mut() - .families_mut() - .list - .insert(0, FontFamily::Monospace); - } - /// Execute a template and return the result as a stack node. pub fn exec_template_stack(&mut self, template: &Template) -> StackNode { self.exec_stack(|ctx| template.exec(ctx)) @@ -83,6 +74,14 @@ impl ExecContext { self.stack.par.push(self.make_text_node(text)); } + /// Push text, but in monospace. + pub fn push_monospace_text(&mut self, text: impl Into) { + let prev = Rc::clone(&self.state.font); + self.state.font_mut().monospace = true; + self.push_text(text); + self.state.font = prev; + } + /// Push a word space into the active paragraph. pub fn push_word_space(&mut self) { self.stack.par.push_soft(self.make_text_node(" ")); diff --git a/src/exec/mod.rs b/src/exec/mod.rs index d61e07937..76857b443 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -7,7 +7,6 @@ pub use context::*; pub use state::*; use std::fmt::Write; -use std::rc::Rc; use crate::diag::Pass; use crate::eco::EcoString; @@ -75,10 +74,7 @@ impl Exec for RawNode { ctx.parbreak(); } - let snapshot = Rc::clone(&ctx.state.font); - ctx.set_monospace(); - ctx.push_text(&self.text); - ctx.state.font = snapshot; + ctx.push_monospace_text(&self.text); if self.block { ctx.parbreak(); @@ -143,14 +139,9 @@ impl Exec for Value { Value::Str(v) => ctx.push_text(v), Value::Template(v) => v.exec(ctx), Value::Error => {} - other => { - // For values which can't be shown "naturally", we print - // the representation in monospace. - let prev = Rc::clone(&ctx.state.font.families); - ctx.set_monospace(); - ctx.push_text(pretty(other)); - ctx.state.font_mut().families = prev; - } + // For values which can't be shown "naturally", we print the + // representation in monospace. + other => ctx.push_monospace_text(pretty(other)), } } } diff --git a/src/exec/state.rs b/src/exec/state.rs index f99a8901a..624394ba9 100644 --- a/src/exec/state.rs +++ b/src/exec/state.rs @@ -111,6 +111,14 @@ pub struct FontState { pub families: 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, + /// Whether the monospace toggle is active or inactive. + pub monospace: bool, /// The font size. pub size: Length, /// The top end of the text bounding box. @@ -119,12 +127,6 @@ pub struct FontState { pub bottom_edge: VerticalFontMetric, /// Glyph color. pub fill: Paint, - /// 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 specifications for a strikethrough line, if any. pub strikethrough: Option>, /// The specifications for a underline, if any. @@ -138,6 +140,35 @@ impl FontState { pub fn families_mut(&mut self) -> &mut FamilyList { Rc::make_mut(&mut self.families) } + + /// The canonical 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 canonical 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 + } } impl Default for FontState { @@ -149,12 +180,13 @@ impl Default for FontState { 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)), - strong: false, - emph: false, strikethrough: None, underline: None, overline: None, diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs index 5e1bc3277..3ede51227 100644 --- a/src/layout/shaping.rs +++ b/src/layout/shaping.rs @@ -6,7 +6,7 @@ use rustybuzz::UnicodeBuffer; use super::{Element, Frame, Glyph, LayoutContext, Text}; use crate::exec::{FontState, LineState}; -use crate::font::{Face, FaceId, FontStyle, LineMetrics}; +use crate::font::{Face, FaceId, FontVariant, LineMetrics}; use crate::geom::{Dir, Length, Point, Size}; use crate::layout::Geometry; use crate::util::SliceExt; @@ -188,9 +188,18 @@ pub fn shape<'a>( state: &'a FontState, ) -> ShapedText<'a> { let mut glyphs = vec![]; - let families = state.families.iter(); if !text.is_empty() { - shape_segment(ctx, &mut glyphs, 0, text, dir, state, families, None); + shape_segment( + ctx, + &mut glyphs, + 0, + text, + dir, + state.size, + state.variant(), + state.families(), + None, + ); } let (size, baseline) = measure(ctx, &glyphs, state); @@ -212,7 +221,8 @@ fn shape_segment<'a>( base: usize, text: &str, dir: Dir, - state: &FontState, + size: Length, + variant: FontVariant, mut families: impl Iterator + Clone, mut first_face: Option, ) { @@ -221,20 +231,6 @@ fn shape_segment<'a>( // Try to load the next available font family. match families.next() { Some(family) => { - let mut variant = state.variant; - - if state.strong { - variant.weight = variant.weight.thicken(300); - } - - if state.emph { - variant.style = match variant.style { - FontStyle::Normal => FontStyle::Italic, - FontStyle::Italic => FontStyle::Normal, - FontStyle::Oblique => FontStyle::Normal, - } - } - if let Some(id) = ctx.fonts.select(family, variant) { break (id, true); } @@ -280,8 +276,8 @@ fn shape_segment<'a>( glyphs.push(ShapedGlyph { face_id, glyph_id: info.glyph_id as u16, - x_advance: face.to_em(pos[i].x_advance).to_length(state.size), - x_offset: face.to_em(pos[i].x_offset).to_length(state.size), + x_advance: face.to_em(pos[i].x_advance).to_length(size), + x_offset: face.to_em(pos[i].x_offset).to_length(size), text_index: base + cluster, safe_to_break: !info.unsafe_to_break(), }); @@ -332,7 +328,8 @@ fn shape_segment<'a>( base + range.start, &text[range], dir, - state, + size, + variant, families.clone(), first_face, ); @@ -362,7 +359,7 @@ fn measure( if glyphs.is_empty() { // When there are no glyphs, we just use the vertical metrics of the // first available font. - for family in state.families.iter() { + for family in state.families() { if let Some(face_id) = ctx.fonts.select(family, state.variant) { expand_vertical(ctx.fonts.get(face_id)); break; diff --git a/tests/typeset.rs b/tests/typeset.rs index 08d812fb2..6b2258bf7 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -143,10 +143,7 @@ fn register_helpers(scope: &mut Scope, panics: Rc>>) { scope.def_func("args", |_, args| { let repr = typst::pretty::pretty(args); args.items.clear(); - Value::template(move |ctx| { - ctx.set_monospace(); - ctx.push_text(&repr); - }) + Value::template(move |ctx| ctx.push_monospace_text(&repr)) }); scope.def_func("test", move |ctx, args| { let lhs = args.expect::(ctx, "left-hand side");