diff --git a/internal/web/static/paste-lines.js b/internal/web/static/paste-lines.js index 231a473..41537f9 100644 --- a/internal/web/static/paste-lines.js +++ b/internal/web/static/paste-lines.js @@ -28,9 +28,11 @@ body.innerHTML = out.join(''); } - // One .gutline block per visual row. Numbers are placed at the gutter row - // whose top matches their .codeline's top; filler rows pad the gaps so - // alignment is driven by measured geometry, not by uniform row counts. + // One .gutline block per visual row. The gutter must reproduce the code + // column's REAL rendered rows: each number goes on the visual row whose top + // matches its .codeline's top (a line wrapping to N rows gets its number on + // the FIRST of those rows), and filler rows pad the gaps. Geometry is + // measured, not derived from span counts or heights. function renumber() { var lines = body.querySelectorAll('.codeline'); if (!wrapOn() || !lines.length) { @@ -42,11 +44,8 @@ return; } - // Measure each logical line's geometry. Reading all rects first avoids - // interleaved layout reads/writes. A line's number goes on the visual row - // whose top matches the line's top; the gutter is sized to cover the - // deepest row any line STARTS on (a trailing empty logical line renders - // with zero height but still needs its number row). + // Read all rects first: batching layout reads before the writes below + // avoids interleaved read/write reflows. var bodyTop = body.getBoundingClientRect().top; var lh = parseFloat(getComputedStyle(body).lineHeight) || 1; var rowIdx = []; @@ -76,15 +75,16 @@ // settle a hair under N * line-height after the gutter is rebuilt; a // gutter pass that changes the code column width reflows it. Verify the // placement one frame later and re-run if any line's row moved (#167). - var placed = {}; - for (var q = 0; q < tops.length; q++) placed[q] = Math.round((tops[q] - bodyTop) / lh); + var placed = []; + for (var p = 0; p < rowIdx.length; p++) placed.push(rowIdx[p]); requestAnimationFrame(function () { var moved = false; + var bodyTop2 = body.getBoundingClientRect().top; + var lh2 = parseFloat(getComputedStyle(body).lineHeight) || lh; var tops2 = []; for (var q2 = 0; q2 < lines.length; q2++) tops2.push(lines[q2].getBoundingClientRect().top); - var bodyTop2 = body.getBoundingClientRect().top; for (var q3 = 0; q3 < tops2.length; q3++) { - if (Math.round((tops2[q3] - bodyTop2) / lh) !== placed[q3]) { moved = true; break; } + if (Math.round((tops2[q3] - bodyTop2) / lh2) !== placed[q3]) { moved = true; break; } } if (moved) renumber(); });