@graphein/node 0.19.0 → 0.21.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/README.md +9 -0
- package/dist/index.d.ts +26 -2
- package/dist/index.js +56 -14
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -96,6 +96,15 @@ Convenience wrapper that returns only the PNG bytes.
|
|
|
96
96
|
| `fonts` | — | `{ path, family }[]` font files to register before rendering. |
|
|
97
97
|
| `quality` | `false` | `true` or `{maxPasses?:1\|2\|3}`. Initial/candidate/restoration share one budget. Budgets below 3 only evaluate the initial render because rollback capacity is reserved. |
|
|
98
98
|
|
|
99
|
+
`width` and `height` must be finite numbers from 1 to 8192 CSS pixels. `dpr`
|
|
100
|
+
must be finite from 0.25 to 4, and the final rasterized canvas
|
|
101
|
+
(`round(width*dpr) × round(height*dpr)`) is capped at 67,108,864 pixels.
|
|
102
|
+
Invalid or unsafe sizes throw a `RangeError` before any canvas is allocated.
|
|
103
|
+
Core still records backing-store evidence on reports; if a composing target has
|
|
104
|
+
to lower the effective DPR for safety, `report.evidence.backingStore` contains the
|
|
105
|
+
requested/effective DPR and raster size and the report includes a
|
|
106
|
+
`backing-store-clamped` info diagnostic.
|
|
107
|
+
|
|
99
108
|
### Fonts
|
|
100
109
|
|
|
101
110
|
`@napi-rs/canvas` uses system fonts by default. For pixel-perfect parity with the browser
|
package/dist/index.d.ts
CHANGED
|
@@ -83,6 +83,30 @@ declare class NodeRenderError extends Error {
|
|
|
83
83
|
readonly result: DraftResult;
|
|
84
84
|
constructor(result: DraftResult);
|
|
85
85
|
}
|
|
86
|
+
/** Hard bounds that keep headless renders from creating invalid or enormous canvases. */
|
|
87
|
+
declare const RENDER_LIMITS: Readonly<{
|
|
88
|
+
minWidth: 1;
|
|
89
|
+
maxWidth: 8192;
|
|
90
|
+
minHeight: 1;
|
|
91
|
+
maxHeight: 8192;
|
|
92
|
+
minDpr: 0.25;
|
|
93
|
+
maxDpr: 4;
|
|
94
|
+
maxPixels: 67108864;
|
|
95
|
+
}>;
|
|
96
|
+
interface ResolvedRenderImageOptions {
|
|
97
|
+
/** Logical width in CSS pixels. */
|
|
98
|
+
width: number;
|
|
99
|
+
/** Logical height in CSS pixels. */
|
|
100
|
+
height: number;
|
|
101
|
+
/** Device pixel ratio. */
|
|
102
|
+
dpr: number;
|
|
103
|
+
/** Rounded raster width in physical pixels. */
|
|
104
|
+
pixelWidth: number;
|
|
105
|
+
/** Rounded raster height in physical pixels. */
|
|
106
|
+
pixelHeight: number;
|
|
107
|
+
}
|
|
108
|
+
/** Validate and resolve render sizing options before allocating a canvas. */
|
|
109
|
+
declare function validateRenderImageOptions(options?: RenderImageOptions): ResolvedRenderImageOptions;
|
|
86
110
|
/**
|
|
87
111
|
* Render `spec` to a PNG buffer and its {@link RenderReport}, entirely in Node.
|
|
88
112
|
*
|
|
@@ -98,6 +122,6 @@ declare function renderChart(spec: AnySpec, options?: RenderImageOptions): NodeR
|
|
|
98
122
|
*/
|
|
99
123
|
declare function renderToPNG(spec: AnySpec, options?: RenderImageOptions): Buffer;
|
|
100
124
|
|
|
101
|
-
declare const VERSION = "0.
|
|
125
|
+
declare const VERSION = "0.21.0";
|
|
102
126
|
|
|
103
|
-
export { type FontFace, type FontRegistration, NodeRenderError, type NodeRenderResult, type RenderImageOptions, VERSION, renderChart, renderToPNG };
|
|
127
|
+
export { type FontFace, type FontRegistration, NodeRenderError, type NodeRenderResult, RENDER_LIMITS, type RenderImageOptions, type ResolvedRenderImageOptions, VERSION, renderChart, renderToPNG, validateRenderImageOptions };
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
5
5
|
// src/index.ts
|
|
6
6
|
import { createCanvas, GlobalFonts } from "@napi-rs/canvas";
|
|
7
7
|
import {
|
|
8
|
-
draft
|
|
8
|
+
draft
|
|
9
|
+
} from "graphein";
|
|
10
|
+
import {
|
|
11
|
+
computeBackingSize,
|
|
9
12
|
renderDashboardToContext,
|
|
10
13
|
renderToContext,
|
|
11
14
|
setMeasureContext
|
|
12
|
-
} from "graphein";
|
|
15
|
+
} from "graphein/internal";
|
|
13
16
|
var NodeRenderError = class extends Error {
|
|
14
17
|
constructor(result) {
|
|
15
18
|
super((result.renderError ?? result.errors.map((e) => `${e.path}: ${e.message}`).join("; ")) || "Graphein: no final render report is available.");
|
|
@@ -20,6 +23,37 @@ var NodeRenderError = class extends Error {
|
|
|
20
23
|
var DEFAULT_WIDTH = 800;
|
|
21
24
|
var DEFAULT_HEIGHT = 500;
|
|
22
25
|
var DEFAULT_DPR = 2;
|
|
26
|
+
var RENDER_LIMITS = Object.freeze({
|
|
27
|
+
minWidth: 1,
|
|
28
|
+
maxWidth: 8192,
|
|
29
|
+
minHeight: 1,
|
|
30
|
+
maxHeight: 8192,
|
|
31
|
+
minDpr: 0.25,
|
|
32
|
+
maxDpr: 4,
|
|
33
|
+
maxPixels: 67108864
|
|
34
|
+
});
|
|
35
|
+
function assertFiniteInRange(name, value, min, max, unit) {
|
|
36
|
+
if (!Number.isFinite(value) || value < min || value > max) {
|
|
37
|
+
throw new RangeError(`Graphein render option "${name}" must be a finite number between ${min} and ${max}${unit ? ` ${unit}` : ""}.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function validateRenderImageOptions(options = {}) {
|
|
41
|
+
const width = options.width ?? DEFAULT_WIDTH;
|
|
42
|
+
const height = options.height ?? DEFAULT_HEIGHT;
|
|
43
|
+
const dpr = options.dpr ?? DEFAULT_DPR;
|
|
44
|
+
assertFiniteInRange("width", width, RENDER_LIMITS.minWidth, RENDER_LIMITS.maxWidth, "CSS pixels");
|
|
45
|
+
assertFiniteInRange("height", height, RENDER_LIMITS.minHeight, RENDER_LIMITS.maxHeight, "CSS pixels");
|
|
46
|
+
assertFiniteInRange("dpr", dpr, RENDER_LIMITS.minDpr, RENDER_LIMITS.maxDpr, "");
|
|
47
|
+
const pixelWidth = Math.round(width * dpr);
|
|
48
|
+
const pixelHeight = Math.round(height * dpr);
|
|
49
|
+
const pixels = pixelWidth * pixelHeight;
|
|
50
|
+
if (!Number.isSafeInteger(pixels) || pixels > RENDER_LIMITS.maxPixels) {
|
|
51
|
+
throw new RangeError(
|
|
52
|
+
`Graphein render size ${pixelWidth}\xD7${pixelHeight} (${pixels.toLocaleString("en-US")} pixels) exceeds the ${RENDER_LIMITS.maxPixels.toLocaleString("en-US")}-pixel cap. Reduce width, height, or dpr.`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return { width, height, dpr, pixelWidth, pixelHeight };
|
|
56
|
+
}
|
|
23
57
|
function registerFonts(fonts) {
|
|
24
58
|
return (fonts ?? []).map((f) => {
|
|
25
59
|
try {
|
|
@@ -50,19 +84,18 @@ function withFontFindings(report, fonts) {
|
|
|
50
84
|
};
|
|
51
85
|
}
|
|
52
86
|
function renderChart(spec, options = {}) {
|
|
53
|
-
const width = options
|
|
54
|
-
const height = options.height ?? DEFAULT_HEIGHT;
|
|
55
|
-
const dpr = options.dpr ?? DEFAULT_DPR;
|
|
87
|
+
const { width, height, dpr } = validateRenderImageOptions(options);
|
|
56
88
|
const fontRegistrations = registerFonts(options.fonts);
|
|
57
|
-
const
|
|
58
|
-
const
|
|
89
|
+
const backing = computeBackingSize(width, height, dpr);
|
|
90
|
+
const pxW = backing.pixelWidth;
|
|
91
|
+
const pxH = backing.pixelHeight;
|
|
59
92
|
const canvas = createCanvas(pxW, pxH);
|
|
60
93
|
const ctx = canvas.getContext("2d");
|
|
61
|
-
ctx.scale(dpr, dpr);
|
|
94
|
+
ctx.scale(backing.dpr, backing.dpr);
|
|
62
95
|
const measureCtx = createCanvas(1, 1).getContext("2d");
|
|
63
96
|
const interactionCtx = createCanvas(pxW, pxH).getContext("2d");
|
|
64
|
-
interactionCtx.scale(dpr, dpr);
|
|
65
|
-
setMeasureContext(measureCtx);
|
|
97
|
+
interactionCtx.scale(backing.dpr, backing.dpr);
|
|
98
|
+
const restoreMeasureContext = setMeasureContext(measureCtx);
|
|
66
99
|
let report;
|
|
67
100
|
let effectiveSpec = spec;
|
|
68
101
|
let quality;
|
|
@@ -72,7 +105,14 @@ function renderChart(spec, options = {}) {
|
|
|
72
105
|
marks: ctx,
|
|
73
106
|
interaction: interactionCtx,
|
|
74
107
|
width,
|
|
75
|
-
height
|
|
108
|
+
height,
|
|
109
|
+
backingStore: {
|
|
110
|
+
requestedDpr: backing.requestedDpr,
|
|
111
|
+
effectiveDpr: backing.dpr,
|
|
112
|
+
pixelWidth: backing.pixelWidth,
|
|
113
|
+
pixelHeight: backing.pixelHeight,
|
|
114
|
+
clamped: backing.clamped
|
|
115
|
+
}
|
|
76
116
|
};
|
|
77
117
|
if (options.quality) {
|
|
78
118
|
const selected = draft(spec, { target, repair: false, quality: options.quality });
|
|
@@ -85,7 +125,7 @@ function renderChart(spec, options = {}) {
|
|
|
85
125
|
report = spec.type === "dashboard" ? renderDashboardToContext(target, spec) : renderToContext(target, spec);
|
|
86
126
|
}
|
|
87
127
|
} finally {
|
|
88
|
-
|
|
128
|
+
restoreMeasureContext();
|
|
89
129
|
}
|
|
90
130
|
if (quality) {
|
|
91
131
|
for (const iteration of quality.iterations) {
|
|
@@ -110,12 +150,14 @@ function renderChart(spec, options = {}) {
|
|
|
110
150
|
function renderToPNG(spec, options) {
|
|
111
151
|
return renderChart(spec, options).png;
|
|
112
152
|
}
|
|
113
|
-
var VERSION = "0.
|
|
153
|
+
var VERSION = "0.21.0";
|
|
114
154
|
export {
|
|
115
155
|
GlobalFonts,
|
|
116
156
|
NodeRenderError,
|
|
157
|
+
RENDER_LIMITS,
|
|
117
158
|
VERSION,
|
|
118
159
|
renderChart,
|
|
119
|
-
renderToPNG
|
|
160
|
+
renderToPNG,
|
|
161
|
+
validateRenderImageOptions
|
|
120
162
|
};
|
|
121
163
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@graphein/node` — headless Graphein rendering for Node.\n *\n * Render any canvas-backed {@link ChartSpec} to a PNG buffer **and** its\n * {@link RenderReport} with no browser and no JSDOM, so the whole\n * generate → validate → render → critique loop can run server-side (CI, agents,\n * email/PDF report assets). It wires `graphein`'s dependency-free\n * `renderToContext` to `@napi-rs/canvas`; the core engine stays zero-dependency.\n *\n * ```ts\n * import { renderChart } from '@graphein/node';\n * const { png, report } = renderChart(spec, { width: 900, height: 480, dpr: 2 });\n * if (!report.ok) console.warn(report.diagnostics);\n * await fs.writeFile('chart.png', png);\n * ```\n *\n * Every chart type rasterizes — kpi/table/matrix/slicers/dashboard render a\n * static canvas snapshot, so the whole catalog can be validated server-side.\n */\n\nimport { createCanvas, GlobalFonts } from '@napi-rs/canvas';\nimport {\n draft, renderDashboardToContext, renderToContext, setMeasureContext,\n type AnySpec, type ChartSpec, type DashboardSpec, type DraftResult,\n type AnyRenderReport, type DashboardRenderReport, type QualityOptions, type QualityResult, type RenderReport,\n type ValidationError,\n} from 'graphein';\n\n/** A font file to register before rendering, improving text fidelity. */\nexport interface FontFace {\n /** Absolute path to a `.ttf` / `.otf` / `.woff` file. */\n path: string;\n /**\n * The family name to register the font under. Match this to the family your\n * theme uses (Graphein's default is `Inter`) so text renders in that face.\n */\n family: string;\n}\n\n/** Options for {@link renderChart} / {@link renderToPNG}. */\nexport interface RenderImageOptions {\n /** Logical width in CSS pixels. Default `800`. */\n width?: number;\n /** Logical height in CSS pixels. Default `500`. */\n height?: number;\n /**\n * Device pixel ratio. The PNG is rasterized at `width * dpr × height * dpr`\n * for crisp output. Default `2`.\n */\n dpr?: number;\n /** Extra fonts to register before rendering. */\n fonts?: FontFace[];\n /** Opt in to core's presentation-default loop (at most three whole-visual render attempts). */\n quality?: boolean | QualityOptions;\n}\n\n/** Registration evidence for a caller-supplied font; failure is non-fatal. */\nexport interface FontRegistration {\n path: string;\n family: string;\n registered: boolean;\n message?: string;\n}\n\n/** The result of a headless render: the PNG bytes plus the render report. */\nexport interface NodeRenderResult<R extends AnyRenderReport = RenderReport> {\n /** PNG-encoded image bytes. */\n png: Buffer;\n /**\n * The same machine-readable diagnostics as `instance.report()` in the\n * browser — `ok`, mark/series/color counts, and any clipping / overlap /\n * contrast warnings. Lets an agent critique the chart with no vision model.\n */\n report: R;\n /** Pixel width of the PNG (`width * dpr`, rounded). */\n width: number;\n /** Pixel height of the PNG (`height * dpr`, rounded). */\n height: number;\n /** Effective spec corresponding to the final PNG and report. Input data is never transformed by quality mode. */\n spec: AnySpec;\n /** Core quality-loop evidence, present only when opted in. */\n quality?: QualityResult;\n /** Validation advisories retained when quality mode validates the spec; absent on the direct default path. */\n warnings?: ValidationError[];\n /** Outcome of each explicitly requested font registration. */\n fontRegistrations: FontRegistration[];\n}\n\n/** Opt-in validation/rendering failed, so no potentially mismatched PNG was encoded. */\nexport class NodeRenderError extends Error {\n constructor(readonly result: DraftResult) {\n super((result.renderError ?? result.errors.map((e) => `${e.path}: ${e.message}`).join('; ')) ||\n 'Graphein: no final render report is available.');\n this.name = 'NodeRenderError';\n }\n}\n\nconst DEFAULT_WIDTH = 800;\nconst DEFAULT_HEIGHT = 500;\nconst DEFAULT_DPR = 2;\n\nfunction registerFonts(fonts: FontFace[] | undefined): FontRegistration[] {\n return (fonts ?? []).map((f) => {\n try {\n const registered = !!GlobalFonts.registerFromPath(f.path, f.family);\n return { ...f, registered, ...(registered ? {} : { message: 'Font registration returned no font.' }) };\n } catch (error) {\n return { ...f, registered: false, message: error instanceof Error ? error.message : String(error) };\n }\n });\n}\n\nfunction withFontFindings<R extends AnyRenderReport>(report: R, fonts: FontRegistration[]): R {\n const failed = fonts.filter((f) => !f.registered);\n if (!failed.length) return report;\n return {\n ...report,\n ok: false,\n diagnostics: [\n ...report.diagnostics.filter((d) => d.severity === 'error'),\n ...failed.map((f) => ({\n code: 'font-fallback', severity: 'warning' as const,\n message: `Could not register \"${f.family}\"; text may use an available fallback font.`,\n details: { family: f.family, path: f.path, reason: f.message },\n hint: 'Supply a readable font file and register it under the family used by the theme.',\n })),\n ...report.diagnostics.filter((d) => d.severity !== 'error'),\n ],\n };\n}\n\n/**\n * Render `spec` to a PNG buffer and its {@link RenderReport}, entirely in Node.\n *\n * Supports the entire catalog: line, area, bar, scatter, box, pie, heatmap,\n * sankey, choropleth, combo, histogram, funnel, and the formerly DOM-only kpi,\n * table, matrix, slicers and dashboard (painted as a static canvas snapshot).\n */\nexport function renderChart(spec: DashboardSpec, options?: RenderImageOptions): NodeRenderResult<DashboardRenderReport>;\nexport function renderChart(spec: ChartSpec, options?: RenderImageOptions): NodeRenderResult;\nexport function renderChart(spec: AnySpec, options?: RenderImageOptions): NodeRenderResult<AnyRenderReport>;\nexport function renderChart(spec: AnySpec, options: RenderImageOptions = {}): NodeRenderResult<AnyRenderReport> {\n const width = options.width ?? DEFAULT_WIDTH;\n const height = options.height ?? DEFAULT_HEIGHT;\n const dpr = options.dpr ?? DEFAULT_DPR;\n\n const fontRegistrations = registerFonts(options.fonts);\n\n const pxW = Math.max(1, Math.round(width * dpr));\n const pxH = Math.max(1, Math.round(height * dpr));\n\n const canvas = createCanvas(pxW, pxH);\n const ctx = canvas.getContext('2d');\n ctx.scale(dpr, dpr);\n\n // A dedicated 1×1 context for text measurement so layout uses real font\n // metrics (not the SSR heuristic) without mutating the drawing context.\n const measureCtx = createCanvas(1, 1).getContext('2d');\n\n // The interaction layer is unused on the static path; give it its own canvas\n // so a stray hover-layer paint can never land on the marks canvas.\n const interactionCtx = createCanvas(pxW, pxH).getContext('2d');\n interactionCtx.scale(dpr, dpr);\n\n setMeasureContext(measureCtx as unknown as CanvasRenderingContext2D);\n let report: AnyRenderReport;\n let effectiveSpec = spec;\n let quality: QualityResult | undefined;\n let warnings: ValidationError[] | undefined;\n try {\n const target = {\n marks: ctx as unknown as CanvasRenderingContext2D,\n interaction: interactionCtx as unknown as CanvasRenderingContext2D,\n width,\n height,\n };\n if (options.quality) {\n const selected = draft(spec, { target, repair: false, quality: options.quality });\n if (!selected.report) throw new NodeRenderError(selected);\n report = selected.report;\n effectiveSpec = selected.spec as AnySpec;\n quality = selected.quality;\n warnings = selected.warnings;\n } else {\n report = spec.type === 'dashboard'\n ? renderDashboardToContext(target, spec)\n : renderToContext(target, spec);\n }\n } finally {\n setMeasureContext(null);\n }\n\n if (quality) {\n for (const iteration of quality.iterations) {\n if (iteration.report) iteration.report = withFontFindings(iteration.report, fontRegistrations);\n }\n report = quality.iterations.find((i) => i.pass === quality.selectedPass)?.report ?? report;\n if (!report.ok && quality.stopReason === 'satisfied') quality.stopReason = 'no-actions';\n } else {\n report = withFontFindings(report, fontRegistrations);\n }\n return {\n png: canvas.toBuffer('image/png'), report, spec: effectiveSpec, quality,\n ...(warnings ? { warnings } : {}),\n fontRegistrations, width: pxW, height: pxH,\n };\n}\n\n/**\n * Convenience wrapper around {@link renderChart} that returns only the PNG bytes.\n */\nexport function renderToPNG(spec: AnySpec, options?: RenderImageOptions): Buffer {\n return renderChart(spec, options).png;\n}\n\n/** Re-exported so callers can register fonts globally without a second dep. */\nexport { GlobalFonts };\nexport type {\n AnySpec, ChartSpec, DashboardSpec, QualityOptions, QualityResult,\n AnyRenderReport, DashboardRenderReport, RenderReport,\n ValidationError,\n} from 'graphein';\n\nexport const VERSION = '0.7.0';\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@graphein/node` — headless Graphein rendering for Node.\n *\n * Render any canvas-backed {@link ChartSpec} to a PNG buffer **and** its\n * {@link RenderReport} with no browser and no JSDOM, so the whole\n * generate → validate → render → critique loop can run server-side (CI, agents,\n * email/PDF report assets). It wires `graphein`'s dependency-free\n * `renderToContext` to `@napi-rs/canvas`; the core engine stays zero-dependency.\n *\n * ```ts\n * import { renderChart } from '@graphein/node';\n * const { png, report } = renderChart(spec, { width: 900, height: 480, dpr: 2 });\n * if (!report.ok) console.warn(report.diagnostics);\n * await fs.writeFile('chart.png', png);\n * ```\n *\n * Every chart type rasterizes — kpi/table/matrix/slicers/dashboard render a\n * static canvas snapshot, so the whole catalog can be validated server-side.\n */\n\nimport { createCanvas, GlobalFonts } from '@napi-rs/canvas';\nimport {\n draft,\n type AnySpec, type ChartSpec, type DashboardSpec, type DraftResult,\n type AnyRenderReport, type DashboardRenderReport, type QualityOptions, type QualityResult, type RenderReport,\n type ValidationError,\n} from 'graphein';\nimport {\n computeBackingSize,\n renderDashboardToContext,\n renderToContext,\n setMeasureContext,\n} from 'graphein/internal';\n\n/** A font file to register before rendering, improving text fidelity. */\nexport interface FontFace {\n /** Absolute path to a `.ttf` / `.otf` / `.woff` file. */\n path: string;\n /**\n * The family name to register the font under. Match this to the family your\n * theme uses (Graphein's default is `Inter`) so text renders in that face.\n */\n family: string;\n}\n\n/** Options for {@link renderChart} / {@link renderToPNG}. */\nexport interface RenderImageOptions {\n /** Logical width in CSS pixels. Default `800`. */\n width?: number;\n /** Logical height in CSS pixels. Default `500`. */\n height?: number;\n /**\n * Device pixel ratio. The PNG is rasterized at `width * dpr × height * dpr`\n * for crisp output. Default `2`.\n */\n dpr?: number;\n /** Extra fonts to register before rendering. */\n fonts?: FontFace[];\n /** Opt in to core's presentation-default loop (at most three whole-visual render attempts). */\n quality?: boolean | QualityOptions;\n}\n\n/** Registration evidence for a caller-supplied font; failure is non-fatal. */\nexport interface FontRegistration {\n path: string;\n family: string;\n registered: boolean;\n message?: string;\n}\n\n/** The result of a headless render: the PNG bytes plus the render report. */\nexport interface NodeRenderResult<R extends AnyRenderReport = RenderReport> {\n /** PNG-encoded image bytes. */\n png: Buffer;\n /**\n * The same machine-readable diagnostics as `instance.report()` in the\n * browser — `ok`, mark/series/color counts, and any clipping / overlap /\n * contrast warnings. Lets an agent critique the chart with no vision model.\n */\n report: R;\n /** Pixel width of the PNG (`width * dpr`, rounded). */\n width: number;\n /** Pixel height of the PNG (`height * dpr`, rounded). */\n height: number;\n /** Effective spec corresponding to the final PNG and report. Input data is never transformed by quality mode. */\n spec: AnySpec;\n /** Core quality-loop evidence, present only when opted in. */\n quality?: QualityResult;\n /** Validation advisories retained when quality mode validates the spec; absent on the direct default path. */\n warnings?: ValidationError[];\n /** Outcome of each explicitly requested font registration. */\n fontRegistrations: FontRegistration[];\n}\n\n/** Opt-in validation/rendering failed, so no potentially mismatched PNG was encoded. */\nexport class NodeRenderError extends Error {\n constructor(readonly result: DraftResult) {\n super((result.renderError ?? result.errors.map((e) => `${e.path}: ${e.message}`).join('; ')) ||\n 'Graphein: no final render report is available.');\n this.name = 'NodeRenderError';\n }\n}\n\nconst DEFAULT_WIDTH = 800;\nconst DEFAULT_HEIGHT = 500;\nconst DEFAULT_DPR = 2;\n\n/** Hard bounds that keep headless renders from creating invalid or enormous canvases. */\nexport const RENDER_LIMITS = Object.freeze({\n minWidth: 1,\n maxWidth: 8192,\n minHeight: 1,\n maxHeight: 8192,\n minDpr: 0.25,\n maxDpr: 4,\n maxPixels: 67_108_864,\n});\n\nexport interface ResolvedRenderImageOptions {\n /** Logical width in CSS pixels. */\n width: number;\n /** Logical height in CSS pixels. */\n height: number;\n /** Device pixel ratio. */\n dpr: number;\n /** Rounded raster width in physical pixels. */\n pixelWidth: number;\n /** Rounded raster height in physical pixels. */\n pixelHeight: number;\n}\n\nfunction assertFiniteInRange(name: string, value: number, min: number, max: number, unit: string): void {\n if (!Number.isFinite(value) || value < min || value > max) {\n throw new RangeError(`Graphein render option \"${name}\" must be a finite number between ${min} and ${max}${unit ? ` ${unit}` : ''}.`);\n }\n}\n\n/** Validate and resolve render sizing options before allocating a canvas. */\nexport function validateRenderImageOptions(options: RenderImageOptions = {}): ResolvedRenderImageOptions {\n const width = options.width ?? DEFAULT_WIDTH;\n const height = options.height ?? DEFAULT_HEIGHT;\n const dpr = options.dpr ?? DEFAULT_DPR;\n assertFiniteInRange('width', width, RENDER_LIMITS.minWidth, RENDER_LIMITS.maxWidth, 'CSS pixels');\n assertFiniteInRange('height', height, RENDER_LIMITS.minHeight, RENDER_LIMITS.maxHeight, 'CSS pixels');\n assertFiniteInRange('dpr', dpr, RENDER_LIMITS.minDpr, RENDER_LIMITS.maxDpr, '');\n const pixelWidth = Math.round(width * dpr);\n const pixelHeight = Math.round(height * dpr);\n const pixels = pixelWidth * pixelHeight;\n if (!Number.isSafeInteger(pixels) || pixels > RENDER_LIMITS.maxPixels) {\n throw new RangeError(\n `Graphein render size ${pixelWidth}×${pixelHeight} (${pixels.toLocaleString('en-US')} pixels) exceeds the ${RENDER_LIMITS.maxPixels.toLocaleString('en-US')}-pixel cap. Reduce width, height, or dpr.`,\n );\n }\n return { width, height, dpr, pixelWidth, pixelHeight };\n}\n\nfunction registerFonts(fonts: FontFace[] | undefined): FontRegistration[] {\n return (fonts ?? []).map((f) => {\n try {\n const registered = !!GlobalFonts.registerFromPath(f.path, f.family);\n return { ...f, registered, ...(registered ? {} : { message: 'Font registration returned no font.' }) };\n } catch (error) {\n return { ...f, registered: false, message: error instanceof Error ? error.message : String(error) };\n }\n });\n}\n\nfunction withFontFindings<R extends AnyRenderReport>(report: R, fonts: FontRegistration[]): R {\n const failed = fonts.filter((f) => !f.registered);\n if (!failed.length) return report;\n return {\n ...report,\n ok: false,\n diagnostics: [\n ...report.diagnostics.filter((d) => d.severity === 'error'),\n ...failed.map((f) => ({\n code: 'font-fallback', severity: 'warning' as const,\n message: `Could not register \"${f.family}\"; text may use an available fallback font.`,\n details: { family: f.family, path: f.path, reason: f.message },\n hint: 'Supply a readable font file and register it under the family used by the theme.',\n })),\n ...report.diagnostics.filter((d) => d.severity !== 'error'),\n ],\n };\n}\n\n/**\n * Render `spec` to a PNG buffer and its {@link RenderReport}, entirely in Node.\n *\n * Supports the entire catalog: line, area, bar, scatter, box, pie, heatmap,\n * sankey, choropleth, combo, histogram, funnel, and the formerly DOM-only kpi,\n * table, matrix, slicers and dashboard (painted as a static canvas snapshot).\n */\nexport function renderChart(spec: DashboardSpec, options?: RenderImageOptions): NodeRenderResult<DashboardRenderReport>;\nexport function renderChart(spec: ChartSpec, options?: RenderImageOptions): NodeRenderResult;\nexport function renderChart(spec: AnySpec, options?: RenderImageOptions): NodeRenderResult<AnyRenderReport>;\nexport function renderChart(spec: AnySpec, options: RenderImageOptions = {}): NodeRenderResult<AnyRenderReport> {\n const { width, height, dpr } = validateRenderImageOptions(options);\n\n const fontRegistrations = registerFonts(options.fonts);\n\n const backing = computeBackingSize(width, height, dpr);\n const pxW = backing.pixelWidth;\n const pxH = backing.pixelHeight;\n\n const canvas = createCanvas(pxW, pxH);\n const ctx = canvas.getContext('2d');\n ctx.scale(backing.dpr, backing.dpr);\n\n // A dedicated 1×1 context for text measurement so layout uses real font\n // metrics (not the SSR heuristic) without mutating the drawing context.\n const measureCtx = createCanvas(1, 1).getContext('2d');\n\n // The interaction layer is unused on the static path; give it its own canvas\n // so a stray hover-layer paint can never land on the marks canvas.\n const interactionCtx = createCanvas(pxW, pxH).getContext('2d');\n interactionCtx.scale(backing.dpr, backing.dpr);\n\n const restoreMeasureContext = setMeasureContext(measureCtx as unknown as CanvasRenderingContext2D);\n let report: AnyRenderReport;\n let effectiveSpec = spec;\n let quality: QualityResult | undefined;\n let warnings: ValidationError[] | undefined;\n try {\n const target = {\n marks: ctx as unknown as CanvasRenderingContext2D,\n interaction: interactionCtx as unknown as CanvasRenderingContext2D,\n width,\n height,\n backingStore: {\n requestedDpr: backing.requestedDpr,\n effectiveDpr: backing.dpr,\n pixelWidth: backing.pixelWidth,\n pixelHeight: backing.pixelHeight,\n clamped: backing.clamped,\n },\n };\n if (options.quality) {\n const selected = draft(spec, { target, repair: false, quality: options.quality });\n if (!selected.report) throw new NodeRenderError(selected);\n report = selected.report;\n effectiveSpec = selected.spec as AnySpec;\n quality = selected.quality;\n warnings = selected.warnings;\n } else {\n report = spec.type === 'dashboard'\n ? renderDashboardToContext(target, spec)\n : renderToContext(target, spec);\n }\n } finally {\n restoreMeasureContext();\n }\n\n if (quality) {\n for (const iteration of quality.iterations) {\n if (iteration.report) iteration.report = withFontFindings(iteration.report, fontRegistrations);\n }\n report = quality.iterations.find((i) => i.pass === quality.selectedPass)?.report ?? report;\n if (!report.ok && quality.stopReason === 'satisfied') quality.stopReason = 'no-actions';\n } else {\n report = withFontFindings(report, fontRegistrations);\n }\n return {\n png: canvas.toBuffer('image/png'), report, spec: effectiveSpec, quality,\n ...(warnings ? { warnings } : {}),\n fontRegistrations, width: pxW, height: pxH,\n };\n}\n\n/**\n * Convenience wrapper around {@link renderChart} that returns only the PNG bytes.\n */\nexport function renderToPNG(spec: AnySpec, options?: RenderImageOptions): Buffer {\n return renderChart(spec, options).png;\n}\n\n/** Re-exported so callers can register fonts globally without a second dep. */\nexport { GlobalFonts };\nexport type {\n AnySpec, ChartSpec, DashboardSpec, QualityOptions, QualityResult,\n AnyRenderReport, DashboardRenderReport, RenderReport,\n ValidationError,\n} from 'graphein';\n\nexport const VERSION = '0.21.0';\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphein/node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Headless Graphein rendering for Node — render any ChartSpec to a PNG buffer with a RenderReport, no browser required.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,14 +48,15 @@
|
|
|
48
48
|
"access": "public"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
|
-
"
|
|
51
|
+
"sync:version": "node ../core/scripts/sync-version.mjs .",
|
|
52
|
+
"build": "npm run sync:version && tsup",
|
|
52
53
|
"dev": "tsup --watch",
|
|
53
54
|
"test": "vitest run",
|
|
54
55
|
"typecheck": "tsc --noEmit",
|
|
55
|
-
"prepack": "tsup"
|
|
56
|
+
"prepack": "npm run sync:version && tsup"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
58
59
|
"@napi-rs/canvas": "^1.0.1",
|
|
59
|
-
"graphein": "^0.
|
|
60
|
+
"graphein": "^0.21.0"
|
|
60
61
|
}
|
|
61
62
|
}
|