@pixldocs/canvas-renderer 0.5.55 → 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 +164 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +36 -1
- package/dist/index.js +164 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5289,6 +5289,10 @@ function createText(element) {
|
|
|
5289
5289
|
const splitByGrapheme = overflowPolicy === "auto-shrink" ? false : element.splitByGrapheme ?? element.wordWrap === "break-word";
|
|
5290
5290
|
if (overflowPolicy === "auto-shrink") {
|
|
5291
5291
|
const explicitLineCount = Math.max(1, text.split("\n").length);
|
|
5292
|
+
const debugAutoShrink = typeof window !== "undefined" && window.__pixldocsDebugAutoShrink === true;
|
|
5293
|
+
const startFontSize = fontSize;
|
|
5294
|
+
let breakReason = "min-font-size-reached";
|
|
5295
|
+
let lastIter = null;
|
|
5292
5296
|
while (fontSize > 1) {
|
|
5293
5297
|
const testTextbox = new fabric__namespace.Textbox(text, {
|
|
5294
5298
|
width: fixedWidth,
|
|
@@ -5310,11 +5314,47 @@ function createText(element) {
|
|
|
5310
5314
|
const lineWidths = testTextbox.__lineWidths;
|
|
5311
5315
|
const maxLineWidth = lineWidths && lineWidths.length > 0 ? Math.max(...lineWidths) : 0;
|
|
5312
5316
|
const fitsWidth = !widthDidGrow && maxLineWidth <= fixedWidth + 1;
|
|
5317
|
+
if (debugAutoShrink) {
|
|
5318
|
+
lastIter = {
|
|
5319
|
+
fontSize,
|
|
5320
|
+
renderedLineCount,
|
|
5321
|
+
explicitLineCount,
|
|
5322
|
+
textHeight,
|
|
5323
|
+
maxLineWidth,
|
|
5324
|
+
fixedWidth,
|
|
5325
|
+
widthDidGrow,
|
|
5326
|
+
hasNoImplicitWrap,
|
|
5327
|
+
fitsHeight,
|
|
5328
|
+
fitsWidth
|
|
5329
|
+
};
|
|
5330
|
+
}
|
|
5313
5331
|
if (hasNoImplicitWrap && fitsHeight && fitsWidth) {
|
|
5332
|
+
breakReason = "fits";
|
|
5314
5333
|
break;
|
|
5315
5334
|
}
|
|
5316
5335
|
fontSize--;
|
|
5317
5336
|
}
|
|
5337
|
+
if (debugAutoShrink) {
|
|
5338
|
+
console.log("[auto-shrink][diag]", {
|
|
5339
|
+
id: element.id,
|
|
5340
|
+
name: element.name,
|
|
5341
|
+
text,
|
|
5342
|
+
fontFamily: element.fontFamily,
|
|
5343
|
+
fontWeight: element.fontWeight,
|
|
5344
|
+
elementWidth: element.width,
|
|
5345
|
+
elementHeight: element.height,
|
|
5346
|
+
scaleX: element.scaleX ?? 1,
|
|
5347
|
+
scaleY: element.scaleY ?? 1,
|
|
5348
|
+
fixedWidth,
|
|
5349
|
+
baseHeight,
|
|
5350
|
+
startFontSize,
|
|
5351
|
+
finalFontSize: fontSize,
|
|
5352
|
+
breakReason,
|
|
5353
|
+
lastIter,
|
|
5354
|
+
fontCheckRegular: typeof document !== "undefined" && document.fonts ? document.fonts.check(`16px "${element.fontFamily || "Open Sans"}"`) : null,
|
|
5355
|
+
fontCheckBold: typeof document !== "undefined" && document.fonts ? document.fonts.check(`bold 16px "${element.fontFamily || "Open Sans"}"`) : null
|
|
5356
|
+
});
|
|
5357
|
+
}
|
|
5318
5358
|
}
|
|
5319
5359
|
if (overflowPolicy === "max-lines-ellipsis") {
|
|
5320
5360
|
const originalText = element.text || "Text";
|
|
@@ -12431,7 +12471,7 @@ function PixldocsPreview(props) {
|
|
|
12431
12471
|
!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..." }) })
|
|
12432
12472
|
] });
|
|
12433
12473
|
}
|
|
12434
|
-
const PACKAGE_VERSION = "0.5.
|
|
12474
|
+
const PACKAGE_VERSION = "0.5.57";
|
|
12435
12475
|
let __underlineFixInstalled = false;
|
|
12436
12476
|
function installUnderlineFix(fab) {
|
|
12437
12477
|
var _a;
|
|
@@ -12528,6 +12568,22 @@ function installUnderlineFix(fab) {
|
|
|
12528
12568
|
__underlineFixInstalled = true;
|
|
12529
12569
|
console.log(`[canvas-renderer] underline-fix monkey patch installed (v${PACKAGE_VERSION})`);
|
|
12530
12570
|
}
|
|
12571
|
+
function configHasAutoShrinkText(config) {
|
|
12572
|
+
var _a;
|
|
12573
|
+
if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
|
|
12574
|
+
const walk = (nodes) => {
|
|
12575
|
+
for (const node of nodes || []) {
|
|
12576
|
+
if (!node) continue;
|
|
12577
|
+
if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
|
|
12578
|
+
if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
|
|
12579
|
+
}
|
|
12580
|
+
return false;
|
|
12581
|
+
};
|
|
12582
|
+
for (const page of config.pages) {
|
|
12583
|
+
if (walk(page.children || [])) return true;
|
|
12584
|
+
}
|
|
12585
|
+
return false;
|
|
12586
|
+
}
|
|
12531
12587
|
class PixldocsRenderer {
|
|
12532
12588
|
constructor(config) {
|
|
12533
12589
|
__publicField(this, "config");
|
|
@@ -12554,6 +12610,11 @@ class PixldocsRenderer {
|
|
|
12554
12610
|
throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
|
|
12555
12611
|
}
|
|
12556
12612
|
await ensureFontsForResolvedConfig(templateConfig);
|
|
12613
|
+
if (!options.skipFontReadyWait) {
|
|
12614
|
+
const hasAutoShrink = configHasAutoShrinkText(templateConfig);
|
|
12615
|
+
const defaultWait = hasAutoShrink ? 4e3 : 1800;
|
|
12616
|
+
await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
|
|
12617
|
+
}
|
|
12557
12618
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12558
12619
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12559
12620
|
const dataUrl = await this.renderPageViaPreviewCanvas(
|
|
@@ -12561,7 +12622,8 @@ class PixldocsRenderer {
|
|
|
12561
12622
|
pageIndex,
|
|
12562
12623
|
pixelRatio,
|
|
12563
12624
|
format,
|
|
12564
|
-
quality
|
|
12625
|
+
quality,
|
|
12626
|
+
{ skipFontReadyWait: options.skipFontReadyWait, waitForFontsMs: options.waitForFontsMs }
|
|
12565
12627
|
);
|
|
12566
12628
|
return {
|
|
12567
12629
|
dataUrl,
|
|
@@ -12575,9 +12637,14 @@ class PixldocsRenderer {
|
|
|
12575
12637
|
* Render all pages and return array of results.
|
|
12576
12638
|
*/
|
|
12577
12639
|
async renderAllPages(templateConfig, options = {}) {
|
|
12640
|
+
if (!options.skipFontReadyWait) {
|
|
12641
|
+
const hasAutoShrink = configHasAutoShrinkText(templateConfig);
|
|
12642
|
+
const defaultWait = hasAutoShrink ? 4e3 : 1800;
|
|
12643
|
+
await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
|
|
12644
|
+
}
|
|
12578
12645
|
const results = [];
|
|
12579
12646
|
for (let i = 0; i < templateConfig.pages.length; i++) {
|
|
12580
|
-
results.push(await this.render(templateConfig, { ...options, pageIndex: i }));
|
|
12647
|
+
results.push(await this.render(templateConfig, { ...options, pageIndex: i, skipFontReadyWait: true }));
|
|
12581
12648
|
}
|
|
12582
12649
|
return results;
|
|
12583
12650
|
}
|
|
@@ -12614,6 +12681,8 @@ class PixldocsRenderer {
|
|
|
12614
12681
|
throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
|
|
12615
12682
|
}
|
|
12616
12683
|
await ensureFontsForResolvedConfig(templateConfig);
|
|
12684
|
+
const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
|
|
12685
|
+
await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
|
|
12617
12686
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12618
12687
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12619
12688
|
const canvasWidth = templateConfig.canvas.width;
|
|
@@ -12625,6 +12694,8 @@ class PixldocsRenderer {
|
|
|
12625
12694
|
*/
|
|
12626
12695
|
async renderAllPageSvgs(templateConfig) {
|
|
12627
12696
|
await ensureFontsForResolvedConfig(templateConfig);
|
|
12697
|
+
const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
|
|
12698
|
+
await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
|
|
12628
12699
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12629
12700
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12630
12701
|
const results = [];
|
|
@@ -12877,6 +12948,26 @@ class PixldocsRenderer {
|
|
|
12877
12948
|
]);
|
|
12878
12949
|
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
|
|
12879
12950
|
}
|
|
12951
|
+
/**
|
|
12952
|
+
* Block until the webfonts referenced by `config` have actually loaded
|
|
12953
|
+
* (or `maxWaitMs` elapses). Used by the headless capture path BEFORE
|
|
12954
|
+
* mounting `PreviewCanvas`, so the synchronous `createText` auto-shrink
|
|
12955
|
+
* loop measures against final font metrics instead of fallback ones.
|
|
12956
|
+
*
|
|
12957
|
+
* Stronger than `ensureFontsForResolvedConfig` (which is fire-and-forget)
|
|
12958
|
+
* — this awaits each `document.fonts.load(spec)` AND `document.fonts.ready`,
|
|
12959
|
+
* racing the whole thing against `maxWaitMs` so a slow CDN can't hang the
|
|
12960
|
+
* renderer.
|
|
12961
|
+
*/
|
|
12962
|
+
async awaitFontsForConfig(config, maxWaitMs) {
|
|
12963
|
+
if (typeof document === "undefined" || !document.fonts) return;
|
|
12964
|
+
void ensureFontsForResolvedConfig(config);
|
|
12965
|
+
await this.waitForRelevantFonts(config, maxWaitMs);
|
|
12966
|
+
await Promise.race([
|
|
12967
|
+
document.fonts.ready.catch(() => void 0).then(() => void 0),
|
|
12968
|
+
new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
|
|
12969
|
+
]);
|
|
12970
|
+
}
|
|
12880
12971
|
getNormalizedGradientStops(gradient) {
|
|
12881
12972
|
const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
|
|
12882
12973
|
offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
|
|
@@ -12950,10 +13041,19 @@ class PixldocsRenderer {
|
|
|
12950
13041
|
} catch {
|
|
12951
13042
|
}
|
|
12952
13043
|
}
|
|
12953
|
-
async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality) {
|
|
13044
|
+
async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
|
|
12954
13045
|
const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
|
|
12955
13046
|
const canvasWidth = config.canvas.width;
|
|
12956
13047
|
const canvasHeight = config.canvas.height;
|
|
13048
|
+
const hasAutoShrink = configHasAutoShrinkText(config);
|
|
13049
|
+
let firstMountSettled = false;
|
|
13050
|
+
let lateFontSettleDetected = false;
|
|
13051
|
+
if (typeof document !== "undefined" && document.fonts && hasAutoShrink) {
|
|
13052
|
+
document.fonts.ready.then(() => {
|
|
13053
|
+
if (firstMountSettled) lateFontSettleDetected = true;
|
|
13054
|
+
}).catch(() => {
|
|
13055
|
+
});
|
|
13056
|
+
}
|
|
12957
13057
|
return new Promise((resolve, reject) => {
|
|
12958
13058
|
const container = document.createElement("div");
|
|
12959
13059
|
container.style.cssText = `
|
|
@@ -12966,6 +13066,8 @@ class PixldocsRenderer {
|
|
|
12966
13066
|
cleanup();
|
|
12967
13067
|
reject(new Error("Render timeout (30s)"));
|
|
12968
13068
|
}, 3e4);
|
|
13069
|
+
let root;
|
|
13070
|
+
let mountKey = 0;
|
|
12969
13071
|
const cleanup = () => {
|
|
12970
13072
|
clearTimeout(timeout);
|
|
12971
13073
|
try {
|
|
@@ -12974,6 +13076,46 @@ class PixldocsRenderer {
|
|
|
12974
13076
|
}
|
|
12975
13077
|
container.remove();
|
|
12976
13078
|
};
|
|
13079
|
+
const remountWithFreshKey = async () => {
|
|
13080
|
+
mountKey += 1;
|
|
13081
|
+
try {
|
|
13082
|
+
clearMeasurementCache();
|
|
13083
|
+
} catch {
|
|
13084
|
+
}
|
|
13085
|
+
try {
|
|
13086
|
+
clearFabricCharCache();
|
|
13087
|
+
} catch {
|
|
13088
|
+
}
|
|
13089
|
+
try {
|
|
13090
|
+
root.unmount();
|
|
13091
|
+
} catch {
|
|
13092
|
+
}
|
|
13093
|
+
root = client.createRoot(container);
|
|
13094
|
+
await new Promise((settle) => {
|
|
13095
|
+
const onReadyOnce = () => {
|
|
13096
|
+
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
13097
|
+
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
13098
|
+
const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
|
|
13099
|
+
await this.waitForCanvasImages(container, expectedImageCount);
|
|
13100
|
+
await this.waitForStableTextMetrics(container, config);
|
|
13101
|
+
await this.waitForCanvasScene(container, config, pageIndex);
|
|
13102
|
+
if (!fabricInstance) return settle();
|
|
13103
|
+
settle();
|
|
13104
|
+
}).catch(() => settle());
|
|
13105
|
+
};
|
|
13106
|
+
root.render(
|
|
13107
|
+
react.createElement(PreviewCanvas2, {
|
|
13108
|
+
key: `remount-${mountKey}`,
|
|
13109
|
+
config,
|
|
13110
|
+
pageIndex,
|
|
13111
|
+
zoom: pixelRatio,
|
|
13112
|
+
absoluteZoom: true,
|
|
13113
|
+
skipFontReadyWait: false,
|
|
13114
|
+
onReady: onReadyOnce
|
|
13115
|
+
})
|
|
13116
|
+
);
|
|
13117
|
+
});
|
|
13118
|
+
};
|
|
12977
13119
|
const onReady = () => {
|
|
12978
13120
|
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
12979
13121
|
try {
|
|
@@ -12982,16 +13124,23 @@ class PixldocsRenderer {
|
|
|
12982
13124
|
await this.waitForCanvasImages(container, expectedImageCount);
|
|
12983
13125
|
await this.waitForStableTextMetrics(container, config);
|
|
12984
13126
|
await this.waitForCanvasScene(container, config, pageIndex);
|
|
13127
|
+
firstMountSettled = true;
|
|
13128
|
+
if (hasAutoShrink && lateFontSettleDetected) {
|
|
13129
|
+
console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
|
|
13130
|
+
await remountWithFreshKey();
|
|
13131
|
+
}
|
|
12985
13132
|
const fabricCanvas = container.querySelector("canvas.upper-canvas, canvas");
|
|
12986
13133
|
const sourceCanvas = (fabricInstance == null ? void 0 : fabricInstance.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || fabricCanvas;
|
|
13134
|
+
const fabricInstanceAfter = this.getFabricCanvasFromContainer(container) || fabricInstance;
|
|
13135
|
+
const sourceCanvasAfter = (fabricInstanceAfter == null ? void 0 : fabricInstanceAfter.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || sourceCanvas;
|
|
12987
13136
|
if (!sourceCanvas) {
|
|
12988
13137
|
cleanup();
|
|
12989
13138
|
reject(new Error("No canvas element found after render"));
|
|
12990
13139
|
return;
|
|
12991
13140
|
}
|
|
12992
13141
|
const exportCanvas = document.createElement("canvas");
|
|
12993
|
-
exportCanvas.width =
|
|
12994
|
-
exportCanvas.height =
|
|
13142
|
+
exportCanvas.width = sourceCanvasAfter.width;
|
|
13143
|
+
exportCanvas.height = sourceCanvasAfter.height;
|
|
12995
13144
|
const exportCtx = exportCanvas.getContext("2d");
|
|
12996
13145
|
if (!exportCtx) {
|
|
12997
13146
|
cleanup();
|
|
@@ -12999,10 +13148,10 @@ class PixldocsRenderer {
|
|
|
12999
13148
|
return;
|
|
13000
13149
|
}
|
|
13001
13150
|
exportCtx.save();
|
|
13002
|
-
exportCtx.scale(
|
|
13151
|
+
exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
|
|
13003
13152
|
this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
|
|
13004
13153
|
exportCtx.restore();
|
|
13005
|
-
exportCtx.drawImage(
|
|
13154
|
+
exportCtx.drawImage(sourceCanvasAfter, 0, 0);
|
|
13006
13155
|
const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
|
|
13007
13156
|
const dataUrl = exportCanvas.toDataURL(mimeType, quality);
|
|
13008
13157
|
cleanup();
|
|
@@ -13013,7 +13162,7 @@ class PixldocsRenderer {
|
|
|
13013
13162
|
}
|
|
13014
13163
|
});
|
|
13015
13164
|
};
|
|
13016
|
-
|
|
13165
|
+
root = client.createRoot(container);
|
|
13017
13166
|
root.render(
|
|
13018
13167
|
react.createElement(PreviewCanvas2, {
|
|
13019
13168
|
config,
|
|
@@ -15480,6 +15629,11 @@ async function warmTemplateFromForm(options) {
|
|
|
15480
15629
|
if (signal == null ? void 0 : signal.aborted) return;
|
|
15481
15630
|
await warmResolvedTemplateForPreview(resolved.config, { signal, imageProxyUrl });
|
|
15482
15631
|
}
|
|
15632
|
+
function setAutoShrinkDebug(enabled) {
|
|
15633
|
+
if (typeof window !== "undefined") {
|
|
15634
|
+
window.__pixldocsDebugAutoShrink = !!enabled;
|
|
15635
|
+
}
|
|
15636
|
+
}
|
|
15483
15637
|
exports.FONT_FALLBACK_DEVANAGARI = FONT_FALLBACK_DEVANAGARI;
|
|
15484
15638
|
exports.FONT_FALLBACK_SYMBOLS = FONT_FALLBACK_SYMBOLS;
|
|
15485
15639
|
exports.FONT_FILES = FONT_FILES;
|
|
@@ -15507,6 +15661,7 @@ exports.resolveFontWeight = resolveFontWeight;
|
|
|
15507
15661
|
exports.resolveFromForm = resolveFromForm;
|
|
15508
15662
|
exports.resolveTemplateData = resolveTemplateData;
|
|
15509
15663
|
exports.rewriteSvgFontsForJsPDF = rewriteSvgFontsForJsPDF;
|
|
15664
|
+
exports.setAutoShrinkDebug = setAutoShrinkDebug;
|
|
15510
15665
|
exports.setBundledAssetPrefixes = setBundledAssetPrefixes;
|
|
15511
15666
|
exports.warmResolvedTemplateForPreview = warmResolvedTemplateForPreview;
|
|
15512
15667
|
exports.warmTemplateFromForm = warmTemplateFromForm;
|