From c03513da4fae2e4f49f52eb4e715383872d9c8cf Mon Sep 17 00:00:00 2001 From: Akiba So Date: Tue, 23 Jun 2026 03:14:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(p0.5):=20pdfrx-backed=20PageDocumentSource?= =?UTF-8?q?=20=E2=80=94=20static=20API=20source-pin=20(step=2012/SF4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Production adapter wrapping a pdfrx PdfDocument: snapshots page sizes via the real pdfrx 2.4.4 geometry API (PdfDocument.pages, PdfPage.width/height) so the pure layout math runs on the real document. Because it lives under lib/editor/, `flutter analyze lib/editor` (the Oracle) type-checks it against the installed pdfrx every run — a version bump that renames/retypes these members now FAILS analysis instead of silently drifting (SF4 source-pin, statically). Runtime contract + layout composition tested via the pdfium-free .fromSizes ctor; the .fromDocument pin is the static guarantee (exercising it needs pdfium = device path). flutter analyze lib/editor clean; 123/123 tests (+3). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../pdf/pdfrx_page_document_source.dart | 42 +++++++++++++++++++ test/pdfrx_page_document_source_test.dart | 41 ++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 lib/editor/pdf/pdfrx_page_document_source.dart create mode 100644 test/pdfrx_page_document_source_test.dart diff --git a/lib/editor/pdf/pdfrx_page_document_source.dart b/lib/editor/pdf/pdfrx_page_document_source.dart new file mode 100644 index 0000000..3005543 --- /dev/null +++ b/lib/editor/pdf/pdfrx_page_document_source.dart @@ -0,0 +1,42 @@ +// lib/editor/pdf/pdfrx_page_document_source.dart +// +// Production [PageDocumentSource] backed by a pdfrx PdfDocument — the thin +// device-side adapter that feeds real page geometry into the (pure, tested) +// continuous-single layout math. +// +// SOURCE-PIN (plan SF4 / P0.5 step 12): this file references the exact pdfrx +// 2.4.4 page-geometry API (`PdfDocument.pages`, `PdfPage.width`, `PdfPage.height`). +// Because it lives under lib/editor/, the Oracle's `flutter analyze lib/editor` +// type-checks it against the installed pdfrx on every run — so a version bump +// that renames/retypes these members FAILS analysis instead of silently +// drifting. (Static pin only: exercising it needs pdfium at runtime, which is +// the device-gated path.) + +import 'dart:ui' show Size; + +import 'package:pdfrx/pdfrx.dart'; + +import 'pdf_document_source.dart'; + +/// Snapshots page sizes (PDF points) from an open pdfrx [PdfDocument] so the +/// layout layer can read them synchronously (pdfrx loads pages asynchronously; +/// once open, `document.pages[i].width/height` are available). +class PdfrxPageDocumentSource implements PageDocumentSource { + const PdfrxPageDocumentSource.fromSizes(this._sizes); + + /// Snapshot the intrinsic size of every page from an open [document]. + factory PdfrxPageDocumentSource.fromDocument(PdfDocument document) { + final sizes = document.pages + .map((page) => Size(page.width, page.height)) + .toList(growable: false); + return PdfrxPageDocumentSource.fromSizes(sizes); + } + + final List _sizes; + + @override + int get pageCount => _sizes.length; + + @override + Size pageSize(int index) => _sizes[index]; +} diff --git a/test/pdfrx_page_document_source_test.dart b/test/pdfrx_page_document_source_test.dart new file mode 100644 index 0000000..4658ddb --- /dev/null +++ b/test/pdfrx_page_document_source_test.dart @@ -0,0 +1,41 @@ +// Runtime contract test for the pdfrx-backed PageDocumentSource (P0.5 step 12). +// The .fromDocument factory's pdfrx-API pin is verified statically by +// `flutter analyze lib/editor` (the Oracle); here we exercise the concrete +// type's PageDocumentSource behavior + composition with the layout math via the +// pdfium-free .fromSizes constructor. + +import 'dart:ui' show Size; + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:badnote/editor/pdf/pdf_document_source.dart'; +import 'package:badnote/editor/pdf/pdfrx_page_document_source.dart'; + +void main() { + test('fromSizes implements the PageDocumentSource contract', () { + const src = PdfrxPageDocumentSource.fromSizes([ + Size(600, 800), + Size(595, 842), + ]); + expect(src, isA()); + expect(src.pageCount, 2); + expect(src.pageSize(0), const Size(600, 800)); + expect(src.pageSize(1), const Size(595, 842)); + }); + + test('composes with the fit-to-width layout math', () { + const src = PdfrxPageDocumentSource.fromSizes([ + Size(600, 800), // portrait → at width 300, height 400 + Size(800, 600), // landscape → at width 300, height 225 + ]); + final metrics = pageStackMetricsForWidth(src, 300, gap: 10); + expect(metrics.heightOf(0), closeTo(400, 1e-9)); + expect(metrics.heightOf(1), closeTo(225, 1e-9)); + expect(metrics.offsetOf(1), closeTo(410, 1e-9)); // 400 + 10 gap + }); + + test('empty document', () { + const src = PdfrxPageDocumentSource.fromSizes([]); + expect(src.pageCount, 0); + }); +}