// @ts-nocheck import { TSVGReviver } from '../../typedefs'; import { uid } from '../../util/internals/uid'; import { colorPropToSVG, matrixToSVG } from '../../util/misc/svgParsing'; export class FabricObjectSVGExportMixin { /** * When an object is being exported as SVG as a clippath, a reference inside the SVG is needed. * This reference is a UID in the fabric namespace and is temporary stored here. * @type {String} */ declare clipPathId?: string; /** * Returns styles-string for svg-export * @param {Boolean} skipShadow a boolean to skip shadow filter output * @return {String} */ getSvgStyles(skipShadow?: boolean) { const fillRule = this.fillRule ? this.fillRule : 'nonzero', strokeWidth = this.strokeWidth ? this.strokeWidth : '0', strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none', strokeDashOffset = this.strokeDashOffset ? this.strokeDashOffset : '0', strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', visibility = this.visible ? '' : ' visibility: hidden;', filter = skipShadow ? '' : this.getSvgFilter(), fill = colorPropToSVG('fill', this.fill), stroke = colorPropToSVG('stroke', this.stroke); return [ stroke, 'stroke-width: ', strokeWidth, '; ', 'stroke-dasharray: ', strokeDashArray, '; ', 'stroke-linecap: ', strokeLineCap, '; ', 'stroke-dashoffset: ', strokeDashOffset, '; ', 'stroke-linejoin: ', strokeLineJoin, '; ', 'stroke-miterlimit: ', strokeMiterLimit, '; ', fill, 'fill-rule: ', fillRule, '; ', 'opacity: ', opacity, ';', filter, visibility, ].join(''); } /** * Returns styles-string for svg-export * @param {Object} style the object from which to retrieve style properties * @param {Boolean} useWhiteSpace a boolean to include an additional attribute in the style. * @return {String} */ getSvgSpanStyles(style, useWhiteSpace?: boolean) { const term = '; ', fontFamily = style.fontFamily ? `font-family: ${ style.fontFamily.indexOf("'") === -1 && style.fontFamily.indexOf('"') === -1 ? `'${style.fontFamily}'` : style.fontFamily }${term}` : '', strokeWidth = style.strokeWidth ? `stroke-width: ${style.strokeWidth}${term}` : '', fontSize = style.fontSize ? `font-size: ${style.fontSize}px${term}` : '', fontStyle = style.fontStyle ? `font-style: ${style.fontStyle}${term}` : '', fontWeight = style.fontWeight ? `font-weight: ${style.fontWeight}${term}` : '', 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}; ` : ''; return [ stroke, strokeWidth, fontFamily, fontSize, fontStyle, fontWeight, textDecoration ? `text-decoration: ${textDecoration}${term}` : textDecoration, fill, deltaY, useWhiteSpace ? 'white-space: pre; ' : '', ].join(''); } /** * Returns text-decoration property for svg-export * @param {Object} style the object from which to retrieve style properties * @return {String} */ getSvgTextDecoration(style) { return ['overline', 'underline', 'line-through'] .filter((decoration) => style[decoration.replace('-', '')]) .join(' '); } /** * Returns filter for svg shadow * @return {String} */ getSvgFilter() { return this.shadow ? `filter: url(#SVGID_${this.shadow.id});` : ''; } /** * Returns id attribute for svg output * @return {String} */ getSvgCommons() { return [ this.id ? `id="${this.id}" ` : '', this.clipPath ? `clip-path="url(#${this.clipPath.clipPathId})" ` : '', ].join(''); } /** * Returns transform-string for svg-export * @param {Boolean} use the full transform or the single object one. * @return {String} */ getSvgTransform(full?: boolean, additionalTransform = '') { const transform = full ? this.calcTransformMatrix() : this.calcOwnMatrix(), svgTransform = `transform="${matrixToSVG(transform)}`; return `${svgTransform}${additionalTransform}" `; } /** * Returns svg representation of an instance * @param {TSVGReviver} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toSVG(reviver?: TSVGReviver) { return this._createBaseSVGMarkup(this._toSVG(reviver), { reviver, }); } /** * Returns svg clipPath representation of an instance * @param {TSVGReviver} [reviver] Method for further parsing of svg representation. * @return {String} svg representation of an instance */ toClipPathSVG(reviver?: TSVGReviver) { return ( '\t' + this._createBaseClipPathSVGMarkup(this._toSVG(reviver), { reviver, }) ); } /** * @private */ _createBaseClipPathSVGMarkup( objectMarkup: string[], { reviver, additionalTransform = '', }: { reviver?: TSVGReviver; additionalTransform?: string } = {} ) { const commonPieces = [ this.getSvgTransform(true, additionalTransform), this.getSvgCommons(), ].join(''), // insert commons in the markup, style and svgCommons index = objectMarkup.indexOf('COMMON_PARTS'); objectMarkup[index] = commonPieces; return reviver ? reviver(objectMarkup.join('')) : objectMarkup.join(''); } /** * @private */ _createBaseSVGMarkup( objectMarkup: string[], { noStyle, reviver, withShadow, additionalTransform, }: { noStyle?: boolean; reviver?: TSVGReviver; withShadow?: boolean; additionalTransform?: string; } = {} ) { const styleInfo = noStyle ? '' : `style="${this.getSvgStyles()}" `, shadowInfo = withShadow ? `style="${this.getSvgFilter()}" ` : '', clipPath = this.clipPath, vectorEffect = this.strokeUniform ? 'vector-effect="non-scaling-stroke" ' : '', absoluteClipPath = clipPath && clipPath.absolutePositioned, stroke = this.stroke, fill = this.fill, shadow = this.shadow, markup = [], // insert commons in the markup, style and svgCommons index = objectMarkup.indexOf('COMMON_PARTS'); let clipPathMarkup; if (clipPath) { clipPath.clipPathId = `CLIPPATH_${uid()}`; clipPathMarkup = `\n${clipPath.toClipPathSVG(reviver)}\n`; } if (absoluteClipPath) { markup.push('\n'); } markup.push( '\n' ); const commonPieces = [ styleInfo, vectorEffect, noStyle ? '' : this.addPaintOrder(), ' ', additionalTransform ? `transform="${additionalTransform}" ` : '', ].join(''); objectMarkup[index] = commonPieces; if (fill && fill.toLive) { markup.push(fill.toSVG(this)); } if (stroke && stroke.toLive) { markup.push(stroke.toSVG(this)); } if (shadow) { markup.push(shadow.toSVG(this)); } if (clipPath) { markup.push(clipPathMarkup); } markup.push(objectMarkup.join('')); markup.push('\n'); absoluteClipPath && markup.push('\n'); return reviver ? reviver(markup.join('')) : markup.join(''); } addPaintOrder() { return this.paintFirst !== 'fill' ? ` paint-order="${this.paintFirst}" ` : ''; } }