@marimo-team/islands 0.20.3-dev76 → 0.20.3-dev78
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/dist/main.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/editor/actions/__tests__/pdf-export.test.ts +42 -0
- package/src/components/editor/actions/pdf-export.ts +26 -0
- package/src/components/editor/actions/useNotebookActions.tsx +86 -44
- package/src/components/editor/controls/notebook-menu-dropdown.tsx +32 -29
- package/src/core/network/__tests__/requests-network.test.ts +21 -0
- package/src/utils/__tests__/download.test.tsx +39 -0
- package/src/utils/download.ts +21 -1
package/package.json
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { runServerSidePDFDownload } from "../pdf-export";
|
|
5
|
+
|
|
6
|
+
describe("runServerSidePDFDownload", () => {
|
|
7
|
+
it("downloads document preset via backend PDF endpoint", async () => {
|
|
8
|
+
const downloadPDF = vi.fn().mockResolvedValue(undefined);
|
|
9
|
+
|
|
10
|
+
await runServerSidePDFDownload({
|
|
11
|
+
filename: "slides.py",
|
|
12
|
+
preset: "document",
|
|
13
|
+
downloadPDF,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(downloadPDF).toHaveBeenCalledWith({
|
|
17
|
+
filename: "slides.py",
|
|
18
|
+
webpdf: true,
|
|
19
|
+
preset: "document",
|
|
20
|
+
includeInputs: false,
|
|
21
|
+
rasterServer: "live",
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("downloads slides preset via backend PDF endpoint", async () => {
|
|
26
|
+
const downloadPDF = vi.fn().mockResolvedValue(undefined);
|
|
27
|
+
|
|
28
|
+
await runServerSidePDFDownload({
|
|
29
|
+
filename: "slides.py",
|
|
30
|
+
preset: "slides",
|
|
31
|
+
downloadPDF,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(downloadPDF).toHaveBeenCalledWith({
|
|
35
|
+
filename: "slides.py",
|
|
36
|
+
webpdf: true,
|
|
37
|
+
preset: "slides",
|
|
38
|
+
includeInputs: false,
|
|
39
|
+
rasterServer: "live",
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
type Preset = "document" | "slides";
|
|
4
|
+
type DownloadPDF = (opts: {
|
|
5
|
+
filename: string;
|
|
6
|
+
webpdf: boolean;
|
|
7
|
+
preset: Preset;
|
|
8
|
+
includeInputs: boolean;
|
|
9
|
+
rasterServer: "live" | "static";
|
|
10
|
+
}) => Promise<void>;
|
|
11
|
+
|
|
12
|
+
export async function runServerSidePDFDownload(opts: {
|
|
13
|
+
filename: string;
|
|
14
|
+
preset: Preset;
|
|
15
|
+
downloadPDF: DownloadPDF;
|
|
16
|
+
}): Promise<void> {
|
|
17
|
+
const { filename, preset, downloadPDF } = opts;
|
|
18
|
+
|
|
19
|
+
await downloadPDF({
|
|
20
|
+
filename,
|
|
21
|
+
webpdf: true,
|
|
22
|
+
preset,
|
|
23
|
+
includeInputs: false,
|
|
24
|
+
rasterServer: "live",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -56,10 +56,6 @@ import { disabledCellIds } from "@/core/cells/utils";
|
|
|
56
56
|
import { useResolvedMarimoConfig } from "@/core/config/config";
|
|
57
57
|
import { getFeatureFlag } from "@/core/config/feature-flag";
|
|
58
58
|
import { Constants } from "@/core/constants";
|
|
59
|
-
import {
|
|
60
|
-
updateCellOutputsWithScreenshots,
|
|
61
|
-
useEnrichCellOutputs,
|
|
62
|
-
} from "@/core/export/hooks";
|
|
63
59
|
import { useLayoutActions, useLayoutState } from "@/core/layout/layout";
|
|
64
60
|
import { useTogglePresenting } from "@/core/layout/useTogglePresenting";
|
|
65
61
|
import { kioskModeAtom, viewStateAtom } from "@/core/mode";
|
|
@@ -78,7 +74,6 @@ import {
|
|
|
78
74
|
} from "@/utils/download";
|
|
79
75
|
import { Filenames } from "@/utils/filenames";
|
|
80
76
|
import { Objects } from "@/utils/objects";
|
|
81
|
-
import type { ProgressState } from "@/utils/progress";
|
|
82
77
|
import { newNotebookURL } from "@/utils/urls";
|
|
83
78
|
import { useRunAllCells } from "../cell/useRunCells";
|
|
84
79
|
import { useChromeActions, useChromeState } from "../chrome/state";
|
|
@@ -88,6 +83,7 @@ import { commandPaletteAtom } from "../controls/state";
|
|
|
88
83
|
import { AddDatabaseDialogContent } from "../database/add-database-form";
|
|
89
84
|
import { displayLayoutName, getLayoutIcon } from "../renderers/layout-select";
|
|
90
85
|
import { LAYOUT_TYPES } from "../renderers/types";
|
|
86
|
+
import { runServerSidePDFDownload } from "./pdf-export";
|
|
91
87
|
import type { ActionButton } from "./types";
|
|
92
88
|
import { useCopyNotebook } from "./useCopyNotebook";
|
|
93
89
|
import { useHideAllMarkdownCode } from "./useHideAllMarkdownCode";
|
|
@@ -122,16 +118,13 @@ export function useNotebookActions() {
|
|
|
122
118
|
const setCommandPaletteOpen = useSetAtom(commandPaletteAtom);
|
|
123
119
|
const setSettingsDialogOpen = useSetAtom(settingDialogAtom);
|
|
124
120
|
const setKeyboardShortcutsOpen = useSetAtom(keyboardShortcutsAtom);
|
|
125
|
-
const { exportAsMarkdown, readCode, saveCellConfig
|
|
126
|
-
useRequestClient();
|
|
121
|
+
const { exportAsMarkdown, readCode, saveCellConfig } = useRequestClient();
|
|
127
122
|
|
|
128
123
|
const hasDisabledCells = useAtomValue(hasDisabledCellsAtom);
|
|
129
124
|
const canUndoDeletes = useAtomValue(canUndoDeletesAtom);
|
|
130
125
|
const { selectedLayout } = useLayoutState();
|
|
131
126
|
const { setLayoutView } = useLayoutActions();
|
|
132
127
|
const togglePresenting = useTogglePresenting();
|
|
133
|
-
const takeScreenshots = useEnrichCellOutputs();
|
|
134
|
-
|
|
135
128
|
// Fallback: if sharing is undefined, both are enabled by default
|
|
136
129
|
const sharingHtmlEnabled = resolvedConfig.sharing?.html ?? true;
|
|
137
130
|
const sharingWasmEnabled = resolvedConfig.sharing?.wasm ?? true;
|
|
@@ -141,6 +134,7 @@ export function useNotebookActions() {
|
|
|
141
134
|
// Default export uses browser print, which is better in present mode
|
|
142
135
|
const pdfDownloadEnabled =
|
|
143
136
|
isServerSidePdfExportEnabled || viewState.mode === "present";
|
|
137
|
+
const isSlidesLayout = selectedLayout === "slides";
|
|
144
138
|
|
|
145
139
|
const renderCheckboxElement = (checked: boolean) => (
|
|
146
140
|
<div className="w-8 flex justify-end">
|
|
@@ -148,6 +142,39 @@ export function useNotebookActions() {
|
|
|
148
142
|
</div>
|
|
149
143
|
);
|
|
150
144
|
|
|
145
|
+
const renderRecommendedElement = (recommended: boolean) => {
|
|
146
|
+
if (!recommended) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
return (
|
|
150
|
+
<span className="ml-3 shrink-0 rounded-full border border-emerald-200 bg-emerald-50 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-emerald-700">
|
|
151
|
+
Recommended
|
|
152
|
+
</span>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const downloadServerSidePDF = async ({
|
|
157
|
+
preset,
|
|
158
|
+
title,
|
|
159
|
+
}: {
|
|
160
|
+
preset: "document" | "slides";
|
|
161
|
+
title: string;
|
|
162
|
+
}) => {
|
|
163
|
+
if (!filename) {
|
|
164
|
+
toastNotebookMustBeNamed();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const runDownload = async () => {
|
|
169
|
+
await runServerSidePDFDownload({
|
|
170
|
+
filename,
|
|
171
|
+
preset,
|
|
172
|
+
downloadPDF: downloadAsPDF,
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
await withLoadingToast(title, runDownload);
|
|
176
|
+
};
|
|
177
|
+
|
|
151
178
|
const actions: ActionButton[] = [
|
|
152
179
|
{
|
|
153
180
|
icon: <DownloadIcon size={14} strokeWidth={1.5} />,
|
|
@@ -226,45 +253,60 @@ export function useNotebookActions() {
|
|
|
226
253
|
},
|
|
227
254
|
},
|
|
228
255
|
{
|
|
256
|
+
divider: true,
|
|
229
257
|
icon: <FileIcon size={14} strokeWidth={1.5} />,
|
|
230
258
|
label: "Download as PDF",
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
259
|
+
handle: NOOP_HANDLER,
|
|
260
|
+
disabled: !pdfDownloadEnabled && !isServerSidePdfExportEnabled,
|
|
261
|
+
dropdown: [
|
|
262
|
+
{
|
|
263
|
+
icon: <FileIcon size={14} strokeWidth={1.5} />,
|
|
264
|
+
label: "Document Layout",
|
|
265
|
+
rightElement: renderRecommendedElement(!isSlidesLayout),
|
|
266
|
+
disabled: !pdfDownloadEnabled,
|
|
267
|
+
tooltip: pdfDownloadEnabled ? undefined : (
|
|
268
|
+
<span>
|
|
269
|
+
Only available in app view. <br />
|
|
270
|
+
Toggle with: {renderShortcut("global.hideCode", false)}
|
|
271
|
+
</span>
|
|
272
|
+
),
|
|
273
|
+
handle: async () => {
|
|
274
|
+
if (isServerSidePdfExportEnabled) {
|
|
275
|
+
await downloadServerSidePDF({
|
|
276
|
+
preset: "document",
|
|
277
|
+
title: "Downloading Document PDF...",
|
|
278
|
+
});
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
244
281
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
282
|
+
const beforeprint = new Event("export-beforeprint");
|
|
283
|
+
const afterprint = new Event("export-afterprint");
|
|
284
|
+
function print() {
|
|
285
|
+
window.dispatchEvent(beforeprint);
|
|
286
|
+
setTimeout(() => window.print(), 0);
|
|
287
|
+
setTimeout(() => window.dispatchEvent(afterprint), 0);
|
|
288
|
+
}
|
|
289
|
+
print();
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
icon: <FileIcon size={14} strokeWidth={1.5} />,
|
|
294
|
+
label: "Slides Layout",
|
|
295
|
+
rightElement: renderRecommendedElement(isSlidesLayout),
|
|
296
|
+
disabled: !isServerSidePdfExportEnabled,
|
|
297
|
+
tooltip: isServerSidePdfExportEnabled ? undefined : (
|
|
298
|
+
<span>
|
|
299
|
+
Requires Better PDF Export in Settings > Experimental.
|
|
300
|
+
</span>
|
|
301
|
+
),
|
|
302
|
+
handle: async () => {
|
|
303
|
+
await downloadServerSidePDF({
|
|
304
|
+
preset: "slides",
|
|
305
|
+
title: "Downloading Slides PDF...",
|
|
253
306
|
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const beforeprint = new Event("export-beforeprint");
|
|
260
|
-
const afterprint = new Event("export-afterprint");
|
|
261
|
-
function print() {
|
|
262
|
-
window.dispatchEvent(beforeprint);
|
|
263
|
-
setTimeout(() => window.print(), 0);
|
|
264
|
-
setTimeout(() => window.dispatchEvent(afterprint), 0);
|
|
265
|
-
}
|
|
266
|
-
print();
|
|
267
|
-
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
],
|
|
268
310
|
},
|
|
269
311
|
],
|
|
270
312
|
},
|
|
@@ -106,6 +106,37 @@ export const NotebookMenuDropdown: React.FC<Props> = ({
|
|
|
106
106
|
return item;
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
+
const renderAction = (action: ActionButton) => {
|
|
110
|
+
if (action.hidden || action.redundant) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (action.dropdown) {
|
|
115
|
+
return (
|
|
116
|
+
<DropdownMenuSub key={action.label}>
|
|
117
|
+
<DropdownMenuSubTrigger
|
|
118
|
+
data-testid={`notebook-menu-dropdown-${action.label}`}
|
|
119
|
+
disabled={action.disabled}
|
|
120
|
+
>
|
|
121
|
+
{renderLabel(action)}
|
|
122
|
+
</DropdownMenuSubTrigger>
|
|
123
|
+
<DropdownMenuPortal>
|
|
124
|
+
<DropdownMenuSubContent>
|
|
125
|
+
{action.dropdown.map((childAction) => (
|
|
126
|
+
<React.Fragment key={childAction.label}>
|
|
127
|
+
{childAction.divider && <DropdownMenuSeparator />}
|
|
128
|
+
{renderAction(childAction)}
|
|
129
|
+
</React.Fragment>
|
|
130
|
+
))}
|
|
131
|
+
</DropdownMenuSubContent>
|
|
132
|
+
</DropdownMenuPortal>
|
|
133
|
+
</DropdownMenuSub>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return renderLeafAction(action);
|
|
138
|
+
};
|
|
139
|
+
|
|
109
140
|
return (
|
|
110
141
|
<DropdownMenu modal={false}>
|
|
111
142
|
<DropdownMenuTrigger asChild={true} disabled={disabled}>
|
|
@@ -113,38 +144,10 @@ export const NotebookMenuDropdown: React.FC<Props> = ({
|
|
|
113
144
|
</DropdownMenuTrigger>
|
|
114
145
|
<DropdownMenuContent align="end" className="print:hidden w-[240px]">
|
|
115
146
|
{actions.map((action) => {
|
|
116
|
-
if (action.hidden || action.redundant) {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (action.dropdown) {
|
|
121
|
-
return (
|
|
122
|
-
<DropdownMenuSub key={action.label}>
|
|
123
|
-
<DropdownMenuSubTrigger
|
|
124
|
-
data-testid={`notebook-menu-dropdown-${action.label}`}
|
|
125
|
-
>
|
|
126
|
-
{renderLabel(action)}
|
|
127
|
-
</DropdownMenuSubTrigger>
|
|
128
|
-
<DropdownMenuPortal>
|
|
129
|
-
<DropdownMenuSubContent>
|
|
130
|
-
{action.dropdown.map((action) => {
|
|
131
|
-
return (
|
|
132
|
-
<React.Fragment key={action.label}>
|
|
133
|
-
{action.divider && <DropdownMenuSeparator />}
|
|
134
|
-
{renderLeafAction(action)}
|
|
135
|
-
</React.Fragment>
|
|
136
|
-
);
|
|
137
|
-
})}
|
|
138
|
-
</DropdownMenuSubContent>
|
|
139
|
-
</DropdownMenuPortal>
|
|
140
|
-
</DropdownMenuSub>
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
147
|
return (
|
|
145
148
|
<React.Fragment key={action.label}>
|
|
146
149
|
{action.divider && <DropdownMenuSeparator />}
|
|
147
|
-
{
|
|
150
|
+
{renderAction(action)}
|
|
148
151
|
</React.Fragment>
|
|
149
152
|
);
|
|
150
153
|
})}
|
|
@@ -92,5 +92,26 @@ describe("createNetworkRequests", () => {
|
|
|
92
92
|
|
|
93
93
|
process.env.NODE_ENV = originalEnv;
|
|
94
94
|
});
|
|
95
|
+
|
|
96
|
+
it("exportAsPDF should pass preset through to the API", async () => {
|
|
97
|
+
const requests = createNetworkRequests();
|
|
98
|
+
await requests.exportAsPDF({
|
|
99
|
+
webpdf: false,
|
|
100
|
+
preset: "slides",
|
|
101
|
+
includeInputs: false,
|
|
102
|
+
} as any);
|
|
103
|
+
|
|
104
|
+
expect(mockClient.POST).toHaveBeenCalledWith(
|
|
105
|
+
"/api/export/pdf",
|
|
106
|
+
expect.objectContaining({
|
|
107
|
+
body: expect.objectContaining({
|
|
108
|
+
webpdf: false,
|
|
109
|
+
preset: "slides",
|
|
110
|
+
includeInputs: false,
|
|
111
|
+
}),
|
|
112
|
+
parseAs: "blob",
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
});
|
|
95
116
|
});
|
|
96
117
|
});
|
|
@@ -3,6 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
3
3
|
import type { CellId } from "@/core/cells/ids";
|
|
4
4
|
import { CellOutputId } from "@/core/cells/ids";
|
|
5
5
|
import {
|
|
6
|
+
downloadAsPDF,
|
|
6
7
|
downloadByURL,
|
|
7
8
|
downloadCellOutputAsImage,
|
|
8
9
|
downloadHTMLAsImage,
|
|
@@ -10,11 +11,21 @@ import {
|
|
|
10
11
|
withLoadingToast,
|
|
11
12
|
} from "../download";
|
|
12
13
|
|
|
14
|
+
const { mockExportAsPDF } = vi.hoisted(() => ({
|
|
15
|
+
mockExportAsPDF: vi.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
13
18
|
// Mock html-to-image
|
|
14
19
|
vi.mock("html-to-image", () => ({
|
|
15
20
|
toPng: vi.fn(),
|
|
16
21
|
}));
|
|
17
22
|
|
|
23
|
+
vi.mock("@/core/network/requests", () => ({
|
|
24
|
+
getRequestClient: () => ({
|
|
25
|
+
exportAsPDF: mockExportAsPDF,
|
|
26
|
+
}),
|
|
27
|
+
}));
|
|
28
|
+
|
|
18
29
|
// Mock the toast module
|
|
19
30
|
const mockDismiss = vi.fn();
|
|
20
31
|
const mockUpdate = vi.fn();
|
|
@@ -41,6 +52,7 @@ vi.mock("@/utils/Logger", () => ({
|
|
|
41
52
|
vi.mock("@/utils/filenames", () => ({
|
|
42
53
|
Filenames: {
|
|
43
54
|
toPNG: (name: string) => `${name}.png`,
|
|
55
|
+
toPDF: (name: string) => `${name}.pdf`,
|
|
44
56
|
},
|
|
45
57
|
}));
|
|
46
58
|
|
|
@@ -174,6 +186,33 @@ describe("withLoadingToast", () => {
|
|
|
174
186
|
});
|
|
175
187
|
});
|
|
176
188
|
|
|
189
|
+
describe("downloadAsPDF", () => {
|
|
190
|
+
beforeEach(() => {
|
|
191
|
+
vi.clearAllMocks();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("should send the preset in export request payload", async () => {
|
|
195
|
+
mockExportAsPDF.mockRejectedValue(new Error("network"));
|
|
196
|
+
|
|
197
|
+
await expect(
|
|
198
|
+
downloadAsPDF({
|
|
199
|
+
filename: "path/to/notebook.py",
|
|
200
|
+
webpdf: false,
|
|
201
|
+
preset: "slides",
|
|
202
|
+
}),
|
|
203
|
+
).rejects.toThrow("network");
|
|
204
|
+
|
|
205
|
+
expect(mockExportAsPDF).toHaveBeenCalledWith({
|
|
206
|
+
webpdf: false,
|
|
207
|
+
preset: "slides",
|
|
208
|
+
includeInputs: false,
|
|
209
|
+
rasterizeOutputs: true,
|
|
210
|
+
rasterScale: 4,
|
|
211
|
+
rasterServer: "static",
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
177
216
|
describe("getImageDataUrlForCell", () => {
|
|
178
217
|
const mockDataUrl = "data:image/png;base64,mockbase64data";
|
|
179
218
|
let mockElement: HTMLElement;
|
package/src/utils/download.ts
CHANGED
|
@@ -188,6 +188,8 @@ export function downloadBlob(blob: Blob, filename: string) {
|
|
|
188
188
|
URL.revokeObjectURL(url);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
export type PDFExportPreset = "document" | "slides";
|
|
192
|
+
|
|
191
193
|
/**
|
|
192
194
|
* Download the current notebook as a PDF file.
|
|
193
195
|
*
|
|
@@ -197,13 +199,31 @@ export function downloadBlob(blob: Blob, filename: string) {
|
|
|
197
199
|
export async function downloadAsPDF(opts: {
|
|
198
200
|
filename: string;
|
|
199
201
|
webpdf: boolean;
|
|
202
|
+
preset?: PDFExportPreset;
|
|
203
|
+
includeInputs?: boolean;
|
|
204
|
+
rasterizeOutputs?: boolean;
|
|
205
|
+
rasterScale?: number;
|
|
206
|
+
rasterServer?: "static" | "live";
|
|
200
207
|
}) {
|
|
201
208
|
const client = getRequestClient();
|
|
202
|
-
const {
|
|
209
|
+
const {
|
|
210
|
+
filename,
|
|
211
|
+
webpdf,
|
|
212
|
+
preset = "document",
|
|
213
|
+
includeInputs = false,
|
|
214
|
+
rasterizeOutputs = true,
|
|
215
|
+
rasterScale = 4,
|
|
216
|
+
rasterServer = "static",
|
|
217
|
+
} = opts;
|
|
203
218
|
|
|
204
219
|
try {
|
|
205
220
|
const pdfBlob = await client.exportAsPDF({
|
|
206
221
|
webpdf,
|
|
222
|
+
preset,
|
|
223
|
+
includeInputs,
|
|
224
|
+
rasterizeOutputs,
|
|
225
|
+
rasterScale,
|
|
226
|
+
rasterServer,
|
|
207
227
|
});
|
|
208
228
|
|
|
209
229
|
const filenameWithoutPath = Paths.basename(filename);
|