@mccaskie-labs/workbench-pdf 0.1.0 → 0.1.2
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/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/logo.d.ts +2 -0
- package/dist/logo.js +11 -2
- package/dist/maker.d.ts +6 -0
- package/dist/maker.js +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @mccaskie-labs/workbench-pdf
|
|
2
2
|
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- `createPdfMaker({ logo })` takes the logo's bytes when the app already holds them, instead of fetching `brand.logo.url`. A logo URL that answers with a web page (such as an Access sign-in) now says so.
|
|
8
|
+
|
|
9
|
+
## 0.1.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- No code change. The first version published by the publish workflow, which confirms the package's npm trusted publisher.
|
|
14
|
+
|
|
3
15
|
## 0.1.0
|
|
4
16
|
|
|
5
17
|
- First version: `createPdfMaker` with the standard branded layout (`document`) and the app's own HTML (`html`), `renderDocument` for previews, and `WorkbenchPdfError` (`docs/spec.md` §10, 11 November 2026).
|
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ export default {
|
|
|
40
40
|
|
|
41
41
|
| Export | What it does |
|
|
42
42
|
| --- | --- |
|
|
43
|
-
| `createPdfMaker({ browser, brand, size?, timeoutMs? })` | The maker. Checks the brand at once (`invalid_brand`). `size` is `A4` (default) or `Letter`; `timeoutMs` is 1,000 to 120,000, default 30,000 |
|
|
43
|
+
| `createPdfMaker({ browser, brand, logo?, size?, timeoutMs? })` | The maker. Checks the brand at once (`invalid_brand`). `logo` is the logo's bytes when the app already holds them (a customer's upload in its own R2, for example): used instead of fetching `brand.logo.url`, so a private logo needn't be public. `size` is `A4` (default) or `Letter`; `timeoutMs` is 1,000 to 120,000, default 30,000 |
|
|
44
44
|
| `pdf.document(doc, { size?, landscape? })` | The standard layout: logo and name across the top, the title, subtitle and date, then the blocks; the footer on every page has the name, site, `footer` line and "Page n of m". Returns the PDF bytes |
|
|
45
45
|
| `pdf.html(body, { title, standardStyles?, header?, javascript?, allowOrigins?, size?, landscape? })` | The app's own HTML (a body fragment, trusted as given) with the brand's fonts, colours, header and footer. The brand is in CSS variables: `--brand-primary`, `--brand-text`, `--brand-background`, `--brand-muted`, `--brand-heading-font`, `--brand-body-font` |
|
|
46
46
|
| `pdf.renderDocument(doc)` | The standard layout's HTML without printing, for previews and tests |
|
package/dist/logo.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export declare const MAX_LOGO_BYTES: number;
|
|
2
2
|
/** The logo as a `data:` URI. PNG, JPEG or SVG only, at most 1 MiB, within `timeoutMs`. */
|
|
3
3
|
export declare function logoDataUri(url: string, fetcher: typeof fetch, timeoutMs?: number): Promise<string>;
|
|
4
|
+
/** Logo bytes the app already holds (from its own storage) as a `data:` URI. Same checks as a fetched logo. */
|
|
5
|
+
export declare function logoBytesDataUri(bytes: Uint8Array): string;
|
package/dist/logo.js
CHANGED
|
@@ -33,11 +33,20 @@ export async function logoDataUri(url, fetcher, timeoutMs = 10_000) {
|
|
|
33
33
|
const length = Number(res.headers.get("content-length") ?? "0");
|
|
34
34
|
if (length > MAX_LOGO_BYTES)
|
|
35
35
|
throw fail("larger than 1 MiB");
|
|
36
|
-
|
|
36
|
+
return logoBytesDataUri(new Uint8Array(await res.arrayBuffer()));
|
|
37
|
+
}
|
|
38
|
+
/** Logo bytes the app already holds (from its own storage) as a `data:` URI. Same checks as a fetched logo. */
|
|
39
|
+
export function logoBytesDataUri(bytes) {
|
|
40
|
+
if (!(bytes instanceof Uint8Array))
|
|
41
|
+
throw fail("bytes must be a Uint8Array");
|
|
37
42
|
if (bytes.length > MAX_LOGO_BYTES)
|
|
38
43
|
throw fail("larger than 1 MiB");
|
|
39
44
|
const kind = sniff(bytes);
|
|
40
|
-
if (!kind)
|
|
45
|
+
if (!kind) {
|
|
46
|
+
const head = new TextDecoder().decode(bytes.subarray(0, 256)).trimStart().toLowerCase();
|
|
47
|
+
if (head.startsWith("<!doctype html") || head.startsWith("<html"))
|
|
48
|
+
throw fail("got a web page, not an image (is the logo behind a sign-in such as Cloudflare Access?)");
|
|
41
49
|
throw fail("not PNG, JPEG or SVG");
|
|
50
|
+
}
|
|
42
51
|
return `data:${kind};base64,${base64(bytes)}`;
|
|
43
52
|
}
|
package/dist/maker.d.ts
CHANGED
|
@@ -37,6 +37,12 @@ export interface PdfMakerOptions {
|
|
|
37
37
|
size?: PageSize;
|
|
38
38
|
/** Time allowed for one PDF, in ms. Default 30,000. */
|
|
39
39
|
timeoutMs?: number;
|
|
40
|
+
/**
|
|
41
|
+
* The logo's bytes, when the app already holds them (a customer's upload in
|
|
42
|
+
* its own R2, say). Used instead of fetching `brand.logo.url`, so a private
|
|
43
|
+
* logo needn't be public. PNG, JPEG or SVG, at most 1 MiB.
|
|
44
|
+
*/
|
|
45
|
+
logo?: Uint8Array;
|
|
40
46
|
/** For tests. */
|
|
41
47
|
launch?: Launch;
|
|
42
48
|
/** For tests: fetches the logo. Defaults to the global fetch. */
|
package/dist/maker.js
CHANGED
|
@@ -6,7 +6,7 @@ import { printBrand } from "./brand.js";
|
|
|
6
6
|
import { checkDocument } from "./document.js";
|
|
7
7
|
import { WorkbenchPdfError } from "./errors.js";
|
|
8
8
|
import { footerTemplate, ownPage, standardPage } from "./layout.js";
|
|
9
|
-
import { logoDataUri } from "./logo.js";
|
|
9
|
+
import { logoBytesDataUri, logoDataUri } from "./logo.js";
|
|
10
10
|
const GOOGLE_FONT_FILES = "https://fonts.gstatic.com";
|
|
11
11
|
function fontOrigins(brand) {
|
|
12
12
|
const out = new Set();
|
|
@@ -71,7 +71,8 @@ export function createPdfMaker(options) {
|
|
|
71
71
|
const launch = options.launch ?? defaultLaunch;
|
|
72
72
|
const fetcher = options.fetch ?? ((input, init) => fetch(input, init));
|
|
73
73
|
const fonts = fontOrigins(brand);
|
|
74
|
-
|
|
74
|
+
// Bytes the app passed are checked now, so a bad file fails at once.
|
|
75
|
+
let logo = options.logo === undefined ? undefined : Promise.resolve(logoBytesDataUri(options.logo));
|
|
75
76
|
// Fetched once per maker; a failure is not kept, so the next PDF tries again.
|
|
76
77
|
const logoSrc = () => {
|
|
77
78
|
logo ??= logoDataUri(brand.logo.url, fetcher).catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mccaskie-labs/workbench-pdf",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Branded PDFs for Workbench apps: the app's content printed with its logo, colours and fonts, in its own Worker through the Browser Rendering binding.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|