diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cb9bf9ddc9..55570808635 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) - refactor(TS): `animate` and `AnimationRegistry` to classes [#8297](https://github.com/fabricjs/fabric.js/pull/8297) BREAKING: - return animation instance from animate instead of a cancel function and remove `findAnimationByXXX` from `AnimationRegistry` diff --git a/index.js b/index.js index 1b6debeb8c9..0f372678728 100644 --- a/index.js +++ b/index.js @@ -48,8 +48,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/text.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/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, diff --git a/src/canvas/static_canvas.class.ts b/src/canvas/static_canvas.class.ts index ecc98102541..d0b15af7b78 100644 --- a/src/canvas/static_canvas.class.ts +++ b/src/canvas/static_canvas.class.ts @@ -5,7 +5,6 @@ import { iMatrix, VERSION } from '../constants'; import type { CanvasEvents, 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'; @@ -19,6 +18,7 @@ import { TFiller, TMat2D, TSize, + TSVGReviver, TToCanvasElementOptions, TValidToObjectMethod, } from '../typedefs'; diff --git a/src/mixins/itext.svg_export.ts b/src/mixins/itext.svg_export.ts deleted file mode 100644 index 4d4e4c1ed37..00000000000 --- a/src/mixins/itext.svg_export.ts +++ /dev/null @@ -1,336 +0,0 @@ -//@ts-nocheck - -import { Color } from '../color'; -import { config } from '../config'; -import { FabricObject } from '../shapes/Object/FabricObject'; - -/* _TO_SVG_START_ */ -(function (global) { - var fabric = global.fabric, - toFixed = fabric.util.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); - }, - - /** - * 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, - }); - }, - - /** - * @private - */ - _getSVGLeftTopOffsets: function () { - 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 - * @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); - - // 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, - i, - textLeftOffset + lineOffset, - height - ); - height += this.getHeightOfLine(i); - } - - 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(''); - }, - - _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; - - 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; - } - } - }, - - _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; - } else { - boxWidth += charBox.kernedWidth; - } - } - 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 + '"'; - } - return ( - 'opacity="' + - fillColor.getAlpha() + - '" fill="' + - fillColor.setAlpha(1).toRgb() + - '"' - ); - }, - - /** - * @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), - }; - }, - - /** - * 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;'; - }, - } - ); -})(typeof exports !== 'undefined' ? exports : window); -/* _TO_SVG_END_ */ diff --git a/src/mixins/object.svg_export.ts b/src/mixins/object.svg_export.ts index 40f7bf4d22d..d50ebf27423 100644 --- a/src/mixins/object.svg_export.ts +++ b/src/mixins/object.svg_export.ts @@ -1,32 +1,7 @@ // @ts-nocheck -import { Color } from '../color'; -import { config } from '../config'; +import { TSVGReviver } from '../typedefs'; import { uid } from '../util/internals/uid'; -import { matrixToSVG } from '../util/misc/svgParsing'; -import { toFixed } from '../util/misc/toFixed'; - -export type TSVGReviver = (markup: string) => string; - -/* _TO_SVG_START_ */ - -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; - } -} +import { colorPropToSVG, matrixToSVG } from '../util/misc/svgParsing'; export class FabricObjectSVGExportMixin { /** @@ -54,8 +29,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 +90,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}; ` : ''; @@ -177,25 +152,6 @@ export class FabricObjectSVGExportMixin { return `${svgTransform}${additionalTransform}" `; } - _setSVGBg(textBgRects: string[]) { - if (this.backgroundColor) { - const NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS; - textBgRects.push( - '\t\t\n' - ); - } - } - /** * Returns svg representation of an instance * @param {TSVGReviver} [reviver] Method for further parsing of svg representation. @@ -319,5 +275,3 @@ export class FabricObjectSVGExportMixin { : ''; } } - -/* _TO_SVG_END_ */ diff --git a/src/mixins/text.svg_export.ts b/src/mixins/text.svg_export.ts new file mode 100644 index 00000000000..b5d92ec7cfb --- /dev/null +++ b/src/mixins/text.svg_export.ts @@ -0,0 +1,294 @@ +// @ts-nocheck + +import { config } from '../config'; +import { Text } from '../shapes/text.class'; +import { TSVGReviver } 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 } from './object.svg_export'; +import type { TextStyleDeclaration } from './text_style.mixin'; + +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;`; + } +} + +// TODO: handle differently? +applyMixins(Text, [TextSVGExportMixin]); diff --git a/src/shapes/itext.class.ts b/src/shapes/itext.class.ts index ec8cb4c738d..5dc71fd4f0a 100644 --- a/src/shapes/itext.class.ts +++ b/src/shapes/itext.class.ts @@ -20,10 +20,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 @@ -35,8 +31,8 @@ export type ITextEvents = ObjectEvents & { * @fires cut * @fires paste * - *

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
@@ -55,16 +51,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 { /** @@ -156,7 +152,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); diff --git a/src/typedefs.ts b/src/typedefs.ts index 83989a543c5..50c1369bae8 100644 --- a/src/typedefs.ts +++ b/src/typedefs.ts @@ -87,6 +87,8 @@ export type TCornerPoint = { br: Point; }; +export type TSVGReviver = (markup: string) => string; + export type TValidToObjectMethod = 'toDatalessObject' | 'toObject'; export type TCacheCanvasDimensions = { diff --git a/src/util/misc/svgParsing.ts b/src/util/misc/svgParsing.ts index 60396bf1728..6f93df6fdde 100644 --- a/src/util/misc/svgParsing.ts +++ b/src/util/misc/svgParsing.ts @@ -1,6 +1,12 @@ +import { Color } from '../../color'; import { config } from '../../config'; import { DEFAULT_SVG_FONT_SIZE } from '../../constants'; -import { SupportedSVGUnit, SVGElementName, TMat2D } from '../../typedefs'; +import { + SupportedSVGUnit, + SVGElementName, + TBBox, + TMat2D, +} from '../../typedefs'; import { toFixed } from './toFixed'; /** * Returns array of attributes for given svg that fabric parses @@ -135,3 +141,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 ``; +};