@@ -12,20 +12,73 @@ export const pdfGenerateTask = defineSandboxTask<SandboxTaskInput>({
1212 if (!PDFLib) throw new Error('pdf-lib bundle not loaded');
1313 globalThis.PDFLib = PDFLib;
1414 globalThis.pdf = await PDFLib.PDFDocument.create();
15- globalThis.embedImage = async (dataUri) => {
15+
16+ // Convenience shortcuts — avoids verbose PDFLib.rgb() / PDFLib.StandardFonts.Helvetica
17+ globalThis.rgb = PDFLib.rgb;
18+ globalThis.StandardFonts = PDFLib.StandardFonts;
19+
20+ // Page-size constants in points (1pt = 1/72 inch)
21+ globalThis.LETTER = [612, 792]; // 8.5" × 11"
22+ globalThis.A4 = [595.28, 841.89]; // 210mm × 297mm
23+
24+ // 6 MB raw ≈ 8 MB base64; reject above this to avoid sandbox OOM.
25+ const _MAX_IMG_B64 = 8 * 1024 * 1024;
26+
27+ /**
28+ * embedImage(dataUri) — embed a data-URI image into the active PDF document.
29+ * Dispatches to embedPng or embedJpg based on MIME type.
30+ */
31+ globalThis.embedImage = async function embedImage(dataUri) {
32+ if (!dataUri || typeof dataUri !== 'string') {
33+ throw new Error('embedImage: dataUri must be a non-empty string');
34+ }
35+ if (dataUri.length > _MAX_IMG_B64) {
36+ throw new Error(
37+ 'embedImage: image exceeds the 6 MB embed limit (~8 MB base64). Use a smaller/compressed image.'
38+ );
39+ }
1640 const comma = dataUri.indexOf(',');
41+ if (comma === -1) throw new Error('embedImage: invalid data URI (no comma separator)');
1742 const header = dataUri.slice(0, comma);
1843 const base64 = dataUri.slice(comma + 1);
1944 const binary = globalThis.Buffer ? globalThis.Buffer.from(base64, 'base64') : null;
2045 if (!binary) throw new Error('Buffer polyfill missing');
2146 const mime = header.split(';')[0].split(':')[1] || '';
22- if (mime.includes('png')) return globalThis.pdf.embedPng(binary);
23- return globalThis.pdf.embedJpg(binary);
47+ // image/jpg is non-standard but tolerated; the canonical MIME is image/jpeg
48+ if (mime === 'image/png') return globalThis.pdf.embedPng(binary);
49+ if (mime === 'image/jpeg' || mime === 'image/jpg') return globalThis.pdf.embedJpg(binary);
50+ throw new Error('embedImage: only PNG and JPEG are supported (got ' + (mime || 'unknown — check data URI header') + ')');
2451 };
25- globalThis.getFileBase64 = async (fileId) => {
52+
53+ /**
54+ * getFileBase64(fileId) — load a workspace file as a data URI string.
55+ */
56+ globalThis.getFileBase64 = async function getFileBase64(fileId) {
57+ if (!fileId || typeof fileId !== 'string') {
58+ throw new Error('getFileBase64: fileId must be a non-empty string');
59+ }
2660 const res = await globalThis.__brokers.workspaceFile({ fileId });
61+ if (!res || !res.dataUri) {
62+ throw new Error('getFileBase64: broker returned no data for file ' + fileId);
63+ }
64+ if (res.dataUri.length > _MAX_IMG_B64) {
65+ throw new Error(
66+ 'getFileBase64: image exceeds the 6 MB embed limit (~8 MB base64). Use a smaller/compressed image.'
67+ );
68+ }
2769 return res.dataUri;
2870 };
71+
72+ /**
73+ * drawImage(page, fileId, opts) — fetch a workspace file and draw it on the given page.
74+ * Required opts: x, y, width, height (points).
75+ * Example: await drawImage(page, 'abc123', { x: 50, y: 700, width: 200, height: 100 });
76+ */
77+ globalThis.drawImage = async function drawImage(page, fileId, opts) {
78+ const dataUri = await globalThis.getFileBase64(fileId);
79+ const img = await globalThis.embedImage(dataUri);
80+ page.drawImage(img, opts || {});
81+ };
2982 ` ,
3083 finalize : `
3184 const pdf = globalThis.pdf;
0 commit comments