summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author+merlan #flirora <uruwi@protonmail.com>2024-08-05 15:19:56 -0400
committerGitHub <noreply@github.com>2024-08-05 19:19:56 +0000
commit6856d5e672d68d8ac82d327da774985327433a4f (patch)
treef153d492566c71016660ce03a0a3eaf79919d26c
parent18ce3f111d66622a52bf5b0c58b3e1da3c646603 (diff)
Support multiple stylistic sets in text (#4685)
-rw-r--r--crates/typst/src/text/mod.rs72
-rw-r--r--tests/ref/text-alternates-and-stylistic-sets.pngbin458 -> 1223 bytes
-rw-r--r--tests/suite/layout/inline/text.typ5
3 files changed, 51 insertions, 26 deletions
diff --git a/crates/typst/src/text/mod.rs b/crates/typst/src/text/mod.rs
index f2e525cf..e379c05e 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<StylisticSet>,
+ 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<Item = u8> {
+ (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<i64> => {
+ 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<Feature> {
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<Feature> {
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 877542fc..48300021 100644
--- a/tests/ref/text-alternates-and-stylistic-sets.png
+++ b/tests/ref/text-alternates-and-stylistic-sets.png
Binary files differ
diff --git a/tests/suite/layout/inline/text.typ b/tests/suite/layout/inline/text.typ
index ba0e625e..369aba7f 100644
--- a/tests/suite/layout/inline/text.typ
+++ b/tests/suite/layout/inline/text.typ
@@ -9,7 +9,8 @@
// Test alternates and stylistic sets.
#set text(font: "IBM Plex Serif")
a vs #text(alternates: true)[a] \
-ß vs #text(stylistic-set: 5)[ß]
+ß vs #text(stylistic-set: 5)[ß] \
+10 years ago vs #text(stylistic-set: (1, 2, 3))[10 years ago]
--- text-ligatures ---
// Test text turning off (standard) ligatures of the font.
@@ -43,7 +44,7 @@ waltz vs #text(discretionary-ligatures: true)[waltz]
fi vs. #text(features: (liga: 0))[No fi]
--- text-stylistic-set-bad-type ---
-// Error: 26-31 expected integer or none, found boolean
+// Error: 26-31 expected none, integer, or array, found boolean
#set text(stylistic-set: false)
--- text-stylistic-set-out-of-bounds ---