// @ts-nocheck
import { config } from '../../config';
import { TSVGReviver } from '../../typedefs';
import { escapeXml } from '../../util/lang_string';
import { createSVGRect } from '../../util/misc/svgParsing';
import { hasStyleChanged } from '../../util/misc/textStyles';
import { toFixed } from '../../util/misc/toFixed';
import { FabricObjectSVGExportMixin } from '../Object/FabricObjectSVGExportMixin';
import type { TextStyleDeclaration } from './StyledText';
const multipleSpacesRegex = / +/g;
const dblQuoteRegex = /"/g;
function createSVGInlineRect(
color: string,
left: number,
top: number,
width: number,
height: number
) {
return `\t\t${createSVGRect(color, { left, top, width, height })}\n`;
}
export class TextSVGExportMixin extends FabricObjectSVGExportMixin {
_toSVG() {
const offsets = this._getSVGLeftTopOffsets(),
textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft);
return this._wrapSVGTextAndBg(textAndBg);
}
toSVG(reviver: TSVGReviver) {
return this._createBaseSVGMarkup(this._toSVG(), {
reviver,
noStyle: true,
withShadow: true,
});
}
private _getSVGLeftTopOffsets() {
return {
textLeft: -this.width / 2,
textTop: -this.height / 2,
lineTop: this.getHeightOfLine(0),
};
}
private _wrapSVGTextAndBg({
textBgRects,
textSpans,
}: {
textSpans: string[];
textBgRects: string[];
}) {
const noShadow = true,
textDecoration = this.getSvgTextDecoration(this);
return [
textBgRects.join(''),
'\t\t',
textSpans.join(''),
'\n',
];
}
/**
* @private
* @param {Number} textTopOffset Text top offset
* @param {Number} textLeftOffset Text left offset
* @return {Object}
*/
private _getSVGTextAndBg(textTopOffset: number, textLeftOffset: number) {
const textSpans: string[] = [],
textBgRects: string[] = [];
let height = textTopOffset,
lineOffset;
// bounding-box background
this.backgroundColor &&
textBgRects.push(
...createSVGInlineRect(
this.backgroundColor,
-this.width / 2,
-this.height / 2,
this.width,
this.height
)
);
// text and text-background
for (let i = 0, len = this._textLines.length; i < len; i++) {
lineOffset = this._getLineLeftOffset(i);
if (this.direction === 'rtl') {
lineOffset += this.width;
}
if (this.textBackgroundColor || this.styleHas('textBackgroundColor', i)) {
this._setSVGTextLineBg(
textBgRects,
i,
textLeftOffset + lineOffset,
height
);
}
this._setSVGTextLineText(
textSpans,
i,
textLeftOffset + lineOffset,
height
);
height += this.getHeightOfLine(i);
}
return {
textSpans,
textBgRects,
};
}
private _createTextCharSpan(
char: string,
styleDecl: TextStyleDeclaration,
left: number,
top: number
) {
const styleProps = this.getSvgSpanStyles(
styleDecl,
char !== char.trim() || !!char.match(multipleSpacesRegex)
),
fillStyles = styleProps ? `style="${styleProps}"` : '',
dy = styleDecl.deltaY,
dySpan = dy ? ` dy="${toFixed(dy, config.NUM_FRACTION_DIGITS)}" ` : '';
return `${escapeXml(char)}`;
}
private _setSVGTextLineText(
textSpans: string[],
lineIndex: number,
textLeftOffset: number,
textTopOffset: number
) {
const lineHeight = this.getHeightOfLine(lineIndex),
isJustify = this.textAlign.indexOf('justify') !== -1,
line = this._textLines[lineIndex];
let actualStyle,
nextStyle,
charsToRender = '',
charBox,
style,
boxWidth = 0,
timeToRender;
textTopOffset +=
(lineHeight * (1 - this._fontSizeFraction)) / this.lineHeight;
for (let i = 0, len = line.length - 1; i <= len; i++) {
timeToRender = i === len || this.charSpacing;
charsToRender += line[i];
charBox = this.__charBounds[lineIndex][i];
if (boxWidth === 0) {
textLeftOffset += charBox.kernedWidth - charBox.width;
boxWidth += charBox.width;
} else {
boxWidth += charBox.kernedWidth;
}
if (isJustify && !timeToRender) {
if (this._reSpaceAndTab.test(line[i])) {
timeToRender = true;
}
}
if (!timeToRender) {
// if we have charSpacing, we render char by char
actualStyle =
actualStyle || this.getCompleteStyleDeclaration(lineIndex, i);
nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1);
timeToRender = hasStyleChanged(actualStyle, nextStyle, true);
}
if (timeToRender) {
style = this._getStyleDeclaration(lineIndex, i) || {};
textSpans.push(
this._createTextCharSpan(
charsToRender,
style,
textLeftOffset,
textTopOffset
)
);
charsToRender = '';
actualStyle = nextStyle;
if (this.direction === 'rtl') {
textLeftOffset -= boxWidth;
} else {
textLeftOffset += boxWidth;
}
boxWidth = 0;
}
}
}
private _setSVGTextLineBg(
textBgRects: (string | number)[],
i: number,
leftOffset: number,
textTopOffset: number
) {
const line = this._textLines[i],
heightOfLine = this.getHeightOfLine(i) / this.lineHeight;
let boxWidth = 0,
boxStart = 0,
charBox,
currentColor,
lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor');
for (let j = 0; j < line.length; j++) {
charBox = this.__charBounds[i][j];
currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor');
if (currentColor !== lastColor) {
lastColor &&
textBgRects.push(
...createSVGInlineRect(
lastColor,
leftOffset + boxStart,
textTopOffset,
boxWidth,
heightOfLine
)
);
boxStart = charBox.left;
boxWidth = charBox.width;
lastColor = currentColor;
} else {
boxWidth += charBox.kernedWidth;
}
}
currentColor &&
textBgRects.push(
...createSVGInlineRect(
lastColor,
leftOffset + boxStart,
textTopOffset,
boxWidth,
heightOfLine
)
);
}
/**
* @deprecated unused
*/
_getSVGLineTopOffset(lineIndex: number) {
let lineTopOffset = 0,
lastHeight = 0;
for (let j = 0; j < lineIndex; j++) {
lineTopOffset += this.getHeightOfLine(j);
}
lastHeight = this.getHeightOfLine(j);
return {
lineTop: lineTopOffset,
offset:
((this._fontSizeMult - this._fontSizeFraction) * lastHeight) /
(this.lineHeight * this._fontSizeMult),
};
}
/**
* Returns styles-string for svg-export
* @param {Boolean} skipShadow a boolean to skip shadow filter output
* @return {String}
*/
getSvgStyles(skipShadow?: boolean) {
return `${super.getSvgStyles(skipShadow)} white-space: pre;`;
}
}