summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYip Coekjan <69834864+Coekjan@users.noreply.github.com>2024-06-14 00:57:34 +0800
committerGitHub <noreply@github.com>2024-06-13 16:57:34 +0000
commit6f9855a8c39bea36a06496a34a9083a12c93308b (patch)
treee73509018fea80eef8b2c822eb1fd9ad2e8fb110
parentad4ef68a112dedabf80f885a02bcb574eb9af9e4 (diff)
Make symbols callable like functions & migrate callable accents to callable symbols (#4299)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
-rw-r--r--crates/typst-macros/src/lib.rs38
-rw-r--r--crates/typst-macros/src/symbols.rs56
-rw-r--r--crates/typst/src/eval/call.rs30
-rw-r--r--crates/typst/src/eval/markup.rs4
-rw-r--r--crates/typst/src/foundations/selector.rs2
-rw-r--r--crates/typst/src/foundations/value.rs3
-rw-r--r--crates/typst/src/math/accent.rs76
-rw-r--r--crates/typst/src/math/mod.rs6
-rw-r--r--crates/typst/src/symbols/sym.rs41
-rw-r--r--crates/typst/src/symbols/symbol.rs105
-rw-r--r--docs/src/lib.rs6
11 files changed, 254 insertions, 113 deletions
diff --git a/crates/typst-macros/src/lib.rs b/crates/typst-macros/src/lib.rs
index c37af237..acc5e603 100644
--- a/crates/typst-macros/src/lib.rs
+++ b/crates/typst-macros/src/lib.rs
@@ -340,17 +340,37 @@ pub fn derive_cast(item: BoundaryStream) -> BoundaryStream {
/// Defines a list of `Symbol`s.
///
+/// The `#[call(path)]` attribute can be used to specify a function to call when
+/// the symbol is invoked. The function must be `NativeFunc`.
+///
/// ```ignore
/// const EMOJI: &[(&str, Symbol)] = symbols! {
-/// // A plain symbol without modifiers.
-/// abacus: '🧮',
-///
-/// // A symbol with a modifierless default and one modifier.
-/// alien: ['👽', monster: '👾'],
-///
-/// // A symbol where each variant has a modifier. The first one will be
-/// // the default.
-/// clock: [one: '🕐', two: '🕑', ...],
+/// // A plain symbol without modifiers.
+/// abacus: '🧮',
+///
+/// // A symbol with a modifierless default and one modifier.
+/// alien: ['👽', monster: '👾'],
+///
+/// // A symbol where each variant has a modifier. The first one will be
+/// // the default.
+/// clock: [one: '🕐', two: '🕑', ...],
+///
+/// // A callable symbol without modifiers.
+/// breve: #[call(crate::math::breve)] '˘',
+///
+/// // A callable symbol with a modifierless default and one modifier.
+/// acute: [
+/// #[call(crate::math::acute)] '´',
+/// double: '˝',
+/// ],
+///
+/// // A callable symbol where each variant has a modifier.
+/// arrow: [
+/// #[call(crate::math::arrow)] r: '→',
+/// r.long.bar: '⟼',
+/// #[call(crate::math::arrow_l)] l: '←',
+/// l.long.bar: '⟻',
+/// ],
/// }
/// ```
///
diff --git a/crates/typst-macros/src/symbols.rs b/crates/typst-macros/src/symbols.rs
index 2ddb922f..6b35f87f 100644
--- a/crates/typst-macros/src/symbols.rs
+++ b/crates/typst-macros/src/symbols.rs
@@ -3,7 +3,7 @@ use quote::quote;
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream, Parser};
use syn::punctuated::Punctuated;
-use syn::{Ident, Result, Token};
+use syn::{Ident, LitChar, Path, Result, Token};
/// Expand the `symbols!` macro.
pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
@@ -12,12 +12,16 @@ pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
let pairs = list.iter().map(|symbol| {
let name = symbol.name.to_string();
let kind = match &symbol.kind {
- Kind::Single(c) => quote! { ::typst::symbols::Symbol::single(#c), },
+ Kind::Single(c, h) => {
+ let symbol = construct_sym_char(c, h);
+ quote! { ::typst::symbols::Symbol::single(#symbol), }
+ }
Kind::Multiple(variants) => {
let variants = variants.iter().map(|variant| {
let name = &variant.name;
let c = &variant.c;
- quote! { (#name, #c) }
+ let symbol = construct_sym_char(c, &variant.handler);
+ quote! { (#name, #symbol) }
});
quote! {
::typst::symbols::Symbol::list(&[#(#variants),*])
@@ -29,21 +33,36 @@ pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
Ok(quote! { &[#(#pairs),*] })
}
+fn construct_sym_char(ch: &LitChar, handler: &Handler) -> TokenStream {
+ match &handler.0 {
+ None => quote! { ::typst::symbols::SymChar::pure(#ch), },
+ Some(path) => quote! {
+ ::typst::symbols::SymChar::with_func(
+ #ch,
+ <#path as ::typst::foundations::NativeFunc>::func,
+ ),
+ },
+ }
+}
+
struct Symbol {
name: syn::Ident,
kind: Kind,
}
enum Kind {
- Single(syn::LitChar),
+ Single(syn::LitChar, Handler),
Multiple(Punctuated<Variant, Token![,]>),
}
struct Variant {
name: String,
c: syn::LitChar,
+ handler: Handler,
}
+struct Handler(Option<Path>);
+
impl Parse for Symbol {
fn parse(input: ParseStream) -> Result<Self> {
let name = input.call(Ident::parse_any)?;
@@ -55,9 +74,13 @@ impl Parse for Symbol {
impl Parse for Kind {
fn parse(input: ParseStream) -> Result<Self> {
+ let handler = input.parse::<Handler>()?;
if input.peek(syn::LitChar) {
- Ok(Self::Single(input.parse()?))
+ Ok(Self::Single(input.parse()?, handler))
} else {
+ if handler.0.is_some() {
+ return Err(input.error("unexpected handler"));
+ }
let content;
syn::bracketed!(content in input);
Ok(Self::Multiple(Punctuated::parse_terminated(&content)?))
@@ -68,6 +91,7 @@ impl Parse for Kind {
impl Parse for Variant {
fn parse(input: ParseStream) -> Result<Self> {
let mut name = String::new();
+ let handler = input.parse::<Handler>()?;
if input.peek(syn::Ident::peek_any) {
name.push_str(&input.call(Ident::parse_any)?.to_string());
while input.peek(Token![.]) {
@@ -78,6 +102,26 @@ impl Parse for Variant {
input.parse::<Token![:]>()?;
}
let c = input.parse()?;
- Ok(Self { name, c })
+ Ok(Self { name, c, handler })
+ }
+}
+
+impl Parse for Handler {
+ fn parse(input: ParseStream) -> Result<Self> {
+ let Ok(attrs) = input.call(syn::Attribute::parse_outer) else {
+ return Ok(Self(None));
+ };
+ let handler = attrs
+ .iter()
+ .find_map(|attr| {
+ if attr.path().is_ident("call") {
+ if let Ok(path) = attr.parse_args::<Path>() {
+ return Some(Self(Some(path)));
+ }
+ }
+ None
+ })
+ .unwrap_or(Self(None));
+ Ok(handler)
}
}
diff --git a/crates/typst/src/eval/call.rs b/crates/typst/src/eval/call.rs
index 278c3afb..12d024ad 100644
--- a/crates/typst/src/eval/call.rs
+++ b/crates/typst/src/eval/call.rs
@@ -9,8 +9,7 @@ use crate::foundations::{
Context, Func, IntoValue, NativeElement, Scope, Scopes, Value,
};
use crate::introspection::Introspector;
-use crate::math::{Accent, AccentElem, LrElem};
-use crate::symbols::Symbol;
+use crate::math::LrElem;
use crate::syntax::ast::{self, AstNode};
use crate::syntax::{Span, Spanned, SyntaxNode};
use crate::text::TextElem;
@@ -129,23 +128,9 @@ impl Eval for ast::FuncCall<'_> {
(callee.eval(vm)?, args.eval(vm)?.spanned(span))
};
- // Handle math special cases for non-functions:
- // Combining accent symbols apply themselves while everything else
- // simply displays the arguments verbatim.
- if in_math && !matches!(callee, Value::Func(_)) {
- if let Value::Symbol(sym) = &callee {
- let c = sym.get();
- if let Some(accent) = Symbol::combining_accent(c) {
- let base = args.expect("base")?;
- let size = args.named("size")?;
- args.finish()?;
- let mut accent = AccentElem::new(base, Accent::new(accent));
- if let Some(size) = size {
- accent = accent.with_size(size);
- }
- return Ok(Value::Content(accent.pack()));
- }
- }
+ let func_result = callee.clone().cast::<Func>().at(callee_span);
+ if in_math && func_result.is_err() {
+ // For non-functions in math, we wrap the arguments in parentheses.
let mut body = Content::empty();
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
if i > 0 {
@@ -163,11 +148,10 @@ impl Eval for ast::FuncCall<'_> {
));
}
- let callee = callee.cast::<Func>().at(callee_span)?;
- let point = || Tracepoint::Call(callee.name().map(Into::into));
+ let func = func_result?;
+ let point = || Tracepoint::Call(func.name().map(Into::into));
let f = || {
- callee
- .call(&mut vm.engine, vm.context, args)
+ func.call(&mut vm.engine, vm.context, args)
.trace(vm.world(), point, span)
};
diff --git a/crates/typst/src/eval/markup.rs b/crates/typst/src/eval/markup.rs
index e38f137d..b44c9789 100644
--- a/crates/typst/src/eval/markup.rs
+++ b/crates/typst/src/eval/markup.rs
@@ -107,7 +107,7 @@ impl Eval for ast::Escape<'_> {
type Output = Value;
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
- Ok(Value::Symbol(Symbol::single(self.get())))
+ Ok(Value::Symbol(Symbol::single(self.get().into())))
}
}
@@ -115,7 +115,7 @@ impl Eval for ast::Shorthand<'_> {
type Output = Value;
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
- Ok(Value::Symbol(Symbol::single(self.get())))
+ Ok(Value::Symbol(Symbol::single(self.get().into())))
}
}
diff --git a/crates/typst/src/foundations/selector.rs b/crates/typst/src/foundations/selector.rs
index 1aee961b..6802a150 100644
--- a/crates/typst/src/foundations/selector.rs
+++ b/crates/typst/src/foundations/selector.rs
@@ -277,12 +277,12 @@ impl Repr for Selector {
cast! {
type Selector,
+ text: EcoString => Self::text(&text)?,
func: Func => func
.element()
.ok_or("only element functions can be used as selectors")?
.select(),
label: Label => Self::Label(label),
- text: EcoString => Self::text(&text)?,
regex: Regex => Self::regex(regex)?,
location: Location => Self::Location(location),
}
diff --git a/crates/typst/src/foundations/value.rs b/crates/typst/src/foundations/value.rs
index 47ce5d5d..05abbfba 100644
--- a/crates/typst/src/foundations/value.rs
+++ b/crates/typst/src/foundations/value.rs
@@ -658,7 +658,8 @@ primitive! { Dict: "dictionary", Dict }
primitive! {
Func: "function",
Func,
- Type(ty) => ty.constructor()?.clone()
+ Type(ty) => ty.constructor()?.clone(),
+ Symbol(symbol) => symbol.func()?
}
primitive! { Args: "arguments", Args }
primitive! { Type: "type", Type }
diff --git a/crates/typst/src/math/accent.rs b/crates/typst/src/math/accent.rs
index 9a12f6d5..dbc0fad6 100644
--- a/crates/typst/src/math/accent.rs
+++ b/crates/typst/src/math/accent.rs
@@ -1,18 +1,64 @@
use crate::diag::{bail, SourceResult};
use crate::foundations::{
- cast, elem, Content, Packed, Resolve, Smart, StyleChain, Value,
+ cast, elem, func, Content, NativeElement, Packed, Resolve, Smart, StyleChain, Value,
};
use crate::layout::{Em, Frame, Length, Point, Rel, Size};
use crate::math::{
style_cramped, FrameFragment, GlyphFragment, LayoutMath, MathContext, MathFragment,
Scaled,
};
-use crate::symbols::Symbol;
use crate::text::TextElem;
/// How much the accent can be shorter than the base.
const ACCENT_SHORT_FALL: Em = Em::new(0.5);
+/// This macro generates accent-related functions.
+///
+/// ```ignore
+/// accents! {
+/// '\u{0300}' | '`' => grave,
+/// // ^^^^^^^^^ ^^^ ^^^^^
+/// // | | |
+/// // | | +-- The name of the function.
+/// // | +--------- The alternative characters that represent the accent.
+/// // +---------------------- The primary character that represents the accent.
+/// }
+/// ```
+///
+/// When combined with the `Accent::combine` function, accent characters can be normalized
+/// to the primary character.
+macro_rules! accents {
+ ($($primary:literal $(| $alt:literal)* => $name:ident),* $(,)?) => {
+ impl Accent {
+ /// Normalize an accent to a combining one.
+ pub fn combine(c: char) -> Option<char> {
+ Some(match c {
+ $($primary $(| $alt)* => $primary,)*
+ _ => return None,
+ })
+ }
+ }
+
+ $(
+ /// The accent function for callable symbol definitions.
+ #[func]
+ pub fn $name(
+ /// The base to which the accent is applied.
+ base: Content,
+ /// The size of the accent, relative to the width of the base.
+ #[named]
+ size: Option<Smart<Rel<Length>>>,
+ ) -> Content {
+ let mut accent = AccentElem::new(base, Accent::new($primary));
+ if let Some(size) = size {
+ accent = accent.with_size(size);
+ }
+ accent.pack()
+ }
+ )+
+ };
+}
+
/// Attaches an accent to a base.
///
/// # Example
@@ -43,6 +89,7 @@ pub struct AccentElem {
/// | Circumflex | `hat` | `^` |
/// | Tilde | `tilde` | `~` |
/// | Macron | `macron` | `¯` |
+ /// | Dash | `dash` | `‾` |
/// | Breve | `breve` | `˘` |
/// | Dot | `dot` | `.` |
/// | Double dot, Diaeresis | `dot.double`, `diaer` | `¨` |
@@ -130,10 +177,33 @@ pub struct Accent(char);
impl Accent {
/// Normalize a character into an accent.
pub fn new(c: char) -> Self {
- Self(Symbol::combining_accent(c).unwrap_or(c))
+ Self(Self::combine(c).unwrap_or(c))
}
}
+// Keep it synced with the documenting table above.
+accents! {
+ '\u{0300}' | '`' => grave,
+ '\u{0301}' | '´' => acute,
+ '\u{0302}' | '^' | 'ˆ' => hat,
+ '\u{0303}' | '~' | '∼' | '˜' => tilde,
+ '\u{0304}' | '¯' => macron,
+ '\u{0305}' | '-' | '‾' | '−' => dash,
+ '\u{0306}' | '˘' => breve,
+ '\u{0307}' | '.' | '˙' | '⋅' => dot,
+ '\u{0308}' | '¨' => dot_double,
+ '\u{20db}' => dot_triple,
+ '\u{20dc}' => dot_quad,
+ '\u{030a}' | '∘' | '○' => circle,
+ '\u{030b}' | '˝' => acute_double,
+ '\u{030c}' | 'ˇ' => caron,
+ '\u{20d6}' | '←' => arrow_l,
+ '\u{20d7}' | '→' | '⟶' => arrow,
+ '\u{20e1}' | '↔' | '⟷' => arrow_l_r,
+ '\u{20d0}' | '↼' => harpoon_lt,
+ '\u{20d1}' | '⇀' => harpoon,
+}
+
cast! {
Accent,
self => self.0.into_value(),
diff --git a/crates/typst/src/math/mod.rs b/crates/typst/src/math/mod.rs
index 6ef3df9a..4b5ce56b 100644
--- a/crates/typst/src/math/mod.rs
+++ b/crates/typst/src/math/mod.rs
@@ -2,7 +2,9 @@
#[macro_use]
mod ctx;
-mod accent;
+
+pub mod accent;
+
mod align;
mod attach;
mod cancel;
@@ -21,7 +23,7 @@ mod stretch;
mod style;
mod underover;
-pub use self::accent::*;
+pub use self::accent::{Accent, AccentElem};
pub use self::align::*;
pub use self::attach::*;
pub use self::cancel::*;
diff --git a/crates/typst/src/symbols/sym.rs b/crates/typst/src/symbols/sym.rs
index b3c90d5b..d09cb4c2 100644
--- a/crates/typst/src/symbols/sym.rs
+++ b/crates/typst/src/symbols/sym.rs
@@ -84,7 +84,7 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! {
comma: ',',
dagger: ['†', double: '‡'],
dash: [
- en: '–',
+ #[call(crate::math::accent::dash)] en: '–',
em: '—',
fig: '‒',
wave: '〜',
@@ -93,15 +93,15 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! {
wave.double: '〰',
],
dot: [
- op: '⋅',
+ #[call(crate::math::accent::dot)] op: '⋅',
basic: '.',
c: '·',
circle: '⊙',
circle.big: '⨀',
square: '⊡',
- double: '¨',
- triple: '\u{20db}',
- quad: '\u{20dc}',
+ #[call(crate::math::accent::dot_double)] double: '¨',
+ #[call(crate::math::accent::dot_triple)] triple: '\u{20db}',
+ #[call(crate::math::accent::dot_quad)] quad: '\u{20dc}',
],
excl: ['!', double: '‼', inv: '¡', quest: '⁉'],
quest: ['?', double: '⁇', excl: '⁈', inv: '¿'],
@@ -117,7 +117,7 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! {
slash: ['/', double: '⫽', triple: '⫻', big: '\u{29f8}'],
dots: [h.c: '⋯', h: '…', v: '⋮', down: '⋱', up: '⋰'],
tilde: [
- op: '∼',
+ #[call(crate::math::accent::tilde)] op: '∼',
basic: '~',
dot: '⩪',
eq: '≃',
@@ -133,14 +133,17 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! {
],
// Accents, quotes, and primes.
- acute: ['´', double: '˝'],
- breve: '˘',
+ acute: [
+ #[call(crate::math::accent::acute)] '´',
+ #[call(crate::math::accent::acute_double)] double: '˝',
+ ],
+ breve: #[call(crate::math::accent::breve)] '˘',
caret: '‸',
- caron: 'ˇ',
- hat: '^',
- diaer: '¨',
- grave: '`',
- macron: '¯',
+ caron: #[call(crate::math::accent::caron)] 'ˇ',
+ hat: #[call(crate::math::accent::hat)] '^',
+ diaer: #[call(crate::math::accent::dot_double)] '¨',
+ grave: #[call(crate::math::accent::grave)] '`',
+ macron: #[call(crate::math::accent::macron)] '¯',
quote: [
double: '"',
single: '\'',
@@ -487,7 +490,7 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! {
// Shapes.
bullet: '•',
circle: [
- stroked: '○',
+ #[call(crate::math::accent::circle)] stroked: '○',
stroked.tiny: '∘',
stroked.small: '⚬',
stroked.big: '◯',
@@ -580,7 +583,7 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! {
// Arrows, harpoons, and tacks.
arrow: [
- r: '→',
+ #[call(crate::math::accent::arrow)] r: '→',
r.long.bar: '⟼',
r.bar: '↦',
r.curve: '⤷',
@@ -607,7 +610,7 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! {
r.twohead.bar: '⤅',
r.twohead: '↠',
r.wave: '↝',
- l: '←',
+ #[call(crate::math::accent::arrow_l)] l: '←',
l.bar: '↤',
l.curve: '⤶',
l.dashed: '⇠',
@@ -656,7 +659,7 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! {
b.stroked: '⇩',
b.triple: '⤋',
b.twohead: '↡',
- l.r: '↔',
+ #[call(crate::math::accent::arrow_l_r)] l.r: '↔',
l.r.double: '⇔',
l.r.double.long: '⟺',
l.r.double.not: '⇎',
@@ -715,13 +718,13 @@ pub(crate) const SYM: &[(&str, Symbol)] = symbols! {
b: '⌄',
],
harpoon: [
- rt: '⇀',
+ #[call(crate::math::accent::harpoon)] rt: '⇀',
rt.bar: '⥛',
rt.stop: '⥓',
rb: '⇁',
rb.bar: '⥟',
rb.stop: '⥗',
- lt: '↼',
+ #[call(crate::math::accent::harpoon_lt)] lt: '↼',
lt.bar: '⥚',
lt.stop: '⥒',
lb: '↽',
diff --git a/crates/typst/src/symbols/symbol.rs b/crates/typst/src/symbols/symbol.rs
index 73bdd286..0be4adc4 100644
--- a/crates/typst/src/symbols/symbol.rs
+++ b/crates/typst/src/symbols/symbol.rs
@@ -7,7 +7,7 @@ use ecow::{eco_format, EcoString};
use serde::{Serialize, Serializer};
use crate::diag::{bail, SourceResult, StrResult};
-use crate::foundations::{cast, func, scope, ty, Array};
+use crate::foundations::{cast, func, scope, ty, Array, Func};
use crate::syntax::{Span, Spanned};
#[doc(inline)]
@@ -46,43 +46,52 @@ pub use typst_macros::symbols;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Symbol(Repr);
+/// The character of a symbol, possibly with a function.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub struct SymChar(char, Option<fn() -> Func>);
+
/// The internal representation.
#[derive(Clone, Eq, PartialEq, Hash)]
enum Repr {
- Single(char),
- Const(&'static [(&'static str, char)]),
+ Single(SymChar),
+ Const(&'static [(&'static str, SymChar)]),
Multi(Arc<(List, EcoString)>),
}
/// A collection of symbols.
#[derive(Clone, Eq, PartialEq, Hash)]
enum List {
- Static(&'static [(&'static str, char)]),
- Runtime(Box<[(EcoString, char)]>),
+ Static(&'static [(&'static str, SymChar)]),
+ Runtime(Box<[(EcoString, SymChar)]>),
}
impl Symbol {
/// Create a new symbol from a single character.
- pub const fn single(c: char) -> Self {
+ pub const fn single(c: SymChar) -> Self {
Self(Repr::Single(c))
}
/// Create a symbol with a static variant list.
#[track_caller]
- pub const fn list(list: &'static [(&'static str, char)]) -> Self {
+ pub const fn list(list: &'static [(&'static str, SymChar)]) -> Self {
debug_assert!(!list.is_empty());
Self(Repr::Const(list))
}
/// Create a symbol with a runtime variant list.
#[track_caller]
- pub fn runtime(list: Box<[(EcoString, char)]>) -> Self {
+ pub fn runtime(list: Box<[(EcoString, SymChar)]>) -> Self {
debug_assert!(!list.is_empty());
Self(Repr::Multi(Arc::new((List::Runtime(list), EcoString::new()))))
}
- /// Get the symbol's text.
+ /// Get the symbol's char.
pub fn get(&self) -> char {
+ self.sym().char()
+ }
+
+ /// Resolve the symbol's `SymChar`.
+ pub fn sym(&self) -> SymChar {
match &self.0 {
Repr::Single(c) => *c,
Repr::Const(_) => find(self.variants(), "").unwrap(),
@@ -90,6 +99,13 @@ impl Symbol {
}
}
+ /// Try to get the function associated with the symbol, if any.
+ pub fn func(&self) -> StrResult<Func> {
+ self.sym()
+ .func()
+ .ok_or_else(|| eco_format!("symbol {self} is not callable"))
+ }
+
/// Apply a modifier to the symbol.
pub fn modified(mut self, modifier: &str) -> StrResult<Self> {
if let Repr::Const(list) = self.0 {
@@ -111,7 +127,7 @@ impl Symbol {
}
/// The characters that are covered by this symbol.
- pub fn variants(&self) -> impl Iterator<Item = (&str, char)> {
+ pub fn variants(&self) -> impl Iterator<Item = (&str, SymChar)> {
match &self.0 {
Repr::Single(c) => Variants::Single(Some(*c).into_iter()),
Repr::Const(list) => Variants::Static(list.iter()),
@@ -133,33 +149,6 @@ impl Symbol {
}
set.into_iter()
}
-
- /// Normalize an accent to a combining one. Keep it synced with the
- /// documenting table in accent.rs AccentElem.
- 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{20db}' => '\u{20db}',
- '\u{20dc}' => '\u{20dc}',
- '\u{030a}' | '∘' | '○' => '\u{030a}',
- '\u{030b}' | '˝' => '\u{030b}',
- '\u{030c}' | 'ˇ' => '\u{030c}',
- '\u{20d6}' | '←' => '\u{20d6}',
- '\u{20d7}' | '→' | '⟶' => '\u{20d7}',
- '\u{20e1}' | '↔' | '⟷' => '\u{20e1}',
- '\u{20d0}' | '↼' => '\u{20d0}',
- '\u{20d1}' | '⇀' => '\u{20d1}',
- _ => return None,
- })
- }
}
#[scope]
@@ -203,7 +192,7 @@ impl Symbol {
if list.iter().any(|(prev, _)| &v.0 == prev) {
bail!(span, "duplicate variant");
}
- list.push((v.0, v.1));
+ list.push((v.0, SymChar::pure(v.1)));
}
Ok(Symbol::runtime(list.into_boxed_slice()))
}
@@ -215,6 +204,34 @@ impl Display for Symbol {
}
}
+impl SymChar {
+ /// Create a symbol character without a function.
+ pub const fn pure(c: char) -> Self {
+ Self(c, None)
+ }
+
+ /// Create a symbol character with a function.
+ pub const fn with_func(c: char, func: fn() -> Func) -> Self {
+ Self(c, Some(func))
+ }
+
+ /// Get the character of the symbol.
+ pub const fn char(&self) -> char {
+ self.0
+ }
+
+ /// Get the function associated with the symbol.
+ pub fn func(&self) -> Option<Func> {
+ self.1.map(|f| f())
+ }
+}
+
+impl From<char> for SymChar {
+ fn from(c: char) -> Self {
+ SymChar(c, None)
+ }
+}
+
impl Debug for Repr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
@@ -276,13 +293,13 @@ cast! {
/// Iterator over variants.
enum Variants<'a> {
- Single(std::option::IntoIter<char>),
- Static(std::slice::Iter<'static, (&'static str, char)>),
- Runtime(std::slice::Iter<'a, (EcoString, char)>),
+ Single(std::option::IntoIter<SymChar>),
+ Static(std::slice::Iter<'static, (&'static str, SymChar)>),
+ Runtime(std::slice::Iter<'a, (EcoString, SymChar)>),
}
impl<'a> Iterator for Variants<'a> {
- type Item = (&'a str, char);
+ type Item = (&'a str, SymChar);
fn next(&mut self) -> Option<Self::Item> {
match self {
@@ -295,9 +312,9 @@ impl<'a> Iterator for Variants<'a> {
/// Find the best symbol from the list.
fn find<'a>(
- variants: impl Iterator<Item = (&'a str, char)>,
+ variants: impl Iterator<Item = (&'a str, SymChar)>,
modifiers: &str,
-) -> Option<char> {
+) -> Option<SymChar> {
let mut best = None;
let mut best_score = None;
diff --git a/docs/src/lib.rs b/docs/src/lib.rs
index 8af7dc0d..231fe97c 100644
--- a/docs/src/lib.rs
+++ b/docs/src/lib.rs
@@ -661,15 +661,15 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
for (variant, c) in symbol.variants() {
let shorthand = |list: &[(&'static str, char)]| {
- list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s)
+ list.iter().copied().find(|&(_, x)| x == c.char()).map(|(s, _)| s)
};
list.push(SymbolModel {
name: complete(variant),
markup_shorthand: shorthand(typst::syntax::ast::Shorthand::MARKUP_LIST),
math_shorthand: shorthand(typst::syntax::ast::Shorthand::MATH_LIST),
- codepoint: c as u32,
- accent: typst::symbols::Symbol::combining_accent(c).is_some(),
+ codepoint: c.char() as _,
+ accent: typst::math::Accent::combine(c.char()).is_some(),
alternates: symbol
.variants()
.filter(|(other, _)| other != &variant)