UNPKG

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
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