Fix math spacing bugs
@ -31,7 +31,7 @@ impl LayoutMath for AtomNode {
|
|||||||
{
|
{
|
||||||
// A single letter that is available in the math font.
|
// A single letter that is available in the math font.
|
||||||
if ctx.style.size == MathSize::Display
|
if ctx.style.size == MathSize::Display
|
||||||
&& glyph.class() == Some(MathClass::Large)
|
&& glyph.class == Some(MathClass::Large)
|
||||||
{
|
{
|
||||||
let height = scaled!(ctx, display_operator_min_height);
|
let height = scaled!(ctx, display_operator_min_height);
|
||||||
ctx.push(glyph.stretch_vertical(ctx, height, Abs::zero()));
|
ctx.push(glyph.stretch_vertical(ctx, height, Abs::zero()));
|
||||||
@ -49,7 +49,8 @@ impl LayoutMath for AtomNode {
|
|||||||
ctx.push(frame);
|
ctx.push(frame);
|
||||||
} else {
|
} else {
|
||||||
// Anything else is handled by Typst's standard text layout.
|
// Anything else is handled by Typst's standard text layout.
|
||||||
TextNode(self.0.clone()).pack().layout_math(ctx)?;
|
let frame = ctx.layout_non_math(&TextNode(self.0.clone()).pack())?;
|
||||||
|
ctx.push(FrameFragment::new(frame).with_class(MathClass::Alphabetic));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -30,6 +30,7 @@ pub(super) struct MathContext<'a, 'b, 'v> {
|
|||||||
pub ttf: &'a ttf_parser::Face<'a>,
|
pub ttf: &'a ttf_parser::Face<'a>,
|
||||||
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 fill: Paint,
|
pub fill: Paint,
|
||||||
pub lang: Lang,
|
pub lang: Lang,
|
||||||
pub row: MathRow,
|
pub row: MathRow,
|
||||||
@ -50,6 +51,14 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
let table = font.ttf().tables().math.unwrap();
|
let table = font.ttf().tables().math.unwrap();
|
||||||
let constants = table.constants.unwrap();
|
let constants = table.constants.unwrap();
|
||||||
let size = styles.get(TextNode::SIZE);
|
let size = styles.get(TextNode::SIZE);
|
||||||
|
|
||||||
|
let ttf = font.ttf();
|
||||||
|
let space_width = ttf
|
||||||
|
.glyph_index(' ')
|
||||||
|
.and_then(|id| ttf.glyph_hor_advance(id))
|
||||||
|
.map(|advance| font.to_em(advance))
|
||||||
|
.unwrap_or(THICK);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
vt,
|
vt,
|
||||||
outer: styles,
|
outer: styles,
|
||||||
@ -71,6 +80,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
ttf: font.ttf(),
|
ttf: font.ttf(),
|
||||||
table,
|
table,
|
||||||
constants,
|
constants,
|
||||||
|
space_width,
|
||||||
row: MathRow::new(),
|
row: MathRow::new(),
|
||||||
base_size: size,
|
base_size: size,
|
||||||
scaled_size: size,
|
scaled_size: size,
|
||||||
@ -79,7 +89,16 @@ 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.scaled_size, self.style, fragment);
|
self.row
|
||||||
|
.push(self.scaled_size, self.space_width, self.style, fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
pub fn layout_non_math(&mut self, content: &Content) -> SourceResult<Frame> {
|
||||||
|
@ -6,8 +6,9 @@ pub(super) enum MathFragment {
|
|||||||
Variant(VariantFragment),
|
Variant(VariantFragment),
|
||||||
Frame(FrameFragment),
|
Frame(FrameFragment),
|
||||||
Spacing(Abs),
|
Spacing(Abs),
|
||||||
Align,
|
Space,
|
||||||
Linebreak,
|
Linebreak,
|
||||||
|
Align,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MathFragment {
|
impl MathFragment {
|
||||||
@ -54,13 +55,26 @@ impl MathFragment {
|
|||||||
|
|
||||||
pub fn class(&self) -> Option<MathClass> {
|
pub fn class(&self) -> Option<MathClass> {
|
||||||
match self {
|
match self {
|
||||||
Self::Glyph(glyph) => glyph.class(),
|
Self::Glyph(glyph) => glyph.class,
|
||||||
Self::Variant(variant) => variant.class(),
|
Self::Variant(variant) => variant.class,
|
||||||
Self::Frame(fragment) => Some(fragment.class),
|
Self::Frame(fragment) => Some(fragment.class),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_class(&mut self, class: MathClass) {
|
||||||
|
match self {
|
||||||
|
Self::Glyph(glyph) => glyph.class = Some(class),
|
||||||
|
Self::Variant(variant) => variant.class = Some(class),
|
||||||
|
Self::Frame(fragment) => fragment.class = class,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn participating(&self) -> bool {
|
||||||
|
!matches!(self, Self::Space | Self::Spacing(_) | Self::Align)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn italics_correction(&self) -> Abs {
|
pub fn italics_correction(&self) -> Abs {
|
||||||
match self {
|
match self {
|
||||||
Self::Glyph(glyph) => glyph.italics_correction,
|
Self::Glyph(glyph) => glyph.italics_correction,
|
||||||
@ -99,7 +113,7 @@ impl From<FrameFragment> for MathFragment {
|
|||||||
|
|
||||||
impl From<Frame> for MathFragment {
|
impl From<Frame> for MathFragment {
|
||||||
fn from(frame: Frame) -> Self {
|
fn from(frame: Frame) -> Self {
|
||||||
Self::Frame(FrameFragment { frame, class: MathClass::Normal, limits: false })
|
Self::Frame(FrameFragment::new(frame))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,6 +126,7 @@ pub(super) struct GlyphFragment {
|
|||||||
pub ascent: Abs,
|
pub ascent: Abs,
|
||||||
pub descent: Abs,
|
pub descent: Abs,
|
||||||
pub italics_correction: Abs,
|
pub italics_correction: Abs,
|
||||||
|
pub class: Option<MathClass>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlyphFragment {
|
impl GlyphFragment {
|
||||||
@ -144,6 +159,10 @@ impl GlyphFragment {
|
|||||||
ascent: bbox.y_max.scaled(ctx),
|
ascent: bbox.y_max.scaled(ctx),
|
||||||
descent: -bbox.y_min.scaled(ctx),
|
descent: -bbox.y_min.scaled(ctx),
|
||||||
italics_correction: italics,
|
italics_correction: italics,
|
||||||
|
class: match c {
|
||||||
|
':' => Some(MathClass::Relation),
|
||||||
|
_ => unicode_math_class::class(c),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,16 +170,13 @@ impl GlyphFragment {
|
|||||||
self.ascent + self.descent
|
self.ascent + self.descent
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn class(&self) -> Option<MathClass> {
|
|
||||||
unicode_math_class::class(self.c)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_variant(&self, ctx: &MathContext) -> VariantFragment {
|
pub fn to_variant(&self, ctx: &MathContext) -> VariantFragment {
|
||||||
VariantFragment {
|
VariantFragment {
|
||||||
c: self.c,
|
c: self.c,
|
||||||
id: Some(self.id),
|
id: Some(self.id),
|
||||||
frame: self.to_frame(ctx),
|
frame: self.to_frame(ctx),
|
||||||
italics_correction: self.italics_correction,
|
italics_correction: self.italics_correction,
|
||||||
|
class: self.class,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,12 +207,7 @@ pub struct VariantFragment {
|
|||||||
pub id: Option<GlyphId>,
|
pub id: Option<GlyphId>,
|
||||||
pub frame: Frame,
|
pub frame: Frame,
|
||||||
pub italics_correction: Abs,
|
pub italics_correction: Abs,
|
||||||
}
|
pub class: Option<MathClass>,
|
||||||
|
|
||||||
impl VariantFragment {
|
|
||||||
pub fn class(&self) -> Option<MathClass> {
|
|
||||||
unicode_math_class::class(self.c)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -204,6 +215,30 @@ pub struct FrameFragment {
|
|||||||
pub frame: Frame,
|
pub frame: Frame,
|
||||||
pub class: MathClass,
|
pub class: MathClass,
|
||||||
pub limits: bool,
|
pub limits: bool,
|
||||||
|
pub spaced: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameFragment {
|
||||||
|
pub fn new(frame: Frame) -> Self {
|
||||||
|
Self {
|
||||||
|
frame,
|
||||||
|
class: MathClass::Normal,
|
||||||
|
limits: false,
|
||||||
|
spaced: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_class(self, class: MathClass) -> Self {
|
||||||
|
Self { class, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_limits(self, limits: bool) -> Self {
|
||||||
|
Self { limits, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_spaced(self, spaced: bool) -> Self {
|
||||||
|
Self { spaced, ..self }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up the italics correction for a glyph.
|
/// Look up the italics correction for a glyph.
|
||||||
|
@ -69,9 +69,7 @@ impl LayoutMath for LrNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for fragment in row.0 {
|
ctx.extend(row);
|
||||||
ctx.push(fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ use self::row::*;
|
|||||||
use self::spacing::*;
|
use self::spacing::*;
|
||||||
use crate::layout::HNode;
|
use crate::layout::HNode;
|
||||||
use crate::layout::ParNode;
|
use crate::layout::ParNode;
|
||||||
|
use crate::layout::Spacing;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::LinebreakNode;
|
use crate::text::LinebreakNode;
|
||||||
use crate::text::TextNode;
|
use crate::text::TextNode;
|
||||||
@ -222,6 +223,7 @@ impl LayoutMath for FormulaNode {
|
|||||||
impl LayoutMath for Content {
|
impl LayoutMath for Content {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
if self.is::<SpaceNode>() {
|
if self.is::<SpaceNode>() {
|
||||||
|
ctx.push(MathFragment::Space);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +232,17 @@ impl LayoutMath for Content {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(node) = self.to::<HNode>() {
|
||||||
|
if let Spacing::Relative(rel) = node.amount {
|
||||||
|
if rel.rel.is_zero() {
|
||||||
|
ctx.push(MathFragment::Spacing(
|
||||||
|
rel.abs.resolve(ctx.outer.chain(&ctx.map)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(node) = self.to::<SequenceNode>() {
|
if let Some(node) = self.to::<SequenceNode>() {
|
||||||
for child in &node.0 {
|
for child in &node.0 {
|
||||||
child.layout_math(ctx)?;
|
child.layout_math(ctx)?;
|
||||||
@ -242,7 +255,7 @@ impl LayoutMath for Content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let frame = ctx.layout_non_math(self)?;
|
let frame = ctx.layout_non_math(self)?;
|
||||||
ctx.push(frame);
|
ctx.push(FrameFragment::new(frame).with_spaced(true));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -39,11 +39,11 @@ 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_non_math(&TextNode(self.text.clone()).pack())?;
|
||||||
ctx.push(FrameFragment {
|
ctx.push(
|
||||||
frame,
|
FrameFragment::new(frame)
|
||||||
class: MathClass::Large,
|
.with_class(MathClass::Large)
|
||||||
limits: self.limits,
|
.with_limits(self.limits),
|
||||||
});
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,36 +15,60 @@ impl MathRow {
|
|||||||
pub fn push(
|
pub fn push(
|
||||||
&mut self,
|
&mut self,
|
||||||
font_size: Abs,
|
font_size: Abs,
|
||||||
|
space_width: Em,
|
||||||
style: MathStyle,
|
style: MathStyle,
|
||||||
fragment: impl Into<MathFragment>,
|
fragment: impl Into<MathFragment>,
|
||||||
) {
|
) {
|
||||||
let fragment = fragment.into();
|
let mut fragment = fragment.into();
|
||||||
if let Some(fragment_class) = fragment.class() {
|
if !fragment.participating() {
|
||||||
for (i, prev) in self.0.iter().enumerate().rev() {
|
self.0.push(fragment);
|
||||||
if matches!(prev, MathFragment::Align) {
|
return;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut amount = Abs::zero();
|
|
||||||
if let MathFragment::Glyph(glyph) = *prev {
|
|
||||||
if !glyph.italics_correction.is_zero()
|
|
||||||
&& fragment_class != MathClass::Alphabetic
|
|
||||||
{
|
|
||||||
amount += glyph.italics_correction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(prev_class) = prev.class() {
|
|
||||||
amount += spacing(prev_class, fragment_class, style).at(font_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !amount.is_zero() {
|
|
||||||
self.0.insert(i + 1, MathFragment::Spacing(amount));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if fragment.class() == Some(MathClass::Vary) {
|
||||||
|
if matches!(
|
||||||
|
prev.class(),
|
||||||
|
Some(
|
||||||
|
MathClass::Normal
|
||||||
|
| MathClass::Alphabetic
|
||||||
|
| MathClass::Binary
|
||||||
|
| MathClass::Closing
|
||||||
|
| MathClass::Fence
|
||||||
|
| MathClass::Relation
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
fragment.set_class(MathClass::Binary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut amount = Abs::zero();
|
||||||
|
if let MathFragment::Glyph(glyph) = *prev {
|
||||||
|
if !glyph.italics_correction.is_zero()
|
||||||
|
&& fragment.class() != Some(MathClass::Alphabetic)
|
||||||
|
{
|
||||||
|
amount += glyph.italics_correction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
amount += spacing(prev, &fragment, style, space, space_width).at(font_size);
|
||||||
|
|
||||||
|
if !amount.is_zero() {
|
||||||
|
self.0.insert(i + 1, MathFragment::Spacing(amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
self.0.push(fragment);
|
self.0.push(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ fn scripts(
|
|||||||
frame.push_frame(base_pos, base.to_frame(ctx));
|
frame.push_frame(base_pos, base.to_frame(ctx));
|
||||||
frame.push_frame(sub_pos, sub);
|
frame.push_frame(sub_pos, sub);
|
||||||
frame.push_frame(sup_pos, sup);
|
frame.push_frame(sup_pos, sup);
|
||||||
ctx.push(FrameFragment { frame, class, limits: false });
|
ctx.push(FrameFragment::new(frame).with_class(class));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -172,7 +172,7 @@ fn limits(
|
|||||||
frame.push_frame(base_pos, base.to_frame(ctx));
|
frame.push_frame(base_pos, base.to_frame(ctx));
|
||||||
frame.push_frame(sub_pos, sub);
|
frame.push_frame(sub_pos, sub);
|
||||||
frame.push_frame(sup_pos, sup);
|
frame.push_frame(sup_pos, sup);
|
||||||
ctx.push(FrameFragment { frame, class, limits: false });
|
ctx.push(FrameFragment::new(frame).with_class(class));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const ZERO: Em = Em::zero();
|
pub(super) const ZERO: Em = Em::zero();
|
||||||
const THIN: Em = Em::new(1.0 / 6.0);
|
pub(super) const THIN: Em = Em::new(1.0 / 6.0);
|
||||||
const MEDIUM: Em = Em::new(2.0 / 9.0);
|
pub(super) const MEDIUM: Em = Em::new(2.0 / 9.0);
|
||||||
const THICK: Em = Em::new(5.0 / 18.0);
|
pub(super) const THICK: Em = Em::new(5.0 / 18.0);
|
||||||
const QUAD: Em = Em::new(1.0);
|
pub(super) const QUAD: Em = Em::new(1.0);
|
||||||
|
|
||||||
/// Hook up all spacings.
|
/// Hook up all spacings.
|
||||||
pub(super) fn define(math: &mut Scope) {
|
pub(super) fn define(math: &mut Scope) {
|
||||||
@ -15,10 +15,20 @@ pub(super) fn define(math: &mut Scope) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Determine the spacing between two fragments in a given style.
|
/// Determine the spacing between two fragments in a given style.
|
||||||
pub(super) fn spacing(left: MathClass, right: MathClass, style: MathStyle) -> Em {
|
pub(super) fn spacing(
|
||||||
|
left: &MathFragment,
|
||||||
|
right: &MathFragment,
|
||||||
|
style: MathStyle,
|
||||||
|
space: bool,
|
||||||
|
space_width: Em,
|
||||||
|
) -> Em {
|
||||||
use MathClass::*;
|
use MathClass::*;
|
||||||
let script = style.size <= MathSize::Script;
|
let script = style.size <= MathSize::Script;
|
||||||
match (left, right) {
|
let (Some(l), Some(r)) = (left.class(), right.class()) else {
|
||||||
|
return ZERO;
|
||||||
|
};
|
||||||
|
|
||||||
|
match (l, 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) => ZERO,
|
||||||
@ -33,12 +43,23 @@ pub(super) fn spacing(left: MathClass, right: MathClass, style: MathStyle) -> Em
|
|||||||
(Relation, _) | (_, Relation) if !script => THICK,
|
(Relation, _) | (_, Relation) if !script => THICK,
|
||||||
|
|
||||||
// Medium spacing around binary operators, unless in script size.
|
// Medium spacing around binary operators, unless in script size.
|
||||||
(Vary | Binary, _) | (_, Vary | Binary) if !script => MEDIUM,
|
(Binary, _) | (_, Binary) if !script => MEDIUM,
|
||||||
|
|
||||||
// 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) => ZERO,
|
||||||
(Large, _) | (_, Large) => THIN,
|
(Large, _) | (_, Large) => THIN,
|
||||||
|
|
||||||
|
// Spacing around spaced frames.
|
||||||
|
_ if space && (is_spaced(left) || is_spaced(right)) => space_width,
|
||||||
|
|
||||||
_ => ZERO,
|
_ => 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -178,6 +178,7 @@ fn assemble(
|
|||||||
id: None,
|
id: None,
|
||||||
frame,
|
frame,
|
||||||
italics_correction: Abs::zero(),
|
italics_correction: Abs::zero(),
|
||||||
|
class: base.class,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +209,12 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
|
|||||||
SyntaxKind::Unary => None,
|
SyntaxKind::Unary => None,
|
||||||
SyntaxKind::Binary => None,
|
SyntaxKind::Binary => None,
|
||||||
SyntaxKind::FieldAccess => match node.parent_kind() {
|
SyntaxKind::FieldAccess => match node.parent_kind() {
|
||||||
Some(SyntaxKind::Markup | SyntaxKind::Math) => Some(Category::Interpolated),
|
Some(
|
||||||
|
SyntaxKind::Markup
|
||||||
|
| SyntaxKind::Math
|
||||||
|
| SyntaxKind::MathFrac
|
||||||
|
| SyntaxKind::MathScript,
|
||||||
|
) => Some(Category::Interpolated),
|
||||||
Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight),
|
Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
|
@ -9,7 +9,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Func, Label,
|
methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Func, Label,
|
||||||
LangItems, Module, Recipe, Scopes, Selector, StyleMap, Transform, Value,
|
LangItems, Module, Recipe, Scopes, Selector, StyleMap, Symbol, Transform, Value,
|
||||||
};
|
};
|
||||||
use crate::diag::{
|
use crate::diag::{
|
||||||
bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint,
|
bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint,
|
||||||
@ -421,9 +421,7 @@ impl Eval for ast::Escape {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
// This can be in markup and math, going through a string ensure
|
Ok(Value::Symbol(Symbol::new(self.get())))
|
||||||
// that either text or atom is picked.
|
|
||||||
Ok(Value::Str(self.get().into()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,9 +429,7 @@ impl Eval for ast::Shorthand {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
// This can be in markup and math, going through a string ensure
|
Ok(Value::Symbol(Symbol::new(self.get())))
|
||||||
// that either text or atom is picked.
|
|
||||||
Ok(Value::Str(self.get().into()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +151,6 @@ impl Value {
|
|||||||
Self::Int(v) => item!(math_atom)(format_eco!("{}", v)),
|
Self::Int(v) => item!(math_atom)(format_eco!("{}", v)),
|
||||||
Self::Float(v) => item!(math_atom)(format_eco!("{}", v)),
|
Self::Float(v) => item!(math_atom)(format_eco!("{}", v)),
|
||||||
Self::Symbol(v) => item!(math_atom)(v.get().into()),
|
Self::Symbol(v) => item!(math_atom)(v.get().into()),
|
||||||
Self::Str(v) => item!(math_atom)(v.into()),
|
|
||||||
_ => self.display(),
|
_ => self.display(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -308,7 +308,12 @@ fn math_delimited(p: &mut Parser, stop: MathClass) {
|
|||||||
p.eat();
|
p.eat();
|
||||||
let m2 = p.marker();
|
let m2 = p.marker();
|
||||||
while !p.eof() && !p.at(SyntaxKind::Dollar) {
|
while !p.eof() && !p.at(SyntaxKind::Dollar) {
|
||||||
if math_class(p.current_text()) == Some(stop) {
|
let class = math_class(p.current_text());
|
||||||
|
if stop == MathClass::Fence && class == Some(MathClass::Closing) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if class == Some(stop) {
|
||||||
p.wrap(m2, SyntaxKind::Math);
|
p.wrap(m2, SyntaxKind::Math);
|
||||||
p.eat();
|
p.eat();
|
||||||
p.wrap(m, SyntaxKind::MathDelimited);
|
p.wrap(m, SyntaxKind::MathDelimited);
|
||||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
@ -12,8 +12,8 @@
|
|||||||
```
|
```
|
||||||
Let $x in NN$ be ...
|
Let $x in NN$ be ...
|
||||||
$ (1 + x/2)^2 $
|
$ (1 + x/2)^2 $
|
||||||
$ x arrow:l y $
|
$ x arrow.l y $
|
||||||
$ sum_(n=1)^mu 1 + (2pi (5 + n)) / k $
|
$ sum_(n=1)^mu 1 + (2pi(5 + n)) / k $
|
||||||
$ { x in RR | x "is natural" and x < 10 } $
|
$ { x in RR | x "is natural" and x < 10 } $
|
||||||
$ sqrt(x^2) = frac(x, 1) $
|
$ sqrt(x^2) = frac(x, 1) $
|
||||||
$ "profit" = "income" - "expenses" $
|
$ "profit" = "income" - "expenses" $
|
||||||
|