UNPKG

fabric

Version:

Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.

1 lines 15.2 kB
{"version":3,"file":"Path.min.mjs","names":[],"sources":["../../../src/shapes/Path.ts"],"sourcesContent":["import { config } from '../config';\nimport { SHARED_ATTRIBUTES } from '../parser/attributes';\nimport { parseAttributes } from '../parser/parseAttributes';\nimport type { XY } from '../Point';\nimport { Point } from '../Point';\nimport { makeBoundingBoxFromPoints } from '../util/misc/boundingBoxFromPoints';\nimport { toFixed } from '../util/misc/toFixed';\nimport {\n getBoundsOfCurve,\n joinPath,\n makePathSimpler,\n parsePath,\n} from '../util/path';\nimport { classRegistry } from '../ClassRegistry';\nimport { FabricObject, cacheProperties } from './Object/FabricObject';\nimport type {\n TComplexPathData,\n TPathSegmentInfo,\n TSimplePathData,\n} from '../util/path/typedefs';\nimport type { FabricObjectProps, SerializedObjectProps } from './Object/types';\nimport type { ObjectEvents } from '../EventTypeDefs';\nimport type {\n TBBox,\n TClassProperties,\n TSVGReviver,\n TOptions,\n} from '../typedefs';\nimport { CENTER, LEFT, TOP } from '../constants';\nimport type { CSSRules } from '../parser/typedefs';\n\ninterface UniquePathProps {\n sourcePath?: string;\n path?: TSimplePathData;\n}\n\nexport interface SerializedPathProps\n extends SerializedObjectProps, UniquePathProps {}\n\nexport interface PathProps extends FabricObjectProps, UniquePathProps {}\n\nexport interface IPathBBox extends TBBox {\n left: number;\n top: number;\n pathOffset: Point;\n}\n\nexport class Path<\n Props extends TOptions<PathProps> = Partial<PathProps>,\n SProps extends SerializedPathProps = SerializedPathProps,\n EventSpec extends ObjectEvents = ObjectEvents,\n> extends FabricObject<Props, SProps, EventSpec> {\n /**\n * Array of path points\n * @type Array\n */\n declare path: TSimplePathData;\n\n declare pathOffset: Point;\n\n declare sourcePath?: string;\n\n declare segmentsInfo?: TPathSegmentInfo[];\n\n static type = 'Path';\n\n static cacheProperties = [...cacheProperties, 'path', 'fillRule'];\n\n /**\n * Constructor\n * @param {TComplexPathData} path Path data (sequence of coordinates and corresponding \"command\" tokens)\n * @param {Partial<PathProps>} [options] Options object\n * @return {Path} thisArg\n */\n constructor(\n path: TComplexPathData | string,\n // todo: evaluate this spread here\n { path: _, left, top, ...options }: Partial<Props> = {},\n ) {\n super();\n Object.assign(this, Path.ownDefaults);\n this.setOptions(options);\n this._setPath(path || [], true);\n typeof left === 'number' && this.set(LEFT, left);\n typeof top === 'number' && this.set(TOP, top);\n }\n\n /**\n * @private\n * @param {TComplexPathData | string} path Path data (sequence of coordinates and corresponding \"command\" tokens)\n * @param {boolean} [adjustPosition] pass true to reposition the object according to the bounding box\n * @returns {Point} top left position of the bounding box, useful for complementary positioning\n */\n _setPath(path: TComplexPathData | string, adjustPosition?: boolean) {\n this.path = makePathSimpler(Array.isArray(path) ? path : parsePath(path));\n this.setBoundingBox(adjustPosition);\n }\n\n /**\n * This function is an helper for svg import. it returns the center of the object in the svg\n * untransformed coordinates, by look at the polyline/polygon points.\n * @private\n * @return {Point} center point from element coordinates\n */\n _findCenterFromElement(): Point {\n const bbox = this._calcBoundsFromPath();\n return new Point(bbox.left + bbox.width / 2, bbox.top + bbox.height / 2);\n }\n\n /**\n * @private\n * @param {CanvasRenderingContext2D} ctx context to render path on\n */\n _renderPathCommands(ctx: CanvasRenderingContext2D) {\n const l = -this.pathOffset.x,\n t = -this.pathOffset.y;\n\n ctx.beginPath();\n\n for (const command of this.path) {\n switch (\n command[0] // first letter\n ) {\n case 'L': // lineto, absolute\n ctx.lineTo(command[1] + l, command[2] + t);\n break;\n\n case 'M': // moveTo, absolute\n ctx.moveTo(command[1] + l, command[2] + t);\n break;\n\n case 'C': // bezierCurveTo, absolute\n ctx.bezierCurveTo(\n command[1] + l,\n command[2] + t,\n command[3] + l,\n command[4] + t,\n command[5] + l,\n command[6] + t,\n );\n break;\n\n case 'Q': // quadraticCurveTo, absolute\n ctx.quadraticCurveTo(\n command[1] + l,\n command[2] + t,\n command[3] + l,\n command[4] + t,\n );\n break;\n\n case 'Z':\n ctx.closePath();\n break;\n }\n }\n }\n\n /**\n * @private\n * @param {CanvasRenderingContext2D} ctx context to render path on\n */\n _render(ctx: CanvasRenderingContext2D) {\n this._renderPathCommands(ctx);\n this._renderPaintInOrder(ctx);\n }\n\n /**\n * Returns string representation of an instance\n * @return {string} string representation of an instance\n */\n toString() {\n return `#<Path (${this.complexity()}): { \"top\": ${this.top}, \"left\": ${\n this.left\n } }>`;\n }\n\n /**\n * Returns object representation of an instance\n * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output\n * @return {Object} object representation of an instance\n */\n toObject<\n T extends Omit<Props & TClassProperties<this>, keyof SProps>,\n K extends keyof T = never,\n >(propertiesToInclude: K[] = []): Pick<T, K> & SProps {\n return {\n ...super.toObject(propertiesToInclude),\n path: this.path.map((pathCmd) => pathCmd.slice()),\n };\n }\n\n /**\n * Returns dataless object representation of an instance\n * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output\n * @return {Object} object representation of an instance\n */\n toDatalessObject<\n T extends Omit<Props & TClassProperties<this>, keyof SProps>,\n K extends keyof T = never,\n >(propertiesToInclude: K[] = []): Pick<T, K> & SProps {\n const o = this.toObject<T, K>(propertiesToInclude);\n if (this.sourcePath) {\n delete o.path;\n o.sourcePath = this.sourcePath;\n }\n return o;\n }\n\n /**\n * Returns svg representation of an instance\n * @return {Array} an array of strings with the specific svg representation\n * of the instance\n */\n _toSVG() {\n return [\n '<path ',\n 'COMMON_PARTS',\n `d=\"${joinPath(this.path, config.NUM_FRACTION_DIGITS)}\" stroke-linecap=\"round\" />\\n`,\n ];\n }\n\n /**\n * @private\n * @return the path command's translate transform attribute\n */\n _getOffsetTransform() {\n const digits = config.NUM_FRACTION_DIGITS;\n return ` translate(${toFixed(-this.pathOffset.x, digits)}, ${toFixed(\n -this.pathOffset.y,\n digits,\n )})`;\n }\n\n /**\n * Returns svg clipPath representation of an instance\n * @param {Function} [reviver] Method for further parsing of svg representation.\n * @return {string} svg representation of an instance\n */\n toClipPathSVG(reviver?: TSVGReviver): string {\n const additionalTransform = this._getOffsetTransform();\n return (\n '\\t' +\n this._createBaseClipPathSVGMarkup(this._toSVG(), {\n reviver,\n additionalTransform: additionalTransform,\n })\n );\n }\n\n /**\n * Returns svg representation of an instance\n * @param {Function} [reviver] Method for further parsing of svg representation.\n * @return {string} svg representation of an instance\n */\n toSVG(reviver?: TSVGReviver): string {\n const additionalTransform = this._getOffsetTransform();\n return this._createBaseSVGMarkup(this._toSVG(), {\n reviver,\n additionalTransform: additionalTransform,\n });\n }\n\n /**\n * Returns number representation of an instance complexity\n * @return {number} complexity of this instance\n */\n complexity() {\n return this.path.length;\n }\n\n setDimensions() {\n this.setBoundingBox();\n }\n\n setBoundingBox(adjustPosition?: boolean) {\n const { width, height, pathOffset } = this._calcDimensions();\n this.set({ width, height, pathOffset });\n // using pathOffset because it match the use case.\n // if pathOffset change here we need to use left + width/2 , top + height/2\n adjustPosition && this.setPositionByOrigin(pathOffset, CENTER, CENTER);\n }\n\n _calcBoundsFromPath(): TBBox {\n const bounds: XY[] = [];\n let subpathStartX = 0,\n subpathStartY = 0,\n x = 0, // current x\n y = 0; // current y\n\n for (const command of this.path) {\n // current instruction\n switch (\n command[0] // first letter\n ) {\n case 'L': // lineto, absolute\n x = command[1];\n y = command[2];\n bounds.push({ x: subpathStartX, y: subpathStartY }, { x, y });\n break;\n\n case 'M': // moveTo, absolute\n x = command[1];\n y = command[2];\n subpathStartX = x;\n subpathStartY = y;\n break;\n\n case 'C': // bezierCurveTo, absolute\n bounds.push(\n ...getBoundsOfCurve(\n x,\n y,\n command[1],\n command[2],\n command[3],\n command[4],\n command[5],\n command[6],\n ),\n );\n x = command[5];\n y = command[6];\n break;\n\n case 'Q': // quadraticCurveTo, absolute\n bounds.push(\n ...getBoundsOfCurve(\n x,\n y,\n command[1],\n command[2],\n command[1],\n command[2],\n command[3],\n command[4],\n ),\n );\n x = command[3];\n y = command[4];\n break;\n\n case 'Z':\n x = subpathStartX;\n y = subpathStartY;\n break;\n }\n }\n return makeBoundingBoxFromPoints(bounds);\n }\n\n /**\n * @private\n */\n _calcDimensions(): IPathBBox {\n const bbox = this._calcBoundsFromPath();\n\n return {\n ...bbox,\n pathOffset: new Point(\n bbox.left + bbox.width / 2,\n bbox.top + bbox.height / 2,\n ),\n };\n }\n\n /**\n * List of attribute names to account for when parsing SVG element (used by `Path.fromElement`)\n * @see http://www.w3.org/TR/SVG/paths.html#PathElement\n */\n static ATTRIBUTE_NAMES = [...SHARED_ATTRIBUTES, 'd'];\n\n /**\n * Creates an instance of Path from an object\n * @param {Object} object\n * @returns {Promise<Path>}\n */\n static fromObject<T extends TOptions<SerializedPathProps>>(object: T) {\n return this._fromObject<Path>(object, {\n extraParam: 'path',\n });\n }\n\n /**\n * Creates an instance of Path from an SVG <path> element\n * @param {HTMLElement} element to parse\n * @param {Partial<PathProps>} [options] Options object\n */\n static async fromElement(\n element: HTMLElement | SVGElement,\n options?: Partial<PathProps>,\n cssRules?: CSSRules,\n ) {\n const { d, ...parsedAttributes } = parseAttributes(\n element,\n this.ATTRIBUTE_NAMES,\n cssRules,\n );\n return new this(d, {\n ...parsedAttributes,\n ...options,\n // we pass undefined to instruct the constructor to position the object using the bbox\n left: undefined,\n top: undefined,\n });\n }\n}\n\nclassRegistry.setClass(Path);\nclassRegistry.setSVGClass(Path);\n\n/* _FROM_SVG_START_ */\n"],"mappings":"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"}