diff --git a/crates/typst/src/layout/inline/shaping.rs b/crates/typst/src/layout/inline/shaping.rs index 43dc351a5..0b4a17d75 100644 --- a/crates/typst/src/layout/inline/shaping.rs +++ b/crates/typst/src/layout/inline/shaping.rs @@ -515,14 +515,14 @@ impl<'a> ShapedText<'a> { std::mem::swap(&mut start, &mut end); } - let left = self.find_safe_to_break(start, Side::Left)?; - let right = self.find_safe_to_break(end, Side::Right)?; + let left = self.find_safe_to_break(start)?; + let right = self.find_safe_to_break(end)?; Some(&self.glyphs[left..right]) } /// Find the glyph offset matching the text index that is most towards the - /// given side and safe-to-break. - fn find_safe_to_break(&self, text_index: usize, towards: Side) -> Option { + /// start of the text and safe-to-break. + fn find_safe_to_break(&self, text_index: usize) -> Option { let ltr = self.dir.is_positive(); // Handle edge cases. @@ -542,6 +542,7 @@ impl<'a> ShapedText<'a> { ordering.reverse() } }); + let mut idx = match found { Ok(idx) => idx, Err(idx) => { @@ -565,13 +566,11 @@ impl<'a> ShapedText<'a> { } }; - let next = match towards { - Side::Left => usize::checked_sub, - Side::Right => usize::checked_add, - }; - - // Search for the outermost glyph with the text index. - while let Some(next) = next(idx, 1) { + // Search for the start-most glyph with the text index. This means + // we take empty range glyphs at the start and leave those at the end + // for the next line. + let dec = if ltr { usize::checked_sub } else { usize::checked_add }; + while let Some(next) = dec(idx, 1) { if self.glyphs.get(next).map_or(true, |g| g.range.start != text_index) { break; } diff --git a/tests/ref/issue-4468-linebreak-thai.png b/tests/ref/issue-4468-linebreak-thai.png new file mode 100644 index 000000000..6257c5607 Binary files /dev/null and b/tests/ref/issue-4468-linebreak-thai.png differ diff --git a/tests/ref/linebreak-thai.png b/tests/ref/linebreak-thai.png index 8053a2128..c31a61bd1 100644 Binary files a/tests/ref/linebreak-thai.png and b/tests/ref/linebreak-thai.png differ diff --git a/tests/suite/layout/inline/linebreak.typ b/tests/suite/layout/inline/linebreak.typ index 7e959352b..8371d76e2 100644 --- a/tests/suite/layout/inline/linebreak.typ +++ b/tests/suite/layout/inline/linebreak.typ @@ -115,3 +115,11 @@ For info see #link("https://myhost.tld"). // for links because it now splits on word boundaries. We avoid the link markup // syntax because it's show rule interferes. #"http://creativecommons.org/licenses/by-nc-sa/4.0/" + +--- issue-4468-linebreak-thai --- +// In this bug, empty-range glyphs at line break boundaries could be duplicated. +// This happens for Thai specifically because it has both +// - line break opportunities +// - shaping that results in multiple glyphs in the same cluster +#set text(font: "Noto Sans Thai") +#h(85pt) งบิก