From 6856d5e672d68d8ac82d327da774985327433a4f Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Mon, 5 Aug 2024 15:19:56 -0400 Subject: [PATCH] Support multiple stylistic sets in text (#4685) --- crates/typst/src/text/mod.rs | 72 ++++++++++++------ .../text-alternates-and-stylistic-sets.png | Bin 458 -> 1223 bytes tests/suite/layout/inline/text.typ | 5 +- 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/crates/typst/src/text/mod.rs b/crates/typst/src/text/mod.rs index f2e525cfd..e379c05e0 100644 --- a/crates/typst/src/text/mod.rs +++ b/crates/typst/src/text/mod.rs @@ -39,8 +39,8 @@ use crate::diag::{bail, warning, HintedStrResult, SourceResult}; use crate::engine::Engine; use crate::foundations::{ cast, category, dict, elem, Args, Array, Cast, Category, Construct, Content, Dict, - Fold, NativeElement, Never, Packed, PlainText, Repr, Resolve, Scope, Set, Smart, - StyleChain, + Fold, IntoValue, NativeElement, Never, NoneValue, Packed, PlainText, Repr, Resolve, + Scope, Set, Smart, StyleChain, }; use crate::layout::{Abs, Axis, Dir, Em, Length, Ratio, Rel}; use crate::model::ParElem; @@ -566,13 +566,22 @@ pub struct TextElem { #[ghost] pub alternates: bool, - /// Which stylistic set to apply. Font designers can categorize alternative + /// Which stylistic sets to apply. Font designers can categorize alternative /// glyphs forms into stylistic sets. As this value is highly font-specific, - /// you need to consult your font to know which sets are available. When set - /// to an integer between `{1}` and `{20}`, enables the corresponding - /// OpenType font feature from `ss01`, ..., `ss20`. + /// you need to consult your font to know which sets are available. + /// + /// This can be set to an integer or an array of integers, all + /// of which must be between `{1}` and `{20}`, enabling the + /// corresponding OpenType feature(s) from `ss01` to `ss20`. + /// Setting this to `none` will disable all stylistic sets. + /// + /// ``` + /// #set text(font: "IBM Plex Serif") + /// ß vs #text(stylistic-set: 5)[ß] \ + /// 10 years ago vs #text(stylistic-set: (1, 2, 3))[10 years ago] + /// ``` #[ghost] - pub stylistic_set: Option, + pub stylistic_set: StylisticSets, /// Whether standard ligatures are active. /// @@ -1059,29 +1068,45 @@ impl Resolve for Hyphenate { } } -/// A stylistic set in a font. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct StylisticSet(u8); +/// A set of stylistic sets to enable. +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)] +pub struct StylisticSets(u32); -impl StylisticSet { - /// Create a new set, clamping to 1-20. - pub fn new(index: u8) -> Self { - Self(index.clamp(1, 20)) +impl StylisticSets { + /// Converts this set into a Typst array of values. + pub fn into_array(self) -> Array { + self.sets().map(IntoValue::into_value).collect() } - /// Get the value, guaranteed to be 1-20. - pub fn get(self) -> u8 { - self.0 + /// Returns whether this set contains a particular stylistic set. + pub fn has(self, ss: u8) -> bool { + self.0 & (1 << (ss as u32)) != 0 + } + + /// Returns an iterator over all stylistic sets to enable. + pub fn sets(self) -> impl Iterator { + (1..=20).filter(move |i| self.has(*i)) } } cast! { - StylisticSet, - self => self.0.into_value(), + StylisticSets, + self => self.into_array().into_value(), + _: NoneValue => Self(0), v: i64 => match v { - 1 ..= 20 => Self::new(v as u8), + 1 ..= 20 => Self(1 << (v as u32)), _ => bail!("stylistic set must be between 1 and 20"), }, + v: Vec => { + let mut flags = 0; + for i in v { + match i { + 1 ..= 20 => flags |= 1 << (i as u32), + _ => bail!("stylistic set must be between 1 and 20"), + } + } + Self(flags) + }, } /// Which kind of numbers / figures to select. @@ -1145,7 +1170,7 @@ impl Fold for FontFeatures { /// Collect the OpenType features to apply. pub(crate) fn features(styles: StyleChain) -> Vec { let mut tags = vec![]; - let mut feat = |tag, value| { + let mut feat = |tag: &[u8; 4], value: u32| { tags.push(Feature::new(Tag::from_bytes(tag), value, ..)); }; @@ -1163,9 +1188,8 @@ pub(crate) fn features(styles: StyleChain) -> Vec { feat(b"salt", 1); } - let storage; - if let Some(set) = TextElem::stylistic_set_in(styles) { - storage = [b's', b's', b'0' + set.get() / 10, b'0' + set.get() % 10]; + for set in TextElem::stylistic_set_in(styles).sets() { + let storage = [b's', b's', b'0' + set / 10, b'0' + set % 10]; feat(&storage, 1); } diff --git a/tests/ref/text-alternates-and-stylistic-sets.png b/tests/ref/text-alternates-and-stylistic-sets.png index 877542fc235a97f5ed0c76902f3aeceaa82032ed..483000214f13345b8bdbcc812510e9dc40ca4a0f 100644 GIT binary patch delta 1216 zcmV;x1V8)A1IG!F7k@(t00000aZy1L~$rO9U?)aObsCTKAf{)2a1$pm6swiJF&VsQP53Ttbia4t-#RnA}S!I zAR-lLODVLp=UGM~7jv0i=n2mBxj8xKTzry~obPWOW7Ieg?0KJ7@i*l^=M z!OsAcztx0hKe5>if9l(_#f3}cTuk^405$0H;tqxiUa1lSvs}cfuzDRU0#cI$eSFcU3ZI2!W6WXw-ICwaqkL zF~g($ZhvTca9vOP)#f36j0NplylYqd0Mt(Jl&15n_c4EXe+-suVqf5EB* zNKiq|8*TkT_1THHSDN(ms^>l?$|q~d@1YewU0|ACv$&RnvdXEpj=;*~qhO2JN>zw? z*)2&H0Fyy@hkILRxeg~PK#>RpX@#$u@vvq8DSulziKo$g#XxR|@vaSoVooKoM92I# zX%4&#=)FS#zXu`peVfXIGlh;XL zJ%8jxaa=}5gB{XUIA#H4hpA}2idsGb`!-N4aRu&EtX-)vk}8p;0uUDu)iVRP3^Xm= zD_Pwa=Bq^>6B}d$!-72r{kqOhm^P=>7MT!H3Sjp#^G9$(Kpl{)n5Zb@{)U5L#{V)P zf&gIFs~-Uj0XT2~0FGxDhp-%U>qnl9ynp`ZJJ**>^M#s!Z(X!J@|FmbDe$74cF!T< zfnRcXM`+KDkjAq8-;`rq7nzqy&YH~6?dfsS%Rr4^Ou=QE{kP}+Na^0_TzA6v7V|XT z><`|~sN>vN0dLw5^(>|O0x-RqZ1Uiz<+yy z|Efy@*rvdDN+v>{q+|m~Pq`C5DVI{Q5Wu(Y89ZdZ6QBlRJY*_)+stK2Wiaf^aVOkA z#yD6hKT1Z1rC4LJeT8jL#X)h@r%|8R z?R|RG`|x@N;HLygSi%yP@V|!b1{d&rc+AD2N7=GH0qmW6AAbeI*7&U$2R!D~v#fGv z7*tDus(hw&K``uIr)3r}=P-0-jkDWq;0%R2(eOj9lv4v!(diFhNreW{aDP8d zAMilc94BTv$3Y49Q6m~YN@%LeSAhGV7gh^;GS7j*gtqFUH?c5Y1^3tG0)Q7UZdo7_ z{x$5m<7ZroaDUj8+m-i2F&4)=M8heU42K;3*u9_bpPj%LhEHK{$*)%moy#ckueG zk2L~nQ+TgKG;FE!rP%>f$Qdh*G|zp$dOun$Y|t^^zBUcO1X!1}