ggaction 0.0.10 → 0.0.12
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 +97 -0
- package/README.md +5 -3
- package/knowledge/action-card.schema.json +1 -0
- package/knowledge/action-cards.json +1 -10941
- package/knowledge/action-cards.schema.json +1 -0
- package/knowledge/intent-taxonomy.json +1 -183
- package/knowledge/intent-taxonomy.schema.json +1 -0
- package/knowledge/mcp-resources.json +1 -95
- package/knowledge/mcp-resources.schema.json +1 -0
- package/knowledge/task-packet.schema.json +1 -159
- package/knowledge/task-resolver.js +1 -1230
- package/package.json +18 -2
- package/src/actions/boxPlots/edit.js +45 -298
- package/src/actions/boxPlots/options.js +46 -77
- package/src/actions/boxPlots/resolve.js +1 -1
- package/src/actions/charts/bar.js +3 -0
- package/src/actions/charts/heatmap.js +73 -83
- package/src/actions/charts/index.js +1 -1
- package/src/actions/charts/parallel.js +35 -1
- package/src/actions/charts/shared.js +20 -0
- package/src/actions/composition/actions.js +46 -49
- package/src/actions/data/bin2d.js +105 -2
- package/src/actions/data/box.js +17 -17
- package/src/actions/data/density.js +42 -78
- package/src/actions/data/gradientProfile.js +22 -52
- package/src/actions/data/horizon.js +16 -41
- package/src/actions/data/index.js +10 -10
- package/src/actions/data/interval.js +16 -38
- package/src/actions/data/intervalEdit.js +55 -0
- package/src/actions/data/regression.js +7 -17
- package/src/actions/data/shared.js +47 -0
- package/src/actions/data/timeUnit.js +14 -38
- package/src/actions/data/window.js +14 -38
- package/src/actions/distributions/revision.js +154 -34
- package/src/actions/encodings/angle.js +9 -10
- package/src/actions/encodings/appearance.js +17 -51
- package/src/actions/encodings/density/resolve.js +3 -1
- package/src/actions/encodings/density.js +1 -15
- package/src/actions/encodings/horizon/resolve.js +13 -3
- package/src/actions/encodings/horizon.js +8 -7
- package/src/actions/encodings/index.js +9 -9
- package/src/actions/encodings/offset.js +41 -45
- package/src/actions/encodings/parallel.js +9 -10
- package/src/actions/encodings/pathOrder.js +6 -13
- package/src/actions/encodings/position/apply.js +30 -0
- package/src/actions/encodings/position/policies/arc.js +23 -2
- package/src/actions/encodings/position/policies/index.js +4 -4
- package/src/actions/encodings/position/policies/line.js +47 -17
- package/src/actions/encodings/position/resolve.js +36 -7
- package/src/actions/encodings/ranged.js +18 -23
- package/src/actions/encodings/remove.js +15 -25
- package/src/actions/encodings/ruleAppearance.js +9 -24
- package/src/actions/encodings/shared.js +37 -0
- package/src/actions/encodings/strokeDash.js +8 -23
- package/src/actions/encodings/text.js +9 -10
- package/src/actions/errorBands/create.js +105 -39
- package/src/actions/errorBands/edit.js +89 -93
- package/src/actions/errorBands/index.js +1 -2
- package/src/actions/errorBars/create.js +212 -30
- package/src/actions/errorBars/edit.js +91 -48
- package/src/actions/errorBars/index.js +1 -2
- package/src/actions/facets/actions.js +45 -69
- package/src/actions/facets/derive.js +38 -53
- package/src/actions/facets/replay.js +0 -1
- package/src/actions/gradientPlots/components.js +59 -48
- package/src/actions/gradientPlots/edit.js +35 -216
- package/src/actions/gradientPlots/index.js +11 -10
- package/src/actions/gradientPlots/materialize.js +21 -35
- package/src/actions/gradientPlots/options.js +5 -7
- package/src/actions/gradientPlots/paint.js +9 -6
- package/src/actions/gradientPlots/resolve.js +1 -1
- package/src/actions/guides/axes/index.js +3 -3
- package/src/actions/guides/axes/labels.js +68 -26
- package/src/actions/guides/axes/lines.js +22 -2
- package/src/actions/guides/axes/policy.js +28 -8
- package/src/actions/guides/axes/ticks.js +27 -9
- package/src/actions/guides/axes/titles.js +113 -65
- package/src/actions/guides/grids/resolve.js +14 -0
- package/src/actions/guides/guides.js +2 -4
- package/src/actions/guides/index.js +2 -2
- package/src/actions/guides/legends/categorical/actions.js +44 -68
- package/src/actions/guides/legends/categorical/components.js +6 -1
- package/src/actions/guides/legends/categorical/index.js +2 -2
- package/src/actions/guides/legends/categorical/layout.js +148 -175
- package/src/actions/guides/legends/categorical/recipes.js +5 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -1
- package/src/actions/guides/legends/continuous/common.js +117 -66
- package/src/actions/guides/legends/continuous/gradient.js +38 -37
- package/src/actions/guides/legends/continuous/interval.js +97 -76
- package/src/actions/guides/legends/continuous/opacity.js +28 -38
- package/src/actions/guides/legends/edit.js +14 -1
- package/src/actions/guides/legends/index.js +1 -1
- package/src/actions/guides/legends/lane.js +2 -1
- package/src/actions/guides/legends/remove.js +8 -0
- package/src/actions/guides/legends/size.js +36 -43
- package/src/actions/guides/legends/strokeWidth.js +20 -26
- package/src/actions/guides/polar/axes/labels.js +17 -1
- package/src/actions/guides/polar/axes/shared.js +7 -1
- package/src/actions/guides/polar/axes/titles.js +19 -2
- package/src/actions/guides/polar/grids.js +4 -0
- package/src/actions/guides/polar/resolve.js +6 -0
- package/src/actions/index.js +13 -13
- package/src/actions/intervals/resolve.js +87 -117
- package/src/actions/marks/arc/actions.js +31 -36
- package/src/actions/marks/area/actions.js +23 -31
- package/src/actions/marks/index.js +6 -6
- package/src/actions/marks/line/actions.js +33 -52
- package/src/actions/marks/line/materialize.js +7 -5
- package/src/actions/marks/point/materialize.js +38 -16
- package/src/actions/marks/remove.js +21 -0
- package/src/actions/marks/rule/actions.js +79 -54
- package/src/actions/marks/shared.js +8 -4
- package/src/actions/marks/text/actions.js +14 -6
- package/src/actions/marks/text/layout.js +21 -45
- package/src/actions/marks/tick/actions.js +22 -21
- package/src/actions/primitives/createGraphics.js +4 -0
- package/src/actions/primitives/editGraphics.js +4 -0
- package/src/actions/primitives/index.js +3 -3
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +2 -2
- package/src/actions/primitives/semanticValidation/layer.js +1 -1
- package/src/actions/regression/edit.js +3 -43
- package/src/actions/scales/consumers/families.js +24 -4
- package/src/actions/scales/consumers/index.js +1 -1
- package/src/actions/scales/consumers/seriesLayout.js +17 -0
- package/src/actions/scales/definitions.js +100 -103
- package/src/actions/scales/materialize.js +5 -0
- package/src/actions/selection/actions.js +79 -121
- package/src/actions/titles/actions.js +48 -94
- package/src/actions/titles/resolve.js +14 -27
- package/src/actions/violinPlots/create.js +37 -44
- package/src/core/textMetrics.js +74 -37
- package/src/core/validation.js +18 -0
- package/src/core/vocabulary.js +2 -2
- package/src/grammar/aggregate.js +48 -28
- package/src/grammar/arcs.js +170 -32
- package/src/grammar/areaSeries.js +14 -16
- package/src/grammar/bin2d.js +37 -23
- package/src/grammar/boxPlot.js +14 -13
- package/src/grammar/categoricalDensity.js +14 -14
- package/src/grammar/categoryOrder.js +8 -14
- package/src/grammar/curveCommands.js +87 -20
- package/src/grammar/density.js +162 -141
- package/src/grammar/facets/index.js +14 -0
- package/src/grammar/facets/scales.js +7 -4
- package/src/grammar/gradientProfile.js +12 -15
- package/src/grammar/histogram.js +145 -83
- package/src/grammar/horizon.js +60 -23
- package/src/grammar/interval.js +14 -13
- package/src/grammar/lineSeries.js +91 -61
- package/src/grammar/numeric.js +290 -0
- package/src/grammar/paint.js +3 -4
- package/src/grammar/palettes.js +21 -32
- package/src/grammar/pathCommands.js +6 -0
- package/src/grammar/polarPaths.js +5 -5
- package/src/grammar/positionCompatibility.js +1 -1
- package/src/grammar/regression/derive.js +48 -9
- package/src/grammar/regression/models.js +248 -69
- package/src/grammar/regression/parameters.js +8 -0
- package/src/grammar/rules.js +2 -0
- package/src/grammar/scales/color.js +35 -26
- package/src/grammar/scales/continuous.js +71 -24
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/discretized.js +42 -12
- package/src/grammar/scales/fields.js +21 -4
- package/src/grammar/scales/ordinal.js +139 -60
- package/src/grammar/scales/temporal.js +40 -11
- package/src/grammar/scales/transformed.js +199 -52
- package/src/grammar/schemas/graphicBounds.js +270 -79
- package/src/grammar/seriesLayout.js +50 -18
- package/src/grammar/ticks.js +123 -53
- package/src/grammar/timeUnit.js +18 -8
- package/src/grammar/transforms.js +4 -1
- package/src/grammar/window.js +70 -57
- package/src/index.js +1 -1
- package/src/layout/canvas.js +4 -8
- package/src/layout/composition.js +39 -40
- package/src/layout/facets.js +55 -30
- package/src/layout/labels.js +91 -15
- package/src/layout/legend.js +22 -20
- package/src/layout/legendLane.js +54 -40
- package/src/layout/title.js +7 -7
- package/src/materialization/bars/aggregate.js +14 -40
- package/src/materialization/bars/grouped.js +7 -18
- package/src/materialization/bars/histogram.js +2 -19
- package/src/materialization/bars/ranged.js +20 -15
- package/src/materialization/bars/resolve.js +21 -0
- package/src/materialization/facetGuides/legacyCategorical.js +9 -2
- package/src/materialization/facetGuides/placement.js +49 -19
- package/src/materialization/facetGuides/preparation.js +35 -6
- package/src/materialization/facets.js +42 -28
- package/src/materialization/guides/resources.js +2 -1
- package/src/materialization/marks/capabilities.js +15 -4
- package/src/materialization/marks/index.js +29 -6
- package/src/materialization/rowEncoding.js +57 -1
- package/src/materialization/scales/policies/bar.js +61 -102
- package/src/materialization/scales/policies/binnedPosition.js +34 -0
- package/src/materialization/scales/policies/offset.js +54 -0
- package/src/materialization/scales/policies/series.js +13 -7
- package/src/materialization/scales/resolve.js +6 -9
- package/src/materialization/selection/items/arc.js +16 -2
- package/src/materialization/selection/items/bar.js +4 -2
- package/src/materialization/selection/items/common.js +21 -1
- package/src/materialization/selection/items/path.js +19 -2
- package/src/materialization/selection/items/point.js +4 -16
- package/src/materialization/selection/items/tick.js +13 -21
- package/src/materialization/selection/point.js +11 -4
- package/src/materialization/selection/styles.js +19 -27
- package/src/materialization/text.js +4 -1
- package/src/mcp/adapter.js +8 -3
- package/src/renderers/canvas/fill.js +2 -2
- package/src/renderers/canvas/index.js +35 -2
- package/src/renderers/canvas/native.js +160 -0
- package/src/renderers/pdf.js +14 -0
- package/src/renderers/svg.js +37 -4
- package/types/index.d.ts +3 -0
- package/types/program.d.ts +523 -131
- package/types/svg.d.ts +1 -0
- package/src/actions/errorBands/components.js +0 -81
- package/src/actions/errorBands/options.js +0 -39
- package/src/actions/errorBands/resolve.js +0 -28
- package/src/actions/errorBars/components.js +0 -62
- package/src/actions/errorBars/options.js +0 -58
- package/src/actions/errorBars/resolve.js +0 -25
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createPointShapeGraphic } from "../../grammar/pointShapes.js";
|
|
2
|
+
import { finiteMidpoint } from "../../grammar/numeric.js";
|
|
2
3
|
|
|
3
4
|
function pointCenterAndArea(child) {
|
|
4
5
|
const properties = child.properties;
|
|
@@ -19,17 +20,23 @@ function pointCenterAndArea(child) {
|
|
|
19
20
|
const points = properties.commands.filter(command =>
|
|
20
21
|
command.op === "M" || command.op === "L"
|
|
21
22
|
);
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
let left = Infinity;
|
|
24
|
+
let right = -Infinity;
|
|
25
|
+
let top = Infinity;
|
|
26
|
+
let bottom = -Infinity;
|
|
24
27
|
let twiceArea = 0;
|
|
25
28
|
for (let index = 0; index < points.length; index += 1) {
|
|
26
29
|
const current = points[index];
|
|
27
30
|
const next = points[(index + 1) % points.length];
|
|
31
|
+
left = Math.min(left, current.x);
|
|
32
|
+
right = Math.max(right, current.x);
|
|
33
|
+
top = Math.min(top, current.y);
|
|
34
|
+
bottom = Math.max(bottom, current.y);
|
|
28
35
|
twiceArea += current.x * next.y - next.x * current.y;
|
|
29
36
|
}
|
|
30
37
|
return {
|
|
31
|
-
x: (
|
|
32
|
-
y: (
|
|
38
|
+
x: finiteMidpoint(left, right),
|
|
39
|
+
y: finiteMidpoint(top, bottom),
|
|
33
40
|
area: Math.abs(twiceArea) / 2
|
|
34
41
|
};
|
|
35
42
|
}
|
|
@@ -29,6 +29,22 @@ function normalizeOffset(offset) {
|
|
|
29
29
|
return { x, y };
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function normalizeOptionalStroke(args, mark) {
|
|
33
|
+
const stroke = args.stroke === undefined
|
|
34
|
+
? undefined
|
|
35
|
+
: validateNonEmptyString(args.stroke, `${mark} highlight stroke`);
|
|
36
|
+
const strokeWidth = args.strokeWidth === undefined
|
|
37
|
+
? undefined
|
|
38
|
+
: validateNonNegativeFinite(
|
|
39
|
+
args.strokeWidth,
|
|
40
|
+
`${mark} highlight strokeWidth`
|
|
41
|
+
);
|
|
42
|
+
if (strokeWidth !== undefined && stroke === undefined) {
|
|
43
|
+
throw new Error(`${mark} highlight strokeWidth requires stroke.`);
|
|
44
|
+
}
|
|
45
|
+
return { stroke, strokeWidth };
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
export function normalizeDimOthers(dimOthers) {
|
|
33
49
|
if (dimOthers === undefined || dimOthers === false) return false;
|
|
34
50
|
if (dimOthers === true) return { opacity: DEFAULT_DIM_OPACITY };
|
|
@@ -58,15 +74,7 @@ export function normalizePointHighlightStyle(args) {
|
|
|
58
74
|
const opacity = args.opacity === undefined
|
|
59
75
|
? undefined
|
|
60
76
|
: validateUnitInterval(args.opacity, "Point highlight opacity");
|
|
61
|
-
const stroke = args
|
|
62
|
-
? undefined
|
|
63
|
-
: validateNonEmptyString(args.stroke, "Point highlight stroke");
|
|
64
|
-
const strokeWidth = args.strokeWidth === undefined
|
|
65
|
-
? undefined
|
|
66
|
-
: validateNonNegativeFinite(args.strokeWidth, "Point highlight strokeWidth");
|
|
67
|
-
if (strokeWidth !== undefined && stroke === undefined) {
|
|
68
|
-
throw new Error("Point highlight strokeWidth requires stroke.");
|
|
69
|
-
}
|
|
77
|
+
const { stroke, strokeWidth } = normalizeOptionalStroke(args, "Point");
|
|
70
78
|
return {
|
|
71
79
|
fill,
|
|
72
80
|
...(opacity === undefined ? {} : { opacity }),
|
|
@@ -96,15 +104,7 @@ function normalizeRectangularHighlightStyle(args, mark, { offset = false } = {})
|
|
|
96
104
|
const opacity = args.opacity === undefined
|
|
97
105
|
? undefined
|
|
98
106
|
: validateUnitInterval(args.opacity, `${mark} highlight opacity`);
|
|
99
|
-
const stroke = args
|
|
100
|
-
? undefined
|
|
101
|
-
: validateNonEmptyString(args.stroke, `${mark} highlight stroke`);
|
|
102
|
-
const strokeWidth = args.strokeWidth === undefined
|
|
103
|
-
? undefined
|
|
104
|
-
: validateNonNegativeFinite(args.strokeWidth, `${mark} highlight strokeWidth`);
|
|
105
|
-
if (strokeWidth !== undefined && stroke === undefined) {
|
|
106
|
-
throw new Error(`${mark} highlight strokeWidth requires stroke.`);
|
|
107
|
-
}
|
|
107
|
+
const { stroke, strokeWidth } = normalizeOptionalStroke(args, mark);
|
|
108
108
|
const hasAppearanceOverride = opacity !== undefined || stroke !== undefined ||
|
|
109
109
|
strokeWidth !== undefined || args.offset !== undefined;
|
|
110
110
|
const fill = explicitFill === undefined && hasAppearanceOverride
|
|
@@ -178,15 +178,7 @@ export function normalizeAreaHighlightStyle(args, mark = "Area") {
|
|
|
178
178
|
if (args.color !== undefined && args.fill !== undefined) {
|
|
179
179
|
throw new Error(`${mark} highlight accepts color or fill, not both.`);
|
|
180
180
|
}
|
|
181
|
-
const stroke = args
|
|
182
|
-
? undefined
|
|
183
|
-
: validateNonEmptyString(args.stroke, `${mark} highlight stroke`);
|
|
184
|
-
const strokeWidth = args.strokeWidth === undefined
|
|
185
|
-
? undefined
|
|
186
|
-
: validateNonNegativeFinite(args.strokeWidth, `${mark} highlight strokeWidth`);
|
|
187
|
-
if (strokeWidth !== undefined && stroke === undefined) {
|
|
188
|
-
throw new Error(`${mark} highlight strokeWidth requires stroke.`);
|
|
189
|
-
}
|
|
181
|
+
const { stroke, strokeWidth } = normalizeOptionalStroke(args, mark);
|
|
190
182
|
return {
|
|
191
183
|
fill: validateNonEmptyString(
|
|
192
184
|
args.fill ?? args.color ?? DEFAULT_COLORS.highlight,
|
|
@@ -104,7 +104,10 @@ function relativeLuminance(color) {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
function sourceTextConfig(config, source, item) {
|
|
107
|
-
if (
|
|
107
|
+
if (
|
|
108
|
+
!["arc", "rect"].includes(source.mark.type) ||
|
|
109
|
+
config.fillExplicit === true
|
|
110
|
+
) return config;
|
|
108
111
|
const luminance = relativeLuminance(item.properties.fill);
|
|
109
112
|
if (luminance === undefined) return config;
|
|
110
113
|
return {
|
package/src/mcp/adapter.js
CHANGED
|
@@ -28,7 +28,7 @@ export const OVERVIEW_URI = "ggaction://overview";
|
|
|
28
28
|
export const SEARCH_TOOL = Object.freeze({
|
|
29
29
|
name: SEARCH_TOOL_NAME,
|
|
30
30
|
title: "Search ggaction authoring knowledge",
|
|
31
|
-
description: "Resolve one complete ggaction chart request into a bounded ordered task packet with exact imports, authoring prerequisites,
|
|
31
|
+
description: "Resolve one complete ggaction chart request into a bounded ordered task packet with exact imports, authoring prerequisites, syntax-valid immutable steps, applied options, explicit placeholders and unmatched requirements, terminal unsupported constraints, and unresolved decisions.",
|
|
32
32
|
inputSchema: Object.freeze({
|
|
33
33
|
type: "object",
|
|
34
34
|
additionalProperties: false,
|
|
@@ -164,6 +164,7 @@ export function readKnowledgeResource(uri, { allowedDocs = [] } = {}) {
|
|
|
164
164
|
if (uri === OVERVIEW_URI) {
|
|
165
165
|
return jsonResource(uri, {
|
|
166
166
|
schemaVersion: resourcesArtifact.schemaVersion,
|
|
167
|
+
packageVersion: resourcesArtifact.packageVersion,
|
|
167
168
|
title: resourcesArtifact.overview.title,
|
|
168
169
|
text: resourcesArtifact.overview.text
|
|
169
170
|
});
|
|
@@ -184,6 +185,7 @@ export function readKnowledgeResource(uri, { allowedDocs = [] } = {}) {
|
|
|
184
185
|
if (!recipe) throw new KnowledgeResourceError(`Unknown ggaction recipe: ${id}`);
|
|
185
186
|
return jsonResource(uri, {
|
|
186
187
|
schemaVersion: resourcesArtifact.schemaVersion,
|
|
188
|
+
packageVersion: resourcesArtifact.packageVersion,
|
|
187
189
|
...recipe,
|
|
188
190
|
packet: searchGgaction(recipe.query)
|
|
189
191
|
});
|
|
@@ -201,6 +203,9 @@ export function readKnowledgeResource(uri, { allowedDocs = [] } = {}) {
|
|
|
201
203
|
throw new KnowledgeResourceError(`Unknown knowledge resource URI: ${uri}`);
|
|
202
204
|
}
|
|
203
205
|
|
|
204
|
-
if (cardsArtifact.schemaVersion !==
|
|
205
|
-
throw new Error("MCP action cards must use schemaVersion
|
|
206
|
+
if (cardsArtifact.schemaVersion !== 2 || resourcesArtifact.schemaVersion !== 2) {
|
|
207
|
+
throw new Error("MCP action cards and resources must use schemaVersion 2.");
|
|
208
|
+
}
|
|
209
|
+
if (resourcesArtifact.packageVersion !== cardsArtifact.packageVersion) {
|
|
210
|
+
throw new Error("MCP action cards and resources must use one packageVersion.");
|
|
206
211
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isLinearGradientPaint,
|
|
3
|
-
resolveLinearGradientCoordinates,
|
|
4
3
|
validateFillPaint
|
|
5
4
|
} from "../../grammar/paint.js";
|
|
5
|
+
import { resolveCanvasGradientCoordinates } from "./native.js";
|
|
6
6
|
|
|
7
7
|
export function applyCanvasFill(context, fill, bounds, graphicId) {
|
|
8
8
|
validateFillPaint(fill, `${graphicId}.fill`);
|
|
@@ -15,7 +15,7 @@ export function applyCanvasFill(context, fill, bounds, graphicId) {
|
|
|
15
15
|
`Graphic "${graphicId}" requires Canvas context createLinearGradient().`
|
|
16
16
|
);
|
|
17
17
|
}
|
|
18
|
-
const coordinates =
|
|
18
|
+
const coordinates = resolveCanvasGradientCoordinates(fill, bounds, graphicId);
|
|
19
19
|
const gradient = context.createLinearGradient(
|
|
20
20
|
coordinates.from.x,
|
|
21
21
|
coordinates.from.y,
|
|
@@ -4,6 +4,10 @@ import { drawPathGraphic } from "./path.js";
|
|
|
4
4
|
import { drawRectGraphic } from "./rect.js";
|
|
5
5
|
import { drawTextGraphic } from "./text.js";
|
|
6
6
|
import { requireFiniteProperty } from "./validation.js";
|
|
7
|
+
import {
|
|
8
|
+
preflightCanvasGraphicSpec,
|
|
9
|
+
requireCanvasNativeValue
|
|
10
|
+
} from "./native.js";
|
|
7
11
|
import {
|
|
8
12
|
requireSingleOrderedGraphicByType,
|
|
9
13
|
walkGraphicTreeEvents
|
|
@@ -34,6 +38,8 @@ const DRAWING_CONTEXT_METHODS = Object.freeze([
|
|
|
34
38
|
"rotate",
|
|
35
39
|
"fillText"
|
|
36
40
|
]);
|
|
41
|
+
const MAX_RASTER_DIMENSION = 32_767;
|
|
42
|
+
const MAX_RASTER_PIXELS = 16_777_216;
|
|
37
43
|
|
|
38
44
|
function requireDrawingContext(context) {
|
|
39
45
|
if (context === null || typeof context !== "object") {
|
|
@@ -182,12 +188,39 @@ export function render(program, context, { pixelRatio = 1 } = {}) {
|
|
|
182
188
|
if (!Number.isFinite(pixelRatio) || pixelRatio <= 0) {
|
|
183
189
|
throw new RangeError("render pixelRatio must be a positive finite number.");
|
|
184
190
|
}
|
|
191
|
+
requireCanvasNativeValue(pixelRatio, "render Canvas pixelRatio");
|
|
192
|
+
if (Math.fround(pixelRatio) === 0) {
|
|
193
|
+
throw new RangeError(
|
|
194
|
+
"render Canvas pixelRatio must remain positive at native precision."
|
|
195
|
+
);
|
|
196
|
+
}
|
|
185
197
|
|
|
186
198
|
const target = resolveGraphicRenderTarget(graphicSpec);
|
|
187
199
|
const { width, height } = target;
|
|
200
|
+
const physicalWidth = Math.max(1, Math.round(width * pixelRatio));
|
|
201
|
+
const physicalHeight = Math.max(1, Math.round(height * pixelRatio));
|
|
202
|
+
if (![physicalWidth, physicalHeight].every(Number.isSafeInteger)) {
|
|
203
|
+
throw new RangeError(
|
|
204
|
+
"render physical Canvas dimensions must be finite safe integers."
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (
|
|
208
|
+
physicalWidth > MAX_RASTER_DIMENSION ||
|
|
209
|
+
physicalHeight > MAX_RASTER_DIMENSION
|
|
210
|
+
) {
|
|
211
|
+
throw new RangeError(
|
|
212
|
+
`render physical Canvas dimensions must not exceed ${MAX_RASTER_DIMENSION}.`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (physicalWidth * physicalHeight > MAX_RASTER_PIXELS) {
|
|
216
|
+
throw new RangeError(
|
|
217
|
+
`render physical Canvas pixel count must not exceed ${MAX_RASTER_PIXELS}.`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
preflightCanvasGraphicSpec(target, pixelRatio);
|
|
188
221
|
|
|
189
|
-
context.canvas.width =
|
|
190
|
-
context.canvas.height =
|
|
222
|
+
context.canvas.width = physicalWidth;
|
|
223
|
+
context.canvas.height = physicalHeight;
|
|
191
224
|
if (
|
|
192
225
|
context.canvas.style !== null &&
|
|
193
226
|
typeof context.canvas.style === "object"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { resolveLinearGradientCoordinates } from "../../grammar/paint.js";
|
|
2
|
+
import { resolvePathCommandBounds } from
|
|
3
|
+
"../../grammar/schemas/graphicBounds.js";
|
|
4
|
+
import { walkGraphicTreeEvents } from
|
|
5
|
+
"../../grammar/schemas/graphicTree.js";
|
|
6
|
+
import { validateConcreteGraphicProperties } from
|
|
7
|
+
"../../grammar/schemas/concreteGraphic.js";
|
|
8
|
+
|
|
9
|
+
const MAX_NATIVE_VALUE = 16_777_216;
|
|
10
|
+
const NATIVE_PROPERTIES = {
|
|
11
|
+
canvas: ["x", "y", "width", "height"],
|
|
12
|
+
circle: ["x", "y", "radius", "strokeWidth"],
|
|
13
|
+
rect: ["x", "y", "width", "height", "strokeWidth"],
|
|
14
|
+
line: ["x1", "y1", "x2", "y2", "strokeWidth"],
|
|
15
|
+
path: ["strokeWidth"],
|
|
16
|
+
text: ["x", "y", "fontSize"]
|
|
17
|
+
};
|
|
18
|
+
const COMMAND_PROPERTIES = ["x", "y", "x1", "y1", "x2", "y2"];
|
|
19
|
+
|
|
20
|
+
export function requireCanvasNativeValue(value, label, scale = 1, offset = 0) {
|
|
21
|
+
const magnitude = Math.abs(value) + Math.abs(offset);
|
|
22
|
+
const scaled = magnitude * scale;
|
|
23
|
+
if (
|
|
24
|
+
!Number.isFinite(value) || magnitude > MAX_NATIVE_VALUE ||
|
|
25
|
+
!Number.isFinite(scaled) || scaled > MAX_NATIVE_VALUE
|
|
26
|
+
) {
|
|
27
|
+
throw new RangeError(
|
|
28
|
+
`${label} must be finite with magnitude <= ${MAX_NATIVE_VALUE}.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function requireValues(values, label, scale) {
|
|
34
|
+
for (const value of values) {
|
|
35
|
+
requireCanvasNativeValue(value, label, scale);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function requireProperties(properties, keys, label, scale, offset) {
|
|
40
|
+
for (const key of keys) {
|
|
41
|
+
const value = properties?.[key];
|
|
42
|
+
if (value !== undefined) {
|
|
43
|
+
requireCanvasNativeValue(
|
|
44
|
+
value,
|
|
45
|
+
label,
|
|
46
|
+
scale,
|
|
47
|
+
offset?.["xy".indexOf(key[0])]
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function resolveCanvasGradientCoordinates(fill, bounds, graphicId, scale = 1) {
|
|
54
|
+
const coordinates = resolveLinearGradientCoordinates(fill, bounds);
|
|
55
|
+
requireValues([
|
|
56
|
+
coordinates.from.x,
|
|
57
|
+
coordinates.from.y,
|
|
58
|
+
coordinates.to.x,
|
|
59
|
+
coordinates.to.y,
|
|
60
|
+
Math.hypot(
|
|
61
|
+
coordinates.to.x - coordinates.from.x,
|
|
62
|
+
coordinates.to.y - coordinates.from.y
|
|
63
|
+
)
|
|
64
|
+
],
|
|
65
|
+
`Graphic "${graphicId}" Canvas linear gradient geometry`,
|
|
66
|
+
scale
|
|
67
|
+
);
|
|
68
|
+
return coordinates;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function preflightGraphic(id, graphic, scale, type, offset) {
|
|
72
|
+
const properties = graphic.properties ?? {};
|
|
73
|
+
const label = `Graphic "${id}" Canvas native geometry`;
|
|
74
|
+
requireProperties(
|
|
75
|
+
properties,
|
|
76
|
+
NATIVE_PROPERTIES[type] ?? [],
|
|
77
|
+
label,
|
|
78
|
+
scale,
|
|
79
|
+
offset
|
|
80
|
+
);
|
|
81
|
+
if (properties.rotation !== undefined) {
|
|
82
|
+
requireCanvasNativeValue(properties.rotation, label);
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(properties.strokeDash)) {
|
|
85
|
+
requireValues(properties.strokeDash, label, scale);
|
|
86
|
+
}
|
|
87
|
+
const gradient = properties.fill?.type === "linear-gradient";
|
|
88
|
+
|
|
89
|
+
let bounds;
|
|
90
|
+
if (type === "canvas" || type === "rect" || type === "circle") {
|
|
91
|
+
const circle = type === "circle";
|
|
92
|
+
const width = circle ? properties.radius : properties.width;
|
|
93
|
+
const height = circle ? properties.radius : properties.height;
|
|
94
|
+
if ([properties.x, properties.y, width, height].every(Number.isFinite) &&
|
|
95
|
+
width >= 0 && height >= 0) {
|
|
96
|
+
bounds = {
|
|
97
|
+
left: properties.x - (circle ? width : 0),
|
|
98
|
+
right: properties.x + width,
|
|
99
|
+
top: properties.y - (circle ? height : 0),
|
|
100
|
+
bottom: properties.y + height
|
|
101
|
+
};
|
|
102
|
+
requireCanvasNativeValue(bounds.left, label, scale, offset?.[0]);
|
|
103
|
+
requireCanvasNativeValue(bounds.right, label, scale, offset?.[0]);
|
|
104
|
+
requireCanvasNativeValue(bounds.top, label, scale, offset?.[1]);
|
|
105
|
+
requireCanvasNativeValue(bounds.bottom, label, scale, offset?.[1]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (type === "path" && Array.isArray(properties.commands)) {
|
|
109
|
+
for (const command of properties.commands) {
|
|
110
|
+
requireProperties(command, COMMAND_PROPERTIES, label, scale, offset);
|
|
111
|
+
}
|
|
112
|
+
if (gradient) {
|
|
113
|
+
bounds = resolvePathCommandBounds(properties.commands);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (gradient && bounds) {
|
|
117
|
+
resolveCanvasGradientCoordinates(properties.fill, bounds, id, scale);
|
|
118
|
+
}
|
|
119
|
+
if (NATIVE_PROPERTIES[type] !== undefined) {
|
|
120
|
+
validateConcreteGraphicProperties(type, properties);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function preflightCanvasGraphicSpec(target, scale = 1) {
|
|
125
|
+
const offsets = [];
|
|
126
|
+
walkGraphicTreeEvents(target.graphicSpec, {
|
|
127
|
+
enter({ id, object }) {
|
|
128
|
+
let offset = offsets.at(-1);
|
|
129
|
+
if (object.type === "canvas") {
|
|
130
|
+
const parent = offset ?? [0, 0];
|
|
131
|
+
const properties = object.properties ?? {};
|
|
132
|
+
const x = id === target.canvasId ? 0 : properties.x;
|
|
133
|
+
const y = id === target.canvasId ? 0 : properties.y;
|
|
134
|
+
const effective = [parent[0] + x, parent[1] + y];
|
|
135
|
+
if (Number.isFinite(x) && Number.isFinite(y)) {
|
|
136
|
+
requireValues(
|
|
137
|
+
effective,
|
|
138
|
+
`Graphic "${id}" Canvas nested translation`,
|
|
139
|
+
scale
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
offsets.push(effective);
|
|
143
|
+
offset = id === target.canvasId ? undefined : parent;
|
|
144
|
+
}
|
|
145
|
+
preflightGraphic(id, object, scale, object.type, offset);
|
|
146
|
+
},
|
|
147
|
+
item({ id, object, owner }) {
|
|
148
|
+
preflightGraphic(
|
|
149
|
+
id,
|
|
150
|
+
object,
|
|
151
|
+
scale,
|
|
152
|
+
object.type ?? owner.type,
|
|
153
|
+
offsets.at(-1)
|
|
154
|
+
);
|
|
155
|
+
},
|
|
156
|
+
exit({ object }) {
|
|
157
|
+
if (object.type === "canvas") offsets.pop();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
package/src/renderers/pdf.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
requireProgramGraphicSpec,
|
|
9
9
|
resolveGraphicRenderTarget
|
|
10
10
|
} from "./canvas/index.js";
|
|
11
|
+
import { preflightCanvasGraphicSpec } from "./canvas/native.js";
|
|
11
12
|
|
|
12
13
|
const PDF_OPTIONS = new Set(["output", "metadata"]);
|
|
13
14
|
const PDF_METADATA = new Set([
|
|
@@ -16,6 +17,7 @@ const PDF_METADATA = new Set([
|
|
|
16
17
|
"subject",
|
|
17
18
|
"keywords"
|
|
18
19
|
]);
|
|
20
|
+
const MAX_PDF_DIMENSION = 16_777_216;
|
|
19
21
|
|
|
20
22
|
function requirePlainObject(value, label) {
|
|
21
23
|
if (
|
|
@@ -93,10 +95,22 @@ function renderPDFBuffer(target, metadata) {
|
|
|
93
95
|
return document.close();
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
function requirePDFPageDimensions(target) {
|
|
99
|
+
if (![target.width, target.height].every(value =>
|
|
100
|
+
Number.isSafeInteger(value) && value > 0 && value <= MAX_PDF_DIMENSION
|
|
101
|
+
)) {
|
|
102
|
+
throw new RangeError(
|
|
103
|
+
`PDF page dimensions must be positive integers no larger than ${MAX_PDF_DIMENSION}.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
96
108
|
export async function renderToPDF(program, options = {}) {
|
|
97
109
|
const { output, metadata } = requirePDFOptions(options);
|
|
98
110
|
const graphicSpec = requireProgramGraphicSpec(program);
|
|
99
111
|
const target = resolveGraphicRenderTarget(graphicSpec);
|
|
112
|
+
requirePDFPageDimensions(target);
|
|
113
|
+
preflightCanvasGraphicSpec(target);
|
|
100
114
|
const buffer = renderPDFBuffer(target, metadata);
|
|
101
115
|
|
|
102
116
|
await mkdir(dirname(output), { recursive: true });
|
package/src/renderers/svg.js
CHANGED
|
@@ -19,7 +19,8 @@ import {
|
|
|
19
19
|
import { normalizeRendererFontWeight } from "./text.js";
|
|
20
20
|
|
|
21
21
|
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
22
|
-
const SVG_OPTIONS = new Set(["title", "description"]);
|
|
22
|
+
const SVG_OPTIONS = new Set(["title", "description", "resourceNamespace"]);
|
|
23
|
+
const RESOURCE_NAMESPACE = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
23
24
|
const TEXT_ANCHORS = Object.freeze({
|
|
24
25
|
left: "start",
|
|
25
26
|
start: "start",
|
|
@@ -36,7 +37,28 @@ const DOMINANT_BASELINES = Object.freeze({
|
|
|
36
37
|
bottom: "text-after-edge"
|
|
37
38
|
});
|
|
38
39
|
|
|
40
|
+
function resourceNamespace(graphicSpec) {
|
|
41
|
+
const serialized = JSON.stringify(graphicSpec);
|
|
42
|
+
let hash = 0xcbf29ce484222325n;
|
|
43
|
+
for (let index = 0; index < serialized.length; index += 1) {
|
|
44
|
+
hash ^= BigInt(serialized.charCodeAt(index));
|
|
45
|
+
hash = BigInt.asUintN(64, hash * 0x100000001b3n);
|
|
46
|
+
}
|
|
47
|
+
return hash.toString(36);
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
function escapeText(value) {
|
|
51
|
+
for (const character of value) {
|
|
52
|
+
const point = character.codePointAt(0);
|
|
53
|
+
if (!(
|
|
54
|
+
point === 9 || point === 10 || point === 13 ||
|
|
55
|
+
(point >= 0x20 && point <= 0xd7ff) ||
|
|
56
|
+
(point >= 0xe000 && point <= 0xfffd) ||
|
|
57
|
+
(point >= 0x10000 && point <= 0x10ffff)
|
|
58
|
+
)) {
|
|
59
|
+
throw new TypeError("SVG output requires XML 1.0-compatible strings.");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
40
62
|
return value
|
|
41
63
|
.replaceAll("&", "&")
|
|
42
64
|
.replaceAll("<", "<")
|
|
@@ -122,7 +144,8 @@ function requireCanvasBackground(id, canvas) {
|
|
|
122
144
|
|
|
123
145
|
function addLinearGradient(state, fill, bounds, graphicId) {
|
|
124
146
|
const coordinates = resolveLinearGradientCoordinates(fill, bounds);
|
|
125
|
-
const id =
|
|
147
|
+
const id =
|
|
148
|
+
`ggaction-gradient-${state.resourceNamespace}-${state.gradientCount}`;
|
|
126
149
|
state.gradientCount += 1;
|
|
127
150
|
const stops = fill.stops.map(stop => element("stop", [
|
|
128
151
|
["offset", formatNumber(stop.offset)],
|
|
@@ -410,7 +433,7 @@ function openNestedCanvas(state, id, canvas) {
|
|
|
410
433
|
canvas,
|
|
411
434
|
{ nested: true }
|
|
412
435
|
);
|
|
413
|
-
const clipId = `ggaction-clip-${state.clipCount}`;
|
|
436
|
+
const clipId = `ggaction-clip-${state.resourceNamespace}-${state.clipCount}`;
|
|
414
437
|
state.clipCount += 1;
|
|
415
438
|
state.definitions.push(element("clipPath", [["id", clipId]], element(
|
|
416
439
|
"rect",
|
|
@@ -504,6 +527,14 @@ function requireSVGOptions(options) {
|
|
|
504
527
|
throw new TypeError(`renderToSVG ${key} must be a non-empty string.`);
|
|
505
528
|
}
|
|
506
529
|
}
|
|
530
|
+
if (
|
|
531
|
+
options.resourceNamespace !== undefined &&
|
|
532
|
+
!RESOURCE_NAMESPACE.test(options.resourceNamespace)
|
|
533
|
+
) {
|
|
534
|
+
throw new TypeError(
|
|
535
|
+
"renderToSVG resourceNamespace must start with a letter and contain only letters, numbers, _ or -."
|
|
536
|
+
);
|
|
537
|
+
}
|
|
507
538
|
return options;
|
|
508
539
|
}
|
|
509
540
|
|
|
@@ -529,7 +560,9 @@ export function renderToSVG(program, options = {}) {
|
|
|
529
560
|
const state = {
|
|
530
561
|
clipCount: 1,
|
|
531
562
|
definitions: [],
|
|
532
|
-
gradientCount: 1
|
|
563
|
+
gradientCount: 1,
|
|
564
|
+
resourceNamespace:
|
|
565
|
+
resolvedOptions.resourceNamespace ?? resourceNamespace(graphicSpec)
|
|
533
566
|
};
|
|
534
567
|
const body = serializeBody(
|
|
535
568
|
graphicSpec,
|
package/types/index.d.ts
CHANGED
|
@@ -157,6 +157,7 @@ export type {
|
|
|
157
157
|
MarkGraphicProperty,
|
|
158
158
|
Palette,
|
|
159
159
|
PaletteName,
|
|
160
|
+
NonPointQuantitativePositionScaleOptions,
|
|
160
161
|
PositionEncodingOptions,
|
|
161
162
|
YPositionEncodingOptions,
|
|
162
163
|
RulePositionEncodingOptions,
|
|
@@ -181,6 +182,8 @@ export type {
|
|
|
181
182
|
ScaleRange,
|
|
182
183
|
ScaleOptions,
|
|
183
184
|
ScaleType,
|
|
185
|
+
ShapeScaleOptions,
|
|
186
|
+
SizeScaleOptions,
|
|
184
187
|
StackMode,
|
|
185
188
|
YStackMode,
|
|
186
189
|
SemanticCoordinate,
|