@flighthq/scene2d-formats 0.2.1-next.840.857425c
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/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/lottieDocument.d.ts +8 -0
- package/dist/lottieDocument.d.ts.map +1 -0
- package/dist/lottieDocument.js +894 -0
- package/dist/lottieDocument.js.map +1 -0
- package/dist/svgDocument.d.ts +14 -0
- package/dist/svgDocument.d.ts.map +1 -0
- package/dist/svgDocument.js +1124 -0
- package/dist/svgDocument.js.map +1 -0
- package/package.json +57 -0
- package/src/lottieDocument.test.ts +429 -0
- package/src/svgDocument.test.ts +535 -0
- package/src/svgDocumentConformance.test.ts +539 -0
|
@@ -0,0 +1,1124 @@
|
|
|
1
|
+
import { createClipRegionFromPath, intersectClipRegions, transformClipRegion, unionClipRegions } from '@flighthq/clip';
|
|
2
|
+
import { packColor } from '@flighthq/color';
|
|
3
|
+
import { createGradientTransformMatrix, createMatrix, createRectangle, createTransform2D, decomposeMatrixToTransform2D, matrixTransformRectangle, multiplyMatrix, } from '@flighthq/geometry';
|
|
4
|
+
import { reportImportDiagnostic } from '@flighthq/importdiagnostics';
|
|
5
|
+
import { addNodeChild, getNodeChildAt, getNodeChildCount, getNodeLocalBoundsRectangle, getNodeLocalMatrix, } from '@flighthq/node';
|
|
6
|
+
import { appendPathCircle, appendPathEllipse, appendPathLineTo, appendPathMoveTo, appendPathPolygon, appendPathPolyline, appendPathRectangle, appendPathRoundRectangle, createPath, dashPath, getPathBounds, transformPath, } from '@flighthq/path';
|
|
7
|
+
import { parseSvgPathData } from '@flighthq/path-formats';
|
|
8
|
+
import { createBitmap, createDisplayObject } from '@flighthq/scene2d';
|
|
9
|
+
import { appendShapeBeginFill, appendShapeBeginGradientFill, appendShapeEndFill, appendShapeLineGradientStyle, appendShapeLineStyle, appendShapePath, createShape, } from '@flighthq/shape';
|
|
10
|
+
import { createRichText, createTextLabel } from '@flighthq/text';
|
|
11
|
+
import { createTextFormatRange } from '@flighthq/textlayout';
|
|
12
|
+
import { ImportDiagnosticSeverity, RichTextKind, TextLabelKind } from '@flighthq/types';
|
|
13
|
+
import { parseXmlDocument } from '@flighthq/xml';
|
|
14
|
+
/**
|
|
15
|
+
* Imports a static SVG document into a Flight display-object subtree. The returned container is
|
|
16
|
+
* always allocated; malformed or non-SVG input yields an empty container and an opt-in Reject
|
|
17
|
+
* diagnostic. The importer covers the document concerns above `@flighthq/path-formats`: SVG
|
|
18
|
+
* geometry, inherited presentation styles, basic stylesheet selectors, transforms, gradients,
|
|
19
|
+
* groups, nested SVG view boxes, `defs`/`use`, text, clip paths, and hard-mask degradation.
|
|
20
|
+
*
|
|
21
|
+
* SVG is consumed as a static authoring artifact. Animation, filters, scripting, foreign objects,
|
|
22
|
+
* and live DOM behavior are deliberately not retained. Pass an `ImportDiagnostic[]` collector to
|
|
23
|
+
* observe every recognized feature that was skipped or recovered.
|
|
24
|
+
*/
|
|
25
|
+
export function createScene2DFromSvgDocument(source, diagnostics, options) {
|
|
26
|
+
const out = createDisplayObject();
|
|
27
|
+
const document = parseXmlDocument(source);
|
|
28
|
+
if (document === null || localName(document.name) !== 'svg') {
|
|
29
|
+
reportImportDiagnostic(diagnostics, ImportDiagnosticSeverity.Reject, 'svg.invalid-document', 'createScene2DFromSvgDocument');
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
const context = {
|
|
33
|
+
cssRules: collectCssRules(document),
|
|
34
|
+
diagnostics,
|
|
35
|
+
elementsById: new Map(),
|
|
36
|
+
gradientsById: new Map(),
|
|
37
|
+
options,
|
|
38
|
+
parentByElement: new Map(),
|
|
39
|
+
reportedUnsupportedElements: new Set(),
|
|
40
|
+
resolvingClipUses: new Set(),
|
|
41
|
+
resolvingClips: new Set(),
|
|
42
|
+
resolvingGradients: new Set(),
|
|
43
|
+
resolvingUses: new Set(),
|
|
44
|
+
resolvedDefinitionStyles: new Map(),
|
|
45
|
+
};
|
|
46
|
+
indexSvgDefinitions(document, context);
|
|
47
|
+
const viewport = createSvgViewportMatrix(document);
|
|
48
|
+
const rootStyle = applySvgElementAppearance(out, document, defaultSvgStyle, context, viewport, true);
|
|
49
|
+
appendSvgChildren(out, document, rootStyle, context);
|
|
50
|
+
applySvgElementClip(out, document, context, createSvgNode2DBounds(out));
|
|
51
|
+
reportRemainingUnsupportedSvgElements(document, context);
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
const defaultSvgStyle = {
|
|
55
|
+
clipRule: 'nonZero',
|
|
56
|
+
color: '#000000',
|
|
57
|
+
display: 'inline',
|
|
58
|
+
fill: '#000000',
|
|
59
|
+
fillOpacity: 1,
|
|
60
|
+
fillRule: 'nonZero',
|
|
61
|
+
filter: 'none',
|
|
62
|
+
fontFamily: 'sans-serif',
|
|
63
|
+
fontSize: 16,
|
|
64
|
+
fontStyle: 'normal',
|
|
65
|
+
fontWeight: 'normal',
|
|
66
|
+
opacity: 1,
|
|
67
|
+
stroke: 'none',
|
|
68
|
+
strokeDasharray: 'none',
|
|
69
|
+
strokeDashoffset: 0,
|
|
70
|
+
strokeLinecap: 'butt',
|
|
71
|
+
strokeLinejoin: 'miter',
|
|
72
|
+
strokeMiterlimit: 4,
|
|
73
|
+
strokeOpacity: 1,
|
|
74
|
+
strokeWidth: 1,
|
|
75
|
+
textAnchor: 'start',
|
|
76
|
+
visibility: 'visible',
|
|
77
|
+
};
|
|
78
|
+
function appendSvgChildren(parent, element, parentStyle, context) {
|
|
79
|
+
for (const child of element.children) {
|
|
80
|
+
const node = createSvgElementNode(child, parentStyle, context);
|
|
81
|
+
if (node !== null)
|
|
82
|
+
addNodeChild(parent, node);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function applySvgElementAppearance(target, element, parentStyle, context, geometryTransform = null, hasVisualDescendants = false) {
|
|
86
|
+
const style = resolveSvgStyle(element, parentStyle, context);
|
|
87
|
+
target.alpha = style.opacity;
|
|
88
|
+
target.visible =
|
|
89
|
+
style.display !== 'none' &&
|
|
90
|
+
(hasVisualDescendants || (style.visibility !== 'hidden' && style.visibility !== 'collapse'));
|
|
91
|
+
target.name = attribute(element, 'id');
|
|
92
|
+
if (style.filter !== 'none') {
|
|
93
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Skip, 'svg.unsupported-filter', 'applySvgElementAppearance', { filter: style.filter });
|
|
94
|
+
}
|
|
95
|
+
const authorTransform = parseSvgTransform(attribute(element, 'transform'));
|
|
96
|
+
const transform = authorTransform === null
|
|
97
|
+
? geometryTransform
|
|
98
|
+
: geometryTransform === null
|
|
99
|
+
? authorTransform
|
|
100
|
+
: multiplySvgMatrices(authorTransform, geometryTransform);
|
|
101
|
+
applySvgTransform(target, transform);
|
|
102
|
+
return style;
|
|
103
|
+
}
|
|
104
|
+
function applySvgElementClip(target, element, context, targetBounds) {
|
|
105
|
+
const clipReference = parseUrlReference(attribute(element, 'clip-path'));
|
|
106
|
+
const maskReference = parseUrlReference(attribute(element, 'mask'));
|
|
107
|
+
const clipId = clipReference ?? maskReference;
|
|
108
|
+
if (clipId === null)
|
|
109
|
+
return;
|
|
110
|
+
const clipElement = context.elementsById.get(clipId);
|
|
111
|
+
if (clipElement === undefined) {
|
|
112
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Drop, 'svg.unresolved-clip-reference', 'applySvgElementClip', { id: clipId });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (usesSvgObjectBoundingBoxUnits(clipElement) && targetBounds === null) {
|
|
116
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Skip, 'svg.object-bounding-box-clip-without-bounds', 'applySvgElementClip', { id: clipId });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
target.clip = createSvgClipRegion(clipElement, targetBounds, context);
|
|
120
|
+
if (maskReference !== null) {
|
|
121
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Recover, 'svg.mask-as-hard-clip', 'applySvgElementClip', { id: maskReference });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function applySvgTransform(target, matrix) {
|
|
125
|
+
if (matrix === null)
|
|
126
|
+
return;
|
|
127
|
+
const transform = createTransform2D();
|
|
128
|
+
decomposeMatrixToTransform2D(transform, matrix);
|
|
129
|
+
assignSvgTransform(target, transform);
|
|
130
|
+
}
|
|
131
|
+
function assignSvgTransform(target, transform) {
|
|
132
|
+
target.pivotX = transform.pivotX;
|
|
133
|
+
target.pivotY = transform.pivotY;
|
|
134
|
+
target.rotation = transform.rotation;
|
|
135
|
+
target.scaleX = transform.scaleX;
|
|
136
|
+
target.scaleY = transform.scaleY;
|
|
137
|
+
target.skewX = transform.skewX;
|
|
138
|
+
target.skewY = transform.skewY;
|
|
139
|
+
target.x = transform.x;
|
|
140
|
+
target.y = transform.y;
|
|
141
|
+
}
|
|
142
|
+
function attribute(element, name) {
|
|
143
|
+
return element.attributes[name] ?? element.attributes[`xlink:${name}`] ?? null;
|
|
144
|
+
}
|
|
145
|
+
function collectCssRules(root) {
|
|
146
|
+
const rules = [];
|
|
147
|
+
visitXmlElements(root, (element) => {
|
|
148
|
+
if (localName(element.name) !== 'style')
|
|
149
|
+
return;
|
|
150
|
+
const text = element.text.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
151
|
+
const expression = /([^{}]+)\{([^{}]*)\}/g;
|
|
152
|
+
let match;
|
|
153
|
+
while ((match = expression.exec(text)) !== null) {
|
|
154
|
+
const declarations = parseStyleDeclarations(match[2]);
|
|
155
|
+
for (const selectorText of match[1].split(',')) {
|
|
156
|
+
const selector = selectorText.trim();
|
|
157
|
+
if (selector === '' || /[\s>+~:[\]]/.test(selector))
|
|
158
|
+
continue;
|
|
159
|
+
rules.push({ declarations, order: rules.length, selector, specificity: getCssSelectorSpecificity(selector) });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
return rules;
|
|
164
|
+
}
|
|
165
|
+
function createSvgNode2DBounds(target) {
|
|
166
|
+
if (target.kind === TextLabelKind || target.kind === RichTextKind)
|
|
167
|
+
return null;
|
|
168
|
+
const out = createRectangle();
|
|
169
|
+
let hasBounds = copyNonEmptySvgBounds(out, getNodeLocalBoundsRectangle(target), false);
|
|
170
|
+
let hasUnresolvedChildBounds = false;
|
|
171
|
+
const childCount = getNodeChildCount(target);
|
|
172
|
+
for (let index = 0; index < childCount; index++) {
|
|
173
|
+
const child = getNodeChildAt(target, index);
|
|
174
|
+
if (child === null)
|
|
175
|
+
continue;
|
|
176
|
+
const childBounds = createSvgNode2DBounds(child);
|
|
177
|
+
if (childBounds === null) {
|
|
178
|
+
hasUnresolvedChildBounds = true;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const bounds = createRectangle();
|
|
182
|
+
matrixTransformRectangle(bounds, getNodeLocalMatrix(child), childBounds);
|
|
183
|
+
hasBounds = copyNonEmptySvgBounds(out, bounds, hasBounds);
|
|
184
|
+
}
|
|
185
|
+
return hasBounds && !hasUnresolvedChildBounds ? out : null;
|
|
186
|
+
}
|
|
187
|
+
function createSvgClipRegion(element, targetBounds, context) {
|
|
188
|
+
const clipStyle = resolveSvgDefinitionStyle(element, context);
|
|
189
|
+
const out = createPath(clipStyle.clipRule);
|
|
190
|
+
const geometries = [];
|
|
191
|
+
collectSvgClipGeometry(element, clipStyle, null, context, geometries);
|
|
192
|
+
let winding = null;
|
|
193
|
+
let mixedWindingReported = false;
|
|
194
|
+
const clippedRegions = [];
|
|
195
|
+
for (const geometry of geometries) {
|
|
196
|
+
if (winding === null) {
|
|
197
|
+
winding = geometry.winding;
|
|
198
|
+
out.winding = winding;
|
|
199
|
+
}
|
|
200
|
+
else if (geometry.winding !== winding && !mixedWindingReported) {
|
|
201
|
+
mixedWindingReported = true;
|
|
202
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Recover, 'svg.mixed-clip-rule', 'createSvgClipPath', { id: attribute(element, 'id') ?? '' });
|
|
203
|
+
}
|
|
204
|
+
if (geometry.path !== null)
|
|
205
|
+
appendPathData(out, geometry.path);
|
|
206
|
+
if (geometry.region !== null)
|
|
207
|
+
clippedRegions.push(geometry.region);
|
|
208
|
+
}
|
|
209
|
+
let region = createClipRegionFromPath(out);
|
|
210
|
+
for (const clippedRegion of clippedRegions) {
|
|
211
|
+
if (out.commands.length === 0 && region.rect.width === 0 && region.rect.height === 0)
|
|
212
|
+
region = clippedRegion;
|
|
213
|
+
else
|
|
214
|
+
unionClipRegions(region, region, clippedRegion);
|
|
215
|
+
}
|
|
216
|
+
region = intersectSvgClipReference(region, element, null, context);
|
|
217
|
+
let transform = parseSvgTransform(attribute(element, 'transform'));
|
|
218
|
+
const unitsAttribute = localName(element.name) === 'mask' ? attribute(element, 'maskContentUnits') : attribute(element, 'clipPathUnits');
|
|
219
|
+
if (unitsAttribute === 'objectBoundingBox' && targetBounds !== null) {
|
|
220
|
+
const unitTransform = createMatrix(targetBounds.width, 0, 0, targetBounds.height, targetBounds.x, targetBounds.y);
|
|
221
|
+
transform = transform === null ? unitTransform : multiplySvgMatrices(unitTransform, transform);
|
|
222
|
+
}
|
|
223
|
+
if (transform === null)
|
|
224
|
+
return region;
|
|
225
|
+
transformClipRegion(region, region, transform);
|
|
226
|
+
return region;
|
|
227
|
+
}
|
|
228
|
+
function collectSvgClipGeometry(element, parentStyle, parentTransform, context, out) {
|
|
229
|
+
for (const child of element.children) {
|
|
230
|
+
collectSvgClipGeometryElement(child, parentStyle, parentTransform, context, out);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function collectSvgClipGeometryElement(element, parentStyle, parentTransform, context, out, viewportElement) {
|
|
234
|
+
const style = resolveSvgStyle(element, parentStyle, context);
|
|
235
|
+
if (style.display === 'none')
|
|
236
|
+
return;
|
|
237
|
+
const name = localName(element.name);
|
|
238
|
+
let geometryTransform = null;
|
|
239
|
+
if (name === 'use') {
|
|
240
|
+
geometryTransform = createMatrix(1, 0, 0, 1, numberAttribute(element, 'x', 0), numberAttribute(element, 'y', 0));
|
|
241
|
+
}
|
|
242
|
+
else if (name === 'svg') {
|
|
243
|
+
geometryTransform = createSvgViewportMatrix(element);
|
|
244
|
+
}
|
|
245
|
+
else if (name === 'symbol' && viewportElement !== undefined) {
|
|
246
|
+
geometryTransform = createSvgViewportMatrix(element, {
|
|
247
|
+
height: optionalNumberAttribute(viewportElement, 'height') ?? undefined,
|
|
248
|
+
width: optionalNumberAttribute(viewportElement, 'width') ?? undefined,
|
|
249
|
+
x: 0,
|
|
250
|
+
y: 0,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const authorTransform = parseSvgTransform(attribute(element, 'transform'));
|
|
254
|
+
const localTransform = authorTransform === null
|
|
255
|
+
? geometryTransform
|
|
256
|
+
: geometryTransform === null
|
|
257
|
+
? authorTransform
|
|
258
|
+
: multiplySvgMatrices(authorTransform, geometryTransform);
|
|
259
|
+
const transform = parentTransform === null
|
|
260
|
+
? localTransform
|
|
261
|
+
: localTransform === null
|
|
262
|
+
? parentTransform
|
|
263
|
+
: multiplySvgMatrices(parentTransform, localTransform);
|
|
264
|
+
if (name === 'use') {
|
|
265
|
+
collectSvgClipUseGeometry(element, style, transform, context, out);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const path = createSvgGeometryPath(element, style.clipRule);
|
|
269
|
+
if (path !== null) {
|
|
270
|
+
if (style.visibility !== 'visible')
|
|
271
|
+
return;
|
|
272
|
+
let transformedPath = path;
|
|
273
|
+
if (transform !== null) {
|
|
274
|
+
const transformed = createPath(path.winding);
|
|
275
|
+
transformPath(path, transform, transformed);
|
|
276
|
+
transformedPath = transformed;
|
|
277
|
+
}
|
|
278
|
+
if (parseUrlReference(attribute(element, 'clip-path')) === null) {
|
|
279
|
+
out.push({ path: transformedPath, region: null, winding: path.winding });
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
const region = intersectSvgClipReference(createClipRegionFromPath(transformedPath), element, transform, context);
|
|
283
|
+
out.push({ path: null, region, winding: region.winding });
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (name === 'a' || name === 'g' || name === 'svg' || name === 'switch' || name === 'symbol') {
|
|
288
|
+
const start = out.length;
|
|
289
|
+
collectSvgClipGeometry(element, style, transform, context, out);
|
|
290
|
+
if (parseUrlReference(attribute(element, 'clip-path')) !== null) {
|
|
291
|
+
for (let index = start; index < out.length; index++) {
|
|
292
|
+
const geometry = out[index];
|
|
293
|
+
const region = geometry.region ?? createClipRegionFromPath(geometry.path);
|
|
294
|
+
geometry.path = null;
|
|
295
|
+
geometry.region = intersectSvgClipReference(region, element, transform, context);
|
|
296
|
+
geometry.winding = geometry.region.winding;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (name === 'text') {
|
|
302
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Skip, 'svg.unsupported-clip-text', 'collectSvgClipGeometryElement', { element: name });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function intersectSvgClipReference(base, element, transform, context) {
|
|
306
|
+
const id = parseUrlReference(attribute(element, 'clip-path'));
|
|
307
|
+
if (id === null)
|
|
308
|
+
return base;
|
|
309
|
+
const referenced = context.elementsById.get(id);
|
|
310
|
+
if (referenced === undefined || context.resolvingClips.has(id)) {
|
|
311
|
+
reportImportDiagnostic(context.diagnostics, context.resolvingClips.has(id) ? ImportDiagnosticSeverity.Reject : ImportDiagnosticSeverity.Drop, 'svg.unresolved-clip-reference', 'intersectSvgClipReference', { id });
|
|
312
|
+
return base;
|
|
313
|
+
}
|
|
314
|
+
if (transform !== null && usesSvgObjectBoundingBoxUnits(referenced)) {
|
|
315
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Skip, 'svg.clip-nested-intersection-unsupported', 'intersectSvgClipReference', { id, reason: 'transformed-object-bounding-box-target' });
|
|
316
|
+
return base;
|
|
317
|
+
}
|
|
318
|
+
context.resolvingClips.add(id);
|
|
319
|
+
const referencedRegion = createSvgClipRegion(referenced, base.rect, context);
|
|
320
|
+
context.resolvingClips.delete(id);
|
|
321
|
+
if (transform !== null)
|
|
322
|
+
transformClipRegion(referencedRegion, referencedRegion, transform);
|
|
323
|
+
intersectClipRegions(base, base, referencedRegion);
|
|
324
|
+
return base;
|
|
325
|
+
}
|
|
326
|
+
function collectSvgClipUseGeometry(element, style, transform, context, out) {
|
|
327
|
+
const href = attribute(element, 'href');
|
|
328
|
+
const id = href?.startsWith('#') === true ? href.slice(1) : null;
|
|
329
|
+
if (id === null || context.resolvingClipUses.has(id)) {
|
|
330
|
+
reportImportDiagnostic(context.diagnostics, id === null ? ImportDiagnosticSeverity.Drop : ImportDiagnosticSeverity.Reject, id === null ? 'svg.unresolved-use' : 'svg.recursive-use', 'collectSvgClipUseGeometry', id === null ? undefined : { id });
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const referenced = context.elementsById.get(id);
|
|
334
|
+
if (referenced === undefined) {
|
|
335
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Drop, 'svg.unresolved-use', 'collectSvgClipUseGeometry', { id });
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
context.resolvingClipUses.add(id);
|
|
339
|
+
collectSvgClipGeometryElement(referenced, style, transform, context, out, element);
|
|
340
|
+
context.resolvingClipUses.delete(id);
|
|
341
|
+
}
|
|
342
|
+
function copyNonEmptySvgBounds(out, source, hasBounds) {
|
|
343
|
+
if (source.width === 0 || source.height === 0)
|
|
344
|
+
return hasBounds;
|
|
345
|
+
if (!hasBounds) {
|
|
346
|
+
out.x = source.x;
|
|
347
|
+
out.y = source.y;
|
|
348
|
+
out.width = source.width;
|
|
349
|
+
out.height = source.height;
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
const minimumX = Math.min(out.x, source.x);
|
|
353
|
+
const minimumY = Math.min(out.y, source.y);
|
|
354
|
+
const maximumX = Math.max(out.x + out.width, source.x + source.width);
|
|
355
|
+
const maximumY = Math.max(out.y + out.height, source.y + source.height);
|
|
356
|
+
out.x = minimumX;
|
|
357
|
+
out.y = minimumY;
|
|
358
|
+
out.width = maximumX - minimumX;
|
|
359
|
+
out.height = maximumY - minimumY;
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
function createSvgElementNode(element, parentStyle, context) {
|
|
363
|
+
const name = localName(element.name);
|
|
364
|
+
if (name === 'defs' || name === 'style' || name === 'title' || name === 'desc' || name === 'metadata')
|
|
365
|
+
return null;
|
|
366
|
+
if (name === 'linearGradient' ||
|
|
367
|
+
name === 'radialGradient' ||
|
|
368
|
+
name === 'clipPath' ||
|
|
369
|
+
name === 'mask' ||
|
|
370
|
+
name === 'symbol') {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
if (name === 'g' || name === 'a' || name === 'switch' || name === 'svg') {
|
|
374
|
+
const container = createDisplayObject();
|
|
375
|
+
const viewport = name === 'svg' ? createSvgViewportMatrix(element) : null;
|
|
376
|
+
const style = applySvgElementAppearance(container, element, parentStyle, context, viewport, true);
|
|
377
|
+
appendSvgChildren(container, element, style, context);
|
|
378
|
+
applySvgElementClip(container, element, context, createSvgNode2DBounds(container));
|
|
379
|
+
return container;
|
|
380
|
+
}
|
|
381
|
+
if (name === 'use')
|
|
382
|
+
return createSvgUseNode(element, parentStyle, context);
|
|
383
|
+
if (name === 'text')
|
|
384
|
+
return createSvgTextNode(element, parentStyle, context);
|
|
385
|
+
if (name === 'image')
|
|
386
|
+
return createSvgImageNode(element, parentStyle, context);
|
|
387
|
+
const style = resolveSvgStyle(element, parentStyle, context);
|
|
388
|
+
const path = createSvgGeometryPath(element, style.fillRule);
|
|
389
|
+
if (path !== null) {
|
|
390
|
+
const shape = createShape();
|
|
391
|
+
applySvgElementAppearance(shape, element, parentStyle, context);
|
|
392
|
+
appendSvgShapePaint(shape, path, style, element, context);
|
|
393
|
+
const bounds = createRectangle();
|
|
394
|
+
getPathBounds(path, bounds);
|
|
395
|
+
applySvgElementClip(shape, element, context, bounds);
|
|
396
|
+
return shape;
|
|
397
|
+
}
|
|
398
|
+
if (isUnsupportedSvgElementName(name))
|
|
399
|
+
reportUnsupportedSvgElement(element, context);
|
|
400
|
+
else
|
|
401
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Skip, 'svg.unknown-element', 'createSvgElementNode', { element: name });
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
function createSvgImageNode(element, parentStyle, context) {
|
|
405
|
+
const href = attribute(element, 'href');
|
|
406
|
+
const image = href === null ? null : (context.options?.resolveImageResource?.(href) ?? null);
|
|
407
|
+
if (href === null || image === null) {
|
|
408
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Skip, href === null ? 'svg.image-missing-href' : 'svg.unresolved-image', 'createSvgImageNode', href === null ? undefined : { href });
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
const width = numberAttribute(element, 'width', image.width);
|
|
412
|
+
const height = numberAttribute(element, 'height', image.height);
|
|
413
|
+
const x = numberAttribute(element, 'x', 0);
|
|
414
|
+
const y = numberAttribute(element, 'y', 0);
|
|
415
|
+
const scaleX = image.width > 0 && width >= 0 ? width / image.width : 1;
|
|
416
|
+
const scaleY = image.height > 0 && height >= 0 ? height / image.height : 1;
|
|
417
|
+
const bitmap = createBitmap({
|
|
418
|
+
data: { image, smoothing: true },
|
|
419
|
+
});
|
|
420
|
+
const geometry = createMatrix(scaleX, 0, 0, scaleY, x, y);
|
|
421
|
+
applySvgElementAppearance(bitmap, element, parentStyle, context, geometry);
|
|
422
|
+
const bounds = createRectangle(0, 0, image.width, image.height);
|
|
423
|
+
applySvgElementClip(bitmap, element, context, bounds);
|
|
424
|
+
return bitmap;
|
|
425
|
+
}
|
|
426
|
+
function createSvgGeometryPath(element, winding) {
|
|
427
|
+
const name = localName(element.name);
|
|
428
|
+
if (name === 'path') {
|
|
429
|
+
const path = parseSvgPathData(attribute(element, 'd') ?? '');
|
|
430
|
+
if (path !== null)
|
|
431
|
+
path.winding = winding;
|
|
432
|
+
return path;
|
|
433
|
+
}
|
|
434
|
+
const path = createPath(winding);
|
|
435
|
+
if (name === 'rect') {
|
|
436
|
+
const x = numberAttribute(element, 'x', 0);
|
|
437
|
+
const y = numberAttribute(element, 'y', 0);
|
|
438
|
+
const width = numberAttribute(element, 'width', 0);
|
|
439
|
+
const height = numberAttribute(element, 'height', 0);
|
|
440
|
+
const rx = Math.max(0, numberAttribute(element, 'rx', 0));
|
|
441
|
+
const ry = Math.max(0, numberAttribute(element, 'ry', rx));
|
|
442
|
+
if (width <= 0 || height <= 0)
|
|
443
|
+
return path;
|
|
444
|
+
if (rx > 0 || ry > 0)
|
|
445
|
+
appendPathRoundRectangle(path, x, y, width, height, Math.min(Math.max(rx, ry), width / 2, height / 2));
|
|
446
|
+
else
|
|
447
|
+
appendPathRectangle(path, x, y, width, height);
|
|
448
|
+
return path;
|
|
449
|
+
}
|
|
450
|
+
if (name === 'circle') {
|
|
451
|
+
appendPathCircle(path, numberAttribute(element, 'cx', 0), numberAttribute(element, 'cy', 0), Math.max(0, numberAttribute(element, 'r', 0)));
|
|
452
|
+
return path;
|
|
453
|
+
}
|
|
454
|
+
if (name === 'ellipse') {
|
|
455
|
+
appendPathEllipse(path, numberAttribute(element, 'cx', 0), numberAttribute(element, 'cy', 0), Math.max(0, numberAttribute(element, 'rx', 0)), Math.max(0, numberAttribute(element, 'ry', 0)));
|
|
456
|
+
return path;
|
|
457
|
+
}
|
|
458
|
+
if (name === 'line') {
|
|
459
|
+
appendPathMoveTo(path, numberAttribute(element, 'x1', 0), numberAttribute(element, 'y1', 0));
|
|
460
|
+
appendPathLineTo(path, numberAttribute(element, 'x2', 0), numberAttribute(element, 'y2', 0));
|
|
461
|
+
return path;
|
|
462
|
+
}
|
|
463
|
+
if (name === 'polygon' || name === 'polyline') {
|
|
464
|
+
const points = parseSvgNumberList(attribute(element, 'points') ?? '');
|
|
465
|
+
if (name === 'polygon')
|
|
466
|
+
appendPathPolygon(path, points);
|
|
467
|
+
else
|
|
468
|
+
appendPathPolyline(path, points);
|
|
469
|
+
return path;
|
|
470
|
+
}
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
function createSvgTextNode(element, parentStyle, context) {
|
|
474
|
+
const style = resolveSvgStyle(element, parentStyle, context);
|
|
475
|
+
const format = createSvgTextFormat(style);
|
|
476
|
+
const directTspans = element.children.filter((child) => localName(child.name) === 'tspan');
|
|
477
|
+
const tspans = collectSvgTspanElements(element);
|
|
478
|
+
const runs = [];
|
|
479
|
+
collectSvgTextRuns(element, style, context, runs);
|
|
480
|
+
const lastRun = runs[runs.length - 1];
|
|
481
|
+
if (lastRun !== undefined)
|
|
482
|
+
lastRun.text = lastRun.text.trimEnd();
|
|
483
|
+
const text = runs.map((run) => run.text).join('');
|
|
484
|
+
const ranges = [];
|
|
485
|
+
let offset = 0;
|
|
486
|
+
for (const run of runs) {
|
|
487
|
+
const end = offset + run.text.length;
|
|
488
|
+
if (run.style !== style)
|
|
489
|
+
ranges.push(createTextFormatRange(createSvgTextFormat(run.style), offset, end));
|
|
490
|
+
offset = end;
|
|
491
|
+
}
|
|
492
|
+
const firstTspan = directTspans[0];
|
|
493
|
+
const xElement = attribute(element, 'x') === null && firstTspan !== undefined ? firstTspan : element;
|
|
494
|
+
const yElement = attribute(element, 'y') === null && firstTspan !== undefined ? firstTspan : element;
|
|
495
|
+
const x = firstNumber(attribute(xElement, 'x'), 0) +
|
|
496
|
+
firstNumber(attribute(element, 'dx'), 0) +
|
|
497
|
+
(xElement === element ? 0 : firstNumber(attribute(xElement, 'dx'), 0));
|
|
498
|
+
const y = firstNumber(attribute(yElement, 'y'), 0) +
|
|
499
|
+
firstNumber(attribute(element, 'dy'), 0) +
|
|
500
|
+
(yElement === element ? 0 : firstNumber(attribute(yElement, 'dy'), 0)) -
|
|
501
|
+
style.fontSize;
|
|
502
|
+
const common = {
|
|
503
|
+
alpha: style.opacity,
|
|
504
|
+
name: attribute(element, 'id'),
|
|
505
|
+
visible: style.display !== 'none' && style.visibility === 'visible',
|
|
506
|
+
};
|
|
507
|
+
const label = ranges.length === 0
|
|
508
|
+
? createTextLabel({
|
|
509
|
+
...common,
|
|
510
|
+
data: { autoSize: 'left', height: style.fontSize * 1.25, text, textFormat: format, width: 10000 },
|
|
511
|
+
})
|
|
512
|
+
: createRichText({
|
|
513
|
+
...common,
|
|
514
|
+
data: {
|
|
515
|
+
autoSize: 'left',
|
|
516
|
+
defaultTextFormat: format,
|
|
517
|
+
height: style.fontSize * 1.25,
|
|
518
|
+
text,
|
|
519
|
+
textFormat: format,
|
|
520
|
+
textFormatRanges: ranges,
|
|
521
|
+
width: 10000,
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
if (hasFlattenedSvgTextPosition(tspans, firstTspan)) {
|
|
525
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Recover, 'svg.tspan-position-flattened', 'createSvgTextNode', { count: tspans.length });
|
|
526
|
+
}
|
|
527
|
+
applySvgElementAppearance(label, element, parentStyle, context, createMatrix(1, 0, 0, 1, x, y));
|
|
528
|
+
applySvgElementClip(label, element, context, null);
|
|
529
|
+
return label;
|
|
530
|
+
}
|
|
531
|
+
function createSvgTextFormat(style) {
|
|
532
|
+
const color = resolveSvgColor(style.fill, style.color) ?? { alpha: 1, rgb: 0 };
|
|
533
|
+
return {
|
|
534
|
+
align: style.textAnchor === 'middle' ? 'center' : style.textAnchor === 'end' ? 'right' : 'left',
|
|
535
|
+
bold: style.fontWeight === 'bold' || Number(style.fontWeight) >= 600,
|
|
536
|
+
color: packColor(((color.rgb >>> 16) & 0xff) / 255, ((color.rgb >>> 8) & 0xff) / 255, (color.rgb & 0xff) / 255, color.alpha * style.fillOpacity),
|
|
537
|
+
font: style.fontFamily.replace(/^['"]|['"]$/g, ''),
|
|
538
|
+
italic: style.fontStyle === 'italic' || style.fontStyle === 'oblique',
|
|
539
|
+
size: style.fontSize,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function createSvgUseNode(element, parentStyle, context) {
|
|
543
|
+
const href = attribute(element, 'href');
|
|
544
|
+
const id = href?.startsWith('#') === true ? href.slice(1) : null;
|
|
545
|
+
if (id === null || context.resolvingUses.has(id)) {
|
|
546
|
+
reportImportDiagnostic(context.diagnostics, id === null ? ImportDiagnosticSeverity.Drop : ImportDiagnosticSeverity.Reject, id === null ? 'svg.unresolved-use' : 'svg.recursive-use', 'createSvgUseNode', id === null ? undefined : { id });
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
const referenced = context.elementsById.get(id);
|
|
550
|
+
if (referenced === undefined) {
|
|
551
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Drop, 'svg.unresolved-use', 'createSvgUseNode', { id });
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
context.resolvingUses.add(id);
|
|
555
|
+
reportRemainingUnsupportedSvgElements(referenced, context);
|
|
556
|
+
const container = createDisplayObject();
|
|
557
|
+
const placement = createMatrix(1, 0, 0, 1, numberAttribute(element, 'x', 0), numberAttribute(element, 'y', 0));
|
|
558
|
+
const style = applySvgElementAppearance(container, element, parentStyle, context, placement, true);
|
|
559
|
+
const referencedNode = localName(referenced.name) === 'symbol'
|
|
560
|
+
? createSvgSymbolNode(referenced, element, style, context)
|
|
561
|
+
: createSvgElementNode(referenced, style, context);
|
|
562
|
+
if (referencedNode !== null)
|
|
563
|
+
addNodeChild(container, referencedNode);
|
|
564
|
+
applySvgElementClip(container, element, context, createSvgNode2DBounds(container));
|
|
565
|
+
context.resolvingUses.delete(id);
|
|
566
|
+
return container;
|
|
567
|
+
}
|
|
568
|
+
function createSvgSymbolNode(element, useElement, parentStyle, context) {
|
|
569
|
+
const container = createDisplayObject({ name: attribute(element, 'id') });
|
|
570
|
+
const useWidth = optionalNumberAttribute(useElement, 'width');
|
|
571
|
+
const useHeight = optionalNumberAttribute(useElement, 'height');
|
|
572
|
+
const viewport = createSvgViewportMatrix(element, {
|
|
573
|
+
height: useHeight ?? undefined,
|
|
574
|
+
width: useWidth ?? undefined,
|
|
575
|
+
x: 0,
|
|
576
|
+
y: 0,
|
|
577
|
+
});
|
|
578
|
+
const style = applySvgElementAppearance(container, element, parentStyle, context, viewport, true);
|
|
579
|
+
appendSvgChildren(container, element, style, context);
|
|
580
|
+
applySvgElementClip(container, element, context, createSvgNode2DBounds(container));
|
|
581
|
+
return container;
|
|
582
|
+
}
|
|
583
|
+
function appendSvgShapePaint(shape, path, style, element, context) {
|
|
584
|
+
const fillGradient = resolveSvgGradient(style.fill, context);
|
|
585
|
+
const fillColor = resolveSvgColor(style.fill, style.color);
|
|
586
|
+
if (fillGradient !== null) {
|
|
587
|
+
appendShapeBeginGradientFill(shape, fillGradient.kind, fillGradient.stops.map((stop) => stop.color.rgb), fillGradient.stops.map((stop) => stop.color.alpha * style.fillOpacity), fillGradient.stops.map((stop) => Math.round(stop.offset * 255)), createSvgGradientMatrix(fillGradient, path), fillGradient.spreadMethod);
|
|
588
|
+
}
|
|
589
|
+
else if (fillColor !== null) {
|
|
590
|
+
appendShapeBeginFill(shape, fillColor.rgb, fillColor.alpha * style.fillOpacity);
|
|
591
|
+
}
|
|
592
|
+
if (fillGradient !== null || fillColor !== null) {
|
|
593
|
+
appendShapePath(shape, path.commands.slice(), path.data.slice(), path.winding);
|
|
594
|
+
appendShapeEndFill(shape);
|
|
595
|
+
}
|
|
596
|
+
else if (parseUrlReference(style.fill) !== null) {
|
|
597
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Drop, 'svg.unresolved-fill-gradient', 'appendSvgShapePaint', { element: localName(element.name) });
|
|
598
|
+
}
|
|
599
|
+
const strokeGradient = resolveSvgGradient(style.stroke, context);
|
|
600
|
+
const strokeColor = resolveSvgColor(style.stroke, style.color);
|
|
601
|
+
if (strokeGradient === null && strokeColor === null) {
|
|
602
|
+
if (parseUrlReference(style.stroke) !== null) {
|
|
603
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Drop, 'svg.unresolved-stroke-gradient', 'appendSvgShapePaint', { element: localName(element.name) });
|
|
604
|
+
}
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
appendShapeLineStyle(shape, style.strokeWidth, strokeColor?.rgb ?? 0, (strokeColor?.alpha ?? 1) * style.strokeOpacity, false, 'normal', mapSvgLineCap(style.strokeLinecap), mapSvgLineJoin(style.strokeLinejoin), style.strokeMiterlimit);
|
|
608
|
+
if (strokeGradient !== null) {
|
|
609
|
+
appendShapeLineGradientStyle(shape, strokeGradient.kind, strokeGradient.stops.map((stop) => stop.color.rgb), strokeGradient.stops.map((stop) => stop.color.alpha * style.strokeOpacity), strokeGradient.stops.map((stop) => Math.round(stop.offset * 255)), createSvgGradientMatrix(strokeGradient, path), strokeGradient.spreadMethod);
|
|
610
|
+
}
|
|
611
|
+
let strokePath = path;
|
|
612
|
+
if (style.strokeDasharray !== 'none') {
|
|
613
|
+
const dash = parseSvgNumberList(style.strokeDasharray).filter((value) => value >= 0);
|
|
614
|
+
if (dash.length > 0) {
|
|
615
|
+
strokePath = createPath(path.winding);
|
|
616
|
+
dashPath(path, dash.length % 2 === 0 ? dash : [...dash, ...dash], style.strokeDashoffset, strokePath);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
appendShapePath(shape, strokePath.commands.slice(), strokePath.data.slice(), strokePath.winding);
|
|
620
|
+
}
|
|
621
|
+
function appendPathData(out, source) {
|
|
622
|
+
out.commands.push(...source.commands);
|
|
623
|
+
out.data.push(...source.data);
|
|
624
|
+
}
|
|
625
|
+
function collectSvgTextRuns(element, style, context, out) {
|
|
626
|
+
for (const content of element.content) {
|
|
627
|
+
if (typeof content === 'string') {
|
|
628
|
+
appendSvgTextRun(out, style, content);
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (localName(content.name) !== 'tspan')
|
|
632
|
+
continue;
|
|
633
|
+
collectSvgTextRuns(content, resolveSvgStyle(content, style, context), context, out);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function collectSvgTspanElements(element) {
|
|
637
|
+
const out = [];
|
|
638
|
+
for (const child of element.children) {
|
|
639
|
+
if (localName(child.name) === 'tspan')
|
|
640
|
+
out.push(child);
|
|
641
|
+
out.push(...collectSvgTspanElements(child));
|
|
642
|
+
}
|
|
643
|
+
return out;
|
|
644
|
+
}
|
|
645
|
+
function appendSvgTextRun(out, style, source) {
|
|
646
|
+
let text = source
|
|
647
|
+
.replace(/[\n\r]/g, '')
|
|
648
|
+
.replace(/\t/g, ' ')
|
|
649
|
+
.replace(/ +/g, ' ');
|
|
650
|
+
if (out.length === 0)
|
|
651
|
+
text = text.trimStart();
|
|
652
|
+
const previous = out[out.length - 1];
|
|
653
|
+
if (previous?.text.endsWith(' ') === true && text.startsWith(' '))
|
|
654
|
+
text = text.slice(1);
|
|
655
|
+
if (text !== '')
|
|
656
|
+
out.push({ style, text });
|
|
657
|
+
}
|
|
658
|
+
function createSvgGradientMatrix(gradient, path) {
|
|
659
|
+
const bounds = createRectangle();
|
|
660
|
+
getPathBounds(path, bounds);
|
|
661
|
+
const mapX = (value) => gradient.units === 'objectBoundingBox' ? bounds.x + value * bounds.width : value;
|
|
662
|
+
const mapY = (value) => gradient.units === 'objectBoundingBox' ? bounds.y + value * bounds.height : value;
|
|
663
|
+
let matrix;
|
|
664
|
+
if (gradient.kind === 'linear') {
|
|
665
|
+
const x1 = mapX(gradient.x1);
|
|
666
|
+
const y1 = mapY(gradient.y1);
|
|
667
|
+
const x2 = mapX(gradient.x2);
|
|
668
|
+
const y2 = mapY(gradient.y2);
|
|
669
|
+
const dx = x2 - x1;
|
|
670
|
+
const dy = y2 - y1;
|
|
671
|
+
const length = Math.max(Math.hypot(dx, dy), 1);
|
|
672
|
+
matrix = createGradientTransformMatrix(length, length, Math.atan2(dy, dx), (x1 + x2) / 2, (y1 + y2) / 2);
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
const radiusX = gradient.units === 'objectBoundingBox' ? gradient.radius * bounds.width : gradient.radius;
|
|
676
|
+
const radiusY = gradient.units === 'objectBoundingBox' ? gradient.radius * bounds.height : gradient.radius;
|
|
677
|
+
matrix = createGradientTransformMatrix(Math.max(radiusX * 2, 1), Math.max(radiusY * 2, 1), 0, mapX(gradient.cx), mapY(gradient.cy));
|
|
678
|
+
}
|
|
679
|
+
if (gradient.transform !== null)
|
|
680
|
+
matrix = multiplySvgMatrices(gradient.transform, matrix);
|
|
681
|
+
return matrix;
|
|
682
|
+
}
|
|
683
|
+
function createSvgViewportMatrix(element, viewport) {
|
|
684
|
+
const x = viewport?.x ?? numberAttribute(element, 'x', 0);
|
|
685
|
+
const y = viewport?.y ?? numberAttribute(element, 'y', 0);
|
|
686
|
+
const viewBox = parseSvgNumberList(attribute(element, 'viewBox') ?? '');
|
|
687
|
+
if (viewBox.length < 4)
|
|
688
|
+
return x === 0 && y === 0 ? null : createMatrix(1, 0, 0, 1, x, y);
|
|
689
|
+
const width = viewport?.width ?? parseSvgLength(attribute(element, 'width'), viewBox[2]);
|
|
690
|
+
const height = viewport?.height ?? parseSvgLength(attribute(element, 'height'), viewBox[3]);
|
|
691
|
+
let sx = viewBox[2] === 0 ? 1 : width / viewBox[2];
|
|
692
|
+
let sy = viewBox[3] === 0 ? 1 : height / viewBox[3];
|
|
693
|
+
const preserveAspectRatio = (attribute(element, 'preserveAspectRatio') ?? 'xMidYMid meet').trim();
|
|
694
|
+
if (preserveAspectRatio === 'none') {
|
|
695
|
+
return createMatrix(sx, 0, 0, sy, x - viewBox[0] * sx, y - viewBox[1] * sy);
|
|
696
|
+
}
|
|
697
|
+
const parts = preserveAspectRatio.split(/\s+/);
|
|
698
|
+
const alignment = parts[0] === 'defer' ? (parts[1] ?? 'xMidYMid') : parts[0];
|
|
699
|
+
const mode = parts.includes('slice') ? 'slice' : 'meet';
|
|
700
|
+
const uniformScale = mode === 'slice' ? Math.max(sx, sy) : Math.min(sx, sy);
|
|
701
|
+
sx = uniformScale;
|
|
702
|
+
sy = uniformScale;
|
|
703
|
+
const spareX = width - viewBox[2] * sx;
|
|
704
|
+
const spareY = height - viewBox[3] * sy;
|
|
705
|
+
const alignX = alignment.includes('xMax') ? spareX : alignment.includes('xMid') ? spareX / 2 : 0;
|
|
706
|
+
const alignY = alignment.includes('YMax') ? spareY : alignment.includes('YMid') ? spareY / 2 : 0;
|
|
707
|
+
return createMatrix(sx, 0, 0, sy, x + alignX - viewBox[0] * sx, y + alignY - viewBox[1] * sy);
|
|
708
|
+
}
|
|
709
|
+
function getCssSelectorSpecificity(selector) {
|
|
710
|
+
const ids = selector.match(/#[\w-]+/g)?.length ?? 0;
|
|
711
|
+
const classes = selector.match(/\.[\w-]+/g)?.length ?? 0;
|
|
712
|
+
const hasType = /^[a-zA-Z_][\w-]*/.test(selector) ? 1 : 0;
|
|
713
|
+
return ids * 100 + classes * 10 + hasType;
|
|
714
|
+
}
|
|
715
|
+
function indexSvgDefinitions(root, context) {
|
|
716
|
+
const indexElement = (element, parent) => {
|
|
717
|
+
context.parentByElement.set(element, parent);
|
|
718
|
+
const id = attribute(element, 'id');
|
|
719
|
+
if (id !== null)
|
|
720
|
+
context.elementsById.set(id, element);
|
|
721
|
+
for (const child of element.children)
|
|
722
|
+
indexElement(child, element);
|
|
723
|
+
};
|
|
724
|
+
indexElement(root, null);
|
|
725
|
+
visitXmlElements(root, (element) => {
|
|
726
|
+
const name = localName(element.name);
|
|
727
|
+
const id = attribute(element, 'id');
|
|
728
|
+
if (id !== null && (name === 'linearGradient' || name === 'radialGradient')) {
|
|
729
|
+
const gradient = parseSvgGradient(element, context);
|
|
730
|
+
if (gradient !== null)
|
|
731
|
+
context.gradientsById.set(id, gradient);
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
function localName(name) {
|
|
736
|
+
const colon = name.indexOf(':');
|
|
737
|
+
return colon === -1 ? name : name.slice(colon + 1);
|
|
738
|
+
}
|
|
739
|
+
function mapSvgLineCap(value) {
|
|
740
|
+
return value === 'round' ? 'round' : value === 'square' ? 'square' : 'none';
|
|
741
|
+
}
|
|
742
|
+
function mapSvgLineJoin(value) {
|
|
743
|
+
return value === 'round' ? 'round' : value === 'bevel' ? 'bevel' : 'miter';
|
|
744
|
+
}
|
|
745
|
+
function matchesCssSelector(element, selector) {
|
|
746
|
+
if (selector.startsWith('#'))
|
|
747
|
+
return attribute(element, 'id') === selector.slice(1);
|
|
748
|
+
if (selector.startsWith('.')) {
|
|
749
|
+
const classes = (attribute(element, 'class') ?? '').split(/\s+/);
|
|
750
|
+
return classes.includes(selector.slice(1));
|
|
751
|
+
}
|
|
752
|
+
const [tag, className] = selector.split('.');
|
|
753
|
+
return localName(element.name) === tag && (className === undefined || matchesCssSelector(element, `.${className}`));
|
|
754
|
+
}
|
|
755
|
+
function multiplySvgMatrices(a, b) {
|
|
756
|
+
const out = createMatrix();
|
|
757
|
+
multiplyMatrix(out, a, b);
|
|
758
|
+
return out;
|
|
759
|
+
}
|
|
760
|
+
function numberAttribute(element, name, fallback) {
|
|
761
|
+
return parseSvgLength(attribute(element, name), fallback);
|
|
762
|
+
}
|
|
763
|
+
function optionalNumberAttribute(element, name) {
|
|
764
|
+
const value = attribute(element, name);
|
|
765
|
+
if (value === null)
|
|
766
|
+
return null;
|
|
767
|
+
const parsed = Number.parseFloat(value);
|
|
768
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
769
|
+
}
|
|
770
|
+
function parseCssNumber(value, fallback) {
|
|
771
|
+
if (value === undefined)
|
|
772
|
+
return fallback;
|
|
773
|
+
const number = Number.parseFloat(value);
|
|
774
|
+
return Number.isFinite(number) ? number : fallback;
|
|
775
|
+
}
|
|
776
|
+
function parseStyleDeclarations(value) {
|
|
777
|
+
const declarations = {};
|
|
778
|
+
for (const part of value.split(';')) {
|
|
779
|
+
const colon = part.indexOf(':');
|
|
780
|
+
if (colon === -1)
|
|
781
|
+
continue;
|
|
782
|
+
const name = part.slice(0, colon).trim();
|
|
783
|
+
const propertyValue = part
|
|
784
|
+
.slice(colon + 1)
|
|
785
|
+
.trim()
|
|
786
|
+
.replace(/\s*!important\s*$/, '');
|
|
787
|
+
if (name !== '')
|
|
788
|
+
declarations[name] = propertyValue;
|
|
789
|
+
}
|
|
790
|
+
return declarations;
|
|
791
|
+
}
|
|
792
|
+
function parseSvgColor(value) {
|
|
793
|
+
const normalized = value.trim().toLowerCase();
|
|
794
|
+
if (normalized === '' || normalized === 'none' || normalized === 'transparent')
|
|
795
|
+
return null;
|
|
796
|
+
if (normalized.startsWith('#')) {
|
|
797
|
+
const hex = normalized.slice(1);
|
|
798
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
799
|
+
const r = Number.parseInt(hex[0] + hex[0], 16);
|
|
800
|
+
const g = Number.parseInt(hex[1] + hex[1], 16);
|
|
801
|
+
const b = Number.parseInt(hex[2] + hex[2], 16);
|
|
802
|
+
const a = hex.length === 4 ? Number.parseInt(hex[3] + hex[3], 16) / 255 : 1;
|
|
803
|
+
return Number.isNaN(r + g + b + a) ? null : { alpha: a, rgb: (r << 16) | (g << 8) | b };
|
|
804
|
+
}
|
|
805
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
806
|
+
const rgb = Number.parseInt(hex.slice(0, 6), 16);
|
|
807
|
+
const alpha = hex.length === 8 ? Number.parseInt(hex.slice(6), 16) / 255 : 1;
|
|
808
|
+
return Number.isNaN(rgb + alpha) ? null : { alpha, rgb };
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const functionMatch = /^(rgba?|hsla?)\((.*)\)$/.exec(normalized);
|
|
812
|
+
if (functionMatch !== null && (functionMatch[1] === 'rgb' || functionMatch[1] === 'rgba')) {
|
|
813
|
+
const components = functionMatch[2].split(/[\s,/]+/).filter(Boolean);
|
|
814
|
+
if (components.length >= 3) {
|
|
815
|
+
const component = (index) => {
|
|
816
|
+
const text = components[index];
|
|
817
|
+
return text.endsWith('%') ? (Number.parseFloat(text) * 255) / 100 : Number.parseFloat(text);
|
|
818
|
+
};
|
|
819
|
+
const r = Math.round(clamp(component(0), 0, 255));
|
|
820
|
+
const g = Math.round(clamp(component(1), 0, 255));
|
|
821
|
+
const b = Math.round(clamp(component(2), 0, 255));
|
|
822
|
+
const alpha = components[3] === undefined ? 1 : clamp(Number.parseFloat(components[3]), 0, 1);
|
|
823
|
+
if (Number.isFinite(r + g + b + alpha))
|
|
824
|
+
return { alpha, rgb: (r << 16) | (g << 8) | b };
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const named = svgNamedColors[normalized];
|
|
828
|
+
return named === undefined ? null : { alpha: 1, rgb: named };
|
|
829
|
+
}
|
|
830
|
+
function parseSvgGradient(element, context) {
|
|
831
|
+
const kind = localName(element.name) === 'linearGradient' ? 'linear' : 'radial';
|
|
832
|
+
const href = attribute(element, 'href');
|
|
833
|
+
const inheritedId = href?.startsWith('#') === true ? href.slice(1) : null;
|
|
834
|
+
let inherited = inheritedId === null ? undefined : context.gradientsById.get(inheritedId);
|
|
835
|
+
if (inherited === undefined && inheritedId !== null && !context.resolvingGradients.has(inheritedId)) {
|
|
836
|
+
const inheritedElement = context.elementsById.get(inheritedId);
|
|
837
|
+
if (inheritedElement !== undefined &&
|
|
838
|
+
(localName(inheritedElement.name) === 'linearGradient' || localName(inheritedElement.name) === 'radialGradient')) {
|
|
839
|
+
context.resolvingGradients.add(inheritedId);
|
|
840
|
+
inherited = parseSvgGradient(inheritedElement, context) ?? undefined;
|
|
841
|
+
context.resolvingGradients.delete(inheritedId);
|
|
842
|
+
if (inherited !== undefined)
|
|
843
|
+
context.gradientsById.set(inheritedId, inherited);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (inheritedId !== null && inherited === undefined) {
|
|
847
|
+
reportImportDiagnostic(context.diagnostics, context.resolvingGradients.has(inheritedId) ? ImportDiagnosticSeverity.Reject : ImportDiagnosticSeverity.Drop, context.resolvingGradients.has(inheritedId) ? 'svg.recursive-gradient' : 'svg.unresolved-gradient-reference', 'parseSvgGradient', { id: inheritedId });
|
|
848
|
+
}
|
|
849
|
+
const stops = [];
|
|
850
|
+
for (const child of element.children) {
|
|
851
|
+
if (localName(child.name) !== 'stop')
|
|
852
|
+
continue;
|
|
853
|
+
const declarations = parseStyleDeclarations(attribute(child, 'style') ?? '');
|
|
854
|
+
const color = parseSvgColor(attribute(child, 'stop-color') ?? declarations['stop-color'] ?? '#000000');
|
|
855
|
+
if (color === null)
|
|
856
|
+
continue;
|
|
857
|
+
color.alpha *= clamp(parseCssNumber(attribute(child, 'stop-opacity') ?? declarations['stop-opacity'], 1), 0, 1);
|
|
858
|
+
stops.push({
|
|
859
|
+
color,
|
|
860
|
+
offset: clamp(parseSvgOffset(attribute(child, 'offset')), 0, 1),
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
const resolvedStops = stops.length === 0
|
|
864
|
+
? (inherited?.stops.map((stop) => ({ color: { ...stop.color }, offset: stop.offset })) ?? [])
|
|
865
|
+
: stops;
|
|
866
|
+
if (resolvedStops.length === 0)
|
|
867
|
+
return null;
|
|
868
|
+
resolvedStops.sort((a, b) => a.offset - b.offset);
|
|
869
|
+
return {
|
|
870
|
+
cx: parseSvgCoordinate(attribute(element, 'cx'), inherited?.cx ?? 0.5),
|
|
871
|
+
cy: parseSvgCoordinate(attribute(element, 'cy'), inherited?.cy ?? 0.5),
|
|
872
|
+
fx: parseSvgCoordinate(attribute(element, 'fx'), inherited?.fx ?? 0.5),
|
|
873
|
+
fy: parseSvgCoordinate(attribute(element, 'fy'), inherited?.fy ?? 0.5),
|
|
874
|
+
kind,
|
|
875
|
+
radius: parseSvgCoordinate(attribute(element, 'r'), inherited?.radius ?? 0.5),
|
|
876
|
+
spreadMethod: parseSvgSpreadMethod(attribute(element, 'spreadMethod')) ?? inherited?.spreadMethod ?? 'pad',
|
|
877
|
+
stops: resolvedStops,
|
|
878
|
+
transform: parseSvgTransform(attribute(element, 'gradientTransform')) ?? inherited?.transform ?? null,
|
|
879
|
+
units: attribute(element, 'gradientUnits') === 'userSpaceOnUse'
|
|
880
|
+
? 'userSpaceOnUse'
|
|
881
|
+
: (inherited?.units ?? 'objectBoundingBox'),
|
|
882
|
+
x1: parseSvgCoordinate(attribute(element, 'x1'), inherited?.x1 ?? 0),
|
|
883
|
+
x2: parseSvgCoordinate(attribute(element, 'x2'), inherited?.x2 ?? 1),
|
|
884
|
+
y1: parseSvgCoordinate(attribute(element, 'y1'), inherited?.y1 ?? 0),
|
|
885
|
+
y2: parseSvgCoordinate(attribute(element, 'y2'), inherited?.y2 ?? 0),
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
function parseSvgCoordinate(value, fallback) {
|
|
889
|
+
if (value === null)
|
|
890
|
+
return fallback;
|
|
891
|
+
const parsed = Number.parseFloat(value);
|
|
892
|
+
if (!Number.isFinite(parsed))
|
|
893
|
+
return fallback;
|
|
894
|
+
return value.trim().endsWith('%') ? parsed / 100 : parsed;
|
|
895
|
+
}
|
|
896
|
+
function parseSvgLength(value, fallback) {
|
|
897
|
+
if (value === null)
|
|
898
|
+
return fallback;
|
|
899
|
+
const parsed = Number.parseFloat(value);
|
|
900
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
901
|
+
}
|
|
902
|
+
function parseSvgNumberList(value) {
|
|
903
|
+
const matches = value.match(/[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?/gi);
|
|
904
|
+
return matches?.map(Number) ?? [];
|
|
905
|
+
}
|
|
906
|
+
function parseSvgOffset(value) {
|
|
907
|
+
if (value === null)
|
|
908
|
+
return 0;
|
|
909
|
+
const parsed = Number.parseFloat(value);
|
|
910
|
+
if (!Number.isFinite(parsed))
|
|
911
|
+
return 0;
|
|
912
|
+
return value.trim().endsWith('%') ? parsed / 100 : parsed;
|
|
913
|
+
}
|
|
914
|
+
function parseSvgSpreadMethod(value) {
|
|
915
|
+
return value === 'reflect' || value === 'repeat' || value === 'pad' ? value : null;
|
|
916
|
+
}
|
|
917
|
+
function parseSvgTransform(value) {
|
|
918
|
+
if (value === null || value.trim() === '')
|
|
919
|
+
return null;
|
|
920
|
+
let result = createMatrix();
|
|
921
|
+
const expression = /([a-zA-Z]+)\s*\(([^)]*)\)/g;
|
|
922
|
+
let matched = false;
|
|
923
|
+
let match;
|
|
924
|
+
while ((match = expression.exec(value)) !== null) {
|
|
925
|
+
matched = true;
|
|
926
|
+
const values = parseSvgNumberList(match[2]);
|
|
927
|
+
let operation = createMatrix();
|
|
928
|
+
if (match[1] === 'matrix' && values.length >= 6) {
|
|
929
|
+
operation = createMatrix(values[0], values[1], values[2], values[3], values[4], values[5]);
|
|
930
|
+
}
|
|
931
|
+
else if (match[1] === 'translate') {
|
|
932
|
+
operation.tx = values[0] ?? 0;
|
|
933
|
+
operation.ty = values[1] ?? 0;
|
|
934
|
+
}
|
|
935
|
+
else if (match[1] === 'scale') {
|
|
936
|
+
operation.a = values[0] ?? 1;
|
|
937
|
+
operation.d = values[1] ?? values[0] ?? 1;
|
|
938
|
+
}
|
|
939
|
+
else if (match[1] === 'rotate') {
|
|
940
|
+
const radians = ((values[0] ?? 0) * Math.PI) / 180;
|
|
941
|
+
const cosine = Math.cos(radians);
|
|
942
|
+
const sine = Math.sin(radians);
|
|
943
|
+
const rotation = createMatrix(cosine, sine, -sine, cosine);
|
|
944
|
+
if (values.length >= 3) {
|
|
945
|
+
operation = multiplySvgMatrices(createMatrix(1, 0, 0, 1, values[1], values[2]), multiplySvgMatrices(rotation, createMatrix(1, 0, 0, 1, -values[1], -values[2])));
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
operation = rotation;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
else if (match[1] === 'skewX') {
|
|
952
|
+
operation.c = Math.tan(((values[0] ?? 0) * Math.PI) / 180);
|
|
953
|
+
}
|
|
954
|
+
else if (match[1] === 'skewY') {
|
|
955
|
+
operation.b = Math.tan(((values[0] ?? 0) * Math.PI) / 180);
|
|
956
|
+
}
|
|
957
|
+
else {
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
result = multiplySvgMatrices(result, operation);
|
|
961
|
+
}
|
|
962
|
+
return matched ? result : null;
|
|
963
|
+
}
|
|
964
|
+
function parseUrlReference(value) {
|
|
965
|
+
if (value === null)
|
|
966
|
+
return null;
|
|
967
|
+
const match = /^url\(\s*['"]?#([^'")\s]+)['"]?\s*\)$/.exec(value.trim());
|
|
968
|
+
return match?.[1] ?? null;
|
|
969
|
+
}
|
|
970
|
+
function reportRemainingUnsupportedSvgElements(element, context, insideDefinitions = false) {
|
|
971
|
+
const name = localName(element.name);
|
|
972
|
+
const definitions = insideDefinitions || name === 'defs';
|
|
973
|
+
if (!definitions && isUnsupportedSvgElementName(name))
|
|
974
|
+
reportUnsupportedSvgElement(element, context);
|
|
975
|
+
for (const child of element.children)
|
|
976
|
+
reportRemainingUnsupportedSvgElements(child, context, definitions);
|
|
977
|
+
}
|
|
978
|
+
function reportUnsupportedSvgElement(element, context) {
|
|
979
|
+
if (context.reportedUnsupportedElements.has(element))
|
|
980
|
+
return;
|
|
981
|
+
context.reportedUnsupportedElements.add(element);
|
|
982
|
+
const name = localName(element.name);
|
|
983
|
+
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Skip, `svg.unsupported-${name}`, 'createSvgElementNode', { element: name });
|
|
984
|
+
}
|
|
985
|
+
function resolveSvgColor(value, currentColor) {
|
|
986
|
+
return parseSvgColor(value === 'currentColor' ? currentColor : value);
|
|
987
|
+
}
|
|
988
|
+
function resolveSvgGradient(value, context) {
|
|
989
|
+
const id = parseUrlReference(value);
|
|
990
|
+
return id === null ? null : (context.gradientsById.get(id) ?? null);
|
|
991
|
+
}
|
|
992
|
+
function resolveSvgStyle(element, parentStyle, context) {
|
|
993
|
+
const declarations = {};
|
|
994
|
+
for (const name of svgPresentationAttributes) {
|
|
995
|
+
const value = attribute(element, name);
|
|
996
|
+
if (value !== null)
|
|
997
|
+
declarations[name] = value;
|
|
998
|
+
}
|
|
999
|
+
const matchingRules = context.cssRules
|
|
1000
|
+
.filter((rule) => matchesCssSelector(element, rule.selector))
|
|
1001
|
+
.sort((a, b) => a.specificity - b.specificity || a.order - b.order);
|
|
1002
|
+
for (const rule of matchingRules)
|
|
1003
|
+
Object.assign(declarations, rule.declarations);
|
|
1004
|
+
Object.assign(declarations, parseStyleDeclarations(attribute(element, 'style') ?? ''));
|
|
1005
|
+
const style = { ...parentStyle };
|
|
1006
|
+
style.clipRule = resolveSvgWinding(declarations['clip-rule'], style.clipRule);
|
|
1007
|
+
style.color = declarations.color ?? style.color;
|
|
1008
|
+
// `display` is not inherited. An ancestor with display:none still suppresses its subtree via
|
|
1009
|
+
// the display-object hierarchy (and the clip collector's early return).
|
|
1010
|
+
style.display = declarations.display ?? 'inline';
|
|
1011
|
+
style.fill = declarations.fill ?? style.fill;
|
|
1012
|
+
style.fillOpacity = clamp(parseCssNumber(declarations['fill-opacity'], style.fillOpacity), 0, 1);
|
|
1013
|
+
style.fillRule = resolveSvgWinding(declarations['fill-rule'], style.fillRule);
|
|
1014
|
+
style.filter = declarations.filter ?? 'none';
|
|
1015
|
+
style.fontFamily = declarations['font-family'] ?? style.fontFamily;
|
|
1016
|
+
style.fontSize = parseSvgLength(declarations['font-size'] ?? null, style.fontSize);
|
|
1017
|
+
style.fontStyle = declarations['font-style'] ?? style.fontStyle;
|
|
1018
|
+
style.fontWeight = declarations['font-weight'] ?? style.fontWeight;
|
|
1019
|
+
style.opacity = clamp(parseCssNumber(declarations.opacity, 1), 0, 1);
|
|
1020
|
+
style.stroke = declarations.stroke ?? style.stroke;
|
|
1021
|
+
style.strokeDasharray = declarations['stroke-dasharray'] ?? style.strokeDasharray;
|
|
1022
|
+
style.strokeDashoffset = parseSvgLength(declarations['stroke-dashoffset'] ?? null, style.strokeDashoffset);
|
|
1023
|
+
style.strokeLinecap = declarations['stroke-linecap'] ?? style.strokeLinecap;
|
|
1024
|
+
style.strokeLinejoin = declarations['stroke-linejoin'] ?? style.strokeLinejoin;
|
|
1025
|
+
style.strokeMiterlimit = parseCssNumber(declarations['stroke-miterlimit'], style.strokeMiterlimit);
|
|
1026
|
+
style.strokeOpacity = clamp(parseCssNumber(declarations['stroke-opacity'], style.strokeOpacity), 0, 1);
|
|
1027
|
+
style.strokeWidth = Math.max(0, parseSvgLength(declarations['stroke-width'] ?? null, style.strokeWidth));
|
|
1028
|
+
style.textAnchor = declarations['text-anchor'] ?? style.textAnchor;
|
|
1029
|
+
style.visibility = declarations.visibility ?? style.visibility;
|
|
1030
|
+
return style;
|
|
1031
|
+
}
|
|
1032
|
+
function resolveSvgDefinitionStyle(element, context) {
|
|
1033
|
+
const cached = context.resolvedDefinitionStyles.get(element);
|
|
1034
|
+
if (cached !== undefined)
|
|
1035
|
+
return cached;
|
|
1036
|
+
const parent = context.parentByElement.get(element) ?? null;
|
|
1037
|
+
const parentStyle = parent === null ? defaultSvgStyle : resolveSvgDefinitionStyle(parent, context);
|
|
1038
|
+
const style = resolveSvgStyle(element, parentStyle, context);
|
|
1039
|
+
context.resolvedDefinitionStyles.set(element, style);
|
|
1040
|
+
return style;
|
|
1041
|
+
}
|
|
1042
|
+
function firstNumber(value, fallback) {
|
|
1043
|
+
return parseSvgNumberList(value ?? '')[0] ?? fallback;
|
|
1044
|
+
}
|
|
1045
|
+
function hasFlattenedSvgTextPosition(tspans, firstTspan) {
|
|
1046
|
+
return tspans.some((tspan) => ['x', 'y', 'dx', 'dy', 'rotate', 'transform'].some((name) => attribute(tspan, name) !== null && (tspan !== firstTspan || name === 'rotate' || name === 'transform')));
|
|
1047
|
+
}
|
|
1048
|
+
function isUnsupportedSvgElementName(name) {
|
|
1049
|
+
return (name === 'animate' ||
|
|
1050
|
+
name === 'animateMotion' ||
|
|
1051
|
+
name === 'animateTransform' ||
|
|
1052
|
+
name === 'filter' ||
|
|
1053
|
+
name === 'foreignObject' ||
|
|
1054
|
+
name === 'pattern' ||
|
|
1055
|
+
name === 'script' ||
|
|
1056
|
+
name === 'set');
|
|
1057
|
+
}
|
|
1058
|
+
function resolveSvgWinding(value, fallback) {
|
|
1059
|
+
if (value === 'evenodd')
|
|
1060
|
+
return 'evenOdd';
|
|
1061
|
+
if (value === 'nonzero')
|
|
1062
|
+
return 'nonZero';
|
|
1063
|
+
return fallback;
|
|
1064
|
+
}
|
|
1065
|
+
function usesSvgObjectBoundingBoxUnits(element) {
|
|
1066
|
+
return ((localName(element.name) === 'mask'
|
|
1067
|
+
? attribute(element, 'maskContentUnits')
|
|
1068
|
+
: attribute(element, 'clipPathUnits')) === 'objectBoundingBox');
|
|
1069
|
+
}
|
|
1070
|
+
function visitXmlElements(element, visitor) {
|
|
1071
|
+
visitor(element);
|
|
1072
|
+
for (const child of element.children)
|
|
1073
|
+
visitXmlElements(child, visitor);
|
|
1074
|
+
}
|
|
1075
|
+
function clamp(value, minimum, maximum) {
|
|
1076
|
+
return Math.max(minimum, Math.min(maximum, value));
|
|
1077
|
+
}
|
|
1078
|
+
const svgPresentationAttributes = [
|
|
1079
|
+
'clip-rule',
|
|
1080
|
+
'color',
|
|
1081
|
+
'display',
|
|
1082
|
+
'fill',
|
|
1083
|
+
'fill-opacity',
|
|
1084
|
+
'fill-rule',
|
|
1085
|
+
'filter',
|
|
1086
|
+
'font-family',
|
|
1087
|
+
'font-size',
|
|
1088
|
+
'font-style',
|
|
1089
|
+
'font-weight',
|
|
1090
|
+
'opacity',
|
|
1091
|
+
'stroke',
|
|
1092
|
+
'stroke-dasharray',
|
|
1093
|
+
'stroke-dashoffset',
|
|
1094
|
+
'stroke-linecap',
|
|
1095
|
+
'stroke-linejoin',
|
|
1096
|
+
'stroke-miterlimit',
|
|
1097
|
+
'stroke-opacity',
|
|
1098
|
+
'stroke-width',
|
|
1099
|
+
'text-anchor',
|
|
1100
|
+
'visibility',
|
|
1101
|
+
];
|
|
1102
|
+
const svgNamedColors = {
|
|
1103
|
+
aqua: 0x00ffff,
|
|
1104
|
+
black: 0x000000,
|
|
1105
|
+
blue: 0x0000ff,
|
|
1106
|
+
cyan: 0x00ffff,
|
|
1107
|
+
fuchsia: 0xff00ff,
|
|
1108
|
+
gray: 0x808080,
|
|
1109
|
+
green: 0x008000,
|
|
1110
|
+
grey: 0x808080,
|
|
1111
|
+
lime: 0x00ff00,
|
|
1112
|
+
magenta: 0xff00ff,
|
|
1113
|
+
maroon: 0x800000,
|
|
1114
|
+
navy: 0x000080,
|
|
1115
|
+
olive: 0x808000,
|
|
1116
|
+
orange: 0xffa500,
|
|
1117
|
+
purple: 0x800080,
|
|
1118
|
+
red: 0xff0000,
|
|
1119
|
+
silver: 0xc0c0c0,
|
|
1120
|
+
teal: 0x008080,
|
|
1121
|
+
white: 0xffffff,
|
|
1122
|
+
yellow: 0xffff00,
|
|
1123
|
+
};
|
|
1124
|
+
//# sourceMappingURL=svgDocument.js.map
|