diff --git a/library/src/math/ctx.rs b/library/src/math/ctx.rs index 547d3cc85..fafb96f22 100644 --- a/library/src/math/ctx.rs +++ b/library/src/math/ctx.rs @@ -1,4 +1,5 @@ use ttf_parser::math::MathValue; +use typst::font::{FontStyle, FontWeight}; use unicode_segmentation::UnicodeSegmentation; use super::*; @@ -24,21 +25,18 @@ macro_rules! percent { /// The context for math layout. pub(super) struct MathContext<'a, 'b, 'v> { pub vt: &'v mut Vt<'b>, - pub outer: StyleChain<'a>, - pub map: StyleMap, pub regions: Regions<'a>, pub font: &'a Font, pub ttf: &'a ttf_parser::Face<'a>, pub table: ttf_parser::math::Table<'a>, pub constants: ttf_parser::math::Constants<'a>, pub space_width: Em, - pub fill: Paint, - pub lang: Lang, pub row: MathRow, + pub map: StyleMap, pub style: MathStyle, - base_size: Abs, - scaled_size: Abs, - style_stack: Vec, + pub size: Abs, + outer: StyleChain<'a>, + style_stack: Vec<(MathStyle, Abs)>, } impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { @@ -52,7 +50,6 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { let table = font.ttf().tables().math.unwrap(); let constants = table.constants.unwrap(); let size = styles.get(TextNode::SIZE); - let ttf = font.ttf(); let space_width = ttf .glyph_index(' ') @@ -60,38 +57,38 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { .map(|advance| font.to_em(advance)) .unwrap_or(THICK); + let variant = variant(styles); Self { vt, - outer: styles, - map: StyleMap::new(), regions: { let size = Size::new(regions.first.x, regions.base.y); Regions::one(size, regions.base, Axes::splat(false)) }, - style: MathStyle { - variant: MathVariant::Serif, - size: if block { MathSize::Display } else { MathSize::Text }, - cramped: false, - bold: false, - italic: true, - }, - fill: styles.get(TextNode::FILL), - lang: styles.get(TextNode::LANG), font: &font, ttf: font.ttf(), table, constants, space_width, row: MathRow::new(), - base_size: size, - scaled_size: size, + map: StyleMap::new(), + style: MathStyle { + variant: MathVariant::Serif, + size: if block { MathSize::Display } else { MathSize::Text }, + cramped: false, + bold: variant.weight >= FontWeight::BOLD, + italic: match variant.style { + FontStyle::Normal => Smart::Auto, + FontStyle::Italic | FontStyle::Oblique => Smart::Custom(true), + }, + }, + size, + outer: styles, style_stack: vec![], } } pub fn push(&mut self, fragment: impl Into) { - self.row - .push(self.scaled_size, self.space_width, self.style, fragment); + self.row.push(self.size, self.space_width, self.style, fragment); } pub fn extend(&mut self, row: MathRow) { @@ -130,11 +127,12 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { Ok(self.layout_fragment(node)?.to_frame(self)) } - pub fn layout_text(&mut self, text: &EcoString) -> SourceResult<()> { + pub fn layout_text(&mut self, text: &str) -> SourceResult<()> { let mut chars = text.chars(); if let Some(glyph) = chars .next() .filter(|_| chars.next().is_none()) + .map(|c| self.style.styled_char(c)) .and_then(|c| GlyphFragment::try_new(self, c)) { // A single letter that is available in the math font. @@ -147,10 +145,10 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { self.push(glyph); } } else if text.chars().all(|c| c.is_ascii_digit()) { - // A number that should respect math styling and can therefore - // not fall back to the normal text layout. + // Numbers aren't that difficult. let mut vec = vec![]; for c in text.chars() { + let c = self.style.styled_char(c); vec.push(GlyphFragment::new(self, c).into()); } let frame = MathRow(vec).to_frame(self); @@ -158,7 +156,12 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { } else { // Anything else is handled by Typst's standard text layout. let spaced = text.graphemes(true).count() > 1; - let frame = self.layout_non_math(&TextNode::packed(text.clone()))?; + let mut style = self.style; + if self.style.italic == Smart::Auto { + style = style.with_italic(false); + } + let text: EcoString = text.chars().map(|c| style.styled_char(c)).collect(); + let frame = self.layout_non_math(&TextNode::packed(text))?; self.push( FrameFragment::new(frame) .with_class(MathClass::Alphabetic) @@ -169,33 +172,35 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { Ok(()) } - pub fn size(&self) -> Abs { - self.scaled_size + pub fn styles(&self) -> StyleChain { + self.outer.chain(&self.map) } pub fn style(&mut self, style: MathStyle) { - self.style_stack.push(self.style); + self.style_stack.push((self.style, self.size)); + let base_size = self.styles().get(TextNode::SIZE) / self.style.size.factor(self); + self.size = base_size * style.size.factor(self); + self.map.set(TextNode::SIZE, TextSize(self.size.into())); + self.map.set( + TextNode::STYLE, + if style.italic == Smart::Custom(true) { + FontStyle::Italic + } else { + FontStyle::Normal + }, + ); + self.map.set( + TextNode::WEIGHT, + if style.bold { FontWeight::BOLD } else { FontWeight::REGULAR }, + ); self.style = style; - self.rescale(); - self.map.set(TextNode::SIZE, TextSize(self.scaled_size.into())); } pub fn unstyle(&mut self) { - self.style = self.style_stack.pop().unwrap(); - self.rescale(); + (self.style, self.size) = self.style_stack.pop().unwrap(); + self.map.unset(); + self.map.unset(); self.map.unset(); - } - - fn rescale(&mut self) { - self.scaled_size = match self.style.size { - MathSize::Display | MathSize::Text => self.base_size, - MathSize::Script => { - self.base_size * percent!(self, script_percent_scale_down) - } - MathSize::ScriptScript => { - self.base_size * percent!(self, script_script_percent_scale_down) - } - }; } } @@ -217,7 +222,7 @@ impl Scaled for u16 { impl Scaled for Em { fn scaled(self, ctx: &MathContext) -> Abs { - self.at(ctx.size()) + self.at(ctx.size) } } diff --git a/library/src/math/frac.rs b/library/src/math/frac.rs index e945473ca..ebdb5c026 100644 --- a/library/src/math/frac.rs +++ b/library/src/math/frac.rs @@ -157,8 +157,10 @@ fn layout( frame.push( line_pos, Element::Shape( - Geometry::Line(Point::with_x(line_width)) - .stroked(Stroke { paint: ctx.fill, thickness }), + Geometry::Line(Point::with_x(line_width)).stroked(Stroke { + paint: ctx.styles().get(TextNode::FILL), + thickness, + }), ), ); ctx.push(frame); diff --git a/library/src/math/fragment.rs b/library/src/math/fragment.rs index fef57a0ad..a7ca8db3e 100644 --- a/library/src/math/fragment.rs +++ b/library/src/math/fragment.rs @@ -121,6 +121,8 @@ impl From for MathFragment { pub(super) struct GlyphFragment { pub id: GlyphId, pub c: char, + pub lang: Lang, + pub fill: Paint, pub font_size: Abs, pub width: Abs, pub ascent: Abs, @@ -131,7 +133,6 @@ pub(super) struct GlyphFragment { impl GlyphFragment { pub fn new(ctx: &MathContext, c: char) -> Self { - let c = ctx.style.styled_char(c); let id = ctx.ttf.glyph_index(c).unwrap_or_default(); Self::with_id(ctx, c, id) } @@ -154,7 +155,9 @@ impl GlyphFragment { Self { id, c, - font_size: ctx.size(), + lang: ctx.styles().get(TextNode::LANG), + fill: ctx.styles().get(TextNode::FILL), + font_size: ctx.size, width: advance.scaled(ctx), ascent: bbox.y_max.scaled(ctx), descent: -bbox.y_min.scaled(ctx), @@ -184,12 +187,12 @@ impl GlyphFragment { let text = Text { font: ctx.font.clone(), size: self.font_size, - fill: ctx.fill, - lang: ctx.lang, + fill: self.fill, + lang: self.lang, glyphs: vec![Glyph { id: self.id.0, c: self.c, - x_advance: Em::from_length(self.width, ctx.size()), + x_advance: Em::from_length(self.width, ctx.size), x_offset: Em::zero(), }], }; diff --git a/library/src/math/lr.rs b/library/src/math/lr.rs index 30ff532aa..e265affb8 100644 --- a/library/src/math/lr.rs +++ b/library/src/math/lr.rs @@ -66,7 +66,7 @@ impl LayoutMath for LrNode { let height = self .size .unwrap_or(Rel::one()) - .resolve(ctx.outer.chain(&ctx.map)) + .resolve(ctx.styles()) .relative_to(2.0 * max_extent); match row.0.as_mut_slice() { diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs index 45ebdda7a..72ef02531 100644 --- a/library/src/math/matrix.rs +++ b/library/src/math/matrix.rs @@ -39,20 +39,11 @@ impl VecNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.all()?).pack()) } - - fn field(&self, name: &str) -> Option { - match name { - "elements" => { - Some(Value::Array(self.0.iter().cloned().map(Value::Content).collect())) - } - _ => None, - } - } } impl LayoutMath for VecNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let delim = ctx.outer.get(Self::DELIM); + let delim = ctx.styles().get(Self::DELIM); layout(ctx, &self.0, Align::Center, Some(delim.open()), Some(delim.close())) } } @@ -86,15 +77,6 @@ impl CasesNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.all()?).pack()) } - - fn field(&self, name: &str) -> Option { - match name { - "branches" => { - Some(Value::Array(self.0.iter().cloned().map(Value::Content).collect())) - } - _ => None, - } - } } impl LayoutMath for CasesNode { diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index f1bcd8f45..ab67a0d3d 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -29,29 +29,27 @@ pub use self::root::*; pub use self::stack::*; pub use self::style::*; -use ttf_parser::GlyphId; -use ttf_parser::Rect; +use ttf_parser::{GlyphId, Rect}; use typst::font::Font; -use typst::model::{Guard, Module, Scope, SequenceNode}; +use typst::model::{Guard, Module, Scope, SequenceNode, StyledNode}; use unicode_math_class::MathClass; use self::ctx::*; use self::fragment::*; use self::row::*; use self::spacing::*; -use crate::layout::HNode; -use crate::layout::ParNode; -use crate::layout::Spacing; +use crate::layout::{HNode, ParNode, Spacing}; use crate::prelude::*; -use crate::text::LinebreakNode; -use crate::text::TextNode; -use crate::text::TextSize; -use crate::text::{families, variant, FallbackList, FontFamily, SpaceNode}; +use crate::text::{ + families, variant, FallbackList, FontFamily, LinebreakNode, SpaceNode, TextNode, + TextSize, +}; /// Create a module with all math definitions. pub fn module(sym: &Module) -> Module { let mut math = Scope::deduplicating(); math.def_func::("formula"); + math.def_func::("text"); // Grouping. math.def_func::("lr"); @@ -83,6 +81,7 @@ pub fn module(sym: &Module) -> Module { math.def_func::("root"); // Styles. + math.def_func::("upright"); math.def_func::("bold"); math.def_func::("italic"); math.def_func::("serif"); @@ -243,6 +242,24 @@ impl LayoutMath for FormulaNode { impl LayoutMath for Content { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + if let Some(node) = self.to::() { + for child in &node.0 { + child.layout_math(ctx)?; + } + return Ok(()); + } + + if let Some(styled) = self.to::() { + let prev_map = std::mem::replace(&mut ctx.map, styled.map.clone()); + let prev_size = ctx.size; + ctx.map.apply(prev_map.clone()); + ctx.size = ctx.styles().get(TextNode::SIZE); + styled.sub.layout_math(ctx)?; + ctx.size = prev_size; + ctx.map = prev_map; + return Ok(()); + } + if self.is::() { ctx.push(MathFragment::Space); return Ok(()); @@ -256,9 +273,7 @@ impl LayoutMath for Content { if let Some(node) = self.to::() { if let Spacing::Relative(rel) = node.amount { if rel.rel.is_zero() { - ctx.push(MathFragment::Spacing( - rel.abs.resolve(ctx.outer.chain(&ctx.map)), - )); + ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles()))); } } return Ok(()); @@ -269,13 +284,6 @@ impl LayoutMath for Content { return Ok(()); } - if let Some(node) = self.to::() { - for child in &node.0 { - child.layout_math(ctx)?; - } - return Ok(()); - } - if let Some(node) = self.with::() { return node.layout_math(ctx); } diff --git a/library/src/math/root.rs b/library/src/math/root.rs index 79bcfe385..1416325c1 100644 --- a/library/src/math/root.rs +++ b/library/src/math/root.rs @@ -144,7 +144,7 @@ fn layout( line_pos, Element::Shape( Geometry::Line(Point::with_x(line_length)) - .stroked(Stroke { paint: ctx.fill, thickness }), + .stroked(Stroke { paint: ctx.styles().get(TextNode::FILL), thickness }), ), ); frame.push_frame(radicand_pos, radicand); diff --git a/library/src/math/row.rs b/library/src/math/row.rs index f75aed99e..bc0131828 100644 --- a/library/src/math/row.rs +++ b/library/src/math/row.rs @@ -77,7 +77,7 @@ impl MathRow { let mut frame = Frame::new(Size::zero()); let fragments = std::mem::take(&mut self.0); - let leading = ctx.outer.chain(&ctx.map).get(ParNode::LEADING); + let leading = ctx.styles().get(ParNode::LEADING); let rows: Vec<_> = fragments .split(|frag| matches!(frag, MathFragment::Linebreak)) .map(|slice| Self(slice.to_vec())) diff --git a/library/src/math/style.rs b/library/src/math/style.rs index d408b5322..14f97ae8a 100644 --- a/library/src/math/style.rs +++ b/library/src/math/style.rs @@ -1,5 +1,40 @@ use super::*; +/// # Upright +/// Upright (non-italic) font style in math. +/// +/// ## Example +/// ``` +/// $ upright(A) != A $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The piece of formula to style. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct UprightNode(pub Content); + +#[node] +impl UprightNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Self(args.expect("body")?).pack()) + } +} + +impl LayoutMath for UprightNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + ctx.style(ctx.style.with_italic(false)); + self.0.layout_math(ctx)?; + ctx.unstyle(); + Ok(()) + } +} + /// # Bold /// Bold font style in math. /// @@ -28,7 +63,7 @@ impl BoldNode { impl LayoutMath for BoldNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_italic(false).with_bold_toggled()); + ctx.style(ctx.style.with_bold(true)); self.0.layout_math(ctx)?; ctx.unstyle(); Ok(()) @@ -60,7 +95,7 @@ impl ItalicNode { impl LayoutMath for ItalicNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - ctx.style(ctx.style.with_italic_toggled()); + ctx.style(ctx.style.with_italic(true)); self.0.layout_math(ctx)?; ctx.unstyle(); Ok(()) @@ -290,22 +325,7 @@ pub struct MathStyle { /// Whether to use bold glyphs. pub bold: bool, /// Whether to use italic glyphs. - pub italic: bool, -} - -/// The size of elements in a formula. -/// -/// See the TeXbook p. 141. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum MathSize { - /// Second-level sub- and superscripts. - ScriptScript, - /// Sub- and superscripts. - Script, - /// Math in text. - Text, - /// Math on its own line. - Display, + pub italic: Smart, } impl MathStyle { @@ -331,17 +351,7 @@ impl MathStyle { /// This style, with `italic` set to the given value. pub fn with_italic(self, italic: bool) -> Self { - Self { italic, ..self } - } - - /// This style, with boldness inverted. - pub fn with_bold_toggled(self) -> Self { - self.with_bold(!self.bold) - } - - /// This style, with italicness inverted. - pub fn with_italic_toggled(self) -> Self { - self.with_italic(!self.italic) + Self { italic: Smart::Custom(italic), ..self } } /// The style for subscripts in the current style. @@ -377,6 +387,31 @@ impl MathStyle { } } +/// The size of elements in a formula. +/// +/// See the TeXbook p. 141. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum MathSize { + /// Second-level sub- and superscripts. + ScriptScript, + /// Sub- and superscripts. + Script, + /// Math in text. + Text, + /// Math on its own line. + Display, +} + +impl MathSize { + pub(super) fn factor(self, ctx: &MathContext) -> f64 { + match self { + Self::Display | Self::Text => 1.0, + Self::Script => percent!(ctx, script_percent_scale_down), + Self::ScriptScript => percent!(ctx, script_script_percent_scale_down), + } + } +} + /// A mathematical style variant, as defined by Unicode. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum MathVariant { @@ -401,17 +436,17 @@ impl Default for MathVariant { pub(super) fn styled_char(style: MathStyle, c: char) -> char { use MathVariant::*; - let tuple = (style.variant, style.bold, style.italic); - let base = match c { - 'a'..='z' => 'a', - 'A'..='Z' => 'A', - 'α'..='ω' => 'α', - 'Α'..='Ω' => 'Α', - '0'..='9' => '0', + let (base, default_italic) = match c { + 'a'..='z' => ('a', true), + 'A'..='Z' => ('A', true), + 'α'..='ω' => ('α', false), + 'Α'..='Ω' => ('Α', false), + '0'..='9' => ('0', false), '-' => return '−', _ => return c, }; + let tuple = (style.variant, style.bold, style.italic.unwrap_or(default_italic)); let start = match c { // Latin upper. 'A'..='Z' => match tuple {