mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
User-defined symbols
This commit is contained in:
parent
3a4c5ae4b9
commit
1ea0a93325
@ -240,6 +240,67 @@ castable! {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Symbol
|
||||||
|
/// Create a custom symbol with modifiers.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// #let envelope = symbol(
|
||||||
|
/// "🖂",
|
||||||
|
/// ("stamped", "🖃"),
|
||||||
|
/// ("stamped.pen", "🖆"),
|
||||||
|
/// ("lightning", "🖄"),
|
||||||
|
/// ("fly", "🖅"),
|
||||||
|
/// )
|
||||||
|
///
|
||||||
|
/// #envelope
|
||||||
|
/// #envelope.stamped
|
||||||
|
/// #envelope.stamped.pen
|
||||||
|
/// #envelope.lightning
|
||||||
|
/// #envelope.fly
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - variants: Variant (positional, variadic)
|
||||||
|
/// The variants of the symbol.
|
||||||
|
///
|
||||||
|
/// Can be a just a string consisting of a single character for the
|
||||||
|
/// modifierless variant or an array with two strings specifying the modifiers
|
||||||
|
/// and the symbol. Individual modifiers should be separated by dots. When
|
||||||
|
/// displaying a symbol, Typst selects the first from the variants that have
|
||||||
|
/// all attached modifiers and the minimum number of other modifiers.
|
||||||
|
///
|
||||||
|
/// - returns: symbol
|
||||||
|
///
|
||||||
|
/// ## Category
|
||||||
|
/// construct
|
||||||
|
#[func]
|
||||||
|
pub fn symbol(args: &mut Args) -> SourceResult<Value> {
|
||||||
|
let mut list: Vec<(EcoString, char)> = vec![];
|
||||||
|
for Spanned { v, span } in args.all::<Spanned<Variant>>()? {
|
||||||
|
if list.iter().any(|(prev, _)| &v.0 == prev) {
|
||||||
|
bail!(span, "duplicate variant");
|
||||||
|
}
|
||||||
|
list.push((v.0, v.1));
|
||||||
|
}
|
||||||
|
Ok(Value::Symbol(Symbol::runtime(list)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A value that can be cast to a symbol.
|
||||||
|
struct Variant(EcoString, char);
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
Variant,
|
||||||
|
c: char => Self(EcoString::new(), c),
|
||||||
|
array: Array => {
|
||||||
|
let mut iter = array.into_iter();
|
||||||
|
match (iter.next(), iter.next(), iter.next()) {
|
||||||
|
(Some(a), Some(b), None) => Self(a.cast()?, b.cast()?),
|
||||||
|
_ => Err("point array must contain exactly two entries")?,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// # String
|
/// # String
|
||||||
/// Convert a value to a string.
|
/// Convert a value to a string.
|
||||||
///
|
///
|
||||||
|
@ -101,6 +101,7 @@ fn global(sym: Module, math: Module) -> Module {
|
|||||||
global.def_func::<compute::LumaFunc>("luma");
|
global.def_func::<compute::LumaFunc>("luma");
|
||||||
global.def_func::<compute::RgbFunc>("rgb");
|
global.def_func::<compute::RgbFunc>("rgb");
|
||||||
global.def_func::<compute::CmykFunc>("cmyk");
|
global.def_func::<compute::CmykFunc>("cmyk");
|
||||||
|
global.def_func::<compute::SymbolFunc>("symbol");
|
||||||
global.def_func::<compute::StrFunc>("str");
|
global.def_func::<compute::StrFunc>("str");
|
||||||
global.def_func::<compute::LabelFunc>("label");
|
global.def_func::<compute::LabelFunc>("label");
|
||||||
global.def_func::<compute::RegexFunc>("regex");
|
global.def_func::<compute::RegexFunc>("regex");
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::fmt::{self, Debug, Display, Formatter, Write};
|
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
@ -38,7 +39,8 @@ pub struct Symbol {
|
|||||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||||
enum Repr {
|
enum Repr {
|
||||||
Single(char),
|
Single(char),
|
||||||
List(&'static [(&'static str, char)]),
|
Static(&'static [(&'static str, char)]),
|
||||||
|
Runtime(Arc<Vec<(EcoString, char)>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Symbol {
|
impl Symbol {
|
||||||
@ -47,12 +49,22 @@ impl Symbol {
|
|||||||
Self { repr: Repr::Single(c), modifiers: EcoString::new() }
|
Self { repr: Repr::Single(c), modifiers: EcoString::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a symbol with variants.
|
/// Create a symbol with a static variant list.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn list(list: &'static [(&'static str, char)]) -> Self {
|
pub fn list(list: &'static [(&'static str, char)]) -> Self {
|
||||||
debug_assert!(!list.is_empty());
|
debug_assert!(!list.is_empty());
|
||||||
Self {
|
Self {
|
||||||
repr: Repr::List(list),
|
repr: Repr::Static(list),
|
||||||
|
modifiers: EcoString::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a symbol with a runtime variant list.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn runtime(list: Vec<(EcoString, char)>) -> Self {
|
||||||
|
debug_assert!(!list.is_empty());
|
||||||
|
Self {
|
||||||
|
repr: Repr::Runtime(Arc::new(list)),
|
||||||
modifiers: EcoString::new(),
|
modifiers: EcoString::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,7 +73,7 @@ impl Symbol {
|
|||||||
pub fn get(&self) -> char {
|
pub fn get(&self) -> char {
|
||||||
match self.repr {
|
match self.repr {
|
||||||
Repr::Single(c) => c,
|
Repr::Single(c) => c,
|
||||||
Repr::List(list) => find(list, &self.modifiers).unwrap(),
|
_ => find(self.variants(), &self.modifiers).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,10 +83,7 @@ impl Symbol {
|
|||||||
self.modifiers.push('.');
|
self.modifiers.push('.');
|
||||||
}
|
}
|
||||||
self.modifiers.push_str(modifier);
|
self.modifiers.push_str(modifier);
|
||||||
if match self.repr {
|
if find(self.variants(), &self.modifiers).is_none() {
|
||||||
Repr::Single(_) => true,
|
|
||||||
Repr::List(list) => find(list, &self.modifiers).is_none(),
|
|
||||||
} {
|
|
||||||
Err("unknown modifier")?
|
Err("unknown modifier")?
|
||||||
}
|
}
|
||||||
Ok(self)
|
Ok(self)
|
||||||
@ -82,21 +91,19 @@ impl Symbol {
|
|||||||
|
|
||||||
/// The characters that are covered by this 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, char)> {
|
||||||
let (first, slice) = match self.repr {
|
match &self.repr {
|
||||||
Repr::Single(c) => (Some(("", c)), [].as_slice()),
|
Repr::Single(c) => Variants::Single(Some(*c).into_iter()),
|
||||||
Repr::List(list) => (None, list),
|
Repr::Static(list) => Variants::Static(list.iter()),
|
||||||
};
|
Repr::Runtime(list) => Variants::Runtime(list.iter()),
|
||||||
first.into_iter().chain(slice.iter().copied())
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Possible modifiers.
|
/// Possible modifiers.
|
||||||
pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ {
|
pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ {
|
||||||
let mut set = BTreeSet::new();
|
let mut set = BTreeSet::new();
|
||||||
if let Repr::List(list) = self.repr {
|
for modifier in self.variants().flat_map(|(name, _)| name.split('.')) {
|
||||||
for modifier in list.iter().flat_map(|(name, _)| name.split('.')) {
|
if !modifier.is_empty() && !contained(&self.modifiers, modifier) {
|
||||||
if !modifier.is_empty() && !contained(&self.modifiers, modifier) {
|
set.insert(modifier);
|
||||||
set.insert(modifier);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set.into_iter()
|
set.into_iter()
|
||||||
@ -115,13 +122,35 @@ impl Display for Symbol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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)>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for Variants<'a> {
|
||||||
|
type Item = (&'a str, char);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self {
|
||||||
|
Self::Single(iter) => Some(("", iter.next()?)),
|
||||||
|
Self::Static(list) => list.next().copied(),
|
||||||
|
Self::Runtime(list) => list.next().map(|(s, c)| (s.as_str(), *c)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 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<'a>(
|
||||||
|
variants: impl Iterator<Item = (&'a str, char)>,
|
||||||
|
modifiers: &str,
|
||||||
|
) -> Option<char> {
|
||||||
let mut best = None;
|
let mut best = None;
|
||||||
let mut best_score = None;
|
let mut best_score = None;
|
||||||
|
|
||||||
// Find the best table entry with this name.
|
// Find the best table entry with this name.
|
||||||
'outer: for candidate in list {
|
'outer: for candidate in variants {
|
||||||
for modifier in parts(modifiers) {
|
for modifier in parts(modifiers) {
|
||||||
if !contained(candidate.0, modifier) {
|
if !contained(candidate.0, modifier) {
|
||||||
continue 'outer;
|
continue 'outer;
|
||||||
|
BIN
tests/fonts/NotoSansSymbols2-Regular.ttf
Normal file
BIN
tests/fonts/NotoSansSymbols2-Regular.ttf
Normal file
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 155 B After Width: | Height: | Size: 1.3 KiB |
@ -41,6 +41,23 @@
|
|||||||
// Error: 21-26 expected integer or ratio, found boolean
|
// Error: 21-26 expected integer or ratio, found boolean
|
||||||
#rgb(10%, 20%, 30%, false)
|
#rgb(10%, 20%, 30%, false)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Ref: true
|
||||||
|
#let envelope = symbol(
|
||||||
|
"🖂",
|
||||||
|
("stamped", "🖃"),
|
||||||
|
("stamped.pen", "🖆"),
|
||||||
|
("lightning", "🖄"),
|
||||||
|
("fly", "🖅"),
|
||||||
|
)
|
||||||
|
|
||||||
|
#envelope
|
||||||
|
#envelope.stamped
|
||||||
|
#envelope.pen
|
||||||
|
#envelope.stamped.pen
|
||||||
|
#envelope.lightning
|
||||||
|
#envelope.fly
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test conversion to string.
|
// Test conversion to string.
|
||||||
#test(str(123), "123")
|
#test(str(123), "123")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user