mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Handle single and alternate substs for single glyphs in math mode (#1592)
This commit is contained in:
parent
c4a1bd0055
commit
cba200d4ec
@ -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(),
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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, ..));
|
||||
|
BIN
tests/ref/math/font-features.png
Normal file
BIN
tests/ref/math/font-features.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
10
tests/typ/math/font-features.typ
Normal file
10
tests/typ/math/font-features.typ
Normal 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 $
|
Loading…
x
Reference in New Issue
Block a user