diff --git a/src/parser/applyViewboxTransform.ts b/src/parser/applyViewboxTransform.ts index c485c20bd0a..dbe7150bfa8 100644 --- a/src/parser/applyViewboxTransform.ts +++ b/src/parser/applyViewboxTransform.ts @@ -1,6 +1,6 @@ //@ts-nocheck import { svgNS } from './constants'; -import { parsePreserveAspectRatioAttribute, parseUnit } from '../util'; +import { parsePreserveAspectRatioAttribute, parseUnit } from '../util/misc/svgParsing'; import { svgViewBoxElementsRegEx, reViewBoxAttrValue } from './constants'; /** diff --git a/src/parser/normalizeValue.ts b/src/parser/normalizeValue.ts index 0291630b9ee..4cadee7a9ce 100644 --- a/src/parser/normalizeValue.ts +++ b/src/parser/normalizeValue.ts @@ -1,5 +1,6 @@ //@ts-nocheck -import { multiplyTransformMatrices, parseUnit } from '../util'; +import { multiplyTransformMatrices } from '../util/misc/matrix'; +import { parseUnit } from '../util/misc/svgParsing'; import { parseTransformAttribute } from "./parseTransformAttribute"; diff --git a/src/parser/parseAttributes.ts b/src/parser/parseAttributes.ts index 08166afdd4a..f0abd5776f9 100644 --- a/src/parser/parseAttributes.ts +++ b/src/parser/parseAttributes.ts @@ -1,6 +1,6 @@ //@ts-nocheck import { DEFAULT_SVG_FONT_SIZE } from '../constants'; -import { parseUnit } from '../util'; +import { parseUnit } from '../util/misc/svgParsing'; import { cPath, fSize, svgValidParentsRegEx } from './constants'; import { getGlobalStylesForElement } from "./getGlobalStylesForElement"; import { normalizeAttr } from './normalizeAttr'; diff --git a/src/parser/parseFontDeclaration.ts b/src/parser/parseFontDeclaration.ts index c86c04cbbca..de747c8808a 100644 --- a/src/parser/parseFontDeclaration.ts +++ b/src/parser/parseFontDeclaration.ts @@ -1,5 +1,5 @@ //@ts-nocheck -import { parseUnit } from '../util'; +import { parseUnit } from '../util/misc/svgParsing'; import { reFontDeclaration } from './constants'; /** diff --git a/src/parser/parseSVGDocument.ts b/src/parser/parseSVGDocument.ts index 25f2cb8459c..b65751a7b86 100644 --- a/src/parser/parseSVGDocument.ts +++ b/src/parser/parseSVGDocument.ts @@ -1,7 +1,6 @@ //@ts-nocheck import { fabric } from '../../HEADER'; -import { toArray } from '../util'; import { applyViewboxTransform } from "./applyViewboxTransform"; import { clipPaths, cssRules, gradientDefs, svgInvalidAncestorsRegEx, svgValidTagNamesRegEx } from "./constants"; import { getCSSRules } from './getCSSRules'; @@ -32,7 +31,7 @@ export function parseSVGDocument(doc, callback, reviver, parsingOptions) { } parseUseDirectives(doc); - let svgUid = fabric.Object.__uid++, i, len, options = applyViewboxTransform(doc), descendants = toArray(doc.getElementsByTagName('*')); + let svgUid = fabric.Object.__uid++, i, len, options = applyViewboxTransform(doc), descendants = fabric.util.toArray(doc.getElementsByTagName('*')); options.crossOrigin = parsingOptions && parsingOptions.crossOrigin; options.svgUid = svgUid; options.signal = parsingOptions && parsingOptions.signal; @@ -62,7 +61,7 @@ export function parseSVGDocument(doc, callback, reviver, parsingOptions) { return el.nodeName.replace('svg:', '') === 'clipPath'; }).forEach(function (el) { const id = el.getAttribute('id'); - localClipPaths[id] = toArray(el.getElementsByTagName('*')).filter(function (el) { + localClipPaths[id] = fabric.util.toArray(el.getElementsByTagName('*')).filter(function (el) { return svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', '')); }); }); diff --git a/src/parser/parseTransformAttribute.ts b/src/parser/parseTransformAttribute.ts index 5144e882198..0156d9bc255 100644 --- a/src/parser/parseTransformAttribute.ts +++ b/src/parser/parseTransformAttribute.ts @@ -1,7 +1,8 @@ //@ts-nocheck import { iMatrix } from '../constants'; import { commaWsp,reNum } from './constants'; -import { degreesToRadians, multiplyTransformMatrices } from '../util'; +import { multiplyTransformMatrices } from '../util/misc/matrix'; +import { degreesToRadians } from '../util/misc/radiansDegreesConversion'; import { rotateMatrix } from './rotateMatrix'; import { scaleMatrix } from './scaleMatrix'; import { skewMatrix } from './skewMatrix'; diff --git a/src/parser/rotateMatrix.ts b/src/parser/rotateMatrix.ts index ef449413bf8..f09e490cf54 100644 --- a/src/parser/rotateMatrix.ts +++ b/src/parser/rotateMatrix.ts @@ -1,9 +1,10 @@ //@ts-nocheck -import { cos, sin } from '../util'; - +import { cos } from '../util/misc/cos'; +import { sin } from '../util/misc/sin'; export function rotateMatrix(matrix, args) { - let cosValue = cos(args[0]), sinValue = sin(args[0]), x = 0, y = 0; + const cosValue = cos(args[0]), sinValue = sin(args[0]); + let x = 0, y = 0; if (args.length === 3) { x = args[1]; y = args[2]; diff --git a/src/parser/setStrokeFillOpacity.ts b/src/parser/setStrokeFillOpacity.ts index 2a1d3e742da..4ac99337ccb 100644 --- a/src/parser/setStrokeFillOpacity.ts +++ b/src/parser/setStrokeFillOpacity.ts @@ -1,7 +1,7 @@ //@ts-nocheck import { fabric } from '../../HEADER'; import { Color } from '../color'; -import { toFixed } from '../util'; +import { toFixed } from '../util/misc/toFixed'; import { colorAttributes } from './constants'; /** diff --git a/src/parser/skewMatrix.ts b/src/parser/skewMatrix.ts index 7e9da7eec7f..b5a8c5e2752 100644 --- a/src/parser/skewMatrix.ts +++ b/src/parser/skewMatrix.ts @@ -1,5 +1,5 @@ //@ts-nocheck -import { degreesToRadians } from '../util'; +import { degreesToRadians } from '../util/misc/radiansDegreesConversion'; export function skewMatrix(matrix, args, pos) { diff --git a/src/point.class.ts b/src/point.class.ts index acbf1589fcb..ec7be726a2a 100644 --- a/src/point.class.ts +++ b/src/point.class.ts @@ -362,6 +362,14 @@ export class Point { return fabric.util.rotateVector(this.subtract(origin), radians).add(origin); } + /** + * Apply transform t to point p + * @static + * @memberOf fabric.util + * @param {TMat2D} t The transform + * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied + * @return {Point} The transformed point + */ transform(t: TMat2D, ignoreOffset: boolean): Point { return new Point( t[0] * this.x + t[2] * this.y + (ignoreOffset ? 0 : t[4]), diff --git a/src/shapes/object.class.ts b/src/shapes/object.class.ts index b8dad9f2c79..46fd6fd9613 100644 --- a/src/shapes/object.class.ts +++ b/src/shapes/object.class.ts @@ -1,5 +1,6 @@ //@ts-nocheck import { Point } from '../point.class'; +import { capValue } from '../util/misc/capValue'; (function(global) { var fabric = global.fabric || (global.fabric = { }), @@ -692,7 +693,6 @@ import { Point } from '../point.class'; return dims; } var ar = width / height, limitedDims = fabric.util.limitDimsByArea(ar, perfLimitSizeTotal), - capValue = fabric.util.capValue, x = capValue(min, limitedDims.x, max), y = capValue(min, limitedDims.y, max); if (width > x) { diff --git a/src/typedefs.ts b/src/typedefs.ts index 7b2498bf94e..240ff09f8e0 100644 --- a/src/typedefs.ts +++ b/src/typedefs.ts @@ -17,4 +17,25 @@ export const enum StrokeLineJoin { round = 'round', } +export const enum ImageFormat { + jpeg = 'jpeg', + jpg = 'jpeg', + png = 'png', +} + +export const enum SVGElementName { + linearGradient = 'linearGradient', + radialGradient = 'radialGradient', + stop = 'stop', +} + +export const enum SupportedSVGUnit { + mm = 'mm', + cm = 'cm', + in = 'in', + pt = 'pt', + pc = 'pc', + em = 'em', +} + export type TMat2D = [number, number, number, number, number, number]; diff --git a/src/util/misc/capValue.ts b/src/util/misc/capValue.ts new file mode 100644 index 00000000000..10df7f18892 --- /dev/null +++ b/src/util/misc/capValue.ts @@ -0,0 +1 @@ +export const capValue = (min: number, value: number, max: number) => Math.max(min, Math.min(value, max)); diff --git a/src/util/misc/dom.ts b/src/util/misc/dom.ts new file mode 100644 index 00000000000..b944733458d --- /dev/null +++ b/src/util/misc/dom.ts @@ -0,0 +1,44 @@ +import { fabric } from '../../../HEADER'; +import { ImageFormat } from '../../typedefs'; +/** + * Creates canvas element + * @static + * @memberOf fabric.util + * @return {CanvasElement} initialized canvas element + */ +export const createCanvasElement = (): HTMLCanvasElement => fabric.document.createElement('canvas'); + +/** + * Creates image element (works on client and node) + * @static + * @memberOf fabric.util + * @return {HTMLImageElement} HTML image element + */ +export const createImage = () =>fabric.document.createElement('img'); + +/** + * Creates a canvas element that is a copy of another and is also painted + * @param {CanvasElement} canvas to copy size and content of + * @static + * @memberOf fabric.util + * @return {CanvasElement} initialized canvas element + */ +export const copyCanvasElement = (canvas: HTMLCanvasElement): HTMLCanvasElement => { + const newCanvas = createCanvasElement(); + newCanvas.width = canvas.width; + newCanvas.height = canvas.height; + newCanvas.getContext('2d')?.drawImage(canvas, 0, 0); + return newCanvas; +}; + +/** + * since 2.6.0 moved from canvas instance to utility. + * possibly useless + * @param {CanvasElement} canvasEl to copy size and content of + * @param {String} format 'jpeg' or 'png', in some browsers 'webp' is ok too + * @param {Number} quality <= 1 and > 0 + * @static + * @memberOf fabric.util + * @return {String} data url + */ +export const toDataURL = (canvasEl: HTMLCanvasElement, format: ImageFormat, quality: number) => canvasEl.toDataURL(`image/${format}`, quality); diff --git a/src/util/misc/findScaleTo.ts b/src/util/misc/findScaleTo.ts new file mode 100644 index 00000000000..51c1b6c5b5b --- /dev/null +++ b/src/util/misc/findScaleTo.ts @@ -0,0 +1,36 @@ +interface IWithDimensions { + width: number; + height: number; +} + +/** + * Finds the scale for the object source to fit inside the object destination, + * keeping aspect ratio intact. + * respect the total allowed area for the cache. + * @memberOf fabric.util + * @param {Object | fabric.Object} source + * @param {Number} source.height natural unscaled height of the object + * @param {Number} source.width natural unscaled width of the object + * @param {Object | fabric.Object} destination + * @param {Number} destination.height natural unscaled height of the object + * @param {Number} destination.width natural unscaled width of the object + * @return {Number} scale factor to apply to source to fit into destination + */ +export const findScaleToFit = (source: IWithDimensions, destination: IWithDimensions) => + Math.min(destination.width / source.width, destination.height / source.height); + +/** + * Finds the scale for the object source to cover entirely the object destination, + * keeping aspect ratio intact. + * respect the total allowed area for the cache. + * @memberOf fabric.util + * @param {Object | fabric.Object} source + * @param {Number} source.height natural unscaled height of the object + * @param {Number} source.width natural unscaled width of the object + * @param {Object | fabric.Object} destination + * @param {Number} destination.height natural unscaled height of the object + * @param {Number} destination.width natural unscaled width of the object + * @return {Number} scale factor to apply to source to cover destination + */ +export const findScaleToCover = (source: IWithDimensions, destination: IWithDimensions) => + Math.max(destination.width / source.width, destination.height / source.height); diff --git a/src/util/misc/misc.ts b/src/util/misc/misc.ts index 7f4e5adc275..d5485d96bfa 100644 --- a/src/util/misc/misc.ts +++ b/src/util/misc/misc.ts @@ -1,6 +1,5 @@ //@ts-nocheck -import { fabric } from '../../../HEADER' -import { DEFAULT_SVG_FONT_SIZE } from '../../constants'; +import { fabric } from '../../../HEADER'; import { Point } from '../../point.class'; import { cos } from './cos'; import { sin } from './sin'; @@ -20,6 +19,17 @@ import { } from './matrix'; import { stylesFromArray, stylesToArray, hasStyleChanged } from './textStyles'; import { clone, extend } from '../lang_object'; +import { createCanvasElement, createImage, copyCanvasElement, toDataURL } from './dom'; +import { toFixed } from './toFixed'; +import { + matrixToSVG, + parsePreserveAspectRatioAttribute, + groupSVGElements, + parseUnit, + getSvgAttributes, +} from './svgParsing'; +import { findScaleToFit, findScaleToCover } from './findScaleTo'; +import { capValue } from './capValue'; /** * @typedef {[number,number,number,number,number,number]} Matrix @@ -59,7 +69,19 @@ import { clone, extend } from '../lang_object'; clone, extend, }, - + createCanvasElement, + createImage, + copyCanvasElement, + toDataURL, + toFixed, + matrixToSVG, + parsePreserveAspectRatioAttribute, + groupSVGElements, + parseUnit, + getSvgAttributes, + findScaleToFit, + findScaleToCover, + capValue, /** * Sends a point from the source coordinate plane to the destination coordinate plane.\ * From the canvas/viewer's perspective the point remains unchanged. @@ -151,55 +173,6 @@ import { clone, extend } from '../lang_object'; }; }, - /** - * A wrapper around Number#toFixed, which contrary to native method returns number, not string. - * @static - * @memberOf fabric.util - * @param {Number|String} number number to operate on - * @param {Number} fractionDigits number of fraction digits to "leave" - * @return {Number} - */ - toFixed: function(number, fractionDigits) { - return parseFloat(Number(number).toFixed(fractionDigits)); - }, - - /** - * Converts from attribute value to pixel value if applicable. - * Returns converted pixels or original value not converted. - * @param {Number|String} value number to operate on - * @param {Number} fontSize - * @return {Number|String} - */ - parseUnit: function(value, fontSize) { - var unit = /\D{0,2}$/.exec(value), - number = parseFloat(value); - if (!fontSize) { - fontSize = DEFAULT_SVG_FONT_SIZE; - } - switch (unit[0]) { - case 'mm': - return number * fabric.DPI / 25.4; - - case 'cm': - return number * fabric.DPI / 2.54; - - case 'in': - return number * fabric.DPI; - - case 'pt': - return number * fabric.DPI / 72; // or * 4 / 3 - - case 'pc': - return number * fabric.DPI / 72 * 12; // or * 16 - - case 'em': - return number * fontSize; - - default: - return number; - } - }, - /** * Returns klass "Class" object of given namespace * @memberOf fabric.util @@ -213,33 +186,6 @@ import { clone, extend } from '../lang_object'; return (namespace || fabric)[type]; }, - /** - * Returns array of attributes for given svg that fabric parses - * @memberOf fabric.util - * @param {String} type Type of svg element (eg. 'circle') - * @return {Array} string names of supported attributes - */ - getSvgAttributes: function(type) { - var attributes = [ - 'instantiated_by_use', - 'style', - 'id', - 'class' - ]; - switch (type) { - case 'linearGradient': - attributes = attributes.concat(['x1', 'y1', 'x2', 'y2', 'gradientUnits', 'gradientTransform']); - break; - case 'radialGradient': - attributes = attributes.concat(['gradientUnits', 'gradientTransform', 'cx', 'cy', 'r', 'fx', 'fy', 'fr']); - break; - case 'stop': - attributes = attributes.concat(['offset', 'stop-color', 'stop-opacity']); - break; - } - return attributes; - }, - /** * Loads image element from given url and resolve it, or catch. * @memberOf fabric.util @@ -379,20 +325,6 @@ import { clone, extend } from '../lang_object'; }); }, - /** - * Groups SVG elements (usually those retrieved from SVG document) - * @static - * @memberOf fabric.util - * @param {Array} elements SVG elements to group - * @return {fabric.Object|fabric.Group} - */ - groupSVGElements: function(elements) { - if (elements && elements.length === 1) { - return elements[0]; - } - return new fabric.Group(elements); - }, - /** * Populates an object with properties of another object * @static @@ -411,54 +343,6 @@ import { clone, extend } from '../lang_object'; } }, - /** - * Creates canvas element - * @static - * @memberOf fabric.util - * @return {CanvasElement} initialized canvas element - */ - createCanvasElement: function() { - return fabric.document.createElement('canvas'); - }, - - /** - * Creates a canvas element that is a copy of another and is also painted - * @param {CanvasElement} canvas to copy size and content of - * @static - * @memberOf fabric.util - * @return {CanvasElement} initialized canvas element - */ - copyCanvasElement: function(canvas) { - var newCanvas = fabric.util.createCanvasElement(); - newCanvas.width = canvas.width; - newCanvas.height = canvas.height; - newCanvas.getContext('2d').drawImage(canvas, 0, 0); - return newCanvas; - }, - - /** - * since 2.6.0 moved from canvas instance to utility. - * @param {CanvasElement} canvasEl to copy size and content of - * @param {String} format 'jpeg' or 'png', in some browsers 'webp' is ok too - * @param {Number} quality <= 1 and > 0 - * @static - * @memberOf fabric.util - * @return {String} data url - */ - toDataURL: function(canvasEl, format, quality) { - return canvasEl.toDataURL('image/' + format, quality); - }, - - /** - * Creates image element (works on client and node) - * @static - * @memberOf fabric.util - * @return {HTMLImageElement} HTML image element - */ - createImage: function() { - return fabric.document.createElement('img'); - }, - /** * reset an object transform state to neutral. Top and left are not accounted for * @static @@ -541,35 +425,6 @@ import { clone, extend } from '../lang_object'; return _isTransparent; }, - /** - * Parse preserveAspectRatio attribute from element - * @param {string} attribute to be parsed - * @return {Object} an object containing align and meetOrSlice attribute - */ - parsePreserveAspectRatioAttribute: function(attribute) { - var meetOrSlice = 'meet', alignX = 'Mid', alignY = 'Mid', - aspectRatioAttrs = attribute.split(' '), align; - - if (aspectRatioAttrs && aspectRatioAttrs.length) { - meetOrSlice = aspectRatioAttrs.pop(); - if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') { - align = meetOrSlice; - meetOrSlice = 'meet'; - } - else if (aspectRatioAttrs.length) { - align = aspectRatioAttrs.pop(); - } - } - //divide align in alignX and alignY - alignX = align !== 'none' ? align.slice(1, 4) : 'none'; - alignY = align !== 'none' ? align.slice(5, 8) : 'none'; - return { - meetOrSlice: meetOrSlice, - alignX: alignX, - alignY: alignY - }; - }, - /** * Clear char widths cache for the given font family or all the cache if no * fontFamily is specified. @@ -607,57 +462,6 @@ import { clone, extend } from '../lang_object'; return { x: Math.floor(roughWidth), y: perfLimitSizeY }; }, - capValue: function(min, value, max) { - return Math.max(min, Math.min(value, max)); - }, - - /** - * Finds the scale for the object source to fit inside the object destination, - * keeping aspect ratio intact. - * respect the total allowed area for the cache. - * @memberOf fabric.util - * @param {Object | fabric.Object} source - * @param {Number} source.height natural unscaled height of the object - * @param {Number} source.width natural unscaled width of the object - * @param {Object | fabric.Object} destination - * @param {Number} destination.height natural unscaled height of the object - * @param {Number} destination.width natural unscaled width of the object - * @return {Number} scale factor to apply to source to fit into destination - */ - findScaleToFit: function(source, destination) { - return Math.min(destination.width / source.width, destination.height / source.height); - }, - - /** - * Finds the scale for the object source to cover entirely the object destination, - * keeping aspect ratio intact. - * respect the total allowed area for the cache. - * @memberOf fabric.util - * @param {Object | fabric.Object} source - * @param {Number} source.height natural unscaled height of the object - * @param {Number} source.width natural unscaled width of the object - * @param {Object | fabric.Object} destination - * @param {Number} destination.height natural unscaled height of the object - * @param {Number} destination.width natural unscaled width of the object - * @return {Number} scale factor to apply to source to cover destination - */ - findScaleToCover: function(source, destination) { - return Math.max(destination.width / source.width, destination.height / source.height); - }, - - /** - * given an array of 6 number returns something like `"matrix(...numbers)"` - * @memberOf fabric.util - * @param {Array} transform an array with 6 numbers - * @return {String} transform matrix for svg - * @return {Object.y} Limited dimensions by Y - */ - matrixToSVG: function(transform) { - return 'matrix(' + transform.map(function(value) { - return fabric.util.toFixed(value, fabric.Object.NUM_FRACTION_DIGITS); - }).join(' ') + ')'; - }, - /** * given an object and a transform, apply the inverse transform to the object, * this is equivalent to remove from that object that transformation, so that diff --git a/src/util/misc/svgParsing.ts b/src/util/misc/svgParsing.ts new file mode 100644 index 00000000000..2c8ea233e1f --- /dev/null +++ b/src/util/misc/svgParsing.ts @@ -0,0 +1,136 @@ +import { fabric } from '../../../HEADER'; +import { SVGElementName, SupportedSVGUnit, TMat2D } from '../../typedefs'; +import { DEFAULT_SVG_FONT_SIZE } from '../../constants'; +import { toFixed } from './toFixed'; +/** + * 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 + */ +export const getSvgAttributes = (type: SVGElementName) => { + const commonAttributes = [ + 'instantiated_by_use', + 'style', + 'id', + 'class' + ]; + switch (type) { + case SVGElementName.linearGradient: + return commonAttributes.concat(['x1', 'y1', 'x2', 'y2', 'gradientUnits', 'gradientTransform']); + case 'radialGradient': + return commonAttributes.concat(['gradientUnits', 'gradientTransform', 'cx', 'cy', 'r', 'fx', 'fy', 'fr']); + case 'stop': + return commonAttributes.concat(['offset', 'stop-color', 'stop-opacity']); + } + return commonAttributes; +}; + +/** + * Converts from attribute value to pixel value if applicable. + * Returns converted pixels or original value not converted. + * @param {string} value number to operate on + * @param {number} fontSize + * @return {number} + */ +export const parseUnit = (value: string, fontSize: number) => { + const unit = /\D{0,2}$/.exec(value), number = parseFloat(value); + if (!fontSize) { + fontSize = DEFAULT_SVG_FONT_SIZE; + } + const dpi = fabric.DPI; + switch (unit?.[0]) { + case SupportedSVGUnit.mm: + return number * dpi / 25.4; + + case SupportedSVGUnit.cm: + return number * dpi / 2.54; + + case SupportedSVGUnit.in: + return number * dpi; + + case SupportedSVGUnit.pt: + return number * dpi / 72; // or * 4 / 3 + + case SupportedSVGUnit.pc: + return number * dpi / 72 * 12; // or * 16 + + case SupportedSVGUnit.em: + return number * fontSize; + + default: + return 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} + */ +export const groupSVGElements = (elements: any[]) => { + if (elements && elements.length === 1) { + return elements[0]; + } + return new fabric.Group(elements); +}; + +const enum MeetOrSlice { + meet = 'meet', + slice = 'slice', +} + +const enum MinMidMax { + min = 'Min', + mid = 'Mid', + max = 'Max', + none = 'none', +} + +type TPreserveArParsed = { + meetOrSlice: MeetOrSlice; + alignX: MinMidMax; + alignY: MinMidMax; +}; + +// align can be either none or undefined or a combination of mid/max +const parseAlign = (align: string): MinMidMax[] => { + //divide align in alignX and alignY + if (align && align !== MinMidMax.none) { + return [ + align.slice(1, 4) as MinMidMax, + align.slice(5, 8) as MinMidMax, + ]; + } + else if (align === MinMidMax.none) { + return [align, align]; + } + return [MinMidMax.mid, MinMidMax.mid]; +}; + +/** + * Parse preserveAspectRatio attribute from element + * https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio + * @param {string} attribute to be parsed + * @return {Object} an object containing align and meetOrSlice attribute + */ +export const parsePreserveAspectRatioAttribute = (attribute: string): TPreserveArParsed => { + const [firstPart, secondPart] = attribute.trim().split(' ') as [MinMidMax, MeetOrSlice | undefined]; + const [alignX, alignY] = parseAlign(firstPart); + return { + meetOrSlice: secondPart || MeetOrSlice.meet, + alignX, + alignY, + }; +}; + +/** + * 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 + */ +export const matrixToSVG = (transform: TMat2D) => + 'matrix(' + transform.map((value) => toFixed(value, fabric.Object.NUM_FRACTION_DIGITS)).join(' ') + ')'; diff --git a/src/util/misc/toFixed.ts b/src/util/misc/toFixed.ts new file mode 100644 index 00000000000..c0c08752d64 --- /dev/null +++ b/src/util/misc/toFixed.ts @@ -0,0 +1,9 @@ +/** + * A wrapper around Number#toFixed, which contrary to native method returns number, not string. + * @static + * @memberOf fabric.util + * @param {number|string} number number to operate on + * @param {number} fractionDigits number of fraction digits to "leave" + * @return {number} + */ + export const toFixed = (number: number | string, fractionDigits: number) => parseFloat(Number(number).toFixed(fractionDigits)); diff --git a/test/unit/image.js b/test/unit/image.js index e712ebbabed..7a1d0234210 100644 --- a/test/unit/image.js +++ b/test/unit/image.js @@ -647,7 +647,7 @@ y: '0', width: '70', height: '170', - preserveAspectRatio: 'meet xMidYMid', + preserveAspectRatio: 'xMidYMid meet', 'xlink:href': IMAGE_DATA_URL }); @@ -676,7 +676,7 @@ y: '0', width: '70', height: '170', - preserveAspectRatio: 'meet xMidYMax', + preserveAspectRatio: 'xMidYMax meet', 'xlink:href': IMAGE_DATA_URL }); @@ -705,7 +705,7 @@ y: '0', width: '70', height: '170', - preserveAspectRatio: 'meet xMidYMin', + preserveAspectRatio: 'xMidYMin meet', 'xlink:href': IMAGE_DATA_URL }); @@ -734,7 +734,7 @@ y: '0', width: '140', height: '85', - preserveAspectRatio: 'meet xMinYMin', + preserveAspectRatio: 'xMinYMin meet', 'xlink:href': IMAGE_DATA_URL }); @@ -763,7 +763,7 @@ y: '0', width: '140', height: '85', - preserveAspectRatio: 'meet xMidYMin', + preserveAspectRatio: 'xMidYMin meet', 'xlink:href': IMAGE_DATA_URL }); @@ -792,7 +792,7 @@ y: '0', width: '140', height: '85', - preserveAspectRatio: 'meet xMaxYMin', + preserveAspectRatio: 'xMaxYMin meet', 'xlink:href': IMAGE_DATA_URL }); diff --git a/test/unit/util.js b/test/unit/util.js index c71599025e4..a54dcab10e0 100644 --- a/test/unit/util.js +++ b/test/unit/util.js @@ -743,6 +743,10 @@ assert.equal(parsed.meetOrSlice, 'meet'); assert.equal(parsed.alignX, 'mid'); assert.equal(parsed.alignY, 'max'); + parsed = fabric.util.parsePreserveAspectRatioAttribute('XmidYmin'); + assert.equal(parsed.meetOrSlice, 'meet'); + assert.equal(parsed.alignX, 'mid'); + assert.equal(parsed.alignY, 'min'); }); QUnit.test('multiplyTransformMatrices', function(assert) { @@ -850,10 +854,10 @@ }); /** - * - * @param {*} actual - * @param {*} expected - * @param {*} [message] + * + * @param {*} actual + * @param {*} expected + * @param {*} [message] * @param {number} [error] floating point percision, defaults to 10 */ QUnit.assert.matrixIsEqualEnough = function (actual, expected, message, error) { @@ -878,7 +882,7 @@ invert = fabric.util.invertTransform, multiply = fabric.util.multiplyTransformMatrices, transformPoint = fabric.util.transformPoint; - + function sendPointToPlane(point, from, to, relationFrom, relationTo) { return fabric.util.sendPointToPlane( point,