@reekon-tools/react-native-pdf-canvas 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PdfCanvas.podspec +80 -0
- package/README.md +1391 -0
- package/android/build.gradle +175 -0
- package/android/consumer-rules.pro +3 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/android/gradle.properties +2 -0
- package/android/gradlew +248 -0
- package/android/gradlew.bat +98 -0
- package/android/settings.gradle +34 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasCancellationTest.java +226 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasConcurrencyTest.java +239 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasDeviceTimingTest.java +251 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasFailureTest.java +221 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasJsiTransportTest.java +783 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPhaseTimingTest.java +1388 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasPoisonGuardTest.java +98 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasQuirksTest.java +324 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PdfCanvasRasterizerTest.java +775 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/PoisonsTheProcess.java +20 -0
- package/android/src/androidTest/java/tools/reekon/pdfcanvas/TestPdfs.java +558 -0
- package/android/src/jsi/cpp/CMakeLists.txt +33 -0
- package/android/src/jsi/cpp/pdfcanvas-jsi.cpp +469 -0
- package/android/src/jsi/java/tools/reekon/pdfcanvas/jsi/PdfCanvasPixelBridge.java +140 -0
- package/android/src/main/AndroidManifest.xml +7 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PageGeometry.java +45 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfCanvasRasterizer.java +564 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfErrorCode.java +33 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfPlatformQuirks.java +277 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfPreflight.java +214 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfRasterException.java +30 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/PdfRendererPool.java +234 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterCancellation.java +47 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterPixels.java +143 -0
- package/android/src/main/java/tools/reekon/pdfcanvas/RasterRequest.java +95 -0
- package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasModule.java +626 -0
- package/android/src/reactnative/java/tools/reekon/pdfcanvas/rn/PdfCanvasPackage.java +33 -0
- package/android/tools/compile-gate.sh +289 -0
- package/dist/cache.d.ts +161 -0
- package/dist/cache.js +389 -0
- package/dist/clock.d.ts +21 -0
- package/dist/clock.js +31 -0
- package/dist/controller.d.ts +131 -0
- package/dist/controller.js +1002 -0
- package/dist/geometry.d.ts +96 -0
- package/dist/geometry.js +248 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +50 -0
- package/dist/index.native.d.ts +21 -0
- package/dist/index.native.js +23 -0
- package/dist/index.web.d.ts +24 -0
- package/dist/index.web.js +26 -0
- package/dist/layout.d.ts +78 -0
- package/dist/layout.js +152 -0
- package/dist/peer-check.d.ts +83 -0
- package/dist/peer-check.js +183 -0
- package/dist/planner.d.ts +35 -0
- package/dist/planner.js +530 -0
- package/dist/policy.d.ts +14 -0
- package/dist/policy.js +168 -0
- package/dist/rasterizer/android.d.ts +121 -0
- package/dist/rasterizer/android.js +126 -0
- package/dist/rasterizer/buffer.d.ts +18 -0
- package/dist/rasterizer/buffer.js +18 -0
- package/dist/rasterizer/fake.d.ts +92 -0
- package/dist/rasterizer/fake.js +433 -0
- package/dist/rasterizer/index.d.ts +38 -0
- package/dist/rasterizer/index.js +69 -0
- package/dist/rasterizer/index.native.d.ts +19 -0
- package/dist/rasterizer/index.native.js +80 -0
- package/dist/rasterizer/index.web.d.ts +63 -0
- package/dist/rasterizer/index.web.js +75 -0
- package/dist/rasterizer/ingest.d.ts +180 -0
- package/dist/rasterizer/ingest.js +307 -0
- package/dist/rasterizer/ios.d.ts +84 -0
- package/dist/rasterizer/ios.js +155 -0
- package/dist/rasterizer/native-bridge.d.ts +261 -0
- package/dist/rasterizer/native-bridge.js +413 -0
- package/dist/rasterizer/web/client.d.ts +84 -0
- package/dist/rasterizer/web/client.js +359 -0
- package/dist/rasterizer/web/engine.d.ts +69 -0
- package/dist/rasterizer/web/engine.js +308 -0
- package/dist/rasterizer/web/index.d.ts +26 -0
- package/dist/rasterizer/web/index.js +21 -0
- package/dist/rasterizer/web/pdfium.d.ts +213 -0
- package/dist/rasterizer/web/pdfium.js +170 -0
- package/dist/rasterizer/web/protocol.d.ts +138 -0
- package/dist/rasterizer/web/protocol.js +37 -0
- package/dist/rasterizer/web/session.d.ts +106 -0
- package/dist/rasterizer/web/session.js +413 -0
- package/dist/rasterizer/web/worker.d.ts +44 -0
- package/dist/rasterizer/web/worker.js +76 -0
- package/dist/react/PdfContentView.d.ts +98 -0
- package/dist/react/PdfContentView.js +189 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +3 -0
- package/dist/react/usePdfDocument.d.ts +134 -0
- package/dist/react/usePdfDocument.js +275 -0
- package/dist/react/usePdfLayer.d.ts +94 -0
- package/dist/react/usePdfLayer.js +388 -0
- package/dist/skia/index.d.ts +26 -0
- package/dist/skia/index.js +25 -0
- package/dist/skia/index.native.d.ts +14 -0
- package/dist/skia/index.native.js +16 -0
- package/dist/skia/index.web.d.ts +12 -0
- package/dist/skia/index.web.js +14 -0
- package/dist/skia/resolve.native.d.ts +18 -0
- package/dist/skia/resolve.native.js +22 -0
- package/dist/skia/resolve.web.d.ts +41 -0
- package/dist/skia/resolve.web.js +62 -0
- package/dist/skia/skia-api.d.ts +94 -0
- package/dist/skia/skia-api.js +113 -0
- package/dist/testing/index.d.ts +27 -0
- package/dist/testing/index.js +28 -0
- package/dist/testing/scenes.d.ts +100 -0
- package/dist/testing/scenes.js +267 -0
- package/dist/types.d.ts +611 -0
- package/dist/types.js +21 -0
- package/dist/web-init.d.ts +68 -0
- package/dist/web-init.js +64 -0
- package/ios/Package.swift +58 -0
- package/ios/Sources/PdfCanvasBridge/PdfCanvasModule.mm +743 -0
- package/ios/Sources/PdfCanvasBridge/include/PdfCanvasModule.h +36 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasCancellation.m +38 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasDocumentPool.m +389 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasErrorCode.m +57 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasPageGeometry.m +44 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasPageTransform.m +82 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasPixelSink.m +37 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasPreflight.m +158 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterPixels.m +80 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterRequest.m +65 -0
- package/ios/Sources/PdfCanvasCore/PdfCanvasRasterizer.m +548 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasCancellation.h +48 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasCore.h +26 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasDocumentPool.h +122 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasErrorCode.h +80 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageGeometry.h +79 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPageTransform.h +129 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPixelSink.h +100 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasPreflight.h +65 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterPixels.h +111 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterRequest.h +85 -0
- package/ios/Sources/PdfCanvasCore/include/PdfCanvasRasterizer.h +163 -0
- package/ios/Sources/PdfCanvasSlots/PdfCanvasSlots.mm +297 -0
- package/ios/Sources/PdfCanvasSlots/include/PdfCanvasSlots.h +140 -0
- package/ios/Tests/PdfCanvasCoreTests/ConcurrencyTests.swift +358 -0
- package/ios/Tests/PdfCanvasCoreTests/FailureTests.swift +262 -0
- package/ios/Tests/PdfCanvasCoreTests/GeometryTests.swift +173 -0
- package/ios/Tests/PdfCanvasCoreTests/PreviewTests.swift +114 -0
- package/ios/Tests/PdfCanvasCoreTests/RasterAssertions.swift +158 -0
- package/ios/Tests/PdfCanvasCoreTests/RasterizerTests.swift +544 -0
- package/ios/Tests/PdfCanvasCoreTests/SinkTests.swift +287 -0
- package/ios/Tests/PdfCanvasCoreTests/SlotsTests.swift +396 -0
- package/ios/Tests/PdfCanvasCoreTests/TestPdfs.swift +639 -0
- package/ios/Tests/PdfCanvasCoreTests/TimingTests.swift +179 -0
- package/ios/Tests/PdfCanvasCoreTests/TransformTests.swift +283 -0
- package/package.json +144 -0
- package/react-native.config.js +40 -0
- package/scripts/check-skia-peer.mjs +86 -0
- package/scripts/skia-peer-check.mjs +363 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doc-space geometry: PDF points, y-down, origin at the page's top-left.
|
|
3
|
+
*
|
|
4
|
+
* Every function here is pure and total — no throws, no NaN propagation that
|
|
5
|
+
* survives a guard. These are the numbers that end up as a texture allocation
|
|
6
|
+
* and a draw rect, so a silent NaN here is a blank page or an OOM later.
|
|
7
|
+
*/
|
|
8
|
+
import type { DocRect, DocSize, PixelSize } from './types.js';
|
|
9
|
+
/** The result of aligning a doc rect to the device-pixel grid. */
|
|
10
|
+
export interface SnappedRect {
|
|
11
|
+
/** The rect actually covered by the bytes. NOT the rect you asked for. */
|
|
12
|
+
docRect: DocRect;
|
|
13
|
+
pixels: PixelSize;
|
|
14
|
+
}
|
|
15
|
+
export declare function rectArea(r: DocRect): number;
|
|
16
|
+
/**
|
|
17
|
+
* The negation is deliberate: `!(w > 0)` treats NaN as empty, where
|
|
18
|
+
* `w <= 0` would call a NaN rect non-empty and let it reach a texture alloc.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isEmptyRect(r: DocRect): boolean;
|
|
21
|
+
export declare function rectIntersects(a: DocRect, b: DocRect): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* True when `inner` lies wholly within `outer`. Touching edges count as
|
|
24
|
+
* contained; `epsilon` widens `outer` to absorb float noise at high zoom,
|
|
25
|
+
* where a rect is reconstructed from a transform every frame.
|
|
26
|
+
*/
|
|
27
|
+
export declare function rectContains(outer: DocRect, inner: DocRect, epsilon?: number): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* True when `covers` TOGETHER contain every point of `target`.
|
|
30
|
+
*
|
|
31
|
+
* WHY THIS IS NOT `covers.some(c => rectContains(c, target))`. Once a layer is
|
|
32
|
+
* tiled, the thing replacing an old raster is a GRID, and no single grid cell
|
|
33
|
+
* contains an old cell that straddles a seam. A per-rect containment test
|
|
34
|
+
* therefore reports "not covered" for a grid that re-covers the area completely
|
|
35
|
+
* — which is how the controller ended up retaining an entire previous grid on
|
|
36
|
+
* every epoch, holding two full grids through every transition.
|
|
37
|
+
*
|
|
38
|
+
* WHY NOT THE UNION RECT EITHER. `rectUnion` of an L-shaped set spans the
|
|
39
|
+
* missing corner, so a union test claims coverage nobody paints. This is an
|
|
40
|
+
* EXACT rectangle-union coverage test instead, and it is exact for both shapes:
|
|
41
|
+
* cut the target into vertical slabs at every candidate edge, and inside each
|
|
42
|
+
* slab merge the y-intervals of the candidates that span it. A slab that cannot
|
|
43
|
+
* be spanned from `target`'s top edge to its bottom edge without a gap is a
|
|
44
|
+
* hole, wherever it came from.
|
|
45
|
+
*
|
|
46
|
+
* `epsilon` widens each candidate — both rects are the product of float
|
|
47
|
+
* arithmetic and a 1e-9 overhang is not a coverage failure. O(n^2) over an n in
|
|
48
|
+
* the low tens (a grid is at most a few dozen pieces), evaluated once per commit.
|
|
49
|
+
*/
|
|
50
|
+
export declare function rectsCover(covers: readonly DocRect[], target: DocRect, epsilon?: number): boolean;
|
|
51
|
+
/** Null rather than an empty rect, so "no overlap" cannot be drawn by accident. */
|
|
52
|
+
export declare function rectIntersection(a: DocRect, b: DocRect): DocRect | null;
|
|
53
|
+
/**
|
|
54
|
+
* An empty operand is skipped rather than unioned: a zero-size rect still has
|
|
55
|
+
* an origin, and folding one in would drag the union to (0,0) and make the
|
|
56
|
+
* coverage predicate think the whole document was covered.
|
|
57
|
+
*/
|
|
58
|
+
export declare function rectUnion(a: DocRect, b: DocRect): DocRect;
|
|
59
|
+
/**
|
|
60
|
+
* Grow by a FRACTION of the rect's own extents on each side — `fx = 0.25`
|
|
61
|
+
* adds 25% of the width to the left AND to the right, so the result is 1.5x
|
|
62
|
+
* wide. Fractions, not absolute points, because the only caller is
|
|
63
|
+
* `detailOverflow`, which is defined that way and must stay scale-invariant.
|
|
64
|
+
*/
|
|
65
|
+
export declare function inflateRect(r: DocRect, fx: number, fy: number): DocRect;
|
|
66
|
+
/**
|
|
67
|
+
* Intersection that always returns a rect. A disjoint input collapses to a
|
|
68
|
+
* zero-size rect on the nearest edge of `bounds` rather than to null, so
|
|
69
|
+
* callers that only need "clip this to the page" need no null branch.
|
|
70
|
+
*/
|
|
71
|
+
export declare function clampRectTo(r: DocRect, bounds: DocRect): DocRect;
|
|
72
|
+
export declare function rectsEqual(a: DocRect, b: DocRect, epsilon?: number): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Device pixels per point at which `content` fits inside `viewport`.
|
|
75
|
+
*
|
|
76
|
+
* `padding` is in DEVICE PIXELS (it is inset from a pixel-space box) and is
|
|
77
|
+
* removed from both sides of each axis. Returns 0 when nothing can fit, which
|
|
78
|
+
* callers must treat as "do not plan a raster" rather than scaling by zero.
|
|
79
|
+
*/
|
|
80
|
+
export declare function fitScale(content: DocSize, viewport: PixelSize, padding?: number): number;
|
|
81
|
+
export declare function scaleToPixels(rect: DocRect, scale: number): PixelSize;
|
|
82
|
+
/**
|
|
83
|
+
* Expand `rect` outward until its device-pixel bounds are whole numbers.
|
|
84
|
+
*
|
|
85
|
+
* LOAD-BEARING: the returned `docRect` is what the caller must draw at, not
|
|
86
|
+
* the rect it asked for. A backend renders `round(width * scale)` pixels; if
|
|
87
|
+
* the request straddled the pixel grid, those bytes cover a slightly larger
|
|
88
|
+
* area than the requested rect, and drawing them into the requested rect
|
|
89
|
+
* rescales the bitmap by a sub-pixel fraction. That is not a rounding error
|
|
90
|
+
* you can ignore — it lands as blur, and as misregistration against the
|
|
91
|
+
* annotation layer drawn above at exact doc coordinates.
|
|
92
|
+
*
|
|
93
|
+
* Always expands (floor the near edge, ceil the far one) so the covered area
|
|
94
|
+
* is a superset of the request; adjacent pieces therefore overlap, never gap.
|
|
95
|
+
*/
|
|
96
|
+
export declare function snapRectToDevicePixels(rect: DocRect, scale: number): SnappedRect;
|
package/dist/geometry.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doc-space geometry: PDF points, y-down, origin at the page's top-left.
|
|
3
|
+
*
|
|
4
|
+
* Every function here is pure and total — no throws, no NaN propagation that
|
|
5
|
+
* survives a guard. These are the numbers that end up as a texture allocation
|
|
6
|
+
* and a draw rect, so a silent NaN here is a blank page or an OOM later.
|
|
7
|
+
*/
|
|
8
|
+
/** Comparison tolerance in points. Sub-micron on any real sheet. */
|
|
9
|
+
const DEFAULT_EPSILON = 1e-6;
|
|
10
|
+
export function rectArea(r) {
|
|
11
|
+
return Math.max(0, r.width) * Math.max(0, r.height);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The negation is deliberate: `!(w > 0)` treats NaN as empty, where
|
|
15
|
+
* `w <= 0` would call a NaN rect non-empty and let it reach a texture alloc.
|
|
16
|
+
*/
|
|
17
|
+
export function isEmptyRect(r) {
|
|
18
|
+
return !(r.width > 0) || !(r.height > 0);
|
|
19
|
+
}
|
|
20
|
+
export function rectIntersects(a, b) {
|
|
21
|
+
return (a.x < b.x + b.width &&
|
|
22
|
+
b.x < a.x + a.width &&
|
|
23
|
+
a.y < b.y + b.height &&
|
|
24
|
+
b.y < a.y + a.height);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* True when `inner` lies wholly within `outer`. Touching edges count as
|
|
28
|
+
* contained; `epsilon` widens `outer` to absorb float noise at high zoom,
|
|
29
|
+
* where a rect is reconstructed from a transform every frame.
|
|
30
|
+
*/
|
|
31
|
+
export function rectContains(outer, inner, epsilon = 0) {
|
|
32
|
+
return (inner.x >= outer.x - epsilon &&
|
|
33
|
+
inner.y >= outer.y - epsilon &&
|
|
34
|
+
inner.x + inner.width <= outer.x + outer.width + epsilon &&
|
|
35
|
+
inner.y + inner.height <= outer.y + outer.height + epsilon);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* True when `covers` TOGETHER contain every point of `target`.
|
|
39
|
+
*
|
|
40
|
+
* WHY THIS IS NOT `covers.some(c => rectContains(c, target))`. Once a layer is
|
|
41
|
+
* tiled, the thing replacing an old raster is a GRID, and no single grid cell
|
|
42
|
+
* contains an old cell that straddles a seam. A per-rect containment test
|
|
43
|
+
* therefore reports "not covered" for a grid that re-covers the area completely
|
|
44
|
+
* — which is how the controller ended up retaining an entire previous grid on
|
|
45
|
+
* every epoch, holding two full grids through every transition.
|
|
46
|
+
*
|
|
47
|
+
* WHY NOT THE UNION RECT EITHER. `rectUnion` of an L-shaped set spans the
|
|
48
|
+
* missing corner, so a union test claims coverage nobody paints. This is an
|
|
49
|
+
* EXACT rectangle-union coverage test instead, and it is exact for both shapes:
|
|
50
|
+
* cut the target into vertical slabs at every candidate edge, and inside each
|
|
51
|
+
* slab merge the y-intervals of the candidates that span it. A slab that cannot
|
|
52
|
+
* be spanned from `target`'s top edge to its bottom edge without a gap is a
|
|
53
|
+
* hole, wherever it came from.
|
|
54
|
+
*
|
|
55
|
+
* `epsilon` widens each candidate — both rects are the product of float
|
|
56
|
+
* arithmetic and a 1e-9 overhang is not a coverage failure. O(n^2) over an n in
|
|
57
|
+
* the low tens (a grid is at most a few dozen pieces), evaluated once per commit.
|
|
58
|
+
*/
|
|
59
|
+
export function rectsCover(covers, target, epsilon = 0) {
|
|
60
|
+
// Nothing to cover. Vacuously true, and the caller's "may I release the old
|
|
61
|
+
// set" question is answered yes, which is correct for an empty old set.
|
|
62
|
+
if (isEmptyRect(target))
|
|
63
|
+
return true;
|
|
64
|
+
const tx0 = target.x;
|
|
65
|
+
const tx1 = target.x + target.width;
|
|
66
|
+
const ty0 = target.y;
|
|
67
|
+
const ty1 = target.y + target.height;
|
|
68
|
+
/** Candidates clipped to the target, in edge coordinates. */
|
|
69
|
+
const clipped = [];
|
|
70
|
+
for (const c of covers) {
|
|
71
|
+
if (isEmptyRect(c))
|
|
72
|
+
continue;
|
|
73
|
+
const x0 = Math.max(c.x - epsilon, tx0);
|
|
74
|
+
const x1 = Math.min(c.x + c.width + epsilon, tx1);
|
|
75
|
+
const y0 = Math.max(c.y - epsilon, ty0);
|
|
76
|
+
const y1 = Math.min(c.y + c.height + epsilon, ty1);
|
|
77
|
+
if (x1 - x0 > DEFAULT_EPSILON && y1 - y0 > DEFAULT_EPSILON) {
|
|
78
|
+
clipped.push({ x0, x1, y0, y1 });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (clipped.length === 0)
|
|
82
|
+
return false;
|
|
83
|
+
const xs = [tx0, tx1];
|
|
84
|
+
for (const c of clipped) {
|
|
85
|
+
if (c.x0 > tx0 && c.x0 < tx1)
|
|
86
|
+
xs.push(c.x0);
|
|
87
|
+
if (c.x1 > tx0 && c.x1 < tx1)
|
|
88
|
+
xs.push(c.x1);
|
|
89
|
+
}
|
|
90
|
+
xs.sort((a, b) => a - b);
|
|
91
|
+
for (let i = 0; i < xs.length - 1; i++) {
|
|
92
|
+
const from = xs[i] ?? tx0;
|
|
93
|
+
const to = xs[i + 1] ?? tx1;
|
|
94
|
+
// Duplicate edges collapse to a zero-width slab. Sub-micron on any real
|
|
95
|
+
// sheet, and a slab with no width has no area to leave uncovered.
|
|
96
|
+
if (to - from <= DEFAULT_EPSILON)
|
|
97
|
+
continue;
|
|
98
|
+
const spans = [];
|
|
99
|
+
for (const c of clipped) {
|
|
100
|
+
if (c.x0 <= from + DEFAULT_EPSILON && c.x1 >= to - DEFAULT_EPSILON) {
|
|
101
|
+
spans.push({ y0: c.y0, y1: c.y1 });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (spans.length === 0)
|
|
105
|
+
return false;
|
|
106
|
+
spans.sort((a, b) => a.y0 - b.y0);
|
|
107
|
+
let reach = ty0;
|
|
108
|
+
for (const span of spans) {
|
|
109
|
+
// A gap between the last covered y and this span's top is a hole, and
|
|
110
|
+
// sorting by y0 means no later span can fill it.
|
|
111
|
+
if (span.y0 > reach + DEFAULT_EPSILON)
|
|
112
|
+
break;
|
|
113
|
+
if (span.y1 > reach)
|
|
114
|
+
reach = span.y1;
|
|
115
|
+
if (reach >= ty1 - DEFAULT_EPSILON)
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
if (reach < ty1 - DEFAULT_EPSILON)
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
/** Null rather than an empty rect, so "no overlap" cannot be drawn by accident. */
|
|
124
|
+
export function rectIntersection(a, b) {
|
|
125
|
+
const x = Math.max(a.x, b.x);
|
|
126
|
+
const y = Math.max(a.y, b.y);
|
|
127
|
+
const right = Math.min(a.x + a.width, b.x + b.width);
|
|
128
|
+
const bottom = Math.min(a.y + a.height, b.y + b.height);
|
|
129
|
+
if (!(right > x) || !(bottom > y))
|
|
130
|
+
return null;
|
|
131
|
+
return { x, y, width: right - x, height: bottom - y };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* An empty operand is skipped rather than unioned: a zero-size rect still has
|
|
135
|
+
* an origin, and folding one in would drag the union to (0,0) and make the
|
|
136
|
+
* coverage predicate think the whole document was covered.
|
|
137
|
+
*/
|
|
138
|
+
export function rectUnion(a, b) {
|
|
139
|
+
if (isEmptyRect(a))
|
|
140
|
+
return { ...b };
|
|
141
|
+
if (isEmptyRect(b))
|
|
142
|
+
return { ...a };
|
|
143
|
+
const x = Math.min(a.x, b.x);
|
|
144
|
+
const y = Math.min(a.y, b.y);
|
|
145
|
+
const right = Math.max(a.x + a.width, b.x + b.width);
|
|
146
|
+
const bottom = Math.max(a.y + a.height, b.y + b.height);
|
|
147
|
+
return { x, y, width: right - x, height: bottom - y };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Grow by a FRACTION of the rect's own extents on each side — `fx = 0.25`
|
|
151
|
+
* adds 25% of the width to the left AND to the right, so the result is 1.5x
|
|
152
|
+
* wide. Fractions, not absolute points, because the only caller is
|
|
153
|
+
* `detailOverflow`, which is defined that way and must stay scale-invariant.
|
|
154
|
+
*/
|
|
155
|
+
export function inflateRect(r, fx, fy) {
|
|
156
|
+
const dx = r.width * fx;
|
|
157
|
+
const dy = r.height * fy;
|
|
158
|
+
return {
|
|
159
|
+
x: r.x - dx,
|
|
160
|
+
y: r.y - dy,
|
|
161
|
+
width: r.width + dx * 2,
|
|
162
|
+
height: r.height + dy * 2,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Intersection that always returns a rect. A disjoint input collapses to a
|
|
167
|
+
* zero-size rect on the nearest edge of `bounds` rather than to null, so
|
|
168
|
+
* callers that only need "clip this to the page" need no null branch.
|
|
169
|
+
*/
|
|
170
|
+
export function clampRectTo(r, bounds) {
|
|
171
|
+
const bRight = bounds.x + bounds.width;
|
|
172
|
+
const bBottom = bounds.y + bounds.height;
|
|
173
|
+
const x = Math.min(Math.max(r.x, bounds.x), bRight);
|
|
174
|
+
const y = Math.min(Math.max(r.y, bounds.y), bBottom);
|
|
175
|
+
const right = Math.min(Math.max(r.x + r.width, bounds.x), bRight);
|
|
176
|
+
const bottom = Math.min(Math.max(r.y + r.height, bounds.y), bBottom);
|
|
177
|
+
return {
|
|
178
|
+
x,
|
|
179
|
+
y,
|
|
180
|
+
width: Math.max(0, right - x),
|
|
181
|
+
height: Math.max(0, bottom - y),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
export function rectsEqual(a, b, epsilon = DEFAULT_EPSILON) {
|
|
185
|
+
return (Math.abs(a.x - b.x) <= epsilon &&
|
|
186
|
+
Math.abs(a.y - b.y) <= epsilon &&
|
|
187
|
+
Math.abs(a.width - b.width) <= epsilon &&
|
|
188
|
+
Math.abs(a.height - b.height) <= epsilon);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Device pixels per point at which `content` fits inside `viewport`.
|
|
192
|
+
*
|
|
193
|
+
* `padding` is in DEVICE PIXELS (it is inset from a pixel-space box) and is
|
|
194
|
+
* removed from both sides of each axis. Returns 0 when nothing can fit, which
|
|
195
|
+
* callers must treat as "do not plan a raster" rather than scaling by zero.
|
|
196
|
+
*/
|
|
197
|
+
export function fitScale(content, viewport, padding = 0) {
|
|
198
|
+
if (!(content.width > 0) || !(content.height > 0))
|
|
199
|
+
return 0;
|
|
200
|
+
const available = {
|
|
201
|
+
width: viewport.width - padding * 2,
|
|
202
|
+
height: viewport.height - padding * 2,
|
|
203
|
+
};
|
|
204
|
+
if (!(available.width > 0) || !(available.height > 0))
|
|
205
|
+
return 0;
|
|
206
|
+
return Math.min(available.width / content.width, available.height / content.height);
|
|
207
|
+
}
|
|
208
|
+
export function scaleToPixels(rect, scale) {
|
|
209
|
+
if (!(scale > 0))
|
|
210
|
+
return { width: 0, height: 0 };
|
|
211
|
+
return {
|
|
212
|
+
width: Math.max(0, Math.round(rect.width * scale)),
|
|
213
|
+
height: Math.max(0, Math.round(rect.height * scale)),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Expand `rect` outward until its device-pixel bounds are whole numbers.
|
|
218
|
+
*
|
|
219
|
+
* LOAD-BEARING: the returned `docRect` is what the caller must draw at, not
|
|
220
|
+
* the rect it asked for. A backend renders `round(width * scale)` pixels; if
|
|
221
|
+
* the request straddled the pixel grid, those bytes cover a slightly larger
|
|
222
|
+
* area than the requested rect, and drawing them into the requested rect
|
|
223
|
+
* rescales the bitmap by a sub-pixel fraction. That is not a rounding error
|
|
224
|
+
* you can ignore — it lands as blur, and as misregistration against the
|
|
225
|
+
* annotation layer drawn above at exact doc coordinates.
|
|
226
|
+
*
|
|
227
|
+
* Always expands (floor the near edge, ceil the far one) so the covered area
|
|
228
|
+
* is a superset of the request; adjacent pieces therefore overlap, never gap.
|
|
229
|
+
*/
|
|
230
|
+
export function snapRectToDevicePixels(rect, scale) {
|
|
231
|
+
if (!(scale > 0))
|
|
232
|
+
return { docRect: { ...rect }, pixels: { width: 0, height: 0 } };
|
|
233
|
+
const left = Math.floor(rect.x * scale);
|
|
234
|
+
const top = Math.floor(rect.y * scale);
|
|
235
|
+
const right = Math.ceil((rect.x + rect.width) * scale);
|
|
236
|
+
const bottom = Math.ceil((rect.y + rect.height) * scale);
|
|
237
|
+
const width = Math.max(0, right - left);
|
|
238
|
+
const height = Math.max(0, bottom - top);
|
|
239
|
+
return {
|
|
240
|
+
docRect: {
|
|
241
|
+
x: left / scale,
|
|
242
|
+
y: top / scale,
|
|
243
|
+
width: width / scale,
|
|
244
|
+
height: height / scale,
|
|
245
|
+
},
|
|
246
|
+
pixels: { width, height },
|
|
247
|
+
};
|
|
248
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared public surface of @reekon-tools/react-native-pdf-canvas.
|
|
3
|
+
*
|
|
4
|
+
* `./testing` is deliberately NOT re-exported here — the deterministic fake
|
|
5
|
+
* rasterizer has its own subpath export so it never ends up in a production
|
|
6
|
+
* bundle just because someone imported a type from the root.
|
|
7
|
+
*
|
|
8
|
+
* Nor is `./rasterizer/index.{native,web}.js`: the platform default backend is
|
|
9
|
+
* resolved through `setDefaultRasterizer` / `getDefaultRasterizer` on the React
|
|
10
|
+
* side, and star-exporting a platform file from this platform-agnostic barrel is
|
|
11
|
+
* how the two entry points start disagreeing about what `getDefaultRasterizer`
|
|
12
|
+
* means.
|
|
13
|
+
*
|
|
14
|
+
* ------------------------------------------------------------------------
|
|
15
|
+
* AND NEITHER IS THE INGEST SEAM. THIS BARREL MUST STAY SKIA-FREE.
|
|
16
|
+
* ------------------------------------------------------------------------
|
|
17
|
+
*
|
|
18
|
+
* `ingestRaster` / `imageFromPixels` / `rasterByteLength` used to be re-exported
|
|
19
|
+
* from here, which meant that merely NAMING this package pulled
|
|
20
|
+
* `@shopify/react-native-skia`'s barrel — and with it `Skia.web.js`, whose
|
|
21
|
+
* module body is `JsiSkApi(global.CanvasKit)`. On web that call is taken once
|
|
22
|
+
* and never retaken, so evaluating it before `LoadSkiaWeb` resolves leaves a
|
|
23
|
+
* permanently broken `Skia` that re-importing cannot repair. rock-desktop is
|
|
24
|
+
* `{ssr: true}` with `flatRoutes()` and no prerender, so every route module is
|
|
25
|
+
* evaluated in Node on every request: one root import is enough.
|
|
26
|
+
*
|
|
27
|
+
* They now live on the `./skia` subpath, and everything reachable from here is
|
|
28
|
+
* free of module-scope Skia. That is not a style rule — it is asserted by
|
|
29
|
+
* `__tests__/module-ordering.test.ts`, which BUNDLES this file and greps the
|
|
30
|
+
* output for `JsiSkApi(global.CanvasKit)`.
|
|
31
|
+
*/
|
|
32
|
+
/** The frozen type contract, plus the `PdfError` class every seam throws. */
|
|
33
|
+
export * from './types.js';
|
|
34
|
+
/** The React surface: both hooks and the Skia node. */
|
|
35
|
+
export * from './react/index.js';
|
|
36
|
+
/** Page layouts. `layout.ts` is singular — there is no `layouts.js`. */
|
|
37
|
+
export * from './layout.js';
|
|
38
|
+
/** Cadence constants and the ignore-undefined merge over them. */
|
|
39
|
+
export * from './policy.js';
|
|
40
|
+
/**
|
|
41
|
+
* The cadence engine, for a host that drives it outside React — an export or a
|
|
42
|
+
* print path, where there is no component to hang a hook on. Its companion types
|
|
43
|
+
* come with it: an exported factory whose options type is unreachable is a dead
|
|
44
|
+
* export.
|
|
45
|
+
*/
|
|
46
|
+
export { createPdfController } from './controller.js';
|
|
47
|
+
export type { PdfControllerInstance, PdfControllerOptions, RasterIngestMeta, ScheduledHandle, } from './controller.js';
|
|
48
|
+
/** The cache `createPdfController` requires. */
|
|
49
|
+
export { createRasterCache } from './cache.js';
|
|
50
|
+
export type { EvictionReason, RasterCache, RasterCacheOptions, RasterCacheStats, } from './cache.js';
|
|
51
|
+
/** The two pure decisions, WHAT to raster and WHEN, usable on their own. */
|
|
52
|
+
export { planRasters, shouldReraster } from './planner.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared public surface of @reekon-tools/react-native-pdf-canvas.
|
|
3
|
+
*
|
|
4
|
+
* `./testing` is deliberately NOT re-exported here — the deterministic fake
|
|
5
|
+
* rasterizer has its own subpath export so it never ends up in a production
|
|
6
|
+
* bundle just because someone imported a type from the root.
|
|
7
|
+
*
|
|
8
|
+
* Nor is `./rasterizer/index.{native,web}.js`: the platform default backend is
|
|
9
|
+
* resolved through `setDefaultRasterizer` / `getDefaultRasterizer` on the React
|
|
10
|
+
* side, and star-exporting a platform file from this platform-agnostic barrel is
|
|
11
|
+
* how the two entry points start disagreeing about what `getDefaultRasterizer`
|
|
12
|
+
* means.
|
|
13
|
+
*
|
|
14
|
+
* ------------------------------------------------------------------------
|
|
15
|
+
* AND NEITHER IS THE INGEST SEAM. THIS BARREL MUST STAY SKIA-FREE.
|
|
16
|
+
* ------------------------------------------------------------------------
|
|
17
|
+
*
|
|
18
|
+
* `ingestRaster` / `imageFromPixels` / `rasterByteLength` used to be re-exported
|
|
19
|
+
* from here, which meant that merely NAMING this package pulled
|
|
20
|
+
* `@shopify/react-native-skia`'s barrel — and with it `Skia.web.js`, whose
|
|
21
|
+
* module body is `JsiSkApi(global.CanvasKit)`. On web that call is taken once
|
|
22
|
+
* and never retaken, so evaluating it before `LoadSkiaWeb` resolves leaves a
|
|
23
|
+
* permanently broken `Skia` that re-importing cannot repair. rock-desktop is
|
|
24
|
+
* `{ssr: true}` with `flatRoutes()` and no prerender, so every route module is
|
|
25
|
+
* evaluated in Node on every request: one root import is enough.
|
|
26
|
+
*
|
|
27
|
+
* They now live on the `./skia` subpath, and everything reachable from here is
|
|
28
|
+
* free of module-scope Skia. That is not a style rule — it is asserted by
|
|
29
|
+
* `__tests__/module-ordering.test.ts`, which BUNDLES this file and greps the
|
|
30
|
+
* output for `JsiSkApi(global.CanvasKit)`.
|
|
31
|
+
*/
|
|
32
|
+
/** The frozen type contract, plus the `PdfError` class every seam throws. */
|
|
33
|
+
export * from './types.js';
|
|
34
|
+
/** The React surface: both hooks and the Skia node. */
|
|
35
|
+
export * from './react/index.js';
|
|
36
|
+
/** Page layouts. `layout.ts` is singular — there is no `layouts.js`. */
|
|
37
|
+
export * from './layout.js';
|
|
38
|
+
/** Cadence constants and the ignore-undefined merge over them. */
|
|
39
|
+
export * from './policy.js';
|
|
40
|
+
/**
|
|
41
|
+
* The cadence engine, for a host that drives it outside React — an export or a
|
|
42
|
+
* print path, where there is no component to hang a hook on. Its companion types
|
|
43
|
+
* come with it: an exported factory whose options type is unreachable is a dead
|
|
44
|
+
* export.
|
|
45
|
+
*/
|
|
46
|
+
export { createPdfController } from './controller.js';
|
|
47
|
+
/** The cache `createPdfController` requires. */
|
|
48
|
+
export { createRasterCache } from './cache.js';
|
|
49
|
+
/** The two pure decisions, WHAT to raster and WHEN, usable on their own. */
|
|
50
|
+
export { planRasters, shouldReraster } from './planner.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform entry point — the shared surface, plus the native Skia resolver.
|
|
3
|
+
*
|
|
4
|
+
* This file exists ONLY because Metro applies NO platform-extension expansion to
|
|
5
|
+
* the targets of a package's `exports` map. Metro will happily resolve
|
|
6
|
+
* `./foo` to `foo.native.ts` for a normal relative import, but an `exports`
|
|
7
|
+
* condition is matched literally: `"react-native": "./dist/index.js"` would load
|
|
8
|
+
* `dist/index.js` and never look for `dist/index.native.js`. So the map points at
|
|
9
|
+
* the literal `dist/index.native.js` path, and this file is what produces it.
|
|
10
|
+
*
|
|
11
|
+
* Keep the EXPORTS a pure re-export. The moment platform-specific wiring is
|
|
12
|
+
* exported from here, the two entry points can drift and the `types` condition
|
|
13
|
+
* (which resolves to `index.d.ts`) starts lying about one of them.
|
|
14
|
+
*
|
|
15
|
+
* The resolver install below adds no exports and is the one thing that
|
|
16
|
+
* legitimately differs: on native `Skia` is `global.SkiaApi`, installed
|
|
17
|
+
* synchronously by RN Skia's own import-time side effect, so importing the
|
|
18
|
+
* barrel here is both correct and free of the web ordering hazard. `index.web.ts`
|
|
19
|
+
* installs the CanvasKit-backed resolver instead. See `./skia/skia-api.ts`.
|
|
20
|
+
*/
|
|
21
|
+
export * from './index.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform entry point — the shared surface, plus the native Skia resolver.
|
|
3
|
+
*
|
|
4
|
+
* This file exists ONLY because Metro applies NO platform-extension expansion to
|
|
5
|
+
* the targets of a package's `exports` map. Metro will happily resolve
|
|
6
|
+
* `./foo` to `foo.native.ts` for a normal relative import, but an `exports`
|
|
7
|
+
* condition is matched literally: `"react-native": "./dist/index.js"` would load
|
|
8
|
+
* `dist/index.js` and never look for `dist/index.native.js`. So the map points at
|
|
9
|
+
* the literal `dist/index.native.js` path, and this file is what produces it.
|
|
10
|
+
*
|
|
11
|
+
* Keep the EXPORTS a pure re-export. The moment platform-specific wiring is
|
|
12
|
+
* exported from here, the two entry points can drift and the `types` condition
|
|
13
|
+
* (which resolves to `index.d.ts`) starts lying about one of them.
|
|
14
|
+
*
|
|
15
|
+
* The resolver install below adds no exports and is the one thing that
|
|
16
|
+
* legitimately differs: on native `Skia` is `global.SkiaApi`, installed
|
|
17
|
+
* synchronously by RN Skia's own import-time side effect, so importing the
|
|
18
|
+
* barrel here is both correct and free of the web ordering hazard. `index.web.ts`
|
|
19
|
+
* installs the CanvasKit-backed resolver instead. See `./skia/skia-api.ts`.
|
|
20
|
+
*/
|
|
21
|
+
import { installNativeSkiaResolver } from './skia/resolve.native.js';
|
|
22
|
+
export * from './index.js';
|
|
23
|
+
installNativeSkiaResolver();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web/default entry point — the shared surface, plus the web Skia resolver.
|
|
3
|
+
*
|
|
4
|
+
* Same reason as index.native.ts for existing at all: `exports` targets are
|
|
5
|
+
* matched literally, with no platform-extension expansion, so the `browser` and
|
|
6
|
+
* `default` conditions need a real `dist/index.web.js` file to point at.
|
|
7
|
+
*
|
|
8
|
+
* ------------------------------------------------------------------------
|
|
9
|
+
* THE INSTALL BELOW DECIDES NOTHING AT IMPORT TIME.
|
|
10
|
+
* ------------------------------------------------------------------------
|
|
11
|
+
*
|
|
12
|
+
* It stores a closure. `globalThis.CanvasKit` is not read, `JsiSkApi` is not
|
|
13
|
+
* called, and no module that calls it is evaluated — so this file stays safe to
|
|
14
|
+
* evaluate in Node during rock-desktop's SSR pass, before CanvasKit exists
|
|
15
|
+
* anywhere. Asserted by `__tests__/module-ordering.test.ts`, which bundles this
|
|
16
|
+
* exact file and greps for `JsiSkApi(global.CanvasKit)`.
|
|
17
|
+
*
|
|
18
|
+
* It exists so that a host which loads CanvasKit its own way (`WithSkiaWeb`
|
|
19
|
+
* around the canvas, RN Skia's bare `LoadSkiaWeb`, a `<script>` that sets
|
|
20
|
+
* `globalThis.CanvasKit`) still gets a working `skiaApi()` without knowing this
|
|
21
|
+
* package has a `./web-init` subpath. `loadPdfCanvasSkiaWeb()` remains the
|
|
22
|
+
* explicit, awaitable route and is what the README points at.
|
|
23
|
+
*/
|
|
24
|
+
export * from './index.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web/default entry point — the shared surface, plus the web Skia resolver.
|
|
3
|
+
*
|
|
4
|
+
* Same reason as index.native.ts for existing at all: `exports` targets are
|
|
5
|
+
* matched literally, with no platform-extension expansion, so the `browser` and
|
|
6
|
+
* `default` conditions need a real `dist/index.web.js` file to point at.
|
|
7
|
+
*
|
|
8
|
+
* ------------------------------------------------------------------------
|
|
9
|
+
* THE INSTALL BELOW DECIDES NOTHING AT IMPORT TIME.
|
|
10
|
+
* ------------------------------------------------------------------------
|
|
11
|
+
*
|
|
12
|
+
* It stores a closure. `globalThis.CanvasKit` is not read, `JsiSkApi` is not
|
|
13
|
+
* called, and no module that calls it is evaluated — so this file stays safe to
|
|
14
|
+
* evaluate in Node during rock-desktop's SSR pass, before CanvasKit exists
|
|
15
|
+
* anywhere. Asserted by `__tests__/module-ordering.test.ts`, which bundles this
|
|
16
|
+
* exact file and greps for `JsiSkApi(global.CanvasKit)`.
|
|
17
|
+
*
|
|
18
|
+
* It exists so that a host which loads CanvasKit its own way (`WithSkiaWeb`
|
|
19
|
+
* around the canvas, RN Skia's bare `LoadSkiaWeb`, a `<script>` that sets
|
|
20
|
+
* `globalThis.CanvasKit`) still gets a working `skiaApi()` without knowing this
|
|
21
|
+
* package has a `./web-init` subpath. `loadPdfCanvasSkiaWeb()` remains the
|
|
22
|
+
* explicit, awaitable route and is what the README points at.
|
|
23
|
+
*/
|
|
24
|
+
import { installWebSkiaResolver } from './skia/resolve.web.js';
|
|
25
|
+
export * from './index.js';
|
|
26
|
+
installWebSkiaResolver();
|
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page layouts: pure functions from page sizes to doc-space rects.
|
|
3
|
+
*
|
|
4
|
+
* A layout describes where pages SIT, and says nothing about what is visible
|
|
5
|
+
* or what is rastered — it returns a rect for every page, always, so the host
|
|
6
|
+
* can hit-test and scroll to a page that has never been rendered.
|
|
7
|
+
*
|
|
8
|
+
* Each export is a factory so the tuning parameter (`gap`) is captured once
|
|
9
|
+
* and the resulting `PdfPageLayout` stays referentially stable in a host that
|
|
10
|
+
* memoizes on it.
|
|
11
|
+
*
|
|
12
|
+
* INVARIANT every layout upholds: a page's rect has exactly the page's own
|
|
13
|
+
* post-rotation extents. Layouts position pages; they never resize them. The
|
|
14
|
+
* planner derives raster scale from the rect, so a layout that scaled a page
|
|
15
|
+
* would silently rescale its bitmap.
|
|
16
|
+
*/
|
|
17
|
+
import type { DocRect, PdfPageLayout } from './types.js';
|
|
18
|
+
/**
|
|
19
|
+
* Every page at the origin, stacked on top of each other.
|
|
20
|
+
*
|
|
21
|
+
* Overlap is the point: in a paged viewer, doc space contains ONE page and the
|
|
22
|
+
* host swaps which page index it draws. Page 4 having the same rect as page 0
|
|
23
|
+
* is what lets the host page forward without translating its world transform.
|
|
24
|
+
*
|
|
25
|
+
* PAIR IT WITH A ONE-PAGE SELECTION — `usePdfLayer({layout: singlePage(),
|
|
26
|
+
* pages: [n]})` — AND NOTHING ELSE. That is not a style preference; it is the
|
|
27
|
+
* only configuration in which a stacked layout is correct, and the reason is
|
|
28
|
+
* that "which pages may hold rasters" is the only thing standing between this
|
|
29
|
+
* layout and every page in the document:
|
|
30
|
+
*
|
|
31
|
+
* - The raster planner decides what is on screen by intersecting the viewport
|
|
32
|
+
* with each page rect. Here every page rect IS the viewport, so with the
|
|
33
|
+
* on-screen selection a 400-page document plans 400 bases — measured at
|
|
34
|
+
* exactly N times the bytes of one page in `__tests__/single-page.test.ts`.
|
|
35
|
+
* - `PdfContentView` draws pages in index order at the rects `content` carries,
|
|
36
|
+
* so those bases land on top of each other and the highest index wins. The
|
|
37
|
+
* cost is not merely memory: the wrong page is on screen.
|
|
38
|
+
*
|
|
39
|
+
* With `pages: [n]` none of that can happen — no page outside the allow-list is
|
|
40
|
+
* ever rastered, so no page outside it can ever be drawn. `usePdfLayer` warns in
|
|
41
|
+
* `__DEV__` when it is handed a stacked layout with a selection that admits more
|
|
42
|
+
* than one page, and `hasOverlappingPages` below is the predicate it uses.
|
|
43
|
+
*/
|
|
44
|
+
export declare function singlePage(): PdfPageLayout;
|
|
45
|
+
/**
|
|
46
|
+
* Does this layout put two pages on top of each other?
|
|
47
|
+
*
|
|
48
|
+
* Answered about the RECTS a layout produced for a specific document, never
|
|
49
|
+
* about the layout function, because it is a property of both: `singlePage()`
|
|
50
|
+
* over a one-page document stacks nothing, and a bespoke host layout can stack
|
|
51
|
+
* without being `singlePage()`.
|
|
52
|
+
*
|
|
53
|
+
* Overlap is STRICT — `rectIntersects` uses `<`, so a zero-gap continuous layout,
|
|
54
|
+
* where consecutive pages share an edge exactly, is not a stack. Empty rects are
|
|
55
|
+
* skipped: a layout that produced two zero-size rects at the same origin has a
|
|
56
|
+
* different problem than stacking, and reporting this one would send the reader
|
|
57
|
+
* to the wrong place.
|
|
58
|
+
*
|
|
59
|
+
* O(n^2) with an early exit, evaluated once per layout/document change and only
|
|
60
|
+
* behind a `__DEV__` guard at its one call site. Do not put it on a per-frame
|
|
61
|
+
* path.
|
|
62
|
+
*/
|
|
63
|
+
export declare function hasOverlappingPages(rects: readonly DocRect[]): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Pages stacked top to bottom, horizontally centred on the widest page.
|
|
66
|
+
* Centring (rather than left-aligning) matters in mixed-size documents — a
|
|
67
|
+
* letter page bound into a set of plans should not hug the left edge.
|
|
68
|
+
*/
|
|
69
|
+
export declare function continuousVertical(gap?: number): PdfPageLayout;
|
|
70
|
+
/**
|
|
71
|
+
* Two-up: pages (0,1) form the first row, (2,3) the second, and so on. `gap`
|
|
72
|
+
* separates both the two pages of a row and consecutive rows.
|
|
73
|
+
*
|
|
74
|
+
* No cover-page special case. A viewer that wants page 0 alone can pad its own
|
|
75
|
+
* page list; baking that convention in here would make the layout lie about
|
|
76
|
+
* which page sits where for every document that does not follow it.
|
|
77
|
+
*/
|
|
78
|
+
export declare function spread(gap?: number): PdfPageLayout;
|