diff --git a/crates/resvg/tests/integration/render.rs b/crates/resvg/tests/integration/render.rs index 3118508a3..ee0bae263 100644 --- a/crates/resvg/tests/integration/render.rs +++ b/crates/resvg/tests/integration/render.rs @@ -1502,6 +1502,7 @@ use crate::render; #[test] fn text_text_filter_bbox() { assert_eq!(render("tests/text/text/filter-bbox"), 0); } #[test] fn text_text_ligatures_handling_in_mixed_fonts_1() { assert_eq!(render("tests/text/text/ligatures-handling-in-mixed-fonts-1"), 0); } #[test] fn text_text_ligatures_handling_in_mixed_fonts_2() { assert_eq!(render("tests/text/text/ligatures-handling-in-mixed-fonts-2"), 0); } +#[test] fn text_text_glyph_splitting() { assert_eq!(render("tests/text/text/glyph-splitting"), 0); } #[test] fn text_text_mm_coordinates() { assert_eq!(render("tests/text/text/mm-coordinates"), 0); } #[test] fn text_text_nested() { assert_eq!(render("tests/text/text/nested"), 0); } #[test] fn text_text_no_coordinates() { assert_eq!(render("tests/text/text/no-coordinates"), 0); } diff --git a/crates/resvg/tests/tests/text/text/glyph-splitting.png b/crates/resvg/tests/tests/text/text/glyph-splitting.png new file mode 100644 index 000000000..11196b26e Binary files /dev/null and b/crates/resvg/tests/tests/text/text/glyph-splitting.png differ diff --git a/crates/resvg/tests/tests/text/text/glyph-splitting.svg b/crates/resvg/tests/tests/text/text/glyph-splitting.svg new file mode 100644 index 000000000..e2568f7e3 --- /dev/null +++ b/crates/resvg/tests/tests/text/text/glyph-splitting.svg @@ -0,0 +1,17 @@ + + Ligatures handling in mixed fonts (2) + + This is a very resvg-specific test to make sure we're corrently + handling ligatures in text with multiple fonts + + + + + + H नमस्ते + + + + + diff --git a/crates/usvg/src/text/layout.rs b/crates/usvg/src/text/layout.rs index 3e9ced0e4..2261f66bb 100644 --- a/crates/usvg/src/text/layout.rs +++ b/crates/usvg/src/text/layout.rs @@ -1,7 +1,7 @@ // Copyright 2022 the Resvg Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::num::NonZeroU16; use std::sync::Arc; @@ -882,6 +882,11 @@ fn process_chunk( // but some can use `fi` (U+FB01) instead. // Meaning that during merging we have to overwrite not individual glyphs, but clusters. + // Glyph splitting assigns distinct glyphs to the same index in the original text, we need to + // store previously used indices to make sure we do not re-use the same index while overwriting + // span glyphs. + let mut positions = HashSet::new(); + let mut glyphs = Vec::new(); for span in &chunk.spans { let font = match fonts_cache.get(&span.font) { @@ -904,6 +909,8 @@ fn process_chunk( continue; } + positions.clear(); + // Overwrite span's glyphs. let mut iter = tmp_glyphs.into_iter(); while let Some(new_glyph) = iter.next() { @@ -911,10 +918,16 @@ fn process_chunk( continue; } - let Some(idx) = glyphs.iter().position(|g| g.byte_idx == new_glyph.byte_idx) else { + let Some(idx) = glyphs + .iter() + .position(|g| g.byte_idx == new_glyph.byte_idx) + .filter(|pos| !positions.contains(pos)) + else { continue; }; + positions.insert(idx); + let prev_cluster_len = glyphs[idx].cluster_len; if prev_cluster_len < new_glyph.cluster_len { // If the new font represents the same cluster with fewer glyphs