reflexo_typst2vec/font/
glyph.rs1use std::fmt::Write;
2use std::hash::{Hash, Hasher};
3use std::{ops::Deref, sync::Arc};
4
5use reflexo::hash::{item_hash128, HashedTrait, StaticHash128};
6use reflexo::ImmutStr;
7use typst::foundations::{Bytes, Smart};
8use typst::text::Font;
9use typst::visualize::{ExchangeFormat, Image as TypstImage, RasterImage};
10
11use super::ligature::resolve_ligature;
12
13pub use ttf_parser::GlyphId;
14
15pub trait IGlyphProvider {
19 fn ligature_glyph(&self, font: &Font, id: GlyphId) -> Option<ImmutStr>;
23
24 fn svg_glyph(&self, font: &Font, id: GlyphId) -> Option<Arc<[u8]>>;
28
29 fn bitmap_glyph(&self, font: &Font, id: GlyphId, ppem: u16) -> Option<(TypstImage, i16, i16)>;
34
35 fn outline_glyph(&self, font: &Font, id: GlyphId) -> Option<String>;
39}
40
41#[derive(Clone)]
42pub struct GlyphProvider(Arc<HashedTrait<dyn IGlyphProvider + Send + Sync>>);
43
44impl GlyphProvider {
45 #[allow(clippy::arc_with_non_send_sync)]
46 pub fn new<T>(provider: T) -> Self
47 where
48 T: IGlyphProvider + Send + Sync + Hash + 'static,
49 {
50 let hash = item_hash128(&provider);
51 let provider = Box::new(provider);
52 Self(Arc::new(
53 HashedTrait::<dyn IGlyphProvider + Send + Sync>::new(hash, provider),
54 ))
55 }
56}
57
58impl Deref for GlyphProvider {
59 type Target = dyn IGlyphProvider;
60
61 fn deref(&self) -> &Self::Target {
62 (*self.0.as_ref()).deref()
63 }
64}
65
66impl Hash for GlyphProvider {
67 #[inline]
68 fn hash<H: Hasher>(&self, state: &mut H) {
69 state.write_u128(self.0.get_hash());
70 }
71}
72
73impl Default for GlyphProvider {
74 fn default() -> Self {
75 Self::new(FontGlyphProvider::default())
76 }
77}
78
79#[derive(Default, Hash)]
82pub struct FontGlyphProvider {}
83
84impl IGlyphProvider for FontGlyphProvider {
85 fn ligature_glyph(&self, font: &Font, id: GlyphId) -> Option<ImmutStr> {
87 resolve_ligature(font, id)
88 }
89
90 fn svg_glyph(&self, font: &Font, id: GlyphId) -> Option<Arc<[u8]>> {
92 let font_face = font.ttf();
93
94 Some(font_face.glyph_svg_image(id)?.data.into())
95 }
96
97 fn bitmap_glyph(&self, font: &Font, id: GlyphId, ppem: u16) -> Option<(TypstImage, i16, i16)> {
101 let font_face = font.ttf();
102
103 let raster = font_face.glyph_raster_image(id, ppem)?;
104
105 if raster.format != ttf_parser::RasterImageFormat::PNG {
107 return None;
108 }
109
110 let glyph_image = TypstImage::new(
113 RasterImage::new(
114 Bytes::new(raster.data.to_vec()),
115 ExchangeFormat::Png,
116 Smart::Auto,
117 )
118 .ok()?,
119 None,
121 Smart::Auto,
123 );
124
125 Some((glyph_image, raster.x, raster.y))
126 }
127
128 fn outline_glyph(&self, font: &Font, id: GlyphId) -> Option<String> {
130 let font_face = font.ttf();
131
132 let mut builder = SvgOutlineBuilder(String::new());
134 font_face.outline_glyph(id, &mut builder)?;
135 Some(builder.0)
136 }
137}
138
139#[derive(Default, Hash)]
142pub struct DummyFontGlyphProvider {}
143
144impl IGlyphProvider for DummyFontGlyphProvider {
145 fn ligature_glyph(&self, _font: &Font, _id: GlyphId) -> Option<ImmutStr> {
147 None
148 }
149
150 fn svg_glyph(&self, _font: &Font, _id: GlyphId) -> Option<Arc<[u8]>> {
152 None
153 }
154
155 fn bitmap_glyph(
157 &self,
158 _font: &Font,
159 _id: GlyphId,
160 _ppem: u16,
161 ) -> Option<(TypstImage, i16, i16)> {
162 None
163 }
164
165 fn outline_glyph(&self, _font: &Font, _id: GlyphId) -> Option<String> {
167 None
168 }
169}
170
171#[derive(Default)]
172struct SvgOutlineBuilder(pub String);
173
174impl ttf_parser::OutlineBuilder for SvgOutlineBuilder {
175 fn move_to(&mut self, x: f32, y: f32) {
176 write!(&mut self.0, "M {x} {y} ").unwrap();
177 }
178
179 fn line_to(&mut self, x: f32, y: f32) {
180 write!(&mut self.0, "L {x} {y} ").unwrap();
181 }
182
183 fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
184 write!(&mut self.0, "Q {x1} {y1} {x} {y} ").unwrap();
185 }
186
187 fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
188 write!(&mut self.0, "C {x1} {y1} {x2} {y2} {x} {y} ").unwrap();
189 }
190
191 fn close(&mut self) {
192 write!(&mut self.0, "Z ").unwrap();
193 }
194}