diff --git a/src/exec/context.rs b/src/exec/context.rs index 630082606..e03a7c677 100644 --- a/src/exec/context.rs +++ b/src/exec/context.rs @@ -1,4 +1,5 @@ use std::mem; +use std::rc::Rc; use super::{Exec, ExecWithMap, FontFamily, State}; use crate::diag::{Diag, DiagSet, Pass}; @@ -43,8 +44,11 @@ impl ExecContext { /// Set the font to monospace. pub fn set_monospace(&mut self) { - let families = self.state.font.families_mut(); - families.list.insert(0, FontFamily::Monospace); + self.state + .font_mut() + .families_mut() + .list + .insert(0, FontFamily::Monospace); } /// Execute a template and return the result as a stack node. @@ -108,8 +112,7 @@ impl ExecContext { /// Apply a forced paragraph break. pub fn parbreak(&mut self) { - let em = self.state.font.resolve_size(); - let amount = self.state.par.spacing.resolve(em); + let amount = self.state.par.spacing.resolve(self.state.font.size); self.stack.parbreak(&self.state); self.stack.push_soft(StackChild::Spacing(amount)); } @@ -133,9 +136,11 @@ impl ExecContext { } fn make_text_node(&self, text: impl Into) -> ParChild { - let align = self.state.aligns.cross; - let props = self.state.font.resolve_props(); - ParChild::Text(text.into(), props, align) + ParChild::Text( + text.into(), + self.state.aligns.cross, + Rc::clone(&self.state.font), + ) } } @@ -217,11 +222,10 @@ struct ParBuilder { impl ParBuilder { fn new(state: &State) -> Self { - let em = state.font.resolve_size(); Self { aligns: state.aligns, dir: state.lang.dir, - line_spacing: state.par.leading.resolve(em), + line_spacing: state.par.leading.resolve(state.font.size), children: vec![], last: Last::None, } diff --git a/src/exec/mod.rs b/src/exec/mod.rs index a13d8c5ed..639c1a875 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -55,8 +55,8 @@ impl ExecWithMap for syntax::Node { Self::Space => ctx.push_word_space(), Self::Linebreak(_) => ctx.linebreak(), Self::Parbreak(_) => ctx.parbreak(), - Self::Strong(_) => ctx.state.font.strong ^= true, - Self::Emph(_) => ctx.state.font.emph ^= true, + Self::Strong(_) => ctx.state.font_mut().strong ^= true, + Self::Emph(_) => ctx.state.font_mut().emph ^= true, Self::Raw(raw) => raw.exec(ctx), Self::Heading(heading) => heading.exec_with_map(ctx, map), Self::List(list) => list.exec_with_map(ctx, map), @@ -85,10 +85,11 @@ impl Exec for syntax::RawNode { impl ExecWithMap for syntax::HeadingNode { fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { let snapshot = ctx.state.clone(); + let font = ctx.state.font_mut(); let upscale = 1.6 - 0.1 * self.level as f64; - ctx.state.font.scale *= upscale; - ctx.state.font.strong = true; + font.size *= upscale; + font.strong = true; self.body.exec_with_map(ctx, map); @@ -109,7 +110,7 @@ impl ExecWithMap for syntax::ListNode { aspect: None, children: vec![ StackChild::Any(bullet.into(), Gen::default()), - StackChild::Spacing(ctx.state.font.resolve_size() / 2.0), + StackChild::Spacing(ctx.state.font.size / 2.0), StackChild::Any(body.into(), Gen::default()), ], }; @@ -139,7 +140,7 @@ impl Exec for Value { let prev = Rc::clone(&ctx.state.font.families); ctx.set_monospace(); ctx.push_text(pretty(other)); - ctx.state.font.families = prev; + ctx.state.font_mut().families = prev; } } } diff --git a/src/exec/state.rs b/src/exec/state.rs index 2b824afe0..fa16004ed 100644 --- a/src/exec/state.rs +++ b/src/exec/state.rs @@ -8,7 +8,7 @@ use crate::layout::Fill; use crate::paper::{Paper, PaperClass, PAPER_A4}; /// The execution state. -#[derive(Debug, Clone, PartialEq)] +#[derive(Default, Debug, Clone, PartialEq, Hash)] pub struct State { /// The current language-related settings. pub lang: LangState, @@ -17,25 +17,20 @@ pub struct State { /// The current paragraph settings. pub par: ParState, /// The current font settings. - pub font: FontState, + pub font: Rc, /// The current alignments of layouts in their parents. pub aligns: Gen, } -impl Default for State { - fn default() -> Self { - Self { - lang: LangState::default(), - page: PageState::default(), - par: ParState::default(), - font: FontState::default(), - aligns: Gen::splat(Align::Start), - } +impl State { + /// Access the `font` state mutably. + pub fn font_mut(&mut self) -> &mut FontState { + Rc::make_mut(&mut self.font) } } /// Defines language properties. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Hash)] pub struct LangState { /// The direction for text and other inline objects. pub dir: Dir, @@ -48,7 +43,7 @@ impl Default for LangState { } /// Defines page properties. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Hash)] pub struct PageState { /// The class of this page. pub class: PaperClass, @@ -88,7 +83,7 @@ impl Default for PageState { } /// Defines paragraph properties. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Hash)] pub struct ParState { /// The spacing between paragraphs (dependent on scaled font size). pub spacing: Linear, @@ -110,7 +105,7 @@ impl Default for ParState { } /// Defines font properties. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Hash)] pub struct FontState { /// A list of font families with generic class definitions. pub families: Rc, @@ -118,8 +113,6 @@ pub struct FontState { pub variant: FontVariant, /// The font size. pub size: Length, - /// The linear to apply on the base font size. - pub scale: Linear, /// The top end of the text bounding box. pub top_edge: VerticalFontMetric, /// The bottom end of the text bounding box. @@ -133,49 +126,14 @@ pub struct FontState { /// whether the next `_` makes italic or non-italic. pub emph: bool, /// The specifications for a strikethrough line, if any. - pub strikethrough: Option, + pub strikethrough: Option>, /// The specifications for a underline, if any. - pub underline: Option, + pub underline: Option>, /// The specifications for a overline line, if any. - pub overline: Option, + pub overline: Option>, } impl FontState { - /// The resolved font size. - pub fn resolve_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, - } - } - - let size = self.resolve_size(); - FontProps { - families: Rc::clone(&self.families), - variant, - size, - top_edge: self.top_edge, - bottom_edge: self.bottom_edge, - strikethrough: self.strikethrough.map(|s| s.resolve_props(size, &self.fill)), - underline: self.underline.map(|s| s.resolve_props(size, &self.fill)), - overline: self.overline.map(|s| s.resolve_props(size, &self.fill)), - fill: self.fill, - } - } - /// Access the `families` mutably. pub fn families_mut(&mut self) -> &mut FamilyList { Rc::make_mut(&mut self.families) @@ -194,7 +152,6 @@ impl Default for FontState { size: Length::pt(11.0), top_edge: VerticalFontMetric::CapHeight, bottom_edge: VerticalFontMetric::Baseline, - scale: Linear::one(), fill: Fill::Color(Color::Rgba(RgbaColor::BLACK)), strong: false, emph: false, @@ -205,11 +162,9 @@ impl Default for FontState { } } -/// Describes a line that could be positioned over or under text. +/// Describes a line that could be positioned over, under or on top of text. #[derive(Debug, Copy, Clone, PartialEq, Hash)] pub struct LineState { - /// Color of the line. Will default to text color if `None`. - pub fill: 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 strength: Option, @@ -219,40 +174,8 @@ pub struct LineState { pub position: Option, /// Amount that the line will be longer or shorter than its associated text. pub extent: Linear, -} - -impl LineState { - pub fn resolve_props(&self, font_size: Length, fill: &Fill) -> LineProps { - LineProps { - fill: self.fill.unwrap_or_else(|| fill.clone()), - strength: self.strength.map(|s| s.resolve(font_size)), - position: self.position.map(|p| p.resolve(font_size)), - extent: self.extent.resolve(font_size), - } - } -} - -/// Properties used for font selection and layout. -#[derive(Debug, Clone, PartialEq, Hash)] -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 fill color of the text. - pub fill: Fill, - /// 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, + /// Color of the line. Will default to text color if `None`. + pub fill: Option, } /// Font family definitions. @@ -319,19 +242,3 @@ impl Display for FontFamily { }) } } - -/// Describes a line that could be positioned over or under text. -#[derive(Debug, Copy, Clone, PartialEq, Hash)] -pub struct LineProps { - /// Color of the line. - pub fill: Fill, - /// Thickness of the line's stroke. Calling functions should attempt to - /// read this value from the appropriate font tables if this is `None`. - pub strength: 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 position: Option, - /// Amount that the line will be longer or shorter than its associated text. - pub extent: Length, -} diff --git a/src/export/pdf.rs b/src/export/pdf.rs index da3c9369d..f939d8abd 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -14,7 +14,7 @@ use ttf_parser::{name_id, GlyphId}; use crate::cache::Cache; use crate::color::Color; -use crate::font::{Em, FaceId, VerticalFontMetric}; +use crate::font::{Em, FaceId}; use crate::geom::{self, Length, Size}; use crate::image::{Image, ImageId}; use crate::layout::{Element, Fill, Frame, Shape}; @@ -256,9 +256,9 @@ impl<'a> PdfExporter<'a> { ); let italic_angle = ttf.italic_angle().unwrap_or(0.0); - let ascender = face.vertical_metric(VerticalFontMetric::Ascender).to_pdf(); - let descender = face.vertical_metric(VerticalFontMetric::Descender).to_pdf(); - let cap_height = face.vertical_metric(VerticalFontMetric::CapHeight).to_pdf(); + let ascender = face.ascender.to_pdf(); + let descender = face.descender.to_pdf(); + let cap_height = face.cap_height.to_pdf(); let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0); // Write the base font object referencing the CID font. diff --git a/src/font.rs b/src/font.rs index a55a2a135..fc7a99d98 100644 --- a/src/font.rs +++ b/src/font.rs @@ -15,10 +15,19 @@ pub struct Face { index: u32, ttf: rustybuzz::Face<'static>, units_per_em: f64, - ascender: Em, - cap_height: Em, - x_height: Em, - descender: Em, + pub ascender: Em, + pub cap_height: Em, + pub x_height: Em, + pub descender: Em, + pub strikethrough: LineMetrics, + pub underline: LineMetrics, + pub overline: LineMetrics, +} + +/// Metrics for a decorative line. +pub struct LineMetrics { + pub strength: Em, + pub position: Em, } impl Face { @@ -35,22 +44,45 @@ impl Face { let ttf = rustybuzz::Face::from_slice(slice, index)?; - // Look up some metrics we may need often. let units_per_em = f64::from(ttf.units_per_em()); - let ascender = ttf.typographic_ascender().unwrap_or(ttf.ascender()); - let cap_height = ttf.capital_height().filter(|&h| h > 0).unwrap_or(ascender); - let x_height = ttf.x_height().filter(|&h| h > 0).unwrap_or(ascender); - let descender = ttf.typographic_descender().unwrap_or(ttf.descender()); + let to_em = |units| Em::from_units(units, units_per_em); + + let ascender = to_em(ttf.typographic_ascender().unwrap_or(ttf.ascender())); + let cap_height = ttf.capital_height().filter(|&h| h > 0).map_or(ascender, to_em); + let x_height = ttf.x_height().filter(|&h| h > 0).map_or(ascender, to_em); + let descender = to_em(ttf.typographic_descender().unwrap_or(ttf.descender())); + + let strikeout = ttf.strikeout_metrics(); + let underline = ttf.underline_metrics(); + let default = Em::new(0.06); + + let strikethrough = LineMetrics { + strength: strikeout.or(underline).map_or(default, |s| to_em(s.thickness)), + position: strikeout.map_or(Em::new(0.25), |s| to_em(s.position)), + }; + + let underline = LineMetrics { + strength: underline.or(strikeout).map_or(default, |s| to_em(s.thickness)), + position: underline.map_or(Em::new(-0.2), |s| to_em(s.position)), + }; + + let overline = LineMetrics { + strength: underline.strength, + position: cap_height + Em::new(0.1), + }; Some(Self { buffer, index, ttf, units_per_em, - ascender: Em::from_units(ascender, units_per_em), - cap_height: Em::from_units(cap_height, units_per_em), - x_height: Em::from_units(x_height, units_per_em), - descender: Em::from_units(descender, units_per_em), + ascender, + cap_height, + x_height, + descender, + strikethrough, + underline, + overline, }) } diff --git a/src/geom/angle.rs b/src/geom/angle.rs index f1db841c5..bf8bcc660 100644 --- a/src/geom/angle.rs +++ b/src/geom/angle.rs @@ -1,15 +1,18 @@ use super::*; +use decorum::N64; /// An angle. -#[derive(Default, Copy, Clone, PartialEq, PartialOrd)] +#[derive(Default, Copy, Clone, PartialEq, PartialOrd, Hash)] pub struct Angle { /// The angle in raw units. - raw: f64, + raw: N64, } impl Angle { /// The zero angle. - pub const ZERO: Self = Self { raw: 0.0 }; + pub fn zero() -> Self { + Self { raw: N64::from(0.0) } + } /// Create an angle from a number of radians. pub fn rad(rad: f64) -> Self { @@ -23,7 +26,7 @@ impl Angle { /// Create an angle from a number of raw units. pub fn raw(raw: f64) -> Self { - Self { raw } + Self { raw: N64::from(raw) } } /// Convert this to a number of radians. @@ -38,17 +41,17 @@ impl Angle { /// Get the value of this angle in raw units. pub fn to_raw(self) -> f64 { - self.raw + self.raw.into() } /// Create an angle from a value in a unit. pub fn with_unit(val: f64, unit: AngularUnit) -> Self { - Self { raw: val * unit.raw_scale() } + Self { raw: N64::from(val * unit.raw_scale()) } } /// Get the value of this length in unit. pub fn to_unit(self, unit: AngularUnit) -> f64 { - self.raw / unit.raw_scale() + self.to_raw() / unit.raw_scale() } } @@ -119,7 +122,7 @@ impl Div for Angle { type Output = f64; fn div(self, other: Self) -> f64 { - self.raw / other.raw + self.to_raw() / other.to_raw() } } @@ -130,7 +133,7 @@ assign_impl!(Angle /= f64); impl Sum for Angle { fn sum>(iter: I) -> Self { - iter.fold(Angle::ZERO, Add::add) + iter.fold(Angle::zero(), Add::add) } } /// Different units of angular measurement. diff --git a/src/geom/point.rs b/src/geom/point.rs index 8982dcea9..129205f15 100644 --- a/src/geom/point.rs +++ b/src/geom/point.rs @@ -3,7 +3,7 @@ use super::*; use serde::{Deserialize, Serialize}; /// A point in 2D. -#[derive(Default, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Default, Copy, Clone, PartialEq, Serialize, Deserialize, Hash)] pub struct Point { /// The x coordinate. pub x: Length, diff --git a/src/geom/size.rs b/src/geom/size.rs index b859cb800..ea5220914 100644 --- a/src/geom/size.rs +++ b/src/geom/size.rs @@ -3,7 +3,7 @@ use super::*; use serde::{Deserialize, Serialize}; /// A size in 2D. -#[derive(Default, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Default, Copy, Clone, PartialEq, Serialize, Deserialize, Hash)] pub struct Size { /// The width. pub width: Length, diff --git a/src/geom/spec.rs b/src/geom/spec.rs index e7a2d056d..782cf0f6c 100644 --- a/src/geom/spec.rs +++ b/src/geom/spec.rs @@ -1,7 +1,7 @@ use super::*; /// A container with a horizontal and vertical component. -#[derive(Default, Copy, Clone, Eq, PartialEq)] +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct Spec { /// The horizontal component. pub horizontal: T, diff --git a/src/layout/par.rs b/src/layout/par.rs index 8b3cbf8be..d51bad3eb 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -1,10 +1,11 @@ use std::fmt::{self, Debug, Formatter}; +use std::rc::Rc; use unicode_bidi::{BidiInfo, Level}; use xi_unicode::LineBreakIterator; use super::*; -use crate::exec::FontProps; +use crate::exec::FontState; use crate::util::{RangeExt, SliceExt}; type Range = std::ops::Range; @@ -26,7 +27,7 @@ pub enum ParChild { /// Spacing between other nodes. Spacing(Length), /// A run of text and how to align it in its line. - Text(String, FontProps, Align), + Text(String, Align, Rc), /// Any child node and how to align it in its line. Any(AnyNode, Align), } @@ -131,11 +132,11 @@ impl<'a> ParLayout<'a> { items.push(ParItem::Spacing(amount)); ranges.push(range); } - ParChild::Text(_, ref props, align) => { + ParChild::Text(_, align, ref state) => { // TODO: Also split by language and script. for (subrange, dir) in split_runs(&bidi, range) { let text = &bidi.text[subrange.clone()]; - let shaped = shape(ctx, text, dir, props); + let shaped = shape(ctx, text, dir, state); items.push(ParItem::Text(shaped, align)); ranges.push(subrange); } diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs index 232e9fc56..d8901471c 100644 --- a/src/layout/shaping.rs +++ b/src/layout/shaping.rs @@ -1,12 +1,12 @@ use std::borrow::Cow; use std::fmt::{self, Debug, Formatter}; -use std::ops::{Add, Range}; +use std::ops::Range; use rustybuzz::UnicodeBuffer; use super::{Element, Frame, Glyph, LayoutContext, Text}; -use crate::exec::FontProps; -use crate::font::{Em, Face, FaceId, VerticalFontMetric}; +use crate::exec::{FontState, LineState}; +use crate::font::{Face, FaceId, FontStyle, LineMetrics}; use crate::geom::{Dir, Length, Point, Size}; use crate::layout::Shape; use crate::util::SliceExt; @@ -23,7 +23,7 @@ pub struct ShapedText<'a> { /// The text direction. pub dir: Dir, /// The properties used for font selection. - pub props: &'a FontProps, + pub state: &'a FontState, /// The font size. pub size: Size, /// The baseline from the top of the frame. @@ -69,8 +69,8 @@ impl<'a> ShapedText<'a> { let mut text = Text { face_id, - size: self.props.size, - fill: self.props.fill, + size: self.state.size, + fill: self.state.fill, glyphs: vec![], }; @@ -85,7 +85,7 @@ impl<'a> ShapedText<'a> { } frame.push(pos, Element::Text(text)); - decorate(ctx, &mut frame, &self.props, face_id, pos, width); + decorate(ctx, &mut frame, pos, width, face_id, &self.state); offset += width; } @@ -101,17 +101,17 @@ impl<'a> ShapedText<'a> { text_range: Range, ) -> ShapedText<'a> { if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) { - let (size, baseline) = measure(ctx, glyphs, self.props); + let (size, baseline) = measure(ctx, glyphs, self.state); Self { text: &self.text[text_range], dir: self.dir, - props: self.props, + state: self.state, size, baseline, glyphs: Cow::Borrowed(glyphs), } } else { - shape(ctx, &self.text[text_range], self.dir, self.props) + shape(ctx, &self.text[text_range], self.dir, self.state) } } @@ -185,20 +185,20 @@ pub fn shape<'a>( ctx: &mut LayoutContext, text: &'a str, dir: Dir, - props: &'a FontProps, + state: &'a FontState, ) -> ShapedText<'a> { let mut glyphs = vec![]; - let families = props.families.iter(); + let families = state.families.iter(); if !text.is_empty() { - shape_segment(ctx, &mut glyphs, 0, text, dir, props, families, None); + shape_segment(ctx, &mut glyphs, 0, text, dir, state, families, None); } - let (size, baseline) = measure(ctx, &glyphs, props); + let (size, baseline) = measure(ctx, &glyphs, state); ShapedText { text, dir, - props, + state, size, baseline, glyphs: Cow::Owned(glyphs), @@ -212,7 +212,7 @@ fn shape_segment<'a>( base: usize, text: &str, dir: Dir, - props: &FontProps, + state: &FontState, mut families: impl Iterator + Clone, mut first_face: Option, ) { @@ -221,7 +221,21 @@ fn shape_segment<'a>( // Try to load the next available font family. match families.next() { Some(family) => { - match ctx.cache.font.select(ctx.loader, family, props.variant) { + 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, + } + } + + match ctx.cache.font.select(ctx.loader, family, variant) { Some(id) => break (id, true), None => {} } @@ -267,8 +281,8 @@ fn shape_segment<'a>( glyphs.push(ShapedGlyph { face_id, glyph_id: info.codepoint as u16, - x_advance: face.to_em(pos[i].x_advance).to_length(props.size), - x_offset: face.to_em(pos[i].x_offset).to_length(props.size), + 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), text_index: base + cluster, safe_to_break: !info.unsafe_to_break(), }); @@ -319,7 +333,7 @@ fn shape_segment<'a>( base + range.start, &text[range], dir, - props, + state, families.clone(), first_face, ); @@ -336,7 +350,7 @@ fn shape_segment<'a>( fn measure( ctx: &mut LayoutContext, glyphs: &[ShapedGlyph], - props: &FontProps, + state: &FontState, ) -> (Size, Length) { let cache = &mut ctx.cache.font; @@ -344,15 +358,15 @@ fn measure( let mut top = Length::zero(); let mut bottom = Length::zero(); let mut expand_vertical = |face: &Face| { - top.set_max(face.vertical_metric(props.top_edge).to_length(props.size)); - bottom.set_max(-face.vertical_metric(props.bottom_edge).to_length(props.size)); + top.set_max(face.vertical_metric(state.top_edge).to_length(state.size)); + bottom.set_max(-face.vertical_metric(state.bottom_edge).to_length(state.size)); }; if glyphs.is_empty() { // When there are no glyphs, we just use the vertical metrics of the // first available font. - for family in props.families.iter() { - if let Some(face_id) = cache.select(ctx.loader, family, props.variant) { + for family in state.families.iter() { + if let Some(face_id) = cache.select(ctx.loader, family, state.variant) { expand_vertical(cache.get(face_id)); break; } @@ -375,76 +389,44 @@ fn measure( fn decorate( ctx: &LayoutContext, frame: &mut Frame, - props: &FontProps, - face_id: FaceId, pos: Point, width: Length, + face_id: FaceId, + state: &FontState, ) { - let mut apply = |strength, position, extent, fill| { + let mut apply = |substate: &LineState, metrics: fn(&Face) -> &LineMetrics| { + let metrics = metrics(&ctx.cache.font.get(face_id)); + + let strength = substate + .strength + .map(|s| s.resolve(state.size)) + .unwrap_or(metrics.strength.to_length(state.size)); + + let position = substate + .position + .map(|s| s.resolve(state.size)) + .unwrap_or(metrics.position.to_length(state.size)); + + let extent = substate.extent.resolve(state.size); + let fill = substate.fill.unwrap_or(state.fill); + let pos = Point::new(pos.x - extent, pos.y - position); let target = Point::new(width + 2.0 * extent, Length::zero()); - frame.push(pos, Element::Geometry(Shape::Line(target, strength), fill)); + let shape = Shape::Line(target, strength); + let element = Element::Geometry(shape, fill); + + frame.push(pos, element); }; - if let Some(strikethrough) = props.strikethrough { - let face = ctx.cache.font.get(face_id); - - let strength = strikethrough.strength.unwrap_or_else(|| { - face.ttf() - .strikeout_metrics() - .or_else(|| face.ttf().underline_metrics()) - .map_or(Em::new(0.06), |m| face.to_em(m.thickness)) - .to_length(props.size) - }); - - let position = strikethrough.position.unwrap_or_else(|| { - face.ttf() - .strikeout_metrics() - .map_or(Em::new(0.25), |m| face.to_em(m.position)) - .to_length(props.size) - }); - - apply(strength, position, strikethrough.extent, strikethrough.fill); + if let Some(strikethrough) = &state.strikethrough { + apply(strikethrough, |face| &face.strikethrough); } - if let Some(underline) = props.underline { - let face = ctx.cache.font.get(face_id); - - let strength = underline.strength.unwrap_or_else(|| { - face.ttf() - .underline_metrics() - .or_else(|| face.ttf().strikeout_metrics()) - .map_or(Em::new(0.06), |m| face.to_em(m.thickness)) - .to_length(props.size) - }); - - let position = underline.position.unwrap_or_else(|| { - face.ttf() - .underline_metrics() - .map_or(Em::new(-0.2), |m| face.to_em(m.position)) - .to_length(props.size) - }); - - apply(strength, position, underline.extent, underline.fill); + if let Some(underline) = &state.underline { + apply(underline, |face| &face.underline); } - if let Some(overline) = props.overline { - let face = ctx.cache.font.get(face_id); - - let strength = overline.strength.unwrap_or_else(|| { - face.ttf() - .underline_metrics() - .or_else(|| face.ttf().strikeout_metrics()) - .map_or(Em::new(0.06), |m| face.to_em(m.thickness)) - .to_length(props.size) - }); - - let position = overline.position.unwrap_or_else(|| { - face.vertical_metric(VerticalFontMetric::CapHeight) - .add(Em::new(0.1)) - .to_length(props.size) - }); - - apply(strength, position, overline.extent, overline.fill); + if let Some(overline) = &state.overline { + apply(overline, |face| &face.overline); } } diff --git a/src/library/decorations.rs b/src/library/decorations.rs index ef9afd37c..423041c1c 100644 --- a/src/library/decorations.rs +++ b/src/library/decorations.rs @@ -55,7 +55,7 @@ fn line_impl( name: &str, ctx: &mut EvalContext, args: &mut FuncArgs, - substate: impl Fn(&mut FontState) -> &mut Option + 'static, + substate: fn(&mut FontState) -> &mut Option>, ) -> Value { let color = args.eat_named(ctx, "color"); let position = args.eat_named(ctx, "position"); @@ -64,17 +64,19 @@ fn line_impl( let body = args.eat::(ctx); // Suppress any existing strikethrough if strength is explicitly zero. - let state = strength.map_or(true, |s| !s.is_zero()).then(|| LineState { - fill: color.map(Fill::Color), - strength, - position, - extent, + let state = strength.map_or(true, |s| !s.is_zero()).then(|| { + Rc::new(LineState { + strength, + position, + extent, + fill: color.map(Fill::Color), + }) }); Value::template(name, move |ctx| { let snapshot = ctx.state.clone(); - *substate(&mut ctx.state.font) = state; + *substate(ctx.state.font_mut()) = state.clone(); if let Some(body) = &body { body.exec(ctx); diff --git a/src/library/font.rs b/src/library/font.rs index a3fe6c136..c1ed1c98f 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -64,54 +64,50 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { Value::template("font", move |ctx| { let snapshot = ctx.state.clone(); + let font = ctx.state.font_mut(); if let Some(linear) = size { - if linear.rel.is_zero() { - ctx.state.font.size = linear.abs; - ctx.state.font.scale = Linear::one(); - } else { - ctx.state.font.scale = linear; - } + font.size = linear.resolve(font.size); } if !list.is_empty() { - ctx.state.font.families_mut().list = list.clone(); + font.families_mut().list = list.clone(); } if let Some(style) = style { - ctx.state.font.variant.style = style; + font.variant.style = style; } if let Some(weight) = weight { - ctx.state.font.variant.weight = weight; + font.variant.weight = weight; } if let Some(stretch) = stretch { - ctx.state.font.variant.stretch = stretch; + font.variant.stretch = stretch; } if let Some(top_edge) = top_edge { - ctx.state.font.top_edge = top_edge; + font.top_edge = top_edge; } if let Some(bottom_edge) = bottom_edge { - ctx.state.font.bottom_edge = bottom_edge; + font.bottom_edge = bottom_edge; } if let Some(color) = color { - ctx.state.font.fill = Fill::Color(color); + font.fill = Fill::Color(color); } if let Some(FontFamilies(serif)) = &serif { - ctx.state.font.families_mut().serif = serif.clone(); + font.families_mut().serif = serif.clone(); } if let Some(FontFamilies(sans_serif)) = &sans_serif { - ctx.state.font.families_mut().sans_serif = sans_serif.clone(); + font.families_mut().sans_serif = sans_serif.clone(); } if let Some(FontFamilies(monospace)) = &monospace { - ctx.state.font.families_mut().monospace = monospace.clone(); + font.families_mut().monospace = monospace.clone(); } if let Some(body) = &body { diff --git a/src/library/mod.rs b/src/library/mod.rs index 553b39e65..0536eaa19 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -34,6 +34,7 @@ pub use spacing::*; pub use stack::*; use std::fmt::{self, Display, Formatter}; +use std::rc::Rc; use crate::color::RgbaColor; use crate::eval::{EvalContext, FuncArgs, Scope, TemplateValue, Value}; diff --git a/src/library/spacing.rs b/src/library/spacing.rs index ff6aaa69f..654a18c4b 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -31,7 +31,8 @@ fn spacing_impl( let spacing: Option = args.eat_expect(ctx, "spacing"); Value::template(name, move |ctx| { if let Some(linear) = spacing { - let amount = linear.resolve(ctx.state.font.resolve_size()); + // TODO: Should this really always be font-size relative? + let amount = linear.resolve(ctx.state.font.size); ctx.push_spacing(axis, amount); } }) diff --git a/src/paper.rs b/src/paper.rs index 58d87be16..8dfc071c5 100644 --- a/src/paper.rs +++ b/src/paper.rs @@ -26,7 +26,7 @@ impl Paper { } /// Defines default margins for a class of related papers. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum PaperClass { Custom, Base,