@cs-object/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,607 @@
1
+ import {
2
+ parseStrictJson
3
+ } from "./chunk-KYWIVB2V.js";
4
+ import {
5
+ stringifyJson
6
+ } from "./chunk-MOM6Q2VG.js";
7
+
8
+ // src/evidence.ts
9
+ import {
10
+ mkdirSync,
11
+ mkdtempSync,
12
+ readFileSync as readFileSync2,
13
+ readdirSync,
14
+ renameSync,
15
+ rmSync,
16
+ writeFileSync,
17
+ lstatSync
18
+ } from "fs";
19
+ import { basename, dirname as dirname2, join } from "path";
20
+ import { randomUUID } from "crypto";
21
+ import { isDeepStrictEqual } from "util";
22
+ import {
23
+ BoundPreparedDocumentSchema
24
+ } from "@cs-object/core";
25
+
26
+ // src/assets.ts
27
+ import { readFileSync, realpathSync, statSync } from "fs";
28
+ import { dirname, isAbsolute, relative, resolve } from "path";
29
+ import { createHash } from "crypto";
30
+ import {
31
+ ResolvedAssetSchema
32
+ } from "@cs-object/core";
33
+ import { DocumentPreparationError } from "@cs-object/react";
34
+ var sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
35
+ function captureAssets(sourcePath, execution) {
36
+ const root = dirname(realpathSync(sourcePath));
37
+ const captures = /* @__PURE__ */ new Map();
38
+ return execution.assets.map((asset) => {
39
+ try {
40
+ const path = realpathSync(
41
+ resolve(root, dirname(asset.moduleId), asset.path)
42
+ );
43
+ const within = relative(root, path);
44
+ if (within === ".." || within.startsWith("../") || isAbsolute(within))
45
+ throw new Error("Asset resolves outside the entry source directory");
46
+ if (!statSync(path).isFile())
47
+ throw new Error("Asset is not a regular file");
48
+ let bytes = captures.get(path);
49
+ if (!bytes) {
50
+ bytes = readFileSync(path);
51
+ captures.set(path, bytes);
52
+ }
53
+ if (sha256(bytes) !== asset.sha256)
54
+ throw new Error("Asset bytes changed since Python capture");
55
+ const png = bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
56
+ const jpeg = bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
57
+ if (asset.mediaType === "image/png" && !png)
58
+ throw new Error("Declared PNG has the wrong media signature");
59
+ if (asset.mediaType === "image/jpeg" && !jpeg)
60
+ throw new Error("Declared JPEG has the wrong media signature");
61
+ if (asset.mediaType === "image/svg+xml") {
62
+ const xml = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
63
+ if (png || jpeg || /<!DOCTYPE|<!ENTITY/i.test(xml) || !/^\s*(?:<\?xml[^?]*\?>\s*)?<svg[\s>]/i.test(xml))
64
+ throw new Error(
65
+ "SVG requires UTF-8 SVG markup without document types or entities"
66
+ );
67
+ }
68
+ return ResolvedAssetSchema.parse({
69
+ asset,
70
+ dataUrl: `data:${asset.mediaType};base64,${bytes.toString("base64")}`
71
+ });
72
+ } catch (error) {
73
+ throw new DocumentPreparationError([
74
+ {
75
+ code: "ASSET_CAPTURE_FAILED",
76
+ stage: "document",
77
+ message: `${asset.moduleId}:${asset.path}: ${error instanceof Error ? error.message : String(error)}`
78
+ }
79
+ ]);
80
+ }
81
+ });
82
+ }
83
+
84
+ // src/evidence.ts
85
+ var jsonBytes = (value) => Buffer.from(`${stringifyJson(value)}
86
+ `);
87
+ var pointerKey = (key) => key.replaceAll("~", "~0").replaceAll("/", "~1");
88
+ var suppliedJson = (value) => Array.isArray(value) ? value.map(suppliedJson) : value !== null && typeof value === "object" ? Object.fromEntries(
89
+ Object.entries(value).filter(([, entry]) => entry !== void 0).map(([key, entry]) => [key, suppliedJson(entry)])
90
+ ) : value;
91
+ function fields(value, artifact, pointer = "") {
92
+ if (value !== null && typeof value === "object" && Object.keys(value).length)
93
+ return Object.entries(value).flatMap(
94
+ ([key, entry]) => fields(entry, artifact, `${pointer}/${pointerKey(key)}`)
95
+ );
96
+ return [{ artifact, pointer, sha256: sha256(jsonBytes(value)) }];
97
+ }
98
+ function retainSource(capture, document2) {
99
+ BoundPreparedDocumentSchema.parse({ execution: capture.execution, document: document2 });
100
+ const preparedBytes = jsonBytes(document2);
101
+ const roundTrip = parseStrictJson(preparedBytes.toString("utf8"));
102
+ if (!isDeepStrictEqual(roundTrip, suppliedJson(document2)))
103
+ throw new Error("Prepared data did not survive exact JSON round trip");
104
+ const execution = parseStrictJson(
105
+ new TextDecoder("utf-8", { fatal: true }).decode(capture.executionBytes)
106
+ );
107
+ const payloads = [
108
+ { path: "execution.json", bytes: capture.executionBytes },
109
+ { path: "verification.json", bytes: jsonBytes(capture.verification) },
110
+ { path: "prepared.json", bytes: preparedBytes }
111
+ ];
112
+ if (capture.referenceBytes)
113
+ payloads.push({ path: "reference.json", bytes: capture.referenceBytes });
114
+ return {
115
+ payloads,
116
+ retainedFields: [
117
+ ...fields(execution, "execution.json"),
118
+ ...fields(roundTrip, "prepared.json")
119
+ ]
120
+ };
121
+ }
122
+ function publishDocumentEvidence(options) {
123
+ const { outPath, artifact } = options;
124
+ const bytes = artifact.bytes;
125
+ mkdirSync(dirname2(outPath), { recursive: true });
126
+ const temporaryOutput = join(
127
+ dirname2(outPath),
128
+ `.${basename(outPath)}.${randomUUID()}.tmp`
129
+ );
130
+ try {
131
+ writeFileSync(temporaryOutput, bytes, { flag: "wx" });
132
+ if (!readFileSync2(temporaryOutput).equals(bytes)) {
133
+ throw new Error("Temporary document byte verification failed");
134
+ }
135
+ const evidence = options.retainEvidence === false ? void 0 : publishEvidenceBundle(options);
136
+ renameSync(temporaryOutput, outPath);
137
+ return evidence;
138
+ } finally {
139
+ try {
140
+ rmSync(temporaryOutput, { force: true });
141
+ } catch {
142
+ }
143
+ }
144
+ }
145
+ function publishEvidenceBundle(options) {
146
+ const { outPath, artifact, reportBytes, retained, presentation } = options;
147
+ const payloads = [
148
+ ...retained.payloads,
149
+ {
150
+ path: "source-audit.json",
151
+ bytes: jsonBytes({
152
+ auditVersion: "1",
153
+ defaultDisposition: "Retained in the named evidence artifact; only explicit engineering projections are presented.",
154
+ retainedFields: retained.retainedFields,
155
+ presentationArtifact: "prepared.json",
156
+ presentation
157
+ })
158
+ }
159
+ ];
160
+ const manifest = jsonBytes({
161
+ manifestVersion: "1",
162
+ [artifact.kind]: { path: outPath, sha256: sha256(artifact.bytes) },
163
+ prospectiveCommandReport: {
164
+ sha256: sha256(reportBytes),
165
+ publication: "Requires the actual successful stdout report and matching document; this manifest alone is not a publication receipt."
166
+ },
167
+ artifacts: payloads.map(({ path, bytes }) => ({
168
+ path,
169
+ sha256: sha256(bytes)
170
+ })),
171
+ outcomes: {
172
+ dataRetention: {
173
+ status: "passed",
174
+ fields: retained.retainedFields.length
175
+ },
176
+ engineeringPresentation: {
177
+ automatic: options.presentationCheck,
178
+ visualInspection: "pending"
179
+ },
180
+ sourceToDocumentConsistency: {
181
+ artifact: "verification.json",
182
+ pointer: "/checks/sourceToDocumentConsistency"
183
+ },
184
+ independentReferenceAgreement: {
185
+ artifact: "verification.json",
186
+ pointer: "/checks/independentReferenceAgreement"
187
+ }
188
+ }
189
+ });
190
+ const hash = sha256(manifest);
191
+ const parent = `${outPath}.evidence`;
192
+ const destination = join(parent, hash);
193
+ let temporaryEvidence;
194
+ try {
195
+ mkdirSync(parent, { recursive: true });
196
+ temporaryEvidence = mkdtempSync(join(parent, ".tmp-"));
197
+ const files = [...payloads, { path: "manifest.json", bytes: manifest }];
198
+ for (const file of files) {
199
+ const path = join(temporaryEvidence, file.path);
200
+ writeFileSync(path, file.bytes, { flag: "wx" });
201
+ if (!readFileSync2(path).equals(file.bytes))
202
+ throw new Error(`Evidence verification failed: ${file.path}`);
203
+ }
204
+ try {
205
+ renameSync(temporaryEvidence, destination);
206
+ temporaryEvidence = void 0;
207
+ } catch (error) {
208
+ if (!lstatSync(destination).isDirectory() || readdirSync(destination).sort().join("\0") !== files.map((file) => file.path).sort().join("\0") || files.some(
209
+ (file) => !lstatSync(join(destination, file.path)).isFile() || !readFileSync2(join(destination, file.path)).equals(file.bytes)
210
+ ))
211
+ throw error;
212
+ }
213
+ if (temporaryEvidence) {
214
+ rmSync(temporaryEvidence, { recursive: true });
215
+ temporaryEvidence = void 0;
216
+ }
217
+ } finally {
218
+ if (temporaryEvidence) {
219
+ try {
220
+ rmSync(temporaryEvidence, { recursive: true, force: true });
221
+ } catch {
222
+ }
223
+ }
224
+ }
225
+ return { path: join(destination, "manifest.json"), sha256: hash };
226
+ }
227
+
228
+ // src/prepared-html.ts
229
+ import { readFileSync as readFileSync3 } from "fs";
230
+ import { createRequire } from "module";
231
+ import { createElement } from "react";
232
+ import { renderToStaticMarkup } from "react-dom/server";
233
+ import { PreparedFormulaSheet } from "@cs-object/react";
234
+ function buildPreparedHtml(preparedDocument, reviewNotice) {
235
+ const css = readFileSync3(
236
+ createRequire(import.meta.url).resolve("@cs-object/react/style.css"),
237
+ "utf8"
238
+ );
239
+ const markup = renderToStaticMarkup(
240
+ createElement(PreparedFormulaSheet, { document: preparedDocument })
241
+ );
242
+ const notice = reviewNotice ? renderToStaticMarkup(
243
+ createElement(
244
+ "p",
245
+ {
246
+ "data-cso-review-notice": true,
247
+ style: {
248
+ fontSize: "10pt",
249
+ margin: "0 0 4mm",
250
+ padding: "2mm",
251
+ border: "1px solid #bbb"
252
+ }
253
+ },
254
+ reviewNotice
255
+ )
256
+ ) : "";
257
+ return [
258
+ '<!doctype html><html lang="en"><head><meta charset="utf-8">',
259
+ renderToStaticMarkup(createElement("title", null, preparedDocument.title)),
260
+ `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data:; style-src 'unsafe-inline'; font-src data:">`,
261
+ `<meta name="viewport" content="width=device-width, initial-scale=1"><style>${css}</style></head><body class="formula-sheet-printing cso-standalone-report">`,
262
+ `<div data-formula-sheet-print-root="true">${notice}${markup}</div></body></html>`
263
+ ].join("");
264
+ }
265
+
266
+ // src/pdf-rendering.ts
267
+ import { chromium } from "playwright";
268
+ var LayoutInspectionError = class extends Error {
269
+ constructor(diagnostics) {
270
+ super(diagnostics.map((diagnostic) => diagnostic.message).join("\n"));
271
+ this.diagnostics = diagnostics;
272
+ }
273
+ };
274
+ function assertPassiveSvgAssetsInPage(assets) {
275
+ for (const resolved of assets) {
276
+ if (resolved.asset.mediaType !== "image/svg+xml") continue;
277
+ const xml = new TextDecoder("utf-8", { fatal: true }).decode(
278
+ Uint8Array.from(
279
+ atob(resolved.dataUrl.split(",")[1]),
280
+ (c) => c.charCodeAt(0)
281
+ )
282
+ );
283
+ const parsed = new DOMParser().parseFromString(xml, "image/svg+xml");
284
+ if (parsed.querySelector("parsererror") || parsed.documentElement.localName !== "svg" || parsed.documentElement.namespaceURI !== "http://www.w3.org/2000/svg")
285
+ throw new Error("Invalid SVG XML");
286
+ const forbidden = /* @__PURE__ */ new Set([
287
+ "script",
288
+ "foreignobject",
289
+ "iframe",
290
+ "object",
291
+ "embed",
292
+ "audio",
293
+ "video",
294
+ "a",
295
+ "animate",
296
+ "animatetransform",
297
+ "animatemotion",
298
+ "discard",
299
+ "set",
300
+ "style"
301
+ ]);
302
+ for (const element of parsed.querySelectorAll("*")) {
303
+ if (element.namespaceURI !== "http://www.w3.org/2000/svg" || forbidden.has(element.localName.toLowerCase()))
304
+ throw new Error(`Active SVG element ${element.localName}`);
305
+ for (const attr of element.attributes) {
306
+ const name = attr.localName.toLowerCase();
307
+ if (name.startsWith("on") || name === "style" || name === "base" || (name === "href" || name === "src") && !/^#[^\s]*$/.test(attr.value) || /url\s*\(/i.test(attr.value) && !/^url\(\s*#[A-Za-z_][\w.-]*\s*\)$/.test(attr.value))
308
+ throw new Error(`Active or external SVG attribute ${attr.name}`);
309
+ }
310
+ }
311
+ }
312
+ }
313
+ async function waitForPrintAssetsInPage() {
314
+ let timer;
315
+ try {
316
+ await Promise.race([
317
+ Promise.all([
318
+ document.fonts.ready,
319
+ ...Array.from(document.images, async (image) => {
320
+ await image.decode();
321
+ if (!image.naturalWidth || !image.naturalHeight)
322
+ throw new Error("Image decoded without dimensions");
323
+ })
324
+ ]),
325
+ new Promise((_, reject) => {
326
+ timer = setTimeout(
327
+ () => reject(new Error("Image/font readiness timed out")),
328
+ 15e3
329
+ );
330
+ })
331
+ ]);
332
+ } finally {
333
+ clearTimeout(timer);
334
+ }
335
+ }
336
+ function presentationInput(preparedDocument) {
337
+ return {
338
+ sections: preparedDocument.sections.map((section) => ({
339
+ id: section.id,
340
+ title: section.title,
341
+ sourcePlacementId: section.sourcePlacementId,
342
+ items: section.items.map((item) => ({
343
+ kind: item.kind,
344
+ sourcePlacementId: item.sourcePlacementId,
345
+ text: item.kind === "text" ? item.text.content : void 0,
346
+ figure: item.kind === "figure" ? item.figure : void 0
347
+ }))
348
+ })),
349
+ assets: preparedDocument.assets.map(({ asset }) => asset)
350
+ };
351
+ }
352
+ function inspectPreparedPresentationInPage(prepared) {
353
+ const mappings = [
354
+ { pointer: "/title", role: "document title", selector: "h1" }
355
+ ];
356
+ const placements = Array.from(
357
+ document.querySelectorAll("[data-source-placement]")
358
+ );
359
+ function mapItemPlacement(item, itemPointer) {
360
+ const element = placements.find(
361
+ (element2) => element2.dataset.sourcePlacement === item.sourcePlacementId
362
+ );
363
+ if (!element)
364
+ throw new Error(`Missing ordered placement ${item.sourcePlacementId}`);
365
+ const itemSelector = `[data-source-placement=${JSON.stringify(item.sourcePlacementId)}]`;
366
+ switch (item.kind) {
367
+ case "text":
368
+ if (!element.textContent?.includes(item.text ?? ""))
369
+ throw new Error("Missing authored text");
370
+ mappings.push({
371
+ pointer: `${itemPointer}/text/content`,
372
+ role: "authored text",
373
+ selector: itemSelector
374
+ });
375
+ break;
376
+ case "figure":
377
+ if (element.querySelector("img")?.alt !== item.figure?.alt || element.dataset.assetSha256 !== prepared.assets.find((asset) => asset.id === item.figure?.assetId)?.sha256)
378
+ throw new Error("Figure identity mismatch");
379
+ mappings.push({
380
+ pointer: `${itemPointer}/figure`,
381
+ role: "figure with caption and alt text",
382
+ selector: itemSelector
383
+ });
384
+ break;
385
+ case "symbol":
386
+ case "symbolRef":
387
+ if (!element.querySelector("math"))
388
+ throw new Error("Missing mathematical notation");
389
+ mappings.push({
390
+ pointer: item.kind === "symbol" ? `${itemPointer}/symbol` : `${itemPointer}/id`,
391
+ role: "engineering row projection: glyph, description, units, active formula, substitution, result and comment; internal fields remain evidence",
392
+ selector: itemSelector
393
+ });
394
+ break;
395
+ case "section":
396
+ break;
397
+ }
398
+ }
399
+ for (const [sectionIndex, section] of prepared.sections.entries()) {
400
+ const pointer = `/sections/${sectionIndex}`;
401
+ const heading = placements.find(
402
+ (element) => element.dataset.sourcePlacement === section.sourcePlacementId
403
+ );
404
+ if (!heading || heading.textContent !== section.title)
405
+ throw new Error(`Missing section ${section.id}`);
406
+ const selector = `[data-source-placement=${JSON.stringify(section.sourcePlacementId)}]`;
407
+ mappings.push({
408
+ pointer: `${pointer}/title`,
409
+ role: "section title",
410
+ selector
411
+ });
412
+ for (const [itemIndex, item] of section.items.entries()) {
413
+ mapItemPlacement(item, `${pointer}/items/${itemIndex}`);
414
+ }
415
+ }
416
+ for (const element of document.querySelectorAll(
417
+ "[data-engineering-value]"
418
+ )) {
419
+ const pointer = element.dataset.engineeringValue;
420
+ if (pointer)
421
+ mappings.push({
422
+ pointer,
423
+ role: "selected engineering context",
424
+ selector: `[data-engineering-value=${JSON.stringify(pointer)}]`
425
+ });
426
+ }
427
+ return mappings;
428
+ }
429
+ function inspectLayoutInPage(media) {
430
+ const sheet = document.querySelector("[data-formula-sheet]");
431
+ if (!sheet) throw new Error("Missing formula sheet");
432
+ const sheetBounds = sheet.getBoundingClientRect();
433
+ const findings = /* @__PURE__ */ new Map();
434
+ function recordOverflow({
435
+ element,
436
+ bounds,
437
+ containerLeft,
438
+ containerRight,
439
+ placement,
440
+ fallback,
441
+ message
442
+ }) {
443
+ if (!bounds.width || !bounds.height) return;
444
+ const overflowPx = Math.max(
445
+ 0,
446
+ containerLeft - bounds.left,
447
+ bounds.right - containerRight
448
+ );
449
+ if (overflowPx <= 2) return;
450
+ const key = placement ?? fallback;
451
+ const previous = findings.get(key);
452
+ if (previous && (previous.layout?.overflowPx ?? 0) >= overflowPx) return;
453
+ const sourcePlacementId = placement?.getAttribute("data-source-placement") ?? void 0;
454
+ const selector = sourcePlacementId ? `[data-source-placement=${JSON.stringify(sourcePlacementId)}]` : "[data-formula-sheet]";
455
+ findings.set(key, {
456
+ code: "DOCUMENT_LAYOUT_OVERFLOW",
457
+ stage: "rendering",
458
+ check: "rendering",
459
+ message: `${message} in ${media} layout: ${selector}, ${element.tagName}, ${overflowPx.toFixed(2)}px beyond content bounds.`,
460
+ layout: {
461
+ media,
462
+ sourcePlacementId,
463
+ selector,
464
+ left: bounds.left,
465
+ right: bounds.right,
466
+ containerLeft,
467
+ containerRight,
468
+ overflowPx
469
+ }
470
+ });
471
+ }
472
+ for (const math of sheet.querySelectorAll("math")) {
473
+ const placement = math.closest("[data-source-placement]");
474
+ const row = math.closest(".cso-symbol-row") ?? sheet;
475
+ const cell = math.closest("td") ?? row;
476
+ const rowBounds = cell.getBoundingClientRect();
477
+ let containerLeft = Math.max(sheetBounds.left, rowBounds.left);
478
+ let containerRight = Math.min(sheetBounds.right, rowBounds.right);
479
+ const parent = math.closest("td") ?? math.parentElement;
480
+ const parentOverflow = parent && math.getBoundingClientRect().width > parent.getBoundingClientRect().width + 2;
481
+ if (parentOverflow && parent) {
482
+ const parentBounds = parent.getBoundingClientRect();
483
+ containerLeft = Math.max(containerLeft, parentBounds.left);
484
+ containerRight = Math.min(containerRight, parentBounds.right);
485
+ }
486
+ for (const element of [math, ...math.querySelectorAll("*")]) {
487
+ recordOverflow({
488
+ element,
489
+ bounds: element.getBoundingClientRect(),
490
+ containerLeft,
491
+ containerRight,
492
+ placement,
493
+ fallback: row,
494
+ message: "Mathematical notation overflows its cell or sheet"
495
+ });
496
+ }
497
+ }
498
+ for (const element of sheet.querySelectorAll("*")) {
499
+ if (element.closest("math") || element.closest("svg")) continue;
500
+ const rectangles = [element.getBoundingClientRect()];
501
+ for (const node of element.childNodes) {
502
+ if (node.nodeType !== Node.TEXT_NODE || !node.textContent?.trim())
503
+ continue;
504
+ const range = document.createRange();
505
+ range.selectNodeContents(node);
506
+ rectangles.push(...range.getClientRects());
507
+ }
508
+ for (const bounds of rectangles) {
509
+ recordOverflow({
510
+ element,
511
+ bounds,
512
+ containerLeft: sheetBounds.left,
513
+ containerRight: sheetBounds.right,
514
+ placement: element.closest("[data-source-placement]"),
515
+ fallback: sheet,
516
+ message: "Document content exceeds printable sheet width"
517
+ });
518
+ }
519
+ }
520
+ return [...findings.values()];
521
+ }
522
+ async function withPreparedPage(document2, html, usePage, signal) {
523
+ const browser = await chromium.launch({ headless: true, timeout: 3e4 });
524
+ const abort = () => {
525
+ void browser.close().catch(() => {
526
+ });
527
+ };
528
+ signal?.addEventListener("abort", abort, { once: true });
529
+ try {
530
+ signal?.throwIfAborted();
531
+ const page = await browser.newPage();
532
+ page.setDefaultTimeout(15e3);
533
+ const requests = [];
534
+ await page.route("**/*", (route) => {
535
+ requests.push(route.request().url());
536
+ return route.abort();
537
+ });
538
+ await page.evaluate(assertPassiveSvgAssetsInPage, document2.assets);
539
+ await page.setContent(html, { waitUntil: "load", timeout: 15e3 });
540
+ const diagnostics = [];
541
+ for (const media of ["screen", "print"]) {
542
+ await page.emulateMedia({ media });
543
+ await page.evaluate(waitForPrintAssetsInPage);
544
+ diagnostics.push(...await page.evaluate(inspectLayoutInPage, media));
545
+ }
546
+ if (requests.length)
547
+ throw new Error(
548
+ `Document attempted external requests: ${requests.join(", ")}`
549
+ );
550
+ if (diagnostics.length) throw new LayoutInspectionError(diagnostics);
551
+ const presentation = await page.evaluate(
552
+ inspectPreparedPresentationInPage,
553
+ presentationInput(document2)
554
+ );
555
+ return await usePage(page, presentation);
556
+ } finally {
557
+ signal?.removeEventListener("abort", abort);
558
+ await browser.close();
559
+ }
560
+ }
561
+ function inspectPreparedHtml(document2, html) {
562
+ return withPreparedPage(
563
+ document2,
564
+ html,
565
+ async (_page, presentation) => presentation
566
+ );
567
+ }
568
+ function renderPreparedPdf(document2, html = buildPreparedHtml(document2), signal) {
569
+ return withPreparedPage(
570
+ document2,
571
+ html,
572
+ async (page, presentation) => {
573
+ let printTimer;
574
+ try {
575
+ const pdf = await Promise.race([
576
+ page.pdf({
577
+ format: "A4",
578
+ printBackground: true,
579
+ margin: { top: "0", right: "0", bottom: "0", left: "0" }
580
+ }),
581
+ new Promise((_, reject) => {
582
+ printTimer = setTimeout(
583
+ () => reject(new Error("PDF printing timed out")),
584
+ 3e4
585
+ );
586
+ })
587
+ ]);
588
+ return { pdf, presentation };
589
+ } finally {
590
+ clearTimeout(printTimer);
591
+ }
592
+ },
593
+ signal
594
+ );
595
+ }
596
+
597
+ export {
598
+ sha256,
599
+ captureAssets,
600
+ jsonBytes,
601
+ retainSource,
602
+ publishDocumentEvidence,
603
+ buildPreparedHtml,
604
+ LayoutInspectionError,
605
+ inspectPreparedHtml,
606
+ renderPreparedPdf
607
+ };