Symbol values and modules

This commit is contained in:
Laurenz 2023-01-27 12:04:23 +01:00
parent 13efa128c8
commit 1de53730bc
24 changed files with 2472 additions and 235 deletions

7
Cargo.lock generated
View File

@ -974,11 +974,6 @@ dependencies = [
"siphasher",
]
[[package]]
name = "symmie"
version = "0.1.0"
source = "git+https://github.com/typst/symmie#75755e62895fcd822b3a1038462f4548b18b8119"
[[package]]
name = "syn"
version = "1.0.107"
@ -1107,7 +1102,6 @@ dependencies = [
"siphasher",
"subsetter",
"svg2pdf",
"symmie",
"thin-vec",
"tiny-skia",
"ttf-parser 0.18.1",
@ -1152,7 +1146,6 @@ dependencies = [
"roxmltree",
"rustybuzz",
"serde_json",
"symmie",
"syntect",
"ttf-parser 0.18.1",
"typed-arena",

View File

@ -32,7 +32,6 @@ serde = { version = "1", features = ["derive"] }
siphasher = "0.3"
subsetter = "0.1"
svg2pdf = "0.4"
symmie = { git = "https://github.com/typst/symmie" }
thin-vec = "0.2"
tiny-skia = "0.6.2"
ttf-parser = "0.18.1"

View File

@ -20,7 +20,6 @@ once_cell = "1"
roxmltree = "0.14"
rustybuzz = "0.5"
serde_json = "1"
symmie = { git = "https://github.com/typst/symmie" }
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
ttf-parser = "0.18.1"
typed-arena = "2"

View File

@ -17,13 +17,14 @@ use self::layout::LayoutRoot;
/// Construct the standard library.
pub fn build() -> Library {
let math = math::module();
let global = global(math.clone());
let sym = text::sym();
let math = math::module(&sym);
let global = global(sym, math.clone());
Library { global, math, styles: styles(), items: items() }
}
/// Construct the module with global definitions.
fn global(math: Module) -> Module {
fn global(sym: Module, math: Module) -> Module {
let mut global = Scope::deduplicating();
// Basics.
@ -36,7 +37,6 @@ fn global(math: Module) -> Module {
// Text.
global.def_func::<text::TextNode>("text");
global.def_func::<text::LinebreakNode>("linebreak");
global.def_func::<text::SymbolNode>("symbol");
global.def_func::<text::SmartQuoteNode>("smartquote");
global.def_func::<text::StrongNode>("strong");
global.def_func::<text::EmphNode>("emph");
@ -49,6 +49,8 @@ fn global(math: Module) -> Module {
global.def_func::<text::StrikeNode>("strike");
global.def_func::<text::OverlineNode>("overline");
global.def_func::<text::RawNode>("raw");
global.define("sym", sym);
global.define("emoji", text::emoji());
// Math.
global.define("math", math);
@ -169,7 +171,6 @@ fn items() -> LangItems {
text: |text| text::TextNode(text).pack(),
text_id: NodeId::of::<text::TextNode>(),
text_str: |content| Some(&content.to::<text::TextNode>()?.0),
symbol: |notation| text::SymbolNode(notation).pack(),
smart_quote: |double| text::SmartQuoteNode { double }.pack(),
parbreak: || layout::ParbreakNode.pack(),
strong: |body| text::StrongNode(body).pack(),

View File

@ -17,6 +17,7 @@ mod script;
mod spacing;
mod stretch;
mod style;
mod symbols;
pub use self::accent::*;
pub use self::align::*;
@ -46,10 +47,10 @@ use crate::prelude::*;
use crate::text::LinebreakNode;
use crate::text::TextNode;
use crate::text::TextSize;
use crate::text::{families, variant, FallbackList, FontFamily, SpaceNode, SymbolNode};
use crate::text::{families, variant, FallbackList, FontFamily, SpaceNode};
/// Create a module with all math definitions.
pub fn module() -> Module {
pub fn module(sym: &Module) -> Module {
let mut math = Scope::deduplicating();
math.def_func::<FormulaNode>("formula");
math.def_func::<LrNode>("lr");
@ -77,7 +78,9 @@ pub fn module() -> Module {
math.def_func::<MonoNode>("mono");
math.def_func::<BbNode>("bb");
spacing::define(&mut math);
symbols::define(&mut math);
op::define(&mut math);
math.copy_from(sym.scope());
Module::new("math").with_scope(math)
}
@ -227,14 +230,6 @@ impl LayoutMath for Content {
return Ok(());
}
if let Some(node) = self.to::<SymbolNode>() {
if let Some(c) = symmie::get(&node.0) {
return AtomNode(c.into()).layout_math(ctx);
} else if let Some(span) = self.span() {
bail!(span, "unknown symbol");
}
}
if let Some(node) = self.to::<SequenceNode>() {
for child in &node.0 {
child.layout_math(ctx)?;

425
library/src/math/symbols.rs Normal file
View File

@ -0,0 +1,425 @@
use typst::model::symbols;
symbols! {
define,
// Lowercase Greek.
alpha: 'α',
beta: ['β', "alt": 'ϐ'],
chi: 'χ',
delta: 'δ',
epsilon: ['ε', "alt": 'ϵ'],
eta: 'η',
gamma: 'γ',
iota: 'ι',
kai: 'ϗ',
kappa: ['κ', "alt": 'ϰ'],
lambda: 'λ',
mu: 'μ',
nu: 'ν',
ohm: ['Ω', "inv": ''],
omega: 'ω',
omicron: 'ο',
phi: ['φ', "alt": 'ϕ'],
pi: ['π', "alt": 'ϖ'],
psi: 'ψ',
rho: ['ρ', "alt": 'ϱ'],
sigma: 'σ',
tau: 'τ',
theta: ['θ', "alt": 'ϑ'],
upsilon: 'υ',
xi: 'ξ',
zeta: 'ζ',
// Uppercase Greek.
Alpha: 'Α',
Beta: 'Β',
Chi: 'Χ',
Delta: 'Δ',
Epsilon: 'Ε',
Eta: 'Η',
Gamma: 'Γ',
Iota: 'Ι',
Kai: 'Ϗ',
Kappa: 'Κ',
Lambda: 'Λ',
Mu: 'Μ',
Nu: 'Ν',
Omega: 'Ω',
Omicron: 'Ο',
Phi: 'Φ',
Pi: 'Π',
Psi: 'Ψ',
Rho: 'Ρ',
Sigma: 'Σ',
Tau: 'Τ',
Theta: 'Θ',
Upsilon: 'Υ',
Xi: 'Ξ',
Zeta: 'Ζ',
// Hebrew.
alef: 'א',
bet: 'ב',
gimel: 'ג',
shin: 'ש',
// Double-struck.
AA: '𝔸',
BB: '𝔹',
CC: '',
DD: '𝔻',
EE: '𝔼',
FF: '𝔽',
GG: '𝔾',
HH: '',
II: '𝕀',
JJ: '𝕁',
KK: '𝕂',
LL: '𝕃',
MM: '𝕄',
NN: '',
OO: '𝕆',
PP: '',
QQ: '',
RR: '',
SS: '𝕊',
TT: '𝕋',
UU: '𝕌',
VV: '𝕍',
WW: '𝕎',
XX: '𝕏',
YY: '𝕐',
ZZ: '',
// Override.
angled: [
"l": '',
"l.double": '',
"r": '',
"r.double": '',
],
// Operators.
plus: [
'+',
"circle": '',
"circle.arrow": '',
"circle.big": '',
"dot": '',
"minus": '±',
"small": '',
"square": '',
"triangle": '',
],
minus: [
'',
"circle": '',
"dot": '',
"plus": '',
"square": '',
"tilde": '',
"triangle": '',
],
dot: [
'⋅',
"circle": '',
"circle.big": '',
"square": '',
],
div: [
'',
"circle": '',
"sign": '÷',
"sign.circle": '',
],
times: [
'×',
"big": '',
"circle": '',
"circle.big": '',
"div": '',
"l": '',
"r": '',
"square": '',
"triangle": '',
],
ast: [
'',
"circle": '',
"small": '',
"sq": '',
],
star: '',
smash: '',
wreath: '',
// Relations.
eq: [
'=',
"ast": '',
"circle": '',
"colon": '',
"def": '',
"delta": '',
"equi": '',
"est": '',
"gt": '',
"lt": '',
"m": '',
"not": '',
"prec": '',
"quest": '',
"small": '',
"succ": '',
],
gt: [
'>',
"circle": '',
"dot": '',
"double": '',
"eq": '',
"eq.lt": '',
"eq.not": '',
"eqq": '',
"lt": '',
"lt.not": '',
"neqq": '',
"not": '',
"ntilde": '',
"small": '',
"tilde": '',
"tilde.not": '',
"triple": '',
"triple.nested": '',
],
lt: [
'<',
"circle": '',
"dot": '',
"double": '',
"eq": '',
"eq.gt": '',
"eq.not": '',
"eqq": '',
"gt": '',
"gt.not": '',
"neqq": '',
"not": '',
"ntilde": '',
"small": '',
"tilde": '',
"tilde.not": '',
"triple": '',
"triple.nested": '',
],
prec: [
'≺',
"approx": '',
"double": '',
"eq": '',
"eq.not": '',
"eqq": '',
"napprox": '',
"neqq": '',
"not": '',
"ntilde": '',
"tilde": '',
],
succ: [
'≻',
"approx": '',
"double": '',
"eq": '',
"eq.not": '',
"eqq": '',
"napprox": '',
"neqq": '',
"not": '',
"ntilde": '',
"tilde": '',
],
ident: ['≡', "not": '', "strict": ''],
approx: ['≈', "eq": '', "not": ''],
tilde: [
'',
"eq": '',
"eq.not": '',
"eq.rev": '',
"eqq": '',
"eqq.not": '',
"neqq": '',
"not": '',
"rev": '',
"rev.eqq": '',
"triple": '',
],
// Set theory.
nothing: ['∅', "rev": ''],
in: [
'∈',
"not": '',
"rev": '',
"rev.not": '',
"rev.small": '',
"small": '',
],
subset: [
'⊂',
"dot": '',
"double": '',
"eq": '',
"eq.not": '',
"eq.sq": '',
"eq.sq.not": '',
"neq": '',
"not": '',
"sq": '',
"sq.neq": '',
],
supset: [
'⊃',
"dot": '',
"double": '',
"eq": '',
"eq.not": '',
"eq.sq": '',
"eq.sq.not": '',
"neq": '',
"not": '',
"sq": '',
"sq.neq": '',
],
union: [
'',
"arrow": '',
"big": '',
"dot": '',
"dot.big": '',
"double": '',
"minus": '',
"or": '',
"plus": '',
"plus.big": '',
"sq": '',
"sq.big": '',
"sq.double": '',
],
sect: [
'∩',
"and": '',
"big": '',
"dot": '',
"double": '',
"sq": '',
"sq.big": '',
"sq.double": '',
],
without: '',
complement: '',
// Relational algebra.
join: [
'⨝',
"r": '',
"l": '',
"l.r": '',
],
// Logic.
forall: '',
exists: ['∃', "not": ''],
top: '',
bot: '',
not: '¬',
and: [
'∧',
"big": '',
"curly": '',
"dot": '',
"double": '',
],
or: [
'',
"big": '',
"curly": '',
"dot": '',
"double": '',
],
models: '',
therefore: '',
because: '',
qed: '',
tack: [
"r": '',
"r.long": '',
"l": '',
"l.long": '',
"l.short": '',
"t": '',
"t.big": '',
"t.double": '',
"t.short": '',
"b": '',
"b.big": '',
"b.double": '',
"b.short": '',
"l.r": '',
],
// Geometry.
perp: ['⟂', "circle": ''],
parallel: ['∥', "circle": '', "not": ''],
angle: [
'∠',
"acute": '',
"arc": '',
"arc.rev": '',
"rev": '',
"right": '',
"right.rev": '',
"right.arc": '',
"right.dot": '',
"right.sq": '',
"spatial": '',
"spheric": '',
"spheric.rev": '',
"spheric.top": '',
],
// Analysis.
infty: '',
sum: ['∑', "integral": ''],
prod: ['∏', "co": ''],
integral: [
'∫',
"arrow.hook": '',
"ccw": '',
"cont": '',
"cont.ccw": '',
"cont.cw": '',
"cw": '',
"double": '',
"quad": '',
"sect": '',
"sq": '',
"surf": '',
"times": '',
"triple": '',
"union": '',
"vol": '',
],
prop: '',
divides: ['', "not": ''],
// Miscellaneous.
diff: '',
nabla: '',
kelvin: '',
micro: 'µ',
degree: ['°', "c": '', "f": ''],
planck: ['', "reduce": ''],
angstrom: '',
diameter: '',
}

1355
library/src/text/emoji.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,22 @@
//! Text handling.
mod deco;
mod emoji;
mod misc;
mod quotes;
mod raw;
mod shaping;
mod shift;
mod symbol;
mod symbols;
pub use self::deco::*;
pub use self::emoji::*;
pub use self::misc::*;
pub use self::quotes::*;
pub use self::raw::*;
pub use self::shaping::*;
pub use self::shift::*;
pub use self::symbol::*;
pub use self::symbols::*;
use std::borrow::Cow;

View File

@ -1,111 +0,0 @@
use crate::prelude::*;
use crate::text::TextNode;
/// # Symbol
/// A symbol identified by symmie notation.
///
/// Symmie is Typst's notation for Unicode symbols. It is based on the idea of
/// _modifiers._ Many symbols in Unicode are very similar. In symmie, such
/// groups of symbols share a common name. To distinguish between the symbols
/// within a group, we use one or multiple modifiers that are separated from the
/// name by colons.
///
/// There is currently no easily viewable list of all names, but in the
/// meantime you can rely on the autocompletion in Typst's web editor.
///
/// ## Syntax
/// This function also has dedicated syntax: In markup, you can enclose symmie
/// notation within colons to produce a symbol. And in math, you can just write
/// the notation directly. There, all letter sequence of length at least two are
/// automatically parsed as symbols (unless a variable of that name is defined).
///
/// Additionally, some very common but hard to type symbols can be expressed with
/// dedicated shortcuts. These are:
///
/// | Symmie | Shorthand | Result |
/// | ----------- | --------- | ------ |
/// | `dots:b` | `...` | … |
/// | `dash:en` | `---` | |
/// | `dash:em` | `--` | — |
/// | none yet | `-?` | A soft hyphen |
/// | none yet | `~` | A non breaking space |
///
/// Within math mode, additional shorthands are available:
///
/// | Symmie | Shorthand | Result |
/// | ------------------ | --------- | ------ |
/// | `arrow:r` | `->` | `→` |
/// | `arrow:r:double` | `=>` | `⇒` |
/// | `arrow:l` | `<-` | `←` |
/// | `arrow:r:bar` | <code>&VerticalLine;-></code> | `↦` |
/// | `arrow:l:r` | `<->` | `↔` |
/// | `arrow:l:r:double` | `<=>` | `⇔` |
/// | `eq:not` | `!=` | `≠` |
/// | `eq:gt` | `>=` | `≥` |
/// | `eq:lt` | `<=` | `≤` |
/// | `colon:eq` | `:=` | `≔` |
///
/// ## Example
/// ```
/// // In text, with colons.
/// :arrow:l: \
/// :arrow:r: \
/// :arrow:t: \
/// :turtle: \
/// :face:halo: \
/// :woman:old:
///
/// // In math, directly.
/// $f : NN -> RR$ \
/// $A sub:eq B without C$ \
/// $a times:div b eq:not c$
/// ```
///
/// ## Parameters
/// - notation: EcoString (positional, required)
/// The symbol's symmie notation.
///
/// Consists of a name, followed by a number colon-separated modifiers
/// in no particular order.
///
/// ### Example
/// ```
/// #symbol("NN") \
/// #symbol("face:grin")
/// ```
///
/// ## Category
/// text
#[func]
#[capable(Show)]
#[derive(Debug, Hash)]
pub struct SymbolNode(pub EcoString);
#[node]
impl SymbolNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("notation")?).pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"notation" => Some(Value::Str(self.0.clone().into())),
_ => None,
}
}
}
impl Show for SymbolNode {
fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult<Content> {
match symmie::get(&self.0) {
Some(c) => Ok(TextNode::packed(c)),
None => {
if let Some(span) = this.span() {
bail!(span, "unknown symbol");
}
Ok(Content::empty())
}
}
}
}

460
library/src/text/symbols.rs Normal file
View File

@ -0,0 +1,460 @@
use typst::model::{symbols, Module, Scope};
/// A module with all symbols.
pub fn sym() -> Module {
let mut scope = Scope::new();
define(&mut scope);
Module::new("sym").with_scope(scope)
}
symbols! {
define,
// Space and control.
space: [
' ',
"em": '\u{2003}',
"en": '\u{2002}',
"fig": '\u{2007}',
"hair": '\u{200A}',
"ideo": '\u{3000}',
"nobreak": '\u{A0}',
"punct": '\u{2008}',
"quarter": '\u{2005}',
"sixth": '\u{2006}',
"thin": '\u{2009}',
"third": '\u{2004}',
],
wj: '\u{2060}',
zwj: '\u{200D}',
zwnj: '\u{200C}',
zws: '\u{200B}',
// Punctuation.
dot: ['.', "c": '·'],
dots: [
"h": '',
"h.c": '',
"v": '',
"down": '',
"up": '',
],
colon: [
':',
"eq": '',
"double.eq": '',
],
comma: ',',
semi: [';', "rev": ''],
quest: ['?', "double": '', "excl": '', "inv": '¿'],
excl: ['!', "double": '', "inv": '¡', "quest": ''],
interrobang: '',
hash: '#',
at: '@',
section: '§',
percent: '%',
permille: '',
co: '',
pilcrow: ['¶', "rev": ''],
dagger: ['†', "double": ''],
slash: ['/', "double": '', "triple": ''],
backslash: ['\\', "circle": '', "not": ''],
ast: ['*', "low": '', "double": '', "triple": ''],
amp: ['&', "inv": ''],
dash: [
"en": '',
"em": '',
"fig": '',
"wave": '',
"colon": '',
"circle": '',
"wave.double": '',
],
hyph: [
'',
"minus": '\u{2D}',
"nobreak": '\u{2011}',
"point": '',
"soft": '\u{ad}',
],
prime: [
'',
"rev": '',
"double": '',
"double.rev": '',
"triple": '',
"triple.rev": '',
"quad": '',
],
quote: [
"double": '"',
"single": '\'',
"l.double": '',
"l.single": '',
"r.double": '',
"r.single": '',
"angle.l.double": '«',
"angle.l.single": '',
"angle.r.double": '»',
"angle.r.single": '',
"high.double": '',
"high.single": '',
"low.double": '',
"low.single": '',
],
// Delimiters.
paren: [
"l": '(',
"r": ')',
"t": '',
"b": '',
],
bracket: [
"l": '[',
"r": ']',
"t": '',
"b": '',
],
brace: [
"l": '{',
"r": '}',
"t": '',
"b": '',
],
turtle: [
"l": '',
"r": '',
"t": '',
"b": '',
],
angled: ["l": '', "r": ''],
bar: [
"v": '|',
"v.double": '',
"v.triple": '',
"v.broken": '¦',
"v.circle": '',
"h": '',
],
fence: [
"l": '',
"l.double": '',
"r": '',
"r.double": '',
"dotted": '',
],
// Accents.
acute: ['´', "double": '˝'],
breve: '˘',
caret: '',
caron: 'ˇ',
cedilla: '¸',
circum: '^',
diaer: '¨',
grave: '`',
macron: '¯',
tilde: '~',
overline: '',
// Currency.
bitcoin: '',
dollar: '$',
euro: '',
franc: '',
lira: '',
peso: '',
pound: '£',
ruble: '',
rupee: '',
won: '',
yen: '¥',
// Miscellaneous.
ballot: ['☐', "x": ''],
checkmark: ['✓', "light": '🗸'],
copyright: ['©', "sound": ''],
floral: ['❦', "l": '', "r": ''],
notes: ["up": '🎜', "down": '🎝'],
refmark: '',
servicemark: '',
maltese: '',
suit: [
"club": '',
"diamond": '',
"heart": '',
"spade": '',
],
// Shapes.
circle: [
"stroked": '',
"stroked.tiny": '',
"stroked.small": '',
"stroked.big": '',
"filled": '',
"filled.tiny": '',
"filled.small": '',
"filled.big": '',
"dotted": '',
"nested": '',
],
square: [
"stroked": '',
"stroked.tiny": '',
"stroked.small": '',
"stroked.medium": '',
"stroked.big": '',
"stroked.dotted": '',
"stroked.rounded": '',
"filled": '',
"filled.tiny": '',
"filled.small": '',
"filled.medium": '',
"filled.big": '',
],
ellipse: [
"stroked.h": '',
"stroked.v": '',
"filled.h": '',
"filled.v": '',
],
rect: [
"stroked.h": '',
"stroked.v": '',
"filled.h": '',
"filled.v": '',
],
triangle: [
"stroked.r": '',
"stroked.l": '',
"stroked.t": '',
"stroked.b": '',
"stroked.bl": '',
"stroked.br": '',
"stroked.tl": '',
"stroked.tr": '',
"stroked.small.r": '',
"stroked.small.b": '',
"stroked.small.l": '',
"stroked.small.t": '',
"stroked.rounded": '🛆',
"stroked.nested": '',
"stroked.dot": '',
"filled.r": '',
"filled.l": '',
"filled.t": '',
"filled.b": '',
"filled.bl": '',
"filled.br": '',
"filled.tl": '',
"filled.tr": '',
"filled.small.r": '',
"filled.small.b": '',
"filled.small.l": '',
"filled.small.t": '',
],
diamond: [
"stroked": '',
"stroked.small": '',
"stroked.medium": '',
"stroked.dot": '',
"filled": '',
"filled.medium": '',
"filled.small": '',
],
lozenge: [
"stroked": '',
"stroked.small": '',
"stroked.medium": '',
"filled": '',
"filled.small": '',
"filled.medium": '',
],
penta: ["stroked": '', "filled": ''],
hexa: ["stroked": '', "filled": ''],
// Arrows and harpoons.
arrow: [
"r": '',
"r.long.bar": '',
"r.bar": '',
"r.curve": '',
"r.dashed": '',
"r.dotted": '',
"r.double": '',
"r.double.bar": '',
"r.double.long": '',
"r.double.long.bar": '',
"r.double.not": '',
"r.filled": '',
"r.hook": '',
"r.long": '',
"r.long.squiggly": '',
"r.loop": '',
"r.not": '',
"r.quad": '',
"r.squiggly": '',
"r.stop": '',
"r.stroked": '',
"r.tail": '',
"r.triple": '',
"r.twohead.bar": '',
"r.twohead": '',
"r.wave": '',
"l": '',
"l.bar": '',
"l.curve": '',
"l.dashed": '',
"l.dotted": '',
"l.double": '',
"l.double.bar": '',
"l.double.long": '',
"l.double.long.bar": '',
"l.double.not": '',
"l.filled": '',
"l.hook": '',
"l.long": '',
"l.long.bar": '',
"l.long.squiggly": '',
"l.loop": '',
"l.not": '',
"l.quad": '',
"l.squiggly": '',
"l.stop": '',
"l.stroked": '',
"l.tail": '',
"l.triple": '',
"l.twohead.bar": '',
"l.twohead": '',
"l.wave": '',
"t": '',
"t.bar": '',
"t.curve": '',
"t.dashed": '',
"t.double": '',
"t.filled": '',
"t.quad": '',
"t.stop": '',
"t.stroked": '',
"t.triple": '',
"t.twohead": '',
"b": '',
"b.bar": '',
"b.curve": '',
"b.dashed": '',
"b.double": '',
"b.filled": '',
"b.quad": '',
"b.stop": '',
"b.stroked": '',
"b.triple": '',
"b.twohead": '',
"l.r": '',
"l.r.double": '',
"l.r.double.long": '',
"l.r.double.not": '',
"l.r.filled": '',
"l.r.long": '',
"l.r.not": '',
"l.r.stroked": '',
"l.r.wave": '',
"t.b": '',
"t.b.double": '',
"t.b.filled": '',
"t.b.stroked": '',
"tr": '',
"tr.double": '',
"tr.filled": '',
"tr.hook": '',
"tr.stroked": '',
"br": '',
"br.double": '',
"br.filled": '',
"br.hook": '',
"br.stroked": '',
"tl": '',
"tl.double": '',
"tl.filled": '',
"tl.hook": '',
"tl.stroked": '',
"bl": '',
"bl.double": '',
"bl.filled": '',
"bl.hook": '',
"bl.stroked": '',
"tl.br": '',
"tr.bl": '',
"ccw": '',
"ccw.half": '',
"cw": '',
"cw.half": '',
"zigzag": '',
],
arrows: [
"rr": '',
"ll": '',
"tt": '',
"bb": '',
"lr": '',
"lr.stop": '',
"rl": '',
"tb": '',
"bt": '',
"rrr": '',
"lll": '',
],
arrowhead: [
"t": '',
"b": '',
],
harpoon: [
"rt": '',
"rt.bar": '',
"rt.stop": '',
"rb": '',
"rb.bar": '',
"rb.stop": '',
"lt": '',
"lt.bar": '',
"lt.stop": '',
"lb": '',
"lb.bar": '',
"lb.stop": '',
"tl": '',
"tl.bar": '',
"tl.stop": '',
"tr": '',
"tr.bar": '',
"tr.stop": '',
"bl": '',
"bl.bar": '',
"bl.stop": '',
"br": '',
"br.bar": '',
"br.stop": '',
"lt.rt": '',
"lb.rb": '',
"lb.rt": '',
"lt.rb": '',
"tl.bl": '',
"tr.br": '',
"tl.br": '',
"tr.bl": '',
],
harpoons: [
"rtrb": '',
"blbr": '',
"bltr": '',
"lbrb": '',
"ltlb": '',
"ltrb": '',
"ltrt": '',
"rblb": '',
"rtlb": '',
"rtlt": '',
"tlbr": '',
"tltr": '',
],
}

View File

@ -43,8 +43,6 @@ pub struct LangItems {
pub text_id: NodeId,
/// Get the string if this is a text node.
pub text_str: fn(&Content) -> Option<&str>,
/// Symbol notation: `:arrow:l:`.
pub symbol: fn(notation: EcoString) -> Content,
/// A smart quote: `'` or `"`.
pub smart_quote: fn(double: bool) -> Content,
/// A paragraph break.

View File

@ -23,6 +23,7 @@ mod module;
mod ops;
mod realize;
mod scope;
mod symbol;
mod typeset;
#[doc(hidden)]
@ -42,5 +43,6 @@ pub use self::realize::*;
pub use self::scope::*;
pub use self::str::*;
pub use self::styles::*;
pub use self::symbol::*;
pub use self::typeset::*;
pub use self::value::*;

View File

@ -108,6 +108,15 @@ impl Scope {
self.0.insert(var.into(), Slot::new(value.into(), Kind::Captured));
}
/// Copy definitions from another scope that aren't yet defined in this one.
pub fn copy_from(&mut self, other: &Self) {
for (name, value) in other.iter() {
self.0
.entry(name.clone())
.or_insert_with(|| Slot::new(value.clone(), Kind::Normal));
}
}
/// Try to access a variable immutably.
pub fn get(&self, var: &str) -> Option<&Value> {
self.0.get(var).map(Slot::read)

152
src/model/symbol.rs Normal file
View File

@ -0,0 +1,152 @@
use std::cmp::Reverse;
use std::collections::BTreeSet;
use std::fmt::{self, Debug, Formatter, Write};
use crate::diag::StrResult;
use crate::util::EcoString;
/// Define a list of symbols.
#[macro_export]
#[doc(hidden)]
macro_rules! __symbols {
($func:ident, $($name:ident: $value:tt),* $(,)?) => {
pub(super) fn $func(scope: &mut $crate::model::Scope) {
$(scope.define(stringify!($name), $crate::model::symbols!(@one $value));)*
}
};
(@one $c:literal) => { $crate::model::Symbol::new($c) };
(@one [$($first:literal $(: $second:literal)?),* $(,)?]) => {
$crate::model::Symbol::list(&[
$($crate::model::symbols!(@pair $first $(: $second)?)),*
])
};
(@pair $first:literal) => { ("", $first) };
(@pair $first:literal: $second:literal) => { ($first, $second) };
}
#[doc(inline)]
pub use crate::__symbols as symbols;
/// A symbol.
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct Symbol {
repr: Repr,
modifiers: EcoString,
}
/// A collection of symbols.
#[derive(Clone, Eq, PartialEq, Hash)]
enum Repr {
Single(char),
List(&'static [(&'static str, char)]),
}
impl Symbol {
/// Create a new symbol from a single character.
pub fn new(c: char) -> Self {
Self { repr: Repr::Single(c), modifiers: EcoString::new() }
}
/// Create a symbol with variants.
#[track_caller]
pub fn list(list: &'static [(&'static str, char)]) -> Self {
debug_assert!(!list.is_empty());
Self {
repr: Repr::List(list),
modifiers: EcoString::new(),
}
}
/// Get the symbol's text.
pub fn get(&self) -> char {
match self.repr {
Repr::Single(c) => c,
Repr::List(list) => find(list, &self.modifiers).unwrap(),
}
}
/// Apply a modifier to the symbol.
pub fn modified(mut self, modifier: &str) -> StrResult<Self> {
if !self.modifiers.is_empty() {
self.modifiers.push('.');
}
self.modifiers.push_str(modifier);
if match self.repr {
Repr::Single(_) => true,
Repr::List(list) => find(list, &self.modifiers).is_none(),
} {
Err("unknown modifier")?
}
Ok(self)
}
/// The characters that are covered by this symbol.
pub fn chars(&self) -> impl Iterator<Item = char> {
let (first, slice) = match self.repr {
Repr::Single(c) => (Some(c), [].as_slice()),
Repr::List(list) => (None, list),
};
first.into_iter().chain(slice.iter().map(|&(_, c)| c))
}
/// Possible modifiers.
pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ {
let mut set = BTreeSet::new();
if let Repr::List(list) = self.repr {
for modifier in list.iter().flat_map(|(name, _)| name.split('.')) {
if !modifier.is_empty() && !contained(&self.modifiers, modifier) {
set.insert(modifier);
}
}
}
set.into_iter()
}
}
impl Debug for Symbol {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_char(self.get())
}
}
/// Find the best symbol from the list.
fn find(list: &[(&str, char)], modifiers: &str) -> Option<char> {
let mut best = None;
let mut best_score = None;
// Find the best table entry with this name.
'outer: for candidate in list {
for modifier in parts(modifiers) {
if !contained(candidate.0, modifier) {
continue 'outer;
}
}
let mut matching = 0;
let mut total = 0;
for modifier in parts(candidate.0) {
if contained(modifiers, modifier) {
matching += 1;
}
total += 1;
}
let score = (matching, Reverse(total));
if best_score.map_or(true, |b| score > b) {
best = Some(candidate.1);
best_score = Some(score);
}
}
best
}
/// Split a modifier list into its parts.
fn parts(modifiers: &str) -> impl Iterator<Item = &str> {
modifiers.split('.').filter(|s| !s.is_empty())
}
/// Whether the modifier string contains the modifier `m`.
fn contained(modifiers: &str, m: &str) -> bool {
parts(modifiers).any(|part| part == m)
}

View File

@ -7,10 +7,12 @@ use std::sync::Arc;
use siphasher::sip128::{Hasher128, SipHasher};
use super::{
format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, Label, Module, Str,
format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, Label, Module,
Str, Symbol,
};
use crate::diag::StrResult;
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor};
use crate::syntax::Span;
use crate::util::{format_eco, EcoString};
/// A computational value.
@ -38,6 +40,8 @@ pub enum Value {
Fraction(Fr),
/// A color value: `#f79143ff`.
Color(Color),
/// A symbol: `arrow.l`.
Symbol(Symbol),
/// A string: `"string"`.
Str(Str),
/// A label: `<intro>`.
@ -81,6 +85,7 @@ impl Value {
Self::Relative(_) => Rel::<Length>::TYPE_NAME,
Self::Fraction(_) => Fr::TYPE_NAME,
Self::Color(_) => Color::TYPE_NAME,
Self::Symbol(_) => Symbol::TYPE_NAME,
Self::Str(_) => Str::TYPE_NAME,
Self::Label(_) => Label::TYPE_NAME,
Self::Content(_) => Content::TYPE_NAME,
@ -98,11 +103,33 @@ impl Value {
T::cast(self)
}
/// Try to access a field on the value.
pub fn field(&self, field: &str) -> StrResult<Value> {
match self {
Self::Symbol(symbol) => symbol.clone().modified(&field).map(Self::Symbol),
Self::Dict(dict) => dict.at(&field).cloned(),
Self::Content(content) => content
.field(&field)
.ok_or_else(|| format_eco!("unknown field `{field}`")),
Self::Module(module) => module.get(&field).cloned(),
v => Err(format_eco!("cannot access fields on type {}", v.type_name())),
}
}
/// Return the debug representation of the value.
pub fn repr(&self) -> Str {
format_str!("{:?}", self)
}
/// Attach a span to the value, if possibly.
pub fn spanned(self, span: Span) -> Self {
match self {
Value::Content(v) => Value::Content(v.spanned(span)),
Value::Func(v) => Value::Func(v.spanned(span)),
v => v,
}
}
/// Return the display representation of the value.
pub fn display(self) -> Content {
match self {
@ -110,6 +137,7 @@ impl Value {
Self::Int(v) => item!(text)(format_eco!("{}", v)),
Self::Float(v) => item!(text)(format_eco!("{}", v)),
Self::Str(v) => item!(text)(v.into()),
Self::Symbol(v) => item!(text)(v.get().into()),
Self::Content(v) => v,
Self::Func(_) => Content::empty(),
Self::Module(module) => module.content(),
@ -122,6 +150,8 @@ impl Value {
match self {
Self::Int(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::Str(v) => item!(math_atom)(v.into()),
_ => self.display(),
}
}
@ -147,6 +177,7 @@ impl Debug for Value {
Self::Relative(v) => Debug::fmt(v, f),
Self::Fraction(v) => Debug::fmt(v, f),
Self::Color(v) => Debug::fmt(v, f),
Self::Symbol(v) => Debug::fmt(v, f),
Self::Str(v) => Debug::fmt(v, f),
Self::Label(v) => Debug::fmt(v, f),
Self::Content(_) => f.pad("[...]"),
@ -187,6 +218,7 @@ impl Hash for Value {
Self::Relative(v) => v.hash(state),
Self::Fraction(v) => v.hash(state),
Self::Color(v) => v.hash(state),
Self::Symbol(v) => v.hash(state),
Self::Str(v) => v.hash(state),
Self::Label(v) => v.hash(state),
Self::Content(v) => v.hash(state),
@ -398,11 +430,13 @@ primitive! { Rel<Length>: "relative length",
}
primitive! { Fr: "fraction", Fraction }
primitive! { Color: "color", Color }
primitive! { Symbol: "symbol", Symbol }
primitive! { Str: "string", Str }
primitive! { Label: "label", Label }
primitive! { Content: "content",
Content,
None => Content::empty(),
Symbol(symbol) => item!(text)(symbol.get().into()),
Str(text) => item!(text)(text.into())
}
primitive! { Array: "array", Array }

View File

@ -89,8 +89,6 @@ pub enum Expr {
/// A shorthand for a unicode codepoint. For example, `~` for non-breaking
/// space or `-?` for a soft hyphen.
Shorthand(Shorthand),
/// Symbol notation: `:arrow:l:`.
Symbol(Symbol),
/// A smart quote: `'` or `"`.
SmartQuote(SmartQuote),
/// Strong content: `*Strong*`.
@ -413,18 +411,6 @@ impl Shorthand {
}
}
node! {
/// Symbol notation: `:arrow:l:`.
Symbol
}
impl Symbol {
/// Get the symbol's notation.
pub fn get(&self) -> &str {
self.0.text().trim_matches(':')
}
}
node! {
/// A smart quote: `'` or `"`.
SmartQuote

View File

@ -24,9 +24,6 @@ pub enum SyntaxKind {
/// A shorthand for a unicode codepoint. For example, `~` for non-breaking
/// space or `-?` for a soft hyphen.
Shorthand,
/// Symbol notation: `:arrow:l:`. The string only contains the inner part
/// without leading and trailing dot.
Symbol,
/// A smart quote: `'` or `"`.
SmartQuote,
/// Strong content: `*Strong*`.
@ -332,7 +329,6 @@ impl SyntaxKind {
Self::Parbreak => "paragraph break",
Self::Escape => "escape sequence",
Self::Shorthand => "shorthand",
Self::Symbol => "symbol notation",
Self::Strong => "strong content",
Self::Emph => "emphasized content",
Self::Raw => "raw block",

View File

@ -167,21 +167,12 @@ impl Lexer<'_> {
fn markup(&mut self, start: usize, c: char) -> SyntaxKind {
match c {
'\\' => self.backslash(),
':' if self.s.at(is_id_start) => self.maybe_symbol(),
'`' => self.raw(),
'h' if self.s.eat_if("ttp://") => self.link(),
'h' if self.s.eat_if("ttps://") => self.link(),
'0'..='9' => self.numbering(start),
'<' if self.s.at(is_id_continue) => self.label(),
'@' if self.s.at(is_id_continue) => self.reference(),
'#' if self.s.eat_if('{') => SyntaxKind::LeftBrace,
'#' if self.s.eat_if('[') => SyntaxKind::LeftBracket,
'#' if self.s.at(is_id_start) => {
match keyword(self.s.eat_while(is_id_continue)) {
Some(keyword) => keyword,
None => SyntaxKind::Ident,
}
}
'.' if self.s.eat_if("..") => SyntaxKind::Shorthand,
'-' if self.s.eat_if("--") => SyntaxKind::Shorthand,
@ -190,8 +181,7 @@ impl Lexer<'_> {
'*' if !self.in_word() => SyntaxKind::Star,
'_' if !self.in_word() => SyntaxKind::Underscore,
'{' => SyntaxKind::LeftBrace,
'}' => SyntaxKind::RightBrace,
'#' if !self.s.at(char::is_whitespace) => SyntaxKind::Hashtag,
'[' => SyntaxKind::LeftBracket,
']' => SyntaxKind::RightBracket,
'\'' => SyntaxKind::SmartQuote,
@ -241,26 +231,6 @@ impl Lexer<'_> {
}
}
fn maybe_symbol(&mut self) -> SyntaxKind {
let start = self.s.cursor();
let mut end = start;
while !self.s.eat_while(is_id_continue).is_empty() && self.s.at(':') {
end = self.s.cursor();
self.s.eat();
}
self.s.jump(end);
if start < end {
self.s.expect(':');
SyntaxKind::Symbol
} else if self.mode == LexMode::Markup {
SyntaxKind::Colon
} else {
SyntaxKind::Atom
}
}
fn raw(&mut self) -> SyntaxKind {
let mut backticks = 1;
while self.s.eat_if('`') {
@ -408,7 +378,6 @@ impl Lexer<'_> {
fn math(&mut self, start: usize, c: char) -> SyntaxKind {
match c {
'\\' => self.backslash(),
':' if self.s.at(is_id_start) => self.maybe_symbol(),
'"' => self.string(),
'.' if self.s.eat_if("..") => SyntaxKind::Shorthand,
@ -434,9 +403,10 @@ impl Lexer<'_> {
'^' => SyntaxKind::Hat,
'&' => SyntaxKind::MathAlignPoint,
// Identifiers and symbol notation.
// Identifiers.
c if is_math_id_start(c) && self.s.at(is_math_id_continue) => {
self.math_ident()
self.s.eat_while(is_math_id_continue);
SyntaxKind::MathIdent
}
// Other math atoms.
@ -444,25 +414,6 @@ impl Lexer<'_> {
}
}
fn math_ident(&mut self) -> SyntaxKind {
self.s.eat_while(is_math_id_continue);
let mut symbol = false;
while self.s.eat_if(':') && !self.s.eat_while(char::is_alphanumeric).is_empty() {
symbol = true;
}
if symbol {
return SyntaxKind::Symbol;
}
if self.s.scout(-1) == Some(':') {
self.s.uneat();
}
SyntaxKind::Ident
}
fn atom(&mut self, start: usize, c: char) -> SyntaxKind {
// Keep numbers and grapheme clusters together.
if c.is_numeric() {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -23,6 +23,6 @@ $ accent(a, grave),
accent(a, overline),
accent(a, breve),
accent(a, dot),
accent(a, dia),
accent(a, diaer),
accent(a, caron),
accent(a, arrow) $

View File

@ -2,7 +2,7 @@
#let kinds = (math.serif, math.sans, math.cal, math.frak, math.mono, math.bb)
#let modifiers = (v => v, math.italic, math.bold, v => math.italic(math.bold(v)))
#let cells = ([:triangle:nested:], [--], [`italic`], [`bold`], [both])
#let cells = (sym.triangle.nested, [--], [`italic`], [`bold`], [both])
#for kk in kinds {
cells.push(raw(repr(kk).trim("<function ").trim(">")))
for mm in modifiers {

View File

@ -19,6 +19,6 @@ $ sqrt(x^2) = frac(x, 1) $
$ "profit" = "income" - "expenses" $
$ x < #for i in range(5) [$ #i < $] y $
$ 1 + 2 = #{1 + 2} $
$ A sub:eq:not B $
$ A subset.eq.not B $
```
<table>

View File

@ -1,27 +1,18 @@
// Test symbol notation.
// Test symbols.
---
:face:
:face:unknown:
:woman:old:
:turtle:
#emoji.face
#emoji.woman.old
#emoji.turtle
#set text("New Computer Modern Math")
:arrow:
:arrow:l:
:arrow:r:squiggly:
#symbol(("arrow", "tr", "hook").join(":"))
#sym.arrow
#sym.arrow.l
#sym.arrow.r.squiggly
#sym.arrow.tr.hook
#sym.arrow.r;this and this#sym.arrow.l;
---
Just a: colon. \
Still :not a symbol. \
Also not:a symbol \
:arrow:r:this and this:arrow:l: \
---
#show symbol.where(notation: "my:custom"): "MY"
This is :my:custom: notation.
---
// Error: 1-14 unknown symbol
:nonexisting:
// Error: 13-20 unknown modifier
#emoji.face.garbage