76 lines
2.6 KiB
Dart
76 lines
2.6 KiB
Dart
|
|
// 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<List<int>> pairIntoRows(int pageCount, {bool coverAlone = false}) {
|
||
|
|
assert(pageCount >= 0);
|
||
|
|
final rows = <List<int>>[];
|
||
|
|
var i = 0;
|
||
|
|
if (coverAlone && pageCount > 0) {
|
||
|
|
rows.add(<int>[0]);
|
||
|
|
i = 1;
|
||
|
|
}
|
||
|
|
while (i < pageCount) {
|
||
|
|
if (i + 1 < pageCount) {
|
||
|
|
rows.add(<int>[i, i + 1]);
|
||
|
|
i += 2;
|
||
|
|
} else {
|
||
|
|
rows.add(<int>[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<double> spreadRowHeights(
|
||
|
|
PageDocumentSource source,
|
||
|
|
double columnWidth,
|
||
|
|
List<List<int>> 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,
|
||
|
|
);
|
||
|
|
}
|