@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.js CHANGED
@@ -11038,6 +11038,14 @@ class PixldocsRenderer {
11038
11038
  });
11039
11039
  }
11040
11040
  // ─── Internal: capture SVG from a rendered Fabric canvas ───
11041
+ //
11042
+ // APPROACH: Use the SAME PreviewCanvas that renders perfect PNGs, then call
11043
+ // Fabric's toSVG() on that canvas. This guarantees 100% layout parity —
11044
+ // the SVG is a vector snapshot of exactly what's on screen.
11045
+ //
11046
+ // The trick: before calling toSVG(), we temporarily neutralize the viewport
11047
+ // transform and retina scaling so Fabric emits coordinates in logical
11048
+ // document space (e.g. 612x792) instead of inflated pixel space.
11041
11049
  captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
11042
11050
  return new Promise(async (resolve, reject) => {
11043
11051
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
@@ -11073,17 +11081,25 @@ class PixldocsRenderer {
11073
11081
  }
11074
11082
  const prevVPT = fabricInstance.viewportTransform ? [...fabricInstance.viewportTransform] : void 0;
11075
11083
  const prevSvgVPT = fabricInstance.svgViewportTransformation;
11076
- const prevCanvasWidth = fabricInstance.width;
11077
- const prevCanvasHeight = fabricInstance.height;
11084
+ const prevRetina = fabricInstance.enableRetinaScaling;
11085
+ const prevWidth = fabricInstance.width;
11086
+ const prevHeight = fabricInstance.height;
11087
+ fabricInstance.viewportTransform = [1, 0, 0, 1, 0, 0];
11088
+ fabricInstance.svgViewportTransformation = false;
11089
+ fabricInstance.enableRetinaScaling = false;
11078
11090
  fabricInstance.setDimensions(
11079
11091
  { width: canvasWidth, height: canvasHeight },
11080
11092
  { cssOnly: false, backstoreOnly: false }
11081
11093
  );
11082
- fabricInstance.viewportTransform = [1, 0, 0, 1, 0, 0];
11083
- fabricInstance.svgViewportTransformation = false;
11084
- const svgString = fabricInstance.toSVG();
11094
+ const rawSvgString = fabricInstance.toSVG();
11095
+ const svgString = this.normalizeSvgDimensions(
11096
+ rawSvgString,
11097
+ canvasWidth,
11098
+ canvasHeight
11099
+ );
11100
+ fabricInstance.enableRetinaScaling = prevRetina;
11085
11101
  fabricInstance.setDimensions(
11086
- { width: prevCanvasWidth, height: prevCanvasHeight },
11102
+ { width: prevWidth, height: prevHeight },
11087
11103
  { cssOnly: false, backstoreOnly: false }
11088
11104
  );
11089
11105
  if (prevVPT) fabricInstance.viewportTransform = prevVPT;
@@ -11111,7 +11127,7 @@ class PixldocsRenderer {
11111
11127
  config,
11112
11128
  pageIndex,
11113
11129
  zoom: 1,
11114
- // 1:1 — no scaling for SVG capture
11130
+ // 1:1 — no UI scaling for SVG capture
11115
11131
  absoluteZoom: true,
11116
11132
  onReady
11117
11133
  })
@@ -11126,22 +11142,43 @@ class PixldocsRenderer {
11126
11142
  * the SVG coordinate system matches the intended page size exactly.
11127
11143
  */
11128
11144
  normalizeSvgDimensions(svg, targetWidth, targetHeight) {
11129
- const widthMatch = svg.match(/<svg[^>]*\bwidth="([^"]+)"/);
11130
- const heightMatch = svg.match(/<svg[^>]*\bheight="([^"]+)"/);
11145
+ const widthMatch = svg.match(/<svg[^>]*\bwidth="([^"]+)"/i);
11146
+ const heightMatch = svg.match(/<svg[^>]*\bheight="([^"]+)"/i);
11131
11147
  const svgWidth = widthMatch ? parseFloat(widthMatch[1]) : targetWidth;
11132
11148
  const svgHeight = heightMatch ? parseFloat(heightMatch[1]) : targetHeight;
11133
- if (Math.abs(svgWidth - targetWidth) < 1 && Math.abs(svgHeight - targetHeight) < 1) {
11134
- return svg;
11135
- }
11136
11149
  console.log(
11137
- `[canvas-renderer][svg-normalize] SVG dimensions ${svgWidth}x${svgHeight} → ${targetWidth}x${targetHeight}`
11150
+ `[canvas-renderer][svg-normalize] root ${svgWidth}x${svgHeight} → page ${targetWidth}x${targetHeight}`
11138
11151
  );
11139
- let normalized = svg.replace(/(<svg[^>]*\b)width="[^"]*"/, `$1width="${targetWidth}"`).replace(/(<svg[^>]*\b)height="[^"]*"/, `$1height="${targetHeight}"`);
11140
- const viewBox = `0 0 ${svgWidth} ${svgHeight}`;
11141
- if (/viewBox="[^"]*"/.test(normalized)) {
11142
- normalized = normalized.replace(/viewBox="[^"]*"/, `viewBox="${viewBox}"`);
11152
+ let normalized = svg;
11153
+ if (/\bwidth="[^"]*"/i.test(normalized)) {
11154
+ normalized = normalized.replace(/(<svg[^>]*\b)width="[^"]*"/i, `$1width="${targetWidth}"`);
11155
+ } else {
11156
+ normalized = normalized.replace(/<svg\b/i, `<svg width="${targetWidth}"`);
11157
+ }
11158
+ if (/\bheight="[^"]*"/i.test(normalized)) {
11159
+ normalized = normalized.replace(/(<svg[^>]*\b)height="[^"]*"/i, `$1height="${targetHeight}"`);
11143
11160
  } else {
11144
- normalized = normalized.replace(/<svg\b/, `<svg viewBox="${viewBox}"`);
11161
+ normalized = normalized.replace(/<svg\b/i, `<svg height="${targetHeight}"`);
11162
+ }
11163
+ const viewBox = `0 0 ${targetWidth} ${targetHeight}`;
11164
+ if (/\bviewBox="[^"]*"/i.test(normalized)) {
11165
+ normalized = normalized.replace(/viewBox="[^"]*"/i, `viewBox="${viewBox}"`);
11166
+ } else {
11167
+ normalized = normalized.replace(/<svg\b/i, `<svg viewBox="${viewBox}"`);
11168
+ }
11169
+ if (/\bx="[^"]*"/i.test(normalized)) {
11170
+ normalized = normalized.replace(/(<svg[^>]*\b)x="[^"]*"/i, '$1x="0"');
11171
+ } else {
11172
+ normalized = normalized.replace(/<svg\b/i, '<svg x="0"');
11173
+ }
11174
+ if (/\by="[^"]*"/i.test(normalized)) {
11175
+ normalized = normalized.replace(/(<svg[^>]*\b)y="[^"]*"/i, '$1y="0"');
11176
+ } else {
11177
+ normalized = normalized.replace(/<svg\b/i, '<svg y="0"');
11178
+ }
11179
+ normalized = normalized.replace(/\bpreserveAspectRatio="[^"]*"/i, 'preserveAspectRatio="none"');
11180
+ if (!/\bpreserveAspectRatio="[^"]*"/i.test(normalized)) {
11181
+ normalized = normalized.replace(/<svg\b/i, '<svg preserveAspectRatio="none"');
11145
11182
  }
11146
11183
  return normalized;
11147
11184
  }