mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 18:54:57 +00:00
Type: an inline graphic takes its character's manual kerning
A manual kerning value on an inline graphic's character was stored and shown but did not move the text after it. The graphic's advance now includes the value, as a letter's does, and in right-to-left horizontal type its art moves right by the value, so the space goes between it and the next character in reading order. Auto and Optical kerning still apply no font pairs to an inline graphic.
This commit is contained in:
@@ -226,8 +226,9 @@ impl Ctx<'_> {
|
||||
let upright = self.vertical && !self.on_path && g.tcy.is_none() && stands_upright(g);
|
||||
let lead = if upright { 0.0 } else { g.lead };
|
||||
let local = match &g.inline {
|
||||
// Inline graphics: art space to glyph space (no font outline).
|
||||
Some(ib) => Affine::translate((-lead, -g.bshift)) * ib.xf,
|
||||
// Inline graphics: art space to glyph space (no font outline), moved along the line by
|
||||
// right-to-left manual kerning as a glyph is.
|
||||
Some(ib) => Affine::translate((g.kern_shift - 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.
|
||||
|
||||
@@ -401,7 +401,7 @@ pub(crate) fn shape_range(
|
||||
let b = bytes.end;
|
||||
if let Some(art) = art {
|
||||
if let Some(face) = db.style_face(st).or_else(|| db.face_covering('a')) {
|
||||
out.push(inline_glyph(&face, st, art, source_runs.start, a..b, text, level_at(a)));
|
||||
out.push(inline_glyph(&face, st, art, source_runs.start, a..b, text, level_at(a), feats.vertical));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -795,10 +795,21 @@ fn shaping_script(c: char) -> Script {
|
||||
///
|
||||
/// The art is scaled uniformly to `scale` x the font size tall, its left edge on the pen and its
|
||||
/// vertical centre on the middle of the cap height raised by the inline's shift (see
|
||||
/// [`InlineArt`]). Its advance is the scaled art width plus tracking; the glyph's ascent and
|
||||
/// descent grow to the art's extent so a tall graphic opens up its line. A missing symbol takes a
|
||||
/// square of the same height and draws nothing.
|
||||
fn inline_glyph(face: &Arc<FontFace>, st: &CharStyle, art: &InlineArt, run: usize, range: Range<usize>, text: &str, level: Level) -> SGlyph {
|
||||
/// [`InlineArt`]). Its advance is the scaled art width plus tracking and its character's manual
|
||||
/// kerning, which right to left in horizontal type also moves the art right by the value, as it
|
||||
/// moves a letter's glyph. The glyph's ascent and descent grow to the art's extent so a tall
|
||||
/// graphic opens up its line. A missing symbol takes a square of the same height and draws nothing.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn inline_glyph(
|
||||
face: &Arc<FontFace>,
|
||||
st: &CharStyle,
|
||||
art: &InlineArt,
|
||||
run: usize,
|
||||
range: Range<usize>,
|
||||
text: &str,
|
||||
level: Level,
|
||||
vertical: bool,
|
||||
) -> SGlyph {
|
||||
let size = if st.size.is_finite() { st.size.max(0.0) } else { 0.0 };
|
||||
let vs = if st.v_scale.is_finite() { st.v_scale / 100.0 } else { 1.0 };
|
||||
let km = size / face.upem;
|
||||
@@ -815,7 +826,8 @@ fn inline_glyph(face: &Arc<FontFace>, st: &CharStyle, art: &InlineArt, run: usiz
|
||||
_ => (kurbo::Affine::IDENTITY, h, None),
|
||||
};
|
||||
let tracking = st.tracking / 1000.0 * size;
|
||||
let adv = width + if tracking.is_finite() { tracking } else { 0.0 };
|
||||
let manual_kern = st.kerning.map(|v| v / 1000.0 * size).filter(|k| k.is_finite()).unwrap_or(0.0);
|
||||
let adv = width + if tracking.is_finite() { tracking } else { 0.0 } + manual_kern;
|
||||
SGlyph {
|
||||
face: face.clone(),
|
||||
gid: 0,
|
||||
@@ -825,7 +837,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,
|
||||
kern_shift: if level.is_rtl() && !vertical { manual_kern } else { 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`).
|
||||
|
||||
@@ -248,6 +248,56 @@ fn inline_art_in_a_right_to_left_paragraph_is_placed_by_the_bidi_reordering() {
|
||||
assert!(inline_rtl && inline < first);
|
||||
}
|
||||
|
||||
/// A manual kerning value on an inline graphic's character spaces it from the next character in
|
||||
/// reading order by the distance the same value spaces a letter of its size: left to right, right
|
||||
/// to left and down a vertical column. The character before it keeps its place, and a graphic's 0
|
||||
/// sets the text as no value does.
|
||||
#[test]
|
||||
fn manual_kerning_on_an_inline_graphic_spaces_it_like_a_letter() {
|
||||
let db = FontDb::global();
|
||||
// `before`, then the graphic (or `letter` in its place) with `kerning`, then `after`.
|
||||
let set = |before: &str, letter: Option<&str>, after: &str, kerning: Option<f64>, vertical: bool| {
|
||||
let mut t = TextObject::point(Point::ZERO, before, style(20.0));
|
||||
let st = CharStyle { kerning, ..style(20.0) };
|
||||
t.runs.push(match letter {
|
||||
Some(c) => TextRun::new(c, st),
|
||||
None => TextRun::inline(art(1.0), st),
|
||||
});
|
||||
t.runs.push(TextRun::new(after, style(20.0)));
|
||||
t.vertical = vertical;
|
||||
let l = layout(db, &t);
|
||||
let ink = |run: usize, last: bool| {
|
||||
let mut g: Vec<&PositionedGlyph> = l.glyphs.iter().filter(|g| g.run == run).collect();
|
||||
g.sort_by_key(|g| g.byte);
|
||||
let g = if last { g.last() } else { g.first() };
|
||||
g.expect("glyph").xf * Point::ZERO
|
||||
};
|
||||
assert_eq!(l.inlines.len(), usize::from(letter.is_none()));
|
||||
// Distance along the line: across the page, or down the column.
|
||||
let along = |a: Point, b: Point| if vertical { (b.y - a.y).abs() } else { (b.x - a.x).abs() };
|
||||
let (previous, middle, next) = (ink(0, true), ink(1, false), ink(2, false));
|
||||
(along(previous, middle), along(middle, next), l.glyphs.iter().map(|g| (g.origin, g.advance)).collect::<Vec<_>>())
|
||||
};
|
||||
for (before, letter, after, vertical) in [("A", "X", "B", false), ("אב", "ג", "דה", false), ("ab", "x", "cd", true)] {
|
||||
for letter in [None, Some(letter)] {
|
||||
let (before_plain, after_plain, plain) = set(before, letter, after, Some(0.0), vertical);
|
||||
let (before_kerned, after_kerned, _) = set(before, letter, after, Some(200.0), vertical);
|
||||
// 200/1000 em of 20 pt.
|
||||
assert!((after_kerned - after_plain - 4.0).abs() < 1e-6, "{before:?} {letter:?}: {after_plain} -> {after_kerned}");
|
||||
assert!((before_kerned - before_plain).abs() < 1e-6, "{before:?} {letter:?}: {before_plain} -> {before_kerned}");
|
||||
if letter.is_some() {
|
||||
// A letter's 0 also turns off its font's kerning pairs; a graphic has none.
|
||||
continue;
|
||||
}
|
||||
let (_, _, none) = set(before, letter, after, None, vertical);
|
||||
assert!(
|
||||
plain.iter().zip(&none).all(|(a, b)| (a.0 - b.0).hypot() < 1e-9 && (a.1 - b.1).abs() < 1e-9),
|
||||
"{before:?} {letter:?}: 0 sets the text as no value does"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Character Alignment: an inline graphic's em box is its run's, whatever its scale. It moves with
|
||||
/// its run's text when that is smaller than the line's largest characters, and a big graphic in
|
||||
/// small text doesn't make its line's small characters move.
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user