@jasy/pdf 1.0.0-beta.3 → 1.0.0-beta.4
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/dist/api/content.d.ts +5 -5
- package/dist/api/content.js +12 -6
- package/dist/api/font-url.d.ts +1 -2
- package/dist/api/font-url.js +8 -74
- package/dist/api/forms.d.ts +3 -0
- package/dist/api/forms.js +3 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +1 -0
- package/dist/api/layout.d.ts +6 -0
- package/dist/api/layout.js +7 -2
- package/dist/api/page-builder.d.ts +4 -0
- package/dist/api/page-builder.js +4 -0
- package/dist/api/structure.d.ts +10 -2
- package/dist/api/structure.js +9 -6
- package/dist/api/table.d.ts +5 -3
- package/dist/api/table.js +99 -22
- package/dist/common/diagnostics.d.ts +53 -0
- package/dist/common/diagnostics.js +77 -0
- package/dist/edit/acroform-reader.js +7 -7
- package/dist/edit/appearance.js +3 -3
- package/dist/edit/attachments.d.ts +52 -0
- package/dist/edit/attachments.js +178 -0
- package/dist/edit/document.d.ts +38 -8
- package/dist/edit/document.js +208 -51
- package/dist/edit/fill.js +8 -23
- package/dist/edit/filters.d.ts +15 -0
- package/dist/edit/filters.js +157 -0
- package/dist/edit/index.d.ts +4 -1
- package/dist/edit/index.js +3 -1
- package/dist/edit/objects.d.ts +3 -4
- package/dist/edit/objects.js +5 -18
- package/dist/elements/canvas-element.d.ts +3 -0
- package/dist/elements/canvas-element.js +14 -3
- package/dist/elements/container-element.d.ts +5 -1
- package/dist/elements/container-element.js +35 -15
- package/dist/elements/forms/checkbox-element.d.ts +2 -0
- package/dist/elements/forms/checkbox-element.js +12 -3
- package/dist/elements/forms/choice-element.d.ts +6 -1
- package/dist/elements/forms/choice-element.js +17 -6
- package/dist/elements/forms/field-font.d.ts +8 -0
- package/dist/elements/forms/field-font.js +5 -0
- package/dist/elements/forms/push-button-element.d.ts +6 -1
- package/dist/elements/forms/push-button-element.js +16 -4
- package/dist/elements/forms/radio-element.d.ts +2 -0
- package/dist/elements/forms/radio-element.js +12 -3
- package/dist/elements/forms/signature-element.d.ts +6 -1
- package/dist/elements/forms/signature-element.js +16 -4
- package/dist/elements/forms/text-field-element.d.ts +6 -1
- package/dist/elements/forms/text-field-element.js +16 -4
- package/dist/elements/image-element.d.ts +39 -1
- package/dist/elements/image-element.js +95 -12
- package/dist/elements/layout/anchor-element.d.ts +8 -10
- package/dist/elements/layout/anchor-element.js +10 -18
- package/dist/elements/layout/bookmark-element.d.ts +9 -11
- package/dist/elements/layout/bookmark-element.js +11 -20
- package/dist/elements/layout/default-text-style-element.d.ts +11 -13
- package/dist/elements/layout/default-text-style-element.js +14 -31
- package/dist/elements/layout/deferred-element.d.ts +12 -4
- package/dist/elements/layout/deferred-element.js +24 -7
- package/dist/elements/layout/expanded-element.d.ts +7 -0
- package/dist/elements/layout/expanded-element.js +40 -18
- package/dist/elements/layout/keep-together-element.d.ts +8 -18
- package/dist/elements/layout/keep-together-element.js +21 -45
- package/dist/elements/layout/link-element.d.ts +8 -13
- package/dist/elements/layout/link-element.js +9 -19
- package/dist/elements/layout/padding-element.d.ts +15 -1
- package/dist/elements/layout/padding-element.js +55 -27
- package/dist/elements/layout/page-break-element.d.ts +4 -1
- package/dist/elements/layout/page-break-element.js +6 -1
- package/dist/elements/layout/page-builder-element.d.ts +5 -0
- package/dist/elements/layout/page-builder-element.js +11 -0
- package/dist/elements/layout/positioned-element.d.ts +14 -2
- package/dist/elements/layout/positioned-element.js +36 -10
- package/dist/elements/layout/proxy-element.d.ts +43 -0
- package/dist/elements/layout/proxy-element.js +94 -0
- package/dist/elements/layout/repeating-header-element.d.ts +6 -0
- package/dist/elements/layout/repeating-header-element.js +40 -13
- package/dist/elements/layout/rotated-box-element.d.ts +6 -0
- package/dist/elements/layout/rotated-box-element.js +27 -14
- package/dist/elements/layout/rotated-element.d.ts +7 -0
- package/dist/elements/layout/rotated-element.js +25 -2
- package/dist/elements/layout/struct-group.d.ts +9 -17
- package/dist/elements/layout/struct-group.js +10 -35
- package/dist/elements/line-element.d.ts +1 -0
- package/dist/elements/line-element.js +6 -0
- package/dist/elements/page-element.d.ts +10 -3
- package/dist/elements/page-element.js +39 -31
- package/dist/elements/pdf-document-element.d.ts +3 -0
- package/dist/elements/pdf-document-element.js +7 -0
- package/dist/elements/pdf-element.d.ts +75 -15
- package/dist/elements/pdf-element.js +68 -18
- package/dist/elements/rectangle-element.d.ts +14 -0
- package/dist/elements/rectangle-element.js +97 -52
- package/dist/elements/row-element.d.ts +11 -0
- package/dist/elements/row-element.js +58 -8
- package/dist/elements/svg-element.d.ts +30 -6
- package/dist/elements/svg-element.js +88 -17
- package/dist/elements/text-element.d.ts +32 -13
- package/dist/elements/text-element.js +119 -97
- package/dist/forms/acroform.d.ts +9 -1
- package/dist/forms/acroform.js +83 -34
- package/dist/forms/appearance.d.ts +17 -6
- package/dist/forms/appearance.js +18 -16
- package/dist/ir/display-list.d.ts +12 -2
- package/dist/layout/box-constraints.d.ts +5 -0
- package/dist/layout/box-constraints.js +14 -0
- package/dist/layout/collect-images.d.ts +6 -4
- package/dist/layout/collect-images.js +6 -4
- package/dist/layout/flex-item.d.ts +9 -0
- package/dist/layout/flex-item.js +39 -0
- package/dist/layout/fragmentation.d.ts +12 -4
- package/dist/layout/fragmentation.js +37 -14
- package/dist/layout/layout-cache.d.ts +53 -0
- package/dist/layout/layout-cache.js +107 -0
- package/dist/renderer/anchor-renderer.d.ts +2 -1
- package/dist/renderer/anchor-renderer.js +5 -4
- package/dist/renderer/bookmark-renderer.d.ts +2 -1
- package/dist/renderer/bookmark-renderer.js +5 -4
- package/dist/renderer/canvas-renderer.d.ts +2 -1
- package/dist/renderer/canvas-renderer.js +5 -2
- package/dist/renderer/container-renderer.d.ts +2 -1
- package/dist/renderer/container-renderer.js +6 -5
- package/dist/renderer/default-text-style-renderer.d.ts +2 -1
- package/dist/renderer/default-text-style-renderer.js +2 -3
- package/dist/renderer/deferred-renderer.d.ts +2 -1
- package/dist/renderer/deferred-renderer.js +2 -3
- package/dist/renderer/expanded-renderer.d.ts +2 -1
- package/dist/renderer/expanded-renderer.js +2 -3
- package/dist/renderer/form-field-renderer.d.ts +2 -1
- package/dist/renderer/form-field-renderer.js +16 -3
- package/dist/renderer/image-renderer.d.ts +2 -1
- package/dist/renderer/image-renderer.js +5 -2
- package/dist/renderer/keep-together-renderer.d.ts +2 -1
- package/dist/renderer/keep-together-renderer.js +2 -3
- package/dist/renderer/line-renderer.d.ts +2 -1
- package/dist/renderer/line-renderer.js +7 -2
- package/dist/renderer/link-renderer.d.ts +2 -1
- package/dist/renderer/link-renderer.js +36 -5
- package/dist/renderer/padding-renderer.d.ts +2 -1
- package/dist/renderer/padding-renderer.js +5 -5
- package/dist/renderer/page-break-renderer.d.ts +6 -5
- package/dist/renderer/page-break-renderer.js +14 -7
- package/dist/renderer/page-builder-renderer.d.ts +2 -1
- package/dist/renderer/page-builder-renderer.js +2 -3
- package/dist/renderer/page-renderer.js +28 -7
- package/dist/renderer/pdf-backend.d.ts +4 -3
- package/dist/renderer/pdf-backend.js +5 -5
- package/dist/renderer/pdf-document-renderer.d.ts +1 -8
- package/dist/renderer/pdf-document-renderer.js +15 -16
- package/dist/renderer/pdf-renderer.js +11 -11
- package/dist/renderer/positioned-renderer.d.ts +6 -1
- package/dist/renderer/positioned-renderer.js +6 -5
- package/dist/renderer/rectangle-renderer.d.ts +2 -1
- package/dist/renderer/rectangle-renderer.js +9 -4
- package/dist/renderer/repeating-header-renderer.d.ts +2 -1
- package/dist/renderer/repeating-header-renderer.js +6 -5
- package/dist/renderer/rotated-renderer.d.ts +2 -1
- package/dist/renderer/rotated-renderer.js +8 -5
- package/dist/renderer/row-renderer.d.ts +2 -1
- package/dist/renderer/row-renderer.js +6 -5
- package/dist/renderer/struct-group-renderer.d.ts +2 -1
- package/dist/renderer/struct-group-renderer.js +2 -3
- package/dist/renderer/svg-renderer.d.ts +2 -1
- package/dist/renderer/svg-renderer.js +8 -2
- package/dist/renderer/text-renderer.d.ts +4 -6
- package/dist/renderer/text-renderer.js +101 -111
- package/dist/svg/gradients.js +5 -0
- package/dist/svg/index.js +10 -1
- package/dist/svg/style.d.ts +2 -0
- package/dist/svg/style.js +11 -0
- package/dist/text/advance.d.ts +4 -0
- package/dist/text/advance.js +17 -4
- package/dist/text/bidi.d.ts +0 -2
- package/dist/text/bidi.js +0 -10
- package/dist/text/glyph-coverage.d.ts +1 -1
- package/dist/text/glyph-coverage.js +7 -3
- package/dist/text/hyphen-breaks.d.ts +17 -0
- package/dist/text/hyphen-breaks.js +54 -0
- package/dist/text/line-breaker.d.ts +34 -31
- package/dist/text/line-breaker.js +364 -176
- package/dist/text/line-metrics.d.ts +6 -3
- package/dist/text/line-metrics.js +7 -2
- package/dist/text/pdf-text-string.d.ts +21 -0
- package/dist/text/pdf-text-string.js +114 -0
- package/dist/text/text-decoration.d.ts +5 -5
- package/dist/text/text-decoration.js +5 -5
- package/dist/text/word-splitting.d.ts +15 -2
- package/dist/text/word-splitting.js +34 -8
- package/dist/utils/afm-parser.d.ts +2 -0
- package/dist/utils/afm-parser.js +10 -1
- package/dist/utils/dest-registry.d.ts +1 -1
- package/dist/utils/dest-registry.js +3 -2
- package/dist/utils/fetch-bytes.d.ts +19 -0
- package/dist/utils/fetch-bytes.js +79 -0
- package/dist/utils/flex-layout.d.ts +13 -13
- package/dist/utils/flex-layout.js +318 -216
- package/dist/utils/font-metrics.d.ts +4 -3
- package/dist/utils/image-helper.js +2 -1
- package/dist/utils/pdf-object-manager.d.ts +11 -3
- package/dist/utils/pdf-object-manager.js +39 -13
- package/dist/utils/renderer-registry.d.ts +43 -2
- package/dist/utils/renderer-registry.js +40 -0
- package/dist/utils/struct-tree.d.ts +24 -0
- package/dist/utils/struct-tree.js +61 -9
- package/dist/utils/ttf-parser.d.ts +4 -0
- package/dist/utils/ttf-parser.js +27 -4
- package/dist/validators/element-validator.js +2 -1
- package/package.json +1 -1
- package/dist/elements/layout/sized-container-element.d.ts +0 -14
- package/dist/elements/layout/sized-container-element.js +0 -32
package/dist/api/content.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export interface SvgOptions extends BoundsInput {
|
|
|
27
27
|
/** Alternate text (tagged PDF). With it the drawing is a `Figure`; without it, decoration. */
|
|
28
28
|
alt?: string;
|
|
29
29
|
}
|
|
30
|
-
/** An image source: a local file path (Node), raw bytes
|
|
30
|
+
/** An image source: a URL (`http(s):`, `data:`), a local file path (Node), raw bytes, or a `CustomImage`. */
|
|
31
31
|
export type ImageSource = string | Uint8Array | CustomImage;
|
|
32
32
|
/** How the image fills its box (locked §4). Mirrors CSS `object-fit`. */
|
|
33
33
|
export type ImageFit = "none" | "contain" | "cover" | "fill";
|
|
@@ -36,8 +36,8 @@ export interface ImageOptions extends BoundsInput {
|
|
|
36
36
|
* space). Pin exactly ONE axis and the other follows the image's aspect ratio (CSS `height: auto`). */
|
|
37
37
|
width?: SizeInput;
|
|
38
38
|
height?: SizeInput;
|
|
39
|
-
/** Fit within the box. Default `none
|
|
40
|
-
*
|
|
39
|
+
/** Fit within the box. Default `none` (the image's own size); with any explicit size or an
|
|
40
|
+
* `aspectRatio`, `fill` - the image scales into its box, as `<img width height>` does. */
|
|
41
41
|
fit?: ImageFit;
|
|
42
42
|
/** Corner radius in points (rounds the image box). */
|
|
43
43
|
radius?: RadiusInput;
|
|
@@ -73,8 +73,8 @@ export declare function Canvas(paint: CanvasPaint): CanvasElement;
|
|
|
73
73
|
*/
|
|
74
74
|
export declare function Svg(source: SvgSource, opts?: SvgOptions): SvgElement;
|
|
75
75
|
/**
|
|
76
|
-
* An image. `src` is a local file path
|
|
77
|
-
* `CustomImage
|
|
76
|
+
* An image. `src` is a URL (fetched when the document renders), a local file path, raw bytes, or a
|
|
77
|
+
* ready `CustomImage`. Maps to an `ImageElement`.
|
|
78
78
|
*
|
|
79
79
|
* An SVG source is routed to `Svg` instead, so `Image({ src: "logo.svg" })` just works and a logo
|
|
80
80
|
* stays a VECTOR in the PDF rather than becoming a bitmap.
|
package/dist/api/content.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LineElement } from "../elements/line-element.js";
|
|
2
2
|
import { PaddingElement } from "../elements/layout/padding-element.js";
|
|
3
|
-
import { ImageElement, CustomLocalImage, CustomBytesImage, BoxFit, } from "../elements/image-element.js";
|
|
3
|
+
import { ImageElement, CustomLocalImage, CustomBytesImage, CustomUrlImage, isImageUrl, BoxFit, } from "../elements/image-element.js";
|
|
4
4
|
import { toColor } from "./color.js";
|
|
5
5
|
import { toEdges } from "./insets.js";
|
|
6
6
|
import { toBounds, toDimension, toRadius, } from "./dimension.js";
|
|
@@ -84,13 +84,17 @@ export function Svg(source, opts = {}) {
|
|
|
84
84
|
/** SVG is recognised by its extension or by its own opening bytes - never by a caller having to say. */
|
|
85
85
|
function isSvg(src) {
|
|
86
86
|
if (typeof src === "string") {
|
|
87
|
+
// A URL by its media type (`data:image/svg+xml`) or the extension of its PATH - a query may follow.
|
|
88
|
+
const path = isImageUrl(src) ? src.trim().replace(/[?#].*$/, "") : src.trim();
|
|
89
|
+
if (/^data:image\/svg\+xml[;,]/i.test(src.trim()))
|
|
90
|
+
return true;
|
|
87
91
|
// `.svgz` is a GZIPPED svg. It is caught here rather than left to the image path, because the
|
|
88
92
|
// error a user needs says what the file is - not "unsupported image bytes".
|
|
89
|
-
if (/\.svgz$/i.test(
|
|
93
|
+
if (/\.svgz$/i.test(path)) {
|
|
90
94
|
throw new Error("@jasy/pdf: SVGZ (a gzip-compressed SVG) is not supported. Decompress it first " +
|
|
91
95
|
"(`gunzip logo.svgz`) and pass the plain .svg.");
|
|
92
96
|
}
|
|
93
|
-
return /\.svg$/i.test(
|
|
97
|
+
return /\.svg$/i.test(path) || /^\s*[<\ufeff]/.test(src);
|
|
94
98
|
}
|
|
95
99
|
if (src instanceof Uint8Array) {
|
|
96
100
|
const head = new TextDecoder().decode(src.subarray(0, 200)).trimStart();
|
|
@@ -99,8 +103,8 @@ function isSvg(src) {
|
|
|
99
103
|
return false;
|
|
100
104
|
}
|
|
101
105
|
/**
|
|
102
|
-
* An image. `src` is a local file path
|
|
103
|
-
* `CustomImage
|
|
106
|
+
* An image. `src` is a URL (fetched when the document renders), a local file path, raw bytes, or a
|
|
107
|
+
* ready `CustomImage`. Maps to an `ImageElement`.
|
|
104
108
|
*
|
|
105
109
|
* An SVG source is routed to `Svg` instead, so `Image({ src: "logo.svg" })` just works and a logo
|
|
106
110
|
* stays a VECTOR in the PDF rather than becoming a bitmap.
|
|
@@ -129,7 +133,9 @@ function rasterImage(src, opts = {}) {
|
|
|
129
133
|
const fit = opts.fit ? FIT[opts.fit] : sized ? BoxFit.fill : undefined;
|
|
130
134
|
return new ImageElement({
|
|
131
135
|
image: typeof src === "string"
|
|
132
|
-
?
|
|
136
|
+
? isImageUrl(src)
|
|
137
|
+
? new CustomUrlImage(src)
|
|
138
|
+
: new CustomLocalImage(src)
|
|
133
139
|
: src instanceof Uint8Array
|
|
134
140
|
? new CustomBytesImage(src)
|
|
135
141
|
: src,
|
package/dist/api/font-url.d.ts
CHANGED
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
* same contract, differing only in being async. So the failure lands where you asked for the font,
|
|
6
6
|
* not somewhere inside a later render.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* port, unlike the filesystem read beside it.
|
|
8
|
+
* The fetch itself - timeout, size ceiling - is `utils/fetch-bytes.ts`, shared with images.
|
|
10
9
|
*/
|
|
11
10
|
/** One font file by URL, or a styled family of them. Mirrors `FontSource`, with strings meaning URLs. */
|
|
12
11
|
export type UrlFontSource = string | {
|
package/dist/api/font-url.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* same contract, differing only in being async. So the failure lands where you asked for the font,
|
|
6
6
|
* not somewhere inside a later render.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* port, unlike the filesystem read beside it.
|
|
8
|
+
* The fetch itself - timeout, size ceiling - is `utils/fetch-bytes.ts`, shared with images.
|
|
10
9
|
*/
|
|
10
|
+
import { fetchBytes } from "../utils/fetch-bytes.js";
|
|
11
11
|
/** Thrown when a font could not be fetched or is not one we can parse. Always says which URL. */
|
|
12
12
|
export class FontUrlError extends Error {
|
|
13
13
|
constructor(message) {
|
|
@@ -36,82 +36,16 @@ function describeSignature(bytes) {
|
|
|
36
36
|
return { ok: false, what: "not a font at all" };
|
|
37
37
|
return { ok: false, what: "not a font we recognise" };
|
|
38
38
|
}
|
|
39
|
-
/** How long one font may take to arrive before we give up.
|
|
39
|
+
/** How long one font may take to arrive before we give up. */
|
|
40
40
|
const TIMEOUT_MS = 15000;
|
|
41
|
-
/**
|
|
42
|
-
* How large a font file may be. Generous on purpose - a full CJK face is tens of megabytes and is a
|
|
43
|
-
* legitimate thing to embed - but not unbounded: a URL pointing at the wrong file (a video, a disk
|
|
44
|
-
* image) would otherwise be read into memory in full before anyone notices it is not a font.
|
|
45
|
-
*/
|
|
41
|
+
/** How large a font file may be: generous on purpose - a full CJK face is tens of megabytes. */
|
|
46
42
|
const MAX_BYTES = 32 * 1024 * 1024;
|
|
47
|
-
/** Read the body with a running ceiling, so an oversized response is dropped WHILE it arrives. */
|
|
48
|
-
async function readBounded(response, url) {
|
|
49
|
-
// `Content-Length` is the cheap early exit; a server may omit or misstate it, so it is a hint only
|
|
50
|
-
// and the real ceiling is enforced below while reading.
|
|
51
|
-
const declared = Number(response.headers?.get?.("content-length") ?? NaN);
|
|
52
|
-
if (Number.isFinite(declared) && declared > MAX_BYTES) {
|
|
53
|
-
throw new FontUrlError(`the font at ${url} is ${declared} bytes; the limit is ${MAX_BYTES}`);
|
|
54
|
-
}
|
|
55
|
-
const body = response.body;
|
|
56
|
-
if (!body?.getReader) {
|
|
57
|
-
// No streaming body (an older runtime, or a stubbed response): fall back to reading it whole and
|
|
58
|
-
// checking afterwards. Weaker, but never worse than not checking at all.
|
|
59
|
-
const whole = new Uint8Array(await response.arrayBuffer());
|
|
60
|
-
if (whole.length > MAX_BYTES) {
|
|
61
|
-
throw new FontUrlError(`the font at ${url} is larger than the ${MAX_BYTES} byte limit`);
|
|
62
|
-
}
|
|
63
|
-
return whole;
|
|
64
|
-
}
|
|
65
|
-
const reader = body.getReader();
|
|
66
|
-
const chunks = [];
|
|
67
|
-
let total = 0;
|
|
68
|
-
for (;;) {
|
|
69
|
-
const { done, value } = await reader.read();
|
|
70
|
-
if (done)
|
|
71
|
-
break;
|
|
72
|
-
total += value.length;
|
|
73
|
-
if (total > MAX_BYTES) {
|
|
74
|
-
await reader.cancel();
|
|
75
|
-
throw new FontUrlError(`the font at ${url} is larger than the ${MAX_BYTES} byte limit`);
|
|
76
|
-
}
|
|
77
|
-
chunks.push(value);
|
|
78
|
-
}
|
|
79
|
-
if (chunks.length === 1)
|
|
80
|
-
return chunks[0];
|
|
81
|
-
const out = new Uint8Array(total);
|
|
82
|
-
let at = 0;
|
|
83
|
-
for (const c of chunks) {
|
|
84
|
-
out.set(c, at);
|
|
85
|
-
at += c.length;
|
|
86
|
-
}
|
|
87
|
-
return out;
|
|
88
|
-
}
|
|
89
43
|
/** Fetch one font file and hand back its bytes, refusing anything we could not embed. */
|
|
90
44
|
async function fetchFont(url) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
catch (e) {
|
|
96
|
-
const why = e?.name === "TimeoutError" || e?.name === "AbortError"
|
|
97
|
-
? `it did not respond within ${TIMEOUT_MS} ms`
|
|
98
|
-
: String(e?.message ?? e);
|
|
99
|
-
throw new FontUrlError(`could not fetch the font at ${url}: ${why}`);
|
|
100
|
-
}
|
|
101
|
-
if (!response.ok) {
|
|
102
|
-
throw new FontUrlError(`the font at ${url} could not be fetched: HTTP ${response.status}`);
|
|
103
|
-
}
|
|
104
|
-
// Everything past here still talks to the network, so a failure mid-body must not escape as a raw
|
|
105
|
-
// TypeError - a caller catching FontUrlError would miss it.
|
|
106
|
-
let bytes;
|
|
107
|
-
try {
|
|
108
|
-
bytes = await readBounded(response, url);
|
|
109
|
-
}
|
|
110
|
-
catch (e) {
|
|
111
|
-
if (e instanceof FontUrlError)
|
|
112
|
-
throw e;
|
|
113
|
-
throw new FontUrlError(`the font at ${url} could not be read: ${String(e?.message ?? e)}`);
|
|
114
|
-
}
|
|
45
|
+
const bytes = await fetchBytes(url, "font", FontUrlError, {
|
|
46
|
+
timeoutMs: TIMEOUT_MS,
|
|
47
|
+
maxBytes: MAX_BYTES,
|
|
48
|
+
});
|
|
115
49
|
if (bytes.length < 4) {
|
|
116
50
|
throw new FontUrlError(`the font at ${url} is empty`);
|
|
117
51
|
}
|
package/dist/api/forms.d.ts
CHANGED
|
@@ -297,6 +297,9 @@ export interface PushButtonOptions {
|
|
|
297
297
|
* A clickable push button (AcroForm /Btn). It holds no value - it fires an action. Its caption is baked
|
|
298
298
|
* into the appearance, so the button looks the same in every viewer and in print.
|
|
299
299
|
*
|
|
300
|
+
* Not for PDF/A: PDF/A forbids an action on a form field (ISO 19005-2/-3, 6.4.1), and an action is all a
|
|
301
|
+
* push button does.
|
|
302
|
+
*
|
|
300
303
|
* ```ts
|
|
301
304
|
* PushButton({ name: "send", label: "Submit", action: { submit: "https://example.com/f" }, width: 120 })
|
|
302
305
|
* PushButton({ name: "clear", label: "Reset", action: "reset", width: 90 })
|
package/dist/api/forms.js
CHANGED
|
@@ -146,6 +146,9 @@ const toAction = (a) => {
|
|
|
146
146
|
* A clickable push button (AcroForm /Btn). It holds no value - it fires an action. Its caption is baked
|
|
147
147
|
* into the appearance, so the button looks the same in every viewer and in print.
|
|
148
148
|
*
|
|
149
|
+
* Not for PDF/A: PDF/A forbids an action on a form field (ISO 19005-2/-3, 6.4.1), and an action is all a
|
|
150
|
+
* push button does.
|
|
151
|
+
*
|
|
149
152
|
* ```ts
|
|
150
153
|
* PushButton({ name: "send", label: "Submit", action: { submit: "https://example.com/f" }, width: 120 })
|
|
151
154
|
* PushButton({ name: "clear", label: "Reset", action: "reset", width: 90 })
|
package/dist/api/index.d.ts
CHANGED
|
@@ -2,12 +2,14 @@ export * from "./color.js";
|
|
|
2
2
|
export * from "./insets.js";
|
|
3
3
|
export * from "./gradient.js";
|
|
4
4
|
export * from "./font-url.js";
|
|
5
|
+
export { FontFormatError } from "../utils/ttf-parser.js";
|
|
5
6
|
export * from "./dimension.js";
|
|
6
7
|
export * from "./text.js";
|
|
7
8
|
export * from "./layout.js";
|
|
8
9
|
export * from "./content.js";
|
|
9
10
|
export * from "./page-builder.js";
|
|
10
11
|
export * from "./structure.js";
|
|
12
|
+
export type { Diagnostic, DiagnosticCode, DiagnosticHandler } from "../common/diagnostics.js";
|
|
11
13
|
export * from "./table.js";
|
|
12
14
|
export * from "./forms.js";
|
|
13
15
|
export * from "./descriptor.js";
|
package/dist/api/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./color.js";
|
|
|
5
5
|
export * from "./insets.js";
|
|
6
6
|
export * from "./gradient.js";
|
|
7
7
|
export * from "./font-url.js";
|
|
8
|
+
export { FontFormatError } from "../utils/ttf-parser.js";
|
|
8
9
|
export * from "./dimension.js";
|
|
9
10
|
export * from "./text.js";
|
|
10
11
|
export * from "./layout.js";
|
package/dist/api/layout.d.ts
CHANGED
|
@@ -187,6 +187,12 @@ export interface ExpandedOptions {
|
|
|
187
187
|
* rest`, so `flexBasis: 120` with `flex: 0` is simply a fixed 120pt slot. Default 0.
|
|
188
188
|
*/
|
|
189
189
|
flexBasis?: SizeInput;
|
|
190
|
+
/**
|
|
191
|
+
* How willingly this child gives up its `flexBasis` when the line overflows (CSS `flex-shrink`). Default
|
|
192
|
+
* 1, as for every flex item: an overflowing line pulls a `flexBasis: 120` slot back down to what its
|
|
193
|
+
* content can hold. `0` keeps the basis and lets the line overflow. A basis of 0 has nothing to give.
|
|
194
|
+
*/
|
|
195
|
+
flexShrink?: number;
|
|
190
196
|
}
|
|
191
197
|
/**
|
|
192
198
|
* Makes a child fill the leftover space along the stack's main axis (height in a Column,
|
package/dist/api/layout.js
CHANGED
|
@@ -11,6 +11,7 @@ import { BookmarkElement } from "../elements/layout/bookmark-element.js";
|
|
|
11
11
|
import { AnchorElement } from "../elements/layout/anchor-element.js";
|
|
12
12
|
import { RotatedElement } from "../elements/layout/rotated-element.js";
|
|
13
13
|
import { RotatedBoxElement } from "../elements/layout/rotated-box-element.js";
|
|
14
|
+
import { flexibleOf } from "../layout/flex-item.js";
|
|
14
15
|
import { toColor } from "./color.js";
|
|
15
16
|
import { isGradientInput } from "./gradient.js";
|
|
16
17
|
import { toEdges } from "./insets.js";
|
|
@@ -90,9 +91,13 @@ export function Box(a, b) {
|
|
|
90
91
|
new PaddingElement({
|
|
91
92
|
margin: toEdges(opts.padding),
|
|
92
93
|
child: children.length === 1 ? children[0] : Column(children),
|
|
94
|
+
ofBox: true,
|
|
93
95
|
}),
|
|
94
96
|
]
|
|
95
|
-
:
|
|
97
|
+
: // A Spacer/Expanded - also one inside a transparent wrapper such as a Link - needs a flex parent.
|
|
98
|
+
children.length > 1 && children.some((child) => flexibleOf(child) !== undefined)
|
|
99
|
+
? [Column(children)]
|
|
100
|
+
: children;
|
|
96
101
|
// A side is set if it (or the uniform `border`) is given. If ANY differs from a plain
|
|
97
102
|
// uniform border, we hand the engine per-side colors (which draws individual lines).
|
|
98
103
|
const sideKeys = [opts.borderTop, opts.borderRight, opts.borderBottom, opts.borderLeft];
|
|
@@ -226,5 +231,5 @@ export function Expanded(a, b) {
|
|
|
226
231
|
basis: basis?.points,
|
|
227
232
|
basisFactor: basis?.factor,
|
|
228
233
|
child,
|
|
229
|
-
});
|
|
234
|
+
}).withFlexShrink(opts.flexShrink);
|
|
230
235
|
}
|
|
@@ -24,6 +24,10 @@ export type { PageInfo };
|
|
|
24
24
|
* content decides the count that the content displays): in the flowing BODY the box is reserved before the
|
|
25
25
|
* total is known, so a much wider final string can paint slightly past it; and a conditional header may
|
|
26
26
|
* SHRINK on later pages but must not GROW, because the body band was measured against the first build.
|
|
27
|
+
*
|
|
28
|
+
* What it builds is placed WHOLE: it never splits across pages, and a `PageBreak` inside it has no
|
|
29
|
+
* effect. Its content belongs to one page by construction - a piece continued on the next page would
|
|
30
|
+
* have been built for the wrong one. Keep it to what fits on a page; flowing content goes beside it.
|
|
27
31
|
*/
|
|
28
32
|
export declare function PageBuilder(build: (info: PageInfo) => PDFElement): PageBuilderElement;
|
|
29
33
|
/** `Text` options plus an `offset` added to the number (e.g. `-1` to not count a cover page). */
|
package/dist/api/page-builder.js
CHANGED
|
@@ -22,6 +22,10 @@ import { Text } from "./text.js";
|
|
|
22
22
|
* content decides the count that the content displays): in the flowing BODY the box is reserved before the
|
|
23
23
|
* total is known, so a much wider final string can paint slightly past it; and a conditional header may
|
|
24
24
|
* SHRINK on later pages but must not GROW, because the body band was measured against the first build.
|
|
25
|
+
*
|
|
26
|
+
* What it builds is placed WHOLE: it never splits across pages, and a `PageBreak` inside it has no
|
|
27
|
+
* effect. Its content belongs to one page by construction - a piece continued on the next page would
|
|
28
|
+
* have been built for the wrong one. Keep it to what fits on a page; flowing content goes beside it.
|
|
25
29
|
*/
|
|
26
30
|
export function PageBuilder(build) {
|
|
27
31
|
return new PageBuilderElement({ build });
|
package/dist/api/structure.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { PageElement } from "../elements/page-element.js";
|
|
|
3
3
|
import { PDFElement } from "../elements/pdf-element.js";
|
|
4
4
|
import { DefaultTextStyleElement } from "../elements/layout/default-text-style-element.js";
|
|
5
5
|
import type { OverflowPolicy } from "../layout/fragmentation.js";
|
|
6
|
+
import type { DiagnosticHandler } from "../common/diagnostics.js";
|
|
6
7
|
import { PageSize } from "../constants/page-sizes.js";
|
|
7
8
|
import { type EncryptOptions } from "../crypto/security-handler.js";
|
|
8
9
|
export type { EncryptOptions, Permissions } from "../crypto/security-handler.js";
|
|
@@ -38,8 +39,6 @@ export interface PageOptions extends StackOptions {
|
|
|
38
39
|
*/
|
|
39
40
|
export declare function Page(children: PDFElement[]): PageElement;
|
|
40
41
|
export declare function Page(opts: PageOptions, children: PDFElement[]): PageElement;
|
|
41
|
-
/** Document options: PDF metadata plus the inheritable text defaults (font, size, color, lineHeight,
|
|
42
|
-
* align, bold/italic) every `Text` inherits unless it sets its own - Flutter's `DefaultTextStyle`. */
|
|
43
42
|
/** Where color emoji come from when a code point isn't in the current text font, so `Text("Hi 😅")`
|
|
44
43
|
* works in one string. Either a **font** (native vector, offline - recommended; register it in
|
|
45
44
|
* `fonts`) as a string name or `{ font }`, or an **image/CDN source** `{ url, format }` (react-pdf
|
|
@@ -51,6 +50,8 @@ export type EmojiSource = string | {
|
|
|
51
50
|
url: string;
|
|
52
51
|
format?: "png";
|
|
53
52
|
};
|
|
53
|
+
/** Document options: PDF metadata plus the inheritable text defaults (font, size, color, lineHeight,
|
|
54
|
+
* align, bold/italic) every `Text` inherits unless it sets its own - Flutter's `DefaultTextStyle`. */
|
|
54
55
|
export interface DocumentOptions extends TextDefaults {
|
|
55
56
|
/** PDF metadata. */
|
|
56
57
|
meta?: {
|
|
@@ -158,6 +159,13 @@ export interface RenderOptions {
|
|
|
158
159
|
* able to SAY which character went missing, where a server log would never be read.
|
|
159
160
|
*/
|
|
160
161
|
onMissingGlyphs?: (characters: string[]) => void;
|
|
162
|
+
/**
|
|
163
|
+
* Everything the engine has to say about this document, one call per finding: a page break that had
|
|
164
|
+
* no effect, a group that could not be kept together, clipped overflow, removed characters, ... -
|
|
165
|
+
* each with a stable `code`, the kind of element and its first text. Without it, what warned before
|
|
166
|
+
* still goes to `console.warn`. `onOverflow: "error"` still throws.
|
|
167
|
+
*/
|
|
168
|
+
onDiagnostic?: DiagnosticHandler;
|
|
161
169
|
/** Encrypt the PDF with a password (AES-256, the newest standard). NOT compatible with PDF/A
|
|
162
170
|
* (ZUGFeRD invoices) - encrypting one throws, since PDF/A forbids encryption. */
|
|
163
171
|
encrypt?: EncryptOptions;
|
package/dist/api/structure.js
CHANGED
|
@@ -180,6 +180,7 @@ export async function renderPdf(doc, options) {
|
|
|
180
180
|
manager = om;
|
|
181
181
|
om.setCompress(options?.compress !== false); // FlateDecode streams by default
|
|
182
182
|
om.setOverflowPolicy(options?.onOverflow ?? "error");
|
|
183
|
+
om.diagnostics.setHandler(options?.onDiagnostic);
|
|
183
184
|
om.setKerning(options?.kerning !== false); // on by default (experimental); `kerning:false` opts out
|
|
184
185
|
om.acroform.setBakeAppearances(options?.fieldAppearances !== false); // baked unless opted out
|
|
185
186
|
for (const [name, value] of Object.entries(fonts)) {
|
|
@@ -196,6 +197,14 @@ export async function renderPdf(doc, options) {
|
|
|
196
197
|
om.registerCustomFont(name, value.boldItalic, FontStyle.BoldItalic);
|
|
197
198
|
}
|
|
198
199
|
}
|
|
200
|
+
// Version, id and encryption first - they create no objects, and an attachment's stream and name
|
|
201
|
+
// are written below and must already be enciphered. The handler after the version: R6 needs PDF 2.0.
|
|
202
|
+
if (options?.pdfVersion)
|
|
203
|
+
om.setPdfVersion(options.pdfVersion);
|
|
204
|
+
if (options?.documentId)
|
|
205
|
+
om.enableDocumentId();
|
|
206
|
+
if (securityHandler)
|
|
207
|
+
om.setSecurityHandler(securityHandler);
|
|
199
208
|
for (const a of attachments) {
|
|
200
209
|
om.attachFile(a.name, a.data, {
|
|
201
210
|
relationship: a.relationship,
|
|
@@ -215,12 +224,6 @@ export async function renderPdf(doc, options) {
|
|
|
215
224
|
om.setXmpMetadata(options.xmp);
|
|
216
225
|
if (options?.outputIntent)
|
|
217
226
|
om.setOutputIntent(options.outputIntent);
|
|
218
|
-
if (options?.pdfVersion)
|
|
219
|
-
om.setPdfVersion(options.pdfVersion);
|
|
220
|
-
if (options?.documentId)
|
|
221
|
-
om.enableDocumentId();
|
|
222
|
-
if (securityHandler)
|
|
223
|
-
om.setSecurityHandler(securityHandler);
|
|
224
227
|
if (options?.accessible) {
|
|
225
228
|
om.struct.enabled = true;
|
|
226
229
|
if (options.lang)
|
package/dist/api/table.d.ts
CHANGED
|
@@ -2,14 +2,16 @@ import { PDFElement } from "../elements/pdf-element.js";
|
|
|
2
2
|
import { Insets } from "./insets.js";
|
|
3
3
|
import { ColorInput } from "./color.js";
|
|
4
4
|
/**
|
|
5
|
-
* A column width: a `number` of points (fixed), an `"Nfr"`
|
|
6
|
-
* width),
|
|
5
|
+
* A column width, as in CSS grid with points for units: a `number` of points (fixed), an `"Nfr"`
|
|
6
|
+
* fraction (splits the leftover width, never narrower than its longest word), `"auto"` (as wide as the
|
|
7
|
+
* widest cell in that column, giving way down to its longest word when the table would not fit), or
|
|
8
|
+
* `"minmax(40, auto)"` (like `auto`, but never narrower than 40pt). Measured at layout time.
|
|
7
9
|
*/
|
|
8
10
|
export type ColumnWidth = number | string;
|
|
9
11
|
/** A table cell: any element, or a string (wrapped in `Text`). */
|
|
10
12
|
export type Cell = PDFElement | string;
|
|
11
13
|
export interface TableOptions {
|
|
12
|
-
/** One entry per column: a fixed point width, an `"Nfr"` fraction, or `"auto"`. */
|
|
14
|
+
/** One entry per column: a fixed point width, an `"Nfr"` fraction, `"auto"` or `"minmax(<pt>, auto)"`. */
|
|
13
15
|
columns: ColumnWidth[];
|
|
14
16
|
/** A header row that REPEATS at the top of every page the table flows onto. */
|
|
15
17
|
header?: Cell[];
|
package/dist/api/table.js
CHANGED
|
@@ -14,27 +14,100 @@ function fractionOf(col) {
|
|
|
14
14
|
const n = parseFloat(col);
|
|
15
15
|
return Number.isFinite(n) ? n : 1;
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const MINMAX_AUTO = /^minmax\(\s*(\d+(?:\.\d+)?)\s*,\s*auto\s*\)$/;
|
|
18
|
+
/**
|
|
19
|
+
* The floor of a content-sized column: 0 for `"auto"`, N for `"minmax(N, auto)"`, null for any other
|
|
20
|
+
* width. Other `minmax()` forms are not supported yet and say so, rather than being read as something else.
|
|
21
|
+
*/
|
|
22
|
+
function autoMinimum(col) {
|
|
23
|
+
if (col === "auto")
|
|
24
|
+
return 0;
|
|
25
|
+
if (typeof col !== "string" || !col.startsWith("minmax("))
|
|
26
|
+
return null;
|
|
27
|
+
const min = MINMAX_AUTO.exec(col);
|
|
28
|
+
if (!min) {
|
|
29
|
+
throw new Error(`Unsupported column width "${col}" - minmax() takes a minimum in points and "auto", e.g. "minmax(40, auto)"`);
|
|
30
|
+
}
|
|
31
|
+
return parseFloat(min[1]);
|
|
21
32
|
}
|
|
22
|
-
/**
|
|
23
|
-
|
|
33
|
+
/**
|
|
34
|
+
* What the cells of column `i` need: `max` is the widest cell on one line (max-content), `min` the
|
|
35
|
+
* widest piece that cannot break (min-content, its longest word).
|
|
36
|
+
*/
|
|
37
|
+
function cellWidths(opts, rows, i, ctx) {
|
|
24
38
|
// The composed cell sits in a 1pt-border Box (border-box) that eats 1pt each side, which the
|
|
25
39
|
// measured content doesn't include; add it back, and `ceil` so a single-word cell can't land
|
|
26
40
|
// a hair over the width and wrap to an empty first line.
|
|
27
41
|
const slack = opts.cellBorder !== undefined || opts.rule !== undefined ? 2 : 0;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
let min = 0;
|
|
43
|
+
let max = 0;
|
|
44
|
+
for (const cell of [opts.header?.[i], ...rows.map((r) => r[i])]) {
|
|
45
|
+
if (cell === undefined)
|
|
46
|
+
continue;
|
|
47
|
+
const el = opts.cellPadding !== undefined ? Padding(opts.cellPadding, asElement(cell)) : asElement(cell);
|
|
48
|
+
max = Math.max(max, el.measure(BoxConstraints.loose(Infinity, Infinity), ctx).width);
|
|
49
|
+
min = Math.max(min, el.minIntrinsicMain(true, ctx));
|
|
50
|
+
}
|
|
51
|
+
return { min: Math.ceil(min + slack), max: Math.ceil(max + slack) };
|
|
52
|
+
}
|
|
53
|
+
/** Grows `lo` toward `hi` by `extra`, in equal steps - each column stops at its own `hi`. */
|
|
54
|
+
function growEvenly(lo, hi, extra) {
|
|
55
|
+
const out = [...lo];
|
|
56
|
+
let open = out.map((_, k) => k).filter((k) => hi[k] > out[k]);
|
|
57
|
+
while (extra > 1e-9 && open.length > 0) {
|
|
58
|
+
const each = extra / open.length;
|
|
59
|
+
for (const k of open) {
|
|
60
|
+
const add = Math.min(each, hi[k] - out[k]);
|
|
61
|
+
out[k] += add;
|
|
62
|
+
extra -= add;
|
|
35
63
|
}
|
|
36
|
-
|
|
37
|
-
}
|
|
64
|
+
open = open.filter((k) => hi[k] - out[k] > 1e-9);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The column widths for a table `width` wide, as CSS grid sizes its tracks. An `"auto"` column takes
|
|
70
|
+
* its widest cell while that leaves every `fr` column its longest word; when it does not, the `auto`
|
|
71
|
+
* columns give way evenly, down to their own longest word. The `fr` columns then share what is left -
|
|
72
|
+
* one whose share would be narrower than its longest word gets exactly that, and the table overflows
|
|
73
|
+
* instead of drawing words over each other. With no width to fit (a table in a Row), nothing gives way.
|
|
74
|
+
*/
|
|
75
|
+
function resolveColumns(opts, rows, ctx, width) {
|
|
76
|
+
const columns = opts.columns;
|
|
77
|
+
const colGap = opts.colGap ?? opts.gap ?? 0;
|
|
78
|
+
const room = width - Math.max(0, columns.length - 1) * colGap;
|
|
79
|
+
const fixed = columns.reduce((n, c) => n + (typeof c === "number" ? c : 0), 0);
|
|
80
|
+
const autos = columns.flatMap((c, i) => (autoMinimum(c) === null ? [] : [i]));
|
|
81
|
+
const fractions = Number.isFinite(room)
|
|
82
|
+
? columns.flatMap((c, i) => (fractionOf(c) === null ? [] : [i]))
|
|
83
|
+
: [];
|
|
84
|
+
const need = new Map();
|
|
85
|
+
for (const i of [...autos, ...fractions])
|
|
86
|
+
need.set(i, cellWidths(opts, rows, i, ctx));
|
|
87
|
+
const resolved = [...columns];
|
|
88
|
+
// 1. The auto columns: their widest cell, or less while an fr column would lose its longest word.
|
|
89
|
+
const lo = autos.map((i) => Math.max(autoMinimum(columns[i]), need.get(i).min));
|
|
90
|
+
const hi = autos.map((i) => Math.max(autoMinimum(columns[i]), need.get(i).max));
|
|
91
|
+
const frMin = fractions.reduce((n, i) => n + need.get(i).min, 0);
|
|
92
|
+
const free = room - fixed - frMin;
|
|
93
|
+
const sum = (xs) => xs.reduce((n, x) => n + x, 0);
|
|
94
|
+
const autoWidths = free >= sum(hi) ? hi : free <= sum(lo) ? lo : growEvenly(lo, hi, free - sum(lo));
|
|
95
|
+
autos.forEach((i, k) => (resolved[i] = autoWidths[k]));
|
|
96
|
+
// 2. The fr columns share the rest; one that would get less than its longest word is fixed at it.
|
|
97
|
+
let left = room - fixed - sum(autoWidths);
|
|
98
|
+
let flexible = fractions;
|
|
99
|
+
for (;;) {
|
|
100
|
+
const unit = left / flexible.reduce((n, i) => n + fractionOf(columns[i]), 0);
|
|
101
|
+
const short = flexible.filter((i) => need.get(i).min > fractionOf(columns[i]) * unit);
|
|
102
|
+
if (short.length === 0)
|
|
103
|
+
break;
|
|
104
|
+
for (const i of short) {
|
|
105
|
+
resolved[i] = need.get(i).min;
|
|
106
|
+
left -= need.get(i).min;
|
|
107
|
+
}
|
|
108
|
+
flexible = flexible.filter((i) => !short.includes(i));
|
|
109
|
+
}
|
|
110
|
+
return resolved;
|
|
38
111
|
}
|
|
39
112
|
/** Builds the table tree for already-resolved column widths (no `"auto"`). */
|
|
40
113
|
function composeTable(opts, rows, columns) {
|
|
@@ -64,12 +137,10 @@ function composeTable(opts, rows, columns) {
|
|
|
64
137
|
...(ruled && rule !== undefined ? { borderBottom: rule } : {}),
|
|
65
138
|
};
|
|
66
139
|
const boxed = cb !== undefined || (ruled && rule !== undefined);
|
|
140
|
+
// The columns are the grid: a row never shrinks one on its own, or its cells leave their columns.
|
|
67
141
|
if (typeof col === "number")
|
|
68
|
-
return Box({ width: col, ...border }, [inner]);
|
|
69
|
-
|
|
70
|
-
if (fr !== null)
|
|
71
|
-
return Expanded({ flex: fr }, boxed ? Box({ ...border }, [inner]) : inner);
|
|
72
|
-
throw new Error(`Unsupported column width "${col}" - use a number of points or "<n>fr"`);
|
|
142
|
+
return Box({ width: col, flexShrink: 0, ...border }, [inner]);
|
|
143
|
+
return Expanded({ flex: fractionOf(col) }, boxed ? Box({ ...border }, [inner]) : inner);
|
|
73
144
|
};
|
|
74
145
|
// align:stretch → equal-height cells, so a wrapping cell keeps the row's bottom rule straight.
|
|
75
146
|
const buildRow = (cells, firstRow, ruled = false, isHeader = false) => new StructGroup({
|
|
@@ -92,8 +163,14 @@ function composeTable(opts, rows, columns) {
|
|
|
92
163
|
* columns are resolved at layout time (a `DeferredElement`), once metrics are available.
|
|
93
164
|
*/
|
|
94
165
|
export function Table(opts, rows) {
|
|
95
|
-
|
|
96
|
-
|
|
166
|
+
// Checked here, where the table is written, not at layout: a typo names its line.
|
|
167
|
+
for (const col of opts.columns) {
|
|
168
|
+
if (typeof col !== "number" && fractionOf(col) === null && autoMinimum(col) === null) {
|
|
169
|
+
throw new Error(`Unsupported column width "${col}" - use a number of points, "<n>fr", "auto" or "minmax(<pt>, auto)"`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (opts.columns.some((c) => typeof c !== "number")) {
|
|
173
|
+
return new DeferredElement((ctx, width) => composeTable(opts, rows, resolveColumns(opts, rows, ctx, width)));
|
|
97
174
|
}
|
|
98
175
|
return composeTable(opts, rows, opts.columns);
|
|
99
176
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the engine tells the author about a document it rendered anyway - one channel for every such
|
|
3
|
+
* signal, so an application (or an agent, which cannot see the page) can act on it by `code`.
|
|
4
|
+
*
|
|
5
|
+
* Without a handler nothing changes: what warned before still goes to `console.warn`, what was silent
|
|
6
|
+
* stays silent. A handler receives everything, once per element and code - layout runs several times
|
|
7
|
+
* per render, and the same mistake must not be reported for each pass.
|
|
8
|
+
*/
|
|
9
|
+
export type DiagnosticCode =
|
|
10
|
+
/** A `PageBreak` outside a paginating flow (in a Row, in a PageBuilder) - drawn as nothing. */
|
|
11
|
+
"page-break-ignored"
|
|
12
|
+
/** `breakBefore` / `breakAfter` on a child of a Row or a table row, which never breaks. */
|
|
13
|
+
| "break-ignored"
|
|
14
|
+
/** A `keepTogether` group contains a forced break; the break wins and the group is split. */
|
|
15
|
+
| "keep-together-split"
|
|
16
|
+
/** Content taller than its page region that cannot break, placed whole and clipped. */
|
|
17
|
+
| "overflow-clipped"
|
|
18
|
+
/** Characters no font could draw, removed (PDF/A forbids `.notdef`). */
|
|
19
|
+
| "missing-glyphs";
|
|
20
|
+
export interface Diagnostic {
|
|
21
|
+
code: DiagnosticCode;
|
|
22
|
+
severity: "warning" | "info";
|
|
23
|
+
message: string;
|
|
24
|
+
/** The kind of element it is about, by the factory name an author writes: `"Row"`, `"Box"`, `"Text"`. */
|
|
25
|
+
element?: string;
|
|
26
|
+
/** The first text inside that element, shortened - so the place can be found in the document. */
|
|
27
|
+
text?: string;
|
|
28
|
+
/** The 1-based page, when the element is being laid out on a known page. */
|
|
29
|
+
page?: number;
|
|
30
|
+
}
|
|
31
|
+
export type DiagnosticHandler = (diagnostic: Diagnostic) => void;
|
|
32
|
+
/** Whether a diagnostic goes to `console.warn` when no handler is set - only what warned before. */
|
|
33
|
+
type Fallback = "warn" | "silent";
|
|
34
|
+
export declare class Diagnostics {
|
|
35
|
+
private handler?;
|
|
36
|
+
private readonly seen;
|
|
37
|
+
setHandler(handler: DiagnosticHandler | undefined): void;
|
|
38
|
+
/** Report once per `about` and code. `about` is the element the message is about, or any key. */
|
|
39
|
+
report(d: Omit<Diagnostic, "element" | "text"> & {
|
|
40
|
+
about?: object;
|
|
41
|
+
fallback?: Fallback;
|
|
42
|
+
}): void;
|
|
43
|
+
}
|
|
44
|
+
/** The 1-based page a layout context is on, as far as it knows: drawn (`pageInfo`) or being paginated. */
|
|
45
|
+
export declare function pageOf(ctx: {
|
|
46
|
+
pageInfo?: {
|
|
47
|
+
pageNumber: number;
|
|
48
|
+
};
|
|
49
|
+
paginatingPage?: number;
|
|
50
|
+
}): number | undefined;
|
|
51
|
+
/** Report through the render's channel, or - laid out on its own - warn as before. */
|
|
52
|
+
export declare function diagnose(diagnostics: Diagnostics | undefined, d: Parameters<Diagnostics["report"]>[0]): void;
|
|
53
|
+
export {};
|