1use std::{borrow::Borrow, cell::RefCell, ffi::OsStr, fs, hash::Hash, path::Path as FilePath, rc::Rc};
2
3use fnv::FnvHashMap;
4#[cfg(feature = "textlayout")]
5use rustybuzz::ttf_parser;
6use slotmap::{DefaultKey, SlotMap};
7
8use crate::{
9 paint::{PaintFlavor, StrokeSettings},
10 Canvas, Color, ErrorKind, FillRule, ImageFlags, ImageId, ImageInfo, Paint, PixelFormat, PositionedGlyph,
11 RenderTarget, Renderer,
12};
13
14mod atlas;
15pub use atlas::Atlas;
16
17mod font;
18pub use font::FontMetrics;
19use font::{Font, GlyphRendering};
20
21#[cfg(feature = "textlayout")]
22mod textlayout;
23#[cfg(feature = "textlayout")]
24pub use textlayout::*;
25
26const GLYPH_PADDING: u32 = 1;
29const GLYPH_MARGIN: u32 = 1;
34
35const TEXTURE_SIZE: usize = 512;
36#[cfg(feature = "textlayout")]
37const DEFAULT_LRU_CACHE_CAPACITY: usize = 1000;
38
39#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
41pub struct FontId(DefaultKey);
42
43#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
47#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
48pub enum Baseline {
49 Top,
51 Middle,
53 #[default]
55 Alphabetic,
56 Bottom,
58}
59
60#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
64#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
65pub enum Align {
66 #[default]
68 Left,
69 Center,
71 Right,
73}
74
75#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
79pub enum RenderMode {
80 #[default]
82 Fill,
83 Stroke,
85}
86
87#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
88pub struct RenderedGlyphId {
89 glyph_index: u16,
90 font_id: FontId,
91 size: u32,
92 line_width: u32,
93 render_mode: RenderMode,
94 subpixel_location: u8,
95}
96
97impl RenderedGlyphId {
98 fn new(
99 glyph_index: u16,
100 font_id: FontId,
101 font_size: f32,
102 line_width: f32,
103 mode: RenderMode,
104 subpixel_location: u8,
105 ) -> Self {
106 Self {
107 glyph_index,
108 font_id,
109 size: (font_size * 10.0).trunc() as u32,
110 line_width: (line_width * 10.0).trunc() as u32,
111 render_mode: mode,
112 subpixel_location,
113 }
114 }
115}
116
117#[derive(Copy, Clone, Debug)]
118pub struct RenderedGlyph {
119 texture_index: usize,
120 width: u32,
121 height: u32,
122 bearing_x: i32,
123 bearing_y: i32,
124 atlas_x: u32,
125 atlas_y: u32,
126 color_glyph: bool,
127}
128
129pub struct FontTexture {
130 pub atlas: Atlas,
131 pub(crate) image_id: ImageId,
132}
133
134#[derive(Clone, Default)]
151pub struct TextContext(pub(crate) Rc<RefCell<TextContextImpl>>);
152
153impl TextContext {
154 pub fn add_font_dir<T: AsRef<FilePath>>(&self, path: T) -> Result<Vec<FontId>, ErrorKind> {
157 self.0.borrow_mut().add_font_dir(path)
158 }
159
160 pub fn add_font_file<T: AsRef<FilePath>>(&self, path: T) -> Result<FontId, ErrorKind> {
163 self.0.borrow_mut().add_font_file(path)
164 }
165
166 pub fn add_font_mem(&self, data: &[u8]) -> Result<FontId, ErrorKind> {
169 self.0.borrow_mut().add_font_mem(data)
170 }
171
172 pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>(
177 &self,
178 data: T,
179 face_index: u32,
180 ) -> Result<FontId, ErrorKind> {
181 self.0.borrow_mut().add_shared_font_with_index(data, face_index)
182 }
183
184 pub fn measure_font(&self, paint: &Paint) -> Result<FontMetrics, ErrorKind> {
186 self.0
187 .borrow_mut()
188 .measure_font(paint.text.font_size, &paint.text.font_ids)
189 }
190}
191
192pub struct TextContextImpl {
193 fonts: SlotMap<DefaultKey, Font>,
194 #[cfg(feature = "textlayout")]
195 shaping_run_cache: textlayout::ShapingRunCache<fnv::FnvBuildHasher>,
196 #[cfg(feature = "textlayout")]
197 shaped_words_cache: textlayout::ShapedWordsCache<fnv::FnvBuildHasher>,
198}
199
200impl Default for TextContextImpl {
201 fn default() -> Self {
202 #[cfg(feature = "textlayout")]
203 let fnv_run = fnv::FnvBuildHasher::default();
204 #[cfg(feature = "textlayout")]
205 let fnv_words = fnv::FnvBuildHasher::default();
206
207 Self {
208 fonts: SlotMap::default(),
209 #[cfg(feature = "textlayout")]
210 shaping_run_cache: lru::LruCache::with_hasher(
211 std::num::NonZeroUsize::new(DEFAULT_LRU_CACHE_CAPACITY).unwrap(),
212 fnv_run,
213 ),
214 #[cfg(feature = "textlayout")]
215 shaped_words_cache: lru::LruCache::with_hasher(
216 std::num::NonZeroUsize::new(DEFAULT_LRU_CACHE_CAPACITY).unwrap(),
217 fnv_words,
218 ),
219 }
220 }
221}
222
223impl TextContextImpl {
224 pub fn add_font_dir<T: AsRef<FilePath>>(&mut self, path: T) -> Result<Vec<FontId>, ErrorKind> {
225 let path = path.as_ref();
226 let mut fonts = Vec::new();
227
228 if path.is_dir() {
229 for entry in fs::read_dir(path)? {
230 let entry = entry?;
231 let path = entry.path();
232
233 if path.is_dir() {
234 self.add_font_dir(&path)?;
235 } else if Some("ttf") == path.extension().and_then(OsStr::to_str) {
236 fonts.push(self.add_font_file(path)?);
237 } else if Some("ttc") == path.extension().and_then(OsStr::to_str) {
238 fonts.extend(self.add_font_file_collection(path)?);
239 }
240 }
241 }
242
243 Ok(fonts)
244 }
245
246 pub fn add_font_file<T: AsRef<FilePath>>(&mut self, path: T) -> Result<FontId, ErrorKind> {
247 let data = std::fs::read(path)?;
248
249 self.add_font_mem(&data)
250 }
251
252 pub fn add_font_file_collection<T: AsRef<FilePath>>(
253 &mut self,
254 path: T,
255 ) -> Result<impl Iterator<Item = FontId> + '_, ErrorKind> {
256 let data = std::fs::read(path)?;
257
258 let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1);
259 Ok((0..count).filter_map(move |index| self.add_font_mem_with_index(&data, index).ok()))
260 }
261
262 pub fn add_font_mem(&mut self, data: &[u8]) -> Result<FontId, ErrorKind> {
263 self.add_font_mem_with_index(data, 0)
264 }
265
266 pub fn add_font_mem_with_index(&mut self, data: &[u8], face_index: u32) -> Result<FontId, ErrorKind> {
267 self.clear_caches();
268
269 let data_copy = data.to_owned();
270 let font = Font::new_with_data(data_copy, face_index)?;
271 Ok(FontId(self.fonts.insert(font)))
272 }
273
274 pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>(
275 &mut self,
276 data: T,
277 face_index: u32,
278 ) -> Result<FontId, ErrorKind> {
279 self.clear_caches();
280
281 let font = Font::new_with_data(data, face_index)?;
282 Ok(FontId(self.fonts.insert(font)))
283 }
284
285 pub fn font(&self, id: FontId) -> Option<&Font> {
286 self.fonts.get(id.0)
287 }
288
289 pub fn font_mut(&mut self, id: FontId) -> Option<&mut Font> {
290 self.fonts.get_mut(id.0)
291 }
292
293 #[cfg(feature = "textlayout")]
294 pub fn find_font<F, T>(&mut self, font_ids: &[Option<FontId>; 8], mut callback: F) -> Result<T, ErrorKind>
295 where
296 F: FnMut((FontId, &mut Font)) -> (bool, T),
297 {
298 for maybe_font_id in font_ids {
300 if let &Some(font_id) = maybe_font_id {
301 if let Some(font) = self.fonts.get_mut(font_id.0) {
302 let (has_missing, result) = callback((font_id, font));
303
304 if !has_missing {
305 return Ok(result);
306 }
307 }
308 } else {
309 break;
310 }
311 }
312
313 for (id, font) in &mut self.fonts {
316 let (has_missing, result) = callback((FontId(id), font));
317
318 if !has_missing {
319 return Ok(result);
320 }
321 }
322
323 if let Some((id, font)) = self.fonts.iter_mut().next() {
325 return Ok(callback((FontId(id), font)).1);
326 }
327
328 Err(ErrorKind::NoFontFound)
329 }
330
331 fn clear_caches(&mut self) {
332 #[cfg(feature = "textlayout")]
333 self.shaped_words_cache.clear();
334 }
335
336 pub fn measure_font(&self, font_size: f32, font_ids: &[Option<FontId>; 8]) -> Result<FontMetrics, ErrorKind> {
337 if let Some(Some(id)) = font_ids.first() {
338 if let Some(font) = self.font(*id) {
339 return Ok(font.metrics(font_size));
340 }
341 }
342
343 Err(ErrorKind::NoFontFound)
344 }
345}
346
347#[derive(Clone, Debug)]
351pub struct DrawCommand {
352 pub image_id: ImageId,
354 pub quads: Vec<Quad>,
356}
357
358#[derive(Copy, Clone, Default, Debug)]
360pub struct Quad {
361 pub x0: f32,
363 pub y0: f32,
365 pub s0: f32,
367 pub t0: f32,
369 pub x1: f32,
371 pub y1: f32,
373 pub s1: f32,
375 pub t1: f32,
377}
378
379#[derive(Default)]
381pub struct GlyphDrawCommands {
382 pub alpha_glyphs: Vec<DrawCommand>,
384 pub color_glyphs: Vec<DrawCommand>,
386}
387
388#[derive(Default)]
389pub struct GlyphAtlas {
390 pub rendered_glyphs: RefCell<FnvHashMap<RenderedGlyphId, RenderedGlyph>>,
391 pub glyph_textures: RefCell<Vec<FontTexture>>,
392}
393
394impl GlyphAtlas {
395 pub(crate) fn render_atlas<T: Renderer>(
396 &self,
397 canvas: &mut Canvas<T>,
398 font_id: FontId,
399 font: &Font,
400 font_face: &ttf_parser::Face<'_>,
401 glyphs: impl Iterator<Item = PositionedGlyph>,
402 font_size: f32,
403 line_width: f32,
404 mode: RenderMode,
405 ) -> Result<GlyphDrawCommands, ErrorKind> {
406 let mut alpha_cmd_map = FnvHashMap::default();
407 let mut color_cmd_map = FnvHashMap::default();
408
409 let line_width_offset = if mode == RenderMode::Stroke {
410 (line_width / 2.0).ceil()
411 } else {
412 0.0
413 };
414
415 let initial_render_target = canvas.current_render_target;
416
417 for glyph in glyphs {
418 let subpixel_location = crate::geometry::quantize(glyph.x.fract(), 0.1) * 10.0;
419
420 let id = RenderedGlyphId::new(
421 glyph.glyph_id,
422 font_id,
423 font_size,
424 line_width,
425 mode,
426 subpixel_location as u8,
427 );
428
429 let mut rendered_glyphs = self.rendered_glyphs.borrow_mut();
430 let glyph_cache_entry = rendered_glyphs.entry(id);
431 let glyph_cache_entry = match glyph_cache_entry {
432 std::collections::hash_map::Entry::Occupied(occupied_entry) => occupied_entry,
433 std::collections::hash_map::Entry::Vacant(_) => {
434 if let Some(glyph) =
435 self.render_glyph(canvas, font_size, line_width, mode, font, &font_face, glyph.glyph_id)?
436 {
437 glyph_cache_entry.insert_entry(glyph)
438 } else {
439 continue;
440 }
441 }
442 };
443
444 let rendered = glyph_cache_entry.get();
445
446 if let Some(texture) = self.glyph_textures.borrow().get(rendered.texture_index) {
447 let image_id = texture.image_id;
448 let size = texture.atlas.size();
449 let itw = 1.0 / size.0 as f32;
450 let ith = 1.0 / size.1 as f32;
451
452 let cmd_map = if rendered.color_glyph {
453 &mut color_cmd_map
454 } else {
455 &mut alpha_cmd_map
456 };
457
458 let cmd = cmd_map.entry(rendered.texture_index).or_insert_with(|| DrawCommand {
459 image_id,
460 quads: Vec::new(),
461 });
462
463 let mut q = Quad::default();
464
465 let line_width_offset = if rendered.color_glyph { 0. } else { line_width_offset };
466
467 q.x0 = glyph.x.trunc() + rendered.bearing_x as f32 - line_width_offset - GLYPH_PADDING as f32;
468 q.y0 = glyph.y.round() - rendered.bearing_y as f32 - line_width_offset - GLYPH_PADDING as f32;
469 q.x1 = q.x0 + rendered.width as f32;
470 q.y1 = q.y0 + rendered.height as f32;
471
472 q.s0 = rendered.atlas_x as f32 * itw;
473 q.t0 = rendered.atlas_y as f32 * ith;
474 q.s1 = (rendered.atlas_x + rendered.width) as f32 * itw;
475 q.t1 = (rendered.atlas_y + rendered.height) as f32 * ith;
476
477 cmd.quads.push(q);
478 }
479 }
480
481 canvas.set_render_target(initial_render_target);
482
483 Ok(GlyphDrawCommands {
484 alpha_glyphs: alpha_cmd_map.drain().map(|(_, cmd)| cmd).collect(),
485 color_glyphs: color_cmd_map.drain().map(|(_, cmd)| cmd).collect(),
486 })
487 }
488
489 fn render_glyph<T: Renderer>(
492 &self,
493 canvas: &mut Canvas<T>,
494 font_size: f32,
495 line_width: f32,
496 mode: RenderMode,
497 font: &Font,
498 font_face: &ttf_parser::Face<'_>,
499 glyph_id: u16,
500 ) -> Result<Option<RenderedGlyph>, ErrorKind> {
501 let padding = GLYPH_PADDING + GLYPH_MARGIN;
502
503 let (mut glyph_representation, glyph_metrics, scale) = {
504 let scale = font.scale(font_size);
505 let maybe_glyph_metrics = font.glyph(&font_face, glyph_id).map(|g| g.metrics.clone());
506
507 if let (Some(glyph_representation), Some(glyph_metrics)) = (
508 font.glyph_rendering_representation(&font_face, glyph_id, font_size as u16),
509 maybe_glyph_metrics,
510 ) {
511 (glyph_representation, glyph_metrics, scale)
512 } else {
513 return Ok(None);
514 }
515 };
516
517 #[cfg(feature = "image-loading")]
518 let color_glyph = matches!(glyph_representation, GlyphRendering::RenderAsImage(..));
519 #[cfg(not(feature = "image-loading"))]
520 let color_glyph = false;
521
522 let line_width = if color_glyph || mode != RenderMode::Stroke {
523 0.0
524 } else {
525 line_width
526 };
527
528 let line_width_offset = (line_width / 2.0).ceil();
529
530 let width = (glyph_metrics.width * scale).ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2;
531 let height = (glyph_metrics.height * scale).ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2;
532
533 let (dst_index, dst_image_id, (dst_x, dst_y)) =
534 self.find_texture_or_alloc(canvas, width as usize, height as usize)?;
535
536 canvas.save();
538 canvas.reset();
539
540 let rendered_bearing_x = (glyph_metrics.bearing_x * scale).round();
541 let rendered_bearing_y = (glyph_metrics.bearing_y * scale).round();
542 let x = dst_x as f32 - rendered_bearing_x + line_width_offset + padding as f32;
543 let y = TEXTURE_SIZE as f32 - dst_y as f32 - rendered_bearing_y - line_width_offset - padding as f32;
544
545 let rendered_glyph = RenderedGlyph {
546 width: width - 2 * GLYPH_MARGIN,
547 height: height - 2 * GLYPH_MARGIN,
548 bearing_x: rendered_bearing_x as i32,
549 bearing_y: rendered_bearing_y as i32,
550 atlas_x: dst_x as u32 + GLYPH_MARGIN,
551 atlas_y: dst_y as u32 + GLYPH_MARGIN,
552 texture_index: dst_index,
553 color_glyph,
554 };
555
556 match glyph_representation {
557 GlyphRendering::RenderAsPath(ref mut path) => {
558 canvas.translate(x, y);
559
560 canvas.set_render_target(RenderTarget::Image(dst_image_id));
561 canvas.clear_rect(
562 dst_x as u32,
563 TEXTURE_SIZE as u32 - dst_y as u32 - height,
564 width,
565 height,
566 Color::black(),
567 );
568 let factor = 1.0 / 8.0;
569
570 let mask_color = Color::rgbf(factor, factor, factor);
571
572 let mut line_width = line_width;
573
574 if mode == RenderMode::Stroke {
575 line_width /= scale;
576 }
577
578 canvas.global_composite_blend_func(crate::BlendFactor::SrcAlpha, crate::BlendFactor::One);
579
580 let points = [
590 (-7.0 / 16.0, -1.0 / 16.0),
591 (-1.0 / 16.0, -5.0 / 16.0),
592 (3.0 / 16.0, -7.0 / 16.0),
593 (5.0 / 16.0, -3.0 / 16.0),
594 (7.0 / 16.0, 1.0 / 16.0),
595 (1.0 / 16.0, 5.0 / 16.0),
596 (-3.0 / 16.0, 7.0 / 16.0),
597 (-5.0 / 16.0, 3.0 / 16.0),
598 ];
599
600 for point in &points {
601 canvas.save();
602 canvas.translate(point.0, point.1);
603
604 canvas.scale(scale, scale);
605
606 if mode == RenderMode::Stroke {
607 canvas.stroke_path_internal(
608 path,
609 &PaintFlavor::Color(mask_color),
610 false,
611 &StrokeSettings {
612 line_width,
613 ..Default::default()
614 },
615 );
616 } else {
617 canvas.fill_path_internal(path, &PaintFlavor::Color(mask_color), false, FillRule::NonZero);
618 }
619
620 canvas.restore();
621 }
622 }
623 #[cfg(feature = "image-loading")]
624 GlyphRendering::RenderAsImage(image_buffer) => {
625 let target_x = rendered_glyph.atlas_x as usize;
626 let target_y = rendered_glyph.atlas_y as usize;
627 let target_width = rendered_glyph.width;
628 let target_height = rendered_glyph.height;
629
630 let image_buffer =
631 image_buffer.resize(target_width, target_height, image::imageops::FilterType::Nearest);
632 if let Ok(image) = crate::image::ImageSource::try_from(&image_buffer) {
633 canvas.update_image(dst_image_id, image, target_x, target_y).unwrap();
634 }
635 }
636 }
637
638 canvas.restore();
639
640 Ok(Some(rendered_glyph))
641 }
642
643 fn find_texture_or_alloc<T: Renderer>(
645 &self,
646 canvas: &mut Canvas<T>,
647 width: usize,
648 height: usize,
649 ) -> Result<(usize, ImageId, (usize, usize)), ErrorKind> {
650 let mut texture_search_result = {
652 let mut glyph_textures = self.glyph_textures.borrow_mut();
653 let mut textures = glyph_textures.iter_mut().enumerate();
654 textures.find_map(|(index, texture)| {
655 texture
656 .atlas
657 .add_rect(width, height)
658 .map(|loc| (index, texture.image_id, loc))
659 })
660 };
661
662 if texture_search_result.is_none() {
663 let mut atlas = Atlas::new(TEXTURE_SIZE, TEXTURE_SIZE);
665
666 let loc = atlas
667 .add_rect(width, height)
668 .ok_or(ErrorKind::FontSizeTooLargeForAtlas)?;
669
670 let info = ImageInfo::new(ImageFlags::NEAREST, atlas.size().0, atlas.size().1, PixelFormat::Rgba8);
676 let image_id = canvas.images.alloc(&mut canvas.renderer, info)?;
677
678 #[cfg(feature = "debug_inspector")]
679 if cfg!(debug_assertions) {
680 if let Ok(size) = canvas.image_size(image_id) {
682 #[cfg(feature = "image-loading")]
687 {
688 use rgb::FromSlice;
689 let clear_image = image::RgbaImage::from_pixel(
690 size.0 as u32,
691 size.1 as u32,
692 image::Rgba::<u8>([255, 0, 0, 0]),
693 );
694 canvas
695 .update_image(
696 image_id,
697 crate::image::ImageSource::from(imgref::Img::new(
698 clear_image.as_rgba(),
699 clear_image.width() as usize,
700 clear_image.height() as usize,
701 )),
702 0,
703 0,
704 )
705 .unwrap();
706 }
707 #[cfg(not(feature = "image-loading"))]
708 {
709 canvas.save();
710 canvas.reset();
711 canvas.set_render_target(RenderTarget::Image(image_id));
712 canvas.clear_rect(
713 0,
714 0,
715 size.0 as u32,
716 size.1 as u32,
717 Color::rgb(255, 0, 0), );
719 canvas.restore();
720 }
721 }
722 }
723
724 self.glyph_textures.borrow_mut().push(FontTexture { atlas, image_id });
725
726 let index = self.glyph_textures.borrow().len() - 1;
727 texture_search_result = Some((index, image_id, loc));
728 }
729
730 texture_search_result.ok_or(ErrorKind::UnknownError)
731 }
732
733 pub(crate) fn clear<T: Renderer>(&self, canvas: &mut Canvas<T>) {
734 let image_ids = std::mem::take(&mut *self.glyph_textures.borrow_mut())
735 .into_iter()
736 .map(|font_texture| font_texture.image_id);
737 image_ids.for_each(|id| canvas.delete_image(id));
738
739 self.rendered_glyphs.borrow_mut().clear();
740 }
741}
742
743pub fn render_direct<T: Renderer>(
744 canvas: &mut Canvas<T>,
745 font: &Font,
746 glyphs: impl Iterator<Item = PositionedGlyph>,
747 paint_flavor: &PaintFlavor,
748 anti_alias: bool,
749 stroke: &StrokeSettings,
750 font_size: f32,
751 mode: RenderMode,
752) -> Result<(), ErrorKind> {
753 let face = font.face_ref();
754
755 for glyph in glyphs {
756 let (glyph_rendering, scale) = {
757 let scale = font.scale(font_size);
758
759 let Some(glyph_rendering) = font.glyph_rendering_representation(&face, glyph.glyph_id, font_size as u16)
760 else {
761 continue;
762 };
763
764 (glyph_rendering, scale)
765 };
766
767 canvas.save();
768
769 let line_width = match mode {
770 RenderMode::Fill => stroke.line_width,
771 RenderMode::Stroke => stroke.line_width / scale,
772 };
773
774 canvas.translate(glyph.x, glyph.y);
775 canvas.scale(scale, -scale);
776
777 match glyph_rendering {
778 GlyphRendering::RenderAsPath(path) => {
779 if mode == RenderMode::Stroke {
780 canvas.stroke_path_internal(
781 path.borrow(),
782 paint_flavor,
783 anti_alias,
784 &StrokeSettings {
785 line_width,
786 ..stroke.clone()
787 },
788 );
789 } else {
790 canvas.fill_path_internal(path.borrow(), paint_flavor, anti_alias, FillRule::NonZero);
791 }
792 }
793 #[cfg(feature = "image-loading")]
794 GlyphRendering::RenderAsImage(_) => unreachable!(),
795 }
796
797 canvas.restore();
798 }
799
800 Ok(())
801}