@antv/infographic 0.2.18 → 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.
@@ -44,38 +44,69 @@ function parseAbsoluteLength(value) {
44
44
  return Number.NaN;
45
45
  return Number.parseFloat(trimmed);
46
46
  }
47
- function measureSpanContentHeight(span) {
47
+ function parseCoordinate(value) {
48
+ const parsed = parseAbsoluteLength(value);
49
+ return Number.isNaN(parsed) ? 0 : parsed;
50
+ }
51
+ function measureSpanContentDimensions(span, measureWidth) {
48
52
  const prevHeight = span.style.height;
53
+ const prevWidth = span.style.width;
49
54
  const prevOverflow = span.style.overflow;
50
55
  try {
51
56
  span.style.height = 'max-content';
52
57
  span.style.overflow = 'hidden';
53
58
  void span.offsetHeight; // force reflow
54
- return span.scrollHeight;
59
+ const scrollHeight = span.scrollHeight;
60
+ const rectHeight = span.getBoundingClientRect().height;
61
+ let width = span.scrollWidth;
62
+ if (measureWidth) {
63
+ span.style.width = 'max-content';
64
+ void span.offsetWidth; // force reflow
65
+ width = span.scrollWidth;
66
+ }
67
+ return {
68
+ height: Math.max(scrollHeight, rectHeight),
69
+ width,
70
+ };
55
71
  }
56
72
  finally {
57
73
  span.style.height = prevHeight;
58
- span.style.overflow = prevOverflow;
59
- }
60
- }
61
- function measureSpanContentWidth(span) {
62
- const prevWidth = span.style.width;
63
- const prevOverflow = span.style.overflow;
64
- try {
65
- span.style.width = 'max-content';
66
- span.style.overflow = 'hidden';
67
- void span.offsetWidth; // force reflow
68
- return span.scrollWidth;
69
- }
70
- finally {
71
74
  span.style.width = prevWidth;
72
75
  span.style.overflow = prevOverflow;
73
76
  }
74
77
  }
75
- // Returns [left, top, right, bottom] in SVG coordinates for a foreignObject,
78
+ function shouldKeepForeignObjectWidth(style) {
79
+ const whiteSpace = style.whiteSpace;
80
+ const flexWrap = style.flexWrap;
81
+ const wordBreak = style.wordBreak;
82
+ const overflowWrap = style.overflowWrap;
83
+ return (flexWrap === 'wrap' ||
84
+ flexWrap === 'wrap-reverse' ||
85
+ whiteSpace === 'pre-wrap' ||
86
+ whiteSpace === 'pre-line' ||
87
+ whiteSpace === 'normal' ||
88
+ overflowWrap === 'break-word' ||
89
+ wordBreak === 'break-word' ||
90
+ wordBreak === 'break-all');
91
+ }
92
+ function createCoordConverter(svg, element) {
93
+ if (typeof element.getScreenCTM !== 'function')
94
+ return null;
95
+ const screenCTM = element.getScreenCTM();
96
+ if (!screenCTM)
97
+ return null;
98
+ const inverseCTM = screenCTM.inverse();
99
+ return (clientX, clientY) => {
100
+ const pt = svg.createSVGPoint();
101
+ pt.x = clientX;
102
+ pt.y = clientY;
103
+ return pt.matrixTransform(inverseCTM);
104
+ };
105
+ }
106
+ // Returns [left, top, right, bottom] in target coordinates for a foreignObject,
76
107
  // accounting for flex alignment: bottom/center-aligned content can overflow,
77
108
  // and horizontally aligned content can overflow as well.
78
- function getFOContentBoundsInSVG(fo, content, toSVGCoord) {
109
+ function getFOContentBoundsInSVG(fo, toSVGCoord, { contentHeight, contentWidth, keepForeignObjectWidth, }) {
79
110
  const foRect = fo.getBoundingClientRect();
80
111
  const foTopLeft = toSVGCoord(foRect.left, foRect.top);
81
112
  const foBottomRight = toSVGCoord(foRect.right, foRect.bottom);
@@ -87,16 +118,15 @@ function getFOContentBoundsInSVG(fo, content, toSVGCoord) {
87
118
  const foHeightSVG = foBottomSVG - foTopSVG;
88
119
  const svgUnitsPerClientPxY = foRect.height > 0 ? foHeightSVG / foRect.height : 1;
89
120
  const svgUnitsPerClientPxX = foRect.width > 0 ? foWidthSVG / foRect.width : 1;
90
- // Measure actual content dimensions
91
- const realScrollHeight = measureSpanContentHeight(content);
92
- const contentHeightSVG = realScrollHeight > 0
93
- ? realScrollHeight * svgUnitsPerClientPxY
121
+ const contentHeightSVG = contentHeight > 0
122
+ ? contentHeight * svgUnitsPerClientPxY
94
123
  : foHeightSVG;
95
- const realScrollWidth = measureSpanContentWidth(content);
96
- const contentWidthSVG = realScrollWidth > 0 ? realScrollWidth * svgUnitsPerClientPxX : foWidthSVG;
97
- const computedStyle = window.getComputedStyle(content);
124
+ const computedStyle = window.getComputedStyle(fo.firstElementChild);
98
125
  const alignItems = computedStyle.alignItems;
99
126
  const justifyContent = computedStyle.justifyContent;
127
+ const contentWidthSVG = keepForeignObjectWidth
128
+ ? foWidthSVG
129
+ : Math.max(foWidthSVG, contentWidth * svgUnitsPerClientPxX);
100
130
  // Calculate vertical bounds
101
131
  let top, bottom;
102
132
  if (alignItems === 'flex-end' || alignItems === 'end') {
@@ -131,38 +161,75 @@ function getFOContentBoundsInSVG(fo, content, toSVGCoord) {
131
161
  }
132
162
  return [left, top, right, bottom];
133
163
  }
134
- /**
135
- * Computes a viewBox that fully covers all foreignObject text content,
136
- * accounting for overflow caused by flex alignment (bottom/center align
137
- * can push content outside the foreignObject bounds).
138
- */
139
- function computeFullViewBox(svg) {
164
+ function collectForeignObjectExportAdjustments(svg) {
165
+ const toSVGCoord = createCoordConverter(svg, svg);
166
+ if (!toSVGCoord)
167
+ return [];
168
+ return Array.from(svg.querySelectorAll('foreignObject')).map((fo) => {
169
+ const content = fo.firstElementChild;
170
+ if (!content)
171
+ return null;
172
+ const computedStyle = window.getComputedStyle(content);
173
+ const keepForeignObjectWidth = shouldKeepForeignObjectWidth(computedStyle);
174
+ const measuredContent = measureSpanContentDimensions(content, !keepForeignObjectWidth);
175
+ const parent = fo.parentElement instanceof SVGGraphicsElement ? fo.parentElement : svg;
176
+ const toParentCoord = createCoordConverter(svg, parent);
177
+ const toLocalCoord = createCoordConverter(svg, fo);
178
+ if (!toParentCoord)
179
+ return null;
180
+ const parentBounds = getFOContentBoundsInSVG(fo, toParentCoord, {
181
+ contentHeight: measuredContent.height,
182
+ contentWidth: measuredContent.width,
183
+ keepForeignObjectWidth,
184
+ });
185
+ const originalX = parseCoordinate(fo.getAttribute('x'));
186
+ const originalY = parseCoordinate(fo.getAttribute('y'));
187
+ const localBounds = toLocalCoord
188
+ ? getFOContentBoundsInSVG(fo, toLocalCoord, {
189
+ contentHeight: measuredContent.height,
190
+ contentWidth: measuredContent.width,
191
+ keepForeignObjectWidth,
192
+ })
193
+ : null;
194
+ const hasTransform = fo.hasAttribute('transform');
195
+ if (hasTransform && !localBounds)
196
+ return null;
197
+ const exportBounds = localBounds
198
+ ? {
199
+ x: originalX + localBounds[0],
200
+ y: originalY + localBounds[1],
201
+ width: localBounds[2] - localBounds[0],
202
+ height: localBounds[3] - localBounds[1],
203
+ }
204
+ : {
205
+ x: parentBounds[0],
206
+ y: parentBounds[1],
207
+ width: parentBounds[2] - parentBounds[0],
208
+ height: parentBounds[3] - parentBounds[1],
209
+ };
210
+ return {
211
+ rootBounds: getFOContentBoundsInSVG(fo, toSVGCoord, {
212
+ contentHeight: measuredContent.height,
213
+ contentWidth: measuredContent.width,
214
+ keepForeignObjectWidth,
215
+ }),
216
+ exportBounds,
217
+ };
218
+ });
219
+ }
220
+ function computeFullViewBox(svg, adjustments) {
140
221
  const viewBox = getExportViewBox(svg);
141
222
  if (!viewBox)
142
223
  return null;
143
- if (typeof svg.getScreenCTM !== 'function')
144
- return null;
145
- const screenCTM = svg.getScreenCTM();
146
- if (!screenCTM)
147
- return null;
148
- const inverseCTM = screenCTM.inverse();
149
- const toSVGCoord = (clientX, clientY) => {
150
- const pt = svg.createSVGPoint();
151
- pt.x = clientX;
152
- pt.y = clientY;
153
- return pt.matrixTransform(inverseCTM);
154
- };
155
224
  let minX = viewBox.x;
156
225
  let minY = viewBox.y;
157
226
  let maxX = viewBox.x + viewBox.width;
158
227
  let maxY = viewBox.y + viewBox.height;
159
- svg
160
- .querySelectorAll('foreignObject')
161
- .forEach((fo) => {
162
- const content = fo.firstElementChild;
163
- if (!content)
228
+ adjustments.forEach((adjustment) => {
229
+ if (!adjustment)
164
230
  return;
165
- const [left, top, right, bottom] = getFOContentBoundsInSVG(fo, content, toSVGCoord);
231
+ const { rootBounds } = adjustment;
232
+ const [left, top, right, bottom] = rootBounds;
166
233
  minX = Math.min(minX, left);
167
234
  minY = Math.min(minY, top);
168
235
  maxX = Math.max(maxX, right);
@@ -179,12 +246,25 @@ function computeFullViewBox(svg) {
179
246
  return null;
180
247
  return `${newX} ${newY} ${newWidth} ${newHeight}`;
181
248
  }
249
+ function applyForeignObjectExportAdjustments(svg, adjustments) {
250
+ const clonedForeignObjects = Array.from(svg.querySelectorAll('foreignObject'));
251
+ adjustments.forEach((adjustment, index) => {
252
+ if (!adjustment)
253
+ return;
254
+ const clonedForeignObject = clonedForeignObjects[index];
255
+ if (!clonedForeignObject)
256
+ return;
257
+ setAttributes(clonedForeignObject, adjustment.exportBounds);
258
+ });
259
+ }
182
260
  export function exportToSVG(svg_1) {
183
261
  return __awaiter(this, arguments, void 0, function* (svg, options = {}) {
184
262
  const { removeBackground = false, embedResources = true, removeIds = false, } = options;
185
263
  const clonedSVG = svg.cloneNode(true);
186
264
  if (typeof document !== 'undefined') {
187
- const fullViewBox = computeFullViewBox(svg);
265
+ const adjustments = collectForeignObjectExportAdjustments(svg);
266
+ applyForeignObjectExportAdjustments(clonedSVG, adjustments);
267
+ const fullViewBox = computeFullViewBox(svg, adjustments);
188
268
  if (fullViewBox) {
189
269
  clonedSVG.setAttribute('viewBox', fullViewBox);
190
270
  }
package/esm/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.2.18";
1
+ export declare const VERSION = "0.2.19";
package/esm/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.2.18';
1
+ export const VERSION = '0.2.19';
@@ -48,38 +48,69 @@ function parseAbsoluteLength(value) {
48
48
  return Number.NaN;
49
49
  return Number.parseFloat(trimmed);
50
50
  }
51
- function measureSpanContentHeight(span) {
51
+ function parseCoordinate(value) {
52
+ const parsed = parseAbsoluteLength(value);
53
+ return Number.isNaN(parsed) ? 0 : parsed;
54
+ }
55
+ function measureSpanContentDimensions(span, measureWidth) {
52
56
  const prevHeight = span.style.height;
57
+ const prevWidth = span.style.width;
53
58
  const prevOverflow = span.style.overflow;
54
59
  try {
55
60
  span.style.height = 'max-content';
56
61
  span.style.overflow = 'hidden';
57
62
  void span.offsetHeight; // force reflow
58
- return span.scrollHeight;
63
+ const scrollHeight = span.scrollHeight;
64
+ const rectHeight = span.getBoundingClientRect().height;
65
+ let width = span.scrollWidth;
66
+ if (measureWidth) {
67
+ span.style.width = 'max-content';
68
+ void span.offsetWidth; // force reflow
69
+ width = span.scrollWidth;
70
+ }
71
+ return {
72
+ height: Math.max(scrollHeight, rectHeight),
73
+ width,
74
+ };
59
75
  }
60
76
  finally {
61
77
  span.style.height = prevHeight;
62
- span.style.overflow = prevOverflow;
63
- }
64
- }
65
- function measureSpanContentWidth(span) {
66
- const prevWidth = span.style.width;
67
- const prevOverflow = span.style.overflow;
68
- try {
69
- span.style.width = 'max-content';
70
- span.style.overflow = 'hidden';
71
- void span.offsetWidth; // force reflow
72
- return span.scrollWidth;
73
- }
74
- finally {
75
78
  span.style.width = prevWidth;
76
79
  span.style.overflow = prevOverflow;
77
80
  }
78
81
  }
79
- // Returns [left, top, right, bottom] in SVG coordinates for a foreignObject,
82
+ function shouldKeepForeignObjectWidth(style) {
83
+ const whiteSpace = style.whiteSpace;
84
+ const flexWrap = style.flexWrap;
85
+ const wordBreak = style.wordBreak;
86
+ const overflowWrap = style.overflowWrap;
87
+ return (flexWrap === 'wrap' ||
88
+ flexWrap === 'wrap-reverse' ||
89
+ whiteSpace === 'pre-wrap' ||
90
+ whiteSpace === 'pre-line' ||
91
+ whiteSpace === 'normal' ||
92
+ overflowWrap === 'break-word' ||
93
+ wordBreak === 'break-word' ||
94
+ wordBreak === 'break-all');
95
+ }
96
+ function createCoordConverter(svg, element) {
97
+ if (typeof element.getScreenCTM !== 'function')
98
+ return null;
99
+ const screenCTM = element.getScreenCTM();
100
+ if (!screenCTM)
101
+ return null;
102
+ const inverseCTM = screenCTM.inverse();
103
+ return (clientX, clientY) => {
104
+ const pt = svg.createSVGPoint();
105
+ pt.x = clientX;
106
+ pt.y = clientY;
107
+ return pt.matrixTransform(inverseCTM);
108
+ };
109
+ }
110
+ // Returns [left, top, right, bottom] in target coordinates for a foreignObject,
80
111
  // accounting for flex alignment: bottom/center-aligned content can overflow,
81
112
  // and horizontally aligned content can overflow as well.
82
- function getFOContentBoundsInSVG(fo, content, toSVGCoord) {
113
+ function getFOContentBoundsInSVG(fo, toSVGCoord, { contentHeight, contentWidth, keepForeignObjectWidth, }) {
83
114
  const foRect = fo.getBoundingClientRect();
84
115
  const foTopLeft = toSVGCoord(foRect.left, foRect.top);
85
116
  const foBottomRight = toSVGCoord(foRect.right, foRect.bottom);
@@ -91,16 +122,15 @@ function getFOContentBoundsInSVG(fo, content, toSVGCoord) {
91
122
  const foHeightSVG = foBottomSVG - foTopSVG;
92
123
  const svgUnitsPerClientPxY = foRect.height > 0 ? foHeightSVG / foRect.height : 1;
93
124
  const svgUnitsPerClientPxX = foRect.width > 0 ? foWidthSVG / foRect.width : 1;
94
- // Measure actual content dimensions
95
- const realScrollHeight = measureSpanContentHeight(content);
96
- const contentHeightSVG = realScrollHeight > 0
97
- ? realScrollHeight * svgUnitsPerClientPxY
125
+ const contentHeightSVG = contentHeight > 0
126
+ ? contentHeight * svgUnitsPerClientPxY
98
127
  : foHeightSVG;
99
- const realScrollWidth = measureSpanContentWidth(content);
100
- const contentWidthSVG = realScrollWidth > 0 ? realScrollWidth * svgUnitsPerClientPxX : foWidthSVG;
101
- const computedStyle = window.getComputedStyle(content);
128
+ const computedStyle = window.getComputedStyle(fo.firstElementChild);
102
129
  const alignItems = computedStyle.alignItems;
103
130
  const justifyContent = computedStyle.justifyContent;
131
+ const contentWidthSVG = keepForeignObjectWidth
132
+ ? foWidthSVG
133
+ : Math.max(foWidthSVG, contentWidth * svgUnitsPerClientPxX);
104
134
  // Calculate vertical bounds
105
135
  let top, bottom;
106
136
  if (alignItems === 'flex-end' || alignItems === 'end') {
@@ -135,38 +165,75 @@ function getFOContentBoundsInSVG(fo, content, toSVGCoord) {
135
165
  }
136
166
  return [left, top, right, bottom];
137
167
  }
138
- /**
139
- * Computes a viewBox that fully covers all foreignObject text content,
140
- * accounting for overflow caused by flex alignment (bottom/center align
141
- * can push content outside the foreignObject bounds).
142
- */
143
- function computeFullViewBox(svg) {
168
+ function collectForeignObjectExportAdjustments(svg) {
169
+ const toSVGCoord = createCoordConverter(svg, svg);
170
+ if (!toSVGCoord)
171
+ return [];
172
+ return Array.from(svg.querySelectorAll('foreignObject')).map((fo) => {
173
+ const content = fo.firstElementChild;
174
+ if (!content)
175
+ return null;
176
+ const computedStyle = window.getComputedStyle(content);
177
+ const keepForeignObjectWidth = shouldKeepForeignObjectWidth(computedStyle);
178
+ const measuredContent = measureSpanContentDimensions(content, !keepForeignObjectWidth);
179
+ const parent = fo.parentElement instanceof SVGGraphicsElement ? fo.parentElement : svg;
180
+ const toParentCoord = createCoordConverter(svg, parent);
181
+ const toLocalCoord = createCoordConverter(svg, fo);
182
+ if (!toParentCoord)
183
+ return null;
184
+ const parentBounds = getFOContentBoundsInSVG(fo, toParentCoord, {
185
+ contentHeight: measuredContent.height,
186
+ contentWidth: measuredContent.width,
187
+ keepForeignObjectWidth,
188
+ });
189
+ const originalX = parseCoordinate(fo.getAttribute('x'));
190
+ const originalY = parseCoordinate(fo.getAttribute('y'));
191
+ const localBounds = toLocalCoord
192
+ ? getFOContentBoundsInSVG(fo, toLocalCoord, {
193
+ contentHeight: measuredContent.height,
194
+ contentWidth: measuredContent.width,
195
+ keepForeignObjectWidth,
196
+ })
197
+ : null;
198
+ const hasTransform = fo.hasAttribute('transform');
199
+ if (hasTransform && !localBounds)
200
+ return null;
201
+ const exportBounds = localBounds
202
+ ? {
203
+ x: originalX + localBounds[0],
204
+ y: originalY + localBounds[1],
205
+ width: localBounds[2] - localBounds[0],
206
+ height: localBounds[3] - localBounds[1],
207
+ }
208
+ : {
209
+ x: parentBounds[0],
210
+ y: parentBounds[1],
211
+ width: parentBounds[2] - parentBounds[0],
212
+ height: parentBounds[3] - parentBounds[1],
213
+ };
214
+ return {
215
+ rootBounds: getFOContentBoundsInSVG(fo, toSVGCoord, {
216
+ contentHeight: measuredContent.height,
217
+ contentWidth: measuredContent.width,
218
+ keepForeignObjectWidth,
219
+ }),
220
+ exportBounds,
221
+ };
222
+ });
223
+ }
224
+ function computeFullViewBox(svg, adjustments) {
144
225
  const viewBox = getExportViewBox(svg);
145
226
  if (!viewBox)
146
227
  return null;
147
- if (typeof svg.getScreenCTM !== 'function')
148
- return null;
149
- const screenCTM = svg.getScreenCTM();
150
- if (!screenCTM)
151
- return null;
152
- const inverseCTM = screenCTM.inverse();
153
- const toSVGCoord = (clientX, clientY) => {
154
- const pt = svg.createSVGPoint();
155
- pt.x = clientX;
156
- pt.y = clientY;
157
- return pt.matrixTransform(inverseCTM);
158
- };
159
228
  let minX = viewBox.x;
160
229
  let minY = viewBox.y;
161
230
  let maxX = viewBox.x + viewBox.width;
162
231
  let maxY = viewBox.y + viewBox.height;
163
- svg
164
- .querySelectorAll('foreignObject')
165
- .forEach((fo) => {
166
- const content = fo.firstElementChild;
167
- if (!content)
232
+ adjustments.forEach((adjustment) => {
233
+ if (!adjustment)
168
234
  return;
169
- const [left, top, right, bottom] = getFOContentBoundsInSVG(fo, content, toSVGCoord);
235
+ const { rootBounds } = adjustment;
236
+ const [left, top, right, bottom] = rootBounds;
170
237
  minX = Math.min(minX, left);
171
238
  minY = Math.min(minY, top);
172
239
  maxX = Math.max(maxX, right);
@@ -183,12 +250,25 @@ function computeFullViewBox(svg) {
183
250
  return null;
184
251
  return `${newX} ${newY} ${newWidth} ${newHeight}`;
185
252
  }
253
+ function applyForeignObjectExportAdjustments(svg, adjustments) {
254
+ const clonedForeignObjects = Array.from(svg.querySelectorAll('foreignObject'));
255
+ adjustments.forEach((adjustment, index) => {
256
+ if (!adjustment)
257
+ return;
258
+ const clonedForeignObject = clonedForeignObjects[index];
259
+ if (!clonedForeignObject)
260
+ return;
261
+ (0, utils_1.setAttributes)(clonedForeignObject, adjustment.exportBounds);
262
+ });
263
+ }
186
264
  function exportToSVG(svg_1) {
187
265
  return __awaiter(this, arguments, void 0, function* (svg, options = {}) {
188
266
  const { removeBackground = false, embedResources = true, removeIds = false, } = options;
189
267
  const clonedSVG = svg.cloneNode(true);
190
268
  if (typeof document !== 'undefined') {
191
- const fullViewBox = computeFullViewBox(svg);
269
+ const adjustments = collectForeignObjectExportAdjustments(svg);
270
+ applyForeignObjectExportAdjustments(clonedSVG, adjustments);
271
+ const fullViewBox = computeFullViewBox(svg, adjustments);
192
272
  if (fullViewBox) {
193
273
  clonedSVG.setAttribute('viewBox', fullViewBox);
194
274
  }
package/lib/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.2.18";
1
+ export declare const VERSION = "0.2.19";
package/lib/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
- exports.VERSION = '0.2.18';
4
+ exports.VERSION = '0.2.19';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antv/infographic",
3
- "version": "0.2.18",
3
+ "version": "0.2.19",
4
4
  "description": "An Infographic Generation and Rendering Framework, bring words to life!",
5
5
  "keywords": [
6
6
  "antv",