@otisk/preview 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # `engine-wasm` — WASM bindings for otisk
2
+
3
+ Thin `wasm-bindgen` wrapper around `otisk::Engine`. Exposes a single
4
+ JS-callable entry point so browser (and other `wasm32-unknown-unknown`)
5
+ hosts can drive the otisk pipeline:
6
+
7
+ ```rust
8
+ // Issue 30 baseline + Issue 115 widening: an **optional** 4th argument
9
+ // crosses a JS-side `AssetResolver` into the engine. The 3-arg call
10
+ // `render_pdf(html, css, margin)` remains byte-identical to the
11
+ // Issue 31 parity baseline; passing `null` / `undefined` for the 4th
12
+ // arg is equivalent. When present, `assets` must be either a bare
13
+ // `function(url): Uint8Array` callable or an object with a synchronous
14
+ // `fetch(url): Uint8Array` method — every engine-side `@font-face`,
15
+ // `<img>`, `background-image`, `@color-profile` lookup is funneled
16
+ // through it.
17
+ #[wasm_bindgen]
18
+ pub fn render_pdf(
19
+ html: &str,
20
+ css: &str,
21
+ margin_mm: f32,
22
+ assets: Option<JsValue>, // bindgen-side: omittable in JS
23
+ ) -> Result<Vec<u8>, JsValue>;
24
+ ```
25
+
26
+ JS call sites:
27
+
28
+ ```js
29
+ // 3-arg legacy form — unchanged, empty resolver.
30
+ const pdf = render_pdf(html, css, 10);
31
+
32
+ // 4-arg widened form — supply a resolver. `null` / `undefined` are
33
+ // equivalent to omitting the arg.
34
+ const pdf = render_pdf(html, css, 10, {
35
+ fetch(url) { /* return Uint8Array */ }
36
+ });
37
+ ```
38
+
39
+ See `src/lib.rs` and the issue specs at
40
+ `.plans/pdfx4-engine/issues/30-wasm-bindings-crate.md` (baseline) and
41
+ `.plans/pdfx4-engine/issues/115-wasm-asset-resolver-crossing.md`
42
+ (resolver crossing) for the full rationale.
43
+
44
+ ## Build (browser bundle)
45
+
46
+ ```sh
47
+ wasm-pack build crates/engine-wasm --target web --out-dir ../../examples/web-ui/pkg --release
48
+ ```
49
+
50
+ This produces `examples/web-ui/pkg/engine_wasm_bg.wasm` plus the
51
+ `engine_wasm.js` glue module. Serve the demo UI with any static server:
52
+
53
+ ```sh
54
+ python3 -m http.server 4173 --directory examples/web-ui
55
+ ```
56
+
57
+ The `pkg/` output directory is git-ignored.
58
+
59
+ ## Build (raw `.wasm`, no JS glue)
60
+
61
+ For CI gate parity with the issue spec — no `wasm-pack` required:
62
+
63
+ ```sh
64
+ cargo build -p engine-wasm --target wasm32-unknown-unknown --locked --release
65
+ ```
66
+
67
+ Artefact at `target/wasm32-unknown-unknown/release/engine_wasm.wasm`.
68
+
69
+ ## Size budget
70
+
71
+ `SPECIFICATION.md` §4.2: **5 MB compressed** for the full engine. Phase 2
72
+ ships the raw bundle without `wasm-opt`; binary-size optimization lands in
73
+ Issue 31 / Phase 6. The commit that introduces this crate records the
74
+ current unoptimized `.wasm` size as a baseline.
75
+
76
+ ## Host-target build
77
+
78
+ The crate is also exposed as an `rlib` so `cargo build -p engine-wasm`
79
+ on the host triple works (useful for `cargo doc`, future host-side
80
+ integration tests, and IDE indexing). The `cdylib` artefact only matters
81
+ for the `wasm32-unknown-unknown` target.
82
+
83
+ ## `parity_export` feature (Issue 31)
84
+
85
+ The crate ships a second WASM entry point — a raw C-ABI
86
+ `render_pdf_raw` — gated behind the `parity_export` feature. The Phase 2
87
+ parity harness (`crates/otisk/tests/wasm_parity.rs`) builds the artefact
88
+ with `--no-default-features --features parity_export` and drives it
89
+ through `wasmtime` without the `wasm-bindgen` JS glue. This surface is
90
+ **internal** — the wire format is not a stability contract, and browser
91
+ hosts should always use the default `render_pdf`.
92
+
93
+ ```sh
94
+ cargo build -p engine-wasm --target wasm32-unknown-unknown \
95
+ --no-default-features --features parity_export --release
96
+ ```
@@ -0,0 +1,194 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /**
5
+ * Issue 297 — a rasterised page handed back to JS.
6
+ *
7
+ * `rgba` is straight (non-premultiplied) RGBA8, row-major, top-down —
8
+ * exactly the layout an HTML5 `ImageData` / `createImageBitmap`
9
+ * expects, so the browser can blit it to a `<canvas>` with no
10
+ * per-pixel rework. `width` / `height` are device pixels.
11
+ */
12
+ export class RasterResult {
13
+ private constructor();
14
+ free(): void;
15
+ [Symbol.dispose](): void;
16
+ /**
17
+ * Device height in pixels.
18
+ */
19
+ readonly height: number;
20
+ /**
21
+ * Straight RGBA8 samples (`4 * width * height` bytes). Consumes the
22
+ * buffer into a JS `Uint8Array` (wasm-bindgen copies it across the
23
+ * boundary).
24
+ */
25
+ readonly rgba: Uint8Array;
26
+ /**
27
+ * Device width in pixels.
28
+ */
29
+ readonly width: number;
30
+ }
31
+
32
+ /**
33
+ * Issue 297 — number of pages the `(html, css)` pair lays out. Useful
34
+ * for a preview's page navigation. Same argument shape as
35
+ * [`render_page_rgba`] minus `page_index` / `scale`.
36
+ *
37
+ * # Errors
38
+ *
39
+ * Same channel as [`render_pdf`].
40
+ */
41
+ export function page_count(html: string, css: string, margin_mm: number, assets?: any | null, output_intent?: any | null): number;
42
+
43
+ /**
44
+ * Issue 297 — render one page of an `(html, css)` pair to an RGBA8
45
+ * bitmap for an HTML5-canvas print preview.
46
+ *
47
+ * Mirrors [`render_pdf`]'s argument shape (`html`, `css`, `margin_mm`,
48
+ * optional `assets`, optional `output_intent`) and adds `page_index`
49
+ * (0-based) + `scale` (pixels-per-point, `scale = dpi / 72`). The
50
+ * returned [`RasterResult`] carries the device dimensions + the RGBA
51
+ * buffer.
52
+ *
53
+ * Because the preview reuses the **same** layout the PDF path produces,
54
+ * it is guaranteed to match print geometry; only the rasteriser differs
55
+ * (tiny-skia here, the press RIP there).
56
+ *
57
+ * # Errors
58
+ *
59
+ * Same channel as [`render_pdf`] (a stringified [`otisk::RenderError`]
60
+ * via `JsValue::from_str`), plus an out-of-range `page_index` or
61
+ * degenerate device dimensions.
62
+ */
63
+ export function render_page_rgba(html: string, css: string, margin_mm: number, assets: any | null | undefined, output_intent: any | null | undefined, page_index: number, scale: number): RasterResult;
64
+
65
+ /**
66
+ * Render an `(html, css)` pair to a PDF/X-4 byte buffer.
67
+ *
68
+ * Thin shim over [`otisk::Engine`]: builds an engine with `margin_mm`
69
+ * applied uniformly to all four sides (see
70
+ * [`otisk::EngineBuilder::with_margins_mm`]), then forwards to
71
+ * [`otisk::Engine::render`] and returns [`otisk::RenderResult::bytes`].
72
+ *
73
+ * ## Optional asset resolver (Issue 115)
74
+ *
75
+ * The `assets` parameter is **optional**. wasm-bindgen surfaces it as
76
+ * a fourth positional argument that the JS caller may omit (older
77
+ * 3-arg call sites — `render_pdf(html, css, margin)` — keep working
78
+ * unchanged) or pass as `null` / `undefined` for the same behaviour.
79
+ * When `assets` is present it must be either:
80
+ *
81
+ * - a bare callable: `function(url: string): Uint8Array`, or
82
+ * - an object with the spec'd shape: `{ fetch(url: string): Uint8Array }`.
83
+ *
84
+ * Every engine-side asset lookup (`@font-face`, `<img>`,
85
+ * `background-image`, and — Phase 4 — `@color-profile`) is funneled
86
+ * through that callable. If `assets.fetch` returns a non-`Uint8Array`
87
+ * value, throws, or the method doesn't exist, the render fails with
88
+ * [`otisk::FetchError`] surfaced through the usual
89
+ * `JsValue`-stringified error channel.
90
+ *
91
+ * ### Why synchronous
92
+ *
93
+ * The native [`otisk::AssetResolver::fetch`] signature is synchronous
94
+ * (`SPECIFICATION.md` §3.7); the engine never spins up an async
95
+ * runtime to fetch an asset. Mirroring that on the JS side keeps the
96
+ * byte-identity contract trivial — every fetch returns bytes
97
+ * immediately, with the same byte-for-byte semantics on both sides.
98
+ * A `Promise<Uint8Array>` arm would either (a) require an async
99
+ * engine entry point — out of scope for this issue — or (b) demand
100
+ * host-side blocking that the browser doesn't permit on the main
101
+ * thread. The simpler synchronous arm is the load-bearing acceptance
102
+ * criterion (byte-identical parity for `font_face_load`,
103
+ * `image_embed`, `background_image`) and an async overload can ride
104
+ * on top of it later.
105
+ *
106
+ * ## Optional output intent (Issue 116)
107
+ *
108
+ * The `output_intent` parameter is **optional**. wasm-bindgen surfaces it
109
+ * as a fifth positional argument that the JS caller may omit (older
110
+ * 3-arg / 4-arg call sites keep working unchanged) or pass as `null` /
111
+ * `undefined` for the same default behaviour. When `output_intent` is
112
+ * present it must be a UTF-8 string naming a known
113
+ * [`otisk::ColorProfile`] variant — see [`parse_color_profile`] for the
114
+ * accepted spellings (`"srgb"`, `"fogra39"`, `"swop_v2"`,
115
+ * case-insensitive). An unknown string surfaces as the usual
116
+ * `JsValue`-stringified error so the JS caller can react.
117
+ *
118
+ * ## Diagnostics
119
+ *
120
+ * Diagnostics from the render are intentionally dropped on this entry
121
+ * point — the spec deliberately keeps the WASM surface minimal
122
+ * (Issue 30 §"Out of scope"). A richer entry point returning
123
+ * `{ bytes, diagnostics }` is deferred to a future issue once the
124
+ * parity harness has a concrete need for it.
125
+ *
126
+ * # Errors
127
+ *
128
+ * On any failure inside the pipeline ([`otisk::RenderError`] — HTML/CSS
129
+ * parse errors, layout failures, missing fonts, PDF emit errors, or
130
+ * resource-limit breaches), any failure inside the JS-side
131
+ * resolver, or an unrecognised `output_intent` string, the error is
132
+ * stringified via its `Display` impl and returned as a
133
+ * `JsValue::from_str`. JS sees a plain string error message; richer
134
+ * error objects can land alongside the diagnostic surface later.
135
+ *
136
+ * # Parameters
137
+ *
138
+ * - `html`: the input HTML source as a UTF-8 string.
139
+ * - `css`: the matching CSS source (currently the only style input — the
140
+ * engine has no `<style>` / `<link rel="stylesheet">` extraction yet).
141
+ * - `margin_mm`: uniform page margin applied to all four sides.
142
+ * - `assets` *(optional)*: a `{ fetch(url): Uint8Array }` object or a
143
+ * bare `(url) -> Uint8Array` callable; pass `null` / `undefined` /
144
+ * omit entirely for the empty-resolver baseline (byte-identical to
145
+ * the pre-Issue-115 3-arg call).
146
+ * - `output_intent` *(optional)*: a UTF-8 string naming a known
147
+ * [`otisk::ColorProfile`] variant (`"srgb"`, `"fogra39"`,
148
+ * `"swop_v2"`); pass `null` / `undefined` / omit entirely to keep
149
+ * the engine's default profile ([`ColorProfile::default`] — sRGB).
150
+ */
151
+ export function render_pdf(html: string, css: string, margin_mm: number, assets?: any | null, output_intent?: any | null): Uint8Array;
152
+
153
+ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
154
+
155
+ export interface InitOutput {
156
+ readonly memory: WebAssembly.Memory;
157
+ readonly __wbg_rasterresult_free: (a: number, b: number) => void;
158
+ readonly page_count: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number];
159
+ readonly rasterresult_height: (a: number) => number;
160
+ readonly rasterresult_rgba: (a: number) => [number, number];
161
+ readonly rasterresult_width: (a: number) => number;
162
+ readonly render_page_rgba: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => [number, number, number];
163
+ readonly render_pdf: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number, number];
164
+ readonly __wbindgen_malloc: (a: number, b: number) => number;
165
+ readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
166
+ readonly __wbindgen_exn_store: (a: number) => void;
167
+ readonly __externref_table_alloc: () => number;
168
+ readonly __wbindgen_externrefs: WebAssembly.Table;
169
+ readonly __externref_table_dealloc: (a: number) => void;
170
+ readonly __wbindgen_free: (a: number, b: number, c: number) => void;
171
+ readonly __wbindgen_start: () => void;
172
+ }
173
+
174
+ export type SyncInitInput = BufferSource | WebAssembly.Module;
175
+
176
+ /**
177
+ * Instantiates the given `module`, which can either be bytes or
178
+ * a precompiled `WebAssembly.Module`.
179
+ *
180
+ * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
181
+ *
182
+ * @returns {InitOutput}
183
+ */
184
+ export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
185
+
186
+ /**
187
+ * If `module_or_path` is {RequestInfo} or {URL}, makes a request and
188
+ * for everything else, calls `WebAssembly.instantiate` directly.
189
+ *
190
+ * @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
191
+ *
192
+ * @returns {Promise<InitOutput>}
193
+ */
194
+ export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
package/engine_wasm.js ADDED
@@ -0,0 +1,519 @@
1
+ /* @ts-self-types="./engine_wasm.d.ts" */
2
+
3
+ /**
4
+ * Issue 297 — a rasterised page handed back to JS.
5
+ *
6
+ * `rgba` is straight (non-premultiplied) RGBA8, row-major, top-down —
7
+ * exactly the layout an HTML5 `ImageData` / `createImageBitmap`
8
+ * expects, so the browser can blit it to a `<canvas>` with no
9
+ * per-pixel rework. `width` / `height` are device pixels.
10
+ */
11
+ export class RasterResult {
12
+ static __wrap(ptr) {
13
+ const obj = Object.create(RasterResult.prototype);
14
+ obj.__wbg_ptr = ptr;
15
+ RasterResultFinalization.register(obj, obj.__wbg_ptr, obj);
16
+ return obj;
17
+ }
18
+ __destroy_into_raw() {
19
+ const ptr = this.__wbg_ptr;
20
+ this.__wbg_ptr = 0;
21
+ RasterResultFinalization.unregister(this);
22
+ return ptr;
23
+ }
24
+ free() {
25
+ const ptr = this.__destroy_into_raw();
26
+ wasm.__wbg_rasterresult_free(ptr, 0);
27
+ }
28
+ /**
29
+ * Device height in pixels.
30
+ * @returns {number}
31
+ */
32
+ get height() {
33
+ const ret = wasm.rasterresult_height(this.__wbg_ptr);
34
+ return ret >>> 0;
35
+ }
36
+ /**
37
+ * Straight RGBA8 samples (`4 * width * height` bytes). Consumes the
38
+ * buffer into a JS `Uint8Array` (wasm-bindgen copies it across the
39
+ * boundary).
40
+ * @returns {Uint8Array}
41
+ */
42
+ get rgba() {
43
+ const ret = wasm.rasterresult_rgba(this.__wbg_ptr);
44
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
45
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
46
+ return v1;
47
+ }
48
+ /**
49
+ * Device width in pixels.
50
+ * @returns {number}
51
+ */
52
+ get width() {
53
+ const ret = wasm.rasterresult_width(this.__wbg_ptr);
54
+ return ret >>> 0;
55
+ }
56
+ }
57
+ if (Symbol.dispose) RasterResult.prototype[Symbol.dispose] = RasterResult.prototype.free;
58
+
59
+ /**
60
+ * Issue 297 — number of pages the `(html, css)` pair lays out. Useful
61
+ * for a preview's page navigation. Same argument shape as
62
+ * [`render_page_rgba`] minus `page_index` / `scale`.
63
+ *
64
+ * # Errors
65
+ *
66
+ * Same channel as [`render_pdf`].
67
+ * @param {string} html
68
+ * @param {string} css
69
+ * @param {number} margin_mm
70
+ * @param {any | null} [assets]
71
+ * @param {any | null} [output_intent]
72
+ * @returns {number}
73
+ */
74
+ export function page_count(html, css, margin_mm, assets, output_intent) {
75
+ const ptr0 = passStringToWasm0(html, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
76
+ const len0 = WASM_VECTOR_LEN;
77
+ const ptr1 = passStringToWasm0(css, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
78
+ const len1 = WASM_VECTOR_LEN;
79
+ const ret = wasm.page_count(ptr0, len0, ptr1, len1, margin_mm, isLikeNone(assets) ? 0 : addToExternrefTable0(assets), isLikeNone(output_intent) ? 0 : addToExternrefTable0(output_intent));
80
+ if (ret[2]) {
81
+ throw takeFromExternrefTable0(ret[1]);
82
+ }
83
+ return ret[0] >>> 0;
84
+ }
85
+
86
+ /**
87
+ * Issue 297 — render one page of an `(html, css)` pair to an RGBA8
88
+ * bitmap for an HTML5-canvas print preview.
89
+ *
90
+ * Mirrors [`render_pdf`]'s argument shape (`html`, `css`, `margin_mm`,
91
+ * optional `assets`, optional `output_intent`) and adds `page_index`
92
+ * (0-based) + `scale` (pixels-per-point, `scale = dpi / 72`). The
93
+ * returned [`RasterResult`] carries the device dimensions + the RGBA
94
+ * buffer.
95
+ *
96
+ * Because the preview reuses the **same** layout the PDF path produces,
97
+ * it is guaranteed to match print geometry; only the rasteriser differs
98
+ * (tiny-skia here, the press RIP there).
99
+ *
100
+ * # Errors
101
+ *
102
+ * Same channel as [`render_pdf`] (a stringified [`otisk::RenderError`]
103
+ * via `JsValue::from_str`), plus an out-of-range `page_index` or
104
+ * degenerate device dimensions.
105
+ * @param {string} html
106
+ * @param {string} css
107
+ * @param {number} margin_mm
108
+ * @param {any | null | undefined} assets
109
+ * @param {any | null | undefined} output_intent
110
+ * @param {number} page_index
111
+ * @param {number} scale
112
+ * @returns {RasterResult}
113
+ */
114
+ export function render_page_rgba(html, css, margin_mm, assets, output_intent, page_index, scale) {
115
+ const ptr0 = passStringToWasm0(html, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
116
+ const len0 = WASM_VECTOR_LEN;
117
+ const ptr1 = passStringToWasm0(css, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
118
+ const len1 = WASM_VECTOR_LEN;
119
+ const ret = wasm.render_page_rgba(ptr0, len0, ptr1, len1, margin_mm, isLikeNone(assets) ? 0 : addToExternrefTable0(assets), isLikeNone(output_intent) ? 0 : addToExternrefTable0(output_intent), page_index, scale);
120
+ if (ret[2]) {
121
+ throw takeFromExternrefTable0(ret[1]);
122
+ }
123
+ return RasterResult.__wrap(ret[0]);
124
+ }
125
+
126
+ /**
127
+ * Render an `(html, css)` pair to a PDF/X-4 byte buffer.
128
+ *
129
+ * Thin shim over [`otisk::Engine`]: builds an engine with `margin_mm`
130
+ * applied uniformly to all four sides (see
131
+ * [`otisk::EngineBuilder::with_margins_mm`]), then forwards to
132
+ * [`otisk::Engine::render`] and returns [`otisk::RenderResult::bytes`].
133
+ *
134
+ * ## Optional asset resolver (Issue 115)
135
+ *
136
+ * The `assets` parameter is **optional**. wasm-bindgen surfaces it as
137
+ * a fourth positional argument that the JS caller may omit (older
138
+ * 3-arg call sites — `render_pdf(html, css, margin)` — keep working
139
+ * unchanged) or pass as `null` / `undefined` for the same behaviour.
140
+ * When `assets` is present it must be either:
141
+ *
142
+ * - a bare callable: `function(url: string): Uint8Array`, or
143
+ * - an object with the spec'd shape: `{ fetch(url: string): Uint8Array }`.
144
+ *
145
+ * Every engine-side asset lookup (`@font-face`, `<img>`,
146
+ * `background-image`, and — Phase 4 — `@color-profile`) is funneled
147
+ * through that callable. If `assets.fetch` returns a non-`Uint8Array`
148
+ * value, throws, or the method doesn't exist, the render fails with
149
+ * [`otisk::FetchError`] surfaced through the usual
150
+ * `JsValue`-stringified error channel.
151
+ *
152
+ * ### Why synchronous
153
+ *
154
+ * The native [`otisk::AssetResolver::fetch`] signature is synchronous
155
+ * (`SPECIFICATION.md` §3.7); the engine never spins up an async
156
+ * runtime to fetch an asset. Mirroring that on the JS side keeps the
157
+ * byte-identity contract trivial — every fetch returns bytes
158
+ * immediately, with the same byte-for-byte semantics on both sides.
159
+ * A `Promise<Uint8Array>` arm would either (a) require an async
160
+ * engine entry point — out of scope for this issue — or (b) demand
161
+ * host-side blocking that the browser doesn't permit on the main
162
+ * thread. The simpler synchronous arm is the load-bearing acceptance
163
+ * criterion (byte-identical parity for `font_face_load`,
164
+ * `image_embed`, `background_image`) and an async overload can ride
165
+ * on top of it later.
166
+ *
167
+ * ## Optional output intent (Issue 116)
168
+ *
169
+ * The `output_intent` parameter is **optional**. wasm-bindgen surfaces it
170
+ * as a fifth positional argument that the JS caller may omit (older
171
+ * 3-arg / 4-arg call sites keep working unchanged) or pass as `null` /
172
+ * `undefined` for the same default behaviour. When `output_intent` is
173
+ * present it must be a UTF-8 string naming a known
174
+ * [`otisk::ColorProfile`] variant — see [`parse_color_profile`] for the
175
+ * accepted spellings (`"srgb"`, `"fogra39"`, `"swop_v2"`,
176
+ * case-insensitive). An unknown string surfaces as the usual
177
+ * `JsValue`-stringified error so the JS caller can react.
178
+ *
179
+ * ## Diagnostics
180
+ *
181
+ * Diagnostics from the render are intentionally dropped on this entry
182
+ * point — the spec deliberately keeps the WASM surface minimal
183
+ * (Issue 30 §"Out of scope"). A richer entry point returning
184
+ * `{ bytes, diagnostics }` is deferred to a future issue once the
185
+ * parity harness has a concrete need for it.
186
+ *
187
+ * # Errors
188
+ *
189
+ * On any failure inside the pipeline ([`otisk::RenderError`] — HTML/CSS
190
+ * parse errors, layout failures, missing fonts, PDF emit errors, or
191
+ * resource-limit breaches), any failure inside the JS-side
192
+ * resolver, or an unrecognised `output_intent` string, the error is
193
+ * stringified via its `Display` impl and returned as a
194
+ * `JsValue::from_str`. JS sees a plain string error message; richer
195
+ * error objects can land alongside the diagnostic surface later.
196
+ *
197
+ * # Parameters
198
+ *
199
+ * - `html`: the input HTML source as a UTF-8 string.
200
+ * - `css`: the matching CSS source (currently the only style input — the
201
+ * engine has no `<style>` / `<link rel="stylesheet">` extraction yet).
202
+ * - `margin_mm`: uniform page margin applied to all four sides.
203
+ * - `assets` *(optional)*: a `{ fetch(url): Uint8Array }` object or a
204
+ * bare `(url) -> Uint8Array` callable; pass `null` / `undefined` /
205
+ * omit entirely for the empty-resolver baseline (byte-identical to
206
+ * the pre-Issue-115 3-arg call).
207
+ * - `output_intent` *(optional)*: a UTF-8 string naming a known
208
+ * [`otisk::ColorProfile`] variant (`"srgb"`, `"fogra39"`,
209
+ * `"swop_v2"`); pass `null` / `undefined` / omit entirely to keep
210
+ * the engine's default profile ([`ColorProfile::default`] — sRGB).
211
+ * @param {string} html
212
+ * @param {string} css
213
+ * @param {number} margin_mm
214
+ * @param {any | null} [assets]
215
+ * @param {any | null} [output_intent]
216
+ * @returns {Uint8Array}
217
+ */
218
+ export function render_pdf(html, css, margin_mm, assets, output_intent) {
219
+ const ptr0 = passStringToWasm0(html, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
220
+ const len0 = WASM_VECTOR_LEN;
221
+ const ptr1 = passStringToWasm0(css, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
222
+ const len1 = WASM_VECTOR_LEN;
223
+ const ret = wasm.render_pdf(ptr0, len0, ptr1, len1, margin_mm, isLikeNone(assets) ? 0 : addToExternrefTable0(assets), isLikeNone(output_intent) ? 0 : addToExternrefTable0(output_intent));
224
+ if (ret[3]) {
225
+ throw takeFromExternrefTable0(ret[2]);
226
+ }
227
+ var v3 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
228
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
229
+ return v3;
230
+ }
231
+ function __wbg_get_imports() {
232
+ const import0 = {
233
+ __proto__: null,
234
+ __wbg___wbindgen_is_function_5cd60d5cf78b4eef: function(arg0) {
235
+ const ret = typeof(arg0) === 'function';
236
+ return ret;
237
+ },
238
+ __wbg___wbindgen_is_null_2042690d351e14f0: function(arg0) {
239
+ const ret = arg0 === null;
240
+ return ret;
241
+ },
242
+ __wbg___wbindgen_is_undefined_35bb9f4c7fd651d5: function(arg0) {
243
+ const ret = arg0 === undefined;
244
+ return ret;
245
+ },
246
+ __wbg___wbindgen_string_get_d109740c0d18f4d7: function(arg0, arg1) {
247
+ const obj = arg1;
248
+ const ret = typeof(obj) === 'string' ? obj : undefined;
249
+ var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
250
+ var len1 = WASM_VECTOR_LEN;
251
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
252
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
253
+ },
254
+ __wbg___wbindgen_throw_9c31b086c2b26051: function(arg0, arg1) {
255
+ throw new Error(getStringFromWasm0(arg0, arg1));
256
+ },
257
+ __wbg_call_dfde26266607c996: function() { return handleError(function (arg0, arg1, arg2) {
258
+ const ret = arg0.call(arg1, arg2);
259
+ return ret;
260
+ }, arguments); },
261
+ __wbg_get_dcf82ab8aad1a593: function() { return handleError(function (arg0, arg1) {
262
+ const ret = Reflect.get(arg0, arg1);
263
+ return ret;
264
+ }, arguments); },
265
+ __wbg_instanceof_Uint8Array_abd07d4bd221d50b: function(arg0) {
266
+ let result;
267
+ try {
268
+ result = arg0 instanceof Uint8Array;
269
+ } catch (_) {
270
+ result = false;
271
+ }
272
+ const ret = result;
273
+ return ret;
274
+ },
275
+ __wbg_length_56fcd3e2b7e0299d: function(arg0) {
276
+ const ret = arg0.length;
277
+ return ret;
278
+ },
279
+ __wbg_prototypesetcall_5f9bdc8d75e07276: function(arg0, arg1, arg2) {
280
+ Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
281
+ },
282
+ __wbg_stringify_ef0c105b1ccc3849: function() { return handleError(function (arg0) {
283
+ const ret = JSON.stringify(arg0);
284
+ return ret;
285
+ }, arguments); },
286
+ __wbindgen_cast_0000000000000001: function(arg0, arg1) {
287
+ // Cast intrinsic for `Ref(String) -> Externref`.
288
+ const ret = getStringFromWasm0(arg0, arg1);
289
+ return ret;
290
+ },
291
+ __wbindgen_init_externref_table: function() {
292
+ const table = wasm.__wbindgen_externrefs;
293
+ const offset = table.grow(4);
294
+ table.set(0, undefined);
295
+ table.set(offset + 0, undefined);
296
+ table.set(offset + 1, null);
297
+ table.set(offset + 2, true);
298
+ table.set(offset + 3, false);
299
+ },
300
+ };
301
+ return {
302
+ __proto__: null,
303
+ "./engine_wasm_bg.js": import0,
304
+ };
305
+ }
306
+
307
+ const RasterResultFinalization = (typeof FinalizationRegistry === 'undefined')
308
+ ? { register: () => {}, unregister: () => {} }
309
+ : new FinalizationRegistry(ptr => wasm.__wbg_rasterresult_free(ptr, 1));
310
+
311
+ function addToExternrefTable0(obj) {
312
+ const idx = wasm.__externref_table_alloc();
313
+ wasm.__wbindgen_externrefs.set(idx, obj);
314
+ return idx;
315
+ }
316
+
317
+ function getArrayU8FromWasm0(ptr, len) {
318
+ ptr = ptr >>> 0;
319
+ return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
320
+ }
321
+
322
+ let cachedDataViewMemory0 = null;
323
+ function getDataViewMemory0() {
324
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
325
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
326
+ }
327
+ return cachedDataViewMemory0;
328
+ }
329
+
330
+ function getStringFromWasm0(ptr, len) {
331
+ return decodeText(ptr >>> 0, len);
332
+ }
333
+
334
+ let cachedUint8ArrayMemory0 = null;
335
+ function getUint8ArrayMemory0() {
336
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
337
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
338
+ }
339
+ return cachedUint8ArrayMemory0;
340
+ }
341
+
342
+ function handleError(f, args) {
343
+ try {
344
+ return f.apply(this, args);
345
+ } catch (e) {
346
+ const idx = addToExternrefTable0(e);
347
+ wasm.__wbindgen_exn_store(idx);
348
+ }
349
+ }
350
+
351
+ function isLikeNone(x) {
352
+ return x === undefined || x === null;
353
+ }
354
+
355
+ function passStringToWasm0(arg, malloc, realloc) {
356
+ if (realloc === undefined) {
357
+ const buf = cachedTextEncoder.encode(arg);
358
+ const ptr = malloc(buf.length, 1) >>> 0;
359
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
360
+ WASM_VECTOR_LEN = buf.length;
361
+ return ptr;
362
+ }
363
+
364
+ let len = arg.length;
365
+ let ptr = malloc(len, 1) >>> 0;
366
+
367
+ const mem = getUint8ArrayMemory0();
368
+
369
+ let offset = 0;
370
+
371
+ for (; offset < len; offset++) {
372
+ const code = arg.charCodeAt(offset);
373
+ if (code > 0x7F) break;
374
+ mem[ptr + offset] = code;
375
+ }
376
+ if (offset !== len) {
377
+ if (offset !== 0) {
378
+ arg = arg.slice(offset);
379
+ }
380
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
381
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
382
+ const ret = cachedTextEncoder.encodeInto(arg, view);
383
+
384
+ offset += ret.written;
385
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
386
+ }
387
+
388
+ WASM_VECTOR_LEN = offset;
389
+ return ptr;
390
+ }
391
+
392
+ function takeFromExternrefTable0(idx) {
393
+ const value = wasm.__wbindgen_externrefs.get(idx);
394
+ wasm.__externref_table_dealloc(idx);
395
+ return value;
396
+ }
397
+
398
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
399
+ cachedTextDecoder.decode();
400
+ const MAX_SAFARI_DECODE_BYTES = 2146435072;
401
+ let numBytesDecoded = 0;
402
+ function decodeText(ptr, len) {
403
+ numBytesDecoded += len;
404
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
405
+ cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
406
+ cachedTextDecoder.decode();
407
+ numBytesDecoded = len;
408
+ }
409
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
410
+ }
411
+
412
+ const cachedTextEncoder = new TextEncoder();
413
+
414
+ if (!('encodeInto' in cachedTextEncoder)) {
415
+ cachedTextEncoder.encodeInto = function (arg, view) {
416
+ const buf = cachedTextEncoder.encode(arg);
417
+ view.set(buf);
418
+ return {
419
+ read: arg.length,
420
+ written: buf.length
421
+ };
422
+ };
423
+ }
424
+
425
+ let WASM_VECTOR_LEN = 0;
426
+
427
+ let wasmModule, wasmInstance, wasm;
428
+ function __wbg_finalize_init(instance, module) {
429
+ wasmInstance = instance;
430
+ wasm = instance.exports;
431
+ wasmModule = module;
432
+ cachedDataViewMemory0 = null;
433
+ cachedUint8ArrayMemory0 = null;
434
+ wasm.__wbindgen_start();
435
+ return wasm;
436
+ }
437
+
438
+ async function __wbg_load(module, imports) {
439
+ if (typeof Response === 'function' && module instanceof Response) {
440
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
441
+ try {
442
+ return await WebAssembly.instantiateStreaming(module, imports);
443
+ } catch (e) {
444
+ const validResponse = module.ok && expectedResponseType(module.type);
445
+
446
+ if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
447
+ console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
448
+
449
+ } else { throw e; }
450
+ }
451
+ }
452
+
453
+ const bytes = await module.arrayBuffer();
454
+ return await WebAssembly.instantiate(bytes, imports);
455
+ } else {
456
+ const instance = await WebAssembly.instantiate(module, imports);
457
+
458
+ if (instance instanceof WebAssembly.Instance) {
459
+ return { instance, module };
460
+ } else {
461
+ return instance;
462
+ }
463
+ }
464
+
465
+ function expectedResponseType(type) {
466
+ switch (type) {
467
+ case 'basic': case 'cors': case 'default': return true;
468
+ }
469
+ return false;
470
+ }
471
+ }
472
+
473
+ function initSync(module) {
474
+ if (wasm !== undefined) return wasm;
475
+
476
+
477
+ if (module !== undefined) {
478
+ if (Object.getPrototypeOf(module) === Object.prototype) {
479
+ ({module} = module)
480
+ } else {
481
+ console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
482
+ }
483
+ }
484
+
485
+ const imports = __wbg_get_imports();
486
+ if (!(module instanceof WebAssembly.Module)) {
487
+ module = new WebAssembly.Module(module);
488
+ }
489
+ const instance = new WebAssembly.Instance(module, imports);
490
+ return __wbg_finalize_init(instance, module);
491
+ }
492
+
493
+ async function __wbg_init(module_or_path) {
494
+ if (wasm !== undefined) return wasm;
495
+
496
+
497
+ if (module_or_path !== undefined) {
498
+ if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
499
+ ({module_or_path} = module_or_path)
500
+ } else {
501
+ console.warn('using deprecated parameters for the initialization function; pass a single object instead')
502
+ }
503
+ }
504
+
505
+ if (module_or_path === undefined) {
506
+ module_or_path = new URL('engine_wasm_bg.wasm', import.meta.url);
507
+ }
508
+ const imports = __wbg_get_imports();
509
+
510
+ if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
511
+ module_or_path = fetch(module_or_path);
512
+ }
513
+
514
+ const { instance, module } = await __wbg_load(await module_or_path, imports);
515
+
516
+ return __wbg_finalize_init(instance, module);
517
+ }
518
+
519
+ export { initSync, __wbg_init as default };
Binary file
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@otisk/preview",
3
+ "type": "module",
4
+ "description": "Raster print-preview for the otisk typesetting engine: render HTML/CSS pages to RGBA bitmaps (WASM) for an HTML5-canvas preview that matches the PDF print geometry.",
5
+ "version": "1.1.0",
6
+ "license": "Apache-2.0",
7
+ "files": [
8
+ "engine_wasm_bg.wasm",
9
+ "engine_wasm.js",
10
+ "engine_wasm.d.ts"
11
+ ],
12
+ "main": "engine_wasm.js",
13
+ "types": "engine_wasm.d.ts",
14
+ "sideEffects": [
15
+ "./snippets/*"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ }
20
+ }