fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
352 lines (332 loc) • 12.7 kB
JavaScript
import { defineProperty as _defineProperty, objectSpread2 as _objectSpread2, objectWithoutProperties as _objectWithoutProperties } from '../../_virtual/_rollupPluginBabelHelpers.mjs';
import { config } from '../config.mjs';
import { SHARED_ATTRIBUTES } from '../parser/attributes.mjs';
import { parseAttributes } from '../parser/parseAttributes.mjs';
import { parsePointsAttribute } from '../parser/parsePointsAttribute.mjs';
import { Point } from '../Point.mjs';
import { classRegistry } from '../ClassRegistry.mjs';
import { makeBoundingBoxFromPoints } from '../util/misc/boundingBoxFromPoints.mjs';
import { calcDimensionsMatrix, transformPoint } from '../util/misc/matrix.mjs';
import { projectStrokeOnPoints } from '../util/misc/projectStroke/index.mjs';
import { degreesToRadians } from '../util/misc/radiansDegreesConversion.mjs';
import { toFixed } from '../util/misc/toFixed.mjs';
import { FabricObject } from './Object/FabricObject.mjs';
import { LEFT, TOP, CENTER, SCALE_X, SCALE_Y, SKEW_X, SKEW_Y } from '../constants.mjs';
import { cacheProperties } from './Object/defaultValues.mjs';
const _excluded = ["left", "top"];
const polylineDefaultValues = {
/**
* @deprecated transient option soon to be removed in favor of a different design
*/
exactBoundingBox: false
};
class Polyline extends FabricObject {
static getDefaults() {
return _objectSpread2(_objectSpread2({}, super.getDefaults()), Polyline.ownDefaults);
}
/**
* A list of properties that if changed trigger a recalculation of dimensions
* @todo check if you really need to recalculate for all cases
*/
/**
* Constructor
* @param {Array} points Array of points (where each point is an object with x and y)
* @param {Object} [options] Options object
* @return {Polyline} thisArg
* @example
* var poly = new Polyline([
* { x: 10, y: 10 },
* { x: 50, y: 30 },
* { x: 40, y: 70 },
* { x: 60, y: 50 },
* { x: 100, y: 150 },
* { x: 40, y: 100 }
* ], {
* stroke: 'red',
* left: 100,
* top: 100
* });
*/
constructor() {
let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
super();
_defineProperty(this, "strokeDiff", void 0);
Object.assign(this, Polyline.ownDefaults);
this.setOptions(options);
this.points = points;
const {
left,
top
} = options;
this.initialized = true;
this.setBoundingBox(true);
typeof left === 'number' && this.set(LEFT, left);
typeof top === 'number' && this.set(TOP, top);
}
isOpen() {
return true;
}
_projectStrokeOnPoints(options) {
return projectStrokeOnPoints(this.points, options, this.isOpen());
}
/**
* Calculate the polygon bounding box
* @private
*/
_calcDimensions(options) {
options = _objectSpread2({
scaleX: this.scaleX,
scaleY: this.scaleY,
skewX: this.skewX,
skewY: this.skewY,
strokeLineCap: this.strokeLineCap,
strokeLineJoin: this.strokeLineJoin,
strokeMiterLimit: this.strokeMiterLimit,
strokeUniform: this.strokeUniform,
strokeWidth: this.strokeWidth
}, options || {});
const points = this.exactBoundingBox ? this._projectStrokeOnPoints(options).map(projection => projection.projectedPoint) : this.points;
if (points.length === 0) {
return {
left: 0,
top: 0,
width: 0,
height: 0,
pathOffset: new Point(),
strokeOffset: new Point(),
strokeDiff: new Point()
};
}
const bbox = makeBoundingBoxFromPoints(points),
// Remove scale effect, since it's applied after
matrix = calcDimensionsMatrix(_objectSpread2(_objectSpread2({}, options), {}, {
scaleX: 1,
scaleY: 1
})),
bboxNoStroke = makeBoundingBoxFromPoints(this.points.map(p => transformPoint(p, matrix, true))),
scale = new Point(this.scaleX, this.scaleY);
let offsetX = bbox.left + bbox.width / 2,
offsetY = bbox.top + bbox.height / 2;
if (this.exactBoundingBox) {
offsetX = offsetX - offsetY * Math.tan(degreesToRadians(this.skewX));
// Order of those assignments is important.
// offsetY relies on offsetX being already changed by the line above
offsetY = offsetY - offsetX * Math.tan(degreesToRadians(this.skewY));
}
return _objectSpread2(_objectSpread2({}, bbox), {}, {
pathOffset: new Point(offsetX, offsetY),
strokeOffset: new Point(bboxNoStroke.left, bboxNoStroke.top).subtract(new Point(bbox.left, bbox.top)).multiply(scale),
strokeDiff: new Point(bbox.width, bbox.height).subtract(new Point(bboxNoStroke.width, bboxNoStroke.height)).multiply(scale)
});
}
/**
* 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 = makeBoundingBoxFromPoints(this.points);
return new Point(bbox.left + bbox.width / 2, bbox.top + bbox.height / 2);
}
setDimensions() {
this.setBoundingBox();
}
setBoundingBox(adjustPosition) {
const {
left,
top,
width,
height,
pathOffset,
strokeOffset,
strokeDiff
} = this._calcDimensions();
this.set({
width,
height,
pathOffset,
strokeOffset,
strokeDiff
});
adjustPosition && this.setPositionByOrigin(new Point(left + width / 2, top + height / 2), CENTER, CENTER);
}
/**
* @deprecated intermidiate method to be removed, do not use
*/
isStrokeAccountedForInDimensions() {
return this.exactBoundingBox;
}
/**
* @override stroke is taken in account in size
*/
_getNonTransformedDimensions() {
return this.exactBoundingBox ?
// TODO: fix this
new Point(this.width, this.height) : super._getNonTransformedDimensions();
}
/**
* @override stroke and skewing are taken into account when projecting stroke on points,
* therefore we don't want the default calculation to account for skewing as well.
* Though it is possible to pass `width` and `height` in `options`, doing so is very strange, use with discretion.
*
* @private
*/
_getTransformedDimensions() {
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
if (this.exactBoundingBox) {
let size;
/* When `strokeUniform = true`, any changes to the properties require recalculating the `width` and `height` because
the stroke projections are affected.
When `strokeUniform = false`, we don't need to recalculate for scale transformations, as the effect of scale on
projections follows a linear function (e.g. scaleX of 2 just multiply width by 2)*/
if (Object.keys(options).some(key => this.strokeUniform || this.constructor.layoutProperties.includes(key))) {
var _options$width, _options$height;
const {
width,
height
} = this._calcDimensions(options);
size = new Point((_options$width = options.width) !== null && _options$width !== void 0 ? _options$width : width, (_options$height = options.height) !== null && _options$height !== void 0 ? _options$height : height);
} else {
var _options$width2, _options$height2;
size = new Point((_options$width2 = options.width) !== null && _options$width2 !== void 0 ? _options$width2 : this.width, (_options$height2 = options.height) !== null && _options$height2 !== void 0 ? _options$height2 : this.height);
}
return size.multiply(new Point(options.scaleX || this.scaleX, options.scaleY || this.scaleY));
} else {
return super._getTransformedDimensions(options);
}
}
/**
* Recalculates dimensions when changing skew and scale
* @private
*/
_set(key, value) {
const changed = this.initialized && this[key] !== value;
const output = super._set(key, value);
if (this.exactBoundingBox && changed && ((key === SCALE_X || key === SCALE_Y) && this.strokeUniform && this.constructor.layoutProperties.includes('strokeUniform') || this.constructor.layoutProperties.includes(key))) {
this.setDimensions();
}
return output;
}
/**
* 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)), {}, {
points: this.points.map(_ref => {
let {
x,
y
} = _ref;
return {
x,
y
};
})
});
}
/**
* Returns svg representation of an instance
* @return {Array} an array of strings with the specific svg representation
* of the instance
*/
_toSVG() {
const points = [],
diffX = this.pathOffset.x,
diffY = this.pathOffset.y,
NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS;
for (let i = 0, len = this.points.length; i < len; i++) {
points.push(toFixed(this.points[i].x - diffX, NUM_FRACTION_DIGITS), ',', toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS), ' ');
}
return ["<".concat(this.constructor.type.toLowerCase(), " "), 'COMMON_PARTS', "points=\"".concat(points.join(''), "\" />\n")];
}
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_render(ctx) {
const len = this.points.length,
x = this.pathOffset.x,
y = this.pathOffset.y;
if (!len || isNaN(this.points[len - 1].y)) {
// do not draw if no points or odd points
// NaN comes from parseFloat of a empty string in parser
return;
}
ctx.beginPath();
ctx.moveTo(this.points[0].x - x, this.points[0].y - y);
for (let i = 0; i < len; i++) {
const point = this.points[i];
ctx.lineTo(point.x - x, point.y - y);
}
!this.isOpen() && ctx.closePath();
this._renderPaintInOrder(ctx);
}
/**
* Returns complexity of an instance
* @return {Number} complexity of this instance
*/
complexity() {
return this.points.length;
}
/* _FROM_SVG_START_ */
/**
* List of attribute names to account for when parsing SVG element (used by {@link Polyline.fromElement})
* @static
* @memberOf Polyline
* @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement
*/
/**
* Returns Polyline instance from an SVG element
* @static
* @memberOf Polyline
* @param {HTMLElement} element Element to parser
* @param {Object} [options] Options object
*/
static async fromElement(element, options, cssRules) {
const points = parsePointsAttribute(element.getAttribute('points')),
_parseAttributes = parseAttributes(element, this.ATTRIBUTE_NAMES, cssRules),
parsedAttributes = _objectWithoutProperties(_parseAttributes, _excluded);
return new this(points, _objectSpread2(_objectSpread2({}, parsedAttributes), options));
}
/* _FROM_SVG_END_ */
/**
* Returns Polyline instance from an object representation
* @static
* @memberOf Polyline
* @param {Object} object Object to create an instance from
* @returns {Promise<Polyline>}
*/
static fromObject(object) {
return this._fromObject(object, {
extraParam: 'points'
});
}
}
/**
* Points array
* @type Array
* @default
*/
/**
* WARNING: Feature in progress
* Calculate the exact bounding box taking in account strokeWidth on acute angles
* this will be turned to true by default on fabric 6.0
* maybe will be left in as an optimization since calculations may be slow
* @deprecated transient option soon to be removed in favor of a different design
* @type Boolean
* @default false
*/
_defineProperty(Polyline, "ownDefaults", polylineDefaultValues);
_defineProperty(Polyline, "type", 'Polyline');
_defineProperty(Polyline, "layoutProperties", [SKEW_X, SKEW_Y, 'strokeLineCap', 'strokeLineJoin', 'strokeMiterLimit', 'strokeWidth', 'strokeUniform', 'points']);
_defineProperty(Polyline, "cacheProperties", [...cacheProperties, 'points']);
_defineProperty(Polyline, "ATTRIBUTE_NAMES", [...SHARED_ATTRIBUTES]);
classRegistry.setClass(Polyline);
classRegistry.setSVGClass(Polyline);
export { Polyline, polylineDefaultValues };
//# sourceMappingURL=Polyline.mjs.map