@pixldocs/canvas-renderer 0.5.56 → 0.5.57
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 +118 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +118 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -231,7 +231,7 @@ export declare function normalizeFontFamily(fontStack: string): string;
|
|
|
231
231
|
* Package version banner. Bump alongside package.json so we can confirm
|
|
232
232
|
* (via browser:log) that the deployed bundle matches the expected build.
|
|
233
233
|
*/
|
|
234
|
-
export declare const PACKAGE_VERSION = "0.5.
|
|
234
|
+
export declare const PACKAGE_VERSION = "0.5.57";
|
|
235
235
|
|
|
236
236
|
export declare interface PageSettings {
|
|
237
237
|
backgroundColor?: string;
|
|
@@ -391,6 +391,18 @@ export declare class PixldocsRenderer {
|
|
|
391
391
|
private waitForCanvasImages;
|
|
392
392
|
private waitForCanvasScene;
|
|
393
393
|
private waitForRelevantFonts;
|
|
394
|
+
/**
|
|
395
|
+
* Block until the webfonts referenced by `config` have actually loaded
|
|
396
|
+
* (or `maxWaitMs` elapses). Used by the headless capture path BEFORE
|
|
397
|
+
* mounting `PreviewCanvas`, so the synchronous `createText` auto-shrink
|
|
398
|
+
* loop measures against final font metrics instead of fallback ones.
|
|
399
|
+
*
|
|
400
|
+
* Stronger than `ensureFontsForResolvedConfig` (which is fire-and-forget)
|
|
401
|
+
* — this awaits each `document.fonts.load(spec)` AND `document.fonts.ready`,
|
|
402
|
+
* racing the whole thing against `maxWaitMs` so a slow CDN can't hang the
|
|
403
|
+
* renderer.
|
|
404
|
+
*/
|
|
405
|
+
private awaitFontsForConfig;
|
|
394
406
|
private getNormalizedGradientStops;
|
|
395
407
|
private paintPageBackground;
|
|
396
408
|
private renderPageViaPreviewCanvas;
|
|
@@ -447,6 +459,21 @@ export declare interface RenderOptions {
|
|
|
447
459
|
scale?: number;
|
|
448
460
|
/** Custom pixel ratio override */
|
|
449
461
|
pixelRatio?: number;
|
|
462
|
+
/**
|
|
463
|
+
* If true, skip the blocking font-ready wait before mounting the headless
|
|
464
|
+
* PreviewCanvas. Default: `false`. Setting this to `true` makes capture
|
|
465
|
+
* faster but can cause `overflowPolicy: 'auto-shrink'` text to overflow
|
|
466
|
+
* when the real webfont loads after auto-shrink has already measured
|
|
467
|
+
* against fallback metrics.
|
|
468
|
+
*/
|
|
469
|
+
skipFontReadyWait?: boolean;
|
|
470
|
+
/**
|
|
471
|
+
* Maximum time (ms) to wait for `document.fonts.load()` per descriptor
|
|
472
|
+
* before mounting PreviewCanvas. Default: `4000` for configs that contain
|
|
473
|
+
* any `auto-shrink` text (correctness matters), `1800` otherwise. Only
|
|
474
|
+
* applies when `skipFontReadyWait` is false.
|
|
475
|
+
*/
|
|
476
|
+
waitForFontsMs?: number;
|
|
450
477
|
}
|
|
451
478
|
|
|
452
479
|
export declare interface RenderResult {
|
package/dist/index.js
CHANGED
|
@@ -12452,7 +12452,7 @@ function PixldocsPreview(props) {
|
|
|
12452
12452
|
!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..." }) })
|
|
12453
12453
|
] });
|
|
12454
12454
|
}
|
|
12455
|
-
const PACKAGE_VERSION = "0.5.
|
|
12455
|
+
const PACKAGE_VERSION = "0.5.57";
|
|
12456
12456
|
let __underlineFixInstalled = false;
|
|
12457
12457
|
function installUnderlineFix(fab) {
|
|
12458
12458
|
var _a;
|
|
@@ -12549,6 +12549,22 @@ function installUnderlineFix(fab) {
|
|
|
12549
12549
|
__underlineFixInstalled = true;
|
|
12550
12550
|
console.log(`[canvas-renderer] underline-fix monkey patch installed (v${PACKAGE_VERSION})`);
|
|
12551
12551
|
}
|
|
12552
|
+
function configHasAutoShrinkText(config) {
|
|
12553
|
+
var _a;
|
|
12554
|
+
if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
|
|
12555
|
+
const walk = (nodes) => {
|
|
12556
|
+
for (const node of nodes || []) {
|
|
12557
|
+
if (!node) continue;
|
|
12558
|
+
if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
|
|
12559
|
+
if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
|
|
12560
|
+
}
|
|
12561
|
+
return false;
|
|
12562
|
+
};
|
|
12563
|
+
for (const page of config.pages) {
|
|
12564
|
+
if (walk(page.children || [])) return true;
|
|
12565
|
+
}
|
|
12566
|
+
return false;
|
|
12567
|
+
}
|
|
12552
12568
|
class PixldocsRenderer {
|
|
12553
12569
|
constructor(config) {
|
|
12554
12570
|
__publicField(this, "config");
|
|
@@ -12575,6 +12591,11 @@ class PixldocsRenderer {
|
|
|
12575
12591
|
throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
|
|
12576
12592
|
}
|
|
12577
12593
|
await ensureFontsForResolvedConfig(templateConfig);
|
|
12594
|
+
if (!options.skipFontReadyWait) {
|
|
12595
|
+
const hasAutoShrink = configHasAutoShrinkText(templateConfig);
|
|
12596
|
+
const defaultWait = hasAutoShrink ? 4e3 : 1800;
|
|
12597
|
+
await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
|
|
12598
|
+
}
|
|
12578
12599
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12579
12600
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12580
12601
|
const dataUrl = await this.renderPageViaPreviewCanvas(
|
|
@@ -12582,7 +12603,8 @@ class PixldocsRenderer {
|
|
|
12582
12603
|
pageIndex,
|
|
12583
12604
|
pixelRatio,
|
|
12584
12605
|
format,
|
|
12585
|
-
quality
|
|
12606
|
+
quality,
|
|
12607
|
+
{ skipFontReadyWait: options.skipFontReadyWait, waitForFontsMs: options.waitForFontsMs }
|
|
12586
12608
|
);
|
|
12587
12609
|
return {
|
|
12588
12610
|
dataUrl,
|
|
@@ -12596,9 +12618,14 @@ class PixldocsRenderer {
|
|
|
12596
12618
|
* Render all pages and return array of results.
|
|
12597
12619
|
*/
|
|
12598
12620
|
async renderAllPages(templateConfig, options = {}) {
|
|
12621
|
+
if (!options.skipFontReadyWait) {
|
|
12622
|
+
const hasAutoShrink = configHasAutoShrinkText(templateConfig);
|
|
12623
|
+
const defaultWait = hasAutoShrink ? 4e3 : 1800;
|
|
12624
|
+
await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
|
|
12625
|
+
}
|
|
12599
12626
|
const results = [];
|
|
12600
12627
|
for (let i = 0; i < templateConfig.pages.length; i++) {
|
|
12601
|
-
results.push(await this.render(templateConfig, { ...options, pageIndex: i }));
|
|
12628
|
+
results.push(await this.render(templateConfig, { ...options, pageIndex: i, skipFontReadyWait: true }));
|
|
12602
12629
|
}
|
|
12603
12630
|
return results;
|
|
12604
12631
|
}
|
|
@@ -12635,6 +12662,8 @@ class PixldocsRenderer {
|
|
|
12635
12662
|
throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
|
|
12636
12663
|
}
|
|
12637
12664
|
await ensureFontsForResolvedConfig(templateConfig);
|
|
12665
|
+
const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
|
|
12666
|
+
await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
|
|
12638
12667
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12639
12668
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12640
12669
|
const canvasWidth = templateConfig.canvas.width;
|
|
@@ -12646,6 +12675,8 @@ class PixldocsRenderer {
|
|
|
12646
12675
|
*/
|
|
12647
12676
|
async renderAllPageSvgs(templateConfig) {
|
|
12648
12677
|
await ensureFontsForResolvedConfig(templateConfig);
|
|
12678
|
+
const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
|
|
12679
|
+
await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
|
|
12649
12680
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12650
12681
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12651
12682
|
const results = [];
|
|
@@ -12898,6 +12929,26 @@ class PixldocsRenderer {
|
|
|
12898
12929
|
]);
|
|
12899
12930
|
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
|
|
12900
12931
|
}
|
|
12932
|
+
/**
|
|
12933
|
+
* Block until the webfonts referenced by `config` have actually loaded
|
|
12934
|
+
* (or `maxWaitMs` elapses). Used by the headless capture path BEFORE
|
|
12935
|
+
* mounting `PreviewCanvas`, so the synchronous `createText` auto-shrink
|
|
12936
|
+
* loop measures against final font metrics instead of fallback ones.
|
|
12937
|
+
*
|
|
12938
|
+
* Stronger than `ensureFontsForResolvedConfig` (which is fire-and-forget)
|
|
12939
|
+
* — this awaits each `document.fonts.load(spec)` AND `document.fonts.ready`,
|
|
12940
|
+
* racing the whole thing against `maxWaitMs` so a slow CDN can't hang the
|
|
12941
|
+
* renderer.
|
|
12942
|
+
*/
|
|
12943
|
+
async awaitFontsForConfig(config, maxWaitMs) {
|
|
12944
|
+
if (typeof document === "undefined" || !document.fonts) return;
|
|
12945
|
+
void ensureFontsForResolvedConfig(config);
|
|
12946
|
+
await this.waitForRelevantFonts(config, maxWaitMs);
|
|
12947
|
+
await Promise.race([
|
|
12948
|
+
document.fonts.ready.catch(() => void 0).then(() => void 0),
|
|
12949
|
+
new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
|
|
12950
|
+
]);
|
|
12951
|
+
}
|
|
12901
12952
|
getNormalizedGradientStops(gradient) {
|
|
12902
12953
|
const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
|
|
12903
12954
|
offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
|
|
@@ -12971,10 +13022,19 @@ class PixldocsRenderer {
|
|
|
12971
13022
|
} catch {
|
|
12972
13023
|
}
|
|
12973
13024
|
}
|
|
12974
|
-
async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality) {
|
|
13025
|
+
async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
|
|
12975
13026
|
const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
|
|
12976
13027
|
const canvasWidth = config.canvas.width;
|
|
12977
13028
|
const canvasHeight = config.canvas.height;
|
|
13029
|
+
const hasAutoShrink = configHasAutoShrinkText(config);
|
|
13030
|
+
let firstMountSettled = false;
|
|
13031
|
+
let lateFontSettleDetected = false;
|
|
13032
|
+
if (typeof document !== "undefined" && document.fonts && hasAutoShrink) {
|
|
13033
|
+
document.fonts.ready.then(() => {
|
|
13034
|
+
if (firstMountSettled) lateFontSettleDetected = true;
|
|
13035
|
+
}).catch(() => {
|
|
13036
|
+
});
|
|
13037
|
+
}
|
|
12978
13038
|
return new Promise((resolve, reject) => {
|
|
12979
13039
|
const container = document.createElement("div");
|
|
12980
13040
|
container.style.cssText = `
|
|
@@ -12987,6 +13047,8 @@ class PixldocsRenderer {
|
|
|
12987
13047
|
cleanup();
|
|
12988
13048
|
reject(new Error("Render timeout (30s)"));
|
|
12989
13049
|
}, 3e4);
|
|
13050
|
+
let root;
|
|
13051
|
+
let mountKey = 0;
|
|
12990
13052
|
const cleanup = () => {
|
|
12991
13053
|
clearTimeout(timeout);
|
|
12992
13054
|
try {
|
|
@@ -12995,6 +13057,46 @@ class PixldocsRenderer {
|
|
|
12995
13057
|
}
|
|
12996
13058
|
container.remove();
|
|
12997
13059
|
};
|
|
13060
|
+
const remountWithFreshKey = async () => {
|
|
13061
|
+
mountKey += 1;
|
|
13062
|
+
try {
|
|
13063
|
+
clearMeasurementCache();
|
|
13064
|
+
} catch {
|
|
13065
|
+
}
|
|
13066
|
+
try {
|
|
13067
|
+
clearFabricCharCache();
|
|
13068
|
+
} catch {
|
|
13069
|
+
}
|
|
13070
|
+
try {
|
|
13071
|
+
root.unmount();
|
|
13072
|
+
} catch {
|
|
13073
|
+
}
|
|
13074
|
+
root = createRoot(container);
|
|
13075
|
+
await new Promise((settle) => {
|
|
13076
|
+
const onReadyOnce = () => {
|
|
13077
|
+
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
13078
|
+
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
13079
|
+
const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
|
|
13080
|
+
await this.waitForCanvasImages(container, expectedImageCount);
|
|
13081
|
+
await this.waitForStableTextMetrics(container, config);
|
|
13082
|
+
await this.waitForCanvasScene(container, config, pageIndex);
|
|
13083
|
+
if (!fabricInstance) return settle();
|
|
13084
|
+
settle();
|
|
13085
|
+
}).catch(() => settle());
|
|
13086
|
+
};
|
|
13087
|
+
root.render(
|
|
13088
|
+
createElement(PreviewCanvas2, {
|
|
13089
|
+
key: `remount-${mountKey}`,
|
|
13090
|
+
config,
|
|
13091
|
+
pageIndex,
|
|
13092
|
+
zoom: pixelRatio,
|
|
13093
|
+
absoluteZoom: true,
|
|
13094
|
+
skipFontReadyWait: false,
|
|
13095
|
+
onReady: onReadyOnce
|
|
13096
|
+
})
|
|
13097
|
+
);
|
|
13098
|
+
});
|
|
13099
|
+
};
|
|
12998
13100
|
const onReady = () => {
|
|
12999
13101
|
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
13000
13102
|
try {
|
|
@@ -13003,16 +13105,23 @@ class PixldocsRenderer {
|
|
|
13003
13105
|
await this.waitForCanvasImages(container, expectedImageCount);
|
|
13004
13106
|
await this.waitForStableTextMetrics(container, config);
|
|
13005
13107
|
await this.waitForCanvasScene(container, config, pageIndex);
|
|
13108
|
+
firstMountSettled = true;
|
|
13109
|
+
if (hasAutoShrink && lateFontSettleDetected) {
|
|
13110
|
+
console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
|
|
13111
|
+
await remountWithFreshKey();
|
|
13112
|
+
}
|
|
13006
13113
|
const fabricCanvas = container.querySelector("canvas.upper-canvas, canvas");
|
|
13007
13114
|
const sourceCanvas = (fabricInstance == null ? void 0 : fabricInstance.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || fabricCanvas;
|
|
13115
|
+
const fabricInstanceAfter = this.getFabricCanvasFromContainer(container) || fabricInstance;
|
|
13116
|
+
const sourceCanvasAfter = (fabricInstanceAfter == null ? void 0 : fabricInstanceAfter.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || sourceCanvas;
|
|
13008
13117
|
if (!sourceCanvas) {
|
|
13009
13118
|
cleanup();
|
|
13010
13119
|
reject(new Error("No canvas element found after render"));
|
|
13011
13120
|
return;
|
|
13012
13121
|
}
|
|
13013
13122
|
const exportCanvas = document.createElement("canvas");
|
|
13014
|
-
exportCanvas.width =
|
|
13015
|
-
exportCanvas.height =
|
|
13123
|
+
exportCanvas.width = sourceCanvasAfter.width;
|
|
13124
|
+
exportCanvas.height = sourceCanvasAfter.height;
|
|
13016
13125
|
const exportCtx = exportCanvas.getContext("2d");
|
|
13017
13126
|
if (!exportCtx) {
|
|
13018
13127
|
cleanup();
|
|
@@ -13020,10 +13129,10 @@ class PixldocsRenderer {
|
|
|
13020
13129
|
return;
|
|
13021
13130
|
}
|
|
13022
13131
|
exportCtx.save();
|
|
13023
|
-
exportCtx.scale(
|
|
13132
|
+
exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
|
|
13024
13133
|
this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
|
|
13025
13134
|
exportCtx.restore();
|
|
13026
|
-
exportCtx.drawImage(
|
|
13135
|
+
exportCtx.drawImage(sourceCanvasAfter, 0, 0);
|
|
13027
13136
|
const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
|
|
13028
13137
|
const dataUrl = exportCanvas.toDataURL(mimeType, quality);
|
|
13029
13138
|
cleanup();
|
|
@@ -13034,7 +13143,7 @@ class PixldocsRenderer {
|
|
|
13034
13143
|
}
|
|
13035
13144
|
});
|
|
13036
13145
|
};
|
|
13037
|
-
|
|
13146
|
+
root = createRoot(container);
|
|
13038
13147
|
root.render(
|
|
13039
13148
|
createElement(PreviewCanvas2, {
|
|
13040
13149
|
config,
|