@pixldocs/canvas-renderer 0.5.24 → 0.5.26
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 +218 -189
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +218 -189
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -100,8 +100,8 @@ export declare function embedFontsInPdf(pdf: jsPDF, fontFamilies: Set<string>, f
|
|
|
100
100
|
* 1. Walks ALL text nodes (including clones/repeatables) collecting
|
|
101
101
|
* fontFamily + fontWeight + fontStyle.
|
|
102
102
|
* 2. Loads each unique family via Google Fonts CSS v1 (idempotent).
|
|
103
|
-
* 3.
|
|
104
|
-
*
|
|
103
|
+
* 3. Kicks off each weight+style combo via `document.fonts.load()` without
|
|
104
|
+
* blocking render completion; late font load reflow handles final metrics.
|
|
105
105
|
*
|
|
106
106
|
* Idempotent — safe to call multiple times for the same config.
|
|
107
107
|
*/
|
|
@@ -286,6 +286,8 @@ declare interface PixldocsPreviewBaseProps {
|
|
|
286
286
|
onReady?: () => void;
|
|
287
287
|
/** Called when resolution or rendering fails */
|
|
288
288
|
onError?: (error: Error) => void;
|
|
289
|
+
/** Allow package previews to skip the blocking font-ready wait used by app preview */
|
|
290
|
+
skipFontReadyWait?: boolean;
|
|
289
291
|
}
|
|
290
292
|
|
|
291
293
|
/** Mode 1: Pre-resolved config */
|
package/dist/index.js
CHANGED
|
@@ -11833,73 +11833,16 @@ async function ensureFontsForResolvedConfig(config) {
|
|
|
11833
11833
|
if (typeof document === "undefined") return;
|
|
11834
11834
|
const descriptors = collectFontDescriptorsFromConfig(config);
|
|
11835
11835
|
const families = new Set(descriptors.map((d) => d.family));
|
|
11836
|
-
|
|
11836
|
+
void withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 2500);
|
|
11837
11837
|
if (document.fonts) {
|
|
11838
|
-
|
|
11838
|
+
descriptors.forEach((d) => {
|
|
11839
11839
|
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
11840
11840
|
const weightStr = String(d.weight);
|
|
11841
11841
|
const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
|
|
11842
|
-
|
|
11842
|
+
document.fonts.load(spec).catch(() => {
|
|
11843
11843
|
});
|
|
11844
|
-
})
|
|
11845
|
-
await withTimeout(document.fonts.ready, 6e3);
|
|
11846
|
-
}
|
|
11847
|
-
}
|
|
11848
|
-
const TEXT_TYPES = /* @__PURE__ */ new Set(["textbox", "text", "i-text"]);
|
|
11849
|
-
function getFabricCanvasFromContainer(container) {
|
|
11850
|
-
const registry2 = window.__fabricCanvasRegistry;
|
|
11851
|
-
if (registry2 instanceof Map) {
|
|
11852
|
-
for (const entry of registry2.values()) {
|
|
11853
|
-
const canvas = (entry == null ? void 0 : entry.canvas) || entry;
|
|
11854
|
-
if (!canvas || typeof canvas.toSVG !== "function") continue;
|
|
11855
|
-
const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
|
|
11856
|
-
if (el && container.contains(el)) return canvas;
|
|
11857
|
-
}
|
|
11844
|
+
});
|
|
11858
11845
|
}
|
|
11859
|
-
return null;
|
|
11860
|
-
}
|
|
11861
|
-
function stabilizeFabricTextObjects(fabricInstance) {
|
|
11862
|
-
var _a, _b, _c;
|
|
11863
|
-
if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
|
|
11864
|
-
clearFabricCharCache();
|
|
11865
|
-
clearMeasurementCache();
|
|
11866
|
-
const walk = (obj) => {
|
|
11867
|
-
var _a2, _b2, _c2, _d;
|
|
11868
|
-
if (!obj) return;
|
|
11869
|
-
const children = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
|
|
11870
|
-
if (children.length) children.forEach(walk);
|
|
11871
|
-
const isTextObject = typeof obj.text === "string" && typeof obj.initDimensions === "function" && (TEXT_TYPES.has(obj.type) || obj.isEditing !== void 0);
|
|
11872
|
-
if (!isTextObject) return;
|
|
11873
|
-
const saved = { width: obj.width, scaleX: obj.scaleX, scaleY: obj.scaleY };
|
|
11874
|
-
const reset = () => {
|
|
11875
|
-
var _a3;
|
|
11876
|
-
(_a3 = obj._clearCache) == null ? void 0 : _a3.call(obj);
|
|
11877
|
-
obj.__charBounds = [];
|
|
11878
|
-
obj.__lineWidths = [];
|
|
11879
|
-
obj.__lineHeights = [];
|
|
11880
|
-
obj.__graphemeLines = [];
|
|
11881
|
-
obj._textLines = [];
|
|
11882
|
-
obj.textLines = [];
|
|
11883
|
-
obj._styleMap = null;
|
|
11884
|
-
obj.styleMap = null;
|
|
11885
|
-
obj.dirty = true;
|
|
11886
|
-
};
|
|
11887
|
-
reset();
|
|
11888
|
-
obj.initDimensions();
|
|
11889
|
-
if (saved.width != null) {
|
|
11890
|
-
(_a2 = obj.set) == null ? void 0 : _a2.call(obj, { width: saved.width, scaleX: saved.scaleX, scaleY: saved.scaleY });
|
|
11891
|
-
reset();
|
|
11892
|
-
obj.initDimensions();
|
|
11893
|
-
(_b2 = obj.set) == null ? void 0 : _b2.call(obj, { width: saved.width, scaleX: saved.scaleX, scaleY: saved.scaleY });
|
|
11894
|
-
}
|
|
11895
|
-
obj.dirty = true;
|
|
11896
|
-
(_c2 = obj._clearCache) == null ? void 0 : _c2.call(obj);
|
|
11897
|
-
(_d = obj.setCoords) == null ? void 0 : _d.call(obj);
|
|
11898
|
-
};
|
|
11899
|
-
fabricInstance.getObjects().forEach(walk);
|
|
11900
|
-
(_a = fabricInstance.calcOffset) == null ? void 0 : _a.call(fabricInstance);
|
|
11901
|
-
(_b = fabricInstance.renderAll) == null ? void 0 : _b.call(fabricInstance);
|
|
11902
|
-
(_c = fabricInstance.requestRenderAll) == null ? void 0 : _c.call(fabricInstance);
|
|
11903
11846
|
}
|
|
11904
11847
|
function PixldocsPreview(props) {
|
|
11905
11848
|
const {
|
|
@@ -11911,7 +11854,8 @@ function PixldocsPreview(props) {
|
|
|
11911
11854
|
style,
|
|
11912
11855
|
onDynamicFieldClick,
|
|
11913
11856
|
onReady,
|
|
11914
|
-
onError
|
|
11857
|
+
onError,
|
|
11858
|
+
skipFontReadyWait = true
|
|
11915
11859
|
} = props;
|
|
11916
11860
|
useEffect(() => {
|
|
11917
11861
|
setPackageApiUrl(imageProxyUrl);
|
|
@@ -11997,13 +11941,6 @@ function PixldocsPreview(props) {
|
|
|
11997
11941
|
() => `${pageIndex}-${fontsReadyVersion}-${stabilizationPass}`,
|
|
11998
11942
|
[pageIndex, fontsReadyVersion, stabilizationPass]
|
|
11999
11943
|
);
|
|
12000
|
-
const previewWrapRef = useCallback((node) => {
|
|
12001
|
-
if (!node || !config) return;
|
|
12002
|
-
requestAnimationFrame(() => {
|
|
12003
|
-
const fabricCanvas = getFabricCanvasFromContainer(node);
|
|
12004
|
-
if (fabricCanvas) stabilizeFabricTextObjects(fabricCanvas);
|
|
12005
|
-
});
|
|
12006
|
-
}, [config, previewKey]);
|
|
12007
11944
|
useEffect(() => {
|
|
12008
11945
|
if (isResolveMode) return;
|
|
12009
11946
|
if (!config) {
|
|
@@ -12034,13 +11971,14 @@ function PixldocsPreview(props) {
|
|
|
12034
11971
|
return /* @__PURE__ */ jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
|
|
12035
11972
|
}
|
|
12036
11973
|
return /* @__PURE__ */ jsxs("div", { className, style: { ...style, position: "relative" }, children: [
|
|
12037
|
-
/* @__PURE__ */ jsx("div", {
|
|
11974
|
+
/* @__PURE__ */ jsx("div", { style: { visibility: canvasSettled ? "visible" : "hidden" }, children: /* @__PURE__ */ jsx(
|
|
12038
11975
|
PreviewCanvas,
|
|
12039
11976
|
{
|
|
12040
11977
|
config,
|
|
12041
11978
|
pageIndex,
|
|
12042
11979
|
zoom,
|
|
12043
11980
|
absoluteZoom,
|
|
11981
|
+
skipFontReadyWait,
|
|
12044
11982
|
onDynamicFieldClick,
|
|
12045
11983
|
onReady: handleCanvasReady
|
|
12046
11984
|
},
|
|
@@ -12049,66 +11987,6 @@ function PixldocsPreview(props) {
|
|
|
12049
11987
|
!canvasSettled && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
|
|
12050
11988
|
] });
|
|
12051
11989
|
}
|
|
12052
|
-
const inlinedAssetCache = /* @__PURE__ */ new Map();
|
|
12053
|
-
function shouldInlineImageUrl(url) {
|
|
12054
|
-
if (!url || url.startsWith("data:")) return false;
|
|
12055
|
-
if (url.startsWith("blob:")) return true;
|
|
12056
|
-
if (url.startsWith("/") && !url.startsWith("//")) return true;
|
|
12057
|
-
try {
|
|
12058
|
-
const parsed = new URL(url, window.location.href);
|
|
12059
|
-
const current = new URL(window.location.href);
|
|
12060
|
-
const host = parsed.hostname.toLowerCase();
|
|
12061
|
-
return parsed.origin === current.origin || host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host.endsWith(".local") || /^(10\.|192\.168\.|169\.254\.)/.test(host);
|
|
12062
|
-
} catch {
|
|
12063
|
-
return false;
|
|
12064
|
-
}
|
|
12065
|
-
}
|
|
12066
|
-
async function imageUrlToDataUrl(url) {
|
|
12067
|
-
if (inlinedAssetCache.has(url)) return inlinedAssetCache.get(url) ?? null;
|
|
12068
|
-
try {
|
|
12069
|
-
const response = await fetch(url);
|
|
12070
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
12071
|
-
const blob = await response.blob();
|
|
12072
|
-
if (blob.type.includes("text/html")) throw new Error("Expected image but got text/html");
|
|
12073
|
-
const dataUrl = await new Promise((resolve, reject) => {
|
|
12074
|
-
const reader = new FileReader();
|
|
12075
|
-
reader.onloadend = () => resolve(String(reader.result || ""));
|
|
12076
|
-
reader.onerror = reject;
|
|
12077
|
-
reader.readAsDataURL(blob);
|
|
12078
|
-
});
|
|
12079
|
-
inlinedAssetCache.set(url, dataUrl);
|
|
12080
|
-
return dataUrl;
|
|
12081
|
-
} catch (error) {
|
|
12082
|
-
console.warn("[@pixldocs/canvas-renderer] Failed to inline image asset:", url, error);
|
|
12083
|
-
inlinedAssetCache.set(url, null);
|
|
12084
|
-
return null;
|
|
12085
|
-
}
|
|
12086
|
-
}
|
|
12087
|
-
async function inlineNodeAssets(node) {
|
|
12088
|
-
if (node.type === "image") {
|
|
12089
|
-
const url = typeof node.src === "string" && node.src.trim() ? node.src.trim() : typeof node.imageUrl === "string" ? node.imageUrl.trim() : "";
|
|
12090
|
-
if (shouldInlineImageUrl(url)) {
|
|
12091
|
-
const dataUrl = await imageUrlToDataUrl(url);
|
|
12092
|
-
if (dataUrl) {
|
|
12093
|
-
node.src = dataUrl;
|
|
12094
|
-
node.imageUrl = dataUrl;
|
|
12095
|
-
}
|
|
12096
|
-
}
|
|
12097
|
-
}
|
|
12098
|
-
if (Array.isArray(node.children)) {
|
|
12099
|
-
await Promise.all(node.children.map(inlineNodeAssets));
|
|
12100
|
-
}
|
|
12101
|
-
}
|
|
12102
|
-
async function inlineBrowserReachableImageAssets(config) {
|
|
12103
|
-
if (typeof window === "undefined" || typeof fetch === "undefined" || typeof FileReader === "undefined") {
|
|
12104
|
-
return config;
|
|
12105
|
-
}
|
|
12106
|
-
const cloned = JSON.parse(JSON.stringify(config));
|
|
12107
|
-
await Promise.all(
|
|
12108
|
-
(cloned.pages || []).flatMap((page) => (page.children || []).map(inlineNodeAssets))
|
|
12109
|
-
);
|
|
12110
|
-
return cloned;
|
|
12111
|
-
}
|
|
12112
11990
|
class PixldocsRenderer {
|
|
12113
11991
|
constructor(config) {
|
|
12114
11992
|
__publicField(this, "config");
|
|
@@ -12119,22 +11997,21 @@ class PixldocsRenderer {
|
|
|
12119
11997
|
* Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
|
|
12120
11998
|
*/
|
|
12121
11999
|
async render(templateConfig, options = {}) {
|
|
12122
|
-
const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
|
|
12123
12000
|
const pageIndex = options.pageIndex ?? 0;
|
|
12124
12001
|
const format = options.format ?? "png";
|
|
12125
12002
|
const quality = options.quality ?? 0.92;
|
|
12126
12003
|
const pixelRatio = options.pixelRatio ?? this.config.pixelRatio ?? 2;
|
|
12127
|
-
const canvasWidth =
|
|
12128
|
-
const canvasHeight =
|
|
12129
|
-
const page =
|
|
12004
|
+
const canvasWidth = templateConfig.canvas.width;
|
|
12005
|
+
const canvasHeight = templateConfig.canvas.height;
|
|
12006
|
+
const page = templateConfig.pages[pageIndex];
|
|
12130
12007
|
if (!page) {
|
|
12131
|
-
throw new Error(`Page index ${pageIndex} not found (template has ${
|
|
12008
|
+
throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
|
|
12132
12009
|
}
|
|
12133
|
-
await ensureFontsForResolvedConfig(
|
|
12010
|
+
await ensureFontsForResolvedConfig(templateConfig);
|
|
12134
12011
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12135
12012
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12136
12013
|
const dataUrl = await this.renderPageViaPreviewCanvas(
|
|
12137
|
-
|
|
12014
|
+
templateConfig,
|
|
12138
12015
|
pageIndex,
|
|
12139
12016
|
pixelRatio,
|
|
12140
12017
|
format,
|
|
@@ -12186,17 +12063,16 @@ class PixldocsRenderer {
|
|
|
12186
12063
|
* This is the key building block for client-side vector PDF export.
|
|
12187
12064
|
*/
|
|
12188
12065
|
async renderPageSvg(templateConfig, pageIndex = 0) {
|
|
12189
|
-
const
|
|
12190
|
-
const page = renderConfig.pages[pageIndex];
|
|
12066
|
+
const page = templateConfig.pages[pageIndex];
|
|
12191
12067
|
if (!page) {
|
|
12192
|
-
throw new Error(`Page index ${pageIndex} not found (template has ${
|
|
12068
|
+
throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
|
|
12193
12069
|
}
|
|
12194
|
-
await ensureFontsForResolvedConfig(
|
|
12070
|
+
await ensureFontsForResolvedConfig(templateConfig);
|
|
12195
12071
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12196
12072
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12197
|
-
const canvasWidth =
|
|
12198
|
-
const canvasHeight =
|
|
12199
|
-
return this.captureSvgViaPreviewCanvas(
|
|
12073
|
+
const canvasWidth = templateConfig.canvas.width;
|
|
12074
|
+
const canvasHeight = templateConfig.canvas.height;
|
|
12075
|
+
return this.captureSvgViaPreviewCanvas(templateConfig, pageIndex, canvasWidth, canvasHeight);
|
|
12200
12076
|
}
|
|
12201
12077
|
/**
|
|
12202
12078
|
* Render all pages and return SVG strings for each.
|
|
@@ -12537,14 +12413,13 @@ class PixldocsRenderer {
|
|
|
12537
12413
|
container.style.cssText = `
|
|
12538
12414
|
position: fixed; left: -99999px; top: -99999px;
|
|
12539
12415
|
width: ${canvasWidth}px; height: ${canvasHeight}px;
|
|
12540
|
-
overflow: hidden; pointer-events: none;
|
|
12416
|
+
overflow: hidden; pointer-events: none; opacity: 0;
|
|
12541
12417
|
`;
|
|
12542
12418
|
document.body.appendChild(container);
|
|
12543
12419
|
const timeout = setTimeout(() => {
|
|
12544
12420
|
cleanup();
|
|
12545
12421
|
reject(new Error("Render timeout (30s)"));
|
|
12546
12422
|
}, 3e4);
|
|
12547
|
-
let finished = false;
|
|
12548
12423
|
const cleanup = () => {
|
|
12549
12424
|
clearTimeout(timeout);
|
|
12550
12425
|
try {
|
|
@@ -12554,12 +12429,8 @@ class PixldocsRenderer {
|
|
|
12554
12429
|
container.remove();
|
|
12555
12430
|
};
|
|
12556
12431
|
const onReady = () => {
|
|
12557
|
-
if (finished) return;
|
|
12558
|
-
finished = true;
|
|
12559
12432
|
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
12560
12433
|
try {
|
|
12561
|
-
await this.waitForStableTextMetrics(container, config, true);
|
|
12562
|
-
await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
|
|
12563
12434
|
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
12564
12435
|
const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
|
|
12565
12436
|
await this.waitForCanvasImages(container, expectedImageCount);
|
|
@@ -12571,8 +12442,8 @@ class PixldocsRenderer {
|
|
|
12571
12442
|
return;
|
|
12572
12443
|
}
|
|
12573
12444
|
const exportCanvas = document.createElement("canvas");
|
|
12574
|
-
exportCanvas.width =
|
|
12575
|
-
exportCanvas.height =
|
|
12445
|
+
exportCanvas.width = sourceCanvas.width;
|
|
12446
|
+
exportCanvas.height = sourceCanvas.height;
|
|
12576
12447
|
const exportCtx = exportCanvas.getContext("2d");
|
|
12577
12448
|
if (!exportCtx) {
|
|
12578
12449
|
cleanup();
|
|
@@ -12580,10 +12451,10 @@ class PixldocsRenderer {
|
|
|
12580
12451
|
return;
|
|
12581
12452
|
}
|
|
12582
12453
|
exportCtx.save();
|
|
12583
|
-
exportCtx.scale(
|
|
12454
|
+
exportCtx.scale(sourceCanvas.width / canvasWidth, sourceCanvas.height / canvasHeight);
|
|
12584
12455
|
this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
|
|
12585
12456
|
exportCtx.restore();
|
|
12586
|
-
exportCtx.drawImage(sourceCanvas, 0, 0
|
|
12457
|
+
exportCtx.drawImage(sourceCanvas, 0, 0);
|
|
12587
12458
|
const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
|
|
12588
12459
|
const dataUrl = exportCanvas.toDataURL(mimeType, quality);
|
|
12589
12460
|
cleanup();
|
|
@@ -12599,8 +12470,9 @@ class PixldocsRenderer {
|
|
|
12599
12470
|
createElement(PreviewCanvas2, {
|
|
12600
12471
|
config,
|
|
12601
12472
|
pageIndex,
|
|
12602
|
-
zoom:
|
|
12473
|
+
zoom: pixelRatio,
|
|
12603
12474
|
absoluteZoom: true,
|
|
12475
|
+
skipFontReadyWait: true,
|
|
12604
12476
|
onReady
|
|
12605
12477
|
})
|
|
12606
12478
|
);
|
|
@@ -12622,14 +12494,13 @@ class PixldocsRenderer {
|
|
|
12622
12494
|
container.style.cssText = `
|
|
12623
12495
|
position: fixed; left: -99999px; top: -99999px;
|
|
12624
12496
|
width: ${canvasWidth}px; height: ${canvasHeight}px;
|
|
12625
|
-
overflow: hidden; pointer-events: none;
|
|
12497
|
+
overflow: hidden; pointer-events: none; opacity: 0;
|
|
12626
12498
|
`;
|
|
12627
12499
|
document.body.appendChild(container);
|
|
12628
12500
|
const timeout = setTimeout(() => {
|
|
12629
12501
|
cleanup();
|
|
12630
12502
|
reject(new Error("SVG render timeout (30s)"));
|
|
12631
12503
|
}, 3e4);
|
|
12632
|
-
let finished = false;
|
|
12633
12504
|
const cleanup = () => {
|
|
12634
12505
|
clearTimeout(timeout);
|
|
12635
12506
|
try {
|
|
@@ -12639,13 +12510,9 @@ class PixldocsRenderer {
|
|
|
12639
12510
|
container.remove();
|
|
12640
12511
|
};
|
|
12641
12512
|
const onReady = () => {
|
|
12642
|
-
if (finished) return;
|
|
12643
|
-
finished = true;
|
|
12644
12513
|
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
12645
12514
|
var _a, _b;
|
|
12646
12515
|
try {
|
|
12647
|
-
await this.waitForStableTextMetrics(container, config, true);
|
|
12648
|
-
await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
|
|
12649
12516
|
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
12650
12517
|
if (!fabricInstance) {
|
|
12651
12518
|
cleanup();
|
|
@@ -12704,6 +12571,7 @@ class PixldocsRenderer {
|
|
|
12704
12571
|
zoom: 1,
|
|
12705
12572
|
// 1:1 — no UI scaling for SVG capture
|
|
12706
12573
|
absoluteZoom: true,
|
|
12574
|
+
skipFontReadyWait: true,
|
|
12707
12575
|
onReady
|
|
12708
12576
|
})
|
|
12709
12577
|
);
|
|
@@ -12764,12 +12632,21 @@ class PixldocsRenderer {
|
|
|
12764
12632
|
* using the global __fabricCanvasRegistry (set by PageCanvas).
|
|
12765
12633
|
*/
|
|
12766
12634
|
getFabricCanvasFromContainer(container) {
|
|
12767
|
-
|
|
12635
|
+
const registry2 = window.__fabricCanvasRegistry;
|
|
12636
|
+
if (registry2 instanceof Map) {
|
|
12637
|
+
for (const entry of registry2.values()) {
|
|
12638
|
+
const canvas = (entry == null ? void 0 : entry.canvas) || entry;
|
|
12639
|
+
if (!canvas || typeof canvas.toSVG !== "function") continue;
|
|
12640
|
+
const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
|
|
12641
|
+
if (el && container.contains(el)) return canvas;
|
|
12642
|
+
}
|
|
12643
|
+
}
|
|
12644
|
+
return null;
|
|
12768
12645
|
}
|
|
12769
|
-
async waitForStableTextMetrics(container, config
|
|
12646
|
+
async waitForStableTextMetrics(container, config) {
|
|
12770
12647
|
if (typeof document !== "undefined") {
|
|
12771
|
-
|
|
12772
|
-
await this.waitForRelevantFonts(config
|
|
12648
|
+
void ensureFontsForResolvedConfig(config);
|
|
12649
|
+
await this.waitForRelevantFonts(config);
|
|
12773
12650
|
}
|
|
12774
12651
|
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
12775
12652
|
if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
|
|
@@ -12778,7 +12655,59 @@ class PixldocsRenderer {
|
|
|
12778
12655
|
clearFabricCharCache();
|
|
12779
12656
|
clearMeasurementCache();
|
|
12780
12657
|
};
|
|
12781
|
-
const reflowTextboxes = () =>
|
|
12658
|
+
const reflowTextboxes = () => {
|
|
12659
|
+
var _a, _b, _c;
|
|
12660
|
+
const walk = (obj) => {
|
|
12661
|
+
var _a2, _b2, _c2, _d;
|
|
12662
|
+
if (!obj) return;
|
|
12663
|
+
const children = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
|
|
12664
|
+
if (children.length) children.forEach(walk);
|
|
12665
|
+
const isTextObject = typeof obj.text === "string" && typeof obj.initDimensions === "function" && (obj.type === "textbox" || obj.type === "text" || obj.type === "i-text" || obj.isEditing !== void 0);
|
|
12666
|
+
if (isTextObject) {
|
|
12667
|
+
const saved = {
|
|
12668
|
+
width: obj.width,
|
|
12669
|
+
scaleX: obj.scaleX,
|
|
12670
|
+
scaleY: obj.scaleY
|
|
12671
|
+
};
|
|
12672
|
+
const resetTextboxLayoutInternals = () => {
|
|
12673
|
+
var _a3;
|
|
12674
|
+
(_a3 = obj._clearCache) == null ? void 0 : _a3.call(obj);
|
|
12675
|
+
obj.__charBounds = [];
|
|
12676
|
+
obj.__lineWidths = [];
|
|
12677
|
+
obj.__lineHeights = [];
|
|
12678
|
+
obj.__graphemeLines = [];
|
|
12679
|
+
obj._textLines = [];
|
|
12680
|
+
obj.textLines = [];
|
|
12681
|
+
obj._styleMap = null;
|
|
12682
|
+
obj.styleMap = null;
|
|
12683
|
+
obj.dirty = true;
|
|
12684
|
+
};
|
|
12685
|
+
resetTextboxLayoutInternals();
|
|
12686
|
+
obj.initDimensions();
|
|
12687
|
+
if (saved.width != null) {
|
|
12688
|
+
(_a2 = obj.set) == null ? void 0 : _a2.call(obj, {
|
|
12689
|
+
width: saved.width,
|
|
12690
|
+
scaleX: saved.scaleX,
|
|
12691
|
+
scaleY: saved.scaleY
|
|
12692
|
+
});
|
|
12693
|
+
resetTextboxLayoutInternals();
|
|
12694
|
+
obj.initDimensions();
|
|
12695
|
+
}
|
|
12696
|
+
(_b2 = obj.set) == null ? void 0 : _b2.call(obj, {
|
|
12697
|
+
width: saved.width,
|
|
12698
|
+
scaleX: saved.scaleX,
|
|
12699
|
+
scaleY: saved.scaleY,
|
|
12700
|
+
dirty: true
|
|
12701
|
+
});
|
|
12702
|
+
(_c2 = obj._clearCache) == null ? void 0 : _c2.call(obj);
|
|
12703
|
+
(_d = obj.setCoords) == null ? void 0 : _d.call(obj);
|
|
12704
|
+
}
|
|
12705
|
+
};
|
|
12706
|
+
fabricInstance.getObjects().forEach(walk);
|
|
12707
|
+
(_a = fabricInstance.calcOffset) == null ? void 0 : _a.call(fabricInstance);
|
|
12708
|
+
(_b = fabricInstance.renderAll) == null ? void 0 : _b.call(fabricInstance);
|
|
12709
|
+
(_c = fabricInstance.requestRenderAll) == null ? void 0 : _c.call(fabricInstance);
|
|
12710
|
+
};
|
|
12782
12711
|
clearTextMetricCaches();
|
|
12783
12712
|
await waitForPaint();
|
|
12784
12713
|
reflowTextboxes();
|
|
@@ -13512,6 +13441,9 @@ function rewriteSvgFontsForJsPDF(svgStr) {
|
|
|
13512
13441
|
const resolved = resolveFontWeight(weight);
|
|
13513
13442
|
const isItalic = /italic|oblique/i.test(styleRaw);
|
|
13514
13443
|
const jsPdfName = getEmbeddedJsPDFFontName(clean, resolved, isItalic);
|
|
13444
|
+
el.setAttribute("data-source-font-family", clean);
|
|
13445
|
+
el.setAttribute("data-source-font-weight", String(resolved));
|
|
13446
|
+
el.setAttribute("data-source-font-style", isItalic ? "italic" : "normal");
|
|
13515
13447
|
const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
|
|
13516
13448
|
const hasDevanagari = containsDevanagari(directText);
|
|
13517
13449
|
if (hasDevanagari && directText.length > 0) {
|
|
@@ -14493,24 +14425,89 @@ async function svg2pdfWithDomMount(svg, pdf, opts) {
|
|
|
14493
14425
|
if (wrap.parentNode) document.body.removeChild(wrap);
|
|
14494
14426
|
}
|
|
14495
14427
|
}
|
|
14496
|
-
function convertTextDecorationsToLines(svg) {
|
|
14428
|
+
async function convertTextDecorationsToLines(svg) {
|
|
14497
14429
|
const doc = svg.ownerDocument;
|
|
14498
14430
|
if (!doc) return;
|
|
14499
|
-
|
|
14431
|
+
const resolveInheritedSvgValue = (el, attr, styleProp = attr) => {
|
|
14432
|
+
var _a, _b;
|
|
14433
|
+
let current = el;
|
|
14434
|
+
while (current) {
|
|
14435
|
+
const attrValue = (_a = current.getAttribute(attr)) == null ? void 0 : _a.trim();
|
|
14436
|
+
if (attrValue) return attrValue;
|
|
14437
|
+
const styleValue = (_b = getInlineStyleValue(current, styleProp)) == null ? void 0 : _b.trim();
|
|
14438
|
+
if (styleValue) return styleValue;
|
|
14439
|
+
current = current.parentElement;
|
|
14440
|
+
}
|
|
14441
|
+
return null;
|
|
14442
|
+
};
|
|
14443
|
+
if (typeof document !== "undefined" && document.fonts) {
|
|
14444
|
+
const fontFamilies = /* @__PURE__ */ new Set();
|
|
14445
|
+
for (const textEl of svg.querySelectorAll("text")) {
|
|
14446
|
+
const ff = textEl.getAttribute("data-source-font-family") || textEl.getAttribute("font-family");
|
|
14447
|
+
if (ff) fontFamilies.add(ff.replace(/'/g, ""));
|
|
14448
|
+
for (const tspan of textEl.querySelectorAll("tspan")) {
|
|
14449
|
+
const tff = tspan.getAttribute("data-source-font-family") || tspan.getAttribute("font-family");
|
|
14450
|
+
if (tff) fontFamilies.add(tff.replace(/'/g, ""));
|
|
14451
|
+
}
|
|
14452
|
+
}
|
|
14453
|
+
await Promise.all(Array.from(fontFamilies).flatMap((ff) => [
|
|
14454
|
+
document.fonts.load(`16px "${ff}"`).then(() => {
|
|
14455
|
+
}).catch(() => {
|
|
14456
|
+
}),
|
|
14457
|
+
document.fonts.load(`bold 16px "${ff}"`).then(() => {
|
|
14458
|
+
}).catch(() => {
|
|
14459
|
+
})
|
|
14460
|
+
]));
|
|
14461
|
+
await document.fonts.ready;
|
|
14462
|
+
}
|
|
14463
|
+
let tempContainer = null;
|
|
14464
|
+
let liveSvg = null;
|
|
14465
|
+
try {
|
|
14466
|
+
if (typeof document !== "undefined") {
|
|
14467
|
+
tempContainer = document.createElement("div");
|
|
14468
|
+
tempContainer.style.cssText = "position:fixed;left:-9999px;top:-9999px;visibility:hidden;pointer-events:none;";
|
|
14469
|
+
document.body.appendChild(tempContainer);
|
|
14470
|
+
const clone = svg.cloneNode(true);
|
|
14471
|
+
for (const textNode of clone.querySelectorAll("text, tspan, textPath")) {
|
|
14472
|
+
const sourceFamily = textNode.getAttribute("data-source-font-family");
|
|
14473
|
+
const sourceWeight = textNode.getAttribute("data-source-font-weight");
|
|
14474
|
+
const sourceStyle = textNode.getAttribute("data-source-font-style");
|
|
14475
|
+
if (sourceFamily) textNode.setAttribute("font-family", sourceFamily);
|
|
14476
|
+
if (sourceWeight) textNode.setAttribute("font-weight", sourceWeight);
|
|
14477
|
+
if (sourceStyle) textNode.setAttribute("font-style", sourceStyle);
|
|
14478
|
+
const inlineStyle = textNode.getAttribute("style") || "";
|
|
14479
|
+
const stylePairs = inlineStyle.split(";").map((part) => part.trim()).filter(Boolean).filter((part) => !/^font-family\s*:/i.test(part) && !/^font-weight\s*:/i.test(part) && !/^font-style\s*:/i.test(part));
|
|
14480
|
+
if (sourceFamily) stylePairs.push(`font-family: ${sourceFamily}`);
|
|
14481
|
+
if (sourceWeight) stylePairs.push(`font-weight: ${sourceWeight}`);
|
|
14482
|
+
if (sourceStyle) stylePairs.push(`font-style: ${sourceStyle}`);
|
|
14483
|
+
if (stylePairs.length > 0) textNode.setAttribute("style", stylePairs.join("; "));
|
|
14484
|
+
}
|
|
14485
|
+
tempContainer.appendChild(clone);
|
|
14486
|
+
liveSvg = clone;
|
|
14487
|
+
}
|
|
14488
|
+
} catch {
|
|
14489
|
+
}
|
|
14500
14490
|
let ctx = null;
|
|
14501
14491
|
try {
|
|
14502
|
-
|
|
14492
|
+
const realDoc = typeof document !== "undefined" ? document : doc;
|
|
14493
|
+
const measureCanvas = realDoc.createElement("canvas");
|
|
14503
14494
|
ctx = measureCanvas.getContext("2d");
|
|
14504
14495
|
} catch {
|
|
14505
14496
|
}
|
|
14506
14497
|
const textEls = Array.from(svg.querySelectorAll("text"));
|
|
14507
|
-
|
|
14498
|
+
const liveTextEls = liveSvg ? Array.from(liveSvg.querySelectorAll("text")) : null;
|
|
14499
|
+
for (let ti = 0; ti < textEls.length; ti++) {
|
|
14500
|
+
const textEl = textEls[ti];
|
|
14501
|
+
const liveTextEl = liveTextEls == null ? void 0 : liveTextEls[ti];
|
|
14508
14502
|
const tspans = Array.from(textEl.querySelectorAll("tspan"));
|
|
14509
14503
|
if (tspans.length === 0) continue;
|
|
14504
|
+
const liveTspans = liveTextEl ? Array.from(liveTextEl.querySelectorAll("tspan")) : null;
|
|
14510
14505
|
const textDecOnText = (textEl.getAttribute("text-decoration") || "").toLowerCase();
|
|
14511
14506
|
const textStyleDec = (getInlineStyleValue(textEl, "text-decoration") || "").toLowerCase();
|
|
14512
14507
|
const textHasUnderline = textDecOnText.includes("underline") || textStyleDec.includes("underline");
|
|
14513
|
-
for (
|
|
14508
|
+
for (let si = 0; si < tspans.length; si++) {
|
|
14509
|
+
const tspan = tspans[si];
|
|
14510
|
+
const liveTspan = liveTspans == null ? void 0 : liveTspans[si];
|
|
14514
14511
|
const tspanDec = (tspan.getAttribute("text-decoration") || "").toLowerCase();
|
|
14515
14512
|
const tspanStyleDec = (getInlineStyleValue(tspan, "text-decoration") || "").toLowerCase();
|
|
14516
14513
|
const hasUnderline = tspanDec.includes("underline") || tspanStyleDec.includes("underline") || textHasUnderline;
|
|
@@ -14524,41 +14521,73 @@ function convertTextDecorationsToLines(svg) {
|
|
|
14524
14521
|
const fontSize = parseFloat(
|
|
14525
14522
|
tspan.getAttribute("font-size") || textEl.getAttribute("font-size") || "16"
|
|
14526
14523
|
);
|
|
14527
|
-
const fontFamily = tspan.getAttribute("font-family") || textEl.getAttribute("font-family") || "sans-serif";
|
|
14528
|
-
const fontWeight = tspan.getAttribute("font-weight") || textEl.getAttribute("font-weight") || "normal";
|
|
14524
|
+
const fontFamily = tspan.getAttribute("data-source-font-family") || textEl.getAttribute("data-source-font-family") || resolveInheritedSvgValue(tspan, "font-family") || "sans-serif";
|
|
14525
|
+
const fontWeight = tspan.getAttribute("data-source-font-weight") || textEl.getAttribute("data-source-font-weight") || resolveInheritedSvgValue(tspan, "font-weight") || "normal";
|
|
14526
|
+
const fontStyle = tspan.getAttribute("data-source-font-style") || textEl.getAttribute("data-source-font-style") || resolveInheritedSvgValue(tspan, "font-style") || "normal";
|
|
14529
14527
|
const fill = tspan.getAttribute("fill") || textEl.getAttribute("fill") || "#000000";
|
|
14530
|
-
let textWidth = 0;
|
|
14531
|
-
if (
|
|
14528
|
+
let textWidth = content.length * fontSize * 0.6;
|
|
14529
|
+
if (ctx) {
|
|
14530
|
+
ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily.replace(/'/g, "")}`;
|
|
14531
|
+
textWidth = ctx.measureText(content).width;
|
|
14532
|
+
}
|
|
14533
|
+
const textAnchorRaw = (resolveInheritedSvgValue(tspan, "text-anchor") || "start").toLowerCase();
|
|
14534
|
+
const textAnchor = textAnchorRaw === "middle" || textAnchorRaw === "end" ? textAnchorRaw : "start";
|
|
14535
|
+
let lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
|
|
14536
|
+
let lineEndX = lineStartX + textWidth;
|
|
14537
|
+
let baselineY = y;
|
|
14538
|
+
if (liveTspan) {
|
|
14532
14539
|
try {
|
|
14533
|
-
|
|
14540
|
+
const liveCharCount = liveTspan.getNumberOfChars();
|
|
14541
|
+
const svgWidth = liveTspan.getComputedTextLength();
|
|
14542
|
+
if (Number.isFinite(svgWidth) && svgWidth > 0) {
|
|
14543
|
+
textWidth = Math.max(textWidth, svgWidth);
|
|
14544
|
+
}
|
|
14545
|
+
if (liveCharCount > 0) {
|
|
14546
|
+
const start = liveTspan.getStartPositionOfChar(0);
|
|
14547
|
+
const end = liveTspan.getEndPositionOfChar(liveCharCount - 1);
|
|
14548
|
+
if (Number.isFinite(start.x)) lineStartX = start.x;
|
|
14549
|
+
if (Number.isFinite(start.y)) baselineY = start.y;
|
|
14550
|
+
if (Number.isFinite(end.x)) lineEndX = Math.max(end.x, lineStartX + textWidth);
|
|
14551
|
+
if (Number.isFinite(end.y) && !Number.isFinite(baselineY)) baselineY = end.y;
|
|
14552
|
+
} else {
|
|
14553
|
+
lineEndX = lineStartX + textWidth;
|
|
14554
|
+
}
|
|
14555
|
+
const bbox = liveTspan.getBBox();
|
|
14556
|
+
if (Number.isFinite(bbox.x) && Number.isFinite(bbox.width)) {
|
|
14557
|
+
lineStartX = Math.min(lineStartX, bbox.x);
|
|
14558
|
+
lineEndX = Math.max(lineEndX, bbox.x + bbox.width);
|
|
14559
|
+
}
|
|
14534
14560
|
} catch {
|
|
14535
14561
|
}
|
|
14536
14562
|
}
|
|
14537
|
-
if (!
|
|
14538
|
-
|
|
14539
|
-
|
|
14540
|
-
textWidth = ctx.measureText(content).width;
|
|
14541
|
-
} else {
|
|
14542
|
-
textWidth = content.length * fontSize * 0.6;
|
|
14543
|
-
}
|
|
14563
|
+
if (!(lineEndX > lineStartX)) {
|
|
14564
|
+
lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
|
|
14565
|
+
lineEndX = lineStartX + textWidth;
|
|
14544
14566
|
}
|
|
14545
|
-
const underlineY =
|
|
14567
|
+
const underlineY = baselineY + fontSize * 0.15;
|
|
14546
14568
|
const thickness = Math.max(0.5, fontSize * 0.066667);
|
|
14547
14569
|
const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
14548
|
-
line.setAttribute("x1", String(
|
|
14570
|
+
line.setAttribute("x1", String(lineStartX));
|
|
14549
14571
|
line.setAttribute("y1", String(underlineY));
|
|
14550
|
-
line.setAttribute("x2", String(
|
|
14572
|
+
line.setAttribute("x2", String(lineEndX));
|
|
14551
14573
|
line.setAttribute("y2", String(underlineY));
|
|
14552
14574
|
line.setAttribute("stroke", fill.startsWith("url(") ? "#000000" : fill);
|
|
14553
14575
|
line.setAttribute("stroke-width", String(thickness));
|
|
14576
|
+
line.setAttribute("stroke-linecap", "butt");
|
|
14554
14577
|
line.setAttribute("fill", "none");
|
|
14555
14578
|
if (textEl.parentElement) {
|
|
14556
14579
|
textEl.parentElement.insertBefore(line, textEl.nextSibling);
|
|
14557
14580
|
}
|
|
14558
14581
|
}
|
|
14559
14582
|
}
|
|
14583
|
+
if (tempContainer) {
|
|
14584
|
+
try {
|
|
14585
|
+
document.body.removeChild(tempContainer);
|
|
14586
|
+
} catch {
|
|
14587
|
+
}
|
|
14588
|
+
}
|
|
14560
14589
|
}
|
|
14561
|
-
function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
|
|
14590
|
+
async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
|
|
14562
14591
|
try {
|
|
14563
14592
|
const parser = new DOMParser();
|
|
14564
14593
|
const processedSvg = inlineNestedSvgImageDataUris(rawSvg, parser);
|
|
@@ -14587,7 +14616,6 @@ function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, opti
|
|
|
14587
14616
|
stripSuspiciousFullPageOverlayNodes(svgToDraw);
|
|
14588
14617
|
if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
|
|
14589
14618
|
sanitizeSvgTreeForPdf(svgToDraw);
|
|
14590
|
-
convertTextDecorationsToLines(svgToDraw);
|
|
14591
14619
|
return svgToDraw;
|
|
14592
14620
|
} catch {
|
|
14593
14621
|
return null;
|
|
@@ -14690,7 +14718,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
|
|
|
14690
14718
|
const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
|
|
14691
14719
|
drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
|
|
14692
14720
|
const shouldStripBg = stripPageBackground ?? hasGradient;
|
|
14693
|
-
let processedSvg = prepareLiveCanvasSvgForPdf(page.svg, page.width, page.height, `page-${i + 1}`, {
|
|
14721
|
+
let processedSvg = await prepareLiveCanvasSvgForPdf(page.svg, page.width, page.height, `page-${i + 1}`, {
|
|
14694
14722
|
stripPageBackground: shouldStripBg
|
|
14695
14723
|
});
|
|
14696
14724
|
if (processedSvg) {
|
|
@@ -14699,6 +14727,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
|
|
|
14699
14727
|
const reParser = new DOMParser();
|
|
14700
14728
|
const reDoc = reParser.parseFromString(rewrittenSvg, "image/svg+xml");
|
|
14701
14729
|
processedSvg = reDoc.documentElement;
|
|
14730
|
+
await convertTextDecorationsToLines(processedSvg);
|
|
14702
14731
|
}
|
|
14703
14732
|
if (processedSvg) {
|
|
14704
14733
|
pdf.setFillColor(0, 0, 0);
|