{"version":3,"file":"TextSVGExportMixin.min.mjs","sources":["../../../../src/shapes/Text/TextSVGExportMixin.ts"],"sourcesContent":["import { config } from '../../config';\nimport type { TSVGReviver } from '../../typedefs';\nimport { escapeXml } from '../../util/lang_string';\nimport { colorPropToSVG, createSVGRect } from '../../util/misc/svgParsing';\nimport { hasStyleChanged } from '../../util/misc/textStyles';\nimport { toFixed } from '../../util/misc/toFixed';\nimport { FabricObjectSVGExportMixin } from '../Object/FabricObjectSVGExportMixin';\nimport { type TextStyleDeclaration } from './StyledText';\nimport { JUSTIFY } from '../Text/constants';\nimport type { FabricText } from './Text';\nimport { STROKE, FILL } from '../../constants';\n\nconst multipleSpacesRegex = /  +/g;\nconst dblQuoteRegex = /\"/g;\n\nfunction createSVGInlineRect(\n  color: string,\n  left: number,\n  top: number,\n  width: number,\n  height: number,\n) {\n  return `\\t\\t${createSVGRect(color, { left, top, width, height })}\\n`;\n}\n\nexport class TextSVGExportMixin extends FabricObjectSVGExportMixin {\n  _toSVG(this: TextSVGExportMixin & FabricText): string[] {\n    const offsets = this._getSVGLeftTopOffsets(),\n      textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft);\n    return this._wrapSVGTextAndBg(textAndBg);\n  }\n\n  toSVG(this: TextSVGExportMixin & FabricText, reviver?: TSVGReviver): string {\n    return this._createBaseSVGMarkup(this._toSVG(), {\n      reviver,\n      noStyle: true,\n      withShadow: true,\n    });\n  }\n\n  private _getSVGLeftTopOffsets(this: TextSVGExportMixin & FabricText) {\n    return {\n      textLeft: -this.width / 2,\n      textTop: -this.height / 2,\n      lineTop: this.getHeightOfLine(0),\n    };\n  }\n\n  private _wrapSVGTextAndBg(\n    this: TextSVGExportMixin & FabricText,\n    {\n      textBgRects,\n      textSpans,\n    }: {\n      textSpans: string[];\n      textBgRects: string[];\n    },\n  ) {\n    const noShadow = true,\n      textDecoration = this.getSvgTextDecoration(this);\n    return [\n      textBgRects.join(''),\n      '\\t\\t<text xml:space=\"preserve\" ',\n      this.fontFamily\n        ? `font-family=\"${this.fontFamily.replace(dblQuoteRegex, \"'\")}\" `\n        : '',\n      this.fontSize ? `font-size=\"${this.fontSize}\" ` : '',\n      this.fontStyle ? `font-style=\"${this.fontStyle}\" ` : '',\n      this.fontWeight ? `font-weight=\"${this.fontWeight}\" ` : '',\n      textDecoration ? `text-decoration=\"${textDecoration}\" ` : '',\n      this.direction === 'rtl' ? `direction=\"${this.direction}\" ` : '',\n      'style=\"',\n      this.getSvgStyles(noShadow),\n      '\"',\n      this.addPaintOrder(),\n      ' >',\n      textSpans.join(''),\n      '</text>\\n',\n    ];\n  }\n\n  /**\n   * @private\n   * @param {Number} textTopOffset Text top offset\n   * @param {Number} textLeftOffset Text left offset\n   * @return {Object}\n   */\n  private _getSVGTextAndBg(\n    this: TextSVGExportMixin & FabricText,\n    textTopOffset: number,\n    textLeftOffset: number,\n  ) {\n    const textSpans: string[] = [],\n      textBgRects: string[] = [];\n    let height = textTopOffset,\n      lineOffset;\n\n    // bounding-box background\n    this.backgroundColor &&\n      textBgRects.push(\n        ...createSVGInlineRect(\n          this.backgroundColor,\n          -this.width / 2,\n          -this.height / 2,\n          this.width,\n          this.height,\n        ),\n      );\n\n    // text and text-background\n    for (let i = 0, len = this._textLines.length; i < len; i++) {\n      lineOffset = this._getLineLeftOffset(i);\n      if (this.direction === 'rtl') {\n        lineOffset += this.width;\n      }\n      if (this.textBackgroundColor || this.styleHas('textBackgroundColor', i)) {\n        this._setSVGTextLineBg(\n          textBgRects,\n          i,\n          textLeftOffset + lineOffset,\n          height,\n        );\n      }\n      this._setSVGTextLineText(\n        textSpans,\n        i,\n        textLeftOffset + lineOffset,\n        height,\n      );\n      height += this.getHeightOfLine(i);\n    }\n\n    return {\n      textSpans,\n      textBgRects,\n    };\n  }\n\n  private _createTextCharSpan(\n    this: TextSVGExportMixin & FabricText,\n    char: string,\n    styleDecl: TextStyleDeclaration,\n    left: number,\n    top: number,\n  ) {\n    const styleProps = this.getSvgSpanStyles(\n        styleDecl,\n        char !== char.trim() || !!char.match(multipleSpacesRegex),\n      ),\n      fillStyles = styleProps ? `style=\"${styleProps}\"` : '',\n      dy = styleDecl.deltaY,\n      dySpan = dy ? ` dy=\"${toFixed(dy, config.NUM_FRACTION_DIGITS)}\" ` : '';\n\n    return `<tspan x=\"${toFixed(\n      left,\n      config.NUM_FRACTION_DIGITS,\n    )}\" y=\"${toFixed(\n      top,\n      config.NUM_FRACTION_DIGITS,\n    )}\" ${dySpan}${fillStyles}>${escapeXml(char)}</tspan>`;\n  }\n\n  private _setSVGTextLineText(\n    this: TextSVGExportMixin & FabricText,\n    textSpans: string[],\n    lineIndex: number,\n    textLeftOffset: number,\n    textTopOffset: number,\n  ) {\n    const lineHeight = this.getHeightOfLine(lineIndex),\n      isJustify = this.textAlign.includes(JUSTIFY),\n      line = this._textLines[lineIndex];\n    let actualStyle,\n      nextStyle,\n      charsToRender = '',\n      charBox,\n      style,\n      boxWidth = 0,\n      timeToRender;\n\n    textTopOffset +=\n      (lineHeight * (1 - this._fontSizeFraction)) / this.lineHeight;\n    for (let i = 0, len = line.length - 1; i <= len; i++) {\n      timeToRender = i === len || this.charSpacing;\n      charsToRender += line[i];\n      charBox = this.__charBounds[lineIndex][i];\n      if (boxWidth === 0) {\n        textLeftOffset += charBox.kernedWidth - charBox.width;\n        boxWidth += charBox.width;\n      } else {\n        boxWidth += charBox.kernedWidth;\n      }\n      if (isJustify && !timeToRender) {\n        if (this._reSpaceAndTab.test(line[i])) {\n          timeToRender = true;\n        }\n      }\n      if (!timeToRender) {\n        // if we have charSpacing, we render char by char\n        actualStyle =\n          actualStyle || this.getCompleteStyleDeclaration(lineIndex, i);\n        nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1);\n        timeToRender = hasStyleChanged(actualStyle, nextStyle, true);\n      }\n      if (timeToRender) {\n        style = this._getStyleDeclaration(lineIndex, i);\n        textSpans.push(\n          this._createTextCharSpan(\n            charsToRender,\n            style,\n            textLeftOffset,\n            textTopOffset,\n          ),\n        );\n        charsToRender = '';\n        actualStyle = nextStyle;\n        if (this.direction === 'rtl') {\n          textLeftOffset -= boxWidth;\n        } else {\n          textLeftOffset += boxWidth;\n        }\n        boxWidth = 0;\n      }\n    }\n  }\n\n  private _setSVGTextLineBg(\n    this: TextSVGExportMixin & FabricText,\n    textBgRects: (string | number)[],\n    i: number,\n    leftOffset: number,\n    textTopOffset: number,\n  ) {\n    const line = this._textLines[i],\n      heightOfLine = this.getHeightOfLine(i) / this.lineHeight;\n    let boxWidth = 0,\n      boxStart = 0,\n      currentColor,\n      lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor');\n    for (let j = 0; j < line.length; j++) {\n      const { left, width, kernedWidth } = this.__charBounds[i][j];\n      currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor');\n      if (currentColor !== lastColor) {\n        lastColor &&\n          textBgRects.push(\n            ...createSVGInlineRect(\n              lastColor,\n              leftOffset + boxStart,\n              textTopOffset,\n              boxWidth,\n              heightOfLine,\n            ),\n          );\n        boxStart = left;\n        boxWidth = width;\n        lastColor = currentColor;\n      } else {\n        boxWidth += kernedWidth;\n      }\n    }\n    currentColor &&\n      textBgRects.push(\n        ...createSVGInlineRect(\n          lastColor,\n          leftOffset + boxStart,\n          textTopOffset,\n          boxWidth,\n          heightOfLine,\n        ),\n      );\n  }\n\n  /**\n   * @deprecated unused\n   */\n  _getSVGLineTopOffset(\n    this: TextSVGExportMixin & FabricText,\n    lineIndex: number,\n  ) {\n    let lineTopOffset = 0,\n      j;\n    for (j = 0; j < lineIndex; j++) {\n      lineTopOffset += this.getHeightOfLine(j);\n    }\n    const lastHeight = this.getHeightOfLine(j);\n    return {\n      lineTop: lineTopOffset,\n      offset:\n        ((this._fontSizeMult - this._fontSizeFraction) * lastHeight) /\n        (this.lineHeight * this._fontSizeMult),\n    };\n  }\n\n  /**\n   * Returns styles-string for svg-export\n   * @param {Boolean} skipShadow a boolean to skip shadow filter output\n   * @return {String}\n   */\n  getSvgStyles(this: TextSVGExportMixin & FabricText, skipShadow?: boolean) {\n    return `${super.getSvgStyles(skipShadow)} white-space: pre;`;\n  }\n\n  /**\n   * Returns styles-string for svg-export\n   * @param {Object} style the object from which to retrieve style properties\n   * @param {Boolean} useWhiteSpace a boolean to include an additional attribute in the style.\n   * @return {String}\n   */\n  getSvgSpanStyles(\n    this: TextSVGExportMixin & FabricText,\n    style: TextStyleDeclaration,\n    useWhiteSpace?: boolean,\n  ) {\n    const {\n      fontFamily,\n      strokeWidth,\n      stroke,\n      fill,\n      fontSize,\n      fontStyle,\n      fontWeight,\n      deltaY,\n    } = style;\n\n    const textDecoration = this.getSvgTextDecoration(style);\n\n    return [\n      stroke ? colorPropToSVG(STROKE, stroke) : '',\n      strokeWidth ? `stroke-width: ${strokeWidth}; ` : '',\n      fontFamily\n        ? `font-family: ${\n            !fontFamily.includes(\"'\") && !fontFamily.includes('\"')\n              ? `'${fontFamily}'`\n              : fontFamily\n          }; `\n        : '',\n      fontSize ? `font-size: ${fontSize}px; ` : '',\n      fontStyle ? `font-style: ${fontStyle}; ` : '',\n      fontWeight ? `font-weight: ${fontWeight}; ` : '',\n      textDecoration ? `text-decoration: ${textDecoration}; ` : textDecoration,\n      fill ? colorPropToSVG(FILL, fill) : '',\n      deltaY ? `baseline-shift: ${-deltaY}; ` : '',\n      useWhiteSpace ? 'white-space: pre; ' : '',\n    ].join('');\n  }\n\n  /**\n   * Returns text-decoration property for svg-export\n   * @param {Object} style the object from which to retrieve style properties\n   * @return {String}\n   */\n  getSvgTextDecoration(\n    this: TextSVGExportMixin & FabricText,\n    style: TextStyleDeclaration,\n  ) {\n    return (['overline', 'underline', 'line-through'] as const)\n      .filter(\n        (decoration) =>\n          style[\n            decoration.replace('-', '') as\n              | 'overline'\n              | 'underline'\n              | 'linethrough'\n          ],\n      )\n      .join(' ');\n  }\n}\n"],"names":["multipleSpacesRegex","dblQuoteRegex","createSVGInlineRect","color","left","top","width","height","concat","createSVGRect","TextSVGExportMixin","FabricObjectSVGExportMixin","_toSVG","offsets","this","_getSVGLeftTopOffsets","textAndBg","_getSVGTextAndBg","textTop","textLeft","_wrapSVGTextAndBg","toSVG","reviver","_createBaseSVGMarkup","noStyle","withShadow","lineTop","getHeightOfLine","_ref","textBgRects","textSpans","textDecoration","getSvgTextDecoration","join","fontFamily","replace","fontSize","fontStyle","fontWeight","direction","getSvgStyles","addPaintOrder","textTopOffset","textLeftOffset","lineOffset","backgroundColor","push","i","len","_textLines","length","_getLineLeftOffset","textBackgroundColor","styleHas","_setSVGTextLineBg","_setSVGTextLineText","_createTextCharSpan","char","styleDecl","styleProps","getSvgSpanStyles","trim","match","fillStyles","dy","deltaY","dySpan","toFixed","config","NUM_FRACTION_DIGITS","escapeXml","lineIndex","lineHeight","isJustify","textAlign","includes","JUSTIFY","line","actualStyle","nextStyle","charBox","style","timeToRender","charsToRender","boxWidth","_fontSizeFraction","charSpacing","__charBounds","kernedWidth","_reSpaceAndTab","test","getCompleteStyleDeclaration","hasStyleChanged","_getStyleDeclaration","leftOffset","heightOfLine","currentColor","boxStart","lastColor","getValueOfPropertyAt","j","_getSVGLineTopOffset","lineTopOffset","lastHeight","offset","_fontSizeMult","skipShadow","super","useWhiteSpace","strokeWidth","stroke","fill","colorPropToSVG","STROKE","FILL","filter","decoration"],"mappings":"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"}