diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index cd098eaea..d706fe46d 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -458,8 +458,8 @@ pub use epaint::{ pub mod text { pub use crate::text_selection::CCursorRange; pub use epaint::text::{ - FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, LayoutSection, TAB_SIZE, - TextFormat, TextWrapping, cursor::CCursor, + FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, LayoutSection, TextFormat, + TextWrapping, cursor::CCursor, }; } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index f7b889506..b84fe727d 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -2915,6 +2915,8 @@ impl Widget for &mut FontTweak { y_offset, hinting_override, coords, + thin_space_width, + tab_size, } = self; ui.label("Scale"); @@ -2987,6 +2989,21 @@ impl Widget for &mut FontTweak { } ui.end_row(); + ui.label("thin_space_width"); + ui.horizontal(|ui| { + ui.add( + DragValue::new(thin_space_width) + .range(0.0..=1.0) + .speed(0.01), + ); + ui.label("1\u{2009}234\u{2009}567\u{2009}890"); + }); + ui.end_row(); + + ui.label("tab_size"); + ui.add(DragValue::new(tab_size).range(0.0..=16.0).speed(0.1)); + ui.end_row(); + if ui.button("Reset").clicked() { *self = Default::default(); } diff --git a/crates/egui/src/widgets/text_edit/text_buffer.rs b/crates/egui/src/widgets/text_edit/text_buffer.rs index a67dc1b38..dbc2db26e 100644 --- a/crates/egui/src/widgets/text_edit/text_buffer.rs +++ b/crates/egui/src/widgets/text_edit/text_buffer.rs @@ -1,9 +1,9 @@ use std::{borrow::Cow, ops::Range}; -use epaint::{ - Galley, - text::{TAB_SIZE, cursor::CCursor}, -}; +use epaint::{Galley, text::cursor::CCursor}; + +/// One `\t` character is this many spaces wide (for indentation purposes). +const TAB_SIZE: usize = 4; use crate::{ text::CCursorRange, diff --git a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x1.png b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x1.png index 218a4e258..d65dcb44f 100644 --- a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x1.png +++ b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ef07080ca2aa10e128c646479b8b322a092d63f15b35c52ed59015e7c2a0f60 -size 15434 +oid sha256:6d64cc7d014cf063689a4dd8a6cdd87eb944f9637890baf32f59a258342400bf +size 15400 diff --git a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x2.png b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x2.png index 8da2d19a9..147a692f2 100644 --- a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x2.png +++ b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_dark_x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4edb61c6619d5e44892fa29da0aa0a624306ae637dbbaa057e3fa47c14dc06bd -size 35988 +oid sha256:9e9f800546cc98bbd92f31072aceef10b5b8b9bbef0db4c8f4dbae652aefec61 +size 35918 diff --git a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x1.png b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x1.png index 1085f9969..ad6dd5637 100644 --- a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x1.png +++ b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8da0c2e37497968864a91fa6bef7c545c37791f4f9b788ea9a2f43dd4ac16b1 -size 16116 +oid sha256:e3f6e1cc6a069ac96ff9e039ce85462453ee943b4cb46080550bc1c0749ad658 +size 16085 diff --git a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x2.png b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x2.png index d89daa1c0..71b8a1cee 100644 --- a/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x2.png +++ b/crates/egui_demo_lib/tests/snapshots/image_kerning/image_light_x2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a603fcb2eb97943d6be3239b91cacee093adcb55a6dd14af93a72fc8b3a61fa -size 39270 +oid sha256:5901b5a8201b85b51118193eef300749a4569eedc62043cd4e03f76e73a12f51 +size 39222 diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 48659fbe9..311b17a05 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -432,8 +432,7 @@ impl FontFace { && let Some(space) = self.glyph_info(' ') { let glyph_info = GlyphInfo { - advance_width_unscaled: (crate::text::TAB_SIZE as f32 - * space.advance_width_unscaled.0) + advance_width_unscaled: (self.tweak.tab_size * space.advance_width_unscaled.0) .into(), ..space }; @@ -441,21 +440,18 @@ impl FontFace { return Some(glyph_info); } - if c == '\u{2009}' { - // Thin space, often used as thousands deliminator: 1 234 567 890 - // https://www.compart.com/en/unicode/U+2009 - // https://en.wikipedia.org/wiki/Thin_space - - if let Some(space) = self.glyph_info(' ') { - let em = self.font.borrow_dependent().metrics.units_per_em as f32; - let advance_width = f32::min(em / 6.0, space.advance_width_unscaled.0 * 0.5); // TODO(emilk): make configurable - let glyph_info = GlyphInfo { - advance_width_unscaled: advance_width.into(), - ..space - }; - self.glyph_info_cache.insert(c, glyph_info); - return Some(glyph_info); - } + if (c == '\u{2009}' || c == '\u{202F}') + && let Some(space) = self.glyph_info(' ') + { + // Thin space (U+2009) and narrow no-break space (U+202F), + // often used as thousands separator: 1 234 567 890 + let advance_width = self.tweak.thin_space_width * space.advance_width_unscaled.0; + let glyph_info = GlyphInfo { + advance_width_unscaled: advance_width.into(), + ..space + }; + self.glyph_info_cache.insert(c, glyph_info); + return Some(glyph_info); } if invisible_char(c) { diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 135ddb6d5..b6c8f3504 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -193,6 +193,19 @@ pub struct FontTweak { /// Override the font's default variation coordinates. pub coords: VariationCoords, + + /// Width of a thin space (`\u{2009}`) and narrow no-break space (`\u{202F}`), + /// as a fraction of the normal space width. + /// + /// Thin space is often used as a thousands separator: `1 234 567`. + /// + /// Default: `0.5` (half a normal space). + pub thin_space_width: f32, + + /// Width of a tab character (`\t`), measured in number of space widths. + /// + /// Default: `4.0`. + pub tab_size: f32, } impl Default for FontTweak { @@ -203,6 +216,8 @@ impl Default for FontTweak { y_offset: 0.0, hinting_override: None, coords: VariationCoords::default(), + thin_space_width: 0.5, + tab_size: 4.0, } } } diff --git a/crates/epaint/src/text/mod.rs b/crates/epaint/src/text/mod.rs index b40ba45b8..7d37c0db6 100644 --- a/crates/epaint/src/text/mod.rs +++ b/crates/epaint/src/text/mod.rs @@ -6,9 +6,6 @@ mod fonts; mod text_layout; mod text_layout_types; -/// One `\t` character is this many spaces wide. -pub const TAB_SIZE: usize = 4; - pub use { fonts::{ FontData, FontDefinitions, FontFamily, FontId, FontInsert, FontPriority, FontTweak, Fonts, diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 0f3089292..75cbb5be0 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -8,7 +8,6 @@ use crate::{ Color32, Mesh, Stroke, Vertex, stroke::PathStroke, text::{ - TAB_SIZE, font::{StyledMetrics, UvRect, is_cjk, is_cjk_break_allowed}, fonts::FontFaceKey, }, @@ -250,11 +249,23 @@ fn layout_shaped_run( .unwrap_or('\u{FFFD}'); // Unicode Replacement Character // Tab is a layout concept, not a glyph — the shaper doesn't know about tab stops. - // Override the advance width to TAB_SIZE × space width. + // Override the advance width using the font's configured tab size. if chr == '\t' { + let tweak = font.fonts_by_id.get(&run.font_key).map(|ff| ff.tweak()); + let tab_size = tweak.map_or(4.0, |t| t.tab_size); let (_, space_info) = font.glyph_info(' '); let space_width_px = space_info.advance_width_unscaled.0 * px_scale; - advance_width_px = TAB_SIZE as f32 * space_width_px; + advance_width_px = tab_size * space_width_px; + } + + // Thin space (U+2009) and narrow no-break space (U+202F): + // override the shaper's advance width with the configured fraction of a space. + if chr == '\u{2009}' || chr == '\u{202F}' { + let tweak = font.fonts_by_id.get(&run.font_key).map(|ff| ff.tweak()); + let thin_space_width = tweak.map_or(0.5, |t| t.thin_space_width); + let (_, space_info) = font.glyph_info(' '); + let space_width_px = space_info.advance_width_unscaled.0 * px_scale; + advance_width_px = thin_space_width * space_width_px; } // Apply extra_letter_spacing only at cluster boundaries,