Compare commits
2 commits
main
...
local/pr-5
| Author | SHA1 | Date | |
|---|---|---|---|
| 63072bbe29 | |||
| 0e15681adb |
2 changed files with 47 additions and 3 deletions
|
|
@ -30,6 +30,8 @@ sys-locale = { version = "0.3.2", optional = true }
|
||||||
unicode-linebreak = "0.1.5"
|
unicode-linebreak = "0.1.5"
|
||||||
unicode-script = "0.5.8"
|
unicode-script = "0.5.8"
|
||||||
unicode-segmentation = "1.12.0"
|
unicode-segmentation = "1.12.0"
|
||||||
|
# Yoda: EAW-aware cell width for monospace/terminal rendering.
|
||||||
|
unicode-width = "0.2"
|
||||||
|
|
||||||
[dependencies.swash]
|
[dependencies.swash]
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
|
|
|
||||||
48
src/shape.rs
48
src/shape.rs
|
|
@ -237,6 +237,8 @@ fn shape_fallback(
|
||||||
metadata: attrs.metadata,
|
metadata: attrs.metadata,
|
||||||
cache_key_flags: override_fake_italic(attrs.cache_key_flags, font, &attrs),
|
cache_key_flags: override_fake_italic(attrs.cache_key_flags, font, &attrs),
|
||||||
metrics_opt: attrs.metrics_opt.map(Into::into),
|
metrics_opt: attrs.metrics_opt.map(Into::into),
|
||||||
|
// Set by the post-loop pass below once `end` is finalized.
|
||||||
|
terminal_cells: 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,6 +267,19 @@ fn shape_fallback(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Yoda: compute EAW terminal width per glyph now that `end` is finalized.
|
||||||
|
// We use the cluster's text (byte range into `line`) rather than just the
|
||||||
|
// first char so ZWJ emoji sequences collapse to a single cluster width.
|
||||||
|
// `unicode-width` returns 0 for combining marks / variation selectors, 2
|
||||||
|
// for CJK & most emoji, 1 for everything else including Arabic.
|
||||||
|
{
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
for glyph in &mut glyphs[glyph_start..] {
|
||||||
|
let cluster = line.get(glyph.start..glyph.end).unwrap_or("");
|
||||||
|
glyph.terminal_cells = UnicodeWidthStr::width(cluster).min(2) as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Restore the buffer to save an allocation.
|
// Restore the buffer to save an allocation.
|
||||||
scratch.harfrust_buffer = Some(glyph_buffer.clear());
|
scratch.harfrust_buffer = Some(glyph_buffer.clear());
|
||||||
|
|
||||||
|
|
@ -590,6 +605,12 @@ fn shape_skip_glyphs(
|
||||||
.letter_spacing_opt
|
.letter_spacing_opt
|
||||||
.map_or(0.0, |spacing| spacing.0);
|
.map_or(0.0, |spacing| spacing.0);
|
||||||
let attrs = attrs_list.get_span(start_run + chr_idx);
|
let attrs = attrs_list.get_span(start_run + chr_idx);
|
||||||
|
// Yoda: EAW width for terminal-aware monospace snap (see
|
||||||
|
// ShapeGlyph.terminal_cells doc).
|
||||||
|
let terminal_cells = {
|
||||||
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
UnicodeWidthChar::width(codepoint).unwrap_or(1).min(2) as u8
|
||||||
|
};
|
||||||
|
|
||||||
ShapeGlyph {
|
ShapeGlyph {
|
||||||
start: chr_idx + start_run,
|
start: chr_idx + start_run,
|
||||||
|
|
@ -608,6 +629,7 @@ fn shape_skip_glyphs(
|
||||||
metadata: attrs.metadata,
|
metadata: attrs.metadata,
|
||||||
cache_key_flags: override_fake_italic(attrs.cache_key_flags, font, &attrs),
|
cache_key_flags: override_fake_italic(attrs.cache_key_flags, font, &attrs),
|
||||||
metrics_opt: attrs.metrics_opt.map(Into::into),
|
metrics_opt: attrs.metrics_opt.map(Into::into),
|
||||||
|
terminal_cells,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -644,6 +666,14 @@ pub struct ShapeGlyph {
|
||||||
pub metadata: usize,
|
pub metadata: usize,
|
||||||
pub cache_key_flags: CacheKeyFlags,
|
pub cache_key_flags: CacheKeyFlags,
|
||||||
pub metrics_opt: Option<Metrics>,
|
pub metrics_opt: Option<Metrics>,
|
||||||
|
/// Yoda: Unicode East Asian Width of the source text covered by this glyph,
|
||||||
|
/// in terminal cells (0, 1 or 2 per `unicode_width::UnicodeWidthStr::width`).
|
||||||
|
/// Populated at shape time from `line[start..end]`. Consumed by
|
||||||
|
/// `layout_to_buffer` when `match_mono_width` is Some — wide chars (emoji,
|
||||||
|
/// CJK, fullwidth forms) snap to 2 cells instead of rounding based on the
|
||||||
|
/// fallback font's natural glyph advance, which was the upstream bug
|
||||||
|
/// breaking terminal column alignment.
|
||||||
|
pub terminal_cells: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapeGlyph {
|
impl ShapeGlyph {
|
||||||
|
|
@ -2903,9 +2933,21 @@ impl ShapeLine {
|
||||||
0.0
|
0.0
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if let Some(match_em_width) = match_mono_em_width {
|
if let Some(mono_width) = match_mono_width {
|
||||||
// Round to nearest monospace width
|
// Yoda: use Unicode East Asian Width, stored at
|
||||||
x_advance = ((x_advance / match_em_width).round()) * match_em_width;
|
// shape time, as the authoritative cell count.
|
||||||
|
// `terminal_cells` is 0 for combining marks /
|
||||||
|
// variation selectors (they stay 0-advance),
|
||||||
|
// 2 for CJK & emoji clusters, 1 for everything
|
||||||
|
// else (including Arabic, Latin, etc.).
|
||||||
|
//
|
||||||
|
// This replaces the previous round-to-nearest
|
||||||
|
// logic which produced variable cell widths
|
||||||
|
// for fallback glyphs because it depended on
|
||||||
|
// the glyph's natural advance in the fallback
|
||||||
|
// font rather than on the source character's
|
||||||
|
// terminal width spec.
|
||||||
|
x_advance = f32::from(glyph.terminal_cells) * mono_width;
|
||||||
}
|
}
|
||||||
if hinting == Hinting::Enabled {
|
if hinting == Hinting::Enabled {
|
||||||
x_advance = x_advance.round();
|
x_advance = x_advance.round();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue