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.
This commit is contained in:
aiongg
2026-10-10 15:57:10 +00:00
parent f9fb640229
commit 5365d0f7ca
2 changed files with 41 additions and 17 deletions
+19 -17
View File
@@ -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<f64>,
align_left: bool,
tabs: Option<(&[designcraft_doc::TabStop], f64)>,
tabs: (&[designcraft_doc::TabStop], f64),
) -> Option<DropCap> {
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);
+22
View File
@@ -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]