fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
322 lines (314 loc) • 11.1 kB
JavaScript
import { defineProperty as _defineProperty, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.mjs';
import { Color } from '../color/Color.mjs';
import { iMatrix } from '../constants.mjs';
import { parseTransformAttribute } from '../parser/parseTransformAttribute.mjs';
import { uid } from '../util/internals/uid.mjs';
import { pick } from '../util/misc/pick.mjs';
import { matrixToSVG } from '../util/misc/svgExport.mjs';
import { radialDefaultCoords, linearDefaultCoords } from './constants.mjs';
import { parseColorStops } from './parser/parseColorStops.mjs';
import { parseCoords } from './parser/parseCoords.mjs';
import { parseGradientUnits, parseType } from './parser/misc.mjs';
import { classRegistry } from '../ClassRegistry.mjs';
import { isPath } from '../util/typeAssertions.mjs';
/**
* Gradient class
* @class Gradient
* @tutorial {@link http://fabricjs.com/fabric-intro-part-2#gradients}
*/
class Gradient {
constructor(options) {
const {
type = 'linear',
gradientUnits = 'pixels',
coords = {},
colorStops = [],
offsetX = 0,
offsetY = 0,
gradientTransform,
id
} = options || {};
Object.assign(this, {
type,
gradientUnits,
coords: _objectSpread2(_objectSpread2({}, type === 'radial' ? radialDefaultCoords : linearDefaultCoords), coords),
colorStops,
offsetX,
offsetY,
gradientTransform,
id: id ? "".concat(id, "_").concat(uid()) : uid()
});
}
/**
* Adds another colorStop
* @param {Record<string, string>} colorStop Object with offset and color
* @return {Gradient} thisArg
*/
addColorStop(colorStops) {
for (const position in colorStops) {
const color = new Color(colorStops[position]);
this.colorStops.push({
offset: parseFloat(position),
color: color.toRgb(),
opacity: color.getAlpha()
});
}
return this;
}
/**
* Returns object representation of a gradient
* @param {string[]} [propertiesToInclude] Any properties that you might want to additionally include in the output
* @return {object}
*/
toObject(propertiesToInclude) {
return _objectSpread2(_objectSpread2({}, pick(this, propertiesToInclude)), {}, {
type: this.type,
coords: _objectSpread2({}, this.coords),
colorStops: this.colorStops.map(colorStop => _objectSpread2({}, colorStop)),
offsetX: this.offsetX,
offsetY: this.offsetY,
gradientUnits: this.gradientUnits,
gradientTransform: this.gradientTransform ? [...this.gradientTransform] : undefined
});
}
/* _TO_SVG_START_ */
/**
* Returns SVG representation of an gradient
* @param {FabricObject} object Object to create a gradient for
* @return {String} SVG representation of an gradient (linear/radial)
*/
toSVG(object) {
let {
additionalTransform: preTransform
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const markup = [],
transform = this.gradientTransform ? this.gradientTransform.concat() : iMatrix.concat(),
gradientUnits = this.gradientUnits === 'pixels' ? 'userSpaceOnUse' : 'objectBoundingBox';
// colorStops must be sorted ascending, and guarded against deep mutations
const colorStops = this.colorStops.map(colorStop => _objectSpread2({}, colorStop)).sort((a, b) => {
return a.offset - b.offset;
});
let offsetX = -this.offsetX,
offsetY = -this.offsetY;
if (gradientUnits === 'objectBoundingBox') {
offsetX /= object.width;
offsetY /= object.height;
} else {
offsetX += object.width / 2;
offsetY += object.height / 2;
}
// todo what about polygon/polyline?
if (isPath(object) && this.gradientUnits !== 'percentage') {
offsetX -= object.pathOffset.x;
offsetY -= object.pathOffset.y;
}
transform[4] -= offsetX;
transform[5] -= offsetY;
const commonAttributes = ["id=\"SVGID_".concat(this.id, "\""), "gradientUnits=\"".concat(gradientUnits, "\""), "gradientTransform=\"".concat(preTransform ? preTransform + ' ' : '').concat(matrixToSVG(transform), "\""), ''].join(' ');
if (this.type === 'linear') {
const {
x1,
y1,
x2,
y2
} = this.coords;
markup.push('<linearGradient ', commonAttributes, ' x1="', x1, '" y1="', y1, '" x2="', x2, '" y2="', y2, '">\n');
} else if (this.type === 'radial') {
const {
x1,
y1,
x2,
y2,
r1,
r2
} = this.coords;
const needsSwap = r1 > r2;
// svg radial gradient has just 1 radius. the biggest.
markup.push('<radialGradient ', commonAttributes, ' cx="', needsSwap ? x1 : x2, '" cy="', needsSwap ? y1 : y2, '" r="', needsSwap ? r1 : r2, '" fx="', needsSwap ? x2 : x1, '" fy="', needsSwap ? y2 : y1, '">\n');
if (needsSwap) {
// svg goes from internal to external radius. if radius are inverted, swap color stops.
colorStops.reverse(); // mutates array
colorStops.forEach(colorStop => {
colorStop.offset = 1 - colorStop.offset;
});
}
const minRadius = Math.min(r1, r2);
if (minRadius > 0) {
// i have to shift all colorStops and add new one in 0.
const maxRadius = Math.max(r1, r2),
percentageShift = minRadius / maxRadius;
colorStops.forEach(colorStop => {
colorStop.offset += percentageShift * (1 - colorStop.offset);
});
}
}
colorStops.forEach(_ref => {
let {
color,
offset,
opacity
} = _ref;
markup.push('<stop ', 'offset="', offset * 100 + '%', '" style="stop-color:', color, typeof opacity !== 'undefined' ? ';stop-opacity: ' + opacity : ';', '"/>\n');
});
markup.push(this.type === 'linear' ? '</linearGradient>' : '</radialGradient>', '\n');
return markup.join('');
}
/* _TO_SVG_END_ */
/**
* Returns an instance of CanvasGradient
* @param {CanvasRenderingContext2D} ctx Context to render on
* @return {CanvasGradient}
*/
toLive(ctx) {
const {
x1,
y1,
x2,
y2,
r1,
r2
} = this.coords;
const gradient = this.type === 'linear' ? ctx.createLinearGradient(x1, y1, x2, y2) : ctx.createRadialGradient(x1, y1, r1, x2, y2, r2);
this.colorStops.forEach(_ref2 => {
let {
color,
opacity,
offset
} = _ref2;
gradient.addColorStop(offset, typeof opacity !== 'undefined' ? new Color(color).setAlpha(opacity).toRgba() : color);
});
return gradient;
}
static async fromObject(options) {
const {
colorStops,
gradientTransform
} = options;
return new this(_objectSpread2(_objectSpread2({}, options), {}, {
colorStops: colorStops ? colorStops.map(colorStop => _objectSpread2({}, colorStop)) : undefined,
gradientTransform: gradientTransform ? [...gradientTransform] : undefined
}));
}
/* _FROM_SVG_START_ */
/**
* Returns {@link Gradient} instance from an SVG element
* @static
* @memberOf Gradient
* @param {SVGGradientElement} el SVG gradient element
* @param {FabricObject} instance
* @param {String} opacity A fill-opacity or stroke-opacity attribute to multiply to each stop's opacity.
* @param {SVGOptions} svgOptions an object containing the size of the SVG in order to parse correctly gradients
* that uses gradientUnits as 'userSpaceOnUse' and percentages.
* @return {Gradient} Gradient instance
* @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement
* @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement
*
* @example
*
* <linearGradient id="linearGrad1">
* <stop offset="0%" stop-color="white"/>
* <stop offset="100%" stop-color="black"/>
* </linearGradient>
*
* OR
*
* <linearGradient id="linearGrad2">
* <stop offset="0" style="stop-color:rgb(255,255,255)"/>
* <stop offset="1" style="stop-color:rgb(0,0,0)"/>
* </linearGradient>
*
* OR
*
* <radialGradient id="radialGrad1">
* <stop offset="0%" stop-color="white" stop-opacity="1" />
* <stop offset="50%" stop-color="black" stop-opacity="0.5" />
* <stop offset="100%" stop-color="white" stop-opacity="1" />
* </radialGradient>
*
* OR
*
* <radialGradient id="radialGrad2">
* <stop offset="0" stop-color="rgb(255,255,255)" />
* <stop offset="0.5" stop-color="rgb(0,0,0)" />
* <stop offset="1" stop-color="rgb(255,255,255)" />
* </radialGradient>
*
*/
static fromElement(el, instance, svgOptions) {
const gradientUnits = parseGradientUnits(el);
const center = instance._findCenterFromElement();
return new this(_objectSpread2({
id: el.getAttribute('id') || undefined,
type: parseType(el),
coords: parseCoords(el, {
width: svgOptions.viewBoxWidth || svgOptions.width,
height: svgOptions.viewBoxHeight || svgOptions.height
}),
colorStops: parseColorStops(el, svgOptions.opacity),
gradientUnits,
gradientTransform: parseTransformAttribute(el.getAttribute('gradientTransform') || '')
}, gradientUnits === 'pixels' ? {
offsetX: instance.width / 2 - center.x,
offsetY: instance.height / 2 - center.y
} : {
offsetX: 0,
offsetY: 0
}));
}
/* _FROM_SVG_END_ */
}
/**
* Horizontal offset for aligning gradients coming from SVG when outside pathgroups
* @type Number
* @default 0
*/
/**
* Vertical offset for aligning gradients coming from SVG when outside pathgroups
* @type Number
* @default 0
*/
/**
* A transform matrix to apply to the gradient before painting.
* Imported from svg gradients, is not applied with the current transform in the center.
* Before this transform is applied, the origin point is at the top left corner of the object
* plus the addition of offsetY and offsetX.
* @type Number[]
* @default null
*/
/**
* coordinates units for coords.
* If `pixels`, the number of coords are in the same unit of width / height.
* If set as `percentage` the coords are still a number, but 1 means 100% of width
* for the X and 100% of the height for the y. It can be bigger than 1 and negative.
* allowed values pixels or percentage.
* @type GradientUnits
* @default 'pixels'
*/
/**
* Gradient type linear or radial
* @type GradientType
* @default 'linear'
*/
/**
* Defines how the gradient is located in space and spread
* @type GradientCoords
*/
/**
* Defines how many colors a gradient has and how they are located on the axis
* defined by coords
* @type GradientCoords
*/
/**
* If true, this object will not be exported during the serialization of a canvas
* @type boolean
*/
/**
* ID used for SVG export functionalities
* @type number | string
*/
_defineProperty(Gradient, "type", 'Gradient');
classRegistry.setClass(Gradient, 'gradient');
classRegistry.setClass(Gradient, 'linear');
classRegistry.setClass(Gradient, 'radial');
export { Gradient };
//# sourceMappingURL=Gradient.mjs.map