summaryrefslogtreecommitdiff
path: root/library/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-01-28 12:01:05 +0100
committerLaurenz <laurmaedje@gmail.com>2023-01-28 12:14:03 +0100
commit28c554ec2185a15e22f0408ce485ed4afe035e03 (patch)
tree622d2d281133c4e6b92633e44bfc1e1301250fb4 /library/src
parent23238d4d44881a5b466ab23a32e2a7447f460127 (diff)
Rework math attachments and accents
Diffstat (limited to 'library/src')
-rw-r--r--library/src/lib.rs5
-rw-r--r--library/src/math/accent.rs95
-rw-r--r--library/src/math/attach.rs (renamed from library/src/math/script.rs)127
-rw-r--r--library/src/math/mod.rs41
-rw-r--r--library/src/math/op.rs8
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())
}