@antv/infographic 0.2.17 → 0.2.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/infographic.min.js +110 -110
- package/dist/infographic.min.js.map +1 -1
- package/esm/constants/service.d.ts +1 -1
- package/esm/constants/service.js +1 -1
- package/esm/designs/structures/chart-line.js +5 -3
- package/esm/editor/interactions/dblclick-edit-text.js +3 -3
- package/esm/editor/managers/interaction.js +6 -4
- package/esm/editor/plugins/components/button.d.ts +2 -1
- package/esm/editor/plugins/components/button.js +4 -4
- package/esm/editor/plugins/components/color-picker.d.ts +1 -0
- package/esm/editor/plugins/components/color-picker.js +3 -3
- package/esm/editor/plugins/components/popover.d.ts +3 -1
- package/esm/editor/plugins/components/popover.js +29 -9
- package/esm/editor/plugins/edit-bar/edit-bar.d.ts +3 -1
- package/esm/editor/plugins/edit-bar/edit-bar.js +17 -7
- package/esm/editor/plugins/edit-bar/edit-items/align-elements.js +6 -4
- package/esm/editor/plugins/edit-bar/edit-items/font-align.js +8 -5
- package/esm/editor/plugins/edit-bar/edit-items/font-color.js +7 -4
- package/esm/editor/plugins/edit-bar/edit-items/font-family.js +11 -9
- package/esm/editor/plugins/edit-bar/edit-items/font-size.js +8 -5
- package/esm/editor/plugins/edit-bar/edit-items/icon-color.js +7 -4
- package/esm/editor/plugins/edit-bar/edit-items/types.d.ts +5 -1
- package/esm/editor/plugins/reset-viewbox.d.ts +4 -1
- package/esm/editor/plugins/reset-viewbox.js +12 -6
- package/esm/editor/utils/index.d.ts +1 -0
- package/esm/editor/utils/index.js +1 -0
- package/esm/editor/utils/root.d.ts +3 -0
- package/esm/editor/utils/root.js +18 -0
- package/esm/exporter/svg.js +192 -52
- package/esm/resource/loaders/search.js +0 -3
- package/esm/templates/utils.js +11 -6
- package/esm/utils/padding.js +1 -1
- package/esm/utils/style.d.ts +3 -1
- package/esm/utils/style.js +27 -4
- package/esm/version.d.ts +1 -1
- package/esm/version.js +1 -1
- package/lib/constants/service.d.ts +1 -1
- package/lib/constants/service.js +1 -1
- package/lib/designs/structures/chart-line.js +5 -3
- package/lib/editor/interactions/dblclick-edit-text.js +3 -3
- package/lib/editor/managers/interaction.js +7 -5
- package/lib/editor/plugins/components/button.d.ts +2 -1
- package/lib/editor/plugins/components/button.js +4 -4
- package/lib/editor/plugins/components/color-picker.d.ts +1 -0
- package/lib/editor/plugins/components/color-picker.js +3 -3
- package/lib/editor/plugins/components/popover.d.ts +3 -1
- package/lib/editor/plugins/components/popover.js +32 -12
- package/lib/editor/plugins/edit-bar/edit-bar.d.ts +3 -1
- package/lib/editor/plugins/edit-bar/edit-bar.js +17 -7
- package/lib/editor/plugins/edit-bar/edit-items/align-elements.js +6 -4
- package/lib/editor/plugins/edit-bar/edit-items/font-align.js +8 -5
- package/lib/editor/plugins/edit-bar/edit-items/font-color.js +7 -4
- package/lib/editor/plugins/edit-bar/edit-items/font-family.js +11 -9
- package/lib/editor/plugins/edit-bar/edit-items/font-size.js +8 -5
- package/lib/editor/plugins/edit-bar/edit-items/icon-color.js +7 -4
- package/lib/editor/plugins/edit-bar/edit-items/types.d.ts +5 -1
- package/lib/editor/plugins/reset-viewbox.d.ts +4 -1
- package/lib/editor/plugins/reset-viewbox.js +12 -6
- package/lib/editor/utils/index.d.ts +1 -0
- package/lib/editor/utils/index.js +1 -0
- package/lib/editor/utils/root.d.ts +3 -0
- package/lib/editor/utils/root.js +22 -0
- package/lib/exporter/svg.js +192 -52
- package/lib/resource/loaders/search.js +0 -3
- package/lib/templates/utils.js +11 -6
- package/lib/utils/padding.js +1 -1
- package/lib/utils/style.d.ts +3 -1
- package/lib/utils/style.js +27 -4
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/src/constants/service.ts +1 -1
- package/src/designs/structures/chart-line.tsx +5 -3
- package/src/editor/interactions/dblclick-edit-text.ts +3 -2
- package/src/editor/managers/interaction.ts +9 -7
- package/src/editor/plugins/components/button.ts +5 -2
- package/src/editor/plugins/components/color-picker.ts +4 -2
- package/src/editor/plugins/components/popover.ts +31 -12
- package/src/editor/plugins/edit-bar/edit-bar.ts +26 -11
- package/src/editor/plugins/edit-bar/edit-items/align-elements.ts +7 -2
- package/src/editor/plugins/edit-bar/edit-items/font-align.ts +8 -3
- package/src/editor/plugins/edit-bar/edit-items/font-color.ts +7 -2
- package/src/editor/plugins/edit-bar/edit-items/font-family.ts +11 -7
- package/src/editor/plugins/edit-bar/edit-items/font-size.ts +8 -3
- package/src/editor/plugins/edit-bar/edit-items/icon-color.ts +7 -2
- package/src/editor/plugins/edit-bar/edit-items/types.ts +6 -1
- package/src/editor/plugins/reset-viewbox.ts +17 -8
- package/src/editor/utils/index.ts +1 -0
- package/src/editor/utils/root.ts +26 -0
- package/src/exporter/svg.ts +267 -62
- package/src/resource/loaders/search.ts +0 -3
- package/src/templates/utils.ts +30 -6
- package/src/utils/padding.ts +1 -1
- package/src/utils/style.ts +31 -4
- package/src/version.ts +1 -1
package/src/exporter/svg.ts
CHANGED
|
@@ -11,6 +11,17 @@ import { embedFonts } from './font';
|
|
|
11
11
|
import type { SVGExportOptions } from './types';
|
|
12
12
|
|
|
13
13
|
const VIEWBOX_CHANGE_TOLERANCE = 0.5;
|
|
14
|
+
type BoundsTuple = [number, number, number, number];
|
|
15
|
+
|
|
16
|
+
interface ForeignObjectExportAdjustment {
|
|
17
|
+
rootBounds: BoundsTuple;
|
|
18
|
+
exportBounds: {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
14
25
|
|
|
15
26
|
export async function exportToSVGString(
|
|
16
27
|
svg: SVGSVGElement,
|
|
@@ -26,7 +37,12 @@ function getExportViewBox(svg: SVGSVGElement) {
|
|
|
26
37
|
|
|
27
38
|
const width = parseAbsoluteLength(svg.getAttribute('width'));
|
|
28
39
|
const height = parseAbsoluteLength(svg.getAttribute('height'));
|
|
29
|
-
if (
|
|
40
|
+
if (
|
|
41
|
+
!Number.isNaN(width) &&
|
|
42
|
+
width > 0 &&
|
|
43
|
+
!Number.isNaN(height) &&
|
|
44
|
+
height > 0
|
|
45
|
+
) {
|
|
30
46
|
return { x: 0, y: 0, width, height };
|
|
31
47
|
}
|
|
32
48
|
|
|
@@ -46,41 +62,98 @@ function parseAbsoluteLength(value: string | null): number {
|
|
|
46
62
|
return Number.parseFloat(trimmed);
|
|
47
63
|
}
|
|
48
64
|
|
|
49
|
-
function
|
|
65
|
+
function parseCoordinate(value: string | null): number {
|
|
66
|
+
const parsed = parseAbsoluteLength(value);
|
|
67
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface MeasuredSpanContentDimensions {
|
|
71
|
+
height: number;
|
|
72
|
+
width: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function measureSpanContentDimensions(
|
|
76
|
+
span: HTMLElement,
|
|
77
|
+
measureWidth: boolean,
|
|
78
|
+
): MeasuredSpanContentDimensions {
|
|
50
79
|
const prevHeight = span.style.height;
|
|
80
|
+
const prevWidth = span.style.width;
|
|
51
81
|
const prevOverflow = span.style.overflow;
|
|
52
82
|
try {
|
|
53
83
|
span.style.height = 'max-content';
|
|
54
84
|
span.style.overflow = 'hidden';
|
|
55
85
|
void span.offsetHeight; // force reflow
|
|
56
|
-
|
|
86
|
+
const scrollHeight = span.scrollHeight;
|
|
87
|
+
const rectHeight = span.getBoundingClientRect().height;
|
|
88
|
+
let width = span.scrollWidth;
|
|
89
|
+
|
|
90
|
+
if (measureWidth) {
|
|
91
|
+
span.style.width = 'max-content';
|
|
92
|
+
void span.offsetWidth; // force reflow
|
|
93
|
+
width = span.scrollWidth;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
height: Math.max(scrollHeight, rectHeight),
|
|
98
|
+
width,
|
|
99
|
+
};
|
|
57
100
|
} finally {
|
|
58
101
|
span.style.height = prevHeight;
|
|
102
|
+
span.style.width = prevWidth;
|
|
59
103
|
span.style.overflow = prevOverflow;
|
|
60
104
|
}
|
|
61
105
|
}
|
|
62
106
|
|
|
63
|
-
function
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
107
|
+
function shouldKeepForeignObjectWidth(style: CSSStyleDeclaration): boolean {
|
|
108
|
+
const whiteSpace = style.whiteSpace;
|
|
109
|
+
const flexWrap = style.flexWrap;
|
|
110
|
+
const wordBreak = style.wordBreak;
|
|
111
|
+
const overflowWrap = style.overflowWrap;
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
flexWrap === 'wrap' ||
|
|
115
|
+
flexWrap === 'wrap-reverse' ||
|
|
116
|
+
whiteSpace === 'pre-wrap' ||
|
|
117
|
+
whiteSpace === 'pre-line' ||
|
|
118
|
+
whiteSpace === 'normal' ||
|
|
119
|
+
overflowWrap === 'break-word' ||
|
|
120
|
+
wordBreak === 'break-word' ||
|
|
121
|
+
wordBreak === 'break-all'
|
|
122
|
+
);
|
|
75
123
|
}
|
|
76
124
|
|
|
77
|
-
|
|
125
|
+
function createCoordConverter(
|
|
126
|
+
svg: SVGSVGElement,
|
|
127
|
+
element: SVGGraphicsElement,
|
|
128
|
+
): ((x: number, y: number) => SVGPoint) | null {
|
|
129
|
+
if (typeof element.getScreenCTM !== 'function') return null;
|
|
130
|
+
const screenCTM = element.getScreenCTM();
|
|
131
|
+
if (!screenCTM) return null;
|
|
132
|
+
const inverseCTM = screenCTM.inverse();
|
|
133
|
+
|
|
134
|
+
return (clientX: number, clientY: number) => {
|
|
135
|
+
const pt = svg.createSVGPoint();
|
|
136
|
+
pt.x = clientX;
|
|
137
|
+
pt.y = clientY;
|
|
138
|
+
return pt.matrixTransform(inverseCTM);
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Returns [left, top, right, bottom] in target coordinates for a foreignObject,
|
|
78
143
|
// accounting for flex alignment: bottom/center-aligned content can overflow,
|
|
79
144
|
// and horizontally aligned content can overflow as well.
|
|
80
145
|
function getFOContentBoundsInSVG(
|
|
81
146
|
fo: SVGForeignObjectElement,
|
|
82
|
-
content: HTMLElement,
|
|
83
147
|
toSVGCoord: (x: number, y: number) => SVGPoint,
|
|
148
|
+
{
|
|
149
|
+
contentHeight,
|
|
150
|
+
contentWidth,
|
|
151
|
+
keepForeignObjectWidth,
|
|
152
|
+
}: {
|
|
153
|
+
contentHeight: number;
|
|
154
|
+
contentWidth: number;
|
|
155
|
+
keepForeignObjectWidth: boolean;
|
|
156
|
+
},
|
|
84
157
|
): [number, number, number, number] {
|
|
85
158
|
const foRect = fo.getBoundingClientRect();
|
|
86
159
|
const foTopLeft = toSVGCoord(foRect.left, foRect.top);
|
|
@@ -98,20 +171,17 @@ function getFOContentBoundsInSVG(
|
|
|
98
171
|
foRect.height > 0 ? foHeightSVG / foRect.height : 1;
|
|
99
172
|
const svgUnitsPerClientPxX = foRect.width > 0 ? foWidthSVG / foRect.width : 1;
|
|
100
173
|
|
|
101
|
-
// Measure actual content dimensions
|
|
102
|
-
const realScrollHeight = measureSpanContentHeight(content);
|
|
103
174
|
const contentHeightSVG =
|
|
104
|
-
|
|
105
|
-
?
|
|
175
|
+
contentHeight > 0
|
|
176
|
+
? contentHeight * svgUnitsPerClientPxY
|
|
106
177
|
: foHeightSVG;
|
|
107
178
|
|
|
108
|
-
const
|
|
109
|
-
const contentWidthSVG =
|
|
110
|
-
realScrollWidth > 0 ? realScrollWidth * svgUnitsPerClientPxX : foWidthSVG;
|
|
111
|
-
|
|
112
|
-
const computedStyle = window.getComputedStyle(content);
|
|
179
|
+
const computedStyle = window.getComputedStyle(fo.firstElementChild as Element);
|
|
113
180
|
const alignItems = computedStyle.alignItems;
|
|
114
181
|
const justifyContent = computedStyle.justifyContent;
|
|
182
|
+
const contentWidthSVG = keepForeignObjectWidth
|
|
183
|
+
? foWidthSVG
|
|
184
|
+
: Math.max(foWidthSVG, contentWidth * svgUnitsPerClientPxX);
|
|
115
185
|
|
|
116
186
|
// Calculate vertical bounds
|
|
117
187
|
let top: number, bottom: number;
|
|
@@ -148,47 +218,91 @@ function getFOContentBoundsInSVG(
|
|
|
148
218
|
return [left, top, right, bottom];
|
|
149
219
|
}
|
|
150
220
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
221
|
+
function collectForeignObjectExportAdjustments(svg: SVGSVGElement) {
|
|
222
|
+
const toSVGCoord = createCoordConverter(svg, svg);
|
|
223
|
+
if (!toSVGCoord) return [];
|
|
224
|
+
|
|
225
|
+
return Array.from(
|
|
226
|
+
svg.querySelectorAll<SVGForeignObjectElement>('foreignObject'),
|
|
227
|
+
).map((fo) => {
|
|
228
|
+
const content = fo.firstElementChild as HTMLElement | null;
|
|
229
|
+
if (!content) return null;
|
|
230
|
+
const computedStyle = window.getComputedStyle(content);
|
|
231
|
+
const keepForeignObjectWidth = shouldKeepForeignObjectWidth(computedStyle);
|
|
232
|
+
const measuredContent = measureSpanContentDimensions(
|
|
233
|
+
content,
|
|
234
|
+
!keepForeignObjectWidth,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const parent =
|
|
238
|
+
fo.parentElement instanceof SVGGraphicsElement ? fo.parentElement : svg;
|
|
239
|
+
const toParentCoord = createCoordConverter(svg, parent);
|
|
240
|
+
const toLocalCoord = createCoordConverter(svg, fo);
|
|
241
|
+
if (!toParentCoord) return null;
|
|
242
|
+
|
|
243
|
+
const parentBounds = getFOContentBoundsInSVG(fo, toParentCoord, {
|
|
244
|
+
contentHeight: measuredContent.height,
|
|
245
|
+
contentWidth: measuredContent.width,
|
|
246
|
+
keepForeignObjectWidth,
|
|
247
|
+
});
|
|
248
|
+
const originalX = parseCoordinate(fo.getAttribute('x'));
|
|
249
|
+
const originalY = parseCoordinate(fo.getAttribute('y'));
|
|
250
|
+
const localBounds = toLocalCoord
|
|
251
|
+
? getFOContentBoundsInSVG(fo, toLocalCoord, {
|
|
252
|
+
contentHeight: measuredContent.height,
|
|
253
|
+
contentWidth: measuredContent.width,
|
|
254
|
+
keepForeignObjectWidth,
|
|
255
|
+
})
|
|
256
|
+
: null;
|
|
257
|
+
const hasTransform = fo.hasAttribute('transform');
|
|
258
|
+
if (hasTransform && !localBounds) return null;
|
|
259
|
+
|
|
260
|
+
const exportBounds = localBounds
|
|
261
|
+
? {
|
|
262
|
+
x: originalX + localBounds[0],
|
|
263
|
+
y: originalY + localBounds[1],
|
|
264
|
+
width: localBounds[2] - localBounds[0],
|
|
265
|
+
height: localBounds[3] - localBounds[1],
|
|
266
|
+
}
|
|
267
|
+
: {
|
|
268
|
+
x: parentBounds[0],
|
|
269
|
+
y: parentBounds[1],
|
|
270
|
+
width: parentBounds[2] - parentBounds[0],
|
|
271
|
+
height: parentBounds[3] - parentBounds[1],
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
rootBounds: getFOContentBoundsInSVG(fo, toSVGCoord, {
|
|
276
|
+
contentHeight: measuredContent.height,
|
|
277
|
+
contentWidth: measuredContent.width,
|
|
278
|
+
keepForeignObjectWidth,
|
|
279
|
+
}),
|
|
280
|
+
exportBounds,
|
|
281
|
+
} satisfies ForeignObjectExportAdjustment;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function computeFullViewBox(
|
|
286
|
+
svg: SVGSVGElement,
|
|
287
|
+
adjustments: Array<ForeignObjectExportAdjustment | null>,
|
|
288
|
+
): string | null {
|
|
157
289
|
const viewBox = getExportViewBox(svg);
|
|
158
290
|
if (!viewBox) return null;
|
|
159
291
|
|
|
160
|
-
if (typeof svg.getScreenCTM !== 'function') return null;
|
|
161
|
-
const screenCTM = svg.getScreenCTM();
|
|
162
|
-
if (!screenCTM) return null;
|
|
163
|
-
const inverseCTM = screenCTM.inverse();
|
|
164
|
-
|
|
165
|
-
const toSVGCoord = (clientX: number, clientY: number) => {
|
|
166
|
-
const pt = svg.createSVGPoint();
|
|
167
|
-
pt.x = clientX;
|
|
168
|
-
pt.y = clientY;
|
|
169
|
-
return pt.matrixTransform(inverseCTM);
|
|
170
|
-
};
|
|
171
|
-
|
|
172
292
|
let minX = viewBox.x;
|
|
173
293
|
let minY = viewBox.y;
|
|
174
294
|
let maxX = viewBox.x + viewBox.width;
|
|
175
295
|
let maxY = viewBox.y + viewBox.height;
|
|
176
296
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
);
|
|
187
|
-
minX = Math.min(minX, left);
|
|
188
|
-
minY = Math.min(minY, top);
|
|
189
|
-
maxX = Math.max(maxX, right);
|
|
190
|
-
maxY = Math.max(maxY, bottom);
|
|
191
|
-
});
|
|
297
|
+
adjustments.forEach((adjustment) => {
|
|
298
|
+
if (!adjustment) return;
|
|
299
|
+
const { rootBounds } = adjustment;
|
|
300
|
+
const [left, top, right, bottom] = rootBounds;
|
|
301
|
+
minX = Math.min(minX, left);
|
|
302
|
+
minY = Math.min(minY, top);
|
|
303
|
+
maxX = Math.max(maxX, right);
|
|
304
|
+
maxY = Math.max(maxY, bottom);
|
|
305
|
+
});
|
|
192
306
|
|
|
193
307
|
const newX = minX;
|
|
194
308
|
const newY = minY;
|
|
@@ -205,6 +319,23 @@ function computeFullViewBox(svg: SVGSVGElement): string | null {
|
|
|
205
319
|
return `${newX} ${newY} ${newWidth} ${newHeight}`;
|
|
206
320
|
}
|
|
207
321
|
|
|
322
|
+
function applyForeignObjectExportAdjustments(
|
|
323
|
+
svg: SVGSVGElement,
|
|
324
|
+
adjustments: Array<ForeignObjectExportAdjustment | null>,
|
|
325
|
+
) {
|
|
326
|
+
const clonedForeignObjects = Array.from(
|
|
327
|
+
svg.querySelectorAll<SVGForeignObjectElement>('foreignObject'),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
adjustments.forEach((adjustment, index) => {
|
|
331
|
+
if (!adjustment) return;
|
|
332
|
+
const clonedForeignObject = clonedForeignObjects[index];
|
|
333
|
+
if (!clonedForeignObject) return;
|
|
334
|
+
|
|
335
|
+
setAttributes(clonedForeignObject, adjustment.exportBounds);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
208
339
|
export async function exportToSVG(
|
|
209
340
|
svg: SVGSVGElement,
|
|
210
341
|
options: Omit<SVGExportOptions, 'type'> = {},
|
|
@@ -217,7 +348,9 @@ export async function exportToSVG(
|
|
|
217
348
|
const clonedSVG = svg.cloneNode(true) as SVGSVGElement;
|
|
218
349
|
|
|
219
350
|
if (typeof document !== 'undefined') {
|
|
220
|
-
const
|
|
351
|
+
const adjustments = collectForeignObjectExportAdjustments(svg);
|
|
352
|
+
applyForeignObjectExportAdjustments(clonedSVG, adjustments);
|
|
353
|
+
const fullViewBox = computeFullViewBox(svg, adjustments);
|
|
221
354
|
if (fullViewBox) {
|
|
222
355
|
clonedSVG.setAttribute('viewBox', fullViewBox);
|
|
223
356
|
}
|
|
@@ -253,7 +386,9 @@ async function embedIcons(svg: SVGSVGElement) {
|
|
|
253
386
|
|
|
254
387
|
if (!existsSymbol) {
|
|
255
388
|
const symbolElement = document.querySelector(href);
|
|
256
|
-
if (symbolElement)
|
|
389
|
+
if (symbolElement) {
|
|
390
|
+
defs.appendChild(symbolElement.cloneNode(true));
|
|
391
|
+
}
|
|
257
392
|
}
|
|
258
393
|
});
|
|
259
394
|
}
|
|
@@ -487,6 +622,7 @@ function collectDefElements(svg: SVGSVGElement, ids: Set<string>) {
|
|
|
487
622
|
|
|
488
623
|
while (queue.length) {
|
|
489
624
|
const id = queue.shift()!;
|
|
625
|
+
if (!id) continue;
|
|
490
626
|
if (visited.has(id)) continue;
|
|
491
627
|
visited.add(id);
|
|
492
628
|
|
|
@@ -503,11 +639,80 @@ function collectDefElements(svg: SVGSVGElement, ids: Set<string>) {
|
|
|
503
639
|
return collected;
|
|
504
640
|
}
|
|
505
641
|
|
|
642
|
+
// Fallback implementation based on the CSS.escape algorithm
|
|
643
|
+
function cssEscape(value: string): string {
|
|
644
|
+
const string = String(value);
|
|
645
|
+
const length = string.length;
|
|
646
|
+
let result = '';
|
|
647
|
+
|
|
648
|
+
if (length === 0) {
|
|
649
|
+
return '';
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
for (let i = 0; i < length; i++) {
|
|
653
|
+
const codeUnit = string.charCodeAt(i);
|
|
654
|
+
|
|
655
|
+
// Null character
|
|
656
|
+
if (codeUnit === 0x0000) {
|
|
657
|
+
result += '\uFFFD';
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Control characters or DEL
|
|
662
|
+
if ((codeUnit >= 0x0001 && codeUnit <= 0x001f) || codeUnit === 0x007f) {
|
|
663
|
+
result += '\\' + codeUnit.toString(16) + ' ';
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Escape if first character is a digit
|
|
668
|
+
if (i === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) {
|
|
669
|
+
result += '\\' + codeUnit.toString(16) + ' ';
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Escape if second character is a digit and first is a hyphen
|
|
674
|
+
if (
|
|
675
|
+
i === 1 &&
|
|
676
|
+
codeUnit >= 0x0030 &&
|
|
677
|
+
codeUnit <= 0x0039 &&
|
|
678
|
+
string.charCodeAt(0) === 0x002d
|
|
679
|
+
) {
|
|
680
|
+
result += '\\' + codeUnit.toString(16) + ' ';
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// If the character is the first and is a hyphen followed by end of string, escape it
|
|
685
|
+
if (i === 0 && length === 1 && codeUnit === 0x002d) {
|
|
686
|
+
result += '\\' + string.charAt(i);
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Characters that are safe to use unescaped
|
|
691
|
+
if (
|
|
692
|
+
codeUnit >= 0x0080 ||
|
|
693
|
+
(codeUnit >= 0x0030 && codeUnit <= 0x0039) || // 0-9
|
|
694
|
+
(codeUnit >= 0x0041 && codeUnit <= 0x005a) || // A-Z
|
|
695
|
+
(codeUnit >= 0x0061 && codeUnit <= 0x007a) || // a-z
|
|
696
|
+
codeUnit === 0x002d || // -
|
|
697
|
+
codeUnit === 0x005f // _
|
|
698
|
+
) {
|
|
699
|
+
result += string.charAt(i);
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// All other characters
|
|
704
|
+
result += '\\' + string.charAt(i);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
return result;
|
|
708
|
+
}
|
|
709
|
+
|
|
506
710
|
function escapeCssId(id: string) {
|
|
507
711
|
if (globalThis.CSS && typeof globalThis.CSS.escape === 'function') {
|
|
508
712
|
return globalThis.CSS.escape(id);
|
|
509
713
|
}
|
|
510
|
-
|
|
714
|
+
|
|
715
|
+
return cssEscape(id);
|
|
511
716
|
}
|
|
512
717
|
|
|
513
718
|
function removeDefs(svg: SVGSVGElement) {
|
|
@@ -43,9 +43,6 @@ export async function loadSearchResource(query: string, format?: string) {
|
|
|
43
43
|
const svgText = commaIndex >= 0 ? result.slice(commaIndex + 1) : result;
|
|
44
44
|
return loadSVGResource(svgText);
|
|
45
45
|
}
|
|
46
|
-
if (mimeType === 'image/svg+xml' && format === 'svg' && isBase64) {
|
|
47
|
-
return loadImageBase64Resource(result);
|
|
48
|
-
}
|
|
49
46
|
return loadImageBase64Resource(result);
|
|
50
47
|
}
|
|
51
48
|
|
package/src/templates/utils.ts
CHANGED
|
@@ -43,13 +43,34 @@ function getCommonPrefixLength(source: string, target: string): number {
|
|
|
43
43
|
const limit = Math.min(source.length, target.length);
|
|
44
44
|
let index = 0;
|
|
45
45
|
|
|
46
|
-
while (
|
|
46
|
+
while (
|
|
47
|
+
index < limit &&
|
|
48
|
+
source.charCodeAt(index) === target.charCodeAt(index)
|
|
49
|
+
) {
|
|
47
50
|
index += 1;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
return index;
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
function isBetterMatch(
|
|
57
|
+
bestMatch: string | undefined,
|
|
58
|
+
bestDistance: number,
|
|
59
|
+
bestPrefixLength: number,
|
|
60
|
+
candidateKey: string,
|
|
61
|
+
candidateDistance: number,
|
|
62
|
+
candidatePrefixLength: number,
|
|
63
|
+
): boolean {
|
|
64
|
+
return (
|
|
65
|
+
candidateDistance < bestDistance ||
|
|
66
|
+
(candidateDistance === bestDistance &&
|
|
67
|
+
candidatePrefixLength > bestPrefixLength) ||
|
|
68
|
+
(candidateDistance === bestDistance &&
|
|
69
|
+
candidatePrefixLength === bestPrefixLength &&
|
|
70
|
+
(!bestMatch || candidateKey < bestMatch))
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
53
74
|
export function findClosestTemplateKey(
|
|
54
75
|
type: string,
|
|
55
76
|
keys: Iterable<string>,
|
|
@@ -71,11 +92,14 @@ export function findClosestTemplateKey(
|
|
|
71
92
|
const prefixLength = getCommonPrefixLength(normalizedType, normalizedKey);
|
|
72
93
|
|
|
73
94
|
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
isBetterMatch(
|
|
96
|
+
bestMatch,
|
|
97
|
+
bestDistance,
|
|
98
|
+
bestPrefixLength,
|
|
99
|
+
key,
|
|
100
|
+
distance,
|
|
101
|
+
prefixLength,
|
|
102
|
+
)
|
|
79
103
|
) {
|
|
80
104
|
bestMatch = key;
|
|
81
105
|
bestDistance = distance;
|
package/src/utils/padding.ts
CHANGED
|
@@ -27,7 +27,7 @@ export function setSVGPadding(svg: SVGSVGElement, padding: ParsedPadding) {
|
|
|
27
27
|
if (isNode) {
|
|
28
28
|
setSVGPaddingInNode(svg, padding);
|
|
29
29
|
} else {
|
|
30
|
-
if (
|
|
30
|
+
if (svg.isConnected) {
|
|
31
31
|
setSVGPaddingInBrowser(svg, padding);
|
|
32
32
|
} else {
|
|
33
33
|
try {
|
package/src/utils/style.ts
CHANGED
|
@@ -1,8 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
if (document.getElementById(id)) return;
|
|
1
|
+
type StyleTarget = Document | ShadowRoot | Node | null | undefined;
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
function resolveStyleRoot(target?: StyleTarget): Document | ShadowRoot {
|
|
4
|
+
if (!target) return document;
|
|
5
|
+
if (target instanceof Document || target instanceof ShadowRoot) return target;
|
|
6
|
+
if (!target.isConnected) return document;
|
|
7
|
+
|
|
8
|
+
const root = target.getRootNode();
|
|
9
|
+
return root instanceof ShadowRoot ? root : document;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function hasStyle(root: Document | ShadowRoot, id: string) {
|
|
13
|
+
if (root instanceof Document) return Boolean(root.getElementById(id));
|
|
14
|
+
return Boolean(root.querySelector(`#${id}`));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function injectStyleOnce(
|
|
18
|
+
id: string,
|
|
19
|
+
styles: string,
|
|
20
|
+
target?: StyleTarget,
|
|
21
|
+
) {
|
|
22
|
+
const root = resolveStyleRoot(target);
|
|
23
|
+
if (hasStyle(root, id)) return;
|
|
24
|
+
|
|
25
|
+
const doc = root instanceof Document ? root : (root.ownerDocument ?? document);
|
|
26
|
+
const style = doc.createElement('style');
|
|
5
27
|
style.id = id;
|
|
6
28
|
style.textContent = styles;
|
|
7
|
-
|
|
29
|
+
|
|
30
|
+
if (root instanceof Document) {
|
|
31
|
+
root.head.appendChild(style);
|
|
32
|
+
} else {
|
|
33
|
+
root.appendChild(style);
|
|
34
|
+
}
|
|
8
35
|
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.2.
|
|
1
|
+
export const VERSION = '0.2.19';
|