import '../core/settings.mjs';
import { Color } from '../color.mjs';
import '../constants.mjs';
import '../extensions.mjs';
import '../math/const.mjs';
import { Rectangle } from '../math/shapes/Rectangle.mjs';
import '../math/groupD8.mjs';
import '../math/Transform.mjs';
import '../runner.mjs';
import { settings } from '../settings/settings.mjs';
import '../ticker/settings.mjs';
import '../ticker/const.mjs';
import '../ticker/Ticker.mjs';
import '../ticker/TickerPlugin.mjs';
import '../utils/settings.mjs';
import '../utils/is_mobile/index.mjs';
import { deprecation } from '../utils/logging/deprecation.mjs';
import '../utils/color/premultiply.mjs';
import { sign } from '../utils/data/sign.mjs';
import '../utils/media/BoundingBox.mjs';
import { trimCanvas } from '../utils/media/trimCanvas.mjs';
import '../core/autoDetectRenderer.mjs';
import '../core/background/BackgroundSystem.mjs';
import '../core/batch/BatchRenderer.mjs';
import '../core/shader/Program.mjs';
import '../core/batch/BatchSystem.mjs';
import '../core/context/ContextSystem.mjs';
import '../core/filters/Filter.mjs';
import '../core/filters/FilterSystem.mjs';
import '../core/textures/BaseTexture.mjs';
import '../core/framebuffer/FramebufferSystem.mjs';
import '../core/framebuffer/MultisampleSystem.mjs';
import '../core/geometry/BufferSystem.mjs';
import '../core/geometry/GeometrySystem.mjs';
import '../core/mask/MaskSystem.mjs';
import '../core/mask/ScissorSystem.mjs';
import '../core/mask/StencilSystem.mjs';
import '../core/plugin/PluginSystem.mjs';
import '../core/projection/ProjectionSystem.mjs';
import '../core/render/ObjectRendererSystem.mjs';
import '../core/Renderer.mjs';
import '../core/renderTexture/GenerateTextureSystem.mjs';
import { Texture } from '../core/textures/Texture.mjs';
import '../core/renderTexture/RenderTexturePool.mjs';
import '../core/renderTexture/RenderTextureSystem.mjs';
import '../core/shader/ShaderSystem.mjs';
import '../core/startup/StartupSystem.mjs';
import '../core/state/StateSystem.mjs';
import '../core/textures/TextureGCSystem.mjs';
import '../core/textures/TextureSystem.mjs';
import '../core/transformFeedback/TransformFeedbackSystem.mjs';
import '../core/view/ViewSystem.mjs';
import '../core/textures/resources/index.mjs';
import { Sprite } from '../sprite.mjs';
import { TEXT_GRADIENT } from './const.mjs';
import { TextMetrics } from './TextMetrics.mjs';
import { TextStyle } from './TextStyle.mjs';

const defaultDestroyOptions = {
  texture: true,
  children: false,
  baseTexture: true
};
class Text extends Sprite {
  /**
   * @see PIXI.TextMetrics.experimentalLetterSpacing
   * @deprecated since 7.1.0
   */
  static get experimentalLetterSpacing() {
    return TextMetrics.experimentalLetterSpacing;
  }
  static set experimentalLetterSpacing(value) {
    deprecation("7.1.0", "Text.experimentalLetterSpacing is deprecated, use TextMetrics.experimentalLetterSpacing");
    TextMetrics.experimentalLetterSpacing = value;
  }
  /**
   * @param text - The string that you would like the text to display
   * @param style - The style parameters
   * @param canvas - The canvas element for drawing text
   */
  constructor(text, style, canvas) {
    let ownCanvas = false;
    if (!canvas) {
      canvas = settings.ADAPTER.createCanvas();
      ownCanvas = true;
    }
    canvas.width = 3;
    canvas.height = 3;
    const texture = Texture.from(canvas);
    texture.orig = new Rectangle();
    texture.trim = new Rectangle();
    super(texture);
    this._ownCanvas = ownCanvas;
    this.canvas = canvas;
    this.context = canvas.getContext("2d", {
      // required for trimming to work without warnings
      willReadFrequently: true
    });
    this._resolution = Text.defaultResolution ?? settings.RESOLUTION;
    this._autoResolution = Text.defaultAutoResolution;
    this._text = null;
    this._style = null;
    this._styleListener = null;
    this._font = "";
    this.text = text;
    this.style = style;
    this.localStyleID = -1;
  }
  /**
   * Renders text to its canvas, and updates its texture.
   *
   * By default this is used internally to ensure the texture is correct before rendering,
   * but it can be used called externally, for example from this class to 'pre-generate' the texture from a piece of text,
   * and then shared across multiple Sprites.
   * @param respectDirty - Whether to abort updating the text if the Text isn't dirty and the function is called.
   */
  updateText(respectDirty) {
    const style = this._style;
    if (this.localStyleID !== style.styleID) {
      this.dirty = true;
      this.localStyleID = style.styleID;
    }
    if (!this.dirty && respectDirty) {
      return;
    }
    this._font = this._style.toFontString();
    const context = this.context;
    const measured = TextMetrics.measureText(this._text || " ", this._style, this._style.wordWrap, this.canvas);
    const width = measured.width;
    const height = measured.height;
    const lines = measured.lines;
    const lineHeight = measured.lineHeight;
    const lineWidths = measured.lineWidths;
    const maxLineWidth = measured.maxLineWidth;
    const fontProperties = measured.fontProperties;
    this.canvas.width = Math.ceil(Math.ceil(Math.max(1, width) + style.padding * 2) * this._resolution);
    this.canvas.height = Math.ceil(Math.ceil(Math.max(1, height) + style.padding * 2) * this._resolution);
    context.scale(this._resolution, this._resolution);
    context.clearRect(0, 0, this.canvas.width, this.canvas.height);
    context.font = this._font;
    context.lineWidth = style.strokeThickness;
    context.textBaseline = style.textBaseline;
    context.lineJoin = style.lineJoin;
    context.miterLimit = style.miterLimit;
    let linePositionX;
    let linePositionY;
    const passesCount = style.dropShadow ? 2 : 1;
    for (let i = 0; i < passesCount; ++i) {
      const isShadowPass = style.dropShadow && i === 0;
      const dsOffsetText = isShadowPass ? Math.ceil(Math.max(1, height) + style.padding * 2) : 0;
      const dsOffsetShadow = dsOffsetText * this._resolution;
      if (isShadowPass) {
        context.fillStyle = "black";
        context.strokeStyle = "black";
        const dropShadowColor = style.dropShadowColor;
        const dropShadowBlur = style.dropShadowBlur * this._resolution;
        const dropShadowDistance = style.dropShadowDistance * this._resolution;
        context.shadowColor = Color.shared.setValue(dropShadowColor).setAlpha(style.dropShadowAlpha).toRgbaString();
        context.shadowBlur = dropShadowBlur;
        context.shadowOffsetX = Math.cos(style.dropShadowAngle) * dropShadowDistance;
        context.shadowOffsetY = Math.sin(style.dropShadowAngle) * dropShadowDistance + dsOffsetShadow;
      } else {
        context.fillStyle = this._generateFillStyle(style, lines, measured);
        context.strokeStyle = style.stroke;
        context.shadowColor = "black";
        context.shadowBlur = 0;
        context.shadowOffsetX = 0;
        context.shadowOffsetY = 0;
      }
      let linePositionYShift = (lineHeight - fontProperties.fontSize) / 2;
      if (lineHeight - fontProperties.fontSize < 0) {
        linePositionYShift = 0;
      }
      for (let i2 = 0; i2 < lines.length; i2++) {
        linePositionX = style.strokeThickness / 2;
        linePositionY = style.strokeThickness / 2 + i2 * lineHeight + fontProperties.ascent + linePositionYShift;
        if (style.align === "right") {
          linePositionX += maxLineWidth - lineWidths[i2];
        } else if (style.align === "center") {
          linePositionX += (maxLineWidth - lineWidths[i2]) / 2;
        }
        if (style.stroke && style.strokeThickness) {
          this.drawLetterSpacing(lines[i2], linePositionX + style.padding, linePositionY + style.padding - dsOffsetText, true);
        }
        if (style.fill) {
          this.drawLetterSpacing(lines[i2], linePositionX + style.padding, linePositionY + style.padding - dsOffsetText);
        }
      }
    }
    this.updateTexture();
  }
  /**
   * Render the text with letter-spacing.
   * @param text - The text to draw
   * @param x - Horizontal position to draw the text
   * @param y - Vertical position to draw the text
   * @param isStroke - Is this drawing for the outside stroke of the
   *  text? If not, it's for the inside fill
   */
  drawLetterSpacing(text, x, y, isStroke = false) {
    const style = this._style;
    const letterSpacing = style.letterSpacing;
    let useExperimentalLetterSpacing = false;
    if (TextMetrics.experimentalLetterSpacingSupported) {
      if (TextMetrics.experimentalLetterSpacing) {
        this.context.letterSpacing = `${letterSpacing}px`;
        this.context.textLetterSpacing = `${letterSpacing}px`;
        useExperimentalLetterSpacing = true;
      } else {
        this.context.letterSpacing = "0px";
        this.context.textLetterSpacing = "0px";
      }
    }
    if (letterSpacing === 0 || useExperimentalLetterSpacing) {
      if (isStroke) {
        this.context.strokeText(text, x, y);
      } else {
        this.context.fillText(text, x, y);
      }
      return;
    }
    let currentPosition = x;
    const stringArray = TextMetrics.graphemeSegmenter(text);
    let previousWidth = this.context.measureText(text).width;
    let currentWidth = 0;
    for (let i = 0; i < stringArray.length; ++i) {
      const currentChar = stringArray[i];
      if (isStroke) {
        this.context.strokeText(currentChar, currentPosition, y);
      } else {
        this.context.fillText(currentChar, currentPosition, y);
      }
      let textStr = "";
      for (let j = i + 1; j < stringArray.length; ++j) {
        textStr += stringArray[j];
      }
      currentWidth = this.context.measureText(textStr).width;
      currentPosition += previousWidth - currentWidth + letterSpacing;
      previousWidth = currentWidth;
    }
  }
  /** Updates texture size based on canvas size. */
  updateTexture() {
    const canvas = this.canvas;
    if (this._style.trim) {
      const trimmed = trimCanvas(canvas);
      if (trimmed.data) {
        canvas.width = trimmed.width;
        canvas.height = trimmed.height;
        this.context.putImageData(trimmed.data, 0, 0);
      }
    }
    const texture = this._texture;
    const style = this._style;
    const padding = style.trim ? 0 : style.padding;
    const baseTexture = texture.baseTexture;
    texture.trim.width = texture._frame.width = canvas.width / this._resolution;
    texture.trim.height = texture._frame.height = canvas.height / this._resolution;
    texture.trim.x = -padding;
    texture.trim.y = -padding;
    texture.orig.width = texture._frame.width - padding * 2;
    texture.orig.height = texture._frame.height - padding * 2;
    this._onTextureUpdate();
    baseTexture.setRealSize(canvas.width, canvas.height, this._resolution);
    texture.updateUvs();
    this.dirty = false;
  }
  /**
   * Renders the object using the WebGL renderer
   * @param renderer - The renderer
   */
  _render(renderer) {
    if (this._autoResolution && this._resolution !== renderer.resolution) {
      this._resolution = renderer.resolution;
      this.dirty = true;
    }
    this.updateText(true);
    super._render(renderer);
  }
  /** Updates the transform on all children of this container for rendering. */
  updateTransform() {
    this.updateText(true);
    super.updateTransform();
  }
  getBounds(skipUpdate, rect) {
    this.updateText(true);
    if (this._textureID === -1) {
      skipUpdate = false;
    }
    return super.getBounds(skipUpdate, rect);
  }
  /**
   * Gets the local bounds of the text object.
   * @param rect - The output rectangle.
   * @returns The bounds.
   */
  getLocalBounds(rect) {
    this.updateText(true);
    return super.getLocalBounds.call(this, rect);
  }
  /** Calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. */
  _calculateBounds() {
    this.calculateVertices();
    this._bounds.addQuad(this.vertexData);
  }
  /**
   * Generates the fill style. Can automatically generate a gradient based on the fill style being an array
   * @param style - The style.
   * @param lines - The lines of text.
   * @param metrics
   * @returns The fill style
   */
  _generateFillStyle(style, lines, metrics) {
    const fillStyle = style.fill;
    if (!Array.isArray(fillStyle)) {
      return fillStyle;
    } else if (fillStyle.length === 1) {
      return fillStyle[0];
    }
    let gradient;
    const dropShadowCorrection = style.dropShadow ? style.dropShadowDistance : 0;
    const padding = style.padding || 0;
    const width = this.canvas.width / this._resolution - dropShadowCorrection - padding * 2;
    const height = this.canvas.height / this._resolution - dropShadowCorrection - padding * 2;
    const fill = fillStyle.slice();
    const fillGradientStops = style.fillGradientStops.slice();
    if (!fillGradientStops.length) {
      const lengthPlus1 = fill.length + 1;
      for (let i = 1; i < lengthPlus1; ++i) {
        fillGradientStops.push(i / lengthPlus1);
      }
    }
    fill.unshift(fillStyle[0]);
    fillGradientStops.unshift(0);
    fill.push(fillStyle[fillStyle.length - 1]);
    fillGradientStops.push(1);
    if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) {
      gradient = this.context.createLinearGradient(width / 2, padding, width / 2, height + padding);
      const textHeight = metrics.fontProperties.fontSize + style.strokeThickness;
      for (let i = 0; i < lines.length; i++) {
        const lastLineBottom = metrics.lineHeight * (i - 1) + textHeight;
        const thisLineTop = metrics.lineHeight * i;
        let thisLineGradientStart = thisLineTop;
        if (i > 0 && lastLineBottom > thisLineTop) {
          thisLineGradientStart = (thisLineTop + lastLineBottom) / 2;
        }
        const thisLineBottom = thisLineTop + textHeight;
        const nextLineTop = metrics.lineHeight * (i + 1);
        let thisLineGradientEnd = thisLineBottom;
        if (i + 1 < lines.length && nextLineTop < thisLineBottom) {
          thisLineGradientEnd = (thisLineBottom + nextLineTop) / 2;
        }
        const gradStopLineHeight = (thisLineGradientEnd - thisLineGradientStart) / height;
        for (let j = 0; j < fill.length; j++) {
          let lineStop = 0;
          if (typeof fillGradientStops[j] === "number") {
            lineStop = fillGradientStops[j];
          } else {
            lineStop = j / fill.length;
          }
          let globalStop = Math.min(1, Math.max(0, thisLineGradientStart / height + lineStop * gradStopLineHeight));
          globalStop = Number(globalStop.toFixed(5));
          gradient.addColorStop(globalStop, fill[j]);
        }
      }
    } else {
      gradient = this.context.createLinearGradient(padding, height / 2, width + padding, height / 2);
      const totalIterations = fill.length + 1;
      let currentIteration = 1;
      for (let i = 0; i < fill.length; i++) {
        let stop;
        if (typeof fillGradientStops[i] === "number") {
          stop = fillGradientStops[i];
        } else {
          stop = currentIteration / totalIterations;
        }
        gradient.addColorStop(stop, fill[i]);
        currentIteration++;
      }
    }
    return gradient;
  }
  /**
   * Destroys this text object.
   *
   * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as
   * the majority of the time the texture will not be shared with any other Sprites.
   * @param options - Options parameter. A boolean will act as if all options
   *  have been set to that value
   * @param {boolean} [options.children=false] - if set to true, all the children will have their
   *  destroy method called as well. 'options' will be passed on to those calls.
   * @param {boolean} [options.texture=true] - Should it destroy the current texture of the sprite as well
   * @param {boolean} [options.baseTexture=true] - Should it destroy the base texture of the sprite as well
   */
  destroy(options) {
    if (typeof options === "boolean") {
      options = { children: options };
    }
    options = Object.assign({}, defaultDestroyOptions, options);
    super.destroy(options);
    if (this._ownCanvas) {
      this.canvas.height = this.canvas.width = 0;
    }
    this.context = null;
    this.canvas = null;
    this._style = null;
  }
  /** The width of the Text, setting this will actually modify the scale to achieve the value set. */
  get width() {
    this.updateText(true);
    return Math.abs(this.scale.x) * this._texture.orig.width;
  }
  set width(value) {
    this.updateText(true);
    const s = sign(this.scale.x) || 1;
    this.scale.x = s * value / this._texture.orig.width;
    this._width = value;
  }
  /** The height of the Text, setting this will actually modify the scale to achieve the value set. */
  get height() {
    this.updateText(true);
    return Math.abs(this.scale.y) * this._texture.orig.height;
  }
  set height(value) {
    this.updateText(true);
    const s = sign(this.scale.y) || 1;
    this.scale.y = s * value / this._texture.orig.height;
    this._height = value;
  }
  /**
   * Set the style of the text.
   *
   * Set up an event listener to listen for changes on the style object and mark the text as dirty.
   *
   * If setting the `style` can also be partial {@link PIXI.ITextStyle}.
   */
  get style() {
    return this._style;
  }
  set style(style) {
    style = style || {};
    if (style instanceof TextStyle) {
      this._style = style;
    } else {
      this._style = new TextStyle(style);
    }
    this.localStyleID = -1;
    this.dirty = true;
  }
  /** Set the copy for the text object. To split a line you can use '\n'. */
  get text() {
    return this._text;
  }
  set text(text) {
    text = String(text === null || text === void 0 ? "" : text);
    if (this._text === text) {
      return;
    }
    this._text = text;
    this.dirty = true;
  }
  /**
   * The resolution / device pixel ratio of the canvas.
   *
   * This is set to automatically match the renderer resolution by default, but can be overridden by setting manually.
   * @default 1
   */
  get resolution() {
    return this._resolution;
  }
  set resolution(value) {
    this._autoResolution = false;
    if (this._resolution === value) {
      return;
    }
    this._resolution = value;
    this.dirty = true;
  }
}
Text.defaultAutoResolution = true;

export { Text };
//# sourceMappingURL=Text.mjs.map
