diff --git a/LICENSE b/LICENSE
index 965fe9b..9ad1145 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2025 FreedTapestry21
+Copyright (c) 2025 Bo Jordans
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..77517df
--- /dev/null
+++ b/index.html
@@ -0,0 +1,29 @@
+
+
+
+
+ Her Purpose
+
+
+
+
+
+
+
+
+
+
+
Her Purpose
+
+
+
+
+
+
Copyright (c) 2025 Bo Jordans
+
+
+
+
+
+
+
diff --git a/src/img/stella/back.png b/src/img/stella/back.png
new file mode 100644
index 0000000..3f491bb
Binary files /dev/null and b/src/img/stella/back.png differ
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..66c2112
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,25 @@
+/*
+ * Her Purpose
+ * v1.0
+ * Copyright (c) 2025 Bo Jordans
+ */
+
+(async () => {
+ const app = new PIXI.Application;
+ await app.init({
+ width: 640,
+ height: 480,
+ });
+
+ document.getElementById("game").appendChild(app.canvas);
+
+ const stellaBack = await PIXI.Assets.load('/src/img/stella/back.png');
+ const stella = new PIXI.Sprite(stellaBack);
+
+ stella.anchor.set(0.5);
+ stella.position.set(100, 100);
+ stella.scale.set(2);
+
+ /* Here I wanted to add a keyboard event, that is before I realized that PixiJS is more a render library then a game engine.
+ * I will go with Godot from here. */
+})();
diff --git a/src/pixi.js b/src/pixi.js
new file mode 100644
index 0000000..8302337
--- /dev/null
+++ b/src/pixi.js
@@ -0,0 +1,55529 @@
+/*!
+ * PixiJS - v8.13.1
+ * Compiled Wed, 03 Sep 2025 20:11:59 UTC
+ *
+ * PixiJS is licensed under the MIT License.
+ * http://www.opensource.org/licenses/mit-license
+ */
+var PIXI = (function (exports) {
+ 'use strict';
+
+ "use strict";
+ var __defProp$1e = Object.defineProperty;
+ var __defProps$s = Object.defineProperties;
+ var __getOwnPropDescs$s = Object.getOwnPropertyDescriptors;
+ var __getOwnPropSymbols$1f = Object.getOwnPropertySymbols;
+ var __hasOwnProp$1f = Object.prototype.hasOwnProperty;
+ var __propIsEnum$1f = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$1e = (obj, key, value) => key in obj ? __defProp$1e(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$1e = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$1f.call(b, prop))
+ __defNormalProp$1e(a, prop, b[prop]);
+ if (__getOwnPropSymbols$1f)
+ for (var prop of __getOwnPropSymbols$1f(b)) {
+ if (__propIsEnum$1f.call(b, prop))
+ __defNormalProp$1e(a, prop, b[prop]);
+ }
+ return a;
+ };
+ var __spreadProps$s = (a, b) => __defProps$s(a, __getOwnPropDescs$s(b));
+ var ExtensionType = /* @__PURE__ */ ((ExtensionType2) => {
+ ExtensionType2["Application"] = "application";
+ ExtensionType2["WebGLPipes"] = "webgl-pipes";
+ ExtensionType2["WebGLPipesAdaptor"] = "webgl-pipes-adaptor";
+ ExtensionType2["WebGLSystem"] = "webgl-system";
+ ExtensionType2["WebGPUPipes"] = "webgpu-pipes";
+ ExtensionType2["WebGPUPipesAdaptor"] = "webgpu-pipes-adaptor";
+ ExtensionType2["WebGPUSystem"] = "webgpu-system";
+ ExtensionType2["CanvasSystem"] = "canvas-system";
+ ExtensionType2["CanvasPipesAdaptor"] = "canvas-pipes-adaptor";
+ ExtensionType2["CanvasPipes"] = "canvas-pipes";
+ ExtensionType2["Asset"] = "asset";
+ ExtensionType2["LoadParser"] = "load-parser";
+ ExtensionType2["ResolveParser"] = "resolve-parser";
+ ExtensionType2["CacheParser"] = "cache-parser";
+ ExtensionType2["DetectionParser"] = "detection-parser";
+ ExtensionType2["MaskEffect"] = "mask-effect";
+ ExtensionType2["BlendMode"] = "blend-mode";
+ ExtensionType2["TextureSource"] = "texture-source";
+ ExtensionType2["Environment"] = "environment";
+ ExtensionType2["ShapeBuilder"] = "shape-builder";
+ ExtensionType2["Batcher"] = "batcher";
+ return ExtensionType2;
+ })(ExtensionType || {});
+ const normalizeExtension = (ext) => {
+ if (typeof ext === "function" || typeof ext === "object" && ext.extension) {
+ if (!ext.extension) {
+ throw new Error("Extension class must have an extension object");
+ }
+ const metadata = typeof ext.extension !== "object" ? { type: ext.extension } : ext.extension;
+ ext = __spreadProps$s(__spreadValues$1e({}, metadata), { ref: ext });
+ }
+ if (typeof ext === "object") {
+ ext = __spreadValues$1e({}, ext);
+ } else {
+ throw new Error("Invalid extension type");
+ }
+ if (typeof ext.type === "string") {
+ ext.type = [ext.type];
+ }
+ return ext;
+ };
+ const normalizeExtensionPriority = (ext, defaultPriority) => {
+ var _a;
+ return (_a = normalizeExtension(ext).priority) != null ? _a : defaultPriority;
+ };
+ const extensions = {
+ /** @ignore */
+ _addHandlers: {},
+ /** @ignore */
+ _removeHandlers: {},
+ /** @ignore */
+ _queue: {},
+ /**
+ * Remove extensions from PixiJS.
+ * @param extensions - Extensions to be removed. Can be:
+ * - Extension class with static `extension` property
+ * - Extension format object with `type` and `ref`
+ * - Multiple extensions as separate arguments
+ * @returns {extensions} this for chaining
+ * @example
+ * ```ts
+ * // Remove a single extension
+ * extensions.remove(MyRendererPlugin);
+ *
+ * // Remove multiple extensions
+ * extensions.remove(
+ * MyRendererPlugin,
+ * MySystemPlugin
+ * );
+ * ```
+ * @see {@link ExtensionType} For available extension types
+ * @see {@link ExtensionFormat} For extension format details
+ */
+ remove(...extensions2) {
+ extensions2.map(normalizeExtension).forEach((ext) => {
+ ext.type.forEach((type) => {
+ var _a, _b;
+ return (_b = (_a = this._removeHandlers)[type]) == null ? void 0 : _b.call(_a, ext);
+ });
+ });
+ return this;
+ },
+ /**
+ * Register new extensions with PixiJS. Extensions can be registered in multiple formats:
+ * - As a class with a static `extension` property
+ * - As an extension format object
+ * - As multiple extensions passed as separate arguments
+ * @param extensions - Extensions to add to PixiJS. Each can be:
+ * - A class with static `extension` property
+ * - An extension format object with `type` and `ref`
+ * - Multiple extensions as separate arguments
+ * @returns This extensions instance for chaining
+ * @example
+ * ```ts
+ * // Register a simple extension
+ * extensions.add(MyRendererPlugin);
+ *
+ * // Register multiple extensions
+ * extensions.add(
+ * MyRendererPlugin,
+ * MySystemPlugin,
+ * });
+ * ```
+ * @see {@link ExtensionType} For available extension types
+ * @see {@link ExtensionFormat} For extension format details
+ * @see {@link extensions.remove} For removing registered extensions
+ */
+ add(...extensions2) {
+ extensions2.map(normalizeExtension).forEach((ext) => {
+ ext.type.forEach((type) => {
+ var _a, _b;
+ const handlers = this._addHandlers;
+ const queue = this._queue;
+ if (!handlers[type]) {
+ queue[type] = queue[type] || [];
+ (_a = queue[type]) == null ? void 0 : _a.push(ext);
+ } else {
+ (_b = handlers[type]) == null ? void 0 : _b.call(handlers, ext);
+ }
+ });
+ });
+ return this;
+ },
+ /**
+ * Internal method to handle extensions by name.
+ * @param type - The extension type.
+ * @param onAdd - Function handler when extensions are added/registered {@link StrictExtensionFormat}.
+ * @param onRemove - Function handler when extensions are removed/unregistered {@link StrictExtensionFormat}.
+ * @returns this for chaining.
+ * @internal
+ * @ignore
+ */
+ handle(type, onAdd, onRemove) {
+ var _a;
+ const addHandlers = this._addHandlers;
+ const removeHandlers = this._removeHandlers;
+ if (addHandlers[type] || removeHandlers[type]) {
+ throw new Error(`Extension type ${type} already has a handler`);
+ }
+ addHandlers[type] = onAdd;
+ removeHandlers[type] = onRemove;
+ const queue = this._queue;
+ if (queue[type]) {
+ (_a = queue[type]) == null ? void 0 : _a.forEach((ext) => onAdd(ext));
+ delete queue[type];
+ }
+ return this;
+ },
+ /**
+ * Handle a type, but using a map by `name` property.
+ * @param type - Type of extension to handle.
+ * @param map - The object map of named extensions.
+ * @returns this for chaining.
+ * @ignore
+ */
+ handleByMap(type, map) {
+ return this.handle(
+ type,
+ (extension) => {
+ if (extension.name) {
+ map[extension.name] = extension.ref;
+ }
+ },
+ (extension) => {
+ if (extension.name) {
+ delete map[extension.name];
+ }
+ }
+ );
+ },
+ /**
+ * Handle a type, but using a list of extensions with a `name` property.
+ * @param type - Type of extension to handle.
+ * @param map - The array of named extensions.
+ * @param defaultPriority - Fallback priority if none is defined.
+ * @returns this for chaining.
+ * @ignore
+ */
+ handleByNamedList(type, map, defaultPriority = -1) {
+ return this.handle(
+ type,
+ (extension) => {
+ const index = map.findIndex((item) => item.name === extension.name);
+ if (index >= 0)
+ return;
+ map.push({ name: extension.name, value: extension.ref });
+ map.sort((a, b) => normalizeExtensionPriority(b.value, defaultPriority) - normalizeExtensionPriority(a.value, defaultPriority));
+ },
+ (extension) => {
+ const index = map.findIndex((item) => item.name === extension.name);
+ if (index !== -1) {
+ map.splice(index, 1);
+ }
+ }
+ );
+ },
+ /**
+ * Handle a type, but using a list of extensions.
+ * @param type - Type of extension to handle.
+ * @param list - The list of extensions.
+ * @param defaultPriority - The default priority to use if none is specified.
+ * @returns this for chaining.
+ * @ignore
+ */
+ handleByList(type, list, defaultPriority = -1) {
+ return this.handle(
+ type,
+ (extension) => {
+ if (list.includes(extension.ref)) {
+ return;
+ }
+ list.push(extension.ref);
+ list.sort((a, b) => normalizeExtensionPriority(b, defaultPriority) - normalizeExtensionPriority(a, defaultPriority));
+ },
+ (extension) => {
+ const index = list.indexOf(extension.ref);
+ if (index !== -1) {
+ list.splice(index, 1);
+ }
+ }
+ );
+ },
+ /**
+ * Mixin the source object(s) properties into the target class's prototype.
+ * Copies all property descriptors from source objects to the target's prototype.
+ * @param Target - The target class to mix properties into
+ * @param sources - One or more source objects containing properties to mix in
+ * @example
+ * ```ts
+ * // Create a mixin with shared properties
+ * const moveable = {
+ * x: 0,
+ * y: 0,
+ * move(x: number, y: number) {
+ * this.x += x;
+ * this.y += y;
+ * }
+ * };
+ *
+ * // Create a mixin with computed properties
+ * const scalable = {
+ * scale: 1,
+ * get scaled() {
+ * return this.scale > 1;
+ * }
+ * };
+ *
+ * // Apply mixins to a class
+ * extensions.mixin(Sprite, moveable, scalable);
+ *
+ * // Use mixed-in properties
+ * const sprite = new Sprite();
+ * sprite.move(10, 20);
+ * console.log(sprite.x, sprite.y); // 10, 20
+ * ```
+ * @remarks
+ * - Copies all properties including getters/setters
+ * - Does not modify source objects
+ * - Preserves property descriptors
+ * @see {@link Object.defineProperties} For details on property descriptors
+ * @see {@link Object.getOwnPropertyDescriptors} For details on property copying
+ */
+ mixin(Target, ...sources) {
+ for (const source of sources) {
+ Object.defineProperties(Target.prototype, Object.getOwnPropertyDescriptors(source));
+ }
+ }
+ };
+
+ var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
+
+ function getDefaultExportFromCjs (x) {
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
+ }
+
+ function getDefaultExportFromNamespaceIfPresent (n) {
+ return n && Object.prototype.hasOwnProperty.call(n, 'default') ? n['default'] : n;
+ }
+
+ function getDefaultExportFromNamespaceIfNotNamed (n) {
+ return n && Object.prototype.hasOwnProperty.call(n, 'default') && Object.keys(n).length === 1 ? n['default'] : n;
+ }
+
+ function getAugmentedNamespace(n) {
+ if (n.__esModule) return n;
+ var f = n.default;
+ if (typeof f == "function") {
+ var a = function a () {
+ if (this instanceof a) {
+ return Reflect.construct(f, arguments, this.constructor);
+ }
+ return f.apply(this, arguments);
+ };
+ a.prototype = f.prototype;
+ } else a = {};
+ Object.defineProperty(a, '__esModule', {value: true});
+ Object.keys(n).forEach(function (k) {
+ var d = Object.getOwnPropertyDescriptor(n, k);
+ Object.defineProperty(a, k, d.get ? d : {
+ enumerable: true,
+ get: function () {
+ return n[k];
+ }
+ });
+ });
+ return a;
+ }
+
+ var eventemitter3$1 = {exports: {}};
+
+ var eventemitter3 = eventemitter3$1.exports;
+
+ (function (module) {
+ 'use strict';
+
+ var has = Object.prototype.hasOwnProperty
+ , prefix = '~';
+
+ /**
+ * Constructor to create a storage for our `EE` objects.
+ * An `Events` instance is a plain object whose properties are event names.
+ *
+ * @constructor
+ * @private
+ */
+ function Events() {}
+
+ //
+ // We try to not inherit from `Object.prototype`. In some engines creating an
+ // instance in this way is faster than calling `Object.create(null)` directly.
+ // If `Object.create(null)` is not supported we prefix the event names with a
+ // character to make sure that the built-in object properties are not
+ // overridden or used as an attack vector.
+ //
+ if (Object.create) {
+ Events.prototype = Object.create(null);
+
+ //
+ // This hack is needed because the `__proto__` property is still inherited in
+ // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5.
+ //
+ if (!new Events().__proto__) prefix = false;
+ }
+
+ /**
+ * Representation of a single event listener.
+ *
+ * @param {Function} fn The listener function.
+ * @param {*} context The context to invoke the listener with.
+ * @param {Boolean} [once=false] Specify if the listener is a one-time listener.
+ * @constructor
+ * @private
+ */
+ function EE(fn, context, once) {
+ this.fn = fn;
+ this.context = context;
+ this.once = once || false;
+ }
+
+ /**
+ * Add a listener for a given event.
+ *
+ * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
+ * @param {(String|Symbol)} event The event name.
+ * @param {Function} fn The listener function.
+ * @param {*} context The context to invoke the listener with.
+ * @param {Boolean} once Specify if the listener is a one-time listener.
+ * @returns {EventEmitter}
+ * @private
+ */
+ function addListener(emitter, event, fn, context, once) {
+ if (typeof fn !== 'function') {
+ throw new TypeError('The listener must be a function');
+ }
+
+ var listener = new EE(fn, context || emitter, once)
+ , evt = prefix ? prefix + event : event;
+
+ if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;
+ else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);
+ else emitter._events[evt] = [emitter._events[evt], listener];
+
+ return emitter;
+ }
+
+ /**
+ * Clear event by name.
+ *
+ * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
+ * @param {(String|Symbol)} evt The Event name.
+ * @private
+ */
+ function clearEvent(emitter, evt) {
+ if (--emitter._eventsCount === 0) emitter._events = new Events();
+ else delete emitter._events[evt];
+ }
+
+ /**
+ * Minimal `EventEmitter` interface that is molded against the Node.js
+ * `EventEmitter` interface.
+ *
+ * @constructor
+ * @public
+ */
+ function EventEmitter() {
+ this._events = new Events();
+ this._eventsCount = 0;
+ }
+
+ /**
+ * Return an array listing the events for which the emitter has registered
+ * listeners.
+ *
+ * @returns {Array}
+ * @public
+ */
+ EventEmitter.prototype.eventNames = function eventNames() {
+ var names = []
+ , events
+ , name;
+
+ if (this._eventsCount === 0) return names;
+
+ for (name in (events = this._events)) {
+ if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);
+ }
+
+ if (Object.getOwnPropertySymbols) {
+ return names.concat(Object.getOwnPropertySymbols(events));
+ }
+
+ return names;
+ };
+
+ /**
+ * Return the listeners registered for a given event.
+ *
+ * @param {(String|Symbol)} event The event name.
+ * @returns {Array} The registered listeners.
+ * @public
+ */
+ EventEmitter.prototype.listeners = function listeners(event) {
+ var evt = prefix ? prefix + event : event
+ , handlers = this._events[evt];
+
+ if (!handlers) return [];
+ if (handlers.fn) return [handlers.fn];
+
+ for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
+ ee[i] = handlers[i].fn;
+ }
+
+ return ee;
+ };
+
+ /**
+ * Return the number of listeners listening to a given event.
+ *
+ * @param {(String|Symbol)} event The event name.
+ * @returns {Number} The number of listeners.
+ * @public
+ */
+ EventEmitter.prototype.listenerCount = function listenerCount(event) {
+ var evt = prefix ? prefix + event : event
+ , listeners = this._events[evt];
+
+ if (!listeners) return 0;
+ if (listeners.fn) return 1;
+ return listeners.length;
+ };
+
+ /**
+ * Calls each of the listeners registered for a given event.
+ *
+ * @param {(String|Symbol)} event The event name.
+ * @returns {Boolean} `true` if the event had listeners, else `false`.
+ * @public
+ */
+ EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
+ var evt = prefix ? prefix + event : event;
+
+ if (!this._events[evt]) return false;
+
+ var listeners = this._events[evt]
+ , len = arguments.length
+ , args
+ , i;
+
+ if (listeners.fn) {
+ if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);
+
+ switch (len) {
+ case 1: return listeners.fn.call(listeners.context), true;
+ case 2: return listeners.fn.call(listeners.context, a1), true;
+ case 3: return listeners.fn.call(listeners.context, a1, a2), true;
+ case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;
+ case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
+ case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
+ }
+
+ for (i = 1, args = new Array(len -1); i < len; i++) {
+ args[i - 1] = arguments[i];
+ }
+
+ listeners.fn.apply(listeners.context, args);
+ } else {
+ var length = listeners.length
+ , j;
+
+ for (i = 0; i < length; i++) {
+ if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);
+
+ switch (len) {
+ case 1: listeners[i].fn.call(listeners[i].context); break;
+ case 2: listeners[i].fn.call(listeners[i].context, a1); break;
+ case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;
+ case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break;
+ default:
+ if (!args) for (j = 1, args = new Array(len -1); j < len; j++) {
+ args[j - 1] = arguments[j];
+ }
+
+ listeners[i].fn.apply(listeners[i].context, args);
+ }
+ }
+ }
+
+ return true;
+ };
+
+ /**
+ * Add a listener for a given event.
+ *
+ * @param {(String|Symbol)} event The event name.
+ * @param {Function} fn The listener function.
+ * @param {*} [context=this] The context to invoke the listener with.
+ * @returns {EventEmitter} `this`.
+ * @public
+ */
+ EventEmitter.prototype.on = function on(event, fn, context) {
+ return addListener(this, event, fn, context, false);
+ };
+
+ /**
+ * Add a one-time listener for a given event.
+ *
+ * @param {(String|Symbol)} event The event name.
+ * @param {Function} fn The listener function.
+ * @param {*} [context=this] The context to invoke the listener with.
+ * @returns {EventEmitter} `this`.
+ * @public
+ */
+ EventEmitter.prototype.once = function once(event, fn, context) {
+ return addListener(this, event, fn, context, true);
+ };
+
+ /**
+ * Remove the listeners of a given event.
+ *
+ * @param {(String|Symbol)} event The event name.
+ * @param {Function} fn Only remove the listeners that match this function.
+ * @param {*} context Only remove the listeners that have this context.
+ * @param {Boolean} once Only remove one-time listeners.
+ * @returns {EventEmitter} `this`.
+ * @public
+ */
+ EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
+ var evt = prefix ? prefix + event : event;
+
+ if (!this._events[evt]) return this;
+ if (!fn) {
+ clearEvent(this, evt);
+ return this;
+ }
+
+ var listeners = this._events[evt];
+
+ if (listeners.fn) {
+ if (
+ listeners.fn === fn &&
+ (!once || listeners.once) &&
+ (!context || listeners.context === context)
+ ) {
+ clearEvent(this, evt);
+ }
+ } else {
+ for (var i = 0, events = [], length = listeners.length; i < length; i++) {
+ if (
+ listeners[i].fn !== fn ||
+ (once && !listeners[i].once) ||
+ (context && listeners[i].context !== context)
+ ) {
+ events.push(listeners[i]);
+ }
+ }
+
+ //
+ // Reset the array, or remove it completely if we have no more listeners.
+ //
+ if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;
+ else clearEvent(this, evt);
+ }
+
+ return this;
+ };
+
+ /**
+ * Remove all listeners, or those of the specified event.
+ *
+ * @param {(String|Symbol)} [event] The event name.
+ * @returns {EventEmitter} `this`.
+ * @public
+ */
+ EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
+ var evt;
+
+ if (event) {
+ evt = prefix ? prefix + event : event;
+ if (this._events[evt]) clearEvent(this, evt);
+ } else {
+ this._events = new Events();
+ this._eventsCount = 0;
+ }
+
+ return this;
+ };
+
+ //
+ // Alias methods names because people roll like that.
+ //
+ EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
+ EventEmitter.prototype.addListener = EventEmitter.prototype.on;
+
+ //
+ // Expose the prefix.
+ //
+ EventEmitter.prefixed = prefix;
+
+ //
+ // Allow `EventEmitter` to be imported as module namespace.
+ //
+ EventEmitter.EventEmitter = EventEmitter;
+
+ //
+ // Expose the module.
+ //
+ if ('undefined' !== 'object') {
+ module.exports = EventEmitter;
+ }
+ } (eventemitter3$1));
+
+ var eventemitter3Exports = eventemitter3$1.exports;
+ var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports);
+
+ var r={grad:.9,turn:360,rad:360/(2*Math.PI)},t=function(r){return "string"==typeof r?r.length>0:"number"==typeof r},n=function(r,t,n){return void 0===t&&(t=0),void 0===n&&(n=Math.pow(10,t)),Math.round(n*r)/n+0},e=function(r,t,n){return void 0===t&&(t=0),void 0===n&&(n=1),r>n?n:r>t?r:t},u=function(r){return (r=isFinite(r)?r%360:0)>0?r:r+360},a=function(r){return {r:e(r.r,0,255),g:e(r.g,0,255),b:e(r.b,0,255),a:e(r.a)}},o=function(r){return {r:n(r.r),g:n(r.g),b:n(r.b),a:n(r.a,3)}},i=/^#([0-9a-f]{3,8})$/i,s=function(r){var t=r.toString(16);return t.length<2?"0"+t:t},h=function(r){var t=r.r,n=r.g,e=r.b,u=r.a,a=Math.max(t,n,e),o=a-Math.min(t,n,e),i=o?a===t?(n-e)/o:a===n?2+(e-t)/o:4+(t-n)/o:0;return {h:60*(i<0?i+6:i),s:a?o/a*100:0,v:a/255*100,a:u}},b=function(r){var t=r.h,n=r.s,e=r.v,u=r.a;t=t/360*6,n/=100,e/=100;var a=Math.floor(t),o=e*(1-n),i=e*(1-(t-a)*n),s=e*(1-(1-t+a)*n),h=a%6;return {r:255*[e,i,o,o,s,e][h],g:255*[s,e,e,i,o,o][h],b:255*[o,o,s,e,e,i][h],a:u}},g=function(r){return {h:u(r.h),s:e(r.s,0,100),l:e(r.l,0,100),a:e(r.a)}},d=function(r){return {h:n(r.h),s:n(r.s),l:n(r.l),a:n(r.a,3)}},f=function(r){return b((n=(t=r).s,{h:t.h,s:(n*=((e=t.l)<50?e:100-e)/100)>0?2*n/(e+n)*100:0,v:e+n,a:t.a}));var t,n,e;},c=function(r){return {h:(t=h(r)).h,s:(u=(200-(n=t.s))*(e=t.v)/100)>0&&u<200?n*e/100/(u<=100?u:200-u)*100:0,l:u/2,a:t.a};var t,n,e,u;},l=/^hsla?\(\s*([+-]?\d*\.?\d+)(deg|rad|grad|turn)?\s*,\s*([+-]?\d*\.?\d+)%\s*,\s*([+-]?\d*\.?\d+)%\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i,p=/^hsla?\(\s*([+-]?\d*\.?\d+)(deg|rad|grad|turn)?\s+([+-]?\d*\.?\d+)%\s+([+-]?\d*\.?\d+)%\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i,v=/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i,m=/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i,y={string:[[function(r){var t=i.exec(r);return t?(r=t[1]).length<=4?{r:parseInt(r[0]+r[0],16),g:parseInt(r[1]+r[1],16),b:parseInt(r[2]+r[2],16),a:4===r.length?n(parseInt(r[3]+r[3],16)/255,2):1}:6===r.length||8===r.length?{r:parseInt(r.substr(0,2),16),g:parseInt(r.substr(2,2),16),b:parseInt(r.substr(4,2),16),a:8===r.length?n(parseInt(r.substr(6,2),16)/255,2):1}:null:null},"hex"],[function(r){var t=v.exec(r)||m.exec(r);return t?t[2]!==t[4]||t[4]!==t[6]?null:a({r:Number(t[1])/(t[2]?100/255:1),g:Number(t[3])/(t[4]?100/255:1),b:Number(t[5])/(t[6]?100/255:1),a:void 0===t[7]?1:Number(t[7])/(t[8]?100:1)}):null},"rgb"],[function(t){var n=l.exec(t)||p.exec(t);if(!n)return null;var e,u,a=g({h:(e=n[1],u=n[2],void 0===u&&(u="deg"),Number(e)*(r[u]||1)),s:Number(n[3]),l:Number(n[4]),a:void 0===n[5]?1:Number(n[5])/(n[6]?100:1)});return f(a)},"hsl"]],object:[[function(r){var n=r.r,e=r.g,u=r.b,o=r.a,i=void 0===o?1:o;return t(n)&&t(e)&&t(u)?a({r:Number(n),g:Number(e),b:Number(u),a:Number(i)}):null},"rgb"],[function(r){var n=r.h,e=r.s,u=r.l,a=r.a,o=void 0===a?1:a;if(!t(n)||!t(e)||!t(u))return null;var i=g({h:Number(n),s:Number(e),l:Number(u),a:Number(o)});return f(i)},"hsl"],[function(r){var n=r.h,a=r.s,o=r.v,i=r.a,s=void 0===i?1:i;if(!t(n)||!t(a)||!t(o))return null;var h=function(r){return {h:u(r.h),s:e(r.s,0,100),v:e(r.v,0,100),a:e(r.a)}}({h:Number(n),s:Number(a),v:Number(o),a:Number(s)});return b(h)},"hsv"]]},N=function(r,t){for(var n=0;n=.5},r.prototype.toHex=function(){return r=o(this.rgba),t=r.r,e=r.g,u=r.b,i=(a=r.a)<1?s(n(255*a)):"","#"+s(t)+s(e)+s(u)+i;var r,t,e,u,a,i;},r.prototype.toRgb=function(){return o(this.rgba)},r.prototype.toRgbString=function(){return r=o(this.rgba),t=r.r,n=r.g,e=r.b,(u=r.a)<1?"rgba("+t+", "+n+", "+e+", "+u+")":"rgb("+t+", "+n+", "+e+")";var r,t,n,e,u;},r.prototype.toHsl=function(){return d(c(this.rgba))},r.prototype.toHslString=function(){return r=d(c(this.rgba)),t=r.h,n=r.s,e=r.l,(u=r.a)<1?"hsla("+t+", "+n+"%, "+e+"%, "+u+")":"hsl("+t+", "+n+"%, "+e+"%)";var r,t,n,e,u;},r.prototype.toHsv=function(){return r=h(this.rgba),{h:n(r.h),s:n(r.s),v:n(r.v),a:n(r.a,3)};var r;},r.prototype.invert=function(){return w({r:255-(r=this.rgba).r,g:255-r.g,b:255-r.b,a:r.a});var r;},r.prototype.saturate=function(r){return void 0===r&&(r=.1),w(M(this.rgba,r))},r.prototype.desaturate=function(r){return void 0===r&&(r=.1),w(M(this.rgba,-r))},r.prototype.grayscale=function(){return w(M(this.rgba,-1))},r.prototype.lighten=function(r){return void 0===r&&(r=.1),w($(this.rgba,r))},r.prototype.darken=function(r){return void 0===r&&(r=.1),w($(this.rgba,-r))},r.prototype.rotate=function(r){return void 0===r&&(r=15),this.hue(this.hue()+r)},r.prototype.alpha=function(r){return "number"==typeof r?w({r:(t=this.rgba).r,g:t.g,b:t.b,a:r}):n(this.rgba.a,3);var t;},r.prototype.hue=function(r){var t=c(this.rgba);return "number"==typeof r?w({h:r,s:t.s,l:t.l,a:t.a}):n(t.h)},r.prototype.isEqual=function(r){return this.toHex()===w(r).toHex()},r}(),w=function(r){return r instanceof j?r:new j(r)},S=[],k=function(r){r.forEach(function(r){S.indexOf(r)<0&&(r(j,y),S.push(r));});},E=function(){return new j({r:255*Math.random(),g:255*Math.random(),b:255*Math.random()})};
+
+ function namesPlugin(e,f){var a={white:"#ffffff",bisque:"#ffe4c4",blue:"#0000ff",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",antiquewhite:"#faebd7",aqua:"#00ffff",azure:"#f0ffff",whitesmoke:"#f5f5f5",papayawhip:"#ffefd5",plum:"#dda0dd",blanchedalmond:"#ffebcd",black:"#000000",gold:"#ffd700",goldenrod:"#daa520",gainsboro:"#dcdcdc",cornsilk:"#fff8dc",cornflowerblue:"#6495ed",burlywood:"#deb887",aquamarine:"#7fffd4",beige:"#f5f5dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkkhaki:"#bdb76b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",peachpuff:"#ffdab9",darkmagenta:"#8b008b",darkred:"#8b0000",darkorchid:"#9932cc",darkorange:"#ff8c00",darkslateblue:"#483d8b",gray:"#808080",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",deeppink:"#ff1493",deepskyblue:"#00bfff",wheat:"#f5deb3",firebrick:"#b22222",floralwhite:"#fffaf0",ghostwhite:"#f8f8ff",darkviolet:"#9400d3",magenta:"#ff00ff",green:"#008000",dodgerblue:"#1e90ff",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",blueviolet:"#8a2be2",forestgreen:"#228b22",lawngreen:"#7cfc00",indianred:"#cd5c5c",indigo:"#4b0082",fuchsia:"#ff00ff",brown:"#a52a2a",maroon:"#800000",mediumblue:"#0000cd",lightcoral:"#f08080",darkturquoise:"#00ced1",lightcyan:"#e0ffff",ivory:"#fffff0",lightyellow:"#ffffe0",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",linen:"#faf0e6",mediumaquamarine:"#66cdaa",lemonchiffon:"#fffacd",lime:"#00ff00",khaki:"#f0e68c",mediumseagreen:"#3cb371",limegreen:"#32cd32",mediumspringgreen:"#00fa9a",lightskyblue:"#87cefa",lightblue:"#add8e6",midnightblue:"#191970",lightpink:"#ffb6c1",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",mintcream:"#f5fffa",lightslategray:"#778899",lightslategrey:"#778899",navajowhite:"#ffdead",navy:"#000080",mediumvioletred:"#c71585",powderblue:"#b0e0e6",palegoldenrod:"#eee8aa",oldlace:"#fdf5e6",paleturquoise:"#afeeee",mediumturquoise:"#48d1cc",mediumorchid:"#ba55d3",rebeccapurple:"#663399",lightsteelblue:"#b0c4de",mediumslateblue:"#7b68ee",thistle:"#d8bfd8",tan:"#d2b48c",orchid:"#da70d6",mediumpurple:"#9370db",purple:"#800080",pink:"#ffc0cb",skyblue:"#87ceeb",springgreen:"#00ff7f",palegreen:"#98fb98",red:"#ff0000",yellow:"#ffff00",slateblue:"#6a5acd",lavenderblush:"#fff0f5",peru:"#cd853f",palevioletred:"#db7093",violet:"#ee82ee",teal:"#008080",slategray:"#708090",slategrey:"#708090",aliceblue:"#f0f8ff",darkseagreen:"#8fbc8f",darkolivegreen:"#556b2f",greenyellow:"#adff2f",seagreen:"#2e8b57",seashell:"#fff5ee",tomato:"#ff6347",silver:"#c0c0c0",sienna:"#a0522d",lavender:"#e6e6fa",lightgreen:"#90ee90",orange:"#ffa500",orangered:"#ff4500",steelblue:"#4682b4",royalblue:"#4169e1",turquoise:"#40e0d0",yellowgreen:"#9acd32",salmon:"#fa8072",saddlebrown:"#8b4513",sandybrown:"#f4a460",rosybrown:"#bc8f8f",darksalmon:"#e9967a",lightgoldenrodyellow:"#fafad2",snow:"#fffafa",lightgrey:"#d3d3d3",lightgray:"#d3d3d3",dimgray:"#696969",dimgrey:"#696969",olivedrab:"#6b8e23",olive:"#808000"},r={};for(var d in a)r[a[d]]=d;var l={};e.prototype.toName=function(f){if(!(this.rgba.a||this.rgba.r||this.rgba.g||this.rgba.b))return "transparent";var d,i,n=r[this.toHex()];if(n)return n;if(null==f?void 0:f.closest){var o=this.toRgb(),t=1/0,b="black";if(!l.length)for(var c in a)l[c]=new e(a[c]).toRgb();for(var g in a){var u=(d=o,i=l[g],Math.pow(d.r-i.r,2)+Math.pow(d.g-i.g,2)+Math.pow(d.b-i.b,2));u key in obj ? __defProp$1d(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$1d = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$1e.call(b, prop))
+ __defNormalProp$1d(a, prop, b[prop]);
+ if (__getOwnPropSymbols$1e)
+ for (var prop of __getOwnPropSymbols$1e(b)) {
+ if (__propIsEnum$1e.call(b, prop))
+ __defNormalProp$1d(a, prop, b[prop]);
+ }
+ return a;
+ };
+ k([namesPlugin]);
+ const _Color = class _Color {
+ /**
+ * @param {ColorSource} value - Optional value to use, if not provided, white is used.
+ */
+ constructor(value = 16777215) {
+ this._value = null;
+ this._components = new Float32Array(4);
+ this._components.fill(1);
+ this._int = 16777215;
+ this.value = value;
+ }
+ /**
+ * Get the red component of the color, normalized between 0 and 1.
+ * @example
+ * ```ts
+ * const color = new Color('red');
+ * console.log(color.red); // 1
+ *
+ * const green = new Color('#00ff00');
+ * console.log(green.red); // 0
+ * ```
+ */
+ get red() {
+ return this._components[0];
+ }
+ /**
+ * Get the green component of the color, normalized between 0 and 1.
+ * @example
+ * ```ts
+ * const color = new Color('lime');
+ * console.log(color.green); // 1
+ *
+ * const red = new Color('#ff0000');
+ * console.log(red.green); // 0
+ * ```
+ */
+ get green() {
+ return this._components[1];
+ }
+ /**
+ * Get the blue component of the color, normalized between 0 and 1.
+ * @example
+ * ```ts
+ * const color = new Color('blue');
+ * console.log(color.blue); // 1
+ *
+ * const yellow = new Color('#ffff00');
+ * console.log(yellow.blue); // 0
+ * ```
+ */
+ get blue() {
+ return this._components[2];
+ }
+ /**
+ * Get the alpha component of the color, normalized between 0 and 1.
+ * @example
+ * ```ts
+ * const color = new Color('red');
+ * console.log(color.alpha); // 1 (fully opaque)
+ *
+ * const transparent = new Color('rgba(255, 0, 0, 0.5)');
+ * console.log(transparent.alpha); // 0.5 (semi-transparent)
+ * ```
+ */
+ get alpha() {
+ return this._components[3];
+ }
+ /**
+ * Sets the color value and returns the instance for chaining.
+ *
+ * This is a chainable version of setting the `value` property.
+ * @param value - The color to set. Accepts various formats:
+ * - Hex strings/numbers (e.g., '#ff0000', 0xff0000)
+ * - RGB/RGBA values (arrays, objects)
+ * - CSS color names
+ * - HSL/HSLA values
+ * - HSV/HSVA values
+ * @returns The Color instance for chaining
+ * @example
+ * ```ts
+ * // Basic usage
+ * const color = new Color();
+ * color.setValue('#ff0000')
+ * .setAlpha(0.5)
+ * .premultiply(0.8);
+ *
+ * // Different formats
+ * color.setValue(0xff0000); // Hex number
+ * color.setValue('#ff0000'); // Hex string
+ * color.setValue([1, 0, 0]); // RGB array
+ * color.setValue([1, 0, 0, 0.5]); // RGBA array
+ * color.setValue({ r: 1, g: 0, b: 0 }); // RGB object
+ *
+ * // Copy from another color
+ * const red = new Color('red');
+ * color.setValue(red);
+ * ```
+ * @throws {Error} If the color value is invalid or null
+ * @see {@link Color.value} For the underlying value property
+ */
+ setValue(value) {
+ this.value = value;
+ return this;
+ }
+ /**
+ * The current color source. This property allows getting and setting the color value
+ * while preserving the original format where possible.
+ * @remarks
+ * When setting:
+ * - Setting to a `Color` instance copies its source and components
+ * - Setting to other valid sources normalizes and stores the value
+ * - Setting to `null` throws an Error
+ * - The color remains unchanged if normalization fails
+ *
+ * When getting:
+ * - Returns `null` if color was modified by {@link Color.multiply} or {@link Color.premultiply}
+ * - Otherwise returns the original color source
+ * @example
+ * ```ts
+ * // Setting different color formats
+ * const color = new Color();
+ *
+ * color.value = 0xff0000; // Hex number
+ * color.value = '#ff0000'; // Hex string
+ * color.value = [1, 0, 0]; // RGB array
+ * color.value = [1, 0, 0, 0.5]; // RGBA array
+ * color.value = { r: 1, g: 0, b: 0 }; // RGB object
+ *
+ * // Copying from another color
+ * const red = new Color('red');
+ * color.value = red; // Copies red's components
+ *
+ * // Getting the value
+ * console.log(color.value); // Returns original format
+ *
+ * // After modifications
+ * color.multiply([0.5, 0.5, 0.5]);
+ * console.log(color.value); // Returns null
+ * ```
+ * @throws {Error} When attempting to set `null`
+ */
+ set value(value) {
+ if (value instanceof _Color) {
+ this._value = this._cloneSource(value._value);
+ this._int = value._int;
+ this._components.set(value._components);
+ } else if (value === null) {
+ throw new Error("Cannot set Color#value to null");
+ } else if (this._value === null || !this._isSourceEqual(this._value, value)) {
+ this._value = this._cloneSource(value);
+ this._normalize(this._value);
+ }
+ }
+ get value() {
+ return this._value;
+ }
+ /**
+ * Copy a color source internally.
+ * @param value - Color source
+ */
+ _cloneSource(value) {
+ if (typeof value === "string" || typeof value === "number" || value instanceof Number || value === null) {
+ return value;
+ } else if (Array.isArray(value) || ArrayBuffer.isView(value)) {
+ return value.slice(0);
+ } else if (typeof value === "object" && value !== null) {
+ return __spreadValues$1d({}, value);
+ }
+ return value;
+ }
+ /**
+ * Equality check for color sources.
+ * @param value1 - First color source
+ * @param value2 - Second color source
+ * @returns `true` if the color sources are equal, `false` otherwise.
+ */
+ _isSourceEqual(value1, value2) {
+ const type1 = typeof value1;
+ const type2 = typeof value2;
+ if (type1 !== type2) {
+ return false;
+ } else if (type1 === "number" || type1 === "string" || value1 instanceof Number) {
+ return value1 === value2;
+ } else if (Array.isArray(value1) && Array.isArray(value2) || ArrayBuffer.isView(value1) && ArrayBuffer.isView(value2)) {
+ if (value1.length !== value2.length) {
+ return false;
+ }
+ return value1.every((v, i) => v === value2[i]);
+ } else if (value1 !== null && value2 !== null) {
+ const keys1 = Object.keys(value1);
+ const keys2 = Object.keys(value2);
+ if (keys1.length !== keys2.length) {
+ return false;
+ }
+ return keys1.every((key) => value1[key] === value2[key]);
+ }
+ return value1 === value2;
+ }
+ /**
+ * Convert to a RGBA color object with normalized components (0-1).
+ * @example
+ * ```ts
+ * import { Color } from 'pixi.js';
+ *
+ * // Convert colors to RGBA objects
+ * new Color('white').toRgba(); // returns { r: 1, g: 1, b: 1, a: 1 }
+ * new Color('#ff0000').toRgba(); // returns { r: 1, g: 0, b: 0, a: 1 }
+ *
+ * // With transparency
+ * new Color('rgba(255,0,0,0.5)').toRgba(); // returns { r: 1, g: 0, b: 0, a: 0.5 }
+ * ```
+ * @returns An RGBA object with normalized components
+ */
+ toRgba() {
+ const [r, g, b, a] = this._components;
+ return { r, g, b, a };
+ }
+ /**
+ * Convert to a RGB color object with normalized components (0-1).
+ *
+ * Alpha component is omitted in the output.
+ * @example
+ * ```ts
+ * import { Color } from 'pixi.js';
+ *
+ * // Convert colors to RGB objects
+ * new Color('white').toRgb(); // returns { r: 1, g: 1, b: 1 }
+ * new Color('#ff0000').toRgb(); // returns { r: 1, g: 0, b: 0 }
+ *
+ * // Alpha is ignored
+ * new Color('rgba(255,0,0,0.5)').toRgb(); // returns { r: 1, g: 0, b: 0 }
+ * ```
+ * @returns An RGB object with normalized components
+ */
+ toRgb() {
+ const [r, g, b] = this._components;
+ return { r, g, b };
+ }
+ /**
+ * Convert to a CSS-style rgba string representation.
+ *
+ * RGB components are scaled to 0-255 range, alpha remains 0-1.
+ * @example
+ * ```ts
+ * import { Color } from 'pixi.js';
+ *
+ * // Convert colors to RGBA strings
+ * new Color('white').toRgbaString(); // returns "rgba(255,255,255,1)"
+ * new Color('#ff0000').toRgbaString(); // returns "rgba(255,0,0,1)"
+ *
+ * // With transparency
+ * new Color([1, 0, 0, 0.5]).toRgbaString(); // returns "rgba(255,0,0,0.5)"
+ * ```
+ * @returns A CSS-compatible rgba string
+ */
+ toRgbaString() {
+ const [r, g, b] = this.toUint8RgbArray();
+ return `rgba(${r},${g},${b},${this.alpha})`;
+ }
+ /**
+ * Convert to an [R, G, B] array of clamped uint8 values (0 to 255).
+ * @param {number[]|Uint8Array|Uint8ClampedArray} [out] - Optional output array. If not provided,
+ * a cached array will be used and returned.
+ * @returns Array containing RGB components as integers between 0-255
+ * @example
+ * ```ts
+ * // Basic usage
+ * new Color('white').toUint8RgbArray(); // returns [255, 255, 255]
+ * new Color('#ff0000').toUint8RgbArray(); // returns [255, 0, 0]
+ *
+ * // Using custom output array
+ * const rgb = new Uint8Array(3);
+ * new Color('blue').toUint8RgbArray(rgb); // rgb is now [0, 0, 255]
+ *
+ * // Using different array types
+ * new Color('red').toUint8RgbArray(new Uint8ClampedArray(3)); // [255, 0, 0]
+ * new Color('red').toUint8RgbArray([]); // [255, 0, 0]
+ * ```
+ * @remarks
+ * - Output values are always clamped between 0-255
+ * - Alpha component is not included in output
+ * - Reuses internal cache array if no output array provided
+ */
+ toUint8RgbArray(out) {
+ const [r, g, b] = this._components;
+ if (!this._arrayRgb) {
+ this._arrayRgb = [];
+ }
+ out || (out = this._arrayRgb);
+ out[0] = Math.round(r * 255);
+ out[1] = Math.round(g * 255);
+ out[2] = Math.round(b * 255);
+ return out;
+ }
+ /**
+ * Convert to an [R, G, B, A] array of normalized floats (numbers from 0.0 to 1.0).
+ * @param {number[]|Float32Array} [out] - Optional output array. If not provided,
+ * a cached array will be used and returned.
+ * @returns Array containing RGBA components as floats between 0-1
+ * @example
+ * ```ts
+ * // Basic usage
+ * new Color('white').toArray(); // returns [1, 1, 1, 1]
+ * new Color('red').toArray(); // returns [1, 0, 0, 1]
+ *
+ * // With alpha
+ * new Color('rgba(255,0,0,0.5)').toArray(); // returns [1, 0, 0, 0.5]
+ *
+ * // Using custom output array
+ * const rgba = new Float32Array(4);
+ * new Color('blue').toArray(rgba); // rgba is now [0, 0, 1, 1]
+ * ```
+ * @remarks
+ * - Output values are normalized between 0-1
+ * - Includes alpha component as the fourth value
+ * - Reuses internal cache array if no output array provided
+ */
+ toArray(out) {
+ if (!this._arrayRgba) {
+ this._arrayRgba = [];
+ }
+ out || (out = this._arrayRgba);
+ const [r, g, b, a] = this._components;
+ out[0] = r;
+ out[1] = g;
+ out[2] = b;
+ out[3] = a;
+ return out;
+ }
+ /**
+ * Convert to an [R, G, B] array of normalized floats (numbers from 0.0 to 1.0).
+ * @param {number[]|Float32Array} [out] - Optional output array. If not provided,
+ * a cached array will be used and returned.
+ * @returns Array containing RGB components as floats between 0-1
+ * @example
+ * ```ts
+ * // Basic usage
+ * new Color('white').toRgbArray(); // returns [1, 1, 1]
+ * new Color('red').toRgbArray(); // returns [1, 0, 0]
+ *
+ * // Using custom output array
+ * const rgb = new Float32Array(3);
+ * new Color('blue').toRgbArray(rgb); // rgb is now [0, 0, 1]
+ * ```
+ * @remarks
+ * - Output values are normalized between 0-1
+ * - Alpha component is omitted from output
+ * - Reuses internal cache array if no output array provided
+ */
+ toRgbArray(out) {
+ if (!this._arrayRgb) {
+ this._arrayRgb = [];
+ }
+ out || (out = this._arrayRgb);
+ const [r, g, b] = this._components;
+ out[0] = r;
+ out[1] = g;
+ out[2] = b;
+ return out;
+ }
+ /**
+ * Convert to a hexadecimal number.
+ * @returns The color as a 24-bit RGB integer
+ * @example
+ * ```ts
+ * // Basic usage
+ * new Color('white').toNumber(); // returns 0xffffff
+ * new Color('red').toNumber(); // returns 0xff0000
+ *
+ * // Store as hex
+ * const color = new Color('blue');
+ * const hex = color.toNumber(); // 0x0000ff
+ * ```
+ */
+ toNumber() {
+ return this._int;
+ }
+ /**
+ * Convert to a BGR number.
+ *
+ * Useful for platforms that expect colors in BGR format.
+ * @returns The color as a 24-bit BGR integer
+ * @example
+ * ```ts
+ * // Convert RGB to BGR
+ * new Color(0xffcc99).toBgrNumber(); // returns 0x99ccff
+ *
+ * // Common use case: platform-specific color format
+ * const color = new Color('orange');
+ * const bgrColor = color.toBgrNumber(); // Color with swapped R/B channels
+ * ```
+ * @remarks
+ * This swaps the red and blue channels compared to the normal RGB format:
+ * - RGB 0xRRGGBB becomes BGR 0xBBGGRR
+ */
+ toBgrNumber() {
+ const [r, g, b] = this.toUint8RgbArray();
+ return (b << 16) + (g << 8) + r;
+ }
+ /**
+ * Convert to a hexadecimal number in little endian format (e.g., BBGGRR).
+ *
+ * Useful for platforms that expect colors in little endian byte order.
+ * @example
+ * ```ts
+ * import { Color } from 'pixi.js';
+ *
+ * // Convert RGB color to little endian format
+ * new Color(0xffcc99).toLittleEndianNumber(); // returns 0x99ccff
+ *
+ * // Common use cases:
+ * const color = new Color('orange');
+ * const leColor = color.toLittleEndianNumber(); // Swaps byte order for LE systems
+ *
+ * // Multiple conversions
+ * const colors = {
+ * normal: 0xffcc99,
+ * littleEndian: new Color(0xffcc99).toLittleEndianNumber(), // 0x99ccff
+ * backToNormal: new Color(0x99ccff).toLittleEndianNumber() // 0xffcc99
+ * };
+ * ```
+ * @remarks
+ * - Swaps R and B channels in the color value
+ * - RGB 0xRRGGBB becomes 0xBBGGRR
+ * - Useful for systems that use little endian byte order
+ * - Can be used to convert back and forth between formats
+ * @returns The color as a number in little endian format (BBGGRR)
+ * @see {@link Color.toBgrNumber} For BGR format without byte swapping
+ */
+ toLittleEndianNumber() {
+ const value = this._int;
+ return (value >> 16) + (value & 65280) + ((value & 255) << 16);
+ }
+ /**
+ * Multiply with another color.
+ *
+ * This action is destructive and modifies the original color.
+ * @param {ColorSource} value - The color to multiply by. Accepts any valid color format:
+ * - Hex strings/numbers (e.g., '#ff0000', 0xff0000)
+ * - RGB/RGBA arrays ([1, 0, 0], [1, 0, 0, 1])
+ * - Color objects ({ r: 1, g: 0, b: 0 })
+ * - CSS color names ('red', 'blue')
+ * @returns this - The Color instance for chaining
+ * @example
+ * ```ts
+ * // Basic multiplication
+ * const color = new Color('#ff0000');
+ * color.multiply(0x808080); // 50% darker red
+ *
+ * // With transparency
+ * color.multiply([1, 1, 1, 0.5]); // 50% transparent
+ *
+ * // Chain operations
+ * color
+ * .multiply('#808080')
+ * .multiply({ r: 1, g: 1, b: 1, a: 0.5 });
+ * ```
+ * @remarks
+ * - Multiplies each RGB component and alpha separately
+ * - Values are clamped between 0-1
+ * - Original color format is lost (value becomes null)
+ * - Operation cannot be undone
+ */
+ multiply(value) {
+ const [r, g, b, a] = _Color._temp.setValue(value)._components;
+ this._components[0] *= r;
+ this._components[1] *= g;
+ this._components[2] *= b;
+ this._components[3] *= a;
+ this._refreshInt();
+ this._value = null;
+ return this;
+ }
+ /**
+ * Converts color to a premultiplied alpha format.
+ *
+ * This action is destructive and modifies the original color.
+ * @param alpha - The alpha value to multiply by (0-1)
+ * @param {boolean} [applyToRGB=true] - Whether to premultiply RGB channels
+ * @returns {Color} The Color instance for chaining
+ * @example
+ * ```ts
+ * // Basic premultiplication
+ * const color = new Color('red');
+ * color.premultiply(0.5); // 50% transparent red with premultiplied RGB
+ *
+ * // Alpha only (RGB unchanged)
+ * color.premultiply(0.5, false); // 50% transparent, original RGB
+ *
+ * // Chain with other operations
+ * color
+ * .multiply(0x808080)
+ * .premultiply(0.5)
+ * .toNumber();
+ * ```
+ * @remarks
+ * - RGB channels are multiplied by alpha when applyToRGB is true
+ * - Alpha is always set to the provided value
+ * - Values are clamped between 0-1
+ * - Original color format is lost (value becomes null)
+ * - Operation cannot be undone
+ */
+ premultiply(alpha, applyToRGB = true) {
+ if (applyToRGB) {
+ this._components[0] *= alpha;
+ this._components[1] *= alpha;
+ this._components[2] *= alpha;
+ }
+ this._components[3] = alpha;
+ this._refreshInt();
+ this._value = null;
+ return this;
+ }
+ /**
+ * Returns the color as a 32-bit premultiplied alpha integer.
+ *
+ * Format: 0xAARRGGBB
+ * @param {number} alpha - The alpha value to multiply by (0-1)
+ * @param {boolean} [applyToRGB=true] - Whether to premultiply RGB channels
+ * @returns {number} The premultiplied color as a 32-bit integer
+ * @example
+ * ```ts
+ * // Convert to premultiplied format
+ * const color = new Color('red');
+ *
+ * // Full opacity (0xFFRRGGBB)
+ * color.toPremultiplied(1.0); // 0xFFFF0000
+ *
+ * // 50% transparency with premultiplied RGB
+ * color.toPremultiplied(0.5); // 0x7F7F0000
+ *
+ * // 50% transparency without RGB premultiplication
+ * color.toPremultiplied(0.5, false); // 0x7FFF0000
+ * ```
+ * @remarks
+ * - Returns full opacity (0xFF000000) when alpha is 1.0
+ * - Returns 0 when alpha is 0.0 and applyToRGB is true
+ * - RGB values are rounded during premultiplication
+ */
+ toPremultiplied(alpha, applyToRGB = true) {
+ if (alpha === 1) {
+ return (255 << 24) + this._int;
+ }
+ if (alpha === 0) {
+ return applyToRGB ? 0 : this._int;
+ }
+ let r = this._int >> 16 & 255;
+ let g = this._int >> 8 & 255;
+ let b = this._int & 255;
+ if (applyToRGB) {
+ r = r * alpha + 0.5 | 0;
+ g = g * alpha + 0.5 | 0;
+ b = b * alpha + 0.5 | 0;
+ }
+ return (alpha * 255 << 24) + (r << 16) + (g << 8) + b;
+ }
+ /**
+ * Convert to a hexadecimal string (6 characters).
+ * @returns A CSS-compatible hex color string (e.g., "#ff0000")
+ * @example
+ * ```ts
+ * import { Color } from 'pixi.js';
+ *
+ * // Basic colors
+ * new Color('red').toHex(); // returns "#ff0000"
+ * new Color('white').toHex(); // returns "#ffffff"
+ * new Color('black').toHex(); // returns "#000000"
+ *
+ * // From different formats
+ * new Color(0xff0000).toHex(); // returns "#ff0000"
+ * new Color([1, 0, 0]).toHex(); // returns "#ff0000"
+ * new Color({ r: 1, g: 0, b: 0 }).toHex(); // returns "#ff0000"
+ * ```
+ * @remarks
+ * - Always returns a 6-character hex string
+ * - Includes leading "#" character
+ * - Alpha channel is ignored
+ * - Values are rounded to nearest hex value
+ */
+ toHex() {
+ const hexString = this._int.toString(16);
+ return `#${"000000".substring(0, 6 - hexString.length) + hexString}`;
+ }
+ /**
+ * Convert to a hexadecimal string with alpha (8 characters).
+ * @returns A CSS-compatible hex color string with alpha (e.g., "#ff0000ff")
+ * @example
+ * ```ts
+ * import { Color } from 'pixi.js';
+ *
+ * // Fully opaque colors
+ * new Color('red').toHexa(); // returns "#ff0000ff"
+ * new Color('white').toHexa(); // returns "#ffffffff"
+ *
+ * // With transparency
+ * new Color('rgba(255, 0, 0, 0.5)').toHexa(); // returns "#ff00007f"
+ * new Color([1, 0, 0, 0]).toHexa(); // returns "#ff000000"
+ * ```
+ * @remarks
+ * - Returns an 8-character hex string
+ * - Includes leading "#" character
+ * - Alpha is encoded in last two characters
+ * - Values are rounded to nearest hex value
+ */
+ toHexa() {
+ const alphaValue = Math.round(this._components[3] * 255);
+ const alphaString = alphaValue.toString(16);
+ return this.toHex() + "00".substring(0, 2 - alphaString.length) + alphaString;
+ }
+ /**
+ * Set alpha (transparency) value while preserving color components.
+ *
+ * Provides a chainable interface for setting alpha.
+ * @param alpha - Alpha value between 0 (fully transparent) and 1 (fully opaque)
+ * @returns The Color instance for chaining
+ * @example
+ * ```ts
+ * // Basic alpha setting
+ * const color = new Color('red');
+ * color.setAlpha(0.5); // 50% transparent red
+ *
+ * // Chain with other operations
+ * color
+ * .setValue('#ff0000')
+ * .setAlpha(0.8) // 80% opaque
+ * .premultiply(0.5); // Further modify alpha
+ *
+ * // Reset to fully opaque
+ * color.setAlpha(1);
+ * ```
+ * @remarks
+ * - Alpha value is clamped between 0-1
+ * - Can be chained with other color operations
+ */
+ setAlpha(alpha) {
+ this._components[3] = this._clamp(alpha);
+ return this;
+ }
+ /**
+ * Normalize the input value into rgba
+ * @param value - Input value
+ */
+ _normalize(value) {
+ let r;
+ let g;
+ let b;
+ let a;
+ if ((typeof value === "number" || value instanceof Number) && value >= 0 && value <= 16777215) {
+ const int = value;
+ r = (int >> 16 & 255) / 255;
+ g = (int >> 8 & 255) / 255;
+ b = (int & 255) / 255;
+ a = 1;
+ } else if ((Array.isArray(value) || value instanceof Float32Array) && value.length >= 3 && value.length <= 4) {
+ value = this._clamp(value);
+ [r, g, b, a = 1] = value;
+ } else if ((value instanceof Uint8Array || value instanceof Uint8ClampedArray) && value.length >= 3 && value.length <= 4) {
+ value = this._clamp(value, 0, 255);
+ [r, g, b, a = 255] = value;
+ r /= 255;
+ g /= 255;
+ b /= 255;
+ a /= 255;
+ } else if (typeof value === "string" || typeof value === "object") {
+ if (typeof value === "string") {
+ const match = _Color.HEX_PATTERN.exec(value);
+ if (match) {
+ value = `#${match[2]}`;
+ }
+ }
+ const color = w(value);
+ if (color.isValid()) {
+ ({ r, g, b, a } = color.rgba);
+ r /= 255;
+ g /= 255;
+ b /= 255;
+ }
+ }
+ if (r !== void 0) {
+ this._components[0] = r;
+ this._components[1] = g;
+ this._components[2] = b;
+ this._components[3] = a;
+ this._refreshInt();
+ } else {
+ throw new Error(`Unable to convert color ${value}`);
+ }
+ }
+ /** Refresh the internal color rgb number */
+ _refreshInt() {
+ this._clamp(this._components);
+ const [r, g, b] = this._components;
+ this._int = (r * 255 << 16) + (g * 255 << 8) + (b * 255 | 0);
+ }
+ /**
+ * Clamps values to a range. Will override original values
+ * @param value - Value(s) to clamp
+ * @param min - Minimum value
+ * @param max - Maximum value
+ */
+ _clamp(value, min = 0, max = 1) {
+ if (typeof value === "number") {
+ return Math.min(Math.max(value, min), max);
+ }
+ value.forEach((v, i) => {
+ value[i] = Math.min(Math.max(v, min), max);
+ });
+ return value;
+ }
+ /**
+ * Check if a value can be interpreted as a valid color format.
+ * Supports all color formats that can be used with the Color class.
+ * @param value - Value to check
+ * @returns True if the value can be used as a color
+ * @example
+ * ```ts
+ * import { Color } from 'pixi.js';
+ *
+ * // CSS colors and hex values
+ * Color.isColorLike('red'); // true
+ * Color.isColorLike('#ff0000'); // true
+ * Color.isColorLike(0xff0000); // true
+ *
+ * // Arrays (RGB/RGBA)
+ * Color.isColorLike([1, 0, 0]); // true
+ * Color.isColorLike([1, 0, 0, 0.5]); // true
+ *
+ * // TypedArrays
+ * Color.isColorLike(new Float32Array([1, 0, 0])); // true
+ * Color.isColorLike(new Uint8Array([255, 0, 0])); // true
+ * Color.isColorLike(new Uint8ClampedArray([255, 0, 0])); // true
+ *
+ * // Object formats
+ * Color.isColorLike({ r: 1, g: 0, b: 0 }); // true (RGB)
+ * Color.isColorLike({ r: 1, g: 0, b: 0, a: 0.5 }); // true (RGBA)
+ * Color.isColorLike({ h: 0, s: 100, l: 50 }); // true (HSL)
+ * Color.isColorLike({ h: 0, s: 100, l: 50, a: 0.5 }); // true (HSLA)
+ * Color.isColorLike({ h: 0, s: 100, v: 100 }); // true (HSV)
+ * Color.isColorLike({ h: 0, s: 100, v: 100, a: 0.5 });// true (HSVA)
+ *
+ * // Color instances
+ * Color.isColorLike(new Color('red')); // true
+ *
+ * // Invalid values
+ * Color.isColorLike(null); // false
+ * Color.isColorLike(undefined); // false
+ * Color.isColorLike({}); // false
+ * Color.isColorLike([]); // false
+ * Color.isColorLike('not-a-color'); // false
+ * ```
+ * @remarks
+ * Checks for the following formats:
+ * - Numbers (0x000000 to 0xffffff)
+ * - CSS color strings
+ * - RGB/RGBA arrays and objects
+ * - HSL/HSLA objects
+ * - HSV/HSVA objects
+ * - TypedArrays (Float32Array, Uint8Array, Uint8ClampedArray)
+ * - Color instances
+ * @see {@link ColorSource} For supported color format types
+ * @see {@link Color.setValue} For setting color values
+ * @category utility
+ */
+ static isColorLike(value) {
+ return typeof value === "number" || typeof value === "string" || value instanceof Number || value instanceof _Color || Array.isArray(value) || value instanceof Uint8Array || value instanceof Uint8ClampedArray || value instanceof Float32Array || value.r !== void 0 && value.g !== void 0 && value.b !== void 0 || value.r !== void 0 && value.g !== void 0 && value.b !== void 0 && value.a !== void 0 || value.h !== void 0 && value.s !== void 0 && value.l !== void 0 || value.h !== void 0 && value.s !== void 0 && value.l !== void 0 && value.a !== void 0 || value.h !== void 0 && value.s !== void 0 && value.v !== void 0 || value.h !== void 0 && value.s !== void 0 && value.v !== void 0 && value.a !== void 0;
+ }
+ };
+ /**
+ * Static shared Color instance used for utility operations. This is a singleton color object
+ * that can be reused to avoid creating unnecessary Color instances.
+ * > [!IMPORTANT] You should be careful when using this shared instance, as it is mutable and can be
+ * > changed by any code that uses it.
+ * >
+ * > It is best used for one-off color operations or temporary transformations.
+ * > For persistent colors, create your own Color instance instead.
+ * @example
+ * ```ts
+ * import { Color } from 'pixi.js';
+ *
+ * // Use shared instance for one-off color operations
+ * Color.shared.setValue(0xff0000);
+ * const redHex = Color.shared.toHex(); // "#ff0000"
+ * const redRgb = Color.shared.toRgbArray(); // [1, 0, 0]
+ *
+ * // Temporary color transformations
+ * const colorNumber = Color.shared
+ * .setValue('#ff0000') // Set to red
+ * .setAlpha(0.5) // Make semi-transparent
+ * .premultiply(0.8) // Apply premultiplication
+ * .toNumber(); // Convert to number
+ *
+ * // Chain multiple operations
+ * const result = Color.shared
+ * .setValue(someColor)
+ * .multiply(tintColor)
+ * .toPremultiplied(alpha);
+ * ```
+ * @remarks
+ * - This is a shared instance - be careful about multiple code paths using it simultaneously
+ * - Use for temporary color operations to avoid allocating new Color instances
+ * - The value is preserved between operations, so reset if needed
+ * - For persistent colors, create your own Color instance instead
+ */
+ _Color.shared = new _Color();
+ /**
+ * Temporary Color object for static uses internally.
+ * As to not conflict with Color.shared.
+ * @ignore
+ */
+ _Color._temp = new _Color();
+ /** Pattern for hex strings */
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ _Color.HEX_PATTERN = /^(#|0x)?(([a-f0-9]{3}){1,2}([a-f0-9]{2})?)$/i;
+ let Color = _Color;
+
+ "use strict";
+ const cullingMixin = {
+ cullArea: null,
+ cullable: false,
+ cullableChildren: true
+ };
+
+ "use strict";
+ const PI_2 = Math.PI * 2;
+ const RAD_TO_DEG = 180 / Math.PI;
+ const DEG_TO_RAD = Math.PI / 180;
+
+ "use strict";
+ class Point {
+ /**
+ * Creates a new `Point`
+ * @param {number} [x=0] - position of the point on the x axis
+ * @param {number} [y=0] - position of the point on the y axis
+ */
+ constructor(x = 0, y = 0) {
+ /**
+ * Position of the point on the x axis
+ * @example
+ * ```ts
+ * // Set x position
+ * const point = new Point();
+ * point.x = 100;
+ *
+ * // Use in calculations
+ * const width = rightPoint.x - leftPoint.x;
+ * ```
+ */
+ this.x = 0;
+ /**
+ * Position of the point on the y axis
+ * @example
+ * ```ts
+ * // Set y position
+ * const point = new Point();
+ * point.y = 200;
+ *
+ * // Use in calculations
+ * const height = bottomPoint.y - topPoint.y;
+ * ```
+ */
+ this.y = 0;
+ this.x = x;
+ this.y = y;
+ }
+ /**
+ * Creates a clone of this point, which is a new instance with the same `x` and `y` values.
+ * @example
+ * ```ts
+ * // Basic point cloning
+ * const original = new Point(100, 200);
+ * const copy = original.clone();
+ *
+ * // Clone and modify
+ * const modified = original.clone();
+ * modified.set(300, 400);
+ *
+ * // Verify independence
+ * console.log(original); // Point(100, 200)
+ * console.log(modified); // Point(300, 400)
+ * ```
+ * @remarks
+ * - Creates new Point instance
+ * - Deep copies x and y values
+ * - Independent from original
+ * - Useful for preserving values
+ * @returns A clone of this point
+ * @see {@link Point.copyFrom} For copying into existing point
+ * @see {@link Point.copyTo} For copying to existing point
+ */
+ clone() {
+ return new Point(this.x, this.y);
+ }
+ /**
+ * Copies x and y from the given point into this point.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new Point(100, 200);
+ * const target = new Point();
+ * target.copyFrom(source);
+ *
+ * // Copy and chain operations
+ * const point = new Point()
+ * .copyFrom(source)
+ * .set(x + 50, y + 50);
+ *
+ * // Copy from any PointData
+ * const data = { x: 10, y: 20 };
+ * point.copyFrom(data);
+ * ```
+ * @param p - The point to copy from
+ * @returns The point instance itself
+ * @see {@link Point.copyTo} For copying to another point
+ * @see {@link Point.clone} For creating new point copy
+ */
+ copyFrom(p) {
+ this.set(p.x, p.y);
+ return this;
+ }
+ /**
+ * Copies this point's x and y into the given point.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new Point(100, 200);
+ * const target = new Point();
+ * source.copyTo(target);
+ * ```
+ * @param p - The point to copy to. Can be any type that is or extends `PointLike`
+ * @returns The point (`p`) with values updated
+ * @see {@link Point.copyFrom} For copying from another point
+ * @see {@link Point.clone} For creating new point copy
+ */
+ copyTo(p) {
+ p.set(this.x, this.y);
+ return p;
+ }
+ /**
+ * Checks if another point is equal to this point.
+ *
+ * Compares x and y values using strict equality.
+ * @example
+ * ```ts
+ * // Basic equality check
+ * const p1 = new Point(100, 200);
+ * const p2 = new Point(100, 200);
+ * console.log(p1.equals(p2)); // true
+ *
+ * // Compare with PointData
+ * const data = { x: 100, y: 200 };
+ * console.log(p1.equals(data)); // true
+ *
+ * // Check different points
+ * const p3 = new Point(200, 300);
+ * console.log(p1.equals(p3)); // false
+ * ```
+ * @param p - The point to check
+ * @returns `true` if both `x` and `y` are equal
+ * @see {@link Point.copyFrom} For making points equal
+ * @see {@link PointData} For point data interface
+ */
+ equals(p) {
+ return p.x === this.x && p.y === this.y;
+ }
+ /**
+ * Sets the point to a new x and y position.
+ *
+ * If y is omitted, both x and y will be set to x.
+ * @example
+ * ```ts
+ * // Basic position setting
+ * const point = new Point();
+ * point.set(100, 200);
+ *
+ * // Set both x and y to same value
+ * point.set(50); // x=50, y=50
+ *
+ * // Chain with other operations
+ * point
+ * .set(10, 20)
+ * .copyTo(otherPoint);
+ * ```
+ * @param x - Position on the x axis
+ * @param y - Position on the y axis, defaults to x
+ * @returns The point instance itself
+ * @see {@link Point.copyFrom} For copying from another point
+ * @see {@link Point.equals} For comparing positions
+ */
+ set(x = 0, y = x) {
+ this.x = x;
+ this.y = y;
+ return this;
+ }
+ toString() {
+ return `[pixi.js/math:Point x=${this.x} y=${this.y}]`;
+ }
+ /**
+ * A static Point object with `x` and `y` values of `0`.
+ *
+ * This shared instance is reset to zero values when accessed.
+ *
+ * > [!IMPORTANT] This point is shared and temporary. Do not store references to it.
+ * @example
+ * ```ts
+ * // Use for temporary calculations
+ * const tempPoint = Point.shared;
+ * tempPoint.set(100, 200);
+ * matrix.apply(tempPoint);
+ *
+ * // Will be reset to (0,0) on next access
+ * const fresh = Point.shared; // x=0, y=0
+ * ```
+ * @readonly
+ * @returns A fresh zeroed point for temporary use
+ * @see {@link Point.constructor} For creating new points
+ * @see {@link PointData} For basic point interface
+ */
+ static get shared() {
+ tempPoint.x = 0;
+ tempPoint.y = 0;
+ return tempPoint;
+ }
+ }
+ const tempPoint = new Point();
+
+ "use strict";
+ class Matrix {
+ /**
+ * @param a - x scale
+ * @param b - y skew
+ * @param c - x skew
+ * @param d - y scale
+ * @param tx - x translation
+ * @param ty - y translation
+ */
+ constructor(a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0) {
+ /**
+ * Array representation of the matrix.
+ * Only populated when `toArray()` is called.
+ * @default null
+ * @see {@link Matrix.toArray} For filling this array
+ */
+ this.array = null;
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ this.tx = tx;
+ this.ty = ty;
+ }
+ /**
+ * Creates a Matrix object based on the given array.
+ * Populates matrix components from a flat array in column-major order.
+ *
+ * > [!NOTE] Array mapping order:
+ * > ```
+ * > array[0] = a (x scale)
+ * > array[1] = b (y skew)
+ * > array[2] = tx (x translation)
+ * > array[3] = c (x skew)
+ * > array[4] = d (y scale)
+ * > array[5] = ty (y translation)
+ * > ```
+ * @example
+ * ```ts
+ * // Create matrix from array
+ * const matrix = new Matrix();
+ * matrix.fromArray([
+ * 2, 0, 100, // a, b, tx
+ * 0, 2, 100 // c, d, ty
+ * ]);
+ *
+ * // Create matrix from typed array
+ * const float32Array = new Float32Array([
+ * 1, 0, 0, // Scale x1, no skew
+ * 0, 1, 0 // No skew, scale x1
+ * ]);
+ * matrix.fromArray(float32Array);
+ * ```
+ * @param array - The array to populate the matrix from
+ * @see {@link Matrix.toArray} For converting matrix to array
+ * @see {@link Matrix.set} For setting values directly
+ */
+ fromArray(array) {
+ this.a = array[0];
+ this.b = array[1];
+ this.c = array[3];
+ this.d = array[4];
+ this.tx = array[2];
+ this.ty = array[5];
+ }
+ /**
+ * Sets the matrix properties directly.
+ * All matrix components can be set in one call.
+ * @example
+ * ```ts
+ * // Set to identity matrix
+ * matrix.set(1, 0, 0, 1, 0, 0);
+ *
+ * // Set to scale matrix
+ * matrix.set(2, 0, 0, 2, 0, 0); // Scale 2x
+ *
+ * // Set to translation matrix
+ * matrix.set(1, 0, 0, 1, 100, 50); // Move 100,50
+ * ```
+ * @param a - Scale on x axis
+ * @param b - Shear on y axis
+ * @param c - Shear on x axis
+ * @param d - Scale on y axis
+ * @param tx - Translation on x axis
+ * @param ty - Translation on y axis
+ * @returns This matrix. Good for chaining method calls.
+ * @see {@link Matrix.identity} For resetting to identity
+ * @see {@link Matrix.fromArray} For setting from array
+ */
+ set(a, b, c, d, tx, ty) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ this.tx = tx;
+ this.ty = ty;
+ return this;
+ }
+ /**
+ * Creates an array from the current Matrix object.
+ *
+ * > [!NOTE] The array format is:
+ * > ```
+ * > Non-transposed:
+ * > [a, c, tx,
+ * > b, d, ty,
+ * > 0, 0, 1]
+ * >
+ * > Transposed:
+ * > [a, b, 0,
+ * > c, d, 0,
+ * > tx,ty,1]
+ * > ```
+ * @example
+ * ```ts
+ * // Basic array conversion
+ * const matrix = new Matrix(2, 0, 0, 2, 100, 100);
+ * const array = matrix.toArray();
+ *
+ * // Using existing array
+ * const float32Array = new Float32Array(9);
+ * matrix.toArray(false, float32Array);
+ *
+ * // Get transposed array
+ * const transposed = matrix.toArray(true);
+ * ```
+ * @param transpose - Whether to transpose the matrix
+ * @param out - Optional Float32Array to store the result
+ * @returns The array containing the matrix values
+ * @see {@link Matrix.fromArray} For creating matrix from array
+ * @see {@link Matrix.array} For cached array storage
+ */
+ toArray(transpose, out) {
+ if (!this.array) {
+ this.array = new Float32Array(9);
+ }
+ const array = out || this.array;
+ if (transpose) {
+ array[0] = this.a;
+ array[1] = this.b;
+ array[2] = 0;
+ array[3] = this.c;
+ array[4] = this.d;
+ array[5] = 0;
+ array[6] = this.tx;
+ array[7] = this.ty;
+ array[8] = 1;
+ } else {
+ array[0] = this.a;
+ array[1] = this.c;
+ array[2] = this.tx;
+ array[3] = this.b;
+ array[4] = this.d;
+ array[5] = this.ty;
+ array[6] = 0;
+ array[7] = 0;
+ array[8] = 1;
+ }
+ return array;
+ }
+ /**
+ * Get a new position with the current transformation applied.
+ *
+ * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering)
+ * @example
+ * ```ts
+ * // Basic point transformation
+ * const matrix = new Matrix().translate(100, 50).rotate(Math.PI / 4);
+ * const point = new Point(10, 20);
+ * const transformed = matrix.apply(point);
+ *
+ * // Reuse existing point
+ * const output = new Point();
+ * matrix.apply(point, output);
+ * ```
+ * @param pos - The origin point to transform
+ * @param newPos - Optional point to store the result
+ * @returns The transformed point
+ * @see {@link Matrix.applyInverse} For inverse transformation
+ * @see {@link Point} For point operations
+ */
+ apply(pos, newPos) {
+ newPos = newPos || new Point();
+ const x = pos.x;
+ const y = pos.y;
+ newPos.x = this.a * x + this.c * y + this.tx;
+ newPos.y = this.b * x + this.d * y + this.ty;
+ return newPos;
+ }
+ /**
+ * Get a new position with the inverse of the current transformation applied.
+ *
+ * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input)
+ * @example
+ * ```ts
+ * // Basic inverse transformation
+ * const matrix = new Matrix().translate(100, 50).rotate(Math.PI / 4);
+ * const worldPoint = new Point(150, 100);
+ * const localPoint = matrix.applyInverse(worldPoint);
+ *
+ * // Reuse existing point
+ * const output = new Point();
+ * matrix.applyInverse(worldPoint, output);
+ *
+ * // Convert mouse position to local space
+ * const mousePoint = new Point(mouseX, mouseY);
+ * const localMouse = matrix.applyInverse(mousePoint);
+ * ```
+ * @param pos - The origin point to inverse-transform
+ * @param newPos - Optional point to store the result
+ * @returns The inverse-transformed point
+ * @see {@link Matrix.apply} For forward transformation
+ * @see {@link Matrix.invert} For getting inverse matrix
+ */
+ applyInverse(pos, newPos) {
+ newPos = newPos || new Point();
+ const a = this.a;
+ const b = this.b;
+ const c = this.c;
+ const d = this.d;
+ const tx = this.tx;
+ const ty = this.ty;
+ const id = 1 / (a * d + c * -b);
+ const x = pos.x;
+ const y = pos.y;
+ newPos.x = d * id * x + -c * id * y + (ty * c - tx * d) * id;
+ newPos.y = a * id * y + -b * id * x + (-ty * a + tx * b) * id;
+ return newPos;
+ }
+ /**
+ * Translates the matrix on the x and y axes.
+ * Adds to the position values while preserving scale, rotation and skew.
+ * @example
+ * ```ts
+ * // Basic translation
+ * const matrix = new Matrix();
+ * matrix.translate(100, 50); // Move right 100, down 50
+ *
+ * // Chain with other transformations
+ * matrix
+ * .scale(2, 2)
+ * .translate(100, 0)
+ * .rotate(Math.PI / 4);
+ * ```
+ * @param x - How much to translate on the x axis
+ * @param y - How much to translate on the y axis
+ * @returns This matrix. Good for chaining method calls.
+ * @see {@link Matrix.set} For setting position directly
+ * @see {@link Matrix.setTransform} For complete transform setup
+ */
+ translate(x, y) {
+ this.tx += x;
+ this.ty += y;
+ return this;
+ }
+ /**
+ * Applies a scale transformation to the matrix.
+ * Multiplies the scale values with existing matrix components.
+ * @example
+ * ```ts
+ * // Basic scaling
+ * const matrix = new Matrix();
+ * matrix.scale(2, 3); // Scale 2x horizontally, 3x vertically
+ *
+ * // Chain with other transformations
+ * matrix
+ * .translate(100, 100)
+ * .scale(2, 2) // Scales after translation
+ * .rotate(Math.PI / 4);
+ * ```
+ * @param x - The amount to scale horizontally
+ * @param y - The amount to scale vertically
+ * @returns This matrix. Good for chaining method calls.
+ * @see {@link Matrix.setTransform} For setting scale directly
+ * @see {@link Matrix.append} For combining transformations
+ */
+ scale(x, y) {
+ this.a *= x;
+ this.d *= y;
+ this.c *= x;
+ this.b *= y;
+ this.tx *= x;
+ this.ty *= y;
+ return this;
+ }
+ /**
+ * Applies a rotation transformation to the matrix.
+ *
+ * Rotates around the origin (0,0) by the given angle in radians.
+ * @example
+ * ```ts
+ * // Basic rotation
+ * const matrix = new Matrix();
+ * matrix.rotate(Math.PI / 4); // Rotate 45 degrees
+ *
+ * // Chain with other transformations
+ * matrix
+ * .translate(100, 100) // Move to rotation center
+ * .rotate(Math.PI) // Rotate 180 degrees
+ * .scale(2, 2); // Scale after rotation
+ *
+ * // Common angles
+ * matrix.rotate(Math.PI / 2); // 90 degrees
+ * matrix.rotate(Math.PI); // 180 degrees
+ * matrix.rotate(Math.PI * 2); // 360 degrees
+ * ```
+ * @remarks
+ * - Rotates around origin point (0,0)
+ * - Affects position if translation was set
+ * - Uses counter-clockwise rotation
+ * - Order of operations matters when chaining
+ * @param angle - The angle in radians
+ * @returns This matrix. Good for chaining method calls.
+ * @see {@link Matrix.setTransform} For setting rotation directly
+ * @see {@link Matrix.append} For combining transformations
+ */
+ rotate(angle) {
+ const cos = Math.cos(angle);
+ const sin = Math.sin(angle);
+ const a1 = this.a;
+ const c1 = this.c;
+ const tx1 = this.tx;
+ this.a = a1 * cos - this.b * sin;
+ this.b = a1 * sin + this.b * cos;
+ this.c = c1 * cos - this.d * sin;
+ this.d = c1 * sin + this.d * cos;
+ this.tx = tx1 * cos - this.ty * sin;
+ this.ty = tx1 * sin + this.ty * cos;
+ return this;
+ }
+ /**
+ * Appends the given Matrix to this Matrix.
+ * Combines two matrices by multiplying them together: this = this * matrix
+ * @example
+ * ```ts
+ * // Basic matrix combination
+ * const matrix = new Matrix();
+ * const other = new Matrix().translate(100, 0).rotate(Math.PI / 4);
+ * matrix.append(other);
+ * ```
+ * @remarks
+ * - Order matters: A.append(B) !== B.append(A)
+ * - Modifies current matrix
+ * - Preserves transformation order
+ * - Commonly used for combining transforms
+ * @param matrix - The matrix to append
+ * @returns This matrix. Good for chaining method calls.
+ * @see {@link Matrix.prepend} For prepending transformations
+ * @see {@link Matrix.appendFrom} For appending two external matrices
+ */
+ append(matrix) {
+ const a1 = this.a;
+ const b1 = this.b;
+ const c1 = this.c;
+ const d1 = this.d;
+ this.a = matrix.a * a1 + matrix.b * c1;
+ this.b = matrix.a * b1 + matrix.b * d1;
+ this.c = matrix.c * a1 + matrix.d * c1;
+ this.d = matrix.c * b1 + matrix.d * d1;
+ this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx;
+ this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty;
+ return this;
+ }
+ /**
+ * Appends two matrices and sets the result to this matrix.
+ * Performs matrix multiplication: this = A * B
+ * @example
+ * ```ts
+ * // Basic matrix multiplication
+ * const result = new Matrix();
+ * const matrixA = new Matrix().scale(2, 2);
+ * const matrixB = new Matrix().rotate(Math.PI / 4);
+ * result.appendFrom(matrixA, matrixB);
+ * ```
+ * @remarks
+ * - Order matters: A * B !== B * A
+ * - Creates a new transformation from two others
+ * - More efficient than append() for multiple operations
+ * - Does not modify input matrices
+ * @param a - The first matrix to multiply
+ * @param b - The second matrix to multiply
+ * @returns This matrix. Good for chaining method calls.
+ * @see {@link Matrix.append} For single matrix combination
+ * @see {@link Matrix.prepend} For reverse order multiplication
+ */
+ appendFrom(a, b) {
+ const a1 = a.a;
+ const b1 = a.b;
+ const c1 = a.c;
+ const d1 = a.d;
+ const tx = a.tx;
+ const ty = a.ty;
+ const a2 = b.a;
+ const b2 = b.b;
+ const c2 = b.c;
+ const d2 = b.d;
+ this.a = a1 * a2 + b1 * c2;
+ this.b = a1 * b2 + b1 * d2;
+ this.c = c1 * a2 + d1 * c2;
+ this.d = c1 * b2 + d1 * d2;
+ this.tx = tx * a2 + ty * c2 + b.tx;
+ this.ty = tx * b2 + ty * d2 + b.ty;
+ return this;
+ }
+ /**
+ * Sets the matrix based on all the available properties.
+ * Combines position, scale, rotation, skew and pivot in a single operation.
+ * @example
+ * ```ts
+ * // Basic transform setup
+ * const matrix = new Matrix();
+ * matrix.setTransform(
+ * 100, 100, // position
+ * 0, 0, // pivot
+ * 2, 2, // scale
+ * Math.PI / 4, // rotation (45 degrees)
+ * 0, 0 // skew
+ * );
+ * ```
+ * @remarks
+ * - Updates all matrix components at once
+ * - More efficient than separate transform calls
+ * - Uses radians for rotation and skew
+ * - Pivot affects rotation center
+ * @param x - Position on the x axis
+ * @param y - Position on the y axis
+ * @param pivotX - Pivot on the x axis
+ * @param pivotY - Pivot on the y axis
+ * @param scaleX - Scale on the x axis
+ * @param scaleY - Scale on the y axis
+ * @param rotation - Rotation in radians
+ * @param skewX - Skew on the x axis
+ * @param skewY - Skew on the y axis
+ * @returns This matrix. Good for chaining method calls.
+ * @see {@link Matrix.decompose} For extracting transform properties
+ * @see {@link TransformableObject} For transform data structure
+ */
+ setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) {
+ this.a = Math.cos(rotation + skewY) * scaleX;
+ this.b = Math.sin(rotation + skewY) * scaleX;
+ this.c = -Math.sin(rotation - skewX) * scaleY;
+ this.d = Math.cos(rotation - skewX) * scaleY;
+ this.tx = x - (pivotX * this.a + pivotY * this.c);
+ this.ty = y - (pivotX * this.b + pivotY * this.d);
+ return this;
+ }
+ /**
+ * Prepends the given Matrix to this Matrix.
+ * Combines two matrices by multiplying them together: this = matrix * this
+ * @example
+ * ```ts
+ * // Basic matrix prepend
+ * const matrix = new Matrix().scale(2, 2);
+ * const other = new Matrix().translate(100, 0);
+ * matrix.prepend(other); // Translation happens before scaling
+ * ```
+ * @remarks
+ * - Order matters: A.prepend(B) !== B.prepend(A)
+ * - Modifies current matrix
+ * - Reverses transformation order compared to append()
+ * @param matrix - The matrix to prepend
+ * @returns This matrix. Good for chaining method calls.
+ * @see {@link Matrix.append} For appending transformations
+ * @see {@link Matrix.appendFrom} For combining external matrices
+ */
+ prepend(matrix) {
+ const tx1 = this.tx;
+ if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) {
+ const a1 = this.a;
+ const c1 = this.c;
+ this.a = a1 * matrix.a + this.b * matrix.c;
+ this.b = a1 * matrix.b + this.b * matrix.d;
+ this.c = c1 * matrix.a + this.d * matrix.c;
+ this.d = c1 * matrix.b + this.d * matrix.d;
+ }
+ this.tx = tx1 * matrix.a + this.ty * matrix.c + matrix.tx;
+ this.ty = tx1 * matrix.b + this.ty * matrix.d + matrix.ty;
+ return this;
+ }
+ /**
+ * Decomposes the matrix into its individual transform components.
+ * Extracts position, scale, rotation and skew values from the matrix.
+ * @example
+ * ```ts
+ * // Basic decomposition
+ * const matrix = new Matrix()
+ * .translate(100, 100)
+ * .rotate(Math.PI / 4)
+ * .scale(2, 2);
+ *
+ * const transform = {
+ * position: new Point(),
+ * scale: new Point(),
+ * pivot: new Point(),
+ * skew: new Point(),
+ * rotation: 0
+ * };
+ *
+ * matrix.decompose(transform);
+ * console.log(transform.position); // Point(100, 100)
+ * console.log(transform.rotation); // ~0.785 (PI/4)
+ * console.log(transform.scale); // Point(2, 2)
+ * ```
+ * @remarks
+ * - Handles combined transformations
+ * - Accounts for pivot points
+ * - Chooses between rotation/skew based on transform type
+ * - Uses radians for rotation and skew
+ * @param transform - The transform object to store the decomposed values
+ * @returns The transform with the newly applied properties
+ * @see {@link Matrix.setTransform} For composing from components
+ * @see {@link TransformableObject} For transform structure
+ */
+ decompose(transform) {
+ const a = this.a;
+ const b = this.b;
+ const c = this.c;
+ const d = this.d;
+ const pivot = transform.pivot;
+ const skewX = -Math.atan2(-c, d);
+ const skewY = Math.atan2(b, a);
+ const delta = Math.abs(skewX + skewY);
+ if (delta < 1e-5 || Math.abs(PI_2 - delta) < 1e-5) {
+ transform.rotation = skewY;
+ transform.skew.x = transform.skew.y = 0;
+ } else {
+ transform.rotation = 0;
+ transform.skew.x = skewX;
+ transform.skew.y = skewY;
+ }
+ transform.scale.x = Math.sqrt(a * a + b * b);
+ transform.scale.y = Math.sqrt(c * c + d * d);
+ transform.position.x = this.tx + (pivot.x * a + pivot.y * c);
+ transform.position.y = this.ty + (pivot.x * b + pivot.y * d);
+ return transform;
+ }
+ /**
+ * Inverts this matrix.
+ * Creates the matrix that when multiplied with this matrix results in an identity matrix.
+ * @example
+ * ```ts
+ * // Basic matrix inversion
+ * const matrix = new Matrix()
+ * .translate(100, 50)
+ * .scale(2, 2);
+ *
+ * matrix.invert(); // Now transforms in opposite direction
+ *
+ * // Verify inversion
+ * const point = new Point(50, 50);
+ * const transformed = matrix.apply(point);
+ * const original = matrix.invert().apply(transformed);
+ * // original ≈ point
+ * ```
+ * @remarks
+ * - Modifies the current matrix
+ * - Useful for reversing transformations
+ * - Cannot invert matrices with zero determinant
+ * @returns This matrix. Good for chaining method calls.
+ * @see {@link Matrix.identity} For resetting to identity
+ * @see {@link Matrix.applyInverse} For inverse transformations
+ */
+ invert() {
+ const a1 = this.a;
+ const b1 = this.b;
+ const c1 = this.c;
+ const d1 = this.d;
+ const tx1 = this.tx;
+ const n = a1 * d1 - b1 * c1;
+ this.a = d1 / n;
+ this.b = -b1 / n;
+ this.c = -c1 / n;
+ this.d = a1 / n;
+ this.tx = (c1 * this.ty - d1 * tx1) / n;
+ this.ty = -(a1 * this.ty - b1 * tx1) / n;
+ return this;
+ }
+ /**
+ * Checks if this matrix is an identity matrix.
+ *
+ * An identity matrix has no transformations applied (default state).
+ * @example
+ * ```ts
+ * // Check if matrix is identity
+ * const matrix = new Matrix();
+ * console.log(matrix.isIdentity()); // true
+ *
+ * // Check after transformations
+ * matrix.translate(100, 0);
+ * console.log(matrix.isIdentity()); // false
+ *
+ * // Reset and verify
+ * matrix.identity();
+ * console.log(matrix.isIdentity()); // true
+ * ```
+ * @remarks
+ * - Verifies a = 1, d = 1 (no scale)
+ * - Verifies b = 0, c = 0 (no skew)
+ * - Verifies tx = 0, ty = 0 (no translation)
+ * @returns True if matrix has no transformations
+ * @see {@link Matrix.identity} For resetting to identity
+ * @see {@link Matrix.IDENTITY} For constant identity matrix
+ */
+ isIdentity() {
+ return this.a === 1 && this.b === 0 && this.c === 0 && this.d === 1 && this.tx === 0 && this.ty === 0;
+ }
+ /**
+ * Resets this Matrix to an identity (default) matrix.
+ * Sets all components to their default values: scale=1, no skew, no translation.
+ * @example
+ * ```ts
+ * // Reset transformed matrix
+ * const matrix = new Matrix()
+ * .scale(2, 2)
+ * .rotate(Math.PI / 4);
+ * matrix.identity(); // Back to default state
+ *
+ * // Chain after reset
+ * matrix
+ * .identity()
+ * .translate(100, 100)
+ * .scale(2, 2);
+ *
+ * // Compare with identity constant
+ * const isDefault = matrix.equals(Matrix.IDENTITY);
+ * ```
+ * @remarks
+ * - Sets a=1, d=1 (default scale)
+ * - Sets b=0, c=0 (no skew)
+ * - Sets tx=0, ty=0 (no translation)
+ * @returns This matrix. Good for chaining method calls.
+ * @see {@link Matrix.IDENTITY} For constant identity matrix
+ * @see {@link Matrix.isIdentity} For checking identity state
+ */
+ identity() {
+ this.a = 1;
+ this.b = 0;
+ this.c = 0;
+ this.d = 1;
+ this.tx = 0;
+ this.ty = 0;
+ return this;
+ }
+ /**
+ * Creates a new Matrix object with the same values as this one.
+ * @returns A copy of this matrix. Good for chaining method calls.
+ */
+ clone() {
+ const matrix = new Matrix();
+ matrix.a = this.a;
+ matrix.b = this.b;
+ matrix.c = this.c;
+ matrix.d = this.d;
+ matrix.tx = this.tx;
+ matrix.ty = this.ty;
+ return matrix;
+ }
+ /**
+ * Creates a new Matrix object with the same values as this one.
+ * @param matrix
+ * @example
+ * ```ts
+ * // Basic matrix cloning
+ * const matrix = new Matrix()
+ * .translate(100, 100)
+ * .rotate(Math.PI / 4);
+ * const copy = matrix.clone();
+ *
+ * // Clone and modify
+ * const modified = matrix.clone()
+ * .scale(2, 2);
+ *
+ * // Compare matrices
+ * console.log(matrix.equals(copy)); // true
+ * console.log(matrix.equals(modified)); // false
+ * ```
+ * @returns A copy of this matrix. Good for chaining method calls.
+ * @see {@link Matrix.copyTo} For copying to existing matrix
+ * @see {@link Matrix.copyFrom} For copying from another matrix
+ */
+ copyTo(matrix) {
+ matrix.a = this.a;
+ matrix.b = this.b;
+ matrix.c = this.c;
+ matrix.d = this.d;
+ matrix.tx = this.tx;
+ matrix.ty = this.ty;
+ return matrix;
+ }
+ /**
+ * Changes the values of the matrix to be the same as the ones in given matrix.
+ * @example
+ * ```ts
+ * // Basic matrix copying
+ * const source = new Matrix()
+ * .translate(100, 100)
+ * .rotate(Math.PI / 4);
+ * const target = new Matrix();
+ * target.copyFrom(source);
+ * ```
+ * @param matrix - The matrix to copy from
+ * @returns This matrix. Good for chaining method calls.
+ * @see {@link Matrix.clone} For creating new matrix copy
+ * @see {@link Matrix.copyTo} For copying to another matrix
+ */
+ copyFrom(matrix) {
+ this.a = matrix.a;
+ this.b = matrix.b;
+ this.c = matrix.c;
+ this.d = matrix.d;
+ this.tx = matrix.tx;
+ this.ty = matrix.ty;
+ return this;
+ }
+ /**
+ * Checks if this matrix equals another matrix.
+ * Compares all components for exact equality.
+ * @example
+ * ```ts
+ * // Basic equality check
+ * const m1 = new Matrix();
+ * const m2 = new Matrix();
+ * console.log(m1.equals(m2)); // true
+ *
+ * // Compare transformed matrices
+ * const transform = new Matrix()
+ * .translate(100, 100)
+ * const clone = new Matrix()
+ * .scale(2, 2);
+ * console.log(transform.equals(clone)); // false
+ * ```
+ * @param matrix - The matrix to compare to
+ * @returns True if matrices are identical
+ * @see {@link Matrix.copyFrom} For copying matrix values
+ * @see {@link Matrix.isIdentity} For identity comparison
+ */
+ equals(matrix) {
+ return matrix.a === this.a && matrix.b === this.b && matrix.c === this.c && matrix.d === this.d && matrix.tx === this.tx && matrix.ty === this.ty;
+ }
+ toString() {
+ return `[pixi.js:Matrix a=${this.a} b=${this.b} c=${this.c} d=${this.d} tx=${this.tx} ty=${this.ty}]`;
+ }
+ /**
+ * A default (identity) matrix with no transformations applied.
+ *
+ * > [!IMPORTANT] This is a shared read-only object. Create a new Matrix if you need to modify it.
+ * @example
+ * ```ts
+ * // Get identity matrix reference
+ * const identity = Matrix.IDENTITY;
+ * console.log(identity.isIdentity()); // true
+ *
+ * // Compare with identity
+ * const matrix = new Matrix();
+ * console.log(matrix.equals(Matrix.IDENTITY)); // true
+ *
+ * // Create new matrix instead of modifying IDENTITY
+ * const transform = new Matrix()
+ * .copyFrom(Matrix.IDENTITY)
+ * .translate(100, 100);
+ * ```
+ * @readonly
+ * @returns A read-only identity matrix
+ * @see {@link Matrix.shared} For temporary calculations
+ * @see {@link Matrix.identity} For resetting matrices
+ */
+ static get IDENTITY() {
+ return identityMatrix$1.identity();
+ }
+ /**
+ * A static Matrix that can be used to avoid creating new objects.
+ * Will always ensure the matrix is reset to identity when requested.
+ *
+ * > [!IMPORTANT] This matrix is shared and temporary. Do not store references to it.
+ * @example
+ * ```ts
+ * // Use for temporary calculations
+ * const tempMatrix = Matrix.shared;
+ * tempMatrix.translate(100, 100).rotate(Math.PI / 4);
+ * const point = tempMatrix.apply({ x: 10, y: 20 });
+ *
+ * // Will be reset to identity on next access
+ * const fresh = Matrix.shared; // Back to identity
+ * ```
+ * @remarks
+ * - Always returns identity matrix
+ * - Safe to modify temporarily
+ * - Not safe to store references
+ * - Useful for one-off calculations
+ * @readonly
+ * @returns A fresh identity matrix for temporary use
+ * @see {@link Matrix.IDENTITY} For immutable identity matrix
+ * @see {@link Matrix.identity} For resetting matrices
+ */
+ static get shared() {
+ return tempMatrix$6.identity();
+ }
+ }
+ const tempMatrix$6 = new Matrix();
+ const identityMatrix$1 = new Matrix();
+
+ "use strict";
+ class ObservablePoint {
+ /**
+ * Creates a new `ObservablePoint`
+ * @param observer - Observer to pass to listen for change events.
+ * @param {number} [x=0] - position of the point on the x axis
+ * @param {number} [y=0] - position of the point on the y axis
+ */
+ constructor(observer, x, y) {
+ this._x = x || 0;
+ this._y = y || 0;
+ this._observer = observer;
+ }
+ /**
+ * Creates a clone of this point.
+ * @example
+ * ```ts
+ * // Basic cloning
+ * const point = new ObservablePoint(observer, 100, 200);
+ * const copy = point.clone();
+ *
+ * // Clone with new observer
+ * const newObserver = {
+ * _onUpdate: (p) => console.log(`Clone updated: (${p.x}, ${p.y})`)
+ * };
+ * const watched = point.clone(newObserver);
+ *
+ * // Verify independence
+ * watched.set(300, 400); // Only triggers new observer
+ * ```
+ * @param observer - Optional observer to pass to the new observable point
+ * @returns A copy of this observable point
+ * @see {@link ObservablePoint.copyFrom} For copying into existing point
+ * @see {@link Observer} For observer interface details
+ */
+ clone(observer) {
+ return new ObservablePoint(observer != null ? observer : this._observer, this._x, this._y);
+ }
+ /**
+ * Sets the point to a new x and y position.
+ *
+ * If y is omitted, both x and y will be set to x.
+ * @example
+ * ```ts
+ * // Basic position setting
+ * const point = new ObservablePoint(observer);
+ * point.set(100, 200);
+ *
+ * // Set both x and y to same value
+ * point.set(50); // x=50, y=50
+ * ```
+ * @param x - Position on the x axis
+ * @param y - Position on the y axis, defaults to x
+ * @returns The point instance itself
+ * @see {@link ObservablePoint.copyFrom} For copying from another point
+ * @see {@link ObservablePoint.equals} For comparing positions
+ */
+ set(x = 0, y = x) {
+ if (this._x !== x || this._y !== y) {
+ this._x = x;
+ this._y = y;
+ this._observer._onUpdate(this);
+ }
+ return this;
+ }
+ /**
+ * Copies x and y from the given point into this point.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new ObservablePoint(observer, 100, 200);
+ * const target = new ObservablePoint();
+ * target.copyFrom(source);
+ *
+ * // Copy and chain operations
+ * const point = new ObservablePoint()
+ * .copyFrom(source)
+ * .set(x + 50, y + 50);
+ *
+ * // Copy from any PointData
+ * const data = { x: 10, y: 20 };
+ * point.copyFrom(data);
+ * ```
+ * @param p - The point to copy from
+ * @returns The point instance itself
+ * @see {@link ObservablePoint.copyTo} For copying to another point
+ * @see {@link ObservablePoint.clone} For creating new point copy
+ */
+ copyFrom(p) {
+ if (this._x !== p.x || this._y !== p.y) {
+ this._x = p.x;
+ this._y = p.y;
+ this._observer._onUpdate(this);
+ }
+ return this;
+ }
+ /**
+ * Copies this point's x and y into the given point.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new ObservablePoint(100, 200);
+ * const target = new ObservablePoint();
+ * source.copyTo(target);
+ * ```
+ * @param p - The point to copy to. Can be any type that is or extends `PointLike`
+ * @returns The point (`p`) with values updated
+ * @see {@link ObservablePoint.copyFrom} For copying from another point
+ * @see {@link ObservablePoint.clone} For creating new point copy
+ */
+ copyTo(p) {
+ p.set(this._x, this._y);
+ return p;
+ }
+ /**
+ * Checks if another point is equal to this point.
+ *
+ * Compares x and y values using strict equality.
+ * @example
+ * ```ts
+ * // Basic equality check
+ * const p1 = new ObservablePoint(100, 200);
+ * const p2 = new ObservablePoint(100, 200);
+ * console.log(p1.equals(p2)); // true
+ *
+ * // Compare with PointData
+ * const data = { x: 100, y: 200 };
+ * console.log(p1.equals(data)); // true
+ *
+ * // Check different points
+ * const p3 = new ObservablePoint(200, 300);
+ * console.log(p1.equals(p3)); // false
+ * ```
+ * @param p - The point to check
+ * @returns `true` if both `x` and `y` are equal
+ * @see {@link ObservablePoint.copyFrom} For making points equal
+ * @see {@link PointData} For point data interface
+ */
+ equals(p) {
+ return p.x === this._x && p.y === this._y;
+ }
+ toString() {
+ return `[pixi.js/math:ObservablePoint x=${this._x} y=${this._y} scope=${this._observer}]`;
+ }
+ /**
+ * Position of the observable point on the x axis.
+ * Triggers observer callback when value changes.
+ * @example
+ * ```ts
+ * // Basic x position
+ * const point = new ObservablePoint(observer);
+ * point.x = 100; // Triggers observer
+ *
+ * // Use in calculations
+ * const width = rightPoint.x - leftPoint.x;
+ * ```
+ * @default 0
+ */
+ get x() {
+ return this._x;
+ }
+ set x(value) {
+ if (this._x !== value) {
+ this._x = value;
+ this._observer._onUpdate(this);
+ }
+ }
+ /**
+ * Position of the observable point on the y axis.
+ * Triggers observer callback when value changes.
+ * @example
+ * ```ts
+ * // Basic y position
+ * const point = new ObservablePoint(observer);
+ * point.y = 200; // Triggers observer
+ *
+ * // Use in calculations
+ * const height = bottomPoint.y - topPoint.y;
+ * ```
+ * @default 0
+ */
+ get y() {
+ return this._y;
+ }
+ set y(value) {
+ if (this._y !== value) {
+ this._y = value;
+ this._observer._onUpdate(this);
+ }
+ }
+ }
+
+ "use strict";
+ const uidCache = {
+ default: -1
+ };
+ function uid$1(name = "default") {
+ if (uidCache[name] === void 0) {
+ uidCache[name] = -1;
+ }
+ return ++uidCache[name];
+ }
+ function resetUids() {
+ for (const key in uidCache) {
+ delete uidCache[key];
+ }
+ }
+
+ "use strict";
+ const warnings = /* @__PURE__ */ new Set();
+ const v8_0_0 = "8.0.0";
+ const v8_3_4 = "8.3.4";
+ const deprecationState = {
+ quiet: false,
+ noColor: false
+ };
+ const deprecation = (version, message, ignoreDepth = 3) => {
+ if (deprecationState.quiet || warnings.has(message))
+ return;
+ let stack = new Error().stack;
+ const deprecationMessage = `${message}
+Deprecated since v${version}`;
+ const useGroup = typeof console.groupCollapsed === "function" && !deprecationState.noColor;
+ if (typeof stack === "undefined") {
+ console.warn("PixiJS Deprecation Warning: ", deprecationMessage);
+ } else {
+ stack = stack.split("\n").splice(ignoreDepth).join("\n");
+ if (useGroup) {
+ console.groupCollapsed(
+ "%cPixiJS Deprecation Warning: %c%s",
+ "color:#614108;background:#fffbe6",
+ "font-weight:normal;color:#614108;background:#fffbe6",
+ deprecationMessage
+ );
+ console.warn(stack);
+ console.groupEnd();
+ } else {
+ console.warn("PixiJS Deprecation Warning: ", deprecationMessage);
+ console.warn(stack);
+ }
+ }
+ warnings.add(message);
+ };
+ Object.defineProperties(deprecation, {
+ quiet: {
+ get: () => deprecationState.quiet,
+ set: (value) => {
+ deprecationState.quiet = value;
+ },
+ enumerable: true,
+ configurable: false
+ },
+ noColor: {
+ get: () => deprecationState.noColor,
+ set: (value) => {
+ deprecationState.noColor = value;
+ },
+ enumerable: true,
+ configurable: false
+ }
+ });
+
+ "use strict";
+ let warnCount = 0;
+ const maxWarnings = 500;
+ function warn(...args) {
+ if (warnCount === maxWarnings)
+ return;
+ warnCount++;
+ if (warnCount === maxWarnings) {
+ console.warn("PixiJS Warning: too many warnings, no more warnings will be reported to the console by PixiJS.");
+ } else {
+ console.warn("PixiJS Warning: ", ...args);
+ }
+ }
+
+ "use strict";
+ const GlobalResourceRegistry = {
+ /**
+ * Set of registered pools and cleanable objects.
+ * @private
+ */
+ _registeredResources: /* @__PURE__ */ new Set(),
+ /**
+ * Registers a pool or cleanable object for cleanup.
+ * @param {Cleanable} pool - The pool or object to register.
+ */
+ register(pool) {
+ this._registeredResources.add(pool);
+ },
+ /**
+ * Unregisters a pool or cleanable object from cleanup.
+ * @param {Cleanable} pool - The pool or object to unregister.
+ */
+ unregister(pool) {
+ this._registeredResources.delete(pool);
+ },
+ /** Clears all registered pools and cleanable objects. This will call clear() on each registered item. */
+ release() {
+ this._registeredResources.forEach((pool) => pool.clear());
+ },
+ /**
+ * Gets the number of registered pools and cleanable objects.
+ * @returns {number} The count of registered items.
+ */
+ get registeredCount() {
+ return this._registeredResources.size;
+ },
+ /**
+ * Checks if a specific pool or cleanable object is registered.
+ * @param {Cleanable} pool - The pool or object to check.
+ * @returns {boolean} True if the item is registered, false otherwise.
+ */
+ isRegistered(pool) {
+ return this._registeredResources.has(pool);
+ },
+ /**
+ * Removes all registrations without clearing the pools.
+ * Useful if you want to reset the collector without affecting the pools.
+ */
+ reset() {
+ this._registeredResources.clear();
+ }
+ };
+
+ "use strict";
+ class Pool {
+ /**
+ * Constructs a new Pool.
+ * @param ClassType - The constructor of the items in the pool.
+ * @param {number} [initialSize] - The initial size of the pool.
+ */
+ constructor(ClassType, initialSize) {
+ this._pool = [];
+ this._count = 0;
+ this._index = 0;
+ this._classType = ClassType;
+ if (initialSize) {
+ this.prepopulate(initialSize);
+ }
+ }
+ /**
+ * Prepopulates the pool with a given number of items.
+ * @param total - The number of items to add to the pool.
+ */
+ prepopulate(total) {
+ for (let i = 0; i < total; i++) {
+ this._pool[this._index++] = new this._classType();
+ }
+ this._count += total;
+ }
+ /**
+ * Gets an item from the pool. Calls the item's `init` method if it exists.
+ * If there are no items left in the pool, a new one will be created.
+ * @param {unknown} [data] - Optional data to pass to the item's constructor.
+ * @returns {T} The item from the pool.
+ */
+ get(data) {
+ var _a;
+ let item;
+ if (this._index > 0) {
+ item = this._pool[--this._index];
+ } else {
+ item = new this._classType();
+ }
+ (_a = item.init) == null ? void 0 : _a.call(item, data);
+ return item;
+ }
+ /**
+ * Returns an item to the pool. Calls the item's `reset` method if it exists.
+ * @param {T} item - The item to return to the pool.
+ */
+ return(item) {
+ var _a;
+ (_a = item.reset) == null ? void 0 : _a.call(item);
+ this._pool[this._index++] = item;
+ }
+ /**
+ * Gets the number of items in the pool.
+ * @readonly
+ */
+ get totalSize() {
+ return this._count;
+ }
+ /**
+ * Gets the number of items in the pool that are free to use without needing to create more.
+ * @readonly
+ */
+ get totalFree() {
+ return this._index;
+ }
+ /**
+ * Gets the number of items in the pool that are currently in use.
+ * @readonly
+ */
+ get totalUsed() {
+ return this._count - this._index;
+ }
+ /** clears the pool */
+ clear() {
+ if (this._pool.length > 0 && this._pool[0].destroy) {
+ for (let i = 0; i < this._index; i++) {
+ this._pool[i].destroy();
+ }
+ }
+ this._pool.length = 0;
+ this._count = 0;
+ this._index = 0;
+ }
+ }
+
+ "use strict";
+ class PoolGroupClass {
+ constructor() {
+ /**
+ * A map to store the pools by their class type.
+ * @private
+ */
+ this._poolsByClass = /* @__PURE__ */ new Map();
+ }
+ /**
+ * Prepopulates a specific pool with a given number of items.
+ * @template T The type of items in the pool. Must extend PoolItem.
+ * @param {PoolItemConstructor} Class - The constructor of the items in the pool.
+ * @param {number} total - The number of items to add to the pool.
+ */
+ prepopulate(Class, total) {
+ const classPool = this.getPool(Class);
+ classPool.prepopulate(total);
+ }
+ /**
+ * Gets an item from a specific pool.
+ * @template T The type of items in the pool. Must extend PoolItem.
+ * @param {PoolItemConstructor} Class - The constructor of the items in the pool.
+ * @param {unknown} [data] - Optional data to pass to the item's constructor.
+ * @returns {T} The item from the pool.
+ */
+ get(Class, data) {
+ const pool = this.getPool(Class);
+ return pool.get(data);
+ }
+ /**
+ * Returns an item to its respective pool.
+ * @param {PoolItem} item - The item to return to the pool.
+ */
+ return(item) {
+ const pool = this.getPool(item.constructor);
+ pool.return(item);
+ }
+ /**
+ * Gets a specific pool based on the class type.
+ * @template T The type of items in the pool. Must extend PoolItem.
+ * @param {PoolItemConstructor} ClassType - The constructor of the items in the pool.
+ * @returns {Pool} The pool of the given class type.
+ */
+ getPool(ClassType) {
+ if (!this._poolsByClass.has(ClassType)) {
+ this._poolsByClass.set(ClassType, new Pool(ClassType));
+ }
+ return this._poolsByClass.get(ClassType);
+ }
+ /** gets the usage stats of each pool in the system */
+ stats() {
+ const stats = {};
+ this._poolsByClass.forEach((pool) => {
+ const name = stats[pool._classType.name] ? pool._classType.name + pool._classType.ID : pool._classType.name;
+ stats[name] = {
+ free: pool.totalFree,
+ used: pool.totalUsed,
+ size: pool.totalSize
+ };
+ });
+ return stats;
+ }
+ /** Clears all pools in the group. This will reset all pools and free their resources. */
+ clear() {
+ this._poolsByClass.forEach((pool) => pool.clear());
+ this._poolsByClass.clear();
+ }
+ }
+ const BigPool = new PoolGroupClass();
+ GlobalResourceRegistry.register(BigPool);
+
+ "use strict";
+ const cacheAsTextureMixin = {
+ get isCachedAsTexture() {
+ var _a;
+ return !!((_a = this.renderGroup) == null ? void 0 : _a.isCachedAsTexture);
+ },
+ cacheAsTexture(val) {
+ if (typeof val === "boolean" && val === false) {
+ this.disableRenderGroup();
+ } else {
+ this.enableRenderGroup();
+ this.renderGroup.enableCacheAsTexture(val === true ? {} : val);
+ }
+ },
+ updateCacheTexture() {
+ var _a;
+ (_a = this.renderGroup) == null ? void 0 : _a.updateCacheTexture();
+ },
+ get cacheAsBitmap() {
+ return this.isCachedAsTexture;
+ },
+ set cacheAsBitmap(val) {
+ deprecation("v8.6.0", "cacheAsBitmap is deprecated, use cacheAsTexture instead.");
+ this.cacheAsTexture(val);
+ }
+ };
+
+ "use strict";
+ function removeItems(arr, startIdx, removeCount) {
+ const length = arr.length;
+ let i;
+ if (startIdx >= length || removeCount === 0) {
+ return;
+ }
+ removeCount = startIdx + removeCount > length ? length - startIdx : removeCount;
+ const len = length - removeCount;
+ for (i = startIdx; i < len; ++i) {
+ arr[i] = arr[i + removeCount];
+ }
+ arr.length = len;
+ }
+
+ "use strict";
+ const childrenHelperMixin = {
+ allowChildren: true,
+ removeChildren(beginIndex = 0, endIndex) {
+ var _a;
+ const end = endIndex != null ? endIndex : this.children.length;
+ const range = end - beginIndex;
+ const removed = [];
+ if (range > 0 && range <= end) {
+ for (let i = end - 1; i >= beginIndex; i--) {
+ const child = this.children[i];
+ if (!child)
+ continue;
+ removed.push(child);
+ child.parent = null;
+ }
+ removeItems(this.children, beginIndex, end);
+ const renderGroup = this.renderGroup || this.parentRenderGroup;
+ if (renderGroup) {
+ renderGroup.removeChildren(removed);
+ }
+ for (let i = 0; i < removed.length; ++i) {
+ const child = removed[i];
+ (_a = child.parentRenderLayer) == null ? void 0 : _a.detach(child);
+ this.emit("childRemoved", child, this, i);
+ removed[i].emit("removed", this);
+ }
+ if (removed.length > 0) {
+ this._didViewChangeTick++;
+ }
+ return removed;
+ } else if (range === 0 && this.children.length === 0) {
+ return removed;
+ }
+ throw new RangeError("removeChildren: numeric values are outside the acceptable range.");
+ },
+ removeChildAt(index) {
+ const child = this.getChildAt(index);
+ return this.removeChild(child);
+ },
+ getChildAt(index) {
+ if (index < 0 || index >= this.children.length) {
+ throw new Error(`getChildAt: Index (${index}) does not exist.`);
+ }
+ return this.children[index];
+ },
+ setChildIndex(child, index) {
+ if (index < 0 || index >= this.children.length) {
+ throw new Error(`The index ${index} supplied is out of bounds ${this.children.length}`);
+ }
+ this.getChildIndex(child);
+ this.addChildAt(child, index);
+ },
+ getChildIndex(child) {
+ const index = this.children.indexOf(child);
+ if (index === -1) {
+ throw new Error("The supplied Container must be a child of the caller");
+ }
+ return index;
+ },
+ addChildAt(child, index) {
+ if (!this.allowChildren) {
+ deprecation(v8_0_0, "addChildAt: Only Containers will be allowed to add children in v8.0.0");
+ }
+ const { children } = this;
+ if (index < 0 || index > children.length) {
+ throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${children.length}`);
+ }
+ if (child.parent) {
+ const currentIndex = child.parent.children.indexOf(child);
+ if (child.parent === this && currentIndex === index) {
+ return child;
+ }
+ if (currentIndex !== -1) {
+ child.parent.children.splice(currentIndex, 1);
+ }
+ }
+ if (index === children.length) {
+ children.push(child);
+ } else {
+ children.splice(index, 0, child);
+ }
+ child.parent = this;
+ child.didChange = true;
+ child._updateFlags = 15;
+ const renderGroup = this.renderGroup || this.parentRenderGroup;
+ if (renderGroup) {
+ renderGroup.addChild(child);
+ }
+ if (this.sortableChildren)
+ this.sortDirty = true;
+ this.emit("childAdded", child, this, index);
+ child.emit("added", this);
+ return child;
+ },
+ swapChildren(child, child2) {
+ if (child === child2) {
+ return;
+ }
+ const index1 = this.getChildIndex(child);
+ const index2 = this.getChildIndex(child2);
+ this.children[index1] = child2;
+ this.children[index2] = child;
+ const renderGroup = this.renderGroup || this.parentRenderGroup;
+ if (renderGroup) {
+ renderGroup.structureDidChange = true;
+ }
+ this._didContainerChangeTick++;
+ },
+ removeFromParent() {
+ var _a;
+ (_a = this.parent) == null ? void 0 : _a.removeChild(this);
+ },
+ reparentChild(...child) {
+ if (child.length === 1) {
+ return this.reparentChildAt(child[0], this.children.length);
+ }
+ child.forEach((c) => this.reparentChildAt(c, this.children.length));
+ return child[0];
+ },
+ reparentChildAt(child, index) {
+ if (child.parent === this) {
+ this.setChildIndex(child, index);
+ return child;
+ }
+ const childMat = child.worldTransform.clone();
+ child.removeFromParent();
+ this.addChildAt(child, index);
+ const newMatrix = this.worldTransform.clone();
+ newMatrix.invert();
+ childMat.prepend(newMatrix);
+ child.setFromMatrix(childMat);
+ return child;
+ },
+ replaceChild(oldChild, newChild) {
+ oldChild.updateLocalTransform();
+ this.addChildAt(newChild, this.getChildIndex(oldChild));
+ newChild.setFromMatrix(oldChild.localTransform);
+ newChild.updateLocalTransform();
+ this.removeChild(oldChild);
+ }
+ };
+
+ "use strict";
+ const collectRenderablesMixin = {
+ collectRenderables(instructionSet, renderer, currentLayer) {
+ if (this.parentRenderLayer && this.parentRenderLayer !== currentLayer || this.globalDisplayStatus < 7 || !this.includeInBuild)
+ return;
+ if (this.sortableChildren) {
+ this.sortChildren();
+ }
+ if (this.isSimple) {
+ this.collectRenderablesSimple(instructionSet, renderer, currentLayer);
+ } else if (this.renderGroup) {
+ renderer.renderPipes.renderGroup.addRenderGroup(this.renderGroup, instructionSet);
+ } else {
+ this.collectRenderablesWithEffects(instructionSet, renderer, currentLayer);
+ }
+ },
+ collectRenderablesSimple(instructionSet, renderer, currentLayer) {
+ const children = this.children;
+ const length = children.length;
+ for (let i = 0; i < length; i++) {
+ children[i].collectRenderables(instructionSet, renderer, currentLayer);
+ }
+ },
+ collectRenderablesWithEffects(instructionSet, renderer, currentLayer) {
+ const { renderPipes } = renderer;
+ for (let i = 0; i < this.effects.length; i++) {
+ const effect = this.effects[i];
+ const pipe = renderPipes[effect.pipe];
+ pipe.push(effect, this, instructionSet);
+ }
+ this.collectRenderablesSimple(instructionSet, renderer, currentLayer);
+ for (let i = this.effects.length - 1; i >= 0; i--) {
+ const effect = this.effects[i];
+ const pipe = renderPipes[effect.pipe];
+ pipe.pop(effect, this, instructionSet);
+ }
+ }
+ };
+
+ "use strict";
+ class FilterEffect {
+ constructor() {
+ /** the pipe that knows how to handle this effect */
+ this.pipe = "filter";
+ /** the priority of this effect */
+ this.priority = 1;
+ }
+ destroy() {
+ for (let i = 0; i < this.filters.length; i++) {
+ this.filters[i].destroy();
+ }
+ this.filters = null;
+ this.filterArea = null;
+ }
+ }
+
+ "use strict";
+ class MaskEffectManagerClass {
+ constructor() {
+ /** @private */
+ this._effectClasses = [];
+ this._tests = [];
+ this._initialized = false;
+ }
+ init() {
+ if (this._initialized)
+ return;
+ this._initialized = true;
+ this._effectClasses.forEach((test) => {
+ this.add({
+ test: test.test,
+ maskClass: test
+ });
+ });
+ }
+ add(test) {
+ this._tests.push(test);
+ }
+ getMaskEffect(item) {
+ if (!this._initialized)
+ this.init();
+ for (let i = 0; i < this._tests.length; i++) {
+ const test = this._tests[i];
+ if (test.test(item)) {
+ return BigPool.get(test.maskClass, item);
+ }
+ }
+ return item;
+ }
+ returnMaskEffect(effect) {
+ BigPool.return(effect);
+ }
+ }
+ const MaskEffectManager = new MaskEffectManagerClass();
+ extensions.handleByList(ExtensionType.MaskEffect, MaskEffectManager._effectClasses);
+
+ "use strict";
+ var __defProp$1c = Object.defineProperty;
+ var __getOwnPropSymbols$1d = Object.getOwnPropertySymbols;
+ var __hasOwnProp$1d = Object.prototype.hasOwnProperty;
+ var __propIsEnum$1d = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$1c = (obj, key, value) => key in obj ? __defProp$1c(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$1c = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$1d.call(b, prop))
+ __defNormalProp$1c(a, prop, b[prop]);
+ if (__getOwnPropSymbols$1d)
+ for (var prop of __getOwnPropSymbols$1d(b)) {
+ if (__propIsEnum$1d.call(b, prop))
+ __defNormalProp$1c(a, prop, b[prop]);
+ }
+ return a;
+ };
+ const effectsMixin = {
+ _maskEffect: null,
+ _maskOptions: {
+ inverse: false
+ },
+ _filterEffect: null,
+ effects: [],
+ _markStructureAsChanged() {
+ const renderGroup = this.renderGroup || this.parentRenderGroup;
+ if (renderGroup) {
+ renderGroup.structureDidChange = true;
+ }
+ },
+ addEffect(effect) {
+ const index = this.effects.indexOf(effect);
+ if (index !== -1)
+ return;
+ this.effects.push(effect);
+ this.effects.sort((a, b) => a.priority - b.priority);
+ this._markStructureAsChanged();
+ this._updateIsSimple();
+ },
+ removeEffect(effect) {
+ const index = this.effects.indexOf(effect);
+ if (index === -1)
+ return;
+ this.effects.splice(index, 1);
+ this._markStructureAsChanged();
+ this._updateIsSimple();
+ },
+ set mask(value) {
+ const effect = this._maskEffect;
+ if ((effect == null ? void 0 : effect.mask) === value)
+ return;
+ if (effect) {
+ this.removeEffect(effect);
+ MaskEffectManager.returnMaskEffect(effect);
+ this._maskEffect = null;
+ }
+ if (value === null || value === void 0)
+ return;
+ this._maskEffect = MaskEffectManager.getMaskEffect(value);
+ this.addEffect(this._maskEffect);
+ },
+ get mask() {
+ var _a;
+ return (_a = this._maskEffect) == null ? void 0 : _a.mask;
+ },
+ setMask(options) {
+ this._maskOptions = __spreadValues$1c(__spreadValues$1c({}, this._maskOptions), options);
+ if (options.mask) {
+ this.mask = options.mask;
+ }
+ this._markStructureAsChanged();
+ },
+ set filters(value) {
+ var _a;
+ if (!Array.isArray(value) && value)
+ value = [value];
+ const effect = this._filterEffect || (this._filterEffect = new FilterEffect());
+ value = value;
+ const hasFilters = (value == null ? void 0 : value.length) > 0;
+ const hadFilters = ((_a = effect.filters) == null ? void 0 : _a.length) > 0;
+ const didChange = hasFilters !== hadFilters;
+ value = Array.isArray(value) ? value.slice(0) : value;
+ effect.filters = Object.freeze(value);
+ if (didChange) {
+ if (hasFilters) {
+ this.addEffect(effect);
+ } else {
+ this.removeEffect(effect);
+ effect.filters = value != null ? value : null;
+ }
+ }
+ },
+ get filters() {
+ var _a;
+ return (_a = this._filterEffect) == null ? void 0 : _a.filters;
+ },
+ set filterArea(value) {
+ this._filterEffect || (this._filterEffect = new FilterEffect());
+ this._filterEffect.filterArea = value;
+ },
+ get filterArea() {
+ var _a;
+ return (_a = this._filterEffect) == null ? void 0 : _a.filterArea;
+ }
+ };
+
+ "use strict";
+ const findMixin = {
+ label: null,
+ get name() {
+ deprecation(v8_0_0, "Container.name property has been removed, use Container.label instead");
+ return this.label;
+ },
+ set name(value) {
+ deprecation(v8_0_0, "Container.name property has been removed, use Container.label instead");
+ this.label = value;
+ },
+ getChildByName(name, deep = false) {
+ return this.getChildByLabel(name, deep);
+ },
+ getChildByLabel(label, deep = false) {
+ const children = this.children;
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ if (child.label === label || label instanceof RegExp && label.test(child.label))
+ return child;
+ }
+ if (deep) {
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ const found = child.getChildByLabel(label, true);
+ if (found) {
+ return found;
+ }
+ }
+ }
+ return null;
+ },
+ getChildrenByLabel(label, deep = false, out = []) {
+ const children = this.children;
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ if (child.label === label || label instanceof RegExp && label.test(child.label)) {
+ out.push(child);
+ }
+ }
+ if (deep) {
+ for (let i = 0; i < children.length; i++) {
+ children[i].getChildrenByLabel(label, true, out);
+ }
+ }
+ return out;
+ }
+ };
+
+ "use strict";
+ const tempPoints = [new Point(), new Point(), new Point(), new Point()];
+ class Rectangle {
+ /**
+ * @param x - The X coordinate of the upper-left corner of the rectangle
+ * @param y - The Y coordinate of the upper-left corner of the rectangle
+ * @param width - The overall width of the rectangle
+ * @param height - The overall height of the rectangle
+ */
+ constructor(x = 0, y = 0, width = 0, height = 0) {
+ /**
+ * The type of the object, mainly used to avoid `instanceof` checks
+ * @example
+ * ```ts
+ * // Check shape type
+ * const shape = new Rectangle(0, 0, 100, 100);
+ * console.log(shape.type); // 'rectangle'
+ *
+ * // Use in type guards
+ * if (shape.type === 'rectangle') {
+ * console.log(shape.width, shape.height);
+ * }
+ * ```
+ * @readonly
+ * @default 'rectangle'
+ * @see {@link SHAPE_PRIMITIVE} For all shape types
+ */
+ this.type = "rectangle";
+ this.x = Number(x);
+ this.y = Number(y);
+ this.width = Number(width);
+ this.height = Number(height);
+ }
+ /**
+ * Returns the left edge (x-coordinate) of the rectangle.
+ * @example
+ * ```ts
+ * // Get left edge position
+ * const rect = new Rectangle(100, 100, 200, 150);
+ * console.log(rect.left); // 100
+ *
+ * // Use in alignment calculations
+ * sprite.x = rect.left + padding;
+ *
+ * // Compare positions
+ * if (point.x > rect.left) {
+ * console.log('Point is right of rectangle');
+ * }
+ * ```
+ * @readonly
+ * @returns The x-coordinate of the left edge
+ * @see {@link Rectangle.right} For right edge position
+ * @see {@link Rectangle.x} For direct x-coordinate access
+ */
+ get left() {
+ return this.x;
+ }
+ /**
+ * Returns the right edge (x + width) of the rectangle.
+ * @example
+ * ```ts
+ * // Get right edge position
+ * const rect = new Rectangle(100, 100, 200, 150);
+ * console.log(rect.right); // 300
+ *
+ * // Align to right edge
+ * sprite.x = rect.right - sprite.width;
+ *
+ * // Check boundaries
+ * if (point.x < rect.right) {
+ * console.log('Point is inside right bound');
+ * }
+ * ```
+ * @readonly
+ * @returns The x-coordinate of the right edge
+ * @see {@link Rectangle.left} For left edge position
+ * @see {@link Rectangle.width} For width value
+ */
+ get right() {
+ return this.x + this.width;
+ }
+ /**
+ * Returns the top edge (y-coordinate) of the rectangle.
+ * @example
+ * ```ts
+ * // Get top edge position
+ * const rect = new Rectangle(100, 100, 200, 150);
+ * console.log(rect.top); // 100
+ *
+ * // Position above rectangle
+ * sprite.y = rect.top - sprite.height;
+ *
+ * // Check vertical position
+ * if (point.y > rect.top) {
+ * console.log('Point is below top edge');
+ * }
+ * ```
+ * @readonly
+ * @returns The y-coordinate of the top edge
+ * @see {@link Rectangle.bottom} For bottom edge position
+ * @see {@link Rectangle.y} For direct y-coordinate access
+ */
+ get top() {
+ return this.y;
+ }
+ /**
+ * Returns the bottom edge (y + height) of the rectangle.
+ * @example
+ * ```ts
+ * // Get bottom edge position
+ * const rect = new Rectangle(100, 100, 200, 150);
+ * console.log(rect.bottom); // 250
+ *
+ * // Stack below rectangle
+ * sprite.y = rect.bottom + margin;
+ *
+ * // Check vertical bounds
+ * if (point.y < rect.bottom) {
+ * console.log('Point is above bottom edge');
+ * }
+ * ```
+ * @readonly
+ * @returns The y-coordinate of the bottom edge
+ * @see {@link Rectangle.top} For top edge position
+ * @see {@link Rectangle.height} For height value
+ */
+ get bottom() {
+ return this.y + this.height;
+ }
+ /**
+ * Determines whether the Rectangle is empty (has no area).
+ * @example
+ * ```ts
+ * // Check zero dimensions
+ * const rect = new Rectangle(100, 100, 0, 50);
+ * console.log(rect.isEmpty()); // true
+ * ```
+ * @returns True if the rectangle has no area
+ * @see {@link Rectangle.width} For width value
+ * @see {@link Rectangle.height} For height value
+ */
+ isEmpty() {
+ return this.left === this.right || this.top === this.bottom;
+ }
+ /**
+ * A constant empty rectangle. This is a new object every time the property is accessed.
+ * @example
+ * ```ts
+ * // Get fresh empty rectangle
+ * const empty = Rectangle.EMPTY;
+ * console.log(empty.isEmpty()); // true
+ * ```
+ * @returns A new empty rectangle instance
+ * @see {@link Rectangle.isEmpty} For empty state testing
+ */
+ static get EMPTY() {
+ return new Rectangle(0, 0, 0, 0);
+ }
+ /**
+ * Creates a clone of this Rectangle
+ * @example
+ * ```ts
+ * // Basic cloning
+ * const original = new Rectangle(100, 100, 200, 150);
+ * const copy = original.clone();
+ *
+ * // Clone and modify
+ * const modified = original.clone();
+ * modified.width *= 2;
+ * modified.height += 50;
+ *
+ * // Verify independence
+ * console.log(original.width); // 200
+ * console.log(modified.width); // 400
+ * ```
+ * @returns A copy of the rectangle
+ * @see {@link Rectangle.copyFrom} For copying into existing rectangle
+ * @see {@link Rectangle.copyTo} For copying to another rectangle
+ */
+ clone() {
+ return new Rectangle(this.x, this.y, this.width, this.height);
+ }
+ /**
+ * Converts a Bounds object to a Rectangle object.
+ * @example
+ * ```ts
+ * // Convert bounds to rectangle
+ * const bounds = container.getBounds();
+ * const rect = new Rectangle().copyFromBounds(bounds);
+ * ```
+ * @param bounds - The bounds to copy and convert to a rectangle
+ * @returns Returns itself
+ * @see {@link Bounds} For bounds object structure
+ * @see {@link Rectangle.getBounds} For getting rectangle bounds
+ */
+ copyFromBounds(bounds) {
+ this.x = bounds.minX;
+ this.y = bounds.minY;
+ this.width = bounds.maxX - bounds.minX;
+ this.height = bounds.maxY - bounds.minY;
+ return this;
+ }
+ /**
+ * Copies another rectangle to this one.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new Rectangle(100, 100, 200, 150);
+ * const target = new Rectangle();
+ * target.copyFrom(source);
+ *
+ * // Chain with other operations
+ * const rect = new Rectangle()
+ * .copyFrom(source)
+ * .pad(10);
+ * ```
+ * @param rectangle - The rectangle to copy from
+ * @returns Returns itself
+ * @see {@link Rectangle.copyTo} For copying to another rectangle
+ * @see {@link Rectangle.clone} For creating new rectangle copy
+ */
+ copyFrom(rectangle) {
+ this.x = rectangle.x;
+ this.y = rectangle.y;
+ this.width = rectangle.width;
+ this.height = rectangle.height;
+ return this;
+ }
+ /**
+ * Copies this rectangle to another one.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new Rectangle(100, 100, 200, 150);
+ * const target = new Rectangle();
+ * source.copyTo(target);
+ *
+ * // Chain with other operations
+ * const result = source
+ * .copyTo(new Rectangle())
+ * .getBounds();
+ * ```
+ * @param rectangle - The rectangle to copy to
+ * @returns Returns given parameter
+ * @see {@link Rectangle.copyFrom} For copying from another rectangle
+ * @see {@link Rectangle.clone} For creating new rectangle copy
+ */
+ copyTo(rectangle) {
+ rectangle.copyFrom(this);
+ return rectangle;
+ }
+ /**
+ * Checks whether the x and y coordinates given are contained within this Rectangle
+ * @example
+ * ```ts
+ * // Basic containment check
+ * const rect = new Rectangle(100, 100, 200, 150);
+ * const isInside = rect.contains(150, 125); // true
+ * // Check edge cases
+ * console.log(rect.contains(100, 100)); // true (on edge)
+ * console.log(rect.contains(300, 250)); // false (outside)
+ * ```
+ * @param x - The X coordinate of the point to test
+ * @param y - The Y coordinate of the point to test
+ * @returns Whether the x/y coordinates are within this Rectangle
+ * @see {@link Rectangle.containsRect} For rectangle containment
+ * @see {@link Rectangle.strokeContains} For checking stroke intersection
+ */
+ contains(x, y) {
+ if (this.width <= 0 || this.height <= 0) {
+ return false;
+ }
+ if (x >= this.x && x < this.x + this.width) {
+ if (y >= this.y && y < this.y + this.height) {
+ return true;
+ }
+ }
+ return false;
+ }
+ /**
+ * Checks whether the x and y coordinates given are contained within this rectangle including the stroke.
+ * @example
+ * ```ts
+ * // Basic stroke check
+ * const rect = new Rectangle(100, 100, 200, 150);
+ * const isOnStroke = rect.strokeContains(150, 100, 4); // 4px line width
+ *
+ * // Check with different alignments
+ * const innerStroke = rect.strokeContains(150, 100, 4, 1); // Inside
+ * const centerStroke = rect.strokeContains(150, 100, 4, 0.5); // Centered
+ * const outerStroke = rect.strokeContains(150, 100, 4, 0); // Outside
+ * ```
+ * @param x - The X coordinate of the point to test
+ * @param y - The Y coordinate of the point to test
+ * @param strokeWidth - The width of the line to check
+ * @param alignment - The alignment of the stroke (1 = inner, 0.5 = centered, 0 = outer)
+ * @returns Whether the x/y coordinates are within this rectangle's stroke
+ * @see {@link Rectangle.contains} For checking fill containment
+ * @see {@link Rectangle.getBounds} For getting stroke bounds
+ */
+ strokeContains(x, y, strokeWidth, alignment = 0.5) {
+ const { width, height } = this;
+ if (width <= 0 || height <= 0)
+ return false;
+ const _x = this.x;
+ const _y = this.y;
+ const strokeWidthOuter = strokeWidth * (1 - alignment);
+ const strokeWidthInner = strokeWidth - strokeWidthOuter;
+ const outerLeft = _x - strokeWidthOuter;
+ const outerRight = _x + width + strokeWidthOuter;
+ const outerTop = _y - strokeWidthOuter;
+ const outerBottom = _y + height + strokeWidthOuter;
+ const innerLeft = _x + strokeWidthInner;
+ const innerRight = _x + width - strokeWidthInner;
+ const innerTop = _y + strokeWidthInner;
+ const innerBottom = _y + height - strokeWidthInner;
+ return x >= outerLeft && x <= outerRight && y >= outerTop && y <= outerBottom && !(x > innerLeft && x < innerRight && y > innerTop && y < innerBottom);
+ }
+ /**
+ * Determines whether the `other` Rectangle transformed by `transform` intersects with `this` Rectangle object.
+ * Returns true only if the area of the intersection is >0, this means that Rectangles
+ * sharing a side are not overlapping. Another side effect is that an arealess rectangle
+ * (width or height equal to zero) can't intersect any other rectangle.
+ * @param {Rectangle} other - The Rectangle to intersect with `this`.
+ * @param {Matrix} transform - The transformation matrix of `other`.
+ * @returns {boolean} A value of `true` if the transformed `other` Rectangle intersects with `this`; otherwise `false`.
+ */
+ /**
+ * Determines whether the `other` Rectangle transformed by `transform` intersects with `this` Rectangle object.
+ *
+ * Returns true only if the area of the intersection is greater than 0.
+ * This means that rectangles sharing only a side are not considered intersecting.
+ * @example
+ * ```ts
+ * // Basic intersection check
+ * const rect1 = new Rectangle(0, 0, 100, 100);
+ * const rect2 = new Rectangle(50, 50, 100, 100);
+ * console.log(rect1.intersects(rect2)); // true
+ *
+ * // With transformation matrix
+ * const matrix = new Matrix();
+ * matrix.rotate(Math.PI / 4); // 45 degrees
+ * console.log(rect1.intersects(rect2, matrix)); // Checks with rotation
+ *
+ * // Edge cases
+ * const zeroWidth = new Rectangle(0, 0, 0, 100);
+ * console.log(rect1.intersects(zeroWidth)); // false (no area)
+ * ```
+ * @remarks
+ * - Returns true only if intersection area is > 0
+ * - Rectangles sharing only a side are not intersecting
+ * - Zero-area rectangles cannot intersect anything
+ * - Supports optional transformation matrix
+ * @param other - The Rectangle to intersect with `this`
+ * @param transform - Optional transformation matrix of `other`
+ * @returns True if the transformed `other` Rectangle intersects with `this`
+ * @see {@link Rectangle.containsRect} For containment testing
+ * @see {@link Rectangle.contains} For point testing
+ */
+ intersects(other, transform) {
+ if (!transform) {
+ const x02 = this.x < other.x ? other.x : this.x;
+ const x12 = this.right > other.right ? other.right : this.right;
+ if (x12 <= x02) {
+ return false;
+ }
+ const y02 = this.y < other.y ? other.y : this.y;
+ const y12 = this.bottom > other.bottom ? other.bottom : this.bottom;
+ return y12 > y02;
+ }
+ const x0 = this.left;
+ const x1 = this.right;
+ const y0 = this.top;
+ const y1 = this.bottom;
+ if (x1 <= x0 || y1 <= y0) {
+ return false;
+ }
+ const lt = tempPoints[0].set(other.left, other.top);
+ const lb = tempPoints[1].set(other.left, other.bottom);
+ const rt = tempPoints[2].set(other.right, other.top);
+ const rb = tempPoints[3].set(other.right, other.bottom);
+ if (rt.x <= lt.x || lb.y <= lt.y) {
+ return false;
+ }
+ const s = Math.sign(transform.a * transform.d - transform.b * transform.c);
+ if (s === 0) {
+ return false;
+ }
+ transform.apply(lt, lt);
+ transform.apply(lb, lb);
+ transform.apply(rt, rt);
+ transform.apply(rb, rb);
+ if (Math.max(lt.x, lb.x, rt.x, rb.x) <= x0 || Math.min(lt.x, lb.x, rt.x, rb.x) >= x1 || Math.max(lt.y, lb.y, rt.y, rb.y) <= y0 || Math.min(lt.y, lb.y, rt.y, rb.y) >= y1) {
+ return false;
+ }
+ const nx = s * (lb.y - lt.y);
+ const ny = s * (lt.x - lb.x);
+ const n00 = nx * x0 + ny * y0;
+ const n10 = nx * x1 + ny * y0;
+ const n01 = nx * x0 + ny * y1;
+ const n11 = nx * x1 + ny * y1;
+ if (Math.max(n00, n10, n01, n11) <= nx * lt.x + ny * lt.y || Math.min(n00, n10, n01, n11) >= nx * rb.x + ny * rb.y) {
+ return false;
+ }
+ const mx = s * (lt.y - rt.y);
+ const my = s * (rt.x - lt.x);
+ const m00 = mx * x0 + my * y0;
+ const m10 = mx * x1 + my * y0;
+ const m01 = mx * x0 + my * y1;
+ const m11 = mx * x1 + my * y1;
+ if (Math.max(m00, m10, m01, m11) <= mx * lt.x + my * lt.y || Math.min(m00, m10, m01, m11) >= mx * rb.x + my * rb.y) {
+ return false;
+ }
+ return true;
+ }
+ /**
+ * Pads the rectangle making it grow in all directions.
+ *
+ * If paddingY is omitted, both paddingX and paddingY will be set to paddingX.
+ * @example
+ * ```ts
+ * // Basic padding
+ * const rect = new Rectangle(100, 100, 200, 150);
+ * rect.pad(10); // Adds 10px padding on all sides
+ *
+ * // Different horizontal and vertical padding
+ * const uiRect = new Rectangle(0, 0, 100, 50);
+ * uiRect.pad(20, 10); // 20px horizontal, 10px vertical
+ * ```
+ * @remarks
+ * - Adjusts x/y by subtracting padding
+ * - Increases width/height by padding * 2
+ * - Common in UI layout calculations
+ * - Chainable with other methods
+ * @param paddingX - The horizontal padding amount
+ * @param paddingY - The vertical padding amount
+ * @returns Returns itself
+ * @see {@link Rectangle.enlarge} For growing to include another rectangle
+ * @see {@link Rectangle.fit} For shrinking to fit within another rectangle
+ */
+ pad(paddingX = 0, paddingY = paddingX) {
+ this.x -= paddingX;
+ this.y -= paddingY;
+ this.width += paddingX * 2;
+ this.height += paddingY * 2;
+ return this;
+ }
+ /**
+ * Fits this rectangle around the passed one.
+ * @example
+ * ```ts
+ * // Basic fitting
+ * const container = new Rectangle(0, 0, 100, 100);
+ * const content = new Rectangle(25, 25, 200, 200);
+ * content.fit(container); // Clips to container bounds
+ * ```
+ * @param rectangle - The rectangle to fit around
+ * @returns Returns itself
+ * @see {@link Rectangle.enlarge} For growing to include another rectangle
+ * @see {@link Rectangle.pad} For adding padding around the rectangle
+ */
+ fit(rectangle) {
+ const x1 = Math.max(this.x, rectangle.x);
+ const x2 = Math.min(this.x + this.width, rectangle.x + rectangle.width);
+ const y1 = Math.max(this.y, rectangle.y);
+ const y2 = Math.min(this.y + this.height, rectangle.y + rectangle.height);
+ this.x = x1;
+ this.width = Math.max(x2 - x1, 0);
+ this.y = y1;
+ this.height = Math.max(y2 - y1, 0);
+ return this;
+ }
+ /**
+ * Enlarges rectangle so that its corners lie on a grid defined by resolution.
+ * @example
+ * ```ts
+ * // Basic grid alignment
+ * const rect = new Rectangle(10.2, 10.6, 100.8, 100.4);
+ * rect.ceil(); // Aligns to whole pixels
+ *
+ * // Custom resolution grid
+ * const uiRect = new Rectangle(5.3, 5.7, 50.2, 50.8);
+ * uiRect.ceil(0.5); // Aligns to half pixels
+ *
+ * // Use with precision value
+ * const preciseRect = new Rectangle(20.001, 20.999, 100.001, 100.999);
+ * preciseRect.ceil(1, 0.01); // Handles small decimal variations
+ * ```
+ * @param resolution - The grid size to align to (1 = whole pixels)
+ * @param eps - Small number to prevent floating point errors
+ * @returns Returns itself
+ * @see {@link Rectangle.fit} For constraining to bounds
+ * @see {@link Rectangle.enlarge} For growing dimensions
+ */
+ ceil(resolution = 1, eps = 1e-3) {
+ const x2 = Math.ceil((this.x + this.width - eps) * resolution) / resolution;
+ const y2 = Math.ceil((this.y + this.height - eps) * resolution) / resolution;
+ this.x = Math.floor((this.x + eps) * resolution) / resolution;
+ this.y = Math.floor((this.y + eps) * resolution) / resolution;
+ this.width = x2 - this.x;
+ this.height = y2 - this.y;
+ return this;
+ }
+ /**
+ * Scales the rectangle's dimensions and position by the specified factors.
+ * @example
+ * ```ts
+ * const rect = new Rectangle(50, 50, 100, 100);
+ *
+ * // Scale uniformly
+ * rect.scale(0.5, 0.5);
+ * // rect is now: x=25, y=25, width=50, height=50
+ *
+ * // non-uniformly
+ * rect.scale(0.5, 1);
+ * // rect is now: x=25, y=50, width=50, height=100
+ * ```
+ * @param x - The factor by which to scale the horizontal properties (x, width).
+ * @param y - The factor by which to scale the vertical properties (y, height).
+ * @returns Returns itself
+ */
+ scale(x, y = x) {
+ this.x *= x;
+ this.y *= y;
+ this.width *= x;
+ this.height *= y;
+ return this;
+ }
+ /**
+ * Enlarges this rectangle to include the passed rectangle.
+ * @example
+ * ```ts
+ * // Basic enlargement
+ * const rect = new Rectangle(50, 50, 100, 100);
+ * const other = new Rectangle(0, 0, 200, 75);
+ * rect.enlarge(other);
+ * // rect is now: x=0, y=0, width=200, height=150
+ *
+ * // Use for bounding box calculation
+ * const bounds = new Rectangle();
+ * objects.forEach((obj) => {
+ * bounds.enlarge(obj.getBounds());
+ * });
+ * ```
+ * @param rectangle - The rectangle to include
+ * @returns Returns itself
+ * @see {@link Rectangle.fit} For shrinking to fit within another rectangle
+ * @see {@link Rectangle.pad} For adding padding around the rectangle
+ */
+ enlarge(rectangle) {
+ const x1 = Math.min(this.x, rectangle.x);
+ const x2 = Math.max(this.x + this.width, rectangle.x + rectangle.width);
+ const y1 = Math.min(this.y, rectangle.y);
+ const y2 = Math.max(this.y + this.height, rectangle.y + rectangle.height);
+ this.x = x1;
+ this.width = x2 - x1;
+ this.y = y1;
+ this.height = y2 - y1;
+ return this;
+ }
+ /**
+ * Returns the framing rectangle of the rectangle as a Rectangle object
+ * @example
+ * ```ts
+ * // Basic bounds retrieval
+ * const rect = new Rectangle(100, 100, 200, 150);
+ * const bounds = rect.getBounds();
+ *
+ * // Reuse existing rectangle
+ * const out = new Rectangle();
+ * rect.getBounds(out);
+ * ```
+ * @param out - Optional rectangle to store the result
+ * @returns The framing rectangle
+ * @see {@link Rectangle.copyFrom} For direct copying
+ * @see {@link Rectangle.clone} For creating new copy
+ */
+ getBounds(out) {
+ out || (out = new Rectangle());
+ out.copyFrom(this);
+ return out;
+ }
+ /**
+ * Determines whether another Rectangle is fully contained within this Rectangle.
+ *
+ * Rectangles that occupy the same space are considered to be containing each other.
+ *
+ * Rectangles without area (width or height equal to zero) can't contain anything,
+ * not even other arealess rectangles.
+ * @example
+ * ```ts
+ * // Check if one rectangle contains another
+ * const container = new Rectangle(0, 0, 100, 100);
+ * const inner = new Rectangle(25, 25, 50, 50);
+ *
+ * console.log(container.containsRect(inner)); // true
+ *
+ * // Check overlapping rectangles
+ * const partial = new Rectangle(75, 75, 50, 50);
+ * console.log(container.containsRect(partial)); // false
+ *
+ * // Zero-area rectangles
+ * const empty = new Rectangle(0, 0, 0, 100);
+ * console.log(container.containsRect(empty)); // false
+ * ```
+ * @param other - The Rectangle to check for containment
+ * @returns True if other is fully contained within this Rectangle
+ * @see {@link Rectangle.contains} For point containment
+ * @see {@link Rectangle.intersects} For overlap testing
+ */
+ containsRect(other) {
+ if (this.width <= 0 || this.height <= 0)
+ return false;
+ const x1 = other.x;
+ const y1 = other.y;
+ const x2 = other.x + other.width;
+ const y2 = other.y + other.height;
+ return x1 >= this.x && x1 < this.x + this.width && y1 >= this.y && y1 < this.y + this.height && x2 >= this.x && x2 < this.x + this.width && y2 >= this.y && y2 < this.y + this.height;
+ }
+ /**
+ * Sets the position and dimensions of the rectangle.
+ * @example
+ * ```ts
+ * // Basic usage
+ * const rect = new Rectangle();
+ * rect.set(100, 100, 200, 150);
+ *
+ * // Chain with other operations
+ * const bounds = new Rectangle()
+ * .set(0, 0, 100, 100)
+ * .pad(10);
+ * ```
+ * @param x - The X coordinate of the upper-left corner of the rectangle
+ * @param y - The Y coordinate of the upper-left corner of the rectangle
+ * @param width - The overall width of the rectangle
+ * @param height - The overall height of the rectangle
+ * @returns Returns itself for method chaining
+ * @see {@link Rectangle.copyFrom} For copying from another rectangle
+ * @see {@link Rectangle.clone} For creating a new copy
+ */
+ set(x, y, width, height) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ return this;
+ }
+ toString() {
+ return `[pixi.js/math:Rectangle x=${this.x} y=${this.y} width=${this.width} height=${this.height}]`;
+ }
+ }
+
+ "use strict";
+ const defaultMatrix = new Matrix();
+ class Bounds {
+ /**
+ * Creates a new Bounds object.
+ * @param minX - The minimum X coordinate of the bounds.
+ * @param minY - The minimum Y coordinate of the bounds.
+ * @param maxX - The maximum X coordinate of the bounds.
+ * @param maxY - The maximum Y coordinate of the bounds.
+ */
+ constructor(minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity) {
+ /**
+ * The minimum X coordinate of the bounds.
+ * Represents the leftmost edge of the bounding box.
+ * @example
+ * ```ts
+ * const bounds = new Bounds();
+ * // Set left edge
+ * bounds.minX = 100;
+ * ```
+ * @default Infinity
+ */
+ this.minX = Infinity;
+ /**
+ * The minimum Y coordinate of the bounds.
+ * Represents the topmost edge of the bounding box.
+ * @example
+ * ```ts
+ * const bounds = new Bounds();
+ * // Set top edge
+ * bounds.minY = 100;
+ * ```
+ * @default Infinity
+ */
+ this.minY = Infinity;
+ /**
+ * The maximum X coordinate of the bounds.
+ * Represents the rightmost edge of the bounding box.
+ * @example
+ * ```ts
+ * const bounds = new Bounds();
+ * // Set right edge
+ * bounds.maxX = 200;
+ * // Get width
+ * const width = bounds.maxX - bounds.minX;
+ * ```
+ * @default -Infinity
+ */
+ this.maxX = -Infinity;
+ /**
+ * The maximum Y coordinate of the bounds.
+ * Represents the bottommost edge of the bounding box.
+ * @example
+ * ```ts
+ * const bounds = new Bounds();
+ * // Set bottom edge
+ * bounds.maxY = 200;
+ * // Get height
+ * const height = bounds.maxY - bounds.minY;
+ * ```
+ * @default -Infinity
+ */
+ this.maxY = -Infinity;
+ /**
+ * The transformation matrix applied to this bounds object.
+ * Used when calculating bounds with transforms.
+ * @example
+ * ```ts
+ * const bounds = new Bounds();
+ *
+ * // Apply translation matrix
+ * bounds.matrix = new Matrix()
+ * .translate(100, 100);
+ *
+ * // Combine transformations
+ * bounds.matrix = new Matrix()
+ * .translate(50, 50)
+ * .rotate(Math.PI / 4)
+ * .scale(2, 2);
+ *
+ * // Use in bounds calculations
+ * bounds.addFrame(0, 0, 100, 100); // Uses current matrix
+ * bounds.addFrame(0, 0, 100, 100, customMatrix); // Override matrix
+ * ```
+ * @advanced
+ */
+ this.matrix = defaultMatrix;
+ this.minX = minX;
+ this.minY = minY;
+ this.maxX = maxX;
+ this.maxY = maxY;
+ }
+ /**
+ * Checks if bounds are empty, meaning either width or height is zero or negative.
+ * Empty bounds occur when min values exceed max values on either axis.
+ * @example
+ * ```ts
+ * const bounds = new Bounds();
+ *
+ * // Check if newly created bounds are empty
+ * console.log(bounds.isEmpty()); // true, default bounds are empty
+ *
+ * // Add frame and check again
+ * bounds.addFrame(0, 0, 100, 100);
+ * console.log(bounds.isEmpty()); // false, bounds now have area
+ *
+ * // Clear bounds
+ * bounds.clear();
+ * console.log(bounds.isEmpty()); // true, bounds are empty again
+ * ```
+ * @returns True if bounds are empty (have no area)
+ * @see {@link Bounds#clear} For resetting bounds
+ * @see {@link Bounds#isValid} For checking validity
+ */
+ isEmpty() {
+ return this.minX > this.maxX || this.minY > this.maxY;
+ }
+ /**
+ * The bounding rectangle representation of these bounds.
+ * Lazily creates and updates a Rectangle instance based on the current bounds.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ *
+ * // Get rectangle representation
+ * const rect = bounds.rectangle;
+ * console.log(rect.x, rect.y, rect.width, rect.height);
+ *
+ * // Use for hit testing
+ * if (bounds.rectangle.contains(mouseX, mouseY)) {
+ * console.log('Mouse is inside bounds!');
+ * }
+ * ```
+ * @see {@link Rectangle} For rectangle methods
+ * @see {@link Bounds.isEmpty} For bounds validation
+ */
+ get rectangle() {
+ if (!this._rectangle) {
+ this._rectangle = new Rectangle();
+ }
+ const rectangle = this._rectangle;
+ if (this.minX > this.maxX || this.minY > this.maxY) {
+ rectangle.x = 0;
+ rectangle.y = 0;
+ rectangle.width = 0;
+ rectangle.height = 0;
+ } else {
+ rectangle.copyFromBounds(this);
+ }
+ return rectangle;
+ }
+ /**
+ * Clears the bounds and resets all coordinates to their default values.
+ * Resets the transformation matrix back to identity.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ * console.log(bounds.isEmpty()); // false
+ * // Clear the bounds
+ * bounds.clear();
+ * console.log(bounds.isEmpty()); // true
+ * ```
+ * @returns This bounds object for chaining
+ */
+ clear() {
+ this.minX = Infinity;
+ this.minY = Infinity;
+ this.maxX = -Infinity;
+ this.maxY = -Infinity;
+ this.matrix = defaultMatrix;
+ return this;
+ }
+ /**
+ * Sets the bounds directly using coordinate values.
+ * Provides a way to set all bounds values at once.
+ * @example
+ * ```ts
+ * const bounds = new Bounds();
+ * bounds.set(0, 0, 100, 100);
+ * ```
+ * @param x0 - Left X coordinate of frame
+ * @param y0 - Top Y coordinate of frame
+ * @param x1 - Right X coordinate of frame
+ * @param y1 - Bottom Y coordinate of frame
+ * @see {@link Bounds#addFrame} For matrix-aware bounds setting
+ * @see {@link Bounds#clear} For resetting bounds
+ */
+ set(x0, y0, x1, y1) {
+ this.minX = x0;
+ this.minY = y0;
+ this.maxX = x1;
+ this.maxY = y1;
+ }
+ /**
+ * Adds a rectangular frame to the bounds, optionally transformed by a matrix.
+ * Updates the bounds to encompass the new frame coordinates.
+ * @example
+ * ```ts
+ * const bounds = new Bounds();
+ * bounds.addFrame(0, 0, 100, 100);
+ *
+ * // Add transformed frame
+ * const matrix = new Matrix()
+ * .translate(50, 50)
+ * .rotate(Math.PI / 4);
+ * bounds.addFrame(0, 0, 100, 100, matrix);
+ * ```
+ * @param x0 - Left X coordinate of frame
+ * @param y0 - Top Y coordinate of frame
+ * @param x1 - Right X coordinate of frame
+ * @param y1 - Bottom Y coordinate of frame
+ * @param matrix - Optional transformation matrix
+ * @see {@link Bounds#addRect} For adding Rectangle objects
+ * @see {@link Bounds#addBounds} For adding other Bounds
+ */
+ addFrame(x0, y0, x1, y1, matrix) {
+ matrix || (matrix = this.matrix);
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+ let minX = this.minX;
+ let minY = this.minY;
+ let maxX = this.maxX;
+ let maxY = this.maxY;
+ let x = a * x0 + c * y0 + tx;
+ let y = b * x0 + d * y0 + ty;
+ if (x < minX)
+ minX = x;
+ if (y < minY)
+ minY = y;
+ if (x > maxX)
+ maxX = x;
+ if (y > maxY)
+ maxY = y;
+ x = a * x1 + c * y0 + tx;
+ y = b * x1 + d * y0 + ty;
+ if (x < minX)
+ minX = x;
+ if (y < minY)
+ minY = y;
+ if (x > maxX)
+ maxX = x;
+ if (y > maxY)
+ maxY = y;
+ x = a * x0 + c * y1 + tx;
+ y = b * x0 + d * y1 + ty;
+ if (x < minX)
+ minX = x;
+ if (y < minY)
+ minY = y;
+ if (x > maxX)
+ maxX = x;
+ if (y > maxY)
+ maxY = y;
+ x = a * x1 + c * y1 + tx;
+ y = b * x1 + d * y1 + ty;
+ if (x < minX)
+ minX = x;
+ if (y < minY)
+ minY = y;
+ if (x > maxX)
+ maxX = x;
+ if (y > maxY)
+ maxY = y;
+ this.minX = minX;
+ this.minY = minY;
+ this.maxX = maxX;
+ this.maxY = maxY;
+ }
+ /**
+ * Adds a rectangle to the bounds, optionally transformed by a matrix.
+ * Updates the bounds to encompass the given rectangle.
+ * @example
+ * ```ts
+ * const bounds = new Bounds();
+ * // Add simple rectangle
+ * const rect = new Rectangle(0, 0, 100, 100);
+ * bounds.addRect(rect);
+ *
+ * // Add transformed rectangle
+ * const matrix = new Matrix()
+ * .translate(50, 50)
+ * .rotate(Math.PI / 4);
+ * bounds.addRect(rect, matrix);
+ * ```
+ * @param rect - The rectangle to be added
+ * @param matrix - Optional transformation matrix
+ * @see {@link Bounds#addFrame} For adding raw coordinates
+ * @see {@link Bounds#addBounds} For adding other bounds
+ */
+ addRect(rect, matrix) {
+ this.addFrame(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, matrix);
+ }
+ /**
+ * Adds another bounds object to this one, optionally transformed by a matrix.
+ * Expands the bounds to include the given bounds' area.
+ * @example
+ * ```ts
+ * const bounds = new Bounds();
+ *
+ * // Add child bounds
+ * const childBounds = sprite.getBounds();
+ * bounds.addBounds(childBounds);
+ *
+ * // Add transformed bounds
+ * const matrix = new Matrix()
+ * .scale(2, 2);
+ * bounds.addBounds(childBounds, matrix);
+ * ```
+ * @param bounds - The bounds to be added
+ * @param matrix - Optional transformation matrix
+ * @see {@link Bounds#addFrame} For adding raw coordinates
+ * @see {@link Bounds#addRect} For adding rectangles
+ */
+ addBounds(bounds, matrix) {
+ this.addFrame(bounds.minX, bounds.minY, bounds.maxX, bounds.maxY, matrix);
+ }
+ /**
+ * Adds other Bounds as a mask, creating an intersection of the two bounds.
+ * Only keeps the overlapping region between current bounds and mask bounds.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ * // Create mask bounds
+ * const mask = new Bounds();
+ * mask.addFrame(50, 50, 150, 150);
+ * // Apply mask - results in bounds of (50,50,100,100)
+ * bounds.addBoundsMask(mask);
+ * ```
+ * @param mask - The Bounds to use as a mask
+ * @see {@link Bounds#addBounds} For union operation
+ * @see {@link Bounds#fit} For fitting to rectangle
+ */
+ addBoundsMask(mask) {
+ this.minX = this.minX > mask.minX ? this.minX : mask.minX;
+ this.minY = this.minY > mask.minY ? this.minY : mask.minY;
+ this.maxX = this.maxX < mask.maxX ? this.maxX : mask.maxX;
+ this.maxY = this.maxY < mask.maxY ? this.maxY : mask.maxY;
+ }
+ /**
+ * Applies a transformation matrix to the bounds, updating its coordinates.
+ * Transforms all corners of the bounds using the given matrix.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ * // Apply translation
+ * const translateMatrix = new Matrix()
+ * .translate(50, 50);
+ * bounds.applyMatrix(translateMatrix);
+ * ```
+ * @param matrix - The matrix to apply to the bounds
+ * @see {@link Matrix} For matrix operations
+ * @see {@link Bounds#addFrame} For adding transformed frames
+ */
+ applyMatrix(matrix) {
+ const minX = this.minX;
+ const minY = this.minY;
+ const maxX = this.maxX;
+ const maxY = this.maxY;
+ const { a, b, c, d, tx, ty } = matrix;
+ let x = a * minX + c * minY + tx;
+ let y = b * minX + d * minY + ty;
+ this.minX = x;
+ this.minY = y;
+ this.maxX = x;
+ this.maxY = y;
+ x = a * maxX + c * minY + tx;
+ y = b * maxX + d * minY + ty;
+ this.minX = x < this.minX ? x : this.minX;
+ this.minY = y < this.minY ? y : this.minY;
+ this.maxX = x > this.maxX ? x : this.maxX;
+ this.maxY = y > this.maxY ? y : this.maxY;
+ x = a * minX + c * maxY + tx;
+ y = b * minX + d * maxY + ty;
+ this.minX = x < this.minX ? x : this.minX;
+ this.minY = y < this.minY ? y : this.minY;
+ this.maxX = x > this.maxX ? x : this.maxX;
+ this.maxY = y > this.maxY ? y : this.maxY;
+ x = a * maxX + c * maxY + tx;
+ y = b * maxX + d * maxY + ty;
+ this.minX = x < this.minX ? x : this.minX;
+ this.minY = y < this.minY ? y : this.minY;
+ this.maxX = x > this.maxX ? x : this.maxX;
+ this.maxY = y > this.maxY ? y : this.maxY;
+ }
+ /**
+ * Resizes the bounds object to fit within the given rectangle.
+ * Clips the bounds if they extend beyond the rectangle's edges.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 200, 200);
+ * // Fit within viewport
+ * const viewport = new Rectangle(50, 50, 100, 100);
+ * bounds.fit(viewport);
+ * // bounds are now (50, 50, 150, 150)
+ * ```
+ * @param rect - The rectangle to fit within
+ * @returns This bounds object for chaining
+ * @see {@link Bounds#addBoundsMask} For intersection
+ * @see {@link Bounds#pad} For expanding bounds
+ */
+ fit(rect) {
+ if (this.minX < rect.left)
+ this.minX = rect.left;
+ if (this.maxX > rect.right)
+ this.maxX = rect.right;
+ if (this.minY < rect.top)
+ this.minY = rect.top;
+ if (this.maxY > rect.bottom)
+ this.maxY = rect.bottom;
+ return this;
+ }
+ /**
+ * Resizes the bounds object to include the given bounds.
+ * Similar to fit() but works with raw coordinate values instead of a Rectangle.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 200, 200);
+ * // Fit to specific coordinates
+ * bounds.fitBounds(50, 150, 50, 150);
+ * // bounds are now (50, 50, 150, 150)
+ * ```
+ * @param left - The left value of the bounds
+ * @param right - The right value of the bounds
+ * @param top - The top value of the bounds
+ * @param bottom - The bottom value of the bounds
+ * @returns This bounds object for chaining
+ * @see {@link Bounds#fit} For fitting to Rectangle
+ * @see {@link Bounds#addBoundsMask} For intersection
+ */
+ fitBounds(left, right, top, bottom) {
+ if (this.minX < left)
+ this.minX = left;
+ if (this.maxX > right)
+ this.maxX = right;
+ if (this.minY < top)
+ this.minY = top;
+ if (this.maxY > bottom)
+ this.maxY = bottom;
+ return this;
+ }
+ /**
+ * Pads bounds object, making it grow in all directions.
+ * If paddingY is omitted, both paddingX and paddingY will be set to paddingX.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ *
+ * // Add equal padding
+ * bounds.pad(10);
+ * // bounds are now (-10, -10, 110, 110)
+ *
+ * // Add different padding for x and y
+ * bounds.pad(20, 10);
+ * // bounds are now (-30, -20, 130, 120)
+ * ```
+ * @param paddingX - The horizontal padding amount
+ * @param paddingY - The vertical padding amount
+ * @returns This bounds object for chaining
+ * @see {@link Bounds#fit} For constraining bounds
+ * @see {@link Bounds#scale} For uniform scaling
+ */
+ pad(paddingX, paddingY = paddingX) {
+ this.minX -= paddingX;
+ this.maxX += paddingX;
+ this.minY -= paddingY;
+ this.maxY += paddingY;
+ return this;
+ }
+ /**
+ * Ceils the bounds by rounding up max values and rounding down min values.
+ * Useful for pixel-perfect calculations and avoiding fractional pixels.
+ * @example
+ * ```ts
+ * const bounds = new Bounds();
+ * bounds.set(10.2, 10.9, 50.1, 50.8);
+ *
+ * // Round to whole pixels
+ * bounds.ceil();
+ * // bounds are now (10, 10, 51, 51)
+ * ```
+ * @returns This bounds object for chaining
+ * @see {@link Bounds#scale} For size adjustments
+ * @see {@link Bounds#fit} For constraining bounds
+ */
+ ceil() {
+ this.minX = Math.floor(this.minX);
+ this.minY = Math.floor(this.minY);
+ this.maxX = Math.ceil(this.maxX);
+ this.maxY = Math.ceil(this.maxY);
+ return this;
+ }
+ /**
+ * Creates a new Bounds instance with the same values.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ *
+ * // Create a copy
+ * const copy = bounds.clone();
+ *
+ * // Original and copy are independent
+ * bounds.pad(10);
+ * console.log(copy.width === bounds.width); // false
+ * ```
+ * @returns A new Bounds instance with the same values
+ * @see {@link Bounds#copyFrom} For reusing existing bounds
+ */
+ clone() {
+ return new Bounds(this.minX, this.minY, this.maxX, this.maxY);
+ }
+ /**
+ * Scales the bounds by the given values, adjusting all edges proportionally.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ *
+ * // Scale uniformly
+ * bounds.scale(2);
+ * // bounds are now (0, 0, 200, 200)
+ *
+ * // Scale non-uniformly
+ * bounds.scale(0.5, 2);
+ * // bounds are now (0, 0, 100, 400)
+ * ```
+ * @param x - The X value to scale by
+ * @param y - The Y value to scale by (defaults to x)
+ * @returns This bounds object for chaining
+ * @see {@link Bounds#pad} For adding padding
+ * @see {@link Bounds#fit} For constraining size
+ */
+ scale(x, y = x) {
+ this.minX *= x;
+ this.minY *= y;
+ this.maxX *= x;
+ this.maxY *= y;
+ return this;
+ }
+ /**
+ * The x position of the bounds in local space.
+ * Setting this value will move the bounds while maintaining its width.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ * // Get x position
+ * console.log(bounds.x); // 0
+ *
+ * // Move bounds horizontally
+ * bounds.x = 50;
+ * console.log(bounds.minX, bounds.maxX); // 50, 150
+ *
+ * // Width stays the same
+ * console.log(bounds.width); // Still 100
+ * ```
+ */
+ get x() {
+ return this.minX;
+ }
+ set x(value) {
+ const width = this.maxX - this.minX;
+ this.minX = value;
+ this.maxX = value + width;
+ }
+ /**
+ * The y position of the bounds in local space.
+ * Setting this value will move the bounds while maintaining its height.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ * // Get y position
+ * console.log(bounds.y); // 0
+ *
+ * // Move bounds vertically
+ * bounds.y = 50;
+ * console.log(bounds.minY, bounds.maxY); // 50, 150
+ *
+ * // Height stays the same
+ * console.log(bounds.height); // Still 100
+ * ```
+ */
+ get y() {
+ return this.minY;
+ }
+ set y(value) {
+ const height = this.maxY - this.minY;
+ this.minY = value;
+ this.maxY = value + height;
+ }
+ /**
+ * The width value of the bounds.
+ * Represents the distance between minX and maxX coordinates.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ * // Get width
+ * console.log(bounds.width); // 100
+ * // Resize width
+ * bounds.width = 200;
+ * console.log(bounds.maxX - bounds.minX); // 200
+ * ```
+ */
+ get width() {
+ return this.maxX - this.minX;
+ }
+ set width(value) {
+ this.maxX = this.minX + value;
+ }
+ /**
+ * The height value of the bounds.
+ * Represents the distance between minY and maxY coordinates.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ * // Get height
+ * console.log(bounds.height); // 100
+ * // Resize height
+ * bounds.height = 150;
+ * console.log(bounds.maxY - bounds.minY); // 150
+ * ```
+ */
+ get height() {
+ return this.maxY - this.minY;
+ }
+ set height(value) {
+ this.maxY = this.minY + value;
+ }
+ /**
+ * The left edge coordinate of the bounds.
+ * Alias for minX.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(50, 0, 150, 100);
+ * console.log(bounds.left); // 50
+ * console.log(bounds.left === bounds.minX); // true
+ * ```
+ * @readonly
+ */
+ get left() {
+ return this.minX;
+ }
+ /**
+ * The right edge coordinate of the bounds.
+ * Alias for maxX.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ * console.log(bounds.right); // 100
+ * console.log(bounds.right === bounds.maxX); // true
+ * ```
+ * @readonly
+ */
+ get right() {
+ return this.maxX;
+ }
+ /**
+ * The top edge coordinate of the bounds.
+ * Alias for minY.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 25, 100, 125);
+ * console.log(bounds.top); // 25
+ * console.log(bounds.top === bounds.minY); // true
+ * ```
+ * @readonly
+ */
+ get top() {
+ return this.minY;
+ }
+ /**
+ * The bottom edge coordinate of the bounds.
+ * Alias for maxY.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 200);
+ * console.log(bounds.bottom); // 200
+ * console.log(bounds.bottom === bounds.maxY); // true
+ * ```
+ * @readonly
+ */
+ get bottom() {
+ return this.maxY;
+ }
+ /**
+ * Whether the bounds has positive width and height.
+ * Checks if both dimensions are greater than zero.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ * // Check if bounds are positive
+ * console.log(bounds.isPositive); // true
+ *
+ * // Negative bounds
+ * bounds.maxX = bounds.minX;
+ * console.log(bounds.isPositive); // false, width is 0
+ * ```
+ * @readonly
+ * @see {@link Bounds#isEmpty} For checking empty state
+ * @see {@link Bounds#isValid} For checking validity
+ */
+ get isPositive() {
+ return this.maxX - this.minX > 0 && this.maxY - this.minY > 0;
+ }
+ /**
+ * Whether the bounds has valid coordinates.
+ * Checks if the bounds has been initialized with real values.
+ * @example
+ * ```ts
+ * const bounds = new Bounds();
+ * console.log(bounds.isValid); // false, default state
+ *
+ * // Set valid bounds
+ * bounds.addFrame(0, 0, 100, 100);
+ * console.log(bounds.isValid); // true
+ * ```
+ * @readonly
+ * @see {@link Bounds#isEmpty} For checking empty state
+ * @see {@link Bounds#isPositive} For checking dimensions
+ */
+ get isValid() {
+ return this.minX + this.minY !== Infinity;
+ }
+ /**
+ * Adds vertices from a Float32Array to the bounds, optionally transformed by a matrix.
+ * Used for efficiently updating bounds from raw vertex data.
+ * @example
+ * ```ts
+ * const bounds = new Bounds();
+ *
+ * // Add vertices from geometry
+ * const vertices = new Float32Array([
+ * 0, 0, // Vertex 1
+ * 100, 0, // Vertex 2
+ * 100, 100 // Vertex 3
+ * ]);
+ * bounds.addVertexData(vertices, 0, 6);
+ *
+ * // Add transformed vertices
+ * const matrix = new Matrix()
+ * .translate(50, 50)
+ * .rotate(Math.PI / 4);
+ * bounds.addVertexData(vertices, 0, 6, matrix);
+ *
+ * // Add subset of vertices
+ * bounds.addVertexData(vertices, 2, 4); // Only second vertex
+ * ```
+ * @param vertexData - The array of vertices to add
+ * @param beginOffset - Starting index in the vertex array
+ * @param endOffset - Ending index in the vertex array (excluded)
+ * @param matrix - Optional transformation matrix
+ * @see {@link Bounds#addFrame} For adding rectangular frames
+ * @see {@link Matrix} For transformation details
+ */
+ addVertexData(vertexData, beginOffset, endOffset, matrix) {
+ let minX = this.minX;
+ let minY = this.minY;
+ let maxX = this.maxX;
+ let maxY = this.maxY;
+ matrix || (matrix = this.matrix);
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+ for (let i = beginOffset; i < endOffset; i += 2) {
+ const localX = vertexData[i];
+ const localY = vertexData[i + 1];
+ const x = a * localX + c * localY + tx;
+ const y = b * localX + d * localY + ty;
+ minX = x < minX ? x : minX;
+ minY = y < minY ? y : minY;
+ maxX = x > maxX ? x : maxX;
+ maxY = y > maxY ? y : maxY;
+ }
+ this.minX = minX;
+ this.minY = minY;
+ this.maxX = maxX;
+ this.maxY = maxY;
+ }
+ /**
+ * Checks if a point is contained within the bounds.
+ * Returns true if the point's coordinates fall within the bounds' area.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ * // Basic point check
+ * console.log(bounds.containsPoint(50, 50)); // true
+ * console.log(bounds.containsPoint(150, 150)); // false
+ *
+ * // Check edges
+ * console.log(bounds.containsPoint(0, 0)); // true, includes edges
+ * console.log(bounds.containsPoint(100, 100)); // true, includes edges
+ * ```
+ * @param x - x coordinate to check
+ * @param y - y coordinate to check
+ * @returns True if the point is inside the bounds
+ * @see {@link Bounds#isPositive} For valid bounds check
+ * @see {@link Bounds#rectangle} For Rectangle representation
+ */
+ containsPoint(x, y) {
+ if (this.minX <= x && this.minY <= y && this.maxX >= x && this.maxY >= y) {
+ return true;
+ }
+ return false;
+ }
+ /**
+ * Returns a string representation of the bounds.
+ * Useful for debugging and logging bounds information.
+ * @example
+ * ```ts
+ * const bounds = new Bounds(0, 0, 100, 100);
+ * console.log(bounds.toString()); // "[pixi.js:Bounds minX=0 minY=0 maxX=100 maxY=100 width=100 height=100]"
+ * ```
+ * @returns A string describing the bounds
+ * @see {@link Bounds#copyFrom} For copying bounds
+ * @see {@link Bounds#clone} For creating a new instance
+ */
+ toString() {
+ return `[pixi.js:Bounds minX=${this.minX} minY=${this.minY} maxX=${this.maxX} maxY=${this.maxY} width=${this.width} height=${this.height}]`;
+ }
+ /**
+ * Copies the bounds from another bounds object.
+ * Useful for reusing bounds objects and avoiding allocations.
+ * @example
+ * ```ts
+ * const sourceBounds = new Bounds(0, 0, 100, 100);
+ * // Copy bounds
+ * const targetBounds = new Bounds();
+ * targetBounds.copyFrom(sourceBounds);
+ * ```
+ * @param bounds - The bounds to copy from
+ * @returns This bounds object for chaining
+ * @see {@link Bounds#clone} For creating new instances
+ */
+ copyFrom(bounds) {
+ this.minX = bounds.minX;
+ this.minY = bounds.minY;
+ this.maxX = bounds.maxX;
+ this.maxY = bounds.maxY;
+ return this;
+ }
+ }
+
+ "use strict";
+ const matrixPool = BigPool.getPool(Matrix);
+ const boundsPool = BigPool.getPool(Bounds);
+
+ "use strict";
+ const tempMatrix$5 = new Matrix();
+ const getFastGlobalBoundsMixin = {
+ getFastGlobalBounds(factorRenderLayers, bounds) {
+ bounds || (bounds = new Bounds());
+ bounds.clear();
+ this._getGlobalBoundsRecursive(!!factorRenderLayers, bounds, this.parentRenderLayer);
+ if (!bounds.isValid) {
+ bounds.set(0, 0, 0, 0);
+ }
+ const renderGroup = this.renderGroup || this.parentRenderGroup;
+ bounds.applyMatrix(renderGroup.worldTransform);
+ return bounds;
+ },
+ _getGlobalBoundsRecursive(factorRenderLayers, bounds, currentLayer) {
+ let localBounds = bounds;
+ if (factorRenderLayers && this.parentRenderLayer && this.parentRenderLayer !== currentLayer)
+ return;
+ if (this.localDisplayStatus !== 7 || !this.measurable) {
+ return;
+ }
+ const manageEffects = !!this.effects.length;
+ if (this.renderGroup || manageEffects) {
+ localBounds = boundsPool.get().clear();
+ }
+ if (this.boundsArea) {
+ bounds.addRect(this.boundsArea, this.worldTransform);
+ } else {
+ if (this.renderPipeId) {
+ const viewBounds = this.bounds;
+ localBounds.addFrame(
+ viewBounds.minX,
+ viewBounds.minY,
+ viewBounds.maxX,
+ viewBounds.maxY,
+ this.groupTransform
+ );
+ }
+ const children = this.children;
+ for (let i = 0; i < children.length; i++) {
+ children[i]._getGlobalBoundsRecursive(factorRenderLayers, localBounds, currentLayer);
+ }
+ }
+ if (manageEffects) {
+ let advanced = false;
+ const renderGroup = this.renderGroup || this.parentRenderGroup;
+ for (let i = 0; i < this.effects.length; i++) {
+ if (this.effects[i].addBounds) {
+ if (!advanced) {
+ advanced = true;
+ localBounds.applyMatrix(renderGroup.worldTransform);
+ }
+ this.effects[i].addBounds(localBounds, true);
+ }
+ }
+ if (advanced) {
+ localBounds.applyMatrix(renderGroup.worldTransform.copyTo(tempMatrix$5).invert());
+ }
+ bounds.addBounds(localBounds);
+ boundsPool.return(localBounds);
+ } else if (this.renderGroup) {
+ bounds.addBounds(localBounds, this.relativeGroupTransform);
+ boundsPool.return(localBounds);
+ }
+ }
+ };
+
+ "use strict";
+ function getGlobalBounds(target, skipUpdateTransform, bounds) {
+ bounds.clear();
+ let parentTransform;
+ let pooledMatrix;
+ if (target.parent) {
+ if (!skipUpdateTransform) {
+ pooledMatrix = matrixPool.get().identity();
+ parentTransform = updateTransformBackwards(target, pooledMatrix);
+ } else {
+ parentTransform = target.parent.worldTransform;
+ }
+ } else {
+ parentTransform = Matrix.IDENTITY;
+ }
+ _getGlobalBounds(target, bounds, parentTransform, skipUpdateTransform);
+ if (pooledMatrix) {
+ matrixPool.return(pooledMatrix);
+ }
+ if (!bounds.isValid) {
+ bounds.set(0, 0, 0, 0);
+ }
+ return bounds;
+ }
+ function _getGlobalBounds(target, bounds, parentTransform, skipUpdateTransform) {
+ var _a, _b;
+ if (!target.visible || !target.measurable)
+ return;
+ let worldTransform;
+ if (!skipUpdateTransform) {
+ target.updateLocalTransform();
+ worldTransform = matrixPool.get();
+ worldTransform.appendFrom(target.localTransform, parentTransform);
+ } else {
+ worldTransform = target.worldTransform;
+ }
+ const parentBounds = bounds;
+ const preserveBounds = !!target.effects.length;
+ if (preserveBounds) {
+ bounds = boundsPool.get().clear();
+ }
+ if (target.boundsArea) {
+ bounds.addRect(target.boundsArea, worldTransform);
+ } else {
+ const renderableBounds = target.bounds;
+ if (renderableBounds && !renderableBounds.isEmpty()) {
+ bounds.matrix = worldTransform;
+ bounds.addBounds(renderableBounds);
+ }
+ for (let i = 0; i < target.children.length; i++) {
+ _getGlobalBounds(target.children[i], bounds, worldTransform, skipUpdateTransform);
+ }
+ }
+ if (preserveBounds) {
+ for (let i = 0; i < target.effects.length; i++) {
+ (_b = (_a = target.effects[i]).addBounds) == null ? void 0 : _b.call(_a, bounds);
+ }
+ parentBounds.addBounds(bounds, Matrix.IDENTITY);
+ boundsPool.return(bounds);
+ }
+ if (!skipUpdateTransform) {
+ matrixPool.return(worldTransform);
+ }
+ }
+ function updateTransformBackwards(target, parentTransform) {
+ const parent = target.parent;
+ if (parent) {
+ updateTransformBackwards(parent, parentTransform);
+ parent.updateLocalTransform();
+ parentTransform.append(parent.localTransform);
+ }
+ return parentTransform;
+ }
+
+ "use strict";
+ function multiplyHexColors(color1, color2) {
+ if (color1 === 16777215 || !color2)
+ return color2;
+ if (color2 === 16777215 || !color1)
+ return color1;
+ const r1 = color1 >> 16 & 255;
+ const g1 = color1 >> 8 & 255;
+ const b1 = color1 & 255;
+ const r2 = color2 >> 16 & 255;
+ const g2 = color2 >> 8 & 255;
+ const b2 = color2 & 255;
+ const r = r1 * r2 / 255 | 0;
+ const g = g1 * g2 / 255 | 0;
+ const b = b1 * b2 / 255 | 0;
+ return (r << 16) + (g << 8) + b;
+ }
+
+ "use strict";
+ const WHITE_BGR = 16777215;
+ function multiplyColors(localBGRColor, parentBGRColor) {
+ if (localBGRColor === WHITE_BGR) {
+ return parentBGRColor;
+ }
+ if (parentBGRColor === WHITE_BGR) {
+ return localBGRColor;
+ }
+ return multiplyHexColors(localBGRColor, parentBGRColor);
+ }
+
+ "use strict";
+ function bgr2rgb(color) {
+ return ((color & 255) << 16) + (color & 65280) + (color >> 16 & 255);
+ }
+ const getGlobalMixin = {
+ getGlobalAlpha(skipUpdate) {
+ if (skipUpdate) {
+ if (this.renderGroup) {
+ return this.renderGroup.worldAlpha;
+ }
+ if (this.parentRenderGroup) {
+ return this.parentRenderGroup.worldAlpha * this.alpha;
+ }
+ return this.alpha;
+ }
+ let alpha = this.alpha;
+ let current = this.parent;
+ while (current) {
+ alpha *= current.alpha;
+ current = current.parent;
+ }
+ return alpha;
+ },
+ getGlobalTransform(matrix = new Matrix(), skipUpdate) {
+ if (skipUpdate) {
+ return matrix.copyFrom(this.worldTransform);
+ }
+ this.updateLocalTransform();
+ const parentTransform = updateTransformBackwards(this, matrixPool.get().identity());
+ matrix.appendFrom(this.localTransform, parentTransform);
+ matrixPool.return(parentTransform);
+ return matrix;
+ },
+ getGlobalTint(skipUpdate) {
+ if (skipUpdate) {
+ if (this.renderGroup) {
+ return bgr2rgb(this.renderGroup.worldColor);
+ }
+ if (this.parentRenderGroup) {
+ return bgr2rgb(
+ multiplyColors(this.localColor, this.parentRenderGroup.worldColor)
+ );
+ }
+ return this.tint;
+ }
+ let color = this.localColor;
+ let parent = this.parent;
+ while (parent) {
+ color = multiplyColors(color, parent.localColor);
+ parent = parent.parent;
+ }
+ return bgr2rgb(color);
+ }
+ };
+
+ "use strict";
+ function getLocalBounds(target, bounds, relativeMatrix) {
+ bounds.clear();
+ relativeMatrix || (relativeMatrix = Matrix.IDENTITY);
+ _getLocalBounds(target, bounds, relativeMatrix, target, true);
+ if (!bounds.isValid) {
+ bounds.set(0, 0, 0, 0);
+ }
+ return bounds;
+ }
+ function _getLocalBounds(target, bounds, parentTransform, rootContainer, isRoot) {
+ var _a, _b;
+ let relativeTransform;
+ if (!isRoot) {
+ if (!target.visible || !target.measurable)
+ return;
+ target.updateLocalTransform();
+ const localTransform = target.localTransform;
+ relativeTransform = matrixPool.get();
+ relativeTransform.appendFrom(localTransform, parentTransform);
+ } else {
+ relativeTransform = matrixPool.get();
+ relativeTransform = parentTransform.copyTo(relativeTransform);
+ }
+ const parentBounds = bounds;
+ const preserveBounds = !!target.effects.length;
+ if (preserveBounds) {
+ bounds = boundsPool.get().clear();
+ }
+ if (target.boundsArea) {
+ bounds.addRect(target.boundsArea, relativeTransform);
+ } else {
+ if (target.renderPipeId) {
+ bounds.matrix = relativeTransform;
+ bounds.addBounds(target.bounds);
+ }
+ const children = target.children;
+ for (let i = 0; i < children.length; i++) {
+ _getLocalBounds(children[i], bounds, relativeTransform, rootContainer, false);
+ }
+ }
+ if (preserveBounds) {
+ for (let i = 0; i < target.effects.length; i++) {
+ (_b = (_a = target.effects[i]).addLocalBounds) == null ? void 0 : _b.call(_a, bounds, rootContainer);
+ }
+ parentBounds.addBounds(bounds, Matrix.IDENTITY);
+ boundsPool.return(bounds);
+ }
+ matrixPool.return(relativeTransform);
+ }
+
+ "use strict";
+ function checkChildrenDidChange(container, previousData) {
+ const children = container.children;
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ const uid = child.uid;
+ const didChange = (child._didViewChangeTick & 65535) << 16 | child._didContainerChangeTick & 65535;
+ const index = previousData.index;
+ if (previousData.data[index] !== uid || previousData.data[index + 1] !== didChange) {
+ previousData.data[previousData.index] = uid;
+ previousData.data[previousData.index + 1] = didChange;
+ previousData.didChange = true;
+ }
+ previousData.index = index + 2;
+ if (child.children.length) {
+ checkChildrenDidChange(child, previousData);
+ }
+ }
+ return previousData.didChange;
+ }
+
+ "use strict";
+ const tempMatrix$4 = new Matrix();
+ const measureMixin = {
+ _localBoundsCacheId: -1,
+ _localBoundsCacheData: null,
+ _setWidth(value, localWidth) {
+ const sign = Math.sign(this.scale.x) || 1;
+ if (localWidth !== 0) {
+ this.scale.x = value / localWidth * sign;
+ } else {
+ this.scale.x = sign;
+ }
+ },
+ _setHeight(value, localHeight) {
+ const sign = Math.sign(this.scale.y) || 1;
+ if (localHeight !== 0) {
+ this.scale.y = value / localHeight * sign;
+ } else {
+ this.scale.y = sign;
+ }
+ },
+ getLocalBounds() {
+ if (!this._localBoundsCacheData) {
+ this._localBoundsCacheData = {
+ data: [],
+ index: 1,
+ didChange: false,
+ localBounds: new Bounds()
+ };
+ }
+ const localBoundsCacheData = this._localBoundsCacheData;
+ localBoundsCacheData.index = 1;
+ localBoundsCacheData.didChange = false;
+ if (localBoundsCacheData.data[0] !== this._didViewChangeTick) {
+ localBoundsCacheData.didChange = true;
+ localBoundsCacheData.data[0] = this._didViewChangeTick;
+ }
+ checkChildrenDidChange(this, localBoundsCacheData);
+ if (localBoundsCacheData.didChange) {
+ getLocalBounds(this, localBoundsCacheData.localBounds, tempMatrix$4);
+ }
+ return localBoundsCacheData.localBounds;
+ },
+ getBounds(skipUpdate, bounds) {
+ return getGlobalBounds(this, skipUpdate, bounds || new Bounds());
+ }
+ };
+
+ "use strict";
+ const onRenderMixin = {
+ _onRender: null,
+ set onRender(func) {
+ const renderGroup = this.renderGroup || this.parentRenderGroup;
+ if (!func) {
+ if (this._onRender) {
+ renderGroup == null ? void 0 : renderGroup.removeOnRender(this);
+ }
+ this._onRender = null;
+ return;
+ }
+ if (!this._onRender) {
+ renderGroup == null ? void 0 : renderGroup.addOnRender(this);
+ }
+ this._onRender = func;
+ },
+ get onRender() {
+ return this._onRender;
+ }
+ };
+
+ "use strict";
+ const sortMixin = {
+ _zIndex: 0,
+ sortDirty: false,
+ sortableChildren: false,
+ get zIndex() {
+ return this._zIndex;
+ },
+ set zIndex(value) {
+ if (this._zIndex === value)
+ return;
+ this._zIndex = value;
+ this.depthOfChildModified();
+ },
+ depthOfChildModified() {
+ if (this.parent) {
+ this.parent.sortableChildren = true;
+ this.parent.sortDirty = true;
+ }
+ if (this.parentRenderGroup) {
+ this.parentRenderGroup.structureDidChange = true;
+ }
+ },
+ sortChildren() {
+ if (!this.sortDirty)
+ return;
+ this.sortDirty = false;
+ this.children.sort(sortChildren);
+ }
+ };
+ function sortChildren(a, b) {
+ return a._zIndex - b._zIndex;
+ }
+
+ "use strict";
+ const toLocalGlobalMixin = {
+ getGlobalPosition(point = new Point(), skipUpdate = false) {
+ if (this.parent) {
+ this.parent.toGlobal(this._position, point, skipUpdate);
+ } else {
+ point.x = this._position.x;
+ point.y = this._position.y;
+ }
+ return point;
+ },
+ toGlobal(position, point, skipUpdate = false) {
+ const globalMatrix = this.getGlobalTransform(matrixPool.get(), skipUpdate);
+ point = globalMatrix.apply(position, point);
+ matrixPool.return(globalMatrix);
+ return point;
+ },
+ toLocal(position, from, point, skipUpdate) {
+ if (from) {
+ position = from.toGlobal(position, point, skipUpdate);
+ }
+ const globalMatrix = this.getGlobalTransform(matrixPool.get(), skipUpdate);
+ point = globalMatrix.applyInverse(position, point);
+ matrixPool.return(globalMatrix);
+ return point;
+ }
+ };
+
+ "use strict";
+ class InstructionSet {
+ constructor() {
+ /** a unique id for this instruction set used through the renderer */
+ this.uid = uid$1("instructionSet");
+ /** the array of instructions */
+ this.instructions = [];
+ /** the actual size of the array (any instructions passed this should be ignored) */
+ this.instructionSize = 0;
+ this.renderables = [];
+ /** used by the garbage collector to track when the instruction set was last used */
+ this.gcTick = 0;
+ }
+ /** reset the instruction set so it can be reused set size back to 0 */
+ reset() {
+ this.instructionSize = 0;
+ }
+ /**
+ * Destroy the instruction set, clearing the instructions and renderables.
+ * @internal
+ */
+ destroy() {
+ this.instructions.length = 0;
+ this.renderables.length = 0;
+ this.renderPipes = null;
+ this.gcTick = 0;
+ }
+ /**
+ * Add an instruction to the set
+ * @param instruction - add an instruction to the set
+ */
+ add(instruction) {
+ this.instructions[this.instructionSize++] = instruction;
+ }
+ /**
+ * Log the instructions to the console (for debugging)
+ * @internal
+ */
+ log() {
+ this.instructions.length = this.instructionSize;
+ console.table(this.instructions, ["type", "action"]);
+ }
+ }
+
+ "use strict";
+ function nextPow2(v) {
+ v += v === 0 ? 1 : 0;
+ --v;
+ v |= v >>> 1;
+ v |= v >>> 2;
+ v |= v >>> 4;
+ v |= v >>> 8;
+ v |= v >>> 16;
+ return v + 1;
+ }
+ function isPow2(v) {
+ return !(v & v - 1) && !!v;
+ }
+ function log2(v) {
+ let r = (v > 65535 ? 1 : 0) << 4;
+ v >>>= r;
+ let shift = (v > 255 ? 1 : 0) << 3;
+ v >>>= shift;
+ r |= shift;
+ shift = (v > 15 ? 1 : 0) << 2;
+ v >>>= shift;
+ r |= shift;
+ shift = (v > 3 ? 1 : 0) << 1;
+ v >>>= shift;
+ r |= shift;
+ return r | v >> 1;
+ }
+
+ "use strict";
+ function definedProps(obj) {
+ const result = {};
+ for (const key in obj) {
+ if (obj[key] !== void 0) {
+ result[key] = obj[key];
+ }
+ }
+ return result;
+ }
+
+ "use strict";
+ var __defProp$1b = Object.defineProperty;
+ var __getOwnPropSymbols$1c = Object.getOwnPropertySymbols;
+ var __hasOwnProp$1c = Object.prototype.hasOwnProperty;
+ var __propIsEnum$1c = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$1b = (obj, key, value) => key in obj ? __defProp$1b(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$1b = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$1c.call(b, prop))
+ __defNormalProp$1b(a, prop, b[prop]);
+ if (__getOwnPropSymbols$1c)
+ for (var prop of __getOwnPropSymbols$1c(b)) {
+ if (__propIsEnum$1c.call(b, prop))
+ __defNormalProp$1b(a, prop, b[prop]);
+ }
+ return a;
+ };
+ const idHash$1 = /* @__PURE__ */ Object.create(null);
+ function createResourceIdFromString(value) {
+ const id = idHash$1[value];
+ if (id === void 0) {
+ idHash$1[value] = uid$1("resource");
+ }
+ return id;
+ }
+ const _TextureStyle = class _TextureStyle extends EventEmitter {
+ /**
+ * @param options - options for the style
+ */
+ constructor(options = {}) {
+ var _a, _b, _c, _d, _e, _f, _g;
+ super();
+ /** @internal */
+ this._resourceType = "textureSampler";
+ /** @internal */
+ this._touched = 0;
+ /**
+ * Specifies the maximum anisotropy value clamp used by the sampler.
+ * Note: Most implementations support {@link TextureStyle#maxAnisotropy} values in range
+ * between 1 and 16, inclusive. The used value of {@link TextureStyle#maxAnisotropy} will
+ * be clamped to the maximum value that the platform supports.
+ * @internal
+ */
+ this._maxAnisotropy = 1;
+ /**
+ * Has the style been destroyed?
+ * @readonly
+ */
+ this.destroyed = false;
+ options = __spreadValues$1b(__spreadValues$1b({}, _TextureStyle.defaultOptions), options);
+ this.addressMode = options.addressMode;
+ this.addressModeU = (_a = options.addressModeU) != null ? _a : this.addressModeU;
+ this.addressModeV = (_b = options.addressModeV) != null ? _b : this.addressModeV;
+ this.addressModeW = (_c = options.addressModeW) != null ? _c : this.addressModeW;
+ this.scaleMode = options.scaleMode;
+ this.magFilter = (_d = options.magFilter) != null ? _d : this.magFilter;
+ this.minFilter = (_e = options.minFilter) != null ? _e : this.minFilter;
+ this.mipmapFilter = (_f = options.mipmapFilter) != null ? _f : this.mipmapFilter;
+ this.lodMinClamp = options.lodMinClamp;
+ this.lodMaxClamp = options.lodMaxClamp;
+ this.compare = options.compare;
+ this.maxAnisotropy = (_g = options.maxAnisotropy) != null ? _g : 1;
+ }
+ set addressMode(value) {
+ this.addressModeU = value;
+ this.addressModeV = value;
+ this.addressModeW = value;
+ }
+ /** setting this will set wrapModeU,wrapModeV and wrapModeW all at once! */
+ get addressMode() {
+ return this.addressModeU;
+ }
+ set wrapMode(value) {
+ deprecation(v8_0_0, "TextureStyle.wrapMode is now TextureStyle.addressMode");
+ this.addressMode = value;
+ }
+ get wrapMode() {
+ return this.addressMode;
+ }
+ set scaleMode(value) {
+ this.magFilter = value;
+ this.minFilter = value;
+ this.mipmapFilter = value;
+ }
+ /** setting this will set magFilter,minFilter and mipmapFilter all at once! */
+ get scaleMode() {
+ return this.magFilter;
+ }
+ /** Specifies the maximum anisotropy value clamp used by the sampler. */
+ set maxAnisotropy(value) {
+ this._maxAnisotropy = Math.min(value, 16);
+ if (this._maxAnisotropy > 1) {
+ this.scaleMode = "linear";
+ }
+ }
+ get maxAnisotropy() {
+ return this._maxAnisotropy;
+ }
+ // TODO - move this to WebGL?
+ get _resourceId() {
+ return this._sharedResourceId || this._generateResourceId();
+ }
+ update() {
+ this.emit("change", this);
+ this._sharedResourceId = null;
+ }
+ _generateResourceId() {
+ const bigKey = `${this.addressModeU}-${this.addressModeV}-${this.addressModeW}-${this.magFilter}-${this.minFilter}-${this.mipmapFilter}-${this.lodMinClamp}-${this.lodMaxClamp}-${this.compare}-${this._maxAnisotropy}`;
+ this._sharedResourceId = createResourceIdFromString(bigKey);
+ return this._resourceId;
+ }
+ /** Destroys the style */
+ destroy() {
+ this.destroyed = true;
+ this.emit("destroy", this);
+ this.emit("change", this);
+ this.removeAllListeners();
+ }
+ };
+ /** default options for the style */
+ _TextureStyle.defaultOptions = {
+ addressMode: "clamp-to-edge",
+ scaleMode: "linear"
+ };
+ let TextureStyle = _TextureStyle;
+
+ "use strict";
+ var __defProp$1a = Object.defineProperty;
+ var __getOwnPropSymbols$1b = Object.getOwnPropertySymbols;
+ var __hasOwnProp$1b = Object.prototype.hasOwnProperty;
+ var __propIsEnum$1b = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$1a = (obj, key, value) => key in obj ? __defProp$1a(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$1a = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$1b.call(b, prop))
+ __defNormalProp$1a(a, prop, b[prop]);
+ if (__getOwnPropSymbols$1b)
+ for (var prop of __getOwnPropSymbols$1b(b)) {
+ if (__propIsEnum$1b.call(b, prop))
+ __defNormalProp$1a(a, prop, b[prop]);
+ }
+ return a;
+ };
+ const _TextureSource = class _TextureSource extends EventEmitter {
+ /**
+ * @param options - options for creating a new TextureSource
+ */
+ constructor(options = {}) {
+ var _a, _b, _c;
+ super();
+ this.options = options;
+ /** unique id for this Texture source */
+ this.uid = uid$1("textureSource");
+ /**
+ * The resource type used by this TextureSource. This is used by the bind groups to determine
+ * how to handle this resource.
+ * @internal
+ */
+ this._resourceType = "textureSource";
+ /**
+ * i unique resource id, used by the bind group systems.
+ * This can change if the texture is resized or its resource changes
+ * @internal
+ */
+ this._resourceId = uid$1("resource");
+ /**
+ * this is how the backends know how to upload this texture to the GPU
+ * It changes depending on the resource type. Classes that extend TextureSource
+ * should override this property.
+ * @internal
+ */
+ this.uploadMethodId = "unknown";
+ /** @internal */
+ this._resolution = 1;
+ /** the pixel width of this texture source. This is the REAL pure number, not accounting resolution */
+ this.pixelWidth = 1;
+ /** the pixel height of this texture source. This is the REAL pure number, not accounting resolution */
+ this.pixelHeight = 1;
+ /**
+ * the width of this texture source, accounting for resolution
+ * eg pixelWidth 200, resolution 2, then width will be 100
+ */
+ this.width = 1;
+ /**
+ * the height of this texture source, accounting for resolution
+ * eg pixelHeight 200, resolution 2, then height will be 100
+ */
+ this.height = 1;
+ /**
+ * The number of samples of a multisample texture. This is always 1 for non-multisample textures.
+ * To enable multisample for a texture, set antialias to true
+ * @internal
+ */
+ this.sampleCount = 1;
+ /** The number of mip levels to generate for this texture. this is overridden if autoGenerateMipmaps is true */
+ this.mipLevelCount = 1;
+ /**
+ * Should we auto generate mipmaps for this texture? This will automatically generate mipmaps
+ * for this texture when uploading to the GPU. Mipmapped textures take up more memory, but
+ * can look better when scaled down.
+ *
+ * For performance reasons, it is recommended to NOT use this with RenderTextures, as they are often updated every frame.
+ * If you do, make sure to call `updateMipmaps` after you update the texture.
+ */
+ this.autoGenerateMipmaps = false;
+ /** the format that the texture data has */
+ this.format = "rgba8unorm";
+ /** how many dimensions does this texture have? currently v8 only supports 2d */
+ this.dimension = "2d";
+ /**
+ * Only really affects RenderTextures.
+ * Should we use antialiasing for this texture. It will look better, but may impact performance as a
+ * Blit operation will be required to resolve the texture.
+ */
+ this.antialias = false;
+ /**
+ * Used by automatic texture Garbage Collection, stores last GC tick when it was bound
+ * @protected
+ */
+ this._touched = 0;
+ /**
+ * Used by the batcher to build texture batches. faster to have the variable here!
+ * @protected
+ */
+ this._batchTick = -1;
+ /**
+ * A temporary batch location for the texture batching. Here for performance reasons only!
+ * @protected
+ */
+ this._textureBindLocation = -1;
+ options = __spreadValues$1a(__spreadValues$1a({}, _TextureSource.defaultOptions), options);
+ this.label = (_a = options.label) != null ? _a : "";
+ this.resource = options.resource;
+ this.autoGarbageCollect = options.autoGarbageCollect;
+ this._resolution = options.resolution;
+ if (options.width) {
+ this.pixelWidth = options.width * this._resolution;
+ } else {
+ this.pixelWidth = this.resource ? (_b = this.resourceWidth) != null ? _b : 1 : 1;
+ }
+ if (options.height) {
+ this.pixelHeight = options.height * this._resolution;
+ } else {
+ this.pixelHeight = this.resource ? (_c = this.resourceHeight) != null ? _c : 1 : 1;
+ }
+ this.width = this.pixelWidth / this._resolution;
+ this.height = this.pixelHeight / this._resolution;
+ this.format = options.format;
+ this.dimension = options.dimensions;
+ this.mipLevelCount = options.mipLevelCount;
+ this.autoGenerateMipmaps = options.autoGenerateMipmaps;
+ this.sampleCount = options.sampleCount;
+ this.antialias = options.antialias;
+ this.alphaMode = options.alphaMode;
+ this.style = new TextureStyle(definedProps(options));
+ this.destroyed = false;
+ this._refreshPOT();
+ }
+ /** returns itself */
+ get source() {
+ return this;
+ }
+ /** the style of the texture */
+ get style() {
+ return this._style;
+ }
+ set style(value) {
+ var _a, _b;
+ if (this.style === value)
+ return;
+ (_a = this._style) == null ? void 0 : _a.off("change", this._onStyleChange, this);
+ this._style = value;
+ (_b = this._style) == null ? void 0 : _b.on("change", this._onStyleChange, this);
+ this._onStyleChange();
+ }
+ /** Specifies the maximum anisotropy value clamp used by the sampler. */
+ set maxAnisotropy(value) {
+ this._style.maxAnisotropy = value;
+ }
+ get maxAnisotropy() {
+ return this._style.maxAnisotropy;
+ }
+ /** setting this will set wrapModeU, wrapModeV and wrapModeW all at once! */
+ get addressMode() {
+ return this._style.addressMode;
+ }
+ set addressMode(value) {
+ this._style.addressMode = value;
+ }
+ /** setting this will set wrapModeU, wrapModeV and wrapModeW all at once! */
+ get repeatMode() {
+ return this._style.addressMode;
+ }
+ set repeatMode(value) {
+ this._style.addressMode = value;
+ }
+ /** Specifies the sampling behavior when the sample footprint is smaller than or equal to one texel. */
+ get magFilter() {
+ return this._style.magFilter;
+ }
+ set magFilter(value) {
+ this._style.magFilter = value;
+ }
+ /** Specifies the sampling behavior when the sample footprint is larger than one texel. */
+ get minFilter() {
+ return this._style.minFilter;
+ }
+ set minFilter(value) {
+ this._style.minFilter = value;
+ }
+ /** Specifies behavior for sampling between mipmap levels. */
+ get mipmapFilter() {
+ return this._style.mipmapFilter;
+ }
+ set mipmapFilter(value) {
+ this._style.mipmapFilter = value;
+ }
+ /** Specifies the minimum and maximum levels of detail, respectively, used internally when sampling a texture. */
+ get lodMinClamp() {
+ return this._style.lodMinClamp;
+ }
+ set lodMinClamp(value) {
+ this._style.lodMinClamp = value;
+ }
+ /** Specifies the minimum and maximum levels of detail, respectively, used internally when sampling a texture. */
+ get lodMaxClamp() {
+ return this._style.lodMaxClamp;
+ }
+ set lodMaxClamp(value) {
+ this._style.lodMaxClamp = value;
+ }
+ _onStyleChange() {
+ this.emit("styleChange", this);
+ }
+ /** call this if you have modified the texture outside of the constructor */
+ update() {
+ if (this.resource) {
+ const resolution = this._resolution;
+ const didResize = this.resize(this.resourceWidth / resolution, this.resourceHeight / resolution);
+ if (didResize)
+ return;
+ }
+ this.emit("update", this);
+ }
+ /** Destroys this texture source */
+ destroy() {
+ this.destroyed = true;
+ this.emit("destroy", this);
+ this.emit("change", this);
+ if (this._style) {
+ this._style.destroy();
+ this._style = null;
+ }
+ this.uploadMethodId = null;
+ this.resource = null;
+ this.removeAllListeners();
+ }
+ /**
+ * This will unload the Texture source from the GPU. This will free up the GPU memory
+ * As soon as it is required fore rendering, it will be re-uploaded.
+ */
+ unload() {
+ this._resourceId = uid$1("resource");
+ this.emit("change", this);
+ this.emit("unload", this);
+ }
+ /** the width of the resource. This is the REAL pure number, not accounting resolution */
+ get resourceWidth() {
+ const { resource } = this;
+ return resource.naturalWidth || resource.videoWidth || resource.displayWidth || resource.width;
+ }
+ /** the height of the resource. This is the REAL pure number, not accounting resolution */
+ get resourceHeight() {
+ const { resource } = this;
+ return resource.naturalHeight || resource.videoHeight || resource.displayHeight || resource.height;
+ }
+ /**
+ * the resolution of the texture. Changing this number, will not change the number of pixels in the actual texture
+ * but will the size of the texture when rendered.
+ *
+ * changing the resolution of this texture to 2 for example will make it appear twice as small when rendered (as pixel
+ * density will have increased)
+ */
+ get resolution() {
+ return this._resolution;
+ }
+ set resolution(resolution) {
+ if (this._resolution === resolution)
+ return;
+ this._resolution = resolution;
+ this.width = this.pixelWidth / resolution;
+ this.height = this.pixelHeight / resolution;
+ }
+ /**
+ * Resize the texture, this is handy if you want to use the texture as a render texture
+ * @param width - the new width of the texture
+ * @param height - the new height of the texture
+ * @param resolution - the new resolution of the texture
+ * @returns - if the texture was resized
+ */
+ resize(width, height, resolution) {
+ resolution || (resolution = this._resolution);
+ width || (width = this.width);
+ height || (height = this.height);
+ const newPixelWidth = Math.round(width * resolution);
+ const newPixelHeight = Math.round(height * resolution);
+ this.width = newPixelWidth / resolution;
+ this.height = newPixelHeight / resolution;
+ this._resolution = resolution;
+ if (this.pixelWidth === newPixelWidth && this.pixelHeight === newPixelHeight) {
+ return false;
+ }
+ this._refreshPOT();
+ this.pixelWidth = newPixelWidth;
+ this.pixelHeight = newPixelHeight;
+ this.emit("resize", this);
+ this._resourceId = uid$1("resource");
+ this.emit("change", this);
+ return true;
+ }
+ /**
+ * Lets the renderer know that this texture has been updated and its mipmaps should be re-generated.
+ * This is only important for RenderTexture instances, as standard Texture instances will have their
+ * mipmaps generated on upload. You should call this method after you make any change to the texture
+ *
+ * The reason for this is is can be quite expensive to update mipmaps for a texture. So by default,
+ * We want you, the developer to specify when this action should happen.
+ *
+ * Generally you don't want to have mipmaps generated on Render targets that are changed every frame,
+ */
+ updateMipmaps() {
+ if (this.autoGenerateMipmaps && this.mipLevelCount > 1) {
+ this.emit("updateMipmaps", this);
+ }
+ }
+ set wrapMode(value) {
+ this._style.wrapMode = value;
+ }
+ get wrapMode() {
+ return this._style.wrapMode;
+ }
+ set scaleMode(value) {
+ this._style.scaleMode = value;
+ }
+ /** setting this will set magFilter,minFilter and mipmapFilter all at once! */
+ get scaleMode() {
+ return this._style.scaleMode;
+ }
+ /**
+ * Refresh check for isPowerOfTwo texture based on size
+ * @private
+ */
+ _refreshPOT() {
+ this.isPowerOfTwo = isPow2(this.pixelWidth) && isPow2(this.pixelHeight);
+ }
+ static test(_resource) {
+ throw new Error("Unimplemented");
+ }
+ };
+ /** The default options used when creating a new TextureSource. override these to add your own defaults */
+ _TextureSource.defaultOptions = {
+ resolution: 1,
+ format: "bgra8unorm",
+ alphaMode: "premultiply-alpha-on-upload",
+ dimensions: "2d",
+ mipLevelCount: 1,
+ autoGenerateMipmaps: false,
+ sampleCount: 1,
+ antialias: false,
+ autoGarbageCollect: false
+ };
+ let TextureSource = _TextureSource;
+
+ "use strict";
+ const ux = [1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1];
+ const uy = [0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1];
+ const vx = [0, -1, -1, -1, 0, 1, 1, 1, 0, 1, 1, 1, 0, -1, -1, -1];
+ const vy = [1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, 1, 0, -1];
+ const rotationCayley = [];
+ const rotationMatrices = [];
+ const signum = Math.sign;
+ function init() {
+ for (let i = 0; i < 16; i++) {
+ const row = [];
+ rotationCayley.push(row);
+ for (let j = 0; j < 16; j++) {
+ const _ux = signum(ux[i] * ux[j] + vx[i] * uy[j]);
+ const _uy = signum(uy[i] * ux[j] + vy[i] * uy[j]);
+ const _vx = signum(ux[i] * vx[j] + vx[i] * vy[j]);
+ const _vy = signum(uy[i] * vx[j] + vy[i] * vy[j]);
+ for (let k = 0; k < 16; k++) {
+ if (ux[k] === _ux && uy[k] === _uy && vx[k] === _vx && vy[k] === _vy) {
+ row.push(k);
+ break;
+ }
+ }
+ }
+ }
+ for (let i = 0; i < 16; i++) {
+ const mat = new Matrix();
+ mat.set(ux[i], uy[i], vx[i], vy[i], 0, 0);
+ rotationMatrices.push(mat);
+ }
+ }
+ init();
+ const groupD8 = {
+ /**
+ * | Rotation | Direction |
+ * |----------|-----------|
+ * | 0° | East |
+ * @group groupD8
+ * @type {GD8Symmetry}
+ */
+ E: 0,
+ /**
+ * | Rotation | Direction |
+ * |----------|-----------|
+ * | 45°↻ | Southeast |
+ * @group groupD8
+ * @type {GD8Symmetry}
+ */
+ SE: 1,
+ /**
+ * | Rotation | Direction |
+ * |----------|-----------|
+ * | 90°↻ | South |
+ * @group groupD8
+ * @type {GD8Symmetry}
+ */
+ S: 2,
+ /**
+ * | Rotation | Direction |
+ * |----------|-----------|
+ * | 135°↻ | Southwest |
+ * @group groupD8
+ * @type {GD8Symmetry}
+ */
+ SW: 3,
+ /**
+ * | Rotation | Direction |
+ * |----------|-----------|
+ * | 180° | West |
+ * @group groupD8
+ * @type {GD8Symmetry}
+ */
+ W: 4,
+ /**
+ * | Rotation | Direction |
+ * |-------------|--------------|
+ * | -135°/225°↻ | Northwest |
+ * @group groupD8
+ * @type {GD8Symmetry}
+ */
+ NW: 5,
+ /**
+ * | Rotation | Direction |
+ * |-------------|--------------|
+ * | -90°/270°↻ | North |
+ * @group groupD8
+ * @type {GD8Symmetry}
+ */
+ N: 6,
+ /**
+ * | Rotation | Direction |
+ * |-------------|--------------|
+ * | -45°/315°↻ | Northeast |
+ * @group groupD8
+ * @type {GD8Symmetry}
+ */
+ NE: 7,
+ /**
+ * Reflection about Y-axis.
+ * @group groupD8
+ * @type {GD8Symmetry}
+ */
+ MIRROR_VERTICAL: 8,
+ /**
+ * Reflection about the main diagonal.
+ * @group groupD8
+ * @type {GD8Symmetry}
+ */
+ MAIN_DIAGONAL: 10,
+ /**
+ * Reflection about X-axis.
+ * @group groupD8
+ * @type {GD8Symmetry}
+ */
+ MIRROR_HORIZONTAL: 12,
+ /**
+ * Reflection about reverse diagonal.
+ * @group groupD8
+ * @type {GD8Symmetry}
+ */
+ REVERSE_DIAGONAL: 14,
+ /**
+ * @group groupD8
+ * @param {GD8Symmetry} ind - sprite rotation angle.
+ * @returns {GD8Symmetry} The X-component of the U-axis
+ * after rotating the axes.
+ */
+ uX: (ind) => ux[ind],
+ /**
+ * @group groupD8
+ * @param {GD8Symmetry} ind - sprite rotation angle.
+ * @returns {GD8Symmetry} The Y-component of the U-axis
+ * after rotating the axes.
+ */
+ uY: (ind) => uy[ind],
+ /**
+ * @group groupD8
+ * @param {GD8Symmetry} ind - sprite rotation angle.
+ * @returns {GD8Symmetry} The X-component of the V-axis
+ * after rotating the axes.
+ */
+ vX: (ind) => vx[ind],
+ /**
+ * @group groupD8
+ * @param {GD8Symmetry} ind - sprite rotation angle.
+ * @returns {GD8Symmetry} The Y-component of the V-axis
+ * after rotating the axes.
+ */
+ vY: (ind) => vy[ind],
+ /**
+ * @group groupD8
+ * @param {GD8Symmetry} rotation - symmetry whose opposite
+ * is needed. Only rotations have opposite symmetries while
+ * reflections don't.
+ * @returns {GD8Symmetry} The opposite symmetry of `rotation`
+ */
+ inv: (rotation) => {
+ if (rotation & 8) {
+ return rotation & 15;
+ }
+ return -rotation & 7;
+ },
+ /**
+ * Composes the two D8 operations.
+ *
+ * Taking `^` as reflection:
+ *
+ * | | E=0 | S=2 | W=4 | N=6 | E^=8 | S^=10 | W^=12 | N^=14 |
+ * |-------|-----|-----|-----|-----|------|-------|-------|-------|
+ * | E=0 | E | S | W | N | E^ | S^ | W^ | N^ |
+ * | S=2 | S | W | N | E | S^ | W^ | N^ | E^ |
+ * | W=4 | W | N | E | S | W^ | N^ | E^ | S^ |
+ * | N=6 | N | E | S | W | N^ | E^ | S^ | W^ |
+ * | E^=8 | E^ | N^ | W^ | S^ | E | N | W | S |
+ * | S^=10 | S^ | E^ | N^ | W^ | S | E | N | W |
+ * | W^=12 | W^ | S^ | E^ | N^ | W | S | E | N |
+ * | N^=14 | N^ | W^ | S^ | E^ | N | W | S | E |
+ *
+ * [This is a Cayley table]{@link https://en.wikipedia.org/wiki/Cayley_table}
+ * @group groupD8
+ * @param {GD8Symmetry} rotationSecond - Second operation, which
+ * is the row in the above cayley table.
+ * @param {GD8Symmetry} rotationFirst - First operation, which
+ * is the column in the above cayley table.
+ * @returns {GD8Symmetry} Composed operation
+ */
+ add: (rotationSecond, rotationFirst) => rotationCayley[rotationSecond][rotationFirst],
+ /**
+ * Reverse of `add`.
+ * @group groupD8
+ * @param {GD8Symmetry} rotationSecond - Second operation
+ * @param {GD8Symmetry} rotationFirst - First operation
+ * @returns {GD8Symmetry} Result
+ */
+ sub: (rotationSecond, rotationFirst) => rotationCayley[rotationSecond][groupD8.inv(rotationFirst)],
+ /**
+ * Adds 180 degrees to rotation, which is a commutative
+ * operation.
+ * @group groupD8
+ * @param {number} rotation - The number to rotate.
+ * @returns {number} Rotated number
+ */
+ rotate180: (rotation) => rotation ^ 4,
+ /**
+ * Checks if the rotation angle is vertical, i.e. south
+ * or north. It doesn't work for reflections.
+ * @group groupD8
+ * @param {GD8Symmetry} rotation - The number to check.
+ * @returns {boolean} Whether or not the direction is vertical
+ */
+ isVertical: (rotation) => (rotation & 3) === 2,
+ // rotation % 4 === 2
+ /**
+ * Approximates the vector `V(dx,dy)` into one of the
+ * eight directions provided by `groupD8`.
+ * @group groupD8
+ * @param {number} dx - X-component of the vector
+ * @param {number} dy - Y-component of the vector
+ * @returns {GD8Symmetry} Approximation of the vector into
+ * one of the eight symmetries.
+ */
+ byDirection: (dx, dy) => {
+ if (Math.abs(dx) * 2 <= Math.abs(dy)) {
+ if (dy >= 0) {
+ return groupD8.S;
+ }
+ return groupD8.N;
+ } else if (Math.abs(dy) * 2 <= Math.abs(dx)) {
+ if (dx > 0) {
+ return groupD8.E;
+ }
+ return groupD8.W;
+ } else if (dy > 0) {
+ if (dx > 0) {
+ return groupD8.SE;
+ }
+ return groupD8.SW;
+ } else if (dx > 0) {
+ return groupD8.NE;
+ }
+ return groupD8.NW;
+ },
+ /**
+ * Helps sprite to compensate texture packer rotation.
+ * @group groupD8
+ * @param {Matrix} matrix - sprite world matrix
+ * @param {GD8Symmetry} rotation - The rotation factor to use.
+ * @param {number} tx - sprite anchoring
+ * @param {number} ty - sprite anchoring
+ */
+ matrixAppendRotationInv: (matrix, rotation, tx = 0, ty = 0) => {
+ const mat = rotationMatrices[groupD8.inv(rotation)];
+ mat.tx = tx;
+ mat.ty = ty;
+ matrix.append(mat);
+ },
+ /**
+ * Transforms rectangle coordinates based on texture packer rotation.
+ * Used when texture atlas pages are rotated and coordinates need to be adjusted.
+ * @group groupD8
+ * @param {RectangleLike} rect - Rectangle with original coordinates to transform
+ * @param {RectangleLike} sourceFrame - Source texture frame (includes offset and dimensions)
+ * @param {GD8Symmetry} rotation - The groupD8 rotation value
+ * @param {Rectangle} out - Rectangle to store the result
+ * @returns {Rectangle} Transformed coordinates (includes source frame offset)
+ */
+ transformRectCoords: (rect, sourceFrame, rotation, out) => {
+ const { x, y, width, height } = rect;
+ const { x: frameX, y: frameY, width: frameWidth, height: frameHeight } = sourceFrame;
+ if (rotation === groupD8.E) {
+ out.set(x + frameX, y + frameY, width, height);
+ return out;
+ } else if (rotation === groupD8.S) {
+ return out.set(
+ frameWidth - y - height + frameX,
+ x + frameY,
+ height,
+ width
+ );
+ } else if (rotation === groupD8.W) {
+ return out.set(
+ frameWidth - x - width + frameX,
+ frameHeight - y - height + frameY,
+ width,
+ height
+ );
+ } else if (rotation === groupD8.N) {
+ return out.set(
+ y + frameX,
+ frameHeight - x - width + frameY,
+ height,
+ width
+ );
+ }
+ return out.set(x + frameX, y + frameY, width, height);
+ }
+ };
+
+ "use strict";
+ const NOOP = () => {
+ };
+
+ "use strict";
+ var __defProp$19 = Object.defineProperty;
+ var __defProps$r = Object.defineProperties;
+ var __getOwnPropDescs$r = Object.getOwnPropertyDescriptors;
+ var __getOwnPropSymbols$1a = Object.getOwnPropertySymbols;
+ var __hasOwnProp$1a = Object.prototype.hasOwnProperty;
+ var __propIsEnum$1a = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$19 = (obj, key, value) => key in obj ? __defProp$19(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$19 = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$1a.call(b, prop))
+ __defNormalProp$19(a, prop, b[prop]);
+ if (__getOwnPropSymbols$1a)
+ for (var prop of __getOwnPropSymbols$1a(b)) {
+ if (__propIsEnum$1a.call(b, prop))
+ __defNormalProp$19(a, prop, b[prop]);
+ }
+ return a;
+ };
+ var __spreadProps$r = (a, b) => __defProps$r(a, __getOwnPropDescs$r(b));
+ class BufferImageSource extends TextureSource {
+ constructor(options) {
+ const buffer = options.resource || new Float32Array(options.width * options.height * 4);
+ let format = options.format;
+ if (!format) {
+ if (buffer instanceof Float32Array) {
+ format = "rgba32float";
+ } else if (buffer instanceof Int32Array) {
+ format = "rgba32uint";
+ } else if (buffer instanceof Uint32Array) {
+ format = "rgba32uint";
+ } else if (buffer instanceof Int16Array) {
+ format = "rgba16uint";
+ } else if (buffer instanceof Uint16Array) {
+ format = "rgba16uint";
+ } else if (buffer instanceof Int8Array) {
+ format = "bgra8unorm";
+ } else {
+ format = "bgra8unorm";
+ }
+ }
+ super(__spreadProps$r(__spreadValues$19({}, options), {
+ resource: buffer,
+ format
+ }));
+ this.uploadMethodId = "buffer";
+ }
+ static test(resource) {
+ return resource instanceof Int8Array || resource instanceof Uint8Array || resource instanceof Uint8ClampedArray || resource instanceof Int16Array || resource instanceof Uint16Array || resource instanceof Int32Array || resource instanceof Uint32Array || resource instanceof Float32Array;
+ }
+ }
+ BufferImageSource.extension = ExtensionType.TextureSource;
+
+ "use strict";
+ const tempMat = new Matrix();
+ class TextureMatrix {
+ /**
+ * @param texture - observed texture
+ * @param clampMargin - Changes frame clamping, 0.5 by default. Use -0.5 for extra border.
+ */
+ constructor(texture, clampMargin) {
+ this.mapCoord = new Matrix();
+ this.uClampFrame = new Float32Array(4);
+ this.uClampOffset = new Float32Array(2);
+ this._textureID = -1;
+ this._updateID = 0;
+ this.clampOffset = 0;
+ if (typeof clampMargin === "undefined") {
+ this.clampMargin = texture.width < 10 ? 0 : 0.5;
+ } else {
+ this.clampMargin = clampMargin;
+ }
+ this.isSimple = false;
+ this.texture = texture;
+ }
+ /** Texture property. */
+ get texture() {
+ return this._texture;
+ }
+ set texture(value) {
+ var _a;
+ if (this.texture === value)
+ return;
+ (_a = this._texture) == null ? void 0 : _a.removeListener("update", this.update, this);
+ this._texture = value;
+ this._texture.addListener("update", this.update, this);
+ this.update();
+ }
+ /**
+ * Multiplies uvs array to transform
+ * @param uvs - mesh uvs
+ * @param [out=uvs] - output
+ * @returns - output
+ */
+ multiplyUvs(uvs, out) {
+ if (out === void 0) {
+ out = uvs;
+ }
+ const mat = this.mapCoord;
+ for (let i = 0; i < uvs.length; i += 2) {
+ const x = uvs[i];
+ const y = uvs[i + 1];
+ out[i] = x * mat.a + y * mat.c + mat.tx;
+ out[i + 1] = x * mat.b + y * mat.d + mat.ty;
+ }
+ return out;
+ }
+ /**
+ * Updates matrices if texture was changed
+ * @returns - whether or not it was updated
+ */
+ update() {
+ const tex = this._texture;
+ this._updateID++;
+ const uvs = tex.uvs;
+ this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0);
+ const orig = tex.orig;
+ const trim = tex.trim;
+ if (trim) {
+ tempMat.set(
+ orig.width / trim.width,
+ 0,
+ 0,
+ orig.height / trim.height,
+ -trim.x / trim.width,
+ -trim.y / trim.height
+ );
+ this.mapCoord.append(tempMat);
+ }
+ const texBase = tex.source;
+ const frame = this.uClampFrame;
+ const margin = this.clampMargin / texBase._resolution;
+ const offset = this.clampOffset / texBase._resolution;
+ frame[0] = (tex.frame.x + margin + offset) / texBase.width;
+ frame[1] = (tex.frame.y + margin + offset) / texBase.height;
+ frame[2] = (tex.frame.x + tex.frame.width - margin + offset) / texBase.width;
+ frame[3] = (tex.frame.y + tex.frame.height - margin + offset) / texBase.height;
+ this.uClampOffset[0] = this.clampOffset / texBase.pixelWidth;
+ this.uClampOffset[1] = this.clampOffset / texBase.pixelHeight;
+ this.isSimple = tex.frame.width === texBase.width && tex.frame.height === texBase.height && tex.rotate === 0;
+ return true;
+ }
+ }
+
+ "use strict";
+ class Texture extends EventEmitter {
+ /**
+ * @param {TextureOptions} options - Options for the texture
+ */
+ constructor({
+ source,
+ label,
+ frame,
+ orig,
+ trim,
+ defaultAnchor,
+ defaultBorders,
+ rotate,
+ dynamic
+ } = {}) {
+ var _a;
+ super();
+ /** unique id for this texture */
+ this.uid = uid$1("texture");
+ /** A uvs object based on the given frame and the texture source */
+ this.uvs = { x0: 0, y0: 0, x1: 0, y1: 0, x2: 0, y2: 0, x3: 0, y3: 0 };
+ /**
+ * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering,
+ * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases)
+ */
+ this.frame = new Rectangle();
+ /**
+ * Does this Texture have any frame data assigned to it?
+ *
+ * This mode is enabled automatically if no frame was passed inside constructor.
+ *
+ * In this mode texture is subscribed to baseTexture events, and fires `update` on any change.
+ *
+ * Beware, after loading or resize of baseTexture event can fired two times!
+ * If you want more control, subscribe on baseTexture itself.
+ * @example
+ * texture.on('update', () => {});
+ */
+ this.noFrame = false;
+ /**
+ * Set to true if you plan on modifying the uvs of this texture.
+ * When this is the case, sprites and other objects using the texture will
+ * make sure to listen for changes to the uvs and update their vertices accordingly.
+ */
+ this.dynamic = false;
+ /** is it a texture? yes! used for type checking */
+ this.isTexture = true;
+ this.label = label;
+ this.source = (_a = source == null ? void 0 : source.source) != null ? _a : new TextureSource();
+ this.noFrame = !frame;
+ if (frame) {
+ this.frame.copyFrom(frame);
+ } else {
+ const { width, height } = this._source;
+ this.frame.width = width;
+ this.frame.height = height;
+ }
+ this.orig = orig || this.frame;
+ this.trim = trim;
+ this.rotate = rotate != null ? rotate : 0;
+ this.defaultAnchor = defaultAnchor;
+ this.defaultBorders = defaultBorders;
+ this.destroyed = false;
+ this.dynamic = dynamic || false;
+ this.updateUvs();
+ }
+ set source(value) {
+ if (this._source) {
+ this._source.off("resize", this.update, this);
+ }
+ this._source = value;
+ value.on("resize", this.update, this);
+ this.emit("update", this);
+ }
+ /** the underlying source of the texture (equivalent of baseTexture in v7) */
+ get source() {
+ return this._source;
+ }
+ /** returns a TextureMatrix instance for this texture. By default, that object is not created because its heavy. */
+ get textureMatrix() {
+ if (!this._textureMatrix) {
+ this._textureMatrix = new TextureMatrix(this);
+ }
+ return this._textureMatrix;
+ }
+ /** The width of the Texture in pixels. */
+ get width() {
+ return this.orig.width;
+ }
+ /** The height of the Texture in pixels. */
+ get height() {
+ return this.orig.height;
+ }
+ /** Call this function when you have modified the frame of this texture. */
+ updateUvs() {
+ const { uvs, frame } = this;
+ const { width, height } = this._source;
+ const nX = frame.x / width;
+ const nY = frame.y / height;
+ const nW = frame.width / width;
+ const nH = frame.height / height;
+ let rotate = this.rotate;
+ if (rotate) {
+ const w2 = nW / 2;
+ const h2 = nH / 2;
+ const cX = nX + w2;
+ const cY = nY + h2;
+ rotate = groupD8.add(rotate, groupD8.NW);
+ uvs.x0 = cX + w2 * groupD8.uX(rotate);
+ uvs.y0 = cY + h2 * groupD8.uY(rotate);
+ rotate = groupD8.add(rotate, 2);
+ uvs.x1 = cX + w2 * groupD8.uX(rotate);
+ uvs.y1 = cY + h2 * groupD8.uY(rotate);
+ rotate = groupD8.add(rotate, 2);
+ uvs.x2 = cX + w2 * groupD8.uX(rotate);
+ uvs.y2 = cY + h2 * groupD8.uY(rotate);
+ rotate = groupD8.add(rotate, 2);
+ uvs.x3 = cX + w2 * groupD8.uX(rotate);
+ uvs.y3 = cY + h2 * groupD8.uY(rotate);
+ } else {
+ uvs.x0 = nX;
+ uvs.y0 = nY;
+ uvs.x1 = nX + nW;
+ uvs.y1 = nY;
+ uvs.x2 = nX + nW;
+ uvs.y2 = nY + nH;
+ uvs.x3 = nX;
+ uvs.y3 = nY + nH;
+ }
+ }
+ /**
+ * Destroys this texture
+ * @param destroySource - Destroy the source when the texture is destroyed.
+ */
+ destroy(destroySource = false) {
+ if (this._source) {
+ if (destroySource) {
+ this._source.destroy();
+ this._source = null;
+ }
+ }
+ this._textureMatrix = null;
+ this.destroyed = true;
+ this.emit("destroy", this);
+ this.removeAllListeners();
+ }
+ /**
+ * Call this if you have modified the `texture outside` of the constructor.
+ *
+ * If you have modified this texture's source, you must separately call `texture.source.update()` to see those changes.
+ */
+ update() {
+ if (this.noFrame) {
+ this.frame.width = this._source.width;
+ this.frame.height = this._source.height;
+ }
+ this.updateUvs();
+ this.emit("update", this);
+ }
+ /** @deprecated since 8.0.0 */
+ get baseTexture() {
+ deprecation(v8_0_0, "Texture.baseTexture is now Texture.source");
+ return this._source;
+ }
+ }
+ Texture.EMPTY = new Texture({
+ label: "EMPTY",
+ source: new TextureSource({
+ label: "EMPTY"
+ })
+ });
+ Texture.EMPTY.destroy = NOOP;
+ Texture.WHITE = new Texture({
+ source: new BufferImageSource({
+ resource: new Uint8Array([255, 255, 255, 255]),
+ width: 1,
+ height: 1,
+ alphaMode: "premultiply-alpha-on-upload",
+ label: "WHITE"
+ }),
+ label: "WHITE"
+ });
+ Texture.WHITE.destroy = NOOP;
+
+ "use strict";
+ var __defProp$18 = Object.defineProperty;
+ var __defProps$q = Object.defineProperties;
+ var __getOwnPropDescs$q = Object.getOwnPropertyDescriptors;
+ var __getOwnPropSymbols$19 = Object.getOwnPropertySymbols;
+ var __hasOwnProp$19 = Object.prototype.hasOwnProperty;
+ var __propIsEnum$19 = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$18 = (obj, key, value) => key in obj ? __defProp$18(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$18 = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$19.call(b, prop))
+ __defNormalProp$18(a, prop, b[prop]);
+ if (__getOwnPropSymbols$19)
+ for (var prop of __getOwnPropSymbols$19(b)) {
+ if (__propIsEnum$19.call(b, prop))
+ __defNormalProp$18(a, prop, b[prop]);
+ }
+ return a;
+ };
+ var __spreadProps$q = (a, b) => __defProps$q(a, __getOwnPropDescs$q(b));
+ let count = 0;
+ class TexturePoolClass {
+ /**
+ * @param textureOptions - options that will be passed to BaseRenderTexture constructor
+ * @param {SCALE_MODE} [textureOptions.scaleMode] - See {@link SCALE_MODE} for possible values.
+ */
+ constructor(textureOptions) {
+ this._poolKeyHash = /* @__PURE__ */ Object.create(null);
+ this._texturePool = {};
+ this.textureOptions = textureOptions || {};
+ this.enableFullScreen = false;
+ this.textureStyle = new TextureStyle(this.textureOptions);
+ }
+ /**
+ * Creates texture with params that were specified in pool constructor.
+ * @param pixelWidth - Width of texture in pixels.
+ * @param pixelHeight - Height of texture in pixels.
+ * @param antialias
+ */
+ createTexture(pixelWidth, pixelHeight, antialias) {
+ const textureSource = new TextureSource(__spreadProps$q(__spreadValues$18({}, this.textureOptions), {
+ width: pixelWidth,
+ height: pixelHeight,
+ resolution: 1,
+ antialias,
+ autoGarbageCollect: false
+ }));
+ return new Texture({
+ source: textureSource,
+ label: `texturePool_${count++}`
+ });
+ }
+ /**
+ * Gets a Power-of-Two render texture or fullScreen texture
+ * @param frameWidth - The minimum width of the render texture.
+ * @param frameHeight - The minimum height of the render texture.
+ * @param resolution - The resolution of the render texture.
+ * @param antialias
+ * @returns The new render texture.
+ */
+ getOptimalTexture(frameWidth, frameHeight, resolution = 1, antialias) {
+ let po2Width = Math.ceil(frameWidth * resolution - 1e-6);
+ let po2Height = Math.ceil(frameHeight * resolution - 1e-6);
+ po2Width = nextPow2(po2Width);
+ po2Height = nextPow2(po2Height);
+ const key = (po2Width << 17) + (po2Height << 1) + (antialias ? 1 : 0);
+ if (!this._texturePool[key]) {
+ this._texturePool[key] = [];
+ }
+ let texture = this._texturePool[key].pop();
+ if (!texture) {
+ texture = this.createTexture(po2Width, po2Height, antialias);
+ }
+ texture.source._resolution = resolution;
+ texture.source.width = po2Width / resolution;
+ texture.source.height = po2Height / resolution;
+ texture.source.pixelWidth = po2Width;
+ texture.source.pixelHeight = po2Height;
+ texture.frame.x = 0;
+ texture.frame.y = 0;
+ texture.frame.width = frameWidth;
+ texture.frame.height = frameHeight;
+ texture.updateUvs();
+ this._poolKeyHash[texture.uid] = key;
+ return texture;
+ }
+ /**
+ * Gets extra texture of the same size as input renderTexture
+ * @param texture - The texture to check what size it is.
+ * @param antialias - Whether to use antialias.
+ * @returns A texture that is a power of two
+ */
+ getSameSizeTexture(texture, antialias = false) {
+ const source = texture.source;
+ return this.getOptimalTexture(texture.width, texture.height, source._resolution, antialias);
+ }
+ /**
+ * Place a render texture back into the pool. Optionally reset the style of the texture to the default texture style.
+ * useful if you modified the style of the texture after getting it from the pool.
+ * @param renderTexture - The renderTexture to free
+ * @param resetStyle - Whether to reset the style of the texture to the default texture style
+ */
+ returnTexture(renderTexture, resetStyle = false) {
+ const key = this._poolKeyHash[renderTexture.uid];
+ if (resetStyle) {
+ renderTexture.source.style = this.textureStyle;
+ }
+ this._texturePool[key].push(renderTexture);
+ }
+ /**
+ * Clears the pool.
+ * @param destroyTextures - Destroy all stored textures.
+ */
+ clear(destroyTextures) {
+ destroyTextures = destroyTextures !== false;
+ if (destroyTextures) {
+ for (const i in this._texturePool) {
+ const textures = this._texturePool[i];
+ if (textures) {
+ for (let j = 0; j < textures.length; j++) {
+ textures[j].destroy(true);
+ }
+ }
+ }
+ }
+ this._texturePool = {};
+ }
+ }
+ const TexturePool = new TexturePoolClass();
+ GlobalResourceRegistry.register(TexturePool);
+
+ "use strict";
+ class RenderGroup {
+ constructor() {
+ this.renderPipeId = "renderGroup";
+ this.root = null;
+ this.canBundle = false;
+ this.renderGroupParent = null;
+ this.renderGroupChildren = [];
+ this.worldTransform = new Matrix();
+ this.worldColorAlpha = 4294967295;
+ this.worldColor = 16777215;
+ this.worldAlpha = 1;
+ // these updates are transform changes..
+ this.childrenToUpdate = /* @__PURE__ */ Object.create(null);
+ this.updateTick = 0;
+ this.gcTick = 0;
+ // these update are renderable changes..
+ this.childrenRenderablesToUpdate = { list: [], index: 0 };
+ // other
+ this.structureDidChange = true;
+ this.instructionSet = new InstructionSet();
+ this._onRenderContainers = [];
+ /**
+ * Indicates if the cached texture needs to be updated.
+ * @default true
+ */
+ this.textureNeedsUpdate = true;
+ /**
+ * Indicates if the container should be cached as a texture.
+ * @default false
+ */
+ this.isCachedAsTexture = false;
+ this._matrixDirty = 7;
+ }
+ init(root) {
+ this.root = root;
+ if (root._onRender)
+ this.addOnRender(root);
+ root.didChange = true;
+ const children = root.children;
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ child._updateFlags = 15;
+ this.addChild(child);
+ }
+ }
+ enableCacheAsTexture(options = {}) {
+ this.textureOptions = options;
+ this.isCachedAsTexture = true;
+ this.textureNeedsUpdate = true;
+ }
+ disableCacheAsTexture() {
+ this.isCachedAsTexture = false;
+ if (this.texture) {
+ TexturePool.returnTexture(this.texture, true);
+ this.texture = null;
+ }
+ }
+ updateCacheTexture() {
+ this.textureNeedsUpdate = true;
+ const cachedParent = this._parentCacheAsTextureRenderGroup;
+ if (cachedParent && !cachedParent.textureNeedsUpdate) {
+ cachedParent.updateCacheTexture();
+ }
+ }
+ reset() {
+ this.renderGroupChildren.length = 0;
+ for (const i in this.childrenToUpdate) {
+ const childrenAtDepth = this.childrenToUpdate[i];
+ childrenAtDepth.list.fill(null);
+ childrenAtDepth.index = 0;
+ }
+ this.childrenRenderablesToUpdate.index = 0;
+ this.childrenRenderablesToUpdate.list.fill(null);
+ this.root = null;
+ this.updateTick = 0;
+ this.structureDidChange = true;
+ this._onRenderContainers.length = 0;
+ this.renderGroupParent = null;
+ this.disableCacheAsTexture();
+ }
+ get localTransform() {
+ return this.root.localTransform;
+ }
+ addRenderGroupChild(renderGroupChild) {
+ if (renderGroupChild.renderGroupParent) {
+ renderGroupChild.renderGroupParent._removeRenderGroupChild(renderGroupChild);
+ }
+ renderGroupChild.renderGroupParent = this;
+ this.renderGroupChildren.push(renderGroupChild);
+ }
+ _removeRenderGroupChild(renderGroupChild) {
+ const index = this.renderGroupChildren.indexOf(renderGroupChild);
+ if (index > -1) {
+ this.renderGroupChildren.splice(index, 1);
+ }
+ renderGroupChild.renderGroupParent = null;
+ }
+ addChild(child) {
+ this.structureDidChange = true;
+ child.parentRenderGroup = this;
+ child.updateTick = -1;
+ if (child.parent === this.root) {
+ child.relativeRenderGroupDepth = 1;
+ } else {
+ child.relativeRenderGroupDepth = child.parent.relativeRenderGroupDepth + 1;
+ }
+ child.didChange = true;
+ this.onChildUpdate(child);
+ if (child.renderGroup) {
+ this.addRenderGroupChild(child.renderGroup);
+ return;
+ }
+ if (child._onRender)
+ this.addOnRender(child);
+ const children = child.children;
+ for (let i = 0; i < children.length; i++) {
+ this.addChild(children[i]);
+ }
+ }
+ removeChild(child) {
+ this.structureDidChange = true;
+ if (child._onRender) {
+ if (!child.renderGroup) {
+ this.removeOnRender(child);
+ }
+ }
+ child.parentRenderGroup = null;
+ if (child.renderGroup) {
+ this._removeRenderGroupChild(child.renderGroup);
+ return;
+ }
+ const children = child.children;
+ for (let i = 0; i < children.length; i++) {
+ this.removeChild(children[i]);
+ }
+ }
+ removeChildren(children) {
+ for (let i = 0; i < children.length; i++) {
+ this.removeChild(children[i]);
+ }
+ }
+ onChildUpdate(child) {
+ let childrenToUpdate = this.childrenToUpdate[child.relativeRenderGroupDepth];
+ if (!childrenToUpdate) {
+ childrenToUpdate = this.childrenToUpdate[child.relativeRenderGroupDepth] = {
+ index: 0,
+ list: []
+ };
+ }
+ childrenToUpdate.list[childrenToUpdate.index++] = child;
+ }
+ updateRenderable(renderable) {
+ if (renderable.globalDisplayStatus < 7)
+ return;
+ this.instructionSet.renderPipes[renderable.renderPipeId].updateRenderable(renderable);
+ renderable.didViewUpdate = false;
+ }
+ onChildViewUpdate(child) {
+ this.childrenRenderablesToUpdate.list[this.childrenRenderablesToUpdate.index++] = child;
+ }
+ get isRenderable() {
+ return this.root.localDisplayStatus === 7 && this.worldAlpha > 0;
+ }
+ /**
+ * adding a container to the onRender list will make sure the user function
+ * passed in to the user defined 'onRender` callBack
+ * @param container - the container to add to the onRender list
+ */
+ addOnRender(container) {
+ this._onRenderContainers.push(container);
+ }
+ removeOnRender(container) {
+ this._onRenderContainers.splice(this._onRenderContainers.indexOf(container), 1);
+ }
+ runOnRender(renderer) {
+ for (let i = 0; i < this._onRenderContainers.length; i++) {
+ this._onRenderContainers[i]._onRender(renderer);
+ }
+ }
+ destroy() {
+ this.disableCacheAsTexture();
+ this.renderGroupParent = null;
+ this.root = null;
+ this.childrenRenderablesToUpdate = null;
+ this.childrenToUpdate = null;
+ this.renderGroupChildren = null;
+ this._onRenderContainers = null;
+ this.instructionSet = null;
+ }
+ getChildren(out = []) {
+ const children = this.root.children;
+ for (let i = 0; i < children.length; i++) {
+ this._getChildren(children[i], out);
+ }
+ return out;
+ }
+ _getChildren(container, out = []) {
+ out.push(container);
+ if (container.renderGroup)
+ return out;
+ const children = container.children;
+ for (let i = 0; i < children.length; i++) {
+ this._getChildren(children[i], out);
+ }
+ return out;
+ }
+ invalidateMatrices() {
+ this._matrixDirty = 7;
+ }
+ /**
+ * Returns the inverse of the world transform matrix.
+ * @returns {Matrix} The inverse of the world transform matrix.
+ */
+ get inverseWorldTransform() {
+ if ((this._matrixDirty & 1) === 0)
+ return this._inverseWorldTransform;
+ this._matrixDirty &= ~1;
+ this._inverseWorldTransform || (this._inverseWorldTransform = new Matrix());
+ return this._inverseWorldTransform.copyFrom(this.worldTransform).invert();
+ }
+ /**
+ * Returns the inverse of the texture offset transform matrix.
+ * @returns {Matrix} The inverse of the texture offset transform matrix.
+ */
+ get textureOffsetInverseTransform() {
+ if ((this._matrixDirty & 2) === 0)
+ return this._textureOffsetInverseTransform;
+ this._matrixDirty &= ~2;
+ this._textureOffsetInverseTransform || (this._textureOffsetInverseTransform = new Matrix());
+ return this._textureOffsetInverseTransform.copyFrom(this.inverseWorldTransform).translate(
+ -this._textureBounds.x,
+ -this._textureBounds.y
+ );
+ }
+ /**
+ * Returns the inverse of the parent texture transform matrix.
+ * This is used to properly transform coordinates when rendering into cached textures.
+ * @returns {Matrix} The inverse of the parent texture transform matrix.
+ */
+ get inverseParentTextureTransform() {
+ if ((this._matrixDirty & 4) === 0)
+ return this._inverseParentTextureTransform;
+ this._matrixDirty &= ~4;
+ const parentCacheAsTexture = this._parentCacheAsTextureRenderGroup;
+ if (parentCacheAsTexture) {
+ this._inverseParentTextureTransform || (this._inverseParentTextureTransform = new Matrix());
+ return this._inverseParentTextureTransform.copyFrom(this.worldTransform).prepend(parentCacheAsTexture.inverseWorldTransform).translate(
+ -parentCacheAsTexture._textureBounds.x,
+ -parentCacheAsTexture._textureBounds.y
+ );
+ }
+ return this.worldTransform;
+ }
+ /**
+ * Returns a matrix that transforms coordinates to the correct coordinate space of the texture being rendered to.
+ * This is the texture offset inverse transform of the closest parent RenderGroup that is cached as a texture.
+ * @returns {Matrix | null} The transform matrix for the cached texture coordinate space,
+ * or null if no parent is cached as texture.
+ */
+ get cacheToLocalTransform() {
+ if (this.isCachedAsTexture) {
+ return this.textureOffsetInverseTransform;
+ }
+ if (!this._parentCacheAsTextureRenderGroup)
+ return null;
+ return this._parentCacheAsTextureRenderGroup.textureOffsetInverseTransform;
+ }
+ }
+
+ "use strict";
+ function assignWithIgnore(target, options, ignore = {}) {
+ for (const key in options) {
+ if (!ignore[key] && options[key] !== void 0) {
+ target[key] = options[key];
+ }
+ }
+ }
+
+ "use strict";
+ const defaultSkew = new ObservablePoint(null);
+ const defaultPivot = new ObservablePoint(null);
+ const defaultScale = new ObservablePoint(null, 1, 1);
+ const defaultOrigin = new ObservablePoint(null);
+ const UPDATE_COLOR = 1;
+ const UPDATE_BLEND = 2;
+ const UPDATE_VISIBLE = 4;
+ const UPDATE_TRANSFORM = 8;
+ class Container extends EventEmitter {
+ constructor(options = {}) {
+ var _a, _b;
+ super();
+ /**
+ * unique id for this container
+ * @internal
+ */
+ this.uid = uid$1("renderable");
+ /** @private */
+ this._updateFlags = 15;
+ // the render group this container owns
+ /** @private */
+ this.renderGroup = null;
+ // the render group this container belongs to
+ /** @private */
+ this.parentRenderGroup = null;
+ // the index of the container in the render group
+ /** @private */
+ this.parentRenderGroupIndex = 0;
+ // set to true if the container has changed. It is reset once the changes have been applied
+ // by the transform system
+ // its here to stop ensure that when things change, only one update gets registers with the transform system
+ /** @private */
+ this.didChange = false;
+ // same as above, but for the renderable
+ /** @private */
+ this.didViewUpdate = false;
+ // how deep is the container relative to its render group..
+ // unless the element is the root render group - it will be relative to its parent
+ /** @private */
+ this.relativeRenderGroupDepth = 0;
+ /**
+ * The array of children of this container. Each child must be a Container or extend from it.
+ *
+ * The array is read-only, but its contents can be modified using Container methods.
+ * @example
+ * ```ts
+ * // Access children
+ * const firstChild = container.children[0];
+ * const lastChild = container.children[container.children.length - 1];
+ * ```
+ * @readonly
+ * @see {@link Container#addChild} For adding children
+ * @see {@link Container#removeChild} For removing children
+ */
+ this.children = [];
+ /**
+ * The display object container that contains this display object.
+ * This represents the parent-child relationship in the display tree.
+ * @example
+ * ```ts
+ * // Basic parent access
+ * const parent = sprite.parent;
+ *
+ * // Walk up the tree
+ * let current = sprite;
+ * while (current.parent) {
+ * console.log('Level up:', current.parent.constructor.name);
+ * current = current.parent;
+ * }
+ * ```
+ * @readonly
+ * @see {@link Container#addChild} For adding to a parent
+ * @see {@link Container#removeChild} For removing from parent
+ */
+ this.parent = null;
+ // used internally for changing up the render order.. mainly for masks and filters
+ // TODO setting this should cause a rebuild??
+ /** @private */
+ this.includeInBuild = true;
+ /** @private */
+ this.measurable = true;
+ /** @private */
+ this.isSimple = true;
+ // / /////////////Transform related props//////////////
+ // used by the transform system to check if a container needs to be updated that frame
+ // if the tick matches the current transform system tick, it is not updated again
+ /** @internal */
+ this.updateTick = -1;
+ /**
+ * Current transform of the object based on local factors: position, scale, other stuff.
+ * This matrix represents the local transformation without any parent influence.
+ * @example
+ * ```ts
+ * // Basic transform access
+ * const localMatrix = sprite.localTransform;
+ * console.log(localMatrix.toString());
+ * ```
+ * @readonly
+ * @see {@link Container#worldTransform} For global transform
+ * @see {@link Container#groupTransform} For render group transform
+ */
+ this.localTransform = new Matrix();
+ /**
+ * The relative group transform is a transform relative to the render group it belongs too. It will include all parent
+ * transforms and up to the render group (think of it as kind of like a stage - but the stage can be nested).
+ * If this container is is self a render group matrix will be relative to its parent render group
+ * @readonly
+ * @advanced
+ */
+ this.relativeGroupTransform = new Matrix();
+ /**
+ * The group transform is a transform relative to the render group it belongs too.
+ * If this container is render group then this will be an identity matrix. other wise it
+ * will be the same as the relativeGroupTransform.
+ * Use this value when actually rendering things to the screen
+ * @readonly
+ * @advanced
+ */
+ this.groupTransform = this.relativeGroupTransform;
+ /**
+ * Whether this object has been destroyed. If true, the object should no longer be used.
+ * After an object is destroyed, all of its functionality is disabled and references are removed.
+ * @example
+ * ```ts
+ * // Cleanup with destroy
+ * sprite.destroy();
+ * console.log(sprite.destroyed); // true
+ * ```
+ * @default false
+ * @see {@link Container#destroy} For destroying objects
+ */
+ this.destroyed = false;
+ // transform data..
+ /**
+ * The coordinate of the object relative to the local coordinates of the parent.
+ * @internal
+ */
+ this._position = new ObservablePoint(this, 0, 0);
+ /**
+ * The scale factor of the object.
+ * @internal
+ */
+ this._scale = defaultScale;
+ /**
+ * The pivot point of the container that it rotates around.
+ * @internal
+ */
+ this._pivot = defaultPivot;
+ /**
+ * The origin point around which the container rotates and scales.
+ * Unlike pivot, changing origin will not move the container's position.
+ * @private
+ */
+ this._origin = defaultOrigin;
+ /**
+ * The skew amount, on the x and y axis.
+ * @internal
+ */
+ this._skew = defaultSkew;
+ /**
+ * The X-coordinate value of the normalized local X axis,
+ * the first column of the local transformation matrix without a scale.
+ * @internal
+ */
+ this._cx = 1;
+ /**
+ * The Y-coordinate value of the normalized local X axis,
+ * the first column of the local transformation matrix without a scale.
+ * @internal
+ */
+ this._sx = 0;
+ /**
+ * The X-coordinate value of the normalized local Y axis,
+ * the second column of the local transformation matrix without a scale.
+ * @internal
+ */
+ this._cy = 0;
+ /**
+ * The Y-coordinate value of the normalized local Y axis,
+ * the second column of the local transformation matrix without a scale.
+ * @internal
+ */
+ this._sy = 1;
+ /**
+ * The rotation amount.
+ * @internal
+ */
+ this._rotation = 0;
+ // / COLOR related props //////////////
+ // color stored as ABGR
+ /** @internal */
+ this.localColor = 16777215;
+ /** @internal */
+ this.localAlpha = 1;
+ /** @internal */
+ this.groupAlpha = 1;
+ // A
+ /** @internal */
+ this.groupColor = 16777215;
+ // BGR
+ /** @internal */
+ this.groupColorAlpha = 4294967295;
+ // ABGR
+ // / BLEND related props //////////////
+ /** @internal */
+ this.localBlendMode = "inherit";
+ /** @internal */
+ this.groupBlendMode = "normal";
+ // / VISIBILITY related props //////////////
+ // visibility
+ // 0b11
+ // first bit is visible, second bit is renderable
+ /**
+ * This property holds three bits: culled, visible, renderable
+ * the third bit represents culling (0 = culled, 1 = not culled) 0b100
+ * the second bit represents visibility (0 = not visible, 1 = visible) 0b010
+ * the first bit represents renderable (0 = not renderable, 1 = renderable) 0b001
+ * @internal
+ */
+ this.localDisplayStatus = 7;
+ // 0b11 | 0b10 | 0b01 | 0b00
+ /** @internal */
+ this.globalDisplayStatus = 7;
+ /**
+ * A value that increments each time the containe is modified
+ * eg children added, removed etc
+ * @ignore
+ */
+ this._didContainerChangeTick = 0;
+ /**
+ * A value that increments each time the container view is modified
+ * eg texture swap, geometry change etc
+ * @ignore
+ */
+ this._didViewChangeTick = 0;
+ /**
+ * property that tracks if the container transform has changed
+ * @ignore
+ */
+ this._didLocalTransformChangeId = -1;
+ this.effects = [];
+ assignWithIgnore(this, options, {
+ children: true,
+ parent: true,
+ effects: true
+ });
+ (_a = options.children) == null ? void 0 : _a.forEach((child) => this.addChild(child));
+ (_b = options.parent) == null ? void 0 : _b.addChild(this);
+ }
+ /**
+ * Mixes all enumerable properties and methods from a source object to Container.
+ * @param source - The source of properties and methods to mix in.
+ * @deprecated since 8.8.0
+ */
+ static mixin(source) {
+ deprecation("8.8.0", "Container.mixin is deprecated, please use extensions.mixin instead.");
+ extensions.mixin(Container, source);
+ }
+ // = 'default';
+ /**
+ * We now use the _didContainerChangeTick and _didViewChangeTick to track changes
+ * @deprecated since 8.2.6
+ * @ignore
+ */
+ set _didChangeId(value) {
+ this._didViewChangeTick = value >> 12 & 4095;
+ this._didContainerChangeTick = value & 4095;
+ }
+ /** @ignore */
+ get _didChangeId() {
+ return this._didContainerChangeTick & 4095 | (this._didViewChangeTick & 4095) << 12;
+ }
+ /**
+ * Adds one or more children to the container.
+ * The children will be rendered as part of this container's display list.
+ * @example
+ * ```ts
+ * // Add a single child
+ * container.addChild(sprite);
+ *
+ * // Add multiple children
+ * container.addChild(background, player, foreground);
+ *
+ * // Add with type checking
+ * const sprite = container.addChild(new Sprite(texture));
+ * sprite.tint = 'red';
+ * ```
+ * @param children - The Container(s) to add to the container
+ * @returns The first child that was added
+ * @see {@link Container#removeChild} For removing children
+ * @see {@link Container#addChildAt} For adding at specific index
+ */
+ addChild(...children) {
+ if (!this.allowChildren) {
+ deprecation(v8_0_0, "addChild: Only Containers will be allowed to add children in v8.0.0");
+ }
+ if (children.length > 1) {
+ for (let i = 0; i < children.length; i++) {
+ this.addChild(children[i]);
+ }
+ return children[0];
+ }
+ const child = children[0];
+ const renderGroup = this.renderGroup || this.parentRenderGroup;
+ if (child.parent === this) {
+ this.children.splice(this.children.indexOf(child), 1);
+ this.children.push(child);
+ if (renderGroup) {
+ renderGroup.structureDidChange = true;
+ }
+ return child;
+ }
+ if (child.parent) {
+ child.parent.removeChild(child);
+ }
+ this.children.push(child);
+ if (this.sortableChildren)
+ this.sortDirty = true;
+ child.parent = this;
+ child.didChange = true;
+ child._updateFlags = 15;
+ if (renderGroup) {
+ renderGroup.addChild(child);
+ }
+ this.emit("childAdded", child, this, this.children.length - 1);
+ child.emit("added", this);
+ this._didViewChangeTick++;
+ if (child._zIndex !== 0) {
+ child.depthOfChildModified();
+ }
+ return child;
+ }
+ /**
+ * Removes one or more children from the container.
+ * When removing multiple children, events will be triggered for each child in sequence.
+ * @example
+ * ```ts
+ * // Remove a single child
+ * const removed = container.removeChild(sprite);
+ *
+ * // Remove multiple children
+ * const bg = container.removeChild(background, player, userInterface);
+ *
+ * // Remove with type checking
+ * const sprite = container.removeChild(childSprite);
+ * sprite.texture = newTexture;
+ * ```
+ * @param children - The Container(s) to remove
+ * @returns The first child that was removed
+ * @see {@link Container#addChild} For adding children
+ * @see {@link Container#removeChildren} For removing multiple children
+ */
+ removeChild(...children) {
+ if (children.length > 1) {
+ for (let i = 0; i < children.length; i++) {
+ this.removeChild(children[i]);
+ }
+ return children[0];
+ }
+ const child = children[0];
+ const index = this.children.indexOf(child);
+ if (index > -1) {
+ this._didViewChangeTick++;
+ this.children.splice(index, 1);
+ if (this.renderGroup) {
+ this.renderGroup.removeChild(child);
+ } else if (this.parentRenderGroup) {
+ this.parentRenderGroup.removeChild(child);
+ }
+ if (child.parentRenderLayer) {
+ child.parentRenderLayer.detach(child);
+ }
+ child.parent = null;
+ this.emit("childRemoved", child, this, index);
+ child.emit("removed", this);
+ }
+ return child;
+ }
+ /** @ignore */
+ _onUpdate(point) {
+ if (point) {
+ if (point === this._skew) {
+ this._updateSkew();
+ }
+ }
+ this._didContainerChangeTick++;
+ if (this.didChange)
+ return;
+ this.didChange = true;
+ if (this.parentRenderGroup) {
+ this.parentRenderGroup.onChildUpdate(this);
+ }
+ }
+ set isRenderGroup(value) {
+ if (!!this.renderGroup === value)
+ return;
+ if (value) {
+ this.enableRenderGroup();
+ } else {
+ this.disableRenderGroup();
+ }
+ }
+ /**
+ * Returns true if this container is a render group.
+ * This means that it will be rendered as a separate pass, with its own set of instructions
+ * @advanced
+ */
+ get isRenderGroup() {
+ return !!this.renderGroup;
+ }
+ /**
+ * Calling this enables a render group for this container.
+ * This means it will be rendered as a separate set of instructions.
+ * The transform of the container will also be handled on the GPU rather than the CPU.
+ * @advanced
+ */
+ enableRenderGroup() {
+ if (this.renderGroup)
+ return;
+ const parentRenderGroup = this.parentRenderGroup;
+ parentRenderGroup == null ? void 0 : parentRenderGroup.removeChild(this);
+ this.renderGroup = BigPool.get(RenderGroup, this);
+ this.groupTransform = Matrix.IDENTITY;
+ parentRenderGroup == null ? void 0 : parentRenderGroup.addChild(this);
+ this._updateIsSimple();
+ }
+ /**
+ * This will disable the render group for this container.
+ * @advanced
+ */
+ disableRenderGroup() {
+ if (!this.renderGroup)
+ return;
+ const parentRenderGroup = this.parentRenderGroup;
+ parentRenderGroup == null ? void 0 : parentRenderGroup.removeChild(this);
+ BigPool.return(this.renderGroup);
+ this.renderGroup = null;
+ this.groupTransform = this.relativeGroupTransform;
+ parentRenderGroup == null ? void 0 : parentRenderGroup.addChild(this);
+ this._updateIsSimple();
+ }
+ /** @ignore */
+ _updateIsSimple() {
+ this.isSimple = !this.renderGroup && this.effects.length === 0;
+ }
+ /**
+ * Current transform of the object based on world (parent) factors.
+ *
+ * This matrix represents the absolute transformation in the scene graph.
+ * @example
+ * ```ts
+ * // Get world position
+ * const worldPos = container.worldTransform;
+ * console.log(`World position: (${worldPos.tx}, ${worldPos.ty})`);
+ * ```
+ * @readonly
+ * @see {@link Container#localTransform} For local space transform
+ */
+ get worldTransform() {
+ this._worldTransform || (this._worldTransform = new Matrix());
+ if (this.renderGroup) {
+ this._worldTransform.copyFrom(this.renderGroup.worldTransform);
+ } else if (this.parentRenderGroup) {
+ this._worldTransform.appendFrom(this.relativeGroupTransform, this.parentRenderGroup.worldTransform);
+ }
+ return this._worldTransform;
+ }
+ /**
+ * The position of the container on the x axis relative to the local coordinates of the parent.
+ *
+ * An alias to position.x
+ * @example
+ * ```ts
+ * // Basic position
+ * container.x = 100;
+ * ```
+ */
+ get x() {
+ return this._position.x;
+ }
+ set x(value) {
+ this._position.x = value;
+ }
+ /**
+ * The position of the container on the y axis relative to the local coordinates of the parent.
+ *
+ * An alias to position.y
+ * @example
+ * ```ts
+ * // Basic position
+ * container.y = 200;
+ * ```
+ */
+ get y() {
+ return this._position.y;
+ }
+ set y(value) {
+ this._position.y = value;
+ }
+ /**
+ * The coordinate of the object relative to the local coordinates of the parent.
+ * @example
+ * ```ts
+ * // Basic position setting
+ * container.position.set(100, 200);
+ * container.position.set(100); // Sets both x and y to 100
+ * // Using point data
+ * container.position = { x: 50, y: 75 };
+ * ```
+ * @since 4.0.0
+ */
+ get position() {
+ return this._position;
+ }
+ set position(value) {
+ this._position.copyFrom(value);
+ }
+ /**
+ * The rotation of the object in radians.
+ *
+ * > [!NOTE] 'rotation' and 'angle' have the same effect on a display object;
+ * > rotation is in radians, angle is in degrees.
+ * @example
+ * ```ts
+ * // Basic rotation
+ * container.rotation = Math.PI / 4; // 45 degrees
+ *
+ * // Convert from degrees
+ * const degrees = 45;
+ * container.rotation = degrees * Math.PI / 180;
+ *
+ * // Rotate around center
+ * container.pivot.set(container.width / 2, container.height / 2);
+ * container.rotation = Math.PI; // 180 degrees
+ *
+ * // Rotate around center with origin
+ * container.origin.set(container.width / 2, container.height / 2);
+ * container.rotation = Math.PI; // 180 degrees
+ * ```
+ */
+ get rotation() {
+ return this._rotation;
+ }
+ set rotation(value) {
+ if (this._rotation !== value) {
+ this._rotation = value;
+ this._onUpdate(this._skew);
+ }
+ }
+ /**
+ * The angle of the object in degrees.
+ *
+ * > [!NOTE] 'rotation' and 'angle' have the same effect on a display object;
+ * > rotation is in radians, angle is in degrees.
+ * @example
+ * ```ts
+ * // Basic angle rotation
+ * sprite.angle = 45; // 45 degrees
+ *
+ * // Rotate around center
+ * sprite.pivot.set(sprite.width / 2, sprite.height / 2);
+ * sprite.angle = 180; // Half rotation
+ *
+ * // Rotate around center with origin
+ * sprite.origin.set(sprite.width / 2, sprite.height / 2);
+ * sprite.angle = 180; // Half rotation
+ *
+ * // Reset rotation
+ * sprite.angle = 0;
+ * ```
+ */
+ get angle() {
+ return this.rotation * RAD_TO_DEG;
+ }
+ set angle(value) {
+ this.rotation = value * DEG_TO_RAD;
+ }
+ /**
+ * The center of rotation, scaling, and skewing for this display object in its local space.
+ * The `position` is the projection of `pivot` in the parent's local space.
+ *
+ * By default, the pivot is the origin (0, 0).
+ * @example
+ * ```ts
+ * // Rotate around center
+ * container.pivot.set(container.width / 2, container.height / 2);
+ * container.rotation = Math.PI; // Rotates around center
+ * ```
+ * @since 4.0.0
+ */
+ get pivot() {
+ if (this._pivot === defaultPivot) {
+ this._pivot = new ObservablePoint(this, 0, 0);
+ }
+ return this._pivot;
+ }
+ set pivot(value) {
+ if (this._pivot === defaultPivot) {
+ this._pivot = new ObservablePoint(this, 0, 0);
+ if (this._origin !== defaultOrigin) {
+ warn(`Setting both a pivot and origin on a Container is not recommended. This can lead to unexpected behavior if not handled carefully.`);
+ }
+ }
+ typeof value === "number" ? this._pivot.set(value) : this._pivot.copyFrom(value);
+ }
+ /**
+ * The skew factor for the object in radians. Skewing is a transformation that distorts
+ * the object by rotating it differently at each point, creating a non-uniform shape.
+ * @example
+ * ```ts
+ * // Basic skewing
+ * container.skew.set(0.5, 0); // Skew horizontally
+ * container.skew.set(0, 0.5); // Skew vertically
+ *
+ * // Skew with point data
+ * container.skew = { x: 0.3, y: 0.3 }; // Diagonal skew
+ *
+ * // Reset skew
+ * container.skew.set(0, 0);
+ *
+ * // Animate skew
+ * app.ticker.add(() => {
+ * // Create wave effect
+ * container.skew.x = Math.sin(Date.now() / 1000) * 0.3;
+ * });
+ *
+ * // Combine with rotation
+ * container.rotation = Math.PI / 4; // 45 degrees
+ * container.skew.set(0.2, 0.2); // Skew the rotated object
+ * ```
+ * @since 4.0.0
+ * @type {ObservablePoint} Point-like object with x/y properties in radians
+ * @default {x: 0, y: 0}
+ */
+ get skew() {
+ if (this._skew === defaultSkew) {
+ this._skew = new ObservablePoint(this, 0, 0);
+ }
+ return this._skew;
+ }
+ set skew(value) {
+ if (this._skew === defaultSkew) {
+ this._skew = new ObservablePoint(this, 0, 0);
+ }
+ this._skew.copyFrom(value);
+ }
+ /**
+ * The scale factors of this object along the local coordinate axes.
+ *
+ * The default scale is (1, 1).
+ * @example
+ * ```ts
+ * // Basic scaling
+ * container.scale.set(2, 2); // Scales to double size
+ * container.scale.set(2); // Scales uniformly to double size
+ * container.scale = 2; // Scales uniformly to double size
+ * // Scale to a specific width and height
+ * container.setSize(200, 100); // Sets width to 200 and height to 100
+ * ```
+ * @since 4.0.0
+ */
+ get scale() {
+ if (this._scale === defaultScale) {
+ this._scale = new ObservablePoint(this, 1, 1);
+ }
+ return this._scale;
+ }
+ set scale(value) {
+ if (this._scale === defaultScale) {
+ this._scale = new ObservablePoint(this, 0, 0);
+ }
+ if (typeof value === "string") {
+ value = parseFloat(value);
+ }
+ typeof value === "number" ? this._scale.set(value) : this._scale.copyFrom(value);
+ }
+ /**
+ * @experimental
+ * The origin point around which the container rotates and scales without affecting its position.
+ * Unlike pivot, changing the origin will not move the container's position.
+ * @example
+ * ```ts
+ * // Rotate around center point
+ * container.origin.set(container.width / 2, container.height / 2);
+ * container.rotation = Math.PI; // Rotates around center
+ *
+ * // Reset origin
+ * container.origin.set(0, 0);
+ * ```
+ */
+ get origin() {
+ if (this._origin === defaultOrigin) {
+ this._origin = new ObservablePoint(this, 0, 0);
+ }
+ return this._origin;
+ }
+ set origin(value) {
+ if (this._origin === defaultOrigin) {
+ this._origin = new ObservablePoint(this, 0, 0);
+ if (this._pivot !== defaultPivot) {
+ warn(`Setting both a pivot and origin on a Container is not recommended. This can lead to unexpected behavior if not handled carefully.`);
+ }
+ }
+ typeof value === "number" ? this._origin.set(value) : this._origin.copyFrom(value);
+ }
+ /**
+ * The width of the Container, setting this will actually modify the scale to achieve the value set.
+ * > [!NOTE] Changing the width will adjust the scale.x property of the container while maintaining its aspect ratio.
+ * > [!NOTE] If you want to set both width and height at the same time, use {@link Container#setSize}
+ * as it is more optimized by not recalculating the local bounds twice.
+ * @example
+ * ```ts
+ * // Basic width setting
+ * container.width = 100;
+ * // Optimized width setting
+ * container.setSize(100, 100);
+ * ```
+ */
+ get width() {
+ return Math.abs(this.scale.x * this.getLocalBounds().width);
+ }
+ set width(value) {
+ const localWidth = this.getLocalBounds().width;
+ this._setWidth(value, localWidth);
+ }
+ /**
+ * The height of the Container,
+ * > [!NOTE] Changing the height will adjust the scale.y property of the container while maintaining its aspect ratio.
+ * > [!NOTE] If you want to set both width and height at the same time, use {@link Container#setSize}
+ * as it is more optimized by not recalculating the local bounds twice.
+ * @example
+ * ```ts
+ * // Basic height setting
+ * container.height = 200;
+ * // Optimized height setting
+ * container.setSize(100, 200);
+ * ```
+ */
+ get height() {
+ return Math.abs(this.scale.y * this.getLocalBounds().height);
+ }
+ set height(value) {
+ const localHeight = this.getLocalBounds().height;
+ this._setHeight(value, localHeight);
+ }
+ /**
+ * Retrieves the size of the container as a [Size]{@link Size} object.
+ *
+ * This is faster than get the width and height separately.
+ * @example
+ * ```ts
+ * // Basic size retrieval
+ * const size = container.getSize();
+ * console.log(`Size: ${size.width}x${size.height}`);
+ *
+ * // Reuse existing size object
+ * const reuseSize = { width: 0, height: 0 };
+ * container.getSize(reuseSize);
+ * ```
+ * @param out - Optional object to store the size in.
+ * @returns The size of the container.
+ */
+ getSize(out) {
+ if (!out) {
+ out = {};
+ }
+ const bounds = this.getLocalBounds();
+ out.width = Math.abs(this.scale.x * bounds.width);
+ out.height = Math.abs(this.scale.y * bounds.height);
+ return out;
+ }
+ /**
+ * Sets the size of the container to the specified width and height.
+ * This is more efficient than setting width and height separately as it only recalculates bounds once.
+ * @example
+ * ```ts
+ * // Basic size setting
+ * container.setSize(100, 200);
+ *
+ * // Set uniform size
+ * container.setSize(100); // Sets both width and height to 100
+ * ```
+ * @param value - This can be either a number or a [Size]{@link Size} object.
+ * @param height - The height to set. Defaults to the value of `width` if not provided.
+ */
+ setSize(value, height) {
+ var _a;
+ const size = this.getLocalBounds();
+ if (typeof value === "object") {
+ height = (_a = value.height) != null ? _a : value.width;
+ value = value.width;
+ } else {
+ height != null ? height : height = value;
+ }
+ value !== void 0 && this._setWidth(value, size.width);
+ height !== void 0 && this._setHeight(height, size.height);
+ }
+ /** Called when the skew or the rotation changes. */
+ _updateSkew() {
+ const rotation = this._rotation;
+ const skew = this._skew;
+ this._cx = Math.cos(rotation + skew._y);
+ this._sx = Math.sin(rotation + skew._y);
+ this._cy = -Math.sin(rotation - skew._x);
+ this._sy = Math.cos(rotation - skew._x);
+ }
+ /**
+ * Updates the transform properties of the container.
+ * Allows partial updates of transform properties for optimized manipulation.
+ * @example
+ * ```ts
+ * // Basic transform update
+ * container.updateTransform({
+ * x: 100,
+ * y: 200,
+ * rotation: Math.PI / 4
+ * });
+ *
+ * // Scale and rotate around center
+ * sprite.updateTransform({
+ * pivotX: sprite.width / 2,
+ * pivotY: sprite.height / 2,
+ * scaleX: 2,
+ * scaleY: 2,
+ * rotation: Math.PI
+ * });
+ *
+ * // Update position only
+ * button.updateTransform({
+ * x: button.x + 10, // Move right
+ * y: button.y // Keep same y
+ * });
+ * ```
+ * @param opts - Transform options to update
+ * @param opts.x - The x position
+ * @param opts.y - The y position
+ * @param opts.scaleX - The x-axis scale factor
+ * @param opts.scaleY - The y-axis scale factor
+ * @param opts.rotation - The rotation in radians
+ * @param opts.skewX - The x-axis skew factor
+ * @param opts.skewY - The y-axis skew factor
+ * @param opts.pivotX - The x-axis pivot point
+ * @param opts.pivotY - The y-axis pivot point
+ * @returns This container, for chaining
+ * @see {@link Container#setFromMatrix} For matrix-based transforms
+ * @see {@link Container#position} For direct position access
+ */
+ updateTransform(opts) {
+ this.position.set(
+ typeof opts.x === "number" ? opts.x : this.position.x,
+ typeof opts.y === "number" ? opts.y : this.position.y
+ );
+ this.scale.set(
+ typeof opts.scaleX === "number" ? opts.scaleX || 1 : this.scale.x,
+ typeof opts.scaleY === "number" ? opts.scaleY || 1 : this.scale.y
+ );
+ this.rotation = typeof opts.rotation === "number" ? opts.rotation : this.rotation;
+ this.skew.set(
+ typeof opts.skewX === "number" ? opts.skewX : this.skew.x,
+ typeof opts.skewY === "number" ? opts.skewY : this.skew.y
+ );
+ this.pivot.set(
+ typeof opts.pivotX === "number" ? opts.pivotX : this.pivot.x,
+ typeof opts.pivotY === "number" ? opts.pivotY : this.pivot.y
+ );
+ this.origin.set(
+ typeof opts.originX === "number" ? opts.originX : this.origin.x,
+ typeof opts.originY === "number" ? opts.originY : this.origin.y
+ );
+ return this;
+ }
+ /**
+ * Updates the local transform properties by decomposing the given matrix.
+ * Extracts position, scale, rotation, and skew from a transformation matrix.
+ * @example
+ * ```ts
+ * // Basic matrix transform
+ * const matrix = new Matrix()
+ * .translate(100, 100)
+ * .rotate(Math.PI / 4)
+ * .scale(2, 2);
+ *
+ * container.setFromMatrix(matrix);
+ *
+ * // Copy transform from another container
+ * const source = new Container();
+ * source.position.set(100, 100);
+ * source.rotation = Math.PI / 2;
+ *
+ * target.setFromMatrix(source.localTransform);
+ *
+ * // Reset transform
+ * container.setFromMatrix(Matrix.IDENTITY);
+ * ```
+ * @param matrix - The matrix to use for updating the transform
+ * @see {@link Container#updateTransform} For property-based updates
+ * @see {@link Matrix#decompose} For matrix decomposition details
+ */
+ setFromMatrix(matrix) {
+ matrix.decompose(this);
+ }
+ /** Updates the local transform. */
+ updateLocalTransform() {
+ const localTransformChangeId = this._didContainerChangeTick;
+ if (this._didLocalTransformChangeId === localTransformChangeId)
+ return;
+ this._didLocalTransformChangeId = localTransformChangeId;
+ const lt = this.localTransform;
+ const scale = this._scale;
+ const pivot = this._pivot;
+ const origin = this._origin;
+ const position = this._position;
+ const sx = scale._x;
+ const sy = scale._y;
+ const px = pivot._x;
+ const py = pivot._y;
+ const ox = -origin._x;
+ const oy = -origin._y;
+ lt.a = this._cx * sx;
+ lt.b = this._sx * sx;
+ lt.c = this._cy * sy;
+ lt.d = this._sy * sy;
+ lt.tx = position._x - (px * lt.a + py * lt.c) + (ox * lt.a + oy * lt.c) - ox;
+ lt.ty = position._y - (px * lt.b + py * lt.d) + (ox * lt.b + oy * lt.d) - oy;
+ }
+ // / ///// color related stuff
+ set alpha(value) {
+ if (value === this.localAlpha)
+ return;
+ this.localAlpha = value;
+ this._updateFlags |= UPDATE_COLOR;
+ this._onUpdate();
+ }
+ /**
+ * The opacity of the object relative to its parent's opacity.
+ * Value ranges from 0 (fully transparent) to 1 (fully opaque).
+ * @example
+ * ```ts
+ * // Basic transparency
+ * sprite.alpha = 0.5; // 50% opacity
+ *
+ * // Inherited opacity
+ * container.alpha = 0.5;
+ * const child = new Sprite(texture);
+ * child.alpha = 0.5;
+ * container.addChild(child);
+ * // child's effective opacity is 0.25 (0.5 * 0.5)
+ * ```
+ * @default 1
+ * @see {@link Container#visible} For toggling visibility
+ * @see {@link Container#renderable} For render control
+ */
+ get alpha() {
+ return this.localAlpha;
+ }
+ set tint(value) {
+ const tempColor = Color.shared.setValue(value != null ? value : 16777215);
+ const bgr = tempColor.toBgrNumber();
+ if (bgr === this.localColor)
+ return;
+ this.localColor = bgr;
+ this._updateFlags |= UPDATE_COLOR;
+ this._onUpdate();
+ }
+ /**
+ * The tint applied to the sprite.
+ *
+ * This can be any valid {@link ColorSource}.
+ * @example
+ * ```ts
+ * // Basic color tinting
+ * container.tint = 0xff0000; // Red tint
+ * container.tint = 'red'; // Same as above
+ * container.tint = '#00ff00'; // Green
+ * container.tint = 'rgb(0,0,255)'; // Blue
+ *
+ * // Remove tint
+ * container.tint = 0xffffff; // White = no tint
+ * container.tint = null; // Also removes tint
+ * ```
+ * @default 0xFFFFFF
+ * @see {@link Container#alpha} For transparency
+ * @see {@link Container#visible} For visibility control
+ */
+ get tint() {
+ return bgr2rgb(this.localColor);
+ }
+ // / //////////////// blend related stuff
+ set blendMode(value) {
+ if (this.localBlendMode === value)
+ return;
+ if (this.parentRenderGroup) {
+ this.parentRenderGroup.structureDidChange = true;
+ }
+ this._updateFlags |= UPDATE_BLEND;
+ this.localBlendMode = value;
+ this._onUpdate();
+ }
+ /**
+ * The blend mode to be applied to the sprite. Controls how pixels are blended when rendering.
+ *
+ * Setting to 'normal' will reset to default blending.
+ * > [!NOTE] More blend modes are available after importing the `pixi.js/advanced-blend-modes` sub-export.
+ * @example
+ * ```ts
+ * // Basic blend modes
+ * sprite.blendMode = 'add'; // Additive blending
+ * sprite.blendMode = 'multiply'; // Multiply colors
+ * sprite.blendMode = 'screen'; // Screen blend
+ *
+ * // Reset blend mode
+ * sprite.blendMode = 'normal'; // Normal blending
+ * ```
+ * @default 'normal'
+ * @see {@link Container#alpha} For transparency
+ * @see {@link Container#tint} For color adjustments
+ */
+ get blendMode() {
+ return this.localBlendMode;
+ }
+ // / ///////// VISIBILITY / RENDERABLE /////////////////
+ /**
+ * The visibility of the object. If false the object will not be drawn,
+ * and the transform will not be updated.
+ * @example
+ * ```ts
+ * // Basic visibility toggle
+ * sprite.visible = false; // Hide sprite
+ * sprite.visible = true; // Show sprite
+ * ```
+ * @default true
+ * @see {@link Container#renderable} For render-only control
+ * @see {@link Container#alpha} For transparency
+ */
+ get visible() {
+ return !!(this.localDisplayStatus & 2);
+ }
+ set visible(value) {
+ const valueNumber = value ? 2 : 0;
+ if ((this.localDisplayStatus & 2) === valueNumber)
+ return;
+ if (this.parentRenderGroup) {
+ this.parentRenderGroup.structureDidChange = true;
+ }
+ this._updateFlags |= UPDATE_VISIBLE;
+ this.localDisplayStatus ^= 2;
+ this._onUpdate();
+ }
+ /** @ignore */
+ get culled() {
+ return !(this.localDisplayStatus & 4);
+ }
+ /** @ignore */
+ set culled(value) {
+ const valueNumber = value ? 0 : 4;
+ if ((this.localDisplayStatus & 4) === valueNumber)
+ return;
+ if (this.parentRenderGroup) {
+ this.parentRenderGroup.structureDidChange = true;
+ }
+ this._updateFlags |= UPDATE_VISIBLE;
+ this.localDisplayStatus ^= 4;
+ this._onUpdate();
+ }
+ /**
+ * Controls whether this object can be rendered. If false the object will not be drawn,
+ * but the transform will still be updated. This is different from visible, which skips
+ * transform updates.
+ * @example
+ * ```ts
+ * // Basic render control
+ * sprite.renderable = false; // Skip rendering
+ * sprite.renderable = true; // Enable rendering
+ * ```
+ * @default true
+ * @see {@link Container#visible} For skipping transform updates
+ * @see {@link Container#alpha} For transparency
+ */
+ get renderable() {
+ return !!(this.localDisplayStatus & 1);
+ }
+ set renderable(value) {
+ const valueNumber = value ? 1 : 0;
+ if ((this.localDisplayStatus & 1) === valueNumber)
+ return;
+ this._updateFlags |= UPDATE_VISIBLE;
+ this.localDisplayStatus ^= 1;
+ if (this.parentRenderGroup) {
+ this.parentRenderGroup.structureDidChange = true;
+ }
+ this._onUpdate();
+ }
+ /**
+ * Whether or not the object should be rendered.
+ * @advanced
+ */
+ get isRenderable() {
+ return this.localDisplayStatus === 7 && this.groupAlpha > 0;
+ }
+ /**
+ * Removes all internal references and listeners as well as removes children from the display list.
+ * Do not use a Container after calling `destroy`.
+ * @param options - Options parameter. A boolean will act as if all options
+ * have been set to that value
+ * @example
+ * ```ts
+ * container.destroy();
+ * container.destroy(true);
+ * container.destroy({ children: true });
+ * container.destroy({ children: true, texture: true, textureSource: true });
+ * ```
+ */
+ destroy(options = false) {
+ var _a;
+ if (this.destroyed)
+ return;
+ this.destroyed = true;
+ let oldChildren;
+ if (this.children.length) {
+ oldChildren = this.removeChildren(0, this.children.length);
+ }
+ this.removeFromParent();
+ this.parent = null;
+ this._maskEffect = null;
+ this._filterEffect = null;
+ this.effects = null;
+ this._position = null;
+ this._scale = null;
+ this._pivot = null;
+ this._origin = null;
+ this._skew = null;
+ this.emit("destroyed", this);
+ this.removeAllListeners();
+ const destroyChildren = typeof options === "boolean" ? options : options == null ? void 0 : options.children;
+ if (destroyChildren && oldChildren) {
+ for (let i = 0; i < oldChildren.length; ++i) {
+ oldChildren[i].destroy(options);
+ }
+ }
+ (_a = this.renderGroup) == null ? void 0 : _a.destroy();
+ this.renderGroup = null;
+ }
+ }
+ extensions.mixin(
+ Container,
+ childrenHelperMixin,
+ getFastGlobalBoundsMixin,
+ toLocalGlobalMixin,
+ onRenderMixin,
+ measureMixin,
+ effectsMixin,
+ findMixin,
+ sortMixin,
+ cullingMixin,
+ cacheAsTextureMixin,
+ getGlobalMixin,
+ collectRenderablesMixin
+ );
+
+ "use strict";
+ var UPDATE_PRIORITY = /* @__PURE__ */ ((UPDATE_PRIORITY2) => {
+ UPDATE_PRIORITY2[UPDATE_PRIORITY2["INTERACTION"] = 50] = "INTERACTION";
+ UPDATE_PRIORITY2[UPDATE_PRIORITY2["HIGH"] = 25] = "HIGH";
+ UPDATE_PRIORITY2[UPDATE_PRIORITY2["NORMAL"] = 0] = "NORMAL";
+ UPDATE_PRIORITY2[UPDATE_PRIORITY2["LOW"] = -25] = "LOW";
+ UPDATE_PRIORITY2[UPDATE_PRIORITY2["UTILITY"] = -50] = "UTILITY";
+ return UPDATE_PRIORITY2;
+ })(UPDATE_PRIORITY || {});
+
+ "use strict";
+ class TickerListener {
+ /**
+ * Constructor
+ * @private
+ * @param fn - The listener function to be added for one update
+ * @param context - The listener context
+ * @param priority - The priority for emitting
+ * @param once - If the handler should fire once
+ */
+ constructor(fn, context = null, priority = 0, once = false) {
+ /** The next item in chain. */
+ this.next = null;
+ /** The previous item in chain. */
+ this.previous = null;
+ /** `true` if this listener has been destroyed already. */
+ this._destroyed = false;
+ this._fn = fn;
+ this._context = context;
+ this.priority = priority;
+ this._once = once;
+ }
+ /**
+ * Simple compare function to figure out if a function and context match.
+ * @param fn - The listener function to be added for one update
+ * @param context - The listener context
+ * @returns `true` if the listener match the arguments
+ */
+ match(fn, context = null) {
+ return this._fn === fn && this._context === context;
+ }
+ /**
+ * Emit by calling the current function.
+ * @param ticker - The ticker emitting.
+ * @returns Next ticker
+ */
+ emit(ticker) {
+ if (this._fn) {
+ if (this._context) {
+ this._fn.call(this._context, ticker);
+ } else {
+ this._fn(ticker);
+ }
+ }
+ const redirect = this.next;
+ if (this._once) {
+ this.destroy(true);
+ }
+ if (this._destroyed) {
+ this.next = null;
+ }
+ return redirect;
+ }
+ /**
+ * Connect to the list.
+ * @param previous - Input node, previous listener
+ */
+ connect(previous) {
+ this.previous = previous;
+ if (previous.next) {
+ previous.next.previous = this;
+ }
+ this.next = previous.next;
+ previous.next = this;
+ }
+ /**
+ * Destroy and don't use after this.
+ * @param hard - `true` to remove the `next` reference, this
+ * is considered a hard destroy. Soft destroy maintains the next reference.
+ * @returns The listener to redirect while emitting or removing.
+ */
+ destroy(hard = false) {
+ this._destroyed = true;
+ this._fn = null;
+ this._context = null;
+ if (this.previous) {
+ this.previous.next = this.next;
+ }
+ if (this.next) {
+ this.next.previous = this.previous;
+ }
+ const redirect = this.next;
+ this.next = hard ? null : redirect;
+ this.previous = null;
+ return redirect;
+ }
+ }
+
+ "use strict";
+ const _Ticker = class _Ticker {
+ constructor() {
+ /**
+ * Whether or not this ticker should invoke the method {@link Ticker#start|start}
+ * automatically when a listener is added.
+ * @example
+ * ```ts
+ * // Default behavior (manual start)
+ * const ticker = new Ticker();
+ * ticker.autoStart = false;
+ * ticker.add(() => {
+ * // Won't run until ticker.start() is called
+ * });
+ *
+ * // Auto-start behavior
+ * const autoTicker = new Ticker();
+ * autoTicker.autoStart = true;
+ * autoTicker.add(() => {
+ * // Runs immediately when added
+ * });
+ * ```
+ * @default false
+ * @see {@link Ticker#start} For manually starting the ticker
+ * @see {@link Ticker#stop} For manually stopping the ticker
+ */
+ this.autoStart = false;
+ /**
+ * Scalar representing the delta time factor.
+ * This is a dimensionless value representing the fraction of a frame at the target framerate.
+ * At 60 FPS, this value is typically around 1.0.
+ *
+ * This is NOT in milliseconds - it's a scalar multiplier for frame-independent animations.
+ * For actual milliseconds, use {@link Ticker#deltaMS}.
+ * @member {number}
+ * @example
+ * ```ts
+ * // Frame-independent animation using deltaTime scalar
+ * ticker.add((ticker) => {
+ * // Rotate sprite by 0.1 radians per frame, scaled by deltaTime
+ * sprite.rotation += 0.1 * ticker.deltaTime;
+ * });
+ * ```
+ */
+ this.deltaTime = 1;
+ /**
+ * The last time update was invoked, in milliseconds since epoch.
+ * Similar to performance.now() timestamp format.
+ *
+ * Used internally for calculating time deltas between frames.
+ * @member {number}
+ * @example
+ * ```ts
+ * ticker.add((ticker) => {
+ * const currentTime = performance.now();
+ * const timeSinceLastFrame = currentTime - ticker.lastTime;
+ * console.log(`Time since last frame: ${timeSinceLastFrame}ms`);
+ * });
+ * ```
+ */
+ this.lastTime = -1;
+ /**
+ * Factor of current {@link Ticker#deltaTime|deltaTime}.
+ * Used to scale time for slow motion or fast-forward effects.
+ * @example
+ * ```ts
+ * // Basic speed adjustment
+ * ticker.speed = 0.5; // Half speed (slow motion)
+ * ticker.speed = 2.0; // Double speed (fast forward)
+ *
+ * // Temporary speed changes
+ * function slowMotion() {
+ * const normalSpeed = ticker.speed;
+ * ticker.speed = 0.2;
+ * setTimeout(() => {
+ * ticker.speed = normalSpeed;
+ * }, 1000);
+ * }
+ * ```
+ */
+ this.speed = 1;
+ /**
+ * Whether or not this ticker has been started.
+ *
+ * `true` if {@link Ticker#start|start} has been called.
+ * `false` if {@link Ticker#stop|Stop} has been called.
+ *
+ * While `false`, this value may change to `true` in the
+ * event of {@link Ticker#autoStart|autoStart} being `true`
+ * and a listener is added.
+ * @example
+ * ```ts
+ * // Check ticker state
+ * const ticker = new Ticker();
+ * console.log(ticker.started); // false
+ *
+ * // Start and verify
+ * ticker.start();
+ * console.log(ticker.started); // true
+ * ```
+ */
+ this.started = false;
+ /** Internal current frame request ID */
+ this._requestId = null;
+ /**
+ * Internal value managed by minFPS property setter and getter.
+ * This is the maximum allowed milliseconds between updates.
+ */
+ this._maxElapsedMS = 100;
+ /**
+ * Internal value managed by minFPS property setter and getter.
+ * This is the minimum allowed milliseconds between updates.
+ */
+ this._minElapsedMS = 0;
+ /** If enabled, deleting is disabled.*/
+ this._protected = false;
+ /** The last time keyframe was executed. Maintains a relatively fixed interval with the previous value. */
+ this._lastFrame = -1;
+ this._head = new TickerListener(null, null, Infinity);
+ this.deltaMS = 1 / _Ticker.targetFPMS;
+ this.elapsedMS = 1 / _Ticker.targetFPMS;
+ this._tick = (time) => {
+ this._requestId = null;
+ if (this.started) {
+ this.update(time);
+ if (this.started && this._requestId === null && this._head.next) {
+ this._requestId = requestAnimationFrame(this._tick);
+ }
+ }
+ };
+ }
+ /**
+ * Conditionally requests a new animation frame.
+ * If a frame has not already been requested, and if the internal
+ * emitter has listeners, a new frame is requested.
+ */
+ _requestIfNeeded() {
+ if (this._requestId === null && this._head.next) {
+ this.lastTime = performance.now();
+ this._lastFrame = this.lastTime;
+ this._requestId = requestAnimationFrame(this._tick);
+ }
+ }
+ /** Conditionally cancels a pending animation frame. */
+ _cancelIfNeeded() {
+ if (this._requestId !== null) {
+ cancelAnimationFrame(this._requestId);
+ this._requestId = null;
+ }
+ }
+ /**
+ * Conditionally requests a new animation frame.
+ * If the ticker has been started it checks if a frame has not already
+ * been requested, and if the internal emitter has listeners. If these
+ * conditions are met, a new frame is requested. If the ticker has not
+ * been started, but autoStart is `true`, then the ticker starts now,
+ * and continues with the previous conditions to request a new frame.
+ */
+ _startIfPossible() {
+ if (this.started) {
+ this._requestIfNeeded();
+ } else if (this.autoStart) {
+ this.start();
+ }
+ }
+ /**
+ * Register a handler for tick events.
+ * @param fn - The listener function to add. Receives the Ticker instance as parameter
+ * @param context - The context for the listener
+ * @param priority - The priority of the listener
+ * @example
+ * ```ts
+ * // Access time properties through the ticker parameter
+ * ticker.add((ticker) => {
+ * // Use deltaTime (dimensionless scalar) for frame-independent animations
+ * sprite.rotation += 0.1 * ticker.deltaTime;
+ *
+ * // Use deltaMS (milliseconds) for time-based calculations
+ * const progress = ticker.deltaMS / animationDuration;
+ *
+ * // Use elapsedMS for raw timing measurements
+ * console.log(`Raw frame time: ${ticker.elapsedMS}ms`);
+ * });
+ * ```
+ */
+ add(fn, context, priority = UPDATE_PRIORITY.NORMAL) {
+ return this._addListener(new TickerListener(fn, context, priority));
+ }
+ /**
+ * Add a handler for the tick event which is only executed once on the next frame.
+ * @example
+ * ```ts
+ * // Basic one-time update
+ * ticker.addOnce(() => {
+ * console.log('Runs next frame only');
+ * });
+ *
+ * // With specific context
+ * const game = {
+ * init(ticker) {
+ * this.loadResources();
+ * console.log('Game initialized');
+ * }
+ * };
+ * ticker.addOnce(game.init, game);
+ *
+ * // With priority
+ * ticker.addOnce(
+ * () => {
+ * // High priority one-time setup
+ * physics.init();
+ * },
+ * undefined,
+ * UPDATE_PRIORITY.HIGH
+ * );
+ * ```
+ * @param fn - The listener function to be added for one update
+ * @param context - The listener context
+ * @param priority - The priority for emitting (default: UPDATE_PRIORITY.NORMAL)
+ * @returns This instance of a ticker
+ * @see {@link Ticker#add} For continuous updates
+ * @see {@link Ticker#remove} For removing handlers
+ */
+ addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) {
+ return this._addListener(new TickerListener(fn, context, priority, true));
+ }
+ /**
+ * Internally adds the event handler so that it can be sorted by priority.
+ * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run
+ * before the rendering.
+ * @private
+ * @param listener - Current listener being added.
+ * @returns This instance of a ticker
+ */
+ _addListener(listener) {
+ let current = this._head.next;
+ let previous = this._head;
+ if (!current) {
+ listener.connect(previous);
+ } else {
+ while (current) {
+ if (listener.priority > current.priority) {
+ listener.connect(previous);
+ break;
+ }
+ previous = current;
+ current = current.next;
+ }
+ if (!listener.previous) {
+ listener.connect(previous);
+ }
+ }
+ this._startIfPossible();
+ return this;
+ }
+ /**
+ * Removes any handlers matching the function and context parameters.
+ * If no handlers are left after removing, then it cancels the animation frame.
+ * @example
+ * ```ts
+ * // Basic removal
+ * const onTick = () => {
+ * sprite.rotation += 0.1;
+ * };
+ * ticker.add(onTick);
+ * ticker.remove(onTick);
+ *
+ * // Remove with context
+ * const game = {
+ * update(ticker) {
+ * this.physics.update(ticker.deltaTime);
+ * }
+ * };
+ * ticker.add(game.update, game);
+ * ticker.remove(game.update, game);
+ *
+ * // Remove all matching handlers
+ * // (if same function was added multiple times)
+ * ticker.add(onTick);
+ * ticker.add(onTick);
+ * ticker.remove(onTick); // Removes all instances
+ * ```
+ * @param fn - The listener function to be removed
+ * @param context - The listener context to be removed
+ * @returns This instance of a ticker
+ * @see {@link Ticker#add} For adding handlers
+ * @see {@link Ticker#addOnce} For one-time handlers
+ */
+ remove(fn, context) {
+ let listener = this._head.next;
+ while (listener) {
+ if (listener.match(fn, context)) {
+ listener = listener.destroy();
+ } else {
+ listener = listener.next;
+ }
+ }
+ if (!this._head.next) {
+ this._cancelIfNeeded();
+ }
+ return this;
+ }
+ /**
+ * The number of listeners on this ticker, calculated by walking through linked list.
+ * @example
+ * ```ts
+ * // Check number of active listeners
+ * const ticker = new Ticker();
+ * console.log(ticker.count); // 0
+ *
+ * // Add some listeners
+ * ticker.add(() => {});
+ * ticker.add(() => {});
+ * console.log(ticker.count); // 2
+ *
+ * // Check after cleanup
+ * ticker.destroy();
+ * console.log(ticker.count); // 0
+ * ```
+ * @readonly
+ * @see {@link Ticker#add} For adding listeners
+ * @see {@link Ticker#remove} For removing listeners
+ */
+ get count() {
+ if (!this._head) {
+ return 0;
+ }
+ let count = 0;
+ let current = this._head;
+ while (current = current.next) {
+ count++;
+ }
+ return count;
+ }
+ /**
+ * Starts the ticker. If the ticker has listeners a new animation frame is requested at this point.
+ * @example
+ * ```ts
+ * // Basic manual start
+ * const ticker = new Ticker();
+ * ticker.add(() => {
+ * // Animation code here
+ * });
+ * ticker.start();
+ * ```
+ * @see {@link Ticker#stop} For stopping the ticker
+ * @see {@link Ticker#autoStart} For automatic starting
+ * @see {@link Ticker#started} For checking ticker state
+ */
+ start() {
+ if (!this.started) {
+ this.started = true;
+ this._requestIfNeeded();
+ }
+ }
+ /**
+ * Stops the ticker. If the ticker has requested an animation frame it is canceled at this point.
+ * @example
+ * ```ts
+ * // Basic stop
+ * const ticker = new Ticker();
+ * ticker.stop();
+ * ```
+ * @see {@link Ticker#start} For starting the ticker
+ * @see {@link Ticker#started} For checking ticker state
+ * @see {@link Ticker#destroy} For cleaning up the ticker
+ */
+ stop() {
+ if (this.started) {
+ this.started = false;
+ this._cancelIfNeeded();
+ }
+ }
+ /**
+ * Destroy the ticker and don't use after this. Calling this method removes all references to internal events.
+ * @example
+ * ```ts
+ * // Clean up with active listeners
+ * const ticker = new Ticker();
+ * ticker.add(() => {});
+ * ticker.destroy(); // Removes all listeners
+ * ```
+ * @see {@link Ticker#stop} For stopping without destroying
+ * @see {@link Ticker#remove} For removing specific listeners
+ */
+ destroy() {
+ if (!this._protected) {
+ this.stop();
+ let listener = this._head.next;
+ while (listener) {
+ listener = listener.destroy(true);
+ }
+ this._head.destroy();
+ this._head = null;
+ }
+ }
+ /**
+ * Triggers an update.
+ *
+ * An update entails setting the
+ * current {@link Ticker#elapsedMS|elapsedMS},
+ * the current {@link Ticker#deltaTime|deltaTime},
+ * invoking all listeners with current deltaTime,
+ * and then finally setting {@link Ticker#lastTime|lastTime}
+ * with the value of currentTime that was provided.
+ *
+ * This method will be called automatically by animation
+ * frame callbacks if the ticker instance has been started
+ * and listeners are added.
+ * @example
+ * ```ts
+ * // Basic manual update
+ * const ticker = new Ticker();
+ * ticker.update(performance.now());
+ * ```
+ * @param currentTime - The current time of execution (defaults to performance.now())
+ * @see {@link Ticker#deltaTime} For frame delta value
+ * @see {@link Ticker#elapsedMS} For raw elapsed time
+ */
+ update(currentTime = performance.now()) {
+ let elapsedMS;
+ if (currentTime > this.lastTime) {
+ elapsedMS = this.elapsedMS = currentTime - this.lastTime;
+ if (elapsedMS > this._maxElapsedMS) {
+ elapsedMS = this._maxElapsedMS;
+ }
+ elapsedMS *= this.speed;
+ if (this._minElapsedMS) {
+ const delta = currentTime - this._lastFrame | 0;
+ if (delta < this._minElapsedMS) {
+ return;
+ }
+ this._lastFrame = currentTime - delta % this._minElapsedMS;
+ }
+ this.deltaMS = elapsedMS;
+ this.deltaTime = this.deltaMS * _Ticker.targetFPMS;
+ const head = this._head;
+ let listener = head.next;
+ while (listener) {
+ listener = listener.emit(this);
+ }
+ if (!head.next) {
+ this._cancelIfNeeded();
+ }
+ } else {
+ this.deltaTime = this.deltaMS = this.elapsedMS = 0;
+ }
+ this.lastTime = currentTime;
+ }
+ /**
+ * The frames per second at which this ticker is running.
+ * The default is approximately 60 in most modern browsers.
+ * > [!NOTE] This does not factor in the value of
+ * > {@link Ticker#speed|speed}, which is specific
+ * > to scaling {@link Ticker#deltaTime|deltaTime}.
+ * @example
+ * ```ts
+ * // Basic FPS monitoring
+ * ticker.add(() => {
+ * console.log(`Current FPS: ${Math.round(ticker.FPS)}`);
+ * });
+ * ```
+ * @readonly
+ */
+ get FPS() {
+ return 1e3 / this.elapsedMS;
+ }
+ /**
+ * Manages the maximum amount of milliseconds allowed to
+ * elapse between invoking {@link Ticker#update|update}.
+ *
+ * This value is used to cap {@link Ticker#deltaTime|deltaTime},
+ * but does not effect the measured value of {@link Ticker#FPS|FPS}.
+ *
+ * When setting this property it is clamped to a value between
+ * `0` and `Ticker.targetFPMS * 1000`.
+ * @example
+ * ```ts
+ * // Set minimum acceptable frame rate
+ * const ticker = new Ticker();
+ * ticker.minFPS = 30; // Never go below 30 FPS
+ *
+ * // Use with maxFPS for frame rate clamping
+ * ticker.minFPS = 30;
+ * ticker.maxFPS = 60;
+ *
+ * // Monitor delta capping
+ * ticker.add(() => {
+ * // Delta time will be capped based on minFPS
+ * console.log(`Delta time: ${ticker.deltaTime}`);
+ * });
+ * ```
+ * @default 10
+ */
+ get minFPS() {
+ return 1e3 / this._maxElapsedMS;
+ }
+ set minFPS(fps) {
+ const minFPS = Math.min(this.maxFPS, fps);
+ const minFPMS = Math.min(Math.max(0, minFPS) / 1e3, _Ticker.targetFPMS);
+ this._maxElapsedMS = 1 / minFPMS;
+ }
+ /**
+ * Manages the minimum amount of milliseconds required to
+ * elapse between invoking {@link Ticker#update|update}.
+ *
+ * This will effect the measured value of {@link Ticker#FPS|FPS}.
+ *
+ * If it is set to `0`, then there is no limit; PixiJS will render as many frames as it can.
+ * Otherwise it will be at least `minFPS`
+ * @example
+ * ```ts
+ * // Set minimum acceptable frame rate
+ * const ticker = new Ticker();
+ * ticker.maxFPS = 60; // Never go above 60 FPS
+ *
+ * // Use with maxFPS for frame rate clamping
+ * ticker.minFPS = 30;
+ * ticker.maxFPS = 60;
+ *
+ * // Monitor delta capping
+ * ticker.add(() => {
+ * // Delta time will be capped based on maxFPS
+ * console.log(`Delta time: ${ticker.deltaTime}`);
+ * });
+ * ```
+ * @default 0
+ */
+ get maxFPS() {
+ if (this._minElapsedMS) {
+ return Math.round(1e3 / this._minElapsedMS);
+ }
+ return 0;
+ }
+ set maxFPS(fps) {
+ if (fps === 0) {
+ this._minElapsedMS = 0;
+ } else {
+ const maxFPS = Math.max(this.minFPS, fps);
+ this._minElapsedMS = 1 / (maxFPS / 1e3);
+ }
+ }
+ /**
+ * The shared ticker instance used by {@link AnimatedSprite} and by
+ * {@link VideoSource} to update animation frames / video textures.
+ *
+ * It may also be used by {@link Application} if created with the `sharedTicker` option property set to true.
+ *
+ * The property {@link Ticker#autoStart|autoStart} is set to `true` for this instance.
+ * Please follow the examples for usage, including how to opt-out of auto-starting the shared ticker.
+ * @example
+ * import { Ticker } from 'pixi.js';
+ *
+ * const ticker = Ticker.shared;
+ * // Set this to prevent starting this ticker when listeners are added.
+ * // By default this is true only for the Ticker.shared instance.
+ * ticker.autoStart = false;
+ *
+ * // FYI, call this to ensure the ticker is stopped. It should be stopped
+ * // if you have not attempted to render anything yet.
+ * ticker.stop();
+ *
+ * // Call this when you are ready for a running shared ticker.
+ * ticker.start();
+ * @example
+ * import { autoDetectRenderer, Container } from 'pixi.js';
+ *
+ * // You may use the shared ticker to render...
+ * const renderer = autoDetectRenderer();
+ * const stage = new Container();
+ * document.body.appendChild(renderer.view);
+ * ticker.add((time) => renderer.render(stage));
+ *
+ * // Or you can just update it manually.
+ * ticker.autoStart = false;
+ * ticker.stop();
+ * const animate = (time) => {
+ * ticker.update(time);
+ * renderer.render(stage);
+ * requestAnimationFrame(animate);
+ * };
+ * animate(performance.now());
+ * @type {Ticker}
+ * @readonly
+ */
+ static get shared() {
+ if (!_Ticker._shared) {
+ const shared = _Ticker._shared = new _Ticker();
+ shared.autoStart = true;
+ shared._protected = true;
+ }
+ return _Ticker._shared;
+ }
+ /**
+ * The system ticker instance used by {@link PrepareBase} for core timing
+ * functionality that shouldn't usually need to be paused, unlike the `shared`
+ * ticker which drives visual animations and rendering which may want to be paused.
+ *
+ * The property {@link Ticker#autoStart|autoStart} is set to `true` for this instance.
+ * @type {Ticker}
+ * @readonly
+ * @advanced
+ */
+ static get system() {
+ if (!_Ticker._system) {
+ const system = _Ticker._system = new _Ticker();
+ system.autoStart = true;
+ system._protected = true;
+ }
+ return _Ticker._system;
+ }
+ };
+ /**
+ * Target frame rate in frames per millisecond.
+ * Used for converting deltaTime to a scalar time delta.
+ * @example
+ * ```ts
+ * // Default is 0.06 (60 FPS)
+ * console.log(Ticker.targetFPMS); // 0.06
+ *
+ * // Calculate target frame duration
+ * const frameDuration = 1 / Ticker.targetFPMS; // ≈ 16.67ms
+ *
+ * // Use in custom timing calculations
+ * const deltaTime = elapsedMS * Ticker.targetFPMS;
+ * ```
+ * @remarks
+ * - Default is 0.06 (equivalent to 60 FPS)
+ * - Used in deltaTime calculations
+ * - Affects all ticker instances
+ * @default 0.06
+ * @see {@link Ticker#deltaTime} For time scaling
+ * @see {@link Ticker#FPS} For actual frame rate
+ */
+ _Ticker.targetFPMS = 0.06;
+ let Ticker = _Ticker;
+
+ "use strict";
+ class CanvasObserver {
+ constructor(options) {
+ /** A cached value of the last transform applied to the DOM element. */
+ this._lastTransform = "";
+ /** A ResizeObserver instance to observe changes in the canvas size. */
+ this._observer = null;
+ /** A flag to indicate whether the observer is attached to the Ticker for continuous updates. */
+ this._tickerAttached = false;
+ /**
+ * Updates the transform of the DOM element based on the canvas size and position.
+ * This method calculates the scale and translation needed to keep the DOM element in sync with the canvas.
+ */
+ this.updateTranslation = () => {
+ if (!this._canvas)
+ return;
+ const rect = this._canvas.getBoundingClientRect();
+ const contentWidth = this._canvas.width;
+ const contentHeight = this._canvas.height;
+ const sx = rect.width / contentWidth * this._renderer.resolution;
+ const sy = rect.height / contentHeight * this._renderer.resolution;
+ const tx = rect.left;
+ const ty = rect.top;
+ const newTransform = `translate(${tx}px, ${ty}px) scale(${sx}, ${sy})`;
+ if (newTransform !== this._lastTransform) {
+ this._domElement.style.transform = newTransform;
+ this._lastTransform = newTransform;
+ }
+ };
+ this._domElement = options.domElement;
+ this._renderer = options.renderer;
+ if (globalThis.OffscreenCanvas && this._renderer.canvas instanceof OffscreenCanvas)
+ return;
+ this._canvas = this._renderer.canvas;
+ this._attachObserver();
+ }
+ /** The canvas element that this CanvasObserver is associated with. */
+ get canvas() {
+ return this._canvas;
+ }
+ /** Attaches the DOM element to the canvas parent if it is not already attached. */
+ ensureAttached() {
+ if (!this._domElement.parentNode && this._canvas.parentNode) {
+ this._canvas.parentNode.appendChild(this._domElement);
+ this.updateTranslation();
+ }
+ }
+ /** Sets up a ResizeObserver if available. This ensures that the DOM element is kept in sync with the canvas size . */
+ _attachObserver() {
+ if ("ResizeObserver" in globalThis) {
+ if (this._observer) {
+ this._observer.disconnect();
+ this._observer = null;
+ }
+ this._observer = new ResizeObserver((entries) => {
+ for (const entry of entries) {
+ if (entry.target !== this._canvas) {
+ continue;
+ }
+ const contentWidth = this.canvas.width;
+ const contentHeight = this.canvas.height;
+ const sx = entry.contentRect.width / contentWidth * this._renderer.resolution;
+ const sy = entry.contentRect.height / contentHeight * this._renderer.resolution;
+ const needsUpdate = this._lastScaleX !== sx || this._lastScaleY !== sy;
+ if (needsUpdate) {
+ this.updateTranslation();
+ this._lastScaleX = sx;
+ this._lastScaleY = sy;
+ }
+ }
+ });
+ this._observer.observe(this._canvas);
+ } else if (!this._tickerAttached) {
+ Ticker.shared.add(this.updateTranslation, this, UPDATE_PRIORITY.HIGH);
+ }
+ }
+ /** Destroys the CanvasObserver instance, cleaning up observers and Ticker. */
+ destroy() {
+ if (this._observer) {
+ this._observer.disconnect();
+ this._observer = null;
+ } else if (this._tickerAttached) {
+ Ticker.shared.remove(this.updateTranslation);
+ }
+ this._domElement = null;
+ this._renderer = null;
+ this._canvas = null;
+ this._tickerAttached = false;
+ this._lastTransform = "";
+ this._lastScaleX = null;
+ this._lastScaleY = null;
+ }
+ }
+
+ "use strict";
+ class FederatedEvent {
+ /**
+ * @param manager - The event boundary which manages this event. Propagation can only occur
+ * within the boundary's jurisdiction.
+ */
+ constructor(manager) {
+ /** Flags whether this event bubbles. This will take effect only if it is set before propagation. */
+ this.bubbles = true;
+ /** @deprecated since 7.0.0 */
+ this.cancelBubble = true;
+ /**
+ * Flags whether this event can be canceled using {@link FederatedEvent.preventDefault}. This is always
+ * false (for now).
+ */
+ this.cancelable = false;
+ /**
+ * Flag added for compatibility with DOM `Event`. It is not used in the Federated Events
+ * API.
+ * @see https://dom.spec.whatwg.org/#dom-event-composed
+ * @ignore
+ */
+ this.composed = false;
+ /** Flags whether the default response of the user agent was prevent through this event. */
+ this.defaultPrevented = false;
+ /**
+ * The propagation phase.
+ * @default {@link FederatedEvent.NONE}
+ */
+ this.eventPhase = FederatedEvent.prototype.NONE;
+ /** Flags whether propagation was stopped. */
+ this.propagationStopped = false;
+ /** Flags whether propagation was immediately stopped. */
+ this.propagationImmediatelyStopped = false;
+ /** The coordinates of the event relative to the nearest DOM layer. This is a non-standard property. */
+ this.layer = new Point();
+ /** The coordinates of the event relative to the DOM document. This is a non-standard property. */
+ this.page = new Point();
+ /**
+ * The event propagation phase NONE that indicates that the event is not in any phase.
+ * @default 0
+ * @advanced
+ */
+ this.NONE = 0;
+ /**
+ * The event propagation phase CAPTURING_PHASE that indicates that the event is in the capturing phase.
+ * @default 1
+ * @advanced
+ */
+ this.CAPTURING_PHASE = 1;
+ /**
+ * The event propagation phase AT_TARGET that indicates that the event is at the target.
+ * @default 2
+ * @advanced
+ */
+ this.AT_TARGET = 2;
+ /**
+ * The event propagation phase BUBBLING_PHASE that indicates that the event is in the bubbling phase.
+ * @default 3
+ * @advanced
+ */
+ this.BUBBLING_PHASE = 3;
+ this.manager = manager;
+ }
+ /** @readonly */
+ get layerX() {
+ return this.layer.x;
+ }
+ /** @readonly */
+ get layerY() {
+ return this.layer.y;
+ }
+ /** @readonly */
+ get pageX() {
+ return this.page.x;
+ }
+ /** @readonly */
+ get pageY() {
+ return this.page.y;
+ }
+ /**
+ * Fallback for the deprecated `InteractionEvent.data`.
+ * @deprecated since 7.0.0
+ */
+ get data() {
+ return this;
+ }
+ /**
+ * The propagation path for this event. Alias for {@link EventBoundary.propagationPath}.
+ * @advanced
+ */
+ composedPath() {
+ if (this.manager && (!this.path || this.path[this.path.length - 1] !== this.target)) {
+ this.path = this.target ? this.manager.propagationPath(this.target) : [];
+ }
+ return this.path;
+ }
+ /**
+ * Unimplemented method included for implementing the DOM interface `Event`. It will throw an `Error`.
+ * @deprecated
+ * @ignore
+ * @param _type
+ * @param _bubbles
+ * @param _cancelable
+ */
+ initEvent(_type, _bubbles, _cancelable) {
+ throw new Error("initEvent() is a legacy DOM API. It is not implemented in the Federated Events API.");
+ }
+ /**
+ * Unimplemented method included for implementing the DOM interface `UIEvent`. It will throw an `Error`.
+ * @ignore
+ * @deprecated
+ * @param _typeArg
+ * @param _bubblesArg
+ * @param _cancelableArg
+ * @param _viewArg
+ * @param _detailArg
+ */
+ initUIEvent(_typeArg, _bubblesArg, _cancelableArg, _viewArg, _detailArg) {
+ throw new Error("initUIEvent() is a legacy DOM API. It is not implemented in the Federated Events API.");
+ }
+ /**
+ * Prevent default behavior of both PixiJS and the user agent.
+ * @example
+ * ```ts
+ * sprite.on('click', (event) => {
+ * // Prevent both browser's default click behavior
+ * // and PixiJS's default handling
+ * event.preventDefault();
+ *
+ * // Custom handling
+ * customClickHandler();
+ * });
+ * ```
+ * @remarks
+ * - Only works if the native event is cancelable
+ * - Does not stop event propagation
+ */
+ preventDefault() {
+ if (this.nativeEvent instanceof Event && this.nativeEvent.cancelable) {
+ this.nativeEvent.preventDefault();
+ }
+ this.defaultPrevented = true;
+ }
+ /**
+ * Stop this event from propagating to any additional listeners, including those
+ * on the current target and any following targets in the propagation path.
+ * @example
+ * ```ts
+ * container.on('pointerdown', (event) => {
+ * // Stop all further event handling
+ * event.stopImmediatePropagation();
+ *
+ * // These handlers won't be called:
+ * // - Other pointerdown listeners on this container
+ * // - Any pointerdown listeners on parent containers
+ * });
+ * ```
+ * @remarks
+ * - Immediately stops all event propagation
+ * - Prevents other listeners on same target from being called
+ * - More aggressive than stopPropagation()
+ */
+ stopImmediatePropagation() {
+ this.propagationImmediatelyStopped = true;
+ }
+ /**
+ * Stop this event from propagating to the next target in the propagation path.
+ * The rest of the listeners on the current target will still be notified.
+ * @example
+ * ```ts
+ * child.on('pointermove', (event) => {
+ * // Handle event on child
+ * updateChild();
+ *
+ * // Prevent parent handlers from being called
+ * event.stopPropagation();
+ * });
+ *
+ * // This won't be called if child handles the event
+ * parent.on('pointermove', (event) => {
+ * updateParent();
+ * });
+ * ```
+ * @remarks
+ * - Stops event bubbling to parent containers
+ * - Does not prevent other listeners on same target
+ * - Less aggressive than stopImmediatePropagation()
+ */
+ stopPropagation() {
+ this.propagationStopped = true;
+ }
+ }
+
+ var appleIphone = /iPhone/i;
+ var appleIpod = /iPod/i;
+ var appleTablet = /iPad/i;
+ var appleUniversal = /\biOS-universal(?:.+)Mac\b/i;
+ var androidPhone = /\bAndroid(?:.+)Mobile\b/i;
+ var androidTablet = /Android/i;
+ var amazonPhone = /(?:SD4930UR|\bSilk(?:.+)Mobile\b)/i;
+ var amazonTablet = /Silk/i;
+ var windowsPhone = /Windows Phone/i;
+ var windowsTablet = /\bWindows(?:.+)ARM\b/i;
+ var otherBlackBerry = /BlackBerry/i;
+ var otherBlackBerry10 = /BB10/i;
+ var otherOpera = /Opera Mini/i;
+ var otherChrome = /\b(CriOS|Chrome)(?:.+)Mobile/i;
+ var otherFirefox = /Mobile(?:.+)Firefox\b/i;
+ var isAppleTabletOnIos13 = function (navigator) {
+ return (typeof navigator !== 'undefined' &&
+ navigator.platform === 'MacIntel' &&
+ typeof navigator.maxTouchPoints === 'number' &&
+ navigator.maxTouchPoints > 1 &&
+ typeof MSStream === 'undefined');
+ };
+ function createMatch(userAgent) {
+ return function (regex) { return regex.test(userAgent); };
+ }
+ function isMobile$1(param) {
+ var nav = {
+ userAgent: '',
+ platform: '',
+ maxTouchPoints: 0
+ };
+ if (!param && typeof navigator !== 'undefined') {
+ nav = {
+ userAgent: navigator.userAgent,
+ platform: navigator.platform,
+ maxTouchPoints: navigator.maxTouchPoints || 0
+ };
+ }
+ else if (typeof param === 'string') {
+ nav.userAgent = param;
+ }
+ else if (param && param.userAgent) {
+ nav = {
+ userAgent: param.userAgent,
+ platform: param.platform,
+ maxTouchPoints: param.maxTouchPoints || 0
+ };
+ }
+ var userAgent = nav.userAgent;
+ var tmp = userAgent.split('[FBAN');
+ if (typeof tmp[1] !== 'undefined') {
+ userAgent = tmp[0];
+ }
+ tmp = userAgent.split('Twitter');
+ if (typeof tmp[1] !== 'undefined') {
+ userAgent = tmp[0];
+ }
+ var match = createMatch(userAgent);
+ var result = {
+ apple: {
+ phone: match(appleIphone) && !match(windowsPhone),
+ ipod: match(appleIpod),
+ tablet: !match(appleIphone) &&
+ (match(appleTablet) || isAppleTabletOnIos13(nav)) &&
+ !match(windowsPhone),
+ universal: match(appleUniversal),
+ device: (match(appleIphone) ||
+ match(appleIpod) ||
+ match(appleTablet) ||
+ match(appleUniversal) ||
+ isAppleTabletOnIos13(nav)) &&
+ !match(windowsPhone)
+ },
+ amazon: {
+ phone: match(amazonPhone),
+ tablet: !match(amazonPhone) && match(amazonTablet),
+ device: match(amazonPhone) || match(amazonTablet)
+ },
+ android: {
+ phone: (!match(windowsPhone) && match(amazonPhone)) ||
+ (!match(windowsPhone) && match(androidPhone)),
+ tablet: !match(windowsPhone) &&
+ !match(amazonPhone) &&
+ !match(androidPhone) &&
+ (match(amazonTablet) || match(androidTablet)),
+ device: (!match(windowsPhone) &&
+ (match(amazonPhone) ||
+ match(amazonTablet) ||
+ match(androidPhone) ||
+ match(androidTablet))) ||
+ match(/\bokhttp\b/i)
+ },
+ windows: {
+ phone: match(windowsPhone),
+ tablet: match(windowsTablet),
+ device: match(windowsPhone) || match(windowsTablet)
+ },
+ other: {
+ blackberry: match(otherBlackBerry),
+ blackberry10: match(otherBlackBerry10),
+ opera: match(otherOpera),
+ firefox: match(otherFirefox),
+ chrome: match(otherChrome),
+ device: match(otherBlackBerry) ||
+ match(otherBlackBerry10) ||
+ match(otherOpera) ||
+ match(otherFirefox) ||
+ match(otherChrome)
+ },
+ any: false,
+ phone: false,
+ tablet: false
+ };
+ result.any =
+ result.apple.device ||
+ result.android.device ||
+ result.windows.device ||
+ result.other.device;
+ result.phone =
+ result.apple.phone || result.android.phone || result.windows.phone;
+ result.tablet =
+ result.apple.tablet || result.android.tablet || result.windows.tablet;
+ return result;
+ }
+
+ "use strict";
+ var _a;
+ const isMobileCall = (_a = isMobile$1.default) != null ? _a : isMobile$1;
+ const isMobile = isMobileCall(globalThis.navigator);
+
+ "use strict";
+ var __defProp$17 = Object.defineProperty;
+ var __getOwnPropSymbols$18 = Object.getOwnPropertySymbols;
+ var __hasOwnProp$18 = Object.prototype.hasOwnProperty;
+ var __propIsEnum$18 = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$17 = (obj, key, value) => key in obj ? __defProp$17(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$17 = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$18.call(b, prop))
+ __defNormalProp$17(a, prop, b[prop]);
+ if (__getOwnPropSymbols$18)
+ for (var prop of __getOwnPropSymbols$18(b)) {
+ if (__propIsEnum$18.call(b, prop))
+ __defNormalProp$17(a, prop, b[prop]);
+ }
+ return a;
+ };
+ const KEY_CODE_TAB = 9;
+ const DIV_TOUCH_SIZE = 100;
+ const DIV_TOUCH_POS_X = 0;
+ const DIV_TOUCH_POS_Y = 0;
+ const DIV_TOUCH_ZINDEX = 2;
+ const DIV_HOOK_SIZE = 1;
+ const DIV_HOOK_POS_X = -1e3;
+ const DIV_HOOK_POS_Y = -1e3;
+ const DIV_HOOK_ZINDEX = 2;
+ const _AccessibilitySystem = class _AccessibilitySystem {
+ // eslint-disable-next-line jsdoc/require-param
+ /**
+ * @param {WebGLRenderer|WebGPURenderer} renderer - A reference to the current renderer
+ */
+ constructor(renderer, _mobileInfo = isMobile) {
+ this._mobileInfo = _mobileInfo;
+ /** Whether accessibility divs are visible for debugging */
+ this.debug = false;
+ /** Whether to activate on tab key press */
+ this._activateOnTab = true;
+ /** Whether to deactivate accessibility when mouse moves */
+ this._deactivateOnMouseMove = true;
+ /** Internal variable, see isActive getter. */
+ this._isActive = false;
+ /** Internal variable, see isMobileAccessibility getter. */
+ this._isMobileAccessibility = false;
+ /** This is the dom element that will sit over the PixiJS element. This is where the div overlays will go. */
+ this._div = null;
+ /** A simple pool for storing divs. */
+ this._pool = [];
+ /** This is a tick used to check if an object is no longer being rendered. */
+ this._renderId = 0;
+ /** The array of currently active accessible items. */
+ this._children = [];
+ /** Count to throttle div updates on android devices. */
+ this._androidUpdateCount = 0;
+ /** The frequency to update the div elements. */
+ this._androidUpdateFrequency = 500;
+ this._hookDiv = null;
+ if (_mobileInfo.tablet || _mobileInfo.phone) {
+ this._createTouchHook();
+ }
+ this._renderer = renderer;
+ }
+ /**
+ * Value of `true` if accessibility is currently active and accessibility layers are showing.
+ * @type {boolean}
+ * @readonly
+ */
+ get isActive() {
+ return this._isActive;
+ }
+ /**
+ * Value of `true` if accessibility is enabled for touch devices.
+ * @type {boolean}
+ * @readonly
+ */
+ get isMobileAccessibility() {
+ return this._isMobileAccessibility;
+ }
+ /**
+ * The DOM element that will sit over the PixiJS element. This is where the div overlays will go.
+ * @readonly
+ */
+ get hookDiv() {
+ return this._hookDiv;
+ }
+ /**
+ * Creates the touch hooks.
+ * @private
+ */
+ _createTouchHook() {
+ const hookDiv = document.createElement("button");
+ hookDiv.style.width = `${DIV_HOOK_SIZE}px`;
+ hookDiv.style.height = `${DIV_HOOK_SIZE}px`;
+ hookDiv.style.position = "absolute";
+ hookDiv.style.top = `${DIV_HOOK_POS_X}px`;
+ hookDiv.style.left = `${DIV_HOOK_POS_Y}px`;
+ hookDiv.style.zIndex = DIV_HOOK_ZINDEX.toString();
+ hookDiv.style.backgroundColor = "#FF0000";
+ hookDiv.title = "select to enable accessibility for this content";
+ hookDiv.addEventListener("focus", () => {
+ this._isMobileAccessibility = true;
+ this._activate();
+ this._destroyTouchHook();
+ });
+ document.body.appendChild(hookDiv);
+ this._hookDiv = hookDiv;
+ }
+ /**
+ * Destroys the touch hooks.
+ * @private
+ */
+ _destroyTouchHook() {
+ if (!this._hookDiv) {
+ return;
+ }
+ document.body.removeChild(this._hookDiv);
+ this._hookDiv = null;
+ }
+ /**
+ * Activating will cause the Accessibility layer to be shown.
+ * This is called when a user presses the tab key.
+ * @private
+ */
+ _activate() {
+ if (this._isActive) {
+ return;
+ }
+ this._isActive = true;
+ if (!this._div) {
+ this._div = document.createElement("div");
+ this._div.style.position = "absolute";
+ this._div.style.top = `${DIV_TOUCH_POS_X}px`;
+ this._div.style.left = `${DIV_TOUCH_POS_Y}px`;
+ this._div.style.pointerEvents = "none";
+ this._div.style.zIndex = DIV_TOUCH_ZINDEX.toString();
+ this._canvasObserver = new CanvasObserver({
+ domElement: this._div,
+ renderer: this._renderer
+ });
+ }
+ if (this._activateOnTab) {
+ this._onKeyDown = this._onKeyDown.bind(this);
+ globalThis.addEventListener("keydown", this._onKeyDown, false);
+ }
+ if (this._deactivateOnMouseMove) {
+ this._onMouseMove = this._onMouseMove.bind(this);
+ globalThis.document.addEventListener("mousemove", this._onMouseMove, true);
+ }
+ const canvas = this._renderer.view.canvas;
+ if (!canvas.parentNode) {
+ const observer = new MutationObserver(() => {
+ if (canvas.parentNode) {
+ observer.disconnect();
+ this._canvasObserver.ensureAttached();
+ this._initAccessibilitySetup();
+ }
+ });
+ observer.observe(document.body, { childList: true, subtree: true });
+ } else {
+ this._canvasObserver.ensureAttached();
+ this._initAccessibilitySetup();
+ }
+ }
+ // New method to handle initialization after div is ready
+ _initAccessibilitySetup() {
+ this._renderer.runners.postrender.add(this);
+ if (this._renderer.lastObjectRendered) {
+ this._updateAccessibleObjects(this._renderer.lastObjectRendered);
+ }
+ }
+ /**
+ * Deactivates the accessibility system. Removes listeners and accessibility elements.
+ * @private
+ */
+ _deactivate() {
+ if (!this._isActive || this._isMobileAccessibility) {
+ return;
+ }
+ this._isActive = false;
+ globalThis.document.removeEventListener("mousemove", this._onMouseMove, true);
+ if (this._activateOnTab) {
+ globalThis.addEventListener("keydown", this._onKeyDown, false);
+ }
+ this._renderer.runners.postrender.remove(this);
+ for (const child of this._children) {
+ if (child._accessibleDiv && child._accessibleDiv.parentNode) {
+ child._accessibleDiv.parentNode.removeChild(child._accessibleDiv);
+ child._accessibleDiv = null;
+ }
+ child._accessibleActive = false;
+ }
+ this._pool.forEach((div) => {
+ if (div.parentNode) {
+ div.parentNode.removeChild(div);
+ }
+ });
+ if (this._div && this._div.parentNode) {
+ this._div.parentNode.removeChild(this._div);
+ }
+ this._pool = [];
+ this._children = [];
+ }
+ /**
+ * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer.
+ * @private
+ * @param {Container} container - The Container to check.
+ */
+ _updateAccessibleObjects(container) {
+ if (!container.visible || !container.accessibleChildren) {
+ return;
+ }
+ if (container.accessible) {
+ if (!container._accessibleActive) {
+ this._addChild(container);
+ }
+ container._renderId = this._renderId;
+ }
+ const children = container.children;
+ if (children) {
+ for (let i = 0; i < children.length; i++) {
+ this._updateAccessibleObjects(children[i]);
+ }
+ }
+ }
+ /**
+ * Runner init called, view is available at this point.
+ * @ignore
+ */
+ init(options) {
+ const defaultOpts = _AccessibilitySystem.defaultOptions;
+ const mergedOptions = {
+ accessibilityOptions: __spreadValues$17(__spreadValues$17({}, defaultOpts), (options == null ? void 0 : options.accessibilityOptions) || {})
+ };
+ this.debug = mergedOptions.accessibilityOptions.debug;
+ this._activateOnTab = mergedOptions.accessibilityOptions.activateOnTab;
+ this._deactivateOnMouseMove = mergedOptions.accessibilityOptions.deactivateOnMouseMove;
+ if (mergedOptions.accessibilityOptions.enabledByDefault) {
+ this._activate();
+ } else if (this._activateOnTab) {
+ this._onKeyDown = this._onKeyDown.bind(this);
+ globalThis.addEventListener("keydown", this._onKeyDown, false);
+ }
+ this._renderer.runners.postrender.remove(this);
+ }
+ /**
+ * Updates the accessibility layer during rendering.
+ * - Removes divs for containers no longer in the scene
+ * - Updates the position and dimensions of the root div
+ * - Updates positions of active accessibility divs
+ * Only fires while the accessibility system is active.
+ * @ignore
+ */
+ postrender() {
+ const now = performance.now();
+ if (this._mobileInfo.android.device && now < this._androidUpdateCount) {
+ return;
+ }
+ this._androidUpdateCount = now + this._androidUpdateFrequency;
+ if (!this._renderer.renderingToScreen || !this._renderer.view.canvas) {
+ return;
+ }
+ const activeIds = /* @__PURE__ */ new Set();
+ if (this._renderer.lastObjectRendered) {
+ this._updateAccessibleObjects(this._renderer.lastObjectRendered);
+ for (const child of this._children) {
+ if (child._renderId === this._renderId) {
+ activeIds.add(this._children.indexOf(child));
+ }
+ }
+ }
+ for (let i = this._children.length - 1; i >= 0; i--) {
+ const child = this._children[i];
+ if (!activeIds.has(i)) {
+ if (child._accessibleDiv && child._accessibleDiv.parentNode) {
+ child._accessibleDiv.parentNode.removeChild(child._accessibleDiv);
+ this._pool.push(child._accessibleDiv);
+ child._accessibleDiv = null;
+ }
+ child._accessibleActive = false;
+ removeItems(this._children, i, 1);
+ }
+ }
+ if (this._renderer.renderingToScreen) {
+ this._canvasObserver.ensureAttached();
+ }
+ for (let i = 0; i < this._children.length; i++) {
+ const child = this._children[i];
+ if (!child._accessibleActive || !child._accessibleDiv) {
+ continue;
+ }
+ const div = child._accessibleDiv;
+ const hitArea = child.hitArea || child.getBounds().rectangle;
+ if (child.hitArea) {
+ const wt = child.worldTransform;
+ div.style.left = `${wt.tx + hitArea.x * wt.a}px`;
+ div.style.top = `${wt.ty + hitArea.y * wt.d}px`;
+ div.style.width = `${hitArea.width * wt.a}px`;
+ div.style.height = `${hitArea.height * wt.d}px`;
+ } else {
+ this._capHitArea(hitArea);
+ div.style.left = `${hitArea.x}px`;
+ div.style.top = `${hitArea.y}px`;
+ div.style.width = `${hitArea.width}px`;
+ div.style.height = `${hitArea.height}px`;
+ }
+ }
+ this._renderId++;
+ }
+ /**
+ * private function that will visually add the information to the
+ * accessibility div
+ * @param {HTMLElement} div -
+ */
+ _updateDebugHTML(div) {
+ div.innerHTML = `type: ${div.type} title : ${div.title} tabIndex: ${div.tabIndex}`;
+ }
+ /**
+ * Adjust the hit area based on the bounds of a display object
+ * @param {Rectangle} hitArea - Bounds of the child
+ */
+ _capHitArea(hitArea) {
+ if (hitArea.x < 0) {
+ hitArea.width += hitArea.x;
+ hitArea.x = 0;
+ }
+ if (hitArea.y < 0) {
+ hitArea.height += hitArea.y;
+ hitArea.y = 0;
+ }
+ const { width: viewWidth, height: viewHeight } = this._renderer;
+ if (hitArea.x + hitArea.width > viewWidth) {
+ hitArea.width = viewWidth - hitArea.x;
+ }
+ if (hitArea.y + hitArea.height > viewHeight) {
+ hitArea.height = viewHeight - hitArea.y;
+ }
+ }
+ /**
+ * Creates or reuses a div element for a Container and adds it to the accessibility layer.
+ * Sets up ARIA attributes, event listeners, and positioning based on the container's properties.
+ * @private
+ * @param {Container} container - The child to make accessible.
+ */
+ _addChild(container) {
+ let div = this._pool.pop();
+ if (!div) {
+ if (container.accessibleType === "button") {
+ div = document.createElement("button");
+ } else {
+ div = document.createElement(container.accessibleType);
+ div.style.cssText = `
+ color: transparent;
+ pointer-events: none;
+ padding: 0;
+ margin: 0;
+ border: 0;
+ outline: 0;
+ background: transparent;
+ box-sizing: border-box;
+ user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ `;
+ if (container.accessibleText) {
+ div.innerText = container.accessibleText;
+ }
+ }
+ div.style.width = `${DIV_TOUCH_SIZE}px`;
+ div.style.height = `${DIV_TOUCH_SIZE}px`;
+ div.style.backgroundColor = this.debug ? "rgba(255,255,255,0.5)" : "transparent";
+ div.style.position = "absolute";
+ div.style.zIndex = DIV_TOUCH_ZINDEX.toString();
+ div.style.borderStyle = "none";
+ if (navigator.userAgent.toLowerCase().includes("chrome")) {
+ div.setAttribute("aria-live", "off");
+ } else {
+ div.setAttribute("aria-live", "polite");
+ }
+ if (navigator.userAgent.match(/rv:.*Gecko\//)) {
+ div.setAttribute("aria-relevant", "additions");
+ } else {
+ div.setAttribute("aria-relevant", "text");
+ }
+ div.addEventListener("click", this._onClick.bind(this));
+ div.addEventListener("focus", this._onFocus.bind(this));
+ div.addEventListener("focusout", this._onFocusOut.bind(this));
+ }
+ div.style.pointerEvents = container.accessiblePointerEvents;
+ div.type = container.accessibleType;
+ if (container.accessibleTitle && container.accessibleTitle !== null) {
+ div.title = container.accessibleTitle;
+ } else if (!container.accessibleHint || container.accessibleHint === null) {
+ div.title = `container ${container.tabIndex}`;
+ }
+ if (container.accessibleHint && container.accessibleHint !== null) {
+ div.setAttribute("aria-label", container.accessibleHint);
+ }
+ if (container.interactive) {
+ div.tabIndex = container.tabIndex;
+ } else {
+ div.tabIndex = 0;
+ }
+ if (this.debug) {
+ this._updateDebugHTML(div);
+ }
+ container._accessibleActive = true;
+ container._accessibleDiv = div;
+ div.container = container;
+ this._children.push(container);
+ this._div.appendChild(container._accessibleDiv);
+ }
+ /**
+ * Dispatch events with the EventSystem.
+ * @param e
+ * @param type
+ * @private
+ */
+ _dispatchEvent(e, type) {
+ const { container: target } = e.target;
+ const boundary = this._renderer.events.rootBoundary;
+ const event = Object.assign(new FederatedEvent(boundary), { target });
+ boundary.rootTarget = this._renderer.lastObjectRendered;
+ type.forEach((type2) => boundary.dispatchEvent(event, type2));
+ }
+ /**
+ * Maps the div button press to pixi's EventSystem (click)
+ * @private
+ * @param {MouseEvent} e - The click event.
+ */
+ _onClick(e) {
+ this._dispatchEvent(e, ["click", "pointertap", "tap"]);
+ }
+ /**
+ * Maps the div focus events to pixi's EventSystem (mouseover)
+ * @private
+ * @param {FocusEvent} e - The focus event.
+ */
+ _onFocus(e) {
+ if (!e.target.getAttribute("aria-live")) {
+ e.target.setAttribute("aria-live", "assertive");
+ }
+ this._dispatchEvent(e, ["mouseover"]);
+ }
+ /**
+ * Maps the div focus events to pixi's EventSystem (mouseout)
+ * @private
+ * @param {FocusEvent} e - The focusout event.
+ */
+ _onFocusOut(e) {
+ if (!e.target.getAttribute("aria-live")) {
+ e.target.setAttribute("aria-live", "polite");
+ }
+ this._dispatchEvent(e, ["mouseout"]);
+ }
+ /**
+ * Is called when a key is pressed
+ * @private
+ * @param {KeyboardEvent} e - The keydown event.
+ */
+ _onKeyDown(e) {
+ if (e.keyCode !== KEY_CODE_TAB || !this._activateOnTab) {
+ return;
+ }
+ this._activate();
+ }
+ /**
+ * Is called when the mouse moves across the renderer element
+ * @private
+ * @param {MouseEvent} e - The mouse event.
+ */
+ _onMouseMove(e) {
+ if (e.movementX === 0 && e.movementY === 0) {
+ return;
+ }
+ this._deactivate();
+ }
+ /**
+ * Destroys the accessibility system. Removes all elements and listeners.
+ * > [!IMPORTANT] This is typically called automatically when the {@link Application} is destroyed.
+ * > A typically user should not need to call this method directly.
+ */
+ destroy() {
+ var _a;
+ this._deactivate();
+ this._destroyTouchHook();
+ (_a = this._canvasObserver) == null ? void 0 : _a.destroy();
+ this._canvasObserver = null;
+ this._div = null;
+ this._pool = null;
+ this._children = null;
+ this._renderer = null;
+ if (this._activateOnTab) {
+ globalThis.removeEventListener("keydown", this._onKeyDown);
+ }
+ }
+ /**
+ * Enables or disables the accessibility system.
+ * @param enabled - Whether to enable or disable accessibility.
+ * @example
+ * ```js
+ * app.renderer.accessibility.setAccessibilityEnabled(true); // Enable accessibility
+ * app.renderer.accessibility.setAccessibilityEnabled(false); // Disable accessibility
+ * ```
+ */
+ setAccessibilityEnabled(enabled) {
+ if (enabled) {
+ this._activate();
+ } else {
+ this._deactivate();
+ }
+ }
+ };
+ /** @ignore */
+ _AccessibilitySystem.extension = {
+ type: [
+ ExtensionType.WebGLSystem,
+ ExtensionType.WebGPUSystem
+ ],
+ name: "accessibility"
+ };
+ /**
+ * The default options used by the system.
+ * You can set these before initializing the {@link Application} to change the default behavior.
+ * @example
+ * ```js
+ * import { AccessibilitySystem } from 'pixi.js';
+ *
+ * AccessibilitySystem.defaultOptions.enabledByDefault = true;
+ *
+ * const app = new Application()
+ * app.init()
+ * ```
+ */
+ _AccessibilitySystem.defaultOptions = {
+ /**
+ * Whether to enable accessibility features on initialization
+ * @default false
+ */
+ enabledByDefault: false,
+ /**
+ * Whether to visually show the accessibility divs for debugging
+ * @default false
+ */
+ debug: false,
+ /**
+ * Whether to activate accessibility when tab key is pressed
+ * @default true
+ */
+ activateOnTab: true,
+ /**
+ * Whether to deactivate accessibility when mouse moves
+ * @default true
+ */
+ deactivateOnMouseMove: true
+ };
+ let AccessibilitySystem = _AccessibilitySystem;
+
+ "use strict";
+ const accessibilityTarget = {
+ accessible: false,
+ accessibleTitle: null,
+ accessibleHint: null,
+ tabIndex: 0,
+ accessibleType: "button",
+ accessibleText: null,
+ accessiblePointerEvents: "auto",
+ accessibleChildren: true,
+ _accessibleActive: false,
+ _accessibleDiv: null,
+ _renderId: -1
+ };
+
+ "use strict";
+ extensions.add(AccessibilitySystem);
+ extensions.mixin(Container, accessibilityTarget);
+
+ "use strict";
+ class ResizePlugin {
+ /**
+ * Initialize the plugin with scope of application instance
+ * @private
+ * @param {object} [options] - See application options
+ */
+ static init(options) {
+ Object.defineProperty(
+ this,
+ "resizeTo",
+ {
+ set(dom) {
+ globalThis.removeEventListener("resize", this.queueResize);
+ this._resizeTo = dom;
+ if (dom) {
+ globalThis.addEventListener("resize", this.queueResize);
+ this.resize();
+ }
+ },
+ get() {
+ return this._resizeTo;
+ }
+ }
+ );
+ this.queueResize = () => {
+ if (!this._resizeTo) {
+ return;
+ }
+ this._cancelResize();
+ this._resizeId = requestAnimationFrame(() => this.resize());
+ };
+ this._cancelResize = () => {
+ if (this._resizeId) {
+ cancelAnimationFrame(this._resizeId);
+ this._resizeId = null;
+ }
+ };
+ this.resize = () => {
+ if (!this._resizeTo) {
+ return;
+ }
+ this._cancelResize();
+ let width;
+ let height;
+ if (this._resizeTo === globalThis.window) {
+ width = globalThis.innerWidth;
+ height = globalThis.innerHeight;
+ } else {
+ const { clientWidth, clientHeight } = this._resizeTo;
+ width = clientWidth;
+ height = clientHeight;
+ }
+ this.renderer.resize(width, height);
+ this.render();
+ };
+ this._resizeId = null;
+ this._resizeTo = null;
+ this.resizeTo = options.resizeTo || null;
+ }
+ /**
+ * Clean up the ticker, scoped to application
+ * @private
+ */
+ static destroy() {
+ globalThis.removeEventListener("resize", this.queueResize);
+ this._cancelResize();
+ this._cancelResize = null;
+ this.queueResize = null;
+ this.resizeTo = null;
+ this.resize = null;
+ }
+ }
+ /** @ignore */
+ ResizePlugin.extension = ExtensionType.Application;
+
+ "use strict";
+ class TickerPlugin {
+ /**
+ * Initialize the plugin with scope of application instance
+ * @private
+ * @param {object} [options] - See application options
+ */
+ static init(options) {
+ options = Object.assign({
+ autoStart: true,
+ sharedTicker: false
+ }, options);
+ Object.defineProperty(
+ this,
+ "ticker",
+ {
+ set(ticker) {
+ if (this._ticker) {
+ this._ticker.remove(this.render, this);
+ }
+ this._ticker = ticker;
+ if (ticker) {
+ ticker.add(this.render, this, UPDATE_PRIORITY.LOW);
+ }
+ },
+ get() {
+ return this._ticker;
+ }
+ }
+ );
+ this.stop = () => {
+ this._ticker.stop();
+ };
+ this.start = () => {
+ this._ticker.start();
+ };
+ this._ticker = null;
+ this.ticker = options.sharedTicker ? Ticker.shared : new Ticker();
+ if (options.autoStart) {
+ this.start();
+ }
+ }
+ /**
+ * Clean up the ticker, scoped to application.
+ * @private
+ */
+ static destroy() {
+ if (this._ticker) {
+ const oldTicker = this._ticker;
+ this.ticker = null;
+ oldTicker.destroy();
+ }
+ }
+ }
+ /** @ignore */
+ TickerPlugin.extension = ExtensionType.Application;
+
+ "use strict";
+ extensions.add(ResizePlugin);
+ extensions.add(TickerPlugin);
+
+ "use strict";
+ class EventsTickerClass {
+ constructor() {
+ /** The frequency that fake events will be fired. */
+ this.interactionFrequency = 10;
+ this._deltaTime = 0;
+ this._didMove = false;
+ this._tickerAdded = false;
+ this._pauseUpdate = true;
+ }
+ /**
+ * Initializes the event ticker.
+ * @param events - The event system.
+ */
+ init(events) {
+ this.removeTickerListener();
+ this.events = events;
+ this.interactionFrequency = 10;
+ this._deltaTime = 0;
+ this._didMove = false;
+ this._tickerAdded = false;
+ this._pauseUpdate = true;
+ }
+ /** Whether to pause the update checks or not. */
+ get pauseUpdate() {
+ return this._pauseUpdate;
+ }
+ set pauseUpdate(paused) {
+ this._pauseUpdate = paused;
+ }
+ /** Adds the ticker listener. */
+ addTickerListener() {
+ if (this._tickerAdded || !this.domElement) {
+ return;
+ }
+ Ticker.system.add(this._tickerUpdate, this, UPDATE_PRIORITY.INTERACTION);
+ this._tickerAdded = true;
+ }
+ /** Removes the ticker listener. */
+ removeTickerListener() {
+ if (!this._tickerAdded) {
+ return;
+ }
+ Ticker.system.remove(this._tickerUpdate, this);
+ this._tickerAdded = false;
+ }
+ /** Sets flag to not fire extra events when the user has already moved there mouse */
+ pointerMoved() {
+ this._didMove = true;
+ }
+ /** Updates the state of interactive objects. */
+ _update() {
+ if (!this.domElement || this._pauseUpdate) {
+ return;
+ }
+ if (this._didMove) {
+ this._didMove = false;
+ return;
+ }
+ const rootPointerEvent = this.events["_rootPointerEvent"];
+ if (this.events.supportsTouchEvents && rootPointerEvent.pointerType === "touch") {
+ return;
+ }
+ globalThis.document.dispatchEvent(this.events.supportsPointerEvents ? new PointerEvent("pointermove", {
+ clientX: rootPointerEvent.clientX,
+ clientY: rootPointerEvent.clientY,
+ pointerType: rootPointerEvent.pointerType,
+ pointerId: rootPointerEvent.pointerId
+ }) : new MouseEvent("mousemove", {
+ clientX: rootPointerEvent.clientX,
+ clientY: rootPointerEvent.clientY
+ }));
+ }
+ /**
+ * Updates the state of interactive objects if at least {@link interactionFrequency}
+ * milliseconds have passed since the last invocation.
+ *
+ * Invoked by a throttled ticker update from {@link Ticker.system}.
+ * @param ticker - The throttled ticker.
+ */
+ _tickerUpdate(ticker) {
+ this._deltaTime += ticker.deltaTime;
+ if (this._deltaTime < this.interactionFrequency) {
+ return;
+ }
+ this._deltaTime = 0;
+ this._update();
+ }
+ /** Destroys the event ticker. */
+ destroy() {
+ this.removeTickerListener();
+ this.events = null;
+ this.domElement = null;
+ this._deltaTime = 0;
+ this._didMove = false;
+ this._tickerAdded = false;
+ this._pauseUpdate = true;
+ }
+ }
+ const EventsTicker = new EventsTickerClass();
+
+ "use strict";
+ class FederatedMouseEvent extends FederatedEvent {
+ constructor() {
+ super(...arguments);
+ /** The coordinates of the mouse event relative to the canvas. */
+ this.client = new Point();
+ /** The movement in this pointer relative to the last `mousemove` event. */
+ this.movement = new Point();
+ /** The offset of the pointer coordinates w.r.t. target Container in world space. This is not supported at the moment. */
+ this.offset = new Point();
+ /** The pointer coordinates in world space. */
+ this.global = new Point();
+ /**
+ * The pointer coordinates in the renderer's {@link AbstractRenderer.screen screen}. This has slightly
+ * different semantics than native PointerEvent screenX/screenY.
+ */
+ this.screen = new Point();
+ }
+ /** @readonly */
+ get clientX() {
+ return this.client.x;
+ }
+ /** @readonly */
+ get clientY() {
+ return this.client.y;
+ }
+ /**
+ * Alias for {@link FederatedMouseEvent.clientX this.clientX}.
+ * @readonly
+ */
+ get x() {
+ return this.clientX;
+ }
+ /**
+ * Alias for {@link FederatedMouseEvent.clientY this.clientY}.
+ * @readonly
+ */
+ get y() {
+ return this.clientY;
+ }
+ /** @readonly */
+ get movementX() {
+ return this.movement.x;
+ }
+ /** @readonly */
+ get movementY() {
+ return this.movement.y;
+ }
+ /** @readonly */
+ get offsetX() {
+ return this.offset.x;
+ }
+ /** @readonly */
+ get offsetY() {
+ return this.offset.y;
+ }
+ /** @readonly */
+ get globalX() {
+ return this.global.x;
+ }
+ /** @readonly */
+ get globalY() {
+ return this.global.y;
+ }
+ /**
+ * The pointer coordinates in the renderer's screen. Alias for `screen.x`.
+ * @readonly
+ */
+ get screenX() {
+ return this.screen.x;
+ }
+ /**
+ * The pointer coordinates in the renderer's screen. Alias for `screen.y`.
+ * @readonly
+ */
+ get screenY() {
+ return this.screen.y;
+ }
+ /**
+ * Converts global coordinates into container-local coordinates.
+ *
+ * This method transforms coordinates from world space to a container's local space,
+ * useful for precise positioning and hit testing.
+ * @param container - The Container to get local coordinates for
+ * @param point - Optional Point object to store the result. If not provided, a new Point will be created
+ * @param globalPos - Optional custom global coordinates. If not provided, the event's global position is used
+ * @returns The local coordinates as a Point object
+ * @example
+ * ```ts
+ * // Basic usage - get local coordinates relative to a container
+ * sprite.on('pointermove', (event: FederatedMouseEvent) => {
+ * // Get position relative to the sprite
+ * const localPos = event.getLocalPosition(sprite);
+ * console.log('Local position:', localPos.x, localPos.y);
+ * });
+ * // Using custom global coordinates
+ * const customGlobal = new Point(100, 100);
+ * sprite.on('pointermove', (event: FederatedMouseEvent) => {
+ * // Transform custom coordinates
+ * const localPos = event.getLocalPosition(sprite, undefined, customGlobal);
+ * console.log('Custom local position:', localPos.x, localPos.y);
+ * });
+ * ```
+ * @see {@link Container.worldTransform} For the transformation matrix
+ * @see {@link Point} For the point class used to store coordinates
+ */
+ getLocalPosition(container, point, globalPos) {
+ return container.worldTransform.applyInverse(globalPos || this.global, point);
+ }
+ /**
+ * Whether the modifier key was pressed when this event natively occurred.
+ * @param key - The modifier key.
+ */
+ getModifierState(key) {
+ return "getModifierState" in this.nativeEvent && this.nativeEvent.getModifierState(key);
+ }
+ /**
+ * Not supported.
+ * @param _typeArg
+ * @param _canBubbleArg
+ * @param _cancelableArg
+ * @param _viewArg
+ * @param _detailArg
+ * @param _screenXArg
+ * @param _screenYArg
+ * @param _clientXArg
+ * @param _clientYArg
+ * @param _ctrlKeyArg
+ * @param _altKeyArg
+ * @param _shiftKeyArg
+ * @param _metaKeyArg
+ * @param _buttonArg
+ * @param _relatedTargetArg
+ * @deprecated since 7.0.0
+ * @ignore
+ */
+ // eslint-disable-next-line max-params
+ initMouseEvent(_typeArg, _canBubbleArg, _cancelableArg, _viewArg, _detailArg, _screenXArg, _screenYArg, _clientXArg, _clientYArg, _ctrlKeyArg, _altKeyArg, _shiftKeyArg, _metaKeyArg, _buttonArg, _relatedTargetArg) {
+ throw new Error("Method not implemented.");
+ }
+ }
+
+ "use strict";
+ class FederatedPointerEvent extends FederatedMouseEvent {
+ constructor() {
+ super(...arguments);
+ /**
+ * The width of the pointer's contact along the x-axis, measured in CSS pixels.
+ * radiusX of TouchEvents will be represented by this value.
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width
+ */
+ this.width = 0;
+ /**
+ * The height of the pointer's contact along the y-axis, measured in CSS pixels.
+ * radiusY of TouchEvents will be represented by this value.
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height
+ */
+ this.height = 0;
+ /**
+ * Indicates whether or not the pointer device that created the event is the primary pointer.
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary
+ */
+ this.isPrimary = false;
+ }
+ /**
+ * Only included for completeness for now
+ * @ignore
+ */
+ getCoalescedEvents() {
+ if (this.type === "pointermove" || this.type === "mousemove" || this.type === "touchmove") {
+ return [this];
+ }
+ return [];
+ }
+ /**
+ * Only included for completeness for now
+ * @ignore
+ */
+ getPredictedEvents() {
+ throw new Error("getPredictedEvents is not supported!");
+ }
+ }
+
+ "use strict";
+ class FederatedWheelEvent extends FederatedMouseEvent {
+ constructor() {
+ super(...arguments);
+ /**
+ * Units specified in pixels.
+ * @ignore
+ */
+ this.DOM_DELTA_PIXEL = 0;
+ /**
+ * Units specified in lines.
+ * @ignore
+ */
+ this.DOM_DELTA_LINE = 1;
+ /**
+ * Units specified in pages.
+ * @ignore
+ */
+ this.DOM_DELTA_PAGE = 2;
+ }
+ }
+ /**
+ * Units specified in pixels.
+ * @ignore
+ */
+ FederatedWheelEvent.DOM_DELTA_PIXEL = 0;
+ /**
+ * Units specified in lines.
+ * @ignore
+ */
+ FederatedWheelEvent.DOM_DELTA_LINE = 1;
+ /**
+ * Units specified in pages.
+ * @ignore
+ */
+ FederatedWheelEvent.DOM_DELTA_PAGE = 2;
+
+ "use strict";
+ const PROPAGATION_LIMIT = 2048;
+ const tempHitLocation = new Point();
+ const tempLocalMapping = new Point();
+ class EventBoundary {
+ /**
+ * @param rootTarget - The holder of the event boundary.
+ */
+ constructor(rootTarget) {
+ /**
+ * Emits events after they were dispatched into the scene graph.
+ *
+ * This can be used for global events listening, regardless of the scene graph being used. It should
+ * not be used by interactive libraries for normal use.
+ *
+ * Special events that do not bubble all the way to the root target are not emitted from here,
+ * e.g. pointerenter, pointerleave, click.
+ */
+ this.dispatch = new EventEmitter();
+ /**
+ * This flag would emit `pointermove`, `touchmove`, and `mousemove` events on all Containers.
+ *
+ * The `moveOnAll` semantics mirror those of earlier versions of PixiJS. This was disabled in favor of
+ * the Pointer Event API's approach.
+ */
+ this.moveOnAll = false;
+ /** Enables the global move events. `globalpointermove`, `globaltouchmove`, and `globalmousemove` */
+ this.enableGlobalMoveEvents = true;
+ /**
+ * State object for mapping methods.
+ * @see EventBoundary#trackingData
+ */
+ this.mappingState = {
+ trackingData: {}
+ };
+ /**
+ * The event pool maps event constructors to an free pool of instances of those specific events.
+ * @see EventBoundary#allocateEvent
+ * @see EventBoundary#freeEvent
+ */
+ this.eventPool = /* @__PURE__ */ new Map();
+ /** Every interactive element gathered from the scene. Only used in `pointermove` */
+ this._allInteractiveElements = [];
+ /** Every element that passed the hit test. Only used in `pointermove` */
+ this._hitElements = [];
+ /** Whether or not to collect all the interactive elements from the scene. Enabled in `pointermove` */
+ this._isPointerMoveEvent = false;
+ this.rootTarget = rootTarget;
+ this.hitPruneFn = this.hitPruneFn.bind(this);
+ this.hitTestFn = this.hitTestFn.bind(this);
+ this.mapPointerDown = this.mapPointerDown.bind(this);
+ this.mapPointerMove = this.mapPointerMove.bind(this);
+ this.mapPointerOut = this.mapPointerOut.bind(this);
+ this.mapPointerOver = this.mapPointerOver.bind(this);
+ this.mapPointerUp = this.mapPointerUp.bind(this);
+ this.mapPointerUpOutside = this.mapPointerUpOutside.bind(this);
+ this.mapWheel = this.mapWheel.bind(this);
+ this.mappingTable = {};
+ this.addEventMapping("pointerdown", this.mapPointerDown);
+ this.addEventMapping("pointermove", this.mapPointerMove);
+ this.addEventMapping("pointerout", this.mapPointerOut);
+ this.addEventMapping("pointerleave", this.mapPointerOut);
+ this.addEventMapping("pointerover", this.mapPointerOver);
+ this.addEventMapping("pointerup", this.mapPointerUp);
+ this.addEventMapping("pointerupoutside", this.mapPointerUpOutside);
+ this.addEventMapping("wheel", this.mapWheel);
+ }
+ /**
+ * Adds an event mapping for the event `type` handled by `fn`.
+ *
+ * Event mappings can be used to implement additional or custom events. They take an event
+ * coming from the upstream scene (or directly from the {@link EventSystem}) and dispatch new downstream events
+ * generally trickling down and bubbling up to {@link EventBoundary.rootTarget this.rootTarget}.
+ *
+ * To modify the semantics of existing events, the built-in mapping methods of EventBoundary should be overridden
+ * instead.
+ * @param type - The type of upstream event to map.
+ * @param fn - The mapping method. The context of this function must be bound manually, if desired.
+ */
+ addEventMapping(type, fn) {
+ if (!this.mappingTable[type]) {
+ this.mappingTable[type] = [];
+ }
+ this.mappingTable[type].push({
+ fn,
+ priority: 0
+ });
+ this.mappingTable[type].sort((a, b) => a.priority - b.priority);
+ }
+ /**
+ * Dispatches the given event
+ * @param e - The event to dispatch.
+ * @param type - The type of event to dispatch. Defaults to `e.type`.
+ */
+ dispatchEvent(e, type) {
+ e.propagationStopped = false;
+ e.propagationImmediatelyStopped = false;
+ this.propagate(e, type);
+ this.dispatch.emit(type || e.type, e);
+ }
+ /**
+ * Maps the given upstream event through the event boundary and propagates it downstream.
+ * @param e - The event to map.
+ */
+ mapEvent(e) {
+ if (!this.rootTarget) {
+ return;
+ }
+ const mappers = this.mappingTable[e.type];
+ if (mappers) {
+ for (let i = 0, j = mappers.length; i < j; i++) {
+ mappers[i].fn(e);
+ }
+ } else {
+ warn(`[EventBoundary]: Event mapping not defined for ${e.type}`);
+ }
+ }
+ /**
+ * Finds the Container that is the target of a event at the given coordinates.
+ *
+ * The passed (x,y) coordinates are in the world space above this event boundary.
+ * @param x - The x coordinate of the event.
+ * @param y - The y coordinate of the event.
+ */
+ hitTest(x, y) {
+ EventsTicker.pauseUpdate = true;
+ const useMove = this._isPointerMoveEvent && this.enableGlobalMoveEvents;
+ const fn = useMove ? "hitTestMoveRecursive" : "hitTestRecursive";
+ const invertedPath = this[fn](
+ this.rootTarget,
+ this.rootTarget.eventMode,
+ tempHitLocation.set(x, y),
+ this.hitTestFn,
+ this.hitPruneFn
+ );
+ return invertedPath && invertedPath[0];
+ }
+ /**
+ * Propagate the passed event from from {@link EventBoundary.rootTarget this.rootTarget} to its
+ * target `e.target`.
+ * @param e - The event to propagate.
+ * @param type - The type of event to propagate. Defaults to `e.type`.
+ */
+ propagate(e, type) {
+ if (!e.target) {
+ return;
+ }
+ const composedPath = e.composedPath();
+ e.eventPhase = e.CAPTURING_PHASE;
+ for (let i = 0, j = composedPath.length - 1; i < j; i++) {
+ e.currentTarget = composedPath[i];
+ this.notifyTarget(e, type);
+ if (e.propagationStopped || e.propagationImmediatelyStopped)
+ return;
+ }
+ e.eventPhase = e.AT_TARGET;
+ e.currentTarget = e.target;
+ this.notifyTarget(e, type);
+ if (e.propagationStopped || e.propagationImmediatelyStopped)
+ return;
+ e.eventPhase = e.BUBBLING_PHASE;
+ for (let i = composedPath.length - 2; i >= 0; i--) {
+ e.currentTarget = composedPath[i];
+ this.notifyTarget(e, type);
+ if (e.propagationStopped || e.propagationImmediatelyStopped)
+ return;
+ }
+ }
+ /**
+ * Emits the event `e` to all interactive containers. The event is propagated in the bubbling phase always.
+ *
+ * This is used in the `globalpointermove` event.
+ * @param e - The emitted event.
+ * @param type - The listeners to notify.
+ * @param targets - The targets to notify.
+ */
+ all(e, type, targets = this._allInteractiveElements) {
+ if (targets.length === 0)
+ return;
+ e.eventPhase = e.BUBBLING_PHASE;
+ const events = Array.isArray(type) ? type : [type];
+ for (let i = targets.length - 1; i >= 0; i--) {
+ events.forEach((event) => {
+ e.currentTarget = targets[i];
+ this.notifyTarget(e, event);
+ });
+ }
+ }
+ /**
+ * Finds the propagation path from {@link EventBoundary.rootTarget rootTarget} to the passed
+ * `target`. The last element in the path is `target`.
+ * @param target - The target to find the propagation path to.
+ */
+ propagationPath(target) {
+ const propagationPath = [target];
+ for (let i = 0; i < PROPAGATION_LIMIT && (target !== this.rootTarget && target.parent); i++) {
+ if (!target.parent) {
+ throw new Error("Cannot find propagation path to disconnected target");
+ }
+ propagationPath.push(target.parent);
+ target = target.parent;
+ }
+ propagationPath.reverse();
+ return propagationPath;
+ }
+ hitTestMoveRecursive(currentTarget, eventMode, location, testFn, pruneFn, ignore = false) {
+ let shouldReturn = false;
+ if (this._interactivePrune(currentTarget))
+ return null;
+ if (currentTarget.eventMode === "dynamic" || eventMode === "dynamic") {
+ EventsTicker.pauseUpdate = false;
+ }
+ if (currentTarget.interactiveChildren && currentTarget.children) {
+ const children = currentTarget.children;
+ for (let i = children.length - 1; i >= 0; i--) {
+ const child = children[i];
+ const nestedHit = this.hitTestMoveRecursive(
+ child,
+ this._isInteractive(eventMode) ? eventMode : child.eventMode,
+ location,
+ testFn,
+ pruneFn,
+ ignore || pruneFn(currentTarget, location)
+ );
+ if (nestedHit) {
+ if (nestedHit.length > 0 && !nestedHit[nestedHit.length - 1].parent) {
+ continue;
+ }
+ const isInteractive = currentTarget.isInteractive();
+ if (nestedHit.length > 0 || isInteractive) {
+ if (isInteractive)
+ this._allInteractiveElements.push(currentTarget);
+ nestedHit.push(currentTarget);
+ }
+ if (this._hitElements.length === 0)
+ this._hitElements = nestedHit;
+ shouldReturn = true;
+ }
+ }
+ }
+ const isInteractiveMode = this._isInteractive(eventMode);
+ const isInteractiveTarget = currentTarget.isInteractive();
+ if (isInteractiveTarget && isInteractiveTarget)
+ this._allInteractiveElements.push(currentTarget);
+ if (ignore || this._hitElements.length > 0)
+ return null;
+ if (shouldReturn)
+ return this._hitElements;
+ if (isInteractiveMode && (!pruneFn(currentTarget, location) && testFn(currentTarget, location))) {
+ return isInteractiveTarget ? [currentTarget] : [];
+ }
+ return null;
+ }
+ /**
+ * Recursive implementation for {@link EventBoundary.hitTest hitTest}.
+ * @param currentTarget - The Container that is to be hit tested.
+ * @param eventMode - The event mode for the `currentTarget` or one of its parents.
+ * @param location - The location that is being tested for overlap.
+ * @param testFn - Callback that determines whether the target passes hit testing. This callback
+ * can assume that `pruneFn` failed to prune the container.
+ * @param pruneFn - Callback that determiness whether the target and all of its children
+ * cannot pass the hit test. It is used as a preliminary optimization to prune entire subtrees
+ * of the scene graph.
+ * @returns An array holding the hit testing target and all its ancestors in order. The first element
+ * is the target itself and the last is {@link EventBoundary.rootTarget rootTarget}. This is the opposite
+ * order w.r.t. the propagation path. If no hit testing target is found, null is returned.
+ */
+ hitTestRecursive(currentTarget, eventMode, location, testFn, pruneFn) {
+ if (this._interactivePrune(currentTarget) || pruneFn(currentTarget, location)) {
+ return null;
+ }
+ if (currentTarget.eventMode === "dynamic" || eventMode === "dynamic") {
+ EventsTicker.pauseUpdate = false;
+ }
+ if (currentTarget.interactiveChildren && currentTarget.children) {
+ const children = currentTarget.children;
+ const relativeLocation = location;
+ for (let i = children.length - 1; i >= 0; i--) {
+ const child = children[i];
+ const nestedHit = this.hitTestRecursive(
+ child,
+ this._isInteractive(eventMode) ? eventMode : child.eventMode,
+ relativeLocation,
+ testFn,
+ pruneFn
+ );
+ if (nestedHit) {
+ if (nestedHit.length > 0 && !nestedHit[nestedHit.length - 1].parent) {
+ continue;
+ }
+ const isInteractive = currentTarget.isInteractive();
+ if (nestedHit.length > 0 || isInteractive)
+ nestedHit.push(currentTarget);
+ return nestedHit;
+ }
+ }
+ }
+ const isInteractiveMode = this._isInteractive(eventMode);
+ const isInteractiveTarget = currentTarget.isInteractive();
+ if (isInteractiveMode && testFn(currentTarget, location)) {
+ return isInteractiveTarget ? [currentTarget] : [];
+ }
+ return null;
+ }
+ _isInteractive(int) {
+ return int === "static" || int === "dynamic";
+ }
+ _interactivePrune(container) {
+ if (!container || !container.visible || !container.renderable || !container.measurable) {
+ return true;
+ }
+ if (container.eventMode === "none") {
+ return true;
+ }
+ if (container.eventMode === "passive" && !container.interactiveChildren) {
+ return true;
+ }
+ return false;
+ }
+ /**
+ * Checks whether the container or any of its children cannot pass the hit test at all.
+ *
+ * {@link EventBoundary}'s implementation uses the {@link Container.hitArea hitArea}
+ * and {@link Container._maskEffect} for pruning.
+ * @param container - The container to prune.
+ * @param location - The location to test for overlap.
+ */
+ hitPruneFn(container, location) {
+ if (container.hitArea) {
+ container.worldTransform.applyInverse(location, tempLocalMapping);
+ if (!container.hitArea.contains(tempLocalMapping.x, tempLocalMapping.y)) {
+ return true;
+ }
+ }
+ if (container.effects && container.effects.length) {
+ for (let i = 0; i < container.effects.length; i++) {
+ const effect = container.effects[i];
+ if (effect.containsPoint) {
+ const effectContainsPoint = effect.containsPoint(location, this.hitTestFn);
+ if (!effectContainsPoint) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+ /**
+ * Checks whether the container passes hit testing for the given location.
+ * @param container - The container to test.
+ * @param location - The location to test for overlap.
+ * @returns - Whether `container` passes hit testing for `location`.
+ */
+ hitTestFn(container, location) {
+ if (container.hitArea) {
+ return true;
+ }
+ if (container == null ? void 0 : container.containsPoint) {
+ container.worldTransform.applyInverse(location, tempLocalMapping);
+ return container.containsPoint(tempLocalMapping);
+ }
+ return false;
+ }
+ /**
+ * Notify all the listeners to the event's `currentTarget`.
+ *
+ * If the `currentTarget` contains the property `on`, then it is called here,
+ * simulating the behavior from version 6.x and prior.
+ * @param e - The event passed to the target.
+ * @param type - The type of event to notify. Defaults to `e.type`.
+ */
+ notifyTarget(e, type) {
+ var _a, _b;
+ if (!e.currentTarget.isInteractive()) {
+ return;
+ }
+ type != null ? type : type = e.type;
+ const handlerKey = `on${type}`;
+ (_b = (_a = e.currentTarget)[handlerKey]) == null ? void 0 : _b.call(_a, e);
+ const key = e.eventPhase === e.CAPTURING_PHASE || e.eventPhase === e.AT_TARGET ? `${type}capture` : type;
+ this._notifyListeners(e, key);
+ if (e.eventPhase === e.AT_TARGET) {
+ this._notifyListeners(e, type);
+ }
+ }
+ /**
+ * Maps the upstream `pointerdown` events to a downstream `pointerdown` event.
+ *
+ * `touchstart`, `rightdown`, `mousedown` events are also dispatched for specific pointer types.
+ * @param from - The upstream `pointerdown` event.
+ */
+ mapPointerDown(from) {
+ if (!(from instanceof FederatedPointerEvent)) {
+ warn("EventBoundary cannot map a non-pointer event as a pointer event");
+ return;
+ }
+ const e = this.createPointerEvent(from);
+ this.dispatchEvent(e, "pointerdown");
+ if (e.pointerType === "touch") {
+ this.dispatchEvent(e, "touchstart");
+ } else if (e.pointerType === "mouse" || e.pointerType === "pen") {
+ const isRightButton = e.button === 2;
+ this.dispatchEvent(e, isRightButton ? "rightdown" : "mousedown");
+ }
+ const trackingData = this.trackingData(from.pointerId);
+ trackingData.pressTargetsByButton[from.button] = e.composedPath();
+ this.freeEvent(e);
+ }
+ /**
+ * Maps the upstream `pointermove` to downstream `pointerout`, `pointerover`, and `pointermove` events, in that order.
+ *
+ * The tracking data for the specific pointer has an updated `overTarget`. `mouseout`, `mouseover`,
+ * `mousemove`, and `touchmove` events are fired as well for specific pointer types.
+ * @param from - The upstream `pointermove` event.
+ */
+ mapPointerMove(from) {
+ var _a, _b, _c;
+ if (!(from instanceof FederatedPointerEvent)) {
+ warn("EventBoundary cannot map a non-pointer event as a pointer event");
+ return;
+ }
+ this._allInteractiveElements.length = 0;
+ this._hitElements.length = 0;
+ this._isPointerMoveEvent = true;
+ const e = this.createPointerEvent(from);
+ this._isPointerMoveEvent = false;
+ const isMouse = e.pointerType === "mouse" || e.pointerType === "pen";
+ const trackingData = this.trackingData(from.pointerId);
+ const outTarget = this.findMountedTarget(trackingData.overTargets);
+ if (((_a = trackingData.overTargets) == null ? void 0 : _a.length) > 0 && outTarget !== e.target) {
+ const outType = from.type === "mousemove" ? "mouseout" : "pointerout";
+ const outEvent = this.createPointerEvent(from, outType, outTarget);
+ this.dispatchEvent(outEvent, "pointerout");
+ if (isMouse)
+ this.dispatchEvent(outEvent, "mouseout");
+ if (!e.composedPath().includes(outTarget)) {
+ const leaveEvent = this.createPointerEvent(from, "pointerleave", outTarget);
+ leaveEvent.eventPhase = leaveEvent.AT_TARGET;
+ while (leaveEvent.target && !e.composedPath().includes(leaveEvent.target)) {
+ leaveEvent.currentTarget = leaveEvent.target;
+ this.notifyTarget(leaveEvent);
+ if (isMouse)
+ this.notifyTarget(leaveEvent, "mouseleave");
+ leaveEvent.target = leaveEvent.target.parent;
+ }
+ this.freeEvent(leaveEvent);
+ }
+ this.freeEvent(outEvent);
+ }
+ if (outTarget !== e.target) {
+ const overType = from.type === "mousemove" ? "mouseover" : "pointerover";
+ const overEvent = this.clonePointerEvent(e, overType);
+ this.dispatchEvent(overEvent, "pointerover");
+ if (isMouse)
+ this.dispatchEvent(overEvent, "mouseover");
+ let overTargetAncestor = outTarget == null ? void 0 : outTarget.parent;
+ while (overTargetAncestor && overTargetAncestor !== this.rootTarget.parent) {
+ if (overTargetAncestor === e.target)
+ break;
+ overTargetAncestor = overTargetAncestor.parent;
+ }
+ const didPointerEnter = !overTargetAncestor || overTargetAncestor === this.rootTarget.parent;
+ if (didPointerEnter) {
+ const enterEvent = this.clonePointerEvent(e, "pointerenter");
+ enterEvent.eventPhase = enterEvent.AT_TARGET;
+ while (enterEvent.target && enterEvent.target !== outTarget && enterEvent.target !== this.rootTarget.parent) {
+ enterEvent.currentTarget = enterEvent.target;
+ this.notifyTarget(enterEvent);
+ if (isMouse)
+ this.notifyTarget(enterEvent, "mouseenter");
+ enterEvent.target = enterEvent.target.parent;
+ }
+ this.freeEvent(enterEvent);
+ }
+ this.freeEvent(overEvent);
+ }
+ const allMethods = [];
+ const allowGlobalPointerEvents = (_b = this.enableGlobalMoveEvents) != null ? _b : true;
+ this.moveOnAll ? allMethods.push("pointermove") : this.dispatchEvent(e, "pointermove");
+ allowGlobalPointerEvents && allMethods.push("globalpointermove");
+ if (e.pointerType === "touch") {
+ this.moveOnAll ? allMethods.splice(1, 0, "touchmove") : this.dispatchEvent(e, "touchmove");
+ allowGlobalPointerEvents && allMethods.push("globaltouchmove");
+ }
+ if (isMouse) {
+ this.moveOnAll ? allMethods.splice(1, 0, "mousemove") : this.dispatchEvent(e, "mousemove");
+ allowGlobalPointerEvents && allMethods.push("globalmousemove");
+ this.cursor = (_c = e.target) == null ? void 0 : _c.cursor;
+ }
+ if (allMethods.length > 0) {
+ this.all(e, allMethods);
+ }
+ this._allInteractiveElements.length = 0;
+ this._hitElements.length = 0;
+ trackingData.overTargets = e.composedPath();
+ this.freeEvent(e);
+ }
+ /**
+ * Maps the upstream `pointerover` to downstream `pointerover` and `pointerenter` events, in that order.
+ *
+ * The tracking data for the specific pointer gets a new `overTarget`.
+ * @param from - The upstream `pointerover` event.
+ */
+ mapPointerOver(from) {
+ var _a;
+ if (!(from instanceof FederatedPointerEvent)) {
+ warn("EventBoundary cannot map a non-pointer event as a pointer event");
+ return;
+ }
+ const trackingData = this.trackingData(from.pointerId);
+ const e = this.createPointerEvent(from);
+ const isMouse = e.pointerType === "mouse" || e.pointerType === "pen";
+ this.dispatchEvent(e, "pointerover");
+ if (isMouse)
+ this.dispatchEvent(e, "mouseover");
+ if (e.pointerType === "mouse")
+ this.cursor = (_a = e.target) == null ? void 0 : _a.cursor;
+ const enterEvent = this.clonePointerEvent(e, "pointerenter");
+ enterEvent.eventPhase = enterEvent.AT_TARGET;
+ while (enterEvent.target && enterEvent.target !== this.rootTarget.parent) {
+ enterEvent.currentTarget = enterEvent.target;
+ this.notifyTarget(enterEvent);
+ if (isMouse)
+ this.notifyTarget(enterEvent, "mouseenter");
+ enterEvent.target = enterEvent.target.parent;
+ }
+ trackingData.overTargets = e.composedPath();
+ this.freeEvent(e);
+ this.freeEvent(enterEvent);
+ }
+ /**
+ * Maps the upstream `pointerout` to downstream `pointerout`, `pointerleave` events, in that order.
+ *
+ * The tracking data for the specific pointer is cleared of a `overTarget`.
+ * @param from - The upstream `pointerout` event.
+ */
+ mapPointerOut(from) {
+ if (!(from instanceof FederatedPointerEvent)) {
+ warn("EventBoundary cannot map a non-pointer event as a pointer event");
+ return;
+ }
+ const trackingData = this.trackingData(from.pointerId);
+ if (trackingData.overTargets) {
+ const isMouse = from.pointerType === "mouse" || from.pointerType === "pen";
+ const outTarget = this.findMountedTarget(trackingData.overTargets);
+ const outEvent = this.createPointerEvent(from, "pointerout", outTarget);
+ this.dispatchEvent(outEvent);
+ if (isMouse)
+ this.dispatchEvent(outEvent, "mouseout");
+ const leaveEvent = this.createPointerEvent(from, "pointerleave", outTarget);
+ leaveEvent.eventPhase = leaveEvent.AT_TARGET;
+ while (leaveEvent.target && leaveEvent.target !== this.rootTarget.parent) {
+ leaveEvent.currentTarget = leaveEvent.target;
+ this.notifyTarget(leaveEvent);
+ if (isMouse)
+ this.notifyTarget(leaveEvent, "mouseleave");
+ leaveEvent.target = leaveEvent.target.parent;
+ }
+ trackingData.overTargets = null;
+ this.freeEvent(outEvent);
+ this.freeEvent(leaveEvent);
+ }
+ this.cursor = null;
+ }
+ /**
+ * Maps the upstream `pointerup` event to downstream `pointerup`, `pointerupoutside`,
+ * and `click`/`rightclick`/`pointertap` events, in that order.
+ *
+ * The `pointerupoutside` event bubbles from the original `pointerdown` target to the most specific
+ * ancestor of the `pointerdown` and `pointerup` targets, which is also the `click` event's target. `touchend`,
+ * `rightup`, `mouseup`, `touchendoutside`, `rightupoutside`, `mouseupoutside`, and `tap` are fired as well for
+ * specific pointer types.
+ * @param from - The upstream `pointerup` event.
+ */
+ mapPointerUp(from) {
+ if (!(from instanceof FederatedPointerEvent)) {
+ warn("EventBoundary cannot map a non-pointer event as a pointer event");
+ return;
+ }
+ const now = performance.now();
+ const e = this.createPointerEvent(from);
+ this.dispatchEvent(e, "pointerup");
+ if (e.pointerType === "touch") {
+ this.dispatchEvent(e, "touchend");
+ } else if (e.pointerType === "mouse" || e.pointerType === "pen") {
+ const isRightButton = e.button === 2;
+ this.dispatchEvent(e, isRightButton ? "rightup" : "mouseup");
+ }
+ const trackingData = this.trackingData(from.pointerId);
+ const pressTarget = this.findMountedTarget(trackingData.pressTargetsByButton[from.button]);
+ let clickTarget = pressTarget;
+ if (pressTarget && !e.composedPath().includes(pressTarget)) {
+ let currentTarget = pressTarget;
+ while (currentTarget && !e.composedPath().includes(currentTarget)) {
+ e.currentTarget = currentTarget;
+ this.notifyTarget(e, "pointerupoutside");
+ if (e.pointerType === "touch") {
+ this.notifyTarget(e, "touchendoutside");
+ } else if (e.pointerType === "mouse" || e.pointerType === "pen") {
+ const isRightButton = e.button === 2;
+ this.notifyTarget(e, isRightButton ? "rightupoutside" : "mouseupoutside");
+ }
+ currentTarget = currentTarget.parent;
+ }
+ delete trackingData.pressTargetsByButton[from.button];
+ clickTarget = currentTarget;
+ }
+ if (clickTarget) {
+ const clickEvent = this.clonePointerEvent(e, "click");
+ clickEvent.target = clickTarget;
+ clickEvent.path = null;
+ if (!trackingData.clicksByButton[from.button]) {
+ trackingData.clicksByButton[from.button] = {
+ clickCount: 0,
+ target: clickEvent.target,
+ timeStamp: now
+ };
+ }
+ const clickHistory = trackingData.clicksByButton[from.button];
+ if (clickHistory.target === clickEvent.target && now - clickHistory.timeStamp < 200) {
+ ++clickHistory.clickCount;
+ } else {
+ clickHistory.clickCount = 1;
+ }
+ clickHistory.target = clickEvent.target;
+ clickHistory.timeStamp = now;
+ clickEvent.detail = clickHistory.clickCount;
+ if (clickEvent.pointerType === "mouse") {
+ const isRightButton = clickEvent.button === 2;
+ this.dispatchEvent(clickEvent, isRightButton ? "rightclick" : "click");
+ } else if (clickEvent.pointerType === "touch") {
+ this.dispatchEvent(clickEvent, "tap");
+ }
+ this.dispatchEvent(clickEvent, "pointertap");
+ this.freeEvent(clickEvent);
+ }
+ this.freeEvent(e);
+ }
+ /**
+ * Maps the upstream `pointerupoutside` event to a downstream `pointerupoutside` event, bubbling from the original
+ * `pointerdown` target to `rootTarget`.
+ *
+ * (The most specific ancestor of the `pointerdown` event and the `pointerup` event must the
+ * `{@link EventBoundary}'s root because the `pointerup` event occurred outside of the boundary.)
+ *
+ * `touchendoutside`, `mouseupoutside`, and `rightupoutside` events are fired as well for specific pointer
+ * types. The tracking data for the specific pointer is cleared of a `pressTarget`.
+ * @param from - The upstream `pointerupoutside` event.
+ */
+ mapPointerUpOutside(from) {
+ if (!(from instanceof FederatedPointerEvent)) {
+ warn("EventBoundary cannot map a non-pointer event as a pointer event");
+ return;
+ }
+ const trackingData = this.trackingData(from.pointerId);
+ const pressTarget = this.findMountedTarget(trackingData.pressTargetsByButton[from.button]);
+ const e = this.createPointerEvent(from);
+ if (pressTarget) {
+ let currentTarget = pressTarget;
+ while (currentTarget) {
+ e.currentTarget = currentTarget;
+ this.notifyTarget(e, "pointerupoutside");
+ if (e.pointerType === "touch") {
+ this.notifyTarget(e, "touchendoutside");
+ } else if (e.pointerType === "mouse" || e.pointerType === "pen") {
+ this.notifyTarget(e, e.button === 2 ? "rightupoutside" : "mouseupoutside");
+ }
+ currentTarget = currentTarget.parent;
+ }
+ delete trackingData.pressTargetsByButton[from.button];
+ }
+ this.freeEvent(e);
+ }
+ /**
+ * Maps the upstream `wheel` event to a downstream `wheel` event.
+ * @param from - The upstream `wheel` event.
+ */
+ mapWheel(from) {
+ if (!(from instanceof FederatedWheelEvent)) {
+ warn("EventBoundary cannot map a non-wheel event as a wheel event");
+ return;
+ }
+ const wheelEvent = this.createWheelEvent(from);
+ this.dispatchEvent(wheelEvent);
+ this.freeEvent(wheelEvent);
+ }
+ /**
+ * Finds the most specific event-target in the given propagation path that is still mounted in the scene graph.
+ *
+ * This is used to find the correct `pointerup` and `pointerout` target in the case that the original `pointerdown`
+ * or `pointerover` target was unmounted from the scene graph.
+ * @param propagationPath - The propagation path was valid in the past.
+ * @returns - The most specific event-target still mounted at the same location in the scene graph.
+ */
+ findMountedTarget(propagationPath) {
+ if (!propagationPath) {
+ return null;
+ }
+ let currentTarget = propagationPath[0];
+ for (let i = 1; i < propagationPath.length; i++) {
+ if (propagationPath[i].parent === currentTarget) {
+ currentTarget = propagationPath[i];
+ } else {
+ break;
+ }
+ }
+ return currentTarget;
+ }
+ /**
+ * Creates an event whose `originalEvent` is `from`, with an optional `type` and `target` override.
+ *
+ * The event is allocated using {@link EventBoundary#allocateEvent this.allocateEvent}.
+ * @param from - The `originalEvent` for the returned event.
+ * @param [type=from.type] - The type of the returned event.
+ * @param target - The target of the returned event.
+ */
+ createPointerEvent(from, type, target) {
+ var _a;
+ const event = this.allocateEvent(FederatedPointerEvent);
+ this.copyPointerData(from, event);
+ this.copyMouseData(from, event);
+ this.copyData(from, event);
+ event.nativeEvent = from.nativeEvent;
+ event.originalEvent = from;
+ event.target = (_a = target != null ? target : this.hitTest(event.global.x, event.global.y)) != null ? _a : this._hitElements[0];
+ if (typeof type === "string") {
+ event.type = type;
+ }
+ return event;
+ }
+ /**
+ * Creates a wheel event whose `originalEvent` is `from`.
+ *
+ * The event is allocated using {@link EventBoundary#allocateEvent this.allocateEvent}.
+ * @param from - The upstream wheel event.
+ */
+ createWheelEvent(from) {
+ const event = this.allocateEvent(FederatedWheelEvent);
+ this.copyWheelData(from, event);
+ this.copyMouseData(from, event);
+ this.copyData(from, event);
+ event.nativeEvent = from.nativeEvent;
+ event.originalEvent = from;
+ event.target = this.hitTest(event.global.x, event.global.y);
+ return event;
+ }
+ /**
+ * Clones the event `from`, with an optional `type` override.
+ *
+ * The event is allocated using {@link EventBoundary#allocateEvent this.allocateEvent}.
+ * @param from - The event to clone.
+ * @param [type=from.type] - The type of the returned event.
+ */
+ clonePointerEvent(from, type) {
+ const event = this.allocateEvent(FederatedPointerEvent);
+ event.nativeEvent = from.nativeEvent;
+ event.originalEvent = from.originalEvent;
+ this.copyPointerData(from, event);
+ this.copyMouseData(from, event);
+ this.copyData(from, event);
+ event.target = from.target;
+ event.path = from.composedPath().slice();
+ event.type = type != null ? type : event.type;
+ return event;
+ }
+ /**
+ * Copies wheel {@link FederatedWheelEvent} data from `from` into `to`.
+ *
+ * The following properties are copied:
+ * + deltaMode
+ * + deltaX
+ * + deltaY
+ * + deltaZ
+ * @param from - The event to copy data from.
+ * @param to - The event to copy data into.
+ */
+ copyWheelData(from, to) {
+ to.deltaMode = from.deltaMode;
+ to.deltaX = from.deltaX;
+ to.deltaY = from.deltaY;
+ to.deltaZ = from.deltaZ;
+ }
+ /**
+ * Copies pointer {@link FederatedPointerEvent} data from `from` into `to`.
+ *
+ * The following properties are copied:
+ * + pointerId
+ * + width
+ * + height
+ * + isPrimary
+ * + pointerType
+ * + pressure
+ * + tangentialPressure
+ * + tiltX
+ * + tiltY
+ * @param from - The event to copy data from.
+ * @param to - The event to copy data into.
+ */
+ copyPointerData(from, to) {
+ if (!(from instanceof FederatedPointerEvent && to instanceof FederatedPointerEvent))
+ return;
+ to.pointerId = from.pointerId;
+ to.width = from.width;
+ to.height = from.height;
+ to.isPrimary = from.isPrimary;
+ to.pointerType = from.pointerType;
+ to.pressure = from.pressure;
+ to.tangentialPressure = from.tangentialPressure;
+ to.tiltX = from.tiltX;
+ to.tiltY = from.tiltY;
+ to.twist = from.twist;
+ }
+ /**
+ * Copies mouse {@link FederatedMouseEvent} data from `from` to `to`.
+ *
+ * The following properties are copied:
+ * + altKey
+ * + button
+ * + buttons
+ * + clientX
+ * + clientY
+ * + metaKey
+ * + movementX
+ * + movementY
+ * + pageX
+ * + pageY
+ * + x
+ * + y
+ * + screen
+ * + shiftKey
+ * + global
+ * @param from - The event to copy data from.
+ * @param to - The event to copy data into.
+ */
+ copyMouseData(from, to) {
+ if (!(from instanceof FederatedMouseEvent && to instanceof FederatedMouseEvent))
+ return;
+ to.altKey = from.altKey;
+ to.button = from.button;
+ to.buttons = from.buttons;
+ to.client.copyFrom(from.client);
+ to.ctrlKey = from.ctrlKey;
+ to.metaKey = from.metaKey;
+ to.movement.copyFrom(from.movement);
+ to.screen.copyFrom(from.screen);
+ to.shiftKey = from.shiftKey;
+ to.global.copyFrom(from.global);
+ }
+ /**
+ * Copies base {@link FederatedEvent} data from `from` into `to`.
+ *
+ * The following properties are copied:
+ * + isTrusted
+ * + srcElement
+ * + timeStamp
+ * + type
+ * @param from - The event to copy data from.
+ * @param to - The event to copy data into.
+ */
+ copyData(from, to) {
+ to.isTrusted = from.isTrusted;
+ to.srcElement = from.srcElement;
+ to.timeStamp = performance.now();
+ to.type = from.type;
+ to.detail = from.detail;
+ to.view = from.view;
+ to.which = from.which;
+ to.layer.copyFrom(from.layer);
+ to.page.copyFrom(from.page);
+ }
+ /**
+ * @param id - The pointer ID.
+ * @returns The tracking data stored for the given pointer. If no data exists, a blank
+ * state will be created.
+ */
+ trackingData(id) {
+ if (!this.mappingState.trackingData[id]) {
+ this.mappingState.trackingData[id] = {
+ pressTargetsByButton: {},
+ clicksByButton: {},
+ overTarget: null
+ };
+ }
+ return this.mappingState.trackingData[id];
+ }
+ /**
+ * Allocate a specific type of event from {@link EventBoundary#eventPool this.eventPool}.
+ *
+ * This allocation is constructor-agnostic, as long as it only takes one argument - this event
+ * boundary.
+ * @param constructor - The event's constructor.
+ * @returns An event of the given type.
+ */
+ allocateEvent(constructor) {
+ if (!this.eventPool.has(constructor)) {
+ this.eventPool.set(constructor, []);
+ }
+ const event = this.eventPool.get(constructor).pop() || new constructor(this);
+ event.eventPhase = event.NONE;
+ event.currentTarget = null;
+ event.defaultPrevented = false;
+ event.path = null;
+ event.target = null;
+ return event;
+ }
+ /**
+ * Frees the event and puts it back into the event pool.
+ *
+ * It is illegal to reuse the event until it is allocated again, using `this.allocateEvent`.
+ *
+ * It is also advised that events not allocated from {@link EventBoundary#allocateEvent this.allocateEvent}
+ * not be freed. This is because of the possibility that the same event is freed twice, which can cause
+ * it to be allocated twice & result in overwriting.
+ * @param event - The event to be freed.
+ * @throws Error if the event is managed by another event boundary.
+ */
+ freeEvent(event) {
+ if (event.manager !== this)
+ throw new Error("It is illegal to free an event not managed by this EventBoundary!");
+ const constructor = event.constructor;
+ if (!this.eventPool.has(constructor)) {
+ this.eventPool.set(constructor, []);
+ }
+ this.eventPool.get(constructor).push(event);
+ }
+ /**
+ * Similar to {@link EventEmitter.emit}, except it stops if the `propagationImmediatelyStopped` flag
+ * is set on the event.
+ * @param e - The event to call each listener with.
+ * @param type - The event key.
+ */
+ _notifyListeners(e, type) {
+ const listeners = e.currentTarget._events[type];
+ if (!listeners)
+ return;
+ if ("fn" in listeners) {
+ if (listeners.once)
+ e.currentTarget.removeListener(type, listeners.fn, void 0, true);
+ listeners.fn.call(listeners.context, e);
+ } else {
+ for (let i = 0, j = listeners.length; i < j && !e.propagationImmediatelyStopped; i++) {
+ if (listeners[i].once)
+ e.currentTarget.removeListener(type, listeners[i].fn, void 0, true);
+ listeners[i].fn.call(listeners[i].context, e);
+ }
+ }
+ }
+ }
+
+ "use strict";
+ var __defProp$16 = Object.defineProperty;
+ var __getOwnPropSymbols$17 = Object.getOwnPropertySymbols;
+ var __hasOwnProp$17 = Object.prototype.hasOwnProperty;
+ var __propIsEnum$17 = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$16 = (obj, key, value) => key in obj ? __defProp$16(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$16 = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$17.call(b, prop))
+ __defNormalProp$16(a, prop, b[prop]);
+ if (__getOwnPropSymbols$17)
+ for (var prop of __getOwnPropSymbols$17(b)) {
+ if (__propIsEnum$17.call(b, prop))
+ __defNormalProp$16(a, prop, b[prop]);
+ }
+ return a;
+ };
+ const MOUSE_POINTER_ID = 1;
+ const TOUCH_TO_POINTER = {
+ touchstart: "pointerdown",
+ touchend: "pointerup",
+ touchendoutside: "pointerupoutside",
+ touchmove: "pointermove",
+ touchcancel: "pointercancel"
+ };
+ const _EventSystem = class _EventSystem {
+ /**
+ * @param {Renderer} renderer
+ */
+ constructor(renderer) {
+ /**
+ * Indicates whether the current device supports touch events according to the W3C Touch Events spec.
+ * This is used to determine the appropriate event handling strategy.
+ * @see {@link https://www.w3.org/TR/touch-events/} W3C Touch Events Specification
+ * @readonly
+ * @default 'ontouchstart' in globalThis
+ */
+ this.supportsTouchEvents = "ontouchstart" in globalThis;
+ /**
+ * Indicates whether the current device supports pointer events according to the W3C Pointer Events spec.
+ * Used to optimize event handling and provide more consistent cross-device interaction.
+ * @see {@link https://www.w3.org/TR/pointerevents/} W3C Pointer Events Specification
+ * @readonly
+ * @default !!globalThis.PointerEvent
+ */
+ this.supportsPointerEvents = !!globalThis.PointerEvent;
+ /**
+ * The DOM element to which the root event listeners are bound. This is automatically set to
+ * the renderer's {@link Renderer#view view}.
+ */
+ this.domElement = null;
+ /** The resolution used to convert between the DOM client space into world space. */
+ this.resolution = 1;
+ this.renderer = renderer;
+ this.rootBoundary = new EventBoundary(null);
+ EventsTicker.init(this);
+ this.autoPreventDefault = true;
+ this._eventsAdded = false;
+ this._rootPointerEvent = new FederatedPointerEvent(null);
+ this._rootWheelEvent = new FederatedWheelEvent(null);
+ this.cursorStyles = {
+ default: "inherit",
+ pointer: "pointer"
+ };
+ this.features = new Proxy(__spreadValues$16({}, _EventSystem.defaultEventFeatures), {
+ set: (target, key, value) => {
+ if (key === "globalMove") {
+ this.rootBoundary.enableGlobalMoveEvents = value;
+ }
+ target[key] = value;
+ return true;
+ }
+ });
+ this._onPointerDown = this._onPointerDown.bind(this);
+ this._onPointerMove = this._onPointerMove.bind(this);
+ this._onPointerUp = this._onPointerUp.bind(this);
+ this._onPointerOverOut = this._onPointerOverOut.bind(this);
+ this.onWheel = this.onWheel.bind(this);
+ }
+ /**
+ * The default interaction mode for all display objects.
+ * @see Container.eventMode
+ * @type {EventMode}
+ * @readonly
+ * @since 7.2.0
+ */
+ static get defaultEventMode() {
+ return this._defaultEventMode;
+ }
+ /**
+ * Runner init called, view is available at this point.
+ * @ignore
+ */
+ init(options) {
+ var _a, _b;
+ const { canvas, resolution } = this.renderer;
+ this.setTargetElement(canvas);
+ this.resolution = resolution;
+ _EventSystem._defaultEventMode = (_a = options.eventMode) != null ? _a : "passive";
+ Object.assign(this.features, (_b = options.eventFeatures) != null ? _b : {});
+ this.rootBoundary.enableGlobalMoveEvents = this.features.globalMove;
+ }
+ /**
+ * Handle changing resolution.
+ * @ignore
+ */
+ resolutionChange(resolution) {
+ this.resolution = resolution;
+ }
+ /** Destroys all event listeners and detaches the renderer. */
+ destroy() {
+ EventsTicker.destroy();
+ this.setTargetElement(null);
+ this.renderer = null;
+ this._currentCursor = null;
+ }
+ /**
+ * Sets the current cursor mode, handling any callbacks or CSS style changes.
+ * The cursor can be a CSS cursor string, a custom callback function, or a key from the cursorStyles dictionary.
+ * @param mode - Cursor mode to set. Can be:
+ * - A CSS cursor string (e.g., 'pointer', 'grab')
+ * - A key from the cursorStyles dictionary
+ * - null/undefined to reset to default
+ * @example
+ * ```ts
+ * // Using predefined cursor styles
+ * app.renderer.events.setCursor('pointer'); // Set standard pointer cursor
+ * app.renderer.events.setCursor('grab'); // Set grab cursor
+ * app.renderer.events.setCursor(null); // Reset to default
+ *
+ * // Using custom cursor styles
+ * app.renderer.events.cursorStyles.custom = 'url("cursor.png"), auto';
+ * app.renderer.events.setCursor('custom'); // Apply custom cursor
+ *
+ * // Using callback-based cursor
+ * app.renderer.events.cursorStyles.dynamic = (mode) => {
+ * document.body.style.cursor = mode === 'hover' ? 'pointer' : 'default';
+ * };
+ * app.renderer.events.setCursor('dynamic'); // Trigger cursor callback
+ * ```
+ * @remarks
+ * - Has no effect on OffscreenCanvas except for callback-based cursors
+ * - Caches current cursor to avoid unnecessary DOM updates
+ * - Supports CSS cursor values, style objects, and callback functions
+ * @see {@link EventSystem.cursorStyles} For defining custom cursor styles
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/cursor} MDN Cursor Reference
+ */
+ setCursor(mode) {
+ mode || (mode = "default");
+ let applyStyles = true;
+ if (globalThis.OffscreenCanvas && this.domElement instanceof OffscreenCanvas) {
+ applyStyles = false;
+ }
+ if (this._currentCursor === mode) {
+ return;
+ }
+ this._currentCursor = mode;
+ const style = this.cursorStyles[mode];
+ if (style) {
+ switch (typeof style) {
+ case "string":
+ if (applyStyles) {
+ this.domElement.style.cursor = style;
+ }
+ break;
+ case "function":
+ style(mode);
+ break;
+ case "object":
+ if (applyStyles) {
+ Object.assign(this.domElement.style, style);
+ }
+ break;
+ }
+ } else if (applyStyles && typeof mode === "string" && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) {
+ this.domElement.style.cursor = mode;
+ }
+ }
+ /**
+ * The global pointer event instance containing the most recent pointer state.
+ * This is useful for accessing pointer information without listening to events.
+ * @example
+ * ```ts
+ * // Access current pointer position at any time
+ * const eventSystem = app.renderer.events;
+ * const pointer = eventSystem.pointer;
+ *
+ * // Get global coordinates
+ * console.log('Position:', pointer.global.x, pointer.global.y);
+ *
+ * // Check button state
+ * console.log('Buttons pressed:', pointer.buttons);
+ *
+ * // Get pointer type and pressure
+ * console.log('Type:', pointer.pointerType);
+ * console.log('Pressure:', pointer.pressure);
+ * ```
+ * @readonly
+ * @since 7.2.0
+ * @see {@link FederatedPointerEvent} For all available pointer properties
+ */
+ get pointer() {
+ return this._rootPointerEvent;
+ }
+ /**
+ * Event handler for pointer down events on {@link EventSystem#domElement this.domElement}.
+ * @param nativeEvent - The native mouse/pointer/touch event.
+ */
+ _onPointerDown(nativeEvent) {
+ if (!this.features.click)
+ return;
+ this.rootBoundary.rootTarget = this.renderer.lastObjectRendered;
+ const events = this._normalizeToPointerData(nativeEvent);
+ if (this.autoPreventDefault && events[0].isNormalized) {
+ const cancelable = nativeEvent.cancelable || !("cancelable" in nativeEvent);
+ if (cancelable) {
+ nativeEvent.preventDefault();
+ }
+ }
+ for (let i = 0, j = events.length; i < j; i++) {
+ const nativeEvent2 = events[i];
+ const federatedEvent = this._bootstrapEvent(this._rootPointerEvent, nativeEvent2);
+ this.rootBoundary.mapEvent(federatedEvent);
+ }
+ this.setCursor(this.rootBoundary.cursor);
+ }
+ /**
+ * Event handler for pointer move events on on {@link EventSystem#domElement this.domElement}.
+ * @param nativeEvent - The native mouse/pointer/touch events.
+ */
+ _onPointerMove(nativeEvent) {
+ if (!this.features.move)
+ return;
+ this.rootBoundary.rootTarget = this.renderer.lastObjectRendered;
+ EventsTicker.pointerMoved();
+ const normalizedEvents = this._normalizeToPointerData(nativeEvent);
+ for (let i = 0, j = normalizedEvents.length; i < j; i++) {
+ const event = this._bootstrapEvent(this._rootPointerEvent, normalizedEvents[i]);
+ this.rootBoundary.mapEvent(event);
+ }
+ this.setCursor(this.rootBoundary.cursor);
+ }
+ /**
+ * Event handler for pointer up events on {@link EventSystem#domElement this.domElement}.
+ * @param nativeEvent - The native mouse/pointer/touch event.
+ */
+ _onPointerUp(nativeEvent) {
+ if (!this.features.click)
+ return;
+ this.rootBoundary.rootTarget = this.renderer.lastObjectRendered;
+ let target = nativeEvent.target;
+ if (nativeEvent.composedPath && nativeEvent.composedPath().length > 0) {
+ target = nativeEvent.composedPath()[0];
+ }
+ const outside = target !== this.domElement ? "outside" : "";
+ const normalizedEvents = this._normalizeToPointerData(nativeEvent);
+ for (let i = 0, j = normalizedEvents.length; i < j; i++) {
+ const event = this._bootstrapEvent(this._rootPointerEvent, normalizedEvents[i]);
+ event.type += outside;
+ this.rootBoundary.mapEvent(event);
+ }
+ this.setCursor(this.rootBoundary.cursor);
+ }
+ /**
+ * Event handler for pointer over & out events on {@link EventSystem#domElement this.domElement}.
+ * @param nativeEvent - The native mouse/pointer/touch event.
+ */
+ _onPointerOverOut(nativeEvent) {
+ if (!this.features.click)
+ return;
+ this.rootBoundary.rootTarget = this.renderer.lastObjectRendered;
+ const normalizedEvents = this._normalizeToPointerData(nativeEvent);
+ for (let i = 0, j = normalizedEvents.length; i < j; i++) {
+ const event = this._bootstrapEvent(this._rootPointerEvent, normalizedEvents[i]);
+ this.rootBoundary.mapEvent(event);
+ }
+ this.setCursor(this.rootBoundary.cursor);
+ }
+ /**
+ * Passive handler for `wheel` events on {@link EventSystem.domElement this.domElement}.
+ * @param nativeEvent - The native wheel event.
+ */
+ onWheel(nativeEvent) {
+ if (!this.features.wheel)
+ return;
+ const wheelEvent = this.normalizeWheelEvent(nativeEvent);
+ this.rootBoundary.rootTarget = this.renderer.lastObjectRendered;
+ this.rootBoundary.mapEvent(wheelEvent);
+ }
+ /**
+ * Sets the {@link EventSystem#domElement domElement} and binds event listeners.
+ * This method manages the DOM event bindings for the event system, allowing you to
+ * change or remove the target element that receives input events.
+ * > [!IMPORTANT] This will default to the canvas element of the renderer, so you
+ * > should not need to call this unless you are using a custom element.
+ * @param element - The new DOM element to bind events to, or null to remove all event bindings
+ * @example
+ * ```ts
+ * // Set a new canvas element as the target
+ * const canvas = document.createElement('canvas');
+ * app.renderer.events.setTargetElement(canvas);
+ *
+ * // Remove all event bindings
+ * app.renderer.events.setTargetElement(null);
+ *
+ * // Switch to a different canvas
+ * const newCanvas = document.querySelector('#game-canvas');
+ * app.renderer.events.setTargetElement(newCanvas);
+ * ```
+ * @remarks
+ * - Automatically removes event listeners from previous element
+ * - Required for the event system to function
+ * - Safe to call multiple times
+ * @see {@link EventSystem#domElement} The current DOM element
+ * @see {@link EventsTicker} For the ticker system that tracks pointer movement
+ */
+ setTargetElement(element) {
+ this._removeEvents();
+ this.domElement = element;
+ EventsTicker.domElement = element;
+ this._addEvents();
+ }
+ /** Register event listeners on {@link Renderer#domElement this.domElement}. */
+ _addEvents() {
+ if (this._eventsAdded || !this.domElement) {
+ return;
+ }
+ EventsTicker.addTickerListener();
+ const style = this.domElement.style;
+ if (style) {
+ if (globalThis.navigator.msPointerEnabled) {
+ style.msContentZooming = "none";
+ style.msTouchAction = "none";
+ } else if (this.supportsPointerEvents) {
+ style.touchAction = "none";
+ }
+ }
+ if (this.supportsPointerEvents) {
+ globalThis.document.addEventListener("pointermove", this._onPointerMove, true);
+ this.domElement.addEventListener("pointerdown", this._onPointerDown, true);
+ this.domElement.addEventListener("pointerleave", this._onPointerOverOut, true);
+ this.domElement.addEventListener("pointerover", this._onPointerOverOut, true);
+ globalThis.addEventListener("pointerup", this._onPointerUp, true);
+ } else {
+ globalThis.document.addEventListener("mousemove", this._onPointerMove, true);
+ this.domElement.addEventListener("mousedown", this._onPointerDown, true);
+ this.domElement.addEventListener("mouseout", this._onPointerOverOut, true);
+ this.domElement.addEventListener("mouseover", this._onPointerOverOut, true);
+ globalThis.addEventListener("mouseup", this._onPointerUp, true);
+ if (this.supportsTouchEvents) {
+ this.domElement.addEventListener("touchstart", this._onPointerDown, true);
+ this.domElement.addEventListener("touchend", this._onPointerUp, true);
+ this.domElement.addEventListener("touchmove", this._onPointerMove, true);
+ }
+ }
+ this.domElement.addEventListener("wheel", this.onWheel, {
+ passive: true,
+ capture: true
+ });
+ this._eventsAdded = true;
+ }
+ /** Unregister event listeners on {@link EventSystem#domElement this.domElement}. */
+ _removeEvents() {
+ if (!this._eventsAdded || !this.domElement) {
+ return;
+ }
+ EventsTicker.removeTickerListener();
+ const style = this.domElement.style;
+ if (style) {
+ if (globalThis.navigator.msPointerEnabled) {
+ style.msContentZooming = "";
+ style.msTouchAction = "";
+ } else if (this.supportsPointerEvents) {
+ style.touchAction = "";
+ }
+ }
+ if (this.supportsPointerEvents) {
+ globalThis.document.removeEventListener("pointermove", this._onPointerMove, true);
+ this.domElement.removeEventListener("pointerdown", this._onPointerDown, true);
+ this.domElement.removeEventListener("pointerleave", this._onPointerOverOut, true);
+ this.domElement.removeEventListener("pointerover", this._onPointerOverOut, true);
+ globalThis.removeEventListener("pointerup", this._onPointerUp, true);
+ } else {
+ globalThis.document.removeEventListener("mousemove", this._onPointerMove, true);
+ this.domElement.removeEventListener("mousedown", this._onPointerDown, true);
+ this.domElement.removeEventListener("mouseout", this._onPointerOverOut, true);
+ this.domElement.removeEventListener("mouseover", this._onPointerOverOut, true);
+ globalThis.removeEventListener("mouseup", this._onPointerUp, true);
+ if (this.supportsTouchEvents) {
+ this.domElement.removeEventListener("touchstart", this._onPointerDown, true);
+ this.domElement.removeEventListener("touchend", this._onPointerUp, true);
+ this.domElement.removeEventListener("touchmove", this._onPointerMove, true);
+ }
+ }
+ this.domElement.removeEventListener("wheel", this.onWheel, true);
+ this.domElement = null;
+ this._eventsAdded = false;
+ }
+ /**
+ * Maps coordinates from DOM/screen space into PixiJS normalized coordinates.
+ * This takes into account the current scale, position, and resolution of the DOM element.
+ * @param point - The point to store the mapped coordinates in
+ * @param x - The x coordinate in DOM/client space
+ * @param y - The y coordinate in DOM/client space
+ * @example
+ * ```ts
+ * // Map mouse coordinates to PixiJS space
+ * const point = new Point();
+ * app.renderer.events.mapPositionToPoint(
+ * point,
+ * event.clientX,
+ * event.clientY
+ * );
+ * console.log('Mapped position:', point.x, point.y);
+ *
+ * // Using with pointer events
+ * sprite.on('pointermove', (event) => {
+ * // event.global already contains mapped coordinates
+ * console.log('Global:', event.global.x, event.global.y);
+ *
+ * // Map to local coordinates
+ * const local = event.getLocalPosition(sprite);
+ * console.log('Local:', local.x, local.y);
+ * });
+ * ```
+ * @remarks
+ * - Accounts for element scaling and positioning
+ * - Adjusts for device pixel ratio/resolution
+ */
+ mapPositionToPoint(point, x, y) {
+ const rect = this.domElement.isConnected ? this.domElement.getBoundingClientRect() : {
+ x: 0,
+ y: 0,
+ width: this.domElement.width,
+ height: this.domElement.height,
+ left: 0,
+ top: 0
+ };
+ const resolutionMultiplier = 1 / this.resolution;
+ point.x = (x - rect.left) * (this.domElement.width / rect.width) * resolutionMultiplier;
+ point.y = (y - rect.top) * (this.domElement.height / rect.height) * resolutionMultiplier;
+ }
+ /**
+ * Ensures that the original event object contains all data that a regular pointer event would have
+ * @param event - The original event data from a touch or mouse event
+ * @returns An array containing a single normalized pointer event, in the case of a pointer
+ * or mouse event, or a multiple normalized pointer events if there are multiple changed touches
+ */
+ _normalizeToPointerData(event) {
+ const normalizedEvents = [];
+ if (this.supportsTouchEvents && event instanceof TouchEvent) {
+ for (let i = 0, li = event.changedTouches.length; i < li; i++) {
+ const touch = event.changedTouches[i];
+ if (typeof touch.button === "undefined")
+ touch.button = 0;
+ if (typeof touch.buttons === "undefined")
+ touch.buttons = 1;
+ if (typeof touch.isPrimary === "undefined") {
+ touch.isPrimary = event.touches.length === 1 && event.type === "touchstart";
+ }
+ if (typeof touch.width === "undefined")
+ touch.width = touch.radiusX || 1;
+ if (typeof touch.height === "undefined")
+ touch.height = touch.radiusY || 1;
+ if (typeof touch.tiltX === "undefined")
+ touch.tiltX = 0;
+ if (typeof touch.tiltY === "undefined")
+ touch.tiltY = 0;
+ if (typeof touch.pointerType === "undefined")
+ touch.pointerType = "touch";
+ if (typeof touch.pointerId === "undefined")
+ touch.pointerId = touch.identifier || 0;
+ if (typeof touch.pressure === "undefined")
+ touch.pressure = touch.force || 0.5;
+ if (typeof touch.twist === "undefined")
+ touch.twist = 0;
+ if (typeof touch.tangentialPressure === "undefined")
+ touch.tangentialPressure = 0;
+ if (typeof touch.layerX === "undefined")
+ touch.layerX = touch.offsetX = touch.clientX;
+ if (typeof touch.layerY === "undefined")
+ touch.layerY = touch.offsetY = touch.clientY;
+ touch.isNormalized = true;
+ touch.type = event.type;
+ normalizedEvents.push(touch);
+ }
+ } else if (!globalThis.MouseEvent || event instanceof MouseEvent && (!this.supportsPointerEvents || !(event instanceof globalThis.PointerEvent))) {
+ const tempEvent = event;
+ if (typeof tempEvent.isPrimary === "undefined")
+ tempEvent.isPrimary = true;
+ if (typeof tempEvent.width === "undefined")
+ tempEvent.width = 1;
+ if (typeof tempEvent.height === "undefined")
+ tempEvent.height = 1;
+ if (typeof tempEvent.tiltX === "undefined")
+ tempEvent.tiltX = 0;
+ if (typeof tempEvent.tiltY === "undefined")
+ tempEvent.tiltY = 0;
+ if (typeof tempEvent.pointerType === "undefined")
+ tempEvent.pointerType = "mouse";
+ if (typeof tempEvent.pointerId === "undefined")
+ tempEvent.pointerId = MOUSE_POINTER_ID;
+ if (typeof tempEvent.pressure === "undefined")
+ tempEvent.pressure = 0.5;
+ if (typeof tempEvent.twist === "undefined")
+ tempEvent.twist = 0;
+ if (typeof tempEvent.tangentialPressure === "undefined")
+ tempEvent.tangentialPressure = 0;
+ tempEvent.isNormalized = true;
+ normalizedEvents.push(tempEvent);
+ } else {
+ normalizedEvents.push(event);
+ }
+ return normalizedEvents;
+ }
+ /**
+ * Normalizes the native {@link https://w3c.github.io/uievents/#interface-wheelevent WheelEvent}.
+ *
+ * The returned {@link FederatedWheelEvent} is a shared instance. It will not persist across
+ * multiple native wheel events.
+ * @param nativeEvent - The native wheel event that occurred on the canvas.
+ * @returns A federated wheel event.
+ */
+ normalizeWheelEvent(nativeEvent) {
+ const event = this._rootWheelEvent;
+ this._transferMouseData(event, nativeEvent);
+ event.deltaX = nativeEvent.deltaX;
+ event.deltaY = nativeEvent.deltaY;
+ event.deltaZ = nativeEvent.deltaZ;
+ event.deltaMode = nativeEvent.deltaMode;
+ this.mapPositionToPoint(event.screen, nativeEvent.clientX, nativeEvent.clientY);
+ event.global.copyFrom(event.screen);
+ event.offset.copyFrom(event.screen);
+ event.nativeEvent = nativeEvent;
+ event.type = nativeEvent.type;
+ return event;
+ }
+ /**
+ * Normalizes the `nativeEvent` into a federateed {@link FederatedPointerEvent}.
+ * @param event
+ * @param nativeEvent
+ */
+ _bootstrapEvent(event, nativeEvent) {
+ event.originalEvent = null;
+ event.nativeEvent = nativeEvent;
+ event.pointerId = nativeEvent.pointerId;
+ event.width = nativeEvent.width;
+ event.height = nativeEvent.height;
+ event.isPrimary = nativeEvent.isPrimary;
+ event.pointerType = nativeEvent.pointerType;
+ event.pressure = nativeEvent.pressure;
+ event.tangentialPressure = nativeEvent.tangentialPressure;
+ event.tiltX = nativeEvent.tiltX;
+ event.tiltY = nativeEvent.tiltY;
+ event.twist = nativeEvent.twist;
+ this._transferMouseData(event, nativeEvent);
+ this.mapPositionToPoint(event.screen, nativeEvent.clientX, nativeEvent.clientY);
+ event.global.copyFrom(event.screen);
+ event.offset.copyFrom(event.screen);
+ event.isTrusted = nativeEvent.isTrusted;
+ if (event.type === "pointerleave") {
+ event.type = "pointerout";
+ }
+ if (event.type.startsWith("mouse")) {
+ event.type = event.type.replace("mouse", "pointer");
+ }
+ if (event.type.startsWith("touch")) {
+ event.type = TOUCH_TO_POINTER[event.type] || event.type;
+ }
+ return event;
+ }
+ /**
+ * Transfers base & mouse event data from the `nativeEvent` to the federated event.
+ * @param event
+ * @param nativeEvent
+ */
+ _transferMouseData(event, nativeEvent) {
+ event.isTrusted = nativeEvent.isTrusted;
+ event.srcElement = nativeEvent.srcElement;
+ event.timeStamp = performance.now();
+ event.type = nativeEvent.type;
+ event.altKey = nativeEvent.altKey;
+ event.button = nativeEvent.button;
+ event.buttons = nativeEvent.buttons;
+ event.client.x = nativeEvent.clientX;
+ event.client.y = nativeEvent.clientY;
+ event.ctrlKey = nativeEvent.ctrlKey;
+ event.metaKey = nativeEvent.metaKey;
+ event.movement.x = nativeEvent.movementX;
+ event.movement.y = nativeEvent.movementY;
+ event.page.x = nativeEvent.pageX;
+ event.page.y = nativeEvent.pageY;
+ event.relatedTarget = null;
+ event.shiftKey = nativeEvent.shiftKey;
+ }
+ };
+ /** @ignore */
+ _EventSystem.extension = {
+ name: "events",
+ type: [
+ ExtensionType.WebGLSystem,
+ ExtensionType.CanvasSystem,
+ ExtensionType.WebGPUSystem
+ ],
+ priority: -1
+ };
+ /**
+ * The event features that are enabled by the EventSystem
+ * @since 7.2.0
+ * @example
+ * ```ts
+ * import { EventSystem, EventSystemFeatures } from 'pixi.js';
+ * // Access the default event features
+ * EventSystem.defaultEventFeatures = {
+ * // Enable pointer movement events
+ * move: true,
+ * // Enable global pointer move events
+ * globalMove: true,
+ * // Enable click events
+ * click: true,
+ * // Enable wheel events
+ * wheel: true,
+ * };
+ * ```
+ */
+ _EventSystem.defaultEventFeatures = {
+ /** Enables pointer events associated with pointer movement. */
+ move: true,
+ /** Enables global pointer move events. */
+ globalMove: true,
+ /** Enables pointer events associated with clicking. */
+ click: true,
+ /** Enables wheel events. */
+ wheel: true
+ };
+ let EventSystem = _EventSystem;
+
+ "use strict";
+ const FederatedContainer = {
+ onclick: null,
+ onmousedown: null,
+ onmouseenter: null,
+ onmouseleave: null,
+ onmousemove: null,
+ onglobalmousemove: null,
+ onmouseout: null,
+ onmouseover: null,
+ onmouseup: null,
+ onmouseupoutside: null,
+ onpointercancel: null,
+ onpointerdown: null,
+ onpointerenter: null,
+ onpointerleave: null,
+ onpointermove: null,
+ onglobalpointermove: null,
+ onpointerout: null,
+ onpointerover: null,
+ onpointertap: null,
+ onpointerup: null,
+ onpointerupoutside: null,
+ onrightclick: null,
+ onrightdown: null,
+ onrightup: null,
+ onrightupoutside: null,
+ ontap: null,
+ ontouchcancel: null,
+ ontouchend: null,
+ ontouchendoutside: null,
+ ontouchmove: null,
+ onglobaltouchmove: null,
+ ontouchstart: null,
+ onwheel: null,
+ get interactive() {
+ return this.eventMode === "dynamic" || this.eventMode === "static";
+ },
+ set interactive(value) {
+ this.eventMode = value ? "static" : "passive";
+ },
+ _internalEventMode: void 0,
+ get eventMode() {
+ var _a;
+ return (_a = this._internalEventMode) != null ? _a : EventSystem.defaultEventMode;
+ },
+ set eventMode(value) {
+ this._internalEventMode = value;
+ },
+ isInteractive() {
+ return this.eventMode === "static" || this.eventMode === "dynamic";
+ },
+ interactiveChildren: true,
+ hitArea: null,
+ addEventListener(type, listener, options) {
+ const capture = typeof options === "boolean" && options || typeof options === "object" && options.capture;
+ const signal = typeof options === "object" ? options.signal : void 0;
+ const once = typeof options === "object" ? options.once === true : false;
+ const context = typeof listener === "function" ? void 0 : listener;
+ type = capture ? `${type}capture` : type;
+ const listenerFn = typeof listener === "function" ? listener : listener.handleEvent;
+ const emitter = this;
+ if (signal) {
+ signal.addEventListener("abort", () => {
+ emitter.off(type, listenerFn, context);
+ });
+ }
+ if (once) {
+ emitter.once(type, listenerFn, context);
+ } else {
+ emitter.on(type, listenerFn, context);
+ }
+ },
+ removeEventListener(type, listener, options) {
+ const capture = typeof options === "boolean" && options || typeof options === "object" && options.capture;
+ const context = typeof listener === "function" ? void 0 : listener;
+ type = capture ? `${type}capture` : type;
+ listener = typeof listener === "function" ? listener : listener.handleEvent;
+ this.off(type, listener, context);
+ },
+ dispatchEvent(e) {
+ if (!(e instanceof FederatedEvent)) {
+ throw new Error("Container cannot propagate events outside of the Federated Events API");
+ }
+ e.defaultPrevented = false;
+ e.path = null;
+ e.target = this;
+ e.manager.dispatchEvent(e);
+ return !e.defaultPrevented;
+ }
+ };
+
+ "use strict";
+ extensions.add(EventSystem);
+ extensions.mixin(Container, FederatedContainer);
+
+ "use strict";
+ class DOMPipe {
+ /**
+ * Constructor for the DOMPipe class.
+ * @param renderer - The renderer instance that this DOMPipe will be associated with.
+ */
+ constructor(renderer) {
+ /** Array to keep track of attached DOM elements */
+ this._attachedDomElements = [];
+ this._renderer = renderer;
+ this._renderer.runners.postrender.add(this);
+ this._renderer.runners.init.add(this);
+ this._domElement = document.createElement("div");
+ this._domElement.style.position = "absolute";
+ this._domElement.style.top = "0";
+ this._domElement.style.left = "0";
+ this._domElement.style.pointerEvents = "none";
+ this._domElement.style.zIndex = "1000";
+ }
+ /** Initializes the DOMPipe, setting up the main DOM element and adding it to the document body. */
+ init() {
+ this._canvasObserver = new CanvasObserver({
+ domElement: this._domElement,
+ renderer: this._renderer
+ });
+ }
+ /**
+ * Adds a renderable DOM container to the list of attached elements.
+ * @param domContainer - The DOM container to be added.
+ * @param _instructionSet - The instruction set (unused).
+ */
+ addRenderable(domContainer, _instructionSet) {
+ if (!this._attachedDomElements.includes(domContainer)) {
+ this._attachedDomElements.push(domContainer);
+ }
+ }
+ /**
+ * Updates a renderable DOM container.
+ * @param _domContainer - The DOM container to be updated (unused).
+ */
+ updateRenderable(_domContainer) {
+ }
+ /**
+ * Validates a renderable DOM container.
+ * @param _domContainer - The DOM container to be validated (unused).
+ * @returns Always returns true as validation is not required.
+ */
+ validateRenderable(_domContainer) {
+ return true;
+ }
+ /** Handles the post-rendering process, ensuring DOM elements are correctly positioned and visible. */
+ postrender() {
+ const attachedDomElements = this._attachedDomElements;
+ if (attachedDomElements.length === 0) {
+ this._domElement.remove();
+ return;
+ }
+ this._canvasObserver.ensureAttached();
+ for (let i = 0; i < attachedDomElements.length; i++) {
+ const domContainer = attachedDomElements[i];
+ const element = domContainer.element;
+ if (!domContainer.parent || domContainer.globalDisplayStatus < 7) {
+ element == null ? void 0 : element.remove();
+ attachedDomElements.splice(i, 1);
+ i--;
+ } else {
+ if (!this._domElement.contains(element)) {
+ element.style.position = "absolute";
+ element.style.pointerEvents = "auto";
+ this._domElement.appendChild(element);
+ }
+ const wt = domContainer.worldTransform;
+ const anchor = domContainer._anchor;
+ const ax = domContainer.width * anchor.x;
+ const ay = domContainer.height * anchor.y;
+ element.style.transformOrigin = `${ax}px ${ay}px`;
+ element.style.transform = `matrix(${wt.a}, ${wt.b}, ${wt.c}, ${wt.d}, ${wt.tx - ax}, ${wt.ty - ay})`;
+ element.style.opacity = domContainer.groupAlpha.toString();
+ }
+ }
+ }
+ /** Destroys the DOMPipe, removing all attached DOM elements and cleaning up resources. */
+ destroy() {
+ var _a;
+ this._renderer.runners.postrender.remove(this);
+ for (let i = 0; i < this._attachedDomElements.length; i++) {
+ const domContainer = this._attachedDomElements[i];
+ (_a = domContainer.element) == null ? void 0 : _a.remove();
+ }
+ this._attachedDomElements.length = 0;
+ this._domElement.remove();
+ this._canvasObserver.destroy();
+ this._renderer = null;
+ }
+ }
+ /**
+ * Static property defining the extension type and name for the DOMPipe.
+ * This is used to register the DOMPipe with different rendering pipelines.
+ */
+ DOMPipe.extension = {
+ type: [
+ ExtensionType.WebGLPipes,
+ ExtensionType.WebGPUPipes,
+ ExtensionType.CanvasPipes
+ ],
+ name: "dom"
+ };
+
+ "use strict";
+ class ViewContainer extends Container {
+ // eslint-disable-next-line @typescript-eslint/no-useless-constructor
+ constructor(options) {
+ super(options);
+ /** @internal */
+ this.canBundle = true;
+ /** @internal */
+ this.allowChildren = false;
+ /** @internal */
+ this._roundPixels = 0;
+ /** @internal */
+ this._lastUsed = -1;
+ /** @internal */
+ this._gpuData = /* @__PURE__ */ Object.create(null);
+ this._bounds = new Bounds(0, 1, 0, 0);
+ this._boundsDirty = true;
+ }
+ /**
+ * The local bounds of the view in its own coordinate space.
+ * Bounds are automatically updated when the view's content changes.
+ * @example
+ * ```ts
+ * // Get bounds dimensions
+ * const bounds = view.bounds;
+ * console.log(`Width: ${bounds.maxX - bounds.minX}`);
+ * console.log(`Height: ${bounds.maxY - bounds.minY}`);
+ * ```
+ * @returns The rectangular bounds of the view
+ * @see {@link Bounds} For bounds operations
+ */
+ get bounds() {
+ if (!this._boundsDirty)
+ return this._bounds;
+ this.updateBounds();
+ this._boundsDirty = false;
+ return this._bounds;
+ }
+ /**
+ * Whether or not to round the x/y position of the sprite.
+ * @example
+ * ```ts
+ * // Enable pixel rounding for crisp rendering
+ * view.roundPixels = true;
+ * ```
+ * @default false
+ */
+ get roundPixels() {
+ return !!this._roundPixels;
+ }
+ set roundPixels(value) {
+ this._roundPixels = value ? 1 : 0;
+ }
+ /**
+ * Checks if the object contains the given point in local coordinates.
+ * Uses the view's bounds for hit testing.
+ * @example
+ * ```ts
+ * // Basic point check
+ * const localPoint = { x: 50, y: 25 };
+ * const contains = view.containsPoint(localPoint);
+ * console.log('Point is inside:', contains);
+ * ```
+ * @param point - The point to check in local coordinates
+ * @returns True if the point is within the view's bounds
+ * @see {@link ViewContainer#bounds} For the bounds used in hit testing
+ * @see {@link Container#toLocal} For converting global coordinates to local
+ */
+ containsPoint(point) {
+ const bounds = this.bounds;
+ const { x, y } = point;
+ return x >= bounds.minX && x <= bounds.maxX && y >= bounds.minY && y <= bounds.maxY;
+ }
+ /** @private */
+ onViewUpdate() {
+ this._didViewChangeTick++;
+ this._boundsDirty = true;
+ if (this.didViewUpdate)
+ return;
+ this.didViewUpdate = true;
+ const renderGroup = this.renderGroup || this.parentRenderGroup;
+ if (renderGroup) {
+ renderGroup.onChildViewUpdate(this);
+ }
+ }
+ destroy(options) {
+ var _a, _b;
+ super.destroy(options);
+ this._bounds = null;
+ for (const key in this._gpuData) {
+ (_b = (_a = this._gpuData[key]).destroy) == null ? void 0 : _b.call(_a);
+ }
+ this._gpuData = null;
+ }
+ /**
+ * Collects renderables for the view container.
+ * @param instructionSet - The instruction set to collect renderables for.
+ * @param renderer - The renderer to collect renderables for.
+ * @param currentLayer - The current render layer.
+ * @internal
+ */
+ collectRenderablesSimple(instructionSet, renderer, currentLayer) {
+ const { renderPipes } = renderer;
+ renderPipes.blendMode.pushBlendMode(this, this.groupBlendMode, instructionSet);
+ const rp = renderPipes;
+ rp[this.renderPipeId].addRenderable(this, instructionSet);
+ this.didViewUpdate = false;
+ const children = this.children;
+ const length = children.length;
+ for (let i = 0; i < length; i++) {
+ children[i].collectRenderables(instructionSet, renderer, currentLayer);
+ }
+ renderPipes.blendMode.popBlendMode(instructionSet);
+ }
+ }
+
+ "use strict";
+ var __defProp$15 = Object.defineProperty;
+ var __getOwnPropSymbols$16 = Object.getOwnPropertySymbols;
+ var __hasOwnProp$16 = Object.prototype.hasOwnProperty;
+ var __propIsEnum$16 = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$15 = (obj, key, value) => key in obj ? __defProp$15(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$15 = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$16.call(b, prop))
+ __defNormalProp$15(a, prop, b[prop]);
+ if (__getOwnPropSymbols$16)
+ for (var prop of __getOwnPropSymbols$16(b)) {
+ if (__propIsEnum$16.call(b, prop))
+ __defNormalProp$15(a, prop, b[prop]);
+ }
+ return a;
+ };
+ var __objRest$n = (source, exclude) => {
+ var target = {};
+ for (var prop in source)
+ if (__hasOwnProp$16.call(source, prop) && exclude.indexOf(prop) < 0)
+ target[prop] = source[prop];
+ if (source != null && __getOwnPropSymbols$16)
+ for (var prop of __getOwnPropSymbols$16(source)) {
+ if (exclude.indexOf(prop) < 0 && __propIsEnum$16.call(source, prop))
+ target[prop] = source[prop];
+ }
+ return target;
+ };
+ class DOMContainer extends ViewContainer {
+ /**
+ * @param options - The options for creating the DOM container.
+ */
+ constructor(options = {}) {
+ const _a = options, { element, anchor } = _a, rest = __objRest$n(_a, ["element", "anchor"]);
+ super(__spreadValues$15({
+ label: "DOMContainer"
+ }, rest));
+ /** @internal */
+ this.renderPipeId = "dom";
+ /** @internal */
+ this.batched = false;
+ this._anchor = new Point(0, 0);
+ if (anchor) {
+ this.anchor = anchor;
+ }
+ this.element = options.element || document.createElement("div");
+ }
+ /**
+ * The anchor sets the origin point of the container.
+ * Controls the relative positioning of the DOM element.
+ *
+ * The default is `(0,0)`, this means the container's origin is the top left.
+ * Setting the anchor to `(0.5,0.5)` means the container's origin is centered.
+ * Setting the anchor to `(1,1)` would mean the container's origin point will be the bottom right corner.
+ * @example
+ * ```ts
+ * const container = new DOMContainer();
+ *
+ * // Set anchor to center (shorthand)
+ * container.anchor = 0.5;
+ *
+ * // Set anchor to bottom-right
+ * container.anchor = { x: 1, y: 1 };
+ *
+ * // Set anchor to custom position
+ * container.anchor = new Point(0.3, 0.7);
+ * ```
+ */
+ get anchor() {
+ return this._anchor;
+ }
+ /**
+ * Sets the anchor point of the container.
+ * @param value - New anchor value:
+ * - number: Sets both x and y to same value
+ * - PointData: Sets x and y separately
+ */
+ set anchor(value) {
+ typeof value === "number" ? this._anchor.set(value) : this._anchor.copyFrom(value);
+ }
+ /**
+ * Sets the DOM element for this container.
+ * This will replace the current element and update the view.
+ * @param value - The new DOM element to use
+ * @example
+ * ```ts
+ * const domContainer = new DOMContainer();
+ * domContainer.element = document.createElement('input');
+ * ```
+ */
+ set element(value) {
+ if (this._element === value)
+ return;
+ this._element = value;
+ this.onViewUpdate();
+ }
+ /**
+ * The DOM element associated with this container.
+ * @example
+ * ```ts
+ * const domContainer = new DOMContainer();
+ * domContainer.element.innerHTML = 'Hello World!';
+ * document.body.appendChild(domContainer.element);
+ * ```
+ */
+ get element() {
+ return this._element;
+ }
+ /** @private */
+ updateBounds() {
+ const bounds = this._bounds;
+ const element = this._element;
+ if (!element) {
+ bounds.minX = 0;
+ bounds.minY = 0;
+ bounds.maxX = 0;
+ bounds.maxY = 0;
+ return;
+ }
+ const { offsetWidth, offsetHeight } = element;
+ bounds.minX = 0;
+ bounds.maxX = offsetWidth;
+ bounds.minY = 0;
+ bounds.maxY = offsetHeight;
+ }
+ /**
+ * Destroys this DOM container.
+ * @param options - Options parameter. A boolean will act as if all options
+ * have been set to that
+ * @example
+ * domContainer.destroy();
+ * domContainer.destroy(true);
+ */
+ destroy(options = false) {
+ var _a, _b;
+ super.destroy(options);
+ (_b = (_a = this._element) == null ? void 0 : _a.parentNode) == null ? void 0 : _b.removeChild(this._element);
+ this._element = null;
+ this._anchor = null;
+ }
+ }
+
+ "use strict";
+
+ "use strict";
+ extensions.add(DOMPipe);
+
+ "use strict";
+ var LoaderParserPriority = /* @__PURE__ */ ((LoaderParserPriority2) => {
+ LoaderParserPriority2[LoaderParserPriority2["Low"] = 0] = "Low";
+ LoaderParserPriority2[LoaderParserPriority2["Normal"] = 1] = "Normal";
+ LoaderParserPriority2[LoaderParserPriority2["High"] = 2] = "High";
+ return LoaderParserPriority2;
+ })(LoaderParserPriority || {});
+
+ "use strict";
+ const BrowserAdapter = {
+ createCanvas: (width, height) => {
+ const canvas = document.createElement("canvas");
+ canvas.width = width;
+ canvas.height = height;
+ return canvas;
+ },
+ createImage: () => new Image(),
+ getCanvasRenderingContext2D: () => CanvasRenderingContext2D,
+ getWebGLRenderingContext: () => WebGLRenderingContext,
+ getNavigator: () => navigator,
+ getBaseUrl: () => {
+ var _a;
+ return (_a = document.baseURI) != null ? _a : window.location.href;
+ },
+ getFontFaceSet: () => document.fonts,
+ fetch: (url, options) => fetch(url, options),
+ parseXML: (xml) => {
+ const parser = new DOMParser();
+ return parser.parseFromString(xml, "text/xml");
+ }
+ };
+
+ "use strict";
+ let currentAdapter = BrowserAdapter;
+ const DOMAdapter = {
+ /**
+ * Returns the current adapter.
+ * @returns {environment.Adapter} The current adapter.
+ */
+ get() {
+ return currentAdapter;
+ },
+ /**
+ * Sets the current adapter.
+ * @param adapter - The new adapter.
+ */
+ set(adapter) {
+ currentAdapter = adapter;
+ }
+ };
+
+ "use strict";
+ function assertPath(path2) {
+ if (typeof path2 !== "string") {
+ throw new TypeError(`Path must be a string. Received ${JSON.stringify(path2)}`);
+ }
+ }
+ function removeUrlParams(url) {
+ const re = url.split("?")[0];
+ return re.split("#")[0];
+ }
+ function escapeRegExp(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ }
+ function replaceAll(str, find, replace) {
+ return str.replace(new RegExp(escapeRegExp(find), "g"), replace);
+ }
+ function normalizeStringPosix(path2, allowAboveRoot) {
+ let res = "";
+ let lastSegmentLength = 0;
+ let lastSlash = -1;
+ let dots = 0;
+ let code = -1;
+ for (let i = 0; i <= path2.length; ++i) {
+ if (i < path2.length) {
+ code = path2.charCodeAt(i);
+ } else if (code === 47) {
+ break;
+ } else {
+ code = 47;
+ }
+ if (code === 47) {
+ if (lastSlash === i - 1 || dots === 1) {
+ } else if (lastSlash !== i - 1 && dots === 2) {
+ if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 || res.charCodeAt(res.length - 2) !== 46) {
+ if (res.length > 2) {
+ const lastSlashIndex = res.lastIndexOf("/");
+ if (lastSlashIndex !== res.length - 1) {
+ if (lastSlashIndex === -1) {
+ res = "";
+ lastSegmentLength = 0;
+ } else {
+ res = res.slice(0, lastSlashIndex);
+ lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
+ }
+ lastSlash = i;
+ dots = 0;
+ continue;
+ }
+ } else if (res.length === 2 || res.length === 1) {
+ res = "";
+ lastSegmentLength = 0;
+ lastSlash = i;
+ dots = 0;
+ continue;
+ }
+ }
+ if (allowAboveRoot) {
+ if (res.length > 0) {
+ res += "/..";
+ } else {
+ res = "..";
+ }
+ lastSegmentLength = 2;
+ }
+ } else {
+ if (res.length > 0) {
+ res += `/${path2.slice(lastSlash + 1, i)}`;
+ } else {
+ res = path2.slice(lastSlash + 1, i);
+ }
+ lastSegmentLength = i - lastSlash - 1;
+ }
+ lastSlash = i;
+ dots = 0;
+ } else if (code === 46 && dots !== -1) {
+ ++dots;
+ } else {
+ dots = -1;
+ }
+ }
+ return res;
+ }
+ const path = {
+ /**
+ * Converts a path to posix format.
+ * @param path - The path to convert to posix
+ * @example
+ * ```ts
+ * // Convert a Windows path to POSIX format
+ * path.toPosix('C:\\Users\\User\\Documents\\file.txt');
+ * // -> 'C:/Users/User/Documents/file.txt'
+ * ```
+ */
+ toPosix(path2) {
+ return replaceAll(path2, "\\", "/");
+ },
+ /**
+ * Checks if the path is a URL e.g. http://, https://
+ * @param path - The path to check
+ * @example
+ * ```ts
+ * // Check if a path is a URL
+ * path.isUrl('http://www.example.com');
+ * // -> true
+ * path.isUrl('C:/Users/User/Documents/file.txt');
+ * // -> false
+ * ```
+ */
+ isUrl(path2) {
+ return /^https?:/.test(this.toPosix(path2));
+ },
+ /**
+ * Checks if the path is a data URL
+ * @param path - The path to check
+ * @example
+ * ```ts
+ * // Check if a path is a data URL
+ * path.isDataUrl('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...');
+ * // -> true
+ * ```
+ */
+ isDataUrl(path2) {
+ return /^data:([a-z]+\/[a-z0-9-+.]+(;[a-z0-9-.!#$%*+.{}|~`]+=[a-z0-9-.!#$%*+.{}()_|~`]+)*)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s<>]*?)$/i.test(path2);
+ },
+ /**
+ * Checks if the path is a blob URL
+ * @param path - The path to check
+ * @example
+ * ```ts
+ * // Check if a path is a blob URL
+ * path.isBlobUrl('blob:http://www.example.com/12345678-1234-1234-1234-123456789012');
+ * // -> true
+ * ```
+ */
+ isBlobUrl(path2) {
+ return path2.startsWith("blob:");
+ },
+ /**
+ * Checks if the path has a protocol e.g. http://, https://, file:///, data:, blob:, C:/
+ * This will return true for windows file paths
+ * @param path - The path to check
+ * @example
+ * ```ts
+ * // Check if a path has a protocol
+ * path.hasProtocol('http://www.example.com');
+ * // -> true
+ * path.hasProtocol('C:/Users/User/Documents/file.txt');
+ * // -> true
+ * ```
+ */
+ hasProtocol(path2) {
+ return /^[^/:]+:/.test(this.toPosix(path2));
+ },
+ /**
+ * Returns the protocol of the path e.g. http://, https://, file:///, data:, blob:, C:/
+ * @param path - The path to get the protocol from
+ * @example
+ * ```ts
+ * // Get the protocol from a URL
+ * path.getProtocol('http://www.example.com/path/to/resource');
+ * // -> 'http://'
+ * // Get the protocol from a file path
+ * path.getProtocol('C:/Users/User/Documents/file.txt');
+ * // -> 'C:/'
+ * ```
+ */
+ getProtocol(path2) {
+ assertPath(path2);
+ path2 = this.toPosix(path2);
+ const matchFile = /^file:\/\/\//.exec(path2);
+ if (matchFile) {
+ return matchFile[0];
+ }
+ const matchProtocol = /^[^/:]+:\/{0,2}/.exec(path2);
+ if (matchProtocol) {
+ return matchProtocol[0];
+ }
+ return "";
+ },
+ /**
+ * Converts URL to an absolute path.
+ * When loading from a Web Worker, we must use absolute paths.
+ * If the URL is already absolute we return it as is
+ * If it's not, we convert it
+ * @param url - The URL to test
+ * @param customBaseUrl - The base URL to use
+ * @param customRootUrl - The root URL to use
+ * @example
+ * ```ts
+ * // Convert a relative URL to an absolute path
+ * path.toAbsolute('images/texture.png', 'http://example.com/assets/');
+ * // -> 'http://example.com/assets/images/texture.png'
+ * ```
+ */
+ toAbsolute(url, customBaseUrl, customRootUrl) {
+ assertPath(url);
+ if (this.isDataUrl(url) || this.isBlobUrl(url))
+ return url;
+ const baseUrl = removeUrlParams(this.toPosix(customBaseUrl != null ? customBaseUrl : DOMAdapter.get().getBaseUrl()));
+ const rootUrl = removeUrlParams(this.toPosix(customRootUrl != null ? customRootUrl : this.rootname(baseUrl)));
+ url = this.toPosix(url);
+ if (url.startsWith("/")) {
+ return path.join(rootUrl, url.slice(1));
+ }
+ const absolutePath = this.isAbsolute(url) ? url : this.join(baseUrl, url);
+ return absolutePath;
+ },
+ /**
+ * Normalizes the given path, resolving '..' and '.' segments
+ * @param path - The path to normalize
+ * @example
+ * ```ts
+ * // Normalize a path with relative segments
+ * path.normalize('http://www.example.com/foo/bar/../baz');
+ * // -> 'http://www.example.com/foo/baz'
+ * // Normalize a file path with relative segments
+ * path.normalize('C:\\Users\\User\\Documents\\..\\file.txt');
+ * // -> 'C:/Users/User/file.txt'
+ * ```
+ */
+ normalize(path2) {
+ assertPath(path2);
+ if (path2.length === 0)
+ return ".";
+ if (this.isDataUrl(path2) || this.isBlobUrl(path2))
+ return path2;
+ path2 = this.toPosix(path2);
+ let protocol = "";
+ const isAbsolute = path2.startsWith("/");
+ if (this.hasProtocol(path2)) {
+ protocol = this.rootname(path2);
+ path2 = path2.slice(protocol.length);
+ }
+ const trailingSeparator = path2.endsWith("/");
+ path2 = normalizeStringPosix(path2, false);
+ if (path2.length > 0 && trailingSeparator)
+ path2 += "/";
+ if (isAbsolute)
+ return `/${path2}`;
+ return protocol + path2;
+ },
+ /**
+ * Determines if path is an absolute path.
+ * Absolute paths can be urls, data urls, or paths on disk
+ * @param path - The path to test
+ * @example
+ * ```ts
+ * // Check if a path is absolute
+ * path.isAbsolute('http://www.example.com/foo/bar');
+ * // -> true
+ * path.isAbsolute('C:/Users/User/Documents/file.txt');
+ * // -> true
+ * ```
+ */
+ isAbsolute(path2) {
+ assertPath(path2);
+ path2 = this.toPosix(path2);
+ if (this.hasProtocol(path2))
+ return true;
+ return path2.startsWith("/");
+ },
+ /**
+ * Joins all given path segments together using the platform-specific separator as a delimiter,
+ * then normalizes the resulting path
+ * @param segments - The segments of the path to join
+ * @example
+ * ```ts
+ * // Join multiple path segments
+ * path.join('assets', 'images', 'sprite.png');
+ * // -> 'assets/images/sprite.png'
+ * // Join with relative segments
+ * path.join('assets', 'images', '../textures', 'sprite.png');
+ * // -> 'assets/textures/sprite.png'
+ * ```
+ */
+ join(...segments) {
+ var _a;
+ if (segments.length === 0) {
+ return ".";
+ }
+ let joined;
+ for (let i = 0; i < segments.length; ++i) {
+ const arg = segments[i];
+ assertPath(arg);
+ if (arg.length > 0) {
+ if (joined === void 0)
+ joined = arg;
+ else {
+ const prevArg = (_a = segments[i - 1]) != null ? _a : "";
+ if (this.joinExtensions.includes(this.extname(prevArg).toLowerCase())) {
+ joined += `/../${arg}`;
+ } else {
+ joined += `/${arg}`;
+ }
+ }
+ }
+ }
+ if (joined === void 0) {
+ return ".";
+ }
+ return this.normalize(joined);
+ },
+ /**
+ * Returns the directory name of a path
+ * @param path - The path to parse
+ * @example
+ * ```ts
+ * // Get the directory name of a path
+ * path.dirname('http://www.example.com/foo/bar/baz.png');
+ * // -> 'http://www.example.com/foo/bar'
+ * // Get the directory name of a file path
+ * path.dirname('C:/Users/User/Documents/file.txt');
+ * // -> 'C:/Users/User/Documents'
+ * ```
+ */
+ dirname(path2) {
+ assertPath(path2);
+ if (path2.length === 0)
+ return ".";
+ path2 = this.toPosix(path2);
+ let code = path2.charCodeAt(0);
+ const hasRoot = code === 47;
+ let end = -1;
+ let matchedSlash = true;
+ const proto = this.getProtocol(path2);
+ const origpath = path2;
+ path2 = path2.slice(proto.length);
+ for (let i = path2.length - 1; i >= 1; --i) {
+ code = path2.charCodeAt(i);
+ if (code === 47) {
+ if (!matchedSlash) {
+ end = i;
+ break;
+ }
+ } else {
+ matchedSlash = false;
+ }
+ }
+ if (end === -1)
+ return hasRoot ? "/" : this.isUrl(origpath) ? proto + path2 : proto;
+ if (hasRoot && end === 1)
+ return "//";
+ return proto + path2.slice(0, end);
+ },
+ /**
+ * Returns the root of the path e.g. /, C:/, file:///, http://domain.com/
+ * @param path - The path to parse
+ * @example
+ * ```ts
+ * // Get the root of a URL
+ * path.rootname('http://www.example.com/foo/bar/baz.png');
+ * // -> 'http://www.example.com/'
+ * // Get the root of a file path
+ * path.rootname('C:/Users/User/Documents/file.txt');
+ * // -> 'C:/'
+ * ```
+ */
+ rootname(path2) {
+ assertPath(path2);
+ path2 = this.toPosix(path2);
+ let root = "";
+ if (path2.startsWith("/"))
+ root = "/";
+ else {
+ root = this.getProtocol(path2);
+ }
+ if (this.isUrl(path2)) {
+ const index = path2.indexOf("/", root.length);
+ if (index !== -1) {
+ root = path2.slice(0, index);
+ } else
+ root = path2;
+ if (!root.endsWith("/"))
+ root += "/";
+ }
+ return root;
+ },
+ /**
+ * Returns the last portion of a path
+ * @param path - The path to test
+ * @param ext - Optional extension to remove
+ * @example
+ * ```ts
+ * // Get the basename of a URL
+ * path.basename('http://www.example.com/foo/bar/baz.png');
+ * // -> 'baz.png'
+ * // Get the basename of a file path
+ * path.basename('C:/Users/User/Documents/file.txt');
+ * // -> 'file.txt'
+ * ```
+ */
+ basename(path2, ext) {
+ assertPath(path2);
+ if (ext)
+ assertPath(ext);
+ path2 = removeUrlParams(this.toPosix(path2));
+ let start = 0;
+ let end = -1;
+ let matchedSlash = true;
+ let i;
+ if (ext !== void 0 && ext.length > 0 && ext.length <= path2.length) {
+ if (ext.length === path2.length && ext === path2)
+ return "";
+ let extIdx = ext.length - 1;
+ let firstNonSlashEnd = -1;
+ for (i = path2.length - 1; i >= 0; --i) {
+ const code = path2.charCodeAt(i);
+ if (code === 47) {
+ if (!matchedSlash) {
+ start = i + 1;
+ break;
+ }
+ } else {
+ if (firstNonSlashEnd === -1) {
+ matchedSlash = false;
+ firstNonSlashEnd = i + 1;
+ }
+ if (extIdx >= 0) {
+ if (code === ext.charCodeAt(extIdx)) {
+ if (--extIdx === -1) {
+ end = i;
+ }
+ } else {
+ extIdx = -1;
+ end = firstNonSlashEnd;
+ }
+ }
+ }
+ }
+ if (start === end)
+ end = firstNonSlashEnd;
+ else if (end === -1)
+ end = path2.length;
+ return path2.slice(start, end);
+ }
+ for (i = path2.length - 1; i >= 0; --i) {
+ if (path2.charCodeAt(i) === 47) {
+ if (!matchedSlash) {
+ start = i + 1;
+ break;
+ }
+ } else if (end === -1) {
+ matchedSlash = false;
+ end = i + 1;
+ }
+ }
+ if (end === -1)
+ return "";
+ return path2.slice(start, end);
+ },
+ /**
+ * Returns the extension of the path, from the last occurrence of the . (period) character to end of string in the last
+ * portion of the path. If there is no . in the last portion of the path, or if there are no . characters other than
+ * the first character of the basename of path, an empty string is returned.
+ * @param path - The path to parse
+ * @example
+ * ```ts
+ * // Get the extension of a URL
+ * path.extname('http://www.example.com/foo/bar/baz.png');
+ * // -> '.png'
+ * // Get the extension of a file path
+ * path.extname('C:/Users/User/Documents/file.txt');
+ * // -> '.txt'
+ * ```
+ */
+ extname(path2) {
+ assertPath(path2);
+ path2 = removeUrlParams(this.toPosix(path2));
+ let startDot = -1;
+ let startPart = 0;
+ let end = -1;
+ let matchedSlash = true;
+ let preDotState = 0;
+ for (let i = path2.length - 1; i >= 0; --i) {
+ const code = path2.charCodeAt(i);
+ if (code === 47) {
+ if (!matchedSlash) {
+ startPart = i + 1;
+ break;
+ }
+ continue;
+ }
+ if (end === -1) {
+ matchedSlash = false;
+ end = i + 1;
+ }
+ if (code === 46) {
+ if (startDot === -1)
+ startDot = i;
+ else if (preDotState !== 1)
+ preDotState = 1;
+ } else if (startDot !== -1) {
+ preDotState = -1;
+ }
+ }
+ if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
+ return "";
+ }
+ return path2.slice(startDot, end);
+ },
+ /**
+ * Parses a path into an object containing the 'root', `dir`, `base`, `ext`, and `name` properties.
+ * @param path - The path to parse
+ * @example
+ * ```ts
+ * // Parse a URL
+ * const parsed = path.parse('http://www.example.com/foo/bar/baz.png');
+ * // -> {
+ * // root: 'http://www.example.com/',
+ * // dir: 'http://www.example.com/foo/bar',
+ * // base: 'baz.png',
+ * // ext: '.png',
+ * // name: 'baz'
+ * // }
+ * // Parse a file path
+ * const parsedFile = path.parse('C:/Users/User/Documents/file.txt');
+ * // -> {
+ * // root: 'C:/',
+ * // dir: 'C:/Users/User/Documents',
+ * // base: 'file.txt',
+ * // ext: '.txt',
+ * // name: 'file'
+ * // }
+ * ```
+ */
+ parse(path2) {
+ assertPath(path2);
+ const ret = { root: "", dir: "", base: "", ext: "", name: "" };
+ if (path2.length === 0)
+ return ret;
+ path2 = removeUrlParams(this.toPosix(path2));
+ let code = path2.charCodeAt(0);
+ const isAbsolute = this.isAbsolute(path2);
+ let start;
+ const protocol = "";
+ ret.root = this.rootname(path2);
+ if (isAbsolute || this.hasProtocol(path2)) {
+ start = 1;
+ } else {
+ start = 0;
+ }
+ let startDot = -1;
+ let startPart = 0;
+ let end = -1;
+ let matchedSlash = true;
+ let i = path2.length - 1;
+ let preDotState = 0;
+ for (; i >= start; --i) {
+ code = path2.charCodeAt(i);
+ if (code === 47) {
+ if (!matchedSlash) {
+ startPart = i + 1;
+ break;
+ }
+ continue;
+ }
+ if (end === -1) {
+ matchedSlash = false;
+ end = i + 1;
+ }
+ if (code === 46) {
+ if (startDot === -1)
+ startDot = i;
+ else if (preDotState !== 1)
+ preDotState = 1;
+ } else if (startDot !== -1) {
+ preDotState = -1;
+ }
+ }
+ if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
+ if (end !== -1) {
+ if (startPart === 0 && isAbsolute)
+ ret.base = ret.name = path2.slice(1, end);
+ else
+ ret.base = ret.name = path2.slice(startPart, end);
+ }
+ } else {
+ if (startPart === 0 && isAbsolute) {
+ ret.name = path2.slice(1, startDot);
+ ret.base = path2.slice(1, end);
+ } else {
+ ret.name = path2.slice(startPart, startDot);
+ ret.base = path2.slice(startPart, end);
+ }
+ ret.ext = path2.slice(startDot, end);
+ }
+ ret.dir = this.dirname(path2);
+ if (protocol)
+ ret.dir = protocol + ret.dir;
+ return ret;
+ },
+ sep: "/",
+ delimiter: ":",
+ joinExtensions: [".html"]
+ };
+
+ "use strict";
+ const convertToList = (input, transform, forceTransform = false) => {
+ if (!Array.isArray(input)) {
+ input = [input];
+ }
+ if (!transform) {
+ return input;
+ }
+ return input.map((item) => {
+ if (typeof item === "string" || forceTransform) {
+ return transform(item);
+ }
+ return item;
+ });
+ };
+
+ "use strict";
+ function processX(base, ids, depth, result, tags) {
+ const id = ids[depth];
+ for (let i = 0; i < id.length; i++) {
+ const value = id[i];
+ if (depth < ids.length - 1) {
+ processX(base.replace(result[depth], value), ids, depth + 1, result, tags);
+ } else {
+ tags.push(base.replace(result[depth], value));
+ }
+ }
+ }
+ function createStringVariations(string) {
+ const regex = /\{(.*?)\}/g;
+ const result = string.match(regex);
+ const tags = [];
+ if (result) {
+ const ids = [];
+ result.forEach((vars) => {
+ const split = vars.substring(1, vars.length - 1).split(",");
+ ids.push(split);
+ });
+ processX(string, ids, 0, result, tags);
+ } else {
+ tags.push(string);
+ }
+ return tags;
+ }
+
+ "use strict";
+ const isSingleItem = (item) => !Array.isArray(item);
+
+ "use strict";
+ var __defProp$14 = Object.defineProperty;
+ var __getOwnPropSymbols$15 = Object.getOwnPropertySymbols;
+ var __hasOwnProp$15 = Object.prototype.hasOwnProperty;
+ var __propIsEnum$15 = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$14 = (obj, key, value) => key in obj ? __defProp$14(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$14 = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$15.call(b, prop))
+ __defNormalProp$14(a, prop, b[prop]);
+ if (__getOwnPropSymbols$15)
+ for (var prop of __getOwnPropSymbols$15(b)) {
+ if (__propIsEnum$15.call(b, prop))
+ __defNormalProp$14(a, prop, b[prop]);
+ }
+ return a;
+ };
+ class Resolver {
+ constructor() {
+ this._defaultBundleIdentifierOptions = {
+ connector: "-",
+ createBundleAssetId: (bundleId, assetId) => `${bundleId}${this._bundleIdConnector}${assetId}`,
+ extractAssetIdFromBundle: (bundleId, assetBundleId) => assetBundleId.replace(`${bundleId}${this._bundleIdConnector}`, "")
+ };
+ /** The character that is used to connect the bundleId and the assetId when generating a bundle asset id key */
+ this._bundleIdConnector = this._defaultBundleIdentifierOptions.connector;
+ /**
+ * A function that generates a bundle asset id key from a bundleId and an assetId
+ * @param bundleId - the bundleId
+ * @param assetId - the assetId
+ * @returns the bundle asset id key
+ */
+ this._createBundleAssetId = this._defaultBundleIdentifierOptions.createBundleAssetId;
+ /**
+ * A function that generates an assetId from a bundle asset id key. This is the reverse of generateBundleAssetId
+ * @param bundleId - the bundleId
+ * @param assetBundleId - the bundle asset id key
+ * @returns the assetId
+ */
+ this._extractAssetIdFromBundle = this._defaultBundleIdentifierOptions.extractAssetIdFromBundle;
+ this._assetMap = {};
+ this._preferredOrder = [];
+ this._parsers = [];
+ this._resolverHash = {};
+ this._bundles = {};
+ }
+ /**
+ * Override how the resolver deals with generating bundle ids.
+ * must be called before any bundles are added
+ * @param bundleIdentifier - the bundle identifier options
+ */
+ setBundleIdentifier(bundleIdentifier) {
+ var _a, _b, _c;
+ this._bundleIdConnector = (_a = bundleIdentifier.connector) != null ? _a : this._bundleIdConnector;
+ this._createBundleAssetId = (_b = bundleIdentifier.createBundleAssetId) != null ? _b : this._createBundleAssetId;
+ this._extractAssetIdFromBundle = (_c = bundleIdentifier.extractAssetIdFromBundle) != null ? _c : this._extractAssetIdFromBundle;
+ if (this._extractAssetIdFromBundle("foo", this._createBundleAssetId("foo", "bar")) !== "bar") {
+ throw new Error("[Resolver] GenerateBundleAssetId are not working correctly");
+ }
+ }
+ /**
+ * Let the resolver know which assets you prefer to use when resolving assets.
+ * Multiple prefer user defined rules can be added.
+ * @example
+ * resolver.prefer({
+ * // first look for something with the correct format, and then then correct resolution
+ * priority: ['format', 'resolution'],
+ * params:{
+ * format:'webp', // prefer webp images
+ * resolution: 2, // prefer a resolution of 2
+ * }
+ * })
+ * resolver.add('foo', ['bar@2x.webp', 'bar@2x.png', 'bar.webp', 'bar.png']);
+ * resolver.resolveUrl('foo') // => 'bar@2x.webp'
+ * @param preferOrders - the prefer options
+ */
+ prefer(...preferOrders) {
+ preferOrders.forEach((prefer) => {
+ this._preferredOrder.push(prefer);
+ if (!prefer.priority) {
+ prefer.priority = Object.keys(prefer.params);
+ }
+ });
+ this._resolverHash = {};
+ }
+ /**
+ * Set the base path to prepend to all urls when resolving
+ * @example
+ * resolver.basePath = 'https://home.com/';
+ * resolver.add('foo', 'bar.ong');
+ * resolver.resolveUrl('foo', 'bar.png'); // => 'https://home.com/bar.png'
+ * @param basePath - the base path to use
+ */
+ set basePath(basePath) {
+ this._basePath = basePath;
+ }
+ get basePath() {
+ return this._basePath;
+ }
+ /**
+ * Set the root path for root-relative URLs. By default the `basePath`'s root is used. If no `basePath` is set, then the
+ * default value for browsers is `window.location.origin`
+ * @example
+ * // Application hosted on https://home.com/some-path/index.html
+ * resolver.basePath = 'https://home.com/some-path/';
+ * resolver.rootPath = 'https://home.com/';
+ * resolver.add('foo', '/bar.png');
+ * resolver.resolveUrl('foo', '/bar.png'); // => 'https://home.com/bar.png'
+ * @param rootPath - the root path to use
+ */
+ set rootPath(rootPath) {
+ this._rootPath = rootPath;
+ }
+ get rootPath() {
+ return this._rootPath;
+ }
+ /**
+ * All the active URL parsers that help the parser to extract information and create
+ * an asset object-based on parsing the URL itself.
+ *
+ * Can be added using the extensions API
+ * @example
+ * resolver.add('foo', [
+ * {
+ * resolution: 2,
+ * format: 'png',
+ * src: 'image@2x.png',
+ * },
+ * {
+ * resolution:1,
+ * format:'png',
+ * src: 'image.png',
+ * },
+ * ]);
+ *
+ * // With a url parser the information such as resolution and file format could extracted from the url itself:
+ * extensions.add({
+ * extension: ExtensionType.ResolveParser,
+ * test: loadTextures.test, // test if url ends in an image
+ * parse: (value: string) =>
+ * ({
+ * resolution: parseFloat(Resolver.RETINA_PREFIX.exec(value)?.[1] ?? '1'),
+ * format: value.split('.').pop(),
+ * src: value,
+ * }),
+ * });
+ *
+ * // Now resolution and format can be extracted from the url
+ * resolver.add('foo', [
+ * 'image@2x.png',
+ * 'image.png',
+ * ]);
+ */
+ get parsers() {
+ return this._parsers;
+ }
+ /** Used for testing, this resets the resolver to its initial state */
+ reset() {
+ this.setBundleIdentifier(this._defaultBundleIdentifierOptions);
+ this._assetMap = {};
+ this._preferredOrder = [];
+ this._resolverHash = {};
+ this._rootPath = null;
+ this._basePath = null;
+ this._manifest = null;
+ this._bundles = {};
+ this._defaultSearchParams = null;
+ }
+ /**
+ * Sets the default URL search parameters for the URL resolver. The urls can be specified as a string or an object.
+ * @param searchParams - the default url parameters to append when resolving urls
+ */
+ setDefaultSearchParams(searchParams) {
+ if (typeof searchParams === "string") {
+ this._defaultSearchParams = searchParams;
+ } else {
+ const queryValues = searchParams;
+ this._defaultSearchParams = Object.keys(queryValues).map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryValues[key])}`).join("&");
+ }
+ }
+ /**
+ * Returns the aliases for a given asset
+ * @param asset - the asset to get the aliases for
+ */
+ getAlias(asset) {
+ const { alias, src } = asset;
+ const aliasesToUse = convertToList(
+ alias || src,
+ (value) => {
+ if (typeof value === "string")
+ return value;
+ if (Array.isArray(value))
+ return value.map((v) => {
+ var _a;
+ return (_a = v == null ? void 0 : v.src) != null ? _a : v;
+ });
+ if (value == null ? void 0 : value.src)
+ return value.src;
+ return value;
+ },
+ true
+ );
+ return aliasesToUse;
+ }
+ /**
+ * Add a manifest to the asset resolver. This is a nice way to add all the asset information in one go.
+ * generally a manifest would be built using a tool.
+ * @param manifest - the manifest to add to the resolver
+ */
+ addManifest(manifest) {
+ if (this._manifest) {
+ warn("[Resolver] Manifest already exists, this will be overwritten");
+ }
+ this._manifest = manifest;
+ manifest.bundles.forEach((bundle) => {
+ this.addBundle(bundle.name, bundle.assets);
+ });
+ }
+ /**
+ * This adds a bundle of assets in one go so that you can resolve them as a group.
+ * For example you could add a bundle for each screen in you pixi app
+ * @example
+ * resolver.addBundle('animals', [
+ * { alias: 'bunny', src: 'bunny.png' },
+ * { alias: 'chicken', src: 'chicken.png' },
+ * { alias: 'thumper', src: 'thumper.png' },
+ * ]);
+ * // or
+ * resolver.addBundle('animals', {
+ * bunny: 'bunny.png',
+ * chicken: 'chicken.png',
+ * thumper: 'thumper.png',
+ * });
+ *
+ * const resolvedAssets = await resolver.resolveBundle('animals');
+ * @param bundleId - The id of the bundle to add
+ * @param assets - A record of the asset or assets that will be chosen from when loading via the specified key
+ */
+ addBundle(bundleId, assets) {
+ const assetNames = [];
+ let convertedAssets = assets;
+ if (!Array.isArray(assets)) {
+ convertedAssets = Object.entries(assets).map(([alias, src]) => {
+ if (typeof src === "string" || Array.isArray(src)) {
+ return { alias, src };
+ }
+ return __spreadValues$14({ alias }, src);
+ });
+ }
+ convertedAssets.forEach((asset) => {
+ const srcs = asset.src;
+ const aliases = asset.alias;
+ let ids;
+ if (typeof aliases === "string") {
+ const bundleAssetId = this._createBundleAssetId(bundleId, aliases);
+ assetNames.push(bundleAssetId);
+ ids = [aliases, bundleAssetId];
+ } else {
+ const bundleIds = aliases.map((name) => this._createBundleAssetId(bundleId, name));
+ assetNames.push(...bundleIds);
+ ids = [...aliases, ...bundleIds];
+ }
+ this.add(__spreadValues$14(__spreadValues$14({}, asset), {
+ alias: ids,
+ src: srcs
+ }));
+ });
+ this._bundles[bundleId] = assetNames;
+ }
+ /**
+ * Tells the resolver what keys are associated with witch asset.
+ * The most important thing the resolver does
+ * @example
+ * // Single key, single asset:
+ * resolver.add({alias: 'foo', src: 'bar.png');
+ * resolver.resolveUrl('foo') // => 'bar.png'
+ *
+ * // Multiple keys, single asset:
+ * resolver.add({alias: ['foo', 'boo'], src: 'bar.png'});
+ * resolver.resolveUrl('foo') // => 'bar.png'
+ * resolver.resolveUrl('boo') // => 'bar.png'
+ *
+ * // Multiple keys, multiple assets:
+ * resolver.add({alias: ['foo', 'boo'], src: ['bar.png', 'bar.webp']});
+ * resolver.resolveUrl('foo') // => 'bar.png'
+ *
+ * // Add custom data attached to the resolver
+ * Resolver.add({
+ * alias: 'bunnyBooBooSmooth',
+ * src: 'bunny{png,webp}',
+ * data: { scaleMode:SCALE_MODES.NEAREST }, // Base texture options
+ * });
+ *
+ * resolver.resolve('bunnyBooBooSmooth') // => { src: 'bunny.png', data: { scaleMode: SCALE_MODES.NEAREST } }
+ * @param aliases - the UnresolvedAsset or array of UnresolvedAssets to add to the resolver
+ */
+ add(aliases) {
+ const assets = [];
+ if (Array.isArray(aliases)) {
+ assets.push(...aliases);
+ } else {
+ assets.push(aliases);
+ }
+ let keyCheck;
+ keyCheck = (key) => {
+ if (this.hasKey(key)) {
+ warn(`[Resolver] already has key: ${key} overwriting`);
+ }
+ };
+ const assetArray = convertToList(assets);
+ assetArray.forEach((asset) => {
+ const { src } = asset;
+ let { data, format, loadParser: userDefinedLoadParser, parser: userDefinedParser } = asset;
+ const srcsToUse = convertToList(src).map((src2) => {
+ if (typeof src2 === "string") {
+ return createStringVariations(src2);
+ }
+ return Array.isArray(src2) ? src2 : [src2];
+ });
+ const aliasesToUse = this.getAlias(asset);
+ Array.isArray(aliasesToUse) ? aliasesToUse.forEach(keyCheck) : keyCheck(aliasesToUse);
+ const resolvedAssets = [];
+ srcsToUse.forEach((srcs) => {
+ srcs.forEach((src2) => {
+ var _a, _b, _c, _d;
+ let formattedAsset = {};
+ if (typeof src2 !== "object") {
+ formattedAsset.src = src2;
+ for (let i = 0; i < this._parsers.length; i++) {
+ const parser = this._parsers[i];
+ if (parser.test(src2)) {
+ formattedAsset = parser.parse(src2);
+ break;
+ }
+ }
+ } else {
+ data = (_a = src2.data) != null ? _a : data;
+ format = (_b = src2.format) != null ? _b : format;
+ if (src2.loadParser || src2.parser) {
+ userDefinedLoadParser = (_c = src2.loadParser) != null ? _c : userDefinedLoadParser;
+ userDefinedParser = (_d = src2.parser) != null ? _d : userDefinedParser;
+ }
+ formattedAsset = __spreadValues$14(__spreadValues$14({}, formattedAsset), src2);
+ }
+ if (!aliasesToUse) {
+ throw new Error(`[Resolver] alias is undefined for this asset: ${formattedAsset.src}`);
+ }
+ formattedAsset = this._buildResolvedAsset(formattedAsset, {
+ aliases: aliasesToUse,
+ data,
+ format,
+ loadParser: userDefinedLoadParser,
+ parser: userDefinedParser
+ });
+ resolvedAssets.push(formattedAsset);
+ });
+ });
+ aliasesToUse.forEach((alias) => {
+ this._assetMap[alias] = resolvedAssets;
+ });
+ });
+ }
+ // TODO: this needs an overload like load did in Assets
+ /**
+ * If the resolver has had a manifest set via setManifest, this will return the assets urls for
+ * a given bundleId or bundleIds.
+ * @example
+ * // Manifest Example
+ * const manifest = {
+ * bundles: [
+ * {
+ * name: 'load-screen',
+ * assets: [
+ * {
+ * alias: 'background',
+ * src: 'sunset.png',
+ * },
+ * {
+ * alias: 'bar',
+ * src: 'load-bar.{png,webp}',
+ * },
+ * ],
+ * },
+ * {
+ * name: 'game-screen',
+ * assets: [
+ * {
+ * alias: 'character',
+ * src: 'robot.png',
+ * },
+ * {
+ * alias: 'enemy',
+ * src: 'bad-guy.png',
+ * },
+ * ],
+ * },
+ * ]
+ * };
+ *
+ * resolver.setManifest(manifest);
+ * const resolved = resolver.resolveBundle('load-screen');
+ * @param bundleIds - The bundle ids to resolve
+ * @returns All the bundles assets or a hash of assets for each bundle specified
+ */
+ resolveBundle(bundleIds) {
+ const singleAsset = isSingleItem(bundleIds);
+ bundleIds = convertToList(bundleIds);
+ const out = {};
+ bundleIds.forEach((bundleId) => {
+ const assetNames = this._bundles[bundleId];
+ if (assetNames) {
+ const results = this.resolve(assetNames);
+ const assets = {};
+ for (const key in results) {
+ const asset = results[key];
+ assets[this._extractAssetIdFromBundle(bundleId, key)] = asset;
+ }
+ out[bundleId] = assets;
+ }
+ });
+ return singleAsset ? out[bundleIds[0]] : out;
+ }
+ /**
+ * Does exactly what resolve does, but returns just the URL rather than the whole asset object
+ * @param key - The key or keys to resolve
+ * @returns - The URLs associated with the key(s)
+ */
+ resolveUrl(key) {
+ const result = this.resolve(key);
+ if (typeof key !== "string") {
+ const out = {};
+ for (const i in result) {
+ out[i] = result[i].src;
+ }
+ return out;
+ }
+ return result.src;
+ }
+ resolve(keys) {
+ const singleAsset = isSingleItem(keys);
+ keys = convertToList(keys);
+ const result = {};
+ keys.forEach((key) => {
+ if (!this._resolverHash[key]) {
+ if (this._assetMap[key]) {
+ let assets = this._assetMap[key];
+ const preferredOrder = this._getPreferredOrder(assets);
+ preferredOrder == null ? void 0 : preferredOrder.priority.forEach((priorityKey) => {
+ preferredOrder.params[priorityKey].forEach((value) => {
+ const filteredAssets = assets.filter((asset) => {
+ if (asset[priorityKey]) {
+ return asset[priorityKey] === value;
+ }
+ return false;
+ });
+ if (filteredAssets.length) {
+ assets = filteredAssets;
+ }
+ });
+ });
+ this._resolverHash[key] = assets[0];
+ } else {
+ this._resolverHash[key] = this._buildResolvedAsset({
+ alias: [key],
+ src: key
+ }, {});
+ }
+ }
+ result[key] = this._resolverHash[key];
+ });
+ return singleAsset ? result[keys[0]] : result;
+ }
+ /**
+ * Checks if an asset with a given key exists in the resolver
+ * @param key - The key of the asset
+ */
+ hasKey(key) {
+ return !!this._assetMap[key];
+ }
+ /**
+ * Checks if a bundle with the given key exists in the resolver
+ * @param key - The key of the bundle
+ */
+ hasBundle(key) {
+ return !!this._bundles[key];
+ }
+ /**
+ * Internal function for figuring out what prefer criteria an asset should use.
+ * @param assets
+ */
+ _getPreferredOrder(assets) {
+ for (let i = 0; i < assets.length; i++) {
+ const asset = assets[i];
+ const preferred = this._preferredOrder.find((preference) => preference.params.format.includes(asset.format));
+ if (preferred) {
+ return preferred;
+ }
+ }
+ return this._preferredOrder[0];
+ }
+ /**
+ * Appends the default url parameters to the url
+ * @param url - The url to append the default parameters to
+ * @returns - The url with the default parameters appended
+ */
+ _appendDefaultSearchParams(url) {
+ if (!this._defaultSearchParams)
+ return url;
+ const paramConnector = /\?/.test(url) ? "&" : "?";
+ return `${url}${paramConnector}${this._defaultSearchParams}`;
+ }
+ _buildResolvedAsset(formattedAsset, data) {
+ var _a, _b;
+ const { aliases, data: assetData, loadParser, parser, format } = data;
+ if (this._basePath || this._rootPath) {
+ formattedAsset.src = path.toAbsolute(formattedAsset.src, this._basePath, this._rootPath);
+ }
+ formattedAsset.alias = (_a = aliases != null ? aliases : formattedAsset.alias) != null ? _a : [formattedAsset.src];
+ formattedAsset.src = this._appendDefaultSearchParams(formattedAsset.src);
+ formattedAsset.data = __spreadValues$14(__spreadValues$14({}, assetData || {}), formattedAsset.data);
+ formattedAsset.loadParser = loadParser != null ? loadParser : formattedAsset.loadParser;
+ formattedAsset.parser = parser != null ? parser : formattedAsset.parser;
+ formattedAsset.format = (_b = format != null ? format : formattedAsset.format) != null ? _b : getUrlExtension(formattedAsset.src);
+ return formattedAsset;
+ }
+ }
+ /**
+ * The prefix that denotes a URL is for a retina asset.
+ * @default /@([0-9\.]+)x/
+ * @example `@2x`
+ */
+ Resolver.RETINA_PREFIX = /@([0-9\.]+)x/;
+ function getUrlExtension(url) {
+ return url.split(".").pop().split("?").shift().split("#").shift();
+ }
+
+ "use strict";
+ const copySearchParams = (targetUrl, sourceUrl) => {
+ const searchParams = sourceUrl.split("?")[1];
+ if (searchParams) {
+ targetUrl += `?${searchParams}`;
+ }
+ return targetUrl;
+ };
+
+ "use strict";
+ const _Spritesheet = class _Spritesheet {
+ constructor(optionsOrTexture, arg1) {
+ /** For multi-packed spritesheets, this contains a reference to all the other spritesheets it depends on. */
+ this.linkedSheets = [];
+ let options = optionsOrTexture;
+ if ((optionsOrTexture == null ? void 0 : optionsOrTexture.source) instanceof TextureSource) {
+ options = {
+ texture: optionsOrTexture,
+ data: arg1
+ };
+ }
+ const { texture, data, cachePrefix = "" } = options;
+ this.cachePrefix = cachePrefix;
+ this._texture = texture instanceof Texture ? texture : null;
+ this.textureSource = texture.source;
+ this.textures = {};
+ this.animations = {};
+ this.data = data;
+ const metaResolution = parseFloat(data.meta.scale);
+ if (metaResolution) {
+ this.resolution = metaResolution;
+ texture.source.resolution = this.resolution;
+ } else {
+ this.resolution = texture.source._resolution;
+ }
+ this._frames = this.data.frames;
+ this._frameKeys = Object.keys(this._frames);
+ this._batchIndex = 0;
+ this._callback = null;
+ }
+ /**
+ * Parser spritesheet from loaded data. This is done asynchronously
+ * to prevent creating too many Texture within a single process.
+ */
+ parse() {
+ return new Promise((resolve) => {
+ this._callback = resolve;
+ this._batchIndex = 0;
+ if (this._frameKeys.length <= _Spritesheet.BATCH_SIZE) {
+ this._processFrames(0);
+ this._processAnimations();
+ this._parseComplete();
+ } else {
+ this._nextBatch();
+ }
+ });
+ }
+ /**
+ * Process a batch of frames
+ * @param initialFrameIndex - The index of frame to start.
+ */
+ _processFrames(initialFrameIndex) {
+ let frameIndex = initialFrameIndex;
+ const maxFrames = _Spritesheet.BATCH_SIZE;
+ while (frameIndex - initialFrameIndex < maxFrames && frameIndex < this._frameKeys.length) {
+ const i = this._frameKeys[frameIndex];
+ const data = this._frames[i];
+ const rect = data.frame;
+ if (rect) {
+ let frame = null;
+ let trim = null;
+ const sourceSize = data.trimmed !== false && data.sourceSize ? data.sourceSize : data.frame;
+ const orig = new Rectangle(
+ 0,
+ 0,
+ Math.floor(sourceSize.w) / this.resolution,
+ Math.floor(sourceSize.h) / this.resolution
+ );
+ if (data.rotated) {
+ frame = new Rectangle(
+ Math.floor(rect.x) / this.resolution,
+ Math.floor(rect.y) / this.resolution,
+ Math.floor(rect.h) / this.resolution,
+ Math.floor(rect.w) / this.resolution
+ );
+ } else {
+ frame = new Rectangle(
+ Math.floor(rect.x) / this.resolution,
+ Math.floor(rect.y) / this.resolution,
+ Math.floor(rect.w) / this.resolution,
+ Math.floor(rect.h) / this.resolution
+ );
+ }
+ if (data.trimmed !== false && data.spriteSourceSize) {
+ trim = new Rectangle(
+ Math.floor(data.spriteSourceSize.x) / this.resolution,
+ Math.floor(data.spriteSourceSize.y) / this.resolution,
+ Math.floor(rect.w) / this.resolution,
+ Math.floor(rect.h) / this.resolution
+ );
+ }
+ this.textures[i] = new Texture({
+ source: this.textureSource,
+ frame,
+ orig,
+ trim,
+ rotate: data.rotated ? 2 : 0,
+ defaultAnchor: data.anchor,
+ defaultBorders: data.borders,
+ label: i.toString()
+ });
+ }
+ frameIndex++;
+ }
+ }
+ /** Parse animations config. */
+ _processAnimations() {
+ const animations = this.data.animations || {};
+ for (const animName in animations) {
+ this.animations[animName] = [];
+ for (let i = 0; i < animations[animName].length; i++) {
+ const frameName = animations[animName][i];
+ this.animations[animName].push(this.textures[frameName]);
+ }
+ }
+ }
+ /** The parse has completed. */
+ _parseComplete() {
+ const callback = this._callback;
+ this._callback = null;
+ this._batchIndex = 0;
+ callback.call(this, this.textures);
+ }
+ /** Begin the next batch of textures. */
+ _nextBatch() {
+ this._processFrames(this._batchIndex * _Spritesheet.BATCH_SIZE);
+ this._batchIndex++;
+ setTimeout(() => {
+ if (this._batchIndex * _Spritesheet.BATCH_SIZE < this._frameKeys.length) {
+ this._nextBatch();
+ } else {
+ this._processAnimations();
+ this._parseComplete();
+ }
+ }, 0);
+ }
+ /**
+ * Destroy Spritesheet and don't use after this.
+ * @param {boolean} [destroyBase=false] - Whether to destroy the base texture as well
+ */
+ destroy(destroyBase = false) {
+ var _a;
+ for (const i in this.textures) {
+ this.textures[i].destroy();
+ }
+ this._frames = null;
+ this._frameKeys = null;
+ this.data = null;
+ this.textures = null;
+ if (destroyBase) {
+ (_a = this._texture) == null ? void 0 : _a.destroy();
+ this.textureSource.destroy();
+ }
+ this._texture = null;
+ this.textureSource = null;
+ this.linkedSheets = [];
+ }
+ };
+ /**
+ * The maximum number of Textures to build per process.
+ * @advanced
+ */
+ _Spritesheet.BATCH_SIZE = 1e3;
+ let Spritesheet = _Spritesheet;
+
+ "use strict";
+ const validImages = [
+ "jpg",
+ "png",
+ "jpeg",
+ "avif",
+ "webp",
+ "basis",
+ "etc2",
+ "bc7",
+ "bc6h",
+ "bc5",
+ "bc4",
+ "bc3",
+ "bc2",
+ "bc1",
+ "eac",
+ "astc"
+ ];
+ function getCacheableAssets(keys, asset, ignoreMultiPack) {
+ const out = {};
+ keys.forEach((key) => {
+ out[key] = asset;
+ });
+ Object.keys(asset.textures).forEach((key) => {
+ out[`${asset.cachePrefix}${key}`] = asset.textures[key];
+ });
+ if (!ignoreMultiPack) {
+ const basePath = path.dirname(keys[0]);
+ asset.linkedSheets.forEach((item, i) => {
+ const out2 = getCacheableAssets([`${basePath}/${asset.data.meta.related_multi_packs[i]}`], item, true);
+ Object.assign(out, out2);
+ });
+ }
+ return out;
+ }
+ const spritesheetAsset = {
+ extension: ExtensionType.Asset,
+ /** Handle the caching of the related Spritesheet Textures */
+ cache: {
+ test: (asset) => asset instanceof Spritesheet,
+ getCacheableAssets: (keys, asset) => getCacheableAssets(keys, asset, false)
+ },
+ /** Resolve the resolution of the asset. */
+ resolver: {
+ extension: {
+ type: ExtensionType.ResolveParser,
+ name: "resolveSpritesheet"
+ },
+ test: (value) => {
+ const tempURL = value.split("?")[0];
+ const split = tempURL.split(".");
+ const extension = split.pop();
+ const format = split.pop();
+ return extension === "json" && validImages.includes(format);
+ },
+ parse: (value) => {
+ var _a, _b;
+ const split = value.split(".");
+ return {
+ resolution: parseFloat((_b = (_a = Resolver.RETINA_PREFIX.exec(value)) == null ? void 0 : _a[1]) != null ? _b : "1"),
+ format: split[split.length - 2],
+ src: value
+ };
+ }
+ },
+ /**
+ * Loader plugin that parses sprite sheets!
+ * once the JSON has been loaded this checks to see if the JSON is spritesheet data.
+ * If it is, we load the spritesheets image and parse the data into Spritesheet
+ * All textures in the sprite sheet are then added to the cache
+ */
+ loader: {
+ /** used for deprecation purposes */
+ name: "spritesheetLoader",
+ id: "spritesheet",
+ extension: {
+ type: ExtensionType.LoadParser,
+ priority: LoaderParserPriority.Normal,
+ name: "spritesheetLoader"
+ },
+ async testParse(asset, options) {
+ return path.extname(options.src).toLowerCase() === ".json" && !!asset.frames;
+ },
+ async parse(asset, options, loader) {
+ var _a, _b, _c;
+ const {
+ texture: imageTexture,
+ // if user need to use preloaded texture
+ imageFilename,
+ // if user need to use custom filename (not from jsonFile.meta.image)
+ textureOptions,
+ // if user need to set texture options on texture
+ cachePrefix
+ // if user need to use custom cache prefix
+ } = (_a = options == null ? void 0 : options.data) != null ? _a : {};
+ let basePath = path.dirname(options.src);
+ if (basePath && basePath.lastIndexOf("/") !== basePath.length - 1) {
+ basePath += "/";
+ }
+ let texture;
+ if (imageTexture instanceof Texture) {
+ texture = imageTexture;
+ } else {
+ const imagePath = copySearchParams(basePath + (imageFilename != null ? imageFilename : asset.meta.image), options.src);
+ const assets = await loader.load([{ src: imagePath, data: textureOptions }]);
+ texture = assets[imagePath];
+ }
+ const spritesheet = new Spritesheet({
+ texture: texture.source,
+ data: asset,
+ cachePrefix
+ });
+ await spritesheet.parse();
+ const multiPacks = (_b = asset == null ? void 0 : asset.meta) == null ? void 0 : _b.related_multi_packs;
+ if (Array.isArray(multiPacks)) {
+ const promises = [];
+ for (const item of multiPacks) {
+ if (typeof item !== "string") {
+ continue;
+ }
+ let itemUrl = basePath + item;
+ if ((_c = options.data) == null ? void 0 : _c.ignoreMultiPack) {
+ continue;
+ }
+ itemUrl = copySearchParams(itemUrl, options.src);
+ promises.push(loader.load({
+ src: itemUrl,
+ data: {
+ textureOptions,
+ ignoreMultiPack: true
+ }
+ }));
+ }
+ const res = await Promise.all(promises);
+ spritesheet.linkedSheets = res;
+ res.forEach((item) => {
+ item.linkedSheets = [spritesheet].concat(spritesheet.linkedSheets.filter((sp) => sp !== item));
+ });
+ }
+ return spritesheet;
+ },
+ async unload(spritesheet, _resolvedAsset, loader) {
+ await loader.unload(spritesheet.textureSource._sourceOrigin);
+ spritesheet.destroy(false);
+ }
+ }
+ };
+
+ "use strict";
+ extensions.add(spritesheetAsset);
+
+ "use strict";
+ function updateQuadBounds(bounds, anchor, texture) {
+ const { width, height } = texture.orig;
+ const trim = texture.trim;
+ if (trim) {
+ const sourceWidth = trim.width;
+ const sourceHeight = trim.height;
+ bounds.minX = trim.x - anchor._x * width;
+ bounds.maxX = bounds.minX + sourceWidth;
+ bounds.minY = trim.y - anchor._y * height;
+ bounds.maxY = bounds.minY + sourceHeight;
+ } else {
+ bounds.minX = -anchor._x * width;
+ bounds.maxX = bounds.minX + width;
+ bounds.minY = -anchor._y * height;
+ bounds.maxY = bounds.minY + height;
+ }
+ }
+
+ "use strict";
+ var __defProp$13 = Object.defineProperty;
+ var __getOwnPropSymbols$14 = Object.getOwnPropertySymbols;
+ var __hasOwnProp$14 = Object.prototype.hasOwnProperty;
+ var __propIsEnum$14 = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$13 = (obj, key, value) => key in obj ? __defProp$13(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$13 = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$14.call(b, prop))
+ __defNormalProp$13(a, prop, b[prop]);
+ if (__getOwnPropSymbols$14)
+ for (var prop of __getOwnPropSymbols$14(b)) {
+ if (__propIsEnum$14.call(b, prop))
+ __defNormalProp$13(a, prop, b[prop]);
+ }
+ return a;
+ };
+ var __objRest$m = (source, exclude) => {
+ var target = {};
+ for (var prop in source)
+ if (__hasOwnProp$14.call(source, prop) && exclude.indexOf(prop) < 0)
+ target[prop] = source[prop];
+ if (source != null && __getOwnPropSymbols$14)
+ for (var prop of __getOwnPropSymbols$14(source)) {
+ if (exclude.indexOf(prop) < 0 && __propIsEnum$14.call(source, prop))
+ target[prop] = source[prop];
+ }
+ return target;
+ };
+ class Sprite extends ViewContainer {
+ /**
+ * @param options - The options for creating the sprite.
+ */
+ constructor(options = Texture.EMPTY) {
+ if (options instanceof Texture) {
+ options = { texture: options };
+ }
+ const _a = options, { texture = Texture.EMPTY, anchor, roundPixels, width, height } = _a, rest = __objRest$m(_a, ["texture", "anchor", "roundPixels", "width", "height"]);
+ super(__spreadValues$13({
+ label: "Sprite"
+ }, rest));
+ /** @internal */
+ this.renderPipeId = "sprite";
+ /** @internal */
+ this.batched = true;
+ this._visualBounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 };
+ this._anchor = new ObservablePoint(
+ {
+ _onUpdate: () => {
+ this.onViewUpdate();
+ }
+ }
+ );
+ if (anchor) {
+ this.anchor = anchor;
+ } else if (texture.defaultAnchor) {
+ this.anchor = texture.defaultAnchor;
+ }
+ this.texture = texture;
+ this.allowChildren = false;
+ this.roundPixels = roundPixels != null ? roundPixels : false;
+ if (width !== void 0)
+ this.width = width;
+ if (height !== void 0)
+ this.height = height;
+ }
+ /**
+ * Creates a new sprite based on a source texture, image, video, or canvas element.
+ * This is a convenience method that automatically creates and manages textures.
+ * @example
+ * ```ts
+ * // Create from path or URL
+ * const sprite = Sprite.from('assets/image.png');
+ *
+ * // Create from existing texture
+ * const sprite = Sprite.from(texture);
+ *
+ * // Create from canvas
+ * const canvas = document.createElement('canvas');
+ * const sprite = Sprite.from(canvas, true); // Skip caching new texture
+ * ```
+ * @param source - The source to create the sprite from. Can be a path to an image, a texture,
+ * or any valid texture source (canvas, video, etc.)
+ * @param skipCache - Whether to skip adding to the texture cache when creating a new texture
+ * @returns A new sprite based on the source
+ * @see {@link Texture.from} For texture creation details
+ * @see {@link Assets} For asset loading and management
+ */
+ static from(source, skipCache = false) {
+ if (source instanceof Texture) {
+ return new Sprite(source);
+ }
+ return new Sprite(Texture.from(source, skipCache));
+ }
+ set texture(value) {
+ value || (value = Texture.EMPTY);
+ const currentTexture = this._texture;
+ if (currentTexture === value)
+ return;
+ if (currentTexture && currentTexture.dynamic)
+ currentTexture.off("update", this.onViewUpdate, this);
+ if (value.dynamic)
+ value.on("update", this.onViewUpdate, this);
+ this._texture = value;
+ if (this._width) {
+ this._setWidth(this._width, this._texture.orig.width);
+ }
+ if (this._height) {
+ this._setHeight(this._height, this._texture.orig.height);
+ }
+ this.onViewUpdate();
+ }
+ /**
+ * The texture that is displayed by the sprite. When changed, automatically updates
+ * the sprite dimensions and manages texture event listeners.
+ * @example
+ * ```ts
+ * // Create sprite with texture
+ * const sprite = new Sprite({
+ * texture: Texture.from('sprite.png')
+ * });
+ *
+ * // Update texture
+ * sprite.texture = Texture.from('newSprite.png');
+ *
+ * // Use texture from spritesheet
+ * const sheet = await Assets.load('spritesheet.json');
+ * sprite.texture = sheet.textures['frame1.png'];
+ *
+ * // Reset to empty texture
+ * sprite.texture = Texture.EMPTY;
+ * ```
+ * @see {@link Texture} For texture creation and management
+ * @see {@link Assets} For asset loading
+ */
+ get texture() {
+ return this._texture;
+ }
+ /**
+ * The bounds of the sprite, taking into account the texture's trim area.
+ * @example
+ * ```ts
+ * const texture = new Texture({
+ * source: new TextureSource({ width: 300, height: 300 }),
+ * frame: new Rectangle(196, 66, 58, 56),
+ * trim: new Rectangle(4, 4, 58, 56),
+ * orig: new Rectangle(0, 0, 64, 64),
+ * rotate: 2,
+ * });
+ * const sprite = new Sprite(texture);
+ * const visualBounds = sprite.visualBounds;
+ * // console.log(visualBounds); // { minX: -4, maxX: 62, minY: -4, maxY: 60 }
+ */
+ get visualBounds() {
+ updateQuadBounds(this._visualBounds, this._anchor, this._texture);
+ return this._visualBounds;
+ }
+ /**
+ * @deprecated
+ * @ignore
+ */
+ get sourceBounds() {
+ deprecation("8.6.1", "Sprite.sourceBounds is deprecated, use visualBounds instead.");
+ return this.visualBounds;
+ }
+ /** @private */
+ updateBounds() {
+ const anchor = this._anchor;
+ const texture = this._texture;
+ const bounds = this._bounds;
+ const { width, height } = texture.orig;
+ bounds.minX = -anchor._x * width;
+ bounds.maxX = bounds.minX + width;
+ bounds.minY = -anchor._y * height;
+ bounds.maxY = bounds.minY + height;
+ }
+ /**
+ * Destroys this sprite renderable and optionally its texture.
+ * @param options - Options parameter. A boolean will act as if all options
+ * have been set to that value
+ * @example
+ * sprite.destroy();
+ * sprite.destroy(true);
+ * sprite.destroy({ texture: true, textureSource: true });
+ */
+ destroy(options = false) {
+ super.destroy(options);
+ const destroyTexture = typeof options === "boolean" ? options : options == null ? void 0 : options.texture;
+ if (destroyTexture) {
+ const destroyTextureSource = typeof options === "boolean" ? options : options == null ? void 0 : options.textureSource;
+ this._texture.destroy(destroyTextureSource);
+ }
+ this._texture = null;
+ this._visualBounds = null;
+ this._bounds = null;
+ this._anchor = null;
+ this._gpuData = null;
+ }
+ /**
+ * The anchor sets the origin point of the sprite. The default value is taken from the {@link Texture}
+ * and passed to the constructor.
+ *
+ * - The default is `(0,0)`, this means the sprite's origin is the top left.
+ * - Setting the anchor to `(0.5,0.5)` means the sprite's origin is centered.
+ * - Setting the anchor to `(1,1)` would mean the sprite's origin point will be the bottom right corner.
+ *
+ * If you pass only single parameter, it will set both x and y to the same value as shown in the example below.
+ * @example
+ * ```ts
+ * // Center the anchor point
+ * sprite.anchor = 0.5; // Sets both x and y to 0.5
+ * sprite.position.set(400, 300); // Sprite will be centered at this position
+ *
+ * // Set specific x/y anchor points
+ * sprite.anchor = {
+ * x: 1, // Right edge
+ * y: 0 // Top edge
+ * };
+ *
+ * // Using individual coordinates
+ * sprite.anchor.set(0.5, 1); // Center-bottom
+ *
+ * // For rotation around center
+ * sprite.anchor.set(0.5);
+ * sprite.rotation = Math.PI / 4; // 45 degrees around center
+ *
+ * // For scaling from center
+ * sprite.anchor.set(0.5);
+ * sprite.scale.set(2); // Scales from center point
+ * ```
+ */
+ get anchor() {
+ return this._anchor;
+ }
+ set anchor(value) {
+ typeof value === "number" ? this._anchor.set(value) : this._anchor.copyFrom(value);
+ }
+ /**
+ * The width of the sprite, setting this will actually modify the scale to achieve the value set.
+ * @example
+ * ```ts
+ * // Set width directly
+ * sprite.width = 200;
+ * console.log(sprite.scale.x); // Scale adjusted to match width
+ *
+ * // Set width while preserving aspect ratio
+ * const ratio = sprite.height / sprite.width;
+ * sprite.width = 300;
+ * sprite.height = 300 * ratio;
+ *
+ * // For better performance when setting both width and height
+ * sprite.setSize(300, 400); // Avoids recalculating bounds twice
+ *
+ * // Reset to original texture size
+ * sprite.width = sprite.texture.orig.width;
+ * ```
+ */
+ get width() {
+ return Math.abs(this.scale.x) * this._texture.orig.width;
+ }
+ set width(value) {
+ this._setWidth(value, this._texture.orig.width);
+ this._width = value;
+ }
+ /**
+ * The height of the sprite, setting this will actually modify the scale to achieve the value set.
+ * @example
+ * ```ts
+ * // Set height directly
+ * sprite.height = 150;
+ * console.log(sprite.scale.y); // Scale adjusted to match height
+ *
+ * // Set height while preserving aspect ratio
+ * const ratio = sprite.width / sprite.height;
+ * sprite.height = 200;
+ * sprite.width = 200 * ratio;
+ *
+ * // For better performance when setting both width and height
+ * sprite.setSize(300, 400); // Avoids recalculating bounds twice
+ *
+ * // Reset to original texture size
+ * sprite.height = sprite.texture.orig.height;
+ * ```
+ */
+ get height() {
+ return Math.abs(this.scale.y) * this._texture.orig.height;
+ }
+ set height(value) {
+ this._setHeight(value, this._texture.orig.height);
+ this._height = value;
+ }
+ /**
+ * Retrieves the size of the Sprite as a [Size]{@link Size} object based on the texture dimensions and scale.
+ * This is faster than getting width and height separately as it only calculates the bounds once.
+ * @example
+ * ```ts
+ * // Basic size retrieval
+ * const sprite = new Sprite(Texture.from('sprite.png'));
+ * const size = sprite.getSize();
+ * console.log(`Size: ${size.width}x${size.height}`);
+ *
+ * // Reuse existing size object
+ * const reuseSize = { width: 0, height: 0 };
+ * sprite.getSize(reuseSize);
+ * ```
+ * @param out - Optional object to store the size in, to avoid allocating a new object
+ * @returns The size of the Sprite
+ * @see {@link Sprite#width} For getting just the width
+ * @see {@link Sprite#height} For getting just the height
+ * @see {@link Sprite#setSize} For setting both width and height
+ */
+ getSize(out) {
+ out || (out = {});
+ out.width = Math.abs(this.scale.x) * this._texture.orig.width;
+ out.height = Math.abs(this.scale.y) * this._texture.orig.height;
+ return out;
+ }
+ /**
+ * Sets the size of the Sprite to the specified width and height.
+ * This is faster than setting width and height separately as it only recalculates bounds once.
+ * @example
+ * ```ts
+ * // Basic size setting
+ * const sprite = new Sprite(Texture.from('sprite.png'));
+ * sprite.setSize(100, 200); // Width: 100, Height: 200
+ *
+ * // Set uniform size
+ * sprite.setSize(100); // Sets both width and height to 100
+ *
+ * // Set size with object
+ * sprite.setSize({
+ * width: 200,
+ * height: 300
+ * });
+ *
+ * // Reset to texture size
+ * sprite.setSize(
+ * sprite.texture.orig.width,
+ * sprite.texture.orig.height
+ * );
+ * ```
+ * @param value - This can be either a number or a {@link Size} object
+ * @param height - The height to set. Defaults to the value of `width` if not provided
+ * @see {@link Sprite#width} For setting width only
+ * @see {@link Sprite#height} For setting height only
+ * @see {@link Sprite#texture} For the source dimensions
+ */
+ setSize(value, height) {
+ var _a;
+ if (typeof value === "object") {
+ height = (_a = value.height) != null ? _a : value.width;
+ value = value.width;
+ } else {
+ height != null ? height : height = value;
+ }
+ value !== void 0 && this._setWidth(value, this._texture.orig.width);
+ height !== void 0 && this._setHeight(height, this._texture.orig.height);
+ }
+ }
+
+ "use strict";
+ const tempBounds$4 = new Bounds();
+ function addMaskBounds(mask, bounds, skipUpdateTransform) {
+ const boundsToMask = tempBounds$4;
+ mask.measurable = true;
+ getGlobalBounds(mask, skipUpdateTransform, boundsToMask);
+ bounds.addBoundsMask(boundsToMask);
+ mask.measurable = false;
+ }
+
+ "use strict";
+ function addMaskLocalBounds(mask, bounds, localRoot) {
+ const boundsToMask = boundsPool.get();
+ mask.measurable = true;
+ const tempMatrix = matrixPool.get().identity();
+ const relativeMask = getMatrixRelativeToParent(mask, localRoot, tempMatrix);
+ getLocalBounds(mask, boundsToMask, relativeMask);
+ mask.measurable = false;
+ bounds.addBoundsMask(boundsToMask);
+ matrixPool.return(tempMatrix);
+ boundsPool.return(boundsToMask);
+ }
+ function getMatrixRelativeToParent(target, root, matrix) {
+ if (!target) {
+ warn("Mask bounds, renderable is not inside the root container");
+ return matrix;
+ }
+ if (target !== root) {
+ getMatrixRelativeToParent(target.parent, root, matrix);
+ target.updateLocalTransform();
+ matrix.append(target.localTransform);
+ }
+ return matrix;
+ }
+
+ "use strict";
+ class AlphaMask {
+ constructor(options) {
+ this.priority = 0;
+ this.inverse = false;
+ this.pipe = "alphaMask";
+ if (options == null ? void 0 : options.mask) {
+ this.init(options.mask);
+ }
+ }
+ init(mask) {
+ this.mask = mask;
+ this.renderMaskToTexture = !(mask instanceof Sprite);
+ this.mask.renderable = this.renderMaskToTexture;
+ this.mask.includeInBuild = !this.renderMaskToTexture;
+ this.mask.measurable = false;
+ }
+ reset() {
+ this.mask.measurable = true;
+ this.mask = null;
+ }
+ addBounds(bounds, skipUpdateTransform) {
+ if (!this.inverse) {
+ addMaskBounds(this.mask, bounds, skipUpdateTransform);
+ }
+ }
+ addLocalBounds(bounds, localRoot) {
+ addMaskLocalBounds(this.mask, bounds, localRoot);
+ }
+ containsPoint(point, hitTestFn) {
+ const mask = this.mask;
+ return hitTestFn(mask, point);
+ }
+ destroy() {
+ this.reset();
+ }
+ static test(mask) {
+ return mask instanceof Sprite;
+ }
+ }
+ AlphaMask.extension = ExtensionType.MaskEffect;
+
+ "use strict";
+ class ColorMask {
+ constructor(options) {
+ this.priority = 0;
+ this.pipe = "colorMask";
+ if (options == null ? void 0 : options.mask) {
+ this.init(options.mask);
+ }
+ }
+ init(mask) {
+ this.mask = mask;
+ }
+ destroy() {
+ }
+ static test(mask) {
+ return typeof mask === "number";
+ }
+ }
+ ColorMask.extension = ExtensionType.MaskEffect;
+
+ "use strict";
+ class StencilMask {
+ constructor(options) {
+ this.priority = 0;
+ this.pipe = "stencilMask";
+ if (options == null ? void 0 : options.mask) {
+ this.init(options.mask);
+ }
+ }
+ init(mask) {
+ this.mask = mask;
+ this.mask.includeInBuild = false;
+ this.mask.measurable = false;
+ }
+ reset() {
+ this.mask.measurable = true;
+ this.mask.includeInBuild = true;
+ this.mask = null;
+ }
+ addBounds(bounds, skipUpdateTransform) {
+ addMaskBounds(this.mask, bounds, skipUpdateTransform);
+ }
+ addLocalBounds(bounds, localRoot) {
+ addMaskLocalBounds(this.mask, bounds, localRoot);
+ }
+ containsPoint(point, hitTestFn) {
+ const mask = this.mask;
+ return hitTestFn(mask, point);
+ }
+ destroy() {
+ this.reset();
+ }
+ static test(mask) {
+ return mask instanceof Container;
+ }
+ }
+ StencilMask.extension = ExtensionType.MaskEffect;
+
+ "use strict";
+ class CanvasSource extends TextureSource {
+ constructor(options) {
+ if (!options.resource) {
+ options.resource = DOMAdapter.get().createCanvas();
+ }
+ if (!options.width) {
+ options.width = options.resource.width;
+ if (!options.autoDensity) {
+ options.width /= options.resolution;
+ }
+ }
+ if (!options.height) {
+ options.height = options.resource.height;
+ if (!options.autoDensity) {
+ options.height /= options.resolution;
+ }
+ }
+ super(options);
+ this.uploadMethodId = "image";
+ this.autoDensity = options.autoDensity;
+ this.resizeCanvas();
+ this.transparent = !!options.transparent;
+ }
+ resizeCanvas() {
+ if (this.autoDensity && "style" in this.resource) {
+ this.resource.style.width = `${this.width}px`;
+ this.resource.style.height = `${this.height}px`;
+ }
+ if (this.resource.width !== this.pixelWidth || this.resource.height !== this.pixelHeight) {
+ this.resource.width = this.pixelWidth;
+ this.resource.height = this.pixelHeight;
+ }
+ }
+ resize(width = this.width, height = this.height, resolution = this._resolution) {
+ const didResize = super.resize(width, height, resolution);
+ if (didResize) {
+ this.resizeCanvas();
+ }
+ return didResize;
+ }
+ static test(resource) {
+ return globalThis.HTMLCanvasElement && resource instanceof HTMLCanvasElement || globalThis.OffscreenCanvas && resource instanceof OffscreenCanvas;
+ }
+ /**
+ * Returns the 2D rendering context for the canvas.
+ * Caches the context after creating it.
+ * @returns The 2D rendering context of the canvas.
+ */
+ get context2D() {
+ return this._context2D || (this._context2D = this.resource.getContext("2d"));
+ }
+ }
+ CanvasSource.extension = ExtensionType.TextureSource;
+
+ "use strict";
+ class ImageSource extends TextureSource {
+ constructor(options) {
+ super(options);
+ this.uploadMethodId = "image";
+ this.autoGarbageCollect = true;
+ }
+ static test(resource) {
+ return globalThis.HTMLImageElement && resource instanceof HTMLImageElement || typeof ImageBitmap !== "undefined" && resource instanceof ImageBitmap || globalThis.VideoFrame && resource instanceof VideoFrame;
+ }
+ }
+ ImageSource.extension = ExtensionType.TextureSource;
+
+ "use strict";
+ let promise;
+ async function detectVideoAlphaMode() {
+ promise != null ? promise : promise = (async () => {
+ var _a;
+ const canvas = DOMAdapter.get().createCanvas(1, 1);
+ const gl = canvas.getContext("webgl");
+ if (!gl) {
+ return "premultiply-alpha-on-upload";
+ }
+ const video = await new Promise((resolve) => {
+ const video2 = document.createElement("video");
+ video2.onloadeddata = () => resolve(video2);
+ video2.onerror = () => resolve(null);
+ video2.autoplay = false;
+ video2.crossOrigin = "anonymous";
+ video2.preload = "auto";
+ video2.src = "data:video/webm;base64,GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQJChYECGFOAZwEAAAAAAAHTEU2bdLpNu4tTq4QVSalmU6yBoU27i1OrhBZUrmtTrIHGTbuMU6uEElTDZ1OsggEXTbuMU6uEHFO7a1OsggG97AEAAAAAAABZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVSalmoCrXsYMPQkBNgIRMYXZmV0GETGF2ZkSJiEBEAAAAAAAAFlSua8yuAQAAAAAAAEPXgQFzxYgAAAAAAAAAAZyBACK1nIN1bmSIgQCGhVZfVlA5g4EBI+ODhAJiWgDglLCBArqBApqBAlPAgQFVsIRVuYEBElTDZ9Vzc9JjwItjxYgAAAAAAAAAAWfInEWjh0VOQ09ERVJEh49MYXZjIGxpYnZweC12cDlnyKJFo4hEVVJBVElPTkSHlDAwOjAwOjAwLjA0MDAwMDAwMAAAH0O2dcfngQCgwqGggQAAAIJJg0IAABAAFgA4JBwYSgAAICAAEb///4r+AAB1oZ2mm+6BAaWWgkmDQgAAEAAWADgkHBhKAAAgIABIQBxTu2uRu4+zgQC3iveBAfGCAXHwgQM=";
+ video2.load();
+ });
+ if (!video) {
+ return "premultiply-alpha-on-upload";
+ }
+ const texture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ const framebuffer = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
+ gl.framebufferTexture2D(
+ gl.FRAMEBUFFER,
+ gl.COLOR_ATTACHMENT0,
+ gl.TEXTURE_2D,
+ texture,
+ 0
+ );
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
+ gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);
+ const pixel = new Uint8Array(4);
+ gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
+ gl.deleteFramebuffer(framebuffer);
+ gl.deleteTexture(texture);
+ (_a = gl.getExtension("WEBGL_lose_context")) == null ? void 0 : _a.loseContext();
+ return pixel[0] <= pixel[3] ? "premultiplied-alpha" : "premultiply-alpha-on-upload";
+ })();
+ return promise;
+ }
+
+ "use strict";
+ var __defProp$12 = Object.defineProperty;
+ var __defProps$p = Object.defineProperties;
+ var __getOwnPropDescs$p = Object.getOwnPropertyDescriptors;
+ var __getOwnPropSymbols$13 = Object.getOwnPropertySymbols;
+ var __hasOwnProp$13 = Object.prototype.hasOwnProperty;
+ var __propIsEnum$13 = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$12 = (obj, key, value) => key in obj ? __defProp$12(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$12 = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$13.call(b, prop))
+ __defNormalProp$12(a, prop, b[prop]);
+ if (__getOwnPropSymbols$13)
+ for (var prop of __getOwnPropSymbols$13(b)) {
+ if (__propIsEnum$13.call(b, prop))
+ __defNormalProp$12(a, prop, b[prop]);
+ }
+ return a;
+ };
+ var __spreadProps$p = (a, b) => __defProps$p(a, __getOwnPropDescs$p(b));
+ const _VideoSource = class _VideoSource extends TextureSource {
+ constructor(options) {
+ var _a;
+ super(options);
+ // Public
+ /** Whether or not the video is ready to play. */
+ this.isReady = false;
+ /** The upload method for this texture. */
+ this.uploadMethodId = "video";
+ options = __spreadValues$12(__spreadValues$12({}, _VideoSource.defaultOptions), options);
+ this._autoUpdate = true;
+ this._isConnectedToTicker = false;
+ this._updateFPS = options.updateFPS || 0;
+ this._msToNextUpdate = 0;
+ this.autoPlay = options.autoPlay !== false;
+ this.alphaMode = (_a = options.alphaMode) != null ? _a : "premultiply-alpha-on-upload";
+ this._videoFrameRequestCallback = this._videoFrameRequestCallback.bind(this);
+ this._videoFrameRequestCallbackHandle = null;
+ this._load = null;
+ this._resolve = null;
+ this._reject = null;
+ this._onCanPlay = this._onCanPlay.bind(this);
+ this._onCanPlayThrough = this._onCanPlayThrough.bind(this);
+ this._onError = this._onError.bind(this);
+ this._onPlayStart = this._onPlayStart.bind(this);
+ this._onPlayStop = this._onPlayStop.bind(this);
+ this._onSeeked = this._onSeeked.bind(this);
+ if (options.autoLoad !== false) {
+ void this.load();
+ }
+ }
+ /** Update the video frame if the source is not destroyed and meets certain conditions. */
+ updateFrame() {
+ if (this.destroyed) {
+ return;
+ }
+ if (this._updateFPS) {
+ const elapsedMS = Ticker.shared.elapsedMS * this.resource.playbackRate;
+ this._msToNextUpdate = Math.floor(this._msToNextUpdate - elapsedMS);
+ }
+ if (!this._updateFPS || this._msToNextUpdate <= 0) {
+ this._msToNextUpdate = this._updateFPS ? Math.floor(1e3 / this._updateFPS) : 0;
+ }
+ if (this.isValid) {
+ this.update();
+ }
+ }
+ /** Callback to update the video frame and potentially request the next frame update. */
+ _videoFrameRequestCallback() {
+ this.updateFrame();
+ if (this.destroyed) {
+ this._videoFrameRequestCallbackHandle = null;
+ } else {
+ this._videoFrameRequestCallbackHandle = this.resource.requestVideoFrameCallback(
+ this._videoFrameRequestCallback
+ );
+ }
+ }
+ /**
+ * Checks if the resource has valid dimensions.
+ * @returns {boolean} True if width and height are set, otherwise false.
+ */
+ get isValid() {
+ return !!this.resource.videoWidth && !!this.resource.videoHeight;
+ }
+ /**
+ * Start preloading the video resource.
+ * @returns {Promise} Handle the validate event
+ */
+ async load() {
+ if (this._load) {
+ return this._load;
+ }
+ const source = this.resource;
+ const options = this.options;
+ if ((source.readyState === source.HAVE_ENOUGH_DATA || source.readyState === source.HAVE_FUTURE_DATA) && source.width && source.height) {
+ source.complete = true;
+ }
+ source.addEventListener("play", this._onPlayStart);
+ source.addEventListener("pause", this._onPlayStop);
+ source.addEventListener("seeked", this._onSeeked);
+ if (!this._isSourceReady()) {
+ if (!options.preload) {
+ source.addEventListener("canplay", this._onCanPlay);
+ }
+ source.addEventListener("canplaythrough", this._onCanPlayThrough);
+ source.addEventListener("error", this._onError, true);
+ } else {
+ this._mediaReady();
+ }
+ this.alphaMode = await detectVideoAlphaMode();
+ this._load = new Promise((resolve, reject) => {
+ if (this.isValid) {
+ resolve(this);
+ } else {
+ this._resolve = resolve;
+ this._reject = reject;
+ if (options.preloadTimeoutMs !== void 0) {
+ this._preloadTimeout = setTimeout(() => {
+ this._onError(new ErrorEvent(`Preload exceeded timeout of ${options.preloadTimeoutMs}ms`));
+ });
+ }
+ source.load();
+ }
+ });
+ return this._load;
+ }
+ /**
+ * Handle video error events.
+ * @param event - The error event
+ */
+ _onError(event) {
+ this.resource.removeEventListener("error", this._onError, true);
+ this.emit("error", event);
+ if (this._reject) {
+ this._reject(event);
+ this._reject = null;
+ this._resolve = null;
+ }
+ }
+ /**
+ * Checks if the underlying source is playing.
+ * @returns True if playing.
+ */
+ _isSourcePlaying() {
+ const source = this.resource;
+ return !source.paused && !source.ended;
+ }
+ /**
+ * Checks if the underlying source is ready for playing.
+ * @returns True if ready.
+ */
+ _isSourceReady() {
+ const source = this.resource;
+ return source.readyState > 2;
+ }
+ /** Runs the update loop when the video is ready to play. */
+ _onPlayStart() {
+ if (!this.isValid) {
+ this._mediaReady();
+ }
+ this._configureAutoUpdate();
+ }
+ /** Stops the update loop when a pause event is triggered. */
+ _onPlayStop() {
+ this._configureAutoUpdate();
+ }
+ /** Handles behavior when the video completes seeking to the current playback position. */
+ _onSeeked() {
+ if (this._autoUpdate && !this._isSourcePlaying()) {
+ this._msToNextUpdate = 0;
+ this.updateFrame();
+ this._msToNextUpdate = 0;
+ }
+ }
+ _onCanPlay() {
+ const source = this.resource;
+ source.removeEventListener("canplay", this._onCanPlay);
+ this._mediaReady();
+ }
+ _onCanPlayThrough() {
+ const source = this.resource;
+ source.removeEventListener("canplaythrough", this._onCanPlay);
+ if (this._preloadTimeout) {
+ clearTimeout(this._preloadTimeout);
+ this._preloadTimeout = void 0;
+ }
+ this._mediaReady();
+ }
+ /** Fired when the video is loaded and ready to play. */
+ _mediaReady() {
+ const source = this.resource;
+ if (this.isValid) {
+ this.isReady = true;
+ this.resize(source.videoWidth, source.videoHeight);
+ }
+ this._msToNextUpdate = 0;
+ this.updateFrame();
+ this._msToNextUpdate = 0;
+ if (this._resolve) {
+ this._resolve(this);
+ this._resolve = null;
+ this._reject = null;
+ }
+ if (this._isSourcePlaying()) {
+ this._onPlayStart();
+ } else if (this.autoPlay) {
+ void this.resource.play();
+ }
+ }
+ /** Cleans up resources and event listeners associated with this texture. */
+ destroy() {
+ this._configureAutoUpdate();
+ const source = this.resource;
+ if (source) {
+ source.removeEventListener("play", this._onPlayStart);
+ source.removeEventListener("pause", this._onPlayStop);
+ source.removeEventListener("seeked", this._onSeeked);
+ source.removeEventListener("canplay", this._onCanPlay);
+ source.removeEventListener("canplaythrough", this._onCanPlayThrough);
+ source.removeEventListener("error", this._onError, true);
+ source.pause();
+ source.src = "";
+ source.load();
+ }
+ super.destroy();
+ }
+ /** Should the base texture automatically update itself, set to true by default. */
+ get autoUpdate() {
+ return this._autoUpdate;
+ }
+ set autoUpdate(value) {
+ if (value !== this._autoUpdate) {
+ this._autoUpdate = value;
+ this._configureAutoUpdate();
+ }
+ }
+ /**
+ * How many times a second to update the texture from the video.
+ * Leave at 0 to update at every render.
+ * A lower fps can help performance, as updating the texture at 60fps on a 30ps video may not be efficient.
+ */
+ get updateFPS() {
+ return this._updateFPS;
+ }
+ set updateFPS(value) {
+ if (value !== this._updateFPS) {
+ this._updateFPS = value;
+ this._configureAutoUpdate();
+ }
+ }
+ /**
+ * Configures the updating mechanism based on the current state and settings.
+ *
+ * This method decides between using the browser's native video frame callback or a custom ticker
+ * for updating the video frame. It ensures optimal performance and responsiveness
+ * based on the video's state, playback status, and the desired frames-per-second setting.
+ *
+ * - If `_autoUpdate` is enabled and the video source is playing:
+ * - It will prefer the native video frame callback if available and no specific FPS is set.
+ * - Otherwise, it will use a custom ticker for manual updates.
+ * - If `_autoUpdate` is disabled or the video isn't playing, any active update mechanisms are halted.
+ */
+ _configureAutoUpdate() {
+ if (this._autoUpdate && this._isSourcePlaying()) {
+ if (!this._updateFPS && this.resource.requestVideoFrameCallback) {
+ if (this._isConnectedToTicker) {
+ Ticker.shared.remove(this.updateFrame, this);
+ this._isConnectedToTicker = false;
+ this._msToNextUpdate = 0;
+ }
+ if (this._videoFrameRequestCallbackHandle === null) {
+ this._videoFrameRequestCallbackHandle = this.resource.requestVideoFrameCallback(
+ this._videoFrameRequestCallback
+ );
+ }
+ } else {
+ if (this._videoFrameRequestCallbackHandle !== null) {
+ this.resource.cancelVideoFrameCallback(this._videoFrameRequestCallbackHandle);
+ this._videoFrameRequestCallbackHandle = null;
+ }
+ if (!this._isConnectedToTicker) {
+ Ticker.shared.add(this.updateFrame, this);
+ this._isConnectedToTicker = true;
+ this._msToNextUpdate = 0;
+ }
+ }
+ } else {
+ if (this._videoFrameRequestCallbackHandle !== null) {
+ this.resource.cancelVideoFrameCallback(this._videoFrameRequestCallbackHandle);
+ this._videoFrameRequestCallbackHandle = null;
+ }
+ if (this._isConnectedToTicker) {
+ Ticker.shared.remove(this.updateFrame, this);
+ this._isConnectedToTicker = false;
+ this._msToNextUpdate = 0;
+ }
+ }
+ }
+ static test(resource) {
+ return globalThis.HTMLVideoElement && resource instanceof HTMLVideoElement;
+ }
+ };
+ _VideoSource.extension = ExtensionType.TextureSource;
+ /** The default options for video sources. */
+ _VideoSource.defaultOptions = __spreadProps$p(__spreadValues$12({}, TextureSource.defaultOptions), {
+ /** If true, the video will start loading immediately. */
+ autoLoad: true,
+ /** If true, the video will start playing as soon as it is loaded. */
+ autoPlay: true,
+ /** The number of times a second to update the texture from the video. Leave at 0 to update at every render. */
+ updateFPS: 0,
+ /** If true, the video will be loaded with the `crossorigin` attribute. */
+ crossorigin: true,
+ /** If true, the video will loop when it ends. */
+ loop: false,
+ /** If true, the video will be muted. */
+ muted: true,
+ /** If true, the video will play inline. */
+ playsinline: true,
+ /** If true, the video will be preloaded. */
+ preload: false
+ });
+ /**
+ * Map of video MIME types that can't be directly derived from file extensions.
+ * @readonly
+ */
+ _VideoSource.MIME_TYPES = {
+ ogv: "video/ogg",
+ mov: "video/quicktime",
+ m4v: "video/mp4"
+ };
+ let VideoSource = _VideoSource;
+
+ "use strict";
+ class CacheClass {
+ constructor() {
+ this._parsers = [];
+ this._cache = /* @__PURE__ */ new Map();
+ this._cacheMap = /* @__PURE__ */ new Map();
+ }
+ /** Clear all entries. */
+ reset() {
+ this._cacheMap.clear();
+ this._cache.clear();
+ }
+ /**
+ * Check if the key exists
+ * @param key - The key to check
+ */
+ has(key) {
+ return this._cache.has(key);
+ }
+ /**
+ * Fetch entry by key
+ * @param key - The key of the entry to get
+ */
+ get(key) {
+ const result = this._cache.get(key);
+ if (!result) {
+ warn(`[Assets] Asset id ${key} was not found in the Cache`);
+ }
+ return result;
+ }
+ /**
+ * Set a value by key or keys name
+ * @param key - The key or keys to set
+ * @param value - The value to store in the cache or from which cacheable assets will be derived.
+ */
+ set(key, value) {
+ const keys = convertToList(key);
+ let cacheableAssets;
+ for (let i = 0; i < this.parsers.length; i++) {
+ const parser = this.parsers[i];
+ if (parser.test(value)) {
+ cacheableAssets = parser.getCacheableAssets(keys, value);
+ break;
+ }
+ }
+ const cacheableMap = new Map(Object.entries(cacheableAssets || {}));
+ if (!cacheableAssets) {
+ keys.forEach((key2) => {
+ cacheableMap.set(key2, value);
+ });
+ }
+ const cacheKeys = [...cacheableMap.keys()];
+ const cachedAssets = {
+ cacheKeys,
+ keys
+ };
+ keys.forEach((key2) => {
+ this._cacheMap.set(key2, cachedAssets);
+ });
+ cacheKeys.forEach((key2) => {
+ const val = cacheableAssets ? cacheableAssets[key2] : value;
+ if (this._cache.has(key2) && this._cache.get(key2) !== val) {
+ warn("[Cache] already has key:", key2);
+ }
+ this._cache.set(key2, cacheableMap.get(key2));
+ });
+ }
+ /**
+ * Remove entry by key
+ *
+ * This function will also remove any associated alias from the cache also.
+ * @param key - The key of the entry to remove
+ */
+ remove(key) {
+ if (!this._cacheMap.has(key)) {
+ warn(`[Assets] Asset id ${key} was not found in the Cache`);
+ return;
+ }
+ const cacheMap = this._cacheMap.get(key);
+ const cacheKeys = cacheMap.cacheKeys;
+ cacheKeys.forEach((key2) => {
+ this._cache.delete(key2);
+ });
+ cacheMap.keys.forEach((key2) => {
+ this._cacheMap.delete(key2);
+ });
+ }
+ /**
+ * All loader parsers registered
+ * @advanced
+ */
+ get parsers() {
+ return this._parsers;
+ }
+ }
+ const Cache = new CacheClass();
+
+ "use strict";
+ const sources = [];
+ extensions.handleByList(ExtensionType.TextureSource, sources);
+ function autoDetectSource(options = {}) {
+ return textureSourceFrom(options);
+ }
+ function textureSourceFrom(options = {}) {
+ const hasResource = options && options.resource;
+ const res = hasResource ? options.resource : options;
+ const opts = hasResource ? options : { resource: options };
+ for (let i = 0; i < sources.length; i++) {
+ const Source = sources[i];
+ if (Source.test(res)) {
+ return new Source(opts);
+ }
+ }
+ throw new Error(`Could not find a source type for resource: ${opts.resource}`);
+ }
+ function resourceToTexture(options = {}, skipCache = false) {
+ const hasResource = options && options.resource;
+ const resource = hasResource ? options.resource : options;
+ const opts = hasResource ? options : { resource: options };
+ if (!skipCache && Cache.has(resource)) {
+ return Cache.get(resource);
+ }
+ const texture = new Texture({ source: textureSourceFrom(opts) });
+ texture.on("destroy", () => {
+ if (Cache.has(resource)) {
+ Cache.remove(resource);
+ }
+ });
+ if (!skipCache) {
+ Cache.set(resource, texture);
+ }
+ return texture;
+ }
+ function textureFrom(id, skipCache = false) {
+ if (typeof id === "string") {
+ return Cache.get(id);
+ } else if (id instanceof TextureSource) {
+ return new Texture({ source: id });
+ }
+ return resourceToTexture(id, skipCache);
+ }
+ Texture.from = textureFrom;
+ TextureSource.from = textureSourceFrom;
+
+ "use strict";
+ extensions.add(AlphaMask, ColorMask, StencilMask, VideoSource, ImageSource, CanvasSource, BufferImageSource);
+
+ "use strict";
+ class BindGroup {
+ /**
+ * Create a new instance eof the Bind Group.
+ * @param resources - The resources that are bound together for use by a shader.
+ */
+ constructor(resources) {
+ /** The resources that are bound together for use by a shader. */
+ this.resources = /* @__PURE__ */ Object.create(null);
+ this._dirty = true;
+ let index = 0;
+ for (const i in resources) {
+ const resource = resources[i];
+ this.setResource(resource, index++);
+ }
+ this._updateKey();
+ }
+ /**
+ * Updates the key if its flagged as dirty. This is used internally to
+ * match this bind group to a WebGPU BindGroup.
+ * @internal
+ */
+ _updateKey() {
+ if (!this._dirty)
+ return;
+ this._dirty = false;
+ const keyParts = [];
+ let index = 0;
+ for (const i in this.resources) {
+ keyParts[index++] = this.resources[i]._resourceId;
+ }
+ this._key = keyParts.join("|");
+ }
+ /**
+ * Set a resource at a given index. this function will
+ * ensure that listeners will be removed from the current resource
+ * and added to the new resource.
+ * @param resource - The resource to set.
+ * @param index - The index to set the resource at.
+ */
+ setResource(resource, index) {
+ var _a, _b;
+ const currentResource = this.resources[index];
+ if (resource === currentResource)
+ return;
+ if (currentResource) {
+ (_a = resource.off) == null ? void 0 : _a.call(resource, "change", this.onResourceChange, this);
+ }
+ (_b = resource.on) == null ? void 0 : _b.call(resource, "change", this.onResourceChange, this);
+ this.resources[index] = resource;
+ this._dirty = true;
+ }
+ /**
+ * Returns the resource at the current specified index.
+ * @param index - The index of the resource to get.
+ * @returns - The resource at the specified index.
+ */
+ getResource(index) {
+ return this.resources[index];
+ }
+ /**
+ * Used internally to 'touch' each resource, to ensure that the GC
+ * knows that all resources in this bind group are still being used.
+ * @param tick - The current tick.
+ * @internal
+ */
+ _touch(tick) {
+ const resources = this.resources;
+ for (const i in resources) {
+ resources[i]._touched = tick;
+ }
+ }
+ /** Destroys this bind group and removes all listeners. */
+ destroy() {
+ var _a;
+ const resources = this.resources;
+ for (const i in resources) {
+ const resource = resources[i];
+ (_a = resource.off) == null ? void 0 : _a.call(resource, "change", this.onResourceChange, this);
+ }
+ this.resources = null;
+ }
+ onResourceChange(resource) {
+ this._dirty = true;
+ if (resource.destroyed) {
+ const resources = this.resources;
+ for (const i in resources) {
+ if (resources[i] === resource) {
+ resources[i] = null;
+ }
+ }
+ } else {
+ this._updateKey();
+ }
+ }
+ }
+
+ "use strict";
+ const cachedGroups = {};
+ function getTextureBatchBindGroup(textures, size, maxTextures) {
+ let uid = 2166136261;
+ for (let i = 0; i < size; i++) {
+ uid ^= textures[i].uid;
+ uid = Math.imul(uid, 16777619);
+ uid >>>= 0;
+ }
+ return cachedGroups[uid] || generateTextureBatchBindGroup(textures, size, uid, maxTextures);
+ }
+ function generateTextureBatchBindGroup(textures, size, key, maxTextures) {
+ const bindGroupResources = {};
+ let bindIndex = 0;
+ for (let i = 0; i < maxTextures; i++) {
+ const texture = i < size ? textures[i] : Texture.EMPTY.source;
+ bindGroupResources[bindIndex++] = texture.source;
+ bindGroupResources[bindIndex++] = texture.style;
+ }
+ const bindGroup = new BindGroup(bindGroupResources);
+ cachedGroups[key] = bindGroup;
+ return bindGroup;
+ }
+
+ "use strict";
+ class ViewableBuffer {
+ constructor(sizeOrBuffer) {
+ if (typeof sizeOrBuffer === "number") {
+ this.rawBinaryData = new ArrayBuffer(sizeOrBuffer);
+ } else if (sizeOrBuffer instanceof Uint8Array) {
+ this.rawBinaryData = sizeOrBuffer.buffer;
+ } else {
+ this.rawBinaryData = sizeOrBuffer;
+ }
+ this.uint32View = new Uint32Array(this.rawBinaryData);
+ this.float32View = new Float32Array(this.rawBinaryData);
+ this.size = this.rawBinaryData.byteLength;
+ }
+ /** View on the raw binary data as a `Int8Array`. */
+ get int8View() {
+ if (!this._int8View) {
+ this._int8View = new Int8Array(this.rawBinaryData);
+ }
+ return this._int8View;
+ }
+ /** View on the raw binary data as a `Uint8Array`. */
+ get uint8View() {
+ if (!this._uint8View) {
+ this._uint8View = new Uint8Array(this.rawBinaryData);
+ }
+ return this._uint8View;
+ }
+ /** View on the raw binary data as a `Int16Array`. */
+ get int16View() {
+ if (!this._int16View) {
+ this._int16View = new Int16Array(this.rawBinaryData);
+ }
+ return this._int16View;
+ }
+ /** View on the raw binary data as a `Int32Array`. */
+ get int32View() {
+ if (!this._int32View) {
+ this._int32View = new Int32Array(this.rawBinaryData);
+ }
+ return this._int32View;
+ }
+ /** View on the raw binary data as a `Float64Array`. */
+ get float64View() {
+ if (!this._float64Array) {
+ this._float64Array = new Float64Array(this.rawBinaryData);
+ }
+ return this._float64Array;
+ }
+ /** View on the raw binary data as a `BigUint64Array`. */
+ get bigUint64View() {
+ if (!this._bigUint64Array) {
+ this._bigUint64Array = new BigUint64Array(this.rawBinaryData);
+ }
+ return this._bigUint64Array;
+ }
+ /**
+ * Returns the view of the given type.
+ * @param type - One of `int8`, `uint8`, `int16`,
+ * `uint16`, `int32`, `uint32`, and `float32`.
+ * @returns - typed array of given type
+ */
+ view(type) {
+ return this[`${type}View`];
+ }
+ /** Destroys all buffer references. Do not use after calling this. */
+ destroy() {
+ this.rawBinaryData = null;
+ this._int8View = null;
+ this._uint8View = null;
+ this._int16View = null;
+ this.uint16View = null;
+ this._int32View = null;
+ this.uint32View = null;
+ this.float32View = null;
+ }
+ /**
+ * Returns the size of the given type in bytes.
+ * @param type - One of `int8`, `uint8`, `int16`,
+ * `uint16`, `int32`, `uint32`, and `float32`.
+ * @returns - size of the type in bytes
+ */
+ static sizeOf(type) {
+ switch (type) {
+ case "int8":
+ case "uint8":
+ return 1;
+ case "int16":
+ case "uint16":
+ return 2;
+ case "int32":
+ case "uint32":
+ case "float32":
+ return 4;
+ default:
+ throw new Error(`${type} isn't a valid view type`);
+ }
+ }
+ }
+
+ "use strict";
+ function fastCopy(sourceBuffer, destinationBuffer) {
+ const lengthDouble = sourceBuffer.byteLength / 8 | 0;
+ const sourceFloat64View = new Float64Array(sourceBuffer, 0, lengthDouble);
+ const destinationFloat64View = new Float64Array(destinationBuffer, 0, lengthDouble);
+ destinationFloat64View.set(sourceFloat64View);
+ const remainingBytes = sourceBuffer.byteLength - lengthDouble * 8;
+ if (remainingBytes > 0) {
+ const sourceUint8View = new Uint8Array(sourceBuffer, lengthDouble * 8, remainingBytes);
+ const destinationUint8View = new Uint8Array(destinationBuffer, lengthDouble * 8, remainingBytes);
+ destinationUint8View.set(sourceUint8View);
+ }
+ }
+
+ "use strict";
+ const BLEND_TO_NPM = {
+ normal: "normal-npm",
+ add: "add-npm",
+ screen: "screen-npm"
+ };
+ var STENCIL_MODES = /* @__PURE__ */ ((STENCIL_MODES2) => {
+ STENCIL_MODES2[STENCIL_MODES2["DISABLED"] = 0] = "DISABLED";
+ STENCIL_MODES2[STENCIL_MODES2["RENDERING_MASK_ADD"] = 1] = "RENDERING_MASK_ADD";
+ STENCIL_MODES2[STENCIL_MODES2["MASK_ACTIVE"] = 2] = "MASK_ACTIVE";
+ STENCIL_MODES2[STENCIL_MODES2["INVERSE_MASK_ACTIVE"] = 3] = "INVERSE_MASK_ACTIVE";
+ STENCIL_MODES2[STENCIL_MODES2["RENDERING_MASK_REMOVE"] = 4] = "RENDERING_MASK_REMOVE";
+ STENCIL_MODES2[STENCIL_MODES2["NONE"] = 5] = "NONE";
+ return STENCIL_MODES2;
+ })(STENCIL_MODES || {});
+
+ "use strict";
+ function getAdjustedBlendModeBlend(blendMode, textureSource) {
+ if (textureSource.alphaMode === "no-premultiply-alpha") {
+ return BLEND_TO_NPM[blendMode] || blendMode;
+ }
+ return blendMode;
+ }
+
+ "use strict";
+ let context;
+ function getTestContext() {
+ if (!context || (context == null ? void 0 : context.isContextLost())) {
+ const canvas = DOMAdapter.get().createCanvas();
+ context = canvas.getContext("webgl", {});
+ }
+ return context;
+ }
+
+ "use strict";
+ const fragTemplate$1 = [
+ "precision mediump float;",
+ "void main(void){",
+ "float test = 0.1;",
+ "%forloop%",
+ "gl_FragColor = vec4(0.0);",
+ "}"
+ ].join("\n");
+ function generateIfTestSrc(maxIfs) {
+ let src = "";
+ for (let i = 0; i < maxIfs; ++i) {
+ if (i > 0) {
+ src += "\nelse ";
+ }
+ if (i < maxIfs - 1) {
+ src += `if(test == ${i}.0){}`;
+ }
+ }
+ return src;
+ }
+ function checkMaxIfStatementsInShader(maxIfs, gl) {
+ if (maxIfs === 0) {
+ throw new Error("Invalid value of `0` passed to `checkMaxIfStatementsInShader`");
+ }
+ const shader = gl.createShader(gl.FRAGMENT_SHADER);
+ try {
+ while (true) {
+ const fragmentSrc = fragTemplate$1.replace(/%forloop%/gi, generateIfTestSrc(maxIfs));
+ gl.shaderSource(shader, fragmentSrc);
+ gl.compileShader(shader);
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+ maxIfs = maxIfs / 2 | 0;
+ } else {
+ break;
+ }
+ }
+ } finally {
+ gl.deleteShader(shader);
+ }
+ return maxIfs;
+ }
+
+ "use strict";
+ let maxTexturesPerBatchCache = null;
+ function getMaxTexturesPerBatch() {
+ var _a;
+ if (maxTexturesPerBatchCache)
+ return maxTexturesPerBatchCache;
+ const gl = getTestContext();
+ maxTexturesPerBatchCache = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
+ maxTexturesPerBatchCache = checkMaxIfStatementsInShader(
+ maxTexturesPerBatchCache,
+ gl
+ );
+ (_a = gl.getExtension("WEBGL_lose_context")) == null ? void 0 : _a.loseContext();
+ return maxTexturesPerBatchCache;
+ }
+
+ "use strict";
+ class BatchTextureArray {
+ constructor() {
+ /** Respective locations for textures. */
+ this.ids = /* @__PURE__ */ Object.create(null);
+ this.textures = [];
+ this.count = 0;
+ }
+ /** Clear the textures and their locations. */
+ clear() {
+ for (let i = 0; i < this.count; i++) {
+ const t = this.textures[i];
+ this.textures[i] = null;
+ this.ids[t.uid] = null;
+ }
+ this.count = 0;
+ }
+ }
+
+ "use strict";
+ var __defProp$11 = Object.defineProperty;
+ var __getOwnPropSymbols$12 = Object.getOwnPropertySymbols;
+ var __hasOwnProp$12 = Object.prototype.hasOwnProperty;
+ var __propIsEnum$12 = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$11 = (obj, key, value) => key in obj ? __defProp$11(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$11 = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$12.call(b, prop))
+ __defNormalProp$11(a, prop, b[prop]);
+ if (__getOwnPropSymbols$12)
+ for (var prop of __getOwnPropSymbols$12(b)) {
+ if (__propIsEnum$12.call(b, prop))
+ __defNormalProp$11(a, prop, b[prop]);
+ }
+ return a;
+ };
+ class Batch {
+ constructor() {
+ this.renderPipeId = "batch";
+ this.action = "startBatch";
+ // TODO - eventually this could be useful for flagging batches as dirty and then only rebuilding those ones
+ // public elementStart = 0;
+ // public elementSize = 0;
+ // for drawing..
+ this.start = 0;
+ this.size = 0;
+ this.textures = new BatchTextureArray();
+ this.blendMode = "normal";
+ this.topology = "triangle-strip";
+ this.canBundle = true;
+ }
+ destroy() {
+ this.textures = null;
+ this.gpuBindGroup = null;
+ this.bindGroup = null;
+ this.batcher = null;
+ }
+ }
+ const batchPool = [];
+ let batchPoolIndex = 0;
+ GlobalResourceRegistry.register({
+ clear: () => {
+ if (batchPool.length > 0) {
+ for (const item of batchPool) {
+ if (item)
+ item.destroy();
+ }
+ }
+ batchPool.length = 0;
+ batchPoolIndex = 0;
+ }
+ });
+ function getBatchFromPool() {
+ return batchPoolIndex > 0 ? batchPool[--batchPoolIndex] : new Batch();
+ }
+ function returnBatchToPool(batch) {
+ batchPool[batchPoolIndex++] = batch;
+ }
+ let BATCH_TICK = 0;
+ const _Batcher = class _Batcher {
+ constructor(options) {
+ /** unique id for this batcher */
+ this.uid = uid$1("batcher");
+ /** Indicates whether the batch data has been modified and needs updating. */
+ this.dirty = true;
+ /** The current index of the batch being processed. */
+ this.batchIndex = 0;
+ /** An array of all batches created during the current rendering process. */
+ this.batches = [];
+ this._elements = [];
+ options = __spreadValues$11(__spreadValues$11({}, _Batcher.defaultOptions), options);
+ if (!options.maxTextures) {
+ deprecation("v8.8.0", "maxTextures is a required option for Batcher now, please pass it in the options");
+ options.maxTextures = getMaxTexturesPerBatch();
+ }
+ const { maxTextures, attributesInitialSize, indicesInitialSize } = options;
+ this.attributeBuffer = new ViewableBuffer(attributesInitialSize * 4);
+ this.indexBuffer = new Uint16Array(indicesInitialSize);
+ this.maxTextures = maxTextures;
+ }
+ begin() {
+ this.elementSize = 0;
+ this.elementStart = 0;
+ this.indexSize = 0;
+ this.attributeSize = 0;
+ for (let i = 0; i < this.batchIndex; i++) {
+ returnBatchToPool(this.batches[i]);
+ }
+ this.batchIndex = 0;
+ this._batchIndexStart = 0;
+ this._batchIndexSize = 0;
+ this.dirty = true;
+ }
+ add(batchableObject) {
+ this._elements[this.elementSize++] = batchableObject;
+ batchableObject._indexStart = this.indexSize;
+ batchableObject._attributeStart = this.attributeSize;
+ batchableObject._batcher = this;
+ this.indexSize += batchableObject.indexSize;
+ this.attributeSize += batchableObject.attributeSize * this.vertexSize;
+ }
+ checkAndUpdateTexture(batchableObject, texture) {
+ const textureId = batchableObject._batch.textures.ids[texture._source.uid];
+ if (!textureId && textureId !== 0)
+ return false;
+ batchableObject._textureId = textureId;
+ batchableObject.texture = texture;
+ return true;
+ }
+ updateElement(batchableObject) {
+ this.dirty = true;
+ const attributeBuffer = this.attributeBuffer;
+ if (batchableObject.packAsQuad) {
+ this.packQuadAttributes(
+ batchableObject,
+ attributeBuffer.float32View,
+ attributeBuffer.uint32View,
+ batchableObject._attributeStart,
+ batchableObject._textureId
+ );
+ } else {
+ this.packAttributes(
+ batchableObject,
+ attributeBuffer.float32View,
+ attributeBuffer.uint32View,
+ batchableObject._attributeStart,
+ batchableObject._textureId
+ );
+ }
+ }
+ /**
+ * breaks the batcher. This happens when a batch gets too big,
+ * or we need to switch to a different type of rendering (a filter for example)
+ * @param instructionSet
+ */
+ break(instructionSet) {
+ const elements = this._elements;
+ if (!elements[this.elementStart])
+ return;
+ let batch = getBatchFromPool();
+ let textureBatch = batch.textures;
+ textureBatch.clear();
+ const firstElement = elements[this.elementStart];
+ let blendMode = getAdjustedBlendModeBlend(firstElement.blendMode, firstElement.texture._source);
+ let topology = firstElement.topology;
+ if (this.attributeSize * 4 > this.attributeBuffer.size) {
+ this._resizeAttributeBuffer(this.attributeSize * 4);
+ }
+ if (this.indexSize > this.indexBuffer.length) {
+ this._resizeIndexBuffer(this.indexSize);
+ }
+ const f32 = this.attributeBuffer.float32View;
+ const u32 = this.attributeBuffer.uint32View;
+ const indexBuffer = this.indexBuffer;
+ let size = this._batchIndexSize;
+ let start = this._batchIndexStart;
+ let action = "startBatch";
+ const maxTextures = this.maxTextures;
+ for (let i = this.elementStart; i < this.elementSize; ++i) {
+ const element = elements[i];
+ elements[i] = null;
+ const texture = element.texture;
+ const source = texture._source;
+ const adjustedBlendMode = getAdjustedBlendModeBlend(element.blendMode, source);
+ const breakRequired = blendMode !== adjustedBlendMode || topology !== element.topology;
+ if (source._batchTick === BATCH_TICK && !breakRequired) {
+ element._textureId = source._textureBindLocation;
+ size += element.indexSize;
+ if (element.packAsQuad) {
+ this.packQuadAttributes(
+ element,
+ f32,
+ u32,
+ element._attributeStart,
+ element._textureId
+ );
+ this.packQuadIndex(
+ indexBuffer,
+ element._indexStart,
+ element._attributeStart / this.vertexSize
+ );
+ } else {
+ this.packAttributes(
+ element,
+ f32,
+ u32,
+ element._attributeStart,
+ element._textureId
+ );
+ this.packIndex(
+ element,
+ indexBuffer,
+ element._indexStart,
+ element._attributeStart / this.vertexSize
+ );
+ }
+ element._batch = batch;
+ continue;
+ }
+ source._batchTick = BATCH_TICK;
+ if (textureBatch.count >= maxTextures || breakRequired) {
+ this._finishBatch(
+ batch,
+ start,
+ size - start,
+ textureBatch,
+ blendMode,
+ topology,
+ instructionSet,
+ action
+ );
+ action = "renderBatch";
+ start = size;
+ blendMode = adjustedBlendMode;
+ topology = element.topology;
+ batch = getBatchFromPool();
+ textureBatch = batch.textures;
+ textureBatch.clear();
+ ++BATCH_TICK;
+ }
+ element._textureId = source._textureBindLocation = textureBatch.count;
+ textureBatch.ids[source.uid] = textureBatch.count;
+ textureBatch.textures[textureBatch.count++] = source;
+ element._batch = batch;
+ size += element.indexSize;
+ if (element.packAsQuad) {
+ this.packQuadAttributes(
+ element,
+ f32,
+ u32,
+ element._attributeStart,
+ element._textureId
+ );
+ this.packQuadIndex(
+ indexBuffer,
+ element._indexStart,
+ element._attributeStart / this.vertexSize
+ );
+ } else {
+ this.packAttributes(
+ element,
+ f32,
+ u32,
+ element._attributeStart,
+ element._textureId
+ );
+ this.packIndex(
+ element,
+ indexBuffer,
+ element._indexStart,
+ element._attributeStart / this.vertexSize
+ );
+ }
+ }
+ if (textureBatch.count > 0) {
+ this._finishBatch(
+ batch,
+ start,
+ size - start,
+ textureBatch,
+ blendMode,
+ topology,
+ instructionSet,
+ action
+ );
+ start = size;
+ ++BATCH_TICK;
+ }
+ this.elementStart = this.elementSize;
+ this._batchIndexStart = start;
+ this._batchIndexSize = size;
+ }
+ _finishBatch(batch, indexStart, indexSize, textureBatch, blendMode, topology, instructionSet, action) {
+ batch.gpuBindGroup = null;
+ batch.bindGroup = null;
+ batch.action = action;
+ batch.batcher = this;
+ batch.textures = textureBatch;
+ batch.blendMode = blendMode;
+ batch.topology = topology;
+ batch.start = indexStart;
+ batch.size = indexSize;
+ ++BATCH_TICK;
+ this.batches[this.batchIndex++] = batch;
+ instructionSet.add(batch);
+ }
+ finish(instructionSet) {
+ this.break(instructionSet);
+ }
+ /**
+ * Resizes the attribute buffer to the given size (1 = 1 float32)
+ * @param size - the size in vertices to ensure (not bytes!)
+ */
+ ensureAttributeBuffer(size) {
+ if (size * 4 <= this.attributeBuffer.size)
+ return;
+ this._resizeAttributeBuffer(size * 4);
+ }
+ /**
+ * Resizes the index buffer to the given size (1 = 1 float32)
+ * @param size - the size in vertices to ensure (not bytes!)
+ */
+ ensureIndexBuffer(size) {
+ if (size <= this.indexBuffer.length)
+ return;
+ this._resizeIndexBuffer(size);
+ }
+ _resizeAttributeBuffer(size) {
+ const newSize = Math.max(size, this.attributeBuffer.size * 2);
+ const newArrayBuffer = new ViewableBuffer(newSize);
+ fastCopy(this.attributeBuffer.rawBinaryData, newArrayBuffer.rawBinaryData);
+ this.attributeBuffer = newArrayBuffer;
+ }
+ _resizeIndexBuffer(size) {
+ const indexBuffer = this.indexBuffer;
+ let newSize = Math.max(size, indexBuffer.length * 1.5);
+ newSize += newSize % 2;
+ const newIndexBuffer = newSize > 65535 ? new Uint32Array(newSize) : new Uint16Array(newSize);
+ if (newIndexBuffer.BYTES_PER_ELEMENT !== indexBuffer.BYTES_PER_ELEMENT) {
+ for (let i = 0; i < indexBuffer.length; i++) {
+ newIndexBuffer[i] = indexBuffer[i];
+ }
+ } else {
+ fastCopy(indexBuffer.buffer, newIndexBuffer.buffer);
+ }
+ this.indexBuffer = newIndexBuffer;
+ }
+ packQuadIndex(indexBuffer, index, indicesOffset) {
+ indexBuffer[index] = indicesOffset + 0;
+ indexBuffer[index + 1] = indicesOffset + 1;
+ indexBuffer[index + 2] = indicesOffset + 2;
+ indexBuffer[index + 3] = indicesOffset + 0;
+ indexBuffer[index + 4] = indicesOffset + 2;
+ indexBuffer[index + 5] = indicesOffset + 3;
+ }
+ packIndex(element, indexBuffer, index, indicesOffset) {
+ const indices = element.indices;
+ const size = element.indexSize;
+ const indexOffset = element.indexOffset;
+ const attributeOffset = element.attributeOffset;
+ for (let i = 0; i < size; i++) {
+ indexBuffer[index++] = indicesOffset + indices[i + indexOffset] - attributeOffset;
+ }
+ }
+ destroy() {
+ if (this.batches === null)
+ return;
+ for (let i = 0; i < this.batches.length; i++) {
+ returnBatchToPool(this.batches[i]);
+ }
+ this.batches = null;
+ for (let i = 0; i < this._elements.length; i++) {
+ if (this._elements[i])
+ this._elements[i]._batch = null;
+ }
+ this._elements = null;
+ this.indexBuffer = null;
+ this.attributeBuffer.destroy();
+ this.attributeBuffer = null;
+ }
+ };
+ _Batcher.defaultOptions = {
+ maxTextures: null,
+ attributesInitialSize: 4,
+ indicesInitialSize: 6
+ };
+ let Batcher = _Batcher;
+
+ "use strict";
+ var BufferUsage = /* @__PURE__ */ ((BufferUsage2) => {
+ BufferUsage2[BufferUsage2["MAP_READ"] = 1] = "MAP_READ";
+ BufferUsage2[BufferUsage2["MAP_WRITE"] = 2] = "MAP_WRITE";
+ BufferUsage2[BufferUsage2["COPY_SRC"] = 4] = "COPY_SRC";
+ BufferUsage2[BufferUsage2["COPY_DST"] = 8] = "COPY_DST";
+ BufferUsage2[BufferUsage2["INDEX"] = 16] = "INDEX";
+ BufferUsage2[BufferUsage2["VERTEX"] = 32] = "VERTEX";
+ BufferUsage2[BufferUsage2["UNIFORM"] = 64] = "UNIFORM";
+ BufferUsage2[BufferUsage2["STORAGE"] = 128] = "STORAGE";
+ BufferUsage2[BufferUsage2["INDIRECT"] = 256] = "INDIRECT";
+ BufferUsage2[BufferUsage2["QUERY_RESOLVE"] = 512] = "QUERY_RESOLVE";
+ BufferUsage2[BufferUsage2["STATIC"] = 1024] = "STATIC";
+ return BufferUsage2;
+ })(BufferUsage || {});
+
+ "use strict";
+ class Buffer extends EventEmitter {
+ /**
+ * Creates a new Buffer with the given options
+ * @param options - the options for the buffer
+ */
+ constructor(options) {
+ let { data, size } = options;
+ const { usage, label, shrinkToFit } = options;
+ super();
+ /**
+ * emits when the underlying buffer has changed shape (i.e. resized)
+ * letting the renderer know that it needs to discard the old buffer on the GPU and create a new one
+ * @event change
+ */
+ /**
+ * emits when the underlying buffer data has been updated. letting the renderer know
+ * that it needs to update the buffer on the GPU
+ * @event update
+ */
+ /**
+ * emits when the buffer is destroyed. letting the renderer know that it needs to destroy the buffer on the GPU
+ * @event destroy
+ */
+ /** a unique id for this uniform group used through the renderer */
+ this.uid = uid$1("buffer");
+ /**
+ * a resource type, used to identify how to handle it when its in a bind group / shader resource
+ * @internal
+ */
+ this._resourceType = "buffer";
+ /**
+ * the resource id used internally by the renderer to build bind group keys
+ * @internal
+ */
+ this._resourceId = uid$1("resource");
+ /**
+ * used internally to know if a uniform group was used in the last render pass
+ * @internal
+ */
+ this._touched = 0;
+ /** @internal */
+ this._updateID = 1;
+ this._dataInt32 = null;
+ /**
+ * should the GPU buffer be shrunk when the data becomes smaller?
+ * changing this will cause the buffer to be destroyed and a new one created on the GPU
+ * this can be expensive, especially if the buffer is already big enough!
+ * setting this to false will prevent the buffer from being shrunk. This will yield better performance
+ * if you are constantly setting data that is changing size often.
+ * @default true
+ */
+ this.shrinkToFit = true;
+ /**
+ * Has the buffer been destroyed?
+ * @readonly
+ */
+ this.destroyed = false;
+ if (data instanceof Array) {
+ data = new Float32Array(data);
+ }
+ this._data = data;
+ size != null ? size : size = data == null ? void 0 : data.byteLength;
+ const mappedAtCreation = !!data;
+ this.descriptor = {
+ size,
+ usage,
+ mappedAtCreation,
+ label
+ };
+ this.shrinkToFit = shrinkToFit != null ? shrinkToFit : true;
+ }
+ /** the data in the buffer */
+ get data() {
+ return this._data;
+ }
+ set data(value) {
+ this.setDataWithSize(value, value.length, true);
+ }
+ get dataInt32() {
+ if (!this._dataInt32) {
+ this._dataInt32 = new Int32Array(this.data.buffer);
+ }
+ return this._dataInt32;
+ }
+ /** whether the buffer is static or not */
+ get static() {
+ return !!(this.descriptor.usage & BufferUsage.STATIC);
+ }
+ set static(value) {
+ if (value) {
+ this.descriptor.usage |= BufferUsage.STATIC;
+ } else {
+ this.descriptor.usage &= ~BufferUsage.STATIC;
+ }
+ }
+ /**
+ * Sets the data in the buffer to the given value. This will immediately update the buffer on the GPU.
+ * If you only want to update a subset of the buffer, you can pass in the size of the data.
+ * @param value - the data to set
+ * @param size - the size of the data in bytes
+ * @param syncGPU - should the buffer be updated on the GPU immediately?
+ */
+ setDataWithSize(value, size, syncGPU) {
+ this._updateID++;
+ this._updateSize = size * value.BYTES_PER_ELEMENT;
+ if (this._data === value) {
+ if (syncGPU)
+ this.emit("update", this);
+ return;
+ }
+ const oldData = this._data;
+ this._data = value;
+ this._dataInt32 = null;
+ if (!oldData || oldData.length !== value.length) {
+ if (!this.shrinkToFit && oldData && value.byteLength < oldData.byteLength) {
+ if (syncGPU)
+ this.emit("update", this);
+ } else {
+ this.descriptor.size = value.byteLength;
+ this._resourceId = uid$1("resource");
+ this.emit("change", this);
+ }
+ return;
+ }
+ if (syncGPU)
+ this.emit("update", this);
+ }
+ /**
+ * updates the buffer on the GPU to reflect the data in the buffer.
+ * By default it will update the entire buffer. If you only want to update a subset of the buffer,
+ * you can pass in the size of the buffer to update.
+ * @param sizeInBytes - the new size of the buffer in bytes
+ */
+ update(sizeInBytes) {
+ this._updateSize = sizeInBytes != null ? sizeInBytes : this._updateSize;
+ this._updateID++;
+ this.emit("update", this);
+ }
+ /** Destroys the buffer */
+ destroy() {
+ this.destroyed = true;
+ this.emit("destroy", this);
+ this.emit("change", this);
+ this._data = null;
+ this.descriptor = null;
+ this.removeAllListeners();
+ }
+ }
+
+ "use strict";
+ function ensureIsBuffer(buffer, index) {
+ if (!(buffer instanceof Buffer)) {
+ let usage = index ? BufferUsage.INDEX : BufferUsage.VERTEX;
+ if (buffer instanceof Array) {
+ if (index) {
+ buffer = new Uint32Array(buffer);
+ usage = BufferUsage.INDEX | BufferUsage.COPY_DST;
+ } else {
+ buffer = new Float32Array(buffer);
+ usage = BufferUsage.VERTEX | BufferUsage.COPY_DST;
+ }
+ }
+ buffer = new Buffer({
+ data: buffer,
+ label: index ? "index-mesh-buffer" : "vertex-mesh-buffer",
+ usage
+ });
+ }
+ return buffer;
+ }
+
+ "use strict";
+ function getGeometryBounds(geometry, attributeId, bounds) {
+ const attribute = geometry.getAttribute(attributeId);
+ if (!attribute) {
+ bounds.minX = 0;
+ bounds.minY = 0;
+ bounds.maxX = 0;
+ bounds.maxY = 0;
+ return bounds;
+ }
+ const data = attribute.buffer.data;
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+ const byteSize = data.BYTES_PER_ELEMENT;
+ const offset = (attribute.offset || 0) / byteSize;
+ const stride = (attribute.stride || 2 * 4) / byteSize;
+ for (let i = offset; i < data.length; i += stride) {
+ const x = data[i];
+ const y = data[i + 1];
+ if (x > maxX)
+ maxX = x;
+ if (y > maxY)
+ maxY = y;
+ if (x < minX)
+ minX = x;
+ if (y < minY)
+ minY = y;
+ }
+ bounds.minX = minX;
+ bounds.minY = minY;
+ bounds.maxX = maxX;
+ bounds.maxY = maxY;
+ return bounds;
+ }
+
+ "use strict";
+ function ensureIsAttribute(attribute) {
+ if (attribute instanceof Buffer || Array.isArray(attribute) || attribute.BYTES_PER_ELEMENT) {
+ attribute = {
+ buffer: attribute
+ };
+ }
+ attribute.buffer = ensureIsBuffer(attribute.buffer, false);
+ return attribute;
+ }
+ class Geometry extends EventEmitter {
+ /**
+ * Create a new instance of a geometry
+ * @param options - The options for the geometry.
+ */
+ constructor(options = {}) {
+ var _a;
+ super();
+ /** The unique id of the geometry. */
+ this.uid = uid$1("geometry");
+ /**
+ * the layout key will be generated by WebGPU all geometries that have the same structure
+ * will have the same layout key. This is used to cache the pipeline layout
+ * @internal
+ */
+ this._layoutKey = 0;
+ /** the instance count of the geometry to draw */
+ this.instanceCount = 1;
+ this._bounds = new Bounds();
+ this._boundsDirty = true;
+ const { attributes, indexBuffer, topology } = options;
+ this.buffers = [];
+ this.attributes = {};
+ if (attributes) {
+ for (const i in attributes) {
+ this.addAttribute(i, attributes[i]);
+ }
+ }
+ this.instanceCount = (_a = options.instanceCount) != null ? _a : 1;
+ if (indexBuffer) {
+ this.addIndex(indexBuffer);
+ }
+ this.topology = topology || "triangle-list";
+ }
+ onBufferUpdate() {
+ this._boundsDirty = true;
+ this.emit("update", this);
+ }
+ /**
+ * Returns the requested attribute.
+ * @param id - The name of the attribute required
+ * @returns - The attribute requested.
+ */
+ getAttribute(id) {
+ return this.attributes[id];
+ }
+ /**
+ * Returns the index buffer
+ * @returns - The index buffer.
+ */
+ getIndex() {
+ return this.indexBuffer;
+ }
+ /**
+ * Returns the requested buffer.
+ * @param id - The name of the buffer required.
+ * @returns - The buffer requested.
+ */
+ getBuffer(id) {
+ return this.getAttribute(id).buffer;
+ }
+ /**
+ * Used to figure out how many vertices there are in this geometry
+ * @returns the number of vertices in the geometry
+ */
+ getSize() {
+ for (const i in this.attributes) {
+ const attribute = this.attributes[i];
+ const buffer = attribute.buffer;
+ return buffer.data.length / (attribute.stride / 4 || attribute.size);
+ }
+ return 0;
+ }
+ /**
+ * Adds an attribute to the geometry.
+ * @param name - The name of the attribute to add.
+ * @param attributeOption - The attribute option to add.
+ */
+ addAttribute(name, attributeOption) {
+ const attribute = ensureIsAttribute(attributeOption);
+ const bufferIndex = this.buffers.indexOf(attribute.buffer);
+ if (bufferIndex === -1) {
+ this.buffers.push(attribute.buffer);
+ attribute.buffer.on("update", this.onBufferUpdate, this);
+ attribute.buffer.on("change", this.onBufferUpdate, this);
+ }
+ this.attributes[name] = attribute;
+ }
+ /**
+ * Adds an index buffer to the geometry.
+ * @param indexBuffer - The index buffer to add. Can be a Buffer, TypedArray, or an array of numbers.
+ */
+ addIndex(indexBuffer) {
+ this.indexBuffer = ensureIsBuffer(indexBuffer, true);
+ this.buffers.push(this.indexBuffer);
+ }
+ /** Returns the bounds of the geometry. */
+ get bounds() {
+ if (!this._boundsDirty)
+ return this._bounds;
+ this._boundsDirty = false;
+ return getGeometryBounds(this, "aPosition", this._bounds);
+ }
+ /**
+ * destroys the geometry.
+ * @param destroyBuffers - destroy the buffers associated with this geometry
+ */
+ destroy(destroyBuffers = false) {
+ this.emit("destroy", this);
+ this.removeAllListeners();
+ if (destroyBuffers) {
+ this.buffers.forEach((buffer) => buffer.destroy());
+ }
+ this.attributes = null;
+ this.buffers = null;
+ this.indexBuffer = null;
+ this._bounds = null;
+ }
+ }
+
+ "use strict";
+ const placeHolderBufferData = new Float32Array(1);
+ const placeHolderIndexData = new Uint32Array(1);
+ class BatchGeometry extends Geometry {
+ constructor() {
+ const vertexSize = 6;
+ const attributeBuffer = new Buffer({
+ data: placeHolderBufferData,
+ label: "attribute-batch-buffer",
+ usage: BufferUsage.VERTEX | BufferUsage.COPY_DST,
+ shrinkToFit: false
+ });
+ const indexBuffer = new Buffer({
+ data: placeHolderIndexData,
+ label: "index-batch-buffer",
+ usage: BufferUsage.INDEX | BufferUsage.COPY_DST,
+ // | BufferUsage.STATIC,
+ shrinkToFit: false
+ });
+ const stride = vertexSize * 4;
+ super({
+ attributes: {
+ aPosition: {
+ buffer: attributeBuffer,
+ format: "float32x2",
+ stride,
+ offset: 0
+ },
+ aUV: {
+ buffer: attributeBuffer,
+ format: "float32x2",
+ stride,
+ offset: 2 * 4
+ },
+ aColor: {
+ buffer: attributeBuffer,
+ format: "unorm8x4",
+ stride,
+ offset: 4 * 4
+ },
+ aTextureIdAndRound: {
+ buffer: attributeBuffer,
+ format: "uint16x2",
+ stride,
+ offset: 5 * 4
+ }
+ },
+ indexBuffer
+ });
+ }
+ }
+
+ "use strict";
+ const idCounts = /* @__PURE__ */ Object.create(null);
+ const idHash = /* @__PURE__ */ Object.create(null);
+ function createIdFromString(value, groupId) {
+ let id = idHash[value];
+ if (id === void 0) {
+ if (idCounts[groupId] === void 0) {
+ idCounts[groupId] = 1;
+ }
+ idHash[value] = id = idCounts[groupId]++;
+ }
+ return id;
+ }
+
+ "use strict";
+ let maxFragmentPrecision;
+ function getMaxFragmentPrecision() {
+ if (!maxFragmentPrecision) {
+ maxFragmentPrecision = "mediump";
+ const gl = getTestContext();
+ if (gl) {
+ if (gl.getShaderPrecisionFormat) {
+ const shaderFragment = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
+ maxFragmentPrecision = shaderFragment.precision ? "highp" : "mediump";
+ }
+ }
+ }
+ return maxFragmentPrecision;
+ }
+
+ "use strict";
+ function addProgramDefines(src, isES300, isFragment) {
+ if (isES300)
+ return src;
+ if (isFragment) {
+ src = src.replace("out vec4 finalColor;", "");
+ return `
+
+ #ifdef GL_ES // This checks if it is WebGL1
+ #define in varying
+ #define finalColor gl_FragColor
+ #define texture texture2D
+ #endif
+ ${src}
+ `;
+ }
+ return `
+
+ #ifdef GL_ES // This checks if it is WebGL1
+ #define in attribute
+ #define out varying
+ #endif
+ ${src}
+ `;
+ }
+
+ "use strict";
+ function ensurePrecision(src, options, isFragment) {
+ const maxSupportedPrecision = isFragment ? options.maxSupportedFragmentPrecision : options.maxSupportedVertexPrecision;
+ if (src.substring(0, 9) !== "precision") {
+ let precision = isFragment ? options.requestedFragmentPrecision : options.requestedVertexPrecision;
+ if (precision === "highp" && maxSupportedPrecision !== "highp") {
+ precision = "mediump";
+ }
+ return `precision ${precision} float;
+${src}`;
+ } else if (maxSupportedPrecision !== "highp" && src.substring(0, 15) === "precision highp") {
+ return src.replace("precision highp", "precision mediump");
+ }
+ return src;
+ }
+
+ "use strict";
+ function insertVersion(src, isES300) {
+ if (!isES300)
+ return src;
+ return `#version 300 es
+${src}`;
+ }
+
+ "use strict";
+ const fragmentNameCache = {};
+ const VertexNameCache = {};
+ function setProgramName(src, { name = `pixi-program` }, isFragment = true) {
+ name = name.replace(/\s+/g, "-");
+ name += isFragment ? "-fragment" : "-vertex";
+ const nameCache = isFragment ? fragmentNameCache : VertexNameCache;
+ if (nameCache[name]) {
+ nameCache[name]++;
+ name += `-${nameCache[name]}`;
+ } else {
+ nameCache[name] = 1;
+ }
+ if (src.indexOf("#define SHADER_NAME") !== -1)
+ return src;
+ const shaderName = `#define SHADER_NAME ${name}`;
+ return `${shaderName}
+${src}`;
+ }
+
+ "use strict";
+ function stripVersion(src, isES300) {
+ if (!isES300)
+ return src;
+ return src.replace("#version 300 es", "");
+ }
+
+ "use strict";
+ var __defProp$10 = Object.defineProperty;
+ var __getOwnPropSymbols$11 = Object.getOwnPropertySymbols;
+ var __hasOwnProp$11 = Object.prototype.hasOwnProperty;
+ var __propIsEnum$11 = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$10 = (obj, key, value) => key in obj ? __defProp$10(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$10 = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$11.call(b, prop))
+ __defNormalProp$10(a, prop, b[prop]);
+ if (__getOwnPropSymbols$11)
+ for (var prop of __getOwnPropSymbols$11(b)) {
+ if (__propIsEnum$11.call(b, prop))
+ __defNormalProp$10(a, prop, b[prop]);
+ }
+ return a;
+ };
+ const processes = {
+ // strips any version headers..
+ stripVersion,
+ // adds precision string if not already present
+ ensurePrecision,
+ // add some defines if WebGL1 to make it more compatible with WebGL2 shaders
+ addProgramDefines,
+ // add the program name to the shader
+ setProgramName,
+ // add the version string to the shader header
+ insertVersion
+ };
+ const programCache$1 = /* @__PURE__ */ Object.create(null);
+ const _GlProgram = class _GlProgram {
+ /**
+ * Creates a shiny new GlProgram. Used by WebGL renderer.
+ * @param options - The options for the program.
+ */
+ constructor(options) {
+ options = __spreadValues$10(__spreadValues$10({}, _GlProgram.defaultOptions), options);
+ const isES300 = options.fragment.indexOf("#version 300 es") !== -1;
+ const preprocessorOptions = {
+ stripVersion: isES300,
+ ensurePrecision: {
+ requestedFragmentPrecision: options.preferredFragmentPrecision,
+ requestedVertexPrecision: options.preferredVertexPrecision,
+ maxSupportedVertexPrecision: "highp",
+ maxSupportedFragmentPrecision: getMaxFragmentPrecision()
+ },
+ setProgramName: {
+ name: options.name
+ },
+ addProgramDefines: isES300,
+ insertVersion: isES300
+ };
+ let fragment = options.fragment;
+ let vertex = options.vertex;
+ Object.keys(processes).forEach((processKey) => {
+ const processOptions = preprocessorOptions[processKey];
+ fragment = processes[processKey](fragment, processOptions, true);
+ vertex = processes[processKey](vertex, processOptions, false);
+ });
+ this.fragment = fragment;
+ this.vertex = vertex;
+ this.transformFeedbackVaryings = options.transformFeedbackVaryings;
+ this._key = createIdFromString(`${this.vertex}:${this.fragment}`, "gl-program");
+ }
+ /** destroys the program */
+ destroy() {
+ this.fragment = null;
+ this.vertex = null;
+ this._attributeData = null;
+ this._uniformData = null;
+ this._uniformBlockData = null;
+ this.transformFeedbackVaryings = null;
+ programCache$1[this._cacheKey] = null;
+ }
+ /**
+ * Helper function that creates a program for a given source.
+ * It will check the program cache if the program has already been created.
+ * If it has that one will be returned, if not a new one will be created and cached.
+ * @param options - The options for the program.
+ * @returns A program using the same source
+ */
+ static from(options) {
+ const key = `${options.vertex}:${options.fragment}`;
+ if (!programCache$1[key]) {
+ programCache$1[key] = new _GlProgram(options);
+ programCache$1[key]._cacheKey = key;
+ }
+ return programCache$1[key];
+ }
+ };
+ /** The default options used by the program. */
+ _GlProgram.defaultOptions = {
+ preferredVertexPrecision: "highp",
+ preferredFragmentPrecision: "mediump"
+ };
+ let GlProgram = _GlProgram;
+
+ "use strict";
+ const attributeFormatData = {
+ uint8x2: { size: 2, stride: 2, normalised: false },
+ uint8x4: { size: 4, stride: 4, normalised: false },
+ sint8x2: { size: 2, stride: 2, normalised: false },
+ sint8x4: { size: 4, stride: 4, normalised: false },
+ unorm8x2: { size: 2, stride: 2, normalised: true },
+ unorm8x4: { size: 4, stride: 4, normalised: true },
+ snorm8x2: { size: 2, stride: 2, normalised: true },
+ snorm8x4: { size: 4, stride: 4, normalised: true },
+ uint16x2: { size: 2, stride: 4, normalised: false },
+ uint16x4: { size: 4, stride: 8, normalised: false },
+ sint16x2: { size: 2, stride: 4, normalised: false },
+ sint16x4: { size: 4, stride: 8, normalised: false },
+ unorm16x2: { size: 2, stride: 4, normalised: true },
+ unorm16x4: { size: 4, stride: 8, normalised: true },
+ snorm16x2: { size: 2, stride: 4, normalised: true },
+ snorm16x4: { size: 4, stride: 8, normalised: true },
+ float16x2: { size: 2, stride: 4, normalised: false },
+ float16x4: { size: 4, stride: 8, normalised: false },
+ float32: { size: 1, stride: 4, normalised: false },
+ float32x2: { size: 2, stride: 8, normalised: false },
+ float32x3: { size: 3, stride: 12, normalised: false },
+ float32x4: { size: 4, stride: 16, normalised: false },
+ uint32: { size: 1, stride: 4, normalised: false },
+ uint32x2: { size: 2, stride: 8, normalised: false },
+ uint32x3: { size: 3, stride: 12, normalised: false },
+ uint32x4: { size: 4, stride: 16, normalised: false },
+ sint32: { size: 1, stride: 4, normalised: false },
+ sint32x2: { size: 2, stride: 8, normalised: false },
+ sint32x3: { size: 3, stride: 12, normalised: false },
+ sint32x4: { size: 4, stride: 16, normalised: false }
+ };
+ function getAttributeInfoFromFormat(format) {
+ var _a;
+ return (_a = attributeFormatData[format]) != null ? _a : attributeFormatData.float32;
+ }
+
+ "use strict";
+ const WGSL_TO_VERTEX_TYPES = {
+ f32: "float32",
+ "vec2": "float32x2",
+ "vec3": "float32x3",
+ "vec4": "float32x4",
+ vec2f: "float32x2",
+ vec3f: "float32x3",
+ vec4f: "float32x4",
+ i32: "sint32",
+ "vec2": "sint32x2",
+ "vec3": "sint32x3",
+ "vec4": "sint32x4",
+ u32: "uint32",
+ "vec2": "uint32x2",
+ "vec3": "uint32x3",
+ "vec4": "uint32x4",
+ bool: "uint32",
+ "vec2": "uint32x2",
+ "vec3": "uint32x3",
+ "vec4": "uint32x4"
+ };
+ function extractAttributesFromGpuProgram({ source, entryPoint }) {
+ var _a;
+ const results = {};
+ const mainVertStart = source.indexOf(`fn ${entryPoint}`);
+ if (mainVertStart !== -1) {
+ const arrowFunctionStart = source.indexOf("->", mainVertStart);
+ if (arrowFunctionStart !== -1) {
+ const functionArgsSubstring = source.substring(mainVertStart, arrowFunctionStart);
+ const inputsRegex = /@location\((\d+)\)\s+([a-zA-Z0-9_]+)\s*:\s*([a-zA-Z0-9_<>]+)(?:,|\s|$)/g;
+ let match;
+ while ((match = inputsRegex.exec(functionArgsSubstring)) !== null) {
+ const format = (_a = WGSL_TO_VERTEX_TYPES[match[3]]) != null ? _a : "float32";
+ results[match[2]] = {
+ location: parseInt(match[1], 10),
+ format,
+ stride: getAttributeInfoFromFormat(format).stride,
+ offset: 0,
+ instance: false,
+ start: 0
+ };
+ }
+ }
+ }
+ return results;
+ }
+
+ "use strict";
+ function extractStructAndGroups(wgsl) {
+ var _a, _b, _c;
+ const linePattern = /(^|[^/])@(group|binding)\(\d+\)[^;]+;/g;
+ const groupPattern = /@group\((\d+)\)/;
+ const bindingPattern = /@binding\((\d+)\)/;
+ const namePattern = /var(<[^>]+>)? (\w+)/;
+ const typePattern = /:\s*(\w+)/;
+ const structPattern = /struct\s+(\w+)\s*{([^}]+)}/g;
+ const structMemberPattern = /(\w+)\s*:\s*([\w\<\>]+)/g;
+ const structName = /struct\s+(\w+)/;
+ const groups = (_a = wgsl.match(linePattern)) == null ? void 0 : _a.map((item) => ({
+ group: parseInt(item.match(groupPattern)[1], 10),
+ binding: parseInt(item.match(bindingPattern)[1], 10),
+ name: item.match(namePattern)[2],
+ isUniform: item.match(namePattern)[1] === "",
+ type: item.match(typePattern)[1]
+ }));
+ if (!groups) {
+ return {
+ groups: [],
+ structs: []
+ };
+ }
+ const structs = (_c = (_b = wgsl.match(structPattern)) == null ? void 0 : _b.map((struct) => {
+ const name = struct.match(structName)[1];
+ const members = struct.match(structMemberPattern).reduce((acc, member) => {
+ const [name2, type] = member.split(":");
+ acc[name2.trim()] = type.trim();
+ return acc;
+ }, {});
+ if (!members) {
+ return null;
+ }
+ return { name, members };
+ }).filter(({ name }) => groups.some((group) => group.type === name))) != null ? _c : [];
+ return {
+ groups,
+ structs
+ };
+ }
+
+ "use strict";
+ var ShaderStage = /* @__PURE__ */ ((ShaderStage2) => {
+ ShaderStage2[ShaderStage2["VERTEX"] = 1] = "VERTEX";
+ ShaderStage2[ShaderStage2["FRAGMENT"] = 2] = "FRAGMENT";
+ ShaderStage2[ShaderStage2["COMPUTE"] = 4] = "COMPUTE";
+ return ShaderStage2;
+ })(ShaderStage || {});
+
+ "use strict";
+ function generateGpuLayoutGroups({ groups }) {
+ const layout = [];
+ for (let i = 0; i < groups.length; i++) {
+ const group = groups[i];
+ if (!layout[group.group]) {
+ layout[group.group] = [];
+ }
+ if (group.isUniform) {
+ layout[group.group].push({
+ binding: group.binding,
+ visibility: ShaderStage.VERTEX | ShaderStage.FRAGMENT,
+ buffer: {
+ type: "uniform"
+ }
+ });
+ } else if (group.type === "sampler") {
+ layout[group.group].push({
+ binding: group.binding,
+ visibility: ShaderStage.FRAGMENT,
+ sampler: {
+ type: "filtering"
+ }
+ });
+ } else if (group.type === "texture_2d") {
+ layout[group.group].push({
+ binding: group.binding,
+ visibility: ShaderStage.FRAGMENT,
+ texture: {
+ sampleType: "float",
+ viewDimension: "2d",
+ multisampled: false
+ }
+ });
+ }
+ }
+ return layout;
+ }
+
+ "use strict";
+ function generateLayoutHash({ groups }) {
+ const layout = [];
+ for (let i = 0; i < groups.length; i++) {
+ const group = groups[i];
+ if (!layout[group.group]) {
+ layout[group.group] = {};
+ }
+ layout[group.group][group.name] = group.binding;
+ }
+ return layout;
+ }
+
+ "use strict";
+ function removeStructAndGroupDuplicates(vertexStructsAndGroups, fragmentStructsAndGroups) {
+ const structNameSet = /* @__PURE__ */ new Set();
+ const dupeGroupKeySet = /* @__PURE__ */ new Set();
+ const structs = [...vertexStructsAndGroups.structs, ...fragmentStructsAndGroups.structs].filter((struct) => {
+ if (structNameSet.has(struct.name)) {
+ return false;
+ }
+ structNameSet.add(struct.name);
+ return true;
+ });
+ const groups = [...vertexStructsAndGroups.groups, ...fragmentStructsAndGroups.groups].filter((group) => {
+ const key = `${group.name}-${group.binding}`;
+ if (dupeGroupKeySet.has(key)) {
+ return false;
+ }
+ dupeGroupKeySet.add(key);
+ return true;
+ });
+ return { structs, groups };
+ }
+
+ "use strict";
+ const programCache = /* @__PURE__ */ Object.create(null);
+ class GpuProgram {
+ /**
+ * Create a new GpuProgram
+ * @param options - The options for the gpu program
+ */
+ constructor(options) {
+ /** @internal */
+ this._layoutKey = 0;
+ /** @internal */
+ this._attributeLocationsKey = 0;
+ var _a, _b;
+ const { fragment, vertex, layout, gpuLayout, name } = options;
+ this.name = name;
+ this.fragment = fragment;
+ this.vertex = vertex;
+ if (fragment.source === vertex.source) {
+ const structsAndGroups = extractStructAndGroups(fragment.source);
+ this.structsAndGroups = structsAndGroups;
+ } else {
+ const vertexStructsAndGroups = extractStructAndGroups(vertex.source);
+ const fragmentStructsAndGroups = extractStructAndGroups(fragment.source);
+ this.structsAndGroups = removeStructAndGroupDuplicates(vertexStructsAndGroups, fragmentStructsAndGroups);
+ }
+ this.layout = layout != null ? layout : generateLayoutHash(this.structsAndGroups);
+ this.gpuLayout = gpuLayout != null ? gpuLayout : generateGpuLayoutGroups(this.structsAndGroups);
+ this.autoAssignGlobalUniforms = !!(((_a = this.layout[0]) == null ? void 0 : _a.globalUniforms) !== void 0);
+ this.autoAssignLocalUniforms = !!(((_b = this.layout[1]) == null ? void 0 : _b.localUniforms) !== void 0);
+ this._generateProgramKey();
+ }
+ // TODO maker this pure
+ _generateProgramKey() {
+ const { vertex, fragment } = this;
+ const bigKey = vertex.source + fragment.source + vertex.entryPoint + fragment.entryPoint;
+ this._layoutKey = createIdFromString(bigKey, "program");
+ }
+ get attributeData() {
+ var _a;
+ (_a = this._attributeData) != null ? _a : this._attributeData = extractAttributesFromGpuProgram(this.vertex);
+ return this._attributeData;
+ }
+ /** destroys the program */
+ destroy() {
+ this.gpuLayout = null;
+ this.layout = null;
+ this.structsAndGroups = null;
+ this.fragment = null;
+ this.vertex = null;
+ programCache[this._cacheKey] = null;
+ }
+ /**
+ * Helper function that creates a program for a given source.
+ * It will check the program cache if the program has already been created.
+ * If it has that one will be returned, if not a new one will be created and cached.
+ * @param options - The options for the program.
+ * @returns A program using the same source
+ */
+ static from(options) {
+ const key = `${options.vertex.source}:${options.fragment.source}:${options.fragment.entryPoint}:${options.vertex.entryPoint}`;
+ if (!programCache[key]) {
+ programCache[key] = new GpuProgram(options);
+ programCache[key]._cacheKey = key;
+ }
+ return programCache[key];
+ }
+ }
+
+ "use strict";
+ function addBits(srcParts, parts, name) {
+ if (srcParts) {
+ for (const i in srcParts) {
+ const id = i.toLocaleLowerCase();
+ const part = parts[id];
+ if (part) {
+ let sanitisedPart = srcParts[i];
+ if (i === "header") {
+ sanitisedPart = sanitisedPart.replace(/@in\s+[^;]+;\s*/g, "").replace(/@out\s+[^;]+;\s*/g, "");
+ }
+ if (name) {
+ part.push(`//----${name}----//`);
+ }
+ part.push(sanitisedPart);
+ } else {
+ warn(`${i} placement hook does not exist in shader`);
+ }
+ }
+ }
+ }
+
+ "use strict";
+ const findHooksRx = /\{\{(.*?)\}\}/g;
+ function compileHooks(programSrc) {
+ var _a, _b;
+ const parts = {};
+ const partMatches = (_b = (_a = programSrc.match(findHooksRx)) == null ? void 0 : _a.map((hook) => hook.replace(/[{()}]/g, ""))) != null ? _b : [];
+ partMatches.forEach((hook) => {
+ parts[hook] = [];
+ });
+ return parts;
+ }
+
+ "use strict";
+ function extractInputs(fragmentSource, out) {
+ let match;
+ const regex = /@in\s+([^;]+);/g;
+ while ((match = regex.exec(fragmentSource)) !== null) {
+ out.push(match[1]);
+ }
+ }
+ function compileInputs(fragments, template, sort = false) {
+ const results = [];
+ extractInputs(template, results);
+ fragments.forEach((fragment) => {
+ if (fragment.header) {
+ extractInputs(fragment.header, results);
+ }
+ });
+ const mainInput = results;
+ if (sort) {
+ mainInput.sort();
+ }
+ const finalString = mainInput.map((inValue, i) => ` @location(${i}) ${inValue},`).join("\n");
+ let cleanedString = template.replace(/@in\s+[^;]+;\s*/g, "");
+ cleanedString = cleanedString.replace("{{in}}", `
+${finalString}
+`);
+ return cleanedString;
+ }
+
+ "use strict";
+ function extractOutputs(fragmentSource, out) {
+ let match;
+ const regex = /@out\s+([^;]+);/g;
+ while ((match = regex.exec(fragmentSource)) !== null) {
+ out.push(match[1]);
+ }
+ }
+ function extractVariableName(value) {
+ const regex = /\b(\w+)\s*:/g;
+ const match = regex.exec(value);
+ return match ? match[1] : "";
+ }
+ function stripVariable(value) {
+ const regex = /@.*?\s+/g;
+ return value.replace(regex, "");
+ }
+ function compileOutputs(fragments, template) {
+ const results = [];
+ extractOutputs(template, results);
+ fragments.forEach((fragment) => {
+ if (fragment.header) {
+ extractOutputs(fragment.header, results);
+ }
+ });
+ let index = 0;
+ const mainStruct = results.sort().map((inValue) => {
+ if (inValue.indexOf("builtin") > -1) {
+ return inValue;
+ }
+ return `@location(${index++}) ${inValue}`;
+ }).join(",\n");
+ const mainStart = results.sort().map((inValue) => ` var ${stripVariable(inValue)};`).join("\n");
+ const mainEnd = `return VSOutput(
+ ${results.sort().map((inValue) => ` ${extractVariableName(inValue)}`).join(",\n")});`;
+ let compiledCode = template.replace(/@out\s+[^;]+;\s*/g, "");
+ compiledCode = compiledCode.replace("{{struct}}", `
+${mainStruct}
+`);
+ compiledCode = compiledCode.replace("{{start}}", `
+${mainStart}
+`);
+ compiledCode = compiledCode.replace("{{return}}", `
+${mainEnd}
+`);
+ return compiledCode;
+ }
+
+ "use strict";
+ function injectBits(templateSrc, fragmentParts) {
+ let out = templateSrc;
+ for (const i in fragmentParts) {
+ const parts = fragmentParts[i];
+ const toInject = parts.join("\n");
+ if (toInject.length) {
+ out = out.replace(`{{${i}}}`, `//-----${i} START-----//
+${parts.join("\n")}
+//----${i} FINISH----//`);
+ } else {
+ out = out.replace(`{{${i}}}`, "");
+ }
+ }
+ return out;
+ }
+
+ "use strict";
+ const cacheMap = /* @__PURE__ */ Object.create(null);
+ const bitCacheMap = /* @__PURE__ */ new Map();
+ let CACHE_UID = 0;
+ function compileHighShader({
+ template,
+ bits
+ }) {
+ const cacheId = generateCacheId(template, bits);
+ if (cacheMap[cacheId])
+ return cacheMap[cacheId];
+ const { vertex, fragment } = compileInputsAndOutputs(template, bits);
+ cacheMap[cacheId] = compileBits(vertex, fragment, bits);
+ return cacheMap[cacheId];
+ }
+ function compileHighShaderGl({
+ template,
+ bits
+ }) {
+ const cacheId = generateCacheId(template, bits);
+ if (cacheMap[cacheId])
+ return cacheMap[cacheId];
+ cacheMap[cacheId] = compileBits(template.vertex, template.fragment, bits);
+ return cacheMap[cacheId];
+ }
+ function compileInputsAndOutputs(template, bits) {
+ const vertexFragments = bits.map((shaderBit) => shaderBit.vertex).filter((v) => !!v);
+ const fragmentFragments = bits.map((shaderBit) => shaderBit.fragment).filter((v) => !!v);
+ let compiledVertex = compileInputs(vertexFragments, template.vertex, true);
+ compiledVertex = compileOutputs(vertexFragments, compiledVertex);
+ const compiledFragment = compileInputs(fragmentFragments, template.fragment, true);
+ return {
+ vertex: compiledVertex,
+ fragment: compiledFragment
+ };
+ }
+ function generateCacheId(template, bits) {
+ return bits.map((highFragment) => {
+ if (!bitCacheMap.has(highFragment)) {
+ bitCacheMap.set(highFragment, CACHE_UID++);
+ }
+ return bitCacheMap.get(highFragment);
+ }).sort((a, b) => a - b).join("-") + template.vertex + template.fragment;
+ }
+ function compileBits(vertex, fragment, bits) {
+ const vertexParts = compileHooks(vertex);
+ const fragmentParts = compileHooks(fragment);
+ bits.forEach((shaderBit) => {
+ addBits(shaderBit.vertex, vertexParts, shaderBit.name);
+ addBits(shaderBit.fragment, fragmentParts, shaderBit.name);
+ });
+ return {
+ vertex: injectBits(vertex, vertexParts),
+ fragment: injectBits(fragment, fragmentParts)
+ };
+ }
+
+ "use strict";
+ const vertexGPUTemplate = (
+ /* wgsl */
+ `
+ @in aPosition: vec2;
+ @in aUV: vec2;
+
+ @out @builtin(position) vPosition: vec4;
+ @out vUV : vec2;
+ @out vColor : vec4;
+
+ {{header}}
+
+ struct VSOutput {
+ {{struct}}
+ };
+
+ @vertex
+ fn main( {{in}} ) -> VSOutput {
+
+ var worldTransformMatrix = globalUniforms.uWorldTransformMatrix;
+ var modelMatrix = mat3x3(
+ 1.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0
+ );
+ var position = aPosition;
+ var uv = aUV;
+
+ {{start}}
+
+ vColor = vec4(1., 1., 1., 1.);
+
+ {{main}}
+
+ vUV = uv;
+
+ var modelViewProjectionMatrix = globalUniforms.uProjectionMatrix * worldTransformMatrix * modelMatrix;
+
+ vPosition = vec4((modelViewProjectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+
+ vColor *= globalUniforms.uWorldColorAlpha;
+
+ {{end}}
+
+ {{return}}
+ };
+`
+ );
+ const fragmentGPUTemplate = (
+ /* wgsl */
+ `
+ @in vUV : vec2;
+ @in vColor : vec4;
+
+ {{header}}
+
+ @fragment
+ fn main(
+ {{in}}
+ ) -> @location(0) vec4 {
+
+ {{start}}
+
+ var outColor:vec4;
+
+ {{main}}
+
+ var finalColor:vec4 = outColor * vColor;
+
+ {{end}}
+
+ return finalColor;
+ };
+`
+ );
+ const vertexGlTemplate = (
+ /* glsl */
+ `
+ in vec2 aPosition;
+ in vec2 aUV;
+
+ out vec4 vColor;
+ out vec2 vUV;
+
+ {{header}}
+
+ void main(void){
+
+ mat3 worldTransformMatrix = uWorldTransformMatrix;
+ mat3 modelMatrix = mat3(
+ 1.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0
+ );
+ vec2 position = aPosition;
+ vec2 uv = aUV;
+
+ {{start}}
+
+ vColor = vec4(1.);
+
+ {{main}}
+
+ vUV = uv;
+
+ mat3 modelViewProjectionMatrix = uProjectionMatrix * worldTransformMatrix * modelMatrix;
+
+ gl_Position = vec4((modelViewProjectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
+
+ vColor *= uWorldColorAlpha;
+
+ {{end}}
+ }
+`
+ );
+ const fragmentGlTemplate = (
+ /* glsl */
+ `
+
+ in vec4 vColor;
+ in vec2 vUV;
+
+ out vec4 finalColor;
+
+ {{header}}
+
+ void main(void) {
+
+ {{start}}
+
+ vec4 outColor;
+
+ {{main}}
+
+ finalColor = outColor * vColor;
+
+ {{end}}
+ }
+`
+ );
+
+ "use strict";
+ const globalUniformsBit = {
+ name: "global-uniforms-bit",
+ vertex: {
+ header: (
+ /* wgsl */
+ `
+ struct GlobalUniforms {
+ uProjectionMatrix:mat3x3,
+ uWorldTransformMatrix:mat3x3,
+ uWorldColorAlpha: vec4,
+ uResolution: vec2,
+ }
+
+ @group(0) @binding(0) var globalUniforms : GlobalUniforms;
+ `
+ )
+ }
+ };
+ const globalUniformsUBOBitGl = {
+ name: "global-uniforms-ubo-bit",
+ vertex: {
+ header: (
+ /* glsl */
+ `
+ uniform globalUniforms {
+ mat3 uProjectionMatrix;
+ mat3 uWorldTransformMatrix;
+ vec4 uWorldColorAlpha;
+ vec2 uResolution;
+ };
+ `
+ )
+ }
+ };
+ const globalUniformsBitGl = {
+ name: "global-uniforms-bit",
+ vertex: {
+ header: (
+ /* glsl */
+ `
+ uniform mat3 uProjectionMatrix;
+ uniform mat3 uWorldTransformMatrix;
+ uniform vec4 uWorldColorAlpha;
+ uniform vec2 uResolution;
+ `
+ )
+ }
+ };
+
+ "use strict";
+ var __defProp$$ = Object.defineProperty;
+ var __getOwnPropSymbols$10 = Object.getOwnPropertySymbols;
+ var __hasOwnProp$10 = Object.prototype.hasOwnProperty;
+ var __propIsEnum$10 = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$$ = (obj, key, value) => key in obj ? __defProp$$(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$$ = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$10.call(b, prop))
+ __defNormalProp$$(a, prop, b[prop]);
+ if (__getOwnPropSymbols$10)
+ for (var prop of __getOwnPropSymbols$10(b)) {
+ if (__propIsEnum$10.call(b, prop))
+ __defNormalProp$$(a, prop, b[prop]);
+ }
+ return a;
+ };
+ function compileHighShaderGpuProgram({ bits, name }) {
+ const source = compileHighShader({
+ template: {
+ fragment: fragmentGPUTemplate,
+ vertex: vertexGPUTemplate
+ },
+ bits: [
+ globalUniformsBit,
+ ...bits
+ ]
+ });
+ return GpuProgram.from({
+ name,
+ vertex: {
+ source: source.vertex,
+ entryPoint: "main"
+ },
+ fragment: {
+ source: source.fragment,
+ entryPoint: "main"
+ }
+ });
+ }
+ function compileHighShaderGlProgram({ bits, name }) {
+ return new GlProgram(__spreadValues$$({
+ name
+ }, compileHighShaderGl({
+ template: {
+ vertex: vertexGlTemplate,
+ fragment: fragmentGlTemplate
+ },
+ bits: [
+ globalUniformsBitGl,
+ ...bits
+ ]
+ })));
+ }
+
+ "use strict";
+ const colorBit = {
+ name: "color-bit",
+ vertex: {
+ header: (
+ /* wgsl */
+ `
+ @in aColor: vec4;
+ `
+ ),
+ main: (
+ /* wgsl */
+ `
+ vColor *= vec4(aColor.rgb * aColor.a, aColor.a);
+ `
+ )
+ }
+ };
+ const colorBitGl = {
+ name: "color-bit",
+ vertex: {
+ header: (
+ /* glsl */
+ `
+ in vec4 aColor;
+ `
+ ),
+ main: (
+ /* glsl */
+ `
+ vColor *= vec4(aColor.rgb * aColor.a, aColor.a);
+ `
+ )
+ }
+ };
+
+ "use strict";
+ const textureBatchBitGpuCache = {};
+ function generateBindingSrc(maxTextures) {
+ const src = [];
+ if (maxTextures === 1) {
+ src.push("@group(1) @binding(0) var textureSource1: texture_2d;");
+ src.push("@group(1) @binding(1) var textureSampler1: sampler;");
+ } else {
+ let bindingIndex = 0;
+ for (let i = 0; i < maxTextures; i++) {
+ src.push(`@group(1) @binding(${bindingIndex++}) var textureSource${i + 1}: texture_2d;`);
+ src.push(`@group(1) @binding(${bindingIndex++}) var textureSampler${i + 1}: sampler;`);
+ }
+ }
+ return src.join("\n");
+ }
+ function generateSampleSrc(maxTextures) {
+ const src = [];
+ if (maxTextures === 1) {
+ src.push("outColor = textureSampleGrad(textureSource1, textureSampler1, vUV, uvDx, uvDy);");
+ } else {
+ src.push("switch vTextureId {");
+ for (let i = 0; i < maxTextures; i++) {
+ if (i === maxTextures - 1) {
+ src.push(` default:{`);
+ } else {
+ src.push(` case ${i}:{`);
+ }
+ src.push(` outColor = textureSampleGrad(textureSource${i + 1}, textureSampler${i + 1}, vUV, uvDx, uvDy);`);
+ src.push(` break;}`);
+ }
+ src.push(`}`);
+ }
+ return src.join("\n");
+ }
+ function generateTextureBatchBit(maxTextures) {
+ if (!textureBatchBitGpuCache[maxTextures]) {
+ textureBatchBitGpuCache[maxTextures] = {
+ name: "texture-batch-bit",
+ vertex: {
+ header: `
+ @in aTextureIdAndRound: vec2;
+ @out @interpolate(flat) vTextureId : u32;
+ `,
+ main: `
+ vTextureId = aTextureIdAndRound.y;
+ `,
+ end: `
+ if(aTextureIdAndRound.x == 1)
+ {
+ vPosition = vec4(roundPixels(vPosition.xy, globalUniforms.uResolution), vPosition.zw);
+ }
+ `
+ },
+ fragment: {
+ header: `
+ @in @interpolate(flat) vTextureId: u32;
+
+ ${generateBindingSrc(maxTextures)}
+ `,
+ main: `
+ var uvDx = dpdx(vUV);
+ var uvDy = dpdy(vUV);
+
+ ${generateSampleSrc(maxTextures)}
+ `
+ }
+ };
+ }
+ return textureBatchBitGpuCache[maxTextures];
+ }
+ const textureBatchBitGlCache = {};
+ function generateSampleGlSrc(maxTextures) {
+ const src = [];
+ for (let i = 0; i < maxTextures; i++) {
+ if (i > 0) {
+ src.push("else");
+ }
+ if (i < maxTextures - 1) {
+ src.push(`if(vTextureId < ${i}.5)`);
+ }
+ src.push("{");
+ src.push(` outColor = texture(uTextures[${i}], vUV);`);
+ src.push("}");
+ }
+ return src.join("\n");
+ }
+ function generateTextureBatchBitGl(maxTextures) {
+ if (!textureBatchBitGlCache[maxTextures]) {
+ textureBatchBitGlCache[maxTextures] = {
+ name: "texture-batch-bit",
+ vertex: {
+ header: `
+ in vec2 aTextureIdAndRound;
+ out float vTextureId;
+
+ `,
+ main: `
+ vTextureId = aTextureIdAndRound.y;
+ `,
+ end: `
+ if(aTextureIdAndRound.x == 1.)
+ {
+ gl_Position.xy = roundPixels(gl_Position.xy, uResolution);
+ }
+ `
+ },
+ fragment: {
+ header: `
+ in float vTextureId;
+
+ uniform sampler2D uTextures[${maxTextures}];
+
+ `,
+ main: `
+
+ ${generateSampleGlSrc(maxTextures)}
+ `
+ }
+ };
+ }
+ return textureBatchBitGlCache[maxTextures];
+ }
+
+ "use strict";
+ const roundPixelsBit = {
+ name: "round-pixels-bit",
+ vertex: {
+ header: (
+ /* wgsl */
+ `
+ fn roundPixels(position: vec2, targetSize: vec2) -> vec2
+ {
+ return (floor(((position * 0.5 + 0.5) * targetSize) + 0.5) / targetSize) * 2.0 - 1.0;
+ }
+ `
+ )
+ }
+ };
+ const roundPixelsBitGl = {
+ name: "round-pixels-bit",
+ vertex: {
+ header: (
+ /* glsl */
+ `
+ vec2 roundPixels(vec2 position, vec2 targetSize)
+ {
+ return (floor(((position * 0.5 + 0.5) * targetSize) + 0.5) / targetSize) * 2.0 - 1.0;
+ }
+ `
+ )
+ }
+ };
+
+ "use strict";
+ const UNIFORM_TYPES_VALUES = [
+ "f32",
+ "i32",
+ "vec2",
+ "vec3",
+ "vec4",
+ "mat2x2",
+ "mat3x3",
+ "mat4x4",
+ "mat3x2",
+ "mat4x2",
+ "mat2x3",
+ "mat4x3",
+ "mat2x4",
+ "mat3x4",
+ "vec2",
+ "vec3",
+ "vec4"
+ ];
+ const UNIFORM_TYPES_MAP = UNIFORM_TYPES_VALUES.reduce((acc, type) => {
+ acc[type] = true;
+ return acc;
+ }, {});
+
+ "use strict";
+ function getDefaultUniformValue(type, size) {
+ switch (type) {
+ case "f32":
+ return 0;
+ case "vec2":
+ return new Float32Array(2 * size);
+ case "vec3":
+ return new Float32Array(3 * size);
+ case "vec4":
+ return new Float32Array(4 * size);
+ case "mat2x2":
+ return new Float32Array([
+ 1,
+ 0,
+ 0,
+ 1
+ ]);
+ case "mat3x3":
+ return new Float32Array([
+ 1,
+ 0,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ 0,
+ 1
+ ]);
+ case "mat4x4":
+ return new Float32Array([
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1
+ ]);
+ }
+ return null;
+ }
+
+ "use strict";
+ var __defProp$_ = Object.defineProperty;
+ var __getOwnPropSymbols$$ = Object.getOwnPropertySymbols;
+ var __hasOwnProp$$ = Object.prototype.hasOwnProperty;
+ var __propIsEnum$$ = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$_ = (obj, key, value) => key in obj ? __defProp$_(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$_ = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$$.call(b, prop))
+ __defNormalProp$_(a, prop, b[prop]);
+ if (__getOwnPropSymbols$$)
+ for (var prop of __getOwnPropSymbols$$(b)) {
+ if (__propIsEnum$$.call(b, prop))
+ __defNormalProp$_(a, prop, b[prop]);
+ }
+ return a;
+ };
+ const _UniformGroup = class _UniformGroup {
+ /**
+ * Create a new Uniform group
+ * @param uniformStructures - The structures of the uniform group
+ * @param options - The optional parameters of this uniform group
+ */
+ constructor(uniformStructures, options) {
+ /**
+ * used internally to know if a uniform group was used in the last render pass
+ * @internal
+ */
+ this._touched = 0;
+ /** a unique id for this uniform group used through the renderer */
+ this.uid = uid$1("uniform");
+ /**
+ * a resource type, used to identify how to handle it when its in a bind group / shader resource
+ * @internal
+ */
+ this._resourceType = "uniformGroup";
+ /**
+ * the resource id used internally by the renderer to build bind group keys
+ * @internal
+ */
+ this._resourceId = uid$1("resource");
+ /** used ito identify if this is a uniform group */
+ this.isUniformGroup = true;
+ /**
+ * used to flag if this Uniform groups data is different from what it has stored in its buffer / on the GPU
+ * @internal
+ */
+ this._dirtyId = 0;
+ // implementing the interface - UniformGroup are not destroyed
+ this.destroyed = false;
+ var _a, _b;
+ options = __spreadValues$_(__spreadValues$_({}, _UniformGroup.defaultOptions), options);
+ this.uniformStructures = uniformStructures;
+ const uniforms = {};
+ for (const i in uniformStructures) {
+ const uniformData = uniformStructures[i];
+ uniformData.name = i;
+ uniformData.size = (_a = uniformData.size) != null ? _a : 1;
+ if (!UNIFORM_TYPES_MAP[uniformData.type]) {
+ const arrayMatch = uniformData.type.match(/^array<(\w+(?:<\w+>)?),\s*(\d+)>$/);
+ if (arrayMatch) {
+ const [, innerType, size] = arrayMatch;
+ throw new Error(
+ `Uniform type ${uniformData.type} is not supported. Use type: '${innerType}', size: ${size} instead.`
+ );
+ }
+ throw new Error(`Uniform type ${uniformData.type} is not supported. Supported uniform types are: ${UNIFORM_TYPES_VALUES.join(", ")}`);
+ }
+ (_b = uniformData.value) != null ? _b : uniformData.value = getDefaultUniformValue(uniformData.type, uniformData.size);
+ uniforms[i] = uniformData.value;
+ }
+ this.uniforms = uniforms;
+ this._dirtyId = 1;
+ this.ubo = options.ubo;
+ this.isStatic = options.isStatic;
+ this._signature = createIdFromString(Object.keys(uniforms).map(
+ (i) => `${i}-${uniformStructures[i].type}`
+ ).join("-"), "uniform-group");
+ }
+ /** Call this if you want the uniform groups data to be uploaded to the GPU only useful if `isStatic` is true. */
+ update() {
+ this._dirtyId++;
+ }
+ };
+ /** The default options used by the uniform group. */
+ _UniformGroup.defaultOptions = {
+ /** if true the UniformGroup is handled as an Uniform buffer object. */
+ ubo: false,
+ /** if true, then you are responsible for when the data is uploaded to the GPU by calling `update()` */
+ isStatic: false
+ };
+ let UniformGroup = _UniformGroup;
+
+ "use strict";
+ const batchSamplersUniformGroupHash = {};
+ function getBatchSamplersUniformGroup(maxTextures) {
+ let batchSamplersUniformGroup = batchSamplersUniformGroupHash[maxTextures];
+ if (batchSamplersUniformGroup)
+ return batchSamplersUniformGroup;
+ const sampleValues = new Int32Array(maxTextures);
+ for (let i = 0; i < maxTextures; i++) {
+ sampleValues[i] = i;
+ }
+ batchSamplersUniformGroup = batchSamplersUniformGroupHash[maxTextures] = new UniformGroup({
+ uTextures: { value: sampleValues, type: `i32`, size: maxTextures }
+ }, { isStatic: true });
+ return batchSamplersUniformGroup;
+ }
+
+ "use strict";
+ var RendererType = /* @__PURE__ */ ((RendererType2) => {
+ RendererType2[RendererType2["WEBGL"] = 1] = "WEBGL";
+ RendererType2[RendererType2["WEBGPU"] = 2] = "WEBGPU";
+ RendererType2[RendererType2["BOTH"] = 3] = "BOTH";
+ return RendererType2;
+ })(RendererType || {});
+
+ "use strict";
+ var __defProp$Z = Object.defineProperty;
+ var __getOwnPropSymbols$_ = Object.getOwnPropertySymbols;
+ var __hasOwnProp$_ = Object.prototype.hasOwnProperty;
+ var __propIsEnum$_ = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$Z = (obj, key, value) => key in obj ? __defProp$Z(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$Z = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$_.call(b, prop))
+ __defNormalProp$Z(a, prop, b[prop]);
+ if (__getOwnPropSymbols$_)
+ for (var prop of __getOwnPropSymbols$_(b)) {
+ if (__propIsEnum$_.call(b, prop))
+ __defNormalProp$Z(a, prop, b[prop]);
+ }
+ return a;
+ };
+ var __objRest$l = (source, exclude) => {
+ var target = {};
+ for (var prop in source)
+ if (__hasOwnProp$_.call(source, prop) && exclude.indexOf(prop) < 0)
+ target[prop] = source[prop];
+ if (source != null && __getOwnPropSymbols$_)
+ for (var prop of __getOwnPropSymbols$_(source)) {
+ if (exclude.indexOf(prop) < 0 && __propIsEnum$_.call(source, prop))
+ target[prop] = source[prop];
+ }
+ return target;
+ };
+ class Shader extends EventEmitter {
+ constructor(options) {
+ super();
+ /** A unique identifier for the shader */
+ this.uid = uid$1("shader");
+ /**
+ * A record of the uniform groups and resources used by the shader.
+ * This is used by WebGL renderer to sync uniform data.
+ * @internal
+ */
+ this._uniformBindMap = /* @__PURE__ */ Object.create(null);
+ this._ownedBindGroups = [];
+ let {
+ gpuProgram,
+ glProgram,
+ groups,
+ resources,
+ compatibleRenderers,
+ groupMap
+ } = options;
+ this.gpuProgram = gpuProgram;
+ this.glProgram = glProgram;
+ if (compatibleRenderers === void 0) {
+ compatibleRenderers = 0;
+ if (gpuProgram)
+ compatibleRenderers |= RendererType.WEBGPU;
+ if (glProgram)
+ compatibleRenderers |= RendererType.WEBGL;
+ }
+ this.compatibleRenderers = compatibleRenderers;
+ const nameHash = {};
+ if (!resources && !groups) {
+ resources = {};
+ }
+ if (resources && groups) {
+ throw new Error("[Shader] Cannot have both resources and groups");
+ } else if (!gpuProgram && groups && !groupMap) {
+ throw new Error("[Shader] No group map or WebGPU shader provided - consider using resources instead.");
+ } else if (!gpuProgram && groups && groupMap) {
+ for (const i in groupMap) {
+ for (const j in groupMap[i]) {
+ const uniformName = groupMap[i][j];
+ nameHash[uniformName] = {
+ group: i,
+ binding: j,
+ name: uniformName
+ };
+ }
+ }
+ } else if (gpuProgram && groups && !groupMap) {
+ const groupData = gpuProgram.structsAndGroups.groups;
+ groupMap = {};
+ groupData.forEach((data) => {
+ groupMap[data.group] = groupMap[data.group] || {};
+ groupMap[data.group][data.binding] = data.name;
+ nameHash[data.name] = data;
+ });
+ } else if (resources) {
+ groups = {};
+ groupMap = {};
+ if (gpuProgram) {
+ const groupData = gpuProgram.structsAndGroups.groups;
+ groupData.forEach((data) => {
+ groupMap[data.group] = groupMap[data.group] || {};
+ groupMap[data.group][data.binding] = data.name;
+ nameHash[data.name] = data;
+ });
+ }
+ let bindTick = 0;
+ for (const i in resources) {
+ if (nameHash[i])
+ continue;
+ if (!groups[99]) {
+ groups[99] = new BindGroup();
+ this._ownedBindGroups.push(groups[99]);
+ }
+ nameHash[i] = { group: 99, binding: bindTick, name: i };
+ groupMap[99] = groupMap[99] || {};
+ groupMap[99][bindTick] = i;
+ bindTick++;
+ }
+ for (const i in resources) {
+ const name = i;
+ let value = resources[i];
+ if (!value.source && !value._resourceType) {
+ value = new UniformGroup(value);
+ }
+ const data = nameHash[name];
+ if (data) {
+ if (!groups[data.group]) {
+ groups[data.group] = new BindGroup();
+ this._ownedBindGroups.push(groups[data.group]);
+ }
+ groups[data.group].setResource(value, data.binding);
+ }
+ }
+ }
+ this.groups = groups;
+ this._uniformBindMap = groupMap;
+ this.resources = this._buildResourceAccessor(groups, nameHash);
+ }
+ /**
+ * Sometimes a resource group will be provided later (for example global uniforms)
+ * In such cases, this method can be used to let the shader know about the group.
+ * @param name - the name of the resource group
+ * @param groupIndex - the index of the group (should match the webGPU shader group location)
+ * @param bindIndex - the index of the bind point (should match the webGPU shader bind point)
+ */
+ addResource(name, groupIndex, bindIndex) {
+ var _a, _b;
+ (_a = this._uniformBindMap)[groupIndex] || (_a[groupIndex] = {});
+ (_b = this._uniformBindMap[groupIndex])[bindIndex] || (_b[bindIndex] = name);
+ if (!this.groups[groupIndex]) {
+ this.groups[groupIndex] = new BindGroup();
+ this._ownedBindGroups.push(this.groups[groupIndex]);
+ }
+ }
+ _buildResourceAccessor(groups, nameHash) {
+ const uniformsOut = {};
+ for (const i in nameHash) {
+ const data = nameHash[i];
+ Object.defineProperty(uniformsOut, data.name, {
+ get() {
+ return groups[data.group].getResource(data.binding);
+ },
+ set(value) {
+ groups[data.group].setResource(value, data.binding);
+ }
+ });
+ }
+ return uniformsOut;
+ }
+ /**
+ * Use to destroy the shader when its not longer needed.
+ * It will destroy the resources and remove listeners.
+ * @param destroyPrograms - if the programs should be destroyed as well.
+ * Make sure its not being used by other shaders!
+ */
+ destroy(destroyPrograms = false) {
+ var _a, _b;
+ this.emit("destroy", this);
+ if (destroyPrograms) {
+ (_a = this.gpuProgram) == null ? void 0 : _a.destroy();
+ (_b = this.glProgram) == null ? void 0 : _b.destroy();
+ }
+ this.gpuProgram = null;
+ this.glProgram = null;
+ this.removeAllListeners();
+ this._uniformBindMap = null;
+ this._ownedBindGroups.forEach((bindGroup) => {
+ bindGroup.destroy();
+ });
+ this._ownedBindGroups = null;
+ this.resources = null;
+ this.groups = null;
+ }
+ static from(options) {
+ const _a = options, { gpu, gl } = _a, rest = __objRest$l(_a, ["gpu", "gl"]);
+ let gpuProgram;
+ let glProgram;
+ if (gpu) {
+ gpuProgram = GpuProgram.from(gpu);
+ }
+ if (gl) {
+ glProgram = GlProgram.from(gl);
+ }
+ return new Shader(__spreadValues$Z({
+ gpuProgram,
+ glProgram
+ }, rest));
+ }
+ }
+
+ "use strict";
+ class DefaultShader extends Shader {
+ constructor(maxTextures) {
+ const glProgram = compileHighShaderGlProgram({
+ name: "batch",
+ bits: [
+ colorBitGl,
+ generateTextureBatchBitGl(maxTextures),
+ roundPixelsBitGl
+ ]
+ });
+ const gpuProgram = compileHighShaderGpuProgram({
+ name: "batch",
+ bits: [
+ colorBit,
+ generateTextureBatchBit(maxTextures),
+ roundPixelsBit
+ ]
+ });
+ super({
+ glProgram,
+ gpuProgram,
+ resources: {
+ batchSamplers: getBatchSamplersUniformGroup(maxTextures)
+ }
+ });
+ }
+ }
+
+ "use strict";
+ let defaultShader = null;
+ const _DefaultBatcher = class _DefaultBatcher extends Batcher {
+ constructor(options) {
+ super(options);
+ this.geometry = new BatchGeometry();
+ this.name = _DefaultBatcher.extension.name;
+ /** The size of one attribute. 1 = 32 bit. x, y, u, v, color, textureIdAndRound -> total = 6 */
+ this.vertexSize = 6;
+ defaultShader != null ? defaultShader : defaultShader = new DefaultShader(options.maxTextures);
+ this.shader = defaultShader;
+ }
+ /**
+ * Packs the attributes of a DefaultBatchableMeshElement into the provided views.
+ * @param element - The DefaultBatchableMeshElement to pack.
+ * @param float32View - The Float32Array view to pack into.
+ * @param uint32View - The Uint32Array view to pack into.
+ * @param index - The starting index in the views.
+ * @param textureId - The texture ID to use.
+ */
+ packAttributes(element, float32View, uint32View, index, textureId) {
+ const textureIdAndRound = textureId << 16 | element.roundPixels & 65535;
+ const wt = element.transform;
+ const a = wt.a;
+ const b = wt.b;
+ const c = wt.c;
+ const d = wt.d;
+ const tx = wt.tx;
+ const ty = wt.ty;
+ const { positions, uvs } = element;
+ const argb = element.color;
+ const offset = element.attributeOffset;
+ const end = offset + element.attributeSize;
+ for (let i = offset; i < end; i++) {
+ const i2 = i * 2;
+ const x = positions[i2];
+ const y = positions[i2 + 1];
+ float32View[index++] = a * x + c * y + tx;
+ float32View[index++] = d * y + b * x + ty;
+ float32View[index++] = uvs[i2];
+ float32View[index++] = uvs[i2 + 1];
+ uint32View[index++] = argb;
+ uint32View[index++] = textureIdAndRound;
+ }
+ }
+ /**
+ * Packs the attributes of a DefaultBatchableQuadElement into the provided views.
+ * @param element - The DefaultBatchableQuadElement to pack.
+ * @param float32View - The Float32Array view to pack into.
+ * @param uint32View - The Uint32Array view to pack into.
+ * @param index - The starting index in the views.
+ * @param textureId - The texture ID to use.
+ */
+ packQuadAttributes(element, float32View, uint32View, index, textureId) {
+ const texture = element.texture;
+ const wt = element.transform;
+ const a = wt.a;
+ const b = wt.b;
+ const c = wt.c;
+ const d = wt.d;
+ const tx = wt.tx;
+ const ty = wt.ty;
+ const bounds = element.bounds;
+ const w0 = bounds.maxX;
+ const w1 = bounds.minX;
+ const h0 = bounds.maxY;
+ const h1 = bounds.minY;
+ const uvs = texture.uvs;
+ const argb = element.color;
+ const textureIdAndRound = textureId << 16 | element.roundPixels & 65535;
+ float32View[index + 0] = a * w1 + c * h1 + tx;
+ float32View[index + 1] = d * h1 + b * w1 + ty;
+ float32View[index + 2] = uvs.x0;
+ float32View[index + 3] = uvs.y0;
+ uint32View[index + 4] = argb;
+ uint32View[index + 5] = textureIdAndRound;
+ float32View[index + 6] = a * w0 + c * h1 + tx;
+ float32View[index + 7] = d * h1 + b * w0 + ty;
+ float32View[index + 8] = uvs.x1;
+ float32View[index + 9] = uvs.y1;
+ uint32View[index + 10] = argb;
+ uint32View[index + 11] = textureIdAndRound;
+ float32View[index + 12] = a * w0 + c * h0 + tx;
+ float32View[index + 13] = d * h0 + b * w0 + ty;
+ float32View[index + 14] = uvs.x2;
+ float32View[index + 15] = uvs.y2;
+ uint32View[index + 16] = argb;
+ uint32View[index + 17] = textureIdAndRound;
+ float32View[index + 18] = a * w1 + c * h0 + tx;
+ float32View[index + 19] = d * h0 + b * w1 + ty;
+ float32View[index + 20] = uvs.x3;
+ float32View[index + 21] = uvs.y3;
+ uint32View[index + 22] = argb;
+ uint32View[index + 23] = textureIdAndRound;
+ }
+ };
+ /** @ignore */
+ _DefaultBatcher.extension = {
+ type: [
+ ExtensionType.Batcher
+ ],
+ name: "default"
+ };
+ let DefaultBatcher = _DefaultBatcher;
+
+ "use strict";
+ function buildUvs(vertices, verticesStride, verticesOffset, uvs, uvsOffset, uvsStride, size, matrix = null) {
+ let index = 0;
+ verticesOffset *= verticesStride;
+ uvsOffset *= uvsStride;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+ while (index < size) {
+ const x = vertices[verticesOffset];
+ const y = vertices[verticesOffset + 1];
+ uvs[uvsOffset] = a * x + c * y + tx;
+ uvs[uvsOffset + 1] = b * x + d * y + ty;
+ uvsOffset += uvsStride;
+ verticesOffset += verticesStride;
+ index++;
+ }
+ }
+ function buildSimpleUvs(uvs, uvsOffset, uvsStride, size) {
+ let index = 0;
+ uvsOffset *= uvsStride;
+ while (index < size) {
+ uvs[uvsOffset] = 0;
+ uvs[uvsOffset + 1] = 0;
+ uvsOffset += uvsStride;
+ index++;
+ }
+ }
+
+ "use strict";
+ function transformVertices(vertices, m, offset, stride, size) {
+ const a = m.a;
+ const b = m.b;
+ const c = m.c;
+ const d = m.d;
+ const tx = m.tx;
+ const ty = m.ty;
+ offset || (offset = 0);
+ stride || (stride = 2);
+ size || (size = vertices.length / stride - offset);
+ let index = offset * stride;
+ for (let i = 0; i < size; i++) {
+ const x = vertices[index];
+ const y = vertices[index + 1];
+ vertices[index] = a * x + c * y + tx;
+ vertices[index + 1] = b * x + d * y + ty;
+ index += stride;
+ }
+ }
+
+ "use strict";
+ const identityMatrix = new Matrix();
+ class BatchableGraphics {
+ constructor() {
+ this.packAsQuad = false;
+ this.batcherName = "default";
+ this.topology = "triangle-list";
+ this.applyTransform = true;
+ this.roundPixels = 0;
+ this._batcher = null;
+ this._batch = null;
+ }
+ get uvs() {
+ return this.geometryData.uvs;
+ }
+ get positions() {
+ return this.geometryData.vertices;
+ }
+ get indices() {
+ return this.geometryData.indices;
+ }
+ get blendMode() {
+ if (this.renderable && this.applyTransform) {
+ return this.renderable.groupBlendMode;
+ }
+ return "normal";
+ }
+ get color() {
+ const rgb = this.baseColor;
+ const bgr = rgb >> 16 | rgb & 65280 | (rgb & 255) << 16;
+ const renderable = this.renderable;
+ if (renderable) {
+ return multiplyHexColors(bgr, renderable.groupColor) + (this.alpha * renderable.groupAlpha * 255 << 24);
+ }
+ return bgr + (this.alpha * 255 << 24);
+ }
+ get transform() {
+ var _a;
+ return ((_a = this.renderable) == null ? void 0 : _a.groupTransform) || identityMatrix;
+ }
+ copyTo(gpuBuffer) {
+ gpuBuffer.indexOffset = this.indexOffset;
+ gpuBuffer.indexSize = this.indexSize;
+ gpuBuffer.attributeOffset = this.attributeOffset;
+ gpuBuffer.attributeSize = this.attributeSize;
+ gpuBuffer.baseColor = this.baseColor;
+ gpuBuffer.alpha = this.alpha;
+ gpuBuffer.texture = this.texture;
+ gpuBuffer.geometryData = this.geometryData;
+ gpuBuffer.topology = this.topology;
+ }
+ reset() {
+ this.applyTransform = true;
+ this.renderable = null;
+ this.topology = "triangle-list";
+ }
+ destroy() {
+ this.renderable = null;
+ this.texture = null;
+ this.geometryData = null;
+ this._batcher.destroy();
+ this._batcher = null;
+ this._batch.destroy();
+ this._batch = null;
+ }
+ }
+
+ "use strict";
+ var __defProp$Y = Object.defineProperty;
+ var __defProps$o = Object.defineProperties;
+ var __getOwnPropDescs$o = Object.getOwnPropertyDescriptors;
+ var __getOwnPropSymbols$Z = Object.getOwnPropertySymbols;
+ var __hasOwnProp$Z = Object.prototype.hasOwnProperty;
+ var __propIsEnum$Z = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$Y = (obj, key, value) => key in obj ? __defProp$Y(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$Y = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$Z.call(b, prop))
+ __defNormalProp$Y(a, prop, b[prop]);
+ if (__getOwnPropSymbols$Z)
+ for (var prop of __getOwnPropSymbols$Z(b)) {
+ if (__propIsEnum$Z.call(b, prop))
+ __defNormalProp$Y(a, prop, b[prop]);
+ }
+ return a;
+ };
+ var __spreadProps$o = (a, b) => __defProps$o(a, __getOwnPropDescs$o(b));
+ const buildCircle = {
+ extension: {
+ type: ExtensionType.ShapeBuilder,
+ name: "circle"
+ },
+ build(shape, points) {
+ let x;
+ let y;
+ let dx;
+ let dy;
+ let rx;
+ let ry;
+ if (shape.type === "circle") {
+ const circle = shape;
+ rx = ry = circle.radius;
+ if (rx <= 0) {
+ return false;
+ }
+ x = circle.x;
+ y = circle.y;
+ dx = dy = 0;
+ } else if (shape.type === "ellipse") {
+ const ellipse = shape;
+ rx = ellipse.halfWidth;
+ ry = ellipse.halfHeight;
+ if (rx <= 0 || ry <= 0) {
+ return false;
+ }
+ x = ellipse.x;
+ y = ellipse.y;
+ dx = dy = 0;
+ } else {
+ const roundedRect = shape;
+ const halfWidth = roundedRect.width / 2;
+ const halfHeight = roundedRect.height / 2;
+ x = roundedRect.x + halfWidth;
+ y = roundedRect.y + halfHeight;
+ rx = ry = Math.max(0, Math.min(roundedRect.radius, Math.min(halfWidth, halfHeight)));
+ dx = halfWidth - rx;
+ dy = halfHeight - ry;
+ }
+ if (dx < 0 || dy < 0) {
+ return false;
+ }
+ const n = Math.ceil(2.3 * Math.sqrt(rx + ry));
+ const m = n * 8 + (dx ? 4 : 0) + (dy ? 4 : 0);
+ if (m === 0) {
+ return false;
+ }
+ if (n === 0) {
+ points[0] = points[6] = x + dx;
+ points[1] = points[3] = y + dy;
+ points[2] = points[4] = x - dx;
+ points[5] = points[7] = y - dy;
+ return true;
+ }
+ let j1 = 0;
+ let j2 = n * 4 + (dx ? 2 : 0) + 2;
+ let j3 = j2;
+ let j4 = m;
+ let x0 = dx + rx;
+ let y0 = dy;
+ let x1 = x + x0;
+ let x2 = x - x0;
+ let y1 = y + y0;
+ points[j1++] = x1;
+ points[j1++] = y1;
+ points[--j2] = y1;
+ points[--j2] = x2;
+ if (dy) {
+ const y22 = y - y0;
+ points[j3++] = x2;
+ points[j3++] = y22;
+ points[--j4] = y22;
+ points[--j4] = x1;
+ }
+ for (let i = 1; i < n; i++) {
+ const a = Math.PI / 2 * (i / n);
+ const x02 = dx + Math.cos(a) * rx;
+ const y02 = dy + Math.sin(a) * ry;
+ const x12 = x + x02;
+ const x22 = x - x02;
+ const y12 = y + y02;
+ const y22 = y - y02;
+ points[j1++] = x12;
+ points[j1++] = y12;
+ points[--j2] = y12;
+ points[--j2] = x22;
+ points[j3++] = x22;
+ points[j3++] = y22;
+ points[--j4] = y22;
+ points[--j4] = x12;
+ }
+ x0 = dx;
+ y0 = dy + ry;
+ x1 = x + x0;
+ x2 = x - x0;
+ y1 = y + y0;
+ const y2 = y - y0;
+ points[j1++] = x1;
+ points[j1++] = y1;
+ points[--j4] = y2;
+ points[--j4] = x1;
+ if (dx) {
+ points[j1++] = x2;
+ points[j1++] = y1;
+ points[--j4] = y2;
+ points[--j4] = x2;
+ }
+ return true;
+ },
+ triangulate(points, vertices, verticesStride, verticesOffset, indices, indicesOffset) {
+ if (points.length === 0) {
+ return;
+ }
+ let centerX = 0;
+ let centerY = 0;
+ for (let i = 0; i < points.length; i += 2) {
+ centerX += points[i];
+ centerY += points[i + 1];
+ }
+ centerX /= points.length / 2;
+ centerY /= points.length / 2;
+ let count = verticesOffset;
+ vertices[count * verticesStride] = centerX;
+ vertices[count * verticesStride + 1] = centerY;
+ const centerIndex = count++;
+ for (let i = 0; i < points.length; i += 2) {
+ vertices[count * verticesStride] = points[i];
+ vertices[count * verticesStride + 1] = points[i + 1];
+ if (i > 0) {
+ indices[indicesOffset++] = count;
+ indices[indicesOffset++] = centerIndex;
+ indices[indicesOffset++] = count - 1;
+ }
+ count++;
+ }
+ indices[indicesOffset++] = centerIndex + 1;
+ indices[indicesOffset++] = centerIndex;
+ indices[indicesOffset++] = count - 1;
+ }
+ };
+ const buildEllipse = __spreadProps$o(__spreadValues$Y({}, buildCircle), { extension: __spreadProps$o(__spreadValues$Y({}, buildCircle.extension), { name: "ellipse" }) });
+ const buildRoundedRectangle = __spreadProps$o(__spreadValues$Y({}, buildCircle), { extension: __spreadProps$o(__spreadValues$Y({}, buildCircle.extension), { name: "roundedRectangle" }) });
+
+ "use strict";
+ const closePointEps = 1e-4;
+ const curveEps = 1e-4;
+
+ "use strict";
+ function getOrientationOfPoints(points) {
+ const m = points.length;
+ if (m < 6) {
+ return 1;
+ }
+ let area = 0;
+ for (let i = 0, x1 = points[m - 2], y1 = points[m - 1]; i < m; i += 2) {
+ const x2 = points[i];
+ const y2 = points[i + 1];
+ area += (x2 - x1) * (y2 + y1);
+ x1 = x2;
+ y1 = y2;
+ }
+ if (area < 0) {
+ return -1;
+ }
+ return 1;
+ }
+
+ "use strict";
+ function square(x, y, nx, ny, innerWeight, outerWeight, clockwise, verts) {
+ const ix = x - nx * innerWeight;
+ const iy = y - ny * innerWeight;
+ const ox = x + nx * outerWeight;
+ const oy = y + ny * outerWeight;
+ let exx;
+ let eyy;
+ if (clockwise) {
+ exx = ny;
+ eyy = -nx;
+ } else {
+ exx = -ny;
+ eyy = nx;
+ }
+ const eix = ix + exx;
+ const eiy = iy + eyy;
+ const eox = ox + exx;
+ const eoy = oy + eyy;
+ verts.push(eix, eiy);
+ verts.push(eox, eoy);
+ return 2;
+ }
+ function round(cx, cy, sx, sy, ex, ey, verts, clockwise) {
+ const cx2p0x = sx - cx;
+ const cy2p0y = sy - cy;
+ let angle0 = Math.atan2(cx2p0x, cy2p0y);
+ let angle1 = Math.atan2(ex - cx, ey - cy);
+ if (clockwise && angle0 < angle1) {
+ angle0 += Math.PI * 2;
+ } else if (!clockwise && angle0 > angle1) {
+ angle1 += Math.PI * 2;
+ }
+ let startAngle = angle0;
+ const angleDiff = angle1 - angle0;
+ const absAngleDiff = Math.abs(angleDiff);
+ const radius = Math.sqrt(cx2p0x * cx2p0x + cy2p0y * cy2p0y);
+ const segCount = (15 * absAngleDiff * Math.sqrt(radius) / Math.PI >> 0) + 1;
+ const angleInc = angleDiff / segCount;
+ startAngle += angleInc;
+ if (clockwise) {
+ verts.push(cx, cy);
+ verts.push(sx, sy);
+ for (let i = 1, angle = startAngle; i < segCount; i++, angle += angleInc) {
+ verts.push(cx, cy);
+ verts.push(
+ cx + Math.sin(angle) * radius,
+ cy + Math.cos(angle) * radius
+ );
+ }
+ verts.push(cx, cy);
+ verts.push(ex, ey);
+ } else {
+ verts.push(sx, sy);
+ verts.push(cx, cy);
+ for (let i = 1, angle = startAngle; i < segCount; i++, angle += angleInc) {
+ verts.push(
+ cx + Math.sin(angle) * radius,
+ cy + Math.cos(angle) * radius
+ );
+ verts.push(cx, cy);
+ }
+ verts.push(ex, ey);
+ verts.push(cx, cy);
+ }
+ return segCount * 2;
+ }
+ function buildLine(points, lineStyle, flipAlignment, closed, vertices, indices) {
+ const eps = closePointEps;
+ if (points.length === 0) {
+ return;
+ }
+ const style = lineStyle;
+ let alignment = style.alignment;
+ if (lineStyle.alignment !== 0.5) {
+ let orientation = getOrientationOfPoints(points);
+ if (flipAlignment)
+ orientation *= -1;
+ alignment = (alignment - 0.5) * orientation + 0.5;
+ }
+ const firstPoint = new Point(points[0], points[1]);
+ const lastPoint = new Point(points[points.length - 2], points[points.length - 1]);
+ const closedShape = closed;
+ const closedPath = Math.abs(firstPoint.x - lastPoint.x) < eps && Math.abs(firstPoint.y - lastPoint.y) < eps;
+ if (closedShape) {
+ points = points.slice();
+ if (closedPath) {
+ points.pop();
+ points.pop();
+ lastPoint.set(points[points.length - 2], points[points.length - 1]);
+ }
+ const midPointX = (firstPoint.x + lastPoint.x) * 0.5;
+ const midPointY = (lastPoint.y + firstPoint.y) * 0.5;
+ points.unshift(midPointX, midPointY);
+ points.push(midPointX, midPointY);
+ }
+ const verts = vertices;
+ const length = points.length / 2;
+ let indexCount = points.length;
+ const indexStart = verts.length / 2;
+ const width = style.width / 2;
+ const widthSquared = width * width;
+ const miterLimitSquared = style.miterLimit * style.miterLimit;
+ let x0 = points[0];
+ let y0 = points[1];
+ let x1 = points[2];
+ let y1 = points[3];
+ let x2 = 0;
+ let y2 = 0;
+ let perpX = -(y0 - y1);
+ let perpY = x0 - x1;
+ let perp1x = 0;
+ let perp1y = 0;
+ let dist = Math.sqrt(perpX * perpX + perpY * perpY);
+ perpX /= dist;
+ perpY /= dist;
+ perpX *= width;
+ perpY *= width;
+ const ratio = alignment;
+ const innerWeight = (1 - ratio) * 2;
+ const outerWeight = ratio * 2;
+ if (!closedShape) {
+ if (style.cap === "round") {
+ indexCount += round(
+ x0 - perpX * (innerWeight - outerWeight) * 0.5,
+ y0 - perpY * (innerWeight - outerWeight) * 0.5,
+ x0 - perpX * innerWeight,
+ y0 - perpY * innerWeight,
+ x0 + perpX * outerWeight,
+ y0 + perpY * outerWeight,
+ verts,
+ true
+ ) + 2;
+ } else if (style.cap === "square") {
+ indexCount += square(x0, y0, perpX, perpY, innerWeight, outerWeight, true, verts);
+ }
+ }
+ verts.push(
+ x0 - perpX * innerWeight,
+ y0 - perpY * innerWeight
+ );
+ verts.push(
+ x0 + perpX * outerWeight,
+ y0 + perpY * outerWeight
+ );
+ for (let i = 1; i < length - 1; ++i) {
+ x0 = points[(i - 1) * 2];
+ y0 = points[(i - 1) * 2 + 1];
+ x1 = points[i * 2];
+ y1 = points[i * 2 + 1];
+ x2 = points[(i + 1) * 2];
+ y2 = points[(i + 1) * 2 + 1];
+ perpX = -(y0 - y1);
+ perpY = x0 - x1;
+ dist = Math.sqrt(perpX * perpX + perpY * perpY);
+ perpX /= dist;
+ perpY /= dist;
+ perpX *= width;
+ perpY *= width;
+ perp1x = -(y1 - y2);
+ perp1y = x1 - x2;
+ dist = Math.sqrt(perp1x * perp1x + perp1y * perp1y);
+ perp1x /= dist;
+ perp1y /= dist;
+ perp1x *= width;
+ perp1y *= width;
+ const dx0 = x1 - x0;
+ const dy0 = y0 - y1;
+ const dx1 = x1 - x2;
+ const dy1 = y2 - y1;
+ const dot = dx0 * dx1 + dy0 * dy1;
+ const cross = dy0 * dx1 - dy1 * dx0;
+ const clockwise = cross < 0;
+ if (Math.abs(cross) < 1e-3 * Math.abs(dot)) {
+ verts.push(
+ x1 - perpX * innerWeight,
+ y1 - perpY * innerWeight
+ );
+ verts.push(
+ x1 + perpX * outerWeight,
+ y1 + perpY * outerWeight
+ );
+ if (dot >= 0) {
+ if (style.join === "round") {
+ indexCount += round(
+ x1,
+ y1,
+ x1 - perpX * innerWeight,
+ y1 - perpY * innerWeight,
+ x1 - perp1x * innerWeight,
+ y1 - perp1y * innerWeight,
+ verts,
+ false
+ ) + 4;
+ } else {
+ indexCount += 2;
+ }
+ verts.push(
+ x1 - perp1x * outerWeight,
+ y1 - perp1y * outerWeight
+ );
+ verts.push(
+ x1 + perp1x * innerWeight,
+ y1 + perp1y * innerWeight
+ );
+ }
+ continue;
+ }
+ const c1 = (-perpX + x0) * (-perpY + y1) - (-perpX + x1) * (-perpY + y0);
+ const c2 = (-perp1x + x2) * (-perp1y + y1) - (-perp1x + x1) * (-perp1y + y2);
+ const px = (dx0 * c2 - dx1 * c1) / cross;
+ const py = (dy1 * c1 - dy0 * c2) / cross;
+ const pDist = (px - x1) * (px - x1) + (py - y1) * (py - y1);
+ const imx = x1 + (px - x1) * innerWeight;
+ const imy = y1 + (py - y1) * innerWeight;
+ const omx = x1 - (px - x1) * outerWeight;
+ const omy = y1 - (py - y1) * outerWeight;
+ const smallerInsideSegmentSq = Math.min(dx0 * dx0 + dy0 * dy0, dx1 * dx1 + dy1 * dy1);
+ const insideWeight = clockwise ? innerWeight : outerWeight;
+ const smallerInsideDiagonalSq = smallerInsideSegmentSq + insideWeight * insideWeight * widthSquared;
+ const insideMiterOk = pDist <= smallerInsideDiagonalSq;
+ if (insideMiterOk) {
+ if (style.join === "bevel" || pDist / widthSquared > miterLimitSquared) {
+ if (clockwise) {
+ verts.push(imx, imy);
+ verts.push(x1 + perpX * outerWeight, y1 + perpY * outerWeight);
+ verts.push(imx, imy);
+ verts.push(x1 + perp1x * outerWeight, y1 + perp1y * outerWeight);
+ } else {
+ verts.push(x1 - perpX * innerWeight, y1 - perpY * innerWeight);
+ verts.push(omx, omy);
+ verts.push(x1 - perp1x * innerWeight, y1 - perp1y * innerWeight);
+ verts.push(omx, omy);
+ }
+ indexCount += 2;
+ } else if (style.join === "round") {
+ if (clockwise) {
+ verts.push(imx, imy);
+ verts.push(x1 + perpX * outerWeight, y1 + perpY * outerWeight);
+ indexCount += round(
+ x1,
+ y1,
+ x1 + perpX * outerWeight,
+ y1 + perpY * outerWeight,
+ x1 + perp1x * outerWeight,
+ y1 + perp1y * outerWeight,
+ verts,
+ true
+ ) + 4;
+ verts.push(imx, imy);
+ verts.push(x1 + perp1x * outerWeight, y1 + perp1y * outerWeight);
+ } else {
+ verts.push(x1 - perpX * innerWeight, y1 - perpY * innerWeight);
+ verts.push(omx, omy);
+ indexCount += round(
+ x1,
+ y1,
+ x1 - perpX * innerWeight,
+ y1 - perpY * innerWeight,
+ x1 - perp1x * innerWeight,
+ y1 - perp1y * innerWeight,
+ verts,
+ false
+ ) + 4;
+ verts.push(x1 - perp1x * innerWeight, y1 - perp1y * innerWeight);
+ verts.push(omx, omy);
+ }
+ } else {
+ verts.push(imx, imy);
+ verts.push(omx, omy);
+ }
+ } else {
+ verts.push(x1 - perpX * innerWeight, y1 - perpY * innerWeight);
+ verts.push(x1 + perpX * outerWeight, y1 + perpY * outerWeight);
+ if (style.join === "round") {
+ if (clockwise) {
+ indexCount += round(
+ x1,
+ y1,
+ x1 + perpX * outerWeight,
+ y1 + perpY * outerWeight,
+ x1 + perp1x * outerWeight,
+ y1 + perp1y * outerWeight,
+ verts,
+ true
+ ) + 2;
+ } else {
+ indexCount += round(
+ x1,
+ y1,
+ x1 - perpX * innerWeight,
+ y1 - perpY * innerWeight,
+ x1 - perp1x * innerWeight,
+ y1 - perp1y * innerWeight,
+ verts,
+ false
+ ) + 2;
+ }
+ } else if (style.join === "miter" && pDist / widthSquared <= miterLimitSquared) {
+ if (clockwise) {
+ verts.push(omx, omy);
+ verts.push(omx, omy);
+ } else {
+ verts.push(imx, imy);
+ verts.push(imx, imy);
+ }
+ indexCount += 2;
+ }
+ verts.push(x1 - perp1x * innerWeight, y1 - perp1y * innerWeight);
+ verts.push(x1 + perp1x * outerWeight, y1 + perp1y * outerWeight);
+ indexCount += 2;
+ }
+ }
+ x0 = points[(length - 2) * 2];
+ y0 = points[(length - 2) * 2 + 1];
+ x1 = points[(length - 1) * 2];
+ y1 = points[(length - 1) * 2 + 1];
+ perpX = -(y0 - y1);
+ perpY = x0 - x1;
+ dist = Math.sqrt(perpX * perpX + perpY * perpY);
+ perpX /= dist;
+ perpY /= dist;
+ perpX *= width;
+ perpY *= width;
+ verts.push(x1 - perpX * innerWeight, y1 - perpY * innerWeight);
+ verts.push(x1 + perpX * outerWeight, y1 + perpY * outerWeight);
+ if (!closedShape) {
+ if (style.cap === "round") {
+ indexCount += round(
+ x1 - perpX * (innerWeight - outerWeight) * 0.5,
+ y1 - perpY * (innerWeight - outerWeight) * 0.5,
+ x1 - perpX * innerWeight,
+ y1 - perpY * innerWeight,
+ x1 + perpX * outerWeight,
+ y1 + perpY * outerWeight,
+ verts,
+ false
+ ) + 2;
+ } else if (style.cap === "square") {
+ indexCount += square(x1, y1, perpX, perpY, innerWeight, outerWeight, false, verts);
+ }
+ }
+ const eps2 = curveEps * curveEps;
+ for (let i = indexStart; i < indexCount + indexStart - 2; ++i) {
+ x0 = verts[i * 2];
+ y0 = verts[i * 2 + 1];
+ x1 = verts[(i + 1) * 2];
+ y1 = verts[(i + 1) * 2 + 1];
+ x2 = verts[(i + 2) * 2];
+ y2 = verts[(i + 2) * 2 + 1];
+ if (Math.abs(x0 * (y1 - y2) + x1 * (y2 - y0) + x2 * (y0 - y1)) < eps2) {
+ continue;
+ }
+ indices.push(i, i + 1, i + 2);
+ }
+ }
+
+ "use strict";
+ function buildPixelLine(points, closed, vertices, indices) {
+ const eps = closePointEps;
+ if (points.length === 0) {
+ return;
+ }
+ const fx = points[0];
+ const fy = points[1];
+ const lx = points[points.length - 2];
+ const ly = points[points.length - 1];
+ const closePath = closed || Math.abs(fx - lx) < eps && Math.abs(fy - ly) < eps;
+ const verts = vertices;
+ const length = points.length / 2;
+ const indexStart = verts.length / 2;
+ for (let i = 0; i < length; i++) {
+ verts.push(points[i * 2]);
+ verts.push(points[i * 2 + 1]);
+ }
+ for (let i = 0; i < length - 1; i++) {
+ indices.push(indexStart + i, indexStart + i + 1);
+ }
+ if (closePath) {
+ indices.push(indexStart + length - 1, indexStart);
+ }
+ }
+
+ function earcut$1(data, holeIndices, dim = 2) {
+
+ const hasHoles = holeIndices && holeIndices.length;
+ const outerLen = hasHoles ? holeIndices[0] * dim : data.length;
+ let outerNode = linkedList(data, 0, outerLen, dim, true);
+ const triangles = [];
+
+ if (!outerNode || outerNode.next === outerNode.prev) return triangles;
+
+ let minX, minY, invSize;
+
+ if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
+
+ // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
+ if (data.length > 80 * dim) {
+ minX = data[0];
+ minY = data[1];
+ let maxX = minX;
+ let maxY = minY;
+
+ for (let i = dim; i < outerLen; i += dim) {
+ const x = data[i];
+ const y = data[i + 1];
+ if (x < minX) minX = x;
+ if (y < minY) minY = y;
+ if (x > maxX) maxX = x;
+ if (y > maxY) maxY = y;
+ }
+
+ // minX, minY and invSize are later used to transform coords into integers for z-order calculation
+ invSize = Math.max(maxX - minX, maxY - minY);
+ invSize = invSize !== 0 ? 32767 / invSize : 0;
+ }
+
+ earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0);
+
+ return triangles;
+ }
+
+ // create a circular doubly linked list from polygon points in the specified winding order
+ function linkedList(data, start, end, dim, clockwise) {
+ let last;
+
+ if (clockwise === (signedArea(data, start, end, dim) > 0)) {
+ for (let i = start; i < end; i += dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last);
+ } else {
+ for (let i = end - dim; i >= start; i -= dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last);
+ }
+
+ if (last && equals(last, last.next)) {
+ removeNode(last);
+ last = last.next;
+ }
+
+ return last;
+ }
+
+ // eliminate colinear or duplicate points
+ function filterPoints(start, end) {
+ if (!start) return start;
+ if (!end) end = start;
+
+ let p = start,
+ again;
+ do {
+ again = false;
+
+ if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
+ removeNode(p);
+ p = end = p.prev;
+ if (p === p.next) break;
+ again = true;
+
+ } else {
+ p = p.next;
+ }
+ } while (again || p !== end);
+
+ return end;
+ }
+
+ // main ear slicing loop which triangulates a polygon (given as a linked list)
+ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
+ if (!ear) return;
+
+ // interlink polygon nodes in z-order
+ if (!pass && invSize) indexCurve(ear, minX, minY, invSize);
+
+ let stop = ear;
+
+ // iterate through ears, slicing them one by one
+ while (ear.prev !== ear.next) {
+ const prev = ear.prev;
+ const next = ear.next;
+
+ if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
+ triangles.push(prev.i, ear.i, next.i); // cut off the triangle
+
+ removeNode(ear);
+
+ // skipping the next vertex leads to less sliver triangles
+ ear = next.next;
+ stop = next.next;
+
+ continue;
+ }
+
+ ear = next;
+
+ // if we looped through the whole remaining polygon and can't find any more ears
+ if (ear === stop) {
+ // try filtering points and slicing again
+ if (!pass) {
+ earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
+
+ // if this didn't work, try curing all small self-intersections locally
+ } else if (pass === 1) {
+ ear = cureLocalIntersections(filterPoints(ear), triangles);
+ earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
+
+ // as a last resort, try splitting the remaining polygon into two
+ } else if (pass === 2) {
+ splitEarcut(ear, triangles, dim, minX, minY, invSize);
+ }
+
+ break;
+ }
+ }
+ }
+
+ // check whether a polygon node forms a valid ear with adjacent nodes
+ function isEar(ear) {
+ const a = ear.prev,
+ b = ear,
+ c = ear.next;
+
+ if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
+
+ // now make sure we don't have other points inside the potential ear
+ const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
+
+ // triangle bbox
+ const x0 = Math.min(ax, bx, cx),
+ y0 = Math.min(ay, by, cy),
+ x1 = Math.max(ax, bx, cx),
+ y1 = Math.max(ay, by, cy);
+
+ let p = c.next;
+ while (p !== a) {
+ if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 &&
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) &&
+ area(p.prev, p, p.next) >= 0) return false;
+ p = p.next;
+ }
+
+ return true;
+ }
+
+ function isEarHashed(ear, minX, minY, invSize) {
+ const a = ear.prev,
+ b = ear,
+ c = ear.next;
+
+ if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
+
+ const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
+
+ // triangle bbox
+ const x0 = Math.min(ax, bx, cx),
+ y0 = Math.min(ay, by, cy),
+ x1 = Math.max(ax, bx, cx),
+ y1 = Math.max(ay, by, cy);
+
+ // z-order range for the current triangle bbox;
+ const minZ = zOrder(x0, y0, minX, minY, invSize),
+ maxZ = zOrder(x1, y1, minX, minY, invSize);
+
+ let p = ear.prevZ,
+ n = ear.nextZ;
+
+ // look for points inside the triangle in both directions
+ while (p && p.z >= minZ && n && n.z <= maxZ) {
+ if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
+ p = p.prevZ;
+
+ if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
+ n = n.nextZ;
+ }
+
+ // look for remaining points in decreasing z-order
+ while (p && p.z >= minZ) {
+ if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
+ p = p.prevZ;
+ }
+
+ // look for remaining points in increasing z-order
+ while (n && n.z <= maxZ) {
+ if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
+ pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
+ n = n.nextZ;
+ }
+
+ return true;
+ }
+
+ // go through all polygon nodes and cure small local self-intersections
+ function cureLocalIntersections(start, triangles) {
+ let p = start;
+ do {
+ const a = p.prev,
+ b = p.next.next;
+
+ if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
+
+ triangles.push(a.i, p.i, b.i);
+
+ // remove two nodes involved
+ removeNode(p);
+ removeNode(p.next);
+
+ p = start = b;
+ }
+ p = p.next;
+ } while (p !== start);
+
+ return filterPoints(p);
+ }
+
+ // try splitting polygon into two and triangulate them independently
+ function splitEarcut(start, triangles, dim, minX, minY, invSize) {
+ // look for a valid diagonal that divides the polygon into two
+ let a = start;
+ do {
+ let b = a.next.next;
+ while (b !== a.prev) {
+ if (a.i !== b.i && isValidDiagonal(a, b)) {
+ // split the polygon in two by the diagonal
+ let c = splitPolygon(a, b);
+
+ // filter colinear points around the cuts
+ a = filterPoints(a, a.next);
+ c = filterPoints(c, c.next);
+
+ // run earcut on each half
+ earcutLinked(a, triangles, dim, minX, minY, invSize, 0);
+ earcutLinked(c, triangles, dim, minX, minY, invSize, 0);
+ return;
+ }
+ b = b.next;
+ }
+ a = a.next;
+ } while (a !== start);
+ }
+
+ // link every hole into the outer loop, producing a single-ring polygon without holes
+ function eliminateHoles(data, holeIndices, outerNode, dim) {
+ const queue = [];
+
+ for (let i = 0, len = holeIndices.length; i < len; i++) {
+ const start = holeIndices[i] * dim;
+ const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
+ const list = linkedList(data, start, end, dim, false);
+ if (list === list.next) list.steiner = true;
+ queue.push(getLeftmost(list));
+ }
+
+ queue.sort(compareXYSlope);
+
+ // process holes from left to right
+ for (let i = 0; i < queue.length; i++) {
+ outerNode = eliminateHole(queue[i], outerNode);
+ }
+
+ return outerNode;
+ }
+
+ function compareXYSlope(a, b) {
+ let result = a.x - b.x;
+ // when the left-most point of 2 holes meet at a vertex, sort the holes counterclockwise so that when we find
+ // the bridge to the outer shell is always the point that they meet at.
+ if (result === 0) {
+ result = a.y - b.y;
+ if (result === 0) {
+ const aSlope = (a.next.y - a.y) / (a.next.x - a.x);
+ const bSlope = (b.next.y - b.y) / (b.next.x - b.x);
+ result = aSlope - bSlope;
+ }
+ }
+ return result;
+ }
+
+ // find a bridge between vertices that connects hole with an outer ring and link it
+ function eliminateHole(hole, outerNode) {
+ const bridge = findHoleBridge(hole, outerNode);
+ if (!bridge) {
+ return outerNode;
+ }
+
+ const bridgeReverse = splitPolygon(bridge, hole);
+
+ // filter collinear points around the cuts
+ filterPoints(bridgeReverse, bridgeReverse.next);
+ return filterPoints(bridge, bridge.next);
+ }
+
+ // David Eberly's algorithm for finding a bridge between hole and outer polygon
+ function findHoleBridge(hole, outerNode) {
+ let p = outerNode;
+ const hx = hole.x;
+ const hy = hole.y;
+ let qx = -Infinity;
+ let m;
+
+ // find a segment intersected by a ray from the hole's leftmost point to the left;
+ // segment's endpoint with lesser x will be potential connection point
+ // unless they intersect at a vertex, then choose the vertex
+ if (equals(hole, p)) return p;
+ do {
+ if (equals(hole, p.next)) return p.next;
+ else if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
+ const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
+ if (x <= hx && x > qx) {
+ qx = x;
+ m = p.x < p.next.x ? p : p.next;
+ if (x === hx) return m; // hole touches outer segment; pick leftmost endpoint
+ }
+ }
+ p = p.next;
+ } while (p !== outerNode);
+
+ if (!m) return null;
+
+ // look for points inside the triangle of hole point, segment intersection and endpoint;
+ // if there are no points found, we have a valid connection;
+ // otherwise choose the point of the minimum angle with the ray as connection point
+
+ const stop = m;
+ const mx = m.x;
+ const my = m.y;
+ let tanMin = Infinity;
+
+ p = m;
+
+ do {
+ if (hx >= p.x && p.x >= mx && hx !== p.x &&
+ pointInTriangle$1(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
+
+ const tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
+
+ if (locallyInside(p, hole) &&
+ (tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) {
+ m = p;
+ tanMin = tan;
+ }
+ }
+
+ p = p.next;
+ } while (p !== stop);
+
+ return m;
+ }
+
+ // whether sector in vertex m contains sector in vertex p in the same coordinates
+ function sectorContainsSector(m, p) {
+ return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0;
+ }
+
+ // interlink polygon nodes in z-order
+ function indexCurve(start, minX, minY, invSize) {
+ let p = start;
+ do {
+ if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize);
+ p.prevZ = p.prev;
+ p.nextZ = p.next;
+ p = p.next;
+ } while (p !== start);
+
+ p.prevZ.nextZ = null;
+ p.prevZ = null;
+
+ sortLinked(p);
+ }
+
+ // Simon Tatham's linked list merge sort algorithm
+ // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
+ function sortLinked(list) {
+ let numMerges;
+ let inSize = 1;
+
+ do {
+ let p = list;
+ let e;
+ list = null;
+ let tail = null;
+ numMerges = 0;
+
+ while (p) {
+ numMerges++;
+ let q = p;
+ let pSize = 0;
+ for (let i = 0; i < inSize; i++) {
+ pSize++;
+ q = q.nextZ;
+ if (!q) break;
+ }
+ let qSize = inSize;
+
+ while (pSize > 0 || (qSize > 0 && q)) {
+
+ if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
+ e = p;
+ p = p.nextZ;
+ pSize--;
+ } else {
+ e = q;
+ q = q.nextZ;
+ qSize--;
+ }
+
+ if (tail) tail.nextZ = e;
+ else list = e;
+
+ e.prevZ = tail;
+ tail = e;
+ }
+
+ p = q;
+ }
+
+ tail.nextZ = null;
+ inSize *= 2;
+
+ } while (numMerges > 1);
+
+ return list;
+ }
+
+ // z-order of a point given coords and inverse of the longer side of data bbox
+ function zOrder(x, y, minX, minY, invSize) {
+ // coords are transformed into non-negative 15-bit integer range
+ x = (x - minX) * invSize | 0;
+ y = (y - minY) * invSize | 0;
+
+ x = (x | (x << 8)) & 0x00FF00FF;
+ x = (x | (x << 4)) & 0x0F0F0F0F;
+ x = (x | (x << 2)) & 0x33333333;
+ x = (x | (x << 1)) & 0x55555555;
+
+ y = (y | (y << 8)) & 0x00FF00FF;
+ y = (y | (y << 4)) & 0x0F0F0F0F;
+ y = (y | (y << 2)) & 0x33333333;
+ y = (y | (y << 1)) & 0x55555555;
+
+ return x | (y << 1);
+ }
+
+ // find the leftmost node of a polygon ring
+ function getLeftmost(start) {
+ let p = start,
+ leftmost = start;
+ do {
+ if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p;
+ p = p.next;
+ } while (p !== start);
+
+ return leftmost;
+ }
+
+ // check if a point lies within a convex triangle
+ function pointInTriangle$1(ax, ay, bx, by, cx, cy, px, py) {
+ return (cx - px) * (ay - py) >= (ax - px) * (cy - py) &&
+ (ax - px) * (by - py) >= (bx - px) * (ay - py) &&
+ (bx - px) * (cy - py) >= (cx - px) * (by - py);
+ }
+
+ // check if a point lies within a convex triangle but false if its equal to the first point of the triangle
+ function pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, px, py) {
+ return !(ax === px && ay === py) && pointInTriangle$1(ax, ay, bx, by, cx, cy, px, py);
+ }
+
+ // check if a diagonal between two polygon nodes is valid (lies in polygon interior)
+ function isValidDiagonal(a, b) {
+ return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // doesn't intersect other edges
+ (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
+ (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors
+ equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case
+ }
+
+ // signed area of a triangle
+ function area(p, q, r) {
+ return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
+ }
+
+ // check if two points are equal
+ function equals(p1, p2) {
+ return p1.x === p2.x && p1.y === p2.y;
+ }
+
+ // check if two segments intersect
+ function intersects(p1, q1, p2, q2) {
+ const o1 = sign(area(p1, q1, p2));
+ const o2 = sign(area(p1, q1, q2));
+ const o3 = sign(area(p2, q2, p1));
+ const o4 = sign(area(p2, q2, q1));
+
+ if (o1 !== o2 && o3 !== o4) return true; // general case
+
+ if (o1 === 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1
+ if (o2 === 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1
+ if (o3 === 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2
+ if (o4 === 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2
+
+ return false;
+ }
+
+ // for collinear points p, q, r, check if point q lies on segment pr
+ function onSegment(p, q, r) {
+ return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
+ }
+
+ function sign(num) {
+ return num > 0 ? 1 : num < 0 ? -1 : 0;
+ }
+
+ // check if a polygon diagonal intersects any polygon segments
+ function intersectsPolygon(a, b) {
+ let p = a;
+ do {
+ if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
+ intersects(p, p.next, a, b)) return true;
+ p = p.next;
+ } while (p !== a);
+
+ return false;
+ }
+
+ // check if a polygon diagonal is locally inside the polygon
+ function locallyInside(a, b) {
+ return area(a.prev, a, a.next) < 0 ?
+ area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 :
+ area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
+ }
+
+ // check if the middle point of a polygon diagonal is inside the polygon
+ function middleInside(a, b) {
+ let p = a;
+ let inside = false;
+ const px = (a.x + b.x) / 2;
+ const py = (a.y + b.y) / 2;
+ do {
+ if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
+ (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
+ inside = !inside;
+ p = p.next;
+ } while (p !== a);
+
+ return inside;
+ }
+
+ // link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
+ // if one belongs to the outer ring and another to a hole, it merges it into a single ring
+ function splitPolygon(a, b) {
+ const a2 = createNode(a.i, a.x, a.y),
+ b2 = createNode(b.i, b.x, b.y),
+ an = a.next,
+ bp = b.prev;
+
+ a.next = b;
+ b.prev = a;
+
+ a2.next = an;
+ an.prev = a2;
+
+ b2.next = a2;
+ a2.prev = b2;
+
+ bp.next = b2;
+ b2.prev = bp;
+
+ return b2;
+ }
+
+ // create a node and optionally link it with previous one (in a circular doubly linked list)
+ function insertNode(i, x, y, last) {
+ const p = createNode(i, x, y);
+
+ if (!last) {
+ p.prev = p;
+ p.next = p;
+
+ } else {
+ p.next = last.next;
+ p.prev = last;
+ last.next.prev = p;
+ last.next = p;
+ }
+ return p;
+ }
+
+ function removeNode(p) {
+ p.next.prev = p.prev;
+ p.prev.next = p.next;
+
+ if (p.prevZ) p.prevZ.nextZ = p.nextZ;
+ if (p.nextZ) p.nextZ.prevZ = p.prevZ;
+ }
+
+ function createNode(i, x, y) {
+ return {
+ i, // vertex index in coordinates array
+ x, y, // vertex coordinates
+ prev: null, // previous and next vertex nodes in a polygon ring
+ next: null,
+ z: 0, // z-order curve value
+ prevZ: null, // previous and next nodes in z-order
+ nextZ: null,
+ steiner: false // indicates whether this is a steiner point
+ };
+ }
+
+ // return a percentage difference between the polygon area and its triangulation area;
+ // used to verify correctness of triangulation
+ function deviation(data, holeIndices, dim, triangles) {
+ const hasHoles = holeIndices && holeIndices.length;
+ const outerLen = hasHoles ? holeIndices[0] * dim : data.length;
+
+ let polygonArea = Math.abs(signedArea(data, 0, outerLen, dim));
+ if (hasHoles) {
+ for (let i = 0, len = holeIndices.length; i < len; i++) {
+ const start = holeIndices[i] * dim;
+ const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
+ polygonArea -= Math.abs(signedArea(data, start, end, dim));
+ }
+ }
+
+ let trianglesArea = 0;
+ for (let i = 0; i < triangles.length; i += 3) {
+ const a = triangles[i] * dim;
+ const b = triangles[i + 1] * dim;
+ const c = triangles[i + 2] * dim;
+ trianglesArea += Math.abs(
+ (data[a] - data[c]) * (data[b + 1] - data[a + 1]) -
+ (data[a] - data[b]) * (data[c + 1] - data[a + 1]));
+ }
+
+ return polygonArea === 0 && trianglesArea === 0 ? 0 :
+ Math.abs((trianglesArea - polygonArea) / polygonArea);
+ }
+
+ function signedArea(data, start, end, dim) {
+ let sum = 0;
+ for (let i = start, j = end - dim; i < end; i += dim) {
+ sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
+ j = i;
+ }
+ return sum;
+ }
+
+ // turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
+ function flatten(data) {
+ const vertices = [];
+ const holes = [];
+ const dimensions = data[0][0].length;
+ let holeIndex = 0;
+ let prevLen = 0;
+
+ for (const ring of data) {
+ for (const p of ring) {
+ for (let d = 0; d < dimensions; d++) vertices.push(p[d]);
+ }
+ if (prevLen) {
+ holeIndex += prevLen;
+ holes.push(holeIndex);
+ }
+ prevLen = ring.length;
+ }
+ return {vertices, holes, dimensions};
+ }
+
+ "use strict";
+ const earcut = earcut$1.default || earcut$1;
+
+ "use strict";
+ function triangulateWithHoles(points, holes, vertices, verticesStride, verticesOffset, indices, indicesOffset) {
+ const triangles = earcut(points, holes, 2);
+ if (!triangles) {
+ return;
+ }
+ for (let i = 0; i < triangles.length; i += 3) {
+ indices[indicesOffset++] = triangles[i] + verticesOffset;
+ indices[indicesOffset++] = triangles[i + 1] + verticesOffset;
+ indices[indicesOffset++] = triangles[i + 2] + verticesOffset;
+ }
+ let index = verticesOffset * verticesStride;
+ for (let i = 0; i < points.length; i += 2) {
+ vertices[index] = points[i];
+ vertices[index + 1] = points[i + 1];
+ index += verticesStride;
+ }
+ }
+
+ "use strict";
+ const emptyArray = [];
+ const buildPolygon = {
+ extension: {
+ type: ExtensionType.ShapeBuilder,
+ name: "polygon"
+ },
+ build(shape, points) {
+ for (let i = 0; i < shape.points.length; i++) {
+ points[i] = shape.points[i];
+ }
+ return true;
+ },
+ triangulate(points, vertices, verticesStride, verticesOffset, indices, indicesOffset) {
+ triangulateWithHoles(points, emptyArray, vertices, verticesStride, verticesOffset, indices, indicesOffset);
+ }
+ };
+
+ "use strict";
+ const buildRectangle = {
+ extension: {
+ type: ExtensionType.ShapeBuilder,
+ name: "rectangle"
+ },
+ build(shape, points) {
+ const rectData = shape;
+ const x = rectData.x;
+ const y = rectData.y;
+ const width = rectData.width;
+ const height = rectData.height;
+ if (!(width > 0 && height > 0)) {
+ return false;
+ }
+ points[0] = x;
+ points[1] = y;
+ points[2] = x + width;
+ points[3] = y;
+ points[4] = x + width;
+ points[5] = y + height;
+ points[6] = x;
+ points[7] = y + height;
+ return true;
+ },
+ triangulate(points, vertices, verticesStride, verticesOffset, indices, indicesOffset) {
+ let count = 0;
+ verticesOffset *= verticesStride;
+ vertices[verticesOffset + count] = points[0];
+ vertices[verticesOffset + count + 1] = points[1];
+ count += verticesStride;
+ vertices[verticesOffset + count] = points[2];
+ vertices[verticesOffset + count + 1] = points[3];
+ count += verticesStride;
+ vertices[verticesOffset + count] = points[6];
+ vertices[verticesOffset + count + 1] = points[7];
+ count += verticesStride;
+ vertices[verticesOffset + count] = points[4];
+ vertices[verticesOffset + count + 1] = points[5];
+ count += verticesStride;
+ const verticesIndex = verticesOffset / verticesStride;
+ indices[indicesOffset++] = verticesIndex;
+ indices[indicesOffset++] = verticesIndex + 1;
+ indices[indicesOffset++] = verticesIndex + 2;
+ indices[indicesOffset++] = verticesIndex + 1;
+ indices[indicesOffset++] = verticesIndex + 3;
+ indices[indicesOffset++] = verticesIndex + 2;
+ }
+ };
+
+ "use strict";
+ const buildTriangle = {
+ extension: {
+ type: ExtensionType.ShapeBuilder,
+ name: "triangle"
+ },
+ build(shape, points) {
+ points[0] = shape.x;
+ points[1] = shape.y;
+ points[2] = shape.x2;
+ points[3] = shape.y2;
+ points[4] = shape.x3;
+ points[5] = shape.y3;
+ return true;
+ },
+ triangulate(points, vertices, verticesStride, verticesOffset, indices, indicesOffset) {
+ let count = 0;
+ verticesOffset *= verticesStride;
+ vertices[verticesOffset + count] = points[0];
+ vertices[verticesOffset + count + 1] = points[1];
+ count += verticesStride;
+ vertices[verticesOffset + count] = points[2];
+ vertices[verticesOffset + count + 1] = points[3];
+ count += verticesStride;
+ vertices[verticesOffset + count] = points[4];
+ vertices[verticesOffset + count + 1] = points[5];
+ const verticesIndex = verticesOffset / verticesStride;
+ indices[indicesOffset++] = verticesIndex;
+ indices[indicesOffset++] = verticesIndex + 1;
+ indices[indicesOffset++] = verticesIndex + 2;
+ }
+ };
+
+ "use strict";
+ var __defProp$X = Object.defineProperty;
+ var __getOwnPropSymbols$Y = Object.getOwnPropertySymbols;
+ var __hasOwnProp$Y = Object.prototype.hasOwnProperty;
+ var __propIsEnum$Y = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$X = (obj, key, value) => key in obj ? __defProp$X(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$X = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$Y.call(b, prop))
+ __defNormalProp$X(a, prop, b[prop]);
+ if (__getOwnPropSymbols$Y)
+ for (var prop of __getOwnPropSymbols$Y(b)) {
+ if (__propIsEnum$Y.call(b, prop))
+ __defNormalProp$X(a, prop, b[prop]);
+ }
+ return a;
+ };
+ const emptyColorStops = [{ offset: 0, color: "white" }, { offset: 1, color: "black" }];
+ const _FillGradient = class _FillGradient {
+ constructor(...args) {
+ /**
+ * Unique identifier for this gradient instance
+ * @internal
+ */
+ this.uid = uid$1("fillGradient");
+ /**
+ * Internal tick counter to track changes in the gradient.
+ * This is used to invalidate the gradient when the texture changes.
+ * @internal
+ */
+ this._tick = 0;
+ /** Type of gradient - currently only supports 'linear' */
+ this.type = "linear";
+ /** Array of color stops defining the gradient */
+ this.colorStops = [];
+ var _a;
+ let options = ensureGradientOptions(args);
+ const defaults = options.type === "radial" ? _FillGradient.defaultRadialOptions : _FillGradient.defaultLinearOptions;
+ options = __spreadValues$X(__spreadValues$X({}, defaults), definedProps(options));
+ this._textureSize = options.textureSize;
+ this._wrapMode = options.wrapMode;
+ if (options.type === "radial") {
+ this.center = options.center;
+ this.outerCenter = (_a = options.outerCenter) != null ? _a : this.center;
+ this.innerRadius = options.innerRadius;
+ this.outerRadius = options.outerRadius;
+ this.scale = options.scale;
+ this.rotation = options.rotation;
+ } else {
+ this.start = options.start;
+ this.end = options.end;
+ }
+ this.textureSpace = options.textureSpace;
+ this.type = options.type;
+ options.colorStops.forEach((stop) => {
+ this.addColorStop(stop.offset, stop.color);
+ });
+ }
+ /**
+ * Adds a color stop to the gradient
+ * @param offset - Position of the stop (0-1)
+ * @param color - Color of the stop
+ * @returns This gradient instance for chaining
+ */
+ addColorStop(offset, color) {
+ this.colorStops.push({ offset, color: Color.shared.setValue(color).toHexa() });
+ return this;
+ }
+ /**
+ * Builds the internal texture and transform for the gradient.
+ * Called automatically when the gradient is first used.
+ * @internal
+ */
+ buildLinearGradient() {
+ if (this.texture)
+ return;
+ let { x: x0, y: y0 } = this.start;
+ let { x: x1, y: y1 } = this.end;
+ let dx = x1 - x0;
+ let dy = y1 - y0;
+ const flip = dx < 0 || dy < 0;
+ if (this._wrapMode === "clamp-to-edge") {
+ if (dx < 0) {
+ const temp = x0;
+ x0 = x1;
+ x1 = temp;
+ dx *= -1;
+ }
+ if (dy < 0) {
+ const temp = y0;
+ y0 = y1;
+ y1 = temp;
+ dy *= -1;
+ }
+ }
+ const colorStops = this.colorStops.length ? this.colorStops : emptyColorStops;
+ const defaultSize = this._textureSize;
+ const { canvas, context } = getCanvas(defaultSize, 1);
+ const gradient = !flip ? context.createLinearGradient(0, 0, this._textureSize, 0) : context.createLinearGradient(this._textureSize, 0, 0, 0);
+ addColorStops(gradient, colorStops);
+ context.fillStyle = gradient;
+ context.fillRect(0, 0, defaultSize, 1);
+ this.texture = new Texture({
+ source: new ImageSource({
+ resource: canvas,
+ addressMode: this._wrapMode
+ })
+ });
+ const dist = Math.sqrt(dx * dx + dy * dy);
+ const angle = Math.atan2(dy, dx);
+ const m = new Matrix();
+ m.scale(dist / defaultSize, 1);
+ m.rotate(angle);
+ m.translate(x0, y0);
+ if (this.textureSpace === "local") {
+ m.scale(defaultSize, defaultSize);
+ }
+ this.transform = m;
+ }
+ /**
+ * Builds the internal texture and transform for the gradient.
+ * Called automatically when the gradient is first used.
+ * @internal
+ */
+ buildGradient() {
+ if (!this.texture)
+ this._tick++;
+ if (this.type === "linear") {
+ this.buildLinearGradient();
+ } else {
+ this.buildRadialGradient();
+ }
+ }
+ /**
+ * Builds the internal texture and transform for the radial gradient.
+ * Called automatically when the gradient is first used.
+ * @internal
+ */
+ buildRadialGradient() {
+ if (this.texture)
+ return;
+ const colorStops = this.colorStops.length ? this.colorStops : emptyColorStops;
+ const defaultSize = this._textureSize;
+ const { canvas, context } = getCanvas(defaultSize, defaultSize);
+ const { x: x0, y: y0 } = this.center;
+ const { x: x1, y: y1 } = this.outerCenter;
+ const r0 = this.innerRadius;
+ const r1 = this.outerRadius;
+ const ox = x1 - r1;
+ const oy = y1 - r1;
+ const scale = defaultSize / (r1 * 2);
+ const cx = (x0 - ox) * scale;
+ const cy = (y0 - oy) * scale;
+ const gradient = context.createRadialGradient(
+ cx,
+ cy,
+ r0 * scale,
+ (x1 - ox) * scale,
+ (y1 - oy) * scale,
+ r1 * scale
+ );
+ addColorStops(gradient, colorStops);
+ context.fillStyle = colorStops[colorStops.length - 1].color;
+ context.fillRect(0, 0, defaultSize, defaultSize);
+ context.fillStyle = gradient;
+ context.translate(cx, cy);
+ context.rotate(this.rotation);
+ context.scale(1, this.scale);
+ context.translate(-cx, -cy);
+ context.fillRect(0, 0, defaultSize, defaultSize);
+ this.texture = new Texture({
+ source: new ImageSource({
+ resource: canvas,
+ addressMode: this._wrapMode
+ })
+ });
+ const m = new Matrix();
+ m.scale(1 / scale, 1 / scale);
+ m.translate(ox, oy);
+ if (this.textureSpace === "local") {
+ m.scale(defaultSize, defaultSize);
+ }
+ this.transform = m;
+ }
+ /** Destroys the gradient, releasing resources. This will also destroy the internal texture. */
+ destroy() {
+ var _a;
+ (_a = this.texture) == null ? void 0 : _a.destroy(true);
+ this.texture = null;
+ this.transform = null;
+ this.colorStops = [];
+ this.start = null;
+ this.end = null;
+ this.center = null;
+ this.outerCenter = null;
+ }
+ /**
+ * Returns a unique key for this gradient instance.
+ * This key is used for caching and texture management.
+ * @returns {string} Unique key for the gradient
+ */
+ get styleKey() {
+ return `fill-gradient-${this.uid}-${this._tick}`;
+ }
+ };
+ /** Default options for creating a gradient fill */
+ _FillGradient.defaultLinearOptions = {
+ start: { x: 0, y: 0 },
+ end: { x: 0, y: 1 },
+ colorStops: [],
+ textureSpace: "local",
+ type: "linear",
+ textureSize: 256,
+ wrapMode: "clamp-to-edge"
+ };
+ /** Default options for creating a radial gradient fill */
+ _FillGradient.defaultRadialOptions = {
+ center: { x: 0.5, y: 0.5 },
+ innerRadius: 0,
+ outerRadius: 0.5,
+ colorStops: [],
+ scale: 1,
+ textureSpace: "local",
+ type: "radial",
+ textureSize: 256,
+ wrapMode: "clamp-to-edge"
+ };
+ let FillGradient = _FillGradient;
+ function addColorStops(gradient, colorStops) {
+ for (let i = 0; i < colorStops.length; i++) {
+ const stop = colorStops[i];
+ gradient.addColorStop(stop.offset, stop.color);
+ }
+ }
+ function getCanvas(width, height) {
+ const canvas = DOMAdapter.get().createCanvas(width, height);
+ const context = canvas.getContext("2d");
+ return { canvas, context };
+ }
+ function ensureGradientOptions(args) {
+ var _a, _b;
+ let options = (_a = args[0]) != null ? _a : {};
+ if (typeof options === "number" || args[1]) {
+ deprecation("8.5.2", `use options object instead`);
+ options = {
+ type: "linear",
+ start: { x: args[0], y: args[1] },
+ end: { x: args[2], y: args[3] },
+ textureSpace: args[4],
+ textureSize: (_b = args[5]) != null ? _b : FillGradient.defaultLinearOptions.textureSize
+ };
+ }
+ return options;
+ }
+
+ "use strict";
+ const tempTextureMatrix$1 = new Matrix();
+ const tempRect$4 = new Rectangle();
+ function generateTextureMatrix(out, style, shape, matrix) {
+ const textureMatrix = style.matrix ? out.copyFrom(style.matrix).invert() : out.identity();
+ if (style.textureSpace === "local") {
+ const bounds = shape.getBounds(tempRect$4);
+ if (style.width) {
+ bounds.pad(style.width);
+ }
+ const { x: tx, y: ty } = bounds;
+ const sx = 1 / bounds.width;
+ const sy = 1 / bounds.height;
+ const mTx = -tx * sx;
+ const mTy = -ty * sy;
+ const a1 = textureMatrix.a;
+ const b1 = textureMatrix.b;
+ const c1 = textureMatrix.c;
+ const d1 = textureMatrix.d;
+ textureMatrix.a *= sx;
+ textureMatrix.b *= sx;
+ textureMatrix.c *= sy;
+ textureMatrix.d *= sy;
+ textureMatrix.tx = mTx * a1 + mTy * c1 + textureMatrix.tx;
+ textureMatrix.ty = mTx * b1 + mTy * d1 + textureMatrix.ty;
+ } else {
+ textureMatrix.translate(style.texture.frame.x, style.texture.frame.y);
+ textureMatrix.scale(1 / style.texture.source.width, 1 / style.texture.source.height);
+ }
+ const sourceStyle = style.texture.source.style;
+ if (!(style.fill instanceof FillGradient) && sourceStyle.addressMode === "clamp-to-edge") {
+ sourceStyle.addressMode = "repeat";
+ sourceStyle.update();
+ }
+ if (matrix) {
+ textureMatrix.append(tempTextureMatrix$1.copyFrom(matrix).invert());
+ }
+ return textureMatrix;
+ }
+
+ "use strict";
+ const shapeBuilders = {};
+ extensions.handleByMap(ExtensionType.ShapeBuilder, shapeBuilders);
+ extensions.add(buildRectangle, buildPolygon, buildTriangle, buildCircle, buildEllipse, buildRoundedRectangle);
+ const tempRect$3 = new Rectangle();
+ const tempTextureMatrix = new Matrix();
+ function buildContextBatches(context, gpuContext) {
+ const { geometryData, batches } = gpuContext;
+ batches.length = 0;
+ geometryData.indices.length = 0;
+ geometryData.vertices.length = 0;
+ geometryData.uvs.length = 0;
+ for (let i = 0; i < context.instructions.length; i++) {
+ const instruction = context.instructions[i];
+ if (instruction.action === "texture") {
+ addTextureToGeometryData(instruction.data, batches, geometryData);
+ } else if (instruction.action === "fill" || instruction.action === "stroke") {
+ const isStroke = instruction.action === "stroke";
+ const shapePath = instruction.data.path.shapePath;
+ const style = instruction.data.style;
+ const hole = instruction.data.hole;
+ if (isStroke && hole) {
+ addShapePathToGeometryData(hole.shapePath, style, true, batches, geometryData);
+ }
+ if (hole) {
+ shapePath.shapePrimitives[shapePath.shapePrimitives.length - 1].holes = hole.shapePath.shapePrimitives;
+ }
+ addShapePathToGeometryData(shapePath, style, isStroke, batches, geometryData);
+ }
+ }
+ }
+ function addTextureToGeometryData(data, batches, geometryData) {
+ const points = [];
+ const build = shapeBuilders.rectangle;
+ const rect = tempRect$3;
+ rect.x = data.dx;
+ rect.y = data.dy;
+ rect.width = data.dw;
+ rect.height = data.dh;
+ const matrix = data.transform;
+ if (!build.build(rect, points)) {
+ return;
+ }
+ const { vertices, uvs, indices } = geometryData;
+ const indexOffset = indices.length;
+ const vertOffset = vertices.length / 2;
+ if (matrix) {
+ transformVertices(points, matrix);
+ }
+ build.triangulate(points, vertices, 2, vertOffset, indices, indexOffset);
+ const texture = data.image;
+ const textureUvs = texture.uvs;
+ uvs.push(
+ textureUvs.x0,
+ textureUvs.y0,
+ textureUvs.x1,
+ textureUvs.y1,
+ textureUvs.x3,
+ textureUvs.y3,
+ textureUvs.x2,
+ textureUvs.y2
+ );
+ const graphicsBatch = BigPool.get(BatchableGraphics);
+ graphicsBatch.indexOffset = indexOffset;
+ graphicsBatch.indexSize = indices.length - indexOffset;
+ graphicsBatch.attributeOffset = vertOffset;
+ graphicsBatch.attributeSize = vertices.length / 2 - vertOffset;
+ graphicsBatch.baseColor = data.style;
+ graphicsBatch.alpha = data.alpha;
+ graphicsBatch.texture = texture;
+ graphicsBatch.geometryData = geometryData;
+ batches.push(graphicsBatch);
+ }
+ function addShapePathToGeometryData(shapePath, style, isStroke, batches, geometryData) {
+ const { vertices, uvs, indices } = geometryData;
+ shapePath.shapePrimitives.forEach(({ shape, transform: matrix, holes }) => {
+ var _a;
+ const points = [];
+ const build = shapeBuilders[shape.type];
+ if (!build.build(shape, points)) {
+ return;
+ }
+ const indexOffset = indices.length;
+ const vertOffset = vertices.length / 2;
+ let topology = "triangle-list";
+ if (matrix) {
+ transformVertices(points, matrix);
+ }
+ if (!isStroke) {
+ if (holes) {
+ const holeIndices = [];
+ const otherPoints = points.slice();
+ const holeArrays = getHoleArrays(holes);
+ holeArrays.forEach((holePoints) => {
+ holeIndices.push(otherPoints.length / 2);
+ otherPoints.push(...holePoints);
+ });
+ triangulateWithHoles(otherPoints, holeIndices, vertices, 2, vertOffset, indices, indexOffset);
+ } else {
+ build.triangulate(points, vertices, 2, vertOffset, indices, indexOffset);
+ }
+ } else {
+ const close = (_a = shape.closePath) != null ? _a : true;
+ const lineStyle = style;
+ if (!lineStyle.pixelLine) {
+ buildLine(points, lineStyle, false, close, vertices, indices);
+ } else {
+ buildPixelLine(points, close, vertices, indices);
+ topology = "line-list";
+ }
+ }
+ const uvsOffset = uvs.length / 2;
+ const texture = style.texture;
+ if (texture !== Texture.WHITE) {
+ const textureMatrix = generateTextureMatrix(tempTextureMatrix, style, shape, matrix);
+ buildUvs(vertices, 2, vertOffset, uvs, uvsOffset, 2, vertices.length / 2 - vertOffset, textureMatrix);
+ } else {
+ buildSimpleUvs(uvs, uvsOffset, 2, vertices.length / 2 - vertOffset);
+ }
+ const graphicsBatch = BigPool.get(BatchableGraphics);
+ graphicsBatch.indexOffset = indexOffset;
+ graphicsBatch.indexSize = indices.length - indexOffset;
+ graphicsBatch.attributeOffset = vertOffset;
+ graphicsBatch.attributeSize = vertices.length / 2 - vertOffset;
+ graphicsBatch.baseColor = style.color;
+ graphicsBatch.alpha = style.alpha;
+ graphicsBatch.texture = texture;
+ graphicsBatch.geometryData = geometryData;
+ graphicsBatch.topology = topology;
+ batches.push(graphicsBatch);
+ });
+ }
+ function getHoleArrays(holePrimitives) {
+ const holeArrays = [];
+ for (let k = 0; k < holePrimitives.length; k++) {
+ const holePrimitive = holePrimitives[k].shape;
+ const holePoints = [];
+ const holeBuilder = shapeBuilders[holePrimitive.type];
+ if (holeBuilder.build(holePrimitive, holePoints)) {
+ holeArrays.push(holePoints);
+ }
+ }
+ return holeArrays;
+ }
+
+ "use strict";
+ class GpuGraphicsContext {
+ constructor() {
+ this.batches = [];
+ this.geometryData = {
+ vertices: [],
+ uvs: [],
+ indices: []
+ };
+ }
+ }
+ class GraphicsContextRenderData {
+ constructor() {
+ this.instructions = new InstructionSet();
+ }
+ init(maxTextures) {
+ this.batcher = new DefaultBatcher({
+ maxTextures
+ });
+ this.instructions.reset();
+ }
+ /**
+ * @deprecated since version 8.0.0
+ * Use `batcher.geometry` instead.
+ * @see {Batcher#geometry}
+ */
+ get geometry() {
+ deprecation(v8_3_4, "GraphicsContextRenderData#geometry is deprecated, please use batcher.geometry instead.");
+ return this.batcher.geometry;
+ }
+ destroy() {
+ this.batcher.destroy();
+ this.instructions.destroy();
+ this.batcher = null;
+ this.instructions = null;
+ }
+ }
+ const _GraphicsContextSystem = class _GraphicsContextSystem {
+ constructor(renderer) {
+ // the root context batches, used to either make a batch or geometry
+ // all graphics use this as a base
+ this._gpuContextHash = {};
+ // used for non-batchable graphics
+ this._graphicsDataContextHash = /* @__PURE__ */ Object.create(null);
+ this._renderer = renderer;
+ renderer.renderableGC.addManagedHash(this, "_gpuContextHash");
+ renderer.renderableGC.addManagedHash(this, "_graphicsDataContextHash");
+ }
+ /**
+ * Runner init called, update the default options
+ * @ignore
+ */
+ init(options) {
+ var _a;
+ _GraphicsContextSystem.defaultOptions.bezierSmoothness = (_a = options == null ? void 0 : options.bezierSmoothness) != null ? _a : _GraphicsContextSystem.defaultOptions.bezierSmoothness;
+ }
+ /**
+ * Returns the render data for a given GraphicsContext.
+ * @param context - The GraphicsContext to get the render data for.
+ * @internal
+ */
+ getContextRenderData(context) {
+ return this._graphicsDataContextHash[context.uid] || this._initContextRenderData(context);
+ }
+ /**
+ * Updates the GPU context for a given GraphicsContext.
+ * If the context is dirty, it will rebuild the batches and geometry data.
+ * @param context - The GraphicsContext to update.
+ * @returns The updated GpuGraphicsContext.
+ * @internal
+ */
+ updateGpuContext(context) {
+ let gpuContext = this._gpuContextHash[context.uid] || this._initContext(context);
+ if (context.dirty) {
+ if (gpuContext) {
+ this._cleanGraphicsContextData(context);
+ } else {
+ gpuContext = this._initContext(context);
+ }
+ buildContextBatches(context, gpuContext);
+ const batchMode = context.batchMode;
+ if (context.customShader || batchMode === "no-batch") {
+ gpuContext.isBatchable = false;
+ } else if (batchMode === "auto") {
+ gpuContext.isBatchable = gpuContext.geometryData.vertices.length < 400;
+ } else {
+ gpuContext.isBatchable = true;
+ }
+ context.dirty = false;
+ }
+ return gpuContext;
+ }
+ /**
+ * Returns the GpuGraphicsContext for a given GraphicsContext.
+ * If it does not exist, it will initialize a new one.
+ * @param context - The GraphicsContext to get the GpuGraphicsContext for.
+ * @returns The GpuGraphicsContext for the given GraphicsContext.
+ * @internal
+ */
+ getGpuContext(context) {
+ return this._gpuContextHash[context.uid] || this._initContext(context);
+ }
+ _initContextRenderData(context) {
+ const graphicsData = BigPool.get(GraphicsContextRenderData, {
+ maxTextures: this._renderer.limits.maxBatchableTextures
+ });
+ const { batches, geometryData } = this._gpuContextHash[context.uid];
+ const vertexSize = geometryData.vertices.length;
+ const indexSize = geometryData.indices.length;
+ for (let i = 0; i < batches.length; i++) {
+ batches[i].applyTransform = false;
+ }
+ const batcher = graphicsData.batcher;
+ batcher.ensureAttributeBuffer(vertexSize);
+ batcher.ensureIndexBuffer(indexSize);
+ batcher.begin();
+ for (let i = 0; i < batches.length; i++) {
+ const batch = batches[i];
+ batcher.add(batch);
+ }
+ batcher.finish(graphicsData.instructions);
+ const geometry = batcher.geometry;
+ geometry.indexBuffer.setDataWithSize(batcher.indexBuffer, batcher.indexSize, true);
+ geometry.buffers[0].setDataWithSize(batcher.attributeBuffer.float32View, batcher.attributeSize, true);
+ const drawBatches = batcher.batches;
+ for (let i = 0; i < drawBatches.length; i++) {
+ const batch = drawBatches[i];
+ batch.bindGroup = getTextureBatchBindGroup(
+ batch.textures.textures,
+ batch.textures.count,
+ this._renderer.limits.maxBatchableTextures
+ );
+ }
+ this._graphicsDataContextHash[context.uid] = graphicsData;
+ return graphicsData;
+ }
+ _initContext(context) {
+ const gpuContext = new GpuGraphicsContext();
+ gpuContext.context = context;
+ this._gpuContextHash[context.uid] = gpuContext;
+ context.on("destroy", this.onGraphicsContextDestroy, this);
+ return this._gpuContextHash[context.uid];
+ }
+ onGraphicsContextDestroy(context) {
+ this._cleanGraphicsContextData(context);
+ context.off("destroy", this.onGraphicsContextDestroy, this);
+ this._gpuContextHash[context.uid] = null;
+ }
+ _cleanGraphicsContextData(context) {
+ const gpuContext = this._gpuContextHash[context.uid];
+ if (!gpuContext.isBatchable) {
+ if (this._graphicsDataContextHash[context.uid]) {
+ BigPool.return(this.getContextRenderData(context));
+ this._graphicsDataContextHash[context.uid] = null;
+ }
+ }
+ if (gpuContext.batches) {
+ gpuContext.batches.forEach((batch) => {
+ BigPool.return(batch);
+ });
+ }
+ }
+ destroy() {
+ for (const i in this._gpuContextHash) {
+ if (this._gpuContextHash[i]) {
+ this.onGraphicsContextDestroy(this._gpuContextHash[i].context);
+ }
+ }
+ }
+ };
+ /** @ignore */
+ _GraphicsContextSystem.extension = {
+ type: [
+ ExtensionType.WebGLSystem,
+ ExtensionType.WebGPUSystem,
+ ExtensionType.CanvasSystem
+ ],
+ name: "graphicsContext"
+ };
+ /** The default options for the GraphicsContextSystem. */
+ _GraphicsContextSystem.defaultOptions = {
+ /**
+ * A value from 0 to 1 that controls the smoothness of bezier curves (the higher the smoother)
+ * @default 0.5
+ */
+ bezierSmoothness: 0.5
+ };
+ let GraphicsContextSystem = _GraphicsContextSystem;
+
+ "use strict";
+ const blendModeIds = {
+ normal: 0,
+ add: 1,
+ multiply: 2,
+ screen: 3,
+ overlay: 4,
+ erase: 5,
+ "normal-npm": 6,
+ "add-npm": 7,
+ "screen-npm": 8,
+ min: 9,
+ max: 10
+ };
+ const BLEND$1 = 0;
+ const OFFSET$1 = 1;
+ const CULLING$1 = 2;
+ const DEPTH_TEST$1 = 3;
+ const WINDING$1 = 4;
+ const DEPTH_MASK$1 = 5;
+ const _State = class _State {
+ constructor() {
+ this.data = 0;
+ this.blendMode = "normal";
+ this.polygonOffset = 0;
+ this.blend = true;
+ this.depthMask = true;
+ }
+ /**
+ * Activates blending of the computed fragment color values.
+ * @default true
+ */
+ get blend() {
+ return !!(this.data & 1 << BLEND$1);
+ }
+ set blend(value) {
+ if (!!(this.data & 1 << BLEND$1) !== value) {
+ this.data ^= 1 << BLEND$1;
+ }
+ }
+ /**
+ * Activates adding an offset to depth values of polygon's fragments
+ * @default false
+ */
+ get offsets() {
+ return !!(this.data & 1 << OFFSET$1);
+ }
+ set offsets(value) {
+ if (!!(this.data & 1 << OFFSET$1) !== value) {
+ this.data ^= 1 << OFFSET$1;
+ }
+ }
+ /** The culling settings for this state none - No culling back - Back face culling front - Front face culling */
+ set cullMode(value) {
+ if (value === "none") {
+ this.culling = false;
+ return;
+ }
+ this.culling = true;
+ this.clockwiseFrontFace = value === "front";
+ }
+ get cullMode() {
+ if (!this.culling) {
+ return "none";
+ }
+ return this.clockwiseFrontFace ? "front" : "back";
+ }
+ /**
+ * Activates culling of polygons.
+ * @default false
+ */
+ get culling() {
+ return !!(this.data & 1 << CULLING$1);
+ }
+ set culling(value) {
+ if (!!(this.data & 1 << CULLING$1) !== value) {
+ this.data ^= 1 << CULLING$1;
+ }
+ }
+ /**
+ * Activates depth comparisons and updates to the depth buffer.
+ * @default false
+ */
+ get depthTest() {
+ return !!(this.data & 1 << DEPTH_TEST$1);
+ }
+ set depthTest(value) {
+ if (!!(this.data & 1 << DEPTH_TEST$1) !== value) {
+ this.data ^= 1 << DEPTH_TEST$1;
+ }
+ }
+ /**
+ * Enables or disables writing to the depth buffer.
+ * @default true
+ */
+ get depthMask() {
+ return !!(this.data & 1 << DEPTH_MASK$1);
+ }
+ set depthMask(value) {
+ if (!!(this.data & 1 << DEPTH_MASK$1) !== value) {
+ this.data ^= 1 << DEPTH_MASK$1;
+ }
+ }
+ /**
+ * Specifies whether or not front or back-facing polygons can be culled.
+ * @default false
+ */
+ get clockwiseFrontFace() {
+ return !!(this.data & 1 << WINDING$1);
+ }
+ set clockwiseFrontFace(value) {
+ if (!!(this.data & 1 << WINDING$1) !== value) {
+ this.data ^= 1 << WINDING$1;
+ }
+ }
+ /**
+ * The blend mode to be applied when this state is set. Apply a value of `normal` to reset the blend mode.
+ * Setting this mode to anything other than NO_BLEND will automatically switch blending on.
+ * @default 'normal'
+ */
+ get blendMode() {
+ return this._blendMode;
+ }
+ set blendMode(value) {
+ this.blend = value !== "none";
+ this._blendMode = value;
+ this._blendModeId = blendModeIds[value] || 0;
+ }
+ /**
+ * The polygon offset. Setting this property to anything other than 0 will automatically enable polygon offset fill.
+ * @default 0
+ */
+ get polygonOffset() {
+ return this._polygonOffset;
+ }
+ set polygonOffset(value) {
+ this.offsets = !!value;
+ this._polygonOffset = value;
+ }
+ toString() {
+ return `[pixi.js/core:State blendMode=${this.blendMode} clockwiseFrontFace=${this.clockwiseFrontFace} culling=${this.culling} depthMask=${this.depthMask} polygonOffset=${this.polygonOffset}]`;
+ }
+ /**
+ * A quickly getting an instance of a State that is configured for 2d rendering.
+ * @returns a new State with values set for 2d rendering
+ */
+ static for2d() {
+ const state = new _State();
+ state.depthTest = false;
+ state.blend = true;
+ return state;
+ }
+ };
+ _State.default2d = _State.for2d();
+ let State = _State;
+
+ "use strict";
+ function colorToUniform(rgb, alpha, out, offset) {
+ out[offset++] = (rgb >> 16 & 255) / 255;
+ out[offset++] = (rgb >> 8 & 255) / 255;
+ out[offset++] = (rgb & 255) / 255;
+ out[offset++] = alpha;
+ }
+ function color32BitToUniform(abgr, out, offset) {
+ const alpha = (abgr >> 24 & 255) / 255;
+ out[offset++] = (abgr & 255) / 255 * alpha;
+ out[offset++] = (abgr >> 8 & 255) / 255 * alpha;
+ out[offset++] = (abgr >> 16 & 255) / 255 * alpha;
+ out[offset++] = alpha;
+ }
+
+ "use strict";
+ class GraphicsGpuData {
+ constructor() {
+ this.batches = [];
+ this.batched = false;
+ }
+ destroy() {
+ this.batches.forEach((batch) => {
+ BigPool.return(batch);
+ });
+ this.batches.length = 0;
+ }
+ }
+ class GraphicsPipe {
+ constructor(renderer, adaptor) {
+ this.state = State.for2d();
+ this.renderer = renderer;
+ this._adaptor = adaptor;
+ this.renderer.runners.contextChange.add(this);
+ }
+ contextChange() {
+ this._adaptor.contextChange(this.renderer);
+ }
+ validateRenderable(graphics) {
+ const context = graphics.context;
+ const wasBatched = !!graphics._gpuData;
+ const gpuContext = this.renderer.graphicsContext.updateGpuContext(context);
+ if (gpuContext.isBatchable || wasBatched !== gpuContext.isBatchable) {
+ return true;
+ }
+ return false;
+ }
+ addRenderable(graphics, instructionSet) {
+ const gpuContext = this.renderer.graphicsContext.updateGpuContext(graphics.context);
+ if (graphics.didViewUpdate) {
+ this._rebuild(graphics);
+ }
+ if (gpuContext.isBatchable) {
+ this._addToBatcher(graphics, instructionSet);
+ } else {
+ this.renderer.renderPipes.batch.break(instructionSet);
+ instructionSet.add(graphics);
+ }
+ }
+ updateRenderable(graphics) {
+ const gpuData = this._getGpuDataForRenderable(graphics);
+ const batches = gpuData.batches;
+ for (let i = 0; i < batches.length; i++) {
+ const batch = batches[i];
+ batch._batcher.updateElement(batch);
+ }
+ }
+ execute(graphics) {
+ if (!graphics.isRenderable)
+ return;
+ const renderer = this.renderer;
+ const context = graphics.context;
+ const contextSystem = renderer.graphicsContext;
+ if (!contextSystem.getGpuContext(context).batches.length) {
+ return;
+ }
+ const shader = context.customShader || this._adaptor.shader;
+ this.state.blendMode = graphics.groupBlendMode;
+ const localUniforms = shader.resources.localUniforms.uniforms;
+ localUniforms.uTransformMatrix = graphics.groupTransform;
+ localUniforms.uRound = renderer._roundPixels | graphics._roundPixels;
+ color32BitToUniform(
+ graphics.groupColorAlpha,
+ localUniforms.uColor,
+ 0
+ );
+ this._adaptor.execute(this, graphics);
+ }
+ _rebuild(graphics) {
+ const gpuData = this._getGpuDataForRenderable(graphics);
+ const gpuContext = this.renderer.graphicsContext.updateGpuContext(graphics.context);
+ gpuData.destroy();
+ if (gpuContext.isBatchable) {
+ this._updateBatchesForRenderable(graphics, gpuData);
+ }
+ }
+ _addToBatcher(graphics, instructionSet) {
+ const batchPipe = this.renderer.renderPipes.batch;
+ const batches = this._getGpuDataForRenderable(graphics).batches;
+ for (let i = 0; i < batches.length; i++) {
+ const batch = batches[i];
+ batchPipe.addToBatch(batch, instructionSet);
+ }
+ }
+ _getGpuDataForRenderable(graphics) {
+ return graphics._gpuData[this.renderer.uid] || this._initGpuDataForRenderable(graphics);
+ }
+ _initGpuDataForRenderable(graphics) {
+ const gpuData = new GraphicsGpuData();
+ graphics._gpuData[this.renderer.uid] = gpuData;
+ return gpuData;
+ }
+ _updateBatchesForRenderable(graphics, gpuData) {
+ const context = graphics.context;
+ const gpuContext = this.renderer.graphicsContext.getGpuContext(context);
+ const roundPixels = this.renderer._roundPixels | graphics._roundPixels;
+ gpuData.batches = gpuContext.batches.map((batch) => {
+ const batchClone = BigPool.get(BatchableGraphics);
+ batch.copyTo(batchClone);
+ batchClone.renderable = graphics;
+ batchClone.roundPixels = roundPixels;
+ return batchClone;
+ });
+ }
+ destroy() {
+ this.renderer = null;
+ this._adaptor.destroy();
+ this._adaptor = null;
+ this.state = null;
+ }
+ }
+ /** @ignore */
+ GraphicsPipe.extension = {
+ type: [
+ ExtensionType.WebGLPipes,
+ ExtensionType.WebGPUPipes,
+ ExtensionType.CanvasPipes
+ ],
+ name: "graphics"
+ };
+
+ "use strict";
+ extensions.add(GraphicsPipe);
+ extensions.add(GraphicsContextSystem);
+
+ "use strict";
+ class BatchableMesh {
+ constructor() {
+ this.batcherName = "default";
+ this.packAsQuad = false;
+ this.indexOffset = 0;
+ this.attributeOffset = 0;
+ this.roundPixels = 0;
+ this._batcher = null;
+ this._batch = null;
+ this._textureMatrixUpdateId = -1;
+ this._uvUpdateId = -1;
+ }
+ get blendMode() {
+ return this.renderable.groupBlendMode;
+ }
+ get topology() {
+ return this._topology || this.geometry.topology;
+ }
+ set topology(value) {
+ this._topology = value;
+ }
+ reset() {
+ this.renderable = null;
+ this.texture = null;
+ this._batcher = null;
+ this._batch = null;
+ this.geometry = null;
+ this._uvUpdateId = -1;
+ this._textureMatrixUpdateId = -1;
+ }
+ /**
+ * Sets the texture for the batchable mesh.
+ * As it does so, it resets the texture matrix update ID.
+ * this is to ensure that the texture matrix is recalculated when the uvs are referenced
+ * @param value - The texture to set.
+ */
+ setTexture(value) {
+ if (this.texture === value)
+ return;
+ this.texture = value;
+ this._textureMatrixUpdateId = -1;
+ }
+ get uvs() {
+ const geometry = this.geometry;
+ const uvBuffer = geometry.getBuffer("aUV");
+ const uvs = uvBuffer.data;
+ let transformedUvs = uvs;
+ const textureMatrix = this.texture.textureMatrix;
+ if (!textureMatrix.isSimple) {
+ transformedUvs = this._transformedUvs;
+ if (this._textureMatrixUpdateId !== textureMatrix._updateID || this._uvUpdateId !== uvBuffer._updateID) {
+ if (!transformedUvs || transformedUvs.length < uvs.length) {
+ transformedUvs = this._transformedUvs = new Float32Array(uvs.length);
+ }
+ this._textureMatrixUpdateId = textureMatrix._updateID;
+ this._uvUpdateId = uvBuffer._updateID;
+ textureMatrix.multiplyUvs(uvs, transformedUvs);
+ }
+ }
+ return transformedUvs;
+ }
+ get positions() {
+ return this.geometry.positions;
+ }
+ get indices() {
+ return this.geometry.indices;
+ }
+ get color() {
+ return this.renderable.groupColorAlpha;
+ }
+ get groupTransform() {
+ return this.renderable.groupTransform;
+ }
+ get attributeSize() {
+ return this.geometry.positions.length / 2;
+ }
+ get indexSize() {
+ return this.geometry.indices.length;
+ }
+ }
+
+ "use strict";
+ class MeshGpuData {
+ destroy() {
+ }
+ }
+ class MeshPipe {
+ constructor(renderer, adaptor) {
+ this.localUniforms = new UniformGroup({
+ uTransformMatrix: { value: new Matrix(), type: "mat3x3" },
+ uColor: { value: new Float32Array([1, 1, 1, 1]), type: "vec4" },
+ uRound: { value: 0, type: "f32" }
+ });
+ this.localUniformsBindGroup = new BindGroup({
+ 0: this.localUniforms
+ });
+ this.renderer = renderer;
+ this._adaptor = adaptor;
+ this._adaptor.init();
+ }
+ validateRenderable(mesh) {
+ const meshData = this._getMeshData(mesh);
+ const wasBatched = meshData.batched;
+ const isBatched = mesh.batched;
+ meshData.batched = isBatched;
+ if (wasBatched !== isBatched) {
+ return true;
+ } else if (isBatched) {
+ const geometry = mesh._geometry;
+ if (geometry.indices.length !== meshData.indexSize || geometry.positions.length !== meshData.vertexSize) {
+ meshData.indexSize = geometry.indices.length;
+ meshData.vertexSize = geometry.positions.length;
+ return true;
+ }
+ const batchableMesh = this._getBatchableMesh(mesh);
+ if (batchableMesh.texture.uid !== mesh._texture.uid) {
+ batchableMesh._textureMatrixUpdateId = -1;
+ }
+ return !batchableMesh._batcher.checkAndUpdateTexture(
+ batchableMesh,
+ mesh._texture
+ );
+ }
+ return false;
+ }
+ addRenderable(mesh, instructionSet) {
+ var _a, _b;
+ const batcher = this.renderer.renderPipes.batch;
+ const meshData = this._getMeshData(mesh);
+ if (mesh.didViewUpdate) {
+ meshData.indexSize = (_a = mesh._geometry.indices) == null ? void 0 : _a.length;
+ meshData.vertexSize = (_b = mesh._geometry.positions) == null ? void 0 : _b.length;
+ }
+ if (meshData.batched) {
+ const gpuBatchableMesh = this._getBatchableMesh(mesh);
+ gpuBatchableMesh.setTexture(mesh._texture);
+ gpuBatchableMesh.geometry = mesh._geometry;
+ batcher.addToBatch(gpuBatchableMesh, instructionSet);
+ } else {
+ batcher.break(instructionSet);
+ instructionSet.add(mesh);
+ }
+ }
+ updateRenderable(mesh) {
+ if (mesh.batched) {
+ const gpuBatchableMesh = this._getBatchableMesh(mesh);
+ gpuBatchableMesh.setTexture(mesh._texture);
+ gpuBatchableMesh.geometry = mesh._geometry;
+ gpuBatchableMesh._batcher.updateElement(gpuBatchableMesh);
+ }
+ }
+ execute(mesh) {
+ if (!mesh.isRenderable)
+ return;
+ mesh.state.blendMode = getAdjustedBlendModeBlend(mesh.groupBlendMode, mesh.texture._source);
+ const localUniforms = this.localUniforms;
+ localUniforms.uniforms.uTransformMatrix = mesh.groupTransform;
+ localUniforms.uniforms.uRound = this.renderer._roundPixels | mesh._roundPixels;
+ localUniforms.update();
+ color32BitToUniform(
+ mesh.groupColorAlpha,
+ localUniforms.uniforms.uColor,
+ 0
+ );
+ this._adaptor.execute(this, mesh);
+ }
+ _getMeshData(mesh) {
+ var _a, _b;
+ (_a = mesh._gpuData)[_b = this.renderer.uid] || (_a[_b] = new MeshGpuData());
+ return mesh._gpuData[this.renderer.uid].meshData || this._initMeshData(mesh);
+ }
+ _initMeshData(mesh) {
+ mesh._gpuData[this.renderer.uid].meshData = {
+ batched: mesh.batched,
+ indexSize: 0,
+ vertexSize: 0
+ };
+ return mesh._gpuData[this.renderer.uid].meshData;
+ }
+ _getBatchableMesh(mesh) {
+ var _a, _b;
+ (_a = mesh._gpuData)[_b = this.renderer.uid] || (_a[_b] = new MeshGpuData());
+ return mesh._gpuData[this.renderer.uid].batchableMesh || this._initBatchableMesh(mesh);
+ }
+ _initBatchableMesh(mesh) {
+ const gpuMesh = new BatchableMesh();
+ gpuMesh.renderable = mesh;
+ gpuMesh.setTexture(mesh._texture);
+ gpuMesh.transform = mesh.groupTransform;
+ gpuMesh.roundPixels = this.renderer._roundPixels | mesh._roundPixels;
+ mesh._gpuData[this.renderer.uid].batchableMesh = gpuMesh;
+ return gpuMesh;
+ }
+ destroy() {
+ this.localUniforms = null;
+ this.localUniformsBindGroup = null;
+ this._adaptor.destroy();
+ this._adaptor = null;
+ this.renderer = null;
+ }
+ }
+ /** @ignore */
+ MeshPipe.extension = {
+ type: [
+ ExtensionType.WebGLPipes,
+ ExtensionType.WebGPUPipes,
+ ExtensionType.CanvasPipes
+ ],
+ name: "mesh"
+ };
+
+ "use strict";
+ extensions.add(MeshPipe);
+
+ "use strict";
+ class GlParticleContainerAdaptor {
+ execute(particleContainerPipe, container) {
+ const state = particleContainerPipe.state;
+ const renderer = particleContainerPipe.renderer;
+ const shader = container.shader || particleContainerPipe.defaultShader;
+ shader.resources.uTexture = container.texture._source;
+ shader.resources.uniforms = particleContainerPipe.localUniforms;
+ const gl = renderer.gl;
+ const buffer = particleContainerPipe.getBuffers(container);
+ renderer.shader.bind(shader);
+ renderer.state.set(state);
+ renderer.geometry.bind(buffer.geometry, shader.glProgram);
+ const byteSize = buffer.geometry.indexBuffer.data.BYTES_PER_ELEMENT;
+ const glType = byteSize === 2 ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT;
+ gl.drawElements(gl.TRIANGLES, container.particleChildren.length * 6, glType, 0);
+ }
+ }
+
+ "use strict";
+ function createIndicesForQuads(size, outBuffer = null) {
+ const totalIndices = size * 6;
+ if (totalIndices > 65535) {
+ outBuffer || (outBuffer = new Uint32Array(totalIndices));
+ } else {
+ outBuffer || (outBuffer = new Uint16Array(totalIndices));
+ }
+ if (outBuffer.length !== totalIndices) {
+ throw new Error(`Out buffer length is incorrect, got ${outBuffer.length} and expected ${totalIndices}`);
+ }
+ for (let i = 0, j = 0; i < totalIndices; i += 6, j += 4) {
+ outBuffer[i + 0] = j + 0;
+ outBuffer[i + 1] = j + 1;
+ outBuffer[i + 2] = j + 2;
+ outBuffer[i + 3] = j + 0;
+ outBuffer[i + 4] = j + 2;
+ outBuffer[i + 5] = j + 3;
+ }
+ return outBuffer;
+ }
+
+ "use strict";
+ function generateParticleUpdateFunction(properties) {
+ return {
+ dynamicUpdate: generateUpdateFunction(properties, true),
+ staticUpdate: generateUpdateFunction(properties, false)
+ };
+ }
+ function generateUpdateFunction(properties, dynamic) {
+ const funcFragments = [];
+ funcFragments.push(`
+
+ var index = 0;
+
+ for (let i = 0; i < ps.length; ++i)
+ {
+ const p = ps[i];
+
+ `);
+ let offset = 0;
+ for (const i in properties) {
+ const property = properties[i];
+ if (dynamic !== property.dynamic)
+ continue;
+ funcFragments.push(`offset = index + ${offset}`);
+ funcFragments.push(property.code);
+ const attributeInfo = getAttributeInfoFromFormat(property.format);
+ offset += attributeInfo.stride / 4;
+ }
+ funcFragments.push(`
+ index += stride * 4;
+ }
+ `);
+ funcFragments.unshift(`
+ var stride = ${offset};
+ `);
+ const functionSource = funcFragments.join("\n");
+ return new Function("ps", "f32v", "u32v", functionSource);
+ }
+
+ "use strict";
+ class ParticleBuffer {
+ constructor(options) {
+ this._size = 0;
+ this._generateParticleUpdateCache = {};
+ var _a;
+ const size = this._size = (_a = options.size) != null ? _a : 1e3;
+ const properties = options.properties;
+ let staticVertexSize = 0;
+ let dynamicVertexSize = 0;
+ for (const i in properties) {
+ const property = properties[i];
+ const attributeInfo = getAttributeInfoFromFormat(property.format);
+ if (property.dynamic) {
+ dynamicVertexSize += attributeInfo.stride;
+ } else {
+ staticVertexSize += attributeInfo.stride;
+ }
+ }
+ this._dynamicStride = dynamicVertexSize / 4;
+ this._staticStride = staticVertexSize / 4;
+ this.staticAttributeBuffer = new ViewableBuffer(size * 4 * staticVertexSize);
+ this.dynamicAttributeBuffer = new ViewableBuffer(size * 4 * dynamicVertexSize);
+ this.indexBuffer = createIndicesForQuads(size);
+ const geometry = new Geometry();
+ let dynamicOffset = 0;
+ let staticOffset = 0;
+ this._staticBuffer = new Buffer({
+ data: new Float32Array(1),
+ label: "static-particle-buffer",
+ shrinkToFit: false,
+ usage: BufferUsage.VERTEX | BufferUsage.COPY_DST
+ });
+ this._dynamicBuffer = new Buffer({
+ data: new Float32Array(1),
+ label: "dynamic-particle-buffer",
+ shrinkToFit: false,
+ usage: BufferUsage.VERTEX | BufferUsage.COPY_DST
+ });
+ for (const i in properties) {
+ const property = properties[i];
+ const attributeInfo = getAttributeInfoFromFormat(property.format);
+ if (property.dynamic) {
+ geometry.addAttribute(property.attributeName, {
+ buffer: this._dynamicBuffer,
+ stride: this._dynamicStride * 4,
+ offset: dynamicOffset * 4,
+ format: property.format
+ });
+ dynamicOffset += attributeInfo.size;
+ } else {
+ geometry.addAttribute(property.attributeName, {
+ buffer: this._staticBuffer,
+ stride: this._staticStride * 4,
+ offset: staticOffset * 4,
+ format: property.format
+ });
+ staticOffset += attributeInfo.size;
+ }
+ }
+ geometry.addIndex(this.indexBuffer);
+ const uploadFunction = this.getParticleUpdate(properties);
+ this._dynamicUpload = uploadFunction.dynamicUpdate;
+ this._staticUpload = uploadFunction.staticUpdate;
+ this.geometry = geometry;
+ }
+ getParticleUpdate(properties) {
+ const key = getParticleSyncKey(properties);
+ if (this._generateParticleUpdateCache[key]) {
+ return this._generateParticleUpdateCache[key];
+ }
+ this._generateParticleUpdateCache[key] = this.generateParticleUpdate(properties);
+ return this._generateParticleUpdateCache[key];
+ }
+ generateParticleUpdate(properties) {
+ return generateParticleUpdateFunction(properties);
+ }
+ update(particles, uploadStatic) {
+ if (particles.length > this._size) {
+ uploadStatic = true;
+ this._size = Math.max(particles.length, this._size * 1.5 | 0);
+ this.staticAttributeBuffer = new ViewableBuffer(this._size * this._staticStride * 4 * 4);
+ this.dynamicAttributeBuffer = new ViewableBuffer(this._size * this._dynamicStride * 4 * 4);
+ this.indexBuffer = createIndicesForQuads(this._size);
+ this.geometry.indexBuffer.setDataWithSize(
+ this.indexBuffer,
+ this.indexBuffer.byteLength,
+ true
+ );
+ }
+ const dynamicAttributeBuffer = this.dynamicAttributeBuffer;
+ this._dynamicUpload(particles, dynamicAttributeBuffer.float32View, dynamicAttributeBuffer.uint32View);
+ this._dynamicBuffer.setDataWithSize(
+ this.dynamicAttributeBuffer.float32View,
+ particles.length * this._dynamicStride * 4,
+ true
+ );
+ if (uploadStatic) {
+ const staticAttributeBuffer = this.staticAttributeBuffer;
+ this._staticUpload(particles, staticAttributeBuffer.float32View, staticAttributeBuffer.uint32View);
+ this._staticBuffer.setDataWithSize(
+ staticAttributeBuffer.float32View,
+ particles.length * this._staticStride * 4,
+ true
+ );
+ }
+ }
+ destroy() {
+ this._staticBuffer.destroy();
+ this._dynamicBuffer.destroy();
+ this.geometry.destroy();
+ }
+ }
+ function getParticleSyncKey(properties) {
+ const keyGen = [];
+ for (const key in properties) {
+ const property = properties[key];
+ keyGen.push(key, property.code, property.dynamic ? "d" : "s");
+ }
+ return keyGen.join("_");
+ }
+
+ var fragment$5 = "varying vec2 vUV;\nvarying vec4 vColor;\n\nuniform sampler2D uTexture;\n\nvoid main(void){\n vec4 color = texture2D(uTexture, vUV) * vColor;\n gl_FragColor = color;\n}";
+
+ var vertex$3 = "attribute vec2 aVertex;\nattribute vec2 aUV;\nattribute vec4 aColor;\n\nattribute vec2 aPosition;\nattribute float aRotation;\n\nuniform mat3 uTranslationMatrix;\nuniform float uRound;\nuniform vec2 uResolution;\nuniform vec4 uColor;\n\nvarying vec2 vUV;\nvarying vec4 vColor;\n\nvec2 roundPixels(vec2 position, vec2 targetSize)\n{ \n return (floor(((position * 0.5 + 0.5) * targetSize) + 0.5) / targetSize) * 2.0 - 1.0;\n}\n\nvoid main(void){\n float cosRotation = cos(aRotation);\n float sinRotation = sin(aRotation);\n float x = aVertex.x * cosRotation - aVertex.y * sinRotation;\n float y = aVertex.x * sinRotation + aVertex.y * cosRotation;\n\n vec2 v = vec2(x, y);\n v = v + aPosition;\n\n gl_Position = vec4((uTranslationMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);\n\n if(uRound == 1.0)\n {\n gl_Position.xy = roundPixels(gl_Position.xy, uResolution);\n }\n\n vUV = aUV;\n vColor = vec4(aColor.rgb * aColor.a, aColor.a) * uColor;\n}\n";
+
+ var wgsl = "\nstruct ParticleUniforms {\n uTranslationMatrix:mat3x3,\n uColor:vec4,\n uRound:f32,\n uResolution:vec2,\n};\n\nfn roundPixels(position: vec2, targetSize: vec2) -> vec2\n{\n return (floor(((position * 0.5 + 0.5) * targetSize) + 0.5) / targetSize) * 2.0 - 1.0;\n}\n\n@group(0) @binding(0) var uniforms: ParticleUniforms;\n\n@group(1) @binding(0) var uTexture: texture_2d;\n@group(1) @binding(1) var uSampler : sampler;\n\nstruct VSOutput {\n @builtin(position) position: vec4,\n @location(0) uv : vec2,\n @location(1) color : vec4,\n };\n@vertex\nfn mainVertex(\n @location(0) aVertex: vec2,\n @location(1) aPosition: vec2,\n @location(2) aUV: vec2,\n @location(3) aColor: vec4,\n @location(4) aRotation: f32,\n) -> VSOutput {\n \n let v = vec2(\n aVertex.x * cos(aRotation) - aVertex.y * sin(aRotation),\n aVertex.x * sin(aRotation) + aVertex.y * cos(aRotation)\n ) + aPosition;\n\n var position = vec4((uniforms.uTranslationMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);\n\n if(uniforms.uRound == 1.0) {\n position = vec4(roundPixels(position.xy, uniforms.uResolution), position.zw);\n }\n\n let vColor = vec4(aColor.rgb * aColor.a, aColor.a) * uniforms.uColor;\n\n return VSOutput(\n position,\n aUV,\n vColor,\n );\n}\n\n@fragment\nfn mainFragment(\n @location(0) uv: vec2,\n @location(1) color: vec4,\n @builtin(position) position: vec4,\n) -> @location(0) vec4 {\n\n var sample = textureSample(uTexture, uSampler, uv) * color;\n \n return sample;\n}";
+
+ "use strict";
+ class ParticleShader extends Shader {
+ constructor() {
+ const glProgram = GlProgram.from({
+ vertex: vertex$3,
+ fragment: fragment$5
+ });
+ const gpuProgram = GpuProgram.from({
+ fragment: {
+ source: wgsl,
+ entryPoint: "mainFragment"
+ },
+ vertex: {
+ source: wgsl,
+ entryPoint: "mainVertex"
+ }
+ });
+ super({
+ glProgram,
+ gpuProgram,
+ resources: {
+ // this will be replaced with the texture from the particle container
+ uTexture: Texture.WHITE.source,
+ // this will be replaced with the texture style from the particle container
+ uSampler: new TextureStyle({}),
+ // this will be replaced with the local uniforms from the particle container
+ uniforms: {
+ uTranslationMatrix: { value: new Matrix(), type: "mat3x3" },
+ uColor: { value: new Color(16777215), type: "vec4" },
+ uRound: { value: 1, type: "f32" },
+ uResolution: { value: [0, 0], type: "vec2" }
+ }
+ }
+ });
+ }
+ }
+
+ "use strict";
+ class ParticleContainerPipe {
+ /**
+ * @param renderer - The renderer this sprite batch works for.
+ * @param adaptor
+ */
+ constructor(renderer, adaptor) {
+ /** @internal */
+ this.state = State.for2d();
+ /** Local uniforms that are used for rendering particles. */
+ this.localUniforms = new UniformGroup({
+ uTranslationMatrix: { value: new Matrix(), type: "mat3x3" },
+ uColor: { value: new Float32Array(4), type: "vec4" },
+ uRound: { value: 1, type: "f32" },
+ uResolution: { value: [0, 0], type: "vec2" }
+ });
+ this.renderer = renderer;
+ this.adaptor = adaptor;
+ this.defaultShader = new ParticleShader();
+ this.state = State.for2d();
+ }
+ validateRenderable(_renderable) {
+ return false;
+ }
+ addRenderable(renderable, instructionSet) {
+ this.renderer.renderPipes.batch.break(instructionSet);
+ instructionSet.add(renderable);
+ }
+ getBuffers(renderable) {
+ return renderable._gpuData[this.renderer.uid] || this._initBuffer(renderable);
+ }
+ _initBuffer(renderable) {
+ renderable._gpuData[this.renderer.uid] = new ParticleBuffer({
+ size: renderable.particleChildren.length,
+ properties: renderable._properties
+ });
+ return renderable._gpuData[this.renderer.uid];
+ }
+ updateRenderable(_renderable) {
+ }
+ execute(container) {
+ const children = container.particleChildren;
+ if (children.length === 0) {
+ return;
+ }
+ const renderer = this.renderer;
+ const buffer = this.getBuffers(container);
+ container.texture || (container.texture = children[0].texture);
+ const state = this.state;
+ buffer.update(children, container._childrenDirty);
+ container._childrenDirty = false;
+ state.blendMode = getAdjustedBlendModeBlend(container.blendMode, container.texture._source);
+ const uniforms = this.localUniforms.uniforms;
+ const transformationMatrix = uniforms.uTranslationMatrix;
+ container.worldTransform.copyTo(transformationMatrix);
+ transformationMatrix.prepend(renderer.globalUniforms.globalUniformData.projectionMatrix);
+ uniforms.uResolution = renderer.globalUniforms.globalUniformData.resolution;
+ uniforms.uRound = renderer._roundPixels | container._roundPixels;
+ color32BitToUniform(
+ container.groupColorAlpha,
+ uniforms.uColor,
+ 0
+ );
+ this.adaptor.execute(this, container);
+ }
+ /** Destroys the ParticleRenderer. */
+ destroy() {
+ this.renderer = null;
+ if (this.defaultShader) {
+ this.defaultShader.destroy();
+ this.defaultShader = null;
+ }
+ }
+ }
+
+ "use strict";
+ class GlParticleContainerPipe extends ParticleContainerPipe {
+ constructor(renderer) {
+ super(renderer, new GlParticleContainerAdaptor());
+ }
+ }
+ /** @ignore */
+ GlParticleContainerPipe.extension = {
+ type: [
+ ExtensionType.WebGLPipes
+ ],
+ name: "particle"
+ };
+
+ "use strict";
+ class GpuParticleContainerAdaptor {
+ execute(particleContainerPipe, container) {
+ const renderer = particleContainerPipe.renderer;
+ const shader = container.shader || particleContainerPipe.defaultShader;
+ shader.groups[0] = renderer.renderPipes.uniformBatch.getUniformBindGroup(particleContainerPipe.localUniforms, true);
+ shader.groups[1] = renderer.texture.getTextureBindGroup(container.texture);
+ const state = particleContainerPipe.state;
+ const buffer = particleContainerPipe.getBuffers(container);
+ renderer.encoder.draw({
+ geometry: buffer.geometry,
+ shader: container.shader || particleContainerPipe.defaultShader,
+ state,
+ size: container.particleChildren.length * 6
+ });
+ }
+ }
+
+ "use strict";
+ class GpuParticleContainerPipe extends ParticleContainerPipe {
+ constructor(renderer) {
+ super(renderer, new GpuParticleContainerAdaptor());
+ }
+ }
+ /** @ignore */
+ GpuParticleContainerPipe.extension = {
+ type: [
+ ExtensionType.WebGPUPipes
+ ],
+ name: "particle"
+ };
+
+ "use strict";
+ extensions.add(GlParticleContainerPipe);
+ extensions.add(GpuParticleContainerPipe);
+
+ "use strict";
+ function updateTextBounds(batchableSprite, text) {
+ const { texture, bounds } = batchableSprite;
+ const padding = text._style._getFinalPadding();
+ updateQuadBounds(bounds, text._anchor, texture);
+ const paddingOffset = text._anchor._x * padding * 2;
+ const paddingOffsetY = text._anchor._y * padding * 2;
+ bounds.minX -= padding - paddingOffset;
+ bounds.minY -= padding - paddingOffsetY;
+ bounds.maxX -= padding - paddingOffset;
+ bounds.maxY -= padding - paddingOffsetY;
+ }
+
+ "use strict";
+ class BatchableSprite {
+ constructor() {
+ this.batcherName = "default";
+ this.topology = "triangle-list";
+ // batch specific..
+ this.attributeSize = 4;
+ this.indexSize = 6;
+ this.packAsQuad = true;
+ this.roundPixels = 0;
+ this._attributeStart = 0;
+ // location in the buffer
+ this._batcher = null;
+ this._batch = null;
+ }
+ get blendMode() {
+ return this.renderable.groupBlendMode;
+ }
+ get color() {
+ return this.renderable.groupColorAlpha;
+ }
+ reset() {
+ this.renderable = null;
+ this.texture = null;
+ this._batcher = null;
+ this._batch = null;
+ this.bounds = null;
+ }
+ destroy() {
+ }
+ }
+
+ "use strict";
+ class BatchableText extends BatchableSprite {
+ constructor(renderer) {
+ super();
+ this._renderer = renderer;
+ renderer.runners.resolutionChange.add(this);
+ }
+ resolutionChange() {
+ const text = this.renderable;
+ if (text._autoResolution) {
+ text.onViewUpdate();
+ }
+ }
+ destroy() {
+ const { canvasText } = this._renderer;
+ canvasText.getReferenceCount(this.currentKey) === null ? canvasText.returnTexture(this.texture) : canvasText.decreaseReferenceCount(this.currentKey);
+ this._renderer.runners.resolutionChange.remove(this);
+ this._renderer = null;
+ }
+ }
+
+ "use strict";
+ class CanvasTextPipe {
+ constructor(renderer) {
+ this._renderer = renderer;
+ }
+ validateRenderable(text) {
+ const gpuText = this._getGpuText(text);
+ const newKey = text.styleKey;
+ if (gpuText.currentKey !== newKey)
+ return true;
+ return text._didTextUpdate;
+ }
+ addRenderable(text, instructionSet) {
+ const batchableText = this._getGpuText(text);
+ if (text._didTextUpdate) {
+ const resolution = text._autoResolution ? this._renderer.resolution : text.resolution;
+ if (batchableText.currentKey !== text.styleKey || text.resolution !== resolution) {
+ this._updateGpuText(text);
+ }
+ text._didTextUpdate = false;
+ }
+ this._renderer.renderPipes.batch.addToBatch(batchableText, instructionSet);
+ }
+ updateRenderable(text) {
+ const batchableText = this._getGpuText(text);
+ batchableText._batcher.updateElement(batchableText);
+ }
+ _updateGpuText(text) {
+ const batchableText = this._getGpuText(text);
+ if (batchableText.texture) {
+ this._renderer.canvasText.decreaseReferenceCount(batchableText.currentKey);
+ }
+ text._resolution = text._autoResolution ? this._renderer.resolution : text.resolution;
+ batchableText.texture = this._renderer.canvasText.getManagedTexture(text);
+ batchableText.currentKey = text.styleKey;
+ updateTextBounds(batchableText, text);
+ }
+ _getGpuText(text) {
+ return text._gpuData[this._renderer.uid] || this.initGpuText(text);
+ }
+ initGpuText(text) {
+ const batchableText = new BatchableText(this._renderer);
+ batchableText.currentKey = "--";
+ batchableText.renderable = text;
+ batchableText.transform = text.groupTransform;
+ batchableText.bounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 };
+ batchableText.roundPixels = this._renderer._roundPixels | text._roundPixels;
+ text._gpuData[this._renderer.uid] = batchableText;
+ return batchableText;
+ }
+ destroy() {
+ this._renderer = null;
+ }
+ }
+ /** @ignore */
+ CanvasTextPipe.extension = {
+ type: [
+ ExtensionType.WebGLPipes,
+ ExtensionType.WebGPUPipes,
+ ExtensionType.CanvasPipes
+ ],
+ name: "text"
+ };
+
+ "use strict";
+ const repetitionMap = {
+ repeat: {
+ addressModeU: "repeat",
+ addressModeV: "repeat"
+ },
+ "repeat-x": {
+ addressModeU: "repeat",
+ addressModeV: "clamp-to-edge"
+ },
+ "repeat-y": {
+ addressModeU: "clamp-to-edge",
+ addressModeV: "repeat"
+ },
+ "no-repeat": {
+ addressModeU: "clamp-to-edge",
+ addressModeV: "clamp-to-edge"
+ }
+ };
+ class FillPattern {
+ constructor(texture, repetition) {
+ /**
+ * unique id for this fill pattern
+ * @internal
+ */
+ this.uid = uid$1("fillPattern");
+ /**
+ * Internal tick counter to track changes in the pattern.
+ * This is used to invalidate the pattern when the texture or transform changes.
+ * @internal
+ */
+ this._tick = 0;
+ /** The transform matrix applied to the pattern */
+ this.transform = new Matrix();
+ this.texture = texture;
+ this.transform.scale(
+ 1 / texture.frame.width,
+ 1 / texture.frame.height
+ );
+ if (repetition) {
+ texture.source.style.addressModeU = repetitionMap[repetition].addressModeU;
+ texture.source.style.addressModeV = repetitionMap[repetition].addressModeV;
+ }
+ }
+ /**
+ * Sets the transform for the pattern
+ * @param transform - The transform matrix to apply to the pattern.
+ * If not provided, the pattern will use the default transform.
+ */
+ setTransform(transform) {
+ const texture = this.texture;
+ this.transform.copyFrom(transform);
+ this.transform.invert();
+ this.transform.scale(
+ 1 / texture.frame.width,
+ 1 / texture.frame.height
+ );
+ this._tick++;
+ }
+ /** Internal texture used to render the gradient */
+ get texture() {
+ return this._texture;
+ }
+ set texture(value) {
+ if (this._texture === value)
+ return;
+ this._texture = value;
+ this._tick++;
+ }
+ /**
+ * Returns a unique key for this instance.
+ * This key is used for caching.
+ * @returns {string} Unique key for the instance
+ */
+ get styleKey() {
+ return `fill-pattern-${this.uid}-${this._tick}`;
+ }
+ /** Destroys the fill pattern, releasing resources. This will also destroy the internal texture. */
+ destroy() {
+ this.texture.destroy(true);
+ this.texture = null;
+ }
+ }
+
+ var parseSvgPath = parse;
+
+ /**
+ * expected argument lengths
+ * @type {Object}
+ */
+
+ var length = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0};
+
+ /**
+ * segment pattern
+ * @type {RegExp}
+ */
+
+ var segment = /([astvzqmhlc])([^astvzqmhlc]*)/ig;
+
+ /**
+ * parse an svg path data string. Generates an Array
+ * of commands where each command is an Array of the
+ * form `[command, arg1, arg2, ...]`
+ *
+ * @param {String} path
+ * @return {Array}
+ */
+
+ function parse(path) {
+ var data = [];
+ path.replace(segment, function(_, command, args){
+ var type = command.toLowerCase();
+ args = parseValues(args);
+
+ // overloaded moveTo
+ if (type == 'm' && args.length > 2) {
+ data.push([command].concat(args.splice(0, 2)));
+ type = 'l';
+ command = command == 'm' ? 'l' : 'L';
+ }
+
+ while (true) {
+ if (args.length == length[type]) {
+ args.unshift(command);
+ return data.push(args)
+ }
+ if (args.length < length[type]) throw new Error('malformed path data')
+ data.push([command].concat(args.splice(0, length[type])));
+ }
+ });
+ return data
+ }
+
+ var number = /-?[0-9]*\.?[0-9]+(?:e[-+]?\d+)?/ig;
+
+ function parseValues(args) {
+ var numbers = args.match(number);
+ return numbers ? numbers.map(Number) : []
+ }
+
+ var parse$1 = /*@__PURE__*/getDefaultExportFromCjs(parseSvgPath);
+
+ "use strict";
+ function parseSVGPath(svgPath, path) {
+ const commands = parse$1(svgPath);
+ const subpaths = [];
+ let currentSubPath = null;
+ let lastX = 0;
+ let lastY = 0;
+ for (let i = 0; i < commands.length; i++) {
+ const command = commands[i];
+ const type = command[0];
+ const data = command;
+ switch (type) {
+ case "M":
+ lastX = data[1];
+ lastY = data[2];
+ path.moveTo(lastX, lastY);
+ break;
+ case "m":
+ lastX += data[1];
+ lastY += data[2];
+ path.moveTo(lastX, lastY);
+ break;
+ case "H":
+ lastX = data[1];
+ path.lineTo(lastX, lastY);
+ break;
+ case "h":
+ lastX += data[1];
+ path.lineTo(lastX, lastY);
+ break;
+ case "V":
+ lastY = data[1];
+ path.lineTo(lastX, lastY);
+ break;
+ case "v":
+ lastY += data[1];
+ path.lineTo(lastX, lastY);
+ break;
+ case "L":
+ lastX = data[1];
+ lastY = data[2];
+ path.lineTo(lastX, lastY);
+ break;
+ case "l":
+ lastX += data[1];
+ lastY += data[2];
+ path.lineTo(lastX, lastY);
+ break;
+ case "C":
+ lastX = data[5];
+ lastY = data[6];
+ path.bezierCurveTo(
+ data[1],
+ data[2],
+ // First control point
+ data[3],
+ data[4],
+ // Second control point
+ lastX,
+ lastY
+ // End point
+ );
+ break;
+ case "c":
+ path.bezierCurveTo(
+ lastX + data[1],
+ lastY + data[2],
+ // First control point
+ lastX + data[3],
+ lastY + data[4],
+ // Second control point
+ lastX + data[5],
+ lastY + data[6]
+ // End point
+ );
+ lastX += data[5];
+ lastY += data[6];
+ break;
+ case "S":
+ lastX = data[3];
+ lastY = data[4];
+ path.bezierCurveToShort(
+ data[1],
+ data[2],
+ // Control point
+ lastX,
+ lastY
+ // End point
+ );
+ break;
+ case "s":
+ path.bezierCurveToShort(
+ lastX + data[1],
+ lastY + data[2],
+ // Control point
+ lastX + data[3],
+ lastY + data[4]
+ // End point
+ );
+ lastX += data[3];
+ lastY += data[4];
+ break;
+ case "Q":
+ lastX = data[3];
+ lastY = data[4];
+ path.quadraticCurveTo(
+ data[1],
+ data[2],
+ // Control point
+ lastX,
+ lastY
+ // End point
+ );
+ break;
+ case "q":
+ path.quadraticCurveTo(
+ lastX + data[1],
+ lastY + data[2],
+ // Control point
+ lastX + data[3],
+ lastY + data[4]
+ // End point
+ );
+ lastX += data[3];
+ lastY += data[4];
+ break;
+ case "T":
+ lastX = data[1];
+ lastY = data[2];
+ path.quadraticCurveToShort(
+ lastX,
+ lastY
+ // End point
+ );
+ break;
+ case "t":
+ lastX += data[1];
+ lastY += data[2];
+ path.quadraticCurveToShort(
+ lastX,
+ lastY
+ // End point
+ );
+ break;
+ case "A":
+ lastX = data[6];
+ lastY = data[7];
+ path.arcToSvg(
+ data[1],
+ // rx
+ data[2],
+ // ry
+ data[3],
+ // x-axis-rotation
+ data[4],
+ // large-arc-flag
+ data[5],
+ // sweep-flag
+ lastX,
+ lastY
+ // End point
+ );
+ break;
+ case "a":
+ lastX += data[6];
+ lastY += data[7];
+ path.arcToSvg(
+ data[1],
+ // rx
+ data[2],
+ // ry
+ data[3],
+ // x-axis-rotation
+ data[4],
+ // large-arc-flag
+ data[5],
+ // sweep-flag
+ lastX,
+ lastY
+ // End point
+ );
+ break;
+ case "Z":
+ case "z":
+ path.closePath();
+ if (subpaths.length > 0) {
+ currentSubPath = subpaths.pop();
+ if (currentSubPath) {
+ lastX = currentSubPath.startX;
+ lastY = currentSubPath.startY;
+ } else {
+ lastX = 0;
+ lastY = 0;
+ }
+ }
+ currentSubPath = null;
+ break;
+ default:
+ warn(`Unknown SVG path command: ${type}`);
+ }
+ if (type !== "Z" && type !== "z") {
+ if (currentSubPath === null) {
+ currentSubPath = { startX: lastX, startY: lastY };
+ subpaths.push(currentSubPath);
+ }
+ }
+ }
+ return path;
+ }
+
+ "use strict";
+ class Circle {
+ /**
+ * @param x - The X coordinate of the center of this circle
+ * @param y - The Y coordinate of the center of this circle
+ * @param radius - The radius of the circle
+ */
+ constructor(x = 0, y = 0, radius = 0) {
+ /**
+ * The type of the object, mainly used to avoid `instanceof` checks.
+ * @example
+ * ```ts
+ * // Check shape type
+ * const shape = new Circle(0, 0, 50);
+ * console.log(shape.type); // 'circle'
+ *
+ * // Use in type guards
+ * if (shape.type === 'circle') {
+ * console.log(shape.radius);
+ * }
+ * ```
+ * @remarks
+ * - Used for shape type checking
+ * - More efficient than instanceof
+ * - Read-only property
+ * @readonly
+ * @default 'circle'
+ * @see {@link SHAPE_PRIMITIVE} For all shape types
+ * @see {@link ShapePrimitive} For shape interface
+ */
+ this.type = "circle";
+ this.x = x;
+ this.y = y;
+ this.radius = radius;
+ }
+ /**
+ * Creates a clone of this Circle instance.
+ * @example
+ * ```ts
+ * // Basic circle cloning
+ * const original = new Circle(100, 100, 50);
+ * const copy = original.clone();
+ *
+ * // Clone and modify
+ * const modified = original.clone();
+ * modified.radius = 75;
+ *
+ * // Verify independence
+ * console.log(original.radius); // 50
+ * console.log(modified.radius); // 75
+ * ```
+ * @returns A copy of the Circle
+ * @see {@link Circle.copyFrom} For copying into existing circle
+ * @see {@link Circle.copyTo} For copying to another circle
+ */
+ clone() {
+ return new Circle(this.x, this.y, this.radius);
+ }
+ /**
+ * Checks whether the x and y coordinates given are contained within this circle.
+ *
+ * Uses the distance formula to determine if a point is inside the circle's radius.
+ *
+ * Commonly used for hit testing in PixiJS events and graphics.
+ * @example
+ * ```ts
+ * // Basic containment check
+ * const circle = new Circle(100, 100, 50);
+ * const isInside = circle.contains(120, 120);
+ *
+ * // Check mouse position
+ * const circle = new Circle(0, 0, 100);
+ * container.hitArea = circle;
+ * container.on('pointermove', (e) => {
+ * // only called if pointer is within circle
+ * });
+ * ```
+ * @param x - The X coordinate of the point to test
+ * @param y - The Y coordinate of the point to test
+ * @returns Whether the x/y coordinates are within this Circle
+ * @see {@link Circle.strokeContains} For checking stroke intersection
+ * @see {@link Circle.getBounds} For getting bounding box
+ */
+ contains(x, y) {
+ if (this.radius <= 0)
+ return false;
+ const r2 = this.radius * this.radius;
+ let dx = this.x - x;
+ let dy = this.y - y;
+ dx *= dx;
+ dy *= dy;
+ return dx + dy <= r2;
+ }
+ /**
+ * Checks whether the x and y coordinates given are contained within this circle including the stroke.
+ * @example
+ * ```ts
+ * // Basic stroke check
+ * const circle = new Circle(100, 100, 50);
+ * const isOnStroke = circle.strokeContains(150, 100, 4); // 4px line width
+ *
+ * // Check with different alignments
+ * const innerStroke = circle.strokeContains(150, 100, 4, 1); // Inside
+ * const centerStroke = circle.strokeContains(150, 100, 4, 0.5); // Centered
+ * const outerStroke = circle.strokeContains(150, 100, 4, 0); // Outside
+ * ```
+ * @param x - The X coordinate of the point to test
+ * @param y - The Y coordinate of the point to test
+ * @param width - The width of the line to check
+ * @param alignment - The alignment of the stroke, 0.5 by default
+ * @returns Whether the x/y coordinates are within this Circle's stroke
+ * @see {@link Circle.contains} For checking fill containment
+ * @see {@link Circle.getBounds} For getting stroke bounds
+ */
+ strokeContains(x, y, width, alignment = 0.5) {
+ if (this.radius === 0)
+ return false;
+ const dx = this.x - x;
+ const dy = this.y - y;
+ const radius = this.radius;
+ const outerWidth = (1 - alignment) * width;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+ return distance <= radius + outerWidth && distance > radius - (width - outerWidth);
+ }
+ /**
+ * Returns the framing rectangle of the circle as a Rectangle object.
+ * @example
+ * ```ts
+ * // Basic bounds calculation
+ * const circle = new Circle(100, 100, 50);
+ * const bounds = circle.getBounds();
+ * // bounds: x=50, y=50, width=100, height=100
+ *
+ * // Reuse existing rectangle
+ * const rect = new Rectangle();
+ * circle.getBounds(rect);
+ * ```
+ * @param out - Optional Rectangle object to store the result
+ * @returns The framing rectangle
+ * @see {@link Rectangle} For rectangle properties
+ * @see {@link Circle.contains} For point containment
+ */
+ getBounds(out) {
+ out || (out = new Rectangle());
+ out.x = this.x - this.radius;
+ out.y = this.y - this.radius;
+ out.width = this.radius * 2;
+ out.height = this.radius * 2;
+ return out;
+ }
+ /**
+ * Copies another circle to this one.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new Circle(100, 100, 50);
+ * const target = new Circle();
+ * target.copyFrom(source);
+ * ```
+ * @param circle - The circle to copy from
+ * @returns Returns itself
+ * @see {@link Circle.copyTo} For copying to another circle
+ * @see {@link Circle.clone} For creating new circle copy
+ */
+ copyFrom(circle) {
+ this.x = circle.x;
+ this.y = circle.y;
+ this.radius = circle.radius;
+ return this;
+ }
+ /**
+ * Copies this circle to another one.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new Circle(100, 100, 50);
+ * const target = new Circle();
+ * source.copyTo(target);
+ * ```
+ * @param circle - The circle to copy to
+ * @returns Returns given parameter
+ * @see {@link Circle.copyFrom} For copying from another circle
+ * @see {@link Circle.clone} For creating new circle copy
+ */
+ copyTo(circle) {
+ circle.copyFrom(this);
+ return circle;
+ }
+ toString() {
+ return `[pixi.js/math:Circle x=${this.x} y=${this.y} radius=${this.radius}]`;
+ }
+ }
+
+ "use strict";
+ class Ellipse {
+ /**
+ * @param x - The X coordinate of the center of this ellipse
+ * @param y - The Y coordinate of the center of this ellipse
+ * @param halfWidth - The half width of this ellipse
+ * @param halfHeight - The half height of this ellipse
+ */
+ constructor(x = 0, y = 0, halfWidth = 0, halfHeight = 0) {
+ /**
+ * The type of the object, mainly used to avoid `instanceof` checks
+ * @example
+ * ```ts
+ * // Check shape type
+ * const shape = new Ellipse(0, 0, 50, 25);
+ * console.log(shape.type); // 'ellipse'
+ *
+ * // Use in type guards
+ * if (shape.type === 'ellipse') {
+ * console.log(shape.halfWidth, shape.halfHeight);
+ * }
+ * ```
+ * @readonly
+ * @default 'ellipse'
+ * @see {@link SHAPE_PRIMITIVE} For all shape types
+ */
+ this.type = "ellipse";
+ this.x = x;
+ this.y = y;
+ this.halfWidth = halfWidth;
+ this.halfHeight = halfHeight;
+ }
+ /**
+ * Creates a clone of this Ellipse instance.
+ * @example
+ * ```ts
+ * // Basic cloning
+ * const original = new Ellipse(100, 100, 50, 25);
+ * const copy = original.clone();
+ *
+ * // Clone and modify
+ * const modified = original.clone();
+ * modified.halfWidth *= 2;
+ * modified.halfHeight *= 2;
+ *
+ * // Verify independence
+ * console.log(original.halfWidth); // 50
+ * console.log(modified.halfWidth); // 100
+ * ```
+ * @returns A copy of the ellipse
+ * @see {@link Ellipse.copyFrom} For copying into existing ellipse
+ * @see {@link Ellipse.copyTo} For copying to another ellipse
+ */
+ clone() {
+ return new Ellipse(this.x, this.y, this.halfWidth, this.halfHeight);
+ }
+ /**
+ * Checks whether the x and y coordinates given are contained within this ellipse.
+ * Uses normalized coordinates and the ellipse equation to determine containment.
+ * @example
+ * ```ts
+ * // Basic containment check
+ * const ellipse = new Ellipse(100, 100, 50, 25);
+ * const isInside = ellipse.contains(120, 110);
+ * ```
+ * @remarks
+ * - Uses ellipse equation (x²/a² + y²/b² ≤ 1)
+ * - Returns false if dimensions are 0 or negative
+ * - Normalized to center (0,0) for calculation
+ * @param x - The X coordinate of the point to test
+ * @param y - The Y coordinate of the point to test
+ * @returns Whether the x/y coords are within this ellipse
+ * @see {@link Ellipse.strokeContains} For checking stroke intersection
+ * @see {@link Ellipse.getBounds} For getting containing rectangle
+ */
+ contains(x, y) {
+ if (this.halfWidth <= 0 || this.halfHeight <= 0) {
+ return false;
+ }
+ let normx = (x - this.x) / this.halfWidth;
+ let normy = (y - this.y) / this.halfHeight;
+ normx *= normx;
+ normy *= normy;
+ return normx + normy <= 1;
+ }
+ /**
+ * Checks whether the x and y coordinates given are contained within this ellipse including stroke.
+ * @example
+ * ```ts
+ * // Basic stroke check
+ * const ellipse = new Ellipse(100, 100, 50, 25);
+ * const isOnStroke = ellipse.strokeContains(150, 100, 4); // 4px line width
+ *
+ * // Check with different alignments
+ * const innerStroke = ellipse.strokeContains(150, 100, 4, 1); // Inside
+ * const centerStroke = ellipse.strokeContains(150, 100, 4, 0.5); // Centered
+ * const outerStroke = ellipse.strokeContains(150, 100, 4, 0); // Outside
+ * ```
+ * @remarks
+ * - Uses normalized ellipse equations
+ * - Considers stroke alignment
+ * - Returns false if dimensions are 0
+ * @param x - The X coordinate of the point to test
+ * @param y - The Y coordinate of the point to test
+ * @param strokeWidth - The width of the line to check
+ * @param alignment - The alignment of the stroke (1 = inner, 0.5 = centered, 0 = outer)
+ * @returns Whether the x/y coords are within this ellipse's stroke
+ * @see {@link Ellipse.contains} For checking fill containment
+ * @see {@link Ellipse.getBounds} For getting stroke bounds
+ */
+ strokeContains(x, y, strokeWidth, alignment = 0.5) {
+ const { halfWidth, halfHeight } = this;
+ if (halfWidth <= 0 || halfHeight <= 0) {
+ return false;
+ }
+ const strokeOuterWidth = strokeWidth * (1 - alignment);
+ const strokeInnerWidth = strokeWidth - strokeOuterWidth;
+ const innerHorizontal = halfWidth - strokeInnerWidth;
+ const innerVertical = halfHeight - strokeInnerWidth;
+ const outerHorizontal = halfWidth + strokeOuterWidth;
+ const outerVertical = halfHeight + strokeOuterWidth;
+ const normalizedX = x - this.x;
+ const normalizedY = y - this.y;
+ const innerEllipse = normalizedX * normalizedX / (innerHorizontal * innerHorizontal) + normalizedY * normalizedY / (innerVertical * innerVertical);
+ const outerEllipse = normalizedX * normalizedX / (outerHorizontal * outerHorizontal) + normalizedY * normalizedY / (outerVertical * outerVertical);
+ return innerEllipse > 1 && outerEllipse <= 1;
+ }
+ /**
+ * Returns the framing rectangle of the ellipse as a Rectangle object.
+ * @example
+ * ```ts
+ * // Basic bounds calculation
+ * const ellipse = new Ellipse(100, 100, 50, 25);
+ * const bounds = ellipse.getBounds();
+ * // bounds: x=50, y=75, width=100, height=50
+ *
+ * // Reuse existing rectangle
+ * const rect = new Rectangle();
+ * ellipse.getBounds(rect);
+ * ```
+ * @remarks
+ * - Creates Rectangle if none provided
+ * - Top-left is (x-halfWidth, y-halfHeight)
+ * - Width is halfWidth * 2
+ * - Height is halfHeight * 2
+ * @param out - Optional Rectangle object to store the result
+ * @returns The framing rectangle
+ * @see {@link Rectangle} For rectangle properties
+ * @see {@link Ellipse.contains} For checking if a point is inside
+ */
+ getBounds(out) {
+ out || (out = new Rectangle());
+ out.x = this.x - this.halfWidth;
+ out.y = this.y - this.halfHeight;
+ out.width = this.halfWidth * 2;
+ out.height = this.halfHeight * 2;
+ return out;
+ }
+ /**
+ * Copies another ellipse to this one.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new Ellipse(100, 100, 50, 25);
+ * const target = new Ellipse();
+ * target.copyFrom(source);
+ * ```
+ * @param ellipse - The ellipse to copy from
+ * @returns Returns itself
+ * @see {@link Ellipse.copyTo} For copying to another ellipse
+ * @see {@link Ellipse.clone} For creating new ellipse copy
+ */
+ copyFrom(ellipse) {
+ this.x = ellipse.x;
+ this.y = ellipse.y;
+ this.halfWidth = ellipse.halfWidth;
+ this.halfHeight = ellipse.halfHeight;
+ return this;
+ }
+ /**
+ * Copies this ellipse to another one.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new Ellipse(100, 100, 50, 25);
+ * const target = new Ellipse();
+ * source.copyTo(target);
+ * ```
+ * @param ellipse - The ellipse to copy to
+ * @returns Returns given parameter
+ * @see {@link Ellipse.copyFrom} For copying from another ellipse
+ * @see {@link Ellipse.clone} For creating new ellipse copy
+ */
+ copyTo(ellipse) {
+ ellipse.copyFrom(this);
+ return ellipse;
+ }
+ toString() {
+ return `[pixi.js/math:Ellipse x=${this.x} y=${this.y} halfWidth=${this.halfWidth} halfHeight=${this.halfHeight}]`;
+ }
+ }
+
+ "use strict";
+ function squaredDistanceToLineSegment(x, y, x1, y1, x2, y2) {
+ const a = x - x1;
+ const b = y - y1;
+ const c = x2 - x1;
+ const d = y2 - y1;
+ const dot = a * c + b * d;
+ const lenSq = c * c + d * d;
+ let param = -1;
+ if (lenSq !== 0) {
+ param = dot / lenSq;
+ }
+ let xx;
+ let yy;
+ if (param < 0) {
+ xx = x1;
+ yy = y1;
+ } else if (param > 1) {
+ xx = x2;
+ yy = y2;
+ } else {
+ xx = x1 + param * c;
+ yy = y1 + param * d;
+ }
+ const dx = x - xx;
+ const dy = y - yy;
+ return dx * dx + dy * dy;
+ }
+
+ "use strict";
+ let tempRect$2;
+ let tempRect2;
+ class Polygon {
+ /**
+ * @param points - This can be an array of Points
+ * that form the polygon, a flat array of numbers that will be interpreted as [x,y, x,y, ...], or
+ * the arguments passed can be all the points of the polygon e.g.
+ * `new Polygon(new Point(), new Point(), ...)`, or the arguments passed can be flat
+ * x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are Numbers.
+ */
+ constructor(...points) {
+ /**
+ * The type of the object, mainly used to avoid `instanceof` checks
+ * @example
+ * ```ts
+ * // Check shape type
+ * const shape = new Polygon([0, 0, 100, 0, 50, 100]);
+ * console.log(shape.type); // 'polygon'
+ *
+ * // Use in type guards
+ * if (shape.type === 'polygon') {
+ * // TypeScript knows this is a Polygon
+ * console.log(shape.points.length);
+ * }
+ * ```
+ * @readonly
+ * @default 'polygon'
+ * @see {@link SHAPE_PRIMITIVE} For all shape types
+ */
+ this.type = "polygon";
+ let flat = Array.isArray(points[0]) ? points[0] : points;
+ if (typeof flat[0] !== "number") {
+ const p = [];
+ for (let i = 0, il = flat.length; i < il; i++) {
+ p.push(flat[i].x, flat[i].y);
+ }
+ flat = p;
+ }
+ this.points = flat;
+ this.closePath = true;
+ }
+ /**
+ * Determines whether the polygon's points are arranged in a clockwise direction.
+ * Uses the shoelace formula (surveyor's formula) to calculate the signed area.
+ *
+ * A positive area indicates clockwise winding, while negative indicates counter-clockwise.
+ *
+ * The formula sums up the cross products of adjacent vertices:
+ * For each pair of adjacent points (x1,y1) and (x2,y2), we calculate (x1*y2 - x2*y1)
+ * The final sum divided by 2 gives the signed area - positive for clockwise.
+ * @example
+ * ```ts
+ * // Check polygon winding
+ * const polygon = new Polygon([0, 0, 100, 0, 50, 100]);
+ * console.log(polygon.isClockwise()); // Check direction
+ *
+ * // Use in path construction
+ * const hole = new Polygon([25, 25, 75, 25, 75, 75, 25, 75]);
+ * if (hole.isClockwise() === shape.isClockwise()) {
+ * hole.points.reverse(); // Reverse for proper hole winding
+ * }
+ * ```
+ * @returns `true` if the polygon's points are arranged clockwise, `false` if counter-clockwise
+ */
+ isClockwise() {
+ let area = 0;
+ const points = this.points;
+ const length = points.length;
+ for (let i = 0; i < length; i += 2) {
+ const x1 = points[i];
+ const y1 = points[i + 1];
+ const x2 = points[(i + 2) % length];
+ const y2 = points[(i + 3) % length];
+ area += (x2 - x1) * (y2 + y1);
+ }
+ return area < 0;
+ }
+ /**
+ * Checks if this polygon completely contains another polygon.
+ * Used for detecting holes in shapes, like when parsing SVG paths.
+ * @example
+ * ```ts
+ * // Basic containment check
+ * const outerSquare = new Polygon([0,0, 100,0, 100,100, 0,100]); // A square
+ * const innerSquare = new Polygon([25,25, 75,25, 75,75, 25,75]); // A smaller square inside
+ *
+ * outerSquare.containsPolygon(innerSquare); // Returns true
+ * innerSquare.containsPolygon(outerSquare); // Returns false
+ * ```
+ * @remarks
+ * - Uses bounds check for quick rejection
+ * - Tests all points for containment
+ * @param polygon - The polygon to test for containment
+ * @returns True if this polygon completely contains the other polygon
+ * @see {@link Polygon.contains} For single point testing
+ * @see {@link Polygon.getBounds} For bounds calculation
+ */
+ containsPolygon(polygon) {
+ const thisBounds = this.getBounds(tempRect$2);
+ const otherBounds = polygon.getBounds(tempRect2);
+ if (!thisBounds.containsRect(otherBounds)) {
+ return false;
+ }
+ const points = polygon.points;
+ for (let i = 0; i < points.length; i += 2) {
+ const x = points[i];
+ const y = points[i + 1];
+ if (!this.contains(x, y)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ /**
+ * Creates a clone of this polygon.
+ * @example
+ * ```ts
+ * // Basic cloning
+ * const original = new Polygon([0, 0, 100, 0, 50, 100]);
+ * const copy = original.clone();
+ *
+ * // Clone and modify
+ * const modified = original.clone();
+ * modified.points[0] = 10; // Modify first x coordinate
+ * ```
+ * @returns A copy of the polygon
+ * @see {@link Polygon.copyFrom} For copying into existing polygon
+ * @see {@link Polygon.copyTo} For copying to another polygon
+ */
+ clone() {
+ const points = this.points.slice();
+ const polygon = new Polygon(points);
+ polygon.closePath = this.closePath;
+ return polygon;
+ }
+ /**
+ * Checks whether the x and y coordinates passed to this function are contained within this polygon.
+ * Uses raycasting algorithm for point-in-polygon testing.
+ * @example
+ * ```ts
+ * // Basic containment check
+ * const polygon = new Polygon([0, 0, 100, 0, 50, 100]);
+ * const isInside = polygon.contains(25, 25); // true
+ * ```
+ * @param x - The X coordinate of the point to test
+ * @param y - The Y coordinate of the point to test
+ * @returns Whether the x/y coordinates are within this polygon
+ * @see {@link Polygon.strokeContains} For checking stroke intersection
+ * @see {@link Polygon.containsPolygon} For polygon-in-polygon testing
+ */
+ contains(x, y) {
+ let inside = false;
+ const length = this.points.length / 2;
+ for (let i = 0, j = length - 1; i < length; j = i++) {
+ const xi = this.points[i * 2];
+ const yi = this.points[i * 2 + 1];
+ const xj = this.points[j * 2];
+ const yj = this.points[j * 2 + 1];
+ const intersect = yi > y !== yj > y && x < (xj - xi) * ((y - yi) / (yj - yi)) + xi;
+ if (intersect) {
+ inside = !inside;
+ }
+ }
+ return inside;
+ }
+ /**
+ * Checks whether the x and y coordinates given are contained within this polygon including the stroke.
+ * @example
+ * ```ts
+ * // Basic stroke check
+ * const polygon = new Polygon([0, 0, 100, 0, 50, 100]);
+ * const isOnStroke = polygon.strokeContains(25, 25, 4); // 4px line width
+ *
+ * // Check with different alignments
+ * const innerStroke = polygon.strokeContains(25, 25, 4, 1); // Inside
+ * const centerStroke = polygon.strokeContains(25, 25, 4, 0.5); // Centered
+ * const outerStroke = polygon.strokeContains(25, 25, 4, 0); // Outside
+ * ```
+ * @param x - The X coordinate of the point to test
+ * @param y - The Y coordinate of the point to test
+ * @param strokeWidth - The width of the line to check
+ * @param alignment - The alignment of the stroke (1 = inner, 0.5 = centered, 0 = outer)
+ * @returns Whether the x/y coordinates are within this polygon's stroke
+ * @see {@link Polygon.contains} For checking fill containment
+ * @see {@link Polygon.getBounds} For getting stroke bounds
+ */
+ strokeContains(x, y, strokeWidth, alignment = 0.5) {
+ const strokeWidthSquared = strokeWidth * strokeWidth;
+ const rightWidthSquared = strokeWidthSquared * (1 - alignment);
+ const leftWidthSquared = strokeWidthSquared - rightWidthSquared;
+ const { points } = this;
+ const iterationLength = points.length - (this.closePath ? 0 : 2);
+ for (let i = 0; i < iterationLength; i += 2) {
+ const x1 = points[i];
+ const y1 = points[i + 1];
+ const x2 = points[(i + 2) % points.length];
+ const y2 = points[(i + 3) % points.length];
+ const distanceSquared = squaredDistanceToLineSegment(x, y, x1, y1, x2, y2);
+ const sign = Math.sign((x2 - x1) * (y - y1) - (y2 - y1) * (x - x1));
+ if (distanceSquared <= (sign < 0 ? leftWidthSquared : rightWidthSquared)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ /**
+ * Returns the framing rectangle of the polygon as a Rectangle object.
+ * @example
+ * ```ts
+ * // Basic bounds calculation
+ * const polygon = new Polygon([0, 0, 100, 0, 50, 100]);
+ * const bounds = polygon.getBounds();
+ * // bounds: x=0, y=0, width=100, height=100
+ *
+ * // Reuse existing rectangle
+ * const rect = new Rectangle();
+ * polygon.getBounds(rect);
+ * ```
+ * @param out - Optional rectangle to store the result
+ * @returns The framing rectangle
+ * @see {@link Rectangle} For rectangle properties
+ * @see {@link Polygon.contains} For checking if a point is inside
+ */
+ getBounds(out) {
+ out || (out = new Rectangle());
+ const points = this.points;
+ let minX = Infinity;
+ let maxX = -Infinity;
+ let minY = Infinity;
+ let maxY = -Infinity;
+ for (let i = 0, n = points.length; i < n; i += 2) {
+ const x = points[i];
+ const y = points[i + 1];
+ minX = x < minX ? x : minX;
+ maxX = x > maxX ? x : maxX;
+ minY = y < minY ? y : minY;
+ maxY = y > maxY ? y : maxY;
+ }
+ out.x = minX;
+ out.width = maxX - minX;
+ out.y = minY;
+ out.height = maxY - minY;
+ return out;
+ }
+ /**
+ * Copies another polygon to this one.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new Polygon([0, 0, 100, 0, 50, 100]);
+ * const target = new Polygon();
+ * target.copyFrom(source);
+ * ```
+ * @param polygon - The polygon to copy from
+ * @returns Returns itself
+ * @see {@link Polygon.copyTo} For copying to another polygon
+ * @see {@link Polygon.clone} For creating new polygon copy
+ */
+ copyFrom(polygon) {
+ this.points = polygon.points.slice();
+ this.closePath = polygon.closePath;
+ return this;
+ }
+ /**
+ * Copies this polygon to another one.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new Polygon([0, 0, 100, 0, 50, 100]);
+ * const target = new Polygon();
+ * source.copyTo(target);
+ * ```
+ * @param polygon - The polygon to copy to
+ * @returns Returns given parameter
+ * @see {@link Polygon.copyFrom} For copying from another polygon
+ * @see {@link Polygon.clone} For creating new polygon copy
+ */
+ copyTo(polygon) {
+ polygon.copyFrom(this);
+ return polygon;
+ }
+ toString() {
+ return `[pixi.js/math:PolygoncloseStroke=${this.closePath}points=${this.points.reduce((pointsDesc, currentPoint) => `${pointsDesc}, ${currentPoint}`, "")}]`;
+ }
+ /**
+ * Get the last X coordinate of the polygon.
+ * @example
+ * ```ts
+ * // Basic coordinate access
+ * const polygon = new Polygon([0, 0, 100, 200, 300, 400]);
+ * console.log(polygon.lastX); // 300
+ * ```
+ * @readonly
+ * @returns The x-coordinate of the last vertex
+ * @see {@link Polygon.lastY} For last Y coordinate
+ * @see {@link Polygon.points} For raw points array
+ */
+ get lastX() {
+ return this.points[this.points.length - 2];
+ }
+ /**
+ * Get the last Y coordinate of the polygon.
+ * @example
+ * ```ts
+ * // Basic coordinate access
+ * const polygon = new Polygon([0, 0, 100, 200, 300, 400]);
+ * console.log(polygon.lastY); // 400
+ * ```
+ * @readonly
+ * @returns The y-coordinate of the last vertex
+ * @see {@link Polygon.lastX} For last X coordinate
+ * @see {@link Polygon.points} For raw points array
+ */
+ get lastY() {
+ return this.points[this.points.length - 1];
+ }
+ /**
+ * Get the last X coordinate of the polygon.
+ * @readonly
+ * @deprecated since 8.11.0, use {@link Polygon.lastX} instead.
+ */
+ get x() {
+ deprecation("8.11.0", "Polygon.lastX is deprecated, please use Polygon.lastX instead.");
+ return this.points[this.points.length - 2];
+ }
+ /**
+ * Get the last Y coordinate of the polygon.
+ * @readonly
+ * @deprecated since 8.11.0, use {@link Polygon.lastY} instead.
+ */
+ get y() {
+ deprecation("8.11.0", "Polygon.y is deprecated, please use Polygon.lastY instead.");
+ return this.points[this.points.length - 1];
+ }
+ /**
+ * Get the first X coordinate of the polygon.
+ * @example
+ * ```ts
+ * // Basic coordinate access
+ * const polygon = new Polygon([0, 0, 100, 200, 300, 400]);
+ * console.log(polygon.x); // 0
+ * ```
+ * @readonly
+ * @returns The x-coordinate of the first vertex
+ * @see {@link Polygon.startY} For first Y coordinate
+ * @see {@link Polygon.points} For raw points array
+ */
+ get startX() {
+ return this.points[0];
+ }
+ /**
+ * Get the first Y coordinate of the polygon.
+ * @example
+ * ```ts
+ * // Basic coordinate access
+ * const polygon = new Polygon([0, 0, 100, 200, 300, 400]);
+ * console.log(polygon.y); // 0
+ * ```
+ * @readonly
+ * @returns The y-coordinate of the first vertex
+ * @see {@link Polygon.startX} For first X coordinate
+ * @see {@link Polygon.points} For raw points array
+ */
+ get startY() {
+ return this.points[1];
+ }
+ }
+
+ "use strict";
+ const isCornerWithinStroke = (pX, pY, cornerX, cornerY, radius, strokeWidthInner, strokeWidthOuter) => {
+ const dx = pX - cornerX;
+ const dy = pY - cornerY;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+ return distance >= radius - strokeWidthInner && distance <= radius + strokeWidthOuter;
+ };
+ class RoundedRectangle {
+ /**
+ * @param x - The X coordinate of the upper-left corner of the rounded rectangle
+ * @param y - The Y coordinate of the upper-left corner of the rounded rectangle
+ * @param width - The overall width of this rounded rectangle
+ * @param height - The overall height of this rounded rectangle
+ * @param radius - Controls the radius of the rounded corners
+ */
+ constructor(x = 0, y = 0, width = 0, height = 0, radius = 20) {
+ /**
+ * The type of the object, mainly used to avoid `instanceof` checks
+ * @example
+ * ```ts
+ * // Check shape type
+ * const shape = new RoundedRectangle(0, 0, 100, 100, 20);
+ * console.log(shape.type); // 'roundedRectangle'
+ *
+ * // Use in type guards
+ * if (shape.type === 'roundedRectangle') {
+ * console.log(shape.radius);
+ * }
+ * ```
+ * @readonly
+ * @default 'roundedRectangle'
+ * @see {@link SHAPE_PRIMITIVE} For all shape types
+ */
+ this.type = "roundedRectangle";
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ this.radius = radius;
+ }
+ /**
+ * Returns the framing rectangle of the rounded rectangle as a Rectangle object
+ * @example
+ * ```ts
+ * // Basic bounds calculation
+ * const rect = new RoundedRectangle(100, 100, 200, 150, 20);
+ * const bounds = rect.getBounds();
+ * // bounds: x=100, y=100, width=200, height=150
+ *
+ * // Reuse existing rectangle
+ * const out = new Rectangle();
+ * rect.getBounds(out);
+ * ```
+ * @remarks
+ * - Rectangle matches outer dimensions
+ * - Ignores corner radius
+ * @param out - Optional rectangle to store the result
+ * @returns The framing rectangle
+ * @see {@link Rectangle} For rectangle properties
+ * @see {@link RoundedRectangle.contains} For checking if a point is inside
+ */
+ getBounds(out) {
+ out || (out = new Rectangle());
+ out.x = this.x;
+ out.y = this.y;
+ out.width = this.width;
+ out.height = this.height;
+ return out;
+ }
+ /**
+ * Creates a clone of this Rounded Rectangle.
+ * @example
+ * ```ts
+ * // Basic cloning
+ * const original = new RoundedRectangle(100, 100, 200, 150, 20);
+ * const copy = original.clone();
+ *
+ * // Clone and modify
+ * const modified = original.clone();
+ * modified.radius = 30;
+ * modified.width *= 2;
+ *
+ * // Verify independence
+ * console.log(original.radius); // 20
+ * console.log(modified.radius); // 30
+ * ```
+ * @returns A copy of the rounded rectangle
+ * @see {@link RoundedRectangle.copyFrom} For copying into existing rectangle
+ * @see {@link RoundedRectangle.copyTo} For copying to another rectangle
+ */
+ clone() {
+ return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius);
+ }
+ /**
+ * Copies another rectangle to this one.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new RoundedRectangle(100, 100, 200, 150, 20);
+ * const target = new RoundedRectangle();
+ * target.copyFrom(source);
+ *
+ * // Chain with other operations
+ * const rect = new RoundedRectangle()
+ * .copyFrom(source)
+ * .getBounds(rect);
+ * ```
+ * @param rectangle - The rectangle to copy from
+ * @returns Returns itself
+ * @see {@link RoundedRectangle.copyTo} For copying to another rectangle
+ * @see {@link RoundedRectangle.clone} For creating new rectangle copy
+ */
+ copyFrom(rectangle) {
+ this.x = rectangle.x;
+ this.y = rectangle.y;
+ this.width = rectangle.width;
+ this.height = rectangle.height;
+ return this;
+ }
+ /**
+ * Copies this rectangle to another one.
+ * @example
+ * ```ts
+ * // Basic copying
+ * const source = new RoundedRectangle(100, 100, 200, 150, 20);
+ * const target = new RoundedRectangle();
+ * source.copyTo(target);
+ *
+ * // Chain with other operations
+ * const result = source
+ * .copyTo(new RoundedRectangle())
+ * .getBounds();
+ * ```
+ * @param rectangle - The rectangle to copy to
+ * @returns Returns given parameter
+ * @see {@link RoundedRectangle.copyFrom} For copying from another rectangle
+ * @see {@link RoundedRectangle.clone} For creating new rectangle copy
+ */
+ copyTo(rectangle) {
+ rectangle.copyFrom(this);
+ return rectangle;
+ }
+ /**
+ * Checks whether the x and y coordinates given are contained within this Rounded Rectangle
+ * @example
+ * ```ts
+ * // Basic containment check
+ * const rect = new RoundedRectangle(100, 100, 200, 150, 20);
+ * const isInside = rect.contains(150, 125); // true
+ * // Check corner radius
+ * const corner = rect.contains(100, 100); // false if within corner curve
+ * ```
+ * @remarks
+ * - Returns false if width/height is 0 or negative
+ * - Handles rounded corners with radius check
+ * @param x - The X coordinate of the point to test
+ * @param y - The Y coordinate of the point to test
+ * @returns Whether the x/y coordinates are within this Rounded Rectangle
+ * @see {@link RoundedRectangle.strokeContains} For checking stroke intersection
+ * @see {@link RoundedRectangle.getBounds} For getting containing rectangle
+ */
+ contains(x, y) {
+ if (this.width <= 0 || this.height <= 0) {
+ return false;
+ }
+ if (x >= this.x && x <= this.x + this.width) {
+ if (y >= this.y && y <= this.y + this.height) {
+ const radius = Math.max(0, Math.min(this.radius, Math.min(this.width, this.height) / 2));
+ if (y >= this.y + radius && y <= this.y + this.height - radius || x >= this.x + radius && x <= this.x + this.width - radius) {
+ return true;
+ }
+ let dx = x - (this.x + radius);
+ let dy = y - (this.y + radius);
+ const radius2 = radius * radius;
+ if (dx * dx + dy * dy <= radius2) {
+ return true;
+ }
+ dx = x - (this.x + this.width - radius);
+ if (dx * dx + dy * dy <= radius2) {
+ return true;
+ }
+ dy = y - (this.y + this.height - radius);
+ if (dx * dx + dy * dy <= radius2) {
+ return true;
+ }
+ dx = x - (this.x + radius);
+ if (dx * dx + dy * dy <= radius2) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ /**
+ * Checks whether the x and y coordinates given are contained within this rectangle including the stroke.
+ * @example
+ * ```ts
+ * // Basic stroke check
+ * const rect = new RoundedRectangle(100, 100, 200, 150, 20);
+ * const isOnStroke = rect.strokeContains(150, 100, 4); // 4px line width
+ *
+ * // Check with different alignments
+ * const innerStroke = rect.strokeContains(150, 100, 4, 1); // Inside
+ * const centerStroke = rect.strokeContains(150, 100, 4, 0.5); // Centered
+ * const outerStroke = rect.strokeContains(150, 100, 4, 0); // Outside
+ * ```
+ * @param pX - The X coordinate of the point to test
+ * @param pY - The Y coordinate of the point to test
+ * @param strokeWidth - The width of the line to check
+ * @param alignment - The alignment of the stroke (1 = inner, 0.5 = centered, 0 = outer)
+ * @returns Whether the x/y coordinates are within this rectangle's stroke
+ * @see {@link RoundedRectangle.contains} For checking fill containment
+ * @see {@link RoundedRectangle.getBounds} For getting stroke bounds
+ */
+ strokeContains(pX, pY, strokeWidth, alignment = 0.5) {
+ const { x, y, width, height, radius } = this;
+ const strokeWidthOuter = strokeWidth * (1 - alignment);
+ const strokeWidthInner = strokeWidth - strokeWidthOuter;
+ const innerX = x + radius;
+ const innerY = y + radius;
+ const innerWidth = width - radius * 2;
+ const innerHeight = height - radius * 2;
+ const rightBound = x + width;
+ const bottomBound = y + height;
+ if ((pX >= x - strokeWidthOuter && pX <= x + strokeWidthInner || pX >= rightBound - strokeWidthInner && pX <= rightBound + strokeWidthOuter) && pY >= innerY && pY <= innerY + innerHeight) {
+ return true;
+ }
+ if ((pY >= y - strokeWidthOuter && pY <= y + strokeWidthInner || pY >= bottomBound - strokeWidthInner && pY <= bottomBound + strokeWidthOuter) && pX >= innerX && pX <= innerX + innerWidth) {
+ return true;
+ }
+ return (
+ // Top-left
+ pX < innerX && pY < innerY && isCornerWithinStroke(
+ pX,
+ pY,
+ innerX,
+ innerY,
+ radius,
+ strokeWidthInner,
+ strokeWidthOuter
+ ) || pX > rightBound - radius && pY < innerY && isCornerWithinStroke(
+ pX,
+ pY,
+ rightBound - radius,
+ innerY,
+ radius,
+ strokeWidthInner,
+ strokeWidthOuter
+ ) || pX > rightBound - radius && pY > bottomBound - radius && isCornerWithinStroke(
+ pX,
+ pY,
+ rightBound - radius,
+ bottomBound - radius,
+ radius,
+ strokeWidthInner,
+ strokeWidthOuter
+ ) || pX < innerX && pY > bottomBound - radius && isCornerWithinStroke(
+ pX,
+ pY,
+ innerX,
+ bottomBound - radius,
+ radius,
+ strokeWidthInner,
+ strokeWidthOuter
+ )
+ );
+ }
+ toString() {
+ return `[pixi.js/math:RoundedRectangle x=${this.x} y=${this.y}width=${this.width} height=${this.height} radius=${this.radius}]`;
+ }
+ }
+
+ "use strict";
+ const RECURSION_LIMIT$1 = 8;
+ const FLT_EPSILON$1 = 11920929e-14;
+ const PATH_DISTANCE_EPSILON$1 = 1;
+ const curveAngleToleranceEpsilon$1 = 0.01;
+ const mAngleTolerance$1 = 0;
+ const mCuspLimit = 0;
+ function buildAdaptiveBezier(points, sX, sY, cp1x, cp1y, cp2x, cp2y, eX, eY, smoothness) {
+ const scale = 1;
+ const smoothing = Math.min(
+ 0.99,
+ // a value of 1.0 actually inverts smoothing, so we cap it at 0.99
+ Math.max(0, smoothness != null ? smoothness : GraphicsContextSystem.defaultOptions.bezierSmoothness)
+ );
+ let distanceTolerance = (PATH_DISTANCE_EPSILON$1 - smoothing) / scale;
+ distanceTolerance *= distanceTolerance;
+ begin$1(sX, sY, cp1x, cp1y, cp2x, cp2y, eX, eY, points, distanceTolerance);
+ return points;
+ }
+ function begin$1(sX, sY, cp1x, cp1y, cp2x, cp2y, eX, eY, points, distanceTolerance) {
+ recursive$1(sX, sY, cp1x, cp1y, cp2x, cp2y, eX, eY, points, distanceTolerance, 0);
+ points.push(eX, eY);
+ }
+ function recursive$1(x1, y1, x2, y2, x3, y3, x4, y4, points, distanceTolerance, level) {
+ if (level > RECURSION_LIMIT$1) {
+ return;
+ }
+ const pi = Math.PI;
+ const x12 = (x1 + x2) / 2;
+ const y12 = (y1 + y2) / 2;
+ const x23 = (x2 + x3) / 2;
+ const y23 = (y2 + y3) / 2;
+ const x34 = (x3 + x4) / 2;
+ const y34 = (y3 + y4) / 2;
+ const x123 = (x12 + x23) / 2;
+ const y123 = (y12 + y23) / 2;
+ const x234 = (x23 + x34) / 2;
+ const y234 = (y23 + y34) / 2;
+ const x1234 = (x123 + x234) / 2;
+ const y1234 = (y123 + y234) / 2;
+ if (level > 0) {
+ let dx = x4 - x1;
+ let dy = y4 - y1;
+ const d2 = Math.abs((x2 - x4) * dy - (y2 - y4) * dx);
+ const d3 = Math.abs((x3 - x4) * dy - (y3 - y4) * dx);
+ let da1;
+ let da2;
+ if (d2 > FLT_EPSILON$1 && d3 > FLT_EPSILON$1) {
+ if ((d2 + d3) * (d2 + d3) <= distanceTolerance * (dx * dx + dy * dy)) {
+ if (mAngleTolerance$1 < curveAngleToleranceEpsilon$1) {
+ points.push(x1234, y1234);
+ return;
+ }
+ const a23 = Math.atan2(y3 - y2, x3 - x2);
+ da1 = Math.abs(a23 - Math.atan2(y2 - y1, x2 - x1));
+ da2 = Math.abs(Math.atan2(y4 - y3, x4 - x3) - a23);
+ if (da1 >= pi)
+ da1 = 2 * pi - da1;
+ if (da2 >= pi)
+ da2 = 2 * pi - da2;
+ if (da1 + da2 < mAngleTolerance$1) {
+ points.push(x1234, y1234);
+ return;
+ }
+ if (mCuspLimit !== 0) {
+ if (da1 > mCuspLimit) {
+ points.push(x2, y2);
+ return;
+ }
+ if (da2 > mCuspLimit) {
+ points.push(x3, y3);
+ return;
+ }
+ }
+ }
+ } else if (d2 > FLT_EPSILON$1) {
+ if (d2 * d2 <= distanceTolerance * (dx * dx + dy * dy)) {
+ if (mAngleTolerance$1 < curveAngleToleranceEpsilon$1) {
+ points.push(x1234, y1234);
+ return;
+ }
+ da1 = Math.abs(Math.atan2(y3 - y2, x3 - x2) - Math.atan2(y2 - y1, x2 - x1));
+ if (da1 >= pi)
+ da1 = 2 * pi - da1;
+ if (da1 < mAngleTolerance$1) {
+ points.push(x2, y2);
+ points.push(x3, y3);
+ return;
+ }
+ if (mCuspLimit !== 0) {
+ if (da1 > mCuspLimit) {
+ points.push(x2, y2);
+ return;
+ }
+ }
+ }
+ } else if (d3 > FLT_EPSILON$1) {
+ if (d3 * d3 <= distanceTolerance * (dx * dx + dy * dy)) {
+ if (mAngleTolerance$1 < curveAngleToleranceEpsilon$1) {
+ points.push(x1234, y1234);
+ return;
+ }
+ da1 = Math.abs(Math.atan2(y4 - y3, x4 - x3) - Math.atan2(y3 - y2, x3 - x2));
+ if (da1 >= pi)
+ da1 = 2 * pi - da1;
+ if (da1 < mAngleTolerance$1) {
+ points.push(x2, y2);
+ points.push(x3, y3);
+ return;
+ }
+ if (mCuspLimit !== 0) {
+ if (da1 > mCuspLimit) {
+ points.push(x3, y3);
+ return;
+ }
+ }
+ }
+ } else {
+ dx = x1234 - (x1 + x4) / 2;
+ dy = y1234 - (y1 + y4) / 2;
+ if (dx * dx + dy * dy <= distanceTolerance) {
+ points.push(x1234, y1234);
+ return;
+ }
+ }
+ }
+ recursive$1(x1, y1, x12, y12, x123, y123, x1234, y1234, points, distanceTolerance, level + 1);
+ recursive$1(x1234, y1234, x234, y234, x34, y34, x4, y4, points, distanceTolerance, level + 1);
+ }
+
+ "use strict";
+ const RECURSION_LIMIT = 8;
+ const FLT_EPSILON = 11920929e-14;
+ const PATH_DISTANCE_EPSILON = 1;
+ const curveAngleToleranceEpsilon = 0.01;
+ const mAngleTolerance = 0;
+ function buildAdaptiveQuadratic(points, sX, sY, cp1x, cp1y, eX, eY, smoothness) {
+ const scale = 1;
+ const smoothing = Math.min(
+ 0.99,
+ // a value of 1.0 actually inverts smoothing, so we cap it at 0.99
+ Math.max(0, smoothness != null ? smoothness : GraphicsContextSystem.defaultOptions.bezierSmoothness)
+ );
+ let distanceTolerance = (PATH_DISTANCE_EPSILON - smoothing) / scale;
+ distanceTolerance *= distanceTolerance;
+ begin(sX, sY, cp1x, cp1y, eX, eY, points, distanceTolerance);
+ return points;
+ }
+ function begin(sX, sY, cp1x, cp1y, eX, eY, points, distanceTolerance) {
+ recursive(points, sX, sY, cp1x, cp1y, eX, eY, distanceTolerance, 0);
+ points.push(eX, eY);
+ }
+ function recursive(points, x1, y1, x2, y2, x3, y3, distanceTolerance, level) {
+ if (level > RECURSION_LIMIT) {
+ return;
+ }
+ const pi = Math.PI;
+ const x12 = (x1 + x2) / 2;
+ const y12 = (y1 + y2) / 2;
+ const x23 = (x2 + x3) / 2;
+ const y23 = (y2 + y3) / 2;
+ const x123 = (x12 + x23) / 2;
+ const y123 = (y12 + y23) / 2;
+ let dx = x3 - x1;
+ let dy = y3 - y1;
+ const d = Math.abs((x2 - x3) * dy - (y2 - y3) * dx);
+ if (d > FLT_EPSILON) {
+ if (d * d <= distanceTolerance * (dx * dx + dy * dy)) {
+ if (mAngleTolerance < curveAngleToleranceEpsilon) {
+ points.push(x123, y123);
+ return;
+ }
+ let da = Math.abs(Math.atan2(y3 - y2, x3 - x2) - Math.atan2(y2 - y1, x2 - x1));
+ if (da >= pi)
+ da = 2 * pi - da;
+ if (da < mAngleTolerance) {
+ points.push(x123, y123);
+ return;
+ }
+ }
+ } else {
+ dx = x123 - (x1 + x3) / 2;
+ dy = y123 - (y1 + y3) / 2;
+ if (dx * dx + dy * dy <= distanceTolerance) {
+ points.push(x123, y123);
+ return;
+ }
+ }
+ recursive(points, x1, y1, x12, y12, x123, y123, distanceTolerance, level + 1);
+ recursive(points, x123, y123, x23, y23, x3, y3, distanceTolerance, level + 1);
+ }
+
+ "use strict";
+ function buildArc(points, x, y, radius, start, end, clockwise, steps) {
+ let dist = Math.abs(start - end);
+ if (!clockwise && start > end) {
+ dist = 2 * Math.PI - dist;
+ } else if (clockwise && end > start) {
+ dist = 2 * Math.PI - dist;
+ }
+ steps || (steps = Math.max(6, Math.floor(6 * Math.pow(radius, 1 / 3) * (dist / Math.PI))));
+ steps = Math.max(steps, 3);
+ let f = dist / steps;
+ let t = start;
+ f *= clockwise ? -1 : 1;
+ for (let i = 0; i < steps + 1; i++) {
+ const cs = Math.cos(t);
+ const sn = Math.sin(t);
+ const nx = x + cs * radius;
+ const ny = y + sn * radius;
+ points.push(nx, ny);
+ t += f;
+ }
+ }
+
+ "use strict";
+ function buildArcTo(points, x1, y1, x2, y2, radius) {
+ const fromX = points[points.length - 2];
+ const fromY = points[points.length - 1];
+ const a1 = fromY - y1;
+ const b1 = fromX - x1;
+ const a2 = y2 - y1;
+ const b2 = x2 - x1;
+ const mm = Math.abs(a1 * b2 - b1 * a2);
+ if (mm < 1e-8 || radius === 0) {
+ if (points[points.length - 2] !== x1 || points[points.length - 1] !== y1) {
+ points.push(x1, y1);
+ }
+ return;
+ }
+ const dd = a1 * a1 + b1 * b1;
+ const cc = a2 * a2 + b2 * b2;
+ const tt = a1 * a2 + b1 * b2;
+ const k1 = radius * Math.sqrt(dd) / mm;
+ const k2 = radius * Math.sqrt(cc) / mm;
+ const j1 = k1 * tt / dd;
+ const j2 = k2 * tt / cc;
+ const cx = k1 * b2 + k2 * b1;
+ const cy = k1 * a2 + k2 * a1;
+ const px = b1 * (k2 + j1);
+ const py = a1 * (k2 + j1);
+ const qx = b2 * (k1 + j2);
+ const qy = a2 * (k1 + j2);
+ const startAngle = Math.atan2(py - cy, px - cx);
+ const endAngle = Math.atan2(qy - cy, qx - cx);
+ buildArc(
+ points,
+ cx + x1,
+ cy + y1,
+ radius,
+ startAngle,
+ endAngle,
+ b1 * a2 > b2 * a1
+ );
+ }
+
+ "use strict";
+ const TAU = Math.PI * 2;
+ const out = {
+ centerX: 0,
+ centerY: 0,
+ ang1: 0,
+ ang2: 0
+ };
+ const mapToEllipse = ({ x, y }, rx, ry, cosPhi, sinPhi, centerX, centerY, out2) => {
+ x *= rx;
+ y *= ry;
+ const xp = cosPhi * x - sinPhi * y;
+ const yp = sinPhi * x + cosPhi * y;
+ out2.x = xp + centerX;
+ out2.y = yp + centerY;
+ return out2;
+ };
+ function approxUnitArc(ang1, ang2) {
+ const a1 = ang2 === -1.5707963267948966 ? -0.551915024494 : 4 / 3 * Math.tan(ang2 / 4);
+ const a = ang2 === 1.5707963267948966 ? 0.551915024494 : a1;
+ const x1 = Math.cos(ang1);
+ const y1 = Math.sin(ang1);
+ const x2 = Math.cos(ang1 + ang2);
+ const y2 = Math.sin(ang1 + ang2);
+ return [
+ {
+ x: x1 - y1 * a,
+ y: y1 + x1 * a
+ },
+ {
+ x: x2 + y2 * a,
+ y: y2 - x2 * a
+ },
+ {
+ x: x2,
+ y: y2
+ }
+ ];
+ }
+ const vectorAngle = (ux, uy, vx, vy) => {
+ const sign = ux * vy - uy * vx < 0 ? -1 : 1;
+ let dot = ux * vx + uy * vy;
+ if (dot > 1) {
+ dot = 1;
+ }
+ if (dot < -1) {
+ dot = -1;
+ }
+ return sign * Math.acos(dot);
+ };
+ const getArcCenter = (px, py, cx, cy, rx, ry, largeArcFlag, sweepFlag, sinPhi, cosPhi, pxp, pyp, out2) => {
+ const rxSq = Math.pow(rx, 2);
+ const rySq = Math.pow(ry, 2);
+ const pxpSq = Math.pow(pxp, 2);
+ const pypSq = Math.pow(pyp, 2);
+ let radicant = rxSq * rySq - rxSq * pypSq - rySq * pxpSq;
+ if (radicant < 0) {
+ radicant = 0;
+ }
+ radicant /= rxSq * pypSq + rySq * pxpSq;
+ radicant = Math.sqrt(radicant) * (largeArcFlag === sweepFlag ? -1 : 1);
+ const centerXp = radicant * rx / ry * pyp;
+ const centerYp = radicant * -ry / rx * pxp;
+ const centerX = cosPhi * centerXp - sinPhi * centerYp + (px + cx) / 2;
+ const centerY = sinPhi * centerXp + cosPhi * centerYp + (py + cy) / 2;
+ const vx1 = (pxp - centerXp) / rx;
+ const vy1 = (pyp - centerYp) / ry;
+ const vx2 = (-pxp - centerXp) / rx;
+ const vy2 = (-pyp - centerYp) / ry;
+ const ang1 = vectorAngle(1, 0, vx1, vy1);
+ let ang2 = vectorAngle(vx1, vy1, vx2, vy2);
+ if (sweepFlag === 0 && ang2 > 0) {
+ ang2 -= TAU;
+ }
+ if (sweepFlag === 1 && ang2 < 0) {
+ ang2 += TAU;
+ }
+ out2.centerX = centerX;
+ out2.centerY = centerY;
+ out2.ang1 = ang1;
+ out2.ang2 = ang2;
+ };
+ function buildArcToSvg(points, px, py, cx, cy, rx, ry, xAxisRotation = 0, largeArcFlag = 0, sweepFlag = 0) {
+ if (rx === 0 || ry === 0) {
+ return;
+ }
+ const sinPhi = Math.sin(xAxisRotation * TAU / 360);
+ const cosPhi = Math.cos(xAxisRotation * TAU / 360);
+ const pxp = cosPhi * (px - cx) / 2 + sinPhi * (py - cy) / 2;
+ const pyp = -sinPhi * (px - cx) / 2 + cosPhi * (py - cy) / 2;
+ if (pxp === 0 && pyp === 0) {
+ return;
+ }
+ rx = Math.abs(rx);
+ ry = Math.abs(ry);
+ const lambda = Math.pow(pxp, 2) / Math.pow(rx, 2) + Math.pow(pyp, 2) / Math.pow(ry, 2);
+ if (lambda > 1) {
+ rx *= Math.sqrt(lambda);
+ ry *= Math.sqrt(lambda);
+ }
+ getArcCenter(
+ px,
+ py,
+ cx,
+ cy,
+ rx,
+ ry,
+ largeArcFlag,
+ sweepFlag,
+ sinPhi,
+ cosPhi,
+ pxp,
+ pyp,
+ out
+ );
+ let { ang1, ang2 } = out;
+ const { centerX, centerY } = out;
+ let ratio = Math.abs(ang2) / (TAU / 4);
+ if (Math.abs(1 - ratio) < 1e-7) {
+ ratio = 1;
+ }
+ const segments = Math.max(Math.ceil(ratio), 1);
+ ang2 /= segments;
+ let lastX = points[points.length - 2];
+ let lastY = points[points.length - 1];
+ const outCurvePoint = { x: 0, y: 0 };
+ for (let i = 0; i < segments; i++) {
+ const curve = approxUnitArc(ang1, ang2);
+ const { x: x1, y: y1 } = mapToEllipse(curve[0], rx, ry, cosPhi, sinPhi, centerX, centerY, outCurvePoint);
+ const { x: x2, y: y2 } = mapToEllipse(curve[1], rx, ry, cosPhi, sinPhi, centerX, centerY, outCurvePoint);
+ const { x, y } = mapToEllipse(curve[2], rx, ry, cosPhi, sinPhi, centerX, centerY, outCurvePoint);
+ buildAdaptiveBezier(
+ points,
+ lastX,
+ lastY,
+ x1,
+ y1,
+ x2,
+ y2,
+ x,
+ y
+ );
+ lastX = x;
+ lastY = y;
+ ang1 += ang2;
+ }
+ }
+
+ "use strict";
+ function roundedShapeArc(g, points, radius) {
+ var _a;
+ const vecFrom = (p, pp) => {
+ const x = pp.x - p.x;
+ const y = pp.y - p.y;
+ const len = Math.sqrt(x * x + y * y);
+ const nx = x / len;
+ const ny = y / len;
+ return { len, nx, ny };
+ };
+ const sharpCorner = (i, p) => {
+ if (i === 0) {
+ g.moveTo(p.x, p.y);
+ } else {
+ g.lineTo(p.x, p.y);
+ }
+ };
+ let p1 = points[points.length - 1];
+ for (let i = 0; i < points.length; i++) {
+ const p2 = points[i % points.length];
+ const pRadius = (_a = p2.radius) != null ? _a : radius;
+ if (pRadius <= 0) {
+ sharpCorner(i, p2);
+ p1 = p2;
+ continue;
+ }
+ const p3 = points[(i + 1) % points.length];
+ const v1 = vecFrom(p2, p1);
+ const v2 = vecFrom(p2, p3);
+ if (v1.len < 1e-4 || v2.len < 1e-4) {
+ sharpCorner(i, p2);
+ p1 = p2;
+ continue;
+ }
+ let angle = Math.asin(v1.nx * v2.ny - v1.ny * v2.nx);
+ let radDirection = 1;
+ let drawDirection = false;
+ if (v1.nx * v2.nx - v1.ny * -v2.ny < 0) {
+ if (angle < 0) {
+ angle = Math.PI + angle;
+ } else {
+ angle = Math.PI - angle;
+ radDirection = -1;
+ drawDirection = true;
+ }
+ } else if (angle > 0) {
+ radDirection = -1;
+ drawDirection = true;
+ }
+ const halfAngle = angle / 2;
+ let cRadius;
+ let lenOut = Math.abs(
+ Math.cos(halfAngle) * pRadius / Math.sin(halfAngle)
+ );
+ if (lenOut > Math.min(v1.len / 2, v2.len / 2)) {
+ lenOut = Math.min(v1.len / 2, v2.len / 2);
+ cRadius = Math.abs(lenOut * Math.sin(halfAngle) / Math.cos(halfAngle));
+ } else {
+ cRadius = pRadius;
+ }
+ const cX = p2.x + v2.nx * lenOut + -v2.ny * cRadius * radDirection;
+ const cY = p2.y + v2.ny * lenOut + v2.nx * cRadius * radDirection;
+ const startAngle = Math.atan2(v1.ny, v1.nx) + Math.PI / 2 * radDirection;
+ const endAngle = Math.atan2(v2.ny, v2.nx) - Math.PI / 2 * radDirection;
+ if (i === 0) {
+ g.moveTo(
+ cX + Math.cos(startAngle) * cRadius,
+ cY + Math.sin(startAngle) * cRadius
+ );
+ }
+ g.arc(cX, cY, cRadius, startAngle, endAngle, drawDirection);
+ p1 = p2;
+ }
+ }
+ function roundedShapeQuadraticCurve(g, points, radius, smoothness) {
+ var _a;
+ const distance = (p1, p2) => Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
+ const pointLerp = (p1, p2, t) => ({
+ x: p1.x + (p2.x - p1.x) * t,
+ y: p1.y + (p2.y - p1.y) * t
+ });
+ const numPoints = points.length;
+ for (let i = 0; i < numPoints; i++) {
+ const thisPoint = points[(i + 1) % numPoints];
+ const pRadius = (_a = thisPoint.radius) != null ? _a : radius;
+ if (pRadius <= 0) {
+ if (i === 0) {
+ g.moveTo(thisPoint.x, thisPoint.y);
+ } else {
+ g.lineTo(thisPoint.x, thisPoint.y);
+ }
+ continue;
+ }
+ const lastPoint = points[i];
+ const nextPoint = points[(i + 2) % numPoints];
+ const lastEdgeLength = distance(lastPoint, thisPoint);
+ let start;
+ if (lastEdgeLength < 1e-4) {
+ start = thisPoint;
+ } else {
+ const lastOffsetDistance = Math.min(lastEdgeLength / 2, pRadius);
+ start = pointLerp(
+ thisPoint,
+ lastPoint,
+ lastOffsetDistance / lastEdgeLength
+ );
+ }
+ const nextEdgeLength = distance(nextPoint, thisPoint);
+ let end;
+ if (nextEdgeLength < 1e-4) {
+ end = thisPoint;
+ } else {
+ const nextOffsetDistance = Math.min(nextEdgeLength / 2, pRadius);
+ end = pointLerp(
+ thisPoint,
+ nextPoint,
+ nextOffsetDistance / nextEdgeLength
+ );
+ }
+ if (i === 0) {
+ g.moveTo(start.x, start.y);
+ } else {
+ g.lineTo(start.x, start.y);
+ }
+ g.quadraticCurveTo(thisPoint.x, thisPoint.y, end.x, end.y, smoothness);
+ }
+ }
+
+ "use strict";
+ const tempRectangle = new Rectangle();
+ class ShapePath {
+ constructor(graphicsPath2D) {
+ /** The list of shape primitives that make up the path. */
+ this.shapePrimitives = [];
+ this._currentPoly = null;
+ this._bounds = new Bounds();
+ this._graphicsPath2D = graphicsPath2D;
+ this.signed = graphicsPath2D.checkForHoles;
+ }
+ /**
+ * Sets the starting point for a new sub-path. Any subsequent drawing commands are considered part of this path.
+ * @param x - The x-coordinate for the starting point.
+ * @param y - The y-coordinate for the starting point.
+ * @returns The instance of the current object for chaining.
+ */
+ moveTo(x, y) {
+ this.startPoly(x, y);
+ return this;
+ }
+ /**
+ * Connects the current point to a new point with a straight line. This method updates the current path.
+ * @param x - The x-coordinate of the new point to connect to.
+ * @param y - The y-coordinate of the new point to connect to.
+ * @returns The instance of the current object for chaining.
+ */
+ lineTo(x, y) {
+ this._ensurePoly();
+ const points = this._currentPoly.points;
+ const fromX = points[points.length - 2];
+ const fromY = points[points.length - 1];
+ if (fromX !== x || fromY !== y) {
+ points.push(x, y);
+ }
+ return this;
+ }
+ /**
+ * Adds an arc to the path. The arc is centered at (x, y)
+ * position with radius `radius` starting at `startAngle` and ending at `endAngle`.
+ * @param x - The x-coordinate of the arc's center.
+ * @param y - The y-coordinate of the arc's center.
+ * @param radius - The radius of the arc.
+ * @param startAngle - The starting angle of the arc, in radians.
+ * @param endAngle - The ending angle of the arc, in radians.
+ * @param counterclockwise - Specifies whether the arc should be drawn in the anticlockwise direction. False by default.
+ * @returns The instance of the current object for chaining.
+ */
+ arc(x, y, radius, startAngle, endAngle, counterclockwise) {
+ this._ensurePoly(false);
+ const points = this._currentPoly.points;
+ buildArc(points, x, y, radius, startAngle, endAngle, counterclockwise);
+ return this;
+ }
+ /**
+ * Adds an arc to the path with the arc tangent to the line joining two specified points.
+ * The arc radius is specified by `radius`.
+ * @param x1 - The x-coordinate of the first point.
+ * @param y1 - The y-coordinate of the first point.
+ * @param x2 - The x-coordinate of the second point.
+ * @param y2 - The y-coordinate of the second point.
+ * @param radius - The radius of the arc.
+ * @returns The instance of the current object for chaining.
+ */
+ arcTo(x1, y1, x2, y2, radius) {
+ this._ensurePoly();
+ const points = this._currentPoly.points;
+ buildArcTo(points, x1, y1, x2, y2, radius);
+ return this;
+ }
+ /**
+ * Adds an SVG-style arc to the path, allowing for elliptical arcs based on the SVG spec.
+ * @param rx - The x-radius of the ellipse.
+ * @param ry - The y-radius of the ellipse.
+ * @param xAxisRotation - The rotation of the ellipse's x-axis relative
+ * to the x-axis of the coordinate system, in degrees.
+ * @param largeArcFlag - Determines if the arc should be greater than or less than 180 degrees.
+ * @param sweepFlag - Determines if the arc should be swept in a positive angle direction.
+ * @param x - The x-coordinate of the arc's end point.
+ * @param y - The y-coordinate of the arc's end point.
+ * @returns The instance of the current object for chaining.
+ */
+ arcToSvg(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y) {
+ const points = this._currentPoly.points;
+ buildArcToSvg(
+ points,
+ this._currentPoly.lastX,
+ this._currentPoly.lastY,
+ x,
+ y,
+ rx,
+ ry,
+ xAxisRotation,
+ largeArcFlag,
+ sweepFlag
+ );
+ return this;
+ }
+ /**
+ * Adds a cubic Bezier curve to the path.
+ * It requires three points: the first two are control points and the third one is the end point.
+ * The starting point is the last point in the current path.
+ * @param cp1x - The x-coordinate of the first control point.
+ * @param cp1y - The y-coordinate of the first control point.
+ * @param cp2x - The x-coordinate of the second control point.
+ * @param cp2y - The y-coordinate of the second control point.
+ * @param x - The x-coordinate of the end point.
+ * @param y - The y-coordinate of the end point.
+ * @param smoothness - Optional parameter to adjust the smoothness of the curve.
+ * @returns The instance of the current object for chaining.
+ */
+ bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y, smoothness) {
+ this._ensurePoly();
+ const currentPoly = this._currentPoly;
+ buildAdaptiveBezier(
+ this._currentPoly.points,
+ currentPoly.lastX,
+ currentPoly.lastY,
+ cp1x,
+ cp1y,
+ cp2x,
+ cp2y,
+ x,
+ y,
+ smoothness
+ );
+ return this;
+ }
+ /**
+ * Adds a quadratic curve to the path. It requires two points: the control point and the end point.
+ * The starting point is the last point in the current path.
+ * @param cp1x - The x-coordinate of the control point.
+ * @param cp1y - The y-coordinate of the control point.
+ * @param x - The x-coordinate of the end point.
+ * @param y - The y-coordinate of the end point.
+ * @param smoothing - Optional parameter to adjust the smoothness of the curve.
+ * @returns The instance of the current object for chaining.
+ */
+ quadraticCurveTo(cp1x, cp1y, x, y, smoothing) {
+ this._ensurePoly();
+ const currentPoly = this._currentPoly;
+ buildAdaptiveQuadratic(
+ this._currentPoly.points,
+ currentPoly.lastX,
+ currentPoly.lastY,
+ cp1x,
+ cp1y,
+ x,
+ y,
+ smoothing
+ );
+ return this;
+ }
+ /**
+ * Closes the current path by drawing a straight line back to the start.
+ * If the shape is already closed or there are no points in the path, this method does nothing.
+ * @returns The instance of the current object for chaining.
+ */
+ closePath() {
+ this.endPoly(true);
+ return this;
+ }
+ /**
+ * Adds another path to the current path. This method allows for the combination of multiple paths into one.
+ * @param path - The `GraphicsPath` object representing the path to add.
+ * @param transform - An optional `Matrix` object to apply a transformation to the path before adding it.
+ * @returns The instance of the current object for chaining.
+ */
+ addPath(path, transform) {
+ this.endPoly();
+ if (transform && !transform.isIdentity()) {
+ path = path.clone(true);
+ path.transform(transform);
+ }
+ const shapePrimitives = this.shapePrimitives;
+ const start = shapePrimitives.length;
+ for (let i = 0; i < path.instructions.length; i++) {
+ const instruction = path.instructions[i];
+ this[instruction.action](...instruction.data);
+ }
+ if (path.checkForHoles && shapePrimitives.length - start > 1) {
+ let mainShape = null;
+ for (let i = start; i < shapePrimitives.length; i++) {
+ const shapePrimitive = shapePrimitives[i];
+ if (shapePrimitive.shape.type === "polygon") {
+ const polygon = shapePrimitive.shape;
+ const mainPolygon = mainShape == null ? void 0 : mainShape.shape;
+ if (mainPolygon && mainPolygon.containsPolygon(polygon)) {
+ mainShape.holes || (mainShape.holes = []);
+ mainShape.holes.push(shapePrimitive);
+ shapePrimitives.copyWithin(i, i + 1);
+ shapePrimitives.length--;
+ i--;
+ } else {
+ mainShape = shapePrimitive;
+ }
+ }
+ }
+ }
+ return this;
+ }
+ /**
+ * Finalizes the drawing of the current path. Optionally, it can close the path.
+ * @param closePath - A boolean indicating whether to close the path after finishing. False by default.
+ */
+ finish(closePath = false) {
+ this.endPoly(closePath);
+ }
+ /**
+ * Draws a rectangle shape. This method adds a new rectangle path to the current drawing.
+ * @param x - The x-coordinate of the top-left corner of the rectangle.
+ * @param y - The y-coordinate of the top-left corner of the rectangle.
+ * @param w - The width of the rectangle.
+ * @param h - The height of the rectangle.
+ * @param transform - An optional `Matrix` object to apply a transformation to the rectangle.
+ * @returns The instance of the current object for chaining.
+ */
+ rect(x, y, w, h, transform) {
+ this.drawShape(new Rectangle(x, y, w, h), transform);
+ return this;
+ }
+ /**
+ * Draws a circle shape. This method adds a new circle path to the current drawing.
+ * @param x - The x-coordinate of the center of the circle.
+ * @param y - The y-coordinate of the center of the circle.
+ * @param radius - The radius of the circle.
+ * @param transform - An optional `Matrix` object to apply a transformation to the circle.
+ * @returns The instance of the current object for chaining.
+ */
+ circle(x, y, radius, transform) {
+ this.drawShape(new Circle(x, y, radius), transform);
+ return this;
+ }
+ /**
+ * Draws a polygon shape. This method allows for the creation of complex polygons by specifying a sequence of points.
+ * @param points - An array of numbers, or or an array of PointData objects eg [{x,y}, {x,y}, {x,y}]
+ * representing the x and y coordinates of the polygon's vertices, in sequence.
+ * @param close - A boolean indicating whether to close the polygon path. True by default.
+ * @param transform - An optional `Matrix` object to apply a transformation to the polygon.
+ * @returns The instance of the current object for chaining.
+ */
+ poly(points, close, transform) {
+ const polygon = new Polygon(points);
+ polygon.closePath = close;
+ this.drawShape(polygon, transform);
+ return this;
+ }
+ /**
+ * Draws a regular polygon with a specified number of sides. All sides and angles are equal.
+ * @param x - The x-coordinate of the center of the polygon.
+ * @param y - The y-coordinate of the center of the polygon.
+ * @param radius - The radius of the circumscribed circle of the polygon.
+ * @param sides - The number of sides of the polygon. Must be 3 or more.
+ * @param rotation - The rotation angle of the polygon, in radians. Zero by default.
+ * @param transform - An optional `Matrix` object to apply a transformation to the polygon.
+ * @returns The instance of the current object for chaining.
+ */
+ regularPoly(x, y, radius, sides, rotation = 0, transform) {
+ sides = Math.max(sides | 0, 3);
+ const startAngle = -1 * Math.PI / 2 + rotation;
+ const delta = Math.PI * 2 / sides;
+ const polygon = [];
+ for (let i = 0; i < sides; i++) {
+ const angle = startAngle - i * delta;
+ polygon.push(
+ x + radius * Math.cos(angle),
+ y + radius * Math.sin(angle)
+ );
+ }
+ this.poly(polygon, true, transform);
+ return this;
+ }
+ /**
+ * Draws a polygon with rounded corners.
+ * Similar to `regularPoly` but with the ability to round the corners of the polygon.
+ * @param x - The x-coordinate of the center of the polygon.
+ * @param y - The y-coordinate of the center of the polygon.
+ * @param radius - The radius of the circumscribed circle of the polygon.
+ * @param sides - The number of sides of the polygon. Must be 3 or more.
+ * @param corner - The radius of the rounding of the corners.
+ * @param rotation - The rotation angle of the polygon, in radians. Zero by default.
+ * @param smoothness - Optional parameter to adjust the smoothness of the rounding.
+ * @returns The instance of the current object for chaining.
+ */
+ roundPoly(x, y, radius, sides, corner, rotation = 0, smoothness) {
+ sides = Math.max(sides | 0, 3);
+ if (corner <= 0) {
+ return this.regularPoly(x, y, radius, sides, rotation);
+ }
+ const sideLength = radius * Math.sin(Math.PI / sides) - 1e-3;
+ corner = Math.min(corner, sideLength);
+ const startAngle = -1 * Math.PI / 2 + rotation;
+ const delta = Math.PI * 2 / sides;
+ const internalAngle = (sides - 2) * Math.PI / sides / 2;
+ for (let i = 0; i < sides; i++) {
+ const angle = i * delta + startAngle;
+ const x0 = x + radius * Math.cos(angle);
+ const y0 = y + radius * Math.sin(angle);
+ const a1 = angle + Math.PI + internalAngle;
+ const a2 = angle - Math.PI - internalAngle;
+ const x1 = x0 + corner * Math.cos(a1);
+ const y1 = y0 + corner * Math.sin(a1);
+ const x3 = x0 + corner * Math.cos(a2);
+ const y3 = y0 + corner * Math.sin(a2);
+ if (i === 0) {
+ this.moveTo(x1, y1);
+ } else {
+ this.lineTo(x1, y1);
+ }
+ this.quadraticCurveTo(x0, y0, x3, y3, smoothness);
+ }
+ return this.closePath();
+ }
+ /**
+ * Draws a shape with rounded corners. This function supports custom radius for each corner of the shape.
+ * Optionally, corners can be rounded using a quadratic curve instead of an arc, providing a different aesthetic.
+ * @param points - An array of `RoundedPoint` representing the corners of the shape to draw.
+ * A minimum of 3 points is required.
+ * @param radius - The default radius for the corners.
+ * This radius is applied to all corners unless overridden in `points`.
+ * @param useQuadratic - If set to true, rounded corners are drawn using a quadraticCurve
+ * method instead of an arc method. Defaults to false.
+ * @param smoothness - Specifies the smoothness of the curve when `useQuadratic` is true.
+ * Higher values make the curve smoother.
+ * @returns The instance of the current object for chaining.
+ */
+ roundShape(points, radius, useQuadratic = false, smoothness) {
+ if (points.length < 3) {
+ return this;
+ }
+ if (useQuadratic) {
+ roundedShapeQuadraticCurve(this, points, radius, smoothness);
+ } else {
+ roundedShapeArc(this, points, radius);
+ }
+ return this.closePath();
+ }
+ /**
+ * Draw Rectangle with fillet corners. This is much like rounded rectangle
+ * however it support negative numbers as well for the corner radius.
+ * @param x - Upper left corner of rect
+ * @param y - Upper right corner of rect
+ * @param width - Width of rect
+ * @param height - Height of rect
+ * @param fillet - accept negative or positive values
+ */
+ filletRect(x, y, width, height, fillet) {
+ if (fillet === 0) {
+ return this.rect(x, y, width, height);
+ }
+ const maxFillet = Math.min(width, height) / 2;
+ const inset = Math.min(maxFillet, Math.max(-maxFillet, fillet));
+ const right = x + width;
+ const bottom = y + height;
+ const dir = inset < 0 ? -inset : 0;
+ const size = Math.abs(inset);
+ return this.moveTo(x, y + size).arcTo(x + dir, y + dir, x + size, y, size).lineTo(right - size, y).arcTo(right - dir, y + dir, right, y + size, size).lineTo(right, bottom - size).arcTo(right - dir, bottom - dir, x + width - size, bottom, size).lineTo(x + size, bottom).arcTo(x + dir, bottom - dir, x, bottom - size, size).closePath();
+ }
+ /**
+ * Draw Rectangle with chamfer corners. These are angled corners.
+ * @param x - Upper left corner of rect
+ * @param y - Upper right corner of rect
+ * @param width - Width of rect
+ * @param height - Height of rect
+ * @param chamfer - non-zero real number, size of corner cutout
+ * @param transform
+ */
+ chamferRect(x, y, width, height, chamfer, transform) {
+ if (chamfer <= 0) {
+ return this.rect(x, y, width, height);
+ }
+ const inset = Math.min(chamfer, Math.min(width, height) / 2);
+ const right = x + width;
+ const bottom = y + height;
+ const points = [
+ x + inset,
+ y,
+ right - inset,
+ y,
+ right,
+ y + inset,
+ right,
+ bottom - inset,
+ right - inset,
+ bottom,
+ x + inset,
+ bottom,
+ x,
+ bottom - inset,
+ x,
+ y + inset
+ ];
+ for (let i = points.length - 1; i >= 2; i -= 2) {
+ if (points[i] === points[i - 2] && points[i - 1] === points[i - 3]) {
+ points.splice(i - 1, 2);
+ }
+ }
+ return this.poly(points, true, transform);
+ }
+ /**
+ * Draws an ellipse at the specified location and with the given x and y radii.
+ * An optional transformation can be applied, allowing for rotation, scaling, and translation.
+ * @param x - The x-coordinate of the center of the ellipse.
+ * @param y - The y-coordinate of the center of the ellipse.
+ * @param radiusX - The horizontal radius of the ellipse.
+ * @param radiusY - The vertical radius of the ellipse.
+ * @param transform - An optional `Matrix` object to apply a transformation to the ellipse. This can include rotations.
+ * @returns The instance of the current object for chaining.
+ */
+ ellipse(x, y, radiusX, radiusY, transform) {
+ this.drawShape(new Ellipse(x, y, radiusX, radiusY), transform);
+ return this;
+ }
+ /**
+ * Draws a rectangle with rounded corners.
+ * The corner radius can be specified to determine how rounded the corners should be.
+ * An optional transformation can be applied, which allows for rotation, scaling, and translation of the rectangle.
+ * @param x - The x-coordinate of the top-left corner of the rectangle.
+ * @param y - The y-coordinate of the top-left corner of the rectangle.
+ * @param w - The width of the rectangle.
+ * @param h - The height of the rectangle.
+ * @param radius - The radius of the rectangle's corners. If not specified, corners will be sharp.
+ * @param transform - An optional `Matrix` object to apply a transformation to the rectangle.
+ * @returns The instance of the current object for chaining.
+ */
+ roundRect(x, y, w, h, radius, transform) {
+ this.drawShape(new RoundedRectangle(x, y, w, h, radius), transform);
+ return this;
+ }
+ /**
+ * Draws a given shape on the canvas.
+ * This is a generic method that can draw any type of shape specified by the `ShapePrimitive` parameter.
+ * An optional transformation matrix can be applied to the shape, allowing for complex transformations.
+ * @param shape - The shape to draw, defined as a `ShapePrimitive` object.
+ * @param matrix - An optional `Matrix` for transforming the shape. This can include rotations,
+ * scaling, and translations.
+ * @returns The instance of the current object for chaining.
+ */
+ drawShape(shape, matrix) {
+ this.endPoly();
+ this.shapePrimitives.push({ shape, transform: matrix });
+ return this;
+ }
+ /**
+ * Starts a new polygon path from the specified starting point.
+ * This method initializes a new polygon or ends the current one if it exists.
+ * @param x - The x-coordinate of the starting point of the new polygon.
+ * @param y - The y-coordinate of the starting point of the new polygon.
+ * @returns The instance of the current object for chaining.
+ */
+ startPoly(x, y) {
+ let currentPoly = this._currentPoly;
+ if (currentPoly) {
+ this.endPoly();
+ }
+ currentPoly = new Polygon();
+ currentPoly.points.push(x, y);
+ this._currentPoly = currentPoly;
+ return this;
+ }
+ /**
+ * Ends the current polygon path. If `closePath` is set to true,
+ * the path is closed by connecting the last point to the first one.
+ * This method finalizes the current polygon and prepares it for drawing or adding to the shape primitives.
+ * @param closePath - A boolean indicating whether to close the polygon by connecting the last point
+ * back to the starting point. False by default.
+ * @returns The instance of the current object for chaining.
+ */
+ endPoly(closePath = false) {
+ const shape = this._currentPoly;
+ if (shape && shape.points.length > 2) {
+ shape.closePath = closePath;
+ this.shapePrimitives.push({ shape });
+ }
+ this._currentPoly = null;
+ return this;
+ }
+ _ensurePoly(start = true) {
+ if (this._currentPoly)
+ return;
+ this._currentPoly = new Polygon();
+ if (start) {
+ const lastShape = this.shapePrimitives[this.shapePrimitives.length - 1];
+ if (lastShape) {
+ let lx = lastShape.shape.x;
+ let ly = lastShape.shape.y;
+ if (lastShape.transform && !lastShape.transform.isIdentity()) {
+ const t = lastShape.transform;
+ const tempX = lx;
+ lx = t.a * lx + t.c * ly + t.tx;
+ ly = t.b * tempX + t.d * ly + t.ty;
+ }
+ this._currentPoly.points.push(lx, ly);
+ } else {
+ this._currentPoly.points.push(0, 0);
+ }
+ }
+ }
+ /** Builds the path. */
+ buildPath() {
+ const path = this._graphicsPath2D;
+ this.shapePrimitives.length = 0;
+ this._currentPoly = null;
+ for (let i = 0; i < path.instructions.length; i++) {
+ const instruction = path.instructions[i];
+ this[instruction.action](...instruction.data);
+ }
+ this.finish();
+ }
+ /** Gets the bounds of the path. */
+ get bounds() {
+ const bounds = this._bounds;
+ bounds.clear();
+ const shapePrimitives = this.shapePrimitives;
+ for (let i = 0; i < shapePrimitives.length; i++) {
+ const shapePrimitive = shapePrimitives[i];
+ const boundsRect = shapePrimitive.shape.getBounds(tempRectangle);
+ if (shapePrimitive.transform) {
+ bounds.addRect(boundsRect, shapePrimitive.transform);
+ } else {
+ bounds.addRect(boundsRect);
+ }
+ }
+ return bounds;
+ }
+ }
+
+ "use strict";
+ class GraphicsPath {
+ /**
+ * Creates a `GraphicsPath` instance optionally from an SVG path string or an array of `PathInstruction`.
+ * @param instructions - An SVG path string or an array of `PathInstruction` objects.
+ * @param signed
+ */
+ constructor(instructions, signed = false) {
+ this.instructions = [];
+ /** unique id for this graphics path */
+ this.uid = uid$1("graphicsPath");
+ this._dirty = true;
+ var _a;
+ this.checkForHoles = signed;
+ if (typeof instructions === "string") {
+ parseSVGPath(instructions, this);
+ } else {
+ this.instructions = (_a = instructions == null ? void 0 : instructions.slice()) != null ? _a : [];
+ }
+ }
+ /**
+ * Provides access to the internal shape path, ensuring it is up-to-date with the current instructions.
+ * @returns The `ShapePath` instance associated with this `GraphicsPath`.
+ */
+ get shapePath() {
+ if (!this._shapePath) {
+ this._shapePath = new ShapePath(this);
+ }
+ if (this._dirty) {
+ this._dirty = false;
+ this._shapePath.buildPath();
+ }
+ return this._shapePath;
+ }
+ /**
+ * Adds another `GraphicsPath` to this path, optionally applying a transformation.
+ * @param path - The `GraphicsPath` to add.
+ * @param transform - An optional transformation to apply to the added path.
+ * @returns The instance of the current object for chaining.
+ */
+ addPath(path, transform) {
+ path = path.clone();
+ this.instructions.push({ action: "addPath", data: [path, transform] });
+ this._dirty = true;
+ return this;
+ }
+ arc(...args) {
+ this.instructions.push({ action: "arc", data: args });
+ this._dirty = true;
+ return this;
+ }
+ arcTo(...args) {
+ this.instructions.push({ action: "arcTo", data: args });
+ this._dirty = true;
+ return this;
+ }
+ arcToSvg(...args) {
+ this.instructions.push({ action: "arcToSvg", data: args });
+ this._dirty = true;
+ return this;
+ }
+ bezierCurveTo(...args) {
+ this.instructions.push({ action: "bezierCurveTo", data: args });
+ this._dirty = true;
+ return this;
+ }
+ /**
+ * Adds a cubic Bezier curve to the path.
+ * It requires two points: the second control point and the end point. The first control point is assumed to be
+ * The starting point is the last point in the current path.
+ * @param cp2x - The x-coordinate of the second control point.
+ * @param cp2y - The y-coordinate of the second control point.
+ * @param x - The x-coordinate of the end point.
+ * @param y - The y-coordinate of the end point.
+ * @param smoothness - Optional parameter to adjust the smoothness of the curve.
+ * @returns The instance of the current object for chaining.
+ */
+ bezierCurveToShort(cp2x, cp2y, x, y, smoothness) {
+ const last = this.instructions[this.instructions.length - 1];
+ const lastPoint = this.getLastPoint(Point.shared);
+ let cp1x = 0;
+ let cp1y = 0;
+ if (!last || last.action !== "bezierCurveTo") {
+ cp1x = lastPoint.x;
+ cp1y = lastPoint.y;
+ } else {
+ cp1x = last.data[2];
+ cp1y = last.data[3];
+ const currentX = lastPoint.x;
+ const currentY = lastPoint.y;
+ cp1x = currentX + (currentX - cp1x);
+ cp1y = currentY + (currentY - cp1y);
+ }
+ this.instructions.push({ action: "bezierCurveTo", data: [cp1x, cp1y, cp2x, cp2y, x, y, smoothness] });
+ this._dirty = true;
+ return this;
+ }
+ /**
+ * Closes the current path by drawing a straight line back to the start.
+ * If the shape is already closed or there are no points in the path, this method does nothing.
+ * @returns The instance of the current object for chaining.
+ */
+ closePath() {
+ this.instructions.push({ action: "closePath", data: [] });
+ this._dirty = true;
+ return this;
+ }
+ ellipse(...args) {
+ this.instructions.push({ action: "ellipse", data: args });
+ this._dirty = true;
+ return this;
+ }
+ lineTo(...args) {
+ this.instructions.push({ action: "lineTo", data: args });
+ this._dirty = true;
+ return this;
+ }
+ moveTo(...args) {
+ this.instructions.push({ action: "moveTo", data: args });
+ return this;
+ }
+ quadraticCurveTo(...args) {
+ this.instructions.push({ action: "quadraticCurveTo", data: args });
+ this._dirty = true;
+ return this;
+ }
+ /**
+ * Adds a quadratic curve to the path. It uses the previous point as the control point.
+ * @param x - The x-coordinate of the end point.
+ * @param y - The y-coordinate of the end point.
+ * @param smoothness - Optional parameter to adjust the smoothness of the curve.
+ * @returns The instance of the current object for chaining.
+ */
+ quadraticCurveToShort(x, y, smoothness) {
+ const last = this.instructions[this.instructions.length - 1];
+ const lastPoint = this.getLastPoint(Point.shared);
+ let cpx1 = 0;
+ let cpy1 = 0;
+ if (!last || last.action !== "quadraticCurveTo") {
+ cpx1 = lastPoint.x;
+ cpy1 = lastPoint.y;
+ } else {
+ cpx1 = last.data[0];
+ cpy1 = last.data[1];
+ const currentX = lastPoint.x;
+ const currentY = lastPoint.y;
+ cpx1 = currentX + (currentX - cpx1);
+ cpy1 = currentY + (currentY - cpy1);
+ }
+ this.instructions.push({ action: "quadraticCurveTo", data: [cpx1, cpy1, x, y, smoothness] });
+ this._dirty = true;
+ return this;
+ }
+ /**
+ * Draws a rectangle shape. This method adds a new rectangle path to the current drawing.
+ * @param x - The x-coordinate of the top-left corner of the rectangle.
+ * @param y - The y-coordinate of the top-left corner of the rectangle.
+ * @param w - The width of the rectangle.
+ * @param h - The height of the rectangle.
+ * @param transform - An optional `Matrix` object to apply a transformation to the rectangle.
+ * @returns The instance of the current object for chaining.
+ */
+ rect(x, y, w, h, transform) {
+ this.instructions.push({ action: "rect", data: [x, y, w, h, transform] });
+ this._dirty = true;
+ return this;
+ }
+ /**
+ * Draws a circle shape. This method adds a new circle path to the current drawing.
+ * @param x - The x-coordinate of the center of the circle.
+ * @param y - The y-coordinate of the center of the circle.
+ * @param radius - The radius of the circle.
+ * @param transform - An optional `Matrix` object to apply a transformation to the circle.
+ * @returns The instance of the current object for chaining.
+ */
+ circle(x, y, radius, transform) {
+ this.instructions.push({ action: "circle", data: [x, y, radius, transform] });
+ this._dirty = true;
+ return this;
+ }
+ roundRect(...args) {
+ this.instructions.push({ action: "roundRect", data: args });
+ this._dirty = true;
+ return this;
+ }
+ poly(...args) {
+ this.instructions.push({ action: "poly", data: args });
+ this._dirty = true;
+ return this;
+ }
+ regularPoly(...args) {
+ this.instructions.push({ action: "regularPoly", data: args });
+ this._dirty = true;
+ return this;
+ }
+ roundPoly(...args) {
+ this.instructions.push({ action: "roundPoly", data: args });
+ this._dirty = true;
+ return this;
+ }
+ roundShape(...args) {
+ this.instructions.push({ action: "roundShape", data: args });
+ this._dirty = true;
+ return this;
+ }
+ filletRect(...args) {
+ this.instructions.push({ action: "filletRect", data: args });
+ this._dirty = true;
+ return this;
+ }
+ chamferRect(...args) {
+ this.instructions.push({ action: "chamferRect", data: args });
+ this._dirty = true;
+ return this;
+ }
+ /**
+ * Draws a star shape centered at a specified location. This method allows for the creation
+ * of stars with a variable number of points, outer radius, optional inner radius, and rotation.
+ * The star is drawn as a closed polygon with alternating outer and inner vertices to create the star's points.
+ * An optional transformation can be applied to scale, rotate, or translate the star as needed.
+ * @param x - The x-coordinate of the center of the star.
+ * @param y - The y-coordinate of the center of the star.
+ * @param points - The number of points of the star.
+ * @param radius - The outer radius of the star (distance from the center to the outer points).
+ * @param innerRadius - Optional. The inner radius of the star
+ * (distance from the center to the inner points between the outer points).
+ * If not provided, defaults to half of the `radius`.
+ * @param rotation - Optional. The rotation of the star in radians, where 0 is aligned with the y-axis.
+ * Defaults to 0, meaning one point is directly upward.
+ * @param transform - An optional `Matrix` object to apply a transformation to the star.
+ * This can include rotations, scaling, and translations.
+ * @returns The instance of the current object for chaining further drawing commands.
+ */
+ // eslint-disable-next-line max-len
+ star(x, y, points, radius, innerRadius, rotation, transform) {
+ innerRadius || (innerRadius = radius / 2);
+ const startAngle = -1 * Math.PI / 2 + rotation;
+ const len = points * 2;
+ const delta = Math.PI * 2 / len;
+ const polygon = [];
+ for (let i = 0; i < len; i++) {
+ const r = i % 2 ? innerRadius : radius;
+ const angle = i * delta + startAngle;
+ polygon.push(
+ x + r * Math.cos(angle),
+ y + r * Math.sin(angle)
+ );
+ }
+ this.poly(polygon, true, transform);
+ return this;
+ }
+ /**
+ * Creates a copy of the current `GraphicsPath` instance. This method supports both shallow and deep cloning.
+ * A shallow clone copies the reference of the instructions array, while a deep clone creates a new array and
+ * copies each instruction individually, ensuring that modifications to the instructions of the cloned `GraphicsPath`
+ * do not affect the original `GraphicsPath` and vice versa.
+ * @param deep - A boolean flag indicating whether the clone should be deep.
+ * @returns A new `GraphicsPath` instance that is a clone of the current instance.
+ */
+ clone(deep = false) {
+ const newGraphicsPath2D = new GraphicsPath();
+ newGraphicsPath2D.checkForHoles = this.checkForHoles;
+ if (!deep) {
+ newGraphicsPath2D.instructions = this.instructions.slice();
+ } else {
+ for (let i = 0; i < this.instructions.length; i++) {
+ const instruction = this.instructions[i];
+ newGraphicsPath2D.instructions.push({ action: instruction.action, data: instruction.data.slice() });
+ }
+ }
+ return newGraphicsPath2D;
+ }
+ clear() {
+ this.instructions.length = 0;
+ this._dirty = true;
+ return this;
+ }
+ /**
+ * Applies a transformation matrix to all drawing instructions within the `GraphicsPath`.
+ * This method enables the modification of the path's geometry according to the provided
+ * transformation matrix, which can include translations, rotations, scaling, and skewing.
+ *
+ * Each drawing instruction in the path is updated to reflect the transformation,
+ * ensuring the visual representation of the path is consistent with the applied matrix.
+ *
+ * Note: The transformation is applied directly to the coordinates and control points of the drawing instructions,
+ * not to the path as a whole. This means the transformation's effects are baked into the individual instructions,
+ * allowing for fine-grained control over the path's appearance.
+ * @param matrix - A `Matrix` object representing the transformation to apply.
+ * @returns The instance of the current object for chaining further operations.
+ */
+ transform(matrix) {
+ if (matrix.isIdentity())
+ return this;
+ const a = matrix.a;
+ const b = matrix.b;
+ const c = matrix.c;
+ const d = matrix.d;
+ const tx = matrix.tx;
+ const ty = matrix.ty;
+ let x = 0;
+ let y = 0;
+ let cpx1 = 0;
+ let cpy1 = 0;
+ let cpx2 = 0;
+ let cpy2 = 0;
+ let rx = 0;
+ let ry = 0;
+ for (let i = 0; i < this.instructions.length; i++) {
+ const instruction = this.instructions[i];
+ const data = instruction.data;
+ switch (instruction.action) {
+ case "moveTo":
+ case "lineTo":
+ x = data[0];
+ y = data[1];
+ data[0] = a * x + c * y + tx;
+ data[1] = b * x + d * y + ty;
+ break;
+ case "bezierCurveTo":
+ cpx1 = data[0];
+ cpy1 = data[1];
+ cpx2 = data[2];
+ cpy2 = data[3];
+ x = data[4];
+ y = data[5];
+ data[0] = a * cpx1 + c * cpy1 + tx;
+ data[1] = b * cpx1 + d * cpy1 + ty;
+ data[2] = a * cpx2 + c * cpy2 + tx;
+ data[3] = b * cpx2 + d * cpy2 + ty;
+ data[4] = a * x + c * y + tx;
+ data[5] = b * x + d * y + ty;
+ break;
+ case "quadraticCurveTo":
+ cpx1 = data[0];
+ cpy1 = data[1];
+ x = data[2];
+ y = data[3];
+ data[0] = a * cpx1 + c * cpy1 + tx;
+ data[1] = b * cpx1 + d * cpy1 + ty;
+ data[2] = a * x + c * y + tx;
+ data[3] = b * x + d * y + ty;
+ break;
+ case "arcToSvg":
+ x = data[5];
+ y = data[6];
+ rx = data[0];
+ ry = data[1];
+ data[0] = a * rx + c * ry;
+ data[1] = b * rx + d * ry;
+ data[5] = a * x + c * y + tx;
+ data[6] = b * x + d * y + ty;
+ break;
+ case "circle":
+ data[4] = adjustTransform(data[3], matrix);
+ break;
+ case "rect":
+ data[4] = adjustTransform(data[4], matrix);
+ break;
+ case "ellipse":
+ data[8] = adjustTransform(data[8], matrix);
+ break;
+ case "roundRect":
+ data[5] = adjustTransform(data[5], matrix);
+ break;
+ case "addPath":
+ data[0].transform(matrix);
+ break;
+ case "poly":
+ data[2] = adjustTransform(data[2], matrix);
+ break;
+ default:
+ warn("unknown transform action", instruction.action);
+ break;
+ }
+ }
+ this._dirty = true;
+ return this;
+ }
+ get bounds() {
+ return this.shapePath.bounds;
+ }
+ /**
+ * Retrieves the last point from the current drawing instructions in the `GraphicsPath`.
+ * This method is useful for operations that depend on the path's current endpoint,
+ * such as connecting subsequent shapes or paths. It supports various drawing instructions,
+ * ensuring the last point's position is accurately determined regardless of the path's complexity.
+ *
+ * If the last instruction is a `closePath`, the method iterates backward through the instructions
+ * until it finds an actionable instruction that defines a point (e.g., `moveTo`, `lineTo`,
+ * `quadraticCurveTo`, etc.). For compound paths added via `addPath`, it recursively retrieves
+ * the last point from the nested path.
+ * @param out - A `Point` object where the last point's coordinates will be stored.
+ * This object is modified directly to contain the result.
+ * @returns The `Point` object containing the last point's coordinates.
+ */
+ getLastPoint(out) {
+ let index = this.instructions.length - 1;
+ let lastInstruction = this.instructions[index];
+ if (!lastInstruction) {
+ out.x = 0;
+ out.y = 0;
+ return out;
+ }
+ while (lastInstruction.action === "closePath") {
+ index--;
+ if (index < 0) {
+ out.x = 0;
+ out.y = 0;
+ return out;
+ }
+ lastInstruction = this.instructions[index];
+ }
+ switch (lastInstruction.action) {
+ case "moveTo":
+ case "lineTo":
+ out.x = lastInstruction.data[0];
+ out.y = lastInstruction.data[1];
+ break;
+ case "quadraticCurveTo":
+ out.x = lastInstruction.data[2];
+ out.y = lastInstruction.data[3];
+ break;
+ case "bezierCurveTo":
+ out.x = lastInstruction.data[4];
+ out.y = lastInstruction.data[5];
+ break;
+ case "arc":
+ case "arcToSvg":
+ out.x = lastInstruction.data[5];
+ out.y = lastInstruction.data[6];
+ break;
+ case "addPath":
+ lastInstruction.data[0].getLastPoint(out);
+ break;
+ }
+ return out;
+ }
+ }
+ function adjustTransform(currentMatrix, transform) {
+ if (currentMatrix) {
+ return currentMatrix.prepend(transform);
+ }
+ return transform.clone();
+ }
+
+ "use strict";
+ function parseSVGFloatAttribute(svg, id, defaultValue) {
+ const value = svg.getAttribute(id);
+ return value ? Number(value) : defaultValue;
+ }
+
+ "use strict";
+ function parseSVGDefinitions(svg, session) {
+ const definitions = svg.querySelectorAll("defs");
+ for (let i = 0; i < definitions.length; i++) {
+ const definition = definitions[i];
+ for (let j = 0; j < definition.children.length; j++) {
+ const child = definition.children[j];
+ switch (child.nodeName.toLowerCase()) {
+ case "lineargradient":
+ session.defs[child.id] = parseLinearGradient(child);
+ break;
+ case "radialgradient":
+ session.defs[child.id] = parseRadialGradient(child);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ function parseLinearGradient(child) {
+ const x0 = parseSVGFloatAttribute(child, "x1", 0);
+ const y0 = parseSVGFloatAttribute(child, "y1", 0);
+ const x1 = parseSVGFloatAttribute(child, "x2", 1);
+ const y1 = parseSVGFloatAttribute(child, "y2", 0);
+ const gradientUnit = child.getAttribute("gradientUnits") || "objectBoundingBox";
+ const gradient = new FillGradient(
+ x0,
+ y0,
+ x1,
+ y1,
+ gradientUnit === "objectBoundingBox" ? "local" : "global"
+ );
+ for (let k = 0; k < child.children.length; k++) {
+ const stop = child.children[k];
+ const offset = parseSVGFloatAttribute(stop, "offset", 0);
+ const color = Color.shared.setValue(stop.getAttribute("stop-color")).toNumber();
+ gradient.addColorStop(offset, color);
+ }
+ return gradient;
+ }
+ function parseRadialGradient(_child) {
+ warn("[SVG Parser] Radial gradients are not yet supported");
+ return new FillGradient(0, 0, 1, 0);
+ }
+
+ "use strict";
+ function extractSvgUrlId(url) {
+ const match = url.match(/url\s*\(\s*['"]?\s*#([^'"\s)]+)\s*['"]?\s*\)/i);
+ return match ? match[1] : "";
+ }
+
+ "use strict";
+ const styleAttributes = {
+ // Fill properties
+ fill: { type: "paint", default: 0 },
+ // Fill color/gradient
+ "fill-opacity": { type: "number", default: 1 },
+ // Fill transparency
+ // Stroke properties
+ stroke: { type: "paint", default: 0 },
+ // Stroke color/gradient
+ "stroke-width": { type: "number", default: 1 },
+ // Width of stroke
+ "stroke-opacity": { type: "number", default: 1 },
+ // Stroke transparency
+ "stroke-linecap": { type: "string", default: "butt" },
+ // End cap style: butt, round, square
+ "stroke-linejoin": { type: "string", default: "miter" },
+ // Join style: miter, round, bevel
+ "stroke-miterlimit": { type: "number", default: 10 },
+ // Limit on miter join sharpness
+ "stroke-dasharray": { type: "string", default: "none" },
+ // Dash pattern
+ "stroke-dashoffset": { type: "number", default: 0 },
+ // Offset for dash pattern
+ // Global properties
+ opacity: { type: "number", default: 1 }
+ // Overall opacity
+ };
+ function parseSVGStyle(svg, session) {
+ const style = svg.getAttribute("style");
+ const strokeStyle = {};
+ const fillStyle = {};
+ const result = {
+ strokeStyle,
+ fillStyle,
+ useFill: false,
+ useStroke: false
+ };
+ for (const key in styleAttributes) {
+ const attribute = svg.getAttribute(key);
+ if (attribute) {
+ parseAttribute(session, result, key, attribute.trim());
+ }
+ }
+ if (style) {
+ const styleParts = style.split(";");
+ for (let i = 0; i < styleParts.length; i++) {
+ const stylePart = styleParts[i].trim();
+ const [key, value] = stylePart.split(":");
+ if (styleAttributes[key]) {
+ parseAttribute(session, result, key, value.trim());
+ }
+ }
+ }
+ return {
+ strokeStyle: result.useStroke ? strokeStyle : null,
+ fillStyle: result.useFill ? fillStyle : null,
+ useFill: result.useFill,
+ useStroke: result.useStroke
+ };
+ }
+ function parseAttribute(session, result, id, value) {
+ switch (id) {
+ case "stroke":
+ if (value !== "none") {
+ if (value.startsWith("url(")) {
+ const id2 = extractSvgUrlId(value);
+ result.strokeStyle.fill = session.defs[id2];
+ } else {
+ result.strokeStyle.color = Color.shared.setValue(value).toNumber();
+ }
+ result.useStroke = true;
+ }
+ break;
+ case "stroke-width":
+ result.strokeStyle.width = Number(value);
+ break;
+ case "fill":
+ if (value !== "none") {
+ if (value.startsWith("url(")) {
+ const id2 = extractSvgUrlId(value);
+ result.fillStyle.fill = session.defs[id2];
+ } else {
+ result.fillStyle.color = Color.shared.setValue(value).toNumber();
+ }
+ result.useFill = true;
+ }
+ break;
+ case "fill-opacity":
+ result.fillStyle.alpha = Number(value);
+ break;
+ case "stroke-opacity":
+ result.strokeStyle.alpha = Number(value);
+ break;
+ case "opacity":
+ result.fillStyle.alpha = Number(value);
+ result.strokeStyle.alpha = Number(value);
+ break;
+ }
+ }
+
+ "use strict";
+ function checkForNestedPattern(subpathsWithArea) {
+ if (subpathsWithArea.length <= 2) {
+ return true;
+ }
+ const areas = subpathsWithArea.map((s) => s.area).sort((a, b) => b - a);
+ const [largestArea, secondArea] = areas;
+ const smallestArea = areas[areas.length - 1];
+ const largestToSecondRatio = largestArea / secondArea;
+ const secondToSmallestRatio = secondArea / smallestArea;
+ if (largestToSecondRatio > 3 && secondToSmallestRatio < 2) {
+ return false;
+ }
+ return true;
+ }
+ function getFillInstructionData(context, index = 0) {
+ const instruction = context.instructions[index];
+ if (!instruction || instruction.action !== "fill") {
+ throw new Error(`Expected fill instruction at index ${index}, got ${(instruction == null ? void 0 : instruction.action) || "undefined"}`);
+ }
+ return instruction.data;
+ }
+
+ "use strict";
+ function extractSubpaths(pathData) {
+ const parts = pathData.split(/(?=[Mm])/);
+ const subpaths = parts.filter((part) => part.trim().length > 0);
+ return subpaths;
+ }
+ function calculatePathArea(pathData) {
+ const coords = pathData.match(/[-+]?[0-9]*\.?[0-9]+/g);
+ if (!coords || coords.length < 4)
+ return 0;
+ const numbers = coords.map(Number);
+ const xs = [];
+ const ys = [];
+ for (let i = 0; i < numbers.length; i += 2) {
+ if (i + 1 < numbers.length) {
+ xs.push(numbers[i]);
+ ys.push(numbers[i + 1]);
+ }
+ }
+ if (xs.length === 0 || ys.length === 0)
+ return 0;
+ const minX = Math.min(...xs);
+ const maxX = Math.max(...xs);
+ const minY = Math.min(...ys);
+ const maxY = Math.max(...ys);
+ const area = (maxX - minX) * (maxY - minY);
+ return area;
+ }
+ function appendSVGPath(pathData, graphicsPath) {
+ const tempPath = new GraphicsPath(pathData, false);
+ for (const instruction of tempPath.instructions) {
+ graphicsPath.instructions.push(instruction);
+ }
+ }
+
+ "use strict";
+ var __defProp$W = Object.defineProperty;
+ var __getOwnPropSymbols$X = Object.getOwnPropertySymbols;
+ var __hasOwnProp$X = Object.prototype.hasOwnProperty;
+ var __propIsEnum$X = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$W = (obj, key, value) => key in obj ? __defProp$W(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$W = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$X.call(b, prop))
+ __defNormalProp$W(a, prop, b[prop]);
+ if (__getOwnPropSymbols$X)
+ for (var prop of __getOwnPropSymbols$X(b)) {
+ if (__propIsEnum$X.call(b, prop))
+ __defNormalProp$W(a, prop, b[prop]);
+ }
+ return a;
+ };
+ function SVGParser(svg, graphicsContext) {
+ if (typeof svg === "string") {
+ const div = document.createElement("div");
+ div.innerHTML = svg.trim();
+ svg = div.querySelector("svg");
+ }
+ const session = {
+ context: graphicsContext,
+ defs: {},
+ path: new GraphicsPath()
+ };
+ parseSVGDefinitions(svg, session);
+ const children = svg.children;
+ const { fillStyle, strokeStyle } = parseSVGStyle(svg, session);
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+ if (child.nodeName.toLowerCase() === "defs")
+ continue;
+ renderChildren(child, session, fillStyle, strokeStyle);
+ }
+ return graphicsContext;
+ }
+ function renderChildren(svg, session, fillStyle, strokeStyle) {
+ const children = svg.children;
+ const { fillStyle: f1, strokeStyle: s1 } = parseSVGStyle(svg, session);
+ if (f1 && fillStyle) {
+ fillStyle = __spreadValues$W(__spreadValues$W({}, fillStyle), f1);
+ } else if (f1) {
+ fillStyle = f1;
+ }
+ if (s1 && strokeStyle) {
+ strokeStyle = __spreadValues$W(__spreadValues$W({}, strokeStyle), s1);
+ } else if (s1) {
+ strokeStyle = s1;
+ }
+ const noStyle = !fillStyle && !strokeStyle;
+ if (noStyle) {
+ fillStyle = { color: 0 };
+ }
+ let x;
+ let y;
+ let x1;
+ let y1;
+ let x2;
+ let y2;
+ let cx;
+ let cy;
+ let r;
+ let rx;
+ let ry;
+ let points;
+ let pointsString;
+ let d;
+ let graphicsPath;
+ let width;
+ let height;
+ switch (svg.nodeName.toLowerCase()) {
+ case "path": {
+ d = svg.getAttribute("d");
+ const fillRule = svg.getAttribute("fill-rule");
+ const subpaths = extractSubpaths(d);
+ const hasExplicitEvenodd = fillRule === "evenodd";
+ const hasMultipleSubpaths = subpaths.length > 1;
+ const shouldProcessHoles = hasExplicitEvenodd && hasMultipleSubpaths;
+ if (shouldProcessHoles) {
+ const subpathsWithArea = subpaths.map((subpath) => ({
+ path: subpath,
+ area: calculatePathArea(subpath)
+ }));
+ subpathsWithArea.sort((a, b) => b.area - a.area);
+ const useMultipleHolesApproach = subpaths.length > 3 || !checkForNestedPattern(subpathsWithArea);
+ if (useMultipleHolesApproach) {
+ for (let i = 0; i < subpathsWithArea.length; i++) {
+ const subpath = subpathsWithArea[i];
+ const isMainShape = i === 0;
+ session.context.beginPath();
+ const newPath = new GraphicsPath(void 0, true);
+ appendSVGPath(subpath.path, newPath);
+ session.context.path(newPath);
+ if (isMainShape) {
+ if (fillStyle)
+ session.context.fill(fillStyle);
+ if (strokeStyle)
+ session.context.stroke(strokeStyle);
+ } else {
+ session.context.cut();
+ }
+ }
+ } else {
+ for (let i = 0; i < subpathsWithArea.length; i++) {
+ const subpath = subpathsWithArea[i];
+ const isHole = i % 2 === 1;
+ session.context.beginPath();
+ const newPath = new GraphicsPath(void 0, true);
+ appendSVGPath(subpath.path, newPath);
+ session.context.path(newPath);
+ if (isHole) {
+ session.context.cut();
+ } else {
+ if (fillStyle)
+ session.context.fill(fillStyle);
+ if (strokeStyle)
+ session.context.stroke(strokeStyle);
+ }
+ }
+ }
+ } else {
+ const useEvenoddForGraphicsPath = fillRule ? fillRule === "evenodd" : true;
+ graphicsPath = new GraphicsPath(d, useEvenoddForGraphicsPath);
+ session.context.path(graphicsPath);
+ if (fillStyle)
+ session.context.fill(fillStyle);
+ if (strokeStyle)
+ session.context.stroke(strokeStyle);
+ }
+ break;
+ }
+ case "circle":
+ cx = parseSVGFloatAttribute(svg, "cx", 0);
+ cy = parseSVGFloatAttribute(svg, "cy", 0);
+ r = parseSVGFloatAttribute(svg, "r", 0);
+ session.context.ellipse(cx, cy, r, r);
+ if (fillStyle)
+ session.context.fill(fillStyle);
+ if (strokeStyle)
+ session.context.stroke(strokeStyle);
+ break;
+ case "rect":
+ x = parseSVGFloatAttribute(svg, "x", 0);
+ y = parseSVGFloatAttribute(svg, "y", 0);
+ width = parseSVGFloatAttribute(svg, "width", 0);
+ height = parseSVGFloatAttribute(svg, "height", 0);
+ rx = parseSVGFloatAttribute(svg, "rx", 0);
+ ry = parseSVGFloatAttribute(svg, "ry", 0);
+ if (rx || ry) {
+ session.context.roundRect(x, y, width, height, rx || ry);
+ } else {
+ session.context.rect(x, y, width, height);
+ }
+ if (fillStyle)
+ session.context.fill(fillStyle);
+ if (strokeStyle)
+ session.context.stroke(strokeStyle);
+ break;
+ case "ellipse":
+ cx = parseSVGFloatAttribute(svg, "cx", 0);
+ cy = parseSVGFloatAttribute(svg, "cy", 0);
+ rx = parseSVGFloatAttribute(svg, "rx", 0);
+ ry = parseSVGFloatAttribute(svg, "ry", 0);
+ session.context.beginPath();
+ session.context.ellipse(cx, cy, rx, ry);
+ if (fillStyle)
+ session.context.fill(fillStyle);
+ if (strokeStyle)
+ session.context.stroke(strokeStyle);
+ break;
+ case "line":
+ x1 = parseSVGFloatAttribute(svg, "x1", 0);
+ y1 = parseSVGFloatAttribute(svg, "y1", 0);
+ x2 = parseSVGFloatAttribute(svg, "x2", 0);
+ y2 = parseSVGFloatAttribute(svg, "y2", 0);
+ session.context.beginPath();
+ session.context.moveTo(x1, y1);
+ session.context.lineTo(x2, y2);
+ if (strokeStyle)
+ session.context.stroke(strokeStyle);
+ break;
+ case "polygon":
+ pointsString = svg.getAttribute("points");
+ points = pointsString.match(/\d+/g).map((n) => parseInt(n, 10));
+ session.context.poly(points, true);
+ if (fillStyle)
+ session.context.fill(fillStyle);
+ if (strokeStyle)
+ session.context.stroke(strokeStyle);
+ break;
+ case "polyline":
+ pointsString = svg.getAttribute("points");
+ points = pointsString.match(/\d+/g).map((n) => parseInt(n, 10));
+ session.context.poly(points, false);
+ if (strokeStyle)
+ session.context.stroke(strokeStyle);
+ break;
+ case "g":
+ case "svg":
+ break;
+ default: {
+ warn(`[SVG parser] <${svg.nodeName}> elements unsupported`);
+ break;
+ }
+ }
+ if (noStyle) {
+ fillStyle = null;
+ }
+ for (let i = 0; i < children.length; i++) {
+ renderChildren(children[i], session, fillStyle, strokeStyle);
+ }
+ }
+
+ "use strict";
+ var __defProp$V = Object.defineProperty;
+ var __getOwnPropSymbols$W = Object.getOwnPropertySymbols;
+ var __hasOwnProp$W = Object.prototype.hasOwnProperty;
+ var __propIsEnum$W = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$V = (obj, key, value) => key in obj ? __defProp$V(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$V = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$W.call(b, prop))
+ __defNormalProp$V(a, prop, b[prop]);
+ if (__getOwnPropSymbols$W)
+ for (var prop of __getOwnPropSymbols$W(b)) {
+ if (__propIsEnum$W.call(b, prop))
+ __defNormalProp$V(a, prop, b[prop]);
+ }
+ return a;
+ };
+ var __objRest$k = (source, exclude) => {
+ var target = {};
+ for (var prop in source)
+ if (__hasOwnProp$W.call(source, prop) && exclude.indexOf(prop) < 0)
+ target[prop] = source[prop];
+ if (source != null && __getOwnPropSymbols$W)
+ for (var prop of __getOwnPropSymbols$W(source)) {
+ if (exclude.indexOf(prop) < 0 && __propIsEnum$W.call(source, prop))
+ target[prop] = source[prop];
+ }
+ return target;
+ };
+ function isColorLike(value) {
+ return Color.isColorLike(value);
+ }
+ function isFillPattern(value) {
+ return value instanceof FillPattern;
+ }
+ function isFillGradient(value) {
+ return value instanceof FillGradient;
+ }
+ function isTexture(value) {
+ return value instanceof Texture;
+ }
+ function handleColorLike(fill, value, defaultStyle) {
+ const temp = Color.shared.setValue(value != null ? value : 0);
+ fill.color = temp.toNumber();
+ fill.alpha = temp.alpha === 1 ? defaultStyle.alpha : temp.alpha;
+ fill.texture = Texture.WHITE;
+ return __spreadValues$V(__spreadValues$V({}, defaultStyle), fill);
+ }
+ function handleTexture(fill, value, defaultStyle) {
+ fill.texture = value;
+ return __spreadValues$V(__spreadValues$V({}, defaultStyle), fill);
+ }
+ function handleFillPattern(fill, value, defaultStyle) {
+ fill.fill = value;
+ fill.color = 16777215;
+ fill.texture = value.texture;
+ fill.matrix = value.transform;
+ return __spreadValues$V(__spreadValues$V({}, defaultStyle), fill);
+ }
+ function handleFillGradient(fill, value, defaultStyle) {
+ value.buildGradient();
+ fill.fill = value;
+ fill.color = 16777215;
+ fill.texture = value.texture;
+ fill.matrix = value.transform;
+ fill.textureSpace = value.textureSpace;
+ return __spreadValues$V(__spreadValues$V({}, defaultStyle), fill);
+ }
+ function handleFillObject(value, defaultStyle) {
+ const style = __spreadValues$V(__spreadValues$V({}, defaultStyle), value);
+ const color = Color.shared.setValue(style.color);
+ style.alpha *= color.alpha;
+ style.color = color.toNumber();
+ return style;
+ }
+ function toFillStyle(value, defaultStyle) {
+ if (value === void 0 || value === null) {
+ return null;
+ }
+ const fill = {};
+ const objectStyle = value;
+ if (isColorLike(value)) {
+ return handleColorLike(fill, value, defaultStyle);
+ } else if (isTexture(value)) {
+ return handleTexture(fill, value, defaultStyle);
+ } else if (isFillPattern(value)) {
+ return handleFillPattern(fill, value, defaultStyle);
+ } else if (isFillGradient(value)) {
+ return handleFillGradient(fill, value, defaultStyle);
+ } else if (objectStyle.fill && isFillPattern(objectStyle.fill)) {
+ return handleFillPattern(objectStyle, objectStyle.fill, defaultStyle);
+ } else if (objectStyle.fill && isFillGradient(objectStyle.fill)) {
+ return handleFillGradient(objectStyle, objectStyle.fill, defaultStyle);
+ }
+ return handleFillObject(objectStyle, defaultStyle);
+ }
+ function toStrokeStyle(value, defaultStyle) {
+ const _a = defaultStyle, { width, alignment, miterLimit, cap, join, pixelLine } = _a, rest = __objRest$k(_a, ["width", "alignment", "miterLimit", "cap", "join", "pixelLine"]);
+ const fill = toFillStyle(value, rest);
+ if (!fill) {
+ return null;
+ }
+ return __spreadValues$V({
+ width,
+ alignment,
+ miterLimit,
+ cap,
+ join,
+ pixelLine
+ }, fill);
+ }
+
+ "use strict";
+ var __defProp$U = Object.defineProperty;
+ var __getOwnPropSymbols$V = Object.getOwnPropertySymbols;
+ var __hasOwnProp$V = Object.prototype.hasOwnProperty;
+ var __propIsEnum$V = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$U = (obj, key, value) => key in obj ? __defProp$U(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$U = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$V.call(b, prop))
+ __defNormalProp$U(a, prop, b[prop]);
+ if (__getOwnPropSymbols$V)
+ for (var prop of __getOwnPropSymbols$V(b)) {
+ if (__propIsEnum$V.call(b, prop))
+ __defNormalProp$U(a, prop, b[prop]);
+ }
+ return a;
+ };
+ const tmpPoint = new Point();
+ const tempMatrix$3 = new Matrix();
+ const _GraphicsContext = class _GraphicsContext extends EventEmitter {
+ constructor() {
+ super(...arguments);
+ /**
+ * unique id for this graphics context
+ * @internal
+ */
+ this.uid = uid$1("graphicsContext");
+ /** @internal */
+ this.dirty = true;
+ /** The batch mode for this graphics context. It can be 'auto', 'batch', or 'no-batch'. */
+ this.batchMode = "auto";
+ /** @internal */
+ this.instructions = [];
+ this._activePath = new GraphicsPath();
+ this._transform = new Matrix();
+ this._fillStyle = __spreadValues$U({}, _GraphicsContext.defaultFillStyle);
+ this._strokeStyle = __spreadValues$U({}, _GraphicsContext.defaultStrokeStyle);
+ this._stateStack = [];
+ this._tick = 0;
+ this._bounds = new Bounds();
+ this._boundsDirty = true;
+ }
+ /**
+ * Creates a new GraphicsContext object that is a clone of this instance, copying all properties,
+ * including the current drawing state, transformations, styles, and instructions.
+ * @returns A new GraphicsContext instance with the same properties and state as this one.
+ */
+ clone() {
+ const clone = new _GraphicsContext();
+ clone.batchMode = this.batchMode;
+ clone.instructions = this.instructions.slice();
+ clone._activePath = this._activePath.clone();
+ clone._transform = this._transform.clone();
+ clone._fillStyle = __spreadValues$U({}, this._fillStyle);
+ clone._strokeStyle = __spreadValues$U({}, this._strokeStyle);
+ clone._stateStack = this._stateStack.slice();
+ clone._bounds = this._bounds.clone();
+ clone._boundsDirty = true;
+ return clone;
+ }
+ /**
+ * The current fill style of the graphics context. This can be a color, gradient, pattern, or a more complex style defined by a FillStyle object.
+ */
+ get fillStyle() {
+ return this._fillStyle;
+ }
+ set fillStyle(value) {
+ this._fillStyle = toFillStyle(value, _GraphicsContext.defaultFillStyle);
+ }
+ /**
+ * The current stroke style of the graphics context. Similar to fill styles, stroke styles can encompass colors, gradients, patterns, or more detailed configurations via a StrokeStyle object.
+ */
+ get strokeStyle() {
+ return this._strokeStyle;
+ }
+ set strokeStyle(value) {
+ this._strokeStyle = toStrokeStyle(value, _GraphicsContext.defaultStrokeStyle);
+ }
+ /**
+ * Sets the current fill style of the graphics context. The fill style can be a color, gradient,
+ * pattern, or a more complex style defined by a FillStyle object.
+ * @param style - The fill style to apply. This can be a simple color, a gradient or pattern object,
+ * or a FillStyle or ConvertedFillStyle object.
+ * @returns The instance of the current GraphicsContext for method chaining.
+ */
+ setFillStyle(style) {
+ this._fillStyle = toFillStyle(style, _GraphicsContext.defaultFillStyle);
+ return this;
+ }
+ /**
+ * Sets the current stroke style of the graphics context. Similar to fill styles, stroke styles can
+ * encompass colors, gradients, patterns, or more detailed configurations via a StrokeStyle object.
+ * @param style - The stroke style to apply. Can be defined as a color, a gradient or pattern,
+ * or a StrokeStyle or ConvertedStrokeStyle object.
+ * @returns The instance of the current GraphicsContext for method chaining.
+ */
+ setStrokeStyle(style) {
+ this._strokeStyle = toFillStyle(style, _GraphicsContext.defaultStrokeStyle);
+ return this;
+ }
+ texture(texture, tint, dx, dy, dw, dh) {
+ this.instructions.push({
+ action: "texture",
+ data: {
+ image: texture,
+ dx: dx || 0,
+ dy: dy || 0,
+ dw: dw || texture.frame.width,
+ dh: dh || texture.frame.height,
+ transform: this._transform.clone(),
+ alpha: this._fillStyle.alpha,
+ style: tint ? Color.shared.setValue(tint).toNumber() : 16777215
+ }
+ });
+ this.onUpdate();
+ return this;
+ }
+ /**
+ * Resets the current path. Any previous path and its commands are discarded and a new path is
+ * started. This is typically called before beginning a new shape or series of drawing commands.
+ * @returns The instance of the current GraphicsContext for method chaining.
+ */
+ beginPath() {
+ this._activePath = new GraphicsPath();
+ return this;
+ }
+ fill(style, alpha) {
+ let path;
+ const lastInstruction = this.instructions[this.instructions.length - 1];
+ if (this._tick === 0 && lastInstruction && lastInstruction.action === "stroke") {
+ path = lastInstruction.data.path;
+ } else {
+ path = this._activePath.clone();
+ }
+ if (!path)
+ return this;
+ if (style != null) {
+ if (alpha !== void 0 && typeof style === "number") {
+ deprecation(v8_0_0, "GraphicsContext.fill(color, alpha) is deprecated, use GraphicsContext.fill({ color, alpha }) instead");
+ style = { color: style, alpha };
+ }
+ this._fillStyle = toFillStyle(style, _GraphicsContext.defaultFillStyle);
+ }
+ this.instructions.push({
+ action: "fill",
+ // TODO copy fill style!
+ data: { style: this.fillStyle, path }
+ });
+ this.onUpdate();
+ this._initNextPathLocation();
+ this._tick = 0;
+ return this;
+ }
+ _initNextPathLocation() {
+ const { x, y } = this._activePath.getLastPoint(Point.shared);
+ this._activePath.clear();
+ this._activePath.moveTo(x, y);
+ }
+ /**
+ * Strokes the current path with the current stroke style. This method can take an optional
+ * FillInput parameter to define the stroke's appearance, including its color, width, and other properties.
+ * @param style - (Optional) The stroke style to apply. Can be defined as a simple color or a more complex style object. If omitted, uses the current stroke style.
+ * @returns The instance of the current GraphicsContext for method chaining.
+ */
+ stroke(style) {
+ let path;
+ const lastInstruction = this.instructions[this.instructions.length - 1];
+ if (this._tick === 0 && lastInstruction && lastInstruction.action === "fill") {
+ path = lastInstruction.data.path;
+ } else {
+ path = this._activePath.clone();
+ }
+ if (!path)
+ return this;
+ if (style != null) {
+ this._strokeStyle = toStrokeStyle(style, _GraphicsContext.defaultStrokeStyle);
+ }
+ this.instructions.push({
+ action: "stroke",
+ // TODO copy fill style!
+ data: { style: this.strokeStyle, path }
+ });
+ this.onUpdate();
+ this._initNextPathLocation();
+ this._tick = 0;
+ return this;
+ }
+ /**
+ * Applies a cutout to the last drawn shape. This is used to create holes or complex shapes by
+ * subtracting a path from the previously drawn path. If a hole is not completely in a shape, it will
+ * fail to cut correctly!
+ * @returns The instance of the current GraphicsContext for method chaining.
+ */
+ cut() {
+ for (let i = 0; i < 2; i++) {
+ const lastInstruction = this.instructions[this.instructions.length - 1 - i];
+ const holePath = this._activePath.clone();
+ if (lastInstruction) {
+ if (lastInstruction.action === "stroke" || lastInstruction.action === "fill") {
+ if (lastInstruction.data.hole) {
+ lastInstruction.data.hole.addPath(holePath);
+ } else {
+ lastInstruction.data.hole = holePath;
+ break;
+ }
+ }
+ }
+ }
+ this._initNextPathLocation();
+ return this;
+ }
+ /**
+ * Adds an arc to the current path, which is centered at (x, y) with the specified radius,
+ * starting and ending angles, and direction.
+ * @param x - The x-coordinate of the arc's center.
+ * @param y - The y-coordinate of the arc's center.
+ * @param radius - The arc's radius.
+ * @param startAngle - The starting angle, in radians.
+ * @param endAngle - The ending angle, in radians.
+ * @param counterclockwise - (Optional) Specifies whether the arc is drawn counterclockwise (true) or clockwise (false). Defaults to false.
+ * @returns The instance of the current GraphicsContext for method chaining.
+ */
+ arc(x, y, radius, startAngle, endAngle, counterclockwise) {
+ this._tick++;
+ const t = this._transform;
+ this._activePath.arc(
+ t.a * x + t.c * y + t.tx,
+ t.b * x + t.d * y + t.ty,
+ radius,
+ startAngle,
+ endAngle,
+ counterclockwise
+ );
+ return this;
+ }
+ /**
+ * Adds an arc to the current path with the given control points and radius, connected to the previous point
+ * by a straight line if necessary.
+ * @param x1 - The x-coordinate of the first control point.
+ * @param y1 - The y-coordinate of the first control point.
+ * @param x2 - The x-coordinate of the second control point.
+ * @param y2 - The y-coordinate of the second control point.
+ * @param radius - The arc's radius.
+ * @returns The instance of the current GraphicsContext for method chaining.
+ */
+ arcTo(x1, y1, x2, y2, radius) {
+ this._tick++;
+ const t = this._transform;
+ this._activePath.arcTo(
+ t.a * x1 + t.c * y1 + t.tx,
+ t.b * x1 + t.d * y1 + t.ty,
+ t.a * x2 + t.c * y2 + t.tx,
+ t.b * x2 + t.d * y2 + t.ty,
+ radius
+ );
+ return this;
+ }
+ /**
+ * Adds an SVG-style arc to the path, allowing for elliptical arcs based on the SVG spec.
+ * @param rx - The x-radius of the ellipse.
+ * @param ry - The y-radius of the ellipse.
+ * @param xAxisRotation - The rotation of the ellipse's x-axis relative
+ * to the x-axis of the coordinate system, in degrees.
+ * @param largeArcFlag - Determines if the arc should be greater than or less than 180 degrees.
+ * @param sweepFlag - Determines if the arc should be swept in a positive angle direction.
+ * @param x - The x-coordinate of the arc's end point.
+ * @param y - The y-coordinate of the arc's end point.
+ * @returns The instance of the current object for chaining.
+ */
+ arcToSvg(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y) {
+ this._tick++;
+ const t = this._transform;
+ this._activePath.arcToSvg(
+ rx,
+ ry,
+ xAxisRotation,
+ // should we rotate this with transform??
+ largeArcFlag,
+ sweepFlag,
+ t.a * x + t.c * y + t.tx,
+ t.b * x + t.d * y + t.ty
+ );
+ return this;
+ }
+ /**
+ * Adds a cubic Bezier curve to the path.
+ * It requires three points: the first two are control points and the third one is the end point.
+ * The starting point is the last point in the current path.
+ * @param cp1x - The x-coordinate of the first control point.
+ * @param cp1y - The y-coordinate of the first control point.
+ * @param cp2x - The x-coordinate of the second control point.
+ * @param cp2y - The y-coordinate of the second control point.
+ * @param x - The x-coordinate of the end point.
+ * @param y - The y-coordinate of the end point.
+ * @param smoothness - Optional parameter to adjust the smoothness of the curve.
+ * @returns The instance of the current object for chaining.
+ */
+ bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y, smoothness) {
+ this._tick++;
+ const t = this._transform;
+ this._activePath.bezierCurveTo(
+ t.a * cp1x + t.c * cp1y + t.tx,
+ t.b * cp1x + t.d * cp1y + t.ty,
+ t.a * cp2x + t.c * cp2y + t.tx,
+ t.b * cp2x + t.d * cp2y + t.ty,
+ t.a * x + t.c * y + t.tx,
+ t.b * x + t.d * y + t.ty,
+ smoothness
+ );
+ return this;
+ }
+ /**
+ * Closes the current path by drawing a straight line back to the start.
+ * If the shape is already closed or there are no points in the path, this method does nothing.
+ * @returns The instance of the current object for chaining.
+ */
+ closePath() {
+ var _a;
+ this._tick++;
+ (_a = this._activePath) == null ? void 0 : _a.closePath();
+ return this;
+ }
+ /**
+ * Draws an ellipse at the specified location and with the given x and y radii.
+ * An optional transformation can be applied, allowing for rotation, scaling, and translation.
+ * @param x - The x-coordinate of the center of the ellipse.
+ * @param y - The y-coordinate of the center of the ellipse.
+ * @param radiusX - The horizontal radius of the ellipse.
+ * @param radiusY - The vertical radius of the ellipse.
+ * @returns The instance of the current object for chaining.
+ */
+ ellipse(x, y, radiusX, radiusY) {
+ this._tick++;
+ this._activePath.ellipse(x, y, radiusX, radiusY, this._transform.clone());
+ return this;
+ }
+ /**
+ * Draws a circle shape. This method adds a new circle path to the current drawing.
+ * @param x - The x-coordinate of the center of the circle.
+ * @param y - The y-coordinate of the center of the circle.
+ * @param radius - The radius of the circle.
+ * @returns The instance of the current object for chaining.
+ */
+ circle(x, y, radius) {
+ this._tick++;
+ this._activePath.circle(x, y, radius, this._transform.clone());
+ return this;
+ }
+ /**
+ * Adds another `GraphicsPath` to this path, optionally applying a transformation.
+ * @param path - The `GraphicsPath` to add.
+ * @returns The instance of the current object for chaining.
+ */
+ path(path) {
+ this._tick++;
+ this._activePath.addPath(path, this._transform.clone());
+ return this;
+ }
+ /**
+ * Connects the current point to a new point with a straight line. This method updates the current path.
+ * @param x - The x-coordinate of the new point to connect to.
+ * @param y - The y-coordinate of the new point to connect to.
+ * @returns The instance of the current object for chaining.
+ */
+ lineTo(x, y) {
+ this._tick++;
+ const t = this._transform;
+ this._activePath.lineTo(
+ t.a * x + t.c * y + t.tx,
+ t.b * x + t.d * y + t.ty
+ );
+ return this;
+ }
+ /**
+ * Sets the starting point for a new sub-path. Any subsequent drawing commands are considered part of this path.
+ * @param x - The x-coordinate for the starting point.
+ * @param y - The y-coordinate for the starting point.
+ * @returns The instance of the current object for chaining.
+ */
+ moveTo(x, y) {
+ this._tick++;
+ const t = this._transform;
+ const instructions = this._activePath.instructions;
+ const transformedX = t.a * x + t.c * y + t.tx;
+ const transformedY = t.b * x + t.d * y + t.ty;
+ if (instructions.length === 1 && instructions[0].action === "moveTo") {
+ instructions[0].data[0] = transformedX;
+ instructions[0].data[1] = transformedY;
+ return this;
+ }
+ this._activePath.moveTo(
+ transformedX,
+ transformedY
+ );
+ return this;
+ }
+ /**
+ * Adds a quadratic curve to the path. It requires two points: the control point and the end point.
+ * The starting point is the last point in the current path.
+ * @param cpx - The x-coordinate of the control point.
+ * @param cpy - The y-coordinate of the control point.
+ * @param x - The x-coordinate of the end point.
+ * @param y - The y-coordinate of the end point.
+ * @param smoothness - Optional parameter to adjust the smoothness of the curve.
+ * @returns The instance of the current object for chaining.
+ */
+ quadraticCurveTo(cpx, cpy, x, y, smoothness) {
+ this._tick++;
+ const t = this._transform;
+ this._activePath.quadraticCurveTo(
+ t.a * cpx + t.c * cpy + t.tx,
+ t.b * cpx + t.d * cpy + t.ty,
+ t.a * x + t.c * y + t.tx,
+ t.b * x + t.d * y + t.ty,
+ smoothness
+ );
+ return this;
+ }
+ /**
+ * Draws a rectangle shape. This method adds a new rectangle path to the current drawing.
+ * @param x - The x-coordinate of the top-left corner of the rectangle.
+ * @param y - The y-coordinate of the top-left corner of the rectangle.
+ * @param w - The width of the rectangle.
+ * @param h - The height of the rectangle.
+ * @returns The instance of the current object for chaining.
+ */
+ rect(x, y, w, h) {
+ this._tick++;
+ this._activePath.rect(x, y, w, h, this._transform.clone());
+ return this;
+ }
+ /**
+ * Draws a rectangle with rounded corners.
+ * The corner radius can be specified to determine how rounded the corners should be.
+ * An optional transformation can be applied, which allows for rotation, scaling, and translation of the rectangle.
+ * @param x - The x-coordinate of the top-left corner of the rectangle.
+ * @param y - The y-coordinate of the top-left corner of the rectangle.
+ * @param w - The width of the rectangle.
+ * @param h - The height of the rectangle.
+ * @param radius - The radius of the rectangle's corners. If not specified, corners will be sharp.
+ * @returns The instance of the current object for chaining.
+ */
+ roundRect(x, y, w, h, radius) {
+ this._tick++;
+ this._activePath.roundRect(x, y, w, h, radius, this._transform.clone());
+ return this;
+ }
+ /**
+ * Draws a polygon shape by specifying a sequence of points. This method allows for the creation of complex polygons,
+ * which can be both open and closed. An optional transformation can be applied, enabling the polygon to be scaled,
+ * rotated, or translated as needed.
+ * @param points - An array of numbers, or an array of PointData objects eg [{x,y}, {x,y}, {x,y}]
+ * representing the x and y coordinates, of the polygon's vertices, in sequence.
+ * @param close - A boolean indicating whether to close the polygon path. True by default.
+ */
+ poly(points, close) {
+ this._tick++;
+ this._activePath.poly(points, close, this._transform.clone());
+ return this;
+ }
+ /**
+ * Draws a regular polygon with a specified number of sides. All sides and angles are equal.
+ * @param x - The x-coordinate of the center of the polygon.
+ * @param y - The y-coordinate of the center of the polygon.
+ * @param radius - The radius of the circumscribed circle of the polygon.
+ * @param sides - The number of sides of the polygon. Must be 3 or more.
+ * @param rotation - The rotation angle of the polygon, in radians. Zero by default.
+ * @param transform - An optional `Matrix` object to apply a transformation to the polygon.
+ * @returns The instance of the current object for chaining.
+ */
+ regularPoly(x, y, radius, sides, rotation = 0, transform) {
+ this._tick++;
+ this._activePath.regularPoly(x, y, radius, sides, rotation, transform);
+ return this;
+ }
+ /**
+ * Draws a polygon with rounded corners.
+ * Similar to `regularPoly` but with the ability to round the corners of the polygon.
+ * @param x - The x-coordinate of the center of the polygon.
+ * @param y - The y-coordinate of the center of the polygon.
+ * @param radius - The radius of the circumscribed circle of the polygon.
+ * @param sides - The number of sides of the polygon. Must be 3 or more.
+ * @param corner - The radius of the rounding of the corners.
+ * @param rotation - The rotation angle of the polygon, in radians. Zero by default.
+ * @returns The instance of the current object for chaining.
+ */
+ roundPoly(x, y, radius, sides, corner, rotation) {
+ this._tick++;
+ this._activePath.roundPoly(x, y, radius, sides, corner, rotation);
+ return this;
+ }
+ /**
+ * Draws a shape with rounded corners. This function supports custom radius for each corner of the shape.
+ * Optionally, corners can be rounded using a quadratic curve instead of an arc, providing a different aesthetic.
+ * @param points - An array of `RoundedPoint` representing the corners of the shape to draw.
+ * A minimum of 3 points is required.
+ * @param radius - The default radius for the corners.
+ * This radius is applied to all corners unless overridden in `points`.
+ * @param useQuadratic - If set to true, rounded corners are drawn using a quadraticCurve
+ * method instead of an arc method. Defaults to false.
+ * @param smoothness - Specifies the smoothness of the curve when `useQuadratic` is true.
+ * Higher values make the curve smoother.
+ * @returns The instance of the current object for chaining.
+ */
+ roundShape(points, radius, useQuadratic, smoothness) {
+ this._tick++;
+ this._activePath.roundShape(points, radius, useQuadratic, smoothness);
+ return this;
+ }
+ /**
+ * Draw Rectangle with fillet corners. This is much like rounded rectangle
+ * however it support negative numbers as well for the corner radius.
+ * @param x - Upper left corner of rect
+ * @param y - Upper right corner of rect
+ * @param width - Width of rect
+ * @param height - Height of rect
+ * @param fillet - accept negative or positive values
+ */
+ filletRect(x, y, width, height, fillet) {
+ this._tick++;
+ this._activePath.filletRect(x, y, width, height, fillet);
+ return this;
+ }
+ /**
+ * Draw Rectangle with chamfer corners. These are angled corners.
+ * @param x - Upper left corner of rect
+ * @param y - Upper right corner of rect
+ * @param width - Width of rect
+ * @param height - Height of rect
+ * @param chamfer - non-zero real number, size of corner cutout
+ * @param transform
+ */
+ chamferRect(x, y, width, height, chamfer, transform) {
+ this._tick++;
+ this._activePath.chamferRect(x, y, width, height, chamfer, transform);
+ return this;
+ }
+ /**
+ * Draws a star shape centered at a specified location. This method allows for the creation
+ * of stars with a variable number of points, outer radius, optional inner radius, and rotation.
+ * The star is drawn as a closed polygon with alternating outer and inner vertices to create the star's points.
+ * An optional transformation can be applied to scale, rotate, or translate the star as needed.
+ * @param x - The x-coordinate of the center of the star.
+ * @param y - The y-coordinate of the center of the star.
+ * @param points - The number of points of the star.
+ * @param radius - The outer radius of the star (distance from the center to the outer points).
+ * @param innerRadius - Optional. The inner radius of the star
+ * (distance from the center to the inner points between the outer points).
+ * If not provided, defaults to half of the `radius`.
+ * @param rotation - Optional. The rotation of the star in radians, where 0 is aligned with the y-axis.
+ * Defaults to 0, meaning one point is directly upward.
+ * @returns The instance of the current object for chaining further drawing commands.
+ */
+ star(x, y, points, radius, innerRadius = 0, rotation = 0) {
+ this._tick++;
+ this._activePath.star(x, y, points, radius, innerRadius, rotation, this._transform.clone());
+ return this;
+ }
+ /**
+ * Parses and renders an SVG string into the graphics context. This allows for complex shapes and paths
+ * defined in SVG format to be drawn within the graphics context.
+ * @param svg - The SVG string to be parsed and rendered.
+ */
+ svg(svg) {
+ this._tick++;
+ SVGParser(svg, this);
+ return this;
+ }
+ /**
+ * Restores the most recently saved graphics state by popping the top of the graphics state stack.
+ * This includes transformations, fill styles, and stroke styles.
+ */
+ restore() {
+ const state = this._stateStack.pop();
+ if (state) {
+ this._transform = state.transform;
+ this._fillStyle = state.fillStyle;
+ this._strokeStyle = state.strokeStyle;
+ }
+ return this;
+ }
+ /** Saves the current graphics state, including transformations, fill styles, and stroke styles, onto a stack. */
+ save() {
+ this._stateStack.push({
+ transform: this._transform.clone(),
+ fillStyle: __spreadValues$U({}, this._fillStyle),
+ strokeStyle: __spreadValues$U({}, this._strokeStyle)
+ });
+ return this;
+ }
+ /**
+ * Returns the current transformation matrix of the graphics context.
+ * @returns The current transformation matrix.
+ */
+ getTransform() {
+ return this._transform;
+ }
+ /**
+ * Resets the current transformation matrix to the identity matrix, effectively removing any transformations (rotation, scaling, translation) previously applied.
+ * @returns The instance of the current GraphicsContext for method chaining.
+ */
+ resetTransform() {
+ this._transform.identity();
+ return this;
+ }
+ /**
+ * Applies a rotation transformation to the graphics context around the current origin.
+ * @param angle - The angle of rotation in radians.
+ * @returns The instance of the current GraphicsContext for method chaining.
+ */
+ rotate(angle) {
+ this._transform.rotate(angle);
+ return this;
+ }
+ /**
+ * Applies a scaling transformation to the graphics context, scaling drawings by x horizontally and by y vertically.
+ * @param x - The scale factor in the horizontal direction.
+ * @param y - (Optional) The scale factor in the vertical direction. If not specified, the x value is used for both directions.
+ * @returns The instance of the current GraphicsContext for method chaining.
+ */
+ scale(x, y = x) {
+ this._transform.scale(x, y);
+ return this;
+ }
+ setTransform(a, b, c, d, dx, dy) {
+ if (a instanceof Matrix) {
+ this._transform.set(a.a, a.b, a.c, a.d, a.tx, a.ty);
+ return this;
+ }
+ this._transform.set(a, b, c, d, dx, dy);
+ return this;
+ }
+ transform(a, b, c, d, dx, dy) {
+ if (a instanceof Matrix) {
+ this._transform.append(a);
+ return this;
+ }
+ tempMatrix$3.set(a, b, c, d, dx, dy);
+ this._transform.append(tempMatrix$3);
+ return this;
+ }
+ /**
+ * Applies a translation transformation to the graphics context, moving the origin by the specified amounts.
+ * @param x - The amount to translate in the horizontal direction.
+ * @param y - (Optional) The amount to translate in the vertical direction. If not specified, the x value is used for both directions.
+ * @returns The instance of the current GraphicsContext for method chaining.
+ */
+ translate(x, y = x) {
+ this._transform.translate(x, y);
+ return this;
+ }
+ /**
+ * Clears all drawing commands from the graphics context, effectively resetting it. This includes clearing the path,
+ * and optionally resetting transformations to the identity matrix.
+ * @returns The instance of the current GraphicsContext for method chaining.
+ */
+ clear() {
+ this._activePath.clear();
+ this.instructions.length = 0;
+ this.resetTransform();
+ this.onUpdate();
+ return this;
+ }
+ onUpdate() {
+ if (this.dirty)
+ return;
+ this.emit("update", this, 16);
+ this.dirty = true;
+ this._boundsDirty = true;
+ }
+ /** The bounds of the graphic shape. */
+ get bounds() {
+ if (!this._boundsDirty)
+ return this._bounds;
+ this._boundsDirty = false;
+ const bounds = this._bounds;
+ bounds.clear();
+ for (let i = 0; i < this.instructions.length; i++) {
+ const instruction = this.instructions[i];
+ const action = instruction.action;
+ if (action === "fill") {
+ const data = instruction.data;
+ bounds.addBounds(data.path.bounds);
+ } else if (action === "texture") {
+ const data = instruction.data;
+ bounds.addFrame(data.dx, data.dy, data.dx + data.dw, data.dy + data.dh, data.transform);
+ }
+ if (action === "stroke") {
+ const data = instruction.data;
+ const alignment = data.style.alignment;
+ const outerPadding = data.style.width * (1 - alignment);
+ const _bounds = data.path.bounds;
+ bounds.addFrame(
+ _bounds.minX - outerPadding,
+ _bounds.minY - outerPadding,
+ _bounds.maxX + outerPadding,
+ _bounds.maxY + outerPadding
+ );
+ }
+ }
+ return bounds;
+ }
+ /**
+ * Check to see if a point is contained within this geometry.
+ * @param point - Point to check if it's contained.
+ * @returns {boolean} `true` if the point is contained within geometry.
+ */
+ containsPoint(point) {
+ var _a;
+ if (!this.bounds.containsPoint(point.x, point.y))
+ return false;
+ const instructions = this.instructions;
+ let hasHit = false;
+ for (let k = 0; k < instructions.length; k++) {
+ const instruction = instructions[k];
+ const data = instruction.data;
+ const path = data.path;
+ if (!instruction.action || !path)
+ continue;
+ const style = data.style;
+ const shapes = path.shapePath.shapePrimitives;
+ for (let i = 0; i < shapes.length; i++) {
+ const shape = shapes[i].shape;
+ if (!style || !shape)
+ continue;
+ const transform = shapes[i].transform;
+ const transformedPoint = transform ? transform.applyInverse(point, tmpPoint) : point;
+ if (instruction.action === "fill") {
+ hasHit = shape.contains(transformedPoint.x, transformedPoint.y);
+ } else {
+ const strokeStyle = style;
+ hasHit = shape.strokeContains(transformedPoint.x, transformedPoint.y, strokeStyle.width, strokeStyle.alignment);
+ }
+ const holes = data.hole;
+ if (holes) {
+ const holeShapes = (_a = holes.shapePath) == null ? void 0 : _a.shapePrimitives;
+ if (holeShapes) {
+ for (let j = 0; j < holeShapes.length; j++) {
+ if (holeShapes[j].shape.contains(transformedPoint.x, transformedPoint.y)) {
+ hasHit = false;
+ }
+ }
+ }
+ }
+ if (hasHit) {
+ return true;
+ }
+ }
+ }
+ return hasHit;
+ }
+ /**
+ * Destroys the GraphicsData object.
+ * @param options - Options parameter. A boolean will act as if all options
+ * have been set to that value
+ * @example
+ * context.destroy();
+ * context.destroy(true);
+ * context.destroy({ texture: true, textureSource: true });
+ */
+ destroy(options = false) {
+ this._stateStack.length = 0;
+ this._transform = null;
+ this.emit("destroy", this);
+ this.removeAllListeners();
+ const destroyTexture = typeof options === "boolean" ? options : options == null ? void 0 : options.texture;
+ if (destroyTexture) {
+ const destroyTextureSource = typeof options === "boolean" ? options : options == null ? void 0 : options.textureSource;
+ if (this._fillStyle.texture) {
+ this._fillStyle.fill && "uid" in this._fillStyle.fill ? this._fillStyle.fill.destroy() : this._fillStyle.texture.destroy(destroyTextureSource);
+ }
+ if (this._strokeStyle.texture) {
+ this._strokeStyle.fill && "uid" in this._strokeStyle.fill ? this._strokeStyle.fill.destroy() : this._strokeStyle.texture.destroy(destroyTextureSource);
+ }
+ }
+ this._fillStyle = null;
+ this._strokeStyle = null;
+ this.instructions = null;
+ this._activePath = null;
+ this._bounds = null;
+ this._stateStack = null;
+ this.customShader = null;
+ this._transform = null;
+ }
+ };
+ /** The default fill style to use when none is provided. */
+ _GraphicsContext.defaultFillStyle = {
+ /** The color to use for the fill. */
+ color: 16777215,
+ /** The alpha value to use for the fill. */
+ alpha: 1,
+ /** The texture to use for the fill. */
+ texture: Texture.WHITE,
+ /** The matrix to apply. */
+ matrix: null,
+ /** The fill pattern to use. */
+ fill: null,
+ /** Whether coordinates are 'global' or 'local' */
+ textureSpace: "local"
+ };
+ /** The default stroke style to use when none is provided. */
+ _GraphicsContext.defaultStrokeStyle = {
+ /** The width of the stroke. */
+ width: 1,
+ /** The color to use for the stroke. */
+ color: 16777215,
+ /** The alpha value to use for the stroke. */
+ alpha: 1,
+ /** The alignment of the stroke. */
+ alignment: 0.5,
+ /** The miter limit to use. */
+ miterLimit: 10,
+ /** The line cap style to use. */
+ cap: "butt",
+ /** The line join style to use. */
+ join: "miter",
+ /** The texture to use for the fill. */
+ texture: Texture.WHITE,
+ /** The matrix to apply. */
+ matrix: null,
+ /** The fill pattern to use. */
+ fill: null,
+ /** Whether coordinates are 'global' or 'local' */
+ textureSpace: "local",
+ /** If the stroke is a pixel line. */
+ pixelLine: false
+ };
+ let GraphicsContext = _GraphicsContext;
+
+ "use strict";
+ var __defProp$T = Object.defineProperty;
+ var __defProps$n = Object.defineProperties;
+ var __getOwnPropDescs$n = Object.getOwnPropertyDescriptors;
+ var __getOwnPropSymbols$U = Object.getOwnPropertySymbols;
+ var __hasOwnProp$U = Object.prototype.hasOwnProperty;
+ var __propIsEnum$U = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$T = (obj, key, value) => key in obj ? __defProp$T(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$T = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$U.call(b, prop))
+ __defNormalProp$T(a, prop, b[prop]);
+ if (__getOwnPropSymbols$U)
+ for (var prop of __getOwnPropSymbols$U(b)) {
+ if (__propIsEnum$U.call(b, prop))
+ __defNormalProp$T(a, prop, b[prop]);
+ }
+ return a;
+ };
+ var __spreadProps$n = (a, b) => __defProps$n(a, __getOwnPropDescs$n(b));
+ const _TextStyle = class _TextStyle extends EventEmitter {
+ constructor(style = {}) {
+ super();
+ /**
+ * Unique identifier for the TextStyle class.
+ * This is used to track instances and ensure uniqueness.
+ * @internal
+ */
+ this.uid = uid$1("textStyle");
+ /**
+ * Internal tick counter used to track updates and changes.
+ * This is incremented whenever the style is modified, allowing for efficient change detection.
+ * @internal
+ */
+ this._tick = 0;
+ convertV7Tov8Style(style);
+ const fullStyle = __spreadValues$T(__spreadValues$T({}, _TextStyle.defaultTextStyle), style);
+ for (const key in fullStyle) {
+ const thisKey = key;
+ this[thisKey] = fullStyle[key];
+ }
+ this.update();
+ this._tick = 0;
+ }
+ /**
+ * Alignment for multiline text, does not affect single line text.
+ * @type {'left'|'center'|'right'|'justify'}
+ */
+ get align() {
+ return this._align;
+ }
+ set align(value) {
+ this._align = value;
+ this.update();
+ }
+ /** Indicates if lines can be wrapped within words, it needs wordWrap to be set to true. */
+ get breakWords() {
+ return this._breakWords;
+ }
+ set breakWords(value) {
+ this._breakWords = value;
+ this.update();
+ }
+ /** Set a drop shadow for the text. */
+ get dropShadow() {
+ return this._dropShadow;
+ }
+ set dropShadow(value) {
+ if (value !== null && typeof value === "object") {
+ this._dropShadow = this._createProxy(__spreadValues$T(__spreadValues$T({}, _TextStyle.defaultDropShadow), value));
+ } else {
+ this._dropShadow = value ? this._createProxy(__spreadValues$T({}, _TextStyle.defaultDropShadow)) : null;
+ }
+ this.update();
+ }
+ /** The font family, can be a single font name, or a list of names where the first is the preferred font. */
+ get fontFamily() {
+ return this._fontFamily;
+ }
+ set fontFamily(value) {
+ this._fontFamily = value;
+ this.update();
+ }
+ /** The font size (as a number it converts to px, but as a string, equivalents are '26px','20pt','160%' or '1.6em') */
+ get fontSize() {
+ return this._fontSize;
+ }
+ set fontSize(value) {
+ if (typeof value === "string") {
+ this._fontSize = parseInt(value, 10);
+ } else {
+ this._fontSize = value;
+ }
+ this.update();
+ }
+ /**
+ * The font style.
+ * @type {'normal'|'italic'|'oblique'}
+ */
+ get fontStyle() {
+ return this._fontStyle;
+ }
+ set fontStyle(value) {
+ this._fontStyle = value.toLowerCase();
+ this.update();
+ }
+ /**
+ * The font variant.
+ * @type {'normal'|'small-caps'}
+ */
+ get fontVariant() {
+ return this._fontVariant;
+ }
+ set fontVariant(value) {
+ this._fontVariant = value;
+ this.update();
+ }
+ /**
+ * The font weight.
+ * @type {'normal'|'bold'|'bolder'|'lighter'|'100'|'200'|'300'|'400'|'500'|'600'|'700'|'800'|'900'}
+ */
+ get fontWeight() {
+ return this._fontWeight;
+ }
+ set fontWeight(value) {
+ this._fontWeight = value;
+ this.update();
+ }
+ /** The space between lines. */
+ get leading() {
+ return this._leading;
+ }
+ set leading(value) {
+ this._leading = value;
+ this.update();
+ }
+ /** The amount of spacing between letters, default is 0. */
+ get letterSpacing() {
+ return this._letterSpacing;
+ }
+ set letterSpacing(value) {
+ this._letterSpacing = value;
+ this.update();
+ }
+ /** The line height, a number that represents the vertical space that a letter uses. */
+ get lineHeight() {
+ return this._lineHeight;
+ }
+ set lineHeight(value) {
+ this._lineHeight = value;
+ this.update();
+ }
+ /**
+ * Occasionally some fonts are cropped. Adding some padding will prevent this from happening
+ * by adding padding to all sides of the text.
+ * > [!NOTE] This will NOT affect the positioning or bounds of the text.
+ */
+ get padding() {
+ return this._padding;
+ }
+ set padding(value) {
+ this._padding = value;
+ this.update();
+ }
+ /**
+ * An optional filter or array of filters to apply to the text, allowing for advanced visual effects.
+ * These filters will be applied to the text as it is created, resulting in faster rendering for static text
+ * compared to applying the filter directly to the text object (which would be applied at run time).
+ * @default null
+ */
+ get filters() {
+ return this._filters;
+ }
+ set filters(value) {
+ this._filters = Object.freeze(value);
+ this.update();
+ }
+ /**
+ * Trim transparent borders from the text texture.
+ * > [!IMPORTANT] PERFORMANCE WARNING:
+ * > This is a costly operation as it requires scanning pixel alpha values.
+ * > Avoid using `trim: true` for dynamic text, as it could significantly impact performance.
+ */
+ get trim() {
+ return this._trim;
+ }
+ set trim(value) {
+ this._trim = value;
+ this.update();
+ }
+ /**
+ * The baseline of the text that is rendered.
+ * @type {'alphabetic'|'top'|'hanging'|'middle'|'ideographic'|'bottom'}
+ */
+ get textBaseline() {
+ return this._textBaseline;
+ }
+ set textBaseline(value) {
+ this._textBaseline = value;
+ this.update();
+ }
+ /**
+ * How newlines and spaces should be handled.
+ * Default is 'pre' (preserve, preserve).
+ *
+ * value | New lines | Spaces
+ * --- | --- | ---
+ * 'normal' | Collapse | Collapse
+ * 'pre' | Preserve | Preserve
+ * 'pre-line' | Preserve | Collapse
+ * @type {'normal'|'pre'|'pre-line'}
+ */
+ get whiteSpace() {
+ return this._whiteSpace;
+ }
+ set whiteSpace(value) {
+ this._whiteSpace = value;
+ this.update();
+ }
+ /** Indicates if word wrap should be used. */
+ get wordWrap() {
+ return this._wordWrap;
+ }
+ set wordWrap(value) {
+ this._wordWrap = value;
+ this.update();
+ }
+ /** The width at which text will wrap, it needs wordWrap to be set to true. */
+ get wordWrapWidth() {
+ return this._wordWrapWidth;
+ }
+ set wordWrapWidth(value) {
+ this._wordWrapWidth = value;
+ this.update();
+ }
+ /**
+ * The fill style that will be used to color the text.
+ * This can be:
+ * - A color string like 'red', '#00FF00', or 'rgba(255,0,0,0.5)'
+ * - A hex number like 0xff0000 for red
+ * - A FillStyle object with properties like { color: 0xff0000, alpha: 0.5 }
+ * - A FillGradient for gradient fills
+ * - A FillPattern for pattern/texture fills
+ *
+ * When using a FillGradient, vertical gradients (angle of 90 degrees) are applied per line of text,
+ * while gradients at any other angle are spread across the entire text body as a whole.
+ * @example
+ * // Vertical gradient applied per line
+ * const verticalGradient = new FillGradient(0, 0, 0, 1)
+ * .addColorStop(0, 0xff0000)
+ * .addColorStop(1, 0x0000ff);
+ *
+ * const text = new Text({
+ * text: 'Line 1\nLine 2',
+ * style: { fill: verticalGradient }
+ * });
+ *
+ * To manage the gradient in a global scope, set the textureSpace property of the FillGradient to 'global'.
+ * @type {string|number|FillStyle|FillGradient|FillPattern}
+ */
+ get fill() {
+ return this._originalFill;
+ }
+ set fill(value) {
+ if (value === this._originalFill)
+ return;
+ this._originalFill = value;
+ if (this._isFillStyle(value)) {
+ this._originalFill = this._createProxy(__spreadValues$T(__spreadValues$T({}, GraphicsContext.defaultFillStyle), value), () => {
+ this._fill = toFillStyle(
+ __spreadValues$T({}, this._originalFill),
+ GraphicsContext.defaultFillStyle
+ );
+ });
+ }
+ this._fill = toFillStyle(
+ value === 0 ? "black" : value,
+ GraphicsContext.defaultFillStyle
+ );
+ this.update();
+ }
+ /** A fillstyle that will be used on the text stroke, e.g., 'blue', '#FCFF00'. */
+ get stroke() {
+ return this._originalStroke;
+ }
+ set stroke(value) {
+ if (value === this._originalStroke)
+ return;
+ this._originalStroke = value;
+ if (this._isFillStyle(value)) {
+ this._originalStroke = this._createProxy(__spreadValues$T(__spreadValues$T({}, GraphicsContext.defaultStrokeStyle), value), () => {
+ this._stroke = toStrokeStyle(
+ __spreadValues$T({}, this._originalStroke),
+ GraphicsContext.defaultStrokeStyle
+ );
+ });
+ }
+ this._stroke = toStrokeStyle(value, GraphicsContext.defaultStrokeStyle);
+ this.update();
+ }
+ update() {
+ this._tick++;
+ this.emit("update", this);
+ }
+ /** Resets all properties to the default values */
+ reset() {
+ const defaultStyle = _TextStyle.defaultTextStyle;
+ for (const key in defaultStyle) {
+ this[key] = defaultStyle[key];
+ }
+ }
+ /**
+ * Returns a unique key for this instance.
+ * This key is used for caching.
+ * @returns {string} Unique key for the instance
+ */
+ get styleKey() {
+ return `${this.uid}-${this._tick}`;
+ }
+ /**
+ * Creates a new TextStyle object with the same values as this one.
+ * @returns New cloned TextStyle object
+ */
+ clone() {
+ return new _TextStyle({
+ align: this.align,
+ breakWords: this.breakWords,
+ dropShadow: this._dropShadow ? __spreadValues$T({}, this._dropShadow) : null,
+ fill: this._fill,
+ fontFamily: this.fontFamily,
+ fontSize: this.fontSize,
+ fontStyle: this.fontStyle,
+ fontVariant: this.fontVariant,
+ fontWeight: this.fontWeight,
+ leading: this.leading,
+ letterSpacing: this.letterSpacing,
+ lineHeight: this.lineHeight,
+ padding: this.padding,
+ stroke: this._stroke,
+ textBaseline: this.textBaseline,
+ whiteSpace: this.whiteSpace,
+ wordWrap: this.wordWrap,
+ wordWrapWidth: this.wordWrapWidth,
+ filters: this._filters ? [...this._filters] : void 0
+ });
+ }
+ /**
+ * Returns the final padding for the text style, taking into account any filters applied.
+ * Used internally for correct measurements
+ * @internal
+ * @returns {number} The final padding for the text style.
+ */
+ _getFinalPadding() {
+ let filterPadding = 0;
+ if (this._filters) {
+ for (let i = 0; i < this._filters.length; i++) {
+ filterPadding += this._filters[i].padding;
+ }
+ }
+ return Math.max(this._padding, filterPadding);
+ }
+ /**
+ * Destroys this text style.
+ * @param options - Options parameter. A boolean will act as if all options
+ * have been set to that value
+ * @example
+ * // Destroy the text style and its textures
+ * textStyle.destroy({ texture: true, textureSource: true });
+ * textStyle.destroy(true);
+ */
+ destroy(options = false) {
+ var _a, _b, _c, _d;
+ this.removeAllListeners();
+ const destroyTexture = typeof options === "boolean" ? options : options == null ? void 0 : options.texture;
+ if (destroyTexture) {
+ const destroyTextureSource = typeof options === "boolean" ? options : options == null ? void 0 : options.textureSource;
+ if ((_a = this._fill) == null ? void 0 : _a.texture) {
+ this._fill.texture.destroy(destroyTextureSource);
+ }
+ if ((_b = this._originalFill) == null ? void 0 : _b.texture) {
+ this._originalFill.texture.destroy(destroyTextureSource);
+ }
+ if ((_c = this._stroke) == null ? void 0 : _c.texture) {
+ this._stroke.texture.destroy(destroyTextureSource);
+ }
+ if ((_d = this._originalStroke) == null ? void 0 : _d.texture) {
+ this._originalStroke.texture.destroy(destroyTextureSource);
+ }
+ }
+ this._fill = null;
+ this._stroke = null;
+ this.dropShadow = null;
+ this._originalStroke = null;
+ this._originalFill = null;
+ }
+ _createProxy(value, cb) {
+ return new Proxy(value, {
+ set: (target, property, newValue) => {
+ target[property] = newValue;
+ cb == null ? void 0 : cb(property, newValue);
+ this.update();
+ return true;
+ }
+ });
+ }
+ _isFillStyle(value) {
+ return (value != null ? value : null) !== null && !(Color.isColorLike(value) || value instanceof FillGradient || value instanceof FillPattern);
+ }
+ };
+ /**
+ * Default drop shadow settings used when enabling drop shadows on text.
+ * These values are used as the base configuration when drop shadows are enabled without specific settings.
+ * @example
+ * ```ts
+ * // Customize default settings globally
+ * TextStyle.defaultDropShadow.alpha = 0.5; // 50% opacity for all shadows
+ * TextStyle.defaultDropShadow.blur = 2; // 2px blur for all shadows
+ * TextStyle.defaultDropShadow.color = 'blue'; // Blue shadows by default
+ * ```
+ */
+ _TextStyle.defaultDropShadow = {
+ alpha: 1,
+ angle: Math.PI / 6,
+ blur: 0,
+ color: "black",
+ distance: 5
+ };
+ /**
+ * Default text style settings used when creating new text objects.
+ * These values serve as the base configuration and can be customized globally.
+ * @example
+ * ```ts
+ * // Customize default text style globally
+ * TextStyle.defaultTextStyle.fontSize = 16;
+ * TextStyle.defaultTextStyle.fill = 0x333333;
+ * TextStyle.defaultTextStyle.fontFamily = ['Arial', 'Helvetica', 'sans-serif'];
+ * ```
+ */
+ _TextStyle.defaultTextStyle = {
+ align: "left",
+ breakWords: false,
+ dropShadow: null,
+ fill: "black",
+ fontFamily: "Arial",
+ fontSize: 26,
+ fontStyle: "normal",
+ fontVariant: "normal",
+ fontWeight: "normal",
+ leading: 0,
+ letterSpacing: 0,
+ lineHeight: 0,
+ padding: 0,
+ stroke: null,
+ textBaseline: "alphabetic",
+ trim: false,
+ whiteSpace: "pre",
+ wordWrap: false,
+ wordWrapWidth: 100
+ };
+ let TextStyle = _TextStyle;
+ function convertV7Tov8Style(style) {
+ var _a, _b, _c, _d, _e;
+ const oldStyle = style;
+ if (typeof oldStyle.dropShadow === "boolean" && oldStyle.dropShadow) {
+ const defaults = TextStyle.defaultDropShadow;
+ style.dropShadow = {
+ alpha: (_a = oldStyle.dropShadowAlpha) != null ? _a : defaults.alpha,
+ angle: (_b = oldStyle.dropShadowAngle) != null ? _b : defaults.angle,
+ blur: (_c = oldStyle.dropShadowBlur) != null ? _c : defaults.blur,
+ color: (_d = oldStyle.dropShadowColor) != null ? _d : defaults.color,
+ distance: (_e = oldStyle.dropShadowDistance) != null ? _e : defaults.distance
+ };
+ }
+ if (oldStyle.strokeThickness !== void 0) {
+ deprecation(v8_0_0, "strokeThickness is now a part of stroke");
+ const color = oldStyle.stroke;
+ let obj = {};
+ if (Color.isColorLike(color)) {
+ obj.color = color;
+ } else if (color instanceof FillGradient || color instanceof FillPattern) {
+ obj.fill = color;
+ } else if (Object.hasOwnProperty.call(color, "color") || Object.hasOwnProperty.call(color, "fill")) {
+ obj = color;
+ } else {
+ throw new Error("Invalid stroke value.");
+ }
+ style.stroke = __spreadProps$n(__spreadValues$T({}, obj), {
+ width: oldStyle.strokeThickness
+ });
+ }
+ if (Array.isArray(oldStyle.fillGradientStops)) {
+ deprecation(v8_0_0, "gradient fill is now a fill pattern: `new FillGradient(...)`");
+ if (!Array.isArray(oldStyle.fill) || oldStyle.fill.length === 0) {
+ throw new Error("Invalid fill value. Expected an array of colors for gradient fill.");
+ }
+ if (oldStyle.fill.length !== oldStyle.fillGradientStops.length) {
+ warn("The number of fill colors must match the number of fill gradient stops.");
+ }
+ const gradientFill = new FillGradient({
+ start: { x: 0, y: 0 },
+ end: { x: 0, y: 1 },
+ textureSpace: "local"
+ });
+ const fillGradientStops = oldStyle.fillGradientStops.slice();
+ const fills = oldStyle.fill.map((color) => Color.shared.setValue(color).toNumber());
+ fillGradientStops.forEach((stop, index) => {
+ gradientFill.addColorStop(stop, fills[index]);
+ });
+ style.fill = {
+ fill: gradientFill
+ };
+ }
+ }
+
+ "use strict";
+ const tempBounds$3 = new Bounds();
+ function getPo2TextureFromSource(image, width, height, resolution) {
+ const bounds = tempBounds$3;
+ bounds.minX = 0;
+ bounds.minY = 0;
+ bounds.maxX = image.width / resolution | 0;
+ bounds.maxY = image.height / resolution | 0;
+ const texture = TexturePool.getOptimalTexture(
+ bounds.width,
+ bounds.height,
+ resolution,
+ false
+ );
+ texture.source.uploadMethodId = "image";
+ texture.source.resource = image;
+ texture.source.alphaMode = "premultiply-alpha-on-upload";
+ texture.frame.width = width / resolution;
+ texture.frame.height = height / resolution;
+ texture.source.emit("update", texture.source);
+ texture.updateUvs();
+ return texture;
+ }
+
+ "use strict";
+ class CanvasPoolClass {
+ constructor(canvasOptions) {
+ this._canvasPool = /* @__PURE__ */ Object.create(null);
+ this.canvasOptions = canvasOptions || {};
+ this.enableFullScreen = false;
+ }
+ /**
+ * Creates texture with params that were specified in pool constructor.
+ * @param pixelWidth - Width of texture in pixels.
+ * @param pixelHeight - Height of texture in pixels.
+ */
+ _createCanvasAndContext(pixelWidth, pixelHeight) {
+ const canvas = DOMAdapter.get().createCanvas();
+ canvas.width = pixelWidth;
+ canvas.height = pixelHeight;
+ const context = canvas.getContext("2d");
+ return { canvas, context };
+ }
+ /**
+ * Gets a Power-of-Two render texture or fullScreen texture
+ * @param minWidth - The minimum width of the render texture.
+ * @param minHeight - The minimum height of the render texture.
+ * @param resolution - The resolution of the render texture.
+ * @returns The new render texture.
+ */
+ getOptimalCanvasAndContext(minWidth, minHeight, resolution = 1) {
+ minWidth = Math.ceil(minWidth * resolution - 1e-6);
+ minHeight = Math.ceil(minHeight * resolution - 1e-6);
+ minWidth = nextPow2(minWidth);
+ minHeight = nextPow2(minHeight);
+ const key = (minWidth << 17) + (minHeight << 1);
+ if (!this._canvasPool[key]) {
+ this._canvasPool[key] = [];
+ }
+ let canvasAndContext = this._canvasPool[key].pop();
+ if (!canvasAndContext) {
+ canvasAndContext = this._createCanvasAndContext(minWidth, minHeight);
+ }
+ return canvasAndContext;
+ }
+ /**
+ * Place a render texture back into the pool.
+ * @param canvasAndContext
+ */
+ returnCanvasAndContext(canvasAndContext) {
+ const canvas = canvasAndContext.canvas;
+ const { width, height } = canvas;
+ const key = (width << 17) + (height << 1);
+ canvasAndContext.context.resetTransform();
+ canvasAndContext.context.clearRect(0, 0, width, height);
+ this._canvasPool[key].push(canvasAndContext);
+ }
+ clear() {
+ this._canvasPool = {};
+ }
+ }
+ const CanvasPool = new CanvasPoolClass();
+ GlobalResourceRegistry.register(CanvasPool);
+
+ "use strict";
+ let _internalCanvas = null;
+ let _internalContext = null;
+ function ensureInternalCanvas(width, height) {
+ if (!_internalCanvas) {
+ _internalCanvas = DOMAdapter.get().createCanvas(256, 128);
+ _internalContext = _internalCanvas.getContext("2d", { willReadFrequently: true });
+ _internalContext.globalCompositeOperation = "copy";
+ _internalContext.globalAlpha = 1;
+ }
+ if (_internalCanvas.width < width || _internalCanvas.height < height) {
+ _internalCanvas.width = nextPow2(width);
+ _internalCanvas.height = nextPow2(height);
+ }
+ }
+ function checkRow(data, width, y) {
+ for (let x = 0, index = 4 * y * width; x < width; ++x, index += 4) {
+ if (data[index + 3] !== 0)
+ return false;
+ }
+ return true;
+ }
+ function checkColumn(data, width, x, top, bottom) {
+ const stride = 4 * width;
+ for (let y = top, index = top * stride + 4 * x; y <= bottom; ++y, index += stride) {
+ if (data[index + 3] !== 0)
+ return false;
+ }
+ return true;
+ }
+ function getCanvasBoundingBox(...args) {
+ var _a, _b, _c;
+ let options = args[0];
+ if (!options.canvas) {
+ options = { canvas: args[0], resolution: args[1] };
+ }
+ const { canvas } = options;
+ const resolution = Math.min((_a = options.resolution) != null ? _a : 1, 1);
+ const width = (_b = options.width) != null ? _b : canvas.width;
+ const height = (_c = options.height) != null ? _c : canvas.height;
+ let output = options.output;
+ ensureInternalCanvas(width, height);
+ if (!_internalContext) {
+ throw new TypeError("Failed to get canvas 2D context");
+ }
+ _internalContext.drawImage(
+ canvas,
+ 0,
+ 0,
+ width,
+ height,
+ 0,
+ 0,
+ width * resolution,
+ height * resolution
+ );
+ const imageData = _internalContext.getImageData(0, 0, width, height);
+ const data = imageData.data;
+ let left = 0;
+ let top = 0;
+ let right = width - 1;
+ let bottom = height - 1;
+ while (top < height && checkRow(data, width, top))
+ ++top;
+ if (top === height)
+ return Rectangle.EMPTY;
+ while (checkRow(data, width, bottom))
+ --bottom;
+ while (checkColumn(data, width, left, top, bottom))
+ ++left;
+ while (checkColumn(data, width, right, top, bottom))
+ --right;
+ ++right;
+ ++bottom;
+ _internalContext.globalCompositeOperation = "source-over";
+ _internalContext.strokeRect(left, top, right - left, bottom - top);
+ _internalContext.globalCompositeOperation = "copy";
+ output != null ? output : output = new Rectangle();
+ output.set(left / resolution, top / resolution, (right - left) / resolution, (bottom - top) / resolution);
+ return output;
+ }
+
+ /**
+ * tiny-lru
+ *
+ * @copyright 2025 Jason Mulligan
+ * @license BSD-3-Clause
+ * @version 11.4.5
+ */
+ /**
+ * A high-performance Least Recently Used (LRU) cache implementation with optional TTL support.
+ * Items are automatically evicted when the cache reaches its maximum size,
+ * removing the least recently used items first. All core operations (get, set, delete) are O(1).
+ *
+ * @class LRU
+ * @example
+ * // Create a cache with max 100 items
+ * const cache = new LRU(100);
+ * cache.set('key1', 'value1');
+ * console.log(cache.get('key1')); // 'value1'
+ *
+ * @example
+ * // Create a cache with TTL
+ * const cache = new LRU(100, 5000); // 5 second TTL
+ * cache.set('key1', 'value1');
+ * // After 5 seconds, key1 will be expired
+ */
+ class LRU {
+ /**
+ * Creates a new LRU cache instance.
+ * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation.
+ *
+ * @constructor
+ * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited.
+ * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration.
+ * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
+ * @example
+ * const cache = new LRU(1000, 60000, true); // 1000 items, 1 minute TTL, reset on access
+ * @see {@link lru} For parameter validation
+ * @since 1.0.0
+ */
+ constructor (max = 0, ttl = 0, resetTtl = false) {
+ this.first = null;
+ this.items = Object.create(null);
+ this.last = null;
+ this.max = max;
+ this.resetTtl = resetTtl;
+ this.size = 0;
+ this.ttl = ttl;
+ }
+
+ /**
+ * Removes all items from the cache.
+ *
+ * @method clear
+ * @memberof LRU
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.clear();
+ * console.log(cache.size); // 0
+ * @since 1.0.0
+ */
+ clear () {
+ this.first = null;
+ this.items = Object.create(null);
+ this.last = null;
+ this.size = 0;
+
+ return this;
+ }
+
+ /**
+ * Removes an item from the cache by key.
+ *
+ * @method delete
+ * @memberof LRU
+ * @param {string} key - The key of the item to delete.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('key1', 'value1');
+ * cache.delete('key1');
+ * console.log(cache.has('key1')); // false
+ * @see {@link LRU#has}
+ * @see {@link LRU#clear}
+ * @since 1.0.0
+ */
+ delete (key) {
+ if (this.has(key)) {
+ const item = this.items[key];
+
+ delete this.items[key];
+ this.size--;
+
+ if (item.prev !== null) {
+ item.prev.next = item.next;
+ }
+
+ if (item.next !== null) {
+ item.next.prev = item.prev;
+ }
+
+ if (this.first === item) {
+ this.first = item.next;
+ }
+
+ if (this.last === item) {
+ this.last = item.prev;
+ }
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns an array of [key, value] pairs for the specified keys.
+ * Order follows LRU order (least to most recently used).
+ *
+ * @method entries
+ * @memberof LRU
+ * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys.
+ * @returns {Array>} Array of [key, value] pairs in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * console.log(cache.entries()); // [['a', 1], ['b', 2]]
+ * console.log(cache.entries(['a'])); // [['a', 1]]
+ * @see {@link LRU#keys}
+ * @see {@link LRU#values}
+ * @since 11.1.0
+ */
+ entries (keys = this.keys()) {
+ return keys.map(key => [key, this.get(key)]);
+ }
+
+ /**
+ * Removes the least recently used item from the cache.
+ *
+ * @method evict
+ * @memberof LRU
+ * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('old', 'value').set('new', 'value');
+ * cache.evict(); // Removes 'old' item
+ * @see {@link LRU#setWithEvicted}
+ * @since 1.0.0
+ */
+ evict (bypass = false) {
+ if (bypass || this.size > 0) {
+ const item = this.first;
+
+ delete this.items[item.key];
+
+ if (--this.size === 0) {
+ this.first = null;
+ this.last = null;
+ } else {
+ this.first = item.next;
+ this.first.prev = null;
+ }
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns the expiration timestamp for a given key.
+ *
+ * @method expiresAt
+ * @memberof LRU
+ * @param {string} key - The key to check expiration for.
+ * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist.
+ * @example
+ * const cache = new LRU(100, 5000); // 5 second TTL
+ * cache.set('key1', 'value1');
+ * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now
+ * @see {@link LRU#get}
+ * @see {@link LRU#has}
+ * @since 1.0.0
+ */
+ expiresAt (key) {
+ let result;
+
+ if (this.has(key)) {
+ result = this.items[key].expiry;
+ }
+
+ return result;
+ }
+
+ /**
+ * Retrieves a value from the cache by key. Updates the item's position to most recently used.
+ *
+ * @method get
+ * @memberof LRU
+ * @param {string} key - The key to retrieve.
+ * @returns {*} The value associated with the key, or undefined if not found or expired.
+ * @example
+ * cache.set('key1', 'value1');
+ * console.log(cache.get('key1')); // 'value1'
+ * console.log(cache.get('nonexistent')); // undefined
+ * @see {@link LRU#set}
+ * @see {@link LRU#has}
+ * @since 1.0.0
+ */
+ get (key) {
+ const item = this.items[key];
+
+ if (item !== undefined) {
+ // Check TTL only if enabled to avoid unnecessary Date.now() calls
+ if (this.ttl > 0) {
+ if (item.expiry <= Date.now()) {
+ this.delete(key);
+
+ return undefined;
+ }
+ }
+
+ // Fast LRU update without full set() overhead
+ this.moveToEnd(item);
+
+ return item.value;
+ }
+
+ return undefined;
+ }
+
+ /**
+ * Checks if a key exists in the cache.
+ *
+ * @method has
+ * @memberof LRU
+ * @param {string} key - The key to check for.
+ * @returns {boolean} True if the key exists, false otherwise.
+ * @example
+ * cache.set('key1', 'value1');
+ * console.log(cache.has('key1')); // true
+ * console.log(cache.has('nonexistent')); // false
+ * @see {@link LRU#get}
+ * @see {@link LRU#delete}
+ * @since 9.0.0
+ */
+ has (key) {
+ return key in this.items;
+ }
+
+ /**
+ * Efficiently moves an item to the end of the LRU list (most recently used position).
+ * This is an internal optimization method that avoids the overhead of the full set() operation
+ * when only LRU position needs to be updated.
+ *
+ * @method moveToEnd
+ * @memberof LRU
+ * @param {Object} item - The cache item with prev/next pointers to reposition.
+ * @private
+ * @since 11.3.5
+ */
+ moveToEnd (item) {
+ // If already at the end, nothing to do
+ if (this.last === item) {
+ return;
+ }
+
+ // Remove item from current position in the list
+ if (item.prev !== null) {
+ item.prev.next = item.next;
+ }
+
+ if (item.next !== null) {
+ item.next.prev = item.prev;
+ }
+
+ // Update first pointer if this was the first item
+ if (this.first === item) {
+ this.first = item.next;
+ }
+
+ // Add item to the end
+ item.prev = this.last;
+ item.next = null;
+
+ if (this.last !== null) {
+ this.last.next = item;
+ }
+
+ this.last = item;
+
+ // Handle edge case: if this was the only item, it's also first
+ if (this.first === null) {
+ this.first = item;
+ }
+ }
+
+ /**
+ * Returns an array of all keys in the cache, ordered from least to most recently used.
+ *
+ * @method keys
+ * @memberof LRU
+ * @returns {string[]} Array of keys in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * cache.get('a'); // Move 'a' to most recent
+ * console.log(cache.keys()); // ['b', 'a']
+ * @see {@link LRU#values}
+ * @see {@link LRU#entries}
+ * @since 9.0.0
+ */
+ keys () {
+ const result = [];
+ let x = this.first;
+
+ while (x !== null) {
+ result.push(x.key);
+ x = x.next;
+ }
+
+ return result;
+ }
+
+ /**
+ * Sets a value in the cache and returns any evicted item.
+ *
+ * @method setWithEvicted
+ * @memberof LRU
+ * @param {string} key - The key to set.
+ * @param {*} value - The value to store.
+ * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
+ * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null.
+ * @example
+ * const cache = new LRU(2);
+ * cache.set('a', 1).set('b', 2);
+ * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...}
+ * @see {@link LRU#set}
+ * @see {@link LRU#evict}
+ * @since 11.3.0
+ */
+ setWithEvicted (key, value, resetTtl = this.resetTtl) {
+ let evicted = null;
+
+ if (this.has(key)) {
+ this.set(key, value, true, resetTtl);
+ } else {
+ if (this.max > 0 && this.size === this.max) {
+ evicted = {...this.first};
+ this.evict(true);
+ }
+
+ let item = this.items[key] = {
+ expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
+ key: key,
+ prev: this.last,
+ next: null,
+ value
+ };
+
+ if (++this.size === 1) {
+ this.first = item;
+ } else {
+ this.last.next = item;
+ }
+
+ this.last = item;
+ }
+
+ return evicted;
+ }
+
+ /**
+ * Sets a value in the cache. Updates the item's position to most recently used.
+ *
+ * @method set
+ * @memberof LRU
+ * @param {string} key - The key to set.
+ * @param {*} value - The value to store.
+ * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method.
+ * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation.
+ * @returns {LRU} The LRU instance for method chaining.
+ * @example
+ * cache.set('key1', 'value1')
+ * .set('key2', 'value2')
+ * .set('key3', 'value3');
+ * @see {@link LRU#get}
+ * @see {@link LRU#setWithEvicted}
+ * @since 1.0.0
+ */
+ set (key, value, bypass = false, resetTtl = this.resetTtl) {
+ let item = this.items[key];
+
+ if (bypass || item !== undefined) {
+ // Existing item: update value and position
+ item.value = value;
+
+ if (bypass === false && resetTtl) {
+ item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
+ }
+
+ // Always move to end, but the bypass parameter affects TTL reset behavior
+ this.moveToEnd(item);
+ } else {
+ // New item: check for eviction and create
+ if (this.max > 0 && this.size === this.max) {
+ this.evict(true);
+ }
+
+ item = this.items[key] = {
+ expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
+ key: key,
+ prev: this.last,
+ next: null,
+ value
+ };
+
+ if (++this.size === 1) {
+ this.first = item;
+ } else {
+ this.last.next = item;
+ }
+
+ this.last = item;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns an array of all values in the cache for the specified keys.
+ * Order follows LRU order (least to most recently used).
+ *
+ * @method values
+ * @memberof LRU
+ * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys.
+ * @returns {Array<*>} Array of values corresponding to the keys in LRU order.
+ * @example
+ * cache.set('a', 1).set('b', 2);
+ * console.log(cache.values()); // [1, 2]
+ * console.log(cache.values(['a'])); // [1]
+ * @see {@link LRU#keys}
+ * @see {@link LRU#entries}
+ * @since 11.1.0
+ */
+ values (keys = this.keys()) {
+ return keys.map(key => this.get(key));
+ }
+ }
+
+ /**
+ * Factory function to create a new LRU cache instance with parameter validation.
+ *
+ * @function lru
+ * @param {number} [max=1000] - Maximum number of items to store. Must be >= 0. Use 0 for unlimited size.
+ * @param {number} [ttl=0] - Time to live in milliseconds. Must be >= 0. Use 0 for no expiration.
+ * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get().
+ * @returns {LRU} A new LRU cache instance.
+ * @throws {TypeError} When parameters are invalid (negative numbers or wrong types).
+ * @example
+ * // Create cache with factory function
+ * const cache = lru(100, 5000, true);
+ * cache.set('key', 'value');
+ *
+ * @example
+ * // Error handling
+ * try {
+ * const cache = lru(-1); // Invalid max
+ * } catch (error) {
+ * console.error(error.message); // "Invalid max value"
+ * }
+ * @see {@link LRU}
+ * @since 1.0.0
+ */
+ function lru (max = 1000, ttl = 0, resetTtl = false) {
+ if (isNaN(max) || max < 0) {
+ throw new TypeError("Invalid max value");
+ }
+
+ if (isNaN(ttl) || ttl < 0) {
+ throw new TypeError("Invalid ttl value");
+ }
+
+ if (typeof resetTtl !== "boolean") {
+ throw new TypeError("Invalid resetTtl value");
+ }
+
+ return new LRU(max, ttl, resetTtl);
+ }
+
+ "use strict";
+ const genericFontFamilies = [
+ "serif",
+ "sans-serif",
+ "monospace",
+ "cursive",
+ "fantasy",
+ "system-ui"
+ ];
+ function fontStringFromTextStyle(style) {
+ const fontSizeString = typeof style.fontSize === "number" ? `${style.fontSize}px` : style.fontSize;
+ let fontFamilies = style.fontFamily;
+ if (!Array.isArray(style.fontFamily)) {
+ fontFamilies = style.fontFamily.split(",");
+ }
+ for (let i = fontFamilies.length - 1; i >= 0; i--) {
+ let fontFamily = fontFamilies[i].trim();
+ if (!/([\"\'])[^\'\"]+\1/.test(fontFamily) && !genericFontFamilies.includes(fontFamily)) {
+ fontFamily = `"${fontFamily}"`;
+ }
+ fontFamilies[i] = fontFamily;
+ }
+ return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(",")}`;
+ }
+
+ "use strict";
+ const contextSettings = {
+ // TextMetrics requires getImageData readback for measuring fonts.
+ willReadFrequently: true
+ };
+ const _CanvasTextMetrics = class _CanvasTextMetrics {
+ /**
+ * Checking that we can use modern canvas 2D API.
+ *
+ * Note: This is an unstable API, Chrome < 94 use `textLetterSpacing`, later versions use `letterSpacing`.
+ * @see TextMetrics.experimentalLetterSpacing
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/ICanvasRenderingContext2D/letterSpacing
+ * @see https://developer.chrome.com/origintrials/#/view_trial/3585991203293757441
+ */
+ static get experimentalLetterSpacingSupported() {
+ let result = _CanvasTextMetrics._experimentalLetterSpacingSupported;
+ if (result === void 0) {
+ const proto = DOMAdapter.get().getCanvasRenderingContext2D().prototype;
+ result = _CanvasTextMetrics._experimentalLetterSpacingSupported = "letterSpacing" in proto || "textLetterSpacing" in proto;
+ }
+ return result;
+ }
+ /**
+ * @param text - the text that was measured
+ * @param style - the style that was measured
+ * @param width - the measured width of the text
+ * @param height - the measured height of the text
+ * @param lines - an array of the lines of text broken by new lines and wrapping if specified in style
+ * @param lineWidths - an array of the line widths for each line matched to `lines`
+ * @param lineHeight - the measured line height for this style
+ * @param maxLineWidth - the maximum line width for all measured lines
+ * @param {FontMetrics} fontProperties - the font properties object from TextMetrics.measureFont
+ */
+ constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) {
+ this.text = text;
+ this.style = style;
+ this.width = width;
+ this.height = height;
+ this.lines = lines;
+ this.lineWidths = lineWidths;
+ this.lineHeight = lineHeight;
+ this.maxLineWidth = maxLineWidth;
+ this.fontProperties = fontProperties;
+ }
+ /**
+ * Measures the supplied string of text and returns a Rectangle.
+ * @param text - The text to measure.
+ * @param style - The text style to use for measuring
+ * @param canvas - optional specification of the canvas to use for measuring.
+ * @param wordWrap
+ * @returns Measured width and height of the text.
+ */
+ static measureText(text = " ", style, canvas = _CanvasTextMetrics._canvas, wordWrap = style.wordWrap) {
+ var _a;
+ const textKey = `${text}-${style.styleKey}-wordWrap-${wordWrap}`;
+ if (_CanvasTextMetrics._measurementCache.has(textKey)) {
+ return _CanvasTextMetrics._measurementCache.get(textKey);
+ }
+ const font = fontStringFromTextStyle(style);
+ const fontProperties = _CanvasTextMetrics.measureFont(font);
+ if (fontProperties.fontSize === 0) {
+ fontProperties.fontSize = style.fontSize;
+ fontProperties.ascent = style.fontSize;
+ }
+ const context = _CanvasTextMetrics.__context;
+ context.font = font;
+ const outputText = wordWrap ? _CanvasTextMetrics._wordWrap(text, style, canvas) : text;
+ const lines = outputText.split(/(?:\r\n|\r|\n)/);
+ const lineWidths = new Array(lines.length);
+ let maxLineWidth = 0;
+ for (let i = 0; i < lines.length; i++) {
+ const lineWidth = _CanvasTextMetrics._measureText(lines[i], style.letterSpacing, context);
+ lineWidths[i] = lineWidth;
+ maxLineWidth = Math.max(maxLineWidth, lineWidth);
+ }
+ const strokeWidth = ((_a = style._stroke) == null ? void 0 : _a.width) || 0;
+ let width = maxLineWidth + strokeWidth;
+ if (style.dropShadow) {
+ width += style.dropShadow.distance;
+ }
+ const lineHeight = style.lineHeight || fontProperties.fontSize;
+ let height = Math.max(lineHeight, fontProperties.fontSize + strokeWidth) + (lines.length - 1) * (lineHeight + style.leading);
+ if (style.dropShadow) {
+ height += style.dropShadow.distance;
+ }
+ const measurements = new _CanvasTextMetrics(
+ text,
+ style,
+ width,
+ height,
+ lines,
+ lineWidths,
+ lineHeight + style.leading,
+ maxLineWidth,
+ fontProperties
+ );
+ _CanvasTextMetrics._measurementCache.set(textKey, measurements);
+ return measurements;
+ }
+ static _measureText(text, letterSpacing, context) {
+ let useExperimentalLetterSpacing = false;
+ if (_CanvasTextMetrics.experimentalLetterSpacingSupported) {
+ if (_CanvasTextMetrics.experimentalLetterSpacing) {
+ context.letterSpacing = `${letterSpacing}px`;
+ context.textLetterSpacing = `${letterSpacing}px`;
+ useExperimentalLetterSpacing = true;
+ } else {
+ context.letterSpacing = "0px";
+ context.textLetterSpacing = "0px";
+ }
+ }
+ const metrics = context.measureText(text);
+ let metricWidth = metrics.width;
+ const actualBoundingBoxLeft = -metrics.actualBoundingBoxLeft;
+ const actualBoundingBoxRight = metrics.actualBoundingBoxRight;
+ let boundsWidth = actualBoundingBoxRight - actualBoundingBoxLeft;
+ if (metricWidth > 0) {
+ if (useExperimentalLetterSpacing) {
+ metricWidth -= letterSpacing;
+ boundsWidth -= letterSpacing;
+ } else {
+ const val = (_CanvasTextMetrics.graphemeSegmenter(text).length - 1) * letterSpacing;
+ metricWidth += val;
+ boundsWidth += val;
+ }
+ }
+ return Math.max(metricWidth, boundsWidth);
+ }
+ /**
+ * Applies newlines to a string to have it optimally fit into the horizontal
+ * bounds set by the Text object's wordWrapWidth property.
+ * @param text - String to apply word wrapping to
+ * @param style - the style to use when wrapping
+ * @param canvas - optional specification of the canvas to use for measuring.
+ * @returns New string with new lines applied where required
+ */
+ static _wordWrap(text, style, canvas = _CanvasTextMetrics._canvas) {
+ const context = canvas.getContext("2d", contextSettings);
+ let width = 0;
+ let line = "";
+ let lines = "";
+ const cache = /* @__PURE__ */ Object.create(null);
+ const { letterSpacing, whiteSpace } = style;
+ const collapseSpaces = _CanvasTextMetrics._collapseSpaces(whiteSpace);
+ const collapseNewlines = _CanvasTextMetrics._collapseNewlines(whiteSpace);
+ let canPrependSpaces = !collapseSpaces;
+ const wordWrapWidth = style.wordWrapWidth + letterSpacing;
+ const tokens = _CanvasTextMetrics._tokenize(text);
+ for (let i = 0; i < tokens.length; i++) {
+ let token = tokens[i];
+ if (_CanvasTextMetrics._isNewline(token)) {
+ if (!collapseNewlines) {
+ lines += _CanvasTextMetrics._addLine(line);
+ canPrependSpaces = !collapseSpaces;
+ line = "";
+ width = 0;
+ continue;
+ }
+ token = " ";
+ }
+ if (collapseSpaces) {
+ const currIsBreakingSpace = _CanvasTextMetrics.isBreakingSpace(token);
+ const lastIsBreakingSpace = _CanvasTextMetrics.isBreakingSpace(line[line.length - 1]);
+ if (currIsBreakingSpace && lastIsBreakingSpace) {
+ continue;
+ }
+ }
+ const tokenWidth = _CanvasTextMetrics._getFromCache(token, letterSpacing, cache, context);
+ if (tokenWidth > wordWrapWidth) {
+ if (line !== "") {
+ lines += _CanvasTextMetrics._addLine(line);
+ line = "";
+ width = 0;
+ }
+ if (_CanvasTextMetrics.canBreakWords(token, style.breakWords)) {
+ const characters = _CanvasTextMetrics.wordWrapSplit(token);
+ for (let j = 0; j < characters.length; j++) {
+ let char = characters[j];
+ let lastChar = char;
+ let k = 1;
+ while (characters[j + k]) {
+ const nextChar = characters[j + k];
+ if (!_CanvasTextMetrics.canBreakChars(lastChar, nextChar, token, j, style.breakWords)) {
+ char += nextChar;
+ } else {
+ break;
+ }
+ lastChar = nextChar;
+ k++;
+ }
+ j += k - 1;
+ const characterWidth = _CanvasTextMetrics._getFromCache(char, letterSpacing, cache, context);
+ if (characterWidth + width > wordWrapWidth) {
+ lines += _CanvasTextMetrics._addLine(line);
+ canPrependSpaces = false;
+ line = "";
+ width = 0;
+ }
+ line += char;
+ width += characterWidth;
+ }
+ } else {
+ if (line.length > 0) {
+ lines += _CanvasTextMetrics._addLine(line);
+ line = "";
+ width = 0;
+ }
+ const isLastToken = i === tokens.length - 1;
+ lines += _CanvasTextMetrics._addLine(token, !isLastToken);
+ canPrependSpaces = false;
+ line = "";
+ width = 0;
+ }
+ } else {
+ if (tokenWidth + width > wordWrapWidth) {
+ canPrependSpaces = false;
+ lines += _CanvasTextMetrics._addLine(line);
+ line = "";
+ width = 0;
+ }
+ if (line.length > 0 || !_CanvasTextMetrics.isBreakingSpace(token) || canPrependSpaces) {
+ line += token;
+ width += tokenWidth;
+ }
+ }
+ }
+ lines += _CanvasTextMetrics._addLine(line, false);
+ return lines;
+ }
+ /**
+ * Convenience function for logging each line added during the wordWrap method.
+ * @param line - The line of text to add
+ * @param newLine - Add new line character to end
+ * @returns A formatted line
+ */
+ static _addLine(line, newLine = true) {
+ line = _CanvasTextMetrics._trimRight(line);
+ line = newLine ? `${line}
+` : line;
+ return line;
+ }
+ /**
+ * Gets & sets the widths of calculated characters in a cache object
+ * @param key - The key
+ * @param letterSpacing - The letter spacing
+ * @param cache - The cache
+ * @param context - The canvas context
+ * @returns The from cache.
+ */
+ static _getFromCache(key, letterSpacing, cache, context) {
+ let width = cache[key];
+ if (typeof width !== "number") {
+ width = _CanvasTextMetrics._measureText(key, letterSpacing, context) + letterSpacing;
+ cache[key] = width;
+ }
+ return width;
+ }
+ /**
+ * Determines whether we should collapse breaking spaces.
+ * @param whiteSpace - The TextStyle property whiteSpace
+ * @returns Should collapse
+ */
+ static _collapseSpaces(whiteSpace) {
+ return whiteSpace === "normal" || whiteSpace === "pre-line";
+ }
+ /**
+ * Determines whether we should collapse newLine chars.
+ * @param whiteSpace - The white space
+ * @returns should collapse
+ */
+ static _collapseNewlines(whiteSpace) {
+ return whiteSpace === "normal";
+ }
+ /**
+ * Trims breaking whitespaces from string.
+ * @param text - The text
+ * @returns Trimmed string
+ */
+ static _trimRight(text) {
+ if (typeof text !== "string") {
+ return "";
+ }
+ for (let i = text.length - 1; i >= 0; i--) {
+ const char = text[i];
+ if (!_CanvasTextMetrics.isBreakingSpace(char)) {
+ break;
+ }
+ text = text.slice(0, -1);
+ }
+ return text;
+ }
+ /**
+ * Determines if char is a newline.
+ * @param char - The character
+ * @returns True if newline, False otherwise.
+ */
+ static _isNewline(char) {
+ if (typeof char !== "string") {
+ return false;
+ }
+ return _CanvasTextMetrics._newlines.includes(char.charCodeAt(0));
+ }
+ /**
+ * Determines if char is a breaking whitespace.
+ *
+ * It allows one to determine whether char should be a breaking whitespace
+ * For example certain characters in CJK langs or numbers.
+ * It must return a boolean.
+ * @param char - The character
+ * @param [_nextChar] - The next character
+ * @returns True if whitespace, False otherwise.
+ */
+ static isBreakingSpace(char, _nextChar) {
+ if (typeof char !== "string") {
+ return false;
+ }
+ return _CanvasTextMetrics._breakingSpaces.includes(char.charCodeAt(0));
+ }
+ /**
+ * Splits a string into words, breaking-spaces and newLine characters
+ * @param text - The text
+ * @returns A tokenized array
+ */
+ static _tokenize(text) {
+ const tokens = [];
+ let token = "";
+ if (typeof text !== "string") {
+ return tokens;
+ }
+ for (let i = 0; i < text.length; i++) {
+ const char = text[i];
+ const nextChar = text[i + 1];
+ if (_CanvasTextMetrics.isBreakingSpace(char, nextChar) || _CanvasTextMetrics._isNewline(char)) {
+ if (token !== "") {
+ tokens.push(token);
+ token = "";
+ }
+ if (char === "\r" && nextChar === "\n") {
+ tokens.push("\r\n");
+ i++;
+ } else {
+ tokens.push(char);
+ }
+ continue;
+ }
+ token += char;
+ }
+ if (token !== "") {
+ tokens.push(token);
+ }
+ return tokens;
+ }
+ /**
+ * Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior.
+ *
+ * It allows one to customise which words should break
+ * Examples are if the token is CJK or numbers.
+ * It must return a boolean.
+ * @param _token - The token
+ * @param breakWords - The style attr break words
+ * @returns Whether to break word or not
+ */
+ static canBreakWords(_token, breakWords) {
+ return breakWords;
+ }
+ /**
+ * Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior.
+ *
+ * It allows one to determine whether a pair of characters
+ * should be broken by newlines
+ * For example certain characters in CJK langs or numbers.
+ * It must return a boolean.
+ * @param _char - The character
+ * @param _nextChar - The next character
+ * @param _token - The token/word the characters are from
+ * @param _index - The index in the token of the char
+ * @param _breakWords - The style attr break words
+ * @returns whether to break word or not
+ */
+ static canBreakChars(_char, _nextChar, _token, _index, _breakWords) {
+ return true;
+ }
+ /**
+ * Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior.
+ *
+ * It is called when a token (usually a word) has to be split into separate pieces
+ * in order to determine the point to break a word.
+ * It must return an array of characters.
+ * @param token - The token to split
+ * @returns The characters of the token
+ * @see CanvasTextMetrics.graphemeSegmenter
+ */
+ static wordWrapSplit(token) {
+ return _CanvasTextMetrics.graphemeSegmenter(token);
+ }
+ /**
+ * Calculates the ascent, descent and fontSize of a given font-style
+ * @param font - String representing the style of the font
+ * @returns Font properties object
+ */
+ static measureFont(font) {
+ if (_CanvasTextMetrics._fonts[font]) {
+ return _CanvasTextMetrics._fonts[font];
+ }
+ const context = _CanvasTextMetrics._context;
+ context.font = font;
+ const metrics = context.measureText(_CanvasTextMetrics.METRICS_STRING + _CanvasTextMetrics.BASELINE_SYMBOL);
+ const properties = {
+ ascent: metrics.actualBoundingBoxAscent,
+ descent: metrics.actualBoundingBoxDescent,
+ fontSize: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent
+ };
+ _CanvasTextMetrics._fonts[font] = properties;
+ return properties;
+ }
+ /**
+ * Clear font metrics in metrics cache.
+ * @param {string} [font] - font name. If font name not set then clear cache for all fonts.
+ */
+ static clearMetrics(font = "") {
+ if (font) {
+ delete _CanvasTextMetrics._fonts[font];
+ } else {
+ _CanvasTextMetrics._fonts = {};
+ }
+ }
+ /**
+ * Cached canvas element for measuring text
+ * TODO: this should be private, but isn't because of backward compat, will fix later.
+ * @ignore
+ */
+ static get _canvas() {
+ if (!_CanvasTextMetrics.__canvas) {
+ let canvas;
+ try {
+ const c = new OffscreenCanvas(0, 0);
+ const context = c.getContext("2d", contextSettings);
+ if (context == null ? void 0 : context.measureText) {
+ _CanvasTextMetrics.__canvas = c;
+ return c;
+ }
+ canvas = DOMAdapter.get().createCanvas();
+ } catch (_cx) {
+ canvas = DOMAdapter.get().createCanvas();
+ }
+ canvas.width = canvas.height = 10;
+ _CanvasTextMetrics.__canvas = canvas;
+ }
+ return _CanvasTextMetrics.__canvas;
+ }
+ /**
+ * TODO: this should be private, but isn't because of backward compat, will fix later.
+ * @ignore
+ */
+ static get _context() {
+ if (!_CanvasTextMetrics.__context) {
+ _CanvasTextMetrics.__context = _CanvasTextMetrics._canvas.getContext("2d", contextSettings);
+ }
+ return _CanvasTextMetrics.__context;
+ }
+ };
+ /**
+ * String used for calculate font metrics.
+ * These characters are all tall to help calculate the height required for text.
+ */
+ _CanvasTextMetrics.METRICS_STRING = "|\xC9q\xC5";
+ /** Baseline symbol for calculate font metrics. */
+ _CanvasTextMetrics.BASELINE_SYMBOL = "M";
+ /** Baseline multiplier for calculate font metrics. */
+ _CanvasTextMetrics.BASELINE_MULTIPLIER = 1.4;
+ /** Height multiplier for setting height of canvas to calculate font metrics. */
+ _CanvasTextMetrics.HEIGHT_MULTIPLIER = 2;
+ /**
+ * A Unicode "character", or "grapheme cluster", can be composed of multiple Unicode code points,
+ * such as letters with diacritical marks (e.g. `'\u0065\u0301'`, letter e with acute)
+ * or emojis with modifiers (e.g. `'\uD83E\uDDD1\u200D\uD83D\uDCBB'`, technologist).
+ * The new `Intl.Segmenter` API in ES2022 can split the string into grapheme clusters correctly. If it is not available,
+ * PixiJS will fallback to use the iterator of String, which can only spilt the string into code points.
+ * If you want to get full functionality in environments that don't support `Intl.Segmenter` (such as Firefox),
+ * you can use other libraries such as [grapheme-splitter]{@link https://www.npmjs.com/package/grapheme-splitter}
+ * or [graphemer]{@link https://www.npmjs.com/package/graphemer} to create a polyfill. Since these libraries can be
+ * relatively large in size to handle various Unicode grapheme clusters properly, PixiJS won't use them directly.
+ */
+ _CanvasTextMetrics.graphemeSegmenter = (() => {
+ if (typeof (Intl == null ? void 0 : Intl.Segmenter) === "function") {
+ const segmenter = new Intl.Segmenter();
+ return (s) => {
+ const segments = segmenter.segment(s);
+ const result = [];
+ let i = 0;
+ for (const segment of segments) {
+ result[i++] = segment.segment;
+ }
+ return result;
+ };
+ }
+ return (s) => [...s];
+ })();
+ /**
+ * New rendering behavior for letter-spacing which uses Chrome's new native API. This will
+ * lead to more accurate letter-spacing results because it does not try to manually draw
+ * each character. However, this Chrome API is experimental and may not serve all cases yet.
+ * @see TextMetrics.experimentalLetterSpacingSupported
+ */
+ _CanvasTextMetrics.experimentalLetterSpacing = false;
+ /** Cache of {@link TextMetrics.FontMetrics} objects. */
+ _CanvasTextMetrics._fonts = {};
+ /** Cache of new line chars. */
+ _CanvasTextMetrics._newlines = [
+ 10,
+ // line feed
+ 13
+ // carriage return
+ ];
+ /** Cache of breaking spaces. */
+ _CanvasTextMetrics._breakingSpaces = [
+ 9,
+ // character tabulation
+ 32,
+ // space
+ 8192,
+ // en quad
+ 8193,
+ // em quad
+ 8194,
+ // en space
+ 8195,
+ // em space
+ 8196,
+ // three-per-em space
+ 8197,
+ // four-per-em space
+ 8198,
+ // six-per-em space
+ 8200,
+ // punctuation space
+ 8201,
+ // thin space
+ 8202,
+ // hair space
+ 8287,
+ // medium mathematical space
+ 12288
+ // ideographic space
+ ];
+ /** Cache for measured text metrics */
+ _CanvasTextMetrics._measurementCache = lru(1e3);
+ let CanvasTextMetrics = _CanvasTextMetrics;
+
+ "use strict";
+ const PRECISION = 1e5;
+ function getCanvasFillStyle(fillStyle, context, textMetrics, padding = 0) {
+ var _a;
+ if (fillStyle.texture === Texture.WHITE && !fillStyle.fill) {
+ return Color.shared.setValue(fillStyle.color).setAlpha((_a = fillStyle.alpha) != null ? _a : 1).toHexa();
+ } else if (!fillStyle.fill) {
+ const pattern = context.createPattern(fillStyle.texture.source.resource, "repeat");
+ const tempMatrix = fillStyle.matrix.copyTo(Matrix.shared);
+ tempMatrix.scale(fillStyle.texture.frame.width, fillStyle.texture.frame.height);
+ pattern.setTransform(tempMatrix);
+ return pattern;
+ } else if (fillStyle.fill instanceof FillPattern) {
+ const fillPattern = fillStyle.fill;
+ const pattern = context.createPattern(fillPattern.texture.source.resource, "repeat");
+ const tempMatrix = fillPattern.transform.copyTo(Matrix.shared);
+ tempMatrix.scale(
+ fillPattern.texture.frame.width,
+ fillPattern.texture.frame.height
+ );
+ pattern.setTransform(tempMatrix);
+ return pattern;
+ } else if (fillStyle.fill instanceof FillGradient) {
+ const fillGradient = fillStyle.fill;
+ const isLinear = fillGradient.type === "linear";
+ const isLocal = fillGradient.textureSpace === "local";
+ let width = 1;
+ let height = 1;
+ if (isLocal && textMetrics) {
+ width = textMetrics.width + padding;
+ height = textMetrics.height + padding;
+ }
+ let gradient;
+ let isNearlyVertical = false;
+ if (isLinear) {
+ const { start, end } = fillGradient;
+ gradient = context.createLinearGradient(
+ start.x * width,
+ start.y * height,
+ end.x * width,
+ end.y * height
+ );
+ isNearlyVertical = Math.abs(end.x - start.x) < Math.abs((end.y - start.y) * 0.1);
+ } else {
+ const { center, innerRadius, outerCenter, outerRadius } = fillGradient;
+ gradient = context.createRadialGradient(
+ center.x * width,
+ center.y * height,
+ innerRadius * width,
+ outerCenter.x * width,
+ outerCenter.y * height,
+ outerRadius * width
+ );
+ }
+ if (isNearlyVertical && isLocal && textMetrics) {
+ const ratio = textMetrics.lineHeight / height;
+ for (let i = 0; i < textMetrics.lines.length; i++) {
+ const start = (i * textMetrics.lineHeight + padding / 2) / height;
+ fillGradient.colorStops.forEach((stop) => {
+ const globalStop = start + stop.offset * ratio;
+ gradient.addColorStop(
+ // fix to 5 decimal places to avoid floating point precision issues
+ Math.floor(globalStop * PRECISION) / PRECISION,
+ Color.shared.setValue(stop.color).toHex()
+ );
+ });
+ }
+ } else {
+ fillGradient.colorStops.forEach((stop) => {
+ gradient.addColorStop(stop.offset, Color.shared.setValue(stop.color).toHex());
+ });
+ }
+ return gradient;
+ }
+ warn("FillStyle not recognised", fillStyle);
+ return "red";
+ }
+
+ "use strict";
+ const tempRect$1 = new Rectangle();
+ class CanvasTextGeneratorClass {
+ /**
+ * Creates a canvas with the specified text rendered to it.
+ *
+ * Generates a canvas of appropriate size, renders the text with the provided style,
+ * and returns both the canvas/context and a Rectangle representing the text bounds.
+ *
+ * When trim is enabled in the style, the frame will represent the bounds of the
+ * non-transparent pixels, which can be smaller than the full canvas.
+ * @param options - The options for generating the text canvas
+ * @param options.text - The text to render
+ * @param options.style - The style to apply to the text
+ * @param options.resolution - The resolution of the canvas (defaults to 1)
+ * @param options.padding
+ * @returns An object containing the canvas/context and the frame (bounds) of the text
+ */
+ getCanvasAndContext(options) {
+ const { text, style, resolution = 1 } = options;
+ const padding = style._getFinalPadding();
+ const measured = CanvasTextMetrics.measureText(text || " ", style);
+ const width = Math.ceil(Math.ceil(Math.max(1, measured.width) + padding * 2) * resolution);
+ const height = Math.ceil(Math.ceil(Math.max(1, measured.height) + padding * 2) * resolution);
+ const canvasAndContext = CanvasPool.getOptimalCanvasAndContext(width, height);
+ this._renderTextToCanvas(text, style, padding, resolution, canvasAndContext);
+ const frame = style.trim ? getCanvasBoundingBox({ canvas: canvasAndContext.canvas, width, height, resolution: 1, output: tempRect$1 }) : tempRect$1.set(0, 0, width, height);
+ return {
+ canvasAndContext,
+ frame
+ };
+ }
+ /**
+ * Returns a canvas and context to the pool.
+ *
+ * This should be called when you're done with the canvas to allow reuse
+ * and prevent memory leaks.
+ * @param canvasAndContext - The canvas and context to return to the pool
+ */
+ returnCanvasAndContext(canvasAndContext) {
+ CanvasPool.returnCanvasAndContext(canvasAndContext);
+ }
+ /**
+ * Renders text to its canvas, and updates its texture.
+ * @param text - The text to render
+ * @param style - The style of the text
+ * @param padding - The padding of the text
+ * @param resolution - The resolution of the text
+ * @param canvasAndContext - The canvas and context to render the text to
+ */
+ _renderTextToCanvas(text, style, padding, resolution, canvasAndContext) {
+ var _a, _b, _c, _d, _e;
+ const { canvas, context } = canvasAndContext;
+ const font = fontStringFromTextStyle(style);
+ const measured = CanvasTextMetrics.measureText(text || " ", style);
+ const lines = measured.lines;
+ const lineHeight = measured.lineHeight;
+ const lineWidths = measured.lineWidths;
+ const maxLineWidth = measured.maxLineWidth;
+ const fontProperties = measured.fontProperties;
+ const height = canvas.height;
+ context.resetTransform();
+ context.scale(resolution, resolution);
+ context.textBaseline = style.textBaseline;
+ if ((_a = style._stroke) == null ? void 0 : _a.width) {
+ const strokeStyle = style._stroke;
+ context.lineWidth = strokeStyle.width;
+ context.miterLimit = strokeStyle.miterLimit;
+ context.lineJoin = strokeStyle.join;
+ context.lineCap = strokeStyle.cap;
+ }
+ context.font = font;
+ 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) + padding * 2) : 0;
+ const dsOffsetShadow = dsOffsetText * resolution;
+ if (isShadowPass) {
+ context.fillStyle = "black";
+ context.strokeStyle = "black";
+ const shadowOptions = style.dropShadow;
+ const dropShadowColor = shadowOptions.color;
+ const dropShadowAlpha = shadowOptions.alpha;
+ context.shadowColor = Color.shared.setValue(dropShadowColor).setAlpha(dropShadowAlpha).toRgbaString();
+ const dropShadowBlur = shadowOptions.blur * resolution;
+ const dropShadowDistance = shadowOptions.distance * resolution;
+ context.shadowBlur = dropShadowBlur;
+ context.shadowOffsetX = Math.cos(shadowOptions.angle) * dropShadowDistance;
+ context.shadowOffsetY = Math.sin(shadowOptions.angle) * dropShadowDistance + dsOffsetShadow;
+ } else {
+ context.fillStyle = style._fill ? getCanvasFillStyle(style._fill, context, measured, padding * 2) : null;
+ if ((_b = style._stroke) == null ? void 0 : _b.width) {
+ const strokePadding = style._stroke.width * 0.5 + padding * 2;
+ context.strokeStyle = getCanvasFillStyle(style._stroke, context, measured, strokePadding);
+ }
+ context.shadowColor = "black";
+ }
+ let linePositionYShift = (lineHeight - fontProperties.fontSize) / 2;
+ if (lineHeight - fontProperties.fontSize < 0) {
+ linePositionYShift = 0;
+ }
+ const strokeWidth = (_d = (_c = style._stroke) == null ? void 0 : _c.width) != null ? _d : 0;
+ for (let i2 = 0; i2 < lines.length; i2++) {
+ linePositionX = strokeWidth / 2;
+ linePositionY = strokeWidth / 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 ((_e = style._stroke) == null ? void 0 : _e.width) {
+ this._drawLetterSpacing(
+ lines[i2],
+ style,
+ canvasAndContext,
+ linePositionX + padding,
+ linePositionY + padding - dsOffsetText,
+ true
+ );
+ }
+ if (style._fill !== void 0) {
+ this._drawLetterSpacing(
+ lines[i2],
+ style,
+ canvasAndContext,
+ linePositionX + padding,
+ linePositionY + padding - dsOffsetText
+ );
+ }
+ }
+ }
+ }
+ /**
+ * Render the text with letter-spacing.
+ *
+ * This method handles rendering text with the correct letter spacing, using either:
+ * 1. Native letter spacing if supported by the browser
+ * 2. Manual letter spacing calculation if not natively supported
+ *
+ * For manual letter spacing, it calculates the position of each character
+ * based on its width and the desired spacing.
+ * @param text - The text to draw
+ * @param style - The text style to apply
+ * @param canvasAndContext - The canvas and context to draw to
+ * @param x - Horizontal position to draw the text
+ * @param y - Vertical position to draw the text
+ * @param isStroke - Whether to render the stroke (true) or fill (false)
+ * @private
+ */
+ _drawLetterSpacing(text, style, canvasAndContext, x, y, isStroke = false) {
+ const { context } = canvasAndContext;
+ const letterSpacing = style.letterSpacing;
+ let useExperimentalLetterSpacing = false;
+ if (CanvasTextMetrics.experimentalLetterSpacingSupported) {
+ if (CanvasTextMetrics.experimentalLetterSpacing) {
+ context.letterSpacing = `${letterSpacing}px`;
+ context.textLetterSpacing = `${letterSpacing}px`;
+ useExperimentalLetterSpacing = true;
+ } else {
+ context.letterSpacing = "0px";
+ context.textLetterSpacing = "0px";
+ }
+ }
+ if (letterSpacing === 0 || useExperimentalLetterSpacing) {
+ if (isStroke) {
+ context.strokeText(text, x, y);
+ } else {
+ context.fillText(text, x, y);
+ }
+ return;
+ }
+ let currentPosition = x;
+ const stringArray = CanvasTextMetrics.graphemeSegmenter(text);
+ let previousWidth = context.measureText(text).width;
+ let currentWidth = 0;
+ for (let i = 0; i < stringArray.length; ++i) {
+ const currentChar = stringArray[i];
+ if (isStroke) {
+ context.strokeText(currentChar, currentPosition, y);
+ } else {
+ context.fillText(currentChar, currentPosition, y);
+ }
+ let textStr = "";
+ for (let j = i + 1; j < stringArray.length; ++j) {
+ textStr += stringArray[j];
+ }
+ currentWidth = context.measureText(textStr).width;
+ currentPosition += previousWidth - currentWidth + letterSpacing;
+ previousWidth = currentWidth;
+ }
+ }
+ }
+ const CanvasTextGenerator = new CanvasTextGeneratorClass();
+
+ "use strict";
+ class CanvasTextSystem {
+ constructor(_renderer) {
+ this._activeTextures = {};
+ this._renderer = _renderer;
+ }
+ getTexture(options, _resolution, _style, _textKey) {
+ var _a;
+ if (typeof options === "string") {
+ deprecation("8.0.0", "CanvasTextSystem.getTexture: Use object TextOptions instead of separate arguments");
+ options = {
+ text: options,
+ style: _style,
+ resolution: _resolution
+ };
+ }
+ if (!(options.style instanceof TextStyle)) {
+ options.style = new TextStyle(options.style);
+ }
+ if (!(options.textureStyle instanceof TextureStyle)) {
+ options.textureStyle = new TextureStyle(options.textureStyle);
+ }
+ if (typeof options.text !== "string") {
+ options.text = options.text.toString();
+ }
+ const { text, style, textureStyle } = options;
+ const resolution = (_a = options.resolution) != null ? _a : this._renderer.resolution;
+ const { frame, canvasAndContext } = CanvasTextGenerator.getCanvasAndContext({
+ text,
+ style,
+ resolution
+ });
+ const texture = getPo2TextureFromSource(canvasAndContext.canvas, frame.width, frame.height, resolution);
+ if (textureStyle)
+ texture.source.style = textureStyle;
+ if (style.trim) {
+ frame.pad(style.padding);
+ texture.frame.copyFrom(frame);
+ texture.frame.scale(1 / resolution);
+ texture.updateUvs();
+ }
+ if (style.filters) {
+ const filteredTexture = this._applyFilters(texture, style.filters);
+ this.returnTexture(texture);
+ CanvasTextGenerator.returnCanvasAndContext(canvasAndContext);
+ return filteredTexture;
+ }
+ this._renderer.texture.initSource(texture._source);
+ CanvasTextGenerator.returnCanvasAndContext(canvasAndContext);
+ return texture;
+ }
+ /**
+ * Returns a texture that was created wit the above `getTexture` function.
+ * Handy if you are done with a texture and want to return it to the pool.
+ * @param texture - The texture to be returned.
+ */
+ returnTexture(texture) {
+ const source = texture.source;
+ source.resource = null;
+ source.uploadMethodId = "unknown";
+ source.alphaMode = "no-premultiply-alpha";
+ TexturePool.returnTexture(texture, true);
+ }
+ /**
+ * Renders text to its canvas, and updates its texture.
+ * @deprecated since 8.10.0
+ */
+ renderTextToCanvas() {
+ deprecation(
+ "8.10.0",
+ "CanvasTextSystem.renderTextToCanvas: no longer supported, use CanvasTextSystem.getTexture instead"
+ );
+ }
+ /**
+ * Gets or creates a managed texture for a Text object. This method handles texture reuse and reference counting.
+ * @param text - The Text object that needs a texture
+ * @returns A Texture instance that represents the rendered text
+ * @remarks
+ * This method performs the following:
+ * 1. Sets the appropriate resolution based on auto-resolution settings
+ * 2. Checks if a texture already exists for the text's style
+ * 3. Creates a new texture if needed or returns an existing one
+ * 4. Manages reference counting for texture reuse
+ */
+ getManagedTexture(text) {
+ text._resolution = text._autoResolution ? this._renderer.resolution : text.resolution;
+ const textKey = text.styleKey;
+ if (this._activeTextures[textKey]) {
+ this._increaseReferenceCount(textKey);
+ return this._activeTextures[textKey].texture;
+ }
+ const texture = this.getTexture({
+ text: text.text,
+ style: text.style,
+ resolution: text._resolution,
+ textureStyle: text.textureStyle
+ });
+ this._activeTextures[textKey] = {
+ texture,
+ usageCount: 1
+ };
+ return texture;
+ }
+ /**
+ * Decreases the reference count for a texture associated with a text key.
+ * When the reference count reaches zero, the texture is returned to the pool.
+ * @param textKey - The unique key identifying the text style configuration
+ * @remarks
+ * This method is crucial for memory management, ensuring textures are properly
+ * cleaned up when they are no longer needed by any Text instances.
+ */
+ decreaseReferenceCount(textKey) {
+ const activeTexture = this._activeTextures[textKey];
+ activeTexture.usageCount--;
+ if (activeTexture.usageCount === 0) {
+ this.returnTexture(activeTexture.texture);
+ this._activeTextures[textKey] = null;
+ }
+ }
+ /**
+ * Gets the current reference count for a texture associated with a text key.
+ * @param textKey - The unique key identifying the text style configuration
+ * @returns The number of Text instances currently using this texture
+ */
+ getReferenceCount(textKey) {
+ var _a, _b;
+ return (_b = (_a = this._activeTextures[textKey]) == null ? void 0 : _a.usageCount) != null ? _b : null;
+ }
+ _increaseReferenceCount(textKey) {
+ this._activeTextures[textKey].usageCount++;
+ }
+ /**
+ * Applies the specified filters to the given texture.
+ *
+ * This method takes a texture and a list of filters, applies the filters to the texture,
+ * and returns the resulting texture. It also ensures that the alpha mode of the resulting
+ * texture is set to 'premultiplied-alpha'.
+ * @param {Texture} texture - The texture to which the filters will be applied.
+ * @param {Filter[]} filters - The filters to apply to the texture.
+ * @returns {Texture} The resulting texture after all filters have been applied.
+ */
+ _applyFilters(texture, filters) {
+ const currentRenderTarget = this._renderer.renderTarget.renderTarget;
+ const resultTexture = this._renderer.filter.generateFilteredTexture({
+ texture,
+ filters
+ });
+ this._renderer.renderTarget.bind(currentRenderTarget, false);
+ return resultTexture;
+ }
+ destroy() {
+ this._renderer = null;
+ for (const key in this._activeTextures) {
+ if (this._activeTextures[key])
+ this.returnTexture(this._activeTextures[key].texture);
+ }
+ this._activeTextures = null;
+ }
+ }
+ /** @ignore */
+ CanvasTextSystem.extension = {
+ type: [
+ ExtensionType.WebGLSystem,
+ ExtensionType.WebGPUSystem,
+ ExtensionType.CanvasSystem
+ ],
+ name: "canvasText"
+ };
+
+ "use strict";
+ extensions.add(CanvasTextSystem);
+ extensions.add(CanvasTextPipe);
+
+ "use strict";
+ var __defProp$S = Object.defineProperty;
+ var __getOwnPropSymbols$T = Object.getOwnPropertySymbols;
+ var __hasOwnProp$T = Object.prototype.hasOwnProperty;
+ var __propIsEnum$T = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$S = (obj, key, value) => key in obj ? __defProp$S(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$S = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$T.call(b, prop))
+ __defNormalProp$S(a, prop, b[prop]);
+ if (__getOwnPropSymbols$T)
+ for (var prop of __getOwnPropSymbols$T(b)) {
+ if (__propIsEnum$T.call(b, prop))
+ __defNormalProp$S(a, prop, b[prop]);
+ }
+ return a;
+ };
+ var __objRest$j = (source, exclude) => {
+ var target = {};
+ for (var prop in source)
+ if (__hasOwnProp$T.call(source, prop) && exclude.indexOf(prop) < 0)
+ target[prop] = source[prop];
+ if (source != null && __getOwnPropSymbols$T)
+ for (var prop of __getOwnPropSymbols$T(source)) {
+ if (exclude.indexOf(prop) < 0 && __propIsEnum$T.call(source, prop))
+ target[prop] = source[prop];
+ }
+ return target;
+ };
+ class Graphics extends ViewContainer {
+ /**
+ * Creates a new Graphics object.
+ * @param options - Options for the Graphics.
+ */
+ constructor(options) {
+ if (options instanceof GraphicsContext) {
+ options = { context: options };
+ }
+ const _a = options || {}, { context, roundPixels } = _a, rest = __objRest$j(_a, ["context", "roundPixels"]);
+ super(__spreadValues$S({
+ label: "Graphics"
+ }, rest));
+ /** @internal */
+ this.renderPipeId = "graphics";
+ if (!context) {
+ this._context = this._ownedContext = new GraphicsContext();
+ } else {
+ this._context = context;
+ }
+ this._context.on("update", this.onViewUpdate, this);
+ this.didViewUpdate = true;
+ this.allowChildren = false;
+ this.roundPixels = roundPixels != null ? roundPixels : false;
+ }
+ set context(context) {
+ if (context === this._context)
+ return;
+ this._context.off("update", this.onViewUpdate, this);
+ this._context = context;
+ this._context.on("update", this.onViewUpdate, this);
+ this.onViewUpdate();
+ }
+ /**
+ * The underlying graphics context used for drawing operations.
+ * Controls how shapes and paths are rendered.
+ * @example
+ * ```ts
+ * // Create a shared context
+ * const sharedContext = new GraphicsContext();
+ *
+ * // Create graphics objects sharing the same context
+ * const graphics1 = new Graphics();
+ * const graphics2 = new Graphics();
+ *
+ * // Assign shared context
+ * graphics1.context = sharedContext;
+ * graphics2.context = sharedContext;
+ *
+ * // Both graphics will show the same shapes
+ * sharedContext
+ * .rect(0, 0, 100, 100)
+ * .fill({ color: 0xff0000 });
+ * ```
+ * @see {@link GraphicsContext} For drawing operations
+ * @see {@link GraphicsOptions} For context configuration
+ */
+ get context() {
+ return this._context;
+ }
+ /**
+ * The local bounds of the graphics object.
+ * Returns the boundaries after all graphical operations but before any transforms.
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ *
+ * // Draw a shape
+ * graphics
+ * .rect(0, 0, 100, 100)
+ * .fill({ color: 0xff0000 });
+ *
+ * // Get bounds information
+ * const bounds = graphics.bounds;
+ * console.log(bounds.width); // 100
+ * console.log(bounds.height); // 100
+ * ```
+ * @readonly
+ * @see {@link Bounds} For bounds operations
+ * @see {@link Container#getBounds} For transformed bounds
+ */
+ get bounds() {
+ return this._context.bounds;
+ }
+ /**
+ * Graphics objects do not need to update their bounds as the context handles this.
+ * @private
+ */
+ updateBounds() {
+ }
+ /**
+ * Checks if the object contains the given point.
+ * Returns true if the point lies within the Graphics object's rendered area.
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ *
+ * // Draw a shape
+ * graphics
+ * .rect(0, 0, 100, 100)
+ * .fill({ color: 0xff0000 });
+ *
+ * // Check point intersection
+ * if (graphics.containsPoint({ x: 50, y: 50 })) {
+ * console.log('Point is inside rectangle!');
+ * }
+ * ```
+ * @param point - The point to check in local coordinates
+ * @returns True if the point is inside the Graphics object
+ * @see {@link Graphics#bounds} For bounding box checks
+ * @see {@link PointData} For point data structure
+ */
+ containsPoint(point) {
+ return this._context.containsPoint(point);
+ }
+ /**
+ * Destroys this graphics renderable and optionally its context.
+ * @param options - Options parameter. A boolean will act as if all options
+ *
+ * If the context was created by this graphics and `destroy(false)` or `destroy()` is called
+ * then the context will still be destroyed.
+ *
+ * If you want to explicitly not destroy this context that this graphics created,
+ * then you should pass destroy({ context: false })
+ *
+ * If the context was passed in as an argument to the constructor then it will not be destroyed
+ * @example
+ * ```ts
+ * // Destroy the graphics and its context
+ * graphics.destroy();
+ * graphics.destroy(true);
+ * graphics.destroy({ context: true, texture: true, textureSource: true });
+ * ```
+ */
+ destroy(options) {
+ if (this._ownedContext && !options) {
+ this._ownedContext.destroy(options);
+ } else if (options === true || (options == null ? void 0 : options.context) === true) {
+ this._context.destroy(options);
+ }
+ this._ownedContext = null;
+ this._context = null;
+ super.destroy(options);
+ }
+ _callContextMethod(method, args) {
+ this.context[method](...args);
+ return this;
+ }
+ // --------------------------------------- GraphicsContext methods ---------------------------------------
+ /**
+ * Sets the current fill style of the graphics context.
+ * The fill style can be a color, gradient, pattern, or a complex style object.
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ *
+ * // Basic color fill
+ * graphics
+ * .setFillStyle({ color: 0xff0000 }) // Red fill
+ * .rect(0, 0, 100, 100)
+ * .fill();
+ *
+ * // Gradient fill
+ * const gradient = new FillGradient({
+ * end: { x: 1, y: 0 },
+ * colorStops: [
+ * { offset: 0, color: 0xff0000 }, // Red at start
+ * { offset: 0.5, color: 0x00ff00 }, // Green at middle
+ * { offset: 1, color: 0x0000ff }, // Blue at end
+ * ],
+ * });
+ *
+ * graphics
+ * .setFillStyle(gradient)
+ * .circle(100, 100, 50)
+ * .fill();
+ *
+ * // Pattern fill
+ * const pattern = new FillPattern(texture);
+ * graphics
+ * .setFillStyle({
+ * fill: pattern,
+ * alpha: 0.5
+ * })
+ * .rect(0, 0, 200, 200)
+ * .fill();
+ * ```
+ * @param {FillInput} args - The fill style to apply
+ * @returns The Graphics instance for chaining
+ * @see {@link FillStyle} For fill style options
+ * @see {@link FillGradient} For gradient fills
+ * @see {@link FillPattern} For pattern fills
+ */
+ setFillStyle(...args) {
+ return this._callContextMethod("setFillStyle", args);
+ }
+ /**
+ * Sets the current stroke style of the graphics context.
+ * Similar to fill styles, stroke styles can encompass colors, gradients, patterns, or more detailed configurations.
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ *
+ * // Basic color stroke
+ * graphics
+ * .setStrokeStyle({
+ * width: 2,
+ * color: 0x000000
+ * })
+ * .rect(0, 0, 100, 100)
+ * .stroke();
+ *
+ * // Complex stroke style
+ * graphics
+ * .setStrokeStyle({
+ * width: 4,
+ * color: 0xff0000,
+ * alpha: 0.5,
+ * join: 'round',
+ * cap: 'round',
+ * alignment: 0.5
+ * })
+ * .circle(100, 100, 50)
+ * .stroke();
+ *
+ * // Gradient stroke
+ * const gradient = new FillGradient({
+ * end: { x: 1, y: 0 },
+ * colorStops: [
+ * { offset: 0, color: 0xff0000 }, // Red at start
+ * { offset: 0.5, color: 0x00ff00 }, // Green at middle
+ * { offset: 1, color: 0x0000ff }, // Blue at end
+ * ],
+ * });
+ *
+ * graphics
+ * .setStrokeStyle({
+ * width: 10,
+ * fill: gradient
+ * })
+ * .poly([0,0, 100,50, 0,100])
+ * .stroke();
+ * ```
+ * @param {StrokeInput} args - The stroke style to apply
+ * @returns The Graphics instance for chaining
+ * @see {@link StrokeStyle} For stroke style options
+ * @see {@link FillGradient} For gradient strokes
+ * @see {@link FillPattern} For pattern strokes
+ */
+ setStrokeStyle(...args) {
+ return this._callContextMethod("setStrokeStyle", args);
+ }
+ fill(...args) {
+ return this._callContextMethod("fill", args);
+ }
+ /**
+ * Strokes the current path with the current stroke style or specified style.
+ * Outlines the shape using the stroke settings.
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ *
+ * // Stroke with direct color
+ * graphics
+ * .circle(50, 50, 25)
+ * .stroke({
+ * width: 2,
+ * color: 0xff0000
+ * }); // 2px red stroke
+ *
+ * // Fill with texture
+ * graphics
+ * .rect(0, 0, 100, 100)
+ * .stroke(myTexture); // Fill with texture
+ *
+ * // Stroke with gradient
+ * const gradient = new FillGradient({
+ * end: { x: 1, y: 0 },
+ * colorStops: [
+ * { offset: 0, color: 0xff0000 },
+ * { offset: 0.5, color: 0x00ff00 },
+ * { offset: 1, color: 0x0000ff },
+ * ],
+ * });
+ *
+ * graphics
+ * .rect(0, 0, 100, 100)
+ * .stroke({
+ * width: 4,
+ * fill: gradient,
+ * alignment: 0.5,
+ * join: 'round'
+ * });
+ * ```
+ * @param {StrokeStyle} args - Optional stroke style to apply. Can be:
+ * - A stroke style object with width, color, etc.
+ * - A gradient
+ * - A pattern
+ * If omitted, uses current stroke style.
+ * @returns The Graphics instance for chaining
+ * @see {@link StrokeStyle} For stroke style options
+ * @see {@link FillGradient} For gradient strokes
+ * @see {@link setStrokeStyle} For setting default stroke style
+ */
+ stroke(...args) {
+ return this._callContextMethod("stroke", args);
+ }
+ texture(...args) {
+ return this._callContextMethod("texture", args);
+ }
+ /**
+ * Resets the current path. Any previous path and its commands are discarded and a new path is
+ * started. This is typically called before beginning a new shape or series of drawing commands.
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ * graphics
+ * .circle(150, 150, 50)
+ * .fill({ color: 0x00ff00 })
+ * .beginPath() // Starts a new path
+ * .circle(250, 150, 50)
+ * .fill({ color: 0x0000ff });
+ * ```
+ * @returns The Graphics instance for chaining
+ * @see {@link Graphics#moveTo} For starting a new subpath
+ * @see {@link Graphics#closePath} For closing the current path
+ */
+ beginPath() {
+ return this._callContextMethod("beginPath", []);
+ }
+ /**
+ * Applies a cutout to the last drawn shape. This is used to create holes or complex shapes by
+ * subtracting a path from the previously drawn path.
+ *
+ * If a hole is not completely in a shape, it will fail to cut correctly.
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ *
+ * // Draw outer circle
+ * graphics
+ * .circle(100, 100, 50)
+ * .fill({ color: 0xff0000 });
+ * .circle(100, 100, 25) // Inner circle
+ * .cut() // Cuts out the inner circle from the outer circle
+ * ```
+ */
+ cut() {
+ return this._callContextMethod("cut", []);
+ }
+ arc(...args) {
+ return this._callContextMethod("arc", args);
+ }
+ arcTo(...args) {
+ return this._callContextMethod("arcTo", args);
+ }
+ arcToSvg(...args) {
+ return this._callContextMethod("arcToSvg", args);
+ }
+ bezierCurveTo(...args) {
+ return this._callContextMethod("bezierCurveTo", args);
+ }
+ /**
+ * Closes the current path by drawing a straight line back to the start point.
+ *
+ * This is useful for completing shapes and ensuring they are properly closed for fills.
+ * @example
+ * ```ts
+ * // Create a triangle with closed path
+ * const graphics = new Graphics();
+ * graphics
+ * .moveTo(50, 50)
+ * .lineTo(100, 100)
+ * .lineTo(0, 100)
+ * .closePath()
+ * ```
+ * @returns The Graphics instance for method chaining
+ * @see {@link Graphics#beginPath} For starting a new path
+ * @see {@link Graphics#fill} For filling closed paths
+ * @see {@link Graphics#stroke} For stroking paths
+ */
+ closePath() {
+ return this._callContextMethod("closePath", []);
+ }
+ ellipse(...args) {
+ return this._callContextMethod("ellipse", args);
+ }
+ circle(...args) {
+ return this._callContextMethod("circle", args);
+ }
+ path(...args) {
+ return this._callContextMethod("path", args);
+ }
+ lineTo(...args) {
+ return this._callContextMethod("lineTo", args);
+ }
+ moveTo(...args) {
+ return this._callContextMethod("moveTo", args);
+ }
+ quadraticCurveTo(...args) {
+ return this._callContextMethod("quadraticCurveTo", args);
+ }
+ rect(...args) {
+ return this._callContextMethod("rect", args);
+ }
+ roundRect(...args) {
+ return this._callContextMethod("roundRect", args);
+ }
+ poly(...args) {
+ return this._callContextMethod("poly", args);
+ }
+ regularPoly(...args) {
+ return this._callContextMethod("regularPoly", args);
+ }
+ roundPoly(...args) {
+ return this._callContextMethod("roundPoly", args);
+ }
+ roundShape(...args) {
+ return this._callContextMethod("roundShape", args);
+ }
+ filletRect(...args) {
+ return this._callContextMethod("filletRect", args);
+ }
+ chamferRect(...args) {
+ return this._callContextMethod("chamferRect", args);
+ }
+ star(...args) {
+ return this._callContextMethod("star", args);
+ }
+ svg(...args) {
+ return this._callContextMethod("svg", args);
+ }
+ restore(...args) {
+ return this._callContextMethod("restore", args);
+ }
+ /**
+ * Saves the current graphics state onto a stack. The state includes:
+ * - Current transformation matrix
+ * - Current fill style
+ * - Current stroke style
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ *
+ * // Save state before complex operations
+ * graphics.save();
+ *
+ * // Create transformed and styled shape
+ * graphics
+ * .translateTransform(100, 100)
+ * .rotateTransform(Math.PI / 4)
+ * .setFillStyle({
+ * color: 0xff0000,
+ * alpha: 0.5
+ * })
+ * .rect(-25, -25, 50, 50)
+ * .fill();
+ *
+ * // Restore to original state
+ * graphics.restore();
+ *
+ * // Continue drawing with previous state
+ * graphics
+ * .circle(50, 50, 25)
+ * .fill();
+ * ```
+ * @returns The Graphics instance for method chaining
+ * @see {@link Graphics#restore} For restoring the saved state
+ * @see {@link Graphics#setTransform} For setting transformations
+ */
+ save() {
+ return this._callContextMethod("save", []);
+ }
+ /**
+ * Returns the current transformation matrix of the graphics context.
+ * This matrix represents all accumulated transformations including translate, scale, and rotate.
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ *
+ * // Apply some transformations
+ * graphics
+ * .translateTransform(100, 100)
+ * .rotateTransform(Math.PI / 4);
+ *
+ * // Get the current transform matrix
+ * const matrix = graphics.getTransform();
+ * console.log(matrix.tx, matrix.ty); // 100, 100
+ *
+ * // Use the matrix for other operations
+ * graphics
+ * .setTransform(matrix)
+ * .circle(0, 0, 50)
+ * .fill({ color: 0xff0000 });
+ * ```
+ * @returns The current transformation matrix.
+ * @see {@link Graphics#setTransform} For setting the transform matrix
+ * @see {@link Matrix} For matrix operations
+ */
+ getTransform() {
+ return this.context.getTransform();
+ }
+ /**
+ * Resets the current transformation matrix to the identity matrix, effectively removing
+ * any transformations (rotation, scaling, translation) previously applied.
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ *
+ * // Apply transformations
+ * graphics
+ * .translateTransform(100, 100)
+ * .scaleTransform(2, 2)
+ * .circle(0, 0, 25)
+ * .fill({ color: 0xff0000 });
+ * // Reset transform to default state
+ * graphics
+ * .resetTransform()
+ * .circle(50, 50, 25) // Will draw at actual coordinates
+ * .fill({ color: 0x00ff00 });
+ * ```
+ * @returns The Graphics instance for method chaining
+ * @see {@link Graphics#getTransform} For getting the current transform
+ * @see {@link Graphics#setTransform} For setting a specific transform
+ * @see {@link Graphics#save} For saving the current transform state
+ * @see {@link Graphics#restore} For restoring a previous transform state
+ */
+ resetTransform() {
+ return this._callContextMethod("resetTransform", []);
+ }
+ rotateTransform(...args) {
+ return this._callContextMethod("rotate", args);
+ }
+ scaleTransform(...args) {
+ return this._callContextMethod("scale", args);
+ }
+ setTransform(...args) {
+ return this._callContextMethod("setTransform", args);
+ }
+ transform(...args) {
+ return this._callContextMethod("transform", args);
+ }
+ translateTransform(...args) {
+ return this._callContextMethod("translate", args);
+ }
+ /**
+ * Clears all drawing commands from the graphics context, effectively resetting it.
+ * This includes clearing the current path, fill style, stroke style, and transformations.
+ *
+ * > [!NOTE] Graphics objects are not designed to be continuously cleared and redrawn.
+ * > Instead, they are intended to be used for static or semi-static graphics that
+ * > can be redrawn as needed. Frequent clearing and redrawing may lead to performance issues.
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ *
+ * // Draw some shapes
+ * graphics
+ * .circle(100, 100, 50)
+ * .fill({ color: 0xff0000 })
+ * .rect(200, 100, 100, 50)
+ * .fill({ color: 0x00ff00 });
+ *
+ * // Clear all graphics
+ * graphics.clear();
+ *
+ * // Start fresh with new shapes
+ * graphics
+ * .circle(150, 150, 30)
+ * .fill({ color: 0x0000ff });
+ * ```
+ * @returns The Graphics instance for method chaining
+ * @see {@link Graphics#beginPath} For starting a new path without clearing styles
+ * @see {@link Graphics#save} For saving the current state
+ * @see {@link Graphics#restore} For restoring a previous state
+ */
+ clear() {
+ return this._callContextMethod("clear", []);
+ }
+ /**
+ * Gets or sets the current fill style for the graphics context. The fill style determines
+ * how shapes are filled when using the fill() method.
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ *
+ * // Basic color fill
+ * graphics.fillStyle = {
+ * color: 0xff0000, // Red
+ * alpha: 1
+ * };
+ *
+ * // Using gradients
+ * const gradient = new FillGradient({
+ * end: { x: 0, y: 1 }, // Vertical gradient
+ * stops: [
+ * { offset: 0, color: 0xff0000, alpha: 1 }, // Start color
+ * { offset: 1, color: 0x0000ff, alpha: 1 } // End color
+ * ]
+ * });
+ *
+ * graphics.fillStyle = {
+ * fill: gradient,
+ * alpha: 0.8
+ * };
+ *
+ * // Using patterns
+ * graphics.fillStyle = {
+ * texture: myTexture,
+ * alpha: 1,
+ * matrix: new Matrix()
+ * .scale(0.5, 0.5)
+ * .rotate(Math.PI / 4)
+ * };
+ * ```
+ * @type {ConvertedFillStyle}
+ * @see {@link FillStyle} For all available fill style options
+ * @see {@link FillGradient} For creating gradient fills
+ * @see {@link Graphics#fill} For applying the fill to paths
+ */
+ get fillStyle() {
+ return this._context.fillStyle;
+ }
+ set fillStyle(value) {
+ this._context.fillStyle = value;
+ }
+ /**
+ * Gets or sets the current stroke style for the graphics context. The stroke style determines
+ * how paths are outlined when using the stroke() method.
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ *
+ * // Basic stroke style
+ * graphics.strokeStyle = {
+ * width: 2,
+ * color: 0xff0000,
+ * alpha: 1
+ * };
+ *
+ * // Using with gradients
+ * const gradient = new FillGradient({
+ * end: { x: 0, y: 1 },
+ * stops: [
+ * { offset: 0, color: 0xff0000, alpha: 1 },
+ * { offset: 1, color: 0x0000ff, alpha: 1 }
+ * ]
+ * });
+ *
+ * graphics.strokeStyle = {
+ * width: 4,
+ * fill: gradient,
+ * alignment: 0.5,
+ * join: 'round',
+ * cap: 'round'
+ * };
+ *
+ * // Complex stroke settings
+ * graphics.strokeStyle = {
+ * width: 6,
+ * color: 0x00ff00,
+ * alpha: 0.5,
+ * join: 'miter',
+ * miterLimit: 10,
+ * };
+ * ```
+ * @see {@link StrokeStyle} For all available stroke style options
+ * @see {@link Graphics#stroke} For applying the stroke to paths
+ */
+ get strokeStyle() {
+ return this._context.strokeStyle;
+ }
+ set strokeStyle(value) {
+ this._context.strokeStyle = value;
+ }
+ /**
+ * Creates a new Graphics object that copies the current graphics content.
+ * The clone can either share the same context (shallow clone) or have its own independent
+ * context (deep clone).
+ * @example
+ * ```ts
+ * const graphics = new Graphics();
+ *
+ * // Create original graphics content
+ * graphics
+ * .circle(100, 100, 50)
+ * .fill({ color: 0xff0000 });
+ *
+ * // Create a shallow clone (shared context)
+ * const shallowClone = graphics.clone();
+ *
+ * // Changes to original affect the clone
+ * graphics
+ * .circle(200, 100, 30)
+ * .fill({ color: 0x00ff00 });
+ *
+ * // Create a deep clone (independent context)
+ * const deepClone = graphics.clone(true);
+ *
+ * // Modify deep clone independently
+ * deepClone
+ * .translateTransform(100, 100)
+ * .circle(0, 0, 40)
+ * .fill({ color: 0x0000ff });
+ * ```
+ * @param deep - Whether to create a deep clone of the graphics object.
+ * If false (default), the context will be shared between objects.
+ * If true, creates an independent copy of the context.
+ * @returns A new Graphics instance with either shared or copied context
+ * @see {@link Graphics#context} For accessing the underlying graphics context
+ * @see {@link GraphicsContext} For understanding the shared context behavior
+ */
+ clone(deep = false) {
+ if (deep) {
+ return new Graphics(this._context.clone());
+ }
+ this._ownedContext = null;
+ const clone = new Graphics(this._context);
+ return clone;
+ }
+ // -------- v7 deprecations ---------
+ /**
+ * @param width
+ * @param color
+ * @param alpha
+ * @deprecated since 8.0.0 Use {@link Graphics#setStrokeStyle} instead
+ */
+ lineStyle(width, color, alpha) {
+ deprecation(v8_0_0, "Graphics#lineStyle is no longer needed. Use Graphics#setStrokeStyle to set the stroke style.");
+ const strokeStyle = {};
+ width && (strokeStyle.width = width);
+ color && (strokeStyle.color = color);
+ alpha && (strokeStyle.alpha = alpha);
+ this.context.strokeStyle = strokeStyle;
+ return this;
+ }
+ /**
+ * @param color
+ * @param alpha
+ * @deprecated since 8.0.0 Use {@link Graphics#fill} instead
+ */
+ beginFill(color, alpha) {
+ deprecation(v8_0_0, "Graphics#beginFill is no longer needed. Use Graphics#fill to fill the shape with the desired style.");
+ const fillStyle = {};
+ if (color !== void 0)
+ fillStyle.color = color;
+ if (alpha !== void 0)
+ fillStyle.alpha = alpha;
+ this.context.fillStyle = fillStyle;
+ return this;
+ }
+ /**
+ * @deprecated since 8.0.0 Use {@link Graphics#fill} instead
+ */
+ endFill() {
+ deprecation(v8_0_0, "Graphics#endFill is no longer needed. Use Graphics#fill to fill the shape with the desired style.");
+ this.context.fill();
+ const strokeStyle = this.context.strokeStyle;
+ if (strokeStyle.width !== GraphicsContext.defaultStrokeStyle.width || strokeStyle.color !== GraphicsContext.defaultStrokeStyle.color || strokeStyle.alpha !== GraphicsContext.defaultStrokeStyle.alpha) {
+ this.context.stroke();
+ }
+ return this;
+ }
+ /**
+ * @param {...any} args
+ * @deprecated since 8.0.0 Use {@link Graphics#circle} instead
+ */
+ drawCircle(...args) {
+ deprecation(v8_0_0, "Graphics#drawCircle has been renamed to Graphics#circle");
+ return this._callContextMethod("circle", args);
+ }
+ /**
+ * @param {...any} args
+ * @deprecated since 8.0.0 Use {@link Graphics#ellipse} instead
+ */
+ drawEllipse(...args) {
+ deprecation(v8_0_0, "Graphics#drawEllipse has been renamed to Graphics#ellipse");
+ return this._callContextMethod("ellipse", args);
+ }
+ /**
+ * @param {...any} args
+ * @deprecated since 8.0.0 Use {@link Graphics#poly} instead
+ */
+ drawPolygon(...args) {
+ deprecation(v8_0_0, "Graphics#drawPolygon has been renamed to Graphics#poly");
+ return this._callContextMethod("poly", args);
+ }
+ /**
+ * @param {...any} args
+ * @deprecated since 8.0.0 Use {@link Graphics#rect} instead
+ */
+ drawRect(...args) {
+ deprecation(v8_0_0, "Graphics#drawRect has been renamed to Graphics#rect");
+ return this._callContextMethod("rect", args);
+ }
+ /**
+ * @param {...any} args
+ * @deprecated since 8.0.0 Use {@link Graphics#roundRect} instead
+ */
+ drawRoundedRect(...args) {
+ deprecation(v8_0_0, "Graphics#drawRoundedRect has been renamed to Graphics#roundRect");
+ return this._callContextMethod("roundRect", args);
+ }
+ /**
+ * @param {...any} args
+ * @deprecated since 8.0.0 Use {@link Graphics#star} instead
+ */
+ drawStar(...args) {
+ deprecation(v8_0_0, "Graphics#drawStar has been renamed to Graphics#star");
+ return this._callContextMethod("star", args);
+ }
+ }
+
+ "use strict";
+ const localUniformMSDFBit = {
+ name: "local-uniform-msdf-bit",
+ vertex: {
+ header: (
+ /* wgsl */
+ `
+ struct LocalUniforms {
+ uColor:vec4,
+ uTransformMatrix:mat3x3,
+ uDistance: f32,
+ uRound:f32,
+ }
+
+ @group(2) @binding(0) var localUniforms : LocalUniforms;
+ `
+ ),
+ main: (
+ /* wgsl */
+ `
+ vColor *= localUniforms.uColor;
+ modelMatrix *= localUniforms.uTransformMatrix;
+ `
+ ),
+ end: (
+ /* wgsl */
+ `
+ if(localUniforms.uRound == 1)
+ {
+ vPosition = vec4(roundPixels(vPosition.xy, globalUniforms.uResolution), vPosition.zw);
+ }
+ `
+ )
+ },
+ fragment: {
+ header: (
+ /* wgsl */
+ `
+ struct LocalUniforms {
+ uColor:vec4,
+ uTransformMatrix:mat3x3,
+ uDistance: f32
+ }
+
+ @group(2) @binding(0) var localUniforms : LocalUniforms;
+ `
+ ),
+ main: (
+ /* wgsl */
+ `
+ outColor = vec4(calculateMSDFAlpha(outColor, localUniforms.uColor, localUniforms.uDistance));
+ `
+ )
+ }
+ };
+ const localUniformMSDFBitGl = {
+ name: "local-uniform-msdf-bit",
+ vertex: {
+ header: (
+ /* glsl */
+ `
+ uniform mat3 uTransformMatrix;
+ uniform vec4 uColor;
+ uniform float uRound;
+ `
+ ),
+ main: (
+ /* glsl */
+ `
+ vColor *= uColor;
+ modelMatrix *= uTransformMatrix;
+ `
+ ),
+ end: (
+ /* glsl */
+ `
+ if(uRound == 1.)
+ {
+ gl_Position.xy = roundPixels(gl_Position.xy, uResolution);
+ }
+ `
+ )
+ },
+ fragment: {
+ header: (
+ /* glsl */
+ `
+ uniform float uDistance;
+ `
+ ),
+ main: (
+ /* glsl */
+ `
+ outColor = vec4(calculateMSDFAlpha(outColor, vColor, uDistance));
+ `
+ )
+ }
+ };
+
+ "use strict";
+ const mSDFBit = {
+ name: "msdf-bit",
+ fragment: {
+ header: (
+ /* wgsl */
+ `
+ fn calculateMSDFAlpha(msdfColor:vec4, shapeColor:vec4, distance:f32) -> f32 {
+
+ // MSDF
+ var median = msdfColor.r + msdfColor.g + msdfColor.b -
+ min(msdfColor.r, min(msdfColor.g, msdfColor.b)) -
+ max(msdfColor.r, max(msdfColor.g, msdfColor.b));
+
+ // SDF
+ median = min(median, msdfColor.a);
+
+ var screenPxDistance = distance * (median - 0.5);
+ var alpha = clamp(screenPxDistance + 0.5, 0.0, 1.0);
+ if (median < 0.01) {
+ alpha = 0.0;
+ } else if (median > 0.99) {
+ alpha = 1.0;
+ }
+
+ // Gamma correction for coverage-like alpha
+ var luma: f32 = dot(shapeColor.rgb, vec3(0.299, 0.587, 0.114));
+ var gamma: f32 = mix(1.0, 1.0 / 2.2, luma);
+ var coverage: f32 = pow(shapeColor.a * alpha, gamma);
+
+ return coverage;
+
+ }
+ `
+ )
+ }
+ };
+ const mSDFBitGl = {
+ name: "msdf-bit",
+ fragment: {
+ header: (
+ /* glsl */
+ `
+ float calculateMSDFAlpha(vec4 msdfColor, vec4 shapeColor, float distance) {
+
+ // MSDF
+ float median = msdfColor.r + msdfColor.g + msdfColor.b -
+ min(msdfColor.r, min(msdfColor.g, msdfColor.b)) -
+ max(msdfColor.r, max(msdfColor.g, msdfColor.b));
+
+ // SDF
+ median = min(median, msdfColor.a);
+
+ float screenPxDistance = distance * (median - 0.5);
+ float alpha = clamp(screenPxDistance + 0.5, 0.0, 1.0);
+
+ if (median < 0.01) {
+ alpha = 0.0;
+ } else if (median > 0.99) {
+ alpha = 1.0;
+ }
+
+ // Gamma correction for coverage-like alpha
+ float luma = dot(shapeColor.rgb, vec3(0.299, 0.587, 0.114));
+ float gamma = mix(1.0, 1.0 / 2.2, luma);
+ float coverage = pow(shapeColor.a * alpha, gamma);
+
+ return coverage;
+ }
+ `
+ )
+ }
+ };
+
+ "use strict";
+ let gpuProgram$1;
+ let glProgram$1;
+ class SdfShader extends Shader {
+ constructor(maxTextures) {
+ const uniforms = new UniformGroup({
+ uColor: { value: new Float32Array([1, 1, 1, 1]), type: "vec4" },
+ uTransformMatrix: { value: new Matrix(), type: "mat3x3" },
+ uDistance: { value: 4, type: "f32" },
+ uRound: { value: 0, type: "f32" }
+ });
+ gpuProgram$1 != null ? gpuProgram$1 : gpuProgram$1 = compileHighShaderGpuProgram({
+ name: "sdf-shader",
+ bits: [
+ colorBit,
+ generateTextureBatchBit(maxTextures),
+ localUniformMSDFBit,
+ mSDFBit,
+ roundPixelsBit
+ ]
+ });
+ glProgram$1 != null ? glProgram$1 : glProgram$1 = compileHighShaderGlProgram({
+ name: "sdf-shader",
+ bits: [
+ colorBitGl,
+ generateTextureBatchBitGl(maxTextures),
+ localUniformMSDFBitGl,
+ mSDFBitGl,
+ roundPixelsBitGl
+ ]
+ });
+ super({
+ glProgram: glProgram$1,
+ gpuProgram: gpuProgram$1,
+ resources: {
+ localUniforms: uniforms,
+ batchSamplers: getBatchSamplersUniformGroup(maxTextures)
+ }
+ });
+ }
+ }
+
+ "use strict";
+ class AbstractBitmapFont extends EventEmitter {
+ constructor() {
+ super(...arguments);
+ /** The map of characters by character code. */
+ this.chars = /* @__PURE__ */ Object.create(null);
+ /**
+ * The line-height of the font face in pixels.
+ * @type {number}
+ */
+ this.lineHeight = 0;
+ /**
+ * The name of the font face
+ * @type {string}
+ */
+ this.fontFamily = "";
+ /** The metrics of the font face. */
+ this.fontMetrics = { fontSize: 0, ascent: 0, descent: 0 };
+ /**
+ * The offset of the font face from the baseline.
+ * @type {number}
+ */
+ this.baseLineOffset = 0;
+ /** The range and type of the distance field for this font. */
+ this.distanceField = { type: "none", range: 0 };
+ /** The map of base page textures (i.e., sheets of glyphs). */
+ this.pages = [];
+ /** should the fill for this font be applied as a tint to the text. */
+ this.applyFillAsTint = true;
+ /** The size of the font face in pixels. */
+ this.baseMeasurementFontSize = 100;
+ this.baseRenderedFontSize = 100;
+ }
+ /**
+ * The name of the font face.
+ * @deprecated since 8.0.0 Use `fontFamily` instead.
+ */
+ get font() {
+ deprecation(v8_0_0, "BitmapFont.font is deprecated, please use BitmapFont.fontFamily instead.");
+ return this.fontFamily;
+ }
+ /**
+ * The map of base page textures (i.e., sheets of glyphs).
+ * @deprecated since 8.0.0 Use `pages` instead.
+ */
+ get pageTextures() {
+ deprecation(v8_0_0, "BitmapFont.pageTextures is deprecated, please use BitmapFont.pages instead.");
+ return this.pages;
+ }
+ /**
+ * The size of the font face in pixels.
+ * @deprecated since 8.0.0 Use `fontMetrics.fontSize` instead.
+ */
+ get size() {
+ deprecation(v8_0_0, "BitmapFont.size is deprecated, please use BitmapFont.fontMetrics.fontSize instead.");
+ return this.fontMetrics.fontSize;
+ }
+ /**
+ * The kind of distance field for this font or "none".
+ * @deprecated since 8.0.0 Use `distanceField.type` instead.
+ */
+ get distanceFieldRange() {
+ deprecation(v8_0_0, "BitmapFont.distanceFieldRange is deprecated, please use BitmapFont.distanceField.range instead.");
+ return this.distanceField.range;
+ }
+ /**
+ * The range of the distance field in pixels.
+ * @deprecated since 8.0.0 Use `distanceField.range` instead.
+ */
+ get distanceFieldType() {
+ deprecation(v8_0_0, "BitmapFont.distanceFieldType is deprecated, please use BitmapFont.distanceField.type instead.");
+ return this.distanceField.type;
+ }
+ destroy(destroyTextures = false) {
+ var _a;
+ this.emit("destroy", this);
+ this.removeAllListeners();
+ for (const i in this.chars) {
+ (_a = this.chars[i].texture) == null ? void 0 : _a.destroy();
+ }
+ this.chars = null;
+ if (destroyTextures) {
+ this.pages.forEach((page) => page.texture.destroy(true));
+ this.pages = null;
+ }
+ }
+ }
+
+ "use strict";
+ var __defProp$R = Object.defineProperty;
+ var __getOwnPropSymbols$S = Object.getOwnPropertySymbols;
+ var __hasOwnProp$S = Object.prototype.hasOwnProperty;
+ var __propIsEnum$S = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$R = (obj, key, value) => key in obj ? __defProp$R(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$R = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$S.call(b, prop))
+ __defNormalProp$R(a, prop, b[prop]);
+ if (__getOwnPropSymbols$S)
+ for (var prop of __getOwnPropSymbols$S(b)) {
+ if (__propIsEnum$S.call(b, prop))
+ __defNormalProp$R(a, prop, b[prop]);
+ }
+ return a;
+ };
+ const _DynamicBitmapFont = class _DynamicBitmapFont extends AbstractBitmapFont {
+ /**
+ * @param options - The options for the dynamic bitmap font.
+ */
+ constructor(options) {
+ var _a, _b, _c;
+ super();
+ /**
+ * this is a resolution modifier for the font size..
+ * texture resolution will also be used to scale texture according to its font size also
+ */
+ this.resolution = 1;
+ /** The pages of the font. */
+ this.pages = [];
+ this._padding = 0;
+ this._measureCache = /* @__PURE__ */ Object.create(null);
+ this._currentChars = [];
+ this._currentX = 0;
+ this._currentY = 0;
+ this._currentMaxCharHeight = 0;
+ this._currentPageIndex = -1;
+ this._skipKerning = false;
+ const dynamicOptions = __spreadValues$R(__spreadValues$R({}, _DynamicBitmapFont.defaultOptions), options);
+ this._textureSize = dynamicOptions.textureSize;
+ this._mipmap = dynamicOptions.mipmap;
+ const style = dynamicOptions.style.clone();
+ if (dynamicOptions.overrideFill) {
+ style._fill.color = 16777215;
+ style._fill.alpha = 1;
+ style._fill.texture = Texture.WHITE;
+ style._fill.fill = null;
+ }
+ this.applyFillAsTint = dynamicOptions.overrideFill;
+ const requestedFontSize = style.fontSize;
+ style.fontSize = this.baseMeasurementFontSize;
+ const font = fontStringFromTextStyle(style);
+ if (dynamicOptions.overrideSize) {
+ if (style._stroke) {
+ style._stroke.width *= this.baseRenderedFontSize / requestedFontSize;
+ }
+ } else {
+ style.fontSize = this.baseRenderedFontSize = requestedFontSize;
+ }
+ this._style = style;
+ this._skipKerning = (_a = dynamicOptions.skipKerning) != null ? _a : false;
+ this.resolution = (_b = dynamicOptions.resolution) != null ? _b : 1;
+ this._padding = (_c = dynamicOptions.padding) != null ? _c : 4;
+ if (dynamicOptions.textureStyle) {
+ this._textureStyle = dynamicOptions.textureStyle instanceof TextureStyle ? dynamicOptions.textureStyle : new TextureStyle(dynamicOptions.textureStyle);
+ }
+ this.fontMetrics = CanvasTextMetrics.measureFont(font);
+ this.lineHeight = style.lineHeight || this.fontMetrics.fontSize || style.fontSize;
+ }
+ ensureCharacters(chars) {
+ var _a, _b, _c, _d;
+ const charList = CanvasTextMetrics.graphemeSegmenter(chars).filter((char) => !this._currentChars.includes(char)).filter((char, index, self) => self.indexOf(char) === index);
+ if (!charList.length)
+ return;
+ this._currentChars = [...this._currentChars, ...charList];
+ let pageData;
+ if (this._currentPageIndex === -1) {
+ pageData = this._nextPage();
+ } else {
+ pageData = this.pages[this._currentPageIndex];
+ }
+ let { canvas, context } = pageData.canvasAndContext;
+ let textureSource = pageData.texture.source;
+ const style = this._style;
+ let currentX = this._currentX;
+ let currentY = this._currentY;
+ let currentMaxCharHeight = this._currentMaxCharHeight;
+ const fontScale = this.baseRenderedFontSize / this.baseMeasurementFontSize;
+ const padding = this._padding * fontScale;
+ let skipTexture = false;
+ const maxTextureWidth = canvas.width / this.resolution;
+ const maxTextureHeight = canvas.height / this.resolution;
+ for (let i = 0; i < charList.length; i++) {
+ const char = charList[i];
+ const metrics = CanvasTextMetrics.measureText(char, style, canvas, false);
+ metrics.lineHeight = metrics.height;
+ const width = metrics.width * fontScale;
+ const textureGlyphWidth = Math.ceil((style.fontStyle === "italic" ? 2 : 1) * width);
+ const height = metrics.height * fontScale;
+ const paddedWidth = textureGlyphWidth + padding * 2;
+ const paddedHeight = height + padding * 2;
+ skipTexture = false;
+ if (char !== "\n" && char !== "\r" && char !== " " && char !== " ") {
+ skipTexture = true;
+ currentMaxCharHeight = Math.ceil(Math.max(paddedHeight, currentMaxCharHeight));
+ }
+ if (currentX + paddedWidth > maxTextureWidth) {
+ currentY += currentMaxCharHeight;
+ currentMaxCharHeight = paddedHeight;
+ currentX = 0;
+ if (currentY + currentMaxCharHeight > maxTextureHeight) {
+ textureSource.update();
+ const pageData2 = this._nextPage();
+ canvas = pageData2.canvasAndContext.canvas;
+ context = pageData2.canvasAndContext.context;
+ textureSource = pageData2.texture.source;
+ currentX = 0;
+ currentY = 0;
+ currentMaxCharHeight = 0;
+ }
+ }
+ const xAdvance = width / fontScale - ((_b = (_a = style.dropShadow) == null ? void 0 : _a.distance) != null ? _b : 0) - ((_d = (_c = style._stroke) == null ? void 0 : _c.width) != null ? _d : 0);
+ this.chars[char] = {
+ id: char.codePointAt(0),
+ xOffset: -this._padding,
+ yOffset: -this._padding,
+ xAdvance,
+ kerning: {}
+ };
+ if (skipTexture) {
+ this._drawGlyph(
+ context,
+ metrics,
+ currentX + padding,
+ currentY + padding,
+ fontScale,
+ style
+ );
+ const px = textureSource.width * fontScale;
+ const py = textureSource.height * fontScale;
+ const frame = new Rectangle(
+ currentX / px * textureSource.width,
+ currentY / py * textureSource.height,
+ paddedWidth / px * textureSource.width,
+ paddedHeight / py * textureSource.height
+ );
+ this.chars[char].texture = new Texture({
+ source: textureSource,
+ frame
+ });
+ currentX += Math.ceil(paddedWidth);
+ }
+ }
+ textureSource.update();
+ this._currentX = currentX;
+ this._currentY = currentY;
+ this._currentMaxCharHeight = currentMaxCharHeight;
+ this._skipKerning && this._applyKerning(charList, context);
+ }
+ /**
+ * @deprecated since 8.0.0
+ * The map of base page textures (i.e., sheets of glyphs).
+ */
+ get pageTextures() {
+ deprecation(v8_0_0, "BitmapFont.pageTextures is deprecated, please use BitmapFont.pages instead.");
+ return this.pages;
+ }
+ _applyKerning(newChars, context) {
+ const measureCache = this._measureCache;
+ for (let i = 0; i < newChars.length; i++) {
+ const first = newChars[i];
+ for (let j = 0; j < this._currentChars.length; j++) {
+ const second = this._currentChars[j];
+ let c1 = measureCache[first];
+ if (!c1)
+ c1 = measureCache[first] = context.measureText(first).width;
+ let c2 = measureCache[second];
+ if (!c2)
+ c2 = measureCache[second] = context.measureText(second).width;
+ let total = context.measureText(first + second).width;
+ let amount = total - (c1 + c2);
+ if (amount) {
+ this.chars[first].kerning[second] = amount;
+ }
+ total = context.measureText(first + second).width;
+ amount = total - (c1 + c2);
+ if (amount) {
+ this.chars[second].kerning[first] = amount;
+ }
+ }
+ }
+ }
+ _nextPage() {
+ this._currentPageIndex++;
+ const textureResolution = this.resolution;
+ const canvasAndContext = CanvasPool.getOptimalCanvasAndContext(
+ this._textureSize,
+ this._textureSize,
+ textureResolution
+ );
+ this._setupContext(canvasAndContext.context, this._style, textureResolution);
+ const resolution = textureResolution * (this.baseRenderedFontSize / this.baseMeasurementFontSize);
+ const texture = new Texture({
+ source: new ImageSource({
+ resource: canvasAndContext.canvas,
+ resolution,
+ alphaMode: "premultiply-alpha-on-upload",
+ autoGenerateMipmaps: this._mipmap
+ })
+ });
+ if (this._textureStyle) {
+ texture.source.style = this._textureStyle;
+ }
+ const pageData = {
+ canvasAndContext,
+ texture
+ };
+ this.pages[this._currentPageIndex] = pageData;
+ return pageData;
+ }
+ // canvas style!
+ _setupContext(context, style, resolution) {
+ var _a;
+ style.fontSize = this.baseRenderedFontSize;
+ context.scale(resolution, resolution);
+ context.font = fontStringFromTextStyle(style);
+ style.fontSize = this.baseMeasurementFontSize;
+ context.textBaseline = style.textBaseline;
+ const stroke = style._stroke;
+ const strokeThickness = (_a = stroke == null ? void 0 : stroke.width) != null ? _a : 0;
+ if (stroke) {
+ context.lineWidth = strokeThickness;
+ context.lineJoin = stroke.join;
+ context.miterLimit = stroke.miterLimit;
+ context.strokeStyle = getCanvasFillStyle(stroke, context);
+ }
+ if (style._fill) {
+ context.fillStyle = getCanvasFillStyle(style._fill, context);
+ }
+ if (style.dropShadow) {
+ const shadowOptions = style.dropShadow;
+ const rgb = Color.shared.setValue(shadowOptions.color).toArray();
+ const dropShadowBlur = shadowOptions.blur * resolution;
+ const dropShadowDistance = shadowOptions.distance * resolution;
+ context.shadowColor = `rgba(${rgb[0] * 255},${rgb[1] * 255},${rgb[2] * 255},${shadowOptions.alpha})`;
+ context.shadowBlur = dropShadowBlur;
+ context.shadowOffsetX = Math.cos(shadowOptions.angle) * dropShadowDistance;
+ context.shadowOffsetY = Math.sin(shadowOptions.angle) * dropShadowDistance;
+ } else {
+ context.shadowColor = "black";
+ context.shadowBlur = 0;
+ context.shadowOffsetX = 0;
+ context.shadowOffsetY = 0;
+ }
+ }
+ _drawGlyph(context, metrics, x, y, fontScale, style) {
+ var _a;
+ const char = metrics.text;
+ const fontProperties = metrics.fontProperties;
+ const stroke = style._stroke;
+ const strokeThickness = ((_a = stroke == null ? void 0 : stroke.width) != null ? _a : 0) * fontScale;
+ const tx = x + strokeThickness / 2;
+ const ty = y - strokeThickness / 2;
+ const descent = fontProperties.descent * fontScale;
+ const lineHeight = metrics.lineHeight * fontScale;
+ let removeShadow = false;
+ if (style.stroke && strokeThickness) {
+ removeShadow = true;
+ context.strokeText(char, tx, ty + lineHeight - descent);
+ }
+ const { shadowBlur, shadowOffsetX, shadowOffsetY } = context;
+ if (style._fill) {
+ if (removeShadow) {
+ context.shadowBlur = 0;
+ context.shadowOffsetX = 0;
+ context.shadowOffsetY = 0;
+ }
+ context.fillText(char, tx, ty + lineHeight - descent);
+ }
+ if (removeShadow) {
+ context.shadowBlur = shadowBlur;
+ context.shadowOffsetX = shadowOffsetX;
+ context.shadowOffsetY = shadowOffsetY;
+ }
+ }
+ destroy() {
+ super.destroy();
+ for (let i = 0; i < this.pages.length; i++) {
+ const { canvasAndContext, texture } = this.pages[i];
+ CanvasPool.returnCanvasAndContext(canvasAndContext);
+ texture.destroy(true);
+ }
+ this.pages = null;
+ }
+ };
+ _DynamicBitmapFont.defaultOptions = {
+ textureSize: 512,
+ style: new TextStyle(),
+ mipmap: true
+ };
+ let DynamicBitmapFont = _DynamicBitmapFont;
+
+ "use strict";
+ function getBitmapTextLayout(chars, style, font, trimEnd) {
+ const layoutData = {
+ width: 0,
+ height: 0,
+ offsetY: 0,
+ scale: style.fontSize / font.baseMeasurementFontSize,
+ lines: [{
+ width: 0,
+ charPositions: [],
+ spaceWidth: 0,
+ spacesIndex: [],
+ chars: []
+ }]
+ };
+ layoutData.offsetY = font.baseLineOffset;
+ let currentLine = layoutData.lines[0];
+ let previousChar = null;
+ let firstWord = true;
+ const currentWord = {
+ spaceWord: false,
+ width: 0,
+ start: 0,
+ index: 0,
+ // use index to not modify the array as we use it a lot!
+ positions: [],
+ chars: []
+ };
+ const scale = font.baseMeasurementFontSize / style.fontSize;
+ const adjustedLetterSpacing = style.letterSpacing * scale;
+ const adjustedWordWrapWidth = style.wordWrapWidth * scale;
+ const adjustedLineHeight = style.lineHeight ? style.lineHeight * scale : font.lineHeight;
+ const breakWords = style.wordWrap && style.breakWords;
+ const nextWord = (word) => {
+ const start = currentLine.width;
+ for (let j = 0; j < currentWord.index; j++) {
+ const position = word.positions[j];
+ currentLine.chars.push(word.chars[j]);
+ currentLine.charPositions.push(position + start);
+ }
+ currentLine.width += word.width;
+ firstWord = false;
+ currentWord.width = 0;
+ currentWord.index = 0;
+ currentWord.chars.length = 0;
+ };
+ const nextLine = () => {
+ let index = currentLine.chars.length - 1;
+ if (trimEnd) {
+ let lastChar = currentLine.chars[index];
+ while (lastChar === " ") {
+ currentLine.width -= font.chars[lastChar].xAdvance;
+ lastChar = currentLine.chars[--index];
+ }
+ }
+ layoutData.width = Math.max(layoutData.width, currentLine.width);
+ currentLine = {
+ width: 0,
+ charPositions: [],
+ chars: [],
+ spaceWidth: 0,
+ spacesIndex: []
+ };
+ firstWord = true;
+ layoutData.lines.push(currentLine);
+ layoutData.height += adjustedLineHeight;
+ };
+ const checkIsOverflow = (lineWidth) => lineWidth - adjustedLetterSpacing > adjustedWordWrapWidth;
+ for (let i = 0; i < chars.length + 1; i++) {
+ let char;
+ const isEnd = i === chars.length;
+ if (!isEnd) {
+ char = chars[i];
+ }
+ const charData = font.chars[char] || font.chars[" "];
+ const isSpace = /(?:\s)/.test(char);
+ const isWordBreak = isSpace || char === "\r" || char === "\n" || isEnd;
+ if (isWordBreak) {
+ const addWordToNextLine = !firstWord && style.wordWrap && checkIsOverflow(currentLine.width + currentWord.width);
+ if (addWordToNextLine) {
+ nextLine();
+ nextWord(currentWord);
+ if (!isEnd) {
+ currentLine.charPositions.push(0);
+ }
+ } else {
+ currentWord.start = currentLine.width;
+ nextWord(currentWord);
+ if (!isEnd) {
+ currentLine.charPositions.push(0);
+ }
+ }
+ if (char === "\r" || char === "\n") {
+ nextLine();
+ } else if (!isEnd) {
+ const spaceWidth = charData.xAdvance + (charData.kerning[previousChar] || 0) + adjustedLetterSpacing;
+ currentLine.width += spaceWidth;
+ currentLine.spaceWidth = spaceWidth;
+ currentLine.spacesIndex.push(currentLine.charPositions.length);
+ currentLine.chars.push(char);
+ }
+ } else {
+ const kerning = charData.kerning[previousChar] || 0;
+ const nextCharWidth = charData.xAdvance + kerning + adjustedLetterSpacing;
+ const addWordToNextLine = breakWords && checkIsOverflow(currentLine.width + currentWord.width + nextCharWidth);
+ if (addWordToNextLine) {
+ nextWord(currentWord);
+ nextLine();
+ }
+ currentWord.positions[currentWord.index++] = currentWord.width + kerning;
+ currentWord.chars.push(char);
+ currentWord.width += nextCharWidth;
+ }
+ previousChar = char;
+ }
+ nextLine();
+ if (style.align === "center") {
+ alignCenter(layoutData);
+ } else if (style.align === "right") {
+ alignRight(layoutData);
+ } else if (style.align === "justify") {
+ alignJustify(layoutData);
+ }
+ return layoutData;
+ }
+ function alignCenter(measurementData) {
+ for (let i = 0; i < measurementData.lines.length; i++) {
+ const line = measurementData.lines[i];
+ const offset = measurementData.width / 2 - line.width / 2;
+ for (let j = 0; j < line.charPositions.length; j++) {
+ line.charPositions[j] += offset;
+ }
+ }
+ }
+ function alignRight(measurementData) {
+ for (let i = 0; i < measurementData.lines.length; i++) {
+ const line = measurementData.lines[i];
+ const offset = measurementData.width - line.width;
+ for (let j = 0; j < line.charPositions.length; j++) {
+ line.charPositions[j] += offset;
+ }
+ }
+ }
+ function alignJustify(measurementData) {
+ const width = measurementData.width;
+ for (let i = 0; i < measurementData.lines.length; i++) {
+ const line = measurementData.lines[i];
+ let indy = 0;
+ let spaceIndex = line.spacesIndex[indy++];
+ let offset = 0;
+ const totalSpaces = line.spacesIndex.length;
+ const newSpaceWidth = (width - line.width) / totalSpaces;
+ const spaceWidth = newSpaceWidth;
+ for (let j = 0; j < line.charPositions.length; j++) {
+ if (j === spaceIndex) {
+ spaceIndex = line.spacesIndex[indy++];
+ offset += spaceWidth;
+ }
+ line.charPositions[j] += offset;
+ }
+ }
+ }
+
+ "use strict";
+ function resolveCharacters(chars) {
+ if (chars === "") {
+ return [];
+ }
+ if (typeof chars === "string") {
+ chars = [chars];
+ }
+ const result = [];
+ for (let i = 0, j = chars.length; i < j; i++) {
+ const item = chars[i];
+ if (Array.isArray(item)) {
+ if (item.length !== 2) {
+ throw new Error(`[BitmapFont]: Invalid character range length, expecting 2 got ${item.length}.`);
+ }
+ if (item[0].length === 0 || item[1].length === 0) {
+ throw new Error("[BitmapFont]: Invalid character delimiter.");
+ }
+ const startCode = item[0].charCodeAt(0);
+ const endCode = item[1].charCodeAt(0);
+ if (endCode < startCode) {
+ throw new Error("[BitmapFont]: Invalid character range.");
+ }
+ for (let i2 = startCode, j2 = endCode; i2 <= j2; i2++) {
+ result.push(String.fromCharCode(i2));
+ }
+ } else {
+ result.push(...Array.from(item));
+ }
+ }
+ if (result.length === 0) {
+ throw new Error("[BitmapFont]: Empty set when resolving characters.");
+ }
+ return result;
+ }
+
+ "use strict";
+ var __defProp$Q = Object.defineProperty;
+ var __getOwnPropSymbols$R = Object.getOwnPropertySymbols;
+ var __hasOwnProp$R = Object.prototype.hasOwnProperty;
+ var __propIsEnum$R = Object.prototype.propertyIsEnumerable;
+ var __defNormalProp$Q = (obj, key, value) => key in obj ? __defProp$Q(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
+ var __spreadValues$Q = (a, b) => {
+ for (var prop in b || (b = {}))
+ if (__hasOwnProp$R.call(b, prop))
+ __defNormalProp$Q(a, prop, b[prop]);
+ if (__getOwnPropSymbols$R)
+ for (var prop of __getOwnPropSymbols$R(b)) {
+ if (__propIsEnum$R.call(b, prop))
+ __defNormalProp$Q(a, prop, b[prop]);
+ }
+ return a;
+ };
+ let fontCount = 0;
+ class BitmapFontManagerClass {
+ constructor() {
+ /**
+ * This character set includes all the letters in the alphabet (both lower- and upper- case).
+ * @type {string[][]}
+ * @example
+ * BitmapFont.from('ExampleFont', style, { chars: BitmapFont.ALPHA })
+ */
+ this.ALPHA = [["a", "z"], ["A", "Z"], " "];
+ /**
+ * This character set includes all decimal digits (from 0 to 9).
+ * @type {string[][]}
+ * @example
+ * BitmapFont.from('ExampleFont', style, { chars: BitmapFont.NUMERIC })
+ */
+ this.NUMERIC = [["0", "9"]];
+ /**
+ * This character set is the union of `BitmapFont.ALPHA` and `BitmapFont.NUMERIC`.
+ * @type {string[][]}
+ */
+ this.ALPHANUMERIC = [["a", "z"], ["A", "Z"], ["0", "9"], " "];
+ /**
+ * This character set consists of all the ASCII table.
+ * @type {string[][]}
+ * @see http://www.asciitable.com/
+ */
+ this.ASCII = [[" ", "~"]];
+ /** Default options for installing a new BitmapFont. */
+ this.defaultOptions = {
+ chars: this.ALPHANUMERIC,
+ resolution: 1,
+ padding: 4,
+ skipKerning: false,
+ textureStyle: null
+ };
+ /** Cache for measured text layouts to avoid recalculating them multiple times. */
+ this.measureCache = lru(1e3);
+ }
+ /**
+ * Get a font for the specified text and style.
+ * @param text - The text to get the font for
+ * @param style - The style to use
+ */
+ getFont(text, style) {
+ var _a;
+ let fontFamilyKey = `${style.fontFamily}-bitmap`;
+ let overrideFill = true;
+ if (style._fill.fill && !style._stroke) {
+ fontFamilyKey += style._fill.fill.styleKey;
+ overrideFill = false;
+ } else if (style._stroke || style.dropShadow) {
+ fontFamilyKey = `${style.styleKey}-bitmap`;
+ overrideFill = false;
+ }
+ if (!Cache.has(fontFamilyKey)) {
+ const styleCopy = Object.create(style);
+ styleCopy.lineHeight = 0;
+ const fnt = new DynamicBitmapFont(__spreadValues$Q({
+ style: styleCopy,
+ overrideFill,
+ overrideSize: true
+ }, this.defaultOptions));
+ fontCount++;
+ if (fontCount > 50) {
+ warn("BitmapText", `You have dynamically created ${fontCount} bitmap fonts, this can be inefficient. Try pre installing your font styles using \`BitmapFont.install({name:"style1", style})\``);
+ }
+ fnt.once("destroy", () => {
+ fontCount--;
+ Cache.remove(fontFamilyKey);
+ });
+ Cache.set(
+ fontFamilyKey,
+ fnt
+ );
+ }
+ const dynamicFont = Cache.get(fontFamilyKey);
+ (_a = dynamicFont.ensureCharacters) == null ? void 0 : _a.call(dynamicFont, text);
+ return dynamicFont;
+ }
+ /**
+ * Get the layout of a text for the specified style.
+ * @param text - The text to get the layout for
+ * @param style - The style to use
+ * @param trimEnd - Whether to ignore whitespaces at the end of each line
+ */
+ getLayout(text, style, trimEnd = true) {
+ const bitmapFont = this.getFont(text, style);
+ const id = `${text}-${style.styleKey}-${trimEnd}`;
+ if (this.measureCache.has(id)) {
+ return this.measureCache.get(id);
+ }
+ const segments = CanvasTextMetrics.graphemeSegmenter(text);
+ const layoutData = getBitmapTextLayout(segments, style, bitmapFont, trimEnd);
+ this.measureCache.set(id, layoutData);
+ return layoutData;
+ }
+ /**
+ * Measure the text using the specified style.
+ * @param text - The text to measure
+ * @param style - The style to use
+ * @param trimEnd - Whether to ignore whitespaces at the end of each line
+ */
+ measureText(text, style, trimEnd = true) {
+ return this.getLayout(text, style, trimEnd);
+ }
+ // eslint-disable-next-line max-len
+ install(...args) {
+ var _a, _b, _c, _d, _e;
+ let options = args[0];
+ if (typeof options === "string") {
+ options = {
+ name: options,
+ style: args[1],
+ chars: (_a = args[2]) == null ? void 0 : _a.chars,
+ resolution: (_b = args[2]) == null ? void 0 : _b.resolution,
+ padding: (_c = args[2]) == null ? void 0 : _c.padding,
+ skipKerning: (_d = args[2]) == null ? void 0 : _d.skipKerning
+ };
+ deprecation(v8_0_0, "BitmapFontManager.install(name, style, options) is deprecated, use BitmapFontManager.install({name, style, ...options})");
+ }
+ const name = options == null ? void 0 : options.name;
+ if (!name) {
+ throw new Error("[BitmapFontManager] Property `name` is required.");
+ }
+ options = __spreadValues$Q(__spreadValues$Q({}, this.defaultOptions), options);
+ const textStyle = options.style;
+ const style = textStyle instanceof TextStyle ? textStyle : new TextStyle(textStyle);
+ const overrideFill = (_e = options.dynamicFill) != null ? _e : this._canUseTintForStyle(style);
+ const font = new DynamicBitmapFont({
+ style,
+ overrideFill,
+ skipKerning: options.skipKerning,
+ padding: options.padding,
+ resolution: options.resolution,
+ overrideSize: false,
+ textureStyle: options.textureStyle
+ });
+ const flatChars = resolveCharacters(options.chars);
+ font.ensureCharacters(flatChars.join(""));
+ Cache.set(`${name}-bitmap`, font);
+ font.once("destroy", () => Cache.remove(`${name}-bitmap`));
+ return font;
+ }
+ /**
+ * Uninstalls a bitmap font from the cache.
+ * @param {string} name - The name of the bitmap font to uninstall.
+ */
+ uninstall(name) {
+ const cacheKey = `${name}-bitmap`;
+ const font = Cache.get(cacheKey);
+ if (font) {
+ font.destroy();
+ }
+ }
+ /**
+ * Determines if a style can use tinting instead of baking colors into the bitmap.
+ * Tinting is more efficient as it allows reusing the same bitmap with different colors.
+ * @param style - The text style to evaluate
+ * @returns true if the style can use tinting, false if colors must be baked in
+ * @private
+ */
+ _canUseTintForStyle(style) {
+ return !style._stroke && (!style.dropShadow || style.dropShadow.color === 0) && !style._fill.fill && style._fill.color === 16777215;
+ }
+ }
+ const BitmapFontManager = new BitmapFontManagerClass();
+
+ "use strict";
+ class BitmapTextGraphics extends Graphics {
+ destroy() {
+ if (this.context.customShader) {
+ this.context.customShader.destroy();
+ }
+ super.destroy();
+ }
+ }
+ class BitmapTextPipe {
+ constructor(renderer) {
+ this._renderer = renderer;
+ }
+ validateRenderable(bitmapText) {
+ const graphicsRenderable = this._getGpuBitmapText(bitmapText);
+ return this._renderer.renderPipes.graphics.validateRenderable(graphicsRenderable);
+ }
+ addRenderable(bitmapText, instructionSet) {
+ const graphicsRenderable = this._getGpuBitmapText(bitmapText);
+ syncWithProxy(bitmapText, graphicsRenderable);
+ if (bitmapText._didTextUpdate) {
+ bitmapText._didTextUpdate = false;
+ this._updateContext(bitmapText, graphicsRenderable);
+ }
+ this._renderer.renderPipes.graphics.addRenderable(graphicsRenderable, instructionSet);
+ if (graphicsRenderable.context.customShader) {
+ this._updateDistanceField(bitmapText);
+ }
+ }
+ updateRenderable(bitmapText) {
+ const graphicsRenderable = this._getGpuBitmapText(bitmapText);
+ syncWithProxy(bitmapText, graphicsRenderable);
+ this._renderer.renderPipes.graphics.updateRenderable(graphicsRenderable);
+ if (graphicsRenderable.context.customShader) {
+ this._updateDistanceField(bitmapText);
+ }
+ }
+ _updateContext(bitmapText, proxyGraphics) {
+ const { context } = proxyGraphics;
+ const bitmapFont = BitmapFontManager.getFont(bitmapText.text, bitmapText._style);
+ context.clear();
+ if (bitmapFont.distanceField.type !== "none") {
+ if (!context.customShader) {
+ context.customShader = new SdfShader(this._renderer.limits.maxBatchableTextures);
+ }
+ }
+ const chars = CanvasTextMetrics.graphemeSegmenter(bitmapText.text);
+ const style = bitmapText._style;
+ let currentY = bitmapFont.baseLineOffset;
+ const bitmapTextLayout = getBitmapTextLayout(chars, style, bitmapFont, true);
+ const padding = style.padding;
+ const scale = bitmapTextLayout.scale;
+ let tx = bitmapTextLayout.width;
+ let ty = bitmapTextLayout.height + bitmapTextLayout.offsetY;
+ if (style._stroke) {
+ tx += style._stroke.width / scale;
+ ty += style._stroke.width / scale;
+ }
+ context.translate(-bitmapText._anchor._x * tx - padding, -bitmapText._anchor._y * ty - padding).scale(scale, scale);
+ const tint = bitmapFont.applyFillAsTint ? style._fill.color : 16777215;
+ let fontSize = bitmapFont.fontMetrics.fontSize;
+ let lineHeight = bitmapFont.lineHeight;
+ if (style.lineHeight) {
+ fontSize = style.fontSize / scale;
+ lineHeight = style.lineHeight / scale;
+ }
+ let linePositionYShift = (lineHeight - fontSize) / 2;
+ if (linePositionYShift - bitmapFont.baseLineOffset < 0) {
+ linePositionYShift = 0;
+ }
+ for (let i = 0; i < bitmapTextLayout.lines.length; i++) {
+ const line = bitmapTextLayout.lines[i];
+ for (let j = 0; j < line.charPositions.length; j++) {
+ const char = line.chars[j];
+ const charData = bitmapFont.chars[char];
+ if (charData == null ? void 0 : charData.texture) {
+ const texture = charData.texture;
+ context.texture(
+ texture,
+ tint ? tint : "black",
+ Math.round(line.charPositions[j] + charData.xOffset),
+ Math.round(currentY + charData.yOffset + linePositionYShift),
+ texture.orig.width,
+ texture.orig.height
+ );
+ }
+ }
+ currentY += lineHeight;
+ }
+ }
+ _getGpuBitmapText(bitmapText) {
+ return bitmapText._gpuData[this._renderer.uid] || this.initGpuText(bitmapText);
+ }
+ initGpuText(bitmapText) {
+ const proxyRenderable = new BitmapTextGraphics();
+ bitmapText._gpuData[this._renderer.uid] = proxyRenderable;
+ this._updateContext(bitmapText, proxyRenderable);
+ return proxyRenderable;
+ }
+ _updateDistanceField(bitmapText) {
+ const context = this._getGpuBitmapText(bitmapText).context;
+ const fontFamily = bitmapText._style.fontFamily;
+ const dynamicFont = Cache.get(`${fontFamily}-bitmap`);
+ const { a, b, c, d } = bitmapText.groupTransform;
+ const dx = Math.sqrt(a * a + b * b);
+ const dy = Math.sqrt(c * c + d * d);
+ const worldScale = (Math.abs(dx) + Math.abs(dy)) / 2;
+ const fontScale = dynamicFont.baseRenderedFontSize / bitmapText._style.fontSize;
+ const distance = worldScale * dynamicFont.distanceField.range * (1 / fontScale);
+ context.customShader.resources.localUniforms.uniforms.uDistance = distance;
+ }
+ destroy() {
+ this._renderer = null;
+ }
+ }
+ /** @ignore */
+ BitmapTextPipe.extension = {
+ type: [
+ ExtensionType.WebGLPipes,
+ ExtensionType.WebGPUPipes,
+ ExtensionType.CanvasPipes
+ ],
+ name: "bitmapText"
+ };
+ function syncWithProxy(container, proxy) {
+ proxy.groupTransform = container.groupTransform;
+ proxy.groupColorAlpha = container.groupColorAlpha;
+ proxy.groupColor = container.groupColor;
+ proxy.groupBlendMode = container.groupBlendMode;
+ proxy.globalDisplayStatus = container.globalDisplayStatus;
+ proxy.groupTransform = container.groupTransform;
+ proxy.localDisplayStatus = container.localDisplayStatus;
+ proxy.groupAlpha = container.groupAlpha;
+ proxy._roundPixels = container._roundPixels;
+ }
+
+ "use strict";
+ extensions.add(BitmapTextPipe);
+
+ "use strict";
+ class BatchableHTMLText extends BatchableSprite {
+ /**
+ * Creates an instance of BatchableHTMLText.
+ * @param renderer - The renderer instance to be used.
+ */
+ constructor(renderer) {
+ super();
+ this.generatingTexture = false;
+ this.currentKey = "--";
+ this._renderer = renderer;
+ renderer.runners.resolutionChange.add(this);
+ }
+ /** Handles resolution changes for the HTML text. If the text has auto resolution enabled, it triggers a view update. */
+ resolutionChange() {
+ const text = this.renderable;
+ if (text._autoResolution) {
+ text.onViewUpdate();
+ }
+ }
+ /** Destroys the BatchableHTMLText instance. Returns the texture promise to the renderer and cleans up references. */
+ destroy() {
+ const { htmlText } = this._renderer;
+ htmlText.getReferenceCount(this.currentKey) === null ? htmlText.returnTexturePromise(this.texturePromise) : htmlText.decreaseReferenceCount(this.currentKey);
+ this._renderer.runners.resolutionChange.remove(this);
+ this.texturePromise = null;
+ this._renderer = null;
+ }
+ }
+
+ "use strict";
+ class HTMLTextPipe {
+ constructor(renderer) {
+ this._renderer = renderer;
+ }
+ validateRenderable(htmlText) {
+ const gpuText = this._getGpuText(htmlText);
+ const newKey = htmlText.styleKey;
+ if (gpuText.currentKey !== newKey) {
+ return true;
+ }
+ return false;
+ }
+ addRenderable(htmlText, instructionSet) {
+ const batchableHTMLText = this._getGpuText(htmlText);
+ if (htmlText._didTextUpdate) {
+ const resolution = htmlText._autoResolution ? this._renderer.resolution : htmlText.resolution;
+ if (batchableHTMLText.currentKey !== htmlText.styleKey || htmlText.resolution !== resolution) {
+ this._updateGpuText(htmlText).catch((e) => {
+ console.error(e);
+ });
+ }
+ htmlText._didTextUpdate = false;
+ updateTextBounds(batchableHTMLText, htmlText);
+ }
+ this._renderer.renderPipes.batch.addToBatch(batchableHTMLText, instructionSet);
+ }
+ updateRenderable(htmlText) {
+ const batchableHTMLText = this._getGpuText(htmlText);
+ batchableHTMLText._batcher.updateElement(batchableHTMLText);
+ }
+ async _updateGpuText(htmlText) {
+ htmlText._didTextUpdate = false;
+ const batchableHTMLText = this._getGpuText(htmlText);
+ if (batchableHTMLText.generatingTexture)
+ return;
+ const oldTexturePromise = batchableHTMLText.texturePromise;
+ batchableHTMLText.texturePromise = null;
+ batchableHTMLText.generatingTexture = true;
+ htmlText._resolution = htmlText._autoResolution ? this._renderer.resolution : htmlText.resolution;
+ let texturePromise = this._renderer.htmlText.getTexturePromise(htmlText);
+ if (oldTexturePromise) {
+ texturePromise = texturePromise.finally(() => {
+ this._renderer.htmlText.decreaseReferenceCount(batchableHTMLText.currentKey);
+ this._renderer.htmlText.returnTexturePromise(oldTexturePromise);
+ });
+ }
+ batchableHTMLText.texturePromise = texturePromise;
+ batchableHTMLText.currentKey = htmlText.styleKey;
+ batchableHTMLText.texture = await texturePromise;
+ const renderGroup = htmlText.renderGroup || htmlText.parentRenderGroup;
+ if (renderGroup) {
+ renderGroup.structureDidChange = true;
+ }
+ batchableHTMLText.generatingTexture = false;
+ updateTextBounds(batchableHTMLText, htmlText);
+ }
+ _getGpuText(htmlText) {
+ return htmlText._gpuData[this._renderer.uid] || this.initGpuText(htmlText);
+ }
+ initGpuText(htmlText) {
+ const batchableHTMLText = new BatchableHTMLText(this._renderer);
+ batchableHTMLText.renderable = htmlText;
+ batchableHTMLText.transform = htmlText.groupTransform;
+ batchableHTMLText.texture = Texture.EMPTY;
+ batchableHTMLText.bounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 };
+ batchableHTMLText.roundPixels = this._renderer._roundPixels | htmlText._roundPixels;
+ htmlText._resolution = htmlText._autoResolution ? this._renderer.resolution : htmlText.resolution;
+ htmlText._gpuData[this._renderer.uid] = batchableHTMLText;
+ return batchableHTMLText;
+ }
+ destroy() {
+ this._renderer = null;
+ }
+ }
+ /** @ignore */
+ HTMLTextPipe.extension = {
+ type: [
+ ExtensionType.WebGLPipes,
+ ExtensionType.WebGPUPipes,
+ ExtensionType.CanvasPipes
+ ],
+ name: "htmlText"
+ };
+
+ "use strict";
+ function isSafari() {
+ const { userAgent } = DOMAdapter.get().getNavigator();
+ return /^((?!chrome|android).)*safari/i.test(userAgent);
+ }
+
+ "use strict";
+ const nssvg = "http://www.w3.org/2000/svg";
+ const nsxhtml = "http://www.w3.org/1999/xhtml";
+ class HTMLTextRenderData {
+ constructor() {
+ this.svgRoot = document.createElementNS(nssvg, "svg");
+ this.foreignObject = document.createElementNS(nssvg, "foreignObject");
+ this.domElement = document.createElementNS(nsxhtml, "div");
+ this.styleElement = document.createElementNS(nsxhtml, "style");
+ const { foreignObject, svgRoot, styleElement, domElement } = this;
+ foreignObject.setAttribute("width", "10000");
+ foreignObject.setAttribute("height", "10000");
+ foreignObject.style.overflow = "hidden";
+ svgRoot.appendChild(foreignObject);
+ foreignObject.appendChild(styleElement);
+ foreignObject.appendChild(domElement);
+ this.image = DOMAdapter.get().createImage();
+ }
+ destroy() {
+ this.svgRoot.remove();
+ this.foreignObject.remove();
+ this.styleElement.remove();
+ this.domElement.remove();
+ this.image.src = "";
+ this.image.remove();
+ this.svgRoot = null;
+ this.foreignObject = null;
+ this.styleElement = null;
+ this.domElement = null;
+ this.image = null;
+ this.canvasAndContext = null;
+ }
+ }
+
+ "use strict";
+ function extractFontFamilies(text, style) {
+ const fontFamily = style.fontFamily;
+ const fontFamilies = [];
+ const dedupe = {};
+ const regex = /font-family:([^;"\s]+)/g;
+ const matches = text.match(regex);
+ function addFontFamily(fontFamily2) {
+ if (!dedupe[fontFamily2]) {
+ fontFamilies.push(fontFamily2);
+ dedupe[fontFamily2] = true;
+ }
+ }
+ if (Array.isArray(fontFamily)) {
+ for (let i = 0; i < fontFamily.length; i++) {
+ addFontFamily(fontFamily[i]);
+ }
+ } else {
+ addFontFamily(fontFamily);
+ }
+ if (matches) {
+ matches.forEach((match) => {
+ const fontFamily2 = match.split(":")[1].trim();
+ addFontFamily(fontFamily2);
+ });
+ }
+ for (const i in style.tagStyles) {
+ const fontFamily2 = style.tagStyles[i].fontFamily;
+ addFontFamily(fontFamily2);
+ }
+ return fontFamilies;
+ }
+
+ "use strict";
+ async function loadFontAsBase64(url) {
+ const response = await DOMAdapter.get().fetch(url);
+ const blob = await response.blob();
+ const reader = new FileReader();
+ const dataSrc = await new Promise((resolve, reject) => {
+ reader.onloadend = () => resolve(reader.result);
+ reader.onerror = reject;
+ reader.readAsDataURL(blob);
+ });
+ return dataSrc;
+ }
+
+ "use strict";
+ async function loadFontCSS(style, url) {
+ const dataSrc = await loadFontAsBase64(url);
+ return `@font-face {
+ font-family: "${style.fontFamily}";
+ font-weight: ${style.fontWeight};
+ font-style: ${style.fontStyle};
+ src: url('${dataSrc}');
+ }`;
+ }
+
+ "use strict";
+ const FontStylePromiseCache = /* @__PURE__ */ new Map();
+ async function getFontCss(fontFamilies) {
+ const fontPromises = fontFamilies.filter((fontFamily) => Cache.has(`${fontFamily}-and-url`)).map((fontFamily) => {
+ if (!FontStylePromiseCache.has(fontFamily)) {
+ const { entries } = Cache.get(`${fontFamily}-and-url`);
+ const promises = [];
+ entries.forEach((entry) => {
+ const url = entry.url;
+ const faces = entry.faces;
+ const out = faces.map((face) => ({ weight: face.weight, style: face.style }));
+ promises.push(
+ ...out.map(
+ (style) => loadFontCSS(
+ {
+ fontWeight: style.weight,
+ fontStyle: style.style,
+ fontFamily
+ },
+ url
+ )
+ )
+ );
+ });
+ FontStylePromiseCache.set(
+ fontFamily,
+ Promise.all(promises).then((css) => css.join("\n"))
+ );
+ }
+ return FontStylePromiseCache.get(fontFamily);
+ });
+ return (await Promise.all(fontPromises)).join("\n");
+ }
+
+ "use strict";
+ function getSVGUrl(text, style, resolution, fontCSS, htmlTextData) {
+ const { domElement, styleElement, svgRoot } = htmlTextData;
+ domElement.innerHTML = `