freshcoat 0.1.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/LICENSE +202 -0
- package/NOTICE +17 -0
- package/README.md +110 -0
- package/package.json +53 -0
- package/src/adjust.d.ts +18 -0
- package/src/adjust.js +153 -0
- package/src/approx-layout.d.ts +2 -0
- package/src/approx-layout.js +91 -0
- package/src/bake-text.d.ts +12 -0
- package/src/bake-text.js +321 -0
- package/src/browser.d.ts +7 -0
- package/src/browser.js +79 -0
- package/src/canvaskit.d.ts +4 -0
- package/src/canvaskit.js +1674 -0
- package/src/compile-scene.d.ts +24 -0
- package/src/compile-scene.js +325 -0
- package/src/css.d.ts +5 -0
- package/src/css.js +153 -0
- package/src/decode.d.ts +22 -0
- package/src/decode.js +112 -0
- package/src/export-scale.d.ts +25 -0
- package/src/export-scale.js +74 -0
- package/src/font-bytes.d.ts +4 -0
- package/src/font-bytes.js +165 -0
- package/src/font-metrics.d.ts +5 -0
- package/src/font-metrics.js +62 -0
- package/src/headless.d.ts +24 -0
- package/src/headless.js +58 -0
- package/src/index.d.ts +25 -0
- package/src/index.js +29 -0
- package/src/jpeg.d.ts +5 -0
- package/src/jpeg.js +20 -0
- package/src/line-height.d.ts +7 -0
- package/src/line-height.js +80 -0
- package/src/node.d.ts +139 -0
- package/src/node.js +47 -0
- package/src/paint-cache.d.ts +49 -0
- package/src/paint-cache.js +109 -0
- package/src/paint-helpers.d.ts +21 -0
- package/src/paint-helpers.js +71 -0
- package/src/paragraph-layout.d.ts +6 -0
- package/src/paragraph-layout.js +283 -0
- package/src/path-data.d.ts +5 -0
- package/src/path-data.js +101 -0
- package/src/png.d.ts +23 -0
- package/src/png.js +189 -0
- package/src/resolve-layout.d.ts +9 -0
- package/src/resolve-layout.js +657 -0
- package/src/runtime.d.ts +6 -0
- package/src/runtime.js +44 -0
- package/src/squircle.d.ts +1 -0
- package/src/squircle.js +60 -0
- package/src/text-cache.d.ts +14 -0
- package/src/text-cache.js +46 -0
- package/src/text-engine.d.ts +26 -0
- package/src/text-engine.js +1 -0
- package/src/text-types.d.ts +30 -0
- package/src/text-types.js +1 -0
- package/src/types.d.ts +406 -0
- package/src/types.js +13 -0
- package/src/validate-commands.d.ts +8 -0
- package/src/validate-commands.js +206 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
const MASK_KINDS = new Set([
|
|
2
|
+
"rect",
|
|
3
|
+
"rounded-rect",
|
|
4
|
+
"circle",
|
|
5
|
+
"ellipse",
|
|
6
|
+
"polygon",
|
|
7
|
+
"squircle",
|
|
8
|
+
]);
|
|
9
|
+
const finite = (v) => typeof v === "number" && Number.isFinite(v);
|
|
10
|
+
export function validateCommands(commands) {
|
|
11
|
+
const issues = [];
|
|
12
|
+
const add = (code, message, path, id) => issues.push(id ? { code, message, path, id } : { code, message, path });
|
|
13
|
+
const setups = commands.filter((c) => c.op === "createCanvas");
|
|
14
|
+
if (setups.length === 0)
|
|
15
|
+
add("missing_create_canvas", "no createCanvas command", "commands");
|
|
16
|
+
if (setups.length > 1)
|
|
17
|
+
add("duplicate_create_canvas", `${setups.length} createCanvas commands; a scene is one surface`, "commands");
|
|
18
|
+
if (setups.length > 0 && commands[0]?.op !== "createCanvas")
|
|
19
|
+
add("create_canvas_not_first", "createCanvas must be the first command", "commands[0]");
|
|
20
|
+
commands.forEach((cmd, i) => {
|
|
21
|
+
const path = `commands[${i}]`;
|
|
22
|
+
if (cmd.op === "createCanvas") {
|
|
23
|
+
if (!finite(cmd.width) || cmd.width <= 0)
|
|
24
|
+
add("bad_canvas_size", `width must be > 0, got ${cmd.width}`, `${path}.width`);
|
|
25
|
+
if (!finite(cmd.height) || cmd.height <= 0)
|
|
26
|
+
add("bad_canvas_size", `height must be > 0, got ${cmd.height}`, `${path}.height`);
|
|
27
|
+
if (cmd.scale !== undefined && (!finite(cmd.scale) || cmd.scale <= 0))
|
|
28
|
+
add("bad_scale", `scale must be > 0, got ${cmd.scale}`, `${path}.scale`);
|
|
29
|
+
if (cmd.supersample !== undefined &&
|
|
30
|
+
(!finite(cmd.supersample) || cmd.supersample < 1))
|
|
31
|
+
add("bad_supersample", `supersample must be >= 1, got ${cmd.supersample}`, `${path}.supersample`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (cmd.op === "finishFrame") {
|
|
35
|
+
validateFinish(cmd.finish, path, add);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (cmd.op === "loadFonts" || cmd.op === "loadImages")
|
|
39
|
+
return;
|
|
40
|
+
validateDrawable(cmd, path, add);
|
|
41
|
+
});
|
|
42
|
+
return issues;
|
|
43
|
+
}
|
|
44
|
+
function validateFinish(finish, path, add) {
|
|
45
|
+
const threshold = (v, name) => {
|
|
46
|
+
if (v === undefined)
|
|
47
|
+
return;
|
|
48
|
+
if (!finite(v) || v < 0 || v > 255)
|
|
49
|
+
add("bad_finish_threshold", `${name} must be within 0..255, got ${v}`, `${path}.finish.${name}`);
|
|
50
|
+
};
|
|
51
|
+
threshold(finish.whiteClamp, "whiteClamp");
|
|
52
|
+
threshold(finish.blackExtract, "blackExtract");
|
|
53
|
+
const amount = typeof finish.dither === "number" ? finish.dither : finish.dither?.amount;
|
|
54
|
+
if (amount !== undefined && !finite(amount))
|
|
55
|
+
add("bad_dither", `dither amount must be finite, got ${amount}`, `${path}.finish.dither`);
|
|
56
|
+
}
|
|
57
|
+
function validateDrawable(cmd, path, add) {
|
|
58
|
+
const id = cmd.id;
|
|
59
|
+
if (!finite(cmd.pos?.x) || !finite(cmd.pos?.y))
|
|
60
|
+
add("bad_pos", "pos.x and pos.y must be finite", `${path}.pos`, id);
|
|
61
|
+
if (!finite(cmd.size?.width) || !finite(cmd.size?.height))
|
|
62
|
+
add("bad_size", "size.width and size.height must be finite", `${path}.size`, id);
|
|
63
|
+
else if (cmd.size.width < 0 || cmd.size.height < 0)
|
|
64
|
+
add("bad_size", "size must not be negative", `${path}.size`, id);
|
|
65
|
+
if (cmd.opacity !== undefined &&
|
|
66
|
+
(!finite(cmd.opacity) || cmd.opacity < 0 || cmd.opacity > 1))
|
|
67
|
+
add("bad_opacity", `opacity must be within 0..1, got ${cmd.opacity}`, `${path}.opacity`, id);
|
|
68
|
+
if (cmd.rotation !== undefined && !finite(cmd.rotation))
|
|
69
|
+
add("bad_rotation", "rotation must be finite", `${path}.rotation`, id);
|
|
70
|
+
if (cmd.blur !== undefined && (!finite(cmd.blur) || cmd.blur < 0))
|
|
71
|
+
add("bad_blur", `blur must be >= 0, got ${cmd.blur}`, `${path}.blur`, id);
|
|
72
|
+
if (cmd.clip && !MASK_KINDS.has(cmd.clip.kind))
|
|
73
|
+
add("unknown_clip_kind", `unknown clip kind "${cmd.clip.kind}"`, `${path}.clip`, id);
|
|
74
|
+
if (cmd.clip?.kind === "polygon" &&
|
|
75
|
+
(!finite(cmd.clip.sides) || cmd.clip.sides < 3))
|
|
76
|
+
add("bad_polygon", `polygon needs >= 3 sides, got ${cmd.clip.sides}`, `${path}.clip.sides`, id);
|
|
77
|
+
for (const shadow of cmd.shadow
|
|
78
|
+
? Array.isArray(cmd.shadow)
|
|
79
|
+
? cmd.shadow
|
|
80
|
+
: [cmd.shadow]
|
|
81
|
+
: []) {
|
|
82
|
+
if (!finite(shadow.blur) || shadow.blur < 0)
|
|
83
|
+
add("bad_shadow", `shadow.blur must be >= 0, got ${shadow.blur}`, `${path}.shadow`, id);
|
|
84
|
+
if (!finite(shadow.dx) || !finite(shadow.dy))
|
|
85
|
+
add("bad_shadow", "shadow dx/dy must be finite", `${path}.shadow`, id);
|
|
86
|
+
}
|
|
87
|
+
if (cmd.adjust)
|
|
88
|
+
validateAdjust(cmd.adjust, `${path}.adjust`, add, id);
|
|
89
|
+
if ("fills" in cmd && cmd.fills)
|
|
90
|
+
cmd.fills.forEach((f, i) => {
|
|
91
|
+
validateFill(f, `${path}.fills[${i}]`, add, id);
|
|
92
|
+
});
|
|
93
|
+
if ("stroke" in cmd && cmd.stroke)
|
|
94
|
+
validateStroke(cmd.stroke, `${path}.stroke`, add, id);
|
|
95
|
+
if ("cornerRadius" in cmd && cmd.cornerRadius !== undefined)
|
|
96
|
+
validateCornerRadius(cmd.cornerRadius, `${path}.cornerRadius`, add, id);
|
|
97
|
+
switch (cmd.op) {
|
|
98
|
+
case "drawBitmap": {
|
|
99
|
+
const { pixelWidth: w, pixelHeight: h, pixels } = cmd;
|
|
100
|
+
if (!Number.isInteger(w) || w <= 0 || !Number.isInteger(h) || h <= 0)
|
|
101
|
+
add("bad_bitmap_size", `pixelWidth/pixelHeight must be positive integers, got ${w}x${h}`, `${path}`, id);
|
|
102
|
+
else if (pixels.length !== w * h * 4)
|
|
103
|
+
add("bitmap_buffer_mismatch", `pixels.length ${pixels.length} != pixelWidth*pixelHeight*4 (${w * h * 4})`, `${path}.pixels`, id);
|
|
104
|
+
if (cmd.role !== undefined && cmd.role !== "barcode")
|
|
105
|
+
add("bad_bitmap_role", `role must be "barcode" when set, got ${String(cmd.role)}`, `${path}.role`, id);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case "drawPath": {
|
|
109
|
+
if (!cmd.d)
|
|
110
|
+
add("empty_path", "path `d` is empty", `${path}.d`, id);
|
|
111
|
+
if (cmd.viewBox) {
|
|
112
|
+
const { width, height } = cmd.viewBox;
|
|
113
|
+
if (!finite(width) || width <= 0 || !finite(height) || height <= 0)
|
|
114
|
+
add("bad_viewbox", `viewBox must have positive width/height, got ${width}x${height}`, `${path}.viewBox`, id);
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "drawImage":
|
|
119
|
+
if (!cmd.src)
|
|
120
|
+
add("empty_image_src", "image src is empty", `${path}.src`, id);
|
|
121
|
+
break;
|
|
122
|
+
case "drawText":
|
|
123
|
+
if (!cmd.layout?.lines)
|
|
124
|
+
add("missing_text_lines", "text layout has no lines", `${path}.layout`, id);
|
|
125
|
+
else
|
|
126
|
+
cmd.layout.lines.forEach((line, i) => {
|
|
127
|
+
if (!line.spans || line.spans.length === 0)
|
|
128
|
+
add("empty_text_line", `line ${i} has no spans`, `${path}.layout.lines[${i}]`, id);
|
|
129
|
+
if (line.baseline !== undefined && !finite(line.baseline))
|
|
130
|
+
add("bad_baseline", `line ${i} baseline is not finite`, `${path}.layout.lines[${i}].baseline`, id);
|
|
131
|
+
});
|
|
132
|
+
break;
|
|
133
|
+
case "drawGroup":
|
|
134
|
+
cmd.children.forEach((c, i) => {
|
|
135
|
+
validateDrawable(c, `${path}.children[${i}]`, add);
|
|
136
|
+
});
|
|
137
|
+
break;
|
|
138
|
+
case "drawMasked":
|
|
139
|
+
if (cmd.channel && cmd.channel !== "alpha" && cmd.channel !== "luminance")
|
|
140
|
+
add("unknown_mask_channel", `unknown mask channel "${cmd.channel}"`, `${path}.channel`, id);
|
|
141
|
+
validateDrawable(cmd.mask, `${path}.mask`, add);
|
|
142
|
+
cmd.children.forEach((c, i) => {
|
|
143
|
+
validateDrawable(c, `${path}.children[${i}]`, add);
|
|
144
|
+
});
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function validateAdjust(adjust, path, add, id) {
|
|
149
|
+
if (adjust.colorMatrix) {
|
|
150
|
+
if (adjust.colorMatrix.length !== 20)
|
|
151
|
+
add("bad_color_matrix", `colorMatrix must have 20 entries (4x5), got ${adjust.colorMatrix.length}`, `${path}.colorMatrix`, id);
|
|
152
|
+
if (!adjust.colorMatrix.every(finite))
|
|
153
|
+
add("bad_color_matrix", "colorMatrix has a non-finite entry", `${path}.colorMatrix`, id);
|
|
154
|
+
}
|
|
155
|
+
if (adjust.lut)
|
|
156
|
+
for (const ch of ["r", "g", "b"])
|
|
157
|
+
if (adjust.lut[ch]?.length !== 256)
|
|
158
|
+
add("bad_lut", `lut.${ch} must have 256 entries, got ${adjust.lut[ch]?.length}`, `${path}.lut.${ch}`, id);
|
|
159
|
+
if (adjust.lut3d) {
|
|
160
|
+
const { size, data } = adjust.lut3d;
|
|
161
|
+
if (!Number.isInteger(size) || size < 2)
|
|
162
|
+
add("bad_lut3d", `lut3d.size must be an integer >= 2, got ${size}`, `${path}.lut3d.size`, id);
|
|
163
|
+
else if (data.length !== size ** 3 * 3)
|
|
164
|
+
add("bad_lut3d", `lut3d.data.length ${data.length} != size^3*3 (${size ** 3 * 3})`, `${path}.lut3d.data`, id);
|
|
165
|
+
}
|
|
166
|
+
if (adjust.sharpen !== undefined && !finite(adjust.sharpen))
|
|
167
|
+
add("bad_sharpen", "sharpen must be finite", `${path}.sharpen`, id);
|
|
168
|
+
if (adjust.gamut &&
|
|
169
|
+
adjust.gamut !== "clip" &&
|
|
170
|
+
adjust.gamut !== "preserve-hue")
|
|
171
|
+
add("unknown_gamut", `unknown gamut mode "${adjust.gamut}"`, `${path}.gamut`, id);
|
|
172
|
+
}
|
|
173
|
+
function validateFill(fill, path, add, id) {
|
|
174
|
+
if (fill.kind === "solid")
|
|
175
|
+
return;
|
|
176
|
+
const stops = fill.stops;
|
|
177
|
+
if (!stops || stops.length === 0) {
|
|
178
|
+
add("empty_gradient", "gradient has no stops", `${path}.stops`, id);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
let previous = Number.NEGATIVE_INFINITY;
|
|
182
|
+
stops.forEach((stop, i) => {
|
|
183
|
+
if (!finite(stop.offset) || stop.offset < 0 || stop.offset > 1)
|
|
184
|
+
add("bad_stop_offset", `stop offset must be within 0..1, got ${stop.offset}`, `${path}.stops[${i}].offset`, id);
|
|
185
|
+
// Skia and every vector format read stops in order; an unsorted list is a
|
|
186
|
+
// different gradient in each, rather than an error in any.
|
|
187
|
+
if (stop.offset < previous)
|
|
188
|
+
add("unsorted_stops", `stop ${i} offset ${stop.offset} is below the previous ${previous}`, `${path}.stops[${i}].offset`, id);
|
|
189
|
+
previous = stop.offset;
|
|
190
|
+
});
|
|
191
|
+
if (fill.kind === "radial" && (!finite(fill.radius) || fill.radius <= 0))
|
|
192
|
+
add("bad_radius", `radial radius must be > 0, got ${fill.radius}`, `${path}.radius`, id);
|
|
193
|
+
}
|
|
194
|
+
function validateStroke(stroke, path, add, id) {
|
|
195
|
+
if (!finite(stroke.width) || stroke.width < 0)
|
|
196
|
+
add("bad_stroke_width", `stroke width must be >= 0, got ${stroke.width}`, `${path}.width`, id);
|
|
197
|
+
if (stroke.dash && !stroke.dash.every((d) => finite(d) && d >= 0))
|
|
198
|
+
add("bad_dash", "dash segments must be finite and >= 0", `${path}.dash`, id);
|
|
199
|
+
}
|
|
200
|
+
function validateCornerRadius(radius, path, add, id) {
|
|
201
|
+
const values = Array.isArray(radius) ? radius : [radius];
|
|
202
|
+
if (Array.isArray(radius) && radius.length !== 4)
|
|
203
|
+
add("bad_corner_radius", `per-corner radius needs 4 entries, got ${radius.length}`, path, id);
|
|
204
|
+
if (!values.every((v) => finite(v) && v >= 0))
|
|
205
|
+
add("bad_corner_radius", "corner radius entries must be finite and >= 0", path, id);
|
|
206
|
+
}
|