modern-canvas
Version:
A JavaScript WebGL rendering engine.
1,826 lines (1,796 loc) • 416 kB
JavaScript
'use strict';
const modernIdoc = require('modern-idoc');
const colord = require('colord');
const namesPlugin = require('colord/plugins/names');
const modernPath2d = require('modern-path2d');
const modernText = require('modern-text');
const load = require('yoga-layout/load');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
const namesPlugin__default = /*#__PURE__*/_interopDefaultCompat(namesPlugin);
const customNodes = /* @__PURE__ */ new Map();
function customNode(name, defaultProperties) {
return function(constructor) {
Object.defineProperty(constructor.prototype, "is", {
value: name,
enumerable: true,
configurable: true
});
if (defaultProperties) {
Object.keys(defaultProperties).forEach((key) => {
modernIdoc.defineProperty(constructor, key, {
fallback: defaultProperties[key]
});
});
}
customNodes.set(name, constructor);
};
}
function createNode(tag = "node", options = {}) {
const Klass = customNodes.get(tag);
if (!Klass) {
throw new Error(`Failed to createNode, tag: ${tag}`);
}
return new Klass().setProperties(options);
}
class Ticker {
static _queue = [];
static _currentTime = 0;
static _elapsed = 0;
static _requestId;
static get currentTime() {
return this._currentTime;
}
static get elapsed() {
return this._elapsed;
}
static on(cb, options = {}) {
const { sort = 0, once = false } = options;
if (!this._queue[sort])
this._queue[sort] = [];
this._queue[sort].push({ cb, once });
}
static off(cb, options = {}) {
const { sort = 0 } = options;
const items = this._queue[sort];
if (!items)
return;
const newItems = [];
for (let len = items.length, i = 0; i < len; i++) {
const item = items[i];
if (item.cb !== cb) {
newItems.push(item);
}
}
if (newItems.length) {
this._queue[sort] = newItems;
} else {
this._queue.splice(sort, 1);
}
}
static start() {
if ("requestAnimationFrame" in globalThis) {
this._currentTime = performance.now();
this._requestId = requestAnimationFrame((time) => this._update(time));
}
}
static stop() {
if (this._requestId) {
cancelAnimationFrame(this._requestId);
this._requestId = void 0;
}
}
static _update(time) {
this._elapsed = time - this._currentTime;
this._currentTime = time;
this._requestId = requestAnimationFrame((time2) => this._update(time2));
this._performUpdate();
}
static _performUpdate() {
const queue = this._queue;
const newQueue = [];
for (let len = queue.length, i = len; i >= 0; i--) {
const items = queue[i];
if (!items)
continue;
const newItems = [];
for (let len2 = items.length, i2 = 0; i2 < len2; i2++) {
const item = items[i2];
try {
item.cb();
} catch (err) {
console.warn(err);
}
if (!item.once) {
newItems.push(item);
}
}
if (newItems.length) {
newQueue[i] = newItems;
}
}
this._queue = newQueue;
}
}
Ticker.start();
async function nextTick(cb) {
return new Promise((resolve) => {
Ticker.on(
() => {
cb?.();
resolve();
},
{ sort: 1, once: true }
);
});
}
const PI = Math.PI;
const PI_2 = PI * 2;
let UID = 0;
function uid(object) {
return object?.__SPECTOR_Object_TAG?.id ?? ++UID;
}
function isPow2(v) {
return !(v & v - 1) && !!v;
}
const FUNCTIONS_RE = /([\w-]+)\((.+?)\)/g;
const ARGS_RE = /[^,]+/g;
const ARG_RE = /([-e.\d]+)(.*)/;
function getDefaultCssPropertyValue(value) {
if (Array.isArray(value)) {
return value.map((func) => {
return {
name: func.name,
args: func.args.map((arg) => {
return {
...arg,
normalizedIntValue: arg.normalizedDefaultIntValue
};
})
};
});
} else {
return {
...value,
normalizedIntValue: value.normalizedDefaultIntValue
};
}
}
function parseCssProperty(name, propertyValue, context = {}) {
const functions = parseCssFunctions(propertyValue, context);
return functions.length ? functions : parseArgument(name, propertyValue, context);
}
function parseCssFunctions(propertyValue, context = {}) {
const functions = [];
let match;
while ((match = FUNCTIONS_RE.exec(propertyValue)) !== null) {
const [, name, value] = match;
if (name) {
functions.push({
name,
args: parseArguments(name, value, context)
});
}
}
return functions;
}
function parseArguments(name, value, context = {}) {
const values = [];
let match;
let i = 0;
while ((match = ARGS_RE.exec(value)) !== null) {
values.push(
parseArgument(name, match[0], {
...context,
index: i++
})
);
}
return values;
}
function parseArgument(name, value, context = {}) {
const { width = 1, height = 1, index = 0 } = context;
const matched = value.match(ARG_RE);
const result = {
unit: matched?.[2] ?? null,
value,
intValue: Number(matched?.[1]),
normalizedIntValue: 0,
normalizedDefaultIntValue: 0
};
switch (name) {
case "scale":
case "scaleX":
case "scaleY":
case "scale3d":
result.normalizedDefaultIntValue = 1;
break;
}
switch (result.unit) {
case "%":
result.normalizedIntValue = result.intValue / 100;
break;
case "rad":
result.normalizedIntValue = result.intValue / PI_2;
break;
case "deg":
result.normalizedIntValue = result.intValue / 360;
break;
case "px":
switch (index) {
case 0:
result.normalizedIntValue = result.intValue / width;
break;
case 1:
result.normalizedIntValue = result.intValue / height;
break;
}
break;
case "turn":
case "em":
// div fontSize
case "rem":
// div fontSize
default:
result.normalizedIntValue = result.intValue;
break;
}
return result;
}
const SUPPORTS_WEBGL2 = "WebGL2RenderingContext" in globalThis;
const SUPPORTS_IMAGE_BITMAP = "ImageBitmap" in globalThis;
const SUPPORTS_RESIZE_OBSERVER = "ResizeObserver" in globalThis;
const SUPPORTS_POINTER_EVENTS = "PointerEvent" in globalThis;
const SUPPORTS_WHEEL_EVENTS = "WheelEvent" in globalThis;
const SUPPORTS_MOUSE_EVENTS = "MouseEvent" in globalThis;
const SUPPORTS_TOUCH_EVENTS = "ontouchstart" in globalThis;
const SUPPORTS_CLICK_EVENTS = "onclick" in globalThis;
const SUPPORTS_CREATE_IMAGE_BITMAP = "createImageBitmap" in globalThis;
const SUPPORTS_AUDIO_CONTEXT = "AudioContext" in globalThis;
const SUPPORTS_WEBKIT_AUDIO_CONTEXT = "webkitAudioContext" in globalThis;
const SUPPORTS_OFFLINE_AUDIO_CONTEXT = "OfflineAudioContext" in globalThis;
const SUPPORTS_WEBKIT_OFFLINE_AUDIO_CONTEXT = "webkitOfflineAudioContext" in globalThis;
const SUPPORTS_WEB_AUDIO = SUPPORTS_AUDIO_CONTEXT || SUPPORTS_WEBKIT_AUDIO_CONTEXT;
const IN_BROWSER = typeof window !== "undefined";
const DEVICE_PIXEL_RATIO = globalThis.devicePixelRatio || 1;
const isElementNode = (node) => node !== null && typeof node === "object" && node.nodeType === 1;
const isVideoElement = (node) => isElementNode(node) && node.tagName === "VIDEO";
const isImageElement = (node) => isElementNode(node) && node.tagName === "IMG";
function isCanvasElement(node) {
return typeof node === "object" && node !== null && node.nodeType === 1 && node.tagName === "CANVAS";
}
function isWebgl2(gl) {
return SUPPORTS_WEBGL2 && gl instanceof globalThis.WebGL2RenderingContext;
}
function createHTMLCanvas() {
if (IN_BROWSER) {
return globalThis.document.createElement("canvas");
}
return void 0;
}
function determineCrossOrigin(url, loc = globalThis.location) {
if (url.startsWith("data:")) {
return "";
}
loc = loc || globalThis.location;
const parsedUrl = new URL(url, document.baseURI);
if (parsedUrl.hostname !== loc.hostname || parsedUrl.port !== loc.port || parsedUrl.protocol !== loc.protocol) {
return "anonymous";
}
return "";
}
function crossOrigin(element, url, crossorigin) {
if (crossorigin === null && !url.startsWith("data:")) {
element.crossOrigin = determineCrossOrigin(url);
} else if (crossorigin !== false) {
element.crossOrigin = typeof crossorigin === "string" ? crossorigin : "anonymous";
}
}
class RawWeakMap {
_map = /* @__PURE__ */ new WeakMap();
// fix: vue reactive object
_toRaw(value) {
if (value && typeof value === "object") {
const raw = value.__v_raw;
if (raw) {
value = this._toRaw(raw);
}
}
return value;
}
/**
* Removes the specified element from the WeakMap.
* @returns true if the element was successfully removed, or false if it was not present.
*/
delete(key) {
return this._map.delete(this._toRaw(key));
}
/**
* @returns a specified element.
*/
get(key) {
return this._map.get(this._toRaw(key));
}
/**
* @returns a boolean indicating whether an element with the specified key exists or not.
*/
has(key) {
return this._map.has(this._toRaw(key));
}
/**
* Adds a new element with a specified key and value.
* @param key Must be an object or symbol.
*/
set(key, value) {
this._map.set(this._toRaw(key), this._toRaw(value));
return this;
}
}
class InputEvent {
cursor;
// Event
bubbles = false;
cancelBubble = false;
cancelable = false;
composed = false;
defaultPrevented = false;
eventPhase = InputEvent.prototype.NONE;
isTrusted = false;
timeStamp = 0;
type = "";
composedPath() {
return this.path;
}
initEvent(..._args) {
throw new Error("initEvent() is a legacy DOM API. It is not implemented in the Federated Events API.");
}
preventDefault() {
if ("preventDefault" in this.nativeEvent && this.nativeEvent.cancelable) {
this.nativeEvent.preventDefault();
}
this.defaultPrevented = true;
}
propagationImmediatelyStopped = false;
stopImmediatePropagation() {
this.propagationImmediatelyStopped = true;
}
propagationStopped = false;
stopPropagation() {
if ("stopPropagation" in this.nativeEvent) {
this.nativeEvent.stopPropagation();
}
this.propagationStopped = true;
}
NONE = 0;
CAPTURING_PHASE = 1;
AT_TARGET = 2;
BUBBLING_PHASE = 3;
initUIEvent(..._args) {
throw new Error("initUIEvent() is a legacy DOM API. It is not implemented in the Federated Events API.");
}
}
class KeyboardInputEvent extends InputEvent {
getModifierState(..._args) {
throw new Error("getModifierState() is a legacy DOM API. It is not implemented in the Federated Events API.");
}
initKeyboardEvent(..._args) {
throw new Error("initKeyboardEvent() is a legacy DOM API. It is not implemented in the Federated Events API.");
}
}
class MouseInputEvent extends InputEvent {
client = { x: 0, y: 0 };
get clientX() {
return this.client.x;
}
get clientY() {
return this.client.y;
}
layer = { x: 0, y: 0 };
get layerX() {
return this.layer.x;
}
get layerY() {
return this.layer.y;
}
movement = { x: 0, y: 0 };
get movementX() {
return this.movement.x;
}
get movementY() {
return this.movement.y;
}
offset = { x: 0, y: 0 };
get offsetX() {
return this.offset.x;
}
get offsetY() {
return this.offset.y;
}
page = { x: 0, y: 0 };
get pageX() {
return this.page.x;
}
get pageY() {
return this.page.y;
}
screen = { x: 0, y: 0 };
get screenX() {
return this.screen.x;
}
get screenY() {
return this.screen.y;
}
get x() {
return this.clientX;
}
get y() {
return this.clientY;
}
getModifierState(key) {
return "getModifierState" in this.nativeEvent && this.nativeEvent.getModifierState(key);
}
initMouseEvent(..._args) {
throw new Error("Method not implemented.");
}
global = { x: 0, y: 0 };
get globalX() {
return this.global.x;
}
get globalY() {
return this.global.y;
}
}
class PointerInputEvent extends MouseInputEvent {
width = 0;
height = 0;
isPrimary = false;
getCoalescedEvents() {
if (this.type === "pointermove" || this.type === "mousemove" || this.type === "touchmove") {
return [this];
}
return [];
}
getPredictedEvents() {
throw new Error("getPredictedEvents is not supported!");
}
}
class WheelInputEvent extends MouseInputEvent {
static DOM_DELTA_PIXEL = 0;
DOM_DELTA_PIXEL = 0;
static DOM_DELTA_LINE = 1;
DOM_DELTA_LINE = 1;
static DOM_DELTA_PAGE = 2;
DOM_DELTA_PAGE = 2;
}
const TOUCH_TO_POINTER = {
touchstart: "pointerdown",
touchend: "pointerup",
touchendoutside: "pointerupoutside",
touchmove: "pointermove",
touchcancel: "pointercancel"
};
class Input extends modernIdoc.EventEmitter {
/**
* Current event
*/
event;
target;
cursor = "default";
cursorStyles = {
default: "inherit",
pointer: "pointer"
};
setuped = false;
enableMoveEvent = true;
enableWheelEvent = true;
enableClickEvent = true;
setTarget(target) {
this.removeEventListeners();
this.target = target;
this.addEventListeners();
}
removeEventListeners() {
if (!this.setuped || !this.target) {
return;
}
const style = this.target.style;
if (globalThis.navigator.msPointerEnabled) {
style.msContentZooming = "";
style.msTouchAction = "";
} else if (SUPPORTS_POINTER_EVENTS) {
style.touchAction = "";
}
if (SUPPORTS_POINTER_EVENTS) {
this.target.removeEventListener("pointerdown", this._onPointerDown);
this.target.removeEventListener("pointerleave", this._onPointerOver);
this.target.removeEventListener("pointerover", this._onPointerOver);
this.target.removeEventListener("pointermove", this._onPointerMove);
this.target.removeEventListener("pointerup", this._onPointerUp);
} else {
this.target.removeEventListener("mousedown", this._onPointerDown);
this.target.removeEventListener("mouseout", this._onPointerOver);
this.target.removeEventListener("mouseover", this._onPointerOver);
this.target.removeEventListener("mousemove", this._onPointerMove);
this.target.removeEventListener("mouseup", this._onPointerUp);
}
if (SUPPORTS_TOUCH_EVENTS) {
this.target.removeEventListener("touchstart", this._onPointerDown);
this.target.removeEventListener("touchmove", this._onPointerMove);
this.target.removeEventListener("touchend", this._onPointerUp);
}
this.target.removeEventListener("wheel", this._onWheel);
document.removeEventListener("keydown", this._onKeyDown);
document.removeEventListener("keypress", this._onKeyPress);
document.removeEventListener("keyup", this._onKeyUp);
this.target = void 0;
this.setuped = false;
}
addEventListeners() {
if (this.setuped || !this.target) {
return;
}
const style = this.target.style;
if (style) {
if (globalThis.navigator.msPointerEnabled) {
style.msContentZooming = "none";
style.msTouchAction = "none";
} else if (SUPPORTS_POINTER_EVENTS) {
style.touchAction = "none";
}
}
if (SUPPORTS_POINTER_EVENTS) {
this.target.addEventListener("pointerdown", this._onPointerDown);
this.target.addEventListener("pointerleave", this._onPointerOver);
this.target.addEventListener("pointerover", this._onPointerOver);
this.target.addEventListener("pointermove", this._onPointerMove);
this.target.addEventListener("pointerup", this._onPointerUp);
} else {
this.target.addEventListener("mousedown", this._onPointerDown);
this.target.addEventListener("mouseout", this._onPointerOver);
this.target.addEventListener("mouseover", this._onPointerOver);
this.target.addEventListener("mousemove", this._onPointerMove);
this.target.addEventListener("mouseup", this._onPointerUp);
}
if (SUPPORTS_TOUCH_EVENTS) {
this.target.addEventListener("touchstart", this._onPointerDown);
this.target.addEventListener("touchmove", this._onPointerMove);
this.target.addEventListener("touchend", this._onPointerUp);
}
this.target.addEventListener("wheel", this._onWheel);
document.addEventListener("keydown", this._onKeyDown);
document.addEventListener("keypress", this._onKeyPress);
document.addEventListener("keyup", this._onKeyUp);
this.setuped = true;
}
normalize(event) {
const events = [];
if (SUPPORTS_TOUCH_EVENTS && event instanceof globalThis.TouchEvent) {
for (let i = 0, li = event.changedTouches.length; i < li; i++) {
const touch = event.changedTouches[i];
if (typeof touch.button === "undefined")
touch.button = 0;
if (typeof touch.buttons === "undefined")
touch.buttons = 1;
if (typeof touch.isPrimary === "undefined") {
touch.isPrimary = event.touches.length === 1 && event.type === "touchstart";
}
if (typeof touch.width === "undefined")
touch.width = touch.radiusX || 1;
if (typeof touch.height === "undefined")
touch.height = touch.radiusY || 1;
if (typeof touch.tiltX === "undefined")
touch.tiltX = 0;
if (typeof touch.tiltY === "undefined")
touch.tiltY = 0;
if (typeof touch.pointerType === "undefined")
touch.pointerType = "touch";
if (typeof touch.pointerId === "undefined")
touch.pointerId = touch.identifier || 0;
if (typeof touch.pressure === "undefined")
touch.pressure = touch.force || 0.5;
if (typeof touch.twist === "undefined")
touch.twist = 0;
if (typeof touch.tangentialPressure === "undefined")
touch.tangentialPressure = 0;
if (typeof touch.layerX === "undefined")
touch.layerX = touch.offsetX = touch.clientX;
if (typeof touch.layerY === "undefined")
touch.layerY = touch.offsetY = touch.clientY;
touch.type = event.type;
events.push(touch);
}
} else if (SUPPORTS_WHEEL_EVENTS && event instanceof globalThis.WheelEvent) {
events.push(event);
} else if (SUPPORTS_POINTER_EVENTS && event instanceof globalThis.PointerEvent) {
events.push(event);
} else {
const mouse = event;
if (typeof mouse.isPrimary === "undefined")
mouse.isPrimary = true;
if (typeof mouse.width === "undefined")
mouse.width = 1;
if (typeof mouse.height === "undefined")
mouse.height = 1;
if (typeof mouse.tiltX === "undefined")
mouse.tiltX = 0;
if (typeof mouse.tiltY === "undefined")
mouse.tiltY = 0;
if (typeof mouse.pointerType === "undefined")
mouse.pointerType = "mouse";
if (typeof mouse.pointerId === "undefined")
mouse.pointerId = 1;
if (typeof mouse.pressure === "undefined")
mouse.pressure = 0.5;
if (typeof mouse.twist === "undefined")
mouse.twist = 0;
if (typeof mouse.tangentialPressure === "undefined")
mouse.tangentialPressure = 0;
events.push(mouse);
}
return events;
}
_clonePointerEvent(nativeEvent) {
const event = new PointerInputEvent();
event.nativeEvent = nativeEvent;
event.pointerId = nativeEvent.pointerId;
event.width = nativeEvent.width;
event.height = nativeEvent.height;
event.isPrimary = nativeEvent.isPrimary;
event.pointerType = nativeEvent.pointerType;
event.pressure = nativeEvent.pressure;
event.tangentialPressure = nativeEvent.tangentialPressure;
event.tiltX = nativeEvent.tiltX;
event.tiltY = nativeEvent.tiltY;
event.twist = nativeEvent.twist;
event.isTrusted = nativeEvent.isTrusted;
this._copyMouseEvent(event, nativeEvent);
this.mapPositionToPoint(event.screen, nativeEvent.clientX, nativeEvent.clientY);
event.global.x = event.screen.x;
event.global.y = event.screen.y;
event.offset.x = event.screen.x;
event.offset.y = event.screen.y;
if (event.type === "pointerleave") {
event.type = "pointerout";
} else if (event.type.startsWith("mouse")) {
event.type = event.type.replace("mouse", "pointer");
} else if (event.type.startsWith("touch")) {
event.type = TOUCH_TO_POINTER[event.type] || event.type;
}
return event;
}
_copyInputEvent(event, nativeEvent) {
event.nativeEvent = nativeEvent;
event.bubbles = nativeEvent.bubbles;
event.cancelBubble = nativeEvent.cancelBubble;
event.cancelable = nativeEvent.cancelable;
event.composed = nativeEvent.composed;
event.currentTarget = nativeEvent.currentTarget;
event.defaultPrevented = nativeEvent.defaultPrevented;
event.eventPhase = nativeEvent.eventPhase;
event.isTrusted = nativeEvent.isTrusted;
event.returnValue = nativeEvent.returnValue;
event.srcElement = nativeEvent.srcElement;
event.timeStamp = nativeEvent.timeStamp;
event.type = nativeEvent.type;
}
_copyMouseEvent(event, nativeEvent) {
this._copyInputEvent(event, nativeEvent);
event.altKey = nativeEvent.altKey;
event.button = nativeEvent.button;
event.buttons = nativeEvent.buttons;
event.client.x = nativeEvent.clientX;
event.client.y = nativeEvent.clientY;
event.ctrlKey = nativeEvent.ctrlKey;
event.metaKey = nativeEvent.metaKey;
event.movement.x = nativeEvent.movementX;
event.movement.y = nativeEvent.movementY;
event.page.x = nativeEvent.pageX;
event.page.y = nativeEvent.pageY;
event.relatedTarget = null;
event.shiftKey = nativeEvent.shiftKey;
}
_cloneWheelEvent(nativeEvent) {
const event = new WheelInputEvent();
this._copyMouseEvent(event, nativeEvent);
event.wheelDeltaY = nativeEvent.wheelDeltaY;
event.deltaX = nativeEvent.deltaX;
event.deltaY = nativeEvent.deltaY;
event.deltaZ = nativeEvent.deltaZ;
event.deltaMode = nativeEvent.deltaMode;
this.mapPositionToPoint(event.screen, nativeEvent.clientX, nativeEvent.clientY);
event.global.x = event.screen.x;
event.global.y = event.screen.y;
event.offset.x = event.screen.x;
event.offset.y = event.screen.y;
return event;
}
_cloneKeyboardEvent(nativeEvent) {
const event = new KeyboardInputEvent();
this._copyInputEvent(event, nativeEvent);
event.altKey = nativeEvent.altKey;
event.charCode = nativeEvent.charCode;
event.code = nativeEvent.code;
event.ctrlKey = nativeEvent.ctrlKey;
event.isComposing = nativeEvent.isComposing;
event.key = nativeEvent.key;
event.keyCode = nativeEvent.keyCode;
event.location = nativeEvent.location;
event.metaKey = nativeEvent.metaKey;
event.repeat = nativeEvent.repeat;
event.shiftKey = nativeEvent.shiftKey;
return event;
}
setCursor(mode = "default") {
if (!this.target)
return;
if (this.cursor === mode) {
return;
}
this.cursor = mode;
const applyStyles = !(globalThis.OffscreenCanvas && this.target instanceof OffscreenCanvas);
const style = this.cursorStyles[mode];
if (style) {
switch (typeof style) {
case "string":
if (applyStyles) {
this.target.style.cursor = style;
}
break;
case "function":
style(mode);
break;
case "object":
if (applyStyles) {
Object.assign(this.target.style, style);
}
break;
}
} else if (applyStyles && typeof mode === "string" && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) {
this.target.style.cursor = mode;
}
}
mapPositionToPoint(point, x, y) {
if (!this.target)
return;
const clientRect = this.target.isConnected ? this.target.getBoundingClientRect() : void 0;
const pixelRatio = Number(this.target.getAttribute("data-pixel-ratio")) || 1;
const width = Number(this.target.getAttribute("width")) || (clientRect ? clientRect.width * pixelRatio : 0);
const height = Number(this.target.getAttribute("height")) || (clientRect ? clientRect.height * pixelRatio : 0);
const rect = clientRect ?? { width, height, left: 0, top: 0 };
const multiplier = 1 / pixelRatio;
point.x = (x - rect.left) * (width / rect.width) * multiplier;
point.y = (y - rect.top) * (height / rect.height) * multiplier;
}
_onKeyDown = (nativeEvent) => {
const events = this.normalize(nativeEvent);
for (let i = 0, len = events.length; i < len; i++) {
this.emit("keydown", this.event = this._cloneKeyboardEvent(events[i]));
}
if (this.event?.cursor) {
this.setCursor(this.event.cursor);
}
};
_onKeyPress = (nativeEvent) => {
const events = this.normalize(nativeEvent);
for (let i = 0, len = events.length; i < len; i++) {
this.emit("keypress", this.event = this._cloneKeyboardEvent(events[i]));
}
if (this.event?.cursor) {
this.setCursor(this.event.cursor);
}
};
_onKeyUp = (nativeEvent) => {
const events = this.normalize(nativeEvent);
for (let i = 0, len = events.length; i < len; i++) {
this.emit("keyup", this.event = this._cloneKeyboardEvent(events[i]));
}
if (this.event?.cursor) {
this.setCursor(this.event.cursor);
}
};
_onPointerDown = (nativeEvent) => {
if (SUPPORTS_TOUCH_EVENTS && nativeEvent.pointerType === "touch")
return;
const events = this.normalize(nativeEvent);
for (let i = 0, len = events.length; i < len; i++) {
this.emit("pointerdown", this.event = this._clonePointerEvent(events[i]));
}
if (this.event?.cursor) {
this.setCursor(this.event.cursor);
}
};
_onPointerOver = (nativeEvent) => {
if (!this.enableClickEvent)
return;
if (SUPPORTS_TOUCH_EVENTS && nativeEvent.pointerType === "touch")
return;
const events = this.normalize(nativeEvent);
for (let i = 0, len = events.length; i < len; i++) {
this.emit("pointerover", this.event = this._clonePointerEvent(events[i]));
}
if (this.event?.cursor) {
this.setCursor(this.event.cursor);
}
};
_onPointerMove = (nativeEvent) => {
if (!this.enableMoveEvent)
return;
if (SUPPORTS_TOUCH_EVENTS && nativeEvent.pointerType === "touch")
return;
const events = this.normalize(nativeEvent);
for (let i = 0, len = events.length; i < len; i++) {
this.emit("pointermove", this.event = this._clonePointerEvent(events[i]));
}
if (this.event?.cursor) {
this.setCursor(this.event.cursor);
}
};
_onPointerUp = (nativeEvent) => {
if (!this.enableClickEvent)
return;
if (SUPPORTS_TOUCH_EVENTS && nativeEvent.pointerType === "touch")
return;
let target = nativeEvent.target;
if (nativeEvent.composedPath && nativeEvent.composedPath().length > 0) {
target = nativeEvent.composedPath()[0];
}
const outside = target !== this.target ? "outside" : "";
const events = this.normalize(nativeEvent);
for (let i = 0, len = events.length; i < len; i++) {
const event = this._clonePointerEvent(events[i]);
event.type += outside;
this.emit("pointerup", this.event = event);
}
if (this.event?.cursor) {
this.setCursor(this.event.cursor);
}
};
_onWheel = (nativeEvent) => {
if (!this.enableWheelEvent)
return;
const events = this.normalize(nativeEvent);
for (let i = 0, len = events.length; i < len; i++) {
this.emit("wheel", this.event = this._cloneWheelEvent(events[i]));
}
if (this.event?.cursor) {
this.setCursor(this.event.cursor);
}
};
}
let IID = 0;
class CoreObject extends modernIdoc.EventEmitter {
instanceId = ++IID;
_customPropertyAccessor;
_properties = /* @__PURE__ */ new Map();
_updatedProperties = /* @__PURE__ */ new Map();
_changedProperties = /* @__PURE__ */ new Set();
_updatingPromise = Promise.resolve();
_updating = false;
useCustomPropertyAccessor(accessor) {
this._customPropertyAccessor = accessor;
this.getPropertyDeclarations().forEach((declaration, key) => {
const newValue = accessor.get(key, () => void 0);
const oldValue = this._properties.get(key);
if (newValue === void 0) {
if (oldValue !== void 0) {
accessor.set(key, oldValue);
}
} else if (newValue !== oldValue) {
if (declaration.alias && declaration.alias !== key) {
this.setProperty(key, newValue);
} else {
this._properties.set(key, newValue);
}
this._updateProperty(key, newValue, oldValue, declaration);
this.emit("updateProperty", key, newValue, oldValue, declaration);
}
});
return this;
}
getter(key, context) {
if (context?.declaration.protected) {
return this[context.internalKey];
} else {
return this._customPropertyAccessor ? this._customPropertyAccessor.get(key, () => this._properties.get(key)) : this._properties.get(key);
}
}
setter(key, value, context) {
if (context?.declaration.protected) {
this[context.internalKey] = value;
} else {
if (this._customPropertyAccessor) {
this._customPropertyAccessor.set(key, value);
}
this._properties.set(key, value);
}
}
equal(target) {
return Boolean(target && this.instanceId === target.instanceId);
}
async _enqueueUpdate() {
this._updating = true;
try {
await this._updatingPromise;
} catch (e) {
Promise.reject(e);
}
await nextTick();
if (!this._updating)
return;
this.update();
this._updating = false;
}
update() {
this._update(this._updatedProperties);
this._updatedProperties = /* @__PURE__ */ new Map();
}
// eslint-disable-next-line unused-imports/no-unused-vars
_update(changed) {
}
// eslint-disable-next-line unused-imports/no-unused-vars
_updateProperty(key, value, oldValue, declaration) {
}
isDirty(key) {
return this._updatedProperties.has(key);
}
getPropertyDeclarations() {
return modernIdoc.getDeclarations(this.constructor);
}
getPropertyDeclaration(key) {
return this.getPropertyDeclarations().get(key);
}
getProperty(key) {
return this[key];
}
setProperty(key, value) {
this[key] = value;
return this;
}
getProperties(keys) {
const properties = {};
for (const [name, property] of this.getPropertyDeclarations()) {
if (!property.protected && !property.alias && (!keys || keys.includes(name))) {
properties[name] = this.getProperty(name);
}
}
return properties;
}
setProperties(properties) {
if (properties && typeof properties === "object") {
for (const [name] of this.getPropertyDeclarations()) {
if (name in properties) {
this.setProperty(name, properties[name]);
}
}
}
return this;
}
resetProperties() {
for (const [name, property] of this.getPropertyDeclarations()) {
this.setProperty(
name,
typeof property.fallback === "function" ? property.fallback() : property.fallback
);
}
return this;
}
onUpdateProperty(key, newValue, oldValue, declaration) {
this.requestUpdate(key, newValue, oldValue, declaration);
}
requestUpdate(key, newValue, oldValue, declaration) {
if (key !== void 0) {
if (!Object.is(newValue, oldValue)) {
this._updatedProperties.set(key, oldValue);
this._changedProperties.add(key);
declaration ??= this.getPropertyDeclaration(key);
this._updateProperty(key, newValue, oldValue, declaration);
this.emit("updateProperty", key, newValue, oldValue, declaration);
} else {
return;
}
}
if (!this._updating) {
this._updatingPromise = this._enqueueUpdate();
}
}
toJSON() {
const json = {};
this._properties.forEach((value, key) => {
if (value === void 0) {
return;
}
if (value && typeof value === "object") {
if ("toJSON" in value && typeof value.toJSON === "function") {
json[key] = value.toJSON();
} else if (Array.isArray(value)) {
json[key] = [...value];
} else {
json[key] = { ...value };
}
} else {
json[key] = value;
}
});
return json;
}
clone() {
return new this.constructor(this.toJSON());
}
free() {
this.removeAllListeners();
}
}
class RefCounted extends CoreObject {
//
}
class Resource extends RefCounted {
//
}
colord.extend([namesPlugin__default]);
class Color {
get value() {
return this._value;
}
set value(value) {
this._colord = modernIdoc.parseColor(value ?? "none");
}
get r8() {
return this._colord.rgba.r;
}
get g8() {
return this._colord.rgba.g;
}
get b8() {
return this._colord.rgba.b;
}
get a8() {
return this._colord.rgba.a * 255 & 255;
}
get r() {
return this.r8 / 255;
}
get g() {
return this.g8 / 255;
}
get b() {
return this.b8 / 255;
}
get a() {
return this._colord.rgba.a;
}
get rgb() {
return (this.r8 << 16) + (this.g8 << 8) + this.b8;
}
get bgr() {
return (this.b8 << 16) + (this.g8 << 8) + this.r8;
}
get abgr() {
return (this.a8 << 24) + this.bgr;
}
constructor(value = 0) {
this.value = value;
}
toHex() {
return this._colord.toHex();
}
toRgb() {
return this._colord.toRgb();
}
toRgbString() {
return this._colord.toRgbString();
}
toHsl() {
return this._colord.toHsl();
}
toHslString() {
return this._colord.toHslString();
}
toHsv() {
return this._colord.toHsv();
}
toArgb(alpha = this.a, applyToRGB = true) {
if (alpha === 1) {
return (255 << 24) + this.rgb;
}
if (alpha === 0) {
return applyToRGB ? 0 : this.rgb;
}
let r = this.r8;
let g = this.g8;
let b = this.b8;
if (applyToRGB) {
r = r * alpha + 0.5 | 0;
g = g * alpha + 0.5 | 0;
b = b * alpha + 0.5 | 0;
}
return (alpha * 255 << 24) + (r << 16) + (g << 8) + b;
}
toArray() {
return [this.r, this.g, this.b, this.a];
}
}
class Vector extends modernIdoc.EventEmitter {
constructor(dim) {
super();
this.dim = dim;
}
_array = [];
get length() {
return this.dim;
}
operate(operator, target, output) {
const { dim: length, _array: array } = this;
let targetArray;
if (typeof target === "number") {
targetArray = Array.from({ length }, () => target);
} else if (target instanceof Matrix || target instanceof Vector) {
targetArray = target.toArray();
} else {
targetArray = target;
}
let outputObject;
let outputArray = [];
if (!output) {
outputObject = this;
} else if (output instanceof Vector) {
outputObject = output;
} else {
outputArray = output;
}
if (target instanceof Matrix) {
const { cols } = target;
switch (operator) {
case "*":
for (let x = 0; x < length; x++) {
let val = 0;
for (let y = 0; y < length; y++) {
val += array[x] * targetArray[y * cols + x];
}
outputArray[x] = val;
}
break;
default:
throw new Error(`Not support operator in '${this.toName()} ${operator} ${target.toName()}'`);
}
} else {
switch (operator) {
case "+":
for (let i = 0; i < length; i++) {
outputArray[i] = array[i] + targetArray[i];
}
break;
case "-":
for (let i = 0; i < length; i++) {
outputArray[i] = array[i] - targetArray[i];
}
break;
case "*":
for (let i = 0; i < length; i++) {
outputArray[i] = array[i] * targetArray[i];
}
break;
case "/":
for (let i = 0; i < length; i++) {
outputArray[i] = array[i] / targetArray[i];
}
break;
case "rot": {
const c = Math.cos(targetArray[0]);
const s = Math.sin(targetArray[0]);
outputArray[0] = array[0] * c - array[1] * s;
outputArray[1] = array[1] * c + array[0] * s;
break;
}
case "==": {
let flag = true;
for (let i = 0; i < length; i++) {
flag = flag && array[i] === targetArray[i];
}
return flag;
}
case "=":
for (let i = 0; i < length; i++) {
const val = targetArray[i];
if (val !== void 0) {
array[i] = val;
}
}
this._onUpdate(array);
this.emit("update", array);
return this;
default:
throw new Error(`Not support operator in '${this.toName()} ${operator} Vector'`);
}
}
return outputObject?.set(outputArray) ?? outputArray;
}
add(value, ...args) {
if (args.length && typeof value === "number") {
value = [value, ...args];
}
return this.operate("+", value);
}
sub(value, ...args) {
if (args.length && typeof value === "number") {
value = [value, ...args];
}
return this.operate("-", value);
}
multiply(value, ...args) {
if (args.length && typeof value === "number") {
value = [value, ...args];
}
return this.operate("*", value);
}
divide(value, ...args) {
if (args.length && typeof value === "number") {
value = [value, ...args];
}
return this.operate("/", value);
}
rotate(angle) {
return this.operate("rot", angle);
}
set(value, ...args) {
if (args.length && typeof value === "number") {
value = [value, ...args];
}
return this.operate("=", value);
}
equals(value) {
return this.operate("==", value);
}
copy(value) {
return this.set(value);
}
clone() {
const cloned = new this.constructor();
cloned.set(this.toArray());
return cloned;
}
_onUpdate(_array) {
}
toName() {
return `Vector${this.dim}`;
}
toArray() {
return this._array.slice();
}
toJSON() {
return this.toArray();
}
}
class Matrix extends modernIdoc.EventEmitter {
constructor(rows, cols, array) {
super();
this.rows = rows;
this.cols = cols;
if (array) {
this.set(array);
} else {
this.identity();
}
}
_array = [];
dirtyId = 0;
get length() {
return this.cols * this.rows;
}
operate(operator, target, output) {
const { cols, rows, length, _array: array } = this;
let targetArray;
if (typeof target === "number") {
targetArray = Array.from({ length }, () => target);
} else if (target instanceof Vector || target instanceof Matrix) {
targetArray = target.toArray();
} else {
targetArray = target;
}
let outputObject;
let outputArray = [];
if (!output) {
if (target instanceof Vector) {
outputObject = new target.constructor();
} else {
outputObject = this;
}
} else if (output instanceof Vector) {
outputObject = output;
} else if (output instanceof Matrix) {
outputObject = output;
} else {
outputArray = output;
}
if (target instanceof Vector) {
const { dim } = target;
switch (operator) {
case "*":
for (let y = 0; y < dim; y++) {
let sum = 0;
for (let i = 0; i < cols; i++) {
if (i < dim) {
sum += array[y * cols + i] * (targetArray[i] ?? 0);
}
}
outputArray[y] = sum;
}
break;
default:
throw new Error(`Not support operator in '${this.toName()} ${operator} ${target.toName()}'`);
}
} else {
switch (operator) {
case "*":
for (let x = 0; x < cols; x++) {
for (let y = 0; y < rows; y++) {
const iy = y * cols;
let sum = 0;
for (let i = 0; i < rows; i++) {
const a = iy + i;
const b = i * cols + x;
sum += array[a] * (targetArray[b] ?? 0);
}
outputArray[iy + x] = sum;
}
}
break;
case "=":
for (let i = 0; i < length; i++) {
const val = targetArray[i];
if (val !== void 0) {
array[i] = val;
}
}
this._onUpdate(array);
this.emit("update", array);
return this;
default:
throw new Error(`Not support operator in '${this.toName()} ${operator} Matrix2'`);
}
}
return outputObject?.set(outputArray) ?? outputArray;
}
identity() {
const { cols, rows } = this;
const array = [];
for (let x = 0; x < cols; x++) {
for (let y = 0; y < rows; y++) {
const iy = y * cols;
const i = x + iy;
array[i] = y + iy === i ? 1 : 0;
}
}
return this.set(array);
}
set(value) {
return this.operate("=", value);
}
copy(value) {
return this.set(value);
}
clone() {
const cloned = new this.constructor();
cloned.set(this.toArray());
return cloned;
}
multiply(value, output) {
return this.operate("*", value, output);
}
_onUpdate(_array) {
this.dirtyId++;
}
toArray(transpose = false) {
const { cols, rows, _array: array } = this;
if (transpose) {
const newArray = [];
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
newArray[y + x * cols] = array[x + y * cols];
}
}
return newArray;
}
return array.slice();
}
toName() {
return `Matrix${this.rows}(${this.rows}x${this.cols})`;
}
toJSON() {
return this._array.slice();
}
}
class Matrix4 extends Matrix {
constructor(array) {
super(4, 4, array);
}
}
const DEG_TO_RAD = PI / 180;
const RAD_TO_DEG = 180 / PI;
function clamp(val, min, max) {
return Math.max(min, Math.min(val, max));
}
function lerp(a, b, weight) {
return (1 - weight) * a + weight * b;
}
const curves = {
adaptive: true,
maxLength: 10,
minSegments: 8,
maxSegments: 2048,
epsilon: 1e-4,
_segmentsCount(length, defaultSegments = 20) {
if (!this.adaptive || !length || Number.isNaN(length)) {
return defaultSegments;
}
let result = Math.ceil(length / this.maxLength);
if (result < this.minSegments) {
result = this.minSegments;
} else if (result > this.maxSegments) {
result = this.maxSegments;
}
return result;
}
};
class Vector4 extends Vector {
constructor(x = 0, y = 0, z = 0, m = 0) {
super(4);
this.set(x, y, z, m);
}
}
class ColorMatrix extends Matrix {
constructor(array) {
super(4, 5, array);
}
hueRotate(angle = 0) {
const sin = Math.sin(angle);
const cos = Math.cos(angle);
const lumR = 0.213;
const lumG = 0.715;
const lumB = 0.072;
return this.multiply([
lumR + cos * (1 - lumR) + sin * -lumR,
lumG + cos * -lumG + sin * -lumG,
lumB + cos * -lumB + sin * (1 - lumB),
0,
0,
lumR + cos * -lumR + sin * 0.143,
lumG + cos * (1 - lumG) + sin * 0.14,
lumB + cos * -lumB + sin * -0.283,
0,
0,
lumR + cos * -lumR + sin * -0.787,
lumG + cos * -lumG + sin * lumG,
lumB + cos * (1 - lumB) + sin * lumB,
0,
0,
0,
0,
0,
1,
0
]);
}
saturate(amount = 1) {
const x = (amount - 1) * 2 / 3 + 1;
const y = (x - 1) * -0.5;
return this.multiply([
x,
y,
y,
0,
0,
y,
x,
y,
0,
0,
y,
y,
x,
0,
0,
0,
0,
0,
1,
0
]);
}
brightness(amount = 1) {
const v = amount;
return this.multiply([
v,
0,
0,
0,
0,
0,
v,
0,
0,
0,
0,
0,
v,
0,
0,
0,
0,
0,
1,
0
]);
}
contrast(amount = 1) {
const v = amount;
const o = -128 * (v - 1);
return this.multiply([
v,
0,
0,
0,
o,
0,
v,
0,
0,
o,
0,
0,
v,
0,
o,
0,
0,
0,
1,
0
]);
}
invert(amount = 1) {
const v = lerp(1, -1, amount);
const o = lerp(0, 255, amount);
return this.multiply([
v,
0,
0,
0,
o,
0,
v,
0,
0,
o,
0,
0,
v,
0,
o,
0,
0,
0,
1,
0
]);
}
sepia(amount = 1) {
const v = clamp(amount, 0, 1);
return this.multiply([
lerp(1, 0.393, v),
lerp(0, 0.7689999, v),
lerp(0, 0.18899999, v),
0,
0,
lerp(0, 0.349, v),
lerp(1, 0.6859999, v),
lerp(0, 0.16799999, v),
0,
0,
lerp(0, 0.272, v),
lerp(0, 0.5339999, v),
lerp(1, 0.13099999, v),
0,
0,
0,
0,
0,
1,
0
]);
}
opacity(amount = 1) {
return this.multiply([
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
amount,
0
]);
}
grayscale(amount = 1) {
const v = clamp(amount, 0, 1);
const r = lerp(1, 0.3, v);
const rr = lerp(0, 0.3, v);
const g = lerp(1, 0.59, v);
const gg = lerp(0, 0.59, v);
const b = lerp(1, 0.11, v);
const bb = lerp(0, 0.11, v);
return this.multiply([
r,
gg,
bb,
0,
0,
rr,
g,
bb,
0,
0,
rr,
gg,
b,
0,
0,
0,
0,
0,
1,
0
]);
}
multiply(target) {
const b = target;
const a = this._array;
return this.set([
// Red Channel
a[0] * b[0] + a[1] * b[5] + a[2] * b[10] + a[3] * b[15],
a[0] * b[1] + a[1] * b[6] + a[2] * b[11] + a[3] * b[16],
a[0] * b[2] + a[1] * b[7] + a[2] * b[12] + a[3] * b[17],
a[0] * b[3] + a[1] * b[8] + a[2] * b[13] + a[3] * b[18],
a[0] * b[4] + a[1] * b[9] + a[2] * b[14] + a[3] * b[19] + a[4],
// Green Channel
a[5] * b[0] + a[6] * b[5] + a[7] * b[10] + a[8] * b[15],
a[5] * b[1] + a[6] * b[6] + a[7] * b[11] + a[8] * b[16],
a[5] * b[2] + a[6] * b[7] + a[7] * b[12] + a[8] * b[17],
a[5] * b[3] + a[6] * b[8] + a[7] * b[13] + a[8] * b[18],
a[5] * b[4] + a[6] * b[9] + a[7] * b[14] + a[8] * b[19] + a[9],
// Blue Channel
a[10] * b[0] + a[11] * b[5] + a[12] * b[10] + a[13] * b[15],
a[10] * b[1] + a[11] * b[6] + a[12] * b[11] + a[13] * b[16],
a[10] * b[2] + a[11] * b[7] + a[12] * b[12] + a[13] * b[17],
a[10] * b[3] + a[11] * b[8] + a[12] * b[13] + a[13] * b[18],
a[10] * b[4] + a[11] * b[9] + a[12] * b[14] + a[13] * b[19] + a[14],
// Alpha Channel
a[15] * b[0] + a[16] * b[5] + a[17] * b[10] + a[18] * b[15],
a[15] * b[1] + a[16] * b[6] + a[17] * b[11] + a[18] * b[16],
a[15] * b[2] + a[16] * b[7] + a[17] * b[12] + a[18] * b[17],
a[15] * b[3] + a[16] * b[8] + a[17] * b[13] + a[18] * b[18],
a[15] * b[4] + a[16] * b[9] + a[17] * b[14] + a[18] * b[19] + a[19]
]);
}
toMatrix4() {
const array = this._array;
return new Matrix4([
array[0],
array[1],
array[2],
array[3],
array[5],
array[6],
array[7],
array[8],
array[10],
array[11],
array[12],
array[13],
array[15],
array[16],
array[17],
array[18]
]);
}
toVector4() {
const array = this._array;
return new Vector4(array[4] / 255, array[9] / 255, array[14] / 255, array[19] / 255);
}
}
class Matrix2 extends Matrix {
constructor(array) {
super(2, 2, array);
}
}
class Matrix3 extends Matrix {
constructor(array) {
super(3, 3, array);
}
invert() {
const [
n11,
n21,
n31,
n12,
n22,
n32,
n13,
n23,
n33
] = this._array;
const t11 = n33 * n22 - n32 * n23;
const t12 = n32 * n13 - n33 * n12;
const t13 = n23 * n12 - n22 * n13;
const det = n11 * t11 + n21 * t12 + n31 * t13;
if (det === 0) {
return this.set([0, 0, 0, 0, 0, 0, 0, 0, 0]);
}
const detInv = 1 / det;
return this.set([
t11 * detInv,
(n31 * n23 - n33 * n21) * detInv,
(n32 * n21 - n31 * n22) * detInv,
t12 * detInv,
(n33 * n11 - n31 * n13) * detInv,
(n31 * n12 - n32 * n11) * detInv,
t13 * detInv,
(n21 * n13 - n23 * n11) * detInv,
(n22 * n11 - n21 * n12) * detInv
]);
}
}
class Projection2D extends Matrix3 {
constructor(_x = 0, _y = 0, _width = 0, _height = 0, _flipY = false) {
super();
this._x = _x;
this._y = _y;
this._width = _width;
this._height = _height;
this._flipY = _flipY;
this._performUpdateArray();
}
flipY(flipY) {
if (this._flipY !== flipY) {
this._flipY = flipY;
this._performUpdateArray();
}
return this;
}
translate(x, y) {
if (this._x !== x || this._y !== y) {
this._x = x;
this._y = y;
this._performUpdateArray();
}
return this;
}
resize(width, height) {
if (this._width !== width || this._height !== height) {
this._width = width;
this._height = height;
this._performUpdateArray();
}
return this;
}
_performUpdateArray() {
const width = this._width;
const height = this._height;
if (!width || !height) {
return;
}
const x = this._x;
const y = this._y;
const sign = !this._flipY ? 1 : -1;
const a = 1 / width * 2;
const d