mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 13:23:22 +00:00
Merge pull request #5245 from dotCooCoo/fix/inline-graphic-manual-kerning
Type: an inline graphic takes its character's manual kerning
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