h17-sspdf 1.1.0 → 1.3.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/DOCUMENTATION.md +2 -2
- package/README.md +3 -3
- package/cli.js +1 -1
- package/core/plugin-chart.js +61 -24
- package/core/plugin-registry.js +1 -0
- package/examples/output/article.pdf +0 -0
- package/examples/output/board-brief.pdf +0 -0
- package/examples/output/certificate.pdf +0 -0
- package/examples/output/event-program.pdf +0 -0
- package/examples/output/financial-report.pdf +3612 -0
- package/examples/output/generic-document.pdf +0 -0
- package/examples/output/invoice-table.pdf +0 -0
- package/examples/output/invoice.pdf +0 -0
- package/examples/output/magazine.pdf +0 -0
- package/examples/output/newspaper-frontpage.pdf +0 -0
- package/examples/output/presentation.pdf +7994 -22
- package/examples/output/styled-invoice.pdf +0 -0
- package/examples/output/two-column.pdf +0 -0
- package/examples/sources/source-newspaper-frontpage.json +1 -1
- package/examples/sources/source-newspaper-hugopalma-arc.json +1 -1
- package/examples/sources/source-sspdf-story.json +11 -11
- package/examples/sources/source-two-column.json +4 -4
- package/package.json +8 -2
package/DOCUMENTATION.md
CHANGED
|
@@ -675,9 +675,9 @@ Renders two independent operation lists side by side. Each column gets half the
|
|
|
675
675
|
|
|
676
676
|
**Column width:** `(contentWidth - gutterMm) / 2`. Both columns are always equal width.
|
|
677
677
|
|
|
678
|
-
**Cursor after:** `max(col1EndY, col2EndY)`
|
|
678
|
+
**Cursor after:** `max(col1EndY, col2EndY)` - the bottom of the taller column.
|
|
679
679
|
|
|
680
|
-
Any operation type that works at the top level works inside columns. Tables, images, bullets, nested blocks
|
|
680
|
+
Any operation type that works at the top level works inside columns. Tables, images, bullets, nested blocks all work. Columns inside columns are supported (the margin stack is re-entrant).
|
|
681
681
|
|
|
682
682
|
### Inferred text operations
|
|
683
683
|
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Source JSON + Theme = PDF
|
|
|
20
20
|
npm install h17-sspdf
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Requires **Node.js 18 or newer**. The engine vendors a single self-contained UMD build of jsPDF (which bundles fflate, fast-png, iobuffer internally) plus Chart.js and chartjs-node-canvas. The
|
|
23
|
+
Requires **Node.js 18 or newer**. The engine vendors a single self-contained UMD build of jsPDF (which bundles fflate, fast-png, iobuffer internally) plus Chart.js and chartjs-node-canvas. The base install has no runtime dependencies. The optional chart plugin uses [`canvas`](https://www.npmjs.com/package/canvas) as an optional peer dependency.
|
|
24
24
|
|
|
25
25
|
## The problem it solves
|
|
26
26
|
|
|
@@ -199,7 +199,7 @@ A repeating pattern of `row` + `text` pairs. The row carries the label/value, th
|
|
|
199
199
|
"type": "quote",
|
|
200
200
|
"label": "news.pullquote",
|
|
201
201
|
"text": "When the format becomes a system instead of a template, agencies stop re-solving the same layout problem every week.",
|
|
202
|
-
"attribution": "
|
|
202
|
+
"attribution": "- Elena Ward, public records modernization lead",
|
|
203
203
|
"xMm": 22,
|
|
204
204
|
"maxWidthMm": 166
|
|
205
205
|
}
|
|
@@ -474,7 +474,7 @@ Claude Code skills for generating PDFs and themes are available in the `skills/`
|
|
|
474
474
|
- Single-line `row` cells, no multi-line column pairs
|
|
475
475
|
- `{{page}}` gives the current page number; `{{pages}}` (total page count) is not supported because keep-together rules make the final page count unpredictable until the last operation is laid out
|
|
476
476
|
- Charts require the `canvas` npm package (native C++ addon) for server-side rendering; everything else is zero native dependencies
|
|
477
|
-
- A `jspdf.umd.js` build is vendored for client-side/browser use. It bundles all dependencies internally but requires wiring up your own entry point
|
|
477
|
+
- A `jspdf.umd.js` build is vendored for client-side/browser use. It bundles all dependencies internally but requires wiring up your own entry point; `pdf-core.js` uses the Node build by default
|
|
478
478
|
|
|
479
479
|
---
|
|
480
480
|
|
package/cli.js
CHANGED
|
@@ -155,7 +155,7 @@ function collectChartOps(obj) {
|
|
|
155
155
|
obj.forEach((item) => charts.push(...collectChartOps(item)));
|
|
156
156
|
} else {
|
|
157
157
|
if (obj.type === "chart") charts.push(obj);
|
|
158
|
-
for (const key of ["operations", "sections", "content", "items", "children"]) {
|
|
158
|
+
for (const key of ["operations", "sections", "content", "items", "children", "column1", "column2"]) {
|
|
159
159
|
if (Array.isArray(obj[key])) charts.push(...collectChartOps(obj[key]));
|
|
160
160
|
}
|
|
161
161
|
if (obj.pageTemplates) {
|
package/core/plugin-chart.js
CHANGED
|
@@ -50,6 +50,10 @@
|
|
|
50
50
|
*/
|
|
51
51
|
|
|
52
52
|
let _ChartJSNodeCanvas = null;
|
|
53
|
+
const _rendererCache = new Map();
|
|
54
|
+
const MAX_RENDERER_CACHE_SIZE = 8;
|
|
55
|
+
const DEFAULT_CANVAS_WIDTH = 1600;
|
|
56
|
+
const DEFAULT_CANVAS_HEIGHT = 800;
|
|
53
57
|
|
|
54
58
|
function getCanvas() {
|
|
55
59
|
if (!_ChartJSNodeCanvas) {
|
|
@@ -64,6 +68,46 @@ function getCanvas() {
|
|
|
64
68
|
return _ChartJSNodeCanvas;
|
|
65
69
|
}
|
|
66
70
|
|
|
71
|
+
function getRenderer(width, height) {
|
|
72
|
+
const ChartJSNodeCanvas = getCanvas();
|
|
73
|
+
const key = `${width}x${height}`;
|
|
74
|
+
if (_rendererCache.has(key)) {
|
|
75
|
+
return _rendererCache.get(key);
|
|
76
|
+
}
|
|
77
|
+
if (_rendererCache.size >= MAX_RENDERER_CACHE_SIZE) {
|
|
78
|
+
const oldestKey = _rendererCache.keys().next().value;
|
|
79
|
+
_rendererCache.delete(oldestKey);
|
|
80
|
+
}
|
|
81
|
+
const renderer = new ChartJSNodeCanvas({
|
|
82
|
+
width,
|
|
83
|
+
height,
|
|
84
|
+
backgroundColour: 'transparent',
|
|
85
|
+
});
|
|
86
|
+
_rendererCache.set(key, renderer);
|
|
87
|
+
return renderer;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resolvePositiveNumber(value, fallback, name) {
|
|
91
|
+
if (value === undefined || value === null || value === '') {
|
|
92
|
+
return fallback;
|
|
93
|
+
}
|
|
94
|
+
const numberValue = Number(value);
|
|
95
|
+
if (!Number.isFinite(numberValue) || numberValue <= 0) {
|
|
96
|
+
throw new Error(`chart plugin: ${name} must be a positive number`);
|
|
97
|
+
}
|
|
98
|
+
return numberValue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function cloneChartValue(value) {
|
|
102
|
+
if (!value || typeof value !== 'object') return value;
|
|
103
|
+
if (Array.isArray(value)) return value.map(cloneChartValue);
|
|
104
|
+
const out = {};
|
|
105
|
+
for (const key of Object.keys(value)) {
|
|
106
|
+
out[key] = cloneChartValue(value[key]);
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
|
|
67
111
|
/**
|
|
68
112
|
* Pre-render the chart to a PNG buffer and cache it on the operation.
|
|
69
113
|
* Call this before renderDocument() for any source containing chart operations.
|
|
@@ -77,35 +121,28 @@ function getCanvas() {
|
|
|
77
121
|
*
|
|
78
122
|
* Supported token: {{v}} - replaced with the callback's first argument (value).
|
|
79
123
|
*/
|
|
80
|
-
function resolveCallbackTemplates(
|
|
81
|
-
if (!
|
|
82
|
-
if (Array.isArray(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
obj[key] = function (value) { return template.replace(/\{\{v\}\}/g, String(value)); };
|
|
124
|
+
function resolveCallbackTemplates(value) {
|
|
125
|
+
if (!value || typeof value !== 'object') return value;
|
|
126
|
+
if (Array.isArray(value)) return value.map(resolveCallbackTemplates);
|
|
127
|
+
|
|
128
|
+
const out = {};
|
|
129
|
+
for (const key of Object.keys(value)) {
|
|
130
|
+
if (key === 'callback' && typeof value[key] === 'string' && value[key].includes('{{v}}')) {
|
|
131
|
+
const template = value[key];
|
|
132
|
+
out[key] = function (callbackValue) {
|
|
133
|
+
return template.replace(/\{\{v\}\}/g, String(callbackValue));
|
|
134
|
+
};
|
|
92
135
|
} else {
|
|
93
|
-
|
|
136
|
+
out[key] = resolveCallbackTemplates(value[key]);
|
|
94
137
|
}
|
|
95
138
|
}
|
|
96
|
-
return
|
|
139
|
+
return out;
|
|
97
140
|
}
|
|
98
141
|
|
|
99
142
|
async function preRender(operation) {
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
const canvas = new ChartJSNodeCanvas({
|
|
105
|
-
width: canvasW,
|
|
106
|
-
height: canvasH,
|
|
107
|
-
backgroundColour: 'transparent',
|
|
108
|
-
});
|
|
143
|
+
const canvasW = resolvePositiveNumber(operation.canvasWidth, DEFAULT_CANVAS_WIDTH, 'canvasWidth');
|
|
144
|
+
const canvasH = resolvePositiveNumber(operation.canvasHeight, DEFAULT_CANVAS_HEIGHT, 'canvasHeight');
|
|
145
|
+
const canvas = getRenderer(canvasW, canvasH);
|
|
109
146
|
|
|
110
147
|
const options = resolveCallbackTemplates({
|
|
111
148
|
...(operation.options || {}),
|
|
@@ -115,7 +152,7 @@ async function preRender(operation) {
|
|
|
115
152
|
|
|
116
153
|
operation._buf = await canvas.renderToBuffer({
|
|
117
154
|
type: operation.chartType || 'bar',
|
|
118
|
-
data: operation.data
|
|
155
|
+
data: cloneChartValue(operation.data || { labels: [], datasets: [] }),
|
|
119
156
|
options,
|
|
120
157
|
});
|
|
121
158
|
}
|
package/core/plugin-registry.js
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|