mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 09:29:44 -06:00
Type: right-to-left manual kerning moves a rotated character along the line
Right-to-left manual kerning was added to the glyph's offset, which Character Rotation turns, so a rotated character with kerning moved across the line (up at 90 degrees) instead of away from the next character in reading order. The shift is now applied to the pen position along the baseline, outside the rotation, and the rotation turns the glyph about that moved pen position.
This commit is contained in:
@@ -91,6 +91,7 @@ pub(crate) fn apply_desired(sg: &mut [SGlyph], j: &JustificationOptions, scale_g
|
||||
g.adv *= g_des;
|
||||
g.sx *= g_des;
|
||||
g.dx *= g_des;
|
||||
g.kern_shift *= g_des;
|
||||
}
|
||||
f.width = g.adv;
|
||||
f.scale = g_des;
|
||||
|
||||
@@ -181,6 +181,7 @@ impl Ctx<'_> {
|
||||
tab.gid = 0;
|
||||
tab.dx = 0.0;
|
||||
tab.dy = 0.0;
|
||||
tab.kern_shift = 0.0;
|
||||
tab.tcy = None;
|
||||
tab.vpal = None;
|
||||
let pen = para.left_indent + para.first_line_indent + marker.iter().map(|g| g.adv).sum::<f64>();
|
||||
@@ -229,8 +230,9 @@ impl Ctx<'_> {
|
||||
Some(ib) => Affine::translate((-lead, -g.bshift)) * ib.xf,
|
||||
// Baseline shift moves the glyph across its line, and Character Rotation then turns it
|
||||
// about its shifted pen position, so the shift is across the line at any rotation.
|
||||
// Right-to-left manual kerning moves that pen position along the line.
|
||||
None => {
|
||||
Affine::translate((0.0, -g.bshift))
|
||||
Affine::translate((g.kern_shift, -g.bshift))
|
||||
* Affine::rotate(-g.rotation.to_radians())
|
||||
* Affine::translate((g.dx - lead, g.dy))
|
||||
* Affine::scale_non_uniform(g.sx, g.sy)
|
||||
@@ -238,7 +240,7 @@ impl Ctx<'_> {
|
||||
};
|
||||
// The turning point in font units, which later moves of the glyph's transform keep.
|
||||
let pivot = match local.determinant() {
|
||||
det if det != 0.0 && det.is_finite() => local.inverse() * Point::new(0.0, -g.bshift),
|
||||
det if det != 0.0 && det.is_finite() => local.inverse() * Point::new(g.kern_shift, -g.bshift),
|
||||
_ => Point::ZERO,
|
||||
};
|
||||
let unturned = if g.inline.is_none() && g.rotation != 0.0 && g.sx != 0.0 && g.sy != 0.0 {
|
||||
@@ -332,6 +334,7 @@ impl Ctx<'_> {
|
||||
k.adv = t;
|
||||
k.dx = 0.0;
|
||||
k.dy = 0.0;
|
||||
k.kern_shift = 0.0;
|
||||
k.len = 0;
|
||||
k.lead = 0.0;
|
||||
k.ch = kashida::TATWEEL;
|
||||
|
||||
@@ -18,6 +18,7 @@ fn synthetic_glyphs(specs: &[(usize, usize, f64, char)]) -> Vec<SGlyph> {
|
||||
adv,
|
||||
dx: 0.0,
|
||||
dy: 0.0,
|
||||
kern_shift: 0.0,
|
||||
sx: 1.0,
|
||||
sy: 1.0,
|
||||
bshift: 0.0,
|
||||
|
||||
+19
-10
@@ -30,9 +30,12 @@ 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 (right to left, manual kerning applied).
|
||||
/// Offset from the pen position, y down.
|
||||
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.
|
||||
pub kern_shift: f64,
|
||||
/// Outline scale (font units -> points).
|
||||
pub sx: f64,
|
||||
pub sy: f64,
|
||||
@@ -169,6 +172,7 @@ pub(crate) fn hyphen_glyph(g: &SGlyph) -> SGlyph {
|
||||
h.adv = g.face.advance(gid) * g.sx;
|
||||
h.dx = 0.0;
|
||||
h.dy = 0.0;
|
||||
h.kern_shift = 0.0;
|
||||
h.byte = g.byte + g.len;
|
||||
h.len = 0;
|
||||
h.ch = '-';
|
||||
@@ -616,6 +620,9 @@ fn optical_pairs(db: &FontDb, text: &str, glyphs: &mut [SGlyph], runs: &[(Range<
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Neither glyph is rotated, so a right-to-left manual kerning shift adds to the offset.
|
||||
let left_x = left.dx + left.kern_shift;
|
||||
let right_x = right.dx + right.kern_shift;
|
||||
let left_y = left.dy - left.bshift;
|
||||
let right_y = right.dy - right.bshift;
|
||||
let Some(left_style) = runs.get(left.run).map(|(_, s)| *s) else { continue };
|
||||
@@ -631,11 +638,11 @@ fn optical_pairs(db: &FontDb, text: &str, glyphs: &mut [SGlyph], runs: &[(Range<
|
||||
u64::from(right.gid),
|
||||
left.sx.to_bits(),
|
||||
left.sy.to_bits(),
|
||||
left.dx.to_bits(),
|
||||
left_x.to_bits(),
|
||||
left_y.to_bits(),
|
||||
right.sx.to_bits(),
|
||||
right.sy.to_bits(),
|
||||
right.dx.to_bits(),
|
||||
right_x.to_bits(),
|
||||
right_y.to_bits(),
|
||||
advance.to_bits(),
|
||||
em.to_bits(),
|
||||
@@ -648,8 +655,8 @@ fn optical_pairs(db: &FontDb, text: &str, glyphs: &mut [SGlyph], runs: &[(Range<
|
||||
profile_correction(
|
||||
&a.0,
|
||||
&b.0,
|
||||
(left.sx, left.sy, left.dx, left_y),
|
||||
(right.sx, right.sy, right.dx, right_y),
|
||||
(left.sx, left.sy, left_x, left_y),
|
||||
(right.sx, right.sy, right_x, right_y),
|
||||
advance,
|
||||
em,
|
||||
left.face.upem,
|
||||
@@ -812,6 +819,7 @@ fn inline_glyph(face: &Arc<FontFace>, st: &CharStyle, art: &InlineArt, run: usiz
|
||||
adv: if adv.is_finite() { adv } else { 0.0 },
|
||||
dx: 0.0,
|
||||
dy: 0.0,
|
||||
kern_shift: 0.0,
|
||||
// Not used to draw (the art has its own transform), but they make the glyph's em box
|
||||
// its run's em: Character Alignment and Top-to-Top leading treat an inline graphic as a
|
||||
// character of its run's size, whatever its scale (see `layout::glyph_em`).
|
||||
@@ -1111,16 +1119,16 @@ 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;
|
||||
let mut kern_shift = 0.0;
|
||||
if ch == SOFT_HYPHEN || ch == '\u{2028}' {
|
||||
adv = 0.0;
|
||||
} else {
|
||||
let manual_kern = gst.kerning.map(|v| v / 1000.0 * size).unwrap_or(0.0);
|
||||
// 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.
|
||||
// that one is drawn to its left: the cluster's glyphs move right along the baseline 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;
|
||||
kern_shift = manual_kern;
|
||||
}
|
||||
if last_in_cluster {
|
||||
let tracking = gst.tracking / 1000.0 * size;
|
||||
@@ -1134,8 +1142,9 @@ fn shape_segment(text: &str, context: &Range<usize>, seg: &Segment, feats: &OtFe
|
||||
len: end.saturating_sub(cl).max(1),
|
||||
run,
|
||||
adv,
|
||||
dx,
|
||||
dx: xo as f64 * k * hs,
|
||||
dy: -(yo as f64) * k * vs,
|
||||
kern_shift,
|
||||
sx: k * hs,
|
||||
sy: k * vs,
|
||||
bshift: gst.baseline_shift + pos_shift,
|
||||
|
||||
@@ -98,6 +98,32 @@ fn rtl_manual_kerning_spaces_the_character_from_the_next_in_reading_order() {
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
// carries the kerning.
|
||||
let kerned = |value: f64| {
|
||||
let mut t = TextObject::point(Point::ZERO, "אבג", CharStyle { size: 100.0, ..CharStyle::default() });
|
||||
edit::style_range(&mut t.runs, 2, 4, |s| {
|
||||
s.kerning = Some(value);
|
||||
s.rotation = 90.0;
|
||||
});
|
||||
layout(db(), &t)
|
||||
};
|
||||
let origin = |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
|
||||
};
|
||||
let (plain, spaced) = (kerned(0.0), kerned(200.0));
|
||||
let moved = origin(&spaced, 2) - origin(&plain, 2);
|
||||
let gimel = origin(&spaced, 4) - origin(&plain, 4);
|
||||
assert!((moved.x - gimel.x - 20.0).abs() < 0.01, "kerning on the turned bet spaces it from gimel along the line: {moved:?}, {gimel:?}");
|
||||
assert!(moved.y.abs() < 0.01, "the turned bet stays on its baseline: {moved:?}");
|
||||
let alef = origin(&spaced, 0) - origin(&plain, 0);
|
||||
assert!((alef.x - moved.x).abs() < 0.01 && alef.y.abs() < 0.01, "alef keeps its place next to bet: {alef:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optical_pair_skips_an_invisible_soft_hyphen_with_an_outline() {
|
||||
use crate::test_fonts::{SOFT_HYPHEN_OUTLINE_FAMILY, soft_hyphen_outline_font};
|
||||
|
||||
Reference in New Issue
Block a user