Spans in math

This commit is contained in:
Laurenz 2023-03-12 12:53:50 +01:00
parent 2720a44b96
commit ad05ba5925
13 changed files with 118 additions and 51 deletions

View File

@ -66,7 +66,7 @@ impl LayoutMath for AccentNode {
// Forcing the accent to be at least as large as the base makes it too // Forcing the accent to be at least as large as the base makes it too
// wide in many case. // wide in many case.
let Accent(c) = self.accent(); let Accent(c) = self.accent();
let glyph = GlyphFragment::new(ctx, c); let glyph = GlyphFragment::new(ctx, c, self.span());
let short_fall = ACCENT_SHORT_FALL.scaled(ctx); let short_fall = ACCENT_SHORT_FALL.scaled(ctx);
let variant = glyph.stretch_horizontal(ctx, base.width(), short_fall); let variant = glyph.stretch_horizontal(ctx, base.width(), short_fall);
let accent = variant.frame; let accent = variant.frame;

View File

@ -124,13 +124,15 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
.into_frame()) .into_frame())
} }
pub fn layout_text(&mut self, text: &str) -> SourceResult<()> { pub fn layout_text(&mut self, node: &TextNode) -> SourceResult<()> {
let text = node.text();
let span = node.span();
let mut chars = text.chars(); let mut chars = text.chars();
if let Some(glyph) = chars if let Some(glyph) = chars
.next() .next()
.filter(|_| chars.next().is_none()) .filter(|_| chars.next().is_none())
.map(|c| self.style.styled_char(c)) .map(|c| self.style.styled_char(c))
.and_then(|c| GlyphFragment::try_new(self, c)) .and_then(|c| GlyphFragment::try_new(self, c, span))
{ {
// A single letter that is available in the math font. // A single letter that is available in the math font.
if self.style.size == MathSize::Display if self.style.size == MathSize::Display
@ -146,7 +148,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
let mut fragments = vec![]; let mut fragments = vec![];
for c in text.chars() { for c in text.chars() {
let c = self.style.styled_char(c); let c = self.style.styled_char(c);
fragments.push(GlyphFragment::new(self, c).into()); fragments.push(GlyphFragment::new(self, c, span).into());
} }
let frame = MathRow::new(fragments).to_frame(self); let frame = MathRow::new(fragments).to_frame(self);
self.push(FrameFragment::new(self, frame)); self.push(FrameFragment::new(self, frame));
@ -158,7 +160,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
style = style.with_italic(false); style = style.with_italic(false);
} }
let text: EcoString = text.chars().map(|c| style.styled_char(c)).collect(); let text: EcoString = text.chars().map(|c| style.styled_char(c)).collect();
let frame = self.layout_content(&TextNode::packed(text))?; let frame = self.layout_content(&TextNode::packed(text).spanned(span))?;
self.push( self.push(
FrameFragment::new(self, frame) FrameFragment::new(self, frame)
.with_class(MathClass::Alphabetic) .with_class(MathClass::Alphabetic)

View File

@ -89,7 +89,9 @@ fn scale(
) { ) {
let glyph = match fragment { let glyph = match fragment {
MathFragment::Glyph(glyph) => glyph.clone(), MathFragment::Glyph(glyph) => glyph.clone(),
MathFragment::Variant(variant) => GlyphFragment::new(ctx, variant.c), MathFragment::Variant(variant) => {
GlyphFragment::new(ctx, variant.c, variant.span)
}
_ => return, _ => return,
}; };

View File

@ -32,7 +32,7 @@ pub struct FracNode {
impl LayoutMath for FracNode { impl LayoutMath for FracNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.num(), &self.denom(), false) layout(ctx, &self.num(), &self.denom(), false, self.span())
} }
} }
@ -58,7 +58,7 @@ pub struct BinomNode {
impl LayoutMath for BinomNode { impl LayoutMath for BinomNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.upper(), &self.lower(), true) layout(ctx, &self.upper(), &self.lower(), true, self.span())
} }
} }
@ -68,6 +68,7 @@ fn layout(
num: &Content, num: &Content,
denom: &Content, denom: &Content,
binom: bool, binom: bool,
span: Span,
) -> SourceResult<()> { ) -> SourceResult<()> {
let short_fall = DELIM_SHORT_FALL.scaled(ctx); let short_fall = DELIM_SHORT_FALL.scaled(ctx);
let axis = scaled!(ctx, axis_height); let axis = scaled!(ctx, axis_height);
@ -121,9 +122,13 @@ fn layout(
frame.push_frame(denom_pos, denom); frame.push_frame(denom_pos, denom);
if binom { if binom {
ctx.push(GlyphFragment::new(ctx, '(').stretch_vertical(ctx, height, short_fall)); ctx.push(
GlyphFragment::new(ctx, '(', span).stretch_vertical(ctx, height, short_fall),
);
ctx.push(FrameFragment::new(ctx, frame)); ctx.push(FrameFragment::new(ctx, frame));
ctx.push(GlyphFragment::new(ctx, ')').stretch_vertical(ctx, height, short_fall)); ctx.push(
GlyphFragment::new(ctx, ')', span).stretch_vertical(ctx, height, short_fall),
);
} else { } else {
frame.push( frame.push(
line_pos, line_pos,

View File

@ -147,21 +147,22 @@ pub struct GlyphFragment {
pub style: MathStyle, pub style: MathStyle,
pub font_size: Abs, pub font_size: Abs,
pub class: Option<MathClass>, pub class: Option<MathClass>,
pub span: Span,
} }
impl GlyphFragment { impl GlyphFragment {
pub fn new(ctx: &MathContext, c: char) -> Self { pub fn new(ctx: &MathContext, c: char, span: Span) -> Self {
let id = ctx.ttf.glyph_index(c).unwrap_or_default(); let id = ctx.ttf.glyph_index(c).unwrap_or_default();
Self::with_id(ctx, c, id) Self::with_id(ctx, c, id, span)
} }
pub fn try_new(ctx: &MathContext, c: char) -> Option<Self> { pub fn try_new(ctx: &MathContext, c: char, span: Span) -> Option<Self> {
let c = ctx.style.styled_char(c); let c = ctx.style.styled_char(c);
let id = ctx.ttf.glyph_index(c)?; let id = ctx.ttf.glyph_index(c)?;
Some(Self::with_id(ctx, c, id)) Some(Self::with_id(ctx, c, id, span))
} }
pub fn with_id(ctx: &MathContext, c: char, id: GlyphId) -> Self { pub fn with_id(ctx: &MathContext, c: char, id: GlyphId, span: Span) -> Self {
let advance = ctx.ttf.glyph_hor_advance(id).unwrap_or_default(); let advance = ctx.ttf.glyph_hor_advance(id).unwrap_or_default();
let italics = italics_correction(ctx, id).unwrap_or_default(); let italics = italics_correction(ctx, id).unwrap_or_default();
let bbox = ctx.ttf.glyph_bounding_box(id).unwrap_or(Rect { let bbox = ctx.ttf.glyph_bounding_box(id).unwrap_or(Rect {
@ -192,6 +193,7 @@ impl GlyphFragment {
':' => Some(MathClass::Relation), ':' => Some(MathClass::Relation),
_ => unicode_math_class::class(c), _ => unicode_math_class::class(c),
}, },
span,
} }
} }
@ -208,6 +210,7 @@ impl GlyphFragment {
font_size: self.font_size, font_size: self.font_size,
italics_correction: self.italics_correction, italics_correction: self.italics_correction,
class: self.class, class: self.class,
span: self.span,
} }
} }
@ -222,7 +225,7 @@ impl GlyphFragment {
c: self.c, c: self.c,
x_advance: Em::from_length(self.width, self.font_size), x_advance: Em::from_length(self.width, self.font_size),
x_offset: Em::zero(), x_offset: Em::zero(),
span: Span::detached(), span: self.span,
offset: 0, offset: 0,
}], }],
}; };
@ -249,6 +252,7 @@ pub struct VariantFragment {
pub style: MathStyle, pub style: MathStyle,
pub font_size: Abs, pub font_size: Abs,
pub class: Option<MathClass>, pub class: Option<MathClass>,
pub span: Span,
} }
impl Debug for VariantFragment { impl Debug for VariantFragment {

View File

@ -36,7 +36,13 @@ impl LayoutMath for VecNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let delim = self.delim(ctx.styles()); let delim = self.delim(ctx.styles());
let frame = layout_vec_body(ctx, &self.children(), Align::Center)?; let frame = layout_vec_body(ctx, &self.children(), Align::Center)?;
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close())) layout_delimiters(
ctx,
frame,
Some(delim.open()),
Some(delim.close()),
self.span(),
)
} }
} }
@ -112,7 +118,13 @@ impl LayoutMath for MatNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let delim = self.delim(ctx.styles()); let delim = self.delim(ctx.styles());
let frame = layout_mat_body(ctx, &self.rows())?; let frame = layout_mat_body(ctx, &self.rows())?;
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close())) layout_delimiters(
ctx,
frame,
Some(delim.open()),
Some(delim.close()),
self.span(),
)
} }
} }
@ -152,7 +164,7 @@ impl LayoutMath for CasesNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let delim = self.delim(ctx.styles()); let delim = self.delim(ctx.styles());
let frame = layout_vec_body(ctx, &self.children(), Align::Left)?; let frame = layout_vec_body(ctx, &self.children(), Align::Left)?;
layout_delimiters(ctx, frame, Some(delim.open()), None) layout_delimiters(ctx, frame, Some(delim.open()), None, self.span())
} }
} }
@ -288,6 +300,7 @@ fn layout_delimiters(
mut frame: Frame, mut frame: Frame,
left: Option<char>, left: Option<char>,
right: Option<char>, right: Option<char>,
span: Span,
) -> SourceResult<()> { ) -> SourceResult<()> {
let axis = scaled!(ctx, axis_height); let axis = scaled!(ctx, axis_height);
let short_fall = DELIM_SHORT_FALL.scaled(ctx); let short_fall = DELIM_SHORT_FALL.scaled(ctx);
@ -296,14 +309,17 @@ fn layout_delimiters(
frame.set_baseline(height / 2.0 + axis); frame.set_baseline(height / 2.0 + axis);
if let Some(left) = left { if let Some(left) = left {
ctx.push(GlyphFragment::new(ctx, left).stretch_vertical(ctx, target, short_fall)); ctx.push(
GlyphFragment::new(ctx, left, span).stretch_vertical(ctx, target, short_fall),
);
} }
ctx.push(FrameFragment::new(ctx, frame)); ctx.push(FrameFragment::new(ctx, frame));
if let Some(right) = right { if let Some(right) = right {
ctx.push( ctx.push(
GlyphFragment::new(ctx, right).stretch_vertical(ctx, target, short_fall), GlyphFragment::new(ctx, right, span)
.stretch_vertical(ctx, target, short_fall),
); );
} }

View File

@ -264,7 +264,7 @@ impl LayoutMath for Content {
} }
if let Some(node) = self.to::<TextNode>() { if let Some(node) = self.to::<TextNode>() {
ctx.layout_text(&node.text())?; ctx.layout_text(node)?;
return Ok(()); return Ok(());
} }

View File

@ -18,7 +18,7 @@ pub struct SqrtNode {
impl LayoutMath for SqrtNode { impl LayoutMath for SqrtNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, None, &self.radicand()) layout(ctx, None, &self.radicand(), self.span())
} }
} }
@ -44,7 +44,7 @@ pub struct RootNode {
impl LayoutMath for RootNode { impl LayoutMath for RootNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, Some(&self.index()), &self.radicand()) layout(ctx, Some(&self.index()), &self.radicand(), self.span())
} }
} }
@ -55,6 +55,7 @@ fn layout(
ctx: &mut MathContext, ctx: &mut MathContext,
mut index: Option<&Content>, mut index: Option<&Content>,
radicand: &Content, radicand: &Content,
span: Span,
) -> SourceResult<()> { ) -> SourceResult<()> {
let gap = scaled!( let gap = scaled!(
ctx, ctx,
@ -80,7 +81,7 @@ fn layout(
frame frame
}) })
.unwrap_or_else(|| { .unwrap_or_else(|| {
let glyph = GlyphFragment::new(ctx, '√'); let glyph = GlyphFragment::new(ctx, '√', span);
glyph.stretch_vertical(ctx, target, Abs::zero()).frame glyph.stretch_vertical(ctx, target, Abs::zero()).frame
}); });
@ -145,7 +146,7 @@ fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Optio
}; };
ctx.ttf.glyph_index(c)?; ctx.ttf.glyph_index(c)?;
let glyph = GlyphFragment::new(ctx, c); let glyph = GlyphFragment::new(ctx, c, node.span());
let variant = glyph.stretch_vertical(ctx, target, Abs::zero()).frame; let variant = glyph.stretch_vertical(ctx, target, Abs::zero()).frame;
if variant.height() < target { if variant.height() < target {
return None; return None;

View File

@ -73,7 +73,7 @@ fn stretch_glyph(
// This is either good or the best we've got. // This is either good or the best we've got.
if short_target <= best_advance || construction.assembly.is_none() { if short_target <= best_advance || construction.assembly.is_none() {
return GlyphFragment::with_id(ctx, base.c, best_id).to_variant(); return GlyphFragment::with_id(ctx, base.c, best_id, base.span).to_variant();
} }
// Assemble from parts. // Assemble from parts.
@ -142,7 +142,7 @@ fn assemble(
advance += ratio * (max_overlap - min_overlap); advance += ratio * (max_overlap - min_overlap);
} }
let fragment = GlyphFragment::with_id(ctx, base.c, part.glyph_id); let fragment = GlyphFragment::with_id(ctx, base.c, part.glyph_id, base.span);
selected.push((fragment, advance)); selected.push((fragment, advance));
} }
@ -181,6 +181,7 @@ fn assemble(
font_size: base.font_size, font_size: base.font_size,
italics_correction: Abs::zero(), italics_correction: Abs::zero(),
class: base.class, class: base.class,
span: base.span,
} }
} }

View File

@ -22,7 +22,7 @@ pub struct UnderlineNode {
impl LayoutMath for UnderlineNode { impl LayoutMath for UnderlineNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.body(), &None, '\u{305}', LINE_GAP, false) layout(ctx, &self.body(), &None, '\u{305}', LINE_GAP, false, self.span())
} }
} }
@ -44,7 +44,7 @@ pub struct OverlineNode {
impl LayoutMath for OverlineNode { impl LayoutMath for OverlineNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.body(), &None, '\u{332}', LINE_GAP, true) layout(ctx, &self.body(), &None, '\u{332}', LINE_GAP, true, self.span())
} }
} }
@ -70,7 +70,15 @@ pub struct UnderbraceNode {
impl LayoutMath for UnderbraceNode { impl LayoutMath for UnderbraceNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.body(), &self.annotation(ctx.styles()), '⏟', BRACE_GAP, false) layout(
ctx,
&self.body(),
&self.annotation(ctx.styles()),
'⏟',
BRACE_GAP,
false,
self.span(),
)
} }
} }
@ -96,7 +104,15 @@ pub struct OverbraceNode {
impl LayoutMath for OverbraceNode { impl LayoutMath for OverbraceNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.body(), &self.annotation(ctx.styles()), '⏞', BRACE_GAP, true) layout(
ctx,
&self.body(),
&self.annotation(ctx.styles()),
'⏞',
BRACE_GAP,
true,
self.span(),
)
} }
} }
@ -122,7 +138,15 @@ pub struct UnderbracketNode {
impl LayoutMath for UnderbracketNode { impl LayoutMath for UnderbracketNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.body(), &self.annotation(ctx.styles()), '⎵', BRACKET_GAP, false) layout(
ctx,
&self.body(),
&self.annotation(ctx.styles()),
'⎵',
BRACKET_GAP,
false,
self.span(),
)
} }
} }
@ -148,7 +172,15 @@ pub struct OverbracketNode {
impl LayoutMath for OverbracketNode { impl LayoutMath for OverbracketNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.body(), &self.annotation(ctx.styles()), '⎴', BRACKET_GAP, true) layout(
ctx,
&self.body(),
&self.annotation(ctx.styles()),
'⎴',
BRACKET_GAP,
true,
self.span(),
)
} }
} }
@ -160,10 +192,11 @@ fn layout(
c: char, c: char,
gap: Em, gap: Em,
reverse: bool, reverse: bool,
span: Span,
) -> SourceResult<()> { ) -> SourceResult<()> {
let gap = gap.scaled(ctx); let gap = gap.scaled(ctx);
let body = ctx.layout_row(body)?; let body = ctx.layout_row(body)?;
let glyph = GlyphFragment::new(ctx, c); let glyph = GlyphFragment::new(ctx, c, span);
let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero()); let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
let mut rows = vec![body, stretched.into()]; let mut rows = vec![body, stretched.into()];

View File

@ -107,6 +107,7 @@ impl Synthesize for HeadingNode {
let node = self let node = self
.clone() .clone()
.with_level(self.level(styles))
.with_outlined(self.outlined(styles)) .with_outlined(self.outlined(styles))
.with_numbers(numbering.is_some().then(|| counter.take())) .with_numbers(numbering.is_some().then(|| counter.take()))
.with_numbering(numbering) .with_numbering(numbering)

View File

@ -368,7 +368,7 @@ fn eval_markup(
*node = mem::take(node).labelled(label); *node = mem::take(node).labelled(label);
} }
} }
value => seq.push(value.display()), value => seq.push(value.display().spanned(expr.span())),
}, },
} }
@ -458,6 +458,12 @@ impl Eval for ast::Expr {
} }
} }
impl ast::Expr {
fn eval_display(&self, vm: &mut Vm) -> SourceResult<Content> {
Ok(self.eval(vm)?.display().spanned(self.span()))
}
}
impl Eval for ast::Text { impl Eval for ast::Text {
type Output = Content; type Output = Content;
@ -619,10 +625,9 @@ impl Eval for ast::Math {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok(Content::sequence( Ok(Content::sequence(
self.exprs() self.exprs()
.map(|expr| Ok(expr.eval(vm)?.display())) .map(|expr| expr.eval_display(vm))
.collect::<SourceResult<_>>()?, .collect::<SourceResult<_>>()?,
) ))
.spanned(self.span()))
} }
} }
@ -646,9 +651,9 @@ impl Eval for ast::MathDelimited {
type Output = Content; type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let open = self.open().eval(vm)?.display(); let open = self.open().eval_display(vm)?;
let body = self.body().eval(vm)?; let body = self.body().eval(vm)?;
let close = self.close().eval(vm)?.display(); let close = self.close().eval_display(vm)?;
Ok((vm.items.math_delimited)(open, body, close)) Ok((vm.items.math_delimited)(open, body, close))
} }
} }
@ -657,12 +662,9 @@ impl Eval for ast::MathAttach {
type Output = Content; type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let base = self.base().eval(vm)?.display(); let base = self.base().eval_display(vm)?;
let bottom = self let bottom = self.bottom().map(|expr| expr.eval_display(vm)).transpose()?;
.bottom() let top = self.top().map(|expr| expr.eval_display(vm)).transpose()?;
.map(|expr| expr.eval(vm).map(Value::display))
.transpose()?;
let top = self.top().map(|expr| expr.eval(vm).map(Value::display)).transpose()?;
Ok((vm.items.math_attach)(base, bottom, top)) Ok((vm.items.math_attach)(base, bottom, top))
} }
} }
@ -671,8 +673,8 @@ impl Eval for ast::MathFrac {
type Output = Content; type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let num = self.num().eval(vm)?.display(); let num = self.num().eval_display(vm)?;
let denom = self.denom().eval(vm)?.display(); let denom = self.denom().eval_display(vm)?;
Ok((vm.items.math_frac)(num, denom)) Ok((vm.items.math_frac)(num, denom))
} }
} }
@ -1038,7 +1040,7 @@ impl Eval for ast::FuncCall {
body += arg; body += arg;
} }
return Ok(Value::Content( return Ok(Value::Content(
callee.display() callee.display().spanned(callee_span)
+ (vm.items.math_delimited)( + (vm.items.math_delimited)(
(vm.items.text)('('.into()), (vm.items.text)('('.into()),
body, body,

View File

@ -15,7 +15,7 @@ use typst::diag::{bail, FileError, FileResult};
use typst::doc::{Document, Element, Frame, Meta}; use typst::doc::{Document, Element, Frame, Meta};
use typst::eval::{func, Library, Value}; use typst::eval::{func, Library, Value};
use typst::font::{Font, FontBook}; use typst::font::{Font, FontBook};
use typst::geom::{Abs, RgbaColor, Sides, Smart}; use typst::geom::{Abs, Color, RgbaColor, Sides, Smart};
use typst::syntax::{Source, SourceId, Span, SyntaxNode}; use typst::syntax::{Source, SourceId, Span, SyntaxNode};
use typst::util::{Buffer, PathExt}; use typst::util::{Buffer, PathExt};
use typst::World; use typst::World;