@pixldocs/canvas-renderer 0.5.167 → 0.5.168

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.
@@ -330,11 +330,12 @@ function getCacheKey(element) {
330
330
  scaleY: element.scaleY,
331
331
  splitByGrapheme: element.splitByGrapheme,
332
332
  overflowPolicy: element.overflowPolicy,
333
- height: element.overflowPolicy === "auto-shrink" ? element.height : void 0
333
+ height: element.overflowPolicy === "auto-shrink" ? element.height : void 0,
334
+ minBoxHeight: element.minBoxHeight,
335
+ verticalAlign: element.verticalAlign
334
336
  });
335
337
  }
336
338
  function measureTextHeight(element) {
337
- var _a;
338
339
  if (element.type !== "text") {
339
340
  return element.height || 20;
340
341
  }
@@ -350,8 +351,8 @@ function measureTextHeight(element) {
350
351
  let fontSize = element.fontSize || 16;
351
352
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
352
353
  if (overflowPolicy === "auto-shrink") {
353
- const baseHeight = element.height;
354
- const explicitLineCount = Math.max(1, textToMeasure.split("\n").length);
354
+ const minBoxH = Math.max(0, Number(element.minBoxHeight) || 0);
355
+ const baseHeight = typeof element.height === "number" ? Math.max(element.height, minBoxH) : minBoxH > 0 ? minBoxH : element.height;
355
356
  while (fontSize > 1) {
356
357
  const testTb = new fabric.Textbox(textToMeasure, {
357
358
  width: measureWidth,
@@ -365,11 +366,9 @@ function measureTextHeight(element) {
365
366
  });
366
367
  testTb.initDimensions();
367
368
  const textHeight = testTb.height || 0;
368
- const renderedLineCount = ((_a = testTb.textLines) == null ? void 0 : _a.length) || 1;
369
- const hasNoImplicitWrap = renderedLineCount <= explicitLineCount;
370
369
  const fitsHeight = !baseHeight || textHeight <= baseHeight;
371
370
  const { fitsWidth } = getTextboxWidthFitMetrics(testTb, measureWidth);
372
- if (hasNoImplicitWrap && fitsHeight && fitsWidth) break;
371
+ if (fitsHeight && fitsWidth) break;
373
372
  fontSize--;
374
373
  }
375
374
  const finalTb = new fabric.Textbox(textToMeasure, {
@@ -384,7 +383,7 @@ function measureTextHeight(element) {
384
383
  });
385
384
  finalTb.initDimensions();
386
385
  const measuredH = (finalTb.height || element.height || 20) * (element.scaleY || 1);
387
- const cappedH = typeof baseHeight === "number" ? Math.min(baseHeight * (element.scaleY || 1), measuredH) : measuredH;
386
+ const cappedH = typeof baseHeight === "number" ? Math.max(baseHeight * (element.scaleY || 1), measuredH) : measuredH;
388
387
  heightCache.set(cacheKey, { height: cappedH, timestamp: Date.now() });
389
388
  return cappedH;
390
389
  }
@@ -5532,6 +5531,54 @@ function calculateScaleSnapGuides(scalingObj, corner, canvas, canvasWidth, canva
5532
5531
  return true;
5533
5532
  });
5534
5533
  }
5534
+ const TextboxProto = fabric.Textbox.prototype;
5535
+ if (!TextboxProto.__pixldocsOrigCalcTextHeight) {
5536
+ TextboxProto.__pixldocsOrigCalcTextHeight = TextboxProto.calcTextHeight;
5537
+ TextboxProto.calcTextHeight = function() {
5538
+ const orig = TextboxProto.__pixldocsOrigCalcTextHeight.call(this);
5539
+ this._contentHeight = orig;
5540
+ const min = this.minBoxHeight || 0;
5541
+ return min > orig ? min : orig;
5542
+ };
5543
+ }
5544
+ if (!TextboxProto.__pixldocsOrigGetTopOffset) {
5545
+ TextboxProto.__pixldocsOrigGetTopOffset = TextboxProto._getTopOffset;
5546
+ TextboxProto._getTopOffset = function() {
5547
+ const baseOffset = TextboxProto.__pixldocsOrigGetTopOffset.call(this);
5548
+ const valign = this.verticalAlign || "top";
5549
+ if (valign === "top") return baseOffset;
5550
+ const content = typeof this._contentHeight === "number" ? this._contentHeight : TextboxProto.__pixldocsOrigCalcTextHeight.call(this);
5551
+ const padding = (this.height || 0) - content;
5552
+ if (padding <= 0) return baseOffset;
5553
+ if (valign === "middle") return baseOffset + padding / 2;
5554
+ if (valign === "bottom") return baseOffset + padding;
5555
+ return baseOffset;
5556
+ };
5557
+ }
5558
+ if (TextboxProto._getSVGLeftTopOffsets && !TextboxProto.__pixldocsOrigGetSVGLeftTopOffsets) {
5559
+ TextboxProto.__pixldocsOrigGetSVGLeftTopOffsets = TextboxProto._getSVGLeftTopOffsets;
5560
+ TextboxProto._getSVGLeftTopOffsets = function() {
5561
+ const base = TextboxProto.__pixldocsOrigGetSVGLeftTopOffsets.call(this);
5562
+ const valign = this.verticalAlign || "top";
5563
+ if (valign === "top") return base;
5564
+ const content = typeof this._contentHeight === "number" ? this._contentHeight : TextboxProto.__pixldocsOrigCalcTextHeight.call(this);
5565
+ const padding = (this.height || 0) - content;
5566
+ if (padding <= 0) return base;
5567
+ const extra = valign === "middle" ? padding / 2 : padding;
5568
+ return { ...base, textTop: base.textTop + extra };
5569
+ };
5570
+ }
5571
+ const stateProps = fabric.Textbox.prototype.stateProperties;
5572
+ if (Array.isArray(stateProps)) {
5573
+ if (!stateProps.includes("minBoxHeight")) stateProps.push("minBoxHeight");
5574
+ if (!stateProps.includes("verticalAlign")) stateProps.push("verticalAlign");
5575
+ }
5576
+ const cacheProps = fabric.Textbox.prototype.cacheProperties;
5577
+ if (Array.isArray(cacheProps)) {
5578
+ if (!cacheProps.includes("minBoxHeight")) cacheProps.push("minBoxHeight");
5579
+ if (!cacheProps.includes("verticalAlign")) cacheProps.push("verticalAlign");
5580
+ }
5581
+ TextboxProto.__pixldocsTextboxExtended = true;
5535
5582
  const PD_BG_KEY = "__pdBg";
5536
5583
  const PATCHED_KEY = "__pdBgPatched";
5537
5584
  function extractTextBgConfig(element) {
@@ -6351,7 +6398,7 @@ function createText(element) {
6351
6398
  iterationSamples.push(lastIter);
6352
6399
  }
6353
6400
  }
6354
- if (hasNoImplicitWrap && fitsHeight && fitsWidth) {
6401
+ if (fitsHeight && fitsWidth) {
6355
6402
  breakReason = "fits";
6356
6403
  break;
6357
6404
  }
@@ -6444,7 +6491,15 @@ function createText(element) {
6444
6491
  // formatting tokens (**, __, [c=...], etc). Disable inline edit and steer
6445
6492
  // users to the right-panel text field which exposes the raw markdown.
6446
6493
  editable: !formattingEnabled,
6447
- ...formattingEnabled ? { styles: parsedStyles } : element.styles ? { styles: element.styles } : {}
6494
+ ...formattingEnabled ? { styles: parsedStyles } : element.styles ? { styles: element.styles } : {},
6495
+ // Vertical sizing extensions (see fabricTextboxExtensions.ts). Apply for
6496
+ // every overflow policy — in auto-shrink mode `minBoxHeight` acts as a
6497
+ // visual floor so the box renders at the user's chosen height even after
6498
+ // the font shrinks to fit. PageCanvas's auto-shrink loop already uses the
6499
+ // same value as a fit-target, so the rendered box and the shrink target
6500
+ // stay in sync (parity with the Use page / EC2 renderer).
6501
+ ...(element.minBoxHeight ?? 0) > 0 ? { minBoxHeight: element.minBoxHeight } : {},
6502
+ verticalAlign: element.verticalAlign || "top"
6448
6503
  });
6449
6504
  textbox.__formattingEnabled = formattingEnabled;
6450
6505
  textbox.initDimensions();
@@ -8039,6 +8094,22 @@ const PageCanvas = forwardRef(
8039
8094
  obj.dirty = true;
8040
8095
  }
8041
8096
  }
8097
+ if (obj instanceof fabric.Textbox) {
8098
+ const sy = obj.scaleY ?? 1;
8099
+ if (Math.abs(sy - 1) > 1e-3) {
8100
+ const center = obj.getCenterPoint();
8101
+ const newMinH = Math.max(0, (obj.height ?? 0) * Math.abs(sy));
8102
+ obj.minBoxHeight = newMinH;
8103
+ obj.scaleY = 1;
8104
+ try {
8105
+ obj.initDimensions();
8106
+ } catch {
8107
+ }
8108
+ obj.setPositionByOrigin(center, "center", "center");
8109
+ obj.setCoords();
8110
+ obj.dirty = true;
8111
+ }
8112
+ }
8042
8113
  if (obj.__lockScaleDuringCrop || obj.__cropDrag) {
8043
8114
  obj.set({ scaleX: 1, scaleY: 1 });
8044
8115
  obj.setCoords();
@@ -8615,6 +8686,12 @@ const PageCanvas = forwardRef(
8615
8686
  scaleY: finalScaleY,
8616
8687
  transformMatrix: finalAbsoluteMatrix
8617
8688
  };
8689
+ if (obj instanceof fabric.Textbox) {
8690
+ const baked = obj.minBoxHeight;
8691
+ if (typeof baked === "number" && baked > 0) {
8692
+ elementUpdate.minBoxHeight = baked;
8693
+ }
8694
+ }
8618
8695
  if (sourceElement && sourceElement.opacity !== void 0) {
8619
8696
  elementUpdate.opacity = sourceElement.opacity;
8620
8697
  }
@@ -9326,7 +9403,9 @@ const PageCanvas = forwardRef(
9326
9403
  const resolvedSizeForCompare = (pageChildren == null ? void 0 : pageChildren.length) ? getNodeBounds(element, pageChildren) : { width: typeof element.width === "number" ? element.width : 0, height: typeof element.height === "number" ? element.height : 0 };
9327
9404
  const fabricText = existingObj.text ?? "";
9328
9405
  const storeText = element.text ?? "";
9329
- const otherPropsChanged = Math.abs((existingObj.width ?? 0) - resolvedSizeForCompare.width) > 0.1 || Math.abs((existingObj.height ?? 0) - resolvedSizeForCompare.height) > 0.1 || Math.abs((existingObj.angle ?? 0) - (element.angle ?? 0)) > 0.1 || Math.abs((existingObj.scaleX ?? 1) - (element.scaleX ?? 1)) > 0.01 || Math.abs((existingObj.scaleY ?? 1) - (element.scaleY ?? 1)) > 0.01 || (existingObj.flipX ?? false) !== (element.flipX ?? false) || (existingObj.flipY ?? false) !== (element.flipY ?? false) || fabricText !== storeText || existingObj.fill !== (element.fill ?? "") || existingObj.stroke !== (element.stroke ?? "") || Math.abs((existingObj.strokeWidth ?? 0) - (element.strokeWidth ?? 0)) > 0.01 || Math.abs((existingObj.opacity ?? 1) - (element.opacity ?? 1)) > 0.01 || (existingObj.fontSize ?? 0) !== (element.fontSize ?? 0) || (existingObj.fontFamily ?? "") !== (element.fontFamily ?? "") || // Detect text background + shadow changes so panel edits flow into Fabric.
9406
+ const otherPropsChanged = Math.abs((existingObj.width ?? 0) - resolvedSizeForCompare.width) > 0.1 || Math.abs((existingObj.height ?? 0) - resolvedSizeForCompare.height) > 0.1 || Math.abs((existingObj.angle ?? 0) - (element.angle ?? 0)) > 0.1 || Math.abs((existingObj.scaleX ?? 1) - (element.scaleX ?? 1)) > 0.01 || Math.abs((existingObj.scaleY ?? 1) - (element.scaleY ?? 1)) > 0.01 || (existingObj.flipX ?? false) !== (element.flipX ?? false) || (existingObj.flipY ?? false) !== (element.flipY ?? false) || fabricText !== storeText || existingObj.fill !== (element.fill ?? "") || existingObj.stroke !== (element.stroke ?? "") || Math.abs((existingObj.strokeWidth ?? 0) - (element.strokeWidth ?? 0)) > 0.01 || Math.abs((existingObj.opacity ?? 1) - (element.opacity ?? 1)) > 0.01 || (existingObj.fontSize ?? 0) !== (element.fontSize ?? 0) || (existingObj.fontFamily ?? "") !== (element.fontFamily ?? "") || // Vertical alignment & min box height: panel-driven changes must trigger a re-apply
9407
+ // so _getTopOffset (verticalAlign) and calcTextHeight (minBoxHeight) repaint correctly.
9408
+ (existingObj.verticalAlign ?? "top") !== (element.verticalAlign ?? "top") || Math.abs((existingObj.minBoxHeight ?? 0) - (element.minBoxHeight ?? 0)) > 0.1 || // Detect text background + shadow changes so panel edits flow into Fabric.
9330
9409
  JSON.stringify({
9331
9410
  c: element.textBgColor ?? null,
9332
9411
  p: element.textBgPadding ?? 0,
@@ -9781,7 +9860,7 @@ const PageCanvas = forwardRef(
9781
9860
  });
9782
9861
  }, [selectedIds, isActive, ready, elements]);
9783
9862
  const updateFabricObject = (obj, element, skipPositionUpdate = false) => {
9784
- var _a, _b, _c;
9863
+ var _a, _b;
9785
9864
  const fc = fabricRef.current;
9786
9865
  if (fc && isTransforming(fc)) {
9787
9866
  return;
@@ -10150,7 +10229,8 @@ const PageCanvas = forwardRef(
10150
10229
  const fixedWidth = Math.max(storedWidth, 1);
10151
10230
  const splitByGrapheme = overflowPolicy === "auto-shrink" ? false : element.splitByGrapheme ?? element.wordWrap === "break-word";
10152
10231
  if (overflowPolicy === "auto-shrink") {
10153
- const explicitLineCount = Math.max(1, text.split("\n").length);
10232
+ const minBoxHForShrink = Math.max(0, Number(element.minBoxHeight) || 0);
10233
+ const heightBound = Math.max(rH || 0, minBoxHForShrink);
10154
10234
  while (fontSize > 1) {
10155
10235
  const testTextbox = new fabric.Textbox(text, {
10156
10236
  width: fixedWidth,
@@ -10160,15 +10240,13 @@ const PageCanvas = forwardRef(
10160
10240
  fontStyle: element.fontStyle || "normal",
10161
10241
  lineHeight: element.lineHeight || 1.2,
10162
10242
  charSpacing: element.charSpacing || 0,
10163
- splitByGrapheme: false
10243
+ splitByGrapheme: element.splitByGrapheme ?? false
10164
10244
  });
10165
10245
  testTextbox.initDimensions();
10166
10246
  const textHeight = testTextbox.height || 0;
10167
- const renderedLineCount = ((_b = testTextbox.textLines) == null ? void 0 : _b.length) || 1;
10168
- const hasNoImplicitWrap = renderedLineCount <= explicitLineCount;
10169
- const fitsHeight = rH <= 0 || textHeight <= rH;
10247
+ const fitsHeight = heightBound <= 0 || textHeight <= heightBound;
10170
10248
  const { fitsWidth } = getTextboxWidthFitMetrics(testTextbox, fixedWidth);
10171
- if (hasNoImplicitWrap && fitsHeight && fitsWidth) {
10249
+ if (fitsHeight && fitsWidth) {
10172
10250
  break;
10173
10251
  }
10174
10252
  fontSize--;
@@ -10239,6 +10317,10 @@ const PageCanvas = forwardRef(
10239
10317
  splitByGrapheme,
10240
10318
  text
10241
10319
  });
10320
+ const valign = element.verticalAlign || "top";
10321
+ const minBoxH = Math.max(0, Number(element.minBoxHeight) || 0);
10322
+ obj.verticalAlign = valign;
10323
+ obj.minBoxHeight = minBoxH;
10242
10324
  if (element.formattingEnabled === true) {
10243
10325
  obj.styles = parsedStyles || {};
10244
10326
  } else {
@@ -10261,7 +10343,7 @@ const PageCanvas = forwardRef(
10261
10343
  } catch {
10262
10344
  }
10263
10345
  obj.dirty = true;
10264
- (_c = obj.setCoords) == null ? void 0 : _c.call(obj);
10346
+ (_b = obj.setCoords) == null ? void 0 : _b.call(obj);
10265
10347
  obj.__lastTextBgShadowJson = JSON.stringify({
10266
10348
  c: element.textBgColor ?? null,
10267
10349
  p: element.textBgPadding ?? 0,
@@ -16018,9 +16100,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
16018
16100
  }
16019
16101
  return svgString;
16020
16102
  }
16021
- const resolvedPackageVersion = "0.5.167";
16103
+ const resolvedPackageVersion = "0.5.168";
16022
16104
  const PACKAGE_VERSION = resolvedPackageVersion;
16023
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.167";
16105
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.168";
16024
16106
  const roundParityValue = (value) => {
16025
16107
  if (typeof value !== "number") return value;
16026
16108
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -16146,6 +16228,68 @@ function installUnderlineFix(fab) {
16146
16228
  __underlineFixInstalled = true;
16147
16229
  console.log(`[canvas-renderer] underline-fix monkey patch installed (v${PACKAGE_VERSION})`);
16148
16230
  }
16231
+ let __textboxBoxExtensionsInstalled = false;
16232
+ function installTextboxBoxExtensions(fab) {
16233
+ var _a;
16234
+ if (__textboxBoxExtensionsInstalled) return;
16235
+ const TextboxProto2 = (_a = fab.Textbox) == null ? void 0 : _a.prototype;
16236
+ if (!TextboxProto2) return;
16237
+ if (TextboxProto2.__pixldocsTextboxExtended) {
16238
+ __textboxBoxExtensionsInstalled = true;
16239
+ return;
16240
+ }
16241
+ if (typeof TextboxProto2.calcTextHeight === "function") {
16242
+ const origCalc = TextboxProto2.calcTextHeight;
16243
+ TextboxProto2.__pixldocsOrigCalcTextHeight = origCalc;
16244
+ TextboxProto2.calcTextHeight = function() {
16245
+ const orig = origCalc.call(this);
16246
+ this._contentHeight = orig;
16247
+ const min = this.minBoxHeight || 0;
16248
+ return min > orig ? min : orig;
16249
+ };
16250
+ }
16251
+ if (typeof TextboxProto2._getTopOffset === "function") {
16252
+ const origTop = TextboxProto2._getTopOffset;
16253
+ TextboxProto2.__pixldocsOrigGetTopOffset = origTop;
16254
+ TextboxProto2._getTopOffset = function() {
16255
+ const baseOffset = origTop.call(this);
16256
+ const valign = this.verticalAlign || "top";
16257
+ if (valign === "top") return baseOffset;
16258
+ const content = typeof this._contentHeight === "number" ? this._contentHeight : TextboxProto2.__pixldocsOrigCalcTextHeight ? TextboxProto2.__pixldocsOrigCalcTextHeight.call(this) : 0;
16259
+ const padding = (this.height || 0) - content;
16260
+ if (padding <= 0) return baseOffset;
16261
+ if (valign === "middle") return baseOffset + padding / 2;
16262
+ if (valign === "bottom") return baseOffset + padding;
16263
+ return baseOffset;
16264
+ };
16265
+ }
16266
+ if (typeof TextboxProto2._getSVGLeftTopOffsets === "function") {
16267
+ const origSvgOffsets = TextboxProto2._getSVGLeftTopOffsets;
16268
+ TextboxProto2.__pixldocsOrigGetSVGLeftTopOffsets = origSvgOffsets;
16269
+ TextboxProto2._getSVGLeftTopOffsets = function() {
16270
+ const base = origSvgOffsets.call(this);
16271
+ const valign = this.verticalAlign || "top";
16272
+ if (valign === "top") return base;
16273
+ const content = typeof this._contentHeight === "number" ? this._contentHeight : TextboxProto2.__pixldocsOrigCalcTextHeight ? TextboxProto2.__pixldocsOrigCalcTextHeight.call(this) : 0;
16274
+ const padding = (this.height || 0) - content;
16275
+ if (padding <= 0) return base;
16276
+ const extra = valign === "middle" ? padding / 2 : padding;
16277
+ return { ...base, textTop: base.textTop + extra };
16278
+ };
16279
+ }
16280
+ const stateProps2 = TextboxProto2.stateProperties;
16281
+ if (Array.isArray(stateProps2)) {
16282
+ if (!stateProps2.includes("minBoxHeight")) stateProps2.push("minBoxHeight");
16283
+ if (!stateProps2.includes("verticalAlign")) stateProps2.push("verticalAlign");
16284
+ }
16285
+ const cacheProps2 = TextboxProto2.cacheProperties;
16286
+ if (Array.isArray(cacheProps2)) {
16287
+ if (!cacheProps2.includes("minBoxHeight")) cacheProps2.push("minBoxHeight");
16288
+ if (!cacheProps2.includes("verticalAlign")) cacheProps2.push("verticalAlign");
16289
+ }
16290
+ TextboxProto2.__pixldocsTextboxExtended = true;
16291
+ __textboxBoxExtensionsInstalled = true;
16292
+ }
16149
16293
  function configHasAutoShrinkText(config) {
16150
16294
  var _a;
16151
16295
  if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
@@ -16168,6 +16312,7 @@ class PixldocsRenderer {
16168
16312
  this.config = config;
16169
16313
  this.installRuntimeGlobals();
16170
16314
  installUnderlineFix(fabric);
16315
+ installTextboxBoxExtensions(fabric);
16171
16316
  try {
16172
16317
  console.log(`[canvas-renderer] PixldocsRenderer v${PACKAGE_VERSION} initialized`);
16173
16318
  } catch {
@@ -16455,7 +16600,7 @@ class PixldocsRenderer {
16455
16600
  await this.waitForCanvasScene(container, cloned, i);
16456
16601
  }
16457
16602
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
16458
- const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-Ce68BOFf.js");
16603
+ const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-DCy1uYUF.js");
16459
16604
  const prepared = preparePagesForExport(
16460
16605
  cloned.pages,
16461
16606
  canvasWidth,
@@ -18557,7 +18702,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
18557
18702
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
18558
18703
  sanitizeSvgTreeForPdf(svgToDraw);
18559
18704
  try {
18560
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-Ce68BOFf.js");
18705
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-DCy1uYUF.js");
18561
18706
  try {
18562
18707
  await logTextMeasurementDiagnostic(svgToDraw);
18563
18708
  } catch {
@@ -18959,4 +19104,4 @@ export {
18959
19104
  collectFontDescriptorsFromConfig as y,
18960
19105
  collectFontsFromConfig as z
18961
19106
  };
18962
- //# sourceMappingURL=index-7Etfiz-e.js.map
19107
+ //# sourceMappingURL=index-oFnROAcT.js.map