diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-01-28 12:01:05 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-01-28 12:14:03 +0100 |
| commit | 28c554ec2185a15e22f0408ce485ed4afe035e03 (patch) | |
| tree | 622d2d281133c4e6b92633e44bfc1e1301250fb4 /library/src | |
| parent | 23238d4d44881a5b466ab23a32e2a7447f460127 (diff) | |
Rework math attachments and accents
Diffstat (limited to 'library/src')
| -rw-r--r-- | library/src/lib.rs | 5 | ||||
| -rw-r--r-- | library/src/math/accent.rs | 95 | ||||
| -rw-r--r-- | library/src/math/attach.rs (renamed from library/src/math/script.rs) | 127 | ||||
| -rw-r--r-- | library/src/math/mod.rs | 41 | ||||
| -rw-r--r-- | library/src/math/op.rs | 8 |
5 files changed, 156 insertions, 120 deletions
diff --git a/library/src/lib.rs b/library/src/lib.rs index 76245b9e..17f3de6d 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -194,7 +194,10 @@ fn items() -> LangItems { 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_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(), + math_attach: |base, sub, sup| { + math::AttachNode { base, top: sub, bottom: sup }.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 6829554c..90e64b96 100644 --- a/library/src/math/accent.rs +++ b/library/src/math/accent.rs @@ -1,15 +1,17 @@ +use typst::model::combining_accent; + use super::*; /// How much the accent can be shorter than the base. const ACCENT_SHORT_FALL: Em = Em::new(0.5); /// # Accent -/// An accented node. +/// Attach an accent to a base. /// /// ## Example /// ``` -/// $accent(a, ->) != accent(a, ~)$ \ -/// $accent(a, `) = accent(a, grave)$ +/// $arrow(a) = accent(a, arrow)$ \ +/// $grave(a) = accent(a, `)$ /// ``` /// /// ## Parameters @@ -19,30 +21,26 @@ const ACCENT_SHORT_FALL: Em = Em::new(0.5); /// /// ### Example /// ``` -/// $accent(A B C, ->)$ +/// $arrow(A B C)$ /// ``` /// -/// - accent: Content (positional, required) +/// - accent: char (positional, required) /// The accent to apply to the base. /// /// Supported accents include: -/// - Plus: `` + `` -/// - Overline: `` - ``, `‾` -/// - Dot: `.` -/// - Circumflex: `^` -/// - Acute: `´` -/// - Low Line: `_` -/// - Grave: `` ` `` -/// - Tilde: `~` -/// - Diaeresis: `¨` -/// - Macron: `¯` -/// - Acute: `´` -/// - Cedilla: `¸` -/// - Caron: `ˇ` -/// - Breve: `˘` -/// - Double acute: `˝` -/// - Left arrow: `<-` -/// - Right arrow: `->` +/// - Grave: `grave`, `` ` `` +/// - Acute: `acute`, `´` +/// - Circumflex: `circum`, `^` +/// - Tilde: `tilde`, `~` +/// - Macron: `macron`, `¯` +/// - Breve: `breve`, `˘` +/// - Dot: `dot`, `.` +/// - Diaeresis: `diaer` `¨` +/// - Circle: `circle`, `∘` +/// - Double acute: `acute.double`, `˝` +/// - Caron: `caron`, `ˇ` +/// - Right arrow: `arrow`, `->` +/// - Left arrow: `arrow.l`, `<-` /// /// ## Category /// math @@ -53,7 +51,7 @@ pub struct AccentNode { /// The accent base. pub base: Content, /// The accent. - pub accent: Content, + pub accent: char, } #[node] @@ -78,16 +76,9 @@ impl LayoutMath for AccentNode { _ => (base.width() + base.italics_correction()) / 2.0, }; - let Some(c) = extract(&self.accent) else { - ctx.push(base); - if let Some(span) = self.accent.span() { - bail!(span, "not an accent"); - } - return Ok(()); - }; - // Forcing the accent to be at least as large as the base makes it too // wide in many case. + let c = combining_accent(self.accent).unwrap_or(self.accent); let glyph = GlyphFragment::new(ctx, c); let short_fall = ACCENT_SHORT_FALL.scaled(ctx); let variant = glyph.stretch_horizontal(ctx, base.width(), short_fall); @@ -130,45 +121,3 @@ fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs { (advance.scaled(ctx) + italics_correction) / 2.0 }) } - -/// Extract a single character from content. -fn extract(accent: &Content) -> Option<char> { - let atom = accent.to::<AtomNode>()?; - let mut chars = atom.0.chars(); - let c = chars.next().filter(|_| chars.next().is_none())?; - Some(combining(c)) -} - -/// Convert from a non-combining accent to a combining one. -/// -/// https://www.w3.org/TR/mathml-core/#combining-character-equivalences -fn combining(c: char) -> char { - match c { - '\u{002b}' => '\u{031f}', - '\u{002d}' => '\u{0305}', - '\u{002e}' => '\u{0307}', - '\u{005e}' => '\u{0302}', - '\u{005f}' => '\u{0332}', - '\u{0060}' => '\u{0300}', - '\u{007e}' => '\u{0303}', - '\u{00a8}' => '\u{0308}', - '\u{00af}' => '\u{0304}', - '\u{00b4}' => '\u{0301}', - '\u{00b8}' => '\u{0327}', - '\u{02c6}' => '\u{0302}', - '\u{02c7}' => '\u{030c}', - '\u{02d8}' => '\u{0306}', - '\u{02d9}' => '\u{0307}', - '\u{02db}' => '\u{0328}', - '\u{02dc}' => '\u{0303}', - '\u{02dd}' => '\u{030b}', - '\u{203e}' => '\u{0305}', - '\u{2190}' => '\u{20d6}', - '\u{2192}' => '\u{20d7}', - '\u{2212}' => '\u{0305}', - '\u{223C}' => '\u{0303}', - '\u{22C5}' => '\u{0307}', - '\u{27f6}' => '\u{20d7}', - _ => c, - } -} diff --git a/library/src/math/script.rs b/library/src/math/attach.rs index 2c765fbf..2205e556 100644 --- a/library/src/math/script.rs +++ b/library/src/math/attach.rs @@ -1,86 +1,153 @@ use super::*; -/// # Script -/// A mathematical sub- and/or superscript. +/// # Attachment +/// A base with optional attachments. /// /// ## Syntax /// This function also has dedicated syntax: Use the underscore (`_`) to -/// indicate a subscript and the circumflex (`^`) to indicate a superscript. +/// indicate a bottom attachment and the circumflex (`^`) to indicate a top +/// attachment. /// /// ## Example /// ``` -/// $ a_i = 2^(1+i) $ +/// $ sum_(i=0)^n a_i = 2^(1+i) $ /// ``` /// /// ## Parameters /// - base: Content (positional, required) -/// The base to which the applies the sub- and/or superscript. +/// The base to which things are attached. /// -/// - sub: Content (named) -/// The subscript. +/// - top: Content (named) +/// The top attachment. /// -/// - sup: Content (named) -/// The superscript. +/// - bottom: Content (named) +/// The bottom attachment. /// /// ## Category /// math #[func] #[capable(LayoutMath)] #[derive(Debug, Hash)] -pub struct ScriptNode { +pub struct AttachNode { /// The base. pub base: Content, - /// The subscript. - pub sub: Option<Content>, - /// The superscript. - pub sup: Option<Content>, + /// The top attachment. + pub top: Option<Content>, + /// The bottom attachment. + pub bottom: Option<Content>, } #[node] -impl ScriptNode { +impl AttachNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { let base = args.expect("base")?; - let sub = args.named("sub")?; - let sup = args.named("sup")?; - Ok(Self { base, sub, sup }.pack()) + let top = args.named("top")?; + let bottom = args.named("bottom")?; + Ok(Self { base, top, bottom }.pack()) } } -impl LayoutMath for ScriptNode { +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()); - if let Some(node) = &self.sub { + if let Some(node) = &self.top { ctx.style(ctx.style.for_subscript()); sub = ctx.layout_frame(node)?; ctx.unstyle(); } let mut sup = Frame::new(Size::zero()); - if let Some(node) = &self.sup { + if let Some(node) = &self.bottom { ctx.style(ctx.style.for_superscript()); sup = ctx.layout_frame(node)?; ctx.unstyle(); } - let render_limits = ctx.style.size == MathSize::Display - && base.class() == Some(MathClass::Large) - && match &base { - MathFragment::Variant(variant) => LIMITS.contains(&variant.c), - MathFragment::Frame(fragment) => fragment.limits, - _ => false, - }; + let render_limits = self.base.is::<LimitsNode>() + || (!self.base.is::<ScriptsNode>() + && ctx.style.size == MathSize::Display + && base.class() == Some(MathClass::Large) + && match &base { + MathFragment::Variant(variant) => LIMITS.contains(&variant.c), + MathFragment::Frame(fragment) => fragment.limits, + _ => false, + }); if render_limits { limits(ctx, base, sub, sup) } else { - scripts(ctx, base, sub, sup, self.sub.is_some() && self.sup.is_some()) + scripts(ctx, base, sub, sup, self.top.is_some() && self.bottom.is_some()) } } } -/// Layout normal sub- and superscripts. +/// # Scripts +/// Force a base to display attachments as scripts. +/// +/// ## Example +/// ``` +/// $ scripts(sum)_1^2 != sum_1^2 $ +/// ``` +/// +/// ## Parameters +/// - base: Content (positional, required) +/// The base to attach the scripts to. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct ScriptsNode(Content); + +#[node] +impl ScriptsNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + Ok(Self(args.expect("base")?).pack()) + } +} + +impl LayoutMath for ScriptsNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + self.0.layout_math(ctx) + } +} + +/// # Limits +/// Force a base to display attachments as limits. +/// +/// ## Example +/// ``` +/// $ limits(A)_1^2 != A_1^2 $ +/// ``` +/// +/// ## Parameters +/// - base: Content (positional, required) +/// The base to attach the limits to. +/// +/// ## Category +/// math +#[func] +#[capable(LayoutMath)] +#[derive(Debug, Hash)] +pub struct LimitsNode(Content); + +#[node] +impl LimitsNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { + Ok(Self(args.expect("base")?).pack()) + } +} + +impl LayoutMath for LimitsNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + self.0.layout_math(ctx) + } +} + +/// Layout sub- and superscripts. fn scripts( ctx: &mut MathContext, base: MathFragment, diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 1d94661a..636affed 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -5,6 +5,7 @@ mod ctx; mod accent; mod align; mod atom; +mod attach; mod braced; mod frac; mod fragment; @@ -13,7 +14,6 @@ mod matrix; mod op; mod root; mod row; -mod script; mod spacing; mod stretch; mod style; @@ -22,13 +22,13 @@ 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::*; pub use self::lr::*; pub use self::matrix::*; pub use self::op::*; pub use self::root::*; -pub use self::script::*; pub use self::style::*; use ttf_parser::GlyphId; @@ -54,22 +54,33 @@ use crate::text::{families, variant, FallbackList, FontFamily, SpaceNode}; pub fn module(sym: &Module) -> Module { let mut math = Scope::deduplicating(); math.def_func::<FormulaNode>("formula"); + + // Grouping. math.def_func::<LrNode>("lr"); - math.def_func::<OpNode>("op"); - math.def_func::<FloorFunc>("floor"); - math.def_func::<CeilFunc>("ceil"); math.def_func::<AbsFunc>("abs"); math.def_func::<NormFunc>("norm"); + math.def_func::<FloorFunc>("floor"); + math.def_func::<CeilFunc>("ceil"); + + // Attachments and accents. + math.def_func::<AttachNode>("attach"); + math.def_func::<ScriptsNode>("scripts"); + math.def_func::<LimitsNode>("limits"); math.def_func::<AccentNode>("accent"); + math.def_func::<UnderbraceNode>("underbrace"); + math.def_func::<OverbraceNode>("overbrace"); + + // Fractions and matrix-likes. math.def_func::<FracNode>("frac"); math.def_func::<BinomNode>("binom"); - math.def_func::<ScriptNode>("script"); - math.def_func::<SqrtNode>("sqrt"); - math.def_func::<RootNode>("root"); math.def_func::<VecNode>("vec"); math.def_func::<CasesNode>("cases"); - math.def_func::<UnderbraceNode>("underbrace"); - math.def_func::<OverbraceNode>("overbrace"); + + // Roots. + math.def_func::<SqrtNode>("sqrt"); + math.def_func::<RootNode>("root"); + + // Styles. math.def_func::<BoldNode>("bold"); math.def_func::<ItalicNode>("italic"); math.def_func::<SerifNode>("serif"); @@ -78,10 +89,16 @@ pub fn module(sym: &Module) -> Module { math.def_func::<FrakNode>("frak"); math.def_func::<MonoNode>("mono"); math.def_func::<BbNode>("bb"); - spacing::define(&mut math); - symbols::define(&mut math); + + // Text operators. + math.def_func::<OpNode>("op"); op::define(&mut math); + + // Symbols and spacing. + symbols::define(&mut math); + spacing::define(&mut math); math.copy_from(sym.scope()); + Module::new("math").with_scope(math) } diff --git a/library/src/math/op.rs b/library/src/math/op.rs index 22daee65..766d6381 100644 --- a/library/src/math/op.rs +++ b/library/src/math/op.rs @@ -9,9 +9,9 @@ use super::*; /// - text: EcoString (positional, required) /// The operator's text. /// - limits: bool (named) -/// Whether the operator should display sub- and superscripts as limits. +/// Whether the operator should force attachments to display as limits. /// -/// Defaults to `true`. +/// Defaults to `false`. /// /// ## Category /// math @@ -21,7 +21,7 @@ use super::*; pub struct OpNode { /// The operator's text. pub text: EcoString, - /// Whether the operator should display sub- and superscripts as limits. + /// Whether the operator should force attachments to display as limits. pub limits: bool, } @@ -30,7 +30,7 @@ impl OpNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { Ok(Self { text: args.expect("text")?, - limits: args.named("limits")?.unwrap_or(true), + limits: args.named("limits")?.unwrap_or(false), } .pack()) } |
