1use alloc::vec::Vec;
5use core::ops::Range;
6
7use super::TextLayout;
8
9#[derive(Clone, Default, Debug)]
12pub struct Glyph<Length> {
13 pub advance: Length,
14 pub offset_x: Length,
15 pub offset_y: Length,
16 pub glyph_id: Option<core::num::NonZeroU16>,
19 pub text_byte_offset: usize,
23}
24
25pub trait TextShaper {
40 type LengthPrimitive: core::ops::Mul
41 + core::ops::Div
42 + core::ops::Add<Output = Self::LengthPrimitive>
43 + core::ops::AddAssign
44 + euclid::num::Zero
45 + euclid::num::One
46 + core::convert::From<i16>
47 + Copy
48 + core::fmt::Debug;
49 type Length: euclid::num::Zero
50 + core::ops::AddAssign
51 + core::ops::Add<Output = Self::Length>
52 + core::ops::Sub<Output = Self::Length>
53 + Default
54 + Clone
55 + Copy
56 + core::cmp::PartialOrd
57 + core::ops::Mul<Self::LengthPrimitive, Output = Self::Length>
58 + core::ops::Div<Self::LengthPrimitive, Output = Self::Length>
59 + core::fmt::Debug;
60 fn shape_text<GlyphStorage: core::iter::Extend<Glyph<Self::Length>>>(
62 &self,
63 text: &str,
64 glyphs: &mut GlyphStorage,
65 );
66 fn glyph_for_char(&self, ch: char) -> Option<Glyph<Self::Length>>;
67 fn max_lines(&self, max_height: Self::Length) -> usize;
68}
69
70pub trait FontMetrics<Length: Copy + core::ops::Sub<Output = Length>> {
71 fn height(&self) -> Length {
72 self.ascent() - self.descent()
73 }
74 fn ascent(&self) -> Length;
75 fn descent(&self) -> Length;
76 fn x_height(&self) -> Length;
77 fn cap_height(&self) -> Length;
78}
79
80pub trait AbstractFont: TextShaper + FontMetrics<<Self as TextShaper>::Length> {}
81
82impl<T> AbstractFont for T where T: TextShaper + FontMetrics<<Self as TextShaper>::Length> {}
83
84pub struct ShapeBoundaries<'a> {
85 text: &'a str,
86 #[cfg(feature = "unicode-script")]
87 chars: core::str::CharIndices<'a>,
91 next_boundary_start: Option<usize>,
92 #[cfg(feature = "unicode-script")]
93 last_script: Option<unicode_script::Script>,
94}
95
96impl<'a> ShapeBoundaries<'a> {
97 pub fn new(text: &'a str) -> Self {
98 let next_boundary_start = if !text.is_empty() { Some(0) } else { None };
99 Self {
100 text,
101 #[cfg(feature = "unicode-script")]
102 chars: text.char_indices(),
103 next_boundary_start,
104 #[cfg(feature = "unicode-script")]
105 last_script: None,
106 }
107 }
108}
109
110impl Iterator for ShapeBoundaries<'_> {
111 type Item = usize;
112
113 #[cfg(feature = "unicode-script")]
114 fn next(&mut self) -> Option<Self::Item> {
115 self.next_boundary_start?;
116
117 let (next_offset, script) = loop {
118 match self.chars.next() {
119 Some((byte_offset, ch)) => {
120 use unicode_script::UnicodeScript;
121 let next_script = ch.script();
122 let previous_script = *self.last_script.get_or_insert(next_script);
123
124 if next_script == previous_script {
125 continue;
126 }
127 if matches!(
128 next_script,
129 unicode_script::Script::Unknown
130 | unicode_script::Script::Common
131 | unicode_script::Script::Inherited,
132 ) {
133 continue;
134 }
135
136 break (Some(byte_offset), Some(next_script));
137 }
138 None => {
139 break (None, None);
140 }
141 }
142 };
143
144 self.last_script = script;
145 self.next_boundary_start = next_offset;
146
147 Some(self.next_boundary_start.unwrap_or(self.text.len()))
148 }
149
150 #[cfg(not(feature = "unicode-script"))]
151 fn next(&mut self) -> Option<Self::Item> {
152 match self.next_boundary_start {
153 Some(_) => {
154 self.next_boundary_start = None;
155 Some(self.text.len())
156 }
157 None => None,
158 }
159 }
160}
161
162#[derive(Debug)]
163pub struct TextRun {
164 pub byte_range: Range<usize>,
165 pub glyph_range: Range<usize>,
166 }
168
169pub struct ShapeBuffer<Length> {
170 pub glyphs: Vec<Glyph<Length>>,
171 pub text_runs: Vec<TextRun>,
172}
173
174impl<Length> ShapeBuffer<Length> {
175 pub fn new<Font>(layout: &TextLayout<Font>, text: &str) -> Self
176 where
177 Font: AbstractFont<Length = Length>,
178 Length: Copy + core::ops::AddAssign,
179 {
180 let mut glyphs = Vec::new();
181 let text_runs = ShapeBoundaries::new(text)
182 .scan(0, |run_start, run_end| {
183 let glyphs_start = glyphs.len();
184
185 layout.font.shape_text(&text[*run_start..run_end], &mut glyphs);
186
187 if let Some(letter_spacing) = layout.letter_spacing {
188 if glyphs.len() > glyphs_start {
189 let mut last_byte_offset = glyphs[glyphs_start].text_byte_offset;
190 for index in glyphs_start + 1..glyphs.len() {
191 let current_glyph_byte_offset = glyphs[index].text_byte_offset;
192 if current_glyph_byte_offset != last_byte_offset {
193 let previous_glyph = &mut glyphs[index - 1];
194 previous_glyph.advance += letter_spacing;
195 }
196 last_byte_offset = current_glyph_byte_offset;
197 }
198
199 glyphs.last_mut().unwrap().advance += letter_spacing;
200 }
201 }
202
203 let run = TextRun {
204 byte_range: Range { start: *run_start, end: run_end },
205 glyph_range: Range { start: glyphs_start, end: glyphs.len() },
206 };
207 *run_start = run_end;
208
209 Some(run)
210 })
211 .collect();
212
213 Self { glyphs, text_runs }
214 }
215}
216
217#[test]
218fn test_shape_boundaries_simple() {
219 {
220 let simple_text = "Hello World";
221 let mut itemizer = ShapeBoundaries::new(simple_text);
222 assert_eq!(itemizer.next(), Some(simple_text.len()));
223 assert_eq!(itemizer.next(), None);
224 }
225}
226
227#[test]
228fn test_shape_boundaries_empty() {
229 {
230 let mut itemizer = ShapeBoundaries::new("");
231 assert_eq!(itemizer.next(), None);
232 }
233}
234
235#[test]
236#[cfg_attr(
237 not(feature = "unicode-script"),
238 ignore = "Not supported without the unicode-script feature"
239)]
240fn test_shape_boundaries_script_change() {
241 {
242 let text = "abc🍌🐒defதோசை.";
243 let mut itemizer = ShapeBoundaries::new(text).scan(0, |start, end| {
244 let str = &text[*start..end];
245 *start = end;
246 Some(str)
247 });
248 assert_eq!(itemizer.next(), Some("abc🍌🐒def"));
249 assert_eq!(itemizer.next(), Some("தோசை."));
250 assert_eq!(itemizer.next(), None);
251 }
252}
253
254#[cfg(test)]
255impl TextShaper for &rustybuzz::Face<'_> {
256 type LengthPrimitive = f32;
257 type Length = f32;
258 fn shape_text<GlyphStorage: std::iter::Extend<Glyph<f32>>>(
259 &self,
260 text: &str,
261 glyphs: &mut GlyphStorage,
262 ) {
263 let mut buffer = rustybuzz::UnicodeBuffer::new();
264 buffer.push_str(text);
265 let glyph_buffer = rustybuzz::shape(self, &[], buffer);
266
267 let output_glyph_generator =
268 glyph_buffer.glyph_infos().iter().zip(glyph_buffer.glyph_positions().iter()).map(
269 |(info, position)| {
270 let mut out_glyph = Glyph::default();
271 out_glyph.glyph_id = core::num::NonZeroU16::new(info.glyph_id as u16);
272 out_glyph.offset_x = position.x_offset as _;
273 out_glyph.offset_y = position.y_offset as _;
274 out_glyph.advance = position.x_advance as _;
275 out_glyph.text_byte_offset = info.cluster as usize;
276 out_glyph
277 },
278 );
279
280 glyphs.extend(output_glyph_generator);
282 }
283
284 fn glyph_for_char(&self, _ch: char) -> Option<Glyph<f32>> {
285 todo!()
286 }
287
288 fn max_lines(&self, max_height: f32) -> usize {
289 (max_height / self.height()).floor() as _
290 }
291}
292
293#[cfg(test)]
294impl FontMetrics<f32> for &rustybuzz::Face<'_> {
295 fn ascent(&self) -> f32 {
296 self.ascender() as _
297 }
298
299 fn descent(&self) -> f32 {
300 self.descender() as _
301 }
302
303 fn x_height(&self) -> f32 {
304 rustybuzz::ttf_parser::Face::x_height(self).unwrap_or_default() as _
305 }
306
307 fn cap_height(&self) -> f32 {
308 rustybuzz::ttf_parser::Face::capital_height(self).unwrap_or_default() as _
309 }
310}
311
312#[cfg(test)]
313fn with_dejavu_font<R>(mut callback: impl FnMut(&rustybuzz::Face<'_>) -> R) -> R {
314 let mut collection = fontique::Collection::default();
315 let dejavu_path: std::path::PathBuf =
316 [env!("CARGO_MANIFEST_DIR"), "..", "common", "sharedfontique", "DejaVuSans.ttf"]
317 .iter()
318 .collect();
319 let registered_fonts =
320 collection.register_fonts(std::fs::read(&dejavu_path).unwrap().into(), None);
321 let mut cache = fontique::SourceCache::default();
322 let mut query = collection.query(&mut cache);
323 query.set_families(std::iter::once(fontique::QueryFamily::from(registered_fonts[0].0)));
324 let mut font = None;
325 query.matches_with(|query_font| {
326 font = Some(query_font.clone());
327 fontique::QueryStatus::Stop
328 });
329 let font = font.unwrap();
330 let face = rustybuzz::Face::from_slice(font.blob.data(), font.index)
331 .expect("unable to parse dejavu font");
332 callback(&face)
333}
334
335#[test]
336fn test_shaping() {
337 use std::num::NonZeroU16;
338 use TextShaper;
339
340 with_dejavu_font(|face| {
341 {
342 let mut shaped_glyphs = Vec::new();
343 face.shape_text("a\u{0304}\u{0301}b", &mut shaped_glyphs);
345
346 assert_eq!(shaped_glyphs.len(), 3);
347 assert_eq!(shaped_glyphs[0].glyph_id, NonZeroU16::new(195));
348 assert_eq!(shaped_glyphs[0].text_byte_offset, 0);
349
350 assert_eq!(shaped_glyphs[1].glyph_id, NonZeroU16::new(690));
351 assert_eq!(shaped_glyphs[1].text_byte_offset, 0);
352
353 assert_eq!(shaped_glyphs[2].glyph_id, NonZeroU16::new(69));
354 assert_eq!(shaped_glyphs[2].text_byte_offset, 5);
355 }
356
357 {
358 let mut shaped_glyphs = Vec::new();
359 face.shape_text("a b", &mut shaped_glyphs);
361
362 assert_eq!(shaped_glyphs.len(), 3);
363 assert_eq!(shaped_glyphs[0].glyph_id, NonZeroU16::new(68));
364 assert_eq!(shaped_glyphs[0].text_byte_offset, 0);
365
366 assert_eq!(shaped_glyphs[1].text_byte_offset, 1);
367
368 assert_eq!(shaped_glyphs[2].glyph_id, NonZeroU16::new(69));
369 assert_eq!(shaped_glyphs[2].text_byte_offset, 2);
370 }
371 });
372}
373
374#[test]
375fn test_letter_spacing() {
376 use TextShaper;
377
378 with_dejavu_font(|face| {
379 let text = "a\u{0304}\u{0301}b";
381 let advances = {
382 let mut shaped_glyphs = Vec::new();
383 face.shape_text(text, &mut shaped_glyphs);
384
385 assert_eq!(shaped_glyphs.len(), 3);
386
387 shaped_glyphs.iter().map(|g| g.advance).collect::<Vec<_>>()
388 };
389
390 let layout = TextLayout { font: &face, letter_spacing: Some(20.) };
391 let buffer = ShapeBuffer::new(&layout, text);
392
393 assert_eq!(buffer.glyphs.len(), advances.len());
394
395 let mut expected_advances = advances;
396 expected_advances[1] += layout.letter_spacing.unwrap();
397 *expected_advances.last_mut().unwrap() += layout.letter_spacing.unwrap();
398
399 assert_eq!(
400 buffer.glyphs.iter().map(|glyph| glyph.advance).collect::<Vec<_>>(),
401 expected_advances
402 );
403 });
404}