support optical sizes for script/scriptscript when available (#1580)
@ -31,6 +31,7 @@ pub struct MathContext<'a, 'b, 'v> {
|
||||
pub ttf: &'a ttf_parser::Face<'a>,
|
||||
pub table: ttf_parser::math::Table<'a>,
|
||||
pub constants: ttf_parser::math::Constants<'a>,
|
||||
pub ssty_table: Option<ttf_parser::gsub::AlternateSubstitution<'a>>,
|
||||
pub space_width: Em,
|
||||
pub fragments: Vec<MathFragment>,
|
||||
pub local: Styles,
|
||||
@ -50,6 +51,27 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
||||
) -> Self {
|
||||
let table = font.ttf().tables().math.unwrap();
|
||||
let constants = table.constants.unwrap();
|
||||
|
||||
let ssty_table = font
|
||||
.ttf()
|
||||
.tables()
|
||||
.gsub
|
||||
.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| match ssty {
|
||||
ttf_parser::gsub::SubstitutionSubtable::Alternate(alt_glyphs) => {
|
||||
Some(alt_glyphs)
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let size = TextElem::size_in(styles);
|
||||
let ttf = font.ttf();
|
||||
let space_width = ttf
|
||||
@ -66,6 +88,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
||||
ttf: font.ttf(),
|
||||
table,
|
||||
constants,
|
||||
ssty_table,
|
||||
space_width,
|
||||
fragments: vec![],
|
||||
local: Styles::new(),
|
||||
@ -129,21 +152,32 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
||||
let text = elem.text();
|
||||
let span = elem.span();
|
||||
let mut chars = text.chars();
|
||||
let fragment = if let Some(glyph) = chars
|
||||
let fragment = if let Some(mut glyph) = chars
|
||||
.next()
|
||||
.filter(|_| chars.next().is_none())
|
||||
.map(|c| self.style.styled_char(c))
|
||||
.and_then(|c| GlyphFragment::try_new(self, c, span))
|
||||
{
|
||||
// A single letter that is available in the math font.
|
||||
if self.style.size == MathSize::Display
|
||||
&& glyph.class == Some(MathClass::Large)
|
||||
{
|
||||
match self.style.size {
|
||||
MathSize::Display => {
|
||||
if glyph.class == Some(MathClass::Large) {
|
||||
let height = scaled!(self, display_operator_min_height);
|
||||
glyph.stretch_vertical(self, height, Abs::zero()).into()
|
||||
} else {
|
||||
glyph.into()
|
||||
}
|
||||
}
|
||||
MathSize::Script => {
|
||||
glyph.make_scriptsize(self);
|
||||
glyph.into()
|
||||
}
|
||||
MathSize::ScriptScript => {
|
||||
glyph.make_scriptscriptsize(self);
|
||||
glyph.into()
|
||||
}
|
||||
_ => glyph.into(),
|
||||
}
|
||||
} else if text.chars().all(|c| c.is_ascii_digit()) {
|
||||
// Numbers aren't that difficult.
|
||||
let mut fragments = vec![];
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use ttf_parser::gsub::AlternateSet;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MathFragment {
|
||||
@ -272,6 +273,25 @@ impl GlyphFragment {
|
||||
frame.meta_iter(self.meta);
|
||||
frame
|
||||
}
|
||||
|
||||
pub fn make_scriptsize(&mut self, ctx: &MathContext) {
|
||||
let alt_id =
|
||||
script_alternatives(ctx, self.id).and_then(|alts| alts.alternates.get(0));
|
||||
|
||||
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)));
|
||||
|
||||
if let Some(alt_id) = alt_id {
|
||||
self.set_id(ctx, alt_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for GlyphFragment {
|
||||
@ -347,6 +367,16 @@ fn italics_correction(ctx: &MathContext, id: GlyphId) -> Option<Abs> {
|
||||
Some(ctx.table.glyph_info?.italic_corrections?.get(id)?.scaled(ctx))
|
||||
}
|
||||
|
||||
/// 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 the italics correction for a glyph.
|
||||
fn is_extended_shape(ctx: &MathContext, id: GlyphId) -> bool {
|
||||
ctx.table
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.6 KiB |
BIN
tests/ref/math/opticalsize.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 49 KiB |
@ -11,6 +11,7 @@ $ &sin x + log_2 x \
|
||||
|
||||
---
|
||||
// Test scripts vs limits.
|
||||
#set page(width: auto)
|
||||
#set text(font: "New Computer Modern")
|
||||
Discuss $lim_(n->oo) 1/n$ now.
|
||||
$ lim_(n->infinity) 1/n = 0 $
|
||||
|
30
tests/typ/math/opticalsize.typ
Normal file
@ -0,0 +1,30 @@
|
||||
// test optical sized variants in sub/superscripts
|
||||
|
||||
---
|
||||
|
||||
// Test transition from script to scriptscript.
|
||||
#[
|
||||
#set text(size:20pt)
|
||||
$ e^(e^(e^(e))) $
|
||||
]
|
||||
A large number: $e^(e^(e^(e)))$.
|
||||
|
||||
---
|
||||
// Test prime/double prime via scriptsize
|
||||
#let prime = [ \u{2032} ]
|
||||
#let dprime = [ \u{2033} ]
|
||||
#let tprime = [ \u{2034} ]
|
||||
$ y^dprime-2y^prime + y = 0 $
|
||||
$y^dprime-2y^prime + y = 0$
|
||||
$ y^tprime_3 + g^(prime 2) $
|
||||
|
||||
---
|
||||
// Test prime superscript on large symbol
|
||||
$ scripts(sum_(k in NN))^prime 1/k^2 $
|
||||
$sum_(k in NN)^prime 1/k^2$
|
||||
|
||||
---
|
||||
// Test script-script in a fraction.
|
||||
$ 1/(x^A) $
|
||||
#[#set text(size:18pt); $1/(x^A)$] vs. #[#set text(size:14pt); $x^A$]
|
||||
|