mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-24 10:16:59 -06:00
Merge pull request #1184 from seanth/drop-cap-size
(fix)Drop caps: scale with the cap's point size, as InDesign doesDrop cap size
This commit is contained in:
@@ -1716,7 +1716,8 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C
|
||||
drop_box = Some(DropCapBox {
|
||||
end: dc.end,
|
||||
baseline: baseline + dc.drop,
|
||||
rect: Rect::new(at, baseline - asc, at + dc.width, baseline + dc.drop + desc),
|
||||
// From its line's top, or its own top when it rises above that line.
|
||||
rect: Rect::new(at, (baseline - asc).min(baseline + dc.drop - dc.cap), at + dc.width, baseline + dc.drop + desc),
|
||||
});
|
||||
}
|
||||
ruby::annotate(db, &mut styles_tab, &story.text, &mut placed, doc.settings.glyph_fallback, f.vertical);
|
||||
@@ -3764,11 +3765,15 @@ struct DropCap {
|
||||
/// Its baseline below the first line's: `lines − 1` leadings of the text after it (grid steps
|
||||
/// on a baseline grid), wherever line `lines` is set.
|
||||
drop: f64,
|
||||
/// Its cap height as set (how far its top sits above its baseline).
|
||||
cap: f64,
|
||||
}
|
||||
|
||||
/// Take the drop cap (the glyphs before byte `end`) off the front of a paragraph's glyphs and scale
|
||||
/// 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
|
||||
/// measured in the leading and cap height of the text after it: the size of a cap set at that text's
|
||||
/// size. As in InDesign, a cap set larger (or smaller) is scaled by its size over that text's,
|
||||
/// keeping its baseline, so it rises above the first line (or falls short of 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 inside
|
||||
/// it advance to the stops of `tabs` (stops and the start-side indent; in right-to-left paragraphs
|
||||
@@ -3796,14 +3801,19 @@ fn split_drop_cap(
|
||||
let mut after: Vec<Glyph> = glyphs.drain(..spaces).collect();
|
||||
let end = after.last().map_or(end, |g| g.byte.saturating_add(g.len).max(end));
|
||||
let body = glyphs.iter().find(|g| g.len > 0 && g.cap > 0.0);
|
||||
let (lead, cap) = body.map_or((base_leading, base_cap), |g| (g.leading, g.cap));
|
||||
let (lead, cap, body_size) = body.map_or((base_leading, base_cap, None), |g| (g.leading, g.cap, Some(g.size)));
|
||||
let lead = match grid.map(|inc| (lead / inc - 1e-6).ceil().max(1.0) * inc) {
|
||||
Some(pitch) if pitch.is_finite() && pitch > 0.0 => pitch,
|
||||
_ => lead,
|
||||
};
|
||||
let drop = (lines - 1) as f64 * lead;
|
||||
let own = dc.iter().map(|g| g.cap).fold(0.0, f64::max);
|
||||
let k = if own > 0.0 { (drop + cap) / own } else { 1.0 };
|
||||
// The cap's tallest glyph sets its height; its point size over the body's scales the fit.
|
||||
let (own, own_size) = dc.iter().fold((0.0, 0.0), |(c, s), g| if g.cap > c { (g.cap, g.size) } else { (c, s) });
|
||||
let ratio = match body_size {
|
||||
Some(b) if b > 0.0 && own_size > 0.0 && (own_size / b).is_finite() => own_size / b,
|
||||
_ => 1.0,
|
||||
};
|
||||
let k = if own > 0.0 { (drop + cap) / own * ratio } else { 1.0 };
|
||||
let k = if lines == 1 { k.min(1.0) } else { k };
|
||||
let k = if k.is_finite() && k > 0.0 { k.min(1000.0) } else { 1.0 };
|
||||
for g in dc.iter_mut().chain(after.iter_mut()) {
|
||||
@@ -3844,7 +3854,8 @@ fn split_drop_cap(
|
||||
}
|
||||
let width = dc.iter().map(|g| g.adv).sum();
|
||||
dc.append(&mut after);
|
||||
Some(DropCap { glyphs: dc, end, lines, width, drop, lsb })
|
||||
let cap = dc.iter().map(|g| g.cap).fold(0.0, f64::max);
|
||||
Some(DropCap { glyphs: dc, end, lines, width, drop, lsb, cap })
|
||||
}
|
||||
|
||||
impl DropCap {
|
||||
|
||||
@@ -3646,6 +3646,37 @@ fn right_to_left_drop_cap_sets_its_tabs_from_the_right_edge() {
|
||||
assert!(right <= 260.0 + 1e-6 && right > 255.0, "text ends at {right}");
|
||||
}
|
||||
|
||||
/// The cap's point size scales it, as in InDesign: at the body's size (10 pt) it fills its lines
|
||||
/// exactly; set larger or smaller it is scaled by its size over the body's, keeping its baseline,
|
||||
/// so it rises above the first line (or falls short of it). The body's lines don't move, and the
|
||||
/// cap's box (highlight, caret) reaches the top of a cap that rises above its line.
|
||||
#[test]
|
||||
fn drop_cap_scales_with_its_point_size() {
|
||||
let set = |size: f64| {
|
||||
let (mut d, sid, _) = drop_doc(&[LOREM; 2].join(" "), drop_cap(3, 1));
|
||||
d.story_mut(sid).unwrap().format_chars(0..1, |f| f.over.size = Some(size));
|
||||
let cs = compose_story(&d, sid, &ComposeOptions::default());
|
||||
all_lines(&cs).into_iter().cloned().collect::<Vec<Line>>()
|
||||
};
|
||||
let base = set(10.0);
|
||||
let (b0, b2) = (base[0].baseline, base[2].baseline);
|
||||
let body = base[0].glyphs.iter().find(|g| g.byte == 1).unwrap();
|
||||
let fitted = cap_of(&base[0].glyphs[0]);
|
||||
assert!((b2 - fitted - (b0 - cap_of(body))).abs() < 0.01, "at the body's size the cap fills its lines");
|
||||
for (size, scale) in [(15.0, 1.5), (7.5, 0.75)] {
|
||||
let l = set(size);
|
||||
let dc = &l[0].glyphs[0];
|
||||
for (a, b) in l.iter().zip(&base) {
|
||||
assert!((a.baseline - b.baseline).abs() < 1e-6, "{size} pt: the lines stay where they were");
|
||||
}
|
||||
assert!((l[0].baseline + dc.y - b2).abs() < 1e-6, "{size} pt: the cap sits on line 3's baseline");
|
||||
assert!((cap_of(dc) - fitted * scale).abs() < 0.01, "{size} pt: cap height {} vs {}", cap_of(dc), fitted * scale);
|
||||
let top = (l[0].baseline - l[0].ascent).min(b2 - cap_of(dc));
|
||||
assert!((l[0].drop_cap.unwrap().rect.y0 - top).abs() < 1e-6, "{size} pt: the box starts at the line's or the cap's top");
|
||||
}
|
||||
assert!(b2 - fitted * 1.5 < b0 - base[0].ascent, "a 15 pt cap rises above the first line");
|
||||
}
|
||||
|
||||
/// 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]
|
||||
|
||||
Reference in New Issue
Block a user