| // README "Alignment rule": every note's top sits exactly at its paragraph's top. | |
| // spacer[i] = max(0, noteTop[i] + noteHeight[i] + gap - nextParagraphTop). | |
| // Applied spacers shift all later paragraphs; callers pass naturalTop (measured top | |
| // minus spacers already applied) so repeated runs are idempotent. | |
| export function computeLayout(items, gap = 26) { | |
| const tops = {}, spacers = {}; | |
| let shift = 0; | |
| for (let i = 0; i < items.length; i++) { | |
| const { id, naturalTop, noteHeight } = items[i]; | |
| const top = naturalTop + shift; | |
| tops[id] = top; | |
| if (i < items.length - 1) { | |
| const nextTop = items[i + 1].naturalTop + shift; | |
| const overflow = Math.max(0, top + noteHeight + gap - nextTop); | |
| spacers[id] = overflow; | |
| shift += overflow; | |
| } else { | |
| spacers[id] = 0; | |
| } | |
| } | |
| return { tops, spacers }; | |
| } | |