1use std::{
2 borrow::Borrow,
3 cell::RefCell,
4 convert::TryInto,
5 ffi::OsStr,
6 fs,
7 hash::{Hash, Hasher},
8 ops::Range,
9 path::Path as FilePath,
10 rc::Rc,
11};
12
13use fnv::{FnvBuildHasher, FnvHashMap, FnvHasher};
14use lru::LruCache;
15use rustybuzz::ttf_parser;
16use slotmap::{DefaultKey, SlotMap};
17
18use unicode_bidi::BidiInfo;
19use unicode_segmentation::UnicodeSegmentation;
20
21use crate::{
22 paint::{PaintFlavor, StrokeSettings, TextSettings},
23 Canvas, Color, ErrorKind, FillRule, ImageFlags, ImageId, ImageInfo, Paint, PixelFormat, PositionedGlyph,
24 RenderTarget, Renderer,
25};
26
27mod atlas;
28pub use atlas::Atlas;
29
30mod font;
31pub use font::FontMetrics;
32use font::{Font, GlyphRendering};
33
34const GLYPH_PADDING: u32 = 1;
37const GLYPH_MARGIN: u32 = 1;
42
43const TEXTURE_SIZE: usize = 512;
44const DEFAULT_LRU_CACHE_CAPACITY: usize = 1000;
45
46#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
48pub struct FontId(DefaultKey);
49
50#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
54#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
55pub enum Baseline {
56 Top,
58 Middle,
60 #[default]
62 Alphabetic,
63 Bottom,
65}
66
67#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
71#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
72pub enum Align {
73 #[default]
75 Left,
76 Center,
78 Right,
80}
81
82#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
86pub enum RenderMode {
87 #[default]
89 Fill,
90 Stroke,
92}
93
94#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
95pub struct RenderedGlyphId {
96 glyph_index: u16,
97 font_id: FontId,
98 size: u32,
99 line_width: u32,
100 render_mode: RenderMode,
101 subpixel_location: u8,
102}
103
104impl RenderedGlyphId {
105 fn new(
106 glyph_index: u16,
107 font_id: FontId,
108 font_size: f32,
109 line_width: f32,
110 mode: RenderMode,
111 subpixel_location: u8,
112 ) -> Self {
113 Self {
114 glyph_index,
115 font_id,
116 size: (font_size * 10.0).trunc() as u32,
117 line_width: (line_width * 10.0).trunc() as u32,
118 render_mode: mode,
119 subpixel_location,
120 }
121 }
122}
123
124#[derive(Copy, Clone, Debug)]
125pub struct RenderedGlyph {
126 texture_index: usize,
127 width: u32,
128 height: u32,
129 bearing_y: i32,
130 atlas_x: u32,
131 atlas_y: u32,
132 color_glyph: bool,
133}
134
135#[derive(Copy, Clone, Debug)]
136pub struct ShapedGlyph {
137 pub x: f32,
138 pub y: f32,
139 pub c: char,
140 pub byte_index: usize,
141 pub font_id: FontId,
142 pub glyph_id: u16,
143 pub width: f32,
144 pub height: f32,
145 pub advance_x: f32,
146 pub advance_y: f32,
147 pub offset_x: f32,
148 pub offset_y: f32,
149 pub bearing_x: f32,
150 pub bearing_y: f32,
151}
152
153#[derive(Clone, Debug, Default)]
154struct ShapedWord {
155 glyphs: Vec<ShapedGlyph>,
156 width: f32,
157}
158
159#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
160struct ShapingId {
161 size: u32,
162 word_hash: u64,
163 font_ids: [Option<FontId>; 8],
164}
165
166impl ShapingId {
167 fn new(font_size: f32, font_ids: [Option<FontId>; 8], word: &str, max_width: Option<f32>) -> Self {
168 let mut hasher = FnvHasher::default();
169 word.hash(&mut hasher);
170 if let Some(max_width) = max_width {
171 (max_width.trunc() as i32).hash(&mut hasher);
172 }
173
174 Self {
175 size: (font_size * 10.0).trunc() as u32,
176 word_hash: hasher.finish(),
177 font_ids,
178 }
179 }
180}
181
182type ShapedWordsCache<H> = LruCache<ShapingId, Result<ShapedWord, ErrorKind>, H>;
183type ShapingRunCache<H> = LruCache<ShapingId, TextMetrics, H>;
184
185pub struct FontTexture {
186 pub atlas: Atlas,
187 pub(crate) image_id: ImageId,
188}
189
190#[derive(Clone, Default)]
207pub struct TextContext(pub(crate) Rc<RefCell<TextContextImpl>>);
208
209impl TextContext {
210 pub fn add_font_dir<T: AsRef<FilePath>>(&self, path: T) -> Result<Vec<FontId>, ErrorKind> {
213 self.0.borrow_mut().add_font_dir(path)
214 }
215
216 pub fn add_font_file<T: AsRef<FilePath>>(&self, path: T) -> Result<FontId, ErrorKind> {
219 self.0.borrow_mut().add_font_file(path)
220 }
221
222 pub fn add_font_mem(&self, data: &[u8]) -> Result<FontId, ErrorKind> {
225 self.0.borrow_mut().add_font_mem(data)
226 }
227
228 pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>(
233 &self,
234 data: T,
235 face_index: u32,
236 ) -> Result<FontId, ErrorKind> {
237 self.0.borrow_mut().add_shared_font_with_index(data, face_index)
238 }
239
240 pub fn measure_text<S: AsRef<str>>(
242 &self,
243 x: f32,
244 y: f32,
245 text: S,
246 paint: &Paint,
247 ) -> Result<TextMetrics, ErrorKind> {
248 self.0.borrow_mut().measure_text(x, y, text, &paint.text)
249 }
250
251 pub fn break_text<S: AsRef<str>>(&self, max_width: f32, text: S, paint: &Paint) -> Result<usize, ErrorKind> {
255 self.0.borrow_mut().break_text(max_width, text, &paint.text)
256 }
257
258 pub fn break_text_vec<S: AsRef<str>>(
260 &self,
261 max_width: f32,
262 text: S,
263 paint: &Paint,
264 ) -> Result<Vec<Range<usize>>, ErrorKind> {
265 self.0.borrow_mut().break_text_vec(max_width, text, &paint.text)
266 }
267
268 pub fn measure_font(&self, paint: &Paint) -> Result<FontMetrics, ErrorKind> {
270 self.0
271 .borrow_mut()
272 .measure_font(paint.text.font_size, &paint.text.font_ids)
273 }
274
275 pub fn resize_shaping_run_cache(&self, capacity: std::num::NonZeroUsize) {
278 self.0.borrow_mut().resize_shaping_run_cache(capacity)
279 }
280
281 pub fn resize_shaped_words_cache(&self, capacity: std::num::NonZeroUsize) {
285 self.0.borrow_mut().resize_shaped_words_cache(capacity)
286 }
287}
288
289pub struct TextContextImpl {
290 fonts: SlotMap<DefaultKey, Font>,
291 shaping_run_cache: ShapingRunCache<FnvBuildHasher>,
292 shaped_words_cache: ShapedWordsCache<FnvBuildHasher>,
293}
294
295impl Default for TextContextImpl {
296 fn default() -> Self {
297 let fnv_run = FnvBuildHasher::default();
298 let fnv_words = FnvBuildHasher::default();
299
300 Self {
301 fonts: SlotMap::default(),
302 shaping_run_cache: LruCache::with_hasher(
303 std::num::NonZeroUsize::new(DEFAULT_LRU_CACHE_CAPACITY).unwrap(),
304 fnv_run,
305 ),
306 shaped_words_cache: LruCache::with_hasher(
307 std::num::NonZeroUsize::new(DEFAULT_LRU_CACHE_CAPACITY).unwrap(),
308 fnv_words,
309 ),
310 }
311 }
312}
313
314impl TextContextImpl {
315 pub fn resize_shaping_run_cache(&mut self, capacity: std::num::NonZeroUsize) {
316 self.shaping_run_cache.resize(capacity);
317 }
318
319 pub fn resize_shaped_words_cache(&mut self, capacity: std::num::NonZeroUsize) {
320 self.shaped_words_cache.resize(capacity);
321 }
322
323 pub fn add_font_dir<T: AsRef<FilePath>>(&mut self, path: T) -> Result<Vec<FontId>, ErrorKind> {
324 let path = path.as_ref();
325 let mut fonts = Vec::new();
326
327 if path.is_dir() {
328 for entry in fs::read_dir(path)? {
329 let entry = entry?;
330 let path = entry.path();
331
332 if path.is_dir() {
333 self.add_font_dir(&path)?;
334 } else if Some("ttf") == path.extension().and_then(OsStr::to_str) {
335 fonts.push(self.add_font_file(path)?);
336 } else if Some("ttc") == path.extension().and_then(OsStr::to_str) {
337 fonts.extend(self.add_font_file_collection(path)?);
338 }
339 }
340 }
341
342 Ok(fonts)
343 }
344
345 pub fn add_font_file<T: AsRef<FilePath>>(&mut self, path: T) -> Result<FontId, ErrorKind> {
346 let data = std::fs::read(path)?;
347
348 self.add_font_mem(&data)
349 }
350
351 pub fn add_font_file_collection<T: AsRef<FilePath>>(
352 &mut self,
353 path: T,
354 ) -> Result<impl Iterator<Item = FontId> + '_, ErrorKind> {
355 let data = std::fs::read(path)?;
356
357 let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1);
358 Ok((0..count).filter_map(move |index| self.add_font_mem_with_index(&data, index).ok()))
359 }
360
361 pub fn add_font_mem(&mut self, data: &[u8]) -> Result<FontId, ErrorKind> {
362 self.add_font_mem_with_index(data, 0)
363 }
364
365 pub fn add_font_mem_with_index(&mut self, data: &[u8], face_index: u32) -> Result<FontId, ErrorKind> {
366 self.clear_caches();
367
368 let data_copy = data.to_owned();
369 let font = Font::new_with_data(data_copy, face_index)?;
370 Ok(FontId(self.fonts.insert(font)))
371 }
372
373 pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>(
374 &mut self,
375 data: T,
376 face_index: u32,
377 ) -> Result<FontId, ErrorKind> {
378 self.clear_caches();
379
380 let font = Font::new_with_data(data, face_index)?;
381 Ok(FontId(self.fonts.insert(font)))
382 }
383
384 pub fn font(&self, id: FontId) -> Option<&Font> {
385 self.fonts.get(id.0)
386 }
387
388 pub fn font_mut(&mut self, id: FontId) -> Option<&mut Font> {
389 self.fonts.get_mut(id.0)
390 }
391
392 pub fn find_font<F, T>(&mut self, font_ids: &[Option<FontId>; 8], mut callback: F) -> Result<T, ErrorKind>
393 where
394 F: FnMut((FontId, &mut Font)) -> (bool, T),
395 {
396 for maybe_font_id in font_ids {
398 if let &Some(font_id) = maybe_font_id {
399 if let Some(font) = self.fonts.get_mut(font_id.0) {
400 let (has_missing, result) = callback((font_id, font));
401
402 if !has_missing {
403 return Ok(result);
404 }
405 }
406 } else {
407 break;
408 }
409 }
410
411 for (id, font) in &mut self.fonts {
414 let (has_missing, result) = callback((FontId(id), font));
415
416 if !has_missing {
417 return Ok(result);
418 }
419 }
420
421 if let Some((id, font)) = self.fonts.iter_mut().next() {
423 return Ok(callback((FontId(id), font)).1);
424 }
425
426 Err(ErrorKind::NoFontFound)
427 }
428
429 fn clear_caches(&mut self) {
430 self.shaped_words_cache.clear();
431 }
432
433 pub fn measure_text<S: AsRef<str>>(
434 &mut self,
435 x: f32,
436 y: f32,
437 text: S,
438 text_settings: &TextSettings,
439 ) -> Result<TextMetrics, ErrorKind> {
440 shape(x, y, self, text_settings, text.as_ref(), None)
441 }
442
443 pub fn break_text<S: AsRef<str>>(
444 &mut self,
445 max_width: f32,
446 text: S,
447 text_settings: &TextSettings,
448 ) -> Result<usize, ErrorKind> {
449 let layout = shape(0.0, 0.0, self, text_settings, text.as_ref(), Some(max_width))?;
450
451 Ok(layout.final_byte_index)
452 }
453
454 pub fn break_text_vec<S: AsRef<str>>(
455 &mut self,
456 max_width: f32,
457 text: S,
458 text_settings: &TextSettings,
459 ) -> Result<Vec<Range<usize>>, ErrorKind> {
460 let text = text.as_ref();
461
462 let mut res = Vec::new();
463 let mut start = 0;
464
465 while start < text.len() {
466 let Ok(index) = self.break_text(max_width, &text[start..], text_settings) else {
467 break;
468 };
469
470 if index == 0 {
471 break;
472 }
473
474 let index = start + index;
475 res.push(start..index);
476 start += &text[start..index].len();
477 }
478
479 Ok(res)
480 }
481
482 pub fn measure_font(&self, font_size: f32, font_ids: &[Option<FontId>; 8]) -> Result<FontMetrics, ErrorKind> {
483 if let Some(Some(id)) = font_ids.first() {
484 if let Some(font) = self.font(*id) {
485 return Ok(font.metrics(font_size));
486 }
487 }
488
489 Err(ErrorKind::NoFontFound)
490 }
491}
492
493#[derive(Clone, Default, Debug)]
495pub struct TextMetrics {
496 pub x: f32,
498 pub y: f32,
500 width: f32,
501 height: f32,
502 pub glyphs: Vec<ShapedGlyph>,
504 pub(crate) final_byte_index: usize,
505}
506
507impl TextMetrics {
508 pub(crate) fn scale(&mut self, scale: f32) {
509 self.x *= scale;
510 self.y *= scale;
511 self.width *= scale;
512 self.height *= scale;
513
514 for glyph in &mut self.glyphs {
515 glyph.x *= scale;
516 glyph.y *= scale;
517 glyph.width *= scale;
518 glyph.height *= scale;
519 }
520 }
521
522 pub fn width(&self) -> f32 {
524 self.width
525 }
526
527 pub fn height(&self) -> f32 {
529 self.height
530 }
531}
532
533pub fn shape(
536 x: f32,
537 y: f32,
538 context: &mut TextContextImpl,
539 text_settings: &TextSettings,
540 text: &str,
541 max_width: Option<f32>,
542) -> Result<TextMetrics, ErrorKind> {
543 let id = ShapingId::new(text_settings.font_size, text_settings.font_ids, text, max_width);
544
545 if !context.shaping_run_cache.contains(&id) {
546 let metrics = shape_run(
547 context,
548 text_settings.font_size,
549 text_settings.font_ids,
550 text_settings.letter_spacing,
551 text,
552 max_width,
553 );
554 context.shaping_run_cache.put(id, metrics);
555 }
556
557 if let Some(mut metrics) = context.shaping_run_cache.get(&id).cloned() {
558 layout(x, y, context, &mut metrics, text_settings)?;
559
560 return Ok(metrics);
561 }
562
563 Err(ErrorKind::UnknownError)
564}
565
566fn shape_run(
567 context: &mut TextContextImpl,
568 font_size: f32,
569 font_ids: [Option<FontId>; 8],
570 letter_spacing: f32,
571 text: &str,
572 max_width: Option<f32>,
573) -> TextMetrics {
574 let mut result = TextMetrics {
575 x: 0.0,
576 y: 0.0,
577 width: 0.0,
578 height: 0.0,
579 glyphs: Vec::with_capacity(text.len()),
580 final_byte_index: 0,
581 };
582
583 let bidi_info = BidiInfo::new(text, Some(unicode_bidi::Level::ltr()));
584
585 let mut first_word_in_paragraph = true;
587
588 let Some(paragraph) = bidi_info.paragraphs.first() else {
589 return result;
590 };
591
592 let line = paragraph.range.clone();
593
594 let (levels, runs) = bidi_info.visual_runs(paragraph, line);
595
596 for run in runs {
597 let sub_text = &text[run.clone()];
598
599 if sub_text.is_empty() {
600 continue;
601 }
602
603 let hb_direction = if levels[run.start].is_rtl() {
604 rustybuzz::Direction::RightToLeft
605 } else {
606 rustybuzz::Direction::LeftToRight
607 };
608
609 let mut words = Vec::new();
610 let mut word_break_reached = false;
611 let mut byte_index = run.start;
612
613 for mut word_txt in sub_text.split_word_bounds() {
614 let id = ShapingId::new(font_size, font_ids, word_txt, max_width);
615
616 if !context.shaped_words_cache.contains(&id) {
617 let word = shape_word(word_txt, hb_direction, context, font_size, &font_ids, letter_spacing);
618 context.shaped_words_cache.put(id, word);
619 }
620
621 if let Some(Ok(word)) = context.shaped_words_cache.get(&id) {
622 let mut word = word.clone();
623
624 if let Some(max_width) = max_width {
625 if result.width + word.width >= max_width {
626 word_break_reached = true;
627 if first_word_in_paragraph {
628 let mut bytes_included = 0;
630 let mut subword_width = 0.0;
631 let target_width = max_width - result.width;
632 for glyph in word.glyphs {
633 bytes_included = glyph.byte_index;
634 let glyph_width = glyph.advance_x + letter_spacing;
635
636 if subword_width + glyph_width >= target_width && bytes_included != 0 {
640 break;
641 }
642
643 subword_width += glyph_width;
644 }
645
646 if bytes_included == 0 {
647 break;
649 }
650
651 let subword_txt = &word_txt[..bytes_included];
652 let id = ShapingId::new(font_size, font_ids, subword_txt, Some(max_width));
653 if !context.shaped_words_cache.contains(&id) {
654 let subword = shape_word(
655 subword_txt,
656 hb_direction,
657 context,
658 font_size,
659 &font_ids,
660 letter_spacing,
661 );
662 context.shaped_words_cache.put(id, subword);
663 }
664
665 if let Some(Ok(subword)) = context.shaped_words_cache.get(&id) {
666 word = subword.clone();
668 word_txt = subword_txt;
669 } else {
670 break;
671 }
672 } else if word.glyphs.iter().all(|g| g.c.is_whitespace()) {
673 } else {
676 break;
678 }
679 }
680 }
681
682 if !word_break_reached || !word.glyphs.iter().all(|g| g.c.is_whitespace()) {
684 result.width += word.width;
685 }
686
687 for glyph in &mut word.glyphs {
688 glyph.byte_index += byte_index;
689 debug_assert!(text.get(glyph.byte_index..).is_some());
690 }
691 words.push(word);
692 first_word_in_paragraph = false;
693 }
694
695 byte_index += word_txt.len();
696
697 if word_break_reached {
698 break;
699 }
700 }
701
702 if levels[run.start].is_rtl() {
703 words.reverse();
704 }
705
706 for word in words {
707 result.glyphs.extend(word.glyphs.clone());
708 }
709
710 result.final_byte_index = byte_index;
711
712 if word_break_reached {
713 break;
714 }
715 }
716
717 result
718}
719
720fn shape_word(
721 word: &str,
722 hb_direction: rustybuzz::Direction,
723 context: &mut TextContextImpl,
724 font_size: f32,
725 font_ids: &[Option<FontId>; 8],
726 letter_spacing: f32,
727) -> Result<ShapedWord, ErrorKind> {
728 context.find_font(font_ids, |(font_id, font)| {
731 let face = font.face_ref();
732 let output = {
734 let mut buffer = rustybuzz::UnicodeBuffer::new();
735 buffer.push_str(word);
736 buffer.set_direction(hb_direction);
737
738 rustybuzz::shape(&face, &[], buffer)
739 };
740
741 let positions = output.glyph_positions();
742 let infos = output.glyph_infos();
743
744 let mut shaped_word = ShapedWord {
745 glyphs: Vec::with_capacity(positions.len()),
746 width: 0.0,
747 };
748
749 let mut has_missing = false;
750
751 for (position, (info, c)) in positions.iter().zip(infos.iter().zip(word.chars())) {
752 if info.glyph_id == 0 {
753 has_missing = true;
754 }
755
756 let scale = font.scale(font_size);
757
758 let mut g = ShapedGlyph {
759 x: 0.0,
760 y: 0.0,
761 c,
762 byte_index: info.cluster as usize,
763 font_id,
764 glyph_id: info
765 .glyph_id
766 .try_into()
767 .expect("rustybuzz guarantees the output glyph id is u16"),
768 width: 0.0,
769 height: 0.0,
770 advance_x: position.x_advance as f32 * scale,
771 advance_y: position.y_advance as f32 * scale,
772 offset_x: position.x_offset as f32 * scale,
773 offset_y: position.y_offset as f32 * scale,
774 bearing_x: 0.0,
775 bearing_y: 0.0,
776 };
777
778 if let Some(glyph) = font.glyph(&face, g.glyph_id) {
779 g.width = glyph.metrics.width * scale;
780 g.height = glyph.metrics.height * scale;
781 g.bearing_x = glyph.metrics.bearing_x * scale;
782 g.bearing_y = glyph.metrics.bearing_y * scale;
783 }
784
785 shaped_word.width += g.advance_x + letter_spacing;
786 shaped_word.glyphs.push(g);
787 }
788
789 (has_missing, shaped_word)
790 })
791}
792
793fn layout(
795 x: f32,
796 y: f32,
797 context: &mut TextContextImpl,
798 res: &mut TextMetrics,
799 text_settings: &TextSettings,
800) -> Result<(), ErrorKind> {
801 let mut cursor_x = x;
802 let mut cursor_y = y;
803
804 match text_settings.text_align {
806 Align::Center => cursor_x -= res.width / 2.0,
807 Align::Right => cursor_x -= res.width,
808 Align::Left => (),
809 }
810
811 res.x = cursor_x;
812
813 let mut min_y = cursor_y;
814 let mut max_y = cursor_y;
815
816 let mut ascender: f32 = 0.;
817 let mut descender: f32 = 0.;
818
819 for glyph in &mut res.glyphs {
820 let font = context.font_mut(glyph.font_id).ok_or(ErrorKind::NoFontFound)?;
821 let metrics = font.metrics(text_settings.font_size);
822 ascender = ascender.max(metrics.ascender());
823 descender = descender.min(metrics.descender());
824 }
825
826 let primary_metrics = context.find_font(&text_settings.font_ids, |(_, font)| {
827 (false, font.metrics(text_settings.font_size))
828 })?;
829 if ascender.abs() < f32::EPSILON {
830 ascender = primary_metrics.ascender();
831 }
832 if descender.abs() < f32::EPSILON {
833 descender = primary_metrics.descender();
834 }
835
836 let alignment_offset_y = match text_settings.text_baseline {
838 Baseline::Top => ascender,
839 Baseline::Middle => (ascender + descender) / 2.0,
840 Baseline::Alphabetic => 0.0,
841 Baseline::Bottom => descender,
842 };
843
844 for glyph in &mut res.glyphs {
845 glyph.x = cursor_x + glyph.offset_x + glyph.bearing_x;
846 glyph.y = (cursor_y + alignment_offset_y).round() + glyph.offset_y - glyph.bearing_y;
847
848 min_y = min_y.min(glyph.y);
849 max_y = max_y.max(glyph.y + glyph.height);
850
851 cursor_x += glyph.advance_x + text_settings.letter_spacing;
852 cursor_y += glyph.advance_y;
853 }
854
855 res.y = min_y;
856 res.height = max_y - min_y;
857
858 Ok(())
859}
860
861#[derive(Clone, Debug)]
865pub struct DrawCommand {
866 pub image_id: ImageId,
868 pub quads: Vec<Quad>,
870}
871
872#[derive(Copy, Clone, Default, Debug)]
874pub struct Quad {
875 pub x0: f32,
877 pub y0: f32,
879 pub s0: f32,
881 pub t0: f32,
883 pub x1: f32,
885 pub y1: f32,
887 pub s1: f32,
889 pub t1: f32,
891}
892
893#[derive(Default)]
895pub struct GlyphDrawCommands {
896 pub alpha_glyphs: Vec<DrawCommand>,
898 pub color_glyphs: Vec<DrawCommand>,
900}
901
902#[derive(Default)]
903pub struct GlyphAtlas {
904 pub rendered_glyphs: RefCell<FnvHashMap<RenderedGlyphId, RenderedGlyph>>,
905 pub glyph_textures: RefCell<Vec<FontTexture>>,
906}
907
908impl GlyphAtlas {
909 pub(crate) fn render_atlas<T: Renderer>(
910 &self,
911 canvas: &mut Canvas<T>,
912 font_id: FontId,
913 font: &Font,
914 font_face: &rustybuzz::Face<'_>,
915 glyphs: impl Iterator<Item = PositionedGlyph>,
916 font_size: f32,
917 line_width: f32,
918 mode: RenderMode,
919 ) -> Result<GlyphDrawCommands, ErrorKind> {
920 let mut alpha_cmd_map = FnvHashMap::default();
921 let mut color_cmd_map = FnvHashMap::default();
922
923 let line_width_offset = if mode == RenderMode::Stroke {
924 (line_width / 2.0).ceil()
925 } else {
926 0.0
927 };
928
929 let initial_render_target = canvas.current_render_target;
930
931 for glyph in glyphs {
932 let subpixel_location = crate::geometry::quantize(glyph.x.fract(), 0.1) * 10.0;
933
934 let id = RenderedGlyphId::new(
935 glyph.glyph_id,
936 font_id,
937 font_size,
938 line_width,
939 mode,
940 subpixel_location as u8,
941 );
942
943 if !self.rendered_glyphs.borrow().contains_key(&id) {
944 if let Some(glyph) =
945 self.render_glyph(canvas, font_size, line_width, mode, font, &font_face, glyph.glyph_id)?
946 {
947 self.rendered_glyphs.borrow_mut().insert(id, glyph);
948 } else {
949 continue;
950 }
951 }
952
953 let rendered_glyphs = self.rendered_glyphs.borrow();
954 let rendered = rendered_glyphs.get(&id).unwrap();
955
956 if let Some(texture) = self.glyph_textures.borrow().get(rendered.texture_index) {
957 let image_id = texture.image_id;
958 let size = texture.atlas.size();
959 let itw = 1.0 / size.0 as f32;
960 let ith = 1.0 / size.1 as f32;
961
962 let cmd_map = if rendered.color_glyph {
963 &mut color_cmd_map
964 } else {
965 &mut alpha_cmd_map
966 };
967
968 let cmd = cmd_map.entry(rendered.texture_index).or_insert_with(|| DrawCommand {
969 image_id,
970 quads: Vec::new(),
971 });
972
973 let mut q = Quad::default();
974
975 let line_width_offset = if rendered.color_glyph { 0. } else { line_width_offset };
976
977 q.x0 = glyph.x.trunc() - line_width_offset - GLYPH_PADDING as f32;
978 q.y0 = glyph.y.round() - rendered.bearing_y as f32 - line_width_offset - GLYPH_PADDING as f32;
979 q.x1 = q.x0 + rendered.width as f32;
980 q.y1 = q.y0 + rendered.height as f32;
981
982 q.s0 = rendered.atlas_x as f32 * itw;
983 q.t0 = rendered.atlas_y as f32 * ith;
984 q.s1 = (rendered.atlas_x + rendered.width) as f32 * itw;
985 q.t1 = (rendered.atlas_y + rendered.height) as f32 * ith;
986
987 cmd.quads.push(q);
988 }
989 }
990
991 canvas.set_render_target(initial_render_target);
992
993 Ok(GlyphDrawCommands {
994 alpha_glyphs: alpha_cmd_map.drain().map(|(_, cmd)| cmd).collect(),
995 color_glyphs: color_cmd_map.drain().map(|(_, cmd)| cmd).collect(),
996 })
997 }
998
999 fn render_glyph<T: Renderer>(
1002 &self,
1003 canvas: &mut Canvas<T>,
1004 font_size: f32,
1005 line_width: f32,
1006 mode: RenderMode,
1007 font: &Font,
1008 font_face: &rustybuzz::Face<'_>,
1009 glyph_id: u16,
1010 ) -> Result<Option<RenderedGlyph>, ErrorKind> {
1011 let padding = GLYPH_PADDING + GLYPH_MARGIN;
1012
1013 let (mut glyph_representation, glyph_metrics, scale) = {
1014 let scale = font.scale(font_size);
1015 let maybe_glyph_metrics = font.glyph(&font_face, glyph_id).map(|g| g.metrics.clone());
1016
1017 if let (Some(glyph_representation), Some(glyph_metrics)) = (
1018 font.glyph_rendering_representation(&font_face, glyph_id, font_size as u16),
1019 maybe_glyph_metrics,
1020 ) {
1021 (glyph_representation, glyph_metrics, scale)
1022 } else {
1023 return Ok(None);
1024 }
1025 };
1026
1027 #[cfg(feature = "image-loading")]
1028 let color_glyph = matches!(glyph_representation, GlyphRendering::RenderAsImage(..));
1029 #[cfg(not(feature = "image-loading"))]
1030 let color_glyph = false;
1031
1032 let line_width = if color_glyph || mode != RenderMode::Stroke {
1033 0.0
1034 } else {
1035 line_width
1036 };
1037
1038 let line_width_offset = (line_width / 2.0).ceil();
1039
1040 let width = (glyph_metrics.width * scale).ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2;
1041 let height = (glyph_metrics.height * scale).ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2;
1042
1043 let (dst_index, dst_image_id, (dst_x, dst_y)) =
1044 self.find_texture_or_alloc(canvas, width as usize, height as usize)?;
1045
1046 canvas.save();
1048 canvas.reset();
1049
1050 let rendered_bearing_y = (glyph_metrics.bearing_y * scale).round();
1051 let x = dst_x as f32 - (glyph_metrics.bearing_x * scale) + line_width_offset + padding as f32;
1052 let y = TEXTURE_SIZE as f32 - dst_y as f32 - rendered_bearing_y - line_width_offset - padding as f32;
1053
1054 let rendered_glyph = RenderedGlyph {
1055 width: width - 2 * GLYPH_MARGIN,
1056 height: height - 2 * GLYPH_MARGIN,
1057 bearing_y: rendered_bearing_y as i32,
1058 atlas_x: dst_x as u32 + GLYPH_MARGIN,
1059 atlas_y: dst_y as u32 + GLYPH_MARGIN,
1060 texture_index: dst_index,
1061 color_glyph,
1062 };
1063
1064 match glyph_representation {
1065 GlyphRendering::RenderAsPath(ref mut path) => {
1066 canvas.translate(x, y);
1067
1068 canvas.set_render_target(RenderTarget::Image(dst_image_id));
1069 canvas.clear_rect(
1070 dst_x as u32,
1071 TEXTURE_SIZE as u32 - dst_y as u32 - height,
1072 width,
1073 height,
1074 Color::black(),
1075 );
1076 let factor = 1.0 / 8.0;
1077
1078 let mask_color = Color::rgbf(factor, factor, factor);
1079
1080 let mut line_width = line_width;
1081
1082 if mode == RenderMode::Stroke {
1083 line_width /= scale;
1084 }
1085
1086 canvas.global_composite_blend_func(crate::BlendFactor::SrcAlpha, crate::BlendFactor::One);
1087
1088 let points = [
1098 (-7.0 / 16.0, -1.0 / 16.0),
1099 (-1.0 / 16.0, -5.0 / 16.0),
1100 (3.0 / 16.0, -7.0 / 16.0),
1101 (5.0 / 16.0, -3.0 / 16.0),
1102 (7.0 / 16.0, 1.0 / 16.0),
1103 (1.0 / 16.0, 5.0 / 16.0),
1104 (-3.0 / 16.0, 7.0 / 16.0),
1105 (-5.0 / 16.0, 3.0 / 16.0),
1106 ];
1107
1108 for point in &points {
1109 canvas.save();
1110 canvas.translate(point.0, point.1);
1111
1112 canvas.scale(scale, scale);
1113
1114 if mode == RenderMode::Stroke {
1115 canvas.stroke_path_internal(
1116 path,
1117 &PaintFlavor::Color(mask_color),
1118 false,
1119 &StrokeSettings {
1120 line_width,
1121 ..Default::default()
1122 },
1123 );
1124 } else {
1125 canvas.fill_path_internal(path, &PaintFlavor::Color(mask_color), false, FillRule::NonZero);
1126 }
1127
1128 canvas.restore();
1129 }
1130 }
1131 #[cfg(feature = "image-loading")]
1132 GlyphRendering::RenderAsImage(image_buffer) => {
1133 let target_x = rendered_glyph.atlas_x as usize;
1134 let target_y = rendered_glyph.atlas_y as usize;
1135 let target_width = rendered_glyph.width;
1136 let target_height = rendered_glyph.height;
1137
1138 let image_buffer =
1139 image_buffer.resize(target_width, target_height, image::imageops::FilterType::Nearest);
1140 if let Ok(image) = crate::image::ImageSource::try_from(&image_buffer) {
1141 canvas.update_image(dst_image_id, image, target_x, target_y).unwrap();
1142 }
1143 }
1144 }
1145
1146 canvas.restore();
1147
1148 Ok(Some(rendered_glyph))
1149 }
1150
1151 fn find_texture_or_alloc<T: Renderer>(
1153 &self,
1154 canvas: &mut Canvas<T>,
1155 width: usize,
1156 height: usize,
1157 ) -> Result<(usize, ImageId, (usize, usize)), ErrorKind> {
1158 let mut texture_search_result = {
1160 let mut glyph_textures = self.glyph_textures.borrow_mut();
1161 let mut textures = glyph_textures.iter_mut().enumerate();
1162 textures.find_map(|(index, texture)| {
1163 texture
1164 .atlas
1165 .add_rect(width, height)
1166 .map(|loc| (index, texture.image_id, loc))
1167 })
1168 };
1169
1170 if texture_search_result.is_none() {
1171 let mut atlas = Atlas::new(TEXTURE_SIZE, TEXTURE_SIZE);
1173
1174 let loc = atlas
1175 .add_rect(width, height)
1176 .ok_or(ErrorKind::FontSizeTooLargeForAtlas)?;
1177
1178 let info = ImageInfo::new(ImageFlags::NEAREST, atlas.size().0, atlas.size().1, PixelFormat::Rgba8);
1184 let image_id = canvas.images.alloc(&mut canvas.renderer, info)?;
1185
1186 #[cfg(feature = "debug_inspector")]
1187 if cfg!(debug_assertions) {
1188 if let Ok(size) = canvas.image_size(image_id) {
1190 #[cfg(feature = "image-loading")]
1195 {
1196 use rgb::FromSlice;
1197 let clear_image = image::RgbaImage::from_pixel(
1198 size.0 as u32,
1199 size.1 as u32,
1200 image::Rgba::<u8>([255, 0, 0, 0]),
1201 );
1202 canvas
1203 .update_image(
1204 image_id,
1205 crate::image::ImageSource::from(imgref::Img::new(
1206 clear_image.as_rgba(),
1207 clear_image.width() as usize,
1208 clear_image.height() as usize,
1209 )),
1210 0,
1211 0,
1212 )
1213 .unwrap();
1214 }
1215 #[cfg(not(feature = "image-loading"))]
1216 {
1217 canvas.save();
1218 canvas.reset();
1219 canvas.set_render_target(RenderTarget::Image(image_id));
1220 canvas.clear_rect(
1221 0,
1222 0,
1223 size.0 as u32,
1224 size.1 as u32,
1225 Color::rgb(255, 0, 0), );
1227 canvas.restore();
1228 }
1229 }
1230 }
1231
1232 self.glyph_textures.borrow_mut().push(FontTexture { atlas, image_id });
1233
1234 let index = self.glyph_textures.borrow().len() - 1;
1235 texture_search_result = Some((index, image_id, loc));
1236 }
1237
1238 texture_search_result.ok_or(ErrorKind::UnknownError)
1239 }
1240
1241 pub(crate) fn clear<T: Renderer>(&self, canvas: &mut Canvas<T>) {
1242 let image_ids = std::mem::take(&mut *self.glyph_textures.borrow_mut())
1243 .into_iter()
1244 .map(|font_texture| font_texture.image_id);
1245 image_ids.for_each(|id| canvas.delete_image(id));
1246
1247 self.rendered_glyphs.borrow_mut().clear();
1248 }
1249}
1250
1251pub fn render_direct<T: Renderer>(
1252 canvas: &mut Canvas<T>,
1253 font: &Font,
1254 glyphs: impl Iterator<Item = PositionedGlyph>,
1255 paint_flavor: &PaintFlavor,
1256 anti_alias: bool,
1257 stroke: &StrokeSettings,
1258 font_size: f32,
1259 mode: RenderMode,
1260) -> Result<(), ErrorKind> {
1261 let face = font.face_ref();
1262
1263 for glyph in glyphs {
1264 let (glyph_rendering, scale) = {
1265 let scale = font.scale(font_size);
1266
1267 let Some(glyph_rendering) = font.glyph_rendering_representation(&face, glyph.glyph_id, font_size as u16)
1268 else {
1269 continue;
1270 };
1271
1272 (glyph_rendering, scale)
1273 };
1274
1275 canvas.save();
1276
1277 let line_width = match mode {
1278 RenderMode::Fill => stroke.line_width,
1279 RenderMode::Stroke => stroke.line_width / scale,
1280 };
1281
1282 canvas.translate(glyph.x, glyph.y);
1283 canvas.scale(scale, -scale);
1284
1285 match glyph_rendering {
1286 GlyphRendering::RenderAsPath(path) => {
1287 if mode == RenderMode::Stroke {
1288 canvas.stroke_path_internal(
1289 path.borrow(),
1290 paint_flavor,
1291 anti_alias,
1292 &StrokeSettings {
1293 line_width,
1294 ..stroke.clone()
1295 },
1296 );
1297 } else {
1298 canvas.fill_path_internal(path.borrow(), paint_flavor, anti_alias, FillRule::NonZero);
1299 }
1300 }
1301 #[cfg(feature = "image-loading")]
1302 GlyphRendering::RenderAsImage(_) => unreachable!(),
1303 }
1304
1305 canvas.restore();
1306 }
1307
1308 Ok(())
1309}