summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/typst-library/src/math/ctx.rs32
-rw-r--r--crates/typst-library/src/math/fragment.rs66
-rw-r--r--crates/typst-library/src/text/shaping.rs2
-rw-r--r--tests/ref/math/font-features.pngbin0 -> 3005 bytes
-rw-r--r--tests/typ/math/font-features.typ10
5 files changed, 95 insertions, 15 deletions
diff --git a/crates/typst-library/src/math/ctx.rs b/crates/typst-library/src/math/ctx.rs
index a1dc6cf4..a1684ffa 100644
--- a/crates/typst-library/src/math/ctx.rs
+++ b/crates/typst-library/src/math/ctx.rs
@@ -1,9 +1,11 @@
+use ttf_parser::gsub::SubstitutionSubtable;
use ttf_parser::math::MathValue;
use typst::font::{FontStyle, FontWeight};
use typst::model::realize;
use unicode_segmentation::UnicodeSegmentation;
use super::*;
+use crate::text::tags;
macro_rules! scaled {
($ctx:expr, text: $text:ident, display: $display:ident $(,)?) => {
@@ -32,6 +34,7 @@ pub struct MathContext<'a, 'b, 'v> {
pub table: ttf_parser::math::Table<'a>,
pub constants: ttf_parser::math::Constants<'a>,
pub ssty_table: Option<ttf_parser::gsub::AlternateSubstitution<'a>>,
+ pub glyphwise_tables: Option<Vec<GlyphwiseSubsts<'a>>>,
pub space_width: Em,
pub fragments: Vec<MathFragment>,
pub local: Styles,
@@ -49,29 +52,31 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
font: &'a Font,
block: bool,
) -> Self {
- let table = font.ttf().tables().math.unwrap();
- let constants = table.constants.unwrap();
+ let math_table = font.ttf().tables().math.unwrap();
+ let gsub_table = font.ttf().tables().gsub;
+ let constants = math_table.constants.unwrap();
- let ssty_table = font
- .ttf()
- .tables()
- .gsub
+ let ssty_table = gsub_table
.and_then(|gsub| {
gsub.features
.find(ttf_parser::Tag::from_bytes(b"ssty"))
.and_then(|feature| feature.lookup_indices.get(0))
.and_then(|index| gsub.lookups.get(index))
})
- .and_then(|ssty| {
- ssty.subtables.get::<ttf_parser::gsub::SubstitutionSubtable>(0)
- })
+ .and_then(|ssty| ssty.subtables.get::<SubstitutionSubtable>(0))
.and_then(|ssty| match ssty {
- ttf_parser::gsub::SubstitutionSubtable::Alternate(alt_glyphs) => {
- Some(alt_glyphs)
- }
+ SubstitutionSubtable::Alternate(alt_glyphs) => Some(alt_glyphs),
_ => None,
});
+ let features = tags(styles);
+ let glyphwise_tables = gsub_table.map(|gsub| {
+ features
+ .into_iter()
+ .filter_map(|feature| GlyphwiseSubsts::new(gsub, feature))
+ .collect()
+ });
+
let size = TextElem::size_in(styles);
let ttf = font.ttf();
let space_width = ttf
@@ -86,9 +91,10 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
regions: Regions::one(regions.base(), Axes::splat(false)),
font,
ttf: font.ttf(),
- table,
+ table: math_table,
constants,
ssty_table,
+ glyphwise_tables,
space_width,
fragments: vec![],
local: Styles::new(),
diff --git a/crates/typst-library/src/math/fragment.rs b/crates/typst-library/src/math/fragment.rs
index 139ce07b..a1702aef 100644
--- a/crates/typst-library/src/math/fragment.rs
+++ b/crates/typst-library/src/math/fragment.rs
@@ -1,5 +1,10 @@
+use rustybuzz::Feature;
+use ttf_parser::gsub::{
+ AlternateSet, AlternateSubstitution, SingleSubstitution, SubstitutionSubtable,
+};
+use ttf_parser::opentype_layout::LayoutTable;
+
use super::*;
-use ttf_parser::gsub::AlternateSet;
#[derive(Debug, Clone)]
pub enum MathFragment {
@@ -174,12 +179,14 @@ pub struct GlyphFragment {
impl GlyphFragment {
pub fn new(ctx: &MathContext, c: char, span: Span) -> Self {
let id = ctx.ttf.glyph_index(c).unwrap_or_default();
+ let id = Self::adjust_glyph_index(ctx, id);
Self::with_id(ctx, c, id, span)
}
pub fn try_new(ctx: &MathContext, c: char, span: Span) -> Option<Self> {
let c = ctx.style.styled_char(c);
let id = ctx.ttf.glyph_index(c)?;
+ let id = Self::adjust_glyph_index(ctx, id);
Some(Self::with_id(ctx, c, id, span))
}
@@ -209,6 +216,15 @@ impl GlyphFragment {
fragment
}
+ /// Apply GSUB substitutions.
+ fn adjust_glyph_index(ctx: &MathContext, id: GlyphId) -> GlyphId {
+ if let Some(glyphwise_tables) = &ctx.glyphwise_tables {
+ glyphwise_tables.iter().fold(id, |id, table| table.apply(id))
+ } else {
+ id
+ }
+ }
+
/// Sets element id and boxes in appropriate way without changing other
/// styles. This is used to replace the glyph with a stretch variant.
pub fn set_id(&mut self, ctx: &MathContext, id: GlyphId) {
@@ -412,3 +428,51 @@ fn kern_at_height(
Some(kern.kern(i)?.scaled(ctx))
}
+
+/// An OpenType substitution table that is applicable to glyph-wise substitutions.
+pub enum GlyphwiseSubsts<'a> {
+ Single(SingleSubstitution<'a>),
+ Alternate(AlternateSubstitution<'a>, u32),
+}
+
+impl<'a> GlyphwiseSubsts<'a> {
+ pub fn new(gsub: LayoutTable<'a>, feature: Feature) -> Option<Self> {
+ let ssty = gsub
+ .features
+ .find(feature.tag)
+ .and_then(|feature| feature.lookup_indices.get(0))
+ .and_then(|index| gsub.lookups.get(index))?;
+ let ssty = ssty.subtables.get::<SubstitutionSubtable>(0)?;
+ match ssty {
+ SubstitutionSubtable::Single(single_glyphs) => {
+ Some(Self::Single(single_glyphs))
+ }
+ SubstitutionSubtable::Alternate(alt_glyphs) => {
+ Some(Self::Alternate(alt_glyphs, feature.value))
+ }
+ _ => None,
+ }
+ }
+
+ pub fn try_apply(&self, glyph_id: GlyphId) -> Option<GlyphId> {
+ match self {
+ Self::Single(single) => match single {
+ SingleSubstitution::Format1 { coverage, delta } => coverage
+ .get(glyph_id)
+ .map(|_| GlyphId(glyph_id.0.wrapping_add(*delta as u16))),
+ SingleSubstitution::Format2 { coverage, substitutes } => {
+ coverage.get(glyph_id).and_then(|idx| substitutes.get(idx))
+ }
+ },
+ Self::Alternate(alternate, value) => alternate
+ .coverage
+ .get(glyph_id)
+ .and_then(|idx| alternate.alternate_sets.get(idx))
+ .and_then(|set| set.alternates.get(*value as u16)),
+ }
+ }
+
+ pub fn apply(&self, glyph_id: GlyphId) -> GlyphId {
+ self.try_apply(glyph_id).unwrap_or(glyph_id)
+ }
+}
diff --git a/crates/typst-library/src/text/shaping.rs b/crates/typst-library/src/text/shaping.rs
index ec8812fe..5be22390 100644
--- a/crates/typst-library/src/text/shaping.rs
+++ b/crates/typst-library/src/text/shaping.rs
@@ -858,7 +858,7 @@ pub fn families(styles: StyleChain) -> impl Iterator<Item = FontFamily> + Clone
}
/// Collect the tags of the OpenType features to apply.
-fn tags(styles: StyleChain) -> Vec<Feature> {
+pub fn tags(styles: StyleChain) -> Vec<Feature> {
let mut tags = vec![];
let mut feat = |tag, value| {
tags.push(Feature::new(Tag::from_bytes(tag), value, ..));
diff --git a/tests/ref/math/font-features.png b/tests/ref/math/font-features.png
new file mode 100644
index 00000000..1fff3547
--- /dev/null
+++ b/tests/ref/math/font-features.png
Binary files differ
diff --git a/tests/typ/math/font-features.typ b/tests/typ/math/font-features.typ
new file mode 100644
index 00000000..ffdd1924
--- /dev/null
+++ b/tests/typ/math/font-features.typ
@@ -0,0 +1,10 @@
+// Test that setting font features in math.equation has an effect.
+
+---
+$ nothing $
+$ "hi ∅ hey" $
+$ sum_(i in NN) 1 + i $
+#show math.equation: set text(features: ("cv01",), fallback: false)
+$ nothing $
+$ "hi ∅ hey" $
+$ sum_(i in NN) 1 + i $