From 5365d0f7ca2f4e4c131e792a532f0c3021314a9f Mon Sep 17 00:00:00 2001 From: aiongg Date: Sat, 10 Oct 2026 15:57:10 +0000 Subject: [PATCH] Drop caps: tabs in right-to-left drop caps advance to the tab stops Measured from the column's right edge, past the right indent. --- crates/compose/src/lib.rs | 36 +++++++++++++++++++----------------- crates/compose/src/tests.rs | 22 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/crates/compose/src/lib.rs b/crates/compose/src/lib.rs index 96a0bd997..7e2163ddc 100644 --- a/crates/compose/src/lib.rs +++ b/crates/compose/src/lib.rs @@ -695,7 +695,8 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C let drop_grid = cur_frame.and_then(|f| f.grid).filter(|_| pp.grid_align == GridAlign::AllLines).map(|g| g.1); // Align Left Edge (left-to-right paragraphs). let drop_align = pp.drop_cap_align_left && pp.direction == designcraft_doc::TextDirection::LeftToRight; - let drop_tabs = (pp.direction == designcraft_doc::TextDirection::LeftToRight).then_some((&pp.tabs[..], pp.left_indent)); + // Tab stops for the drop cap, with the start-side indent (the right one, right to left). + let drop_tabs = (&pp.tabs[..], if pp.direction == designcraft_doc::TextDirection::RightToLeft { pp.right_indent } else { pp.left_indent }); if !pp.nested_line_styles.is_empty() && cur.fi < frames.len() { // Nested line styles: find where the first lines end in this column, restyle them, and // look again (the style changes the widths) until the lines settle. @@ -2869,9 +2870,10 @@ struct DropCap { /// it so its cap height reaches from the first line's cap height down to line `lines`' baseline, /// measured in the leading and cap height of the text after it. A one-line drop cap keeps its own /// size when that is smaller. On a baseline grid of increment `grid`, lines are that many grid -/// steps apart. With `align_left` its ink starts at the indent (see [`DropCap::lsb`]). `tabs` -/// (stops and left indent, left-to-right paragraphs) sets the tabs inside it. Spaces right after -/// it join it at its size without widening it. +/// steps apart. With `align_left` its ink starts at the indent (see [`DropCap::lsb`]). Tabs inside +/// it advance to the stops of `tabs` (stops and the start-side indent; in right-to-left paragraphs +/// both measured from the column's right edge). Spaces right after it join it at its size without +/// widening it. #[allow(clippy::too_many_arguments)] fn split_drop_cap( db: &ScopedFonts<'_>, @@ -2882,7 +2884,7 @@ fn split_drop_cap( base_cap: f64, grid: Option, align_left: bool, - tabs: Option<(&[designcraft_doc::TabStop], f64)>, + tabs: (&[designcraft_doc::TabStop], f64), ) -> Option { let n = glyphs.iter().take_while(|g| g.byte < end).count(); if n == 0 { @@ -2926,19 +2928,19 @@ fn split_drop_cap( } _ => 0.0, }; - // Tabs advance to the paragraph's stops, measured from the column edge as in its lines. - if let Some((stops, left_indent)) = tabs { - let mut x = left_indent - lsb; - for i in 0..dc.len() { - let tab = match dc.get(i) { - Some(g) if g.ch == '\t' => Some(tab_advance(stops, left_indent, x, dc.get(i + 1..).unwrap_or_default()).0), - _ => None, - }; - if let (Some(w), Some(g)) = (tab, dc.get_mut(i)) { - g.adv = w; - } - x += dc.get(i).map_or(0.0, |g| g.adv); + // Tabs advance to the paragraph's stops, measured from the column's start edge. Right to left, + // its glyphs run from that (right) edge in this order (see `place_drop_cap`). + let (stops, indent) = tabs; + let mut x = indent - lsb; + for i in 0..dc.len() { + let tab = match dc.get(i) { + Some(g) if g.ch == '\t' => Some(tab_advance(stops, indent, x, dc.get(i + 1..).unwrap_or_default()).0), + _ => None, + }; + if let (Some(w), Some(g)) = (tab, dc.get_mut(i)) { + g.adv = w; } + x += dc.get(i).map_or(0.0, |g| g.adv); } let width = dc.iter().map(|g| g.adv).sum(); dc.append(&mut after); diff --git a/crates/compose/src/tests.rs b/crates/compose/src/tests.rs index 59723e2e2..302374d8e 100644 --- a/crates/compose/src/tests.rs +++ b/crates/compose/src/tests.rs @@ -2209,6 +2209,28 @@ fn one_line_drop_cap_keeps_its_size_and_sets_its_tabs() { assert!((text_x(l, 3) - 40.0).abs() < 1e-6, "the text starts at the left tab: {}", text_x(l, 3)); } +/// Right to left, the tabs in a drop cap advance to tab stops measured from the column's right +/// edge. +#[test] +fn right_to_left_drop_cap_sets_its_tabs_from_the_right_edge() { + let stop = |position, align| designcraft_doc::TabStop { position, align, leader: String::new(), align_on: String::new() }; + let para = ParaAttrs { + direction: Some(designcraft_doc::TextDirection::RightToLeft), + align: Some(Align::Right), + tabs: Some(vec![stop(30.0, TabAlign::Right), stop(40.0, TabAlign::Left)]), + ..drop_cap(1, 3) + }; + let (d, sid, _) = drop_doc(&format!("\t7\t{LOREM}"), para); + let cs = compose_story(&d, sid, &ComposeOptions::default()); + let l = &cs.frames[0].lines[0]; + let seven = l.glyphs.iter().find(|g| g.byte == 1).unwrap(); + // The 300 pt column's right edge is at 300: the number's far (left) end is 30 pt from it. + assert!((seven.x - 270.0).abs() < 1e-6, "the number ends at the right tab: {}", seven.x); + // The text beside it starts at the left tab, 40 pt from the edge. + let right = l.glyphs.iter().filter(|g| g.byte >= 3 && g.visible).map(|g| g.x + g.adv).fold(f64::NEG_INFINITY, f64::max); + assert!(right <= 260.0 + 1e-6 && right > 255.0, "text ends at {right}"); +} + /// A rule in Text Color takes the colour of the paragraph's text: the first character's for the /// rule above, the last character's for the rule below. A swatch colour is used as is. #[test]