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, glyph ranges, and font configuration.
5
6pub mod atlas;
7pub mod font;
8pub mod glyph;
9/// Deprecated glyph ranges helpers.
10///
11/// With Dear ImGui 1.92+, fonts are dynamically sized and glyphs are loaded on demand.
12/// In most cases you no longer need to specify glyph ranges. Keep using this module
13/// only for legacy code or very constrained environments where you explicitly want to
14/// limit the character set.
15#[deprecated(
16    since = "0.2.0",
17    note = "ImGui 1.92+ recommends dynamic fonts with on-demand glyph loading; glyph ranges are kept for legacy compatibility"
18)]
19pub mod glyph_ranges;
20
21pub use atlas::*;
22pub use font::*;
23pub use glyph::*;
24#[allow(deprecated)]
25pub use glyph_ranges::*;
26
27use crate::Ui;
28
29fn assert_non_negative_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 non-negative");
32}
33
34fn assert_positive_finite_f32(caller: &str, name: &str, value: f32) {
35    assert!(value.is_finite(), "{caller} {name} must be finite");
36    assert!(value > 0.0, "{caller} {name} must be positive");
37}
38
39/// # Fonts
40impl Ui {
41    /// Returns the current font
42    #[doc(alias = "GetFont")]
43    pub fn current_font(&self) -> &Font {
44        self.run_with_bound_context(|| unsafe {
45            Font::from_raw(crate::sys::igGetFont() as *const _)
46        })
47    }
48
49    /// Returns the current font size (= height in pixels) with font scale applied
50    #[doc(alias = "GetFontSize")]
51    pub fn current_font_size(&self) -> f32 {
52        self.run_with_bound_context(|| unsafe { crate::sys::igGetFontSize() })
53    }
54
55    /// Push a font with dynamic size support (v1.92+ feature).
56    ///
57    /// This allows changing font size at runtime without pre-loading different sizes.
58    /// Pass None for font to use the current font with the new size.
59    ///
60    /// Returns a `FontStackToken` that pops the font stack when dropped or when
61    /// [`FontStackToken::pop`] is called.
62    #[doc(alias = "PushFont")]
63    pub fn push_font_with_size(&self, font: Option<&Font>, size: f32) -> crate::FontStackToken<'_> {
64        assert_non_negative_finite_f32("Ui::push_font_with_size()", "size", size);
65        self.run_with_bound_context(|| unsafe {
66            let font_ptr = font.map_or(std::ptr::null_mut(), |f| {
67                crate::fonts::validate_font_for_current_context(f, "Ui::push_font_with_size()")
68            });
69            crate::sys::igPushFont(font_ptr, size);
70        });
71        crate::FontStackToken::new(self)
72    }
73
74    /// Execute a closure with a specific font and size (v1.92+ dynamic fonts)
75    pub fn with_font_and_size<F, R>(&self, font: Option<&Font>, size: f32, f: F) -> R
76    where
77        F: FnOnce() -> R,
78    {
79        let _token = self.push_font_with_size(font, size);
80        f()
81    }
82
83    /// Returns the UV coordinate for a white pixel.
84    ///
85    /// Useful for drawing custom shapes with the draw list API.
86    #[doc(alias = "GetFontTexUvWhitePixel")]
87    pub fn font_tex_uv_white_pixel(&self) -> [f32; 2] {
88        self.run_with_bound_context(|| unsafe {
89            let uv = crate::sys::igGetFontTexUvWhitePixel();
90            [uv.x, uv.y]
91        })
92    }
93
94    /// Sets the legacy per-window font scale of the current window.
95    ///
96    /// Prefer [`Ui::push_font_with_size`] or `style.FontScaleMain` for new code.
97    #[doc(alias = "SetWindowFontScale")]
98    pub fn set_window_font_scale(&self, scale: f32) {
99        assert_positive_finite_f32("Ui::set_window_font_scale()", "scale", scale);
100
101        self.run_with_bound_context(|| unsafe {
102            let window = crate::sys::igGetCurrentWindow();
103            if window.is_null() {
104                return;
105            }
106            (*window).FontWindowScale = scale;
107            crate::sys::igUpdateCurrentFontSize(0.0);
108        });
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    fn setup_context() -> crate::Context {
115        let mut ctx = crate::Context::create();
116        let _ = ctx.font_atlas_mut().build();
117        ctx.io_mut().set_display_size([128.0, 128.0]);
118        ctx.io_mut().set_delta_time(1.0 / 60.0);
119        ctx
120    }
121
122    #[test]
123    fn set_window_font_scale_updates_current_window_state() {
124        let mut ctx = setup_context();
125        let ui = ctx.frame();
126
127        ui.window("font_scale_test").build(|| {
128            let window = unsafe { crate::sys::igGetCurrentWindowRead() };
129            assert!(!window.is_null());
130            assert_eq!(unsafe { (*window).FontWindowScale }, 1.0);
131
132            ui.set_window_font_scale(1.5);
133
134            assert_eq!(unsafe { (*window).FontWindowScale }, 1.5);
135        });
136    }
137
138    #[test]
139    fn font_runtime_size_setters_validate_before_ffi() {
140        let mut ctx = setup_context();
141        {
142            let ui = ctx.frame();
143
144            ui.window("font_size_token").build(|| {
145                let _font = ui.push_font_with_size(None, 18.0);
146                ui.text("font token is scoped");
147            });
148
149            ui.with_font_and_size(None, 0.0, || {
150                ui.text("closure helper is scoped");
151            });
152        }
153        let _ = ctx.render();
154
155        let ui = ctx.frame();
156
157        assert!(
158            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
159                let _ = ui.push_font_with_size(None, -1.0);
160            }))
161            .is_err()
162        );
163
164        ui.window("font_scale_invalid").build(|| {
165            let window = unsafe { crate::sys::igGetCurrentWindowRead() };
166            assert_eq!(unsafe { (*window).FontWindowScale }, 1.0);
167
168            assert!(
169                std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
170                    ui.set_window_font_scale(f32::INFINITY);
171                }))
172                .is_err()
173            );
174            assert_eq!(unsafe { (*window).FontWindowScale }, 1.0);
175        });
176    }
177
178    #[test]
179    fn with_font_and_size_pops_after_panic() {
180        let mut ctx = setup_context();
181        {
182            let ui = ctx.frame();
183
184            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
185                ui.with_font_and_size(None, 18.0, || {
186                    panic!("forced panic while font is pushed");
187                });
188            }));
189
190            assert!(result.is_err());
191            ui.text("frame remains balanced after panic");
192        }
193
194        let _ = ctx.render();
195    }
196}