UNPKG

fabric

Version:

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

227 lines (226 loc) 8.38 kB
import { _defineProperty } from "../../_virtual/_@oxc-project_runtime@0.122.0/helpers/defineProperty.mjs"; import { iMatrix } from "../constants.mjs"; import { classRegistry } from "../ClassRegistry.mjs"; import { uid } from "../util/internals/uid.mjs"; import { pick } from "../util/misc/pick.mjs"; import { matrixToSVG } from "../util/misc/svgExport.mjs"; import { isPath } from "../util/typeAssertions.mjs"; import { escapeXml } from "../util/lang_string.mjs"; import { parseTransformAttribute } from "../parser/parseTransformAttribute.mjs"; import { linearDefaultCoords, radialDefaultCoords } from "./constants.mjs"; import { parseColorStops } from "./parser/parseColorStops.mjs"; import { parseGradientUnits, parseType } from "./parser/misc.mjs"; import { parseCoords } from "./parser/parseCoords.mjs"; //#region src/gradient/Gradient.ts /** * Gradient class * @class Gradient * @see {@link http://fabric5.fabricjs.com/fabric-intro-part-2#gradients} */ var Gradient = class { constructor(options) { const { type = "linear", gradientUnits = "pixels", coords = {}, colorStops = [], offsetX = 0, offsetY = 0, gradientTransform, id } = options || {}; Object.assign(this, { type, gradientUnits, coords: { ...type === "radial" ? radialDefaultCoords : linearDefaultCoords, ...coords }, colorStops, offsetX, offsetY, gradientTransform, id: id ? `${id}_${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) this.colorStops.push({ offset: parseFloat(position), color: colorStops[position] }); 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 { ...pick(this, propertiesToInclude), type: this.type, coords: { ...this.coords }, colorStops: this.colorStops.map((colorStop) => ({ ...colorStop })), offsetX: this.offsetX, offsetY: this.offsetY, gradientUnits: this.gradientUnits, gradientTransform: this.gradientTransform ? [...this.gradientTransform] : void 0 }; } /** * 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, { additionalTransform: preTransform } = {}) { const markup = [], transform = this.gradientTransform ? this.gradientTransform.concat() : iMatrix.concat(), gradientUnits = this.gradientUnits === "pixels" ? "userSpaceOnUse" : "objectBoundingBox"; const colorStops = this.colorStops.map((colorStop) => ({ ...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; } if (isPath(object) && this.gradientUnits !== "percentage") { offsetX -= object.pathOffset.x; offsetY -= object.pathOffset.y; } transform[4] -= offsetX; transform[5] -= offsetY; const commonAttributes = [ `id="SVGID_${escapeXml(String(this.id))}"`, `gradientUnits="${gradientUnits}"`, `gradientTransform="${preTransform ? preTransform + " " : ""}${matrixToSVG(transform)}"`, "" ].join(" "); const sanitizeCoord = (value) => parseFloat(String(value)); if (this.type === "linear") { const { x1, y1, x2, y2 } = this.coords; const sx1 = sanitizeCoord(x1); const sy1 = sanitizeCoord(y1); const sx2 = sanitizeCoord(x2); const sy2 = sanitizeCoord(y2); markup.push("<linearGradient ", commonAttributes, " x1=\"", sx1, "\" y1=\"", sy1, "\" x2=\"", sx2, "\" y2=\"", sy2, "\">\n"); } else if (this.type === "radial") { const { x1, y1, x2, y2, r1, r2 } = this.coords; const sx1 = sanitizeCoord(x1); const sy1 = sanitizeCoord(y1); const sx2 = sanitizeCoord(x2); const sy2 = sanitizeCoord(y2); const sr1 = sanitizeCoord(r1); const sr2 = sanitizeCoord(r2); const needsSwap = sr1 > sr2; markup.push("<radialGradient ", commonAttributes, " cx=\"", needsSwap ? sx1 : sx2, "\" cy=\"", needsSwap ? sy1 : sy2, "\" r=\"", needsSwap ? sr1 : sr2, "\" fx=\"", needsSwap ? sx2 : sx1, "\" fy=\"", needsSwap ? sy2 : sy1, "\">\n"); if (needsSwap) { colorStops.reverse(); colorStops.forEach((colorStop) => { colorStop.offset = 1 - colorStop.offset; }); } const minRadius = Math.min(sr1, sr2); if (minRadius > 0) { const percentageShift = minRadius / Math.max(sr1, sr2); colorStops.forEach((colorStop) => { colorStop.offset += percentageShift * (1 - colorStop.offset); }); } } colorStops.forEach(({ color, offset }) => { markup.push(`<stop offset="${offset * 100}%" style="stop-color:${color};"/>\n`); }); markup.push(this.type === "linear" ? "</linearGradient>" : "</radialGradient>", "\n"); return markup.join(""); } /** * 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(({ color, offset }) => { gradient.addColorStop(offset, color); }); return gradient; } static async fromObject(options) { const { colorStops, gradientTransform } = options; return new this({ ...options, colorStops: colorStops ? colorStops.map((colorStop) => ({ ...colorStop })) : void 0, gradientTransform: gradientTransform ? [...gradientTransform] : void 0 }); } /** * Returns {@link Gradient} instance from an SVG element * @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({ id: el.getAttribute("id") || void 0, 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 } }); } }; _defineProperty(Gradient, "type", "Gradient"); classRegistry.setClass(Gradient, "gradient"); classRegistry.setClass(Gradient, "linear"); classRegistry.setClass(Gradient, "radial"); //#endregion export { Gradient }; //# sourceMappingURL=Gradient.mjs.map