From 92463069ac3a1e6caa67dec858bd70319388dbf2 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 4 Dec 2022 10:30:55 +0200 Subject: [PATCH 01/15] transform --- src/mixins/itext.svg_export.ts | 587 ++++++++++++++++----------------- 1 file changed, 285 insertions(+), 302 deletions(-) diff --git a/src/mixins/itext.svg_export.ts b/src/mixins/itext.svg_export.ts index ac8234b303f..8f87a788e92 100644 --- a/src/mixins/itext.svg_export.ts +++ b/src/mixins/itext.svg_export.ts @@ -5,332 +5,315 @@ import { config } from '../config'; import { FabricObject } from '../shapes/fabricObject.class'; /* _TO_SVG_START_ */ -(function (global) { - var fabric = global.fabric, - toFixed = fabric.util.toFixed, - multipleSpacesRegex = / +/g; +var fabric = global.fabric, + toFixed = toFixed, + multipleSpacesRegex = / +/g; - fabric.util.object.extend( - fabric.Text.prototype, - /** @lends fabric.Text.prototype */ { - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - _toSVG: function () { - var offsets = this._getSVGLeftTopOffsets(), - textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); - return this._wrapSVGTextAndBg(textAndBg); - }, +export function TextIMixinGenerator(Klass) { + return class TextIMixin extends Klass { + /** + * Returns SVG representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + _toSVG() { + var offsets = this._getSVGLeftTopOffsets(), + textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); + return this._wrapSVGTextAndBg(textAndBg); + } - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG: function (reviver) { - return this._createBaseSVGMarkup(this._toSVG(), { - reviver: reviver, - noStyle: true, - withShadow: true, - }); - }, + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG(reviver) { + return this._createBaseSVGMarkup(this._toSVG(), { + reviver: reviver, + noStyle: true, + withShadow: true, + }); + } - /** - * @private - */ - _getSVGLeftTopOffsets: function () { - return { - textLeft: -this.width / 2, - textTop: -this.height / 2, - lineTop: this.getHeightOfLine(0), - }; - }, + /** + * @private + */ + _getSVGLeftTopOffsets() { + return { + textLeft: -this.width / 2, + textTop: -this.height / 2, + lineTop: this.getHeightOfLine(0), + }; + } - /** - * @private - */ - _wrapSVGTextAndBg: function (textAndBg) { - var noShadow = true, - textDecoration = this.getSvgTextDecoration(this); - return [ - textAndBg.textBgRects.join(''), - '\t\t', - textAndBg.textSpans.join(''), - '\n', - ]; - }, + /** + * @private + */ + _wrapSVGTextAndBg(textAndBg) { + var noShadow = true, + textDecoration = this.getSvgTextDecoration(this); + return [ + textAndBg.textBgRects.join(''), + '\t\t', + textAndBg.textSpans.join(''), + '\n', + ]; + } - /** - * @private - * @param {Number} textTopOffset Text top offset - * @param {Number} textLeftOffset Text left offset - * @return {Object} - */ - _getSVGTextAndBg: function (textTopOffset, textLeftOffset) { - var textSpans = [], - textBgRects = [], - height = textTopOffset, - lineOffset; - // bounding-box background - this._setSVGBg(textBgRects); + /** + * @private + * @param {Number} textTopOffset Text top offset + * @param {Number} textLeftOffset Text left offset + * @return {Object} + */ + _getSVGTextAndBg(textTopOffset, textLeftOffset) { + var textSpans = [], + textBgRects = [], + height = textTopOffset, + lineOffset; + // bounding-box background + this._setSVGBg(textBgRects); - // text and text-background - for (var 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, + // text and text-background + for (var 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 ); - height += this.getHeightOfLine(i); } + this._setSVGTextLineText( + textSpans, + i, + textLeftOffset + lineOffset, + height + ); + height += this.getHeightOfLine(i); + } - return { - textSpans: textSpans, - textBgRects: textBgRects, - }; - }, + return { + textSpans: textSpans, + textBgRects: textBgRects, + }; + } - /** - * @private - */ - _createTextCharSpan: function (_char, styleDecl, left, top) { - var shouldUseWhitespace = - _char !== _char.trim() || _char.match(multipleSpacesRegex), - styleProps = this.getSvgSpanStyles(styleDecl, shouldUseWhitespace), - fillStyles = styleProps ? 'style="' + styleProps + '"' : '', - dy = styleDecl.deltaY, - dySpan = '', - NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS; - if (dy) { - dySpan = ' dy="' + toFixed(dy, NUM_FRACTION_DIGITS) + '" '; - } - return [ - '', - fabric.util.string.escapeXml(_char), - '', - ].join(''); - }, + /** + * @private + */ + _createTextCharSpan(_char, styleDecl, left, top) { + var shouldUseWhitespace = + _char !== _char.trim() || _char.match(multipleSpacesRegex), + styleProps = this.getSvgSpanStyles(styleDecl, shouldUseWhitespace), + fillStyles = styleProps ? 'style="' + styleProps + '"' : '', + dy = styleDecl.deltaY, + dySpan = '', + NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS; + if (dy) { + dySpan = ' dy="' + toFixed(dy, NUM_FRACTION_DIGITS) + '" '; + } + return [ + '', + string.escapeXml(_char), + '', + ].join(''); + } - _setSVGTextLineText: function ( - textSpans, - lineIndex, - textLeftOffset, - textTopOffset - ) { - // set proper line offset - var lineHeight = this.getHeightOfLine(lineIndex), - isJustify = this.textAlign.indexOf('justify') !== -1, - actualStyle, - nextStyle, - charsToRender = '', - charBox, - style, - boxWidth = 0, - line = this._textLines[lineIndex], - timeToRender; + _setSVGTextLineText(textSpans, lineIndex, textLeftOffset, textTopOffse) { + var lineHeight = this.getHeightOfLine(lineIndex), + isJustify = this.textAlign.indexOf('justify') !== -1, + actualStyle, + nextStyle, + charsToRender = '', + charBox, + style, + boxWidth = 0, + line = this._textLines[lineIndex], + timeToRender; - textTopOffset += - (lineHeight * (1 - this._fontSizeFraction)) / this.lineHeight; - for (var 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 = fabric.util.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; + textTopOffset += + (lineHeight * (1 - this._fontSizeFraction)) / this.lineHeight; + for (var 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; } } - }, - - _pushTextBgRect: function (textBgRects, color, left, top, width, height) { - var NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS; - textBgRects.push( - '\t\t\n' - ); - }, - - _setSVGTextLineBg: function (textBgRects, i, leftOffset, textTopOffset) { - var line = this._textLines[i], - heightOfLine = this.getHeightOfLine(i) / this.lineHeight, - boxWidth = 0, - boxStart = 0, - charBox, - currentColor, - lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); - for (var j = 0, jlen = line.length; j < jlen; j++) { - charBox = this.__charBounds[i][j]; - currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); - if (currentColor !== lastColor) { - lastColor && - this._pushTextBgRect( - textBgRects, - lastColor, - leftOffset + boxStart, - textTopOffset, - boxWidth, - heightOfLine - ); - boxStart = charBox.left; - boxWidth = charBox.width; - lastColor = currentColor; + 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 { - boxWidth += charBox.kernedWidth; + textLeftOffset += boxWidth; } + boxWidth = 0; } - currentColor && - this._pushTextBgRect( - textBgRects, - currentColor, - leftOffset + boxStart, - textTopOffset, - boxWidth, - heightOfLine - ); - }, + } + } - /** - * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values - * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 - * - * @private - * @param {*} value - * @return {String} - */ - _getFillAttributes: function (value) { - var fillColor = - value && typeof value === 'string' ? new Color(value) : ''; - if ( - !fillColor || - !fillColor.getSource() || - fillColor.getAlpha() === 1 - ) { - return 'fill="' + value + '"'; + _pushTextBgRect(textBgRects, color, left, top, width, height) { + var NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS; + textBgRects.push( + '\t\t\n' + ); + } + + _setSVGTextLineBg(textBgRects, i, leftOffset, textTopOffset) { + var line = this._textLines[i], + heightOfLine = this.getHeightOfLine(i) / this.lineHeight, + boxWidth = 0, + boxStart = 0, + charBox, + currentColor, + lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); + for (var j = 0, jlen = line.length; j < jlen; j++) { + charBox = this.__charBounds[i][j]; + currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); + if (currentColor !== lastColor) { + lastColor && + this._pushTextBgRect( + textBgRects, + lastColor, + leftOffset + boxStart, + textTopOffset, + boxWidth, + heightOfLine + ); + boxStart = charBox.left; + boxWidth = charBox.width; + lastColor = currentColor; + } else { + boxWidth += charBox.kernedWidth; } - return ( - 'opacity="' + - fillColor.getAlpha() + - '" fill="' + - fillColor.setAlpha(1).toRgb() + - '"' + } + currentColor && + this._pushTextBgRect( + textBgRects, + currentColor, + leftOffset + boxStart, + textTopOffset, + boxWidth, + heightOfLine ); - }, + } - /** - * @private - */ - _getSVGLineTopOffset: function (lineIndex) { - var lineTopOffset = 0, - lastHeight = 0; - for (var 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), - }; - }, + /** + * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values + * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 + * + * @private + * @param {*} value + * @return {String} + */ + _getFillAttributes(value) { + var fillColor = + value && typeof value === 'string' ? new Color(value) : ''; + if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { + return 'fill="' + value + '"'; + } + return ( + 'opacity="' + + fillColor.getAlpha() + + '" fill="' + + fillColor.setAlpha(1).toRgb() + + '"' + ); + } - /** - * Returns styles-string for svg-export - * @param {Boolean} skipShadow a boolean to skip shadow filter output - * @return {String} - */ - getSvgStyles: function (skipShadow) { - var svgStyle = FabricObject.prototype.getSvgStyles.call( - this, - skipShadow - ); - return svgStyle + ' white-space: pre;'; - }, + /** + * @private + */ + _getSVGLineTopOffset(lineIndex) { + var lineTopOffset = 0, + lastHeight = 0; + for (var 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) { + var svgStyle = FabricObject.prototype.getSvgStyles.call(this, skipShadow); + return svgStyle + ' white-space: pre;'; } - ); -})(typeof exports !== 'undefined' ? exports : window); + }; +} + +Text = TextIMixinGenerator(Text); + /* _TO_SVG_END_ */ From 55f6a987a2eba492defa2254cafadc876ed40d48 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 4 Dec 2022 11:17:50 +0200 Subject: [PATCH 02/15] migrate --- src/mixins/itext.svg_export.ts | 513 +++++++++++++++----------------- src/mixins/object.svg_export.ts | 62 +++- 2 files changed, 282 insertions(+), 293 deletions(-) diff --git a/src/mixins/itext.svg_export.ts b/src/mixins/itext.svg_export.ts index 8f87a788e92..11aa5f35f9a 100644 --- a/src/mixins/itext.svg_export.ts +++ b/src/mixins/itext.svg_export.ts @@ -1,319 +1,272 @@ -//@ts-nocheck +// @ts-nocheck -import { Color } from '../color'; import { config } from '../config'; -import { FabricObject } from '../shapes/fabricObject.class'; +import { IText } from '../shapes/itext.class'; +import { applyMixins } from '../util/applyMixins'; +import { escapeXml } from '../util/lang_string'; +import { hasStyleChanged } from '../util/misc/textStyles'; +import { toFixed } from '../util/misc/toFixed'; +import { FabricObjectSVGExportMixin, SVGReviver } from './object.svg_export'; +import { TextStyleDeclaration } from './text_style.mixin'; -/* _TO_SVG_START_ */ -var fabric = global.fabric, - toFixed = toFixed, - multipleSpacesRegex = / +/g; +const multipleSpacesRegex = / +/g; +const dblQuoteRegex = /"/g; -export function TextIMixinGenerator(Klass) { - return class TextIMixin extends Klass { - /** - * Returns SVG representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - _toSVG() { - var offsets = this._getSVGLeftTopOffsets(), - textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); - return this._wrapSVGTextAndBg(textAndBg); - } +export class ITextSVGExportMixin extends FabricObjectSVGExportMixin { + _toSVG() { + const offsets = this._getSVGLeftTopOffsets(), + textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); + return this._wrapSVGTextAndBg(textAndBg); + } - /** - * Returns svg representation of an instance - * @param {Function} [reviver] Method for further parsing of svg representation. - * @return {String} svg representation of an instance - */ - toSVG(reviver) { - return this._createBaseSVGMarkup(this._toSVG(), { - reviver: reviver, - noStyle: true, - withShadow: true, - }); - } + toSVG(reviver: SVGReviver) { + 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 _getSVGLeftTopOffsets() { + return { + textLeft: -this.width / 2, + textTop: -this.height / 2, + lineTop: this.getHeightOfLine(0), + }; + } - /** - * @private - */ - _wrapSVGTextAndBg(textAndBg) { - var noShadow = true, - textDecoration = this.getSvgTextDecoration(this); - return [ - textAndBg.textBgRects.join(''), - '\t\t', - textAndBg.textSpans.join(''), - '\n', - ]; - } + 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} - */ - _getSVGTextAndBg(textTopOffset, textLeftOffset) { - var textSpans = [], - textBgRects = [], - height = textTopOffset, - lineOffset; - // bounding-box background - this._setSVGBg(textBgRects); + /** + * @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._setSVGBg(textBgRects); - // text and text-background - for (var 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, + // 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 ); - height += this.getHeightOfLine(i); } - - return { - textSpans: textSpans, - textBgRects: textBgRects, - }; + this._setSVGTextLineText( + textSpans, + i, + textLeftOffset + lineOffset, + height + ); + height += this.getHeightOfLine(i); } - /** - * @private - */ - _createTextCharSpan(_char, styleDecl, left, top) { - var shouldUseWhitespace = - _char !== _char.trim() || _char.match(multipleSpacesRegex), - styleProps = this.getSvgSpanStyles(styleDecl, shouldUseWhitespace), - fillStyles = styleProps ? 'style="' + styleProps + '"' : '', - dy = styleDecl.deltaY, - dySpan = '', - NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS; - if (dy) { - dySpan = ' dy="' + toFixed(dy, NUM_FRACTION_DIGITS) + '" '; - } - return [ - '', - string.escapeXml(_char), - '', - ].join(''); - } + return { + textSpans, + textBgRects, + }; + } - _setSVGTextLineText(textSpans, lineIndex, textLeftOffset, textTopOffse) { - var lineHeight = this.getHeightOfLine(lineIndex), - isJustify = this.textAlign.indexOf('justify') !== -1, - actualStyle, - nextStyle, - charsToRender = '', - charBox, - style, - boxWidth = 0, - line = this._textLines[lineIndex], - timeToRender; + 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)}" ` : ''; - textTopOffset += - (lineHeight * (1 - this._fontSizeFraction)) / this.lineHeight; - for (var 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); + 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) { - 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; + } + 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; } } + } - _pushTextBgRect(textBgRects, color, left, top, width, height) { - var NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS; - textBgRects.push( - '\t\t\n' - ); - } - - _setSVGTextLineBg(textBgRects, i, leftOffset, textTopOffset) { - var line = this._textLines[i], - heightOfLine = this.getHeightOfLine(i) / this.lineHeight, - boxWidth = 0, - boxStart = 0, - charBox, - currentColor, - lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); - for (var j = 0, jlen = line.length; j < jlen; j++) { - charBox = this.__charBounds[i][j]; - currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); - if (currentColor !== lastColor) { - lastColor && - this._pushTextBgRect( - textBgRects, + 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( + ...this._createBgRect( lastColor, leftOffset + boxStart, textTopOffset, boxWidth, heightOfLine - ); - boxStart = charBox.left; - boxWidth = charBox.width; - lastColor = currentColor; - } else { - boxWidth += charBox.kernedWidth; - } + ) + ); + boxStart = charBox.left; + boxWidth = charBox.width; + lastColor = currentColor; + } else { + boxWidth += charBox.kernedWidth; } - currentColor && - this._pushTextBgRect( - textBgRects, - currentColor, + } + currentColor && + textBgRects.push( + ...this._createBgRect( + lastColor, leftOffset + boxStart, textTopOffset, boxWidth, heightOfLine - ); - } - - /** - * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values - * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 - * - * @private - * @param {*} value - * @return {String} - */ - _getFillAttributes(value) { - var fillColor = - value && typeof value === 'string' ? new Color(value) : ''; - if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { - return 'fill="' + value + '"'; - } - return ( - 'opacity="' + - fillColor.getAlpha() + - '" fill="' + - fillColor.setAlpha(1).toRgb() + - '"' + ) ); - } + } - /** - * @private - */ - _getSVGLineTopOffset(lineIndex) { - var lineTopOffset = 0, - lastHeight = 0; - for (var 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), - }; + /** + * @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) { - var svgStyle = FabricObject.prototype.getSvgStyles.call(this, skipShadow); - return svgStyle + ' white-space: pre;'; - } - }; + /** + * 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;`; + } } -Text = TextIMixinGenerator(Text); - -/* _TO_SVG_END_ */ +// TODO: handle differently? +applyMixins(IText, [ITextSVGExportMixin]); diff --git a/src/mixins/object.svg_export.ts b/src/mixins/object.svg_export.ts index 72566784f38..d838bb15314 100644 --- a/src/mixins/object.svg_export.ts +++ b/src/mixins/object.svg_export.ts @@ -5,7 +5,7 @@ import { uid } from '../util/internals/uid'; import { matrixToSVG } from '../util/misc/svgParsing'; import { toFixed } from '../util/misc/toFixed'; -type SVGReviver = (markup: string) => string; +export type SVGReviver = (markup: string) => string; /* _TO_SVG_START_ */ @@ -170,21 +170,57 @@ export class FabricObjectSVGExportMixin { return `${svgTransform}${additionalTransform}" `; } + /** + * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values + * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 + * + * @param {*} value + * @return {String} + */ + protected _getFillAttributes(value?: string) { + const fillColor = + value && typeof value === 'string' ? new Color(value) : ''; + if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { + return `fill="${value}"`; + } + return `opacity="${fillColor.getAlpha()}" fill="${fillColor + .setAlpha(1) + .toRgb()}"`; + } + + protected _createBgRect( + color: string, + left: number, + top: number, + width: number, + height: number + ) { + const NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS; + return [ + '\t\t\n', + ]; + } + _setSVGBg(textBgRects: string[]) { if (this.backgroundColor) { - const NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS; textBgRects.push( - '\t\t\n' + ...this._createBgRect( + this.backgroundColor, + -this.width / 2, + -this.height / 2, + this.width, + this.height + ) ); } } From b8e830533103e3e629e62e68afce2545282e4f37 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 4 Dec 2022 11:49:44 +0200 Subject: [PATCH 03/15] Update itext.class.ts --- src/shapes/itext.class.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/shapes/itext.class.ts b/src/shapes/itext.class.ts index 98f1dfb9d38..29bcca0a1cc 100644 --- a/src/shapes/itext.class.ts +++ b/src/shapes/itext.class.ts @@ -21,10 +21,6 @@ export type ITextEvents = ObjectEvents & { }; /** - * IText class (introduced in v1.4) Events are also fired with "text:" - * prefix when observing canvas. - * @class IText - * * @fires changed * @fires selection:changed * @fires editing:entered @@ -36,11 +32,8 @@ export type ITextEvents = ObjectEvents & { * @fires cut * @fires paste * - * @return {IText} thisArg - * @see {@link IText#initialize} for constructor definition - * - *

Supported key combinations:

- *
+ * #### Supported key combinations
+ * ```
  *   Move cursor:                    left, right, up, down
  *   Select character:               shift + left, shift + right
  *   Select text vertically:         shift + up, shift + down
@@ -59,16 +52,16 @@ export type ITextEvents = ObjectEvents & {
  *   Cut text:                       ctrl/cmd + x
  *   Select entire text:             ctrl/cmd + a
  *   Quit editing                    tab or esc
- * 
+ * ``` * - *

Supported mouse/touch combination

- *
+ * #### Supported mouse/touch combination
+ * ```
  *   Position cursor:                click/touch
  *   Create selection:               click/touch & drag
  *   Create selection:               click & shift + click
  *   Select word:                    double click
  *   Select line:                    triple click
- * 
+ * ``` */ export class IText extends ITextClickBehaviorMixin { /** From b5a4fa18520baac10b703e4f2b8add43c0d1b236 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 4 Dec 2022 12:10:03 +0200 Subject: [PATCH 04/15] init --- src/mixins/itext.svg_export.ts | 39 +++++++++++++++++--- src/mixins/object.svg_export.ts | 64 +++++---------------------------- src/shapes/itext.class.ts | 1 - 3 files changed, 43 insertions(+), 61 deletions(-) diff --git a/src/mixins/itext.svg_export.ts b/src/mixins/itext.svg_export.ts index 11aa5f35f9a..155fddc910b 100644 --- a/src/mixins/itext.svg_export.ts +++ b/src/mixins/itext.svg_export.ts @@ -6,12 +6,33 @@ import { applyMixins } from '../util/applyMixins'; import { escapeXml } from '../util/lang_string'; import { hasStyleChanged } from '../util/misc/textStyles'; import { toFixed } from '../util/misc/toFixed'; -import { FabricObjectSVGExportMixin, SVGReviver } from './object.svg_export'; +import { + FabricObjectSVGExportMixin, + getSvgColorString, + SVGReviver, +} from './object.svg_export'; import { TextStyleDeclaration } from './text_style.mixin'; const multipleSpacesRegex = / +/g; const dblQuoteRegex = /"/g; +function createSVGRect( + color: string, + left: number, + top: number, + width: number, + height: number +) { + const rounding = config.NUM_FRACTION_DIGITS; + return `\t\t\n`; +} + export class ITextSVGExportMixin extends FabricObjectSVGExportMixin { _toSVG() { const offsets = this._getSVGLeftTopOffsets(), @@ -76,8 +97,18 @@ export class ITextSVGExportMixin extends FabricObjectSVGExportMixin { textBgRects: string[] = []; let height = textTopOffset, lineOffset; + // bounding-box background - this._setSVGBg(textBgRects); + this.backgroundColor && + textBgRects.push( + ...createSVGRect( + 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++) { @@ -213,7 +244,7 @@ export class ITextSVGExportMixin extends FabricObjectSVGExportMixin { if (currentColor !== lastColor) { lastColor && textBgRects.push( - ...this._createBgRect( + ...createSVGRect( lastColor, leftOffset + boxStart, textTopOffset, @@ -230,7 +261,7 @@ export class ITextSVGExportMixin extends FabricObjectSVGExportMixin { } currentColor && textBgRects.push( - ...this._createBgRect( + ...createSVGRect( lastColor, leftOffset + boxStart, textTopOffset, diff --git a/src/mixins/object.svg_export.ts b/src/mixins/object.svg_export.ts index d838bb15314..68c8f3b3d97 100644 --- a/src/mixins/object.svg_export.ts +++ b/src/mixins/object.svg_export.ts @@ -9,7 +9,14 @@ export type SVGReviver = (markup: string) => string; /* _TO_SVG_START_ */ -function getSvgColorString(prop: string, value?: any) { +/** + * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values + * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 + * @param prop + * @param value + * @returns + */ +export function getSvgColorString(prop: string, value?: any) { if (!value) { return `${prop}: none; `; } else if (value.toLive) { @@ -170,61 +177,6 @@ export class FabricObjectSVGExportMixin { return `${svgTransform}${additionalTransform}" `; } - /** - * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values - * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 - * - * @param {*} value - * @return {String} - */ - protected _getFillAttributes(value?: string) { - const fillColor = - value && typeof value === 'string' ? new Color(value) : ''; - if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { - return `fill="${value}"`; - } - return `opacity="${fillColor.getAlpha()}" fill="${fillColor - .setAlpha(1) - .toRgb()}"`; - } - - protected _createBgRect( - color: string, - left: number, - top: number, - width: number, - height: number - ) { - const NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS; - return [ - '\t\t\n', - ]; - } - - _setSVGBg(textBgRects: string[]) { - if (this.backgroundColor) { - textBgRects.push( - ...this._createBgRect( - this.backgroundColor, - -this.width / 2, - -this.height / 2, - this.width, - this.height - ) - ); - } - } - /** * Returns svg representation of an instance * @param {SVGReviver} [reviver] Method for further parsing of svg representation. diff --git a/src/shapes/itext.class.ts b/src/shapes/itext.class.ts index 29bcca0a1cc..371372abf54 100644 --- a/src/shapes/itext.class.ts +++ b/src/shapes/itext.class.ts @@ -153,7 +153,6 @@ export class IText extends ITextClickBehaviorMixin { * Constructor * @param {String} text Text string * @param {Object} [options] Options object - * @return {IText} thisArg */ constructor(text: string, options: object) { super(text, options); From 3b7055a0a4c4d67844222bcfa492c8a59cefdd71 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 4 Dec 2022 12:17:49 +0200 Subject: [PATCH 05/15] fix imports --- index.js | 2 +- src/mixins/itext.svg_export.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 75a173672f4..e4e83ee7f74 100644 --- a/index.js +++ b/index.js @@ -55,8 +55,8 @@ import './src/filters/gamma_filter.class'; // optional image_filters import './src/filters/composed_filter.class'; // optional image_filters import './src/filters/hue_rotation.class'; // optional image_filters import './src/shapes/text.class'; // optional text +import './src/mixins/itext.svg_export'; // optional svg import './src/shapes/itext.class'; // optional itext -import './src/mixins/itext.svg_export'; // optional itext import './src/shapes/textbox.class'; // optional textbox import './src/controls'; // optional interaction import './src/brushes'; // optional freedrawing diff --git a/src/mixins/itext.svg_export.ts b/src/mixins/itext.svg_export.ts index 155fddc910b..6a6d5ab7d14 100644 --- a/src/mixins/itext.svg_export.ts +++ b/src/mixins/itext.svg_export.ts @@ -1,7 +1,7 @@ // @ts-nocheck import { config } from '../config'; -import { IText } from '../shapes/itext.class'; +import { Text } from '../shapes/text.class'; import { applyMixins } from '../util/applyMixins'; import { escapeXml } from '../util/lang_string'; import { hasStyleChanged } from '../util/misc/textStyles'; @@ -11,7 +11,7 @@ import { getSvgColorString, SVGReviver, } from './object.svg_export'; -import { TextStyleDeclaration } from './text_style.mixin'; +import type { TextStyleDeclaration } from './text_style.mixin'; const multipleSpacesRegex = / +/g; const dblQuoteRegex = /"/g; @@ -33,7 +33,7 @@ function createSVGRect( )}" height="${toFixed(height, rounding)}">\n`; } -export class ITextSVGExportMixin extends FabricObjectSVGExportMixin { +export class TextSVGExportMixin extends FabricObjectSVGExportMixin { _toSVG() { const offsets = this._getSVGLeftTopOffsets(), textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); @@ -300,4 +300,4 @@ export class ITextSVGExportMixin extends FabricObjectSVGExportMixin { } // TODO: handle differently? -applyMixins(IText, [ITextSVGExportMixin]); +applyMixins(Text, [TextSVGExportMixin]); From cb7404ff3646d7113b2583897aeebc0db73c4414 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 4 Dec 2022 12:18:23 +0200 Subject: [PATCH 06/15] rename file --- index.js | 2 +- src/mixins/{itext.svg_export.ts => text.svg_export.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/mixins/{itext.svg_export.ts => text.svg_export.ts} (100%) diff --git a/index.js b/index.js index e4e83ee7f74..a5d23f64221 100644 --- a/index.js +++ b/index.js @@ -55,7 +55,7 @@ import './src/filters/gamma_filter.class'; // optional image_filters import './src/filters/composed_filter.class'; // optional image_filters import './src/filters/hue_rotation.class'; // optional image_filters import './src/shapes/text.class'; // optional text -import './src/mixins/itext.svg_export'; // optional svg +import './src/mixins/text.svg_export'; // optional svg import './src/shapes/itext.class'; // optional itext import './src/shapes/textbox.class'; // optional textbox import './src/controls'; // optional interaction diff --git a/src/mixins/itext.svg_export.ts b/src/mixins/text.svg_export.ts similarity index 100% rename from src/mixins/itext.svg_export.ts rename to src/mixins/text.svg_export.ts From 3bed0abe90bf5752c5c52eee8eb309f01c167842 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 4 Dec 2022 12:22:06 +0200 Subject: [PATCH 07/15] Revert "rename file" This reverts commit cb7404ff3646d7113b2583897aeebc0db73c4414. @asturur revert this commit to rename the file once you are done reviewing (done for diff readability) --- index.js | 2 +- src/mixins/{text.svg_export.ts => itext.svg_export.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/mixins/{text.svg_export.ts => itext.svg_export.ts} (100%) diff --git a/index.js b/index.js index a5d23f64221..e4e83ee7f74 100644 --- a/index.js +++ b/index.js @@ -55,7 +55,7 @@ import './src/filters/gamma_filter.class'; // optional image_filters import './src/filters/composed_filter.class'; // optional image_filters import './src/filters/hue_rotation.class'; // optional image_filters import './src/shapes/text.class'; // optional text -import './src/mixins/text.svg_export'; // optional svg +import './src/mixins/itext.svg_export'; // optional svg import './src/shapes/itext.class'; // optional itext import './src/shapes/textbox.class'; // optional textbox import './src/controls'; // optional interaction diff --git a/src/mixins/text.svg_export.ts b/src/mixins/itext.svg_export.ts similarity index 100% rename from src/mixins/text.svg_export.ts rename to src/mixins/itext.svg_export.ts From e1f6c03f9da4d3888119bbd14ababde8dd2b254f Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 4 Dec 2022 12:39:24 +0200 Subject: [PATCH 08/15] refactor/rename --- src/mixins/itext.svg_export.ts | 24 ++++--------- src/mixins/object.svg_export.ts | 36 +++---------------- src/util/misc/svgParsing.ts | 62 +++++++++++++++++++++++++++------ 3 files changed, 64 insertions(+), 58 deletions(-) diff --git a/src/mixins/itext.svg_export.ts b/src/mixins/itext.svg_export.ts index 6a6d5ab7d14..edf4822620f 100644 --- a/src/mixins/itext.svg_export.ts +++ b/src/mixins/itext.svg_export.ts @@ -4,33 +4,23 @@ import { config } from '../config'; import { Text } from '../shapes/text.class'; import { applyMixins } from '../util/applyMixins'; 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, - getSvgColorString, - SVGReviver, -} from './object.svg_export'; +import { FabricObjectSVGExportMixin, SVGReviver } from './object.svg_export'; import type { TextStyleDeclaration } from './text_style.mixin'; const multipleSpacesRegex = / +/g; const dblQuoteRegex = /"/g; -function createSVGRect( +function createSVGInlineRect( color: string, left: number, top: number, width: number, height: number ) { - const rounding = config.NUM_FRACTION_DIGITS; - return `\t\t\n`; + return `\t\t${createSVGRect(color, { left, top, width, height })}\n`; } export class TextSVGExportMixin extends FabricObjectSVGExportMixin { @@ -101,7 +91,7 @@ export class TextSVGExportMixin extends FabricObjectSVGExportMixin { // bounding-box background this.backgroundColor && textBgRects.push( - ...createSVGRect( + ...createSVGInlineRect( this.backgroundColor, -this.width / 2, -this.height / 2, @@ -244,7 +234,7 @@ export class TextSVGExportMixin extends FabricObjectSVGExportMixin { if (currentColor !== lastColor) { lastColor && textBgRects.push( - ...createSVGRect( + ...createSVGInlineRect( lastColor, leftOffset + boxStart, textTopOffset, @@ -261,7 +251,7 @@ export class TextSVGExportMixin extends FabricObjectSVGExportMixin { } currentColor && textBgRects.push( - ...createSVGRect( + ...createSVGInlineRect( lastColor, leftOffset + boxStart, textTopOffset, diff --git a/src/mixins/object.svg_export.ts b/src/mixins/object.svg_export.ts index 68c8f3b3d97..acfcec0c0c6 100644 --- a/src/mixins/object.svg_export.ts +++ b/src/mixins/object.svg_export.ts @@ -2,39 +2,13 @@ import { Color } from '../color'; import { config } from '../config'; import { uid } from '../util/internals/uid'; -import { matrixToSVG } from '../util/misc/svgParsing'; +import { colorPropToSVG, matrixToSVG } from '../util/misc/svgParsing'; import { toFixed } from '../util/misc/toFixed'; export type SVGReviver = (markup: string) => string; /* _TO_SVG_START_ */ -/** - * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values - * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 - * @param prop - * @param value - * @returns - */ -export function getSvgColorString(prop: string, value?: any) { - if (!value) { - return `${prop}: none; `; - } else if (value.toLive) { - return `${prop}: url(#SVGID_${value.id}); `; - } else { - const color = new Color(value), - opacity = color.getAlpha(); - - let str = `${prop}: ${color.toRgb()}; `; - - if (opacity !== 1) { - //change the color in rgb + opacity - str += `${prop}-opacity: ${opacity.toString()}; `; - } - return str; - } -} - export class FabricObjectSVGExportMixin { /** * Returns styles-string for svg-export @@ -54,8 +28,8 @@ export class FabricObjectSVGExportMixin { opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', visibility = this.visible ? '' : ' visibility: hidden;', filter = skipShadow ? '' : this.getSvgFilter(), - fill = getSvgColorString('fill', this.fill), - stroke = getSvgColorString('stroke', this.stroke); + fill = colorPropToSVG('fill', this.fill), + stroke = colorPropToSVG('stroke', this.stroke); return [ stroke, @@ -115,8 +89,8 @@ export class FabricObjectSVGExportMixin { fontWeight = style.fontWeight ? `font-weight: ${style.fontWeight}${term}` : '', - fill = style.fill ? getSvgColorString('fill', style.fill) : '', - stroke = style.stroke ? getSvgColorString('stroke', style.stroke) : '', + fill = style.fill ? colorPropToSVG('fill', style.fill) : '', + stroke = style.stroke ? colorPropToSVG('stroke', style.stroke) : '', textDecoration = this.getSvgTextDecoration(style), deltaY = style.deltaY ? `baseline-shift: ${-style.deltaY}; ` : ''; diff --git a/src/util/misc/svgParsing.ts b/src/util/misc/svgParsing.ts index f92ec279e84..46867cbb32e 100644 --- a/src/util/misc/svgParsing.ts +++ b/src/util/misc/svgParsing.ts @@ -1,11 +1,17 @@ -import { fabric } from '../../../HEADER'; -import { SVGElementName, SupportedSVGUnit, TMat2D } from '../../typedefs'; +import { Color } from '../../color'; +import { config } from '../../config'; import { DEFAULT_SVG_FONT_SIZE } from '../../constants'; +import { Group } from '../../shapes/group.class'; +import type { FabricObject } from '../../shapes/fabricObject.class'; +import { + SupportedSVGUnit, + SVGElementName, + TBBox, + TMat2D, +} from '../../typedefs'; import { toFixed } from './toFixed'; -import { config } from '../../config'; /** * Returns array of attributes for given svg that fabric parses - * @memberOf fabric.util * @param {SVGElementName} type Type of svg element (eg. 'circle') * @return {Array} string names of supported attributes */ @@ -79,15 +85,14 @@ export const parseUnit = (value: string, fontSize: number) => { /** * Groups SVG elements (usually those retrieved from SVG document) * @static - * @memberOf fabric.util - * @param {Array} elements fabric.Object(s) parsed from svg, to group - * @return {fabric.Object|fabric.Group} + * @param {Array} elements FabricObject(s) parsed from svg, to group + * @return {FabricObject | Group} */ -export const groupSVGElements = (elements: any[]) => { +export const groupSVGElements = (elements: FabricObject[]) => { if (elements && elements.length === 1) { return elements[0]; } - return new fabric.Group(elements); + return new Group(elements); }; const enum MeetOrSlice { @@ -142,7 +147,6 @@ export const parsePreserveAspectRatioAttribute = ( /** * given an array of 6 number returns something like `"matrix(...numbers)"` - * @memberOf fabric.util * @param {TMat2D} transform an array with 6 numbers * @return {String} transform matrix for svg */ @@ -152,3 +156,41 @@ export const matrixToSVG = (transform: TMat2D) => .map((value) => toFixed(value, config.NUM_FRACTION_DIGITS)) .join(' ') + ')'; + +/** + * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values + * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 + * @param prop + * @param value + * @returns + */ +export const colorPropToSVG = (prop: string, value?: any) => { + if (!value) { + return `${prop}: none; `; + } else if (value.toLive) { + return `${prop}: url(#SVGID_${value.id}); `; + } else { + const color = new Color(value), + opacity = color.getAlpha(); + + let str = `${prop}: ${color.toRgb()}; `; + + if (opacity !== 1) { + //change the color in rgb + opacity + str += `${prop}-opacity: ${opacity.toString()}; `; + } + return str; + } +}; + +export const createSVGRect = ( + color: string, + { left, top, width, height }: TBBox, + precision = config.NUM_FRACTION_DIGITS +) => { + const svgColor = colorPropToSVG('fill', color); + const [x, y, w, h] = [left, top, width, height].map((value) => + toFixed(value, precision) + ); + return ``; +}; From e3ed93dfe0d00cdf2f966db42a7b9e2719021f63 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 4 Dec 2022 12:48:38 +0200 Subject: [PATCH 09/15] extract type `SVGReviver` --- src/mixins/itext.svg_export.ts | 3 ++- src/mixins/object.svg_export.ts | 10 +--------- src/typedefs.ts | 2 ++ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/mixins/itext.svg_export.ts b/src/mixins/itext.svg_export.ts index edf4822620f..c87a844e2df 100644 --- a/src/mixins/itext.svg_export.ts +++ b/src/mixins/itext.svg_export.ts @@ -2,12 +2,13 @@ import { config } from '../config'; import { Text } from '../shapes/text.class'; +import { SVGReviver } from '../typedefs'; import { applyMixins } from '../util/applyMixins'; 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, SVGReviver } from './object.svg_export'; +import { FabricObjectSVGExportMixin } from './object.svg_export'; import type { TextStyleDeclaration } from './text_style.mixin'; const multipleSpacesRegex = / +/g; diff --git a/src/mixins/object.svg_export.ts b/src/mixins/object.svg_export.ts index acfcec0c0c6..c0c36021545 100644 --- a/src/mixins/object.svg_export.ts +++ b/src/mixins/object.svg_export.ts @@ -1,13 +1,7 @@ // @ts-nocheck -import { Color } from '../color'; -import { config } from '../config'; +import { SVGReviver } from '../typedefs'; import { uid } from '../util/internals/uid'; import { colorPropToSVG, matrixToSVG } from '../util/misc/svgParsing'; -import { toFixed } from '../util/misc/toFixed'; - -export type SVGReviver = (markup: string) => string; - -/* _TO_SVG_START_ */ export class FabricObjectSVGExportMixin { /** @@ -274,5 +268,3 @@ export class FabricObjectSVGExportMixin { : ''; } } - -/* _TO_SVG_END_ */ diff --git a/src/typedefs.ts b/src/typedefs.ts index 26d6f8eeac2..ae6e6b960de 100644 --- a/src/typedefs.ts +++ b/src/typedefs.ts @@ -82,3 +82,5 @@ export type TCornerPoint = { bl: Point; br: Point; }; + +export type SVGReviver = (markup: string) => string; From af55d8de21c2c5df04e7f8cd21ac87c4b66287ff Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 4 Dec 2022 12:50:19 +0200 Subject: [PATCH 10/15] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7054d42c3c2..0af73d2f580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [next] +- chore(TS): migrate text SVG export mixin [#8486](https://github.com/fabricjs/fabric.js/pull/8486) - chore(): refactor `Object.__uid++` => `uid()` [#8482](https://github.com/fabricjs/fabric.js/pull/8482) - chore(TS): migrate object mixins to TS [#8414](https://github.com/fabricjs/fabric.js/pull/8414) - chore(TS): migrate filters [#8474](https://github.com/fabricjs/fabric.js/pull/8474) From ebbbffd5e84d091c07c805b4cd5dd93b1b8d3f20 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 4 Dec 2022 12:53:45 +0200 Subject: [PATCH 11/15] rename file reverts 3bed0abe9 @asturur revert this if you want. I thought the diff would be readable without it but it is unreadable regardless --- index.js | 2 +- src/mixins/{itext.svg_export.ts => text.svg_export.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/mixins/{itext.svg_export.ts => text.svg_export.ts} (100%) diff --git a/index.js b/index.js index e4e83ee7f74..a5d23f64221 100644 --- a/index.js +++ b/index.js @@ -55,7 +55,7 @@ import './src/filters/gamma_filter.class'; // optional image_filters import './src/filters/composed_filter.class'; // optional image_filters import './src/filters/hue_rotation.class'; // optional image_filters import './src/shapes/text.class'; // optional text -import './src/mixins/itext.svg_export'; // optional svg +import './src/mixins/text.svg_export'; // optional svg import './src/shapes/itext.class'; // optional itext import './src/shapes/textbox.class'; // optional textbox import './src/controls'; // optional interaction diff --git a/src/mixins/itext.svg_export.ts b/src/mixins/text.svg_export.ts similarity index 100% rename from src/mixins/itext.svg_export.ts rename to src/mixins/text.svg_export.ts From dca534a161cefdf6e8c48b19789ab8e794d2a781 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 4 Dec 2022 16:25:46 +0200 Subject: [PATCH 12/15] hadle in #8488 --- src/util/misc/svgParsing.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/util/misc/svgParsing.ts b/src/util/misc/svgParsing.ts index 46867cbb32e..b5edc4b5d78 100644 --- a/src/util/misc/svgParsing.ts +++ b/src/util/misc/svgParsing.ts @@ -1,8 +1,6 @@ import { Color } from '../../color'; import { config } from '../../config'; import { DEFAULT_SVG_FONT_SIZE } from '../../constants'; -import { Group } from '../../shapes/group.class'; -import type { FabricObject } from '../../shapes/fabricObject.class'; import { SupportedSVGUnit, SVGElementName, @@ -85,14 +83,15 @@ export const parseUnit = (value: string, fontSize: number) => { /** * Groups SVG elements (usually those retrieved from SVG document) * @static - * @param {Array} elements FabricObject(s) parsed from svg, to group - * @return {FabricObject | Group} + * @memberOf fabric.util + * @param {Array} elements fabric.Object(s) parsed from svg, to group + * @return {fabric.Object|fabric.Group} */ -export const groupSVGElements = (elements: FabricObject[]) => { +export const groupSVGElements = (elements: any[]) => { if (elements && elements.length === 1) { return elements[0]; } - return new Group(elements); + return new fabric.Group(elements); }; const enum MeetOrSlice { From d02b4a8ad743908c81d8ad16b2b5d4aaa6f15e95 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 4 Dec 2022 16:27:50 +0200 Subject: [PATCH 13/15] Update svgParsing.ts --- src/util/misc/svgParsing.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/misc/svgParsing.ts b/src/util/misc/svgParsing.ts index b5edc4b5d78..1b4fab7e39f 100644 --- a/src/util/misc/svgParsing.ts +++ b/src/util/misc/svgParsing.ts @@ -1,3 +1,4 @@ +import { fabric } from '../../../HEADER'; import { Color } from '../../color'; import { config } from '../../config'; import { DEFAULT_SVG_FONT_SIZE } from '../../constants'; From f127fc9bb0f51e4c7c0f7c3a6d1c6d281b9aa3ec Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 5 Dec 2022 13:03:15 +0200 Subject: [PATCH 14/15] imports fix --- src/mixins/text.svg_export.ts | 4 ++-- src/static_canvas.class.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mixins/text.svg_export.ts b/src/mixins/text.svg_export.ts index c87a844e2df..b5d92ec7cfb 100644 --- a/src/mixins/text.svg_export.ts +++ b/src/mixins/text.svg_export.ts @@ -2,7 +2,7 @@ import { config } from '../config'; import { Text } from '../shapes/text.class'; -import { SVGReviver } from '../typedefs'; +import { TSVGReviver } from '../typedefs'; import { applyMixins } from '../util/applyMixins'; import { escapeXml } from '../util/lang_string'; import { createSVGRect } from '../util/misc/svgParsing'; @@ -31,7 +31,7 @@ export class TextSVGExportMixin extends FabricObjectSVGExportMixin { return this._wrapSVGTextAndBg(textAndBg); } - toSVG(reviver: SVGReviver) { + toSVG(reviver: TSVGReviver) { return this._createBaseSVGMarkup(this._toSVG(), { reviver, noStyle: true, diff --git a/src/static_canvas.class.ts b/src/static_canvas.class.ts index e9097b70360..a965ed9f6b5 100644 --- a/src/static_canvas.class.ts +++ b/src/static_canvas.class.ts @@ -4,7 +4,6 @@ import { iMatrix, VERSION } from './constants'; import type { StaticCanvasEvents } from './EventTypeDefs'; import { Gradient } from './gradient'; import { createCollectionMixin } from './mixins/collection.mixin'; -import { TSVGReviver } from './mixins/object.svg_export'; import { CommonMethods } from './mixins/shared_methods.mixin'; import { Pattern } from './pattern.class'; import { Point } from './point.class'; @@ -16,6 +15,7 @@ import type { TFiller, TMat2D, TSize, + TSVGReviver, TValidToObjectMethod, } from './typedefs'; import { cancelAnimFrame, requestAnimFrame } from './util/animate'; From 57e188ec3fe78d393673e21581644b69ad3a16e6 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 1 Jan 2023 00:08:25 +0100 Subject: [PATCH 15/15] saved conflicts --- src/canvas/canvas.class.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/canvas/canvas.class.ts b/src/canvas/canvas.class.ts index b49cba36201..e7083fe2483 100644 --- a/src/canvas/canvas.class.ts +++ b/src/canvas/canvas.class.ts @@ -35,9 +35,8 @@ import { setStyle } from '../util/dom_style'; import type { BaseBrush } from '../brushes/base_brush.class'; import type { Textbox } from '../shapes/textbox.class'; import { pick } from '../util/misc/pick'; -import { TSVGReviver } from '../mixins/object.svg_export'; +import { TSVGReviver } from '../typedefs'; import { sendPointToPlane } from '../util/misc/planeChange'; -import { createCanvasElement } from '../util/misc/dom'; type TDestroyedCanvas = Omit< SelectableCanvas,