UNPKG

paper-pen

Version:

A JavaScript library for annotations

1,444 lines (1,407 loc) 129 kB
(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