mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
shape_tofus: respect text direction when inserting tofus (#1519)
This ensures that the ranges of the shaped glyphs are monotonically decreasing in right-to-left-text, thus avoiding nonsensical results in find_safe_to_break that later causes a panic (see #1373). Additionally, debug assertions have been added to catch non-monotonic glyph ranges.
This commit is contained in:
parent
47a131dfbb
commit
9ef4643ba1
@ -1,5 +1,7 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::cmp::Ordering;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
use std::panic;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use az::SaturatingAs;
|
use az::SaturatingAs;
|
||||||
@ -58,6 +60,12 @@ pub struct ShapedGlyph {
|
|||||||
/// The byte range of this glyph's cluster in the full paragraph. A cluster
|
/// The byte range of this glyph's cluster in the full paragraph. A cluster
|
||||||
/// is a sequence of one or multiple glyphs that cannot be separated and
|
/// is a sequence of one or multiple glyphs that cannot be separated and
|
||||||
/// must always be treated as a union.
|
/// must always be treated as a union.
|
||||||
|
///
|
||||||
|
/// The range values of the glyphs in a [`ShapedText`] should not
|
||||||
|
/// overlap with each other, and they should be monotonically
|
||||||
|
/// increasing (for left-to-right or top-to-bottom text) or
|
||||||
|
/// monotonically decreasing (for right-to-left or bottom-to-top
|
||||||
|
/// text).
|
||||||
pub range: Range<usize>,
|
pub range: Range<usize>,
|
||||||
/// Whether splitting the shaping result before this glyph would yield the
|
/// Whether splitting the shaping result before this glyph would yield the
|
||||||
/// same results as shaping the parts to both sides of `text_index`
|
/// same results as shaping the parts to both sides of `text_index`
|
||||||
@ -388,6 +396,11 @@ impl<'a> ShapedText<'a> {
|
|||||||
) -> ShapedText<'a> {
|
) -> ShapedText<'a> {
|
||||||
let text = &self.text[text_range.start - self.base..text_range.end - self.base];
|
let text = &self.text[text_range.start - self.base..text_range.end - self.base];
|
||||||
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
|
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
|
||||||
|
debug_assert!(
|
||||||
|
all_glyphs_in_range(glyphs, &text_range),
|
||||||
|
"one or more glyphs in {:?} fell out of range",
|
||||||
|
text
|
||||||
|
);
|
||||||
Self {
|
Self {
|
||||||
base: text_range.start,
|
base: text_range.start,
|
||||||
text,
|
text,
|
||||||
@ -559,6 +572,14 @@ pub fn shape<'a>(
|
|||||||
track_and_space(&mut ctx);
|
track_and_space(&mut ctx);
|
||||||
calculate_adjustability(&mut ctx, lang, region);
|
calculate_adjustability(&mut ctx, lang, region);
|
||||||
|
|
||||||
|
debug_assert!(
|
||||||
|
all_glyphs_in_range(&ctx.glyphs, &(base..(base + text.len()))),
|
||||||
|
"one or more glyphs in {:?} fell out of range",
|
||||||
|
text
|
||||||
|
);
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
assert_glyph_ranges_in_order(&ctx.glyphs, dir);
|
||||||
|
|
||||||
ShapedText {
|
ShapedText {
|
||||||
base,
|
base,
|
||||||
text,
|
text,
|
||||||
@ -708,7 +729,7 @@ fn shape_segment(
|
|||||||
/// Shape the text with tofus from the given font.
|
/// Shape the text with tofus from the given font.
|
||||||
fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font: Font) {
|
fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font: Font) {
|
||||||
let x_advance = font.advance(0).unwrap_or_default();
|
let x_advance = font.advance(0).unwrap_or_default();
|
||||||
for (cluster, c) in text.char_indices() {
|
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();
|
||||||
ctx.glyphs.push(ShapedGlyph {
|
ctx.glyphs.push(ShapedGlyph {
|
||||||
@ -723,6 +744,11 @@ fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font: Font) {
|
|||||||
c,
|
c,
|
||||||
span: ctx.spans.span_at(start),
|
span: ctx.spans.span_at(start),
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
if ctx.dir.is_positive() {
|
||||||
|
text.char_indices().for_each(add_glyph);
|
||||||
|
} else {
|
||||||
|
text.char_indices().rev().for_each(add_glyph);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -916,3 +942,32 @@ fn language(styles: StyleChain) -> rustybuzz::Language {
|
|||||||
}
|
}
|
||||||
rustybuzz::Language::from_str(&bcp).unwrap()
|
rustybuzz::Language::from_str(&bcp).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if all glyphs in `glyphs` have ranges within the range `range`.
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
fn all_glyphs_in_range(glyphs: &[ShapedGlyph], range: &Range<usize>) -> bool {
|
||||||
|
glyphs
|
||||||
|
.iter()
|
||||||
|
.all(|g| g.range.start >= range.start && g.range.end <= range.end)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the ranges of `glyphs` is in the proper order according to `dir`.
|
||||||
|
///
|
||||||
|
/// This asserts instead of returning a bool in order to provide a more informative message when the invariant is violated.
|
||||||
|
fn assert_glyph_ranges_in_order(glyphs: &[ShapedGlyph], dir: Dir) {
|
||||||
|
// Iterator::is_sorted and friends are unstable as of Rust 1.70.0
|
||||||
|
if !glyphs.is_empty() {
|
||||||
|
for i in 0..(glyphs.len() - 1) {
|
||||||
|
let a = &glyphs[i];
|
||||||
|
let b = &glyphs[i + 1];
|
||||||
|
let ord = a.range.start.cmp(&b.range.start);
|
||||||
|
let ord = if dir.is_positive() { ord } else { ord.reverse() };
|
||||||
|
if ord == Ordering::Greater {
|
||||||
|
panic!(
|
||||||
|
"glyph ranges should be monotonically {}, but found glyphs out of order:\n\nfirst: {a:#?}\nsecond: {b:#?}",
|
||||||
|
if dir.is_positive() { "increasing" } else { "decreasing" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BIN
tests/ref/bugs/bidi-tofus.png
Normal file
BIN
tests/ref/bugs/bidi-tofus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 513 B |
7
tests/typ/bugs/bidi-tofus.typ
Normal file
7
tests/typ/bugs/bidi-tofus.typ
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Test that shaping missing characters in both left-to-right and
|
||||||
|
// right-to-left directions does not cause a crash.
|
||||||
|
|
||||||
|
---
|
||||||
|
#"\u{590}\u{591}\u{592}\u{593}"
|
||||||
|
|
||||||
|
#"\u{30000}\u{30001}\u{30002}\u{30003}"
|
Loading…
x
Reference in New Issue
Block a user