reflexo_typst2vec/font/
glyph.rs

1use std::fmt::Write;
2use std::hash::{Hash, Hasher};
3use std::{ops::Deref, sync::Arc};
4
5pub use ttf_parser::GlyphId;
6use typst::text::Font;
7// use typst::geom::Axes;
8use typst::visualize::{Image as TypstImage, RasterFormat};
9
10use reflexo::hash::item_hash128;
11use reflexo::{HashedTrait, ImmutStr, StaticHash128};
12
13use super::ligature::resolve_ligature;
14
15/// IGlyphProvider extracts the font data from the font.
16/// Note (Possibly block unsafe): If a [`Font`] is dummy (lazy loaded),
17///   it will block current thread and fetch the font data from the server.
18pub trait IGlyphProvider {
19    /// With font with glyph id, return the raw ligature string.
20    /// See [`FontGlyphProvider::ligature_glyph`] for the default
21    /// implementation.
22    fn ligature_glyph(&self, font: &Font, id: GlyphId) -> Option<ImmutStr>;
23
24    /// With font with glyph id, return the svg document data.
25    /// Note: The returned data is possibly compressed.
26    /// See [`FontGlyphProvider::svg_glyph`] for the default implementation.
27    fn svg_glyph(&self, font: &Font, id: GlyphId) -> Option<Arc<[u8]>>;
28
29    /// With font with glyph id, return the bitmap image data.
30    /// Optionally, with given ppem, return the best fit bitmap image.
31    /// Return the best quality bitmap image if ppem is [`std::u16::MAX`].
32    /// See [`FontGlyphProvider::bitmap_glyph`] for the default implementation.
33    fn bitmap_glyph(&self, font: &Font, id: GlyphId, ppem: u16) -> Option<(TypstImage, i16, i16)>;
34
35    /// With font with glyph id, return the outline path data.
36    /// The returned data is in Path2D format.
37    /// See [`FontGlyphProvider::outline_glyph`] for the default implementation.
38    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/// The default [`IGlyphProvider`] implementation.
80/// It uses the local font data to extract the glyph data.
81#[derive(Default, Hash)]
82pub struct FontGlyphProvider {}
83
84impl IGlyphProvider for FontGlyphProvider {
85    /// See [`IGlyphProvider::ligature_glyph`] for more information.
86    fn ligature_glyph(&self, font: &Font, id: GlyphId) -> Option<ImmutStr> {
87        resolve_ligature(font, id)
88    }
89
90    /// See [`IGlyphProvider::svg_glyph`] for more information.
91    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    /// See [`IGlyphProvider::bitmap_glyph`] for more information.
98    /// Note: It converts the data into [`typst::visualize::Image`] and
99    /// introduces overhead.
100    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        // todo: more raster image support?
106        if raster.format != ttf_parser::RasterImageFormat::PNG {
107            return None;
108        }
109
110        // convert to typst's image format
111        // todo: verify result
112        let glyph_image = TypstImage::new(
113            raster.data.into(),
114            RasterFormat::Png.into(),
115            // Axes::new(raster.width as u32, raster.height as u32),
116            None,
117        )
118        .ok()?;
119
120        Some((glyph_image, raster.x, raster.y))
121    }
122
123    /// See [`IGlyphProvider::outline_glyph`] for more information.
124    fn outline_glyph(&self, font: &Font, id: GlyphId) -> Option<String> {
125        let font_face = font.ttf();
126
127        // todo: handling no such glyph
128        let mut builder = SvgOutlineBuilder(String::new());
129        font_face.outline_glyph(id, &mut builder)?;
130        Some(builder.0)
131    }
132}
133
134/// The dummy [`IGlyphProvider`] implementation.
135/// It performs no operation and always returns [`None`].
136#[derive(Default, Hash)]
137pub struct DummyFontGlyphProvider {}
138
139impl IGlyphProvider for DummyFontGlyphProvider {
140    /// See [`IGlyphProvider::ligature_glyph`] for more information.
141    fn ligature_glyph(&self, _font: &Font, _id: GlyphId) -> Option<ImmutStr> {
142        None
143    }
144
145    /// See [`IGlyphProvider::svg_glyph`] for more information.
146    fn svg_glyph(&self, _font: &Font, _id: GlyphId) -> Option<Arc<[u8]>> {
147        None
148    }
149
150    /// See [`IGlyphProvider::bitmap_glyph`] for more information.
151    fn bitmap_glyph(
152        &self,
153        _font: &Font,
154        _id: GlyphId,
155        _ppem: u16,
156    ) -> Option<(TypstImage, i16, i16)> {
157        None
158    }
159
160    /// See [`IGlyphProvider::outline_glyph`] for more information.
161    fn outline_glyph(&self, _font: &Font, _id: GlyphId) -> Option<String> {
162        None
163    }
164}
165
166#[derive(Default)]
167struct SvgOutlineBuilder(pub String);
168
169impl ttf_parser::OutlineBuilder for SvgOutlineBuilder {
170    fn move_to(&mut self, x: f32, y: f32) {
171        write!(&mut self.0, "M {} {} ", x, y).unwrap();
172    }
173
174    fn line_to(&mut self, x: f32, y: f32) {
175        write!(&mut self.0, "L {} {} ", x, y).unwrap();
176    }
177
178    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
179        write!(&mut self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap();
180    }
181
182    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
183        write!(&mut self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap();
184    }
185
186    fn close(&mut self) {
187        write!(&mut self.0, "Z ").unwrap();
188    }
189}