imagescript 1.3.0 → 3.0.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.
Files changed (146) hide show
  1. package/LICENSE +4 -1
  2. package/README.md +64 -26
  3. package/THIRD_PARTY_NOTICES.md +74 -0
  4. package/dist/codecs/crc.d.ts +9 -0
  5. package/dist/codecs/crc.js +29 -0
  6. package/dist/codecs/exif.d.ts +11 -0
  7. package/dist/codecs/exif.js +27 -0
  8. package/dist/codecs/gif.d.ts +44 -0
  9. package/dist/codecs/gif.js +401 -0
  10. package/dist/codecs/jpeg.d.ts +35 -0
  11. package/dist/codecs/jpeg.js +980 -0
  12. package/dist/codecs/png.d.ts +44 -0
  13. package/dist/codecs/png.js +442 -0
  14. package/dist/codecs/quantize.d.ts +25 -0
  15. package/dist/codecs/quantize.js +302 -0
  16. package/dist/codecs/tiff.d.ts +6 -0
  17. package/dist/codecs/tiff.js +546 -0
  18. package/dist/codecs/zlib.d.ts +22 -0
  19. package/dist/codecs/zlib.js +607 -0
  20. package/dist/frame.d.ts +57 -0
  21. package/dist/frame.js +65 -0
  22. package/dist/gif.d.ts +44 -0
  23. package/dist/gif.js +93 -0
  24. package/dist/image-type.d.ts +20 -0
  25. package/dist/image-type.js +49 -0
  26. package/dist/image.d.ts +521 -0
  27. package/dist/image.js +1118 -0
  28. package/dist/index.d.ts +19 -0
  29. package/dist/index.js +13 -0
  30. package/dist/package.json +1 -0
  31. package/dist/render/font.d.ts +63 -0
  32. package/dist/render/font.js +857 -0
  33. package/dist/render/svg.d.ts +11 -0
  34. package/dist/render/svg.js +1717 -0
  35. package/dist/render/text.d.ts +61 -0
  36. package/dist/render/text.js +317 -0
  37. package/dist/text-layout.d.ts +46 -0
  38. package/dist/text-layout.js +35 -0
  39. package/dist/util/blur.d.ts +13 -0
  40. package/dist/util/blur.js +146 -0
  41. package/dist/util/mem.d.ts +10 -0
  42. package/dist/util/mem.js +23 -0
  43. package/dist/util/resample.d.ts +12 -0
  44. package/dist/util/resample.js +99 -0
  45. package/dist/util/utf8.d.ts +5 -0
  46. package/dist/util/utf8.js +55 -0
  47. package/dist/version.d.ts +1 -0
  48. package/dist/version.js +1 -0
  49. package/licenses/Apache-2.0.txt +202 -0
  50. package/licenses/README.ijg +260 -0
  51. package/package.json +31 -7
  52. package/ImageScript.d.ts +0 -235
  53. package/ImageScript.js +0 -1669
  54. package/bench/_.mjs +0 -166
  55. package/bench/all.mjs +0 -4
  56. package/bench/blur.mjs +0 -59
  57. package/bench/flip.mjs +0 -63
  58. package/bench/overlay.mjs +0 -35
  59. package/bench/package.json +0 -7
  60. package/bench/readme.md +0 -64
  61. package/bench/resize.mjs +0 -52
  62. package/codecs/LICENSE +0 -21
  63. package/codecs/lib.d.ts +0 -50
  64. package/codecs/lib.js +0 -6
  65. package/codecs/lib.mjs +0 -1
  66. package/codecs/node/bin/arm64-darwin.node +0 -0
  67. package/codecs/node/bin/arm64-linux.node +0 -0
  68. package/codecs/node/bin/x64-darwin.node +0 -0
  69. package/codecs/node/bin/x64-linux.node +0 -0
  70. package/codecs/node/bin/x64-win32.node +0 -0
  71. package/codecs/node/index.js +0 -3
  72. package/codecs/node/index.mjs +0 -6
  73. package/codecs/package.json +0 -14
  74. package/codecs/readme.md +0 -38
  75. package/codecs/tests/gif.js +0 -0
  76. package/codecs/tests/jpeg.js +0 -0
  77. package/codecs/tests/png.js +0 -0
  78. package/codecs/tests/results/gif/1024.nis.gif +0 -0
  79. package/codecs/tests/results/gif/1024.skyra.gif +0 -0
  80. package/codecs/tests/results/gif/256.nis.gif +0 -0
  81. package/codecs/tests/results/gif/256.skyra.gif +0 -0
  82. package/codecs/tests/results/jpeg/256.jpegjs.jpeg +0 -0
  83. package/codecs/tests/results/jpeg/256.nis.jpeg +0 -0
  84. package/codecs/tests/results/jpeg/256.sharp.jpeg +0 -0
  85. package/codecs/tests/results/jpeg/512.jpegjs.jpeg +0 -0
  86. package/codecs/tests/results/jpeg/512.nis.jpeg +0 -0
  87. package/codecs/tests/results/jpeg/512.sharp.jpeg +0 -0
  88. package/codecs/tests/results/jpeg/64.jpegjs.jpeg +0 -0
  89. package/codecs/tests/results/jpeg/64.nis.jpeg +0 -0
  90. package/codecs/tests/results/jpeg/64.sharp.jpeg +0 -0
  91. package/codecs/tests/results/png/256.jis.png +0 -0
  92. package/codecs/tests/results/png/256.nis.png +0 -0
  93. package/codecs/tests/results/png/256.sharp.png +0 -0
  94. package/codecs/tests/results/png/256.upng.png +0 -0
  95. package/codecs/tests/results/png/512.jis.png +0 -0
  96. package/codecs/tests/results/png/512.nis.png +0 -0
  97. package/codecs/tests/results/png/512.sharp.png +0 -0
  98. package/codecs/tests/results/png/512.upng.png +0 -0
  99. package/codecs/tests/results/png/64.jis.png +0 -0
  100. package/codecs/tests/results/png/64.nis.png +0 -0
  101. package/codecs/tests/results/png/64.sharp.png +0 -0
  102. package/codecs/tests/results/png/64.upng.png +0 -0
  103. package/codecs/tests/webp.js +0 -0
  104. package/codecs/wasm/bin/codecs.wasm +0 -0
  105. package/codecs/wasm/bin/simd.wasm +0 -0
  106. package/codecs/wasm/bundle/browser.js +0 -1
  107. package/codecs/wasm/index.js +0 -1
  108. package/codecs/wasm/index.mjs +0 -1
  109. package/png/README.md +0 -1
  110. package/png/node.js +0 -1137
  111. package/png/src/crc.mjs +0 -49
  112. package/png/src/mem.mjs +0 -24
  113. package/png/src/png.mjs +0 -254
  114. package/png/src/zlib.mjs +0 -618
  115. package/tsconfig.json +0 -21
  116. package/utils/mem.js +0 -26
  117. package/v2/codecs/magic.js +0 -25
  118. package/v2/framebuffer.d.ts +0 -67
  119. package/v2/framebuffer.js +0 -2352
  120. package/v2/framebuffer.mjs +0 -126
  121. package/v2/framebuffer.rs +0 -1264
  122. package/v2/index.d.ts +0 -84
  123. package/v2/index.js +0 -261
  124. package/v2/ops/blur.mjs +0 -303
  125. package/v2/ops/color.mjs +0 -277
  126. package/v2/ops/crop.mjs +0 -64
  127. package/v2/ops/fill.mjs +0 -33
  128. package/v2/ops/flip.mjs +0 -33
  129. package/v2/ops/index.mjs +0 -9
  130. package/v2/ops/iterator.mjs +0 -34
  131. package/v2/ops/overlay.mjs +0 -63
  132. package/v2/ops/resize.mjs +0 -166
  133. package/v2/ops/rotate.mjs +0 -117
  134. package/v2/util/mem.mjs +0 -24
  135. package/wasm/any/font.wasm +0 -0
  136. package/wasm/any/gif.wasm +0 -0
  137. package/wasm/any/jpeg.wasm +0 -0
  138. package/wasm/any/png.wasm +0 -0
  139. package/wasm/any/svg.wasm +0 -0
  140. package/wasm/any/tiff.wasm +0 -0
  141. package/wasm/node/font.js +0 -142
  142. package/wasm/node/gif.js +0 -76
  143. package/wasm/node/jpeg.js +0 -92
  144. package/wasm/node/png.js +0 -49
  145. package/wasm/node/svg.js +0 -56
  146. package/wasm/node/tiff.js +0 -47
@@ -0,0 +1,1717 @@
1
+ import { decode as decodeUTF8 } from "../util/utf8.js";
2
+ const UNSUPPORTED = new Set(['text', 'image', 'filter', 'pattern', 'marker', 'foreignObject', 'script', 'style', 'title', 'desc', 'metadata',
3
+ 'defs', 'clipPath', 'mask', 'linearGradient', 'radialGradient', 'stop', 'symbol', 'font', 'font-face', 'animate', 'animateTransform', 'animateMotion', 'set']);
4
+ const INHERITED = new Set(['fill', 'fill-opacity', 'fill-rule', 'stroke', 'stroke-width', 'stroke-opacity', 'stroke-linecap', 'stroke-linejoin',
5
+ 'stroke-miterlimit', 'stroke-dasharray', 'stroke-dashoffset', 'color', 'visibility', 'clip-rule', 'font-size']);
6
+ const SWITCHABLE = new Set(['svg', 'g', 'a', 'switch', 'use', 'path', 'rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'text', 'image', 'foreignObject']);
7
+ const PROPERTIES = new Set([...INHERITED, 'opacity', 'display', 'clip-path', 'mask', 'stop-color', 'stop-opacity', 'transform']);
8
+ const DEFAULTS = {
9
+ 'fill': 'black', 'fill-opacity': '1', 'fill-rule': 'nonzero', 'stroke': 'none', 'stroke-width': '1', 'stroke-opacity': '1',
10
+ 'stroke-linecap': 'butt', 'stroke-linejoin': 'miter', 'stroke-miterlimit': '4', 'stroke-dasharray': 'none', 'stroke-dashoffset': '0',
11
+ 'color': 'black', 'visibility': 'visible', 'clip-rule': 'nonzero', 'font-size': '16', 'opacity': '1', 'display': 'inline',
12
+ 'stop-color': 'black', 'stop-opacity': '1'
13
+ };
14
+ const SAMPLES = 16;
15
+ export function render(source, mode, size) {
16
+ if (typeof source !== 'string')
17
+ source = decodeUTF8(source);
18
+ const root = parseXML(source).children.find(node => node.name === 'svg' && node.svg);
19
+ if (!root)
20
+ throw new Error('SVG: no <svg> element');
21
+ const doc = new Document(root);
22
+ const viewBox = parseViewBox(root.attrs.viewBox);
23
+ const lengthOf = (value, fallback) => {
24
+ if (value === undefined || value === 'auto')
25
+ return fallback;
26
+ if (/%\s*$/.test(value))
27
+ return fallback * parseFloat(value) / 100;
28
+ return parseLength(value, null, 16);
29
+ };
30
+ const width = lengthOf(root.attrs.width, viewBox ? viewBox[2] : 100);
31
+ const height = lengthOf(root.attrs.height, viewBox ? viewBox[3] : 100);
32
+ if (!(width > 0) || !(height > 0))
33
+ throw new Error('SVG: invalid dimensions');
34
+ let outWidth, outHeight;
35
+ if (mode === 2) {
36
+ outWidth = Math.round(size);
37
+ outHeight = Math.round(height * size / width);
38
+ }
39
+ else if (mode === 3) {
40
+ outHeight = Math.round(size);
41
+ outWidth = Math.round(width * size / height);
42
+ }
43
+ else {
44
+ outWidth = Math.round(width * size);
45
+ outHeight = Math.round(height * size);
46
+ }
47
+ outWidth = Math.max(1, outWidth);
48
+ outHeight = Math.max(1, outHeight);
49
+ const canvas = new Layer(outWidth, outHeight);
50
+ let matrix0 = [outWidth / width, 0, 0, outHeight / height, 0, 0];
51
+ const rootStyle = doc.computeStyle(root, null);
52
+ if (rootStyle.transform)
53
+ matrix0 = multiply(matrix0, parseTransform(rootStyle.transform));
54
+ if (viewBox)
55
+ matrix0 = multiply(matrix0, viewBoxTransform(viewBox, width, height, root.attrs.preserveAspectRatio));
56
+ const viewport = viewBox ? { width: viewBox[2], height: viewBox[3] } : { width, height };
57
+ doc.renderNode(root, { matrix: matrix0, style: null, viewport, layer: canvas, depth: 0 }, true);
58
+ return { width: outWidth, height: outHeight, pixels: canvas.toRGBA() };
59
+ }
60
+ const SVG_NS = 'http://www.w3.org/2000/svg';
61
+ function parseXML(text) {
62
+ const root = { name: '#root', attrs: {}, children: [], text: '', svg: false };
63
+ const stack = [root];
64
+ const namespaces = [{ '': SVG_NS }];
65
+ const entities = { amp: '&', lt: '<', gt: '>', quot: '"', apos: '\'' };
66
+ const doctype = /<!DOCTYPE[^[>]*\[([\s\S]*?)\]\s*>/i.exec(text);
67
+ if (doctype) {
68
+ const custom = {};
69
+ for (const [, name, a, b] of doctype[1].matchAll(/<!ENTITY\s+([\w.-]+)\s+(?:"([^"]*)"|'([^']*)')\s*>/g))
70
+ custom[name] = a !== undefined ? a : b;
71
+ const limit = Math.max(1 << 20, text.length * 16);
72
+ let rest = text.slice(doctype.index + doctype[0].length);
73
+ for (let pass = 0; pass < 4; pass++) {
74
+ let size = rest.length;
75
+ rest = rest.replace(/&([\w.-]+);/g, (match, name) => {
76
+ if (!(name in custom))
77
+ return match;
78
+ size += custom[name].length - match.length;
79
+ if (size > limit)
80
+ throw new Error('SVG: entity expansion exceeds the size limit');
81
+ return custom[name];
82
+ });
83
+ }
84
+ text = text.slice(0, doctype.index) + rest;
85
+ }
86
+ const decode = (s) => s.replace(/&(#x[0-9a-f]+|#[0-9]+|[a-z_][\w.-]*);/gi, (match, name) => {
87
+ if (name[0] === '#')
88
+ return String.fromCodePoint(name[1] === 'x' || name[1] === 'X' ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10));
89
+ return name in entities ? entities[name] : match;
90
+ });
91
+ let i = 0;
92
+ while (i < text.length) {
93
+ if (text[i] !== '<') {
94
+ const end = text.indexOf('<', i);
95
+ stack[stack.length - 1].text += decode(text.slice(i, end === -1 ? text.length : end));
96
+ i = end === -1 ? text.length : end;
97
+ continue;
98
+ }
99
+ if (text.startsWith('<!--', i)) {
100
+ const end = text.indexOf('-->', i + 4);
101
+ i = end === -1 ? text.length : end + 3;
102
+ }
103
+ else if (text.startsWith('<![CDATA[', i)) {
104
+ const end = text.indexOf(']]>', i);
105
+ stack[stack.length - 1].text += text.slice(i + 9, end === -1 ? text.length : end);
106
+ i = end === -1 ? text.length : end + 3;
107
+ }
108
+ else if (text.startsWith('<!', i)) {
109
+ let depth = 0, j = i + 2;
110
+ for (; j < text.length; j++) {
111
+ if (text[j] === '[')
112
+ depth++;
113
+ else if (text[j] === ']')
114
+ depth--;
115
+ else if (text[j] === '>' && depth <= 0)
116
+ break;
117
+ }
118
+ i = j + 1;
119
+ }
120
+ else if (text.startsWith('<?', i)) {
121
+ const end = text.indexOf('?>', i);
122
+ i = end === -1 ? text.length : end + 2;
123
+ }
124
+ else if (text.startsWith('</', i)) {
125
+ const end = text.indexOf('>', i);
126
+ if (stack.length > 1) {
127
+ stack.pop();
128
+ namespaces.pop();
129
+ }
130
+ i = end === -1 ? text.length : end + 1;
131
+ }
132
+ else {
133
+ const match = /^<([^\s/>]+)/.exec(text.slice(i, i + 256));
134
+ if (!match) {
135
+ i++;
136
+ continue;
137
+ }
138
+ const qualified = match[1];
139
+ const node = { name: '', attrs: {}, children: [], text: '', svg: false };
140
+ i += match[0].length;
141
+ const attribute = /\s*([^\s=/>]+)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/y;
142
+ for (;;) {
143
+ if (/^\s*\/?>/.test(text.slice(i, i + 64)))
144
+ break;
145
+ attribute.lastIndex = i;
146
+ const a = attribute.exec(text);
147
+ if (!a)
148
+ break;
149
+ node.attrs[a[1]] = decode(a[2] !== undefined ? a[2] : a[3] !== undefined ? a[3] : a[4] !== undefined ? a[4] : '');
150
+ i = attribute.lastIndex;
151
+ }
152
+ const scope = Object.assign({}, namespaces[namespaces.length - 1]);
153
+ for (const name in node.attrs) {
154
+ if (name === 'xmlns')
155
+ scope[''] = node.attrs[name];
156
+ else if (name.startsWith('xmlns:'))
157
+ scope[name.slice(6)] = node.attrs[name];
158
+ }
159
+ const colon = qualified.indexOf(':');
160
+ const prefix = colon === -1 ? '' : qualified.slice(0, colon);
161
+ node.name = colon === -1 ? qualified : qualified.slice(colon + 1);
162
+ node.svg = scope[prefix] === SVG_NS;
163
+ const close = /\s*(\/?)>/y;
164
+ close.lastIndex = i;
165
+ const end = close.exec(text);
166
+ i = end ? close.lastIndex : text.length;
167
+ stack[stack.length - 1].children.push(node);
168
+ if (!end || !end[1]) {
169
+ stack.push(node);
170
+ namespaces.push(scope);
171
+ }
172
+ }
173
+ }
174
+ return root;
175
+ }
176
+ function parseStyleSheet(text) {
177
+ const rules = [];
178
+ text = text.replace(/\/\*[\s\S]*?\*\//g, '');
179
+ let order = 0;
180
+ const blocks = /([^{}@]+)\{([^{}]*)\}|@[^{;]*(\{(?:[^{}]*\{[^{}]*\})*[^{}]*\}|;)/g;
181
+ for (const [, selectors, body] of text.matchAll(blocks)) {
182
+ if (selectors === undefined)
183
+ continue;
184
+ const declarations = parseDeclarations(body);
185
+ for (const selector of selectors.split(',')) {
186
+ const parsed = parseSelector(selector.trim());
187
+ if (parsed)
188
+ rules.push({ ...parsed, order: order++, declarations });
189
+ }
190
+ }
191
+ return rules.sort((a, b) => a.specificity - b.specificity || a.order - b.order);
192
+ }
193
+ function parseSelector(selector) {
194
+ const tokens = selector.match(/\s*[>+~]\s*|\s+|[^\s>+~]+/g);
195
+ if (!tokens)
196
+ return null;
197
+ const compounds = [];
198
+ let combinator = null;
199
+ let specificity = 0;
200
+ for (const token of tokens) {
201
+ const trimmed = token.trim();
202
+ if (!trimmed || trimmed === '>' || trimmed === '+' || trimmed === '~') {
203
+ if (compounds.length)
204
+ combinator = trimmed || ' ';
205
+ continue;
206
+ }
207
+ const tests = [];
208
+ const part = /([a-zA-Z][\w-]*|\*)|#([\w-]+)|\.([\w-]+)|\[\s*([\w:-]+)\s*(?:([~|^$*]?=)\s*(?:"([^"]*)"|'([^']*)'|([^\]\s]*)))?\s*\]|:(first-child)|(.)/g;
209
+ for (const m of trimmed.matchAll(part)) {
210
+ if (m[10] !== undefined)
211
+ return null;
212
+ if (m[1]) {
213
+ if (m[1] !== '*') {
214
+ tests.push(node => node.name === m[1]);
215
+ specificity += 1;
216
+ }
217
+ }
218
+ else if (m[2]) {
219
+ tests.push(node => node.attrs.id === m[2]);
220
+ specificity += 100;
221
+ }
222
+ else if (m[3]) {
223
+ tests.push(node => (node.attrs.class || '').split(/\s+/).includes(m[3]));
224
+ specificity += 10;
225
+ }
226
+ else if (m[4]) {
227
+ const name = m[4], op = m[5], value = m[6] !== undefined ? m[6] : m[7] !== undefined ? m[7] : m[8];
228
+ tests.push(node => {
229
+ const v = node.attrs[name];
230
+ if (v === undefined)
231
+ return false;
232
+ if (!op)
233
+ return true;
234
+ if (op === '=')
235
+ return v === value;
236
+ if (op === '~=')
237
+ return v.split(/\s+/).includes(value);
238
+ if (op === '^=')
239
+ return v.startsWith(value);
240
+ if (op === '$=')
241
+ return v.endsWith(value);
242
+ if (op === '*=')
243
+ return v.includes(value);
244
+ return v === value || v.startsWith(value + '-');
245
+ });
246
+ specificity += 10;
247
+ }
248
+ else if (m[9]) {
249
+ tests.push(node => !node.parent || node.parent.children.find(c => c.name) === node);
250
+ specificity += 10;
251
+ }
252
+ }
253
+ compounds.push({ combinator: compounds.length ? combinator || ' ' : null, test: node => tests.every(t => t(node)) });
254
+ combinator = null;
255
+ }
256
+ if (!compounds.length)
257
+ return null;
258
+ const matchAt = (node, index) => {
259
+ if (!node || !compounds[index].test(node))
260
+ return false;
261
+ if (index === 0)
262
+ return true;
263
+ const { combinator } = compounds[index];
264
+ if (combinator === '>')
265
+ return matchAt(node.parent, index - 1);
266
+ if (combinator === '+' || combinator === '~') {
267
+ const siblings = node.parent ? node.parent.children : [];
268
+ const position = siblings.indexOf(node);
269
+ if (combinator === '+')
270
+ return matchAt(siblings[position - 1], index - 1);
271
+ return siblings.slice(0, position).some(sibling => matchAt(sibling, index - 1));
272
+ }
273
+ for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent)
274
+ if (matchAt(ancestor, index - 1))
275
+ return true;
276
+ return false;
277
+ };
278
+ return { specificity, match: node => matchAt(node, compounds.length - 1) };
279
+ }
280
+ function parseDeclarations(text) {
281
+ const declarations = { normal: {}, important: {} };
282
+ text = text.replace(/\/\*[\s\S]*?\*\//g, '');
283
+ for (const part of text.split(';')) {
284
+ const colon = part.indexOf(':');
285
+ if (colon === -1)
286
+ continue;
287
+ const name = part.slice(0, colon).trim().toLowerCase();
288
+ let value = part.slice(colon + 1).trim();
289
+ const important = /!\s*important\s*$/i.test(value);
290
+ if (important)
291
+ value = value.replace(/!\s*important\s*$/i, '').trim();
292
+ if (name && value)
293
+ (important ? declarations.important : declarations.normal)[name] = value;
294
+ }
295
+ return declarations;
296
+ }
297
+ function validProperty(name, value) {
298
+ switch (name) {
299
+ case 'fill':
300
+ case 'stroke':
301
+ return value === 'none' || value === 'currentColor' || value === 'inherit' || /^context-(fill|stroke)$/.test(value)
302
+ || /^url\(/.test(value) || parseColor(value) !== null;
303
+ case 'color':
304
+ case 'stop-color':
305
+ return value === 'currentColor' || value === 'inherit' || parseColor(value) !== null;
306
+ case 'opacity':
307
+ case 'fill-opacity':
308
+ case 'stroke-opacity':
309
+ case 'stop-opacity':
310
+ return value === 'inherit' || parseStrictNumber(value) !== null;
311
+ default:
312
+ return true;
313
+ }
314
+ }
315
+ class Document {
316
+ ids;
317
+ active;
318
+ root;
319
+ rules;
320
+ constructor(root) {
321
+ this.ids = new Map();
322
+ this.active = new Set();
323
+ const styles = [];
324
+ const walk = (node) => {
325
+ if (node.attrs.id && !this.ids.has(node.attrs.id))
326
+ this.ids.set(node.attrs.id, node);
327
+ if (node.name === 'style' && node.svg && (!node.attrs.type || node.attrs.type === 'text/css'))
328
+ styles.push(node.text);
329
+ for (const child of node.children) {
330
+ child.parent = node;
331
+ walk(child);
332
+ }
333
+ };
334
+ walk(root);
335
+ this.root = root;
336
+ this.rules = parseStyleSheet(styles.join('\n'));
337
+ }
338
+ reference(value) {
339
+ if (!value)
340
+ return null;
341
+ const match = /url\(\s*['"]?\s*#([^)'"]+?)\s*['"]?\s*\)/.exec(value) || /^\s*#(.+?)\s*$/.exec(value);
342
+ return match ? this.ids.get(match[1]) || null : null;
343
+ }
344
+ href(node) {
345
+ return node.attrs.href || node.attrs['xlink:href'];
346
+ }
347
+ computeStyle(node, parent) {
348
+ if (node.computed && node.computedParent === parent)
349
+ return node.computed;
350
+ const own = {};
351
+ const apply = (declarations) => {
352
+ for (const name in declarations)
353
+ if (validProperty(name, declarations[name]))
354
+ own[name] = declarations[name];
355
+ };
356
+ const presentation = {};
357
+ for (const name in node.attrs)
358
+ if (PROPERTIES.has(name))
359
+ presentation[name] = node.attrs[name];
360
+ apply(presentation);
361
+ const rules = this.rules.filter(rule => rule.match(node));
362
+ const inline = node.attrs.style ? parseDeclarations(node.attrs.style) : null;
363
+ for (const rule of rules)
364
+ apply(rule.declarations.normal);
365
+ if (inline)
366
+ apply(inline.normal);
367
+ for (const rule of rules)
368
+ apply(rule.declarations.important);
369
+ if (inline)
370
+ apply(inline.important);
371
+ const style = {};
372
+ for (const name of PROPERTIES) {
373
+ if (name === 'transform')
374
+ continue;
375
+ let value = own[name];
376
+ if (value === 'inherit' || (value === undefined && INHERITED.has(name)))
377
+ value = parent ? parent[name] : DEFAULTS[name];
378
+ if (value === undefined || value === 'initial')
379
+ value = DEFAULTS[name];
380
+ style[name] = value === undefined ? undefined : String(value).trim();
381
+ }
382
+ style['mask-type'] = own['mask-type'] || node.attrs['mask-type'];
383
+ style.transform = own.transform;
384
+ node.computed = style;
385
+ node.computedParent = parent;
386
+ return style;
387
+ }
388
+ styleOf(node) {
389
+ return this.computeStyle(node, node.parent && node.parent.name !== '#root' ? this.styleOf(node.parent) : null);
390
+ }
391
+ renderChildren(node, context) {
392
+ for (const child of node.children)
393
+ this.renderNode(child, context);
394
+ }
395
+ passesConditions(node) {
396
+ if (node.attrs.requiredExtensions !== undefined)
397
+ return false;
398
+ const languages = node.attrs.systemLanguage;
399
+ if (languages === undefined)
400
+ return true;
401
+ return languages.split(',').some(lang => {
402
+ lang = lang.trim().toLowerCase();
403
+ return lang === 'en' || lang.startsWith('en-');
404
+ });
405
+ }
406
+ renderNode(node, context, root = false) {
407
+ if (!node.svg || UNSUPPORTED.has(node.name) || context.depth > 32 || !this.passesConditions(node))
408
+ return;
409
+ const style = this.computeStyle(node, context.style);
410
+ if (style.display === 'none')
411
+ return;
412
+ let matrix = context.matrix;
413
+ if (style.transform && !root)
414
+ matrix = multiply(matrix, parseTransform(style.transform));
415
+ const opacity = clamp01(parseNumber(style.opacity, 1));
416
+ if (opacity <= 0)
417
+ return;
418
+ const clip = this.reference(style['clip-path']);
419
+ const mask = this.reference(style.mask);
420
+ if ((clip && clip.name !== 'clipPath') || (mask && mask.name !== 'mask'))
421
+ return;
422
+ const clipping = clip && !this.active.has(clip);
423
+ const masking = mask && !this.active.has(mask);
424
+ const draw = (layer) => {
425
+ const ctx = { ...context, matrix, style, layer, depth: context.depth + 1 };
426
+ switch (node.name) {
427
+ case 'svg': return root ? this.renderChildren(node, ctx) : this.renderNestedSvg(node, ctx);
428
+ case 'g':
429
+ case 'a': return this.renderChildren(node, ctx);
430
+ case 'switch': {
431
+ const first = node.children.find(child => child.svg && SWITCHABLE.has(child.name) && this.passesConditions(child));
432
+ if (first)
433
+ this.renderNode(first, ctx);
434
+ return;
435
+ }
436
+ case 'use': return this.renderUse(node, ctx);
437
+ default: {
438
+ const path = shapePath(node, ctx.viewport, parseNumber(style['font-size'], 16));
439
+ if (path)
440
+ this.paintPath(path, node, ctx);
441
+ }
442
+ }
443
+ };
444
+ if (opacity >= 1 && !clipping && !masking)
445
+ return draw(context.layer);
446
+ const layer = new Layer(context.layer.width, context.layer.height);
447
+ draw(layer);
448
+ let alpha = null;
449
+ if (clipping)
450
+ alpha = this.clipMask(clip, node, matrix, context);
451
+ if (masking) {
452
+ const values = this.maskValues(mask, node, matrix, context);
453
+ if (alpha)
454
+ for (let i = 0; i < alpha.length; i++)
455
+ alpha[i] *= values[i];
456
+ else
457
+ alpha = values;
458
+ }
459
+ context.layer.composite(layer, opacity, alpha);
460
+ }
461
+ clipped(ctx, x, y, width, height, draw) {
462
+ const layer = new Layer(ctx.layer.width, ctx.layer.height);
463
+ draw(layer);
464
+ const { width: w } = ctx.layer;
465
+ const alpha = new Float32Array(w * ctx.layer.height);
466
+ const coverage = rasterize([transformPoints([x, y, x + width, y, x + width, y + height, x, y + height], ctx.matrix)], false, w, ctx.layer.height);
467
+ if (coverage)
468
+ for (let row = 0; row < coverage.h; row++)
469
+ for (let column = 0; column < coverage.w; column++)
470
+ alpha[(coverage.y0 + row) * w + coverage.x0 + column] = coverage.data[row * coverage.w + column];
471
+ ctx.layer.composite(layer, 1, alpha);
472
+ }
473
+ renderNestedSvg(node, ctx) {
474
+ const { viewport } = ctx;
475
+ const x = parseLength(node.attrs.x, viewport, 16, 'x');
476
+ const y = parseLength(node.attrs.y, viewport, 16, 'y');
477
+ const width = node.attrs.width === undefined ? viewport.width : parseLength(node.attrs.width, viewport, 16, 'x');
478
+ const height = node.attrs.height === undefined ? viewport.height : parseLength(node.attrs.height, viewport, 16, 'y');
479
+ if (!(width > 0) || !(height > 0))
480
+ return;
481
+ const viewBox = parseViewBox(node.attrs.viewBox);
482
+ let matrix = multiply(ctx.matrix, [1, 0, 0, 1, x, y]);
483
+ if (viewBox)
484
+ matrix = multiply(matrix, viewBoxTransform(viewBox, width, height, node.attrs.preserveAspectRatio));
485
+ const inner = { ...ctx, matrix, viewport: viewBox ? { width: viewBox[2], height: viewBox[3] } : { width, height } };
486
+ if (/^(visible|auto)$/.test(node.attrs.overflow || ''))
487
+ return this.renderChildren(node, inner);
488
+ this.clipped(ctx, x, y, width, height, layer => this.renderChildren(node, { ...inner, layer }));
489
+ }
490
+ renderUse(node, ctx) {
491
+ const target = this.reference(this.href(node));
492
+ if (!target || ctx.depth > 24)
493
+ return;
494
+ for (let p = node; p; p = p.parent)
495
+ if (p === target)
496
+ return;
497
+ const x = parseLength(node.attrs.x, ctx.viewport, 16, 'x');
498
+ const y = parseLength(node.attrs.y, ctx.viewport, 16, 'y');
499
+ const matrix = multiply(ctx.matrix, [1, 0, 0, 1, x, y]);
500
+ if (target.name === 'symbol' || target.name === 'svg') {
501
+ const style = this.computeStyle(target, ctx.style);
502
+ if (style.display === 'none')
503
+ return;
504
+ const width = node.attrs.width !== undefined ? parseLength(node.attrs.width, ctx.viewport, 16, 'x')
505
+ : target.attrs.width !== undefined ? parseLength(target.attrs.width, ctx.viewport, 16, 'x') : ctx.viewport.width;
506
+ const height = node.attrs.height !== undefined ? parseLength(node.attrs.height, ctx.viewport, 16, 'y')
507
+ : target.attrs.height !== undefined ? parseLength(target.attrs.height, ctx.viewport, 16, 'y') : ctx.viewport.height;
508
+ if (!(width > 0) || !(height > 0))
509
+ return;
510
+ const viewBox = parseViewBox(target.attrs.viewBox);
511
+ let outer = matrix;
512
+ if (target.name === 'svg')
513
+ outer = multiply(outer, [1, 0, 0, 1, parseLength(target.attrs.x, ctx.viewport, 16, 'x'), parseLength(target.attrs.y, ctx.viewport, 16, 'y')]);
514
+ const inner = viewBox ? multiply(outer, viewBoxTransform(viewBox, width, height, target.attrs.preserveAspectRatio)) : outer;
515
+ const opacity = clamp01(parseNumber(style.opacity, 1));
516
+ const innerContext = { ...ctx, matrix: inner, style, viewport: viewBox ? { width: viewBox[2], height: viewBox[3] } : { width, height } };
517
+ const draw = (layer) => /^(visible|auto)$/.test(target.attrs.overflow || '')
518
+ ? this.renderChildren(target, { ...innerContext, layer })
519
+ : this.clipped({ ...ctx, matrix: outer, layer }, 0, 0, width, height, inner => this.renderChildren(target, { ...innerContext, layer: inner }));
520
+ if (opacity >= 1)
521
+ return draw(ctx.layer);
522
+ const layer = new Layer(ctx.layer.width, ctx.layer.height);
523
+ draw(layer);
524
+ ctx.layer.composite(layer, opacity, null);
525
+ return;
526
+ }
527
+ const computed = target.computed, computedParent = target.computedParent;
528
+ target.computed = null;
529
+ this.renderNode(target, { ...ctx, matrix });
530
+ target.computed = computed;
531
+ target.computedParent = computedParent;
532
+ }
533
+ paintPath(path, node, ctx) {
534
+ const { matrix, layer } = ctx;
535
+ const style = ctx.style;
536
+ if (style.visibility === 'hidden' || style.visibility === 'collapse')
537
+ return;
538
+ const scale = Math.sqrt(Math.abs(matrix[0] * matrix[3] - matrix[1] * matrix[2])) || 1e-9;
539
+ const tolerance = 0.1 / scale;
540
+ const subpaths = flatten(path, tolerance);
541
+ const bbox = boundingBox(subpaths);
542
+ const fill = this.paint(style.fill, style, bbox, matrix, style['fill-opacity'], ctx);
543
+ if (fill && node.name !== 'line') {
544
+ const polygons = subpaths.map(points => transformPoints(points.points, matrix));
545
+ const coverage = rasterize(polygons, style['fill-rule'] === 'evenodd', layer.width, layer.height);
546
+ if (coverage)
547
+ layer.fill(coverage, fill);
548
+ }
549
+ const stroke = this.paint(style.stroke, style, bbox, matrix, style['stroke-opacity'], ctx);
550
+ const strokeWidth = parseLength(style['stroke-width'], ctx.viewport, 16, 'diagonal');
551
+ if (stroke && strokeWidth > 0) {
552
+ let lines = subpaths;
553
+ const fontSize = parseNumber(style['font-size'], 16);
554
+ const dashes = parseDashArray(style['stroke-dasharray'], ctx.viewport, fontSize);
555
+ if (dashes)
556
+ lines = dash(subpaths, dashes, parseLength(style['stroke-dashoffset'], ctx.viewport, fontSize, 'diagonal'));
557
+ const polygons = strokePolygons(lines, strokeWidth / 2, style['stroke-linecap'], style['stroke-linejoin'], Math.max(1, parseNumber(style['stroke-miterlimit'], 4)), tolerance).map(points => transformPoints(points, matrix));
558
+ const coverage = rasterize(polygons, false, layer.width, layer.height);
559
+ if (coverage)
560
+ layer.fill(coverage, stroke);
561
+ }
562
+ }
563
+ paint(value, style, bbox, matrix, opacityValue, ctx) {
564
+ if (!value || value === 'none')
565
+ return null;
566
+ const opacity = clamp01(parseNumber(opacityValue, 1));
567
+ let color = value;
568
+ if (/^url\(/.test(value)) {
569
+ const server = this.reference(value);
570
+ const fallback = value.replace(/^\s*url\([^)]*\)\s*/, '');
571
+ if (server && (server.name === 'linearGradient' || server.name === 'radialGradient')) {
572
+ const gradient = this.gradient(server, bbox, matrix, opacity, ctx);
573
+ if (gradient !== undefined || !fallback)
574
+ return gradient || null;
575
+ }
576
+ color = fallback;
577
+ if (!color || color === 'none')
578
+ return null;
579
+ }
580
+ const parsed = parseColor(color === 'currentColor' ? style.color : color);
581
+ if (!parsed || parsed[3] * opacity <= 0)
582
+ return null;
583
+ return solidPaint([parsed[0], parsed[1], parsed[2], parsed[3] * opacity]);
584
+ }
585
+ gradientAttributes(node) {
586
+ const attrs = {};
587
+ let stops = null;
588
+ const geometry = node.name === 'linearGradient' ? ['x1', 'y1', 'x2', 'y2'] : ['cx', 'cy', 'r', 'fx', 'fy', 'fr'];
589
+ for (let current = node, depth = 0; current && depth < 16; current = this.reference(this.href(current)), depth++) {
590
+ if (current.name !== 'linearGradient' && current.name !== 'radialGradient')
591
+ break;
592
+ const names = current.name === node.name ? [...geometry, 'gradientUnits', 'gradientTransform', 'spreadMethod'] : ['gradientUnits', 'gradientTransform', 'spreadMethod'];
593
+ for (const name of names)
594
+ if (attrs[name] === undefined && current.attrs[name] !== undefined)
595
+ attrs[name] = current.attrs[name];
596
+ if (!stops) {
597
+ const own = current.children.filter(child => child.name === 'stop');
598
+ if (own.length)
599
+ stops = own.map(stop => {
600
+ const style = this.styleOf(stop);
601
+ const offset = clamp01(parseStrictNumber(stop.attrs.offset) || 0);
602
+ const color = parseColor(style['stop-color'] === 'currentColor' ? style.color : style['stop-color']) || [0, 0, 0, 1];
603
+ return { offset, color: [color[0], color[1], color[2], color[3] * clamp01(parseNumber(style['stop-opacity'], 1))] };
604
+ });
605
+ }
606
+ }
607
+ return { attrs, stops: stops || [] };
608
+ }
609
+ gradient(node, bbox, matrix, opacity, ctx) {
610
+ const { attrs, stops } = this.gradientAttributes(node);
611
+ if (!stops.length)
612
+ return undefined;
613
+ for (let i = 1; i < stops.length; i++)
614
+ stops[i].offset = Math.max(stops[i].offset, stops[i - 1].offset);
615
+ if (stops.length === 1) {
616
+ const c = stops[0].color;
617
+ return c[3] * opacity > 0 ? solidPaint([c[0], c[1], c[2], c[3] * opacity]) : null;
618
+ }
619
+ const objectBox = attrs.gradientUnits !== 'userSpaceOnUse';
620
+ if (objectBox && (!bbox || bbox.width <= 0 || bbox.height <= 0))
621
+ return null;
622
+ const coordinate = (value, fallback, axis) => {
623
+ if (value === undefined)
624
+ return fallback;
625
+ if (objectBox)
626
+ return /%\s*$/.test(value) ? parseFloat(value) / 100 : parseNumber(value, fallback);
627
+ return parseLength(value, ctx.viewport, 16, axis);
628
+ };
629
+ let gradientMatrix = matrix;
630
+ if (objectBox && bbox)
631
+ gradientMatrix = multiply(gradientMatrix, [bbox.width, 0, 0, bbox.height, bbox.x, bbox.y]);
632
+ if (attrs.gradientTransform)
633
+ gradientMatrix = multiply(gradientMatrix, parseTransform(attrs.gradientTransform));
634
+ const inverse = invert(gradientMatrix);
635
+ if (!inverse)
636
+ return null;
637
+ const spread = attrs.spreadMethod === 'reflect' ? 1 : attrs.spreadMethod === 'repeat' ? 2 : 0;
638
+ const lookup = gradientLookup(stops, opacity);
639
+ const color = (t) => {
640
+ if (spread === 2)
641
+ t -= Math.floor(t);
642
+ else if (spread === 1) {
643
+ t = Math.abs(t) % 2;
644
+ if (t > 1)
645
+ t = 2 - t;
646
+ }
647
+ return lookup[Math.round(clamp01(t) * 1023)];
648
+ };
649
+ if (node.name === 'linearGradient') {
650
+ const x1 = coordinate(attrs.x1, 0, 'x'), y1 = coordinate(attrs.y1, 0, 'y');
651
+ const x2 = coordinate(attrs.x2, objectBox ? 1 : ctx.viewport.width, 'x'), y2 = coordinate(attrs.y2, 0, 'y');
652
+ const dx = x2 - x1, dy = y2 - y1;
653
+ const length = dx * dx + dy * dy;
654
+ if (length === 0)
655
+ return this.lastStopPaint(stops, opacity);
656
+ return (px, py) => {
657
+ const x = inverse[0] * px + inverse[2] * py + inverse[4];
658
+ const y = inverse[1] * px + inverse[3] * py + inverse[5];
659
+ return color(((x - x1) * dx + (y - y1) * dy) / length);
660
+ };
661
+ }
662
+ const cx = coordinate(attrs.cx, objectBox ? 0.5 : ctx.viewport.width / 2, 'x');
663
+ const cy = coordinate(attrs.cy, objectBox ? 0.5 : ctx.viewport.height / 2, 'y');
664
+ const r = coordinate(attrs.r, objectBox ? 0.5 : Math.hypot(ctx.viewport.width, ctx.viewport.height) / Math.SQRT2 / 2, 'diagonal');
665
+ let fx = coordinate(attrs.fx, cx, 'x'), fy = coordinate(attrs.fy, cy, 'y');
666
+ const fr = coordinate(attrs.fr, 0, 'diagonal');
667
+ if (!(r > 0))
668
+ return this.lastStopPaint(stops, opacity);
669
+ const fd = Math.hypot(fx - cx, fy - cy);
670
+ if (fd > r * 0.999) {
671
+ fx = cx + (fx - cx) * r * 0.999 / fd;
672
+ fy = cy + (fy - cy) * r * 0.999 / fd;
673
+ }
674
+ const cdx = cx - fx, cdy = cy - fy, dr = r - fr;
675
+ const a = cdx * cdx + cdy * cdy - dr * dr;
676
+ return (px, py) => {
677
+ const x = inverse[0] * px + inverse[2] * py + inverse[4] - fx;
678
+ const y = inverse[1] * px + inverse[3] * py + inverse[5] - fy;
679
+ const b = x * cdx + y * cdy + fr * dr;
680
+ const c = x * x + y * y - fr * fr;
681
+ let t;
682
+ if (Math.abs(a) < 1e-9)
683
+ t = c / (2 * b);
684
+ else {
685
+ const discriminant = b * b - a * c;
686
+ if (discriminant < 0)
687
+ return TRANSPARENT;
688
+ const root = Math.sqrt(discriminant);
689
+ const t1 = (b + root) / a, t2 = (b - root) / a;
690
+ t = Math.max(t1, t2);
691
+ if (fr + t * dr < 0)
692
+ t = Math.min(t1, t2);
693
+ }
694
+ if (!isFinite(t) || fr + t * dr < 0)
695
+ return TRANSPARENT;
696
+ return color(t);
697
+ };
698
+ }
699
+ lastStopPaint(stops, opacity) {
700
+ const c = stops[stops.length - 1].color;
701
+ return solidPaint([c[0], c[1], c[2], c[3] * opacity]);
702
+ }
703
+ clipMask(clip, node, matrix, context) {
704
+ const { width, height } = context.layer;
705
+ const alpha = new Float32Array(width * height);
706
+ const style = this.styleOf(clip);
707
+ this.active.add(clip);
708
+ let clipMatrix = matrix;
709
+ if (style.transform)
710
+ clipMatrix = multiply(clipMatrix, parseTransform(style.transform));
711
+ if (clip.attrs.clipPathUnits === 'objectBoundingBox') {
712
+ const path = shapePath(node, context.viewport, 16);
713
+ const bbox = path ? boundingBox(flatten(path, 0.1)) : this.groupBoundingBox(node, context.viewport);
714
+ if (!bbox || bbox.width <= 0 || bbox.height <= 0)
715
+ return alpha;
716
+ clipMatrix = multiply(clipMatrix, [bbox.width, 0, 0, bbox.height, bbox.x, bbox.y]);
717
+ }
718
+ for (const child of clip.children) {
719
+ if (UNSUPPORTED.has(child.name) && child.name !== 'text')
720
+ continue;
721
+ const childStyle = this.computeStyle(child, style);
722
+ if (childStyle.display === 'none' || childStyle.visibility === 'hidden')
723
+ continue;
724
+ let childMatrix = clipMatrix;
725
+ if (childStyle.transform)
726
+ childMatrix = multiply(childMatrix, parseTransform(childStyle.transform));
727
+ let shape = child;
728
+ if (child.name === 'use') {
729
+ const target = this.reference(this.href(child));
730
+ if (!target || !['path', 'rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon'].includes(target.name))
731
+ continue;
732
+ childMatrix = multiply(childMatrix, [1, 0, 0, 1, parseLength(child.attrs.x, context.viewport, 16, 'x'), parseLength(child.attrs.y, context.viewport, 16, 'y')]);
733
+ const targetStyle = this.computeStyle(target, childStyle);
734
+ if (targetStyle.transform)
735
+ childMatrix = multiply(childMatrix, parseTransform(targetStyle.transform));
736
+ shape = target;
737
+ }
738
+ const path = shapePath(shape, context.viewport, 16);
739
+ if (!path)
740
+ continue;
741
+ const scale = Math.sqrt(Math.abs(childMatrix[0] * childMatrix[3] - childMatrix[1] * childMatrix[2])) || 1e-9;
742
+ const polygons = flatten(path, 0.1 / scale).map(points => transformPoints(points.points, childMatrix));
743
+ const coverage = rasterize(polygons, this.computeStyle(shape, childStyle)['clip-rule'] === 'evenodd', width, height);
744
+ if (!coverage)
745
+ continue;
746
+ const own = this.reference(childStyle['clip-path']);
747
+ const ownMask = own && own.name === 'clipPath' && !this.active.has(own) ? this.clipMask(own, shape, childMatrix, context) : null;
748
+ const { x0, y0, w, h, data } = coverage;
749
+ for (let y = 0; y < h; y++)
750
+ for (let x = 0; x < w; x++) {
751
+ let c = data[y * w + x];
752
+ const i = (y0 + y) * width + x0 + x;
753
+ if (ownMask)
754
+ c *= ownMask[i];
755
+ alpha[i] = alpha[i] + c - alpha[i] * c;
756
+ }
757
+ }
758
+ const nested = this.reference(style['clip-path']);
759
+ if (nested && nested.name === 'clipPath' && !this.active.has(nested)) {
760
+ const outer = this.clipMask(nested, node, matrix, context);
761
+ for (let i = 0; i < alpha.length; i++)
762
+ alpha[i] *= outer[i];
763
+ }
764
+ this.active.delete(clip);
765
+ return alpha;
766
+ }
767
+ groupBoundingBox(node, viewport) {
768
+ let box = null;
769
+ const visit = (n, matrix) => {
770
+ const path = shapePath(n, viewport, 16);
771
+ if (path) {
772
+ const points = flatten(path, 0.1).map(p => transformPoints(p.points, matrix));
773
+ const b = boundingBox(points.map(p => ({ points: p })));
774
+ if (b)
775
+ box = box ? union(box, b) : b;
776
+ }
777
+ for (const child of n.children)
778
+ visit(child, child.attrs.transform ? multiply(matrix, parseTransform(child.attrs.transform)) : matrix);
779
+ };
780
+ visit(node, [1, 0, 0, 1, 0, 0]);
781
+ return box;
782
+ }
783
+ maskValues(mask, node, matrix, context) {
784
+ const { width, height } = context.layer;
785
+ const values = new Float32Array(width * height);
786
+ const path = shapePath(node, context.viewport, 16);
787
+ const bbox = path ? boundingBox(flatten(path, 0.1)) : this.groupBoundingBox(node, context.viewport);
788
+ const userSpace = mask.attrs.maskUnits === 'userSpaceOnUse';
789
+ const contentBox = mask.attrs.maskContentUnits === 'objectBoundingBox';
790
+ if ((!userSpace || contentBox) && (!bbox || bbox.width <= 0 || bbox.height <= 0))
791
+ return values;
792
+ const box = bbox || { x: 0, y: 0, width: 0, height: 0 };
793
+ const fraction = (name, fallback) => {
794
+ const value = mask.attrs[name];
795
+ if (value === undefined)
796
+ return fallback;
797
+ return /%\s*$/.test(value) ? parseFloat(value) / 100 : parseNumber(value, fallback);
798
+ };
799
+ let rx, ry, rw, rh;
800
+ if (userSpace) {
801
+ const { viewport } = context;
802
+ const length = (name, fallback, axis) => mask.attrs[name] === undefined ? fallback : parseLength(mask.attrs[name], viewport, 16, axis);
803
+ rx = length('x', -0.1 * viewport.width, 'x');
804
+ ry = length('y', -0.1 * viewport.height, 'y');
805
+ rw = length('width', 1.2 * viewport.width, 'x');
806
+ rh = length('height', 1.2 * viewport.height, 'y');
807
+ }
808
+ else {
809
+ rx = box.x + fraction('x', -0.1) * box.width;
810
+ ry = box.y + fraction('y', -0.1) * box.height;
811
+ rw = fraction('width', 1.2) * box.width;
812
+ rh = fraction('height', 1.2) * box.height;
813
+ }
814
+ const layer = new Layer(width, height);
815
+ let contentMatrix = matrix;
816
+ if (contentBox)
817
+ contentMatrix = multiply(contentMatrix, [box.width, 0, 0, box.height, box.x, box.y]);
818
+ const maskStyle = this.styleOf(mask);
819
+ this.active.add(mask);
820
+ this.renderChildren(mask, { ...context, matrix: contentMatrix, style: maskStyle, layer, depth: context.depth + 1 });
821
+ const alphaMask = maskStyle['mask-type'] === 'alpha';
822
+ const regionCoverage = rasterize([transformPoints([rx, ry, rx + rw, ry, rx + rw, ry + rh, rx, ry + rh], matrix)], false, width, height);
823
+ if (!regionCoverage) {
824
+ this.active.delete(mask);
825
+ return values;
826
+ }
827
+ const { x0, y0, w, h, data } = regionCoverage;
828
+ const d = layer.data;
829
+ for (let y = 0; y < h; y++)
830
+ for (let x = 0; x < w; x++) {
831
+ const i = (y0 + y) * width + x0 + x;
832
+ values[i] = data[y * w + x] * (alphaMask ? d[i * 4 + 3] : 0.2125 * d[i * 4] + 0.7154 * d[i * 4 + 1] + 0.0721 * d[i * 4 + 2]);
833
+ }
834
+ const nested = this.reference(maskStyle.mask);
835
+ if (nested && nested.name === 'mask' && !this.active.has(nested)) {
836
+ const outer = this.maskValues(nested, node, matrix, context);
837
+ for (let i = 0; i < values.length; i++)
838
+ values[i] *= outer[i];
839
+ }
840
+ else if (nested)
841
+ values.fill(0);
842
+ this.active.delete(mask);
843
+ return values;
844
+ }
845
+ }
846
+ const TRANSPARENT = [0, 0, 0, 0];
847
+ function solidPaint(color) {
848
+ const paint = () => color;
849
+ paint.solid = color;
850
+ return paint;
851
+ }
852
+ function gradientLookup(stops, opacity) {
853
+ const table = new Array(1024);
854
+ for (let i = 0; i < 1024; i++) {
855
+ const t = i / 1023;
856
+ let k = 0;
857
+ while (k < stops.length - 1 && stops[k + 1].offset < t)
858
+ k++;
859
+ let c;
860
+ if (t <= stops[0].offset)
861
+ c = stops[0].color;
862
+ else if (k >= stops.length - 1)
863
+ c = stops[stops.length - 1].color;
864
+ else {
865
+ const a = stops[k], b = stops[k + 1];
866
+ const span = b.offset - a.offset;
867
+ const f = span > 0 ? (t - a.offset) / span : 1;
868
+ c = [0, 1, 2, 3].map(j => a.color[j] + (b.color[j] - a.color[j]) * f);
869
+ }
870
+ table[i] = [c[0], c[1], c[2], c[3] * opacity];
871
+ }
872
+ return table;
873
+ }
874
+ class Layer {
875
+ width;
876
+ height;
877
+ data;
878
+ constructor(width, height) {
879
+ this.width = width;
880
+ this.height = height;
881
+ this.data = new Float32Array(width * height * 4);
882
+ }
883
+ fill({ x0, y0, w, h, data }, paint) {
884
+ const d = this.data;
885
+ const solid = paint.solid;
886
+ for (let y = 0; y < h; y++) {
887
+ let i = ((y0 + y) * this.width + x0) * 4;
888
+ for (let x = 0, o = y * w; x < w; x++, o++, i += 4) {
889
+ const c = data[o];
890
+ if (c <= 0)
891
+ continue;
892
+ const color = solid || paint(x0 + x + 0.5, y0 + y + 0.5);
893
+ const a = color[3] * c;
894
+ if (a <= 0)
895
+ continue;
896
+ if (a >= 1) {
897
+ d[i] = color[0];
898
+ d[i + 1] = color[1];
899
+ d[i + 2] = color[2];
900
+ d[i + 3] = 1;
901
+ if (solid) {
902
+ let run = 1;
903
+ while (x + run < w && data[o + run] >= 1)
904
+ run++;
905
+ if (run > 1) {
906
+ for (let filled = 1; filled < run; filled *= 2)
907
+ d.copyWithin(i + filled * 4, i, i + Math.min(filled, run - filled) * 4);
908
+ x += run - 1;
909
+ o += run - 1;
910
+ i += (run - 1) * 4;
911
+ }
912
+ }
913
+ continue;
914
+ }
915
+ const inv = 1 - a;
916
+ d[i] = color[0] * a + d[i] * inv;
917
+ d[i + 1] = color[1] * a + d[i + 1] * inv;
918
+ d[i + 2] = color[2] * a + d[i + 2] * inv;
919
+ d[i + 3] = a + d[i + 3] * inv;
920
+ }
921
+ }
922
+ }
923
+ composite(layer, opacity, mask) {
924
+ const s = layer.data, d = this.data;
925
+ for (let p = 0, i = 0; p < this.width * this.height; p++, i += 4) {
926
+ if (s[i + 3] <= 0)
927
+ continue;
928
+ const f = mask ? opacity * mask[p] : opacity;
929
+ if (f <= 0)
930
+ continue;
931
+ const a = s[i + 3] * f;
932
+ const inv = 1 - a;
933
+ d[i] = s[i] * f + d[i] * inv;
934
+ d[i + 1] = s[i + 1] * f + d[i + 1] * inv;
935
+ d[i + 2] = s[i + 2] * f + d[i + 2] * inv;
936
+ d[i + 3] = a + d[i + 3] * inv;
937
+ }
938
+ }
939
+ toRGBA() {
940
+ const out = new Uint8ClampedArray(this.width * this.height * 4);
941
+ const d = this.data;
942
+ for (let i = 0; i < d.length; i += 4) {
943
+ const a = d[i + 3];
944
+ if (a <= 0)
945
+ continue;
946
+ const f = a >= 1 ? 255 : 255 / a;
947
+ out[i] = d[i] * f;
948
+ out[i + 1] = d[i + 1] * f;
949
+ out[i + 2] = d[i + 2] * f;
950
+ out[i + 3] = a * 255;
951
+ }
952
+ return new Uint8Array(out.buffer);
953
+ }
954
+ }
955
+ function rasterize(polygons, evenOdd, width, height) {
956
+ const edges = [];
957
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
958
+ for (const points of polygons) {
959
+ const n = points.length >> 1;
960
+ if (n < 2)
961
+ continue;
962
+ for (let i = 0; i < n; i++) {
963
+ const ax = points[i * 2], ay = points[i * 2 + 1];
964
+ const j = (i + 1) % n;
965
+ const bx = points[j * 2], by = points[j * 2 + 1];
966
+ if (!isFinite(ax) || !isFinite(ay) || !isFinite(bx) || !isFinite(by) || ay === by)
967
+ continue;
968
+ const down = ay < by;
969
+ const top = down ? ay : by, bottom = down ? by : ay;
970
+ const topX = down ? ax : bx;
971
+ edges.push({ top, bottom, x: topX, slope: (down ? bx - ax : ax - bx) / (bottom - top), dir: down ? 1 : -1 });
972
+ minY = Math.min(minY, top);
973
+ maxY = Math.max(maxY, bottom);
974
+ minX = Math.min(minX, ax, bx);
975
+ maxX = Math.max(maxX, ax, bx);
976
+ }
977
+ }
978
+ if (!edges.length)
979
+ return null;
980
+ const x0 = Math.max(0, Math.floor(minX)), x1 = Math.min(width, Math.ceil(maxX));
981
+ const y0 = Math.max(0, Math.floor(minY)), y1 = Math.min(height, Math.ceil(maxY));
982
+ if (x0 >= x1 || y0 >= y1)
983
+ return null;
984
+ const w = x1 - x0, h = y1 - y0;
985
+ const data = new Float32Array(w * h);
986
+ const row = new Float32Array(w + 2);
987
+ const steps = new Float32Array(w + 2);
988
+ const weight = 1 / SAMPLES;
989
+ edges.sort((a, b) => a.top - b.top);
990
+ let next = 0;
991
+ let active = [];
992
+ const crossings = [];
993
+ const span = (a, b) => {
994
+ a = Math.max(a, x0) - x0;
995
+ b = Math.min(b, x1) - x0;
996
+ if (b <= a)
997
+ return;
998
+ const ia = Math.floor(a), ib = Math.floor(b);
999
+ if (ia === ib)
1000
+ row[ia] += (b - a) * weight;
1001
+ else {
1002
+ row[ia] += (ia + 1 - a) * weight;
1003
+ steps[ia + 1] += weight;
1004
+ steps[ib] -= weight;
1005
+ row[ib] += (b - ib) * weight;
1006
+ }
1007
+ };
1008
+ for (let py = y0; py < y1; py++) {
1009
+ row.fill(0);
1010
+ steps.fill(0);
1011
+ while (next < edges.length && edges[next].top < py + 1)
1012
+ active.push(edges[next++]);
1013
+ active = active.filter(edge => edge.bottom > py);
1014
+ for (let s = 0; s < SAMPLES; s++) {
1015
+ const sy = py + (s + 0.5) / SAMPLES;
1016
+ crossings.length = 0;
1017
+ for (const edge of active)
1018
+ if (sy >= edge.top && sy < edge.bottom)
1019
+ crossings.push(edge.x + (sy - edge.top) * edge.slope, edge.dir);
1020
+ if (crossings.length < 4)
1021
+ continue;
1022
+ for (let i = 2; i < crossings.length; i += 2) {
1023
+ const x = crossings[i], dir = crossings[i + 1];
1024
+ let j = i - 2;
1025
+ for (; j >= 0 && crossings[j] > x; j -= 2) {
1026
+ crossings[j + 2] = crossings[j];
1027
+ crossings[j + 3] = crossings[j + 1];
1028
+ }
1029
+ crossings[j + 2] = x;
1030
+ crossings[j + 3] = dir;
1031
+ }
1032
+ let winding = 0;
1033
+ for (let i = 0; i < crossings.length - 2; i += 2) {
1034
+ winding += crossings[i + 1];
1035
+ if (evenOdd ? winding & 1 : winding !== 0)
1036
+ span(crossings[i], crossings[i + 2]);
1037
+ }
1038
+ }
1039
+ let step = 0;
1040
+ const o = (py - y0) * w;
1041
+ for (let x = 0; x < w; x++) {
1042
+ step += steps[x];
1043
+ const v = row[x] + step;
1044
+ data[o + x] = v > 1 ? 1 : v;
1045
+ }
1046
+ }
1047
+ return { x0, y0, w, h, data };
1048
+ }
1049
+ function shapePath(node, viewport, fontSize) {
1050
+ const length = (name, axis, fallback = 0) => node.attrs[name] === undefined ? fallback : parseLength(node.attrs[name], viewport, fontSize, axis);
1051
+ switch (node.name) {
1052
+ case 'path':
1053
+ return parsePath(node.attrs.d || '');
1054
+ case 'rect': {
1055
+ const x = length('x', 'x'), y = length('y', 'y');
1056
+ const w = length('width', 'x'), h = length('height', 'y');
1057
+ if (!(w > 0) || !(h > 0))
1058
+ return null;
1059
+ let rx = node.attrs.rx === undefined || node.attrs.rx === 'auto' ? undefined : length('rx', 'x');
1060
+ let ry = node.attrs.ry === undefined || node.attrs.ry === 'auto' ? undefined : length('ry', 'y');
1061
+ if (rx === undefined || rx < 0)
1062
+ rx = ry !== undefined && ry >= 0 ? ry : 0;
1063
+ if (ry === undefined || ry < 0)
1064
+ ry = rx;
1065
+ rx = Math.min(rx, w / 2);
1066
+ ry = Math.min(ry, h / 2);
1067
+ if (!rx || !ry)
1068
+ return [['M', x, y], ['L', x + w, y], ['L', x + w, y + h], ['L', x, y + h], ['Z']];
1069
+ const k = 0.5522847498;
1070
+ return [
1071
+ ['M', x + rx, y], ['L', x + w - rx, y],
1072
+ ['C', x + w - rx + k * rx, y, x + w, y + ry - k * ry, x + w, y + ry], ['L', x + w, y + h - ry],
1073
+ ['C', x + w, y + h - ry + k * ry, x + w - rx + k * rx, y + h, x + w - rx, y + h], ['L', x + rx, y + h],
1074
+ ['C', x + rx - k * rx, y + h, x, y + h - ry + k * ry, x, y + h - ry], ['L', x, y + ry],
1075
+ ['C', x, y + ry - k * ry, x + rx - k * rx, y, x + rx, y], ['Z']
1076
+ ];
1077
+ }
1078
+ case 'circle':
1079
+ case 'ellipse': {
1080
+ const cx = length('cx', 'x'), cy = length('cy', 'y');
1081
+ let rx, ry;
1082
+ if (node.name === 'circle')
1083
+ rx = ry = length('r', 'diagonal');
1084
+ else {
1085
+ rx = node.attrs.rx === undefined || node.attrs.rx === 'auto' ? undefined : length('rx', 'x');
1086
+ ry = node.attrs.ry === undefined || node.attrs.ry === 'auto' ? undefined : length('ry', 'y');
1087
+ if (rx !== undefined && rx < 0)
1088
+ rx = undefined;
1089
+ if (ry !== undefined && ry < 0)
1090
+ ry = undefined;
1091
+ if (rx === undefined)
1092
+ rx = ry;
1093
+ if (ry === undefined)
1094
+ ry = rx;
1095
+ }
1096
+ if (rx === undefined || ry === undefined || !(rx > 0) || !(ry > 0))
1097
+ return null;
1098
+ const k = 0.5522847498;
1099
+ return [
1100
+ ['M', cx + rx, cy],
1101
+ ['C', cx + rx, cy + k * ry, cx + k * rx, cy + ry, cx, cy + ry],
1102
+ ['C', cx - k * rx, cy + ry, cx - rx, cy + k * ry, cx - rx, cy],
1103
+ ['C', cx - rx, cy - k * ry, cx - k * rx, cy - ry, cx, cy - ry],
1104
+ ['C', cx + k * rx, cy - ry, cx + rx, cy - k * ry, cx + rx, cy],
1105
+ ['Z']
1106
+ ];
1107
+ }
1108
+ case 'line':
1109
+ return [['M', length('x1', 'x'), length('y1', 'y')], ['L', length('x2', 'x'), length('y2', 'y')]];
1110
+ case 'polyline':
1111
+ case 'polygon': {
1112
+ const numbers = parseNumberList(node.attrs.points || '');
1113
+ if (numbers.length < 4)
1114
+ return null;
1115
+ const path = [['M', numbers[0], numbers[1]]];
1116
+ for (let i = 2; i + 1 < numbers.length; i += 2)
1117
+ path.push(['L', numbers[i], numbers[i + 1]]);
1118
+ if (node.name === 'polygon')
1119
+ path.push(['Z']);
1120
+ return path;
1121
+ }
1122
+ }
1123
+ return null;
1124
+ }
1125
+ function parsePath(d) {
1126
+ const path = [];
1127
+ const tokens = /([MmZzLlHhVvCcSsQqTtAa])|([-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?)/g;
1128
+ const items = [];
1129
+ for (const m of d.matchAll(tokens))
1130
+ items.push(m[1] ? { command: m[1] } : m[2]);
1131
+ let i = 0;
1132
+ let command = null;
1133
+ let x = 0, y = 0, sx = 0, sy = 0;
1134
+ let lastControlX = 0, lastControlY = 0, lastCommand = '';
1135
+ const number = () => typeof items[i] === 'string' ? parseFloat(items[i++]) : NaN;
1136
+ const flag = () => {
1137
+ const v = items[i];
1138
+ if (typeof v !== 'string' || (v[0] !== '0' && v[0] !== '1'))
1139
+ return NaN;
1140
+ if (v.length === 1)
1141
+ i++;
1142
+ else
1143
+ items[i] = v.slice(1);
1144
+ return +v[0];
1145
+ };
1146
+ while (i < items.length) {
1147
+ if (typeof items[i] === 'object')
1148
+ command = items[i++].command;
1149
+ else if (command === null)
1150
+ return path;
1151
+ else if (command === 'M')
1152
+ command = 'L';
1153
+ else if (command === 'm')
1154
+ command = 'l';
1155
+ else if (command === 'Z' || command === 'z')
1156
+ return path;
1157
+ const relative = command === command.toLowerCase();
1158
+ const ox = relative ? x : 0, oy = relative ? y : 0;
1159
+ const upper = command.toUpperCase();
1160
+ if (upper === 'Z') {
1161
+ path.push(['Z']);
1162
+ x = sx;
1163
+ y = sy;
1164
+ lastCommand = 'Z';
1165
+ continue;
1166
+ }
1167
+ if (!path.length && upper !== 'M')
1168
+ return path;
1169
+ if (upper === 'M') {
1170
+ const nx = number() + ox, ny = number() + oy;
1171
+ if (isNaN(nx) || isNaN(ny))
1172
+ return path;
1173
+ x = sx = nx;
1174
+ y = sy = ny;
1175
+ path.push(['M', x, y]);
1176
+ }
1177
+ else if (upper === 'L') {
1178
+ const nx = number() + ox, ny = number() + oy;
1179
+ if (isNaN(nx) || isNaN(ny))
1180
+ return path;
1181
+ x = nx;
1182
+ y = ny;
1183
+ path.push(['L', x, y]);
1184
+ }
1185
+ else if (upper === 'H') {
1186
+ const nx = number() + ox;
1187
+ if (isNaN(nx))
1188
+ return path;
1189
+ x = nx;
1190
+ path.push(['L', x, y]);
1191
+ }
1192
+ else if (upper === 'V') {
1193
+ const ny = number() + oy;
1194
+ if (isNaN(ny))
1195
+ return path;
1196
+ y = ny;
1197
+ path.push(['L', x, y]);
1198
+ }
1199
+ else if (upper === 'C' || upper === 'S') {
1200
+ let c1x, c1y;
1201
+ if (upper === 'S') {
1202
+ const smooth = lastCommand === 'C' || lastCommand === 'S';
1203
+ c1x = smooth ? 2 * x - lastControlX : x;
1204
+ c1y = smooth ? 2 * y - lastControlY : y;
1205
+ }
1206
+ else {
1207
+ c1x = number() + ox;
1208
+ c1y = number() + oy;
1209
+ }
1210
+ const c2x = number() + ox, c2y = number() + oy;
1211
+ const nx = number() + ox, ny = number() + oy;
1212
+ if ([c1x, c1y, c2x, c2y, nx, ny].some(isNaN))
1213
+ return path;
1214
+ path.push(['C', c1x, c1y, c2x, c2y, nx, ny]);
1215
+ lastControlX = c2x;
1216
+ lastControlY = c2y;
1217
+ x = nx;
1218
+ y = ny;
1219
+ }
1220
+ else if (upper === 'Q' || upper === 'T') {
1221
+ let cx, cy;
1222
+ if (upper === 'T') {
1223
+ const smooth = lastCommand === 'Q' || lastCommand === 'T';
1224
+ cx = smooth ? 2 * x - lastControlX : x;
1225
+ cy = smooth ? 2 * y - lastControlY : y;
1226
+ }
1227
+ else {
1228
+ cx = number() + ox;
1229
+ cy = number() + oy;
1230
+ }
1231
+ const nx = number() + ox, ny = number() + oy;
1232
+ if ([cx, cy, nx, ny].some(isNaN))
1233
+ return path;
1234
+ path.push(['C', x + 2 / 3 * (cx - x), y + 2 / 3 * (cy - y), nx + 2 / 3 * (cx - nx), ny + 2 / 3 * (cy - ny), nx, ny]);
1235
+ lastControlX = cx;
1236
+ lastControlY = cy;
1237
+ x = nx;
1238
+ y = ny;
1239
+ }
1240
+ else if (upper === 'A') {
1241
+ const rx = number(), ry = number(), rotation = number();
1242
+ const large = flag(), sweep = flag();
1243
+ const nx = number() + ox, ny = number() + oy;
1244
+ if ([rx, ry, rotation, large, sweep, nx, ny].some(isNaN))
1245
+ return path;
1246
+ arcToCubics(path, x, y, rx, ry, rotation, large, sweep, nx, ny);
1247
+ x = nx;
1248
+ y = ny;
1249
+ }
1250
+ else
1251
+ return path;
1252
+ lastCommand = upper;
1253
+ }
1254
+ return path;
1255
+ }
1256
+ function arcToCubics(path, x1, y1, rx, ry, rotation, large, sweep, x2, y2) {
1257
+ if (x1 === x2 && y1 === y2)
1258
+ return;
1259
+ rx = Math.abs(rx);
1260
+ ry = Math.abs(ry);
1261
+ if (!rx || !ry) {
1262
+ path.push(['L', x2, y2]);
1263
+ return;
1264
+ }
1265
+ const phi = rotation * Math.PI / 180;
1266
+ const cos = Math.cos(phi), sin = Math.sin(phi);
1267
+ const dx = (x1 - x2) / 2, dy = (y1 - y2) / 2;
1268
+ const x1p = cos * dx + sin * dy, y1p = -sin * dx + cos * dy;
1269
+ const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
1270
+ if (lambda > 1) {
1271
+ rx *= Math.sqrt(lambda);
1272
+ ry *= Math.sqrt(lambda);
1273
+ }
1274
+ const num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
1275
+ const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
1276
+ let coefficient = Math.sqrt(Math.max(0, num / den));
1277
+ if (large === sweep)
1278
+ coefficient = -coefficient;
1279
+ const cxp = coefficient * rx * y1p / ry, cyp = -coefficient * ry * x1p / rx;
1280
+ const cx = cos * cxp - sin * cyp + (x1 + x2) / 2;
1281
+ const cy = sin * cxp + cos * cyp + (y1 + y2) / 2;
1282
+ const angle = (ux, uy, vx, vy) => {
1283
+ const a = Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
1284
+ return a;
1285
+ };
1286
+ const theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
1287
+ let delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
1288
+ if (!sweep && delta > 0)
1289
+ delta -= 2 * Math.PI;
1290
+ else if (sweep && delta < 0)
1291
+ delta += 2 * Math.PI;
1292
+ const segments = Math.ceil(Math.abs(delta) / (Math.PI / 2) - 1e-9);
1293
+ const step = delta / segments;
1294
+ const k = 4 / 3 * Math.tan(step / 4);
1295
+ const point = (t) => [cx + rx * Math.cos(t) * cos - ry * Math.sin(t) * sin, cy + rx * Math.cos(t) * sin + ry * Math.sin(t) * cos];
1296
+ const derivative = (t) => [-rx * Math.sin(t) * cos - ry * Math.cos(t) * sin, -rx * Math.sin(t) * sin + ry * Math.cos(t) * cos];
1297
+ let t = theta1;
1298
+ for (let i = 0; i < segments; i++) {
1299
+ const t2 = t + step;
1300
+ const [px, py] = point(t), [qx, qy] = i === segments - 1 ? [x2, y2] : point(t2);
1301
+ const [dpx, dpy] = derivative(t), [dqx, dqy] = derivative(t2);
1302
+ path.push(['C', px + k * dpx, py + k * dpy, qx - k * dqx, qy - k * dqy, qx, qy]);
1303
+ t = t2;
1304
+ }
1305
+ }
1306
+ function flatten(path, tolerance) {
1307
+ const subpaths = [];
1308
+ let current = null;
1309
+ let x = 0, y = 0, sx = 0, sy = 0;
1310
+ const start = (px, py) => {
1311
+ const subpath = { points: [px, py], closed: false };
1312
+ subpaths.push(subpath);
1313
+ return subpath;
1314
+ };
1315
+ for (const command of path) {
1316
+ switch (command[0]) {
1317
+ case 'M':
1318
+ x = sx = command[1];
1319
+ y = sy = command[2];
1320
+ current = start(x, y);
1321
+ break;
1322
+ case 'L':
1323
+ if (!current)
1324
+ current = start(x, y);
1325
+ x = command[1];
1326
+ y = command[2];
1327
+ current.points.push(x, y);
1328
+ break;
1329
+ case 'C': {
1330
+ if (!current)
1331
+ current = start(x, y);
1332
+ const [, c1x, c1y, c2x, c2y, ex, ey] = command;
1333
+ const ddx = Math.max(Math.abs(x - 2 * c1x + c2x), Math.abs(c1x - 2 * c2x + ex));
1334
+ const ddy = Math.max(Math.abs(y - 2 * c1y + c2y), Math.abs(c1y - 2 * c2y + ey));
1335
+ const n = Math.min(512, Math.max(1, Math.ceil(Math.sqrt(0.75 * Math.hypot(ddx, ddy) / tolerance))));
1336
+ for (let i = 1; i <= n; i++) {
1337
+ const t = i / n, mt = 1 - t;
1338
+ current.points.push(mt * mt * mt * x + 3 * mt * mt * t * c1x + 3 * mt * t * t * c2x + t * t * t * ex, mt * mt * mt * y + 3 * mt * mt * t * c1y + 3 * mt * t * t * c2y + t * t * t * ey);
1339
+ }
1340
+ x = ex;
1341
+ y = ey;
1342
+ break;
1343
+ }
1344
+ case 'Z':
1345
+ if (current) {
1346
+ current.closed = true;
1347
+ x = sx;
1348
+ y = sy;
1349
+ current = null;
1350
+ }
1351
+ break;
1352
+ }
1353
+ }
1354
+ return subpaths;
1355
+ }
1356
+ function boundingBox(subpaths) {
1357
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1358
+ for (const { points } of subpaths)
1359
+ for (let i = 0; i < points.length; i += 2) {
1360
+ minX = Math.min(minX, points[i]);
1361
+ maxX = Math.max(maxX, points[i]);
1362
+ minY = Math.min(minY, points[i + 1]);
1363
+ maxY = Math.max(maxY, points[i + 1]);
1364
+ }
1365
+ return minX === Infinity ? null : { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
1366
+ }
1367
+ function union(a, b) {
1368
+ const x = Math.min(a.x, b.x), y = Math.min(a.y, b.y);
1369
+ return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };
1370
+ }
1371
+ function transformPoints(points, m) {
1372
+ const out = new Array(points.length);
1373
+ for (let i = 0; i < points.length; i += 2) {
1374
+ const x = points[i], y = points[i + 1];
1375
+ out[i] = m[0] * x + m[2] * y + m[4];
1376
+ out[i + 1] = m[1] * x + m[3] * y + m[5];
1377
+ }
1378
+ return out;
1379
+ }
1380
+ function dash(subpaths, pattern, offset) {
1381
+ const total = pattern.reduce((a, b) => a + b, 0);
1382
+ const result = [];
1383
+ for (const { points, closed } of subpaths) {
1384
+ const pts = closed ? [...points, points[0], points[1]] : points;
1385
+ let index = 0;
1386
+ let remaining = ((offset % total) + total) % total;
1387
+ while (remaining >= pattern[index]) {
1388
+ remaining -= pattern[index];
1389
+ index = (index + 1) % pattern.length;
1390
+ }
1391
+ remaining = pattern[index] - remaining;
1392
+ let on = index % 2 === 0;
1393
+ let current = on ? [pts[0], pts[1]] : null;
1394
+ for (let i = 0; i + 3 < pts.length; i += 2) {
1395
+ let ax = pts[i], ay = pts[i + 1];
1396
+ const bx = pts[i + 2], by = pts[i + 3];
1397
+ let length = Math.hypot(bx - ax, by - ay);
1398
+ while (length > 0) {
1399
+ if (remaining >= length) {
1400
+ remaining -= length;
1401
+ if (on && current)
1402
+ current.push(bx, by);
1403
+ length = 0;
1404
+ }
1405
+ else {
1406
+ const t = remaining / length;
1407
+ const mx = ax + (bx - ax) * t, my = ay + (by - ay) * t;
1408
+ if (on) {
1409
+ if (current) {
1410
+ current.push(mx, my);
1411
+ result.push({ points: current, closed: false });
1412
+ }
1413
+ current = null;
1414
+ }
1415
+ else
1416
+ current = [mx, my];
1417
+ on = !on;
1418
+ length -= remaining;
1419
+ ax = mx;
1420
+ ay = my;
1421
+ index = (index + 1) % pattern.length;
1422
+ remaining = pattern[index];
1423
+ }
1424
+ }
1425
+ }
1426
+ if (on && current && current.length >= 4)
1427
+ result.push({ points: current, closed: false });
1428
+ }
1429
+ return result;
1430
+ }
1431
+ function strokePolygons(subpaths, halfWidth, cap, join, miterLimit, tolerance) {
1432
+ const polygons = [];
1433
+ const add = (polygon) => {
1434
+ let area = 0;
1435
+ for (let i = 0, n = polygon.length; i < n; i += 2) {
1436
+ const j = (i + 2) % n;
1437
+ area += polygon[i] * polygon[j + 1] - polygon[j] * polygon[i + 1];
1438
+ }
1439
+ if (area < 0) {
1440
+ const reversed = [];
1441
+ for (let i = polygon.length - 2; i >= 0; i -= 2)
1442
+ reversed.push(polygon[i], polygon[i + 1]);
1443
+ polygon = reversed;
1444
+ }
1445
+ polygons.push(polygon);
1446
+ };
1447
+ const circleSegments = Math.max(8, Math.min(256, Math.ceil(Math.PI / Math.acos(Math.max(-1, 1 - tolerance / halfWidth)))));
1448
+ const circle = (cx, cy) => {
1449
+ const polygon = [];
1450
+ for (let i = 0; i < circleSegments; i++) {
1451
+ const a = i / circleSegments * 2 * Math.PI;
1452
+ polygon.push(cx + Math.cos(a) * halfWidth, cy + Math.sin(a) * halfWidth);
1453
+ }
1454
+ add(polygon);
1455
+ };
1456
+ for (const { points: raw, closed } of subpaths) {
1457
+ const points = [raw[0], raw[1]];
1458
+ for (let i = 2; i < raw.length; i += 2)
1459
+ if (Math.abs(raw[i] - points[points.length - 2]) > 1e-9 || Math.abs(raw[i + 1] - points[points.length - 1]) > 1e-9)
1460
+ points.push(raw[i], raw[i + 1]);
1461
+ if (closed && points.length > 4 && Math.abs(points[0] - points[points.length - 2]) < 1e-9 && Math.abs(points[1] - points[points.length - 1]) < 1e-9)
1462
+ points.length -= 2;
1463
+ const n = points.length >> 1;
1464
+ if (n === 1) {
1465
+ const [x, y] = points;
1466
+ if (cap === 'round')
1467
+ circle(x, y);
1468
+ else if (cap === 'square')
1469
+ add([x - halfWidth, y - halfWidth, x + halfWidth, y - halfWidth, x + halfWidth, y + halfWidth, x - halfWidth, y + halfWidth]);
1470
+ continue;
1471
+ }
1472
+ const segmentCount = closed ? n : n - 1;
1473
+ const directions = [];
1474
+ for (let i = 0; i < segmentCount; i++) {
1475
+ const ax = points[i * 2], ay = points[i * 2 + 1];
1476
+ const j = (i + 1) % n;
1477
+ const dx = points[j * 2] - ax, dy = points[j * 2 + 1] - ay;
1478
+ const length = Math.hypot(dx, dy);
1479
+ directions.push([dx / length, dy / length]);
1480
+ const nx = -dy / length * halfWidth, ny = dx / length * halfWidth;
1481
+ const bx = points[j * 2], by = points[j * 2 + 1];
1482
+ add([ax + nx, ay + ny, bx + nx, by + ny, bx - nx, by - ny, ax - nx, ay - ny]);
1483
+ }
1484
+ for (let i = closed ? 0 : 1; i < (closed ? segmentCount : n - 1); i++) {
1485
+ const prev = directions[(i - 1 + segmentCount) % segmentCount], next = directions[i % segmentCount];
1486
+ const vx = points[i * 2], vy = points[i * 2 + 1];
1487
+ const cross = prev[0] * next[1] - prev[1] * next[0];
1488
+ const dot = prev[0] * next[0] + prev[1] * next[1];
1489
+ if (Math.abs(cross) < 1e-9 && dot > 0)
1490
+ continue;
1491
+ if (join === 'round') {
1492
+ circle(vx, vy);
1493
+ continue;
1494
+ }
1495
+ const side = cross > 0 ? -1 : 1;
1496
+ const ax = vx + side * -prev[1] * halfWidth, ay = vy + side * prev[0] * halfWidth;
1497
+ const bx = vx + side * -next[1] * halfWidth, by = vy + side * next[0] * halfWidth;
1498
+ const theta = Math.acos(Math.max(-1, Math.min(1, dot)));
1499
+ const ratio = 1 / Math.sin((Math.PI - theta) / 2);
1500
+ if ((join === 'miter' || join === 'miter-clip' || join === 'arcs') && ratio <= miterLimit && Math.abs(cross) > 1e-9) {
1501
+ const t = ((bx - ax) * next[1] - (by - ay) * next[0]) / (prev[0] * next[1] - prev[1] * next[0]);
1502
+ add([vx, vy, ax, ay, ax + prev[0] * t, ay + prev[1] * t, bx, by]);
1503
+ }
1504
+ else
1505
+ add([vx, vy, ax, ay, bx, by]);
1506
+ }
1507
+ if (!closed) {
1508
+ for (const [index, direction, sign] of [[0, directions[0], -1], [n - 1, directions[segmentCount - 1], 1]]) {
1509
+ const x = points[index * 2], y = points[index * 2 + 1];
1510
+ if (cap === 'round')
1511
+ circle(x, y);
1512
+ else if (cap === 'square') {
1513
+ const dx = direction[0] * halfWidth * sign, dy = direction[1] * halfWidth * sign;
1514
+ const nx = -direction[1] * halfWidth, ny = direction[0] * halfWidth;
1515
+ add([x + nx, y + ny, x + nx + dx, y + ny + dy, x - nx + dx, y - ny + dy, x - nx, y - ny]);
1516
+ }
1517
+ }
1518
+ }
1519
+ }
1520
+ return polygons;
1521
+ }
1522
+ function multiply(a, b) {
1523
+ return [
1524
+ a[0] * b[0] + a[2] * b[1], a[1] * b[0] + a[3] * b[1],
1525
+ a[0] * b[2] + a[2] * b[3], a[1] * b[2] + a[3] * b[3],
1526
+ a[0] * b[4] + a[2] * b[5] + a[4], a[1] * b[4] + a[3] * b[5] + a[5]
1527
+ ];
1528
+ }
1529
+ function invert(m) {
1530
+ const det = m[0] * m[3] - m[1] * m[2];
1531
+ if (!det || !isFinite(det))
1532
+ return null;
1533
+ return [m[3] / det, -m[1] / det, -m[2] / det, m[0] / det, (m[2] * m[5] - m[3] * m[4]) / det, (m[1] * m[4] - m[0] * m[5]) / det];
1534
+ }
1535
+ function parseTransform(value) {
1536
+ let matrix = [1, 0, 0, 1, 0, 0];
1537
+ const functions = /(matrix|translate|scale|rotate|skewX|skewY)\s*\(([^)]*)\)/g;
1538
+ for (const [, name, args] of value.matchAll(functions)) {
1539
+ const a = parseNumberList(args);
1540
+ let m = [1, 0, 0, 1, 0, 0];
1541
+ switch (name) {
1542
+ case 'matrix':
1543
+ if (a.length !== 6)
1544
+ return [1, 0, 0, 1, 0, 0];
1545
+ m = a;
1546
+ break;
1547
+ case 'translate':
1548
+ m = [1, 0, 0, 1, a[0] || 0, a[1] || 0];
1549
+ break;
1550
+ case 'scale':
1551
+ m = [a[0], 0, 0, a.length > 1 ? a[1] : a[0], 0, 0];
1552
+ break;
1553
+ case 'rotate': {
1554
+ const r = (a[0] || 0) * Math.PI / 180;
1555
+ const cos = Math.cos(r), sin = Math.sin(r);
1556
+ m = [cos, sin, -sin, cos, 0, 0];
1557
+ if (a.length === 3)
1558
+ m = multiply(multiply([1, 0, 0, 1, a[1], a[2]], m), [1, 0, 0, 1, -a[1], -a[2]]);
1559
+ break;
1560
+ }
1561
+ case 'skewX':
1562
+ m = [1, 0, Math.tan((a[0] || 0) * Math.PI / 180), 1, 0, 0];
1563
+ break;
1564
+ case 'skewY':
1565
+ m = [1, Math.tan((a[0] || 0) * Math.PI / 180), 0, 1, 0, 0];
1566
+ break;
1567
+ }
1568
+ if (m.some(v => !isFinite(v)))
1569
+ return [1, 0, 0, 1, 0, 0];
1570
+ matrix = multiply(matrix, m);
1571
+ }
1572
+ return matrix;
1573
+ }
1574
+ function parseViewBox(value) {
1575
+ if (!value)
1576
+ return null;
1577
+ const v = parseNumberList(value);
1578
+ return v.length === 4 && v[2] > 0 && v[3] > 0 ? v : null;
1579
+ }
1580
+ function viewBoxTransform([vx, vy, vw, vh], width, height, preserve = 'xMidYMid meet') {
1581
+ const parts = preserve.trim().split(/\s+/);
1582
+ const align = parts[0] === 'defer' ? parts[1] : parts[0];
1583
+ const slice = parts[parts.length - 1] === 'slice';
1584
+ let sx = width / vw, sy = height / vh;
1585
+ if (align === 'none')
1586
+ return [sx, 0, 0, sy, -vx * sx, -vy * sy];
1587
+ sx = sy = slice ? Math.max(sx, sy) : Math.min(sx, sy);
1588
+ let tx = -vx * sx, ty = -vy * sy;
1589
+ const extraX = width - vw * sx, extraY = height - vh * sy;
1590
+ if (/xMid/.test(align))
1591
+ tx += extraX / 2;
1592
+ else if (/xMax/.test(align))
1593
+ tx += extraX;
1594
+ if (/YMid/.test(align))
1595
+ ty += extraY / 2;
1596
+ else if (/YMax/.test(align))
1597
+ ty += extraY;
1598
+ if (!/^x(Min|Mid|Max)Y(Min|Mid|Max)$/.test(align)) {
1599
+ tx = -vx * sx + extraX / 2;
1600
+ ty = -vy * sy + extraY / 2;
1601
+ }
1602
+ return [sx, 0, 0, sy, tx, ty];
1603
+ }
1604
+ const UNITS = { px: 1, pt: 4 / 3, pc: 16, mm: 96 / 25.4, cm: 96 / 2.54, in: 96 };
1605
+ function parseLength(value, viewport, fontSize = 16, axis = 'x') {
1606
+ if (value === undefined || value === null)
1607
+ return 0;
1608
+ const match = /^\s*([-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?)\s*(px|pt|pc|mm|cm|in|em|ex|%)?\s*$/.exec(String(value));
1609
+ if (!match)
1610
+ return 0;
1611
+ const number = parseFloat(match[1]);
1612
+ const unit = match[2];
1613
+ if (!unit)
1614
+ return number;
1615
+ if (unit === 'em')
1616
+ return number * fontSize;
1617
+ if (unit === 'ex')
1618
+ return number * fontSize / 2;
1619
+ if (unit === '%') {
1620
+ if (!viewport)
1621
+ return 0;
1622
+ const reference = axis === 'x' ? viewport.width : axis === 'y' ? viewport.height
1623
+ : Math.hypot(viewport.width, viewport.height) / Math.SQRT2;
1624
+ return number / 100 * reference;
1625
+ }
1626
+ return number * UNITS[unit];
1627
+ }
1628
+ function parseNumber(value, fallback) {
1629
+ const v = parseStrictNumber(value);
1630
+ return v === null ? fallback : v;
1631
+ }
1632
+ function parseStrictNumber(value) {
1633
+ if (value === undefined || value === null)
1634
+ return null;
1635
+ const match = /^\s*([-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?)\s*(%?)\s*$/.exec(String(value));
1636
+ if (!match)
1637
+ return null;
1638
+ return match[2] ? parseFloat(match[1]) / 100 : parseFloat(match[1]);
1639
+ }
1640
+ function parseNumberList(value) {
1641
+ return [...String(value).matchAll(/[-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?/g)].map(m => parseFloat(m[0]));
1642
+ }
1643
+ function parseDashArray(value, viewport, fontSize) {
1644
+ if (!value || value === 'none')
1645
+ return null;
1646
+ let dashes = value.split(/[\s,]+/).filter(Boolean).map(v => parseLength(v, viewport, fontSize, 'diagonal'));
1647
+ if (!dashes.length || dashes.some(d => d < 0 || isNaN(d)))
1648
+ return null;
1649
+ if (dashes.length % 2)
1650
+ dashes = [...dashes, ...dashes];
1651
+ return dashes.reduce((a, b) => a + b, 0) > 0 ? dashes : null;
1652
+ }
1653
+ function clamp01(v) {
1654
+ return v < 0 ? 0 : v > 1 ? 1 : v;
1655
+ }
1656
+ function parseColor(value) {
1657
+ if (!value)
1658
+ return null;
1659
+ value = value.trim().toLowerCase();
1660
+ if (value === 'transparent')
1661
+ return [0, 0, 0, 0];
1662
+ let match = /^#([0-9a-f]{3,8})$/.exec(value);
1663
+ if (match) {
1664
+ const hex = match[1];
1665
+ if (hex.length === 3 || hex.length === 4)
1666
+ return [...hex].map(c => parseInt(c + c, 16) / 255).concat(hex.length === 3 ? [1] : []);
1667
+ if (hex.length === 6 || hex.length === 8)
1668
+ return [0, 2, 4, 6].slice(0, hex.length / 2).map(i => parseInt(hex.slice(i, i + 2), 16) / 255).concat(hex.length === 6 ? [1] : []);
1669
+ return null;
1670
+ }
1671
+ match = /^(rgba?|hsla?)\(\s*([^)]*)\)$/.exec(value);
1672
+ if (match) {
1673
+ const parts = match[2].split(/\s*[,/]\s*|\s+/).filter(Boolean);
1674
+ if (parts.length < 3)
1675
+ return null;
1676
+ const alpha = parts.length > 3 ? clamp01(parseNumber(parts[3], 1)) : 1;
1677
+ if (match[1].startsWith('rgb')) {
1678
+ const channel = (p) => clamp01(/%$/.test(p) ? parseFloat(p) / 100 : parseFloat(p) / 255);
1679
+ return [channel(parts[0]), channel(parts[1]), channel(parts[2]), alpha];
1680
+ }
1681
+ const h = (((parseFloat(parts[0]) % 360) + 360) % 360) / 360;
1682
+ const s = clamp01(parseFloat(parts[1]) / 100), l = clamp01(parseFloat(parts[2]) / 100);
1683
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s, p = 2 * l - q;
1684
+ const hue = (t) => {
1685
+ t = (t + 1) % 1;
1686
+ return t < 1 / 6 ? p + (q - p) * 6 * t : t < 1 / 2 ? q : t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 : p;
1687
+ };
1688
+ return [hue(h + 1 / 3), hue(h), hue(h - 1 / 3), alpha];
1689
+ }
1690
+ const named = NAMED_COLORS[value];
1691
+ return named === undefined ? null : [(named >> 16 & 255) / 255, (named >> 8 & 255) / 255, (named & 255) / 255, 1];
1692
+ }
1693
+ const NAMED_COLORS = {
1694
+ aliceblue: 0xf0f8ff, antiquewhite: 0xfaebd7, aqua: 0x00ffff, aquamarine: 0x7fffd4, azure: 0xf0ffff, beige: 0xf5f5dc, bisque: 0xffe4c4,
1695
+ black: 0x000000, blanchedalmond: 0xffebcd, blue: 0x0000ff, blueviolet: 0x8a2be2, brown: 0xa52a2a, burlywood: 0xdeb887, cadetblue: 0x5f9ea0,
1696
+ chartreuse: 0x7fff00, chocolate: 0xd2691e, coral: 0xff7f50, cornflowerblue: 0x6495ed, cornsilk: 0xfff8dc, crimson: 0xdc143c, cyan: 0x00ffff,
1697
+ darkblue: 0x00008b, darkcyan: 0x008b8b, darkgoldenrod: 0xb8860b, darkgray: 0xa9a9a9, darkgreen: 0x006400, darkgrey: 0xa9a9a9, darkkhaki: 0xbdb76b,
1698
+ darkmagenta: 0x8b008b, darkolivegreen: 0x556b2f, darkorange: 0xff8c00, darkorchid: 0x9932cc, darkred: 0x8b0000, darksalmon: 0xe9967a,
1699
+ darkseagreen: 0x8fbc8f, darkslateblue: 0x483d8b, darkslategray: 0x2f4f4f, darkslategrey: 0x2f4f4f, darkturquoise: 0x00ced1, darkviolet: 0x9400d3,
1700
+ deeppink: 0xff1493, deepskyblue: 0x00bfff, dimgray: 0x696969, dimgrey: 0x696969, dodgerblue: 0x1e90ff, firebrick: 0xb22222, floralwhite: 0xfffaf0,
1701
+ forestgreen: 0x228b22, fuchsia: 0xff00ff, gainsboro: 0xdcdcdc, ghostwhite: 0xf8f8ff, gold: 0xffd700, goldenrod: 0xdaa520, gray: 0x808080,
1702
+ grey: 0x808080, green: 0x008000, greenyellow: 0xadff2f, honeydew: 0xf0fff0, hotpink: 0xff69b4, indianred: 0xcd5c5c, indigo: 0x4b0082,
1703
+ ivory: 0xfffff0, khaki: 0xf0e68c, lavender: 0xe6e6fa, lavenderblush: 0xfff0f5, lawngreen: 0x7cfc00, lemonchiffon: 0xfffacd, lightblue: 0xadd8e6,
1704
+ lightcoral: 0xf08080, lightcyan: 0xe0ffff, lightgoldenrodyellow: 0xfafad2, lightgray: 0xd3d3d3, lightgreen: 0x90ee90, lightgrey: 0xd3d3d3,
1705
+ lightpink: 0xffb6c1, lightsalmon: 0xffa07a, lightseagreen: 0x20b2aa, lightskyblue: 0x87cefa, lightslategray: 0x778899, lightslategrey: 0x778899,
1706
+ lightsteelblue: 0xb0c4de, lightyellow: 0xffffe0, lime: 0x00ff00, limegreen: 0x32cd32, linen: 0xfaf0e6, magenta: 0xff00ff, maroon: 0x800000,
1707
+ mediumaquamarine: 0x66cdaa, mediumblue: 0x0000cd, mediumorchid: 0xba55d3, mediumpurple: 0x9370db, mediumseagreen: 0x3cb371,
1708
+ mediumslateblue: 0x7b68ee, mediumspringgreen: 0x00fa9a, mediumturquoise: 0x48d1cc, mediumvioletred: 0xc71585, midnightblue: 0x191970,
1709
+ mintcream: 0xf5fffa, mistyrose: 0xffe4e1, moccasin: 0xffe4b5, navajowhite: 0xffdead, navy: 0x000080, oldlace: 0xfdf5e6, olive: 0x808000,
1710
+ olivedrab: 0x6b8e23, orange: 0xffa500, orangered: 0xff4500, orchid: 0xda70d6, palegoldenrod: 0xeee8aa, palegreen: 0x98fb98,
1711
+ paleturquoise: 0xafeeee, palevioletred: 0xdb7093, papayawhip: 0xffefd5, peachpuff: 0xffdab9, peru: 0xcd853f, pink: 0xffc0cb, plum: 0xdda0dd,
1712
+ powderblue: 0xb0e0e6, purple: 0x800080, rebeccapurple: 0x663399, red: 0xff0000, rosybrown: 0xbc8f8f, royalblue: 0x4169e1, saddlebrown: 0x8b4513,
1713
+ salmon: 0xfa8072, sandybrown: 0xf4a460, seagreen: 0x2e8b57, seashell: 0xfff5ee, sienna: 0xa0522d, silver: 0xc0c0c0, skyblue: 0x87ceeb,
1714
+ slateblue: 0x6a5acd, slategray: 0x708090, slategrey: 0x708090, snow: 0xfffafa, springgreen: 0x00ff7f, steelblue: 0x4682b4, tan: 0xd2b48c,
1715
+ teal: 0x008080, thistle: 0xd8bfd8, tomato: 0xff6347, turquoise: 0x40e0d0, violet: 0xee82ee, wheat: 0xf5deb3, white: 0xffffff,
1716
+ whitesmoke: 0xf5f5f5, yellow: 0xffff00, yellowgreen: 0x9acd32
1717
+ };