@antv/infographic 0.2.4 → 0.2.6

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 (55) hide show
  1. package/README.md +2 -2
  2. package/README.zh-CN.md +2 -2
  3. package/dist/infographic.min.js +88 -78
  4. package/dist/infographic.min.js.map +1 -1
  5. package/esm/exporter/index.d.ts +1 -1
  6. package/esm/exporter/index.js +1 -1
  7. package/esm/exporter/svg.js +223 -2
  8. package/esm/exporter/types.d.ts +5 -0
  9. package/esm/index.d.ts +4 -2
  10. package/esm/index.js +3 -2
  11. package/esm/renderer/fonts/loader.js +63 -8
  12. package/esm/renderer/stylize/gradient.js +1 -1
  13. package/esm/resource/index.d.ts +1 -1
  14. package/esm/resource/index.js +1 -1
  15. package/esm/resource/load-tracker.d.ts +6 -0
  16. package/esm/resource/load-tracker.js +36 -0
  17. package/esm/resource/loader.d.ts +1 -0
  18. package/esm/resource/loader.js +27 -14
  19. package/esm/runtime/Infographic.js +13 -0
  20. package/esm/utils/is-browser.d.ts +1 -0
  21. package/esm/utils/is-browser.js +68 -0
  22. package/esm/utils/measure-text.d.ts +1 -0
  23. package/esm/utils/measure-text.js +9 -7
  24. package/lib/exporter/index.d.ts +1 -1
  25. package/lib/exporter/index.js +2 -1
  26. package/lib/exporter/svg.js +223 -2
  27. package/lib/exporter/types.d.ts +5 -0
  28. package/lib/index.d.ts +4 -2
  29. package/lib/index.js +6 -3
  30. package/lib/renderer/fonts/loader.js +63 -8
  31. package/lib/renderer/stylize/gradient.js +1 -1
  32. package/lib/resource/index.d.ts +1 -1
  33. package/lib/resource/index.js +3 -1
  34. package/lib/resource/load-tracker.d.ts +6 -0
  35. package/lib/resource/load-tracker.js +42 -0
  36. package/lib/resource/loader.d.ts +1 -0
  37. package/lib/resource/loader.js +30 -14
  38. package/lib/runtime/Infographic.js +13 -0
  39. package/lib/utils/is-browser.d.ts +1 -0
  40. package/lib/utils/is-browser.js +71 -0
  41. package/lib/utils/measure-text.d.ts +1 -0
  42. package/lib/utils/measure-text.js +11 -7
  43. package/package.json +1 -1
  44. package/src/exporter/index.ts +1 -1
  45. package/src/exporter/svg.ts +254 -2
  46. package/src/exporter/types.ts +5 -0
  47. package/src/index.ts +8 -3
  48. package/src/renderer/fonts/loader.ts +82 -9
  49. package/src/renderer/stylize/gradient.ts +1 -1
  50. package/src/resource/index.ts +1 -1
  51. package/src/resource/load-tracker.ts +51 -0
  52. package/src/resource/loader.ts +27 -12
  53. package/src/runtime/Infographic.tsx +12 -0
  54. package/src/utils/is-browser.ts +79 -0
  55. package/src/utils/measure-text.ts +11 -7
@@ -2,16 +2,18 @@ import { measureText as measure, registerFont } from 'measury';
2
2
  import AlibabaPuHuiTi from 'measury/fonts/AlibabaPuHuiTi-Regular';
3
3
  import { DEFAULT_FONT } from '../renderer';
4
4
  import { encodeFontFamily } from './font';
5
+ import { isBrowser } from './is-browser';
5
6
  import { isNode } from './is-node';
7
+ let FONT_EXTEND_FACTOR = 1.01;
8
+ export const setFontExtendFactor = (factor) => {
9
+ FONT_EXTEND_FACTOR = factor;
10
+ };
6
11
  if (isNode) {
7
12
  registerFont(AlibabaPuHuiTi);
8
13
  }
9
- const canUseDOM = !isNode && typeof window !== 'undefined' && typeof document !== 'undefined';
10
14
  let canvasContext = null;
11
15
  let measureSpan = null;
12
16
  function getCanvasContext() {
13
- if (!canUseDOM)
14
- return null;
15
17
  if (canvasContext)
16
18
  return canvasContext;
17
19
  const canvas = document.createElement('canvas');
@@ -19,7 +21,7 @@ function getCanvasContext() {
19
21
  return canvasContext;
20
22
  }
21
23
  function getMeasureSpan() {
22
- if (!canUseDOM || !document.body)
24
+ if (!document.body)
23
25
  return null;
24
26
  if (measureSpan)
25
27
  return measureSpan;
@@ -91,12 +93,12 @@ export function measureText(text = '', attrs) {
91
93
  lineHeight,
92
94
  };
93
95
  const fallback = () => measure(content, options);
94
- const metrics = canUseDOM
96
+ const metrics = isBrowser()
95
97
  ? (measureTextInBrowser(content, options) ?? fallback())
96
98
  : fallback();
97
99
  // 额外添加 1% 宽高
98
100
  return {
99
- width: Math.ceil(metrics.width * 1.01),
100
- height: Math.ceil(metrics.height * 1.01),
101
+ width: Math.ceil(metrics.width * FONT_EXTEND_FACTOR),
102
+ height: Math.ceil(metrics.height * FONT_EXTEND_FACTOR),
101
103
  };
102
104
  }
@@ -1,3 +1,3 @@
1
1
  export { exportToPNGString } from './png';
2
- export { exportToSVGString } from './svg';
2
+ export { exportToSVG, exportToSVGString } from './svg';
3
3
  export type { ExportOptions, PNGExportOptions, SVGExportOptions, } from './types';
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.exportToSVGString = exports.exportToPNGString = void 0;
3
+ exports.exportToSVGString = exports.exportToSVG = exports.exportToPNGString = void 0;
4
4
  var png_1 = require("./png");
5
5
  Object.defineProperty(exports, "exportToPNGString", { enumerable: true, get: function () { return png_1.exportToPNGString; } });
6
6
  var svg_1 = require("./svg");
7
+ Object.defineProperty(exports, "exportToSVG", { enumerable: true, get: function () { return svg_1.exportToSVG; } });
7
8
  Object.defineProperty(exports, "exportToSVGString", { enumerable: true, get: function () { return svg_1.exportToSVGString; } });
@@ -10,11 +10,17 @@ async function exportToSVGString(svg, options = {}) {
10
10
  return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(str);
11
11
  }
12
12
  async function exportToSVG(svg, options = {}) {
13
- const { embedResources = true } = options;
13
+ const { embedResources = true, removeIds = false } = options;
14
14
  const clonedSVG = svg.cloneNode(true);
15
15
  const { width, height } = (0, utils_1.getViewBox)(svg);
16
16
  (0, utils_1.setAttributes)(clonedSVG, { width, height });
17
- await embedIcons(clonedSVG);
17
+ if (removeIds) {
18
+ inlineUseElements(clonedSVG);
19
+ inlineDefsReferences(clonedSVG);
20
+ }
21
+ else {
22
+ await embedIcons(clonedSVG);
23
+ }
18
24
  await (0, font_1.embedFonts)(clonedSVG, embedResources);
19
25
  cleanSVG(clonedSVG);
20
26
  return clonedSVG;
@@ -44,6 +50,221 @@ function getDefs(svg) {
44
50
  svg.prepend(_defs);
45
51
  return _defs;
46
52
  }
53
+ function inlineUseElements(svg) {
54
+ const uses = Array.from(svg.querySelectorAll('use'));
55
+ if (!uses.length)
56
+ return;
57
+ uses.forEach((use) => {
58
+ const href = getUseHref(use);
59
+ if (!href || !href.startsWith('#'))
60
+ return;
61
+ const target = resolveUseTarget(svg, href);
62
+ if (!target || target === use)
63
+ return;
64
+ const replacement = createInlineElement(use, target);
65
+ if (!replacement)
66
+ return;
67
+ use.replaceWith(replacement);
68
+ });
69
+ }
70
+ function getUseHref(use) {
71
+ return use.getAttribute('href') ?? use.getAttribute('xlink:href');
72
+ }
73
+ function resolveUseTarget(svg, href) {
74
+ const localTarget = svg.querySelector(href);
75
+ if (localTarget)
76
+ return localTarget;
77
+ const docTarget = document.querySelector(href);
78
+ return docTarget;
79
+ }
80
+ function createInlineElement(use, target) {
81
+ const tag = target.tagName.toLowerCase();
82
+ if (tag === 'symbol') {
83
+ return materializeSymbol(use, target);
84
+ }
85
+ if (tag === 'svg') {
86
+ return materializeSVG(use, target);
87
+ }
88
+ return materializeElement(use, target);
89
+ }
90
+ function materializeSymbol(use, symbol) {
91
+ const symbolClone = symbol.cloneNode(true);
92
+ const svg = (0, utils_1.createElement)('svg');
93
+ applyAttributes(svg, symbolClone, new Set(['id']));
94
+ applyAttributes(svg, use, new Set(['href', 'xlink:href']));
95
+ while (symbolClone.firstChild) {
96
+ svg.appendChild(symbolClone.firstChild);
97
+ }
98
+ return svg;
99
+ }
100
+ function materializeSVG(use, source) {
101
+ const clone = source.cloneNode(true);
102
+ clone.removeAttribute('id');
103
+ applyAttributes(clone, use, new Set(['href', 'xlink:href']));
104
+ return clone;
105
+ }
106
+ function materializeElement(use, source) {
107
+ const clone = source.cloneNode(true);
108
+ clone.removeAttribute('id');
109
+ const wrapper = (0, utils_1.createElement)('g');
110
+ applyAttributes(wrapper, use, new Set(['href', 'xlink:href', 'x', 'y', 'width', 'height', 'transform']));
111
+ const transform = buildUseTransform(use);
112
+ if (transform) {
113
+ wrapper.setAttribute('transform', transform);
114
+ }
115
+ wrapper.appendChild(clone);
116
+ return wrapper;
117
+ }
118
+ function buildUseTransform(use) {
119
+ const x = use.getAttribute('x');
120
+ const y = use.getAttribute('y');
121
+ const translate = x || y ? `translate(${x ?? 0} ${y ?? 0})` : '';
122
+ const transform = use.getAttribute('transform') ?? '';
123
+ if (translate && transform)
124
+ return `${translate} ${transform}`;
125
+ return translate || transform || null;
126
+ }
127
+ function applyAttributes(target, source, exclude = new Set()) {
128
+ Array.from(source.attributes).forEach((attr) => {
129
+ if (exclude.has(attr.name))
130
+ return;
131
+ if (attr.name === 'style') {
132
+ mergeStyleAttribute(target, attr.value);
133
+ return;
134
+ }
135
+ if (attr.name === 'class') {
136
+ mergeClassAttribute(target, attr.value);
137
+ return;
138
+ }
139
+ target.setAttribute(attr.name, attr.value);
140
+ });
141
+ }
142
+ function mergeStyleAttribute(target, value) {
143
+ const current = target.getAttribute('style');
144
+ if (!current) {
145
+ target.setAttribute('style', value);
146
+ return;
147
+ }
148
+ const separator = current.trim().endsWith(';') ? '' : ';';
149
+ target.setAttribute('style', `${current}${separator}${value}`);
150
+ }
151
+ function mergeClassAttribute(target, value) {
152
+ const current = target.getAttribute('class');
153
+ if (!current) {
154
+ target.setAttribute('class', value);
155
+ return;
156
+ }
157
+ target.setAttribute('class', `${current} ${value}`.trim());
158
+ }
159
+ const urlRefRegex = /url\(\s*['"]?#([^'")\s]+)['"]?\s*\)/g;
160
+ function inlineDefsReferences(svg) {
161
+ const referencedIds = collectReferencedIds(svg);
162
+ if (referencedIds.size === 0) {
163
+ removeDefs(svg);
164
+ return;
165
+ }
166
+ const defsDataUrl = createDefsDataUrl(svg, referencedIds);
167
+ if (!defsDataUrl)
168
+ return;
169
+ (0, utils_1.traverse)(svg, (node) => {
170
+ if (node.tagName.toLowerCase() === 'defs')
171
+ return false;
172
+ const attrs = Array.from(node.attributes);
173
+ attrs.forEach((attr) => {
174
+ const value = attr.value;
175
+ if (!value.includes('url('))
176
+ return;
177
+ const updated = value.replace(urlRefRegex, (_match, id) => {
178
+ const encodedId = encodeURIComponent(id);
179
+ return `url("${defsDataUrl}#${encodedId}")`;
180
+ });
181
+ if (updated !== value)
182
+ node.setAttribute(attr.name, updated);
183
+ });
184
+ });
185
+ removeDefs(svg);
186
+ }
187
+ function collectReferencedIds(svg) {
188
+ const ids = new Set();
189
+ (0, utils_1.traverse)(svg, (node) => {
190
+ if (node.tagName.toLowerCase() === 'defs')
191
+ return false;
192
+ collectIdsFromAttributes(node, (id) => ids.add(id));
193
+ });
194
+ return ids;
195
+ }
196
+ function collectIdsFromAttributes(node, addId) {
197
+ for (const attr of Array.from(node.attributes)) {
198
+ const value = attr.value;
199
+ if (value.includes('url(')) {
200
+ for (const match of value.matchAll(urlRefRegex)) {
201
+ if (match[1])
202
+ addId(match[1]);
203
+ }
204
+ }
205
+ if ((attr.name === 'href' || attr.name === 'xlink:href') &&
206
+ value[0] === '#') {
207
+ addId(value.slice(1));
208
+ }
209
+ }
210
+ }
211
+ function createDefsDataUrl(svg, ids) {
212
+ if (ids.size === 0)
213
+ return null;
214
+ const collected = collectDefElements(svg, ids);
215
+ if (collected.size === 0)
216
+ return null;
217
+ const defsSvg = (0, utils_1.createElement)('svg', {
218
+ xmlns: 'http://www.w3.org/2000/svg',
219
+ 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
220
+ });
221
+ const defs = (0, utils_1.createElement)('defs');
222
+ collected.forEach((node) => {
223
+ defs.appendChild(node.cloneNode(true));
224
+ });
225
+ if (!defs.children.length)
226
+ return null;
227
+ defsSvg.appendChild(defs);
228
+ const serialized = new XMLSerializer().serializeToString(defsSvg);
229
+ return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(serialized);
230
+ }
231
+ function collectDefElements(svg, ids) {
232
+ const collected = new Map();
233
+ const queue = Array.from(ids);
234
+ const queued = new Set(queue);
235
+ const visited = new Set();
236
+ const enqueue = (id) => {
237
+ if (visited.has(id) || queued.has(id))
238
+ return;
239
+ queue.push(id);
240
+ queued.add(id);
241
+ };
242
+ while (queue.length) {
243
+ const id = queue.shift();
244
+ if (visited.has(id))
245
+ continue;
246
+ visited.add(id);
247
+ const selector = `#${escapeCssId(id)}`;
248
+ const target = svg.querySelector(selector);
249
+ if (!target)
250
+ continue;
251
+ collected.set(id, target);
252
+ (0, utils_1.traverse)(target, (node) => {
253
+ collectIdsFromAttributes(node, enqueue);
254
+ });
255
+ }
256
+ return collected;
257
+ }
258
+ function escapeCssId(id) {
259
+ if (globalThis.CSS && typeof globalThis.CSS.escape === 'function') {
260
+ return globalThis.CSS.escape(id);
261
+ }
262
+ return id.replace(/([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g, '\\$1');
263
+ }
264
+ function removeDefs(svg) {
265
+ const defsList = Array.from(svg.querySelectorAll('defs'));
266
+ defsList.forEach((defs) => defs.remove());
267
+ }
47
268
  function cleanSVG(svg) {
48
269
  removeBtnGroup(svg);
49
270
  removeTransientContainer(svg);
@@ -5,6 +5,11 @@ export interface SVGExportOptions {
5
5
  * @default true
6
6
  */
7
7
  embedResources?: boolean;
8
+ /**
9
+ * 是否移除 id 依赖(展开 <use> 并内联 defs 引用)
10
+ * @default false
11
+ */
12
+ removeIds?: boolean;
8
13
  }
9
14
  export interface PNGExportOptions {
10
15
  type: 'png';
package/lib/index.d.ts CHANGED
@@ -3,15 +3,17 @@ export * from './designs';
3
3
  export { getItemProps, getThemeColors } from './designs/utils';
4
4
  export { BrushSelect, ClickSelect, DblClickEditText, DragElement, HotkeyHistory, Interaction, SelectHighlight, ZoomWheel, } from './editor/interactions';
5
5
  export { EditBar, Plugin, ResizeElement } from './editor/plugins';
6
+ export { exportToSVG } from './exporter';
6
7
  export { Defs, Ellipse, Fragment, Group, Path, Polygon, Rect, Text, cloneElement, createFragment, createLayout, getCombinedBounds, getElementBounds, getElementsBounds, jsx, jsxDEV, jsxs, renderSVG, } from './jsx';
7
- export { getFont, getFonts, getPalette, getPalettes, getPaletteColor, registerFont, registerPalette, registerPattern, setDefaultFont, } from './renderer';
8
+ export { getFont, getFonts, getPalette, getPaletteColor, getPalettes, registerFont, registerPalette, registerPattern, setDefaultFont, } from './renderer';
8
9
  export { loadSVGResource, registerResourceLoader } from './resource';
9
10
  export { Infographic } from './runtime';
10
11
  export { parseSyntax } from './syntax';
11
12
  export { getTemplate, getTemplates, registerTemplate } from './templates';
12
13
  export { getTheme, getThemes, registerTheme } from './themes';
13
- export { parseSVG } from './utils';
14
+ export { parseSVG, setFontExtendFactor } from './utils';
14
15
  export type { EditBarOptions } from './editor';
16
+ export type { ExportOptions, PNGExportOptions, SVGExportOptions, } from './exporter';
15
17
  export type { Bounds, ComponentType, DefsProps, EllipseProps, FragmentProps, GroupProps, JSXElement, JSXElementConstructor, JSXNode, PathProps, Point, PolygonProps, RectProps, RenderableNode, SVGAttributes, SVGProps, TextProps, WithChildren, } from './jsx';
16
18
  export type { InfographicOptions, ParsedInfographicOptions } from './options';
17
19
  export type { GradientConfig, IRenderer, LinearGradient, Palette, PatternConfig, PatternGenerator, PatternStyle, RadialGradient, RoughConfig, StylizeConfig, } from './renderer';
package/lib/index.js CHANGED
@@ -17,8 +17,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.getThemes = exports.getTheme = exports.registerTemplate = exports.getTemplates = exports.getTemplate = exports.parseSyntax = exports.Infographic = exports.registerResourceLoader = exports.loadSVGResource = exports.setDefaultFont = exports.registerPattern = exports.registerPalette = exports.registerFont = exports.getPaletteColor = exports.getPalettes = exports.getPalette = exports.getFonts = exports.getFont = exports.renderSVG = exports.jsxs = exports.jsxDEV = exports.jsx = exports.getElementsBounds = exports.getElementBounds = exports.getCombinedBounds = exports.createLayout = exports.createFragment = exports.cloneElement = exports.Text = exports.Rect = exports.Polygon = exports.Path = exports.Group = exports.Fragment = exports.Ellipse = exports.Defs = exports.ResizeElement = exports.Plugin = exports.EditBar = exports.ZoomWheel = exports.SelectHighlight = exports.Interaction = exports.HotkeyHistory = exports.DragElement = exports.DblClickEditText = exports.ClickSelect = exports.BrushSelect = exports.getThemeColors = exports.getItemProps = exports.VERSION = void 0;
21
- exports.parseSVG = exports.registerTheme = void 0;
20
+ exports.getTheme = exports.registerTemplate = exports.getTemplates = exports.getTemplate = exports.parseSyntax = exports.Infographic = exports.registerResourceLoader = exports.loadSVGResource = exports.setDefaultFont = exports.registerPattern = exports.registerPalette = exports.registerFont = exports.getPalettes = exports.getPaletteColor = exports.getPalette = exports.getFonts = exports.getFont = exports.renderSVG = exports.jsxs = exports.jsxDEV = exports.jsx = exports.getElementsBounds = exports.getElementBounds = exports.getCombinedBounds = exports.createLayout = exports.createFragment = exports.cloneElement = exports.Text = exports.Rect = exports.Polygon = exports.Path = exports.Group = exports.Fragment = exports.Ellipse = exports.Defs = exports.exportToSVG = exports.ResizeElement = exports.Plugin = exports.EditBar = exports.ZoomWheel = exports.SelectHighlight = exports.Interaction = exports.HotkeyHistory = exports.DragElement = exports.DblClickEditText = exports.ClickSelect = exports.BrushSelect = exports.getThemeColors = exports.getItemProps = exports.VERSION = void 0;
21
+ exports.setFontExtendFactor = exports.parseSVG = exports.registerTheme = exports.getThemes = void 0;
22
22
  const package_json_1 = __importDefault(require("../package.json"));
23
23
  exports.VERSION = package_json_1.default.version;
24
24
  __exportStar(require("./designs"), exports);
@@ -38,6 +38,8 @@ var plugins_1 = require("./editor/plugins");
38
38
  Object.defineProperty(exports, "EditBar", { enumerable: true, get: function () { return plugins_1.EditBar; } });
39
39
  Object.defineProperty(exports, "Plugin", { enumerable: true, get: function () { return plugins_1.Plugin; } });
40
40
  Object.defineProperty(exports, "ResizeElement", { enumerable: true, get: function () { return plugins_1.ResizeElement; } });
41
+ var exporter_1 = require("./exporter");
42
+ Object.defineProperty(exports, "exportToSVG", { enumerable: true, get: function () { return exporter_1.exportToSVG; } });
41
43
  var jsx_1 = require("./jsx");
42
44
  Object.defineProperty(exports, "Defs", { enumerable: true, get: function () { return jsx_1.Defs; } });
43
45
  Object.defineProperty(exports, "Ellipse", { enumerable: true, get: function () { return jsx_1.Ellipse; } });
@@ -61,8 +63,8 @@ var renderer_1 = require("./renderer");
61
63
  Object.defineProperty(exports, "getFont", { enumerable: true, get: function () { return renderer_1.getFont; } });
62
64
  Object.defineProperty(exports, "getFonts", { enumerable: true, get: function () { return renderer_1.getFonts; } });
63
65
  Object.defineProperty(exports, "getPalette", { enumerable: true, get: function () { return renderer_1.getPalette; } });
64
- Object.defineProperty(exports, "getPalettes", { enumerable: true, get: function () { return renderer_1.getPalettes; } });
65
66
  Object.defineProperty(exports, "getPaletteColor", { enumerable: true, get: function () { return renderer_1.getPaletteColor; } });
67
+ Object.defineProperty(exports, "getPalettes", { enumerable: true, get: function () { return renderer_1.getPalettes; } });
66
68
  Object.defineProperty(exports, "registerFont", { enumerable: true, get: function () { return renderer_1.registerFont; } });
67
69
  Object.defineProperty(exports, "registerPalette", { enumerable: true, get: function () { return renderer_1.registerPalette; } });
68
70
  Object.defineProperty(exports, "registerPattern", { enumerable: true, get: function () { return renderer_1.registerPattern; } });
@@ -84,3 +86,4 @@ Object.defineProperty(exports, "getThemes", { enumerable: true, get: function ()
84
86
  Object.defineProperty(exports, "registerTheme", { enumerable: true, get: function () { return themes_1.registerTheme; } });
85
87
  var utils_2 = require("./utils");
86
88
  Object.defineProperty(exports, "parseSVG", { enumerable: true, get: function () { return utils_2.parseSVG; } });
89
+ Object.defineProperty(exports, "setFontExtendFactor", { enumerable: true, get: function () { return utils_2.setFontExtendFactor; } });
@@ -4,6 +4,7 @@ exports.getFontURLs = getFontURLs;
4
4
  exports.getWoff2BaseURL = getWoff2BaseURL;
5
5
  exports.loadFont = loadFont;
6
6
  exports.loadFonts = loadFonts;
7
+ const load_tracker_1 = require("../../resource/load-tracker");
7
8
  const utils_1 = require("../../utils");
8
9
  const registry_1 = require("./registry");
9
10
  function getFontURLs(font) {
@@ -35,6 +36,52 @@ function getWoff2BaseURL(font, fontWeightName) {
35
36
  return (0, utils_1.join)(config.baseUrl, path.replace(/\/result.css$/, ''));
36
37
  }
37
38
  const FONT_LOAD_MAP = new WeakMap();
39
+ const FONT_PROMISE_MAP = new WeakMap();
40
+ function trackFontPromise(target, id, promise) {
41
+ let map = FONT_PROMISE_MAP.get(target);
42
+ if (!map) {
43
+ map = new Map();
44
+ FONT_PROMISE_MAP.set(target, map);
45
+ }
46
+ map.set(id, promise);
47
+ promise.finally(() => {
48
+ const map = FONT_PROMISE_MAP.get(target);
49
+ if (!map)
50
+ return;
51
+ if (map.get(id) === promise)
52
+ map.delete(id);
53
+ if (map.size === 0)
54
+ FONT_PROMISE_MAP.delete(target);
55
+ });
56
+ return promise;
57
+ }
58
+ function isLinkLoaded(link) {
59
+ if (link.dataset.infographicFontLoaded === 'true')
60
+ return true;
61
+ try {
62
+ return !!link.sheet;
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ }
68
+ function getFontLoadPromise(target, id, link) {
69
+ const existing = FONT_PROMISE_MAP.get(target)?.get(id);
70
+ if (existing)
71
+ return existing;
72
+ if (!link || isLinkLoaded(link)) {
73
+ return trackFontPromise(target, id, Promise.resolve());
74
+ }
75
+ const promise = new Promise((resolve) => {
76
+ const done = () => {
77
+ link.dataset.infographicFontLoaded = 'true';
78
+ resolve();
79
+ };
80
+ link.addEventListener('load', done, { once: true });
81
+ link.addEventListener('error', done, { once: true });
82
+ });
83
+ return trackFontPromise(target, id, promise);
84
+ }
38
85
  function loadFont(svg, font) {
39
86
  const doc = svg.ownerDocument;
40
87
  const target = doc?.head || document.head;
@@ -43,18 +90,26 @@ function loadFont(svg, font) {
43
90
  if (!FONT_LOAD_MAP.has(target))
44
91
  FONT_LOAD_MAP.set(target, new Map());
45
92
  const map = FONT_LOAD_MAP.get(target);
46
- const links = [];
47
93
  const urls = getFontURLs(font);
94
+ if (!urls.length)
95
+ return;
96
+ const links = [];
48
97
  urls.forEach((url) => {
49
98
  const id = `${font}-${url}`;
50
- if (map.has(id))
99
+ const promiseKey = `font:${id}`;
100
+ if ((0, load_tracker_1.getSvgLoadPromise)(svg, promiseKey))
51
101
  return;
52
- const link = doc.createElement('link');
53
- link.id = id;
54
- link.rel = 'stylesheet';
55
- link.href = url;
56
- links.push(link);
57
- map.set(id, link);
102
+ let link = map.get(id);
103
+ if (!link) {
104
+ link = doc.createElement('link');
105
+ link.id = id;
106
+ link.rel = 'stylesheet';
107
+ link.href = url;
108
+ links.push(link);
109
+ map.set(id, link);
110
+ }
111
+ const promise = getFontLoadPromise(target, id, link);
112
+ (0, load_tracker_1.trackSvgLoadPromise)(svg, promiseKey, promise);
58
113
  });
59
114
  if (!links.length)
60
115
  return;
@@ -26,7 +26,7 @@ function applyGradientStyle(node, svg, config, attr) {
26
26
  });
27
27
  }
28
28
  });
29
- const id = getGradientId(config);
29
+ const id = getGradientId(actualConfig);
30
30
  if (type === 'linear-gradient') {
31
31
  const { angle = 0 } = actualConfig;
32
32
  const [[x1, y1], [x2, y2]] = angleToUnitVector(angle);
@@ -1,4 +1,4 @@
1
- export { loadResource } from './loader';
1
+ export { getSvgLoadPromises, loadResource, waitForSvgLoads } from './loader';
2
2
  export * from './loaders';
3
3
  export { registerResourceLoader } from './registry';
4
4
  export type * from './types';
@@ -14,9 +14,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.parseResourceConfig = exports.getResourceId = exports.getResourceHref = exports.registerResourceLoader = exports.loadResource = void 0;
17
+ exports.parseResourceConfig = exports.getResourceId = exports.getResourceHref = exports.registerResourceLoader = exports.waitForSvgLoads = exports.loadResource = exports.getSvgLoadPromises = void 0;
18
18
  var loader_1 = require("./loader");
19
+ Object.defineProperty(exports, "getSvgLoadPromises", { enumerable: true, get: function () { return loader_1.getSvgLoadPromises; } });
19
20
  Object.defineProperty(exports, "loadResource", { enumerable: true, get: function () { return loader_1.loadResource; } });
21
+ Object.defineProperty(exports, "waitForSvgLoads", { enumerable: true, get: function () { return loader_1.waitForSvgLoads; } });
20
22
  __exportStar(require("./loaders"), exports);
21
23
  var registry_1 = require("./registry");
22
24
  Object.defineProperty(exports, "registerResourceLoader", { enumerable: true, get: function () { return registry_1.registerResourceLoader; } });
@@ -0,0 +1,6 @@
1
+ type SvgLoadPromise = Promise<unknown>;
2
+ export declare function getSvgLoadPromises(svg: SVGSVGElement): SvgLoadPromise[];
3
+ export declare function getSvgLoadPromise<T = unknown>(svg: SVGSVGElement, key: string): Promise<T> | undefined;
4
+ export declare function trackSvgLoadPromise<T>(svg: SVGSVGElement, key: string, promise: Promise<T>): Promise<T>;
5
+ export declare function waitForSvgLoads(svg: SVGSVGElement): Promise<void>;
6
+ export {};
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSvgLoadPromises = getSvgLoadPromises;
4
+ exports.getSvgLoadPromise = getSvgLoadPromise;
5
+ exports.trackSvgLoadPromise = trackSvgLoadPromise;
6
+ exports.waitForSvgLoads = waitForSvgLoads;
7
+ const SVG_LOAD_PROMISE_MAP = new WeakMap();
8
+ function getSvgLoadPromises(svg) {
9
+ const map = SVG_LOAD_PROMISE_MAP.get(svg);
10
+ return map ? Array.from(map.values()) : [];
11
+ }
12
+ function getSvgLoadPromise(svg, key) {
13
+ return SVG_LOAD_PROMISE_MAP.get(svg)?.get(key);
14
+ }
15
+ function trackSvgLoadPromise(svg, key, promise) {
16
+ let map = SVG_LOAD_PROMISE_MAP.get(svg);
17
+ if (!map) {
18
+ map = new Map();
19
+ SVG_LOAD_PROMISE_MAP.set(svg, map);
20
+ }
21
+ map.set(key, promise);
22
+ promise.finally(() => {
23
+ const map = SVG_LOAD_PROMISE_MAP.get(svg);
24
+ if (!map)
25
+ return;
26
+ if (map.get(key) === promise)
27
+ map.delete(key);
28
+ if (map.size === 0)
29
+ SVG_LOAD_PROMISE_MAP.delete(svg);
30
+ });
31
+ return promise;
32
+ }
33
+ async function waitForSvgLoads(svg) {
34
+ await Promise.resolve();
35
+ while (true) {
36
+ const promises = getSvgLoadPromises(svg);
37
+ if (!promises.length)
38
+ break;
39
+ await Promise.allSettled(promises);
40
+ await Promise.resolve();
41
+ }
42
+ }
@@ -5,3 +5,4 @@ import type { ResourceConfig, ResourceScene } from './types';
5
5
  * @returns resource ref id
6
6
  */
7
7
  export declare function loadResource(svg: SVGSVGElement | null, scene: ResourceScene, config: string | ResourceConfig, datum?: ItemDatum): Promise<string | null>;
8
+ export { getSvgLoadPromises, waitForSvgLoads } from './load-tracker';
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.waitForSvgLoads = exports.getSvgLoadPromises = void 0;
3
4
  exports.loadResource = loadResource;
4
5
  const utils_1 = require("../utils");
6
+ const load_tracker_1 = require("./load-tracker");
5
7
  const loaders_1 = require("./loaders");
6
8
  const registry_1 = require("./registry");
7
9
  const utils_2 = require("./utils");
@@ -60,22 +62,36 @@ async function loadResource(svg, scene, config, datum) {
60
62
  if (!cfg)
61
63
  return null;
62
64
  const id = (0, utils_2.getResourceId)(cfg);
63
- const resource = RESOURCE_MAP.has(id)
64
- ? RESOURCE_MAP.get(id) || null
65
- : await getResource(scene, cfg, datum);
66
- if (!resource)
67
- return null;
68
- if (!RESOURCE_LOAD_MAP.has(svg))
69
- RESOURCE_LOAD_MAP.set(svg, new Map());
70
- const map = RESOURCE_LOAD_MAP.get(svg);
71
- if (map.has(id))
65
+ const promiseKey = `resource:${id}`;
66
+ const loadedMap = RESOURCE_LOAD_MAP.get(svg);
67
+ if (loadedMap?.has(id))
68
+ return id;
69
+ const existingPromise = (0, load_tracker_1.getSvgLoadPromise)(svg, promiseKey);
70
+ if (existingPromise)
71
+ return await existingPromise;
72
+ const loadPromise = (async () => {
73
+ const resource = RESOURCE_MAP.has(id)
74
+ ? RESOURCE_MAP.get(id) || null
75
+ : await getResource(scene, cfg, datum);
76
+ if (!resource)
77
+ return null;
78
+ if (!RESOURCE_LOAD_MAP.has(svg))
79
+ RESOURCE_LOAD_MAP.set(svg, new Map());
80
+ const map = RESOURCE_LOAD_MAP.get(svg);
81
+ if (map.has(id))
82
+ return id;
83
+ const defs = (0, utils_1.getOrCreateDefs)(svg);
84
+ resource.id = id;
85
+ defs.appendChild(resource);
86
+ map.set(id, resource);
72
87
  return id;
73
- const defs = (0, utils_1.getOrCreateDefs)(svg);
74
- resource.id = id;
75
- defs.appendChild(resource);
76
- map.set(id, resource);
77
- return id;
88
+ })();
89
+ (0, load_tracker_1.trackSvgLoadPromise)(svg, promiseKey, loadPromise);
90
+ return await loadPromise;
78
91
  }
92
+ var load_tracker_2 = require("./load-tracker");
93
+ Object.defineProperty(exports, "getSvgLoadPromises", { enumerable: true, get: function () { return load_tracker_2.getSvgLoadPromises; } });
94
+ Object.defineProperty(exports, "waitForSvgLoads", { enumerable: true, get: function () { return load_tracker_2.waitForSvgLoads; } });
79
95
  function getFallbackQuery(cfg, scene, datum) {
80
96
  const defaultQuery = scene === 'illus' ? 'illustration' : 'icon';
81
97
  const datumQuery = normalizeQuery(datum?.label) || normalizeQuery(datum?.desc);
@@ -11,6 +11,7 @@ const exporter_1 = require("../exporter");
11
11
  const jsx_1 = require("../jsx");
12
12
  const options_1 = require("../options");
13
13
  const renderer_1 = require("../renderer");
14
+ const resource_1 = require("../resource");
14
15
  const syntax_1 = require("../syntax");
15
16
  const utils_1 = require("../utils");
16
17
  const options_2 = require("./options");
@@ -77,6 +78,18 @@ class Infographic {
77
78
  }
78
79
  this.rendered = true;
79
80
  this.emitter.emit('rendered', { node: this.node, options: this.options });
81
+ const currentNode = this.node;
82
+ if (currentNode) {
83
+ void (0, resource_1.waitForSvgLoads)(currentNode).then(() => {
84
+ if (this.node !== currentNode)
85
+ return;
86
+ this.emitter.emit('loaded', {
87
+ node: currentNode,
88
+ options: this.options,
89
+ });
90
+ });
91
+ }
92
+ return true;
80
93
  }
81
94
  /**
82
95
  * Compose the SVG template
@@ -0,0 +1 @@
1
+ export declare function isBrowser(): boolean;