summaryrefslogtreecommitdiff
path: root/crates/typst-layout/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-layout/src')
-rw-r--r--crates/typst-layout/src/math/accent.rs18
-rw-r--r--crates/typst-layout/src/math/fragment.rs60
-rw-r--r--crates/typst-layout/src/math/mod.rs36
-rw-r--r--crates/typst-layout/src/math/text.rs24
4 files changed, 88 insertions, 50 deletions
diff --git a/crates/typst-layout/src/math/accent.rs b/crates/typst-layout/src/math/accent.rs
index 9fa7a5a0..4e26502e 100644
--- a/crates/typst-layout/src/math/accent.rs
+++ b/crates/typst-layout/src/math/accent.rs
@@ -19,7 +19,12 @@ pub fn layout_accent(
styles: StyleChain,
) -> SourceResult<()> {
let cramped = style_cramped();
- let base = ctx.layout_into_fragment(elem.base(), styles.chain(&cramped))?;
+ let mut base = ctx.layout_into_fragment(elem.base(), styles.chain(&cramped))?;
+
+ // Try to replace a glyph with its dotless variant.
+ if let MathFragment::Glyph(glyph) = &mut base {
+ glyph.make_dotless_form(ctx);
+ }
// Preserve class to preserve automatic spacing.
let base_class = base.class();
@@ -31,10 +36,17 @@ pub fn layout_accent(
.at(scaled_font_size(ctx, styles))
.relative_to(base.width());
+ let Accent(c) = elem.accent();
+ let mut glyph = GlyphFragment::new(ctx, styles, *c, elem.span());
+
+ // Try to replace accent glyph with flattened variant.
+ let flattened_base_height = scaled!(ctx, styles, flattened_accent_base_height);
+ if base.height() > flattened_base_height {
+ glyph.make_flattened_accent_form(ctx);
+ }
+
// Forcing the accent to be at least as large as the base makes it too
// wide in many case.
- let Accent(c) = elem.accent();
- let glyph = GlyphFragment::new(ctx, styles, *c, elem.span());
let short_fall = ACCENT_SHORT_FALL.at(glyph.font_size);
let variant = glyph.stretch_horizontal(ctx, width, short_fall);
let accent = variant.frame;
diff --git a/crates/typst-layout/src/math/fragment.rs b/crates/typst-layout/src/math/fragment.rs
index 19a4494e..8da7c077 100644
--- a/crates/typst-layout/src/math/fragment.rs
+++ b/crates/typst-layout/src/math/fragment.rs
@@ -2,9 +2,7 @@ use std::fmt::{self, Debug, Formatter};
use rustybuzz::Feature;
use smallvec::SmallVec;
-use ttf_parser::gsub::{
- AlternateSet, AlternateSubstitution, SingleSubstitution, SubstitutionSubtable,
-};
+use ttf_parser::gsub::{AlternateSubstitution, SingleSubstitution, SubstitutionSubtable};
use ttf_parser::opentype_layout::LayoutTable;
use ttf_parser::{GlyphId, Rect};
use typst_library::foundations::StyleChain;
@@ -390,20 +388,39 @@ impl GlyphFragment {
frame
}
- pub fn make_scriptsize(&mut self, ctx: &MathContext) {
+ pub fn make_script_size(&mut self, ctx: &MathContext) {
let alt_id =
- script_alternatives(ctx, self.id).and_then(|alts| alts.alternates.get(0));
+ ctx.ssty_table.as_ref().and_then(|ssty| ssty.try_apply(self.id, None));
+ if let Some(alt_id) = alt_id {
+ self.set_id(ctx, alt_id);
+ }
+ }
+ pub fn make_script_script_size(&mut self, ctx: &MathContext) {
+ let alt_id = ctx.ssty_table.as_ref().and_then(|ssty| {
+ // We explicitly request to apply the alternate set with value 1,
+ // as opposed to the default value in ssty, as the former
+ // corresponds to second level scripts and the latter corresponds
+ // to first level scripts.
+ ssty.try_apply(self.id, Some(1))
+ .or_else(|| ssty.try_apply(self.id, None))
+ });
if let Some(alt_id) = alt_id {
self.set_id(ctx, alt_id);
}
}
- pub fn make_scriptscriptsize(&mut self, ctx: &MathContext) {
- let alts = script_alternatives(ctx, self.id);
- let alt_id = alts
- .and_then(|alts| alts.alternates.get(1).or_else(|| alts.alternates.get(0)));
+ pub fn make_dotless_form(&mut self, ctx: &MathContext) {
+ let alt_id =
+ ctx.dtls_table.as_ref().and_then(|dtls| dtls.try_apply(self.id, None));
+ if let Some(alt_id) = alt_id {
+ self.set_id(ctx, alt_id);
+ }
+ }
+ pub fn make_flattened_accent_form(&mut self, ctx: &MathContext) {
+ let alt_id =
+ ctx.flac_table.as_ref().and_then(|flac| flac.try_apply(self.id, None));
if let Some(alt_id) = alt_id {
self.set_id(ctx, alt_id);
}
@@ -561,16 +578,6 @@ fn accent_attach(ctx: &MathContext, id: GlyphId, font_size: Abs) -> Option<Abs>
)
}
-/// Look up the script/scriptscript alternates for a glyph
-fn script_alternatives<'a>(
- ctx: &MathContext<'a, '_, '_>,
- id: GlyphId,
-) -> Option<AlternateSet<'a>> {
- ctx.ssty_table.and_then(|ssty| {
- ssty.coverage.get(id).and_then(|index| ssty.alternate_sets.get(index))
- })
-}
-
/// Look up whether a glyph is an extended shape.
fn is_extended_shape(ctx: &MathContext, id: GlyphId) -> bool {
ctx.table
@@ -662,10 +669,11 @@ pub enum GlyphwiseSubsts<'a> {
}
impl<'a> GlyphwiseSubsts<'a> {
- pub fn new(gsub: LayoutTable<'a>, feature: Feature) -> Option<Self> {
+ pub fn new(gsub: Option<LayoutTable<'a>>, feature: Feature) -> Option<Self> {
+ let gsub = gsub?;
let table = gsub
.features
- .find(ttf_parser::Tag(feature.tag.0))
+ .find(feature.tag)
.and_then(|feature| feature.lookup_indices.get(0))
.and_then(|index| gsub.lookups.get(index))?;
let table = table.subtables.get::<SubstitutionSubtable>(0)?;
@@ -680,7 +688,11 @@ impl<'a> GlyphwiseSubsts<'a> {
}
}
- pub fn try_apply(&self, glyph_id: GlyphId) -> Option<GlyphId> {
+ pub fn try_apply(
+ &self,
+ glyph_id: GlyphId,
+ alt_value: Option<u32>,
+ ) -> Option<GlyphId> {
match self {
Self::Single(single) => match single {
SingleSubstitution::Format1 { coverage, delta } => coverage
@@ -694,11 +706,11 @@ impl<'a> GlyphwiseSubsts<'a> {
.coverage
.get(glyph_id)
.and_then(|idx| alternate.alternate_sets.get(idx))
- .and_then(|set| set.alternates.get(*value as u16)),
+ .and_then(|set| set.alternates.get(alt_value.unwrap_or(*value) as u16)),
}
}
pub fn apply(&self, glyph_id: GlyphId) -> GlyphId {
- self.try_apply(glyph_id).unwrap_or(glyph_id)
+ self.try_apply(glyph_id, None).unwrap_or(glyph_id)
}
}
diff --git a/crates/typst-layout/src/math/mod.rs b/crates/typst-layout/src/math/mod.rs
index b3dde977..32059cef 100644
--- a/crates/typst-layout/src/math/mod.rs
+++ b/crates/typst-layout/src/math/mod.rs
@@ -13,7 +13,8 @@ mod stretch;
mod text;
mod underover;
-use ttf_parser::gsub::SubstitutionSubtable;
+use rustybuzz::Feature;
+use ttf_parser::Tag;
use typst_library::diag::{bail, SourceResult};
use typst_library::engine::Engine;
use typst_library::foundations::{Content, NativeElement, Packed, Resolve, StyleChain};
@@ -369,7 +370,9 @@ struct MathContext<'a, 'v, 'e> {
ttf: &'a ttf_parser::Face<'a>,
table: ttf_parser::math::Table<'a>,
constants: ttf_parser::math::Constants<'a>,
- ssty_table: Option<ttf_parser::gsub::AlternateSubstitution<'a>>,
+ dtls_table: Option<GlyphwiseSubsts<'a>>,
+ flac_table: Option<GlyphwiseSubsts<'a>>,
+ ssty_table: Option<GlyphwiseSubsts<'a>>,
glyphwise_tables: Option<Vec<GlyphwiseSubsts<'a>>>,
space_width: Em,
// Mutable.
@@ -389,26 +392,17 @@ impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> {
let gsub_table = font.ttf().tables().gsub;
let constants = math_table.constants.unwrap();
- 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::<SubstitutionSubtable>(0))
- .and_then(|ssty| match ssty {
- SubstitutionSubtable::Alternate(alt_glyphs) => Some(alt_glyphs),
- _ => None,
- });
+ let feat = |tag: &[u8; 4]| {
+ GlyphwiseSubsts::new(gsub_table, Feature::new(Tag::from_bytes(tag), 0, ..))
+ };
let features = features(styles);
- let glyphwise_tables = gsub_table.map(|gsub| {
+ let glyphwise_tables = Some(
features
.into_iter()
- .filter_map(|feature| GlyphwiseSubsts::new(gsub, feature))
- .collect()
- });
+ .filter_map(|feature| GlyphwiseSubsts::new(gsub_table, feature))
+ .collect(),
+ );
let ttf = font.ttf();
let space_width = ttf
@@ -422,10 +416,12 @@ impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> {
locator,
region: Region::new(base, Axes::splat(false)),
font,
- ttf: font.ttf(),
+ ttf,
table: math_table,
constants,
- ssty_table,
+ dtls_table: feat(b"dtls"),
+ flac_table: feat(b"flac"),
+ ssty_table: feat(b"ssty"),
glyphwise_tables,
space_width,
fragments: vec![],
diff --git a/crates/typst-layout/src/math/text.rs b/crates/typst-layout/src/math/text.rs
index df80b45a..86d871a2 100644
--- a/crates/typst-layout/src/math/text.rs
+++ b/crates/typst-layout/src/math/text.rs
@@ -26,20 +26,25 @@ pub fn layout_text(
let span = elem.span();
let mut chars = text.chars();
let math_size = EquationElem::size_in(styles);
-
+ let mut dtls = ctx.dtls_table.is_some();
let fragment: MathFragment = if let Some(mut glyph) = chars
.next()
.filter(|_| chars.next().is_none())
+ .map(|c| dtls_char(c, &mut dtls))
.map(|c| styled_char(styles, c, true))
.and_then(|c| GlyphFragment::try_new(ctx, styles, c, span))
{
// A single letter that is available in the math font.
+ if dtls {
+ glyph.make_dotless_form(ctx);
+ }
+
match math_size {
MathSize::Script => {
- glyph.make_scriptsize(ctx);
+ glyph.make_script_size(ctx);
}
MathSize::ScriptScript => {
- glyph.make_scriptscriptsize(ctx);
+ glyph.make_script_script_size(ctx);
}
_ => (),
}
@@ -342,3 +347,16 @@ fn greek_exception(
_ => return None,
})
}
+
+/// Switch dotless character to non dotless character for use of the dtls
+/// OpenType feature.
+pub fn dtls_char(c: char, dtls: &mut bool) -> char {
+ match (c, *dtls) {
+ ('ı', true) => 'i',
+ ('ȷ', true) => 'j',
+ _ => {
+ *dtls = false;
+ c
+ }
+ }
+}