paper-pen
Version:
A JavaScript library for annotations
1,444 lines (1,407 loc) • 129 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.PaperPen = {}));
})(this, (function (exports) { 'use strict';
function _iterableToArrayLimit(arr, i) {
var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"];
if (null != _i) {
var _s,
_e,
_x,
_r,
_arr = [],
_n = !0,
_d = !1;
try {
if (_x = (_i = _i.call(arr)).next, 0 === i) {
if (Object(_i) !== _i) return;
_n = !1;
} else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0);
} catch (err) {
_d = !0, _e = err;
} finally {
try {
if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return;
} finally {
if (_d) throw _e;
}
}
return _arr;
}
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
enumerableOnly && (symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
})), keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = null != arguments[i] ? arguments[i] : {};
i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
_defineProperty(target, key, source[key]);
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
return target;
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, "prototype", {
writable: false
});
return Constructor;
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
Object.defineProperty(subClass, "prototype", {
writable: false
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
return true;
} catch (e) {
return false;
}
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _possibleConstructorReturn(self, call) {
if (call && (typeof call === "object" || typeof call === "function")) {
return call;
} else if (call !== void 0) {
throw new TypeError("Derived constructors may only return object or undefined");
}
return _assertThisInitialized(self);
}
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _toPrimitive(input, hint) {
if (typeof input !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (typeof res !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return typeof key === "symbol" ? key : String(key);
}
var Event = /*#__PURE__*/function () {
function Event() {
_classCallCheck(this, Event);
this.eventList = {};
}
_createClass(Event, [{
key: "on",
value: function on(eventName, fn) {
this.eventList[eventName] = this.eventList[eventName] || [];
this.eventList[eventName].push(fn);
}
}, {
key: "emit",
value: function emit(eventName) {
var _this = this;
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
var fns = this.eventList[eventName];
if (fns) {
this.eventList[eventName].forEach(function (item) {
item === null || item === void 0 ? void 0 : item.call.apply(item, [_this].concat(args));
});
}
}
}, {
key: "off",
value: function off(eventName, fn) {
if (!this.eventList[eventName]) {
return;
}
this.eventList[eventName] = this.eventList[eventName].filter(function (item) {
return item !== fn;
});
}
}, {
key: "offAll",
value: function offAll(eventName) {
if (eventName) {
delete this.eventList[eventName];
return;
}
this.eventList = {};
}
}]);
return Event;
}();
var Matrix = /*#__PURE__*/function () {
function Matrix(a, b, c, d, e, f) {
_classCallCheck(this, Matrix);
this.a = a || 1;
this.b = b || 0;
this.c = c || 0;
this.d = d || 1;
this.e = e || 0;
this.f = f || 0;
}
/**
* matrix translate
* 矩阵平移
* @param {*} x
* @param {*} y
* @returns
*/
_createClass(Matrix, [{
key: "translate",
value: function translate(x, y) {
return this.transform(1, 0, 0, 1, x, y);
}
/**
* matrix scale
* 矩阵缩放指定倍数
* @param {*} x
* @returns
*/
}, {
key: "scaleU",
value: function scaleU(x) {
return this.transform(x, 0, 0, x, 0, 0);
}
/**
* Multiples corresponding to x-axis and y-axis scaling
* x轴y轴缩放对应的倍数
* @param {*} x
* @param {*} y
* @returns
*/
}, {
key: "scale",
value: function scale(x, y) {
return this.transform(x, 0, 0, y, 0, 0);
}
/**
* rotate Matrix
* 旋转矩阵
* @param {*} radian
* @returns
*/
}, {
key: "rotate",
value: function rotate(radian) {
var cos = Math.cos(radian),
sin = Math.sin(radian);
return this.transform(cos, sin, -sin, cos, 0, 0);
}
/**
* angle to radian
* 角度转弧度
* @param {*} angle
* @returns
*/
}, {
key: "rotateAngle",
value: function rotateAngle(angle) {
var radian = angle / 180 * Math.PI;
return this.rotate(radian);
}
/**
* Matrix 叉乘
* @param {*} a2
* @param {*} b2
* @param {*} c2
* @param {*} d2
* @param {*} e2
* @param {*} f2
* @returns
*/
}, {
key: "transform",
value: function transform(a2, b2, c2, d2, e2, f2) {
var me = this,
a1 = this.a,
b1 = this.b,
c1 = this.c,
d1 = this.d,
e1 = this.e,
f1 = this.f;
/* matrix column order is:
* a1 b1 0 a2 b2 0 a c e
* c1 d1 0 c2 d2 0 b d f
* e1 f1 1 e2 f2 1
*/
// console.log(a2,b2,c2,d2,e2,f2, me)
// a = a1 * a2 + c1 * b2 + 0
// c = a1 * c2 + c1 * d2 + 0
// e = a1 * e2 + c1 * f2 + e1
me.a = a1 * a2 + c1 * b2;
me.b = b1 * a2 + d1 * b2;
me.c = a1 * c2 + c1 * d2;
me.d = b1 * c2 + d1 * d2;
me.e = a1 * e2 + c1 * f2 + e1;
me.f = b1 * e2 + d1 * f2 + f1;
return me;
}
/**
* Apply the current matrix to point
* 将当前矩阵应用到传入的点
* @param {*} x
* @param {*} y
* @returns
*/
}, {
key: "applyToPoint",
value: function applyToPoint(x, y) {
var me = this;
return {
x: x * me.a + y * me.c + me.e,
y: x * me.b + y * me.d + me.f
};
}
/**
* clone a new Matrix
* 克隆一个新的矩阵
* @returns new Matrix
*/
}, {
key: "clone",
value: function clone() {
return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
}
/**
* reset the matrix
* 1 0 0
* 0 1 0
* 0 0 1
* @returns new Matrix
*/
}, {
key: "reset",
value: function reset() {
return new Matrix();
}
}]);
return Matrix;
}();
function quickselect(arr, k, left, right, compare) {
quickselectStep(arr, k, left || 0, right || arr.length - 1, compare || defaultCompare);
}
function quickselectStep(arr, k, left, right, compare) {
while (right > left) {
if (right - left > 600) {
var n = right - left + 1;
var m = k - left + 1;
var z = Math.log(n);
var s = 0.5 * Math.exp(2 * z / 3);
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
quickselectStep(arr, k, newLeft, newRight, compare);
}
var t = arr[k];
var i = left;
var j = right;
swap(arr, left, k);
if (compare(arr[right], t) > 0) swap(arr, left, right);
while (i < j) {
swap(arr, i, j);
i++;
j--;
while (compare(arr[i], t) < 0) i++;
while (compare(arr[j], t) > 0) j--;
}
if (compare(arr[left], t) === 0) swap(arr, left, j);else {
j++;
swap(arr, j, right);
}
if (j <= k) left = j + 1;
if (k <= j) right = j - 1;
}
}
function swap(arr, i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
function defaultCompare(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}
class RBush {
constructor(maxEntries = 9) {
// max entries in a node is 9 by default; min node fill is 40% for best performance
this._maxEntries = Math.max(4, maxEntries);
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
this.clear();
}
all() {
return this._all(this.data, []);
}
search(bbox) {
let node = this.data;
const result = [];
if (!intersects(bbox, node)) return result;
const toBBox = this.toBBox;
const nodesToSearch = [];
while (node) {
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
const childBBox = node.leaf ? toBBox(child) : child;
if (intersects(bbox, childBBox)) {
if (node.leaf) result.push(child);else if (contains(bbox, childBBox)) this._all(child, result);else nodesToSearch.push(child);
}
}
node = nodesToSearch.pop();
}
return result;
}
collides(bbox) {
let node = this.data;
if (!intersects(bbox, node)) return false;
const nodesToSearch = [];
while (node) {
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
const childBBox = node.leaf ? this.toBBox(child) : child;
if (intersects(bbox, childBBox)) {
if (node.leaf || contains(bbox, childBBox)) return true;
nodesToSearch.push(child);
}
}
node = nodesToSearch.pop();
}
return false;
}
load(data) {
if (!(data && data.length)) return this;
if (data.length < this._minEntries) {
for (let i = 0; i < data.length; i++) {
this.insert(data[i]);
}
return this;
}
// recursively build the tree with the given data from scratch using OMT algorithm
let node = this._build(data.slice(), 0, data.length - 1, 0);
if (!this.data.children.length) {
// save as is if tree is empty
this.data = node;
} else if (this.data.height === node.height) {
// split root if trees have the same height
this._splitRoot(this.data, node);
} else {
if (this.data.height < node.height) {
// swap trees if inserted one is bigger
const tmpNode = this.data;
this.data = node;
node = tmpNode;
}
// insert the small tree into the large tree at appropriate level
this._insert(node, this.data.height - node.height - 1, true);
}
return this;
}
insert(item) {
if (item) this._insert(item, this.data.height - 1);
return this;
}
clear() {
this.data = createNode([]);
return this;
}
remove(item, equalsFn) {
if (!item) return this;
let node = this.data;
const bbox = this.toBBox(item);
const path = [];
const indexes = [];
let i, parent, goingUp;
// depth-first iterative tree traversal
while (node || path.length) {
if (!node) {
// go up
node = path.pop();
parent = path[path.length - 1];
i = indexes.pop();
goingUp = true;
}
if (node.leaf) {
// check current node
const index = findItem(item, node.children, equalsFn);
if (index !== -1) {
// item found, remove the item and condense tree upwards
node.children.splice(index, 1);
path.push(node);
this._condense(path);
return this;
}
}
if (!goingUp && !node.leaf && contains(node, bbox)) {
// go down
path.push(node);
indexes.push(i);
i = 0;
parent = node;
node = node.children[0];
} else if (parent) {
// go right
i++;
node = parent.children[i];
goingUp = false;
} else node = null; // nothing found
}
return this;
}
toBBox(item) {
return item;
}
compareMinX(a, b) {
return a.minX - b.minX;
}
compareMinY(a, b) {
return a.minY - b.minY;
}
toJSON() {
return this.data;
}
fromJSON(data) {
this.data = data;
return this;
}
_all(node, result) {
const nodesToSearch = [];
while (node) {
if (node.leaf) result.push(...node.children);else nodesToSearch.push(...node.children);
node = nodesToSearch.pop();
}
return result;
}
_build(items, left, right, height) {
const N = right - left + 1;
let M = this._maxEntries;
let node;
if (N <= M) {
// reached leaf level; return leaf
node = createNode(items.slice(left, right + 1));
calcBBox(node, this.toBBox);
return node;
}
if (!height) {
// target height of the bulk-loaded tree
height = Math.ceil(Math.log(N) / Math.log(M));
// target number of root entries to maximize storage utilization
M = Math.ceil(N / Math.pow(M, height - 1));
}
node = createNode([]);
node.leaf = false;
node.height = height;
// split the items into M mostly square tiles
const N2 = Math.ceil(N / M);
const N1 = N2 * Math.ceil(Math.sqrt(M));
multiSelect(items, left, right, N1, this.compareMinX);
for (let i = left; i <= right; i += N1) {
const right2 = Math.min(i + N1 - 1, right);
multiSelect(items, i, right2, N2, this.compareMinY);
for (let j = i; j <= right2; j += N2) {
const right3 = Math.min(j + N2 - 1, right2);
// pack each entry recursively
node.children.push(this._build(items, j, right3, height - 1));
}
}
calcBBox(node, this.toBBox);
return node;
}
_chooseSubtree(bbox, node, level, path) {
while (true) {
path.push(node);
if (node.leaf || path.length - 1 === level) break;
let minArea = Infinity;
let minEnlargement = Infinity;
let targetNode;
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
const area = bboxArea(child);
const enlargement = enlargedArea(bbox, child) - area;
// choose entry with the least area enlargement
if (enlargement < minEnlargement) {
minEnlargement = enlargement;
minArea = area < minArea ? area : minArea;
targetNode = child;
} else if (enlargement === minEnlargement) {
// otherwise choose one with the smallest area
if (area < minArea) {
minArea = area;
targetNode = child;
}
}
}
node = targetNode || node.children[0];
}
return node;
}
_insert(item, level, isNode) {
const bbox = isNode ? item : this.toBBox(item);
const insertPath = [];
// find the best node for accommodating the item, saving all nodes along the path too
const node = this._chooseSubtree(bbox, this.data, level, insertPath);
// put the item into the node
node.children.push(item);
extend(node, bbox);
// split on node overflow; propagate upwards if necessary
while (level >= 0) {
if (insertPath[level].children.length > this._maxEntries) {
this._split(insertPath, level);
level--;
} else break;
}
// adjust bboxes along the insertion path
this._adjustParentBBoxes(bbox, insertPath, level);
}
// split overflowed node into two
_split(insertPath, level) {
const node = insertPath[level];
const M = node.children.length;
const m = this._minEntries;
this._chooseSplitAxis(node, m, M);
const splitIndex = this._chooseSplitIndex(node, m, M);
const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
newNode.height = node.height;
newNode.leaf = node.leaf;
calcBBox(node, this.toBBox);
calcBBox(newNode, this.toBBox);
if (level) insertPath[level - 1].children.push(newNode);else this._splitRoot(node, newNode);
}
_splitRoot(node, newNode) {
// split root node
this.data = createNode([node, newNode]);
this.data.height = node.height + 1;
this.data.leaf = false;
calcBBox(this.data, this.toBBox);
}
_chooseSplitIndex(node, m, M) {
let index;
let minOverlap = Infinity;
let minArea = Infinity;
for (let i = m; i <= M - m; i++) {
const bbox1 = distBBox(node, 0, i, this.toBBox);
const bbox2 = distBBox(node, i, M, this.toBBox);
const overlap = intersectionArea(bbox1, bbox2);
const area = bboxArea(bbox1) + bboxArea(bbox2);
// choose distribution with minimum overlap
if (overlap < minOverlap) {
minOverlap = overlap;
index = i;
minArea = area < minArea ? area : minArea;
} else if (overlap === minOverlap) {
// otherwise choose distribution with minimum area
if (area < minArea) {
minArea = area;
index = i;
}
}
}
return index || M - m;
}
// sorts node children by the best axis for split
_chooseSplitAxis(node, m, M) {
const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
const xMargin = this._allDistMargin(node, m, M, compareMinX);
const yMargin = this._allDistMargin(node, m, M, compareMinY);
// if total distributions margin value is minimal for x, sort by minX,
// otherwise it's already sorted by minY
if (xMargin < yMargin) node.children.sort(compareMinX);
}
// total margin of all possible split distributions where each node is at least m full
_allDistMargin(node, m, M, compare) {
node.children.sort(compare);
const toBBox = this.toBBox;
const leftBBox = distBBox(node, 0, m, toBBox);
const rightBBox = distBBox(node, M - m, M, toBBox);
let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
for (let i = m; i < M - m; i++) {
const child = node.children[i];
extend(leftBBox, node.leaf ? toBBox(child) : child);
margin += bboxMargin(leftBBox);
}
for (let i = M - m - 1; i >= m; i--) {
const child = node.children[i];
extend(rightBBox, node.leaf ? toBBox(child) : child);
margin += bboxMargin(rightBBox);
}
return margin;
}
_adjustParentBBoxes(bbox, path, level) {
// adjust bboxes along the given tree path
for (let i = level; i >= 0; i--) {
extend(path[i], bbox);
}
}
_condense(path) {
// go through the path, removing empty nodes and updating bboxes
for (let i = path.length - 1, siblings; i >= 0; i--) {
if (path[i].children.length === 0) {
if (i > 0) {
siblings = path[i - 1].children;
siblings.splice(siblings.indexOf(path[i]), 1);
} else this.clear();
} else calcBBox(path[i], this.toBBox);
}
}
}
function findItem(item, items, equalsFn) {
if (!equalsFn) return items.indexOf(item);
for (let i = 0; i < items.length; i++) {
if (equalsFn(item, items[i])) return i;
}
return -1;
}
// calculate node's bbox from bboxes of its children
function calcBBox(node, toBBox) {
distBBox(node, 0, node.children.length, toBBox, node);
}
// min bounding rectangle of node children from k to p-1
function distBBox(node, k, p, toBBox, destNode) {
if (!destNode) destNode = createNode(null);
destNode.minX = Infinity;
destNode.minY = Infinity;
destNode.maxX = -Infinity;
destNode.maxY = -Infinity;
for (let i = k; i < p; i++) {
const child = node.children[i];
extend(destNode, node.leaf ? toBBox(child) : child);
}
return destNode;
}
function extend(a, b) {
a.minX = Math.min(a.minX, b.minX);
a.minY = Math.min(a.minY, b.minY);
a.maxX = Math.max(a.maxX, b.maxX);
a.maxY = Math.max(a.maxY, b.maxY);
return a;
}
function compareNodeMinX(a, b) {
return a.minX - b.minX;
}
function compareNodeMinY(a, b) {
return a.minY - b.minY;
}
function bboxArea(a) {
return (a.maxX - a.minX) * (a.maxY - a.minY);
}
function bboxMargin(a) {
return a.maxX - a.minX + (a.maxY - a.minY);
}
function enlargedArea(a, b) {
return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
}
function intersectionArea(a, b) {
const minX = Math.max(a.minX, b.minX);
const minY = Math.max(a.minY, b.minY);
const maxX = Math.min(a.maxX, b.maxX);
const maxY = Math.min(a.maxY, b.maxY);
return Math.max(0, maxX - minX) * Math.max(0, maxY - minY);
}
function contains(a, b) {
return a.minX <= b.minX && a.minY <= b.minY && b.maxX <= a.maxX && b.maxY <= a.maxY;
}
function intersects(a, b) {
return b.minX <= a.maxX && b.minY <= a.maxY && b.maxX >= a.minX && b.maxY >= a.minY;
}
function createNode(children) {
return {
children,
height: 1,
leaf: true,
minX: Infinity,
minY: Infinity,
maxX: -Infinity,
maxY: -Infinity
};
}
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
// combines selection algorithm with binary divide & conquer approach
function multiSelect(arr, left, right, n, compare) {
const stack = [left, right];
while (stack.length) {
right = stack.pop();
left = stack.pop();
if (right - left <= n) continue;
const mid = left + Math.ceil((right - left) / n / 2) * n;
quickselect(arr, mid, left, right, compare);
stack.push(left, mid, mid, right);
}
}
var defaultTransfromMatrix = {
scaleX: 1,
scaleY: 1,
angle: 0,
skewX: 0,
skewY: 0,
translateX: 0,
translateY: 0,
width: 0,
height: 0,
left: 0,
top: 0,
originX: "left",
originY: "top"
};
var id = 0;
var Selectable = /*#__PURE__*/function (_Event) {
_inherits(Selectable, _Event);
var _super = _createSuper(Selectable);
function Selectable(el, options) {
var _this;
_classCallCheck(this, Selectable);
_this = _super.call(this);
_defineProperty(_assertThisInitialized(_this), "el", null);
_defineProperty(_assertThisInitialized(_this), "width", 0);
_defineProperty(_assertThisInitialized(_this), "height", 0);
_defineProperty(_assertThisInitialized(_this), "lowerCanvas", null);
_defineProperty(_assertThisInitialized(_this), "lowerContext", null);
_defineProperty(_assertThisInitialized(_this), "upperCanvas", null);
_defineProperty(_assertThisInitialized(_this), "canvasContainer", null);
_defineProperty(_assertThisInitialized(_this), "cacheCanvas", null);
_defineProperty(_assertThisInitialized(_this), "cacheCtx", null);
_defineProperty(_assertThisInitialized(_this), "transform", [1, 0, 0, 1, 0, 0]);
_defineProperty(_assertThisInitialized(_this), "transformMatrix", new Matrix());
_defineProperty(_assertThisInitialized(_this), "defaultTransform", defaultTransfromMatrix);
_defineProperty(_assertThisInitialized(_this), "_objects", []);
_defineProperty(_assertThisInitialized(_this), "_activeObject", null);
_defineProperty(_assertThisInitialized(_this), "selection", true);
_defineProperty(_assertThisInitialized(_this), "selectionLineWidth", 1);
_defineProperty(_assertThisInitialized(_this), "selectionColor", "#00f");
_defineProperty(_assertThisInitialized(_this), "selectionBorderColor", "#000");
_defineProperty(_assertThisInitialized(_this), "selectionOpacity", 0.3);
_defineProperty(_assertThisInitialized(_this), "background", "");
_defineProperty(_assertThisInitialized(_this), "backgroundImage", new Image());
_defineProperty(_assertThisInitialized(_this), "mask", false);
_defineProperty(_assertThisInitialized(_this), "maskCanvas", null);
_defineProperty(_assertThisInitialized(_this), "maskContext", null);
_defineProperty(_assertThisInitialized(_this), "imageFillMode", "contain");
// 'contain' (baseheight) | 'cover' (default basewidth)
_defineProperty(_assertThisInitialized(_this), "ratio", {
x: 1,
y: 1
});
_defineProperty(_assertThisInitialized(_this), "pixelSize", {
w: 0,
h: 0
});
_defineProperty(_assertThisInitialized(_this), "preventDefault", true);
_defineProperty(_assertThisInitialized(_this), "selection", true);
// can or not renderTop
_defineProperty(_assertThisInitialized(_this), "skipFindTarget", false);
// 跳过查找当前元素
_defineProperty(_assertThisInitialized(_this), "wheel", {
scale: false,
// 是否开启鼠标滚轮缩放
max: 40,
min: 0.6,
scroll: false // 缩放时是否禁用浏览器滚动功能。
});
_defineProperty(_assertThisInitialized(_this), "rightMove", false);
// 是否开启鼠标右键拖动画布
_defineProperty(_assertThisInitialized(_this), "createCanvas", []);
_defineProperty(_assertThisInitialized(_this), "canvasList", []);
_defineProperty(_assertThisInitialized(_this), "contextList", []);
_defineProperty(_assertThisInitialized(_this), "rBush", null);
_this.setOptions(options);
_this.el = el;
id++;
// this.initLowerCanvas();
_this.initCanvasContainer();
// this.initUpperCanvas();
_this.createCanvas.length && _this.createCanvasElement();
if (_this.mask && _this.maskCanvas) _this.initMaskCanvas();
_this.cacheCanvas = document.createElement('canvas');
_this.cacheCtx = _this.cacheCanvas.getContext('2d');
_this.rBush = new RBush();
return _this;
}
_createClass(Selectable, [{
key: "setOptions",
value: function setOptions(options) {
for (var key in options) {
// For each key in options object...
this[key] = options[key];
}
// if (this.background) {
// if (typeof this.background === 'string') {
// this.backgroundImage.src = this.background
// } else {
// this.backgroundImage = this.background
// }
// }
}
}, {
key: "createCanvasElement",
value: function createCanvasElement() {
var _this2 = this;
this.createCanvas.forEach(function (item) {
console.log(item);
_this2[item.canvasName] = item.el;
_this2[item.contextName] = item.el.getContext('2d');
_this2.setCanvasStyles(_this2[item.canvasName]);
_this2[item.canvasName].classList.add(item.canvasName + '-' + id);
_this2.canvasList.push(_this2[item.canvasName]);
_this2.contextList.push(_this2[item.contextName]);
});
}
}, {
key: "initLowerCanvas",
value: function initLowerCanvas() {
if (!this.el) {
throw new Error("Canvas must have an 'el' parameter and cannot be empty");
}
if (!this.lowerCanvas) {
if (typeof this.el === "string") {
this.lowerCanvas = document.querySelector(this.el);
} else {
this.lowerCanvas = this.el;
}
this.canvasList.push(this.lowerCanvas);
this.lowerCanvas.classList.add("lower-canvas-" + id);
}
this.setCanvasStyles(this.lowerCanvas);
if (!this.lowerContext) {
this.lowerContext = this.lowerCanvas.getContext("2d"); //get 2d context from the lower-canvas element
this.contextList.push(this.lowerContext);
}
}
}, {
key: "initMaskCanvas",
value: function initMaskCanvas() {
if (this.mask && this.maskCanvas) {
this.setCanvasStyles(this.maskCanvas);
this.maskCanvas.classList.add('mask-canvas-' + id);
this.maskContext = this.maskCanvas.getContext('2d');
this.canvasList.push(this.maskCanvas);
this.contextList.push(this.maskContext);
}
}
}, {
key: "initCanvasContainer",
value: function initCanvasContainer() {
var _this$lowerCanvas;
if (!this.canvasContainer) {
this.canvasContainer = document.createElement("div");
this.canvasContainer.classList.add("canvas-container-" + id);
}
this.initLowerCanvas();
this.initUpperCanvas();
if (((_this$lowerCanvas = this.lowerCanvas) === null || _this$lowerCanvas === void 0 ? void 0 : _this$lowerCanvas.parentNode) !== this.canvasContainer) {
var _this$lowerCanvas2;
(_this$lowerCanvas2 = this.lowerCanvas) === null || _this$lowerCanvas2 === void 0 || (_this$lowerCanvas2 = _this$lowerCanvas2.parentNode) === null || _this$lowerCanvas2 === void 0 ? void 0 : _this$lowerCanvas2.replaceChild(this.canvasContainer, this.lowerCanvas);
this.canvasContainer.appendChild(this.lowerCanvas);
}
this.canvasContainer.style.position = "relative";
this.canvasContainer.style.width = this.width + "px";
this.canvasContainer.style.height = this.height + "px";
}
}, {
key: "initUpperCanvas",
value: function initUpperCanvas() {
if (!this.upperCanvas) {
var _this$canvasContainer;
this.upperCanvas = document.createElement("canvas");
this.upperCanvas.classList.add("upper-canvas-" + id);
this.canvasList.push(this.upperCanvas);
(_this$canvasContainer = this.canvasContainer) === null || _this$canvasContainer === void 0 ? void 0 : _this$canvasContainer.appendChild(this.upperCanvas);
}
this.upperContext = this.upperCanvas.getContext("2d");
this.setCanvasStyles(this.upperCanvas);
this.upperCanvas.style.setProperty('z-index', id);
// this.canvasList.push(this.upperCanvas)
// this.contextList.push(this.upperContext)
}
}, {
key: "setCanvasStyles",
value: function setCanvasStyles(element) {
var customCanvasStyle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var styles = {
position: "absolute",
top: "0px",
left: "0px",
width: this.width + "px",
height: this.height + "px"
};
styles = Object.assign(styles, customCanvasStyle);
Object.entries(styles).forEach(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
property = _ref2[0],
value = _ref2[1];
element.style.setProperty(property, value);
});
element.width = this.width;
element.height = this.height;
}
}, {
key: "add",
value: function add() {
var _this3 = this,
_this$_objects;
for (var _len = arguments.length, rest = new Array(_len), _key = 0; _key < _len; _key++) {
rest[_key] = arguments[_key];
}
rest.forEach(function (item) {
item.needControl && item.setCoords && item.setCoords(_this3.lowerContext, _this3);
// this.rBush.insert(item.getBoundingBox())
});
(_this$_objects = this._objects).push.apply(_this$_objects, rest);
this.emit("obj:add");
this.requestRenderAll();
}
}, {
key: "remove",
value: function remove(drawObj) {
this._objects = this._objects.filter(function (item) {
return item.id !== drawObj.id;
});
this.emit("obj:remove");
}
/**
* remove all objects
*/
}, {
key: "removeAll",
value: function removeAll() {
this._objects = [];
}
}, {
key: "requestRenderAll",
value: function requestRenderAll() {
window.cancelAnimationFrame(this._renderAll.bind(this));
window.requestAnimationFrame(this._renderAll.bind(this));
}
}, {
key: "_renderAll",
value: function _renderAll() {
var _this4 = this;
this.contextList.forEach(function (ctx) {
ctx.setTransform(_this4.transformMatrix.clone());
_this4.clearContext(ctx);
});
if (this.backgroundImage) {
this.drawBackground(this.lowerContext);
}
this.emit("render:before");
this._objects.forEach(function (item) {
item.target ? item.target.save() : _this4.lowerContext.save();
item.render(item.target || _this4.lowerContext, _this4);
item.target ? item.target.restore() : _this4.lowerContext.restore();
});
this.emit("render:after");
}
}, {
key: "setBackground",
value: function setBackground(bg, options) {
var _this5 = this;
if (typeof bg === "string") {
this.backgroundImage.src = bg;
this.backgroundImage.onload = function () {
_this5.pixelSize = {
w: _this5.backgroundImage.naturalWidth,
h: _this5.backgroundImage.naturalHeight
};
var pixel = _this5.backgroundImage.naturalWidth / _this5.backgroundImage.naturalHeight;
// let width, height
if (_this5.imageFillMode === "cover") {
_this5.bgWidth = _this5.width;
_this5.bgHeight = _this5.width / pixel;
} else if (_this5.imageFillMode === "contain") {
_this5.bgWidth = _this5.height * pixel;
_this5.bgHeight = _this5.height;
} else if (!options && _this5.imageFillMode === 'baseCanvas') {
_this5.bgWidth = _this5.width;
_this5.bgHeight = _this5.height;
}
if (options !== null && options !== void 0 && options.width && options !== null && options !== void 0 && options.height) {
_this5.bgWidth = options.width;
_this5.bgHeight = options.height;
}
_this5.cacheCanvas.width = _this5.backgroundImage.naturalWidth;
_this5.cacheCanvas.height = _this5.backgroundImage.naturalHeight;
// this.setCanvasStyles(this.lowerCanvas);
// this.setCanvasStyles(this.upperCanvas);
_this5.cacheCtx.drawImage(_this5.backgroundImage, _this5.bgLeft || 0, _this5.bgTop || 0, _this5.backgroundImage.naturalWidth, _this5.backgroundImage.naturalHeight);
_this5.emit("img:load", _this5);
};
this.requestRenderAll();
return;
}
this.backgroundImage = bg;
var pixel = this.backgroundImage.naturalWidth ? this.backgroundImage.naturalWidth / this.backgroundImage.naturalHeight : this.backgroundImage.width ? this.backgroundImage.width / this.backgroundImage.height : 1920 / 1080;
// let width, height
if (this.imageFillMode === "cover") {
this.bgWidth = this.width;
this.bgHeight = this.width / pixel;
} else if (this.imageFillMode === "contain") {
this.bgWidth = this.height * pixel;
this.bgHeight = this.height;
} else if (!options && this.imageFillMode === 'baseCanvas') {
this.bgWidth = this.width;
this.bgHeight = this.height;
}
if (options !== null && options !== void 0 && options.width && options !== null && options !== void 0 && options.height) {
this.bgWidth = options.width;
this.bgHeight = options.height;
}
this.cacheCanvas.width = this.backgroundImage.naturalWidth;
this.cacheCanvas.height = this.backgroundImage.naturalHeight;
this.cacheCtx.drawImage(this.backgroundImage, this.bgLeft || 0, this.bgTop || 0, this.backgroundImage.naturalWidth, this.backgroundImage.naturalHeight);
this.requestRenderAll();
}
}, {
key: "drawBackground",
value: function drawBackground(ctx) {
ctx.save();
ctx.drawImage(this.cacheCanvas, this.bgLeft || 0, this.bgTop || 0, this.bgWidth, this.bgHeight);
ctx.restore();
}
}, {
key: "clearContext",
value: function clearContext(ctx) {
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, this.width, this.height); //Clear canvas
ctx.restore();
}
}, {
key: "renderTop",
value: function renderTop() {
var ctx = this.upperContext;
this.clearContext(ctx);
this.renderTopLayer(ctx);
// todo: how do i know if the after:render is for the top or normal contex?
}
}, {
key: "renderTopLayer",
value: function renderTopLayer(ctx) {
ctx.save();
// brush
// if (this.isDrawingMode && this._isCurrentlyDrawing) {
// this.freeDrawingBrush && this.freeDrawingBrush._render();
// this.contextTopDirty = true;
// }
// we render the top context - last object
ctx.globalAlpha = this.selectionOpacity;
if (this.selection && this._groupSelector) {
this._drawSelection(ctx);
this.contextTopDirty = true;
}
ctx.restore();
}
}, {
key: "_drawSelection",
value: function _drawSelection(ctx) {
var _this$_groupSelector = this._groupSelector,
x = _this$_groupSelector.x,
y = _this$_groupSelector.y,
deltaX = _this$_groupSelector.deltaX,
deltaY = _this$_groupSelector.deltaY;
var _this$transformMatrix = this.transformMatrix,
a = _this$transformMatrix.a;
_this$transformMatrix.b;
_this$transformMatrix.c;
var d = _this$transformMatrix.d,
e = _this$transformMatrix.e,
f = _this$transformMatrix.f;
x = x * a + e;
y = y * d + f;
// deltaX = deltaX * a + e
// deltaY = deltaY * a + f
if (this.selectionColor) {
ctx.fillStyle = this.selectionColor;
ctx.fillRect(x, y, deltaX * a, deltaY * a);
}
if (!this.selectionLineWidth || !this.selectionBorderColor) {
return;
}
ctx.lineWidth = this.selectionLineWidth;
ctx.strokeStyle = this.selectionBorderColor;
ctx.strokeRect(x, y, deltaX * a, deltaY * a);
}
}, {
key: "setDefaultTransform",
value: function setDefaultTransform(pointer, target) {
this.defaultTransform = {
target: target,
scaleX: target.scaleX,
scaleY: target.scaleY,
originX: target.originX,
originY: target.originY,
width: target.width * target.scaleX,
offsetX: pointer.x - target.left,
offsetY: pointer.y - target.top,
left: target.left,
top: target.top,
height: target.height * target.scaleY,
angle: target.angle
};
}
}, {
key: "set",
value: function set(key, val) {
this[key] = val;
}
}, {
key: "_findTarget",
value: function _findTarget(pos, coords) {
var _this6 = this;
var p = pos;
var points = coords.map(function (control) {
return control.getCoords(_this6.lowerContext);
});
points = coords[0].target.rotate ? points.slice(0, 7) : points;
var poly = points.map(function (p1, i) {
return [p1, points[(i + 1) % points.length]];
});
var px = p.x,
py = p.y,
flag = false;
//这个for循环是为了遍历多边形的每一个线段
for (var i = 0, l = poly.length; i < l; i++) {
var sx = poly[i][0].x,
//线段起点x坐标
sy = poly[i][0].y,
//线段起点y坐标
tx = poly[i][1].x,
//线段终点x坐标
ty = poly[i][1].y; //线段终点y坐标
// 点与多边形顶点重合
if (sx === px && sy === py || tx === px && ty === py) {
return true;
}
// 点的射线和多边形的一条边重合,并且点在边上
if (sy === ty && sy === py && (sx > px && tx < px || sx < px && tx > px)) {
return true;
}
// 判断线段两端点是否在射线两侧
if (sy < py && ty >= py || sy >= py && ty < py) {
// 求射线和线段的交点x坐标,交点y坐标当然是py
var x = sx + (py - sy) * (tx - sx) / (ty - sy);
// 点在多边形的边上
if (x === px) {
return true;
}
// x大于px来保证射线是朝右的,往一个方向射,假如射线穿过多边形的边界,flag取反一下
if (x > px) {
flag = !flag;
}
}
}
// 射线穿过多边形边界的次数为奇数时点在多边形内
if (flag) {
return true;
} else {
return false;
}
}
}, {
key: "setActiveObject",
value: function setActiveObject(loomObj) {
if (loomObj) {
this._activeObject = loomObj;
} else {
this._activeObject && this._activeObject.set("isActive", false);
this._activeObject = null;
}
}
}, {
key: "getActiveObject",
value: function getActiveObject() {
return this._activeObject;
}
}, {
key: "setCursorFromTarget",
value: function setCursorFromTarget(target) {
this.setCursor("pointer");
}
}, {
key: "setCursor",
value: function setCursor(cursor) {
this.upperCanvas.style.cursor = cursor;
}
}, {
key: "resetActive",
value: function resetActive() {
this._objects.forEach(function (item) {
item.set("isActive", false);
});
}
}, {
key: "resetTransformMatrix",
value: function resetTransformMatrix() {
this.transformMatrix = this.transformMatrix.reset();
this.requestRenderAll();
}
}, {
key: "destroy",
value: function destroy() {
// this.canvasContainer?.removeChild(this.upperCanvas)
// this.canvasContainer?.removeChild(this.lowerCanvas)
// this.canvasContainer?.replaceChild(this.lowerCanvas, this.canvasContainer)
this.offAll();
}
/**
*
* @returns {imgData: ImageData, imgUrl: base64 string}
*/
}, {
key: "toDataUrl",
value: function toDataUrl() {
var _this7 = this;
// this.upperCanvas.toDataUrl()
var canvas = document.createElement("canvas");
canvas.width = this.lowerCanvas.width;
canvas.height = this.lowerCanvas.height;
var ctx = canvas.getContext("2d");
ctx.setTransform(this.transformMatrix.clone());
this.clearContext(ctx);
ctx.save();
this._objects.forEach(function (item) {
item.render(ctx, _this7);
});
ctx.restore();
var imgBase64Url = canvas.toDataURL();
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
return {
imgData: imgData,
imgUrl: imgBase64Url
};
}
/**
*
* @param {*} fillList [{r: number, g: number, b: number, a: number, fill: number}, ...]
* @returns new Promise() ImageBitmap
*/
}, {
key: "toBitMap",
value: function toBitMap(fillList) {
var _this$toDataUrl = this.toDataUrl(),
imgData = _this$toDataUrl.imgData;
var data = imgData.data;
if (fillList) {
var fillListArr = fillList.map(function (item) {
return item.r + "-" + item.g + "-" + item.b;
});
var _loop = function _loop(i) {
var index = fillListArr.findIndex(function (ite) {
return ite === data[i] + "-" + data[i + 1] + "-" + data[i + 2];
});
if (~inde