mirror of
https://github.com/typst/typst
synced 2025-06-28 16:22:53 +08:00
Do binary search and find the outermost glyph with the text index 🔍
Co-Authored-By: Martin <mhaug@live.de>
This commit is contained in:
parent
464a6ff75e
commit
076e767b0e
@ -28,7 +28,7 @@ miniz_oxide = "0.3"
|
|||||||
pdf-writer = { path = "../pdf-writer" }
|
pdf-writer = { path = "../pdf-writer" }
|
||||||
rustybuzz = { git = "https://github.com/laurmaedje/rustybuzz" }
|
rustybuzz = { git = "https://github.com/laurmaedje/rustybuzz" }
|
||||||
ttf-parser = "0.12"
|
ttf-parser = "0.12"
|
||||||
unicode-bidi = "0.3"
|
unicode-bidi = "0.3.5"
|
||||||
unicode-xid = "0.2"
|
unicode-xid = "0.2"
|
||||||
xi-unicode = "0.3"
|
xi-unicode = "0.3"
|
||||||
anyhow = { version = "1", optional = true }
|
anyhow = { version = "1", optional = true }
|
||||||
|
@ -52,6 +52,12 @@ pub struct ShapedGlyph {
|
|||||||
pub safe_to_break: bool,
|
pub safe_to_break: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A visual side.
|
||||||
|
enum Side {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> ShapedText<'a> {
|
impl<'a> ShapedText<'a> {
|
||||||
/// Build the shaped text's frame.
|
/// Build the shaped text's frame.
|
||||||
pub fn build(&self, loader: &mut FontLoader) -> Frame {
|
pub fn build(&self, loader: &mut FontLoader) -> Frame {
|
||||||
@ -106,21 +112,19 @@ impl<'a> ShapedText<'a> {
|
|||||||
/// Find the subslice of glyphs that represent the given text range if both
|
/// Find the subslice of glyphs that represent the given text range if both
|
||||||
/// sides are safe to break.
|
/// sides are safe to break.
|
||||||
fn slice_safe_to_break(&self, text_range: Range<usize>) -> Option<&[ShapedGlyph]> {
|
fn slice_safe_to_break(&self, text_range: Range<usize>) -> Option<&[ShapedGlyph]> {
|
||||||
let mut start = self.find_safe_to_break(text_range.start)?;
|
let Range { mut start, mut end } = text_range;
|
||||||
let mut end = self.find_safe_to_break(text_range.end)?;
|
|
||||||
|
|
||||||
if !self.dir.is_positive() {
|
if !self.dir.is_positive() {
|
||||||
std::mem::swap(&mut start, &mut end);
|
std::mem::swap(&mut start, &mut end);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Expand to left and right if necessary because
|
let left = self.find_safe_to_break(start, Side::Left)?;
|
||||||
// find_safe_to_break may find any glyph with the text_index.
|
let right = self.find_safe_to_break(end, Side::Right)?;
|
||||||
|
Some(&self.glyphs[left .. right])
|
||||||
Some(&self.glyphs[start .. end])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the glyph slice offset at the text index if it's safe to break.
|
/// Find the glyph offset matching the text index that is most towards the
|
||||||
fn find_safe_to_break(&self, text_index: usize) -> Option<usize> {
|
/// given side and safe-to-break.
|
||||||
|
fn find_safe_to_break(&self, text_index: usize, towards: Side) -> Option<usize> {
|
||||||
let ltr = self.dir.is_positive();
|
let ltr = self.dir.is_positive();
|
||||||
|
|
||||||
// Handle edge cases.
|
// Handle edge cases.
|
||||||
@ -131,16 +135,36 @@ impl<'a> ShapedText<'a> {
|
|||||||
return Some(if ltr { len } else { 0 });
|
return Some(if ltr { len } else { 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Do binary search. Take care that RTL needs reversed ordering.
|
// Find any glyph with the text index.
|
||||||
let idx = self
|
let mut idx = self
|
||||||
.glyphs
|
.glyphs
|
||||||
.iter()
|
.binary_search_by(|g| {
|
||||||
.position(|g| g.text_index == text_index)
|
let ordering = g.text_index.cmp(&text_index);
|
||||||
.filter(|&i| self.glyphs[i].safe_to_break)?;
|
if ltr { ordering } else { ordering.reverse() }
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
// RTL needs offset one because the the start of the range should
|
let next = match towards {
|
||||||
// be exclusive and the end inclusive.
|
Side::Left => usize::checked_sub,
|
||||||
Some(if ltr { idx } else { idx + 1 })
|
Side::Right => usize::checked_add,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Search for the outermost glyph with the text index.
|
||||||
|
while let Some(next) = next(idx, 1) {
|
||||||
|
if self.glyphs.get(next).map_or(true, |g| g.text_index != text_index) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
idx = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RTL needs offset one because the left side of the range should be
|
||||||
|
// exclusive and the right side inclusive, contrary to the normal
|
||||||
|
// behaviour of ranges.
|
||||||
|
if !ltr {
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.glyphs[idx].safe_to_break.then(|| idx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
@ -36,3 +36,10 @@ Aب😀🏞سمB
|
|||||||
|
|
||||||
// Tofus are rendered with the first font.
|
// Tofus are rendered with the first font.
|
||||||
A🐈中文B
|
A🐈中文B
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test reshaping.
|
||||||
|
|
||||||
|
#font("Noto Serif Hebrew")
|
||||||
|
#lang("he")
|
||||||
|
ס \ טֶ
|
||||||
|
Loading…
x
Reference in New Issue
Block a user