{"version":3,"file":"pintura-DzcOKEb-.js","sources":["../../../app/frontend/assets/pintura/pintura.js"],"sourcesContent":["/*!\n* Pintura v8.88.3 \n* (c) 2018-2024 PQINA Inc. - All Rights Reserved\n* License: https://pqina.nl/pintura/license/\n*/\n/* eslint-disable */\n\nconst JFIF_MARKER = 0xffe0;\nconst EXIF_MARKER = 0xffe1;\nconst SOS_MARKER = 0xffda;\nconst XMP_MARKER = 0xffe1;\nconst MPF_MARKER = 0xffe2;\nconst APP2_MARKER = 0xffe2;\nconst Markers = [\n { value: JFIF_MARKER, name: 'jfif' },\n { value: SOS_MARKER, name: 'sos' },\n { value: EXIF_MARKER, subvalue: 0x4578, name: 'exif' },\n { value: XMP_MARKER, subvalue: 0x6874, name: 'xmp' },\n { value: MPF_MARKER, subvalue: 0x4d50, name: 'mpf' },\n { value: APP2_MARKER, name: 'app2' },\n];\nconst JPEG_SOI_MARKER = 0xffd8; // start of JPEG\nconst JPEG_MARKER_PREFIX = 0xff;\nvar dataViewGetApplicationMarkers = (view) => {\n // If no SOI marker exit here because we're not going to find the APP1 header in a non-jpeg file\n if (view.getUint16(0) !== JPEG_SOI_MARKER)\n return undefined;\n const length = view.byteLength; // cache the length here\n let offset = 2; // start at 2 as we skip the SOI marker\n let marker; // this will hold the current marker\n // resulting markers\n let res = undefined;\n while (offset < length) {\n // test if marker is valid JPEG marker (starts with ff)\n if (view.getUint8(offset) !== JPEG_MARKER_PREFIX)\n break;\n // let's read the full marker\n marker = view.getUint16(offset);\n // read marker if included in marker types\n const markerDescription = Markers.find((descriptor) => {\n if (descriptor.value === marker) {\n if (descriptor.subvalue) {\n return descriptor.subvalue === view.getUint16(offset + 2 + 2);\n }\n return true;\n }\n return false;\n });\n if (markerDescription) {\n const { name } = markerDescription;\n if (!res)\n res = {};\n // prevent overwriting by double markers\n if (!res[name]) {\n res[name] = {\n offset,\n size: view.getUint16(offset + 2),\n };\n }\n }\n // Image stream starts here, no markers found\n if (marker === SOS_MARKER)\n break;\n // next offset is 2 to skip over marker type and then we add marker data size to skip to next marker\n offset += 2 + view.getUint16(offset + 2);\n }\n // no APP markers found\n return res;\n};\n\nconst APP1_MARKER = 0xffe1;\nconst APP1_EXIF_IDENTIFIER = 0x45786966;\nconst TIFF_MARKER = 0x002a;\nconst BYTE_ALIGN_MOTOROLA = 0x4d4d;\nconst BYTE_ALIGN_INTEL = 0x4949;\n// offset = start of APP1_MARKER\nvar dataViewGetExifTags = (view, offset) => {\n // If no APP1 marker exit here because we're not going to find the EXIF id header outside of APP1\n if (view.getUint16(offset) !== APP1_MARKER)\n return undefined;\n // get marker size\n const size = view.getUint16(offset + 2); // 14197\n // Let's skip over app1 marker and size marker (2 + 2 bytes)\n offset += 4;\n // We're now at the EXIF header marker (we'll only check the first 4 bytes, reads \"exif\"), if not there, exit\n if (view.getUint32(offset) !== APP1_EXIF_IDENTIFIER)\n return undefined;\n // Let's skip over 6 byte EXIF marker\n offset += 6;\n // Read byte alignment\n const byteAlignment = view.getUint16(offset);\n if (byteAlignment !== BYTE_ALIGN_INTEL && byteAlignment !== BYTE_ALIGN_MOTOROLA)\n return undefined;\n const storedAsLittleEndian = byteAlignment === BYTE_ALIGN_INTEL;\n // Skip over byte alignment\n offset += 2;\n // Test if valid tiff marker data, should always be 0x002a\n if (view.getUint16(offset, storedAsLittleEndian) !== TIFF_MARKER)\n return undefined;\n // Skip to first IDF, position of IDF is read after tiff marker (offset 2)\n offset += view.getUint32(offset + 2, storedAsLittleEndian);\n // helper method to find tag offset by marker\n const getTagOffsets = (marker) => {\n const offsets = [];\n let i = offset;\n const max = Math.min(view.byteLength, offset + size - 16);\n for (; i < max; i += 12) {\n const tagOffset = i;\n // see if is match, if not, next entry\n if (view.getUint16(tagOffset, storedAsLittleEndian) !== marker)\n continue;\n // add offset\n offsets.push(tagOffset);\n }\n return offsets;\n };\n return {\n read: (address) => {\n const tagOffsets = getTagOffsets(address);\n if (!tagOffsets.length)\n return undefined;\n // only return first found tag\n return view.getUint16(tagOffsets[0] + 8, storedAsLittleEndian);\n },\n write: (address, value) => {\n const tagOffsets = getTagOffsets(address);\n if (!tagOffsets.length)\n return false;\n // overwrite all found tags (sometimes images can have multiple tags with the same value, let's make sure they're all set)\n tagOffsets.forEach((offset) => view.setUint16(offset + 8, value, storedAsLittleEndian));\n return true;\n },\n };\n};\n\nconst ORIENTATION_TAG = 0x0112;\nvar arrayBufferImageExif = (data, key, value) => {\n // no data, no go!\n if (!data)\n return;\n const view = new DataView(data);\n // Get app1 header offset\n const markers = dataViewGetApplicationMarkers(view);\n if (!markers || !markers.exif)\n return;\n // Get EXIF tags read/writer\n const tags = dataViewGetExifTags(view, markers.exif.offset);\n if (!tags)\n return;\n // Read the exif orientation marker\n return value === undefined ? tags.read(key) : tags.write(key, value);\n};\n\nconst backup = '__pqina_webapi__';\nvar getNativeAPIRef = (API) => (window[backup] ? window[backup][API] : window[API]);\n\nvar noop$1 = (...args) => { };\n\nconst FileReaderDataFormat = {\n ArrayBuffer: 'readAsArrayBuffer',\n};\nvar readFile = (file, onprogress = noop$1, options = {}) => new Promise((resolve, reject) => {\n const { dataFormat = FileReaderDataFormat.ArrayBuffer } = options;\n const reader = new (getNativeAPIRef('FileReader'))();\n reader.onload = () => resolve(reader.result);\n reader.onerror = () => reject(reader.error);\n reader.onprogress = onprogress;\n reader[dataFormat](file);\n});\n\nvar blobReadSection = async (blob, slice = [0, blob.size], onprogress) => (await readFile(blob.slice(...slice), onprogress));\n\nvar getImageOrientationFromFile = async (file, onprogress) => {\n // 64 * 4096 should be plenty to find extract header\n // Exif metadata are restricted in size to 64 kB in JPEG images because\n // according to the specification this information must be contained within a single JPEG APP1 segment.\n const head = await blobReadSection(file, [0, 64 * 4096], onprogress);\n return arrayBufferImageExif(head, ORIENTATION_TAG) || 1;\n};\n\nlet result$d = null;\nvar isBrowser = () => {\n if (result$d === null)\n result$d = typeof window !== 'undefined' && typeof window.document !== 'undefined';\n return result$d;\n};\n\nlet result$c = null;\nvar canOrientImages = () => new Promise((resolve) => {\n if (result$c === null) {\n // 2x1 pixel image 90CW rotated with orientation EXIF header\n const testSrc = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4QA6RXhpZgAATU0AKgAAAAgAAwESAAMAAAABAAYAAAEoAAMAAAABAAIAAAITAAMAAAABAAEAAAAAAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////wAALCAABAAIBASIA/8QAJgABAAAAAAAAAAAAAAAAAAAAAxABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAAPwBH/9k=';\n let testImage = isBrowser() ? new Image() : {};\n testImage.onload = () => {\n // should correct orientation if is presented in landscape,\n // in which case the browser doesn't autocorrect\n result$c = testImage.naturalWidth === 1;\n testImage = undefined;\n resolve(result$c);\n };\n testImage.src = testSrc;\n return;\n }\n return resolve(result$c);\n});\n\nvar canvasToImageData = (canvas) => {\n const imageData = canvas\n .getContext('2d')\n .getImageData(0, 0, canvas.width, canvas.height);\n return imageData;\n};\n\nvar isString = (v) => typeof v === 'string';\n\nfunction setElementStyles(element, styles) {\n styles.split(';').forEach((style) => {\n const [prop, value] = style.split(':');\n if (!prop.length || !value)\n return;\n const [cleanValue, important] = value.split('!important');\n element.style.setProperty(prop, cleanValue, isString(important) ? 'important' : undefined);\n });\n}\n\nvar h = (name, attributes, children = []) => {\n const el = document.createElement(name);\n // @ts-ignore\n const descriptors = Object.getOwnPropertyDescriptors(el.__proto__);\n for (const key in attributes) {\n if (key === 'style') {\n setElementStyles(el, attributes[key]);\n }\n else if ((descriptors[key] && descriptors[key].set) ||\n /textContent|innerHTML/.test(key) ||\n typeof attributes[key] === 'function') {\n el[key] = attributes[key];\n }\n else {\n el.setAttribute(key, attributes[key]);\n }\n }\n children.forEach((child) => el.appendChild(child));\n return el;\n};\n\nconst MATRICES = {\n 1: () => [1, 0, 0, 1, 0, 0],\n 2: (width) => [-1, 0, 0, 1, width, 0],\n 3: (width, height) => [-1, 0, 0, -1, width, height],\n 4: (width, height) => [1, 0, 0, -1, 0, height],\n 5: () => [0, 1, 1, 0, 0, 0],\n 6: (width, height) => [0, 1, -1, 0, height, 0],\n 7: (width, height) => [0, -1, -1, 0, height, width],\n 8: (width) => [0, -1, 1, 0, 0, width],\n};\nvar getImageOrientationMatrix = (width, height, orientation = -1) => {\n if (orientation === -1)\n orientation = 1;\n return MATRICES[orientation](width, height);\n};\n\nvar releaseCanvas = (canvas) => {\n canvas.width = 1;\n canvas.height = 1;\n const ctx = canvas.getContext('2d');\n ctx && ctx.clearRect(0, 0, 1, 1);\n};\n\nvar isImageData = (obj) => 'data' in obj;\n\nvar isUserAgent = (test) => isBrowser() ? RegExp(test).test(window.navigator.userAgent) : undefined;\n\nlet result$b = null;\nvar isAndroid = () => {\n if (result$b === null)\n result$b = isUserAgent(/Android/);\n return result$b;\n};\n\nvar canvasClone = (canvas, options) => {\n const clone = h('canvas', { width: canvas.width, height: canvas.height });\n clone.getContext('2d', options).drawImage(canvas, 0, 0);\n return clone;\n};\n\nvar imageDataToCanvas = async (imageData, orientation = 1) => {\n const canOrient = await canOrientImages();\n const [width, height] = canOrient || orientation < 5\n ? [imageData.width, imageData.height]\n : [imageData.height, imageData.width];\n const canvas = h('canvas', { width, height });\n const ctx = canvas.getContext('2d', { willReadFrequently: true });\n // transform image data ojects into in memory canvas elements so we can transform them (putImageData isn't affect by transforms)\n if (isImageData(imageData) && !canOrient && orientation > 1) {\n const inMemoryCanvas = h('canvas', {\n width: imageData.width,\n height: imageData.height,\n });\n const ctx = inMemoryCanvas.getContext('2d', { willReadFrequently: true });\n ctx.putImageData(imageData, 0, 0);\n imageData = inMemoryCanvas;\n }\n // get base transformation matrix\n if (!canOrient && orientation > 1) {\n ctx.transform.apply(ctx, getImageOrientationMatrix(imageData.width, imageData.height, orientation));\n }\n // can't test for instanceof ImageBitmap as Safari doesn't support it\n // if still imageData object by this point, we'll use put\n if (isImageData(imageData)) {\n ctx.putImageData(imageData, 0, 0);\n // Somehow on Android + Intel (not on M1) a toBlob call on the current canvas won't return a valid image blob, for some reason duplicating the canvas solves it #882\n if (isAndroid())\n return canvasClone(canvas);\n }\n else {\n ctx.drawImage(imageData, 0, 0);\n }\n // if image data is of type canvas, clean it up\n if (imageData instanceof HTMLCanvasElement)\n releaseCanvas(imageData);\n return canvas;\n};\n\nvar orientImageData = async (imageData, orientation = 1) => {\n if (orientation === 1)\n return imageData;\n // correct image data for when the browser does not correctly read exif orientation headers\n if (!(await canOrientImages()))\n return canvasToImageData(await imageDataToCanvas(imageData, orientation));\n return imageData;\n};\n\nvar isObject = (v) => typeof v === 'object';\n\nconst copy = (val) => {\n // don't clone html elements\n if (val instanceof HTMLElement)\n return val;\n // clone objects\n return isObject(val) ? deepCopy(val) : val;\n};\nconst deepCopy = (src) => {\n let dst;\n if (Array.isArray(src)) {\n dst = [];\n src.forEach((val, i) => {\n dst[i] = copy(val);\n });\n }\n else {\n dst = {};\n src !== null &&\n Object.keys(src).forEach((key) => {\n const val = src[key];\n dst[key] = copy(val);\n });\n }\n return dst;\n};\n\nvar isFunction = (v) => typeof v === 'function';\n\nvar imageToCanvas = (image, { width, height, canvasMemoryLimit, contextOptions }) => {\n let canvasWidth = width || image.naturalWidth;\n let canvasHeight = height || image.naturalHeight;\n // if no width and no height use defaults\n if (!canvasWidth && !canvasHeight) {\n // if these are 0 it's possible that we're trying to convert an SVG that doesn't have width or height attributes\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1328124\n canvasWidth = 300;\n canvasHeight = 150;\n }\n // determine if requires more memory than limit, if so limit target size\n const requiredCanvasMemory = canvasWidth * canvasHeight;\n if (canvasMemoryLimit && requiredCanvasMemory > canvasMemoryLimit) {\n const canvasScalar = Math.sqrt(canvasMemoryLimit) / Math.sqrt(requiredCanvasMemory);\n canvasWidth = Math.floor(canvasWidth * canvasScalar);\n canvasHeight = Math.floor(canvasHeight * canvasScalar);\n }\n // create new canvas element\n const canvas = h('canvas');\n canvas.width = canvasWidth;\n canvas.height = canvasHeight;\n const ctx = canvas.getContext('2d', contextOptions);\n ctx.drawImage(image, 0, 0, canvasWidth, canvasHeight);\n return canvas;\n};\n\n// turns image into canvas only after it's fully loaded\nvar imageToCanvasSafe = (image, options) => new Promise((resolve, reject) => {\n const ready = () => resolve(imageToCanvas(image, options));\n if (image.complete && image.width) {\n // need to test for image.width, on ie11 it will be 0 for object urls\n ready();\n }\n else {\n image.onload = ready;\n image.onerror = () => reject(new Error('Failed to load image'));\n }\n});\n\nvar blobToCanvas = async (imageBlob, options) => {\n const imageElement = h('img', {\n src: URL.createObjectURL(imageBlob),\n });\n const canvas = await imageToCanvasSafe(imageElement, options);\n URL.revokeObjectURL(imageElement.src);\n return canvas;\n};\n\nvar canCreateImageBitmap = () => 'createImageBitmap' in window;\n\nvar canCreateOffscreenCanvas = () => 'OffscreenCanvas' in window;\n\nvar isSVGFile = (blob) => /svg/.test(blob.type);\n\nvar getUniqueId = () => Math.random().toString(36).substring(2, 9);\n\nvar functionToBlob = (fn) => new Blob(['(', typeof fn === 'function' ? fn.toString() : fn, ')()'], {\n type: 'application/javascript',\n});\n\nconst wrapFunction = (fn) => `function () {self.onmessage = function (message) {(${fn.toString()}).apply(null, message.data.content.concat([function (err, response) {\n response = response || {};\n const transfer = 'data' in response ? [response.data.buffer] : 'width' in response ? [response] : [];\n return self.postMessage({ id: message.data.id, content: response, error: err }, transfer);\n}]))}}`;\nconst workerPool = new Map();\nvar thread = (fn, args, transferList) => new Promise((resolve, reject) => {\n const workerKey = fn.toString();\n let pooledWorker = workerPool.get(workerKey);\n if (!pooledWorker) {\n // create worker for this function\n const workerFn = wrapFunction(fn);\n // create a new web worker\n const url = URL.createObjectURL(functionToBlob(workerFn));\n const messages = new Map();\n const worker = new Worker(url);\n // create a pooled worker, this object will contain the worker and active messages\n pooledWorker = {\n url,\n worker,\n messages,\n terminationTimeout: undefined,\n terminate: () => {\n clearTimeout(pooledWorker.terminationTimeout);\n pooledWorker.worker.terminate();\n URL.revokeObjectURL(url);\n workerPool.delete(workerKey);\n },\n };\n // handle received messages\n worker.onmessage = function (e) {\n // should receive message id and message\n const { id, content, error } = e.data;\n // automatically clean up workers after half a second\n clearTimeout(pooledWorker.terminationTimeout);\n pooledWorker.terminationTimeout = setTimeout(() => {\n if (messages.size > 0)\n return;\n pooledWorker.terminate();\n }, 500);\n // message route no longer valid\n if (!messages.has(id))\n return;\n // get related thread and resolve with returned content\n const message = messages.get(id);\n // remove thread from threads cache\n messages.delete(id);\n // resolve or reject message based on response from worker\n error != null ? message.reject(error) : message.resolve(content);\n };\n // pool this worker\n workerPool.set(workerKey, pooledWorker);\n }\n // we need a way to remember this message so we generate a unique id and use that as a key for this request, that way we can link the response back to request in the pooledWorker.onmessage handler\n const messageId = getUniqueId();\n pooledWorker.messages.set(messageId, { resolve, reject });\n // use pooled worker and await response\n pooledWorker.worker.postMessage({ id: messageId, content: args }, transferList);\n});\n\nlet result$a = null;\nvar isFirefox = () => {\n if (result$a === null)\n result$a = isUserAgent(/Firefox/);\n return result$a;\n};\n\nconst mainThreadBlobToImageData = async (imageBlob, canvasMemoryLimit) => {\n const canvas = await blobToCanvas(imageBlob, {\n canvasMemoryLimit,\n contextOptions: { willReadFrequently: true },\n });\n const imageData = canvasToImageData(canvas);\n releaseCanvas(canvas);\n return imageData;\n};\nvar blobToImageData = async (imageBlob, canvasMemoryLimit) => {\n // somehow there's a weird issue with Android where when it uses offscreen canvas\n // the imagedata gets \"lost\" when turning a canvas with that imagedata back into a blob\n // for now we can circumvent the issue by creating the imagedata in the main thread\n // if we do this on Firefox we get a black image so we exclude firefox on android\n if (isAndroid() && !isFirefox()) {\n return await mainThreadBlobToImageData(imageBlob, canvasMemoryLimit);\n }\n let imageData;\n // if can use OffscreenCanvas let's go for it as it will mean we can run this operation on a separate thread\n if (canCreateImageBitmap() && !isSVGFile(imageBlob) && canCreateOffscreenCanvas()) {\n try {\n imageData = await thread((file, canvasMemoryLimit, done) => {\n createImageBitmap(file)\n .then((bitmap) => {\n let canvasWidth = bitmap.width;\n let canvasHeight = bitmap.height;\n // determine if requires more memory than limit, if so limit target size\n const requiredCanvasMemory = canvasWidth * canvasHeight;\n if (canvasMemoryLimit && requiredCanvasMemory > canvasMemoryLimit) {\n const canvasScalar = Math.sqrt(canvasMemoryLimit) / Math.sqrt(requiredCanvasMemory);\n canvasWidth = Math.floor(canvasWidth * canvasScalar);\n canvasHeight = Math.floor(canvasHeight * canvasScalar);\n }\n const canvas = new OffscreenCanvas(canvasWidth, canvasHeight);\n const ctx = canvas.getContext('2d', {\n // we're going to read from this canvas, don't hardware accelerate\n willReadFrequently: true,\n });\n ctx.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight);\n const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\n done(null, imageData);\n })\n .catch((err) => {\n // fail silently\n done(err);\n });\n }, [imageBlob, canvasMemoryLimit]);\n }\n catch (err) {\n // fails silently on purpose, we'll try to turn the blob into image data in the main thread\n // console.error(err);\n }\n }\n // use main thread to generate ImageData\n if (!imageData || !imageData.width) {\n return await mainThreadBlobToImageData(imageBlob, canvasMemoryLimit);\n }\n return imageData;\n};\n\nvar canvasToBlob = (canvas, mimeType = undefined, quality = undefined) => new Promise((resolve, reject) => {\n try {\n canvas.toBlob((blob) => {\n if (!blob)\n return reject(new Error('Failed to create blob'));\n resolve(blob);\n }, mimeType, quality);\n }\n catch (err) {\n reject(err);\n }\n});\n\nvar imageDataToBlob = async (imageData, mimeType, quality) => {\n const canvas = await imageDataToCanvas(imageData);\n const blob = await canvasToBlob(canvas, mimeType, quality);\n releaseCanvas(canvas);\n return blob;\n};\n\nconst mimeTypes = {\n matroska: 'mkv',\n};\nvar getExtensionFromMimeType = (mimeType) => {\n const ext = (mimeType.match(/\\/([a-z0-9]+)/) || [])[1];\n if (/^x/.test(ext)) {\n const [, post = ''] = mimeType.split('/x-');\n return mimeTypes[post];\n }\n return ext;\n};\n\nvar getFilenameWithoutExtension = (name) => name.substring(0, name.lastIndexOf('.')) || name;\n\nvar getExtensionFromFilename = (filename) => filename.split('.').pop();\n\nconst ImageExtensionsRegex = /avif|bmp|gif|jpg|jpeg|jpe|jif|jfif|png|svg|tiff|webp/;\n/*\nSupport image mime types\n- image/webp\n- image/gif\n- image/avif\n- image/jpeg\n- image/png\n- image/bmp\n- image/svg+xml\n*/\nvar getMimeTypeFromExtension = (ext) => {\n // empty string returned if extension not found\n if (!ImageExtensionsRegex.test(ext))\n return '';\n // return MimeType for this extension\n return 'image/' + (/jfif|jif|jpe|jpg/.test(ext) ? 'jpeg' : ext === 'svg' ? 'svg+xml' : ext);\n};\n\nvar getMimeTypeFromFilename = (name) => name && getMimeTypeFromExtension(getExtensionFromFilename(name).toLowerCase());\n\nvar matchFilenameToMimeType = (filename, mimeType) => {\n // get the mime type that matches this extension\n const fileMimeType = getMimeTypeFromFilename(filename);\n // test if type already matches current mime type, no need to change name\n if (fileMimeType === mimeType)\n return filename;\n // get the extension for this mimetype (gets all characters after the \"image/\" part)\n // if mimeType doesn't yield an extension, use the fileMimeType\n const targetMimeTypeExtension = getExtensionFromMimeType(mimeType) || fileMimeType;\n return `${getFilenameWithoutExtension(filename)}.${targetMimeTypeExtension}`;\n};\n\nvar blobToFile = (blob, filename, mimetype) => {\n const lastModified = new Date().getTime();\n const blobHasMimeType = blob.type.length && !/null|text/.test(blob.type);\n const blobMimeType = blobHasMimeType ? blob.type : mimetype;\n const name = matchFilenameToMimeType(filename, blobMimeType);\n try {\n return new (getNativeAPIRef('File'))([blob], name, {\n lastModified,\n type: blobHasMimeType ? blob.type : blobMimeType,\n });\n }\n catch (err) {\n const file = blobHasMimeType ? blob.slice() : blob.slice(0, blob.size, blobMimeType);\n file.lastModified = lastModified;\n file.name = name;\n return file;\n }\n};\n\nvar getAspectRatio = (w, h) => w / h;\n\nvar passthrough = (v) => (v);\n\nconst PI = Math.PI;\nconst HALF_PI = Math.PI / 2;\nconst QUART_PI = HALF_PI / 2;\n\nvar isRotatedSideways = (a) => {\n const rotationLimited = Math.abs(a) % Math.PI;\n return rotationLimited > QUART_PI && rotationLimited < Math.PI - QUART_PI;\n};\n\n// enum Direction {\n// Top = 't',\n// Right = 'r',\n// Bottom = 'b',\n// Left = 'l',\n// TopLeft = 'tl',\n// TopRight = 'tr',\n// BottomRight = 'br',\n// BottomLeft = 'bl',\n// }\n// export const { Top, Right, Bottom, Left, TopLeft, TopRight, BottomRight, BottomLeft } = Direction;\n// export default Direction;\nconst Direction = {\n Top: 't',\n Right: 'r',\n Bottom: 'b',\n Left: 'l',\n TopLeft: 'tl',\n TopRight: 'tr',\n BottomRight: 'br',\n BottomLeft: 'bl',\n};\nconst { Top, Right, Bottom, Left, TopLeft, TopRight, BottomRight, BottomLeft } = Direction;\n\nvar DirectionCoordinateTable = {\n [Top]: [0.5, 0],\n [Right]: [1, 0.5],\n [Bottom]: [0.5, 1],\n [Left]: [0, 0.5],\n [TopLeft]: [0, 0],\n [TopRight]: [1, 0],\n [BottomRight]: [1, 1],\n [BottomLeft]: [0, 1],\n};\n\nvar fixPrecision = (value, precision = 12) => parseFloat(value.toFixed(precision));\n\n//\n// generic\n//\nconst scale = (value, scalar, pivot) => pivot + (value - pivot) * scalar;\nconst ellipseCreateFromRect = (rect) => ({\n x: rect.x + rect.width * 0.5,\n y: rect.y + rect.height * 0.5,\n rx: rect.width * 0.5,\n ry: rect.height * 0.5,\n});\n//\n// vector\n//\nconst vectorCreateEmpty = () => vectorCreate(0, 0);\nconst vectorCreate = (x, y) => ({ x, y });\nconst vectorCreateFromSize = (size) => vectorCreate(size.width, size.height);\nconst vectorCreateFromAny = (obj) => vectorCreate(obj.x, obj.y);\nconst vectorCreateFromPointerEvent = (e) => vectorCreate(e.pageX, e.pageY);\nconst vectorCreateFromPointerEventOffset = (e) => vectorCreate(e.offsetX, e.offsetY);\nconst vectorClone = (v) => vectorCreate(v.x, v.y);\nconst vectorUpdate = (v, x, y) => {\n v.x = x;\n v.y = y;\n return v;\n};\nconst vectorInvert = (v) => {\n v.x = -v.x;\n v.y = -v.y;\n return v;\n};\nconst vectorPerpendicular = (v) => {\n const x = v.x;\n v.x = -v.y;\n v.y = x;\n return v;\n};\nconst vectorRotate = (v, radians, pivot = vectorCreateEmpty()) => {\n const cos = Math.cos(radians);\n const sin = Math.sin(radians);\n const tx = v.x - pivot.x;\n const ty = v.y - pivot.y;\n v.x = pivot.x + cos * tx - sin * ty;\n v.y = pivot.y + sin * tx + cos * ty;\n return v;\n};\nconst vectorLength = (v) => Math.sqrt(v.x * v.x + v.y * v.y);\nconst vectorNormalize = (v) => {\n const length = Math.sqrt(v.x * v.x + v.y * v.y);\n if (length === 0)\n return vectorCreateEmpty();\n v.x /= length;\n v.y /= length;\n return v;\n};\nconst vectorAngle = (v) => Math.atan2(v.y, v.x);\nconst vectorAngleBetween = (a, b) => Math.atan2(b.y - a.y, b.x - a.x);\nconst vectorEqual = (a, b) => a.x === b.x && a.y === b.y;\nconst vectorApply = (v, fn) => {\n v.x = fn(v.x);\n v.y = fn(v.y);\n return v;\n};\nconst vectorInSize = (v, size) => {\n return v.x >= 0 && v.y >= 0 && v.x <= size.width && v.y <= size.height;\n};\nconst vectorAdd = (a, b) => {\n a.x += b.x;\n a.y += b.y;\n return a;\n};\nconst vectorSubtract = (a, b) => {\n a.x -= b.x;\n a.y -= b.y;\n return a;\n};\nconst vectorDivide = (v, l) => {\n v.x /= l;\n v.y /= l;\n return v;\n};\nconst vectorMultiply = (v, f) => {\n v.x *= f;\n v.y *= f;\n return v;\n};\nconst vectorDot = (a, b) => a.x * b.x + a.y * b.y;\nconst vectorCross = (a, b) => a.x * b.y - a.y * b.x;\nconst vectorDistanceSquared = (a, b = vectorCreateEmpty()) => {\n const x = a.x - b.x;\n const y = a.y - b.y;\n return x * x + y * y;\n};\nconst vectorDistance = (a, b = vectorCreateEmpty()) => Math.sqrt(vectorDistanceSquared(a, b));\nconst vectorScale = (v, scalar, pivot) => {\n v.x = scale(v.x, scalar, pivot.x);\n v.y = scale(v.y, scalar, pivot.y);\n return v;\n};\nconst vectorCenter = (v) => {\n let x = 0;\n let y = 0;\n v.forEach((v) => {\n x += v.x;\n y += v.y;\n });\n return vectorCreate(x / v.length, y / v.length);\n};\nconst vectorsFlip = (points, flipX, flipY, cx, cy) => {\n points.forEach((point) => {\n point.x = flipX ? cx - (point.x - cx) : point.x;\n point.y = flipY ? cy - (point.y - cy) : point.y;\n });\n return points;\n};\nconst vectorsRotate = (points, angle, cx, cy) => {\n const s = Math.sin(angle);\n const c = Math.cos(angle);\n points.forEach((p) => {\n p.x -= cx;\n p.y -= cy;\n const rx = p.x * c - p.y * s;\n const ry = p.x * s + p.y * c;\n p.x = cx + rx;\n p.y = cy + ry;\n });\n return points;\n};\n//\n// size\n//\nconst toSize = (width, height) => ({ width, height });\nconst sizeClone = (size) => toSize(size.width, size.height);\nconst sizeCreateFromAny = (obj) => toSize(obj.width, obj.height);\nconst sizeCreateFromRect = (r) => toSize(r.width, r.height);\nconst sizeCreateFromArray = (a) => toSize(a[0], a[1]);\nconst sizeCreateFromImageNaturalSize = (image) => toSize(image.naturalWidth, image.naturalHeight);\nconst sizeCreateFromElement = (element) => {\n if (/img/i.test(element.nodeName)) {\n return sizeCreateFromImageNaturalSize(element);\n }\n return sizeCreateFromAny(element);\n};\nconst sizeCreate = (width, height) => toSize(width, height);\nconst sizeEqual = (a, b, format = passthrough) => format(a.width) === format(b.width) && format(a.height) === format(b.height);\nconst sizeScale = (size, scalar) => {\n size.width *= scalar;\n size.height *= scalar;\n return size;\n};\nconst sizeCenter = (size) => vectorCreate(size.width * 0.5, size.height * 0.5);\nconst sizeRotate = (size, radians) => {\n const r = Math.abs(radians);\n const cos = Math.abs(Math.cos(r));\n const sin = Math.abs(Math.sin(r));\n const w = cos * size.width + sin * size.height;\n const h = sin * size.width + cos * size.height;\n size.width = w;\n size.height = h;\n return size;\n};\nconst sizeTurn = (size, radians) => {\n const w = size.width;\n const h = size.height;\n if (isRotatedSideways(radians)) {\n size.width = h;\n size.height = w;\n }\n return size;\n};\nconst sizeContains = (a, b) => a.width >= b.width && a.height >= b.height;\nconst sizeApply = (size, fn) => {\n size.width = fn(size.width);\n size.height = fn(size.height);\n return size;\n};\nconst sizeHypotenuse = (size) => Math.sqrt(size.width * size.width + size.height * size.height);\nconst sizeMin = (a, b) => sizeCreate(Math.min(a.width, b.width), Math.min(a.height, b.height));\n//\n// line\n//\nconst lineCreate = (start, end) => ({ start, end });\nconst lineClone = (line) => lineCreate(vectorClone(line.start), vectorClone(line.end));\nconst lineExtend = (line, amount) => {\n if (amount === 0)\n return line;\n const v = vectorCreate(line.start.x - line.end.x, line.start.y - line.end.y);\n const n = vectorNormalize(v);\n const m = vectorMultiply(n, amount);\n line.start.x += m.x;\n line.start.y += m.y;\n line.end.x -= m.x;\n line.end.y -= m.y;\n return line;\n};\nconst lineMultiply = (line, amount) => {\n if (amount === 0)\n return line;\n const v = vectorCreate(line.start.x - line.end.x, line.start.y - line.end.y);\n const n = vectorNormalize(v);\n const m = vectorMultiply(n, amount);\n line.end.x += m.x;\n line.end.y += m.y;\n return line;\n};\nconst lineExtrude = ({ start, end }, amount) => {\n if (amount === 0)\n return [\n vectorCreate(start.x, start.y),\n vectorCreate(start.x, start.y),\n vectorCreate(end.x, end.y),\n vectorCreate(end.x, end.y),\n ];\n const a = Math.atan2(end.y - start.y, end.x - start.x);\n const sina = Math.sin(a) * amount;\n const cosa = Math.cos(a) * amount;\n return [\n vectorCreate(sina + start.x, -cosa + start.y),\n vectorCreate(-sina + start.x, cosa + start.y),\n vectorCreate(-sina + end.x, cosa + end.y),\n vectorCreate(sina + end.x, -cosa + end.y),\n ];\n};\n//\n// rect\n//\nconst CornerSigns = [\n vectorCreate(-1, -1),\n vectorCreate(-1, 1),\n vectorCreate(1, 1),\n vectorCreate(1, -1),\n];\nconst toRect = (x, y, width, height) => ({\n x,\n y,\n width,\n height,\n});\nconst rectClone$1 = (rect) => toRect(rect.x, rect.y, rect.width, rect.height);\nconst rectCreateEmpty = () => toRect(0, 0, 0, 0);\nconst rectCreateFromDimensions = (width, height) => toRect(0, 0, width, height);\nconst rectCreateFromSize = (size) => toRect(0, 0, size.width, size.height);\nconst rectCreateFromBounds = (bounds) => toRect(bounds[3], bounds[0], bounds[1] - bounds[3], bounds[2] - bounds[0]);\nconst rectCreateFromAny = (obj) => toRect(obj.x || 0, obj.y || 0, obj.width || 0, obj.height || 0);\nconst rectCreateFromPoints = (points) => {\n let xMin = points[0].x;\n let xMax = points[0].x;\n let yMin = points[0].y;\n let yMax = points[0].y;\n points.forEach((point) => {\n xMin = Math.min(xMin, point.x);\n xMax = Math.max(xMax, point.x);\n yMin = Math.min(yMin, point.y);\n yMax = Math.max(yMax, point.y);\n });\n return toRect(xMin, yMin, xMax - xMin, yMax - yMin);\n};\nconst rectCreateFromEllipse = (ellipse) => rectCreate(ellipse.x - ellipse.rx, ellipse.y - ellipse.ry, ellipse.rx * 2, ellipse.ry * 2);\nconst rectCreateWithCenter = (center, size) => toRect(center.x - size.width * 0.5, center.y - size.height * 0.5, size.width, size.height);\nconst rectCreate = (x, y, width, height) => toRect(x, y, width, height);\nconst rectCenter = (rect) => vectorCreate(rect.x + rect.width * 0.5, rect.y + rect.height * 0.5);\nconst rectTranslate$1 = (rect, t) => {\n rect.x += t.x;\n rect.y += t.y;\n return rect;\n};\nconst rectScale$1 = (rect, scalar, pivot) => {\n pivot = pivot || rectCenter(rect);\n rect.x = scalar * (rect.x - pivot.x) + pivot.x;\n rect.y = scalar * (rect.y - pivot.y) + pivot.y;\n rect.width = scalar * rect.width;\n rect.height = scalar * rect.height;\n return rect;\n};\nconst rectClamp = (rect, sizeMin, sizeMax, pivot) => {\n const x = (pivot.x - rect.x) / rect.width;\n const y = (pivot.y - rect.y) / rect.height;\n let width = Math.max(sizeMin.width, rect.width);\n let height = Math.max(sizeMin.height, rect.height);\n width = Math.min(sizeMax.width, width);\n height = Math.min(sizeMax.height, height);\n rect.x = pivot.x - x * width;\n rect.y = pivot.y - y * height;\n rect.width = width;\n rect.height = height;\n return rect;\n};\nconst rectGetCornerByTarget = (rect, target) => {\n const [xf, yf] = DirectionCoordinateTable[target];\n const x = xf * rect.width;\n const y = yf * rect.height;\n return vectorCreate(rect.x + x, rect.y + y);\n};\nconst rectMultiply = (rect, factor) => {\n rect.x *= factor;\n rect.y *= factor;\n rect.width *= factor;\n rect.height *= factor;\n return rect;\n};\nconst rectDivide = (rect, factor) => {\n rect.x /= factor;\n rect.y /= factor;\n rect.width /= factor;\n rect.height /= factor;\n return rect;\n};\nconst rectSubtract = (a, b) => {\n a.x -= b.x;\n a.y -= b.y;\n a.width -= b.width;\n a.height -= b.height;\n return a;\n};\nconst rectAdd = (a, b) => {\n a.x += b.x;\n a.y += b.y;\n a.width += b.width;\n a.height += b.height;\n return a;\n};\nconst rectEqual = (a, b, format = passthrough) => format(a.x) === format(b.x) &&\n format(a.y) === format(b.y) &&\n format(a.width) === format(b.width) &&\n format(a.height) === format(b.height);\nconst rectAspectRatio = (rect) => getAspectRatio(rect.width, rect.height);\nconst rectUpdate = (rect, x, y, width, height) => {\n rect.x = x;\n rect.y = y;\n rect.width = width;\n rect.height = height;\n return rect;\n};\nconst rectUpdateWithRect = (a, b) => {\n a.x = b.x;\n a.y = b.y;\n a.width = b.width;\n a.height = b.height;\n return a;\n};\nconst rectRotate = (rect, radians, pivot) => {\n if (!pivot)\n pivot = rectCenter(rect);\n return rectGetCorners(rect).map((vertex) => vectorRotate(vertex, radians, pivot));\n};\nconst rectCenterRect = (a, b) => toRect(a.width * 0.5 - b.width * 0.5, a.height * 0.5 - b.height * 0.5, b.width, b.height);\nconst rectContainsPoint = (rect, point) => {\n if (point.x < rect.x)\n return false;\n if (point.y < rect.y)\n return false;\n if (point.x > rect.x + rect.width)\n return false;\n if (point.y > rect.y + rect.height)\n return false;\n return true;\n};\nconst rectCoverRect = (rect, aspectRatio, offset = vectorCreateEmpty()) => {\n if (rect.width === 0 || rect.height === 0)\n return rectCreateEmpty();\n const inputAspectRatio = rectAspectRatio(rect);\n if (!aspectRatio)\n aspectRatio = inputAspectRatio;\n let width = rect.width;\n let height = rect.height;\n if (aspectRatio > inputAspectRatio) {\n // height remains the same, width is expanded\n width = height * aspectRatio;\n }\n else {\n // width remains the same, height is expanded\n height = width / aspectRatio;\n }\n return toRect(offset.x + (rect.width - width) * 0.5, offset.y + (rect.height - height) * 0.5, width, height);\n};\nconst rectLimitInRect = (childRect, parentRect, aspectRatio) => {\n const [t, r, b, l] = rectToBounds(childRect);\n const rectOrigin = { ...childRect };\n if (t < parentRect.y) {\n childRect.height = childRect.height - (parentRect.y - t);\n childRect.y = parentRect.y;\n }\n if (r > parentRect.x + parentRect.width) {\n childRect.width = parentRect.x + parentRect.width - childRect.x;\n }\n if (b > parentRect.y + parentRect.height) {\n childRect.height = parentRect.y + parentRect.height - childRect.y;\n }\n if (l < parentRect.x) {\n childRect.width = childRect.width - (parentRect.x - l);\n childRect.x = parentRect.x;\n }\n if (aspectRatio) {\n // scalar\n const scalar = Math.min(1, rectOrigin.width / childRect.width, rectOrigin.height / childRect.height);\n return rectMultiply(rectOrigin, scalar);\n }\n return childRect;\n};\nconst rectOffsetInRect = (childRect, parentRect) => {\n const [t, r, b, l] = rectToBounds(childRect);\n if (t < parentRect.y)\n childRect.y = Math.max(parentRect.y, childRect.y);\n if (r > parentRect.width)\n childRect.x = parentRect.width - childRect.width;\n if (b > parentRect.height)\n childRect.y = parentRect.height - childRect.height;\n if (l < parentRect.x)\n childRect.x = Math.max(parentRect.x, childRect.x);\n return childRect;\n};\nconst rectContainRect = (rect, aspectRatio = rectAspectRatio(rect), offset = vectorCreateEmpty()) => {\n if (rect.width === 0 || rect.height === 0)\n return rectCreateEmpty();\n let width = rect.width;\n let height = width / aspectRatio;\n if (height > rect.height) {\n height = rect.height;\n width = height * aspectRatio;\n }\n return toRect(offset.x + (rect.width - width) * 0.5, offset.y + (rect.height - height) * 0.5, width, height);\n};\nconst rectToBounds = (rect) => [\n Math.min(rect.y, rect.y + rect.height),\n Math.max(rect.x, rect.x + rect.width),\n Math.max(rect.y, rect.y + rect.height),\n Math.min(rect.x, rect.x + rect.width), // left\n];\nconst rectGetCorners = (rect) => [\n vectorCreate(rect.x, rect.y),\n vectorCreate(rect.x + rect.width, rect.y),\n vectorCreate(rect.x + rect.width, rect.y + rect.height),\n vectorCreate(rect.x, rect.y + rect.height),\n];\nconst rectApply = (rect, fn) => {\n if (!rect)\n return;\n rect.x = fn(rect.x);\n rect.y = fn(rect.y);\n rect.width = fn(rect.width);\n rect.height = fn(rect.height);\n return rect;\n};\nconst rectApplyPerspective = (rect, perspective, pivot = rectCenter(rect)) => rectGetCorners(rect).map((corner, index) => {\n const sign = CornerSigns[index];\n return vectorCreate(scale(corner.x, 1.0 + sign.x * perspective.x, pivot.x), scale(corner.y, 1.0 + sign.y * perspective.y, pivot.y));\n});\nconst rectNormalizeOffset = (rect) => {\n rect.x = 0;\n rect.y = 0;\n return rect;\n};\nconst convexPolyCentroid = (vertices) => {\n const first = vertices[0];\n const last = vertices[vertices.length - 1];\n // make sure is closed loop\n vertices = vectorEqual(first, last) ? vertices : [...vertices, first];\n const fx = first.x;\n const fy = first.y;\n let twiceArea = 0;\n let i = 0;\n let x = 0;\n let y = 0;\n let a;\n let b;\n let f;\n const l = vertices.length;\n for (; i < l; i++) {\n // current vertex\n a = vertices[i];\n // next vertex\n b = vertices[i + 1 > l - 1 ? 0 : i + 1];\n f = (a.y - fy) * (b.x - fx) - (b.y - fy) * (a.x - fx);\n twiceArea += f;\n x += (a.x + b.x - 2 * fx) * f;\n y += (a.y + b.y - 2 * fy) * f;\n }\n f = twiceArea * 3;\n return vectorCreate(fx + x / f, fy + y / f);\n};\nconst lineLineIntersection = (a, b) => getLineLineIntersectionPoint(a.start, a.end, b.start, b.end);\nconst getLineLineIntersectionPoint = (a, b, c, d) => {\n const denominator = (d.y - c.y) * (b.x - a.x) - (d.x - c.x) * (b.y - a.y);\n // lines are parallel\n if (denominator === 0)\n return undefined;\n const uA = ((d.x - c.x) * (a.y - c.y) - (d.y - c.y) * (a.x - c.x)) / denominator;\n const uB = ((b.x - a.x) * (a.y - c.y) - (b.y - a.y) * (a.x - c.x)) / denominator;\n // intersection is not on the line itself\n if (uA < 0 || uA > 1 || uB < 0 || uB > 1)\n return undefined;\n // return intersection point\n return vectorCreate(a.x + uA * (b.x - a.x), a.y + uA * (b.y - a.y));\n};\n// checks if line intersects with one of the lines that can be drawn between the points (in sequence)\nconst linePointsIntersection = (line, points, { ignoreIdenticalLines = false, breakOnIntersection = false } = {}) => {\n const l = points.length;\n const intersections = [];\n for (let i = 0; i < l - 1; i++) {\n if (ignoreIdenticalLines &&\n (vectorEqual(line.start, points[i]) ||\n vectorEqual(line.start, points[i + 1]) ||\n vectorEqual(line.end, points[i]) ||\n vectorEqual(line.end, points[i + 1])))\n continue;\n const intersection = getLineLineIntersectionPoint(line.start, line.end, points[i], points[i + 1]);\n if (!intersection)\n continue;\n if (breakOnIntersection)\n return [intersection];\n intersections.push(intersection);\n }\n return intersections.length ? intersections : undefined;\n};\nconst pointInPoly$1 = (point, vertices) => {\n let i = 0;\n let j = 0;\n let c = false;\n const l = vertices.length;\n for (i = 0, j = l - 1; i < l; j = i++) {\n if (vertices[i].y > point.y != vertices[j].y > point.y &&\n point.x <\n ((vertices[j].x - vertices[i].x) * (point.y - vertices[i].y)) /\n (vertices[j].y - vertices[i].y) +\n vertices[i].x) {\n c = !c;\n }\n }\n return c;\n};\nconst getControlPoint = (p1, p2, p3, scale) => {\n let dx = (p2.x - p1.x) * scale;\n let dy = (p2.y - p1.y) * scale;\n if (vectorDistance(p3, p2) < vectorDistance(p2, p1)) {\n dx *= 0.15;\n dy *= 0.15;\n }\n return {\n x: p2.x + dx,\n y: p2.y + dy,\n };\n};\nconst bezier = (p1, c, p2, t) => {\n const x = (1 - t) * (1 - t) * p1.x + 2 * (1 - t) * t * c.x + t * t * p2.x;\n const y = (1 - t) * (1 - t) * p1.y + 2 * (1 - t) * t * c.y + t * t * p2.y;\n return { x, y };\n};\nconst pointsInterpolate = (p1, p2, p3, numPoints, controlPointScale) => {\n const controlPoint = getControlPoint(p1, p2, p3, controlPointScale);\n const points = [];\n for (let i = 0; i <= numPoints; i++) {\n const t = i / numPoints;\n points.push(bezier(p2, controlPoint, p3, t));\n }\n return points;\n};\n// first tests if points of a are to be found in b, then does the reverse\nconst polyIntersectsWithPoly = (a, b) => {\n const bContainsCornerOfA = a.find((corner) => pointInPoly$1(corner, b));\n if (bContainsCornerOfA)\n return true;\n const aContainsCornerOfB = b.find((corner) => pointInPoly$1(corner, a));\n if (aContainsCornerOfB)\n return true;\n return false;\n};\nconst quadLines = (vertices) => {\n const arr = [];\n for (let i = 0; i < vertices.length; i++) {\n let next = i + 1;\n if (next === vertices.length)\n next = 0;\n arr.push(lineCreate(vectorClone(vertices[i]), vectorClone(vertices[next])));\n }\n return arr;\n};\nconst quadToLines = (vertices, extension = 0) => quadLines(vertices)\n // extend the poly lines a tiny bit so we\n // don't shoot rays between line gaps at corners\n // this caused one intersection to be missing resulting\n // in error while manipulating crop edges\n // (rotate image 90degrees -> drag bottom edge) (2021-04-09)\n .map((line) => lineExtend(line, extension));\nconst ellipseToPolygon = (center, rx, ry, rotation = 0, flipX = false, flipY = false, resolution = 12) => {\n const points = [];\n for (let i = 0; i < resolution; i++) {\n points.push(vectorCreate(center.x + rx * Math.cos((i * (Math.PI * 2)) / resolution), center.y + ry * Math.sin((i * (Math.PI * 2)) / resolution)));\n }\n if (flipX || flipY)\n vectorsFlip(points, flipX, flipY, center.x, center.y);\n if (rotation)\n vectorsRotate(points, rotation, center.x, center.y);\n return points;\n};\nconst polyToBounds = (poly) => poly.reduce(([top, right, bottom, left], { x, y }) => {\n left = Math.min(left, x);\n right = Math.max(right, x);\n top = Math.min(top, y);\n bottom = Math.max(bottom, y);\n return [top, right, bottom, left];\n}, [Infinity, -Infinity, -Infinity, Infinity]);\n// markup editor shape helpers\nconst circleOverlapsWithLine = (point, radius, from, to) => {\n const ac = vectorCreate(point.x - from.x, point.y - from.y);\n const ab = vectorCreate(to.x - from.x, to.y - from.y);\n const ab2 = vectorDot(ab, ab);\n const acab = vectorDot(ac, ab);\n let t = acab / ab2;\n t = t < 0 ? 0 : t;\n t = t > 1 ? 1 : t;\n const h = vectorCreate(ab.x * t + from.x - point.x, ab.y * t + from.y - point.y);\n const h2 = vectorDot(h, h);\n return h2 <= radius * radius;\n};\nconst circleOverlapsWithPath = (position, radius, points) => {\n const l = points.length;\n for (let i = 0; i < l - 1; i++) {\n if (circleOverlapsWithLine(position, radius, points[i], points[i + 1]))\n return true;\n }\n return false;\n};\nconst circleOverlapsWithPolygon = (position, radius, points) => {\n if (pointInPoly$1(position, points))\n return true;\n if (circleOverlapsWithPath(position, radius, points))\n return true;\n return circleOverlapsWithLine(position, radius, points[0], points[points.length - 1]);\n};\nconst circleOverlapsWithEllipse = (position, radius, ellipse, rotation, flipX, flipY) => {\n const points = ellipseToPolygon(vectorCreate(ellipse.x, ellipse.y), ellipse.rx, ellipse.ry, rotation, flipX, flipY, 12);\n return circleOverlapsWithPolygon(position, radius, points);\n};\nconst circleOverlapsWithRect = (position, radius, rect, rotation, pivot) => circleOverlapsWithPolygon(position, radius, rectRotate(rect, rotation, pivot || rectCenter(rect)));\n\nvar getImageTransformedRect = (imageSize, imageRotation) => {\n const imageRect = rectCreateFromSize(imageSize);\n const imageCenter = rectCenter(imageRect);\n const imageTransformedVertices = rectRotate(imageRect, imageRotation, imageCenter);\n return rectNormalizeOffset(rectCreateFromPoints(imageTransformedVertices));\n};\n\nvar isElement = (v, name) => v instanceof HTMLElement && (name ? new RegExp(`^${name}$`, 'i').test(v.nodeName) : true);\n\nvar isFile = (v) => v instanceof File;\n\nvar canvasToFile = async (canvas, mimeType, quality) => {\n const blob = await canvasToBlob(canvas, mimeType, quality);\n return blobToFile(blob, 'canvas');\n};\n\nvar getFilenameFromURL = (url) => url\n .split('/')\n .pop()\n .split(/\\?|\\#/)\n .shift();\n\n// @ts-ignore\nconst supportsReplaceChildren = isBrowser() && !!Node.prototype.replaceChildren;\nconst fn$1 = supportsReplaceChildren\n ? // @ts-ignore\n (parent, newChildren) => parent.replaceChildren(newChildren)\n : (parent, newChildren) => {\n while (parent.lastChild) {\n parent.removeChild(parent.lastChild);\n }\n if (newChildren !== undefined) {\n parent.append(newChildren);\n }\n };\n\nconst container = isBrowser() &&\n h('div', {\n class: 'PinturaMeasure',\n style: 'position:absolute;left:0;top:0;width:99999px;height:0;pointer-events:none;contain:strict;margin:0;padding:0;',\n });\nlet timeoutId;\nvar appendForMeasuring = (element) => {\n // replace element children with this child\n fn$1(container, element);\n // append to DOM if not in it atm\n if (!container.parentNode)\n document.body.append(container);\n // auto detach from DOM after it isn't used for a little while\n clearTimeout(timeoutId);\n timeoutId = setTimeout(() => {\n container.remove();\n }, 500);\n // return added element for measuring\n return element;\n};\n\nlet result$9 = null;\nvar isSafari = () => {\n if (result$9 === null)\n result$9 =\n isBrowser() && /^((?!chrome|android).)*(safari|iphone|ipad)/i.test(navigator.userAgent); // added |iphone|ipad as safari is not present in WebView on React Native\n return result$9;\n};\n\nvar getImageElementSize = (imageElement) => new Promise((resolve, reject) => {\n let shouldAutoRemove = false;\n // test if image is attached to DOM, if not attached, attach so measurement is correct on Safari\n if (!imageElement.parentNode && isSafari()) {\n shouldAutoRemove = true;\n // has width 0 and height 0 to prevent rendering very big SVGs (without width and height) that will for one frame overflow the window and show a scrollbar\n imageElement.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;left:0;top:0;width:0;height:0;`;\n appendForMeasuring(imageElement);\n }\n // start testing size\n const measure = () => {\n const width = imageElement.naturalWidth;\n const height = imageElement.naturalHeight;\n const hasSize = width && height;\n if (!hasSize)\n return;\n // clean up image if was attached for measuring\n if (shouldAutoRemove)\n imageElement.remove();\n clearInterval(intervalId);\n resolve({ width, height });\n };\n imageElement.onerror = (err) => {\n clearInterval(intervalId);\n reject(err);\n };\n const intervalId = setInterval(measure, 1);\n measure();\n});\n\nvar getVideoElementSize = (element) => new Promise((resolve, reject) => {\n const done = () => {\n resolve({\n width: element.videoWidth,\n height: element.videoHeight,\n });\n };\n if (element.readyState >= 1)\n return done();\n element.onloadedmetadata = done;\n element.onerror = () => reject(element.error);\n});\n\nvar isImage = (file) => /^image/.test(file.type);\n\nvar getAsVideoOrImageElement = (src) => new Promise((resolve) => {\n // undetermined or is video\n const elementSrc = (isString(src) ? src : URL.createObjectURL(src));\n const createImage = () => {\n const image = new Image();\n image.src = elementSrc;\n resolve(image);\n };\n // we can already know it's an image\n if (src instanceof Blob && isImage(src))\n return createImage();\n // try video first\n const element = document.createElement('video');\n element.preload = 'metadata';\n element.onloadedmetadata = () => resolve(element);\n // when errors we switch to image\n element.onerror = createImage;\n element.src = elementSrc;\n});\n\nvar isVideoElement = (element) => element.nodeName === 'VIDEO';\n\n// TODO: video rename function\nvar getImageSize = async (src) => {\n // the image element we'll use to load the image\n let mediaElement;\n // if is not an image or video element, it must be a valid image or video source\n if (!src['src']) {\n mediaElement = await getAsVideoOrImageElement(src);\n }\n else {\n mediaElement = src;\n }\n let size;\n try {\n size = isVideoElement(mediaElement)\n ? await getVideoElementSize(mediaElement)\n : await getImageElementSize(mediaElement);\n }\n finally {\n isFile(src) && URL.revokeObjectURL(mediaElement.src);\n }\n return size;\n};\n\nconst awaitComplete = (image) => new Promise((resolve, reject) => {\n if (image.complete)\n return resolve(image);\n image.onload = () => resolve(image);\n image.onerror = () => reject(new Error('Failed to load image'));\n});\nvar imageToFile = async (imageElement) => {\n const size = await getImageSize(imageElement);\n const image = await awaitComplete(imageElement);\n const canvas = h('canvas', size);\n const ctx = canvas.getContext('2d', { willReadFrequently: true });\n ctx.drawImage(image, 0, 0);\n const blob = await canvasToBlob(canvas);\n return blobToFile(blob, getFilenameFromURL(image.src));\n};\n\nvar isDataURI = (str) => /^data:/.test(str);\n\nvar createProgressEvent = (loaded = 0, lengthComputable = true) => new (getNativeAPIRef('ProgressEvent'))('progress', {\n loaded: loaded * 100,\n total: 100,\n lengthComputable,\n});\n\nvar dataURIToFile = async (dataURI, filename = 'data-uri', onprogress = noop$1) => {\n // basic loader, no size info\n onprogress(createProgressEvent(0));\n const res = await fetch(dataURI);\n onprogress(createProgressEvent(0.33));\n const blob = await res.blob();\n let mimeType;\n if (!isImage(blob))\n mimeType = `image/${dataURI.includes(',/9j/') ? 'jpeg' : 'png'}`;\n onprogress(createProgressEvent(0.66));\n const file = blobToFile(blob, filename, mimeType);\n onprogress(createProgressEvent(1));\n return file;\n};\n\nvar getResponseHeader = (xhr, header, parse = (header) => header) => xhr.getAllResponseHeaders().indexOf(header) >= 0\n ? parse(xhr.getResponseHeader(header))\n : undefined;\n\nvar getFilenameFromContentDisposition = (header) => {\n if (!header)\n return null;\n const matches = header.split(/filename=|filename\\*=.+''/)\n .splice(1)\n .map(name => name.trim().replace(/^[\"']|[;\"']{0,2}$/g, ''))\n .filter(name => name.length);\n return matches.length ? decodeURI(matches[matches.length - 1]) : null;\n};\n\nconst EditorErrorCode = {\n URL_REQUEST: 'URL_REQUEST',\n DOCTYPE_MISSING: 'DOCTYPE_MISSING',\n};\nclass EditorError extends Error {\n constructor(message, code, metadata) {\n super(message);\n this.name = 'EditorError';\n this.code = code;\n this.metadata = metadata;\n }\n}\n\n// should be called after xhr.open\nvar configureXHR = (xhr, options) => {\n // https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters\n // only handles headers and credentials for now\n const { headers = {}, credentials } = options || {};\n // apply options to xhr\n Object.entries(headers).forEach(([header, value]) => xhr.setRequestHeader(header, value));\n // set xhr withCredentials\n if (credentials)\n xhr.withCredentials = credentials !== 'omit';\n};\n\nvar fetchFile = (url, onprogress, options) => new Promise((resolve, reject) => {\n const handleError = () => reject(new EditorError('Error fetching image', EditorErrorCode.URL_REQUEST, xhr));\n const xhr = new XMLHttpRequest();\n xhr.onprogress = onprogress;\n (xhr.onerror = handleError),\n (xhr.onload = () => {\n if (!xhr.response || xhr.status >= 300 || xhr.status < 200)\n return handleError();\n // we store the response mime type so we can add it to the blob later on, if it's missing (happens on Safari 10)\n const mimetype = getResponseHeader(xhr, 'Content-Type');\n // try to get filename and any file instructions as well\n const filename = getResponseHeader(xhr, 'Content-Disposition', getFilenameFromContentDisposition) || getFilenameFromURL(url);\n // convert to actual file if possible\n resolve(blobToFile(xhr.response, filename, mimetype || getMimeTypeFromFilename(filename)));\n });\n // get helper\n const { willRequest } = options;\n Promise.resolve(willRequest && willRequest(url, { resourceType: 'image' }))\n .then((requestInit) => {\n // don't load image\n if (requestInit === false)\n return reject('Image load rejected');\n // we're good to go (pass true explicitely on purpose)\n xhr.open('GET', url, true);\n // set props\n configureXHR(xhr, requestInit);\n // fire xhr\n xhr.responseType = 'blob';\n xhr.send();\n })\n .catch(console.error);\n});\n\nvar urlToFile = (url, onprogress, options) => {\n // use fetch to create blob from data uri\n if (isDataURI(url))\n return dataURIToFile(url, undefined, onprogress);\n // load file from url\n return fetchFile(url, onprogress, options);\n};\n\nvar isBlob = (v) => v instanceof Blob && !(v instanceof File);\n\nvar isImageBitmap = (obj) => 'close' in obj;\n\nvar srcToFile = async (src, onprogress, options) => {\n if (isFile(src) || isBlob(src))\n return src;\n else if (isString(src))\n return await urlToFile(src, onprogress, options);\n else if (isElement(src, 'canvas'))\n return await canvasToFile(src);\n else if (isElement(src, 'img'))\n return await imageToFile(src);\n else if (isImageData(src) || isImageBitmap(src))\n return await canvasToFile(await imageDataToCanvas(src));\n else {\n throw new EditorError('Invalid image source', 'invalid-image-source');\n }\n};\n\nlet result$8 = null;\nvar isMac = () => {\n if (result$8 === null)\n result$8 = isBrowser() && /^mac/i.test(navigator.platform);\n return result$8;\n};\n\nlet result$7 = null;\nvar isIOS = () => {\n if (result$7 === null)\n // first part is for iPhones and iPads iOS 12 and below second part is for iPads with iOS 13 and up\n result$7 =\n isBrowser() &&\n (isUserAgent(/iPhone|iPad|iPod/) || (isMac() && navigator.maxTouchPoints >= 1));\n return result$7;\n};\n\nvar orientImageSize = async (size, orientation = 1) => {\n // browser can handle image orientation\n if ((await canOrientImages()) || isIOS())\n return size;\n // no need to correct size\n if (orientation < 5)\n return size;\n // correct image size\n return sizeCreate(size.height, size.width);\n};\n\nvar isJPEG = (file) => /jpeg/.test(file.type);\n\nvar isPlainObject = (obj) => typeof obj == 'object' && obj.constructor == Object;\n\nvar stringify = (value) => (!isPlainObject(value) ? value : JSON.stringify(value));\n\nvar post = (url, dataset, options) => new Promise((resolve, reject) => {\n const { token = {}, beforeSend = noop$1, onprogress = noop$1 } = options;\n token.cancel = () => request.abort();\n const request = new XMLHttpRequest();\n request.upload.onprogress = onprogress;\n request.onload = () => request.status >= 200 && request.status < 300 ? resolve(request) : reject(request);\n request.onerror = () => reject(request);\n request.ontimeout = () => reject(request);\n request.open('POST', encodeURI(url));\n beforeSend(request);\n request.send(\n // if is FormData, we use that\n dataset instanceof FormData\n ? dataset\n : // reduce the dataset to FormData\n dataset.reduce((formData, args) => {\n // @ts-ignore\n formData.append(...args.map(stringify));\n return formData;\n }, new FormData()));\n});\n\nvar ctxRotate = (ctx, rotation = 0, pivot) => {\n if (rotation === 0)\n return ctx;\n ctx.translate(pivot.x, pivot.y);\n ctx.rotate(rotation);\n ctx.translate(-pivot.x, -pivot.y);\n return ctx;\n};\n\nvar ctxTranslate = (ctx, x, y) => {\n ctx.translate(x, y);\n return ctx;\n};\n\nvar ctxScale = (ctx, x, y) => {\n ctx.scale(x, y);\n return ctx;\n};\n\nvar cropImageData = async (imageData, options = {}) => {\n const { flipX, flipY, rotation, crop } = options;\n const imageSize = sizeCreateFromAny(imageData);\n const shouldFlip = flipX || flipY;\n const shouldRotate = !!rotation;\n const cropDefined = crop && (crop.x || crop.y || crop.width || crop.height);\n const cropCoversImage = cropDefined && rectEqual(crop, rectCreateFromSize(imageSize));\n const shouldCrop = cropDefined && !cropCoversImage;\n // skip!\n if (!shouldFlip && !shouldRotate && !shouldCrop)\n return imageData;\n // create drawing context\n let imageDataOut;\n let image = h('canvas', {\n width: imageData.width,\n height: imageData.height,\n });\n image.getContext('2d', { willReadFrequently: true }).putImageData(imageData, 0, 0);\n // flip image data\n if (shouldFlip) {\n const ctx = h('canvas', {\n width: image.width,\n height: image.height,\n }).getContext('2d', { willReadFrequently: true });\n ctxScale(ctx, flipX ? -1 : 1, flipY ? -1 : 1);\n ctx.drawImage(image, flipX ? -image.width : 0, flipY ? -image.height : 0);\n ctx.restore();\n releaseCanvas(image);\n image = ctx.canvas;\n }\n // rotate image data\n if (shouldRotate) {\n // if shouldRotate is true we also receive a crop rect\n const outputSize = sizeApply(sizeCreateFromRect(rectCreateFromPoints(rectRotate(rectCreateFromAny(image), rotation))), Math.floor);\n const ctx = h('canvas', {\n width: crop.width,\n height: crop.height,\n }).getContext('2d', { willReadFrequently: true });\n ctxTranslate(ctx, -crop.x, -crop.y);\n ctxRotate(ctx, rotation, sizeCenter(outputSize));\n ctx.drawImage(image, (outputSize.width - image.width) * 0.5, (outputSize.height - image.height) * 0.5);\n ctx.restore();\n releaseCanvas(image);\n image = ctx.canvas;\n }\n // crop image data\n else if (shouldCrop) {\n const ctx = image.getContext('2d', { willReadFrequently: true });\n imageDataOut = ctx.getImageData(crop.x, crop.y, crop.width, crop.height);\n releaseCanvas(image);\n return imageDataOut;\n }\n // done, return resulting image data\n const ctx = image.getContext('2d', { willReadFrequently: true });\n imageDataOut = ctx.getImageData(0, 0, image.width, image.height);\n releaseCanvas(image);\n return imageDataOut;\n};\n\nvar resizeTransform = (options, done) => {\n const { imageData, width, height } = options;\n const inputWidth = imageData.width;\n const inputHeight = imageData.height;\n const outputWidth = Math.round(width);\n const outputHeight = Math.round(height);\n const inputData = imageData.data;\n const outputData = new Uint8ClampedArray(outputWidth * outputHeight * 4);\n const scaleX = inputWidth / outputWidth;\n const scaleY = inputHeight / outputHeight;\n const scaleXHalf = Math.ceil(scaleX * 0.5);\n const scaleYHalf = Math.ceil(scaleY * 0.5);\n for (let j = 0; j < outputHeight; j++) {\n for (let i = 0; i < outputWidth; i++) {\n const x2 = (i + j * outputWidth) * 4;\n let weight = 0;\n let weights = 0;\n let weightsAlpha = 0;\n let r = 0;\n let g = 0;\n let b = 0;\n let a = 0;\n const centerY = (j + 0.5) * scaleY;\n for (let yy = Math.floor(j * scaleY); yy < (j + 1) * scaleY; yy++) {\n const dy = Math.abs(centerY - (yy + 0.5)) / scaleYHalf;\n const centerX = (i + 0.5) * scaleX;\n const w0 = dy * dy;\n for (let xx = Math.floor(i * scaleX); xx < (i + 1) * scaleX; xx++) {\n let dx = Math.abs(centerX - (xx + 0.5)) / scaleXHalf;\n const w = Math.sqrt(w0 + dx * dx);\n if (w < -1 || w > 1)\n continue;\n weight = 2 * w * w * w - 3 * w * w + 1;\n if (weight <= 0)\n continue;\n dx = 4 * (xx + yy * inputWidth);\n const ref = inputData[dx + 3];\n a += weight * ref;\n weightsAlpha += weight;\n if (ref < 255) {\n weight = (weight * ref) / 250;\n }\n r += weight * inputData[dx];\n g += weight * inputData[dx + 1];\n b += weight * inputData[dx + 2];\n weights += weight;\n }\n }\n outputData[x2] = r / weights;\n outputData[x2 + 1] = g / weights;\n outputData[x2 + 2] = b / weights;\n outputData[x2 + 3] = a / weightsAlpha;\n }\n }\n // @ts-ignore\n done(null, {\n data: outputData,\n width: outputWidth,\n height: outputHeight,\n });\n};\n\nvar imageDataObjectToImageData = (obj) => {\n if (obj instanceof ImageData) {\n return obj;\n }\n let imageData;\n try {\n imageData = new ImageData(obj.width, obj.height);\n }\n catch (err) {\n // IE + Old EDGE (tested on 12)\n const canvas = h('canvas');\n imageData = canvas.getContext('2d').createImageData(obj.width, obj.height);\n }\n imageData.data.set(obj.data);\n return imageData;\n};\n\nvar resizeImageData = async (imageData, options = {}, resizeImageData) => {\n const { width, height, fit, upscale } = options;\n // no need to rescale\n if (!width && !height)\n return imageData;\n let targetWidth = width;\n let targetHeight = height;\n if (!width) {\n targetWidth = height;\n }\n else if (!height) {\n targetHeight = width;\n }\n if (fit !== 'force') {\n const scalarWidth = targetWidth / imageData.width;\n const scalarHeight = targetHeight / imageData.height;\n let scalar = 1;\n if (fit === 'cover') {\n scalar = Math.max(scalarWidth, scalarHeight);\n }\n else if (fit === 'contain') {\n scalar = Math.min(scalarWidth, scalarHeight);\n }\n // if image is too small, exit here with original image\n if (scalar > 1 && upscale === false)\n return imageData;\n targetWidth = Math.round(imageData.width * scalar);\n targetHeight = Math.round(imageData.height * scalar);\n }\n // make sure canvas has a size\n targetWidth = Math.max(targetWidth, 1);\n targetHeight = Math.max(targetHeight, 1);\n // no need to resize?\n if (imageData.width === targetWidth && imageData.height === targetHeight)\n return imageData;\n // run custom image data resizer if defined\n if (resizeImageData)\n return resizeImageData(imageData, targetWidth, targetHeight);\n // let's use the included resize method\n imageData = await thread(resizeTransform, [\n {\n imageData: imageData,\n width: targetWidth,\n height: targetHeight,\n },\n ], [imageData.data.buffer]);\n // the resizer returns a plain object, not an actual image data object, lets create one\n return imageDataObjectToImageData(imageData);\n};\n\nvar colorEffect = (options, done) => {\n const { imageData, matrix } = options;\n if (!matrix)\n return done(null, imageData);\n const data = imageData.data;\n const l = data.length;\n const m11 = matrix[0];\n const m12 = matrix[1];\n const m13 = matrix[2];\n const m14 = matrix[3];\n const m15 = matrix[4];\n const m21 = matrix[5];\n const m22 = matrix[6];\n const m23 = matrix[7];\n const m24 = matrix[8];\n const m25 = matrix[9];\n const m31 = matrix[10];\n const m32 = matrix[11];\n const m33 = matrix[12];\n const m34 = matrix[13];\n const m35 = matrix[14];\n const m41 = matrix[15];\n const m42 = matrix[16];\n const m43 = matrix[17];\n const m44 = matrix[18];\n const m45 = matrix[19];\n let index = 0;\n let r = 0.0;\n let g = 0.0;\n let b = 0.0;\n let a = 0.0;\n let mr = 0.0;\n let mg = 0.0;\n let mb = 0.0;\n let ma = 0.0;\n let or = 0.0;\n let og = 0.0;\n let ob = 0.0;\n for (; index < l; index += 4) {\n r = data[index] / 255;\n g = data[index + 1] / 255;\n b = data[index + 2] / 255;\n a = data[index + 3] / 255;\n mr = r * m11 + g * m12 + b * m13 + a * m14 + m15;\n mg = r * m21 + g * m22 + b * m23 + a * m24 + m25;\n mb = r * m31 + g * m32 + b * m33 + a * m34 + m35;\n ma = r * m41 + g * m42 + b * m43 + a * m44 + m45;\n or = Math.max(0, mr * ma) + (1.0 - ma);\n og = Math.max(0, mg * ma) + (1.0 - ma);\n ob = Math.max(0, mb * ma) + (1.0 - ma);\n data[index] = Math.max(0.0, Math.min(1.0, or)) * 255;\n data[index + 1] = Math.max(0.0, Math.min(1.0, og)) * 255;\n data[index + 2] = Math.max(0.0, Math.min(1.0, ob)) * 255;\n data[index + 3] = a * 255;\n }\n // @ts-ignore\n done(null, {\n data,\n width: imageData.width,\n height: imageData.height,\n });\n};\n\nvar convolutionEffect = (options, done) => {\n const { imageData, matrix } = options;\n if (!matrix)\n return done(null, imageData);\n // calculate kernel weight\n let kernelWeight = matrix.reduce((prev, curr) => prev + curr);\n kernelWeight = kernelWeight <= 0 ? 1 : kernelWeight;\n // input info\n const inputWidth = imageData.width;\n const inputHeight = imageData.height;\n const inputData = imageData.data;\n let i = 0;\n let x = 0;\n let y = 0;\n const side = Math.round(Math.sqrt(matrix.length));\n const sideHalf = Math.floor(side / 2);\n let r = 0, g = 0, b = 0, a = 0, cx = 0, cy = 0, scy = 0, scx = 0, srcOff = 0, weight = 0;\n const outputData = new Uint8ClampedArray(inputWidth * inputHeight * 4);\n for (y = 0; y < inputHeight; y++) {\n for (x = 0; x < inputWidth; x++) {\n // calculate the weighed sum of the source image pixels that\n // fall under the convolution matrix\n r = 0;\n g = 0;\n b = 0;\n a = 0;\n for (cy = 0; cy < side; cy++) {\n for (cx = 0; cx < side; cx++) {\n scy = y + cy - sideHalf;\n scx = x + cx - sideHalf;\n if (scy < 0) {\n scy = inputHeight - 1;\n }\n if (scy >= inputHeight) {\n scy = 0;\n }\n if (scx < 0) {\n scx = inputWidth - 1;\n }\n if (scx >= inputWidth) {\n scx = 0;\n }\n srcOff = (scy * inputWidth + scx) * 4;\n weight = matrix[cy * side + cx];\n r += inputData[srcOff] * weight;\n g += inputData[srcOff + 1] * weight;\n b += inputData[srcOff + 2] * weight;\n a += inputData[srcOff + 3] * weight;\n }\n }\n outputData[i] = r / kernelWeight;\n outputData[i + 1] = g / kernelWeight;\n outputData[i + 2] = b / kernelWeight;\n outputData[i + 3] = a / kernelWeight;\n i += 4;\n }\n }\n // @ts-ignore\n done(null, {\n data: outputData,\n width: inputWidth,\n height: inputHeight,\n });\n};\n\nvar vignetteEffect = (options, done) => {\n let { imageData, strength } = options;\n if (!strength)\n return done(null, imageData);\n const inputWidth = imageData.width;\n const inputHeight = imageData.height;\n const data = imageData.data;\n const dist = (x, y) => {\n dx = x - cx;\n dy = y - cy;\n return Math.sqrt(dx * dx + dy * dy);\n };\n let x = 0;\n let y = 0;\n let cx = inputWidth * 0.5;\n let cy = inputHeight * 0.5;\n let dx;\n let dy;\n let dm = dist(0, 0);\n let fr, fg, fb;\n let br, bg, bb, ba;\n let fa;\n let ca;\n const blend = (index, input, output, alpha) => {\n br = input[index] / 255;\n bg = input[index + 1] / 255;\n bb = input[index + 2] / 255;\n ba = input[index + 3] / 255;\n fa = 1.0 - alpha;\n ca = fa * ba + alpha;\n output[index] = ((fa * ba * br + alpha * fr) / ca) * 255;\n output[index + 1] = ((fa * ba * bg + alpha * fg) / ca) * 255;\n output[index + 2] = ((fa * ba * bb + alpha * fb) / ca) * 255;\n output[index + 3] = ca * 255;\n };\n if (strength > 0) {\n fr = 0;\n fg = 0;\n fb = 0;\n }\n else {\n strength = Math.abs(strength);\n fr = 1;\n fg = 1;\n fb = 1;\n }\n for (y = 0; y < inputHeight; y++) {\n for (x = 0; x < inputWidth; x++) {\n blend(\n // index\n (x + y * inputWidth) * 4, \n // data in\n data, \n // data out\n data, \n // opacity\n (dist(x, y) * strength) / dm);\n }\n }\n // @ts-ignore\n done(null, {\n data,\n width: imageData.width,\n height: imageData.height,\n });\n};\n\nvar noiseEffect = (options, done) => {\n const { imageData, level, monochrome = false } = options;\n if (!level)\n return done(null, imageData);\n const data = imageData.data;\n const l = data.length;\n let index = 0;\n let r;\n let g;\n let b;\n const rand = () => (-1 + Math.random() * 2) * 255 * level;\n const pixel = monochrome\n ? () => {\n const average = rand();\n return [average, average, average];\n }\n : () => {\n return [rand(), rand(), rand()];\n };\n for (; index < l; index += 4) {\n [r, g, b] = pixel();\n data[index] = data[index] + r;\n data[index + 1] = data[index + 1] + g;\n data[index + 2] = data[index + 2] + b;\n }\n // @ts-ignore\n done(null, {\n data,\n width: imageData.width,\n height: imageData.height,\n });\n};\n\nvar gammaEffect = (options, done) => {\n const { imageData, level } = options;\n if (!level)\n return done(null, imageData);\n const data = imageData.data;\n const l = data.length;\n let index = 0;\n let r;\n let g;\n let b;\n for (; index < l; index += 4) {\n r = data[index] / 255;\n g = data[index + 1] / 255;\n b = data[index + 2] / 255;\n data[index] = Math.pow(r, level) * 255;\n data[index + 1] = Math.pow(g, level) * 255;\n data[index + 2] = Math.pow(b, level) * 255;\n }\n // @ts-ignore\n done(null, {\n data,\n width: imageData.width,\n height: imageData.height,\n });\n};\n\nvar isIdentityMatrix = (matrix) => {\n /*\n [\n 1, 0, 0, 0, 0\n 0, 1, 0, 0, 0\n 0, 0, 1, 0, 0\n 0, 0, 0, 1, 0\n ]\n */\n const l = matrix.length;\n let v;\n const s = l >= 20 ? 6 : l >= 16 ? 5 : 3;\n for (let i = 0; i < l; i++) {\n v = matrix[i];\n if (v === 1 && i % s !== 0)\n return false;\n else if (v !== 0 && v !== 1)\n return false;\n }\n return true;\n};\n\nvar filterImageData = async (imageData, options = {}) => {\n const { colorMatrix, convolutionMatrix, gamma: gammaLevel, noise: noiseLevel, vignette: vignetteStrength, } = options;\n // filters\n const filters = [];\n // apply convolution matrix\n if (convolutionMatrix) {\n filters.push([convolutionEffect, { matrix: convolutionMatrix.clarity }]);\n }\n // apply noise\n if (gammaLevel > 0) {\n filters.push([gammaEffect, { level: 1.0 / gammaLevel }]);\n }\n // apply color matrix\n if (colorMatrix && !isIdentityMatrix(colorMatrix)) {\n filters.push([colorEffect, { matrix: colorMatrix }]);\n }\n // apply noise\n if (noiseLevel > 0 || noiseLevel < 0) {\n filters.push([noiseEffect, { level: noiseLevel }]);\n }\n // apply vignette\n if (vignetteStrength > 0 || vignetteStrength < 0) {\n filters.push([vignetteEffect, { strength: vignetteStrength }]);\n }\n // no changes\n if (!filters.length)\n return imageData;\n // builds effect chain\n const chain = (transforms, i) => `(err, imageData) => {\n (${transforms[i][0].toString()})(Object.assign({ imageData: imageData }, filterInstructions[${i}]), \n ${transforms[i + 1] ? chain(transforms, i + 1) : 'done'})\n }`;\n const filterChain = `function (options, done) {\n const filterInstructions = options.filterInstructions;\n const imageData = options.imageData;\n (${chain(filters, 0)})(null, imageData)\n }`;\n imageData = await thread(filterChain, [\n {\n imageData: imageData,\n filterInstructions: filters.map((t) => t[1]),\n },\n ], [imageData.data.buffer]);\n // the resizer returns a plain object, not an actual image data object, lets create one\n return imageDataObjectToImageData(imageData);\n};\n\nvar isNumber = (v) => typeof v === 'number';\n\nvar isEmoji = (str) => isString(str) &&\n str.match(/(?:[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\u0023-\\u0039]\\ufe0f?\\u20e3|\\u3299|\\u3297|\\u303d|\\u3030|\\u24c2|\\ud83c[\\udd70-\\udd71]|\\ud83c[\\udd7e-\\udd7f]|\\ud83c\\udd8e|\\ud83c[\\udd91-\\udd9a]|\\ud83c[\\udde6-\\uddff]|\\ud83c[\\ude01-\\ude02]|\\ud83c\\ude1a|\\ud83c\\ude2f|\\ud83c[\\ude32-\\ude3a]|\\ud83c[\\ude50-\\ude51]|\\u203c|\\u2049|[\\u25aa-\\u25ab]|\\u25b6|\\u25c0|[\\u25fb-\\u25fe]|\\u00a9|\\u00ae|\\u2122|\\u2139|\\ud83c\\udc04|[\\u2600-\\u26FF]|\\u2b05|\\u2b06|\\u2b07|\\u2b1b|\\u2b1c|\\u2b50|\\u2b55|\\u231a|\\u231b|\\u2328|\\u23cf|[\\u23e9-\\u23f3]|[\\u23f8-\\u23fa]|\\ud83c\\udccf|\\u2934|\\u2935|[\\u2190-\\u21ff])/g) !== null;\n\nvar hasProp = (obj, key) => obj.hasOwnProperty(key);\n\nvar isArray = (arr) => Array.isArray(arr);\n\nvar isApple = () => isIOS() || isMac();\n\nvar isWindows = () => /^win/i.test(navigator.platform);\n\n// macos: font-size: 123, x: 63.5, y: 110\n// windows: font-size: 112, x: 64, y: 103\n// android: font-size: 112, x: 64, y: 102\nlet x = 64;\nlet y = 102;\nlet fontSize = 112;\nlet hasSetValues = false;\nvar getEmojiSVG = (emoji, alt) => {\n if (!hasSetValues && isBrowser()) {\n if (isWindows())\n y = 103;\n if (isApple()) {\n x = 63.5;\n y = 110;\n fontSize = 123;\n }\n hasSetValues = true;\n }\n return `${emoji}`;\n};\n\nvar SVGToDataURL = (svg) => `data:image/svg+xml,${svg.replace('<', '%3C').replace('>', '%3E')}`;\n\nvar isBinary = (v) => v instanceof Blob;\n\nvar toPercentage = (value, total) => `${(value / total) * 100}%`;\n\nvar colorArrayToRGBA = (color) => `rgba(${Math.round(color[0] * 255)}, ${Math.round(color[1] * 255)}, ${Math.round(color[2] * 255)}, ${isNumber(color[3]) ? color[3] : 1})`;\n\n// tested and this seems a tiny bit faster than JSON.stringify\nvar objectUID = (obj) => Object.values(obj).join('_');\n\nvar timeout = (timeout = 0) => new Promise((resolve) => {\n setTimeout(resolve, timeout);\n});\n\n// cannot use a ObjectURL because of a webkit bug (throws error in chrome / safari)\n// returns true if every pixel's uint32 representation is 0 (or \"blank\")\nconst isContextBlank = (ctx) => {\n const buffer = new Uint32Array(ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data.buffer);\n return !buffer.some((color) => color !== 0);\n};\nconst width = 80;\nconst height = 80;\nconst whenSVGSafeForDrawing = async (img, delay = 0) => {\n // Safari has some trouble with custom fonts\n const canvas = h('canvas', { width, height });\n const ctx = canvas.getContext('2d');\n // always wait a short while because it's never ready on first draw\n await timeout(delay);\n // we draw the image so we can test if the content is drawn. If custom font isn't ready the canvas will be empty\n ctx.drawImage(img, 0, 0, width, height);\n if (isContextBlank(ctx) && delay <= 256)\n return await whenSVGSafeForDrawing(img, delay + 16);\n return true;\n};\n// store cached draw cycles here\nconst safariDrawCache = new Map();\nvar svgToImage = (svg, { safariCacheKey = '*' } = {}) => new Promise((resolve, reject) => {\n const img = new Image();\n img.onerror = () => reject(new Error('Failed to load SVG'));\n img.onload = () => {\n // uncomment to test difference between SVG as dataURL and SVG in page\n // if (document.getElementById('svgdiv')) document.getElementById('svgdiv').remove();\n // document.body.append(\n // h('div', {\n // id: 'svgdiv',\n // style: `\n // position:absolute;\n // top:0;\n // left:0;\n // display:flex;\n // pointer-events:none;\n // z-index:99999999999999;\n // filter:invert(100%);\n // opacity:.5;\n // outline:1px solid green;`,\n // innerHTML: svg,\n // })\n // );\n // We done!\n if (!isSafari() || !svg.includes('@font-face') || safariDrawCache.has(safariCacheKey)) {\n return resolve(img);\n }\n // wait for embedded fonts to load\n whenSVGSafeForDrawing(img).then(() => {\n safariDrawCache.set(safariCacheKey, true);\n resolve(img);\n });\n };\n img.src = 'data:image/svg+xml,' + svg;\n // uncomment to test difference between SVG as dataURL and SVG in page\n // const dupe = img.cloneNode() as HTMLElement;\n // if (document.getElementById('svgimage')) document.getElementById('svgimage').remove();\n // document.body.append(dupe);\n // dupe.id = 'svgimage';\n // dupe.style.cssText = `\n // pointer-events:none;\n // z-index:99999999999999;\n // position:absolute;\n // top:0;\n // left:0;\n // outline:1px solid purple;`;\n});\n\nvar blobToDataURL = (blob) => new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = () => reject(reader.error);\n reader.onload = () => resolve(reader.result);\n reader.readAsDataURL(blob);\n});\n\nvar pubsub = () => {\n let subs = [];\n return {\n sub: (event, callback) => {\n subs.push({ event, callback });\n return () => (subs = subs.filter((subscriber) => subscriber.event !== event || subscriber.callback !== callback));\n },\n pub: (event, value) => {\n subs\n .filter((sub) => sub.event === event)\n .forEach((sub) => sub.callback(value));\n }\n };\n};\n\n// used to make sure text is drawn outside of element in a correct way\nconst TextPadding = 32;\nconst textGetInitialLineOffset = ({ fontSize = 16, lineHeight = 20 } = {}) => Math.max(0, fontSize - lineHeight) * 0.5;\nconst textGetLineCount = (text = '') => {\n return text.split('\\n').length;\n};\nconst textGetStyles = ({ color = [0, 0, 0], fontSize = 16, fontFamily = 'sans-serif', fontVariant = 'normal', fontWeight = 'normal', fontStyle = 'normal', textAlign = 'left', letterSpacing = 'normal', lineHeight = 20, }, scalar = 1) => `font-size:${fontSize * scalar}px;font-style:${fontStyle};font-weight:${fontWeight};font-family:${fontFamily};font-variant:${fontVariant};line-height:${lineHeight * scalar}px;text-align:${textAlign};letter-spacing:${isNumber(letterSpacing) ? letterSpacing + 'px' : 'normal'};color:${colorArrayToRGBA(color)};`;\nconst textGetContentEditableStyles = (text, options, scalar = 1) => {\n const { width, height, disableNewline } = options;\n const isAutoWidth = !width;\n const allowNewline = disableNewline === false ? textGetLineCount(text) : undefined;\n const widthValue = isAutoWidth ? 'auto' : `${width * scalar}px`;\n const heightValue = height ? `${height * scalar}px` : 'auto';\n const wordBreak = isAutoWidth ? 'normal' : 'break-word';\n const whiteSpace = isAutoWidth && !allowNewline ? 'nowrap' : 'pre-line';\n const paddingTop = textGetInitialLineOffset(options) * scalar;\n return `max-width:none;min-width:auto;width:${widthValue};height:${heightValue};margin-top:0;margin-bottom:0;padding-top:${paddingTop}px;word-break:${wordBreak};word-wrap:normal;white-space:${whiteSpace};`;\n};\nconst TextMeasureCache = new Map();\nconst toParts = (node) => {\n const parts = [];\n for (const childNode of Array.from(node.childNodes)) {\n if (childNode.nodeType === Node.TEXT_NODE) {\n parts.push(textToLines(childNode));\n }\n else {\n parts.push(...toParts(childNode));\n }\n }\n return parts;\n};\nconst MeasureStyles = 'pointer-events:none;visibility:hidden;position:absolute;left:0;top:0;';\nconst textMeasure = (text = '', options) => {\n // gather props that impact size and test if can be found in cache\n let { width = 0, height = 'auto', fontSize, fontFamily, lineHeight, fontWeight, fontStyle, fontVariant, letterSpacing, } = options;\n const uid = objectUID({\n text,\n fontFamily,\n fontWeight,\n fontStyle,\n fontVariant,\n fontSize,\n lineHeight,\n letterSpacing,\n width,\n height,\n });\n let measurement = TextMeasureCache.get(uid);\n if (measurement)\n return measurement;\n // firefox limits font size to 1000px, so if we're above, we scale down before measuring and then scale up after measuring\n let fontScalar = 1;\n if (fontSize > 1000 && isFirefox()) {\n fontScalar = fontSize / 1000;\n fontSize = 1000;\n lineHeight = lineHeight * fontScalar;\n }\n const element = appendForMeasuring(h('pre', {\n contenteditable: 'true',\n spellcheck: 'false',\n style: `${MeasureStyles}${textGetStyles({\n fontFamily,\n fontWeight,\n fontStyle,\n fontVariant,\n fontSize,\n letterSpacing,\n lineHeight,\n })};${textGetContentEditableStyles(text, options)}\"`,\n innerHTML: text,\n }));\n const elementRect = element.getBoundingClientRect();\n // @ts-ignore\n const elementTextParts = toParts(element).flat();\n measurement = {\n // @ts-ignore\n signature: elementTextParts.map((line) => line.text).join('_;_'),\n textSize: sizeApply(sizeCreateFromAny(elementRect), (v) => Math.ceil(v * fontScalar)),\n };\n TextMeasureCache.set(uid, measurement);\n // clean up measured elements\n element.remove();\n return measurement;\n};\nconst textToLines = (node) => {\n const text = node.nodeValue;\n if (!text)\n return [];\n const textLength = text.length;\n const range = document.createRange();\n range.selectNodeContents(node);\n const lines = [];\n for (let i = 0; i < textLength; i++) {\n range.setStart(node, 0);\n range.setEnd(node, i + 1);\n // which line are we at\n const lineIndex = range.getClientRects().length - 1;\n // get current character\n const char = text.charAt(i);\n // new line, or add to current line\n if (!lines[lineIndex])\n lines.push(char);\n else\n lines[lineIndex] += char;\n }\n // merge text and rects\n const clientRects = range.getClientRects();\n return lines.map((line, index) => ({\n rect: clientRects[index],\n text: line,\n }));\n};\nconst TextSizeCache = new Map();\nconst textToSize = (text = '', options) => {\n const { width = 0, height = 0 } = options;\n if (width && height)\n return sizeCreate(width, height);\n // gather props that impact size and test if can be found in cache\n let { fontSize = 16, fontFamily, lineHeight = 20, fontWeight, fontStyle, fontVariant, letterSpacing, } = options;\n const uid = objectUID({\n text,\n fontFamily,\n fontWeight,\n fontStyle,\n fontVariant,\n fontSize,\n letterSpacing,\n lineHeight,\n width,\n });\n let size = TextSizeCache.get(uid);\n if (size)\n return size;\n // firefox limits font size to 1000px, so if we're above, we scale down before measuring and then scale up after measuring\n let fontScalar = 1;\n if (fontSize > 1000 && isFirefox()) {\n fontScalar = fontSize / 1000;\n fontSize = 1000;\n lineHeight = lineHeight / fontScalar;\n }\n // new measurement needed\n const element = appendForMeasuring(h('pre', {\n contenteditable: 'true',\n spellcheck: 'false',\n style: `${MeasureStyles}${textGetStyles({\n ...options,\n fontSize,\n lineHeight,\n })};${textGetContentEditableStyles(text, options)}\"`,\n innerHTML: text,\n }));\n const rect = element.getBoundingClientRect();\n size = sizeCreateFromAny(rect);\n // cache measurement for next query\n size = sizeApply(size, (v) => v * fontScalar);\n // use fixed width if supplied\n if (width)\n size.width = width;\n TextSizeCache.set(uid, size);\n return size;\n};\nconst getPathFromURL = (url) => url.pathname.split('/').slice(0, -1).join('/');\nconst createStyleSheetLoader = (url, willRequest) => {\n const { sub, pub } = pubsub();\n let text;\n let error;\n Promise.resolve(willRequest && willRequest(url, { resourceType: 'stylesheet' })).then((requestInit) => {\n // prevent load\n if (requestInit === false) {\n error = 'requestPrevented';\n return pub('error', error);\n }\n // get props\n const { headers, credentials } = requestInit || {};\n // fetching\n fetch(url, { headers, credentials })\n .then((res) => res.text())\n .then((txt) => {\n text = txt;\n pub('load', text);\n })\n .catch((err) => {\n error = err;\n pub('error', error);\n });\n });\n return {\n sub: (event, cb) => {\n if (event === 'load' && text)\n return cb(text);\n if (event === 'error' && error)\n return cb(error);\n sub(event, cb);\n },\n };\n};\nconst StyleSheetLoaders = new Map();\nconst loadStylesheet = (url, willRequest) => new Promise((resolve, reject) => {\n let loader = StyleSheetLoaders.get(url);\n if (typeof loader === 'undefined') {\n loader = createStyleSheetLoader(url, willRequest);\n StyleSheetLoaders.set(url, loader);\n }\n loader.sub('load', resolve);\n loader.sub('error', reject);\n});\n// use this method to access rules because when accessing third party stylesheetes like google fonts it will throw a warning\nconst getRulesRemote = async (src, willRequest, styleNonce) => {\n // could not access, let's try to load\n let styleSheetContent;\n try {\n styleSheetContent = await loadStylesheet(src, willRequest);\n }\n catch (err) {\n return [];\n }\n const styleProps = {\n innerHTML: styleSheetContent,\n id: getUniqueId(),\n };\n if (styleNonce)\n styleProps.nonce = styleNonce;\n const styleElement = h('style', styleProps);\n document.head.append(styleElement);\n const tempStyleSheet = Array.from(document.styleSheets).find((styleSheet) => {\n const node = styleSheet.ownerNode;\n return node.id === styleElement.id;\n });\n styleElement.remove();\n return Array.from(tempStyleSheet.cssRules);\n};\nconst RemoteStyleSheetRulesCache = new Map();\nconst filterFontFaceRules = (rules) => rules.filter((rule) => rule instanceof CSSFontFaceRule);\nconst filterImportRules = (rules) => rules.filter((rule) => rule instanceof CSSImportRule);\nconst getFontFaceRulesSafe = async (styleSheet, willRequest, styleNonce) => {\n // if cached rules, return\n if (RemoteStyleSheetRulesCache.has(styleSheet.href))\n return RemoteStyleSheetRulesCache.get(styleSheet.href);\n // try to get rules\n let rules;\n try {\n // get all rules\n rules = Array.from(styleSheet.cssRules);\n // get @imports\n for (const importRule of filterImportRules(rules)) {\n const url = importRule.href;\n // already loaded this one before\n if (RemoteStyleSheetRulesCache.has(url)) {\n const remoteRules = RemoteStyleSheetRulesCache.get(url);\n rules = [...rules, ...remoteRules];\n continue;\n }\n // get remote rules for this @import\n const remoteRules = await getRulesRemote(url, willRequest, styleNonce);\n // remember returned rules for next run\n RemoteStyleSheetRulesCache.set(url, remoteRules);\n // add found rules\n rules = [...rules, ...remoteRules];\n }\n }\n catch (err) {\n const url = styleSheet.href;\n // this tries to load the styles with `fetch`\n rules = await getRulesRemote(url, willRequest, styleNonce);\n // remember returned rules for next run\n RemoteStyleSheetRulesCache.set(url, rules);\n }\n // only interested in font face rules\n return filterFontFaceRules(rules);\n};\nconst getCSSPropertyValue = (rule, name) => rule.style.getPropertyValue(name);\nconst isMatchingFontRule = (rule, fontFamily) => {\n if (!rule.style)\n return false;\n const family = getCSSPropertyValue(rule, 'font-family').replace(/^\"|\"$/g, '');\n return family == fontFamily;\n};\nconst getMatchingFontRule = (rules, fontFamily) => {\n const res = [];\n for (const fontRule of rules) {\n const isMatch = isMatchingFontRule(fontRule, fontFamily);\n if (!isMatch)\n continue;\n res.push(fontRule);\n }\n return res;\n};\nconst getDocumentStylesheetFontFaceRules = async (willRequest, styleNonce) => {\n const styleSheets = Array.from(document.styleSheets).map((styleSheet) => getFontFaceRulesSafe(styleSheet, willRequest, styleNonce));\n const ruleSets = await Promise.all(styleSheets);\n const rules = [];\n ruleSets.forEach((ruleSet) => rules.push(...ruleSet));\n return rules;\n};\nconst getFontSources = async (fontFamily, willRequest, styleNonce) => {\n // get matching font face rules\n const fontFaceRules = await getDocumentStylesheetFontFaceRules(willRequest, styleNonce);\n // rules matching this font\n const matchingRules = getMatchingFontRule(fontFaceRules, fontFamily);\n if (!matchingRules.length)\n return [];\n return matchingRules.map((rule) => {\n // create stylesheet root path so we can determine font path\n const url = rule.parentStyleSheet &&\n rule.parentStyleSheet.href &&\n new URL(rule.parentStyleSheet.href);\n const styleSheetPath = url ? url.origin + getPathFromURL(url) + '/' : '';\n // create font source\n const fontSources = rule.style.getPropertyValue('src');\n const fontSrcFirst = fontSources.match(/url\\(\"?(.*?)\"?\\)/)[1];\n // get styles\n const fontProps = Array.from(rule.style)\n .filter((prop) => prop != 'src')\n .reduce((props, key) => {\n props += key + ':' + getCSSPropertyValue(rule, key) + ';';\n return props;\n }, '');\n // merge together to create path (Safari font URL is absolute so if it is we only use that url)\n return [\n /^http/.test(fontSrcFirst) ? fontSrcFirst : styleSheetPath + fontSrcFirst,\n fontProps,\n ];\n });\n};\nconst FontLocal = new Map();\nconst FontCache = new Map();\n// application/x-font-ttf\n// application/x-font-truetype\n// application/x-font-opentype\n// application/font-woff\n// application/font-woff2\n// font/otf\n// font/ttf\n// font/woff\n// font/woff2\nconst getFontFormatByMimeType = (type) => {\n if (!type || /woff2/.test(type))\n return 'woff2';\n if (/woff/.test(type))\n return 'woff';\n if (/ttf|truetype/.test(type))\n return 'truetype';\n if (/otf|opentype/.test(type))\n return 'opentype';\n if (/svg/.test(type))\n return 'svg';\n return 'woff2';\n};\nconst getFontFaceEmbed = async (fontFamily, willRequest, styleNonce) => {\n // todo test if is local font\n if (FontLocal.get(fontFamily))\n return;\n let fontStyles = FontCache.get(fontFamily);\n if (!fontStyles) {\n // not cached yet, let's find the font source and turn it into a dataURL\n const fontSources = await getFontSources(fontFamily, willRequest, styleNonce);\n // no source found, is local font\n if (!fontSources.length) {\n FontLocal.set(fontFamily, true);\n return;\n }\n const fontFaces = [];\n for (const [fontSource, fontProps] of fontSources) {\n const blob = await fetch(fontSource).then((res) => res.blob());\n const dataType = getFontFormatByMimeType(blob.type);\n const dataURL = await blobToDataURL(blob);\n fontFaces.push(`@font-face { src:url(${dataURL}) format('${dataType}');${fontProps};font-display:block; }`);\n }\n fontStyles = fontFaces.join('');\n // cache the font so it's super fast in next request\n FontCache.set(fontFamily, fontStyles);\n }\n return fontStyles;\n};\nvar textToImage = async (text = '', options) => {\n // exit if no text\n if (!text.length)\n return;\n let { color, imageWidth = 300, imageHeight = 150, paddingTop = 0, paddingRight = TextPadding, paddingBottom = 0, paddingLeft = TextPadding, fontFamily, fontSize, pixelRatio = 1, willRequest, outline, blur, styleNonce, } = options;\n // manage high font-size on firefox, don't blanket handle it here as Chrome has trouble scaling svg context\n let fontScalar = 1;\n let fontScalarStyles = '';\n if (fontSize > 1000 && isFirefox()) {\n fontScalar = fontSize / 1000;\n fontScalarStyles = `transform-origin:0 0;transform:scale(${fontScalar})`;\n }\n const computedScalar = pixelRatio / fontScalar;\n const width = (imageWidth + paddingLeft + paddingRight) * pixelRatio;\n const height = (imageHeight + paddingBottom + paddingTop) * pixelRatio;\n // build font style tag if should be embedded\n const fontEmbed = await getFontFaceEmbed(fontFamily, willRequest, styleNonce);\n let fontFaceStyleTag = '';\n if (fontEmbed)\n fontFaceStyleTag = `${fontEmbed}`;\n // make sure text is encoded as characters before rendering in our SVG\n let textEncoded = text\n // these characters need to be encoded in an SVG dataURL\n .replace(/%/g, '%25')\n .replace(/#/g, '%23')\n .replace(/ /g, '\\u00a0')\n // replace non escaped ampersands\n .replace(/&(?!#\\d{4};|[a-z]+;)/gi, '&');\n // no non-closing tags allowed in foreignObject as is xhtml namespace, safari and firefox don't draw newline character so replace with
\n const textFinal = textEncoded.replace(/
|\\n/g, '
');\n const computedPaddingTop = paddingTop * computedScalar;\n const computedPaddingRight = paddingRight * computedScalar;\n const computedPaddingBottom = paddingBottom * computedScalar;\n const computedPaddingLeft = paddingLeft * computedScalar;\n // define padding style\n const paddingStyles = `top:${computedPaddingTop}px;right:${computedPaddingRight}px;bottom:${computedPaddingBottom}px;left:${computedPaddingLeft}px`;\n // if shadow or outline\n let fontColor = color;\n let outlineStyles = '';\n let blurStyles = '';\n if (outline || blur) {\n const computedColor = colorArrayToRGBA(color);\n if (outline) {\n // we hide text\n fontColor = [0, 0, 0, 0];\n // we use text color for outline\n outlineStyles = `-webkit-text-stroke: ${outline * 2 * computedScalar}px ${computedColor}`;\n }\n if (blur) {\n if (isSafari()) {\n // Safari: somehow a text-shadow with a blur doesn't work when drawing the final image, so instead we use the blur()filter. The .4 approximates the amount of blur when using text-shadow.\n blurStyles = `filter:blur(${blur * computedScalar * 0.4}px)`;\n }\n else {\n // we hide text\n fontColor = [0, 0, 0, 0];\n // we use text color for shadow\n blurStyles = `text-shadow: 0 0 ${blur * computedScalar}px ${computedColor}`;\n }\n }\n }\n const textStyles = textGetStyles({ ...options, color: fontColor, fontSize }, computedScalar);\n const textContentEditableStyles = textGetContentEditableStyles(text, options, computedScalar);\n // build svg string\n const svg = `
${fontFaceStyleTag}
${textFinal}
`;\n return svgToImage(svg, { safariCacheKey: fontFamily });\n};\n\nconst shapeEqual = (a, b) => {\n return JSON.stringify(a) === JSON.stringify(b);\n};\nconst shapeDeepCopy = (shape) => {\n const shapeShallowCopy = { ...shape };\n const shapeDeepCopy = deepCopy(shapeShallowCopy);\n return shapeDeepCopy;\n};\nconst getContextSize = (context, size = {}) => {\n const contextAspectRatio = rectAspectRatio(context);\n let xOut;\n let yOut;\n const xIn = size.width || size.rx;\n const yIn = size.height || size.ry;\n if (xIn && yIn)\n return sizeClone(size);\n if (xIn || yIn) {\n xOut = parseFloat(xIn || Number.MAX_SAFE_INTEGER);\n yOut = parseFloat(yIn || Number.MAX_SAFE_INTEGER);\n const min = Math.min(xOut, yOut);\n if (isString(xIn) || isString(yIn)) {\n xOut = `${min}%`;\n yOut = `${min * contextAspectRatio}%`;\n }\n else {\n xOut = min;\n yOut = min;\n }\n }\n else {\n const min = 10;\n xOut = `${min}%`;\n yOut = `${min * contextAspectRatio}%`;\n }\n const xProp = size.width ? 'width' : size.rx ? 'rx' : undefined;\n const yProp = size.width ? 'height' : size.rx ? 'ry' : undefined;\n return {\n [xProp || 'width']: xOut,\n [yProp || 'height']: yOut,\n };\n};\nconst shapeCreateFromEmoji = (emoji, props = {}) => {\n return {\n width: undefined,\n height: undefined,\n ...props,\n aspectRatio: 1,\n backgroundImage: SVGToDataURL(getEmojiSVG(emoji)),\n };\n};\nconst shapeCreateFromImage = (src, shapeProps = {}) => {\n const shapeDefaultLayout = shapeIsEllipse(shapeProps)\n ? {}\n : {\n width: undefined,\n height: undefined,\n aspectRatio: undefined,\n };\n const shape = {\n // required/default image shape props\n backgroundColor: [0, 0, 0, 0],\n // set default layout props\n ...shapeDefaultLayout,\n // merge with custom props\n ...shapeProps,\n // set image\n backgroundImage: \n // is svg or URL\n isString(src) ? src : isBinary(src) ? URL.createObjectURL(src) : src,\n };\n return shape;\n};\nconst shapeCreateFromPreset = (preset, parentRect) => {\n let shape;\n if (isString(preset) || isBinary(preset)) {\n // default props for \"quick\" preset\n const shapeOptions = {\n ...getContextSize(parentRect),\n backgroundSize: 'contain',\n };\n // if is emoji, create default markup,\n if (isEmoji(preset)) {\n shape = shapeCreateFromEmoji(preset, shapeOptions);\n }\n // is URL, create default markup for image\n else {\n shape = shapeCreateFromImage(preset, shapeOptions);\n }\n }\n else {\n // is using src shortcut\n if (preset.src) {\n const contextSize = getContextSize(parentRect, preset.shape || preset);\n // shape options\n const shapeOptions = {\n // default shape styles\n ...preset.shape,\n // precalcualte size of shape in context\n ...contextSize,\n };\n // should auto-fix aspect ratio\n if (preset.width && preset.height && !hasProp(shapeOptions, 'aspectRatio')) {\n const width = shapeGetPropPixelValue(contextSize, 'width', parentRect);\n const height = shapeGetPropPixelValue(contextSize, 'height', parentRect);\n shapeOptions.aspectRatio = getAspectRatio(width, height);\n }\n // should auto-contain sticker in container\n if (!shapeOptions.backgroundSize && !preset.shape && (!preset.width || !preset.height))\n shapeOptions.backgroundSize = 'contain';\n // emoji markup\n if (isEmoji(preset.src)) {\n shape = shapeCreateFromEmoji(preset.src, shapeOptions);\n }\n // is url\n else {\n shape = shapeCreateFromImage(preset.src, shapeOptions);\n }\n }\n // should have markup defined\n else if (preset.shape) {\n shape = shapeDeepCopy(preset.shape);\n }\n }\n if (hasProp(shape, 'backgroundImage')) {\n // set transparent background if no background color defined\n if (!hasProp(shape, 'backgroundColor')) {\n shape.backgroundColor = [0, 0, 0, 0];\n }\n // for rectangles with image presets, disable these styles by default\n if (!hasProp(shape, 'disableStyle')) {\n shape.disableStyle = ['cornerRadius', 'backgroundColor', 'strokeColor', 'strokeWidth'];\n }\n }\n return parentRect ? shapeComputeDisplay(shape, parentRect) : shape;\n};\nconst shapeLineGetStartPoint = (line) => vectorCreate(line.x1, line.y1);\nconst shapeLineGetEndPoint = (line) => vectorCreate(line.x2, line.y2);\n//#endregion\n//#region shape testing\n// shape types\nconst shapeIsText = (shape) => hasProp(shape, 'text');\nconst shapeIsTextBlock = (shape) => shapeIsText(shape) && !(shapeHasRelativeSize(shape) || hasProp(shape, 'width'));\nconst shapeIsTextBox = (shape) => shapeIsText(shape) && (shapeHasRelativeSize(shape) || hasProp(shape, 'width'));\nconst shapeIsRect = (shape) => !shapeIsText(shape) && shapeHasComputedSize(shape);\nconst shapeIsEllipse = (shape) => hasProp(shape, 'rx');\nconst shapeIsLine = (shape) => hasProp(shape, 'x1');\nconst shapeIsPath = (shape) => hasProp(shape, 'points');\n// shape state\nconst shapeIsTextEmpty = (shape) => shapeIsText(shape) && !shape.text.length;\nconst shapeIsTextEditing = (shape) => shapeIsText(shape) && shape.isEditing;\nconst shapeIsVisible = (shape) => hasProp(shape, 'opacity') ? shape.opacity > 0 : true;\nconst shapeIsSelected = (shape) => shape.isSelected;\nconst shapeIsEditing = (shape) => shape.isEditing;\nconst shapeIsDraft = (shape) => shape._isDraft;\nconst shapeHasSize = (shape) => hasProp(shape, 'width') && hasProp(shape, 'height');\nconst shapeHasNumericStroke = (shape) => isNumber(shape.strokeWidth) && shape.strokeWidth > 0; // only relevant if is bigger than 0\nconst shapeHasRelativePosition = (shape) => {\n const hasRight = hasProp(shape, 'right');\n const hasBottom = hasProp(shape, 'bottom');\n return hasRight || hasBottom;\n};\nconst shapeHasTexture = (shape) => hasProp(shape, 'backgroundImage') || hasProp(shape, 'text');\nconst shapeHasRelativeSize = (shape) => ((hasProp(shape, 'x') || hasProp(shape, 'left')) && hasProp(shape, 'right')) ||\n ((hasProp(shape, 'y') || hasProp(shape, 'top')) && hasProp(shape, 'bottom'));\nconst shapeHasComputedSize = (shape) => shapeHasSize(shape) || shapeHasRelativeSize(shape);\nconst shapeIsVisibleColor = (shape, colorProp) => {\n const value = shape[colorProp];\n if (!value)\n return false;\n if (value.length === 3)\n return true;\n return value[3] > 0;\n};\nconst shapeHasStroke = (shape) => shape.strokeWidth && shapeIsVisibleColor(shape, 'strokeColor');\n// actions\nconst shapeSelect = (shape) => {\n shape.isSelected = true;\n return shape;\n};\nconst shapeMakeDraft = (shape) => {\n shape._isDraft = true;\n return shape;\n};\nconst shapeMakeFinal = (shape) => {\n shape._isDraft = false;\n return shape;\n};\n// rights\nconst shapeCanStyle = (shape, style) => {\n if (shape.disableStyle === true)\n return false;\n if (isArray(shape.disableStyle) && style) {\n return !shape.disableStyle.includes(style);\n }\n return true;\n};\nconst shapeCanAcceptSnap = (shape) => shape.disableAcceptSnap !== true;\nconst shapeCanSelect = (shape) => shape.disableSelect !== true;\nconst shapeCanRemove = (shape) => shape.disableRemove !== true;\nconst shapeCanDuplicate = (shape) => shape.disableDuplicate !== true && shapeCanMove(shape);\nconst shapeCanReorder = (shape) => shape.disableReorder !== true;\nconst shapeCanFlip = (shape) => {\n if (shape.disableFlip)\n return false;\n if (shapeIsDraft(shape) || shapeHasRelativePosition(shape))\n return false;\n return shapeHasTexture(shape);\n};\nconst shapeCanInput = (shape, input) => {\n if (!shapeIsText(shape))\n return false;\n if (shape.disableInput === true)\n return false;\n if (isFunction(shape.disableInput))\n return shape.disableInput(input != null ? input : shape.text);\n return input || true;\n};\nconst shapeCanChangeTextLayout = (shape, layout) => {\n if (shape.disableTextLayout === true)\n return false;\n if (isArray(shape.disableTextLayout) && layout) {\n return !shape.disableTextLayout.includes(layout);\n }\n return true;\n};\nconst shapeCanSelectPoint = (shape) => {\n // can't select points if below 3\n return shapeCanAddPoint(shape) && shape.points.length > 3;\n};\nconst shapeCanAddPoint = (shape) => {\n if (!shapeIsPath(shape))\n return false;\n if (shape.disableAddPoints === undefined || shape.disableAddPoints === true)\n return false;\n return true;\n};\nconst shapeCanManipulate = (shape) => shape.disableManipulate !== true && !shapeIsDraft(shape) && !shapeHasRelativePosition(shape);\nconst shapeCanMove = (shape) => shapeCanManipulate(shape) && shape.disableMove !== true;\nconst shapeCanResize = (shape) => shapeCanManipulate(shape) &&\n shapeCanMove(shape) &&\n shape.disableResize !== true &&\n (shapeHasSize(shape) ||\n shapeIsTextBox(shape) ||\n shapeIsEllipse(shape) ||\n shapeIsLine(shape) ||\n (shapeIsPath(shape) && hasProp(shape, 'pathClose')));\nconst shapeCanRotate = (shape) => shapeCanManipulate(shape) &&\n shape.disableRotate !== true &&\n (shapeHasSize(shape) || hasProp(shape, 'text') || shapeIsEllipse(shape));\n//#endregion\n//#region shape formatting\nconst shapeDeleteRelativeProps = (shape) => {\n delete shape.left;\n delete shape.right;\n delete shape.top;\n delete shape.bottom;\n return shape;\n};\nconst shapeDeleteTransformProps = (shape) => {\n delete shape.rotation;\n return shape;\n};\nconst shapeFormatStroke = (shape) => {\n shape.strokeWidth = shape.strokeWidth || 1;\n shape.strokeColor = shape.strokeColor || [0, 0, 0];\n return shape;\n};\nconst shapeFormatFill = (shape) => {\n shape.backgroundColor = shape.backgroundColor\n ? shape.backgroundColor\n : shape.strokeWidth || shape.backgroundImage\n ? undefined\n : [0, 0, 0];\n return shape;\n};\n// encode illegal characters\n// first replace entities, then encode characters that are left, then replace entities\nconst shapeFormatHTMLText = (shape) => {\n let text = shape.text;\n text = text.replace(/&(#[0-9]+|[a-z]+);/gi, (_, value) => `___${value}___`);\n text = text.replace(/&/, '&');\n shape.text = text.replace(/___(#[0-9]+|[a-z]+)___/gi, (_, value) => `&${value};`);\n};\nconst shapeFormatPlainText = (shape) => {\n shapeFormatHTMLText(shape);\n let text = shape.text;\n text = text.replace(//g, '<');\n shape.text = text;\n};\nconst shapeExpandTextShadow = (shape) => {\n if (!shape.textShadow)\n return shape;\n const [x, y, blur, color] = shape.textShadow;\n shape.textShadowX = x;\n shape.textShadowY = y;\n shape.textShadowBlur = blur;\n shape.textShadowColor = color;\n delete shape.textShadow;\n return shape;\n};\nconst shapeExpandOutline = (shape) => {\n if (!shape.textOutline)\n return shape;\n const [width, color] = shape.textOutline;\n shape.textOutlineWidth = width;\n shape.textOutlineColor = color;\n delete shape.textOutline;\n};\nconst shapeFormatText = (shape) => {\n shape.fontSize = shape.fontSize || '4%';\n shape.fontFamily = shape.fontFamily || 'sans-serif';\n shape.fontWeight = shape.fontWeight || 'normal';\n shape.fontStyle = shape.fontStyle || 'normal';\n shape.fontVariant = shape.fontVariant || 'normal';\n shape.lineHeight = shape.lineHeight || '120%';\n shape.color = shape.color || [0, 0, 0];\n shape.format = shape.format === 'html' ? 'html' : 'text';\n shapeExpandTextShadow(shape);\n shapeExpandOutline(shape);\n if (shape.format === 'html')\n shapeFormatHTMLText(shape);\n else\n shapeFormatPlainText(shape);\n return shapeIsTextBlock(shape) ? shapeFormatTextBlock(shape) : shapeFormatTextBox(shape);\n};\nconst shapeFormatTextBlock = (shape) => {\n // for now can't align if not allowed to use newlines\n if (shape.disableNewline !== false) {\n delete shape.textAlign;\n shape.text = shape.text.replace(/\\n/g, ' ');\n }\n return shapeDeleteRelativeProps(shape);\n};\nconst shapeFormatTextBox = (shape) => {\n shape.textAlign = shape.textAlign || 'left';\n return shape;\n};\nconst shapeFormatRect = (shape) => {\n shape.cornerRadius = shape.cornerRadius || 0;\n shape.strokeWidth = shape.strokeWidth || 0;\n shape.strokeColor = shape.strokeColor || [0, 0, 0];\n return shapeFormatFill(shape);\n};\nconst shapeFormatEllipse = (shape) => {\n shape.strokeWidth = shape.strokeWidth || 0;\n shape.strokeColor = shape.strokeColor || [0, 0, 0];\n return shapeFormatFill(shape);\n};\nconst shapeFormatPath = (shape) => {\n shapeFormatStroke(shape);\n shapeDeleteTransformProps(shape);\n if (!hasProp(shape, 'pathClose')) {\n shape.disableResize = true;\n if (!hasProp(shape, 'disableMove')) {\n shape.disableMove = true;\n }\n }\n return shapeDeleteRelativeProps(shape);\n};\nconst shapeFormatLine = (shape) => {\n shapeFormatStroke(shape);\n shape.lineStart = shape.lineStart || undefined;\n shape.lineEnd = shape.lineEnd || undefined;\n shapeDeleteTransformProps(shape);\n return shapeDeleteRelativeProps(shape);\n};\nconst shapeFormatDefaults = (shape) => {\n if (!isString(shape.id))\n shape.id = getUniqueId();\n if (!hasProp(shape, 'rotation'))\n shape.rotation = 0;\n if (!hasProp(shape, 'opacity'))\n shape.opacity = 1;\n if (!hasProp(shape, 'disableErase'))\n shape.disableErase = true;\n};\nconst shapeFormat = (shape) => {\n shapeFormatDefaults(shape);\n if (shapeIsText(shape)) {\n shapeFormatText(shape);\n }\n else if (shapeIsRect(shape)) {\n shapeFormatRect(shape);\n }\n else if (shapeIsPath(shape)) {\n shapeFormatPath(shape);\n }\n else if (shapeIsLine(shape)) {\n shapeFormatLine(shape);\n }\n else if (shapeIsEllipse(shape)) {\n shapeFormatEllipse(shape);\n }\n return shape;\n};\nconst shapeGetDescription = (shape) => {\n if (shapeIsText(shape))\n return 'text';\n if (shapeIsRect(shape))\n return 'rectangle';\n if (shapeIsPath(shape))\n return 'path';\n if (shapeIsLine(shape))\n return 'line';\n if (shapeIsEllipse(shape))\n return 'ellipse';\n return;\n};\n//#endregion\nconst toPixelValue = (percentage, total) => (parseFloat(percentage) / 100) * total;\n//#region shape transforming\nconst xRegExp = new RegExp(/^x|left|right|^width|rx|fontSize|eraseRadius|feather|cornerRadius|strokeWidth|strokeDash/, 'i');\nconst yRegExp = new RegExp(/^y|top|bottom|^height|ry/, 'i');\nconst compute = (key, value, { width, height }) => {\n // handle array of percentage values\n if (Array.isArray(value)) {\n return value.map((v) => {\n if (isObject(v)) {\n // update the object itself\n computeProps(v, { width, height });\n }\n if (isString(v)) {\n v = compute(key, v, { width, height });\n }\n return v;\n });\n }\n // no need to compute (test with typeof instead of for perf)\n if (typeof value !== 'string')\n return value;\n if (!value.endsWith('%'))\n return value;\n const f = parseFloat(value) / 100;\n if (xRegExp.test(key))\n return fixPrecision(width * f, 6);\n if (yRegExp.test(key))\n return fixPrecision(height * f, 6);\n // dont auto-compute\n return value;\n};\nconst shapeComputeProp = compute;\nconst computeRelativeToValue = (percentage, value) => {\n return Math.round(value * (parseFloat(percentage) / 100));\n};\nconst PROPS_RELATIVE_TO_FONT_SIZE = [\n 'lineHeight',\n 'textOutlineWidth',\n 'textShadowX',\n 'textShadowY',\n 'textShadowBlur',\n];\nconst computeProps = (obj, context) => {\n Object.entries(obj).map(([key, value]) => {\n obj[key] = compute(key, value, context);\n });\n // no text, skip\n if (!obj.text)\n return;\n // calculate props relative to font size\n PROPS_RELATIVE_TO_FONT_SIZE.filter((key) => isString(obj[key])).forEach((key) => {\n obj[key] = computeRelativeToValue(obj[key], obj.fontSize);\n });\n};\nconst shapeComputeDisplay = (shape, context) => {\n computeProps(shape, context);\n shapeComputeRect(shape, context);\n return shape;\n};\nconst shapeGetPropPixelTotal = (prop, parentRect) => {\n let total;\n if (/^x|width|rx|fontSize|strokeWidth|cornerRadius/.test(prop)) {\n total = parentRect.width;\n }\n else if (/^y|height|ry/.test(prop)) {\n total = parentRect.height;\n }\n return total;\n};\nconst shapeUpdateProp = (shape, prop, value, parentRect) => {\n if (!isString(shape[prop])) {\n shape[prop] = value;\n return shape;\n }\n const total = shapeGetPropPixelTotal(prop, parentRect);\n shape[prop] = total === undefined ? value : toPercentage(value, total);\n return shape;\n};\nconst shapeGetPropPixelValue = (shape, prop, parentRect) => {\n if (Array.isArray(shape[prop]))\n return shape[prop].map((item) => {\n return Object.entries(item).reduce((mapped, [key, value]) => {\n mapped[key] = isString(value)\n ? toPixelValue(value, shapeGetPropPixelTotal(key, parentRect))\n : value;\n return mapped;\n }, {});\n });\n if (!isString(shape[prop]))\n return shape[prop];\n return toPixelValue(shape[prop], shapeGetPropPixelTotal(prop, parentRect));\n};\nconst shapeGetPropsPixelValues = (shape, props, parentRect) => {\n return props.reduce((prev, prop) => {\n const value = shapeGetPropPixelValue(shape, prop, parentRect);\n prev[prop] = value;\n return prev;\n }, {});\n};\nconst shapeUpdateProps = (shape, props, parentRect) => {\n Object.keys(props).forEach((key) => shapeUpdateProp(shape, key, props[key], parentRect));\n return shape;\n};\nconst shapeBounds = (shape) => {\n const rect = rectCreateEmpty();\n const strokeWidth = shape.strokeWidth || 0;\n if (shapeIsRect(shape)) {\n rect.x = shape.x - strokeWidth * 0.5;\n rect.y = shape.y - strokeWidth * 0.5;\n rect.width = shape.width + strokeWidth;\n rect.height = shape.height + strokeWidth;\n }\n else if (shapeIsLine(shape)) {\n const { x1, y1, x2, y2 } = shape;\n const left = Math.abs(Math.min(x1, x2));\n const right = Math.abs(Math.max(x1, x2));\n const top = Math.abs(Math.min(y1, y2));\n const bottom = Math.abs(Math.min(y1, y2));\n rect.x = left + strokeWidth * 0.5;\n rect.y = right + strokeWidth * 0.5;\n rect.width = right - left + strokeWidth;\n rect.height = bottom - top + strokeWidth;\n }\n else if (shapeIsEllipse(shape)) {\n rect.x = shape.x - shape.rx + strokeWidth * 0.5;\n rect.y = shape.y - shape.ry + strokeWidth * 0.5;\n rect.width = shape.rx * 2 + strokeWidth;\n rect.height = shape.ry * 2 + strokeWidth;\n }\n if (rect && hasProp(shape, 'rotation')) {\n rectRotate(rect, shape.rotation);\n }\n return rectToBounds(rect);\n};\nconst shapesBounds = (shapes, parentRect) => {\n const bounds = shapes\n .filter((shape) => shape.x < 0 || shape.y < 0 || shape.x1 < 0 || shape.y1 < 0)\n .reduce((bounds, shape) => {\n const [top, right, bottom, left] = shapeBounds(shape);\n bounds.top = Math.min(top, bounds.top);\n bounds.left = Math.min(left, bounds.left);\n bounds.bottom = Math.max(bottom, bounds.bottom);\n bounds.right = Math.max(right, bounds.right);\n return bounds;\n }, {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n });\n if (bounds.right > 0)\n bounds.right -= parentRect.width;\n if (bounds.bottom > 0)\n bounds.bottom -= parentRect.height;\n return bounds;\n};\nconst shapesFromCompositShape = (shape, parentRect, parser) => {\n const shapeCopy = shapeDeepCopy(shape);\n shapeComputeDisplay(shapeCopy, parentRect);\n const shapes = parser(shapeCopy);\n return Array.isArray(shapes) ? shapes : [shapes];\n};\nconst shapeComputeRect = (shape, context) => {\n if (hasProp(shape, 'left'))\n shape.x = shape.left;\n if (hasProp(shape, 'right') && !isString(shape.right)) {\n const r = context.width - shape.right;\n if (hasProp(shape, 'left')) {\n shape.x = shape.left;\n shape.width = Math.max(0, r - shape.left);\n }\n else if (hasProp(shape, 'width')) {\n shape.x = r - shape.width;\n }\n }\n if (hasProp(shape, 'top'))\n shape.y = shape.top;\n if (hasProp(shape, 'bottom') && !isString(shape.bottom)) {\n const b = context.height - shape.bottom;\n if (hasProp(shape, 'top')) {\n shape.y = shape.top;\n shape.height = Math.max(0, b - shape.top);\n }\n else if (hasProp(shape, 'height')) {\n shape.y = b - shape.height;\n }\n }\n return shape;\n};\n// currently only used for drawing to canvas in output\nconst shapeScale = (shape, scale) => {\n if (shapeIsPath(shape)) {\n shape.points\n .filter((point) => isNumber(point.x))\n .forEach((point) => {\n point.x *= scale;\n point.y *= scale;\n });\n }\n if (shapeIsLine(shape) && isNumber(shape.x1)) {\n shape.x1 *= scale;\n shape.y1 *= scale;\n shape.x2 *= scale;\n shape.y2 *= scale;\n }\n if (isNumber(shape.x) && isNumber(shape.y)) {\n shape.x *= scale;\n shape.y *= scale;\n }\n if (isNumber(shape.width) && isNumber(shape.height)) {\n shape.width *= scale;\n shape.height *= scale;\n }\n if (isNumber(shape.rx) && isNumber(shape.ry)) {\n shape.rx *= scale;\n shape.ry *= scale;\n }\n if (shapeHasNumericStroke(shape)) {\n shape.strokeWidth *= scale;\n }\n if (shapeIsText(shape)) {\n shape._scale = scale;\n if (isNumber(shape.fontSize)) {\n shape.fontSize *= scale;\n }\n if (isNumber(shape.lineHeight)) {\n shape.lineHeight *= scale;\n }\n if (isNumber(shape.width) && !isNumber(shape.height))\n shape.width *= scale;\n }\n if (hasProp(shape, 'cornerRadius') && isNumber(shape.cornerRadius)) {\n shape.cornerRadius *= scale;\n }\n return shape;\n};\nconst shapeGetLength = (shape) => {\n if (!shape.x1)\n return;\n return vectorDistance(vectorCreate(shape.x1, shape.y1), vectorCreate(shape.x2, shape.y2));\n};\nconst shapeGetCenter = (shape) => {\n if (shapeIsRect(shape)) {\n return vectorCreate(shape.x + shape.width * 0.5, shape.y + shape.height * 0.5);\n }\n if (shapeIsEllipse(shape)) {\n return vectorCreate(shape.x, shape.y);\n }\n if (shape.text) {\n // if is text box we try to use width & height values\n if (shapeIsTextBox(shape)) {\n const height = shape.height || textToSize(shape.text, shape).height;\n return vectorCreate(shape.x + shape.width * 0.5, shape.y + height * 0.5);\n }\n // else we calculate text size\n const size = textToSize(shape.text, shape);\n return vectorCreate(shape.x + size.width * 0.5, shape.y + size.height * 0.5);\n }\n if (shapeIsPath(shape)) {\n return vectorCenter(shape.points);\n }\n if (shapeIsLine(shape)) {\n return vectorCenter([\n shapeLineGetStartPoint(shape),\n shapeLineGetEndPoint(shape),\n ]);\n }\n return undefined;\n};\n//#endregion\nconst shapeGetLevel = (shapeInitialState, imageCurrentState) => {\n let flipX = false;\n if (imageCurrentState.flipX && shapeInitialState.flipX) {\n flipX = true;\n }\n else if (!imageCurrentState.flipX && shapeInitialState.flipX) {\n flipX = false;\n }\n else if (imageCurrentState.flipX && !shapeInitialState.flipX) {\n flipX = true;\n }\n let flipY = false;\n if (imageCurrentState.flipY && shapeInitialState.flipY) {\n flipY = true;\n }\n else if (!imageCurrentState.flipY && shapeInitialState.flipY) {\n flipY = false;\n }\n else if (imageCurrentState.flipY && !shapeInitialState.flipY) {\n flipY = true;\n }\n let rotation = -imageCurrentState.rotation;\n if ((flipX || flipY) && !(flipX && flipY))\n rotation = -rotation;\n return {\n flipX,\n flipY,\n rotation,\n };\n};\n\nvar ctxRoundRect = (ctx, x, y, width, height, radius) => {\n if (width < 2 * radius)\n radius = width / 2;\n if (height < 2 * radius)\n radius = height / 2;\n ctx.beginPath();\n ctx.moveTo(x + radius, y);\n ctx.arcTo(x + width, y, x + width, y + height, radius);\n ctx.arcTo(x + width, y + height, x, y + height, radius);\n ctx.arcTo(x, y + height, x, y, radius);\n ctx.arcTo(x, y, x + width, y, radius);\n ctx.closePath();\n return ctx;\n};\n\nvar isCanvas = (element) => /canvas/i.test(element.nodeName);\n\nvar isRemoteURL = (url) => new URL(url, location.href).origin !== location.origin;\n\nvar loadImage = (image, onSize = undefined) => new Promise((resolve, reject) => {\n // the image element we'll use to load the image\n let imageElement = image;\n let sizeCalculated = false;\n const reportSize = () => {\n if (sizeCalculated)\n return;\n sizeCalculated = true;\n isFunction(onSize) &&\n /* Use Promise.resolve to make async but place before resolve of parent promise */\n Promise.resolve().then(() => onSize(sizeCreate(imageElement.naturalWidth, imageElement.naturalHeight)));\n };\n // if is not an image element, it must be a valid image source\n if (!imageElement.src) {\n imageElement = new Image();\n // if is remote image, set crossOrigin\n // why not always set crossOrigin? -> because when set this fires two requests,\n // one for asking permission and one for downloading the image\n if (isString(image) && isRemoteURL(image))\n imageElement.crossOrigin = 'anonymous';\n imageElement.src = isString(image) ? image : URL.createObjectURL(image);\n }\n if (imageElement.complete) {\n reportSize();\n return resolve(imageElement);\n }\n // try to calculate size faster\n if (isFunction(onSize))\n getImageElementSize(imageElement).then(reportSize).catch(reject);\n imageElement.onload = () => {\n reportSize();\n resolve(imageElement);\n };\n imageElement.onerror = () => reject(new Error('Failed to load image'));\n});\n\nconst cache = new Map([]);\nconst getImage = (src, options = {}) => new Promise((resolve, reject) => {\n const { onMetadata = noop$1, onLoad = resolve, onError = reject, onComplete = noop$1, } = options;\n let imageLoadState = cache.get(src);\n // start loading\n if (!imageLoadState) {\n imageLoadState = {\n loading: false,\n complete: false,\n error: false,\n image: undefined,\n size: undefined,\n bus: pubsub(),\n };\n // store\n cache.set(src, imageLoadState);\n }\n // wait for load\n imageLoadState.bus.sub('meta', onMetadata);\n imageLoadState.bus.sub('load', onLoad);\n imageLoadState.bus.sub('error', onError);\n imageLoadState.bus.sub('complete', onComplete);\n // if is canvas, it's already done\n if (isCanvas(src)) {\n const canvas = src;\n // get image\n const image = canvas.cloneNode();\n // update state\n imageLoadState.complete = true;\n imageLoadState.image = image;\n imageLoadState.size = sizeCreateFromElement(canvas);\n }\n // already loaded\n if (imageLoadState.complete) {\n imageLoadState.bus.pub('meta', { size: imageLoadState.size });\n if (imageLoadState.error) {\n imageLoadState.bus.pub('error', imageLoadState.error);\n }\n else {\n imageLoadState.bus.pub('load', imageLoadState.image);\n }\n imageLoadState.bus.pub('complete');\n // reset subscribers\n imageLoadState.bus = pubsub();\n return;\n }\n // already loading, exit here\n if (imageLoadState.loading)\n return;\n // now loading\n imageLoadState.loading = true;\n // resource needs to be loaded\n loadImage(src, (size) => {\n imageLoadState.size = size;\n imageLoadState.bus.pub('meta', { size });\n })\n .then((image) => {\n imageLoadState.image = image;\n imageLoadState.bus.pub('load', image);\n })\n .catch((err) => {\n imageLoadState.error = err;\n imageLoadState.bus.pub('error', err);\n })\n .finally(() => {\n imageLoadState.complete = true;\n imageLoadState.loading = false;\n imageLoadState.bus.pub('complete');\n // reset subscribers\n imageLoadState.bus = pubsub();\n });\n});\n\nconst drawCanvas = (ctx, image, srcRect, destRect) => ctx.drawImage(image, srcRect.x, srcRect.x, srcRect.width, srcRect.height, destRect.x, destRect.y, destRect.width, destRect.height);\nvar ctxDrawImage = async (ctx, image, srcRect, destRect, draw = drawCanvas, options) => {\n ctx.save();\n ctx.clip();\n await draw(ctx, image, srcRect, destRect, options);\n ctx.restore();\n};\nconst getDrawImageParams = (container, backgroundSize, imageSize, backgroundCorners, backgroundPosition) => {\n // target is container\n const destRect = rectClone$1(container);\n // texture mapping\n if (backgroundCorners) {\n const srcRect = rectApply(rectCreateFromPoints(backgroundCorners), fixPrecision);\n srcRect.x *= imageSize.width;\n srcRect.width *= imageSize.width;\n srcRect.y *= imageSize.height;\n srcRect.height *= imageSize.height;\n return {\n srcRect,\n destRect,\n };\n }\n // by default use entire image\n const srcRect = rectCreate(0, 0, imageSize.width, imageSize.height);\n // css background positioning\n if (backgroundSize === 'contain') {\n const rect = rectContainRect(container, rectAspectRatio(srcRect));\n destRect.width = rect.width;\n destRect.height = rect.height;\n if (backgroundPosition) {\n destRect.x += backgroundPosition.x;\n destRect.y += backgroundPosition.y;\n }\n else {\n destRect.x += rect.x;\n destRect.y += rect.y;\n }\n }\n else if (backgroundSize === 'cover') {\n const relativeRect = rectCoverRect(destRect, srcRect.width / srcRect.height);\n destRect.width = relativeRect.width;\n destRect.height = relativeRect.height;\n if (backgroundPosition) {\n destRect.x += backgroundPosition.x;\n destRect.y += backgroundPosition.y;\n }\n else {\n destRect.x += relativeRect.x;\n destRect.y += relativeRect.y;\n }\n }\n else if (backgroundSize) {\n destRect.width = backgroundSize.width;\n destRect.height = backgroundSize.height;\n if (backgroundPosition) {\n destRect.x += backgroundPosition.x;\n destRect.y += backgroundPosition.y;\n }\n }\n else if (backgroundPosition) {\n destRect.width = imageSize.width;\n destRect.height = imageSize.height;\n destRect.x += backgroundPosition.x;\n destRect.y += backgroundPosition.y;\n }\n return {\n srcRect,\n destRect,\n };\n};\n\nconst defineRectShape = (ctx, shape) => {\n shape.cornerRadius > 0\n ? ctxRoundRect(ctx, shape.x, shape.y, shape.width, shape.height, shape.cornerRadius)\n : ctx.rect(shape.x, shape.y, shape.width, shape.height);\n return ctx;\n};\nconst fillRectShape = (ctx, shape) => {\n shape.backgroundColor && ctx.fill();\n return ctx;\n};\nconst strokeRectShape = (ctx, shape) => {\n shape.strokeWidth && ctx.stroke();\n return ctx;\n};\nvar drawRect = async (ctx, shape, options = {}) => {\n const { drawImage } = options;\n ctx.lineWidth = shape.strokeWidth ? shape.strokeWidth : 1; // 1 is default value for lineWidth prop\n ctx.strokeStyle = shape.strokeColor ? colorArrayToRGBA(shape.strokeColor) : 'none';\n ctx.fillStyle = shape.backgroundColor ? colorArrayToRGBA(shape.backgroundColor) : 'none';\n ctx.globalAlpha = shape.opacity;\n if (shape.backgroundImage) {\n let image;\n if (isCanvas(shape.backgroundImage)) {\n image = shape.backgroundImage;\n }\n else {\n image = await getImage(shape.backgroundImage);\n }\n // draw background color first\n defineRectShape(ctx, shape);\n fillRectShape(ctx, shape);\n // draw pattern OR draw normal image\n if (shape.backgroundRepeat === 'repeat') {\n const backgroundPosition = shape.backgroundPosition || { x: 0, y: 0 };\n const { srcRect, destRect } = getDrawImageParams(shape, shape.backgroundSize || sizeCreateFromElement(image), sizeCreateFromElement(image), shape.backgroundCorners, { x: 0, y: 0 });\n // create pattern canvas so we can scale image before creating pattern\n const patternCanvas = document.createElement('canvas');\n patternCanvas.width = destRect.width;\n patternCanvas.height = destRect.height;\n const patternCtx = patternCanvas.getContext('2d', {\n willReadFrequently: false,\n desynchronized: true,\n });\n await drawImage(patternCtx, image, srcRect, { ...destRect, x: 0, y: 0 });\n // fill context with pattern\n const pattern = ctx.createPattern(patternCanvas, 'repeat');\n ctx.fillStyle = pattern;\n ctx.save();\n ctx.beginPath();\n ctx.rect(shape.x + backgroundPosition.x, shape.y + backgroundPosition.y, shape.width - backgroundPosition.x, shape.height - backgroundPosition.y);\n ctx.clip();\n ctx.translate(destRect.x + backgroundPosition.x, destRect.y + backgroundPosition.y);\n ctx.fill();\n // release pattern canvas\n releaseCanvas(patternCanvas);\n ctx.restore();\n }\n else {\n const { srcRect, destRect } = getDrawImageParams(shape, shape.backgroundSize, sizeCreateFromElement(image), shape.backgroundCorners, shape.backgroundPosition);\n await ctxDrawImage(ctx, image, srcRect, destRect, drawImage, {\n feather: shape.feather,\n });\n }\n ctx.beginPath();\n defineRectShape(ctx, shape);\n strokeRectShape(ctx, shape);\n return [];\n }\n // no image, only draw fill and stroke\n defineRectShape(ctx, shape);\n fillRectShape(ctx, shape);\n strokeRectShape(ctx, shape);\n return [];\n};\n\nvar drawEllipse = async (ctx, shape, options = {}) => new Promise(async (resolve, reject) => {\n const { drawImage } = options;\n ctx.lineWidth = shape.strokeWidth || 1; // 1 is default value for lineWidth prop\n ctx.strokeStyle = shape.strokeColor ? colorArrayToRGBA(shape.strokeColor) : 'none';\n ctx.fillStyle = shape.backgroundColor ? colorArrayToRGBA(shape.backgroundColor) : 'none';\n ctx.globalAlpha = shape.opacity;\n ctx.ellipse(shape.x, shape.y, shape.rx, shape.ry, 0, 0, Math.PI * 2);\n shape.backgroundColor && ctx.fill();\n if (shape.backgroundImage) {\n let image;\n try {\n image = await getImage(shape.backgroundImage);\n }\n catch (err) {\n reject(err);\n }\n const bounds = rectCreate(shape.x - shape.rx, shape.y - shape.ry, shape.rx * 2, shape.ry * 2);\n const { srcRect, destRect } = getDrawImageParams(bounds, shape.backgroundSize, sizeCreateFromElement(image), shape.backgroundCorners, shape.backgroundPosition);\n // @ts-ignore\n await ctxDrawImage(ctx, image, srcRect, destRect, drawImage);\n shape.strokeWidth && ctx.stroke();\n resolve([]);\n }\n else {\n shape.strokeWidth && ctx.stroke();\n resolve([]);\n }\n});\n\nvar drawText = async (ctx, shape, options) => {\n const width = isNumber(shape.width) ? Math.floor(shape.width) : undefined;\n const height = isNumber(shape.height) ? Math.floor(shape.height) : undefined;\n // calculate text size\n const textSize = width && height\n ? sizeCreateFromAny(shape)\n : textToSize(shape.text, { ...shape, width, height });\n const rect = {\n x: shape.x,\n y: shape.y,\n width: textSize.width,\n height: textSize.height,\n };\n drawRect(ctx, {\n ...shape,\n ...rect,\n options,\n });\n // skip if no text\n if (!shape.text.length)\n return [];\n // draw text effects first\n const { textOutlineWidth = 0, textShadowX = 0, textShadowY = 0, textShadowBlur = 0, textShadowColor, \n // remove outline and blur so don't interfere with text shape rendering\n outline, blur, \n // remaining styles for final text shape\n ...plaintTextShape } = shape;\n // if height set, clip to text box\n const shouldClip = !!shape.height;\n if (shouldClip) {\n ctx.rect(rect.x, rect.y, rect.width, rect.height);\n ctx.save();\n ctx.clip();\n }\n // draw shadow\n if (textShadowX || textShadowY || textShadowBlur) {\n ctx.save();\n // move translation based on text shadow\n ctx.translate(textShadowX, textShadowY);\n // adjust rect so we draw all shadow\n const shadowRect = { ...rect };\n if (!shouldClip) {\n shadowRect.height += TextPadding + textShadowBlur;\n }\n // draw base shadow\n await drawTextImage(ctx, shadowRect, { ...shape, width, height }, {\n ...options,\n paddingLeft: TextPadding + textShadowBlur,\n paddingRight: TextPadding + textShadowBlur,\n paddingTop: textShadowBlur,\n paddingBottom: textShadowBlur + (shouldClip ? -1 * textShadowY : shape.fontSize),\n shapeExtendedProps: {\n color: textShadowColor,\n blur: textShadowBlur,\n },\n });\n ctx.restore();\n }\n // draw outline\n if (textOutlineWidth) {\n const outlineRect = { ...rect };\n if (!shouldClip)\n outlineRect.height += TextPadding + textOutlineWidth;\n await drawTextImage(ctx, outlineRect, { ...shape, width, height }, {\n ...options,\n paddingLeft: TextPadding + textOutlineWidth,\n paddingRight: TextPadding + textOutlineWidth,\n paddingTop: textOutlineWidth,\n paddingBottom: textOutlineWidth + shouldClip ? 0 : shape.fontSize,\n shapeExtendedProps: {\n color: shape.textOutlineColor,\n outline: textOutlineWidth,\n },\n });\n }\n // restore text clipping\n if (shouldClip) {\n ctx.restore();\n }\n // draw text\n await drawTextImage(ctx, rect, { ...plaintTextShape, width, height }, {\n ...options,\n paddingLeft: TextPadding,\n paddingRight: TextPadding,\n paddingTop: 0,\n paddingBottom: shouldClip ? 0 : shape.fontSize,\n });\n return [];\n};\nconst drawTextImage = async (ctx, rect, shape, options) => {\n const { willRequest, shapeExtendedProps, paddingLeft, paddingRight, paddingTop, paddingBottom, styleNonce, } = options;\n // aligns with how canvas measured width/height\n const width = isNumber(shape.width) ? Math.floor(rect.width) : undefined;\n const height = isNumber(shape.height) ? Math.floor(rect.height) : undefined;\n const imageWidth = Math.ceil(rect.width);\n const imageHeight = Math.ceil(rect.height);\n // maybe in future use same pixel ratio as device to better match preview text texture?\n const image = await textToImage(shape.text, {\n ...shape,\n ...shapeExtendedProps,\n ...rect,\n width,\n height,\n paddingLeft,\n paddingRight,\n paddingTop,\n paddingBottom,\n imageWidth,\n imageHeight,\n willRequest,\n styleNonce,\n });\n ctx.drawImage(image, shape.x - paddingLeft, shape.y - paddingTop, image.width, image.height);\n};\n\nvar drawLine = async (ctx, shape) => new Promise(async (resolve) => {\n ctx.lineWidth = shape.strokeWidth || 1; // 1 is default value for lineWidth prop\n ctx.strokeStyle = shape.strokeColor ? colorArrayToRGBA(shape.strokeColor) : 'none';\n ctx.globalAlpha = shape.opacity;\n if (shape.bitmap) {\n ctx.lineCap = shape.strokeCap;\n ctx.lineJoin = shape.strokeJoin;\n ctx.setLineDash(shape.strokeDash || []);\n }\n let lineStartPosition = shapeLineGetStartPoint(shape);\n let lineEndPosition = shapeLineGetEndPoint(shape);\n // draw line\n ctx.moveTo(lineStartPosition.x, lineStartPosition.y);\n ctx.lineTo(lineEndPosition.x, lineEndPosition.y);\n shape.strokeWidth && ctx.stroke();\n // draw other shapes\n resolve([]);\n});\n\nvar drawPath = async (ctx, shape) => new Promise((resolve, reject) => {\n // let's draw a dot instead\n if (shape.bitmap && shape.points.length === 1) {\n drawEllipse(ctx, {\n x: shape.points[0].x,\n y: shape.points[0].y,\n rx: shape.strokeWidth * 0.5,\n ry: shape.strokeWidth * 0.5,\n backgroundColor: shape.strokeColor,\n }).then(() => resolve([]));\n return;\n }\n ctx.lineWidth = shape.strokeWidth || 1; // 1 is default value for lineWidth prop\n ctx.strokeStyle = shape.strokeColor ? colorArrayToRGBA(shape.strokeColor) : 'none';\n ctx.fillStyle = shape.backgroundColor ? colorArrayToRGBA(shape.backgroundColor) : 'none';\n ctx.globalAlpha = shape.opacity;\n // draw line\n const { points } = shape;\n if (shape.bitmap) {\n ctx.lineCap = shape.strokeCap;\n ctx.lineJoin = shape.strokeJoin;\n ctx.setLineDash(shape.strokeDash || []);\n }\n if (shape.pathClose)\n ctx.beginPath();\n ctx.moveTo(points[0].x, points[0].y);\n const l = points.length;\n for (let i = 1; i < l; i++) {\n ctx.lineTo(points[i].x, points[i].y);\n }\n if (shape.pathClose)\n ctx.closePath();\n shape.strokeWidth && ctx.stroke();\n shape.backgroundColor && ctx.fill();\n resolve([]);\n});\n\nvar ctxFlip = (ctx, flipX, flipY, pivot) => {\n if (!flipX && !flipY)\n return ctx;\n ctx.translate(pivot.x, pivot.y);\n ctx.scale(flipX ? -1 : 1, flipY ? -1 : 1);\n ctx.translate(-pivot.x, -pivot.y);\n return ctx;\n};\n\nconst drawShape = async (ctx, shape, options) => {\n // add or subtract\n ctx.globalCompositeOperation =\n shape.drawMode === 'subtract' ? 'destination-out' : 'source-over';\n // center, needed for transforms\n const center = shapeGetCenter(shape);\n // rotate context\n ctxRotate(ctx, shape.rotation, center);\n // flip context\n ctxFlip(ctx, shape.flipX, shape.flipY, center);\n let fn;\n if (shapeIsRect(shape)) {\n fn = drawRect;\n }\n else if (shapeIsEllipse(shape)) {\n fn = drawEllipse;\n }\n else if (shapeIsLine(shape)) {\n fn = drawLine;\n }\n else if (shapeIsPath(shape)) {\n fn = drawPath;\n }\n else if (shapeIsText(shape)) {\n fn = drawText;\n }\n if (fn) {\n const subShapes = await fn(ctx, shape, options);\n if (!subShapes.length)\n return [];\n const shapes = await drawShapes(ctx, subShapes, options);\n return [shape, ...shapes];\n }\n return [];\n};\n\nvar drawShapes = async (ctx, shapes, options) => {\n let drawnShapes = [];\n for (const shape of shapes) {\n ctx.save();\n // clears previous shape's path\n ctx.beginPath();\n // wait for shape to draw before drawing next shape\n drawnShapes = [...drawnShapes, ...(await drawShape(ctx, shape, options))];\n ctx.restore();\n }\n return drawnShapes;\n};\n\nvar drawImageData = async (imageData, options = {}) => {\n const { shapes = [], contextBounds = imageData, transform = noop$1, drawImage, willRequest, styleNonce, canvasMemoryLimit, computeShape = passthrough, preprocessShape = passthrough, } = options;\n // no shapes to draw\n if (!shapes.length)\n return imageData;\n // create drawing context\n const canvas = h('canvas');\n canvas.width = contextBounds.width;\n canvas.height = contextBounds.height;\n const ctx = canvas.getContext('2d', { willReadFrequently: true });\n ctx.putImageData(imageData, contextBounds.x || 0, contextBounds.y || 0);\n // compute the position of all shapes\n const computedShapes = shapes\n .map(shapeDeepCopy)\n .map(computeShape)\n .map(preprocessShape)\n .flat()\n .filter(Boolean);\n // compute transforms for context\n transform(ctx);\n // draw shapes to canvas\n await drawShapes(ctx, computedShapes, {\n drawImage,\n canvasMemoryLimit,\n willRequest,\n styleNonce,\n });\n const imageDataOut = ctx.getImageData(0, 0, canvas.width, canvas.height);\n releaseCanvas(canvas);\n return imageDataOut;\n};\n\nvar fillImageData = async (imageData, options = {}) => {\n const { backgroundColor, backgroundImage } = options;\n const hasBackgroundColor = !(!backgroundColor || (backgroundColor && backgroundColor[3] === 0));\n // no background set\n if (!backgroundImage && !hasBackgroundColor)\n return imageData;\n // canvas to fill\n const image = h('canvas');\n image.width = imageData.width;\n image.height = imageData.height;\n const ctx = image.getContext('2d', { willReadFrequently: true });\n ctx.putImageData(imageData, 0, 0);\n // fill behind image\n ctx.globalCompositeOperation = 'destination-over';\n if (hasBackgroundColor) {\n ctx.fillStyle = colorArrayToRGBA(backgroundColor);\n ctx.fillRect(0, 0, image.width, image.height);\n }\n if (backgroundImage) {\n let image;\n if (isCanvas(backgroundImage)) {\n image = backgroundImage;\n }\n else {\n image = await getImage(backgroundImage);\n }\n const { srcRect, destRect } = getDrawImageParams({\n x: 0,\n y: 0,\n width: imageData.width,\n height: imageData.height,\n }, 'cover', {\n width: image.width,\n height: image.height,\n });\n ctx.drawImage(image, srcRect.x, srcRect.y, srcRect.width, srcRect.height, destRect.x, destRect.y, destRect.width, destRect.height);\n }\n const imageDataOut = ctx.getImageData(0, 0, image.width, image.height);\n releaseCanvas(image);\n return imageDataOut;\n};\n\nvar dotColorMatrix = (a, b) => {\n const res = new Array(20);\n // R\n res[0] = a[0] * b[0] + a[1] * b[5] + a[2] * b[10] + a[3] * b[15];\n res[1] = a[0] * b[1] + a[1] * b[6] + a[2] * b[11] + a[3] * b[16];\n res[2] = a[0] * b[2] + a[1] * b[7] + a[2] * b[12] + a[3] * b[17];\n res[3] = a[0] * b[3] + a[1] * b[8] + a[2] * b[13] + a[3] * b[18];\n res[4] = a[0] * b[4] + a[1] * b[9] + a[2] * b[14] + a[3] * b[19] + a[4];\n // G\n res[5] = a[5] * b[0] + a[6] * b[5] + a[7] * b[10] + a[8] * b[15];\n res[6] = a[5] * b[1] + a[6] * b[6] + a[7] * b[11] + a[8] * b[16];\n res[7] = a[5] * b[2] + a[6] * b[7] + a[7] * b[12] + a[8] * b[17];\n res[8] = a[5] * b[3] + a[6] * b[8] + a[7] * b[13] + a[8] * b[18];\n res[9] = a[5] * b[4] + a[6] * b[9] + a[7] * b[14] + a[8] * b[19] + a[9];\n // B\n res[10] = a[10] * b[0] + a[11] * b[5] + a[12] * b[10] + a[13] * b[15];\n res[11] = a[10] * b[1] + a[11] * b[6] + a[12] * b[11] + a[13] * b[16];\n res[12] = a[10] * b[2] + a[11] * b[7] + a[12] * b[12] + a[13] * b[17];\n res[13] = a[10] * b[3] + a[11] * b[8] + a[12] * b[13] + a[13] * b[18];\n res[14] = a[10] * b[4] + a[11] * b[9] + a[12] * b[14] + a[13] * b[19] + a[14];\n // A\n res[15] = a[15] * b[0] + a[16] * b[5] + a[17] * b[10] + a[18] * b[15];\n res[16] = a[15] * b[1] + a[16] * b[6] + a[17] * b[11] + a[18] * b[16];\n res[17] = a[15] * b[2] + a[16] * b[7] + a[17] * b[12] + a[18] * b[17];\n res[18] = a[15] * b[3] + a[16] * b[8] + a[17] * b[13] + a[18] * b[18];\n res[19] = a[15] * b[4] + a[16] * b[9] + a[17] * b[14] + a[18] * b[19] + a[19];\n return res;\n};\n\nvar getColorMatrixFromColorMatrices = (colorMatrices) => colorMatrices.length\n ? colorMatrices.reduce((previous, current) => dotColorMatrix([...previous], current), colorMatrices.shift())\n : [];\n\nvar roundFraction = (value, fr = 2) => Math.round(value * fr) / fr;\n\nvar getImageRedactionScaleFactor = (imageSize, redactionShapes) => {\n const imageRes = imageSize.width * imageSize.height;\n const maxShapeSize = redactionShapes.reduce((max, shape) => {\n if (shape.width > max.width && shape.height > max.height) {\n max.width = shape.width;\n max.height = shape.height;\n }\n return max;\n }, { width: 0, height: 0 });\n const maxShapeRes = maxShapeSize.width * maxShapeSize.height;\n const fraction = Math.max(0.5, 0.5 + (1 - maxShapeRes / imageRes) / 2);\n return roundFraction(fraction, 5);\n};\n\nfunction noop() { }\nconst identity = x => x;\nfunction assign(tar, src) {\n // @ts-ignore\n for (const k in src)\n tar[k] = src[k];\n return tar;\n}\nfunction run(fn) {\n return fn();\n}\nfunction blank_object() {\n return Object.create(null);\n}\nfunction run_all(fns) {\n fns.forEach(run);\n}\nfunction is_function(thing) {\n return typeof thing === 'function';\n}\nfunction safe_not_equal(a, b) {\n return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');\n}\nlet src_url_equal_anchor;\nfunction src_url_equal(element_src, url) {\n if (!src_url_equal_anchor) {\n src_url_equal_anchor = document.createElement('a');\n }\n src_url_equal_anchor.href = url;\n return element_src === src_url_equal_anchor.href;\n}\nfunction is_empty(obj) {\n return Object.keys(obj).length === 0;\n}\nfunction subscribe(store, ...callbacks) {\n if (store == null) {\n return noop;\n }\n const unsub = store.subscribe(...callbacks);\n return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;\n}\nfunction get_store_value(store) {\n let value;\n subscribe(store, _ => value = _)();\n return value;\n}\nfunction component_subscribe(component, store, callback) {\n component.$$.on_destroy.push(subscribe(store, callback));\n}\nfunction create_slot(definition, ctx, $$scope, fn) {\n if (definition) {\n const slot_ctx = get_slot_context(definition, ctx, $$scope, fn);\n return definition[0](slot_ctx);\n }\n}\nfunction get_slot_context(definition, ctx, $$scope, fn) {\n return definition[1] && fn\n ? assign($$scope.ctx.slice(), definition[1](fn(ctx)))\n : $$scope.ctx;\n}\nfunction get_slot_changes(definition, $$scope, dirty, fn) {\n if (definition[2] && fn) {\n const lets = definition[2](fn(dirty));\n if ($$scope.dirty === undefined) {\n return lets;\n }\n if (typeof lets === 'object') {\n const merged = [];\n const len = Math.max($$scope.dirty.length, lets.length);\n for (let i = 0; i < len; i += 1) {\n merged[i] = $$scope.dirty[i] | lets[i];\n }\n return merged;\n }\n return $$scope.dirty | lets;\n }\n return $$scope.dirty;\n}\nfunction update_slot_base(slot, slot_definition, ctx, $$scope, slot_changes, get_slot_context_fn) {\n if (slot_changes) {\n const slot_context = get_slot_context(slot_definition, ctx, $$scope, get_slot_context_fn);\n slot.p(slot_context, slot_changes);\n }\n}\nfunction get_all_dirty_from_scope($$scope) {\n if ($$scope.ctx.length > 32) {\n const dirty = [];\n const length = $$scope.ctx.length / 32;\n for (let i = 0; i < length; i++) {\n dirty[i] = -1;\n }\n return dirty;\n }\n return -1;\n}\nfunction exclude_internal_props(props) {\n const result = {};\n for (const k in props)\n if (k[0] !== '$')\n result[k] = props[k];\n return result;\n}\nfunction compute_rest_props(props, keys) {\n const rest = {};\n keys = new Set(keys);\n for (const k in props)\n if (!keys.has(k) && k[0] !== '$')\n rest[k] = props[k];\n return rest;\n}\nfunction set_store_value(store, ret, value) {\n store.set(value);\n return ret;\n}\nfunction action_destroyer(action_result) {\n return action_result && is_function(action_result.destroy) ? action_result.destroy : noop;\n}\n\nconst is_client = typeof window !== 'undefined';\nlet now = is_client\n ? () => window.performance.now()\n : () => Date.now();\nlet raf = is_client ? cb => requestAnimationFrame(cb) : noop;\n\nconst tasks = new Set();\nfunction run_tasks(now) {\n tasks.forEach(task => {\n if (!task.c(now)) {\n tasks.delete(task);\n task.f();\n }\n });\n if (tasks.size !== 0)\n raf(run_tasks);\n}\n/**\n * Creates a new task that runs on each raf frame\n * until it returns a falsy value or is aborted\n */\nfunction loop(callback) {\n let task;\n if (tasks.size === 0)\n raf(run_tasks);\n return {\n promise: new Promise(fulfill => {\n tasks.add(task = { c: callback, f: fulfill });\n }),\n abort() {\n tasks.delete(task);\n }\n };\n}\nfunction append(target, node) {\n target.appendChild(node);\n}\nfunction get_root_for_style(node) {\n if (!node)\n return document;\n const root = node.getRootNode ? node.getRootNode() : node.ownerDocument;\n if (root && root.host) {\n return root;\n }\n return node.ownerDocument;\n}\nfunction append_empty_stylesheet(node) {\n const style_element = element('style');\n append_stylesheet(get_root_for_style(node), style_element);\n return style_element.sheet;\n}\nfunction append_stylesheet(node, style) {\n append(node.head || node, style);\n return style.sheet;\n}\nfunction insert(target, node, anchor) {\n target.insertBefore(node, anchor || null);\n}\nfunction detach(node) {\n node.parentNode.removeChild(node);\n}\nfunction element(name) {\n return document.createElement(name);\n}\nfunction svg_element(name) {\n return document.createElementNS('http://www.w3.org/2000/svg', name);\n}\nfunction text(data) {\n return document.createTextNode(data);\n}\nfunction space() {\n return text(' ');\n}\nfunction empty() {\n return text('');\n}\nfunction listen(node, event, handler, options) {\n node.addEventListener(event, handler, options);\n return () => node.removeEventListener(event, handler, options);\n}\nfunction prevent_default(fn) {\n return function (event) {\n event.preventDefault();\n // @ts-ignore\n return fn.call(this, event);\n };\n}\nfunction stop_propagation(fn) {\n return function (event) {\n event.stopPropagation();\n // @ts-ignore\n return fn.call(this, event);\n };\n}\nfunction attr(node, attribute, value) {\n if (value == null)\n node.removeAttribute(attribute);\n else if (node.getAttribute(attribute) !== value)\n node.setAttribute(attribute, value);\n}\nfunction set_attributes(node, attributes) {\n // @ts-ignore\n const descriptors = Object.getOwnPropertyDescriptors(node.__proto__);\n for (const key in attributes) {\n if (attributes[key] == null) {\n node.removeAttribute(key);\n }\n else if (key === 'style') {\n node.style.cssText = attributes[key];\n }\n else if (key === '__value') {\n node.value = node[key] = attributes[key];\n }\n else if (descriptors[key] && descriptors[key].set) {\n node[key] = attributes[key];\n }\n else {\n attr(node, key, attributes[key]);\n }\n }\n}\nfunction set_custom_element_data_map(node, data_map) {\n Object.keys(data_map).forEach((key) => {\n set_custom_element_data(node, key, data_map[key]);\n });\n}\nfunction set_custom_element_data(node, prop, value) {\n if (prop in node) {\n node[prop] = typeof node[prop] === 'boolean' && value === '' ? true : value;\n }\n else {\n attr(node, prop, value);\n }\n}\nfunction children(element) {\n return Array.from(element.childNodes);\n}\nfunction set_data(text, data) {\n data = '' + data;\n if (text.wholeText !== data)\n text.data = data;\n}\nfunction set_input_value(input, value) {\n input.value = value == null ? '' : value;\n}\nfunction set_style(node, key, value, important) {\n if (value === null) {\n node.style.removeProperty(key);\n }\n else {\n node.style.setProperty(key, value, important ? 'important' : '');\n }\n}\nfunction custom_event(type, detail, { bubbles = false, cancelable = false } = {}) {\n const e = document.createEvent('CustomEvent');\n e.initCustomEvent(type, bubbles, cancelable, detail);\n return e;\n}\nclass HtmlTag {\n constructor(is_svg = false) {\n this.is_svg = false;\n this.is_svg = is_svg;\n this.e = this.n = null;\n }\n c(html) {\n this.h(html);\n }\n m(html, target, anchor = null) {\n if (!this.e) {\n if (this.is_svg)\n this.e = svg_element(target.nodeName);\n else\n this.e = element(target.nodeName);\n this.t = target;\n this.c(html);\n }\n this.i(anchor);\n }\n h(html) {\n this.e.innerHTML = html;\n this.n = Array.from(this.e.childNodes);\n }\n i(anchor) {\n for (let i = 0; i < this.n.length; i += 1) {\n insert(this.t, this.n[i], anchor);\n }\n }\n p(html) {\n this.d();\n this.h(html);\n this.i(this.a);\n }\n d() {\n this.n.forEach(detach);\n }\n}\nfunction construct_svelte_component(component, props) {\n return new component(props);\n}\n\n// we need to store the information for multiple documents because a Svelte application could also contain iframes\n// https://github.com/sveltejs/svelte/issues/3624\nconst managed_styles = new Map();\nlet active = 0;\n// https://github.com/darkskyapp/string-hash/blob/master/index.js\nfunction hash(str) {\n let hash = 5381;\n let i = str.length;\n while (i--)\n hash = ((hash << 5) - hash) ^ str.charCodeAt(i);\n return hash >>> 0;\n}\nfunction create_style_information(doc, node) {\n const info = { stylesheet: append_empty_stylesheet(node), rules: {} };\n managed_styles.set(doc, info);\n return info;\n}\nfunction create_rule(node, a, b, duration, delay, ease, fn, uid = 0) {\n const step = 16.666 / duration;\n let keyframes = '{\\n';\n for (let p = 0; p <= 1; p += step) {\n const t = a + (b - a) * ease(p);\n keyframes += p * 100 + `%{${fn(t, 1 - t)}}\\n`;\n }\n const rule = keyframes + `100% {${fn(b, 1 - b)}}\\n}`;\n const name = `__svelte_${hash(rule)}_${uid}`;\n const doc = get_root_for_style(node);\n const { stylesheet, rules } = managed_styles.get(doc) || create_style_information(doc, node);\n if (!rules[name]) {\n rules[name] = true;\n stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);\n }\n const animation = node.style.animation || '';\n node.style.animation = `${animation ? `${animation}, ` : ''}${name} ${duration}ms linear ${delay}ms 1 both`;\n active += 1;\n return name;\n}\nfunction delete_rule(node, name) {\n const previous = (node.style.animation || '').split(', ');\n const next = previous.filter(name\n ? anim => anim.indexOf(name) < 0 // remove specific animation\n : anim => anim.indexOf('__svelte') === -1 // remove all Svelte animations\n );\n const deleted = previous.length - next.length;\n if (deleted) {\n node.style.animation = next.join(', ');\n active -= deleted;\n if (!active)\n clear_rules();\n }\n}\nfunction clear_rules() {\n raf(() => {\n if (active)\n return;\n managed_styles.forEach(info => {\n const { ownerNode } = info.stylesheet;\n // there is no ownerNode if it runs on jsdom.\n if (ownerNode)\n detach(ownerNode);\n });\n managed_styles.clear();\n });\n}\n\nlet current_component;\nfunction set_current_component(component) {\n current_component = component;\n}\nfunction get_current_component() {\n if (!current_component)\n throw new Error('Function called outside component initialization');\n return current_component;\n}\n/**\n * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM.\n * It must be called during the component's initialisation (but doesn't need to live *inside* the component;\n * it can be called from an external module).\n *\n * `onMount` does not run inside a [server-side component](/docs#run-time-server-side-component-api).\n *\n * https://svelte.dev/docs#run-time-svelte-onmount\n */\nfunction onMount(fn) {\n get_current_component().$$.on_mount.push(fn);\n}\n/**\n * Schedules a callback to run immediately after the component has been updated.\n *\n * The first time the callback runs will be after the initial `onMount`\n */\nfunction afterUpdate(fn) {\n get_current_component().$$.after_update.push(fn);\n}\n/**\n * Schedules a callback to run immediately before the component is unmounted.\n *\n * Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the\n * only one that runs inside a server-side component.\n *\n * https://svelte.dev/docs#run-time-svelte-ondestroy\n */\nfunction onDestroy(fn) {\n get_current_component().$$.on_destroy.push(fn);\n}\n/**\n * Creates an event dispatcher that can be used to dispatch [component events](/docs#template-syntax-component-directives-on-eventname).\n * Event dispatchers are functions that can take two arguments: `name` and `detail`.\n *\n * Component events created with `createEventDispatcher` create a\n * [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent).\n * These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture).\n * The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail)\n * property and can contain any type of data.\n *\n * https://svelte.dev/docs#run-time-svelte-createeventdispatcher\n */\nfunction createEventDispatcher() {\n const component = get_current_component();\n return (type, detail, { cancelable = false } = {}) => {\n const callbacks = component.$$.callbacks[type];\n if (callbacks) {\n // TODO are there situations where events could be dispatched\n // in a server (non-DOM) environment?\n const event = custom_event(type, detail, { cancelable });\n callbacks.slice().forEach(fn => {\n fn.call(component, event);\n });\n return !event.defaultPrevented;\n }\n return true;\n };\n}\n/**\n * Associates an arbitrary `context` object with the current component and the specified `key`\n * and returns that object. The context is then available to children of the component\n * (including slotted content) with `getContext`.\n *\n * Like lifecycle functions, this must be called during component initialisation.\n *\n * https://svelte.dev/docs#run-time-svelte-setcontext\n */\nfunction setContext(key, context) {\n get_current_component().$$.context.set(key, context);\n return context;\n}\n/**\n * Retrieves the context that belongs to the closest parent component with the specified `key`.\n * Must be called during component initialisation.\n *\n * https://svelte.dev/docs#run-time-svelte-getcontext\n */\nfunction getContext(key) {\n return get_current_component().$$.context.get(key);\n}\n/**\n * Retrieves the whole context map that belongs to the closest parent component.\n * Must be called during component initialisation. Useful, for example, if you\n * programmatically create a component and want to pass the existing context to it.\n *\n * https://svelte.dev/docs#run-time-svelte-getallcontexts\n */\nfunction getAllContexts() {\n return get_current_component().$$.context;\n}\n// TODO figure out if we still want to support\n// shorthand events, or if we want to implement\n// a real bubbling mechanism\nfunction bubble(component, event) {\n const callbacks = component.$$.callbacks[event.type];\n if (callbacks) {\n // @ts-ignore\n callbacks.slice().forEach(fn => fn.call(this, event));\n }\n}\n\nconst dirty_components = [];\nconst binding_callbacks = [];\nconst render_callbacks = [];\nconst flush_callbacks = [];\nconst resolved_promise = Promise.resolve();\nlet update_scheduled = false;\nfunction schedule_update() {\n if (!update_scheduled) {\n update_scheduled = true;\n resolved_promise.then(flush);\n }\n}\nfunction tick$1() {\n schedule_update();\n return resolved_promise;\n}\nfunction add_render_callback(fn) {\n render_callbacks.push(fn);\n}\nfunction add_flush_callback(fn) {\n flush_callbacks.push(fn);\n}\n// flush() calls callbacks in this order:\n// 1. All beforeUpdate callbacks, in order: parents before children\n// 2. All bind:this callbacks, in reverse order: children before parents.\n// 3. All afterUpdate callbacks, in order: parents before children. EXCEPT\n// for afterUpdates called during the initial onMount, which are called in\n// reverse order: children before parents.\n// Since callbacks might update component values, which could trigger another\n// call to flush(), the following steps guard against this:\n// 1. During beforeUpdate, any updated components will be added to the\n// dirty_components array and will cause a reentrant call to flush(). Because\n// the flush index is kept outside the function, the reentrant call will pick\n// up where the earlier call left off and go through all dirty components. The\n// current_component value is saved and restored so that the reentrant call will\n// not interfere with the \"parent\" flush() call.\n// 2. bind:this callbacks cannot trigger new flush() calls.\n// 3. During afterUpdate, any updated components will NOT have their afterUpdate\n// callback called a second time; the seen_callbacks set, outside the flush()\n// function, guarantees this behavior.\nconst seen_callbacks = new Set();\nlet flushidx = 0; // Do *not* move this inside the flush() function\nfunction flush() {\n const saved_component = current_component;\n do {\n // first, call beforeUpdate functions\n // and update components\n while (flushidx < dirty_components.length) {\n const component = dirty_components[flushidx];\n flushidx++;\n set_current_component(component);\n update(component.$$);\n }\n set_current_component(null);\n dirty_components.length = 0;\n flushidx = 0;\n while (binding_callbacks.length)\n binding_callbacks.pop()();\n // then, once components are updated, call\n // afterUpdate functions. This may cause\n // subsequent updates...\n for (let i = 0; i < render_callbacks.length; i += 1) {\n const callback = render_callbacks[i];\n if (!seen_callbacks.has(callback)) {\n // ...so guard against infinite loops\n seen_callbacks.add(callback);\n callback();\n }\n }\n render_callbacks.length = 0;\n } while (dirty_components.length);\n while (flush_callbacks.length) {\n flush_callbacks.pop()();\n }\n update_scheduled = false;\n seen_callbacks.clear();\n set_current_component(saved_component);\n}\nfunction update($$) {\n if ($$.fragment !== null) {\n $$.update();\n run_all($$.before_update);\n const dirty = $$.dirty;\n $$.dirty = [-1];\n $$.fragment && $$.fragment.p($$.ctx, dirty);\n $$.after_update.forEach(add_render_callback);\n }\n}\n\nlet promise;\nfunction wait() {\n if (!promise) {\n promise = Promise.resolve();\n promise.then(() => {\n promise = null;\n });\n }\n return promise;\n}\nfunction dispatch(node, direction, kind) {\n node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}${kind}`));\n}\nconst outroing = new Set();\nlet outros;\nfunction group_outros() {\n outros = {\n r: 0,\n c: [],\n p: outros // parent group\n };\n}\nfunction check_outros() {\n if (!outros.r) {\n run_all(outros.c);\n }\n outros = outros.p;\n}\nfunction transition_in(block, local) {\n if (block && block.i) {\n outroing.delete(block);\n block.i(local);\n }\n}\nfunction transition_out(block, local, detach, callback) {\n if (block && block.o) {\n if (outroing.has(block))\n return;\n outroing.add(block);\n outros.c.push(() => {\n outroing.delete(block);\n if (callback) {\n if (detach)\n block.d(1);\n callback();\n }\n });\n block.o(local);\n }\n else if (callback) {\n callback();\n }\n}\nconst null_transition = { duration: 0 };\nfunction create_bidirectional_transition(node, fn, params, intro) {\n let config = fn(node, params);\n let t = intro ? 0 : 1;\n let running_program = null;\n let pending_program = null;\n let animation_name = null;\n function clear_animation() {\n if (animation_name)\n delete_rule(node, animation_name);\n }\n function init(program, duration) {\n const d = (program.b - t);\n duration *= Math.abs(d);\n return {\n a: t,\n b: program.b,\n d,\n duration,\n start: program.start,\n end: program.start + duration,\n group: program.group\n };\n }\n function go(b) {\n const { delay = 0, duration = 300, easing = identity, tick = noop, css } = config || null_transition;\n const program = {\n start: now() + delay,\n b\n };\n if (!b) {\n // @ts-ignore todo: improve typings\n program.group = outros;\n outros.r += 1;\n }\n if (running_program || pending_program) {\n pending_program = program;\n }\n else {\n // if this is an intro, and there's a delay, we need to do\n // an initial tick and/or apply CSS animation immediately\n if (css) {\n clear_animation();\n animation_name = create_rule(node, t, b, duration, delay, easing, css);\n }\n if (b)\n tick(0, 1);\n running_program = init(program, duration);\n add_render_callback(() => dispatch(node, b, 'start'));\n loop(now => {\n if (pending_program && now > pending_program.start) {\n running_program = init(pending_program, duration);\n pending_program = null;\n dispatch(node, running_program.b, 'start');\n if (css) {\n clear_animation();\n animation_name = create_rule(node, t, running_program.b, running_program.duration, 0, easing, config.css);\n }\n }\n if (running_program) {\n if (now >= running_program.end) {\n tick(t = running_program.b, 1 - t);\n dispatch(node, running_program.b, 'end');\n if (!pending_program) {\n // we're done\n if (running_program.b) {\n // intro — we can tidy up immediately\n clear_animation();\n }\n else {\n // outro — needs to be coordinated\n if (!--running_program.group.r)\n run_all(running_program.group.c);\n }\n }\n running_program = null;\n }\n else if (now >= running_program.start) {\n const p = now - running_program.start;\n t = running_program.a + running_program.d * easing(p / running_program.duration);\n tick(t, 1 - t);\n }\n }\n return !!(running_program || pending_program);\n });\n }\n }\n return {\n run(b) {\n if (is_function(config)) {\n wait().then(() => {\n // @ts-ignore\n config = config();\n go(b);\n });\n }\n else {\n go(b);\n }\n },\n end() {\n clear_animation();\n running_program = pending_program = null;\n }\n };\n}\n\nconst globals = (typeof window !== 'undefined'\n ? window\n : typeof globalThis !== 'undefined'\n ? globalThis\n : global);\n\nfunction destroy_block(block, lookup) {\n block.d(1);\n lookup.delete(block.key);\n}\nfunction outro_and_destroy_block(block, lookup) {\n transition_out(block, 1, 1, () => {\n lookup.delete(block.key);\n });\n}\nfunction update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list, lookup, node, destroy, create_each_block, next, get_context) {\n let o = old_blocks.length;\n let n = list.length;\n let i = o;\n const old_indexes = {};\n while (i--)\n old_indexes[old_blocks[i].key] = i;\n const new_blocks = [];\n const new_lookup = new Map();\n const deltas = new Map();\n i = n;\n while (i--) {\n const child_ctx = get_context(ctx, list, i);\n const key = get_key(child_ctx);\n let block = lookup.get(key);\n if (!block) {\n block = create_each_block(key, child_ctx);\n block.c();\n }\n else if (dynamic) {\n block.p(child_ctx, dirty);\n }\n new_lookup.set(key, new_blocks[i] = block);\n if (key in old_indexes)\n deltas.set(key, Math.abs(i - old_indexes[key]));\n }\n const will_move = new Set();\n const did_move = new Set();\n function insert(block) {\n transition_in(block, 1);\n block.m(node, next);\n lookup.set(block.key, block);\n next = block.first;\n n--;\n }\n while (o && n) {\n const new_block = new_blocks[n - 1];\n const old_block = old_blocks[o - 1];\n const new_key = new_block.key;\n const old_key = old_block.key;\n if (new_block === old_block) {\n // do nothing\n next = new_block.first;\n o--;\n n--;\n }\n else if (!new_lookup.has(old_key)) {\n // remove old block\n destroy(old_block, lookup);\n o--;\n }\n else if (!lookup.has(new_key) || will_move.has(new_key)) {\n insert(new_block);\n }\n else if (did_move.has(old_key)) {\n o--;\n }\n else if (deltas.get(new_key) > deltas.get(old_key)) {\n did_move.add(new_key);\n insert(new_block);\n }\n else {\n will_move.add(old_key);\n o--;\n }\n }\n while (o--) {\n const old_block = old_blocks[o];\n if (!new_lookup.has(old_block.key))\n destroy(old_block, lookup);\n }\n while (n)\n insert(new_blocks[n - 1]);\n return new_blocks;\n}\n\nfunction get_spread_update(levels, updates) {\n const update = {};\n const to_null_out = {};\n const accounted_for = { $$scope: 1 };\n let i = levels.length;\n while (i--) {\n const o = levels[i];\n const n = updates[i];\n if (n) {\n for (const key in o) {\n if (!(key in n))\n to_null_out[key] = 1;\n }\n for (const key in n) {\n if (!accounted_for[key]) {\n update[key] = n[key];\n accounted_for[key] = 1;\n }\n }\n levels[i] = n;\n }\n else {\n for (const key in o) {\n accounted_for[key] = 1;\n }\n }\n }\n for (const key in to_null_out) {\n if (!(key in update))\n update[key] = undefined;\n }\n return update;\n}\nfunction get_spread_object(spread_props) {\n return typeof spread_props === 'object' && spread_props !== null ? spread_props : {};\n}\n\nfunction bind(component, name, callback) {\n const index = component.$$.props[name];\n if (index !== undefined) {\n component.$$.bound[index] = callback;\n callback(component.$$.ctx[index]);\n }\n}\nfunction create_component(block) {\n block && block.c();\n}\nfunction mount_component(component, target, anchor, customElement) {\n const { fragment, after_update } = component.$$;\n fragment && fragment.m(target, anchor);\n if (!customElement) {\n // onMount happens before the initial afterUpdate\n add_render_callback(() => {\n const new_on_destroy = component.$$.on_mount.map(run).filter(is_function);\n // if the component was destroyed immediately\n // it will update the `$$.on_destroy` reference to `null`.\n // the destructured on_destroy may still reference to the old array\n if (component.$$.on_destroy) {\n component.$$.on_destroy.push(...new_on_destroy);\n }\n else {\n // Edge case - component was destroyed immediately,\n // most likely as a result of a binding initialising\n run_all(new_on_destroy);\n }\n component.$$.on_mount = [];\n });\n }\n after_update.forEach(add_render_callback);\n}\nfunction destroy_component(component, detaching) {\n const $$ = component.$$;\n if ($$.fragment !== null) {\n run_all($$.on_destroy);\n $$.fragment && $$.fragment.d(detaching);\n // TODO null out other refs, including component.$$ (but need to\n // preserve final state?)\n $$.on_destroy = $$.fragment = null;\n $$.ctx = [];\n }\n}\nfunction make_dirty(component, i) {\n if (component.$$.dirty[0] === -1) {\n dirty_components.push(component);\n schedule_update();\n component.$$.dirty.fill(0);\n }\n component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));\n}\nfunction init(component, options, instance, create_fragment, not_equal, props, append_styles, dirty = [-1]) {\n const parent_component = current_component;\n set_current_component(component);\n const $$ = component.$$ = {\n fragment: null,\n ctx: [],\n // state\n props,\n update: noop,\n not_equal,\n bound: blank_object(),\n // lifecycle\n on_mount: [],\n on_destroy: [],\n on_disconnect: [],\n before_update: [],\n after_update: [],\n context: new Map(options.context || (parent_component ? parent_component.$$.context : [])),\n // everything else\n callbacks: blank_object(),\n dirty,\n skip_bound: false,\n root: options.target || parent_component.$$.root\n };\n append_styles && append_styles($$.root);\n let ready = false;\n $$.ctx = instance\n ? instance(component, options.props || {}, (i, ret, ...rest) => {\n const value = rest.length ? rest[0] : ret;\n if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {\n if (!$$.skip_bound && $$.bound[i])\n $$.bound[i](value);\n if (ready)\n make_dirty(component, i);\n }\n return ret;\n })\n : [];\n $$.update();\n ready = true;\n run_all($$.before_update);\n // `false` as a special case of no DOM component\n $$.fragment = create_fragment ? create_fragment($$.ctx) : false;\n if (options.target) {\n if (options.hydrate) {\n const nodes = children(options.target);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n $$.fragment && $$.fragment.l(nodes);\n nodes.forEach(detach);\n }\n else {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n $$.fragment && $$.fragment.c();\n }\n if (options.intro)\n transition_in(component.$$.fragment);\n mount_component(component, options.target, options.anchor, options.customElement);\n flush();\n }\n set_current_component(parent_component);\n}\n/**\n * Base class for Svelte components. Used when dev=false.\n */\nclass SvelteComponent {\n $destroy() {\n destroy_component(this, 1);\n this.$destroy = noop;\n }\n $on(type, callback) {\n if (!is_function(callback)) {\n return noop;\n }\n const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));\n callbacks.push(callback);\n return () => {\n const index = callbacks.indexOf(callback);\n if (index !== -1)\n callbacks.splice(index, 1);\n };\n }\n $set($$props) {\n if (this.$$set && !is_empty($$props)) {\n this.$$.skip_bound = true;\n this.$$set($$props);\n this.$$.skip_bound = false;\n }\n }\n}\n\nconst subscriber_queue = [];\n/**\n * Creates a `Readable` store that allows reading by subscription.\n * @param value initial value\n * @param {StartStopNotifier}start start and stop notifications for subscriptions\n */\nfunction readable(value, start) {\n return {\n subscribe: writable(value, start).subscribe\n };\n}\n/**\n * Create a `Writable` store that allows both updating and reading by subscription.\n * @param {*=}value initial value\n * @param {StartStopNotifier=}start start and stop notifications for subscriptions\n */\nfunction writable(value, start = noop) {\n let stop;\n const subscribers = new Set();\n function set(new_value) {\n if (safe_not_equal(value, new_value)) {\n value = new_value;\n if (stop) { // store is ready\n const run_queue = !subscriber_queue.length;\n for (const subscriber of subscribers) {\n subscriber[1]();\n subscriber_queue.push(subscriber, value);\n }\n if (run_queue) {\n for (let i = 0; i < subscriber_queue.length; i += 2) {\n subscriber_queue[i][0](subscriber_queue[i + 1]);\n }\n subscriber_queue.length = 0;\n }\n }\n }\n }\n function update(fn) {\n set(fn(value));\n }\n function subscribe(run, invalidate = noop) {\n const subscriber = [run, invalidate];\n subscribers.add(subscriber);\n if (subscribers.size === 1) {\n stop = start(set) || noop;\n }\n run(value);\n return () => {\n subscribers.delete(subscriber);\n if (subscribers.size === 0) {\n stop();\n stop = null;\n }\n };\n }\n return { set, update, subscribe };\n}\nfunction derived(stores, fn, initial_value) {\n const single = !Array.isArray(stores);\n const stores_array = single\n ? [stores]\n : stores;\n const auto = fn.length < 2;\n return readable(initial_value, (set) => {\n let inited = false;\n const values = [];\n let pending = 0;\n let cleanup = noop;\n const sync = () => {\n if (pending) {\n return;\n }\n cleanup();\n const result = fn(single ? values[0] : values, set);\n if (auto) {\n set(result);\n }\n else {\n cleanup = is_function(result) ? result : noop;\n }\n };\n const unsubscribers = stores_array.map((store, i) => subscribe(store, (value) => {\n values[i] = value;\n pending &= ~(1 << i);\n if (inited) {\n sync();\n }\n }, () => {\n pending |= (1 << i);\n }));\n inited = true;\n sync();\n return function stop() {\n run_all(unsubscribers);\n cleanup();\n };\n });\n}\n\nvar mergeObjects = (objects) => objects.reduce((prev, curr) => Object.assign(prev, curr), {});\n\n// @ts-ignore\nconst UPDATE_VALUE = (updateValue) => ({ updateValue });\nconst DEFAULT_VALUE = (defaultValue) => ({ defaultValue });\nconst CUSTOM_STORE = (fn) => ({ store: fn });\n// @ts-ignore (fixes error thrown in karma typescript)\nconst DERIVED_STORE = (fn) => ({ store: (defaultValue, stores) => derived(...fn(stores)) });\nconst UNIQUE_DERIVED_STORE = (fn) => ({\n store: (defaultValue, stores) => {\n const [selectedStores, update, isEqual = () => false] = fn(stores);\n let isFirst = true;\n let currentValue;\n return derived(selectedStores, (storeValues, set) => {\n update(storeValues, (value) => {\n if (!isFirst && isEqual(currentValue, value))\n return;\n currentValue = value;\n isFirst = false;\n set(value);\n });\n });\n },\n});\nconst MAP_STORE = (fn) => ({\n store: (defaultValue, stores) => {\n const [valueMapper, observedStores = {}, sorter = undefined] = fn(stores);\n let storedItems = [];\n const $observedStores = {};\n const mapValue = (item) => valueMapper(item, $observedStores);\n // set default properties for each item\n const setValue = (items) => {\n // was empty, still empty\n if (!storedItems.length && !items.length)\n return;\n // update value\n storedItems = items;\n updateValue();\n };\n const updateValue = () => {\n const mappedItems = storedItems.map(mapValue);\n if (sorter)\n mappedItems.sort(sorter);\n storedItems = [...mappedItems];\n set(mappedItems);\n };\n // TODO: need to at some point unsub from these stores\n Object.entries(observedStores).map(([name, store]) => {\n return store.subscribe((value) => {\n $observedStores[name] = value;\n if (!value)\n return;\n updateValue();\n });\n });\n const { subscribe, set } = writable(defaultValue || []);\n return {\n set: setValue,\n update: (fn) => setValue(fn(storedItems)),\n subscribe,\n };\n },\n});\nconst createStore$1 = (accessors, stores, options) => {\n const { store = (defaultValue) => writable(defaultValue), defaultValue = noop$1, // should be a function returning the default value\n updateValue = undefined, } = options;\n // create our private store\n const storeInstance = store(defaultValue(), stores, accessors);\n const { subscribe, update = noop$1 } = storeInstance; // update = noop because not all stores can be updated\n // on update private store\n let unsub;\n const onUpdate = (cb) => {\n let ignoreFirstCallback = true;\n if (unsub)\n unsub();\n unsub = subscribe((value) => {\n // need to ignore first callback because that returns current value\n if (ignoreFirstCallback)\n return (ignoreFirstCallback = false);\n // now we have the newly assigned value\n cb(value);\n unsub();\n unsub = undefined;\n });\n };\n // create the value updater function, needs access to stores so can read all store values\n const updateStoreValue = updateValue ? updateValue(accessors) : passthrough;\n // set and validate value\n storeInstance.set = (nextValue) => update((previousValue) => updateStoreValue(nextValue, previousValue, onUpdate));\n // set default value for external reference\n storeInstance.defaultValue = defaultValue;\n // expose store api\n return storeInstance;\n};\nvar createStores = (props) => {\n const stores = {};\n const accessors = {};\n props.forEach(([name, ...options]) => {\n const opts = mergeObjects(options);\n const store = (stores[name] = createStore$1(accessors, stores, opts));\n const property = {\n get: () => get_store_value(store),\n set: store.set,\n };\n Object.defineProperty(accessors, name, property);\n });\n return {\n stores,\n accessors,\n };\n};\n\n// custom source store so we only trigger update when src changes\nconst sourceStore = () => {\n // inner value\n let value;\n const { subscribe, set } = writable();\n const _set = (newValue) => {\n // is equal, bounce!\n if (value === newValue)\n return;\n value = newValue;\n set(value);\n };\n const _update = (fn) => _set(fn(value));\n return {\n set: _set,\n subscribe,\n update: _update,\n };\n};\nvar props = [\n // io\n ['src', CUSTOM_STORE(sourceStore)],\n ['imageReader'],\n ['imageWriter'],\n // will scramble image data for use with image redaction logic\n ['imageScrambler'],\n // should we render a smooth or pixelated redaction\n ['imageRedactionRendering', DEFAULT_VALUE(() => 'pixelated')],\n // current images\n ['images', DEFAULT_VALUE(() => [])],\n // will process markup items before rendering, used by arrows and frames\n ['shapePreprocessor'],\n // will be called when requesting a resource, return false to prevent loading, receives URL and XHR\n ['willRequestResource'] /* deprecated */,\n // will be called when requesting a resource, receives URL and requestType\n // return false to prevent loading\n // return object with options to update request\n // return undefined to continue as normal\n ['willRequest'],\n // can contain `styleNonce`\n ['csp'],\n];\n\nvar capitalizeFirstLetter = (str) => str.charAt(0).toUpperCase() + str.slice(1);\n\nvar defineMethods = (object, api) => {\n Object.keys(api).forEach((name) => {\n const descriptor = isFunction(api[name])\n ? {\n value: api[name],\n writable: false,\n }\n : api[name];\n Object.defineProperty(object, name, descriptor);\n });\n};\n\n// TODO: replace with pointInPoly from geom, this function works correctly with \"offsetRectToFitPolygon\" but doesn't always work correctly, the pointInPoly from geom does work correctly but causes offsetRectToFitPolygon to sometimes work incorrectly, needs to be cleaned up in the future\nconst pointInPoly = (point, vertices) => {\n let i;\n let a;\n let b;\n let aX;\n let aY;\n let bX;\n let bY;\n let edgeX;\n let edgeY;\n let d;\n const l = vertices.length;\n for (i = 0; i < l; i++) {\n // current vertex\n a = vertices[i];\n // next vertex\n b = vertices[i + 1 > l - 1 ? 0 : i + 1];\n // translate so that point is the origin of the calculation\n aX = a.x - point.x;\n aY = a.y - point.y;\n bX = b.x - point.x;\n bY = b.y - point.y;\n edgeX = aX - bX;\n edgeY = aY - bY;\n d = edgeX * aY - edgeY * aX;\n // 0 is ON the edge, but we check for -0.00001 to fix floating point errors\n if (d < -0.00001)\n return false;\n }\n return true;\n};\nconst scalar = 10000;\nvar offsetRectToFitPolygon = (rect, poly) => {\n const polyLines = quadLines(poly);\n const offset = vectorCreateEmpty();\n const rectVertexes = rectGetCorners(rect);\n // we can fit it\n rectVertexes.forEach((vertex) => {\n // we update each corner by adding the current offset\n vectorAdd(vertex, offset);\n // test if point lies in polygon, if so, all is fine and we can exit\n if (pointInPoly(vertex, poly))\n return;\n polyLines.forEach((line) => {\n // get angle of edge and draw a ray from the corner perpendicular to the edge\n const a = Math.atan2(line.start.y - line.end.y, line.start.x - line.end.x);\n const x = Math.sin(Math.PI - a) * scalar;\n const y = Math.cos(Math.PI - a) * scalar;\n const ray = vectorCreate(vertex.x + x, vertex.y + y);\n // extend the poly line so even if we overshoot the polygon we hit it\n const lineExtended = lineExtend(lineClone(line), scalar);\n // get the resulting intersection (there's always an intersection)\n const intersection = lineLineIntersection(lineCreate(vertex, ray), lineExtended);\n // no intersection, no need to do anything\n if (!intersection)\n return;\n // update offset to move towards image\n vectorAdd(offset, vectorSubtract(vectorClone(intersection), vertex));\n });\n });\n // test if any vertexes still fall outside of poly, if so, we can't fit the rect\n const rectOffset = rectClone$1(rect);\n vectorAdd(rectOffset, offset);\n const rectOffsetVertices = rectGetCorners(rectOffset);\n const fits = rectOffsetVertices.every((vertex) => pointInPoly(vertex, poly));\n if (fits) {\n rectUpdateWithRect(rect, rectOffset);\n return true;\n }\n return false;\n};\n\nvar limitCropRectToImage = (rect, poly) => {\n // get crop rect polygon vertexes\n const rectVertexes = rectGetCorners(rect);\n // if we end up here it doesn't fit, we might need to adjust\n const polyLines = quadToLines(poly, 5);\n const rectCenterPosition = rectCenter(rect);\n const intersections = [];\n rectVertexes.forEach((rectVertex) => {\n const ray = lineMultiply(lineCreate(vectorClone(rectCenterPosition), vectorClone(rectVertex)), 1000000);\n let intersectionFound = false;\n polyLines.map(lineClone).forEach((line) => {\n const intersection = lineLineIntersection(ray, line);\n if (!intersection || intersectionFound)\n return;\n intersections.push(intersection);\n intersectionFound = true;\n });\n });\n // top left -> bottom right\n const tlbr = vectorDistance(intersections[0], intersections[2]);\n // top right -> bottom left\n const trbl = vectorDistance(intersections[1], intersections[3]);\n // calculate smallest rectangle we can make, use that\n const rectLimitedVertices = tlbr < trbl ? [intersections[0], intersections[2]] : [intersections[1], intersections[3]];\n const rectLimitedToPolygon = rectCreateFromPoints(rectLimitedVertices);\n // only use our fitted crop rectangle if it's smaller than our current rectangle,\n // this would mean that our current rectangle couldn't be moved to make it fit\n if (rectLimitedToPolygon.width < rect.width) {\n // need to center on previous rect\n rectUpdateWithRect(rect, rectLimitedToPolygon);\n return true;\n }\n return false;\n};\n\nvar getImagePolygon = (image, imageRotation, imagePerspective = { x: 0, y: 0 }) => {\n const imageRect = rectCreateFromSize(image);\n const imageCenter = rectCenter(imageRect);\n const imagePoly = rectApplyPerspective(imageRect, imagePerspective, imageCenter).map((imageVertex) => vectorRotate(imageVertex, imageRotation, imageCenter));\n // get image poly bounds, we need this to offset the poly vertices from 0,0\n const imagePolyBounds = rectCreateFromPoints(imagePoly);\n // get image polygon vertexes\n return imagePoly.map((imageVertex) => vectorSubtract(imageVertex, imagePolyBounds));\n};\n\nvar getMaxSizeInRect = (size, rotation = 0, aspectRatio = rectAspectRatio(size)) => {\n let width;\n let height;\n if (rotation !== 0) {\n const innerAngle = Math.atan2(1, aspectRatio);\n const rotationSigned = Math.sign(rotation) * rotation;\n const rotationSignedMod = rotationSigned % Math.PI;\n const rotationSignedModHalf = rotationSigned % HALF_PI;\n // determine if is turned on side\n let r;\n if (rotationSignedMod > QUART_PI && rotationSignedMod < HALF_PI + QUART_PI) {\n r = rotationSignedModHalf > QUART_PI ? rotationSigned : HALF_PI - rotationSignedModHalf;\n }\n else {\n r = rotationSignedModHalf > QUART_PI ? HALF_PI - rotationSignedModHalf : rotationSigned;\n }\n const hyp = Math.min(Math.abs(size.height / Math.sin(innerAngle + r)), Math.abs(size.width / Math.cos(innerAngle - r)));\n width = Math.cos(innerAngle) * hyp;\n height = width / aspectRatio;\n }\n else {\n width = size.width;\n height = width / aspectRatio;\n if (height > size.height) {\n height = size.height;\n width = height * aspectRatio;\n }\n }\n return sizeCreate(width, height);\n};\n\nvar limitRectToImage = (rect, imageSize, imageRotation = 0, imagePerspective = vectorCreateEmpty(), minSize) => {\n // rotation and/or perspective, let's use the \"advanced\" collision detection method\n if ((isNumber(imageRotation) && imageRotation !== 0) ||\n imagePerspective.x ||\n imagePerspective.y) {\n const inputAspectRatio = rectAspectRatio(rect);\n // test if crop can fit image, if it can, offset the crop so it fits\n const imagePolygon = getImagePolygon(imageSize, imageRotation, imagePerspective);\n const maxSizeInRect = getMaxSizeInRect(imageSize, imageRotation, inputAspectRatio);\n const canFit = rect.width < maxSizeInRect.width && rect.height < maxSizeInRect.height;\n if (!canFit) {\n const dx = rect.width * 0.5 - maxSizeInRect.width * 0.5;\n const dy = rect.height * 0.5 - maxSizeInRect.height * 0.5;\n // adjust crop rect to max size\n if (rect.width > maxSizeInRect.width) {\n rect.width = maxSizeInRect.width;\n rect.x += dx;\n }\n if (rect.height > maxSizeInRect.height) {\n rect.height = maxSizeInRect.height;\n rect.y += dy;\n }\n // test if has exceeded min size, if so we need to limit the size and recalculate the other edge\n /*\n -\\\n / ---\\\n h2 ---\\\n / ---\\\n +--------w---------+\\\n /| | ---\\\n / | | ---\\\n / | | ---\\\n / | | --\n h1 | | /\n / | | /\n / | | /\n -\\ | | /\n ---\\ | | /\n --+------------------+ /\n ---\\ /\n --\\ /\n ---\\ /\n ---\\ /\n ---\\ /\n --\n */\n }\n offsetRectToFitPolygon(rect, imagePolygon);\n const wasLimited = limitCropRectToImage(rect, imagePolygon);\n // this makes sure that after limiting the size, the crop rect is moved to a position that is inside the image\n if (wasLimited)\n offsetRectToFitPolygon(rect, imagePolygon);\n }\n // no rotation, no perspective, use simple bounds method\n else {\n // remember intended aspect ratio so we can try and recreate it\n const intendedAspectRatio = rectAspectRatio(rect);\n // limit to image size first, can never exceed that\n rect.width = Math.min(rect.width, imageSize.width);\n rect.height = Math.min(rect.height, imageSize.height);\n // reposition rect so it's always inside image bounds\n rect.x = Math.max(rect.x, 0);\n if (rect.x + rect.width > imageSize.width) {\n rect.x -= rect.x + rect.width - imageSize.width;\n }\n rect.y = Math.max(rect.y, 0);\n if (rect.y + rect.height > imageSize.height) {\n rect.y -= rect.y + rect.height - imageSize.height;\n }\n // we get the center of the current rect so we can center the contained rect to it\n const intendedCenter = rectCenter(rect);\n // make sure still adheres to aspect ratio\n const containedRect = rectContainRect(rect, intendedAspectRatio);\n containedRect.width = Math.max(minSize.width, containedRect.width);\n containedRect.height = Math.max(minSize.height, containedRect.height);\n containedRect.x = intendedCenter.x - containedRect.width * 0.5;\n containedRect.y = intendedCenter.y - containedRect.height * 0.5;\n rectUpdateWithRect(rect, containedRect);\n }\n};\n\nvar applyCropRectAction = (cropRectPrevious, cropRectNext, imageSize, imageRotation, imagePerspective, cropLimitToImageBounds, cropMinSize, cropMaxSize) => {\n // clone\n const minSize = sizeClone(cropMinSize);\n // set upper bounds to crop max size\n const maxSize = sizeClone(cropMaxSize);\n // limit max size (more important that min size is respected so first limit max size)\n const maxScalar = fixPrecision(Math.max(cropRectNext.width / maxSize.width, cropRectNext.height / maxSize.height));\n const minScalar = fixPrecision(Math.min(cropRectNext.width / minSize.width, cropRectNext.height / minSize.height));\n // clone for resulting crop rect\n const cropRectOut = rectClone$1(cropRectNext);\n //\n // if exceeds min or max scale correct next crop rectangle to conform to bounds\n //\n if (minScalar < 1 || maxScalar > 1) {\n // center of both previous and next crop rects\n const previousCropRectCenter = rectCenter(cropRectPrevious);\n const nextCropRectCenter = rectCenter(cropRectNext);\n // calculate scales\n const scalar = minScalar < 1 ? minScalar : maxScalar;\n const cx = (nextCropRectCenter.x + previousCropRectCenter.x) / 2;\n const cy = (nextCropRectCenter.y + previousCropRectCenter.y) / 2;\n const cw = cropRectOut.width / scalar;\n const ch = cropRectOut.height / scalar;\n rectUpdate(cropRectOut, cx - cw * 0.5, cy - ch * 0.5, cw, ch);\n }\n // no need to limit to bounds, let's go!\n if (!cropLimitToImageBounds)\n return {\n crop: cropRectOut,\n };\n //\n // make sure the crop is made inside the bounds of the image\n //\n limitRectToImage(cropRectOut, imageSize, imageRotation, imagePerspective, minSize);\n return {\n crop: cropRectOut,\n };\n};\n\nvar getBaseCropRect = (imageSize, transformedCropRect, imageRotation) => {\n const imageRect = rectCreateFromSize(imageSize);\n const imageCenter = rectCenter(imageRect);\n const imageTransformedVertices = rectRotate(imageRect, imageRotation, imageCenter);\n // get the rotated image bounds center (offset isn't relevant as crop is relative to top left image position)\n const imageRotatedBoundsCenter = rectCenter(rectNormalizeOffset(rectCreateFromPoints(imageTransformedVertices)));\n // get the center of the crop inside the rotated image\n const cropCenterInTransformedImage = rectCenter(transformedCropRect);\n // invert the rotation of the crop center around the rotated image center\n const deRotatedCropCenter = vectorRotate(cropCenterInTransformedImage, -imageRotation, imageRotatedBoundsCenter);\n // calculate crop distance from rotated image center\n const cropFromCenterOfTransformedImage = vectorSubtract(deRotatedCropCenter, imageRotatedBoundsCenter);\n // calculate original crop offset (from untransformed image)\n const originalCropCenterOffset = vectorApply(vectorAdd(imageCenter, cropFromCenterOfTransformedImage), fixPrecision);\n return rectCreate(originalCropCenterOffset.x - transformedCropRect.width * 0.5, originalCropCenterOffset.y - transformedCropRect.height * 0.5, transformedCropRect.width, transformedCropRect.height);\n};\n\nvar clamp = (value, min, max) => Math.max(min, Math.min(value, max));\n\nvar applyRotationAction = (imageRotationPrevious, imageRotation, imageRotationRange, cropRect, imageSize, imagePerspective, cropLimitToImageBounds, cropRectOrigin, cropMinSize, cropMaxSize) => {\n // clone\n const minSize = sizeClone(cropMinSize);\n // set upper bounds to crop max size if image is bigger than max size,\n // else if should limit to image bounds use image size as limit\n const maxSize = sizeClone(cropMaxSize);\n if (cropLimitToImageBounds) {\n maxSize.width = Math.min(cropMaxSize.width, imageSize.width);\n maxSize.height = Math.min(cropMaxSize.height, imageSize.height);\n }\n let didAttemptDoubleTurn = false;\n const rotate = (rotationPrevious, rotation) => {\n // get the base crop rect (position of crop rect in untransformed image)\n // if we have the base crop rect we can apply the new rotation to it\n const cropRectBase = getBaseCropRect(imageSize, cropRect, rotationPrevious);\n // calculate transforms based on new rotation and base crop rect\n const imageRect = rectCreateFromSize(imageSize);\n const imageCenter = rectCenter(imageRect);\n const imageTransformedCorners = rectApplyPerspective(imageRect, imagePerspective, imageCenter);\n // need this to correct for perspective centroid displacement\n const perspectiveOffset = vectorSubtract(vectorClone(imageCenter), convexPolyCentroid(imageTransformedCorners));\n // rotate around center of image\n const cropCenter = vectorRotate(rectCenter(cropRectBase), rotation, imageCenter);\n const rotateCropOffset = vectorSubtract(vectorClone(imageCenter), cropCenter);\n // get center of image bounds and move to correct position\n imageTransformedCorners.forEach((imageVertex) => vectorRotate(imageVertex, rotation, imageCenter));\n const imageBoundsRect = rectCreateFromPoints(imageTransformedCorners);\n const imageCentroid = convexPolyCentroid(imageTransformedCorners);\n const cropOffset = vectorAdd(vectorSubtract(vectorSubtract(imageCentroid, rotateCropOffset), imageBoundsRect), perspectiveOffset);\n // create output cropRect\n const cropRectOut = rectCreate(cropOffset.x - cropRectBase.width * 0.5, cropOffset.y - cropRectBase.height * 0.5, cropRectBase.width, cropRectBase.height);\n // if has size target, scale croprect to target size\n if (cropRectOrigin) {\n rectScale$1(cropRectOut, cropRectOrigin.width / cropRectOut.width);\n }\n // if should limit to image bounds\n if (cropLimitToImageBounds) {\n const imagePoly = getImagePolygon(imageSize, rotation, imagePerspective);\n // offsetRectToFitPolygon(cropRectOut, imagePoly);\n // commenting this fixes poly sliding problem when adjusting rotation\n limitCropRectToImage(cropRectOut, imagePoly);\n }\n //#region if exceeds min or max adjust rotation to conform to bounds\n const minScalar = fixPrecision(Math.min(cropRectOut.width / minSize.width, cropRectOut.height / minSize.height), 8);\n const maxScalar = fixPrecision(Math.max(cropRectOut.width / maxSize.width, cropRectOut.height / maxSize.height), 8);\n if (minScalar < 1 || maxScalar > 1) {\n // determine if is full image turn\n const isTurn = fixPrecision(Math.abs(rotation - rotationPrevious)) === fixPrecision(Math.PI / 2);\n // try another turn if is turning image\n if (isTurn && !didAttemptDoubleTurn) {\n didAttemptDoubleTurn = true;\n return rotate(imageRotationPrevious, imageRotationPrevious + Math.sign(rotation - rotationPrevious) * Math.PI);\n }\n }\n //#endregion\n return {\n rotation,\n crop: rectApply(cropRectOut, (v) => fixPrecision(v, 8)),\n };\n };\n // amount of turns applied, we need this to correctly determine the allowed rotation range\n const imageTurns = Math.sign(imageRotation) * Math.round(Math.abs(imageRotation) / HALF_PI) * HALF_PI;\n const imageRotationClamped = clamp(imageRotation, imageTurns + imageRotationRange[0], imageTurns + imageRotationRange[1]);\n // set new crop position\n return rotate(imageRotationPrevious, imageRotationClamped);\n};\n\n// @ts-ignore\nconst ORDERED_STATE_PROPS = [\n // requirements\n 'cropLimitToImage',\n 'cropMinSize',\n 'cropMaxSize',\n 'cropAspectRatio',\n // selection -> flip -> rotate -> (perspective ->) crop\n 'flipX',\n 'flipY',\n 'rotation',\n 'crop',\n // effects\n 'colorMatrix',\n 'convolutionMatrix',\n 'gamma',\n 'vignette',\n // 'noise',\n // shapes\n 'manipulation',\n 'redaction',\n 'annotation',\n 'decoration',\n 'selection',\n 'frame',\n // other\n 'backgroundColor',\n 'backgroundImage',\n 'targetSize',\n 'metadata',\n // video\n 'trim',\n 'volume',\n 'minDuration',\n 'maxDuration',\n 'currentTime',\n];\nconst clone = (value) => {\n if (isArray(value)) {\n return value.map(clone);\n }\n else if (isBlob(value) || isFile(value)) {\n return value;\n }\n else if (isObject(value)) {\n return { ...value };\n }\n return value;\n};\nconst filterShapeState = (shapes) => shapes.map((shape) => Object.entries(shape).reduce((copy, [key, value]) => {\n if (key.startsWith('_'))\n return copy;\n copy[key] = value;\n return copy;\n}, {}));\nvar stateStore = (_, stores, accessors) => {\n const observedStores = ORDERED_STATE_PROPS.map((key) => stores[key]);\n // used to wait with deriving state until new state is fully assigned\n let isAssigningState = false;\n const forceState = writable({});\n // can only subscribe, setting is done directly through store accessors\n // @ts-ignore (fixes error thrown in karma typescript)\n const { subscribe } = derived([...observedStores, forceState], (values, set) => {\n // is setting state\n if (isAssigningState)\n return;\n // create new state by looping over props in certain order\n const state = ORDERED_STATE_PROPS.reduce((prev, curr, i) => {\n prev[curr] = clone(values[i]);\n return prev;\n }, {});\n // round crop if defined\n state.crop && rectApply(state.crop, Math.round);\n // remove internal state props from decoration and annotation\n state.manipulation = state.manipulation && filterShapeState(state.manipulation);\n state.redaction = state.redaction && filterShapeState(state.redaction);\n state.annotation = state.annotation && filterShapeState(state.annotation);\n state.decoration = state.decoration && filterShapeState(state.decoration);\n state.selection = state.selection && filterShapeState(state.selection);\n // set new state\n set(state);\n });\n const setState = (state) => {\n // requires at least some state to be supplied\n if (!state)\n return;\n // now assigning state so don't derive state object untill done\n isAssigningState = true;\n // make sure crop origin is reset\n accessors.cropOrigin = undefined;\n // apply new values\n ORDERED_STATE_PROPS\n // remove keys that weren't set\n .filter((key) => hasProp(state, key))\n // apply each key in order\n .forEach((key) => {\n const value = state[key];\n accessors[key] = value === null ? undefined : clone(value);\n });\n // now refresh state\n isAssigningState = false;\n forceState.set({});\n };\n return {\n set: setState,\n update: (fn) => setState(fn(null)),\n subscribe,\n };\n};\n\nvar toNumericAspectRatio = (v) => {\n if (!v)\n return undefined;\n if (/:/.test(v)) {\n const [w, h] = v.split(':');\n return w / h;\n }\n return parseFloat(v);\n};\n\nconst simpleEqual = (a, b) => a === b;\nvar arrayEqual = (a, b, itemEqual = simpleEqual) => {\n // if length is different, it's always changed\n if (a.length !== b.length)\n return false;\n // need to test item equality\n for (let i = 0; i < a.length; i++) {\n if (!itemEqual(a[i], b[i]))\n return false;\n }\n return true;\n};\n\nvar padColorArray = (color = [0, 0, 0, 0], opacity = 1.0) => color.length === 4 ? color : [...color, opacity];\n\n//\n// helper methods\n//\nconst isCropCentered = (crop, imageSize, imageRotation) => {\n const cropCenter = vectorApply(rectCenter(crop), (v) => fixPrecision(v, 8));\n const imageRect = rectCreateFromSize(imageSize);\n const imageCenter = rectCenter(imageRect);\n const imageRotatedVertices = rectRotate(imageRect, imageRotation, imageCenter);\n const imageBoundsCenter = vectorApply(sizeCenter(rectCreateFromPoints(imageRotatedVertices)), (v) => fixPrecision(v, 8));\n const dx = Math.abs(imageBoundsCenter.x - cropCenter.x);\n const dy = Math.abs(imageBoundsCenter.y - cropCenter.y);\n return dx < 1 && dy < 1;\n};\nconst isCropMaxSize = (cropRect, imageSize, rotation) => {\n const maxSize = getMaxSizeInRect(imageSize, rotation, rectAspectRatio(cropRect));\n return sizeEqual(sizeApply(maxSize, Math.round), sizeApply(sizeClone(cropRect), Math.round));\n};\n//\n// updater methods\n//\nconst updateCropRect = (props) => (cropNext, cropPrevious = cropNext) => {\n // wait for image to load\n const { loadState, size, cropMinSize, cropMaxSize, cropLimitToImage, cropAspectRatio, rotation, perspective, } = props;\n // image hasn't loaded yet, use supplied crop rect\n if ((!cropNext && !cropPrevious) || !loadState || !loadState.beforeComplete)\n return cropNext;\n // crop previous set, crop next set to undefined, set crop to fit image\n if (!cropNext)\n cropNext = rectCreateFromSize(getMaxSizeInRect(size, rotation, cropAspectRatio || rectAspectRatio(size)));\n // apply the action\n const res = applyCropRectAction(cropPrevious, cropNext, size, rotation, perspective, cropLimitToImage, cropMinSize, cropMaxSize);\n const cropOut = rectApply(res.crop, (v) => fixPrecision(v, 8));\n if (rectEqual(cropPrevious, cropOut))\n return cropPrevious;\n return cropOut;\n};\nconst updateCropAspectRatio = (props) => (aspectRatioNext, aspectRatioPrevious) => {\n const { loadState, crop, size, rotation, cropLimitToImage } = props;\n const aspectRatio = toNumericAspectRatio(aspectRatioNext);\n // no aspect ratio means custom aspect ratio so set to undefined\n if (!aspectRatio)\n return undefined;\n // can't update crop if not defined yet\n if (!crop || !loadState || !loadState.beforeComplete)\n return aspectRatio;\n // calculate difference between aspect ratios, if big difference, re-align in image\n const aspectRatioDist = aspectRatioPrevious\n ? Math.abs(aspectRatioNext - aspectRatioPrevious)\n : 1;\n // if crop centered scale up\n if (isCropCentered(crop, size, rotation) && cropLimitToImage && aspectRatioDist >= 0.1) {\n const imageSize = sizeTurn(sizeClone(size), rotation);\n props.crop = rectApply(rectContainRect(rectCreateFromSize(imageSize), aspectRatioNext), fixPrecision);\n }\n else {\n const cropSize = {\n width: crop.height * aspectRatio,\n height: crop.height,\n };\n const tx = (crop.width - cropSize.width) * 0.5;\n const ty = (crop.height - cropSize.height) * 0.5;\n props.crop = rectApply(rectCreate(crop.x + tx, crop.y + ty, cropSize.width, cropSize.height), fixPrecision);\n }\n return aspectRatio;\n};\nconst updateCropLimitToImage = (props) => (limitToImageNext, limitToImagePrevious, onUpdate) => {\n // skip if no crop defined\n const { crop } = props;\n if (!crop)\n return limitToImageNext;\n // if was not limiting previously and now set limiting make sure crop fits bounds\n if (!limitToImagePrevious && limitToImageNext) {\n onUpdate(() => (props.crop = rectClone$1(props.crop)));\n }\n return limitToImageNext;\n};\nconst updateRotation = (props) => (rotationNext, rotationPrevious, onUpdate) => {\n // when image rotation is updated we need to adjust the\n // cropRect offset so rotation happens from cropRect center\n // no change\n if (rotationNext === rotationPrevious)\n return rotationNext;\n // get relevant data from store state\n const { loadState, size, rotationRange, cropMinSize, cropMaxSize, crop, perspective, cropLimitToImage, cropOrigin, } = props;\n // image not ready, exit!\n if (!crop || !loadState || !loadState.beforeComplete)\n return rotationNext;\n // remember if current crop was at max size and centered, if so, resulting crop should also be at max size\n const cropWasAtMaxSize = cropLimitToImage && isCropMaxSize(crop, size, rotationPrevious);\n const cropWasCentered = cropLimitToImage && isCropCentered(crop, size, rotationPrevious);\n // get new state\n const res = applyRotationAction(rotationPrevious, rotationNext, rotationRange, crop, size, perspective, cropLimitToImage, cropOrigin, cropMinSize, cropMaxSize);\n // if is centered, and initial crop was at max size expand crop to max size\n if (cropWasAtMaxSize && cropWasCentered) {\n const rect = getMaxSizeInRect(size, rotationNext, rectAspectRatio(res.crop));\n // move top left corner\n res.crop.x += res.crop.width * 0.5;\n res.crop.y += res.crop.height * 0.5;\n res.crop.x -= rect.width * 0.5;\n res.crop.y -= rect.height * 0.5;\n // update size to max size\n res.crop.width = rect.width;\n res.crop.height = rect.height;\n }\n // return validated rotation value, then, after we assign that value, we update the crop rect\n // we may only call onUpdate if a change was made\n onUpdate(() => {\n props.crop = rectApply(res.crop, (v) => fixPrecision(v, 8));\n });\n // return result rotation (might have been rotated twice to fit crop rectangle)\n return res.rotation;\n};\n// updates the range of valid rotation input\nconst updateRotationRange = (imageSize, imageIsRotatedSideways, cropMinSize, cropSize, cropLimitToImage) => {\n if (!cropLimitToImage)\n return [-Infinity, Infinity];\n /*\n - 'a' is angle between diagonal and image height\n - 'b' is angle between diagonal and crop height\n - 'c' is angle between diagonal and image width\n - resulting range is then a - b\n +----------/\\------------------------+\n | / \\ \\ |\n | / \\ \\ |\n | / \\ \\ |\n | \\ \\ \\ |\n | \\ \\ \\ |\n | \\ \\ \\ |\n | \\ \\ / |\n | \\ \\ / |\n | \\ \\ / |\n | \\\\a/b |\n +---------------------\\--------------+\n */\n const scalar = Math.max(cropMinSize.width / cropSize.width, cropMinSize.height / cropSize.height);\n const minSize = sizeCreate(cropSize.width * scalar, cropSize.height * scalar);\n // the hypotenus is the length of the diagonal of the min crop size\n const requiredSpace = sizeHypotenuse(minSize);\n // minimum space available in horizontal / vertical direction\n const availableSpace = Math.min(imageSize.width, imageSize.height);\n // if there's enough space available, we can return the max range\n if (requiredSpace < availableSpace)\n return [-Infinity, Infinity];\n // if the image is turned we need to swap the width and height\n const imageWidth = imageIsRotatedSideways ? imageSize.height : imageSize.width;\n const imageHeight = imageIsRotatedSideways ? imageSize.width : imageSize.height;\n // subtracting the angle between the hypotenuse and the crop itself\n const a = Math.acos(minSize.height / requiredSpace);\n const b = Math.acos(imageHeight / requiredSpace);\n const c = Math.asin(imageWidth / requiredSpace);\n const rangeHorizontal = a - b;\n const rangeVertical = c - a;\n // range is not a number, it means we can rotate as much as we want\n if (Number.isNaN(rangeHorizontal) && Number.isNaN(rangeVertical))\n return [-Infinity, Infinity];\n // get minimum range\n const range = Number.isNaN(rangeHorizontal)\n ? rangeVertical\n : Number.isNaN(rangeVertical)\n ? rangeHorizontal\n : Math.min(rangeHorizontal, rangeVertical);\n // if not, limit range to min and max rotation\n return [-range, range];\n};\n// updates the range of valid crop rectangle input\nconst updateCropRange = (imageSize, rotation, cropAspectRatio, cropMinSize, cropMaxSize, cropLimitToImage) => {\n // ! rotation doesn't affect min size, only max size\n // set lower bounds to crop min size\n const minSize = sizeClone(cropMinSize);\n // set upper bounds to crop max size\n const maxSize = sizeClone(cropMaxSize);\n // now we got basic bounds, let's see if we should limit to the image bounds, else we done\n if (!cropLimitToImage)\n return [minSize, maxSize];\n return [minSize, sizeApply(getMaxSizeInRect(imageSize, rotation, cropAspectRatio), Math.round)];\n};\nconst formatShape = (shape, options) => {\n const { context, props } = options;\n // only auto-format once\n if (!shape._isFormatted) {\n shape = shapeFormat(shape);\n shape._isFormatted = true;\n Object.assign(shape, props);\n }\n // we need to make sure shape is still correctly positioned relative to parent context\n // draft cannot be relative\n // if context changed\n // if has left top right or bottom\n if (!shape._isDraft &&\n shapeHasRelativeSize(shape) &&\n (!shape._context || !rectEqual(context, shape._context))) {\n shapeComputeRect(shape, context);\n shape._context = { ...context };\n }\n return shape;\n};\nconst formatSelectionShape = (shape, options) => {\n if (!shape._isFormatted) {\n shape.disableMove = true;\n shape.disableSelect = true;\n shape.disableResize = true;\n shape.disableRotate = true;\n }\n return formatShape(shape, options);\n};\nconst updateFrame = () => (frameShapeNext) => {\n if (!frameShapeNext)\n return;\n const shape = {\n frameStyle: undefined,\n x: 0,\n y: 0,\n width: '100%',\n height: '100%',\n disableStyle: ['backgroundColor', 'strokeColor', 'strokeWidth'],\n };\n if (isString(frameShapeNext)) {\n shape.frameStyle = frameShapeNext;\n }\n else {\n Object.assign(shape, frameShapeNext);\n }\n return shape;\n};\nvar ImageStorePropDescriptors = [\n // media info received from read\n ['file'],\n ['size'],\n // loading and processing state\n ['loadState'],\n ['processState'],\n // derived info\n [\n 'aspectRatio',\n DERIVED_STORE(({ size }) => [\n size,\n ($size) => ($size ? rectAspectRatio($size) : undefined),\n ]),\n ],\n // image modifications\n ['perspectiveX', DEFAULT_VALUE(() => 0)],\n ['perspectiveY', DEFAULT_VALUE(() => 0)],\n [\n 'perspective',\n DERIVED_STORE(({ perspectiveX, perspectiveY }) => [\n [perspectiveX, perspectiveY],\n ([x, y]) => ({ x, y }),\n ]),\n ],\n ['rotation', DEFAULT_VALUE(() => 0), UPDATE_VALUE(updateRotation)],\n ['flipX', DEFAULT_VALUE(() => false)],\n ['flipY', DEFAULT_VALUE(() => false)],\n ['flip', DERIVED_STORE(({ flipX, flipY }) => [[flipX, flipY], ([x, y]) => ({ x, y })])],\n [\n 'isRotatedSideways',\n UNIQUE_DERIVED_STORE(({ rotation }) => [\n [rotation],\n ([$rotation], set) => set(isRotatedSideways($rotation)),\n (prevValue, nextValue) => prevValue !== nextValue,\n ]),\n ],\n ['crop', UPDATE_VALUE(updateCropRect)],\n ['cropAspectRatio', UPDATE_VALUE(updateCropAspectRatio)],\n ['cropOrigin'],\n ['cropMinSize', DEFAULT_VALUE(() => ({ width: 1, height: 1 }))],\n ['cropMaxSize', DEFAULT_VALUE(() => ({ width: 32768, height: 32768 }))],\n ['cropLimitToImage', DEFAULT_VALUE(() => true), UPDATE_VALUE(updateCropLimitToImage)],\n [\n 'cropSize',\n UNIQUE_DERIVED_STORE(({ crop }) => [\n [crop],\n ([$crop], set) => {\n if (!$crop)\n return;\n set(sizeCreate($crop.width, $crop.height));\n },\n // if is same as previous size, don't trigger update (happens when updating only the crop offset)\n (prevValue, nextValue) => sizeEqual(prevValue, nextValue),\n ]),\n ],\n [\n 'cropRectAspectRatio',\n DERIVED_STORE(({ cropSize }) => [\n [cropSize],\n ([$cropSize], set) => {\n if (!$cropSize)\n return;\n set(fixPrecision(rectAspectRatio($cropSize), 5));\n },\n ]),\n ],\n [\n 'cropRange',\n UNIQUE_DERIVED_STORE(({ size, rotation, cropRectAspectRatio, cropMinSize, cropMaxSize, cropLimitToImage, }) => [\n [size, rotation, cropRectAspectRatio, cropMinSize, cropMaxSize, cropLimitToImage],\n ([$size, $rotation, $cropRectAspectRatio, $cropMinSize, $cropMaxSize, $cropLimitToImage,], set) => {\n // wait for image size\n if (!$size)\n return;\n const range = updateCropRange($size, $rotation, $cropRectAspectRatio, $cropMinSize, $cropMaxSize, $cropLimitToImage);\n set(range);\n },\n // if is same range as previous range, don't trigger update\n (prevRange, nextRange) => arrayEqual(prevRange, nextRange),\n ]),\n ],\n [\n 'rotationRange',\n UNIQUE_DERIVED_STORE(({ size, isRotatedSideways, cropMinSize, cropSize, cropLimitToImage }) => [\n [size, isRotatedSideways, cropMinSize, cropSize, cropLimitToImage],\n ([$size, $isRotatedSideways, $cropMinSize, $cropSize, $cropLimitToImage], set) => {\n // wait for image size\n if (!$size || !$cropSize)\n return;\n const range = updateRotationRange($size, $isRotatedSideways, $cropMinSize, $cropSize, $cropLimitToImage);\n set(range);\n },\n // if is same range as previous range, don't trigger update\n (prevRange, nextRange) => arrayEqual(prevRange, nextRange),\n ]),\n ],\n // canvas\n ['backgroundColor', UPDATE_VALUE(() => (color) => padColorArray(color))],\n ['backgroundImage'],\n // size\n ['targetSize'],\n // effects\n ['colorMatrix'],\n ['convolutionMatrix'],\n ['gamma'],\n ['noise'],\n ['vignette'],\n // video\n ['duration'],\n ['currentTime'],\n ['minDuration', DEFAULT_VALUE(() => 1 / 24)],\n ['maxDuration', DEFAULT_VALUE(() => Infinity)],\n ['volume', DEFAULT_VALUE(() => 1)],\n ['trim'],\n // redaction lives in image space\n ['redaction', MAP_STORE(({ size }) => [formatShape, { context: size }])],\n // modification lives in image space\n ['manipulation', MAP_STORE(({ size }) => [formatShape, { context: size }])],\n // annotation lives in image space\n ['annotation', MAP_STORE(({ size }) => [formatShape, { context: size }])],\n // decoration lives in crop space\n ['decoration', MAP_STORE(({ crop }) => [formatShape, { context: crop }])],\n // image selection\n ['selection', MAP_STORE(({ size }) => [formatSelectionShape, { context: size }])],\n // frame to render on top of the image (or outside)\n ['frame', UPDATE_VALUE(updateFrame)],\n // custom metadata\n ['metadata'],\n // state of image, used to restore a previous state or request the current state\n ['state', CUSTOM_STORE(stateStore)],\n];\n\nvar process = async (value, chainTasks, chainOptions = {}, processOptions) => {\n // options relevant to the process method itself\n const { ontaskstart, ontaskprogress, ontaskend, token } = processOptions;\n // has been cancelled\n let cancelled = false;\n // create token to pass to tasks\n const taskCancelToken = {\n cancel: noop$1,\n };\n // set cancel handler method\n token.cancel = () => {\n // cancel called from outside of the process method\n cancelled = true;\n // assign cancel method to task\n taskCancelToken.cancel();\n };\n // step through chain\n for (const [index, task] of chainTasks.entries()) {\n // exit when cancelled\n if (cancelled)\n return;\n // get the task function and the id so we can notify the callee of the task that is being started\n const [fn, id] = task;\n // start task\n ontaskstart(index, id);\n try {\n value = await fn(value, { ...chainOptions, taskCancelToken }, (event) => ontaskprogress(index, id, event));\n }\n catch (err) {\n // stop processing more items in the chain\n cancelled = true;\n // pass error back to parent\n throw err;\n }\n ontaskend(index, id);\n }\n return value;\n};\n\n// TODO: find better location for minSize / file load validation\nvar createImageCore = ({ minSize = { width: 1, height: 1 }, minDuration = 0 } = {}) => {\n // create default store\n const { stores, accessors } = createStores(ImageStorePropDescriptors);\n // pub/sub\n const { pub, sub } = pubsub();\n // processing handler\n const createProcessingHandler = (stateProp, eventKey) => {\n const getStore = () => accessors[stateProp] || {};\n const setStore = (obj) => {\n accessors[stateProp] = {\n ...getStore(),\n ...obj,\n timeStamp: Date.now(),\n };\n };\n const hasError = () => getStore().error;\n const handleError = (error) => {\n if (hasError())\n return;\n setStore({\n error: error,\n });\n pub(`${eventKey}error`, { ...getStore() });\n };\n return {\n start() {\n pub(`${eventKey}start`);\n },\n onabort() {\n setStore({\n abort: true,\n });\n pub(`${eventKey}abort`, { ...getStore() });\n },\n ontaskstart(index, id) {\n if (hasError())\n return;\n setStore({\n index,\n task: id,\n taskProgress: undefined,\n taskLengthComputable: undefined,\n });\n pub(`${eventKey}taskstart`, { ...getStore() });\n },\n ontaskprogress(index, id, event) {\n if (hasError())\n return;\n setStore({\n index,\n task: id,\n taskProgress: event.loaded / event.total,\n taskLengthComputable: event.lengthComputable,\n });\n pub(`${eventKey}taskprogress`, { ...getStore() });\n pub(`${eventKey}progress`, { ...getStore() });\n },\n ontaskend(index, id) {\n if (hasError())\n return;\n setStore({\n index,\n task: id,\n });\n pub(`${eventKey}taskend`, { ...getStore() });\n },\n ontaskerror(error) {\n handleError(error);\n },\n error(error) {\n handleError(error);\n },\n beforeComplete(data) {\n if (hasError())\n return;\n setStore({ beforeComplete: true });\n pub(`before${eventKey}`, data);\n },\n complete(data) {\n if (hasError())\n return;\n setStore({ complete: true });\n pub(eventKey, data);\n },\n };\n };\n //#region read image\n const read = (src, { reader }, readerOptions = {}) => {\n // exit if no reader supplied\n if (!reader)\n return;\n // reset file data to undefined as we're loading a new image\n Object.assign(accessors, {\n file: undefined,\n size: undefined,\n loadState: undefined,\n });\n // our cancel token so we can abort load if needed, cancel will be set by process\n let imageReadToken = { cancel: noop$1 };\n let imageReadCancelled = false;\n const imageReadHandler = createProcessingHandler('loadState', 'load');\n const processOptions = {\n token: imageReadToken,\n ...imageReadHandler,\n };\n const readerState = {\n src,\n size: undefined,\n dest: undefined,\n duration: undefined,\n };\n // wait a tick before starting image read so the read can be cancelled in loadstart\n Promise.resolve().then(async () => {\n try {\n imageReadHandler.start();\n if (imageReadCancelled)\n return imageReadHandler.onabort();\n const output = (await process(readerState, reader, readerOptions, processOptions));\n // was cancelled\n if (imageReadCancelled)\n return imageReadHandler.onabort();\n // get shortcuts for validation\n const { size, duration, dest } = output || {};\n // if we don't have a size\n if (!size || !size.width || !size.height)\n throw new EditorError('Image size missing', 'IMAGE_SIZE_MISSING', output);\n // if we have a duration and minDuration\n if (duration > 0 && duration < minDuration)\n throw new EditorError('Video too short', 'VIDEO_TOO_SHORT', {\n ...output,\n minDuration,\n });\n // size of image is too small\n if (size.width < minSize.width || size.height < minSize.height)\n throw new EditorError('Image too small', 'IMAGE_TOO_SMALL', {\n ...output,\n minWidth: minSize.width,\n minHeight: minSize.height,\n });\n // update internal data\n Object.assign(accessors, {\n file: dest,\n size,\n duration,\n });\n // before load complete\n imageReadHandler.beforeComplete(output);\n // done loading image\n imageReadHandler.complete(output);\n }\n catch (err) {\n imageReadHandler.error(err);\n }\n finally {\n imageReadToken = undefined;\n }\n });\n // call to abort load\n return () => {\n imageReadCancelled = true;\n imageReadToken && imageReadToken.cancel();\n imageReadHandler.onabort();\n };\n };\n //#endregion\n //#region write image\n const write = (writer, options) => {\n // not ready to start processing\n if (!accessors.loadState.complete)\n return;\n // reset process state to undefined\n accessors.processState = undefined;\n const imageWriteHandler = createProcessingHandler('processState', 'process');\n const writerState = {\n src: accessors.file,\n imageState: accessors.state,\n dest: undefined,\n };\n // writer is function, let's get the actual writer\n if (isFunction(writer)) {\n writer = writer(accessors.file, accessors.state);\n }\n // willProcessImageState\n if (!writer) {\n imageWriteHandler.start();\n imageWriteHandler.complete(writerState);\n return;\n }\n // we need this token to be a blet to cancel the processing operation\n let imageWriteToken = { cancel: noop$1 };\n let imageWriteCancelled = false;\n const writerOptions = options;\n const processOptions = {\n token: imageWriteToken,\n ...imageWriteHandler,\n };\n // wait a tick before starting image write so the write can be cancelled in processtart\n Promise.resolve().then(async () => {\n try {\n imageWriteHandler.start();\n // could be cancelled before processing started\n if (imageWriteCancelled)\n return imageWriteHandler.onabort();\n const output = (await process(writerState, writer, writerOptions, processOptions));\n // cancelled during processing\n if (imageWriteCancelled)\n return imageWriteHandler.onabort();\n imageWriteHandler.complete(output);\n }\n catch (err) {\n imageWriteHandler.error(err);\n }\n finally {\n imageWriteToken = undefined;\n }\n });\n // call to abort processing\n return () => {\n imageWriteCancelled = true;\n imageWriteToken && imageWriteToken.cancel();\n };\n };\n //#endregion\n //#region api\n defineMethods(accessors, {\n read,\n write,\n on: sub,\n });\n //#endregion\n // expose store API\n return {\n accessors,\n stores,\n };\n};\n\nconst warnRequiredProp = (prop) => console.warn(`Pintura: ${prop} is a required prop.`);\n\n// @ts-ignore\nconst editorEventsToBubble = [\n 'loadstart',\n 'loadabort',\n 'loaderror',\n 'loadprogress',\n 'load',\n 'processstart',\n 'processabort',\n 'processerror',\n 'processprogress',\n 'process',\n];\nconst imagePrivateProps = [\n 'flip',\n 'cropOrigin',\n 'isRotatedSideways',\n 'perspective',\n 'perspectiveX',\n 'perspectiveY',\n 'cropRange',\n];\nconst editorPrivateProps = ['images'];\nconst imagePublicProps = ImageStorePropDescriptors\n .map(([prop]) => prop)\n .filter((prop) => !imagePrivateProps.includes(prop));\nconst getImagePropGroupedName = (prop) => `image${capitalizeFirstLetter(prop)}`;\nconst getEditorProps$1 = () => {\n const imageProperties = imagePublicProps.map(getImagePropGroupedName);\n const editorProperties = props\n .map(([prop]) => prop)\n .filter((prop) => !editorPrivateProps.includes(prop));\n return imageProperties.concat(editorProperties);\n};\nconst isImageSource = (src) => isString(src) || isBinary(src) || isElement(src);\nconst isImageState = (obj) => hasProp(obj, 'crop');\nvar createImageEditor = () => {\n // create default stores\n const { stores, accessors } = createStores(props);\n // set up pub/sub for the app layer\n const { sub, pub } = pubsub();\n const bubble = (name) => (value) => pub(name, value);\n // helper method\n const getImageObjSafe = () => (accessors.images ? accessors.images[0] : {});\n // initialImageProps is the list of transforms to apply when the image loads\n let initialImageProps = {};\n const initialImagePropsBackup = {};\n // create shortcuts to image props : `crop` -> `imageCrop`\n imagePublicProps.forEach((prop) => {\n Object.defineProperty(accessors, getImagePropGroupedName(prop), {\n get: () => {\n // no image, can't get\n const image = getImageObjSafe();\n if (!image)\n return;\n // return from image state\n return image.accessors[prop];\n },\n set: (value) => {\n // always use as initial prop when loading a new image without reset\n initialImageProps[getImagePropGroupedName(prop)] = value;\n // set to props backup so we can read it later when loading a new image\n initialImagePropsBackup[getImagePropGroupedName(prop)] = value;\n // no image, we can't update\n const image = getImageObjSafe();\n if (!image)\n return;\n // update the image immidiately\n image.accessors[prop] = value;\n },\n });\n });\n // internal helper method to get active image\n const getImage = () => accessors.images && accessors.images[0];\n // timeout for image reader warning\n let imageReaderTestTimeout;\n // handling loading an image if a src is set\n const unsubSrc = stores.src.subscribe((src) => {\n // no image set, means clear active image\n if (!src)\n return (accessors.images = []);\n // exit here if we don't have an imageReader we'll wait for an imageReader to be defined\n if (!accessors.imageReader) {\n // we're setting a src without an image reader, need to warn\n imageReaderTestTimeout = setTimeout(() => warnRequiredProp('imageReader'), 32);\n return;\n }\n // reset initial image props if an image is already loaded, so props applied to previous image aren't applied to the new one\n if (accessors.images.length)\n initialImageProps = {};\n // load image in src prop\n loadSrc(src);\n });\n const unsubReader = stores.imageReader.subscribe((reader) => {\n // can't do anything without an image reader\n if (!reader)\n return;\n // image reader now set, no need to warn\n clearTimeout(imageReaderTestTimeout);\n // an image has already been loaded no need to load images that were set earlier\n if (accessors.images.length)\n return;\n // no image to load, we'll wait for images to be set to the `src` prop\n if (!accessors.src)\n return;\n // src is waiting to be loaded so let's pick it up,\n loadSrc(accessors.src);\n });\n const loadSrc = (src) => {\n // push it back a tick so we know initialImageProps are set\n Promise.resolve()\n .then(() => {\n // load with initial props\n return loadImage(src, initialImageProps);\n })\n .catch(() => {\n // fail silently, any errors are handled with 'loaderror' event\n });\n };\n //#endregion\n //#region public method (note that these are also called from UI, name of method is name of dispatched event in UI)\n const applyImageOptionsOrState = (image, options) => {\n // test if options is image state, if so, apply and exit\n if (isImageState(options)) {\n accessors.imageState = options;\n return;\n }\n // create an initial crop rect if no crop supplied\n if (!options.imageCrop) {\n const imageSize = image.accessors.size;\n const imageRotation = options.imageRotation || 0;\n const imageRotatedSize = sizeRotate(sizeClone(imageSize), imageRotation);\n const cropRect = rectCreateFromSize(imageRotatedSize);\n // prevent negative width/height\n cropRect.width = fixPrecision(Math.abs(cropRect.width), 6);\n cropRect.height = fixPrecision(Math.abs(cropRect.height), 6);\n const aspectRatio = options.imageCropAspectRatio ||\n (options.imageCropLimitToImage\n ? rectAspectRatio(imageSize) // use image size if should limit to image\n : rectAspectRatio(cropRect)); // use rotated crop rect bounds if no limit\n let crop;\n if (options.imageCropLimitToImage) {\n crop = rectContainRect(cropRect, aspectRatio);\n }\n else {\n crop = rectCoverRect(cropRect, aspectRatio);\n // center the image in the crop rectangle\n crop.x = (imageSize.width - crop.width) / 2;\n crop.y = (imageSize.height - crop.height) / 2;\n }\n options.imageCrop = crop;\n }\n // trim not set, let's make sure we respect max duration\n if (image.accessors.duration && !options.imageTrim && options.imageMaxDuration) {\n const { duration } = image.accessors;\n // if duration is longer than max duration we need to set a trim\n options.imageTrim = [[0, Math.min(options.imageMaxDuration / duration, 1)]];\n }\n // we need to apply these props in the correct order\n const propKeys = [\n 'imageCropLimitToImage',\n 'imageCrop',\n 'imageCropAspectRatio',\n 'imageRotation',\n ];\n propKeys\n .filter((prop) => hasProp(options, prop))\n .forEach((prop) => {\n // assign to `image`\n accessors[prop] = options[prop];\n // remove from normalizedOptions so it's not set twice\n delete options[prop];\n });\n // don't set the above options for a second time\n const filteredOptions = Object.keys(options)\n .filter((key) => !propKeys.includes(key))\n .reduce((filtered, prop) => {\n filtered[prop] = options[prop];\n return filtered;\n }, {});\n // trigger setState\n Object.assign(accessors, filteredOptions);\n };\n // load image, resolve when image is loaded\n let imageLoadAbort;\n const loadImage = (src, options = {}) => new Promise((resolve, reject) => {\n // get current image\n let image = getImage();\n // determine min defined image size (is crop min size)\n const cropLimitedToImage = !(options.cropLimitToImage === false ||\n options.imageCropLimitToImage === false ||\n initialImagePropsBackup.imageCropLmitedToImage === false);\n const cropMinSize = options.cropMinSize ||\n options.imageCropMinSize ||\n initialImagePropsBackup.imageCropMinSize;\n const minImageSize = cropLimitedToImage\n ? cropMinSize\n : image && image.accessors.cropMinSize;\n const minVideoDuration = options.minDuration || options.imageMinDuration;\n // if already has image, remove existing image\n if (image)\n removeImage();\n // access image props and stores\n image = createImageCore({ minSize: minImageSize, minDuration: minVideoDuration });\n editorEventsToBubble.map((event) => image.accessors.on(event, bubble(event)));\n // done, clean up listeners\n const fin = () => {\n // reset initial props (as now applied)\n initialImageProps = {};\n unsubs.forEach((unsub) => unsub());\n };\n const unsubs = [];\n unsubs.push(image.accessors.on('loaderror', (error) => {\n fin();\n reject(error);\n }));\n unsubs.push(image.accessors.on('loadabort', () => {\n fin();\n reject({ name: 'AbortError' });\n }));\n unsubs.push(image.accessors.on('load', (output) => {\n imageLoadAbort = undefined;\n fin();\n resolve(output);\n }));\n unsubs.push(image.accessors.on('beforeload', () => applyImageOptionsOrState(image, options)));\n // set new image\n accessors.images = [image];\n // assign passed options to editor accessors, we ignore 'src'\n if (options.imageReader)\n accessors.imageReader = options.imageReader;\n if (options.imageWriter)\n accessors.imageWriter = options.imageWriter;\n // start reading image\n imageLoadAbort = image.accessors.read(src, {\n reader: accessors.imageReader,\n }, {\n willRequest: accessors.willRequest,\n });\n });\n // start processing a loaded image, resolve when image is processed\n let imageProcessAbort;\n const processImage = (src, options) => new Promise((resolve, reject) => {\n try {\n const unsubs = [];\n // done, clean up listeners\n const fin = () => {\n imageProcessAbort = undefined;\n unsubs.forEach((unsub) => unsub());\n };\n (async () => {\n // if src supplied, first load src, then process\n if (isImageSource(src)) {\n try {\n await loadImage(src, options);\n }\n catch (err) {\n reject(err);\n }\n }\n // if first argument is not `src` but is set it's an options object, so we'll update the options before generating the image\n else if (src) {\n if (isImageState(src)) {\n accessors.imageState = src;\n }\n else {\n Object.assign(accessors, src);\n }\n }\n // get current active image\n const image = getImage();\n // needs image for processing\n if (!image)\n return reject('no image');\n unsubs.push(image.accessors.on('processerror', (error) => {\n fin();\n reject(error);\n }));\n unsubs.push(image.accessors.on('processabort', () => {\n fin();\n reject({ name: 'AbortError' });\n }));\n unsubs.push(image.accessors.on('process', (output) => {\n fin();\n resolve(output);\n }));\n imageProcessAbort = image.accessors.write(accessors.imageWriter, {\n redactionRenderStyle: accessors.imageRedactionRendering,\n shapePreprocessor: accessors.shapePreprocessor || passthrough,\n imageScrambler: accessors.imageScrambler,\n willRequest: accessors.willRequest,\n csp: accessors.csp || {},\n willRequestResource: accessors.willRequestResource, // deprecated\n });\n })();\n }\n catch (err) {\n reject(err);\n }\n });\n const abortProcessImage = () => {\n const image = getImage();\n if (!image)\n return;\n if (imageProcessAbort)\n imageProcessAbort();\n image.accessors.processState = undefined;\n };\n // used internally (triggered by 'x' button when error loading image in UI)\n const abortLoadImage = () => {\n if (imageLoadAbort)\n imageLoadAbort();\n accessors.images = [];\n };\n // edit image, loads an image and resolve when image is processed\n const editImage = (src, options) => new Promise((resolve, reject) => {\n loadImage(src, options)\n .then(() => {\n // access image props and stores\n const { images } = accessors;\n const image = images[0];\n // done, clean up listeners\n const done = () => {\n unsubReject();\n unsubResolve();\n };\n const unsubReject = image.accessors.on('processerror', (error) => {\n done();\n reject(error);\n });\n const unsubResolve = image.accessors.on('process', (output) => {\n done();\n resolve(output);\n });\n })\n .catch(reject);\n });\n const removeImage = () => {\n // no images, nothing to remove\n const image = getImage();\n if (!image)\n return;\n // try to abort image load\n if (imageLoadAbort)\n imageLoadAbort();\n image.accessors.loadState = undefined;\n // clear images\n accessors.images = [];\n };\n //#endregion\n Object.defineProperty(accessors, 'stores', {\n get: () => stores,\n });\n //#region API\n defineMethods(accessors, {\n on: sub,\n loadImage,\n abortLoadImage,\n editImage,\n removeImage,\n processImage,\n abortProcessImage,\n destroy: () => {\n unsubSrc && unsubSrc();\n unsubReader && unsubReader();\n },\n });\n return accessors;\n //#endregion\n};\n\nconst processImage = (src, options) => {\n const { processImage } = createImageEditor();\n return processImage(src, options);\n};\n\nvar getCanvasMemoryLimit = () => {\n if (!isSafari())\n return Infinity;\n if (isIOS()) {\n // limit resolution a little bit further to prevent drawing issues on iOS Safari 15 and 16\n if (/15_|16_/.test(navigator.userAgent))\n return 3840 * 3840;\n // old iOS can deal with 4096 * 4096 without issues\n return 4096 * 4096;\n }\n // limit resolution a little bit further to prevent drawing issues on MacOS Safari 15 (no problems on 16)\n return /15_/.test(navigator.userAgent) ? 4096 * 4096 : Infinity;\n};\n\nvar filterObjectProperties = (obj, props) => Object.keys(obj)\n .filter((key) => !props.includes(key))\n .reduce((prev, curr) => {\n prev[curr] = obj[curr];\n return prev;\n}, {});\n\nvar videoFixDuration = (video) => new Promise((resolve) => {\n // https://bugs.chromium.org/p/chromium/issues/detail?id=656426\n // https://bugs.chromium.org/p/chromium/issues/detail?id=642012\n if (video.duration === Infinity) {\n video.ontimeupdate = () => {\n video.ontimeupdate = undefined;\n resolve(video);\n };\n video.currentTime = Number.MAX_SAFE_INTEGER;\n return;\n }\n resolve(video);\n});\n\nvar getVideoElementDuration = (element) => new Promise((resolve, reject) => {\n const done = () => {\n videoFixDuration(element).then(() => {\n resolve(element.duration);\n });\n };\n if (element.readyState >= 1)\n return done();\n element.onloadedmetadata = done;\n element.onerror = () => reject(element.error);\n});\n\nvar arrayFlatten = (arr) => arr.reduce((flat, item) => {\n const arr = Array.isArray(item) ? [...item] : [item];\n return [...flat, ...arr];\n}, []);\n\nvar getVideoFileRotationMetadata = async (file) => {\n const buffer = await blobToArrayBuffer(file);\n const view = new DataView(buffer);\n return getRotationMetadataInDataView(view);\n};\nconst blobToArrayBuffer = (blob) => new Promise((resolve) => {\n const reader = new FileReader();\n reader.onload = () => resolve(reader.result);\n reader.readAsArrayBuffer(blob);\n});\n//#region mp4\nconst getRotationMetadataInMP4 = (view, offset, limit) => {\n // Bytes for degree metadata\n // 0 degrees\n // 00010000 00000000 00000000 00000000\n // 00010000 00000000 00000000 00000000\n // 40\n // 90 degrees\n // 00000000 00010000 00000000 FFFF0000\n // 00000000 00000000 00000000 00000000\n // 40\n // 180 degrees\n // FFFF0000 00000000 00000000 00000000\n // FFFF0000 00000000 00000000 00000000\n // 40\n // 270 degrees\n // 00000000 FFFF0000 00000000 00010000\n // 00000000 00000000 00000000 00000000\n // 40\n const TRAK_MARKER = 0x7472616b; // Video track\n const ROTA_MARKER = 0x40; // Rotation byte\n let inTrack = false;\n // rotation marker offset\n let r = -1;\n for (let i = offset; i < limit; i++) {\n // find start of video track\n if (!inTrack && view.getUint32(i) === TRAK_MARKER) {\n inTrack = true;\n i += 4;\n }\n // not found yet, skip to next byte\n if (!inTrack)\n continue;\n // found, let's search rotation marker\n const value = view.getUint8(i);\n // now looking for rotation marker offset\n if (value == ROTA_MARKER) {\n r = i;\n break;\n }\n }\n // rotation marker not found, exit\n if (r < 0)\n return 0;\n // Let's read bytes preceding the rotation marker entry to check rotation value\n const B = view.getUint32(r - 28);\n const D = view.getUint32(r - 20);\n const A = view.getUint32(r - 32);\n const E = view.getUint32(r - 16);\n if (B === 0x00010000 && D === 0xffff0000)\n return 90;\n else if (A === 0xffff0000 && E === 0xffff0000)\n return 180;\n else if (B === 0xffff0000 && D === 0x00010000)\n return 270;\n return 0;\n};\n//#endregion\n//#region mov\nconst MOOV_ATOM = 0x6d6f6f76;\nconst MVHD_ATOM = 0x6d766864;\nconst TKHD_ATOM = 0x746b6864;\nconst TRAK_ATOM = 0x7472616b;\nconst getAtoms = (view, offset = 0, size = view.byteLength) => {\n const atoms = [];\n const end = offset + size;\n while (offset < end) {\n const size = view.getUint32(offset);\n if (size < 0)\n break;\n const type = view.getUint32(offset + 4);\n atoms.push({\n type,\n size,\n offset,\n bodyOffset: offset + 8,\n bodySize: size - 8,\n });\n if (size < 8)\n break;\n offset += size;\n }\n return atoms;\n};\nconst getRotationMetadataInMOV = (view) => {\n // find container atoms\n const atoms = getAtoms(view);\n // exit no moov atom find\n const moov = atoms.find((atom) => atom.type === MOOV_ATOM);\n if (!moov)\n return 0;\n // get moov child atoms\n const moovChildAtoms = getAtoms(view, moov.bodyOffset, moov.bodySize);\n // get all relevant atoms\n moovChildAtoms\n .filter((atom) => atom.type === TRAK_ATOM)\n .forEach((atom) => {\n moovChildAtoms.push(...getAtoms(view, atom.bodyOffset, atom.bodySize));\n });\n // filter out irrelevant atoms\n const neededAtoms = [MVHD_ATOM, TKHD_ATOM];\n const relevantAtoms = moovChildAtoms.filter((atom) => neededAtoms.includes(atom.type));\n for (const { type, bodyOffset } of relevantAtoms) {\n // read first byte of body to get version\n const version = view.getUint8(bodyOffset);\n // skip version/tags header\n let offset = bodyOffset + 4;\n // move pointer based on atom type and version\n offset += type === MVHD_ATOM ? 32 : 36; // 'mvhd'\n offset += version === 1 ? 12 : 0;\n // get relevant part of matrix\n const A = view.getInt32(offset);\n const B = view.getInt32(offset + 4);\n if (A === 0 && B > 0)\n return 90;\n if (A < 0 && B === 0)\n return 180;\n if (A === 0 && B < 0)\n return 270;\n }\n return 0;\n};\n//#endregion\nconst getRotationMetadataInDataView = (view, { limit = 1024 } = {}) => {\n const FTYP_MARKER = 0x66747970; // QuickTime Container File Type\n const MP42_MARKER = 0x6d703432; // MP4 format\n const QT_MARKER = 0x71742020; // QT format\n let offset = 0;\n offset += 4;\n const container = view.getUint32(offset);\n if (container !== FTYP_MARKER)\n return 0;\n offset += 4;\n const format = view.getUint32(offset);\n if (format === MP42_MARKER)\n return getRotationMetadataInMP4(view, offset, limit);\n if (format === QT_MARKER)\n return getRotationMetadataInMOV(view);\n return 0;\n};\n\nvar blobToVideo = (src) => new Promise((resolve, reject) => {\n // read metadata\n const metadataReader = isFirefox() ? getVideoFileRotationMetadata(src) : Promise.resolve(0);\n // if rotated need to correct video\n metadataReader.then((rotation) => {\n // let's load this source as a video file\n const video = document.createElement('video');\n video.onerror = () => reject(video.error);\n video.playsInline = true;\n video.preload = 'auto';\n video.onseeked = () => {\n video.onseeked = undefined;\n resolve(video);\n };\n video.onloadeddata = () => {\n video.onloadeddata = undefined;\n video.dataset.rotation = rotation ? `${rotation}` : '0';\n videoFixDuration(video).then(resolve);\n // if we use onloadedmetadata safari doesn't render the frame\n video.currentTime = 0;\n };\n // we need to do this otherwise video won't load on iOS Safari, tested up to iOS 17.5\n // please note that using #t= will work in iOS Simulator but won't work on a real device\n // we can use srcObject instead as it's only supported on Safari\n if (isIOS() && isSafari()) {\n video.srcObject = src;\n video.load();\n }\n else {\n video.src = URL.createObjectURL(src);\n }\n });\n});\n\nvar seekTo = (video, targetTime) => new Promise((resolve) => {\n // already at target time\n if (video.currentTime === targetTime)\n return resolve(video);\n // backup current fn\n let onseeked = video.onseeked;\n video.onseeked = () => {\n // restore backup\n video.onseeked = onseeked;\n // done!\n resolve(video);\n };\n video.currentTime = clamp(targetTime, 0, video.duration);\n});\n\n// TODO: VIDEO cleanup?\nvar videoToImageData = (file, currentTime = 0) => new Promise((resolve, reject) => {\n // turn blob into video element\n blobToVideo(file).then((video) => {\n // now seek to the intended frame\n seekTo(video, currentTime).then((video) => {\n // let's turn that frame into a canvas\n const frame = h('canvas');\n getVideoElementSize(video).then(({ width, height }) => {\n // resize canvas to fit video size\n frame.width = width;\n frame.height = height;\n // get context to draw on, we're going to read from this canvas, no need to hardware accelerate it\n const ctx = frame.getContext('2d', {\n willReadFrequently: true,\n });\n // draw to canvas context\n const drawFrame = () => {\n ctx.drawImage(video, 0, 0, frame.width, frame.height);\n const imageData = ctx.getImageData(0, 0, frame.width, frame.height);\n // after draw release canvas memory\n releaseCanvas(frame);\n // after draw, release video file memory\n URL.revokeObjectURL(video.src);\n resolve(imageData);\n };\n // if we don't use setTimeout for safari sometimes it doesn't draw the frame\n isSafari() ? setTimeout(drawFrame, 16) : drawFrame();\n });\n });\n });\n});\n\nvar isVideoFile = (blob) => /video/.test(blob.type);\n\n// custom method to draw images\nconst createCanvasImageDrawer = ({ imageDataResizer, canvasMemoryLimit } = {}) => async (ctx, image, srcRect, destRect, options) => {\n // min size of src is 1x1\n srcRect.width = Math.max(srcRect.width, 1);\n srcRect.height = Math.max(srcRect.height, 1);\n // min size of dest is 1x1\n destRect.width = Math.max(destRect.width, 1);\n destRect.height = Math.max(destRect.height, 1);\n // get resized image\n const { dest } = await processImage(image, {\n imageReader: createDefaultImageReader$1(),\n imageWriter: createDefaultImageWriter$1({\n format: 'canvas',\n targetSize: {\n ...destRect,\n upscale: true,\n },\n imageDataResizer,\n canvasMemoryLimit,\n }),\n imageCrop: srcRect,\n });\n // feather the canvas\n const { feather = 0 } = options || {};\n if (feather > 0) {\n const featherCanvas = h('canvas');\n featherCanvas.width = dest.width;\n featherCanvas.height = dest.height;\n const featherCtx = featherCanvas.getContext('2d', {\n // this prevents Chrome issue where Chrome doesn't correctly apply the mask in composition operation\n willReadFrequently: true,\n });\n const opacityStep = 1 / feather;\n let i = 0;\n for (i = 0; i < feather; i++) {\n const opacity = i * opacityStep;\n featherCtx.strokeStyle = `rgba(0,0,0,${opacity * opacity})`;\n featherCtx.strokeRect(i + 0.5, i + 0.5, dest.width - i * 2 - 1.0, dest.height - i * 2 - 1.0);\n }\n featherCtx.fillStyle = '#000';\n featherCtx.fillRect(i, i, dest.width + 0.5 - i * 2, dest.height + 0.5 - i * 2);\n const destCtx = dest.getContext('2d');\n destCtx.globalCompositeOperation = 'destination-in';\n destCtx.drawImage(featherCanvas, 0, 0);\n // release image canvas to free up memory\n releaseCanvas(featherCanvas);\n }\n // draw processed image\n ctx.drawImage(dest, destRect.x, destRect.y, destRect.width, destRect.height);\n // release image canvas to free up memory\n releaseCanvas(dest);\n};\n// connect function in process chain\nconst connect = (fn, getter = (...args) => args, setter) => async (state, options, onprogress) => {\n // will hold function result\n // at this point we don't know if the length of this task can be computed\n onprogress(createProgressEvent(0, false));\n // try to run the function\n let progressUpdated = false;\n const res = await fn(...getter(state, options, (event) => {\n progressUpdated = true;\n onprogress(event);\n }));\n // a setter isn't required\n setter && setter(state, res);\n // if progress was updated, we expect the connected function to fire the 1/1 event, else we fire it here\n if (!progressUpdated)\n onprogress(createProgressEvent(1, false));\n return state;\n};\n//\n// Reader/Writer Presets\n//\nconst AnyToFile = ({ willRequest = undefined, srcProp = 'src', destProp = 'dest' } = {}) => [\n connect(srcToFile, (state, options, onprogress) => [\n state[srcProp],\n onprogress,\n { ...options, willRequest },\n ], (state, file) => (state[destProp] = file)),\n 'any-to-file',\n];\nconst BlobGuardMimeType = ({ srcProp = 'dest', destProp = 'dest' } = {}) => [\n connect(async (src) => {\n // attempt to fix octet stream\n if (/octet-stream/.test(src.type)) {\n console.warn(`Pintura: File has unknown mime type \"${src.type}\", make sure your server sets the correct Content-Type header.`);\n // assume is image\n let mediaType = 'image';\n // test if is possibly video\n const el = await getAsVideoOrImageElement(src);\n if (/video/i.test(el.nodeName))\n mediaType = 'video';\n // set unknown media type\n const propertyBag = { type: mediaType + '/unknown' };\n return isFile(src)\n ? new File([src], src.name, {\n ...propertyBag,\n lastModified: src.lastModified,\n })\n : new Blob([src], propertyBag);\n }\n // return original file\n return src;\n }, (state) => [state[srcProp]], (state, file) => (state[destProp] = file)),\n 'any-to-file',\n];\nconst BlobReadImageSize = ({ srcProp = 'src', destProp = 'size' } = {}) => [\n connect(getImageSize, (state) => [state[srcProp]], (state, size) => (state[destProp] = size)),\n 'read-image-size',\n];\nconst getMediaDuration = async (src) => {\n const mediaElement = await getAsVideoOrImageElement(src);\n if (isVideoElement(mediaElement))\n return await getVideoElementDuration(mediaElement);\n return undefined;\n};\nconst BlobReadMediaDuration = ({ srcProp = 'src', destProp = 'duration' } = {}) => [\n connect(getMediaDuration, (state) => [state[srcProp]], (state, duration) => (state[destProp] = duration)),\n 'read-media-duration',\n];\nconst ImageSizeMatchOrientation = ({ srcSize = 'size', srcOrientation = 'orientation', destSize = 'size', } = {}) => [\n connect(orientImageSize, (state) => [state[srcSize], state[srcOrientation]], (state, size) => (state[destSize] = size)),\n 'image-size-match-orientation',\n];\nconst BlobCopy = ({ srcProp = 'src', destProp = 'copy' } = {}) => [\n connect((blob, onprogress) => (isJPEG(blob) ? readFile(blob, onprogress) : undefined), (state) => [state[srcProp], onprogress], (state, head) => (state[destProp] = head)),\n 'read-image-head',\n];\nconst ImageHeadReadExifOrientationTag = ({ srcProp = 'copy', destProp = 'orientation', } = {}) => [\n connect(arrayBufferImageExif, (state) => [state[srcProp], ORIENTATION_TAG], (state, orientation = 1) => (state[destProp] = orientation)),\n 'read-exif-orientation-tag',\n];\nconst ImageHeadClearExifOrientationTag = ({ srcProp = 'copy' } = {}) => [\n connect(arrayBufferImageExif, (state) => [state[srcProp], ORIENTATION_TAG, 1]),\n 'clear-exif-orientation-tag',\n];\nconst CalculateCanvasScalar = ({ srcImageSize = 'size', srcCanvasSize = 'imageData', srcImageState = 'imageState', destImageSize = 'size', destScalar = 'scalar', } = {}) => [\n connect((naturalSize, canvasSize) => {\n return [\n Math.min(canvasSize.width / naturalSize.width, canvasSize.height / naturalSize.height),\n sizeCreateFromAny(canvasSize),\n ];\n }, (state) => [state[srcImageSize], state[srcCanvasSize], state[srcImageState]], (state, [scalar, imageSize]) => {\n state[destScalar] = scalar;\n state[destImageSize] = imageSize;\n }),\n 'calculate-canvas-scalar',\n];\nconst BlobToImageData = ({ srcProp = 'src', destProp = 'imageData', canvasMemoryLimit = undefined, srcImageState = 'imageState', } = {}) => [\n connect((src, canvasMemoryLimit, imageState) => {\n // can now deal with video files\n if (isVideoFile(src))\n return videoToImageData(src, imageState.currentTime);\n // handle images\n return blobToImageData(src, canvasMemoryLimit);\n }, (state) => [state[srcProp], canvasMemoryLimit, state[srcImageState]], (state, imageData) => (state[destProp] = imageData)),\n 'blob-to-image-data',\n];\nconst ImageDataMatchOrientation = ({ srcImageData = 'imageData', srcOrientation = 'orientation', } = {}) => [\n connect(orientImageData, (state) => [state[srcImageData], state[srcOrientation]], (state, imageData) => (state.imageData = imageData)),\n 'image-data-match-orientation',\n];\nconst ImageDataFill = ({ srcImageData = 'imageData', srcImageState = 'imageState' } = {}) => [\n connect(fillImageData, (state) => [\n state[srcImageData],\n {\n backgroundColor: state[srcImageState].backgroundColor,\n backgroundImage: state[srcImageState].backgroundImage,\n },\n ], (state, imageData) => (state.imageData = imageData)),\n 'image-data-fill',\n];\nconst ImageDataCrop = ({ srcImageData = 'imageData', srcImageState = 'imageState', destScalar = 'scalar', } = {}) => [\n connect(cropImageData, (state) => {\n // scale crop if needed\n const scalar = state[destScalar];\n let { crop } = state[srcImageState];\n if (crop && scalar !== 1) {\n crop = rectScale$1(rectClone$1(crop), scalar, vectorCreateEmpty());\n }\n // apply crop\n return [\n state[srcImageData],\n {\n crop,\n rotation: state[srcImageState].rotation,\n flipX: state[srcImageState].flipX,\n flipY: state[srcImageState].flipY,\n },\n ];\n }, (state, imageData) => (state.imageData = imageData)),\n 'image-data-crop',\n];\nconst hasTargetSize = (imageState) => !!((imageState.targetSize && imageState.targetSize.width) ||\n (imageState.targetSize && imageState.targetSize.height));\nconst ImageDataResize = ({ targetSize = {\n width: undefined,\n height: undefined,\n fit: undefined,\n upscale: undefined,\n}, imageDataResizer = undefined, srcProp = 'imageData', srcImageState = 'imageState', destImageScaledSize = 'imageScaledSize', }) => [\n connect(resizeImageData, (state) => {\n const width = Math.min(targetSize.width || Number.MAX_SAFE_INTEGER, (state[srcImageState].targetSize && state[srcImageState].targetSize.width) ||\n Number.MAX_SAFE_INTEGER);\n const height = Math.min(targetSize.height || Number.MAX_SAFE_INTEGER, (state[srcImageState].targetSize && state[srcImageState].targetSize.height) ||\n Number.MAX_SAFE_INTEGER);\n return [\n state[srcProp],\n {\n width,\n height,\n fit: targetSize.fit || 'contain',\n upscale: hasTargetSize(state[srcImageState])\n ? true\n : targetSize.upscale || false,\n },\n imageDataResizer,\n ];\n }, (state, imageData) => {\n // if did resize we need to set scalar\n if (!sizeEqual(state.imageData, imageData)) {\n state[destImageScaledSize] = sizeCreateFromAny(imageData);\n }\n state.imageData = imageData;\n }),\n 'image-data-resize',\n];\nconst ImageDataFilter = ({ srcImageData = 'imageData', srcImageState = 'imageState', destImageData = 'imageData', } = {}) => [\n connect(filterImageData, (state) => {\n const { colorMatrix } = state[srcImageState];\n const colorMatrices = colorMatrix &&\n Object.keys(colorMatrix)\n .map((name) => colorMatrix[name])\n .filter(Boolean);\n return [\n state[srcImageData],\n {\n colorMatrix: colorMatrices && getColorMatrixFromColorMatrices(colorMatrices),\n convolutionMatrix: state[srcImageState].convolutionMatrix,\n gamma: state[srcImageState].gamma,\n noise: state[srcImageState].noise,\n vignette: state[srcImageState].vignette,\n },\n ];\n }, (state, imageData) => (state[destImageData] = imageData)),\n 'image-data-filter',\n];\nconst ImageDataRedact = ({ srcImageData = 'imageData', srcImageState = 'imageState', destImageData = 'imageData', destScalar = 'scalar', } = {}) => [\n connect(async (imageData, imageScrambler, imageBackgroundColor, shapes, scalar, redactionRenderStyle) => {\n // skip!\n if (!imageScrambler || !shapes.length)\n return imageData;\n // create scrambled texture version\n let scrambledCanvas;\n try {\n const options = {\n dataSizeScalar: getImageRedactionScaleFactor(imageData, shapes),\n };\n if (imageBackgroundColor && imageBackgroundColor[3] > 0) {\n options.backgroundColor = [...imageBackgroundColor];\n }\n scrambledCanvas = await imageScrambler(imageData, options);\n }\n catch (err) {\n // log to console for debugging purposes\n }\n // create drawing context\n const canvas = h('canvas');\n canvas.width = imageData.width;\n canvas.height = imageData.height;\n const ctx = canvas.getContext('2d', { willReadFrequently: true });\n ctx.putImageData(imageData, 0, 0);\n // blur\n if (redactionRenderStyle === 'auto') {\n const size = Math.sqrt(imageData.width * imageData.height);\n ctx.filter = `blur(${Math.round(size / 100)}px)`;\n }\n // set up a clip path so we only draw scrambled image within path\n const path = new Path2D();\n shapes.forEach((shape) => {\n const rect = rectCreate(shape.x, shape.y, shape.width, shape.height);\n rectMultiply(rect, scalar);\n const corners = rectRotate(rectClone$1(rect), shape.rotation);\n const poly = new Path2D();\n corners.forEach((corner, i) => {\n if (i === 0)\n return poly.moveTo(corner.x, corner.y);\n poly.lineTo(corner.x, corner.y);\n });\n path.addPath(poly);\n });\n ctx.clip(path, 'nonzero');\n ctx.imageSmoothingEnabled = false;\n ctx.drawImage(scrambledCanvas, 0, 0, canvas.width, canvas.height);\n releaseCanvas(scrambledCanvas);\n // done\n const imageDataOut = ctx.getImageData(0, 0, canvas.width, canvas.height);\n // clean up memory usage\n releaseCanvas(canvas);\n return imageDataOut;\n }, (state, { imageScrambler, redactionRenderStyle }) => [\n state[srcImageData],\n imageScrambler,\n state[srcImageState].backgroundColor,\n state[srcImageState].redaction,\n state[destScalar],\n redactionRenderStyle,\n ], (state, imageData) => (state[destImageData] = imageData)),\n 'image-data-redact',\n];\nconst ImageDataAnnotate = ({ srcImageData = 'imageData', srcSize = 'size', srcImageState = 'imageState', srcShapes = 'annotation', destImageData = 'imageData', destImageScaledSize = 'imageScaledSize', destScalar = 'scalar', imageDataResizer = undefined, canvasMemoryLimit = undefined, shapeFilter = passthrough, shapeClip = true, destImageContext = 'imageContext', } = {}) => [\n connect(drawImageData, (state, { shapePreprocessor, willRequestResource, csp, willRequest }) => {\n // scale annotations if needed\n const shapes = state[srcImageState][srcShapes].filter(shapeFilter);\n // skip if no annotations\n if (!shapes.length)\n return [state[srcImageData]];\n const canvasScalar = state[destScalar];\n const { crop } = state[srcImageState];\n const imageSize = state[srcSize];\n // image is scaled\n let imageSizeScalar = canvasScalar;\n const imageTargetSize = state[destImageScaledSize];\n if (imageTargetSize) {\n // calculate shapes scalar\n imageSizeScalar = Math.min(imageTargetSize.width / crop.width, imageTargetSize.height / crop.height);\n }\n const imageSizeScaled = {\n width: imageSize.width / canvasScalar,\n height: imageSize.height / canvasScalar,\n };\n // go\n return [\n state[srcImageData],\n {\n shapes,\n computeShape: (shape) => {\n shape = shapeComputeDisplay(shape, imageSizeScaled);\n shape = filterObjectProperties(shape, ['left', 'right', 'top', 'bottom']);\n shape = shapeScale(shape, imageSizeScalar);\n return shape;\n },\n transform: (ctx) => {\n const imageSize = state[srcSize];\n const { rotation = 0, flipX, flipY, cropLimitToImage, } = state[srcImageState];\n // scale crop if needed\n const { crop = rectCreateFromSize(imageSize) } = state[srcImageState];\n // calculate image scalar so we can scale annotations accordingly\n const scaledSize = state[destImageScaledSize];\n let scalar = 1;\n if (scaledSize) {\n scalar = imageSizeScalar;\n }\n else {\n scalar = canvasScalar;\n }\n const imageScaledSize = {\n width: (imageSize.width / canvasScalar) * scalar,\n height: (imageSize.height / canvasScalar) * scalar,\n };\n const rotatedRect = getImageTransformedRect(imageScaledSize, rotation);\n const rotatedSize = {\n width: rotatedRect.width,\n height: rotatedRect.height,\n };\n // calculate center\n const dx = imageScaledSize.width * 0.5 - rotatedSize.width * 0.5;\n const dy = imageScaledSize.height * 0.5 - rotatedSize.height * 0.5;\n const center = sizeCenter(imageScaledSize);\n // offset\n ctx.translate(-dx, -dy);\n ctx.translate(-crop.x * scalar, -crop.y * scalar);\n // rotation\n ctx.translate(center.x, center.y);\n ctx.rotate(rotation);\n ctx.translate(-center.x, -center.y);\n // translate shapes if rendered in outside frame context (for example polaroid)\n const shapeTranslation = state[destImageContext] || { x: 0, y: 0 };\n ctx.translate(shapeTranslation.x, shapeTranslation.y);\n // flipping\n ctx.scale(flipX ? -1 : 1, flipY ? -1 : 1);\n ctx.translate(flipX ? -imageScaledSize.width * canvasScalar : 0, flipY ? -imageScaledSize.height * canvasScalar : 0);\n // annotations are clipped to image\n if (cropLimitToImage && shapeClip) {\n ctx.rect(0, 0, imageScaledSize.width, imageScaledSize.height);\n ctx.clip();\n }\n },\n drawImage: createCanvasImageDrawer({ imageDataResizer, canvasMemoryLimit }),\n preprocessShape: (shape) => shapePreprocessor(shape, { isPreview: false, ...state[srcImageState] }),\n canvasMemoryLimit,\n willRequest: willRequest || willRequestResource,\n styleNonce: csp.styleNonce,\n },\n ];\n }, (state, imageData) => (state[destImageData] = imageData)),\n 'image-data-annotate',\n];\nconst ImageDataDecorate = ({ srcImageData = 'imageData', srcImageState = 'imageState', destImageData = 'imageData', destImageScaledSize = 'imageScaledSize', imageDataResizer = undefined, canvasMemoryLimit = undefined, destScalar = 'scalar', shapeFilter = passthrough, destImageContext = 'imageContext', } = {}) => [\n connect(drawImageData, (state, { shapePreprocessor, willRequestResource, willRequest, csp }) => {\n // scale annotations if needed\n // is `let` on purpose\n let decoration = state[srcImageState].decoration.filter(shapeFilter);\n // skip if no annotations\n if (!decoration.length)\n return [state[srcImageData]];\n // scale decoration and crop if needed\n let scalar = state[destScalar];\n const { crop } = state[srcImageState];\n // image is scaled\n const imageTargetSize = state[destImageScaledSize];\n if (imageTargetSize) {\n const imageSizeScalar = Math.min(imageTargetSize.width / crop.width, imageTargetSize.height / crop.height);\n scalar = imageSizeScalar;\n }\n // go\n return [\n state[srcImageData],\n {\n shapes: decoration,\n drawImage: createCanvasImageDrawer({ imageDataResizer, canvasMemoryLimit }),\n computeShape: (shape) => {\n shape = shapeComputeDisplay(shape, crop);\n shape = filterObjectProperties(shape, ['left', 'right', 'top', 'bottom']);\n shape = shapeScale(shape, scalar);\n return shape;\n },\n preprocessShape: (shape) => shapePreprocessor(shape, { isPreview: false, ...state[srcImageState] }),\n canvasMemoryLimit,\n willRequest: willRequest || willRequestResource,\n styleNonce: csp.styleNonce,\n transform: (ctx) => {\n const shapeTranslation = state[destImageContext] || { x: 0, y: 0 };\n ctx.translate(shapeTranslation.x, shapeTranslation.y);\n },\n },\n ];\n }, (state, imageData) => (state[destImageData] = imageData)),\n 'image-data-decorate',\n];\nconst ImageDataFrame = ({ srcImageData = 'imageData', srcImageState = 'imageState', destImageData = 'imageData', destImageScaledSize = 'imageScaledSize', imageDataResizer = undefined, canvasMemoryLimit = undefined, destScalar = 'scalar', destImageContext = 'imageContext', } = {}) => [\n connect((imageData, options) => new Promise((resolve) => {\n if (!options)\n return resolve([imageData]);\n drawImageData(imageData, options).then((imageData) => {\n resolve([imageData, options.contextBounds]);\n });\n }), (state, { shapePreprocessor, willRequestResource, willRequest, csp }) => {\n const frame = state[srcImageState].frame;\n if (!frame)\n return [state[srcImageData]];\n // scale crop if needed\n const scalar = state[destScalar];\n let { crop } = state[srcImageState];\n if (crop && scalar !== 1) {\n crop = rectScale$1(rectClone$1(crop), scalar, vectorCreateEmpty());\n }\n const context = { ...crop };\n const bounds = shapesBounds(shapesFromCompositShape(frame, context, shapePreprocessor), context);\n context.x = Math.abs(bounds.left);\n context.y = Math.abs(bounds.top);\n context.width += Math.abs(bounds.left) + Math.abs(bounds.right);\n context.height += Math.abs(bounds.top) + Math.abs(bounds.bottom);\n const scaledSize = state[destImageScaledSize];\n const contaxtScalar = scaledSize\n ? Math.min(scaledSize.width / crop.width, scaledSize.height / crop.height)\n : 1;\n rectMultiply(context, contaxtScalar);\n // use floor because we can't fill up half pixels\n context.x = Math.floor(context.x);\n context.y = Math.floor(context.y);\n context.width = Math.floor(context.width);\n context.height = Math.floor(context.height);\n return [\n state[srcImageData],\n {\n shapes: [frame],\n contextBounds: context,\n computeShape: (shape) => shapeComputeDisplay(shape, state[srcImageData]),\n transform: (ctx) => {\n ctx.translate(context.x, context.y);\n },\n drawImage: createCanvasImageDrawer({ imageDataResizer, canvasMemoryLimit }),\n preprocessShape: (shape) => shapePreprocessor(shape, { isPreview: false, ...state[srcImageState] }),\n canvasMemoryLimit,\n willRequest: willRequest || willRequestResource,\n styleNonce: csp.styleNonce,\n },\n ];\n }, (state, [imageData, context]) => {\n state[destImageContext] = context;\n state[destImageData] = imageData;\n }),\n 'image-data-frame',\n];\nconst ImageDataToBlob = ({ mimeType = undefined, quality = undefined, srcImageData = 'imageData', srcFile = 'src', destBlob = 'blob', } = {}) => [\n connect(imageDataToBlob, (state) => [\n state[srcImageData],\n mimeType || getMimeTypeFromFilename(state[srcFile].name) || state[srcFile].type,\n quality,\n ], (state, blob) => (state[destBlob] = blob)),\n 'image-data-to-blob',\n];\nconst ImageDataToCanvas = ({ srcImageData = 'imageData', srcOrientation = 'orientation', destCanvas = 'dest', } = {}) => [\n connect(imageDataToCanvas, (state) => [state[srcImageData], state[srcOrientation]], (state, canvas) => (state[destCanvas] = canvas)),\n 'image-data-to-canvas',\n];\nconst writeImageHead = async (blob, head) => {\n if (!isJPEG(blob) || !head)\n return blob;\n const eachMarker = (ab, cb) => {\n // Skip JPEG identifier\n let offset = 2;\n const dataView = new DataView(ab);\n const byteLength = dataView.byteLength;\n // start\n cb(dataView.getUint8(0), 0, 2, byteLength);\n // loop over bytes\n while (offset < byteLength) {\n if (dataView.getUint8(offset) != 0xff)\n break;\n const marker = dataView.getUint8(offset + 1);\n const length = dataView.getUint16(offset + 2) + 2;\n // can exit if returns false\n if (cb(marker, offset, length, byteLength) === false)\n break;\n offset += length;\n }\n };\n const isAppHeader = (marker) => {\n return marker >= 224 && marker <= 237; // APP0 to APP13\n };\n const getAppHeaders = (arrayBuffer) => {\n const headers = [];\n eachMarker(arrayBuffer, (marker, offset, length) => {\n // start of scan\n if (marker === 0xda)\n return false;\n // app header\n if (isAppHeader(marker)) {\n headers.push(new Uint8Array(arrayBuffer, offset, length));\n }\n });\n return headers;\n };\n const insertAppHeaders = (arrayBuffer, appHeaders) => {\n const segments = [];\n eachMarker(arrayBuffer, (marker, offset, length, totalLength) => {\n // start of image marker\n if (offset === 0) {\n // add start header\n segments.push(new Uint8Array(arrayBuffer, offset, length));\n // add original app headers\n segments.push(...appHeaders);\n return;\n }\n // start of scan\n if (marker === 0xda) {\n segments.push(new Uint8Array(arrayBuffer, offset, totalLength - offset));\n return false;\n }\n // app header\n if (!isAppHeader(marker)) {\n segments.push(new Uint8Array(arrayBuffer, offset, length));\n }\n });\n // created new file\n let combinedLength = segments.reduce((acc, seg) => acc + seg.length, 0);\n let combinedArray = new Uint8Array(combinedLength);\n let currentOffset = 0;\n segments.forEach((segment) => {\n combinedArray.set(segment, currentOffset);\n currentOffset += segment.length;\n });\n return combinedArray;\n };\n // gets app headers from original file\n const srcAppHeaders = getAppHeaders(head);\n // removes any app headers from this file (Chrome recently (2024) started adding these)\n const destUintArray = insertAppHeaders(await readFile(blob), srcAppHeaders);\n return new Blob([destUintArray], { type: 'image/jpeg' });\n};\nconst BlobWriteImageHead = (srcBlob = 'blob', srcHead = 'copy', destBlob = 'blob') => [\n connect(writeImageHead, (state) => [state[srcBlob], state[srcHead]], (state, blob) => (state[destBlob] = blob)),\n 'blob-write-image-head',\n];\nconst BlobToFile = ({ renameFile = undefined, srcBlob = 'blob', srcFile = 'src', destFile = 'dest', defaultFilename = undefined, } = {}) => [\n connect(blobToFile, (state) => [\n state[srcBlob],\n renameFile\n ? renameFile(state[srcFile])\n : state[srcFile].name ||\n `${defaultFilename}.${getExtensionFromMimeType(state[srcBlob].type)}`,\n ], (state, file) => (state[destFile] = file)),\n 'blob-to-file',\n];\nconst Store = ({ url = './', dataset = (state) => [\n ['dest', state.dest, state.dest.name],\n ['imageState', state.imageState],\n], destStore = 'store', credentials, headers = {}, }) => [\n connect(\n // upload function\n async (dataset, onprogress) => await post(url, dataset, {\n onprogress,\n beforeSend: (xhr) => configureXHR(xhr, { headers, credentials }),\n }), \n // get state values\n (state, options, onprogress) => [\n dataset(state),\n onprogress,\n ], \n // set state values\n (state, xhr) => (state[destStore] = xhr) // logs XHR request returned by `post`\n ),\n 'store',\n];\nconst PropFilter = (allowlist) => [\n connect((state) => {\n // if no allowlist suppleid or is empty array we don't filter\n if (!allowlist || !allowlist.length)\n return state;\n // else we only allow the props defined in the list and delete non matching props\n Object.keys(state).forEach((key) => {\n if (allowlist.includes(key))\n return;\n delete state[key];\n });\n return state;\n }),\n 'prop-filter',\n];\n// Generic image reader, suitable for most use cases\nconst createDefaultImageReader$1 = (options = {}) => {\n const { \n // add xhr Promise intercept here\n orientImage = true, outputProps = ['src', 'dest', 'size', 'duration'], preprocessImageFile, request = {}, } = options;\n return [\n // can read most source files and turn them into blobs\n AnyToFile({ willRequest: () => request }),\n // test if supported mime/type\n BlobGuardMimeType(),\n // called when file created, can be used to read unrecognized files\n preprocessImageFile && [\n connect(preprocessImageFile, (state, options, onprogress) => [\n state.dest,\n options,\n onprogress,\n ], (state, file) => (state.dest = file)),\n 'preprocess-image-file',\n ],\n // quickly read size (only reads first part of image)\n BlobReadImageSize({ srcProp: 'dest' }),\n // quickly read duration\n BlobReadMediaDuration({ srcProp: 'dest' }),\n // fix image orientation\n orientImage && BlobCopy({ srcProp: 'dest' }),\n orientImage && ImageHeadReadExifOrientationTag(),\n orientImage && ImageSizeMatchOrientation(),\n // remove unwanted props\n PropFilter(outputProps),\n ].filter(Boolean);\n};\nconst createStore = (store) => store &&\n (isString(store)\n ? // a basic store to post to\n Store({ url: store })\n : // see if is fully custom or store config\n isFunction(store)\n ? // fully custom store function\n [store, 'store']\n : // a store configuration object\n Store(store));\nconst createDefaultImageWriter$1 = (options = {}) => (src, imageState, genericOptions = {}) => {\n // let's go!\n let { canvasMemoryLimit = getCanvasMemoryLimit(), orientImage = true, copyImageHead = true, mimeType = undefined, quality = undefined, renameFile = undefined, targetSize = undefined, imageDataResizer = undefined, store = undefined, format = 'file', outputProps = ['src', 'dest', 'imageState', 'store'], preprocessImageSource = undefined, preprocessImageState = undefined, postprocessImageData = undefined, postprocessImageBlob = undefined, \n // by default doesn't test if should process, this prop is set to true by media writer\n testSrcSupport = false, } = { ...options, ...genericOptions };\n // if doesn't pass src filter, skip\n if (testSrcSupport && !isImage(src))\n return;\n // use 'file' format if defined as 'blob'\n if (format === 'blob')\n format = 'file';\n return [\n // allow preprocessing of image blob, should return a new blob, for example to automatically make image background transparent\n preprocessImageSource && [\n connect(preprocessImageSource, (state, options, onprogress) => [state.src, options, onprogress, state.imageState], (state, src) => (state.src = src)),\n 'preprocess-image-source',\n ],\n // get orientation info (if is jpeg)\n (orientImage || copyImageHead) && BlobCopy(),\n orientImage && ImageHeadReadExifOrientationTag(),\n // get image size\n BlobReadImageSize(),\n // allow preproccesing of image state for example to replace placeholders\n preprocessImageState && [\n connect(preprocessImageState, (state, options, onprogress, metadata) => [\n state.imageState,\n options,\n onprogress,\n { size: state.size, orientation: state.orientation },\n ], (state, imageState) => (state.imageState = imageState)),\n 'preprocess-image-state',\n ],\n // get image data\n BlobToImageData({ canvasMemoryLimit }),\n // fix image orientation\n orientImage && ImageSizeMatchOrientation(),\n orientImage && ImageDataMatchOrientation(),\n // apply canvas scalar to data\n CalculateCanvasScalar(),\n // apply image state\n ImageDataRedact(),\n ImageDataCrop(),\n ImageDataResize({ imageDataResizer, targetSize }),\n ImageDataAnnotate({ imageDataResizer, canvasMemoryLimit, srcShapes: 'manipulation' }),\n ImageDataFilter(),\n ImageDataFill(),\n ImageDataAnnotate({\n imageDataResizer,\n canvasMemoryLimit,\n srcShapes: 'annotation',\n shapeFilter: (shape) => !shape.aboveFrame,\n }),\n ImageDataDecorate({\n imageDataResizer,\n canvasMemoryLimit,\n shapeFilter: (shape) => !shape.aboveFrame,\n }),\n ImageDataFrame({ imageDataResizer, canvasMemoryLimit }),\n // Draw on top of frame\n ImageDataAnnotate({\n imageDataResizer,\n canvasMemoryLimit,\n srcShapes: 'annotation',\n shapeFilter: (shape) => shape.aboveFrame,\n shapeClip: false,\n }),\n ImageDataDecorate({\n imageDataResizer,\n canvasMemoryLimit,\n shapeFilter: (shape) => shape.aboveFrame,\n }),\n // run post processing on image data, for example to apply circular crop\n postprocessImageData && [\n connect(postprocessImageData, (state, options, onprogress) => [state.imageData, options, onprogress], (state, imageData) => (state.imageData = imageData)),\n 'postprocess-image-data',\n ],\n // convert to correct output format\n format === 'file'\n ? ImageDataToBlob({ mimeType, quality })\n : format === 'canvas'\n ? ImageDataToCanvas()\n : [\n (state) => {\n state.dest = state.imageData;\n return state;\n },\n ],\n // we overwite the exif orientation tag so the image is oriented correctly\n format === 'file' && orientImage && ImageHeadClearExifOrientationTag(),\n // we write the new image head to the target blob\n format === 'file' && copyImageHead && BlobWriteImageHead(),\n // allow converting the blob to a different format\n postprocessImageBlob && [\n connect(postprocessImageBlob, ({ blob, imageData, src }, options, onprogress) => [{ blob, imageData, src }, options, onprogress], (state, blob) => (state.blob = blob)),\n 'postprocess-image-file',\n ],\n // turn the image blob into a file, will also rename the file\n format === 'file' && BlobToFile({ defaultFilename: 'image', renameFile }),\n // upload or process data if is a file\n format === 'file'\n ? // used for file output formats\n createStore(store)\n : // used for imageData and canvas output formats\n isFunction(store) && [store, 'store'],\n // remove unwanted props\n PropFilter(outputProps),\n ].filter(Boolean);\n};\nconst isWriter = (param) => Array.isArray(param) || isFunction(param);\nconst createDefaultMediaWriter$1 = (genericWriterOptions, ...writers) => (src, imageState) => {\n // no generic options passed\n if (isWriter(genericWriterOptions))\n writers = [genericWriterOptions, ...writers];\n const genericOptions = (isObject(genericWriterOptions) ? genericWriterOptions : {});\n // if used in media writer the sub writers need to test if they support the source file\n genericOptions.testSrcSupport = true;\n // currently assigned writers\n writers = Array.isArray(writers) ? arrayFlatten(writers) : writers;\n // loop over writers and return first that is a match\n for (let i = 0; i < writers.length; i++) {\n const res = writers[i](src, imageState, genericOptions);\n if (Array.isArray(res))\n return res;\n }\n};\n\nvar calculateImageTransforms = (stageRect, rootRect, imageSize, cropRect, imageSelectionRect, imageSelectionRectScalar, imageSelectionRectTranslation, imageScale, imageRotation, imageFlipX, imageFlipY) => {\n if (!stageRect || !rootRect || !imageSize || !cropRect || !imageScale)\n return undefined;\n // scale presentation\n imageScale *= imageSelectionRectScalar;\n const viewRect = rectNormalizeOffset(rectClone$1(rootRect));\n const viewCenter = rectCenter(viewRect);\n const stageCenter = rectCenter(stageRect);\n const imageRect = rectCreateFromSize(imageSize);\n const imageCenter = rectCenter(imageRect);\n // get base crop rect so we can correctly apply transforms\n const cropRectBase = getBaseCropRect(imageSize, cropRect, imageRotation);\n const cropRectBaseCenter = rectCenter(cropRectBase);\n const imageTranslation = vectorSubtract(vectorClone(imageCenter), cropRectBaseCenter);\n // calculate stage center offset from view center\n const imageOffset = vectorSubtract(vectorClone(stageCenter), viewCenter);\n // correct for stage offset\n imageTranslation.x += imageOffset.x;\n imageTranslation.y += imageOffset.y;\n // set origin of translation (so rotates around center of selection)\n const imageOrigin = vectorInvert(vectorClone(imageTranslation));\n // correct for stage offset\n imageOrigin.x += imageOffset.x;\n imageOrigin.y += imageOffset.y;\n // correct for image selection offset relative to view\n const imageSelectionCenter = rectCenter(rectTranslate$1(rectTranslate$1(rectClone$1(imageSelectionRect), imageSelectionRectTranslation), stageRect));\n const imageSelectionOffset = vectorSubtract(imageSelectionCenter, stageCenter);\n vectorAdd(imageTranslation, imageSelectionOffset);\n return {\n origin: imageOrigin,\n translation: imageTranslation,\n rotation: {\n x: imageFlipY ? Math.PI : 0,\n y: imageFlipX ? Math.PI : 0,\n z: imageRotation,\n },\n scale: imageScale,\n };\n};\n\nlet result$6 = null;\nvar supportsWebGL2 = () => {\n if (result$6 === null) {\n if ('WebGL2RenderingContext' in window) {\n let canvas;\n try {\n canvas = h('canvas');\n result$6 = !!canvas.getContext('webgl2');\n }\n catch (err) {\n result$6 = false;\n }\n canvas && releaseCanvas(canvas);\n canvas = undefined;\n }\n else {\n result$6 = false;\n }\n }\n return result$6;\n};\n\nvar isPowerOf2 = (value) => (value & (value - 1)) === 0;\n\nvar stringReplace = (str, entries = {}, prefix = '', postfix = '') => {\n return Object.keys(entries)\n .filter((key) => !isObject(entries[key]))\n .reduce((prev, curr) => {\n return prev.replace(new RegExp(prefix + curr + postfix), entries[curr]);\n }, str);\n};\n\nvar SHADER_FRAG_HEAD = \"#version 300 es\\nprecision highp float;\\n\\nout vec4 fragColor;\"; // eslint-disable-line\n\nvar SHADER_FRAG_INIT = \"\\nfloat a=1.0;vec4 fillColor=uColor;vec4 textureColor=texture(uTexture,vTexCoord);textureColor*=(1.0-step(uRepeat.y,vTexCoord.y))*step(0.0,vTexCoord.y)*(1.0-step(uRepeat.x,vTexCoord.x))*step(0.0,vTexCoord.x);\"; // eslint-disable-line\n\nvar SHADER_FRAG_MASK = \"\\nuniform float uMaskFeather[8];uniform float uMaskBounds[4];uniform float uMaskOpacity;float mask(float x,float y,float bounds[4],float opacity){return 1.0-(1.0-(smoothstep(bounds[3],bounds[3]+1.0,x)*(1.0-smoothstep(bounds[1]-1.0,bounds[1],x))*(1.0-step(bounds[0],y))*step(bounds[2],y)))*(1.0-opacity);}\"; // eslint-disable-line\n\nvar SHADER_FRAG_MASK_APPLY = \"\\nfloat m=mask(gl_FragCoord.x,gl_FragCoord.y,uMaskBounds,uMaskOpacity);\"; // eslint-disable-line\n\nvar SHADER_FRAG_MASK_FEATHER_APPLY = \"\\nfloat leftFeatherOpacity=step(uMaskFeather[1],gl_FragCoord.x)*uMaskFeather[0]+((1.0-uMaskFeather[0])*smoothstep(uMaskFeather[1],uMaskFeather[3],gl_FragCoord.x));float rightFeatherOpacity=(1.0-step(uMaskFeather[7],gl_FragCoord.x))*uMaskFeather[4]+((1.0-uMaskFeather[4])*smoothstep(uMaskFeather[7],uMaskFeather[5],gl_FragCoord.x));a*=leftFeatherOpacity*rightFeatherOpacity;\"; // eslint-disable-line\n\nvar SHADER_FRAG_RECT_AA = \"\\nvec2 scaledPoint=vec2(vRectCoord.x*uSize.x,vRectCoord.y*uSize.y);a*=smoothstep(0.0,uEdgeFeather,uSize.x-scaledPoint.x);a*=smoothstep(0.0,uEdgeFeather,uSize.y-scaledPoint.y);a*=smoothstep(0.0,uEdgeFeather,scaledPoint.x);a*=smoothstep(0.0,uEdgeFeather,scaledPoint.y);\"; // eslint-disable-line\n\nvar SHADER_FRAG_CORNER_RADIUS = \"\\nvec2 s=(uSize-2.0)*.5;vec2 r=(vRectCoord*uSize)-1.0;vec2 p=r-s;float cornerRadius=uCornerRadius[0];bool left=r.x0.0){vec3 colorFlattened=textureColor.rgb/textureColor.a;if(colorFlattened.r>=.9999&&colorFlattened.g==0.0&&colorFlattened.b>=.9999){textureColor.rgb=uTextureColor.rgb*textureColor.a;}textureColor*=uTextureColor.a;}\"; // eslint-disable-line\n\nvar SHADER_VERT_HEAD = \"#version 300 es\\n\\nin vec4 aPosition;uniform mat4 uMatrix;\"; // eslint-disable-line\n\nvar SHADER_VERT_MULTIPLY_MATRUX = \"\\ngl_Position=uMatrix*vec4(aPosition.x,aPosition.y,0,1);\"; // eslint-disable-line\n\nvar SHADER_VERT_TEXTURE = \"\\nin vec2 aTexCoord;out vec2 vTexCoord;\"; // eslint-disable-line\n\nconst SHADER_VERT_SNIPPETS = {\n head: SHADER_VERT_HEAD,\n text: SHADER_VERT_TEXTURE,\n matrix: SHADER_VERT_MULTIPLY_MATRUX,\n};\nconst SHADER_FRAG_SNIPPETS = {\n head: SHADER_FRAG_HEAD,\n mask: SHADER_FRAG_MASK,\n init: SHADER_FRAG_INIT,\n colorize: SHADER_FRAG_TEXTURE_COLORIZE,\n maskapply: SHADER_FRAG_MASK_APPLY,\n maskfeatherapply: SHADER_FRAG_MASK_FEATHER_APPLY,\n rectaa: SHADER_FRAG_RECT_AA,\n cornerradius: SHADER_FRAG_CORNER_RADIUS,\n fragcolor: SHADER_FRAG_SHAPE_BLEND_COLOR,\n};\nconst transpileShader = (gl, src, type) => {\n src = stringReplace(src, type === gl.VERTEX_SHADER ? SHADER_VERT_SNIPPETS : SHADER_FRAG_SNIPPETS, '##').trim();\n // ready if supports webgl\n if (supportsWebGL2())\n return src;\n src = src.replace(/#version.+/gm, '').trim();\n src = src.replace(/^\\/\\/\\#/gm, '#');\n if (type === gl.VERTEX_SHADER) {\n src = src.replace(/in /gm, 'attribute ').replace(/out /g, 'varying ');\n }\n if (type === gl.FRAGMENT_SHADER) {\n src = src\n .replace(/in /gm, 'varying ')\n .replace(/out.*?;/gm, '')\n .replace(/texture\\(/g, 'texture2D(')\n .replace(/fragColor/g, 'gl_FragColor');\n }\n return `${src}`;\n};\nconst compileShader = (gl, src, type) => {\n const shader = gl.createShader(type);\n const transpiledSrc = transpileShader(gl, src, type);\n gl.shaderSource(shader, transpiledSrc);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n console.error(gl.getShaderInfoLog(shader));\n }\n return shader;\n};\nconst createShader = (gl, vertexShaderCode, fragmentShaderCode, attribs, uniforms) => {\n const vertexShader = compileShader(gl, vertexShaderCode, gl.VERTEX_SHADER);\n const fragmentShader = compileShader(gl, fragmentShaderCode, gl.FRAGMENT_SHADER);\n const program = gl.createProgram();\n gl.attachShader(program, vertexShader);\n gl.attachShader(program, fragmentShader);\n gl.linkProgram(program);\n const locations = {};\n attribs.forEach((name) => {\n locations[name] = gl.getAttribLocation(program, name);\n });\n uniforms.forEach((name) => {\n locations[name] = gl.getUniformLocation(program, name);\n });\n return {\n program,\n locations,\n destroy() {\n gl.detachShader(program, vertexShader);\n gl.detachShader(program, fragmentShader);\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n gl.deleteProgram(program);\n },\n };\n};\nconst canMipMap = (source) => {\n if (source['nodeName'] === 'VIDEO')\n return false;\n if (supportsWebGL2())\n return true;\n return isPowerOf2(source.width) && isPowerOf2(source.height);\n};\nconst applyTextureProperties = (gl, source, options) => {\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, canMipMap(source) ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, options.filterParam);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, options.wrapParam);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, options.wrapParam);\n if (canMipMap(source))\n gl.generateMipmap(gl.TEXTURE_2D);\n};\nconst updateTexture = (gl, texture, source, options) => {\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);\n applyTextureProperties(gl, source, options);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return texture;\n};\nconst applyOpacity = (color, opacity = 1) => color\n ? [color[0], color[1], color[2], isNumber(color[3]) ? opacity * color[3] : opacity]\n : [0, 0, 0, 0];\n\nconst mat4Create = () => {\n const mat = new Float32Array(16);\n mat[0] = 1;\n mat[5] = 1;\n mat[10] = 1;\n mat[15] = 1;\n return mat;\n};\nconst mat4Perspective = (mat, fovy, aspect, near, far) => {\n const f = 1.0 / Math.tan(fovy / 2);\n const nf = 1 / (near - far);\n mat[0] = f / aspect;\n mat[1] = 0;\n mat[2] = 0;\n mat[3] = 0;\n mat[4] = 0;\n mat[5] = f;\n mat[6] = 0;\n mat[7] = 0;\n mat[8] = 0;\n mat[9] = 0;\n mat[10] = (far + near) * nf;\n mat[11] = -1;\n mat[12] = 0;\n mat[13] = 0;\n mat[14] = 2 * far * near * nf;\n mat[15] = 0;\n};\nconst mat4Ortho = (mat, left, right, bottom, top, near, far) => {\n const lr = 1 / (left - right);\n const bt = 1 / (bottom - top);\n const nf = 1 / (near - far);\n mat[0] = -2 * lr;\n mat[1] = 0;\n mat[2] = 0;\n mat[3] = 0;\n mat[4] = 0;\n mat[5] = -2 * bt;\n mat[6] = 0;\n mat[7] = 0;\n mat[8] = 0;\n mat[9] = 0;\n mat[10] = 2 * nf;\n mat[11] = 0;\n mat[12] = (left + right) * lr;\n mat[13] = (top + bottom) * bt;\n mat[14] = (far + near) * nf;\n mat[15] = 1;\n};\nconst mat4Translate = (mat, x, y, z) => {\n mat[12] = mat[0] * x + mat[4] * y + mat[8] * z + mat[12];\n mat[13] = mat[1] * x + mat[5] * y + mat[9] * z + mat[13];\n mat[14] = mat[2] * x + mat[6] * y + mat[10] * z + mat[14];\n mat[15] = mat[3] * x + mat[7] * y + mat[11] * z + mat[15];\n};\nconst mat4Scale = (mat, s) => {\n mat[0] *= s;\n mat[1] *= s;\n mat[2] *= s;\n mat[3] *= s;\n mat[4] *= s;\n mat[5] *= s;\n mat[6] *= s;\n mat[7] *= s;\n mat[8] *= s;\n mat[9] *= s;\n mat[10] *= s;\n mat[11] *= s;\n};\nconst mat4ScaleX = (mat, s) => {\n mat[0] *= s;\n mat[1] *= s;\n mat[2] *= s;\n mat[3] *= s;\n};\nconst mat4ScaleY = (mat, s) => {\n mat[4] *= s;\n mat[5] *= s;\n mat[6] *= s;\n mat[7] *= s;\n};\nconst mat4RotateX = (mat, rad) => {\n const s = Math.sin(rad);\n const c = Math.cos(rad);\n const a10 = mat[4];\n const a11 = mat[5];\n const a12 = mat[6];\n const a13 = mat[7];\n const a20 = mat[8];\n const a21 = mat[9];\n const a22 = mat[10];\n const a23 = mat[11];\n mat[4] = a10 * c + a20 * s;\n mat[5] = a11 * c + a21 * s;\n mat[6] = a12 * c + a22 * s;\n mat[7] = a13 * c + a23 * s;\n mat[8] = a20 * c - a10 * s;\n mat[9] = a21 * c - a11 * s;\n mat[10] = a22 * c - a12 * s;\n mat[11] = a23 * c - a13 * s;\n};\nconst mat4RotateY = (mat, rad) => {\n const s = Math.sin(rad);\n const c = Math.cos(rad);\n const a00 = mat[0];\n const a01 = mat[1];\n const a02 = mat[2];\n const a03 = mat[3];\n const a20 = mat[8];\n const a21 = mat[9];\n const a22 = mat[10];\n const a23 = mat[11];\n mat[0] = a00 * c - a20 * s;\n mat[1] = a01 * c - a21 * s;\n mat[2] = a02 * c - a22 * s;\n mat[3] = a03 * c - a23 * s;\n mat[8] = a00 * s + a20 * c;\n mat[9] = a01 * s + a21 * c;\n mat[10] = a02 * s + a22 * c;\n mat[11] = a03 * s + a23 * c;\n};\nconst mat4RotateZ = (mat, rad) => {\n const s = Math.sin(rad);\n const c = Math.cos(rad);\n const a00 = mat[0];\n const a01 = mat[1];\n const a02 = mat[2];\n const a03 = mat[3];\n const a10 = mat[4];\n const a11 = mat[5];\n const a12 = mat[6];\n const a13 = mat[7];\n mat[0] = a00 * c + a10 * s;\n mat[1] = a01 * c + a11 * s;\n mat[2] = a02 * c + a12 * s;\n mat[3] = a03 * c + a13 * s;\n mat[4] = a10 * c - a00 * s;\n mat[5] = a11 * c - a01 * s;\n mat[6] = a12 * c - a02 * s;\n mat[7] = a13 * c - a03 * s;\n};\n\nvar degToRad = (degrees) => degrees * Math.PI / 180;\n\nvar getWebGLContext = (canvas, attrs) => {\n if (supportsWebGL2())\n return canvas.getContext('webgl2', attrs);\n return (canvas.getContext('webgl', attrs) ||\n canvas.getContext('experimental-webgl', attrs));\n};\n\nvar imageFragmentShader = \"\\n##head\\nin vec2 vTexCoord;uniform sampler2D uTexture;uniform sampler2D uTextureOverlay;uniform sampler2D uTextureBlend;uniform vec2 uTextureSize;uniform float uOpacity;uniform int uAntialias;uniform vec4 uOverlayColor;uniform mat4 uColorMatrix;uniform vec4 uColorOffset;uniform float uClarityKernel[9];uniform float uClarityKernelWeight;uniform float uColorGamma;uniform float uColorVignette;uniform float uMaskClip;uniform float uMaskOpacity;uniform float uMaskBounds[4];uniform float uMaskCornerRadius[4];uniform float uMaskFeather[8];vec4 applyGamma(vec4 c,float g){c.r=pow(c.r,g);c.g=pow(c.g,g);c.b=pow(c.b,g);return c;}vec4 applyColorMatrix(vec4 c,mat4 m,vec4 o){vec4 res=(c*m)+(o*c.a);res=clamp(res,0.0,1.0);return res;}vec4 applyConvolutionMatrix(vec4 c,float k0,float k1,float k2,float k3,float k4,float k5,float k6,float k7,float k8,float w){vec2 pixel=vec2(1)/uTextureSize;vec4 colorSum=texture(uTexture,vTexCoord-pixel)*k0+texture(uTexture,vTexCoord+pixel*vec2(0.0,-1.0))*k1+texture(uTexture,vTexCoord+pixel*vec2(1.0,-1.0))*k2+texture(uTexture,vTexCoord+pixel*vec2(-1.0,0.0))*k3+texture(uTexture,vTexCoord)*k4+texture(uTexture,vTexCoord+pixel*vec2(1.0,0.0))*k5+texture(uTexture,vTexCoord+pixel*vec2(-1.0,1.0))*k6+texture(uTexture,vTexCoord+pixel*vec2(0.0,1.0))*k7+texture(uTexture,vTexCoord+pixel)*k8;vec4 color=vec4(clamp((colorSum/w),0.0,1.0).rgb,c.a);return color;}vec4 blendPremultipliedAlpha(vec4 back,vec4 front){return front+(back*(1.0-front.a));}vec4 applyVignette(vec4 c,vec2 pos,vec2 center,float v){float d=distance(pos,center)/length(center);float f=1.0-(d*abs(v));if(v>0.0){c.rgb*=f;}else if(v<0.0){c.rgb+=(1.0-f)*(1.0-c.rgb);}return c;}void main(){float x=gl_FragCoord.x;float y=gl_FragCoord.y;float a=1.0;float maskTop=uMaskBounds[0];float maskRight=uMaskBounds[1];float maskBottom=uMaskBounds[2];float maskLeft=uMaskBounds[3];float leftFeatherOpacity=step(uMaskFeather[1],x)*uMaskFeather[0]+((1.0-uMaskFeather[0])*smoothstep(uMaskFeather[1],uMaskFeather[3],x));float rightFeatherOpacity=(1.0-step(uMaskFeather[7],x))*uMaskFeather[4]+((1.0-uMaskFeather[4])*smoothstep(uMaskFeather[7],uMaskFeather[5],x));a*=leftFeatherOpacity*rightFeatherOpacity;float overlayColorAlpha=(smoothstep(maskLeft,maskLeft+1.0,x)*(1.0-smoothstep(maskRight-1.0,maskRight,x))*(1.0-step(maskTop,y))*step(maskBottom,y));if(uOverlayColor.a==0.0){a*=overlayColorAlpha;}vec2 offset=vec2(maskLeft,maskBottom);vec2 size=vec2(maskRight-maskLeft,maskTop-maskBottom)*.5;vec2 center=offset.xy+size.xy;int pixelX=int(step(center.x,x));int pixelY=int(step(y,center.y));float cornerRadius=0.0;if(pixelX==0&&pixelY==0)cornerRadius=uMaskCornerRadius[0];if(pixelX==1&&pixelY==0)cornerRadius=uMaskCornerRadius[1];if(pixelX==0&&pixelY==1)cornerRadius=uMaskCornerRadius[2];if(pixelX==1&&pixelY==1)cornerRadius=uMaskCornerRadius[3];float cornerOffset=sign(cornerRadius)*length(max(abs(gl_FragCoord.xy-size-offset)-size+cornerRadius,0.0))-cornerRadius;float cornerOpacity=1.0-smoothstep(0.0,1.0,cornerOffset);a*=cornerOpacity;if(uAntialias==1){vec2 scaledPoint=vec2(vTexCoord.x*uTextureSize.x,vTexCoord.y*uTextureSize.y);a*=smoothstep(0.0,1.0,uTextureSize.x-scaledPoint.x);a*=smoothstep(0.0,1.0,uTextureSize.y-scaledPoint.y);a*=smoothstep(0.0,1.0,scaledPoint.x);a*=smoothstep(0.0,1.0,scaledPoint.y);}vec4 color=texture(uTexture,vTexCoord);if(uClarityKernelWeight!=-1.0){color=applyConvolutionMatrix(color,uClarityKernel[0],uClarityKernel[1],uClarityKernel[2],uClarityKernel[3],uClarityKernel[4],uClarityKernel[5],uClarityKernel[6],uClarityKernel[7],uClarityKernel[8],uClarityKernelWeight);}color=blendPremultipliedAlpha(color,texture(uTextureBlend,vTexCoord));color=applyGamma(color,uColorGamma);color=applyColorMatrix(color,uColorMatrix,uColorOffset);color*=a;if(uColorVignette!=0.0){vec2 pos=gl_FragCoord.xy-offset;color=applyVignette(color,pos,center-offset,uColorVignette);}color=blendPremultipliedAlpha(color,texture(uTextureOverlay,vTexCoord));if(overlayColorAlpha<=0.0){color*=1.0-uOverlayColor.a;}color*=uOpacity;fragColor=color;}\"; // eslint-disable-line\n\nvar imageVertexShader = \"\\n##head\\n##text\\nvoid main(){vTexCoord=aTexCoord;gl_Position=uMatrix*aPosition;}\"; // eslint-disable-line\n\nvar pathVertexShader = \"#version 300 es\\n\\nin vec4 aPosition;in vec2 aNormal;in float aMiter;out vec2 vNormal;out float vMiter;out float vWidth;uniform float uWidth;uniform float uSharpness;uniform mat4 uMatrix;void main(){vMiter=aMiter;vNormal=aNormal;vWidth=(uWidth*.5)+uSharpness;gl_Position=uMatrix*vec4(aPosition.x+(aNormal.x*vWidth*aMiter),aPosition.y+(aNormal.y*vWidth*aMiter),0,1);}\"; // eslint-disable-line\n\nvar pathFragmentShader = \"\\n##head\\n##mask\\nin vec2 vNormal;in float vMiter;in float vWidth;uniform float uWidth;uniform vec4 uColor;uniform vec4 uCanvasColor;void main(){vec4 fillColor=uColor;float m=mask(gl_FragCoord.x,gl_FragCoord.y,uMaskBounds,uMaskOpacity);if(m<=0.0)discard;fillColor.a*=clamp(smoothstep(vWidth-.5,vWidth-1.0,abs(vMiter)*vWidth),0.0,1.0);fillColor.rgb*=fillColor.a;fillColor.rgb*=m;fillColor.rgb+=(1.0-m)*(uCanvasColor.rgb*fillColor.a);fragColor=fillColor;}\"; // eslint-disable-line\n\nvar rectVertexShader = \"\\n##head\\n##text\\nin vec2 aRectCoord;out vec2 vRectCoord;void main(){vTexCoord=aTexCoord;vRectCoord=aRectCoord;\\n##matrix\\n}\"; // eslint-disable-line\n\nvar rectFragmentShader = \"\\n##head\\n##mask\\nin vec2 vTexCoord;in vec2 vRectCoord;uniform sampler2D uTexture;uniform vec4 uTextureColor;uniform float uTextureOpacity;uniform vec2 uRepeat;uniform vec4 uColor;uniform float uCornerRadius[4];uniform vec2 uSize;uniform vec2 uPosition;uniform vec4 uCanvasColor;uniform int uInverted;uniform float uEdgeFeather;void main(){\\n##init\\n##colorize\\n##rectaa\\n##cornerradius\\n##maskfeatherapply\\nif(uInverted==1)a=1.0-a;\\n##maskapply\\n##fragcolor\\n}\"; // eslint-disable-line\n\nvar ellipseVertexShader = \"\\n##head\\n##text\\nout vec2 vTexCoordDouble;void main(){vTexCoordDouble=vec2(aTexCoord.x*2.0-1.0,aTexCoord.y*2.0-1.0);vTexCoord=aTexCoord;\\n##matrix\\n}\"; // eslint-disable-line\n\nvar ellipseFragmentShader = \"\\n##head\\n##mask\\nin vec2 vTexCoord;in vec2 vTexCoordDouble;uniform sampler2D uTexture;uniform float uTextureOpacity;uniform vec2 uTextureAdjust;uniform vec2 uRepeat;uniform vec2 uRadius;uniform vec4 uColor;uniform int uInverted;uniform vec4 uCanvasColor;void main(){\\n##init\\nfloat ar=uRadius.x/uRadius.y;vec2 rAA=vec2(uRadius.x-1.0,uRadius.y-(1.0/ar));vec2 scaledPointSq=vec2((vTexCoordDouble.x*uTextureAdjust.x*uRadius.x)*(vTexCoordDouble.x*uTextureAdjust.x*uRadius.x),(vTexCoordDouble.y*uTextureAdjust.y*uRadius.y)*(vTexCoordDouble.y*uTextureAdjust.y*uRadius.y));float p=(scaledPointSq.x/(uRadius.x*uRadius.x))+(scaledPointSq.y/(uRadius.y*uRadius.y));float pAA=(scaledPointSq.x/(rAA.x*rAA.x))+(scaledPointSq.y/(rAA.y*rAA.y));a=smoothstep(1.0,p/pAA,p);if(uInverted==1)a=1.0-a;\\n##maskapply\\n##fragcolor\\n}\"; // eslint-disable-line\n\nvar triangleVertexShader = \"\\n##head\\nvoid main(){\\n##matrix\\n}\"; // eslint-disable-line\n\nvar triangleFragmentShader = \"\\n##head\\n##mask\\nuniform vec4 uColor;uniform vec4 uCanvasColor;void main(){vec4 fillColor=uColor;\\n##maskapply\\nfillColor.rgb*=fillColor.a;fillColor.rgb*=m;fillColor.rgb+=(1.0-m)*(uCanvasColor.rgb*fillColor.a);fragColor=fillColor;}\"; // eslint-disable-line\n\nconst getIndex = (arr, index) => {\n const l = arr.length;\n if (index >= l)\n return arr[index % l];\n if (index < 0)\n return arr[(index % l) + l];\n return arr[index];\n};\nconst getPolygonArea = (vertices) => {\n let area = 0, i, va, vb, w, h;\n const l = vertices.length;\n for (i = 0; i < l; i++) {\n va = vertices[i];\n vb = vertices[(i + 1) % l];\n w = vb.x - va.x;\n h = (vb.y + va.y) / 2;\n area += w * h;\n }\n return area;\n};\nconst isPointInTriangle = (p, a, b, c) => {\n const ab = vectorCreate(b.x - a.x, b.y - a.y);\n const bc = vectorCreate(c.x - b.x, c.y - b.y);\n const ca = vectorCreate(a.x - c.x, a.y - c.y);\n const ap = vectorCreate(p.x - a.x, p.y - a.y);\n const bp = vectorCreate(p.x - b.x, p.y - b.y);\n const cp = vectorCreate(p.x - c.x, p.y - c.y);\n const c1 = vectorCross(ab, ap);\n const c2 = vectorCross(bc, bp);\n const c3 = vectorCross(ca, cp);\n return c1 <= 0 && c2 <= 0 && c3 <= 0;\n};\nconst isIntersectingSelf = (vertices) => {\n // test if lines intersect\n const l = vertices.length;\n for (let i = 0; i < l; i++) {\n const line = lineCreate(vertices[i], getIndex(vertices, i + 1));\n const intersections = linePointsIntersection(line, vertices, {\n ignoreIdenticalLines: true,\n breakOnIntersection: true,\n });\n // is intersecting\n if (intersections)\n return true;\n }\n return false;\n};\nconst triangulate = (vertices) => {\n // not enough vertices\n if (vertices.length < 3)\n return [];\n // test if is simple polygon\n if (isIntersectingSelf(vertices))\n return [];\n // if vertices aren't clockwise, reverse\n if (getPolygonArea(vertices) < 0)\n vertices.reverse();\n // TODO: remove 180 degree corners, vertices should either be convex (< 180) or reflex (> 180), this happens when three vertices are in the same line\n // triangulate\n const totalVertices = vertices.length;\n const indexList = [...Array(totalVertices).keys()];\n const triangles = [];\n // faster to declare vars out of loop\n let i, j, a, b, c, va, vb, vc, vaToVb, vaToVc, isEar, p;\n let safe = 1024;\n while (indexList.length > 3) {\n // safeguard against infinite loops\n if (safe <= 0)\n return [];\n // lower safeguard\n safe--;\n // start calculating triangles\n for (i = 0; i < indexList.length; i++) {\n a = indexList[i];\n b = getIndex(indexList, i - 1);\n c = getIndex(indexList, i + 1);\n va = vertices[a];\n vb = vertices[b];\n vc = vertices[c];\n vaToVb = vectorCreate(vb.x - va.x, vb.y - va.y);\n vaToVc = vectorCreate(vc.x - va.x, vc.y - va.y);\n // is not an ear if is reflex corner (angle > 180)\n if (vectorCross(vaToVb, vaToVc) < 0)\n continue;\n isEar = true;\n for (j = 0; j < totalVertices; j++) {\n // skip as is me\n if (j === a || j === b || j === c)\n continue;\n // is not an ear if another vertex falls in this triangle\n p = vertices[j];\n if (isPointInTriangle(p, vb, va, vc)) {\n isEar = false;\n break;\n }\n }\n // skip\n if (!isEar)\n continue;\n // found ear\n triangles.push([vb, va, vc]);\n // no longer interested in this index\n indexList.splice(i, 1);\n break;\n }\n }\n // finale triangle\n triangles.push([vertices[indexList[0]], vertices[indexList[1]], vertices[indexList[2]]]);\n return triangles;\n};\nconst createPathSegment = (vertices, index, a, b, c) => {\n const ab = vectorNormalize(vectorCreate(b.x - a.x, b.y - a.y));\n const bc = vectorNormalize(vectorCreate(c.x - b.x, c.y - b.y));\n const tangent = vectorNormalize(vectorCreate(ab.x + bc.x, ab.y + bc.y));\n const miter = vectorCreate(-tangent.y, tangent.x);\n const normal = vectorCreate(-ab.y, ab.x);\n // limit miter length (TEMP fix to prevent spikes, should eventually add caps)\n const miterLength = Math.min(1 / vectorDot(miter, normal), 5);\n vertices[index] = b.x;\n vertices[index + 1] = b.y;\n vertices[index + 2] = miter.x * miterLength;\n vertices[index + 3] = miter.y * miterLength;\n vertices[index + 4] = -1;\n vertices[index + 5] = b.x;\n vertices[index + 6] = b.y;\n vertices[index + 7] = miter.x * miterLength;\n vertices[index + 8] = miter.y * miterLength;\n vertices[index + 9] = 1;\n};\nconst createPathVertices = (points, close) => {\n let a, b, c, i = 0;\n const l = points.length;\n const stride = 10;\n const vertices = new Float32Array((close ? l + 1 : l) * stride);\n const first = points[0];\n const last = points[l - 1];\n for (i = 0; i < l; i++) {\n a = points[i - 1];\n b = points[i];\n c = points[i + 1];\n // if previous point not available use inverse vector to next point\n if (!a)\n a = close ? last : vectorCreate(b.x + (b.x - c.x), b.y + (b.y - c.y));\n // if next point not available use inverse vector from previous point\n if (!c)\n c = close ? first : vectorCreate(b.x + (b.x - a.x), b.y + (b.y - a.y));\n createPathSegment(vertices, i * stride, a, b, c);\n }\n if (close)\n createPathSegment(vertices, l * stride, last, first, points[1]);\n return vertices;\n};\nconst rectPointsToVertices = (points) => {\n // [tl, tr, br, bl]\n // B D\n // | \\ |\n // A C\n const vertices = new Float32Array(8);\n vertices[0] = points[3].x;\n vertices[1] = points[3].y;\n vertices[2] = points[0].x;\n vertices[3] = points[0].y;\n vertices[4] = points[2].x;\n vertices[5] = points[2].y;\n vertices[6] = points[1].x;\n vertices[7] = points[1].y;\n return vertices;\n};\nconst createRectPoints = (rect, rotation = 0, flipX, flipY) => {\n const corners = rectGetCorners(rect);\n const cx = rect.x + rect.width * 0.5;\n const cy = rect.y + rect.height * 0.5;\n if (flipX || flipY)\n vectorsFlip(corners, flipX, flipY, cx, cy);\n if (rotation !== 0)\n vectorsRotate(corners, rotation, cx, cy);\n return corners;\n};\nconst createEllipseOutline = (x, y, width, height, rotation, flipX, flipY) => {\n const rx = Math.abs(width) * 0.5;\n const ry = Math.abs(height) * 0.5;\n const size = Math.abs(width) + Math.abs(height);\n const precision = Math.max(20, Math.round(size / 6));\n return ellipseToPolygon(vectorCreate(x + rx, y + ry), rx, ry, rotation, flipX, flipY, precision);\n};\nconst createRectOutline = (x, y, width, height, rotation, cornerRadius, flipX, flipY) => {\n const points = [];\n if (cornerRadius.every((v) => v === 0)) {\n points.push(vectorCreate(x, y), // top left corner\n vectorCreate(x + width, y), // top right corner\n vectorCreate(x + width, y + height), // bottom right corner\n vectorCreate(x, y + height) // bottom left corner\n );\n }\n else {\n const [tl, tr, bl, br] = cornerRadius;\n const l = x;\n const r = x + width;\n const t = y;\n const b = y + height;\n // start at end of top left corner\n points.push(vectorCreate(l + tl, t));\n pushRectCornerPoints(points, r - tr, t + tr, tr, -1);\n // move to bottom right corner\n points.push(vectorCreate(r, t + tr));\n pushRectCornerPoints(points, r - br, b - br, br, 0);\n // move to bottom left corner\n points.push(vectorCreate(r - br, b));\n pushRectCornerPoints(points, l + bl, b - bl, bl, 1);\n // move to top left corner\n points.push(vectorCreate(l, b - bl));\n pushRectCornerPoints(points, l + tl, t + tl, tl, 2);\n }\n if (flipX || flipY)\n vectorsFlip(points, flipX, flipY, x + width * 0.5, y + height * 0.5);\n if (rotation)\n vectorsRotate(points, rotation, x + width * 0.5, y + height * 0.5);\n return points;\n};\nconst pushRectCornerPoints = (points, x, y, radius, offset) => {\n const precision = Math.min(20, Math.max(4, Math.round(radius / 2)));\n let p = 0;\n let s = 0;\n let rx = 0;\n let ry = 0;\n let i = 0;\n for (; i < precision; i++) {\n p = i / precision;\n s = offset * HALF_PI + p * HALF_PI;\n rx = radius * Math.cos(s);\n ry = radius * Math.sin(s);\n points.push(vectorCreate(x + rx, y + ry));\n }\n};\n\nlet limit = null;\nvar getWebGLTextureSizeLimit = () => {\n if (limit !== null)\n return limit;\n let canvas = h('canvas');\n const gl = getWebGLContext(canvas);\n limit = gl ? gl.getParameter(gl.MAX_TEXTURE_SIZE) : undefined;\n releaseCanvas(canvas);\n canvas = undefined;\n return limit;\n};\n\nvar isChrome = () => isBrowser() && !!window['chrome'];\n\n// prettier-ignore\n// B D\n// | \\ |\n// A C\nconst RECT_UV = new Float32Array([\n 0.0, 1.0,\n 0.0, 0.0,\n 1.0, 1.0,\n 1.0, 0.0, // D\n]);\n// prettier-ignore\n// BROWSER BUG FIX: Here to fix Firefox video texture rotation issue\nconst RECT_UV_ROTATED = new Float32Array([\n 1.0, 0.0,\n 0.0, 0.0,\n 1.0, 1.0,\n 0.0, 1.0, // D\n]);\nconst SHOULD_APPLY_ROTATED_UV_TO_VIDEO_TEXTURES = isBrowser() && isFirefox();\nconst CLARITY_IDENTITY = [0, 0, 0, 0, 1, 0, 0, 0, 0];\nconst COLOR_MATRIX_IDENTITY$1 = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0];\nconst TEXTURE_TRANSPARENT_INDEX = 0;\nconst TEXTURE_PREVIEW_BLEND_INDEX = 1;\nconst TEXTURE_PREVIEW_OVERLAY_INDEX = 2;\nconst TEXTURE_PREVIEW_INDEX = 3;\nconst TEXTURE_SHAPE_INDEX = 4;\nconst COLOR_TRANSPARENT = [0, 0, 0, 0];\nconst NO_CORNERS = [0, 0, 0, 0];\nconst calculateBackgroundUVMap = (width, height, backgroundSize, backgroundPosition, viewPixelDensity) => {\n if (!backgroundSize || !backgroundPosition)\n return RECT_UV;\n let x = backgroundPosition.x / backgroundSize.width;\n let y = backgroundPosition.y / backgroundSize.height;\n let w = width / backgroundSize.width / viewPixelDensity;\n let h = height / backgroundSize.height / viewPixelDensity;\n w -= x;\n h -= y;\n // prettier-ignore\n // B D\n // | \\ |\n // A C\n // bottom left\n const ax = -x;\n const ay = h;\n // top left\n const bx = -x;\n const by = -y;\n // bottom right\n const cx = w;\n const cy = h;\n // top right\n const dx = w;\n const dy = -y;\n return new Float32Array([\n ax,\n ay,\n bx,\n by,\n cx,\n cy,\n dx,\n dy, // D\n ]);\n};\nconst limitCornerRadius = (r, size) => {\n return Math.floor(clamp(r, 0, Math.min((size.width - 1) * 0.5, (size.height - 1) * 0.5)));\n};\nvar createWebGLCanvas = (canvas, options = {}) => {\n // defaults\n const { alpha = false } = options;\n // go\n const viewSize = { width: 0, height: 0 };\n const viewSizeVisual = { width: 0, height: 0 };\n const textureSizeLimit = getWebGLTextureSizeLimit() || 1024;\n let viewAspectRatio;\n let viewPixelDensity;\n let pathSharpness;\n const overlayMatrixCanvas = mat4Create();\n const imageFrameBuffer = mat4Create();\n let overlayMatrix;\n let maskTop;\n let maskRight;\n let maskBottom;\n let maskLeft;\n let maskOpacity;\n let maskBounds;\n let IMAGE_MASK_FEATHER; // updated when viewport is resized\n let RECT_MASK_FEATHER;\n let CANVAS_COLOR_R = 0;\n let CANVAS_COLOR_G = 0;\n let CANVAS_COLOR_B = 0;\n let CANVAS_COLOR_A = 1;\n const indexTextureMap = new Map([]);\n // resize view\n const resize = (width, height, pixelDensity) => {\n // density (min is 1 to prevent draw issues when zooming out webview)\n viewPixelDensity = Math.max(1, pixelDensity);\n // set path sharpness factor so paths look a little nicer on low res displays\n pathSharpness = viewPixelDensity === 1 ? 0.75 : 1;\n // visual size\n viewSizeVisual.width = width;\n viewSizeVisual.height = height;\n // size\n viewSize.width = width * viewPixelDensity;\n viewSize.height = height * viewPixelDensity;\n // calculate the aspect ratio, we use this to determine quad size\n viewAspectRatio = getAspectRatio(viewSize.width, viewSize.height);\n // sync dimensions with image data\n canvas.width = viewSize.width;\n canvas.height = viewSize.height;\n // update canvas markup matrix\n mat4Ortho(overlayMatrixCanvas, 0, viewSize.width, viewSize.height, 0, -1, 1);\n IMAGE_MASK_FEATHER = [1, 0, 1, 0, 1, viewSizeVisual.width, 1, viewSizeVisual.width];\n };\n const enablePreviewStencil = () => {\n gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);\n gl.stencilFunc(gl.ALWAYS, 1, 0xff);\n gl.stencilMask(0xff);\n };\n const applyPreviewStencil = () => {\n gl.stencilFunc(gl.EQUAL, 1, 0xff);\n gl.stencilMask(0x00);\n };\n const disablePreviewStencil = () => {\n gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);\n gl.stencilFunc(gl.ALWAYS, 1, 0xff);\n gl.stencilMask(0xff);\n };\n // fov is fixed\n const FOV = degToRad(30);\n const FOV_TAN_HALF = Math.tan(FOV / 2);\n // get gl drawing context\n const gl = getWebGLContext(canvas, {\n alpha,\n antialias: false,\n premultipliedAlpha: true,\n stencil: true,\n });\n // no drawing context received, exit\n if (!gl)\n return;\n // enable derivatives\n gl.getExtension('OES_standard_derivatives');\n // toggle gl settings\n gl.disable(gl.DEPTH_TEST);\n // need stencil to draw overlay on preview only\n gl.enable(gl.STENCIL_TEST);\n // set blend mode, we need it for alpha blending\n gl.enable(gl.BLEND);\n /*\n https://webglfundamentals.org/webgl/lessons/webgl-and-alpha.html\n most if not all Canvas 2D implementations work with pre-multiplied alpha.\n That means when you transfer them to WebGL and UNPACK_PREMULTIPLY_ALPHA_WEBGL\n is false WebGL will convert them back to un-premultipiled.\n With pre-multiplied alpha on, [1, .5, .5, 0] does not exist, it's always [0, 0, 0, 0]\n */\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, !alpha && isFirefox() ? false : true);\n // something to look into:\n // gl.UNPACK_COLORSPACE_CONVERSION_WEBGL\n // stencil test always passes\n disablePreviewStencil();\n // let's create textures\n const transparentTexture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, transparentTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, // width\n 1, // height\n 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(COLOR_TRANSPARENT) // transparent background\n );\n indexTextureMap.set(TEXTURE_TRANSPARENT_INDEX, transparentTexture);\n // create image markup texture and framebuffer\n const imageOverlayTexture = gl.createTexture();\n indexTextureMap.set(TEXTURE_PREVIEW_OVERLAY_INDEX, imageOverlayTexture);\n const overlayFramebuffer = gl.createFramebuffer();\n // create image blend texture and framebuffer\n const imageBlendTexture = gl.createTexture();\n indexTextureMap.set(TEXTURE_PREVIEW_BLEND_INDEX, imageBlendTexture);\n const blendFramebuffer = gl.createFramebuffer();\n // #region image\n // create default pixel drawing program, supports what we need\n const imageShader = createShader(gl, imageVertexShader, imageFragmentShader, ['aPosition', 'aTexCoord'], [\n 'uMatrix',\n 'uTexture',\n 'uTextureBlend',\n 'uTextureOverlay',\n 'uTextureSize',\n 'uColorGamma',\n 'uColorVignette',\n 'uColorOffset',\n 'uColorMatrix',\n 'uClarityKernel',\n 'uClarityKernelWeight',\n 'uOpacity',\n 'uMaskOpacity',\n 'uMaskBounds',\n 'uMaskCornerRadius',\n 'uMaskFeather',\n 'uOverlayColor',\n 'uAntialias',\n ]);\n // create image buffers\n const imagePositionsBuffer = gl.createBuffer();\n const texturePositionsBuffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, texturePositionsBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, RECT_UV, gl.STATIC_DRAW);\n // BROWSER BUG FIX: here to fix firefox video texture rotation issue\n const texturePositionsRotatedBuffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, texturePositionsRotatedBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, RECT_UV_ROTATED, gl.STATIC_DRAW);\n const drawImage = (texture, textureSize, originX, originY, translateX, translateY, rotateX, rotateY, rotateZ, scale, colorMatrix = COLOR_MATRIX_IDENTITY$1, opacity = 1, clarity, gamma = 1, vignette = 0, maskFeather = IMAGE_MASK_FEATHER, maskCornerRadius = NO_CORNERS, imageOverlayColor = COLOR_TRANSPARENT, enableOverlay = false, enableBlend = false, enableAntialiasing = true) => {\n // update image texture\n const imageWidth = textureSize.width * viewPixelDensity;\n const imageHeight = textureSize.height * viewPixelDensity;\n const l = imageWidth * -0.5;\n const t = imageHeight * 0.5;\n const r = imageWidth * 0.5;\n const b = imageHeight * -0.5;\n // prettier-ignore\n // B D\n // | \\ |\n // A C\n const imagePositions = new Float32Array([\n l, b, 0,\n l, t, 0,\n r, b, 0,\n r, t, 0, // D\n ]);\n gl.bindBuffer(gl.ARRAY_BUFFER, imagePositionsBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, imagePositions, gl.STATIC_DRAW);\n // move image backwards so it's presented in actual pixel size\n const viewZ = // 1. we calculate the z offset required to have the\n \n // image height match the view height\n /* /|\n / |\n / | height / 2\n / |\n f / 2 /__z_|\n \\ |\n \\ |\n \\ |\n \\ |\n \\|\n */\n (textureSize.height / 2 / FOV_TAN_HALF) *\n // 2. we want to render the image at the actual height, viewsize / height gets us results in a 1:1 presentation\n (viewSize.height / textureSize.height) *\n // 3. z has to be negative, therefor multiply by -1\n -1;\n // convert to pixel density\n translateX *= viewPixelDensity;\n translateY *= viewPixelDensity;\n originX *= viewPixelDensity;\n originY *= viewPixelDensity;\n // get shader params\n const { program, locations } = imageShader;\n // apply\n const matrix = mat4Create();\n mat4Perspective(matrix, FOV, viewAspectRatio, 1, -viewZ * 2);\n // move image\n mat4Translate(matrix, translateX, -translateY, viewZ);\n // set rotation origin in view\n mat4Translate(matrix, originX, -originY, 0);\n // rotate image\n mat4RotateZ(matrix, -rotateZ);\n // resize\n mat4Scale(matrix, scale);\n // reset rotation origin\n mat4Translate(matrix, -originX, originY, 0);\n // flip\n mat4RotateY(matrix, rotateY);\n mat4RotateX(matrix, rotateX);\n //\n // tell context to draw preview\n //\n gl.useProgram(program);\n gl.enableVertexAttribArray(locations.aPosition);\n gl.enableVertexAttribArray(locations.aTexCoord);\n // set up texture\n gl.uniform1i(locations.uTexture, TEXTURE_PREVIEW_INDEX);\n gl.uniform2f(locations.uTextureSize, textureSize.width, textureSize.height);\n gl.activeTexture(gl.TEXTURE0 + TEXTURE_PREVIEW_INDEX);\n gl.bindTexture(gl.TEXTURE_2D, texture);\n // set up blend texture\n const blendTextureIndex = enableBlend\n ? TEXTURE_PREVIEW_BLEND_INDEX\n : TEXTURE_TRANSPARENT_INDEX;\n const blendTexture = indexTextureMap.get(blendTextureIndex);\n gl.uniform1i(locations.uTextureBlend, blendTextureIndex);\n gl.activeTexture(gl.TEXTURE0 + blendTextureIndex);\n gl.bindTexture(gl.TEXTURE_2D, blendTexture);\n // set up markup texture\n const overlayTextureIndex = enableOverlay\n ? TEXTURE_PREVIEW_OVERLAY_INDEX\n : TEXTURE_TRANSPARENT_INDEX;\n const overlayTexture = indexTextureMap.get(overlayTextureIndex);\n gl.uniform1i(locations.uTextureOverlay, overlayTextureIndex);\n gl.activeTexture(gl.TEXTURE0 + overlayTextureIndex);\n gl.bindTexture(gl.TEXTURE_2D, overlayTexture);\n // set up buffers\n gl.bindBuffer(gl.ARRAY_BUFFER, imagePositionsBuffer);\n gl.vertexAttribPointer(locations.aPosition, 3, gl.FLOAT, false, 0, 0);\n // BROWSER BUG FIX: Here to fix Firefox bug with rotated video textures\n const rotateUV = SHOULD_APPLY_ROTATED_UV_TO_VIDEO_TEXTURES && textureIsRotatedVideo(texture);\n gl.bindBuffer(gl.ARRAY_BUFFER, rotateUV ? texturePositionsRotatedBuffer : texturePositionsBuffer);\n gl.vertexAttribPointer(locations.aTexCoord, 2, gl.FLOAT, false, 0, 0);\n // update matrix\n gl.uniformMatrix4fv(locations.uMatrix, false, matrix);\n // overlay color\n gl.uniform4fv(locations.uOverlayColor, imageOverlayColor);\n // convolution\n let clarityWeight;\n if (!clarity || arrayEqual(clarity, CLARITY_IDENTITY)) {\n clarity = CLARITY_IDENTITY;\n clarityWeight = -1;\n }\n else {\n clarityWeight = clarity.reduce((prev, curr) => prev + curr, 0);\n clarityWeight = clarityWeight <= 0 ? 1 : clarityWeight;\n }\n gl.uniform1fv(locations.uClarityKernel, clarity);\n gl.uniform1f(locations.uClarityKernelWeight, clarityWeight);\n gl.uniform1f(locations.uColorGamma, 1.0 / gamma);\n gl.uniform1f(locations.uColorVignette, vignette);\n gl.uniform1i(locations.uAntialias, enableAntialiasing ? 1 : 0);\n // set color matrix values\n gl.uniform4f(locations.uColorOffset, colorMatrix[4], colorMatrix[9], colorMatrix[14], colorMatrix[19]);\n gl.uniformMatrix4fv(locations.uColorMatrix, false, [\n colorMatrix[0],\n colorMatrix[1],\n colorMatrix[2],\n colorMatrix[3],\n colorMatrix[5],\n colorMatrix[6],\n colorMatrix[7],\n colorMatrix[8],\n colorMatrix[10],\n colorMatrix[11],\n colorMatrix[12],\n colorMatrix[13],\n colorMatrix[15],\n colorMatrix[16],\n colorMatrix[17],\n colorMatrix[18],\n ]);\n // opacity level\n gl.uniform1f(locations.uOpacity, opacity);\n // mask\n gl.uniform1f(locations.uMaskOpacity, maskOpacity);\n gl.uniform1fv(locations.uMaskBounds, maskBounds);\n gl.uniform1fv(locations.uMaskCornerRadius, maskCornerRadius.map((v) => v * viewPixelDensity));\n gl.uniform1fv(locations.uMaskFeather, maskFeather.map((v, i) => (i % 2 === 0 ? v : v * viewPixelDensity)));\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n gl.disableVertexAttribArray(locations.aPosition);\n gl.disableVertexAttribArray(locations.aTexCoord);\n };\n //#endregion\n // #region path\n const pathShader = createShader(gl, pathVertexShader, pathFragmentShader, ['aPosition', 'aNormal', 'aMiter'], ['uColor', 'uCanvasColor', 'uMatrix', 'uWidth', 'uSharpness', 'uMaskBounds', 'uMaskOpacity']);\n const pathBuffer = gl.createBuffer();\n // this fixes a render issue with Chrome 113 (#1176)\n // issue was that a line is drawn from crop corner to top left\n if (isChrome()) {\n gl.bindBuffer(gl.ARRAY_BUFFER, pathBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, 1, gl.DYNAMIC_DRAW);\n }\n const strokePath = (points, width, color, close = false) => {\n const { program, locations } = pathShader;\n gl.useProgram(program);\n gl.enableVertexAttribArray(locations.aPosition);\n gl.enableVertexAttribArray(locations.aNormal);\n gl.enableVertexAttribArray(locations.aMiter);\n const vertices = createPathVertices(points, close);\n const stride = Float32Array.BYTES_PER_ELEMENT * 5;\n const normalOffset = Float32Array.BYTES_PER_ELEMENT * 2; // at position 2\n const miterOffset = Float32Array.BYTES_PER_ELEMENT * 4; // at position 4\n gl.uniform1f(locations.uWidth, width);\n gl.uniform1f(locations.uSharpness, pathSharpness);\n gl.uniform4fv(locations.uColor, color);\n gl.uniformMatrix4fv(locations.uMatrix, false, overlayMatrix);\n gl.uniform4f(locations.uCanvasColor, CANVAS_COLOR_R, CANVAS_COLOR_G, CANVAS_COLOR_B, CANVAS_COLOR_A);\n gl.uniform1fv(locations.uMaskBounds, maskBounds);\n gl.uniform1f(locations.uMaskOpacity, maskOpacity);\n gl.bindBuffer(gl.ARRAY_BUFFER, pathBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);\n gl.vertexAttribPointer(locations.aPosition, 2, gl.FLOAT, false, stride, 0);\n gl.vertexAttribPointer(locations.aNormal, 2, gl.FLOAT, false, stride, normalOffset);\n gl.vertexAttribPointer(locations.aMiter, 1, gl.FLOAT, false, stride, miterOffset);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length / 5);\n gl.disableVertexAttribArray(locations.aPosition);\n gl.disableVertexAttribArray(locations.aNormal);\n gl.disableVertexAttribArray(locations.aMiter);\n };\n //#endregion\n // #region triangle\n const triangleShader = createShader(gl, triangleVertexShader, triangleFragmentShader, ['aPosition'], ['uColor', 'uCanvasColor', 'uMatrix', 'uMaskBounds', 'uMaskOpacity']);\n const trianglesBuffer = gl.createBuffer();\n const fillTriangles = (vertices, backgroundColor) => {\n const { program, locations } = triangleShader;\n gl.useProgram(program);\n gl.enableVertexAttribArray(locations.aPosition);\n gl.uniform4fv(locations.uColor, backgroundColor);\n gl.uniformMatrix4fv(locations.uMatrix, false, overlayMatrix);\n gl.uniform1fv(locations.uMaskBounds, maskBounds);\n gl.uniform1f(locations.uMaskOpacity, maskOpacity);\n gl.uniform4f(locations.uCanvasColor, CANVAS_COLOR_R, CANVAS_COLOR_G, CANVAS_COLOR_B, CANVAS_COLOR_A);\n gl.bindBuffer(gl.ARRAY_BUFFER, trianglesBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);\n gl.vertexAttribPointer(locations.aPosition, 2, gl.FLOAT, false, 0, 0);\n gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 2);\n gl.disableVertexAttribArray(locations.aPosition);\n return vertices;\n };\n // const fillTriangles: (vertices: Float32Array, backgroundColor: number[]) => {};\n //#endregion\n // #region rect\n const rectShaderAttributes = ['aPosition', 'aTexCoord', 'aRectCoord'];\n const rectShaderUniforms = [\n 'uTexture',\n 'uColor',\n 'uMatrix',\n 'uCanvasColor',\n 'uTextureColor',\n 'uTextureOpacity',\n 'uRepeat',\n 'uPosition',\n 'uSize',\n 'uMaskBounds',\n 'uMaskOpacity',\n 'uMaskFeather',\n 'uCornerRadius',\n 'uInverted',\n 'uEdgeFeather',\n ];\n const rectShader = createShader(gl, rectVertexShader, rectFragmentShader, rectShaderAttributes, rectShaderUniforms);\n const rectBuffer = gl.createBuffer();\n const rectTextureBuffer = gl.createBuffer();\n const rectCornerBuffer = gl.createBuffer();\n const fillRect = (vertices, width, height, cornerRadius, backgroundColor, backgroundImage = transparentTexture, backgroundRepeat, opacity = 1.0, colorFilter = COLOR_TRANSPARENT, uv = RECT_UV, maskFeather = RECT_MASK_FEATHER, feather = 1, inverted) => {\n const { program, locations } = rectShader;\n gl.useProgram(program);\n gl.enableVertexAttribArray(locations.aPosition);\n gl.enableVertexAttribArray(locations.aTexCoord);\n gl.enableVertexAttribArray(locations.aRectCoord);\n gl.uniform4fv(locations.uColor, backgroundColor);\n gl.uniform2fv(locations.uSize, [width, height]);\n gl.uniform2fv(locations.uPosition, [vertices[2], vertices[3]]);\n gl.uniform2fv(locations.uRepeat, backgroundRepeat);\n gl.uniform1i(locations.uInverted, inverted ? 1 : 0);\n gl.uniform1fv(locations.uCornerRadius, cornerRadius);\n gl.uniform4f(locations.uCanvasColor, CANVAS_COLOR_R, CANVAS_COLOR_G, CANVAS_COLOR_B, CANVAS_COLOR_A);\n // mask\n gl.uniform1fv(locations.uMaskFeather, maskFeather.map((v, i) => (i % 2 === 0 ? v : v * viewPixelDensity)));\n gl.uniform1fv(locations.uMaskBounds, maskBounds);\n gl.uniform1f(locations.uMaskOpacity, maskOpacity);\n gl.uniform1f(locations.uEdgeFeather, Math.max(0, feather));\n gl.uniformMatrix4fv(locations.uMatrix, false, overlayMatrix);\n gl.uniform1i(locations.uTexture, TEXTURE_SHAPE_INDEX);\n gl.uniform4fv(locations.uTextureColor, colorFilter);\n gl.uniform1f(locations.uTextureOpacity, opacity);\n gl.activeTexture(gl.TEXTURE0 + TEXTURE_SHAPE_INDEX);\n gl.bindTexture(gl.TEXTURE_2D, backgroundImage);\n gl.bindBuffer(gl.ARRAY_BUFFER, rectTextureBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, uv, gl.STATIC_DRAW);\n gl.vertexAttribPointer(locations.aTexCoord, 2, gl.FLOAT, false, 0, 0);\n // we use these coordinates combined with the size of the rect to interpolate and alias edges\n gl.bindBuffer(gl.ARRAY_BUFFER, rectCornerBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, RECT_UV, gl.STATIC_DRAW);\n gl.vertexAttribPointer(locations.aRectCoord, 2, gl.FLOAT, false, 0, 0);\n gl.bindBuffer(gl.ARRAY_BUFFER, rectBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);\n gl.vertexAttribPointer(locations.aPosition, 2, gl.FLOAT, false, 0, 0);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length / 2);\n gl.disableVertexAttribArray(locations.aPosition);\n gl.disableVertexAttribArray(locations.aTexCoord);\n gl.disableVertexAttribArray(locations.aRectCoord);\n return vertices;\n };\n //#endregion\n // #region ellipse\n const ellipseShader = createShader(gl, ellipseVertexShader, ellipseFragmentShader, ['aPosition', 'aTexCoord'], [\n 'uTexture',\n 'uTextureOpacity',\n 'uTextureAdjust',\n 'uRepeat',\n 'uColor',\n 'uCanvasColor',\n 'uMatrix',\n 'uRadius',\n 'uInverted',\n 'uMaskBounds',\n 'uMaskOpacity',\n ]);\n const ellipseBuffer = gl.createBuffer();\n const ellipseTextureBuffer = gl.createBuffer();\n const fillEllipse = (vertices, width, height, backgroundColor, backgroundImage = transparentTexture, backgroundSize, uv = RECT_UV, opacity = 1.0, inverted = false) => {\n const { program, locations } = ellipseShader;\n gl.useProgram(program);\n gl.enableVertexAttribArray(locations.aPosition);\n gl.enableVertexAttribArray(locations.aTexCoord);\n gl.uniformMatrix4fv(locations.uMatrix, false, overlayMatrix);\n gl.uniform2fv(locations.uRadius, [width * 0.5, height * 0.5]);\n gl.uniform2fv(locations.uTextureAdjust, [\n backgroundSize.width / (width / viewPixelDensity),\n backgroundSize.height / (height / viewPixelDensity),\n ]);\n gl.uniform1i(locations.uInverted, inverted ? 1 : 0);\n gl.uniform4fv(locations.uColor, backgroundColor);\n gl.uniform4f(locations.uCanvasColor, CANVAS_COLOR_R, CANVAS_COLOR_G, CANVAS_COLOR_B, CANVAS_COLOR_A);\n gl.uniform2fv(locations.uRepeat, [1, 1]);\n gl.uniform1fv(locations.uMaskBounds, maskBounds);\n gl.uniform1f(locations.uMaskOpacity, maskOpacity);\n gl.uniform1i(locations.uTexture, TEXTURE_SHAPE_INDEX);\n gl.uniform1f(locations.uTextureOpacity, opacity);\n gl.activeTexture(gl.TEXTURE0 + TEXTURE_SHAPE_INDEX);\n gl.bindTexture(gl.TEXTURE_2D, backgroundImage);\n gl.bindBuffer(gl.ARRAY_BUFFER, ellipseTextureBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, uv, gl.STATIC_DRAW);\n gl.vertexAttribPointer(locations.aTexCoord, 2, gl.FLOAT, false, 0, 0);\n gl.bindBuffer(gl.ARRAY_BUFFER, ellipseBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);\n gl.vertexAttribPointer(locations.aPosition, 2, gl.FLOAT, false, 0, 0);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length / 2);\n gl.disableVertexAttribArray(locations.aPosition);\n gl.disableVertexAttribArray(locations.aTexCoord);\n };\n //#endregion\n //\n // draw calls\n //\n const drawPath = (points, rotation = 0, // not implemented yet\n flipX = false, // not implemented yet\n flipY = false, // not implemented yet\n strokeWidth, strokeColor, strokeClose, backgroundColor, opacity) => {\n // is no line\n if (points.length < 2)\n return;\n // manipulate points\n const computedPoints = points.map((p) => ({\n x: p.x * viewPixelDensity,\n y: p.y * viewPixelDensity,\n }));\n const computedStrokeWidth = strokeWidth * viewPixelDensity;\n // stroke\n if (computedStrokeWidth > 0)\n strokePath(computedPoints, computedStrokeWidth, applyOpacity(strokeColor, opacity), strokeClose);\n // need to fill polygon?\n if (computedPoints.length < 3 ||\n !backgroundColor ||\n backgroundColor[3] === 0 ||\n !strokeClose)\n return;\n const triangles = triangulate(computedPoints);\n const vertices = new Float32Array(triangles.length * 6);\n triangles.forEach(([a, b, c], i) => {\n const index = i * 6;\n vertices[index + 0] = a.x;\n vertices[index + 1] = a.y;\n vertices[index + 2] = b.x;\n vertices[index + 3] = b.y;\n vertices[index + 4] = c.x;\n vertices[index + 5] = c.y;\n });\n fillTriangles(vertices, applyOpacity(backgroundColor, opacity));\n };\n const drawRect = (rect, rotation = 0, flipX = false, flipY = false, cornerRadius, backgroundColor, backgroundImage, backgroundSize = undefined, backgroundPosition = undefined, backgroundRepeat = false, backgroundUVMap = undefined, strokeWidth, strokeColor, opacity, maskFeather = undefined, feather = 1 / viewPixelDensity, colorize, inverted) => {\n // clone first\n const rectOut = rectMultiply(rectClone$1(rect), viewPixelDensity);\n // has radius, doesn't matter for coordinates\n const cornerRadiusOut = cornerRadius\n .map((r) => limitCornerRadius(r || 0, rect))\n .map((r) => r * viewPixelDensity);\n // should fill\n if (backgroundColor || backgroundImage) {\n // adjust for edge anti-aliasing, if we don't do this the\n // visible rectangle will be 1 pixel smaller than the actual rectangle\n const rectFill = rectClone$1(rectOut);\n rectFill.x -= 0.5;\n rectFill.y -= 0.5;\n rectFill.width += 1;\n rectFill.height += 1;\n const points = createRectPoints(rectFill, rotation, flipX, flipY);\n const vertices = rectPointsToVertices(points);\n let color;\n if (colorize) {\n color = applyOpacity(colorize);\n // as 0 transparancy is used to test if the colorize filter should be applied we set it to 0.001\n if (color[3] === 0)\n color[3] = 0.001;\n }\n // calculate repeat count\n const fillRepeat = backgroundSize && backgroundRepeat\n ? [\n rectFill.width / backgroundSize.width,\n rectFill.height / backgroundSize.height,\n ]\n : [1, 1];\n fillRect(vertices, rectFill.width, rectFill.height, cornerRadiusOut, applyOpacity(backgroundColor, opacity), backgroundImage, fillRepeat, opacity, color, backgroundUVMap\n ? new Float32Array(backgroundUVMap)\n : calculateBackgroundUVMap(rectFill.width, rectFill.height, backgroundSize, backgroundPosition, viewPixelDensity), maskFeather, feather * viewPixelDensity, inverted);\n }\n // should draw outline\n if (strokeWidth) {\n // fixes issue where stroke would render weirdly\n strokeWidth = Math.min(strokeWidth, rectOut.width, rectOut.height);\n strokePath(\n // rect out is already multiplied by pixel density\n createRectOutline(rectOut.x, rectOut.y, rectOut.width, rectOut.height, rotation, cornerRadiusOut, flipX, flipY), strokeWidth * viewPixelDensity, applyOpacity(strokeColor, opacity), true);\n }\n };\n const drawEllipse = (center, rx, ry, rotation, flipX, flipY, backgroundColor, backgroundImage, backgroundSize = undefined, backgroundPosition = undefined, backgroundUVMap = undefined, strokeWidth, strokeColor, opacity, inverted) => {\n const rectOut = rectMultiply(rectCreate(center.x - rx, center.y - ry, rx * 2, ry * 2), viewPixelDensity);\n if (backgroundColor || backgroundImage) {\n // adjust for edge anti-aliasing, if we don't do this the\n // visible rectangle will be 1 pixel smaller than the actual rectangle\n const rectFill = rectClone$1(rectOut);\n if (!inverted) {\n rectFill.x -= 0.5;\n rectFill.y -= 0.5;\n rectFill.width += 1.0;\n rectFill.height += 1.0;\n }\n const points = createRectPoints(rectFill, rotation, flipX, flipY);\n const vertices = rectPointsToVertices(points);\n fillEllipse(vertices, rectFill.width, rectFill.height, applyOpacity(backgroundColor, opacity), backgroundImage, backgroundSize ||\n sizeCreate(rectFill.width / viewPixelDensity, rectFill.height / viewPixelDensity), backgroundUVMap\n ? new Float32Array(backgroundUVMap)\n : calculateBackgroundUVMap(rectFill.width, rectFill.height, backgroundSize, backgroundPosition, viewPixelDensity), opacity, inverted);\n }\n if (strokeWidth) {\n strokePath(\n // rect out is already multiplied by pixeldensity\n createEllipseOutline(rectOut.x, rectOut.y, rectOut.width, rectOut.height, rotation, flipX, flipY), strokeWidth * viewPixelDensity, applyOpacity(strokeColor, opacity), true);\n }\n };\n //#endregion\n const glTextures = new Map();\n const imageFramebufferSize = {};\n imageFramebufferSize[TEXTURE_PREVIEW_OVERLAY_INDEX] = { width: 0, height: 0 };\n imageFramebufferSize[TEXTURE_PREVIEW_BLEND_INDEX] = { width: 0, height: 0 };\n const imageFrameBufferMaxSize = 4096;\n const drawToImageFramebuffer = (index, buffer, imageSize, pixelDensity = 1) => {\n const textureScalar = Math.min(Math.min(imageFrameBufferMaxSize, textureSizeLimit) / imageSize.width, Math.min(imageFrameBufferMaxSize, textureSizeLimit) / imageSize.height, pixelDensity);\n const textureWidth = Math.floor(textureScalar * imageSize.width);\n const textureHeight = Math.floor(textureScalar * imageSize.height);\n if (!sizeEqual(imageSize, imageFramebufferSize[index])) {\n // update preview markup texture\n gl.bindTexture(gl.TEXTURE_2D, indexTextureMap.get(index));\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, textureWidth, textureHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n // set the filtering, we don't need mips\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindFramebuffer(gl.FRAMEBUFFER, buffer);\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, indexTextureMap.get(index), 0);\n // remember so we know when to update the framebuffer\n imageFramebufferSize[index] = imageSize;\n }\n else {\n gl.bindFramebuffer(gl.FRAMEBUFFER, buffer);\n }\n // switch transformMatrix\n const w = imageSize.width * viewPixelDensity;\n const h = imageSize.height * viewPixelDensity;\n mat4Ortho(imageFrameBuffer, 0, w, h, 0, -1, 1);\n mat4Translate(imageFrameBuffer, 0, h, 0);\n mat4ScaleX(imageFrameBuffer, 1);\n mat4ScaleY(imageFrameBuffer, -1);\n overlayMatrix = imageFrameBuffer;\n // framebuffer lives in image space\n gl.viewport(0, 0, textureWidth, textureHeight);\n // always transparent\n gl.colorMask(true, true, true, true);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n // update rect mask\n RECT_MASK_FEATHER = [\n 1,\n 0,\n 1,\n 0,\n 1,\n Math.max(viewSize.width, imageSize.width),\n 1,\n Math.max(viewSize.width, imageSize.width),\n ];\n };\n const textureDelete = (texture, options) => {\n const { forceRelease = false } = options || {};\n const { src } = glTextures.get(texture);\n // need to force release canvas elements when they're no longer used\n if (src instanceof HTMLCanvasElement) {\n if (!forceRelease && !src.dataset.retain) {\n releaseCanvas(src);\n }\n }\n glTextures.delete(texture);\n gl.deleteTexture(texture);\n };\n const resetCanvasMatrix = () => {\n mat4Ortho(overlayMatrixCanvas, 0, viewSize.width, viewSize.height, 0, -1, 1);\n };\n const textureIsRotatedVideo = (texture) => glTextures.get(texture).isRotatedVideo;\n return {\n // draw api\n drawPath,\n drawRect,\n drawEllipse,\n drawImage,\n // texture filters\n textureFilterNearest: gl.NEAREST,\n textureFilterLinear: gl.LINEAR,\n textureClamp: gl.CLAMP_TO_EDGE,\n textureRepeat: gl.REPEAT,\n //#region texture management\n textureCreate: () => {\n return gl.createTexture();\n },\n textureUpdate: (texture, source, options) => {\n glTextures.set(texture, {\n src: source,\n options,\n isRotatedVideo: source.nodeName === 'VIDEO' &&\n ((source.dataset && source.dataset.rotation == 90) ||\n source.dataset.rotation == 270),\n });\n return updateTexture(gl, texture, source, options);\n },\n textureGetSize: (texture) => {\n const { src, options } = glTextures.get(texture);\n const size = sizeCreateFromAny(src);\n if (options.scalar)\n return sizeApply(size, (v) => v / options.scalar);\n return size;\n },\n textureDelete,\n //#endregion\n enablePreviewStencil,\n applyPreviewStencil,\n disablePreviewStencil,\n setCanvasColor(color) {\n CANVAS_COLOR_R = color[0];\n CANVAS_COLOR_G = color[1];\n CANVAS_COLOR_B = color[2];\n CANVAS_COLOR_A = alpha ? color[3] : 1;\n gl.clear(gl.COLOR_BUFFER_BIT);\n },\n resetCanvasMatrix,\n updateCanvasMatrix(imageSize, origin, translation, scale, rotate) {\n const imageWidth = imageSize.width;\n const imageHeight = imageSize.height;\n const originX = viewSize.width * (0.5 / viewPixelDensity);\n const originY = viewSize.height * (0.5 / viewPixelDensity);\n const imageOrigin = {\n x: originX + (translation.x + origin.x),\n y: originY + (translation.y + origin.y),\n };\n const imageVisualCenter = {\n x: imageOrigin.x - origin.x,\n y: imageOrigin.y - origin.y,\n };\n const imageAbsoluteCenter = {\n x: imageWidth * 0.5,\n y: imageHeight * 0.5,\n };\n vectorRotate(imageVisualCenter, rotate.z, imageOrigin);\n vectorScale(imageVisualCenter, scale, imageOrigin);\n const imageTranslation = {\n x: imageVisualCenter.x - imageAbsoluteCenter.x,\n y: imageVisualCenter.y - imageAbsoluteCenter.y,\n };\n mat4Translate(overlayMatrixCanvas, imageTranslation.x * viewPixelDensity, imageTranslation.y * viewPixelDensity, 0);\n // we apply transforms from center of image\n mat4Translate(overlayMatrixCanvas, imageAbsoluteCenter.x * viewPixelDensity, imageAbsoluteCenter.y * viewPixelDensity, 0);\n mat4RotateZ(overlayMatrixCanvas, rotate.z);\n const flipX = rotate.x > Math.PI / 2;\n mat4RotateX(overlayMatrixCanvas, flipX ? Math.PI : 0);\n const flipY = rotate.y > Math.PI / 2;\n mat4RotateY(overlayMatrixCanvas, flipY ? Math.PI : 0);\n mat4Scale(overlayMatrixCanvas, scale);\n mat4Translate(overlayMatrixCanvas, -imageAbsoluteCenter.x * viewPixelDensity, -imageAbsoluteCenter.y * viewPixelDensity, 0);\n },\n drawToCanvas() {\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n // switch transformMatrix\n overlayMatrix = overlayMatrixCanvas;\n // tell webgl about the viewport\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);\n // black (or other color depending on background)\n gl.colorMask(true, true, true, true);\n gl.clearColor(CANVAS_COLOR_R, CANVAS_COLOR_G, CANVAS_COLOR_B, CANVAS_COLOR_A);\n // gl.clearColor(0.25, 0.25, 0.25, 1); // for debugging\n gl.clear(gl.COLOR_BUFFER_BIT);\n // update rect mask\n RECT_MASK_FEATHER = [1, 0, 1, 0, 1, viewSize.width, 1, viewSize.width];\n },\n drawToImageBlendBuffer(imageSize, pixelDensity) {\n drawToImageFramebuffer(TEXTURE_PREVIEW_BLEND_INDEX, blendFramebuffer, imageSize, pixelDensity);\n },\n drawToImageOverlayBuffer(imageSize, pixelDensity) {\n drawToImageFramebuffer(TEXTURE_PREVIEW_OVERLAY_INDEX, overlayFramebuffer, imageSize, pixelDensity);\n },\n // set mask\n enableMask(rect, opacity) {\n const maskX = rect.x * viewPixelDensity;\n const maskY = rect.y * viewPixelDensity;\n const maskWidth = rect.width * viewPixelDensity;\n const maskHeight = rect.height * viewPixelDensity;\n maskLeft = maskX;\n maskRight = maskLeft + maskWidth;\n maskTop = viewSize.height - maskY;\n maskBottom = viewSize.height - (maskY + maskHeight);\n maskOpacity = 1.0 - opacity;\n maskBounds = [maskTop, maskRight, maskBottom, maskLeft];\n },\n disableMask() {\n maskLeft = 0;\n maskRight = viewSize.width;\n maskTop = viewSize.height;\n maskBottom = 0;\n maskOpacity = 1;\n maskBounds = [maskTop, maskRight, maskBottom, maskLeft];\n },\n // canvas\n resize,\n release() {\n // remove all stored textures\n const textures = Array.from(glTextures.keys());\n textures.forEach((texture) => textureDelete(texture, { forceRelease: true }));\n glTextures.clear();\n // remove indexed textures\n indexTextureMap.forEach((texture) => {\n gl.deleteTexture(texture);\n });\n indexTextureMap.clear();\n // clean up shaders\n imageShader.destroy();\n pathShader.destroy();\n triangleShader.destroy();\n rectShader.destroy();\n ellipseShader.destroy();\n // clear canvas\n canvas.width = 1;\n canvas.height = 1;\n canvas = undefined;\n },\n };\n};\n\nvar numberToEven = (n) => (n % 2 == 0 ? n : n + 1);\n\n// quickly reads canvas data to editor state, replaces createDefaultImageReader\nconst createCanvasReader = () => {\n return [\n [\n async (state, options, onprogress) => {\n const { src } = state;\n const width = parseInt(src.width, 10);\n const height = parseInt(src.height, 10);\n const dest = await canvasToBlob(src);\n return {\n ...state,\n dest,\n size: {\n width,\n height,\n },\n };\n },\n 'read-canvas',\n ],\n ];\n};\nvar imageStateToCanvas = (src, imageState, options) => {\n const { targetCanvas, targetSize, disableDraw = false, shapePreprocessor } = options || {};\n const canvas = targetCanvas || document.createElement('canvas');\n const { crop, colorMatrix, convolutionMatrix } = imageState;\n const isVideoSrc = isVideoElement(src);\n const srcSize = {\n width: isVideoSrc ? src.videoWidth : src.width,\n height: isVideoSrc ? src.videoHeight : src.height,\n };\n // calculate target bounds dimensions\n const { upscale = false, fit = 'contain', width: targetWidth, height: targetHeight, } = targetSize || {};\n // scale canvas\n let canvasScalar = 1;\n if (fit === 'contain') {\n canvasScalar = Math.min((targetWidth || Number.MAX_SAFE_INTEGER) / crop.width, (targetHeight || Number.MAX_SAFE_INTEGER) / crop.height);\n }\n else if (fit === 'cover') {\n canvasScalar = Math.max((targetWidth || crop.width) / crop.width, (targetHeight || crop.height) / crop.height);\n }\n // can't upscale, let's limit scalar to 1\n if (!upscale)\n canvasScalar = Math.min(canvasScalar, 1);\n const canvasScaledWidth = fit === 'force' ? targetWidth : Math.floor(crop.width * canvasScalar);\n const canvasScaledHeight = fit === 'force' ? targetHeight : Math.floor(crop.height * canvasScalar);\n const canvasWidth = isVideoSrc ? numberToEven(canvasScaledWidth) : canvasScaledWidth;\n const canvasHeight = isVideoSrc ? numberToEven(canvasScaledHeight) : canvasScaledHeight;\n const glCanvas = createWebGLCanvas(canvas, { alpha: true });\n glCanvas.resize(canvasWidth, canvasHeight, 1);\n const glTexture = glCanvas.textureCreate();\n glCanvas.setCanvasColor([0, 0, 0, 0]);\n glCanvas.drawToCanvas();\n glCanvas.disableMask();\n glCanvas.disablePreviewStencil();\n // color matrix\n let computedColorMatrix;\n {\n const colorMatrices = Object.values(colorMatrix || {}).filter(Boolean);\n if (colorMatrices.length)\n computedColorMatrix = getColorMatrixFromColorMatrices(colorMatrices);\n }\n const canvasRect = { x: 0, y: 0, width: canvasWidth, height: canvasHeight };\n const { origin, translation, rotation, scale } = calculateImageTransforms(canvasRect, canvasRect, srcSize, crop, {\n x: 0,\n y: 0,\n width: canvasWidth,\n height: canvasHeight,\n }, canvasScalar, { x: 0, y: 0 }, 1, imageState.rotation, imageState.flipX, imageState.flipY);\n const glTransforms = [\n srcSize,\n origin.x,\n origin.y,\n translation.x,\n translation.y,\n rotation.x,\n rotation.y,\n rotation.z,\n scale,\n computedColorMatrix,\n 1,\n // @ts-ignore\n convolutionMatrix && convolutionMatrix.clarity,\n isNumber(imageState.gamma) ? imageState.gamma : 1,\n imageState.vignette || 0,\n [1, 0, 1, 0, 1, canvasHeight, 1, canvasWidth],\n undefined,\n undefined,\n undefined,\n undefined,\n false,\n ];\n // shapes\n let shapesOverlayDrawn = false;\n const hasShapes = imageState.decoration.length || imageState.annotation.length;\n const glShapeTexture = hasShapes && glCanvas.textureCreate();\n // api\n const updateTexture = () => {\n glCanvas.textureUpdate(glTexture, src, {\n filterParam: glCanvas.textureFilterLinear,\n wrapParam: glCanvas.textureClamp,\n });\n };\n const draw = () => {\n // make sure texture is at the correct frame when it's a video\n updateTexture();\n // draw to the canvas\n glCanvas.drawImage(glTexture, ...glTransforms);\n // draw to the canvas\n if (!hasShapes || !shapesOverlayDrawn)\n return;\n glCanvas.drawRect(canvasRect, 0, false, false, [0, 0, 0, 0], undefined, glShapeTexture);\n };\n const prepare = async () => {\n if (!hasShapes)\n return;\n // draw shapes on transparent canvas that has size of input\n const { dest } = await processImage(h('canvas', srcSize), {\n shapePreprocessor,\n imageReader: createCanvasReader(),\n imageWriter: createDefaultImageWriter$1({\n format: 'canvas',\n }),\n imageState: {\n ...imageState,\n redaction: [],\n frame: undefined,\n gamma: undefined,\n convolutionMatrix: undefined,\n colorMatrix: undefined,\n backgroundColor: [0, 0, 0, 0],\n backgroundImage: undefined,\n trim: undefined,\n vignette: undefined,\n volume: undefined,\n },\n });\n // only needs to run once\n glCanvas.textureUpdate(glShapeTexture, dest, {\n filterParam: glCanvas.textureFilterLinear,\n wrapParam: glCanvas.textureClamp,\n });\n // now ready to draw shapes texture\n shapesOverlayDrawn = true;\n // first draw\n !disableDraw && draw();\n };\n // load texture\n updateTexture();\n // first draw\n !disableDraw && draw();\n return {\n canvas,\n prepare,\n redraw: draw,\n destroy: () => {\n glCanvas.release();\n },\n };\n};\n\nvar scrambleEffect = (options, done) => {\n const { imageData, amount = 1 } = options;\n const intensity = Math.round(Math.max(1, amount) * 2);\n const range = Math.round(intensity * 0.5);\n const inputWidth = imageData.width;\n const inputHeight = imageData.height;\n const outputData = new Uint8ClampedArray(inputWidth * inputHeight * 4);\n const inputData = imageData.data;\n let randomData;\n let i = 0, x, y, r;\n let xoffset = 0;\n let yoffset = 0;\n let index;\n const l = inputWidth * inputHeight * 4 - 4;\n for (y = 0; y < inputHeight; y++) {\n randomData = crypto.getRandomValues(new Uint8ClampedArray(inputHeight));\n for (x = 0; x < inputWidth; x++) {\n r = randomData[y] / 255;\n xoffset = 0;\n yoffset = 0;\n if (r < 0.5) {\n xoffset = (-range + Math.round(Math.random() * intensity)) * 4;\n }\n if (r > 0.5) {\n yoffset = (-range + Math.round(Math.random() * intensity)) * (inputWidth * 4);\n }\n // limit to image data\n index = Math.min(Math.max(0, i + xoffset + yoffset), l);\n outputData[i] = inputData[index];\n outputData[i + 1] = inputData[index + 1];\n outputData[i + 2] = inputData[index + 2];\n outputData[i + 3] = inputData[index + 3];\n i += 4;\n }\n }\n // @ts-ignore\n done(null, {\n data: outputData,\n width: imageData.width,\n height: imageData.height,\n });\n};\n\n// basic blur covolution matrix\nconst BLUR_MATRIX = [0.0625, 0.125, 0.0625, 0.125, 0.25, 0.125, 0.0625, 0.125, 0.0625];\nvar imageDataScramble = async (inputData, options = {}) => {\n if (!inputData)\n return;\n const { width, height } = inputData;\n const { dataSize = 96, dataSizeScalar = 1, scrambleAmount = 4, blurAmount = 6, outputFormat = 'canvas', backgroundColor = [0, 0, 0], } = options;\n const size = Math.round(dataSize * dataSizeScalar);\n const scalar = Math.min(size / width, size / height);\n const outputWidth = Math.floor(width * scalar);\n const outputHeight = Math.floor(height * scalar);\n // draw low res preview, add margin so blur isn't transparent\n const scaledOutputCanvas = (h('canvas', { width: outputWidth, height: outputHeight }));\n const ctx = scaledOutputCanvas.getContext('2d', { willReadFrequently: true });\n // fill background on transparent images\n backgroundColor.length = 3; // prevent transparent colors\n ctx.fillStyle = colorArrayToRGBA(backgroundColor);\n ctx.fillRect(0, 0, outputWidth, outputHeight);\n if (isImageData(inputData)) {\n // temporarily draw to canvas so we can draw image data to scaled context\n const transferCanvas = h('canvas', { width, height });\n transferCanvas\n .getContext('2d', { willReadFrequently: true })\n .putImageData(inputData, 0, 0);\n // draw to scaled context\n ctx.drawImage(transferCanvas, 0, 0, outputWidth, outputHeight);\n // release memory\n releaseCanvas(transferCanvas);\n }\n else {\n // bitmap data\n ctx.drawImage(inputData, 0, 0, outputWidth, outputHeight);\n }\n // get scaled image data for scrambling\n const imageData = ctx.getImageData(0, 0, outputWidth, outputHeight);\n // filters to apply\n const filters = [];\n // add scramble filter\n if (scrambleAmount > 0)\n filters.push([scrambleEffect, { amount: scrambleAmount }]);\n // add blur filters\n if (blurAmount > 0)\n for (let i = 0; i < blurAmount; i++) {\n filters.push([convolutionEffect, { matrix: BLUR_MATRIX }]);\n }\n let imageDataScrambled;\n if (filters.length) {\n // builds effect chain\n const chain = (transforms, i) => `(err, imageData) => {\n(${transforms[i][0].toString()})(Object.assign({ imageData: imageData }, filterInstructions[${i}]), \n${transforms[i + 1] ? chain(transforms, i + 1) : 'done'})\n}`;\n const filterChain = `function (options, done) {\nconst filterInstructions = options.filterInstructions;\nconst imageData = options.imageData;\n(${chain(filters, 0)})(null, imageData)\n}`;\n // scramble image data in separate thread\n const imageDataObjectScrambled = await thread(filterChain, [\n {\n imageData: imageData,\n filterInstructions: filters.map((t) => t[1]),\n },\n ], [imageData.data.buffer]);\n imageDataScrambled = imageDataObjectToImageData(imageDataObjectScrambled);\n }\n else {\n imageDataScrambled = imageData;\n }\n if (outputFormat === 'canvas') {\n // put back scrambled data\n ctx.putImageData(imageDataScrambled, 0, 0);\n // return canvas\n return scaledOutputCanvas;\n }\n return imageDataScrambled;\n};\n\nfunction circOut(t) {\n return Math.sqrt(1 - --t * t);\n}\n\nfunction is_date(obj) {\n return Object.prototype.toString.call(obj) === '[object Date]';\n}\n\nfunction get_interpolator(a, b) {\n if (a === b || a !== a)\n return () => a;\n const type = typeof a;\n if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {\n throw new Error('Cannot interpolate values of different type');\n }\n if (Array.isArray(a)) {\n const arr = b.map((bi, i) => {\n return get_interpolator(a[i], bi);\n });\n return t => arr.map(fn => fn(t));\n }\n if (type === 'object') {\n if (!a || !b)\n throw new Error('Object cannot be null');\n if (is_date(a) && is_date(b)) {\n a = a.getTime();\n b = b.getTime();\n const delta = b - a;\n return t => new Date(a + t * delta);\n }\n const keys = Object.keys(b);\n const interpolators = {};\n keys.forEach(key => {\n interpolators[key] = get_interpolator(a[key], b[key]);\n });\n return t => {\n const result = {};\n keys.forEach(key => {\n result[key] = interpolators[key](t);\n });\n return result;\n };\n }\n if (type === 'number') {\n const delta = b - a;\n return t => a + t * delta;\n }\n throw new Error(`Cannot interpolate ${type} values`);\n}\nfunction tweened(value, defaults = {}) {\n const store = writable(value);\n let task;\n let target_value = value;\n function set(new_value, opts) {\n if (value == null) {\n store.set(value = new_value);\n return Promise.resolve();\n }\n target_value = new_value;\n let previous_task = task;\n let started = false;\n let { delay = 0, duration = 400, easing = identity, interpolate = get_interpolator } = assign(assign({}, defaults), opts);\n if (duration === 0) {\n if (previous_task) {\n previous_task.abort();\n previous_task = null;\n }\n store.set(value = target_value);\n return Promise.resolve();\n }\n const start = now() + delay;\n let fn;\n task = loop(now => {\n if (now < start)\n return true;\n if (!started) {\n fn = interpolate(value, new_value);\n if (typeof duration === 'function')\n duration = duration(value, new_value);\n started = true;\n }\n if (previous_task) {\n previous_task.abort();\n previous_task = null;\n }\n const elapsed = now - start;\n if (elapsed > duration) {\n store.set(value = new_value);\n return false;\n }\n // @ts-ignore\n store.set(value = fn(easing(elapsed / duration)));\n return true;\n });\n return task.promise;\n }\n return {\n set,\n update: (fn, opts) => set(fn(target_value, value), opts),\n subscribe: store.subscribe\n };\n}\n\n// @ts-ignore\nfunction tick_spring(ctx, last_value, current_value, target_value) {\n if (typeof current_value === 'number') {\n // @ts-ignore\n const delta = target_value - current_value;\n // @ts-ignore\n const velocity = (current_value - last_value) / (ctx.dt || 1 / 60); // guard div by 0\n const spring = ctx.opts.stiffness * delta;\n const damper = ctx.opts.damping * velocity;\n const acceleration = (spring - damper) * ctx.inv_mass;\n const d = (velocity + acceleration) * ctx.dt;\n if (Math.abs(d) < ctx.opts.precision && Math.abs(delta) < ctx.opts.precision) {\n return target_value; // settled\n }\n else {\n ctx.settled = false; // signal loop to keep ticking\n // @ts-ignore\n return current_value + d;\n }\n }\n else if (isArray(current_value)) {\n // @ts-ignore\n return current_value.map((_, i) => tick_spring(ctx, last_value[i], current_value[i], target_value[i]));\n }\n else if (typeof current_value === 'object') {\n const next_value = {};\n // @ts-ignore\n for (const k in current_value) {\n // @ts-ignore\n next_value[k] = tick_spring(ctx, last_value[k], current_value[k], target_value[k]);\n }\n // @ts-ignore\n return next_value;\n }\n else {\n throw new Error(`Cannot spring ${typeof current_value} values`);\n }\n}\n// export interface Spring {\n// set: (new_value: any, opts?: SpringUpdateOpts) => Promise;\n// update: (fn: Function, opts?: SpringUpdateOpts) => Promise;\n// subscribe: Function;\n// precision: number;\n// damping: number;\n// stiffness: number;\n// }\nfunction spring(value, opts = {}) {\n const store = writable(value);\n const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts;\n let last_time;\n let task;\n let current_token;\n let last_value = value;\n let target_value = value;\n let inv_mass = 1;\n let inv_mass_recovery_rate = 0;\n let cancel_task = false;\n function set(new_value, opts = {}) {\n target_value = new_value;\n const token = (current_token = {});\n if (value == null || opts.hard || (spring.stiffness >= 1 && spring.damping >= 1)) {\n cancel_task = true; // cancel any running animation\n last_time = null;\n last_value = new_value;\n store.set((value = target_value));\n return Promise.resolve();\n }\n else if (opts.soft) {\n const rate = opts.soft === true ? 0.5 : +opts.soft;\n inv_mass_recovery_rate = 1 / (rate * 60);\n inv_mass = 0; // infinite mass, unaffected by spring forces\n }\n if (!task) {\n last_time = null;\n cancel_task = false;\n const ctx = {\n inv_mass: undefined,\n opts: spring,\n settled: true,\n dt: undefined,\n };\n task = loop((now) => {\n if (last_time === null)\n last_time = now;\n if (cancel_task) {\n cancel_task = false;\n task = null;\n return false;\n }\n inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1);\n // altered so doesn't create a new object\n ctx.inv_mass = inv_mass;\n ctx.opts = spring;\n ctx.settled = true; // tick_spring may signal false\n ctx.dt = ((now - last_time) * 60) / 1000;\n const next_value = tick_spring(ctx, last_value, value, target_value);\n last_time = now;\n last_value = value;\n store.set((value = next_value));\n if (ctx.settled)\n task = null;\n return !ctx.settled;\n });\n }\n return new Promise((fulfil) => {\n task.promise.then(() => {\n if (token === current_token)\n fulfil();\n });\n });\n }\n const spring = {\n set,\n update: (fn, opts) => set(fn(target_value, value), opts),\n subscribe: store.subscribe,\n stiffness,\n damping,\n precision,\n };\n return spring;\n}\n\nvar prefersReducedMotion = readable(false, (set) => {\n const mql = window.matchMedia('(prefers-reduced-motion:reduce)');\n const test = () => set(mql.matches);\n test();\n mql.addListener(test);\n return () => mql.removeListener(test);\n});\n\nvar hasResizeObserver = () => 'ResizeObserver' in window;\n\n//\nconst rectNext = rectCreateEmpty();\nconst updateNodeRect = (node, x, y, width, height) => {\n if (!node.rect)\n node.rect = rectCreateEmpty();\n const rect = node.rect;\n rectUpdate(rectNext, x, y, width, height);\n if (rectEqual(rect, rectNext))\n return;\n rectUpdateWithRect(rect, rectNext);\n node.dispatchEvent(new CustomEvent('measure', { detail: rect }));\n};\n// measures the element\nconst r = Math.round;\nconst measureViewRect = (node) => {\n const clientRect = node.getBoundingClientRect();\n // no frame because root has zero size\n if (!frame)\n return;\n updateNodeRect(node, r(clientRect.x), r(clientRect.y), r(clientRect.width), r(clientRect.height));\n};\nconst measureOffset = (node) => {\n // no frame because root has zero size\n if (!frame)\n return;\n updateNodeRect(node, node.offsetLeft, node.offsetTop, node.offsetWidth, node.offsetHeight);\n};\n// holds all the elements to measure using requestAnimationFrame\nconst elements = [];\n// draw loop\nlet frame = undefined; /* is false when paused, is undefined when not initialised */\nfunction tick() {\n if (!elements.length) {\n frame = undefined;\n return;\n }\n elements.forEach((node) => node.measure(node));\n frame = requestAnimationFrame(tick);\n}\nlet observer; // ResizeObserver API not known by TypeScript\n// root observer\nlet rootObserver;\nlet rootRects = new Map([]);\nlet observedRoots = 0;\n// total observed elements so we know when we can unload observer\nlet observedNodes = 0;\nvar measurable = (node, options = {}) => {\n const { observePosition = false, observeViewRect = false, once = false, disabled = false, isMeasureRoot = false, } = options;\n // if is measure root we pause all measuring if this element doesn't have a size and no other roots actively measuring\n if (hasResizeObserver() && isMeasureRoot) {\n if (!rootObserver) {\n rootObserver = new ResizeObserver((entries) => {\n // update root rects\n entries.forEach((entry) => {\n rootRects.set(entry.target, entry.contentRect);\n });\n // loop over all observed roots, if all roots don't have a size we pause measuring\n const someVisible = Array.from(rootRects.values()).some(({ width, height }) => {\n return width > 0 && height > 0;\n });\n // if no roots visible, halt draw loop\n if (!someVisible) {\n frame && cancelAnimationFrame(frame);\n frame = false;\n }\n // if no draw loop and a root became visible\n else if (someVisible && frame === false) {\n tick();\n }\n });\n }\n // increase amount of observed roots so we know when no more roots are observed\n rootObserver.observe(node);\n observedRoots++;\n }\n // exit\n if (disabled)\n return;\n // use resize observe if available\n if (hasResizeObserver() && !observePosition && !observeViewRect) {\n // we only create one observer, it will observe all registered elements\n if (!observer) {\n observer = new ResizeObserver((entries) => {\n entries.forEach((entry) => {\n // no frame because root has zero size\n if (!frame)\n return;\n measureOffset(entry.target);\n });\n });\n }\n // start observing this node\n observer.observe(node);\n // measure our node for the first time\n measureOffset(node);\n // if should only measure once, remove now\n if (once) {\n observer.unobserve(node);\n }\n else {\n observedNodes++;\n }\n // and we done, need to return a clean up method for when our node is destroyed\n return {\n destroy() {\n // if is root\n if (isMeasureRoot && rootObserver) {\n rootRects.delete(node);\n rootObserver.unobserve(node);\n observedRoots--;\n if (observedRoots === 0) {\n rootObserver.disconnect();\n rootObserver = undefined;\n }\n }\n // already unobserved this node\n if (once)\n return;\n // stop observing this node\n observer.unobserve(node);\n // test if all nodes have been removed, if so, remove observer\n observedNodes--;\n if (observedNodes === 0) {\n observer.disconnect();\n observer = undefined;\n }\n },\n };\n }\n // set measure function\n node.measure = observeViewRect ? measureViewRect : measureOffset;\n // add so the element is measured\n elements.push(node);\n // start measuring on next frame, we set up a single measure loop,\n // the loop will check if there's still elements that need to be measured,\n // else it will stop running\n if (frame === undefined)\n frame = requestAnimationFrame(tick);\n // measure this element now\n node.measure(node);\n // remove method\n return {\n destroy() {\n // if is root\n if (isMeasureRoot && rootObserver) {\n rootRects.delete(node);\n rootObserver.unobserve(node);\n observedRoots--;\n if (observedRoots === 0) {\n rootObserver.disconnect();\n rootObserver = undefined;\n }\n }\n // clean up element\n const index = elements.indexOf(node);\n elements.splice(index, 1);\n delete node.measure;\n },\n };\n};\n\nvar focusvisible = (element) => {\n let isKeyboardInteraction = undefined;\n const handlePointerdown = () => {\n isKeyboardInteraction = false;\n };\n const handleKeydown = () => {\n isKeyboardInteraction = true;\n };\n const handleKeyup = () => {\n isKeyboardInteraction = false;\n };\n const handleFocus = (e) => {\n if (isKeyboardInteraction === false)\n return;\n e.target.dataset.focusVisible = '';\n };\n const handleBlur = (e) => {\n delete e.target.dataset.focusVisible;\n };\n const map = {\n pointerdown: handlePointerdown,\n keydown: handleKeydown,\n keyup: handleKeyup,\n focus: handleFocus,\n blur: handleBlur,\n };\n Object.keys(map).forEach((event) => element.addEventListener(event, map[event], true));\n return {\n destroy() {\n Object.keys(map).forEach((event) => element.removeEventListener(event, map[event], true));\n },\n };\n};\n\nconst isURL = (str) => /^http/.test(str);\nconst getResourceFromItem = (item) => new Promise((resolve, reject) => {\n if (item.kind === 'file')\n return resolve(item.getAsFile());\n if (item.kind === 'string')\n return item.getAsString(resolve);\n // can't handle other kinds right now\n reject();\n});\nconst getResourcesFromEvent = (e) => new Promise((resolve, reject) => {\n const { items } = e.dataTransfer;\n if (!items)\n return resolve([]);\n // if is URI list we only return one URL or one FILE\n const isUriList = Array.from(items).some((item) => item.type === 'text/uri-list');\n const itemPromises = Array.from(items)\n .filter((item) => (item.kind === 'file' || item.kind === 'string') &&\n // this contains two urls which messes with the isURL test\n item.type !== 'text/x-moz-url')\n .map(getResourceFromItem);\n Promise.all(itemPromises)\n .then((res) => {\n // if is URI list we only use first URL, when dragging dropping an image tag on Windows Chrome we receive 3 URLs and a File\n if (isUriList)\n return resolve([res.find(isURL)].filter(Boolean));\n // filter out non images\n const resources = res.filter((item) => (isBinary(item) && isImage(item)) || isURL(item));\n // only urls\n resolve(resources);\n })\n .catch(reject);\n});\nvar dropable = (node, options = {}) => {\n const handleDragOver = (e) => {\n // need to be prevent default to allow drop\n e.preventDefault();\n };\n const handleDrop = async (e) => {\n e.preventDefault();\n e.stopPropagation(); // prevents parents from catching this drop\n try {\n const resources = await getResourcesFromEvent(e);\n node.dispatchEvent(new CustomEvent('dropfiles', {\n detail: {\n event: e,\n resources,\n },\n ...options,\n }));\n }\n catch (err) {\n // silent, wasn't able to catch\n }\n };\n node.addEventListener('drop', handleDrop);\n node.addEventListener('dragover', handleDragOver);\n // remove method\n return {\n destroy() {\n node.removeEventListener('drop', handleDrop);\n node.removeEventListener('dragover', handleDragOver);\n },\n };\n};\n\nlet result$5 = null;\nvar isSoftwareRendering = () => {\n if (result$5 === null) {\n if (isBrowser()) {\n const canvas = h('canvas');\n result$5 = !getWebGLContext(canvas, {\n failIfMajorPerformanceCaveat: true,\n });\n releaseCanvas(canvas);\n }\n else {\n result$5 = false;\n }\n }\n return result$5;\n};\n\nvar createTransparencyGrid = (size) => {\n const sizeHalf = size * 0.5;\n const canvas = h('canvas', {\n 'data-retain': true,\n width: size,\n height: size,\n });\n const ctx = canvas.getContext('2d');\n ctx.fillStyle = '#f0f';\n ctx.fillRect(0, 0, sizeHalf, sizeHalf);\n ctx.fillRect(sizeHalf, sizeHalf, sizeHalf, sizeHalf);\n return canvas;\n};\n\nvar durationToMilliseconds = (duration) => {\n let f = parseFloat(duration);\n if (/^[0-9]+s$/.test(duration))\n return f * 1000;\n return f;\n};\n\nvar isTexture = (texture) => texture instanceof WebGLTexture;\n\nvar backgroundCornersToUVMap = ([tl, tr, br, bl]) => {\n // tl, tr, br, bl -> bl, tl, br, tr\n // prettier-ignore\n // B D\n // | \\ |\n // A C\n return [\n bl.x,\n bl.y,\n tl.x,\n tl.y,\n br.x,\n br.y,\n tr.x,\n tr.y\n ];\n};\n\nvar isVideoPlaying = (video) => video.currentTime > 0 && !video.paused && !video.ended && video.readyState > 2;\n\nvar ctxDrawSelection = (ctx, shapes, options) => {\n const { selectionColor = [1, 1, 1], scalar = 1 } = options || {};\n let didDraw = false;\n for (const shape of shapes) {\n const shouldSubtract = shape.drawMode === 'subtract';\n // skip if can't subtract (this prevents first shape from drawing when subtract is set to true)\n if (shouldSubtract && !didDraw)\n continue;\n ctx.globalCompositeOperation = shouldSubtract ? 'destination-out' : 'source-over';\n // remember context state\n ctx.save();\n // clears previous shape's path\n ctx.beginPath();\n // center, needed for transforms\n const center = shapeGetCenter(shape);\n // scale context so we adjust for scaled down selection canvas (2048 max size)\n ctxScale(ctx, scalar, scalar);\n // rotate context\n ctxRotate(ctx, shape.rotation, center);\n // flip context\n ctxFlip(ctx, shape.flipX, shape.flipY, center);\n // draw rect\n if (shape.width) {\n drawRect(ctx, { ...shape, backgroundColor: selectionColor });\n }\n // draw line\n else if (shape.points) {\n drawPath(ctx, Object.assign({}, shape, shape.pathClose\n ? { backgroundColor: selectionColor, strokeColor: [0, 0, 0, 0] }\n : { strokeColor: selectionColor, strokeJoin: 'round', strokeCap: 'round' }));\n }\n // draw circle\n else if (shape.rx) {\n drawEllipse(ctx, {\n ...shape,\n backgroundColor: selectionColor,\n strokeColor: [0, 0, 0, 0],\n strokeJoin: 'round',\n strokeCap: 'round',\n });\n }\n ctx.restore();\n didDraw = true;\n }\n};\n\n/* src/core/ui/components/Canvas.svelte generated by Svelte v3.52.0 */\n\nfunction create_fragment$T(ctx) {\n\tlet div;\n\tlet canvas_1;\n\tlet mounted;\n\tlet dispose;\n\n\treturn {\n\t\tc() {\n\t\t\tdiv = element(\"div\");\n\t\t\tcanvas_1 = element(\"canvas\");\n\t\t\tattr(div, \"class\", \"PinturaCanvas\");\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tinsert(target, div, anchor);\n\t\t\tappend(div, canvas_1);\n\t\t\t/*canvas_1_binding*/ ctx[37](canvas_1);\n\n\t\t\tif (!mounted) {\n\t\t\t\tdispose = [\n\t\t\t\t\tlisten(canvas_1, \"measure\", /*measure_handler*/ ctx[38]),\n\t\t\t\t\taction_destroyer(measurable.call(null, canvas_1))\n\t\t\t\t];\n\n\t\t\t\tmounted = true;\n\t\t\t}\n\t\t},\n\t\tp: noop,\n\t\ti: noop,\n\t\to: noop,\n\t\td(detaching) {\n\t\t\tif (detaching) detach(div);\n\t\t\t/*canvas_1_binding*/ ctx[37](null);\n\t\t\tmounted = false;\n\t\t\trun_all(dispose);\n\t\t}\n\t};\n}\n\nconst SELECTION_MAX_SIZE = 2048;\nlet throttledMinRedrawDist = 48;\n\nfunction instance$U($$self, $$props, $$invalidate) {\n\tlet canDraw;\n\tlet drawUpdate;\n\tlet $maskFrameOpacityStore;\n\tlet $imageOverlayColor;\n\tlet $maskOpacityStore;\n\tlet $mask;\n\tlet $background;\n\tconst Vec4Empty = [0, 0, 0, 0];\n\n\t// just so we can re-use one empty vector\n\tconst EmptyVector = vectorCreateEmpty();\n\n\t// create grid texture\n\tconst TransparencyGrid = isBrowser() && createTransparencyGrid(128);\n\n\t// used to dispatch the 'measure' event\n\tconst dispatch = createEventDispatcher();\n\n\tlet { isAnimated } = $$props;\n\tlet { isTransparent } = $$props;\n\tlet { maskRect } = $$props;\n\tlet { maskOpacity = 1 } = $$props;\n\tlet { maskFrameOpacity = 0.95 } = $$props;\n\tlet { maskMarkupOpacity = 1 } = $$props;\n\tlet { clipAnnotationsToImage = true } = $$props;\n\tlet { pixelRatio = 1 } = $$props;\n\tlet { textPixelRatio = pixelRatio } = $$props;\n\tlet { backgroundColor } = $$props;\n\tlet { willRender = passthrough } = $$props;\n\tlet { didRender = passthrough } = $$props;\n\tlet { willRequest = undefined } = $$props;\n\tlet { csp = undefined } = $$props;\n\tlet { loadImageData = passthrough } = $$props;\n\tlet { enableGrid = false } = $$props;\n\tlet { gridColors = undefined } = $$props;\n\tlet { gridSize = undefined } = $$props;\n\tlet { gridOpacity = 0 } = $$props;\n\tlet { images = [] } = $$props;\n\tlet { interfaceImages = [] } = $$props;\n\tlet { selectionColor = undefined } = $$props;\n\n\t// internal props\n\tlet canvas;\n\n\tlet canvasGL = null;\n\tlet width = null;\n\tlet height = null;\n\n\t// springyness for main preview\n\tconst updateSpring = (spring, value) => spring.set(value, { hard: !isAnimated });\n\n\tconst SPRING_PROPS = { precision: 0.0001 };\n\tconst SPRING_PROPS_FRACTION = { precision: SPRING_PROPS.precision * 0.01 };\n\n\t// Editor UI\n\tlet backgroundColorAnimationDuration = 0;\n\n\tconst background = tweened(undefined, { duration: 0 });\n\tcomponent_subscribe($$self, background, value => $$invalidate(36, $background = value));\n\tconst maskOpacityStore = spring(1, SPRING_PROPS_FRACTION);\n\tcomponent_subscribe($$self, maskOpacityStore, value => $$invalidate(35, $maskOpacityStore = value));\n\tconst maskFrameOpacityStore = spring(1, SPRING_PROPS_FRACTION);\n\tcomponent_subscribe($$self, maskFrameOpacityStore, value => $$invalidate(51, $maskFrameOpacityStore = value));\n\tconst mask = writable();\n\tcomponent_subscribe($$self, mask, value => $$invalidate(53, $mask = value));\n\tconst imageOverlayColor = writable();\n\tcomponent_subscribe($$self, imageOverlayColor, value => $$invalidate(52, $imageOverlayColor = value));\n\n\t//#region selection\n\tconst selectionCache = {};\n\n\tconst shouldRedrawSelection = (previousActions, currentActions) => {\n\t\t// length change, always redraw\n\t\tif (previousActions.total !== currentActions.length) return true;\n\n\t\t// we only have to compare last shape\n\t\tconst previousLast = previousActions.last;\n\n\t\tconst currentLast = currentActions[currentActions.length - 1];\n\n\t\t// if not the same shape, redraw\n\t\tif (previousLast.drawMode !== currentLast.drawMode) return true;\n\n\t\tif (shapeIsRect(previousLast) && (!shapeIsRect(currentLast) || !rectEqual(previousLast, currentLast))) return true;\n\t\tif (shapeIsEllipse(previousLast) && (!shapeIsEllipse(currentLast) || !(previousLast.x === currentLast.x && previousLast.y === currentLast.y && previousLast.rx === currentLast.rx && previousLast.ry === currentLast.ry))) return true;\n\t\tif (shapeIsPath(previousLast) && (!shapeIsPath(currentLast) || currentLast.points.length !== previousLast.points.length)) return true;\n\n\t\t// no change\n\t\treturn false;\n\t};\n\n\tconst mapSelectionToShape = (selection, imageSize) => {\n\t\t// reference to cached state\n\t\tlet cachedSelection = selectionCache[selection.id];\n\n\t\t// calculate selection scalar, this speeds up performance\n\t\tconst scalar = Math.min(1, SELECTION_MAX_SIZE / imageSize.width);\n\n\t\tlet selectionCanvas = cachedSelection\n\t\t? cachedSelection.element\n\t\t: h('canvas', {\n\t\t\t\twidth: imageSize.width * scalar,\n\t\t\t\theight: imageSize.height * scalar,\n\t\t\t\t'data-retain': true\n\t\t\t});\n\n\t\t// new actions, need to redraw\n\t\tif (!cachedSelection || shouldRedrawSelection(cachedSelection, selection.actions)) {\n\t\t\t// clone so texture is redrawn (TODO: make live canvas texture possible)\n\t\t\tselectionCanvas = selectionCanvas.cloneNode();\n\n\t\t\t// draw actions\n\t\t\tctxDrawSelection(selectionCanvas.getContext('2d'), selection.actions, {\n\t\t\t\t// magenta, is replaced in webgl layer\n\t\t\t\tselectionColor: [1, 0, 1],\n\t\t\t\t// scale of canvas relative to image\n\t\t\t\tscalar\n\t\t\t});\n\n\t\t\t// store for next draw\n\t\t\tconst total = selection.actions.length;\n\n\t\t\tselectionCache[selection.id] = {\n\t\t\t\ttotal,\n\t\t\t\tlast: { ...selection.actions[total - 1] },\n\t\t\t\telement: selectionCanvas\n\t\t\t};\n\t\t}\n\n\t\t// create shape to hold texture\n\t\treturn {\n\t\t\tx: 0,\n\t\t\ty: 0,\n\t\t\t...imageSize,\n\t\t\tfillColor: selection.color || [1, 1, 1],\n\t\t\tbackgroundImage: selectionCanvas\n\t\t};\n\t};\n\n\t//#endregion\n\t//#region texture loading and binding\n\tlet redrawRequest;\n\n\tconst requestRedraw = () => {\n\t\tcancelAnimationFrame(redrawRequest);\n\n\t\tredrawRequest = requestAnimationFrame(() => {\n\t\t\t// this triggers redraw of items marked as non-dirty\n\t\t\tredrawForce = true;\n\n\t\t\t// this makes sure delayed redraw also works\n\t\t\tthrottledLastDrawTime = 0;\n\n\t\t\t// draw the update\n\t\t\tdrawUpdate();\n\t\t});\n\t};\n\n\tconst Textures = new Map([]);\n\tconst PlaceholderTextures = new Map([]);\n\n\tconst isTextureResourceLoaded = resource => // not is url or dataurl\n\t!isString(resource) && (// is something that is accepted as webgl texture\n\tisImageBitmap(resource) || isImageData(resource) || isCanvas(resource) || isVideoElement(resource));\n\n\tconst getTextureParams = (imageRendering, textureWrapping) => {\n\t\t// get texture filter mode\n\t\tconst filterParam = imageRendering === 'pixelated'\n\t\t? canvasGL.textureFilterNearest\n\t\t: canvasGL.textureFilterLinear;\n\n\t\tconst wrapParam = textureWrapping === 'repeat'\n\t\t? canvasGL.textureRepeat\n\t\t: canvasGL.textureClamp;\n\n\t\treturn { filterParam, wrapParam };\n\t};\n\n\tconst getImageTexture = (image, imageRendering, textureWrapping) => {\n\t\t// no texture yet for this source\n\t\tif (!Textures.has(image)) {\n\t\t\t// is in loading state when is same as source\n\t\t\tTextures.set(image, image);\n\n\t\t\t// get texture filter mode\n\t\t\tconst textureParams = getTextureParams(imageRendering, textureWrapping);\n\n\t\t\t// already loaded\n\t\t\tif (isTextureResourceLoaded(image)) {\n\t\t\t\t// create texture\n\t\t\t\tconst texture = canvasGL.textureCreate();\n\n\t\t\t\t// udpate texture in gl canvas\n\t\t\t\tcanvasGL.textureUpdate(texture, image, textureParams);\n\n\t\t\t\t// update state we now have a texture\n\t\t\t\tTextures.set(image, texture);\n\t\t\t} else // need to load the image\n\t\t\t{\n\t\t\t\t// start loading the image\n\t\t\t\tloadImageData(image).then(data => {\n\t\t\t\t\t// canvas destroyed while loading texture data, or no data returned\n\t\t\t\t\tif (!canvasGL || !data) return;\n\n\t\t\t\t\t// create texture\n\t\t\t\t\tconst texture = canvasGL.textureCreate();\n\n\t\t\t\t\t// udpate texture in gl canvas\n\t\t\t\t\tcanvasGL.textureUpdate(texture, data, textureParams);\n\n\t\t\t\t\t// update state we now have a texture\n\t\t\t\t\tTextures.set(image, texture);\n\n\t\t\t\t\t// need to redraw because texture is now available\n\t\t\t\t\trequestRedraw();\n\t\t\t\t}).catch(err => {\n\t\t\t\t\tTextures.set(image, err);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// texture is a video and should be updated\n\t\tif (isVideoElement(image) && (isVideoPlaying(image) || image.dataset.redraw === 'true')) {\n\t\t\tconst texture = Textures.get(image);\n\n\t\t\t// get texture filter mode\n\t\t\tconst textureParams = getTextureParams(imageRendering, textureWrapping);\n\n\t\t\t// udpate texture in gl canvas\n\t\t\tcanvasGL.textureUpdate(texture, image, textureParams);\n\n\t\t\t// done drawing\n\t\t\timage.dataset.redraw = false;\n\n\t\t\t// done!\n\t\t\treturn texture;\n\t\t}\n\n\t\t// get texture to return\n\t\treturn Textures.get(image);\n\t};\n\n\tconst getTextTexture = (shape, shapeExtendedProps = {}) => {\n\t\t// this allows setting a custom id (for use with shadow and outline)\n\t\tconst shapeId = shapeExtendedProps.id || shape.id;\n\n\t\t// skip very small shapes and shapes with not text\n\t\tif (shape.width && shape.width < 1 || shape.height && shape.height < 1 || !shape.text.length) {\n\t\t\tPlaceholderTextures.delete(shapeId);\n\t\t\treturn undefined;\n\t\t}\n\n\t\tlet { text, textAlign, fontFamily, fontSize = 16, fontWeight, fontVariant, fontStyle, letterSpacing, lineHeight = fontSize, width, height } = shape;\n\t\tlet { outline = 0, blur = 0, paddingTop = 0, paddingRight = 0, paddingBottom = 0, paddingLeft = 0 } = shapeExtendedProps;\n\n\t\t// this is the same as how contenteditable floors width/height\n\t\twidth = isNumber(width) ? Math.floor(width) : width;\n\n\t\theight = isNumber(height) ? Math.floor(height) : height;\n\n\t\tconst { textSize, signature } = textMeasure(text, {\n\t\t\t...shape,\n\t\t\twidth,\n\t\t\t// we don't want to limit height so the texture can contain all vertical text,\n\t\t\t// when switching between text modes this makes it possible to use same texture\n\t\t\t// for auto-height and fixed-height\n\t\t\theight: undefined\n\t\t});\n\n\t\tconst textUID = objectUID({\n\t\t\ttext,\n\t\t\ttextAlign,\n\t\t\tfontFamily,\n\t\t\tfontSize,\n\t\t\tfontWeight,\n\t\t\tfontVariant,\n\t\t\tfontStyle,\n\t\t\tlineHeight,\n\t\t\tletterSpacing,\n\t\t\toutline,\n\t\t\tblur,\n\t\t\tsignature\n\t\t});\n\n\t\t// get texture unit assigned to this specific text shape\n\t\tif (!Textures.has(textUID)) {\n\t\t\t// is in loading state when is same as source\n\t\t\tTextures.set(textUID, text);\n\n\t\t\t// defined size of text box\n\t\t\t// - line: undefined, undefined\n\t\t\t// - auto: width, undefined\n\t\t\t// - box: width, height\n\t\t\tconst imageWidth = Math.ceil(textSize.width);\n\n\t\t\tconst imageHeight = Math.ceil(textSize.height);\n\n\t\t\t// skip\n\t\t\tif (imageWidth === 0 || imageHeight === 0) return;\n\n\t\t\t// check if needs to scale texture\n\t\t\tconst textureSizeLimit = getWebGLTextureSizeLimit();\n\n\t\t\tconst textureSizeScalar = Math.min(\n\t\t\t\t1,\n\t\t\t\t// x\n\t\t\t\t(textureSizeLimit - (paddingLeft + paddingRight) * textPixelRatio) / (imageWidth * textPixelRatio),\n\t\t\t\t// y\n\t\t\t\t(textureSizeLimit - (paddingTop + paddingBottom) * textPixelRatio) / (imageHeight * textPixelRatio)\n\t\t\t);\n\n\t\t\t// create texture\n\t\t\ttextToImage(text, {\n\t\t\t\tfontSize,\n\t\t\t\tfontFamily,\n\t\t\t\tfontWeight,\n\t\t\t\tfontVariant,\n\t\t\t\tfontStyle,\n\t\t\t\tletterSpacing,\n\t\t\t\ttextAlign,\n\t\t\t\tlineHeight,\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\timageWidth,\n\t\t\t\timageHeight,\n\t\t\t\tpaddingLeft,\n\t\t\t\tpaddingTop,\n\t\t\t\tpaddingRight,\n\t\t\t\tpaddingBottom,\n\t\t\t\tpixelRatio: textPixelRatio * textureSizeScalar,\n\t\t\t\twillRequest,\n\t\t\t\toutline,\n\t\t\t\tblur,\n\t\t\t\tstyleNonce: csp.styleNonce,\n\t\t\t\tcolor: [1, 0, 1], // purple will be replaced in render method\n\t\t\t\t\n\t\t\t}).then(image => {\n\t\t\t\t// canvas destroyed while loading texture data\n\t\t\t\tif (!canvasGL) return;\n\n\t\t\t\t// create texture\n\t\t\t\tconst texture = canvasGL.textureCreate();\n\n\t\t\t\t// update texture in gl canvas\n\t\t\t\tcanvasGL.textureUpdate(texture, image, {\n\t\t\t\t\tfilterParam: canvasGL.textureFilterLinear,\n\t\t\t\t\twrapParam: canvasGL.textureClamp,\n\t\t\t\t\tscalar: textureSizeScalar\n\t\t\t\t});\n\n\t\t\t\t// update state we now have a texture\n\t\t\t\tTextures.set(textUID, texture);\n\n\t\t\t\t// use as fallback texture for next update\n\t\t\t\tPlaceholderTextures.set(shapeId, texture);\n\n\t\t\t\t// need to redraw because texture is now available\n\t\t\t\trequestRedraw();\n\t\t\t}).catch(console.error);\n\t\t}\n\n\t\tconst texture = Textures.get(textUID);\n\n\t\treturn isTexture(texture)\n\t\t? texture\n\t\t: PlaceholderTextures.get(shapeId);\n\t};\n\n\tconst canvasCache = new Map();\n\n\tconst getBitmapTexture = shape => {\n\t\tconst { id, points, strokeWidth, strokeCap, strokeJoin, strokeColor, strokeDash } = shape;\n\t\tlet texture = Textures.get(id);\n\t\tlet canvasState = canvasCache.get(id);\n\n\t\t// unique id for shape (currently only supports path)\n\t\tconst hash = objectUID({\n\t\t\tpoints: points.map(p => `${p.x},${p.y}`).join(','),\n\t\t\tstrokeWidth,\n\t\t\tstrokeCap,\n\t\t\tstrokeJoin,\n\t\t\tstrokeColor,\n\t\t\tstrokeDash: (strokeDash || []).join(',')\n\t\t});\n\n\t\t// test if should redraw\n\t\tif (canvasState) {\n\t\t\tconst { hash: previousHash } = canvasState;\n\n\t\t\t// it's possible the texture has been removed so in that case\n\t\t\t// we need to reset canvas state so the texture is recreated\n\t\t\tif (!Textures.has(id)) {\n\t\t\t\tcanvasState = undefined;\n\t\t\t} else // same texture\n\t\t\tif (hash === previousHash) {\n\t\t\t\treturn Textures.get(id);\n\t\t\t}\n\t\t}\n\n\t\t// if is new bitmap shape we need to add a canvas\n\t\tif (!canvasState) {\n\t\t\t// initial canvas\n\t\t\tconst canvas = h('canvas', { width: 1, height: 1 });\n\n\t\t\tcanvasState = { canvas, hash: undefined };\n\n\t\t\t// remember\n\t\t\tcanvasCache.set(shape.id, canvasState);\n\n\t\t\t// create texture\n\t\t\ttexture = canvasGL.textureCreate();\n\n\t\t\t// udpate texture in gl canvas\n\t\t\tcanvasGL.textureUpdate(texture, canvas, {\n\t\t\t\tfilterParam: canvasGL.textureFilterLinear,\n\t\t\t\twrapParam: canvasGL.textureClamp\n\t\t\t});\n\n\t\t\t// store texture\n\t\t\tTextures.set(id, texture);\n\t\t}\n\n\t\t// get canvas reference\n\t\tconst { canvas } = canvasState;\n\n\t\t// calculate canvas rectangle\n\t\tconst padding = Math.ceil(shape.strokeWidth);\n\n\t\tconst rect = rectCreateFromPoints(points);\n\t\tconst x = Math.floor(rect.x) - padding * 0.5;\n\t\tconst y = Math.floor(rect.y) - padding * 0.5;\n\t\tconst width = Math.ceil(rect.width + padding);\n\t\tconst height = Math.ceil(rect.height + padding);\n\t\tcanvas.width = Math.max(width, 1);\n\t\tcanvas.height = Math.max(height, 1);\n\t\tconst ctx = canvas.getContext('2d');\n\t\tctx.clearRect(0, 0, canvas.width, canvas.height);\n\n\t\tif (width >= 1 && height >= 1) {\n\t\t\tctx.translate(-x, -y);\n\t\t\tdrawPath(ctx, shape);\n\t\t\tctx.resetTransform();\n\t\t}\n\n\t\t// redraw texture in webgl\n\t\tcanvasGL.textureUpdate(texture, canvas, {\n\t\t\tfilterParam: canvasGL.textureFilterLinear,\n\t\t\twrapParam: canvasGL.textureClamp\n\t\t});\n\n\t\t// cache new state\n\t\tcanvasCache.set(id, { canvas, hash });\n\n\t\treturn Textures.get(id);\n\t};\n\n\tconst getShapeTexture = shape => {\n\t\tlet texture;\n\n\t\t// let's create textures for backgrounds\n\t\tif (shape.backgroundImage) {\n\t\t\ttexture = getImageTexture(shape.backgroundImage, shape.backgroundImageRendering, shape.backgroundRepeat === 'repeat'\n\t\t\t? 'repeat'\n\t\t\t: undefined);\n\t\t} else // is bitmap drawn shape (path only for now)\n\t\tif (shape.bitmap && shape.points) {\n\t\t\ttexture = getBitmapTexture(shape);\n\t\t}\n\n\t\treturn texture;\n\t};\n\n\tconst releaseUnusedTextures = usedTextures => {\n\t\tTextures.forEach((registeredTexture, key) => {\n\t\t\tconst isUsed = !!usedTextures.find(usedTexture => usedTexture === registeredTexture);\n\n\t\t\t// stil used, no need to release\n\t\t\tif (isUsed || !isTexture(registeredTexture)) return;\n\n\t\t\t// skip if used as placeholder texture\n\t\t\tif (Array.from(PlaceholderTextures.values()).includes(registeredTexture)) return;\n\n\t\t\t// remove this texture\n\t\t\tTextures.delete(key);\n\n\t\t\tcanvasGL.textureDelete(registeredTexture);\n\t\t});\n\t};\n\n\t//#endregion\n\t//#region drawing\n\tconst drawImage = ({ texture, size, origin, translation, rotation, scale, colorMatrix, opacity, convolutionMatrix, gamma, vignette, maskFeather, maskCornerRadius, overlayColor, enableOverlay, enableManipulation, enableAntialiasing }) => {\n\t\t// this makes sure the image renders extra crisp on low res screens\n\t\tlet sharpenOffsetX = 0;\n\n\t\tlet sharpenOffsetY = 0;\n\n\t\tif (pixelRatio === 1) {\n\t\t\tconst atActualSize = Math.abs(1 - scale) < Number.EPSILON;\n\t\t\tsharpenOffsetX = atActualSize && width % 2 !== 0 ? 0.5 : 0;\n\t\t\tsharpenOffsetY = atActualSize && height % 2 !== 0 ? 0.5 : 0;\n\t\t}\n\n\t\t// draw the image\n\t\tcanvasGL.drawImage(texture, size, origin.x, origin.y, translation.x + sharpenOffsetX, translation.y + sharpenOffsetY, rotation.x, rotation.y, rotation.z, scale, colorMatrix, clamp(opacity, 0, 1), convolutionMatrix, gamma, vignette, maskFeather, maskCornerRadius, overlayColor, enableOverlay, enableManipulation, enableAntialiasing);\n\n\t\treturn texture;\n\t};\n\n\tconst getTextRotationTranslation = (textboxRect, shapeRect, rotation) => {\n\t\tconst shapeRectCenter = rectCenter(shapeRect);\n\t\tconst textboxRectCenter = rectCenter(textboxRect);\n\n\t\tconst shapeTranslation = {\n\t\t\tx: shapeRectCenter.x - textboxRectCenter.x,\n\t\t\ty: shapeRectCenter.y - textboxRectCenter.y\n\t\t};\n\n\t\tconst rotatedTranslation = vectorRotate(shapeRectCenter, rotation, textboxRectCenter);\n\n\t\treturn {\n\t\t\tx: rotatedTranslation.x - textboxRectCenter.x - shapeTranslation.x,\n\t\t\ty: rotatedTranslation.y - textboxRectCenter.y - shapeTranslation.y\n\t\t};\n\t};\n\n\tconst transformCornerRadius = (cornerRadius, scalar) => [cornerRadius, cornerRadius, cornerRadius, cornerRadius].map(v => v * scalar);\n\n\tconst computeBackgroundProps = (shape, shapeRect, textureSize) => {\n\t\tlet backgroundSize = undefined;\n\t\tlet backgroundPosition = undefined;\n\n\t\t// always respect texture aspect ratio\n\t\tconst textureAspectRatio = getAspectRatio(textureSize.width, textureSize.height);\n\n\t\t// if repeat is set, set position to 0,0 and size to textures size\n\t\tif (shape.backgroundRepeat === 'repeat') {\n\t\t\tbackgroundSize = { ...textureSize };\n\t\t\tbackgroundPosition = { x: 0, y: 0 };\n\t\t}\n\n\t\t// contain in rect\n\t\tif (shape.backgroundSize === 'contain') {\n\t\t\tconst rect = rectContainRect(shapeRect, textureAspectRatio, shapeRect);\n\t\t\tbackgroundSize = sizeCreateFromRect(rect);\n\n\t\t\tif (shape.backgroundPosition) {\n\t\t\t\tbackgroundPosition = shape.backgroundPosition;\n\t\t\t} else {\n\t\t\t\tbackgroundPosition = vectorCreate((shapeRect.width - backgroundSize.width) * 0.5, (shapeRect.height - backgroundSize.height) * 0.5);\n\t\t\t}\n\t\t} else // cover rect\n\t\tif (shape.backgroundSize === 'cover') {\n\t\t\tconst rect = rectCoverRect(shapeRect, textureAspectRatio, shapeRect);\n\t\t\tbackgroundSize = sizeCreateFromRect(rect);\n\n\t\t\tif (shape.backgroundPosition) {\n\t\t\t\tbackgroundPosition = shape.backgroundPosition;\n\t\t\t} else {\n\t\t\t\tbackgroundPosition = vectorCreate(rect.x, rect.y);\n\t\t\t\tbackgroundPosition = vectorCreate((shapeRect.width - backgroundSize.width) * 0.5, (shapeRect.height - backgroundSize.height) * 0.5);\n\t\t\t}\n\t\t} else // fixed size\n\t\tif (shape.backgroundSize) {\n\t\t\tbackgroundSize = shape.backgroundSize;\n\t\t\tbackgroundPosition = shape.backgroundPosition || { x: 0, y: 0 };\n\t\t} else // no size set but position is set\n\t\tif (shape.backgroundPosition) {\n\t\t\tbackgroundSize = { ...textureSize };\n\t\t\tbackgroundPosition = shape.backgroundPosition;\n\t\t}\n\n\t\treturn { backgroundSize, backgroundPosition };\n\t};\n\n\tconst drawShapes = (shapes = [], usedTextures) => {\n\t\t// return\n\t\tshapes.forEach(shape => {\n\t\t\t// only show texture if shape is finished loading\n\t\t\tlet shapeTexture = getShapeTexture(shape);\n\n\t\t\t// is loading texture\n\t\t\tconst isLoadingBackgroundImage = shape.status === 'complete'\n\t\t\t? false\n\t\t\t: shape.status === 'loading'\n\t\t\t\t? true\n\t\t\t\t: shape.backgroundImage && shapeTexture === shape.backgroundImage;\n\n\t\t\tif (isLoadingBackgroundImage) requestRedraw();\n\n\t\t\tconst isBackgroundLoadError = shape.status === 'error'\n\t\t\t? true\n\t\t\t: shape.backgroundImage && shapeTexture instanceof Error;\n\n\t\t\t// get the webgl texture\n\t\t\tlet texture = isTexture(shapeTexture) ? shapeTexture : undefined;\n\n\t\t\t// transforms\n\t\t\tconst scalar = shape._scale || 1;\n\n\t\t\tconst translation = shape._translate || EmptyVector;\n\n\t\t\t// scale stroke width\n\t\t\tconst strokeWidth = shape.strokeWidth && shape.strokeWidth * scalar;\n\n\t\t\tconst cornerRadius = shape.cornerRadius\n\t\t\t? transformCornerRadius(shape.cornerRadius, scalar)\n\t\t\t: Vec4Empty;\n\n\t\t\tconst isRect = !!shape.width;\n\t\t\tconst isText = isString(shape.text);\n\t\t\tconst isPath = isArray(shape.points);\n\t\t\tconst isEllipse = shapeIsEllipse(shape);\n\n\t\t\tif (isPath) {\n\t\t\t\t// transform points\n\t\t\t\tconst points = shape.points.map(point => vectorCreate(point.x * scalar + translation.x, point.y * scalar + translation.y));\n\n\t\t\t\t// if is bitmap\n\t\t\t\tif (shape.bitmap) {\n\t\t\t\t\t// need to add to used textures so we can know when no longer used\n\t\t\t\t\tif (shapeTexture) usedTextures.push(shapeTexture);\n\n\t\t\t\t\tconst rect = rectCreateFromPoints(points);\n\t\t\t\t\tconst padding = Math.ceil(shape.strokeWidth * scalar);\n\n\t\t\t\t\tcanvasGL.drawRect(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tx: Math.floor(rect.x) - padding * 0.5,\n\t\t\t\t\t\t\ty: Math.floor(rect.y) - padding * 0.5,\n\t\t\t\t\t\t\twidth: Math.ceil(rect.width + padding),\n\t\t\t\t\t\t\theight: Math.ceil(rect.height + padding)\n\t\t\t\t\t\t},\n\t\t\t\t\t\tshape.rotation,\n\t\t\t\t\t\tshape.flipX,\n\t\t\t\t\t\tshape.flipY,\n\t\t\t\t\t\t[0, 0, 0, 0],\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\ttexture,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tshape.opacity,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tfalse\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tcanvasGL.drawPath(points, shape.rotation, shape.flipX, shape.flipY, strokeWidth, shape.strokeColor, shape.pathClose, shape.backgroundColor, shape.opacity);\n\t\t\t\t}\n\t\t\t} else if (isEllipse) {\n\t\t\t\tlet x = shape.x;\n\t\t\t\tlet y = shape.y;\n\t\t\t\tx *= scalar;\n\t\t\t\ty *= scalar;\n\t\t\t\tx += translation.x;\n\t\t\t\ty += translation.y;\n\n\t\t\t\t// apply shape transforms\n\t\t\t\tconst shapeRect = rectCreateFromEllipse({\n\t\t\t\t\tx,\n\t\t\t\t\ty,\n\t\t\t\t\trx: shape.rx * scalar,\n\t\t\t\t\try: shape.ry * scalar\n\t\t\t\t});\n\n\t\t\t\tconst center = vectorCreate(x, y);\n\t\t\t\tconst textureSize = texture && canvasGL.textureGetSize(texture);\n\t\t\t\tlet backgroundSize;\n\t\t\t\tlet backgroundPosition;\n\n\t\t\t\tif (textureSize && shape.backgroundImage && (shape.backgroundSize || shape.backgroundPosition || shape.backgroundRepeat)) {\n\t\t\t\t\tconst backgroundProps = computeBackgroundProps(shape, shapeRect, textureSize);\n\t\t\t\t\tbackgroundSize = backgroundProps.backgroundSize;\n\t\t\t\t\tbackgroundPosition = backgroundProps.backgroundPosition;\n\t\t\t\t}\n\n\t\t\t\tcanvasGL.drawEllipse(center, shape.rx * scalar, shape.ry * scalar, shape.rotation, shape.flipX, shape.flipY, shape.backgroundColor, texture, backgroundSize, backgroundPosition, shape.backgroundCorners && backgroundCornersToUVMap(shape.backgroundCorners), strokeWidth, shape.strokeColor, shape.opacity, shape.inverted);\n\n\t\t\t\t// need to add to used textures so we can know when no longer used\n\t\t\t\tif (shapeTexture) usedTextures.push(shapeTexture);\n\n\t\t\t\t// draw loading indicator\n\t\t\t\tif (isLoadingBackgroundImage || isBackgroundLoadError) {\n\t\t\t\t\tif (!shape.backgroundColor && (!shape.strokeColor || !shape.strokeWidth)) {\n\t\t\t\t\t\tconst opacity = shape.opacity || 1;\n\n\t\t\t\t\t\tcanvasGL.drawEllipse(center, shape.rx * scalar, shape.ry * scalar, shape.rotation, shape.flipX, shape.flipY, isBackgroundLoadError\n\t\t\t\t\t\t? [1, 0.2549, 0.2118, 0.25 * opacity]\n\t\t\t\t\t\t: [0, 0, 0, 0.25 * opacity]);\n\t\t\t\t\t}\n\n\t\t\t\t\tisBackgroundLoadError && drawErrorIndicator(center);\n\t\t\t\t\tisLoadingBackgroundImage && drawLoadingIndicator(center);\n\t\t\t\t}\n\t\t\t} else if (isText) {\n\t\t\t\t// get fontsize ref and fallback to default\n\t\t\t\tconst { fontSize = 16 } = shape;\n\n\t\t\t\t// adjust font scalar\n\t\t\t\tconst fontScalar = fontSize > 1000 && isFirefox() ? fontSize / 1000 : 1;\n\n\t\t\t\t// calculate text padding\n\t\t\t\tconst textPadding = Math.max(16, Math.ceil(fontSize / fontScalar * 0.25));\n\n\t\t\t\t// get text size\n\t\t\t\tconst textboxIsRotated = Math.abs(shape.rotation) > 0;\n\n\t\t\t\tconst width = isNumber(shape.width)\n\t\t\t\t? Math.floor(shape.width)\n\t\t\t\t: shape.width;\n\n\t\t\t\tconst height = isNumber(shape.height)\n\t\t\t\t? Math.floor(shape.height)\n\t\t\t\t: shape.height;\n\n\t\t\t\tconst textboxSize = textToSize(shape.text, { ...shape, width, height });\n\t\t\t\tconst textboxWidth = Math.ceil(textboxSize.width);\n\t\t\t\tconst textboxHeight = Math.ceil(textboxSize.height);\n\n\t\t\t\tconst textboxRect = {\n\t\t\t\t\tx: shape.x * scalar + translation.x,\n\t\t\t\t\ty: shape.y * scalar + translation.y,\n\t\t\t\t\twidth: textboxWidth * scalar,\n\t\t\t\t\theight: textboxHeight * scalar\n\t\t\t\t};\n\n\t\t\t\t// should clip\n\t\t\t\tconst shouldClipTextEffects = !!shape.height;\n\n\t\t\t\t//\n\t\t\t\t// 1. Draw text background\n\t\t\t\t//\n\t\t\t\tif (shape.backgroundColor || shape.strokeColor) {\n\t\t\t\t\t// draw background\n\t\t\t\t\tcanvasGL.drawRect(textboxRect, shape.rotation, shape.flipX, shape.flipY, cornerRadius, shape.backgroundColor, undefined, undefined, undefined, false, undefined, strokeWidth, shape.strokeColor, shape.opacity, undefined, undefined, undefined, shape.inverted);\n\t\t\t\t}\n\n\t\t\t\t//\n\t\t\t\t// 2. Draw text shadow\n\t\t\t\t//\n\t\t\t\tconst { textShadowX, textShadowY, textShadowBlur } = shape;\n\n\t\t\t\tif (textShadowX || textShadowY || textShadowBlur) {\n\t\t\t\t\t// generate texture\n\t\t\t\t\tconst textShadowBlurHalf = Math.ceil(textShadowBlur * 0.5);\n\n\t\t\t\t\tconst padding = Math.max(textPadding, shouldClipTextEffects ? 0 : textShadowBlurHalf);\n\n\t\t\t\t\tconst shadowTexture = getTextTexture(shape, {\n\t\t\t\t\t\tid: shape.id + 'shadow',\n\t\t\t\t\t\tblur: textShadowBlur,\n\t\t\t\t\t\tpaddingTop: padding,\n\t\t\t\t\t\tpaddingRight: padding,\n\t\t\t\t\t\tpaddingBottom: padding,\n\t\t\t\t\t\tpaddingLeft: padding\n\t\t\t\t\t});\n\n\t\t\t\t\t// when received texture\n\t\t\t\t\tif (shadowTexture && !shape._prerender) {\n\t\t\t\t\t\tusedTextures.push(shadowTexture);\n\t\t\t\t\t\tconst shadowTextureSize = canvasGL.textureGetSize(shadowTexture);\n\n\t\t\t\t\t\t// where to position\n\t\t\t\t\t\tconst backgroundPosition = { x: 0, y: 0 };\n\n\t\t\t\t\t\t// size of background\n\t\t\t\t\t\tconst backgroundSize = {\n\t\t\t\t\t\t\twidth: shadowTextureSize.width / textPixelRatio * scalar,\n\t\t\t\t\t\t\theight: shadowTextureSize.height / textPixelRatio * scalar\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst scaledPadding = textPadding * scalar / fontScalar;\n\n\t\t\t\t\t\t// will hold shape rect to draw\n\t\t\t\t\t\tlet shapeRect;\n\n\t\t\t\t\t\t// when clipping text effects the shadow texture rect is the same as the text box rect so we done\n\t\t\t\t\t\tif (shouldClipTextEffects) {\n\t\t\t\t\t\t\tshapeRect = { ...textboxRect };\n\n\t\t\t\t\t\t\t// adjust position based on shadow offset\n\t\t\t\t\t\t\tbackgroundPosition.x = textShadowX - scaledPadding;\n\n\t\t\t\t\t\t\tbackgroundPosition.y = textShadowY - scaledPadding;\n\t\t\t\t\t\t} else // offset shape rect\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t//\n\t\t\t\t\t\t\tshapeRect = {\n\t\t\t\t\t\t\t\tx: shape.x * scalar + translation.x - scaledPadding,\n\t\t\t\t\t\t\t\ty: shape.y * scalar + translation.y - scaledPadding,\n\t\t\t\t\t\t\t\twidth: backgroundSize.width,\n\t\t\t\t\t\t\t\theight: backgroundSize.height\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t// find offset so we can calculate correct text shape position when rotated (texture rotates around different point than textbox)\n\t\t\t\t\t\t\tif (textboxIsRotated) {\n\t\t\t\t\t\t\t\tconst rotationTranslation = getTextRotationTranslation(textboxRect, shapeRect, shape.rotation);\n\t\t\t\t\t\t\t\tshapeRect.x += rotationTranslation.x;\n\t\t\t\t\t\t\t\tshapeRect.y += rotationTranslation.y;\n\n\t\t\t\t\t\t\t\t// calculate shadow offset\n\t\t\t\t\t\t\t\tconst shadowOffset = vectorRotate(vectorCreate(textShadowX, textShadowY), shape.rotation);\n\n\t\t\t\t\t\t\t\t// apply shadow transforms\n\t\t\t\t\t\t\t\tshapeRect.x += shadowOffset.x;\n\n\t\t\t\t\t\t\t\tshapeRect.y += shadowOffset.y;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tshapeRect.x += textShadowX;\n\t\t\t\t\t\t\t\tshapeRect.y += textShadowY;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// draw shadow\n\t\t\t\t\t\tcanvasGL.drawRect(shapeRect, shape.rotation, shape.flipX, shape.flipY, [0, 0, 0, 0], undefined, shadowTexture, backgroundSize, backgroundPosition, false, undefined, undefined, undefined, shape.opacity, undefined, 0, shape.textShadowColor);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t//\n\t\t\t\t// 3. Draw text outline\n\t\t\t\t//\n\t\t\t\tif (shape.textOutlineWidth) {\n\t\t\t\t\tconst textOutlineWidthHalf = Math.ceil(shape.textOutlineWidth * 0.5);\n\t\t\t\t\tconst outlinePadding = textPadding + textOutlineWidthHalf;\n\n\t\t\t\t\tconst outlineTexture = getTextTexture(shape, {\n\t\t\t\t\t\tid: shape.id + 'outline',\n\t\t\t\t\t\toutline: shape.textOutlineWidth,\n\t\t\t\t\t\tpaddingTop: outlinePadding,\n\t\t\t\t\t\tpaddingRight: outlinePadding,\n\t\t\t\t\t\tpaddingBottom: outlinePadding,\n\t\t\t\t\t\tpaddingLeft: outlinePadding\n\t\t\t\t\t});\n\n\t\t\t\t\tif (outlineTexture && !shape._prerender) {\n\t\t\t\t\t\tusedTextures.push(outlineTexture);\n\t\t\t\t\t\tconst outlineTextureSize = canvasGL.textureGetSize(outlineTexture);\n\n\t\t\t\t\t\t// where to position\n\t\t\t\t\t\tconst backgroundPosition = { x: 0, y: 0 };\n\n\t\t\t\t\t\t// size of background\n\t\t\t\t\t\tconst backgroundSize = {\n\t\t\t\t\t\t\twidth: outlineTextureSize.width / textPixelRatio * scalar,\n\t\t\t\t\t\t\theight: outlineTextureSize.height / textPixelRatio * scalar\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// build scaled shape rect\n\t\t\t\t\t\tconst scaledPadding = (textPadding + textOutlineWidthHalf) * scalar / fontScalar;\n\n\t\t\t\t\t\tconst shapeRect = {\n\t\t\t\t\t\t\tx: shape.x * scalar + translation.x - scaledPadding,\n\t\t\t\t\t\t\ty: shape.y * scalar + translation.y - scaledPadding,\n\t\t\t\t\t\t\twidth: backgroundSize.width,\n\t\t\t\t\t\t\t// need to clip shape if height is set\n\t\t\t\t\t\t\theight: shape.height\n\t\t\t\t\t\t\t? textboxHeight + scaledPadding\n\t\t\t\t\t\t\t: backgroundSize.height\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// apply rotation translation\n\t\t\t\t\t\tif (textboxIsRotated) {\n\t\t\t\t\t\t\tconst rotationTranslation = getTextRotationTranslation(textboxRect, shapeRect, shape.rotation);\n\t\t\t\t\t\t\tshapeRect.x += rotationTranslation.x;\n\t\t\t\t\t\t\tshapeRect.y += rotationTranslation.y;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcanvasGL.drawRect(shapeRect, shape.rotation, shape.flipX, shape.flipY, [0, 0, 0, 0], undefined, outlineTexture, backgroundSize, backgroundPosition, false, undefined, undefined, undefined, shape.opacity, undefined, 0, shape.textOutlineColor);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t//\n\t\t\t\t// 4. Draw text itself\n\t\t\t\t//\n\t\t\t\tconst textTexture = getTextTexture(shape, {\n\t\t\t\t\tid: shape.id + 'text',\n\t\t\t\t\tpaddingTop: textPadding,\n\t\t\t\t\tpaddingRight: textPadding,\n\t\t\t\t\tpaddingBottom: textPadding,\n\t\t\t\t\tpaddingLeft: textPadding\n\t\t\t\t});\n\n\t\t\t\tif (textTexture && !shape._prerender) {\n\t\t\t\t\tusedTextures.push(textTexture);\n\t\t\t\t\tconst textTextureSize = canvasGL.textureGetSize(textTexture);\n\n\t\t\t\t\t// where to position\n\t\t\t\t\tconst backgroundPosition = { x: 0, y: 0 };\n\n\t\t\t\t\t// size of background\n\t\t\t\t\tconst backgroundSize = {\n\t\t\t\t\t\twidth: textTextureSize.width / textPixelRatio * scalar,\n\t\t\t\t\t\theight: textTextureSize.height / textPixelRatio * scalar\n\t\t\t\t\t};\n\n\t\t\t\t\tconst scaledPadding = textPadding * scalar / fontScalar;\n\n\t\t\t\t\tconst shapeRect = {\n\t\t\t\t\t\tx: shape.x * scalar + translation.x - scaledPadding,\n\t\t\t\t\t\ty: shape.y * scalar + translation.y - scaledPadding,\n\t\t\t\t\t\twidth: backgroundSize.width,\n\t\t\t\t\t\t// need to clip shape if height is set\n\t\t\t\t\t\theight: shape.height\n\t\t\t\t\t\t? textboxHeight + scaledPadding\n\t\t\t\t\t\t: backgroundSize.height\n\t\t\t\t\t};\n\n\t\t\t\t\t// find offset so we can calculate correct text shape position when rotated (texture rotates around different point than textbox)\n\t\t\t\t\tif (textboxIsRotated) {\n\t\t\t\t\t\tconst rotationTranslation = getTextRotationTranslation(textboxRect, shapeRect, shape.rotation);\n\t\t\t\t\t\tshapeRect.x += rotationTranslation.x;\n\t\t\t\t\t\tshapeRect.y += rotationTranslation.y;\n\t\t\t\t\t}\n\n\t\t\t\t\t// The text texture needs to be colorized (we don't regenerate it on color change as that would be too slow)\n\t\t\t\t\tconst colorize = shape.color || [0, 0, 0];\n\n\t\t\t\t\t// If we're pre-rendering we do request drawing the texture but we hide it from view\n\t\t\t\t\tif (shape._prerender) colorize[3] = 0;\n\n\t\t\t\t\tcanvasGL.drawRect(shapeRect, shape.rotation, shape.flipX, shape.flipY, Vec4Empty, false, textTexture, backgroundSize, backgroundPosition, false, false, undefined, undefined, shape.opacity, undefined, shape.feather, colorize, shape.inverted);\n\t\t\t\t}\n\t\t\t} else if (isRect) {\n\t\t\t\t// need to add to used textures so we can know when no longer used\n\t\t\t\tif (shapeTexture) {\n\t\t\t\t\tusedTextures.push(shapeTexture);\n\t\t\t\t}\n\n\t\t\t\t// apply shape transforms\n\t\t\t\tconst shapeRect = rectCreateFromAny(shape);\n\n\t\t\t\tif (scalar && translation) {\n\t\t\t\t\tshapeRect.x *= scalar;\n\t\t\t\t\tshapeRect.y *= scalar;\n\t\t\t\t\tshapeRect.x += translation.x;\n\t\t\t\t\tshapeRect.y += translation.y;\n\t\t\t\t\tshapeRect.width *= scalar;\n\t\t\t\t\tshapeRect.height *= scalar;\n\t\t\t\t}\n\n\t\t\t\t// get texture\n\t\t\t\tlet backgroundSize, backgroundPosition;\n\n\t\t\t\tconst { backgroundRepeat = 'no-repeat' } = shape;\n\t\t\t\tconst textureSize = texture && canvasGL.textureGetSize(texture);\n\n\t\t\t\tif (textureSize && shape.backgroundImage && (shape.backgroundSize || shape.backgroundPosition || shape.backgroundRepeat)) {\n\t\t\t\t\tconst backgroundProps = computeBackgroundProps(shape, shapeRect, textureSize);\n\t\t\t\t\tbackgroundSize = backgroundProps.backgroundSize;\n\t\t\t\t\tbackgroundPosition = backgroundProps.backgroundPosition;\n\t\t\t\t}\n\n\t\t\t\t// draw the rect\n\t\t\t\tcanvasGL.drawRect(shapeRect, shape.rotation, shape.flipX, shape.flipY, cornerRadius, shape.backgroundColor || [0, 0, 0, 0], texture, backgroundSize, backgroundPosition, backgroundRepeat === 'repeat', shape.backgroundCorners && backgroundCornersToUVMap(shape.backgroundCorners), strokeWidth, shape.strokeColor, shape.opacity, undefined, shape.feather, shape.fillColor, shape.inverted);\n\n\t\t\t\t// draw loading indicator\n\t\t\t\tif (isLoadingBackgroundImage || isBackgroundLoadError) {\n\t\t\t\t\tif ((!shape.backgroundColor || shape.backgroundColor[3] === 0) && (!shape.strokeColor || !shape.strokeWidth)) {\n\t\t\t\t\t\tconst opacity = shape.opacity || 1;\n\n\t\t\t\t\t\tcanvasGL.drawRect(shapeRect, shape.rotation, shape.flipX, shape.flipY, cornerRadius, isBackgroundLoadError\n\t\t\t\t\t\t? [1, 0.2549, 0.2118, 0.25 * opacity]\n\t\t\t\t\t\t: [0, 0, 0, 0.25 * opacity]);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst position = rectCenter(shapeRect);\n\t\t\t\t\tisBackgroundLoadError && drawErrorIndicator(position);\n\t\t\t\t\tisLoadingBackgroundImage && drawLoadingIndicator(position);\n\t\t\t\t}\n\t\t\t} else if (shapeTexture) {\n\t\t\t\t// rect might have no width but we still want to remember texture for when it has a width in next draw cycle\n\t\t\t\tusedTextures.push(shapeTexture);\n\t\t\t}\n\t\t});\n\t};\n\n\t// loading indicator\n\tlet spinnerRotation = 0;\n\n\tconst errorPointsTLBR = [{ x: -5, y: -5 }, { x: 5, y: 5 }];\n\tconst errorPointsTRBL = [{ x: 5, y: -5 }, { x: -5, y: 5 }];\n\tconst spinnerPoints = ellipseToPolygon({ x: 0, y: 0 }, 10, 10, 0, false, false, 16);\n\tspinnerPoints.length = 9;\n\n\tconst drawLoadingIndicator = position => {\n\t\t// rotate based on date offset\n\t\tspinnerRotation = Date.now() / 50;\n\n\t\t// background fill\n\t\tcanvasGL.drawEllipse(position, 15, 15, 0, false, false, [0, 0, 0, 0.5]);\n\n\t\t// animate infinity spinner points positions\n\t\tconst points = vectorsRotate(spinnerPoints.map(p => ({ x: p.x + position.x, y: p.y + position.y })), spinnerRotation, position.x, position.y);\n\n\t\t// draw the points\n\t\tcanvasGL.drawPath(points, 0, false, false, 2, [1, 1, 1]);\n\t};\n\n\tconst drawErrorIndicator = position => {\n\t\t// background fill\n\t\tcanvasGL.drawEllipse(position, 13, 13, 0, false, false, [1, 0.2549, 0.2118, 0.75]);\n\n\t\tconst tlbr = errorPointsTLBR.map(p => ({ x: p.x + position.x, y: p.y + position.y }));\n\t\tconst trbl = errorPointsTRBL.map(p => ({ x: p.x + position.x, y: p.y + position.y }));\n\n\t\t// draw the points\n\t\tcanvasGL.drawPath(tlbr, 0, false, false, 3, [1, 1, 1]);\n\n\t\tcanvasGL.drawPath(trbl, 0, false, false, 3, [1, 1, 1]);\n\t};\n\n\tconst drawTransparencyGridLayer = (texture, rect, pos, size, color, opacity) => canvasGL.drawRect(\n\t\trect,\n\t\t0,\n\t\tfalse,\n\t\tfalse,\n\t\tVec4Empty,\n\t\tVec4Empty,\n\t\ttexture,\n\t\t// size\n\t\tsize,\n\t\t// pos\n\t\tpos,\n\t\t// repeat\n\t\ttrue,\n\t\tundefined,\n\t\t0,\n\t\tundefined,\n\t\topacity,\n\t\tundefined,\n\t\tundefined,\n\t\tcolor\n\t);\n\n\tconst drawTransparencyGrid = (rect, gridSize, colorEven, colorOdd, opacity) => {\n\t\tconst size = sizeCreate(gridSize, gridSize);\n\t\tconst texture = getImageTexture(TransparencyGrid, 'pixelated', 'repeat');\n\t\tconst posA = vectorCreate(-rect.x % gridSize, -rect.y % gridSize);\n\t\tconst posB = vectorCreate(posA.x + size.width * 0.5, posA.y);\n\t\tdrawTransparencyGridLayer(texture, rect, posA, size, colorEven, opacity);\n\t\tdrawTransparencyGridLayer(texture, rect, posB, size, colorOdd, opacity);\n\t};\n\n\t// redraws state\n\tlet redrawForce = false; // forces a redraw of dirty shapes\n\n\tlet isFirstDraw = true;\n\tlet wasFlipping = false;\n\n\t// textures\n\tconst usedManipulationTextures = [];\n\n\tconst usedAnnotationTextures = [];\n\tconst usedTextures = [];\n\tconst removeShapesAboveFrame = shape => !shape.aboveFrame;\n\tconst retainShapesAboveFrame = shape => shape.aboveFrame;\n\n\tconst redraw = () => {\n\t\t// reset array of textures used in this draw call\n\t\tusedTextures.length = 0;\n\n\t\t// get top image shortcut\n\t\tconst imagesTop = images[0];\n\n\t\t// allow dev to inject more shapes\n\t\tconst { manipulationShapes, manipulationShapesDirty, annotationShapes, annotationShapesDirty, interfaceShapes, decorationShapes, frameShapes, selectionShapes } = willRender({\n\t\t\t// top image state shortcut\n\t\t\topacity: imagesTop.opacity,\n\t\t\trotation: imagesTop.rotation,\n\t\t\tscale: imagesTop.scale,\n\t\t\t// active images\n\t\t\timages,\n\t\t\t// canvas size\n\t\t\tsize: sizeCreate(width, height),\n\t\t\t// canvas background\n\t\t\tbackgroundColor: [...$background],\n\t\t\t// preview selection rect\n\t\t\tselectionRect: $mask\n\t\t});\n\n\t\tconst canvasBackgroundColor = [...$background];\n\n\t\t// adjust mask a bit so bacckground color doesn't bleed through\n\t\tconst imagesMask = $mask;\n\n\t\tconst imagesMaskOpacity = clamp($maskOpacityStore, 0, 1);\n\t\tconst imagesOverlayColor = $imageOverlayColor;\n\t\tconst xFlipProgress = Math.abs(-1 + imagesTop.rotation.x / Math.PI * 2);\n\t\tconst yFlipProgress = Math.abs(-1 + imagesTop.rotation.y / Math.PI * 2);\n\t\tconst isFlipping = xFlipProgress < 0.99 || yFlipProgress < 0.99;\n\t\tconst imagesSize = { ...imagesTop.size };\n\t\tconst imagesBackgroundColor = imagesTop.backgroundColor;\n\t\tconst imagesBackgroundImage = imagesTop.backgroundImage;\n\n\t\t// no need to draw to blend framebuffer if no redactions\n\t\tconst hasManipulationShapes = manipulationShapes.length > 0;\n\n\t\t// no need to draw to markup framebuffer if no annotations\n\t\tconst hasAnnotations = annotationShapes.length > 0;\n\n\t\tconst hasAnnotationsAboveFrame = annotationShapes.filter(retainShapesAboveFrame).length > 0;\n\t\tconst hasDecorationsAboveFrame = decorationShapes.filter(retainShapesAboveFrame).length > 0;\n\n\t\t// if image has background color\n\t\tconst hasImageBackgroundColor = imagesBackgroundColor[3] > 0.0;\n\n\t\t// if the overlay is transparent so we can see the canvas\n\t\tconst hasTransparentOverlay = imagesMaskOpacity < 1.0;\n\n\t\t// should mask annotations outside of preview\n\t\tconst hasPreviewStencil = clipAnnotationsToImage ? maskMarkupOpacity >= 1 : false; //clipAnnotationsToImage || maskMarkupOpacity >= 1;\n\n\t\t// set canvas background color to image background color if is defined\n\t\tif (hasTransparentOverlay && hasImageBackgroundColor) {\n\t\t\tconst backR = canvasBackgroundColor[0];\n\t\t\tconst backG = canvasBackgroundColor[1];\n\t\t\tconst backB = canvasBackgroundColor[2];\n\t\t\tconst frontA = 1.0 - imagesMaskOpacity;\n\t\t\tconst frontR = imagesBackgroundColor[0] * frontA;\n\t\t\tconst frontG = imagesBackgroundColor[1] * frontA;\n\t\t\tconst frontB = imagesBackgroundColor[2] * frontA;\n\t\t\tconst fA = 1.0 - frontA;\n\t\t\tcanvasBackgroundColor[0] = frontR + backR * fA;\n\t\t\tcanvasBackgroundColor[1] = frontG + backG * fA;\n\t\t\tcanvasBackgroundColor[2] = frontB + backB * fA;\n\t\t\tcanvasBackgroundColor[3] = 1;\n\t\t}\n\n\t\t// make background transparent\n\t\tcanvasGL.setCanvasColor(isTransparent ? Vec4Empty : canvasBackgroundColor);\n\n\t\t// if has blend shapes draw blend shapes to framebuffer\n\t\tconst shouldDrawManipulations = hasManipulationShapes && (manipulationShapesDirty || redrawForce);\n\n\t\tif (shouldDrawManipulations) {\n\t\t\tcanvasGL.disableMask();\n\t\t\tcanvasGL.drawToImageBlendBuffer(imagesSize);\n\t\t\tusedManipulationTextures.length = 0;\n\t\t\tdrawShapes(manipulationShapes, usedManipulationTextures);\n\t\t} else if (!hasManipulationShapes) {\n\t\t\tusedManipulationTextures.length = 0;\n\t\t}\n\n\t\tusedTextures.push(...usedManipulationTextures);\n\n\t\t// prepare overlay frame buffer\n\t\tif (isFirstDraw) {\n\t\t\tcanvasGL.drawToImageOverlayBuffer(imagesSize, textPixelRatio);\n\t\t\tisFirstDraw = false;\n\t\t}\n\n\t\t// if has annotations draw annotation shapes to framebuffer\n\t\tconst shouldDrawAnnotations = hasAnnotations && (annotationShapesDirty || redrawForce);\n\n\t\tif (isFlipping) {\n\t\t\tif (shouldDrawAnnotations || !wasFlipping) {\n\t\t\t\tcanvasGL.disableMask();\n\t\t\t\tcanvasGL.drawToImageOverlayBuffer(imagesSize, textPixelRatio);\n\t\t\t\tusedAnnotationTextures.length = 0;\n\n\t\t\t\tconst filteredAnnotationShapes = hasAnnotationsAboveFrame\n\t\t\t\t? annotationShapes.filter(removeShapesAboveFrame)\n\t\t\t\t: annotationShapes;\n\n\t\t\t\tdrawShapes(filteredAnnotationShapes, usedAnnotationTextures);\n\t\t\t} else if (!hasAnnotations) {\n\t\t\t\tusedAnnotationTextures.length = 0;\n\t\t\t}\n\n\t\t\t// we use wasFlipping so we know when we should redraw the overlay framebuffer\n\t\t\twasFlipping = true;\n\t\t} else {\n\t\t\twasFlipping = false;\n\t\t}\n\n\t\t// switch to canvas drawing for other elements\n\t\tcanvasGL.drawToCanvas();\n\n\t\tcanvasGL.enableMask(imagesMask, imagesMaskOpacity);\n\n\t\tif (enableGrid && gridSize >= 1 && gridColors.length === 2 && gridOpacity) {\n\t\t\tdrawTransparencyGrid(imagesMask, gridSize, gridColors[0], gridColors[1], gridOpacity);\n\t\t}\n\n\t\t// draw a colored rectangle behind main preview image\n\t\tif (hasImageBackgroundColor) {\n\t\t\tcanvasGL.drawRect(imagesMask, 0, false, false, Vec4Empty, imagesBackgroundColor);\n\t\t}\n\n\t\tif (imagesBackgroundImage) {\n\t\t\t// hide background image outside of crop area\n\t\t\tcanvasGL.enableMask(imagesMask, 1.0);\n\n\t\t\tconst scalar = Math.max(maskRect.width / imagesBackgroundImage.width, maskRect.height / imagesBackgroundImage.height);\n\t\t\tconst width = imagesBackgroundImage.width * scalar;\n\t\t\tconst height = imagesBackgroundImage.height * scalar;\n\n\t\t\tconst rect = {\n\t\t\t\tx: maskRect.x + maskRect.width * 0.5 - width * 0.5,\n\t\t\t\ty: maskRect.y + maskRect.height * 0.5 - height * 0.5,\n\t\t\t\twidth,\n\t\t\t\theight\n\t\t\t};\n\n\t\t\tcanvasGL.drawRect(rect, 0, false, false, Vec4Empty, Vec4Empty, getImageTexture(imagesBackgroundImage, 'linear'));\n\t\t\tcanvasGL.enableMask(imagesMask, imagesMaskOpacity);\n\t\t}\n\n\t\thasPreviewStencil && canvasGL.enablePreviewStencil();\n\n\t\t// draw main image(s)\n\t\tusedTextures.push(...[...images].reverse().map(image => {\n\t\t\treturn drawImage({\n\t\t\t\t...image,\n\t\t\t\t// the image texture\n\t\t\t\ttexture: getImageTexture(image.data),\n\t\t\t\t// enable drawing markup if defined\n\t\t\t\tenableOverlay: isFlipping && hasAnnotations,\n\t\t\t\t// enable drawing redactions if defined\n\t\t\t\tenableManipulation: hasManipulationShapes,\n\t\t\t\t// antialias image edges\n\t\t\t\tenableAntialiasing: true,\n\t\t\t\t// mask and overlay positions\n\t\t\t\tmask: imagesMask,\n\t\t\t\tmaskOpacity: imagesMaskOpacity,\n\t\t\t\t// overlay\n\t\t\t\toverlayColor: imagesOverlayColor\n\t\t\t});\n\t\t}));\n\n\t\t// TODO: move vignette here (draw with colorized circular gradient texture instead of in shader)\n\t\t// set markup mask\n\t\tcanvasGL.enableMask(imagesMask, maskMarkupOpacity);\n\n\t\t// draw shapes in UI instead of framebuffer\n\t\tif (!isFlipping) {\n\t\t\t// only draw in image area\n\t\t\thasPreviewStencil && canvasGL.applyPreviewStencil();\n\n\t\t\t// draw shapes\n\t\t\tcanvasGL.resetCanvasMatrix();\n\n\t\t\tcanvasGL.updateCanvasMatrix(imagesSize, imagesTop.origin, imagesTop.translation, imagesTop.scale, imagesTop.rotation);\n\n\t\t\t// draw shapes\n\t\t\tusedAnnotationTextures.length = 0;\n\n\t\t\tconst filteredAnnotationShapes = hasAnnotationsAboveFrame\n\t\t\t? annotationShapes.filter(removeShapesAboveFrame)\n\t\t\t: annotationShapes;\n\n\t\t\tdrawShapes(filteredAnnotationShapes, usedAnnotationTextures);\n\n\t\t\t// stencil no longer needed\n\t\t\thasPreviewStencil && canvasGL.disablePreviewStencil();\n\t\t}\n\n\t\tusedTextures.push(...usedAnnotationTextures);\n\n\t\t// draw decorations shapes relative to crop\n\t\tcanvasGL.resetCanvasMatrix();\n\n\t\tcanvasGL.enableMask(imagesMask, maskMarkupOpacity);\n\n\t\tdrawShapes(\n\t\t\thasDecorationsAboveFrame\n\t\t\t? decorationShapes.filter(removeShapesAboveFrame)\n\t\t\t: decorationShapes,\n\t\t\tusedTextures\n\t\t);\n\n\t\t// draw frames\n\t\tif (frameShapes.length) {\n\t\t\t// set frame mask\n\t\t\tcanvasGL.enableMask(imagesMask, 1);\n\n\t\t\tconst shapesInside = frameShapes.filter(shape => !shape.expandsCanvas);\n\t\t\tconst shapesOutside = frameShapes.filter(shape => shape.expandsCanvas);\n\n\t\t\tif (shapesInside.length) {\n\t\t\t\tdrawShapes(shapesInside, usedTextures);\n\t\t\t}\n\n\t\t\tif (shapesOutside.length) {\n\t\t\t\t// the half pixel helps mask the outside shapes at the correct position\n\t\t\t\tcanvasGL.enableMask(\n\t\t\t\t\t{\n\t\t\t\t\t\tx: imagesMask.x + 0.5,\n\t\t\t\t\t\ty: imagesMask.y + 0.5,\n\t\t\t\t\t\twidth: imagesMask.width - 1,\n\t\t\t\t\t\theight: imagesMask.height - 1\n\t\t\t\t\t},\n\t\t\t\t\t$maskFrameOpacityStore\n\t\t\t\t);\n\n\t\t\t\tdrawShapes(shapesOutside, usedTextures);\n\t\t\t}\n\t\t}\n\n\t\t// draw annotation shapes on top of frames\n\t\tif (hasAnnotationsAboveFrame) {\n\t\t\tcanvasGL.resetCanvasMatrix();\n\t\t\tcanvasGL.updateCanvasMatrix(imagesSize, imagesTop.origin, imagesTop.translation, imagesTop.scale, imagesTop.rotation);\n\t\t\tdrawShapes(annotationShapes.filter(retainShapesAboveFrame), usedTextures);\n\t\t\tcanvasGL.resetCanvasMatrix();\n\t\t}\n\n\t\t// draw decoration shapes on top of frames\n\t\tif (hasDecorationsAboveFrame) {\n\t\t\tcanvasGL.resetCanvasMatrix();\n\t\t\tdrawShapes(decorationShapes.filter(retainShapesAboveFrame), usedTextures);\n\t\t\tcanvasGL.resetCanvasMatrix();\n\t\t}\n\n\t\t// draw selection (but not when flipping)\n\t\tif (selectionColor && selectionShapes.length && !isFlipping) {\n\t\t\t// draw shapes\n\t\t\tcanvasGL.resetCanvasMatrix();\n\n\t\t\tcanvasGL.updateCanvasMatrix(imagesSize, imagesTop.origin, imagesTop.translation, imagesTop.scale, imagesTop.rotation);\n\n\t\t\t// selection color\n\t\t\tconst color = [...selectionColor];\n\n\t\t\tcolor[3] = 0.5;\n\n\t\t\t// convert selections to shapes\n\t\t\tconst selectionRectShapes = [\n\t\t\t\t{\n\t\t\t\t\tid: 'selection',\n\t\t\t\t\tcolor,\n\t\t\t\t\tactions: [...selectionShapes]\n\t\t\t\t}\n\t\t\t].map(selectionGroup => mapSelectionToShape(selectionGroup, imagesSize));\n\n\t\t\t// draw selections\n\t\t\tdrawShapes(selectionRectShapes, usedTextures);\n\n\t\t\t// done\n\t\t\tcanvasGL.resetCanvasMatrix();\n\t\t}\n\n\t\t// crop mask not used for interface\n\t\tcanvasGL.disableMask();\n\n\t\t// draw custom interface shapes\n\t\tdrawShapes(interfaceShapes, usedTextures);\n\n\t\t// draw preview images (filters and video frame previews)\n\t\tinterfaceImages.forEach(image => {\n\t\t\tcanvasGL.enableMask(image.mask, image.maskOpacity);\n\n\t\t\t// draw background fill\n\t\t\tif (image.backgroundColor) {\n\t\t\t\t// calculate background image cover rect\n\t\t\t\tconst backgroundRect = imagesBackgroundImage && rectCoverRect({ ...image.mask }, imagesBackgroundImage.width / imagesBackgroundImage.height);\n\n\t\t\t\tconst backgroundImage = imagesBackgroundImage && getImageTexture(imagesBackgroundImage, 'linear');\n\t\t\t\tcanvasGL.drawRect(image.mask, 0, false, false, image.maskCornerRadius, image.backgroundColor, backgroundImage, backgroundRect, backgroundRect, undefined, undefined, undefined, undefined, image.opacity, image.maskFeather);\n\t\t\t\tbackgroundImage && usedTextures.push(backgroundImage);\n\t\t\t}\n\n\t\t\t// draw image\n\t\t\tconst texture = drawImage({\n\t\t\t\t...image,\n\t\t\t\ttexture: getImageTexture(image.data),\n\t\t\t\t// antialias edges\n\t\t\t\tenableAntialiasing: false,\n\t\t\t\t// update translation to apply `offset` from top left\n\t\t\t\ttranslation: {\n\t\t\t\t\tx: image.translation.x + image.offset.x - width * 0.5,\n\t\t\t\t\ty: image.translation.y + image.offset.y - height * 0.5\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tusedTextures.push(texture);\n\t\t});\n\n\t\tcanvasGL.disableMask();\n\n\t\t// determine which textures can be dropped\n\t\treleaseUnusedTextures(usedTextures);\n\n\t\t// done rendering\n\t\tdidRender();\n\n\t\t// redraw force flag\n\t\tredrawForce = false;\n\t};\n\n\t//#endregion\n\t//#region set up\n\t// throttled redrawer\n\tlet throttledLastDrawTime = 0;\n\n\tlet throttledFinalRedrawTimeout;\n\n\tconst redrawThrottled = () => {\n\t\tclearTimeout(throttledFinalRedrawTimeout);\n\t\tconst now = Date.now();\n\t\tconst dist = now - throttledLastDrawTime;\n\n\t\tif (dist < throttledMinRedrawDist) {\n\t\t\tthrottledFinalRedrawTimeout = setTimeout(redrawThrottled, throttledMinRedrawDist);\n\t\t\treturn;\n\t\t}\n\n\t\tthrottledLastDrawTime = now;\n\t\tredraw();\n\t};\n\n\t// returns the render function to use for this browser context\n\tconst selectFittingRenderFunction = () => isSoftwareRendering() ? redrawThrottled : redraw;\n\n\t// after DOM has been altered, redraw to canvas\n\tafterUpdate(() => drawUpdate());\n\n\t// hook up canvas to WebGL drawer\n\tonMount(() => $$invalidate(31, canvasGL = createWebGLCanvas(canvas, { alpha: isTransparent })));\n\n\t// clean up canvas\n\tonDestroy(() => {\n\t\t// if canvas wasn't created we don't need to release it\n\t\tif (!canvasGL) return;\n\n\t\t// clear canvas cache\n\t\tcanvasCache.forEach(({ canvas }) => releaseCanvas(canvas));\n\n\t\tcanvasCache.clear();\n\n\t\t// done drawing\n\t\tcanvasGL.release();\n\n\t\t// remove reference\n\t\t$$invalidate(31, canvasGL = undefined);\n\n\t\t$$invalidate(0, canvas = undefined);\n\t});\n\n\tfunction canvas_1_binding($$value) {\n\t\tbinding_callbacks[$$value ? 'unshift' : 'push'](() => {\n\t\t\tcanvas = $$value;\n\t\t\t$$invalidate(0, canvas);\n\t\t});\n\t}\n\n\tconst measure_handler = e => {\n\t\t$$invalidate(1, width = e.detail.width);\n\t\t$$invalidate(2, height = e.detail.height);\n\t\tdispatch('measure', { width, height });\n\t};\n\n\t$$self.$$set = $$props => {\n\t\tif ('isAnimated' in $$props) $$invalidate(9, isAnimated = $$props.isAnimated);\n\t\tif ('isTransparent' in $$props) $$invalidate(10, isTransparent = $$props.isTransparent);\n\t\tif ('maskRect' in $$props) $$invalidate(11, maskRect = $$props.maskRect);\n\t\tif ('maskOpacity' in $$props) $$invalidate(12, maskOpacity = $$props.maskOpacity);\n\t\tif ('maskFrameOpacity' in $$props) $$invalidate(13, maskFrameOpacity = $$props.maskFrameOpacity);\n\t\tif ('maskMarkupOpacity' in $$props) $$invalidate(14, maskMarkupOpacity = $$props.maskMarkupOpacity);\n\t\tif ('clipAnnotationsToImage' in $$props) $$invalidate(15, clipAnnotationsToImage = $$props.clipAnnotationsToImage);\n\t\tif ('pixelRatio' in $$props) $$invalidate(16, pixelRatio = $$props.pixelRatio);\n\t\tif ('textPixelRatio' in $$props) $$invalidate(17, textPixelRatio = $$props.textPixelRatio);\n\t\tif ('backgroundColor' in $$props) $$invalidate(18, backgroundColor = $$props.backgroundColor);\n\t\tif ('willRender' in $$props) $$invalidate(19, willRender = $$props.willRender);\n\t\tif ('didRender' in $$props) $$invalidate(20, didRender = $$props.didRender);\n\t\tif ('willRequest' in $$props) $$invalidate(21, willRequest = $$props.willRequest);\n\t\tif ('csp' in $$props) $$invalidate(22, csp = $$props.csp);\n\t\tif ('loadImageData' in $$props) $$invalidate(23, loadImageData = $$props.loadImageData);\n\t\tif ('enableGrid' in $$props) $$invalidate(24, enableGrid = $$props.enableGrid);\n\t\tif ('gridColors' in $$props) $$invalidate(25, gridColors = $$props.gridColors);\n\t\tif ('gridSize' in $$props) $$invalidate(26, gridSize = $$props.gridSize);\n\t\tif ('gridOpacity' in $$props) $$invalidate(27, gridOpacity = $$props.gridOpacity);\n\t\tif ('images' in $$props) $$invalidate(28, images = $$props.images);\n\t\tif ('interfaceImages' in $$props) $$invalidate(29, interfaceImages = $$props.interfaceImages);\n\t\tif ('selectionColor' in $$props) $$invalidate(30, selectionColor = $$props.selectionColor);\n\t};\n\n\t$$self.$$.update = () => {\n\t\tif ($$self.$$.dirty[0] & /*canvas*/ 1) {\n\t\t\tif (canvas) {\n\t\t\t\tconst propertyValue = getComputedStyle(canvas).getPropertyValue('--color-transition-duration');\n\t\t\t\t$$invalidate(32, backgroundColorAnimationDuration = durationToMilliseconds(propertyValue));\n\t\t\t}\n\t\t}\n\n\t\tif ($$self.$$.dirty[0] & /*backgroundColor, isAnimated*/ 262656 | $$self.$$.dirty[1] & /*backgroundColorAnimationDuration*/ 2) {\n\t\t\tbackgroundColor && background.set(backgroundColor, {\n\t\t\t\tduration: isAnimated ? backgroundColorAnimationDuration : 0\n\t\t\t});\n\t\t}\n\n\t\tif ($$self.$$.dirty[0] & /*maskOpacity*/ 4096) {\n\t\t\tupdateSpring(maskOpacityStore, isNumber(maskOpacity) ? maskOpacity : 1);\n\t\t}\n\n\t\tif ($$self.$$.dirty[0] & /*maskFrameOpacity*/ 8192) {\n\t\t\tupdateSpring(maskFrameOpacityStore, isNumber(maskFrameOpacity) ? maskFrameOpacity : 1);\n\t\t}\n\n\t\tif ($$self.$$.dirty[0] & /*maskRect*/ 2048) {\n\t\t\tmaskRect && mask.set(maskRect);\n\t\t}\n\n\t\tif ($$self.$$.dirty[1] & /*$background, $maskOpacityStore*/ 48) {\n\t\t\t$background && imageOverlayColor.set([\n\t\t\t\t$background[0],\n\t\t\t\t$background[1],\n\t\t\t\t$background[2],\n\t\t\t\tclamp($maskOpacityStore, 0, 1)\n\t\t\t]);\n\t\t}\n\n\t\tif ($$self.$$.dirty[0] & /*width, height, images*/ 268435462 | $$self.$$.dirty[1] & /*canvasGL*/ 1) {\n\t\t\t// can draw view\n\t\t\t$$invalidate(34, canDraw = !!(canvasGL && width && height && images.length));\n\t\t}\n\n\t\tif ($$self.$$.dirty[0] & /*width, height, pixelRatio*/ 65542 | $$self.$$.dirty[1] & /*canvasGL*/ 1) {\n\t\t\t// observe width and height changes and resize the canvas proportionally\n\t\t\tif (width && height && canvasGL) {\n\t\t\t\t// need to reset last draw date so throttled redraw always runs redraw logic after resize\n\t\t\t\tthrottledLastDrawTime = 0;\n\n\t\t\t\t// resize canvas\n\t\t\t\tcanvasGL.resize(width, height, pixelRatio);\n\t\t\t}\n\t\t}\n\n\t\tif ($$self.$$.dirty[1] & /*canDraw*/ 8) {\n\t\t\t// switch to draw method when can draw\n\t\t\t$$invalidate(33, drawUpdate = canDraw ? selectFittingRenderFunction() : noop$1);\n\t\t}\n\n\t\tif ($$self.$$.dirty[1] & /*canDraw, drawUpdate*/ 12) {\n\t\t\t// if can draw state is updated and we have a draw update function, time to redraw\n\t\t\tcanDraw && drawUpdate && drawUpdate();\n\t\t}\n\t};\n\n\treturn [\n\t\tcanvas,\n\t\twidth,\n\t\theight,\n\t\tdispatch,\n\t\tbackground,\n\t\tmaskOpacityStore,\n\t\tmaskFrameOpacityStore,\n\t\tmask,\n\t\timageOverlayColor,\n\t\tisAnimated,\n\t\tisTransparent,\n\t\tmaskRect,\n\t\tmaskOpacity,\n\t\tmaskFrameOpacity,\n\t\tmaskMarkupOpacity,\n\t\tclipAnnotationsToImage,\n\t\tpixelRatio,\n\t\ttextPixelRatio,\n\t\tbackgroundColor,\n\t\twillRender,\n\t\tdidRender,\n\t\twillRequest,\n\t\tcsp,\n\t\tloadImageData,\n\t\tenableGrid,\n\t\tgridColors,\n\t\tgridSize,\n\t\tgridOpacity,\n\t\timages,\n\t\tinterfaceImages,\n\t\tselectionColor,\n\t\tcanvasGL,\n\t\tbackgroundColorAnimationDuration,\n\t\tdrawUpdate,\n\t\tcanDraw,\n\t\t$maskOpacityStore,\n\t\t$background,\n\t\tcanvas_1_binding,\n\t\tmeasure_handler\n\t];\n}\n\nclass Canvas extends SvelteComponent {\n\tconstructor(options) {\n\t\tsuper();\n\n\t\tinit(\n\t\t\tthis,\n\t\t\toptions,\n\t\t\tinstance$U,\n\t\t\tcreate_fragment$T,\n\t\t\tsafe_not_equal,\n\t\t\t{\n\t\t\t\tisAnimated: 9,\n\t\t\t\tisTransparent: 10,\n\t\t\t\tmaskRect: 11,\n\t\t\t\tmaskOpacity: 12,\n\t\t\t\tmaskFrameOpacity: 13,\n\t\t\t\tmaskMarkupOpacity: 14,\n\t\t\t\tclipAnnotationsToImage: 15,\n\t\t\t\tpixelRatio: 16,\n\t\t\t\ttextPixelRatio: 17,\n\t\t\t\tbackgroundColor: 18,\n\t\t\t\twillRender: 19,\n\t\t\t\tdidRender: 20,\n\t\t\t\twillRequest: 21,\n\t\t\t\tcsp: 22,\n\t\t\t\tloadImageData: 23,\n\t\t\t\tenableGrid: 24,\n\t\t\t\tgridColors: 25,\n\t\t\t\tgridSize: 26,\n\t\t\t\tgridOpacity: 27,\n\t\t\t\timages: 28,\n\t\t\t\tinterfaceImages: 29,\n\t\t\t\tselectionColor: 30\n\t\t\t},\n\t\t\tnull,\n\t\t\t[-1, -1, -1]\n\t\t);\n\t}\n}\n\nvar arrayJoin = (arr, filter = Boolean, str = ' ') => arr.filter(filter).join(str);\n\n/* src/core/ui/components/TabList.svelte generated by Svelte v3.52.0 */\n\nfunction get_each_context$8(ctx, list, i) {\n\tconst child_ctx = ctx.slice();\n\tchild_ctx[17] = list[i];\n\treturn child_ctx;\n}\n\nconst get_default_slot_changes$1 = dirty => ({ tab: dirty & /*tabNodes*/ 4 });\nconst get_default_slot_context$1 = ctx => ({ tab: /*tab*/ ctx[17] });\n\n// (52:0) {#if shouldRender}\nfunction create_if_block$n(ctx) {\n\tlet div;\n\tlet each_blocks = [];\n\tlet each_1_lookup = new Map();\n\tlet div_class_value;\n\tlet current;\n\tlet each_value = /*tabNodes*/ ctx[2];\n\tconst get_key = ctx => /*tab*/ ctx[17].id;\n\n\tfor (let i = 0; i < each_value.length; i += 1) {\n\t\tlet child_ctx = get_each_context$8(ctx, each_value, i);\n\t\tlet key = get_key(child_ctx);\n\t\teach_1_lookup.set(key, each_blocks[i] = create_each_block$8(key, child_ctx));\n\t}\n\n\treturn {\n\t\tc() {\n\t\t\tdiv = element(\"div\");\n\n\t\t\tfor (let i = 0; i < each_blocks.length; i += 1) {\n\t\t\t\teach_blocks[i].c();\n\t\t\t}\n\n\t\t\tattr(div, \"class\", div_class_value = arrayJoin(['PinturaTabList', /*klass*/ ctx[0]]));\n\t\t\tattr(div, \"role\", \"tablist\");\n\t\t\tattr(div, \"data-layout\", /*layout*/ ctx[1]);\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tinsert(target, div, anchor);\n\n\t\t\tfor (let i = 0; i < each_blocks.length; i += 1) {\n\t\t\t\teach_blocks[i].m(div, null);\n\t\t\t}\n\n\t\t\t/*div_binding*/ ctx[14](div);\n\t\t\tcurrent = true;\n\t\t},\n\t\tp(ctx, dirty) {\n\t\t\tif (dirty & /*tabNodes, handleKeyTab, handleClickTab, $$scope*/ 1124) {\n\t\t\t\teach_value = /*tabNodes*/ ctx[2];\n\t\t\t\tgroup_outros();\n\t\t\t\teach_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, div, outro_and_destroy_block, create_each_block$8, null, get_each_context$8);\n\t\t\t\tcheck_outros();\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*klass*/ 1 && div_class_value !== (div_class_value = arrayJoin(['PinturaTabList', /*klass*/ ctx[0]]))) {\n\t\t\t\tattr(div, \"class\", div_class_value);\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*layout*/ 2) {\n\t\t\t\tattr(div, \"data-layout\", /*layout*/ ctx[1]);\n\t\t\t}\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\n\t\t\tfor (let i = 0; i < each_value.length; i += 1) {\n\t\t\t\ttransition_in(each_blocks[i]);\n\t\t\t}\n\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\tfor (let i = 0; i < each_blocks.length; i += 1) {\n\t\t\t\ttransition_out(each_blocks[i]);\n\t\t\t}\n\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\tif (detaching) detach(div);\n\n\t\t\tfor (let i = 0; i < each_blocks.length; i += 1) {\n\t\t\t\teach_blocks[i].d();\n\t\t\t}\n\n\t\t\t/*div_binding*/ ctx[14](null);\n\t\t}\n\t};\n}\n\n// (59:8) {#each tabNodes as tab (tab.id)}\nfunction create_each_block$8(key_1, ctx) {\n\tlet button;\n\tlet t;\n\tlet button_id_value;\n\tlet button_aria_controls_value;\n\tlet button_aria_selected_value;\n\tlet button_disabled_value;\n\tlet current;\n\tlet mounted;\n\tlet dispose;\n\tconst default_slot_template = /*#slots*/ ctx[11].default;\n\tconst default_slot = create_slot(default_slot_template, ctx, /*$$scope*/ ctx[10], get_default_slot_context$1);\n\n\tfunction keydown_handler(...args) {\n\t\treturn /*keydown_handler*/ ctx[12](/*tab*/ ctx[17], ...args);\n\t}\n\n\tfunction click_handler(...args) {\n\t\treturn /*click_handler*/ ctx[13](/*tab*/ ctx[17], ...args);\n\t}\n\n\treturn {\n\t\tkey: key_1,\n\t\tfirst: null,\n\t\tc() {\n\t\t\tbutton = element(\"button\");\n\t\t\tif (default_slot) default_slot.c();\n\t\t\tt = space();\n\t\t\tattr(button, \"role\", \"tab\");\n\t\t\tattr(button, \"id\", button_id_value = /*tab*/ ctx[17].tabId);\n\t\t\tattr(button, \"aria-controls\", button_aria_controls_value = /*tab*/ ctx[17].href.substring(1));\n\t\t\tattr(button, \"aria-selected\", button_aria_selected_value = /*tab*/ ctx[17].selected);\n\t\t\tbutton.disabled = button_disabled_value = /*tab*/ ctx[17].disabled;\n\t\t\tattr(button, \"type\", \"button\");\n\t\t\tthis.first = button;\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tinsert(target, button, anchor);\n\n\t\t\tif (default_slot) {\n\t\t\t\tdefault_slot.m(button, null);\n\t\t\t}\n\n\t\t\tappend(button, t);\n\t\t\tcurrent = true;\n\n\t\t\tif (!mounted) {\n\t\t\t\tdispose = [\n\t\t\t\t\tlisten(button, \"keydown\", keydown_handler),\n\t\t\t\t\tlisten(button, \"click\", click_handler)\n\t\t\t\t];\n\n\t\t\t\tmounted = true;\n\t\t\t}\n\t\t},\n\t\tp(new_ctx, dirty) {\n\t\t\tctx = new_ctx;\n\n\t\t\tif (default_slot) {\n\t\t\t\tif (default_slot.p && (!current || dirty & /*$$scope, tabNodes*/ 1028)) {\n\t\t\t\t\tupdate_slot_base(\n\t\t\t\t\t\tdefault_slot,\n\t\t\t\t\t\tdefault_slot_template,\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t/*$$scope*/ ctx[10],\n\t\t\t\t\t\t!current\n\t\t\t\t\t\t? get_all_dirty_from_scope(/*$$scope*/ ctx[10])\n\t\t\t\t\t\t: get_slot_changes(default_slot_template, /*$$scope*/ ctx[10], dirty, get_default_slot_changes$1),\n\t\t\t\t\t\tget_default_slot_context$1\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*tabNodes*/ 4 && button_id_value !== (button_id_value = /*tab*/ ctx[17].tabId)) {\n\t\t\t\tattr(button, \"id\", button_id_value);\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*tabNodes*/ 4 && button_aria_controls_value !== (button_aria_controls_value = /*tab*/ ctx[17].href.substring(1))) {\n\t\t\t\tattr(button, \"aria-controls\", button_aria_controls_value);\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*tabNodes*/ 4 && button_aria_selected_value !== (button_aria_selected_value = /*tab*/ ctx[17].selected)) {\n\t\t\t\tattr(button, \"aria-selected\", button_aria_selected_value);\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*tabNodes*/ 4 && button_disabled_value !== (button_disabled_value = /*tab*/ ctx[17].disabled)) {\n\t\t\t\tbutton.disabled = button_disabled_value;\n\t\t\t}\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\t\t\ttransition_in(default_slot, local);\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\ttransition_out(default_slot, local);\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\tif (detaching) detach(button);\n\t\t\tif (default_slot) default_slot.d(detaching);\n\t\t\tmounted = false;\n\t\t\trun_all(dispose);\n\t\t}\n\t};\n}\n\nfunction create_fragment$S(ctx) {\n\tlet if_block_anchor;\n\tlet current;\n\tlet if_block = /*shouldRender*/ ctx[4] && create_if_block$n(ctx);\n\n\treturn {\n\t\tc() {\n\t\t\tif (if_block) if_block.c();\n\t\t\tif_block_anchor = empty();\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tif (if_block) if_block.m(target, anchor);\n\t\t\tinsert(target, if_block_anchor, anchor);\n\t\t\tcurrent = true;\n\t\t},\n\t\tp(ctx, [dirty]) {\n\t\t\tif (/*shouldRender*/ ctx[4]) {\n\t\t\t\tif (if_block) {\n\t\t\t\t\tif_block.p(ctx, dirty);\n\n\t\t\t\t\tif (dirty & /*shouldRender*/ 16) {\n\t\t\t\t\t\ttransition_in(if_block, 1);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif_block = create_if_block$n(ctx);\n\t\t\t\t\tif_block.c();\n\t\t\t\t\ttransition_in(if_block, 1);\n\t\t\t\t\tif_block.m(if_block_anchor.parentNode, if_block_anchor);\n\t\t\t\t}\n\t\t\t} else if (if_block) {\n\t\t\t\tgroup_outros();\n\n\t\t\t\ttransition_out(if_block, 1, 1, () => {\n\t\t\t\t\tif_block = null;\n\t\t\t\t});\n\n\t\t\t\tcheck_outros();\n\t\t\t}\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\t\t\ttransition_in(if_block);\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\ttransition_out(if_block);\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\tif (if_block) if_block.d(detaching);\n\t\t\tif (detaching) detach(if_block_anchor);\n\t\t}\n\t};\n}\n\nfunction instance$T($$self, $$props, $$invalidate) {\n\tlet tabNodes;\n\tlet shouldRender;\n\tlet { $$slots: slots = {}, $$scope } = $$props;\n\tlet root;\n\tlet { class: klass = undefined } = $$props;\n\tlet { name } = $$props;\n\tlet { selected } = $$props;\n\tlet { tabs = [] } = $$props;\n\tlet { layout = undefined } = $$props;\n\tconst dispatch = createEventDispatcher();\n\n\tconst focusTab = index => {\n\t\tconst tab = root.querySelectorAll('[role=\"tab\"] button')[index];\n\t\tif (!tab) return;\n\t\ttab.focus();\n\t};\n\n\tconst handleClickTab = (e, id) => {\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t\tdispatch('select', id);\n\t};\n\n\tconst handleKeyTab = ({ key }, id) => {\n\t\tif (!(/arrow/i).test(key)) return;\n\t\tconst index = tabs.findIndex(tab => tab.id === id);\n\n\t\t// next\n\t\tif ((/right|down/i).test(key)) return focusTab(index < tabs.length - 1 ? index + 1 : 0);\n\n\t\t// prev\n\t\tif ((/left|up/i).test(key)) return focusTab(index > 0 ? index - 1 : tabs.length - 1);\n\t};\n\n\tconst keydown_handler = (tab, e) => handleKeyTab(e, tab.id);\n\tconst click_handler = (tab, e) => handleClickTab(e, tab.id);\n\n\tfunction div_binding($$value) {\n\t\tbinding_callbacks[$$value ? 'unshift' : 'push'](() => {\n\t\t\troot = $$value;\n\t\t\t$$invalidate(3, root);\n\t\t});\n\t}\n\n\t$$self.$$set = $$props => {\n\t\tif ('class' in $$props) $$invalidate(0, klass = $$props.class);\n\t\tif ('name' in $$props) $$invalidate(7, name = $$props.name);\n\t\tif ('selected' in $$props) $$invalidate(8, selected = $$props.selected);\n\t\tif ('tabs' in $$props) $$invalidate(9, tabs = $$props.tabs);\n\t\tif ('layout' in $$props) $$invalidate(1, layout = $$props.layout);\n\t\tif ('$$scope' in $$props) $$invalidate(10, $$scope = $$props.$$scope);\n\t};\n\n\t$$self.$$.update = () => {\n\t\tif ($$self.$$.dirty & /*tabs, selected, name*/ 896) {\n\t\t\t$$invalidate(2, tabNodes = tabs.map(tab => {\n\t\t\t\tconst isActive = tab.id === selected;\n\n\t\t\t\treturn {\n\t\t\t\t\t...tab,\n\t\t\t\t\ttabId: `tab-${name}-${tab.id}`,\n\t\t\t\t\thref: `#panel-${name}-${tab.id}`,\n\t\t\t\t\tselected: isActive\n\t\t\t\t};\n\t\t\t}));\n\t\t}\n\n\t\tif ($$self.$$.dirty & /*tabNodes*/ 4) {\n\t\t\t$$invalidate(4, shouldRender = tabNodes.length > 1);\n\t\t}\n\t};\n\n\treturn [\n\t\tklass,\n\t\tlayout,\n\t\ttabNodes,\n\t\troot,\n\t\tshouldRender,\n\t\thandleClickTab,\n\t\thandleKeyTab,\n\t\tname,\n\t\tselected,\n\t\ttabs,\n\t\t$$scope,\n\t\tslots,\n\t\tkeydown_handler,\n\t\tclick_handler,\n\t\tdiv_binding\n\t];\n}\n\nclass TabList extends SvelteComponent {\n\tconstructor(options) {\n\t\tsuper();\n\n\t\tinit(this, options, instance$T, create_fragment$S, safe_not_equal, {\n\t\t\tclass: 0,\n\t\t\tname: 7,\n\t\t\tselected: 8,\n\t\t\ttabs: 9,\n\t\t\tlayout: 1\n\t\t});\n\t}\n}\n\nvar styleable = (element, styles) => {\n if (!styles)\n return;\n const update = (styles) => {\n setElementStyles(element, styles);\n };\n update(styles);\n return {\n update,\n };\n};\n\n/* src/core/ui/components/TabPanels.svelte generated by Svelte v3.52.0 */\nconst get_default_slot_changes_1 = dirty => ({ panel: dirty & /*panelNodes*/ 16 });\n\nconst get_default_slot_context_1 = ctx => ({\n\tpanel: /*panelNodes*/ ctx[4][0].id,\n\tpanelIsActive: true\n});\n\nfunction get_each_context$7(ctx, list, i) {\n\tconst child_ctx = ctx.slice();\n\tchild_ctx[14] = list[i].id;\n\tchild_ctx[15] = list[i].shouldDraw;\n\tchild_ctx[16] = list[i].panelId;\n\tchild_ctx[17] = list[i].labelledBy;\n\tchild_ctx[18] = list[i].isActive;\n\tchild_ctx[19] = list[i].hidden;\n\tchild_ctx[3] = list[i].visible;\n\treturn child_ctx;\n}\n\nconst get_default_slot_changes = dirty => ({\n\tpanel: dirty & /*panelNodes*/ 16,\n\tpanelIsActive: dirty & /*panelNodes*/ 16\n});\n\nconst get_default_slot_context = ctx => ({\n\tpanel: /*id*/ ctx[14],\n\tpanelIsActive: /*isActive*/ ctx[18]\n});\n\n// (63:0) {:else}\nfunction create_else_block$b(ctx) {\n\tlet div1;\n\tlet div0;\n\tlet div0_class_value;\n\tlet styleable_action;\n\tlet current;\n\tlet mounted;\n\tlet dispose;\n\tconst default_slot_template = /*#slots*/ ctx[11].default;\n\tconst default_slot = create_slot(default_slot_template, ctx, /*$$scope*/ ctx[10], get_default_slot_context_1);\n\n\treturn {\n\t\tc() {\n\t\t\tdiv1 = element(\"div\");\n\t\t\tdiv0 = element(\"div\");\n\t\t\tif (default_slot) default_slot.c();\n\t\t\tattr(div0, \"class\", div0_class_value = arrayJoin([/*panelClass*/ ctx[1]]));\n\t\t\tattr(div1, \"class\", /*klass*/ ctx[0]);\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tinsert(target, div1, anchor);\n\t\t\tappend(div1, div0);\n\n\t\t\tif (default_slot) {\n\t\t\t\tdefault_slot.m(div0, null);\n\t\t\t}\n\n\t\t\tcurrent = true;\n\n\t\t\tif (!mounted) {\n\t\t\t\tdispose = [\n\t\t\t\t\taction_destroyer(styleable_action = styleable.call(null, div1, /*style*/ ctx[2])),\n\t\t\t\t\tlisten(div1, \"measure\", /*measure_handler_1*/ ctx[13]),\n\t\t\t\t\taction_destroyer(measurable.call(null, div1))\n\t\t\t\t];\n\n\t\t\t\tmounted = true;\n\t\t\t}\n\t\t},\n\t\tp(ctx, dirty) {\n\t\t\tif (default_slot) {\n\t\t\t\tif (default_slot.p && (!current || dirty & /*$$scope, panelNodes*/ 1040)) {\n\t\t\t\t\tupdate_slot_base(\n\t\t\t\t\t\tdefault_slot,\n\t\t\t\t\t\tdefault_slot_template,\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t/*$$scope*/ ctx[10],\n\t\t\t\t\t\t!current\n\t\t\t\t\t\t? get_all_dirty_from_scope(/*$$scope*/ ctx[10])\n\t\t\t\t\t\t: get_slot_changes(default_slot_template, /*$$scope*/ ctx[10], dirty, get_default_slot_changes_1),\n\t\t\t\t\t\tget_default_slot_context_1\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*panelClass*/ 2 && div0_class_value !== (div0_class_value = arrayJoin([/*panelClass*/ ctx[1]]))) {\n\t\t\t\tattr(div0, \"class\", div0_class_value);\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*klass*/ 1) {\n\t\t\t\tattr(div1, \"class\", /*klass*/ ctx[0]);\n\t\t\t}\n\n\t\t\tif (styleable_action && is_function(styleable_action.update) && dirty & /*style*/ 4) styleable_action.update.call(null, /*style*/ ctx[2]);\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\t\t\ttransition_in(default_slot, local);\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\ttransition_out(default_slot, local);\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\tif (detaching) detach(div1);\n\t\t\tif (default_slot) default_slot.d(detaching);\n\t\t\tmounted = false;\n\t\t\trun_all(dispose);\n\t\t}\n\t};\n}\n\n// (42:0) {#if shouldRender}\nfunction create_if_block$m(ctx) {\n\tlet div;\n\tlet each_blocks = [];\n\tlet each_1_lookup = new Map();\n\tlet div_class_value;\n\tlet styleable_action;\n\tlet current;\n\tlet mounted;\n\tlet dispose;\n\tlet each_value = /*panelNodes*/ ctx[4];\n\tconst get_key = ctx => /*id*/ ctx[14];\n\n\tfor (let i = 0; i < each_value.length; i += 1) {\n\t\tlet child_ctx = get_each_context$7(ctx, each_value, i);\n\t\tlet key = get_key(child_ctx);\n\t\teach_1_lookup.set(key, each_blocks[i] = create_each_block$7(key, child_ctx));\n\t}\n\n\treturn {\n\t\tc() {\n\t\t\tdiv = element(\"div\");\n\n\t\t\tfor (let i = 0; i < each_blocks.length; i += 1) {\n\t\t\t\teach_blocks[i].c();\n\t\t\t}\n\n\t\t\tattr(div, \"class\", div_class_value = arrayJoin(['PinturaTabPanels', /*klass*/ ctx[0]]));\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tinsert(target, div, anchor);\n\n\t\t\tfor (let i = 0; i < each_blocks.length; i += 1) {\n\t\t\t\teach_blocks[i].m(div, null);\n\t\t\t}\n\n\t\t\tcurrent = true;\n\n\t\t\tif (!mounted) {\n\t\t\t\tdispose = [\n\t\t\t\t\taction_destroyer(styleable_action = styleable.call(null, div, /*style*/ ctx[2])),\n\t\t\t\t\tlisten(div, \"measure\", /*measure_handler*/ ctx[12]),\n\t\t\t\t\taction_destroyer(measurable.call(null, div, { observePosition: true }))\n\t\t\t\t];\n\n\t\t\t\tmounted = true;\n\t\t\t}\n\t\t},\n\t\tp(ctx, dirty) {\n\t\t\tif (dirty & /*arrayJoin, panelClass, panelNodes, $$scope*/ 1042) {\n\t\t\t\teach_value = /*panelNodes*/ ctx[4];\n\t\t\t\tgroup_outros();\n\t\t\t\teach_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, div, outro_and_destroy_block, create_each_block$7, null, get_each_context$7);\n\t\t\t\tcheck_outros();\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*klass*/ 1 && div_class_value !== (div_class_value = arrayJoin(['PinturaTabPanels', /*klass*/ ctx[0]]))) {\n\t\t\t\tattr(div, \"class\", div_class_value);\n\t\t\t}\n\n\t\t\tif (styleable_action && is_function(styleable_action.update) && dirty & /*style*/ 4) styleable_action.update.call(null, /*style*/ ctx[2]);\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\n\t\t\tfor (let i = 0; i < each_value.length; i += 1) {\n\t\t\t\ttransition_in(each_blocks[i]);\n\t\t\t}\n\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\tfor (let i = 0; i < each_blocks.length; i += 1) {\n\t\t\t\ttransition_out(each_blocks[i]);\n\t\t\t}\n\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\tif (detaching) detach(div);\n\n\t\t\tfor (let i = 0; i < each_blocks.length; i += 1) {\n\t\t\t\teach_blocks[i].d();\n\t\t\t}\n\n\t\t\tmounted = false;\n\t\t\trun_all(dispose);\n\t\t}\n\t};\n}\n\n// (59:16) {#if shouldDraw}\nfunction create_if_block_1$h(ctx) {\n\tlet current;\n\tconst default_slot_template = /*#slots*/ ctx[11].default;\n\tconst default_slot = create_slot(default_slot_template, ctx, /*$$scope*/ ctx[10], get_default_slot_context);\n\n\treturn {\n\t\tc() {\n\t\t\tif (default_slot) default_slot.c();\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tif (default_slot) {\n\t\t\t\tdefault_slot.m(target, anchor);\n\t\t\t}\n\n\t\t\tcurrent = true;\n\t\t},\n\t\tp(ctx, dirty) {\n\t\t\tif (default_slot) {\n\t\t\t\tif (default_slot.p && (!current || dirty & /*$$scope, panelNodes*/ 1040)) {\n\t\t\t\t\tupdate_slot_base(\n\t\t\t\t\t\tdefault_slot,\n\t\t\t\t\t\tdefault_slot_template,\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t/*$$scope*/ ctx[10],\n\t\t\t\t\t\t!current\n\t\t\t\t\t\t? get_all_dirty_from_scope(/*$$scope*/ ctx[10])\n\t\t\t\t\t\t: get_slot_changes(default_slot_template, /*$$scope*/ ctx[10], dirty, get_default_slot_changes),\n\t\t\t\t\t\tget_default_slot_context\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\t\t\ttransition_in(default_slot, local);\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\ttransition_out(default_slot, local);\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\tif (default_slot) default_slot.d(detaching);\n\t\t}\n\t};\n}\n\n// (50:8) {#each panelNodes as { id, shouldDraw, panelId, labelledBy, isActive, hidden, visible }\nfunction create_each_block$7(key_1, ctx) {\n\tlet div;\n\tlet t;\n\tlet div_class_value;\n\tlet div_hidden_value;\n\tlet div_id_value;\n\tlet div_aria_labelledby_value;\n\tlet div_data_inert_value;\n\tlet current;\n\tlet if_block = /*shouldDraw*/ ctx[15] && create_if_block_1$h(ctx);\n\n\treturn {\n\t\tkey: key_1,\n\t\tfirst: null,\n\t\tc() {\n\t\t\tdiv = element(\"div\");\n\t\t\tif (if_block) if_block.c();\n\t\t\tt = space();\n\t\t\tattr(div, \"class\", div_class_value = arrayJoin(['PinturaTabPanel', /*panelClass*/ ctx[1]]));\n\t\t\tdiv.hidden = div_hidden_value = /*hidden*/ ctx[19];\n\t\t\tattr(div, \"id\", div_id_value = /*panelId*/ ctx[16]);\n\t\t\tattr(div, \"aria-labelledby\", div_aria_labelledby_value = /*labelledBy*/ ctx[17]);\n\t\t\tattr(div, \"data-inert\", div_data_inert_value = !/*visible*/ ctx[3]);\n\t\t\tattr(div, \"role\", \"tabpanel\");\n\t\t\tthis.first = div;\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tinsert(target, div, anchor);\n\t\t\tif (if_block) if_block.m(div, null);\n\t\t\tappend(div, t);\n\t\t\tcurrent = true;\n\t\t},\n\t\tp(new_ctx, dirty) {\n\t\t\tctx = new_ctx;\n\n\t\t\tif (/*shouldDraw*/ ctx[15]) {\n\t\t\t\tif (if_block) {\n\t\t\t\t\tif_block.p(ctx, dirty);\n\n\t\t\t\t\tif (dirty & /*panelNodes*/ 16) {\n\t\t\t\t\t\ttransition_in(if_block, 1);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif_block = create_if_block_1$h(ctx);\n\t\t\t\t\tif_block.c();\n\t\t\t\t\ttransition_in(if_block, 1);\n\t\t\t\t\tif_block.m(div, t);\n\t\t\t\t}\n\t\t\t} else if (if_block) {\n\t\t\t\tgroup_outros();\n\n\t\t\t\ttransition_out(if_block, 1, 1, () => {\n\t\t\t\t\tif_block = null;\n\t\t\t\t});\n\n\t\t\t\tcheck_outros();\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*panelClass*/ 2 && div_class_value !== (div_class_value = arrayJoin(['PinturaTabPanel', /*panelClass*/ ctx[1]]))) {\n\t\t\t\tattr(div, \"class\", div_class_value);\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*panelNodes*/ 16 && div_hidden_value !== (div_hidden_value = /*hidden*/ ctx[19])) {\n\t\t\t\tdiv.hidden = div_hidden_value;\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*panelNodes*/ 16 && div_id_value !== (div_id_value = /*panelId*/ ctx[16])) {\n\t\t\t\tattr(div, \"id\", div_id_value);\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*panelNodes*/ 16 && div_aria_labelledby_value !== (div_aria_labelledby_value = /*labelledBy*/ ctx[17])) {\n\t\t\t\tattr(div, \"aria-labelledby\", div_aria_labelledby_value);\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*panelNodes*/ 16 && div_data_inert_value !== (div_data_inert_value = !/*visible*/ ctx[3])) {\n\t\t\t\tattr(div, \"data-inert\", div_data_inert_value);\n\t\t\t}\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\t\t\ttransition_in(if_block);\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\ttransition_out(if_block);\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\tif (detaching) detach(div);\n\t\t\tif (if_block) if_block.d();\n\t\t}\n\t};\n}\n\nfunction create_fragment$R(ctx) {\n\tlet current_block_type_index;\n\tlet if_block;\n\tlet if_block_anchor;\n\tlet current;\n\tconst if_block_creators = [create_if_block$m, create_else_block$b];\n\tconst if_blocks = [];\n\n\tfunction select_block_type(ctx, dirty) {\n\t\tif (/*shouldRender*/ ctx[5]) return 0;\n\t\treturn 1;\n\t}\n\n\tcurrent_block_type_index = select_block_type(ctx);\n\tif_block = if_blocks[current_block_type_index] = if_block_creators[current_block_type_index](ctx);\n\n\treturn {\n\t\tc() {\n\t\t\tif_block.c();\n\t\t\tif_block_anchor = empty();\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tif_blocks[current_block_type_index].m(target, anchor);\n\t\t\tinsert(target, if_block_anchor, anchor);\n\t\t\tcurrent = true;\n\t\t},\n\t\tp(ctx, [dirty]) {\n\t\t\tlet previous_block_index = current_block_type_index;\n\t\t\tcurrent_block_type_index = select_block_type(ctx);\n\n\t\t\tif (current_block_type_index === previous_block_index) {\n\t\t\t\tif_blocks[current_block_type_index].p(ctx, dirty);\n\t\t\t} else {\n\t\t\t\tgroup_outros();\n\n\t\t\t\ttransition_out(if_blocks[previous_block_index], 1, 1, () => {\n\t\t\t\t\tif_blocks[previous_block_index] = null;\n\t\t\t\t});\n\n\t\t\t\tcheck_outros();\n\t\t\t\tif_block = if_blocks[current_block_type_index];\n\n\t\t\t\tif (!if_block) {\n\t\t\t\t\tif_block = if_blocks[current_block_type_index] = if_block_creators[current_block_type_index](ctx);\n\t\t\t\t\tif_block.c();\n\t\t\t\t} else {\n\t\t\t\t\tif_block.p(ctx, dirty);\n\t\t\t\t}\n\n\t\t\t\ttransition_in(if_block, 1);\n\t\t\t\tif_block.m(if_block_anchor.parentNode, if_block_anchor);\n\t\t\t}\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\t\t\ttransition_in(if_block);\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\ttransition_out(if_block);\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\tif_blocks[current_block_type_index].d(detaching);\n\t\t\tif (detaching) detach(if_block_anchor);\n\t\t}\n\t};\n}\n\nfunction instance$S($$self, $$props, $$invalidate) {\n\tlet panelNodes;\n\tlet shouldRender;\n\tlet { $$slots: slots = {}, $$scope } = $$props;\n\tlet { class: klass = undefined } = $$props;\n\tlet { name } = $$props;\n\tlet { selected } = $$props;\n\tlet { panelClass = undefined } = $$props;\n\tlet { panels = [] } = $$props;\n\tlet { visible = undefined } = $$props;\n\tlet { style = undefined } = $$props;\n\tconst drawCache = {};\n\n\tfunction measure_handler(event) {\n\t\tbubble.call(this, $$self, event);\n\t}\n\n\tfunction measure_handler_1(event) {\n\t\tbubble.call(this, $$self, event);\n\t}\n\n\t$$self.$$set = $$props => {\n\t\tif ('class' in $$props) $$invalidate(0, klass = $$props.class);\n\t\tif ('name' in $$props) $$invalidate(6, name = $$props.name);\n\t\tif ('selected' in $$props) $$invalidate(7, selected = $$props.selected);\n\t\tif ('panelClass' in $$props) $$invalidate(1, panelClass = $$props.panelClass);\n\t\tif ('panels' in $$props) $$invalidate(8, panels = $$props.panels);\n\t\tif ('visible' in $$props) $$invalidate(3, visible = $$props.visible);\n\t\tif ('style' in $$props) $$invalidate(2, style = $$props.style);\n\t\tif ('$$scope' in $$props) $$invalidate(10, $$scope = $$props.$$scope);\n\t};\n\n\t$$self.$$.update = () => {\n\t\tif ($$self.$$.dirty & /*panels, selected, visible, name, drawCache*/ 968) {\n\t\t\t$$invalidate(4, panelNodes = panels.map(id => {\n\t\t\t\tconst isActive = id === selected;\n\n\t\t\t\t// remember that this tab was active so we keep it in DOM even when it's inactive, prevents constructing the node tree on re-render\n\t\t\t\tif (isActive) $$invalidate(9, drawCache[id] = true, drawCache);\n\n\t\t\t\tconst isVisible = visible ? visible.indexOf(id) !== -1 : isActive;\n\n\t\t\t\treturn {\n\t\t\t\t\tid,\n\t\t\t\t\tpanelId: `panel-${name}-${id}`,\n\t\t\t\t\tlabelledBy: `tab-${name}-${id}`,\n\t\t\t\t\tisActive,\n\t\t\t\t\thidden: !isActive,\n\t\t\t\t\tvisible: isVisible,\n\t\t\t\t\tshouldDraw: isActive || drawCache[id]\n\t\t\t\t}; // remove tabindex for now as every tab has elements to focus\n\t\t\t\t// tabindex: isActive ? 0 : -1,\n\t\t\t}));\n\t\t}\n\n\t\tif ($$self.$$.dirty & /*panelNodes*/ 16) {\n\t\t\t$$invalidate(5, shouldRender = panelNodes.length > 1);\n\t\t}\n\t};\n\n\treturn [\n\t\tklass,\n\t\tpanelClass,\n\t\tstyle,\n\t\tvisible,\n\t\tpanelNodes,\n\t\tshouldRender,\n\t\tname,\n\t\tselected,\n\t\tpanels,\n\t\tdrawCache,\n\t\t$$scope,\n\t\tslots,\n\t\tmeasure_handler,\n\t\tmeasure_handler_1\n\t];\n}\n\nclass TabPanels extends SvelteComponent {\n\tconstructor(options) {\n\t\tsuper();\n\n\t\tinit(this, options, instance$S, create_fragment$R, safe_not_equal, {\n\t\t\tclass: 0,\n\t\t\tname: 6,\n\t\t\tselected: 7,\n\t\t\tpanelClass: 1,\n\t\t\tpanels: 8,\n\t\t\tvisible: 3,\n\t\t\tstyle: 2\n\t\t});\n\t}\n}\n\nvar getComponentExportedProps = (Component) => {\n const descriptors = Object.getOwnPropertyDescriptors(Component.prototype);\n return Object.keys(descriptors).filter((key) => !!descriptors[key]['get']);\n};\n\n/* src/core/ui/components/UtilPanel.svelte generated by Svelte v3.52.0 */\n\nfunction create_if_block$l(ctx) {\n\tlet switch_instance;\n\tlet updating_name;\n\tlet switch_instance_anchor;\n\tlet current;\n\tconst switch_instance_spread_levels = [/*componentProps*/ ctx[5], { locale: /*locale*/ ctx[1] }];\n\n\tfunction switch_instance_name_binding(value) {\n\t\t/*switch_instance_name_binding*/ ctx[22](value);\n\t}\n\n\tvar switch_value = /*ComponentView*/ ctx[10];\n\n\tfunction switch_props(ctx) {\n\t\tlet switch_instance_props = {};\n\n\t\tfor (let i = 0; i < switch_instance_spread_levels.length; i += 1) {\n\t\t\tswitch_instance_props = assign(switch_instance_props, switch_instance_spread_levels[i]);\n\t\t}\n\n\t\tif (/*panelName*/ ctx[3] !== void 0) {\n\t\t\tswitch_instance_props.name = /*panelName*/ ctx[3];\n\t\t}\n\n\t\treturn { props: switch_instance_props };\n\t}\n\n\tif (switch_value) {\n\t\tswitch_instance = construct_svelte_component(switch_value, switch_props(ctx));\n\t\tbinding_callbacks.push(() => bind(switch_instance, 'name', switch_instance_name_binding));\n\t\t/*switch_instance_binding*/ ctx[23](switch_instance);\n\t\tswitch_instance.$on(\"measure\", /*handleMeasure*/ ctx[11]);\n\t}\n\n\treturn {\n\t\tc() {\n\t\t\tif (switch_instance) create_component(switch_instance.$$.fragment);\n\t\t\tswitch_instance_anchor = empty();\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tif (switch_instance) mount_component(switch_instance, target, anchor);\n\t\t\tinsert(target, switch_instance_anchor, anchor);\n\t\t\tcurrent = true;\n\t\t},\n\t\tp(ctx, dirty) {\n\t\t\tconst switch_instance_changes = (dirty[0] & /*componentProps, locale*/ 34)\n\t\t\t? get_spread_update(switch_instance_spread_levels, [\n\t\t\t\t\tdirty[0] & /*componentProps*/ 32 && get_spread_object(/*componentProps*/ ctx[5]),\n\t\t\t\t\tdirty[0] & /*locale*/ 2 && { locale: /*locale*/ ctx[1] }\n\t\t\t\t])\n\t\t\t: {};\n\n\t\t\tif (!updating_name && dirty[0] & /*panelName*/ 8) {\n\t\t\t\tupdating_name = true;\n\t\t\t\tswitch_instance_changes.name = /*panelName*/ ctx[3];\n\t\t\t\tadd_flush_callback(() => updating_name = false);\n\t\t\t}\n\n\t\t\tif (switch_value !== (switch_value = /*ComponentView*/ ctx[10])) {\n\t\t\t\tif (switch_instance) {\n\t\t\t\t\tgroup_outros();\n\t\t\t\t\tconst old_component = switch_instance;\n\n\t\t\t\t\ttransition_out(old_component.$$.fragment, 1, 0, () => {\n\t\t\t\t\t\tdestroy_component(old_component, 1);\n\t\t\t\t\t});\n\n\t\t\t\t\tcheck_outros();\n\t\t\t\t}\n\n\t\t\t\tif (switch_value) {\n\t\t\t\t\tswitch_instance = construct_svelte_component(switch_value, switch_props(ctx));\n\t\t\t\t\tbinding_callbacks.push(() => bind(switch_instance, 'name', switch_instance_name_binding));\n\t\t\t\t\t/*switch_instance_binding*/ ctx[23](switch_instance);\n\t\t\t\t\tswitch_instance.$on(\"measure\", /*handleMeasure*/ ctx[11]);\n\t\t\t\t\tcreate_component(switch_instance.$$.fragment);\n\t\t\t\t\ttransition_in(switch_instance.$$.fragment, 1);\n\t\t\t\t\tmount_component(switch_instance, switch_instance_anchor.parentNode, switch_instance_anchor);\n\t\t\t\t} else {\n\t\t\t\t\tswitch_instance = null;\n\t\t\t\t}\n\t\t\t} else if (switch_value) {\n\t\t\t\tswitch_instance.$set(switch_instance_changes);\n\t\t\t}\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\t\t\tif (switch_instance) transition_in(switch_instance.$$.fragment, local);\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\tif (switch_instance) transition_out(switch_instance.$$.fragment, local);\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\t/*switch_instance_binding*/ ctx[23](null);\n\t\t\tif (detaching) detach(switch_instance_anchor);\n\t\t\tif (switch_instance) destroy_component(switch_instance, detaching);\n\t\t}\n\t};\n}\n\nfunction create_fragment$Q(ctx) {\n\tlet div;\n\tlet div_class_value;\n\tlet current;\n\tlet if_block = /*isLocal*/ ctx[6] && create_if_block$l(ctx);\n\n\treturn {\n\t\tc() {\n\t\t\tdiv = element(\"div\");\n\t\t\tif (if_block) if_block.c();\n\t\t\tattr(div, \"data-util\", /*panelName*/ ctx[3]);\n\t\t\tattr(div, \"class\", div_class_value = arrayJoin(['PinturaUtilPanel', /*klass*/ ctx[2]]));\n\t\t\tset_style(div, \"opacity\", /*styleOpacity*/ ctx[7]);\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tinsert(target, div, anchor);\n\t\t\tif (if_block) if_block.m(div, null);\n\t\t\t/*div_binding*/ ctx[24](div);\n\t\t\tcurrent = true;\n\t\t},\n\t\tp(ctx, dirty) {\n\t\t\tif (/*isLocal*/ ctx[6]) {\n\t\t\t\tif (if_block) {\n\t\t\t\t\tif_block.p(ctx, dirty);\n\n\t\t\t\t\tif (dirty[0] & /*isLocal*/ 64) {\n\t\t\t\t\t\ttransition_in(if_block, 1);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif_block = create_if_block$l(ctx);\n\t\t\t\t\tif_block.c();\n\t\t\t\t\ttransition_in(if_block, 1);\n\t\t\t\t\tif_block.m(div, null);\n\t\t\t\t}\n\t\t\t} else if (if_block) {\n\t\t\t\tgroup_outros();\n\n\t\t\t\ttransition_out(if_block, 1, 1, () => {\n\t\t\t\t\tif_block = null;\n\t\t\t\t});\n\n\t\t\t\tcheck_outros();\n\t\t\t}\n\n\t\t\tif (!current || dirty[0] & /*panelName*/ 8) {\n\t\t\t\tattr(div, \"data-util\", /*panelName*/ ctx[3]);\n\t\t\t}\n\n\t\t\tif (!current || dirty[0] & /*klass*/ 4 && div_class_value !== (div_class_value = arrayJoin(['PinturaUtilPanel', /*klass*/ ctx[2]]))) {\n\t\t\t\tattr(div, \"class\", div_class_value);\n\t\t\t}\n\n\t\t\tif (dirty[0] & /*styleOpacity*/ 128) {\n\t\t\t\tset_style(div, \"opacity\", /*styleOpacity*/ ctx[7]);\n\t\t\t}\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\t\t\ttransition_in(if_block);\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\ttransition_out(if_block);\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\tif (detaching) detach(div);\n\t\t\tif (if_block) if_block.d();\n\t\t\t/*div_binding*/ ctx[24](null);\n\t\t}\n\t};\n}\n\nfunction instance$R($$self, $$props, $$invalidate) {\n\tlet styleOpacity;\n\tlet componentProps;\n\tlet isLocal;\n\tlet $opacityClamped;\n\tlet $isAnimated;\n\tconst dispatch = createEventDispatcher();\n\tlet { isActive = true } = $$props;\n\tlet { stores } = $$props;\n\tlet { view } = $$props;\n\tlet { props } = $$props;\n\tlet { component } = $$props;\n\tlet { locale } = $$props;\n\tlet { class: klass = undefined } = $$props;\n\n\t// context\n\tconst isAnimated = getContext('isAnimated');\n\n\tcomponent_subscribe($$self, isAnimated, value => $$invalidate(21, $isAnimated = value));\n\n\t// we remember the view rect in this variable\n\tlet rect;\n\n\tconst opacity = spring(0);\n\tconst opacityClamped = derived(opacity, $opacity => clamp($opacity, 0, 1));\n\tcomponent_subscribe($$self, opacityClamped, value => $$invalidate(20, $opacityClamped = value));\n\n\t// throw hide / show events\n\tlet isHidden = !isActive;\n\n\t// create active store so can be used in derived stores\n\tconst isActivePrivateStore = writable(isActive);\n\n\tconst isActiveStore = derived(\n\t\tisActivePrivateStore,\n\t\tasync ($isActivePrivateStore, set) => {\n\t\t\t// set to false immidiately\n\t\t\tif (!$isActivePrivateStore) return set($isActivePrivateStore);\n\n\t\t\t// wait for next tick when setting to true, this helps deactivating\n\t\t\t// current panel before activing next one\n\t\t\tawait tick$1();\n\n\t\t\tset($isActivePrivateStore);\n\t\t},\n\t\t// needs to be fale initially for the above to work\n\t\tfalse\n\t);\n\n\tconst isActiveFractionStore = derived(opacityClamped, $opacityClamped => $opacityClamped);\n\tconst isVisibleStore = derived(opacityClamped, $opacityClamped => $opacityClamped > 0);\n\n\tconst stateProps = {\n\t\t// derived store to make isActive private\n\t\tisActive: isActiveStore,\n\t\tisActiveFraction: isActiveFractionStore,\n\t\tisVisible: isVisibleStore\n\t};\n\n\t// build the component props\n\t// const ComponentView = content.view;\n\tconst ComponentView = view;\n\n\tconst componentExportedProps = getComponentExportedProps(ComponentView);\n\n\tconst componentComputedProps = Object.keys(props || {}).reduce(\n\t\t(computedProps, key) => {\n\t\t\t// const componentComputedProps = Object.keys(content.props || {}).reduce((computedProps, key) => {\n\t\t\tif (!componentExportedProps.includes(key)) return computedProps;\n\n\t\t\t// computedProps[key] = content.props[key];\n\t\t\tcomputedProps[key] = props[key];\n\n\t\t\treturn computedProps;\n\t\t},\n\t\t{}\n\t);\n\n\tconst componentComputedStateProps = Object.keys(stateProps).reduce(\n\t\t(computedStateProps, key) => {\n\t\t\tif (!componentExportedProps.includes(key)) return computedStateProps;\n\t\t\tcomputedStateProps[key] = stateProps[key];\n\t\t\treturn computedStateProps;\n\t\t},\n\t\t{}\n\t);\n\n\t// class used on panel element\n\tlet panelName;\n\n\t// used when loading external plugin\n\tlet rootElement;\n\n\tconst handleMeasure = e => {\n\t\t// not fully hidden, so store rect for next render\n\t\t$$invalidate(17, rect = { ...e.detail });\n\n\t\tif (!hasBeenMounted || !isActive) return;\n\t\tdispatch('measure', { ...rect });\n\t};\n\n\t// we expose measurable in the context map\n\tsetContext('measurable', measurable);\n\n\t// we get all context so we can pass them to external plugins\n\tconst contextMap = getAllContexts();\n\n\t// we use the `hasBeenMounted` bool to block rect updates until the entire panel is ready\n\tlet hasBeenMounted = false;\n\n\tonMount(() => {\n\t\t$$invalidate(19, hasBeenMounted = true);\n\n\t\t// no special needs for local modules\n\t\tif (isLocal) return;\n\n\t\t$$invalidate(0, component = new ComponentView({\n\t\t\t\ttarget: rootElement,\n\t\t\t\t// pass custom props to external plugin\n\t\t\t\tprops: { ...componentProps, locale },\n\t\t\t\t// pass context to external plugin\n\t\t\t\tcontext: contextMap\n\t\t\t}));\n\n\t\t$$invalidate(3, panelName = component.name);\n\t\tconst unsub = component.$on('measure', handleMeasure);\n\n\t\treturn () => {\n\t\t\tunsub();\n\t\t\tcomponent.$destroy();\n\t\t};\n\t});\n\n\tfunction switch_instance_name_binding(value) {\n\t\tpanelName = value;\n\t\t$$invalidate(3, panelName);\n\t}\n\n\tfunction switch_instance_binding($$value) {\n\t\tbinding_callbacks[$$value ? 'unshift' : 'push'](() => {\n\t\t\tcomponent = $$value;\n\t\t\t$$invalidate(0, component);\n\t\t});\n\t}\n\n\tfunction div_binding($$value) {\n\t\tbinding_callbacks[$$value ? 'unshift' : 'push'](() => {\n\t\t\trootElement = $$value;\n\t\t\t$$invalidate(4, rootElement);\n\t\t});\n\t}\n\n\t$$self.$$set = $$props => {\n\t\tif ('isActive' in $$props) $$invalidate(12, isActive = $$props.isActive);\n\t\tif ('stores' in $$props) $$invalidate(13, stores = $$props.stores);\n\t\tif ('view' in $$props) $$invalidate(14, view = $$props.view);\n\t\tif ('props' in $$props) $$invalidate(15, props = $$props.props);\n\t\tif ('component' in $$props) $$invalidate(0, component = $$props.component);\n\t\tif ('locale' in $$props) $$invalidate(1, locale = $$props.locale);\n\t\tif ('class' in $$props) $$invalidate(2, klass = $$props.class);\n\t};\n\n\t$$self.$$.update = () => {\n\t\tif ($$self.$$.dirty[0] & /*rect, isActive, component*/ 135169) {\n\t\t\t// when the view rect changes and the panel is in active state or became active, dispatch measure event\n\t\t\tif (rect && isActive && component) dispatch('measure', rect);\n\t\t}\n\n\t\tif ($$self.$$.dirty[0] & /*isActive, $isAnimated*/ 2101248) {\n\t\t\topacity.set(isActive ? 1 : 0, { hard: $isAnimated === false });\n\t\t}\n\n\t\tif ($$self.$$.dirty[0] & /*$opacityClamped, isHidden*/ 1310720) {\n\t\t\tif ($opacityClamped <= 0 && !isHidden) {\n\t\t\t\t$$invalidate(18, isHidden = true);\n\t\t\t} else if ($opacityClamped > 0 && isHidden) {\n\t\t\t\t$$invalidate(18, isHidden = false);\n\t\t\t}\n\t\t}\n\n\t\tif ($$self.$$.dirty[0] & /*hasBeenMounted, isHidden*/ 786432) {\n\t\t\thasBeenMounted && dispatch(isHidden ? 'hide' : 'show');\n\t\t}\n\n\t\tif ($$self.$$.dirty[0] & /*$opacityClamped*/ 1048576) {\n\t\t\tdispatch('fade', $opacityClamped);\n\t\t}\n\n\t\tif ($$self.$$.dirty[0] & /*$opacityClamped*/ 1048576) {\n\t\t\t// only set opacity prop if is below 0\n\t\t\t$$invalidate(7, styleOpacity = $opacityClamped);\n\t\t}\n\n\t\tif ($$self.$$.dirty[0] & /*isActive*/ 4096) {\n\t\t\tisActivePrivateStore.set(isActive);\n\t\t}\n\n\t\tif ($$self.$$.dirty[0] & /*stores*/ 8192) {\n\t\t\t$$invalidate(5, componentProps = {\n\t\t\t\t...componentComputedProps,\n\t\t\t\t...componentComputedStateProps,\n\t\t\t\tstores\n\t\t\t});\n\t\t}\n\t};\n\n\t$$invalidate(6, isLocal = !componentExportedProps.includes('external'));\n\n\treturn [\n\t\tcomponent,\n\t\tlocale,\n\t\tklass,\n\t\tpanelName,\n\t\trootElement,\n\t\tcomponentProps,\n\t\tisLocal,\n\t\tstyleOpacity,\n\t\tisAnimated,\n\t\topacityClamped,\n\t\tComponentView,\n\t\thandleMeasure,\n\t\tisActive,\n\t\tstores,\n\t\tview,\n\t\tprops,\n\t\topacity,\n\t\trect,\n\t\tisHidden,\n\t\thasBeenMounted,\n\t\t$opacityClamped,\n\t\t$isAnimated,\n\t\tswitch_instance_name_binding,\n\t\tswitch_instance_binding,\n\t\tdiv_binding\n\t];\n}\n\nclass UtilPanel extends SvelteComponent {\n\tconstructor(options) {\n\t\tsuper();\n\n\t\tinit(\n\t\t\tthis,\n\t\t\toptions,\n\t\t\tinstance$R,\n\t\t\tcreate_fragment$Q,\n\t\t\tsafe_not_equal,\n\t\t\t{\n\t\t\t\tisActive: 12,\n\t\t\t\tstores: 13,\n\t\t\t\tview: 14,\n\t\t\t\tprops: 15,\n\t\t\t\tcomponent: 0,\n\t\t\t\tlocale: 1,\n\t\t\t\tclass: 2,\n\t\t\t\topacity: 16\n\t\t\t},\n\t\t\tnull,\n\t\t\t[-1, -1]\n\t\t);\n\t}\n\n\tget opacity() {\n\t\treturn this.$$.ctx[16];\n\t}\n}\n\n/* src/core/ui/components/Icon.svelte generated by Svelte v3.52.0 */\n\nfunction create_fragment$P(ctx) {\n\tlet svg;\n\tlet svg_viewBox_value;\n\tlet styleable_action;\n\tlet current;\n\tlet mounted;\n\tlet dispose;\n\tconst default_slot_template = /*#slots*/ ctx[5].default;\n\tconst default_slot = create_slot(default_slot_template, ctx, /*$$scope*/ ctx[4], null);\n\n\treturn {\n\t\tc() {\n\t\t\tsvg = svg_element(\"svg\");\n\t\t\tif (default_slot) default_slot.c();\n\t\t\tattr(svg, \"class\", /*klass*/ ctx[3]);\n\t\t\tattr(svg, \"width\", /*width*/ ctx[0]);\n\t\t\tattr(svg, \"height\", /*height*/ ctx[1]);\n\t\t\tattr(svg, \"viewBox\", svg_viewBox_value = \"0 0 \" + /*width*/ ctx[0] + \"\\n \" + /*height*/ ctx[1]);\n\t\t\tattr(svg, \"xmlns\", \"http://www.w3.org/2000/svg\");\n\t\t\tattr(svg, \"aria-hidden\", \"true\");\n\t\t\tattr(svg, \"focusable\", \"false\");\n\t\t\tattr(svg, \"stroke-linecap\", \"round\");\n\t\t\tattr(svg, \"stroke-linejoin\", \"round\");\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tinsert(target, svg, anchor);\n\n\t\t\tif (default_slot) {\n\t\t\t\tdefault_slot.m(svg, null);\n\t\t\t}\n\n\t\t\tcurrent = true;\n\n\t\t\tif (!mounted) {\n\t\t\t\tdispose = action_destroyer(styleable_action = styleable.call(null, svg, /*style*/ ctx[2]));\n\t\t\t\tmounted = true;\n\t\t\t}\n\t\t},\n\t\tp(ctx, [dirty]) {\n\t\t\tif (default_slot) {\n\t\t\t\tif (default_slot.p && (!current || dirty & /*$$scope*/ 16)) {\n\t\t\t\t\tupdate_slot_base(\n\t\t\t\t\t\tdefault_slot,\n\t\t\t\t\t\tdefault_slot_template,\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t/*$$scope*/ ctx[4],\n\t\t\t\t\t\t!current\n\t\t\t\t\t\t? get_all_dirty_from_scope(/*$$scope*/ ctx[4])\n\t\t\t\t\t\t: get_slot_changes(default_slot_template, /*$$scope*/ ctx[4], dirty, null),\n\t\t\t\t\t\tnull\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*klass*/ 8) {\n\t\t\t\tattr(svg, \"class\", /*klass*/ ctx[3]);\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*width*/ 1) {\n\t\t\t\tattr(svg, \"width\", /*width*/ ctx[0]);\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*height*/ 2) {\n\t\t\t\tattr(svg, \"height\", /*height*/ ctx[1]);\n\t\t\t}\n\n\t\t\tif (!current || dirty & /*width, height*/ 3 && svg_viewBox_value !== (svg_viewBox_value = \"0 0 \" + /*width*/ ctx[0] + \"\\n \" + /*height*/ ctx[1])) {\n\t\t\t\tattr(svg, \"viewBox\", svg_viewBox_value);\n\t\t\t}\n\n\t\t\tif (styleable_action && is_function(styleable_action.update) && dirty & /*style*/ 4) styleable_action.update.call(null, /*style*/ ctx[2]);\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\t\t\ttransition_in(default_slot, local);\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\ttransition_out(default_slot, local);\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\tif (detaching) detach(svg);\n\t\t\tif (default_slot) default_slot.d(detaching);\n\t\t\tmounted = false;\n\t\t\tdispose();\n\t\t}\n\t};\n}\n\nfunction instance$Q($$self, $$props, $$invalidate) {\n\tlet { $$slots: slots = {}, $$scope } = $$props;\n\tlet { width = 24 } = $$props;\n\tlet { height = 24 } = $$props;\n\tlet { style = undefined } = $$props;\n\tlet { class: klass = undefined } = $$props;\n\n\t$$self.$$set = $$props => {\n\t\tif ('width' in $$props) $$invalidate(0, width = $$props.width);\n\t\tif ('height' in $$props) $$invalidate(1, height = $$props.height);\n\t\tif ('style' in $$props) $$invalidate(2, style = $$props.style);\n\t\tif ('class' in $$props) $$invalidate(3, klass = $$props.class);\n\t\tif ('$$scope' in $$props) $$invalidate(4, $$scope = $$props.$$scope);\n\t};\n\n\treturn [width, height, style, klass, $$scope, slots];\n}\n\nclass Icon extends SvelteComponent {\n\tconstructor(options) {\n\t\tsuper();\n\t\tinit(this, options, instance$Q, create_fragment$P, safe_not_equal, { width: 0, height: 1, style: 2, class: 3 });\n\t}\n}\n\nvar _isEventTarget = (e, element) => element === e.target || element.contains(e.target);\n\nvar toKeyboardShortcut = (keys) => keys.map((key) => (key === 'CMD' ? (isMac() ? '⌘' : 'Ctrl') : key)).join('+');\n\n/**\n * @param { string } label\n * @param { string } title\n * @param { string[] } shortcut\n * @returns { string }\n */\nvar toTitle = (label, title, shortcut) => {\n const text = isString(title) ? title : label;\n if (shortcut)\n return `${text} (${toKeyboardShortcut(shortcut)})`;\n return text;\n};\n\n/* src/core/ui/components/ImageButton.svelte generated by Svelte v3.52.0 */\n\nconst { document: document_1$1 } = globals;\n\nfunction create_fragment$O(ctx) {\n\tlet t;\n\tlet button;\n\tlet mounted;\n\tlet dispose;\n\n\treturn {\n\t\tc() {\n\t\t\tt = space();\n\t\t\tbutton = element(\"button\");\n\t\t\tattr(button, \"class\", \"PinturaImageButton\");\n\t\t\tattr(button, \"type\", \"button\");\n\t\t\tattr(button, \"title\", /*title*/ ctx[1]);\n\t\t\tbutton.disabled = /*disabled*/ ctx[2];\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tinsert(target, t, anchor);\n\t\t\tinsert(target, button, anchor);\n\t\t\tbutton.innerHTML = /*html*/ ctx[0];\n\t\t\t/*button_binding*/ ctx[11](button);\n\n\t\t\tif (!mounted) {\n\t\t\t\tdispose = [\n\t\t\t\t\tlisten(document_1$1.body, \"load\", /*handleLoad*/ ctx[5], true),\n\t\t\t\t\tlisten(document_1$1.body, \"error\", /*handleError*/ ctx[6], true),\n\t\t\t\t\tlisten(button, \"pointerdown\", /*handleDown*/ ctx[4])\n\t\t\t\t];\n\n\t\t\t\tmounted = true;\n\t\t\t}\n\t\t},\n\t\tp(ctx, [dirty]) {\n\t\t\tif (dirty & /*html*/ 1) button.innerHTML = /*html*/ ctx[0];\n\t\t\tif (dirty & /*title*/ 2) {\n\t\t\t\tattr(button, \"title\", /*title*/ ctx[1]);\n\t\t\t}\n\n\t\t\tif (dirty & /*disabled*/ 4) {\n\t\t\t\tbutton.disabled = /*disabled*/ ctx[2];\n\t\t\t}\n\t\t},\n\t\ti: noop,\n\t\to: noop,\n\t\td(detaching) {\n\t\t\tif (detaching) detach(t);\n\t\t\tif (detaching) detach(button);\n\t\t\t/*button_binding*/ ctx[11](null);\n\t\t\tmounted = false;\n\t\t\trun_all(dispose);\n\t\t}\n\t};\n}\n\nfunction instance$P($$self, $$props, $$invalidate) {\n\tlet { html } = $$props;\n\tlet { title } = $$props;\n\tlet { onclick } = $$props;\n\tlet { disabled = false } = $$props;\n\tlet { ongrab = noop$1 } = $$props;\n\tlet { ondrag = noop$1 } = $$props;\n\tlet { ondrop = noop$1 } = $$props;\n\tlet element;\n\tconst isOverButton = e => vectorDistanceSquared(downPosition, vectorCreate(e.pageX, e.pageY)) < 256;\n\tlet downPosition;\n\n\tconst handleDown = e => {\n\t\tdownPosition = vectorCreate(e.pageX, e.pageY);\n\t\tongrab(e);\n\t\tdocument.documentElement.addEventListener('pointermove', handleMove);\n\t\tdocument.documentElement.addEventListener('pointerup', handleUp);\n\t};\n\n\tconst handleUp = e => {\n\t\tdocument.documentElement.removeEventListener('pointermove', handleMove);\n\t\tdocument.documentElement.removeEventListener('pointerup', handleUp);\n\t\tconst upPosition = vectorCreate(e.pageX, e.pageY);\n\n\t\t// must have moved enough distance to drop\n\t\tif (vectorDistanceSquared(downPosition, upPosition) < 32) return onclick(e);\n\n\t\t// cant drop in button\n\t\tif (isOverButton(e)) return;\n\n\t\t// was dragging\n\t\tondrop(e);\n\t};\n\n\tconst handleMove = e => {\n\t\t// must have moved enough distance from button\n\t\tif (isOverButton(e)) return;\n\n\t\t// dragging out of button\n\t\tondrag(e);\n\t};\n\n\t// image loading\n\tconst isImageTarget = target => element && element.contains(target) && target.nodeName === 'IMG';\n\n\tconst handleLoad = ({ target }) => {\n\t\tif (!isImageTarget(target)) return;\n\t\t$$invalidate(3, element.dataset.load = true, element);\n\t};\n\n\tconst handleError = ({ target }) => {\n\t\tif (!isImageTarget(target)) return;\n\t\t$$invalidate(3, element.dataset.error = true, element);\n\t};\n\n\tfunction button_binding($$value) {\n\t\tbinding_callbacks[$$value ? 'unshift' : 'push'](() => {\n\t\t\telement = $$value;\n\t\t\t$$invalidate(3, element);\n\t\t});\n\t}\n\n\t$$self.$$set = $$props => {\n\t\tif ('html' in $$props) $$invalidate(0, html = $$props.html);\n\t\tif ('title' in $$props) $$invalidate(1, title = $$props.title);\n\t\tif ('onclick' in $$props) $$invalidate(7, onclick = $$props.onclick);\n\t\tif ('disabled' in $$props) $$invalidate(2, disabled = $$props.disabled);\n\t\tif ('ongrab' in $$props) $$invalidate(8, ongrab = $$props.ongrab);\n\t\tif ('ondrag' in $$props) $$invalidate(9, ondrag = $$props.ondrag);\n\t\tif ('ondrop' in $$props) $$invalidate(10, ondrop = $$props.ondrop);\n\t};\n\n\t$$self.$$.update = () => {\n\t\tif ($$self.$$.dirty & /*element*/ 8) {\n\t\t\tif (element && element.querySelector('img')) $$invalidate(3, element.dataset.loader = true, element);\n\t\t}\n\t};\n\n\treturn [\n\t\thtml,\n\t\ttitle,\n\t\tdisabled,\n\t\telement,\n\t\thandleDown,\n\t\thandleLoad,\n\t\thandleError,\n\t\tonclick,\n\t\tongrab,\n\t\tondrag,\n\t\tondrop,\n\t\tbutton_binding\n\t];\n}\n\nclass ImageButton extends SvelteComponent {\n\tconstructor(options) {\n\t\tsuper();\n\n\t\tinit(this, options, instance$P, create_fragment$O, safe_not_equal, {\n\t\t\thtml: 0,\n\t\t\ttitle: 1,\n\t\t\tonclick: 7,\n\t\t\tdisabled: 2,\n\t\t\tongrab: 8,\n\t\t\tondrag: 9,\n\t\t\tondrop: 10\n\t\t});\n\t}\n}\n\n/* src/core/ui/components/ImageButtonList.svelte generated by Svelte v3.52.0 */\n\nfunction get_each_context$6(ctx, list, i) {\n\tconst child_ctx = ctx.slice();\n\tchild_ctx[13] = list[i];\n\treturn child_ctx;\n}\n\n// (23:4) {#each items as item (item.id)}\nfunction create_each_block$6(key_1, ctx) {\n\tlet li;\n\tlet imagebutton;\n\tlet t;\n\tlet didMountAction_action;\n\tlet current;\n\tlet mounted;\n\tlet dispose;\n\n\tfunction func() {\n\t\treturn /*func*/ ctx[9](/*item*/ ctx[13]);\n\t}\n\n\tfunction func_1(...args) {\n\t\treturn /*func_1*/ ctx[10](/*item*/ ctx[13], ...args);\n\t}\n\n\tfunction func_2(...args) {\n\t\treturn /*func_2*/ ctx[11](/*item*/ ctx[13], ...args);\n\t}\n\n\tfunction func_3(...args) {\n\t\treturn /*func_3*/ ctx[12](/*item*/ ctx[13], ...args);\n\t}\n\n\timagebutton = new ImageButton({\n\t\t\tprops: {\n\t\t\t\tonclick: func,\n\t\t\t\tongrab: func_1,\n\t\t\t\tondrag: func_2,\n\t\t\t\tondrop: func_3,\n\t\t\t\tdisabled: /*disabled*/ ctx[1] || /*item*/ ctx[13].disabled,\n\t\t\t\ttitle: /*item*/ ctx[13].title,\n\t\t\t\thtml: /*item*/ ctx[13].thumb\n\t\t\t}\n\t\t});\n\n\treturn {\n\t\tkey: key_1,\n\t\tfirst: null,\n\t\tc() {\n\t\t\tli = element(\"li\");\n\t\t\tcreate_component(imagebutton.$$.fragment);\n\t\t\tt = space();\n\t\t\tset_style(li, \"opacity\", /*$opacity*/ ctx[6]);\n\t\t\tthis.first = li;\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tinsert(target, li, anchor);\n\t\t\tmount_component(imagebutton, li, null);\n\t\t\tappend(li, t);\n\t\t\tcurrent = true;\n\n\t\t\tif (!mounted) {\n\t\t\t\tdispose = action_destroyer(didMountAction_action = /*didMountAction*/ ctx[8].call(null, li, /*item*/ ctx[13]));\n\t\t\t\tmounted = true;\n\t\t\t}\n\t\t},\n\t\tp(new_ctx, dirty) {\n\t\t\tctx = new_ctx;\n\t\t\tconst imagebutton_changes = {};\n\t\t\tif (dirty & /*onclickitem, items*/ 5) imagebutton_changes.onclick = func;\n\t\t\tif (dirty & /*ongrabitem, items*/ 9) imagebutton_changes.ongrab = func_1;\n\t\t\tif (dirty & /*ondragitem, items*/ 17) imagebutton_changes.ondrag = func_2;\n\t\t\tif (dirty & /*ondropitem, items*/ 33) imagebutton_changes.ondrop = func_3;\n\t\t\tif (dirty & /*disabled, items*/ 3) imagebutton_changes.disabled = /*disabled*/ ctx[1] || /*item*/ ctx[13].disabled;\n\t\t\tif (dirty & /*items*/ 1) imagebutton_changes.title = /*item*/ ctx[13].title;\n\t\t\tif (dirty & /*items*/ 1) imagebutton_changes.html = /*item*/ ctx[13].thumb;\n\t\t\timagebutton.$set(imagebutton_changes);\n\t\t\tif (didMountAction_action && is_function(didMountAction_action.update) && dirty & /*items*/ 1) didMountAction_action.update.call(null, /*item*/ ctx[13]);\n\n\t\t\tif (dirty & /*$opacity*/ 64) {\n\t\t\t\tset_style(li, \"opacity\", /*$opacity*/ ctx[6]);\n\t\t\t}\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\t\t\ttransition_in(imagebutton.$$.fragment, local);\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\ttransition_out(imagebutton.$$.fragment, local);\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\tif (detaching) detach(li);\n\t\t\tdestroy_component(imagebutton);\n\t\t\tmounted = false;\n\t\t\tdispose();\n\t\t}\n\t};\n}\n\nfunction create_fragment$N(ctx) {\n\tlet ul;\n\tlet each_blocks = [];\n\tlet each_1_lookup = new Map();\n\tlet current;\n\tlet each_value = /*items*/ ctx[0];\n\tconst get_key = ctx => /*item*/ ctx[13].id;\n\n\tfor (let i = 0; i < each_value.length; i += 1) {\n\t\tlet child_ctx = get_each_context$6(ctx, each_value, i);\n\t\tlet key = get_key(child_ctx);\n\t\teach_1_lookup.set(key, each_blocks[i] = create_each_block$6(key, child_ctx));\n\t}\n\n\treturn {\n\t\tc() {\n\t\t\tul = element(\"ul\");\n\n\t\t\tfor (let i = 0; i < each_blocks.length; i += 1) {\n\t\t\t\teach_blocks[i].c();\n\t\t\t}\n\n\t\t\tattr(ul, \"class\", \"PinturaImageButtonList\");\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tinsert(target, ul, anchor);\n\n\t\t\tfor (let i = 0; i < each_blocks.length; i += 1) {\n\t\t\t\teach_blocks[i].m(ul, null);\n\t\t\t}\n\n\t\t\tcurrent = true;\n\t\t},\n\t\tp(ctx, [dirty]) {\n\t\t\tif (dirty & /*items, $opacity, onclickitem, ongrabitem, ondragitem, ondropitem, disabled*/ 127) {\n\t\t\t\teach_value = /*items*/ ctx[0];\n\t\t\t\tgroup_outros();\n\t\t\t\teach_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, ul, outro_and_destroy_block, create_each_block$6, null, get_each_context$6);\n\t\t\t\tcheck_outros();\n\t\t\t}\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\n\t\t\tfor (let i = 0; i < each_value.length; i += 1) {\n\t\t\t\ttransition_in(each_blocks[i]);\n\t\t\t}\n\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\tfor (let i = 0; i < each_blocks.length; i += 1) {\n\t\t\t\ttransition_out(each_blocks[i]);\n\t\t\t}\n\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\tif (detaching) detach(ul);\n\n\t\t\tfor (let i = 0; i < each_blocks.length; i += 1) {\n\t\t\t\teach_blocks[i].d();\n\t\t\t}\n\t\t}\n\t};\n}\n\nfunction instance$O($$self, $$props, $$invalidate) {\n\tlet $opacity;\n\tlet { items } = $$props;\n\tlet { disabled = undefined } = $$props;\n\tlet { onclickitem } = $$props;\n\tlet { ongrabitem = undefined } = $$props;\n\tlet { ondragitem = undefined } = $$props;\n\tlet { ondropitem = undefined } = $$props;\n\tconst opacity = spring(0, { stiffness: 0.25, damping: 0.9 });\n\tcomponent_subscribe($$self, opacity, value => $$invalidate(6, $opacity = value));\n\tconst didMountAction = (element, item) => item.mount && item.mount(element.firstChild, item);\n\tonMount(() => opacity.set(1));\n\tconst func = item => onclickitem(item.id);\n\tconst func_1 = (item, e) => ongrabitem && ongrabitem(item.id, e);\n\tconst func_2 = (item, e) => ondragitem && ondragitem(item.id, e);\n\tconst func_3 = (item, e) => ondropitem && ondropitem(item.id, e);\n\n\t$$self.$$set = $$props => {\n\t\tif ('items' in $$props) $$invalidate(0, items = $$props.items);\n\t\tif ('disabled' in $$props) $$invalidate(1, disabled = $$props.disabled);\n\t\tif ('onclickitem' in $$props) $$invalidate(2, onclickitem = $$props.onclickitem);\n\t\tif ('ongrabitem' in $$props) $$invalidate(3, ongrabitem = $$props.ongrabitem);\n\t\tif ('ondragitem' in $$props) $$invalidate(4, ondragitem = $$props.ondragitem);\n\t\tif ('ondropitem' in $$props) $$invalidate(5, ondropitem = $$props.ondropitem);\n\t};\n\n\treturn [\n\t\titems,\n\t\tdisabled,\n\t\tonclickitem,\n\t\tongrabitem,\n\t\tondragitem,\n\t\tondropitem,\n\t\t$opacity,\n\t\topacity,\n\t\tdidMountAction,\n\t\tfunc,\n\t\tfunc_1,\n\t\tfunc_2,\n\t\tfunc_3\n\t];\n}\n\nclass ImageButtonList extends SvelteComponent {\n\tconstructor(options) {\n\t\tsuper();\n\n\t\tinit(this, options, instance$O, create_fragment$N, safe_not_equal, {\n\t\t\titems: 0,\n\t\t\tdisabled: 1,\n\t\t\tonclickitem: 2,\n\t\t\tongrabitem: 3,\n\t\t\tondragitem: 4,\n\t\t\tondropitem: 5\n\t\t});\n\t}\n}\n\nvar getDevicePixelRatio = () => (isBrowser() && window.devicePixelRatio) || 1;\n\n// if this is a non retina display snap to pixel\nlet fn = null;\nvar snapToPixel = (v) => {\n if (fn === null)\n fn = getDevicePixelRatio() === 1 ? Math.round : (v) => v;\n return fn(v);\n};\n\nvar focus = (element, options = {}) => {\n if (!element)\n return;\n if (options.preventScroll && isSafari()) {\n const scrollTop = document.body.scrollTop;\n element.focus();\n document.body.scrollTop = scrollTop;\n return;\n }\n element.focus(options);\n};\n\nvar isTextarea = (element) => /textarea/i.test(element.nodeName);\n\nvar isTextInput = (node) => /date|email|number|search|text|url/.test(node.type);\n\nvar isTextField = (node) => isTextarea(node) || isTextInput(node) || node.isContentEditable;\n\n/* src/core/ui/components/Panel.svelte generated by Svelte v3.52.0 */\nconst get_details_slot_changes = dirty => ({});\nconst get_details_slot_context = ctx => ({});\nconst get_label_slot_changes = dirty => ({});\nconst get_label_slot_context = ctx => ({});\n\n// (234:0) {:else}\nfunction create_else_block$a(ctx) {\n\tlet button;\n\tlet current;\n\tconst button_spread_levels = [/*buttonProps*/ ctx[7]];\n\n\tlet button_props = {\n\t\t$$slots: { default: [create_default_slot$h] },\n\t\t$$scope: { ctx }\n\t};\n\n\tfor (let i = 0; i < button_spread_levels.length; i += 1) {\n\t\tbutton_props = assign(button_props, button_spread_levels[i]);\n\t}\n\n\tbutton = new Button({ props: button_props });\n\t/*button_binding_1*/ ctx[53](button);\n\n\treturn {\n\t\tc() {\n\t\t\tcreate_component(button.$$.fragment);\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tmount_component(button, target, anchor);\n\t\t\tcurrent = true;\n\t\t},\n\t\tp(ctx, dirty) {\n\t\t\tconst button_changes = (dirty[0] & /*buttonProps*/ 128)\n\t\t\t? get_spread_update(button_spread_levels, [get_spread_object(/*buttonProps*/ ctx[7])])\n\t\t\t: {};\n\n\t\t\tif (dirty[1] & /*$$scope*/ 67108864) {\n\t\t\t\tbutton_changes.$$scope = { dirty, ctx };\n\t\t\t}\n\n\t\t\tbutton.$set(button_changes);\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\t\t\ttransition_in(button.$$.fragment, local);\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\ttransition_out(button.$$.fragment, local);\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\t/*button_binding_1*/ ctx[53](null);\n\t\t\tdestroy_component(button, detaching);\n\t\t}\n\t};\n}\n\n// (232:0) {#if buttonLabel}\nfunction create_if_block_2$c(ctx) {\n\tlet button;\n\tlet current;\n\tconst button_spread_levels = [/*buttonProps*/ ctx[7]];\n\tlet button_props = {};\n\n\tfor (let i = 0; i < button_spread_levels.length; i += 1) {\n\t\tbutton_props = assign(button_props, button_spread_levels[i]);\n\t}\n\n\tbutton = new Button({ props: button_props });\n\t/*button_binding*/ ctx[52](button);\n\n\treturn {\n\t\tc() {\n\t\t\tcreate_component(button.$$.fragment);\n\t\t},\n\t\tm(target, anchor) {\n\t\t\tmount_component(button, target, anchor);\n\t\t\tcurrent = true;\n\t\t},\n\t\tp(ctx, dirty) {\n\t\t\tconst button_changes = (dirty[0] & /*buttonProps*/ 128)\n\t\t\t? get_spread_update(button_spread_levels, [get_spread_object(/*buttonProps*/ ctx[7])])\n\t\t\t: {};\n\n\t\t\tbutton.$set(button_changes);\n\t\t},\n\t\ti(local) {\n\t\t\tif (current) return;\n\t\t\ttransition_in(button.$$.fragment, local);\n\t\t\tcurrent = true;\n\t\t},\n\t\to(local) {\n\t\t\ttransition_out(button.$$.fragment, local);\n\t\t\tcurrent = false;\n\t\t},\n\t\td(detaching) {\n\t\t\t/*button_binding*/ ctx[52](null);\n\t\t\tdestroy_component(button, detaching);\n\t\t}\n\t};\n}\n\n// (235:4)