mirror of
https://github.com/typst/typst
synced 2025-05-15 09:35:28 +08:00
Cache is_justifiable
(#2399)
This commit is contained in:
parent
372476323f
commit
77b84675e5
@ -73,6 +73,10 @@ pub struct ShapedGlyph {
|
|||||||
pub c: char,
|
pub c: char,
|
||||||
/// The source code location of the glyph and its byte offset within it.
|
/// The source code location of the glyph and its byte offset within it.
|
||||||
pub span: (Span, u16),
|
pub span: (Span, u16),
|
||||||
|
/// Whether this glyph is justifiable for CJK scripts.
|
||||||
|
pub is_justifiable: bool,
|
||||||
|
/// The script of the glyph.
|
||||||
|
pub script: Script,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
@ -86,21 +90,24 @@ pub struct Adjustability {
|
|||||||
impl ShapedGlyph {
|
impl ShapedGlyph {
|
||||||
/// Whether the glyph is a space.
|
/// Whether the glyph is a space.
|
||||||
pub fn is_space(&self) -> bool {
|
pub fn is_space(&self) -> bool {
|
||||||
matches!(self.c, ' ' | '\u{00A0}' | ' ')
|
is_space(self.c)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the glyph is justifiable.
|
/// Whether the glyph is justifiable.
|
||||||
pub fn is_justifiable(&self) -> bool {
|
pub fn is_justifiable(&self) -> bool {
|
||||||
// GB style is not relevant here.
|
// GB style is not relevant here.
|
||||||
self.is_space()
|
self.is_justifiable
|
||||||
|| self.is_cjk_script()
|
|
||||||
|| self.is_cjk_left_aligned_punctuation(true)
|
|
||||||
|| self.is_cjk_right_aligned_punctuation()
|
|
||||||
|| self.is_cjk_center_aligned_punctuation(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates the justifiability of the glyph.
|
||||||
|
fn update_justifiable(&mut self) {
|
||||||
|
self.is_justifiable =
|
||||||
|
is_justifiable(self.c, self.script, self.x_advance, self.stretchability());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the glyph is part of a CJK script.
|
||||||
pub fn is_cjk_script(&self) -> bool {
|
pub fn is_cjk_script(&self) -> bool {
|
||||||
char_is_cjk_script(self.c)
|
is_cjk_script(self.c, self.script)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_cjk_punctuation(&self) -> bool {
|
pub fn is_cjk_punctuation(&self) -> bool {
|
||||||
@ -111,42 +118,22 @@ impl ShapedGlyph {
|
|||||||
|
|
||||||
/// See <https://www.w3.org/TR/clreq/#punctuation_width_adjustment>
|
/// See <https://www.w3.org/TR/clreq/#punctuation_width_adjustment>
|
||||||
pub fn is_cjk_left_aligned_punctuation(&self, gb_style: bool) -> bool {
|
pub fn is_cjk_left_aligned_punctuation(&self, gb_style: bool) -> bool {
|
||||||
// CJK quotation marks shares codepoints with latin quotation marks.
|
is_cjk_left_aligned_punctuation(
|
||||||
// But only the CJK ones have full width.
|
self.c,
|
||||||
if matches!(self.c, '”' | '’')
|
self.x_advance,
|
||||||
&& self.x_advance + self.stretchability().1 == Em::one()
|
self.stretchability(),
|
||||||
{
|
gb_style,
|
||||||
return true;
|
)
|
||||||
}
|
|
||||||
|
|
||||||
if gb_style && matches!(self.c, ',' | '。' | '、' | ':' | ';') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
matches!(self.c, '》' | ')' | '』' | '」')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See <https://www.w3.org/TR/clreq/#punctuation_width_adjustment>
|
/// See <https://www.w3.org/TR/clreq/#punctuation_width_adjustment>
|
||||||
pub fn is_cjk_right_aligned_punctuation(&self) -> bool {
|
pub fn is_cjk_right_aligned_punctuation(&self) -> bool {
|
||||||
// CJK quotation marks shares codepoints with latin quotation marks.
|
is_cjk_right_aligned_punctuation(self.c, self.x_advance, self.stretchability())
|
||||||
// But only the CJK ones have full width.
|
|
||||||
if matches!(self.c, '“' | '‘')
|
|
||||||
&& self.x_advance + self.stretchability().0 == Em::one()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
matches!(self.c, '《' | '(' | '『' | '「')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See <https://www.w3.org/TR/clreq/#punctuation_width_adjustment>
|
/// See <https://www.w3.org/TR/clreq/#punctuation_width_adjustment>
|
||||||
pub fn is_cjk_center_aligned_punctuation(&self, gb_style: bool) -> bool {
|
pub fn is_cjk_center_aligned_punctuation(&self, gb_style: bool) -> bool {
|
||||||
if !gb_style && matches!(self.c, ',' | '。' | '、' | ':' | ';') {
|
is_cjk_center_aligned_punctuation(self.c, gb_style)
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// U+30FB: Katakana Middle Dot
|
|
||||||
matches!(self.c, '\u{30FB}')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the glyph is a western letter or number.
|
/// Whether the glyph is a western letter or number.
|
||||||
@ -200,6 +187,7 @@ impl ShapedGlyph {
|
|||||||
self.x_advance -= amount;
|
self.x_advance -= amount;
|
||||||
self.adjustability.shrinkability.0 -= amount;
|
self.adjustability.shrinkability.0 -= amount;
|
||||||
self.adjustability.stretchability.0 += amount;
|
self.adjustability.stretchability.0 += amount;
|
||||||
|
self.update_justifiable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shrink the width of glyph on the right side.
|
/// Shrink the width of glyph on the right side.
|
||||||
@ -207,6 +195,7 @@ impl ShapedGlyph {
|
|||||||
self.x_advance -= amount;
|
self.x_advance -= amount;
|
||||||
self.adjustability.shrinkability.1 -= amount;
|
self.adjustability.shrinkability.1 -= amount;
|
||||||
self.adjustability.stretchability.1 += amount;
|
self.adjustability.stretchability.1 += amount;
|
||||||
|
self.update_justifiable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,6 +462,8 @@ impl<'a> ShapedText<'a> {
|
|||||||
safe_to_break: true,
|
safe_to_break: true,
|
||||||
c: '-',
|
c: '-',
|
||||||
span: (Span::detached(), 0),
|
span: (Span::detached(), 0),
|
||||||
|
is_justifiable: false,
|
||||||
|
script: Script::Common,
|
||||||
});
|
});
|
||||||
Some(())
|
Some(())
|
||||||
});
|
});
|
||||||
@ -686,18 +677,28 @@ fn shape_segment(
|
|||||||
.and_then(|last| infos.get(last))
|
.and_then(|last| infos.get(last))
|
||||||
.map_or(text.len(), |info| info.cluster as usize);
|
.map_or(text.len(), |info| info.cluster as usize);
|
||||||
|
|
||||||
|
let c = text[cluster..].chars().next().unwrap();
|
||||||
|
let script = c.script();
|
||||||
|
let x_advance = font.to_em(pos[i].x_advance);
|
||||||
ctx.glyphs.push(ShapedGlyph {
|
ctx.glyphs.push(ShapedGlyph {
|
||||||
font: font.clone(),
|
font: font.clone(),
|
||||||
glyph_id: info.glyph_id as u16,
|
glyph_id: info.glyph_id as u16,
|
||||||
// TODO: Don't ignore y_advance.
|
// TODO: Don't ignore y_advance.
|
||||||
x_advance: font.to_em(pos[i].x_advance),
|
x_advance,
|
||||||
x_offset: font.to_em(pos[i].x_offset),
|
x_offset: font.to_em(pos[i].x_offset),
|
||||||
y_offset: font.to_em(pos[i].y_offset),
|
y_offset: font.to_em(pos[i].y_offset),
|
||||||
adjustability: Adjustability::default(),
|
adjustability: Adjustability::default(),
|
||||||
range: start..end,
|
range: start..end,
|
||||||
safe_to_break: !info.unsafe_to_break(),
|
safe_to_break: !info.unsafe_to_break(),
|
||||||
c: text[cluster..].chars().next().unwrap(),
|
c,
|
||||||
span: ctx.spans.span_at(start),
|
span: ctx.spans.span_at(start),
|
||||||
|
is_justifiable: is_justifiable(
|
||||||
|
c,
|
||||||
|
script,
|
||||||
|
x_advance,
|
||||||
|
Adjustability::default().stretchability,
|
||||||
|
),
|
||||||
|
script,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// First, search for the end of the tofu sequence.
|
// First, search for the end of the tofu sequence.
|
||||||
@ -752,6 +753,7 @@ fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font: Font) {
|
|||||||
let add_glyph = |(cluster, c): (usize, char)| {
|
let add_glyph = |(cluster, c): (usize, char)| {
|
||||||
let start = base + cluster;
|
let start = base + cluster;
|
||||||
let end = start + c.len_utf8();
|
let end = start + c.len_utf8();
|
||||||
|
let script = c.script();
|
||||||
ctx.glyphs.push(ShapedGlyph {
|
ctx.glyphs.push(ShapedGlyph {
|
||||||
font: font.clone(),
|
font: font.clone(),
|
||||||
glyph_id: 0,
|
glyph_id: 0,
|
||||||
@ -763,6 +765,13 @@ fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font: Font) {
|
|||||||
safe_to_break: true,
|
safe_to_break: true,
|
||||||
c,
|
c,
|
||||||
span: ctx.spans.span_at(start),
|
span: ctx.spans.span_at(start),
|
||||||
|
is_justifiable: is_justifiable(
|
||||||
|
c,
|
||||||
|
script,
|
||||||
|
x_advance,
|
||||||
|
Adjustability::default().stretchability,
|
||||||
|
),
|
||||||
|
script,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
if ctx.dir.is_positive() {
|
if ctx.dir.is_positive() {
|
||||||
@ -783,10 +792,12 @@ fn track_and_space(ctx: &mut ShapingContext) {
|
|||||||
// Make non-breaking space same width as normal space.
|
// Make non-breaking space same width as normal space.
|
||||||
if glyph.c == '\u{00A0}' {
|
if glyph.c == '\u{00A0}' {
|
||||||
glyph.x_advance -= nbsp_delta(&glyph.font).unwrap_or_default();
|
glyph.x_advance -= nbsp_delta(&glyph.font).unwrap_or_default();
|
||||||
|
glyph.update_justifiable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if glyph.is_space() {
|
if glyph.is_space() {
|
||||||
glyph.x_advance = spacing.relative_to(glyph.x_advance);
|
glyph.x_advance = spacing.relative_to(glyph.x_advance);
|
||||||
|
glyph.update_justifiable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if glyphs
|
if glyphs
|
||||||
@ -794,6 +805,7 @@ fn track_and_space(ctx: &mut ShapingContext) {
|
|||||||
.map_or(false, |next| glyph.range.start != next.range.start)
|
.map_or(false, |next| glyph.range.start != next.range.start)
|
||||||
{
|
{
|
||||||
glyph.x_advance += tracking;
|
glyph.x_advance += tracking;
|
||||||
|
glyph.update_justifiable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -812,6 +824,7 @@ fn calculate_adjustability(ctx: &mut ShapingContext, lang: Lang, region: Option<
|
|||||||
|
|
||||||
for glyph in &mut ctx.glyphs {
|
for glyph in &mut ctx.glyphs {
|
||||||
glyph.adjustability = glyph.base_adjustability(gb_style);
|
glyph.adjustability = glyph.base_adjustability(gb_style);
|
||||||
|
glyph.update_justifiable();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut glyphs = ctx.glyphs.iter_mut().peekable();
|
let mut glyphs = ctx.glyphs.iter_mut().peekable();
|
||||||
@ -963,12 +976,6 @@ fn language(styles: StyleChain) -> rustybuzz::Language {
|
|||||||
rustybuzz::Language::from_str(&bcp).unwrap()
|
rustybuzz::Language::from_str(&bcp).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn char_is_cjk_script(c: char) -> bool {
|
|
||||||
use Script::*;
|
|
||||||
// U+30FC: Katakana-Hiragana Prolonged Sound Mark
|
|
||||||
matches!(c.script(), Hiragana | Katakana | Han) || c == '\u{30FC}'
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if all glyphs in `glyphs` have ranges within the range `range`.
|
/// Returns true if all glyphs in `glyphs` have ranges within the range `range`.
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
fn assert_all_glyphs_in_range(glyphs: &[ShapedGlyph], text: &str, range: Range<usize>) {
|
fn assert_all_glyphs_in_range(glyphs: &[ShapedGlyph], text: &str, range: Range<usize>) {
|
||||||
@ -1005,3 +1012,87 @@ fn assert_glyph_ranges_in_order(glyphs: &[ShapedGlyph], dir: Dir) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the glyph is a space.
|
||||||
|
#[inline]
|
||||||
|
fn is_space(c: char) -> bool {
|
||||||
|
matches!(c, ' ' | '\u{00A0}' | ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the glyph is part of a CJK script.
|
||||||
|
#[inline]
|
||||||
|
pub fn char_is_cjk_script(c: char) -> bool {
|
||||||
|
is_cjk_script(c, c.script())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the glyph is part of a CJK script.
|
||||||
|
#[inline]
|
||||||
|
fn is_cjk_script(c: char, script: Script) -> bool {
|
||||||
|
use Script::*;
|
||||||
|
// U+30FC: Katakana-Hiragana Prolonged Sound Mark
|
||||||
|
matches!(script, Hiragana | Katakana | Han) || c == '\u{30FC}'
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See <https://www.w3.org/TR/clreq/#punctuation_width_adjustment>
|
||||||
|
#[inline]
|
||||||
|
fn is_cjk_left_aligned_punctuation(
|
||||||
|
c: char,
|
||||||
|
x_advance: Em,
|
||||||
|
stretchability: (Em, Em),
|
||||||
|
gb_style: bool,
|
||||||
|
) -> bool {
|
||||||
|
// CJK quotation marks shares codepoints with latin quotation marks.
|
||||||
|
// But only the CJK ones have full width.
|
||||||
|
if matches!(c, '”' | '’') && x_advance + stretchability.1 == Em::one() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if gb_style && matches!(c, ',' | '。' | '、' | ':' | ';') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches!(c, '》' | ')' | '』' | '」')
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See <https://www.w3.org/TR/clreq/#punctuation_width_adjustment>
|
||||||
|
#[inline]
|
||||||
|
fn is_cjk_right_aligned_punctuation(
|
||||||
|
c: char,
|
||||||
|
x_advance: Em,
|
||||||
|
stretchability: (Em, Em),
|
||||||
|
) -> bool {
|
||||||
|
// CJK quotation marks shares codepoints with latin quotation marks.
|
||||||
|
// But only the CJK ones have full width.
|
||||||
|
if matches!(c, '“' | '‘') && x_advance + stretchability.0 == Em::one() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches!(c, '《' | '(' | '『' | '「')
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See <https://www.w3.org/TR/clreq/#punctuation_width_adjustment>
|
||||||
|
#[inline]
|
||||||
|
fn is_cjk_center_aligned_punctuation(c: char, gb_style: bool) -> bool {
|
||||||
|
if !gb_style && matches!(c, ',' | '。' | '、' | ':' | ';') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// U+30FB: Katakana Middle Dot
|
||||||
|
matches!(c, '\u{30FB}')
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the glyph is justifiable.
|
||||||
|
#[inline]
|
||||||
|
fn is_justifiable(
|
||||||
|
c: char,
|
||||||
|
script: Script,
|
||||||
|
x_advance: Em,
|
||||||
|
stretchability: (Em, Em),
|
||||||
|
) -> bool {
|
||||||
|
// GB style is not relevant here.
|
||||||
|
is_space(c)
|
||||||
|
|| is_cjk_script(c, script)
|
||||||
|
|| is_cjk_left_aligned_punctuation(c, x_advance, stretchability, true)
|
||||||
|
|| is_cjk_right_aligned_punctuation(c, x_advance, stretchability)
|
||||||
|
|| is_cjk_center_aligned_punctuation(c, true)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user