mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 20:03:56 +00:00
Type: manual kerning in right-to-left text spaces the pair it is set on
A manual kerning value on a character in right-to-left text changed the space between it and the character before it in reading order, under both Auto and Optical kerning. The shaped glyphs of a right-to-left cluster now also move right by the value, so the space its advance gains is at its left, between it and the next character in reading order. Left-to-right text and tracking are unchanged. Optical kerning no longer takes the left glyph's manual value off the advance it measures, since right to left the value is in that glyph's offset as well.
This commit is contained in:
@@ -30,7 +30,7 @@ pub(crate) struct SGlyph {
|
||||
pub run: usize,
|
||||
/// Advance in points (tracking, manual kerning and horizontal scale applied).
|
||||
pub adv: f64,
|
||||
/// Offset from the pen position, y down.
|
||||
/// Offset from the pen position, y down (right to left, manual kerning applied).
|
||||
pub dx: f64,
|
||||
pub dy: f64,
|
||||
/// Outline scale (font units -> points).
|
||||
@@ -621,8 +621,9 @@ fn optical_pairs(db: &FontDb, text: &str, glyphs: &mut [SGlyph], runs: &[(Range<
|
||||
let Some(left_style) = runs.get(left.run).map(|(_, s)| *s) else { continue };
|
||||
let em = left.sx * left.face.upem / (left_style.h_scale / 100.0);
|
||||
let tracking = left_style.tracking / 1000.0 * em;
|
||||
let manual = left_style.kerning.unwrap_or(0.0) / 1000.0 * em;
|
||||
let advance = glyphs[cluster_start..=cluster_end].iter().map(|g| g.adv).sum::<f64>() - tracking - manual;
|
||||
// A manual kerning value on the left glyph is part of its advance and, right to left, of
|
||||
// its offset too (it spaces the left glyph from its left neighbor): it is not taken off.
|
||||
let advance = glyphs[cluster_start..=cluster_end].iter().map(|g| g.adv).sum::<f64>() - tracking;
|
||||
let key = [
|
||||
u64::from(left.face.id()),
|
||||
u64::from(left.gid),
|
||||
@@ -1110,12 +1111,21 @@ fn shape_segment(text: &str, context: &Range<usize>, seg: &Segment, feats: &OtFe
|
||||
let last_in_cluster = gi + 1 == n || raw[gi + 1].1 as usize != cl;
|
||||
let ch = first_char(cl);
|
||||
let mut adv = xa as f64 * k * hs;
|
||||
let mut dx = xo as f64 * k * hs;
|
||||
if ch == SOFT_HYPHEN || ch == '\u{2028}' {
|
||||
adv = 0.0;
|
||||
} else if last_in_cluster {
|
||||
let tracking = gst.tracking / 1000.0 * size;
|
||||
} else {
|
||||
let manual_kern = gst.kerning.map(|v| v / 1000.0 * size).unwrap_or(0.0);
|
||||
adv += tracking + manual_kern;
|
||||
// Manual kerning spaces a character from the next one in reading order. Right to left,
|
||||
// that one is drawn to its left: the cluster's glyphs move right by the value, so that
|
||||
// the space its last glyph's advance gains is at the cluster's left.
|
||||
if level.is_rtl() && !feats.vertical {
|
||||
dx += manual_kern;
|
||||
}
|
||||
if last_in_cluster {
|
||||
let tracking = gst.tracking / 1000.0 * size;
|
||||
adv += tracking + manual_kern;
|
||||
}
|
||||
}
|
||||
out.push(SGlyph {
|
||||
face: face.clone(),
|
||||
@@ -1124,7 +1134,7 @@ fn shape_segment(text: &str, context: &Range<usize>, seg: &Segment, feats: &OtFe
|
||||
len: end.saturating_sub(cl).max(1),
|
||||
run,
|
||||
adv,
|
||||
dx: xo as f64 * k * hs,
|
||||
dx,
|
||||
dy: -(yo as f64) * k * vs,
|
||||
sx: k * hs,
|
||||
sy: k * vs,
|
||||
|
||||
@@ -66,6 +66,38 @@ fn optical_pairs_cross_paint_runs_and_manual_and_tracking_are_independent() {
|
||||
assert!((pair_advance(&rtl_manual) - pair_advance(&rtl_pair_zero) - 20.0).abs() < 0.01, "manual spacing changes the adjacent right-to-left pair");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rtl_manual_kerning_spaces_the_character_from_the_next_in_reading_order() {
|
||||
for optical_kerning in [false, true] {
|
||||
// Reading order: alef (bytes 0..2), bet (2..4), gimel (4..6); bet carries the kerning.
|
||||
let base = CharStyle { size: 100.0, optical_kerning, ..CharStyle::default() };
|
||||
let kerned = |value: f64| {
|
||||
let mut t = TextObject::point(Point::ZERO, "אבג", base.clone());
|
||||
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(200.0));
|
||||
let gap = |lay: &TextLayout, right: usize, left: usize| ink(lay, right) - ink(lay, left);
|
||||
assert!(
|
||||
(gap(&spaced, 2, 4) - gap(&plain, 2, 4) - 20.0).abs() < 0.01,
|
||||
"kerning on bet spaces it from gimel, after it in reading order (Optical: {optical_kerning})"
|
||||
);
|
||||
assert!(
|
||||
(gap(&spaced, 0, 2) - gap(&plain, 0, 2)).abs() < 0.01,
|
||||
"alef, before bet in reading order, keeps its place (Optical: {optical_kerning})"
|
||||
);
|
||||
// The caret between bet and gimel stays at gimel: the new space is on bet's side of it.
|
||||
let caret = |lay: &TextLayout| caret_position(lay, 4).0.x;
|
||||
assert!((caret(&spaced) - ink(&spaced, 4) - (caret(&plain) - ink(&plain, 4))).abs() < 0.01);
|
||||
assert!((ink(&spaced, 2) - caret(&spaced) - (ink(&plain, 2) - caret(&plain)) - 20.0).abs() < 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optical_pair_skips_an_invisible_soft_hyphen_with_an_outline() {
|
||||
use crate::test_fonts::{SOFT_HYPHEN_OUTLINE_FAMILY, soft_hyphen_outline_font};
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user