easy_imgui/
fontloader.rs

1use super::{FontBaked, PixelImage, SubPixelImage, Vector2, vec2};
2use bitflags::bitflags;
3use easy_imgui_sys::*;
4use image::GenericImage;
5
6/// Trait that implements a custom font loader.
7pub trait GlyphLoader {
8    /// Return true if this font loader contains this character.
9    fn contains_glyph(&mut self, codepoint: char) -> bool;
10    /// Try to load a glyph.
11    ///
12    /// Refer to [`GlyphLoaderArg`] for details.
13    fn load_glyph(&mut self, arg: GlyphLoaderArg<'_>);
14    /// Initialize the parameters of the corresponding `FontBaked`.
15    ///
16    /// Not needed if this custom loader is part of a font collection and is not the first one.
17    fn font_baked_init(&mut self, _baked: &mut FontBaked) {}
18    /// The font baked is about to be destroyed.
19    ///
20    /// Rarely useful, maybe if you had some raw resource stored in the `baked`.
21    fn font_baked_destroy(&mut self, _baked: &mut FontBaked) {}
22}
23
24pub type BoxGlyphLoader = Box<dyn GlyphLoader + 'static>;
25
26/// Newtype for the one and only FONT_LOADER.
27pub struct FontLoader(pub ImFontLoader);
28unsafe impl Sync for FontLoader {}
29
30pub static FONT_LOADER: FontLoader = FontLoader::new();
31
32impl FontLoader {
33    const fn new() -> Self {
34        let inner = ImFontLoader {
35            Name: c"easy_imgui::FontLoader".as_ptr(),
36            LoaderInit: None,
37            LoaderShutdown: None,
38            FontSrcInit: Some(font_src_init),
39            FontSrcDestroy: Some(font_src_destroy),
40            FontSrcContainsGlyph: Some(font_src_contains_glyph),
41            FontBakedInit: Some(font_baked_init),
42            FontBakedDestroy: Some(font_baked_destroy),
43            FontBakedLoadGlyph: Some(font_baked_load_glyph),
44            FontBakedSrcLoaderDataSize: 0,
45        };
46        FontLoader(inner)
47    }
48}
49
50unsafe extern "C" fn font_src_init(_atlas: *mut ImFontAtlas, src: *mut ImFontConfig) -> bool {
51    unsafe {
52        let ptr = (*src).FontLoaderData;
53        if !ptr.is_null() {
54            return false;
55        }
56        (*src).FontLoaderData = std::mem::replace(&mut (*src).FontData, std::ptr::null_mut());
57        true
58    }
59}
60
61unsafe extern "C" fn font_src_destroy(_atlas: *mut ImFontAtlas, src: *mut ImFontConfig) {
62    unsafe {
63        let ptr = (*src).FontLoaderData;
64        if ptr.is_null() {
65            return;
66        }
67        let ptr = ptr as *mut BoxGlyphLoader;
68        drop(Box::from_raw(ptr));
69        (*src).FontLoaderData = std::ptr::null_mut();
70    }
71}
72
73unsafe extern "C" fn font_src_contains_glyph(
74    _atlas: *mut ImFontAtlas,
75    src: *mut ImFontConfig,
76    codepoint: ImWchar,
77) -> bool {
78    unsafe {
79        let ptr = (*src).FontLoaderData;
80        if ptr.is_null() {
81            return false;
82        }
83        let Some(c) = char::from_u32(codepoint) else {
84            return false;
85        };
86        let ptr = ptr as *mut BoxGlyphLoader;
87        let ldr = &mut *ptr;
88        ldr.contains_glyph(c)
89    }
90}
91
92unsafe extern "C" fn font_baked_load_glyph(
93    atlas: *mut ImFontAtlas,
94    src: *mut ImFontConfig,
95    baked: *mut ImFontBaked,
96    _loader_data_for_baked_src: *mut ::std::os::raw::c_void,
97    codepoint: ImWchar,
98    out_glyph: *mut ImFontGlyph,
99    out_advance_x: *mut f32,
100) -> bool {
101    unsafe {
102        let ptr = (*src).FontLoaderData;
103        if ptr.is_null() {
104            return false;
105        }
106        let Some(codepoint) = char::from_u32(codepoint) else {
107            return false;
108        };
109        let ptr = ptr as *mut BoxGlyphLoader;
110        let ldr = &mut *ptr;
111
112        let atlas = &mut *atlas;
113        let baked = &mut *baked;
114        let src = &mut *src;
115        let mut oversample_h = 0;
116        let mut oversample_v = 0;
117        ImFontAtlasBuildGetOversampleFactors(src, baked, &mut oversample_h, &mut oversample_v);
118        let rasterizer_density = src.RasterizerDensity * baked.RasterizerDensity;
119        let mut result = false;
120
121        let output = if out_advance_x.is_null() {
122            GlyphLoaderResult::Glyph(&mut *out_glyph)
123        } else {
124            GlyphLoaderResult::AdvanceX(&mut *out_advance_x)
125        };
126        let arg = GlyphLoaderArg {
127            codepoint,
128            oversample: vec2(oversample_h as f32, oversample_v as f32),
129            rasterizer_density,
130            result: &mut result,
131            atlas,
132            src,
133            baked,
134            output,
135        };
136        ldr.load_glyph(arg);
137        result
138    }
139}
140
141unsafe extern "C" fn font_baked_init(
142    _atlas: *mut ImFontAtlas,
143    src: *mut ImFontConfig,
144    baked: *mut ImFontBaked,
145    _loader_data_for_baked_src: *mut ::std::os::raw::c_void,
146) -> bool {
147    unsafe {
148        let ptr = (*src).FontLoaderData;
149        if ptr.is_null() {
150            return false;
151        }
152        let ptr = ptr as *mut BoxGlyphLoader;
153        let ldr = &mut *ptr;
154
155        ldr.font_baked_init(FontBaked::cast_mut(&mut *baked));
156        true
157    }
158}
159
160unsafe extern "C" fn font_baked_destroy(
161    _atlas: *mut ImFontAtlas,
162    src: *mut ImFontConfig,
163    baked: *mut ImFontBaked,
164    _loader_data_for_baked_src: *mut ::std::os::raw::c_void,
165) {
166    unsafe {
167        let ptr = (*src).FontLoaderData;
168        if ptr.is_null() {
169            return;
170        }
171        let ptr = ptr as *mut BoxGlyphLoader;
172        let ldr = &mut *ptr;
173
174        ldr.font_baked_destroy(FontBaked::cast_mut(&mut *baked));
175    }
176}
177
178enum GlyphLoaderResult<'a> {
179    Glyph(&'a mut ImFontGlyph),
180    AdvanceX(&'a mut f32),
181}
182
183/// Arguments for [`GlyphLoader::load_glyph`].
184///
185/// Having a struct instead of a collection of parameters makes it easier to write and use.
186pub struct GlyphLoaderArg<'a> {
187    codepoint: char,
188    // This are integer in DearImGui, but why not fractions?
189    oversample: Vector2,
190    rasterizer_density: f32,
191    result: &'a mut bool,
192
193    atlas: &'a mut ImFontAtlas,
194    src: &'a mut ImFontConfig,
195    baked: &'a mut ImFontBaked,
196    output: GlyphLoaderResult<'a>,
197}
198
199bitflags! {
200    /// Flags to modify the behavior of `GlyphLoaderArg::build()`.
201    #[derive(Copy, Clone, Debug)]
202    pub struct GlyphBuildFlags: u32 {
203        /// Sets the oversample to (1, 1) before building the image.
204        const IGNORE_OVERSAMPLE = 1;
205        /// Sets dpi_density to 1.0 before building the image.
206        const IGNORE_DPI = 2;
207        /// The size passed in already scaled to the final size.
208        const PRESCALED_SIZE = 4;
209        /// Sets all the scaling factors to 1. This will make the final image to the size passed to `build()`.
210        ///
211        /// Note that setting `PRESCALED_SIZE` and `IGNORE_SCALE` does nothing.
212        const IGNORE_SCALE = Self::IGNORE_OVERSAMPLE.bits() | Self::IGNORE_DPI.bits();
213    }
214}
215
216impl GlyphLoaderArg<'_> {
217    /// The character to be loaded.
218    pub fn codepoint(&self) -> char {
219        self.codepoint
220    }
221    /// The current size of the font requested.
222    pub fn font_size(&self) -> f32 {
223        self.baked.Size
224    }
225    /// Gets the rasterizer density (DPI) of the renderer that requests this glyph.
226    ///
227    /// It is usually 1.0, but in hiDPI settings it may be 2.0 (or any other value, actually).
228    pub fn dpi_density(&self) -> f32 {
229        self.rasterizer_density
230    }
231    /// Sets the rasterizer density.
232    ///
233    /// If you can't (or don't want to) support hiDPI environments you can disable it by either
234    /// setting the `GlyphBuildFlags::IGNORE_DPI` or by calling this function.
235    /// You can set it to values other than 1.0, but the usefulness is limited.
236    pub fn set_dpi_density(&mut self, scale: f32) {
237        self.rasterizer_density = scale;
238    }
239    /// Gets the X/Y oversample factor.
240    ///
241    /// This is usually (1.0, 1.0), but for small fonts, it may be (2.0, 1.0).
242    /// If so, you should render your glyph scaled by these factors in X and Y.
243    pub fn oversample(&self) -> Vector2 {
244        self.oversample
245    }
246    /// Sets the X/Y oversample factor.
247    ///
248    /// If you can't (or don't want to) support oversampling you can disable it by either
249    /// setting the `GlyphBuildFlags::IGNORE_OVERSAMPLE` or by calling this function.
250    pub fn set_oversample(&mut self, oversample: Vector2) {
251        self.oversample = oversample;
252    }
253    /// Returns the "only advance X" flag.
254    ///
255    /// If this is true, when you call `build`, the `draw` callback will not actually be called:
256    /// only the `advance_x` parameter will be used.
257    ///
258    /// This is used by Dear ImGui when using very big fonts to compute the position of the glyphs
259    /// before rendering them, to save space in the texture atlas.
260    ///
261    /// You can safely ignore this, if you don't need to micro-optimize the loading of very big
262    /// custom fonts.
263    pub fn only_advance_x(&self) -> bool {
264        matches!(self.output, GlyphLoaderResult::AdvanceX(_))
265    }
266    /// Builds the requested glyph.
267    ///
268    /// `origin` is the offset of the origin of the glyph, by default it will be just over the
269    /// baseline.
270    /// `size` is the size of the glyph, when drawn to the screen.
271    /// `advance_x` is how many pixels this glyph occupies when part of a string.
272    /// `flags`: how to interpret the size and scale the bitmap.
273    /// `draw`: callback that actually draws the glyph.
274    ///
275    /// Note that `draw()` may not be actually called every time. You can use `only_advance_x()` to
276    /// detect this case, if you need it.
277    pub fn build(
278        mut self,
279        origin: Vector2,
280        mut size: Vector2,
281        advance_x: f32,
282        flags: GlyphBuildFlags,
283        draw: impl FnOnce(&mut SubPixelImage<'_, '_>),
284    ) {
285        match self.output {
286            GlyphLoaderResult::AdvanceX(out_advance_x) => {
287                *out_advance_x = advance_x;
288            }
289            GlyphLoaderResult::Glyph(out_glyph) => {
290                if flags.contains(GlyphBuildFlags::IGNORE_DPI) {
291                    self.rasterizer_density = 1.0;
292                }
293                if flags.contains(GlyphBuildFlags::IGNORE_OVERSAMPLE) {
294                    self.oversample = vec2(1.0, 1.0);
295                }
296
297                let scale_for_raster = self.rasterizer_density * self.oversample;
298
299                let (bmp_size_x, bmp_size_y);
300
301                if flags.contains(GlyphBuildFlags::PRESCALED_SIZE) {
302                    bmp_size_x = size.x.round() as u32;
303                    bmp_size_y = size.y.round() as u32;
304                    size.x /= scale_for_raster.x;
305                    size.y /= scale_for_raster.y;
306                } else {
307                    bmp_size_x = (size.x * scale_for_raster.x).round() as u32;
308                    bmp_size_y = (size.y * scale_for_raster.y).round() as u32;
309                }
310
311                let pack_id = unsafe {
312                    ImFontAtlasPackAddRect(
313                        self.atlas,
314                        bmp_size_x as i32,
315                        bmp_size_y as i32,
316                        std::ptr::null_mut(),
317                    )
318                };
319                if pack_id == ImFontAtlasRectId_Invalid {
320                    return;
321                }
322                let r = unsafe {
323                    let r = ImFontAtlasPackGetRect(self.atlas, pack_id);
324                    &mut *r
325                };
326
327                let ref_size = unsafe { (*(&(*self.baked.ContainerFont).Sources)[0]).SizePixels };
328                let offsets_scale = if ref_size != 0.0 {
329                    self.baked.Size / ref_size
330                } else {
331                    1.0
332                };
333                let mut font_off_x = self.src.GlyphOffset.x * offsets_scale;
334                let mut font_off_y = self.src.GlyphOffset.y * offsets_scale;
335                if self.src.PixelSnapH {
336                    font_off_x = font_off_x.round();
337                }
338                if self.src.PixelSnapV {
339                    font_off_y = font_off_y.round();
340                }
341
342                out_glyph.set_Codepoint(self.codepoint as u32);
343                out_glyph.AdvanceX = advance_x;
344                out_glyph.X0 = font_off_x + origin.x;
345                out_glyph.Y0 = font_off_y + origin.y;
346                out_glyph.X1 = out_glyph.X0 + size.x;
347                out_glyph.Y1 = out_glyph.Y0 + size.y;
348                out_glyph.set_Visible(1);
349                out_glyph.PackId = pack_id;
350
351                let mut pixels =
352                    image::ImageBuffer::<image::Rgba<u8>, Vec<u8>>::new(bmp_size_x, bmp_size_y)
353                        .into_raw();
354                let mut image = PixelImage::from_raw(bmp_size_x, bmp_size_y, &mut pixels).unwrap();
355                draw(&mut image.sub_image(0, 0, bmp_size_x, bmp_size_y));
356                unsafe {
357                    ImFontAtlasBakedSetFontGlyphBitmap(
358                        self.atlas,
359                        self.baked,
360                        self.src,
361                        out_glyph,
362                        r,
363                        pixels.as_ptr(),
364                        ImTextureFormat::ImTextureFormat_RGBA32,
365                        4 * bmp_size_x as i32,
366                    );
367                }
368            }
369        }
370        *self.result = true;
371    }
372}