fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
1 lines • 17.3 kB
Source Map (JSON)
{"version":3,"file":"TextSVGExportMixin.min.mjs","names":[],"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 {\n JUSTIFY,\n TEXT_DECORATION_COLOR,\n TEXT_DECORATION_THICKNESS,\n} from '../Text/constants';\nimport type { FabricText, GraphemeBBox } from './Text';\nimport { STROKE, FILL } from '../../constants';\nimport { createRotateMatrix } from '../../util/misc/matrix';\nimport { radiansToDegrees } from '../../util/misc/radiansDegreesConversion';\nimport { Point } from '../../Point';\nimport { matrixToSVG } from '../../util/misc/svgExport';\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 const textSvg = this._createBaseSVGMarkup(this._toSVG(), {\n reviver,\n noStyle: true,\n withShadow: true,\n }),\n path = this.path;\n if (path) {\n return (\n textSvg +\n path._createBaseSVGMarkup(path._toSVG(), {\n reviver,\n withShadow: true,\n additionalTransform: matrixToSVG(this.calcOwnMatrix()),\n })\n );\n }\n return textSvg;\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 `font-family=\"${escapeXml(this.fontFamily.replace(dblQuoteRegex, \"'\"))}\" `,\n `font-size=\"${escapeXml(this.fontSize)}\" `,\n this.fontStyle ? `font-style=\"${escapeXml(this.fontStyle)}\" ` : '',\n this.fontWeight ? `font-weight=\"${escapeXml(this.fontWeight)}\" ` : '',\n textDecoration ? `text-decoration=\"${textDecoration}\" ` : '',\n this.direction === 'rtl' ? `direction=\"rtl\" ` : '',\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 charBox: GraphemeBBox,\n ) {\n const numFractionDigit = config.NUM_FRACTION_DIGITS;\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, numFractionDigit)}\" ` : '',\n { angle, renderLeft, renderTop, width } = charBox;\n let angleAttr = '';\n if (renderLeft !== undefined) {\n const wBy2 = width / 2;\n angle &&\n (angleAttr = ` rotate=\"${toFixed(radiansToDegrees(angle), numFractionDigit)}\"`);\n const m = createRotateMatrix({ angle: radiansToDegrees(angle!) });\n m[4] = renderLeft!;\n m[5] = renderTop!;\n const renderPoint = new Point(-wBy2, 0).transform(m);\n left = renderPoint.x;\n top = renderPoint.y;\n }\n\n return `<tspan x=\"${toFixed(left, numFractionDigit)}\" y=\"${toFixed(\n top,\n numFractionDigit,\n )}\" ${dySpan}${angleAttr}${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 || this.path;\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 or a path, 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 charBox,\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 * 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 const objectLevelTextDecorationColor = this[TEXT_DECORATION_COLOR]\n ? ` text-decoration-color: ${escapeXml(this[TEXT_DECORATION_COLOR])};`\n : '';\n return `${super.getSvgStyles(skipShadow)} text-decoration-thickness: ${toFixed((this.textDecorationThickness * this.getObjectScaling().y) / 10, config.NUM_FRACTION_DIGITS)}%;${objectLevelTextDecorationColor} 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 textDecorationThickness,\n textDecorationColor,\n linethrough,\n overline,\n underline,\n } = style;\n\n const textDecoration = this.getSvgTextDecoration({\n underline: underline ?? this.underline,\n overline: overline ?? this.overline,\n linethrough: linethrough ?? this.linethrough,\n });\n const thickness =\n textDecorationThickness || this[TEXT_DECORATION_THICKNESS];\n const decorationColor = textDecorationColor || this[TEXT_DECORATION_COLOR];\n return [\n stroke ? colorPropToSVG(STROKE, stroke) : '',\n strokeWidth ? `stroke-width: ${escapeXml(strokeWidth)}; ` : '',\n fontFamily\n ? `font-family: ${\n !fontFamily.includes(\"'\") && !fontFamily.includes('\"')\n ? `'${escapeXml(fontFamily)}'`\n : escapeXml(fontFamily)\n }; `\n : '',\n fontSize ? `font-size: ${escapeXml(fontSize)}px; ` : '',\n fontStyle ? `font-style: ${escapeXml(fontStyle)}; ` : '',\n fontWeight ? `font-weight: ${escapeXml(fontWeight)}; ` : '',\n textDecoration\n ? `text-decoration: ${textDecoration}; text-decoration-thickness: ${toFixed((thickness * this.getObjectScaling().y) / 10, config.NUM_FRACTION_DIGITS)}%;${\n decorationColor\n ? ` text-decoration-color: ${escapeXml(decorationColor)};`\n : ''\n } `\n : '',\n fill ? colorPropToSVG(FILL, fill) : '',\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"],"mappings":"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"}