More robust automatic math spacing
@ -118,7 +118,7 @@ impl LayoutMath for AccentNode {
|
||||
frame.set_baseline(baseline);
|
||||
frame.push_frame(accent_pos, accent);
|
||||
frame.push_frame(base_pos, base.to_frame(ctx));
|
||||
ctx.push(frame);
|
||||
ctx.push(FrameFragment::new(ctx, frame));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -29,8 +29,7 @@ pub(super) fn alignments(rows: &[MathRow]) -> Vec<Abs> {
|
||||
let count = rows
|
||||
.iter()
|
||||
.map(|row| {
|
||||
row.0
|
||||
.iter()
|
||||
row.iter()
|
||||
.filter(|fragment| matches!(fragment, MathFragment::Align))
|
||||
.count()
|
||||
})
|
||||
@ -42,7 +41,7 @@ pub(super) fn alignments(rows: &[MathRow]) -> Vec<Abs> {
|
||||
for row in rows {
|
||||
let mut x = Abs::zero();
|
||||
let mut i = 0;
|
||||
for fragment in &row.0 {
|
||||
for fragment in row.iter() {
|
||||
if matches!(fragment, MathFragment::Align) {
|
||||
if i < current {
|
||||
x = points[i];
|
||||
|
@ -231,7 +231,7 @@ fn scripts(
|
||||
frame.push_frame(sub_pos, sub);
|
||||
}
|
||||
|
||||
ctx.push(FrameFragment::new(frame).with_class(class));
|
||||
ctx.push(FrameFragment::new(ctx, frame).with_class(class));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -284,7 +284,7 @@ fn limits(
|
||||
frame.push_frame(bottom_pos, bottom);
|
||||
}
|
||||
|
||||
ctx.push(FrameFragment::new(frame).with_class(class));
|
||||
ctx.push(FrameFragment::new(ctx, frame).with_class(class));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ pub struct MathContext<'a, 'b, 'v> {
|
||||
pub table: ttf_parser::math::Table<'a>,
|
||||
pub constants: ttf_parser::math::Constants<'a>,
|
||||
pub space_width: Em,
|
||||
pub row: MathRow,
|
||||
pub fragments: Vec<MathFragment>,
|
||||
pub map: StyleMap,
|
||||
pub style: MathStyle,
|
||||
pub size: Abs,
|
||||
@ -69,7 +69,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
||||
table,
|
||||
constants,
|
||||
space_width,
|
||||
row: MathRow::new(),
|
||||
fragments: vec![],
|
||||
map: StyleMap::new(),
|
||||
style: MathStyle {
|
||||
variant: MathVariant::Serif,
|
||||
@ -88,45 +88,45 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
||||
}
|
||||
|
||||
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) {
|
||||
let mut iter = row.0.into_iter();
|
||||
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 extend(&mut self, fragments: Vec<MathFragment>) {
|
||||
self.fragments.extend(fragments);
|
||||
}
|
||||
|
||||
pub fn layout_fragment(
|
||||
&mut self,
|
||||
node: &dyn LayoutMath,
|
||||
) -> SourceResult<MathFragment> {
|
||||
let row = self.layout_row(node)?;
|
||||
Ok(if row.0.len() == 1 {
|
||||
row.0.into_iter().next().unwrap()
|
||||
} else {
|
||||
row.to_frame(self).into()
|
||||
})
|
||||
let row = self.layout_fragments(node)?;
|
||||
Ok(MathRow::new(row).to_fragment(self))
|
||||
}
|
||||
|
||||
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> {
|
||||
let prev = std::mem::take(&mut self.row);
|
||||
node.layout_math(self)?;
|
||||
Ok(std::mem::replace(&mut self.row, prev))
|
||||
let fragments = self.layout_fragments(node)?;
|
||||
Ok(MathRow::new(fragments))
|
||||
}
|
||||
|
||||
pub fn layout_frame(&mut self, node: &dyn LayoutMath) -> SourceResult<Frame> {
|
||||
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<()> {
|
||||
let mut chars = text.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()) {
|
||||
// Numbers aren't that difficult.
|
||||
let mut vec = vec![];
|
||||
let mut fragments = vec![];
|
||||
for c in text.chars() {
|
||||
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);
|
||||
self.push(frame);
|
||||
let frame = MathRow::new(fragments).to_frame(self);
|
||||
self.push(FrameFragment::new(self, frame));
|
||||
} else {
|
||||
// Anything else is handled by Typst's standard text layout.
|
||||
let spaced = text.graphemes(true).count() > 1;
|
||||
@ -161,9 +161,9 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
||||
style = style.with_italic(false);
|
||||
}
|
||||
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(
|
||||
FrameFragment::new(frame)
|
||||
FrameFragment::new(self, frame)
|
||||
.with_class(MathClass::Alphabetic)
|
||||
.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 max_extent = row
|
||||
.0
|
||||
let max_extent = fragments
|
||||
.iter()
|
||||
.map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis))
|
||||
.max()
|
||||
@ -75,7 +74,7 @@ impl LayoutMath for LrNode {
|
||||
.resolve(ctx.styles())
|
||||
.relative_to(2.0 * max_extent);
|
||||
|
||||
match row.0.as_mut_slice() {
|
||||
match fragments.as_mut_slice() {
|
||||
[one] => scale(ctx, one, height, None),
|
||||
[first, .., last] => {
|
||||
scale(ctx, first, height, Some(MathClass::Opening));
|
||||
@ -84,7 +83,7 @@ impl LayoutMath for LrNode {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
ctx.extend(row);
|
||||
ctx.extend(fragments);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ fn layout(
|
||||
|
||||
if binom {
|
||||
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));
|
||||
} else {
|
||||
frame.push(
|
||||
@ -165,7 +165,7 @@ fn layout(
|
||||
}),
|
||||
),
|
||||
);
|
||||
ctx.push(frame);
|
||||
ctx.push(FrameFragment::new(ctx, frame));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -6,7 +6,7 @@ pub enum MathFragment {
|
||||
Variant(VariantFragment),
|
||||
Frame(FrameFragment),
|
||||
Spacing(Abs),
|
||||
Space,
|
||||
Space(Abs),
|
||||
Linebreak,
|
||||
Align,
|
||||
}
|
||||
@ -22,6 +22,7 @@ impl MathFragment {
|
||||
Self::Variant(variant) => variant.frame.width(),
|
||||
Self::Frame(fragment) => fragment.frame.width(),
|
||||
Self::Spacing(amount) => *amount,
|
||||
Self::Space(amount) => *amount,
|
||||
_ => 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) {
|
||||
match self {
|
||||
Self::Glyph(glyph) => glyph.class = Some(class),
|
||||
@ -71,8 +90,11 @@ impl MathFragment {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn participating(&self) -> bool {
|
||||
!matches!(self, Self::Space | Self::Spacing(_) | Self::Align)
|
||||
pub fn is_spaced(&self) -> bool {
|
||||
match self {
|
||||
MathFragment::Frame(frame) => frame.spaced,
|
||||
_ => self.class() == Some(MathClass::Fence),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn italics_correction(&self) -> Abs {
|
||||
@ -111,23 +133,18 @@ impl From<FrameFragment> for MathFragment {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Frame> for MathFragment {
|
||||
fn from(frame: Frame) -> Self {
|
||||
Self::Frame(FrameFragment::new(frame))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct GlyphFragment {
|
||||
pub id: GlyphId,
|
||||
pub c: char,
|
||||
pub lang: Lang,
|
||||
pub fill: Paint,
|
||||
pub font_size: Abs,
|
||||
pub width: Abs,
|
||||
pub ascent: Abs,
|
||||
pub descent: Abs,
|
||||
pub italics_correction: Abs,
|
||||
pub style: MathStyle,
|
||||
pub font_size: Abs,
|
||||
pub class: Option<MathClass>,
|
||||
}
|
||||
|
||||
@ -163,6 +180,7 @@ impl GlyphFragment {
|
||||
c,
|
||||
lang: ctx.styles().get(TextNode::LANG),
|
||||
fill: ctx.styles().get(TextNode::FILL),
|
||||
style: ctx.style,
|
||||
font_size: ctx.size,
|
||||
width,
|
||||
ascent: bbox.y_max.scaled(ctx),
|
||||
@ -184,6 +202,8 @@ impl GlyphFragment {
|
||||
c: self.c,
|
||||
id: Some(self.id),
|
||||
frame: self.to_frame(ctx),
|
||||
style: self.style,
|
||||
font_size: self.font_size,
|
||||
italics_correction: self.italics_correction,
|
||||
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 c: char,
|
||||
pub id: Option<GlyphId>,
|
||||
pub frame: Frame,
|
||||
pub italics_correction: Abs,
|
||||
pub frame: Frame,
|
||||
pub style: MathStyle,
|
||||
pub font_size: Abs,
|
||||
pub class: Option<MathClass>,
|
||||
}
|
||||
|
||||
impl Debug for VariantFragment {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "VariantFragment({:?})", self.c)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FrameFragment {
|
||||
pub frame: Frame,
|
||||
pub class: MathClass,
|
||||
pub limits: bool,
|
||||
pub spaced: bool,
|
||||
pub style: MathStyle,
|
||||
pub font_size: Abs,
|
||||
pub class: MathClass,
|
||||
}
|
||||
|
||||
impl FrameFragment {
|
||||
pub fn new(frame: Frame) -> Self {
|
||||
pub fn new(ctx: &MathContext, frame: Frame) -> Self {
|
||||
Self {
|
||||
frame,
|
||||
class: MathClass::Normal,
|
||||
limits: 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(frame);
|
||||
ctx.push(FrameFragment::new(ctx, frame));
|
||||
|
||||
if let Some(right) = right {
|
||||
ctx.push(
|
||||
|
@ -261,7 +261,7 @@ impl LayoutMath for Content {
|
||||
}
|
||||
|
||||
if self.is::<SpaceNode>() {
|
||||
ctx.push(MathFragment::Space);
|
||||
ctx.push(MathFragment::Space(ctx.space_width.scaled(ctx)));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -288,8 +288,8 @@ impl LayoutMath for Content {
|
||||
return node.layout_math(ctx);
|
||||
}
|
||||
|
||||
let frame = ctx.layout_non_math(self)?;
|
||||
ctx.push(FrameFragment::new(frame).with_spaced(true));
|
||||
let frame = ctx.layout_content(self)?;
|
||||
ctx.push(FrameFragment::new(ctx, frame).with_spaced(true));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -38,9 +38,9 @@ impl OpNode {
|
||||
|
||||
impl LayoutMath for OpNode {
|
||||
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(
|
||||
FrameFragment::new(frame)
|
||||
FrameFragment::new(ctx, frame)
|
||||
.with_class(MathClass::Large)
|
||||
.with_limits(self.limits),
|
||||
);
|
||||
|
@ -158,7 +158,7 @@ fn layout(
|
||||
);
|
||||
|
||||
frame.push_frame(radicand_pos, radicand);
|
||||
ctx.push(frame);
|
||||
ctx.push(FrameFragment::new(ctx, frame));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -5,55 +5,55 @@ use super::*;
|
||||
pub const TIGHT_LEADING: Em = Em::new(0.25);
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct MathRow(pub Vec<MathFragment>);
|
||||
pub struct MathRow(Vec<MathFragment>);
|
||||
|
||||
impl MathRow {
|
||||
pub fn new() -> Self {
|
||||
Self(vec![])
|
||||
}
|
||||
pub fn new(fragments: Vec<MathFragment>) -> Self {
|
||||
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 {
|
||||
self.0.iter().map(|fragment| fragment.width()).sum()
|
||||
}
|
||||
|
||||
pub fn height(&self) -> Abs {
|
||||
self.ascent() + self.descent()
|
||||
}
|
||||
|
||||
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;
|
||||
while let Some(mut fragment) = iter.next() {
|
||||
match fragment {
|
||||
// Keep space only if supported by spaced fragments.
|
||||
MathFragment::Space(_) => {
|
||||
if last.is_some() {
|
||||
space = Some(fragment);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if fragment.class() == Some(MathClass::Vary) {
|
||||
if matches!(
|
||||
prev.class(),
|
||||
// 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;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// 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(
|
||||
MathClass::Normal
|
||||
| MathClass::Alphabetic
|
||||
@ -62,22 +62,43 @@ impl MathRow {
|
||||
| MathClass::Fence
|
||||
| MathClass::Relation
|
||||
)
|
||||
) {
|
||||
)
|
||||
{
|
||||
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();
|
||||
amount += spacing(prev, &fragment, style, space, space_width).at(font_size);
|
||||
|
||||
if !amount.is_zero() {
|
||||
self.0.insert(i + 1, MathFragment::Spacing(amount));
|
||||
last = Some(resolved.len());
|
||||
resolved.push(fragment);
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -86,14 +107,22 @@ impl MathRow {
|
||||
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(
|
||||
mut self,
|
||||
ctx: &MathContext,
|
||||
points: &[Abs],
|
||||
align: Align,
|
||||
) -> Frame {
|
||||
if self.0.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) {
|
||||
let fragments = std::mem::take(&mut self.0);
|
||||
if self.iter().any(|frag| matches!(frag, MathFragment::Linebreak)) {
|
||||
let fragments: Vec<_> = std::mem::take(&mut self.0);
|
||||
let leading = if ctx.style.size >= MathSize::Text {
|
||||
ctx.styles().get(ParNode::LEADING)
|
||||
} else {
|
||||
@ -140,7 +169,7 @@ impl MathRow {
|
||||
|
||||
if let (Some(&first), Align::Center) = (points.first(), align) {
|
||||
let mut offset = first;
|
||||
for fragment in &self.0 {
|
||||
for fragment in self.iter() {
|
||||
offset -= fragment.width();
|
||||
if matches!(fragment, MathFragment::Align) {
|
||||
x = offset;
|
||||
|
@ -1,6 +1,5 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) const ZERO: Em = Em::zero();
|
||||
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 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());
|
||||
}
|
||||
|
||||
/// Determine the spacing between two fragments in a given style.
|
||||
/// Create the spacing between two fragments in a given style.
|
||||
pub(super) fn spacing(
|
||||
left: &MathFragment,
|
||||
right: &MathFragment,
|
||||
style: MathStyle,
|
||||
space: bool,
|
||||
space_width: Em,
|
||||
) -> Em {
|
||||
l: &MathFragment,
|
||||
space: Option<MathFragment>,
|
||||
r: &MathFragment,
|
||||
) -> Option<MathFragment> {
|
||||
use MathClass::*;
|
||||
let script = style.size <= MathSize::Script;
|
||||
let class = |frag: &MathFragment| frag.class().unwrap_or(Special);
|
||||
match (class(left), class(right)) {
|
||||
|
||||
let class = |f: &MathFragment| f.class().unwrap_or(Special);
|
||||
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
|
||||
// in script size.
|
||||
(_, Punctuation) => ZERO,
|
||||
(Punctuation, _) if !script => THIN,
|
||||
(_, Punctuation) => None,
|
||||
(Punctuation, _) if !script(l) => resolve(THIN, l),
|
||||
|
||||
// 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
|
||||
// or in script size.
|
||||
(Relation, Relation) => ZERO,
|
||||
(Relation, _) | (_, Relation) if !script => THICK,
|
||||
(Relation, Relation) => None,
|
||||
(Relation, _) if !script(l) => resolve(THICK, l),
|
||||
(_, Relation) if !script(r) => resolve(THICK, r),
|
||||
|
||||
// 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.
|
||||
(Large, Opening | Fence) | (Closing | Fence, Large) => ZERO,
|
||||
(Large, _) | (_, Large) => THIN,
|
||||
(Large, Opening | Fence) | (Closing | Fence, Large) => None,
|
||||
(Large, _) => resolve(THIN, l),
|
||||
(_, Large) => resolve(THIN, r),
|
||||
|
||||
// Spacing around spaced frames.
|
||||
_ if space && (is_spaced(left) || is_spaced(right)) => space_width,
|
||||
_ if (l.is_spaced() || r.is_spaced()) => space,
|
||||
|
||||
_ => ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -177,6 +177,8 @@ fn assemble(
|
||||
c: base.c,
|
||||
id: None,
|
||||
frame,
|
||||
style: base.style,
|
||||
font_size: base.font_size,
|
||||
italics_correction: Abs::zero(),
|
||||
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)]
|
||||
pub struct MathStyle {
|
||||
/// The style variant to select.
|
||||
|
@ -270,7 +270,8 @@ fn layout(
|
||||
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(())
|
||||
}
|
||||
|
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 |