@postermywall/fabricjs-2 7.1.0-pmw-69 → 7.4.0-pmw-71
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/.vitest-attachments/b895abca787d674e9796cc7d12648978cac129d5.png +0 -0
- package/.vitest-attachments/babbd13e5ebd8c87d4f680401cd695e6d9726cc4.png +0 -0
- package/.vitest-attachments/cf8464e8a89ae803d8259877182f41cb2ace5cbb.png +0 -0
- package/.vitest-attachments/d76689b557e9557f6d3c9a7f0763e94eb8eceb76.png +0 -0
- package/CHANGELOG.md +2 -0
- package/dist/index.js +92 -24
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +26 -26
- package/dist/index.min.js.map +1 -1
- package/dist/index.min.mjs +26 -26
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.mjs +92 -24
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +90 -23
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +90 -23
- package/dist/index.node.mjs.map +1 -1
- package/dist/package.min.mjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/src/parser/normalizeValue.d.ts.map +1 -1
- package/dist/src/parser/normalizeValue.min.mjs +1 -1
- package/dist/src/parser/normalizeValue.min.mjs.map +1 -1
- package/dist/src/parser/normalizeValue.mjs +4 -2
- package/dist/src/parser/normalizeValue.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.d.ts +26 -2
- package/dist/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist/src/shapes/Text/Text.min.mjs +2 -2
- package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.mjs +51 -20
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Text/TextSVGExportMixin.d.ts +12 -0
- package/dist/src/shapes/Text/TextSVGExportMixin.d.ts.map +1 -1
- package/dist/src/shapes/Text/TextSVGExportMixin.min.mjs +2 -2
- package/dist/src/shapes/Text/TextSVGExportMixin.min.mjs.map +1 -1
- package/dist/src/shapes/Text/TextSVGExportMixin.mjs +37 -1
- package/dist/src/shapes/Text/TextSVGExportMixin.mjs.map +1 -1
- package/dist-extensions/src/parser/normalizeValue.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts +26 -2
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/TextSVGExportMixin.d.ts +12 -0
- package/dist-extensions/src/shapes/Text/TextSVGExportMixin.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__screenshots__/__tmp_real_svg.spec.ts/real-Office-exported-SVG-from-order-42675801-parses--measures-and-renders-its-clipped-text-without-throwing-1.png +0 -0
- package/src/parser/loadSVGFromString.test.ts +26 -0
- package/src/parser/normalizeValue.ts +6 -0
- package/src/shapes/Text/Text.ts +94 -38
- package/src/shapes/Text/TextSVGExportMixin.ts +62 -1
- package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-1.png +0 -0
- package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-justified-1.png +0 -0
- package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-with-deltaY-1.png +0 -0
- package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-with-font-1.png +0 -0
- package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-with-font-and-origins-1.png +0 -0
- package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-with-multiple-spaces-1.png +0 -0
- package/src/shapes/Text/__screenshots__/TextSVGExportMixin.spec.ts/TextSvgExport-toSVG-toSVG-with-text-as-a-clipPath-1.png +0 -0
- package/src/shapes/Text/glyphOverhang.spec.ts +32 -0
- package/src/shapes/Text/svgListMarkers.spec.ts +116 -0
- /package/.vitest-attachments/{0b71b97b594519eb09336f957db6701e064f1e6b.png → 1629802dbea5d4ab7edf7e6f65edc8c84e892e6e.png} +0 -0
- /package/.vitest-attachments/{2f7310caa5815d4e4ed35964597101936fcb397f.png → 23857cd0ba8b7c015d07ea8d7ce12e83dd88818d.png} +0 -0
- /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/23b206d0e2c2cbf41ea4d8dd1b0758f9c3447d6f.png} +0 -0
- /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/a9d6ab42d2436605fb54e5a753e00e9a55fd6d98.png} +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## [next]
|
|
4
4
|
|
|
5
|
+
- fix(text): an SVG `letter-spacing: normal` (Office exports) reads as no extra spacing instead of the raw string, and a non-finite glyph reach can no longer size a text cache canvas to 0 and crash the render with `drawImage ... width or height of 0` (pmw-71)
|
|
6
|
+
- 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
7
|
- 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
8
|
- Upgrade 7.4.0 [#4](https://github.com/fabricjs/fabric.js/pull/4)
|
|
7
9
|
|
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.
|
|
455
|
+
const VERSION = "7.4.0-pmw-71";
|
|
456
456
|
function noop() {}
|
|
457
457
|
const halfPI = Math.PI / 2;
|
|
458
458
|
const quarterPI = Math.PI / 4;
|
|
@@ -8745,8 +8745,10 @@
|
|
|
8745
8745
|
ouputValue = parseFloat(value);
|
|
8746
8746
|
if (parentAttributes && typeof parentAttributes.opacity !== "undefined") ouputValue *= parentAttributes.opacity;
|
|
8747
8747
|
} else if (attr === "textAnchor") ouputValue = value === "start" ? LEFT : value === "end" ? RIGHT : CENTER;
|
|
8748
|
-
else if (attr === "charSpacing" || attr === "textDecorationThickness")
|
|
8749
|
-
|
|
8748
|
+
else if (attr === "charSpacing" || attr === "textDecorationThickness") {
|
|
8749
|
+
parsed = parseUnit(value, fontSize) / fontSize * 1e3;
|
|
8750
|
+
if (attr === "charSpacing" && !Number.isFinite(parsed)) parsed = 0;
|
|
8751
|
+
} else if (attr === "paintFirst") {
|
|
8750
8752
|
const fillIndex = value.indexOf(FILL);
|
|
8751
8753
|
const strokeIndex = value.indexOf(STROKE);
|
|
8752
8754
|
ouputValue = FILL;
|
|
@@ -15862,7 +15864,26 @@
|
|
|
15862
15864
|
var TextSVGExportMixin = class extends FabricObjectSVGExportMixin {
|
|
15863
15865
|
_toSVG() {
|
|
15864
15866
|
const offsets = this._getSVGLeftTopOffsets(), textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft);
|
|
15865
|
-
return this._wrapSVGTextAndBg(textAndBg);
|
|
15867
|
+
return [...this._getStyledCharFillerMarkup(), ...this._wrapSVGTextAndBg(textAndBg)];
|
|
15868
|
+
}
|
|
15869
|
+
/**
|
|
15870
|
+
* *PMW* The gradient definitions of per-character fills. The object-level fill is written by
|
|
15871
|
+
* _createBaseSVGMarkup, but a styled character's gradient only ever appeared as a `url(#id)`
|
|
15872
|
+
* reference, so the text rendered black in any SVG consumer.
|
|
15873
|
+
*/
|
|
15874
|
+
_getStyledCharFillerMarkup() {
|
|
15875
|
+
const seen = /* @__PURE__ */ new Set(), markup = [];
|
|
15876
|
+
Object.values(this.styles).forEach((line) => {
|
|
15877
|
+
Object.values(line).forEach((declaration) => {
|
|
15878
|
+
var _declaration$fill;
|
|
15879
|
+
const fill = (_declaration$fill = declaration.fill) !== null && _declaration$fill !== void 0 ? _declaration$fill : null;
|
|
15880
|
+
if (isFiller(fill) && !seen.has(String(fill.id))) {
|
|
15881
|
+
seen.add(String(fill.id));
|
|
15882
|
+
markup.push(fill.toSVG(this));
|
|
15883
|
+
}
|
|
15884
|
+
});
|
|
15885
|
+
});
|
|
15886
|
+
return markup;
|
|
15866
15887
|
}
|
|
15867
15888
|
toSVG(reviver) {
|
|
15868
15889
|
const textSvg = this._createBaseSVGMarkup(this._toSVG(), {
|
|
@@ -15918,6 +15939,7 @@
|
|
|
15918
15939
|
lineOffset = this._getLineLeftOffset(i);
|
|
15919
15940
|
if (this.direction === "rtl") lineOffset += this.width;
|
|
15920
15941
|
if (this.textBackgroundColor || this.styleHas("textBackgroundColor", i)) this._setSVGTextLineBg(textBgRects, i, textLeftOffset + lineOffset, height);
|
|
15942
|
+
this._setSVGListMarker(textSpans, i, textLeftOffset + lineOffset, height);
|
|
15921
15943
|
this._setSVGTextLineText(textSpans, i, textLeftOffset + lineOffset, height);
|
|
15922
15944
|
height += this.getHeightOfLine(i);
|
|
15923
15945
|
}
|
|
@@ -15942,6 +15964,21 @@
|
|
|
15942
15964
|
}
|
|
15943
15965
|
return `<tspan x="${toFixed(left, numFractionDigit)}" y="${toFixed(top, numFractionDigit)}" ${dySpan}${angleAttr}${fillStyles}>${escapeXml(char)}</tspan>`;
|
|
15944
15966
|
}
|
|
15967
|
+
/**
|
|
15968
|
+
* *PMW* The list marker of a visual row as its own span, placed exactly where the canvas
|
|
15969
|
+
* render draws it ({@link FabricText#_getListMarkerPlacement}): the row's text start is the
|
|
15970
|
+
* span's left offset and the row's bottom is its top offset plus the line height.
|
|
15971
|
+
*/
|
|
15972
|
+
_setSVGListMarker(textSpans, lineIndex, textLeftOffset, textTopOffset) {
|
|
15973
|
+
const placed = this._getListMarkerPlacement(lineIndex, textLeftOffset, textTopOffset + this.getHeightOfLineImpl(lineIndex));
|
|
15974
|
+
if (!placed) return;
|
|
15975
|
+
const styleProps = this.getSvgSpanStyles({
|
|
15976
|
+
fontFamily: placed.fontFamily,
|
|
15977
|
+
fontSize: placed.fontSize,
|
|
15978
|
+
fill: placed.fill
|
|
15979
|
+
}, true);
|
|
15980
|
+
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>`);
|
|
15981
|
+
}
|
|
15945
15982
|
_setSVGTextLineText(textSpans, lineIndex, textLeftOffset, textTopOffset) {
|
|
15946
15983
|
const lineHeight = this.getHeightOfLine(lineIndex), isJustify = this.textAlign.includes(JUSTIFY), line = this._textLines[lineIndex];
|
|
15947
15984
|
let actualStyle, nextStyle, charsToRender = "", charBox, style, boxWidth = 0, timeToRender;
|
|
@@ -16090,6 +16127,12 @@
|
|
|
16090
16127
|
/** Peak rise = (period / 2) × this factor: 6.75 text units at the reference size. */
|
|
16091
16128
|
const SQUIGGLY_LINE_WAVE_RISE_FACTOR = 5 * .45;
|
|
16092
16129
|
/**
|
|
16130
|
+
* *PMW* `Math.max` over the finite candidates only. A NaN or infinite reach (a character
|
|
16131
|
+
* bound poisoned by bad input) must never size a cache canvas: `canvas.width = NaN` is 0 and
|
|
16132
|
+
* the next `drawImage` of that canvas throws.
|
|
16133
|
+
*/
|
|
16134
|
+
const maxFinite = (current, ...candidates) => candidates.reduce((max, candidate) => Number.isFinite(candidate) ? Math.max(max, candidate) : max, current);
|
|
16135
|
+
/**
|
|
16093
16136
|
* Text class
|
|
16094
16137
|
* @see {@link http://fabric5.fabricjs.com/fabric-intro-part-2#text}
|
|
16095
16138
|
*/
|
|
@@ -16293,7 +16336,8 @@
|
|
|
16293
16336
|
/**
|
|
16294
16337
|
* *PMW* Walks every measured grapheme: horizontally, its ink left of the line start and
|
|
16295
16338
|
* 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
|
|
16339
|
+
* its graphemes out mirrored, so it counts each glyph's reach past its own advance box
|
|
16340
|
+
* instead, with no alignment credit, and reads conservatively);
|
|
16297
16341
|
* vertically, the first line's ink above its baseline and the last line's ink below the
|
|
16298
16342
|
* box bottom. A stroke paints half its width outside the outline on every side.
|
|
16299
16343
|
* @private
|
|
@@ -16307,20 +16351,20 @@
|
|
|
16307
16351
|
let horizontal = 0, vertical = 0, lineTop = 0;
|
|
16308
16352
|
for (let lineIndex = 0; lineIndex < lineCount; lineIndex++) {
|
|
16309
16353
|
this.getLineWidth(lineIndex);
|
|
16310
|
-
const bounds = this.__charBounds[lineIndex], isRtl = this.direction === "rtl", leftOffset =
|
|
16354
|
+
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
16355
|
for (let charIndex = 0; bounds && charIndex < this._textLines[lineIndex].length; charIndex++) {
|
|
16312
16356
|
const box = bounds[charIndex], ink = box.ink;
|
|
16313
16357
|
if (!ink) continue;
|
|
16314
|
-
horizontal =
|
|
16315
|
-
if (lineIndex === 0) vertical =
|
|
16316
|
-
if (lineIndex === lineCount - 1) vertical =
|
|
16358
|
+
horizontal = isRtl ? maxFinite(horizontal, ink.left, ink.right - box.width) : maxFinite(horizontal, ink.left - box.left - leftOffset, box.left + ink.right - rightEdge);
|
|
16359
|
+
if (lineIndex === 0) vertical = maxFinite(vertical, ink.ascent - box.deltaY - baselineFromTop);
|
|
16360
|
+
if (lineIndex === lineCount - 1) vertical = maxFinite(vertical, ink.descent + box.deltaY - roomBelowBaseline);
|
|
16317
16361
|
}
|
|
16318
16362
|
lineTop += this.getHeightOfLine(lineIndex);
|
|
16319
16363
|
}
|
|
16320
16364
|
const strokeReach = this.stroke ? this.strokeWidth / 2 : 0;
|
|
16321
16365
|
return {
|
|
16322
|
-
horizontal:
|
|
16323
|
-
vertical:
|
|
16366
|
+
horizontal: maxFinite(0, horizontal + strokeReach),
|
|
16367
|
+
vertical: maxFinite(0, vertical + strokeReach)
|
|
16324
16368
|
};
|
|
16325
16369
|
}
|
|
16326
16370
|
/**
|
|
@@ -16651,6 +16695,7 @@
|
|
|
16651
16695
|
* @param {Number} lineIndex index of line to calculate
|
|
16652
16696
|
* @return {Number}
|
|
16653
16697
|
*/
|
|
16698
|
+
/** *PMW* also read by the SVG export mixin, which is a separate class */
|
|
16654
16699
|
getHeightOfLineImpl(lineIndex) {
|
|
16655
16700
|
const lh = this.__lineHeights;
|
|
16656
16701
|
if (lh[lineIndex]) return lh[lineIndex];
|
|
@@ -17056,27 +17101,50 @@
|
|
|
17056
17101
|
* @private
|
|
17057
17102
|
*/
|
|
17058
17103
|
_renderListMarker(ctx, visualLineIndex, lineDrawLeft, lineDrawTop) {
|
|
17059
|
-
|
|
17060
|
-
if (!
|
|
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);
|
|
17104
|
+
const placed = this._getListMarkerPlacement(visualLineIndex, lineDrawLeft, lineDrawTop);
|
|
17105
|
+
if (!placed) return;
|
|
17067
17106
|
ctx.save();
|
|
17068
17107
|
ctx.direction = "ltr";
|
|
17069
17108
|
ctx.textAlign = LEFT;
|
|
17070
17109
|
ctx.font = this._getFontDeclaration({
|
|
17071
|
-
fontSize,
|
|
17072
|
-
fontFamily
|
|
17110
|
+
fontSize: placed.fontSize,
|
|
17111
|
+
fontFamily: placed.fontFamily
|
|
17073
17112
|
});
|
|
17074
|
-
ctx.fillStyle = fill;
|
|
17075
|
-
|
|
17076
|
-
ctx.fillText(String(marker), markerLeft, lineDrawTop - this.getHeightOfLineImpl(visualLineIndex) * this._fontSizeFraction - unorderedMarkerRaise);
|
|
17113
|
+
ctx.fillStyle = placed.fill;
|
|
17114
|
+
ctx.fillText(placed.marker, placed.left, placed.baseline);
|
|
17077
17115
|
ctx.restore();
|
|
17078
17116
|
}
|
|
17079
17117
|
/**
|
|
17118
|
+
* *PMW* Where a visual row's list marker is drawn, in text units, given the row's own draw
|
|
17119
|
+
* anchors (`lineDrawLeft` is the row's text start, `lineDrawTop` its bottom). Shared by the
|
|
17120
|
+
* canvas render and the SVG export so both put the marker in the same place. Null when the
|
|
17121
|
+
* row does not start a listed real line.
|
|
17122
|
+
*
|
|
17123
|
+
* LTR: right-aligned in the LEFT gutter (like the legacy bullets textbox) — the marker's
|
|
17124
|
+
* right edge sits a fixed gap from the text column, so ordinal periods line up and digits
|
|
17125
|
+
* grow left. RTL mirrors: the gutter (and the marker) sit RIGHT of the text, whose draw
|
|
17126
|
+
* anchor is its right edge there. An unordered glyph is raised a little above the baseline.
|
|
17127
|
+
* @private
|
|
17128
|
+
*/
|
|
17129
|
+
_getListMarkerPlacement(visualLineIndex, lineDrawLeft, lineDrawTop) {
|
|
17130
|
+
var _this$lineStyles$real2, _this$lineStyles$real3;
|
|
17131
|
+
if (!this._isStartOfRealLine(visualLineIndex)) return null;
|
|
17132
|
+
const realLineIndex = this._getRealLineIndex(visualLineIndex);
|
|
17133
|
+
const marker = (_this$lineStyles$real2 = this.lineStyles[realLineIndex]) === null || _this$lineStyles$real2 === void 0 ? void 0 : _this$lineStyles$real2.listMarker;
|
|
17134
|
+
if (!marker) return null;
|
|
17135
|
+
const { fontSize, fontFamily, fill } = this._getListMarkerStyle(realLineIndex);
|
|
17136
|
+
const gap = LIST_MARKER_GAP_FACTOR * fontSize;
|
|
17137
|
+
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;
|
|
17138
|
+
return {
|
|
17139
|
+
marker: String(marker),
|
|
17140
|
+
fontSize,
|
|
17141
|
+
fontFamily,
|
|
17142
|
+
fill,
|
|
17143
|
+
left: this.direction === "rtl" ? lineDrawLeft + gap : lineDrawLeft - gap - this._measureListMarkerWidth(realLineIndex),
|
|
17144
|
+
baseline: lineDrawTop - this.getHeightOfLineImpl(visualLineIndex) * this._fontSizeFraction - unorderedMarkerRaise
|
|
17145
|
+
};
|
|
17146
|
+
}
|
|
17147
|
+
/**
|
|
17080
17148
|
* *PMW* Sets or clears (align undefined) one real line's alignment override.
|
|
17081
17149
|
*/
|
|
17082
17150
|
setLineAlignment(realLineIndex, align) {
|