1 | use std::{ |
2 | borrow::Borrow, |
3 | cell::RefCell, |
4 | collections::HashMap, |
5 | ffi::OsStr, |
6 | fs, |
7 | hash::{Hash, Hasher}, |
8 | ops::Range, |
9 | path::Path as FilePath, |
10 | rc::Rc, |
11 | }; |
12 | |
13 | use fnv::{FnvBuildHasher, FnvHashMap, FnvHasher}; |
14 | use lru::LruCache; |
15 | use rustybuzz::ttf_parser; |
16 | use slotmap::{DefaultKey, SlotMap}; |
17 | |
18 | use unicode_bidi::BidiInfo; |
19 | use unicode_segmentation::UnicodeSegmentation; |
20 | |
21 | use crate::{ |
22 | paint::{PaintFlavor, StrokeSettings, TextSettings}, |
23 | Canvas, Color, ErrorKind, FillRule, ImageFlags, ImageId, ImageInfo, Paint, PixelFormat, RenderTarget, Renderer, |
24 | }; |
25 | |
26 | mod atlas; |
27 | pub use atlas::Atlas; |
28 | |
29 | mod font; |
30 | pub use font::FontMetrics; |
31 | use font::{Font, GlyphRendering}; |
32 | |
33 | // This padding is an empty border around the glyph’s pixels but inside the |
34 | // sampled area (texture coordinates) for the quad in render_atlas(). |
35 | const GLYPH_PADDING: u32 = 1; |
36 | // We add an additional margin of 1 pixel outside of the sampled area, |
37 | // to deal with the linear interpolation of texels at the edge of that area |
38 | // which mixes in the texels just outside of the edge. |
39 | // This manifests as noise around the glyph, outside of the padding. |
40 | const GLYPH_MARGIN: u32 = 1; |
41 | |
42 | const TEXTURE_SIZE: usize = 512; |
43 | const DEFAULT_LRU_CACHE_CAPACITY: usize = 1000; |
44 | |
45 | /// A font handle. |
46 | #[derive (Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] |
47 | pub struct FontId(DefaultKey); |
48 | |
49 | /// Text baseline vertical alignment: |
50 | /// `Top`, `Middle`, `Alphabetic` (default), `Bottom`. |
51 | #[derive (Copy, Clone, Debug, Hash, Eq, PartialEq)] |
52 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
53 | pub enum Baseline { |
54 | /// The text baseline is the top of the em square. |
55 | Top, |
56 | /// The text baseline is the middle of the em square. |
57 | Middle, |
58 | /// The text baseline is the normal alphabetic baseline. Default value. |
59 | Alphabetic, |
60 | // The text baseline is the bottom of the bounding box. |
61 | Bottom, |
62 | } |
63 | |
64 | impl Default for Baseline { |
65 | fn default() -> Self { |
66 | Self::Alphabetic |
67 | } |
68 | } |
69 | |
70 | /// Text horizontal alignment: `Left` (default), `Center`, `Right`. |
71 | #[derive (Copy, Clone, Debug, Hash, Eq, PartialEq)] |
72 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
73 | pub enum Align { |
74 | /// The text is left-aligned. |
75 | Left, |
76 | /// The text is centered. |
77 | Center, |
78 | /// The text is right-aligned. |
79 | Right, |
80 | } |
81 | |
82 | impl Default for Align { |
83 | fn default() -> Self { |
84 | Self::Left |
85 | } |
86 | } |
87 | |
88 | #[derive (Copy, Clone, Debug, Hash, Eq, PartialEq)] |
89 | pub enum RenderMode { |
90 | Fill, |
91 | Stroke, |
92 | } |
93 | |
94 | impl Default for RenderMode { |
95 | fn default() -> Self { |
96 | Self::Fill |
97 | } |
98 | } |
99 | |
100 | #[derive (Copy, Clone, Debug, Hash, Eq, PartialEq)] |
101 | pub(crate) struct RenderedGlyphId { |
102 | glyph_index: u32, |
103 | font_id: FontId, |
104 | size: u32, |
105 | line_width: u32, |
106 | render_mode: RenderMode, |
107 | subpixel_location: u8, |
108 | } |
109 | |
110 | impl RenderedGlyphId { |
111 | fn new( |
112 | glyph_index: u32, |
113 | font_id: FontId, |
114 | font_size: f32, |
115 | line_width: f32, |
116 | mode: RenderMode, |
117 | subpixel_location: u8, |
118 | ) -> Self { |
119 | Self { |
120 | glyph_index, |
121 | font_id, |
122 | size: (font_size * 10.0).trunc() as u32, |
123 | line_width: (line_width * 10.0).trunc() as u32, |
124 | render_mode: mode, |
125 | subpixel_location, |
126 | } |
127 | } |
128 | } |
129 | |
130 | #[derive (Copy, Clone, Debug)] |
131 | pub(crate) struct RenderedGlyph { |
132 | texture_index: usize, |
133 | width: u32, |
134 | height: u32, |
135 | bearing_y: i32, |
136 | atlas_x: u32, |
137 | atlas_y: u32, |
138 | color_glyph: bool, |
139 | } |
140 | |
141 | #[derive (Copy, Clone, Debug)] |
142 | pub struct ShapedGlyph { |
143 | pub x: f32, |
144 | pub y: f32, |
145 | pub c: char, |
146 | pub byte_index: usize, |
147 | pub font_id: FontId, |
148 | pub codepoint: u32, |
149 | pub width: f32, |
150 | pub height: f32, |
151 | pub advance_x: f32, |
152 | pub advance_y: f32, |
153 | pub offset_x: f32, |
154 | pub offset_y: f32, |
155 | pub bearing_x: f32, |
156 | pub bearing_y: f32, |
157 | pub bitmap_glyph: bool, |
158 | } |
159 | |
160 | #[derive (Clone, Debug, Default)] |
161 | struct ShapedWord { |
162 | glyphs: Vec<ShapedGlyph>, |
163 | width: f32, |
164 | } |
165 | |
166 | #[derive (Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] |
167 | struct ShapingId { |
168 | size: u32, |
169 | word_hash: u64, |
170 | font_ids: [Option<FontId>; 8], |
171 | } |
172 | |
173 | impl ShapingId { |
174 | fn new(font_size: f32, font_ids: [Option<FontId>; 8], word: &str, max_width: Option<f32>) -> Self { |
175 | let mut hasher: FnvHasher = FnvHasher::default(); |
176 | word.hash(&mut hasher); |
177 | if let Some(max_width: f32) = max_width { |
178 | (max_width.trunc() as i32).hash(&mut hasher); |
179 | } |
180 | |
181 | Self { |
182 | size: (font_size * 10.0).trunc() as u32, |
183 | word_hash: hasher.finish(), |
184 | font_ids, |
185 | } |
186 | } |
187 | } |
188 | |
189 | type ShapedWordsCache<H> = LruCache<ShapingId, Result<ShapedWord, ErrorKind>, H>; |
190 | type ShapingRunCache<H> = LruCache<ShapingId, TextMetrics, H>; |
191 | |
192 | pub(crate) struct FontTexture { |
193 | pub atlas: Atlas, |
194 | pub(crate) image_id: ImageId, |
195 | } |
196 | |
197 | /// TextContext provides functionality for text processing in femtovg. You can |
198 | /// add fonts using the [`Self::add_font_file()`], [`Self::add_font_mem()`] and |
199 | /// [`Self::add_font_dir()`] functions. For each registered font a [`FontId`] is |
200 | /// returned. |
201 | /// |
202 | /// The [`FontId`] can be supplied to [`crate::Paint`] along with additional parameters |
203 | /// such as the font size. |
204 | /// |
205 | /// The paint is needed when using TextContext's measurement functions such as |
206 | /// [`Self::measure_text()`]. |
207 | /// |
208 | /// Note that the measurements are done entirely with the supplied sizes in the paint |
209 | /// parameter. If you need measurements that take a [`crate::Canvas`]'s transform or dpi into |
210 | /// account (see [`crate::Canvas::set_size()`]), you need to use the measurement functions |
211 | /// on the canvas. |
212 | #[derive (Clone, Default)] |
213 | pub struct TextContext(pub(crate) Rc<RefCell<TextContextImpl>>); |
214 | |
215 | impl TextContext { |
216 | /// Registers all .ttf files from a directory with this text context. If successful, the |
217 | /// font ids of all registered fonts are returned. |
218 | pub fn add_font_dir<T: AsRef<FilePath>>(&self, path: T) -> Result<Vec<FontId>, ErrorKind> { |
219 | self.0.borrow_mut().add_font_dir(path) |
220 | } |
221 | |
222 | /// Registers the .ttf file from the specified path with this text context. If successful, |
223 | /// the font id is returned. |
224 | pub fn add_font_file<T: AsRef<FilePath>>(&self, path: T) -> Result<FontId, ErrorKind> { |
225 | self.0.borrow_mut().add_font_file(path) |
226 | } |
227 | |
228 | /// Registers the in-memory representation of a TrueType font pointed to by the data |
229 | /// parameter with this text context. If successful, the font id is returned. |
230 | pub fn add_font_mem(&self, data: &[u8]) -> Result<FontId, ErrorKind> { |
231 | self.0.borrow_mut().add_font_mem(data) |
232 | } |
233 | |
234 | /// Registers the in-memory representation of a TrueType font pointed to by the shared data |
235 | /// parameter with this text context. If successful, the font id is returned. The face_index |
236 | /// specifies the face index if the font data is a true type font collection. For plain true |
237 | /// type fonts, use 0 as index. |
238 | pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>( |
239 | &self, |
240 | data: T, |
241 | face_index: u32, |
242 | ) -> Result<FontId, ErrorKind> { |
243 | self.0.borrow_mut().add_shared_font_with_index(data, face_index) |
244 | } |
245 | |
246 | /// Returns information on how the provided text will be drawn with the specified paint. |
247 | pub fn measure_text<S: AsRef<str>>( |
248 | &self, |
249 | x: f32, |
250 | y: f32, |
251 | text: S, |
252 | paint: &Paint, |
253 | ) -> Result<TextMetrics, ErrorKind> { |
254 | self.0.borrow_mut().measure_text(x, y, text, &paint.text) |
255 | } |
256 | |
257 | /// Returns the maximum index-th byte of text that will fit inside max_width. |
258 | /// |
259 | /// The retuned index will always lie at the start and/or end of a UTF-8 code point sequence or at the start or end of the text |
260 | pub fn break_text<S: AsRef<str>>(&self, max_width: f32, text: S, paint: &Paint) -> Result<usize, ErrorKind> { |
261 | self.0.borrow_mut().break_text(max_width, text, &paint.text) |
262 | } |
263 | |
264 | /// Returnes a list of ranges representing each line of text that will fit inside max_width |
265 | pub fn break_text_vec<S: AsRef<str>>( |
266 | &self, |
267 | max_width: f32, |
268 | text: S, |
269 | paint: &Paint, |
270 | ) -> Result<Vec<Range<usize>>, ErrorKind> { |
271 | self.0.borrow_mut().break_text_vec(max_width, text, &paint.text) |
272 | } |
273 | |
274 | /// Returns font metrics for a particular Paint. |
275 | pub fn measure_font(&self, paint: &Paint) -> Result<FontMetrics, ErrorKind> { |
276 | self.0 |
277 | .borrow_mut() |
278 | .measure_font(paint.text.font_size, &paint.text.font_ids) |
279 | } |
280 | |
281 | /// Adjusts the capacity of the shaping run cache. This is a cache for measurements of whole |
282 | /// strings. |
283 | pub fn resize_shaping_run_cache(&self, capacity: std::num::NonZeroUsize) { |
284 | self.0.borrow_mut().resize_shaping_run_cache(capacity) |
285 | } |
286 | |
287 | /// Adjusts the capacity of the shaped words cache. This is a cache for measurements of |
288 | /// individual words. Words are separated by |
289 | /// [UAX#29 word boundaries](http://www.unicode.org/reports/tr29/#Word_Boundaries). |
290 | pub fn resize_shaped_words_cache(&self, capacity: std::num::NonZeroUsize) { |
291 | self.0.borrow_mut().resize_shaped_words_cache(capacity) |
292 | } |
293 | } |
294 | |
295 | pub(crate) struct TextContextImpl { |
296 | fonts: SlotMap<DefaultKey, Font>, |
297 | shaping_run_cache: ShapingRunCache<FnvBuildHasher>, |
298 | shaped_words_cache: ShapedWordsCache<FnvBuildHasher>, |
299 | } |
300 | |
301 | impl Default for TextContextImpl { |
302 | fn default() -> Self { |
303 | let fnv_run: BuildHasherDefault = FnvBuildHasher::default(); |
304 | let fnv_words: BuildHasherDefault = FnvBuildHasher::default(); |
305 | |
306 | Self { |
307 | fonts: Default::default(), |
308 | shaping_run_cache: LruCache::with_hasher( |
309 | cap:std::num::NonZeroUsize::new(DEFAULT_LRU_CACHE_CAPACITY).unwrap(), |
310 | hash_builder:fnv_run, |
311 | ), |
312 | shaped_words_cache: LruCache::with_hasher( |
313 | cap:std::num::NonZeroUsize::new(DEFAULT_LRU_CACHE_CAPACITY).unwrap(), |
314 | hash_builder:fnv_words, |
315 | ), |
316 | } |
317 | } |
318 | } |
319 | |
320 | impl TextContextImpl { |
321 | pub fn resize_shaping_run_cache(&mut self, capacity: std::num::NonZeroUsize) { |
322 | self.shaping_run_cache.resize(capacity); |
323 | } |
324 | |
325 | pub fn resize_shaped_words_cache(&mut self, capacity: std::num::NonZeroUsize) { |
326 | self.shaped_words_cache.resize(capacity); |
327 | } |
328 | |
329 | pub fn add_font_dir<T: AsRef<FilePath>>(&mut self, path: T) -> Result<Vec<FontId>, ErrorKind> { |
330 | let path = path.as_ref(); |
331 | let mut fonts = Vec::new(); |
332 | |
333 | if path.is_dir() { |
334 | for entry in fs::read_dir(path)? { |
335 | let entry = entry?; |
336 | let path = entry.path(); |
337 | |
338 | if path.is_dir() { |
339 | self.add_font_dir(&path)?; |
340 | } else if let Some("ttf" ) = path.extension().and_then(OsStr::to_str) { |
341 | fonts.push(self.add_font_file(path)?); |
342 | } else if let Some("ttc" ) = path.extension().and_then(OsStr::to_str) { |
343 | fonts.extend(self.add_font_file_collection(path)?); |
344 | } |
345 | } |
346 | } |
347 | |
348 | Ok(fonts) |
349 | } |
350 | |
351 | pub fn add_font_file<T: AsRef<FilePath>>(&mut self, path: T) -> Result<FontId, ErrorKind> { |
352 | let data = std::fs::read(path)?; |
353 | |
354 | self.add_font_mem(&data) |
355 | } |
356 | |
357 | pub fn add_font_file_collection<T: AsRef<FilePath>>( |
358 | &mut self, |
359 | path: T, |
360 | ) -> Result<impl Iterator<Item = FontId> + '_, ErrorKind> { |
361 | let data = std::fs::read(path)?; |
362 | |
363 | let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1); |
364 | Ok((0..count).filter_map(move |index| self.add_font_mem_with_index(&data, index).ok())) |
365 | } |
366 | |
367 | pub fn add_font_mem(&mut self, data: &[u8]) -> Result<FontId, ErrorKind> { |
368 | self.add_font_mem_with_index(data, 0) |
369 | } |
370 | |
371 | pub fn add_font_mem_with_index(&mut self, data: &[u8], face_index: u32) -> Result<FontId, ErrorKind> { |
372 | self.clear_caches(); |
373 | |
374 | let data_copy = data.to_owned(); |
375 | let font = Font::new_with_data(data_copy, face_index)?; |
376 | Ok(FontId(self.fonts.insert(font))) |
377 | } |
378 | |
379 | pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>( |
380 | &mut self, |
381 | data: T, |
382 | face_index: u32, |
383 | ) -> Result<FontId, ErrorKind> { |
384 | self.clear_caches(); |
385 | |
386 | let font = Font::new_with_data(data, face_index)?; |
387 | Ok(FontId(self.fonts.insert(font))) |
388 | } |
389 | |
390 | pub fn font(&self, id: FontId) -> Option<&Font> { |
391 | self.fonts.get(id.0) |
392 | } |
393 | |
394 | pub fn font_mut(&mut self, id: FontId) -> Option<&mut Font> { |
395 | self.fonts.get_mut(id.0) |
396 | } |
397 | |
398 | pub fn find_font<F, T>(&mut self, font_ids: &[Option<FontId>; 8], mut callback: F) -> Result<T, ErrorKind> |
399 | where |
400 | F: FnMut((FontId, &mut Font)) -> (bool, T), |
401 | { |
402 | // Try each font in the paint |
403 | for maybe_font_id in font_ids { |
404 | if let &Some(font_id) = maybe_font_id { |
405 | if let Some(font) = self.fonts.get_mut(font_id.0) { |
406 | let (has_missing, result) = callback((font_id, font)); |
407 | |
408 | if !has_missing { |
409 | return Ok(result); |
410 | } |
411 | } |
412 | } else { |
413 | break; |
414 | } |
415 | } |
416 | |
417 | // Try each registered font |
418 | // An optimisation here would be to skip fonts that were tried by the paint |
419 | for (id, font) in &mut self.fonts { |
420 | let (has_missing, result) = callback((FontId(id), font)); |
421 | |
422 | if !has_missing { |
423 | return Ok(result); |
424 | } |
425 | } |
426 | |
427 | // Just return the first font at this point and let it render .nodef glyphs |
428 | if let Some((id, font)) = self.fonts.iter_mut().next() { |
429 | return Ok(callback((FontId(id), font)).1); |
430 | } |
431 | |
432 | Err(ErrorKind::NoFontFound) |
433 | } |
434 | |
435 | fn clear_caches(&mut self) { |
436 | self.shaped_words_cache.clear(); |
437 | } |
438 | |
439 | pub fn measure_text<S: AsRef<str>>( |
440 | &mut self, |
441 | x: f32, |
442 | y: f32, |
443 | text: S, |
444 | text_settings: &TextSettings, |
445 | ) -> Result<TextMetrics, ErrorKind> { |
446 | shape(x, y, self, text_settings, text.as_ref(), None) |
447 | } |
448 | |
449 | pub fn break_text<S: AsRef<str>>( |
450 | &mut self, |
451 | max_width: f32, |
452 | text: S, |
453 | text_settings: &TextSettings, |
454 | ) -> Result<usize, ErrorKind> { |
455 | let layout = shape(0.0, 0.0, self, text_settings, text.as_ref(), Some(max_width))?; |
456 | |
457 | Ok(layout.final_byte_index) |
458 | } |
459 | |
460 | pub fn break_text_vec<S: AsRef<str>>( |
461 | &mut self, |
462 | max_width: f32, |
463 | text: S, |
464 | text_settings: &TextSettings, |
465 | ) -> Result<Vec<Range<usize>>, ErrorKind> { |
466 | let text = text.as_ref(); |
467 | |
468 | let mut res = Vec::new(); |
469 | let mut start = 0; |
470 | |
471 | while start < text.len() { |
472 | if let Ok(index) = self.break_text(max_width, &text[start..], text_settings) { |
473 | if index == 0 { |
474 | break; |
475 | } |
476 | |
477 | let index = start + index; |
478 | res.push(start..index); |
479 | start += &text[start..index].len(); |
480 | } else { |
481 | break; |
482 | } |
483 | } |
484 | |
485 | Ok(res) |
486 | } |
487 | |
488 | pub fn measure_font(&mut self, font_size: f32, font_ids: &[Option<FontId>; 8]) -> Result<FontMetrics, ErrorKind> { |
489 | if let Some(Some(id)) = font_ids.first() { |
490 | if let Some(font) = self.font(*id) { |
491 | return Ok(font.metrics(font_size)); |
492 | } |
493 | } |
494 | |
495 | Err(ErrorKind::NoFontFound) |
496 | } |
497 | } |
498 | |
499 | /// Result of a shaping run. |
500 | #[derive (Clone, Default, Debug)] |
501 | pub struct TextMetrics { |
502 | pub x: f32, |
503 | pub y: f32, |
504 | width: f32, |
505 | height: f32, |
506 | pub glyphs: Vec<ShapedGlyph>, |
507 | pub(crate) final_byte_index: usize, |
508 | } |
509 | |
510 | impl TextMetrics { |
511 | pub(crate) fn scale(&mut self, scale: f32) { |
512 | self.x *= scale; |
513 | self.y *= scale; |
514 | self.width *= scale; |
515 | self.height *= scale; |
516 | |
517 | for glyph in &mut self.glyphs { |
518 | glyph.x *= scale; |
519 | glyph.y *= scale; |
520 | glyph.width *= scale; |
521 | glyph.height *= scale; |
522 | } |
523 | } |
524 | |
525 | /// width of the glyphs as drawn |
526 | pub fn width(&self) -> f32 { |
527 | self.width |
528 | } |
529 | |
530 | /// height of the glyphs as drawn |
531 | pub fn height(&self) -> f32 { |
532 | self.height |
533 | } |
534 | |
535 | pub(crate) fn has_bitmap_glyphs(&self) -> bool { |
536 | self.glyphs.iter().any(|g| g.bitmap_glyph) |
537 | } |
538 | } |
539 | |
540 | // Shaper |
541 | |
542 | pub(crate) fn shape( |
543 | x: f32, |
544 | y: f32, |
545 | context: &mut TextContextImpl, |
546 | text_settings: &TextSettings, |
547 | text: &str, |
548 | max_width: Option<f32>, |
549 | ) -> Result<TextMetrics, ErrorKind> { |
550 | let id: ShapingId = ShapingId::new(text_settings.font_size, text_settings.font_ids, word:text, max_width); |
551 | |
552 | if !context.shaping_run_cache.contains(&id) { |
553 | let metrics: TextMetrics = shape_run( |
554 | context, |
555 | text_settings.font_size, |
556 | text_settings.font_ids, |
557 | text_settings.letter_spacing, |
558 | text, |
559 | max_width, |
560 | )?; |
561 | context.shaping_run_cache.put(k:id, v:metrics); |
562 | } |
563 | |
564 | if let Some(mut metrics: TextMetrics) = context.shaping_run_cache.get(&id).cloned() { |
565 | layout(x, y, context, &mut metrics, text_settings)?; |
566 | |
567 | return Ok(metrics); |
568 | } |
569 | |
570 | Err(ErrorKind::UnknownError) |
571 | } |
572 | |
573 | fn shape_run( |
574 | context: &mut TextContextImpl, |
575 | font_size: f32, |
576 | font_ids: [Option<FontId>; 8], |
577 | letter_spacing: f32, |
578 | text: &str, |
579 | max_width: Option<f32>, |
580 | ) -> Result<TextMetrics, ErrorKind> { |
581 | let mut result = TextMetrics { |
582 | x: 0.0, |
583 | y: 0.0, |
584 | width: 0.0, |
585 | height: 0.0, |
586 | glyphs: Vec::with_capacity(text.len()), |
587 | final_byte_index: 0, |
588 | }; |
589 | |
590 | let bidi_info = BidiInfo::new(text, Some(unicode_bidi::Level::ltr())); |
591 | |
592 | // this controls whether we should break within words |
593 | let mut first_word_in_paragraph = true; |
594 | |
595 | if let Some(paragraph) = bidi_info.paragraphs.first() { |
596 | let line = paragraph.range.clone(); |
597 | |
598 | let (levels, runs) = bidi_info.visual_runs(paragraph, line); |
599 | |
600 | for run in runs.iter() { |
601 | let sub_text = &text[run.clone()]; |
602 | |
603 | if sub_text.is_empty() { |
604 | continue; |
605 | } |
606 | |
607 | let hb_direction = if levels[run.start].is_rtl() { |
608 | rustybuzz::Direction::RightToLeft |
609 | } else { |
610 | rustybuzz::Direction::LeftToRight |
611 | }; |
612 | |
613 | let mut words = Vec::new(); |
614 | let mut word_break_reached = false; |
615 | let mut byte_index = run.start; |
616 | |
617 | for mut word_txt in sub_text.split_word_bounds() { |
618 | let id = ShapingId::new(font_size, font_ids, word_txt, max_width); |
619 | |
620 | if !context.shaped_words_cache.contains(&id) { |
621 | let word = shape_word(word_txt, hb_direction, context, font_size, &font_ids, letter_spacing); |
622 | context.shaped_words_cache.put(id, word); |
623 | } |
624 | |
625 | if let Some(Ok(word)) = context.shaped_words_cache.get(&id) { |
626 | let mut word = word.clone(); |
627 | |
628 | if let Some(max_width) = max_width { |
629 | if result.width + word.width >= max_width { |
630 | word_break_reached = true; |
631 | if first_word_in_paragraph { |
632 | // search for the largest prefix of the word that can fit |
633 | let mut bytes_included = 0; |
634 | let mut subword_width = 0.0; |
635 | let target_width = max_width - result.width; |
636 | for glyph in word.glyphs.iter() { |
637 | bytes_included = glyph.byte_index; |
638 | let glyph_width = glyph.advance_x + letter_spacing; |
639 | |
640 | // nuance: we want to include the first glyph even if it breaks |
641 | // the bounds. this is to allow pathologically small bounds to |
642 | // at least complete rendering |
643 | if subword_width + glyph_width >= target_width && bytes_included != 0 { |
644 | break; |
645 | } else { |
646 | subword_width += glyph_width; |
647 | } |
648 | } |
649 | |
650 | if bytes_included == 0 { |
651 | // just in case - never mind! |
652 | break; |
653 | } |
654 | |
655 | let subword_txt = &word_txt[..bytes_included]; |
656 | let id = ShapingId::new(font_size, font_ids, subword_txt, Some(max_width)); |
657 | if !context.shaped_words_cache.contains(&id) { |
658 | let subword = shape_word( |
659 | subword_txt, |
660 | hb_direction, |
661 | context, |
662 | font_size, |
663 | &font_ids, |
664 | letter_spacing, |
665 | ); |
666 | context.shaped_words_cache.put(id, subword); |
667 | } |
668 | |
669 | if let Some(Ok(subword)) = context.shaped_words_cache.get(&id) { |
670 | // replace the outer variables so we can continue normally |
671 | word = subword.clone(); |
672 | word_txt = subword_txt; |
673 | } else { |
674 | break; |
675 | } |
676 | } else if word.glyphs.iter().all(|g| g.c.is_whitespace()) { |
677 | // the last word we've broken in the middle of is whitespace. |
678 | // include this word for now, but we will discard its metrics in a moment. |
679 | } else { |
680 | // we are not breaking up words - discard this word |
681 | break; |
682 | } |
683 | } |
684 | } |
685 | |
686 | // if we have broken in the middle of whitespace, do not include this word in metrics |
687 | if !word_break_reached || !word.glyphs.iter().all(|g| g.c.is_whitespace()) { |
688 | result.width += word.width; |
689 | } |
690 | |
691 | for glyph in &mut word.glyphs { |
692 | glyph.byte_index += byte_index; |
693 | debug_assert!(text.get(glyph.byte_index..).is_some()); |
694 | } |
695 | words.push(word); |
696 | first_word_in_paragraph = false; |
697 | } |
698 | |
699 | byte_index += word_txt.len(); |
700 | |
701 | if word_break_reached { |
702 | break; |
703 | } |
704 | } |
705 | |
706 | if levels[run.start].is_rtl() { |
707 | words.reverse(); |
708 | } |
709 | |
710 | for word in words { |
711 | result.glyphs.extend(word.glyphs.clone()); |
712 | } |
713 | |
714 | result.final_byte_index = byte_index; |
715 | |
716 | if word_break_reached { |
717 | break; |
718 | } |
719 | } |
720 | } |
721 | |
722 | Ok(result) |
723 | } |
724 | |
725 | fn shape_word( |
726 | word: &str, |
727 | hb_direction: rustybuzz::Direction, |
728 | context: &mut TextContextImpl, |
729 | font_size: f32, |
730 | font_ids: &[Option<FontId>; 8], |
731 | letter_spacing: f32, |
732 | ) -> Result<ShapedWord, ErrorKind> { |
733 | // find_font will call the closure with each font matching the provided style |
734 | // until a font capable of shaping the word is found |
735 | context.find_font(font_ids, |(font_id, font)| { |
736 | let face = font.face_ref(); |
737 | // Call harfbuzz |
738 | let output = { |
739 | let mut buffer = rustybuzz::UnicodeBuffer::new(); |
740 | buffer.push_str(word); |
741 | buffer.set_direction(hb_direction); |
742 | |
743 | rustybuzz::shape(&face, &[], buffer) |
744 | }; |
745 | |
746 | let positions = output.glyph_positions(); |
747 | let infos = output.glyph_infos(); |
748 | |
749 | let mut shaped_word = ShapedWord { |
750 | glyphs: Vec::with_capacity(positions.len()), |
751 | width: 0.0, |
752 | }; |
753 | |
754 | let mut has_missing = false; |
755 | |
756 | for (position, (info, c)) in positions.iter().zip(infos.iter().zip(word.chars())) { |
757 | if info.glyph_id == 0 { |
758 | has_missing = true; |
759 | } |
760 | |
761 | let scale = font.scale(font_size); |
762 | |
763 | let mut g = ShapedGlyph { |
764 | x: 0.0, |
765 | y: 0.0, |
766 | c, |
767 | byte_index: info.cluster as usize, |
768 | font_id, |
769 | codepoint: info.glyph_id, |
770 | width: 0.0, |
771 | height: 0.0, |
772 | advance_x: position.x_advance as f32 * scale, |
773 | advance_y: position.y_advance as f32 * scale, |
774 | offset_x: position.x_offset as f32 * scale, |
775 | offset_y: position.y_offset as f32 * scale, |
776 | bearing_x: 0.0, |
777 | bearing_y: 0.0, |
778 | bitmap_glyph: false, |
779 | }; |
780 | |
781 | if let Some(glyph) = font.glyph(&face, info.glyph_id as u16) { |
782 | g.width = glyph.metrics.width * scale; |
783 | g.height = glyph.metrics.height * scale; |
784 | g.bearing_x = glyph.metrics.bearing_x * scale; |
785 | g.bearing_y = glyph.metrics.bearing_y * scale; |
786 | g.bitmap_glyph = glyph.path.is_none(); |
787 | } |
788 | |
789 | shaped_word.width += g.advance_x + letter_spacing; |
790 | shaped_word.glyphs.push(g); |
791 | } |
792 | |
793 | (has_missing, shaped_word) |
794 | }) |
795 | } |
796 | |
797 | // Calculates the x,y coordinates for each glyph based on their advances. Calculates total width and height of the shaped text run |
798 | fn layout( |
799 | x: f32, |
800 | y: f32, |
801 | context: &mut TextContextImpl, |
802 | res: &mut TextMetrics, |
803 | text_settings: &TextSettings, |
804 | ) -> Result<(), ErrorKind> { |
805 | let mut cursor_x = x; |
806 | let mut cursor_y = y; |
807 | |
808 | // Horizontal alignment |
809 | match text_settings.text_align { |
810 | Align::Center => cursor_x -= res.width / 2.0, |
811 | Align::Right => cursor_x -= res.width, |
812 | _ => (), |
813 | } |
814 | |
815 | res.x = cursor_x; |
816 | |
817 | let mut min_y = cursor_y; |
818 | let mut max_y = cursor_y; |
819 | |
820 | let mut ascender: f32 = 0.; |
821 | let mut descender: f32 = 0.; |
822 | |
823 | for glyph in &mut res.glyphs { |
824 | let font = context.font_mut(glyph.font_id).ok_or(ErrorKind::NoFontFound)?; |
825 | let metrics = font.metrics(text_settings.font_size); |
826 | ascender = ascender.max(metrics.ascender()); |
827 | descender = descender.min(metrics.descender()); |
828 | } |
829 | |
830 | let primary_metrics = context.find_font(&text_settings.font_ids, |(_, font)| { |
831 | (false, font.metrics(text_settings.font_size)) |
832 | })?; |
833 | if ascender.abs() < std::f32::EPSILON { |
834 | ascender = primary_metrics.ascender(); |
835 | } |
836 | if descender.abs() < std::f32::EPSILON { |
837 | descender = primary_metrics.descender(); |
838 | } |
839 | |
840 | // Baseline alignment |
841 | let alignment_offset_y = match text_settings.text_baseline { |
842 | Baseline::Top => ascender, |
843 | Baseline::Middle => (ascender + descender) / 2.0, |
844 | Baseline::Alphabetic => 0.0, |
845 | Baseline::Bottom => descender, |
846 | }; |
847 | |
848 | for glyph in &mut res.glyphs { |
849 | glyph.x = cursor_x + glyph.offset_x + glyph.bearing_x; |
850 | glyph.y = (cursor_y + alignment_offset_y).round() + glyph.offset_y - glyph.bearing_y; |
851 | |
852 | min_y = min_y.min(glyph.y); |
853 | max_y = max_y.max(glyph.y + glyph.height); |
854 | |
855 | cursor_x += glyph.advance_x + text_settings.letter_spacing; |
856 | cursor_y += glyph.advance_y; |
857 | } |
858 | |
859 | res.y = min_y; |
860 | res.height = max_y - min_y; |
861 | |
862 | Ok(()) |
863 | } |
864 | |
865 | // Renderer |
866 | |
867 | #[derive (Clone, Debug)] |
868 | pub struct DrawCommand { |
869 | pub image_id: ImageId, |
870 | pub quads: Vec<Quad>, |
871 | } |
872 | |
873 | #[derive (Copy, Clone, Default, Debug)] |
874 | pub struct Quad { |
875 | pub x0: f32, |
876 | pub y0: f32, |
877 | pub s0: f32, |
878 | pub t0: f32, |
879 | pub x1: f32, |
880 | pub y1: f32, |
881 | pub s1: f32, |
882 | pub t1: f32, |
883 | } |
884 | |
885 | pub struct GlyphDrawCommands { |
886 | pub alpha_glyphs: Vec<DrawCommand>, |
887 | pub color_glyphs: Vec<DrawCommand>, |
888 | } |
889 | |
890 | #[derive (Default)] |
891 | pub(crate) struct GlyphAtlas { |
892 | pub rendered_glyphs: RefCell<FnvHashMap<RenderedGlyphId, RenderedGlyph>>, |
893 | pub glyph_textures: RefCell<Vec<FontTexture>>, |
894 | } |
895 | |
896 | impl GlyphAtlas { |
897 | pub(crate) fn render_atlas<T: Renderer>( |
898 | &self, |
899 | canvas: &mut Canvas<T>, |
900 | text_layout: &TextMetrics, |
901 | font_size: f32, |
902 | line_width: f32, |
903 | mode: RenderMode, |
904 | ) -> Result<GlyphDrawCommands, ErrorKind> { |
905 | let mut alpha_cmd_map = FnvHashMap::default(); |
906 | let mut color_cmd_map = FnvHashMap::default(); |
907 | |
908 | let line_width_offset = if mode == RenderMode::Stroke { |
909 | (line_width / 2.0).ceil() |
910 | } else { |
911 | 0.0 |
912 | }; |
913 | |
914 | let initial_render_target = canvas.current_render_target; |
915 | |
916 | for glyph in &text_layout.glyphs { |
917 | let subpixel_location = crate::geometry::quantize(glyph.x.fract(), 0.1) * 10.0; |
918 | |
919 | let id = RenderedGlyphId::new( |
920 | glyph.codepoint, |
921 | glyph.font_id, |
922 | font_size, |
923 | line_width, |
924 | mode, |
925 | subpixel_location as u8, |
926 | ); |
927 | |
928 | if !self.rendered_glyphs.borrow().contains_key(&id) { |
929 | let glyph = self.render_glyph(canvas, font_size, line_width, mode, glyph)?; |
930 | |
931 | self.rendered_glyphs.borrow_mut().insert(id, glyph); |
932 | } |
933 | |
934 | let rendered_glyphs = self.rendered_glyphs.borrow(); |
935 | let rendered = rendered_glyphs.get(&id).unwrap(); |
936 | |
937 | if let Some(texture) = self.glyph_textures.borrow().get(rendered.texture_index) { |
938 | let image_id = texture.image_id; |
939 | let size = texture.atlas.size(); |
940 | let itw = 1.0 / size.0 as f32; |
941 | let ith = 1.0 / size.1 as f32; |
942 | |
943 | let cmd_map = if rendered.color_glyph { |
944 | &mut color_cmd_map |
945 | } else { |
946 | &mut alpha_cmd_map |
947 | }; |
948 | |
949 | let cmd = cmd_map.entry(rendered.texture_index).or_insert_with(|| DrawCommand { |
950 | image_id, |
951 | quads: Vec::new(), |
952 | }); |
953 | |
954 | let mut q = Quad::default(); |
955 | |
956 | let line_width_offset = if rendered.color_glyph { 0. } else { line_width_offset }; |
957 | |
958 | q.x0 = glyph.x.trunc() - line_width_offset - GLYPH_PADDING as f32; |
959 | q.y0 = (glyph.y + glyph.bearing_y).round() |
960 | - rendered.bearing_y as f32 |
961 | - line_width_offset |
962 | - GLYPH_PADDING as f32; |
963 | q.x1 = q.x0 + rendered.width as f32; |
964 | q.y1 = q.y0 + rendered.height as f32; |
965 | |
966 | q.s0 = rendered.atlas_x as f32 * itw; |
967 | q.t0 = rendered.atlas_y as f32 * ith; |
968 | q.s1 = (rendered.atlas_x + rendered.width) as f32 * itw; |
969 | q.t1 = (rendered.atlas_y + rendered.height) as f32 * ith; |
970 | |
971 | cmd.quads.push(q); |
972 | } |
973 | } |
974 | |
975 | canvas.set_render_target(initial_render_target); |
976 | |
977 | Ok(GlyphDrawCommands { |
978 | alpha_glyphs: alpha_cmd_map.drain().map(|(_, cmd)| cmd).collect(), |
979 | color_glyphs: color_cmd_map.drain().map(|(_, cmd)| cmd).collect(), |
980 | }) |
981 | } |
982 | |
983 | fn render_glyph<T: Renderer>( |
984 | &self, |
985 | canvas: &mut Canvas<T>, |
986 | font_size: f32, |
987 | line_width: f32, |
988 | mode: RenderMode, |
989 | glyph: &ShapedGlyph, |
990 | ) -> Result<RenderedGlyph, ErrorKind> { |
991 | let padding = GLYPH_PADDING + GLYPH_MARGIN; |
992 | |
993 | let text_context = canvas.text_context.clone(); |
994 | let mut text_context = text_context.borrow_mut(); |
995 | |
996 | let (mut maybe_glyph_representation, scale) = { |
997 | let font = text_context.font_mut(glyph.font_id).ok_or(ErrorKind::NoFontFound)?; |
998 | let face = font.face_ref(); |
999 | let scale = font.scale(font_size); |
1000 | |
1001 | let maybe_glyph_representation = |
1002 | font.glyph_rendering_representation(&face, glyph.codepoint as u16, font_size as u16); |
1003 | (maybe_glyph_representation, scale) |
1004 | }; |
1005 | |
1006 | #[cfg (feature = "image-loading" )] |
1007 | let color_glyph = matches!(maybe_glyph_representation, Some(GlyphRendering::RenderAsImage(..))); |
1008 | #[cfg (not(feature = "image-loading" ))] |
1009 | let color_glyph = false; |
1010 | |
1011 | let line_width = if color_glyph || mode != RenderMode::Stroke { |
1012 | 0.0 |
1013 | } else { |
1014 | line_width |
1015 | }; |
1016 | |
1017 | let line_width_offset = (line_width / 2.0).ceil(); |
1018 | |
1019 | let width = glyph.width.ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2; |
1020 | let height = glyph.height.ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2; |
1021 | |
1022 | let (dst_index, dst_image_id, (dst_x, dst_y)) = |
1023 | self.find_texture_or_alloc(canvas, width as usize, height as usize)?; |
1024 | |
1025 | // render glyph to image |
1026 | canvas.save(); |
1027 | canvas.reset(); |
1028 | |
1029 | let rendered_bearing_y = glyph.bearing_y.round(); |
1030 | let x_quant = crate::geometry::quantize(glyph.x.fract(), 0.1); |
1031 | let x = dst_x as f32 - glyph.bearing_x + line_width_offset + padding as f32 + x_quant; |
1032 | let y = TEXTURE_SIZE as f32 - dst_y as f32 - rendered_bearing_y - line_width_offset - padding as f32; |
1033 | |
1034 | let rendered_glyph = RenderedGlyph { |
1035 | width: width - 2 * GLYPH_MARGIN, |
1036 | height: height - 2 * GLYPH_MARGIN, |
1037 | bearing_y: rendered_bearing_y as i32, |
1038 | atlas_x: dst_x as u32 + GLYPH_MARGIN, |
1039 | atlas_y: dst_y as u32 + GLYPH_MARGIN, |
1040 | texture_index: dst_index, |
1041 | color_glyph, |
1042 | }; |
1043 | |
1044 | match maybe_glyph_representation.as_mut() { |
1045 | Some(GlyphRendering::RenderAsPath(ref mut path)) => { |
1046 | canvas.translate(x, y); |
1047 | |
1048 | canvas.set_render_target(RenderTarget::Image(dst_image_id)); |
1049 | canvas.clear_rect( |
1050 | dst_x as u32, |
1051 | TEXTURE_SIZE as u32 - dst_y as u32 - height, |
1052 | width, |
1053 | height, |
1054 | Color::black(), |
1055 | ); |
1056 | let factor = 1.0 / 8.0; |
1057 | |
1058 | let mask_color = Color::rgbf(factor, factor, factor); |
1059 | |
1060 | let mut line_width = line_width; |
1061 | |
1062 | if mode == RenderMode::Stroke { |
1063 | line_width /= scale; |
1064 | } |
1065 | |
1066 | canvas.global_composite_blend_func(crate::BlendFactor::SrcAlpha, crate::BlendFactor::One); |
1067 | |
1068 | // 4x |
1069 | // let points = [ |
1070 | // (-3.0/8.0, 1.0/8.0), |
1071 | // (1.0/8.0, 3.0/8.0), |
1072 | // (3.0/8.0, -1.0/8.0), |
1073 | // (-1.0/8.0, -3.0/8.0), |
1074 | // ]; |
1075 | |
1076 | // 8x |
1077 | let points = [ |
1078 | (-7.0 / 16.0, -1.0 / 16.0), |
1079 | (-1.0 / 16.0, -5.0 / 16.0), |
1080 | (3.0 / 16.0, -7.0 / 16.0), |
1081 | (5.0 / 16.0, -3.0 / 16.0), |
1082 | (7.0 / 16.0, 1.0 / 16.0), |
1083 | (1.0 / 16.0, 5.0 / 16.0), |
1084 | (-3.0 / 16.0, 7.0 / 16.0), |
1085 | (-5.0 / 16.0, 3.0 / 16.0), |
1086 | ]; |
1087 | |
1088 | for point in &points { |
1089 | canvas.save(); |
1090 | canvas.translate(point.0, point.1); |
1091 | |
1092 | canvas.scale(scale, scale); |
1093 | |
1094 | if mode == RenderMode::Stroke { |
1095 | canvas.stroke_path_internal( |
1096 | path, |
1097 | &PaintFlavor::Color(mask_color), |
1098 | false, |
1099 | &StrokeSettings { |
1100 | line_width, |
1101 | ..Default::default() |
1102 | }, |
1103 | ); |
1104 | } else { |
1105 | canvas.fill_path_internal(path, &PaintFlavor::Color(mask_color), false, FillRule::EvenOdd); |
1106 | } |
1107 | |
1108 | canvas.restore(); |
1109 | } |
1110 | } |
1111 | #[cfg (feature = "image-loading" )] |
1112 | Some(GlyphRendering::RenderAsImage(image_buffer)) => { |
1113 | let target_x = rendered_glyph.atlas_x as usize; |
1114 | let target_y = rendered_glyph.atlas_y as usize; |
1115 | let target_width = rendered_glyph.width; |
1116 | let target_height = rendered_glyph.height; |
1117 | |
1118 | let image_buffer = |
1119 | image_buffer.resize(target_width, target_height, image::imageops::FilterType::Nearest); |
1120 | if let Ok(image) = crate::image::ImageSource::try_from(&image_buffer) { |
1121 | canvas.update_image(dst_image_id, image, target_x, target_y).unwrap(); |
1122 | } |
1123 | } |
1124 | _ => {} |
1125 | } |
1126 | |
1127 | canvas.restore(); |
1128 | |
1129 | Ok(rendered_glyph) |
1130 | } |
1131 | |
1132 | // Returns (texture index, image id, glyph padding box) |
1133 | fn find_texture_or_alloc<T: Renderer>( |
1134 | &self, |
1135 | canvas: &mut Canvas<T>, |
1136 | width: usize, |
1137 | height: usize, |
1138 | ) -> Result<(usize, ImageId, (usize, usize)), ErrorKind> { |
1139 | // Find a free location in one of the atlases |
1140 | let mut texture_search_result = { |
1141 | let mut glyph_textures = self.glyph_textures.borrow_mut(); |
1142 | let mut textures = glyph_textures.iter_mut().enumerate(); |
1143 | textures.find_map(|(index, texture)| { |
1144 | texture |
1145 | .atlas |
1146 | .add_rect(width, height) |
1147 | .map(|loc| (index, texture.image_id, loc)) |
1148 | }) |
1149 | }; |
1150 | |
1151 | if texture_search_result.is_none() { |
1152 | // All atlases are exausted and a new one must be created |
1153 | let mut atlas = Atlas::new(TEXTURE_SIZE, TEXTURE_SIZE); |
1154 | |
1155 | let loc = atlas |
1156 | .add_rect(width, height) |
1157 | .ok_or(ErrorKind::FontSizeTooLargeForAtlas)?; |
1158 | |
1159 | // Using PixelFormat::Gray8 works perfectly and takes less VRAM. |
1160 | // We keep Rgba8 for now because it might be useful for sub-pixel |
1161 | // anti-aliasing (ClearType®), and the atlas debug display is much |
1162 | // clearer with different colors. Also, Rgba8 is required for color |
1163 | // fonts (typically used for emojis). |
1164 | let info = ImageInfo::new(ImageFlags::empty(), atlas.size().0, atlas.size().1, PixelFormat::Rgba8); |
1165 | let image_id = canvas.images.alloc(&mut canvas.renderer, info)?; |
1166 | |
1167 | #[cfg (feature = "debug_inspector" )] |
1168 | if cfg!(debug_assertions) { |
1169 | // Fill the texture with red pixels only in debug builds. |
1170 | if let Ok(size) = canvas.image_size(image_id) { |
1171 | // With image-loading we then subsequently support color fonts, where |
1172 | // the color glyphs are uploaded directly. Since that's immediately and |
1173 | // the clear_rect() is run much later, it would overwrite any uploaded |
1174 | // glyphs. So then when for the debug-inspector, use an image to clear. |
1175 | #[cfg (feature = "image-loading" )] |
1176 | { |
1177 | use rgb::FromSlice; |
1178 | let clear_image = image::RgbaImage::from_pixel( |
1179 | size.0 as u32, |
1180 | size.1 as u32, |
1181 | image::Rgba::<u8>([255, 0, 0, 0]), |
1182 | ); |
1183 | canvas |
1184 | .update_image( |
1185 | image_id, |
1186 | crate::image::ImageSource::from(imgref::Img::new( |
1187 | clear_image.as_rgba(), |
1188 | clear_image.width() as usize, |
1189 | clear_image.height() as usize, |
1190 | )), |
1191 | 0, |
1192 | 0, |
1193 | ) |
1194 | .unwrap(); |
1195 | } |
1196 | #[cfg (not(feature = "image-loading" ))] |
1197 | { |
1198 | canvas.save(); |
1199 | canvas.reset(); |
1200 | canvas.set_render_target(RenderTarget::Image(image_id)); |
1201 | canvas.clear_rect( |
1202 | 0, |
1203 | 0, |
1204 | size.0 as u32, |
1205 | size.1 as u32, |
1206 | Color::rgb(255, 0, 0), // Shown as white if using Gray8., |
1207 | ); |
1208 | canvas.restore(); |
1209 | } |
1210 | } |
1211 | } |
1212 | |
1213 | self.glyph_textures.borrow_mut().push(FontTexture { atlas, image_id }); |
1214 | |
1215 | let index = self.glyph_textures.borrow().len() - 1; |
1216 | texture_search_result = Some((index, image_id, loc)); |
1217 | } |
1218 | |
1219 | texture_search_result.ok_or(ErrorKind::UnknownError) |
1220 | } |
1221 | |
1222 | pub(crate) fn clear<T: Renderer>(&self, canvas: &mut Canvas<T>) { |
1223 | let image_ids = std::mem::take(&mut *self.glyph_textures.borrow_mut()) |
1224 | .into_iter() |
1225 | .map(|font_texture| font_texture.image_id); |
1226 | image_ids.for_each(|id| canvas.delete_image(id)); |
1227 | |
1228 | self.rendered_glyphs.borrow_mut().clear(); |
1229 | } |
1230 | } |
1231 | |
1232 | pub(crate) fn render_direct<T: Renderer>( |
1233 | canvas: &mut Canvas<T>, |
1234 | text_layout: &TextMetrics, |
1235 | paint_flavor: &PaintFlavor, |
1236 | anti_alias: bool, |
1237 | stroke: &StrokeSettings, |
1238 | font_size: f32, |
1239 | mode: RenderMode, |
1240 | invscale: f32, |
1241 | ) -> Result<(), ErrorKind> { |
1242 | let text_context = canvas.text_context.clone(); |
1243 | let text_context = text_context.borrow_mut(); |
1244 | |
1245 | let mut scaled = false; |
1246 | let mut face_cache: HashMap<FontId, rustybuzz::Face> = HashMap::default(); |
1247 | |
1248 | for glyph in &text_layout.glyphs { |
1249 | let (glyph_rendering, scale) = { |
1250 | let font = text_context.font(glyph.font_id).ok_or(ErrorKind::NoFontFound)?; |
1251 | let face = face_cache.entry(glyph.font_id).or_insert_with(|| font.face_ref()); |
1252 | |
1253 | let scale = font.scale(font_size); |
1254 | |
1255 | let glyph_rendering = if let Some(glyph_rendering) = |
1256 | font.glyph_rendering_representation(face, glyph.codepoint as u16, font_size as u16) |
1257 | { |
1258 | glyph_rendering |
1259 | } else { |
1260 | continue; |
1261 | }; |
1262 | |
1263 | (glyph_rendering, scale) |
1264 | }; |
1265 | |
1266 | canvas.save(); |
1267 | |
1268 | let mut line_width = stroke.line_width; |
1269 | |
1270 | if mode == RenderMode::Stroke && !scaled { |
1271 | line_width /= scale; |
1272 | scaled = true; |
1273 | } |
1274 | |
1275 | canvas.translate( |
1276 | (glyph.x - glyph.bearing_x) * invscale, |
1277 | (glyph.y + glyph.bearing_y) * invscale, |
1278 | ); |
1279 | canvas.scale(scale * invscale, -scale * invscale); |
1280 | |
1281 | match glyph_rendering { |
1282 | GlyphRendering::RenderAsPath(path) => { |
1283 | if mode == RenderMode::Stroke { |
1284 | canvas.stroke_path_internal( |
1285 | path.borrow(), |
1286 | paint_flavor, |
1287 | anti_alias, |
1288 | &StrokeSettings { |
1289 | line_width, |
1290 | ..stroke.clone() |
1291 | }, |
1292 | ); |
1293 | } else { |
1294 | canvas.fill_path_internal(path.borrow(), paint_flavor, anti_alias, FillRule::EvenOdd); |
1295 | } |
1296 | } |
1297 | #[cfg (feature = "image-loading" )] |
1298 | GlyphRendering::RenderAsImage(_) => unreachable!(), |
1299 | } |
1300 | |
1301 | canvas.restore(); |
1302 | } |
1303 | |
1304 | Ok(()) |
1305 | } |
1306 | |