mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 07:42:42 +00:00
Merge pull request #7409 from dotCooCoo/fix/rtl-tab-end-trim
Type: right-to-left tabbed lines are measured with their line-end trim where it is drawn
This commit is contained in:
@@ -243,11 +243,16 @@ pub(super) fn drawn_after_tab<'g>(sg: &'g [SGlyph], drawn: &'g [usize], k: usize
|
||||
/// them) set on a line whose content starts `x_start` from the tab origin: tabs of the text take
|
||||
/// what [`tab_advance`] gives them from where the glyphs drawn before them end, aligning the glyphs
|
||||
/// drawn after them, not the width of their glyph. The tab of a list marker (length 0) keeps its
|
||||
/// own width.
|
||||
pub(super) fn drawn_width(sg: &[SGlyph], drawn: &[usize], tabs: &[vectorcraft_doc::TabStop], x_start: f64) -> f64 {
|
||||
/// own width. With `end_trim` (a glyph index and an amount), that glyph is set that much narrower
|
||||
/// where it is drawn (a line's end trim), before the tabs drawn after it take their stops.
|
||||
pub(super) fn drawn_width(sg: &[SGlyph], drawn: &[usize], tabs: &[vectorcraft_doc::TabStop], x_start: f64, end_trim: Option<(usize, f64)>) -> f64 {
|
||||
let mut x = x_start;
|
||||
for (k, g) in drawn.iter().enumerate().filter_map(|(k, &j)| sg.get(j).map(|g| (k, g))) {
|
||||
x += if g.ch == '\t' && g.len > 0 { tab_advance(tabs, 0.0, x, drawn_after_tab(sg, drawn, k)).0 } else { g.adv };
|
||||
for (k, j, g) in drawn.iter().enumerate().filter_map(|(k, &j)| sg.get(j).map(|g| (k, j, g))) {
|
||||
x += if g.ch == '\t' && g.len > 0 {
|
||||
tab_advance(tabs, 0.0, x, drawn_after_tab(sg, drawn, k)).0
|
||||
} else {
|
||||
g.adv - end_trim.filter(|&(at, _)| at == j).map_or(0.0, |(_, trim)| trim)
|
||||
};
|
||||
}
|
||||
x - x_start
|
||||
}
|
||||
|
||||
@@ -459,8 +459,9 @@ fn place_flow_line(cx: &mut Ctx<'_>, pen: &mut Pen<'_>, context: &mut PlacementC
|
||||
.filter(|&j| j < trimmed)
|
||||
.collect();
|
||||
let hyphen = if hyphenated { end.checked_sub(1).and_then(|k| glyphs.get(k)).map_or(0.0, |g| hyphen_glyph(g).adv) } else { 0.0 };
|
||||
let credit = trimmed.checked_sub(1).filter(|&k| k >= start).and_then(|k| line_end.get(k)).copied().unwrap_or(0.0);
|
||||
drawn_width(glyphs, &drawn, ¶.tabs, ind_l) + hyphen - credit <= width + EPS
|
||||
// The last glyph's end trim comes off where it is drawn, before the tabs after it.
|
||||
let credit = trimmed.checked_sub(1).filter(|&k| k >= start).and_then(|k| line_end.get(k).map(|&trim| (k, trim)));
|
||||
drawn_width(glyphs, &drawn, ¶.tabs, ind_l, credit) + hyphen <= width + EPS
|
||||
};
|
||||
let fits: Option<&dyn Fn(usize, bool) -> bool> = (!cx.vertical && bidi.is_some_and(|b| b.has_rtl())).then_some(&drawn_fits);
|
||||
let line = |ty| {
|
||||
@@ -710,9 +711,11 @@ fn prepare_line_geometry(cx: &Ctx<'_>, t: &TextObject, paragraph: &ParagraphData
|
||||
let line_end = sg.get(position.end).map_or(paragraph.pr.end, |g| g.byte);
|
||||
let order = visual_order(paragraph.bidi.as_ref().filter(|_| !cx.vertical), paragraph.pr.start, line_start..line_end, &sg[index..position.end]);
|
||||
// The line is as wide as it is drawn: its glyphs before its trailing spaces in drawing order,
|
||||
// each tab up to its stop from where the glyphs drawn before it end.
|
||||
// each tab up to its stop from where the glyphs drawn before it end, with the last glyph's end
|
||||
// trim taken off where it is drawn.
|
||||
let measured: Vec<usize> = order.iter().map(|&offset| index + offset).filter(|&j| j < trimmed).collect();
|
||||
let measured_width = drawn_width(sg, &measured, ¶.tabs, position.ind_l) + hyphen.as_ref().map_or(0.0, |g| g.adv) - end_trim;
|
||||
let trim_at = trimmed.checked_sub(1).filter(|&k| k >= index).map(|k| (k, end_trim));
|
||||
let measured_width = drawn_width(sg, &measured, ¶.tabs, position.ind_l, trim_at) + hyphen.as_ref().map_or(0.0, |g| g.adv);
|
||||
// The glyphs before `stop` in visual order, each with the advance it is set with. A list
|
||||
// marker does not hang: it is counted with no width.
|
||||
let visible = |stop: usize| -> (Vec<usize>, Vec<(&SGlyph, f64)>) {
|
||||
|
||||
@@ -668,6 +668,36 @@ fn a_right_to_left_line_with_a_tab_and_many_breaks_wraps_when_it_does_not_fit_as
|
||||
}
|
||||
}
|
||||
|
||||
/// A right-to-left line with a tab whose last character loses its Japanese–Latin space at the
|
||||
/// line's end is measured with that space taken off where the character is drawn, before the tab:
|
||||
/// right aligned, it ends on the frame's right edge, and it wraps when it does not fit as drawn.
|
||||
#[test]
|
||||
fn a_right_to_left_tabbed_line_is_measured_without_its_line_end_space() {
|
||||
use vectorcraft_doc::Mojikumi;
|
||||
// א (0), ב (2), tab (4), ג (5), 漢 (7), then a Hebrew word too long for the frame: the first
|
||||
// line ends after 漢, which loses the quarter em it had before the Hebrew word.
|
||||
let text = format!("אב\tג漢{}", "ד".repeat(40));
|
||||
let lay = |width: f64| {
|
||||
let mut t = area(&text, width, Justify::Right, no_ranges());
|
||||
t.para.mojikumi = Mojikumi::GyomatsuYakumonoZenkaku;
|
||||
t.para.tabs = vec![TabStop { position: 150.0, align: TabAlign::Left, leader: String::new(), align_on: '.' }];
|
||||
layout(db(), &t)
|
||||
};
|
||||
let l = lay(300.0);
|
||||
assert!(l.lines.len() >= 2, "wraps: {} line(s)", l.lines.len());
|
||||
assert!(l.glyphs.iter().filter(|g| g.line == 0).all(|g| g.byte < 10), "the first line ends after 漢");
|
||||
let (_, right) = drawn_extent(&l, &text, 0);
|
||||
assert!((right - 300.0).abs() < 1e-6, "ends on the right edge: {right}");
|
||||
assert!((l.lines[0].x1 - right).abs() < 1e-6, "the line's record ends where it is drawn: {}", l.lines[0].x1);
|
||||
// Drawn, that line is as wide as the stop and the text after the tab (אב). In a frame a point
|
||||
// narrower, less than the quarter em, it does not fit and the first line stays in the frame.
|
||||
let after_tab: f64 = l.glyphs.iter().filter(|g| g.byte < 4).map(|g| g.advance).sum();
|
||||
let width = 150.0 + after_tab - 1.0;
|
||||
let l = lay(width);
|
||||
let (left, right) = drawn_extent(&l, &text, 0);
|
||||
assert!(left >= -1e-6 && right <= width + 1e-6, "the first line stays in the frame: {left} {right} (frame {width})");
|
||||
}
|
||||
|
||||
/// Point type has no measure to fill: a justified line with a tab keeps its natural width.
|
||||
#[test]
|
||||
fn justified_point_type_with_a_tab_keeps_its_width() {
|
||||
|
||||
Reference in New Issue
Block a user