@pixldocs/canvas-renderer 0.5.25 → 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 +226 -197
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +226 -197
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9613,6 +9613,10 @@ function PreviewCanvas({
|
|
|
9613
9613
|
}
|
|
9614
9614
|
);
|
|
9615
9615
|
}
|
|
9616
|
+
const PreviewCanvas$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
9617
|
+
__proto__: null,
|
|
9618
|
+
PreviewCanvas
|
|
9619
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
9616
9620
|
function applyThemeToConfig(config, themeOverrides) {
|
|
9617
9621
|
var _a, _b, _c;
|
|
9618
9622
|
if (!themeOverrides || Object.keys(themeOverrides).length === 0) return config;
|
|
@@ -11829,74 +11833,17 @@ async function ensureFontsForResolvedConfig(config) {
|
|
|
11829
11833
|
if (typeof document === "undefined") return;
|
|
11830
11834
|
const descriptors = collectFontDescriptorsFromConfig(config);
|
|
11831
11835
|
const families = new Set(descriptors.map((d) => d.family));
|
|
11832
|
-
|
|
11836
|
+
void withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 2500);
|
|
11833
11837
|
if (document.fonts) {
|
|
11834
|
-
|
|
11838
|
+
descriptors.forEach((d) => {
|
|
11835
11839
|
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
11836
11840
|
const weightStr = String(d.weight);
|
|
11837
11841
|
const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
|
|
11838
|
-
|
|
11842
|
+
document.fonts.load(spec).catch(() => {
|
|
11839
11843
|
});
|
|
11840
|
-
})
|
|
11841
|
-
await withTimeout(document.fonts.ready, 6e3);
|
|
11844
|
+
});
|
|
11842
11845
|
}
|
|
11843
11846
|
}
|
|
11844
|
-
const TEXT_TYPES = /* @__PURE__ */ new Set(["textbox", "text", "i-text"]);
|
|
11845
|
-
function getFabricCanvasFromContainer(container) {
|
|
11846
|
-
const registry2 = window.__fabricCanvasRegistry;
|
|
11847
|
-
if (registry2 instanceof Map) {
|
|
11848
|
-
for (const entry of registry2.values()) {
|
|
11849
|
-
const canvas = (entry == null ? void 0 : entry.canvas) || entry;
|
|
11850
|
-
if (!canvas || typeof canvas.toSVG !== "function") continue;
|
|
11851
|
-
const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
|
|
11852
|
-
if (el && container.contains(el)) return canvas;
|
|
11853
|
-
}
|
|
11854
|
-
}
|
|
11855
|
-
return null;
|
|
11856
|
-
}
|
|
11857
|
-
function stabilizeFabricTextObjects(fabricInstance) {
|
|
11858
|
-
var _a, _b, _c;
|
|
11859
|
-
if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
|
|
11860
|
-
clearFabricCharCache();
|
|
11861
|
-
clearMeasurementCache();
|
|
11862
|
-
const walk = (obj) => {
|
|
11863
|
-
var _a2, _b2, _c2, _d;
|
|
11864
|
-
if (!obj) return;
|
|
11865
|
-
const children = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
|
|
11866
|
-
if (children.length) children.forEach(walk);
|
|
11867
|
-
const isTextObject = typeof obj.text === "string" && typeof obj.initDimensions === "function" && (TEXT_TYPES.has(obj.type) || obj.isEditing !== void 0);
|
|
11868
|
-
if (!isTextObject) return;
|
|
11869
|
-
const saved = { width: obj.width, scaleX: obj.scaleX, scaleY: obj.scaleY };
|
|
11870
|
-
const reset = () => {
|
|
11871
|
-
var _a3;
|
|
11872
|
-
(_a3 = obj._clearCache) == null ? void 0 : _a3.call(obj);
|
|
11873
|
-
obj.__charBounds = [];
|
|
11874
|
-
obj.__lineWidths = [];
|
|
11875
|
-
obj.__lineHeights = [];
|
|
11876
|
-
obj.__graphemeLines = [];
|
|
11877
|
-
obj._textLines = [];
|
|
11878
|
-
obj.textLines = [];
|
|
11879
|
-
obj._styleMap = null;
|
|
11880
|
-
obj.styleMap = null;
|
|
11881
|
-
obj.dirty = true;
|
|
11882
|
-
};
|
|
11883
|
-
reset();
|
|
11884
|
-
obj.initDimensions();
|
|
11885
|
-
if (saved.width != null) {
|
|
11886
|
-
(_a2 = obj.set) == null ? void 0 : _a2.call(obj, { width: saved.width, scaleX: saved.scaleX, scaleY: saved.scaleY });
|
|
11887
|
-
reset();
|
|
11888
|
-
obj.initDimensions();
|
|
11889
|
-
(_b2 = obj.set) == null ? void 0 : _b2.call(obj, { width: saved.width, scaleX: saved.scaleX, scaleY: saved.scaleY });
|
|
11890
|
-
}
|
|
11891
|
-
obj.dirty = true;
|
|
11892
|
-
(_c2 = obj._clearCache) == null ? void 0 : _c2.call(obj);
|
|
11893
|
-
(_d = obj.setCoords) == null ? void 0 : _d.call(obj);
|
|
11894
|
-
};
|
|
11895
|
-
fabricInstance.getObjects().forEach(walk);
|
|
11896
|
-
(_a = fabricInstance.calcOffset) == null ? void 0 : _a.call(fabricInstance);
|
|
11897
|
-
(_b = fabricInstance.renderAll) == null ? void 0 : _b.call(fabricInstance);
|
|
11898
|
-
(_c = fabricInstance.requestRenderAll) == null ? void 0 : _c.call(fabricInstance);
|
|
11899
|
-
}
|
|
11900
11847
|
function PixldocsPreview(props) {
|
|
11901
11848
|
const {
|
|
11902
11849
|
pageIndex = 0,
|
|
@@ -11907,7 +11854,8 @@ function PixldocsPreview(props) {
|
|
|
11907
11854
|
style,
|
|
11908
11855
|
onDynamicFieldClick,
|
|
11909
11856
|
onReady,
|
|
11910
|
-
onError
|
|
11857
|
+
onError,
|
|
11858
|
+
skipFontReadyWait = true
|
|
11911
11859
|
} = props;
|
|
11912
11860
|
useEffect(() => {
|
|
11913
11861
|
setPackageApiUrl(imageProxyUrl);
|
|
@@ -11993,13 +11941,6 @@ function PixldocsPreview(props) {
|
|
|
11993
11941
|
() => `${pageIndex}-${fontsReadyVersion}-${stabilizationPass}`,
|
|
11994
11942
|
[pageIndex, fontsReadyVersion, stabilizationPass]
|
|
11995
11943
|
);
|
|
11996
|
-
const previewWrapRef = useCallback((node) => {
|
|
11997
|
-
if (!node || !config) return;
|
|
11998
|
-
requestAnimationFrame(() => {
|
|
11999
|
-
const fabricCanvas = getFabricCanvasFromContainer(node);
|
|
12000
|
-
if (fabricCanvas) stabilizeFabricTextObjects(fabricCanvas);
|
|
12001
|
-
});
|
|
12002
|
-
}, [config, previewKey]);
|
|
12003
11944
|
useEffect(() => {
|
|
12004
11945
|
if (isResolveMode) return;
|
|
12005
11946
|
if (!config) {
|
|
@@ -12030,13 +11971,14 @@ function PixldocsPreview(props) {
|
|
|
12030
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..." }) });
|
|
12031
11972
|
}
|
|
12032
11973
|
return /* @__PURE__ */ jsxs("div", { className, style: { ...style, position: "relative" }, children: [
|
|
12033
|
-
/* @__PURE__ */ jsx("div", {
|
|
11974
|
+
/* @__PURE__ */ jsx("div", { style: { visibility: canvasSettled ? "visible" : "hidden" }, children: /* @__PURE__ */ jsx(
|
|
12034
11975
|
PreviewCanvas,
|
|
12035
11976
|
{
|
|
12036
11977
|
config,
|
|
12037
11978
|
pageIndex,
|
|
12038
11979
|
zoom,
|
|
12039
11980
|
absoluteZoom,
|
|
11981
|
+
skipFontReadyWait,
|
|
12040
11982
|
onDynamicFieldClick,
|
|
12041
11983
|
onReady: handleCanvasReady
|
|
12042
11984
|
},
|
|
@@ -12045,70 +11987,6 @@ function PixldocsPreview(props) {
|
|
|
12045
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..." }) })
|
|
12046
11988
|
] });
|
|
12047
11989
|
}
|
|
12048
|
-
const PixldocsPreview$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
12049
|
-
__proto__: null,
|
|
12050
|
-
PixldocsPreview
|
|
12051
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
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.
|
|
@@ -12529,7 +12405,7 @@ class PixldocsRenderer {
|
|
|
12529
12405
|
}
|
|
12530
12406
|
}
|
|
12531
12407
|
async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality) {
|
|
12532
|
-
const {
|
|
12408
|
+
const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
|
|
12533
12409
|
const canvasWidth = config.canvas.width;
|
|
12534
12410
|
const canvasHeight = config.canvas.height;
|
|
12535
12411
|
return new Promise((resolve, reject) => {
|
|
@@ -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();
|
|
@@ -12596,11 +12467,12 @@ class PixldocsRenderer {
|
|
|
12596
12467
|
};
|
|
12597
12468
|
const root = createRoot(container);
|
|
12598
12469
|
root.render(
|
|
12599
|
-
createElement(
|
|
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
|
);
|
|
@@ -12617,19 +12489,18 @@ class PixldocsRenderer {
|
|
|
12617
12489
|
// document space (e.g. 612x792) instead of inflated pixel space.
|
|
12618
12490
|
captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
|
|
12619
12491
|
return new Promise(async (resolve, reject) => {
|
|
12620
|
-
const {
|
|
12492
|
+
const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
|
|
12621
12493
|
const container = document.createElement("div");
|
|
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();
|
|
@@ -12698,12 +12565,13 @@ class PixldocsRenderer {
|
|
|
12698
12565
|
};
|
|
12699
12566
|
const root = createRoot(container);
|
|
12700
12567
|
root.render(
|
|
12701
|
-
createElement(
|
|
12568
|
+
createElement(PreviewCanvas2, {
|
|
12702
12569
|
config,
|
|
12703
12570
|
pageIndex,
|
|
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);
|