More robust automatic math spacing

This commit is contained in:
Laurenz 2023-02-02 16:47:10 +01:00
parent e6400861ab
commit e9ff2d6463
21 changed files with 220 additions and 154 deletions

View File

@ -118,7 +118,7 @@ impl LayoutMath for AccentNode {
frame.set_baseline(baseline); frame.set_baseline(baseline);
frame.push_frame(accent_pos, accent); frame.push_frame(accent_pos, accent);
frame.push_frame(base_pos, base.to_frame(ctx)); frame.push_frame(base_pos, base.to_frame(ctx));
ctx.push(frame); ctx.push(FrameFragment::new(ctx, frame));
Ok(()) Ok(())
} }

View File

@ -29,8 +29,7 @@ pub(super) fn alignments(rows: &[MathRow]) -> Vec<Abs> {
let count = rows let count = rows
.iter() .iter()
.map(|row| { .map(|row| {
row.0 row.iter()
.iter()
.filter(|fragment| matches!(fragment, MathFragment::Align)) .filter(|fragment| matches!(fragment, MathFragment::Align))
.count() .count()
}) })
@ -42,7 +41,7 @@ pub(super) fn alignments(rows: &[MathRow]) -> Vec<Abs> {
for row in rows { for row in rows {
let mut x = Abs::zero(); let mut x = Abs::zero();
let mut i = 0; let mut i = 0;
for fragment in &row.0 { for fragment in row.iter() {
if matches!(fragment, MathFragment::Align) { if matches!(fragment, MathFragment::Align) {
if i < current { if i < current {
x = points[i]; x = points[i];

View File

@ -231,7 +231,7 @@ fn scripts(
frame.push_frame(sub_pos, sub); frame.push_frame(sub_pos, sub);
} }
ctx.push(FrameFragment::new(frame).with_class(class)); ctx.push(FrameFragment::new(ctx, frame).with_class(class));
Ok(()) Ok(())
} }
@ -284,7 +284,7 @@ fn limits(
frame.push_frame(bottom_pos, bottom); frame.push_frame(bottom_pos, bottom);
} }
ctx.push(FrameFragment::new(frame).with_class(class)); ctx.push(FrameFragment::new(ctx, frame).with_class(class));
Ok(()) Ok(())
} }

View File

@ -31,7 +31,7 @@ pub struct MathContext<'a, 'b, 'v> {
pub table: ttf_parser::math::Table<'a>, pub table: ttf_parser::math::Table<'a>,
pub constants: ttf_parser::math::Constants<'a>, pub constants: ttf_parser::math::Constants<'a>,
pub space_width: Em, pub space_width: Em,
pub row: MathRow, pub fragments: Vec<MathFragment>,
pub map: StyleMap, pub map: StyleMap,
pub style: MathStyle, pub style: MathStyle,
pub size: Abs, pub size: Abs,
@ -69,7 +69,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
table, table,
constants, constants,
space_width, space_width,
row: MathRow::new(), fragments: vec![],
map: StyleMap::new(), map: StyleMap::new(),
style: MathStyle { style: MathStyle {
variant: MathVariant::Serif, variant: MathVariant::Serif,
@ -88,45 +88,45 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
} }
pub fn push(&mut self, fragment: impl Into<MathFragment>) { pub fn push(&mut self, fragment: impl Into<MathFragment>) {
self.row.push(self.size, self.space_width, self.style, fragment); self.fragments.push(fragment.into());
} }
pub fn extend(&mut self, row: MathRow) { pub fn extend(&mut self, fragments: Vec<MathFragment>) {
let mut iter = row.0.into_iter(); self.fragments.extend(fragments);
if let Some(first) = iter.next() {
self.push(first);
}
self.row.0.extend(iter);
}
pub fn layout_non_math(&mut self, content: &Content) -> SourceResult<Frame> {
Ok(content
.layout(&mut self.vt, self.outer.chain(&self.map), self.regions)?
.into_frame())
} }
pub fn layout_fragment( pub fn layout_fragment(
&mut self, &mut self,
node: &dyn LayoutMath, node: &dyn LayoutMath,
) -> SourceResult<MathFragment> { ) -> SourceResult<MathFragment> {
let row = self.layout_row(node)?; let row = self.layout_fragments(node)?;
Ok(if row.0.len() == 1 { Ok(MathRow::new(row).to_fragment(self))
row.0.into_iter().next().unwrap() }
} else {
row.to_frame(self).into() pub fn layout_fragments(
}) &mut self,
node: &dyn LayoutMath,
) -> SourceResult<Vec<MathFragment>> {
let prev = std::mem::take(&mut self.fragments);
node.layout_math(self)?;
Ok(std::mem::replace(&mut self.fragments, prev))
} }
pub fn layout_row(&mut self, node: &dyn LayoutMath) -> SourceResult<MathRow> { pub fn layout_row(&mut self, node: &dyn LayoutMath) -> SourceResult<MathRow> {
let prev = std::mem::take(&mut self.row); let fragments = self.layout_fragments(node)?;
node.layout_math(self)?; Ok(MathRow::new(fragments))
Ok(std::mem::replace(&mut self.row, prev))
} }
pub fn layout_frame(&mut self, node: &dyn LayoutMath) -> SourceResult<Frame> { pub fn layout_frame(&mut self, node: &dyn LayoutMath) -> SourceResult<Frame> {
Ok(self.layout_fragment(node)?.to_frame(self)) Ok(self.layout_fragment(node)?.to_frame(self))
} }
pub fn layout_content(&mut self, content: &Content) -> SourceResult<Frame> {
Ok(content
.layout(&mut self.vt, self.outer.chain(&self.map), self.regions)?
.into_frame())
}
pub fn layout_text(&mut self, text: &str) -> SourceResult<()> { pub fn layout_text(&mut self, text: &str) -> SourceResult<()> {
let mut chars = text.chars(); let mut chars = text.chars();
if let Some(glyph) = chars if let Some(glyph) = chars
@ -146,13 +146,13 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
} }
} else if text.chars().all(|c| c.is_ascii_digit()) { } else if text.chars().all(|c| c.is_ascii_digit()) {
// Numbers aren't that difficult. // Numbers aren't that difficult.
let mut vec = 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);
vec.push(GlyphFragment::new(self, c).into()); fragments.push(GlyphFragment::new(self, c).into());
} }
let frame = MathRow(vec).to_frame(self); let frame = MathRow::new(fragments).to_frame(self);
self.push(frame); self.push(FrameFragment::new(self, frame));
} else { } else {
// Anything else is handled by Typst's standard text layout. // Anything else is handled by Typst's standard text layout.
let spaced = text.graphemes(true).count() > 1; let spaced = text.graphemes(true).count() > 1;
@ -161,9 +161,9 @@ 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_non_math(&TextNode::packed(text))?; let frame = self.layout_content(&TextNode::packed(text))?;
self.push( self.push(
FrameFragment::new(frame) FrameFragment::new(self, frame)
.with_class(MathClass::Alphabetic) .with_class(MathClass::Alphabetic)
.with_spaced(spaced), .with_spaced(spaced),
); );

View File

@ -60,10 +60,9 @@ impl LayoutMath for LrNode {
} }
} }
let mut row = ctx.layout_row(body)?; let mut fragments = ctx.layout_fragments(body)?;
let axis = scaled!(ctx, axis_height); let axis = scaled!(ctx, axis_height);
let max_extent = row let max_extent = fragments
.0
.iter() .iter()
.map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis)) .map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis))
.max() .max()
@ -75,7 +74,7 @@ impl LayoutMath for LrNode {
.resolve(ctx.styles()) .resolve(ctx.styles())
.relative_to(2.0 * max_extent); .relative_to(2.0 * max_extent);
match row.0.as_mut_slice() { match fragments.as_mut_slice() {
[one] => scale(ctx, one, height, None), [one] => scale(ctx, one, height, None),
[first, .., last] => { [first, .., last] => {
scale(ctx, first, height, Some(MathClass::Opening)); scale(ctx, first, height, Some(MathClass::Opening));
@ -84,7 +83,7 @@ impl LayoutMath for LrNode {
_ => {} _ => {}
} }
ctx.extend(row); ctx.extend(fragments);
Ok(()) Ok(())
} }

View File

@ -153,7 +153,7 @@ fn layout(
if binom { if binom {
ctx.push(GlyphFragment::new(ctx, '(').stretch_vertical(ctx, height, short_fall)); ctx.push(GlyphFragment::new(ctx, '(').stretch_vertical(ctx, height, short_fall));
ctx.push(frame); ctx.push(FrameFragment::new(ctx, frame));
ctx.push(GlyphFragment::new(ctx, ')').stretch_vertical(ctx, height, short_fall)); ctx.push(GlyphFragment::new(ctx, ')').stretch_vertical(ctx, height, short_fall));
} else { } else {
frame.push( frame.push(
@ -165,7 +165,7 @@ fn layout(
}), }),
), ),
); );
ctx.push(frame); ctx.push(FrameFragment::new(ctx, frame));
} }
Ok(()) Ok(())

View File

@ -6,7 +6,7 @@ pub enum MathFragment {
Variant(VariantFragment), Variant(VariantFragment),
Frame(FrameFragment), Frame(FrameFragment),
Spacing(Abs), Spacing(Abs),
Space, Space(Abs),
Linebreak, Linebreak,
Align, Align,
} }
@ -22,6 +22,7 @@ impl MathFragment {
Self::Variant(variant) => variant.frame.width(), Self::Variant(variant) => variant.frame.width(),
Self::Frame(fragment) => fragment.frame.width(), Self::Frame(fragment) => fragment.frame.width(),
Self::Spacing(amount) => *amount, Self::Spacing(amount) => *amount,
Self::Space(amount) => *amount,
_ => Abs::zero(), _ => Abs::zero(),
} }
} }
@ -62,6 +63,24 @@ impl MathFragment {
} }
} }
pub fn style(&self) -> Option<MathStyle> {
match self {
Self::Glyph(glyph) => Some(glyph.style),
Self::Variant(variant) => Some(variant.style),
Self::Frame(fragment) => Some(fragment.style),
_ => None,
}
}
pub fn font_size(&self) -> Option<Abs> {
match self {
Self::Glyph(glyph) => Some(glyph.font_size),
Self::Variant(variant) => Some(variant.font_size),
Self::Frame(fragment) => Some(fragment.font_size),
_ => None,
}
}
pub fn set_class(&mut self, class: MathClass) { pub fn set_class(&mut self, class: MathClass) {
match self { match self {
Self::Glyph(glyph) => glyph.class = Some(class), Self::Glyph(glyph) => glyph.class = Some(class),
@ -71,8 +90,11 @@ impl MathFragment {
} }
} }
pub fn participating(&self) -> bool { pub fn is_spaced(&self) -> bool {
!matches!(self, Self::Space | Self::Spacing(_) | Self::Align) match self {
MathFragment::Frame(frame) => frame.spaced,
_ => self.class() == Some(MathClass::Fence),
}
} }
pub fn italics_correction(&self) -> Abs { pub fn italics_correction(&self) -> Abs {
@ -111,23 +133,18 @@ impl From<FrameFragment> for MathFragment {
} }
} }
impl From<Frame> for MathFragment { #[derive(Clone, Copy)]
fn from(frame: Frame) -> Self {
Self::Frame(FrameFragment::new(frame))
}
}
#[derive(Debug, Clone, Copy)]
pub struct GlyphFragment { pub struct GlyphFragment {
pub id: GlyphId, pub id: GlyphId,
pub c: char, pub c: char,
pub lang: Lang, pub lang: Lang,
pub fill: Paint, pub fill: Paint,
pub font_size: Abs,
pub width: Abs, pub width: Abs,
pub ascent: Abs, pub ascent: Abs,
pub descent: Abs, pub descent: Abs,
pub italics_correction: Abs, pub italics_correction: Abs,
pub style: MathStyle,
pub font_size: Abs,
pub class: Option<MathClass>, pub class: Option<MathClass>,
} }
@ -163,6 +180,7 @@ impl GlyphFragment {
c, c,
lang: ctx.styles().get(TextNode::LANG), lang: ctx.styles().get(TextNode::LANG),
fill: ctx.styles().get(TextNode::FILL), fill: ctx.styles().get(TextNode::FILL),
style: ctx.style,
font_size: ctx.size, font_size: ctx.size,
width, width,
ascent: bbox.y_max.scaled(ctx), ascent: bbox.y_max.scaled(ctx),
@ -184,6 +202,8 @@ impl GlyphFragment {
c: self.c, c: self.c,
id: Some(self.id), id: Some(self.id),
frame: self.to_frame(ctx), frame: self.to_frame(ctx),
style: self.style,
font_size: self.font_size,
italics_correction: self.italics_correction, italics_correction: self.italics_correction,
class: self.class, class: self.class,
} }
@ -210,30 +230,48 @@ impl GlyphFragment {
} }
} }
#[derive(Debug, Clone)] impl Debug for GlyphFragment {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "GlyphFragment({:?})", self.c)
}
}
#[derive(Clone)]
pub struct VariantFragment { pub struct VariantFragment {
pub c: char, pub c: char,
pub id: Option<GlyphId>, pub id: Option<GlyphId>,
pub frame: Frame,
pub italics_correction: Abs, pub italics_correction: Abs,
pub frame: Frame,
pub style: MathStyle,
pub font_size: Abs,
pub class: Option<MathClass>, pub class: Option<MathClass>,
} }
impl Debug for VariantFragment {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "VariantFragment({:?})", self.c)
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FrameFragment { pub struct FrameFragment {
pub frame: Frame, pub frame: Frame,
pub class: MathClass,
pub limits: bool, pub limits: bool,
pub spaced: bool, pub spaced: bool,
pub style: MathStyle,
pub font_size: Abs,
pub class: MathClass,
} }
impl FrameFragment { impl FrameFragment {
pub fn new(frame: Frame) -> Self { pub fn new(ctx: &MathContext, frame: Frame) -> Self {
Self { Self {
frame, frame,
class: MathClass::Normal,
limits: false, limits: false,
spaced: false, spaced: false,
font_size: ctx.size,
style: ctx.style,
class: MathClass::Normal,
} }
} }

View File

@ -307,7 +307,7 @@ fn layout_delimiters(
ctx.push(GlyphFragment::new(ctx, left).stretch_vertical(ctx, target, short_fall)); ctx.push(GlyphFragment::new(ctx, left).stretch_vertical(ctx, target, short_fall));
} }
ctx.push(frame); ctx.push(FrameFragment::new(ctx, frame));
if let Some(right) = right { if let Some(right) = right {
ctx.push( ctx.push(

View File

@ -261,7 +261,7 @@ impl LayoutMath for Content {
} }
if self.is::<SpaceNode>() { if self.is::<SpaceNode>() {
ctx.push(MathFragment::Space); ctx.push(MathFragment::Space(ctx.space_width.scaled(ctx)));
return Ok(()); return Ok(());
} }
@ -288,8 +288,8 @@ impl LayoutMath for Content {
return node.layout_math(ctx); return node.layout_math(ctx);
} }
let frame = ctx.layout_non_math(self)?; let frame = ctx.layout_content(self)?;
ctx.push(FrameFragment::new(frame).with_spaced(true)); ctx.push(FrameFragment::new(ctx, frame).with_spaced(true));
Ok(()) Ok(())
} }

View File

@ -38,9 +38,9 @@ impl OpNode {
impl LayoutMath for OpNode { impl LayoutMath for OpNode {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let frame = ctx.layout_non_math(&TextNode(self.text.clone()).pack())?; let frame = ctx.layout_content(&TextNode(self.text.clone()).pack())?;
ctx.push( ctx.push(
FrameFragment::new(frame) FrameFragment::new(ctx, frame)
.with_class(MathClass::Large) .with_class(MathClass::Large)
.with_limits(self.limits), .with_limits(self.limits),
); );

View File

@ -158,7 +158,7 @@ fn layout(
); );
frame.push_frame(radicand_pos, radicand); frame.push_frame(radicand_pos, radicand);
ctx.push(frame); ctx.push(FrameFragment::new(ctx, frame));
Ok(()) Ok(())
} }

View File

@ -5,55 +5,55 @@ use super::*;
pub const TIGHT_LEADING: Em = Em::new(0.25); pub const TIGHT_LEADING: Em = Em::new(0.25);
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct MathRow(pub Vec<MathFragment>); pub struct MathRow(Vec<MathFragment>);
impl MathRow { impl MathRow {
pub fn new() -> Self { pub fn new(fragments: Vec<MathFragment>) -> Self {
Self(vec![]) let mut iter = fragments.into_iter().peekable();
} let mut last: Option<usize> = None;
let mut space: Option<MathFragment> = None;
let mut resolved: Vec<MathFragment> = vec![];
pub fn width(&self) -> Abs { while let Some(mut fragment) = iter.next() {
self.0.iter().map(|fragment| fragment.width()).sum() match fragment {
} // Keep space only if supported by spaced fragments.
MathFragment::Space(_) => {
pub fn height(&self) -> Abs { if last.is_some() {
self.ascent() + self.descent() space = Some(fragment);
}
pub fn ascent(&self) -> Abs {
self.0.iter().map(MathFragment::ascent).max().unwrap_or_default()
}
pub fn descent(&self) -> Abs {
self.0.iter().map(MathFragment::descent).max().unwrap_or_default()
}
pub fn push(
&mut self,
font_size: Abs,
space_width: Em,
style: MathStyle,
fragment: impl Into<MathFragment>,
) {
let mut fragment = fragment.into();
if !fragment.participating() {
self.0.push(fragment);
return;
}
let mut space = false;
for (i, prev) in self.0.iter().enumerate().rev() {
if !prev.participating() {
space |= matches!(prev, MathFragment::Space);
if matches!(prev, MathFragment::Spacing(_)) {
break;
} }
continue; continue;
} }
if fragment.class() == Some(MathClass::Vary) { // Explicit spacing disables automatic spacing.
if matches!( MathFragment::Spacing(_) => {
prev.class(), last = None;
space = None;
resolved.push(fragment);
continue;
}
// Alignment points are resolved later.
MathFragment::Align => {
resolved.push(fragment);
continue;
}
// New line, new things.
MathFragment::Linebreak => {
resolved.push(fragment);
space = None;
last = None;
continue;
}
_ => {}
}
// Convert variable operators into binary operators if something
// precedes them.
if fragment.class() == Some(MathClass::Vary)
&& matches!(
last.and_then(|i| resolved[i].class()),
Some( Some(
MathClass::Normal MathClass::Normal
| MathClass::Alphabetic | MathClass::Alphabetic
@ -62,22 +62,43 @@ impl MathRow {
| MathClass::Fence | MathClass::Fence
| MathClass::Relation | MathClass::Relation
) )
) { )
{
fragment.set_class(MathClass::Binary); fragment.set_class(MathClass::Binary);
} }
// Insert spacing between the last and this item.
if let Some(i) = last {
if let Some(s) = spacing(&resolved[i], space.take(), &fragment) {
resolved.insert(i + 1, s);
}
} }
let mut amount = Abs::zero(); last = Some(resolved.len());
amount += spacing(prev, &fragment, style, space, space_width).at(font_size); resolved.push(fragment);
if !amount.is_zero() {
self.0.insert(i + 1, MathFragment::Spacing(amount));
} }
break; Self(resolved)
} }
self.0.push(fragment); pub fn iter(&self) -> std::slice::Iter<'_, MathFragment> {
self.0.iter()
}
pub fn width(&self) -> Abs {
self.iter().map(MathFragment::width).sum()
}
pub fn height(&self) -> Abs {
self.ascent() + self.descent()
}
pub fn ascent(&self) -> Abs {
self.iter().map(MathFragment::ascent).max().unwrap_or_default()
}
pub fn descent(&self) -> Abs {
self.iter().map(MathFragment::descent).max().unwrap_or_default()
} }
pub fn to_frame(self, ctx: &MathContext) -> Frame { pub fn to_frame(self, ctx: &MathContext) -> Frame {
@ -86,14 +107,22 @@ impl MathRow {
self.to_aligned_frame(ctx, &[], align) self.to_aligned_frame(ctx, &[], align)
} }
pub fn to_fragment(self, ctx: &MathContext) -> MathFragment {
if self.0.len() == 1 {
self.0.into_iter().next().unwrap()
} else {
FrameFragment::new(ctx, self.to_frame(ctx)).into()
}
}
pub fn to_aligned_frame( pub fn to_aligned_frame(
mut self, mut self,
ctx: &MathContext, ctx: &MathContext,
points: &[Abs], points: &[Abs],
align: Align, align: Align,
) -> Frame { ) -> Frame {
if self.0.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) { if self.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) {
let fragments = std::mem::take(&mut self.0); let fragments: Vec<_> = std::mem::take(&mut self.0);
let leading = if ctx.style.size >= MathSize::Text { let leading = if ctx.style.size >= MathSize::Text {
ctx.styles().get(ParNode::LEADING) ctx.styles().get(ParNode::LEADING)
} else { } else {
@ -140,7 +169,7 @@ impl MathRow {
if let (Some(&first), Align::Center) = (points.first(), align) { if let (Some(&first), Align::Center) = (points.first(), align) {
let mut offset = first; let mut offset = first;
for fragment in &self.0 { for fragment in self.iter() {
offset -= fragment.width(); offset -= fragment.width();
if matches!(fragment, MathFragment::Align) { if matches!(fragment, MathFragment::Align) {
x = offset; x = offset;

View File

@ -1,6 +1,5 @@
use super::*; use super::*;
pub(super) const ZERO: Em = Em::zero();
pub(super) const THIN: Em = Em::new(1.0 / 6.0); pub(super) const THIN: Em = Em::new(1.0 / 6.0);
pub(super) const MEDIUM: Em = Em::new(2.0 / 9.0); pub(super) const MEDIUM: Em = Em::new(2.0 / 9.0);
pub(super) const THICK: Em = Em::new(5.0 / 18.0); pub(super) const THICK: Em = Em::new(5.0 / 18.0);
@ -14,49 +13,48 @@ pub(super) fn define(math: &mut Scope) {
math.define("quad", HNode::strong(QUAD).pack()); math.define("quad", HNode::strong(QUAD).pack());
} }
/// Determine the spacing between two fragments in a given style. /// Create the spacing between two fragments in a given style.
pub(super) fn spacing( pub(super) fn spacing(
left: &MathFragment, l: &MathFragment,
right: &MathFragment, space: Option<MathFragment>,
style: MathStyle, r: &MathFragment,
space: bool, ) -> Option<MathFragment> {
space_width: Em,
) -> Em {
use MathClass::*; use MathClass::*;
let script = style.size <= MathSize::Script;
let class = |frag: &MathFragment| frag.class().unwrap_or(Special); let class = |f: &MathFragment| f.class().unwrap_or(Special);
match (class(left), class(right)) { let resolve = |v: Em, f: &MathFragment| {
Some(MathFragment::Spacing(f.font_size().map_or(Abs::zero(), |size| v.at(size))))
};
let script =
|f: &MathFragment| f.style().map_or(false, |s| s.size <= MathSize::Script);
match (class(l), class(r)) {
// No spacing before punctuation; thin spacing after punctuation, unless // No spacing before punctuation; thin spacing after punctuation, unless
// in script size. // in script size.
(_, Punctuation) => ZERO, (_, Punctuation) => None,
(Punctuation, _) if !script => THIN, (Punctuation, _) if !script(l) => resolve(THIN, l),
// No spacing after opening delimiters and before closing delimiters. // No spacing after opening delimiters and before closing delimiters.
(Opening, _) | (_, Closing) => ZERO, (Opening, _) | (_, Closing) => None,
// Thick spacing around relations, unless followed by a another relation // Thick spacing around relations, unless followed by a another relation
// or in script size. // or in script size.
(Relation, Relation) => ZERO, (Relation, Relation) => None,
(Relation, _) | (_, Relation) if !script => THICK, (Relation, _) if !script(l) => resolve(THICK, l),
(_, Relation) if !script(r) => resolve(THICK, r),
// Medium spacing around binary operators, unless in script size. // Medium spacing around binary operators, unless in script size.
(Binary, _) | (_, Binary) if !script => MEDIUM, (Binary, _) if !script(l) => resolve(MEDIUM, l),
(_, Binary) if !script(r) => resolve(MEDIUM, r),
// Thin spacing around large operators, unless next to a delimiter. // Thin spacing around large operators, unless next to a delimiter.
(Large, Opening | Fence) | (Closing | Fence, Large) => ZERO, (Large, Opening | Fence) | (Closing | Fence, Large) => None,
(Large, _) | (_, Large) => THIN, (Large, _) => resolve(THIN, l),
(_, Large) => resolve(THIN, r),
// Spacing around spaced frames. // Spacing around spaced frames.
_ if space && (is_spaced(left) || is_spaced(right)) => space_width, _ if (l.is_spaced() || r.is_spaced()) => space,
_ => ZERO, _ => None,
}
}
/// Whether this fragment should react to adjacent spaces.
fn is_spaced(fragment: &MathFragment) -> bool {
match fragment {
MathFragment::Frame(frame) => frame.spaced,
_ => fragment.class() == Some(MathClass::Fence),
} }
} }

View File

@ -177,6 +177,8 @@ fn assemble(
c: base.c, c: base.c,
id: None, id: None,
frame, frame,
style: base.style,
font_size: base.font_size,
italics_correction: Abs::zero(), italics_correction: Abs::zero(),
class: base.class, class: base.class,
} }

View File

@ -313,7 +313,7 @@ impl LayoutMath for BbNode {
} }
} }
/// The style in a formula. /// Text properties in a formula.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct MathStyle { pub struct MathStyle {
/// The style variant to select. /// The style variant to select.

View File

@ -270,7 +270,8 @@ fn layout(
baseline = rows.len() - 1; baseline = rows.len() - 1;
} }
ctx.push(stack(ctx, rows, Align::Center, gap, baseline)); let frame = stack(ctx, rows, Align::Center, gap, baseline);
ctx.push(FrameFragment::new(ctx, frame));
Ok(()) Ok(())
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB