mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Symbol notation
This commit is contained in:
parent
1d324235bd
commit
c2e458a133
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1009,6 +1009,11 @@ dependencies = [
|
|||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "symmie"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/typst/symmie#8504bf7ec0d8996d160832c2724ba024ab6e988a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.102"
|
version = "1.0.102"
|
||||||
@ -1175,6 +1180,7 @@ dependencies = [
|
|||||||
"roxmltree",
|
"roxmltree",
|
||||||
"rustybuzz",
|
"rustybuzz",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"symmie",
|
||||||
"syntect",
|
"syntect",
|
||||||
"ttf-parser 0.17.1",
|
"ttf-parser 0.17.1",
|
||||||
"typed-arena",
|
"typed-arena",
|
||||||
|
@ -21,6 +21,7 @@ rex = { git = "https://github.com/laurmaedje/ReX" }
|
|||||||
roxmltree = "0.14"
|
roxmltree = "0.14"
|
||||||
rustybuzz = "0.5"
|
rustybuzz = "0.5"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
symmie = { git = "https://github.com/typst/symmie" }
|
||||||
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
|
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
|
||||||
ttf-parser = "0.17"
|
ttf-parser = "0.17"
|
||||||
typed-arena = "2"
|
typed-arena = "2"
|
||||||
|
@ -34,6 +34,7 @@ fn scope() -> Scope {
|
|||||||
// Text.
|
// Text.
|
||||||
std.def_node::<text::TextNode>("text");
|
std.def_node::<text::TextNode>("text");
|
||||||
std.def_node::<text::LinebreakNode>("linebreak");
|
std.def_node::<text::LinebreakNode>("linebreak");
|
||||||
|
std.def_node::<text::SymbolNode>("symbol");
|
||||||
std.def_node::<text::SmartQuoteNode>("smartquote");
|
std.def_node::<text::SmartQuoteNode>("smartquote");
|
||||||
std.def_node::<text::StrongNode>("strong");
|
std.def_node::<text::StrongNode>("strong");
|
||||||
std.def_node::<text::EmphNode>("emph");
|
std.def_node::<text::EmphNode>("emph");
|
||||||
@ -173,6 +174,7 @@ fn items() -> LangItems {
|
|||||||
text: |text| text::TextNode(text).pack(),
|
text: |text| text::TextNode(text).pack(),
|
||||||
text_id: NodeId::of::<text::TextNode>(),
|
text_id: NodeId::of::<text::TextNode>(),
|
||||||
text_str: |content| Some(&content.to::<text::TextNode>()?.0),
|
text_str: |content| Some(&content.to::<text::TextNode>()?.0),
|
||||||
|
symbol: |notation| text::SymbolNode(notation).pack(),
|
||||||
smart_quote: |double| text::SmartQuoteNode { double }.pack(),
|
smart_quote: |double| text::SmartQuoteNode { double }.pack(),
|
||||||
parbreak: || layout::ParbreakNode.pack(),
|
parbreak: || layout::ParbreakNode.pack(),
|
||||||
strong: |body| text::StrongNode(body).pack(),
|
strong: |body| text::StrongNode(body).pack(),
|
||||||
|
@ -6,6 +6,7 @@ mod quotes;
|
|||||||
mod raw;
|
mod raw;
|
||||||
mod shaping;
|
mod shaping;
|
||||||
mod shift;
|
mod shift;
|
||||||
|
mod symbol;
|
||||||
|
|
||||||
pub use self::deco::*;
|
pub use self::deco::*;
|
||||||
pub use self::misc::*;
|
pub use self::misc::*;
|
||||||
@ -13,6 +14,7 @@ pub use self::quotes::*;
|
|||||||
pub use self::raw::*;
|
pub use self::raw::*;
|
||||||
pub use self::shaping::*;
|
pub use self::shaping::*;
|
||||||
pub use self::shift::*;
|
pub use self::shift::*;
|
||||||
|
pub use self::symbol::*;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
35
library/src/text/symbol.rs
Normal file
35
library/src/text/symbol.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use crate::text::TextNode;
|
||||||
|
|
||||||
|
/// A symbol identified by symmie notation.
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct SymbolNode(pub EcoString);
|
||||||
|
|
||||||
|
#[node(Show)]
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -265,6 +265,7 @@ impl Eval for ast::MarkupNode {
|
|||||||
Self::Text(v) => v.eval(vm)?,
|
Self::Text(v) => v.eval(vm)?,
|
||||||
Self::Escape(v) => (vm.items.text)(v.get().into()),
|
Self::Escape(v) => (vm.items.text)(v.get().into()),
|
||||||
Self::Shorthand(v) => v.eval(vm)?,
|
Self::Shorthand(v) => v.eval(vm)?,
|
||||||
|
Self::Symbol(v) => v.eval(vm)?,
|
||||||
Self::SmartQuote(v) => v.eval(vm)?,
|
Self::SmartQuote(v) => v.eval(vm)?,
|
||||||
Self::Strong(v) => v.eval(vm)?,
|
Self::Strong(v) => v.eval(vm)?,
|
||||||
Self::Emph(v) => v.eval(vm)?,
|
Self::Emph(v) => v.eval(vm)?,
|
||||||
@ -306,6 +307,14 @@ impl Eval for ast::Shorthand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Eval for ast::Symbol {
|
||||||
|
type Output = Content;
|
||||||
|
|
||||||
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
|
Ok((vm.items.symbol)(self.get().clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Eval for ast::SmartQuote {
|
impl Eval for ast::SmartQuote {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@ pub struct LangItems {
|
|||||||
pub text_id: NodeId,
|
pub text_id: NodeId,
|
||||||
/// Get the string if this is a text node.
|
/// Get the string if this is a text node.
|
||||||
pub text_str: fn(&Content) -> Option<&str>,
|
pub text_str: fn(&Content) -> Option<&str>,
|
||||||
|
/// Symbol notation: `:arrow:l:`.
|
||||||
|
pub symbol: fn(notation: EcoString) -> Content,
|
||||||
/// A smart quote: `'` or `"`.
|
/// A smart quote: `'` or `"`.
|
||||||
pub smart_quote: fn(double: bool) -> Content,
|
pub smart_quote: fn(double: bool) -> Content,
|
||||||
/// A paragraph break.
|
/// A paragraph break.
|
||||||
|
@ -85,6 +85,8 @@ pub enum MarkupNode {
|
|||||||
/// A shorthand for a unicode codepoint. For example, `~` for non-breaking
|
/// A shorthand for a unicode codepoint. For example, `~` for non-breaking
|
||||||
/// space or `-?` for a soft hyphen.
|
/// space or `-?` for a soft hyphen.
|
||||||
Shorthand(Shorthand),
|
Shorthand(Shorthand),
|
||||||
|
/// Symbol notation: `:arrow:l:`.
|
||||||
|
Symbol(Symbol),
|
||||||
/// A smart quote: `'` or `"`.
|
/// A smart quote: `'` or `"`.
|
||||||
SmartQuote(SmartQuote),
|
SmartQuote(SmartQuote),
|
||||||
/// Strong content: `*Strong*`.
|
/// Strong content: `*Strong*`.
|
||||||
@ -119,6 +121,7 @@ impl AstNode for MarkupNode {
|
|||||||
SyntaxKind::Text(_) => node.cast().map(Self::Text),
|
SyntaxKind::Text(_) => node.cast().map(Self::Text),
|
||||||
SyntaxKind::Escape(_) => node.cast().map(Self::Escape),
|
SyntaxKind::Escape(_) => node.cast().map(Self::Escape),
|
||||||
SyntaxKind::Shorthand(_) => node.cast().map(Self::Shorthand),
|
SyntaxKind::Shorthand(_) => node.cast().map(Self::Shorthand),
|
||||||
|
SyntaxKind::Symbol(_) => node.cast().map(Self::Symbol),
|
||||||
SyntaxKind::SmartQuote { .. } => node.cast().map(Self::SmartQuote),
|
SyntaxKind::SmartQuote { .. } => node.cast().map(Self::SmartQuote),
|
||||||
SyntaxKind::Strong => node.cast().map(Self::Strong),
|
SyntaxKind::Strong => node.cast().map(Self::Strong),
|
||||||
SyntaxKind::Emph => node.cast().map(Self::Emph),
|
SyntaxKind::Emph => node.cast().map(Self::Emph),
|
||||||
@ -141,6 +144,7 @@ impl AstNode for MarkupNode {
|
|||||||
Self::Text(v) => v.as_untyped(),
|
Self::Text(v) => v.as_untyped(),
|
||||||
Self::Escape(v) => v.as_untyped(),
|
Self::Escape(v) => v.as_untyped(),
|
||||||
Self::Shorthand(v) => v.as_untyped(),
|
Self::Shorthand(v) => v.as_untyped(),
|
||||||
|
Self::Symbol(v) => v.as_untyped(),
|
||||||
Self::SmartQuote(v) => v.as_untyped(),
|
Self::SmartQuote(v) => v.as_untyped(),
|
||||||
Self::Strong(v) => v.as_untyped(),
|
Self::Strong(v) => v.as_untyped(),
|
||||||
Self::Emph(v) => v.as_untyped(),
|
Self::Emph(v) => v.as_untyped(),
|
||||||
@ -223,6 +227,21 @@ impl Shorthand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node! {
|
||||||
|
/// Symbol notation: `:arrow:l:`.
|
||||||
|
Symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Symbol {
|
||||||
|
/// Get the symbol's notation.
|
||||||
|
pub fn get(&self) -> &EcoString {
|
||||||
|
match self.0.kind() {
|
||||||
|
SyntaxKind::Symbol(v) => v,
|
||||||
|
_ => panic!("symbol is of wrong kind"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A smart quote: `'` or `"`.
|
/// A smart quote: `'` or `"`.
|
||||||
SmartQuote
|
SmartQuote
|
||||||
|
@ -139,6 +139,8 @@ pub enum Category {
|
|||||||
Escape,
|
Escape,
|
||||||
/// An easily typable shortcut to a unicode codepoint.
|
/// An easily typable shortcut to a unicode codepoint.
|
||||||
Shorthand,
|
Shorthand,
|
||||||
|
/// Symbol notation.
|
||||||
|
Symbol,
|
||||||
/// A smart quote.
|
/// A smart quote.
|
||||||
SmartQuote,
|
SmartQuote,
|
||||||
/// Strong markup.
|
/// Strong markup.
|
||||||
@ -285,6 +287,7 @@ impl Category {
|
|||||||
SyntaxKind::Linebreak => Some(Category::Escape),
|
SyntaxKind::Linebreak => Some(Category::Escape),
|
||||||
SyntaxKind::Escape(_) => Some(Category::Escape),
|
SyntaxKind::Escape(_) => Some(Category::Escape),
|
||||||
SyntaxKind::Shorthand(_) => Some(Category::Shorthand),
|
SyntaxKind::Shorthand(_) => Some(Category::Shorthand),
|
||||||
|
SyntaxKind::Symbol(_) => Some(Category::Symbol),
|
||||||
SyntaxKind::SmartQuote { .. } => Some(Category::SmartQuote),
|
SyntaxKind::SmartQuote { .. } => Some(Category::SmartQuote),
|
||||||
SyntaxKind::Strong => Some(Category::Strong),
|
SyntaxKind::Strong => Some(Category::Strong),
|
||||||
SyntaxKind::Emph => Some(Category::Emph),
|
SyntaxKind::Emph => Some(Category::Emph),
|
||||||
@ -369,6 +372,7 @@ impl Category {
|
|||||||
Self::Punctuation => "punctuation.typst",
|
Self::Punctuation => "punctuation.typst",
|
||||||
Self::Escape => "constant.character.escape.typst",
|
Self::Escape => "constant.character.escape.typst",
|
||||||
Self::Shorthand => "constant.character.shorthand.typst",
|
Self::Shorthand => "constant.character.shorthand.typst",
|
||||||
|
Self::Symbol => "constant.symbol.typst",
|
||||||
Self::SmartQuote => "constant.character.quote.typst",
|
Self::SmartQuote => "constant.character.quote.typst",
|
||||||
Self::Strong => "markup.bold.typst",
|
Self::Strong => "markup.bold.typst",
|
||||||
Self::Emph => "markup.italic.typst",
|
Self::Emph => "markup.italic.typst",
|
||||||
|
@ -144,6 +144,9 @@ pub enum SyntaxKind {
|
|||||||
/// A shorthand for a unicode codepoint. For example, `~` for non-breaking
|
/// A shorthand for a unicode codepoint. For example, `~` for non-breaking
|
||||||
/// space or `-?` for a soft hyphen.
|
/// space or `-?` for a soft hyphen.
|
||||||
Shorthand(char),
|
Shorthand(char),
|
||||||
|
/// Symbol notation: `:arrow:l:`. The string only contains the inner part
|
||||||
|
/// without leading and trailing dot.
|
||||||
|
Symbol(EcoString),
|
||||||
/// A smart quote: `'` or `"`.
|
/// A smart quote: `'` or `"`.
|
||||||
SmartQuote { double: bool },
|
SmartQuote { double: bool },
|
||||||
/// Strong content: `*Strong*`.
|
/// Strong content: `*Strong*`.
|
||||||
@ -389,6 +392,7 @@ impl SyntaxKind {
|
|||||||
Self::Linebreak => "linebreak",
|
Self::Linebreak => "linebreak",
|
||||||
Self::Escape(_) => "escape sequence",
|
Self::Escape(_) => "escape sequence",
|
||||||
Self::Shorthand(_) => "shorthand",
|
Self::Shorthand(_) => "shorthand",
|
||||||
|
Self::Symbol(_) => "symbol notation",
|
||||||
Self::Strong => "strong content",
|
Self::Strong => "strong content",
|
||||||
Self::Emph => "emphasized content",
|
Self::Emph => "emphasized content",
|
||||||
Self::Raw(_) => "raw block",
|
Self::Raw(_) => "raw block",
|
||||||
@ -507,6 +511,7 @@ impl Hash for SyntaxKind {
|
|||||||
Self::Linebreak => {}
|
Self::Linebreak => {}
|
||||||
Self::Escape(c) => c.hash(state),
|
Self::Escape(c) => c.hash(state),
|
||||||
Self::Shorthand(c) => c.hash(state),
|
Self::Shorthand(c) => c.hash(state),
|
||||||
|
Self::Symbol(s) => s.hash(state),
|
||||||
Self::SmartQuote { double } => double.hash(state),
|
Self::SmartQuote { double } => double.hash(state),
|
||||||
Self::Strong => {}
|
Self::Strong => {}
|
||||||
Self::Emph => {}
|
Self::Emph => {}
|
||||||
|
@ -230,6 +230,7 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) {
|
|||||||
| SyntaxKind::SmartQuote { .. }
|
| SyntaxKind::SmartQuote { .. }
|
||||||
| SyntaxKind::Escape(_)
|
| SyntaxKind::Escape(_)
|
||||||
| SyntaxKind::Shorthand(_)
|
| SyntaxKind::Shorthand(_)
|
||||||
|
| SyntaxKind::Symbol(_)
|
||||||
| SyntaxKind::Link(_)
|
| SyntaxKind::Link(_)
|
||||||
| SyntaxKind::Raw(_)
|
| SyntaxKind::Raw(_)
|
||||||
| SyntaxKind::Ref(_) => p.eat(),
|
| SyntaxKind::Ref(_) => p.eat(),
|
||||||
|
@ -203,6 +203,7 @@ impl<'s> Tokens<'s> {
|
|||||||
'#' => self.hash(start),
|
'#' => self.hash(start),
|
||||||
'.' if self.s.eat_if("..") => SyntaxKind::Shorthand('\u{2026}'),
|
'.' if self.s.eat_if("..") => SyntaxKind::Shorthand('\u{2026}'),
|
||||||
'-' => self.hyph(),
|
'-' => self.hyph(),
|
||||||
|
':' => self.colon(),
|
||||||
'h' if self.s.eat_if("ttp://") || self.s.eat_if("ttps://") => {
|
'h' if self.s.eat_if("ttp://") || self.s.eat_if("ttps://") => {
|
||||||
self.link(start)
|
self.link(start)
|
||||||
}
|
}
|
||||||
@ -224,7 +225,6 @@ impl<'s> Tokens<'s> {
|
|||||||
'=' => SyntaxKind::Eq,
|
'=' => SyntaxKind::Eq,
|
||||||
'+' => SyntaxKind::Plus,
|
'+' => SyntaxKind::Plus,
|
||||||
'/' => SyntaxKind::Slash,
|
'/' => SyntaxKind::Slash,
|
||||||
':' => SyntaxKind::Colon,
|
|
||||||
|
|
||||||
// Plain text.
|
// Plain text.
|
||||||
_ => self.text(start),
|
_ => self.text(start),
|
||||||
@ -328,6 +328,25 @@ impl<'s> Tokens<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn colon(&mut self) -> SyntaxKind {
|
||||||
|
let start = self.s.cursor();
|
||||||
|
let mut end = start;
|
||||||
|
while !self.s.eat_while(char::is_ascii_alphanumeric).is_empty() && self.s.at(':')
|
||||||
|
{
|
||||||
|
end = self.s.cursor();
|
||||||
|
self.s.eat();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.s.jump(end);
|
||||||
|
|
||||||
|
if start < end {
|
||||||
|
self.s.expect(':');
|
||||||
|
SyntaxKind::Symbol(self.s.get(start..end).into())
|
||||||
|
} else {
|
||||||
|
SyntaxKind::Colon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn link(&mut self, start: usize) -> SyntaxKind {
|
fn link(&mut self, start: usize) -> SyntaxKind {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
self.s.eat_while(|c: char| matches!(c,
|
self.s.eat_while(|c: char| matches!(c,
|
||||||
|
BIN
tests/ref/text/symbol.png
Normal file
BIN
tests/ref/text/symbol.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
27
tests/typ/text/symbol.typ
Normal file
27
tests/typ/text/symbol.typ
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Test symbol notation.
|
||||||
|
|
||||||
|
---
|
||||||
|
:face:
|
||||||
|
:face:unknown:
|
||||||
|
:woman:old:
|
||||||
|
:turtle:
|
||||||
|
|
||||||
|
#set text("NewComputerModernMath")
|
||||||
|
:arrow:
|
||||||
|
:arrow:l:
|
||||||
|
:arrow:r:squiggly:
|
||||||
|
#symbol(("arrow", "tr", "hook").join(":"))
|
||||||
|
|
||||||
|
---
|
||||||
|
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:
|
@ -72,6 +72,10 @@
|
|||||||
"name": "punctuation.definition.ellipsis.typst",
|
"name": "punctuation.definition.ellipsis.typst",
|
||||||
"match": "\\.\\.\\."
|
"match": "\\.\\.\\."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.symbol.typst",
|
||||||
|
"match": ":([a-zA-Z0-9]+:)+"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "markup.bold.typst",
|
"name": "markup.bold.typst",
|
||||||
"begin": "(^\\*|\\*$|((?<=\\W|_)\\*)|(\\*(?=\\W|_)))",
|
"begin": "(^\\*|\\*$|((?<=\\W|_)\\*)|(\\*(?=\\W|_)))",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user