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 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(),

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 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)
}
}

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.
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, ..));

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 $