ggaction 0.0.6 → 0.0.7
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/CHANGELOG.md +23 -0
- package/README.md +77 -14
- package/package.json +19 -2
- package/src/BasicChartProgram.js +10 -0
- package/src/actions/basic.js +21 -0
- package/src/actions/canvas/actions.js +42 -0
- package/src/actions/canvas/index.js +5 -1
- package/src/actions/charts/basic.js +13 -0
- package/src/actions/data/basic.js +11 -0
- package/src/actions/data/basicBin2d.js +43 -0
- package/src/actions/data/bin2d.js +1 -26
- package/src/actions/data/bin2dMaterialize.js +29 -0
- package/src/actions/encodings/appearance.js +6 -2
- package/src/actions/encodings/basic.js +21 -0
- package/src/actions/encodings/position/index.js +5 -1
- package/src/actions/encodings/ranged.js +6 -2
- package/src/actions/guides/axes/index.js +10 -2
- package/src/actions/guides/basic.js +19 -0
- package/src/actions/guides/grids/grid.js +7 -3
- package/src/actions/guides/grids/index.js +1 -1
- package/src/actions/guides/legends/categorical/index.js +6 -2
- package/src/actions/guides/legends/continuous/index.js +6 -2
- package/src/actions/marks/bar/index.js +5 -1
- package/src/actions/marks/basic.js +11 -0
- package/src/actions/marks/line/actions.js +5 -1
- package/src/actions/marks/line/index.js +4 -1
- package/src/actions/marks/point/index.js +5 -1
- package/src/actions/marks/rect/actions.js +5 -1
- package/src/actions/marks/rect/index.js +4 -1
- package/src/actions/scales/index.js +5 -1
- package/src/basic.js +2 -0
- package/src/renderers/canvas/index.js +96 -60
- package/src/renderers/canvas/text.js +3 -14
- package/src/renderers/pdf.js +112 -0
- package/src/renderers/svg.js +559 -0
- package/src/renderers/text.js +11 -0
- package/types/basic.d.ts +107 -0
- package/types/pdf.d.ts +26 -0
- package/types/svg.d.ts +11 -0
|
@@ -25,9 +25,8 @@ import {
|
|
|
25
25
|
} from "./symbols.js";
|
|
26
26
|
import { editLegend } from "../edit.js";
|
|
27
27
|
|
|
28
|
-
export function
|
|
28
|
+
export function registerBasicCategoricalLegendActions(ProgramClass) {
|
|
29
29
|
ProgramClass.prototype.createLegend = createLegend;
|
|
30
|
-
ProgramClass.prototype.editLegend = editLegend;
|
|
31
30
|
ProgramClass.prototype.createCategoricalLegend = createCategoricalLegend;
|
|
32
31
|
ProgramClass.prototype.removeCategoricalLegend = removeCategoricalLegend;
|
|
33
32
|
ProgramClass.prototype.createLegendBackground = createLegendBackground;
|
|
@@ -47,3 +46,8 @@ export function registerCategoricalLegendActions(ProgramClass) {
|
|
|
47
46
|
ProgramClass.prototype.rematerializeLegendTitle = rematerializeLegendTitle;
|
|
48
47
|
ProgramClass.prototype.rematerializeLegend = rematerializeLegend;
|
|
49
48
|
}
|
|
49
|
+
|
|
50
|
+
export function registerCategoricalLegendActions(ProgramClass) {
|
|
51
|
+
registerBasicCategoricalLegendActions(ProgramClass);
|
|
52
|
+
ProgramClass.prototype.editLegend = editLegend;
|
|
53
|
+
}
|
|
@@ -15,11 +15,15 @@ export {
|
|
|
15
15
|
export { createIntervalLegend, rematerializeIntervalLegend } from "./interval.js";
|
|
16
16
|
|
|
17
17
|
export function registerContinuousLegendActions(ProgramClass) {
|
|
18
|
-
ProgramClass
|
|
19
|
-
ProgramClass.prototype.rematerializeGradientLegend = rematerializeGradientLegend;
|
|
18
|
+
registerGradientLegendActions(ProgramClass);
|
|
20
19
|
ProgramClass.prototype.createOpacityLegend = createOpacityLegend;
|
|
21
20
|
ProgramClass.prototype.rematerializeOpacityLegend = rematerializeOpacityLegend;
|
|
22
21
|
ProgramClass.prototype.removeOpacityLegend = removeOpacityLegend;
|
|
23
22
|
ProgramClass.prototype.createIntervalLegend = createIntervalLegend;
|
|
24
23
|
ProgramClass.prototype.rematerializeIntervalLegend = rematerializeIntervalLegend;
|
|
25
24
|
}
|
|
25
|
+
|
|
26
|
+
export function registerGradientLegendActions(ProgramClass) {
|
|
27
|
+
ProgramClass.prototype.createGradientLegend = createGradientLegend;
|
|
28
|
+
ProgramClass.prototype.rematerializeGradientLegend = rematerializeGradientLegend;
|
|
29
|
+
}
|
|
@@ -3,7 +3,11 @@ import { editBarMark } from "./edit.js";
|
|
|
3
3
|
import { rematerializeBarMark } from "./materialize.js";
|
|
4
4
|
|
|
5
5
|
export function registerBarMarkActions(ProgramClass) {
|
|
6
|
-
ProgramClass.prototype.createBarMark = createBarMark;
|
|
7
6
|
ProgramClass.prototype.editBarMark = editBarMark;
|
|
7
|
+
registerBasicBarMarkActions(ProgramClass);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function registerBasicBarMarkActions(ProgramClass) {
|
|
11
|
+
ProgramClass.prototype.createBarMark = createBarMark;
|
|
8
12
|
ProgramClass.prototype.rematerializeBarMark = rematerializeBarMark;
|
|
9
13
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { registerBarMarkActions } from "./bar/index.js";
|
|
2
|
+
import { registerLineMarkActions } from "./line/index.js";
|
|
3
|
+
import { registerPointMarkActions } from "./point/index.js";
|
|
4
|
+
import { registerRectMarkActions } from "./rect/index.js";
|
|
5
|
+
|
|
6
|
+
export function registerBasicMarkActions(ProgramClass) {
|
|
7
|
+
registerPointMarkActions(ProgramClass);
|
|
8
|
+
registerLineMarkActions(ProgramClass);
|
|
9
|
+
registerBarMarkActions(ProgramClass);
|
|
10
|
+
registerRectMarkActions(ProgramClass);
|
|
11
|
+
}
|
|
@@ -386,7 +386,11 @@ const editLineMark = action(
|
|
|
386
386
|
);
|
|
387
387
|
|
|
388
388
|
export function registerLineMarkActions(ProgramClass) {
|
|
389
|
-
ProgramClass.prototype.createLineMark = createLineMark;
|
|
390
389
|
ProgramClass.prototype.editLineMark = editLineMark;
|
|
390
|
+
registerBasicLineMarkActions(ProgramClass);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function registerBasicLineMarkActions(ProgramClass) {
|
|
394
|
+
ProgramClass.prototype.createLineMark = createLineMark;
|
|
391
395
|
ProgramClass.prototype.rematerializeLineMark = rematerializeLineMark;
|
|
392
396
|
}
|
|
@@ -5,7 +5,11 @@ import { rematerializePointMark } from "./materialize.js";
|
|
|
5
5
|
export { registerPointJitterActions } from "./jitter.js";
|
|
6
6
|
|
|
7
7
|
export function registerPointMarkActions(ProgramClass) {
|
|
8
|
-
ProgramClass.prototype.createPointMark = createPointMark;
|
|
9
8
|
ProgramClass.prototype.editPointMark = editPointMark;
|
|
9
|
+
registerBasicPointMarkActions(ProgramClass);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function registerBasicPointMarkActions(ProgramClass) {
|
|
13
|
+
ProgramClass.prototype.createPointMark = createPointMark;
|
|
10
14
|
ProgramClass.prototype.rematerializePointMark = rematerializePointMark;
|
|
11
15
|
}
|
|
@@ -162,7 +162,11 @@ const editRectMark = action(
|
|
|
162
162
|
);
|
|
163
163
|
|
|
164
164
|
export function registerRectMarkActions(ProgramClass) {
|
|
165
|
-
ProgramClass.prototype.createRectMark = createRectMark;
|
|
166
165
|
ProgramClass.prototype.editRectMark = editRectMark;
|
|
166
|
+
registerBasicRectMarkActions(ProgramClass);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function registerBasicRectMarkActions(ProgramClass) {
|
|
170
|
+
ProgramClass.prototype.createRectMark = createRectMark;
|
|
167
171
|
ProgramClass.prototype.rematerializeRectMark = rematerializeRectMark;
|
|
168
172
|
}
|
|
@@ -4,8 +4,12 @@ import { rematerializeScale } from "./materialize.js";
|
|
|
4
4
|
import { setQuantitativeColorScale } from "./quantitativeColor.js";
|
|
5
5
|
|
|
6
6
|
export function registerScaleActions(ProgramClass) {
|
|
7
|
-
ProgramClass.prototype.createScale = createScale;
|
|
8
7
|
ProgramClass.prototype.editScale = editScale;
|
|
8
|
+
registerBasicScaleActions(ProgramClass);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function registerBasicScaleActions(ProgramClass) {
|
|
12
|
+
ProgramClass.prototype.createScale = createScale;
|
|
9
13
|
ProgramClass.prototype.rematerializeScale = rematerializeScale;
|
|
10
14
|
ProgramClass.prototype.setQuantitativeColorScale = setQuantitativeColorScale;
|
|
11
15
|
}
|
package/src/basic.js
ADDED
|
@@ -17,32 +17,43 @@ const DRAWERS = Object.freeze({
|
|
|
17
17
|
path: drawPathGraphic
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
20
|
+
const DRAWING_CONTEXT_METHODS = Object.freeze([
|
|
21
|
+
"save",
|
|
22
|
+
"restore",
|
|
23
|
+
"fillRect",
|
|
24
|
+
"beginPath",
|
|
25
|
+
"closePath",
|
|
26
|
+
"arc",
|
|
27
|
+
"fill",
|
|
28
|
+
"moveTo",
|
|
29
|
+
"lineTo",
|
|
30
|
+
"bezierCurveTo",
|
|
31
|
+
"setLineDash",
|
|
32
|
+
"stroke",
|
|
33
|
+
"translate",
|
|
34
|
+
"rotate",
|
|
35
|
+
"fillText"
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
function requireDrawingContext(context) {
|
|
39
|
+
if (context === null || typeof context !== "object") {
|
|
40
|
+
throw new TypeError("Canvas drawing requires a 2D context.");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const method of DRAWING_CONTEXT_METHODS) {
|
|
44
|
+
if (typeof context[method] !== "function") {
|
|
45
|
+
throw new TypeError(`Canvas context is missing ${method}().`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
40
49
|
|
|
50
|
+
function requireCanvasContext(context) {
|
|
41
51
|
if (context === null || typeof context !== "object" || !context.canvas) {
|
|
42
52
|
throw new TypeError("render requires a Canvas 2D context.");
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
|
|
55
|
+
requireDrawingContext(context);
|
|
56
|
+
for (const method of ["clearRect", "scale"]) {
|
|
46
57
|
if (typeof context[method] !== "function") {
|
|
47
58
|
throw new TypeError(`Canvas context is missing ${method}().`);
|
|
48
59
|
}
|
|
@@ -86,7 +97,7 @@ function enterNestedCanvas(context, id, canvas) {
|
|
|
86
97
|
fillCanvasBackground(context, id, canvas, width, height);
|
|
87
98
|
}
|
|
88
99
|
|
|
89
|
-
export function
|
|
100
|
+
export function requireProgramGraphicSpec(program) {
|
|
90
101
|
const graphicSpec = program?.graphicSpec;
|
|
91
102
|
|
|
92
103
|
if (
|
|
@@ -99,12 +110,10 @@ export function render(program, context, { pixelRatio = 1 } = {}) {
|
|
|
99
110
|
throw new TypeError("render requires a program with a graphicSpec.");
|
|
100
111
|
}
|
|
101
112
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (!Number.isFinite(pixelRatio) || pixelRatio <= 0) {
|
|
105
|
-
throw new RangeError("render pixelRatio must be a positive finite number.");
|
|
106
|
-
}
|
|
113
|
+
return graphicSpec;
|
|
114
|
+
}
|
|
107
115
|
|
|
116
|
+
export function resolveGraphicRenderTarget(graphicSpec) {
|
|
108
117
|
const { id: canvasId, object: canvas } =
|
|
109
118
|
requireSingleOrderedGraphicByType(graphicSpec, "canvas");
|
|
110
119
|
const width = requireFiniteProperty(canvas.properties ?? {}, "width", canvasId);
|
|
@@ -118,6 +127,65 @@ export function render(program, context, { pixelRatio = 1 } = {}) {
|
|
|
118
127
|
throw new Error("Canvas width and height must not be negative.");
|
|
119
128
|
}
|
|
120
129
|
|
|
130
|
+
return Object.freeze({
|
|
131
|
+
graphicSpec,
|
|
132
|
+
canvasId,
|
|
133
|
+
canvas,
|
|
134
|
+
width,
|
|
135
|
+
height
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function drawResolvedGraphicSpec(target, context) {
|
|
140
|
+
requireDrawingContext(context);
|
|
141
|
+
const { graphicSpec, canvasId, canvas, width, height } = target;
|
|
142
|
+
|
|
143
|
+
fillCanvasBackground(context, canvasId, canvas, width, height);
|
|
144
|
+
|
|
145
|
+
walkGraphicTreeEvents(graphicSpec, {
|
|
146
|
+
enter({ id, object }) {
|
|
147
|
+
if (id === canvasId) return;
|
|
148
|
+
if (object.type === "canvas") {
|
|
149
|
+
enterNestedCanvas(context, id, object);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (object.type === "collection") {
|
|
153
|
+
context.save();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (!Array.isArray(object.items)) {
|
|
157
|
+
drawConcreteGraphic(context, id, object);
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
item({ id, object, owner }) {
|
|
161
|
+
drawConcreteGraphic(context, id, {
|
|
162
|
+
...object,
|
|
163
|
+
type: object.type ?? owner.type
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
exit({ id, object }) {
|
|
167
|
+
if (
|
|
168
|
+
id !== canvasId &&
|
|
169
|
+
(object.type === "collection" || object.type === "canvas")
|
|
170
|
+
) {
|
|
171
|
+
context.restore();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function render(program, context, { pixelRatio = 1 } = {}) {
|
|
178
|
+
const graphicSpec = requireProgramGraphicSpec(program);
|
|
179
|
+
|
|
180
|
+
requireCanvasContext(context);
|
|
181
|
+
|
|
182
|
+
if (!Number.isFinite(pixelRatio) || pixelRatio <= 0) {
|
|
183
|
+
throw new RangeError("render pixelRatio must be a positive finite number.");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const target = resolveGraphicRenderTarget(graphicSpec);
|
|
187
|
+
const { width, height } = target;
|
|
188
|
+
|
|
121
189
|
context.canvas.width = Math.round(width * pixelRatio);
|
|
122
190
|
context.canvas.height = Math.round(height * pixelRatio);
|
|
123
191
|
if (
|
|
@@ -132,39 +200,7 @@ export function render(program, context, { pixelRatio = 1 } = {}) {
|
|
|
132
200
|
try {
|
|
133
201
|
context.scale(pixelRatio, pixelRatio);
|
|
134
202
|
context.clearRect(0, 0, width, height);
|
|
135
|
-
|
|
136
|
-
fillCanvasBackground(context, canvasId, canvas, width, height);
|
|
137
|
-
|
|
138
|
-
walkGraphicTreeEvents(graphicSpec, {
|
|
139
|
-
enter({ id, object }) {
|
|
140
|
-
if (id === canvasId) return;
|
|
141
|
-
if (object.type === "canvas") {
|
|
142
|
-
enterNestedCanvas(context, id, object);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
if (object.type === "collection") {
|
|
146
|
-
context.save();
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
if (!Array.isArray(object.items)) {
|
|
150
|
-
drawConcreteGraphic(context, id, object);
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
item({ id, object, owner }) {
|
|
154
|
-
drawConcreteGraphic(context, id, {
|
|
155
|
-
...object,
|
|
156
|
-
type: object.type ?? owner.type
|
|
157
|
-
});
|
|
158
|
-
},
|
|
159
|
-
exit({ id, object }) {
|
|
160
|
-
if (
|
|
161
|
-
id !== canvasId &&
|
|
162
|
-
(object.type === "collection" || object.type === "canvas")
|
|
163
|
-
) {
|
|
164
|
-
context.restore();
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
});
|
|
203
|
+
drawResolvedGraphicSpec(target, context);
|
|
168
204
|
} finally {
|
|
169
205
|
context.restore();
|
|
170
206
|
}
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import { requireFiniteProperty, requireStringProperty } from "./validation.js";
|
|
2
2
|
import { validateConcreteGraphicProperties } from
|
|
3
3
|
"../../grammar/schemas/concreteGraphic.js";
|
|
4
|
-
|
|
5
|
-
const MIN_CANVAS_NUMERIC_FONT_WEIGHT = 100;
|
|
6
|
-
const MAX_CANVAS_NUMERIC_FONT_WEIGHT = 900;
|
|
7
|
-
|
|
8
|
-
export function normalizeCanvasFontWeight(fontWeight) {
|
|
9
|
-
if (typeof fontWeight !== "number") return fontWeight;
|
|
10
|
-
const rounded = Math.round(fontWeight / 100) * 100;
|
|
11
|
-
return Math.min(
|
|
12
|
-
MAX_CANVAS_NUMERIC_FONT_WEIGHT,
|
|
13
|
-
Math.max(MIN_CANVAS_NUMERIC_FONT_WEIGHT, rounded)
|
|
14
|
-
);
|
|
15
|
-
}
|
|
4
|
+
import { normalizeRendererFontWeight } from "../text.js";
|
|
16
5
|
|
|
17
6
|
function drawText(context, child, collectionId) {
|
|
18
7
|
const properties = child.properties ?? {};
|
|
@@ -85,8 +74,8 @@ function drawText(context, child, collectionId) {
|
|
|
85
74
|
try {
|
|
86
75
|
context.fillStyle = fill;
|
|
87
76
|
context.globalAlpha = opacity;
|
|
88
|
-
const
|
|
89
|
-
context.font = `${
|
|
77
|
+
const rendererFontWeight = normalizeRendererFontWeight(fontWeight);
|
|
78
|
+
context.font = `${rendererFontWeight} ${fontSize}px ${fontFamily}`;
|
|
90
79
|
context.textAlign = textAlign;
|
|
91
80
|
context.textBaseline = textBaseline;
|
|
92
81
|
context.translate(x, y);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { PDFDocument } from "@napi-rs/canvas";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
drawResolvedGraphicSpec,
|
|
8
|
+
requireProgramGraphicSpec,
|
|
9
|
+
resolveGraphicRenderTarget
|
|
10
|
+
} from "./canvas/index.js";
|
|
11
|
+
|
|
12
|
+
const PDF_OPTIONS = new Set(["output", "metadata"]);
|
|
13
|
+
const PDF_METADATA = new Set([
|
|
14
|
+
"title",
|
|
15
|
+
"author",
|
|
16
|
+
"subject",
|
|
17
|
+
"keywords"
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
function requirePlainObject(value, label) {
|
|
21
|
+
if (
|
|
22
|
+
value === null ||
|
|
23
|
+
typeof value !== "object" ||
|
|
24
|
+
Array.isArray(value) ||
|
|
25
|
+
Object.getPrototypeOf(value) !== Object.prototype
|
|
26
|
+
) {
|
|
27
|
+
throw new TypeError(`${label} must be a plain object.`);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function requireClosedKeys(value, allowed, label) {
|
|
33
|
+
for (const key of Object.keys(value)) {
|
|
34
|
+
if (!allowed.has(key)) {
|
|
35
|
+
throw new TypeError(`${label} does not support option "${key}".`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function requireMetadata(metadata) {
|
|
41
|
+
if (metadata === undefined) return undefined;
|
|
42
|
+
requirePlainObject(metadata, "renderToPDF metadata");
|
|
43
|
+
requireClosedKeys(metadata, PDF_METADATA, "renderToPDF metadata");
|
|
44
|
+
|
|
45
|
+
const resolved = {};
|
|
46
|
+
for (const key of ["title", "author", "subject"]) {
|
|
47
|
+
const value = metadata[key];
|
|
48
|
+
if (
|
|
49
|
+
value !== undefined &&
|
|
50
|
+
(typeof value !== "string" || value.length === 0)
|
|
51
|
+
) {
|
|
52
|
+
throw new TypeError(
|
|
53
|
+
`renderToPDF metadata ${key} must be a non-empty string.`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (value !== undefined) resolved[key] = value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (metadata.keywords !== undefined) {
|
|
60
|
+
if (
|
|
61
|
+
!Array.isArray(metadata.keywords) ||
|
|
62
|
+
!metadata.keywords.every(keyword =>
|
|
63
|
+
typeof keyword === "string" && keyword.length > 0
|
|
64
|
+
)
|
|
65
|
+
) {
|
|
66
|
+
throw new TypeError(
|
|
67
|
+
"renderToPDF metadata keywords must be an array of non-empty strings."
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
resolved.keywords = metadata.keywords.join(", ");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return resolved;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function requirePDFOptions(options) {
|
|
77
|
+
requirePlainObject(options, "renderToPDF options");
|
|
78
|
+
requireClosedKeys(options, PDF_OPTIONS, "renderToPDF");
|
|
79
|
+
if (typeof options.output !== "string" || options.output.length === 0) {
|
|
80
|
+
throw new TypeError("renderToPDF requires a non-empty output path.");
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
output: resolve(options.output),
|
|
84
|
+
metadata: requireMetadata(options.metadata)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function renderPDFBuffer(target, metadata) {
|
|
89
|
+
const document = new PDFDocument(metadata);
|
|
90
|
+
const context = document.beginPage(target.width, target.height);
|
|
91
|
+
drawResolvedGraphicSpec(target, context);
|
|
92
|
+
document.endPage();
|
|
93
|
+
return document.close();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function renderToPDF(program, options = {}) {
|
|
97
|
+
const { output, metadata } = requirePDFOptions(options);
|
|
98
|
+
const graphicSpec = requireProgramGraphicSpec(program);
|
|
99
|
+
const target = resolveGraphicRenderTarget(graphicSpec);
|
|
100
|
+
const buffer = renderPDFBuffer(target, metadata);
|
|
101
|
+
|
|
102
|
+
await mkdir(dirname(output), { recursive: true });
|
|
103
|
+
await writeFile(output, buffer);
|
|
104
|
+
|
|
105
|
+
return Object.freeze({
|
|
106
|
+
output,
|
|
107
|
+
width: target.width,
|
|
108
|
+
height: target.height,
|
|
109
|
+
pages: 1,
|
|
110
|
+
bytes: buffer.length
|
|
111
|
+
});
|
|
112
|
+
}
|