mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Rework math attachments and accents
This commit is contained in:
parent
23238d4d44
commit
28c554ec21
@ -194,7 +194,10 @@ fn items() -> LangItems {
|
|||||||
math_atom: |atom| math::AtomNode(atom).pack(),
|
math_atom: |atom| math::AtomNode(atom).pack(),
|
||||||
math_align_point: || math::AlignPointNode.pack(),
|
math_align_point: || math::AlignPointNode.pack(),
|
||||||
math_delimited: |open, body, close| math::LrNode(open + body + close).pack(),
|
math_delimited: |open, body, close| math::LrNode(open + body + close).pack(),
|
||||||
math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(),
|
math_attach: |base, sub, sup| {
|
||||||
|
math::AttachNode { base, top: sub, bottom: sup }.pack()
|
||||||
|
},
|
||||||
|
math_accent: |base, accent| math::AccentNode { base, accent }.pack(),
|
||||||
math_frac: |num, denom| math::FracNode { num, denom }.pack(),
|
math_frac: |num, denom| math::FracNode { num, denom }.pack(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
|
use typst::model::combining_accent;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// How much the accent can be shorter than the base.
|
/// How much the accent can be shorter than the base.
|
||||||
const ACCENT_SHORT_FALL: Em = Em::new(0.5);
|
const ACCENT_SHORT_FALL: Em = Em::new(0.5);
|
||||||
|
|
||||||
/// # Accent
|
/// # Accent
|
||||||
/// An accented node.
|
/// Attach an accent to a base.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// $accent(a, ->) != accent(a, ~)$ \
|
/// $arrow(a) = accent(a, arrow)$ \
|
||||||
/// $accent(a, `) = accent(a, grave)$
|
/// $grave(a) = accent(a, `)$
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
@ -19,30 +21,26 @@ const ACCENT_SHORT_FALL: Em = Em::new(0.5);
|
|||||||
///
|
///
|
||||||
/// ### Example
|
/// ### Example
|
||||||
/// ```
|
/// ```
|
||||||
/// $accent(A B C, ->)$
|
/// $arrow(A B C)$
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// - accent: Content (positional, required)
|
/// - accent: char (positional, required)
|
||||||
/// The accent to apply to the base.
|
/// The accent to apply to the base.
|
||||||
///
|
///
|
||||||
/// Supported accents include:
|
/// Supported accents include:
|
||||||
/// - Plus: `` + ``
|
/// - Grave: `grave`, `` ` ``
|
||||||
/// - Overline: `` - ``, `‾`
|
/// - Acute: `acute`, `´`
|
||||||
/// - Dot: `.`
|
/// - Circumflex: `circum`, `^`
|
||||||
/// - Circumflex: `^`
|
/// - Tilde: `tilde`, `~`
|
||||||
/// - Acute: `´`
|
/// - Macron: `macron`, `¯`
|
||||||
/// - Low Line: `_`
|
/// - Breve: `breve`, `˘`
|
||||||
/// - Grave: `` ` ``
|
/// - Dot: `dot`, `.`
|
||||||
/// - Tilde: `~`
|
/// - Diaeresis: `diaer` `¨`
|
||||||
/// - Diaeresis: `¨`
|
/// - Circle: `circle`, `∘`
|
||||||
/// - Macron: `¯`
|
/// - Double acute: `acute.double`, `˝`
|
||||||
/// - Acute: `´`
|
/// - Caron: `caron`, `ˇ`
|
||||||
/// - Cedilla: `¸`
|
/// - Right arrow: `arrow`, `->`
|
||||||
/// - Caron: `ˇ`
|
/// - Left arrow: `arrow.l`, `<-`
|
||||||
/// - Breve: `˘`
|
|
||||||
/// - Double acute: `˝`
|
|
||||||
/// - Left arrow: `<-`
|
|
||||||
/// - Right arrow: `->`
|
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// ## Category
|
||||||
/// math
|
/// math
|
||||||
@ -53,7 +51,7 @@ pub struct AccentNode {
|
|||||||
/// The accent base.
|
/// The accent base.
|
||||||
pub base: Content,
|
pub base: Content,
|
||||||
/// The accent.
|
/// The accent.
|
||||||
pub accent: Content,
|
pub accent: char,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
@ -78,16 +76,9 @@ impl LayoutMath for AccentNode {
|
|||||||
_ => (base.width() + base.italics_correction()) / 2.0,
|
_ => (base.width() + base.italics_correction()) / 2.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(c) = extract(&self.accent) else {
|
|
||||||
ctx.push(base);
|
|
||||||
if let Some(span) = self.accent.span() {
|
|
||||||
bail!(span, "not an accent");
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
// Forcing the accent to be at least as large as the base makes it too
|
// Forcing the accent to be at least as large as the base makes it too
|
||||||
// wide in many case.
|
// wide in many case.
|
||||||
|
let c = combining_accent(self.accent).unwrap_or(self.accent);
|
||||||
let glyph = GlyphFragment::new(ctx, c);
|
let glyph = GlyphFragment::new(ctx, c);
|
||||||
let short_fall = ACCENT_SHORT_FALL.scaled(ctx);
|
let short_fall = ACCENT_SHORT_FALL.scaled(ctx);
|
||||||
let variant = glyph.stretch_horizontal(ctx, base.width(), short_fall);
|
let variant = glyph.stretch_horizontal(ctx, base.width(), short_fall);
|
||||||
@ -130,45 +121,3 @@ fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs {
|
|||||||
(advance.scaled(ctx) + italics_correction) / 2.0
|
(advance.scaled(ctx) + italics_correction) / 2.0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract a single character from content.
|
|
||||||
fn extract(accent: &Content) -> Option<char> {
|
|
||||||
let atom = accent.to::<AtomNode>()?;
|
|
||||||
let mut chars = atom.0.chars();
|
|
||||||
let c = chars.next().filter(|_| chars.next().is_none())?;
|
|
||||||
Some(combining(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert from a non-combining accent to a combining one.
|
|
||||||
///
|
|
||||||
/// https://www.w3.org/TR/mathml-core/#combining-character-equivalences
|
|
||||||
fn combining(c: char) -> char {
|
|
||||||
match c {
|
|
||||||
'\u{002b}' => '\u{031f}',
|
|
||||||
'\u{002d}' => '\u{0305}',
|
|
||||||
'\u{002e}' => '\u{0307}',
|
|
||||||
'\u{005e}' => '\u{0302}',
|
|
||||||
'\u{005f}' => '\u{0332}',
|
|
||||||
'\u{0060}' => '\u{0300}',
|
|
||||||
'\u{007e}' => '\u{0303}',
|
|
||||||
'\u{00a8}' => '\u{0308}',
|
|
||||||
'\u{00af}' => '\u{0304}',
|
|
||||||
'\u{00b4}' => '\u{0301}',
|
|
||||||
'\u{00b8}' => '\u{0327}',
|
|
||||||
'\u{02c6}' => '\u{0302}',
|
|
||||||
'\u{02c7}' => '\u{030c}',
|
|
||||||
'\u{02d8}' => '\u{0306}',
|
|
||||||
'\u{02d9}' => '\u{0307}',
|
|
||||||
'\u{02db}' => '\u{0328}',
|
|
||||||
'\u{02dc}' => '\u{0303}',
|
|
||||||
'\u{02dd}' => '\u{030b}',
|
|
||||||
'\u{203e}' => '\u{0305}',
|
|
||||||
'\u{2190}' => '\u{20d6}',
|
|
||||||
'\u{2192}' => '\u{20d7}',
|
|
||||||
'\u{2212}' => '\u{0305}',
|
|
||||||
'\u{223C}' => '\u{0303}',
|
|
||||||
'\u{22C5}' => '\u{0307}',
|
|
||||||
'\u{27f6}' => '\u{20d7}',
|
|
||||||
_ => c,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,86 +1,153 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// # Script
|
/// # Attachment
|
||||||
/// A mathematical sub- and/or superscript.
|
/// A base with optional attachments.
|
||||||
///
|
///
|
||||||
/// ## Syntax
|
/// ## Syntax
|
||||||
/// This function also has dedicated syntax: Use the underscore (`_`) to
|
/// This function also has dedicated syntax: Use the underscore (`_`) to
|
||||||
/// indicate a subscript and the circumflex (`^`) to indicate a superscript.
|
/// indicate a bottom attachment and the circumflex (`^`) to indicate a top
|
||||||
|
/// attachment.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```
|
/// ```
|
||||||
/// $ a_i = 2^(1+i) $
|
/// $ sum_(i=0)^n a_i = 2^(1+i) $
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - base: Content (positional, required)
|
/// - base: Content (positional, required)
|
||||||
/// The base to which the applies the sub- and/or superscript.
|
/// The base to which things are attached.
|
||||||
///
|
///
|
||||||
/// - sub: Content (named)
|
/// - top: Content (named)
|
||||||
/// The subscript.
|
/// The top attachment.
|
||||||
///
|
///
|
||||||
/// - sup: Content (named)
|
/// - bottom: Content (named)
|
||||||
/// The superscript.
|
/// The bottom attachment.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// ## Category
|
||||||
/// math
|
/// math
|
||||||
#[func]
|
#[func]
|
||||||
#[capable(LayoutMath)]
|
#[capable(LayoutMath)]
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ScriptNode {
|
pub struct AttachNode {
|
||||||
/// The base.
|
/// The base.
|
||||||
pub base: Content,
|
pub base: Content,
|
||||||
/// The subscript.
|
/// The top attachment.
|
||||||
pub sub: Option<Content>,
|
pub top: Option<Content>,
|
||||||
/// The superscript.
|
/// The bottom attachment.
|
||||||
pub sup: Option<Content>,
|
pub bottom: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl ScriptNode {
|
impl AttachNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let base = args.expect("base")?;
|
let base = args.expect("base")?;
|
||||||
let sub = args.named("sub")?;
|
let top = args.named("top")?;
|
||||||
let sup = args.named("sup")?;
|
let bottom = args.named("bottom")?;
|
||||||
Ok(Self { base, sub, sup }.pack())
|
Ok(Self { base, top, bottom }.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for ScriptNode {
|
impl LayoutMath for AttachNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
let base = ctx.layout_fragment(&self.base)?;
|
let base = ctx.layout_fragment(&self.base)?;
|
||||||
|
|
||||||
let mut sub = Frame::new(Size::zero());
|
let mut sub = Frame::new(Size::zero());
|
||||||
if let Some(node) = &self.sub {
|
if let Some(node) = &self.top {
|
||||||
ctx.style(ctx.style.for_subscript());
|
ctx.style(ctx.style.for_subscript());
|
||||||
sub = ctx.layout_frame(node)?;
|
sub = ctx.layout_frame(node)?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sup = Frame::new(Size::zero());
|
let mut sup = Frame::new(Size::zero());
|
||||||
if let Some(node) = &self.sup {
|
if let Some(node) = &self.bottom {
|
||||||
ctx.style(ctx.style.for_superscript());
|
ctx.style(ctx.style.for_superscript());
|
||||||
sup = ctx.layout_frame(node)?;
|
sup = ctx.layout_frame(node)?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
let render_limits = ctx.style.size == MathSize::Display
|
let render_limits = self.base.is::<LimitsNode>()
|
||||||
&& base.class() == Some(MathClass::Large)
|
|| (!self.base.is::<ScriptsNode>()
|
||||||
&& match &base {
|
&& ctx.style.size == MathSize::Display
|
||||||
MathFragment::Variant(variant) => LIMITS.contains(&variant.c),
|
&& base.class() == Some(MathClass::Large)
|
||||||
MathFragment::Frame(fragment) => fragment.limits,
|
&& match &base {
|
||||||
_ => false,
|
MathFragment::Variant(variant) => LIMITS.contains(&variant.c),
|
||||||
};
|
MathFragment::Frame(fragment) => fragment.limits,
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
|
||||||
if render_limits {
|
if render_limits {
|
||||||
limits(ctx, base, sub, sup)
|
limits(ctx, base, sub, sup)
|
||||||
} else {
|
} else {
|
||||||
scripts(ctx, base, sub, sup, self.sub.is_some() && self.sup.is_some())
|
scripts(ctx, base, sub, sup, self.top.is_some() && self.bottom.is_some())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout normal sub- and superscripts.
|
/// # Scripts
|
||||||
|
/// Force a base to display attachments as scripts.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// $ scripts(sum)_1^2 != sum_1^2 $
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - base: Content (positional, required)
|
||||||
|
/// The base to attach the scripts to.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// math
|
||||||
|
#[func]
|
||||||
|
#[capable(LayoutMath)]
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct ScriptsNode(Content);
|
||||||
|
|
||||||
|
#[node]
|
||||||
|
impl ScriptsNode {
|
||||||
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
|
Ok(Self(args.expect("base")?).pack())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutMath for ScriptsNode {
|
||||||
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
|
self.0.layout_math(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Limits
|
||||||
|
/// Force a base to display attachments as limits.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// $ limits(A)_1^2 != A_1^2 $
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - base: Content (positional, required)
|
||||||
|
/// The base to attach the limits to.
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// math
|
||||||
|
#[func]
|
||||||
|
#[capable(LayoutMath)]
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct LimitsNode(Content);
|
||||||
|
|
||||||
|
#[node]
|
||||||
|
impl LimitsNode {
|
||||||
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
|
Ok(Self(args.expect("base")?).pack())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutMath for LimitsNode {
|
||||||
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
|
self.0.layout_math(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout sub- and superscripts.
|
||||||
fn scripts(
|
fn scripts(
|
||||||
ctx: &mut MathContext,
|
ctx: &mut MathContext,
|
||||||
base: MathFragment,
|
base: MathFragment,
|
@ -5,6 +5,7 @@ mod ctx;
|
|||||||
mod accent;
|
mod accent;
|
||||||
mod align;
|
mod align;
|
||||||
mod atom;
|
mod atom;
|
||||||
|
mod attach;
|
||||||
mod braced;
|
mod braced;
|
||||||
mod frac;
|
mod frac;
|
||||||
mod fragment;
|
mod fragment;
|
||||||
@ -13,7 +14,6 @@ mod matrix;
|
|||||||
mod op;
|
mod op;
|
||||||
mod root;
|
mod root;
|
||||||
mod row;
|
mod row;
|
||||||
mod script;
|
|
||||||
mod spacing;
|
mod spacing;
|
||||||
mod stretch;
|
mod stretch;
|
||||||
mod style;
|
mod style;
|
||||||
@ -22,13 +22,13 @@ mod symbols;
|
|||||||
pub use self::accent::*;
|
pub use self::accent::*;
|
||||||
pub use self::align::*;
|
pub use self::align::*;
|
||||||
pub use self::atom::*;
|
pub use self::atom::*;
|
||||||
|
pub use self::attach::*;
|
||||||
pub use self::braced::*;
|
pub use self::braced::*;
|
||||||
pub use self::frac::*;
|
pub use self::frac::*;
|
||||||
pub use self::lr::*;
|
pub use self::lr::*;
|
||||||
pub use self::matrix::*;
|
pub use self::matrix::*;
|
||||||
pub use self::op::*;
|
pub use self::op::*;
|
||||||
pub use self::root::*;
|
pub use self::root::*;
|
||||||
pub use self::script::*;
|
|
||||||
pub use self::style::*;
|
pub use self::style::*;
|
||||||
|
|
||||||
use ttf_parser::GlyphId;
|
use ttf_parser::GlyphId;
|
||||||
@ -54,22 +54,33 @@ use crate::text::{families, variant, FallbackList, FontFamily, SpaceNode};
|
|||||||
pub fn module(sym: &Module) -> Module {
|
pub fn module(sym: &Module) -> Module {
|
||||||
let mut math = Scope::deduplicating();
|
let mut math = Scope::deduplicating();
|
||||||
math.def_func::<FormulaNode>("formula");
|
math.def_func::<FormulaNode>("formula");
|
||||||
|
|
||||||
|
// Grouping.
|
||||||
math.def_func::<LrNode>("lr");
|
math.def_func::<LrNode>("lr");
|
||||||
math.def_func::<OpNode>("op");
|
|
||||||
math.def_func::<FloorFunc>("floor");
|
|
||||||
math.def_func::<CeilFunc>("ceil");
|
|
||||||
math.def_func::<AbsFunc>("abs");
|
math.def_func::<AbsFunc>("abs");
|
||||||
math.def_func::<NormFunc>("norm");
|
math.def_func::<NormFunc>("norm");
|
||||||
|
math.def_func::<FloorFunc>("floor");
|
||||||
|
math.def_func::<CeilFunc>("ceil");
|
||||||
|
|
||||||
|
// Attachments and accents.
|
||||||
|
math.def_func::<AttachNode>("attach");
|
||||||
|
math.def_func::<ScriptsNode>("scripts");
|
||||||
|
math.def_func::<LimitsNode>("limits");
|
||||||
math.def_func::<AccentNode>("accent");
|
math.def_func::<AccentNode>("accent");
|
||||||
math.def_func::<FracNode>("frac");
|
|
||||||
math.def_func::<BinomNode>("binom");
|
|
||||||
math.def_func::<ScriptNode>("script");
|
|
||||||
math.def_func::<SqrtNode>("sqrt");
|
|
||||||
math.def_func::<RootNode>("root");
|
|
||||||
math.def_func::<VecNode>("vec");
|
|
||||||
math.def_func::<CasesNode>("cases");
|
|
||||||
math.def_func::<UnderbraceNode>("underbrace");
|
math.def_func::<UnderbraceNode>("underbrace");
|
||||||
math.def_func::<OverbraceNode>("overbrace");
|
math.def_func::<OverbraceNode>("overbrace");
|
||||||
|
|
||||||
|
// Fractions and matrix-likes.
|
||||||
|
math.def_func::<FracNode>("frac");
|
||||||
|
math.def_func::<BinomNode>("binom");
|
||||||
|
math.def_func::<VecNode>("vec");
|
||||||
|
math.def_func::<CasesNode>("cases");
|
||||||
|
|
||||||
|
// Roots.
|
||||||
|
math.def_func::<SqrtNode>("sqrt");
|
||||||
|
math.def_func::<RootNode>("root");
|
||||||
|
|
||||||
|
// Styles.
|
||||||
math.def_func::<BoldNode>("bold");
|
math.def_func::<BoldNode>("bold");
|
||||||
math.def_func::<ItalicNode>("italic");
|
math.def_func::<ItalicNode>("italic");
|
||||||
math.def_func::<SerifNode>("serif");
|
math.def_func::<SerifNode>("serif");
|
||||||
@ -78,10 +89,16 @@ pub fn module(sym: &Module) -> Module {
|
|||||||
math.def_func::<FrakNode>("frak");
|
math.def_func::<FrakNode>("frak");
|
||||||
math.def_func::<MonoNode>("mono");
|
math.def_func::<MonoNode>("mono");
|
||||||
math.def_func::<BbNode>("bb");
|
math.def_func::<BbNode>("bb");
|
||||||
spacing::define(&mut math);
|
|
||||||
symbols::define(&mut math);
|
// Text operators.
|
||||||
|
math.def_func::<OpNode>("op");
|
||||||
op::define(&mut math);
|
op::define(&mut math);
|
||||||
|
|
||||||
|
// Symbols and spacing.
|
||||||
|
symbols::define(&mut math);
|
||||||
|
spacing::define(&mut math);
|
||||||
math.copy_from(sym.scope());
|
math.copy_from(sym.scope());
|
||||||
|
|
||||||
Module::new("math").with_scope(math)
|
Module::new("math").with_scope(math)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@ use super::*;
|
|||||||
/// - text: EcoString (positional, required)
|
/// - text: EcoString (positional, required)
|
||||||
/// The operator's text.
|
/// The operator's text.
|
||||||
/// - limits: bool (named)
|
/// - limits: bool (named)
|
||||||
/// Whether the operator should display sub- and superscripts as limits.
|
/// Whether the operator should force attachments to display as limits.
|
||||||
///
|
///
|
||||||
/// Defaults to `true`.
|
/// Defaults to `false`.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// ## Category
|
||||||
/// math
|
/// math
|
||||||
@ -21,7 +21,7 @@ use super::*;
|
|||||||
pub struct OpNode {
|
pub struct OpNode {
|
||||||
/// The operator's text.
|
/// The operator's text.
|
||||||
pub text: EcoString,
|
pub text: EcoString,
|
||||||
/// Whether the operator should display sub- and superscripts as limits.
|
/// Whether the operator should force attachments to display as limits.
|
||||||
pub limits: bool,
|
pub limits: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ impl OpNode {
|
|||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
text: args.expect("text")?,
|
text: args.expect("text")?,
|
||||||
limits: args.named("limits")?.unwrap_or(true),
|
limits: args.named("limits")?.unwrap_or(false),
|
||||||
}
|
}
|
||||||
.pack())
|
.pack())
|
||||||
}
|
}
|
||||||
|
@ -216,7 +216,7 @@ fn complete_math(ctx: &mut CompletionContext) -> bool {
|
|||||||
Some(SyntaxKind::Formula)
|
Some(SyntaxKind::Formula)
|
||||||
| Some(SyntaxKind::Math)
|
| Some(SyntaxKind::Math)
|
||||||
| Some(SyntaxKind::MathFrac)
|
| Some(SyntaxKind::MathFrac)
|
||||||
| Some(SyntaxKind::MathScript)
|
| Some(SyntaxKind::MathAttach)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -584,7 +584,7 @@ fn complete_code(ctx: &mut CompletionContext) -> bool {
|
|||||||
None | Some(SyntaxKind::Markup)
|
None | Some(SyntaxKind::Markup)
|
||||||
| Some(SyntaxKind::Math)
|
| Some(SyntaxKind::Math)
|
||||||
| Some(SyntaxKind::MathFrac)
|
| Some(SyntaxKind::MathFrac)
|
||||||
| Some(SyntaxKind::MathScript)
|
| Some(SyntaxKind::MathAttach)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -955,7 +955,7 @@ impl<'a> CompletionContext<'a> {
|
|||||||
Some(SyntaxKind::Formula)
|
Some(SyntaxKind::Formula)
|
||||||
| Some(SyntaxKind::Math)
|
| Some(SyntaxKind::Math)
|
||||||
| Some(SyntaxKind::MathFrac)
|
| Some(SyntaxKind::MathFrac)
|
||||||
| Some(SyntaxKind::MathScript)
|
| Some(SyntaxKind::MathAttach)
|
||||||
);
|
);
|
||||||
|
|
||||||
let scope = if in_math { self.math } else { self.global };
|
let scope = if in_math { self.math } else { self.global };
|
||||||
|
@ -118,7 +118,7 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
|
|||||||
SyntaxKind::MathAtom => None,
|
SyntaxKind::MathAtom => None,
|
||||||
SyntaxKind::MathIdent => highlight_ident(node),
|
SyntaxKind::MathIdent => highlight_ident(node),
|
||||||
SyntaxKind::MathDelimited => None,
|
SyntaxKind::MathDelimited => None,
|
||||||
SyntaxKind::MathScript => None,
|
SyntaxKind::MathAttach => None,
|
||||||
SyntaxKind::MathFrac => None,
|
SyntaxKind::MathFrac => None,
|
||||||
SyntaxKind::MathAlignPoint => Some(Category::MathOperator),
|
SyntaxKind::MathAlignPoint => Some(Category::MathOperator),
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
|
|||||||
_ => Some(Category::Operator),
|
_ => Some(Category::Operator),
|
||||||
},
|
},
|
||||||
SyntaxKind::Underscore => match node.parent_kind() {
|
SyntaxKind::Underscore => match node.parent_kind() {
|
||||||
Some(SyntaxKind::MathScript) => Some(Category::MathOperator),
|
Some(SyntaxKind::MathAttach) => Some(Category::MathOperator),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
SyntaxKind::Dollar => Some(Category::MathDelimiter),
|
SyntaxKind::Dollar => Some(Category::MathDelimiter),
|
||||||
@ -213,7 +213,7 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
|
|||||||
SyntaxKind::Markup
|
SyntaxKind::Markup
|
||||||
| SyntaxKind::Math
|
| SyntaxKind::Math
|
||||||
| SyntaxKind::MathFrac
|
| SyntaxKind::MathFrac
|
||||||
| SyntaxKind::MathScript,
|
| SyntaxKind::MathAttach,
|
||||||
) => Some(Category::Interpolated),
|
) => Some(Category::Interpolated),
|
||||||
Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight),
|
Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -252,7 +252,7 @@ fn highlight_ident(node: &LinkedNode) -> Option<Category> {
|
|||||||
SyntaxKind::Markup
|
SyntaxKind::Markup
|
||||||
| SyntaxKind::Math
|
| SyntaxKind::Math
|
||||||
| SyntaxKind::MathFrac
|
| SyntaxKind::MathFrac
|
||||||
| SyntaxKind::MathScript,
|
| SyntaxKind::MathAttach,
|
||||||
) => Some(Category::Interpolated),
|
) => Some(Category::Interpolated),
|
||||||
Some(SyntaxKind::FuncCall) => Some(Category::Function),
|
Some(SyntaxKind::FuncCall) => Some(Category::Function),
|
||||||
Some(SyntaxKind::FieldAccess)
|
Some(SyntaxKind::FieldAccess)
|
||||||
|
@ -232,6 +232,17 @@ castable! {
|
|||||||
color: Color => Self::Solid(color),
|
color: Color => Self::Solid(color),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
char,
|
||||||
|
string: Str => {
|
||||||
|
let mut chars = string.chars();
|
||||||
|
match (chars.next(), chars.next()) {
|
||||||
|
(Some(c), None) => c,
|
||||||
|
_ => Err("expected exactly one character")?,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
EcoString,
|
EcoString,
|
||||||
string: Str => string.into(),
|
string: Str => string.into(),
|
||||||
|
@ -8,8 +8,9 @@ use comemo::{Track, Tracked, TrackedMut};
|
|||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Func, Label,
|
combining_accent, methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content,
|
||||||
LangItems, Module, Recipe, Scopes, Selector, StyleMap, Symbol, Transform, Value,
|
Dict, Func, Label, 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,
|
||||||
@ -347,7 +348,7 @@ impl Eval for ast::Expr {
|
|||||||
Self::MathIdent(v) => v.eval(vm),
|
Self::MathIdent(v) => v.eval(vm),
|
||||||
Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content),
|
Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::MathDelimited(v) => v.eval(vm).map(Value::Content),
|
Self::MathDelimited(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::MathScript(v) => v.eval(vm).map(Value::Content),
|
Self::MathAttach(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::MathFrac(v) => v.eval(vm).map(Value::Content),
|
Self::MathFrac(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::Ident(v) => v.eval(vm),
|
Self::Ident(v) => v.eval(vm),
|
||||||
Self::None(v) => v.eval(vm),
|
Self::None(v) => v.eval(vm),
|
||||||
@ -593,20 +594,20 @@ impl Eval for ast::MathDelimited {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eval for ast::MathScript {
|
impl Eval for ast::MathAttach {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
let base = self.base().eval(vm)?.display_in_math();
|
let base = self.base().eval(vm)?.display_in_math();
|
||||||
let sub = self
|
let sub = self
|
||||||
.sub()
|
.bottom()
|
||||||
.map(|expr| expr.eval(vm).map(Value::display_in_math))
|
.map(|expr| expr.eval(vm).map(Value::display_in_math))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let sup = self
|
let sup = self
|
||||||
.sup()
|
.top()
|
||||||
.map(|expr| expr.eval(vm).map(Value::display_in_math))
|
.map(|expr| expr.eval(vm).map(Value::display_in_math))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
Ok((vm.items.math_script)(base, sub, sup))
|
Ok((vm.items.math_attach)(base, sub, sup))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -929,13 +930,21 @@ impl Eval for ast::FuncCall {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
let callee = self.callee();
|
let callee_expr = self.callee();
|
||||||
let callee_span = callee.span();
|
let callee_span = callee_expr.span();
|
||||||
let in_math = matches!(callee, ast::Expr::MathIdent(_));
|
let callee = callee_expr.eval(vm)?;
|
||||||
let callee = callee.eval(vm)?;
|
|
||||||
let mut args = self.args().eval(vm)?;
|
let mut args = self.args().eval(vm)?;
|
||||||
|
|
||||||
if in_math && !matches!(callee, Value::Func(_)) {
|
if in_math(&callee_expr) && !matches!(callee, Value::Func(_)) {
|
||||||
|
if let Value::Symbol(sym) = &callee {
|
||||||
|
let c = sym.get();
|
||||||
|
if let Some(accent) = combining_accent(c) {
|
||||||
|
let base = args.expect("base")?;
|
||||||
|
args.finish()?;
|
||||||
|
return Ok(Value::Content((vm.items.math_accent)(base, accent)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut body = (vm.items.math_atom)('('.into());
|
let mut body = (vm.items.math_atom)('('.into());
|
||||||
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
|
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
@ -952,6 +961,14 @@ impl Eval for ast::FuncCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn in_math(expr: &ast::Expr) -> bool {
|
||||||
|
match expr {
|
||||||
|
ast::Expr::MathIdent(_) => true,
|
||||||
|
ast::Expr::FieldAccess(access) => in_math(&access.target()),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn complete_call(
|
fn complete_call(
|
||||||
vm: &mut Vm,
|
vm: &mut Vm,
|
||||||
callee: &Func,
|
callee: &Func,
|
||||||
|
@ -71,12 +71,13 @@ pub struct LangItems {
|
|||||||
pub math_atom: fn(atom: EcoString) -> Content,
|
pub math_atom: fn(atom: EcoString) -> Content,
|
||||||
/// An alignment point in a formula: `&`.
|
/// An alignment point in a formula: `&`.
|
||||||
pub math_align_point: fn() -> Content,
|
pub math_align_point: fn() -> Content,
|
||||||
/// A subsection in a math formula that is surrounded by matched delimiters:
|
/// Matched delimiters surrounding math in a formula: `[x + y]`.
|
||||||
/// `[x + y]`.
|
|
||||||
pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content,
|
pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content,
|
||||||
/// A base with optional sub- and superscripts in a formula: `a_1^2`.
|
/// A base with optional attachments in a formula: `a_1^2`.
|
||||||
pub math_script:
|
pub math_attach:
|
||||||
fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content,
|
fn(base: Content, bottom: Option<Content>, top: Option<Content>) -> Content,
|
||||||
|
/// A base with an accent: `arrow(x)`.
|
||||||
|
pub math_accent: fn(base: Content, accent: char) -> Content,
|
||||||
/// A fraction in a formula: `x/2`.
|
/// A fraction in a formula: `x/2`.
|
||||||
pub math_frac: fn(num: Content, denom: Content) -> Content,
|
pub math_frac: fn(num: Content, denom: Content) -> Content,
|
||||||
}
|
}
|
||||||
@ -95,6 +96,8 @@ impl Hash for LangItems {
|
|||||||
self.space.hash(state);
|
self.space.hash(state);
|
||||||
self.linebreak.hash(state);
|
self.linebreak.hash(state);
|
||||||
self.text.hash(state);
|
self.text.hash(state);
|
||||||
|
self.text_id.hash(state);
|
||||||
|
(self.text_str as usize).hash(state);
|
||||||
self.smart_quote.hash(state);
|
self.smart_quote.hash(state);
|
||||||
self.parbreak.hash(state);
|
self.parbreak.hash(state);
|
||||||
self.strong.hash(state);
|
self.strong.hash(state);
|
||||||
@ -108,9 +111,11 @@ impl Hash for LangItems {
|
|||||||
self.term_item.hash(state);
|
self.term_item.hash(state);
|
||||||
self.formula.hash(state);
|
self.formula.hash(state);
|
||||||
self.math_atom.hash(state);
|
self.math_atom.hash(state);
|
||||||
self.math_script.hash(state);
|
|
||||||
self.math_frac.hash(state);
|
|
||||||
self.math_align_point.hash(state);
|
self.math_align_point.hash(state);
|
||||||
|
self.math_delimited.hash(state);
|
||||||
|
self.math_attach.hash(state);
|
||||||
|
self.math_accent.hash(state);
|
||||||
|
self.math_frac.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,3 +78,9 @@ impl Debug for Module {
|
|||||||
write!(f, "<module {}>", self.name())
|
write!(f, "<module {}>", self.name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Module {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
Arc::ptr_eq(&self.0, &other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use super::{Regex, Value};
|
use super::{format_str, Regex, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::geom::{Axes, Axis, GenAlign, Length, Numeric, PartialStroke, Rel, Smart};
|
use crate::geom::{Axes, Axis, GenAlign, Length, Numeric, PartialStroke, Rel, Smart};
|
||||||
use crate::util::format_eco;
|
use crate::util::format_eco;
|
||||||
@ -20,10 +20,15 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
|
|||||||
Ok(match (lhs, rhs) {
|
Ok(match (lhs, rhs) {
|
||||||
(a, None) => a,
|
(a, None) => a,
|
||||||
(None, b) => b,
|
(None, b) => b,
|
||||||
|
(Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")),
|
||||||
(Str(a), Str(b)) => Str(a + b),
|
(Str(a), Str(b)) => Str(a + b),
|
||||||
(Str(a), Content(b)) => Content(item!(text)(a.into()) + b),
|
(Str(a), Symbol(b)) => Str(format_str!("{a}{b}")),
|
||||||
(Content(a), Str(b)) => Content(a + item!(text)(b.into())),
|
(Symbol(a), Str(b)) => Str(format_str!("{a}{b}")),
|
||||||
(Content(a), Content(b)) => Content(a + b),
|
(Content(a), Content(b)) => Content(a + b),
|
||||||
|
(Content(a), Symbol(b)) => Content(a + item!(text)(b.get().into())),
|
||||||
|
(Content(a), Str(b)) => Content(a + item!(text)(b.into())),
|
||||||
|
(Str(a), Content(b)) => Content(item!(text)(a.into()) + b),
|
||||||
|
(Symbol(a), Content(b)) => Content(item!(text)(a.get().into()) + b),
|
||||||
(Array(a), Array(b)) => Array(a + b),
|
(Array(a), Array(b)) => Array(a + b),
|
||||||
(Dict(a), Dict(b)) => Dict(a + b),
|
(Dict(a), Dict(b)) => Dict(a + b),
|
||||||
(a, b) => mismatch!("cannot join {} with {}", a, b),
|
(a, b) => mismatch!("cannot join {} with {}", a, b),
|
||||||
@ -85,10 +90,15 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
|||||||
|
|
||||||
(Fraction(a), Fraction(b)) => Fraction(a + b),
|
(Fraction(a), Fraction(b)) => Fraction(a + b),
|
||||||
|
|
||||||
|
(Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")),
|
||||||
(Str(a), Str(b)) => Str(a + b),
|
(Str(a), Str(b)) => Str(a + b),
|
||||||
|
(Str(a), Symbol(b)) => Str(format_str!("{a}{b}")),
|
||||||
|
(Symbol(a), Str(b)) => Str(format_str!("{a}{b}")),
|
||||||
(Content(a), Content(b)) => Content(a + b),
|
(Content(a), Content(b)) => Content(a + b),
|
||||||
|
(Content(a), Symbol(b)) => Content(a + item!(text)(b.get().into())),
|
||||||
(Content(a), Str(b)) => Content(a + item!(text)(b.into())),
|
(Content(a), Str(b)) => Content(a + item!(text)(b.into())),
|
||||||
(Str(a), Content(b)) => Content(item!(text)(a.into()) + b),
|
(Str(a), Content(b)) => Content(item!(text)(a.into()) + b),
|
||||||
|
(Symbol(a), Content(b)) => Content(item!(text)(a.get().into()) + b),
|
||||||
|
|
||||||
(Array(a), Array(b)) => Array(a + b),
|
(Array(a), Array(b)) => Array(a + b),
|
||||||
(Dict(a), Dict(b)) => Dict(a + b),
|
(Dict(a), Dict(b)) => Dict(a + b),
|
||||||
@ -326,11 +336,14 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
|
|||||||
(Relative(a), Relative(b)) => a == b,
|
(Relative(a), Relative(b)) => a == b,
|
||||||
(Fraction(a), Fraction(b)) => a == b,
|
(Fraction(a), Fraction(b)) => a == b,
|
||||||
(Color(a), Color(b)) => a == b,
|
(Color(a), Color(b)) => a == b,
|
||||||
|
(Symbol(a), Symbol(b)) => a == b,
|
||||||
(Str(a), Str(b)) => a == b,
|
(Str(a), Str(b)) => a == b,
|
||||||
(Label(a), Label(b)) => a == b,
|
(Label(a), Label(b)) => a == b,
|
||||||
(Array(a), Array(b)) => a == b,
|
(Array(a), Array(b)) => a == b,
|
||||||
(Dict(a), Dict(b)) => a == b,
|
(Dict(a), Dict(b)) => a == b,
|
||||||
(Func(a), Func(b)) => a == b,
|
(Func(a), Func(b)) => a == b,
|
||||||
|
(Args(a), Args(b)) => a == b,
|
||||||
|
(Module(a), Module(b)) => a == b,
|
||||||
(Dyn(a), Dyn(b)) => a == b,
|
(Dyn(a), Dyn(b)) => a == b,
|
||||||
|
|
||||||
// Some technically different things should compare equal.
|
// Some technically different things should compare equal.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use std::borrow::{Borrow, Cow};
|
use std::borrow::{Borrow, Cow};
|
||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::ops::{Add, AddAssign, Deref};
|
use std::ops::{Add, AddAssign, Deref};
|
||||||
|
|
||||||
@ -334,6 +334,12 @@ impl Deref for Str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Str {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Debug for Str {
|
impl Debug for Str {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.write_char('"')?;
|
f.write_char('"')?;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||||
|
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
@ -109,6 +109,12 @@ impl Debug for Symbol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Symbol {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.write_char(self.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Find the best symbol from the list.
|
/// Find the best symbol from the list.
|
||||||
fn find(list: &[(&str, char)], modifiers: &str) -> Option<char> {
|
fn find(list: &[(&str, char)], modifiers: &str) -> Option<char> {
|
||||||
let mut best = None;
|
let mut best = None;
|
||||||
@ -150,3 +156,30 @@ fn parts(modifiers: &str) -> impl Iterator<Item = &str> {
|
|||||||
fn contained(modifiers: &str, m: &str) -> bool {
|
fn contained(modifiers: &str, m: &str) -> bool {
|
||||||
parts(modifiers).any(|part| part == m)
|
parts(modifiers).any(|part| part == m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Normalize an accent to a combining one.
|
||||||
|
///
|
||||||
|
/// https://www.w3.org/TR/mathml-core/#combining-character-equivalences
|
||||||
|
pub fn combining_accent(c: char) -> Option<char> {
|
||||||
|
Some(match c {
|
||||||
|
'\u{0300}' | '`' => '\u{0300}',
|
||||||
|
'\u{0301}' | '´' => '\u{0301}',
|
||||||
|
'\u{0302}' | '^' | 'ˆ' => '\u{0302}',
|
||||||
|
'\u{0303}' | '~' | '∼' | '˜' => '\u{0303}',
|
||||||
|
'\u{0304}' | '¯' => '\u{0304}',
|
||||||
|
'\u{0305}' | '-' | '‾' | '−' => '\u{0305}',
|
||||||
|
'\u{0306}' | '˘' => '\u{0306}',
|
||||||
|
'\u{0307}' | '.' | '˙' | '⋅' => '\u{0307}',
|
||||||
|
'\u{0308}' | '¨' => '\u{0308}',
|
||||||
|
'\u{030a}' | '∘' | '○' => '\u{030a}',
|
||||||
|
'\u{030b}' | '˝' => '\u{030b}',
|
||||||
|
'\u{030c}' | 'ˇ' => '\u{030c}',
|
||||||
|
'\u{0327}' | '¸' => '\u{0327}',
|
||||||
|
'\u{0328}' | '˛' => '\u{0328}',
|
||||||
|
'\u{0332}' | '_' => '\u{0332}',
|
||||||
|
'\u{20d6}' | '←' => '\u{20d6}',
|
||||||
|
'\u{20d7}' | '→' | '⟶' => '\u{20d7}',
|
||||||
|
'⏞' | '⏟' | '⎴' | '⎵' | '⏜' | '⏝' | '⏠' | '⏡' => c,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -438,7 +438,11 @@ primitive! { Rel<Length>: "relative length",
|
|||||||
primitive! { Fr: "fraction", Fraction }
|
primitive! { Fr: "fraction", Fraction }
|
||||||
primitive! { Color: "color", Color }
|
primitive! { Color: "color", Color }
|
||||||
primitive! { Symbol: "symbol", Symbol }
|
primitive! { Symbol: "symbol", Symbol }
|
||||||
primitive! { Str: "string", Str }
|
primitive! {
|
||||||
|
Str: "string",
|
||||||
|
Str,
|
||||||
|
Symbol(symbol) => symbol.get().into()
|
||||||
|
}
|
||||||
primitive! { Label: "label", Label }
|
primitive! { Label: "label", Label }
|
||||||
primitive! { Content: "content",
|
primitive! { Content: "content",
|
||||||
Content,
|
Content,
|
||||||
|
@ -121,11 +121,10 @@ pub enum Expr {
|
|||||||
MathIdent(MathIdent),
|
MathIdent(MathIdent),
|
||||||
/// An alignment point in a math formula: `&`.
|
/// An alignment point in a math formula: `&`.
|
||||||
MathAlignPoint(MathAlignPoint),
|
MathAlignPoint(MathAlignPoint),
|
||||||
/// A subsection in a math formula that is surrounded by matched delimiters:
|
/// Matched delimiters surrounding math in a formula: `[x + y]`.
|
||||||
/// `[x + y]`.
|
|
||||||
MathDelimited(MathDelimited),
|
MathDelimited(MathDelimited),
|
||||||
/// A base with optional sub- and superscripts in a math formula: `a_1^2`.
|
/// A base with optional attachments in a formula: `a_1^2`.
|
||||||
MathScript(MathScript),
|
MathAttach(MathAttach),
|
||||||
/// A fraction in a math formula: `x/2`.
|
/// A fraction in a math formula: `x/2`.
|
||||||
MathFrac(MathFrac),
|
MathFrac(MathFrac),
|
||||||
/// An identifier: `left`.
|
/// An identifier: `left`.
|
||||||
@ -224,7 +223,7 @@ impl AstNode for Expr {
|
|||||||
SyntaxKind::MathIdent => node.cast().map(Self::MathIdent),
|
SyntaxKind::MathIdent => node.cast().map(Self::MathIdent),
|
||||||
SyntaxKind::MathAlignPoint => node.cast().map(Self::MathAlignPoint),
|
SyntaxKind::MathAlignPoint => node.cast().map(Self::MathAlignPoint),
|
||||||
SyntaxKind::MathDelimited => node.cast().map(Self::MathDelimited),
|
SyntaxKind::MathDelimited => node.cast().map(Self::MathDelimited),
|
||||||
SyntaxKind::MathScript => node.cast().map(Self::MathScript),
|
SyntaxKind::MathAttach => node.cast().map(Self::MathAttach),
|
||||||
SyntaxKind::MathFrac => node.cast().map(Self::MathFrac),
|
SyntaxKind::MathFrac => node.cast().map(Self::MathFrac),
|
||||||
SyntaxKind::Ident => node.cast().map(Self::Ident),
|
SyntaxKind::Ident => node.cast().map(Self::Ident),
|
||||||
SyntaxKind::None => node.cast().map(Self::None),
|
SyntaxKind::None => node.cast().map(Self::None),
|
||||||
@ -285,7 +284,7 @@ impl AstNode for Expr {
|
|||||||
Self::MathIdent(v) => v.as_untyped(),
|
Self::MathIdent(v) => v.as_untyped(),
|
||||||
Self::MathAlignPoint(v) => v.as_untyped(),
|
Self::MathAlignPoint(v) => v.as_untyped(),
|
||||||
Self::MathDelimited(v) => v.as_untyped(),
|
Self::MathDelimited(v) => v.as_untyped(),
|
||||||
Self::MathScript(v) => v.as_untyped(),
|
Self::MathAttach(v) => v.as_untyped(),
|
||||||
Self::MathFrac(v) => v.as_untyped(),
|
Self::MathFrac(v) => v.as_untyped(),
|
||||||
Self::Ident(v) => v.as_untyped(),
|
Self::Ident(v) => v.as_untyped(),
|
||||||
Self::None(v) => v.as_untyped(),
|
Self::None(v) => v.as_untyped(),
|
||||||
@ -709,8 +708,7 @@ node! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A subsection in a math formula that is surrounded by matched delimiters:
|
/// Matched delimiters surrounding math in a formula: `[x + y]`.
|
||||||
/// `[x + y]`.
|
|
||||||
MathDelimited
|
MathDelimited
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -732,26 +730,26 @@ impl MathDelimited {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A base with an optional sub- and superscript in a formula: `a_1^2`.
|
/// A base with optional attachments in a formula: `a_1^2`.
|
||||||
MathScript
|
MathAttach
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MathScript {
|
impl MathAttach {
|
||||||
/// The base of the script.
|
/// The base, to which things are attached.
|
||||||
pub fn base(&self) -> Expr {
|
pub fn base(&self) -> Expr {
|
||||||
self.0.cast_first_match().unwrap_or_default()
|
self.0.cast_first_match().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The subscript.
|
/// The bottom attachment.
|
||||||
pub fn sub(&self) -> Option<Expr> {
|
pub fn bottom(&self) -> Option<Expr> {
|
||||||
self.0
|
self.0
|
||||||
.children()
|
.children()
|
||||||
.skip_while(|node| !matches!(node.kind(), SyntaxKind::Underscore))
|
.skip_while(|node| !matches!(node.kind(), SyntaxKind::Underscore))
|
||||||
.find_map(SyntaxNode::cast)
|
.find_map(SyntaxNode::cast)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The superscript.
|
/// The top attachment.
|
||||||
pub fn sup(&self) -> Option<Expr> {
|
pub fn top(&self) -> Option<Expr> {
|
||||||
self.0
|
self.0
|
||||||
.children()
|
.children()
|
||||||
.skip_while(|node| !matches!(node.kind(), SyntaxKind::Hat))
|
.skip_while(|node| !matches!(node.kind(), SyntaxKind::Hat))
|
||||||
|
@ -65,11 +65,10 @@ pub enum SyntaxKind {
|
|||||||
MathIdent,
|
MathIdent,
|
||||||
/// An alignment point in math: `&`.
|
/// An alignment point in math: `&`.
|
||||||
MathAlignPoint,
|
MathAlignPoint,
|
||||||
/// A subsection in a math formula that is surrounded by matched delimiters:
|
/// Matched delimiters surrounding math in a formula: `[x + y]`.
|
||||||
/// `[x + y]`.
|
|
||||||
MathDelimited,
|
MathDelimited,
|
||||||
/// A base with optional sub- and superscripts in math: `a_1^2`.
|
/// A base with optional attachments in a formula: `a_1^2`.
|
||||||
MathScript,
|
MathAttach,
|
||||||
/// A fraction in math: `x/2`.
|
/// A fraction in math: `x/2`.
|
||||||
MathFrac,
|
MathFrac,
|
||||||
|
|
||||||
@ -349,7 +348,7 @@ impl SyntaxKind {
|
|||||||
Self::MathAtom => "math atom",
|
Self::MathAtom => "math atom",
|
||||||
Self::MathAlignPoint => "math alignment point",
|
Self::MathAlignPoint => "math alignment point",
|
||||||
Self::MathDelimited => "delimited math",
|
Self::MathDelimited => "delimited math",
|
||||||
Self::MathScript => "math script",
|
Self::MathAttach => "math attachments",
|
||||||
Self::MathFrac => "math fraction",
|
Self::MathFrac => "math fraction",
|
||||||
Self::Hashtag => "hashtag",
|
Self::Hashtag => "hashtag",
|
||||||
Self::LeftBrace => "opening brace",
|
Self::LeftBrace => "opening brace",
|
||||||
|
@ -234,21 +234,20 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) {
|
|||||||
SyntaxKind::Hashtag => embedded_code_expr(p),
|
SyntaxKind::Hashtag => embedded_code_expr(p),
|
||||||
SyntaxKind::MathIdent => {
|
SyntaxKind::MathIdent => {
|
||||||
p.eat();
|
p.eat();
|
||||||
|
while p.directly_at(SyntaxKind::MathAtom)
|
||||||
|
&& p.current_text() == "."
|
||||||
|
&& matches!(
|
||||||
|
p.lexer.clone().next(),
|
||||||
|
SyntaxKind::MathIdent | SyntaxKind::MathAtom
|
||||||
|
)
|
||||||
|
{
|
||||||
|
p.convert(SyntaxKind::Dot);
|
||||||
|
p.convert(SyntaxKind::Ident);
|
||||||
|
p.wrap(m, SyntaxKind::FieldAccess);
|
||||||
|
}
|
||||||
if p.directly_at(SyntaxKind::MathAtom) && p.current_text() == "(" {
|
if p.directly_at(SyntaxKind::MathAtom) && p.current_text() == "(" {
|
||||||
math_args(p);
|
math_args(p);
|
||||||
p.wrap(m, SyntaxKind::FuncCall);
|
p.wrap(m, SyntaxKind::FuncCall);
|
||||||
} else {
|
|
||||||
while p.directly_at(SyntaxKind::MathAtom)
|
|
||||||
&& p.current_text() == "."
|
|
||||||
&& matches!(
|
|
||||||
p.lexer.clone().next(),
|
|
||||||
SyntaxKind::MathIdent | SyntaxKind::MathAtom
|
|
||||||
)
|
|
||||||
{
|
|
||||||
p.convert(SyntaxKind::Dot);
|
|
||||||
p.convert(SyntaxKind::Ident);
|
|
||||||
p.wrap(m, SyntaxKind::FieldAccess);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,10 +361,10 @@ fn math_class(text: &str) -> Option<MathClass> {
|
|||||||
fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usize)> {
|
fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usize)> {
|
||||||
match kind {
|
match kind {
|
||||||
SyntaxKind::Underscore => {
|
SyntaxKind::Underscore => {
|
||||||
Some((SyntaxKind::MathScript, SyntaxKind::Hat, ast::Assoc::Right, 2))
|
Some((SyntaxKind::MathAttach, SyntaxKind::Hat, ast::Assoc::Right, 2))
|
||||||
}
|
}
|
||||||
SyntaxKind::Hat => {
|
SyntaxKind::Hat => {
|
||||||
Some((SyntaxKind::MathScript, SyntaxKind::Underscore, ast::Assoc::Right, 2))
|
Some((SyntaxKind::MathAttach, SyntaxKind::Underscore, ast::Assoc::Right, 2))
|
||||||
}
|
}
|
||||||
SyntaxKind::Slash => {
|
SyntaxKind::Slash => {
|
||||||
Some((SyntaxKind::MathFrac, SyntaxKind::Eof, ast::Assoc::Left, 1))
|
Some((SyntaxKind::MathFrac, SyntaxKind::Eof, ast::Assoc::Left, 1))
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.5 KiB |
@ -3,26 +3,13 @@
|
|||||||
---
|
---
|
||||||
#set page(width: auto)
|
#set page(width: auto)
|
||||||
|
|
||||||
$ accent(a,`),
|
$ grave(a),
|
||||||
accent(a,´),
|
acute(a),
|
||||||
accent(a,\^),
|
circum(a),
|
||||||
accent(a,~),
|
tilde(a),
|
||||||
accent(a,¯),
|
macron(a),
|
||||||
accent(a,‾),
|
breve(a),
|
||||||
accent(a,˘),
|
dot(a),
|
||||||
accent(a,.),
|
diaer(a),
|
||||||
accent(a,¨),
|
caron(a),
|
||||||
accent(a,ˇ),
|
arrow(a) $
|
||||||
accent(a,->) $
|
|
||||||
|
|
||||||
$ accent(a, grave),
|
|
||||||
accent(a, acute),
|
|
||||||
accent(a, circum),
|
|
||||||
accent(a, tilde),
|
|
||||||
accent(a, macron),
|
|
||||||
accent(a, overline),
|
|
||||||
accent(a, breve),
|
|
||||||
accent(a, dot),
|
|
||||||
accent(a, diaer),
|
|
||||||
accent(a, caron),
|
|
||||||
accent(a, arrow) $
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user