summaryrefslogtreecommitdiff
path: root/library/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-01-28 15:35:56 +0100
committerLaurenz <laurmaedje@gmail.com>2023-01-28 15:36:32 +0100
commit4809e685a231a3ade2c78b75685ee859196c38c1 (patch)
treee3141236cca536c31c6ef4a6df6d218c16ba5a94 /library/src
parent28c554ec2185a15e22f0408ce485ed4afe035e03 (diff)
More capable math calls
Diffstat (limited to 'library/src')
-rw-r--r--library/src/lib.rs7
-rw-r--r--library/src/math/accent.rs16
-rw-r--r--library/src/math/atom.rs58
-rw-r--r--library/src/math/attach.rs54
-rw-r--r--library/src/math/ctx.rs40
-rw-r--r--library/src/math/lr.rs82
-rw-r--r--library/src/math/mod.rs7
-rw-r--r--library/src/math/root.rs2
-rw-r--r--library/src/prelude.rs2
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};