diff options
Diffstat (limited to 'library/src')
| -rw-r--r-- | library/src/lib.rs | 7 | ||||
| -rw-r--r-- | library/src/math/accent.rs | 16 | ||||
| -rw-r--r-- | library/src/math/atom.rs | 58 | ||||
| -rw-r--r-- | library/src/math/attach.rs | 54 | ||||
| -rw-r--r-- | library/src/math/ctx.rs | 40 | ||||
| -rw-r--r-- | library/src/math/lr.rs | 82 | ||||
| -rw-r--r-- | library/src/math/mod.rs | 7 | ||||
| -rw-r--r-- | library/src/math/root.rs | 2 | ||||
| -rw-r--r-- | library/src/prelude.rs | 2 |
9 files changed, 153 insertions, 115 deletions
diff --git a/library/src/lib.rs b/library/src/lib.rs index 17f3de6d..41c621cb 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -191,12 +191,11 @@ fn items() -> LangItems { layout::ListItem::Term(basics::TermItem { term, description }).pack() }, formula: |body, block| math::FormulaNode { body, block }.pack(), - math_atom: |atom| math::AtomNode(atom).pack(), math_align_point: || math::AlignPointNode.pack(), - math_delimited: |open, body, close| math::LrNode(open + body + close).pack(), - math_attach: |base, sub, sup| { - math::AttachNode { base, top: sub, bottom: sup }.pack() + math_delimited: |open, body, close| { + math::LrNode { body: open + body + close, size: None }.pack() }, + math_attach: |base, bottom, top| math::AttachNode { base, bottom, top }.pack(), math_accent: |base, accent| math::AccentNode { base, accent }.pack(), math_frac: |num, denom| math::FracNode { num, denom }.pack(), } diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs index 90e64b96..fdb59c5c 100644 --- a/library/src/math/accent.rs +++ b/library/src/math/accent.rs @@ -10,8 +10,9 @@ const ACCENT_SHORT_FALL: Em = Em::new(0.5); /// /// ## Example /// ``` +/// $grave(a) = accent(a, `)$ \ /// $arrow(a) = accent(a, arrow)$ \ -/// $grave(a) = accent(a, `)$ +/// $tilde(a) = accent(a, \u{0303})$ /// ``` /// /// ## Parameters @@ -58,11 +59,22 @@ pub struct AccentNode { impl AccentNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { let base = args.expect("base")?; - let accent = args.expect("accent")?; + let accent = args.expect::<Accent>("accent")?.0; Ok(Self { base, accent }.pack()) } } +struct Accent(char); + +castable! { + Accent, + v: char => Self(v), + v: Content => match v.to::<TextNode>() { + Some(text) => Self(Value::Str(text.0.clone().into()).cast()?), + None => Err("expected text")?, + }, +} + impl LayoutMath for AccentNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { ctx.style(ctx.style.with_cramped(true)); diff --git a/library/src/math/atom.rs b/library/src/math/atom.rs deleted file mode 100644 index 6d7359bf..00000000 --- a/library/src/math/atom.rs +++ /dev/null @@ -1,58 +0,0 @@ -use super::*; - -/// # Atom -/// An atom in a math formula: `x`, `+`, `12`. -/// -/// ## Parameters -/// - text: EcoString (positional, required) -/// The atom's text. -/// -/// ## Category -/// math -#[func] -#[capable(LayoutMath)] -#[derive(Debug, Hash)] -pub struct AtomNode(pub EcoString); - -#[node] -impl AtomNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self(args.expect("text")?).pack()) - } -} - -impl LayoutMath for AtomNode { - fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let mut chars = self.0.chars(); - if let Some(glyph) = chars - .next() - .filter(|_| chars.next().is_none()) - .and_then(|c| GlyphFragment::try_new(ctx, c)) - { - // A single letter that is available in the math font. - if ctx.style.size == MathSize::Display - && glyph.class == Some(MathClass::Large) - { - let height = scaled!(ctx, display_operator_min_height); - ctx.push(glyph.stretch_vertical(ctx, height, Abs::zero())); - } else { - ctx.push(glyph); - } - } else if self.0.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. - let mut vec = vec![]; - for c in self.0.chars() { - vec.push(GlyphFragment::new(ctx, c).into()); - } - let frame = MathRow(vec).to_frame(ctx); - ctx.push(frame); - } else { - // Anything else is handled by Typst's standard text layout. - let frame = ctx.layout_non_math(&TextNode(self.0.clone()).pack())?; - ctx.push(FrameFragment::new(frame).with_class(MathClass::Alphabetic)); - } - - Ok(()) - } -} diff --git a/library/src/math/attach.rs b/library/src/math/attach.rs index 2205e556..0d774839 100644 --- a/library/src/math/attach.rs +++ b/library/src/math/attach.rs @@ -51,17 +51,17 @@ impl LayoutMath for AttachNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { let base = ctx.layout_fragment(&self.base)?; - let mut sub = Frame::new(Size::zero()); + let mut top = Frame::new(Size::zero()); if let Some(node) = &self.top { ctx.style(ctx.style.for_subscript()); - sub = ctx.layout_frame(node)?; + top = ctx.layout_frame(node)?; ctx.unstyle(); } - let mut sup = Frame::new(Size::zero()); + let mut bottom = Frame::new(Size::zero()); if let Some(node) = &self.bottom { ctx.style(ctx.style.for_superscript()); - sup = ctx.layout_frame(node)?; + bottom = ctx.layout_frame(node)?; ctx.unstyle(); } @@ -76,9 +76,9 @@ impl LayoutMath for AttachNode { }); if render_limits { - limits(ctx, base, sub, sup) + limits(ctx, base, top, bottom) } else { - scripts(ctx, base, sub, sup, self.top.is_some() && self.bottom.is_some()) + scripts(ctx, base, top, bottom, self.top.is_some() && self.bottom.is_some()) } } } @@ -151,8 +151,8 @@ impl LayoutMath for LimitsNode { fn scripts( ctx: &mut MathContext, base: MathFragment, - sub: Frame, sup: Frame, + sub: Frame, both: bool, ) -> SourceResult<()> { let sup_shift_up = if ctx.style.cramped { @@ -191,21 +191,28 @@ fn scripts( } } - let delta = base.italics_correction(); + let italics = base.italics_correction(); + let top_delta = match base.class() { + Some(MathClass::Large) => Abs::zero(), + _ => italics, + }; + let bottom_delta = -italics; let ascent = shift_up + sup.ascent(); let descent = shift_down + sub.descent(); let height = ascent + descent; - let width = base.width() + sup.width().max(sub.width() - delta) + space_after; + let width = base.width() + + (sup.width() + top_delta).max(sub.width() + bottom_delta) + + space_after; let base_pos = Point::with_y(ascent - base.ascent()); - let sup_pos = Point::with_x(base.width()); - let sub_pos = Point::new(base.width() - delta, height - sub.height()); + let sup_pos = Point::with_x(base.width() + top_delta); + let sub_pos = Point::new(base.width() + bottom_delta, height - sub.height()); let class = base.class().unwrap_or(MathClass::Normal); let mut frame = Frame::new(Size::new(width, height)); frame.set_baseline(ascent); frame.push_frame(base_pos, base.to_frame(ctx)); - frame.push_frame(sub_pos, sub); frame.push_frame(sup_pos, sup); + frame.push_frame(sub_pos, sub); ctx.push(FrameFragment::new(frame).with_class(class)); Ok(()) @@ -215,30 +222,31 @@ fn scripts( fn limits( ctx: &mut MathContext, base: MathFragment, - sub: Frame, - sup: Frame, + top: Frame, + bottom: Frame, ) -> SourceResult<()> { let upper_gap_min = scaled!(ctx, upper_limit_gap_min); let upper_rise_min = scaled!(ctx, upper_limit_baseline_rise_min); let lower_gap_min = scaled!(ctx, lower_limit_gap_min); let lower_drop_min = scaled!(ctx, lower_limit_baseline_drop_min); - let sup_gap = upper_gap_min.max(upper_rise_min - sup.descent()); - let sub_gap = lower_gap_min.max(lower_drop_min - sub.ascent()); + let top_gap = upper_gap_min.max(upper_rise_min - top.descent()); + let bottom_gap = lower_gap_min.max(lower_drop_min - bottom.ascent()); let delta = base.italics_correction() / 2.0; - let width = base.width().max(sup.width()).max(sub.width()); - let height = sup.height() + sup_gap + base.height() + sub_gap + sub.height(); - let base_pos = Point::new((width - base.width()) / 2.0, sup.height() + sup_gap); - let sup_pos = Point::with_x((width - sup.width()) / 2.0 + delta); - let sub_pos = Point::new((width - sub.width()) / 2.0 - delta, height - sub.height()); + let width = base.width().max(top.width()).max(bottom.width()); + let height = top.height() + top_gap + base.height() + bottom_gap + bottom.height(); + let base_pos = Point::new((width - base.width()) / 2.0, top.height() + top_gap); + let sup_pos = Point::with_x((width - top.width()) / 2.0 + delta); + let sub_pos = + Point::new((width - bottom.width()) / 2.0 - delta, height - bottom.height()); let class = base.class().unwrap_or(MathClass::Normal); let mut frame = Frame::new(Size::new(width, height)); frame.set_baseline(base_pos.y + base.ascent()); frame.push_frame(base_pos, base.to_frame(ctx)); - frame.push_frame(sub_pos, sub); - frame.push_frame(sup_pos, sup); + frame.push_frame(sub_pos, bottom); + frame.push_frame(sup_pos, top); ctx.push(FrameFragment::new(frame).with_class(class)); Ok(()) diff --git a/library/src/math/ctx.rs b/library/src/math/ctx.rs index 551d6cd7..547d3cc8 100644 --- a/library/src/math/ctx.rs +++ b/library/src/math/ctx.rs @@ -1,4 +1,5 @@ use ttf_parser::math::MathValue; +use unicode_segmentation::UnicodeSegmentation; use super::*; @@ -129,6 +130,45 @@ 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<()> { + let mut chars = text.chars(); + if let Some(glyph) = chars + .next() + .filter(|_| chars.next().is_none()) + .and_then(|c| GlyphFragment::try_new(self, c)) + { + // A single letter that is available in the math font. + if self.style.size == MathSize::Display + && glyph.class == Some(MathClass::Large) + { + let height = scaled!(self, display_operator_min_height); + self.push(glyph.stretch_vertical(self, height, Abs::zero())); + } else { + 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. + let mut vec = vec![]; + for c in text.chars() { + vec.push(GlyphFragment::new(self, c).into()); + } + let frame = MathRow(vec).to_frame(self); + self.push(frame); + } 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()))?; + self.push( + FrameFragment::new(frame) + .with_class(MathClass::Alphabetic) + .with_spaced(spaced), + ); + } + + Ok(()) + } + pub fn size(&self) -> Abs { self.scaled_size } diff --git a/library/src/math/lr.rs b/library/src/math/lr.rs index d5951050..a33810df 100644 --- a/library/src/math/lr.rs +++ b/library/src/math/lr.rs @@ -12,18 +12,29 @@ pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1); /// ## Example /// ``` /// $ lr(]a, b/2]) $ +/// $ lr(]sum_(x=1)^n] x, #size: 50%) $ /// ``` /// /// ## Parameters /// - body: Content (positional, variadic) /// The delimited content, including the delimiters. /// +/// - size: Rel<Length> (named) +/// The size of the brackets, relative to the height of the wrapped content. +/// +/// Defaults to `{100%}`. +/// /// ## Category /// math #[func] #[capable(LayoutMath)] #[derive(Debug, Hash)] -pub struct LrNode(pub Content); +pub struct LrNode { + /// The delimited content, including the delimiters. + pub body: Content, + /// The size of the brackets. + pub size: Option<Rel<Length>>, +} #[node] impl LrNode { @@ -31,17 +42,18 @@ impl LrNode { let mut body = Content::empty(); for (i, arg) in args.all::<Content>()?.into_iter().enumerate() { if i > 0 { - body += AtomNode(','.into()).pack(); + body += TextNode::packed(','); } body += arg; } - Ok(Self(body).pack()) + let size = args.named("size")?; + Ok(Self { body, size }.pack()) } } impl LayoutMath for LrNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { - let mut row = ctx.layout_row(&self.0)?; + let mut row = ctx.layout_row(&self.body)?; let axis = scaled!(ctx, axis_height); let max_extent = row @@ -51,22 +63,19 @@ impl LayoutMath for LrNode { .max() .unwrap_or_default(); - let height = 2.0 * max_extent; - if let [first, .., last] = row.0.as_mut_slice() { - for fragment in [first, last] { - if !matches!( - fragment.class(), - Some(MathClass::Opening | MathClass::Closing | MathClass::Fence) - ) { - continue; - } - - let MathFragment::Glyph(glyph) = *fragment else { continue }; - let short_fall = DELIM_SHORT_FALL.scaled(ctx); - *fragment = MathFragment::Variant( - glyph.stretch_vertical(ctx, height, short_fall), - ); + let height = self + .size + .unwrap_or(Rel::one()) + .resolve(ctx.outer.chain(&ctx.map)) + .relative_to(2.0 * max_extent); + + match row.0.as_mut_slice() { + [one] => scale(ctx, one, height, None), + [first, .., last] => { + scale(ctx, first, height, Some(MathClass::Opening)); + scale(ctx, last, height, Some(MathClass::Closing)); } + _ => {} } ctx.extend(row); @@ -75,6 +84,28 @@ impl LayoutMath for LrNode { } } +/// Scale a math fragment to a height. +fn scale( + ctx: &mut MathContext, + fragment: &mut MathFragment, + height: Abs, + apply: Option<MathClass>, +) { + if matches!( + fragment.class(), + Some(MathClass::Opening | MathClass::Closing | MathClass::Fence) + ) { + let MathFragment::Glyph(glyph) = *fragment else { return }; + let short_fall = DELIM_SHORT_FALL.scaled(ctx); + *fragment = + MathFragment::Variant(glyph.stretch_vertical(ctx, height, short_fall)); + + if let Some(class) = apply { + fragment.set_class(class); + } + } +} + /// # Floor /// Floor an expression. /// @@ -153,11 +184,14 @@ pub fn norm(args: &mut Args) -> SourceResult<Value> { fn delimited(args: &mut Args, left: char, right: char) -> SourceResult<Value> { Ok(Value::Content( - LrNode(Content::sequence(vec![ - AtomNode(left.into()).pack(), - args.expect::<Content>("body")?, - AtomNode(right.into()).pack(), - ])) + LrNode { + body: Content::sequence(vec![ + TextNode::packed(left), + args.expect::<Content>("body")?, + TextNode::packed(right), + ]), + size: None, + } .pack(), )) } diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 636affed..4c002cea 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -4,7 +4,6 @@ mod ctx; mod accent; mod align; -mod atom; mod attach; mod braced; mod frac; @@ -21,7 +20,6 @@ mod symbols; pub use self::accent::*; pub use self::align::*; -pub use self::atom::*; pub use self::attach::*; pub use self::braced::*; pub use self::frac::*; @@ -263,6 +261,11 @@ impl LayoutMath for Content { return Ok(()); } + if let Some(node) = self.to::<TextNode>() { + ctx.layout_text(&node.0)?; + return Ok(()); + } + if let Some(node) = self.to::<SequenceNode>() { for child in &node.0 { child.layout_math(ctx)?; diff --git a/library/src/math/root.rs b/library/src/math/root.rs index bae3bbb6..79bcfe38 100644 --- a/library/src/math/root.rs +++ b/library/src/math/root.rs @@ -155,7 +155,7 @@ fn layout( /// Select a precomposed radical, if the font has it. fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> { - let node = index?.to::<AtomNode>()?; + let node = index?.to::<TextNode>()?; let c = match node.0.as_str() { "3" => '∛', "4" => '∜', diff --git a/library/src/prelude.rs b/library/src/prelude.rs index 36d49dbf..b8f6025a 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -18,7 +18,7 @@ pub use typst::model::{ array, capability, capable, castable, dict, format_str, func, node, Args, Array, AutoValue, Cast, CastInfo, Content, Dict, Finalize, Fold, Func, Introspector, Label, Node, NodeId, NoneValue, Prepare, Resolve, Selector, Show, StabilityProvider, Str, - StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm, Vt, + StyleChain, StyleMap, StyleVec, Symbol, Unlabellable, Value, Vm, Vt, }; #[doc(no_inline)] pub use typst::syntax::{Span, Spanned}; |
