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