fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
326 lines (305 loc) • 10.3 kB
JavaScript
import { defineProperty as _defineProperty, objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.mjs';
import { config } from '../config.mjs';
import { SHARED_ATTRIBUTES } from '../parser/attributes.mjs';
import { parseAttributes } from '../parser/parseAttributes.mjs';
import { Point } from '../Point.mjs';
import { makeBoundingBoxFromPoints } from '../util/misc/boundingBoxFromPoints.mjs';
import { toFixed } from '../util/misc/toFixed.mjs';
import { makePathSimpler, parsePath, joinPath, getBoundsOfCurve } from '../util/path/index.mjs';
import { classRegistry } from '../ClassRegistry.mjs';
import { FabricObject } from './Object/FabricObject.mjs';
import { LEFT, TOP, CENTER } from '../constants.mjs';
import { cacheProperties } from './Object/defaultValues.mjs';
const _excluded = ["path", "left", "top"],
_excluded2 = ["d"];
class Path extends FabricObject {
/**
* Constructor
* @param {TComplexPathData} path Path data (sequence of coordinates and corresponding "command" tokens)
* @param {Partial<PathProps>} [options] Options object
* @return {Path} thisArg
*/
constructor(path) {
let _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
{
path: _,
left,
top
} = _ref,
options = _objectWithoutProperties(_ref, _excluded);
super();
Object.assign(this, Path.ownDefaults);
this.setOptions(options);
this._setPath(path || [], true);
typeof left === 'number' && this.set(LEFT, left);
typeof top === 'number' && this.set(TOP, top);
}
/**
* @private
* @param {TComplexPathData | string} path Path data (sequence of coordinates and corresponding "command" tokens)
* @param {boolean} [adjustPosition] pass true to reposition the object according to the bounding box
* @returns {Point} top left position of the bounding box, useful for complementary positioning
*/
_setPath(path, adjustPosition) {
this.path = makePathSimpler(Array.isArray(path) ? path : parsePath(path));
this.setBoundingBox(adjustPosition);
}
/**
* This function is an helper for svg import. it returns the center of the object in the svg
* untransformed coordinates, by look at the polyline/polygon points.
* @private
* @return {Point} center point from element coordinates
*/
_findCenterFromElement() {
const bbox = this._calcBoundsFromPath();
return new Point(bbox.left + bbox.width / 2, bbox.top + bbox.height / 2);
}
/**
* @private
* @param {CanvasRenderingContext2D} ctx context to render path on
*/
_renderPathCommands(ctx) {
const l = -this.pathOffset.x,
t = -this.pathOffset.y;
ctx.beginPath();
for (const command of this.path) {
switch (command[0] // first letter
) {
case 'L':
// lineto, absolute
ctx.lineTo(command[1] + l, command[2] + t);
break;
case 'M':
// moveTo, absolute
ctx.moveTo(command[1] + l, command[2] + t);
break;
case 'C':
// bezierCurveTo, absolute
ctx.bezierCurveTo(command[1] + l, command[2] + t, command[3] + l, command[4] + t, command[5] + l, command[6] + t);
break;
case 'Q':
// quadraticCurveTo, absolute
ctx.quadraticCurveTo(command[1] + l, command[2] + t, command[3] + l, command[4] + t);
break;
case 'Z':
ctx.closePath();
break;
}
}
}
/**
* @private
* @param {CanvasRenderingContext2D} ctx context to render path on
*/
_render(ctx) {
this._renderPathCommands(ctx);
this._renderPaintInOrder(ctx);
}
/**
* Returns string representation of an instance
* @return {string} string representation of an instance
*/
toString() {
return "#<Path (".concat(this.complexity(), "): { \"top\": ").concat(this.top, ", \"left\": ").concat(this.left, " }>");
}
/**
* Returns object representation of an instance
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
* @return {Object} object representation of an instance
*/
toObject() {
let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
return _objectSpread2(_objectSpread2({}, super.toObject(propertiesToInclude)), {}, {
path: this.path.map(pathCmd => pathCmd.slice())
});
}
/**
* Returns dataless object representation of an instance
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
* @return {Object} object representation of an instance
*/
toDatalessObject() {
let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
const o = this.toObject(propertiesToInclude);
if (this.sourcePath) {
delete o.path;
o.sourcePath = this.sourcePath;
}
return o;
}
/**
* Returns svg representation of an instance
* @return {Array} an array of strings with the specific svg representation
* of the instance
*/
_toSVG() {
const path = joinPath(this.path, config.NUM_FRACTION_DIGITS);
return ['<path ', 'COMMON_PARTS', "d=\"".concat(path, "\" stroke-linecap=\"round\" />\n")];
}
/**
* @private
* @return the path command's translate transform attribute
*/
_getOffsetTransform() {
const digits = config.NUM_FRACTION_DIGITS;
return " translate(".concat(toFixed(-this.pathOffset.x, digits), ", ").concat(toFixed(-this.pathOffset.y, digits), ")");
}
/**
* Returns svg clipPath representation of an instance
* @param {Function} [reviver] Method for further parsing of svg representation.
* @return {string} svg representation of an instance
*/
toClipPathSVG(reviver) {
const additionalTransform = this._getOffsetTransform();
return '\t' + this._createBaseClipPathSVGMarkup(this._toSVG(), {
reviver,
additionalTransform: additionalTransform
});
}
/**
* 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) {
const additionalTransform = this._getOffsetTransform();
return this._createBaseSVGMarkup(this._toSVG(), {
reviver,
additionalTransform: additionalTransform
});
}
/**
* Returns number representation of an instance complexity
* @return {number} complexity of this instance
*/
complexity() {
return this.path.length;
}
setDimensions() {
this.setBoundingBox();
}
setBoundingBox(adjustPosition) {
const {
width,
height,
pathOffset
} = this._calcDimensions();
this.set({
width,
height,
pathOffset
});
// using pathOffset because it match the use case.
// if pathOffset change here we need to use left + width/2 , top + height/2
adjustPosition && this.setPositionByOrigin(pathOffset, CENTER, CENTER);
}
_calcBoundsFromPath() {
const bounds = [];
let subpathStartX = 0,
subpathStartY = 0,
x = 0,
// current x
y = 0; // current y
for (const command of this.path) {
// current instruction
switch (command[0] // first letter
) {
case 'L':
// lineto, absolute
x = command[1];
y = command[2];
bounds.push({
x: subpathStartX,
y: subpathStartY
}, {
x,
y
});
break;
case 'M':
// moveTo, absolute
x = command[1];
y = command[2];
subpathStartX = x;
subpathStartY = y;
break;
case 'C':
// bezierCurveTo, absolute
bounds.push(...getBoundsOfCurve(x, y, command[1], command[2], command[3], command[4], command[5], command[6]));
x = command[5];
y = command[6];
break;
case 'Q':
// quadraticCurveTo, absolute
bounds.push(...getBoundsOfCurve(x, y, command[1], command[2], command[1], command[2], command[3], command[4]));
x = command[3];
y = command[4];
break;
case 'Z':
x = subpathStartX;
y = subpathStartY;
break;
}
}
return makeBoundingBoxFromPoints(bounds);
}
/**
* @private
*/
_calcDimensions() {
const bbox = this._calcBoundsFromPath();
return _objectSpread2(_objectSpread2({}, bbox), {}, {
pathOffset: new Point(bbox.left + bbox.width / 2, bbox.top + bbox.height / 2)
});
}
/**
* List of attribute names to account for when parsing SVG element (used by `Path.fromElement`)
* @static
* @memberOf Path
* @see http://www.w3.org/TR/SVG/paths.html#PathElement
*/
/**
* Creates an instance of Path from an object
* @static
* @memberOf Path
* @param {Object} object
* @returns {Promise<Path>}
*/
static fromObject(object) {
return this._fromObject(object, {
extraParam: 'path'
});
}
/**
* Creates an instance of Path from an SVG <path> element
* @static
* @memberOf Path
* @param {HTMLElement} element to parse
* @param {Partial<PathProps>} [options] Options object
*/
static async fromElement(element, options, cssRules) {
const _parseAttributes = parseAttributes(element, this.ATTRIBUTE_NAMES, cssRules),
{
d
} = _parseAttributes,
parsedAttributes = _objectWithoutProperties(_parseAttributes, _excluded2);
return new this(d, _objectSpread2(_objectSpread2(_objectSpread2({}, parsedAttributes), options), {}, {
// we pass undefined to instruct the constructor to position the object using the bbox
left: undefined,
top: undefined
}));
}
}
/**
* Array of path points
* @type Array
* @default
*/
_defineProperty(Path, "type", 'Path');
_defineProperty(Path, "cacheProperties", [...cacheProperties, 'path', 'fillRule']);
_defineProperty(Path, "ATTRIBUTE_NAMES", [...SHARED_ATTRIBUTES, 'd']);
classRegistry.setClass(Path);
classRegistry.setSVGClass(Path);
/* _FROM_SVG_START_ */
export { Path };
//# sourceMappingURL=Path.mjs.map