const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./browserAll-CYHduCqD.js","./webworkerAll-BWt5v-sL.js","./colorToUniform-BD1KgYAg.js","./i18n-CGlVUOvE.js","./index-Bq2njFOY.js","./index-JtiO_h80.css","./i18n-DmEW90TJ.css","./misc-D8a4ZbMA.js","./utils.svelte-Bi9ASwv9.js","./ImagePreview-XayhIqNU.js","./ScrollFade.svelte_svelte_type_style_lang-D0JPkGar.js","./snippet-BG7qkY_1.js","./ScrollFade-DXC81vHi.css","./MarkdownCode.svelte_svelte_type_style_lang-B29zDiob.js","./prism-python-NrKIQnfs.js","./MarkdownCode-hl0Esrgh.css","./BlockLabel-VpI11pHf.js","./DownloadLink-D6EYxQxh.js","./Empty-BrRNpJ3n.js","./ShareButton-CORLYhd9.js","./Download-D5ZNT7uE.js","./Image-DfZjI5oa.js","./IconButtonWrapper-DAsvkfJg.js","./FullscreenButton-C6RgeACK.js","./Maximize-2N5airbC.js","./Image-XuMqTTwB.js","./Image-DQfoGqtb.css","./ImagePreview-CAbFrrhc.css","./Block-DNK-JdLJ.js","./index-1GCmrSlD.js","./StreamingBar.svelte_svelte_type_style_lang-DWGn5tT_.js","./html-Bwif0JPw.js","./StreamingBar-M0OJiclA.css","./Clear-C8Tfa7QP.js","./Upload-xR3P-2U5.js","./actions-CgRQ2lHA.js","./Upload-B9AbOy1B.css","./props-o1aFOJaB.js","./Webcam-CIJ5MvIX.js","./DropdownArrow-3lFHYtTD.js","./Square-kMgNk9Wy.js","./Spinner-CGjvf4Sm.js","./StreamingBar-YIqBWk77.js","./ImageUploader-79Z_jqCu.css","./window-BVsmErEt.js","./tinycolor-fdIevY-E.js","./Plus-CjgGs6LE.js","./input-CXpCw23l.js","./Check-BKMHx_DF.js","./event-modifiers-DanhKw3_.js","./ArrowUp-BfdIRWRH.js","./Trash-DePlRUVn.js","./Undo-4MCSCpCy.js","./Example-BDoLBXEz.css","./WebGPURenderer-CFeMiLws.js","./SharedSystems-BVuOydgJ.js","./WebGLRenderer-C-oCJCbi.js"])))=>i.map(i=>d[i]);
import { w as writable, p as prop, e as init$1, i as if_block, a as set_attribute, b as set_class, f as set_style, G as getDefaultExportFromCjs, d as bind_this, k as each, u as index, t as remove_input_defaults, F as set_value, j as get$1, m as setup_stores, q as store_get, s as slot, r as rest_props, g as spread_props } from './i18n-CGlVUOvE.js';
import { a as append, f as from_svg, ai as delegate, M as push, Y as legacy_pre_effect, $ as set, y as deep_read_state, _ as legacy_pre_effect_reset, Q as child, S as sibling, T as reset, t as template_effect, Z as get, O as pop, a0 as mutable_source, P as flushSync, X as set_text, R as from_html, ag as __vitePreload, a8 as onMount, U as event, av as $window, N as first_child, w as untrack, I as tick, a1 as createEventDispatcher, ae as derived_safe_equal, a2 as user_derived, as as to_array, a3 as comment, at as prepare_files, ab as state, au as FileData } from './index-Bq2njFOY.js';
import { b as bubble_event } from './misc-D8a4ZbMA.js';
import { G as Gradio } from './utils.svelte-Bi9ASwv9.js';
import { I as ImagePreview } from './ImagePreview-XayhIqNU.js';
import { B as Block } from './Block-DNK-JdLJ.js';
import './MarkdownCode.svelte_svelte_type_style_lang-B29zDiob.js';
import { U as Upload, a as ImagePaste, W as Webcam, I as IconButton$1 } from './ScrollFade.svelte_svelte_type_style_lang-D0JPkGar.js';
import { s as spring, S as Static } from './index-1GCmrSlD.js';
import './StreamingBar.svelte_svelte_type_style_lang-DWGn5tT_.js';
/* empty css                                                     */
import { c as create_drag } from './Upload-xR3P-2U5.js';
/* empty css                                             */
/* empty css                                               */
import { b as bind_prop } from './props-o1aFOJaB.js';
import { a as action } from './actions-CgRQ2lHA.js';
import { C as Circle$1, W as Webcam$1 } from './Webcam-CIJ5MvIX.js';
import { I as Image$1 } from './Image-DfZjI5oa.js';
import { b as bind_window_size } from './window-BVsmErEt.js';
import { t as tinycolor, E as Eyedropper } from './tinycolor-fdIevY-E.js';
import { P as Plus } from './Plus-CjgGs6LE.js';
import { b as bind_value } from './input-CXpCw23l.js';
import { C as Check } from './Check-BKMHx_DF.js';
/* empty css                                                    */
import { s as stopPropagation } from './event-modifiers-DanhKw3_.js';
import { A as ArrowUp } from './ArrowUp-BfdIRWRH.js';
import { C as Clear } from './Clear-C8Tfa7QP.js';
import { D as Download } from './Download-D5ZNT7uE.js';
import { T as Trash } from './Trash-DePlRUVn.js';
import { U as Undo } from './Undo-4MCSCpCy.js';
import { I as IconButtonWrapper } from './IconButtonWrapper-DAsvkfJg.js';
import { B as BlockLabel } from './BlockLabel-VpI11pHf.js';

var root$l = from_svg(`<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline></svg>`);

function ArrowDown($$anchor) {
	var svg = root$l();

	append($$anchor, svg);
}

var root$k = from_svg(`<svg width="100%" height="100%" viewBox="0 0 32 32"><path d="M28.828 3.172a4.094 4.094 0 0 0-5.656 0L4.05 22.292A6.954 6.954 0 0 0 2 27.242V30h2.756a6.952 6.952 0 0 0 4.95-2.05L28.828 8.829a3.999 3.999 0 0 0 0-5.657zM10.91 18.26l2.829 2.829l-2.122 2.121l-2.828-2.828zm-2.619 8.276A4.966 4.966 0 0 1 4.756 28H4v-.759a4.967 4.967 0 0 1 1.464-3.535l1.91-1.91l2.829 2.828zM27.415 7.414l-12.261 12.26l-2.829-2.828l12.262-12.26a2.047 2.047 0 0 1 2.828 0a2 2 0 0 1 0 2.828z" fill="currentColor"></path><path d="M6.5 15a3.5 3.5 0 0 1-2.475-5.974l3.5-3.5a1.502 1.502 0 0 0 0-2.121a1.537 1.537 0 0 0-2.121 0L3.415 5.394L2 3.98l1.99-1.988a3.585 3.585 0 0 1 4.95 0a3.504 3.504 0 0 1 0 4.949L5.439 10.44a1.502 1.502 0 0 0 0 2.121a1.537 1.537 0 0 0 2.122 0l4.024-4.024L13 9.95l-4.025 4.024A3.475 3.475 0 0 1 6.5 15z" fill="currentColor"></path></svg>`);

function Brush($$anchor) {
	var svg = root$k();

	append($$anchor, svg);
}

var root$j = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24"><path fill="currentColor" d="M2.753 2.933a.75.75 0 0 1 .814-.68l3.043.272c2.157.205 4.224.452 5.922.732c1.66.273 3.073.594 3.844.983c.197.1.412.233.578.415c.176.192.352.506.28.9c-.067.356-.304.59-.487.729a3.001 3.001 0 0 1-.695.369c-1.02.404-2.952.79-5.984 1.169c-1.442.18-2.489.357-3.214.522c.205.045.43.089.674.132c.992.174 2.241.323 3.568.437a31.21 31.21 0 0 1 3.016.398c.46.087.893.186 1.261.296c.352.105.707.236.971.412c.13.086.304.225.42.437a.988.988 0 0 1 .063.141A1.75 1.75 0 0 0 14.5 12.25v.158c-.758.154-1.743.302-2.986.444c-2.124.243-3.409.55-4.117.859c-.296.128-.442.236-.508.3c.026.037.073.094.156.17c.15.138.369.29.65.45c.56.316 1.282.61 1.979.838l2.637.814a.75.75 0 1 1-.443 1.433l-2.655-.819c-.754-.247-1.58-.578-2.257-.96a5.082 5.082 0 0 1-.924-.65c-.255-.233-.513-.544-.62-.935c-.12-.441-.016-.88.274-1.244c.261-.328.656-.574 1.113-.773c.92-.4 2.387-.727 4.545-.974c1.366-.156 2.354-.313 3.041-.462a16.007 16.007 0 0 0-.552-.114a29.716 29.716 0 0 0-2.865-.378c-1.352-.116-2.649-.27-3.7-.454c-.524-.092-1-.194-1.395-.307c-.376-.106-.75-.241-1.021-.426a1.186 1.186 0 0 1-.43-.49a.934.934 0 0 1 .059-.873c.13-.213.32-.352.472-.442a3.23 3.23 0 0 1 .559-.251c.807-.287 2.222-.562 4.37-.83c2.695-.338 4.377-.666 5.295-.962c-.638-.21-1.623-.427-2.89-.635c-1.65-.273-3.679-.515-5.816-.718l-3.038-.272a.75.75 0 0 1-.68-.814M17 12.25a.75.75 0 0 0-1.5 0v4.19l-.72-.72a.75.75 0 1 0-1.06 1.06l2 2a.75.75 0 0 0 1.06 0l2-2a.75.75 0 1 0-1.06-1.06l-.72.72z"></path></svg>`);

function BrushSize($$anchor) {
	var svg = root$j();

	append($$anchor, svg);
}

var root$i = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24"><path fill="currentColor" d="M12 22q-2.05 0-3.875-.788t-3.187-2.15t-2.15-3.187T2 12q0-2.075.813-3.9t2.2-3.175T8.25 2.788T12.2 2q2 0 3.775.688t3.113 1.9t2.125 2.875T22 11.05q0 2.875-1.75 4.413T16 17h-1.85q-.225 0-.312.125t-.088.275q0 .3.375.863t.375 1.287q0 1.25-.687 1.85T12 22m-5.5-9q.65 0 1.075-.425T8 11.5t-.425-1.075T6.5 10t-1.075.425T5 11.5t.425 1.075T6.5 13m3-4q.65 0 1.075-.425T11 7.5t-.425-1.075T9.5 6t-1.075.425T8 7.5t.425 1.075T9.5 9m5 0q.65 0 1.075-.425T16 7.5t-.425-1.075T14.5 6t-1.075.425T13 7.5t.425 1.075T14.5 9m3 4q.65 0 1.075-.425T19 11.5t-.425-1.075T17.5 10t-1.075.425T16 11.5t.425 1.075T17.5 13"></path></svg>`);

function ColorPickerSolid($$anchor) {
	var svg = root$i();

	append($$anchor, svg);
}

var root$h = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256"><path fill="currentColor" d="M240 192a8 8 0 0 1-8 8h-32v32a8 8 0 0 1-16 0v-32H64a8 8 0 0 1-8-8V72H24a8 8 0 0 1 0-16h32V24a8 8 0 0 1 16 0v160h160a8 8 0 0 1 8 8M96 72h88v88a8 8 0 0 0 16 0V64a8 8 0 0 0-8-8H96a8 8 0 0 0 0 16"></path></svg>`);

function Crop($$anchor) {
	var svg = root$h();

	append($$anchor, svg);
}

var root$g = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24"><g fill="none"><path fill="currentColor" d="m5.505 11.41l.53.53l-.53-.53ZM3 14.952h-.75H3ZM9.048 21v.75V21ZM11.41 5.505l-.53-.53l.53.53Zm1.831 12.34a.75.75 0 0 0 1.06-1.061l-1.06 1.06ZM7.216 9.697a.75.75 0 1 0-1.06 1.061l1.06-1.06Zm10.749 2.362l-5.905 5.905l1.06 1.06l5.905-5.904l-1.06-1.06Zm-11.93-.12l5.905-5.905l-1.06-1.06l-5.905 5.904l1.06 1.06Zm0 6.025c-.85-.85-1.433-1.436-1.812-1.933c-.367-.481-.473-.79-.473-1.08h-1.5c0 .749.312 1.375.78 1.99c.455.596 1.125 1.263 1.945 2.083l1.06-1.06Zm-1.06-7.086c-.82.82-1.49 1.488-1.945 2.084c-.468.614-.78 1.24-.78 1.99h1.5c0-.29.106-.6.473-1.08c.38-.498.962-1.083 1.812-1.933l-1.06-1.06Zm7.085 7.086c-.85.85-1.435 1.433-1.933 1.813c-.48.366-.79.472-1.08.472v1.5c.75 0 1.376-.312 1.99-.78c.596-.455 1.264-1.125 2.084-1.945l-1.06-1.06Zm-7.085 1.06c.82.82 1.487 1.49 2.084 1.945c.614.468 1.24.78 1.989.78v-1.5c-.29 0-.599-.106-1.08-.473c-.497-.38-1.083-.962-1.933-1.812l-1.06 1.06Zm12.99-12.99c.85.85 1.433 1.436 1.813 1.933c.366.481.472.79.472 1.08h1.5c0-.749-.312-1.375-.78-1.99c-.455-.596-1.125-1.263-1.945-2.083l-1.06 1.06Zm1.06 7.086c.82-.82 1.49-1.488 1.945-2.084c.468-.614.78-1.24.78-1.99h-1.5c0 .29-.106.6-.473 1.08c-.38.498-.962 1.083-1.812 1.933l1.06 1.06Zm0-8.146c-.82-.82-1.487-1.49-2.084-1.945c-.614-.468-1.24-.78-1.989-.78v1.5c.29 0 .599.106 1.08.473c.497.38 1.083.962 1.933 1.812l1.06-1.06Zm-7.085 1.06c.85-.85 1.435-1.433 1.933-1.812c.48-.367.79-.473 1.08-.473v-1.5c-.75 0-1.376.312-1.99.78c-.596.455-1.264 1.125-2.084 1.945l1.06 1.06Zm2.362 10.749L7.216 9.698l-1.06 1.061l7.085 7.085l1.06-1.06Z"></path><path stroke="currentColor" stroke-linecap="round" stroke-width="1.5" d="M9 21h12"></path></g></svg>`);

function Erase($$anchor) {
	var svg = root$g();

	append($$anchor, svg);
}

var root$f = from_svg(`<svg width="100%" height="100%" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.35327 10.9495L6.77663 15.158C7.12221 15.4229 7.50051 15.5553 7.91154 15.5553C8.32258 15.5553 8.70126 15.4229 9.0476 15.158L14.471 10.9495" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path><path d="M7.23461 11.4324C7.23406 11.432 7.2335 11.4316 7.23295 11.4312L1.81496 7.2268C1.81471 7.22661 1.81446 7.22641 1.8142 7.22621C1.52269 6.99826 1.39429 6.73321 1.39429 6.37014C1.39429 6.00782 1.52236 5.74301 1.81325 5.51507C1.8136 5.5148 1.81394 5.51453 1.81428 5.51426L7.2331 1.30812C7.45645 1.13785 7.67632 1.06653 7.91159 1.06653C8.14692 1.06653 8.36622 1.13787 8.58861 1.30787C8.58915 1.30828 8.58969 1.30869 8.59023 1.30911L14.0082 5.51462C14.0085 5.51485 14.0088 5.51507 14.0091 5.51529C14.3008 5.74345 14.4289 6.00823 14.4289 6.37014C14.4289 6.73356 14.3006 6.99862 14.01 7.22634C14.0096 7.22662 14.0093 7.22689 14.0089 7.22717L8.59007 11.4322C8.36672 11.6024 8.14686 11.6738 7.91159 11.6738C7.67628 11.6738 7.45699 11.6024 7.23461 11.4324Z" stroke="currentColor" stroke-width="1.5"></path></svg>`);

function Layers($$anchor) {
	var svg = root$f();

	append($$anchor, svg);
}

var root$e = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24"><path fill="currentColor" d="M10.05 23q-.75 0-1.4-.337T7.575 21.7l-5.9-8.65q-.2-.3-.175-.65t.3-.6q.475-.475 1.125-.55t1.175.3L7 13.575V4q0-.425.288-.712T8 3t.713.288T9 4v11.5q0 .6-.537.888t-1.038-.063l-2.125-1.5l3.925 5.725q.125.2.35.325t.475.125H17q.825 0 1.413-.587T19 19V5q0-.425.288-.712T20 4t.713.288T21 5v14q0 1.65-1.175 2.825T17 23zM12 1q.425 0 .713.288T13 2v9q0 .425-.288.713T12 12t-.712-.288T11 11V2q0-.425.288-.712T12 1m4 1q.425 0 .713.288T17 3v8q0 .425-.288.713T16 12t-.712-.288T15 11V3q0-.425.288-.712T16 2m-3.85 14.5"></path></svg>`);

function Pan($$anchor) {
	var svg = root$e();

	append($$anchor, svg);
}

var root$d = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-rotate-ccw" style="transform: rotateY(180deg);"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>`);

function Redo($$anchor) {
	var svg = root$d();

	append($$anchor, svg);
}

var root$c = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24"><path fill="currentColor" d="M12 16q1.875 0 3.188-1.312T16.5 11.5t-1.312-3.187T12 7T8.813 8.313T7.5 11.5t1.313 3.188T12 16m0-1.8q-1.125 0-1.912-.788T9.3 11.5t.788-1.912T12 8.8t1.913.788t.787 1.912t-.787 1.913T12 14.2m0 4.8q-3.65 0-6.65-2.037T1 11.5q1.35-3.425 4.35-5.462T12 4t6.65 2.038T23 11.5q-1.35 3.425-4.35 5.463T12 19"></path></svg>`);

function Visibility($$anchor) {
	var svg = root$c();

	append($$anchor, svg);
}

var root$b = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24"><path fill="currentColor" d="m19.8 22.6l-4.2-4.15q-.875.275-1.762.413T12 19q-3.775 0-6.725-2.087T1 11.5q.525-1.325 1.325-2.463T4.15 7L1.4 4.2l1.4-1.4l18.4 18.4zM12 16q.275 0 .513-.025t.512-.1l-5.4-5.4q-.075.275-.1.513T7.5 11.5q0 1.875 1.313 3.188T12 16m7.3.45l-3.175-3.15q.175-.425.275-.862t.1-.938q0-1.875-1.312-3.187T12 7q-.5 0-.937.1t-.863.3L7.65 4.85q1.025-.425 2.1-.637T12 4q3.775 0 6.725 2.088T23 11.5q-.575 1.475-1.513 2.738T19.3 16.45m-4.625-4.6l-3-3q.7-.125 1.288.113t1.012.687t.613 1.038t.087 1.162"></path></svg>`);

function VisibilityOff($$anchor) {
	var svg = root$b();

	append($$anchor, svg);
}

var root$a = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256"><path fill="currentColor" d="M144 120v88a8 8 0 0 1-8 8H48a8 8 0 0 1-8-8v-88a8 8 0 0 1 8-8h88a8 8 0 0 1 8 8m64 56a8 8 0 0 0-8 8v16h-24a8 8 0 0 0 0 16h24a16 16 0 0 0 16-16v-16a8 8 0 0 0-8-8m0-72a8 8 0 0 0-8 8v32a8 8 0 0 0 16 0v-32a8 8 0 0 0-8-8m-8-64h-16a8 8 0 0 0 0 16h16v16a8 8 0 0 0 16 0V56a16 16 0 0 0-16-16m-56 0h-32a8 8 0 0 0 0 16h32a8 8 0 0 0 0-16M48 88a8 8 0 0 0 8-8V56h16a8 8 0 0 0 0-16H56a16 16 0 0 0-16 16v24a8 8 0 0 0 8 8"></path></svg>`);

function Resize($$anchor) {
	var svg = root$a();

	append($$anchor, svg);
}

var root$9 = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m21 21l-4.343-4.343m0 0A8 8 0 1 0 5.343 5.343a8 8 0 0 0 11.314 11.314M11 8v6m-3-3h6"></path></svg>`);

function ZoomIn($$anchor) {
	var svg = root$9();

	append($$anchor, svg);
}

var root$8 = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m21 21l-4.343-4.343m0 0A8 8 0 1 0 5.343 5.343a8 8 0 0 0 11.314 11.314M8 11h6"></path></svg>`);

function ZoomOut($$anchor) {
	var svg = root$8();

	append($$anchor, svg);
}

class CommandNode {
  command;
  next;
  previous;
  constructor(command) {
    this.command = command || null;
    this.next = null;
    this.previous = null;
  }
  push(command) {
    this.next = null;
    const node = new CommandNode(command);
    node.previous = this;
    this.next = node;
  }
}
class CommandManager {
  history = new CommandNode();
  current_history = writable(this.history);
  undo() {
    if (this.history.previous) {
      this.history.command?.undo();
      this.history = this.history.previous;
      this.current_history.update(() => this.history);
    }
  }
  redo(context) {
    if (this.history.next) {
      this.history = this.history.next;
      this.history.command?.execute(context);
      this.current_history.update(() => this.history);
    }
  }
  async execute(command, context) {
    await command.execute(context);
    this.history.push(command);
    this.history = this.history.next;
    this.current_history.update(() => this.history);
  }
  async wait_for_next_frame() {
    return new Promise((resolve) => {
      requestAnimationFrame(() => {
        resolve();
      });
    });
  }
  async replay(full_history, context) {
    while (full_history.previous) {
      full_history = full_history.previous;
    }
    while (full_history.next) {
      await full_history.next.command.execute(context);
      full_history = full_history.next;
    }
    this.history = full_history;
    this.current_history.update(() => this.history);
  }
  contains(command_name) {
    let current = this.history;
    while (current) {
      if (current.command?.name === command_name) return true;
      current = current.next;
    }
    return false;
  }
  reset() {
    this.history = new CommandNode();
    this.current_history.update(() => this.history);
  }
}

var root_1$5 = from_html(`<span style="margin-left: 4px;" class="svelte-1lrnmgy"> </span>`);
var root_2$6 = from_html(`<span style="margin-right: 4px;" class="svelte-1lrnmgy"> </span>`);
var root$7 = from_html(`<button><!> <div><!></div> <!></button>`);

function IconButton($$anchor, $$props) {
	push($$props, false);

	const _color = mutable_source();
	let Icon = prop($$props, 'Icon', 12);
	let label = prop($$props, 'label', 12, "");
	let show_label = prop($$props, 'show_label', 12, false);
	let pending = prop($$props, 'pending', 12, false);
	let size = prop($$props, 'size', 12, "small");
	let padded = prop($$props, 'padded', 12, true);
	let highlight = prop($$props, 'highlight', 12, false);
	let disabled = prop($$props, 'disabled', 12, false);
	let hasPopup = prop($$props, 'hasPopup', 12, false);
	let color = prop($$props, 'color', 12, "var(--block-label-text-color)");
	let transparent = prop($$props, 'transparent', 12, false);
	let background = prop($$props, 'background', 12, "var(--background-fill-primary)");
	let offset = prop($$props, 'offset', 12, 0);
	let label_position = prop($$props, 'label_position', 12, "left");
	let roundedness = prop($$props, 'roundedness', 12, "quite");
	let onclick = prop($$props, 'onclick', 12, undefined);

	legacy_pre_effect(() => (deep_read_state(highlight()), deep_read_state(color())), () => {
		set(_color, highlight() ? "var(--color-accent)" : color().toString());
	});

	legacy_pre_effect_reset();

	var $$exports = {
		get Icon() {
			return Icon();
		},

		set Icon($$value) {
			Icon($$value);
			flushSync();
		},

		get label() {
			return label();
		},

		set label($$value) {
			label($$value);
			flushSync();
		},

		get show_label() {
			return show_label();
		},

		set show_label($$value) {
			show_label($$value);
			flushSync();
		},

		get pending() {
			return pending();
		},

		set pending($$value) {
			pending($$value);
			flushSync();
		},

		get size() {
			return size();
		},

		set size($$value) {
			size($$value);
			flushSync();
		},

		get padded() {
			return padded();
		},

		set padded($$value) {
			padded($$value);
			flushSync();
		},

		get highlight() {
			return highlight();
		},

		set highlight($$value) {
			highlight($$value);
			flushSync();
		},

		get disabled() {
			return disabled();
		},

		set disabled($$value) {
			disabled($$value);
			flushSync();
		},

		get hasPopup() {
			return hasPopup();
		},

		set hasPopup($$value) {
			hasPopup($$value);
			flushSync();
		},

		get color() {
			return color();
		},

		set color($$value) {
			color($$value);
			flushSync();
		},

		get transparent() {
			return transparent();
		},

		set transparent($$value) {
			transparent($$value);
			flushSync();
		},

		get background() {
			return background();
		},

		set background($$value) {
			background($$value);
			flushSync();
		},

		get offset() {
			return offset();
		},

		set offset($$value) {
			offset($$value);
			flushSync();
		},

		get label_position() {
			return label_position();
		},

		set label_position($$value) {
			label_position($$value);
			flushSync();
		},

		get roundedness() {
			return roundedness();
		},

		set roundedness($$value) {
			roundedness($$value);
			flushSync();
		},

		get onclick() {
			return onclick();
		},

		set onclick($$value) {
			onclick($$value);
			flushSync();
		}
	};

	init$1();

	var button = root$7();

	button.__click = function (...$$args) {
		onclick()?.apply(this, $$args);
	};

	let classes;
	let styles;
	var node = child(button);

	{
		var consequent = ($$anchor) => {
			var span = root_1$5();
			var text = child(span, true);

			reset(span);
			template_effect(() => set_text(text, label()));
			append($$anchor, span);
		};

		if_block(node, ($$render) => {
			if (show_label() && label_position() === "left") $$render(consequent);
		});
	}

	var div = sibling(node, 2);
	let classes_1;
	var node_1 = child(div);

	Icon()(node_1, {});
	reset(div);

	var node_2 = sibling(div, 2);

	{
		var consequent_1 = ($$anchor) => {
			var span_1 = root_2$6();
			var text_1 = child(span_1, true);

			reset(span_1);
			template_effect(() => set_text(text_1, label()));
			append($$anchor, span_1);
		};

		if_block(node_2, ($$render) => {
			if (show_label() && label_position() === "right") $$render(consequent_1);
		});
	}

	reset(button);

	template_effect(() => {
		button.disabled = disabled();
		set_attribute(button, 'aria-label', label());
		set_attribute(button, 'aria-haspopup', hasPopup());
		set_attribute(button, 'title', label());

		classes = set_class(button, 1, `${roundedness() ?? ''}-round`, 'svelte-1lrnmgy', classes, {
			pending: pending(),
			padded: padded(),
			highlight: highlight(),
			transparent: transparent()
		});

		styles = set_style(button, '', styles, {
			color: !disabled() && get(_color) ? get(_color) : "var(--block-label-text-color)",
			'--bg-color': !disabled() ? background() : "auto",
			'margin-left': offset() + "px"
		});

		classes_1 = set_class(div, 1, 'svelte-1lrnmgy', null, classes_1, {
			small: size() === "small",
			large: size() === "large",
			medium: size() === "medium"
		});
	});

	append($$anchor, button);

	return pop($$exports);
}

delegate(['click']);

function click_outside(node, callback) {
  const handle_click = (event) => {
    if (node && !node.contains(event.target) && !event.defaultPrevented) {
      callback(event);
    }
  };
  document.addEventListener("mousedown", handle_click, true);
  return {
    destroy() {
      document.removeEventListener("mousedown", handle_click, true);
    }
  };
}

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 = { ...metadata, ref: ext };
  }
  if (typeof ext === "object") {
    ext = { ...ext };
  } else {
    throw new Error("Invalid extension type");
  }
  if (typeof ext.type === "string") {
    ext.type = [ext.type];
  }
  return ext;
};
const normalizeExtensionPriority = (ext, defaultPriority) => normalizeExtension(ext).priority ?? 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) => this._removeHandlers[type]?.(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) => {
        const handlers = this._addHandlers;
        const queue = this._queue;
        if (!handlers[type]) {
          queue[type] = queue[type] || [];
          queue[type]?.push(ext);
        } else {
          handlers[type]?.(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) {
    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]) {
      queue[type]?.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));
    }
  }
};

const browserExt = {
  extension: {
    type: ExtensionType.Environment,
    name: "browser",
    priority: -1
  },
  test: () => true,
  load: async () => {
    await __vitePreload(() => import('./browserAll-CYHduCqD.js'),true              ?__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53]):void 0,import.meta.url);
  }
};

const webworkerExt = {
  extension: {
    type: ExtensionType.Environment,
    name: "webworker",
    priority: 0
  },
  test: () => typeof self !== "undefined" && self.WorkerGlobalScope !== void 0,
  load: async () => {
    await __vitePreload(() => import('./webworkerAll-BWt5v-sL.js'),true              ?__vite__mapDeps([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53]):void 0,import.meta.url);
  }
};

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 ?? 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);
    }
  }
}

var eventemitter3 = {exports: {}};

var hasRequiredEventemitter3;

function requireEventemitter3 () {
	if (hasRequiredEventemitter3) return eventemitter3.exports;
	hasRequiredEventemitter3 = 1;
	(function (module) {

		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.
		//
		{
		  module.exports = EventEmitter;
		} 
	} (eventemitter3));
	return eventemitter3.exports;
}

var eventemitter3Exports = requireEventemitter3();
const EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports);

const PI_2 = Math.PI * 2;
const RAD_TO_DEG = 180 / Math.PI;
const DEG_TO_RAD = Math.PI / 180;

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();

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$3.identity();
  }
}
const tempMatrix$3 = new Matrix();
const identityMatrix$1 = new Matrix();

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);
  }
};

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}]`;
  }
}

const uidCache = {
  default: -1
};
function uid(name = "default") {
  if (uidCache[name] === void 0) {
    uidCache[name] = -1;
  }
  return ++uidCache[name];
}

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
  }
});

const NOOP = () => {
};

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 definedProps(obj) {
  const result = {};
  for (const key in obj) {
    if (obj[key] !== void 0) {
      result[key] = obj[key];
    }
  }
  return result;
}

const idHash$1 = /* @__PURE__ */ Object.create(null);
function createResourceIdFromString(value) {
  const id = idHash$1[value];
  if (id === void 0) {
    idHash$1[value] = uid("resource");
  }
  return id;
}
const _TextureStyle = class _TextureStyle extends EventEmitter {
  /**
   * @param options - options for the style
   */
  constructor(options = {}) {
    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 = { ..._TextureStyle.defaultOptions, ...options };
    this.addressMode = options.addressMode;
    this.addressModeU = options.addressModeU ?? this.addressModeU;
    this.addressModeV = options.addressModeV ?? this.addressModeV;
    this.addressModeW = options.addressModeW ?? this.addressModeW;
    this.scaleMode = options.scaleMode;
    this.magFilter = options.magFilter ?? this.magFilter;
    this.minFilter = options.minFilter ?? this.minFilter;
    this.mipmapFilter = options.mipmapFilter ?? this.mipmapFilter;
    this.lodMinClamp = options.lodMinClamp;
    this.lodMaxClamp = options.lodMaxClamp;
    this.compare = options.compare;
    this.maxAnisotropy = options.maxAnisotropy ?? 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._sharedResourceId = null;
    this.emit("change", this);
  }
  _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;

const _TextureSource = class _TextureSource extends EventEmitter {
  /**
   * @param options - options for creating a new TextureSource
   */
  constructor(options = {}) {
    super();
    this.options = options;
    /** @internal */
    this._gpuData = /* @__PURE__ */ Object.create(null);
    /** @internal */
    this._gcLastUsed = -1;
    /** unique id for this Texture source */
    this.uid = uid("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("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 = { ..._TextureSource.defaultOptions, ...options };
    this.label = options.label ?? "";
    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 ? this.resourceWidth ?? 1 : 1;
    }
    if (options.height) {
      this.pixelHeight = options.height * this._resolution;
    } else {
      this.pixelHeight = this.resource ? this.resourceHeight ?? 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) {
    if (this.style === value)
      return;
    this._style?.off("change", this._onStyleChange, this);
    this._style = value;
    this._style?.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.unload();
    this.emit("destroy", 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("resource");
    this.emit("change", this);
    this.emit("unload", this);
    for (const key in this._gpuData) {
      this._gpuData[key]?.destroy?.();
    }
    this._gpuData = /* @__PURE__ */ Object.create(null);
  }
  /** 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("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;

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({
      ...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;

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) {
    if (this.texture === value)
      return;
    this._texture?.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;
  }
}

class Texture extends EventEmitter {
  /**
   * @param {TextureOptions} options - Options for the texture
   */
  constructor({
    source,
    label,
    frame,
    orig,
    trim,
    defaultAnchor,
    defaultBorders,
    rotate,
    dynamic
  } = {}) {
    super();
    /** unique id for this texture */
    this.uid = uid("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 = source?.source ?? 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 ?? 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) {
      this._source.off("resize", this.update, this);
      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;

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;
  }
}

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;
  }
}

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<t.length;n++){var e=t[n][0](r);if(e)return [e,t[n][1]]}return [null,void 0]},x=function(r){return "string"==typeof r?N(r.trim(),y.string):"object"==typeof r&&null!==r?N(r,y.object):[null,void 0]},M=function(r,t){var n=c(r);return {h:n.h,s:e(n.s+100*t,0,100),l:n.l,a:n.a}},H=function(r){return (299*r.r+587*r.g+114*r.b)/1e3/255},$=function(r,t){var n=c(r);return {h:n.h,s:n.s,l:e(n.l+100*t,0,100),a:n.a}},j=function(){function r(r){this.parsed=x(r)[0],this.rgba=this.parsed||{r:0,g:0,b:0,a:1};}return r.prototype.isValid=function(){return null!==this.parsed},r.prototype.brightness=function(){return n(H(this.rgba),2)},r.prototype.isDark=function(){return H(this.rgba)<.5},r.prototype.isLight=function(){return H(this.rgba)>=.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));});};

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<t&&(t=u,b=g);}return b}};f.string.push([function(f){var r=f.toLowerCase(),d="transparent"===r?"#0000":a[r];return d?new e(d).toRgb():null},"name"]);}

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 { ...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;

const cullingMixin = {
  cullArea: null,
  cullable: false,
  cullableChildren: true
};

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);
  }
}

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();
  }
};

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) {
    let item;
    if (this._index > 0) {
      item = this._pool[--this._index];
    } else {
      item = new this._classType();
      this._count++;
    }
    item.init?.(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) {
    item.reset?.();
    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;
  }
}

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<T>} 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<T>} 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<T>} ClassType - The constructor of the items in the pool.
   * @returns {Pool<T>} 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);

const cacheAsTextureMixin = {
  get isCachedAsTexture() {
    return !!this.renderGroup?.isCachedAsTexture;
  },
  cacheAsTexture(val) {
    if (typeof val === "boolean" && val === false) {
      this.disableRenderGroup();
    } else {
      this.enableRenderGroup();
      this.renderGroup.enableCacheAsTexture(val === true ? {} : val);
    }
  },
  updateCacheTexture() {
    this.renderGroup?.updateCacheTexture();
  },
  get cacheAsBitmap() {
    return this.isCachedAsTexture;
  },
  set cacheAsBitmap(val) {
    deprecation("v8.6.0", "cacheAsBitmap is deprecated, use cacheAsTexture instead.");
    this.cacheAsTexture(val);
  }
};

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;
}

const childrenHelperMixin = {
  allowChildren: true,
  removeChildren(beginIndex = 0, endIndex) {
    const end = 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];
        child.parentRenderLayer?.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() {
    this.parent?.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);
  }
};

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);
    }
  }
};

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;
  }
}

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);

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?.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() {
    return this._maskEffect?.mask;
  },
  setMask(options) {
    this._maskOptions = {
      ...this._maskOptions,
      ...options
    };
    if (options.mask) {
      this.mask = options.mask;
    }
    this._markStructureAsChanged();
  },
  set filters(value) {
    if (!Array.isArray(value) && value)
      value = [value];
    const effect = this._filterEffect || (this._filterEffect = new FilterEffect());
    value = value;
    const hasFilters = value?.length > 0;
    const hadFilters = effect.filters?.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;
      }
    }
  },
  get filters() {
    return this._filterEffect?.filters;
  },
  set filterArea(value) {
    this._filterEffect || (this._filterEffect = new FilterEffect());
    this._filterEffect.filterArea = value;
  },
  get filterArea() {
    return this._filterEffect?.filterArea;
  }
};

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;
  }
};

const matrixPool = BigPool.getPool(Matrix);
const boundsPool = BigPool.getPool(Bounds);

const tempMatrix$2 = 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$2).invert());
      }
      bounds.addBounds(localBounds);
      boundsPool.return(localBounds);
    } else if (this.renderGroup) {
      bounds.addBounds(localBounds, this.relativeGroupTransform);
      boundsPool.return(localBounds);
    }
  }
};

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) {
  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++) {
      target.effects[i].addBounds?.(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;
}

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;
}

const WHITE_BGR = 16777215;
function multiplyColors(localBGRColor, parentBGRColor) {
  if (localBGRColor === WHITE_BGR) {
    return parentBGRColor;
  }
  if (parentBGRColor === WHITE_BGR) {
    return localBGRColor;
  }
  return multiplyHexColors(localBGRColor, parentBGRColor);
}

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);
  }
};

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) {
  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++) {
      target.effects[i].addLocalBounds?.(bounds, rootContainer);
    }
    parentBounds.addBounds(bounds, Matrix.IDENTITY);
    boundsPool.return(bounds);
  }
  matrixPool.return(relativeTransform);
}

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;
}

const tempMatrix$1 = 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$1);
    }
    return localBoundsCacheData.localBounds;
  },
  getBounds(skipUpdate, bounds) {
    return getGlobalBounds(this, skipUpdate, bounds || new Bounds());
  }
};

const onRenderMixin = {
  _onRender: null,
  set onRender(func) {
    const renderGroup = this.renderGroup || this.parentRenderGroup;
    if (!func) {
      if (this._onRender) {
        renderGroup?.removeOnRender(this);
      }
      this._onRender = null;
      return;
    }
    if (!this._onRender) {
      renderGroup?.addOnRender(this);
    }
    this._onRender = func;
  },
  get onRender() {
    return this._onRender;
  }
};

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;
}

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;
  }
};

class InstructionSet {
  constructor() {
    /** a unique id for this instruction set used through the renderer */
    this.uid = uid("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"]);
  }
}

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({
      ...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);

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 &= -2;
    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 &= -3;
    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 &= -5;
    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;
  }
}

function assignWithIgnore(target, options, ignore = {}) {
  for (const key in options) {
    if (!ignore[key] && options[key] !== void 0) {
      target[key] = options[key];
    }
  }
}

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;
class Container extends EventEmitter {
  constructor(options = {}) {
    super();
    /**
     * unique id for this container
     * @internal
     */
    this.uid = uid("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;
    /**
     * The RenderLayer this container belongs to, if any.
     * If it belongs to a RenderLayer, it will be rendered from the RenderLayer's position in the scene.
     * @readonly
     * @advanced
     */
    this.parentRenderLayer = null;
    // / /////////////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
    });
    options.children?.forEach((child) => this.addChild(child));
    options.parent?.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<Sprite>(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<Sprite>(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?.removeChild(this);
    this.renderGroup = BigPool.get(RenderGroup, this);
    this.groupTransform = Matrix.IDENTITY;
    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?.removeChild(this);
    BigPool.return(this.renderGroup);
    this.renderGroup = null;
    this.groupTransform = this.relativeGroupTransform;
    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) {
    const size = this.getLocalBounds();
    if (typeof value === "object") {
      height = value.height ?? value.width;
      value = value.width;
    } else {
      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 ?? 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) {
    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?.children;
    if (destroyChildren && oldChildren) {
      for (let i = 0; i < oldChildren.length; ++i) {
        oldChildren[i].destroy(options);
      }
    }
    this.renderGroup?.destroy();
    this.renderGroup = null;
  }
}
extensions.mixin(
  Container,
  childrenHelperMixin,
  getFastGlobalBoundsMixin,
  toLocalGlobalMixin,
  onRenderMixin,
  measureMixin,
  effectsMixin,
  findMixin,
  sortMixin,
  cullingMixin,
  cacheAsTextureMixin,
  getGlobalMixin,
  collectRenderablesMixin
);

class ViewContainer extends Container {
  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);
    /** If set to true, the resource will be garbage collected automatically when it is not used. */
    this.autoGarbageCollect = true;
    /** @internal */
    this._gcLastUsed = -1;
    this._bounds = new Bounds(0, 1, 0, 0);
    this._boundsDirty = true;
    this.autoGarbageCollect = options.autoGarbageCollect ?? 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);
    }
  }
  /** Unloads the GPU data from the view. */
  unload() {
    this.emit("unload", this);
    for (const key in this._gpuData) {
      this._gpuData[key]?.destroy();
    }
    this._gpuData = /* @__PURE__ */ Object.create(null);
    this.onViewUpdate();
  }
  destroy(options) {
    this.unload();
    super.destroy(options);
    this._bounds = 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);
  }
}

class Sprite extends ViewContainer {
  /**
   * @param options - The options for creating the sprite.
   */
  constructor(options = Texture.EMPTY) {
    if (options instanceof Texture) {
      options = { texture: options };
    }
    const { texture = Texture.EMPTY, anchor, roundPixels, width, height, ...rest } = options;
    super({
      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 ?? 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?.texture;
    if (destroyTexture) {
      const destroyTextureSource = typeof options === "boolean" ? options : options?.textureSource;
      this._texture.destroy(destroyTextureSource);
    }
    this._texture = null;
    this._visualBounds = null;
    this._bounds = null;
    this._anchor = 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) {
    if (typeof value === "object") {
      height = value.height ?? value.width;
      value = value.width;
    } else {
      height ?? (height = value);
    }
    value !== void 0 && this._setWidth(value, this._texture.orig.width);
    height !== void 0 && this._setHeight(height, this._texture.orig.height);
  }
}

const tempBounds = new Bounds();
function addMaskBounds(mask, bounds, skipUpdateTransform) {
  const boundsToMask = tempBounds;
  mask.measurable = true;
  getGlobalBounds(mask, skipUpdateTransform, boundsToMask);
  bounds.addBoundsMask(boundsToMask);
  mask.measurable = false;
}

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;
}

class AlphaMask {
  constructor(options) {
    this.priority = 0;
    this.inverse = false;
    this.pipe = "alphaMask";
    if (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() {
    if (this.mask === null)
      return;
    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;

class ColorMask {
  constructor(options) {
    this.priority = 0;
    this.pipe = "colorMask";
    if (options?.mask) {
      this.init(options.mask);
    }
  }
  init(mask) {
    this.mask = mask;
  }
  destroy() {
  }
  static test(mask) {
    return typeof mask === "number";
  }
}
ColorMask.extension = ExtensionType.MaskEffect;

class StencilMask {
  constructor(options) {
    this.priority = 0;
    this.pipe = "stencilMask";
    if (options?.mask) {
      this.init(options.mask);
    }
  }
  init(mask) {
    this.mask = mask;
    this.mask.includeInBuild = false;
    this.mask.measurable = false;
  }
  reset() {
    if (this.mask === null)
      return;
    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;

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: () => document.baseURI ?? window.location.href,
  getFontFaceSet: () => document.fonts,
  fetch: (url, options) => fetch(url, options),
  parseXML: (xml) => {
    const parser = new DOMParser();
    return parser.parseFromString(xml, "text/xml");
  }
};

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;
  }
};

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;

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;

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 || {});

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;
  }
}

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}.
     * @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.
     * @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;

let promise;
async function detectVideoAlphaMode() {
  promise ?? (promise = (async () => {
    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);
    gl.getExtension("WEBGL_lose_context")?.loseContext();
    return pixel[0] <= pixel[3] ? "premultiplied-alpha" : "premultiply-alpha-on-upload";
  })());
  return promise;
}

const _VideoSource = class _VideoSource extends TextureSource {
  constructor(options) {
    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 = {
      ..._VideoSource.defaultOptions,
      ...options
    };
    this._autoUpdate = true;
    this._isConnectedToTicker = false;
    this._updateFPS = options.updateFPS || 0;
    this._msToNextUpdate = 0;
    this.autoPlay = options.autoPlay !== false;
    this.alphaMode = options.alphaMode ?? "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<this>} 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 = {
  ...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;

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;
  });
};

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();

const sources = [];
extensions.handleByList(ExtensionType.TextureSource, sources);
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;

extensions.add(AlphaMask, ColorMask, StencilMask, VideoSource, ImageSource, CanvasSource, BufferImageSource);

var LoaderParserPriority = /* @__PURE__ */ ((LoaderParserPriority2) => {
  LoaderParserPriority2[LoaderParserPriority2["Low"] = 0] = "Low";
  LoaderParserPriority2[LoaderParserPriority2["Normal"] = 1] = "Normal";
  LoaderParserPriority2[LoaderParserPriority2["High"] = 2] = "High";
  return LoaderParserPriority2;
})(LoaderParserPriority || {});

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;
          }
        }
      } 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 ?? DOMAdapter.get().getBaseUrl()));
    const rootUrl = removeUrlParams(this.toPosix(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);
    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) {
    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 = segments[i - 1] ?? "";
          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;
    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);
    return ret;
  },
  sep: "/",
  delimiter: ":",
  joinExtensions: [".html"]
};

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;
}

const isSingleItem = (item) => !Array.isArray(item);

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) {
    this._bundleIdConnector = bundleIdentifier.connector ?? this._bundleIdConnector;
    this._createBundleAssetId = bundleIdentifier.createBundleAssetId ?? this._createBundleAssetId;
    this._extractAssetIdFromBundle = bundleIdentifier.extractAssetIdFromBundle ?? 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) => v?.src ?? v);
        if (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 { 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({
        ...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 = [];
      const parseUrl = (url) => {
        const parser = this._parsers.find((p) => p.test(url));
        return {
          ...parser?.parse(url),
          src: url
        };
      };
      srcsToUse.forEach((srcs) => {
        srcs.forEach((src2) => {
          let formattedAsset = {};
          if (typeof src2 !== "object") {
            formattedAsset = parseUrl(src2);
          } else {
            data = src2.data ?? data;
            format = src2.format ?? format;
            if (src2.loadParser || src2.parser) {
              userDefinedLoadParser = src2.loadParser ?? userDefinedLoadParser;
              userDefinedParser = src2.parser ?? userDefinedParser;
            }
            formattedAsset = {
              ...parseUrl(src2.src),
              ...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,
            progressSize: asset.progressSize
          });
          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?.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) {
    const { aliases, data: assetData, loadParser, parser, format, progressSize } = data;
    if (this._basePath || this._rootPath) {
      formattedAsset.src = path.toAbsolute(formattedAsset.src, this._basePath, this._rootPath);
    }
    formattedAsset.alias = aliases ?? formattedAsset.alias ?? [formattedAsset.src];
    formattedAsset.src = this._appendDefaultSearchParams(formattedAsset.src);
    formattedAsset.data = { ...assetData || {}, ...formattedAsset.data };
    formattedAsset.loadParser = loadParser ?? formattedAsset.loadParser;
    formattedAsset.parser = parser ?? formattedAsset.parser;
    formattedAsset.format = format ?? formattedAsset.format ?? getUrlExtension(formattedAsset.src);
    if (progressSize !== void 0) {
      formattedAsset.progressSize = progressSize;
    }
    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();
}

const copySearchParams = (targetUrl, sourceUrl) => {
  const searchParams = sourceUrl.split("?")[1];
  if (searchParams) {
    targetUrl += `?${searchParams}`;
  }
  return targetUrl;
};

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?.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) {
    for (const i in this.textures) {
      this.textures[i].destroy();
    }
    this._frames = null;
    this._frameKeys = null;
    this.data = null;
    this.textures = null;
    if (destroyBase) {
      this._texture?.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;

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) => {
      const split = value.split(".");
      return {
        resolution: parseFloat(Resolver.RETINA_PREFIX.exec(value)?.[1] ?? "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) {
      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
      } = options?.data ?? {};
      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 ?? 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 = asset?.meta?.related_multi_packs;
      if (Array.isArray(multiPacks)) {
        const promises = [];
        for (const item of multiPacks) {
          if (typeof item !== "string") {
            continue;
          }
          let itemUrl = basePath + item;
          if (options.data?.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);
    }
  }
};

extensions.add(spritesheetAsset);

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;
}

let context;
function getTestContext() {
  if (!context || context?.isContextLost()) {
    const canvas = DOMAdapter.get().createCanvas();
    context = canvas.getContext("webgl", {});
  }
  return context;
}

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;
}

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}
        `;
}

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;
}

function insertVersion(src, isES300) {
  if (!isES300)
    return src;
  return `#version 300 es
${src}`;
}

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}`;
}

function stripVersion(src, isES300) {
  if (!isES300)
    return src;
  return src.replace("#version 300 es", "");
}

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 = { ..._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;

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) {
  return attributeFormatData[format] ?? attributeFormatData.float32;
}

const WGSL_TO_VERTEX_TYPES = {
  f32: "float32",
  "vec2<f32>": "float32x2",
  "vec3<f32>": "float32x3",
  "vec4<f32>": "float32x4",
  vec2f: "float32x2",
  vec3f: "float32x3",
  vec4f: "float32x4",
  i32: "sint32",
  "vec2<i32>": "sint32x2",
  "vec3<i32>": "sint32x3",
  "vec4<i32>": "sint32x4",
  u32: "uint32",
  "vec2<u32>": "uint32x2",
  "vec3<u32>": "uint32x3",
  "vec4<u32>": "uint32x4",
  bool: "uint32",
  "vec2<bool>": "uint32x2",
  "vec3<bool>": "uint32x3",
  "vec4<bool>": "uint32x4"
};
function extractAttributesFromGpuProgram({ source, entryPoint }) {
  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 = WGSL_TO_VERTEX_TYPES[match[3]] ?? "float32";
        results[match[2]] = {
          location: parseInt(match[1], 10),
          format,
          stride: getAttributeInfoFromFormat(format).stride,
          offset: 0,
          instance: false,
          start: 0
        };
      }
    }
  }
  return results;
}

function extractStructAndGroups(wgsl) {
  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 = wgsl.match(linePattern)?.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] === "<uniform>",
    type: item.match(typePattern)[1]
  }));
  if (!groups) {
    return {
      groups: [],
      structs: []
    };
  }
  const structs = wgsl.match(structPattern)?.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)) ?? [];
  return {
    groups,
    structs
  };
}

var ShaderStage = /* @__PURE__ */ ((ShaderStage2) => {
  ShaderStage2[ShaderStage2["VERTEX"] = 1] = "VERTEX";
  ShaderStage2[ShaderStage2["FRAGMENT"] = 2] = "FRAGMENT";
  ShaderStage2[ShaderStage2["COMPUTE"] = 4] = "COMPUTE";
  return ShaderStage2;
})(ShaderStage || {});

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;
}

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;
}

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 };
}

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;
    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 ?? generateLayoutHash(this.structsAndGroups);
    this.gpuLayout = gpuLayout ?? generateGpuLayoutGroups(this.structsAndGroups);
    this.autoAssignGlobalUniforms = !!(this.layout[0]?.globalUniforms !== void 0);
    this.autoAssignLocalUniforms = !!(this.layout[1]?.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() {
    this._attributeData ?? (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];
  }
}

const UNIFORM_TYPES_VALUES = [
  "f32",
  "i32",
  "vec2<f32>",
  "vec3<f32>",
  "vec4<f32>",
  "mat2x2<f32>",
  "mat3x3<f32>",
  "mat4x4<f32>",
  "mat3x2<f32>",
  "mat4x2<f32>",
  "mat2x3<f32>",
  "mat4x3<f32>",
  "mat2x4<f32>",
  "mat3x4<f32>",
  "vec2<i32>",
  "vec3<i32>",
  "vec4<i32>"
];
const UNIFORM_TYPES_MAP = UNIFORM_TYPES_VALUES.reduce((acc, type) => {
  acc[type] = true;
  return acc;
}, {});

function getDefaultUniformValue(type, size) {
  switch (type) {
    case "f32":
      return 0;
    case "vec2<f32>":
      return new Float32Array(2 * size);
    case "vec3<f32>":
      return new Float32Array(3 * size);
    case "vec4<f32>":
      return new Float32Array(4 * size);
    case "mat2x2<f32>":
      return new Float32Array([
        1,
        0,
        0,
        1
      ]);
    case "mat3x3<f32>":
      return new Float32Array([
        1,
        0,
        0,
        0,
        1,
        0,
        0,
        0,
        1
      ]);
    case "mat4x4<f32>":
      return new Float32Array([
        1,
        0,
        0,
        0,
        0,
        1,
        0,
        0,
        0,
        0,
        1,
        0,
        0,
        0,
        0,
        1
      ]);
  }
  return null;
}

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("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("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;
    options = { ..._UniformGroup.defaultOptions, ...options };
    this.uniformStructures = uniformStructures;
    const uniforms = {};
    for (const i in uniformStructures) {
      const uniformData = uniformStructures[i];
      uniformData.name = i;
      uniformData.size = uniformData.size ?? 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(", ")}`);
      }
      uniformData.value ?? (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;

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) {
    const currentResource = this.resources[index];
    if (resource === currentResource)
      return;
    if (currentResource) {
      resource.off?.("change", this.onResourceChange, this);
    }
    resource.on?.("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 now - The current time in milliseconds.
   * @param tick - The current tick.
   * @internal
   */
  _touch(now, tick) {
    const resources = this.resources;
    for (const i in resources) {
      resources[i]._gcLastUsed = now;
      resources[i]._touched = tick;
    }
  }
  /** Destroys this bind group and removes all listeners. */
  destroy() {
    const resources = this.resources;
    for (const i in resources) {
      const resource = resources[i];
      resource?.off?.("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();
    }
  }
}

var RendererType = /* @__PURE__ */ ((RendererType2) => {
  RendererType2[RendererType2["WEBGL"] = 1] = "WEBGL";
  RendererType2[RendererType2["WEBGPU"] = 2] = "WEBGPU";
  RendererType2[RendererType2["BOTH"] = 3] = "BOTH";
  return RendererType2;
})(RendererType || {});

class Shader extends EventEmitter {
  constructor(options) {
    super();
    /** A unique identifier for the shader */
    this.uid = uid("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 = [];
    /** @internal */
    this._destroyed = false;
    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) {
    if (this._destroyed)
      return;
    this._destroyed = true;
    this.emit("destroy", this);
    if (destroyPrograms) {
      this.gpuProgram?.destroy();
      this.glProgram?.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 { gpu, gl, ...rest } = options;
    let gpuProgram;
    let glProgram;
    if (gpu) {
      gpuProgram = GpuProgram.from(gpu);
    }
    if (gl) {
      glProgram = GlProgram.from(gl);
    }
    return new Shader({
      gpuProgram,
      glProgram,
      ...rest
    });
  }
}

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 = 0;
const OFFSET = 1;
const CULLING = 2;
const DEPTH_TEST = 3;
const WINDING = 4;
const DEPTH_MASK = 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);
  }
  set blend(value) {
    if (!!(this.data & 1 << BLEND) !== value) {
      this.data ^= 1 << BLEND;
    }
  }
  /**
   * Activates adding an offset to depth values of polygon's fragments
   * @default false
   */
  get offsets() {
    return !!(this.data & 1 << OFFSET);
  }
  set offsets(value) {
    if (!!(this.data & 1 << OFFSET) !== value) {
      this.data ^= 1 << OFFSET;
    }
  }
  /** 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);
  }
  set culling(value) {
    if (!!(this.data & 1 << CULLING) !== value) {
      this.data ^= 1 << CULLING;
    }
  }
  /**
   * Activates depth comparisons and updates to the depth buffer.
   * @default false
   */
  get depthTest() {
    return !!(this.data & 1 << DEPTH_TEST);
  }
  set depthTest(value) {
    if (!!(this.data & 1 << DEPTH_TEST) !== value) {
      this.data ^= 1 << DEPTH_TEST;
    }
  }
  /**
   * Enables or disables writing to the depth buffer.
   * @default true
   */
  get depthMask() {
    return !!(this.data & 1 << DEPTH_MASK);
  }
  set depthMask(value) {
    if (!!(this.data & 1 << DEPTH_MASK) !== value) {
      this.data ^= 1 << DEPTH_MASK;
    }
  }
  /**
   * Specifies whether or not front or back-facing polygons can be culled.
   * @default false
   */
  get clockwiseFrontFace() {
    return !!(this.data & 1 << WINDING);
  }
  set clockwiseFrontFace(value) {
    if (!!(this.data & 1 << WINDING) !== value) {
      this.data ^= 1 << WINDING;
    }
  }
  /**
   * 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;

const _Filter = class _Filter extends Shader {
  /**
   * @param options - The optional parameters of this filter.
   */
  constructor(options) {
    options = { ..._Filter.defaultOptions, ...options };
    super(options);
    /** If enabled is true the filter is applied, if false it will not. */
    this.enabled = true;
    /**
     * The gpu state the filter requires to render.
     * @internal
     */
    this._state = State.for2d();
    this.blendMode = options.blendMode;
    this.padding = options.padding;
    if (typeof options.antialias === "boolean") {
      this.antialias = options.antialias ? "on" : "off";
    } else {
      this.antialias = options.antialias;
    }
    this.resolution = options.resolution;
    this.blendRequired = options.blendRequired;
    this.clipToViewport = options.clipToViewport;
    this.addResource("uTexture", 0, 1);
    if (options.blendRequired) {
      this.addResource("uBackTexture", 0, 3);
    }
  }
  /**
   * Applies the filter
   * @param filterManager - The renderer to retrieve the filter from
   * @param input - The input render target.
   * @param output - The target to output to.
   * @param clearMode - Should the output be cleared before rendering to it
   */
  apply(filterManager, input, output, clearMode) {
    filterManager.applyFilter(this, input, output, clearMode);
  }
  /**
   * Get the blend mode of the filter.
   * @default "normal"
   */
  get blendMode() {
    return this._state.blendMode;
  }
  /** Sets the blend mode of the filter. */
  set blendMode(value) {
    this._state.blendMode = value;
  }
  /**
   * A short hand function to create a filter based of a vertex and fragment shader src.
   * @param options
   * @returns A shiny new PixiJS filter!
   */
  static from(options) {
    const { gpu, gl, ...rest } = options;
    let gpuProgram;
    let glProgram;
    if (gpu) {
      gpuProgram = GpuProgram.from(gpu);
    }
    if (gl) {
      glProgram = GlProgram.from(gl);
    }
    return new _Filter({
      gpuProgram,
      glProgram,
      ...rest
    });
  }
};
/** The default filter settings */
_Filter.defaultOptions = {
  blendMode: "normal",
  resolution: 1,
  padding: 0,
  antialias: "off",
  blendRequired: false,
  clipToViewport: true
};
let Filter = _Filter;

const environments = [];
extensions.handleByNamedList(ExtensionType.Environment, environments);
async function loadEnvironmentExtensions(skip) {
  if (skip)
    return;
  for (let i = 0; i < environments.length; i++) {
    const env = environments[i];
    if (env.value.test()) {
      await env.value.load();
      return;
    }
  }
}

let unsafeEval;
function unsafeEvalSupported() {
  if (typeof unsafeEval === "boolean") {
    return unsafeEval;
  }
  try {
    const func = new Function("param1", "param2", "param3", "return param1[param2] === param3;");
    unsafeEval = func({ a: "b" }, "a", "b") === true;
  } catch (_e) {
    unsafeEval = false;
  }
  return unsafeEval;
}

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(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(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(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
    };
}

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;
}

const earcut = earcut$1.default || earcut$1;

var CLEAR = /* @__PURE__ */ ((CLEAR2) => {
  CLEAR2[CLEAR2["NONE"] = 0] = "NONE";
  CLEAR2[CLEAR2["COLOR"] = 16384] = "COLOR";
  CLEAR2[CLEAR2["STENCIL"] = 1024] = "STENCIL";
  CLEAR2[CLEAR2["DEPTH"] = 256] = "DEPTH";
  CLEAR2[CLEAR2["COLOR_DEPTH"] = 16640] = "COLOR_DEPTH";
  CLEAR2[CLEAR2["COLOR_STENCIL"] = 17408] = "COLOR_STENCIL";
  CLEAR2[CLEAR2["DEPTH_STENCIL"] = 1280] = "DEPTH_STENCIL";
  CLEAR2[CLEAR2["ALL"] = 17664] = "ALL";
  return CLEAR2;
})(CLEAR || {});

class SystemRunner {
  /**
   * @param name - The function name that will be executed on the listeners added to this Runner.
   */
  constructor(name) {
    this.items = [];
    this._name = name;
  }
  /* jsdoc/check-param-names */
  /**
   * Dispatch/Broadcast Runner to all listeners added to the queue.
   * @param {...any} params - (optional) parameters to pass to each listener
   */
  /* jsdoc/check-param-names */
  emit(a0, a1, a2, a3, a4, a5, a6, a7) {
    const { name, items } = this;
    for (let i = 0, len = items.length; i < len; i++) {
      items[i][name](a0, a1, a2, a3, a4, a5, a6, a7);
    }
    return this;
  }
  /**
   * Add a listener to the Runner
   *
   * Runners do not need to have scope or functions passed to them.
   * All that is required is to pass the listening object and ensure that it has contains a function that has the same name
   * as the name provided to the Runner when it was created.
   *
   * Eg A listener passed to this Runner will require a 'complete' function.
   *
   * ```ts
   * import { Runner } from 'pixi.js';
   *
   * const complete = new Runner('complete');
   * ```
   *
   * The scope used will be the object itself.
   * @param {any} item - The object that will be listening.
   */
  add(item) {
    if (item[this._name]) {
      this.remove(item);
      this.items.push(item);
    }
    return this;
  }
  /**
   * Remove a single listener from the dispatch queue.
   * @param {any} item - The listener that you would like to remove.
   */
  remove(item) {
    const index = this.items.indexOf(item);
    if (index !== -1) {
      this.items.splice(index, 1);
    }
    return this;
  }
  /**
   * Check to see if the listener is already in the Runner
   * @param {any} item - The listener that you would like to check.
   */
  contains(item) {
    return this.items.indexOf(item) !== -1;
  }
  /** Remove all listeners from the Runner */
  removeAll() {
    this.items.length = 0;
    return this;
  }
  /** Remove all references, don't use after this. */
  destroy() {
    this.removeAll();
    this.items = null;
    this._name = null;
  }
  /**
   * `true` if there are no this Runner contains no listeners
   * @readonly
   */
  get empty() {
    return this.items.length === 0;
  }
  /**
   * The name of the runner.
   * @readonly
   */
  get name() {
    return this._name;
  }
}

const defaultRunners = [
  "init",
  "destroy",
  "contextChange",
  "resolutionChange",
  "resetState",
  "renderEnd",
  "renderStart",
  "render",
  "update",
  "postrender",
  "prerender"
];
const _AbstractRenderer = class _AbstractRenderer extends EventEmitter {
  /**
   * Set up a system with a collection of SystemClasses and runners.
   * Systems are attached dynamically to this class when added.
   * @param config - the config for the system manager
   */
  constructor(config) {
    super();
    /** The current tick of the renderer. */
    this.tick = 0;
    /** @internal */
    this.uid = uid("renderer");
    /** @internal */
    this.runners = /* @__PURE__ */ Object.create(null);
    /** @internal */
    this.renderPipes = /* @__PURE__ */ Object.create(null);
    this._initOptions = {};
    this._systemsHash = /* @__PURE__ */ Object.create(null);
    this.type = config.type;
    this.name = config.name;
    this.config = config;
    const combinedRunners = [...defaultRunners, ...this.config.runners ?? []];
    this._addRunners(...combinedRunners);
    this._unsafeEvalCheck();
  }
  /**
   * Initialize the renderer.
   * @param options - The options to use to create the renderer.
   */
  async init(options = {}) {
    const skip = options.skipExtensionImports === true ? true : options.manageImports === false;
    await loadEnvironmentExtensions(skip);
    this._addSystems(this.config.systems);
    this._addPipes(this.config.renderPipes, this.config.renderPipeAdaptors);
    for (const systemName in this._systemsHash) {
      const system = this._systemsHash[systemName];
      const defaultSystemOptions = system.constructor.defaultOptions;
      options = { ...defaultSystemOptions, ...options };
    }
    options = { ..._AbstractRenderer.defaultOptions, ...options };
    this._roundPixels = options.roundPixels ? 1 : 0;
    for (let i = 0; i < this.runners.init.items.length; i++) {
      await this.runners.init.items[i].init(options);
    }
    this._initOptions = options;
  }
  render(args, deprecated) {
    this.tick++;
    let options = args;
    if (options instanceof Container) {
      options = { container: options };
      if (deprecated) {
        deprecation(v8_0_0, "passing a second argument is deprecated, please use render options instead");
        options.target = deprecated.renderTexture;
      }
    }
    options.target || (options.target = this.view.renderTarget);
    if (options.target === this.view.renderTarget) {
      this._lastObjectRendered = options.container;
      options.clearColor ?? (options.clearColor = this.background.colorRgba);
      options.clear ?? (options.clear = this.background.clearBeforeRender);
    }
    if (options.clearColor) {
      const isRGBAArray = Array.isArray(options.clearColor) && options.clearColor.length === 4;
      options.clearColor = isRGBAArray ? options.clearColor : Color.shared.setValue(options.clearColor).toArray();
    }
    if (!options.transform) {
      options.container.updateLocalTransform();
      options.transform = options.container.localTransform;
    }
    if (!options.container.visible) {
      return;
    }
    options.container.enableRenderGroup();
    this.runners.prerender.emit(options);
    this.runners.renderStart.emit(options);
    this.runners.render.emit(options);
    this.runners.renderEnd.emit(options);
    this.runners.postrender.emit(options);
  }
  /**
   * Resizes the WebGL view to the specified width and height.
   * @param desiredScreenWidth - The desired width of the screen.
   * @param desiredScreenHeight - The desired height of the screen.
   * @param resolution - The resolution / device pixel ratio of the renderer.
   */
  resize(desiredScreenWidth, desiredScreenHeight, resolution) {
    const previousResolution = this.view.resolution;
    this.view.resize(desiredScreenWidth, desiredScreenHeight, resolution);
    this.emit("resize", this.view.screen.width, this.view.screen.height, this.view.resolution);
    if (resolution !== void 0 && resolution !== previousResolution) {
      this.runners.resolutionChange.emit(resolution);
    }
  }
  /**
   * Clears the render target.
   * @param options - The options to use when clearing the render target.
   * @param options.target - The render target to clear.
   * @param options.clearColor - The color to clear with.
   * @param options.clear - The clear mode to use.
   * @advanced
   */
  clear(options = {}) {
    const renderer = this;
    options.target || (options.target = renderer.renderTarget.renderTarget);
    options.clearColor || (options.clearColor = this.background.colorRgba);
    options.clear ?? (options.clear = CLEAR.ALL);
    const { clear, clearColor, target } = options;
    Color.shared.setValue(clearColor ?? this.background.colorRgba);
    renderer.renderTarget.clear(target, clear, Color.shared.toArray());
  }
  /** The resolution / device pixel ratio of the renderer. */
  get resolution() {
    return this.view.resolution;
  }
  set resolution(value) {
    this.view.resolution = value;
    this.runners.resolutionChange.emit(value);
  }
  /**
   * Same as view.width, actual number of pixels in the canvas by horizontal.
   * @type {number}
   * @readonly
   * @default 800
   */
  get width() {
    return this.view.texture.frame.width;
  }
  /**
   * Same as view.height, actual number of pixels in the canvas by vertical.
   * @default 600
   */
  get height() {
    return this.view.texture.frame.height;
  }
  // NOTE: this was `view` in v7
  /**
   * The canvas element that everything is drawn to.
   * @type {environment.ICanvas}
   */
  get canvas() {
    return this.view.canvas;
  }
  /**
   * the last object rendered by the renderer. Useful for other plugins like interaction managers
   * @readonly
   */
  get lastObjectRendered() {
    return this._lastObjectRendered;
  }
  /**
   * Flag if we are rendering to the screen vs renderTexture
   * @readonly
   * @default true
   */
  get renderingToScreen() {
    const renderer = this;
    return renderer.renderTarget.renderingToScreen;
  }
  /**
   * Measurements of the screen. (0, 0, screenWidth, screenHeight).
   *
   * Its safe to use as filterArea or hitArea for the whole stage.
   */
  get screen() {
    return this.view.screen;
  }
  /**
   * Create a bunch of runners based of a collection of ids
   * @param runnerIds - the runner ids to add
   */
  _addRunners(...runnerIds) {
    runnerIds.forEach((runnerId) => {
      this.runners[runnerId] = new SystemRunner(runnerId);
    });
  }
  _addSystems(systems) {
    let i;
    for (i in systems) {
      const val = systems[i];
      this._addSystem(val.value, val.name);
    }
  }
  /**
   * Add a new system to the renderer.
   * @param ClassRef - Class reference
   * @param name - Property name for system, if not specified
   *        will use a static `name` property on the class itself. This
   *        name will be assigned as s property on the Renderer so make
   *        sure it doesn't collide with properties on Renderer.
   * @returns Return instance of renderer
   */
  _addSystem(ClassRef, name) {
    const system = new ClassRef(this);
    if (this[name]) {
      throw new Error(`Whoops! The name "${name}" is already in use`);
    }
    this[name] = system;
    this._systemsHash[name] = system;
    for (const i in this.runners) {
      this.runners[i].add(system);
    }
    return this;
  }
  _addPipes(pipes, pipeAdaptors) {
    const adaptors = pipeAdaptors.reduce((acc, adaptor) => {
      acc[adaptor.name] = adaptor.value;
      return acc;
    }, {});
    pipes.forEach((pipe) => {
      const PipeClass = pipe.value;
      const name = pipe.name;
      const Adaptor = adaptors[name];
      this.renderPipes[name] = new PipeClass(
        this,
        Adaptor ? new Adaptor() : null
      );
      this.runners.destroy.add(this.renderPipes[name]);
    });
  }
  destroy(options = false) {
    this.runners.destroy.items.reverse();
    this.runners.destroy.emit(options);
    if (options === true || typeof options === "object" && options.releaseGlobalResources) {
      GlobalResourceRegistry.release();
    }
    Object.values(this.runners).forEach((runner) => {
      runner.destroy();
    });
    this._systemsHash = null;
    this.renderPipes = null;
  }
  /**
   * Generate a texture from a container.
   * @param options - options or container target to use when generating the texture
   * @returns a texture
   */
  generateTexture(options) {
    return this.textureGenerator.generateTexture(options);
  }
  /**
   * Whether the renderer will round coordinates to whole pixels when rendering.
   * Can be overridden on a per scene item basis.
   */
  get roundPixels() {
    return !!this._roundPixels;
  }
  /**
   * Overridable function by `pixi.js/unsafe-eval` to silence
   * throwing an error if platform doesn't support unsafe-evals.
   * @private
   * @ignore
   */
  _unsafeEvalCheck() {
    if (!unsafeEvalSupported()) {
      throw new Error("Current environment does not allow unsafe-eval, please use pixi.js/unsafe-eval module to enable support.");
    }
  }
  /**
   * Resets the rendering state of the renderer.
   * This is useful when you want to use the WebGL context directly and need to ensure PixiJS's internal state
   * stays synchronized. When modifying the WebGL context state externally, calling this method before the next Pixi
   * render will reset all internal caches and ensure it executes correctly.
   *
   * This is particularly useful when combining PixiJS with other rendering engines like Three.js:
   * ```js
   * // Reset Three.js state
   * threeRenderer.resetState();
   *
   * // Render a Three.js scene
   * threeRenderer.render(threeScene, threeCamera);
   *
   * // Reset PixiJS state since Three.js modified the WebGL context
   * pixiRenderer.resetState();
   *
   * // Now render Pixi content
   * pixiRenderer.render(pixiScene);
   * ```
   * @advanced
   */
  resetState() {
    this.runners.resetState.emit();
  }
};
/** The default options for the renderer. */
_AbstractRenderer.defaultOptions = {
  /**
   * Default resolution / device pixel ratio of the renderer.
   * @default 1
   */
  resolution: 1,
  /**
   * Should the `failIfMajorPerformanceCaveat` flag be enabled as a context option used in the `isWebGLSupported`
   * function. If set to true, a WebGL renderer can fail to be created if the browser thinks there could be
   * performance issues when using WebGL.
   *
   * In PixiJS v6 this has changed from true to false by default, to allow WebGL to work in as many
   * scenarios as possible. However, some users may have a poor experience, for example, if a user has a gpu or
   * driver version blacklisted by the
   * browser.
   *
   * If your application requires high performance rendering, you may wish to set this to false.
   * We recommend one of two options if you decide to set this flag to false:
   *
   * 1: Use the Canvas renderer as a fallback in case high performance WebGL is
   *    not supported.
   *
   * 2: Call `isWebGLSupported` (which if found in the utils package) in your code before attempting to create a
   *    PixiJS renderer, and show an error message to the user if the function returns false, explaining that their
   *    device & browser combination does not support high performance WebGL.
   *    This is a much better strategy than trying to create a PixiJS renderer and finding it then fails.
   * @default false
   */
  failIfMajorPerformanceCaveat: false,
  /**
   * Should round pixels be forced when rendering?
   * @default false
   */
  roundPixels: false
};
let AbstractRenderer = _AbstractRenderer;

let _isWebGLSupported;
function isWebGLSupported(failIfMajorPerformanceCaveat) {
  if (_isWebGLSupported !== void 0)
    return _isWebGLSupported;
  _isWebGLSupported = (() => {
    const contextOptions = {
      stencil: true,
      failIfMajorPerformanceCaveat: failIfMajorPerformanceCaveat ?? AbstractRenderer.defaultOptions.failIfMajorPerformanceCaveat
    };
    try {
      if (!DOMAdapter.get().getWebGLRenderingContext()) {
        return false;
      }
      const canvas = DOMAdapter.get().createCanvas();
      let gl = canvas.getContext("webgl", contextOptions);
      const success = !!gl?.getContextAttributes()?.stencil;
      if (gl) {
        const loseContext = gl.getExtension("WEBGL_lose_context");
        if (loseContext) {
          loseContext.loseContext();
        }
      }
      gl = null;
      return success;
    } catch (_e) {
      return false;
    }
  })();
  return _isWebGLSupported;
}

let _isWebGPUSupported;
async function isWebGPUSupported(options = {}) {
  if (_isWebGPUSupported !== void 0)
    return _isWebGPUSupported;
  _isWebGPUSupported = await (async () => {
    const gpu = DOMAdapter.get().getNavigator().gpu;
    if (!gpu) {
      return false;
    }
    try {
      const adapter = await gpu.requestAdapter(options);
      await adapter.requestDevice();
      return true;
    } catch (_e) {
      return false;
    }
  })();
  return _isWebGPUSupported;
}

const renderPriority = ["webgl", "webgpu", "canvas"];
async function autoDetectRenderer(options) {
  let preferredOrder = [];
  if (options.preference) {
    preferredOrder.push(options.preference);
    renderPriority.forEach((item) => {
      if (item !== options.preference) {
        preferredOrder.push(item);
      }
    });
  } else {
    preferredOrder = renderPriority.slice();
  }
  let RendererClass;
  let finalOptions = {};
  for (let i = 0; i < preferredOrder.length; i++) {
    const rendererType = preferredOrder[i];
    if (rendererType === "webgpu" && await isWebGPUSupported()) {
      const { WebGPURenderer } = await __vitePreload(async () => { const { WebGPURenderer } = await import('./WebGPURenderer-CFeMiLws.js');return { WebGPURenderer }},true              ?__vite__mapDeps([54,2,55,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53]):void 0,import.meta.url);
      RendererClass = WebGPURenderer;
      finalOptions = { ...options, ...options.webgpu };
      break;
    } else if (rendererType === "webgl" && isWebGLSupported(
      options.failIfMajorPerformanceCaveat ?? AbstractRenderer.defaultOptions.failIfMajorPerformanceCaveat
    )) {
      const { WebGLRenderer } = await __vitePreload(async () => { const { WebGLRenderer } = await import('./WebGLRenderer-C-oCJCbi.js');return { WebGLRenderer }},true              ?__vite__mapDeps([56,2,55,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53]):void 0,import.meta.url);
      RendererClass = WebGLRenderer;
      finalOptions = { ...options, ...options.webgl };
      break;
    } else if (rendererType === "canvas") {
      finalOptions = { ...options };
      throw new Error("CanvasRenderer is not yet implemented");
    }
  }
  delete finalOptions.webgpu;
  delete finalOptions.webgl;
  if (!RendererClass) {
    throw new Error("No available renderer for the current environment");
  }
  const renderer = new RendererClass();
  await renderer.init(finalOptions);
  return renderer;
}

const VERSION = "8.15.0";

class ApplicationInitHook {
  static init() {
    globalThis.__PIXI_APP_INIT__?.(this, VERSION);
  }
  static destroy() {
  }
}
/** @ignore */
ApplicationInitHook.extension = ExtensionType.Application;
class RendererInitHook {
  constructor(renderer) {
    this._renderer = renderer;
  }
  init() {
    globalThis.__PIXI_RENDERER_INIT__?.(this._renderer, VERSION);
  }
  destroy() {
    this._renderer = null;
  }
}
/** @ignore */
RendererInitHook.extension = {
  type: [
    ExtensionType.WebGLSystem,
    ExtensionType.WebGPUSystem
  ],
  name: "initHook",
  priority: -10
};

const _Application = class _Application {
  constructor(...args) {
    /**
     * The root display container for your application.
     * All visual elements should be added to this container or its children.
     * @example
     * ```js
     * // Create a sprite and add it to the stage
     * const sprite = Sprite.from('image.png');
     * app.stage.addChild(sprite);
     *
     * // Create a container for grouping objects
     * const container = new Container();
     * app.stage.addChild(container);
     * ```
     */
    this.stage = new Container();
    if (args[0] !== void 0) {
      deprecation(v8_0_0, "Application constructor options are deprecated, please use Application.init() instead.");
    }
  }
  /**
   * Initializes the PixiJS application with the specified options.
   *
   * This method must be called after creating a new Application instance.
   * @param options - Configuration options for the application and renderer
   * @returns A promise that resolves when initialization is complete
   * @example
   * ```js
   * const app = new Application();
   *
   * // Initialize with custom options
   * await app.init({
   *     width: 800,
   *     height: 600,
   *     backgroundColor: 0x1099bb,
   *     preference: 'webgl', // or 'webgpu'
   * });
   * ```
   */
  async init(options) {
    options = { ...options };
    this.stage || (this.stage = new Container());
    this.renderer = await autoDetectRenderer(options);
    _Application._plugins.forEach((plugin) => {
      plugin.init.call(this, options);
    });
  }
  /**
   * Renders the current stage to the screen.
   *
   * When using the default setup with {@link TickerPlugin} (enabled by default), you typically don't need to call
   * this method directly as rendering is handled automatically.
   *
   * Only use this method if you've disabled the {@link TickerPlugin} or need custom
   * render timing control.
   * @example
   * ```js
   * // Example 1: Default setup (TickerPlugin handles rendering)
   * const app = new Application();
   * await app.init();
   * // No need to call render() - TickerPlugin handles it
   *
   * // Example 2: Custom rendering loop (if TickerPlugin is disabled)
   * const app = new Application();
   * await app.init({ autoStart: false }); // Disable automatic rendering
   *
   * function animate() {
   *     app.render();
   *     requestAnimationFrame(animate);
   * }
   * animate();
   * ```
   */
  render() {
    this.renderer.render({ container: this.stage });
  }
  /**
   * Reference to the renderer's canvas element. This is the HTML element
   * that displays your application's graphics.
   * @readonly
   * @type {HTMLCanvasElement}
   * @example
   * ```js
   * // Create a new application
   * const app = new Application();
   * // Initialize the application
   * await app.init({...});
   * // Add canvas to the page
   * document.body.appendChild(app.canvas);
   *
   * // Access the canvas directly
   * console.log(app.canvas); // HTMLCanvasElement
   * ```
   */
  get canvas() {
    return this.renderer.canvas;
  }
  /**
   * Reference to the renderer's canvas element.
   * @type {HTMLCanvasElement}
   * @deprecated since 8.0.0
   * @see {@link Application#canvas}
   */
  get view() {
    deprecation(v8_0_0, "Application.view is deprecated, please use Application.canvas instead.");
    return this.renderer.canvas;
  }
  /**
   * Reference to the renderer's screen rectangle. This represents the visible area of your application.
   *
   * It's commonly used for:
   * - Setting filter areas for full-screen effects
   * - Defining hit areas for screen-wide interaction
   * - Determining the visible bounds of your application
   * @readonly
   * @example
   * ```js
   * // Use as filter area for a full-screen effect
   * const blurFilter = new BlurFilter();
   * sprite.filterArea = app.screen;
   *
   * // Use as hit area for screen-wide interaction
   * const screenSprite = new Sprite();
   * screenSprite.hitArea = app.screen;
   *
   * // Get screen dimensions
   * console.log(app.screen.width, app.screen.height);
   * ```
   * @see {@link Rectangle} For all available properties and methods
   */
  get screen() {
    return this.renderer.screen;
  }
  /**
   * Destroys the application and all of its resources.
   *
   * This method should be called when you want to completely
   * clean up the application and free all associated memory.
   * @param rendererDestroyOptions - Options for destroying the renderer:
   *  - `false` or `undefined`: Preserves the canvas element (default)
   *  - `true`: Removes the canvas element
   *  - `{ removeView: boolean }`: Object with removeView property to control canvas removal
   * @param options - Options for destroying the application:
   *  - `false` or `undefined`: Basic cleanup (default)
   *  - `true`: Complete cleanup including children
   *  - Detailed options object:
   *    - `children`: Remove children
   *    - `texture`: Destroy textures
   *    - `textureSource`: Destroy texture sources
   *    - `context`: Destroy WebGL context
   * @example
   * ```js
   * // Basic cleanup
   * app.destroy();
   *
   * // Remove canvas and do complete cleanup
   * app.destroy(true, true);
   *
   * // Remove canvas with explicit options
   * app.destroy({ removeView: true }, true);
   *
   * // Detailed cleanup with specific options
   * app.destroy(
   *     { removeView: true },
   *     {
   *         children: true,
   *         texture: true,
   *         textureSource: true,
   *         context: true
   *     }
   * );
   * ```
   * > [!WARNING] After calling destroy, the application instance should no longer be used.
   * > All properties will be null and further operations will throw errors.
   */
  destroy(rendererDestroyOptions = false, options = false) {
    const plugins = _Application._plugins.slice(0);
    plugins.reverse();
    plugins.forEach((plugin) => {
      plugin.destroy.call(this);
    });
    this.stage.destroy(options);
    this.stage = null;
    this.renderer.destroy(rendererDestroyOptions);
    this.renderer = null;
  }
};
/**
 * Collection of installed plugins.
 * @internal
 */
_Application._plugins = [];
let Application = _Application;
extensions.handleByList(ExtensionType.Application, Application._plugins);
extensions.add(ApplicationInitHook);

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) {
    this.emit("destroy", this);
    this.removeAllListeners();
    for (const i in this.chars) {
      this.chars[i].texture?.destroy();
    }
    this.chars = null;
    if (destroyTextures) {
      this.pages.forEach((page) => page.texture.destroy(true));
      this.pages = null;
    }
  }
}

/**
 * tiny-lru
 *
 * @copyright 2026 Jason Mulligan <jason.mulligan@avoidwork.com>
 * @license BSD-3-Clause
 * @version 11.4.7
 */
/**
 * 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<*>>} 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()) {
		const result = new Array(keys.length);
		for (let i = 0; i < keys.length; i++) {
			const key = keys[i];
			result[i] = [key, this.get(key)];
		}

		return result;
	}

	/**
	 * 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 = new Array(this.size);
		let x = this.first;
		let i = 0;

		while (x !== null) {
			result[i++] = 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()) {
		const result = new Array(keys.length);
		for (let i = 0; i < keys.length; i++) {
			result[i] = this.get(keys[i]);
		}

		return result;
	}
}

/**
 * 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);
}

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(",")}`;
}

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) {
    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 = style._stroke?.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?.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?.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;

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("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 = [];
    let options = ensureGradientOptions(args);
    const defaults = options.type === "radial" ? _FillGradient.defaultRadialOptions : _FillGradient.defaultLinearOptions;
    options = { ...defaults, ...definedProps(options) };
    this._textureSize = options.textureSize;
    this._wrapMode = options.wrapMode;
    if (options.type === "radial") {
      this.center = options.center;
      this.outerCenter = options.outerCenter ?? 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() {
    this.texture?.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) {
  let options = args[0] ?? {};
  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: args[5] ?? FillGradient.defaultLinearOptions.textureSize
    };
  }
  return options;
}

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("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;
var hasRequiredParseSvgPath;

function requireParseSvgPath () {
	if (hasRequiredParseSvgPath) return parseSvgPath;
	hasRequiredParseSvgPath = 1;
	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) : []
	}
	return parseSvgPath;
}

var parseSvgPathExports = requireParseSvgPath();
const parse = /*@__PURE__*/getDefaultExportFromCjs(parseSvgPathExports);

function parseSVGPath(svgPath, path) {
  const commands = parse(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;
}

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}]`;
  }
}

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}]`;
  }
}

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;
}

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];
  }
}

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}]`;
  }
}

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;
}

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.uint32View = null;
    this.float32View = null;
    this.uint16View = null;
    this._int8View = null;
    this._uint8View = null;
    this._int16View = null;
    this._int32View = null;
    this._float64Array = null;
    this._bigUint64Array = 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`);
    }
  }
}

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);
  }
}

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 || {});

function getAdjustedBlendModeBlend(blendMode, textureSource) {
  if (textureSource.alphaMode === "no-premultiply-alpha") {
    return BLEND_TO_NPM[blendMode] || blendMode;
  }
  return blendMode;
}

const fragTemplate = [
  "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.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;
}

let maxTexturesPerBatchCache = null;
function getMaxTexturesPerBatch() {
  if (maxTexturesPerBatchCache)
    return maxTexturesPerBatchCache;
  const gl = getTestContext();
  maxTexturesPerBatchCache = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
  maxTexturesPerBatchCache = checkMaxIfStatementsInShader(
    maxTexturesPerBatchCache,
    gl
  );
  gl.getExtension("WEBGL_lose_context")?.loseContext();
  return maxTexturesPerBatchCache;
}

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;
  }
}

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("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 = { ..._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;
    }
  }
  /**
   * Destroys the batch and its resources.
   * @param options - destruction options
   * @param options.shader - whether to destroy the associated shader
   */
  destroy(options = {}) {
    if (this.batches === null)
      return;
    for (let i = 0; i < this.batchIndex; i++) {
      returnBatchToPool(this.batches[i]);
    }
    this.batches = null;
    this.geometry.destroy(true);
    this.geometry = null;
    if (options.shader) {
      this.shader?.destroy();
      this.shader = 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;

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 || {});

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
     */
    /** @internal */
    this._gpuData = /* @__PURE__ */ Object.create(null);
    /** @internal */
    this._gcLastUsed = -1;
    /** If set to true, the buffer will be garbage collected automatically when it is not used. */
    this.autoGarbageCollect = true;
    /** a unique id for this uniform group used through the renderer */
    this.uid = uid("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("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 ?? (size = data?.byteLength);
    const mappedAtCreation = !!data;
    this.descriptor = {
      size,
      usage,
      mappedAtCreation,
      label
    };
    this.shrinkToFit = 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("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 ?? this._updateSize;
    this._updateID++;
    this.emit("update", this);
  }
  /** Unloads the buffer from the GPU */
  unload() {
    this.emit("unload", this);
    for (const key in this._gpuData) {
      this._gpuData[key]?.destroy();
    }
    this._gpuData = /* @__PURE__ */ Object.create(null);
  }
  /** Destroys the buffer */
  destroy() {
    this.destroyed = true;
    this.unload();
    this.emit("destroy", this);
    this.emit("change", this);
    this._data = null;
    this.descriptor = null;
    this.removeAllListeners();
  }
}

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;
}

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;
}

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 = {}) {
    super();
    /** @internal */
    this._gpuData = /* @__PURE__ */ Object.create(null);
    /** If set to true, the resource will be garbage collected automatically when it is not used. */
    this.autoGarbageCollect = true;
    /** @internal */
    this._gcLastUsed = -1;
    /** The unique id of the geometry. */
    this.uid = uid("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 = options.instanceCount ?? 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);
  }
  /** Unloads the geometry from the GPU. */
  unload() {
    this.emit("unload", this);
    for (const key in this._gpuData) {
      this._gpuData[key]?.destroy();
    }
    this._gpuData = /* @__PURE__ */ Object.create(null);
  }
  /**
   * 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.unload();
    this.indexBuffer?.destroy();
    this.attributes = null;
    this.buffers = null;
    this.indexBuffer = null;
    this._bounds = null;
  }
}

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
    });
  }
}

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`);
      }
    }
  }
}

const findHooksRx = /\{\{(.*?)\}\}/g;
function compileHooks(programSrc) {
  const parts = {};
  const partMatches = programSrc.match(findHooksRx)?.map((hook) => hook.replace(/[{()}]/g, "")) ?? [];
  partMatches.forEach((hook) => {
    parts[hook] = [];
  });
  return parts;
}

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;
}

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;
}

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;
}

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)
  };
}

const vertexGPUTemplate = (
  /* wgsl */
  `
    @in aPosition: vec2<f32>;
    @in aUV: vec2<f32>;

    @out @builtin(position) vPosition: vec4<f32>;
    @out vUV : vec2<f32>;
    @out vColor : vec4<f32>;

    {{header}}

    struct VSOutput {
        {{struct}}
    };

    @vertex
    fn main( {{in}} ) -> VSOutput {

        var worldTransformMatrix = globalUniforms.uWorldTransformMatrix;
        var modelMatrix = mat3x3<f32>(
            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<f32>(1., 1., 1., 1.);

        {{main}}

        vUV = uv;

        var modelViewProjectionMatrix = globalUniforms.uProjectionMatrix * worldTransformMatrix * modelMatrix;

        vPosition =  vec4<f32>((modelViewProjectionMatrix *  vec3<f32>(position, 1.0)).xy, 0.0, 1.0);

        vColor *= globalUniforms.uWorldColorAlpha;

        {{end}}

        {{return}}
    };
`
);
const fragmentGPUTemplate = (
  /* wgsl */
  `
    @in vUV : vec2<f32>;
    @in vColor : vec4<f32>;

    {{header}}

    @fragment
    fn main(
        {{in}}
      ) -> @location(0) vec4<f32> {

        {{start}}

        var outColor:vec4<f32>;

        {{main}}

        var finalColor:vec4<f32> = 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}}
    }
`
);

const globalUniformsBit = {
  name: "global-uniforms-bit",
  vertex: {
    header: (
      /* wgsl */
      `
        struct GlobalUniforms {
            uProjectionMatrix:mat3x3<f32>,
            uWorldTransformMatrix:mat3x3<f32>,
            uWorldColorAlpha: vec4<f32>,
            uResolution: vec2<f32>,
        }

        @group(0) @binding(0) var<uniform> globalUniforms : GlobalUniforms;
        `
    )
  }
};
const globalUniformsBitGl = {
  name: "global-uniforms-bit",
  vertex: {
    header: (
      /* glsl */
      `
          uniform mat3 uProjectionMatrix;
          uniform mat3 uWorldTransformMatrix;
          uniform vec4 uWorldColorAlpha;
          uniform vec2 uResolution;
        `
    )
  }
};

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({
    name,
    ...compileHighShaderGl({
      template: {
        vertex: vertexGlTemplate,
        fragment: fragmentGlTemplate
      },
      bits: [
        globalUniformsBitGl,
        ...bits
      ]
    })
  });
}

const colorBit = {
  name: "color-bit",
  vertex: {
    header: (
      /* wgsl */
      `
            @in aColor: vec4<f32>;
        `
    ),
    main: (
      /* wgsl */
      `
            vColor *= vec4<f32>(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);
        `
    )
  }
};

const textureBatchBitGpuCache = {};
function generateBindingSrc(maxTextures) {
  const src = [];
  if (maxTextures === 1) {
    src.push("@group(1) @binding(0) var textureSource1: texture_2d<f32>;");
    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<f32>;`);
      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<u32>;
                @out @interpolate(flat) vTextureId : u32;
            `,
        main: `
                vTextureId = aTextureIdAndRound.y;
            `,
        end: `
                if(aTextureIdAndRound.x == 1)
                {
                    vPosition = vec4<f32>(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];
}

const roundPixelsBit = {
  name: "round-pixels-bit",
  vertex: {
    header: (
      /* wgsl */
      `
            fn roundPixels(position: vec2<f32>, targetSize: vec2<f32>) -> vec2<f32>
            {
                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;
            }
        `
    )
  }
};

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;
}

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)
      }
    });
    this.maxTextures = maxTextures;
  }
}

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 ?? (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;
  }
  /**
   * Updates the maximum number of textures that can be used in the shader.
   * @param maxTextures - The maximum number of textures that can be used in the shader.
   * @internal
   */
  _updateMaxTextures(maxTextures) {
    if (this.shader.maxTextures === maxTextures)
      return;
    defaultShader = new DefaultShader(maxTextures);
    this.shader = defaultShader;
  }
  destroy() {
    this.shader = null;
    super.destroy();
  }
};
/** @ignore */
_DefaultBatcher.extension = {
  type: [
    ExtensionType.Batcher
  ],
  name: "default"
};
let DefaultBatcher = _DefaultBatcher;

class GCManagedHash {
  constructor(options) {
    // Exposed directly for GC system access
    this.items = /* @__PURE__ */ Object.create(null);
    const { renderer, type, onUnload, priority, name } = options;
    this._renderer = renderer;
    renderer.gc.addResourceHash(this, "items", type, priority ?? 0);
    this._onUnload = onUnload;
    this.name = name;
  }
  /**
   * Add an item to the hash. No-op if already added.
   * @param item
   * @returns true if the item was added, false if it was already in the hash
   */
  add(item) {
    if (this.items[item.uid])
      return false;
    this.items[item.uid] = item;
    item.once("unload", this.remove, this);
    item._gcLastUsed = this._renderer.gc.now;
    return true;
  }
  remove(item, ...args) {
    if (!this.items[item.uid])
      return;
    const gpuData = item._gpuData[this._renderer.uid];
    if (!gpuData)
      return;
    this._onUnload?.(item, ...args);
    gpuData.destroy();
    item._gpuData[this._renderer.uid] = null;
    this.items[item.uid] = null;
  }
  removeAll(...args) {
    Object.values(this.items).forEach((item) => item && this.remove(item, ...args));
  }
  destroy(...args) {
    this.removeAll(...args);
    this.items = /* @__PURE__ */ Object.create(null);
    this._renderer = null;
    this._onUnload = null;
  }
}

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++;
  }
}

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;
  }
}

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() {
    return this.renderable?.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 = null;
    this._batch = null;
  }
}

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 = { ...buildCircle, extension: { ...buildCircle.extension, name: "ellipse" } };
const buildRoundedRectangle = { ...buildCircle, extension: { ...buildCircle.extension, name: "roundedRectangle" } };

const closePointEps = 1e-4;
const curveEps = 1e-4;

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;
}

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);
    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);
  }
}

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 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;
  }
}

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);
  }
};

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;
  }
};

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;
  }
};

const tempTextureMatrix$1 = new Matrix();
const tempRect$1 = 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$1);
    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;
}

const shapeBuilders = {};
extensions.handleByMap(ExtensionType.ShapeBuilder, shapeBuilders);
extensions.add(buildRectangle, buildPolygon, buildTriangle, buildCircle, buildEllipse, buildRoundedRectangle);
const tempRect = 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;
  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 }) => {
    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 = shape.closePath ?? 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;
}

class GpuGraphicsContext {
  constructor() {
    this.batches = [];
    this.geometryData = {
      vertices: [],
      uvs: [],
      indices: []
    };
  }
  reset() {
    if (this.batches) {
      this.batches.forEach((batch) => {
        BigPool.return(batch);
      });
    }
    if (this.graphicsData) {
      BigPool.return(this.graphicsData);
    }
    this.isBatchable = false;
    this.context = null;
    this.batches.length = 0;
    this.geometryData.indices.length = 0;
    this.geometryData.vertices.length = 0;
    this.geometryData.uvs.length = 0;
    this.graphicsData = null;
  }
  destroy() {
    this.reset();
    this.batches = null;
    this.geometryData = null;
  }
}
class GraphicsContextRenderData {
  constructor() {
    this.instructions = new InstructionSet();
  }
  init(options) {
    const maxTextures = options.maxTextures;
    this.batcher ? this.batcher._updateMaxTextures(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) {
    this._renderer = renderer;
    this._managedContexts = new GCManagedHash({ renderer, type: "resource", name: "graphicsContext" });
  }
  /**
   * Runner init called, update the default options
   * @ignore
   */
  init(options) {
    _GraphicsContextSystem.defaultOptions.bezierSmoothness = options?.bezierSmoothness ?? _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 context._gpuData[this._renderer.uid].graphicsData || 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) {
    const hasContext = !!context._gpuData[this._renderer.uid];
    const gpuContext = context._gpuData[this._renderer.uid] || this._initContext(context);
    if (context.dirty || !hasContext) {
      if (hasContext) {
        gpuContext.reset();
      }
      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 context._gpuData[this._renderer.uid] || this._initContext(context);
  }
  _initContextRenderData(context) {
    const graphicsData = BigPool.get(GraphicsContextRenderData, {
      maxTextures: this._renderer.limits.maxBatchableTextures
    });
    const gpuContext = context._gpuData[this._renderer.uid];
    const { batches, geometryData } = gpuContext;
    gpuContext.graphicsData = graphicsData;
    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
      );
    }
    return graphicsData;
  }
  _initContext(context) {
    const gpuContext = new GpuGraphicsContext();
    gpuContext.context = context;
    context._gpuData[this._renderer.uid] = gpuContext;
    this._managedContexts.add(context);
    return gpuContext;
  }
  destroy() {
    this._managedContexts.destroy();
    this._renderer = null;
  }
};
/** @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;

const RECURSION_LIMIT$1 = 8;
const FLT_EPSILON$1 = 11920929e-14;
const PATH_DISTANCE_EPSILON$1 = 1;
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 ?? 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 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);
    if (d2 > FLT_EPSILON$1 && d3 > FLT_EPSILON$1) {
      if ((d2 + d3) * (d2 + d3) <= distanceTolerance * (dx * dx + dy * dy)) {
        {
          points.push(x1234, y1234);
          return;
        }
      }
    } else if (d2 > FLT_EPSILON$1) {
      if (d2 * d2 <= distanceTolerance * (dx * dx + dy * dy)) {
        {
          points.push(x1234, y1234);
          return;
        }
      }
    } else if (d3 > FLT_EPSILON$1) {
      if (d3 * d3 <= distanceTolerance * (dx * dx + dy * dy)) {
        {
          points.push(x1234, y1234);
          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);
}

const RECURSION_LIMIT = 8;
const FLT_EPSILON = 11920929e-14;
const PATH_DISTANCE_EPSILON = 1;
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 ?? 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 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)) {
      {
        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);
}

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;
  }
}

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
  );
}

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;
  }
}

function roundedShapeArc(g, points, radius) {
  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 = p2.radius ?? 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) {
  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 = thisPoint.radius ?? 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);
  }
}

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?.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;
  }
}

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("graphicsPath");
    this._dirty = true;
    this.checkForHoles = signed;
    if (typeof instructions === "string") {
      parseSVGPath(instructions, this);
    } else {
      this.instructions = instructions?.slice() ?? [];
    }
  }
  /**
   * 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();
}

function parseSVGFloatAttribute(svg, id, defaultValue) {
  const value = svg.getAttribute(id);
  return value ? Number(value) : defaultValue;
}

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();
          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);
}

function extractSvgUrlId(url) {
  const match = url.match(/url\s*\(\s*['"]?\s*#([^'"\s)]+)\s*['"]?\s*\)/i);
  return match ? match[1] : "";
}

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;
  }
}

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 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);
  }
}

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 = { ...fillStyle, ...f1 };
  } else if (f1) {
    fillStyle = f1;
  }
  if (s1 && strokeStyle) {
    strokeStyle = { ...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);
  }
}

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 ?? 0);
  fill.color = temp.toNumber();
  fill.alpha = temp.alpha === 1 ? defaultStyle.alpha : temp.alpha;
  fill.texture = Texture.WHITE;
  return { ...defaultStyle, ...fill };
}
function handleTexture(fill, value, defaultStyle) {
  fill.texture = value;
  return { ...defaultStyle, ...fill };
}
function handleFillPattern(fill, value, defaultStyle) {
  fill.fill = value;
  fill.color = 16777215;
  fill.texture = value.texture;
  fill.matrix = value.transform;
  return { ...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 { ...defaultStyle, ...fill };
}
function handleFillObject(value, defaultStyle) {
  const style = { ...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 { width, alignment, miterLimit, cap, join, pixelLine, ...rest } = defaultStyle;
  const fill = toFillStyle(value, rest);
  if (!fill) {
    return null;
  }
  return {
    width,
    alignment,
    miterLimit,
    cap,
    join,
    pixelLine,
    ...fill
  };
}

const tmpPoint = new Point();
const tempMatrix = new Matrix();
const _GraphicsContext = class _GraphicsContext extends EventEmitter {
  constructor() {
    super(...arguments);
    /** @internal */
    this._gpuData = /* @__PURE__ */ Object.create(null);
    /** If set to true, the resource will be garbage collected automatically when it is not used. */
    this.autoGarbageCollect = true;
    /** @internal */
    this._gcLastUsed = -1;
    /**
     * unique id for this graphics context
     * @internal
     */
    this.uid = uid("graphicsContext");
    /**
     * Indicates whether content is updated and have to be re-rendered.
     * @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 = [];
    /** Whether the graphics context has been destroyed. */
    this.destroyed = false;
    this._activePath = new GraphicsPath();
    this._transform = new Matrix();
    this._fillStyle = { ..._GraphicsContext.defaultFillStyle };
    this._strokeStyle = { ..._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 = { ...this._fillStyle };
    clone._strokeStyle = { ...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?.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?.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() {
    this._tick++;
    this._activePath?.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: { ...this._fillStyle },
      strokeStyle: { ...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.set(a, b, c, d, dx, dy);
    this._transform.append(tempMatrix);
    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() {
    this._boundsDirty = true;
    if (this.dirty)
      return;
    this.emit("update", this, 16);
    this.dirty = 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) {
    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 = holes.shapePath?.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;
  }
  /** Unloads the GPU data from the graphics context. */
  unload() {
    this.emit("unload", this);
    for (const key in this._gpuData) {
      this._gpuData[key]?.destroy();
    }
    this._gpuData = /* @__PURE__ */ Object.create(null);
  }
  /**
   * 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) {
    if (this.destroyed)
      return;
    this.destroyed = true;
    this._stateStack.length = 0;
    this._transform = null;
    this.unload();
    this.emit("destroy", this);
    this.removeAllListeners();
    const destroyTexture = typeof options === "boolean" ? options : options?.texture;
    if (destroyTexture) {
      const destroyTextureSource = typeof options === "boolean" ? options : 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;

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("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 = { ..._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) {
    if (this._align === value)
      return;
    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) {
    if (this._breakWords === value)
      return;
    this._breakWords = value;
    this.update();
  }
  /** Set a drop shadow for the text. */
  get dropShadow() {
    return this._dropShadow;
  }
  set dropShadow(value) {
    if (this._dropShadow === value)
      return;
    if (value !== null && typeof value === "object") {
      this._dropShadow = this._createProxy({ ..._TextStyle.defaultDropShadow, ...value });
    } else {
      this._dropShadow = value ? this._createProxy({ ..._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) {
    if (this._fontFamily === value)
      return;
    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 (this._fontSize === value)
      return;
    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) {
    if (this._fontStyle === value)
      return;
    this._fontStyle = value.toLowerCase();
    this.update();
  }
  /**
   * The font variant.
   * @type {'normal'|'small-caps'}
   */
  get fontVariant() {
    return this._fontVariant;
  }
  set fontVariant(value) {
    if (this._fontVariant === value)
      return;
    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) {
    if (this._fontWeight === value)
      return;
    this._fontWeight = value;
    this.update();
  }
  /** The space between lines. */
  get leading() {
    return this._leading;
  }
  set leading(value) {
    if (this._leading === value)
      return;
    this._leading = value;
    this.update();
  }
  /** The amount of spacing between letters, default is 0. */
  get letterSpacing() {
    return this._letterSpacing;
  }
  set letterSpacing(value) {
    if (this._letterSpacing === value)
      return;
    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) {
    if (this._lineHeight === value)
      return;
    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) {
    if (this._padding === value)
      return;
    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) {
    if (this._filters === value)
      return;
    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) {
    if (this._trim === value)
      return;
    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) {
    if (this._textBaseline === value)
      return;
    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) {
    if (this._whiteSpace === value)
      return;
    this._whiteSpace = value;
    this.update();
  }
  /** Indicates if word wrap should be used. */
  get wordWrap() {
    return this._wordWrap;
  }
  set wordWrap(value) {
    if (this._wordWrap === value)
      return;
    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) {
    if (this._wordWrapWidth === value)
      return;
    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({ ...GraphicsContext.defaultFillStyle, ...value }, () => {
        this._fill = toFillStyle(
          { ...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({ ...GraphicsContext.defaultStrokeStyle, ...value }, () => {
        this._stroke = toStrokeStyle(
          { ...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 ? { ...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) {
    this.removeAllListeners();
    const destroyTexture = typeof options === "boolean" ? options : options?.texture;
    if (destroyTexture) {
      const destroyTextureSource = typeof options === "boolean" ? options : options?.textureSource;
      if (this._fill?.texture) {
        this._fill.texture.destroy(destroyTextureSource);
      }
      if (this._originalFill?.texture) {
        this._originalFill.texture.destroy(destroyTextureSource);
      }
      if (this._stroke?.texture) {
        this._stroke.texture.destroy(destroyTextureSource);
      }
      if (this._originalStroke?.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) => {
        if (target[property] === newValue)
          return true;
        target[property] = newValue;
        cb?.(property, newValue);
        this.update();
        return true;
      }
    });
  }
  _isFillStyle(value) {
    return (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) {
  const oldStyle = style;
  if (typeof oldStyle.dropShadow === "boolean" && oldStyle.dropShadow) {
    const defaults = TextStyle.defaultDropShadow;
    style.dropShadow = {
      alpha: oldStyle.dropShadowAlpha ?? defaults.alpha,
      angle: oldStyle.dropShadowAngle ?? defaults.angle,
      blur: oldStyle.dropShadowBlur ?? defaults.blur,
      color: oldStyle.dropShadowColor ?? defaults.color,
      distance: oldStyle.dropShadowDistance ?? 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 = {
      ...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
    };
  }
}

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);

const PRECISION = 1e5;
function getCanvasFillStyle(fillStyle, context, textMetrics, padding = 0) {
  if (fillStyle.texture === Texture.WHITE && !fillStyle.fill) {
    return Color.shared.setValue(fillStyle.color).setAlpha(fillStyle.alpha ?? 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";
}

const _DynamicBitmapFont = class _DynamicBitmapFont extends AbstractBitmapFont {
  /**
   * @param options - The options for the dynamic bitmap font.
   */
  constructor(options) {
    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 = { ..._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 = dynamicOptions.skipKerning ?? false;
    this.resolution = dynamicOptions.resolution ?? 1;
    this._padding = dynamicOptions.padding ?? 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) {
    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 - (style.dropShadow?.distance ?? 0) - (style._stroke?.width ?? 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) {
    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 = stroke?.width ?? 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) {
    const char = metrics.text;
    const fontProperties = metrics.fontProperties;
    const stroke = style._stroke;
    const strokeThickness = (stroke?.width ?? 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;

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 = {
    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;
    }
  }
}

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;
}

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) {
    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({
        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);
    dynamicFont.ensureCharacters?.(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) {
    let options = args[0];
    if (typeof options === "string") {
      options = {
        name: options,
        style: args[1],
        chars: args[2]?.chars,
        resolution: args[2]?.resolution,
        padding: args[2]?.padding,
        skipKerning: args[2]?.skipKerning
      };
      deprecation(v8_0_0, "BitmapFontManager.install(name, style, options) is deprecated, use BitmapFontManager.install({name, style, ...options})");
    }
    const name = options?.name;
    if (!name) {
      throw new Error("[BitmapFontManager] Property `name` is required.");
    }
    options = { ...this.defaultOptions, ...options };
    const textStyle = options.style;
    const style = textStyle instanceof TextStyle ? textStyle : new TextStyle(textStyle);
    const overrideFill = options.dynamicFill ?? 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();

class BitmapFont extends AbstractBitmapFont {
  constructor(options, url) {
    super();
    const { textures, data } = options;
    Object.keys(data.pages).forEach((key) => {
      const pageData = data.pages[parseInt(key, 10)];
      const texture = textures[pageData.id];
      this.pages.push({ texture });
    });
    Object.keys(data.chars).forEach((key) => {
      const charData = data.chars[key];
      const {
        frame: textureFrame,
        source: textureSource,
        rotate: textureRotate
      } = textures[charData.page];
      const frame = groupD8.transformRectCoords(
        charData,
        textureFrame,
        textureRotate,
        new Rectangle()
      );
      const texture = new Texture({
        frame,
        orig: new Rectangle(0, 0, charData.width, charData.height),
        source: textureSource,
        rotate: textureRotate
      });
      this.chars[key] = {
        id: key.codePointAt(0),
        xOffset: charData.xOffset,
        yOffset: charData.yOffset,
        xAdvance: charData.xAdvance,
        kerning: charData.kerning ?? {},
        texture
      };
    });
    this.baseRenderedFontSize = data.fontSize;
    this.baseMeasurementFontSize = data.fontSize;
    this.fontMetrics = {
      ascent: 0,
      descent: 0,
      fontSize: data.fontSize
    };
    this.baseLineOffset = data.baseLineOffset;
    this.lineHeight = data.lineHeight;
    this.fontFamily = data.fontFamily;
    this.distanceField = data.distanceField ?? {
      type: "none",
      range: 0
    };
    this.url = url;
  }
  /** Destroys the BitmapFont object. */
  destroy() {
    super.destroy();
    for (let i = 0; i < this.pages.length; i++) {
      const { texture } = this.pages[i];
      texture.destroy(true);
    }
    this.pages = null;
  }
  /**
   * Generates and installs a bitmap font with the specified options.
   * The font will be cached and available for use in BitmapText objects.
   * @param options - Setup options for font generation
   * @returns Installed font instance
   * @example
   * ```ts
   * // Install a basic font
   * BitmapFont.install({
   *     name: 'Title',
   *     style: {
   *         fontFamily: 'Arial',
   *         fontSize: 32,
   *         fill: '#ffffff'
   *     }
   * });
   *
   * // Install with advanced options
   * BitmapFont.install({
   *     name: 'Custom',
   *     style: {
   *         fontFamily: 'Arial',
   *         fontSize: 24,
   *         fill: '#00ff00',
   *         stroke: { color: '#000000', width: 2 }
   *     },
   *     chars: [['a', 'z'], ['A', 'Z'], ['0', '9']],
   *     resolution: 2,
   *     padding: 4,
   *     textureStyle: {
   *         scaleMode: 'nearest'
   *     }
   * });
   * ```
   */
  static install(options) {
    BitmapFontManager.install(options);
  }
  /**
   * Uninstalls a bitmap font from the cache.
   * This frees up memory and resources associated with the font.
   * @param name - The name of the bitmap font to uninstall
   * @example
   * ```ts
   * // Remove a font when it's no longer needed
   * BitmapFont.uninstall('MyCustomFont');
   *
   * // Clear multiple fonts
   * ['Title', 'Heading', 'Body'].forEach(BitmapFont.uninstall);
   * ```
   */
  static uninstall(name) {
    BitmapFontManager.uninstall(name);
  }
}

const bitmapFontTextParser = {
  test(data) {
    return typeof data === "string" && data.startsWith("info face=");
  },
  parse(txt) {
    const items = txt.match(/^[a-z]+\s+.+$/gm);
    const rawData = {
      info: [],
      common: [],
      page: [],
      char: [],
      chars: [],
      kerning: [],
      kernings: [],
      distanceField: []
    };
    for (const i in items) {
      const name = items[i].match(/^[a-z]+/gm)[0];
      const attributeList = items[i].match(/[a-zA-Z]+=([^\s"']+|"([^"]*)")/gm);
      const itemData = {};
      for (const i2 in attributeList) {
        const split = attributeList[i2].split("=");
        const key = split[0];
        const strValue = split[1].replace(/"/gm, "");
        const floatValue = parseFloat(strValue);
        const value = isNaN(floatValue) ? strValue : floatValue;
        itemData[key] = value;
      }
      rawData[name].push(itemData);
    }
    const font = {
      chars: {},
      pages: [],
      lineHeight: 0,
      fontSize: 0,
      fontFamily: "",
      distanceField: null,
      baseLineOffset: 0
    };
    const [info] = rawData.info;
    const [common] = rawData.common;
    const [distanceField] = rawData.distanceField ?? [];
    if (distanceField) {
      font.distanceField = {
        range: parseInt(distanceField.distanceRange, 10),
        type: distanceField.fieldType
      };
    }
    font.fontSize = parseInt(info.size, 10);
    font.fontFamily = info.face;
    font.lineHeight = parseInt(common.lineHeight, 10);
    const page = rawData.page;
    for (let i = 0; i < page.length; i++) {
      font.pages.push({
        id: parseInt(page[i].id, 10) || 0,
        file: page[i].file
      });
    }
    const map = {};
    font.baseLineOffset = font.lineHeight - parseInt(common.base, 10);
    const char = rawData.char;
    for (let i = 0; i < char.length; i++) {
      const charNode = char[i];
      const id = parseInt(charNode.id, 10);
      let letter = charNode.letter ?? charNode.char ?? String.fromCharCode(id);
      if (letter === "space")
        letter = " ";
      map[id] = letter;
      font.chars[letter] = {
        id,
        // texture deets..
        page: parseInt(charNode.page, 10) || 0,
        x: parseInt(charNode.x, 10),
        y: parseInt(charNode.y, 10),
        width: parseInt(charNode.width, 10),
        height: parseInt(charNode.height, 10),
        xOffset: parseInt(charNode.xoffset, 10),
        yOffset: parseInt(charNode.yoffset, 10),
        xAdvance: parseInt(charNode.xadvance, 10),
        kerning: {}
      };
    }
    const kerning = rawData.kerning || [];
    for (let i = 0; i < kerning.length; i++) {
      const first = parseInt(kerning[i].first, 10);
      const second = parseInt(kerning[i].second, 10);
      const amount = parseInt(kerning[i].amount, 10);
      font.chars[map[second]].kerning[map[first]] = amount;
    }
    return font;
  }
};

const bitmapFontXMLParser = {
  test(data) {
    const xml = data;
    return typeof xml !== "string" && "getElementsByTagName" in xml && xml.getElementsByTagName("page").length && xml.getElementsByTagName("info")[0].getAttribute("face") !== null;
  },
  parse(xml) {
    const data = {
      chars: {},
      pages: [],
      lineHeight: 0,
      fontSize: 0,
      fontFamily: "",
      distanceField: null,
      baseLineOffset: 0
    };
    const info = xml.getElementsByTagName("info")[0];
    const common = xml.getElementsByTagName("common")[0];
    const distanceField = xml.getElementsByTagName("distanceField")[0];
    if (distanceField) {
      data.distanceField = {
        type: distanceField.getAttribute("fieldType"),
        range: parseInt(distanceField.getAttribute("distanceRange"), 10)
      };
    }
    const page = xml.getElementsByTagName("page");
    const char = xml.getElementsByTagName("char");
    const kerning = xml.getElementsByTagName("kerning");
    data.fontSize = parseInt(info.getAttribute("size"), 10);
    data.fontFamily = info.getAttribute("face");
    data.lineHeight = parseInt(common.getAttribute("lineHeight"), 10);
    for (let i = 0; i < page.length; i++) {
      data.pages.push({
        id: parseInt(page[i].getAttribute("id"), 10) || 0,
        file: page[i].getAttribute("file")
      });
    }
    const map = {};
    data.baseLineOffset = data.lineHeight - parseInt(common.getAttribute("base"), 10);
    for (let i = 0; i < char.length; i++) {
      const charNode = char[i];
      const id = parseInt(charNode.getAttribute("id"), 10);
      let letter = charNode.getAttribute("letter") ?? charNode.getAttribute("char") ?? String.fromCharCode(id);
      if (letter === "space")
        letter = " ";
      map[id] = letter;
      data.chars[letter] = {
        id,
        // texture deets..
        page: parseInt(charNode.getAttribute("page"), 10) || 0,
        x: parseInt(charNode.getAttribute("x"), 10),
        y: parseInt(charNode.getAttribute("y"), 10),
        width: parseInt(charNode.getAttribute("width"), 10),
        height: parseInt(charNode.getAttribute("height"), 10),
        // render deets..
        xOffset: parseInt(charNode.getAttribute("xoffset"), 10),
        yOffset: parseInt(charNode.getAttribute("yoffset"), 10),
        // + baseLineOffset,
        xAdvance: parseInt(charNode.getAttribute("xadvance"), 10),
        kerning: {}
      };
    }
    for (let i = 0; i < kerning.length; i++) {
      const first = parseInt(kerning[i].getAttribute("first"), 10);
      const second = parseInt(kerning[i].getAttribute("second"), 10);
      const amount = parseInt(kerning[i].getAttribute("amount"), 10);
      data.chars[map[second]].kerning[map[first]] = amount;
    }
    return data;
  }
};

const bitmapFontXMLStringParser = {
  test(data) {
    if (typeof data === "string" && data.match(/<font(\s|>)/)) {
      return bitmapFontXMLParser.test(DOMAdapter.get().parseXML(data));
    }
    return false;
  },
  parse(data) {
    return bitmapFontXMLParser.parse(DOMAdapter.get().parseXML(data));
  }
};

const validExtensions = [".xml", ".fnt"];
const bitmapFontCachePlugin = {
  extension: {
    type: ExtensionType.CacheParser,
    name: "cacheBitmapFont"
  },
  test: (asset) => asset instanceof BitmapFont,
  getCacheableAssets(keys, asset) {
    const out = {};
    keys.forEach((key) => {
      out[key] = asset;
      out[`${key}-bitmap`] = asset;
    });
    out[`${asset.fontFamily}-bitmap`] = asset;
    return out;
  }
};
const loadBitmapFont = {
  extension: {
    type: ExtensionType.LoadParser,
    priority: LoaderParserPriority.Normal
  },
  /** used for deprecation purposes */
  name: "loadBitmapFont",
  id: "bitmap-font",
  test(url) {
    return validExtensions.includes(path.extname(url).toLowerCase());
  },
  async testParse(data) {
    return bitmapFontTextParser.test(data) || bitmapFontXMLStringParser.test(data);
  },
  async parse(asset, data, loader) {
    const bitmapFontData = bitmapFontTextParser.test(asset) ? bitmapFontTextParser.parse(asset) : bitmapFontXMLStringParser.parse(asset);
    const { src } = data;
    const { pages } = bitmapFontData;
    const textureUrls = [];
    const textureOptions = bitmapFontData.distanceField ? {
      scaleMode: "linear",
      alphaMode: "premultiply-alpha-on-upload",
      autoGenerateMipmaps: false,
      resolution: 1
    } : {};
    for (let i = 0; i < pages.length; ++i) {
      const pageFile = pages[i].file;
      let imagePath = path.join(path.dirname(src), pageFile);
      imagePath = copySearchParams(imagePath, src);
      textureUrls.push({
        src: imagePath,
        data: textureOptions
      });
    }
    const loadedTextures = await loader.load(textureUrls);
    const textures = textureUrls.map((url) => loadedTextures[url.src]);
    const bitmapFont = new BitmapFont({
      data: bitmapFontData,
      textures
    }, src);
    return bitmapFont;
  },
  async load(url, _options) {
    const response = await DOMAdapter.get().fetch(url);
    return await response.text();
  },
  async unload(bitmapFont, _resolvedAsset, loader) {
    await Promise.all(bitmapFont.pages.map((page) => loader.unload(page.texture.source._sourceOrigin)));
    bitmapFont.destroy();
  }
};

class BackgroundLoader {
  /**
   * @param loader
   * @param verbose - should the loader log to the console
   */
  constructor(loader, verbose = false) {
    this._loader = loader;
    this._assetList = [];
    this._isLoading = false;
    this._maxConcurrent = 1;
    this.verbose = verbose;
  }
  /**
   * Adds assets to the background loading queue. Assets are loaded one at a time to minimize
   * performance impact.
   * @param assetUrls - Array of resolved assets to load in the background
   * @example
   * ```ts
   * // Add assets to background load queue
   * backgroundLoader.add([
   *     { src: 'images/level1/bg.png' },
   *     { src: 'images/level1/characters.json' }
   * ]);
   *
   * // Assets will load sequentially in the background
   * // The loader automatically pauses when high-priority loads occur
   * // e.g. Assets.load() is called
   * ```
   * @remarks
   * - Assets are loaded one at a time to minimize performance impact
   * - Loading automatically pauses when Assets.load() is called
   * - No progress tracking is available for background loading
   * - Assets are cached as they complete loading
   * @internal
   */
  add(assetUrls) {
    assetUrls.forEach((a) => {
      this._assetList.push(a);
    });
    if (this.verbose) {
      console.log("[BackgroundLoader] assets: ", this._assetList);
    }
    if (this._isActive && !this._isLoading) {
      void this._next();
    }
  }
  /**
   * Loads the next set of assets. Will try to load as many assets as it can at the same time.
   *
   * The max assets it will try to load at one time will be 4.
   */
  async _next() {
    if (this._assetList.length && this._isActive) {
      this._isLoading = true;
      const toLoad = [];
      const toLoadAmount = Math.min(this._assetList.length, this._maxConcurrent);
      for (let i = 0; i < toLoadAmount; i++) {
        toLoad.push(this._assetList.pop());
      }
      await this._loader.load(toLoad);
      this._isLoading = false;
      void this._next();
    }
  }
  /**
   * Controls the active state of the background loader. When active, the loader will
   * continue processing its queue. When inactive, loading is paused.
   * @returns Whether the background loader is currently active
   * @example
   * ```ts
   * // Pause background loading
   * backgroundLoader.active = false;
   *
   * // Resume background loading
   * backgroundLoader.active = true;
   *
   * // Check current state
   * console.log(backgroundLoader.active); // true/false
   *
   * // Common use case: Pause during intensive operations
   * backgroundLoader.active = false;  // Pause background loading
   * ... // Perform high-priority tasks
   * backgroundLoader.active = true;   // Resume background loading
   * ```
   * @remarks
   * - Setting to true resumes loading immediately
   * - Setting to false pauses after current asset completes
   * - Background loading is automatically paused during `Assets.load()`
   * - Assets already being loaded will complete even when set to false
   */
  get active() {
    return this._isActive;
  }
  set active(value) {
    if (this._isActive === value)
      return;
    this._isActive = value;
    if (value && !this._isLoading) {
      void this._next();
    }
  }
}

const cacheTextureArray = {
  extension: {
    type: ExtensionType.CacheParser,
    name: "cacheTextureArray"
  },
  test: (asset) => Array.isArray(asset) && asset.every((t) => t instanceof Texture),
  getCacheableAssets: (keys, asset) => {
    const out = {};
    keys.forEach((key) => {
      asset.forEach((item, i) => {
        out[key + (i === 0 ? "" : i + 1)] = item;
      });
    });
    return out;
  }
};

async function testImageFormat(imageData) {
  if ("Image" in globalThis) {
    return new Promise((resolve) => {
      const image = new Image();
      image.onload = () => {
        resolve(true);
      };
      image.onerror = () => {
        resolve(false);
      };
      image.src = imageData;
    });
  }
  if ("createImageBitmap" in globalThis && "fetch" in globalThis) {
    try {
      const blob = await (await fetch(imageData)).blob();
      await createImageBitmap(blob);
    } catch (_e) {
      return false;
    }
    return true;
  }
  return false;
}

const detectAvif = {
  extension: {
    type: ExtensionType.DetectionParser,
    priority: 1
  },
  test: async () => testImageFormat(
    // eslint-disable-next-line max-len
    "data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A="
  ),
  add: async (formats) => [...formats, "avif"],
  remove: async (formats) => formats.filter((f) => f !== "avif")
};

const imageFormats = ["png", "jpg", "jpeg"];
const detectDefaults = {
  extension: {
    type: ExtensionType.DetectionParser,
    priority: -1
  },
  test: () => Promise.resolve(true),
  add: async (formats) => [...formats, ...imageFormats],
  remove: async (formats) => formats.filter((f) => !imageFormats.includes(f))
};

const inWorker = "WorkerGlobalScope" in globalThis && globalThis instanceof globalThis.WorkerGlobalScope;
function testVideoFormat(mimeType) {
  if (inWorker) {
    return false;
  }
  const video = document.createElement("video");
  return video.canPlayType(mimeType) !== "";
}

const detectMp4 = {
  extension: {
    type: ExtensionType.DetectionParser,
    priority: 0
  },
  test: async () => testVideoFormat("video/mp4"),
  add: async (formats) => [...formats, "mp4", "m4v"],
  remove: async (formats) => formats.filter((f) => f !== "mp4" && f !== "m4v")
};

const detectOgv = {
  extension: {
    type: ExtensionType.DetectionParser,
    priority: 0
  },
  test: async () => testVideoFormat("video/ogg"),
  add: async (formats) => [...formats, "ogv"],
  remove: async (formats) => formats.filter((f) => f !== "ogv")
};

const detectWebm = {
  extension: {
    type: ExtensionType.DetectionParser,
    priority: 0
  },
  test: async () => testVideoFormat("video/webm"),
  add: async (formats) => [...formats, "webm"],
  remove: async (formats) => formats.filter((f) => f !== "webm")
};

const detectWebp = {
  extension: {
    type: ExtensionType.DetectionParser,
    priority: 0
  },
  test: async () => testImageFormat(
    "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA="
  ),
  add: async (formats) => [...formats, "webp"],
  remove: async (formats) => formats.filter((f) => f !== "webp")
};

const _Loader = class _Loader {
  constructor() {
    /**
     * Options for loading assets with the loader.
     * These options will be used as defaults for all load calls made with this loader instance.
     * They can be overridden by passing options directly to the load method.
     * @example
     * ```ts
     * // Create a loader with custom default options
     * const loader = new Loader();
     * loader.loadOptions = {
     *   strategy: 'skip', // Default strategy to 'skip'
     *   retryCount: 5,   // Default retry count to 5
     *   retryDelay: 500, // Default retry delay to 500ms
     * };
     *
     * // This load call will use the loader's default options
     * await loader.load('image1.png');
     */
    this.loadOptions = { ..._Loader.defaultOptions };
    this._parsers = [];
    this._parsersValidated = false;
    /**
     * All loader parsers registered
     * @type {assets.LoaderParser[]}
     */
    this.parsers = new Proxy(this._parsers, {
      set: (target, key, value) => {
        this._parsersValidated = false;
        target[key] = value;
        return true;
      }
    });
    /** Cache loading promises that ae currently active */
    this.promiseCache = {};
  }
  /** function used for testing */
  reset() {
    this._parsersValidated = false;
    this.promiseCache = {};
  }
  /**
   * Used internally to generate a promise for the asset to be loaded.
   * @param url - The URL to be loaded
   * @param data - any custom additional information relevant to the asset being loaded
   * @returns - a promise that will resolve to an Asset for example a Texture of a JSON object
   */
  _getLoadPromiseAndParser(url, data) {
    const result = {
      promise: null,
      parser: null
    };
    result.promise = (async () => {
      let asset = null;
      let parser = null;
      if (data.parser || data.loadParser) {
        parser = this._parserHash[data.parser || data.loadParser];
        if (data.loadParser) {
          warn(
            `[Assets] "loadParser" is deprecated, use "parser" instead for ${url}`
          );
        }
        if (!parser) {
          warn(
            `[Assets] specified load parser "${data.parser || data.loadParser}" not found while loading ${url}`
          );
        }
      }
      if (!parser) {
        for (let i = 0; i < this.parsers.length; i++) {
          const parserX = this.parsers[i];
          if (parserX.load && parserX.test?.(url, data, this)) {
            parser = parserX;
            break;
          }
        }
        if (!parser) {
          warn(`[Assets] ${url} could not be loaded as we don't know how to parse it, ensure the correct parser has been added`);
          return null;
        }
      }
      asset = await parser.load(url, data, this);
      result.parser = parser;
      for (let i = 0; i < this.parsers.length; i++) {
        const parser2 = this.parsers[i];
        if (parser2.parse) {
          if (parser2.parse && await parser2.testParse?.(asset, data, this)) {
            asset = await parser2.parse(asset, data, this) || asset;
            result.parser = parser2;
          }
        }
      }
      return asset;
    })();
    return result;
  }
  async load(assetsToLoadIn, onProgressOrOptions) {
    if (!this._parsersValidated) {
      this._validateParsers();
    }
    const options = typeof onProgressOrOptions === "function" ? { ..._Loader.defaultOptions, ...this.loadOptions, onProgress: onProgressOrOptions } : { ..._Loader.defaultOptions, ...this.loadOptions, ...onProgressOrOptions || {} };
    const { onProgress, onError, strategy, retryCount, retryDelay } = options;
    let count = 0;
    const assets = {};
    const singleAsset = isSingleItem(assetsToLoadIn);
    const assetsToLoad = convertToList(assetsToLoadIn, (item) => ({
      alias: [item],
      src: item,
      data: {}
    }));
    const total = assetsToLoad.reduce((sum, asset) => sum + (asset.progressSize || 1), 0);
    const promises = assetsToLoad.map(async (asset) => {
      const url = path.toAbsolute(asset.src);
      if (assets[asset.src])
        return;
      await this._loadAssetWithRetry(url, asset, { onProgress, onError, strategy, retryCount, retryDelay }, assets);
      count += asset.progressSize || 1;
      if (onProgress)
        onProgress(count / total);
    });
    await Promise.all(promises);
    return singleAsset ? assets[assetsToLoad[0].src] : assets;
  }
  /**
   * Unloads one or more assets. Any unloaded assets will be destroyed, freeing up memory for your app.
   * The parser that created the asset, will be the one that unloads it.
   * @example
   * // Single asset:
   * const asset = await Loader.load('cool.png');
   *
   * await Loader.unload('cool.png');
   *
   * console.log(asset.destroyed); // true
   * @param assetsToUnloadIn - urls that you want to unload, or a single one!
   */
  async unload(assetsToUnloadIn) {
    const assetsToUnload = convertToList(assetsToUnloadIn, (item) => ({
      alias: [item],
      src: item
    }));
    const promises = assetsToUnload.map(async (asset) => {
      const url = path.toAbsolute(asset.src);
      const loadPromise = this.promiseCache[url];
      if (loadPromise) {
        const loadedAsset = await loadPromise.promise;
        delete this.promiseCache[url];
        await loadPromise.parser?.unload?.(loadedAsset, asset, this);
      }
    });
    await Promise.all(promises);
  }
  /** validates our parsers, right now it only checks for name conflicts but we can add more here as required! */
  _validateParsers() {
    this._parsersValidated = true;
    this._parserHash = this._parsers.filter((parser) => parser.name || parser.id).reduce((hash, parser) => {
      if (!parser.name && !parser.id) {
        warn(`[Assets] parser should have an id`);
      } else if (hash[parser.name] || hash[parser.id]) {
        warn(`[Assets] parser id conflict "${parser.id}"`);
      }
      hash[parser.name] = parser;
      if (parser.id)
        hash[parser.id] = parser;
      return hash;
    }, {});
  }
  async _loadAssetWithRetry(url, asset, options, assets) {
    let attempt = 0;
    const { onError, strategy, retryCount, retryDelay } = options;
    const wait = (ms) => new Promise((r) => setTimeout(r, ms));
    while (true) {
      try {
        if (!this.promiseCache[url]) {
          this.promiseCache[url] = this._getLoadPromiseAndParser(url, asset);
        }
        assets[asset.src] = await this.promiseCache[url].promise;
        return;
      } catch (e) {
        delete this.promiseCache[url];
        delete assets[asset.src];
        attempt++;
        const isLast = strategy !== "retry" || attempt > retryCount;
        if (strategy === "retry" && !isLast) {
          if (onError)
            onError(e, asset);
          await wait(retryDelay);
          continue;
        }
        if (strategy === "skip") {
          if (onError)
            onError(e, asset);
          return;
        }
        if (onError)
          onError(e, asset);
        const error = new Error(`[Loader.load] Failed to load ${url}.
${e}`);
        if (e instanceof Error && e.stack) {
          error.stack = e.stack;
        }
        throw error;
      }
    }
  }
};
/**
 * Default options for loading assets
 * @example
 * ```ts
 * // Change default load options globally
 * Loader.defaultOptions = {
 *   strategy: 'skip', // Change default strategy to 'skip'
 *   retryCount: 5,   // Change default retry count to 5
 *   retryDelay: 500, // Change default retry delay to 500ms
 * };
 * ```
 */
_Loader.defaultOptions = {
  onProgress: void 0,
  onError: void 0,
  strategy: "throw",
  retryCount: 3,
  retryDelay: 250
};
let Loader = _Loader;

function checkDataUrl(url, mimes) {
  if (Array.isArray(mimes)) {
    for (const mime of mimes) {
      if (url.startsWith(`data:${mime}`))
        return true;
    }
    return false;
  }
  return url.startsWith(`data:${mimes}`);
}

function checkExtension(url, extension) {
  const tempURL = url.split("?")[0];
  const ext = path.extname(tempURL).toLowerCase();
  if (Array.isArray(extension)) {
    return extension.includes(ext);
  }
  return ext === extension;
}

const validJSONExtension = ".json";
const validJSONMIME = "application/json";
const loadJson = {
  extension: {
    type: ExtensionType.LoadParser,
    priority: LoaderParserPriority.Low
  },
  /** used for deprecation purposes */
  name: "loadJson",
  id: "json",
  test(url) {
    return checkDataUrl(url, validJSONMIME) || checkExtension(url, validJSONExtension);
  },
  async load(url) {
    const response = await DOMAdapter.get().fetch(url);
    const json = await response.json();
    return json;
  }
};

const validTXTExtension = ".txt";
const validTXTMIME = "text/plain";
const loadTxt = {
  /** used for deprecation purposes */
  name: "loadTxt",
  id: "text",
  extension: {
    type: ExtensionType.LoadParser,
    priority: LoaderParserPriority.Low,
    name: "loadTxt"
  },
  test(url) {
    return checkDataUrl(url, validTXTMIME) || checkExtension(url, validTXTExtension);
  },
  async load(url) {
    const response = await DOMAdapter.get().fetch(url);
    const txt = await response.text();
    return txt;
  }
};

const validWeights = [
  "normal",
  "bold",
  "100",
  "200",
  "300",
  "400",
  "500",
  "600",
  "700",
  "800",
  "900"
];
const validFontExtensions = [".ttf", ".otf", ".woff", ".woff2"];
const validFontMIMEs = [
  "font/ttf",
  "font/otf",
  "font/woff",
  "font/woff2"
];
const CSS_IDENT_TOKEN_REGEX = /^(--|-?[A-Z_])[0-9A-Z_-]*$/i;
function getFontFamilyName(url) {
  const ext = path.extname(url);
  const name = path.basename(url, ext);
  const nameWithSpaces = name.replace(/(-|_)/g, " ");
  const nameTokens = nameWithSpaces.toLowerCase().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1));
  let valid = nameTokens.length > 0;
  for (const token of nameTokens) {
    if (!token.match(CSS_IDENT_TOKEN_REGEX)) {
      valid = false;
      break;
    }
  }
  let fontFamilyName = nameTokens.join(" ");
  if (!valid) {
    fontFamilyName = `"${fontFamilyName.replace(/[\\"]/g, "\\$&")}"`;
  }
  return fontFamilyName;
}
const validURICharactersRegex = /^[0-9A-Za-z%:/?#\[\]@!\$&'()\*\+,;=\-._~]*$/;
function encodeURIWhenNeeded(uri) {
  if (validURICharactersRegex.test(uri)) {
    return uri;
  }
  return encodeURI(uri);
}
const loadWebFont = {
  extension: {
    type: ExtensionType.LoadParser,
    priority: LoaderParserPriority.Low
  },
  /** used for deprecation purposes */
  name: "loadWebFont",
  id: "web-font",
  test(url) {
    return checkDataUrl(url, validFontMIMEs) || checkExtension(url, validFontExtensions);
  },
  async load(url, options) {
    const fonts = DOMAdapter.get().getFontFaceSet();
    if (fonts) {
      const fontFaces = [];
      const name = options.data?.family ?? getFontFamilyName(url);
      const weights = options.data?.weights?.filter((weight) => validWeights.includes(weight)) ?? ["normal"];
      const data = options.data ?? {};
      for (let i = 0; i < weights.length; i++) {
        const weight = weights[i];
        const font = new FontFace(name, `url(${encodeURIWhenNeeded(url)})`, {
          ...data,
          weight
        });
        await font.load();
        fonts.add(font);
        fontFaces.push(font);
      }
      if (Cache.has(`${name}-and-url`)) {
        const cached = Cache.get(`${name}-and-url`);
        cached.entries.push({ url, faces: fontFaces });
      } else {
        Cache.set(`${name}-and-url`, {
          entries: [{ url, faces: fontFaces }]
        });
      }
      return fontFaces.length === 1 ? fontFaces[0] : fontFaces;
    }
    warn("[loadWebFont] FontFace API is not supported. Skipping loading font");
    return null;
  },
  unload(font) {
    const fonts = Array.isArray(font) ? font : [font];
    const fontFamily = fonts[0].family;
    const cached = Cache.get(`${fontFamily}-and-url`);
    const entry = cached.entries.find((f) => f.faces.some((t) => fonts.indexOf(t) !== -1));
    entry.faces = entry.faces.filter((f) => fonts.indexOf(f) === -1);
    if (entry.faces.length === 0) {
      cached.entries = cached.entries.filter((f) => f !== entry);
    }
    fonts.forEach((t) => {
      DOMAdapter.get().getFontFaceSet().delete(t);
    });
    if (cached.entries.length === 0) {
      Cache.remove(`${fontFamily}-and-url`);
    }
  }
};

function getResolutionOfUrl(url, defaultValue = 1) {
  const resolution = Resolver.RETINA_PREFIX?.exec(url);
  if (resolution) {
    return parseFloat(resolution[1]);
  }
  return defaultValue;
}

function createTexture(source, loader, url) {
  source.label = url;
  source._sourceOrigin = url;
  const texture = new Texture({
    source,
    label: url
  });
  const unload = () => {
    delete loader.promiseCache[url];
    if (Cache.has(url)) {
      Cache.remove(url);
    }
  };
  texture.source.once("destroy", () => {
    if (loader.promiseCache[url]) {
      warn("[Assets] A TextureSource managed by Assets was destroyed instead of unloaded! Use Assets.unload() instead of destroying the TextureSource.");
      unload();
    }
  });
  texture.once("destroy", () => {
    if (!source.destroyed) {
      warn("[Assets] A Texture managed by Assets was destroyed instead of unloaded! Use Assets.unload() instead of destroying the Texture.");
      unload();
    }
  });
  return texture;
}

const validSVGExtension = ".svg";
const validSVGMIME = "image/svg+xml";
const loadSvg = {
  extension: {
    type: ExtensionType.LoadParser,
    priority: LoaderParserPriority.Low,
    name: "loadSVG"
  },
  /** used for deprecation purposes */
  name: "loadSVG",
  id: "svg",
  config: {
    crossOrigin: "anonymous",
    parseAsGraphicsContext: false
  },
  test(url) {
    return checkDataUrl(url, validSVGMIME) || checkExtension(url, validSVGExtension);
  },
  async load(url, asset, loader) {
    if (asset.data?.parseAsGraphicsContext ?? this.config.parseAsGraphicsContext) {
      return loadAsGraphics(url);
    }
    return loadAsTexture(url, asset, loader, this.config.crossOrigin);
  },
  unload(asset) {
    asset.destroy(true);
  }
};
async function loadAsTexture(url, asset, loader, crossOrigin) {
  const response = await DOMAdapter.get().fetch(url);
  const image = DOMAdapter.get().createImage();
  image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(await response.text())}`;
  image.crossOrigin = crossOrigin;
  await image.decode();
  const width = asset.data?.width ?? image.width;
  const height = asset.data?.height ?? image.height;
  const resolution = asset.data?.resolution || getResolutionOfUrl(url);
  const canvasWidth = Math.ceil(width * resolution);
  const canvasHeight = Math.ceil(height * resolution);
  const canvas = DOMAdapter.get().createCanvas(canvasWidth, canvasHeight);
  const context = canvas.getContext("2d");
  context.imageSmoothingEnabled = true;
  context.imageSmoothingQuality = "high";
  context.drawImage(image, 0, 0, width * resolution, height * resolution);
  const { parseAsGraphicsContext: _p, ...rest } = asset.data ?? {};
  const base = new ImageSource({
    resource: canvas,
    alphaMode: "premultiply-alpha-on-upload",
    resolution,
    ...rest
  });
  return createTexture(base, loader, url);
}
async function loadAsGraphics(url) {
  const response = await DOMAdapter.get().fetch(url);
  const svgSource = await response.text();
  const context = new GraphicsContext();
  context.svg(svgSource);
  return context;
}

const WORKER_CODE$1 = "(function () {\n    'use strict';\n\n    const WHITE_PNG = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=\";\n    async function checkImageBitmap() {\n      try {\n        if (typeof createImageBitmap !== \"function\")\n          return false;\n        const response = await fetch(WHITE_PNG);\n        const imageBlob = await response.blob();\n        const imageBitmap = await createImageBitmap(imageBlob);\n        return imageBitmap.width === 1 && imageBitmap.height === 1;\n      } catch (_e) {\n        return false;\n      }\n    }\n    void checkImageBitmap().then((result) => {\n      self.postMessage(result);\n    });\n\n})();\n";
let WORKER_URL$1 = null;
let WorkerInstance$1 = class WorkerInstance
{
    constructor()
    {
        if (!WORKER_URL$1)
        {
            WORKER_URL$1 = URL.createObjectURL(new Blob([WORKER_CODE$1], { type: 'application/javascript' }));
        }
        this.worker = new Worker(WORKER_URL$1);
    }
};
WorkerInstance$1.revokeObjectURL = function revokeObjectURL()
{
    if (WORKER_URL$1)
    {
        URL.revokeObjectURL(WORKER_URL$1);
        WORKER_URL$1 = null;
    }
};

const WORKER_CODE = "(function () {\n    'use strict';\n\n    async function loadImageBitmap(url, alphaMode) {\n      const response = await fetch(url);\n      if (!response.ok) {\n        throw new Error(`[WorkerManager.loadImageBitmap] Failed to fetch ${url}: ${response.status} ${response.statusText}`);\n      }\n      const imageBlob = await response.blob();\n      return alphaMode === \"premultiplied-alpha\" ? createImageBitmap(imageBlob, { premultiplyAlpha: \"none\" }) : createImageBitmap(imageBlob);\n    }\n    self.onmessage = async (event) => {\n      try {\n        const imageBitmap = await loadImageBitmap(event.data.data[0], event.data.data[1]);\n        self.postMessage({\n          data: imageBitmap,\n          uuid: event.data.uuid,\n          id: event.data.id\n        }, [imageBitmap]);\n      } catch (e) {\n        self.postMessage({\n          error: e,\n          uuid: event.data.uuid,\n          id: event.data.id\n        });\n      }\n    };\n\n})();\n";
let WORKER_URL = null;
class WorkerInstance
{
    constructor()
    {
        if (!WORKER_URL)
        {
            WORKER_URL = URL.createObjectURL(new Blob([WORKER_CODE], { type: 'application/javascript' }));
        }
        this.worker = new Worker(WORKER_URL);
    }
}
WorkerInstance.revokeObjectURL = function revokeObjectURL()
{
    if (WORKER_URL)
    {
        URL.revokeObjectURL(WORKER_URL);
        WORKER_URL = null;
    }
};

let UUID = 0;
let MAX_WORKERS;
class WorkerManagerClass {
  constructor() {
    /** Whether the worker manager has been initialized */
    this._initialized = false;
    /** Current number of created workers (used to enforce MAX_WORKERS limit) */
    this._createdWorkers = 0;
    this._workerPool = [];
    this._queue = [];
    this._resolveHash = {};
  }
  /**
   * Checks if ImageBitmap is supported in the current environment.
   *
   * This method uses a dedicated worker to test ImageBitmap support
   * and caches the result for subsequent calls.
   * @returns Promise that resolves to true if ImageBitmap is supported, false otherwise
   */
  isImageBitmapSupported() {
    if (this._isImageBitmapSupported !== void 0)
      return this._isImageBitmapSupported;
    this._isImageBitmapSupported = new Promise((resolve) => {
      const { worker } = new WorkerInstance$1();
      worker.addEventListener("message", (event) => {
        worker.terminate();
        WorkerInstance$1.revokeObjectURL();
        resolve(event.data);
      });
    });
    return this._isImageBitmapSupported;
  }
  /**
   * Loads an image as an ImageBitmap using a web worker.
   * @param src - The source URL or path of the image to load
   * @param asset - Optional resolved asset containing additional texture source options
   * @returns Promise that resolves to the loaded ImageBitmap
   * @example
   * ```typescript
   * const bitmap = await WorkerManager.loadImageBitmap('image.png');
   * const bitmapWithOptions = await WorkerManager.loadImageBitmap('image.png', asset);
   * ```
   */
  loadImageBitmap(src, asset) {
    return this._run("loadImageBitmap", [src, asset?.data?.alphaMode]);
  }
  /**
   * Initializes the worker pool if not already initialized.
   * Currently a no-op but reserved for future initialization logic.
   */
  async _initWorkers() {
    if (this._initialized)
      return;
    this._initialized = true;
  }
  /**
   * Gets an available worker from the pool or creates a new one if needed.
   *
   * Workers are created up to the MAX_WORKERS limit (based on navigator.hardwareConcurrency).
   * Each worker is configured with a message handler for processing results.
   * @returns Available worker or undefined if pool is at capacity and no workers are free
   */
  _getWorker() {
    if (MAX_WORKERS === void 0) {
      MAX_WORKERS = navigator.hardwareConcurrency || 4;
    }
    let worker = this._workerPool.pop();
    if (!worker && this._createdWorkers < MAX_WORKERS) {
      this._createdWorkers++;
      worker = new WorkerInstance().worker;
      worker.addEventListener("message", (event) => {
        this._complete(event.data);
        this._returnWorker(event.target);
        this._next();
      });
    }
    return worker;
  }
  /**
   * Returns a worker to the pool after completing a task.
   * @param worker - The worker to return to the pool
   */
  _returnWorker(worker) {
    this._workerPool.push(worker);
  }
  /**
   * Handles completion of a worker task by resolving or rejecting the corresponding promise.
   * @param data - Result data from the worker containing uuid, data, and optional error
   */
  _complete(data) {
    if (!this._resolveHash[data.uuid]) {
      return;
    }
    if (data.error !== void 0) {
      this._resolveHash[data.uuid].reject(data.error);
    } else {
      this._resolveHash[data.uuid].resolve(data.data);
    }
    delete this._resolveHash[data.uuid];
  }
  /**
   * Executes a task using the worker pool system.
   *
   * Queues the task and processes it when a worker becomes available.
   * @param id - Identifier for the type of task to run
   * @param args - Arguments to pass to the worker
   * @returns Promise that resolves with the worker's result
   */
  async _run(id, args) {
    await this._initWorkers();
    const promise = new Promise((resolve, reject) => {
      this._queue.push({ id, arguments: args, resolve, reject });
    });
    this._next();
    return promise;
  }
  /**
   * Processes the next item in the queue if workers are available.
   *
   * This method is called after worker initialization and when workers
   * complete tasks to continue processing the queue.
   */
  _next() {
    if (!this._queue.length)
      return;
    const worker = this._getWorker();
    if (!worker) {
      return;
    }
    const toDo = this._queue.pop();
    const id = toDo.id;
    this._resolveHash[UUID] = { resolve: toDo.resolve, reject: toDo.reject };
    worker.postMessage({
      data: toDo.arguments,
      uuid: UUID++,
      id
    });
  }
  /**
   * Resets the worker manager, terminating all workers and clearing the queue.
   *
   * This method:
   * - Terminates all active workers
   * - Rejects all pending promises with an error
   * - Clears all internal state
   * - Resets initialization flags
   *
   * This should be called when the worker manager is no longer needed
   * to prevent memory leaks and ensure proper cleanup.
   * @example
   * ```typescript
   * // Clean up when shutting down
   * WorkerManager.reset();
   * ```
   */
  reset() {
    this._workerPool.forEach((worker) => worker.terminate());
    this._workerPool.length = 0;
    Object.values(this._resolveHash).forEach(({ reject }) => {
      reject?.(new Error("WorkerManager has been reset before completion"));
    });
    this._resolveHash = {};
    this._queue.length = 0;
    this._initialized = false;
    this._createdWorkers = 0;
  }
}
const WorkerManager = new WorkerManagerClass();

const validImageExtensions = [".jpeg", ".jpg", ".png", ".webp", ".avif"];
const validImageMIMEs = [
  "image/jpeg",
  "image/png",
  "image/webp",
  "image/avif"
];
async function loadImageBitmap(url, asset) {
  const response = await DOMAdapter.get().fetch(url);
  if (!response.ok) {
    throw new Error(`[loadImageBitmap] Failed to fetch ${url}: ${response.status} ${response.statusText}`);
  }
  const imageBlob = await response.blob();
  return asset?.data?.alphaMode === "premultiplied-alpha" ? createImageBitmap(imageBlob, { premultiplyAlpha: "none" }) : createImageBitmap(imageBlob);
}
const loadTextures = {
  /** used for deprecation purposes */
  name: "loadTextures",
  id: "texture",
  extension: {
    type: ExtensionType.LoadParser,
    priority: LoaderParserPriority.High,
    name: "loadTextures"
  },
  config: {
    preferWorkers: true,
    preferCreateImageBitmap: true,
    crossOrigin: "anonymous"
  },
  test(url) {
    return checkDataUrl(url, validImageMIMEs) || checkExtension(url, validImageExtensions);
  },
  async load(url, asset, loader) {
    let src = null;
    if (globalThis.createImageBitmap && this.config.preferCreateImageBitmap) {
      if (this.config.preferWorkers && await WorkerManager.isImageBitmapSupported()) {
        src = await WorkerManager.loadImageBitmap(url, asset);
      } else {
        src = await loadImageBitmap(url, asset);
      }
    } else {
      src = await new Promise((resolve, reject) => {
        src = DOMAdapter.get().createImage();
        src.crossOrigin = this.config.crossOrigin;
        src.src = url;
        if (src.complete) {
          resolve(src);
        } else {
          src.onload = () => {
            resolve(src);
          };
          src.onerror = reject;
        }
      });
    }
    const base = new ImageSource({
      resource: src,
      alphaMode: "premultiply-alpha-on-upload",
      resolution: asset.data?.resolution || getResolutionOfUrl(url),
      ...asset.data
    });
    return createTexture(base, loader, url);
  },
  unload(texture) {
    texture.destroy(true);
  }
};

const potentialVideoExtensions = [".mp4", ".m4v", ".webm", ".ogg", ".ogv", ".h264", ".avi", ".mov"];
let validVideoExtensions;
let validVideoMIMEs;
function crossOrigin(element, url, crossorigin) {
  if (crossorigin === void 0 && !url.startsWith("data:")) {
    element.crossOrigin = determineCrossOrigin(url);
  } else if (crossorigin !== false) {
    element.crossOrigin = typeof crossorigin === "string" ? crossorigin : "anonymous";
  }
}
function preloadVideo(element) {
  return new Promise((resolve, reject) => {
    element.addEventListener("canplaythrough", loaded);
    element.addEventListener("error", error);
    element.load();
    function loaded() {
      cleanup();
      resolve();
    }
    function error(err) {
      cleanup();
      reject(err);
    }
    function cleanup() {
      element.removeEventListener("canplaythrough", loaded);
      element.removeEventListener("error", error);
    }
  });
}
function determineCrossOrigin(url, loc = globalThis.location) {
  if (url.startsWith("data:")) {
    return "";
  }
  loc || (loc = globalThis.location);
  const parsedUrl = new URL(url, document.baseURI);
  if (parsedUrl.hostname !== loc.hostname || parsedUrl.port !== loc.port || parsedUrl.protocol !== loc.protocol) {
    return "anonymous";
  }
  return "";
}
function getBrowserSupportedVideoExtensions() {
  const supportedExtensions = [];
  const supportedMimes = [];
  for (const ext of potentialVideoExtensions) {
    const mimeType = VideoSource.MIME_TYPES[ext.substring(1)] || `video/${ext.substring(1)}`;
    if (testVideoFormat(mimeType)) {
      supportedExtensions.push(ext);
      if (!supportedMimes.includes(mimeType)) {
        supportedMimes.push(mimeType);
      }
    }
  }
  return {
    validVideoExtensions: supportedExtensions,
    validVideoMime: supportedMimes
  };
}
const loadVideoTextures = {
  /** used for deprecation purposes */
  name: "loadVideo",
  id: "video",
  extension: {
    type: ExtensionType.LoadParser,
    name: "loadVideo"
  },
  test(url) {
    if (!validVideoExtensions || !validVideoMIMEs) {
      const { validVideoExtensions: ve, validVideoMime: vm } = getBrowserSupportedVideoExtensions();
      validVideoExtensions = ve;
      validVideoMIMEs = vm;
    }
    const isValidDataUrl = checkDataUrl(url, validVideoMIMEs);
    const isValidExtension = checkExtension(url, validVideoExtensions);
    return isValidDataUrl || isValidExtension;
  },
  async load(url, asset, loader) {
    const options = {
      ...VideoSource.defaultOptions,
      resolution: asset.data?.resolution || getResolutionOfUrl(url),
      alphaMode: asset.data?.alphaMode || await detectVideoAlphaMode(),
      ...asset.data
    };
    const videoElement = document.createElement("video");
    const attributeMap = {
      preload: options.autoLoad !== false ? "auto" : void 0,
      "webkit-playsinline": options.playsinline !== false ? "" : void 0,
      playsinline: options.playsinline !== false ? "" : void 0,
      muted: options.muted === true ? "" : void 0,
      loop: options.loop === true ? "" : void 0,
      autoplay: options.autoPlay !== false ? "" : void 0
    };
    Object.keys(attributeMap).forEach((key) => {
      const value = attributeMap[key];
      if (value !== void 0)
        videoElement.setAttribute(key, value);
    });
    if (options.muted === true) {
      videoElement.muted = true;
    }
    crossOrigin(videoElement, url, options.crossorigin);
    const sourceElement = document.createElement("source");
    let mime;
    if (options.mime) {
      mime = options.mime;
    } else if (url.startsWith("data:")) {
      mime = url.slice(5, url.indexOf(";"));
    } else if (!url.startsWith("blob:")) {
      const ext = url.split("?")[0].slice(url.lastIndexOf(".") + 1).toLowerCase();
      mime = VideoSource.MIME_TYPES[ext] || `video/${ext}`;
    }
    sourceElement.src = url;
    if (mime) {
      sourceElement.type = mime;
    }
    return new Promise((resolve) => {
      const onCanPlay = async () => {
        const base = new VideoSource({ ...options, resource: videoElement });
        videoElement.removeEventListener("canplay", onCanPlay);
        if (asset.data.preload) {
          await preloadVideo(videoElement);
        }
        resolve(createTexture(base, loader, url));
      };
      if (options.preload && !options.autoPlay) {
        videoElement.load();
      }
      videoElement.addEventListener("canplay", onCanPlay);
      videoElement.appendChild(sourceElement);
    });
  },
  unload(texture) {
    texture.destroy(true);
  }
};

const resolveTextureUrl = {
  extension: {
    type: ExtensionType.ResolveParser,
    name: "resolveTexture"
  },
  test: loadTextures.test,
  parse: (value) => ({
    resolution: parseFloat(Resolver.RETINA_PREFIX.exec(value)?.[1] ?? "1"),
    format: value.split(".").pop(),
    src: value
  })
};

const resolveJsonUrl = {
  extension: {
    type: ExtensionType.ResolveParser,
    priority: -2,
    name: "resolveJson"
  },
  test: (value) => Resolver.RETINA_PREFIX.test(value) && value.endsWith(".json"),
  parse: resolveTextureUrl.parse
};

class AssetsClass {
  constructor() {
    this._detections = [];
    this._initialized = false;
    this.resolver = new Resolver();
    this.loader = new Loader();
    this.cache = Cache;
    this._backgroundLoader = new BackgroundLoader(this.loader);
    this._backgroundLoader.active = true;
    this.reset();
  }
  /**
   * Initializes the Assets class with configuration options. While not required,
   * calling this before loading assets is recommended to set up default behaviors.
   * @param options - Configuration options for the Assets system
   * @example
   * ```ts
   * // Basic initialization (optional as Assets.load will call this automatically)
   * await Assets.init();
   *
   * // With CDN configuration
   * await Assets.init({
   *     basePath: 'https://my-cdn.com/assets/',
   *     defaultSearchParams: { version: '1.0.0' }
   * });
   *
   * // With manifest and preferences
   * await Assets.init({
   *     manifest: {
   *         bundles: [{
   *             name: 'game-screen',
   *             assets: [
   *                 {
   *                     alias: 'hero',
   *                     src: 'hero.{png,webp}',
   *                     data: { scaleMode: SCALE_MODES.NEAREST }
   *                 },
   *                 {
   *                     alias: 'map',
   *                     src: 'map.json'
   *                 }
   *             ]
   *         }]
   *     },
   *     // Optimize for device capabilities
   *     texturePreference: {
   *         resolution: window.devicePixelRatio,
   *         format: ['webp', 'png']
   *     },
   *     // Set global preferences
   *     preferences: {
   *         crossOrigin: 'anonymous',
   *     }
   * });
   *
   * // Load assets after initialization
   * const heroTexture = await Assets.load('hero');
   * ```
   * @remarks
   * - Can be called only once; subsequent calls will be ignored with a warning
   * - Format detection runs automatically unless `skipDetections` is true
   * - The manifest can be a URL to a JSON file or an inline object
   * @see {@link AssetInitOptions} For all available initialization options
   * @see {@link AssetsManifest} For manifest format details
   */
  async init(options = {}) {
    if (this._initialized) {
      warn("[Assets]AssetManager already initialized, did you load before calling this Assets.init()?");
      return;
    }
    this._initialized = true;
    if (options.defaultSearchParams) {
      this.resolver.setDefaultSearchParams(options.defaultSearchParams);
    }
    if (options.basePath) {
      this.resolver.basePath = options.basePath;
    }
    if (options.bundleIdentifier) {
      this.resolver.setBundleIdentifier(options.bundleIdentifier);
    }
    if (options.manifest) {
      let manifest = options.manifest;
      if (typeof manifest === "string") {
        manifest = await this.load(manifest);
      }
      this.resolver.addManifest(manifest);
    }
    const resolutionPref = options.texturePreference?.resolution ?? 1;
    const resolution = typeof resolutionPref === "number" ? [resolutionPref] : resolutionPref;
    const formats = await this._detectFormats({
      preferredFormats: options.texturePreference?.format,
      skipDetections: options.skipDetections,
      detections: this._detections
    });
    this.resolver.prefer({
      params: {
        format: formats,
        resolution
      }
    });
    if (options.preferences) {
      this.setPreferences(options.preferences);
    }
    if (options.loadOptions) {
      this.loader.loadOptions = {
        ...this.loader.loadOptions,
        ...options.loadOptions
      };
    }
  }
  /**
   * Registers assets with the Assets resolver. This method maps keys (aliases) to asset sources,
   * allowing you to load assets using friendly names instead of direct URLs.
   * @param assets - The unresolved assets to add to the resolver
   * @example
   * ```ts
   * // Basic usage - single asset
   * Assets.add({
   *     alias: 'myTexture',
   *     src: 'assets/texture.png'
   * });
   * const texture = await Assets.load('myTexture');
   *
   * // Multiple aliases for the same asset
   * Assets.add({
   *     alias: ['hero', 'player'],
   *     src: 'hero.png'
   * });
   * const hero1 = await Assets.load('hero');
   * const hero2 = await Assets.load('player'); // Same texture
   *
   * // Multiple format support
   * Assets.add({
   *     alias: 'character',
   *     src: 'character.{webp,png}' // Will choose best format
   * });
   * Assets.add({
   *     alias: 'character',
   *     src: ['character.webp', 'character.png'], // Explicitly specify formats
   * });
   *
   * // With texture options
   * Assets.add({
   *     alias: 'sprite',
   *     src: 'sprite.png',
   *     data: { scaleMode: 'nearest' }
   * });
   *
   * // Multiple assets at once
   * Assets.add([
   *     { alias: 'bg', src: 'background.png' },
   *     { alias: 'music', src: 'music.mp3' },
   *     { alias: 'spritesheet', src: 'sheet.json', data: { ignoreMultiPack: false } }
   * ]);
   * ```
   * @remarks
   * - Assets are resolved when loaded, not when added
   * - Multiple formats use the best available format for the browser
   * - Adding with same alias overwrites previous definition
   * - The `data` property is passed to the asset loader
   * @see {@link Resolver} For details on asset resolution
   * @see {@link LoaderParser} For asset-specific data options
   * @advanced
   */
  add(assets) {
    this.resolver.add(assets);
  }
  async load(urls, onProgress) {
    if (!this._initialized) {
      await this.init();
    }
    const singleAsset = isSingleItem(urls);
    const urlArray = convertToList(urls).map((url) => {
      if (typeof url !== "string") {
        const aliases = this.resolver.getAlias(url);
        if (aliases.some((alias) => !this.resolver.hasKey(alias))) {
          this.add(url);
        }
        return Array.isArray(aliases) ? aliases[0] : aliases;
      }
      if (!this.resolver.hasKey(url))
        this.add({ alias: url, src: url });
      return url;
    });
    const resolveResults = this.resolver.resolve(urlArray);
    const out = await this._mapLoadToResolve(resolveResults, onProgress);
    return singleAsset ? out[urlArray[0]] : out;
  }
  /**
   * Registers a bundle of assets that can be loaded as a group. Bundles are useful for organizing
   * assets into logical groups, such as game levels or UI screens.
   * @param bundleId - Unique identifier for the bundle
   * @param assets - Assets to include in the bundle
   * @example
   * ```ts
   * // Add a bundle using array format
   * Assets.addBundle('animals', [
   *     { alias: 'bunny', src: 'bunny.png' },
   *     { alias: 'chicken', src: 'chicken.png' },
   *     { alias: 'thumper', src: 'thumper.png' },
   * ]);
   *
   * // Add a bundle using object format
   * Assets.addBundle('animals', {
   *     bunny: 'bunny.png',
   *     chicken: 'chicken.png',
   *     thumper: 'thumper.png',
   * });
   *
   * // Add a bundle with advanced options
   * Assets.addBundle('ui', [
   *     {
   *         alias: 'button',
   *         src: 'button.{webp,png}',
   *         data: { scaleMode: 'nearest' }
   *     },
   *     {
   *         alias: ['logo', 'brand'],  // Multiple aliases
   *         src: 'logo.svg',
   *         data: { resolution: 2 }
   *     }
   * ]);
   *
   * // Load the bundle
   * await Assets.loadBundle('animals');
   *
   * // Use the loaded assets
   * const bunny = Sprite.from('bunny');
   * const chicken = Sprite.from('chicken');
   * ```
   * @remarks
   * - Bundle IDs must be unique
   * - Assets in bundles are not loaded until `loadBundle` is called
   * - Bundles can be background loaded using `backgroundLoadBundle`
   * - Assets in bundles can be loaded individually using their aliases
   * @see {@link Assets.loadBundle} For loading bundles
   * @see {@link Assets.backgroundLoadBundle} For background loading bundles
   * @see {@link Assets.unloadBundle} For unloading bundles
   * @see {@link AssetsManifest} For manifest format details
   */
  addBundle(bundleId, assets) {
    this.resolver.addBundle(bundleId, assets);
  }
  /**
   * Loads a bundle or multiple bundles of assets. Bundles are collections of related assets
   * that can be loaded together.
   * @param bundleIds - Single bundle ID or array of bundle IDs to load
   * @param onProgress - Optional callback for load progress (0.0 to 1.0)
   * @returns Promise that resolves with the loaded bundle assets
   * @example
   * ```ts
   * // Define bundles in your manifest
   * const manifest = {
   *     bundles: [
   *         {
   *             name: 'load-screen',
   *             assets: [
   *                 {
   *                     alias: 'background',
   *                     src: 'sunset.png',
   *                 },
   *                 {
   *                     alias: 'bar',
   *                     src: 'load-bar.{png,webp}', // use an array of individual assets
   *                 },
   *             ],
   *         },
   *         {
   *             name: 'game-screen',
   *             assets: [
   *                 {
   *                     alias: 'character',
   *                     src: 'robot.png',
   *                 },
   *                 {
   *                     alias: 'enemy',
   *                     src: 'bad-guy.png',
   *                 },
   *             ],
   *         },
   *     ]
   * };
   *
   * // Initialize with manifest
   * await Assets.init({ manifest });
   *
   * // Or add bundles programmatically
   * Assets.addBundle('load-screen', [...]);
   * Assets.loadBundle('load-screen');
   *
   * // Load a single bundle
   * await Assets.loadBundle('load-screen');
   * const bg = Sprite.from('background'); // Uses alias from bundle
   *
   * // Load multiple bundles
   * await Assets.loadBundle([
   *     'load-screen',
   *     'game-screen'
   * ]);
   *
   * // Load with progress tracking
   * await Assets.loadBundle('game-screen', (progress) => {
   *     console.log(`Loading: ${Math.round(progress * 100)}%`);
   * });
   * ```
   * @remarks
   * - Bundle assets are cached automatically
   * - Bundles can be pre-loaded using `backgroundLoadBundle`
   * - Assets in bundles can be accessed by their aliases
   * - Progress callback receives values from 0.0 to 1.0
   * @throws {Error} If the bundle ID doesn't exist in the manifest
   * @see {@link Assets.addBundle} For adding bundles programmatically
   * @see {@link Assets.backgroundLoadBundle} For background loading bundles
   * @see {@link Assets.unloadBundle} For unloading bundles
   * @see {@link AssetsManifest} For manifest format details
   */
  async loadBundle(bundleIds, onProgress) {
    if (!this._initialized) {
      await this.init();
    }
    let singleAsset = false;
    if (typeof bundleIds === "string") {
      singleAsset = true;
      bundleIds = [bundleIds];
    }
    const resolveResults = this.resolver.resolveBundle(bundleIds);
    const out = {};
    const keys = Object.keys(resolveResults);
    let total = 0;
    const counts = [];
    const _onProgress = () => {
      onProgress?.(counts.reduce((a, b) => a + b, 0) / total);
    };
    const promises = keys.map((bundleId, i) => {
      const resolveResult = resolveResults[bundleId];
      const values = Object.values(resolveResult);
      const totalAssetsToLoad = [...new Set(values.flat())];
      const progressSize = totalAssetsToLoad.reduce((sum, asset) => sum + (asset.progressSize || 1), 0);
      counts.push(0);
      total += progressSize;
      return this._mapLoadToResolve(resolveResult, (e) => {
        counts[i] = e * progressSize;
        _onProgress();
      }).then((resolveResult2) => {
        out[bundleId] = resolveResult2;
      });
    });
    await Promise.all(promises);
    return singleAsset ? out[bundleIds[0]] : out;
  }
  /**
   * Initiates background loading of assets. This allows assets to be loaded passively while other operations
   * continue, making them instantly available when needed later.
   *
   * Background loading is useful for:
   * - Preloading game levels while in a menu
   * - Loading non-critical assets during gameplay
   * - Reducing visible loading screens
   * @param urls - Single URL/alias or array of URLs/aliases to load in the background
   * @example
   * ```ts
   * // Basic background loading
   * Assets.backgroundLoad('images/level2-assets.png');
   *
   * // Background load multiple assets
   * Assets.backgroundLoad([
   *     'images/sprite1.png',
   *     'images/sprite2.png',
   *     'images/background.png'
   * ]);
   *
   * // Later, when you need the assets
   * const textures = await Assets.load([
   *     'images/sprite1.png',
   *     'images/sprite2.png'
   * ]); // Resolves immediately if background loading completed
   * ```
   * @remarks
   * - Background loading happens one asset at a time to avoid blocking the main thread
   * - Loading can be interrupted safely by calling `Assets.load()`
   * - Assets are cached as they complete loading
   * - No progress tracking is available for background loading
   */
  async backgroundLoad(urls) {
    if (!this._initialized) {
      await this.init();
    }
    if (typeof urls === "string") {
      urls = [urls];
    }
    const resolveResults = this.resolver.resolve(urls);
    this._backgroundLoader.add(Object.values(resolveResults));
  }
  /**
   * Initiates background loading of asset bundles. Similar to backgroundLoad but works with
   * predefined bundles of assets.
   *
   * Perfect for:
   * - Preloading level bundles during gameplay
   * - Loading UI assets during splash screens
   * - Preparing assets for upcoming game states
   * @param bundleIds - Single bundle ID or array of bundle IDs to load in the background
   * @example
   * ```ts
   * // Define bundles in your manifest
   * await Assets.init({
   *     manifest: {
   *         bundles: [
   *             {
   *               name: 'home',
   *               assets: [
   *                 {
   *                     alias: 'background',
   *                     src: 'images/home-bg.png',
   *                 },
   *                 {
   *                     alias: 'logo',
   *                     src: 'images/logo.png',
   *                 }
   *              ]
   *            },
   *            {
   *             name: 'level-1',
   *             assets: [
   *                 {
   *                     alias: 'background',
   *                     src: 'images/level1/bg.png',
   *                 },
   *                 {
   *                     alias: 'sprites',
   *                     src: 'images/level1/sprites.json'
   *                 }
   *             ]
   *         }]
   *     }
   * });
   *
   * // Load the home screen assets right away
   * await Assets.loadBundle('home');
   * showHomeScreen();
   *
   * // Start background loading while showing home screen
   * Assets.backgroundLoadBundle('level-1');
   *
   * // When player starts level, load completes faster
   * await Assets.loadBundle('level-1');
   * hideHomeScreen();
   * startLevel();
   * ```
   * @remarks
   * - Bundle assets are loaded one at a time
   * - Loading can be interrupted safely by calling `Assets.loadBundle()`
   * - Assets are cached as they complete loading
   * - Requires bundles to be registered via manifest or `addBundle`
   * @see {@link Assets.addBundle} For adding bundles programmatically
   * @see {@link Assets.loadBundle} For immediate bundle loading
   * @see {@link AssetsManifest} For manifest format details
   */
  async backgroundLoadBundle(bundleIds) {
    if (!this._initialized) {
      await this.init();
    }
    if (typeof bundleIds === "string") {
      bundleIds = [bundleIds];
    }
    const resolveResults = this.resolver.resolveBundle(bundleIds);
    Object.values(resolveResults).forEach((resolveResult) => {
      this._backgroundLoader.add(Object.values(resolveResult));
    });
  }
  /**
   * Only intended for development purposes.
   * This will wipe the resolver and caches.
   * You will need to reinitialize the Asset
   * @internal
   */
  reset() {
    this.resolver.reset();
    this.loader.reset();
    this.cache.reset();
    this._initialized = false;
  }
  get(keys) {
    if (typeof keys === "string") {
      return Cache.get(keys);
    }
    const assets = {};
    for (let i = 0; i < keys.length; i++) {
      assets[i] = Cache.get(keys[i]);
    }
    return assets;
  }
  /**
   * helper function to map resolved assets back to loaded assets
   * @param resolveResults - the resolve results from the resolver
   * @param progressOrLoadOptions - the progress callback or load options
   */
  async _mapLoadToResolve(resolveResults, progressOrLoadOptions) {
    const resolveArray = [...new Set(Object.values(resolveResults))];
    this._backgroundLoader.active = false;
    const loadedAssets = await this.loader.load(resolveArray, progressOrLoadOptions);
    this._backgroundLoader.active = true;
    const out = {};
    resolveArray.forEach((resolveResult) => {
      const asset = loadedAssets[resolveResult.src];
      const keys = [resolveResult.src];
      if (resolveResult.alias) {
        keys.push(...resolveResult.alias);
      }
      keys.forEach((key) => {
        out[key] = asset;
      });
      Cache.set(keys, asset);
    });
    return out;
  }
  /**
   * Unloads assets and releases them from memory. This method ensures proper cleanup of
   * loaded assets when they're no longer needed.
   * @param urls - Single URL/alias or array of URLs/aliases to unload
   * @example
   * ```ts
   * // Unload a single asset
   * await Assets.unload('images/sprite.png');
   *
   * // Unload using an alias
   * await Assets.unload('hero'); // Unloads the asset registered with 'hero' alias
   *
   * // Unload multiple assets
   * await Assets.unload([
   *     'images/background.png',
   *     'images/character.png',
   *     'hero'
   * ]);
   *
   * // Unload and handle creation of new instances
   * await Assets.unload('hero');
   * const newHero = await Assets.load('hero'); // Will load fresh from source
   * ```
   * @remarks
   * > [!WARNING]
   * > Make sure assets aren't being used before unloading:
   * > - Remove sprites using the texture
   * > - Clear any references to the asset
   * > - Textures will be destroyed and can't be used after unloading
   * @throws {Error} If the asset is not found in cache
   */
  async unload(urls) {
    if (!this._initialized) {
      await this.init();
    }
    const urlArray = convertToList(urls).map((url) => typeof url !== "string" ? url.src : url);
    const resolveResults = this.resolver.resolve(urlArray);
    await this._unloadFromResolved(resolveResults);
  }
  /**
   * Unloads all assets in a bundle. Use this to free memory when a bundle's assets
   * are no longer needed, such as when switching game levels.
   * @param bundleIds - Single bundle ID or array of bundle IDs to unload
   * @example
   * ```ts
   * // Define and load a bundle
   * Assets.addBundle('level-1', {
   *     background: 'level1/bg.png',
   *     sprites: 'level1/sprites.json',
   *     music: 'level1/music.mp3'
   * });
   *
   * // Load the bundle
   * const level1 = await Assets.loadBundle('level-1');
   *
   * // Use the assets
   * const background = Sprite.from(level1.background);
   *
   * // When done with the level, unload everything
   * await Assets.unloadBundle('level-1');
   * // background sprite is now invalid!
   *
   * // Unload multiple bundles
   * await Assets.unloadBundle([
   *     'level-1',
   *     'level-2',
   *     'ui-elements'
   * ]);
   * ```
   * @remarks
   * > [!WARNING]
   * > - All assets in the bundle will be destroyed
   * > - Bundle needs to be reloaded to use assets again
   * > - Make sure no sprites or other objects are using the assets
   * @throws {Error} If the bundle is not found
   * @see {@link Assets.addBundle} For adding bundles
   * @see {@link Assets.loadBundle} For loading bundles
   */
  async unloadBundle(bundleIds) {
    if (!this._initialized) {
      await this.init();
    }
    bundleIds = convertToList(bundleIds);
    const resolveResults = this.resolver.resolveBundle(bundleIds);
    const promises = Object.keys(resolveResults).map((bundleId) => this._unloadFromResolved(resolveResults[bundleId]));
    await Promise.all(promises);
  }
  async _unloadFromResolved(resolveResult) {
    const resolveArray = Object.values(resolveResult);
    resolveArray.forEach((resolveResult2) => {
      Cache.remove(resolveResult2.src);
    });
    await this.loader.unload(resolveArray);
  }
  /**
   * Detects the supported formats for the browser, and returns an array of supported formats, respecting
   * the users preferred formats order.
   * @param options - the options to use when detecting formats
   * @param options.preferredFormats - the preferred formats to use
   * @param options.skipDetections - if we should skip the detections altogether
   * @param options.detections - the detections to use
   * @returns - the detected formats
   */
  async _detectFormats(options) {
    let formats = [];
    if (options.preferredFormats) {
      formats = Array.isArray(options.preferredFormats) ? options.preferredFormats : [options.preferredFormats];
    }
    for (const detection of options.detections) {
      if (options.skipDetections || await detection.test()) {
        formats = await detection.add(formats);
      } else if (!options.skipDetections) {
        formats = await detection.remove(formats);
      }
    }
    formats = formats.filter((format, index) => formats.indexOf(format) === index);
    return formats;
  }
  /**
   * All the detection parsers currently added to the Assets class.
   * @advanced
   */
  get detections() {
    return this._detections;
  }
  /**
   * Sets global preferences for asset loading behavior. This method configures how assets
   * are loaded and processed across all parsers.
   * @param preferences - Asset loading preferences
   * @example
   * ```ts
   * // Basic preferences
   * Assets.setPreferences({
   *     crossOrigin: 'anonymous',
   *     parseAsGraphicsContext: false
   * });
   * ```
   * @remarks
   * Preferences are applied to all compatible parsers and affect future asset loading.
   * Common preferences include:
   * - `crossOrigin`: CORS setting for loaded assets
   * - `preferWorkers`: Whether to use web workers for loading textures
   * - `preferCreateImageBitmap`: Use `createImageBitmap` for texture creation. Turning this off will use the `Image` constructor instead.
   * @see {@link AssetsPreferences} For all available preferences
   */
  setPreferences(preferences) {
    this.loader.parsers.forEach((parser) => {
      if (!parser.config)
        return;
      Object.keys(parser.config).filter((key) => key in preferences).forEach((key) => {
        parser.config[key] = preferences[key];
      });
    });
  }
}
const Assets = new AssetsClass();
extensions.handleByList(ExtensionType.LoadParser, Assets.loader.parsers).handleByList(ExtensionType.ResolveParser, Assets.resolver.parsers).handleByList(ExtensionType.CacheParser, Assets.cache.parsers).handleByList(ExtensionType.DetectionParser, Assets.detections);
extensions.add(
  cacheTextureArray,
  detectDefaults,
  detectAvif,
  detectWebp,
  detectMp4,
  detectOgv,
  detectWebm,
  loadJson,
  loadTxt,
  loadWebFont,
  loadSvg,
  loadTextures,
  loadVideoTextures,
  loadBitmapFont,
  bitmapFontCachePlugin,
  resolveTextureUrl,
  resolveJsonUrl
);
const assetKeyMap = {
  loader: ExtensionType.LoadParser,
  resolver: ExtensionType.ResolveParser,
  cache: ExtensionType.CacheParser,
  detection: ExtensionType.DetectionParser
};
extensions.handle(ExtensionType.Asset, (extension) => {
  const ref = extension.ref;
  Object.entries(assetKeyMap).filter(([key]) => !!ref[key]).forEach(([key, type]) => extensions.add(Object.assign(
    ref[key],
    // Allow the function to optionally define it's own
    // ExtensionMetadata, the use cases here is priority for LoaderParsers
    { extension: ref[key].extension ?? type }
  )));
}, (extension) => {
  const ref = extension.ref;
  Object.keys(assetKeyMap).filter((key) => !!ref[key]).forEach((key) => extensions.remove(ref[key]));
});

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 { context, roundPixels, ...rest } = options || {};
    super({
      label: "Graphics",
      ...rest
    });
    /** @internal */
    this.renderPipeId = "graphics";
    if (!context) {
      this.context = this._ownedContext = new GraphicsContext();
      this.context.autoGarbageCollect = this.autoGarbageCollect;
    } else {
      this.context = context;
    }
    this.didViewUpdate = true;
    this.allowChildren = false;
    this.roundPixels = roundPixels ?? false;
  }
  set context(context) {
    if (context === this._context)
      return;
    if (this._context) {
      this._context.off("update", this.onViewUpdate, this);
      this._context.off("unload", this.unload, this);
    }
    this._context = context;
    this._context.on("update", this.onViewUpdate, this);
    this._context.on("unload", this.unload, 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?.context === true) {
      this._context.destroy(options);
    }
    this._ownedContext = null;
    this._context = null;
    super.destroy(options);
  }
  /**
   * @param now - The current time in milliseconds.
   * @internal
   */
  _onTouch(now) {
    this._gcLastUsed = now;
    this._context._gcLastUsed = now;
  }
  _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);
  }
}

class RenderTexture extends Texture {
  /**
   * Creates a RenderTexture. Pass `dynamic: true` in options to allow resizing after creation.
   * @param options - Options for the RenderTexture, including width, height, and dynamic.
   * @returns A new RenderTexture instance.
   * @example
   * const rt = RenderTexture.create({ width: 100, height: 100, dynamic: true });
   * rt.resize(500, 500);
   */
  static create(options) {
    const { dynamic, ...rest } = options;
    return new RenderTexture({
      source: new TextureSource(rest),
      dynamic: dynamic ?? false
    });
  }
  /**
   * Resizes the render texture.
   * @param width - The new width of the render texture.
   * @param height - The new height of the render texture.
   * @param resolution - The new resolution of the render texture.
   * @returns This texture.
   */
  resize(width, height, resolution) {
    this.source.resize(width, height, resolution);
    return this;
  }
}

var DEPRECATED_WRAP_MODES = /* @__PURE__ */ ((DEPRECATED_WRAP_MODES2) => {
  DEPRECATED_WRAP_MODES2["CLAMP"] = "clamp-to-edge";
  DEPRECATED_WRAP_MODES2["REPEAT"] = "repeat";
  DEPRECATED_WRAP_MODES2["MIRRORED_REPEAT"] = "mirror-repeat";
  return DEPRECATED_WRAP_MODES2;
})(DEPRECATED_WRAP_MODES || {});
new Proxy(DEPRECATED_WRAP_MODES, {
  get(target, prop) {
    deprecation(v8_0_0, `DRAW_MODES.${prop} is deprecated, use '${DEPRECATED_WRAP_MODES[prop]}' instead`);
    return target[prop];
  }
});
var DEPRECATED_SCALE_MODES = /* @__PURE__ */ ((DEPRECATED_SCALE_MODES2) => {
  DEPRECATED_SCALE_MODES2["NEAREST"] = "nearest";
  DEPRECATED_SCALE_MODES2["LINEAR"] = "linear";
  return DEPRECATED_SCALE_MODES2;
})(DEPRECATED_SCALE_MODES || {});
const SCALE_MODES = new Proxy(DEPRECATED_SCALE_MODES, {
  get(target, prop) {
    deprecation(v8_0_0, `DRAW_MODES.${prop} is deprecated, use '${DEPRECATED_SCALE_MODES[prop]}' instead`);
    return target[prop];
  }
});

extensions.add(browserExt, webworkerExt);

function clamp(n, min, max) {
  return n < min ? min : n > max ? max : n;
}
function get_canvas_blob(renderer, obj, bounds) {
  return new Promise((resolve) => {
    if (!obj) {
      resolve(null);
      return;
    }
    const image_bounds = obj.getLocalBounds();
    const frame = bounds ? new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height) : new Rectangle(0, 0, image_bounds.width, image_bounds.height);
    const src_canvas = renderer.extract.canvas({
      target: obj,
      resolution: 1,
      frame
    });
    src_canvas.toBlob?.((blob) => {
      if (!blob) {
        resolve(null);
      }
      resolve(blob);
    });
  });
}

var root$6 = from_html(`<div class="color-gradient svelte-qx84u5"><div class="marker svelte-qx84u5"></div></div> <div class="opacity-slider svelte-qx84u5"><div class="opacity-marker svelte-qx84u5"></div></div> <div class="hue-slider svelte-qx84u5"><div class="marker svelte-qx84u5"></div></div>`, 1);

function ColorPicker($$anchor, $$props) {
	push($$props, false);

	let color = prop($$props, 'color', 12, "rgb(255, 255, 255)");
	let sl_marker_pos = mutable_source([0, 0]);
	let sl_rect = null;
	let sl_moving = false;
	let sl = [0, 0];
	let hue = mutable_source(0);
	let hue_marker_pos = mutable_source(0);
	let hue_rect = null;
	let hue_moving = false;
	let opacity_moving = false;

	function handle_hue_down(event) {
		hue_rect = event.currentTarget.getBoundingClientRect();
		hue_moving = true;
		update_hue_from_mouse(event.clientX);
	}

	function update_hue_from_mouse(x) {
		if (!hue_rect) return;

		const _x = clamp(x - hue_rect.left, 0, hue_rect.width); // get the x-coordinate relative to the box

		set(hue_marker_pos, _x);

		const _hue = _x / hue_rect.width * 360; // scale the x position to a hue value (0-360)

		set(hue, _hue);
		color(hsva_to_rgba({ h: _hue, s: sl[0], v: sl[1], a: 1 }));
	}

	function hsva_to_rgba(hsva) {
		const saturation = hsva.s;
		const value = hsva.v;
		let chroma = saturation * value;
		const hue_by_60 = hsva.h / 60;
		let x = chroma * (1 - Math.abs(hue_by_60 % 2 - 1));
		const m = value - chroma;

		chroma = chroma + m;
		x = x + m;

		const index = Math.floor(hue_by_60) % 6;
		const red = [chroma, x, m, m, x, chroma][index];
		const green = [x, chroma, chroma, x, m, m][index];
		const blue = [m, m, x, chroma, chroma, x][index];

		return `rgba(${red * 255}, ${green * 255}, ${blue * 255}, ${hsva.a})`;
	}

	function update_color_from_mouse(x, y) {
		if (!sl_rect) return;

		const _x = clamp(x - sl_rect.left, 0, sl_rect.width);
		const _y = clamp(y - sl_rect.top, 0, sl_rect.height);

		set(sl_marker_pos, [_x, _y]);

		const _hsva = {
			h: get(hue) * 1,
			s: _x / sl_rect.width,
			v: 1 - _y / sl_rect.height,
			a: 1
		};

		sl = [_hsva.s, _hsva.v];
		color(hsva_to_rgba(_hsva));
	}

	function handle_sl_down(event) {
		sl_moving = true;
		sl_rect = event.currentTarget.getBoundingClientRect();
		update_color_from_mouse(event.clientX, event.clientY);
	}

	function handle_move(event) {
		if (sl_moving) update_color_from_mouse(event.clientX, event.clientY);
		if (hue_moving) update_hue_from_mouse(event.clientX);
		if (opacity_moving) update_opacity_from_mouse(event.clientX);
	}

	function handle_end() {
		if (sl_moving) sl_moving = false;
		if (hue_moving) hue_moving = false;
		if (opacity_moving) opacity_moving = false;
	}

	async function update_mouse_from_color(color) {
		if (sl_moving || hue_moving) return;

		await tick();

		if (!color) return;

		if (!sl_rect) {
			sl_rect = get(sl_wrap).getBoundingClientRect();
		}

		if (!hue_rect) {
			hue_rect = get(hue_wrap).getBoundingClientRect();
		}

		const hsva = tinycolor(color).toHsv();
		const _x = hsva.s * sl_rect.width;
		const _y = (1 - hsva.v) * sl_rect.height;

		set(sl_marker_pos, [_x, _y]);
		sl = [hsva.s, hsva.v];
		set(hue, hsva.h);
		set(hue_marker_pos, hsva.h / 360 * hue_rect.width);
	}

	let sl_wrap = mutable_source();
	let hue_wrap = mutable_source();
	let opacity = prop($$props, 'opacity', 12, 1);
	let opacity_wrap = mutable_source();

	function handle_opacity_down(event) {
		set(opacity_rect, event.currentTarget.getBoundingClientRect());
		opacity_moving = true;
		update_opacity_from_mouse(event.clientX);
	}

	let opacity_rect = mutable_source(null);

	function update_opacity_from_mouse(x) {
		if (!get(opacity_rect)) return;

		const _x = clamp(x - get(opacity_rect).left, 0, get(opacity_rect).width);

		opacity(_x / get(opacity_rect).width);
	}

	onMount(() => {
		set(opacity_rect, get(opacity_wrap).getBoundingClientRect());
	});

	legacy_pre_effect(() => (deep_read_state(color())), () => {
		update_mouse_from_color(color());
	});

	legacy_pre_effect_reset();

	var $$exports = {
		get color() {
			return color();
		},

		set color($$value) {
			color($$value);
			flushSync();
		},

		get opacity() {
			return opacity();
		},

		set opacity($$value) {
			opacity($$value);
			flushSync();
		}
	};

	init$1();

	var fragment = root$6();

	event('mousemove', $window, handle_move);
	event('mouseup', $window, handle_end);

	var div = first_child(fragment);
	var div_1 = child(div);
	let styles;

	reset(div);
	bind_this(div, ($$value) => set(sl_wrap, $$value), () => get(sl_wrap));

	var div_2 = sibling(div, 2);
	var div_3 = child(div_2);
	let styles_1;

	reset(div_2);
	bind_this(div_2, ($$value) => set(opacity_wrap, $$value), () => get(opacity_wrap));

	var div_4 = sibling(div_2, 2);
	var div_5 = child(div_4);
	let styles_2;

	reset(div_4);
	bind_this(div_4, ($$value) => set(hue_wrap, $$value), () => get(hue_wrap));

	template_effect(() => {
		set_style(div, `--hue:${get(hue) ?? ''}`);

		styles = set_style(div_1, '', styles, {
			transform: `translate(${(
				get(sl_marker_pos),
				untrack(() => get(sl_marker_pos)[0])
			) ?? ''}px,${(
				get(sl_marker_pos),
				untrack(() => get(sl_marker_pos)[1])
			) ?? ''}px)`,
			background: color()
		});

		set_style(div_2, `--color:${(color() === 'auto' ? 'transparent' : color()) ?? ''}`);

		styles_1 = set_style(div_3, '', styles_1, {
			background: 'white',
			transform: `translateX(${(
				deep_read_state(opacity()),
				get(opacity_rect),
				untrack(() => opacity() * (get(opacity_rect)?.width ?? 1))
			) ?? ''}px)`
		});

		styles_2 = set_style(div_5, '', styles_2, {
			background: "hsl(" + get(hue) + ", 100%, 50%)",
			transform: `translateX(${get(hue_marker_pos) ?? ''}px)`
		});
	});

	event('mousedown', div, handle_sl_down);
	event('mousedown', div_2, handle_opacity_down);
	event('mousedown', div_4, handle_hue_down);
	append($$anchor, fragment);

	return pop($$exports);
}

var root_1$4 = from_html(`<span></span>`);
var root_4$2 = from_html(`<button></button>`);
var root_2$5 = from_html(`<div class="swatch svelte-q8yof7"><!> <!> <button></button></div>`);
var root_5$3 = from_html(`<button></button>`);
var root$5 = from_html(`<!> <div class="swatch-wrap svelte-q8yof7"><div class="swatch-container svelte-q8yof7"><!> <menu class="swatch svelte-q8yof7"></menu></div></div>`, 1);

function ColorSwatch($$anchor, $$props) {
	push($$props, false);

	const _colors = mutable_source();
	const selected_opacity = mutable_source();
	let selected_color = prop($$props, 'selected_color', 12);
	let colors = prop($$props, 'colors', 12);
	let user_colors = prop($$props, 'user_colors', 28, () => []);
	let show_empty = prop($$props, 'show_empty', 12, false);
	let current_mode = prop($$props, 'current_mode', 12, "hex");
	let color_picker = prop($$props, 'color_picker', 12, false);
	const dispatch = createEventDispatcher();

	function get_color(color_input) {
		if (Array.isArray(color_input)) {
			return color_input[0];
		}

		return color_input;
	}

	function get_opacity(color_input) {
		if (Array.isArray(color_input)) {
			return color_input[1];
		}

		const color = tinycolor(color_input);

		return color.getAlpha();
	}

	function get_formatted_color(color, mode) {
		const color_value = get_color(color);

		if (mode === "hex") {
			return tinycolor(color_value).toHexString();
		} else if (mode === "rgb") {
			return tinycolor(color_value).toRgbString();
		}

		return tinycolor(color_value).toHslString();
	}

	`select-${colors().findIndex((c) => get_formatted_color(c, current_mode()) === get_formatted_color(selected_color(), current_mode()))}`;

	function handle_select(type, detail) {
		`${type}-${detail.index}`;
		dispatch(type, detail);
	}

	function handle_picker_click() {
		dispatch("select", { index: null, color: selected_color() });
		color_picker(!color_picker());
	}

	legacy_pre_effect(() => (deep_read_state(show_empty()), deep_read_state(colors())), () => {
		set(_colors, show_empty() ? colors() : colors().filter((c) => c));
	});

	legacy_pre_effect(() => (deep_read_state(selected_color())), () => {
		set(selected_opacity, get_opacity(selected_color()));
	});

	legacy_pre_effect_reset();

	var $$exports = {
		get selected_color() {
			return selected_color();
		},

		set selected_color($$value) {
			selected_color($$value);
			flushSync();
		},

		get colors() {
			return colors();
		},

		set colors($$value) {
			colors($$value);
			flushSync();
		},

		get user_colors() {
			return user_colors();
		},

		set user_colors($$value) {
			user_colors($$value);
			flushSync();
		},

		get show_empty() {
			return show_empty();
		},

		set show_empty($$value) {
			show_empty($$value);
			flushSync();
		},

		get current_mode() {
			return current_mode();
		},

		set current_mode($$value) {
			current_mode($$value);
			flushSync();
		},

		get color_picker() {
			return color_picker();
		},

		set color_picker($$value) {
			color_picker($$value);
			flushSync();
		}
	};

	init$1();

	var fragment = root$5();
	var node = first_child(fragment);

	{
		var consequent = ($$anchor) => {
			var span = root_1$4();
			let classes;

			template_effect(() => classes = set_class(span, 1, 'svelte-q8yof7', null, classes, { lg: user_colors() }));
			append($$anchor, span);
		};

		if_block(node, ($$render) => {
			if (!color_picker()) $$render(consequent);
		});
	}

	var div = sibling(node, 2);
	var div_1 = child(div);
	var node_1 = child(div_1);

	{
		var consequent_2 = ($$anchor) => {
			var div_2 = root_2$5();
			var node_2 = child(div_2);

			{
				var consequent_1 = ($$anchor) => {
					{
						let $0 = derived_safe_equal(() => (
							deep_read_state(selected_color()),
							untrack(() => get_color(selected_color()))
						));

						IconButton($$anchor, {
							get Icon() {
								return Plus;
							},
							onclick: () => dispatch("add_color"),
							roundedness: 'very',
							get background() {
								return get($0);
							},
							color: 'white'
						});
					}
				};

				if_block(node_2, ($$render) => {
					if (color_picker()) $$render(consequent_1);
				});
			}

			var node_3 = sibling(node_2, 2);

			each(node_3, 1, user_colors, index, ($$anchor, color, i) => {
				const color_string = derived_safe_equal(() => (get(color), untrack(() => get_color(get(color)))));
				const opacity = derived_safe_equal(() => (get(color), untrack(() => get_opacity(get(color)))));
				var button = root_4$2();
				let classes_1;
				let styles;

				template_effect(() => {
					classes_1 = set_class(button, 1, 'color svelte-q8yof7', null, classes_1, {
						empty: get(color) === null,
						selected: `${get(color_string)}-${get(opacity)}` === `${selected_color()}-${get(selected_opacity)}`
					});

					styles = set_style(button, `background-color: ${get(color_string) ?? ''}`, styles, { opacity: get(opacity) });
				});

				event('click', button, () => handle_select("edit", {
					index: i,
					color: get(color_string),
					opacity: get(opacity)
				}));

				append($$anchor, button);
			});

			var button_1 = sibling(node_3, 2);
			let classes_2;

			reset(div_2);
			template_effect(() => classes_2 = set_class(button_1, 1, 'color colorpicker svelte-q8yof7', null, classes_2, { hidden: !color_picker() }));
			event('click', button_1, handle_picker_click);
			append($$anchor, div_2);
		};

		if_block(node_1, ($$render) => {
			if (user_colors()) $$render(consequent_2);
		});
	}

	var menu = sibling(node_1, 2);

	each(menu, 5, () => get(_colors), index, ($$anchor, color_item, i) => {
		const color_string = derived_safe_equal(() => (
			get(color_item),
			untrack(() => get_color(get(color_item)))
		));

		const opacity = derived_safe_equal(() => (
			get(color_item),
			untrack(() => get_opacity(get(color_item)))
		));

		var button_2 = root_5$3();
		let classes_3;
		let styles_1;

		template_effect(() => {
			classes_3 = set_class(button_2, 1, 'color svelte-q8yof7', null, classes_3, {
				empty: get(color_item) === null,
				selected: `${get(color_string)}-${get(opacity)}` === `${selected_color()}-${get(selected_opacity)}`
			});

			styles_1 = set_style(button_2, `background-color: ${get(color_string) ?? ''}`, styles_1, { opacity: get(opacity) });
		});

		event('click', button_2, () => handle_select("select", {
			index: i,
			color: get(color_string),
			opacity: get(opacity)
		}));

		append($$anchor, button_2);
	});

	reset(menu);
	reset(div_1);
	reset(div);
	append($$anchor, fragment);

	return pop($$exports);
}

var root_2$4 = from_html(`<button> </button>`);
var root$4 = from_html(`<div class="input svelte-yn52fn"><button class="swatch svelte-yn52fn"></button> <div><div class="input-wrap svelte-yn52fn"><input type="text" class="svelte-yn52fn"/> <button class="eyedropper svelte-yn52fn"><!></button></div> <div class="buttons svelte-yn52fn"></div></div></div>`);

function ColorField($$anchor, $$props) {
	push($$props, false);

	const color_string = mutable_source();
	let color = prop($$props, 'color', 12);
	let current_mode = prop($$props, 'current_mode', 12, "hex");
	const dispatch = createEventDispatcher();
	const modes = [["Hex", "hex"], ["RGB", "rgb"], ["HSL", "hsl"]];

	function format_color(color, mode) {
		if (mode === "hex") {
			return tinycolor(color).toHexString();
		} else if (mode === "rgb") {
			return tinycolor(color).toRgbString();
		}

		return tinycolor(color).toHslString();
	}

	function request_eyedropper() {
		// @ts-ignore
		const eyeDropper = new EyeDropper();

		eyeDropper.open().then((result) => {
			color(result.sRGBHex);
		});
	}

	//@ts-ignore
	const eyedropper_supported = !!window.EyeDropper;

	function handle_click() {
		dispatch("selected", get(color_string));
		dispatch("close");
	}

	legacy_pre_effect(
		() => (
			deep_read_state(color()),
			deep_read_state(current_mode())
		),
		() => {
			set(color_string, format_color(color(), current_mode()));
		}
	);

	legacy_pre_effect(() => (get(color_string)), () => {
		get(color_string) && dispatch("selected", get(color_string));
	});

	legacy_pre_effect_reset();

	var $$exports = {
		get color() {
			return color();
		},

		set color($$value) {
			color($$value);
			flushSync();
		},

		get current_mode() {
			return current_mode();
		},

		set current_mode($$value) {
			current_mode($$value);
			flushSync();
		}
	};

	init$1();

	var div = root$4();
	var button = child(div);
	let styles;
	var div_1 = sibling(button, 2);
	var div_2 = child(div_1);
	var input = child(div_2);

	remove_input_defaults(input);

	var button_1 = sibling(input, 2);
	var node = child(button_1);

	{
		var consequent = ($$anchor) => {
			Eyedropper($$anchor);
		};

		if_block(node, ($$render) => {
			if (eyedropper_supported) $$render(consequent);
		});
	}

	reset(button_1);
	reset(div_2);

	var div_3 = sibling(div_2, 2);

	each(div_3, 5, () => modes, index, ($$anchor, $$item) => {
		var $$array = user_derived(() => to_array(get($$item), 2));
		let label = () => get($$array)[0];
		let value = () => get($$array)[1];
		var button_2 = root_2$4();
		let classes;
		var text = child(button_2, true);

		reset(button_2);

		template_effect(() => {
			classes = set_class(button_2, 1, 'button svelte-yn52fn', null, classes, { active: current_mode() === value() });
			set_text(text, label());
		});

		event('click', button_2, () => current_mode(value()));
		append($$anchor, button_2);
	});

	reset(div_3);
	reset(div_1);
	reset(div);

	template_effect(() => {
		styles = set_style(button, '', styles, { background: color() });
		set_value(input, get(color_string));
	});

	event('click', button, handle_click);
	event('change', input, (e) => color(e.currentTarget.value));
	event('click', button_1, request_eyedropper);
	append($$anchor, div);

	return pop($$exports);
}

var root$3 = from_html(`<div class="wrap svelte-1y25b79"><span class="svelte-1y25b79"><!></span> <input type="range" class="svelte-1y25b79"/></div>`);

function BrushSize_1($$anchor, $$props) {
	push($$props, false);

	let selected_size = prop($$props, 'selected_size', 12);
	let min = prop($$props, 'min', 12);
	let max = prop($$props, 'max', 12);
	const dispatch = createEventDispatcher();

	var $$exports = {
		get selected_size() {
			return selected_size();
		},

		set selected_size($$value) {
			selected_size($$value);
			flushSync();
		},

		get min() {
			return min();
		},

		set min($$value) {
			min($$value);
			flushSync();
		},

		get max() {
			return max();
		},

		set max($$value) {
			max($$value);
			flushSync();
		}
	};

	init$1();

	var div = root$3();
	var span = child(div);
	var node = child(span);

	BrushSize(node);
	reset(span);

	var input = sibling(span, 2);

	remove_input_defaults(input);
	set_attribute(input, 'step', 1);
	reset(div);
	action(div, ($$node, $$action_arg) => click_outside?.($$node, $$action_arg), () => () => dispatch("click_outside"));

	template_effect(() => {
		set_attribute(input, 'min', min());
		set_attribute(input, 'max', max());
	});

	bind_value(input, selected_size);
	append($$anchor, div);

	return pop($$exports);
}

var root_2$3 = from_html(`<!> <!>`, 1);
var root$2 = from_html(`<div><!> <!> <!></div>`);

function BrushOptions($$anchor, $$props) {
	push($$props, false);

	let colors = prop($$props, 'colors', 12);
	let selected_color = prop($$props, 'selected_color', 12);
	let color_mode = prop($$props, 'color_mode', 12, undefined);
	let recent_colors = prop($$props, 'recent_colors', 28, () => []);
	let selected_size = prop($$props, 'selected_size', 12);
	let selected_opacity = prop($$props, 'selected_opacity', 12);
	let show_swatch = prop($$props, 'show_swatch', 12);
	let show_size = prop($$props, 'show_size', 12);
	let mode = prop($$props, 'mode', 12, "brush");
	let color_picker = mutable_source(false);
	let current_mode = mutable_source("hex");
	let editing_index = null;
	const dispatch = createEventDispatcher();

	function handle_color_selection({ index, color, opacity }, type) {
		if (type === "user" && !color) {
			editing_index = index;
			set(color_picker, true);
		}

		if (!color) return;

		selected_color(color);

		if (opacity !== undefined) {
			selected_opacity(opacity);
		}

		if (type === "core") {
			set(color_picker, false);
		}
	}

	function handle_color_change(color) {
		if (editing_index === null) return;

		recent_colors(recent_colors()[editing_index] = color, true);
	}

	let width = mutable_source(0);
	let height = mutable_source(0);

	function debounce(func, delay) {
		let timeout;

		return function (...args) {
			clearTimeout(timeout);
			timeout = setTimeout(() => func(...args), delay);
		};
	}

	let preview = prop($$props, 'preview', 12, false);

	function handle_preview() {
		if (!preview()) {
			preview(true);
			debounced_close_preview();
		} else {
			debounced_close_preview();
		}
	}

	function handle_close_preview() {
		preview(false);
	}

	const debounced_close_preview = debounce(handle_close_preview, 1000);

	function handle_select(color) {
		selected_color(color);
	}

	function handle_add_color() {
		// limit to 5
		if (recent_colors().length >= 5) {
			recent_colors().pop();
		}

		// check if the color is already in the array
		if (recent_colors().some((color) => {
			if (Array.isArray(color)) {
				return color[0] === selected_color() && color[1] === selected_opacity();
			}

			return color === selected_color();
		})) {
			return;
		}

		recent_colors().push([selected_color(), selected_opacity()]);
		recent_colors(recent_colors());
	}

	legacy_pre_effect(() => (deep_read_state(selected_color())), () => {
		handle_color_change(selected_color());
	});

	legacy_pre_effect(
		() => (
			deep_read_state(selected_size()),
			deep_read_state(selected_color())
		),
		() => {
			(selected_size(), selected_color(), handle_preview());
		}
	);

	legacy_pre_effect_reset();

	var $$exports = {
		get colors() {
			return colors();
		},

		set colors($$value) {
			colors($$value);
			flushSync();
		},

		get selected_color() {
			return selected_color();
		},

		set selected_color($$value) {
			selected_color($$value);
			flushSync();
		},

		get color_mode() {
			return color_mode();
		},

		set color_mode($$value) {
			color_mode($$value);
			flushSync();
		},

		get recent_colors() {
			return recent_colors();
		},

		set recent_colors($$value) {
			recent_colors($$value);
			flushSync();
		},

		get selected_size() {
			return selected_size();
		},

		set selected_size($$value) {
			selected_size($$value);
			flushSync();
		},

		get selected_opacity() {
			return selected_opacity();
		},

		set selected_opacity($$value) {
			selected_opacity($$value);
			flushSync();
		},

		get show_swatch() {
			return show_swatch();
		},

		set show_swatch($$value) {
			show_swatch($$value);
			flushSync();
		},

		get show_size() {
			return show_size();
		},

		set show_size($$value) {
			show_size($$value);
			flushSync();
		},

		get mode() {
			return mode();
		},

		set mode($$value) {
			mode($$value);
			flushSync();
		},

		get preview() {
			return preview();
		},

		set preview($$value) {
			preview($$value);
			flushSync();
		}
	};

	init$1();

	var div = root$2();
	let classes;
	var node = child(div);

	{
		var consequent_1 = ($$anchor) => {
			var fragment = comment();
			var node_1 = first_child(fragment);

			{
				var consequent = ($$anchor) => {
					var fragment_1 = root_2$3();
					var node_2 = first_child(fragment_1);

					ColorPicker(node_2, {
						get color() {
							return selected_color();
						},

						set color($$value) {
							selected_color($$value);
						},

						get opacity() {
							return selected_opacity();
						},

						set opacity($$value) {
							selected_opacity($$value);
						},
						$$legacy: true
					});

					var node_3 = sibling(node_2, 2);

					ColorField(node_3, {
						get color() {
							return selected_color();
						},

						get current_mode() {
							return get(current_mode);
						},

						set current_mode($$value) {
							set(current_mode, $$value);
						},

						$$events: {
							close: () => set(color_picker, false),
							selected: ({ detail }) => handle_select(detail)
						},
						$$legacy: true
					});

					append($$anchor, fragment_1);
				};

				if_block(node_1, ($$render) => {
					if (get(color_picker)) $$render(consequent);
				});
			}

			append($$anchor, fragment);
		};

		if_block(node, ($$render) => {
			if (color_mode() === "defaults") $$render(consequent_1);
		});
	}

	var node_4 = sibling(node, 2);

	{
		var consequent_2 = ($$anchor) => {
			{
				let $0 = derived_safe_equal(() => color_mode() === "defaults" ? recent_colors() : null);

				ColorSwatch($$anchor, {
					get colors() {
						return colors();
					},

					get user_colors() {
						return get($0);
					},

					get selected_color() {
						return selected_color();
					},

					get current_mode() {
						return get(current_mode);
					},

					get color_picker() {
						return get(color_picker);
					},

					set color_picker($$value) {
						set(color_picker, $$value);
					},

					$$events: {
						select: ({ detail }) => handle_color_selection(detail, "core"),
						edit: ({ detail }) => handle_color_selection(detail, "user"),
						add_color: handle_add_color
					},
					$$legacy: true
				});
			}
		};

		if_block(node_4, ($$render) => {
			if (show_swatch()) $$render(consequent_2);
		});
	}

	var node_5 = sibling(node_4, 2);

	{
		var consequent_3 = ($$anchor) => {
			BrushSize_1($$anchor, {
				max: 100,
				min: 1,
				get selected_size() {
					return selected_size();
				},

				set selected_size($$value) {
					selected_size($$value);
				},
				$$legacy: true
			});
		};

		if_block(node_5, ($$render) => {
			if (show_size()) $$render(consequent_3);
		});
	}

	reset(div);
	action(div, ($$node, $$action_arg) => click_outside?.($$node, $$action_arg), () => () => dispatch("click_outside"));

	template_effect(() => classes = set_class(div, 1, 'wrap svelte-1l3f0ck', null, classes, {
		padded: !get(color_picker),
		color_picker: get(color_picker),
		size_picker: show_size() && mode() === "brush",
		eraser_picker: mode() === "eraser"
	}));

	bind_window_size('innerHeight', ($$value) => set(height, $$value));
	bind_window_size('innerWidth', ($$value) => set(width, $$value));
	append($$anchor, div);

	return pop($$exports);
}

var root_5$2 = from_html(`<!> <!>`, 1);
var root_8$1 = from_html(`<!> <!> <!>`, 1);
var root_12 = from_html(`<!> <!> <!>`, 1);
var root$1 = from_html(`<div class="toolbar-wrap svelte-1uf24la"><div class="half-container svelte-1uf24la"><!> <!> <!></div> <div><!> <!> <!> <!></div></div>`);

function Toolbar($$anchor, $$props) {
	push($$props, false);

	const can_crop = mutable_source();
	const can_resize = mutable_source();
	const can_upload = mutable_source();
	const can_webcam = mutable_source();
	const can_paste = mutable_source();
	let tool = prop($$props, 'tool', 12, "image");
	let subtool = prop($$props, 'subtool', 12, null);
	let background = prop($$props, 'background', 12, false);
	let brush_options = prop($$props, 'brush_options', 12);
	let selected_size = prop($$props, 'selected_size', 28, () => brush_options() && typeof brush_options().default_size === "number" ? brush_options().default_size : 25);
	let eraser_options = prop($$props, 'eraser_options', 12);
	let selected_eraser_size = prop($$props, 'selected_eraser_size', 28, () => eraser_options() && typeof eraser_options().default_size === "number" ? eraser_options().default_size : 25);

	// Handle default_color including potential color-opacity tuple
	let selected_color = prop($$props, 'selected_color', 28, () => brush_options() && (() => {
		const default_color = brush_options().default_color;

		if (Array.isArray(default_color)) {
			return default_color[0];
		}

		return default_color;
	})());

	// Set default opacity based on default_color if it's a tuple
	let selected_opacity = prop($$props, 'selected_opacity', 28, () => brush_options() && (() => {
		const default_color = brush_options().default_color;

		if (Array.isArray(default_color)) {
			return default_color[1];
		}

		// Check if color string has opacity
		const color = tinycolor(default_color);

		if (color.getAlpha() < 1) {
			return color.getAlpha();
		}

		return 1;
	})());

	let preview = prop($$props, 'preview', 12, false);
	let show_brush_color = prop($$props, 'show_brush_color', 12, false);
	let show_brush_size = prop($$props, 'show_brush_size', 12, false);
	let show_eraser_size = prop($$props, 'show_eraser_size', 12, false);
	let sources = prop($$props, 'sources', 12);
	let transforms = prop($$props, 'transforms', 12);
	let recent_colors = [];
	const dispatch = createEventDispatcher();

	/**
	 * Handles tool click events
	 * @param {Tool} _tool - The selected tool
	 */
	function handle_tool_click(e, _tool) {
		e.stopPropagation();
		dispatch("tool_change", { tool: _tool });
	}

	/**
	 * Handles subtool click events
	 * @param {Event} e - The click event
	 * @param {Subtool} _subtool - The selected subtool
	 */
	function handle_subtool_click(e, _subtool) {
		e.stopPropagation();
		dispatch("subtool_change", { tool: tool(), subtool: _subtool });
	}

	legacy_pre_effect(() => (deep_read_state(tool()), deep_read_state(subtool())), () => {
		show_brush_size(tool() === "draw" && subtool() === "size");
	});

	legacy_pre_effect(() => (deep_read_state(tool()), deep_read_state(subtool())), () => {
		show_brush_color(tool() === "draw" && subtool() === "color");
	});

	legacy_pre_effect(() => (deep_read_state(tool()), deep_read_state(subtool())), () => {
		show_eraser_size(tool() === "erase" && subtool() === "size");
	});

	legacy_pre_effect(() => (deep_read_state(transforms())), () => {
		set(can_crop, transforms().includes("crop"));
	});

	legacy_pre_effect(() => (deep_read_state(transforms())), () => {
		set(can_resize, transforms().includes("resize"));
	});

	legacy_pre_effect(() => (deep_read_state(sources())), () => {
		set(can_upload, sources().includes("upload"));
	});

	legacy_pre_effect(() => (deep_read_state(sources())), () => {
		set(can_webcam, sources().includes("webcam"));
	});

	legacy_pre_effect(() => (deep_read_state(sources())), () => {
		set(can_paste, sources().includes("clipboard"));
	});

	legacy_pre_effect_reset();

	var $$exports = {
		get tool() {
			return tool();
		},

		set tool($$value) {
			tool($$value);
			flushSync();
		},

		get subtool() {
			return subtool();
		},

		set subtool($$value) {
			subtool($$value);
			flushSync();
		},

		get background() {
			return background();
		},

		set background($$value) {
			background($$value);
			flushSync();
		},

		get brush_options() {
			return brush_options();
		},

		set brush_options($$value) {
			brush_options($$value);
			flushSync();
		},

		get selected_size() {
			return selected_size();
		},

		set selected_size($$value) {
			selected_size($$value);
			flushSync();
		},

		get eraser_options() {
			return eraser_options();
		},

		set eraser_options($$value) {
			eraser_options($$value);
			flushSync();
		},

		get selected_eraser_size() {
			return selected_eraser_size();
		},

		set selected_eraser_size($$value) {
			selected_eraser_size($$value);
			flushSync();
		},

		get selected_color() {
			return selected_color();
		},

		set selected_color($$value) {
			selected_color($$value);
			flushSync();
		},

		get selected_opacity() {
			return selected_opacity();
		},

		set selected_opacity($$value) {
			selected_opacity($$value);
			flushSync();
		},

		get preview() {
			return preview();
		},

		set preview($$value) {
			preview($$value);
			flushSync();
		},

		get show_brush_color() {
			return show_brush_color();
		},

		set show_brush_color($$value) {
			show_brush_color($$value);
			flushSync();
		},

		get show_brush_size() {
			return show_brush_size();
		},

		set show_brush_size($$value) {
			show_brush_size($$value);
			flushSync();
		},

		get show_eraser_size() {
			return show_eraser_size();
		},

		set show_eraser_size($$value) {
			show_eraser_size($$value);
			flushSync();
		},

		get sources() {
			return sources();
		},

		set sources($$value) {
			sources($$value);
			flushSync();
		},

		get transforms() {
			return transforms();
		},

		set transforms($$value) {
			transforms($$value);
			flushSync();
		}
	};

	init$1();

	var div = root$1();
	var div_1 = child(div);
	var node = child(div_1);

	{
		var consequent = ($$anchor) => {
			{
				let $0 = derived_safe_equal(() => tool() === "image");

				IconButton($$anchor, {
					get Icon() {
						return Image$1;
					},
					label: 'Image',
					get highlight() {
						return get($0);
					},
					onclick: (e) => handle_tool_click(e, "image"),
					size: 'medium',
					padded: false,
					transparent: true
				});
			}
		};

		if_block(node, ($$render) => {
			if ((
				deep_read_state(sources()),
				untrack(() => sources().length > 0)
			)) $$render(consequent);
		});
	}

	var node_1 = sibling(node, 2);

	{
		var consequent_1 = ($$anchor) => {
			{
				let $0 = derived_safe_equal(() => tool() === "draw");

				IconButton($$anchor, {
					get Icon() {
						return Brush;
					},
					label: 'Brush',
					onclick: (e) => handle_tool_click(e, "draw"),
					get highlight() {
						return get($0);
					},
					size: 'medium',
					padded: false,
					transparent: true
				});
			}
		};

		if_block(node_1, ($$render) => {
			if (brush_options()) $$render(consequent_1);
		});
	}

	var node_2 = sibling(node_1, 2);

	{
		var consequent_2 = ($$anchor) => {
			{
				let $0 = derived_safe_equal(() => tool() === "erase");

				IconButton($$anchor, {
					get Icon() {
						return Erase;
					},
					label: 'Erase',
					onclick: (e) => handle_tool_click(e, "erase"),
					get highlight() {
						return get($0);
					},
					size: 'medium',
					padded: false,
					transparent: true
				});
			}
		};

		if_block(node_2, ($$render) => {
			if (eraser_options()) $$render(consequent_2);
		});
	}

	reset(div_1);

	var div_2 = sibling(div_1, 2);
	let classes;
	var node_3 = child(div_2);

	{
		var consequent_9 = ($$anchor) => {
			var fragment_3 = comment();
			var node_4 = first_child(fragment_3);

			{
				var consequent_5 = ($$anchor) => {
					var fragment_4 = root_5$2();
					var node_5 = first_child(fragment_4);

					{
						var consequent_3 = ($$anchor) => {
							{
								let $0 = derived_safe_equal(() => subtool() === "crop");

								IconButton($$anchor, {
									get Icon() {
										return Crop;
									},
									label: 'Crop',
									onclick: (e) => handle_subtool_click(e, "crop"),
									get highlight() {
										return get($0);
									},
									size: 'medium',
									padded: false,
									transparent: true,
									offset: 0
								});
							}
						};

						if_block(node_5, ($$render) => {
							if (get(can_crop)) $$render(consequent_3);
						});
					}

					var node_6 = sibling(node_5, 2);

					{
						var consequent_4 = ($$anchor) => {
							{
								let $0 = derived_safe_equal(() => subtool() === "size");

								IconButton($$anchor, {
									get Icon() {
										return Resize;
									},
									label: 'Resize',
									onclick: (e) => handle_subtool_click(e, "size"),
									get highlight() {
										return get($0);
									},
									size: 'medium',
									padded: false,
									transparent: true,
									offset: 0
								});
							}
						};

						if_block(node_6, ($$render) => {
							if (get(can_resize)) $$render(consequent_4);
						});
					}

					append($$anchor, fragment_4);
				};

				var alternate = ($$anchor) => {
					var fragment_7 = root_8$1();
					var node_7 = first_child(fragment_7);

					{
						var consequent_6 = ($$anchor) => {
							{
								let $0 = derived_safe_equal(() => subtool() === "upload");

								IconButton($$anchor, {
									get Icon() {
										return Upload;
									},
									label: 'Upload',
									onclick: (e) => handle_subtool_click(e, "upload"),
									get highlight() {
										return get($0);
									},
									size: 'medium',
									padded: false,
									transparent: true,
									offset: 0
								});
							}
						};

						if_block(node_7, ($$render) => {
							if (get(can_upload)) $$render(consequent_6);
						});
					}

					var node_8 = sibling(node_7, 2);

					{
						var consequent_7 = ($$anchor) => {
							{
								let $0 = derived_safe_equal(() => subtool() === "paste");

								IconButton($$anchor, {
									get Icon() {
										return ImagePaste;
									},
									label: 'Paste',
									onclick: (e) => handle_subtool_click(e, "paste"),
									get highlight() {
										return get($0);
									},
									size: 'large',
									padded: false,
									transparent: true,
									offset: 0
								});
							}
						};

						if_block(node_8, ($$render) => {
							if (get(can_paste)) $$render(consequent_7);
						});
					}

					var node_9 = sibling(node_8, 2);

					{
						var consequent_8 = ($$anchor) => {
							{
								let $0 = derived_safe_equal(() => subtool() === "webcam");

								IconButton($$anchor, {
									get Icon() {
										return Webcam;
									},
									label: 'Webcam',
									onclick: (e) => handle_subtool_click(e, "webcam"),
									get highlight() {
										return get($0);
									},
									size: 'medium',
									padded: false,
									transparent: true,
									offset: 0
								});
							}
						};

						if_block(node_9, ($$render) => {
							if (get(can_webcam)) $$render(consequent_8);
						});
					}

					append($$anchor, fragment_7);
				};

				if_block(node_4, ($$render) => {
					if (background()) $$render(consequent_5); else $$render(alternate, false);
				});
			}

			append($$anchor, fragment_3);
		};

		if_block(node_3, ($$render) => {
			if (tool() === "image") $$render(consequent_9);
		});
	}

	var node_10 = sibling(node_3, 2);

	{
		var consequent_11 = ($$anchor) => {
			var fragment_11 = root_12();
			var node_11 = first_child(fragment_11);

			IconButton(node_11, {
				get Icon() {
					return ColorPickerSolid;
				},
				label: 'Color',
				get color() {
					return selected_color();
				},
				onclick: (e) => handle_subtool_click(e, "color"),
				size: 'medium',
				padded: false,
				transparent: true,
				offset: 0
			});

			var node_12 = sibling(node_11, 2);

			{
				let $0 = derived_safe_equal(() => subtool() === "size");

				IconButton(node_12, {
					get Icon() {
						return Circle$1;
					},
					label: 'Brush Size',
					onclick: (e) => handle_subtool_click(e, "size"),
					get highlight() {
						return get($0);
					},
					size: 'medium',
					padded: false,
					transparent: true,
					offset: 0
				});
			}

			var node_13 = sibling(node_12, 2);

			{
				var consequent_10 = ($$anchor) => {
					BrushOptions($$anchor, {
						get colors() {
							return (
								deep_read_state(brush_options()),
								untrack(() => brush_options().colors)
							);
						},

						get color_mode() {
							return (
								deep_read_state(brush_options()),
								untrack(() => brush_options().color_mode)
							);
						},

						get recent_colors() {
							return recent_colors;
						},

						get show_swatch() {
							return show_brush_color();
						},

						get show_size() {
							return show_brush_size();
						},
						mode: 'brush',
						get selected_size() {
							return selected_size();
						},

						set selected_size($$value) {
							selected_size($$value);
						},

						get selected_color() {
							return selected_color();
						},

						set selected_color($$value) {
							selected_color($$value);
						},

						get selected_opacity() {
							return selected_opacity();
						},

						set selected_opacity($$value) {
							selected_opacity($$value);
						},

						get preview() {
							return preview();
						},

						set preview($$value) {
							preview($$value);
						},

						$$events: {
							click_outside: (e) => {
								e.stopPropagation();
								preview(false);
								show_brush_color(false);
								show_brush_size(false);
								handle_subtool_click(e, null);
							}
						},
						$$legacy: true
					});
				};

				if_block(node_13, ($$render) => {
					if (show_brush_color() || show_brush_size()) $$render(consequent_10);
				});
			}

			append($$anchor, fragment_11);
		};

		if_block(node_10, ($$render) => {
			if (tool() === "draw" && brush_options()) $$render(consequent_11);
		});
	}

	var node_14 = sibling(node_10, 2);

	{
		var consequent_12 = ($$anchor) => {
			{
				let $0 = derived_safe_equal(() => subtool() === "size");

				IconButton($$anchor, {
					get Icon() {
						return Circle$1;
					},
					label: 'Eraser Size',
					onclick: (e) => handle_subtool_click(e, "size"),
					get highlight() {
						return get($0);
					},
					size: 'medium',
					padded: false,
					transparent: true,
					offset: 0
				});
			}
		};

		if_block(node_14, ($$render) => {
			if (tool() === "erase" && eraser_options()) $$render(consequent_12);
		});
	}

	var node_15 = sibling(node_14, 2);

	{
		var consequent_13 = ($$anchor) => {
			BrushOptions($$anchor, {
				colors: [],
				show_swatch: false,
				show_size: true,
				mode: 'eraser',
				get selected_size() {
					return selected_eraser_size();
				},

				set selected_size($$value) {
					selected_eraser_size($$value);
				},

				get selected_color() {
					return selected_color();
				},

				set selected_color($$value) {
					selected_color($$value);
				},

				get selected_opacity() {
					return selected_opacity();
				},

				set selected_opacity($$value) {
					selected_opacity($$value);
				},

				get preview() {
					return preview();
				},

				set preview($$value) {
					preview($$value);
				},

				$$events: {
					click_outside: (e) => {
						e.stopPropagation();
						preview(false);
						show_eraser_size(false);
						handle_subtool_click(e, null);
					}
				},
				$$legacy: true
			});
		};

		if_block(node_15, ($$render) => {
			if (show_eraser_size()) $$render(consequent_13);
		});
	}

	reset(div_2);
	reset(div);

	template_effect(() => classes = set_class(div_2, 1, 'half-container right svelte-1uf24la', null, classes, {
		hide: tool() === "pan" || tool() === "image" && !background() && sources().length === 0 || tool() === "image" && background() && transforms().length === 0
	}));

	append($$anchor, div);

	return pop($$exports);
}

var vertex = "in vec2 aPosition;\nout vec2 vTextureCoord;\n\nuniform vec4 uInputSize;\nuniform vec4 uOutputFrame;\nuniform vec4 uOutputTexture;\n\nvec4 filterVertexPosition( void )\n{\n    vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;\n    \n    position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;\n    position.y = position.y * (2.0*uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;\n\n    return vec4(position, 0.0, 1.0);\n}\n\nvec2 filterTextureCoord( void )\n{\n    return aPosition * (uOutputFrame.zw * uInputSize.zw);\n}\n\nvoid main(void)\n{\n    gl_Position = filterVertexPosition();\n    vTextureCoord = filterTextureCoord();\n}\n";

var wgslVertex = "struct GlobalFilterUniforms {\n  uInputSize:vec4<f32>,\n  uInputPixel:vec4<f32>,\n  uInputClamp:vec4<f32>,\n  uOutputFrame:vec4<f32>,\n  uGlobalFrame:vec4<f32>,\n  uOutputTexture:vec4<f32>,\n};\n\n@group(0) @binding(0) var<uniform> gfu: GlobalFilterUniforms;\n\nstruct VSOutput {\n    @builtin(position) position: vec4<f32>,\n    @location(0) uv : vec2<f32>\n  };\n\nfn filterVertexPosition(aPosition:vec2<f32>) -> vec4<f32>\n{\n    var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy;\n\n    position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0;\n    position.y = position.y * (2.0*gfu.uOutputTexture.z / gfu.uOutputTexture.y) - gfu.uOutputTexture.z;\n\n    return vec4(position, 0.0, 1.0);\n}\n\nfn filterTextureCoord( aPosition:vec2<f32> ) -> vec2<f32>\n{\n    return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw);\n}\n\nfn globalTextureCoord( aPosition:vec2<f32> ) -> vec2<f32>\n{\n  return  (aPosition.xy / gfu.uGlobalFrame.zw) + (gfu.uGlobalFrame.xy / gfu.uGlobalFrame.zw);  \n}\n\nfn getSize() -> vec2<f32>\n{\n  return gfu.uGlobalFrame.zw;\n}\n  \n@vertex\nfn mainVertex(\n  @location(0) aPosition : vec2<f32>, \n) -> VSOutput {\n  return VSOutput(\n   filterVertexPosition(aPosition),\n   filterTextureCoord(aPosition)\n  );\n}";

var fragment$1 = "\nin vec2 vTextureCoord;\nout vec4 finalColor;\n\nuniform sampler2D uTexture;\nuniform vec2 uOffset;\n\nvoid main(void)\n{\n    vec4 color = vec4(0.0);\n\n    // Sample top left pixel\n    color += texture(uTexture, vec2(vTextureCoord.x - uOffset.x, vTextureCoord.y + uOffset.y));\n\n    // Sample top right pixel\n    color += texture(uTexture, vec2(vTextureCoord.x + uOffset.x, vTextureCoord.y + uOffset.y));\n\n    // Sample bottom right pixel\n    color += texture(uTexture, vec2(vTextureCoord.x + uOffset.x, vTextureCoord.y - uOffset.y));\n\n    // Sample bottom left pixel\n    color += texture(uTexture, vec2(vTextureCoord.x - uOffset.x, vTextureCoord.y - uOffset.y));\n\n    // Average\n    color *= 0.25;\n\n    finalColor = color;\n}";

var source$1 = "struct KawaseBlurUniforms {\n  uOffset:vec2<f32>,\n};\n\n@group(0) @binding(1) var uTexture: texture_2d<f32>; \n@group(0) @binding(2) var uSampler: sampler;\n@group(1) @binding(0) var<uniform> kawaseBlurUniforms : KawaseBlurUniforms;\n\n@fragment\nfn mainFragment(\n  @builtin(position) position: vec4<f32>,\n  @location(0) uv : vec2<f32>\n) -> @location(0) vec4<f32> {\n  let uOffset = kawaseBlurUniforms.uOffset;\n  var color: vec4<f32> = vec4<f32>(0.0);\n\n  // Sample top left pixel\n  color += textureSample(uTexture, uSampler, vec2<f32>(uv.x - uOffset.x, uv.y + uOffset.y));\n  // Sample top right pixel\n  color += textureSample(uTexture, uSampler, vec2<f32>(uv.x + uOffset.x, uv.y + uOffset.y));\n  // Sample bottom right pixel\n  color += textureSample(uTexture, uSampler, vec2<f32>(uv.x + uOffset.x, uv.y - uOffset.y));\n  // Sample bottom left pixel\n  color += textureSample(uTexture, uSampler, vec2<f32>(uv.x - uOffset.x, uv.y - uOffset.y));\n  // Average\n  color *= 0.25;\n\n  return color;\n}";

var fragmentClamp = "\nprecision highp float;\nin vec2 vTextureCoord;\nout vec4 finalColor;\n\nuniform sampler2D uTexture;\nuniform vec2 uOffset;\n\nuniform vec4 uInputClamp;\n\nvoid main(void)\n{\n    vec4 color = vec4(0.0);\n\n    // Sample top left pixel\n    color += texture(uTexture, clamp(vec2(vTextureCoord.x - uOffset.x, vTextureCoord.y + uOffset.y), uInputClamp.xy, uInputClamp.zw));\n\n    // Sample top right pixel\n    color += texture(uTexture, clamp(vec2(vTextureCoord.x + uOffset.x, vTextureCoord.y + uOffset.y), uInputClamp.xy, uInputClamp.zw));\n\n    // Sample bottom right pixel\n    color += texture(uTexture, clamp(vec2(vTextureCoord.x + uOffset.x, vTextureCoord.y - uOffset.y), uInputClamp.xy, uInputClamp.zw));\n\n    // Sample bottom left pixel\n    color += texture(uTexture, clamp(vec2(vTextureCoord.x - uOffset.x, vTextureCoord.y - uOffset.y), uInputClamp.xy, uInputClamp.zw));\n\n    // Average\n    color *= 0.25;\n\n    finalColor = color;\n}\n";

var sourceClamp = "struct KawaseBlurUniforms {\n  uOffset:vec2<f32>,\n};\n\nstruct GlobalFilterUniforms {\n  uInputSize:vec4<f32>,\n  uInputPixel:vec4<f32>,\n  uInputClamp:vec4<f32>,\n  uOutputFrame:vec4<f32>,\n  uGlobalFrame:vec4<f32>,\n  uOutputTexture:vec4<f32>,\n};\n\n@group(0) @binding(0) var<uniform> gfu: GlobalFilterUniforms;\n\n@group(0) @binding(1) var uTexture: texture_2d<f32>; \n@group(0) @binding(2) var uSampler: sampler;\n@group(1) @binding(0) var<uniform> kawaseBlurUniforms : KawaseBlurUniforms;\n\n@fragment\nfn mainFragment(\n  @builtin(position) position: vec4<f32>,\n  @location(0) uv : vec2<f32>\n) -> @location(0) vec4<f32> {\n  let uOffset = kawaseBlurUniforms.uOffset;\n  var color: vec4<f32> = vec4(0.0);\n\n  // Sample top left pixel\n  color += textureSample(uTexture, uSampler, clamp(vec2<f32>(uv.x - uOffset.x, uv.y + uOffset.y), gfu.uInputClamp.xy, gfu.uInputClamp.zw));\n  // Sample top right pixel\n  color += textureSample(uTexture, uSampler, clamp(vec2<f32>(uv.x + uOffset.x, uv.y + uOffset.y), gfu.uInputClamp.xy, gfu.uInputClamp.zw));\n  // Sample bottom right pixel\n  color += textureSample(uTexture, uSampler, clamp(vec2<f32>(uv.x + uOffset.x, uv.y - uOffset.y), gfu.uInputClamp.xy, gfu.uInputClamp.zw));\n  // Sample bottom left pixel\n  color += textureSample(uTexture, uSampler, clamp(vec2<f32>(uv.x - uOffset.x, uv.y - uOffset.y), gfu.uInputClamp.xy, gfu.uInputClamp.zw));\n  // Average\n  color *= 0.25;\n    \n  return color;\n}";

var __defProp$1 = Object.defineProperty;
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$1 = (obj, key, value) => {
  __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
  return value;
};
const _KawaseBlurFilter = class _KawaseBlurFilter extends Filter {
  /** @ignore */
  constructor(...args) {
    let options = args[0] ?? {};
    if (typeof options === "number" || Array.isArray(options)) {
      deprecation("6.0.0", "KawaseBlurFilter constructor params are now options object. See params: { strength, quality, clamp, pixelSize }");
      options = { strength: options };
      if (args[1] !== void 0)
        options.quality = args[1];
      if (args[2] !== void 0)
        options.clamp = args[2];
    }
    options = { ..._KawaseBlurFilter.DEFAULT_OPTIONS, ...options };
    const gpuProgram = GpuProgram.from({
      vertex: {
        source: wgslVertex,
        entryPoint: "mainVertex"
      },
      fragment: {
        source: options?.clamp ? sourceClamp : source$1,
        entryPoint: "mainFragment"
      }
    });
    const glProgram = GlProgram.from({
      vertex,
      fragment: options?.clamp ? fragmentClamp : fragment$1,
      name: "kawase-blur-filter"
    });
    super({
      gpuProgram,
      glProgram,
      resources: {
        kawaseBlurUniforms: {
          uOffset: { value: new Float32Array(2), type: "vec2<f32>" }
        }
      }
    });
    __publicField$1(this, "uniforms");
    __publicField$1(this, "_pixelSize", { x: 0, y: 0 });
    __publicField$1(this, "_clamp");
    __publicField$1(this, "_kernels", []);
    __publicField$1(this, "_blur");
    __publicField$1(this, "_quality");
    this.uniforms = this.resources.kawaseBlurUniforms.uniforms;
    this.pixelSize = options.pixelSize ?? { x: 1, y: 1 };
    if (Array.isArray(options.strength)) {
      this.kernels = options.strength;
    } else if (typeof options.strength === "number") {
      this._blur = options.strength;
      this.quality = options.quality ?? 3;
    }
    this._clamp = !!options.clamp;
  }
  /**
   * Override existing apply method in `Filter`
   * @override
   * @ignore
   */
  apply(filterManager, input, output, clearMode) {
    const uvX = this.pixelSizeX / input.source.width;
    const uvY = this.pixelSizeY / input.source.height;
    let offset;
    if (this._quality === 1 || this._blur === 0) {
      offset = this._kernels[0] + 0.5;
      this.uniforms.uOffset[0] = offset * uvX;
      this.uniforms.uOffset[1] = offset * uvY;
      filterManager.applyFilter(this, input, output, clearMode);
    } else {
      const renderTarget = TexturePool.getSameSizeTexture(input);
      let source2 = input;
      let target = renderTarget;
      let tmp;
      const last = this._quality - 1;
      for (let i = 0; i < last; i++) {
        offset = this._kernels[i] + 0.5;
        this.uniforms.uOffset[0] = offset * uvX;
        this.uniforms.uOffset[1] = offset * uvY;
        filterManager.applyFilter(this, source2, target, true);
        tmp = source2;
        source2 = target;
        target = tmp;
      }
      offset = this._kernels[last] + 0.5;
      this.uniforms.uOffset[0] = offset * uvX;
      this.uniforms.uOffset[1] = offset * uvY;
      filterManager.applyFilter(this, source2, output, clearMode);
      TexturePool.returnTexture(renderTarget);
    }
  }
  /**
    * The amount of blur, value greater than `0`.
    * @default 4
    */
  get strength() {
    return this._blur;
  }
  set strength(value) {
    this._blur = value;
    this._generateKernels();
  }
  /**
    * The quality of the filter, integer greater than `1`.
    * @default 3
    */
  get quality() {
    return this._quality;
  }
  set quality(value) {
    this._quality = Math.max(1, Math.round(value));
    this._generateKernels();
  }
  /**
    * The kernel size of the blur filter, for advanced usage
    * @default [0]
    */
  get kernels() {
    return this._kernels;
  }
  set kernels(value) {
    if (Array.isArray(value) && value.length > 0) {
      this._kernels = value;
      this._quality = value.length;
      this._blur = Math.max(...value);
    } else {
      this._kernels = [0];
      this._quality = 1;
    }
  }
  /**
    * The size of the pixels. Large size is blurrier. For advanced usage.
    * @default {x:1,y:1}
    */
  get pixelSize() {
    return this._pixelSize;
  }
  set pixelSize(value) {
    if (typeof value === "number") {
      this.pixelSizeX = this.pixelSizeY = value;
      return;
    }
    if (Array.isArray(value)) {
      this.pixelSizeX = value[0];
      this.pixelSizeY = value[1];
      return;
    }
    this._pixelSize = value;
  }
  /**
    * The size of the pixels on the `x` axis. Large size is blurrier. For advanced usage.
    * @default 1
    */
  get pixelSizeX() {
    return this.pixelSize.x;
  }
  set pixelSizeX(value) {
    this.pixelSize.x = value;
  }
  /**
    * The size of the pixels on the `y` axis. Large size is blurrier. For advanced usage.
    * @default 1
    */
  get pixelSizeY() {
    return this.pixelSize.y;
  }
  set pixelSizeY(value) {
    this.pixelSize.y = value;
  }
  /**
    * Get the if the filter is clamped
    * @default false
    */
  get clamp() {
    return this._clamp;
  }
  /** Update padding based on kernel data */
  _updatePadding() {
    this.padding = Math.ceil(this._kernels.reduce((acc, v) => acc + v + 0.5, 0));
  }
  /** Auto generate kernels by blur & quality */
  _generateKernels() {
    const blur = this._blur;
    const quality = this._quality;
    const kernels = [blur];
    if (blur > 0) {
      let k = blur;
      const step = blur / quality;
      for (let i = 1; i < quality; i++) {
        k -= step;
        kernels.push(k);
      }
    }
    this._kernels = kernels;
    this._updatePadding();
  }
};
/** Default values for options. */
__publicField$1(_KawaseBlurFilter, "DEFAULT_OPTIONS", {
  strength: 4,
  quality: 3,
  clamp: false,
  pixelSize: { x: 1, y: 1 }
});
let KawaseBlurFilter = _KawaseBlurFilter;

var fragment = "precision highp float;\nin vec2 vTextureCoord;\nout vec4 finalColor;\n\nuniform sampler2D uTexture;\nuniform float uAlpha;\nuniform vec3 uColor;\nuniform vec2 uOffset;\n\nuniform vec4 uInputSize;\n\nvoid main(void){\n    vec4 sample = texture(uTexture, vTextureCoord - uOffset * uInputSize.zw);\n\n    // Premultiply alpha\n    sample.rgb = uColor.rgb * sample.a;\n\n    // alpha user alpha\n    sample *= uAlpha;\n\n    finalColor = sample;\n}";

var source = "struct DropShadowUniforms {\n  uAlpha: f32,\n  uColor: vec3<f32>,\n  uOffset: vec2<f32>,\n};\n\nstruct GlobalFilterUniforms {\n  uInputSize:vec4<f32>,\n  uInputPixel:vec4<f32>,\n  uInputClamp:vec4<f32>,\n  uOutputFrame:vec4<f32>,\n  uGlobalFrame:vec4<f32>,\n  uOutputTexture:vec4<f32>,\n};\n\n@group(0) @binding(0) var<uniform> gfu: GlobalFilterUniforms;\n\n@group(0) @binding(1) var uTexture: texture_2d<f32>; \n@group(0) @binding(2) var uSampler: sampler;\n@group(1) @binding(0) var<uniform> dropShadowUniforms : DropShadowUniforms;\n\n@fragment\nfn mainFragment(\n  @builtin(position) position: vec4<f32>,\n  @location(0) uv : vec2<f32>\n) -> @location(0) vec4<f32> {\n  var color: vec4<f32> = textureSample(uTexture, uSampler, uv - dropShadowUniforms.uOffset * gfu.uInputSize.zw);\n\n  // Premultiply alpha\n  color = vec4<f32>(vec3<f32>(dropShadowUniforms.uColor.rgb * color.a), color.a);\n  // alpha user alpha\n  color *= dropShadowUniforms.uAlpha;\n\n  return color;\n}";

var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  return value;
};
const _DropShadowFilter = class _DropShadowFilter extends Filter {
  /**
   * @param options - Options for the DropShadowFilter constructor.
   */
  constructor(options) {
    options = { ..._DropShadowFilter.DEFAULT_OPTIONS, ...options };
    const gpuProgram = GpuProgram.from({
      vertex: {
        source: wgslVertex,
        entryPoint: "mainVertex"
      },
      fragment: {
        source,
        entryPoint: "mainFragment"
      }
    });
    const glProgram = GlProgram.from({
      vertex,
      fragment,
      name: "drop-shadow-filter"
    });
    super({
      gpuProgram,
      glProgram,
      resources: {
        dropShadowUniforms: {
          uAlpha: { value: options.alpha, type: "f32" },
          uColor: { value: new Float32Array(3), type: "vec3<f32>" },
          uOffset: { value: options.offset, type: "vec2<f32>" }
        }
      },
      resolution: options.resolution
    });
    __publicField(this, "uniforms");
    /**
     * Hide the contents, only show the shadow.
     * @default false
     */
    __publicField(this, "shadowOnly", false);
    __publicField(this, "_color");
    __publicField(this, "_blurFilter");
    __publicField(this, "_basePass");
    this.uniforms = this.resources.dropShadowUniforms.uniforms;
    this._color = new Color();
    this.color = options.color ?? 0;
    this._blurFilter = new KawaseBlurFilter({
      strength: options.kernels ?? options.blur,
      quality: options.kernels ? void 0 : options.quality
    });
    this._basePass = new Filter({
      gpuProgram: GpuProgram.from({
        vertex: {
          source: wgslVertex,
          entryPoint: "mainVertex"
        },
        fragment: {
          source: `
                    @group(0) @binding(1) var uTexture: texture_2d<f32>; 
                    @group(0) @binding(2) var uSampler: sampler;
                    @fragment
                    fn mainFragment(
                        @builtin(position) position: vec4<f32>,
                        @location(0) uv : vec2<f32>
                    ) -> @location(0) vec4<f32> {
                        return textureSample(uTexture, uSampler, uv);
                    }
                    `,
          entryPoint: "mainFragment"
        }
      }),
      glProgram: GlProgram.from({
        vertex,
        fragment: `
                in vec2 vTextureCoord;
                out vec4 finalColor;
                uniform sampler2D uTexture;

                void main(void){
                    finalColor = texture(uTexture, vTextureCoord);
                }
                `,
        name: "drop-shadow-filter"
      }),
      resources: {}
    });
    Object.assign(this, options);
  }
  /**
   * Override existing apply method in `Filter`
   * @override
   * @ignore
   */
  apply(filterManager, input, output, clearMode) {
    const renderTarget = TexturePool.getSameSizeTexture(input);
    filterManager.applyFilter(this, input, renderTarget, true);
    this._blurFilter.apply(filterManager, renderTarget, output, clearMode);
    if (!this.shadowOnly) {
      filterManager.applyFilter(this._basePass, input, output, false);
    }
    TexturePool.returnTexture(renderTarget);
  }
  /**
   * Set the offset position of the drop-shadow relative to the original image.
   * @default [4,4]
   */
  get offset() {
    return this.uniforms.uOffset;
  }
  set offset(value) {
    this.uniforms.uOffset = value;
    this._updatePadding();
  }
  /**
   * Set the offset position of the drop-shadow relative to the original image on the `x` axis
   * @default 4
   */
  get offsetX() {
    return this.offset.x;
  }
  set offsetX(value) {
    this.offset.x = value;
    this._updatePadding();
  }
  /**
   * Set the offset position of the drop-shadow relative to the original image on the `y` axis
   * @default 4
   */
  get offsetY() {
    return this.offset.y;
  }
  set offsetY(value) {
    this.offset.y = value;
    this._updatePadding();
  }
  /**
   * The color value of shadow.
   * @example [0.0, 0.0, 0.0] = 0x000000
   * @default 0x000000
   */
  get color() {
    return this._color.value;
  }
  set color(value) {
    this._color.setValue(value);
    const [r, g, b] = this._color.toArray();
    this.uniforms.uColor[0] = r;
    this.uniforms.uColor[1] = g;
    this.uniforms.uColor[2] = b;
  }
  /**
   * Coefficient for alpha multiplication
   * @default 1
   */
  get alpha() {
    return this.uniforms.uAlpha;
  }
  set alpha(value) {
    this.uniforms.uAlpha = value;
  }
  /**
   * The strength of the shadow's blur.
   * @default 2
   */
  get blur() {
    return this._blurFilter.strength;
  }
  set blur(value) {
    this._blurFilter.strength = value;
    this._updatePadding();
  }
  /**
   * Sets the quality of the Blur Filter
   * @default 4
   */
  get quality() {
    return this._blurFilter.quality;
  }
  set quality(value) {
    this._blurFilter.quality = value;
    this._updatePadding();
  }
  /** Sets the kernels of the Blur Filter */
  get kernels() {
    return this._blurFilter.kernels;
  }
  set kernels(value) {
    this._blurFilter.kernels = value;
  }
  /**
   * Sets the pixelSize of the Kawase Blur filter
   * @default [1,1]
   */
  get pixelSize() {
    return this._blurFilter.pixelSize;
  }
  set pixelSize(value) {
    if (typeof value === "number") {
      value = { x: value, y: value };
    }
    if (Array.isArray(value)) {
      value = { x: value[0], y: value[1] };
    }
    this._blurFilter.pixelSize = value;
  }
  /**
   * Sets the pixelSize of the Kawase Blur filter on the `x` axis
   * @default 1
   */
  get pixelSizeX() {
    return this._blurFilter.pixelSizeX;
  }
  set pixelSizeX(value) {
    this._blurFilter.pixelSizeX = value;
  }
  /**
   * Sets the pixelSize of the Kawase Blur filter on the `y` axis
   * @default 1
   */
  get pixelSizeY() {
    return this._blurFilter.pixelSizeY;
  }
  set pixelSizeY(value) {
    this._blurFilter.pixelSizeY = value;
  }
  /**
   * Recalculate the proper padding amount.
   * @private
   */
  _updatePadding() {
    const offsetPadding = Math.max(
      Math.abs(this.offsetX),
      Math.abs(this.offsetY)
    );
    this.padding = offsetPadding + this.blur * 2 + this.quality * 4;
  }
};
/** Default values for options. */
__publicField(_DropShadowFilter, "DEFAULT_OPTIONS", {
  offset: { x: 4, y: 4 },
  color: 0,
  alpha: 0.5,
  shadowOnly: false,
  kernels: void 0,
  blur: 2,
  quality: 3,
  pixelSize: { x: 1, y: 1 },
  resolution: 1
});
let DropShadowFilter = _DropShadowFilter;

class ImageTool {
  name = "image";
  context;
  current_tool;
  current_subtool;
  async setup(context, tool, subtool) {
    this.context = context;
    this.current_tool = tool;
    this.current_subtool = subtool;
  }
  cleanup() {
  }
  async add_image({
    image,
    fixed_canvas,
    border_region = 0
  }) {
    const image_command = new AddImageCommand(
      this.context,
      image,
      fixed_canvas,
      border_region
    );
    await this.context.execute_command(image_command);
  }
  set_tool(tool, subtool) {
    this.current_tool = tool;
    this.current_subtool = subtool;
  }
}
class AddImageCommand {
  sprite = null;
  fixed_canvas;
  context;
  background;
  current_canvas_size;
  computed_dimensions;
  current_scale;
  current_position;
  border_region;
  scaled_width;
  scaled_height;
  previous_image = null;
  name;
  constructor(context, background, fixed_canvas, border_region = 0) {
    this.name = "AddImage";
    this.context = context;
    this.background = background;
    this.fixed_canvas = fixed_canvas;
    this.border_region = border_region;
    this.current_canvas_size = get$1(this.context.dimensions);
    this.current_scale = get$1(this.context.scale);
    this.current_position = get$1(this.context.position);
    this.computed_dimensions = { width: 0, height: 0 };
    if (this.context.background_image && this.context.background_image.texture) {
      const bg = this.context.background_image;
      const stored_border_region = bg.borderRegion || 0;
      this.previous_image = {
        texture: this.clone_texture(bg.texture),
        width: bg.width,
        height: bg.height,
        x: bg.position.x,
        y: bg.position.y,
        border_region: stored_border_region
      };
    }
  }
  /**
   * Creates a copy of a texture to avoid reference issues
   */
  clone_texture(texture) {
    const render_texture = RenderTexture.create({
      width: texture.width,
      height: texture.height,
      resolution: window.devicePixelRatio || 1
    });
    const sprite = new Sprite(texture);
    const container = new Container();
    container.addChild(sprite);
    this.context.app.renderer.render(container, {
      renderTexture: render_texture
    });
    container.destroy({ children: true });
    return render_texture;
  }
  async start() {
    let image_texture;
    if (this.background instanceof Texture) {
      image_texture = this.background;
    } else {
      const img = await createImageBitmap(this.background);
      image_texture = Texture.from(img);
    }
    this.sprite = new Sprite(image_texture);
    const [width, height] = this.handle_image();
    this.computed_dimensions = { width, height };
  }
  handle_image() {
    if (this.sprite === null) {
      return [0, 0];
    }
    if (this.fixed_canvas) {
      const effectiveCanvasWidth = Math.max(
        this.current_canvas_size.width - this.border_region * 2,
        10
      );
      const effectiveCanvasHeight = Math.max(
        this.current_canvas_size.height - this.border_region * 2,
        10
      );
      const { width, height, x, y } = fit_image_to_canvas(
        this.sprite.width,
        this.sprite.height,
        effectiveCanvasWidth,
        effectiveCanvasHeight
      );
      this.sprite.width = width;
      this.sprite.height = height;
      this.sprite.x = x + this.border_region;
      this.sprite.y = y + this.border_region;
    } else {
      const width = this.sprite.width;
      const height = this.sprite.height;
      this.sprite.x = this.border_region;
      this.sprite.y = this.border_region;
      return [width + this.border_region * 2, height + this.border_region * 2];
    }
    return [this.current_canvas_size.width, this.current_canvas_size.height];
  }
  async execute(context) {
    if (context) {
      this.context = context;
    }
    await this.start();
    if (this.sprite === null) {
      return;
    }
    const { width, height } = this.computed_dimensions;
    await this.context.set_image_properties({
      scale: 1,
      position: {
        x: this.context.app.screen.width / 2,
        y: this.context.app.screen.height / 2
      },
      width: this.fixed_canvas ? this.current_canvas_size.width : width,
      height: this.fixed_canvas ? this.current_canvas_size.height : height
    });
    const background_layer = this.context.layer_manager.create_background_layer(
      this.fixed_canvas ? this.current_canvas_size.width : width,
      this.fixed_canvas ? this.current_canvas_size.height : height
    );
    this.sprite.zIndex = 0;
    background_layer.addChild(this.sprite);
    this.context.layer_manager.reset_layers(
      this.fixed_canvas ? this.current_canvas_size.width : width,
      this.fixed_canvas ? this.current_canvas_size.height : height,
      true
    );
    if (this.border_region > 0) {
      this.sprite.borderRegion = this.border_region;
    }
    this.context.set_background_image(this.sprite);
    this.context.reset();
  }
  async undo() {
    if (this.sprite) {
      this.sprite.destroy();
      if (this.previous_image) {
        const previous_sprite = new Sprite(this.previous_image.texture);
        previous_sprite.width = this.previous_image.width;
        previous_sprite.height = this.previous_image.height;
        previous_sprite.position.set(
          this.previous_image.x,
          this.previous_image.y
        );
        if (this.previous_image.border_region > 0) {
          previous_sprite.borderRegion = this.previous_image.border_region;
        }
        await this.context.set_image_properties({
          scale: 1,
          position: {
            x: this.context.app.screen.width / 2,
            y: this.context.app.screen.height / 2
          },
          width: this.previous_image.width,
          height: this.previous_image.height
        });
        const background_layer = this.context.layer_manager.create_background_layer(
          this.previous_image.width,
          this.previous_image.height
        );
        previous_sprite.zIndex = 0;
        background_layer.addChild(previous_sprite);
        this.context.layer_manager.reset_layers(
          this.previous_image.width,
          this.previous_image.height,
          true
        );
        this.context.set_background_image(previous_sprite);
      } else {
        await this.context.set_image_properties({
          scale: 1,
          position: {
            x: this.context.app.screen.width / 2,
            y: this.context.app.screen.height / 2
          },
          width: this.current_canvas_size.width,
          height: this.current_canvas_size.height
        });
        this.context.layer_manager.create_background_layer(
          this.current_canvas_size.width,
          this.current_canvas_size.height
        );
        this.context.layer_manager.reset_layers(
          this.current_canvas_size.width,
          this.current_canvas_size.height
        );
      }
      this.context.reset();
    }
  }
}
function fit_image_to_canvas(image_width, image_height, canvas_width, canvas_height) {
  const image_aspect_ratio = image_width / image_height;
  const canvas_aspect_ratio = canvas_width / canvas_height;
  let new_width;
  let new_height;
  if (image_aspect_ratio > canvas_aspect_ratio) {
    new_width = canvas_width;
    new_height = canvas_width / image_aspect_ratio;
  } else {
    new_height = canvas_height;
    new_width = canvas_height * image_aspect_ratio;
  }
  const x = Math.round((canvas_width - new_width) / 2);
  const y = Math.round((canvas_height - new_height) / 2);
  return {
    width: Math.round(new_width),
    height: Math.round(new_height),
    x,
    y
  };
}

class ZoomTool {
  name = "zoom";
  min_zoom = writable(true);
  image_editor_context;
  max_zoom = 10;
  border_padding = 30;
  pad_bottom = 0;
  is_pinching = false;
  is_dragging = false;
  is_pointer_dragging = false;
  last_touch_position = null;
  last_pinch_distance = 0;
  drag_start = new Point();
  current_tool;
  current_subtool;
  local_scale = 1;
  local_dimensions = {
    width: 0,
    height: 0
  };
  local_position = { x: 0, y: 0 };
  /**
   * Prevents default behavior for events
   * @param {FederatedWheelEvent | FederatedPointerEvent} event - The event to prevent default behavior for
   */
  prevent_default(event) {
    event.preventDefault();
    event.stopPropagation();
  }
  /**
   * Sets the current tool and subtool
   * @param {ToolbarTool} tool - The tool to set
   * @param {Subtool} subtool - The subtool to set
   */
  set_tool(tool, subtool) {
    this.current_tool = tool;
    this.current_subtool = subtool;
  }
  /**
   * Sets the zoom level to a specific percentage or fits the image to the screen
   * @param {number | 'fit'} zoom_level - The zoom level to set (0-1) or 'fit' to use min_zoom
   * @returns {void}
   */
  set_zoom(zoom_level) {
    const fit_zoom = this.calculate_min_zoom(
      this.local_dimensions.width,
      this.local_dimensions.height
    );
    let target_zoom;
    const is_fit_zoom = zoom_level === "fit";
    if (is_fit_zoom) {
      target_zoom = fit_zoom;
    } else {
      target_zoom = Math.max(0, Math.min(this.max_zoom, zoom_level));
    }
    const canvas = this.image_editor_context.app.screen;
    const canvas_width = canvas.width;
    const canvas_height = canvas.height;
    let center_point;
    center_point = {
      x: canvas_width / 2,
      y: canvas_height / 2
    };
    this.zoom_to_point(target_zoom, center_point, true, is_fit_zoom);
  }
  /**
   * Sets up the zoom tool with the given context and state
   * @param {ImageEditorContext} context - The image editor context
   * @param {ToolbarTool} tool - The current tool
   * @param {Subtool} subtool - The current subtool
   * @returns {Promise<void>}
   */
  async setup(context, tool, subtool) {
    this.image_editor_context = context;
    this.current_tool = tool;
    this.current_subtool = subtool;
    this.pad_bottom = context.pad_bottom;
    const { width, height } = await this.get_container_dimensions();
    const fit_zoom = this.calculate_min_zoom(width, height);
    const min_zoom = Math.min(fit_zoom, 1);
    this.local_scale = min_zoom;
    const canvas = this.image_editor_context.app.screen;
    const center_x = canvas.width / 2;
    const center_y = canvas.height / 2;
    const scaled_width = width * min_zoom;
    const scaled_height = height * min_zoom;
    const initial_x = center_x - scaled_width / 2;
    const initial_y = center_y - scaled_height / 2;
    this.local_position = { x: initial_x, y: initial_y };
    this.setup_event_listeners();
    await this.image_editor_context.set_image_properties({
      scale: min_zoom,
      position: { x: initial_x, y: initial_y }
    });
    this.image_editor_context.dimensions.subscribe((dimensions) => {
      this.local_dimensions = dimensions;
    });
    this.image_editor_context.scale.subscribe((scale) => {
      this.local_scale = scale;
    });
    this.image_editor_context.position.subscribe((position) => {
      this.local_position = position;
    });
  }
  /**
   * Sets up event listeners for zoom and pan functionality
   * @private
   */
  setup_event_listeners() {
    const stage = this.image_editor_context.app.stage;
    const canvas = this.image_editor_context.app.canvas;
    canvas.addEventListener("wheel", this.prevent_default, { passive: false });
    stage.eventMode = "static";
    stage.hitArea = this.image_editor_context.app.screen;
    const wheel_listener = this.handle_wheel.bind(this);
    stage.addEventListener("wheel", wheel_listener, { passive: false });
    if ("ontouchstart" in window) {
      stage.addEventListener(
        "touchstart",
        this.handle_touch_start.bind(this)
      );
      stage.addEventListener(
        "touchmove",
        this.handle_touch_move.bind(this)
      );
      stage.addEventListener(
        "touchend",
        this.handle_touch_end.bind(this)
      );
    } else {
      stage.addEventListener(
        "pointerdown",
        this.handle_pointer_down.bind(this)
      );
      stage.addEventListener(
        "pointermove",
        this.handle_pointer_move.bind(this)
      );
      stage.addEventListener("pointerup", this.handle_pointer_up.bind(this));
      stage.addEventListener(
        "pointerupoutside",
        this.handle_pointer_up.bind(this)
      );
    }
  }
  /**
   * Handles wheel events for zooming
   * @param {FederatedWheelEvent} event - The wheel event
   * @private
   */
  handle_wheel(event) {
    const is_trackpad = event.deltaMode === 0 && Math.abs(event.deltaY) < 50;
    const scroll_speed = is_trackpad ? 30 : 10;
    if (event.altKey || event.metaKey) {
      const local_point = this.image_editor_context.app.stage.toLocal(
        event.global
      );
      const zoom_delta = -event.deltaY * (is_trackpad ? 1e-3 : 5e-4);
      const new_scale = this.local_scale * (1 + zoom_delta);
      this.zoom_to_point(new_scale, local_point, true);
    } else {
      const delta_x = event.deltaX;
      const delta_y = event.deltaY;
      const raw_position = {
        x: this.local_position.x - delta_x / 100 * scroll_speed,
        y: this.local_position.y - delta_y / 100 * scroll_speed
      };
      const canvas = this.image_editor_context.app.screen;
      const scaled_width = this.local_dimensions.width * this.local_scale;
      const scaled_height = this.local_dimensions.height * this.local_scale;
      const available_width = canvas.width - this.border_padding * 2;
      const available_height = canvas.height - this.border_padding * 2 - this.pad_bottom;
      let final_position = { ...raw_position };
      if (scaled_width <= available_width) {
        final_position.x = (canvas.width - scaled_width) / 2;
      } else {
        const max_offset = canvas.width / 2;
        const left_bound = max_offset;
        const right_bound = canvas.width - max_offset - scaled_width;
        final_position.x = Math.min(
          Math.max(raw_position.x, right_bound),
          left_bound
        );
      }
      if (scaled_height <= available_height) {
        final_position.y = (canvas.height - this.pad_bottom - scaled_height) / 2;
      } else {
        const max_offset = (canvas.height - this.pad_bottom) / 2;
        const top_bound = max_offset;
        const bottom_bound = canvas.height - this.pad_bottom - max_offset - scaled_height;
        final_position.y = Math.min(
          Math.max(raw_position.y, bottom_bound),
          top_bound
        );
      }
      this.image_editor_context.set_image_properties({
        scale: this.local_scale,
        position: final_position
      });
    }
  }
  /**
   * Cleans up resources and event listeners
   */
  cleanup() {
    const stage = this?.image_editor_context?.app?.stage;
    if (!stage) return;
    stage.removeEventListener("wheel", this.handle_wheel.bind(this));
    if ("ontouchstart" in window) {
      stage.removeEventListener(
        "touchstart",
        this.handle_touch_start.bind(this)
      );
      stage.removeEventListener(
        "touchmove",
        this.handle_touch_move.bind(this)
      );
      stage.removeEventListener(
        "touchend",
        this.handle_touch_end.bind(this)
      );
    } else {
      stage.removeEventListener(
        "pointerdown",
        this.handle_pointer_down.bind(this)
      );
      stage.removeEventListener(
        "pointermove",
        this.handle_pointer_move.bind(this)
      );
      stage.removeEventListener("pointerup", this.handle_pointer_up.bind(this));
      stage.removeEventListener(
        "pointerupoutside",
        this.handle_pointer_up.bind(this)
      );
    }
  }
  /**
   * Gets the dimensions of the container
   * @returns {Promise<{width: number, height: number}>} The container dimensions
   * @private
   */
  async get_container_dimensions() {
    const bounds = this.image_editor_context.image_container.getLocalBounds();
    return {
      width: bounds.width,
      height: bounds.height
    };
  }
  /**
   * Calculates the minimum zoom level to fit the image in the viewport
   * @param {number} container_width - The width of the container
   * @param {number} container_height - The height of the container
   * @returns {number} The minimum zoom level
   * @private
   */
  calculate_min_zoom(container_width, container_height) {
    const canvas = this.image_editor_context.app.screen;
    const viewport_width = canvas.width;
    const viewport_height = canvas.height;
    if (!container_width || !viewport_width || !container_height || !viewport_height) {
      return 1;
    }
    const available_width = viewport_width - this.border_padding * 2;
    const available_height = viewport_height - this.border_padding * 2 - this.pad_bottom;
    const width_ratio = available_width / container_width;
    const height_ratio = available_height / container_height;
    return Math.min(width_ratio, height_ratio);
  }
  /**
   * Handles touch start events
   * @param {TouchEvent} event - The touch event
   * @private
   */
  handle_touch_start(event) {
    event.preventDefault();
    const touchEvent = event;
    if (touchEvent.touches && touchEvent.touches.length === 2) {
      this.is_pinching = true;
      this.last_pinch_distance = Math.hypot(
        touchEvent.touches[0].pageX - touchEvent.touches[1].pageX,
        touchEvent.touches[0].pageY - touchEvent.touches[1].pageY
      );
    } else if (touchEvent.touches && touchEvent.touches.length === 1) {
      this.is_dragging = true;
      const rect = this.image_editor_context.app.view.getBoundingClientRect();
      this.last_touch_position = new Point(
        touchEvent.touches[0].pageX - rect.left,
        touchEvent.touches[0].pageY - rect.top
      );
    }
  }
  /**
   * Handles touch move events
   * @param {TouchEvent} event - The touch event
   * @private
   */
  handle_touch_move(event) {
    event.preventDefault();
    const touchEvent = event;
    if (this.is_pinching && touchEvent.touches && touchEvent.touches.length === 2) {
      const rect = this.image_editor_context.app.view.getBoundingClientRect();
      const current_distance = Math.hypot(
        touchEvent.touches[0].pageX - touchEvent.touches[1].pageX,
        touchEvent.touches[0].pageY - touchEvent.touches[1].pageY
      );
      const pinch_center = {
        x: (touchEvent.touches[0].pageX + touchEvent.touches[1].pageX) / 2 - rect.left,
        y: (touchEvent.touches[0].pageY + touchEvent.touches[1].pageY) / 2 - rect.top
      };
      const scale_val = current_distance / this.last_pinch_distance;
      this.last_pinch_distance = current_distance;
      this.zoom_to_point(this.local_scale * scale_val, pinch_center);
    } else if (this.is_dragging && touchEvent.touches && touchEvent.touches.length === 1 && this.last_touch_position) {
      const rect = this.image_editor_context.app.view.getBoundingClientRect();
      const current_position = new Point(
        touchEvent.touches[0].pageX - rect.left,
        touchEvent.touches[0].pageY - rect.top
      );
      const dx = current_position.x - this.last_touch_position.x;
      const dy = current_position.y - this.last_touch_position.y;
      this.image_editor_context.set_image_properties({
        position: {
          x: this.local_position.x + dx,
          y: this.local_position.y + dy
        }
      });
      this.last_touch_position = current_position;
    }
  }
  /**
   * Handles touch end events
   * @param {TouchEvent} event - The touch event
   * @private
   */
  handle_touch_end(event) {
    event.preventDefault();
    const touchEvent = event;
    if (touchEvent.touches && touchEvent.touches.length < 2) {
      this.is_pinching = false;
      if (touchEvent.touches && touchEvent.touches.length === 1) {
        const rect = this.image_editor_context.app.view.getBoundingClientRect();
        this.last_touch_position = new Point(
          touchEvent.touches[0].pageX - rect.left,
          touchEvent.touches[0].pageY - rect.top
        );
        this.is_dragging = true;
      }
    }
    if (touchEvent.touches && touchEvent.touches.length === 0) {
      this.is_dragging = false;
      this.last_touch_position = null;
      this.last_pinch_distance = 0;
    }
  }
  /**
   * Gets the bounded position to keep the image within the viewport
   * @param {Object} position - The position to bound
   * @param {number} position.x - The x coordinate
   * @param {number} position.y - The y coordinate
   * @returns {Object} The bounded position
   * @private
   */
  get_bounded_position(position) {
    const canvas = this.image_editor_context.app.screen;
    const scaled_width = this.local_dimensions.width * this.local_scale;
    const scaled_height = this.local_dimensions.height * this.local_scale;
    const center_position = {
      x: (canvas.width - scaled_width) / 2,
      y: (canvas.height - scaled_height - this.pad_bottom) / 2
    };
    if (scaled_width <= canvas.width && scaled_height <= canvas.height) {
      return center_position;
    }
    let x = position.x;
    let y = position.y;
    if (scaled_width <= canvas.width) {
      x = center_position.x;
    } else {
      const min_x = canvas.width - scaled_width;
      const max_x = 0;
      x = Math.max(min_x, Math.min(max_x, position.x));
    }
    if (scaled_height <= canvas.height - this.pad_bottom) {
      y = center_position.y;
    } else {
      const min_y = canvas.height - scaled_height - this.pad_bottom;
      const max_y = 0;
      y = Math.max(min_y, Math.min(max_y, position.y));
    }
    return { x, y };
  }
  /**
   * Zooms to a specific point with a new zoom level
   * @param {number} new_zoom - The new zoom level
   * @param {Object} point - The point to zoom to
   * @param {number} point.x - The x coordinate of the point
   * @param {number} point.y - The y coordinate of the point
   * @param {boolean} hard - Whether to apply a hard zoom (no animation)
   * @param {boolean} is_fit_zoom - Whether this is a fit zoom operation
   * @private
   */
  zoom_to_point(new_zoom, point, hard, is_fit_zoom) {
    const container = this.image_editor_context.image_container;
    const current_world_pos = container.getGlobalPosition();
    const current_scale = container.scale.x;
    const cursor_image_pixel = {
      x: (point.x - current_world_pos.x) / current_scale,
      y: (point.y - current_world_pos.y) / current_scale
    };
    const fit_zoom = this.calculate_min_zoom(
      this.local_dimensions.width,
      this.local_dimensions.height
    );
    const min_zoom = Math.min(fit_zoom, 1);
    new_zoom = Math.min(Math.max(new_zoom, min_zoom), this.max_zoom);
    const new_container_world_pos = {
      x: point.x - cursor_image_pixel.x * new_zoom,
      y: point.y - cursor_image_pixel.y * new_zoom
    };
    if (new_zoom === min_zoom || is_fit_zoom) {
      const canvas_width = this.image_editor_context.app.screen.width;
      const canvas_height = this.image_editor_context.app.screen.height;
      const new_scaled_width = this.local_dimensions.width * new_zoom;
      const new_scaled_height = this.local_dimensions.height * new_zoom;
      new_container_world_pos.x = (canvas_width - new_scaled_width) / 2;
      new_container_world_pos.y = (canvas_height - this.pad_bottom - new_scaled_height) / 2;
    }
    this.image_editor_context.set_image_properties({
      scale: new_zoom,
      position: new_container_world_pos,
      animate: typeof hard === "boolean" ? !hard : new_zoom === min_zoom
    });
    this.min_zoom.set(new_zoom === min_zoom);
  }
  /**
   * Handles pointer down events
   * @param {FederatedPointerEvent} event - The pointer event
   * @private
   */
  handle_pointer_down(event) {
    if (event.button === 0 && this.current_tool === "pan") {
      this.is_pointer_dragging = true;
      this.drag_start.copyFrom(event.global);
      this.drag_start.x -= this.local_position.x;
      this.drag_start.y -= this.local_position.y;
    }
  }
  /**
   * Handles pointer move events
   * @param {FederatedPointerEvent} event - The pointer event
   * @private
   */
  handle_pointer_move(event) {
    if (this.is_pointer_dragging && this.current_tool === "pan") {
      const raw_position = {
        x: event.global.x - this.drag_start.x,
        y: event.global.y - this.drag_start.y
      };
      const canvas = this.image_editor_context.app.screen;
      const scaled_width = this.local_dimensions.width * this.local_scale;
      const scaled_height = this.local_dimensions.height * this.local_scale;
      const available_width = canvas.width - this.border_padding * 2;
      const available_height = canvas.height - this.border_padding * 2 - this.pad_bottom;
      let final_position = { ...raw_position };
      if (scaled_width <= available_width) {
        final_position.x = (canvas.width - scaled_width) / 2;
      } else {
        final_position.x = raw_position.x;
      }
      if (scaled_height <= available_height) {
        final_position.y = (canvas.height - this.pad_bottom - scaled_height) / 2;
      } else {
        final_position.y = raw_position.y;
      }
      this.image_editor_context.set_image_properties({
        scale: this.local_scale,
        position: final_position
      });
    }
  }
  /**
   * Handles pointer up events
   * @param {FederatedPointerEvent} event - The pointer event
   * @private
   */
  handle_pointer_up(event) {
    if (this.is_pointer_dragging && this.current_tool === "pan") {
      this.is_pointer_dragging = false;
      const raw_position = {
        x: event.global.x - this.drag_start.x,
        y: event.global.y - this.drag_start.y
      };
      const canvas = this.image_editor_context.app.screen;
      const scaled_width = this.local_dimensions.width * this.local_scale;
      const scaled_height = this.local_dimensions.height * this.local_scale;
      const available_width = canvas.width - this.border_padding * 2;
      const available_height = canvas.height - this.border_padding * 2 - this.pad_bottom;
      let final_position = { ...raw_position };
      if (scaled_width <= available_width) {
        final_position.x = (canvas.width - scaled_width) / 2;
      } else {
        const max_offset = canvas.width / 2;
        const left_bound = max_offset;
        const right_bound = canvas.width - max_offset - scaled_width;
        final_position.x = Math.min(
          Math.max(raw_position.x, right_bound),
          left_bound
        );
      }
      if (scaled_height <= available_height) {
        final_position.y = (canvas.height - this.pad_bottom - scaled_height) / 2;
      } else {
        const max_offset = (canvas.height - this.pad_bottom) / 2;
        const top_bound = max_offset;
        const bottom_bound = canvas.height - this.pad_bottom - max_offset - scaled_height;
        final_position.y = Math.min(
          Math.max(raw_position.y, bottom_bound),
          top_bound
        );
      }
      this.image_editor_context.set_image_properties({
        scale: this.local_scale,
        position: final_position,
        animate: true
      });
    }
  }
}

class LayerManager {
  layers = [];
  active_layer = null;
  active_layer_id = null;
  draw_textures = /* @__PURE__ */ new Map();
  layer_store = writable({
    active_layer: "",
    layers: []
  });
  background_layer = null;
  image_container;
  app;
  fixed_canvas;
  dark;
  border_region;
  layer_options;
  constructor(image_container, app, fixed_canvas, dark, border_region, layer_options) {
    this.image_container = image_container;
    this.app = app;
    this.fixed_canvas = fixed_canvas;
    this.dark = dark;
    this.border_region = border_region;
    this.layer_options = layer_options;
  }
  toggle_layer_visibility(id) {
    const layer = this.layers.find((l) => l.id === id);
    if (layer) {
      layer.container.visible = !layer.container.visible;
      layer.visible = layer.container.visible;
      this.layer_store.update((state) => ({
        active_layer: state.active_layer,
        layers: this.layers
      }));
    }
  }
  create_background_layer(width, height) {
    if (this.background_layer) {
      this.background_layer.destroy();
    }
    const layer = new Container();
    this.background_layer = layer;
    const bg_texture = RenderTexture.create({
      width,
      height,
      resolution: window.devicePixelRatio,
      antialias: true,
      scaleMode: SCALE_MODES.NEAREST
    });
    const bg_sprite = new Sprite(bg_texture);
    layer.addChild(bg_sprite);
    const clear_graphics = new Graphics();
    clear_graphics.clear();
    clear_graphics.rect(0, 0, width, height).fill({ color: this.dark ? 3355443 : 16777215, alpha: 1 });
    this.app.renderer.render({
      container: clear_graphics,
      target: bg_texture,
      clear: true
    });
    this.image_container.addChild(layer);
    layer.zIndex = -1;
    this.update_layer_order();
    return layer;
  }
  set_layer_options(layer_options, width, height) {
    this.layer_options = layer_options;
    this.reset_layers(width, height);
  }
  /**
   * Creates a background layer from an image URL
   * @param url The URL of the image to use as background
   * @param width The width of the layer (if fixed_canvas is true)
   * @param height The height of the layer (if fixed_canvas is true)
   * @param borderRegion Minimum border region to add around the image for outpainting (in pixels)
   * @returns A Promise that resolves to the background Container when the image is loaded
   */
  async create_background_layer_from_url(url, width, height) {
    const layer = this.create_background_layer(
      width || this.image_container.width,
      height || this.image_container.height
    );
    try {
      const texture = await Texture.from(url);
      const sprite = new Sprite(texture);
      const imageWidth = sprite.texture.width;
      const imageHeight = sprite.texture.height;
      const containerWidth = width || this.image_container.width;
      const containerHeight = height || this.image_container.height;
      if (this.fixed_canvas) {
        const effectiveContainerWidth = Math.max(
          containerWidth - this.border_region * 2,
          10
        );
        const effectiveContainerHeight = Math.max(
          containerHeight - this.border_region * 2,
          10
        );
        const imageAspectRatio = imageWidth / imageHeight;
        const containerAspectRatio = effectiveContainerWidth / effectiveContainerHeight;
        let finalWidth, finalHeight;
        let posX = this.border_region, posY = this.border_region;
        if (imageWidth <= effectiveContainerWidth && imageHeight <= effectiveContainerHeight) {
          finalWidth = imageWidth;
          finalHeight = imageHeight;
        } else {
          if (imageAspectRatio > containerAspectRatio) {
            finalWidth = effectiveContainerWidth;
            finalHeight = effectiveContainerWidth / imageAspectRatio;
          } else {
            finalHeight = effectiveContainerHeight;
            finalWidth = effectiveContainerHeight * imageAspectRatio;
          }
        }
        posX += Math.round((effectiveContainerWidth - finalWidth) / 2);
        posY += Math.round((effectiveContainerHeight - finalHeight) / 2);
        sprite.width = finalWidth;
        sprite.height = finalHeight;
        sprite.position.set(posX, posY);
      } else {
        sprite.position.set(this.border_region, this.border_region);
        if (this.background_layer) {
          this.background_layer.destroy();
        }
        const totalWidth = imageWidth + this.border_region * 2;
        const totalHeight = imageHeight + this.border_region * 2;
        const newLayer = this.create_background_layer(totalWidth, totalHeight);
        sprite.width = imageWidth;
        sprite.height = imageHeight;
        newLayer.addChild(sprite);
        return newLayer;
      }
      layer.addChild(sprite);
      return layer;
    } catch (error) {
      console.error("Error loading image from URL:", error);
      return layer;
    }
  }
  create_layer({
    width,
    height,
    layer_name,
    user_created,
    layer_id = void 0,
    make_active = false
  }) {
    const layer = new Container();
    const _layer_id = layer_id || Math.random().toString(36).substring(2, 15);
    const _layer_name = layer_name || `Layer ${this.layers.length + 1}`;
    this.layers.push({
      name: _layer_name,
      id: _layer_id,
      container: layer,
      user_created,
      visible: true
    });
    this.image_container.addChild(layer);
    const draw_texture = RenderTexture.create({
      width,
      height,
      resolution: window.devicePixelRatio,
      antialias: true,
      scaleMode: SCALE_MODES.NEAREST
    });
    const canvas_sprite = new Sprite(draw_texture);
    layer.addChild(canvas_sprite);
    const clear_graphics = new Graphics();
    clear_graphics.clear();
    clear_graphics.beginFill(0, 0);
    clear_graphics.drawRect(0, 0, width, height);
    clear_graphics.endFill();
    this.app.renderer.render({
      container: clear_graphics,
      target: draw_texture,
      clear: true
    });
    this.draw_textures.set(layer, draw_texture);
    this.update_layer_order();
    if (make_active) {
      this.set_active_layer(_layer_id);
    }
    this.layer_store.set({
      active_layer: this.active_layer_id || "",
      layers: this.layers
    });
    return layer;
  }
  /**
   * Creates a new layer with an image loaded from a URL
   * @param url The URL of the image to load
   * @param borderRegion Minimum border region to add around the image for outpainting (in pixels)
   * @returns A Promise that resolves to the layer ID
   */
  async add_layer_from_url(url) {
    const { width, height } = this.image_container.getLocalBounds();
    const layer = this.create_layer({
      width,
      height,
      layer_name: "Layer 1",
      user_created: true
    });
    const layerIndex = this.layers.findIndex((l) => l.container === layer);
    if (layerIndex === -1) {
      console.error("Could not find newly created layer");
      return "";
    }
    const layerId = this.layers[layerIndex].id;
    try {
      const texture = await Assets.load(url);
      const drawTexture = this.draw_textures.get(layer);
      if (!drawTexture) {
        console.error("No draw texture found for layer");
        return layerId;
      }
      const sprite = new Sprite(texture);
      const imageWidth = sprite.width;
      const imageHeight = sprite.height;
      let posX = this.border_region;
      let posY = this.border_region;
      const effectiveWidth = this.fixed_canvas ? width - this.border_region * 2 : width;
      const effectiveHeight = this.fixed_canvas ? height - this.border_region * 2 : height;
      if (imageWidth < effectiveWidth || imageHeight < effectiveHeight) {
        posX = Math.floor((effectiveWidth - imageWidth) / 2);
        posY = Math.floor((effectiveHeight - imageHeight) / 2);
      }
      sprite.position.set(posX, posY);
      if (imageWidth > effectiveWidth || imageHeight > effectiveHeight) {
        const imageAspectRatio = imageWidth / imageHeight;
        const areaAspectRatio = effectiveWidth / effectiveHeight;
        let finalWidth, finalHeight;
        if (imageAspectRatio > areaAspectRatio) {
          finalWidth = effectiveWidth;
          finalHeight = effectiveWidth / imageAspectRatio;
        } else {
          finalHeight = effectiveHeight;
          finalWidth = effectiveHeight * imageAspectRatio;
        }
        sprite.width = finalWidth;
        sprite.height = finalHeight;
        posX = this.border_region + Math.floor((effectiveWidth - finalWidth) / 2);
        posY = this.border_region + Math.floor((effectiveHeight - finalHeight) / 2);
        sprite.position.set(posX, posY);
      }
      this.app.renderer.render(sprite, { renderTexture: drawTexture });
      this.set_active_layer(layerId);
      return layerId;
    } catch (error) {
      console.error("Error loading image from URL:", error);
      return layerId;
    }
  }
  get_active_layer() {
    return this.active_layer;
  }
  set_active_layer(id) {
    if (this.layers.some((l) => l.id === id)) {
      this.active_layer = this.layers.find((l) => l.id === id)?.container || this.layers[0]?.container || null;
      this.active_layer_id = id;
      this.layer_store.set({
        active_layer: id,
        layers: this.layers
      });
    }
  }
  get_layers() {
    return this.layers;
  }
  get_layer_textures(id) {
    const layer = this.layers.find((l) => l.id === id);
    if (layer) {
      const draw = this.draw_textures.get(layer.container);
      if (draw) {
        return { draw };
      }
    }
    return null;
  }
  delete_layer(id) {
    const index = this.layers.findIndex((l) => l.id === id);
    if (index > -1) {
      const draw_texture = this.draw_textures.get(this.layers[index].container);
      if (draw_texture) {
        draw_texture.destroy();
        this.draw_textures.delete(this.layers[index].container);
      }
      this.layers[index].container.destroy();
      if (this.active_layer === this.layers[index].container) {
        const new_active_layer = this.layers[Math.max(0, index - 1)] || null;
        this.active_layer = new_active_layer?.container || null;
        this.active_layer_id = new_active_layer?.id || null;
      }
      this.layers = this.layers.filter((l) => l.id !== id);
      this.layer_store.update((_layers) => ({
        active_layer: _layers.active_layer === id ? this.layers[this.layers.length - 1]?.id : _layers.active_layer,
        layers: this.layers
      }));
      this.update_layer_order();
    }
  }
  update_layer_order() {
    if (this.background_layer) {
      this.background_layer.zIndex = -1;
    }
    this.layers.forEach((layer, index) => {
      layer.container.zIndex = index;
    });
  }
  move_layer(id, direction) {
    const index = this.layers.findIndex((l) => l.id === id);
    if (index > -1) {
      const new_index = direction === "up" ? index - 1 : index + 1;
      this.layers = this.layers.map((l, i) => {
        if (i === index) {
          return this.layers[new_index];
        }
        if (i === new_index) {
          return this.layers[index];
        }
        return l;
      });
      this.update_layer_order();
      this.layer_store.update((_layers) => ({
        active_layer: id,
        layers: this.layers
      }));
    }
  }
  /**
   * Resizes all layers to a new width and height
   * @param newWidth The new width of the layers
   * @param newHeight The new height of the layers
   * @param scale If true, scales the layer content to fit the new dimensions. If false, keeps the content size unchanged.
   * @param anchor The anchor point to position the content relative to the new size
   */
  resize_all_layers(newWidth, newHeight, scale, anchor, oldCanvasWidth, oldCanvasHeight) {
    new Map(this.layers.map((l) => [l.id, l]));
    const oldBackgroundLayer = this.background_layer;
    const calculateOffset = () => {
      let offsetX = 0;
      let offsetY = 0;
      const deltaWidth = newWidth - oldCanvasWidth;
      const deltaHeight = newHeight - oldCanvasHeight;
      if (anchor.includes("left")) {
        offsetX = 0;
      } else if (anchor.includes("right")) {
        offsetX = deltaWidth;
      } else {
        offsetX = Math.floor(deltaWidth / 2);
      }
      if (anchor.includes("top")) {
        offsetY = 0;
      } else if (anchor.includes("bottom")) {
        offsetY = deltaHeight;
      } else {
        offsetY = Math.floor(deltaHeight / 2);
      }
      return { offsetX, offsetY };
    };
    this.background_layer = this._resize_background_layer(
      oldBackgroundLayer,
      newWidth,
      newHeight,
      scale,
      calculateOffset
    );
    const processedLayers = [];
    const oldLayerData = this.layers.map((oldLayer) => ({
      id: oldLayer.id,
      name: oldLayer.name,
      user_created: oldLayer.user_created,
      texture: this.draw_textures.get(oldLayer.container),
      container: oldLayer.container
    }));
    this.layers = [];
    this.draw_textures.clear();
    for (const oldData of oldLayerData) {
      const newLayer = this._resize_single_layer(
        oldData,
        newWidth,
        newHeight,
        scale,
        calculateOffset
      );
      if (newLayer) {
        processedLayers.push(newLayer);
      }
    }
    const currentActiveId = get$1(this.layer_store).active_layer;
    const activeLayerExists = processedLayers.some(
      (l) => l.id === currentActiveId
    );
    if (!activeLayerExists && processedLayers.length > 0) {
      this.set_active_layer(processedLayers[0].id);
    } else if (processedLayers.length === 0) {
      this.layer_store.update((s) => ({ ...s, active_layer: "" }));
    } else {
      this.layer_store.update((s) => ({ ...s }));
    }
    this.update_layer_order();
    setTimeout(() => {
      Assets.cache.reset();
      this.app.renderer.textureGC.run();
    }, 100);
  }
  /**
   * Helper method to resize the background layer.
   */
  _resize_background_layer(oldBackgroundLayer, newWidth, newHeight, scale, calculateOffset) {
    if (!oldBackgroundLayer) {
      return this.create_background_layer(newWidth, newHeight);
    }
    let backgroundImage = oldBackgroundLayer.children.find(
      (child) => child instanceof Sprite && child.texture !== oldBackgroundLayer.children[0]?.texture
    );
    const newBackgroundLayer = this.create_background_layer(
      newWidth,
      newHeight
    );
    if (backgroundImage) {
      const newBgImage = new Sprite(backgroundImage.texture);
      newBgImage.width = backgroundImage.width;
      newBgImage.height = backgroundImage.height;
      if (scale) {
        newBgImage.width = newWidth;
        newBgImage.height = newHeight;
        newBgImage.position.set(0, 0);
      } else {
        const { offsetX, offsetY } = calculateOffset();
        newBgImage.position.set(
          backgroundImage.x + offsetX,
          backgroundImage.y + offsetY
        );
      }
      newBackgroundLayer.addChild(newBgImage);
    }
    return newBackgroundLayer;
  }
  /**
   * Helper method to resize a single regular layer.
   */
  _resize_single_layer(oldData, newWidth, newHeight, scale, calculateOffset) {
    if (!oldData.texture) {
      console.warn(
        `No texture found for layer ${oldData.id}, skipping cleanup.`
      );
      if (oldData.container && !oldData.container.destroyed) {
        if (this.image_container.children.includes(oldData.container)) {
          this.image_container.removeChild(oldData.container);
        }
        oldData.container.destroy({ children: true });
      }
      return null;
    }
    const newContainer = this.create_layer({
      width: newWidth,
      height: newHeight,
      layer_name: oldData.name,
      user_created: oldData.user_created
    });
    const newLayer = this.layers[this.layers.length - 1];
    newLayer.id = oldData.id;
    const newTexture = this.draw_textures.get(newContainer);
    if (!newTexture) {
      console.error(
        `Failed to get texture for newly created layer ${newLayer.id}. Cleaning up.`
      );
      if (newContainer && !newContainer.destroyed) {
        if (this.image_container.children.includes(newContainer)) {
          this.image_container.removeChild(newContainer);
        }
        newContainer.destroy({ children: true });
        const idx = this.layers.findIndex((l) => l.container === newContainer);
        if (idx > -1) this.layers.splice(idx, 1);
        this.draw_textures.delete(newContainer);
      }
      if (oldData.texture && !oldData.texture.destroyed)
        oldData.texture.destroy(true);
      if (oldData.container && !oldData.container.destroyed)
        oldData.container.destroy({ children: true });
      return null;
    }
    this.app.renderer.clear({ target: newTexture, clearColor: [0, 0, 0, 0] });
    const sprite = new Sprite(oldData.texture);
    if (scale) {
      sprite.width = newWidth;
      sprite.height = newHeight;
      sprite.position.set(0, 0);
    } else {
      const { offsetX, offsetY } = calculateOffset();
      sprite.position.set(offsetX, offsetY);
    }
    this.app.renderer.render(sprite, { renderTexture: newTexture });
    sprite.destroy();
    if (oldData.texture && !oldData.texture.destroyed) {
      oldData.texture.destroy(true);
    }
    if (oldData.container && !oldData.container.destroyed) {
      if (this.image_container.children.includes(oldData.container)) {
        this.image_container.removeChild(oldData.container);
      }
      oldData.container.destroy({ children: true });
    }
    return newLayer;
  }
  async get_blobs(width, height) {
    const blobs = {
      background: await get_canvas_blob(
        this.app.renderer,
        this.background_layer,
        {
          width,
          height,
          x: 0,
          y: 0
        }
      ),
      layers: await Promise.all(
        this.layers.map(async (layer) => {
          const blob = await get_canvas_blob(
            this.app.renderer,
            layer.container,
            {
              width,
              height,
              x: 0,
              y: 0
            }
          );
          if (blob) {
            return blob;
          }
          return null;
        })
      ),
      composite: await get_canvas_blob(
        this.app.renderer,
        this.image_container,
        {
          width,
          height,
          x: 0,
          y: 0
        }
      )
    };
    return blobs;
  }
  reset_layers(width, height, persist = false) {
    const _layers_to_recreate = persist ? this.layers.map((layer) => [layer.name, layer.id]) : this.layer_options.layers.map((layer) => [layer, void 0]);
    this.layers.forEach((layer) => {
      this.delete_layer(layer.id);
    });
    for (const [layer_name, layer_id] of _layers_to_recreate) {
      this.create_layer({
        width,
        height,
        layer_name,
        user_created: this.layer_options.layers.find((l) => l === layer_name) ? false : true,
        layer_id
      });
    }
    if (!persist) {
      this.active_layer = this.layers[0].container;
      this.active_layer_id = this.layers[0].id;
    } else {
      this.active_layer = this.layers.find((l) => l.id === this.active_layer_id)?.container || this.layers[0]?.container;
      if (!this.active_layer) return;
    }
    this.layer_store.update((state) => ({
      active_layer: this.active_layer_id || this.layers[0].id,
      layers: this.layers
    }));
  }
  init_layers(width, height) {
    for (const layer of this.layers) {
      this.delete_layer(layer.id);
    }
    let i = 0;
    for (const layer of this.layer_options.layers) {
      this.create_layer({
        width,
        height,
        layer_name: layer,
        user_created: false,
        layer_id: `layer-${i}`
      });
      i++;
    }
    this.active_layer = this.layers[0].container;
    this.active_layer_id = this.layers[0].id;
    this.layer_store.update((_layers) => ({
      active_layer: this.layers[0].id,
      layers: this.layers
    }));
  }
}
class AddLayerCommand {
  constructor(context, options) {
    this.context = context;
    this.width = options.width;
    this.height = options.height;
    this.layer_name = options.layer_name || `Layer ${this.context.layer_manager.get_layers().length + 1}`;
    this.user_created = options.user_created;
    this.layer_id = options.layer_id || Math.random().toString(36).substring(2, 15);
    this.make_active = options.make_active || false;
    this.name = "AddLayer";
    const current_layers = this.context.layer_manager.get_layers();
    const current_active = current_layers.find(
      (l) => l.container === this.context.layer_manager.get_active_layer()
    );
    this.previous_active_layer = current_active?.id || null;
  }
  layer_id;
  layer_name;
  width;
  height;
  user_created;
  make_active;
  previous_active_layer = null;
  name;
  async execute(context) {
    if (context) {
      this.context = context;
    }
    this.context.layer_manager.create_layer({
      width: this.width,
      height: this.height,
      layer_name: this.layer_name,
      user_created: this.user_created,
      layer_id: this.layer_id,
      make_active: this.make_active
    });
  }
  async undo() {
    this.context.layer_manager.delete_layer(this.layer_id);
    if (this.previous_active_layer) {
      this.context.layer_manager.set_active_layer(this.previous_active_layer);
    }
  }
}
class RemoveLayerCommand {
  constructor(context, layer_id) {
    this.context = context;
    this.name = "RemoveLayer";
    const layers = this.context.layer_manager.get_layers();
    const layer_to_remove = layers.find((l) => l.id === layer_id);
    if (!layer_to_remove) {
      throw new Error(`Layer with ID ${layer_id} not found`);
    }
    const active_layer = this.context.layer_manager.get_active_layer();
    const was_active = layer_to_remove.container === active_layer;
    if (was_active) {
      const layer_index = layers.findIndex((l) => l.id === layer_id);
      const next_active = layers[Math.max(0, layer_index - 1)];
      this.previous_active_layer = next_active?.id || null;
    }
    this.layer_data = {
      id: layer_to_remove.id,
      name: layer_to_remove.name,
      user_created: layer_to_remove.user_created,
      visible: layer_to_remove.visible,
      was_active
    };
    this.captureTextureData(layer_id);
  }
  layer_data;
  previous_active_layer = null;
  texture_copy = null;
  name;
  /**
   * Create a direct copy of the layer's texture
   */
  captureTextureData(layer_id) {
    const layer_textures = this.context.layer_manager.get_layer_textures(layer_id);
    if (!layer_textures) return;
    try {
      const original_texture = layer_textures.draw;
      const texture_copy = RenderTexture.create({
        width: original_texture.width,
        height: original_texture.height,
        resolution: window.devicePixelRatio || 1
      });
      const sprite = new Sprite(original_texture);
      this.context.app.renderer.render(sprite, { renderTexture: texture_copy });
      this.texture_copy = texture_copy;
      sprite.destroy();
    } catch (e) {
      console.error("Failed to copy layer texture:", e);
      this.texture_copy = null;
    }
  }
  async execute(context) {
    if (context) {
      this.context = context;
    }
    this.context.layer_manager.delete_layer(this.layer_data.id);
  }
  async undo() {
    const dimensions = get$1(this.context.dimensions);
    this.context.layer_manager.create_layer({
      width: dimensions.width,
      height: dimensions.height,
      layer_name: this.layer_data.name,
      user_created: this.layer_data.user_created,
      layer_id: this.layer_data.id,
      make_active: this.layer_data.was_active
    });
    if (this.texture_copy) {
      try {
        const layer_textures = this.context.layer_manager.get_layer_textures(
          this.layer_data.id
        );
        if (layer_textures) {
          const sprite = new Sprite(this.texture_copy);
          this.context.app.renderer.render(sprite, {
            renderTexture: layer_textures.draw
          });
          sprite.destroy();
        }
      } catch (e) {
        console.error("Failed to restore layer content:", e);
      }
    }
    if (!this.layer_data.visible) {
      this.context.layer_manager.toggle_layer_visibility(this.layer_data.id);
    }
    if (!this.layer_data.was_active && this.previous_active_layer) {
      this.context.layer_manager.set_active_layer(this.previous_active_layer);
    }
  }
  /**
   * Clean up resources when the command is no longer needed
   * This would need to be called by a garbage collection mechanism
   */
  destroy() {
    if (this.texture_copy) {
      this.texture_copy.destroy();
      this.texture_copy = null;
    }
  }
}
class ReorderLayerCommand {
  constructor(context, layer_id, direction) {
    this.context = context;
    this.layer_id = layer_id;
    this.direction = direction;
    this.name = "ReorderLayer";
    const layers = this.context.layer_manager.get_layers();
    this.original_order = layers.map((l) => l.id);
    const index = layers.findIndex((l) => l.id === layer_id);
    if (index === -1) {
      throw new Error(`Layer with ID ${layer_id} not found`);
    }
    const new_index = direction === "up" ? index - 1 : index + 1;
    if (new_index < 0 || new_index >= layers.length) {
      this.new_order = [...this.original_order];
    } else {
      this.new_order = [...this.original_order];
      [this.new_order[index], this.new_order[new_index]] = [
        this.new_order[new_index],
        this.new_order[index]
      ];
    }
  }
  original_order;
  new_order;
  layer_id;
  direction;
  name;
  async execute(context) {
    if (context) {
      this.context = context;
    }
    this.context.layer_manager.move_layer(this.layer_id, this.direction);
  }
  async undo() {
    const current_layers = this.context.layer_manager.get_layers();
    if (this.original_order.join(",") === current_layers.map((l) => l.id).join(",")) {
      return;
    }
    const layer_index = current_layers.findIndex((l) => l.id === this.layer_id);
    if (layer_index === -1) return;
    const original_index = this.original_order.indexOf(this.layer_id);
    const move_direction = layer_index > original_index ? "up" : "down";
    this.context.layer_manager.move_layer(this.layer_id, move_direction);
  }
}

const core_tool_map = {
  image: () => new ImageTool(),
  zoom: () => new ZoomTool()
};
const spring_config = {
  stiffness: 0.45,
  damping: 0.8
};
class EditorState {
  state;
  scale;
  position;
  subscribers;
  constructor(editor) {
    if (!(editor instanceof ImageEditor)) {
      throw new Error("EditorState must be created by ImageEditor");
    }
    this.scale = 1;
    this.position = { x: 0, y: 0 };
    this.subscribers = /* @__PURE__ */ new Set();
    this.state = Object.freeze({
      get position() {
        return { ...this.position };
      },
      get scale() {
        return this.scale;
      },
      get current_tool() {
        return this.current_tool;
      },
      get current_subtool() {
        return this.current_subtool;
      },
      subscribe: this.subscribe.bind(this)
    });
  }
  _set_position(x, y) {
    const oldPosition = { ...this.position };
    this.position = { x, y };
    this._notify_subscribers("position", oldPosition, this.position);
  }
  _set_scale(scale) {
    const oldScale = this.scale;
    this.scale = scale;
    this._notify_subscribers("scale", oldScale, scale);
  }
  _notify_subscribers(property, oldValue, newValue) {
    this.subscribers.forEach((callback) => {
      callback({
        property,
        oldValue,
        newValue,
        timestamp: Date.now()
      });
    });
  }
  subscribe(callback) {
    this.subscribers.add(callback);
    return () => this.subscribers.delete(callback);
  }
}
class ImageEditor {
  ready;
  background_image_present = writable(false);
  min_zoom = writable(true);
  app;
  ui_container;
  image_container;
  command_manager;
  layer_manager;
  tools = /* @__PURE__ */ new Map();
  current_tool;
  current_subtool;
  target_element;
  width;
  height;
  original_width;
  original_height;
  dimensions;
  scale;
  position;
  state;
  scale_value = 1;
  position_value = { x: 0, y: 0 };
  dimensions_value = {
    width: 0,
    height: 0
  };
  layers = writable({
    active_layer: "",
    layers: []
  });
  outline_container;
  outline_graphics;
  background_image;
  ready_resolve;
  event_callbacks = /* @__PURE__ */ new Map();
  fixed_canvas;
  dark;
  border_region;
  layer_options;
  overlay_container;
  overlay_graphics;
  pad_bottom;
  theme_mode;
  constructor(options) {
    this.pad_bottom = options.pad_bottom || 0;
    this.dark = options.dark || false;
    this.theme_mode = options.theme_mode || "dark";
    this.target_element = options.target_element;
    this.width = options.width;
    this.height = options.height;
    this.original_width = options.width;
    this.original_height = options.height;
    this.command_manager = new CommandManager();
    this.ready = new Promise((resolve) => {
      this.ready_resolve = resolve;
    });
    this.fixed_canvas = options.fixed_canvas || false;
    this.tools = new Map(
      options.tools.map((tool) => {
        if (typeof tool === "string") {
          return [tool, core_tool_map[tool]()];
        }
        return [tool.name, tool];
      })
    );
    for (const tool of this.tools.values()) {
      if (tool?.on) {
        tool.on("change", () => {
          this.notify("change");
        });
      }
    }
    this.dimensions = spring(
      { width: this.width, height: this.height },
      spring_config
    );
    this.scale = spring(1, spring_config);
    this.position = spring({ x: 0, y: 0 }, spring_config);
    this.state = new EditorState(this);
    this.border_region = options.border_region || 0;
    this.layer_options = options.layer_options || {
      allow_additional_layers: true,
      layers: ["Layer 1"],
      disabled: false
    };
    this.scale.subscribe((scale) => {
      this.state._set_scale(scale);
    });
    this.position.subscribe((position) => {
      this.state._set_position(position.x, position.y);
    });
    this.init();
  }
  get context() {
    const editor = this;
    return {
      app: this.app,
      ui_container: this.ui_container,
      image_container: this.image_container,
      get background_image() {
        return editor.background_image;
      },
      pad_bottom: this.pad_bottom,
      command_manager: this.command_manager,
      layer_manager: this.layer_manager,
      dimensions: { subscribe: this.dimensions.subscribe },
      scale: { subscribe: this.scale.subscribe },
      position: { subscribe: this.position.subscribe },
      set_image_properties: this.set_image_properties.bind(this),
      execute_command: this.execute_command.bind(this),
      resize_canvas: this.resize_canvas.bind(this),
      reset: this.reset.bind(this),
      set_background_image: this.set_background_image.bind(this)
    };
  }
  async init() {
    const container_box = this.target_element.getBoundingClientRect();
    new ResizeObserver((entries) => {
      entries.forEach((entry) => {
        this.resize_canvas(
          entry.contentBoxSize[0].inlineSize,
          entry.contentBoxSize[0].blockSize
        );
      });
    }).observe(this.target_element);
    this.app = new Application();
    if (!this.dark) {
      globalThis.__PIXI_APP__ = this.app;
    }
    await this.app.init({
      width: container_box.width,
      height: container_box.height,
      backgroundAlpha: this.dark ? 0 : 1,
      backgroundColor: this.theme_mode === "dark" ? "#27272a" : "#ffffff",
      resolution: window.devicePixelRatio,
      autoDensity: true,
      antialias: true,
      powerPreference: "high-performance"
    });
    const canvas = this.app.canvas;
    canvas.style.background = "transparent";
    this.setup_containers();
    this.layer_manager.create_background_layer(this.width, this.height);
    this.layer_manager.init_layers(this.width, this.height);
    for (const tool of this.tools.values()) {
      await tool.setup(this.context, this.current_tool, this.current_subtool);
    }
    const zoom = this.tools.get("zoom");
    if (zoom) {
      zoom.min_zoom.subscribe((is_min_zoom) => {
        this.min_zoom.set(is_min_zoom);
      });
    }
    this.target_element.appendChild(canvas);
    this.target_element.style.background = "transparent";
    this.dimensions.subscribe((dimensions) => {
      this.dimensions_value = dimensions;
      this.image_container.width = dimensions.width;
      this.image_container.height = dimensions.height;
    });
    this.scale.subscribe((scale) => {
      this.scale_value = scale;
    });
    this.position.subscribe((position) => {
      this.position_value = position;
    });
    this.app.ticker.add(() => {
      this.image_container.scale.set(this.scale_value);
      this.image_container.position.set(
        this.position_value.x,
        this.position_value.y
      );
      const effective_width = this.dimensions_value.width * this.scale_value;
      const effective_height = this.dimensions_value.height * this.scale_value;
      const local_x = Math.round(
        this.image_container.position.x - this.outline_container.position.x
      );
      const local_y = Math.round(
        this.image_container.position.y - this.outline_container.position.y
      );
      this.overlay_container.position.set(
        this.outline_container.position.x,
        this.outline_container.position.y
      );
      this.outline_graphics.clear();
      this.outline_graphics.rect(local_x, local_y, effective_width, effective_height).fill({
        color: this.dark ? 3355443 : 16777215,
        alpha: 1
      });
      if (this.border_region > 0) {
        const scaled_border = this.border_region * this.scale_value;
        const border_x = local_x + scaled_border - 1;
        const border_y = local_y + scaled_border - 1;
        const border_width = effective_width - scaled_border * 2 + 1;
        const border_height = effective_height - scaled_border * 2 + 1;
        this.overlay_graphics.clear();
        this.overlay_graphics.rect(border_x, border_y, border_width, border_height).stroke({
          color: 10066329,
          width: 1,
          alpha: 0,
          pixelLine: true
        });
        const dashLength = 5;
        const gapLength = 5;
        const totalLength = dashLength + gapLength;
        const lineColor = 10066329;
        for (let x = border_x; x < border_x + border_width; x += totalLength) {
          this.overlay_graphics.rect(
            x,
            border_y,
            Math.min(dashLength, border_x + border_width - x),
            1
          ).fill({ color: lineColor, alpha: 0.7 });
          this.overlay_graphics.rect(
            x,
            border_y + border_height,
            Math.min(dashLength, border_x + border_width - x),
            1
          ).fill({ color: lineColor, alpha: 0.7 });
        }
        for (let y = border_y; y < border_y + border_height; y += totalLength) {
          this.overlay_graphics.rect(
            border_x,
            y,
            1,
            Math.min(dashLength, border_y + border_height - y)
          ).fill({ color: lineColor, alpha: 0.7 });
          this.overlay_graphics.rect(
            border_x + border_width,
            y,
            1,
            Math.min(dashLength, border_y + border_height - y)
          ).fill({ color: lineColor, alpha: 0.7 });
        }
      } else {
        this.overlay_graphics.clear();
      }
    });
    this.ready_resolve();
  }
  setup_containers() {
    this.image_container = new Container({
      eventMode: "static",
      sortableChildren: true
    });
    this.image_container.width = this.width;
    this.image_container.height = this.height;
    this.app.stage.sortableChildren = true;
    this.app.stage.alpha = 1;
    this.app.stage.addChild(this.image_container);
    this.image_container.scale.set(1);
    this.ui_container = new Container({
      eventMode: "static"
    });
    this.app.stage.addChild(this.ui_container);
    this.ui_container.width = this.width;
    this.ui_container.height = this.height;
    this.outline_container = new Container();
    this.outline_container.zIndex = -10;
    this.outline_graphics = new Graphics();
    this.outline_graphics.rect(0, 0, this.width, this.height).fill({
      color: this.dark ? 3355443 : 16777215,
      alpha: 0
    });
    if (!this.dark) {
      const blurFilter = new DropShadowFilter({
        alpha: 0.1,
        blur: 2,
        color: 0,
        offset: { x: 0, y: 0 },
        quality: 4,
        shadowOnly: false
      });
      this.outline_graphics.filters = [blurFilter];
    }
    this.outline_container.addChild(this.outline_graphics);
    this.app.stage.addChild(this.outline_container);
    this.overlay_container = new Container();
    this.app.stage.addChild(this.overlay_container);
    this.overlay_container.width = this.width;
    this.overlay_container.height = this.height;
    this.overlay_container.zIndex = 999;
    this.overlay_container.eventMode = "static";
    this.overlay_container.interactiveChildren = true;
    this.overlay_graphics = new Graphics();
    this.overlay_container.addChild(this.overlay_graphics);
    this.overlay_graphics.rect(0, 0, this.width, this.height).fill({
      color: 0,
      alpha: 0
    });
    const app_center_x = this.app.screen.width / 2;
    const app_center_y = this.app.screen.height / 2;
    this.image_container.position.set(app_center_x, app_center_y);
    this.outline_container.position.set(app_center_x, app_center_y);
    this.layer_manager = new LayerManager(
      this.image_container,
      this.app,
      this.fixed_canvas,
      this.dark,
      this.border_region,
      this.layer_options
    );
    this.layers = this.layer_manager.layer_store;
  }
  resize_canvas(width, height) {
    if (this.app.renderer) {
      this.app.renderer.resize(width, height);
    }
    const app_center_x = width / 2;
    const app_center_y = height / 2;
    if (this.image_container) {
      this.image_container.position.set(app_center_x, app_center_y);
    }
    if (this.outline_container) {
      this.outline_container.position.set(app_center_x, app_center_y);
    }
  }
  reset() {
    const zoom = this.tools.get("zoom");
    if (zoom) {
      zoom.cleanup();
      zoom.setup(this.context, this.current_tool, this.current_subtool);
    }
    const brush = this.tools.get("brush");
    if (brush) {
      this.set_tool("draw");
    }
  }
  async set_image_properties(properties) {
    let hard = typeof properties.animate !== "undefined" ? !properties.animate : true;
    if (properties.position) {
      const pos = this.position.set(properties.position, { hard });
      if (hard) {
        await pos;
      }
    }
    if (properties.scale) {
      const scale = this.scale.set(properties.scale, { hard });
      if (hard) {
        await scale;
      }
    }
    if (properties.width && properties.height) {
      this.width = properties.width;
      this.height = properties.height;
      const dimensions = this.dimensions.set(
        { width: properties.width, height: properties.height },
        { hard }
      );
      if (hard) {
        await dimensions;
      }
    }
  }
  async execute_command(command) {
    await this.command_manager.execute(command, this.context);
  }
  undo() {
    this.command_manager.undo();
    this.notify("change");
  }
  redo() {
    this.command_manager.redo(this.context);
    this.notify("change");
  }
  async add_image({
    image,
    resize = true
  }) {
    const image_tool = this.tools.get("image");
    await image_tool.add_image({
      image,
      fixed_canvas: this.fixed_canvas,
      border_region: this.border_region
    });
  }
  /**
   * Adds an image from a URL as the background layer
   * @param url The URL of the image to add
   */
  async add_image_from_url(url) {
    const image_tool = this.tools.get("image");
    const texture = await Assets.load(url);
    await image_tool.add_image({
      image: texture,
      fixed_canvas: this.fixed_canvas,
      border_region: this.border_region
    });
    const resize_tool = this.tools.get("resize");
    if (resize_tool && typeof resize_tool.set_border_region === "function") {
      resize_tool.set_border_region(this.border_region);
    }
  }
  set_tool(tool) {
    this.current_tool = tool;
    for (const tool2 of this.tools.values()) {
      tool2.set_tool(this.current_tool, this.current_subtool);
    }
  }
  set_subtool(subtool) {
    this.current_subtool = subtool;
    for (const tool of this.tools.values()) {
      tool.set_tool(this.current_tool, this.current_subtool);
    }
  }
  set_background_image(image) {
    this.background_image = image;
  }
  async reset_canvas() {
    this.width = this.original_width;
    this.height = this.original_height;
    this.layer_manager.reset_layers(this.width, this.height);
    this.background_image = void 0;
    this.background_image_present.set(false);
    this.command_manager.reset();
    await this.set_image_properties({
      width: this.width,
      height: this.height,
      scale: 1,
      position: { x: 0, y: 0 },
      animate: false
    });
    this.layer_manager.create_background_layer(this.width, this.height);
    for (const tool of this.tools.values()) {
      tool.cleanup();
      tool.setup(this.context, this.current_tool, this.current_subtool);
    }
    const zoom_tool = this.tools.get("zoom");
    if (zoom_tool) {
      zoom_tool.min_zoom.subscribe((is_min_zoom) => {
        this.min_zoom.set(is_min_zoom);
      });
    }
    this.notify("change");
  }
  add_layer() {
    const add_layer_command = new AddLayerCommand(this.context, {
      width: this.width,
      height: this.height,
      user_created: true,
      make_active: true
    });
    this.execute_command(add_layer_command);
    this.notify("change");
  }
  /**
   * Adds a new layer with an image loaded from a URL
   * @param layer_urls The URLs of the images to load
   * @returns A Promise that resolves when all layers are added
   */
  async add_layers_from_url(layer_urls) {
    this.command_manager.reset();
    const _layers = this.layer_manager.get_layers();
    _layers.forEach((l) => this.layer_manager.delete_layer(l.id));
    if (layer_urls === void 0 || layer_urls.length === 0) {
      this.layer_manager.create_layer({
        width: this.width,
        height: this.height,
        layer_name: void 0,
        user_created: false
      });
      return;
    }
    const created_layer_ids = [];
    for await (const url of layer_urls) {
      const layer_id = await this.layer_manager.add_layer_from_url(url);
      if (layer_id) {
        created_layer_ids.push(layer_id);
      }
    }
    if (created_layer_ids.length > 0) {
      this.layer_manager.set_active_layer(created_layer_ids[0]);
    }
    const resize_tool = this.tools.get("resize");
    if (resize_tool && typeof resize_tool.set_border_region === "function") {
      resize_tool.set_border_region(this.border_region);
    }
    this.notify("change");
    this.notify("input");
  }
  set_layer(id) {
    this.layer_manager.set_active_layer(id);
    this.notify("change");
  }
  move_layer(id, direction) {
    const reorder_layer_command = new ReorderLayerCommand(
      this.context,
      id,
      direction
    );
    this.execute_command(reorder_layer_command);
    this.notify("change");
  }
  delete_layer(id) {
    const remove_layer_command = new RemoveLayerCommand(this.context, id);
    this.execute_command(remove_layer_command);
    this.notify("change");
  }
  modify_canvas_size(width, height, anchor, scale) {
    const oldWidth = this.width;
    const oldHeight = this.height;
    this.layer_manager.resize_all_layers(
      width,
      height,
      scale,
      anchor,
      oldWidth,
      oldHeight
    );
    this.width = width;
    this.height = height;
    this.set_image_properties({
      width,
      height,
      scale: 1,
      position: { x: 0, y: 0 },
      animate: false
    });
    this.notify("change");
  }
  async get_blobs() {
    const blobs = await this.layer_manager.get_blobs(this.width, this.height);
    return blobs;
  }
  on(event, callback) {
    this.event_callbacks.set(event, [
      ...this.event_callbacks.get(event) || [],
      callback
    ]);
  }
  off(event, callback) {
    this.event_callbacks.set(
      event,
      this.event_callbacks.get(event)?.filter((cb) => cb !== callback) || []
    );
  }
  notify(event) {
    for (const callback of this.event_callbacks.get(event) || []) {
      callback();
    }
  }
  destroy() {
    if (!this.app) return;
    this.app?.destroy();
  }
  resize(width, height) {
    this.set_image_properties({
      width,
      height
    });
    this.reset();
  }
  async get_crop_bounds() {
    const crop_tool = this.tools.get("crop");
    const crop_bounds = crop_tool.get_crop_bounds();
    const image = await crop_tool.get_image();
    return {
      image,
      ...crop_bounds
    };
  }
  get background_image_sprite() {
    return this.background_image;
  }
  set_layer_options(layer_options) {
    this.layer_options = layer_options;
    this.layer_manager.set_layer_options(
      layer_options,
      this.width,
      this.height
    );
  }
  toggle_layer_visibility(id) {
    this.layer_manager.toggle_layer_visibility(id);
  }
}

class CropTool {
  name = "crop";
  image_editor_context;
  current_tool = "image";
  current_subtool = "crop";
  // Constants
  CORNER_SIZE = 25;
  LINE_THICKNESS = 5;
  HANDLE_COLOR = 0;
  HIT_AREA_SIZE = 40;
  // State
  is_dragging = false;
  selected_handle = null;
  active_corner_index = -1;
  active_edge_index = -1;
  last_pointer_position = null;
  crop_bounds = { x: 0, y: 0, width: 0, height: 0 };
  crop_ui_container = null;
  crop_mask = null;
  dimensions = {
    width: 0,
    height: 0
  };
  position = { x: 0, y: 0 };
  scale = 1;
  is_dragging_window = false;
  drag_start_position = null;
  drag_start_bounds = null;
  background_image_watcher = null;
  has_been_manually_changed = false;
  event_callbacks = /* @__PURE__ */ new Map();
  /**
   * @method setup
   * @async
   * @description Sets up the crop tool with the given context and state
   * @param {ImageEditorContext} context - The image editor context
   * @param {ToolbarTool} tool - The current tool
   * @param {Subtool} subtool - The current subtool
   * @returns {Promise<void>}
   */
  async setup(context, tool, subtool) {
    this.image_editor_context = context;
    this.current_tool = tool;
    this.current_subtool = subtool;
    context.dimensions.subscribe((dimensions) => {
      this.dimensions = dimensions;
      this.crop_bounds = {
        x: 0,
        y: 0,
        width: dimensions.width,
        height: dimensions.height
      };
      if (this.crop_ui_container) {
        this.update_crop_ui();
      }
      this.set_crop_mask();
      this.update_crop_mask();
    });
    context.position.subscribe((position) => {
      this.position = position;
    });
    context.scale.subscribe((scale) => {
      this.scale = scale;
    });
    this.background_image_watcher = () => {
      if (this.crop_mask && this.current_tool === "image" && this.current_subtool === "crop") {
        if (this.image_editor_context.background_image) {
          this.image_editor_context.background_image.setMask(this.crop_mask);
        }
      }
    };
    this.image_editor_context.app.ticker.add(this.background_image_watcher);
    await this.init_crop_ui();
    this.setup_event_listeners();
    const isCropActive = tool === "image" && subtool === "crop";
    if (this.crop_ui_container) {
      this.crop_ui_container.visible = isCropActive;
    }
  }
  /**
   * @method cleanup
   * @description Cleans up the crop tool resources and removes event listeners
   * @returns {void}
   */
  cleanup() {
    if (this.crop_ui_container) {
      this.image_editor_context.app.stage.removeChild(this.crop_ui_container);
    }
    if (this.crop_mask) {
      if (this.crop_mask.parent) {
        this.crop_mask.parent.removeChild(this.crop_mask);
      }
      if (this.image_editor_context.background_image && this.image_editor_context.background_image.mask === this.crop_mask) {
        this.image_editor_context.background_image.mask = null;
      }
      if (this.image_editor_context.image_container.mask === this.crop_mask) {
        this.image_editor_context.image_container.mask = null;
      }
    }
    this.crop_mask = null;
    this.crop_ui_container = null;
    if (this.background_image_watcher) {
      this.image_editor_context.app.ticker.remove(
        this.background_image_watcher
      );
      this.background_image_watcher = null;
    }
    this.cleanup_event_listeners();
  }
  /**
   * @method set_tool
   * @description Sets the current tool and subtool, updating UI visibility
   * @param {ToolbarTool} tool - The tool to set
   * @param {Subtool} subtool - The subtool to set
   * @returns {void}
   */
  set_tool(tool, subtool) {
    this.current_tool = tool;
    this.current_subtool = subtool;
    const isCropActive = tool === "image" && subtool === "crop";
    if (this.crop_ui_container) {
      this.crop_ui_container.visible = isCropActive;
    }
    if (isCropActive && this.crop_mask) {
      this.update_crop_mask();
    }
  }
  /**
   * @method set_crop_mask
   * @private
   * @description Sets up the crop mask for the image
   * @returns {void}
   */
  set_crop_mask() {
    if (this.crop_mask) {
      if (this.crop_mask.parent) {
        this.crop_mask.parent.removeChild(this.crop_mask);
      }
      if (this.image_editor_context.image_container.mask === this.crop_mask) {
        this.image_editor_context.image_container.mask = null;
      }
      if (this.image_editor_context.background_image && this.image_editor_context.background_image.mask === this.crop_mask) {
        this.image_editor_context.background_image.mask = null;
      }
    }
    this.crop_mask = new Graphics();
    this.image_editor_context.image_container.addChild(this.crop_mask);
  }
  /**
   * @method init_crop_ui
   * @private
   * @async
   * @description Initializes the crop UI elements and sets up the update loop
   * @returns {Promise<void>}
   */
  async init_crop_ui() {
    const { width, height } = this.image_editor_context.background_image?.getLocalBounds() || {
      width: false,
      height: false
    };
    this.crop_ui_container = this.make_crop_ui(
      this.dimensions.width * this.scale,
      this.dimensions.height * this.scale
    );
    this.crop_ui_container.position.set(this.position.x, this.position.y);
    this.crop_ui_container.visible = false;
    this.image_editor_context.app.stage.addChild(this.crop_ui_container);
    this.set_crop_mask();
    this.image_editor_context.app.ticker.add(this.update_crop_ui.bind(this));
    this.update_crop_mask();
  }
  /**
   * @method make_crop_ui
   * @private
   * @description Creates the crop UI with the specified dimensions
   * @param {number} width - Width of the crop area
   * @param {number} height - Height of the crop area
   * @returns {Container} The created crop UI container
   */
  make_crop_ui(width, height) {
    const crop_container = new Container();
    crop_container.eventMode = "static";
    crop_container.interactiveChildren = true;
    const crop_outline = new Graphics().rect(0, 0, width, height).stroke({ width: 1, color: 0, alignment: 0, alpha: 0.5 });
    crop_outline.eventMode = "static";
    crop_outline.cursor = "move";
    crop_outline.hitArea = new Rectangle(0, 0, width, height);
    crop_outline.on("pointerdown", this.handle_window_drag_start.bind(this));
    crop_container.addChild(crop_outline);
    this.create_corner_handles(crop_container, width, height);
    this.create_edge_handles(crop_container, width, height);
    return crop_container;
  }
  /**
   * @method create_handle
   * @private
   * @description Creates a handle for the crop UI
   * @param {boolean} [is_edge=false] - Whether the handle is an edge handle
   * @returns {Container} The created handle container
   */
  create_handle(is_edge = false) {
    const handle = new Container();
    handle.eventMode = "static";
    const handle_graphics = new Graphics();
    if (is_edge) {
      handle_graphics.rect(0, 0, this.CORNER_SIZE * 1.5, this.LINE_THICKNESS).fill(this.HANDLE_COLOR);
    } else {
      handle_graphics.rect(0, 0, this.CORNER_SIZE, this.LINE_THICKNESS).rect(0, 0, this.LINE_THICKNESS, this.CORNER_SIZE).fill(this.HANDLE_COLOR);
    }
    handle.addChild(handle_graphics);
    const hit_size = is_edge ? this.HIT_AREA_SIZE * 1.5 : this.HIT_AREA_SIZE;
    handle.hitArea = new Rectangle(
      -hit_size / 2 + this.LINE_THICKNESS,
      -hit_size / 2 + this.LINE_THICKNESS,
      hit_size,
      hit_size
    );
    return handle;
  }
  /**
   * @method create_edge_handles
   * @private
   * @description Creates edge handles for the crop UI
   * @param {Container} container - The container to add handles to
   * @param {number} width - Width of the crop area
   * @param {number} height - Height of the crop area
   * @returns {void}
   */
  create_edge_handles(container, width, height) {
    [-1, 1].forEach((i, index) => {
      const handle = this.create_handle(true);
      handle.rotation = 0;
      handle.on("pointerdown", (event) => {
        this.handle_pointer_down(event, handle, -1, index);
      });
      handle.x = width / 2 - this.CORNER_SIZE * 3 / 2;
      handle.y = i < 0 ? -this.LINE_THICKNESS : height;
      handle.cursor = "ns-resize";
      container.addChild(handle);
    });
    [-1, 1].forEach((i, index) => {
      const handle = this.create_handle(true);
      handle.rotation = Math.PI / 2;
      handle.on("pointerdown", (event) => {
        this.handle_pointer_down(event, handle, -1, index + 2);
      });
      handle.x = i < 0 ? -this.LINE_THICKNESS : width;
      handle.y = height / 2 - this.CORNER_SIZE * 3 / 2;
      handle.cursor = "ew-resize";
      container.addChild(handle);
    });
  }
  /**
   * @method create_corner_handles
   * @private
   * @description Creates corner handles for the crop UI
   * @param {Container} container - The container to add handles to
   * @param {number} width - Width of the crop area
   * @param {number} height - Height of the crop area
   * @returns {void}
   */
  create_corner_handles(container, width, height) {
    const corners = [
      { x: 0, y: 0, xScale: 1, yScale: 1, cursor: "nwse-resize" },
      // Top-left
      { x: width, y: 0, xScale: -1, yScale: 1, cursor: "nesw-resize" },
      // Top-right
      { x: 0, y: height, xScale: 1, yScale: -1, cursor: "nesw-resize" },
      // Bottom-left
      { x: width, y: height, xScale: -1, yScale: -1, cursor: "nwse-resize" }
      // Bottom-right
    ];
    corners.forEach(({ x, y, xScale, yScale, cursor }, i) => {
      const corner = this.create_handle(false);
      corner.x = x - (xScale < 0 ? -this.LINE_THICKNESS : this.LINE_THICKNESS);
      corner.y = y - (yScale < 0 ? -this.LINE_THICKNESS : this.LINE_THICKNESS);
      corner.scale.set(xScale, yScale);
      corner.on("pointerdown", (event) => {
        this.handle_pointer_down(event, corner, i, -1);
      });
      corner.cursor = cursor;
      container.addChild(corner);
    });
  }
  /**
   * @method handle_pointer_down
   * @private
   * @description Handles pointer down events on handles
   * @param {FederatedPointerEvent} event - The pointer event
   * @param {Container} handle - The handle that was clicked
   * @param {number} corner_index - Index of the corner (-1 if not a corner)
   * @param {number} edge_index - Index of the edge (-1 if not an edge)
   * @returns {void}
   */
  handle_pointer_down(event, handle, corner_index, edge_index) {
    if (this.current_subtool !== "crop") return;
    event.stopPropagation();
    this.is_dragging = true;
    this.selected_handle = handle;
    this.active_corner_index = corner_index;
    this.active_edge_index = edge_index;
    const local_pos = this.image_editor_context.image_container.toLocal(
      event.global
    );
    this.last_pointer_position = new Point(local_pos.x, local_pos.y);
  }
  /**
   * updates the crop bounds based on pointer movement
   * @param delta - the change in position
   */
  update_crop_bounds(delta) {
    this.has_been_manually_changed = true;
    const scaled_delta = new Point(delta.x, delta.y);
    const original = {
      x: this.crop_bounds.x,
      y: this.crop_bounds.y,
      width: this.crop_bounds.width,
      height: this.crop_bounds.height
    };
    if (this.active_corner_index !== -1) {
      switch (this.active_corner_index) {
        case 0:
          const newWidth0 = Math.max(20, original.width - scaled_delta.x);
          const widthDiff0 = original.width - newWidth0;
          this.crop_bounds.width = newWidth0;
          this.crop_bounds.x = original.x + widthDiff0;
          const newHeight0 = Math.max(20, original.height - scaled_delta.y);
          const heightDiff0 = original.height - newHeight0;
          this.crop_bounds.height = newHeight0;
          this.crop_bounds.y = original.y + heightDiff0;
          break;
        case 1:
          this.crop_bounds.width = Math.max(
            20,
            original.width + scaled_delta.x
          );
          const newHeight1 = Math.max(20, original.height - scaled_delta.y);
          const heightDiff1 = original.height - newHeight1;
          this.crop_bounds.height = newHeight1;
          this.crop_bounds.y = original.y + heightDiff1;
          break;
        case 2:
          const newWidth2 = Math.max(20, original.width - scaled_delta.x);
          const widthDiff2 = original.width - newWidth2;
          this.crop_bounds.width = newWidth2;
          this.crop_bounds.x = original.x + widthDiff2;
          this.crop_bounds.height = Math.max(
            20,
            original.height + scaled_delta.y
          );
          break;
        case 3:
          this.crop_bounds.width = Math.max(
            20,
            original.width + scaled_delta.x
          );
          this.crop_bounds.height = Math.max(
            20,
            original.height + scaled_delta.y
          );
          break;
      }
    } else if (this.active_edge_index !== -1) {
      switch (this.active_edge_index) {
        case 0:
          const newHeight = Math.max(20, original.height - scaled_delta.y);
          const heightDiff = original.height - newHeight;
          this.crop_bounds.height = newHeight;
          this.crop_bounds.y = original.y + heightDiff;
          break;
        case 1:
          this.crop_bounds.height = Math.max(
            20,
            original.height + scaled_delta.y
          );
          break;
        case 2:
          const newWidth = Math.max(20, original.width - scaled_delta.x);
          const widthDiff = original.width - newWidth;
          this.crop_bounds.width = newWidth;
          this.crop_bounds.x = original.x + widthDiff;
          break;
        case 3:
          this.crop_bounds.width = Math.max(
            20,
            original.width + scaled_delta.x
          );
          break;
      }
    }
    this.constrain_crop_bounds();
    this.update_crop_ui();
    this.update_crop_mask();
    if (this.crop_mask && this.image_editor_context.background_image) {
      this.image_editor_context.background_image.setMask(this.crop_mask);
    }
  }
  /**
   * constrains the crop bounds to the image dimensions
   */
  constrain_crop_bounds() {
    ({
      x: this.crop_bounds.x,
      y: this.crop_bounds.y,
      width: this.crop_bounds.width,
      height: this.crop_bounds.height
    });
    this.crop_bounds.width = Math.max(
      20,
      Math.min(this.crop_bounds.width, this.dimensions.width)
    );
    this.crop_bounds.height = Math.max(
      20,
      Math.min(this.crop_bounds.height, this.dimensions.height)
    );
    const oldX = this.crop_bounds.x;
    this.crop_bounds.x = Math.max(
      0,
      Math.min(
        this.crop_bounds.x,
        this.dimensions.width - this.crop_bounds.width
      )
    );
    if (this.crop_bounds.x !== oldX && this.active_corner_index === 0 || this.active_edge_index === 2) {
      const xDiff = this.crop_bounds.x - oldX;
      this.crop_bounds.width -= xDiff;
    }
    const oldY = this.crop_bounds.y;
    this.crop_bounds.y = Math.max(
      0,
      Math.min(
        this.crop_bounds.y,
        this.dimensions.height - this.crop_bounds.height
      )
    );
    if (this.crop_bounds.y !== oldY && (this.active_corner_index === 0 || this.active_corner_index === 1 || this.active_edge_index === 0)) {
      const yDiff = this.crop_bounds.y - oldY;
      this.crop_bounds.height -= yDiff;
    }
    this.crop_bounds.width = Math.max(
      20,
      Math.min(
        this.crop_bounds.width,
        this.dimensions.width - this.crop_bounds.x
      )
    );
    this.crop_bounds.height = Math.max(
      20,
      Math.min(
        this.crop_bounds.height,
        this.dimensions.height - this.crop_bounds.y
      )
    );
  }
  /**
   * updates the crop mask graphics
   */
  update_crop_mask() {
    if (!this.crop_mask) return;
    this.crop_mask.clear();
    const { width, height } = this.image_editor_context.background_image?.getLocalBounds() ?? {
      width: 0,
      height: 0
    };
    this.crop_mask.rect(0, 0, width, height).fill({ color: 0, alpha: 0.4 }).rect(
      this.crop_bounds.x,
      this.crop_bounds.y,
      this.crop_bounds.width,
      this.crop_bounds.height
    ).cut();
    if (this.image_editor_context.background_image) {
      this.image_editor_context.background_image.mask = null;
    }
    if (this.image_editor_context.image_container.mask === this.crop_mask) {
      this.image_editor_context.image_container.mask = null;
    }
  }
  /**
   * updates the crop ui position and dimensions
   */
  update_crop_ui() {
    if (!this.crop_mask || !this.crop_ui_container) return;
    this.crop_ui_container.position.set(
      this.position.x + this.crop_bounds.x * this.scale,
      this.position.y + this.crop_bounds.y * this.scale
    );
    const scaled_width = this.crop_bounds.width * this.scale;
    const scaled_height = this.crop_bounds.height * this.scale;
    const outline = this.crop_ui_container.getChildAt(0);
    outline.clear().rect(0, 0, scaled_width, scaled_height).stroke({ width: 1, color: 0, alignment: 0, alpha: 0.5 });
    outline.hitArea = new Rectangle(0, 0, scaled_width, scaled_height);
    this.update_handle_positions(scaled_width, scaled_height);
  }
  /**
   * updates the positions of all handles
   * @param width - width of the crop area
   * @param height - height of the crop area
   */
  update_handle_positions(width, height) {
    if (!this.crop_ui_container) return;
    const handles = this.crop_ui_container.children.slice(1);
    const corners = handles.slice(0, 4);
    const cornerPositions = [
      { x: 0, y: 0 },
      // Top-left
      { x: width, y: 0 },
      // Top-right
      { x: 0, y: height },
      // Bottom-left
      { x: width, y: height }
      // Bottom-right
    ];
    corners.forEach((handle, i) => {
      const xScale = i % 2 === 0 ? 1 : -1;
      const yScale = i < 2 ? 1 : -1;
      handle.position.set(
        cornerPositions[i].x - (xScale < 0 ? -this.LINE_THICKNESS : this.LINE_THICKNESS),
        cornerPositions[i].y - (yScale < 0 ? -this.LINE_THICKNESS : this.LINE_THICKNESS)
      );
    });
    const edges = handles.slice(4);
    edges.forEach((handle, i) => {
      if (i < 2) {
        handle.position.set(
          width / 2 - this.CORNER_SIZE * 1.5 / 2,
          // Changed from 3 to 2
          i === 0 ? -this.LINE_THICKNESS : height
        );
      } else {
        handle.position.set(
          i === 2 ? 0 : width + this.LINE_THICKNESS,
          height / 2 - this.CORNER_SIZE * 1.5 / 2
          // Changed from 3 to 2
        );
      }
    });
  }
  /**
   * sets up event listeners for the crop tool
   */
  setup_event_listeners() {
    const stage = this.image_editor_context.app.stage;
    stage.eventMode = "static";
    stage.on("pointermove", this.handle_pointer_move.bind(this));
    stage.on("pointerup", this.handle_pointer_up.bind(this));
    stage.on("pointerupoutside", this.handle_pointer_up.bind(this));
  }
  /**
   * removes event listeners for the crop tool
   */
  cleanup_event_listeners() {
    const stage = this.image_editor_context.app.stage;
    stage.off("pointermove", this.handle_pointer_move.bind(this));
    stage.off("pointerup", this.handle_pointer_up.bind(this));
    stage.off("pointerupoutside", this.handle_pointer_up.bind(this));
  }
  /**
   * handles pointer move events
   * @param event - the pointer event
   */
  handle_pointer_move(event) {
    if (this.current_subtool !== "crop") return;
    if (this.is_dragging_window && this.drag_start_position && this.drag_start_bounds) {
      const local_pos = this.image_editor_context.image_container.toLocal(
        event.global
      );
      const delta = new Point(
        local_pos.x - this.drag_start_position.x,
        local_pos.y - this.drag_start_position.y
      );
      this.crop_bounds = {
        x: this.drag_start_bounds.x + delta.x,
        y: this.drag_start_bounds.y + delta.y,
        width: this.drag_start_bounds.width,
        height: this.drag_start_bounds.height
      };
      this.constrain_crop_bounds();
      this.update_crop_mask();
      if (this.crop_mask && this.image_editor_context.background_image) {
        this.image_editor_context.background_image.setMask(this.crop_mask);
      }
      return;
    }
    if (this.is_dragging && this.selected_handle && this.last_pointer_position) {
      const local_pos = this.image_editor_context.image_container.toLocal(
        event.global
      );
      const current_position = new Point(local_pos.x, local_pos.y);
      const delta = new Point(
        current_position.x - this.last_pointer_position.x,
        current_position.y - this.last_pointer_position.y
      );
      this.update_crop_bounds(delta);
      this.last_pointer_position = current_position;
      this.update_crop_mask();
    }
  }
  /**
   * handles pointer up events
   */
  handle_pointer_up() {
    if (this.current_subtool !== "crop") return;
    this.is_dragging = false;
    this.is_dragging_window = false;
    this.selected_handle = null;
    this.last_pointer_position = null;
    this.drag_start_position = null;
    this.drag_start_bounds = null;
    this.active_corner_index = -1;
    this.active_edge_index = -1;
    this.notify("change");
  }
  /**
   * handles the start of window dragging
   * @param event - the pointer event
   */
  handle_window_drag_start(event) {
    if (this.current_subtool !== "crop") return;
    event.stopPropagation();
    this.is_dragging_window = true;
    this.has_been_manually_changed = true;
    const local_pos = this.image_editor_context.image_container.toLocal(
      event.global
    );
    this.drag_start_position = new Point(local_pos.x, local_pos.y);
    this.drag_start_bounds = { ...this.crop_bounds };
  }
  /**
   * @returns whether the crop has been manually changed by the user
   */
  get crop_manually_changed() {
    return this.has_been_manually_changed;
  }
  on(event, callback) {
    this.event_callbacks.set(event, [
      ...this.event_callbacks.get(event) || [],
      callback
    ]);
  }
  off(event, callback) {
    this.event_callbacks.set(
      event,
      this.event_callbacks.get(event)?.filter((cb) => cb !== callback) || []
    );
  }
  notify(event) {
    for (const callback of this.event_callbacks.get(event) || []) {
      callback();
    }
  }
  get_crop_bounds() {
    const { width, height } = this.image_editor_context.background_image?.getLocalBounds() ?? {
      width: 0,
      height: 0
    };
    return {
      x: this.crop_bounds.x,
      y: this.crop_bounds.y,
      crop_dimensions: {
        width: this.crop_bounds.width,
        height: this.crop_bounds.height
      },
      image_dimensions: {
        width,
        height
      }
    };
  }
  get_image() {
    if (!this.image_editor_context.background_image)
      return Promise.resolve(null);
    const container = new Container();
    const sprite = new Sprite(
      this.image_editor_context.background_image.texture
    );
    container.addChild(sprite);
    return get_canvas_blob(
      this.image_editor_context.app.renderer,
      container,
      this.crop_bounds
    );
  }
}

class ResizeCommand {
  constructor(context, original_state, new_state, reset_ui) {
    this.context = context;
    this.name = "Resize";
    this.original_width = original_state.width;
    this.original_height = original_state.height;
    this.original_x = original_state.x;
    this.original_y = original_state.y;
    this.new_width = new_state.width;
    this.new_height = new_state.height;
    this.new_x = new_state.x;
    this.new_y = new_state.y;
    this.reset_ui = reset_ui;
  }
  original_width;
  original_height;
  original_x;
  original_y;
  new_width;
  new_height;
  new_x;
  new_y;
  reset_ui;
  name;
  async execute(context) {
    if (context) {
      this.context = context;
    }
    if (!this.context.background_image) return;
    this.context.background_image.width = this.new_width;
    this.context.background_image.height = this.new_height;
    this.context.background_image.position.set(this.new_x, this.new_y);
    this.reset_ui();
  }
  async undo() {
    if (!this.context.background_image) return;
    this.context.background_image.width = this.original_width;
    this.context.background_image.height = this.original_height;
    this.context.background_image.position.set(
      this.original_x,
      this.original_y
    );
    this.reset_ui();
  }
}
class ResizeTool {
  name = "resize";
  image_editor_context;
  current_tool = "image";
  current_subtool = "size";
  current_cursor = "unset";
  CORNER_SIZE = 25;
  LINE_THICKNESS = 5;
  HANDLE_COLOR = 0;
  HIT_AREA_SIZE = 40;
  borderRegion = 0;
  is_dragging = false;
  selected_handle = null;
  active_corner_index = -1;
  active_edge_index = -1;
  last_pointer_position = null;
  resize_ui_container = null;
  dimensions = {
    width: 0,
    height: 0
  };
  position = { x: 0, y: 0 };
  scale = 1;
  is_moving = false;
  dom_mousedown_handler = null;
  dom_mousemove_handler = null;
  dom_mouseup_handler = null;
  event_callbacks = /* @__PURE__ */ new Map();
  last_scale = 1;
  original_state = null;
  /**
   * @method setup
   * @async
   * @description Sets up the resize tool with the given context and tool/subtool
   * @param {ImageEditorContext} context - The image editor context
   * @param {ToolbarTool} tool - The current tool
   * @param {Subtool} subtool - The current subtool
   * @returns {Promise<void>}
   */
  async setup(context, tool, subtool) {
    this.image_editor_context = context;
    this.current_tool = tool;
    this.current_subtool = subtool;
    if (context.background_image) {
      if (context.background_image.borderRegion !== void 0) {
        this.borderRegion = context.background_image.borderRegion;
      } else {
        const bg = context.background_image;
        const container = context.image_container;
        const isHorizontallyCentered = Math.abs(bg.position.x - (container.width - bg.width) / 2) < 2;
        const isVerticallyCentered = Math.abs(bg.position.y - (container.height - bg.height) / 2) < 2;
        if (!isHorizontallyCentered || !isVerticallyCentered) {
          this.borderRegion = Math.max(bg.position.x, bg.position.y);
          context.background_image.borderRegion = this.borderRegion;
        }
      }
    }
    context.dimensions.subscribe((dimensions) => {
      this.dimensions = dimensions;
      if (this.resize_ui_container) {
        this.update_resize_ui();
      }
    });
    context.position.subscribe((position) => {
      this.position = position;
      if (this.resize_ui_container && this.current_tool === "image" && this.current_subtool === "size") {
        this.update_resize_ui();
      }
    });
    context.scale.subscribe((scale) => {
      this.scale = scale;
      if (this.resize_ui_container && this.current_tool === "image" && this.current_subtool === "size") {
        this.update_resize_ui();
      }
    });
    await this.init_resize_ui();
    this.setup_event_listeners();
    if (this.current_subtool === "size" && this.image_editor_context.background_image) {
      const bg = this.image_editor_context.background_image;
      const container = this.image_editor_context.image_container;
      const bg_width = bg.width;
      const bg_height = bg.height;
      const container_bounds = container.getLocalBounds();
      const container_width = container_bounds.width;
      const container_height = container_bounds.height;
      const min_x = -(bg_width - container_width);
      const min_y = -(bg_height - container_height);
      const max_x = 0;
      const max_y = 0;
      const x_out_of_bounds = bg_width > container_width && (bg.position.x < min_x || bg.position.x > max_x);
      const y_out_of_bounds = bg_height > container_height && (bg.position.y < min_y || bg.position.y > max_y);
      if (x_out_of_bounds || y_out_of_bounds) {
        this.apply_boundary_constraints();
      }
    }
  }
  /**
   * @method cleanup
   * @description Cleans up the resize tool resources and removes event listeners
   * @returns {void}
   */
  cleanup() {
    if (this.resize_ui_container) {
      this.image_editor_context.app.stage.removeChild(this.resize_ui_container);
    }
    this.cleanup_event_listeners();
  }
  /**
   * @method set_tool
   * @description Sets the current tool and subtool, updating UI visibility
   * @param {ToolbarTool} tool - The tool to set
   * @param {Subtool} subtool - The subtool to set
   * @returns {void}
   */
  set_tool(tool, subtool) {
    this.current_tool = tool;
    this.current_subtool = subtool;
    if (tool === "image" && subtool === "size") {
      if (this.image_editor_context.background_image) {
        const storedBorderRegion = this.image_editor_context.background_image.borderRegion;
        if (typeof storedBorderRegion === "number" && storedBorderRegion > 0) {
          this.borderRegion = storedBorderRegion;
        }
      }
      this.show_resize_ui();
      if (this.image_editor_context.background_image) {
        const bg = this.image_editor_context.background_image;
        const container = this.image_editor_context.image_container;
        const bg_width = bg.width;
        const bg_height = bg.height;
        const container_bounds = container.getLocalBounds();
        const container_width = container_bounds.width;
        const container_height = container_bounds.height;
        const effectiveWidth = container_width - this.borderRegion * 2;
        const effectiveHeight = container_height - this.borderRegion * 2;
        const min_x = bg_width > effectiveWidth ? -(bg_width - effectiveWidth) + this.borderRegion : this.borderRegion;
        const max_x = bg_width > effectiveWidth ? this.borderRegion : container_width - bg_width - this.borderRegion;
        const min_y = bg_height > effectiveHeight ? -(bg_height - effectiveHeight) + this.borderRegion : this.borderRegion;
        const max_y = bg_height > effectiveHeight ? this.borderRegion : container_height - bg_height - this.borderRegion;
        const x_out_of_bounds = bg.position.x < min_x || bg.position.x > max_x;
        const y_out_of_bounds = bg.position.y < min_y || bg.position.y > max_y;
        if (x_out_of_bounds || y_out_of_bounds) {
          this.apply_boundary_constraints();
        }
      }
    } else {
      this.hide_resize_ui();
    }
  }
  /**
   * @method init_resize_ui
   * @private
   * @async
   * @description Initializes the resize UI elements and sets up the update loop
   * @returns {Promise<void>}
   */
  async init_resize_ui() {
    this.resize_ui_container = this.make_resize_ui(
      this.dimensions.width * this.scale,
      this.dimensions.height * this.scale
    );
    this.resize_ui_container.position.set(this.position.x, this.position.y);
    this.resize_ui_container.visible = false;
    this.image_editor_context.app.stage.addChild(this.resize_ui_container);
  }
  /**
   * @method make_resize_ui
   * @private
   * @description Creates the resize UI with the specified dimensions
   * @param {number} width - Width of the resize area
   * @param {number} height - Height of the resize area
   * @returns {Container} The created resize UI container
   */
  make_resize_ui(width, height) {
    const resize_container = new Container();
    resize_container.eventMode = "static";
    resize_container.interactiveChildren = true;
    const outline = new Graphics().rect(0, 0, width, height).stroke({
      width: 1,
      color: 0,
      alignment: 0,
      alpha: 0.3
    });
    const dotted_outline = new Graphics();
    dotted_outline.rect(0, 0, width, height).stroke({
      width: 1,
      color: 0,
      alpha: 0.7,
      pixelLine: true
    });
    const dash_length = 5;
    const gap_length = 5;
    const total_length = dash_length + gap_length;
    for (let x = 0; x < width; x += total_length * 2) {
      dotted_outline.rect(x, 0, Math.min(dash_length, width - x), 1).fill(0);
      dotted_outline.rect(x, height - 1, Math.min(dash_length, width - x), 1).fill(0);
    }
    for (let y = 0; y < height; y += total_length * 2) {
      dotted_outline.rect(0, y, 1, Math.min(dash_length, height - y)).fill(0);
      dotted_outline.rect(width - 1, y, 1, Math.min(dash_length, height - y)).fill(0);
    }
    resize_container.addChild(outline);
    resize_container.addChild(dotted_outline);
    const move_area = new Graphics().rect(0, 0, width, height).fill(16777215, 0);
    move_area.eventMode = "static";
    move_area.cursor = "move";
    resize_container.addChild(move_area);
    this.create_corner_handles(resize_container, width, height);
    this.create_edge_handles(resize_container, width, height);
    return resize_container;
  }
  /**
   * @method create_handle
   * @private
   * @description Creates a handle for the resize UI
   * @param {boolean} [is_edge=false] - Whether the handle is an edge handle
   * @returns {Container} The created handle container
   */
  create_handle(is_edge = false) {
    const handle = new Container();
    handle.eventMode = "static";
    handle.cursor = "pointer";
    const handle_graphics = new Graphics();
    const handle_size = is_edge ? 8 : 10;
    handle_graphics.rect(-handle_size / 2, -handle_size / 2, handle_size, handle_size).fill(16777215).stroke({ width: 1, color: this.HANDLE_COLOR });
    handle.addChild(handle_graphics);
    const hit_size = is_edge ? this.HIT_AREA_SIZE * 1.5 : this.HIT_AREA_SIZE;
    handle.hitArea = new Rectangle(
      -hit_size / 2,
      -hit_size / 2,
      hit_size,
      hit_size
    );
    return handle;
  }
  /**
   * @method create_edge_handles
   * @private
   * @description Creates edge handles for the resize UI
   * @param {Container} container - The container to add handles to
   * @param {number} width - Width of the resize area
   * @param {number} height - Height of the resize area
   * @returns {void}
   */
  create_edge_handles(container, width, height) {
    [-1, 1].forEach((i, index) => {
      const handle = this.create_handle(true);
      handle.rotation = 0;
      handle.on("pointerdown", (event) => {
        this.handle_pointer_down(event, handle, -1, index);
      });
      handle.x = width / 2;
      handle.y = i < 0 ? 0 : height;
      handle.cursor = "ns-resize";
      container.addChild(handle);
    });
    [-1, 1].forEach((i, index) => {
      const handle = this.create_handle(true);
      handle.rotation = 0;
      handle.on("pointerdown", (event) => {
        this.handle_pointer_down(event, handle, -1, index + 2);
      });
      handle.x = i < 0 ? 0 : width;
      handle.y = height / 2;
      handle.cursor = "ew-resize";
      container.addChild(handle);
    });
  }
  /**
   * @method create_corner_handles
   * @private
   * @description Creates corner handles for the resize UI
   * @param {Container} container - The container to add handles to
   * @param {number} width - Width of the resize area
   * @param {number} height - Height of the resize area
   * @returns {void}
   */
  create_corner_handles(container, width, height) {
    const corners = [
      { x: 0, y: 0, cursor: "nwse-resize" },
      { x: width, y: 0, cursor: "nesw-resize" },
      { x: 0, y: height, cursor: "nesw-resize" },
      { x: width, y: height, cursor: "nwse-resize" }
    ];
    corners.forEach(({ x, y, cursor }, i) => {
      const corner = this.create_handle(false);
      corner.x = x;
      corner.y = y;
      corner.on("pointerdown", (event) => {
        this.handle_pointer_down(event, corner, i, -1);
      });
      corner.cursor = cursor;
      container.addChild(corner);
    });
  }
  /**
   * @method handle_pointer_down
   * @private
   * @description Handles pointer down events on handles
   * @param {FederatedPointerEvent} event - The pointer event
   * @param {Container} handle - The handle that was clicked
   * @param {number} corner_index - Index of the corner (-1 if not a corner)
   * @param {number} edge_index - Index of the edge (-1 if not an edge)
   * @returns {void}
   */
  handle_pointer_down(event, handle, corner_index, edge_index) {
    if (this.current_subtool !== "size") return;
    event.stopPropagation();
    this.is_dragging = true;
    this.selected_handle = handle;
    this.active_corner_index = corner_index;
    this.active_edge_index = edge_index;
    const local_pos = this.image_editor_context.image_container.toLocal(
      event.global
    );
    this.last_pointer_position = new Point(local_pos.x, local_pos.y);
    if (this.image_editor_context.background_image) {
      const bg = this.image_editor_context.background_image;
      this.original_state = {
        width: bg.width,
        height: bg.height,
        x: bg.position.x,
        y: bg.position.y
      };
    }
  }
  /**
   * Maintains aspect ratio during resizing
   * @param new_width - The new width
   * @param new_height - The new height
   * @param original_aspect_ratio - The original aspect ratio
   * @param delta - The delta to apply
   * @returns The adjusted dimensions
   */
  maintain_aspect_ratio(new_width, new_height, original_aspect_ratio, delta) {
    const abs_delta_x = Math.abs(delta.x);
    const abs_delta_y = Math.abs(delta.y);
    if (abs_delta_x > abs_delta_y * 1.2) {
      new_height = new_width / original_aspect_ratio;
    } else if (abs_delta_y > abs_delta_x * 1.2) {
      new_width = new_height * original_aspect_ratio;
    } else {
      if (new_width / original_aspect_ratio > new_height) {
        new_height = new_width / original_aspect_ratio;
      } else {
        new_width = new_height * original_aspect_ratio;
      }
    }
    return { width: new_width, height: new_height };
  }
  /**
   * Limits dimensions based on constraints
   * @param width - The width to limit
   * @param height - The height to limit
   * @param maintain_aspect_ratio - Whether to maintain aspect ratio
   * @returns The limited dimensions
   */
  limit_dimensions(width, height, maintain_aspect_ratio) {
    const max_width = this.dimensions.width;
    const max_height = this.dimensions.height;
    let new_width = width;
    let new_height = height;
    if (new_width > max_width) {
      if (maintain_aspect_ratio && this.active_corner_index !== -1) {
        new_height = max_width / new_width * new_height;
      }
      new_width = max_width;
    }
    if (new_height > max_height) {
      if (maintain_aspect_ratio && this.active_corner_index !== -1) {
        new_width = max_height / new_height * new_width;
      }
      new_height = max_height;
    }
    new_width = Math.max(20, new_width);
    new_height = Math.max(20, new_height);
    return { width: new_width, height: new_height };
  }
  /**
   * Calculates position deltas based on dimension changes
   * @param old_width - The original width
   * @param old_height - The original height
   * @param new_width - The new width
   * @param new_height - The new height
   * @returns Object containing x and y position deltas
   */
  calculate_position_deltas(old_width, old_height, new_width, new_height) {
    let delta_x = 0;
    let delta_y = 0;
    if (this.active_corner_index === 0 || this.active_edge_index === 2) {
      delta_x = old_width - new_width;
    } else if (this.active_corner_index === 2) {
      delta_x = old_width - new_width;
      delta_y = 0;
    }
    if (this.active_corner_index === 0 || this.active_corner_index === 1 || this.active_edge_index === 0) {
      delta_y = old_height - new_height;
    }
    return { x: delta_x, y: delta_y };
  }
  /**
   * Applies boundary constraints to keep the image within the container
   * @private
   */
  apply_boundary_constraints() {
    if (!this.image_editor_context.background_image) return;
    const bg = this.image_editor_context.background_image;
    const image_container = this.image_editor_context.image_container;
    let new_x = bg.position.x;
    let new_y = bg.position.y;
    const container_width = image_container.width;
    const container_height = image_container.height;
    const bg_width = bg.width;
    const bg_height = bg.height;
    const effectiveWidth = container_width - this.borderRegion * 2;
    const effectiveHeight = container_height - this.borderRegion * 2;
    if (bg_width > effectiveWidth) {
      const min_x = -(bg_width - effectiveWidth) + this.borderRegion;
      const max_x = this.borderRegion;
      new_x = Math.max(min_x, Math.min(max_x, new_x));
    } else {
      const min_x = this.borderRegion;
      const max_x = container_width - bg_width - this.borderRegion;
      if (new_x < min_x || new_x > max_x) {
        new_x = Math.max(min_x, Math.min(max_x, new_x));
      }
    }
    if (bg_height > effectiveHeight) {
      const min_y = -(bg_height - effectiveHeight) + this.borderRegion;
      const max_y = this.borderRegion;
      new_y = Math.max(min_y, Math.min(max_y, new_y));
    } else {
      const min_y = this.borderRegion;
      const max_y = container_height - bg_height - this.borderRegion;
      if (new_y < min_y || new_y > max_y) {
        new_y = Math.max(min_y, Math.min(max_y, new_y));
      }
    }
    bg.position.set(new_x, new_y);
    if (this.resize_ui_container) {
      const total_x = image_container.position.x + new_x * this.scale;
      const total_y = image_container.position.y + new_y * this.scale;
      this.resize_ui_container.position.set(total_x, total_y);
    }
  }
  /**
   * Updates the resize bounds based on the delta
   * @param delta - The delta to apply
   * @param maintain_aspect_ratio - Whether to maintain the aspect ratio
   */
  update_resize_bounds(delta, maintain_aspect_ratio = false) {
    const background_image = this.image_editor_context.background_image;
    if (!background_image) return;
    const original_width = background_image.width;
    const original_height = background_image.height;
    const original_x = background_image.position.x;
    const original_y = background_image.position.y;
    const original_aspect_ratio = original_width / original_height;
    let current_position;
    if (this.active_corner_index !== -1) {
      switch (this.active_corner_index) {
        case 0:
          current_position = new Point(
            original_x - delta.x,
            original_y - delta.y
          );
          break;
        case 1:
          current_position = new Point(
            original_x + original_width + delta.x,
            original_y - delta.y
          );
          break;
        case 2:
          current_position = new Point(
            original_x - delta.x,
            original_y + original_height + delta.y
          );
          break;
        case 3:
          current_position = new Point(
            original_x + original_width + delta.x,
            original_y + original_height + delta.y
          );
          break;
        default:
          return;
      }
      const dimensions = this.handle_direct_corner_resize(
        current_position,
        original_width,
        original_height,
        original_x,
        original_y,
        original_aspect_ratio,
        maintain_aspect_ratio
      );
      background_image.width = dimensions.width;
      background_image.height = dimensions.height;
      background_image.position.set(dimensions.x, dimensions.y);
    } else if (this.active_edge_index !== -1) {
      switch (this.active_edge_index) {
        case 0:
          current_position = new Point(
            original_x + original_width / 2,
            original_y - delta.y
          );
          break;
        case 1:
          current_position = new Point(
            original_x + original_width / 2,
            original_y + original_height + delta.y
          );
          break;
        case 2:
          current_position = new Point(
            original_x - delta.x,
            original_y + original_height / 2
          );
          break;
        case 3:
          current_position = new Point(
            original_x + original_width + delta.x,
            original_y + original_height / 2
          );
          break;
        default:
          return;
      }
      const dimensions = this.handle_direct_edge_resize(
        current_position,
        original_width,
        original_height,
        original_x,
        original_y
      );
      background_image.width = dimensions.width;
      background_image.height = dimensions.height;
      background_image.position.set(dimensions.x, dimensions.y);
    }
    this.update_resize_ui();
  }
  /**
   * updates the resize ui position and dimensions
   */
  update_resize_ui() {
    if (!this.resize_ui_container || !this.image_editor_context.background_image || !this.image_editor_context.background_image.position)
      return;
    const background_image = this.image_editor_context.background_image;
    const image_container = this.image_editor_context.image_container;
    const container_global_pos = image_container.getGlobalPosition();
    const bg_pos_x = background_image.position.x * this.scale;
    const bg_pos_y = background_image.position.y * this.scale;
    this.resize_ui_container.position.set(
      container_global_pos.x + bg_pos_x,
      container_global_pos.y + bg_pos_y
    );
    const scaled_width = background_image.width * this.scale;
    const scaled_height = background_image.height * this.scale;
    const outline = this.resize_ui_container.getChildAt(0);
    outline.clear().rect(0, 0, scaled_width, scaled_height).stroke({ width: 1, color: 0, alignment: 0, alpha: 0.3 });
    const dotted_outline = this.resize_ui_container.getChildAt(1);
    dotted_outline.clear();
    dotted_outline.rect(0, 0, scaled_width, scaled_height).stroke({
      width: 1,
      color: 0,
      alpha: 0.7,
      pixelLine: true
    });
    if (!this.is_dragging && !this.is_moving && Math.abs(this.scale - this.last_scale) < 1e-3) {
      const dash_length = 5;
      const gap_length = 5;
      const total_length = dash_length + gap_length;
      for (let x = 0; x < scaled_width; x += total_length * 2) {
        dotted_outline.rect(x, 0, Math.min(dash_length, scaled_width - x), 1).fill(0);
        dotted_outline.rect(
          x,
          scaled_height - 1,
          Math.min(dash_length, scaled_width - x),
          1
        ).fill(0);
      }
      for (let y = 0; y < scaled_height; y += total_length * 2) {
        dotted_outline.rect(0, y, 1, Math.min(dash_length, scaled_height - y)).fill(0);
        dotted_outline.rect(
          scaled_width - 1,
          y,
          1,
          Math.min(dash_length, scaled_height - y)
        ).fill(0);
      }
    }
    this.last_scale = this.scale;
    const move_area = this.resize_ui_container.getChildAt(2);
    move_area.clear().rect(0, 0, scaled_width, scaled_height).fill(16777215, 0);
    this.update_handle_positions(scaled_width, scaled_height);
  }
  /**
   * updates the positions of all handles
   * @param width - width of the resize area
   * @param height - height of the resize area
   */
  update_handle_positions(width, height) {
    if (!this.resize_ui_container) return;
    const handles = this.resize_ui_container.children.slice(3);
    const move_area = this.resize_ui_container.getChildAt(2);
    move_area.clear().rect(0, 0, width, height).fill(16777215, 0);
    const corners = handles.slice(0, 4);
    const cornerPositions = [
      { x: 0, y: 0 },
      { x: width, y: 0 },
      { x: 0, y: height },
      { x: width, y: height }
    ];
    corners.forEach((handle, i) => {
      handle.position.set(cornerPositions[i].x, cornerPositions[i].y);
    });
    const edges = handles.slice(4);
    edges.forEach((handle, i) => {
      if (i < 2) {
        handle.position.set(width / 2, i === 0 ? 0 : height);
      } else {
        handle.position.set(i === 2 ? 0 : width, height / 2);
      }
    });
  }
  /**
   * sets up event listeners for the resize tool
   */
  setup_event_listeners() {
    const stage = this.image_editor_context.app.stage;
    stage.eventMode = "static";
    stage.on("pointermove", this.handle_pointer_move.bind(this));
    stage.on("pointerup", this.handle_pointer_up.bind(this));
    stage.on("pointerupoutside", this.handle_pointer_up.bind(this));
    this.setup_dom_event_listeners();
  }
  /**
   * Sets up direct DOM event listeners for dragging the background image
   */
  setup_dom_event_listeners() {
    this.cleanup_dom_event_listeners();
    const canvas = this.image_editor_context.app.canvas;
    this.dom_mousedown_handler = (e) => {
      if (this.current_subtool !== "size") return;
      if (this.is_dragging || this.selected_handle) return;
      if (!this.image_editor_context.background_image) return;
      const bg = this.image_editor_context.background_image;
      const container = this.image_editor_context.image_container;
      const containerBounds = container.getBounds();
      const rect = canvas.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const y = e.clientY - rect.top;
      const localX = (x - containerBounds.x) / this.scale;
      const localY = (y - containerBounds.y) / this.scale;
      if (localX >= bg.position.x && localX <= bg.position.x + bg.width && localY >= bg.position.y && localY <= bg.position.y + bg.height) {
        this.is_moving = true;
        this.last_pointer_position = new Point(localX, localY);
        this.original_state = {
          width: bg.width,
          height: bg.height,
          x: bg.position.x,
          y: bg.position.y
        };
        e.preventDefault();
        e.stopPropagation();
      }
    };
    this.dom_mousemove_handler = (e) => {
      if (this.current_subtool !== "size" || !this.is_moving) return;
      if (!this.last_pointer_position || !this.image_editor_context.background_image)
        return;
      const container = this.image_editor_context.image_container;
      const containerBounds = container.getBounds();
      const rect = canvas.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const y = e.clientY - rect.top;
      const localX = (x - containerBounds.x) / this.scale;
      const localY = (y - containerBounds.y) / this.scale;
      const current_position = new Point(localX, localY);
      this.handle_image_dragging(current_position);
      this.last_pointer_position = current_position;
      e.preventDefault();
      e.stopPropagation();
    };
    this.dom_mouseup_handler = (e) => {
      if (this.current_subtool !== "size") return;
      if (this.original_state && this.image_editor_context.background_image) {
        const bg = this.image_editor_context.background_image;
        const new_state = {
          width: bg.width,
          height: bg.height,
          x: bg.position.x,
          y: bg.position.y
        };
        if (this.original_state.width !== new_state.width || this.original_state.height !== new_state.height || this.original_state.x !== new_state.x || this.original_state.y !== new_state.y) {
          const resize_command = new ResizeCommand(
            this.image_editor_context,
            this.original_state,
            new_state,
            this.update_resize_ui.bind(this)
          );
          this.image_editor_context.execute_command(resize_command);
        }
        this.original_state = null;
      }
      this.is_moving = false;
      this.last_pointer_position = null;
      this.update_resize_ui();
      e.preventDefault();
      e.stopPropagation();
    };
    canvas.addEventListener("mousedown", this.dom_mousedown_handler);
    window.addEventListener("mousemove", this.dom_mousemove_handler);
    window.addEventListener("mouseup", this.dom_mouseup_handler);
  }
  /**
   * Cleans up DOM event listeners
   */
  cleanup_dom_event_listeners() {
    const canvas = this.image_editor_context.app.canvas;
    if (this.dom_mousedown_handler) {
      canvas.removeEventListener("mousedown", this.dom_mousedown_handler);
      this.dom_mousedown_handler = null;
    }
    if (this.dom_mousemove_handler) {
      window.removeEventListener("mousemove", this.dom_mousemove_handler);
      this.dom_mousemove_handler = null;
    }
    if (this.dom_mouseup_handler) {
      window.removeEventListener("mouseup", this.dom_mouseup_handler);
      this.dom_mouseup_handler = null;
    }
  }
  /**
   * removes event listeners for the resize tool
   */
  cleanup_event_listeners() {
    const stage = this.image_editor_context.app.stage;
    stage.off("pointermove", this.handle_pointer_move.bind(this));
    stage.off("pointerup", this.handle_pointer_up.bind(this));
    stage.off("pointerupoutside", this.handle_pointer_up.bind(this));
    this.cleanup_dom_event_listeners();
  }
  /**
   * Handles pointer move events
   */
  handle_pointer_move(event) {
    if (this.current_subtool !== "size") return;
    const local_pos = this.image_editor_context.image_container.toLocal(
      event.global
    );
    const current_position = new Point(local_pos.x, local_pos.y);
    if (this.is_moving && this.last_pointer_position) {
      this.handle_image_dragging(current_position);
      this.last_pointer_position = current_position;
    } else if (this.is_dragging && this.selected_handle) {
      if (!this.image_editor_context.background_image) return;
      const bg = this.image_editor_context.background_image;
      const maintain_aspect_ratio = this.active_corner_index !== -1 && !event.shiftKey;
      const original_width = bg.width;
      const original_height = bg.height;
      const original_x = bg.position.x;
      const original_y = bg.position.y;
      const original_aspect_ratio = original_width / original_height;
      let dimensions;
      if (this.active_corner_index !== -1) {
        dimensions = this.handle_direct_corner_resize(
          current_position,
          original_width,
          original_height,
          original_x,
          original_y,
          original_aspect_ratio,
          maintain_aspect_ratio
        );
      } else if (this.active_edge_index !== -1) {
        dimensions = this.handle_direct_edge_resize(
          current_position,
          original_width,
          original_height,
          original_x,
          original_y
        );
      } else {
        return;
      }
      dimensions = this.limit_dimensions_to_container(
        dimensions,
        original_x,
        original_y,
        original_width,
        original_height,
        maintain_aspect_ratio
      );
      bg.width = dimensions.width;
      bg.height = dimensions.height;
      bg.position.set(dimensions.x, dimensions.y);
      this.update_resize_ui();
      this.last_pointer_position = current_position;
    }
  }
  /**
   * handles pointer up events
   */
  handle_pointer_up() {
    if (this.current_subtool !== "size") return;
    this.is_dragging = false;
    this.is_moving = false;
    this.selected_handle = null;
    this.last_pointer_position = null;
    this.active_corner_index = -1;
    this.active_edge_index = -1;
    this.update_resize_ui();
    this.notify("change");
  }
  /**
   * Handles dragging of the background image
   * @param current_position - The current pointer position
   * @private
   */
  handle_image_dragging(current_position) {
    if (!this.last_pointer_position || !this.image_editor_context.background_image) {
      return;
    }
    const bg = this.image_editor_context.background_image;
    const image_container = this.image_editor_context.image_container.getLocalBounds();
    const dx = current_position.x - this.last_pointer_position.x;
    const dy = current_position.y - this.last_pointer_position.y;
    let new_x = bg.position.x + dx;
    let new_y = bg.position.y + dy;
    const container_width = image_container.width;
    const container_height = image_container.height;
    const bg_width = bg.width;
    const bg_height = bg.height;
    const effectiveWidth = container_width - this.borderRegion * 2;
    const effectiveHeight = container_height - this.borderRegion * 2;
    if (bg_width > effectiveWidth) {
      const min_x = -(bg_width - effectiveWidth) + this.borderRegion;
      const max_x = this.borderRegion;
      new_x = Math.max(min_x, Math.min(max_x, new_x));
    } else {
      const min_x = this.borderRegion;
      const max_x = container_width - bg_width - this.borderRegion;
      new_x = Math.max(min_x, Math.min(max_x, new_x));
    }
    if (bg_height > effectiveHeight) {
      const min_y = -(bg_height - effectiveHeight) + this.borderRegion;
      const max_y = this.borderRegion;
      new_y = Math.max(min_y, Math.min(max_y, new_y));
    } else {
      const min_y = this.borderRegion;
      const max_y = container_height - bg_height - this.borderRegion;
      new_y = Math.max(min_y, Math.min(max_y, new_y));
    }
    bg.position.set(new_x, new_y);
    this.update_resize_ui();
  }
  /**
   * Handles corner resizing
   * @param current_position - The current mouse position
   * @param original_width - The original width
   * @param original_height - The original height
   * @param original_x - The original x position
   * @param original_y - The original y position
   * @param original_aspect_ratio - The original aspect ratio
   * @param maintain_aspect_ratio - Whether to maintain aspect ratio
   * @returns The new dimensions and position
   * @private
   */
  handle_direct_corner_resize(current_position, original_width, original_height, original_x, original_y, original_aspect_ratio, maintain_aspect_ratio) {
    let raw_width = 0;
    let raw_height = 0;
    let new_x = original_x;
    let new_y = original_y;
    switch (this.active_corner_index) {
      case 0:
        raw_width = original_x + original_width - current_position.x;
        raw_height = original_y + original_height - current_position.y;
        break;
      case 1:
        raw_width = current_position.x - original_x;
        raw_height = original_y + original_height - current_position.y;
        break;
      case 2:
        raw_width = original_x + original_width - current_position.x;
        raw_height = current_position.y - original_y;
        break;
      case 3:
        raw_width = current_position.x - original_x;
        raw_height = current_position.y - original_y;
        break;
    }
    raw_width = Math.max(20, raw_width);
    raw_height = Math.max(20, raw_height);
    let new_width = raw_width;
    let new_height = raw_height;
    if (maintain_aspect_ratio) {
      const diagonal_vector = {
        x: current_position.x - (original_x + original_width / 2),
        y: current_position.y - (original_y + original_height / 2)
      };
      const angle = Math.abs(Math.atan2(diagonal_vector.y, diagonal_vector.x));
      if (angle < Math.PI / 4 || angle > 3 * Math.PI / 4) {
        new_height = new_width / original_aspect_ratio;
      } else {
        new_width = new_height * original_aspect_ratio;
      }
    }
    switch (this.active_corner_index) {
      case 0:
        new_x = original_x + original_width - new_width;
        new_y = original_y + original_height - new_height;
        break;
      case 1:
        new_y = original_y + original_height - new_height;
        break;
      case 2:
        new_x = original_x + original_width - new_width;
        break;
    }
    return { width: new_width, height: new_height, x: new_x, y: new_y };
  }
  /**
   * Handles edge resizing
   * @param current_position - The current mouse position
   * @param original_width - The original width
   * @param original_height - The original height
   * @param original_x - The original x position
   * @param original_y - The original y position
   * @returns The new dimensions and position
   * @private
   */
  handle_direct_edge_resize(current_position, original_width, original_height, original_x, original_y) {
    let new_width = original_width;
    let new_height = original_height;
    let new_x = original_x;
    let new_y = original_y;
    switch (this.active_edge_index) {
      case 0:
        new_height = Math.max(
          20,
          original_y + original_height - current_position.y
        );
        new_y = current_position.y;
        break;
      case 1:
        new_height = Math.max(20, current_position.y - original_y);
        break;
      case 2:
        new_width = Math.max(
          20,
          original_x + original_width - current_position.x
        );
        new_x = current_position.x;
        break;
      case 3:
        new_width = Math.max(20, current_position.x - original_x);
        break;
    }
    return { width: new_width, height: new_height, x: new_x, y: new_y };
  }
  /**
   * Limits dimensions to container size
   * @param dimensions - The dimensions to limit
   * @param original_x - The original x position
   * @param original_y - The original y position
   * @param original_width - The original width
   * @param original_height - The original height
   * @param maintain_aspect_ratio - Whether to maintain aspect ratio
   * @returns The limited dimensions and position
   * @private
   */
  limit_dimensions_to_container(dimensions, original_x, original_y, original_width, original_height, maintain_aspect_ratio) {
    let {
      width: new_width,
      height: new_height,
      x: new_x,
      y: new_y
    } = dimensions;
    const effective_container_width = this.dimensions.width - this.borderRegion * 2;
    const effective_container_height = this.dimensions.height - this.borderRegion * 2;
    const pre_limit_width = new_width;
    const pre_limit_height = new_height;
    const size_limited = this.apply_size_limits(
      new_width,
      new_height,
      effective_container_width,
      effective_container_height,
      maintain_aspect_ratio,
      pre_limit_width / pre_limit_height
    );
    new_width = size_limited.width;
    new_height = size_limited.height;
    const position_adjusted = this.adjust_position_for_resizing(
      new_width,
      new_height,
      original_x,
      original_y,
      original_width,
      original_height
    );
    new_x = position_adjusted.x;
    new_y = position_adjusted.y;
    const border_constrained = this.apply_border_constraints(
      new_width,
      new_height,
      new_x,
      new_y,
      maintain_aspect_ratio,
      pre_limit_width / pre_limit_height
    );
    return border_constrained;
  }
  /**
   * Apply size limits to ensure dimensions don't exceed container
   * @private
   */
  apply_size_limits(width, height, max_width, max_height, maintain_aspect_ratio, aspect_ratio) {
    let new_width = width;
    let new_height = height;
    const needs_width_limit = new_width > max_width;
    const needs_height_limit = new_height > max_height;
    if (needs_width_limit && needs_height_limit) {
      const width_scale = max_width / new_width;
      const height_scale = max_height / new_height;
      if (width_scale < height_scale) {
        new_width = max_width;
        if (maintain_aspect_ratio) {
          new_height = new_width / aspect_ratio;
        }
      } else {
        new_height = max_height;
        if (maintain_aspect_ratio) {
          new_width = new_height * aspect_ratio;
        }
      }
    } else if (needs_width_limit) {
      new_width = max_width;
      if (maintain_aspect_ratio) {
        new_height = new_width / aspect_ratio;
      }
    } else if (needs_height_limit) {
      new_height = max_height;
      if (maintain_aspect_ratio) {
        new_width = new_height * aspect_ratio;
      }
    }
    return { width: new_width, height: new_height };
  }
  /**
   * Adjust position based on which handles are being dragged
   * @private
   */
  adjust_position_for_resizing(new_width, new_height, original_x, original_y, original_width, original_height) {
    let new_x = original_x;
    let new_y = original_y;
    switch (this.active_corner_index) {
      case 0:
        new_x = original_x + original_width - new_width;
        new_y = original_y + original_height - new_height;
        break;
      case 1:
        new_y = original_y + original_height - new_height;
        break;
      case 2:
        new_x = original_x + original_width - new_width;
        break;
    }
    if (this.active_edge_index !== -1) {
      switch (this.active_edge_index) {
        case 0:
          new_y = original_y + original_height - new_height;
          break;
        case 2:
          new_x = original_x + original_width - new_width;
          break;
      }
    }
    return { x: new_x, y: new_y };
  }
  /**
   * Apply border region constraints to prevent image from intruding on border
   * @private
   */
  apply_border_constraints(width, height, x, y, maintain_aspect_ratio, aspect_ratio) {
    const top_left_constrained = this.apply_top_left_constraints(
      width,
      height,
      x,
      y,
      maintain_aspect_ratio,
      aspect_ratio
    );
    const all_constrained = this.apply_bottom_right_constraints(
      top_left_constrained.width,
      top_left_constrained.height,
      top_left_constrained.x,
      top_left_constrained.y,
      maintain_aspect_ratio,
      aspect_ratio
    );
    all_constrained.width = Math.max(20, all_constrained.width);
    all_constrained.height = Math.max(20, all_constrained.height);
    return all_constrained;
  }
  /**
   * Apply top and left border constraints
   * @private
   */
  apply_top_left_constraints(width, height, x, y, maintain_aspect_ratio, aspect_ratio) {
    let new_width = width;
    let new_height = height;
    let new_x = x;
    let new_y = y;
    if (new_x < this.borderRegion) {
      if (this.active_corner_index === 0 || this.active_corner_index === 2 || this.active_edge_index === 2) {
        new_width -= this.borderRegion - new_x;
        if (maintain_aspect_ratio) {
          new_height = new_width / aspect_ratio;
        }
      }
      new_x = this.borderRegion;
    }
    if (new_y < this.borderRegion) {
      if (this.active_corner_index === 0 || this.active_corner_index === 1 || this.active_edge_index === 0) {
        new_height -= this.borderRegion - new_y;
        if (maintain_aspect_ratio) {
          new_width = new_height * aspect_ratio;
        }
      }
      new_y = this.borderRegion;
    }
    return { width: new_width, height: new_height, x: new_x, y: new_y };
  }
  /**
   * Apply bottom and right border constraints
   * @private
   */
  apply_bottom_right_constraints(width, height, x, y, maintain_aspect_ratio, aspect_ratio) {
    let new_width = width;
    let new_height = height;
    let new_x = x;
    let new_y = y;
    if (new_x + new_width > this.dimensions.width - this.borderRegion) {
      if (this.active_corner_index === 1 || this.active_corner_index === 3 || this.active_edge_index === 3) {
        new_width = this.dimensions.width - this.borderRegion - new_x;
        if (maintain_aspect_ratio) {
          new_height = new_width / aspect_ratio;
        }
      } else {
        const right_edge = new_x + new_width;
        const excess = right_edge - (this.dimensions.width - this.borderRegion);
        new_x = Math.max(this.borderRegion, new_x - excess);
      }
    }
    if (new_y + new_height > this.dimensions.height - this.borderRegion) {
      if (this.active_corner_index === 2 || this.active_corner_index === 3 || this.active_edge_index === 1) {
        new_height = this.dimensions.height - this.borderRegion - new_y;
        if (maintain_aspect_ratio) {
          new_width = new_height * aspect_ratio;
        }
      } else {
        const bottom_edge = new_y + new_height;
        const excess = bottom_edge - (this.dimensions.height - this.borderRegion);
        new_y = Math.max(this.borderRegion, new_y - excess);
      }
    }
    return { width: new_width, height: new_height, x: new_x, y: new_y };
  }
  /**
   * Shows the resize UI
   * @private
   */
  show_resize_ui() {
    if (this.resize_ui_container) {
      this.resize_ui_container.visible = true;
      this.update_resize_ui();
    }
    if (this.image_editor_context.background_image) {
      const bg = this.image_editor_context.background_image;
      this.current_cursor = bg.cursor;
      bg.cursor = "move";
    }
    this.setup_dom_event_listeners();
  }
  /**
   * Hides the resize UI
   * @private
   */
  hide_resize_ui() {
    if (this.resize_ui_container) {
      this.resize_ui_container.visible = false;
    }
    if (this.image_editor_context.background_image) {
      const bg = this.image_editor_context.background_image;
      bg.cursor = this.current_cursor;
    }
    this.cleanup_dom_event_listeners();
  }
  on(event, callback) {
    this.event_callbacks.set(event, [
      ...this.event_callbacks.get(event) || [],
      callback
    ]);
  }
  off(event, callback) {
    this.event_callbacks.set(
      event,
      this.event_callbacks.get(event)?.filter((cb) => cb !== callback) || []
    );
  }
  notify(event) {
    for (const callback of this.event_callbacks.get(event) || []) {
      callback();
    }
  }
  /**
   * Sets the border region explicitly
   * @param borderRegion The border region size in pixels
   */
  set_border_region(borderRegion) {
    this.borderRegion = borderRegion;
    if (this.image_editor_context?.background_image) {
      this.image_editor_context.background_image.borderRegion = borderRegion;
    }
    if (this.resize_ui_container) {
      this.update_resize_ui();
    }
    if (this.current_subtool === "size" && this.image_editor_context?.background_image) {
      this.apply_boundary_constraints();
    }
  }
}

function set_cursor(child, cursor) {
  if (child instanceof Container) {
    child.cursor = cursor;
  } else if ("cursor" in child) {
    child.cursor = cursor;
  }
}
function recurse_set_cursor(children, cursor) {
  for (const child of children) {
    set_cursor(child, cursor);
    if (child instanceof Container && child.children.length > 0) {
      recurse_set_cursor(
        child.children,
        cursor
      );
    }
  }
}
function clear_timeout(timeout) {
  if (timeout !== null) {
    window.clearTimeout(timeout);
  }
  return null;
}

class BrushCursor {
  constructor(image_editor_context, state, scale) {
    this.image_editor_context = image_editor_context;
    this.state = state;
    this.scale = scale;
    this.initialize_cursor();
    this.initialize_brush_preview();
  }
  cursor_graphics = null;
  cursor_container = null;
  brush_preview_container = null;
  brush_preview_graphics = null;
  is_preview_visible = false;
  is_cursor_over_image = false;
  cursor_position_check_timeout = null;
  is_brush_or_erase_active = false;
  _bound_update_cursor = null;
  _bound_check_cursor_over_image = null;
  /**
   * Returns whether the cursor is currently over the image.
   */
  is_over_image() {
    return this.is_cursor_over_image;
  }
  /**
   * Sets up cursor-related event listeners.
   */
  setup_event_listeners() {
    this.cleanup_event_listeners();
    this._bound_update_cursor = this.update_cursor_position.bind(this);
    this._bound_check_cursor_over_image = this.check_cursor_over_image.bind(this);
    const stage = this.image_editor_context.app.stage;
    stage.on("pointermove", this._bound_update_cursor);
    stage.on("pointermove", this._bound_check_cursor_over_image);
    this.image_editor_context.image_container.on(
      "pointerenter",
      this.on_image_container_pointer_enter.bind(this)
    );
    this.image_editor_context.image_container.on(
      "pointerleave",
      this.on_image_container_pointer_leave.bind(this)
    );
  }
  /**
   * Removes all cursor-related event listeners.
   */
  cleanup_event_listeners() {
    const stage = this.image_editor_context.app.stage;
    if (this._bound_update_cursor) {
      stage.off("pointermove", this._bound_update_cursor);
      this._bound_update_cursor = null;
    }
    if (this._bound_check_cursor_over_image) {
      stage.off("pointermove", this._bound_check_cursor_over_image);
      this._bound_check_cursor_over_image = null;
    }
    this.image_editor_context.image_container.off("pointerenter");
    this.image_editor_context.image_container.off("pointerleave");
    this.cursor_position_check_timeout = clear_timeout(
      this.cursor_position_check_timeout
    );
  }
  /**
   * initializes the cursor for the brush tool.
   */
  initialize_cursor() {
    if (this.cursor_container) {
      if (this.cursor_container.parent) {
        this.cursor_container.parent.removeChild(this.cursor_container);
      }
      this.cursor_container.destroy({ children: true });
      this.cursor_container = null;
    }
    this.cursor_container = new Container();
    this.image_editor_context.ui_container.addChild(this.cursor_container);
    this.cursor_graphics = new Graphics();
    this.cursor_container.addChild(this.cursor_graphics);
    this.update_cursor_appearance();
  }
  /**
   * updates the appearance of the cursor.
   */
  update_cursor_appearance() {
    if (!this.cursor_graphics) return;
    this.cursor_graphics.clear();
    const cursor_color = this.state.mode === "draw" ? tinycolor(this.state.color).toString() : 16777215;
    this.cursor_graphics.circle(0, 0, this.state.brush_size * this.scale).stroke({
      width: 1.5,
      color: cursor_color,
      alpha: 0.8
    });
    this.cursor_graphics.circle(0, 0, 1).fill({
      color: cursor_color,
      alpha: 0.8
    });
  }
  /**
   * updates the position of the cursor.
   * @param {FederatedPointerEvent} event - The pointer event.
   */
  update_cursor_position(event) {
    if (!this.cursor_container) return;
    const local_pos = this.image_editor_context.image_container.toLocal(
      event.global
    );
    const ui_pos = this.image_editor_context.ui_container.toLocal(
      this.image_editor_context.image_container.toGlobal(local_pos)
    );
    this.cursor_container.position.set(ui_pos.x, ui_pos.y);
  }
  /**
   * checks if the cursor is over the image container.
   * @param {FederatedPointerEvent} event - The pointer event.
   */
  check_cursor_over_image(event) {
    if (this.cursor_position_check_timeout !== null) {
      window.clearTimeout(this.cursor_position_check_timeout);
      this.cursor_position_check_timeout = null;
    }
    const image_container = this.image_editor_context.image_container;
    const bounds = image_container.getBounds();
    const was_over_image = this.is_cursor_over_image;
    this.is_cursor_over_image = event.global.x >= bounds.x && event.global.x <= bounds.x + bounds.width && event.global.y >= bounds.y && event.global.y <= bounds.y + bounds.height;
    if (was_over_image !== this.is_cursor_over_image) {
      this.update_cursor_and_preview_visibility();
    }
  }
  /**
   * updates the size of the cursor.
   */
  update_cursor_size() {
    this.update_cursor_appearance();
  }
  /**
   * Updates whether brush or erase tool is active
   */
  set_active(is_active) {
    this.is_brush_or_erase_active = is_active;
    this.update_cursor_and_preview_visibility();
  }
  /**
   * updates the visibility of the cursor.
   */
  update_cursor_visibility() {
    if (this.cursor_container) {
      this.cursor_container.visible = this.is_cursor_over_image && this.is_brush_or_erase_active;
    }
  }
  /**
   * Shows or hides the brush preview.
   * @param {boolean} show - Whether to show the preview.
   */
  preview_brush(show) {
    this.is_preview_visible = show;
    if (this.brush_preview_container) {
      this.brush_preview_container.visible = show && this.is_brush_or_erase_active;
    }
    if (show) {
      this.update_brush_preview();
      this.update_brush_preview_position();
    }
  }
  /**
   * initializes the brush preview.
   */
  initialize_brush_preview() {
    if (this.brush_preview_container) {
      if (this.brush_preview_container.parent) {
        this.brush_preview_container.parent.removeChild(
          this.brush_preview_container
        );
      }
      this.brush_preview_container.destroy({ children: true });
      this.brush_preview_container = null;
    }
    this.brush_preview_container = new Container();
    this.image_editor_context.ui_container.addChild(
      this.brush_preview_container
    );
    this.brush_preview_graphics = new Graphics();
    this.brush_preview_container.addChild(this.brush_preview_graphics);
    this.brush_preview_container.visible = false;
  }
  /**
   * updates the brush preview appearance.
   */
  update_brush_preview() {
    if (!this.brush_preview_graphics) return;
    this.brush_preview_graphics.clear();
    const preview_size = this.state.brush_size * this.scale;
    const preview_color = this.state.mode === "draw" ? tinycolor(this.state.color).setAlpha(this.state.opacity).toString() : 16777215;
    this.brush_preview_graphics.circle(0, 0, preview_size).fill({
      color: preview_color,
      alpha: this.state.mode === "draw" ? this.state.opacity : 0.3
    });
    this.brush_preview_graphics.circle(0, 0, preview_size + 1).stroke({
      width: 1,
      color: 0,
      alpha: 0.5
    });
  }
  /**
   * updates the position of the brush preview to center of canvas.
   */
  update_brush_preview_position() {
    if (!this.brush_preview_container) return;
    const image_container = this.image_editor_context.image_container;
    const center_x = image_container.width / 2;
    const center_y = image_container.height / 2;
    const global_pos = image_container.toGlobal({ x: center_x, y: center_y });
    const ui_pos = this.image_editor_context.ui_container.toLocal(global_pos);
    this.brush_preview_container.position.set(ui_pos.x, ui_pos.y);
  }
  /**
   * Called when the pointer enters the image container.
   */
  on_image_container_pointer_enter() {
    this.is_cursor_over_image = true;
    this.update_cursor_and_preview_visibility();
  }
  /**
   * Called when the pointer leaves the image container.
   */
  on_image_container_pointer_leave() {
    this.is_cursor_over_image = false;
    this.update_cursor_and_preview_visibility();
  }
  /**
   * Updates both cursor and preview visibility based on cursor position.
   */
  update_cursor_and_preview_visibility() {
    this.update_cursor_visibility();
    if (this.brush_preview_container) {
      this.brush_preview_container.visible = this.is_preview_visible && this.is_brush_or_erase_active;
    }
  }
  /**
   * Cleans up all cursor resources.
   */
  cleanup() {
    this.is_brush_or_erase_active = false;
    if (this.cursor_container) {
      if (this.cursor_container.parent) {
        this.cursor_container.parent.removeChild(this.cursor_container);
      }
      this.cursor_container.destroy({ children: true });
      this.cursor_container = null;
    }
    if (this.brush_preview_container) {
      if (this.brush_preview_container.parent) {
        this.brush_preview_container.parent.removeChild(
          this.brush_preview_container
        );
      }
      this.brush_preview_container.destroy({ children: true });
      this.brush_preview_container = null;
    }
    if (this.cursor_position_check_timeout !== null) {
      window.clearTimeout(this.cursor_position_check_timeout);
      this.cursor_position_check_timeout = null;
    }
  }
  /**
   * Updates the brush state.
   */
  update_state(state, scale) {
    this.state = state;
    this.scale = scale;
    this.update_cursor_appearance();
    if (this.is_preview_visible) {
      this.update_brush_preview();
    }
  }
}

class BrushCommand {
  stroke_data;
  context;
  original_texture = null;
  name;
  constructor(context, stroke_data, original_texture) {
    this.name = "Draw";
    this.stroke_data = stroke_data;
    this.context = context;
    if (original_texture) {
      this.original_texture = this.create_texture_from(original_texture);
    }
  }
  /**
   * Creates a new texture with the same content as the source texture
   */
  create_texture_from(source) {
    const texture = RenderTexture.create({
      width: source.width,
      height: source.height,
      resolution: window.devicePixelRatio || 1
    });
    const sprite = new Sprite(source);
    const container = new Container();
    container.addChild(sprite);
    this.context.app.renderer.render(container, {
      renderTexture: texture
    });
    container.destroy({ children: true });
    return texture;
  }
  /**
   * Recreates the stroke by rendering all segments from the stored parameters
   * Preserves the exact order of draw/erase operations
   */
  render_stroke_from_data(stroke_data, target_texture) {
    const draw_segments = stroke_data.segments.filter((s) => s.mode === "draw");
    const erase_segments = stroke_data.segments.filter(
      (s) => s.mode === "erase"
    );
    if (draw_segments.length > 0) {
      const graphics = new Graphics();
      const container = new Container();
      container.addChild(graphics);
      let alpha = 1;
      for (const segment of draw_segments) {
        if (segment.opacity < alpha) {
          alpha = segment.opacity;
        }
        let colorValue = 16777215;
        if (segment.color.startsWith("#")) {
          colorValue = parseInt(segment.color.replace("#", "0x"), 16);
        }
        graphics.setFillStyle({
          color: colorValue,
          alpha: 1
        });
        this.render_segment_to_graphics(graphics, segment);
      }
      const alpha_sprite_texture = RenderTexture.create({
        width: target_texture.width,
        height: target_texture.height,
        resolution: window.devicePixelRatio || 1
      });
      const alpha_sprite = new Sprite(alpha_sprite_texture);
      this.context.app.renderer.render({
        container,
        target: alpha_sprite_texture
      });
      alpha_sprite.alpha = alpha;
      this.context.app.renderer.render({
        container: alpha_sprite,
        target: target_texture,
        clear: false
      });
      container.destroy({ children: true });
      alpha_sprite.destroy();
      alpha_sprite_texture.destroy();
    }
    if (erase_segments.length > 0) {
      const temp_content_texture = RenderTexture.create({
        width: target_texture.width,
        height: target_texture.height,
        resolution: window.devicePixelRatio || 1
      });
      const copy_sprite = new Sprite(target_texture);
      const copy_container = new Container();
      copy_container.addChild(copy_sprite);
      this.context.app.renderer.render({
        container: copy_container,
        target: temp_content_texture,
        clear: true
      });
      const erase_graphics = new Graphics();
      const erase_container = new Container();
      erase_container.addChild(erase_graphics);
      erase_graphics.setFillStyle({
        color: 16777215,
        alpha: 1
      });
      for (const segment of erase_segments) {
        this.render_segment_to_graphics(erase_graphics, segment);
      }
      const mask_texture = RenderTexture.create({
        width: target_texture.width,
        height: target_texture.height,
        resolution: window.devicePixelRatio || 1
      });
      this.context.app.renderer.render({
        container: erase_container,
        target: mask_texture,
        clear: true
      });
      const content_sprite = new Sprite(temp_content_texture);
      const mask_sprite = new Sprite(mask_texture);
      const masked_container = new Container();
      masked_container.addChild(content_sprite);
      masked_container.setMask({ mask: mask_sprite, inverse: true });
      this.context.app.renderer.render({
        container: masked_container,
        target: target_texture,
        clear: true
      });
      copy_container.destroy({ children: true });
      erase_container.destroy({ children: true });
      masked_container.destroy({ children: true });
      temp_content_texture.destroy();
      mask_texture.destroy();
    }
  }
  /**
   * Renders a segment to a graphics object (extracted from renderSegment)
   */
  render_segment_to_graphics(graphics, segment) {
    const distance = Math.sqrt(
      Math.pow(segment.to_x - segment.from_x, 2) + Math.pow(segment.to_y - segment.from_y, 2)
    );
    if (distance < 0.1) {
      graphics.circle(segment.from_x, segment.from_y, segment.size).fill();
    } else {
      const spacing = Math.max(segment.size / 3, 2);
      const steps = Math.max(Math.ceil(distance / spacing), 2);
      for (let i = 0; i < steps; i++) {
        const t = i / (steps - 1);
        const x = segment.from_x + (segment.to_x - segment.from_x) * t;
        const y = segment.from_y + (segment.to_y - segment.from_y) * t;
        graphics.circle(x, y, segment.size).fill();
      }
    }
  }
  async execute(context) {
    if (context) {
      this.context = context;
    }
    let layer_textures = this.context.layer_manager.get_layer_textures(
      this.stroke_data.layer_id
    );
    if (!layer_textures) {
      const all_layers = this.context.layer_manager.get_layers();
      const top_layer = all_layers[all_layers.length - 1];
      layer_textures = this.context.layer_manager.get_layer_textures(
        top_layer.id
      );
    }
    if (!layer_textures) return;
    const temp_texture = RenderTexture.create({
      width: layer_textures.draw.width,
      height: layer_textures.draw.height,
      resolution: window.devicePixelRatio || 1
    });
    const current_sprite = new Sprite(layer_textures.draw);
    const temp_container = new Container();
    temp_container.addChild(current_sprite);
    this.context.app.renderer.render({
      container: temp_container,
      target: temp_texture,
      clear: true
    });
    temp_container.destroy({ children: true });
    this.render_stroke_from_data(this.stroke_data, temp_texture);
    const final_sprite = new Sprite(temp_texture);
    const final_container = new Container();
    final_container.addChild(final_sprite);
    this.context.app.renderer.render({
      container: final_container,
      target: layer_textures.draw,
      clear: true
    });
    final_container.destroy({ children: true });
    temp_texture.destroy();
  }
  async undo() {
    if (!this.original_texture) return;
    const layer_textures = this.context.layer_manager.get_layer_textures(
      this.stroke_data.layer_id
    );
    if (!layer_textures) return;
    const temp_sprite = new Sprite(this.original_texture);
    const temp_container = new Container();
    temp_container.addChild(temp_sprite);
    this.context.app.renderer.render({
      container: temp_container,
      target: layer_textures.draw,
      clear: true
    });
    temp_container.destroy({ children: true });
  }
}
class BrushTextures {
  stroke_texture = null;
  erase_texture = null;
  display_container = null;
  stroke_container = null;
  stroke_graphics = null;
  preview_sprite = null;
  erase_graphics = null;
  dimensions;
  image_editor_context;
  app;
  is_new_stroke = true;
  current_opacity = 1;
  original_layer_texture = null;
  active_layer_id = null;
  current_stroke_segments = [];
  constructor(image_editor_context, app) {
    this.image_editor_context = image_editor_context;
    this.app = app;
    this.dimensions = {
      width: this.image_editor_context.image_container.width,
      height: this.image_editor_context.image_container.height
    };
  }
  /**
   * Initializes textures needed for the brush tool.
   */
  initialize_textures() {
    this.cleanup_textures();
    const local_bounds = this.image_editor_context.image_container.getLocalBounds();
    this.dimensions = {
      width: local_bounds.width,
      height: local_bounds.height
    };
    this.stroke_texture = RenderTexture.create({
      width: this.dimensions.width,
      height: this.dimensions.height,
      resolution: window.devicePixelRatio || 1
    });
    this.erase_texture = RenderTexture.create({
      width: this.dimensions.width,
      height: this.dimensions.height,
      resolution: window.devicePixelRatio || 1
    });
    this.display_container = new Container();
    this.image_editor_context.image_container.addChild(this.display_container);
    this.stroke_container = new Container();
    this.stroke_graphics = new Graphics();
    this.stroke_container.addChild(this.stroke_graphics);
    this.erase_graphics = new Graphics();
    this.preview_sprite = new Sprite(this.stroke_texture);
    this.preview_sprite.alpha = 0;
    this.display_container.addChild(this.preview_sprite);
    const clear_container = new Container();
    this.app.renderer.render(clear_container, {
      renderTexture: this.stroke_texture
    });
    this.app.renderer.render(clear_container, {
      renderTexture: this.erase_texture
    });
    clear_container.destroy();
    this.is_new_stroke = true;
    this.current_opacity = 1;
  }
  /**
   * Reinitializes textures when needed (e.g., after resizing).
   */
  reinitialize() {
    if (this.image_editor_context.image_container.width !== this.dimensions.width || this.image_editor_context.image_container.height !== this.dimensions.height) {
      this.initialize_textures();
    }
  }
  /**
   * Cleans up texture resources.
   */
  cleanup_textures() {
    if (this.stroke_texture) {
      this.stroke_texture.destroy();
      this.stroke_texture = null;
    }
    if (this.erase_texture) {
      this.erase_texture.destroy();
      this.erase_texture = null;
    }
    if (this.display_container) {
      if (this.display_container.parent) {
        this.display_container.parent.removeChild(this.display_container);
      }
      this.display_container.destroy({ children: true });
      this.display_container = null;
    }
    if (this.original_layer_texture) {
      this.original_layer_texture.destroy();
      this.original_layer_texture = null;
    }
    this.stroke_container = null;
    this.stroke_graphics = null;
    this.preview_sprite = null;
    this.erase_graphics = null;
    this.active_layer_id = null;
  }
  /**
   * Preserve the canvas state when starting a new stroke.
   */
  preserve_canvas_state() {
    const active_layer = this.image_editor_context.layer_manager.get_active_layer();
    if (!active_layer) return;
    const layers = this.image_editor_context.layer_manager.get_layers();
    const layer = layers.find((l) => l.container === active_layer);
    if (!layer) return;
    this.active_layer_id = layer.id;
    const layer_textures = this.image_editor_context.layer_manager.get_layer_textures(layer.id);
    if (!layer_textures) return;
    if (this.original_layer_texture) {
      this.original_layer_texture.destroy();
    }
    this.original_layer_texture = RenderTexture.create({
      width: this.dimensions.width,
      height: this.dimensions.height,
      resolution: window.devicePixelRatio || 1
    });
    const temp_sprite = new Sprite(layer_textures.draw);
    const temp_container = new Container();
    temp_container.addChild(temp_sprite);
    this.app.renderer.render(temp_container, {
      renderTexture: this.original_layer_texture
    });
    temp_container.destroy({ children: true });
    this.is_new_stroke = true;
  }
  /**
   * Resets the eraser mask.
   */
  reset_eraser_mask() {
    if (!this.erase_graphics || !this.erase_texture) return;
    this.erase_graphics.clear();
    this.erase_graphics.setFillStyle({ color: 16777215, alpha: 1 });
    this.erase_graphics.rect(0, 0, this.dimensions.width, this.dimensions.height).fill();
    this.erase_graphics.endFill();
    this.app.renderer.render(this.erase_graphics, {
      renderTexture: this.erase_texture
    });
  }
  /**
   * Commits the current stroke to the active layer and returns a command for undo/redo.
   */
  commit_stroke() {
    if (!this.stroke_texture || !this.preview_sprite || !this.stroke_graphics || !this.original_layer_texture || !this.active_layer_id)
      return;
    this.preview_sprite.visible = false;
    const active_layer = this.image_editor_context.layer_manager.get_active_layer();
    if (!active_layer) return;
    const layers = this.image_editor_context.layer_manager.get_layers();
    const layer = layers.find((l) => l.container === active_layer);
    if (!layer) return;
    if (layer.id !== this.active_layer_id) return;
    const layer_textures = this.image_editor_context.layer_manager.get_layer_textures(layer.id);
    if (!layer_textures) return;
    const stroke_data = {
      segments: [...this.current_stroke_segments],
      layer_id: this.active_layer_id
    };
    const brush_command = new BrushCommand(
      this.image_editor_context,
      stroke_data,
      this.original_layer_texture
    );
    if (this.stroke_graphics) {
      this.stroke_graphics.clear();
    }
    const clear_container = new Container();
    this.app.renderer.render(clear_container, {
      renderTexture: this.stroke_texture
    });
    clear_container.destroy();
    this.is_new_stroke = true;
    this.original_layer_texture = null;
    this.active_layer_id = null;
    this.current_stroke_segments = [];
    this.image_editor_context.command_manager.execute(
      brush_command,
      this.image_editor_context
    );
  }
  /**
   * Calculates the distance between two points.
   */
  calculate_distance(x1, y1, x2, y2) {
    return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
  }
  /**
   * Draws a segment for the current stroke using a simple "stamp" approach.
   */
  draw_segment(from_x, from_y, to_x, to_y, size, color, opacity, mode) {
    if (!this.stroke_graphics || !this.stroke_texture || !this.stroke_container || !this.preview_sprite)
      return;
    if (this.is_new_stroke && !this.original_layer_texture) {
      this.preserve_canvas_state();
    }
    this.current_opacity = mode === "draw" ? Math.min(Math.max(opacity, 0), 1) : 0.5;
    const scaled_size = size;
    if (this.is_new_stroke) {
      this.stroke_graphics.clear();
      this.current_stroke_segments = [];
      const clear_container = new Container();
      this.app.renderer.render(clear_container, {
        renderTexture: this.stroke_texture
      });
      clear_container.destroy();
      this.is_new_stroke = false;
    }
    this.current_stroke_segments.push({
      from_x,
      from_y,
      to_x,
      to_y,
      size: scaled_size,
      color,
      opacity: this.current_opacity,
      mode
    });
    if (mode === "draw") {
      let colorValue = 16777215;
      try {
        if (color.startsWith("#")) {
          colorValue = parseInt(color.replace("#", "0x"), 16);
        }
      } catch (e) {
        colorValue = 16777215;
      }
      this.stroke_graphics.setFillStyle({
        color: colorValue,
        alpha: 1
      });
    } else {
      this.stroke_graphics.setFillStyle({
        color: 16777215,
        alpha: 1
      });
    }
    const distance = this.calculate_distance(from_x, from_y, to_x, to_y);
    if (distance < 0.1) {
      this.stroke_graphics.circle(from_x, from_y, scaled_size).fill();
    } else {
      const spacing = Math.max(scaled_size / 3, 2);
      const steps = Math.max(Math.ceil(distance / spacing), 2);
      for (let i = 0; i < steps; i++) {
        const t = i / (steps - 1);
        const x = from_x + (to_x - from_x) * t;
        const y = from_y + (to_y - from_y) * t;
        this.stroke_graphics.circle(x, y, scaled_size).fill();
      }
    }
    this.stroke_graphics.endFill();
    this.app.renderer.render(this.stroke_container, {
      renderTexture: this.stroke_texture
    });
    if (mode === "draw") {
      this.preview_sprite.texture = this.stroke_texture;
      this.preview_sprite.alpha = this.current_opacity;
      this.preview_sprite.tint = 16777215;
    } else {
      const active_layer = this.image_editor_context.layer_manager.get_active_layer();
      if (!active_layer) return;
      const layers = this.image_editor_context.layer_manager.get_layers();
      const layer = layers.find((l) => l.container === active_layer);
      if (!layer) return;
      const layer_textures = this.image_editor_context.layer_manager.get_layer_textures(layer.id);
      if (!layer_textures) return;
      const preview_texture = RenderTexture.create({
        width: this.dimensions.width,
        height: this.dimensions.height,
        resolution: window.devicePixelRatio || 1
      });
      const base_container = new Container();
      const content_sprite = new Sprite(layer_textures.draw);
      base_container.addChild(content_sprite);
      this.app.renderer.render(base_container, {
        renderTexture: preview_texture
      });
      base_container.destroy({ children: true });
      const preview_container = new Container();
      const mask_sprite = new Sprite(this.stroke_texture);
      const overlay = new Graphics();
      overlay.setFillStyle({ color: 16777215, alpha: 0.5 });
      overlay.rect(0, 0, this.dimensions.width, this.dimensions.height).fill();
      overlay.setMask({ mask: mask_sprite, inverse: false });
      preview_container.addChild(overlay);
      this.app.renderer.render(preview_container, {
        renderTexture: preview_texture
      });
      this.preview_sprite.texture = preview_texture;
      this.preview_sprite.alpha = 1;
      preview_container.destroy({ children: true });
      preview_texture.destroy();
    }
    this.preview_sprite.visible = true;
  }
  /**
   * Gets current dimensions of the texture.
   */
  get_dimensions() {
    return this.dimensions;
  }
  /**
   * Checks if textures are properly initialized.
   */
  get textures_initialized() {
    return !!(this.stroke_texture && this.display_container && this.preview_sprite);
  }
  /**
   * Cleanup all resources.
   */
  cleanup() {
    this.cleanup_textures();
  }
}

class BrushTool {
  /**
   * The name of the tool.
   * @type {string}
   */
  name = "brush";
  /**
   * The context of the image editor.
   * @private
   */
  image_editor_context;
  /**
   * The current tool selected in the toolbar.
   * @private
   */
  current_tool;
  /**
   * The current subtool selected in the toolbar.
   * @private
   */
  current_subtool;
  /**
   * The state of the brush tool.
   * @private
   */
  state = {
    opacity: 1,
    brush_size: 10,
    color: "#000000",
    mode: "draw"
  };
  /**
   * Size settings for brush and eraser
   * @private
   */
  brush_size = 10;
  eraser_size = 20;
  /**
   * Drawing state
   * @private
   */
  is_drawing = false;
  last_x = 0;
  last_y = 0;
  scale = 1;
  /**
   * Event listeners
   * @private
   */
  _bound_pointer_down = null;
  _bound_pointer_move = null;
  _bound_pointer_up = null;
  event_callbacks = /* @__PURE__ */ new Map();
  /**
   * Utility modules
   * @private
   */
  brush_cursor = null;
  brush_textures = null;
  /**
   * Sets up the brush tool with the given context, tool, and subtool.
   * @param {ImageEditorContext} context - The image editor context.
   * @param {ToolbarTool} tool - The tool from the toolbar.
   * @param {Subtool} subtool - The subtool from the toolbar.
   * @returns {Promise<void>}
   */
  async setup(context, tool, subtool) {
    this.image_editor_context = context;
    this.current_tool = tool;
    this.current_subtool = subtool;
    this.state.mode = tool === "erase" ? "erase" : "draw";
    if (this.state.mode === "draw") {
      this.state.brush_size = this.brush_size;
    } else {
      this.state.brush_size = this.eraser_size;
    }
    context.scale.subscribe((scale) => {
      this.scale = scale;
      if (this.brush_cursor) {
        this.brush_cursor.update_state(this.state, this.scale);
      }
    });
    this.brush_cursor = new BrushCursor(
      this.image_editor_context,
      this.state,
      this.scale
    );
    this.brush_cursor.set_active(tool === "draw" || tool === "erase");
    this.brush_textures = new BrushTextures(
      this.image_editor_context,
      context.app
    );
    this.brush_textures.initialize_textures();
    this.setup_event_listeners();
    this.handle_cursors(tool);
  }
  /**
   * Handles cursor styles for the brush tool.
   * @param {ToolbarTool} tool - The current tool.
   * @private
   */
  handle_cursors(tool) {
    recurse_set_cursor(
      this.image_editor_context.image_container.children,
      "none"
    );
  }
  /**
   * Sets the current tool and subtool.
   * @param {ToolbarTool} tool - The current tool.
   * @param {Subtool} subtool - The current subtool.
   */
  set_tool(tool, subtool) {
    this.current_tool = tool;
    this.current_subtool = subtool;
    if (this.current_tool !== "erase" && this.current_tool !== "draw") {
      this.commit_pending_changes();
    }
    if (this.brush_cursor) {
      const should_be_active = tool === "draw" || tool === "erase";
      this.brush_cursor.set_active(should_be_active);
    }
    const new_mode = tool === "erase" ? "erase" : "draw";
    const mode_changed = this.state.mode !== new_mode;
    const needs_brush_tool = tool === "erase" || tool === "draw";
    const textures_initialized = this.brush_textures?.textures_initialized ?? false;
    if (needs_brush_tool && (mode_changed || !textures_initialized)) {
      this.brush_textures?.initialize_textures();
    }
    if (this.state.mode !== new_mode) {
      this.state.mode = new_mode;
      if (this.state.mode === "draw") {
        this.state.brush_size = this.brush_size;
      } else {
        this.state.brush_size = this.eraser_size;
      }
      if (this.brush_cursor) {
        this.brush_cursor.update_state(this.state, this.scale);
      }
    }
  }
  /**
   * Commits any pending changes to the canvas.
   * @private
   */
  commit_pending_changes() {
    if (this.is_drawing) {
      this.on_pointer_up();
    }
  }
  /**
   * Called when the user presses the mouse button.
   * @param {FederatedPointerEvent} event - The pointer event.
   * @private
   */
  on_pointer_down(event) {
    const current_layer = this.image_editor_context.layer_manager.get_active_layer();
    if (!current_layer?.visible || this.current_tool !== "erase" && this.current_tool !== "draw") {
      return;
    }
    if (this.brush_cursor && !this.brush_cursor.is_over_image()) {
      return;
    }
    if (this.brush_textures) {
      this.brush_textures.preserve_canvas_state();
    }
    const local_pos = this.image_editor_context.image_container.toLocal(
      event.global
    );
    this.is_drawing = true;
    this.last_x = local_pos.x;
    this.last_y = local_pos.y;
    if (this.brush_textures) {
      this.brush_textures.draw_segment(
        local_pos.x,
        local_pos.y,
        local_pos.x,
        local_pos.y,
        this.state.brush_size,
        this.state.color,
        this.state.opacity,
        this.state.mode
      );
    }
  }
  /**
   * Called when the user moves the mouse while drawing.
   * @param {FederatedPointerEvent} event - The pointer event.
   * @private
   */
  on_pointer_move(event) {
    if (this.brush_cursor) {
      this.brush_cursor.update_cursor_position(event);
    }
    if (!this.is_drawing) return;
    const local_pos = this.image_editor_context.image_container.toLocal(
      event.global
    );
    if (this.brush_textures) {
      this.brush_textures.draw_segment(
        this.last_x,
        this.last_y,
        local_pos.x,
        local_pos.y,
        this.state.brush_size,
        this.state.color,
        this.state.opacity,
        this.state.mode
      );
    }
    this.last_x = local_pos.x;
    this.last_y = local_pos.y;
  }
  /**
   * Called when the user releases the mouse button.
   * @private
   */
  on_pointer_up() {
    if (!this.is_drawing) return;
    this.is_drawing = false;
    if (this.brush_textures) {
      this.brush_textures.commit_stroke();
    }
    this.notify("change");
  }
  /**
   * Sets up event listeners for drawing.
   * @private
   */
  setup_event_listeners() {
    this.cleanup_event_listeners();
    this._bound_pointer_down = this.on_pointer_down.bind(this);
    this._bound_pointer_move = this.on_pointer_move.bind(this);
    this._bound_pointer_up = this.on_pointer_up.bind(this);
    const image_container = this.image_editor_context.image_container;
    image_container.eventMode = "static";
    image_container.interactiveChildren = true;
    const stage = this.image_editor_context.app.stage;
    stage.eventMode = "static";
    stage.on("pointerdown", this._bound_pointer_down);
    stage.on("pointermove", this._bound_pointer_move);
    stage.on("pointerup", this._bound_pointer_up);
    stage.on("pointerupoutside", this._bound_pointer_up);
    if (this.brush_cursor) {
      this.brush_cursor.setup_event_listeners();
    }
  }
  /**
   * Cleans up event listeners.
   * @private
   */
  cleanup_event_listeners() {
    const stage = this.image_editor_context.app.stage;
    if (this._bound_pointer_down) {
      stage.off("pointerdown", this._bound_pointer_down);
      this._bound_pointer_down = null;
    }
    if (this._bound_pointer_move) {
      stage.off("pointermove", this._bound_pointer_move);
      this._bound_pointer_move = null;
    }
    if (this._bound_pointer_up) {
      stage.off("pointerup", this._bound_pointer_up);
      stage.off("pointerupoutside", this._bound_pointer_up);
      this._bound_pointer_up = null;
    }
    if (this.brush_cursor) {
      this.brush_cursor.cleanup_event_listeners();
    }
  }
  /**
   * Sets the brush size.
   * @param {number} size - The new brush size.
   */
  set_brush_size(size) {
    this.brush_size = size;
    if (this.state.mode === "draw") {
      this.state.brush_size = size;
      if (this.brush_cursor) {
        this.brush_cursor.update_state(this.state, this.scale);
      }
    }
  }
  /**
   * Sets the brush color.
   * @param {string|ColorInput} color - The new brush color.
   */
  set_brush_color(color) {
    const color_string = tinycolor(color).toHexString();
    this.state.color = color_string;
    if (this.brush_cursor) {
      this.brush_cursor.update_state(this.state, this.scale);
    }
  }
  /**
   * Sets the brush opacity.
   * @param {number} opacity - The new brush opacity.
   */
  set_brush_opacity(opacity) {
    const clamped_opacity = Math.max(0, Math.min(1, opacity));
    this.state.opacity = clamped_opacity;
    if (this.brush_cursor) {
      this.brush_cursor.update_state(this.state, this.scale);
    }
  }
  /**
   * Sets the eraser size.
   * @param {number} size - The new eraser size.
   */
  set_eraser_size(size) {
    this.eraser_size = size;
    if (this.state.mode === "erase") {
      this.state.brush_size = size;
      if (this.brush_cursor) {
        this.brush_cursor.update_state(this.state, this.scale);
      }
    }
  }
  /**
   * Gets the current size based on mode (brush or eraser).
   * @returns {number} The current size.
   */
  get_current_size() {
    return this.state.mode === "draw" ? this.brush_size : this.eraser_size;
  }
  /**
   * Shows or hides the brush preview.
   * @param {boolean} show - Whether to show the preview.
   */
  preview_brush(show) {
    if (this.brush_cursor) {
      this.brush_cursor.preview_brush(show);
    }
  }
  /**
   * Cleans up resources used by the brush tool.
   */
  cleanup() {
    this.commit_pending_changes();
    this.cleanup_event_listeners();
    if (this.brush_textures) {
      this.brush_textures.cleanup();
      this.brush_textures = null;
    }
    if (this.brush_cursor) {
      this.brush_cursor.cleanup();
      this.brush_cursor = null;
    }
  }
  on(event, callback) {
    this.event_callbacks.set(event, [
      ...this.event_callbacks.get(event) || [],
      callback
    ]);
  }
  off(event, callback) {
    this.event_callbacks.set(
      event,
      this.event_callbacks.get(event)?.filter((cb) => cb !== callback) || []
    );
  }
  notify(event) {
    for (const callback of this.event_callbacks.get(event) || []) {
      callback();
    }
  }
}

var root_6$1 = from_html(`<div class="svelte-189k4ah"><!> <!> <!></div>`);
var root_3$2 = from_html(`<li class="svelte-189k4ah"><!> <button> </button> <!></li>`);
var root_10$1 = from_html(`<li class="add-layer svelte-189k4ah"><!></li>`);
var root_2$2 = from_html(`<ul class="svelte-189k4ah"><!> <!></ul>`);
var root_1$3 = from_html(`<div><button class="layer-title-button svelte-189k4ah" aria-label="Show Layers"> <span class="icon svelte-189k4ah"><!></span></button> <!></div>`);

function Layers_1($$anchor, $$props) {
	push($$props, false);

	const $layers = () => store_get(layers(), '$layers', $$stores);
	const [$$stores, $$cleanup] = setup_stores();
	const dispatch = createEventDispatcher();
	let layers = prop($$props, 'layers', 12);
	let enable_additional_layers = prop($$props, 'enable_additional_layers', 12, true);
	let enable_layers = prop($$props, 'enable_layers', 12, true);
	let show_layers = prop($$props, 'show_layers', 12, false);

	function new_layer() {
		dispatch("new_layer");
	}

	function change_layer(id) {
		dispatch("change_layer", id);
		show_layers(false);
	}

	function move_layer(id, direction) {
		dispatch("move_layer", { id, direction });
	}

	function delete_layer(id) {
		dispatch("delete_layer", id);
	}

	var $$exports = {
		get layers() {
			return layers();
		},

		set layers($$value) {
			layers($$value);
			flushSync();
		},

		get enable_additional_layers() {
			return enable_additional_layers();
		},

		set enable_additional_layers($$value) {
			enable_additional_layers($$value);
			flushSync();
		},

		get enable_layers() {
			return enable_layers();
		},

		set enable_layers($$value) {
			enable_layers($$value);
			flushSync();
		},

		get show_layers() {
			return show_layers();
		},

		set show_layers($$value) {
			show_layers($$value);
			flushSync();
		}
	};

	init$1();

	var fragment = comment();
	var node = first_child(fragment);

	{
		var consequent_7 = ($$anchor) => {
			var div = root_1$3();
			let classes;
			var button = child(div);
			var text = child(button);
			var span = sibling(text);
			var node_1 = child(span);

			Layers(node_1);
			reset(span);
			reset(button);

			var node_2 = sibling(button, 2);

			{
				var consequent_6 = ($$anchor) => {
					var ul = root_2$2();
					var node_3 = child(ul);

					each(node_3, 1, () => ($layers(), untrack(() => $layers().layers)), index, ($$anchor, $$item, i) => {
						let id = () => get($$item).id;
						let name = () => get($$item).name;
						let user_created = () => get($$item).user_created;
						let visible = () => get($$item).visible;
						var li = root_3$2();
						var node_4 = child(li);

						{
							var consequent = ($$anchor) => {
								IconButton$1($$anchor, {
									get Icon() {
										return VisibilityOff;
									},
									size: 'small',
									onclick: (e) => {
										e.stopPropagation();
										dispatch("toggle_layer_visibility", id());
									}
								});
							};

							var alternate = ($$anchor) => {
								IconButton$1($$anchor, {
									get Icon() {
										return Visibility;
									},
									size: 'small',
									onclick: (e) => {
										e.stopPropagation();
										dispatch("toggle_layer_visibility", id());
									}
								});
							};

							if_block(node_4, ($$render) => {
								if (!visible()) $$render(consequent); else $$render(alternate, false);
							});
						}

						var button_1 = sibling(node_4, 2);

						set_attribute(button_1, 'aria-label', `layer-${i + 1}`);

						let classes_1;
						var text_1 = child(button_1, true);

						reset(button_1);

						var node_5 = sibling(button_1, 2);

						{
							var consequent_4 = ($$anchor) => {
								var div_1 = root_6$1();
								var node_6 = child(div_1);

								{
									var consequent_1 = ($$anchor) => {
										IconButton$1($$anchor, {
											onclick: (e) => {
												e.stopPropagation();
												move_layer(id(), "up");
											},

											get Icon() {
												return ArrowUp;
											},
											size: 'x-small'
										});
									};

									if_block(node_6, ($$render) => {
										if (i > 0) $$render(consequent_1);
									});
								}

								var node_7 = sibling(node_6, 2);

								{
									var consequent_2 = ($$anchor) => {
										IconButton$1($$anchor, {
											onclick: (e) => {
												e.stopPropagation();
												move_layer(id(), "down");
											},

											get Icon() {
												return ArrowDown;
											},
											size: 'x-small'
										});
									};

									if_block(node_7, ($$render) => {
										if ((
											$layers(),
											untrack(() => i < $layers().layers.length - 1)
										)) $$render(consequent_2);
									});
								}

								var node_8 = sibling(node_7, 2);

								{
									var consequent_3 = ($$anchor) => {
										IconButton$1($$anchor, {
											onclick: (e) => {
												e.stopPropagation();
												delete_layer(id());
											},

											get Icon() {
												return Clear;
											},
											size: 'x-small'
										});
									};

									if_block(node_8, ($$render) => {
										if ((
											$layers(),
											user_created(),
											untrack(() => $layers().layers.length > 1 && user_created())
										)) $$render(consequent_3);
									});
								}

								reset(div_1);
								append($$anchor, div_1);
							};

							if_block(node_5, ($$render) => {
								if (($layers(), untrack(() => $layers().layers.length > 1))) $$render(consequent_4);
							});
						}

						reset(li);

						template_effect(() => {
							classes_1 = set_class(button_1, 1, 'svelte-189k4ah', null, classes_1, { selected_layer: $layers().active_layer === id() });
							set_text(text_1, name());
						});

						event('click', button_1, stopPropagation(() => change_layer(id())));
						append($$anchor, li);
					});

					var node_9 = sibling(node_3, 2);

					{
						var consequent_5 = ($$anchor) => {
							var li_1 = root_10$1();
							var node_10 = child(li_1);

							IconButton$1(node_10, {
								get Icon() {
									return Plus;
								},
								label: 'Add Layer',
								onclick: (e) => {
									e.stopPropagation();
									new_layer();
								},
								size: 'x-small'
							});

							reset(li_1);
							append($$anchor, li_1);
						};

						if_block(node_9, ($$render) => {
							if (enable_additional_layers()) $$render(consequent_5);
						});
					}

					reset(ul);
					append($$anchor, ul);
				};

				if_block(node_2, ($$render) => {
					if (show_layers()) $$render(consequent_6);
				});
			}

			reset(div);
			action(div, ($$node, $$action_arg) => click_outside?.($$node, $$action_arg), () => () => show_layers(false));

			template_effect(
				($0) => {
					classes = set_class(div, 1, 'layer-wrap svelte-189k4ah', null, classes, { closed: !show_layers() });
					set_text(text, `${$0 ?? ''} `);
				},
				[
					() => (
						deep_read_state(show_layers()),
						$layers(),
						untrack(() => show_layers()
							? "Layers"
							: $layers().layers.find((l) => l.id === $layers().active_layer)?.name)
					)
				]
			);

			event('click', button, stopPropagation(() => show_layers(!show_layers())));
			append($$anchor, div);
		};

		if_block(node, ($$render) => {
			if (enable_layers()) $$render(consequent_7);
		});
	}

	append($$anchor, fragment);

	var $$pop = pop($$exports);

	$$cleanup();

	return $$pop;
}

var root = from_html(`<div class="toolbar-wrap-wrap svelte-1357rcu"><div class="toolbar-wrap svelte-1357rcu"><!></div></div>`);

function SecondaryToolbar($$anchor, $$props) {
	push($$props, false);

	let layers = prop($$props, 'layers', 12);
	let enable_additional_layers = prop($$props, 'enable_additional_layers', 12, true);
	let enable_layers = prop($$props, 'enable_layers', 12, true);
	let show_layers = prop($$props, 'show_layers', 12, false);

	var $$exports = {
		get layers() {
			return layers();
		},

		set layers($$value) {
			layers($$value);
			flushSync();
		},

		get enable_additional_layers() {
			return enable_additional_layers();
		},

		set enable_additional_layers($$value) {
			enable_additional_layers($$value);
			flushSync();
		},

		get enable_layers() {
			return enable_layers();
		},

		set enable_layers($$value) {
			enable_layers($$value);
			flushSync();
		},

		get show_layers() {
			return show_layers();
		},

		set show_layers($$value) {
			show_layers($$value);
			flushSync();
		}
	};

	var div = root();
	var div_1 = child(div);
	var node = child(div_1);

	{
		var consequent = ($$anchor) => {
			Layers_1($$anchor, {
				get layers() {
					return layers();
				},

				get enable_additional_layers() {
					return enable_additional_layers();
				},

				get enable_layers() {
					return enable_layers();
				},

				get show_layers() {
					return show_layers();
				},

				$$events: {
					new_layer($$arg) {
						bubble_event.call(this, $$props, $$arg);
					},

					change_layer($$arg) {
						bubble_event.call(this, $$props, $$arg);
					},

					move_layer($$arg) {
						bubble_event.call(this, $$props, $$arg);
					},

					delete_layer($$arg) {
						bubble_event.call(this, $$props, $$arg);
					},

					toggle_layer_visibility($$arg) {
						bubble_event.call(this, $$props, $$arg);
					}
				}
			});
		};

		if_block(node, ($$render) => {
			if (enable_layers()) $$render(consequent);
		});
	}

	reset(div_1);
	reset(div);
	append($$anchor, div);

	return pop($$exports);
}

var root_4$1 = from_html(`<li class="svelte-o0h5ih"><button class="svelte-o0h5ih"> </button></li>`);
var root_3$1 = from_html(`<div class="zoom-controls svelte-o0h5ih"><ul class="svelte-o0h5ih"><li class="svelte-o0h5ih"><button class="svelte-o0h5ih">Fit to screen</button></li> <!></ul></div>`);
var root_1$2 = from_html(`<!> <!> <!> <!> <div class="zoom-number svelte-o0h5ih"><span role="button" tabindex="0" class="svelte-o0h5ih"> </span> <!></div> <div class="separator svelte-o0h5ih"></div> <!> <!> <!> <!>`, 1);

function Controls($$anchor, $$props) {
	push($$props, false);

	const formatted_zoom = mutable_source();
	let can_save = prop($$props, 'can_save', 12, false);
	let changeable = prop($$props, 'changeable', 12, false);
	let current_zoom = prop($$props, 'current_zoom', 12, 1);
	let tool = prop($$props, 'tool', 12);
	let min_zoom = prop($$props, 'min_zoom', 12, true);
	let enable_download = prop($$props, 'enable_download', 12, false);
	let can_undo = prop($$props, 'can_undo', 12);
	let can_redo = prop($$props, 'can_redo', 12);
	const dispatch = createEventDispatcher();
	let show_zoom_popup = mutable_source(false);

	function handle_zoom_click(e) {
		e.stopPropagation();
		set(show_zoom_popup, !get(show_zoom_popup));
	}

	function handle_zoom_change(zoom) {
		dispatch("set_zoom", zoom);
		set(show_zoom_popup, false);
	}

	function handle_zoom_keydown(e) {
		if (e.key === "Enter") {
			handle_zoom_change(current_zoom());
		}
	}

	legacy_pre_effect(() => (deep_read_state(current_zoom())), () => {
		set(formatted_zoom, Math.round(current_zoom() * 100));
	});

	legacy_pre_effect_reset();

	var $$exports = {
		get can_save() {
			return can_save();
		},

		set can_save($$value) {
			can_save($$value);
			flushSync();
		},

		get changeable() {
			return changeable();
		},

		set changeable($$value) {
			changeable($$value);
			flushSync();
		},

		get current_zoom() {
			return current_zoom();
		},

		set current_zoom($$value) {
			current_zoom($$value);
			flushSync();
		},

		get tool() {
			return tool();
		},

		set tool($$value) {
			tool($$value);
			flushSync();
		},

		get min_zoom() {
			return min_zoom();
		},

		set min_zoom($$value) {
			min_zoom($$value);
			flushSync();
		},

		get enable_download() {
			return enable_download();
		},

		set enable_download($$value) {
			enable_download($$value);
			flushSync();
		},

		get can_undo() {
			return can_undo();
		},

		set can_undo($$value) {
			can_undo($$value);
			flushSync();
		},

		get can_redo() {
			return can_redo();
		},

		set can_redo($$value) {
			can_redo($$value);
			flushSync();
		}
	};

	init$1();

	IconButtonWrapper($$anchor, {
		children: ($$anchor, $$slotProps) => {
			var fragment_1 = root_1$2();
			var node = first_child(fragment_1);

			{
				var consequent = ($$anchor) => {
					IconButton$1($$anchor, {
						get Icon() {
							return Download;
						},
						label: 'Download',
						onclick: (event) => {
							dispatch("download");
							event.stopPropagation();
						}
					});
				};

				if_block(node, ($$render) => {
					if (enable_download()) $$render(consequent);
				});
			}

			var node_1 = sibling(node, 2);

			{
				let $0 = derived_safe_equal(() => tool() === "pan");

				IconButton$1(node_1, {
					get Icon() {
						return Pan;
					},
					label: 'Pan',
					onclick: (e) => {
						e.stopPropagation();
						dispatch("pan");
					},

					get highlight() {
						return get($0);
					},
					size: 'small',
					padded: false,
					transparent: true,
					get disabled() {
						return min_zoom();
					}
				});
			}

			var node_2 = sibling(node_1, 2);

			IconButton$1(node_2, {
				get Icon() {
					return ZoomOut;
				},
				label: 'Zoom out',
				onclick: (event) => {
					dispatch("zoom_out");
					event.stopPropagation();
				}
			});

			var node_3 = sibling(node_2, 2);

			IconButton$1(node_3, {
				get Icon() {
					return ZoomIn;
				},
				label: 'Zoom in',
				onclick: (event) => {
					dispatch("zoom_in");
					event.stopPropagation();
				}
			});

			var div = sibling(node_3, 2);
			var span = child(div);
			var text = child(span);

			reset(span);

			var node_4 = sibling(span, 2);

			{
				var consequent_1 = ($$anchor) => {
					var div_1 = root_3$1();
					var ul = child(div_1);
					var li = child(ul);
					var button = child(li);

					reset(li);

					var node_5 = sibling(li, 2);

					each(node_5, 0, () => [0.25, 0.5, 1, 2, 4], index, ($$anchor, zoom) => {
						var li_1 = root_4$1();
						var button_1 = child(li_1);
						var text_1 = child(button_1);

						reset(button_1);
						reset(li_1);
						template_effect(() => set_text(text_1, `${zoom * 100}%`));
						event('click', button_1, stopPropagation(() => handle_zoom_change(zoom)));
						append($$anchor, li_1);
					});

					reset(ul);
					reset(div_1);
					event('click', button, stopPropagation(() => handle_zoom_change("fit")));
					append($$anchor, div_1);
				};

				if_block(node_4, ($$render) => {
					if (get(show_zoom_popup)) $$render(consequent_1);
				});
			}

			reset(div);

			var node_6 = sibling(div, 4);

			{
				let $0 = derived_safe_equal(() => !can_undo());

				IconButton$1(node_6, {
					get Icon() {
						return Undo;
					},
					label: 'Undo',
					onclick: (event) => {
						dispatch("undo");
						event.stopPropagation();
					},

					get disabled() {
						return get($0);
					}
				});
			}

			var node_7 = sibling(node_6, 2);

			{
				let $0 = derived_safe_equal(() => !can_redo());

				IconButton$1(node_7, {
					get Icon() {
						return Redo;
					},
					label: 'Redo',
					onclick: (event) => {
						dispatch("redo");
						event.stopPropagation();
					},

					get disabled() {
						return get($0);
					}
				});
			}

			var node_8 = sibling(node_7, 2);

			{
				var consequent_2 = ($$anchor) => {
					{
						let $0 = derived_safe_equal(() => !can_save());

						IconButton$1($$anchor, {
							get disabled() {
								return get($0);
							},

							get Icon() {
								return Check;
							},
							label: 'Save changes',
							onclick: (event) => {
								dispatch("save");
								event.stopPropagation();
							},
							color: 'var(--color-accent)'
						});
					}
				};

				if_block(node_8, ($$render) => {
					if (changeable()) $$render(consequent_2);
				});
			}

			var node_9 = sibling(node_8, 2);

			IconButton$1(node_9, {
				get Icon() {
					return Trash;
				},
				label: 'Clear canvas',
				onclick: (event) => {
					dispatch("remove_image");
					event.stopPropagation();
				}
			});

			template_effect(() => set_text(text, `${get(formatted_zoom) ?? ''}%`));
			event('click', span, handle_zoom_click);
			event('keydown', span, handle_zoom_keydown);
			append($$anchor, fragment_1);
		},
		$$slots: { default: true }
	});

	return pop($$exports);
}

var root_5$1 = from_html(`<div class="modal svelte-6fnl1z"><div class="modal-inner svelte-6fnl1z"><!></div></div>`);
var root_2$1 = from_html(`<!> <!> <!> <!>`, 1);
var root_7$1 = from_html(`<div class="crop-confirm-button svelte-6fnl1z"><!></div>`);
var root_1$1 = from_html(`<div data-testid="image" aria-dropeffect="copy"><!> <div></div> <div></div> <!> <!></div>`);

function ImageEditor_1($$anchor, $$props) {
	push($$props, false);

	const disable_click = mutable_source();
	const { drag, open_file_upload } = create_drag();
	const dispatch = createEventDispatcher();
	const antialias = true;
	let changeable = prop($$props, 'changeable', 12, false);
	let sources = prop($$props, 'sources', 28, () => ["upload", "webcam", "clipboard"]);
	let transforms = prop($$props, 'transforms', 28, () => ["crop", "resize"]);
	let canvas_size = prop($$props, 'canvas_size', 12);
	let is_dragging = prop($$props, 'is_dragging', 12, false);
	let background_image = prop($$props, 'background_image', 12, false);
	let brush_options = prop($$props, 'brush_options', 12);
	let eraser_options = prop($$props, 'eraser_options', 12);
	let fixed_canvas = prop($$props, 'fixed_canvas', 12, false);
	let root = prop($$props, 'root', 12);
	let i18n = prop($$props, 'i18n', 12);
	let upload = prop($$props, 'upload', 12);
	let composite = prop($$props, 'composite', 12);
	let layers = prop($$props, 'layers', 12);
	let background = prop($$props, 'background', 12);
	let border_region = prop($$props, 'border_region', 12, 0);
	let layer_options = prop($$props, 'layer_options', 12);
	let current_tool = prop($$props, 'current_tool', 12);
	let webcam_options = prop($$props, 'webcam_options', 12);
	let show_download_button = prop($$props, 'show_download_button', 12, false);
	let theme_mode = prop($$props, 'theme_mode', 12);
	let full_history = prop($$props, 'full_history', 12, null);
	let pixi_target = mutable_source();
	let pixi_target_crop = mutable_source();

	function refresh_tools() {
		if (!get(editor) || !get(ready)) return;

		get(editor).set_tool(current_tool());
		get(editor).set_subtool(get(current_subtool));
	}

	/**
	 * Refreshes tools when layer state changes
	 */
	function refresh_tools_for_layer_changes(current_layers) {
		if (!get(editor) || !get(ready)) return;

		if (current_layers.layers.length > 0 && !current_layers.active_layer) {
			get(editor).set_layer(current_layers.layers[0].id);
		}

		// reapply current tool to ensure it targets the correct layer
		if (current_tool()) {
			get(editor).set_tool(current_tool());

			if (get(current_subtool)) {
				get(editor).set_subtool(get(current_subtool));
			}
		}

		if (get(brush) && (current_tool() === "draw" || current_tool() === "erase")) {
			get(brush).set_tool(current_tool(), get(current_subtool));
		}
	}

	function check_if_should_init() {
		return layer_options() && get(editor) && get(ready);
	}

	let has_drawn = prop($$props, 'has_drawn', 12, false);

	/**
	 * Gets the image blobs from the editor
	 * @returns {Promise<ImageBlobs>} Object containing background, layers, and composite image blobs
	 */
	async function get_blobs() {
		if (!get(editor)) return { background: null, layers: [], composite: null };
		if (!background_image() && !has_drawn() && !layers().length) return { background: null, layers: [], composite: null };

		const blobs = await get(editor).get_blobs();

		return blobs;
	}

	let editor = mutable_source();

	/**
	 * Adds an image to the editor
	 * @param {Blob | File} image - The image to add
	 */
	function add_image(image) {
		get(editor).add_image({ image });
	}

	let pending_bg;

	/**
	 * Adds an image to the editor from a URL
	 * @param {string | FileData} source - The URL of the image or a FileData object
	 * @returns {Promise<void>}
	 */
	async function add_image_from_url(source) {
		if (!get(editor) || !source || !check_if_should_init()) return;

		let url;

		if (typeof source === "string") {
			url = source;
		} else if (source?.meta?._type === "gradio.FileData" && source?.url) {
			url = source.url;
		} else {
			console.warn("Invalid source provided to add_image_from_url:", source);

			return;
		}

		try {
			pending_bg = get(editor).add_image_from_url(url);

			let pending_crop = crop.add_image_from_url(url);

			await Promise.all([pending_bg, pending_crop]);
			crop.set_tool("image");
			crop.set_subtool("crop");
			background_image(true);
			get(zoom).set_zoom("fit");
			dispatch("upload");
			dispatch("input");
		} catch(error) {
			console.error("Error adding image from URL:", error);
		}
	}

	/**
	 * Adds a new layer with an image loaded from a URL
	 * @param {string | FileData} source - The URL of the image or a FileData object
	 * @returns {Promise<string | null>} - The ID of the created layer, or null if failed
	 */
	async function add_layers_from_url(source) {
		if (!get(editor) || !source.length || !check_if_should_init()) return;

		if (Array.isArray(source) && source.every((item) => item?.meta?._type === "gradio.FileData")) {
			try {
				await pending_bg;
				await get(editor).add_layers_from_url(source.map((item) => item.url));
				dispatch("change");
				dispatch("input");
			} catch(error) {
				console.error("Error adding layer from URL:", error);
			}
		}
	}

	let brush = mutable_source();
	let zoom = mutable_source();
	let zoom_level = mutable_source(1);
	let ready = mutable_source(false);
	let min_zoom = mutable_source(true);
	let last_dimensions = { width: 0, height: 0 };

	/**
	 * Handles visibility changes and resets zoom if dimensions have changed
	 */
	async function handle_visibility_change() {
		if (!get(editor) || !get(ready) || !get(zoom)) return;

		await tick();

		const is_visible = get(pixi_target).offsetParent !== null;

		if (is_visible) {
			const current_dimensions = get(pixi_target).getBoundingClientRect();

			if (current_dimensions.width !== last_dimensions.width || current_dimensions.height !== last_dimensions.height) {
				get(zoom).set_zoom("fit");

				last_dimensions = {
					width: current_dimensions.width,
					height: current_dimensions.height
				};
			}
		}
	}

	onMount(() => {
		let intersection_observer;
		let resize_observer;

		init_image_editor().then(() => {

			intersection_observer = new IntersectionObserver(() => {
				handle_visibility_change();
			});

			resize_observer = new ResizeObserver(() => {
				handle_visibility_change();
			});

			intersection_observer.observe(get(pixi_target));
			resize_observer.observe(get(pixi_target));

			setTimeout(
				() => {
					if (full_history() && get(editor)) {
						get(editor).command_manager.replay(full_history(), get(editor).context).then(() => {
							refresh_tools_after_history();
						});
					}
				},
				0
			);
		});

		if (typeof window !== "undefined") {
			window.editor = get(editor);
		}

		return () => {
			if (intersection_observer) {
				intersection_observer.disconnect();
			}

			if (resize_observer) {
				resize_observer.disconnect();
			}

			if (get(editor)) {
				get(editor).destroy();
			}
		};
	});

	/**
	 * Refreshes tool state after history replay to ensure proper layer targeting
	 */
	function refresh_tools_after_history() {
		if (!get(editor) || !get(ready)) return;

		const current_layers = get$1(get(editor).layers);

		if (current_layers.layers.length > 0 && !current_layers.active_layer) {
			get(editor).set_layer(current_layers.layers[0].id);
		}

		if (current_tool()) {
			get(editor).set_tool(current_tool());

			if (get(current_subtool)) {
				get(editor).set_subtool(get(current_subtool));
			}
		}

		if (get(brush) && (current_tool() === "draw" || current_tool() === "erase")) {
			get(brush).set_tool(current_tool(), get(current_subtool));
		}

		full_history(get(editor).command_manager.history);
	}

	let crop;
	let crop_zoom = mutable_source();
	let can_undo = prop($$props, 'can_undo', 12, false);
	let can_redo = mutable_source(false);

	async function init_image_editor() {
		set(brush, new BrushTool());
		set(zoom, new ZoomTool());

		set(editor, new ImageEditor({
			target_element: get(pixi_target),
			width: canvas_size()[0],
			height: canvas_size()[1],
			tools: ["image", get(zoom), new ResizeTool(), get(brush)],
			fixed_canvas: fixed_canvas(),
			border_region: border_region(),
			layer_options: layer_options(),
			theme_mode: theme_mode()
		}));

		get(brush).on("change", () => {
			has_drawn(true);
		});

		set(crop_zoom, new ZoomTool());

		crop = new ImageEditor({
			target_element: get(pixi_target_crop),
			width: canvas_size()[0],
			height: canvas_size()[1],
			tools: ["image", get(crop_zoom), new CropTool()],
			dark: true,
			fixed_canvas: false,
			border_region: 0,
			pad_bottom: 40
		});

		get(editor).scale.subscribe((_scale) => {
			set(zoom_level, _scale);
		});

		get(editor).min_zoom.subscribe((is_min_zoom) => {
			set(min_zoom, is_min_zoom);
		});

		get(editor).dimensions.subscribe((dimensions) => {
			last_dimensions = { ...dimensions };
		});

		get(editor).command_manager.current_history.subscribe((history) => {
			can_undo(history.previous !== null);
			set(can_redo, history.next !== null);
		});

		await Promise.all([get(editor).ready, crop.ready]).then(() => {
			handle_tool_change({ tool: "image" });
			set(ready, true);

			if (sources().length > 0) {
				handle_tool_change({ tool: "image" });
			} else {
				handle_tool_change({ tool: "draw" });
			}

			crop.set_subtool("crop");
		});

		get(editor).on("change", () => {
			dispatch("change");
			dispatch("input");
			full_history(get(editor).command_manager.history);
		});

		if (background() || layers().length > 0) {
			if (background()) {
				await add_image_from_url(background());
			}

			if (layers().length > 0) {
				await add_layers_from_url(layers());
			}

			handle_tool_change({ tool: "draw" });
		} else if (composite()) {
			await add_image_from_url(composite());
			handle_tool_change({ tool: "draw" });
		}

		refresh_tools();
	}

	/**
	 * Handles file uploads
	 * @param {File[]} files - The uploaded files
	 */
	async function handle_files(files) {
		if (files == null) return;
		if (!sources().includes("upload")) return;

		get(editor).reset_canvas();

		const _file = Array.isArray(files) ? files[0] : files;

		await get(editor).add_image({ image: _file });
		await crop.add_image({ image: _file });
		crop.reset();
		background_image(true);
		get(zoom).set_zoom("fit");
		handle_tool_change({ tool: "draw" });
		dispatch("upload");
	}

	/**
	 * Handles tool change events
	 * @param {{ tool: ToolbarTool }} param0 - Object containing the selected tool
	 */
	function handle_tool_change({ tool }) {
		get(editor).set_tool(tool);
		current_tool(tool);

		if (tool === "image") {
			crop.set_tool("image");
			crop.set_subtool("crop");
		}
	}

	/**
	 * Handles subtool change events
	 * @param {{ tool: ToolbarTool, subtool: Subtool }} param0 - Object containing the selected tool and subtool
	 */
	function handle_subtool_change({ tool, subtool }) {
		get(editor).set_subtool(subtool);
		set(current_subtool, subtool);

		if (subtool === null) {
			return;
		}

		if (tool === "draw") {
			if (subtool === "size") {
				set(brush_size_visible, true);
			} else if (subtool === "color") {
				set(brush_color_visible, true);
			}
		}

		if (tool === "erase" && subtool === "size") {
			set(eraser_size_visible, true);
		}

		if (tool === "image" && subtool === "paste") {
			process_clipboard();
		}

		if (tool === "image" && subtool === "upload") {
			tick().then(() => {
				set(disable_click, false);
				open_file_upload();
			});
		}
	}

	let eraser_size_visible = mutable_source(false);
	let selected_color = mutable_source();
	let selected_size = mutable_source();
	let selected_opacity = mutable_source(1);
	let selected_eraser_size = mutable_source();

	function update_brush_options() {
		const default_color = brush_options().default_color === "auto"
			? brush_options().colors[0]
			: brush_options().default_color;

		// color is already a tuple [color, opacity]
		if (Array.isArray(default_color)) {
			set(selected_color, default_color[0]);
			set(selected_opacity, default_color[1]);
		} else {
			set(selected_color, default_color);

			// color is a string, check if it has opacity info
			const color = tinycolor(default_color);

			if (color.getAlpha() < 1) {
				set(selected_opacity, color.getAlpha());
			} else {
				set(selected_opacity, 1);
			}
		}

		set(selected_size, typeof brush_options().default_size === "number" ? brush_options().default_size : 25);
	}

	function update_eraser_options() {
		set(selected_eraser_size, eraser_options().default_size === "auto" ? 25 : eraser_options().default_size);
	}

	let brush_size_visible = mutable_source(false);
	let brush_color_visible = mutable_source(false);
	let current_subtool = mutable_source(null);
	let preview = mutable_source(false);

	function handle_zoom_change(zoom_level) {
		get(zoom).set_zoom(zoom_level);
	}

	function zoom_in_out(direction) {
		get(zoom).set_zoom(direction === "in"
			? get(zoom_level) + (get(zoom_level) < 1 ? 0.1 : get(zoom_level) * 0.1)
			: get(zoom_level) - (get(zoom_level) < 1 ? 0.1 : get(zoom_level) * 0.1));
	}

	async function process_clipboard() {
		const items = await navigator.clipboard.read();

		for (let i = 0; i < items.length; i++) {
			const type = items[i].types.find((t) => t.startsWith("image/"));

			if (type) {
				const blob = await items[i].getType(type);

				handle_files(blob);
			}
		}
	}

	function handle_capture(e) {
		if (e.detail !== null) {
			handle_files(e.detail);
		}

		handle_subtool_change({ tool: current_tool(), subtool: null });
	}

	function handle_save() {
		dispatch("save");
	}

	async function handle_crop_confirm() {
		const { image } = await crop.get_crop_bounds();

		if (!image) return;

		await get(editor).add_image({ image, resize: false });
		handle_subtool_change({ tool: "image", subtool: null });
		dispatch("change");
		dispatch("input");
	}

	async function handle_download() {
		const blobs = await get(editor).get_blobs();
		const blob = blobs.composite;

		if (!blob) {
			dispatch("download_error", "Unable to generate image to download.");

			return;
		}

		const url = URL.createObjectURL(blob);
		const link = document.createElement("a");

		link.href = url;
		link.download = "image.png";
		link.click();
		URL.revokeObjectURL(url);
	}

	function handle_undo() {
		get(editor).undo();
	}

	function handle_redo() {
		get(editor).redo();
	}

	let old_background = mutable_source(background());

	legacy_pre_effect(() => (deep_read_state(layer_options()), get(editor)), () => {
		if (layer_options()) {
			if (check_if_should_init()) {
				get(editor).set_layer_options(layer_options());
				refresh_tools();
			}
		}
	});

	legacy_pre_effect(() => (get(editor), get(ready), get$1), () => {
		if (get(editor) && get(ready) && get(editor).layers) {
			const current_layers = get$1(get(editor).layers);

			if (current_layers.layers.length > 0 && !current_layers.active_layer) {
				refresh_tools_for_layer_changes(current_layers);
			}
		}
	});

	legacy_pre_effect(
		() => (
			deep_read_state(background()),
			deep_read_state(layers()),
			deep_read_state(composite()),
			get(editor),
			get(ready),
			get(zoom)
		),
		() => {
			if (background() == null && layers().length == 0 && composite() == null && get(editor) && get(ready)) {
				get(editor).reset_canvas();
				get(zoom).set_zoom("fit");
				handle_tool_change({ tool: "image" });
				background_image(false);
				has_drawn(false);
			}
		}
	);

	legacy_pre_effect(() => (get(old_background), deep_read_state(background())), () => {
		if (get(old_background) !== background()) {
			handle_tool_change({ tool: "draw" });
			handle_subtool_change({ tool: "draw", subtool: null });
			current_tool("draw");
			set(old_background, background());
		}
	});

	legacy_pre_effect(
		() => (
			deep_read_state(current_tool()),
			get(current_subtool),
			get(crop_zoom)
		),
		() => {
			current_tool() === "image" && get(current_subtool) === "crop" && get(crop_zoom).set_zoom("fit");
		}
	);

	legacy_pre_effect(() => (deep_read_state(can_undo()), get(editor)), () => {
		background_image(can_undo() && get(editor).command_manager.contains("AddImage"));
	});

	legacy_pre_effect(
		() => (
			deep_read_state(brush_options()),
			deep_read_state(eraser_options())
		),
		() => {
			if (brush_options()) {
				update_brush_options();
			}

			if (eraser_options()) {
				update_eraser_options();
			}
		}
	);

	legacy_pre_effect(
		() => (
			get(brush),
			get(selected_color),
			deep_read_state(brush_options())
		),
		() => {
			get(brush)?.set_brush_color((() => {
				let color_value;

				if (get(selected_color) === "auto") {
					const default_color = brush_options().colors.find((color) => Array.isArray(color)
						? color[0] === brush_options().default_color
						: color === brush_options().default_color) || brush_options().colors[0];

					color_value = Array.isArray(default_color) ? default_color[0] : default_color;
				} else {
					color_value = get(selected_color);
				}

				return color_value;
			})());
		}
	);

	legacy_pre_effect(() => (get(brush), get(selected_size)), () => {
		get(brush)?.set_brush_size(typeof get(selected_size) === "number" ? get(selected_size) : 25);
	});

	legacy_pre_effect(() => (get(brush), get(selected_eraser_size)), () => {
		get(brush)?.set_eraser_size(typeof get(selected_eraser_size) === "number" ? get(selected_eraser_size) : 25);
	});

	legacy_pre_effect(
		() => (
			deep_read_state(current_tool()),
			deep_read_state(background_image()),
			get(current_subtool),
			deep_read_state(sources())
		),
		() => {
			set(disable_click, current_tool() !== "image" || current_tool() === "image" && background_image() || current_tool() === "image" && get(current_subtool) === "webcam" || !sources().includes("upload"));
		}
	);

	legacy_pre_effect(() => (get(brush), get(preview)), () => {
		get(brush)?.preview_brush(get(preview));
	});

	legacy_pre_effect(() => (get(brush), get(selected_opacity)), () => {
		get(brush)?.set_brush_opacity(get(selected_opacity));
	});

	legacy_pre_effect(
		() => (
			deep_read_state(composite()),
			deep_read_state(background())
		),
		() => {
			add_image_from_url(composite() || background());
		}
	);

	legacy_pre_effect(() => (deep_read_state(layers())), () => {
		add_layers_from_url(layers());
	});

	legacy_pre_effect_reset();

	var $$exports = {
		antialias,
		get_blobs,
		add_image,
		add_image_from_url,
		add_layers_from_url,
		get changeable() {
			return changeable();
		},

		set changeable($$value) {
			changeable($$value);
			flushSync();
		},

		get sources() {
			return sources();
		},

		set sources($$value) {
			sources($$value);
			flushSync();
		},

		get transforms() {
			return transforms();
		},

		set transforms($$value) {
			transforms($$value);
			flushSync();
		},

		get canvas_size() {
			return canvas_size();
		},

		set canvas_size($$value) {
			canvas_size($$value);
			flushSync();
		},

		get is_dragging() {
			return is_dragging();
		},

		set is_dragging($$value) {
			is_dragging($$value);
			flushSync();
		},

		get background_image() {
			return background_image();
		},

		set background_image($$value) {
			background_image($$value);
			flushSync();
		},

		get brush_options() {
			return brush_options();
		},

		set brush_options($$value) {
			brush_options($$value);
			flushSync();
		},

		get eraser_options() {
			return eraser_options();
		},

		set eraser_options($$value) {
			eraser_options($$value);
			flushSync();
		},

		get fixed_canvas() {
			return fixed_canvas();
		},

		set fixed_canvas($$value) {
			fixed_canvas($$value);
			flushSync();
		},

		get root() {
			return root();
		},

		set root($$value) {
			root($$value);
			flushSync();
		},

		get i18n() {
			return i18n();
		},

		set i18n($$value) {
			i18n($$value);
			flushSync();
		},

		get upload() {
			return upload();
		},

		set upload($$value) {
			upload($$value);
			flushSync();
		},

		get composite() {
			return composite();
		},

		set composite($$value) {
			composite($$value);
			flushSync();
		},

		get layers() {
			return layers();
		},

		set layers($$value) {
			layers($$value);
			flushSync();
		},

		get background() {
			return background();
		},

		set background($$value) {
			background($$value);
			flushSync();
		},

		get border_region() {
			return border_region();
		},

		set border_region($$value) {
			border_region($$value);
			flushSync();
		},

		get layer_options() {
			return layer_options();
		},

		set layer_options($$value) {
			layer_options($$value);
			flushSync();
		},

		get current_tool() {
			return current_tool();
		},

		set current_tool($$value) {
			current_tool($$value);
			flushSync();
		},

		get webcam_options() {
			return webcam_options();
		},

		set webcam_options($$value) {
			webcam_options($$value);
			flushSync();
		},

		get show_download_button() {
			return show_download_button();
		},

		set show_download_button($$value) {
			show_download_button($$value);
			flushSync();
		},

		get theme_mode() {
			return theme_mode();
		},

		set theme_mode($$value) {
			theme_mode($$value);
			flushSync();
		},

		get full_history() {
			return full_history();
		},

		set full_history($$value) {
			full_history($$value);
			flushSync();
		},

		get has_drawn() {
			return has_drawn();
		},

		set has_drawn($$value) {
			has_drawn($$value);
			flushSync();
		},

		get can_undo() {
			return can_undo();
		},

		set can_undo($$value) {
			can_undo($$value);
			flushSync();
		}
	};

	init$1();

	var div = root_1$1();
	let classes;

	set_attribute(div, 'aria-label', "Click to upload or drop files");

	var node = child(div);

	{
		var consequent_4 = ($$anchor) => {
			var fragment = root_2$1();
			var node_1 = first_child(fragment);

			{
				var consequent = ($$anchor) => {
					Controls($$anchor, {
						get changeable() {
							return changeable();
						},

						get min_zoom() {
							return get(min_zoom);
						},

						get current_zoom() {
							return get(zoom_level);
						},

						get tool() {
							return current_tool();
						},
						can_save: true,
						get enable_download() {
							return show_download_button();
						},

						get can_undo() {
							return can_undo();
						},

						get can_redo() {
							return get(can_redo);
						},

						$$events: {
							set_zoom: (e) => handle_zoom_change(e.detail),
							zoom_in: () => zoom_in_out("in"),
							zoom_out: () => zoom_in_out("out"),
							remove_image: () => {
								dispatch("clear");
								get(editor).reset_canvas();
								handle_tool_change({ tool: "image" });
								background_image(false);
								has_drawn(false);
							},
							save: handle_save,
							pan: (e) => {
								handle_tool_change({ tool: "pan" });
							},
							download: () => handle_download(),
							undo: handle_undo,
							redo: handle_redo
						}
					});
				};

				if_block(node_1, ($$render) => {
					if (get(current_subtool) !== "crop") $$render(consequent);
				});
			}

			var node_2 = sibling(node_1, 2);

			{
				var consequent_1 = ($$anchor) => {
					Toolbar($$anchor, {
						get sources() {
							return sources();
						},

						get transforms() {
							return transforms();
						},

						get background() {
							return background_image();
						},

						get show_brush_size() {
							return get(brush_size_visible);
						},

						get show_brush_color() {
							return get(brush_color_visible);
						},

						get show_eraser_size() {
							return get(eraser_size_visible);
						},

						get brush_options() {
							return brush_options();
						},

						get eraser_options() {
							return eraser_options();
						},

						get tool() {
							return current_tool();
						},

						get subtool() {
							return get(current_subtool);
						},

						get selected_color() {
							return get(selected_color);
						},

						set selected_color($$value) {
							set(selected_color, $$value);
						},

						get selected_size() {
							return get(selected_size);
						},

						set selected_size($$value) {
							set(selected_size, $$value);
						},

						get selected_eraser_size() {
							return get(selected_eraser_size);
						},

						set selected_eraser_size($$value) {
							set(selected_eraser_size, $$value);
						},

						get selected_opacity() {
							return get(selected_opacity);
						},

						set selected_opacity($$value) {
							set(selected_opacity, $$value);
						},

						get preview() {
							return get(preview);
						},

						set preview($$value) {
							set(preview, $$value);
						},

						$$events: {
							tool_change: (e) => handle_tool_change(e.detail),
							subtool_change: (e) => handle_subtool_change(e.detail)
						},
						$$legacy: true
					});
				};

				if_block(node_2, ($$render) => {
					if (get(current_subtool) !== "crop") $$render(consequent_1);
				});
			}

			var node_3 = sibling(node_2, 2);

			{
				var consequent_2 = ($$anchor) => {
					var div_1 = root_5$1();
					var div_2 = child(div_1);
					var node_4 = child(div_2);

					Webcam$1(node_4, {
						get upload() {
							return upload();
						},

						get root() {
							return root();
						},
						streaming: false,
						mode: 'image',
						include_audio: false,
						get i18n() {
							return i18n();
						},

						get mirror_webcam() {
							return (
								deep_read_state(webcam_options()),
								untrack(() => webcam_options().mirror)
							);
						},

						get webcam_constraints() {
							return (
								deep_read_state(webcam_options()),
								untrack(() => webcam_options().constraints)
							);
						},

						$$events: {
							capture: handle_capture,
							error($$arg) {
								bubble_event.call(this, $$props, $$arg);
							},

							drag($$arg) {
								bubble_event.call(this, $$props, $$arg);
							}
						}
					});

					reset(div_2);
					reset(div_1);
					append($$anchor, div_1);
				};

				if_block(node_3, ($$render) => {
					if (current_tool() === "image" && get(current_subtool) === "webcam") $$render(consequent_2);
				});
			}

			var node_5 = sibling(node_3, 2);

			{
				var consequent_3 = ($$anchor) => {
					SecondaryToolbar($$anchor, {
						get enable_additional_layers() {
							return (
								deep_read_state(layer_options()),
								untrack(() => layer_options().allow_additional_layers)
							);
						},

						get layers() {
							return (get(editor), untrack(() => get(editor).layers));
						},

						$$events: {
							new_layer: () => {
								get(editor).add_layer();
							},

							change_layer: (e) => {
								get(editor).set_layer(e.detail);

								if (current_tool() === "draw") {
									handle_tool_change({ tool: "draw" });
								}
							},

							move_layer: (e) => {
								get(editor).move_layer(e.detail.id, e.detail.direction);
							},

							delete_layer: (e) => {
								get(editor).delete_layer(e.detail);
							},

							toggle_layer_visibility: (e) => {
								get(editor).toggle_layer_visibility(e.detail);
							}
						}
					});
				};

				if_block(node_5, ($$render) => {
					if ((
						get(current_subtool),
						deep_read_state(layer_options()),
						untrack(() => get(current_subtool) !== "crop" && !layer_options().disabled)
					)) $$render(consequent_3);
				});
			}

			append($$anchor, fragment);
		};

		if_block(node, ($$render) => {
			if (get(ready)) $$render(consequent_4);
		});
	}

	var div_3 = sibling(node, 2);
	let classes_1;

	bind_this(div_3, ($$value) => set(pixi_target, $$value), () => get(pixi_target));

	var div_4 = sibling(div_3, 2);
	let classes_2;

	bind_this(div_4, ($$value) => set(pixi_target_crop, $$value), () => get(pixi_target_crop));

	var node_6 = sibling(div_4, 2);

	{
		var consequent_5 = ($$anchor) => {
			var div_5 = root_7$1();
			var node_7 = child(div_5);

			IconButton(node_7, {
				get Icon() {
					return Check;
				},
				label: 'Confirm crop',
				show_label: true,
				size: 'large',
				padded: true,
				color: 'white',
				background: 'var(--color-green-500)',
				label_position: 'right',
				onclick: handle_crop_confirm
			});

			reset(div_5);
			append($$anchor, div_5);
		};

		if_block(node_6, ($$render) => {
			if (get(current_subtool) === "crop") $$render(consequent_5);
		});
	}

	var node_8 = sibling(node_6, 2);

	slot(node_8, $$props, 'default', {}, null);
	reset(div);

	action(div, ($$node, $$action_arg) => drag?.($$node, $$action_arg), () => ({
		on_drag_change: (dragging) => is_dragging(dragging),
		on_files: handle_files,
		accepted_types: "image/*",
		disable_click: get(disable_click)
	}));

	template_effect(() => {
		classes = set_class(div, 1, 'image-container svelte-6fnl1z', null, classes, { 'dark-bg': get(current_subtool) === "crop" });
		classes_1 = set_class(div_3, 1, 'pixi-target svelte-6fnl1z', null, classes_1, { visible: get(current_subtool) !== "crop" });
		classes_2 = set_class(div_4, 1, 'pixi-target-crop svelte-6fnl1z', null, classes_2, { visible: get(current_subtool) === "crop" });
	});

	append($$anchor, div);
	bind_prop($$props, 'antialias', antialias);
	bind_prop($$props, 'get_blobs', get_blobs);
	bind_prop($$props, 'add_image', add_image);
	bind_prop($$props, 'add_image_from_url', add_image_from_url);
	bind_prop($$props, 'add_layers_from_url', add_layers_from_url);

	return pop($$exports);
}

const RE_HEADING = /^(#\s*)(.+)$/m;
function inject(text) {
  const trimmed_text = text.trim();
  const heading_match = trimmed_text.match(RE_HEADING);
  if (!heading_match) {
    return [false, trimmed_text || false];
  }
  const [full_match, , heading_content] = heading_match;
  const _heading = heading_content.trim();
  if (trimmed_text === full_match) {
    return [_heading, false];
  }
  const heading_end_index = heading_match.index !== void 0 ? heading_match.index + full_match.length : 0;
  const remaining_text = trimmed_text.substring(heading_end_index).trim();
  const _paragraph = remaining_text || false;
  return [_heading, _paragraph];
}

var root_6 = from_html(`<h2 class="svelte-b4yllx"> </h2>`);
var root_7 = from_html(`<p class="svelte-b4yllx"> </p>`);
var root_5 = from_html(`<!> <!>`, 1);
var root_8 = from_html(`<div>Upload an image</div>`);
var root_9 = from_html(`<div class="or svelte-b4yllx">or</div>`);
var root_10 = from_html(`<div>select the draw tool to start</div>`);
var root_3 = from_html(`<div class="empty wrap svelte-b4yllx"><!> <!> <!></div>`);
var root_1 = from_html(`<!> <!>`, 1);

function InteractiveImageEditor($$anchor, $$props) {
	push($$props, false);

	const heading = mutable_source();
	const paragraph = mutable_source();
	let brush = prop($$props, 'brush', 12);
	let eraser = prop($$props, 'eraser', 12);
	let sources = prop($$props, 'sources', 12);
	let i18n = prop($$props, 'i18n', 12);
	let root = prop($$props, 'root', 12);
	let label = prop($$props, 'label', 12, undefined);
	let show_label = prop($$props, 'show_label', 12);
	let changeable = prop($$props, 'changeable', 12, false);
	let theme_mode = prop($$props, 'theme_mode', 12);
	let layers = prop($$props, 'layers', 12);
	let composite = prop($$props, 'composite', 12);
	let background = prop($$props, 'background', 12);
	let layer_options = prop($$props, 'layer_options', 12);
	let transforms = prop($$props, 'transforms', 12);
	let accept_blobs = prop($$props, 'accept_blobs', 12);
	let canvas_size = prop($$props, 'canvas_size', 12);
	let fixed_canvas = prop($$props, 'fixed_canvas', 12, false);
	let realtime = prop($$props, 'realtime', 12);
	let upload = prop($$props, 'upload', 12);
	let is_dragging = prop($$props, 'is_dragging', 12);
	let placeholder = prop($$props, 'placeholder', 12, undefined);
	let border_region = prop($$props, 'border_region', 12);
	let full_history = prop($$props, 'full_history', 12, null);
	let webcam_options = prop($$props, 'webcam_options', 12);
	let show_download_button = prop($$props, 'show_download_button', 12, false);
	const dispatch = createEventDispatcher();
	let editor = mutable_source();
	let has_drawn = mutable_source(false);

	function is_not_null(o) {
		return !!o;
	}

	function is_file_data(o) {
		return !!o;
	}

	async function get_data() {
		let blobs;

		try {
			blobs = await get(editor).get_blobs();
		} catch(e) {
			return { background: null, layers: [], composite: null };
		}

		const bg = blobs.background
			? upload()(await prepare_files([new File([blobs.background], "background.png")]), root())
			: Promise.resolve(null);

		const layers = blobs.layers.filter(is_not_null).map(async (blob, i) => upload()(await prepare_files([new File([blob], `layer_${i}.png`)]), root()));

		const composite = blobs.composite
			? upload()(await prepare_files([new File([blobs.composite], "composite.png")]), root())
			: Promise.resolve(null);

		const [background, composite_, ...layers_] = await Promise.all([bg, composite, ...layers]);

		return {
			background: Array.isArray(background) ? background[0] : background,
			layers: layers_.flatMap((layer) => Array.isArray(layer) ? layer : [layer]).filter(is_file_data),
			composite: Array.isArray(composite_) ? composite_[0] : composite_
		};
	}

	function handle_value(value) {
		if (!get(editor)) return;

		if (value == null) {
			get(editor).handle_remove();
			dispatch("receive_null");
		}
	}

	let background_image = mutable_source(false);
	let can_undo = mutable_source();
	let image_id = prop($$props, 'image_id', 12, null);

	function nextframe() {
		return new Promise((resolve) => setTimeout(() => resolve(), 30));
	}

	let uploading = false;
	let pending = false;

	async function handle_change(e) {
		if (!realtime()) return;

		if (uploading) {
			pending = true;

			return;
		}

		uploading = true;
		await nextframe();

		const blobs = await get(editor).get_blobs();
		const images = [];
		let id = Math.random().toString(36).substring(2);

		if (blobs.background) images.push([
			id,
			"background",
			new File([blobs.background], "background.png"),
			null
		]);

		if (blobs.composite) images.push([
			id,
			"composite",
			new File([blobs.composite], "composite.png"),
			null
		]);

		blobs.layers.forEach((layer, i) => {
			if (layer) images.push([id, `layer`, new File([layer], `layer_${i}.png`), i]);
		});

		await Promise.all(images.map(async ([image_id, type, data, index]) => {
			return accept_blobs()({
				binary: true,
				data: { file: data, id: image_id, type, index }
			});
		}));

		image_id(id);
		dispatch("change");
		await nextframe();
		uploading = false;

		if (pending) {
			pending = false;
			uploading = false;
			handle_change();
		}
	}

	let current_tool = mutable_source();

	legacy_pre_effect(() => (get(background_image)), () => {
		if (get(background_image)) dispatch("upload");
	});

	legacy_pre_effect(
		() => (
			deep_read_state(layers()),
			deep_read_state(composite()),
			deep_read_state(background())
		),
		() => {
			handle_value({
				layers: layers(),
				composite: composite(),
				background: background()
			});
		}
	);

	legacy_pre_effect(
		() => (
			get(heading),
			get(paragraph),
			deep_read_state(placeholder()),
			inject
		),
		() => {
			(($$value) => {
				var $$array = to_array($$value, 2);

				set(heading, $$array[0]);
				set(paragraph, $$array[1]);
			})(placeholder() ? inject(placeholder()) : [false, false]);
		}
	);

	legacy_pre_effect_reset();

	var $$exports = {
		get_data,
		get brush() {
			return brush();
		},

		set brush($$value) {
			brush($$value);
			flushSync();
		},

		get eraser() {
			return eraser();
		},

		set eraser($$value) {
			eraser($$value);
			flushSync();
		},

		get sources() {
			return sources();
		},

		set sources($$value) {
			sources($$value);
			flushSync();
		},

		get i18n() {
			return i18n();
		},

		set i18n($$value) {
			i18n($$value);
			flushSync();
		},

		get root() {
			return root();
		},

		set root($$value) {
			root($$value);
			flushSync();
		},

		get label() {
			return label();
		},

		set label($$value) {
			label($$value);
			flushSync();
		},

		get show_label() {
			return show_label();
		},

		set show_label($$value) {
			show_label($$value);
			flushSync();
		},

		get changeable() {
			return changeable();
		},

		set changeable($$value) {
			changeable($$value);
			flushSync();
		},

		get theme_mode() {
			return theme_mode();
		},

		set theme_mode($$value) {
			theme_mode($$value);
			flushSync();
		},

		get layers() {
			return layers();
		},

		set layers($$value) {
			layers($$value);
			flushSync();
		},

		get composite() {
			return composite();
		},

		set composite($$value) {
			composite($$value);
			flushSync();
		},

		get background() {
			return background();
		},

		set background($$value) {
			background($$value);
			flushSync();
		},

		get layer_options() {
			return layer_options();
		},

		set layer_options($$value) {
			layer_options($$value);
			flushSync();
		},

		get transforms() {
			return transforms();
		},

		set transforms($$value) {
			transforms($$value);
			flushSync();
		},

		get accept_blobs() {
			return accept_blobs();
		},

		set accept_blobs($$value) {
			accept_blobs($$value);
			flushSync();
		},

		get canvas_size() {
			return canvas_size();
		},

		set canvas_size($$value) {
			canvas_size($$value);
			flushSync();
		},

		get fixed_canvas() {
			return fixed_canvas();
		},

		set fixed_canvas($$value) {
			fixed_canvas($$value);
			flushSync();
		},

		get realtime() {
			return realtime();
		},

		set realtime($$value) {
			realtime($$value);
			flushSync();
		},

		get upload() {
			return upload();
		},

		set upload($$value) {
			upload($$value);
			flushSync();
		},

		get is_dragging() {
			return is_dragging();
		},

		set is_dragging($$value) {
			is_dragging($$value);
			flushSync();
		},

		get placeholder() {
			return placeholder();
		},

		set placeholder($$value) {
			placeholder($$value);
			flushSync();
		},

		get border_region() {
			return border_region();
		},

		set border_region($$value) {
			border_region($$value);
			flushSync();
		},

		get full_history() {
			return full_history();
		},

		set full_history($$value) {
			full_history($$value);
			flushSync();
		},

		get webcam_options() {
			return webcam_options();
		},

		set webcam_options($$value) {
			webcam_options($$value);
			flushSync();
		},

		get show_download_button() {
			return show_download_button();
		},

		set show_download_button($$value) {
			show_download_button($$value);
			flushSync();
		},

		get image_id() {
			return image_id();
		},

		set image_id($$value) {
			image_id($$value);
			flushSync();
		}
	};

	init$1();

	var fragment = root_1();
	var node = first_child(fragment);

	{
		let $0 = derived_safe_equal(() => (
			deep_read_state(label()),
			deep_read_state(i18n()),
			untrack(() => label() || i18n()("image.image"))
		));

		BlockLabel(node, {
			get show_label() {
				return show_label();
			},

			get Icon() {
				return Image$1;
			},

			get label() {
				return get($0);
			}
		});
	}

	var node_1 = sibling(node, 2);

	bind_this(
		ImageEditor_1(node_1, {
			get transforms() {
				return transforms();
			},

			get composite() {
				return composite();
			},

			get layers() {
				return layers();
			},

			get background() {
				return background();
			},

			get canvas_size() {
				return canvas_size();
			},

			get changeable() {
				return changeable();
			},

			get sources() {
				return sources();
			},

			get brush_options() {
				return brush();
			},

			get eraser_options() {
				return eraser();
			},

			get fixed_canvas() {
				return fixed_canvas();
			},

			get border_region() {
				return border_region();
			},

			get layer_options() {
				return layer_options();
			},

			get i18n() {
				return i18n();
			},

			get root() {
				return root();
			},

			get upload() {
				return upload();
			},

			get webcam_options() {
				return webcam_options();
			},

			get show_download_button() {
				return show_download_button();
			},

			get theme_mode() {
				return theme_mode();
			},

			get background_image() {
				return get(background_image);
			},

			set background_image($$value) {
				set(background_image, $$value);
			},

			get current_tool() {
				return get(current_tool);
			},

			set current_tool($$value) {
				set(current_tool, $$value);
			},

			get is_dragging() {
				return is_dragging();
			},

			set is_dragging($$value) {
				is_dragging($$value);
			},

			get has_drawn() {
				return get(has_drawn);
			},

			set has_drawn($$value) {
				set(has_drawn, $$value);
			},

			get can_undo() {
				return get(can_undo);
			},

			set can_undo($$value) {
				set(can_undo, $$value);
			},

			get full_history() {
				return full_history();
			},

			set full_history($$value) {
				full_history($$value);
			},

			$$events: {
				history($$arg) {
					bubble_event.call(this, $$props, $$arg);
				},

				save($$arg) {
					bubble_event.call(this, $$props, $$arg);
				},

				input($$arg) {
					bubble_event.call(this, $$props, $$arg);
				},
				change: handle_change,
				clear: () => dispatch("clear"),
				download_error($$arg) {
					bubble_event.call(this, $$props, $$arg);
				}
			},

			children: ($$anchor, $$slotProps) => {
				var fragment_1 = comment();
				var node_2 = first_child(fragment_1);

				{
					var consequent_6 = ($$anchor) => {
						var div = root_3();
						var node_3 = child(div);

						{
							var consequent_3 = ($$anchor) => {
								var fragment_2 = comment();
								var node_4 = first_child(fragment_2);

								{
									var consequent_2 = ($$anchor) => {
										var fragment_3 = root_5();
										var node_5 = first_child(fragment_3);

										{
											var consequent = ($$anchor) => {
												var h2 = root_6();
												var text = child(h2, true);

												reset(h2);
												template_effect(() => set_text(text, get(heading)));
												append($$anchor, h2);
											};

											if_block(node_5, ($$render) => {
												if (get(heading)) $$render(consequent);
											});
										}

										var node_6 = sibling(node_5, 2);

										{
											var consequent_1 = ($$anchor) => {
												var p = root_7();
												var text_1 = child(p, true);

												reset(p);
												template_effect(() => set_text(text_1, get(paragraph)));
												append($$anchor, p);
											};

											if_block(node_6, ($$render) => {
												if (get(paragraph)) $$render(consequent_1);
											});
										}

										append($$anchor, fragment_3);
									};

									var alternate = ($$anchor) => {
										var div_1 = root_8();

										append($$anchor, div_1);
									};

									if_block(node_4, ($$render) => {
										if (get(heading) || get(paragraph)) $$render(consequent_2); else $$render(alternate, false);
									});
								}

								append($$anchor, fragment_2);
							};

							if_block(node_3, ($$render) => {
								if ((
									deep_read_state(sources()),
									untrack(() => sources() && sources().length)
								)) $$render(consequent_3);
							});
						}

						var node_7 = sibling(node_3, 2);

						{
							var consequent_4 = ($$anchor) => {
								var div_2 = root_9();

								append($$anchor, div_2);
							};

							if_block(node_7, ($$render) => {
								if ((
									deep_read_state(sources()),
									deep_read_state(brush()),
									deep_read_state(placeholder()),
									untrack(() => sources() && sources().length && brush() && !placeholder())
								)) $$render(consequent_4);
							});
						}

						var node_8 = sibling(node_7, 2);

						{
							var consequent_5 = ($$anchor) => {
								var div_3 = root_10();

								append($$anchor, div_3);
							};

							if_block(node_8, ($$render) => {
								if (brush() && !placeholder()) $$render(consequent_5);
							});
						}

						reset(div);
						append($$anchor, div);
					};

					if_block(node_2, ($$render) => {
						if (get(current_tool) === "image" && !get(can_undo)) $$render(consequent_6);
					});
				}

				append($$anchor, fragment_1);
			},
			$$slots: { default: true },
			$$legacy: true
		}),
		($$value) => set(editor, $$value),
		() => get(editor)
	);

	append($$anchor, fragment);
	bind_prop($$props, 'get_data', get_data);

	return pop($$exports);
}

var root_2 = from_html(`<!> <!>`, 1);
var root_4 = from_html(`<!> <!>`, 1);

function Index($$anchor, $$props) {
	push($$props, true);

	let props = rest_props($$props, ['$$slots', '$$events', '$$legacy']);

	class ImageEditorGradio extends Gradio {
		async get_data() {
			const data = await super.get_data();
			const value = await _get_value();

			return { ...data, value };
		}
	}

	const gradio = new ImageEditorGradio(props, {
		server: { accept_blobs: () => {} },
		buttons: [],
		height: 350,
		border_region: 0
	});

	let editor_instance;
	let image_id = state(null);
	let has_run_change = false;
	let has_run_input = false;

	async function _get_value() {
		if (get(image_id)) {
			const val = { id: get(image_id) };

			set(image_id, null);

			return val;
		}

		const blobs = await editor_instance.get_data();

		return blobs;
	}

	let is_dragging;

	// function handle_change(): void {
	// 	if (gradio.props.value) gradio.dispatch("change");
	// }
	function handle_save() {
		gradio.dispatch("apply");
	}

	// function handle_history_change(): void {
	// 	gradio.dispatch("change");
	// 	gradio.dispatch("input");
	// }
	let has_value = user_derived(() => gradio.props.value?.background || gradio.props.value?.layers?.length || gradio.props.value?.composite);

	let normalised_background = user_derived(() => gradio.props.value?.background ? new FileData(gradio.props.value.background) : null);
	let normalised_composite = user_derived(() => gradio.props.value?.composite ? new FileData(gradio.props.value.composite) : null);
	let normalised_layers = user_derived(() => gradio.props.value?.layers?.map((layer) => new FileData(layer)) || []);
	var fragment = comment();
	var node = first_child(fragment);

	{
		var consequent = ($$anchor) => {
			{
				let $0 = user_derived(() => is_dragging ? "focus" : "base");

				Block($$anchor, {
					get visible() {
						return gradio.shared.visible;
					},
					variant: "solid",
					get border_mode() {
						return get($0);
					},
					padding: false,
					get elem_id() {
						return gradio.shared.elem_id;
					},

					get elem_classes() {
						return gradio.shared.elem_classes;
					},

					get height() {
						return gradio.props.height;
					},

					get width() {
						return gradio.props.width;
					},
					allow_overflow: true,
					overflow_behavior: 'visible',
					get container() {
						return gradio.shared.container;
					},

					get scale() {
						return gradio.shared.scale;
					},

					get min_width() {
						return gradio.shared.min_width;
					},

					children: ($$anchor, $$slotProps) => {
						var fragment_2 = root_2();
						var node_1 = first_child(fragment_2);

						Static(node_1, spread_props(
							{
								get autoscroll() {
									return gradio.shared.autoscroll;
								},

								get i18n() {
									return gradio.i18n;
								}
							},
							() => gradio.shared.loading_status,
							{
								on_clear_status: () => gradio.dispatch("clear_status", gradio.shared.loading_status)
							}
						));

						var node_2 = sibling(node_1, 2);

						{
							let $0 = user_derived(() => gradio.props.value?.composite || null);

							ImagePreview(node_2, {
								get value() {
									return get($0);
								},

								get label() {
									return gradio.shared.label;
								},

								get show_label() {
									return gradio.shared.show_label;
								},

								get buttons() {
									return gradio.props.buttons;
								},

								get selectable() {
									return gradio.props._selectable;
								},

								get i18n() {
									return gradio.i18n;
								},

								$$events: {
									select: ({ detail }) => gradio.dispatch("select", detail),
									share: ({ detail }) => gradio.dispatch("share", detail),
									error: ({ detail }) => gradio.dispatch("error", detail)
								}
							});
						}

						append($$anchor, fragment_2);
					},
					$$slots: { default: true }
				});
			}
		};

		var alternate = ($$anchor) => {
			{
				let $0 = user_derived(() => get(has_value) ? "solid" : "dashed");
				let $1 = user_derived(() => is_dragging ? "focus" : "base");

				Block($$anchor, {
					get visible() {
						return gradio.shared.visible;
					},

					get variant() {
						return get($0);
					},

					get border_mode() {
						return get($1);
					},
					padding: false,
					get elem_id() {
						return gradio.shared.elem_id;
					},

					get elem_classes() {
						return gradio.shared.elem_classes;
					},

					get height() {
						return gradio.props.height;
					},

					get width() {
						return gradio.props.width;
					},
					allow_overflow: true,
					overflow_behavior: 'visible',
					get container() {
						return gradio.shared.container;
					},

					get scale() {
						return gradio.shared.scale;
					},

					get min_width() {
						return gradio.shared.min_width;
					},

					children: ($$anchor, $$slotProps) => {
						var fragment_4 = root_4();
						var node_3 = first_child(fragment_4);

						Static(node_3, spread_props(
							{
								get autoscroll() {
									return gradio.shared.autoscroll;
								},

								get i18n() {
									return gradio.i18n;
								}
							},
							() => gradio.shared.loading_status,
							{
								on_clear_status: () => gradio.dispatch("clear_status", gradio.shared.loading_status)
							}
						));

						var node_4 = sibling(node_3, 2);

						{
							let $0 = user_derived(() => gradio.shared.attached_events.includes("apply"));
							let $1 = user_derived(() => gradio.shared.attached_events.includes("change") || gradio.shared.attached_events.includes("input"));
							let $2 = user_derived(() => gradio.props.buttons === null ? true : gradio.props.buttons.includes("download"));

							bind_this(
								InteractiveImageEditor(node_4, {
									get border_region() {
										return gradio.props.border_region;
									},

									get canvas_size() {
										return gradio.props.canvas_size;
									},

									get layers() {
										return get(normalised_layers);
									},

									get composite() {
										return get(normalised_composite);
									},

									get background() {
										return get(normalised_background);
									},

									get root() {
										return gradio.shared.root;
									},

									get sources() {
										return gradio.props.sources;
									},

									get label() {
										return gradio.shared.label;
									},

									get show_label() {
										return gradio.shared.show_label;
									},

									get fixed_canvas() {
										return gradio.props.fixed_canvas;
									},

									get brush() {
										return gradio.props.brush;
									},

									get eraser() {
										return gradio.props.eraser;
									},

									get changeable() {
										return get($0);
									},

									get realtime() {
										return get($1);
									},

									get i18n() {
										return gradio.i18n;
									},

									get transforms() {
										return gradio.props.transforms;
									},

									get accept_blobs() {
										return gradio.shared.server.accept_blobs;
									},

									get layer_options() {
										return gradio.props.layers;
									},
									upload: (...args) => gradio.shared.client.upload(...args),
									get placeholder() {
										return gradio.props.placeholder;
									},

									get webcam_options() {
										return gradio.props.webcam_options;
									},

									get show_download_button() {
										return get($2);
									},

									get theme_mode() {
										return gradio.shared.theme_mode;
									},

									get is_dragging() {
										return is_dragging;
									},

									set is_dragging($$value) {
										is_dragging = $$value;
									},

									get image_id() {
										return get(image_id);
									},

									set image_id($$value) {
										set(image_id, $$value, true);
									},

									$$events: {
										input: () => {
											if (!has_run_input) {
												has_run_input = true;
											} else {
												gradio.dispatch("input");
											}
										},
										save: (e) => handle_save(),
										edit: () => gradio.dispatch("edit"),
										clear: () => gradio.dispatch("clear"),
										drag: ({ detail }) => is_dragging = detail,
										upload: () => gradio.dispatch("upload"),
										share: ({ detail }) => gradio.dispatch("share", detail),
										error: [
											({ detail }) => {
												gradio.shared.loading_status = gradio.shared.loading_status || {};
												gradio.shared.loading_status.status = "error";
												gradio.dispatch("error", detail);
											},

											function ($$arg) {
												bubble_event.call(this, $$props, $$arg);
											}
										],
										receive_null: () => gradio.props.value = { background: null, layers: [], composite: null },
										change: () => {
											if (!has_run_change) {
												has_run_change = true;
											} else {
												gradio.dispatch("change");
											}
										},
										download_error: (e) => gradio.dispatch("error", e.detail)
									}
								}),
								($$value) => editor_instance = $$value,
								() => editor_instance
							);
						}

						append($$anchor, fragment_4);
					},
					$$slots: { default: true }
				});
			}
		};

		if_block(node, ($$render) => {
			if (!gradio.shared.interactive) $$render(consequent); else $$render(alternate, false);
		});
	}

	append($$anchor, fragment);
	pop();
}

const Index$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
	__proto__: null,
	default: Index
}, Symbol.toStringTag, { value: 'Module' }));

export { UPDATE_BLEND as $, AbstractRenderer as A, BufferUsage as B, Container as C, DOMAdapter as D, ExtensionType as E, Filter as F, GCManagedHash as G, BigPool as H, getGlobalBounds as I, Bounds as J, TexturePool as K, FilterEffect as L, Matrix as M, Sprite as N, getAttributeInfoFromFormat as O, Point as P, unsafeEvalSupported as Q, RendererType as R, State as S, Ticker as T, UPDATE_PRIORITY as U, GlobalResourceRegistry as V, Rectangle as W, SystemRunner as X, multiplyColors as Y, UPDATE_VISIBLE as Z, UPDATE_COLOR as _, EventEmitter as a, TextureStyle as a0, Color as a1, RenderGroup as a2, getLocalBounds as a3, RenderTexture as a4, VERSION as a5, deprecation as a6, v8_0_0 as a7, RendererInitHook as a8, Geometry as a9, checkMaxIfStatementsInShader as aa, compileHighShaderGlProgram as ab, colorBitGl as ac, roundPixelsBitGl as ad, generateTextureBatchBitGl as ae, getBatchSamplersUniformGroup as af, nextPow2 as ag, CanvasTextMetrics as ah, fontStringFromTextStyle as ai, getCanvasFillStyle as aj, BatchableGraphics as ak, getAdjustedBlendModeBlend as al, ViewableBuffer as am, BitmapFontManager as an, getBitmapTextLayout as ao, Cache as ap, Graphics as aq, updateQuadBounds as ar, TextStyle as as, GraphicsContextSystem as at, Index$1 as au, STENCIL_MODES as b, Buffer as c, BindGroup as d, extensions as e, fastCopy as f, getTextureBatchBindGroup as g, createIdFromString as h, CLEAR as i, CanvasSource as j, TextureSource as k, UniformGroup as l, CanvasPool as m, compileHighShaderGpuProgram as n, colorBit as o, roundPixelsBit as p, generateTextureBatchBit as q, removeItems as r, Shader as s, Texture as t, uid as u, TextureMatrix as v, warn as w, GpuProgram as x, GlProgram as y, DefaultBatcher as z };
//# sourceMappingURL=Index-CvN5jvte.js.map
