// lib/editor/layout/double_page_layout.dart // // Two-up (spread) layout foundation for book-like reading (F2/F3 — the user's // explicit "two-page spread" ask). Pages are grouped into spread ROWS; each row // then stacks vertically exactly like continuous-single, so windowing reuses // PageStackMetrics on the row heights. // // Pure geometry (no widgets / pdfrx): the row-mounting widget is device-gated; // the pairing + row-height math is here, fully unit-tested. Building it ahead of // its phase is zero-rework-risk (it is not rendering and the P0.5 perf gate // can't invalidate pure geometry). import 'page_viewport.dart'; import '../pdf/pdf_document_source.dart'; /// Groups page indices into two-up spread rows. With [coverAlone], page 0 sits /// on its own row (a book cover / title page) and the rest pair 1-2, 3-4, …; /// otherwise pages pair from 0. A trailing odd page occupies a single-page row. List> pairIntoRows(int pageCount, {bool coverAlone = false}) { assert(pageCount >= 0); final rows = >[]; var i = 0; if (coverAlone && pageCount > 0) { rows.add([0]); i = 1; } while (i < pageCount) { if (i + 1 < pageCount) { rows.add([i, i + 1]); i += 2; } else { rows.add([i]); i += 1; } } return rows; } /// Height of each spread row when each page is fit to HALF of [columnWidth] /// (two pages share the column). A row's height is the tallest of its pages so /// both pages sit on a common baseline. Non-positive page widths contribute 0. List spreadRowHeights( PageDocumentSource source, double columnWidth, List> rows, ) { assert(columnWidth >= 0); final half = columnWidth / 2; return rows.map((row) { var tallest = 0.0; for (final pageIndex in row) { final size = source.pageSize(pageIndex); if (size.width <= 0) continue; final fit = half * (size.height / size.width); if (fit > tallest) tallest = fit; } return tallest; }).toList(growable: false); } /// Continuous-DOUBLE stacking metrics: pair pages into spread rows, then stack /// the rows vertically. [PageStackMetrics.visibleRange] over the result yields /// the visible ROW range; map rows back to pages via the [pairIntoRows] result. PageStackMetrics spreadStackMetrics( PageDocumentSource source, double columnWidth, { bool coverAlone = false, double gap = 0.0, }) { final rows = pairIntoRows(source.pageCount, coverAlone: coverAlone); return PageStackMetrics( pageHeights: spreadRowHeights(source, columnWidth, rows), gap: gap, ); }