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        let result = f();
83        drop(token);
84        result
85    }
86
87    /// Returns the UV coordinate for a white pixel.
88    ///
89    /// Useful for drawing custom shapes with the draw list API.
90    #[doc(alias = "GetFontTexUvWhitePixel")]
91    pub fn font_tex_uv_white_pixel(&self) -> [f32; 2] {
92        self.run_with_bound_context(|| unsafe {
93            let uv = crate::sys::igGetFontTexUvWhitePixel();
94            [uv.x, uv.y]
95        })
96    }
97
98    /// Sets the legacy per-window font scale of the current window.
99    ///
100    /// Prefer [`Ui::push_font_with_size`] or `style.FontScaleMain` for new code.
101    #[doc(alias = "SetWindowFontScale")]
102    pub fn set_window_font_scale(&self, scale: f32) {
103        assert_positive_finite_f32("Ui::set_window_font_scale()", "scale", scale);
104
105        self.run_with_bound_context(|| unsafe {
106            let window = crate::sys::igGetCurrentWindow();
107            if window.is_null() {
108                return;
109            }
110            (*window).FontWindowScale = scale;
111            crate::sys::igUpdateCurrentFontSize(0.0);
112        });
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    fn setup_context() -> crate::Context {
119        let mut ctx = crate::Context::create();
120        ctx.font_atlas()
121            .try_claim_legacy_renderer()
122            .expect("legacy renderer font atlas should be available")
123            .build();
124        ctx.io_mut().set_display_size([128.0, 128.0]);
125        ctx.io_mut().set_delta_time(1.0 / 60.0);
126        ctx
127    }
128
129    #[test]
130    fn set_window_font_scale_updates_current_window_state() {
131        let mut ctx = setup_context();
132        let ui = ctx.frame();
133
134        ui.window("font_scale_test").build(|| {
135            let window = unsafe { crate::sys::igGetCurrentWindowRead() };
136            assert!(!window.is_null());
137            assert_eq!(unsafe { (*window).FontWindowScale }, 1.0);
138
139            ui.set_window_font_scale(1.5);
140
141            assert_eq!(unsafe { (*window).FontWindowScale }, 1.5);
142        });
143    }
144
145    #[test]
146    fn font_runtime_size_setters_validate_before_ffi() {
147        let mut ctx = setup_context();
148        {
149            let ui = ctx.frame();
150
151            ui.window("font_size_token").build(|| {
152                let _font = ui.push_font_with_size(None, 18.0);
153                ui.text("font token is scoped");
154            });
155
156            ui.with_font_and_size(None, 0.0, || {
157                ui.text("closure helper is scoped");
158            });
159        }
160        let _ = ctx.render_legacy();
161
162        let ui = ctx.frame();
163
164        assert!(
165            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
166                let _ = ui.push_font_with_size(None, -1.0);
167            }))
168            .is_err()
169        );
170
171        ui.window("font_scale_invalid").build(|| {
172            let window = unsafe { crate::sys::igGetCurrentWindowRead() };
173            assert_eq!(unsafe { (*window).FontWindowScale }, 1.0);
174
175            assert!(
176                std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
177                    ui.set_window_font_scale(f32::INFINITY);
178                }))
179                .is_err()
180            );
181            assert_eq!(unsafe { (*window).FontWindowScale }, 1.0);
182        });
183    }
184
185    #[test]
186    fn with_font_and_size_pops_after_panic() {
187        let mut ctx = setup_context();
188        {
189            let ui = ctx.frame();
190
191            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
192                ui.with_font_and_size(None, 18.0, || {
193                    panic!("forced panic while font is pushed");
194                });
195            }));
196
197            assert!(result.is_err());
198            ui.text("frame remains balanced after panic");
199        }
200
201        let _ = ctx.render_legacy();
202    }
203
204    #[test]
205    fn push_font_with_size_distinguishes_preserved_and_overridden_sizes() {
206        let mut ctx = crate::Context::create();
207        let small = ctx
208            .font_atlas()
209            .add_font(&[crate::FontSource::default_font_with_size(13.0)]);
210        let large = ctx
211            .font_atlas()
212            .add_font(&[crate::FontSource::default_font_with_size(29.0)]);
213        ctx.font_atlas()
214            .try_claim_legacy_renderer()
215            .expect("legacy renderer font atlas should be available")
216            .build();
217        ctx.io_mut().set_display_size([128.0, 128.0]);
218        ctx.io_mut().set_delta_time(1.0 / 60.0);
219
220        let ui = ctx.frame();
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), 0.0);
226            assert_eq!(ui.current_font(), large);
227            assert_eq!(ui.current_font_size(), 13.0);
228        }
229        assert_eq!(ui.current_font(), small);
230        assert_eq!(ui.current_font_size(), 13.0);
231
232        {
233            let _font = ui.push_font_with_size(Some(large), 37.0);
234            assert_eq!(ui.current_font(), large);
235            assert_eq!(ui.current_font_size(), 37.0);
236        }
237        assert_eq!(ui.current_font(), small);
238        assert_eq!(ui.current_font_size(), 13.0);
239    }
240}