Draft metrics hinting support

This commit is contained in:
Héctor Ramón Jiménez 2025-11-23 06:22:24 +01:00 committed by Jeremy Soller
parent 9339446cfa
commit 48eda6bd7d
15 changed files with 41 additions and 22 deletions

View file

@ -9,7 +9,7 @@ fn load_font_system(c: &mut Criterion) {
fn layout(c: &mut Criterion) { fn layout(c: &mut Criterion) {
let mut fs = ct::FontSystem::new(); let mut fs = ct::FontSystem::new();
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(10.0, 10.0)); let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(10.0, 10.0), false);
buffer.set_size(&mut fs, Some(80.0), None); buffer.set_size(&mut fs, Some(80.0), None);
for (wrap_name, wrap) in &[ for (wrap_name, wrap) in &[

View file

@ -4,7 +4,7 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn bench_ascii_fast_path(c: &mut Criterion) { fn bench_ascii_fast_path(c: &mut Criterion) {
let mut fs = ct::FontSystem::new(); let mut fs = ct::FontSystem::new();
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0)); let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), false);
buffer.set_size(&mut fs, Some(500.0), None); buffer.set_size(&mut fs, Some(500.0), None);
let ascii_text = "Pure ASCII text for BidiParagraphs optimization testing.\n".repeat(50); 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) { fn bench_bidi_processing(c: &mut Criterion) {
let mut fs = ct::FontSystem::new(); let mut fs = ct::FontSystem::new();
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0)); let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), false);
buffer.set_size(&mut fs, Some(500.0), None); 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); 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) { fn bench_lang_mixed(c: &mut Criterion) {
let mut fs = ct::FontSystem::new(); let mut fs = ct::FontSystem::new();
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0)); let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), false);
buffer.set_size(&mut fs, Some(500.0), None); buffer.set_size(&mut fs, Some(500.0), None);
let bidi_text = include_str!("../sample/hello.txt"); 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) { fn bench_layout_heavy(c: &mut Criterion) {
let mut fs = ct::FontSystem::new(); let mut fs = ct::FontSystem::new();
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0)); let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), false);
buffer.set_size(&mut fs, Some(500.0), None); 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); 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) { fn bench_combined_stress(c: &mut Criterion) {
let mut fs = ct::FontSystem::new(); let mut fs = ct::FontSystem::new();
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0)); let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0), false);
buffer.set_size(&mut fs, Some(500.0), None); buffer.set_size(&mut fs, Some(500.0), None);
let stress_text = format!("{}\n{}\n{}\n{}\n", let stress_text = format!("{}\n{}\n{}\n{}\n",

View file

@ -71,7 +71,7 @@ fn main() {
]; ];
let font_size_default = 1; // Body let font_size_default = 1; // Body
let mut buffer = Buffer::new(&mut font_system, font_sizes[font_size_default]); let mut buffer = Buffer::new(&mut font_system, font_sizes[font_size_default], false);
buffer buffer
.borrow_with(&mut font_system) .borrow_with(&mut font_system)
.set_size(Some(window.width() as f32), Some(window.height() as f32)); .set_size(Some(window.width() as f32), Some(window.height() as f32));

View file

@ -45,6 +45,7 @@ fn main() {
Buffer::new( Buffer::new(
&mut font_system, &mut font_system,
font_sizes[font_size_i].scale(display_scale), font_sizes[font_size_i].scale(display_scale),
false,
), ),
&syntax_system, &syntax_system,
"base16-eighties.dark", "base16-eighties.dark",

View file

@ -25,7 +25,7 @@ fn main() {
let mut swash_cache = SwashCache::new(); let mut swash_cache = SwashCache::new();
let mut buffer = Buffer::new_empty(Metrics::new(14.0, 20.0)); let mut buffer = Buffer::new_empty(Metrics::new(14.0, 20.0), false);
let mut buffer = buffer.borrow_with(&mut font_system); let mut buffer = buffer.borrow_with(&mut font_system);

View file

@ -172,7 +172,7 @@ fn main() {
let mut display_scale = window.scale_factor() as f32; let mut display_scale = window.scale_factor() as f32;
let metrics = Metrics::new(32.0, 44.0); let metrics = Metrics::new(32.0, 44.0);
let mut editor = Editor::new(Buffer::new_empty(metrics.scale(display_scale))); let mut editor = Editor::new(Buffer::new_empty(metrics.scale(display_scale), false));
let mut editor = editor.borrow_with(&mut font_system); let mut editor = editor.borrow_with(&mut font_system);
editor.with_buffer_mut(|buffer| { editor.with_buffer_mut(|buffer| {
buffer.set_size( buffer.set_size(

View file

@ -20,7 +20,7 @@ fn main() {
let metrics = Metrics::new(FONT_SIZE, LINE_HEIGHT); let metrics = Metrics::new(FONT_SIZE, LINE_HEIGHT);
// A Buffer provides shaping and layout for a UTF-8 string, create one per text widget // 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); let mut buffer = Buffer::new(&mut font_system, metrics, false);
let mut buffer = buffer.borrow_with(&mut font_system); let mut buffer = buffer.borrow_with(&mut font_system);

View file

@ -216,6 +216,7 @@ pub struct Buffer {
wrap: Wrap, wrap: Wrap,
monospace_width: Option<f32>, monospace_width: Option<f32>,
tab_width: u16, tab_width: u16,
hint: bool,
} }
impl Clone for Buffer { impl Clone for Buffer {
@ -230,6 +231,7 @@ impl Clone for Buffer {
wrap: self.wrap, wrap: self.wrap,
monospace_width: self.monospace_width, monospace_width: self.monospace_width,
tab_width: self.tab_width, tab_width: self.tab_width,
hint: self.hint,
} }
} }
} }
@ -246,7 +248,7 @@ impl Buffer {
/// # Panics /// # Panics
/// ///
/// Will panic if `metrics.line_height` is zero. /// Will panic if `metrics.line_height` is zero.
pub fn new_empty(metrics: Metrics) -> Self { pub fn new_empty(metrics: Metrics, hint: bool) -> Self {
assert_ne!(metrics.line_height, 0.0, "line height cannot be 0"); assert_ne!(metrics.line_height, 0.0, "line height cannot be 0");
Self { Self {
lines: Vec::new(), lines: Vec::new(),
@ -258,6 +260,7 @@ impl Buffer {
wrap: Wrap::WordOrGlyph, wrap: Wrap::WordOrGlyph,
monospace_width: None, monospace_width: None,
tab_width: 8, tab_width: 8,
hint,
} }
} }
@ -266,8 +269,8 @@ impl Buffer {
/// # Panics /// # Panics
/// ///
/// Will panic if `metrics.line_height` is zero. /// Will panic if `metrics.line_height` is zero.
pub fn new(font_system: &mut FontSystem, metrics: Metrics) -> Self { pub fn new(font_system: &mut FontSystem, metrics: Metrics, hint: bool) -> Self {
let mut buffer = Self::new_empty(metrics); let mut buffer = Self::new_empty(metrics, hint);
buffer.set_text(font_system, "", &Attrs::new(), Shaping::Advanced, None); buffer.set_text(font_system, "", &Attrs::new(), Shaping::Advanced, None);
buffer buffer
} }
@ -297,6 +300,7 @@ impl Buffer {
self.wrap, self.wrap,
self.monospace_width, self.monospace_width,
self.tab_width, self.tab_width,
self.hint,
); );
} }
} }
@ -540,6 +544,7 @@ impl Buffer {
self.wrap, self.wrap,
self.monospace_width, self.monospace_width,
self.tab_width, self.tab_width,
self.hint,
)) ))
} }
@ -719,7 +724,7 @@ impl Buffer {
/// ``` /// ```
/// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping}; /// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping};
/// # let mut font_system = FontSystem::new(); /// # let mut font_system = FontSystem::new();
/// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0)); /// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0), false);
/// let attrs = Attrs::new().family(Family::Serif); /// let attrs = Attrs::new().family(Family::Serif);
/// buffer.set_rich_text( /// buffer.set_rich_text(
/// &mut font_system, /// &mut font_system,
@ -1448,7 +1453,7 @@ impl BorrowedWithFontSystem<'_, Buffer> {
/// ``` /// ```
/// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping}; /// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping};
/// # let mut font_system = FontSystem::new(); /// # let mut font_system = FontSystem::new();
/// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0)); /// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0), false);
/// let attrs = Attrs::new().family(Family::Serif); /// let attrs = Attrs::new().family(Family::Serif);
/// buffer.set_rich_text( /// buffer.set_rich_text(
/// &mut font_system, /// &mut font_system,

View file

@ -243,6 +243,7 @@ impl BufferLine {
wrap: Wrap, wrap: Wrap,
match_mono_width: Option<f32>, match_mono_width: Option<f32>,
tab_width: u16, tab_width: u16,
hint: bool,
) -> &[LayoutLine] { ) -> &[LayoutLine] {
if self.layout_opt.is_unused() { if self.layout_opt.is_unused() {
let align = self.align; let align = self.align;
@ -259,6 +260,7 @@ impl BufferLine {
align, align,
&mut layout, &mut layout,
match_mono_width, match_mono_width,
hint,
); );
self.layout_opt.set_used(layout); self.layout_opt.set_used(layout);
} }

View file

@ -24,7 +24,7 @@
//! let metrics = Metrics::new(14.0, 20.0); //! let metrics = Metrics::new(14.0, 20.0);
//! //!
//! // A Buffer provides shaping and layout for a UTF-8 string, create one per text widget //! // 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); //! let mut buffer = Buffer::new(&mut font_system, metrics, false);
//! //!
//! // Borrow buffer together with the font system for more convenient method calls //! // Borrow buffer together with the font system for more convenient method calls
//! let mut buffer = buffer.borrow_with(&mut font_system); //! let mut buffer = buffer.borrow_with(&mut font_system);

View file

@ -1152,6 +1152,7 @@ impl ShapeLine {
wrap: Wrap, wrap: Wrap,
align: Option<Align>, align: Option<Align>,
match_mono_width: Option<f32>, match_mono_width: Option<f32>,
hint: bool,
) -> Vec<LayoutLine> { ) -> Vec<LayoutLine> {
let mut lines = Vec::with_capacity(1); let mut lines = Vec::with_capacity(1);
self.layout_to_buffer( self.layout_to_buffer(
@ -1162,6 +1163,7 @@ impl ShapeLine {
align, align,
&mut lines, &mut lines,
match_mono_width, match_mono_width,
hint,
); );
lines lines
} }
@ -1175,6 +1177,7 @@ impl ShapeLine {
align: Option<Align>, align: Option<Align>,
layout_lines: &mut Vec<LayoutLine>, layout_lines: &mut Vec<LayoutLine>,
match_mono_width: Option<f32>, match_mono_width: Option<f32>,
hint: bool,
) { ) {
fn add_to_visual_line( fn add_to_visual_line(
vl: &mut VisualLine, vl: &mut VisualLine,
@ -1550,6 +1553,10 @@ impl ShapeLine {
x += alignment_correction; x += alignment_correction;
} }
if hint {
x = x.round();
}
// TODO: Only certain `is_whitespace` chars are typically expanded but this is what is // TODO: Only certain `is_whitespace` chars are typically expanded but this is what is
// currently used to compute `visual_line.spaces`. // currently used to compute `visual_line.spaces`.
// //
@ -1628,6 +1635,9 @@ impl ShapeLine {
// Round to nearest monospace width // Round to nearest monospace width
x_advance = ((x_advance / match_em_width).round()) * match_em_width; x_advance = ((x_advance / match_em_width).round()) * match_em_width;
} }
if hint {
x_advance = x_advance.round();
}
if self.rtl { if self.rtl {
x -= x_advance; x -= x_advance;
} }

View file

@ -85,7 +85,7 @@ impl DrawTestCfg {
let mut font_system = FontSystem::new_with_locale_and_db("En-US".into(), font_db); let mut font_system = FontSystem::new_with_locale_and_db("En-US".into(), font_db);
let mut swash_cache = SwashCache::new(); let mut swash_cache = SwashCache::new();
let metrics = Metrics::new(self.font_size, self.line_height); let metrics = Metrics::new(self.font_size, self.line_height);
let mut buffer = Buffer::new(&mut font_system, metrics); let mut buffer = Buffer::new(&mut font_system, metrics, false);
let mut buffer = buffer.borrow_with(&mut font_system); let mut buffer = buffer.borrow_with(&mut font_system);
let margins = 5; let margins = 5;
buffer.set_size( buffer.set_size(

View file

@ -13,7 +13,7 @@ fn editor() -> ViEditor<'static, 'static> {
let line_height = (font_size * 1.4).ceil(); let line_height = (font_size * 1.4).ceil();
let metrics = Metrics::new(font_size, line_height); let metrics = Metrics::new(font_size, line_height);
let buffer = Buffer::new_empty(metrics); let buffer = Buffer::new_empty(metrics, false);
let editor = SyntaxEditor::new( let editor = SyntaxEditor::new(
buffer, buffer,
SYNTAX_SYSTEM.get_or_init(SyntaxSystem::new), SYNTAX_SYSTEM.get_or_init(SyntaxSystem::new),

View file

@ -23,14 +23,15 @@ fn stable_wrap() {
let mut check_wrap = |text: &_, wrap, align_opt, start_width_opt| { let mut check_wrap = |text: &_, wrap, align_opt, start_width_opt| {
let line = ShapeLine::new(&mut font_system, text, &attrs, Shaping::Advanced, 8); 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); let layout_unbounded =
line.layout(font_size, start_width_opt, wrap, align_opt, None, false);
let max_width = layout_unbounded.iter().map(|l| l.w).fold(0.0, f32::max); let max_width = layout_unbounded.iter().map(|l| l.w).fold(0.0, f32::max);
let new_limit = match start_width_opt { let new_limit = match start_width_opt {
Some(start_width) => f32::min(start_width, max_width), Some(start_width) => f32::min(start_width, max_width),
None => max_width, None => max_width,
}; };
let layout_bounded = line.layout(font_size, Some(new_limit), wrap, align_opt, None); let layout_bounded = line.layout(font_size, Some(new_limit), wrap, align_opt, None, false);
let bounded_max_width = layout_bounded.iter().map(|l| l.w).fold(0.0, f32::max); let bounded_max_width = layout_bounded.iter().map(|l| l.w).fold(0.0, f32::max);
// For debugging: // For debugging:
@ -99,7 +100,7 @@ fn wrap_extra_line() {
let mut font_system = FontSystem::new(); let mut font_system = FontSystem::new();
let metrics = Metrics::new(14.0, 20.0); let metrics = Metrics::new(14.0, 20.0);
let mut buffer = Buffer::new(&mut font_system, metrics); let mut buffer = Buffer::new(&mut font_system, metrics, false);
let mut buffer = buffer.borrow_with(&mut font_system); let mut buffer = buffer.borrow_with(&mut font_system);

View file

@ -10,7 +10,7 @@ fn wrap_word_fallback() {
font_system.db_mut().load_font_data(font); font_system.db_mut().load_font_data(font);
let metrics = Metrics::new(14.0, 20.0); let metrics = Metrics::new(14.0, 20.0);
let mut buffer = Buffer::new(&mut font_system, metrics); let mut buffer = Buffer::new(&mut font_system, metrics, false);
let mut buffer = buffer.borrow_with(&mut font_system); let mut buffer = buffer.borrow_with(&mut font_system);