@build-qube/papyra 0.2.0 → 0.3.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/README.md +18 -0
- package/dist/attachments.d.ts +76 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +65 -0
- package/dist/attachments.js.map +1 -0
- package/dist/canvas.d.ts +27 -2
- package/dist/canvas.d.ts.map +1 -1
- package/dist/canvas.js +58 -5
- package/dist/canvas.js.map +1 -1
- package/dist/document.d.ts +61 -0
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +105 -7
- package/dist/document.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/structure.d.ts +119 -0
- package/dist/structure.d.ts.map +1 -0
- package/dist/structure.js +115 -0
- package/dist/structure.js.map +1 -0
- package/dist/text.d.ts +18 -0
- package/dist/text.d.ts.map +1 -1
- package/dist/text.js.map +1 -1
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/viewport.d.ts +129 -0
- package/dist/viewport.d.ts.map +1 -0
- package/dist/viewport.js +163 -0
- package/dist/viewport.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -104,6 +104,24 @@ Encrypted files throw `PasswordRequiredError` or `IncorrectPasswordError`, both
|
|
|
104
104
|
extending `PasswordError` with a `retry` flag, so one dialog handles the cold ask and
|
|
105
105
|
the wrong answer.
|
|
106
106
|
|
|
107
|
+
## Rotate the view
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const vp = viewport(doc.pageSize(index), { fitWidth: 1600, rotation: 90 });
|
|
111
|
+
|
|
112
|
+
paintToCanvas(await doc.renderPage(index, { dpi: vp.dpi }), canvas, {
|
|
113
|
+
rotation: vp.rotation,
|
|
114
|
+
});
|
|
115
|
+
const box = viewportRect(link.rect, vp); // the link layer, turned to match
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Pages render unrotated and the canvas does the turning, so a rotate button costs no
|
|
119
|
+
render. Size through the viewport rather than with `fitWidth` directly: turned, the
|
|
120
|
+
width on screen is the page's height, and `vp.dpi` carries the answer back.
|
|
121
|
+
|
|
122
|
+
Renders draw annotations by default. Pass `{ annotations: false }` when you are drawing
|
|
123
|
+
your own link or highlight layer, or they appear twice.
|
|
124
|
+
|
|
107
125
|
## Two things that will bite you
|
|
108
126
|
|
|
109
127
|
**Size with `fitWidth`, not `dpi`.** Page areas vary by two orders of magnitude. At a
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Attachment as NativeAttachment } from '@build-qube/papyra-native';
|
|
2
|
+
/**
|
|
3
|
+
* A file embedded in the document.
|
|
4
|
+
*
|
|
5
|
+
* Metadata only. The bytes come from {@link Document.attachmentData}, because listing
|
|
6
|
+
* what a document carries is a different question from opening one of the things it
|
|
7
|
+
* carries, and an embedded file is as large as it is.
|
|
8
|
+
*/
|
|
9
|
+
export interface Attachment {
|
|
10
|
+
/** Position in {@link Document.attachments}, and what identifies it for a fetch. */
|
|
11
|
+
readonly index: number;
|
|
12
|
+
/**
|
|
13
|
+
* The name to save the file under.
|
|
14
|
+
*
|
|
15
|
+
* `/UF` where the document provides it — the Unicode spelling, and the one the spec
|
|
16
|
+
* tells a reader to prefer — falling back to `/F`, then to the name the file was
|
|
17
|
+
* filed under. Never empty.
|
|
18
|
+
*/
|
|
19
|
+
readonly name: string;
|
|
20
|
+
/** The document's own description of what the file is for. */
|
|
21
|
+
readonly description: string | null;
|
|
22
|
+
/**
|
|
23
|
+
* The declared media type, e.g. `'application/xml'`.
|
|
24
|
+
*
|
|
25
|
+
* Absent more often than not, so fall back to the extension on {@link name} rather
|
|
26
|
+
* than treating this as the answer. {@link attachmentMediaType} does exactly that.
|
|
27
|
+
*/
|
|
28
|
+
readonly mediaType: string | null;
|
|
29
|
+
/**
|
|
30
|
+
* Length in bytes, uncompressed, **as the document claims it**.
|
|
31
|
+
*
|
|
32
|
+
* Not a measurement: reading the true length means decompressing the stream, which
|
|
33
|
+
* is the work this type exists to defer. Fine for a size column; not something to
|
|
34
|
+
* allocate against.
|
|
35
|
+
*/
|
|
36
|
+
readonly size: number | null;
|
|
37
|
+
/** Creation date, ISO 8601. */
|
|
38
|
+
readonly created: string | null;
|
|
39
|
+
/** Modification date, ISO 8601. */
|
|
40
|
+
readonly modified: string | null;
|
|
41
|
+
/**
|
|
42
|
+
* What the file is to the document: `'Source'`, `'Data'`, `'Alternative'`,
|
|
43
|
+
* `'Supplement'`, `'EncryptedPayload'` or `'Unspecified'`.
|
|
44
|
+
*
|
|
45
|
+
* This is what identifies a hybrid invoice. A ZUGFeRD or Factur-X PDF carries its
|
|
46
|
+
* XML with a relationship of `Alternative` or `Data`, and that is the difference
|
|
47
|
+
* between a machine-readable invoice and a PDF with a file stapled to it — see
|
|
48
|
+
* {@link isInvoiceAttachment}.
|
|
49
|
+
*/
|
|
50
|
+
readonly relationship: string | null;
|
|
51
|
+
}
|
|
52
|
+
/** @internal */
|
|
53
|
+
export declare function toAttachment(native: NativeAttachment, index: number): Attachment;
|
|
54
|
+
/**
|
|
55
|
+
* The attachment's media type, falling back to its extension.
|
|
56
|
+
*
|
|
57
|
+
* `'application/octet-stream'` when neither answers, which is the honest default for
|
|
58
|
+
* bytes nothing describes — and the one a download will not misrender.
|
|
59
|
+
*/
|
|
60
|
+
export declare function attachmentMediaType(file: Attachment): string;
|
|
61
|
+
/**
|
|
62
|
+
* Whether this attachment is the machine-readable half of a hybrid invoice.
|
|
63
|
+
*
|
|
64
|
+
* ZUGFeRD, Factur-X, Order-X and XRechnung all work the same way: the PDF is what a
|
|
65
|
+
* person reads, and an embedded XML file is what an accounting system reads. Both
|
|
66
|
+
* halves are the same invoice, and a consumer that ignores the XML is retyping data
|
|
67
|
+
* the document already carries.
|
|
68
|
+
*
|
|
69
|
+
* Matched on the standardised filename rather than on
|
|
70
|
+
* {@link Attachment.relationship} alone. The relationship narrows it — the standards
|
|
71
|
+
* require `Alternative` or `Data` — but producers get it wrong often enough that
|
|
72
|
+
* requiring it would miss real invoices, while the filenames are fixed by the
|
|
73
|
+
* specifications and are what every other implementation keys on.
|
|
74
|
+
*/
|
|
75
|
+
export declare function isInvoiceAttachment(file: Attachment): boolean;
|
|
76
|
+
//# sourceMappingURL=attachments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,oFAAoF;IACpF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,+BAA+B;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mCAAmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,gBAAgB;AAChB,wBAAgB,YAAY,CAC1B,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,MAAM,GACZ,UAAU,CAWZ;AAiBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAK5D;AAUD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAE7D"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** @internal */
|
|
2
|
+
export function toAttachment(native, index) {
|
|
3
|
+
return {
|
|
4
|
+
index,
|
|
5
|
+
name: native.name,
|
|
6
|
+
description: native.description ?? null,
|
|
7
|
+
mediaType: native.mediaType ?? null,
|
|
8
|
+
size: native.size ?? null,
|
|
9
|
+
created: native.created ?? null,
|
|
10
|
+
modified: native.modified ?? null,
|
|
11
|
+
relationship: native.relationship ?? null,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** Extension to media type, for the majority of files that declare none. */
|
|
15
|
+
const BY_EXTENSION = {
|
|
16
|
+
csv: 'text/csv',
|
|
17
|
+
html: 'text/html',
|
|
18
|
+
json: 'application/json',
|
|
19
|
+
pdf: 'application/pdf',
|
|
20
|
+
png: 'image/png',
|
|
21
|
+
jpg: 'image/jpeg',
|
|
22
|
+
jpeg: 'image/jpeg',
|
|
23
|
+
svg: 'image/svg+xml',
|
|
24
|
+
txt: 'text/plain',
|
|
25
|
+
xml: 'application/xml',
|
|
26
|
+
zip: 'application/zip',
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The attachment's media type, falling back to its extension.
|
|
30
|
+
*
|
|
31
|
+
* `'application/octet-stream'` when neither answers, which is the honest default for
|
|
32
|
+
* bytes nothing describes — and the one a download will not misrender.
|
|
33
|
+
*/
|
|
34
|
+
export function attachmentMediaType(file) {
|
|
35
|
+
if (file.mediaType)
|
|
36
|
+
return file.mediaType;
|
|
37
|
+
const dot = file.name.lastIndexOf('.');
|
|
38
|
+
const extension = dot < 0 ? '' : file.name.slice(dot + 1).toLowerCase();
|
|
39
|
+
return BY_EXTENSION[extension] ?? 'application/octet-stream';
|
|
40
|
+
}
|
|
41
|
+
/** The filenames the hybrid-invoice standards define, lowercased. */
|
|
42
|
+
const INVOICE_NAMES = new Set([
|
|
43
|
+
'factur-x.xml',
|
|
44
|
+
'zugferd-invoice.xml',
|
|
45
|
+
'xrechnung.xml',
|
|
46
|
+
'order-x.xml',
|
|
47
|
+
]);
|
|
48
|
+
/**
|
|
49
|
+
* Whether this attachment is the machine-readable half of a hybrid invoice.
|
|
50
|
+
*
|
|
51
|
+
* ZUGFeRD, Factur-X, Order-X and XRechnung all work the same way: the PDF is what a
|
|
52
|
+
* person reads, and an embedded XML file is what an accounting system reads. Both
|
|
53
|
+
* halves are the same invoice, and a consumer that ignores the XML is retyping data
|
|
54
|
+
* the document already carries.
|
|
55
|
+
*
|
|
56
|
+
* Matched on the standardised filename rather than on
|
|
57
|
+
* {@link Attachment.relationship} alone. The relationship narrows it — the standards
|
|
58
|
+
* require `Alternative` or `Data` — but producers get it wrong often enough that
|
|
59
|
+
* requiring it would miss real invoices, while the filenames are fixed by the
|
|
60
|
+
* specifications and are what every other implementation keys on.
|
|
61
|
+
*/
|
|
62
|
+
export function isInvoiceAttachment(file) {
|
|
63
|
+
return INVOICE_NAMES.has(file.name.toLowerCase());
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=attachments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.js","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"AAqDA,gBAAgB;AAChB,MAAM,UAAU,YAAY,CAC1B,MAAwB,EACxB,KAAa;IAEb,OAAO;QACL,KAAK;QACL,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,IAAI;QACvC,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,IAAI;QACnC,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,IAAI;QACzB,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI;QAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI;QACjC,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,IAAI;KAC1C,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,MAAM,YAAY,GAA2B;IAC3C,GAAG,EAAE,UAAU;IACf,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,kBAAkB;IACxB,GAAG,EAAE,iBAAiB;IACtB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,YAAY;IACjB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,eAAe;IACpB,GAAG,EAAE,YAAY;IACjB,GAAG,EAAE,iBAAiB;IACtB,GAAG,EAAE,iBAAiB;CACvB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAgB;IAClD,IAAI,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACxE,OAAO,YAAY,CAAC,SAAS,CAAC,IAAI,0BAA0B,CAAC;AAC/D,CAAC;AAED,qEAAqE;AACrE,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;IAC5B,cAAc;IACd,qBAAqB;IACrB,eAAe;IACf,aAAa;CACd,CAAC,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAgB;IAClD,OAAO,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;AACpD,CAAC"}
|
package/dist/canvas.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RenderedPage } from './types.js';
|
|
2
|
+
import type { Rotation } from './viewport.js';
|
|
2
3
|
/**
|
|
3
4
|
* Wrap a rendered page as `ImageData`, ready for `putImageData`.
|
|
4
5
|
*
|
|
@@ -7,6 +8,30 @@ import type { RenderedPage } from './types.js';
|
|
|
7
8
|
* unconditionally keeps both runtimes on the same path.
|
|
8
9
|
*/
|
|
9
10
|
export declare function toImageData(page: RenderedPage): ImageData;
|
|
10
|
-
/**
|
|
11
|
-
export
|
|
11
|
+
/** How to paint. */
|
|
12
|
+
export interface PaintOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Quarter turns clockwise to apply while drawing. Defaults to none.
|
|
15
|
+
*
|
|
16
|
+
* Free in the sense that matters: the pixels are not copied twice and no rotated
|
|
17
|
+
* bitmap is retained. Pair it with a {@link Viewport} of the same rotation so that
|
|
18
|
+
* link regions and text highlights land on the same page the pixels do.
|
|
19
|
+
*/
|
|
20
|
+
rotation?: Rotation;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Paint a rendered page onto a canvas, resizing the canvas to match.
|
|
24
|
+
*
|
|
25
|
+
* With a `rotation` the canvas takes the turned dimensions — a 90° view of a portrait
|
|
26
|
+
* page is a landscape canvas.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const vp = viewport(doc.pageSize(0), { fitWidth: 1600, rotation: 90 });
|
|
31
|
+
* paintToCanvas(await doc.renderPage(0, { dpi: vp.dpi }), canvas, {
|
|
32
|
+
* rotation: vp.rotation,
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function paintToCanvas(page: RenderedPage, canvas: HTMLCanvasElement | OffscreenCanvas, options?: PaintOptions): void;
|
|
12
37
|
//# sourceMappingURL=canvas.d.ts.map
|
package/dist/canvas.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../src/canvas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../src/canvas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAU9C;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,CAMzD;AAED,oBAAoB;AACpB,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,iBAAiB,GAAG,eAAe,EAC3C,OAAO,GAAE,YAAiB,GACzB,IAAI,CA+BN"}
|
package/dist/canvas.js
CHANGED
|
@@ -8,13 +8,66 @@
|
|
|
8
8
|
export function toImageData(page) {
|
|
9
9
|
return new ImageData(Uint8ClampedArray.from(page.data), page.width, page.height);
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Paint a rendered page onto a canvas, resizing the canvas to match.
|
|
13
|
+
*
|
|
14
|
+
* With a `rotation` the canvas takes the turned dimensions — a 90° view of a portrait
|
|
15
|
+
* page is a landscape canvas.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const vp = viewport(doc.pageSize(0), { fitWidth: 1600, rotation: 90 });
|
|
20
|
+
* paintToCanvas(await doc.renderPage(0, { dpi: vp.dpi }), canvas, {
|
|
21
|
+
* rotation: vp.rotation,
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function paintToCanvas(page, canvas, options = {}) {
|
|
26
|
+
const rotation = options.rotation ?? 0;
|
|
27
|
+
const turned = rotation === 90 || rotation === 270;
|
|
28
|
+
canvas.width = turned ? page.height : page.width;
|
|
29
|
+
canvas.height = turned ? page.width : page.height;
|
|
15
30
|
const ctx = canvas.getContext('2d');
|
|
16
31
|
if (!ctx)
|
|
17
32
|
throw new Error('papyra: could not get a 2d canvas context');
|
|
18
|
-
|
|
33
|
+
if (rotation === 0) {
|
|
34
|
+
ctx.putImageData(toImageData(page), 0, 0);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// `putImageData` writes straight to the backing store and ignores the transform, so
|
|
38
|
+
// a rotation cannot go through it. The pixels land on a scratch surface first and
|
|
39
|
+
// are then blitted under a transform, which is one extra copy — against rotating
|
|
40
|
+
// the buffer ourselves, which is a copy plus a per-pixel loop in JS.
|
|
41
|
+
const scratch = scratchCanvas(page.width, page.height);
|
|
42
|
+
const scratchCtx = scratch.getContext('2d');
|
|
43
|
+
if (!scratchCtx)
|
|
44
|
+
throw new Error('papyra: could not get a 2d canvas context');
|
|
45
|
+
scratchCtx.putImageData(toImageData(page), 0, 0);
|
|
46
|
+
// Each pair puts the rotated page's origin back at the canvas corner: at 90° the
|
|
47
|
+
// page's top-left corner ends up top-right, so the axes move there first.
|
|
48
|
+
if (rotation === 90)
|
|
49
|
+
ctx.translate(canvas.width, 0);
|
|
50
|
+
else if (rotation === 180)
|
|
51
|
+
ctx.translate(canvas.width, canvas.height);
|
|
52
|
+
else
|
|
53
|
+
ctx.translate(0, canvas.height);
|
|
54
|
+
ctx.rotate((rotation * Math.PI) / 180);
|
|
55
|
+
ctx.drawImage(scratch, 0, 0);
|
|
56
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A surface to hold the unrotated pixels.
|
|
60
|
+
*
|
|
61
|
+
* `OffscreenCanvas` when there is one — it is the only option inside a worker, where
|
|
62
|
+
* there is no `document` to create an element from.
|
|
63
|
+
*/
|
|
64
|
+
function scratchCanvas(width, height) {
|
|
65
|
+
if (typeof OffscreenCanvas !== 'undefined') {
|
|
66
|
+
return new OffscreenCanvas(width, height);
|
|
67
|
+
}
|
|
68
|
+
const el = document.createElement('canvas');
|
|
69
|
+
el.width = width;
|
|
70
|
+
el.height = height;
|
|
71
|
+
return el;
|
|
19
72
|
}
|
|
20
73
|
//# sourceMappingURL=canvas.js.map
|
package/dist/canvas.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvas.js","sourceRoot":"","sources":["../src/canvas.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"canvas.js","sourceRoot":"","sources":["../src/canvas.ts"],"names":[],"mappings":"AAWA;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,IAAkB;IAC5C,OAAO,IAAI,SAAS,CAClB,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EACjC,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,MAAM,CACZ,CAAC;AACJ,CAAC;AAcD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAkB,EAClB,MAA2C,EAC3C,OAAO,GAAiB,EAAE;IAE1B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,QAAQ,KAAK,EAAE,IAAI,QAAQ,KAAK,GAAG,CAAC;IACnD,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;IACjD,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;IAElD,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAsB,CAAC;IACzD,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAEvE,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;QACnB,GAAG,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1C,OAAO;IACT,CAAC;IAED,oFAAoF;IACpF,kFAAkF;IAClF,iFAAiF;IACjF,qEAAqE;IACrE,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACvD,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAsB,CAAC;IACjE,IAAI,CAAC,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC9E,UAAU,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAEjD,iFAAiF;IACjF,0EAA0E;IAC1E,IAAI,QAAQ,KAAK,EAAE;QAAE,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;SAC/C,IAAI,QAAQ,KAAK,GAAG;QAAE,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;;QACjE,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACrC,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC;IACvC,GAAG,CAAC,SAAS,CAAC,OAA4B,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAClD,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACrC,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,KAAa,EAAE,MAAc;IAClD,IAAI,OAAO,eAAe,KAAK,WAAW,EAAE,CAAC;QAC3C,OAAO,IAAI,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC5C,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC;IACjB,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC;IACnB,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
package/dist/document.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { PdfDocument as NativeDocument } from '@build-qube/papyra-native';
|
|
2
|
+
import { type Attachment } from './attachments.js';
|
|
2
3
|
import { type CacheStats } from './cache.js';
|
|
3
4
|
import { PageImage, type SvgPage } from './encode.js';
|
|
4
5
|
import { type PageLink } from './links.js';
|
|
5
6
|
import { type OutlineNode } from './outline.js';
|
|
6
7
|
import { type JobHandle } from './scheduler.js';
|
|
7
8
|
import { type SearchMatch } from './search.js';
|
|
9
|
+
import { type StructNode } from './structure.js';
|
|
8
10
|
import { type PageText } from './text.js';
|
|
9
11
|
import type { DocumentMetadata, OpenOptions, PageSize, PdfSource, RenderedPage, RenderOptions, SearchOptions, StreamedPage, StreamOptions, SvgOptions } from './types.js';
|
|
10
12
|
/** A queued render, plus whether it was served from cache without rendering at all. */
|
|
@@ -139,6 +141,65 @@ export declare class Document {
|
|
|
139
141
|
* ```
|
|
140
142
|
*/
|
|
141
143
|
pageLabels(): Promise<readonly string[]>;
|
|
144
|
+
/**
|
|
145
|
+
* The document structure tree, if the document is tagged.
|
|
146
|
+
*
|
|
147
|
+
* Resolves to an **empty array** for an untagged document, which is the common
|
|
148
|
+
* case — most PDFs carry no `/StructTreeRoot` at all. That emptiness is the signal
|
|
149
|
+
* to fall back to {@link Document.pageText} on its own.
|
|
150
|
+
*
|
|
151
|
+
* What it buys, when it is there, is the thing extraction otherwise cannot get:
|
|
152
|
+
* the reading order the author declared. {@link Document.pageText} reports lines in
|
|
153
|
+
* content-stream order, and a two-column page is free to draw its columns
|
|
154
|
+
* interleaved. Pass both to {@link readingOrder} to resolve it.
|
|
155
|
+
*
|
|
156
|
+
* The walk runs off the event loop and the result is memoised — it is one walk for
|
|
157
|
+
* the whole document, not one per page.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```ts
|
|
161
|
+
* const [text, tree] = await Promise.all([
|
|
162
|
+
* doc.pageText(0),
|
|
163
|
+
* doc.structTree(),
|
|
164
|
+
* ]);
|
|
165
|
+
* for (const { line, node } of readingOrder(text, tree)) {
|
|
166
|
+
* console.log(node?.role ?? '-', line.text);
|
|
167
|
+
* }
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
structTree(): Promise<StructNode[]>;
|
|
171
|
+
/**
|
|
172
|
+
* The files embedded in the document, in the order it files them.
|
|
173
|
+
*
|
|
174
|
+
* Resolves to an **empty array** when there are none, which is the common case.
|
|
175
|
+
* Metadata only — {@link Document.attachmentData} fetches the bytes, so listing a
|
|
176
|
+
* document's attachments never decompresses one.
|
|
177
|
+
*
|
|
178
|
+
* Worth honouring even in a viewer that has no use for the files themselves: a
|
|
179
|
+
* ZUGFeRD or Factur-X invoice keeps its machine-readable half here, and a reader
|
|
180
|
+
* that never mentions it leaves the user retyping data the document is carrying.
|
|
181
|
+
* See {@link isInvoiceAttachment}.
|
|
182
|
+
*
|
|
183
|
+
* The walk runs off the event loop and the result is memoised.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* for (const file of await doc.attachments()) {
|
|
188
|
+
* const bytes = await doc.attachmentData(file.index);
|
|
189
|
+
* await write(file.name, bytes);
|
|
190
|
+
* }
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
attachments(): Promise<readonly Attachment[]>;
|
|
194
|
+
/**
|
|
195
|
+
* Decompress one embedded file, by its {@link Attachment.index}.
|
|
196
|
+
*
|
|
197
|
+
* Not cached: the bytes are the caller's to keep or drop, and holding every
|
|
198
|
+
* attachment a document was ever asked for is the cost {@link Document.attachments}
|
|
199
|
+
* is shaped to avoid. Rejects if the index names no attachment, or if the stream
|
|
200
|
+
* cannot be decoded.
|
|
201
|
+
*/
|
|
202
|
+
attachmentData(index: number): Promise<Uint8Array>;
|
|
142
203
|
/**
|
|
143
204
|
* The links on one page — where they are, and where they go.
|
|
144
205
|
*
|
package/dist/document.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,IAAI,cAAc,EAG9B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,KAAK,UAAU,EAAe,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,KAAK,OAAO,EAAW,MAAM,aAAa,CAAC;AAG/D,OAAO,EAAE,KAAK,QAAQ,EAAc,MAAM,YAAY,CAAC;AACvD,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAOlE,OAAO,EAAoB,KAAK,SAAS,EAAa,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,KAAK,WAAW,EAAkB,MAAM,aAAa,CAAC;AAE/D,OAAO,EAAE,KAAK,QAAQ,EAA6B,MAAM,WAAW,CAAC;AACrE,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,aAAa,EACb,aAAa,EACb,YAAY,EACZ,aAAa,EACb,UAAU,EACX,MAAM,YAAY,CAAC;AA2CpB,uFAAuF;AACvF,MAAM,WAAW,YAAa,SAAQ,SAAS,CAAC,YAAY,CAAC;IAC3D;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;AAE/C;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"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"}
|
package/dist/document.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PdfDocument as NativeDocument, } from '@build-qube/papyra-native';
|
|
2
|
+
import { toAttachment } from './attachments.js';
|
|
2
3
|
import { RenderCache } from './cache.js';
|
|
3
4
|
import { PageImage, svgPage } from './encode.js';
|
|
4
5
|
import { rethrowLoadError } from './errors.js';
|
|
@@ -9,6 +10,7 @@ import { currentRuntime, hardwareConcurrency, init, MAX_WASM_CONCURRENCY, } from
|
|
|
9
10
|
import { DEFAULT_PRIORITY, Scheduler } from './scheduler.js';
|
|
10
11
|
import { searchPageText } from './search.js';
|
|
11
12
|
import { toBytes } from './source.js';
|
|
13
|
+
import { buildStructTree } from './structure.js';
|
|
12
14
|
import { pageTextBytes, toPageText } from './text.js';
|
|
13
15
|
const DEFAULT_DPI = 72;
|
|
14
16
|
/**
|
|
@@ -44,6 +46,17 @@ const LINK_BYTES = 96;
|
|
|
44
46
|
* than one that takes another beat to fill in.
|
|
45
47
|
*/
|
|
46
48
|
const DEFAULT_TEXT_PRIORITY = 3;
|
|
49
|
+
/**
|
|
50
|
+
* Cache- and coalescing-key suffix for the annotation switch.
|
|
51
|
+
*
|
|
52
|
+
* Empty in the default case, so the ordinary key keeps the shape it has always had.
|
|
53
|
+
* The suffix has to exist at all because the scheduler coalesces on this string and
|
|
54
|
+
* the cache is looked up by it: without it, asking for a page with annotations off
|
|
55
|
+
* would be served the bitmap that still has them drawn on.
|
|
56
|
+
*/
|
|
57
|
+
function annotationKey(options) {
|
|
58
|
+
return options.annotations === false ? ':noannots' : '';
|
|
59
|
+
}
|
|
47
60
|
/** napi omits absent strings rather than nulling them; normalise to one shape. */
|
|
48
61
|
function toMetadata(info) {
|
|
49
62
|
return {
|
|
@@ -110,6 +123,10 @@ export class Document {
|
|
|
110
123
|
#linksPending = new Map();
|
|
111
124
|
/** Memoised by {@link outline}; the outline cannot change under us. */
|
|
112
125
|
#outline;
|
|
126
|
+
/** Memoised by {@link attachments}. */
|
|
127
|
+
#attachments;
|
|
128
|
+
/** Memoised by {@link structTree}. */
|
|
129
|
+
#structure;
|
|
113
130
|
/** Memoised by {@link pageLabels}. */
|
|
114
131
|
#labels;
|
|
115
132
|
/** Memoised by {@link metadata}. */
|
|
@@ -242,6 +259,87 @@ export class Document {
|
|
|
242
259
|
});
|
|
243
260
|
return this.#labels;
|
|
244
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* The document structure tree, if the document is tagged.
|
|
264
|
+
*
|
|
265
|
+
* Resolves to an **empty array** for an untagged document, which is the common
|
|
266
|
+
* case — most PDFs carry no `/StructTreeRoot` at all. That emptiness is the signal
|
|
267
|
+
* to fall back to {@link Document.pageText} on its own.
|
|
268
|
+
*
|
|
269
|
+
* What it buys, when it is there, is the thing extraction otherwise cannot get:
|
|
270
|
+
* the reading order the author declared. {@link Document.pageText} reports lines in
|
|
271
|
+
* content-stream order, and a two-column page is free to draw its columns
|
|
272
|
+
* interleaved. Pass both to {@link readingOrder} to resolve it.
|
|
273
|
+
*
|
|
274
|
+
* The walk runs off the event loop and the result is memoised — it is one walk for
|
|
275
|
+
* the whole document, not one per page.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```ts
|
|
279
|
+
* const [text, tree] = await Promise.all([
|
|
280
|
+
* doc.pageText(0),
|
|
281
|
+
* doc.structTree(),
|
|
282
|
+
* ]);
|
|
283
|
+
* for (const { line, node } of readingOrder(text, tree)) {
|
|
284
|
+
* console.log(node?.role ?? '-', line.text);
|
|
285
|
+
* }
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
async structTree() {
|
|
289
|
+
this.#structure ??= this.#inner
|
|
290
|
+
.structTree()
|
|
291
|
+
.then(buildStructTree)
|
|
292
|
+
// A failed walk must not be cached as a permanent absence of structure.
|
|
293
|
+
.catch((e) => {
|
|
294
|
+
this.#structure = undefined;
|
|
295
|
+
throw e;
|
|
296
|
+
});
|
|
297
|
+
return this.#structure;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* The files embedded in the document, in the order it files them.
|
|
301
|
+
*
|
|
302
|
+
* Resolves to an **empty array** when there are none, which is the common case.
|
|
303
|
+
* Metadata only — {@link Document.attachmentData} fetches the bytes, so listing a
|
|
304
|
+
* document's attachments never decompresses one.
|
|
305
|
+
*
|
|
306
|
+
* Worth honouring even in a viewer that has no use for the files themselves: a
|
|
307
|
+
* ZUGFeRD or Factur-X invoice keeps its machine-readable half here, and a reader
|
|
308
|
+
* that never mentions it leaves the user retyping data the document is carrying.
|
|
309
|
+
* See {@link isInvoiceAttachment}.
|
|
310
|
+
*
|
|
311
|
+
* The walk runs off the event loop and the result is memoised.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```ts
|
|
315
|
+
* for (const file of await doc.attachments()) {
|
|
316
|
+
* const bytes = await doc.attachmentData(file.index);
|
|
317
|
+
* await write(file.name, bytes);
|
|
318
|
+
* }
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
async attachments() {
|
|
322
|
+
this.#attachments ??= this.#inner
|
|
323
|
+
.attachments()
|
|
324
|
+
.then((files) => files.map(toAttachment))
|
|
325
|
+
// A failed walk must not be cached as a permanent absence of attachments.
|
|
326
|
+
.catch((e) => {
|
|
327
|
+
this.#attachments = undefined;
|
|
328
|
+
throw e;
|
|
329
|
+
});
|
|
330
|
+
return this.#attachments;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Decompress one embedded file, by its {@link Attachment.index}.
|
|
334
|
+
*
|
|
335
|
+
* Not cached: the bytes are the caller's to keep or drop, and holding every
|
|
336
|
+
* attachment a document was ever asked for is the cost {@link Document.attachments}
|
|
337
|
+
* is shaped to avoid. Rejects if the index names no attachment, or if the stream
|
|
338
|
+
* cannot be decoded.
|
|
339
|
+
*/
|
|
340
|
+
async attachmentData(index) {
|
|
341
|
+
return this.#inner.attachmentData(index);
|
|
342
|
+
}
|
|
245
343
|
/**
|
|
246
344
|
* The links on one page — where they are, and where they go.
|
|
247
345
|
*
|
|
@@ -334,9 +432,9 @@ export class Document {
|
|
|
334
432
|
// A distinct key space from `render`, so an image request never coalesces onto a
|
|
335
433
|
// pending raw render (different return type) or vice versa.
|
|
336
434
|
const handle = this.#scheduler.submit({
|
|
337
|
-
key: `image:${index}@${dpi.toFixed(4)}`,
|
|
435
|
+
key: `image:${index}@${dpi.toFixed(4)}${annotationKey(options)}`,
|
|
338
436
|
priority: options.priority ?? DEFAULT_PRIORITY,
|
|
339
|
-
run: () => this.#inner.renderPageImageAsync(index, dpi, options.signal).then((img) => new PageImage(img)),
|
|
437
|
+
run: () => this.#inner.renderPageImageAsync(index, dpi, options.annotations, options.signal).then((img) => new PageImage(img)),
|
|
340
438
|
});
|
|
341
439
|
attachSignal(handle, options.signal);
|
|
342
440
|
return handle;
|
|
@@ -375,9 +473,9 @@ export class Document {
|
|
|
375
473
|
const handle = this.#scheduler.submit({
|
|
376
474
|
// The background is part of the key: the scheduler coalesces same-key requests,
|
|
377
475
|
// and an opaque and a transparent conversion of one page are different output.
|
|
378
|
-
key: `svg:${index}${opaque ? '' : ':transparent'}`,
|
|
476
|
+
key: `svg:${index}${opaque ? '' : ':transparent'}${annotationKey(options)}`,
|
|
379
477
|
priority: options.priority ?? DEFAULT_PRIORITY,
|
|
380
|
-
run: () => this.#inner.renderPageSvgAsync(index, opaque, options.signal).then(svgPage),
|
|
478
|
+
run: () => this.#inner.renderPageSvgAsync(index, opaque, options.annotations, options.signal).then(svgPage),
|
|
381
479
|
});
|
|
382
480
|
attachSignal(handle, options.signal);
|
|
383
481
|
return handle;
|
|
@@ -400,14 +498,14 @@ export class Document {
|
|
|
400
498
|
*/
|
|
401
499
|
render(index, options = {}) {
|
|
402
500
|
const dpi = this.#resolveDpi(index, options);
|
|
403
|
-
const key = `${index}@${dpi.toFixed(4)}`;
|
|
501
|
+
const key = `${index}@${dpi.toFixed(4)}${annotationKey(options)}`;
|
|
404
502
|
const hit = this.#cache.get(key);
|
|
405
503
|
if (hit)
|
|
406
504
|
return cachedHandle(key, hit);
|
|
407
505
|
const handle = this.#scheduler.submit({
|
|
408
506
|
key,
|
|
409
507
|
priority: options.priority ?? DEFAULT_PRIORITY,
|
|
410
|
-
run: () => this.#inner.renderPageAsync(index, dpi).then((page) => {
|
|
508
|
+
run: () => this.#inner.renderPageAsync(index, dpi, options.annotations).then((page) => {
|
|
411
509
|
this.#cache.set(key, page);
|
|
412
510
|
return page;
|
|
413
511
|
}),
|
|
@@ -455,7 +553,7 @@ export class Document {
|
|
|
455
553
|
let dpi = DEFAULT_DPI;
|
|
456
554
|
for (let i = start; i < end; i++)
|
|
457
555
|
dpi = this.#resolveDpi(i, options);
|
|
458
|
-
return (await this.#inner.renderPagesAsync(start, end, dpi));
|
|
556
|
+
return (await this.#inner.renderPagesAsync(start, end, dpi, options.annotations));
|
|
459
557
|
}
|
|
460
558
|
const out = [];
|
|
461
559
|
for await (const { page, bitmap } of this.stream({
|