mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 01:14:29 -06:00
Type: upright glyphs on a vertical path keep vertical point type's cells, and the caret spans the column
On a vertical path, an upright glyph is now placed in its column as vertical point type places it, then the column is centered on the path. - Its cell starts at its place on the path; it started 0.12 em earlier with fonts whose cell top is less than a full cell above the baseline. - A baseline shift moves it across the column instead of along the path. - A horizontal scale widens it evenly about the column's center. - A combining mark stays on its upright letter. - The caret and the selection highlight span the column, half its em on each side of the path, instead of the font's ascent and descent. With Skew, 3D Ribbon and Stair Step on a path that runs to the left, letters are a quarter turn counterclockwise from upright glyphs: Skew and 3D Ribbon mirror both kinds there, and Stair Step keeps letters level. The test, the progress doc and the params text of Tate-chu-yoko Settings now state these conditions and the block's directions on a path.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -40,7 +40,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Character",
|
||||
[],
|
||||
None,
|
||||
"{id? (default: the one selected type object), start?: byte, end?: byte (default: all text), story?: bool = false (all the text of every frame of the threaded story `id` is a frame of, in one undo step; no `start` or `end`), font?, style?, size?: pt, leading?: pt|\"auto\", tracking?, kerning?: 1/1000 em|\"auto\"|\"optical\", baselineShift?: pt, hScale?: %, vScale?: %, rotation?: deg, fill?: colour|\"none\", stroke?: colour|\"none\" (#rrggbbaa and #rgba are refused in both), keepModel?: bool (fill and stroke, as paint.setFill), strokeWidth?: pt, strokeOptions?: {weight?, cap?, join?, miterLimit?, align?, dash?, dashOffset?, alignDashes?} (as stroke.set: the character stroke), underline?, strikethrough?, allCaps?: bool, smallCaps?: bool, position?: \"normal\"|\"superscript\"|\"subscript\" (sizes from Document Setup), features?: [\"dlig\", \"-liga\", …], featureChanges?: [\"tnum\", \"-onum\", …] (OpenType features turned on, or with - off, on top of each character's own; its other features are kept), charAlign?: \"romanBaseline\"|\"emBoxTop\"|\"emBoxCenter\"|\"emBoxBottom\"|\"icfTop\"|\"icfBottom\", proportionalMetrics?: bool (full-width glyphs on the font's proportional widths: `palt` in horizontal type, `vpal` in vertical type), tateChuYoko?: bool (Character panel menu › Tate-chu-yoko, with Show East Asian Options: in vertical type those characters stand side by side across one em of the column; kept on the characters and ignored in horizontal type), tcyUpDown?, tcyLeftRight?: pt -1296..1296 (Character panel menu › Tate-chu-yoko Settings: in vertical type a block of tate-chu-yoko characters moves up by a positive Up/Down and down by a negative one, right by a positive Left/Right and left by a negative one, as one piece with the values of its first character; 0 by default; automatic digit blocks and horizontal type do not move; neither turns tateChuYoko on), standardVerticalRoman?: bool (Character panel menu › Standard Vertical Roman Alignment, with Show East Asian Options: in vertical type those half-width characters, such as Latin letters and digits, stand upright one below the other instead of being rotated; spaces and tate-chu-yoko blocks are not changed; kept on the characters and ignored in horizontal type; off by default), noBreak?: bool (Character panel menu › No Break: these characters stay on one line, including across a space inside the run), kashidas?: bool (Character panel menu › Kashidas: selected characters allow insertion only when both joined neighbors have it on; on by default), charDirection?: \"default\"|\"leftToRight\"|\"rightToLeft\" (Character panel menu › Character Direction: the characters are set left to right or right to left inside their paragraph, as a Unicode bidi embedding; default: as the bidi algorithm sets them), digits?: \"default\"|\"arabic\"|\"hindi\"|\"farsi\"|\"devanagari\"|\"bengali\"|\"gurmukhi\"|\"gujarati\"|\"odia\"|\"tamil\"|\"telugu\"|\"kannada\"|\"malayalam\" (Character panel menu › Digits: the numeral system every digit is drawn in, whatever was typed: Western, Arabic-Indic (hindi), Eastern Arabic-Indic (farsi) or an Indic script's own digits), fontHeightReference?: \"emBox\"|\"capHeight\"|\"xHeight\"|\"icfBox\" (Character panel › Set Font Height Reference: the height of the font that size measures, the em box by default; with a font that has no such height, or one outside 0.1 to 2 em, the em box), language?: name|tag (Character panel › Language: one of Document Setup's languages by name or BCP 47 tag, case-insensitive, such as \"Turkish\" or \"tr\"; the characters are shaped in it, so the font uses that language's forms (OpenType `locl`); saved as the tag, English: USA, the default, as none; new type takes the document's language), fontAxes?: {\"wght\": 650, …} (Character panel › Variable Font: the setting of each variation axis named, in the axis's units and clamped to its range; null: back to the style's own setting; other axes keep theirs; tags the font has no axis for are left out; a font or style change clears them; text.fontList {family, style} lists the axes)} style a character range (size/leading/baselineShift are document points and hScale includes the object transform; runs are split at the range ends) → {id, runs, warning?: text (the font or style set isn't available: its type is drawn in the fallback font or the family's closest style)}",
|
||||
"{id? (default: the one selected type object), start?: byte, end?: byte (default: all text), story?: bool = false (all the text of every frame of the threaded story `id` is a frame of, in one undo step; no `start` or `end`), font?, style?, size?: pt, leading?: pt|\"auto\", tracking?, kerning?: 1/1000 em|\"auto\"|\"optical\", baselineShift?: pt, hScale?: %, vScale?: %, rotation?: deg, fill?: colour|\"none\", stroke?: colour|\"none\" (#rrggbbaa and #rgba are refused in both), keepModel?: bool (fill and stroke, as paint.setFill), strokeWidth?: pt, strokeOptions?: {weight?, cap?, join?, miterLimit?, align?, dash?, dashOffset?, alignDashes?} (as stroke.set: the character stroke), underline?, strikethrough?, allCaps?: bool, smallCaps?: bool, position?: \"normal\"|\"superscript\"|\"subscript\" (sizes from Document Setup), features?: [\"dlig\", \"-liga\", …], featureChanges?: [\"tnum\", \"-onum\", …] (OpenType features turned on, or with - off, on top of each character's own; its other features are kept), charAlign?: \"romanBaseline\"|\"emBoxTop\"|\"emBoxCenter\"|\"emBoxBottom\"|\"icfTop\"|\"icfBottom\", proportionalMetrics?: bool (full-width glyphs on the font's proportional widths: `palt` in horizontal type, `vpal` in vertical type), tateChuYoko?: bool (Character panel menu › Tate-chu-yoko, with Show East Asian Options: in vertical type those characters stand side by side across one em of the column; kept on the characters and ignored in horizontal type), tcyUpDown?, tcyLeftRight?: pt -1296..1296 (Character panel menu › Tate-chu-yoko Settings: in vertical type a block of tate-chu-yoko characters moves up by a positive Up/Down and down by a negative one, right by a positive Left/Right and left by a negative one, as one piece with the values of its first character; on a vertical path a positive Up/Down moves it toward the path's start and a positive Left/Right to the path's left; 0 by default; automatic digit blocks and horizontal type do not move; neither turns tateChuYoko on), standardVerticalRoman?: bool (Character panel menu › Standard Vertical Roman Alignment, with Show East Asian Options: in vertical type those half-width characters, such as Latin letters and digits, stand upright one below the other instead of being rotated; spaces and tate-chu-yoko blocks are not changed; kept on the characters and ignored in horizontal type; off by default), noBreak?: bool (Character panel menu › No Break: these characters stay on one line, including across a space inside the run), kashidas?: bool (Character panel menu › Kashidas: selected characters allow insertion only when both joined neighbors have it on; on by default), charDirection?: \"default\"|\"leftToRight\"|\"rightToLeft\" (Character panel menu › Character Direction: the characters are set left to right or right to left inside their paragraph, as a Unicode bidi embedding; default: as the bidi algorithm sets them), digits?: \"default\"|\"arabic\"|\"hindi\"|\"farsi\"|\"devanagari\"|\"bengali\"|\"gurmukhi\"|\"gujarati\"|\"odia\"|\"tamil\"|\"telugu\"|\"kannada\"|\"malayalam\" (Character panel menu › Digits: the numeral system every digit is drawn in, whatever was typed: Western, Arabic-Indic (hindi), Eastern Arabic-Indic (farsi) or an Indic script's own digits), fontHeightReference?: \"emBox\"|\"capHeight\"|\"xHeight\"|\"icfBox\" (Character panel › Set Font Height Reference: the height of the font that size measures, the em box by default; with a font that has no such height, or one outside 0.1 to 2 em, the em box), language?: name|tag (Character panel › Language: one of Document Setup's languages by name or BCP 47 tag, case-insensitive, such as \"Turkish\" or \"tr\"; the characters are shaped in it, so the font uses that language's forms (OpenType `locl`); saved as the tag, English: USA, the default, as none; new type takes the document's language), fontAxes?: {\"wght\": 650, …} (Character panel › Variable Font: the setting of each variation axis named, in the axis's units and clamped to its range; null: back to the style's own setting; other axes keep theirs; tags the font has no axis for are left out; a font or style change clears them; text.fontList {family, style} lists the axes)} style a character range (size/leading/baselineShift are document points and hScale includes the object transform; runs are split at the range ends) → {id, runs, warning?: text (the font or style set isn't available: its type is drawn in the fallback font or the family's closest style)}",
|
||||
has_doc,
|
||||
set_range_style
|
||||
),
|
||||
|
||||
+43
-22
@@ -93,6 +93,34 @@ fn takes_cell(g: &SGlyph) -> bool {
|
||||
g.adv > 0.0 || g.face.advance(g.gid) > 0.0
|
||||
}
|
||||
|
||||
/// Vertical type on a path: the turn that takes upright glyph `g` (of size `em`, its glyph space
|
||||
/// mapped onto the path by `pre`) from its local transform `local` (`unturned` takes its Character
|
||||
/// Rotation off) to the path, and where the next glyph of its cluster goes in its glyph space.
|
||||
///
|
||||
/// It stands in glyph space as it stands in vertical point type's line space: a quarter turn
|
||||
/// counterclockwise from horizontal path type's pose about the center of its cell, a baseline shift
|
||||
/// kept across the column and a leading space taken off moving it along the path, then moved across
|
||||
/// the path as a glyph lying on its side is.
|
||||
fn upright_path_turn(g: &SGlyph, em: f64, pre: Affine, local: Affine, unturned: Affine, angle: f64, advance: f64) -> (Affine, f64) {
|
||||
let cell = if takes_cell(g) { upright_cell(g) } else { advance };
|
||||
let (shift, unshift) = (Affine::translate((0.0, -g.bshift)), Affine::translate((0.0, g.bshift)));
|
||||
let about = Point::new(cell * 0.5, -upright_centre(g, cell, em));
|
||||
let mut turn =
|
||||
Affine::translate((-g.lead, g.face.ideographic_centre() * em)) * shift * Affine::rotate_about(-std::f64::consts::FRAC_PI_2, about) * unshift;
|
||||
// The effects that keep the glyph's axes fixed (Stair Step) leave its top toward the path's end on
|
||||
// a path that runs to the left: it turns half a turn about its cell's center.
|
||||
let tangent = Vec2::new(angle.cos(), angle.sin());
|
||||
let placed = pre * turn * local * unturned;
|
||||
if (placed * Point::new(0.0, -1.0) - placed * Point::ZERO).dot(tangent) > 0.0 {
|
||||
let centre = turn * shift * about;
|
||||
turn = Affine::translate(centre.to_vec2() * 2.0) * Affine::scale(-1.0) * turn;
|
||||
}
|
||||
// Its own advance across, from where the cell centers it: where the shaper set the next glyph of
|
||||
// its cluster.
|
||||
let across = g.face.advance(g.gid) * g.sx;
|
||||
(pre * turn, (cell - across) * 0.5 + across)
|
||||
}
|
||||
|
||||
/// Clear the caret stops of each glyph that shares its cluster with another glyph: only a glyph
|
||||
/// that alone stands for its characters is a ligature.
|
||||
fn keep_lone_ligature_carets(glyphs: &mut [PositionedGlyph]) {
|
||||
@@ -333,7 +361,7 @@ impl Ctx<'_> {
|
||||
};
|
||||
// A glyph whose leading space was taken off (mojikumi) is drawn that much earlier: an
|
||||
// upright one in vertical type by moving it up the column once it stands upright.
|
||||
let upright = self.vertical && !self.on_path && g.tcy.is_none() && stands_upright(g);
|
||||
let upright = self.vertical && g.tcy.is_none() && stands_upright(g) && !(self.on_path && g.inline.is_some());
|
||||
let lead = if upright { 0.0 } else { g.lead };
|
||||
let local = match &g.inline {
|
||||
// Inline graphics: art space to glyph space (no font outline), moved along the line by
|
||||
@@ -417,28 +445,21 @@ impl Ctx<'_> {
|
||||
let centre = g.face.ideographic_centre() * g.face.units_per_em() * g.sy;
|
||||
m = pre * Affine::translate((0.0, centre)) * local;
|
||||
} else if self.vertical && self.on_path {
|
||||
// An upright glyph is a quarter turn counterclockwise from horizontal path type's pose,
|
||||
// turned in glyph space so that every effect bends it as it bends glyphs lying on their
|
||||
// side: its top faces the path's start and the characters of an upright ligature read
|
||||
// along the path in order. The center of its cell (without its Character Rotation) keeps
|
||||
// its place along the path, and its em box is centered across the path.
|
||||
let (cell, top) = path_cell.unwrap_or((0.0, 0.0));
|
||||
let unrotated = local * unturned;
|
||||
let middle = Point::new(g.face.units_per_em() * 0.5, cell * 0.5 - top);
|
||||
// The em box's middle across the column, which the shaper's offsets then move.
|
||||
let at = Point::new(g.face.units_per_em() * g.sx * 0.5, (unrotated * middle).y);
|
||||
let target = Point::new(-at.y, 0.0);
|
||||
let quarter = Affine::new([0.0, -1.0, 1.0, 0.0, 0.0, 0.0]);
|
||||
let mut turn = Affine::translate(target.to_vec2() - (quarter * at).to_vec2()) * quarter;
|
||||
// The effects that keep the glyph's axes fixed (Stair Step) leave its top toward the
|
||||
// path's end on a path that runs to the left: it turns half a turn about its cell's
|
||||
// center.
|
||||
let tangent = Vec2::new(angle.cos(), angle.sin());
|
||||
let placed = pre * turn * unrotated;
|
||||
if (placed * Point::new(middle.x, middle.y - 1.0) - placed * middle).dot(tangent) > 0.0 {
|
||||
turn = Affine::translate(target.to_vec2() * 2.0) * Affine::scale(-1.0) * turn;
|
||||
// An upright glyph is turned in glyph space ([`upright_path_turn`]), so every effect bends it
|
||||
// as it bends glyphs lying on their side: its top faces the path's start and the characters
|
||||
// of an upright ligature read along the path in order.
|
||||
let next = self.out.glyphs.len();
|
||||
let after_base = self.out.glyphs.last().is_some_and(|p| p.byte == g.byte && p.line == line && p.len > 0);
|
||||
if let Some((_, _, turn, after)) = cluster.filter(|c| c.0 == g.byte && c.1 == next && after_base && g.len > 0) {
|
||||
// A later glyph of an upright glyph's cluster: set after it as the shaper placed it,
|
||||
// and turned with it, in its glyph space.
|
||||
m = turn * Affine::translate((after, 0.0)) * local;
|
||||
self.upright_cluster = Some((g.byte, next + 1, turn, after));
|
||||
} else {
|
||||
let (turn, after) = upright_path_turn(g, self.style_at(g.byte).size, pre, local, unturned, angle, advance);
|
||||
m = turn * local;
|
||||
self.upright_cluster = (g.len > 0).then_some((g.byte, next + 1, turn, after));
|
||||
}
|
||||
m = pre * turn * local;
|
||||
} else if let (true, Some(t)) = (self.vertical, g.tcy) {
|
||||
// Tate-chu-yoko: the block set across the column, centred on its em of it, then moved up
|
||||
// by its Up/Down and right by its Left/Right (line x runs down the column and line y
|
||||
|
||||
@@ -174,6 +174,15 @@ pub(super) fn on_path(cx: &mut Ctx<'_>, paras: &[Range<usize>], t: &TextObject,
|
||||
let s = style_metrics(cx.face_at(0).as_deref(), cx.style_at(0));
|
||||
(s.0, s.1)
|
||||
});
|
||||
// Vertical type: the caret and the selection span the column, half its em on each side of the
|
||||
// line Align to Path puts the baseline on.
|
||||
let m = if cx.vertical {
|
||||
let half = sg.iter().map(|g| g.face.units_per_em() * g.sx * 0.5).fold(f64::NAN, f64::max);
|
||||
let half = if half.is_finite() { half } else { cx.style_at(0).size * 0.5 };
|
||||
(half, half)
|
||||
} else {
|
||||
m
|
||||
};
|
||||
if ap.is_empty() {
|
||||
cx.out.overflow = !sg.is_empty();
|
||||
cx.out.lines.push(LineInfo {
|
||||
|
||||
@@ -655,7 +655,7 @@ fn vertical_only_settings_keep_a_horizontal_ligature_whole() {
|
||||
}
|
||||
|
||||
/// On a path, an upright ligature in vertical type stands with its top toward the path's start, and
|
||||
/// its cell runs from 0.12 em before the pen to 0.88 em after it, with or without the font's vertical
|
||||
/// its cell runs one em from the pen, as in vertical point type, with or without the font's vertical
|
||||
/// metrics. The inner stop lies at the middle of that cell, and the first character's selection ends
|
||||
/// there.
|
||||
#[test]
|
||||
@@ -673,21 +673,21 @@ fn an_upright_ligature_on_a_path_takes_its_stop_in_its_turned_cell() {
|
||||
assert!(l.on_path);
|
||||
let Some(g) = l.glyphs.iter().find(|g| g.len == 2) else { panic!("f + i is one glyph: {:?}", l.glyphs) };
|
||||
assert_eq!(g.carets, vec![(1, 0.5)], "{family}");
|
||||
assert!((g.width - 100.0).abs() < 1e-9 && (g.width_start + 12.0).abs() < 1e-9, "{family}: {} {}", g.width, g.width_start);
|
||||
assert!((g.width - 100.0).abs() < 1e-9 && g.width_start.abs() < 1e-9, "{family}: {} {}", g.width, g.width_start);
|
||||
let x = |b| caret_position(&l, b).0.x - g.origin.x;
|
||||
assert!((x(1) - 38.0).abs() < 1e-6, "{family}: {}", x(1));
|
||||
assert!((x(1) - 50.0).abs() < 1e-6, "{family}: {}", x(1));
|
||||
let q = selection_quads(&l, 0, 1);
|
||||
assert_eq!(q.len(), 1, "{q:?}");
|
||||
assert!(((q[0][1] - q[0][0]).hypot() - 38.0).abs() < 1e-6, "{family}: {q:?}");
|
||||
// A baseline shift moves the turned glyph along the path, and its stop with it.
|
||||
let ink = g.outline.bounding_box().x0;
|
||||
assert!(((q[0][1] - q[0][0]).hypot() - 50.0).abs() < 1e-6, "{family}: {q:?}");
|
||||
// A baseline shift moves the turned glyph across the path, and leaves its stop in place.
|
||||
let ink = g.outline.bounding_box();
|
||||
t.runs[0].style.baseline_shift = 10.0;
|
||||
let l = layout(&db, &t);
|
||||
let Some(s) = l.glyphs.iter().find(|g| g.len == 2) else { panic!("f + i is one glyph: {:?}", l.glyphs) };
|
||||
let moved = s.outline.bounding_box().x0 - ink;
|
||||
assert!(moved.abs() > 1.0, "{family}: the shift moves the glyph along the path: {moved}");
|
||||
let moved = s.outline.bounding_box().center() - ink.center();
|
||||
assert!((moved.x.abs()) < 1e-6 && (moved.y + 10.0).abs() < 1e-6, "{family}: the shift moves the glyph to the path's left: {moved:?}");
|
||||
let stop = caret_position(&l, 1).0.x - s.origin.x;
|
||||
assert!((stop - 38.0 - moved).abs() < 1e-6, "{family}: {stop} against {}", 38.0 + moved);
|
||||
assert!((stop - 50.0).abs() < 1e-6, "{family}: {stop}");
|
||||
// Horizontal Scale widens the glyph across the path; its stop stays inside the glyph.
|
||||
t.runs[0].style.baseline_shift = 0.0;
|
||||
t.runs[0].style.h_scale = 200.0;
|
||||
|
||||
@@ -201,9 +201,10 @@ fn top_and_ahead(l: &TextLayout, i: usize) -> (Vec2, Vec2) {
|
||||
(unit(g.xf * Point::new(0.0, -500.0) - pen), unit(g.xf * Point::new(500.0, 0.0) - pen))
|
||||
}
|
||||
|
||||
/// On a vertical path, an upright glyph's top faces the path's start, and a letter lying on its side
|
||||
/// is a quarter turn clockwise from it: its top faces the path's left and its baseline runs along
|
||||
/// the path. Both turn with the path when it runs the other way.
|
||||
/// On a vertical path with Rainbow, an upright glyph's top faces the path's start, and a letter lying
|
||||
/// on its side is a quarter turn clockwise from it: its top faces the path's left and its baseline
|
||||
/// runs along the path. Both turn with the path when it runs the other way. The effects that keep
|
||||
/// the glyphs' axes fixed on the page keep that relation only on a path that runs to the right.
|
||||
#[test]
|
||||
fn on_a_vertical_path_letters_lie_a_quarter_turn_clockwise_from_upright_glyphs() {
|
||||
use vectorcraft_doc::PathEffect;
|
||||
@@ -218,6 +219,24 @@ fn on_a_vertical_path_letters_lie_a_quarter_turn_clockwise_from_upright_glyphs()
|
||||
assert!((side - Vec2::new(-upright.y, upright.x)).hypot() < 1e-6, "{to:?}: {side:?} against {upright:?}");
|
||||
assert!((side - left).hypot() < 1e-6 && (ahead - tangent).hypot() < 1e-6, "{to:?}: {side:?} {ahead:?}");
|
||||
}
|
||||
// Skew, 3D Ribbon and Stair Step keep axes of the glyphs fixed on the page: on a path that runs
|
||||
// to the right the relation holds. On one that runs to the left, Skew and 3D Ribbon mirror both
|
||||
// kinds, and Stair Step keeps letters level while upright glyphs still face the path's start, so
|
||||
// letters are a quarter turn counterclockwise from upright glyphs there.
|
||||
for effect in [PathEffect::Skew, PathEffect::Ribbon3d, PathEffect::StairStep] {
|
||||
for to in [(1000.0, 0.0), (700.0, 700.0), (0.0, 1000.0), (0.0, -1000.0), (-1000.0, 0.0), (-700.0, 300.0)] {
|
||||
// Skew and 3D Ribbon slant glyphs on a slanted path, where no turn relates them exactly.
|
||||
if to.1 != 0.0 && effect != PathEffect::StairStep {
|
||||
continue;
|
||||
}
|
||||
let l = layout(FontDb::global(), &on_vertical_path("§L", (0.0, 0.0), to, effect));
|
||||
let (upright, _) = top_and_ahead(&l, 0);
|
||||
let (side, _) = top_and_ahead(&l, 1);
|
||||
let clockwise = to.0 >= 0.0;
|
||||
let want = if clockwise { Vec2::new(-upright.y, upright.x) } else { Vec2::new(upright.y, -upright.x) };
|
||||
assert!((side - want).hypot() < 1e-6, "{effect:?} {to:?}: {side:?} against {upright:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// On a vertical path, letters lying on their side are set as horizontal type on the same path sets
|
||||
@@ -277,6 +296,71 @@ fn on_a_vertical_path_the_column_is_centered_on_the_path() {
|
||||
}
|
||||
}
|
||||
|
||||
/// On a vertical path, the caret and the selection span the column, half its em on each side of the
|
||||
/// path, as the glyphs do.
|
||||
#[test]
|
||||
fn on_a_vertical_path_the_caret_and_selection_span_the_column() {
|
||||
use vectorcraft_doc::PathEffect;
|
||||
for to in [(0.0, 1000.0), (1000.0, 0.0), (-700.0, 700.0)] {
|
||||
let l = layout(FontDb::global(), &on_vertical_path("§L§", (0.0, 0.0), to, PathEffect::Rainbow));
|
||||
let tangent = Vec2::new(to.0, to.1) / Vec2::new(to.0, to.1).hypot();
|
||||
let across = |p: Point| p.to_vec2().dot(Vec2::new(tangent.y, -tangent.x));
|
||||
let (top, bottom) = caret_position(&l, "§".len());
|
||||
assert!((across(top) - 10.0).abs() < 1e-6 && (across(bottom) + 10.0).abs() < 1e-6, "{to:?}: caret {top:?} {bottom:?}");
|
||||
for q in selection_quads(&l, 0, "§L§".len()) {
|
||||
let (lo, hi) = q.iter().map(|p| across(*p)).fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), a| (lo.min(a), hi.max(a)));
|
||||
assert!((lo + 10.0).abs() < 1e-6 && (hi - 10.0).abs() < 1e-6, "{to:?}: highlight from {lo} to {hi}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// On a straight path running down from the anchor, vertical type sets every glyph where vertical
|
||||
/// point type sets it: upright glyphs with a horizontal scale stay centered on the column, a baseline
|
||||
/// shift moves an upright glyph across the column, every upright glyph keeps its own cell along the
|
||||
/// path, and a combining mark stays on its upright letter.
|
||||
#[test]
|
||||
fn on_a_downward_vertical_path_glyphs_sit_where_vertical_point_type_sets_them() {
|
||||
use crate::test_fonts::{VERTICAL_FAMILY, vertical_font};
|
||||
let run = |text: &str, style: CharStyle| TextRun { text: text.into(), style, inline: None };
|
||||
let runs = |plain: CharStyle| {
|
||||
vec![
|
||||
run("§×", plain.clone()),
|
||||
run("§", CharStyle { h_scale: 150.0, ..plain.clone() }),
|
||||
run("§", CharStyle { baseline_shift: 3.0, ..plain.clone() }),
|
||||
run("§", CharStyle { rotation: 30.0, ..plain.clone() }),
|
||||
run("L", CharStyle { h_scale: 150.0, ..plain.clone() }),
|
||||
run("L", CharStyle { baseline_shift: 3.0, ..plain.clone() }),
|
||||
run("q\u{301}x", CharStyle { standard_vertical_roman: true, ..plain.clone() }),
|
||||
run("§", plain),
|
||||
]
|
||||
};
|
||||
let compare = |db: &FontDb, point: &TextObject, name: &str| {
|
||||
let mut on_path = point.clone();
|
||||
on_path.kind =
|
||||
TextKind::OnPath { path: PathData::from_bezpath(&kurbo::Line::new((0.0, 0.0), (0.0, 3000.0)).to_path(0.1)), start: 0.0, end: None };
|
||||
let (p, v) = (layout(db, point), layout(db, &on_path));
|
||||
assert_eq!(p.glyphs.len(), v.glyphs.len(), "{name}");
|
||||
let apart: Vec<_> = (p.glyphs.iter().zip(&v.glyphs).enumerate())
|
||||
.filter(|(_, (a, b))| a.xf.as_coeffs().iter().zip(b.xf.as_coeffs()).any(|(a, b)| (a - b).abs() > 1e-6))
|
||||
.map(|(i, (a, b))| format!("glyph {i}: {:?} on the path, {:?} in point type", b.xf.as_coeffs(), a.xf.as_coeffs()))
|
||||
.collect();
|
||||
assert!(apart.is_empty(), "{name}: {apart:#?}");
|
||||
};
|
||||
let mut point = vertical("");
|
||||
point.runs = runs(CharStyle { size: 20.0, ..CharStyle::default() });
|
||||
compare(FontDb::global(), &point, "the bundled face");
|
||||
// A face with vertical metrics, whose cells hang from their vertical origins.
|
||||
let db = FontDb::with_font_dirs(vec![]);
|
||||
db.add_font(vertical_font(true).unwrap());
|
||||
point.runs = runs(CharStyle { size: 20.0, font_family: VERTICAL_FAMILY.into(), ..CharStyle::default() });
|
||||
compare(&db, &point, "vertical metrics");
|
||||
// The horizontally scaled upright glyph is wider across the column, centered where the plain one is.
|
||||
point.kind = TextKind::OnPath { path: PathData::from_bezpath(&kurbo::Line::new((0.0, 0.0), (0.0, 3000.0)).to_path(0.1)), start: 0.0, end: None };
|
||||
let l = layout(&db, &point);
|
||||
let (plain, wide) = (ink(&l, 0), ink(&l, 2));
|
||||
assert!(wide.width() > plain.width() * 1.4 && (wide.center().x - plain.center().x).abs() < 1e-6, "{plain:?} {wide:?}");
|
||||
}
|
||||
|
||||
/// With Skew on a curved vertical path, every glyph's axis along the path follows the tangent and
|
||||
/// its axis across the path stays upright on the page: an upright glyph's top runs back along the
|
||||
/// tangent and its own x axis points up the page, and a letter lying on its side keeps its top up
|
||||
|
||||
+3
-2
@@ -6374,8 +6374,9 @@ with binding punctuation (`Tg`) on, which opens as `forced`. `none` isn't saved.
|
||||
`text.setFormat` and `text.setRangeStyle {tcyUpDown?, tcyLeftRight?}` set the Character panel menu's Tate-chu-yoko
|
||||
Settings of the characters, in points from -1296 to 1296 (document points, as `baselineShift`). In vertical type a
|
||||
block of tate-chu-yoko characters (`tateChuYoko`) moves up by a positive Up/Down and down by a negative one, and right
|
||||
by a positive Left/Right and left by a negative one. The block moves as one piece with the values of its first
|
||||
character, and keeps its one em down the column, its caret positions and its hit region. Two or three digits set
|
||||
by a positive Left/Right and left by a negative one. On a vertical path, a positive Up/Down moves the block toward the
|
||||
path's start and a positive Left/Right moves it to the path's left, with every path effect. The block moves as one piece
|
||||
with the values of its first character, and keeps its one em down the column, its caret positions and its hit region. Two or three digits set
|
||||
across the column without the mark do not move, and horizontal type keeps the values without moving the characters.
|
||||
Neither value turns `tateChuYoko` on. Both are 0 by default and saved only when not 0, and character styles carry
|
||||
them. A value that is not a number is refused with `` `tcyUpDown` must be a number ``, and one outside the range with
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user