More robust automatic math spacing
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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];
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
);
|
);
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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(())
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
);
|
);
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
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;
|
|
||||||
|
// Explicit spacing disables automatic spacing.
|
||||||
|
MathFragment::Spacing(_) => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fragment.class() == Some(MathClass::Vary) {
|
// Convert variable operators into binary operators if something
|
||||||
if matches!(
|
// precedes them.
|
||||||
prev.class(),
|
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.0.push(fragment);
|
Self(resolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
@ -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),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |