From d779057d9cbf5300f3cd3656a2a60be8bf710c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 23 Nov 2025 20:43:33 +0100 Subject: [PATCH] Replace magic boolean with new `Hinting` enum --- benches/layout.rs | 2 +- benches/text_shaping_benchmarks.rs | 10 +++++----- examples/editor-test/src/main.rs | 8 ++++++-- examples/editor/src/main.rs | 6 +++--- examples/multiview/src/main.rs | 4 ++-- examples/rich-text/src/main.rs | 7 +++++-- examples/terminal/src/main.rs | 4 ++-- src/buffer.rs | 18 +++++++++--------- src/buffer_line.rs | 5 +++-- src/layout.rs | 25 +++++++++++++++++++++++++ src/lib.rs | 4 ++-- src/shape.rs | 14 +++++++------- tests/common/mod.rs | 6 +++--- tests/editor_modified_state.rs | 4 ++-- tests/wrap_stability.rs | 25 +++++++++++++++++++------ tests/wrap_word_fallback.rs | 4 ++-- 16 files changed, 96 insertions(+), 50 deletions(-) diff --git a/benches/layout.rs b/benches/layout.rs index 2d23b8d..3600d69 100644 --- a/benches/layout.rs +++ b/benches/layout.rs @@ -9,7 +9,7 @@ fn load_font_system(c: &mut Criterion) { fn layout(c: &mut Criterion) { let mut fs = ct::FontSystem::new(); - let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(10.0, 10.0), false); + let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(10.0, 10.0), ct::Hinting::Disabled); buffer.set_size(&mut fs, Some(80.0), None); for (wrap_name, wrap) in &[ diff --git a/benches/text_shaping_benchmarks.rs b/benches/text_shaping_benchmarks.rs index 38bbc6e..6f06192 100644 --- a/benches/text_shaping_benchmarks.rs +++ b/benches/text_shaping_benchmarks.rs @@ -4,7 +4,7 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; fn bench_ascii_fast_path(c: &mut Criterion) { let mut fs = ct::FontSystem::new(); - let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), false); + let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), ct::Hinting::Disabled); buffer.set_size(&mut fs, Some(500.0), None); let ascii_text = "Pure ASCII text for BidiParagraphs optimization testing.\n".repeat(50); @@ -25,7 +25,7 @@ fn bench_ascii_fast_path(c: &mut Criterion) { fn bench_bidi_processing(c: &mut Criterion) { let mut fs = ct::FontSystem::new(); - let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), false); + let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), ct::Hinting::Disabled); buffer.set_size(&mut fs, Some(500.0), None); let bidi_text = "Mixed English and العربية النص العربي text for BiDi testing.\nThis tests adjust_levels and combined BiDi optimizations.\n".repeat(30); @@ -46,7 +46,7 @@ fn bench_bidi_processing(c: &mut Criterion) { fn bench_lang_mixed(c: &mut Criterion) { let mut fs = ct::FontSystem::new(); - let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), false); + let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), ct::Hinting::Disabled); buffer.set_size(&mut fs, Some(500.0), None); let bidi_text = include_str!("../sample/hello.txt"); @@ -69,7 +69,7 @@ fn bench_lang_mixed(c: &mut Criterion) { fn bench_layout_heavy(c: &mut Criterion) { let mut fs = ct::FontSystem::new(); - let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), false); + let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), ct::Hinting::Disabled); buffer.set_size(&mut fs, Some(500.0), None); let layout_text = "This is a very long line that will wrap multiple times and stress the reorder optimization through intensive layout processing with comprehensive buffer reuse testing. ".repeat(30); @@ -90,7 +90,7 @@ fn bench_layout_heavy(c: &mut Criterion) { fn bench_combined_stress(c: &mut Criterion) { let mut fs = ct::FontSystem::new(); - let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), false); + let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), ct::Hinting::Disabled); buffer.set_size(&mut fs, Some(500.0), None); let stress_text = format!("{}\n{}\n{}\n{}\n", diff --git a/examples/editor-test/src/main.rs b/examples/editor-test/src/main.rs index 4ce62ec..45f78ee 100644 --- a/examples/editor-test/src/main.rs +++ b/examples/editor-test/src/main.rs @@ -2,7 +2,7 @@ use cosmic_text::{ Action, BidiParagraphs, BorrowedWithFontSystem, Buffer, Color, Edit, Editor, FontSystem, - Metrics, Motion, SwashCache, + Hinting, Metrics, Motion, SwashCache, }; use orbclient::{EventOption, Renderer, Window, WindowFlag}; use std::{env, fs, process, time::Instant}; @@ -71,7 +71,11 @@ fn main() { ]; let font_size_default = 1; // Body - let mut buffer = Buffer::new(&mut font_system, font_sizes[font_size_default], false); + let mut buffer = Buffer::new( + &mut font_system, + font_sizes[font_size_default], + Hinting::Disabled, + ); buffer .borrow_with(&mut font_system) .set_size(Some(window.width() as f32), Some(window.height() as f32)); diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index b429d1b..193b085 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use cosmic_text::{ - Action, Attrs, Buffer, Edit, Family, FontSystem, Metrics, Motion, SwashCache, SyntaxEditor, - SyntaxSystem, + Action, Attrs, Buffer, Edit, Family, FontSystem, Hinting, Metrics, Motion, SwashCache, + SyntaxEditor, SyntaxSystem, }; use std::{env, fs, num::NonZeroU32, rc::Rc, slice}; use tiny_skia::{Paint, PixmapMut, Rect, Transform}; @@ -45,7 +45,7 @@ fn main() { Buffer::new( &mut font_system, font_sizes[font_size_i].scale(display_scale), - false, + Hinting::Disabled, ), &syntax_system, "base16-eighties.dark", diff --git a/examples/multiview/src/main.rs b/examples/multiview/src/main.rs index fdab7f8..677bb56 100644 --- a/examples/multiview/src/main.rs +++ b/examples/multiview/src/main.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use cosmic_text::{ - Action, Attrs, Buffer, Edit, Family, FontSystem, Metrics, Scroll, Shaping, SwashCache, + Action, Attrs, Buffer, Edit, Family, FontSystem, Hinting, Metrics, Scroll, Shaping, SwashCache, }; use std::{collections::HashMap, env, fs, num::NonZeroU32, rc::Rc, slice}; use tiny_skia::{Color, Paint, PixmapMut, Rect, Transform}; @@ -25,7 +25,7 @@ fn main() { let mut swash_cache = SwashCache::new(); - let mut buffer = Buffer::new_empty(Metrics::new(14.0, 20.0), false); + let mut buffer = Buffer::new_empty(Metrics::new(14.0, 20.0), Hinting::Disabled); let mut buffer = buffer.borrow_with(&mut font_system); diff --git a/examples/rich-text/src/main.rs b/examples/rich-text/src/main.rs index d185978..83181d5 100644 --- a/examples/rich-text/src/main.rs +++ b/examples/rich-text/src/main.rs @@ -7,7 +7,7 @@ use cosmic_text::Editor; use cosmic_text::Shaping; use cosmic_text::Style; use cosmic_text::{ - Action, Attrs, Buffer, Edit, Family, FontSystem, Metrics, Motion, SwashCache, Weight, + Action, Attrs, Buffer, Edit, Family, FontSystem, Hinting, Metrics, Motion, SwashCache, Weight, }; use std::{num::NonZeroU32, rc::Rc, slice}; use tiny_skia::{Paint, PixmapMut, Rect, Transform}; @@ -172,7 +172,10 @@ fn main() { let mut display_scale = window.scale_factor() as f32; let metrics = Metrics::new(32.0, 44.0); - let mut editor = Editor::new(Buffer::new_empty(metrics.scale(display_scale), false)); + let mut editor = Editor::new(Buffer::new_empty( + metrics.scale(display_scale), + Hinting::Disabled, + )); let mut editor = editor.borrow_with(&mut font_system); editor.with_buffer_mut(|buffer| { buffer.set_size( diff --git a/examples/terminal/src/main.rs b/examples/terminal/src/main.rs index 4251a45..0bb7d2c 100644 --- a/examples/terminal/src/main.rs +++ b/examples/terminal/src/main.rs @@ -4,7 +4,7 @@ //! or `cargo run --package terminal -- "my own text"` use colored::Colorize; -use cosmic_text::{Attrs, Buffer, Color, FontSystem, Metrics, Shaping, SwashCache}; +use cosmic_text::{Attrs, Buffer, Color, FontSystem, Hinting, Metrics, Shaping, SwashCache}; use std::fmt::Write; fn main() { @@ -20,7 +20,7 @@ fn main() { let metrics = Metrics::new(FONT_SIZE, LINE_HEIGHT); // A Buffer provides shaping and layout for a UTF-8 string, create one per text widget - let mut buffer = Buffer::new(&mut font_system, metrics, false); + let mut buffer = Buffer::new(&mut font_system, metrics, Hinting::Disabled); let mut buffer = buffer.borrow_with(&mut font_system); diff --git a/src/buffer.rs b/src/buffer.rs index a82339a..031eec0 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -11,8 +11,8 @@ use unicode_segmentation::UnicodeSegmentation; use crate::{ Affinity, Align, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Color, - Cursor, FontSystem, LayoutCursor, LayoutGlyph, LayoutLine, LineEnding, LineIter, Motion, - Renderer, Scroll, ShapeLine, Shaping, Wrap, + Cursor, FontSystem, Hinting, LayoutCursor, LayoutGlyph, LayoutLine, LineEnding, LineIter, + Motion, Renderer, Scroll, ShapeLine, Shaping, Wrap, }; /// A line of visible text for rendering @@ -216,7 +216,7 @@ pub struct Buffer { wrap: Wrap, monospace_width: Option, tab_width: u16, - hint: bool, + hint: Hinting, } impl Clone for Buffer { @@ -248,7 +248,7 @@ impl Buffer { /// # Panics /// /// Will panic if `metrics.line_height` is zero. - pub fn new_empty(metrics: Metrics, hint: bool) -> Self { + pub fn new_empty(metrics: Metrics, hint: Hinting) -> Self { assert_ne!(metrics.line_height, 0.0, "line height cannot be 0"); Self { lines: Vec::new(), @@ -269,7 +269,7 @@ impl Buffer { /// # Panics /// /// Will panic if `metrics.line_height` is zero. - pub fn new(font_system: &mut FontSystem, metrics: Metrics, hint: bool) -> Self { + pub fn new(font_system: &mut FontSystem, metrics: Metrics, hint: Hinting) -> Self { let mut buffer = Self::new_empty(metrics, hint); buffer.set_text(font_system, "", &Attrs::new(), Shaping::Advanced, None); buffer @@ -722,9 +722,9 @@ impl Buffer { /// Set text of buffer, using an iterator of styled spans (pairs of text and attributes) /// /// ``` - /// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping}; + /// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Hinting, Shaping}; /// # let mut font_system = FontSystem::new(); - /// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0), false); + /// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0), Hinting::Disabled); /// let attrs = Attrs::new().family(Family::Serif); /// buffer.set_rich_text( /// &mut font_system, @@ -1451,9 +1451,9 @@ impl BorrowedWithFontSystem<'_, Buffer> { /// Set text of buffer, using an iterator of styled spans (pairs of text and attributes) /// /// ``` - /// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping}; + /// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Hinting, Shaping}; /// # let mut font_system = FontSystem::new(); - /// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0), false); + /// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0), Hinting::Disabled); /// let attrs = Attrs::new().family(Family::Serif); /// buffer.set_rich_text( /// &mut font_system, diff --git a/src/buffer_line.rs b/src/buffer_line.rs index 37d21d9..c6ed194 100644 --- a/src/buffer_line.rs +++ b/src/buffer_line.rs @@ -3,7 +3,8 @@ use alloc::{string::String, vec::Vec}; use core::mem; use crate::{ - Align, Attrs, AttrsList, Cached, FontSystem, LayoutLine, LineEnding, ShapeLine, Shaping, Wrap, + Align, Attrs, AttrsList, Cached, FontSystem, Hinting, LayoutLine, LineEnding, ShapeLine, + Shaping, Wrap, }; /// A line (or paragraph) of text that is shaped and laid out @@ -243,7 +244,7 @@ impl BufferLine { wrap: Wrap, match_mono_width: Option, tab_width: u16, - hint: bool, + hint: Hinting, ) -> &[LayoutLine] { if self.layout_opt.is_unused() { let align = self.align; diff --git a/src/layout.rs b/src/layout.rs index 0d45703..03878f1 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -151,3 +151,28 @@ impl Display for Align { } } } + +/// Metrics hinting strategy +#[derive(Debug, Eq, PartialEq, Clone, Copy, Default)] +pub enum Hinting { + /// No metrics hinting. + /// + /// Glyphs will have subpixel coordinates. + /// + /// This is the default. + #[default] + Disabled, + + /// Metrics hinting. + /// + /// Glyphs will be snapped to integral coordinates in the X-axis during layout. + /// This can improve readability for smaller text and/or low-DPI screens. + /// + /// However, in order to get the right effect, you must use physical coordinates + /// during layout and avoid further scaling when rendering. Otherwise, the rounding + /// errors can accumulate and glyph distances may look erratic. + /// + /// In other words, metrics hinting makes layouting dependent of the target + /// resolution. + Enabled, +} diff --git a/src/lib.rs b/src/lib.rs index 682e779..f539e64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ //! point, you can use the `SwashCache` to rasterize glyphs into either images or pixels. //! //! ``` -//! use cosmic_text::{Attrs, Color, FontSystem, SwashCache, Buffer, Metrics, Shaping}; +//! use cosmic_text::{Attrs, Color, FontSystem, SwashCache, Buffer, Metrics, Hinting, Shaping}; //! //! // A FontSystem provides access to detected system fonts, create one per application //! let mut font_system = FontSystem::new(); @@ -24,7 +24,7 @@ //! let metrics = Metrics::new(14.0, 20.0); //! //! // A Buffer provides shaping and layout for a UTF-8 string, create one per text widget -//! let mut buffer = Buffer::new(&mut font_system, metrics, false); +//! let mut buffer = Buffer::new(&mut font_system, metrics, Hinting::Disabled); //! //! // Borrow buffer together with the font system for more convenient method calls //! let mut buffer = buffer.borrow_with(&mut font_system); diff --git a/src/shape.rs b/src/shape.rs index 0399964..0dbffdd 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -4,8 +4,8 @@ use crate::fallback::FontFallbackIter; use crate::{ - math, Align, AttrsList, CacheKeyFlags, Color, Font, FontSystem, LayoutGlyph, LayoutLine, - Metrics, Wrap, + math, Align, AttrsList, CacheKeyFlags, Color, Font, FontSystem, Hinting, LayoutGlyph, + LayoutLine, Metrics, Wrap, }; #[cfg(not(feature = "std"))] use alloc::vec::Vec; @@ -1152,7 +1152,7 @@ impl ShapeLine { wrap: Wrap, align: Option, match_mono_width: Option, - hint: bool, + hinting: Hinting, ) -> Vec { let mut lines = Vec::with_capacity(1); self.layout_to_buffer( @@ -1163,7 +1163,7 @@ impl ShapeLine { align, &mut lines, match_mono_width, - hint, + hinting, ); lines } @@ -1177,7 +1177,7 @@ impl ShapeLine { align: Option, layout_lines: &mut Vec, match_mono_width: Option, - hint: bool, + hinting: Hinting, ) { fn add_to_visual_line( vl: &mut VisualLine, @@ -1553,7 +1553,7 @@ impl ShapeLine { x += alignment_correction; } - if hint { + if hinting == Hinting::Enabled { x = x.round(); } @@ -1635,7 +1635,7 @@ impl ShapeLine { // Round to nearest monospace width x_advance = ((x_advance / match_em_width).round()) * match_em_width; } - if hint { + if hinting == Hinting::Enabled { x_advance = x_advance.round(); } if self.rtl { diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 3181fdb..e5fcf71 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,8 +1,8 @@ use std::path::PathBuf; use cosmic_text::{ - fontdb::Database, Attrs, AttrsOwned, Buffer, Color, Family, FontSystem, Metrics, Shaping, - SwashCache, + fontdb::Database, Attrs, AttrsOwned, Buffer, Color, Family, FontSystem, Hinting, Metrics, + Shaping, SwashCache, }; use tiny_skia::{Paint, Pixmap, Rect, Transform}; @@ -85,7 +85,7 @@ impl DrawTestCfg { let mut font_system = FontSystem::new_with_locale_and_db("En-US".into(), font_db); let mut swash_cache = SwashCache::new(); let metrics = Metrics::new(self.font_size, self.line_height); - let mut buffer = Buffer::new(&mut font_system, metrics, false); + let mut buffer = Buffer::new(&mut font_system, metrics, Hinting::Disabled); let mut buffer = buffer.borrow_with(&mut font_system); let margins = 5; buffer.set_size( diff --git a/tests/editor_modified_state.rs b/tests/editor_modified_state.rs index d8be8d2..a45bc78 100644 --- a/tests/editor_modified_state.rs +++ b/tests/editor_modified_state.rs @@ -2,7 +2,7 @@ use std::sync::OnceLock; -use cosmic_text::{Buffer, Cursor, Edit, Metrics, SyntaxEditor, SyntaxSystem, ViEditor}; +use cosmic_text::{Buffer, Cursor, Edit, Hinting, Metrics, SyntaxEditor, SyntaxSystem, ViEditor}; static SYNTAX_SYSTEM: OnceLock = OnceLock::new(); @@ -13,7 +13,7 @@ fn editor() -> ViEditor<'static, 'static> { let line_height = (font_size * 1.4).ceil(); let metrics = Metrics::new(font_size, line_height); - let buffer = Buffer::new_empty(metrics, false); + let buffer = Buffer::new_empty(metrics, Hinting::Disabled); let editor = SyntaxEditor::new( buffer, SYNTAX_SYSTEM.get_or_init(SyntaxSystem::new), diff --git a/tests/wrap_stability.rs b/tests/wrap_stability.rs index aed22a0..f785805 100644 --- a/tests/wrap_stability.rs +++ b/tests/wrap_stability.rs @@ -1,6 +1,6 @@ use cosmic_text::{ - fontdb, Align, Attrs, AttrsList, BidiParagraphs, Buffer, Family, FontSystem, LayoutLine, - Metrics, ShapeLine, Shaping, Weight, Wrap, + fontdb, Align, Attrs, AttrsList, BidiParagraphs, Buffer, Family, FontSystem, Hinting, + LayoutLine, Metrics, ShapeLine, Shaping, Weight, Wrap, }; // Test for https://github.com/pop-os/cosmic-text/issues/134 @@ -23,15 +23,28 @@ fn stable_wrap() { let mut check_wrap = |text: &_, wrap, align_opt, start_width_opt| { let line = ShapeLine::new(&mut font_system, text, &attrs, Shaping::Advanced, 8); - let layout_unbounded = - line.layout(font_size, start_width_opt, wrap, align_opt, None, false); + let layout_unbounded = line.layout( + font_size, + start_width_opt, + wrap, + align_opt, + None, + Hinting::Disabled, + ); let max_width = layout_unbounded.iter().map(|l| l.w).fold(0.0, f32::max); let new_limit = match start_width_opt { Some(start_width) => f32::min(start_width, max_width), None => max_width, }; - let layout_bounded = line.layout(font_size, Some(new_limit), wrap, align_opt, None, false); + let layout_bounded = line.layout( + font_size, + Some(new_limit), + wrap, + align_opt, + None, + Hinting::Disabled, + ); let bounded_max_width = layout_bounded.iter().map(|l| l.w).fold(0.0, f32::max); // For debugging: @@ -100,7 +113,7 @@ fn wrap_extra_line() { let mut font_system = FontSystem::new(); let metrics = Metrics::new(14.0, 20.0); - let mut buffer = Buffer::new(&mut font_system, metrics, false); + let mut buffer = Buffer::new(&mut font_system, metrics, Hinting::Disabled); let mut buffer = buffer.borrow_with(&mut font_system); diff --git a/tests/wrap_word_fallback.rs b/tests/wrap_word_fallback.rs index 8493bf9..f560025 100644 --- a/tests/wrap_word_fallback.rs +++ b/tests/wrap_word_fallback.rs @@ -1,4 +1,4 @@ -use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping, Wrap}; +use cosmic_text::{Attrs, Buffer, FontSystem, Hinting, Metrics, Shaping, Wrap}; // Tests the ability to fallback to glyph wrapping when a word can't fit on a line by itself. // No line should ever overflow the buffer size. @@ -10,7 +10,7 @@ fn wrap_word_fallback() { font_system.db_mut().load_font_data(font); let metrics = Metrics::new(14.0, 20.0); - let mut buffer = Buffer::new(&mut font_system, metrics, false); + let mut buffer = Buffer::new(&mut font_system, metrics, Hinting::Disabled); let mut buffer = buffer.borrow_with(&mut font_system);