@pixldocs/canvas-renderer 0.3.23 → 0.3.25

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.cjs CHANGED
@@ -11057,6 +11057,14 @@ class PixldocsRenderer {
11057
11057
  });
11058
11058
  }
11059
11059
  // ─── Internal: capture SVG from a rendered Fabric canvas ───
11060
+ //
11061
+ // APPROACH: Use the SAME PreviewCanvas that renders perfect PNGs, then call
11062
+ // Fabric's toSVG() on that canvas. This guarantees 100% layout parity —
11063
+ // the SVG is a vector snapshot of exactly what's on screen.
11064
+ //
11065
+ // The trick: before calling toSVG(), we temporarily neutralize the viewport
11066
+ // transform and retina scaling so Fabric emits coordinates in logical
11067
+ // document space (e.g. 612x792) instead of inflated pixel space.
11060
11068
  captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
11061
11069
  return new Promise(async (resolve, reject) => {
11062
11070
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
@@ -11092,17 +11100,25 @@ class PixldocsRenderer {
11092
11100
  }
11093
11101
  const prevVPT = fabricInstance.viewportTransform ? [...fabricInstance.viewportTransform] : void 0;
11094
11102
  const prevSvgVPT = fabricInstance.svgViewportTransformation;
11095
- const prevCanvasWidth = fabricInstance.width;
11096
- const prevCanvasHeight = fabricInstance.height;
11103
+ const prevRetina = fabricInstance.enableRetinaScaling;
11104
+ const prevWidth = fabricInstance.width;
11105
+ const prevHeight = fabricInstance.height;
11106
+ fabricInstance.viewportTransform = [1, 0, 0, 1, 0, 0];
11107
+ fabricInstance.svgViewportTransformation = false;
11108
+ fabricInstance.enableRetinaScaling = false;
11097
11109
  fabricInstance.setDimensions(
11098
11110
  { width: canvasWidth, height: canvasHeight },
11099
11111
  { cssOnly: false, backstoreOnly: false }
11100
11112
  );
11101
- fabricInstance.viewportTransform = [1, 0, 0, 1, 0, 0];
11102
- fabricInstance.svgViewportTransformation = false;
11103
- const svgString = fabricInstance.toSVG();
11113
+ const rawSvgString = fabricInstance.toSVG();
11114
+ const svgString = this.normalizeSvgDimensions(
11115
+ rawSvgString,
11116
+ canvasWidth,
11117
+ canvasHeight
11118
+ );
11119
+ fabricInstance.enableRetinaScaling = prevRetina;
11104
11120
  fabricInstance.setDimensions(
11105
- { width: prevCanvasWidth, height: prevCanvasHeight },
11121
+ { width: prevWidth, height: prevHeight },
11106
11122
  { cssOnly: false, backstoreOnly: false }
11107
11123
  );
11108
11124
  if (prevVPT) fabricInstance.viewportTransform = prevVPT;
@@ -11130,7 +11146,7 @@ class PixldocsRenderer {
11130
11146
  config,
11131
11147
  pageIndex,
11132
11148
  zoom: 1,
11133
- // 1:1 — no scaling for SVG capture
11149
+ // 1:1 — no UI scaling for SVG capture
11134
11150
  absoluteZoom: true,
11135
11151
  onReady
11136
11152
  })
@@ -11145,22 +11161,43 @@ class PixldocsRenderer {
11145
11161
  * the SVG coordinate system matches the intended page size exactly.
11146
11162
  */
11147
11163
  normalizeSvgDimensions(svg, targetWidth, targetHeight) {
11148
- const widthMatch = svg.match(/<svg[^>]*\bwidth="([^"]+)"/);
11149
- const heightMatch = svg.match(/<svg[^>]*\bheight="([^"]+)"/);
11164
+ const widthMatch = svg.match(/<svg[^>]*\bwidth="([^"]+)"/i);
11165
+ const heightMatch = svg.match(/<svg[^>]*\bheight="([^"]+)"/i);
11150
11166
  const svgWidth = widthMatch ? parseFloat(widthMatch[1]) : targetWidth;
11151
11167
  const svgHeight = heightMatch ? parseFloat(heightMatch[1]) : targetHeight;
11152
- if (Math.abs(svgWidth - targetWidth) < 1 && Math.abs(svgHeight - targetHeight) < 1) {
11153
- return svg;
11154
- }
11155
11168
  console.log(
11156
- `[canvas-renderer][svg-normalize] SVG dimensions ${svgWidth}x${svgHeight} → ${targetWidth}x${targetHeight}`
11169
+ `[canvas-renderer][svg-normalize] root ${svgWidth}x${svgHeight} → page ${targetWidth}x${targetHeight}`
11157
11170
  );
11158
- let normalized = svg.replace(/(<svg[^>]*\b)width="[^"]*"/, `$1width="${targetWidth}"`).replace(/(<svg[^>]*\b)height="[^"]*"/, `$1height="${targetHeight}"`);
11159
- const viewBox = `0 0 ${svgWidth} ${svgHeight}`;
11160
- if (/viewBox="[^"]*"/.test(normalized)) {
11161
- normalized = normalized.replace(/viewBox="[^"]*"/, `viewBox="${viewBox}"`);
11171
+ let normalized = svg;
11172
+ if (/\bwidth="[^"]*"/i.test(normalized)) {
11173
+ normalized = normalized.replace(/(<svg[^>]*\b)width="[^"]*"/i, `$1width="${targetWidth}"`);
11174
+ } else {
11175
+ normalized = normalized.replace(/<svg\b/i, `<svg width="${targetWidth}"`);
11176
+ }
11177
+ if (/\bheight="[^"]*"/i.test(normalized)) {
11178
+ normalized = normalized.replace(/(<svg[^>]*\b)height="[^"]*"/i, `$1height="${targetHeight}"`);
11162
11179
  } else {
11163
- normalized = normalized.replace(/<svg\b/, `<svg viewBox="${viewBox}"`);
11180
+ normalized = normalized.replace(/<svg\b/i, `<svg height="${targetHeight}"`);
11181
+ }
11182
+ const viewBox = `0 0 ${targetWidth} ${targetHeight}`;
11183
+ if (/\bviewBox="[^"]*"/i.test(normalized)) {
11184
+ normalized = normalized.replace(/viewBox="[^"]*"/i, `viewBox="${viewBox}"`);
11185
+ } else {
11186
+ normalized = normalized.replace(/<svg\b/i, `<svg viewBox="${viewBox}"`);
11187
+ }
11188
+ if (/\bx="[^"]*"/i.test(normalized)) {
11189
+ normalized = normalized.replace(/(<svg[^>]*\b)x="[^"]*"/i, '$1x="0"');
11190
+ } else {
11191
+ normalized = normalized.replace(/<svg\b/i, '<svg x="0"');
11192
+ }
11193
+ if (/\by="[^"]*"/i.test(normalized)) {
11194
+ normalized = normalized.replace(/(<svg[^>]*\b)y="[^"]*"/i, '$1y="0"');
11195
+ } else {
11196
+ normalized = normalized.replace(/<svg\b/i, '<svg y="0"');
11197
+ }
11198
+ normalized = normalized.replace(/\bpreserveAspectRatio="[^"]*"/i, 'preserveAspectRatio="none"');
11199
+ if (!/\bpreserveAspectRatio="[^"]*"/i.test(normalized)) {
11200
+ normalized = normalized.replace(/<svg\b/i, '<svg preserveAspectRatio="none"');
11164
11201
  }
11165
11202
  return normalized;
11166
11203
  }