Skip to main content

dear_imgui_rs/fonts/
mod.rs

1//! Font system for Dear ImGui
2//!
3//! This module provides font management functionality including font atlases,
4//! individual fonts, and font configuration.
5//!
6//! Dear ImGui 1.92 loads glyphs on demand, so the deprecated `GlyphRanges` and
7//! `GlyphRangesBuilder` compatibility helpers are intentionally unavailable:
8//!
9//! ```compile_fail
10//! use dear_imgui_rs::fonts::GlyphRangesBuilder;
11//! ```
12
13pub mod atlas;
14mod baked;
15pub mod font;
16pub mod glyph;
17
18pub use atlas::*;
19pub use baked::*;
20pub use glyph::*;
21
22use crate::Ui;
23
24fn assert_non_negative_finite_f32(caller: &str, name: &str, value: f32) {
25    assert!(value.is_finite(), "{caller} {name} must be finite");
26    assert!(value >= 0.0, "{caller} {name} must be non-negative");
27}
28
29fn assert_positive_finite_f32(caller: &str, name: &str, value: f32) {
30    assert!(value.is_finite(), "{caller} {name} must be finite");
31    assert!(value > 0.0, "{caller} {name} must be positive");
32}
33
34/// # Fonts
35impl Ui {
36    /// Return the persistent, atlas-validated ID of the current font.
37    #[doc(alias = "GetFont")]
38    pub fn current_font(&self) -> FontId {
39        self.run_with_bound_context(|| unsafe {
40            FontId::from_font(crate::sys::igGetFont(), "Ui::current_font()")
41        })
42    }
43
44    /// Returns the current font size (= height in pixels) with font scale applied
45    #[doc(alias = "GetFontSize")]
46    pub fn current_font_size(&self) -> f32 {
47        self.run_with_bound_context(|| unsafe { crate::sys::igGetFontSize() })
48    }
49
50    /// Push a font with dynamic size support (v1.92+ feature).
51    ///
52    /// This allows changing font size at runtime without pre-loading different sizes.
53    /// Pass `None` to keep the current font. A size of `0.0` keeps the current
54    /// size, so `push_font_with_size(Some(font), 0.0)` changes only the font.
55    /// A non-zero size is the base size before Dear ImGui applies global and DPI
56    /// font scaling; [`Ui::current_font_size`] already includes those scales.
57    ///
58    /// Returns a `FontStackToken` that pops the font stack when dropped or when
59    /// [`crate::FontStackToken::pop`] is called.
60    #[doc(alias = "PushFont")]
61    pub fn push_font_with_size(
62        &self,
63        font: Option<FontId>,
64        size: f32,
65    ) -> crate::FontStackToken<'_> {
66        assert_non_negative_finite_f32("Ui::push_font_with_size()", "size", size);
67        self.run_with_bound_context(|| unsafe {
68            let font_ptr = font.map_or(std::ptr::null_mut(), |id| {
69                crate::fonts::validate_font_id_for_current_context(id, "Ui::push_font_with_size()")
70            });
71            crate::sys::igPushFont(font_ptr, size);
72        });
73        crate::FontStackToken::new(self)
74    }
75
76    /// Execute a closure with a specific font and size (v1.92+ dynamic fonts)
77    pub fn with_font_and_size<F, R>(&self, font: Option<FontId>, size: f32, f: F) -> R
78    where
79        F: FnOnce() -> R,
80    {
81        let _token = self.push_font_with_size(font, size);
82        f()
83    }
84
85    /// Returns the UV coordinate for a white pixel.
86    ///
87    /// Useful for drawing custom shapes with the draw list API.
88    #[doc(alias = "GetFontTexUvWhitePixel")]
89    pub fn font_tex_uv_white_pixel(&self) -> [f32; 2] {
90        self.run_with_bound_context(|| unsafe {
91            let uv = crate::sys::igGetFontTexUvWhitePixel();
92            [uv.x, uv.y]
93        })
94    }
95
96    /// Sets the legacy per-window font scale of the current window.
97    ///
98    /// Prefer [`Ui::push_font_with_size`] or `style.FontScaleMain` for new code.
99    #[doc(alias = "SetWindowFontScale")]
100    pub fn set_window_font_scale(&self, scale: f32) {
101        assert_positive_finite_f32("Ui::set_window_font_scale()", "scale", scale);
102
103        self.run_with_bound_context(|| unsafe {
104            let window = crate::sys::igGetCurrentWindow();
105            if window.is_null() {
106                return;
107            }
108            (*window).FontWindowScale = scale;
109            crate::sys::igUpdateCurrentFontSize(0.0);
110        });
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    fn setup_context() -> crate::Context {
117        let mut ctx = crate::Context::create();
118        let _ = ctx.font_atlas().build();
119        ctx.io_mut().set_display_size([128.0, 128.0]);
120        ctx.io_mut().set_delta_time(1.0 / 60.0);
121        ctx
122    }
123
124    #[test]
125    fn set_window_font_scale_updates_current_window_state() {
126        let mut ctx = setup_context();
127        let ui = ctx.frame();
128
129        ui.window("font_scale_test").build(|| {
130            let window = unsafe { crate::sys::igGetCurrentWindowRead() };
131            assert!(!window.is_null());
132            assert_eq!(unsafe { (*window).FontWindowScale }, 1.0);
133
134            ui.set_window_font_scale(1.5);
135
136            assert_eq!(unsafe { (*window).FontWindowScale }, 1.5);
137        });
138    }
139
140    #[test]
141    fn font_runtime_size_setters_validate_before_ffi() {
142        let mut ctx = setup_context();
143        {
144            let ui = ctx.frame();
145
146            ui.window("font_size_token").build(|| {
147                let _font = ui.push_font_with_size(None, 18.0);
148                ui.text("font token is scoped");
149            });
150
151            ui.with_font_and_size(None, 0.0, || {
152                ui.text("closure helper is scoped");
153            });
154        }
155        let _ = ctx.render();
156
157        let ui = ctx.frame();
158
159        assert!(
160            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
161                let _ = ui.push_font_with_size(None, -1.0);
162            }))
163            .is_err()
164        );
165
166        ui.window("font_scale_invalid").build(|| {
167            let window = unsafe { crate::sys::igGetCurrentWindowRead() };
168            assert_eq!(unsafe { (*window).FontWindowScale }, 1.0);
169
170            assert!(
171                std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
172                    ui.set_window_font_scale(f32::INFINITY);
173                }))
174                .is_err()
175            );
176            assert_eq!(unsafe { (*window).FontWindowScale }, 1.0);
177        });
178    }
179
180    #[test]
181    fn with_font_and_size_pops_after_panic() {
182        let mut ctx = setup_context();
183        {
184            let ui = ctx.frame();
185
186            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
187                ui.with_font_and_size(None, 18.0, || {
188                    panic!("forced panic while font is pushed");
189                });
190            }));
191
192            assert!(result.is_err());
193            ui.text("frame remains balanced after panic");
194        }
195
196        let _ = ctx.render();
197    }
198
199    #[test]
200    fn push_font_with_size_distinguishes_preserved_and_overridden_sizes() {
201        let mut ctx = crate::Context::create();
202        let small = ctx
203            .font_atlas()
204            .add_font(&[crate::FontSource::default_font_with_size(13.0)]);
205        let large = ctx
206            .font_atlas()
207            .add_font(&[crate::FontSource::default_font_with_size(29.0)]);
208        let _ = ctx.font_atlas().build();
209        ctx.io_mut().set_display_size([128.0, 128.0]);
210        ctx.io_mut().set_delta_time(1.0 / 60.0);
211
212        let ui = ctx.frame();
213        assert_eq!(ui.current_font(), small);
214        assert_eq!(ui.current_font_size(), 13.0);
215
216        {
217            let _font = ui.push_font_with_size(Some(large), 0.0);
218            assert_eq!(ui.current_font(), large);
219            assert_eq!(ui.current_font_size(), 13.0);
220        }
221        assert_eq!(ui.current_font(), small);
222        assert_eq!(ui.current_font_size(), 13.0);
223
224        {
225            let _font = ui.push_font_with_size(Some(large), 37.0);
226            assert_eq!(ui.current_font(), large);
227            assert_eq!(ui.current_font_size(), 37.0);
228        }
229        assert_eq!(ui.current_font(), small);
230        assert_eq!(ui.current_font_size(), 13.0);
231    }
232}