reflexo_typst2vec/font/
glyph.rs

1use 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
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            RasterImage::new(
114                Bytes::new(raster.data.to_vec()),
115                ExchangeFormat::Png,
116                Smart::Auto,
117            )
118            .ok()?,
119            // Axes::new(raster.width as u32, raster.height as u32),
120            None,
121            // todo: scaling
122            Smart::Auto,
123        );
124
125        Some((glyph_image, raster.x, raster.y))
126    }
127
128    /// See [`IGlyphProvider::outline_glyph`] for more information.
129    fn outline_glyph(&self, font: &Font, id: GlyphId) -> Option<String> {
130        let font_face = font.ttf();
131
132        // todo: handling no such glyph
133        let mut builder = SvgOutlineBuilder(String::new());
134        font_face.outline_glyph(id, &mut builder)?;
135        Some(builder.0)
136    }
137}
138
139/// The dummy [`IGlyphProvider`] implementation.
140/// It performs no operation and always returns [`None`].
141#[derive(Default, Hash)]
142pub struct DummyFontGlyphProvider {}
143
144impl IGlyphProvider for DummyFontGlyphProvider {
145    /// See [`IGlyphProvider::ligature_glyph`] for more information.
146    fn ligature_glyph(&self, _font: &Font, _id: GlyphId) -> Option<ImmutStr> {
147        None
148    }
149
150    /// See [`IGlyphProvider::svg_glyph`] for more information.
151    fn svg_glyph(&self, _font: &Font, _id: GlyphId) -> Option<Arc<[u8]>> {
152        None
153    }
154
155    /// See [`IGlyphProvider::bitmap_glyph`] for more information.
156    fn bitmap_glyph(
157        &self,
158        _font: &Font,
159        _id: GlyphId,
160        _ppem: u16,
161    ) -> Option<(TypstImage, i16, i16)> {
162        None
163    }
164
165    /// See [`IGlyphProvider::outline_glyph`] for more information.
166    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}