{"version":3,"file":"Gradient.min.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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"}