mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 22:22:07 +00:00
Merge pull request #5628 from dotCooCoo/fix/rtl-kerning-whole-point-widths
Type: right-to-left manual kerning keeps whole-point spacing on both sides
This commit is contained in:
@@ -1,14 +1,24 @@
|
||||
use super::flow::flow;
|
||||
use super::region::Region;
|
||||
use super::{Affine, Ctx, Punct, Range, Rect, SGlyph, Tcy, TextLayout, TextObject, VerticalAlign, is_cjk, punct};
|
||||
use super::{Affine, Ctx, Punct, Range, Rect, SGlyph, Tcy, TextLayout, TextObject, VerticalAlign, continues_cluster, is_cjk, punct};
|
||||
|
||||
/// System Layout on or Fractional Widths off: each character that takes space advances by a whole
|
||||
/// number of points, at least one, so small type does not collapse onto itself. A zero advance (a
|
||||
/// tate-chu-yoko continuation, a zero-width mark) stays zero.
|
||||
/// tate-chu-yoko continuation, a zero-width mark) stays zero. Right to left, a cluster with manual
|
||||
/// kerning moves by the difference the kerning makes to its rounded advance, so the rounding
|
||||
/// changes the space before the next character in reading order and not the space after the one
|
||||
/// before it.
|
||||
pub(super) fn whole_character_widths(glyphs: &mut [SGlyph]) {
|
||||
for g in glyphs {
|
||||
if g.adv.is_finite() && g.adv > 0.0 {
|
||||
g.adv = g.adv.round().max(1.0);
|
||||
let whole = |adv: f64| if adv.is_finite() && adv > 0.0 { adv.round().max(1.0) } else { adv };
|
||||
for cluster in glyphs.chunk_by_mut(|a, b| continues_cluster(Some(a), Some(b))) {
|
||||
// The cluster's last glyph carries the manual kerning in its advance.
|
||||
let (adv, shift) = cluster.last().map_or((0.0, 0.0), |g| (g.adv, g.kern_shift));
|
||||
let rounded_shift = Some(whole(adv) - whole(adv - shift)).filter(|s| s.is_finite()).unwrap_or(shift);
|
||||
for g in cluster {
|
||||
g.adv = whole(g.adv);
|
||||
if shift != 0.0 {
|
||||
g.kern_shift = rounded_shift;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ pub(crate) struct SGlyph {
|
||||
pub dx: f64,
|
||||
pub dy: f64,
|
||||
/// Right to left: the run's manual kerning, which moves the glyph right along the baseline,
|
||||
/// outside its Character Rotation. 0 otherwise.
|
||||
/// outside its Character Rotation; with whole-point advances, the difference the kerning makes
|
||||
/// to the cluster's rounded advance. 0 otherwise.
|
||||
pub kern_shift: f64,
|
||||
/// Outline scale (font units -> points).
|
||||
pub sx: f64,
|
||||
|
||||
@@ -98,6 +98,36 @@ fn rtl_manual_kerning_spaces_the_character_from_the_next_in_reading_order() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rtl_manual_kerning_with_whole_point_widths_keeps_the_previous_character_in_place() {
|
||||
// Reading order: alef (bytes 0..2), bet (2..4), gimel (4..6); bet carries the kerning, which
|
||||
// is a fraction of a point.
|
||||
for (system_layout, fractional_widths) in [(true, true), (false, false)] {
|
||||
for value in [3.0, 4.0, 6.0, 7.0] {
|
||||
let kerned = |value: f64| {
|
||||
let mut t = TextObject::point(Point::ZERO, "אבג", CharStyle { size: 100.0, ..CharStyle::default() });
|
||||
t.system_layout = system_layout;
|
||||
t.fractional_widths = fractional_widths;
|
||||
edit::style_range(&mut t.runs, 2, 4, |s| s.kerning = Some(value));
|
||||
layout(db(), &t)
|
||||
};
|
||||
let ink = |lay: &TextLayout, byte: usize| {
|
||||
let g = lay.glyphs.iter().find(|g| g.byte == byte).unwrap();
|
||||
assert!(g.rtl && g.gid != 0, "byte {byte} is set right to left with a glyph of its own");
|
||||
(g.xf * Point::ZERO).x
|
||||
};
|
||||
let (plain, spaced) = (kerned(0.0), kerned(value));
|
||||
let gap = |lay: &TextLayout, right: usize, left: usize| ink(lay, right) - ink(lay, left);
|
||||
assert!(
|
||||
(gap(&spaced, 0, 2) - gap(&plain, 0, 2)).abs() < 1e-9,
|
||||
"alef, before bet in reading order, keeps its place (kerning {value}, System Layout {system_layout})"
|
||||
);
|
||||
let wider = gap(&spaced, 2, 4) - gap(&plain, 2, 4);
|
||||
assert!((wider - wider.round()).abs() < 1e-9, "bet moves from gimel by whole points: {wider} (kerning {value})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rtl_manual_kerning_on_a_rotated_character_moves_it_along_the_baseline() {
|
||||
// Reading order: alef (bytes 0..2), bet (2..4), gimel (4..6); bet is turned 90 degrees and
|
||||
|
||||
Reference in New Issue
Block a user