@pixldocs/canvas-renderer 0.5.56 → 0.5.58
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 +178 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +178 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -12263,6 +12263,46 @@ async function ensureFontsForResolvedConfig(config) {
|
|
|
12263
12263
|
});
|
|
12264
12264
|
}
|
|
12265
12265
|
}
|
|
12266
|
+
function configHasAutoShrinkText$1(config) {
|
|
12267
|
+
var _a;
|
|
12268
|
+
if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
|
|
12269
|
+
const walk = (nodes) => {
|
|
12270
|
+
for (const node of nodes || []) {
|
|
12271
|
+
if (!node) continue;
|
|
12272
|
+
if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
|
|
12273
|
+
if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
|
|
12274
|
+
}
|
|
12275
|
+
return false;
|
|
12276
|
+
};
|
|
12277
|
+
for (const page of config.pages) {
|
|
12278
|
+
if (walk(page.children || [])) return true;
|
|
12279
|
+
}
|
|
12280
|
+
return false;
|
|
12281
|
+
}
|
|
12282
|
+
async function awaitFontsForConfig(config, maxWaitMs) {
|
|
12283
|
+
if (typeof document === "undefined" || !document.fonts) return;
|
|
12284
|
+
void ensureFontsForResolvedConfig(config);
|
|
12285
|
+
const descriptors = collectFontDescriptorsFromConfig(config);
|
|
12286
|
+
if (descriptors.length === 0) return;
|
|
12287
|
+
const loads = Promise.all(
|
|
12288
|
+
descriptors.map((d) => {
|
|
12289
|
+
const stylePrefix = d.style === "italic" ? "italic " : "";
|
|
12290
|
+
const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
|
|
12291
|
+
return document.fonts.load(spec).catch(() => []);
|
|
12292
|
+
})
|
|
12293
|
+
).then(() => void 0);
|
|
12294
|
+
await Promise.race([
|
|
12295
|
+
loads,
|
|
12296
|
+
new Promise((resolve) => setTimeout(resolve, maxWaitMs))
|
|
12297
|
+
]);
|
|
12298
|
+
await Promise.race([
|
|
12299
|
+
document.fonts.ready.catch(() => void 0).then(() => void 0),
|
|
12300
|
+
new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
|
|
12301
|
+
]);
|
|
12302
|
+
await new Promise(
|
|
12303
|
+
(resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
|
|
12304
|
+
);
|
|
12305
|
+
}
|
|
12266
12306
|
const PREVIEW_DEBUG_PREFIX = "[canvas-renderer][preview-debug]";
|
|
12267
12307
|
function countUnderlinedNodes(config) {
|
|
12268
12308
|
var _a;
|
|
@@ -12348,15 +12388,17 @@ function PixldocsPreview(props) {
|
|
|
12348
12388
|
underlinedNodes: countUnderlinedNodes(resolved.config)
|
|
12349
12389
|
});
|
|
12350
12390
|
setResolvedConfig(resolved.config);
|
|
12351
|
-
|
|
12391
|
+
const hasAutoShrink = configHasAutoShrinkText$1(resolved.config);
|
|
12392
|
+
const waitMs = hasAutoShrink ? 4e3 : 1800;
|
|
12393
|
+
awaitFontsForConfig(resolved.config, waitMs).then(() => {
|
|
12352
12394
|
if (!cancelled) {
|
|
12353
|
-
console.log(PREVIEW_DEBUG_PREFIX, "resolve-mode fonts
|
|
12395
|
+
console.log(PREVIEW_DEBUG_PREFIX, "resolve-mode fonts settled", { hasAutoShrink, waitMs });
|
|
12354
12396
|
setFontsReady(true);
|
|
12355
12397
|
setIsLoading(false);
|
|
12356
12398
|
}
|
|
12357
12399
|
}).catch((err) => {
|
|
12358
12400
|
if (!cancelled) {
|
|
12359
|
-
console.warn(PREVIEW_DEBUG_PREFIX, "resolve-mode
|
|
12401
|
+
console.warn(PREVIEW_DEBUG_PREFIX, "resolve-mode font wait failed", err);
|
|
12360
12402
|
setFontsReady(true);
|
|
12361
12403
|
setIsLoading(false);
|
|
12362
12404
|
}
|
|
@@ -12425,16 +12467,26 @@ function PixldocsPreview(props) {
|
|
|
12425
12467
|
setFontsReady(false);
|
|
12426
12468
|
setCanvasSettled(false);
|
|
12427
12469
|
setStabilizationPass(0);
|
|
12428
|
-
|
|
12429
|
-
|
|
12470
|
+
let cancelled = false;
|
|
12471
|
+
const hasAutoShrink = configHasAutoShrinkText$1(config);
|
|
12472
|
+
const waitMs = hasAutoShrink ? 4e3 : 1800;
|
|
12473
|
+
awaitFontsForConfig(config, waitMs).then(() => {
|
|
12474
|
+
if (cancelled) return;
|
|
12475
|
+
console.log(PREVIEW_DEBUG_PREFIX, "config-mode fonts settled", {
|
|
12430
12476
|
pageIndex,
|
|
12477
|
+
hasAutoShrink,
|
|
12478
|
+
waitMs,
|
|
12431
12479
|
underlinedNodes: countUnderlinedNodes(config)
|
|
12432
12480
|
});
|
|
12433
12481
|
setFontsReady(true);
|
|
12434
12482
|
}).catch((err) => {
|
|
12435
|
-
|
|
12483
|
+
if (cancelled) return;
|
|
12484
|
+
console.warn(PREVIEW_DEBUG_PREFIX, "config-mode font wait failed", err);
|
|
12436
12485
|
setFontsReady(true);
|
|
12437
12486
|
});
|
|
12487
|
+
return () => {
|
|
12488
|
+
cancelled = true;
|
|
12489
|
+
};
|
|
12438
12490
|
}, [isResolveMode, config]);
|
|
12439
12491
|
const handleCanvasReady = react.useCallback(() => {
|
|
12440
12492
|
if (stabilizationPass === 0) {
|
|
@@ -12471,7 +12523,7 @@ function PixldocsPreview(props) {
|
|
|
12471
12523
|
!canvasSettled && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
|
|
12472
12524
|
] });
|
|
12473
12525
|
}
|
|
12474
|
-
const PACKAGE_VERSION = "0.5.
|
|
12526
|
+
const PACKAGE_VERSION = "0.5.57";
|
|
12475
12527
|
let __underlineFixInstalled = false;
|
|
12476
12528
|
function installUnderlineFix(fab) {
|
|
12477
12529
|
var _a;
|
|
@@ -12568,6 +12620,22 @@ function installUnderlineFix(fab) {
|
|
|
12568
12620
|
__underlineFixInstalled = true;
|
|
12569
12621
|
console.log(`[canvas-renderer] underline-fix monkey patch installed (v${PACKAGE_VERSION})`);
|
|
12570
12622
|
}
|
|
12623
|
+
function configHasAutoShrinkText(config) {
|
|
12624
|
+
var _a;
|
|
12625
|
+
if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
|
|
12626
|
+
const walk = (nodes) => {
|
|
12627
|
+
for (const node of nodes || []) {
|
|
12628
|
+
if (!node) continue;
|
|
12629
|
+
if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
|
|
12630
|
+
if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
|
|
12631
|
+
}
|
|
12632
|
+
return false;
|
|
12633
|
+
};
|
|
12634
|
+
for (const page of config.pages) {
|
|
12635
|
+
if (walk(page.children || [])) return true;
|
|
12636
|
+
}
|
|
12637
|
+
return false;
|
|
12638
|
+
}
|
|
12571
12639
|
class PixldocsRenderer {
|
|
12572
12640
|
constructor(config) {
|
|
12573
12641
|
__publicField(this, "config");
|
|
@@ -12594,6 +12662,11 @@ class PixldocsRenderer {
|
|
|
12594
12662
|
throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
|
|
12595
12663
|
}
|
|
12596
12664
|
await ensureFontsForResolvedConfig(templateConfig);
|
|
12665
|
+
if (!options.skipFontReadyWait) {
|
|
12666
|
+
const hasAutoShrink = configHasAutoShrinkText(templateConfig);
|
|
12667
|
+
const defaultWait = hasAutoShrink ? 4e3 : 1800;
|
|
12668
|
+
await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
|
|
12669
|
+
}
|
|
12597
12670
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12598
12671
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12599
12672
|
const dataUrl = await this.renderPageViaPreviewCanvas(
|
|
@@ -12601,7 +12674,8 @@ class PixldocsRenderer {
|
|
|
12601
12674
|
pageIndex,
|
|
12602
12675
|
pixelRatio,
|
|
12603
12676
|
format,
|
|
12604
|
-
quality
|
|
12677
|
+
quality,
|
|
12678
|
+
{ skipFontReadyWait: options.skipFontReadyWait, waitForFontsMs: options.waitForFontsMs }
|
|
12605
12679
|
);
|
|
12606
12680
|
return {
|
|
12607
12681
|
dataUrl,
|
|
@@ -12615,9 +12689,14 @@ class PixldocsRenderer {
|
|
|
12615
12689
|
* Render all pages and return array of results.
|
|
12616
12690
|
*/
|
|
12617
12691
|
async renderAllPages(templateConfig, options = {}) {
|
|
12692
|
+
if (!options.skipFontReadyWait) {
|
|
12693
|
+
const hasAutoShrink = configHasAutoShrinkText(templateConfig);
|
|
12694
|
+
const defaultWait = hasAutoShrink ? 4e3 : 1800;
|
|
12695
|
+
await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
|
|
12696
|
+
}
|
|
12618
12697
|
const results = [];
|
|
12619
12698
|
for (let i = 0; i < templateConfig.pages.length; i++) {
|
|
12620
|
-
results.push(await this.render(templateConfig, { ...options, pageIndex: i }));
|
|
12699
|
+
results.push(await this.render(templateConfig, { ...options, pageIndex: i, skipFontReadyWait: true }));
|
|
12621
12700
|
}
|
|
12622
12701
|
return results;
|
|
12623
12702
|
}
|
|
@@ -12654,6 +12733,8 @@ class PixldocsRenderer {
|
|
|
12654
12733
|
throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
|
|
12655
12734
|
}
|
|
12656
12735
|
await ensureFontsForResolvedConfig(templateConfig);
|
|
12736
|
+
const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
|
|
12737
|
+
await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
|
|
12657
12738
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12658
12739
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12659
12740
|
const canvasWidth = templateConfig.canvas.width;
|
|
@@ -12665,6 +12746,8 @@ class PixldocsRenderer {
|
|
|
12665
12746
|
*/
|
|
12666
12747
|
async renderAllPageSvgs(templateConfig) {
|
|
12667
12748
|
await ensureFontsForResolvedConfig(templateConfig);
|
|
12749
|
+
const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
|
|
12750
|
+
await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
|
|
12668
12751
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12669
12752
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12670
12753
|
const results = [];
|
|
@@ -12917,6 +13000,26 @@ class PixldocsRenderer {
|
|
|
12917
13000
|
]);
|
|
12918
13001
|
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
|
|
12919
13002
|
}
|
|
13003
|
+
/**
|
|
13004
|
+
* Block until the webfonts referenced by `config` have actually loaded
|
|
13005
|
+
* (or `maxWaitMs` elapses). Used by the headless capture path BEFORE
|
|
13006
|
+
* mounting `PreviewCanvas`, so the synchronous `createText` auto-shrink
|
|
13007
|
+
* loop measures against final font metrics instead of fallback ones.
|
|
13008
|
+
*
|
|
13009
|
+
* Stronger than `ensureFontsForResolvedConfig` (which is fire-and-forget)
|
|
13010
|
+
* — this awaits each `document.fonts.load(spec)` AND `document.fonts.ready`,
|
|
13011
|
+
* racing the whole thing against `maxWaitMs` so a slow CDN can't hang the
|
|
13012
|
+
* renderer.
|
|
13013
|
+
*/
|
|
13014
|
+
async awaitFontsForConfig(config, maxWaitMs) {
|
|
13015
|
+
if (typeof document === "undefined" || !document.fonts) return;
|
|
13016
|
+
void ensureFontsForResolvedConfig(config);
|
|
13017
|
+
await this.waitForRelevantFonts(config, maxWaitMs);
|
|
13018
|
+
await Promise.race([
|
|
13019
|
+
document.fonts.ready.catch(() => void 0).then(() => void 0),
|
|
13020
|
+
new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
|
|
13021
|
+
]);
|
|
13022
|
+
}
|
|
12920
13023
|
getNormalizedGradientStops(gradient) {
|
|
12921
13024
|
const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
|
|
12922
13025
|
offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
|
|
@@ -12990,10 +13093,19 @@ class PixldocsRenderer {
|
|
|
12990
13093
|
} catch {
|
|
12991
13094
|
}
|
|
12992
13095
|
}
|
|
12993
|
-
async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality) {
|
|
13096
|
+
async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
|
|
12994
13097
|
const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
|
|
12995
13098
|
const canvasWidth = config.canvas.width;
|
|
12996
13099
|
const canvasHeight = config.canvas.height;
|
|
13100
|
+
const hasAutoShrink = configHasAutoShrinkText(config);
|
|
13101
|
+
let firstMountSettled = false;
|
|
13102
|
+
let lateFontSettleDetected = false;
|
|
13103
|
+
if (typeof document !== "undefined" && document.fonts && hasAutoShrink) {
|
|
13104
|
+
document.fonts.ready.then(() => {
|
|
13105
|
+
if (firstMountSettled) lateFontSettleDetected = true;
|
|
13106
|
+
}).catch(() => {
|
|
13107
|
+
});
|
|
13108
|
+
}
|
|
12997
13109
|
return new Promise((resolve, reject) => {
|
|
12998
13110
|
const container = document.createElement("div");
|
|
12999
13111
|
container.style.cssText = `
|
|
@@ -13006,6 +13118,8 @@ class PixldocsRenderer {
|
|
|
13006
13118
|
cleanup();
|
|
13007
13119
|
reject(new Error("Render timeout (30s)"));
|
|
13008
13120
|
}, 3e4);
|
|
13121
|
+
let root;
|
|
13122
|
+
let mountKey = 0;
|
|
13009
13123
|
const cleanup = () => {
|
|
13010
13124
|
clearTimeout(timeout);
|
|
13011
13125
|
try {
|
|
@@ -13014,6 +13128,46 @@ class PixldocsRenderer {
|
|
|
13014
13128
|
}
|
|
13015
13129
|
container.remove();
|
|
13016
13130
|
};
|
|
13131
|
+
const remountWithFreshKey = async () => {
|
|
13132
|
+
mountKey += 1;
|
|
13133
|
+
try {
|
|
13134
|
+
clearMeasurementCache();
|
|
13135
|
+
} catch {
|
|
13136
|
+
}
|
|
13137
|
+
try {
|
|
13138
|
+
clearFabricCharCache();
|
|
13139
|
+
} catch {
|
|
13140
|
+
}
|
|
13141
|
+
try {
|
|
13142
|
+
root.unmount();
|
|
13143
|
+
} catch {
|
|
13144
|
+
}
|
|
13145
|
+
root = client.createRoot(container);
|
|
13146
|
+
await new Promise((settle) => {
|
|
13147
|
+
const onReadyOnce = () => {
|
|
13148
|
+
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
13149
|
+
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
13150
|
+
const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
|
|
13151
|
+
await this.waitForCanvasImages(container, expectedImageCount);
|
|
13152
|
+
await this.waitForStableTextMetrics(container, config);
|
|
13153
|
+
await this.waitForCanvasScene(container, config, pageIndex);
|
|
13154
|
+
if (!fabricInstance) return settle();
|
|
13155
|
+
settle();
|
|
13156
|
+
}).catch(() => settle());
|
|
13157
|
+
};
|
|
13158
|
+
root.render(
|
|
13159
|
+
react.createElement(PreviewCanvas2, {
|
|
13160
|
+
key: `remount-${mountKey}`,
|
|
13161
|
+
config,
|
|
13162
|
+
pageIndex,
|
|
13163
|
+
zoom: pixelRatio,
|
|
13164
|
+
absoluteZoom: true,
|
|
13165
|
+
skipFontReadyWait: false,
|
|
13166
|
+
onReady: onReadyOnce
|
|
13167
|
+
})
|
|
13168
|
+
);
|
|
13169
|
+
});
|
|
13170
|
+
};
|
|
13017
13171
|
const onReady = () => {
|
|
13018
13172
|
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
13019
13173
|
try {
|
|
@@ -13022,16 +13176,23 @@ class PixldocsRenderer {
|
|
|
13022
13176
|
await this.waitForCanvasImages(container, expectedImageCount);
|
|
13023
13177
|
await this.waitForStableTextMetrics(container, config);
|
|
13024
13178
|
await this.waitForCanvasScene(container, config, pageIndex);
|
|
13179
|
+
firstMountSettled = true;
|
|
13180
|
+
if (hasAutoShrink && lateFontSettleDetected) {
|
|
13181
|
+
console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
|
|
13182
|
+
await remountWithFreshKey();
|
|
13183
|
+
}
|
|
13025
13184
|
const fabricCanvas = container.querySelector("canvas.upper-canvas, canvas");
|
|
13026
13185
|
const sourceCanvas = (fabricInstance == null ? void 0 : fabricInstance.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || fabricCanvas;
|
|
13186
|
+
const fabricInstanceAfter = this.getFabricCanvasFromContainer(container) || fabricInstance;
|
|
13187
|
+
const sourceCanvasAfter = (fabricInstanceAfter == null ? void 0 : fabricInstanceAfter.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || sourceCanvas;
|
|
13027
13188
|
if (!sourceCanvas) {
|
|
13028
13189
|
cleanup();
|
|
13029
13190
|
reject(new Error("No canvas element found after render"));
|
|
13030
13191
|
return;
|
|
13031
13192
|
}
|
|
13032
13193
|
const exportCanvas = document.createElement("canvas");
|
|
13033
|
-
exportCanvas.width =
|
|
13034
|
-
exportCanvas.height =
|
|
13194
|
+
exportCanvas.width = sourceCanvasAfter.width;
|
|
13195
|
+
exportCanvas.height = sourceCanvasAfter.height;
|
|
13035
13196
|
const exportCtx = exportCanvas.getContext("2d");
|
|
13036
13197
|
if (!exportCtx) {
|
|
13037
13198
|
cleanup();
|
|
@@ -13039,10 +13200,10 @@ class PixldocsRenderer {
|
|
|
13039
13200
|
return;
|
|
13040
13201
|
}
|
|
13041
13202
|
exportCtx.save();
|
|
13042
|
-
exportCtx.scale(
|
|
13203
|
+
exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
|
|
13043
13204
|
this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
|
|
13044
13205
|
exportCtx.restore();
|
|
13045
|
-
exportCtx.drawImage(
|
|
13206
|
+
exportCtx.drawImage(sourceCanvasAfter, 0, 0);
|
|
13046
13207
|
const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
|
|
13047
13208
|
const dataUrl = exportCanvas.toDataURL(mimeType, quality);
|
|
13048
13209
|
cleanup();
|
|
@@ -13053,7 +13214,7 @@ class PixldocsRenderer {
|
|
|
13053
13214
|
}
|
|
13054
13215
|
});
|
|
13055
13216
|
};
|
|
13056
|
-
|
|
13217
|
+
root = client.createRoot(container);
|
|
13057
13218
|
root.render(
|
|
13058
13219
|
react.createElement(PreviewCanvas2, {
|
|
13059
13220
|
config,
|
|
@@ -15533,9 +15694,11 @@ exports.PixldocsPreview = PixldocsPreview;
|
|
|
15533
15694
|
exports.PixldocsRenderer = PixldocsRenderer;
|
|
15534
15695
|
exports.applyThemeToConfig = applyThemeToConfig;
|
|
15535
15696
|
exports.assemblePdfFromSvgs = assemblePdfFromSvgs;
|
|
15697
|
+
exports.awaitFontsForConfig = awaitFontsForConfig;
|
|
15536
15698
|
exports.collectFontDescriptorsFromConfig = collectFontDescriptorsFromConfig;
|
|
15537
15699
|
exports.collectFontsFromConfig = collectFontsFromConfig;
|
|
15538
15700
|
exports.collectImageUrls = collectImageUrls;
|
|
15701
|
+
exports.configHasAutoShrinkText = configHasAutoShrinkText$1;
|
|
15539
15702
|
exports.embedFont = embedFont;
|
|
15540
15703
|
exports.embedFontsForConfig = embedFontsForConfig;
|
|
15541
15704
|
exports.embedFontsInPdf = embedFontsInPdf;
|