@1agh/maude 0.45.2 → 0.47.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/apps/studio/acp/bootstrap-brief.ts +8 -0
- package/apps/studio/acp/bridge.ts +151 -1
- package/apps/studio/annotations-layer.tsx +42 -0
- package/apps/studio/annotations-snap.ts +50 -10
- package/apps/studio/api.ts +760 -15
- package/apps/studio/artboard-guides-overlay.tsx +270 -0
- package/apps/studio/bin/_agent-browser-safe-config.json +1 -0
- package/apps/studio/bin/_agent-browser-safe.mjs +228 -0
- package/apps/studio/bin/_agent-browser-safe.test.mjs +165 -0
- package/apps/studio/bin/_curl-local.mjs +349 -0
- package/apps/studio/bin/_curl-local.test.mjs +280 -0
- package/apps/studio/bin/_pdf-playwright.mjs +35 -4
- package/apps/studio/bin/_png-playwright.mjs +38 -4
- package/apps/studio/bin/_pw-launch.mjs +52 -0
- package/apps/studio/bin/_pw-launch.test.mjs +90 -0
- package/apps/studio/bin/_smart-frames.mjs +419 -0
- package/apps/studio/bin/_smart-frames.test.mjs +140 -0
- package/apps/studio/bin/agent-browser-safe.sh +29 -0
- package/apps/studio/bin/curl-local.sh +28 -0
- package/apps/studio/bin/smart-frames.sh +30 -0
- package/apps/studio/canvas-cursors.ts +6 -0
- package/apps/studio/canvas-edit.ts +972 -6
- package/apps/studio/canvas-icons.tsx +13 -0
- package/apps/studio/canvas-lib.tsx +206 -11
- package/apps/studio/canvas-shell.tsx +809 -50
- package/apps/studio/client/app.jsx +1554 -206
- package/apps/studio/client/panels/ChatPanel.jsx +92 -3
- package/apps/studio/client/panels/SettingsPanel.jsx +211 -0
- package/apps/studio/client/panels/acp-capabilities.js +11 -0
- package/apps/studio/client/styles/3-shell-maude.css +39 -0
- package/apps/studio/client/styles/6-acp-chat.css +41 -0
- package/apps/studio/contextual-toolbar.tsx +5 -3
- package/apps/studio/dist/client.bundle.js +1627 -1627
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/dom-selection.ts +20 -0
- package/apps/studio/export-dialog.tsx +138 -18
- package/apps/studio/exporters/pdf.ts +332 -18
- package/apps/studio/exporters/png.ts +45 -3
- package/apps/studio/footage/schema.ts +17 -1
- package/apps/studio/generation/gemma-models.ts +224 -0
- package/apps/studio/generation/prefs.ts +43 -0
- package/apps/studio/grid-track-handles.ts +179 -0
- package/apps/studio/handoff.ts +35 -0
- package/apps/studio/http.ts +331 -7
- package/apps/studio/input-router.tsx +73 -17
- package/apps/studio/print/marks.ts +113 -0
- package/apps/studio/print/units.ts +269 -0
- package/apps/studio/print-overlay-content.tsx +132 -0
- package/apps/studio/test/acp-mode-banner.test.ts +45 -0
- package/apps/studio/test/acp-session-allowed-tools.test.ts +187 -0
- package/apps/studio/test/annotations-snap.test.ts +56 -0
- package/apps/studio/test/artboard-guides-overlay.test.tsx +152 -0
- package/apps/studio/test/artboard-kinds.test.tsx +83 -0
- package/apps/studio/test/artboard-selection-attrs.test.ts +69 -0
- package/apps/studio/test/browse-posture.test.tsx +107 -0
- package/apps/studio/test/canvas-hide-chrome.test.ts +58 -0
- package/apps/studio/test/canvas-meta-api.test.ts +237 -0
- package/apps/studio/test/canvas-origin-gate.test.ts +4 -0
- package/apps/studio/test/comment-mount.test.ts +2 -1
- package/apps/studio/test/component-map.test.ts +48 -0
- package/apps/studio/test/convert-to-absolute.test.ts +333 -0
- package/apps/studio/test/detach-component.test.ts +94 -0
- package/apps/studio/test/edit-scope-api.test.ts +8 -4
- package/apps/studio/test/element-structural-api.test.ts +74 -0
- package/apps/studio/test/element-structural-edit.test.ts +363 -0
- package/apps/studio/test/exporters/png.test.ts +49 -1
- package/apps/studio/test/grid-track-handles.test.ts +160 -0
- package/apps/studio/test/handoff.test.ts +48 -0
- package/apps/studio/test/input-router.test.ts +82 -8
- package/apps/studio/test/layers-synthetic-groups.test.ts +96 -0
- package/apps/studio/test/pdf-print-boxes.test.ts +326 -0
- package/apps/studio/test/print-marks.test.ts +113 -0
- package/apps/studio/test/print-units.test.ts +173 -0
- package/apps/studio/test/use-snap-guides.test.ts +81 -0
- package/apps/studio/test/use-tool-mode.test.tsx +10 -2
- package/apps/studio/tool-palette.tsx +3 -1
- package/apps/studio/use-canvas-media-drop.tsx +126 -0
- package/apps/studio/use-chrome-visibility.tsx +19 -0
- package/apps/studio/use-element-resize.tsx +24 -3
- package/apps/studio/use-grid-track-handles.tsx +364 -0
- package/apps/studio/use-keyboard-discipline.tsx +15 -0
- package/apps/studio/use-snap-guides.tsx +73 -5
- package/apps/studio/use-spacing-handles.tsx +9 -5
- package/apps/studio/use-tool-mode.tsx +30 -3
- package/apps/studio/web-overlay-content.tsx +52 -0
- package/apps/studio/whats-new.json +55 -0
- package/cli/commands/design.mjs +25 -1
- package/package.json +8 -8
- package/plugins/design/dependencies.json +35 -0
- package/plugins/design/templates/_shell.html +4 -0
|
@@ -5,13 +5,26 @@
|
|
|
5
5
|
// Per DDR-041 (PDF via page.pdf()): print-media emulation, font readiness
|
|
6
6
|
// wait, explicit page size matching the artboard rect.
|
|
7
7
|
//
|
|
8
|
+
// feature-2-print-artboards T5 (dogfood follow-up) — a "vector PDF" is only
|
|
9
|
+
// vector for TEXT and drawn shapes; any raster <img>/<canvas> content on the
|
|
10
|
+
// artboard (a photo dropped onto a flyer, a large-format billboard authored
|
|
11
|
+
// at a small on-screen scale) still embeds as a bitmap, and that bitmap's
|
|
12
|
+
// pixel density is set by the CAPTURING context's deviceScaleFactor — 1× by
|
|
13
|
+
// default, which looks fine on screen but is visibly soft once printed large
|
|
14
|
+
// (e.g. a design authored at 1:10 scale for a billboard). `--scale` sets the
|
|
15
|
+
// context's deviceScaleFactor before `page.pdf()` runs, exactly like
|
|
16
|
+
// _png-playwright.mjs, so embedded raster content captures at print density
|
|
17
|
+
// while vector text/shapes stay crisp regardless (they're never rasterized).
|
|
18
|
+
// Page SIZE (in pt) is unaffected — it always comes from the artboard's own
|
|
19
|
+
// rect, not from this scale.
|
|
20
|
+
//
|
|
8
21
|
// Invocation (Bun.spawn from exporters/pdf.ts — not invoked directly):
|
|
9
22
|
// node _pdf-playwright.mjs --url <url> --selector <css> --out <path>
|
|
10
|
-
// [--multi] [--out-dir <dir>] [--timeout 12]
|
|
23
|
+
// [--multi] [--out-dir <dir>] [--timeout 12] [--scale 3.125]
|
|
11
24
|
|
|
12
25
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
13
26
|
import { dirname, join } from 'node:path';
|
|
14
|
-
import { launchChromium } from './_pw-launch.mjs';
|
|
27
|
+
import { assertRenderOutputSizeOk, launchChromium, safeArtboardFilename } from './_pw-launch.mjs';
|
|
15
28
|
|
|
16
29
|
const args = Object.fromEntries(
|
|
17
30
|
process.argv.slice(2).reduce((acc, cur, i, all) => {
|
|
@@ -28,6 +41,7 @@ const {
|
|
|
28
41
|
'widen-to-artboard': widenFlag,
|
|
29
42
|
multi: multiFlag,
|
|
30
43
|
timeout = '12',
|
|
44
|
+
scale = '1',
|
|
31
45
|
} = args;
|
|
32
46
|
|
|
33
47
|
if (!url) {
|
|
@@ -38,10 +52,21 @@ if (!url) {
|
|
|
38
52
|
const widen = widenFlag !== undefined;
|
|
39
53
|
const multi = multiFlag !== undefined;
|
|
40
54
|
const timeoutMs = Number(timeout) * 1000;
|
|
55
|
+
// Same ceiling as _png-playwright.mjs's raised T4 ceiling (600dpi = 6.25×).
|
|
56
|
+
// The PDF *page* stays vector-sized regardless of this factor (only raster
|
|
57
|
+
// CONTENT density scales with it — see the file header) — but Chromium's
|
|
58
|
+
// rendering/rasterization surface for `page.pdf()` scales with
|
|
59
|
+
// deviceScaleFactor exactly like a screenshot buffer does, so the same
|
|
60
|
+
// output-size guard as PNG's DOES apply here (see assertRenderOutputSizeOk
|
|
61
|
+
// call below); a huge artboard + high DPI is just as costly to render.
|
|
62
|
+
const deviceScaleFactor = Math.max(1, Math.min(8, Number(scale) || 1));
|
|
41
63
|
|
|
42
64
|
const browser = await launchChromium();
|
|
43
65
|
try {
|
|
44
|
-
const ctx = await browser.newContext({
|
|
66
|
+
const ctx = await browser.newContext({
|
|
67
|
+
viewport: { width: 1440, height: 900 },
|
|
68
|
+
deviceScaleFactor,
|
|
69
|
+
});
|
|
45
70
|
const page = await ctx.newPage();
|
|
46
71
|
await page.goto(url, { waitUntil: 'load', timeout: timeoutMs });
|
|
47
72
|
// Wait for web fonts so `@font-face` glyphs land in the PDF instead of
|
|
@@ -102,9 +127,15 @@ try {
|
|
|
102
127
|
const r = el.getBoundingClientRect();
|
|
103
128
|
return { w: r.width, h: r.height, x: r.left, y: r.top };
|
|
104
129
|
});
|
|
130
|
+
assertRenderOutputSizeOk(rect.w, rect.h, deviceScaleFactor, '_pdf-playwright');
|
|
131
|
+
// feature-2-print-artboards T5 — name multi output by the artboard's
|
|
132
|
+
// OWN id (mirrors _png-playwright.mjs's multi-write scheme) rather than
|
|
133
|
+
// a positional index, so the PDF post-pass can correlate each written
|
|
134
|
+
// file back to its artboard's `print` prop via the filename alone.
|
|
135
|
+
const screenId = multi ? await screens[i].getAttribute('data-dc-screen') : null;
|
|
105
136
|
// Set the page size to the artboard's pixel dimensions so the resulting
|
|
106
137
|
// PDF is exactly one artboard per page with no margin.
|
|
107
|
-
const targetPath = multi ? join(outDir,
|
|
138
|
+
const targetPath = multi ? join(outDir, safeArtboardFilename(screenId, i, 'pdf')) : out;
|
|
108
139
|
// Crop trick: set the viewport to the artboard rect, scroll it into the
|
|
109
140
|
// top-left corner, then page.pdf() with matching width/height.
|
|
110
141
|
await page.setViewportSize({
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { mkdirSync } from 'node:fs';
|
|
9
9
|
import { dirname, join } from 'node:path';
|
|
10
|
-
import { launchChromium } from './_pw-launch.mjs';
|
|
10
|
+
import { launchChromium, safeArtboardFilename } from './_pw-launch.mjs';
|
|
11
11
|
|
|
12
12
|
const args = Object.fromEntries(
|
|
13
13
|
process.argv.slice(2).reduce((acc, cur, i, all) => {
|
|
@@ -35,7 +35,38 @@ if (!url) {
|
|
|
35
35
|
const widen = widenFlag !== undefined;
|
|
36
36
|
const multi = multiFlag !== undefined;
|
|
37
37
|
const timeoutMs = Number(timeout) * 1000;
|
|
38
|
-
|
|
38
|
+
// feature-2-print-artboards T4 — ceiling raised 4→8 so a 300dpi export
|
|
39
|
+
// (3.125×) AND 600dpi (6.25×) both fit; exporters/png.ts resolveDeviceScale
|
|
40
|
+
// is the only caller that can reach beyond 3, via the `dpi` option.
|
|
41
|
+
const deviceScaleFactor = Math.max(1, Math.min(8, Number(scale) || 1));
|
|
42
|
+
// feature-2-print-artboards T4 — a raster export's actual pixel dimensions
|
|
43
|
+
// are only known once the target element's DOM rect is measured below (the
|
|
44
|
+
// adapter can't pre-compute them — see png.ts's own comment), so the "too
|
|
45
|
+
// big to render" guard lives here rather than in the Node-side adapter.
|
|
46
|
+
// 16,000px matches common canvas/texture limits across browser engines;
|
|
47
|
+
// ~600MB is a generous RGBA-buffer ceiling (width × height × 4 bytes) well
|
|
48
|
+
// under typical CI/desktop RAM, chosen so a legitimate 600dpi A0 poster still
|
|
49
|
+
// fits (9933×14043px × 4B ≈ 558MB) while a runaway value fails loud instead
|
|
50
|
+
// of OOM-killing the render process.
|
|
51
|
+
const MAX_OUTPUT_SIDE_PX = 16000;
|
|
52
|
+
const MAX_OUTPUT_BYTES = 600 * 1024 * 1024;
|
|
53
|
+
function assertOutputSizeOk(widthCss, heightCss) {
|
|
54
|
+
const outW = Math.ceil(widthCss * deviceScaleFactor);
|
|
55
|
+
const outH = Math.ceil(heightCss * deviceScaleFactor);
|
|
56
|
+
const estBytes = outW * outH * 4;
|
|
57
|
+
if (outW > MAX_OUTPUT_SIDE_PX || outH > MAX_OUTPUT_SIDE_PX || estBytes > MAX_OUTPUT_BYTES) {
|
|
58
|
+
const maxScaleForSide = MAX_OUTPUT_SIDE_PX / Math.max(widthCss, heightCss);
|
|
59
|
+
const maxScaleForBytes = Math.sqrt(MAX_OUTPUT_BYTES / 4 / (widthCss * heightCss));
|
|
60
|
+
const maxScale = Math.min(maxScaleForSide, maxScaleForBytes);
|
|
61
|
+
const maxDpi = Math.floor(maxScale * 96);
|
|
62
|
+
console.error(
|
|
63
|
+
`_png-playwright: requested output ${outW}×${outH}px at ${deviceScaleFactor}× exceeds the ` +
|
|
64
|
+
`render guard (max side ${MAX_OUTPUT_SIDE_PX}px / max ~${Math.round(MAX_OUTPUT_BYTES / 1024 / 1024)}MB). ` +
|
|
65
|
+
`For this artboard size (${Math.round(widthCss)}×${Math.round(heightCss)}px), the max supported DPI is ~${maxDpi}.`
|
|
66
|
+
);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
39
70
|
|
|
40
71
|
const browser = await launchChromium();
|
|
41
72
|
try {
|
|
@@ -97,6 +128,9 @@ try {
|
|
|
97
128
|
const r = el.getBoundingClientRect();
|
|
98
129
|
return { x: r.left, y: r.top, width: r.width, height: r.height };
|
|
99
130
|
});
|
|
131
|
+
// feature-2-print-artboards T4 — fail loud BEFORE resizing the viewport
|
|
132
|
+
// to a pathological size, not after.
|
|
133
|
+
assertOutputSizeOk(rect.width, rect.height);
|
|
100
134
|
// Resize the viewport to fit the artboard so the screenshot doesn't
|
|
101
135
|
// include the world plane's pan/zoom margin. The artboard then sits at
|
|
102
136
|
// (0,0) with its native dimensions.
|
|
@@ -141,8 +175,8 @@ try {
|
|
|
141
175
|
const screens = await page.locator(selector ?? '[data-dc-screen]').all();
|
|
142
176
|
for (let i = 0; i < screens.length; i += 1) {
|
|
143
177
|
const handle = screens[i];
|
|
144
|
-
const id =
|
|
145
|
-
await captureHandle(handle, join(outDir,
|
|
178
|
+
const id = await handle.getAttribute('data-dc-screen');
|
|
179
|
+
await captureHandle(handle, join(outDir, safeArtboardFilename(id, i, 'png')));
|
|
146
180
|
console.log(`MAUDE_PROGRESS {"current":${i + 1},"total":${screens.length}}`);
|
|
147
181
|
}
|
|
148
182
|
} else {
|
|
@@ -113,3 +113,55 @@ export async function launchChromium(opts) {
|
|
|
113
113
|
throw err;
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
+
|
|
117
|
+
// Shared by every multi-artboard export shim (_png/_pdf-playwright.mjs) that
|
|
118
|
+
// names its output file after an artboard's `data-dc-screen` id for T5's
|
|
119
|
+
// filename-correlation convenience. That id comes straight off a live DOM
|
|
120
|
+
// attribute — i.e. from canvas-authored JSX, DDR-054 untrusted content — and
|
|
121
|
+
// feeds directly into `path.join(outDir, ...)`, which does NOT sandbox `..`
|
|
122
|
+
// segments. Re-validates against the SAME shape api.ts's write-side artboard-id
|
|
123
|
+
// ops already enforce (`/^[A-Za-z][\w-]{0,63}$/`) before it's ever used as a
|
|
124
|
+
// path segment; anything else (or a `false`y id) falls back to the safe
|
|
125
|
+
// positional name so a malformed/malicious id can never escape `outDir`.
|
|
126
|
+
const SAFE_ARTBOARD_ID_RE = /^[A-Za-z][\w-]{0,63}$/;
|
|
127
|
+
export function safeArtboardFilename(id, index, ext) {
|
|
128
|
+
const safe =
|
|
129
|
+
typeof id === 'string' && SAFE_ARTBOARD_ID_RE.test(id) ? id : `artboard-${index + 1}`;
|
|
130
|
+
return `${safe}.${ext}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Shared by every raster-capture export shim (_png/_pdf-playwright.mjs) that
|
|
134
|
+
// scales its render surface by `deviceScaleFactor` (feature-2-print-artboards
|
|
135
|
+
// T4's DPI support). The requested pixel output is only knowable once the
|
|
136
|
+
// target element's DOM rect is measured (the Node-side adapter can't
|
|
137
|
+
// pre-compute it), so this guard lives here rather than in exporters/*.ts.
|
|
138
|
+
// Applies EQUALLY to the PDF shim: `deviceScaleFactor` scales Chromium's
|
|
139
|
+
// whole rendering/rasterization surface for `page.pdf()` too, not just PNG's
|
|
140
|
+
// screenshot buffer — the PDF *page* stays vector-sized regardless, but the
|
|
141
|
+
// backing store Chromium allocates to paint it does not, so a huge
|
|
142
|
+
// artboard + high DPI is exactly as costly to render as it is for PNG, even
|
|
143
|
+
// though the resulting PDF bytes are typically far smaller than a raw PNG
|
|
144
|
+
// buffer. 16,000px matches common canvas/texture limits across browser
|
|
145
|
+
// engines; ~600MB is a generous RGBA-buffer ceiling (width × height × 4
|
|
146
|
+
// bytes) chosen so a legitimate 600dpi A0 poster still fits (9933×14043px ×
|
|
147
|
+
// 4B ≈ 558MB) while a runaway value fails loud instead of OOM-killing the
|
|
148
|
+
// render process.
|
|
149
|
+
const MAX_OUTPUT_SIDE_PX = 16000;
|
|
150
|
+
const MAX_OUTPUT_BYTES = 600 * 1024 * 1024;
|
|
151
|
+
export function assertRenderOutputSizeOk(widthCss, heightCss, deviceScaleFactor, label) {
|
|
152
|
+
const outW = Math.ceil(widthCss * deviceScaleFactor);
|
|
153
|
+
const outH = Math.ceil(heightCss * deviceScaleFactor);
|
|
154
|
+
const estBytes = outW * outH * 4;
|
|
155
|
+
if (outW > MAX_OUTPUT_SIDE_PX || outH > MAX_OUTPUT_SIDE_PX || estBytes > MAX_OUTPUT_BYTES) {
|
|
156
|
+
const maxScaleForSide = MAX_OUTPUT_SIDE_PX / Math.max(widthCss, heightCss);
|
|
157
|
+
const maxScaleForBytes = Math.sqrt(MAX_OUTPUT_BYTES / 4 / (widthCss * heightCss));
|
|
158
|
+
const maxScale = Math.min(maxScaleForSide, maxScaleForBytes);
|
|
159
|
+
const maxDpi = Math.floor(maxScale * 96);
|
|
160
|
+
console.error(
|
|
161
|
+
`${label}: requested output ${outW}×${outH}px at ${deviceScaleFactor}× exceeds the ` +
|
|
162
|
+
`render guard (max side ${MAX_OUTPUT_SIDE_PX}px / max ~${Math.round(MAX_OUTPUT_BYTES / 1024 / 1024)}MB). ` +
|
|
163
|
+
`For this artboard size (${Math.round(widthCss)}×${Math.round(heightCss)}px), the max supported DPI is ~${maxDpi}.`
|
|
164
|
+
);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// _pw-launch.test.mjs — the pure, security-relevant helpers shared by the
|
|
2
|
+
// multi-artboard export shims (_png/_pdf-playwright.mjs). Regression coverage
|
|
3
|
+
// for the arbitrary-file-write finding fixed in the print-artboards dogfood
|
|
4
|
+
// pass (DDR-182 addendum): a `data-dc-screen` id is DDR-054 untrusted canvas
|
|
5
|
+
// content, and used to be trusted verbatim as a `path.join` segment.
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from 'bun:test';
|
|
8
|
+
import { assertRenderOutputSizeOk, safeArtboardFilename } from './_pw-launch.mjs';
|
|
9
|
+
|
|
10
|
+
describe('safeArtboardFilename', () => {
|
|
11
|
+
test('passes a well-formed id through unchanged', () => {
|
|
12
|
+
expect(safeArtboardFilename('ak-alpha', 0, 'pdf')).toBe('ak-alpha.pdf');
|
|
13
|
+
expect(safeArtboardFilename('Beta_2', 3, 'png')).toBe('Beta_2.png');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('falls back to the positional name for a path-traversal id', () => {
|
|
17
|
+
expect(safeArtboardFilename('../../../../tmp/pwned', 2, 'pdf')).toBe('artboard-3.pdf');
|
|
18
|
+
expect(safeArtboardFilename('/etc/passwd', 0, 'png')).toBe('artboard-1.png');
|
|
19
|
+
expect(safeArtboardFilename('..', 1, 'pdf')).toBe('artboard-2.pdf');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('falls back for null/undefined/empty/non-string ids', () => {
|
|
23
|
+
expect(safeArtboardFilename(null, 0, 'pdf')).toBe('artboard-1.pdf');
|
|
24
|
+
expect(safeArtboardFilename(undefined, 4, 'png')).toBe('artboard-5.png');
|
|
25
|
+
expect(safeArtboardFilename('', 1, 'pdf')).toBe('artboard-2.pdf');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('falls back for an id starting with a digit or containing unsafe chars', () => {
|
|
29
|
+
expect(safeArtboardFilename('1abc', 0, 'pdf')).toBe('artboard-1.pdf');
|
|
30
|
+
expect(safeArtboardFilename('a/b', 0, 'pdf')).toBe('artboard-1.pdf');
|
|
31
|
+
expect(safeArtboardFilename('a b', 0, 'pdf')).toBe('artboard-1.pdf');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('falls back for an id over the 64-char cap', () => {
|
|
35
|
+
expect(safeArtboardFilename(`a${'x'.repeat(64)}`, 0, 'pdf')).toBe('artboard-1.pdf');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('assertRenderOutputSizeOk', () => {
|
|
40
|
+
test('does not exit for a reasonable A4-at-300dpi request', () => {
|
|
41
|
+
const origExit = process.exit;
|
|
42
|
+
let exited = false;
|
|
43
|
+
process.exit = () => {
|
|
44
|
+
exited = true;
|
|
45
|
+
};
|
|
46
|
+
try {
|
|
47
|
+
assertRenderOutputSizeOk(818, 1146, 3.125, '_test');
|
|
48
|
+
} finally {
|
|
49
|
+
process.exit = origExit;
|
|
50
|
+
}
|
|
51
|
+
expect(exited).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('exits when the requested output exceeds the max side guard', () => {
|
|
55
|
+
const origExit = process.exit;
|
|
56
|
+
const origError = console.error;
|
|
57
|
+
let exitCode;
|
|
58
|
+
process.exit = (code) => {
|
|
59
|
+
exitCode = code;
|
|
60
|
+
throw new Error('exit'); // stop the (test-only) function from continuing past exit
|
|
61
|
+
};
|
|
62
|
+
console.error = () => {};
|
|
63
|
+
try {
|
|
64
|
+
expect(() => assertRenderOutputSizeOk(20000, 20000, 1, '_test')).toThrow();
|
|
65
|
+
} finally {
|
|
66
|
+
process.exit = origExit;
|
|
67
|
+
console.error = origError;
|
|
68
|
+
}
|
|
69
|
+
expect(exitCode).toBe(1);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('exits when the requested output exceeds the byte-size guard even under the side cap', () => {
|
|
73
|
+
const origExit = process.exit;
|
|
74
|
+
const origError = console.error;
|
|
75
|
+
let exitCode;
|
|
76
|
+
process.exit = (code) => {
|
|
77
|
+
exitCode = code;
|
|
78
|
+
throw new Error('exit');
|
|
79
|
+
};
|
|
80
|
+
console.error = () => {};
|
|
81
|
+
try {
|
|
82
|
+
// 15000×15000 stays under the 16000px side cap but blows the ~600MB estimate.
|
|
83
|
+
expect(() => assertRenderOutputSizeOk(15000, 15000, 1, '_test')).toThrow();
|
|
84
|
+
} finally {
|
|
85
|
+
process.exit = origExit;
|
|
86
|
+
console.error = origError;
|
|
87
|
+
}
|
|
88
|
+
expect(exitCode).toBe(1);
|
|
89
|
+
});
|
|
90
|
+
});
|