@2018kguo/recto 0.0.0-stage → 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.
@@ -0,0 +1,166 @@
1
+ // What a page paints, measured over its page objects and chars: raster
2
+ // coverage, vector chart segments and filled rectangles, and whether the
3
+ // text layer shows glyphs. A caller applies its own thresholds to these. Boxes are in the viewer frame.
4
+ import { CHAR_GENERATED, CHAR_UNMAPPED, PATH_FILL, SEGMENT_BEZIER, SEGMENT_LINE, SEGMENT_MOVE, SEGMENT_RECT, } from "./document.js";
5
+ /** pdf.js's lineTo test: a chart segment moves in both axes by more than this. */
6
+ const CHART_SEGMENT_MIN_DELTA = 0.01;
7
+ const AXIS_TOLERANCE = 0.01;
8
+ /**
9
+ * The raster measures over a page's image objects: the largest image's
10
+ * share of the page and the summed share (overlaps count twice).
11
+ */
12
+ export function rasterMeasures(images, pageArea) {
13
+ let largest = 0;
14
+ let total = 0;
15
+ for (let k = 0; k < images.count; k++) {
16
+ const area = Math.max(0, images.boxes[4 * k + 2] ?? 0) *
17
+ Math.max(0, images.boxes[4 * k + 3] ?? 0);
18
+ largest = Math.max(largest, area);
19
+ total += area;
20
+ }
21
+ return {
22
+ largestImageRatio: pageArea > 0 ? largest / pageArea : 0,
23
+ rasterCoverageRatio: pageArea > 0 ? total / pageArea : 0,
24
+ };
25
+ }
26
+ /** The axis-aligned box of a filled four-sided subpath, or null. */
27
+ function rectangleOf(points, kinds, from, to) {
28
+ // MOVE then three or four LINEs (the fourth returning to the start).
29
+ const lines = to - from - 1;
30
+ if (lines !== 3 && lines !== 4) {
31
+ return null;
32
+ }
33
+ for (let s = from + 1; s < to; s++) {
34
+ if (((kinds[s] ?? 0) & 3) !== SEGMENT_LINE) {
35
+ return null;
36
+ }
37
+ }
38
+ const sx = points[2 * from] ?? 0;
39
+ const sy = points[2 * from + 1] ?? 0;
40
+ if (lines === 4 &&
41
+ (Math.abs((points[2 * (to - 1)] ?? 0) - sx) > AXIS_TOLERANCE ||
42
+ Math.abs((points[2 * (to - 1) + 1] ?? 0) - sy) > AXIS_TOLERANCE)) {
43
+ return null;
44
+ }
45
+ let left = Infinity;
46
+ let right = -Infinity;
47
+ let top = Infinity;
48
+ let bottom = -Infinity;
49
+ let previousHorizontal = null;
50
+ // Fill closes the last edge even without `h`. Alternating nonzero edges
51
+ // reject backtracking paths whose bounding box has area but fill does not.
52
+ for (let s = from; s < from + 4; s++) {
53
+ const x = points[2 * s] ?? 0;
54
+ const y = points[2 * s + 1] ?? 0;
55
+ const next = s === from + 3 ? from : s + 1;
56
+ const dx = Math.abs((points[2 * next] ?? 0) - x);
57
+ const dy = Math.abs((points[2 * next + 1] ?? 0) - y);
58
+ const horizontal = dy <= AXIS_TOLERANCE && dx > AXIS_TOLERANCE;
59
+ const vertical = dx <= AXIS_TOLERANCE && dy > AXIS_TOLERANCE;
60
+ if ((!horizontal && !vertical) || previousHorizontal === horizontal) {
61
+ return null;
62
+ }
63
+ left = Math.min(left, x);
64
+ right = Math.max(right, x);
65
+ top = Math.min(top, y);
66
+ bottom = Math.max(bottom, y);
67
+ previousHorizontal = horizontal;
68
+ }
69
+ return right - left > 0 && bottom - top > 0
70
+ ? { left, top, right, bottom }
71
+ : null;
72
+ }
73
+ /**
74
+ * Over a page's painted path objects: the line segments that move in both
75
+ * x and y by more than 0.01 plus curves (a cubic is three Bezier points,
76
+ * counted once), the union box of the paths that have any, and the
77
+ * rectangles of filled paths (four-sided axis-aligned subpaths, implicitly
78
+ * closed by fill; `re` is stored that way, so m/l/l/l rectangles count too).
79
+ */
80
+ export function vectorMeasures(objects) {
81
+ const { paths, segments } = objects;
82
+ const { points, kinds } = segments;
83
+ let chartSegmentCount = 0;
84
+ let chartSegmentBounds = null;
85
+ const filledRectangles = [];
86
+ for (let p = 0; p < paths.count; p++) {
87
+ const from = paths.start[p] ?? 0;
88
+ const to = paths.start[p + 1] ?? from;
89
+ const filled = ((paths.flags[p] ?? 0) & PATH_FILL) !== 0;
90
+ let pathChartSegments = 0;
91
+ let beziers = 0;
92
+ let subpathStart = from;
93
+ for (let s = from; s <= to; s++) {
94
+ const kind = (kinds[s] ?? 0) & 3;
95
+ if (s === to || kind === SEGMENT_MOVE) {
96
+ // pdf.js counts only `re` rectangles, and so does this.
97
+ const drawnByRe = ((kinds[subpathStart] ?? 0) & SEGMENT_RECT) !== 0;
98
+ if (filled && s > subpathStart) {
99
+ const rect = drawnByRe
100
+ ? rectangleOf(points, kinds, subpathStart, s)
101
+ : null;
102
+ if (rect) {
103
+ filledRectangles.push(rect);
104
+ }
105
+ const dx = (points[2 * (s - 1)] ?? 0) - (points[2 * subpathStart] ?? 0);
106
+ const dy = (points[2 * (s - 1) + 1] ?? 0) -
107
+ (points[2 * subpathStart + 1] ?? 0);
108
+ if (Math.abs(dx) > CHART_SEGMENT_MIN_DELTA &&
109
+ Math.abs(dy) > CHART_SEGMENT_MIN_DELTA) {
110
+ pathChartSegments += 1;
111
+ }
112
+ }
113
+ subpathStart = s;
114
+ continue;
115
+ }
116
+ if (kind === SEGMENT_BEZIER) {
117
+ beziers += 1;
118
+ continue;
119
+ }
120
+ if (kind === SEGMENT_LINE && s > from) {
121
+ const dx = (points[2 * s] ?? 0) - (points[2 * s - 2] ?? 0);
122
+ const dy = (points[2 * s + 1] ?? 0) - (points[2 * s - 1] ?? 0);
123
+ if (Math.abs(dx) > CHART_SEGMENT_MIN_DELTA &&
124
+ Math.abs(dy) > CHART_SEGMENT_MIN_DELTA) {
125
+ pathChartSegments += 1;
126
+ }
127
+ }
128
+ }
129
+ // A cubic is three Bezier points.
130
+ pathChartSegments += Math.ceil(beziers / 3);
131
+ if (pathChartSegments > 0) {
132
+ chartSegmentCount += pathChartSegments;
133
+ const x = paths.boxes[4 * p] ?? 0;
134
+ const y = paths.boxes[4 * p + 1] ?? 0;
135
+ const box = {
136
+ left: x,
137
+ top: y,
138
+ right: x + (paths.boxes[4 * p + 2] ?? 0),
139
+ bottom: y + (paths.boxes[4 * p + 3] ?? 0),
140
+ };
141
+ chartSegmentBounds = chartSegmentBounds
142
+ ? {
143
+ left: Math.min(chartSegmentBounds.left, box.left),
144
+ top: Math.min(chartSegmentBounds.top, box.top),
145
+ right: Math.max(chartSegmentBounds.right, box.right),
146
+ bottom: Math.max(chartSegmentBounds.bottom, box.bottom),
147
+ }
148
+ : box;
149
+ }
150
+ }
151
+ return { chartSegmentCount, chartSegmentBounds, filledRectangles };
152
+ }
153
+ /** Whether a page's text layer shows glyphs (non-space or unmapped chars). */
154
+ export function showsGlyphs(text) {
155
+ for (let i = 0; i < text.count; i++) {
156
+ const flags = text.flags[i] ?? 0;
157
+ if (flags & CHAR_GENERATED) {
158
+ continue;
159
+ }
160
+ if (flags & CHAR_UNMAPPED ||
161
+ String.fromCodePoint(text.codepoints[i] ?? 0x20).trim().length > 0) {
162
+ return true;
163
+ }
164
+ }
165
+ return false;
166
+ }
@@ -0,0 +1,88 @@
1
+ import { PdfError } from "./errors.ts";
2
+ import type { PdfDocument } from "./document.ts";
3
+ /**
4
+ * Where a ranged document's bytes come from: an HTTP(S) URL, or bytes in memory (copied, so
5
+ * the caller may reuse the buffer).
6
+ */
7
+ export type PdfRangeSource = string | URL | Uint8Array;
8
+ export type PdfRangedOpenOptions = {
9
+ password?: string;
10
+ /** Smallest fetch and the cache's unit, above 0. Default 64 KiB. */
11
+ chunkBytes?: number;
12
+ /** Fail (code "too-large") rather than fetch more than this in total. Default 64 MiB. */
13
+ maxBytes?: number;
14
+ /**
15
+ * Checked before the open and before every fetch. The thread is blocked during a fetch, so
16
+ * an abort cannot interrupt one; `timeoutMs` bounds that.
17
+ */
18
+ signal?: AbortSignal;
19
+ /** How long one fetch round may block the thread (code "timeout"). Default 30 s. */
20
+ timeoutMs?: number;
21
+ };
22
+ export type PdfRangedStats = {
23
+ /** Bytes received from the source, across every request. */
24
+ bytesFetched: number;
25
+ requests: number;
26
+ /** Fetch rounds (round trips); a round's requests run in parallel. */
27
+ rounds: number;
28
+ /** The engine's reads, and the bytes they asked for: the parse work. */
29
+ reads: number;
30
+ readBytes: number;
31
+ /** The file's full size, from the first response. */
32
+ fileBytes: number;
33
+ };
34
+ /** A document opened over byte ranges. Use and close it like any other. */
35
+ export type PdfRangedDocument = PdfDocument & {
36
+ stats(): PdfRangedStats;
37
+ };
38
+ type Range = {
39
+ start: number;
40
+ end: number;
41
+ };
42
+ type Answer = {
43
+ start: number;
44
+ bytes: Uint8Array;
45
+ length: number;
46
+ etag: string | null;
47
+ };
48
+ /** Fetches `ranges`; `limit` is how many bytes may still arrive. */
49
+ type Fetch = (ranges: Range[], timeoutMs: number, limit: number) => Answer[];
50
+ /** Fetched chunks of one file and the policy for fetching more. */
51
+ declare class RangeLoader {
52
+ #private;
53
+ length: number;
54
+ readonly chunkBytes: number;
55
+ /** Set once a fetch failed; every later read fails and every call throws it. */
56
+ failure: PdfError | null;
57
+ constructor(fetch: Fetch, options: {
58
+ chunkBytes: number;
59
+ maxBytes: number;
60
+ timeoutMs: number;
61
+ signal: AbortSignal | undefined;
62
+ });
63
+ stats(): PdfRangedStats;
64
+ /** Fetches chunks `firstChunk..endChunk`, keeping only those not held yet. */
65
+ fetchChunks(firstChunk: number, endChunk: number): void;
66
+ /**
67
+ * The `startxref` offset in the fetched last chunk, if it names one. Null for a linearized
68
+ * file, whose trailer points at the first-page section near the head.
69
+ */
70
+ startxref(): number | null;
71
+ /**
72
+ * Copies [offset, offset + out.length) into `out`, fetching what is missing first. On a
73
+ * failed fetch it records the failure and fails the read; the call that made it then
74
+ * throws the failure.
75
+ */
76
+ read(offset: number, out: Uint8Array): boolean;
77
+ }
78
+ /**
79
+ * Starts a ranged read of `source`: the head, then (for a larger file) the tail and its last
80
+ * cross-reference section. Blocks while it fetches.
81
+ */
82
+ export declare function startRangeLoader(source: string | Uint8Array, options?: PdfRangedOpenOptions): RangeLoader;
83
+ /**
84
+ * The caller's source in a form that stays valid across an await: bytes copied, a URL as an
85
+ * absolute http(s) string. Runs before anything asynchronous.
86
+ */
87
+ export declare function ownedSource(source: PdfRangeSource): string | Uint8Array;
88
+ export {};