Handle single and alternate substs for single glyphs in math mode (#1592)

This commit is contained in:
bluebear94 2023-07-06 04:11:42 -04:00 committed by GitHub
parent c4a1bd0055
commit cba200d4ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 95 additions and 15 deletions

View File

@ -1,9 +1,11 @@
use ttf_parser::gsub::SubstitutionSubtable;
use ttf_parser::math::MathValue; use ttf_parser::math::MathValue;
use typst::font::{FontStyle, FontWeight}; use typst::font::{FontStyle, FontWeight};
use typst::model::realize; use typst::model::realize;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use super::*; use super::*;
use crate::text::tags;
macro_rules! scaled { macro_rules! scaled {
($ctx:expr, text: $text:ident, display: $display:ident $(,)?) => { ($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 table: ttf_parser::math::Table<'a>,
pub constants: ttf_parser::math::Constants<'a>, pub constants: ttf_parser::math::Constants<'a>,
pub ssty_table: Option<ttf_parser::gsub::AlternateSubstitution<'a>>, pub ssty_table: Option<ttf_parser::gsub::AlternateSubstitution<'a>>,
pub glyphwise_tables: Option<Vec<GlyphwiseSubsts<'a>>>,
pub space_width: Em, pub space_width: Em,
pub fragments: Vec<MathFragment>, pub fragments: Vec<MathFragment>,
pub local: Styles, pub local: Styles,
@ -49,29 +52,31 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
font: &'a Font, font: &'a Font,
block: bool, block: bool,
) -> Self { ) -> Self {
let table = font.ttf().tables().math.unwrap(); let math_table = font.ttf().tables().math.unwrap();
let constants = table.constants.unwrap(); let gsub_table = font.ttf().tables().gsub;
let constants = math_table.constants.unwrap();
let ssty_table = font let ssty_table = gsub_table
.ttf()
.tables()
.gsub
.and_then(|gsub| { .and_then(|gsub| {
gsub.features gsub.features
.find(ttf_parser::Tag::from_bytes(b"ssty")) .find(ttf_parser::Tag::from_bytes(b"ssty"))
.and_then(|feature| feature.lookup_indices.get(0)) .and_then(|feature| feature.lookup_indices.get(0))
.and_then(|index| gsub.lookups.get(index)) .and_then(|index| gsub.lookups.get(index))
}) })
.and_then(|ssty| { .and_then(|ssty| ssty.subtables.get::<SubstitutionSubtable>(0))
ssty.subtables.get::<ttf_parser::gsub::SubstitutionSubtable>(0)
})
.and_then(|ssty| match ssty { .and_then(|ssty| match ssty {
ttf_parser::gsub::SubstitutionSubtable::Alternate(alt_glyphs) => { SubstitutionSubtable::Alternate(alt_glyphs) => Some(alt_glyphs),
Some(alt_glyphs)
}
_ => None, _ => 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 size = TextElem::size_in(styles);
let ttf = font.ttf(); let ttf = font.ttf();
let space_width = 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)), regions: Regions::one(regions.base(), Axes::splat(false)),
font, font,
ttf: font.ttf(), ttf: font.ttf(),
table, table: math_table,
constants, constants,
ssty_table, ssty_table,
glyphwise_tables,
space_width, space_width,
fragments: vec![], fragments: vec![],
local: Styles::new(), local: Styles::new(),

View File

@ -1,5 +1,10 @@
use rustybuzz::Feature;
use ttf_parser::gsub::{
AlternateSet, AlternateSubstitution, SingleSubstitution, SubstitutionSubtable,
};
use ttf_parser::opentype_layout::LayoutTable;
use super::*; use super::*;
use ttf_parser::gsub::AlternateSet;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum MathFragment { pub enum MathFragment {
@ -174,12 +179,14 @@ pub struct GlyphFragment {
impl GlyphFragment { impl GlyphFragment {
pub fn new(ctx: &MathContext, c: char, span: Span) -> Self { pub fn new(ctx: &MathContext, c: char, span: Span) -> Self {
let id = ctx.ttf.glyph_index(c).unwrap_or_default(); 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) Self::with_id(ctx, c, id, span)
} }
pub fn try_new(ctx: &MathContext, c: char, span: Span) -> Option<Self> { pub fn try_new(ctx: &MathContext, c: char, span: Span) -> Option<Self> {
let c = ctx.style.styled_char(c); let c = ctx.style.styled_char(c);
let id = ctx.ttf.glyph_index(c)?; let id = ctx.ttf.glyph_index(c)?;
let id = Self::adjust_glyph_index(ctx, id);
Some(Self::with_id(ctx, c, id, span)) Some(Self::with_id(ctx, c, id, span))
} }
@ -209,6 +216,15 @@ impl GlyphFragment {
fragment 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 /// Sets element id and boxes in appropriate way without changing other
/// styles. This is used to replace the glyph with a stretch variant. /// styles. This is used to replace the glyph with a stretch variant.
pub fn set_id(&mut self, ctx: &MathContext, id: GlyphId) { pub fn set_id(&mut self, ctx: &MathContext, id: GlyphId) {
@ -412,3 +428,51 @@ fn kern_at_height(
Some(kern.kern(i)?.scaled(ctx)) 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)
}
}

View File

@ -858,7 +858,7 @@ pub fn families(styles: StyleChain) -> impl Iterator<Item = FontFamily> + Clone
} }
/// Collect the tags of the OpenType features to apply. /// 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 tags = vec![];
let mut feat = |tag, value| { let mut feat = |tag, value| {
tags.push(Feature::new(Tag::from_bytes(tag), value, ..)); tags.push(Feature::new(Tag::from_bytes(tag), value, ..));

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -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 $