@postermywall/fabricjs-2 7.1.0-pmw-69 → 7.4.0-pmw-70

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.
Files changed (51) hide show
  1. package/.vitest-attachments/babbd13e5ebd8c87d4f680401cd695e6d9726cc4.png +0 -0
  2. package/.vitest-attachments/cf8464e8a89ae803d8259877182f41cb2ace5cbb.png +0 -0
  3. package/.vitest-attachments/d76689b557e9557f6d3c9a7f0763e94eb8eceb76.png +0 -0
  4. package/CHANGELOG.md +1 -0
  5. package/dist/index.js +78 -18
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.min.js +4 -4
  8. package/dist/index.min.js.map +1 -1
  9. package/dist/index.min.mjs +4 -4
  10. package/dist/index.min.mjs.map +1 -1
  11. package/dist/index.mjs +78 -18
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/index.node.cjs +76 -17
  14. package/dist/index.node.cjs.map +1 -1
  15. package/dist/index.node.mjs +76 -17
  16. package/dist/index.node.mjs.map +1 -1
  17. package/dist/package.min.mjs +1 -1
  18. package/dist/package.mjs +1 -1
  19. package/dist/src/shapes/Text/Text.d.ts +26 -2
  20. package/dist/src/shapes/Text/Text.d.ts.map +1 -1
  21. package/dist/src/shapes/Text/Text.min.mjs +1 -1
  22. package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
  23. package/dist/src/shapes/Text/Text.mjs +41 -16
  24. package/dist/src/shapes/Text/Text.mjs.map +1 -1
  25. package/dist/src/shapes/Text/TextSVGExportMixin.d.ts +12 -0
  26. package/dist/src/shapes/Text/TextSVGExportMixin.d.ts.map +1 -1
  27. package/dist/src/shapes/Text/TextSVGExportMixin.min.mjs +2 -2
  28. package/dist/src/shapes/Text/TextSVGExportMixin.min.mjs.map +1 -1
  29. package/dist/src/shapes/Text/TextSVGExportMixin.mjs +37 -1
  30. package/dist/src/shapes/Text/TextSVGExportMixin.mjs.map +1 -1
  31. package/dist-extensions/src/shapes/Text/Text.d.ts +26 -2
  32. package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
  33. package/dist-extensions/src/shapes/Text/TextSVGExportMixin.d.ts +12 -0
  34. package/dist-extensions/src/shapes/Text/TextSVGExportMixin.d.ts.map +1 -1
  35. package/package.json +1 -1
  36. package/postermywall-fabricjs-2-7.4.0-pmw-70.tgz +0 -0
  37. package/src/shapes/Text/Text.ts +78 -34
  38. package/src/shapes/Text/TextSVGExportMixin.ts +62 -1
  39. package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-1.png +0 -0
  40. package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-justified-1.png +0 -0
  41. package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-with-deltaY-1.png +0 -0
  42. package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-with-font-1.png +0 -0
  43. package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-with-font-and-origins-1.png +0 -0
  44. package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-with-multiple-spaces-1.png +0 -0
  45. package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-with-text-as-a-clipPath-1.png +0 -0
  46. package/src/shapes/Text/glyphOverhang.spec.ts +14 -0
  47. package/src/shapes/Text/svgListMarkers.spec.ts +116 -0
  48. /package/.vitest-attachments/{0b71b97b594519eb09336f957db6701e064f1e6b.png → 1629802dbea5d4ab7edf7e6f65edc8c84e892e6e.png} +0 -0
  49. /package/.vitest-attachments/{2f7310caa5815d4e4ed35964597101936fcb397f.png → 23857cd0ba8b7c015d07ea8d7ce12e83dd88818d.png} +0 -0
  50. /package/{src/shapes/Text/__screenshots__/glyphOverhang.spec.ts/glyph-overhang-widens-a-group-cache-that-caters-for-text-children-by-the-same-reach-1.png → .vitest-attachments/a9d6ab42d2436605fb54e5a753e00e9a55fd6d98.png} +0 -0
  51. /package/{src/shapes/Text/__screenshots__/glyphOverhang.spec.ts/glyph-overhang-widens-the-text-cache-canvas-by-twice-the-reach-once-it-exceeds-the-font-size-floor-1.png → .vitest-attachments/b895abca787d674e9796cc7d12648978cac129d5.png} +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  ## [next]
4
4
 
5
+ - feat(text): export list markers as SVG spans where the canvas draws them, write the gradient definitions of per-character fills, and read a right-to-left line's glyph reach by each glyph's own ink (pmw-70)
5
6
  - feat(text): measure each glyph's ink reach and size the text and group caches from it, so swashes and descenders of any font are not cut (pmw-69)
6
7
  - Upgrade 7.4.0 [#4](https://github.com/fabricjs/fabric.js/pull/4)
7
8
 
package/dist/index.js CHANGED
@@ -452,7 +452,7 @@
452
452
  const cache = new Cache();
453
453
  //#endregion
454
454
  //#region src/constants.ts
455
- const VERSION = "7.1.0-pmw-69";
455
+ const VERSION = "7.4.0-pmw-70";
456
456
  function noop() {}
457
457
  const halfPI = Math.PI / 2;
458
458
  const quarterPI = Math.PI / 4;
@@ -15862,7 +15862,26 @@
15862
15862
  var TextSVGExportMixin = class extends FabricObjectSVGExportMixin {
15863
15863
  _toSVG() {
15864
15864
  const offsets = this._getSVGLeftTopOffsets(), textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft);
15865
- return this._wrapSVGTextAndBg(textAndBg);
15865
+ return [...this._getStyledCharFillerMarkup(), ...this._wrapSVGTextAndBg(textAndBg)];
15866
+ }
15867
+ /**
15868
+ * *PMW* The gradient definitions of per-character fills. The object-level fill is written by
15869
+ * _createBaseSVGMarkup, but a styled character's gradient only ever appeared as a `url(#id)`
15870
+ * reference, so the text rendered black in any SVG consumer.
15871
+ */
15872
+ _getStyledCharFillerMarkup() {
15873
+ const seen = /* @__PURE__ */ new Set(), markup = [];
15874
+ Object.values(this.styles).forEach((line) => {
15875
+ Object.values(line).forEach((declaration) => {
15876
+ var _declaration$fill;
15877
+ const fill = (_declaration$fill = declaration.fill) !== null && _declaration$fill !== void 0 ? _declaration$fill : null;
15878
+ if (isFiller(fill) && !seen.has(String(fill.id))) {
15879
+ seen.add(String(fill.id));
15880
+ markup.push(fill.toSVG(this));
15881
+ }
15882
+ });
15883
+ });
15884
+ return markup;
15866
15885
  }
15867
15886
  toSVG(reviver) {
15868
15887
  const textSvg = this._createBaseSVGMarkup(this._toSVG(), {
@@ -15918,6 +15937,7 @@
15918
15937
  lineOffset = this._getLineLeftOffset(i);
15919
15938
  if (this.direction === "rtl") lineOffset += this.width;
15920
15939
  if (this.textBackgroundColor || this.styleHas("textBackgroundColor", i)) this._setSVGTextLineBg(textBgRects, i, textLeftOffset + lineOffset, height);
15940
+ this._setSVGListMarker(textSpans, i, textLeftOffset + lineOffset, height);
15921
15941
  this._setSVGTextLineText(textSpans, i, textLeftOffset + lineOffset, height);
15922
15942
  height += this.getHeightOfLine(i);
15923
15943
  }
@@ -15942,6 +15962,21 @@
15942
15962
  }
15943
15963
  return `<tspan x="${toFixed(left, numFractionDigit)}" y="${toFixed(top, numFractionDigit)}" ${dySpan}${angleAttr}${fillStyles}>${escapeXml(char)}</tspan>`;
15944
15964
  }
15965
+ /**
15966
+ * *PMW* The list marker of a visual row as its own span, placed exactly where the canvas
15967
+ * render draws it ({@link FabricText#_getListMarkerPlacement}): the row's text start is the
15968
+ * span's left offset and the row's bottom is its top offset plus the line height.
15969
+ */
15970
+ _setSVGListMarker(textSpans, lineIndex, textLeftOffset, textTopOffset) {
15971
+ const placed = this._getListMarkerPlacement(lineIndex, textLeftOffset, textTopOffset + this.getHeightOfLineImpl(lineIndex));
15972
+ if (!placed) return;
15973
+ const styleProps = this.getSvgSpanStyles({
15974
+ fontFamily: placed.fontFamily,
15975
+ fontSize: placed.fontSize,
15976
+ fill: placed.fill
15977
+ }, true);
15978
+ textSpans.push(`<tspan x="${toFixed(placed.left, config.NUM_FRACTION_DIGITS)}" y="${toFixed(placed.baseline, config.NUM_FRACTION_DIGITS)}" style="${styleProps}">${escapeXml(placed.marker)}</tspan>`);
15979
+ }
15945
15980
  _setSVGTextLineText(textSpans, lineIndex, textLeftOffset, textTopOffset) {
15946
15981
  const lineHeight = this.getHeightOfLine(lineIndex), isJustify = this.textAlign.includes(JUSTIFY), line = this._textLines[lineIndex];
15947
15982
  let actualStyle, nextStyle, charsToRender = "", charBox, style, boxWidth = 0, timeToRender;
@@ -16293,7 +16328,8 @@
16293
16328
  /**
16294
16329
  * *PMW* Walks every measured grapheme: horizontally, its ink left of the line start and
16295
16330
  * right of the box edge, both after the line's alignment offset (a right-to-left line lays
16296
- * its graphemes out mirrored, so it takes no offset credit and reads conservatively);
16331
+ * its graphemes out mirrored, so it counts each glyph's reach past its own advance box
16332
+ * instead, with no alignment credit, and reads conservatively);
16297
16333
  * vertically, the first line's ink above its baseline and the last line's ink below the
16298
16334
  * box bottom. A stroke paints half its width outside the outline on every side.
16299
16335
  * @private
@@ -16307,11 +16343,11 @@
16307
16343
  let horizontal = 0, vertical = 0, lineTop = 0;
16308
16344
  for (let lineIndex = 0; lineIndex < lineCount; lineIndex++) {
16309
16345
  this.getLineWidth(lineIndex);
16310
- const bounds = this.__charBounds[lineIndex], isRtl = this.direction === "rtl", leftOffset = isRtl ? 0 : this._getLineLeftOffset(lineIndex), rightEdge = isRtl ? this.getLineWidth(lineIndex) : this.width - leftOffset, baselineFromTop = lineTop + this.getHeightOfLineImpl(lineIndex) * (1 - this._fontSizeFraction), roomBelowBaseline = this.height - baselineFromTop;
16346
+ const bounds = this.__charBounds[lineIndex], isRtl = this.direction === "rtl", leftOffset = this._getLineLeftOffset(lineIndex), rightEdge = this.width - leftOffset, baselineFromTop = lineTop + this.getHeightOfLineImpl(lineIndex) * (1 - this._fontSizeFraction), roomBelowBaseline = this.height - baselineFromTop;
16311
16347
  for (let charIndex = 0; bounds && charIndex < this._textLines[lineIndex].length; charIndex++) {
16312
16348
  const box = bounds[charIndex], ink = box.ink;
16313
16349
  if (!ink) continue;
16314
- horizontal = Math.max(horizontal, ink.left - box.left - leftOffset, box.left + ink.right - rightEdge);
16350
+ horizontal = isRtl ? Math.max(horizontal, ink.left, ink.right - box.width) : Math.max(horizontal, ink.left - box.left - leftOffset, box.left + ink.right - rightEdge);
16315
16351
  if (lineIndex === 0) vertical = Math.max(vertical, ink.ascent - box.deltaY - baselineFromTop);
16316
16352
  if (lineIndex === lineCount - 1) vertical = Math.max(vertical, ink.descent + box.deltaY - roomBelowBaseline);
16317
16353
  }
@@ -16651,6 +16687,7 @@
16651
16687
  * @param {Number} lineIndex index of line to calculate
16652
16688
  * @return {Number}
16653
16689
  */
16690
+ /** *PMW* also read by the SVG export mixin, which is a separate class */
16654
16691
  getHeightOfLineImpl(lineIndex) {
16655
16692
  const lh = this.__lineHeights;
16656
16693
  if (lh[lineIndex]) return lh[lineIndex];
@@ -17056,27 +17093,50 @@
17056
17093
  * @private
17057
17094
  */
17058
17095
  _renderListMarker(ctx, visualLineIndex, lineDrawLeft, lineDrawTop) {
17059
- var _this$lineStyles$real2, _this$lineStyles$real3;
17060
- if (!this._isStartOfRealLine(visualLineIndex)) return;
17061
- const realLineIndex = this._getRealLineIndex(visualLineIndex);
17062
- const marker = (_this$lineStyles$real2 = this.lineStyles[realLineIndex]) === null || _this$lineStyles$real2 === void 0 ? void 0 : _this$lineStyles$real2.listMarker;
17063
- if (!marker) return;
17064
- const { fontSize, fontFamily, fill } = this._getListMarkerStyle(realLineIndex);
17065
- const gap = LIST_MARKER_GAP_FACTOR * fontSize;
17066
- const markerLeft = this.direction === "rtl" ? lineDrawLeft + gap : lineDrawLeft - gap - this._measureListMarkerWidth(realLineIndex);
17096
+ const placed = this._getListMarkerPlacement(visualLineIndex, lineDrawLeft, lineDrawTop);
17097
+ if (!placed) return;
17067
17098
  ctx.save();
17068
17099
  ctx.direction = "ltr";
17069
17100
  ctx.textAlign = LEFT;
17070
17101
  ctx.font = this._getFontDeclaration({
17071
- fontSize,
17072
- fontFamily
17102
+ fontSize: placed.fontSize,
17103
+ fontFamily: placed.fontFamily
17073
17104
  });
17074
- ctx.fillStyle = fill;
17075
- const unorderedMarkerRaise = ((_this$lineStyles$real3 = this.lineStyles[realLineIndex]) === null || _this$lineStyles$real3 === void 0 ? void 0 : _this$lineStyles$real3.listMarkerFontFamily) ? fontSize / UNORDERED_MARKER_RAISE_FACTOR : 0;
17076
- ctx.fillText(String(marker), markerLeft, lineDrawTop - this.getHeightOfLineImpl(visualLineIndex) * this._fontSizeFraction - unorderedMarkerRaise);
17105
+ ctx.fillStyle = placed.fill;
17106
+ ctx.fillText(placed.marker, placed.left, placed.baseline);
17077
17107
  ctx.restore();
17078
17108
  }
17079
17109
  /**
17110
+ * *PMW* Where a visual row's list marker is drawn, in text units, given the row's own draw
17111
+ * anchors (`lineDrawLeft` is the row's text start, `lineDrawTop` its bottom). Shared by the
17112
+ * canvas render and the SVG export so both put the marker in the same place. Null when the
17113
+ * row does not start a listed real line.
17114
+ *
17115
+ * LTR: right-aligned in the LEFT gutter (like the legacy bullets textbox) — the marker's
17116
+ * right edge sits a fixed gap from the text column, so ordinal periods line up and digits
17117
+ * grow left. RTL mirrors: the gutter (and the marker) sit RIGHT of the text, whose draw
17118
+ * anchor is its right edge there. An unordered glyph is raised a little above the baseline.
17119
+ * @private
17120
+ */
17121
+ _getListMarkerPlacement(visualLineIndex, lineDrawLeft, lineDrawTop) {
17122
+ var _this$lineStyles$real2, _this$lineStyles$real3;
17123
+ if (!this._isStartOfRealLine(visualLineIndex)) return null;
17124
+ const realLineIndex = this._getRealLineIndex(visualLineIndex);
17125
+ const marker = (_this$lineStyles$real2 = this.lineStyles[realLineIndex]) === null || _this$lineStyles$real2 === void 0 ? void 0 : _this$lineStyles$real2.listMarker;
17126
+ if (!marker) return null;
17127
+ const { fontSize, fontFamily, fill } = this._getListMarkerStyle(realLineIndex);
17128
+ const gap = LIST_MARKER_GAP_FACTOR * fontSize;
17129
+ const unorderedMarkerRaise = ((_this$lineStyles$real3 = this.lineStyles[realLineIndex]) === null || _this$lineStyles$real3 === void 0 ? void 0 : _this$lineStyles$real3.listMarkerFontFamily) ? fontSize / UNORDERED_MARKER_RAISE_FACTOR : 0;
17130
+ return {
17131
+ marker: String(marker),
17132
+ fontSize,
17133
+ fontFamily,
17134
+ fill,
17135
+ left: this.direction === "rtl" ? lineDrawLeft + gap : lineDrawLeft - gap - this._measureListMarkerWidth(realLineIndex),
17136
+ baseline: lineDrawTop - this.getHeightOfLineImpl(visualLineIndex) * this._fontSizeFraction - unorderedMarkerRaise
17137
+ };
17138
+ }
17139
+ /**
17080
17140
  * *PMW* Sets or clears (align undefined) one real line's alignment override.
17081
17141
  */
17082
17142
  setLineAlignment(realLineIndex, align) {