fix(reflow): wrapped bullet continuation got bullet-gap spacing instead of wrap spacing
buildBulletItem computed `leading` as the median over ALL paragraph line gaps. In a bullet list the bullet-to-bullet gap (~14.2pt) is larger than the within-bullet wrap gap (~12.2pt) and dominates the median, so a newly-wrapped continuation line was placed ~2pt too low (visible extra spacing between e.g. the Architecture line and its "Design Patterns…" continuation). Use the WRAP spacing instead: prefer the edited item's own internal line gap (if it already wraps), then any hanging-continuation gap in the block, then the all-lines median, then font*1.2. Only affects multi-line (wrapped) bullet items; single-line edits emit no continuation so are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6edec0cf22
commit
63369d4259
@@ -108,12 +108,27 @@ export function buildBulletItem(para: any, runLineIndex: number): { subPara: any
|
||||
const itemLines = lines.slice(start, end);
|
||||
if (!itemLines.length) return null;
|
||||
|
||||
// Paragraph leading (line-spacing) from all baselines — used so a newly-wrapped line gets the
|
||||
// document's true spacing, not a guess.
|
||||
const baselines = lines.map((l: any) => l.baseline_y).filter((b: any) => typeof b === 'number');
|
||||
const deltas: number[] = [];
|
||||
for (let i = 0; i < baselines.length - 1; i++) deltas.push(Math.abs(baselines[i] - baselines[i + 1]));
|
||||
const leading = deltas.length ? median(deltas) : (itemLines[0].runs?.[0]?.font_size ?? 12) * 1.2;
|
||||
// Leading for a NEWLY-WRAPPED line must be the WRAP spacing — the gap between a bullet line and its
|
||||
// own hanging continuation — NOT the bullet-to-bullet spacing, which is often larger (list items
|
||||
// carry extra spacing). The paragraph-wide median is dominated by the bullet gaps and over-spaces a
|
||||
// wrapped continuation (e.g. 14.2pt bullet gap vs 12.2pt wrap). Prefer: (1) the edited item's own
|
||||
// internal line spacing if it already wraps, (2) any wrap spacing elsewhere in the block (a line
|
||||
// hanging at the text indent, i.e. not flush-left), (3) the all-lines median, (4) font*1.2.
|
||||
const hasBl = (i: number) => typeof lines[i].baseline_y === 'number';
|
||||
const dlt = (i: number) => Math.abs(lines[i].baseline_y - lines[i + 1].baseline_y);
|
||||
const itemDeltas: number[] = [];
|
||||
for (let i = start; i + 1 < end; i++) if (hasBl(i) && hasBl(i + 1)) itemDeltas.push(dlt(i));
|
||||
const wrapDeltas: number[] = [];
|
||||
const allDeltas: number[] = [];
|
||||
for (let i = 0; i + 1 < lines.length; i++) {
|
||||
if (!hasBl(i) || !hasBl(i + 1)) continue;
|
||||
allDeltas.push(dlt(i));
|
||||
if (!flushLeft(lines[i + 1])) wrapDeltas.push(dlt(i)); // line i+1 is a hanging continuation
|
||||
}
|
||||
const leading = itemDeltas.length ? median(itemDeltas)
|
||||
: wrapDeltas.length ? median(wrapDeltas)
|
||||
: allDeltas.length ? median(allDeltas)
|
||||
: (itemLines[0].runs?.[0]?.font_size ?? 12) * 1.2;
|
||||
|
||||
// Strip a leading bullet marker (+ any leading space) from the first line; the text indent is
|
||||
// where the real text begins (which the wrapped lines already hang to).
|
||||
|
||||
Reference in New Issue
Block a user