diff --git a/examples/text/res/fallback.txt b/examples/text/res/fallback.txt new file mode 100644 index 0000000..cf462b2 --- /dev/null +++ b/examples/text/res/fallback.txt @@ -0,0 +1,2 @@ +TE🤯STالعربيةTE🤣ST你好 +العربيTE🤯STالعربيةTE🤣ST你好العربي diff --git a/examples/text/src/font/layout.rs b/examples/text/src/font/layout.rs index 7fbf995..5c86cd0 100644 --- a/examples/text/src/font/layout.rs +++ b/examples/text/src/font/layout.rs @@ -75,7 +75,6 @@ impl<'a> FontLayoutLine<'a> { let mut i = 0; for off_y in 0..image.placement.height as i32 { for off_x in 0..image.placement.width as i32 { - println!("{}, {}, {:x?}", off_x, off_y, &image.data[i..i + 4]); let color = (image.data[i + 3] as u32) << 24 | (image.data[i] as u32) << 16 | diff --git a/examples/text/src/font/matches.rs b/examples/text/src/font/matches.rs index f6ba959..4df6ea6 100644 --- a/examples/text/src/font/matches.rs +++ b/examples/text/src/font/matches.rs @@ -5,117 +5,134 @@ pub struct FontMatches<'a> { } impl<'a> FontMatches<'a> { - fn shape_word(&self, line: &str, start_word: usize, end_word: usize) -> FontShapeWord { + fn shape_word(&self, font_i: usize, line: &str, start_word: usize, end_word: usize) -> FontShapeWord { let word = &line[start_word..end_word]; - let mut words_by_font = Vec::with_capacity(self.fonts.len()); - for font in self.fonts.iter() { - let font_scale = font.rustybuzz.units_per_em() as f32; + let font_scale = self.fonts[font_i].rustybuzz.units_per_em() as f32; - let mut buffer = rustybuzz::UnicodeBuffer::new(); - buffer.push_str(word); - buffer.guess_segment_properties(); - let direction = buffer.direction(); + let mut buffer = rustybuzz::UnicodeBuffer::new(); + buffer.push_str(word); + buffer.guess_segment_properties(); + let direction = buffer.direction(); - let glyph_buffer = rustybuzz::shape(&font.rustybuzz, &[], buffer); - let glyph_infos = glyph_buffer.glyph_infos(); - let glyph_positions = glyph_buffer.glyph_positions(); + let glyph_buffer = rustybuzz::shape(&self.fonts[font_i].rustybuzz, &[], buffer); + let glyph_infos = glyph_buffer.glyph_infos(); + let glyph_positions = glyph_buffer.glyph_positions(); - let mut misses = 0; - let mut glyphs = Vec::with_capacity(glyph_infos.len()); - for (info, pos) in glyph_infos.iter().zip(glyph_positions.iter()) { - let x_advance = pos.x_advance as f32 / font_scale; - let y_advance = pos.y_advance as f32 / font_scale; - let x_offset = pos.x_offset as f32 / font_scale; - let y_offset = pos.y_offset as f32 / font_scale; + let mut missing = Vec::new(); + let mut glyphs = Vec::with_capacity(glyph_infos.len()); + for (info, pos) in glyph_infos.iter().zip(glyph_positions.iter()) { + let x_advance = pos.x_advance as f32 / font_scale; + let y_advance = pos.y_advance as f32 / font_scale; + let x_offset = pos.x_offset as f32 / font_scale; + let y_offset = pos.y_offset as f32 / font_scale; - //println!(" {:?} {:?}", info, pos); - if info.glyph_id == 0 { - misses += 1; + //println!(" {:?} {:?}", info, pos); + if info.glyph_id == 0 { + missing.push(start_word + info.cluster as usize); + } + + #[cfg(feature = "ab_glyph")] + let inner = ab_glyph::GlyphId(info.glyph_id as u16); + + #[cfg(feature = "rusttype")] + let inner = font.rusttype.glyph(rusttype::GlyphId(info.glyph_id as u16)); + + #[cfg(feature = "swash")] + let inner = info.glyph_id as swash::GlyphId; + + glyphs.push(FontShapeGlyph { + start: start_word + info.cluster as usize, + end: end_word, // Set later + x_advance, + y_advance, + x_offset, + y_offset, + #[cfg(feature = "ab_glyph")] + font: &self.fonts[font_i].ab_glyph, + #[cfg(feature = "swash")] + font: &self.fonts[font_i].swash, + inner, + }); + } + + // Adjust end of glyphs + match direction { + rustybuzz::Direction::LeftToRight => { + for i in (1..glyphs.len()).rev() { + let next_start = glyphs[i].start; + let next_end = glyphs[i].end; + let prev = &mut glyphs[i - 1]; + if prev.start == next_start { + prev.end = next_end; + } else { + prev.end = next_start; + } + } + }, + rustybuzz::Direction::RightToLeft => { + for i in 1..glyphs.len() { + let next_start = glyphs[i - 1].start; + let next_end = glyphs[i - 1].end; + let prev = &mut glyphs[i]; + if prev.start == next_start { + prev.end = next_end; + } else { + prev.end = next_start; + } + } + }, + //TODO: other directions + _ => (), + } + + //TODO: improve performance! + if !missing.is_empty() && font_i + 1 < self.fonts.len() { + let mut fb_word = self.shape_word(font_i + 1, line, start_word, end_word); + + for start_glyph in missing { + // Find beginning of glyphs to replace + let mut i = 0; + while i < glyphs.len() { + if glyphs[i].start == start_glyph { + break; + } + i += 1; } - #[cfg(feature = "ab_glyph")] - let inner = ab_glyph::GlyphId(info.glyph_id as u16); - - #[cfg(feature = "rusttype")] - let inner = font.rusttype.glyph(rusttype::GlyphId(info.glyph_id as u16)); - - #[cfg(feature = "swash")] - let inner = info.glyph_id as swash::GlyphId; - - glyphs.push(FontShapeGlyph { - start: start_word + info.cluster as usize, - end: end_word, // Set later - x_advance, - y_advance, - x_offset, - y_offset, - #[cfg(feature = "ab_glyph")] - font: &font.ab_glyph, - #[cfg(feature = "swash")] - font: &font.swash, - inner, - }); - } - - // Adjust end of glyphs - match direction { - rustybuzz::Direction::LeftToRight => { - for i in (1..glyphs.len()).rev() { - let next_start = glyphs[i].start; - let next_end = glyphs[i].end; - let prev = &mut glyphs[i - 1]; - if prev.start == next_start { - prev.end = next_end; - } else { - prev.end = next_start; + // Remove all matching glyphs and find end + let mut end_glyph = start_glyph; + while i < glyphs.len() { + if glyphs[i].start == start_glyph { + let glyph = glyphs.remove(i); + if glyph.end > end_glyph { + end_glyph = glyph.end; } + } else { + break; } - }, - rustybuzz::Direction::RightToLeft => { - for i in 1..glyphs.len() { - let next_start = glyphs[i - 1].start; - let next_end = glyphs[i - 1].end; - let prev = &mut glyphs[i]; - if prev.start == next_start { - prev.end = next_end; - } else { - prev.end = next_start; - } + } + + // Insert all matching glyphs + let mut j = 0; + while j < fb_word.glyphs.len() { + if fb_word.glyphs[j].start >= start_glyph && fb_word.glyphs[j].end <= end_glyph { + glyphs.insert(i, fb_word.glyphs.remove(j)); + i += 1; + } else { + j += 1; } - }, - //TODO: other directions - _ => (), - } - - /* - for glyph in glyphs.iter() { - println!("'{}': {}, {}, {}, {}", &line[glyph.start..glyph.end], glyph.x_advance, glyph.y_advance, glyph.x_offset, glyph.y_offset); - } - */ - - let word = FontShapeWord { glyphs }; - if misses == 0 { - return word; - } else { - words_by_font.push((misses, word)); + } } } - let mut least_misses_i = 0; - let mut least_misses = usize::MAX; - for (i, (misses, _)) in words_by_font.iter().enumerate() { - if *misses < least_misses { - least_misses_i = i; - least_misses = *misses; - } + /* + for glyph in glyphs.iter() { + println!("'{}': {}, {}, {}, {}", &line[glyph.start..glyph.end], glyph.x_advance, glyph.y_advance, glyph.x_offset, glyph.y_offset); } + */ - if least_misses_i > 0 { - //println!("MISSES {}, {}", least_misses_i, least_misses); - } - - words_by_font.remove(least_misses_i).1 + FontShapeWord { glyphs } } fn shape_span(&self, line: &str, start_span: usize, end_span: usize, para_rtl: bool, span_rtl: bool) -> FontShapeSpan { @@ -126,7 +143,7 @@ impl<'a> FontMatches<'a> { println!("Span {}: '{}'", if span_rtl { "RTL" } else { "LTR" }, span); let mut words = vec![ - self.shape_word(line, start_span, end_span), + self.shape_word(0, line, start_span, end_span), ]; if span_rtl {