kritzel-stencil 0.0.160 → 0.0.162

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/dist/cjs/{default-text-tool.config-D10FksvZ.js → default-line-tool.config-D1Ns0NmM.js} +3156 -929
  2. package/dist/cjs/default-line-tool.config-D1Ns0NmM.js.map +1 -0
  3. package/dist/cjs/index.cjs.js +131 -126
  4. package/dist/cjs/index.cjs.js.map +1 -1
  5. package/dist/cjs/kritzel-brush-style.cjs.entry.js +1 -1
  6. package/dist/cjs/kritzel-brush-style.entry.cjs.js.map +1 -1
  7. package/dist/cjs/kritzel-color_22.cjs.entry.js +538 -198
  8. package/dist/cjs/loader.cjs.js +1 -1
  9. package/dist/cjs/stencil.cjs.js +1 -1
  10. package/dist/collection/classes/core/core.class.js +140 -3
  11. package/dist/collection/classes/core/core.class.js.map +1 -1
  12. package/dist/collection/classes/core/reviver.class.js +8 -0
  13. package/dist/collection/classes/core/reviver.class.js.map +1 -1
  14. package/dist/collection/classes/core/store.class.js +8 -0
  15. package/dist/collection/classes/core/store.class.js.map +1 -1
  16. package/dist/collection/classes/handlers/line-handle.handler.js +383 -0
  17. package/dist/collection/classes/handlers/line-handle.handler.js.map +1 -0
  18. package/dist/collection/classes/handlers/move.handler.js +2 -2
  19. package/dist/collection/classes/handlers/move.handler.js.map +1 -1
  20. package/dist/collection/classes/handlers/resize.handler.js +42 -34
  21. package/dist/collection/classes/handlers/resize.handler.js.map +1 -1
  22. package/dist/collection/classes/handlers/rotation.handler.js +12 -8
  23. package/dist/collection/classes/handlers/rotation.handler.js.map +1 -1
  24. package/dist/collection/classes/managers/anchor.manager.js +874 -0
  25. package/dist/collection/classes/managers/anchor.manager.js.map +1 -0
  26. package/dist/collection/classes/managers/cursor.manager.js +117 -0
  27. package/dist/collection/classes/managers/cursor.manager.js.map +1 -0
  28. package/dist/collection/classes/objects/base-object.class.js +4 -2
  29. package/dist/collection/classes/objects/base-object.class.js.map +1 -1
  30. package/dist/collection/classes/objects/line.class.js +564 -0
  31. package/dist/collection/classes/objects/line.class.js.map +1 -0
  32. package/dist/collection/classes/objects/selection-group.class.js +4 -0
  33. package/dist/collection/classes/objects/selection-group.class.js.map +1 -1
  34. package/dist/collection/classes/registries/icon-registry.class.js +7 -1
  35. package/dist/collection/classes/registries/icon-registry.class.js.map +1 -1
  36. package/dist/collection/classes/tools/line-tool.class.js +172 -0
  37. package/dist/collection/classes/tools/line-tool.class.js.map +1 -0
  38. package/dist/collection/classes/tools/selection-tool.class.js +41 -8
  39. package/dist/collection/classes/tools/selection-tool.class.js.map +1 -1
  40. package/dist/collection/components/core/kritzel-cursor-trail/kritzel-cursor-trail.js +1 -1
  41. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +11 -2
  42. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js.map +1 -1
  43. package/dist/collection/components/core/kritzel-engine/kritzel-engine.css +0 -14
  44. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +163 -21
  45. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js.map +1 -1
  46. package/dist/collection/components/shared/kritzel-brush-style/kritzel-brush-style.css +0 -1
  47. package/dist/collection/components/shared/kritzel-color/kritzel-color.js +2 -2
  48. package/dist/collection/components/shared/kritzel-color-palette/kritzel-color-palette.css +1 -1
  49. package/dist/collection/components/shared/kritzel-color-palette/kritzel-color-palette.js +1 -1
  50. package/dist/collection/components/shared/kritzel-dropdown/kritzel-dropdown.css +1 -1
  51. package/dist/collection/components/shared/kritzel-font/kritzel-font.js +1 -1
  52. package/dist/collection/components/shared/kritzel-font-family/kritzel-font-family.css +1 -1
  53. package/dist/collection/components/shared/kritzel-font-family/kritzel-font-family.js +1 -1
  54. package/dist/collection/components/shared/kritzel-font-size/kritzel-font-size.css +1 -1
  55. package/dist/collection/components/shared/kritzel-font-size/kritzel-font-size.js +1 -1
  56. package/dist/collection/components/shared/kritzel-menu/kritzel-menu.js +1 -1
  57. package/dist/collection/components/shared/kritzel-menu-item/kritzel-menu-item.css +1 -2
  58. package/dist/collection/components/shared/kritzel-menu-item/kritzel-menu-item.js +2 -2
  59. package/dist/collection/components/shared/kritzel-portal/kritzel-portal.js +1 -1
  60. package/dist/collection/components/shared/kritzel-split-button/kritzel-split-button.css +1 -1
  61. package/dist/collection/components/shared/kritzel-split-button/kritzel-split-button.js +1 -1
  62. package/dist/collection/components/shared/kritzel-stroke-size/kritzel-stroke-size.css +1 -1
  63. package/dist/collection/components/shared/kritzel-stroke-size/kritzel-stroke-size.js +1 -1
  64. package/dist/collection/components/shared/kritzel-tooltip/kritzel-tooltip.js +4 -4
  65. package/dist/collection/components/ui/kritzel-context-menu/kritzel-context-menu.css +1 -2
  66. package/dist/collection/components/ui/kritzel-context-menu/kritzel-context-menu.js +1 -1
  67. package/dist/collection/components/ui/kritzel-control-brush-config/kritzel-control-brush-config.css +1 -1
  68. package/dist/collection/components/ui/kritzel-control-brush-config/kritzel-control-brush-config.js +2 -2
  69. package/dist/collection/components/ui/kritzel-control-text-config/kritzel-control-text-config.css +1 -1
  70. package/dist/collection/components/ui/kritzel-control-text-config/kritzel-control-text-config.js +2 -2
  71. package/dist/collection/components/ui/kritzel-controls/kritzel-controls.css +4 -4
  72. package/dist/collection/components/ui/kritzel-controls/kritzel-controls.js +3 -3
  73. package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.css +1 -1
  74. package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.js +1 -1
  75. package/dist/collection/configs/default-engine-config.js +10 -0
  76. package/dist/collection/configs/default-engine-config.js.map +1 -1
  77. package/dist/collection/configs/default-line-tool.config.js +34 -0
  78. package/dist/collection/configs/default-line-tool.config.js.map +1 -0
  79. package/dist/collection/helpers/cursor.helper.js +58 -0
  80. package/dist/collection/helpers/cursor.helper.js.map +1 -0
  81. package/dist/collection/helpers/geometry.helper.js +42 -0
  82. package/dist/collection/helpers/geometry.helper.js.map +1 -1
  83. package/dist/collection/index.js +6 -0
  84. package/dist/collection/index.js.map +1 -1
  85. package/dist/collection/interfaces/anchor.interface.js +2 -0
  86. package/dist/collection/interfaces/anchor.interface.js.map +1 -0
  87. package/dist/collection/interfaces/arrow-head.interface.js +2 -0
  88. package/dist/collection/interfaces/arrow-head.interface.js.map +1 -0
  89. package/dist/collection/interfaces/engine-state.interface.js.map +1 -1
  90. package/dist/collection/interfaces/line-options.interface.js +2 -0
  91. package/dist/collection/interfaces/line-options.interface.js.map +1 -0
  92. package/dist/collection/interfaces/toolbar-control.interface.js.map +1 -1
  93. package/dist/components/index.js +4 -4
  94. package/dist/components/kritzel-brush-style.js +3 -3
  95. package/dist/components/kritzel-brush-style.js.map +1 -1
  96. package/dist/components/kritzel-color-palette.js +1 -1
  97. package/dist/components/kritzel-color.js +1 -1
  98. package/dist/components/kritzel-context-menu.js +1 -1
  99. package/dist/components/kritzel-control-brush-config.js +1 -1
  100. package/dist/components/kritzel-control-text-config.js +1 -1
  101. package/dist/components/kritzel-controls.js +1 -1
  102. package/dist/components/kritzel-cursor-trail.js +1 -1
  103. package/dist/components/kritzel-dropdown.js +1 -1
  104. package/dist/components/kritzel-editor.js +64 -23
  105. package/dist/components/kritzel-editor.js.map +1 -1
  106. package/dist/components/kritzel-engine.js +1 -1
  107. package/dist/components/kritzel-font-family.js +1 -1
  108. package/dist/components/kritzel-font-size.js +1 -1
  109. package/dist/components/kritzel-font.js +1 -1
  110. package/dist/components/kritzel-icon.js +1 -1
  111. package/dist/components/kritzel-menu-item.js +1 -1
  112. package/dist/components/kritzel-menu.js +1 -1
  113. package/dist/components/kritzel-portal.js +1 -1
  114. package/dist/components/kritzel-split-button.js +1 -1
  115. package/dist/components/kritzel-stroke-size.js +1 -1
  116. package/dist/components/kritzel-tooltip.js +1 -1
  117. package/dist/components/kritzel-utility-panel.js +1 -1
  118. package/dist/components/kritzel-workspace-manager.js +1 -1
  119. package/dist/components/{p-TdCTkEu0.js → p-7_lwv0zQ.js} +7 -7
  120. package/dist/components/p-7_lwv0zQ.js.map +1 -0
  121. package/dist/components/{p-CTP479Lf.js → p-9Fzdviju.js} +6 -6
  122. package/dist/components/{p-CTP479Lf.js.map → p-9Fzdviju.js.map} +1 -1
  123. package/dist/components/{p-DDmSxM5f.js → p-B6r22FSC.js} +5 -5
  124. package/dist/components/p-B6r22FSC.js.map +1 -0
  125. package/dist/components/{p-CLt3HMl6.js → p-B_3OZeom.js} +3 -3
  126. package/dist/components/{p-CLt3HMl6.js.map → p-B_3OZeom.js.map} +1 -1
  127. package/dist/components/{p-CIXPLjCu.js → p-BdwB-S9G.js} +3 -3
  128. package/dist/components/p-BdwB-S9G.js.map +1 -0
  129. package/dist/components/p-BixlbUD7.js +104 -0
  130. package/dist/components/p-BixlbUD7.js.map +1 -0
  131. package/dist/components/{p-D1uj4A4F.js → p-Brd9SxWS.js} +5 -5
  132. package/dist/components/p-Brd9SxWS.js.map +1 -0
  133. package/dist/components/{p-Ddh40W3x.js → p-CDteBYm9.js} +9 -9
  134. package/dist/components/p-CDteBYm9.js.map +1 -0
  135. package/dist/components/{p-BgznZoBH.js → p-CFH6XRL5.js} +5 -5
  136. package/dist/components/p-CFH6XRL5.js.map +1 -0
  137. package/dist/components/{p-B4kxkVe-.js → p-CRGwaUcp.js} +5 -5
  138. package/dist/components/p-CRGwaUcp.js.map +1 -0
  139. package/dist/components/{p-uuRJU2R1.js → p-Ck4lGnmt.js} +3 -3
  140. package/dist/components/{p-uuRJU2R1.js.map → p-Ck4lGnmt.js.map} +1 -1
  141. package/dist/components/{p-CK6no3mi.js → p-CkD1PQQX.js} +6 -6
  142. package/dist/components/{p-CK6no3mi.js.map → p-CkD1PQQX.js.map} +1 -1
  143. package/dist/components/{p-BQg4YML7.js → p-Cqr0Bah5.js} +12 -12
  144. package/dist/components/p-Cqr0Bah5.js.map +1 -0
  145. package/dist/components/{p-DTHqEUDc.js → p-CuhOrcET.js} +2861 -390
  146. package/dist/components/p-CuhOrcET.js.map +1 -0
  147. package/dist/components/{p-DAfkuR8U.js → p-CvLFRlQU.js} +5 -5
  148. package/dist/components/p-CvLFRlQU.js.map +1 -0
  149. package/dist/components/{p-1lIHoOlH.js → p-DKwJJuFb.js} +19 -19
  150. package/dist/components/p-DKwJJuFb.js.map +1 -0
  151. package/dist/components/{p-CsA9M6me.js → p-DZ7kxJUx.js} +8 -8
  152. package/dist/components/p-DZ7kxJUx.js.map +1 -0
  153. package/dist/components/{p-D4yvhd1d.js → p-LAsVgL2e.js} +4 -4
  154. package/dist/components/{p-D4yvhd1d.js.map → p-LAsVgL2e.js.map} +1 -1
  155. package/dist/components/{p-D5Wq4x4r.js → p-OFrACpZf.js} +3 -3
  156. package/dist/components/{p-D5Wq4x4r.js.map → p-OFrACpZf.js.map} +1 -1
  157. package/dist/components/{p-C2sWlNsJ.js → p-dMCB4tJA.js} +5 -5
  158. package/dist/components/p-dMCB4tJA.js.map +1 -0
  159. package/dist/components/{p-CBYBurdY.js → p-sokRZ7Vn.js} +49 -5
  160. package/dist/components/p-sokRZ7Vn.js.map +1 -0
  161. package/dist/esm/{default-text-tool.config-DzqpOikl.js → default-line-tool.config-C35m-d1Y.js} +3150 -930
  162. package/dist/esm/default-line-tool.config-C35m-d1Y.js.map +1 -0
  163. package/dist/esm/index.js +2 -2
  164. package/dist/esm/kritzel-brush-style.entry.js +1 -1
  165. package/dist/esm/kritzel-brush-style.entry.js.map +1 -1
  166. package/dist/esm/kritzel-color_22.entry.js +457 -117
  167. package/dist/esm/loader.js +1 -1
  168. package/dist/esm/stencil.js +1 -1
  169. package/dist/stencil/index.esm.js +1 -1
  170. package/dist/stencil/kritzel-brush-style.entry.esm.js.map +1 -1
  171. package/dist/stencil/p-385bab97.entry.js +2 -0
  172. package/dist/stencil/{p-d702c5af.entry.js.map → p-385bab97.entry.js.map} +1 -1
  173. package/dist/stencil/p-C35m-d1Y.js +2 -0
  174. package/dist/stencil/p-C35m-d1Y.js.map +1 -0
  175. package/dist/stencil/p-d142ef46.entry.js +10 -0
  176. package/dist/stencil/p-d142ef46.entry.js.map +1 -0
  177. package/dist/stencil/stencil.esm.js +1 -1
  178. package/dist/types/classes/core/core.class.d.ts +18 -0
  179. package/dist/types/classes/core/store.class.d.ts +3 -0
  180. package/dist/types/classes/handlers/line-handle.handler.d.ts +34 -0
  181. package/dist/types/classes/managers/anchor.manager.d.ts +160 -0
  182. package/dist/types/classes/managers/cursor.manager.d.ts +43 -0
  183. package/dist/types/classes/objects/line.class.d.ts +98 -0
  184. package/dist/types/classes/tools/line-tool.class.d.ts +17 -0
  185. package/dist/types/classes/tools/selection-tool.class.d.ts +4 -0
  186. package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +4 -2
  187. package/dist/types/components.d.ts +7 -5
  188. package/dist/types/configs/default-line-tool.config.d.ts +2 -0
  189. package/dist/types/helpers/cursor.helper.d.ts +22 -0
  190. package/dist/types/helpers/geometry.helper.d.ts +10 -0
  191. package/dist/types/index.d.ts +6 -0
  192. package/dist/types/interfaces/anchor.interface.d.ts +137 -0
  193. package/dist/types/interfaces/arrow-head.interface.d.ts +26 -0
  194. package/dist/types/interfaces/engine-state.interface.d.ts +15 -0
  195. package/dist/types/interfaces/line-options.interface.d.ts +21 -0
  196. package/dist/types/interfaces/toolbar-control.interface.d.ts +17 -1
  197. package/package.json +1 -1
  198. package/dist/cjs/default-text-tool.config-D10FksvZ.js.map +0 -1
  199. package/dist/components/p-1lIHoOlH.js.map +0 -1
  200. package/dist/components/p-B4kxkVe-.js.map +0 -1
  201. package/dist/components/p-BQg4YML7.js.map +0 -1
  202. package/dist/components/p-BgznZoBH.js.map +0 -1
  203. package/dist/components/p-Bhtn9qay.js +0 -98
  204. package/dist/components/p-Bhtn9qay.js.map +0 -1
  205. package/dist/components/p-C2sWlNsJ.js.map +0 -1
  206. package/dist/components/p-CBYBurdY.js.map +0 -1
  207. package/dist/components/p-CIXPLjCu.js.map +0 -1
  208. package/dist/components/p-CsA9M6me.js.map +0 -1
  209. package/dist/components/p-D1uj4A4F.js.map +0 -1
  210. package/dist/components/p-DAfkuR8U.js.map +0 -1
  211. package/dist/components/p-DDmSxM5f.js.map +0 -1
  212. package/dist/components/p-DTHqEUDc.js.map +0 -1
  213. package/dist/components/p-Ddh40W3x.js.map +0 -1
  214. package/dist/components/p-TdCTkEu0.js.map +0 -1
  215. package/dist/esm/default-text-tool.config-DzqpOikl.js.map +0 -1
  216. package/dist/stencil/p-5475442e.entry.js +0 -10
  217. package/dist/stencil/p-5475442e.entry.js.map +0 -1
  218. package/dist/stencil/p-DzqpOikl.js +0 -2
  219. package/dist/stencil/p-DzqpOikl.js.map +0 -1
  220. package/dist/stencil/p-d702c5af.entry.js +0 -2
@@ -79,6 +79,48 @@ class KritzelGeometryHelper {
79
79
  const u = -((p1a.x - p2a.x) * (p1b.y - p1a.y) - (p1a.y - p2a.y) * (p1b.x - p1a.x)) / det;
80
80
  return t >= 0 && t <= 1 && u >= 0 && u <= 1;
81
81
  }
82
+ /**
83
+ * Finds the intersection point between a line segment and a line segment.
84
+ * Returns the intersection point or null if no intersection.
85
+ */
86
+ static getLineIntersectionPoint(p1a, p1b, p2a, p2b) {
87
+ const det = (p1b.x - p1a.x) * (p2b.y - p2a.y) - (p1b.y - p1a.y) * (p2b.x - p2a.x);
88
+ if (det === 0) {
89
+ return null; // Lines are parallel
90
+ }
91
+ const t = ((p2a.x - p1a.x) * (p2b.y - p2a.y) - (p2a.y - p1a.y) * (p2b.x - p2a.x)) / det;
92
+ const u = -((p1a.x - p2a.x) * (p1b.y - p1a.y) - (p1a.y - p2a.y) * (p1b.x - p1a.x)) / det;
93
+ if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
94
+ return {
95
+ x: p1a.x + t * (p1b.x - p1a.x),
96
+ y: p1a.y + t * (p1b.y - p1a.y)
97
+ };
98
+ }
99
+ return null;
100
+ }
101
+ /**
102
+ * Finds the closest intersection point between a line segment (from lineStart to lineEnd)
103
+ * and a polygon. Returns the intersection point closest to lineStart, or null if no intersection.
104
+ */
105
+ static getLinePolygonIntersection(lineStart, lineEnd, polygon) {
106
+ const points = [polygon.topLeft, polygon.topRight, polygon.bottomRight, polygon.bottomLeft];
107
+ let closestIntersection = null;
108
+ let closestDistance = Infinity;
109
+ for (let i = 0; i < points.length; i++) {
110
+ const edgeStart = points[i];
111
+ const edgeEnd = points[(i + 1) % points.length];
112
+ const intersection = this.getLineIntersectionPoint(lineStart, lineEnd, edgeStart, edgeEnd);
113
+ if (intersection) {
114
+ const distance = Math.sqrt(Math.pow(intersection.x - lineStart.x, 2) +
115
+ Math.pow(intersection.y - lineStart.y, 2));
116
+ if (distance < closestDistance) {
117
+ closestDistance = distance;
118
+ closestIntersection = intersection;
119
+ }
120
+ }
121
+ }
122
+ return closestIntersection;
123
+ }
82
124
  }
83
125
 
84
126
  class KritzelBaseObject {
@@ -187,10 +229,10 @@ class KritzelBaseObject {
187
229
  return this.rotation * (180 / Math.PI);
188
230
  }
189
231
  get centerX() {
190
- return this.translateX + this.totalWidth / 2;
232
+ return this.translateX + this.totalWidth / 2 / this.scale;
191
233
  }
192
234
  get centerY() {
193
- return this.translateY + this.totalHeight / 2;
235
+ return this.translateY + this.totalHeight / 2 / this.scale;
194
236
  }
195
237
  constructor() {
196
238
  this.id = this.generateId();
@@ -260,6 +302,8 @@ class KritzelBaseObject {
260
302
  this.translateX = x;
261
303
  this.translateY = y;
262
304
  this._core.store.state.objects.update(this);
305
+ // Update any lines that are anchored to this object (after position is updated)
306
+ this._core.anchorManager.updateAnchorsForObject(this.id);
263
307
  }
264
308
  rotate(value) {
265
309
  this.rotation = value;
@@ -14452,6 +14496,568 @@ class KritzelImage extends KritzelBaseObject {
14452
14496
  }
14453
14497
  }
14454
14498
 
14499
+ class KritzelLine extends KritzelBaseObject {
14500
+ __class__ = 'KritzelLine';
14501
+ startX;
14502
+ startY;
14503
+ endX;
14504
+ endY;
14505
+ controlX;
14506
+ controlY;
14507
+ stroke;
14508
+ strokeWidth;
14509
+ scale = 1;
14510
+ options;
14511
+ /** Anchor connection for the start point of the line */
14512
+ startAnchor;
14513
+ /** Anchor connection for the end point of the line */
14514
+ endAnchor;
14515
+ /** Arrow head configuration for start and end of line */
14516
+ arrows;
14517
+ isVisible = true;
14518
+ isDebugInfoVisible = true;
14519
+ isCompleted = false;
14520
+ _adjustedPoints = null;
14521
+ get d() {
14522
+ if (this.controlX !== undefined && this.controlY !== undefined) {
14523
+ return `M ${this.startX} ${this.startY} Q ${this.controlX} ${this.controlY} ${this.endX} ${this.endY}`;
14524
+ }
14525
+ return `M ${this.startX} ${this.startY} L ${this.endX} ${this.endY}`;
14526
+ }
14527
+ get viewBox() {
14528
+ return `${this.x} ${this.y} ${this.width} ${this.height}`;
14529
+ }
14530
+ constructor(config) {
14531
+ super();
14532
+ this.options = config;
14533
+ this.startX = config?.startX ?? 0;
14534
+ this.startY = config?.startY ?? 0;
14535
+ this.endX = config?.endX ?? 0;
14536
+ this.endY = config?.endY ?? 0;
14537
+ this.controlX = config?.controlX;
14538
+ this.controlY = config?.controlY;
14539
+ this.translateX = config?.translateX ?? 0;
14540
+ this.translateY = config?.translateY ?? 0;
14541
+ this.scale = config?.scale ?? 1;
14542
+ this.strokeWidth = config?.strokeWidth ?? 4;
14543
+ this.stroke = config?.stroke ?? '#000000';
14544
+ this.startAnchor = config?.startAnchor;
14545
+ this.endAnchor = config?.endAnchor;
14546
+ this.arrows = config?.arrows;
14547
+ this.updateDimensions();
14548
+ }
14549
+ static create(core, options) {
14550
+ const object = new KritzelLine();
14551
+ object._core = core;
14552
+ object.id = object.generateId();
14553
+ object.workspaceId = core.store.state.activeWorkspace.id;
14554
+ object.options = options;
14555
+ object.startX = options?.startX ?? 0;
14556
+ object.startY = options?.startY ?? 0;
14557
+ object.endX = options?.endX ?? 0;
14558
+ object.endY = options?.endY ?? 0;
14559
+ object.controlX = options?.controlX;
14560
+ object.controlY = options?.controlY;
14561
+ object.translateX = options?.translateX ?? 0;
14562
+ object.translateY = options?.translateY ?? 0;
14563
+ object.scale = options?.scale ?? 1;
14564
+ object.strokeWidth = options?.strokeWidth ?? 4;
14565
+ object.stroke = options?.stroke ?? '#000000';
14566
+ object.startAnchor = options?.startAnchor;
14567
+ object.endAnchor = options?.endAnchor;
14568
+ object.arrows = options?.arrows;
14569
+ object.zIndex = core.store.currentZIndex;
14570
+ object.updateDimensions();
14571
+ return object;
14572
+ }
14573
+ resize(x, y, width, height) {
14574
+ if (width <= 1 || height <= 1) {
14575
+ return;
14576
+ }
14577
+ const scaleX = width / this.width;
14578
+ const scaleY = height / this.height;
14579
+ // Scale the line endpoints
14580
+ this.startX = this.startX * scaleX;
14581
+ this.startY = this.startY * scaleY;
14582
+ this.endX = this.endX * scaleX;
14583
+ this.endY = this.endY * scaleY;
14584
+ if (this.controlX !== undefined && this.controlY !== undefined) {
14585
+ this.controlX = this.controlX * scaleX;
14586
+ this.controlY = this.controlY * scaleY;
14587
+ }
14588
+ // Recalculate dimensions from scaled endpoints
14589
+ const { minX, minY, maxX, maxY } = this.calculateBoundingBox();
14590
+ this.width = maxX - minX;
14591
+ this.height = maxY - minY;
14592
+ this.x = minX;
14593
+ this.y = minY;
14594
+ // Set the new position
14595
+ this.translateX = x;
14596
+ this.translateY = y;
14597
+ this._adjustedPoints = null;
14598
+ this._core.store.state.objects.update(this);
14599
+ // Update anchors after the line is updated
14600
+ this._core.anchorManager.updateAnchorsForObject(this.id);
14601
+ if (this.startAnchor) {
14602
+ this._core.anchorManager.updateAnchorsForObject(this.startAnchor.objectId);
14603
+ }
14604
+ if (this.endAnchor) {
14605
+ this._core.anchorManager.updateAnchorsForObject(this.endAnchor.objectId);
14606
+ }
14607
+ }
14608
+ rotate(value) {
14609
+ this.rotation = value;
14610
+ this._adjustedPoints = null;
14611
+ this._core.store.state.objects.update(this);
14612
+ }
14613
+ move(startX, startY, endX, endY) {
14614
+ const deltaX = (startX - endX) / this._core.store.state.scale;
14615
+ const deltaY = (startY - endY) / this._core.store.state.scale;
14616
+ this.translateX += deltaX;
14617
+ this.translateY += deltaY;
14618
+ // If the line is anchored, we need to ensure the anchored endpoints stay at the target
14619
+ if (this.startAnchor) {
14620
+ this._core.anchorManager.updateAnchorsForObject(this.startAnchor.objectId);
14621
+ }
14622
+ if (this.endAnchor) {
14623
+ this._core.anchorManager.updateAnchorsForObject(this.endAnchor.objectId);
14624
+ }
14625
+ this._adjustedPoints = null;
14626
+ this._core.store.state.objects.update(this);
14627
+ }
14628
+ hitTest(x, y) {
14629
+ const halfStroke = this.strokeWidth / this.scale / 2;
14630
+ if (this._adjustedPoints === null) {
14631
+ this._adjustedPoints = this.computeAdjustedPoints();
14632
+ }
14633
+ // For curved lines, use distance to the Bezier curve
14634
+ if (this.controlX !== undefined && this.controlY !== undefined) {
14635
+ const distance = this.pointToBezierDistance(x, y);
14636
+ return distance <= halfStroke + 2;
14637
+ }
14638
+ // For straight lines, use distance to line segment
14639
+ const p1 = this._adjustedPoints[0];
14640
+ const p2 = this._adjustedPoints[1];
14641
+ const distance = this.pointToLineSegmentDistance(x, y, p1[0], p1[1], p2[0], p2[1]);
14642
+ return distance <= halfStroke + 2; // Add a small tolerance for easier selection
14643
+ }
14644
+ hitTestPolygon(polygon) {
14645
+ const halfStroke = this.strokeWidth / this.scale / 2;
14646
+ if (this._adjustedPoints === null) {
14647
+ this._adjustedPoints = this.computeAdjustedPoints();
14648
+ }
14649
+ const polyPoints = [
14650
+ { x: polygon.bottomLeft.x, y: polygon.bottomLeft.y },
14651
+ { x: polygon.bottomRight.x, y: polygon.bottomRight.y },
14652
+ { x: polygon.topRight.x, y: polygon.topRight.y },
14653
+ { x: polygon.topLeft.x, y: polygon.topLeft.y },
14654
+ ];
14655
+ // Check if any endpoint is inside the polygon
14656
+ for (const [px, py] of this._adjustedPoints) {
14657
+ if (KritzelGeometryHelper.isPointInPolygon({ x: px, y: py }, polyPoints)) {
14658
+ return true;
14659
+ }
14660
+ }
14661
+ // Check if any polygon vertex is on the line/curve
14662
+ for (const pt of polyPoints) {
14663
+ if (this.hitTest(pt.x, pt.y)) {
14664
+ return true;
14665
+ }
14666
+ }
14667
+ // For curved lines, sample points along the curve and check if any are inside the polygon
14668
+ if (this.controlX !== undefined && this.controlY !== undefined) {
14669
+ const p0 = this._adjustedPoints[0];
14670
+ const p2 = this._adjustedPoints[1];
14671
+ const controlAdjusted = this.computeAdjustedControlPoint();
14672
+ const samples = 20;
14673
+ for (let i = 1; i < samples; i++) {
14674
+ const t = i / samples;
14675
+ const oneMinusT = 1 - t;
14676
+ const bx = oneMinusT * oneMinusT * p0[0] + 2 * oneMinusT * t * controlAdjusted[0] + t * t * p2[0];
14677
+ const by = oneMinusT * oneMinusT * p0[1] + 2 * oneMinusT * t * controlAdjusted[1] + t * t * p2[1];
14678
+ if (KritzelGeometryHelper.isPointInPolygon({ x: bx, y: by }, polyPoints)) {
14679
+ return true;
14680
+ }
14681
+ // Also check if curve point is within stroke distance of polygon edges
14682
+ for (let j = 0; j < polyPoints.length; j++) {
14683
+ const q1 = polyPoints[j];
14684
+ const q2 = polyPoints[(j + 1) % polyPoints.length];
14685
+ const d = this.pointToLineSegmentDistance(bx, by, q1.x, q1.y, q2.x, q2.y);
14686
+ if (d <= halfStroke) {
14687
+ return true;
14688
+ }
14689
+ }
14690
+ }
14691
+ return false;
14692
+ }
14693
+ // For straight lines, check if line intersects any polygon edge
14694
+ const p1 = { x: this._adjustedPoints[0][0], y: this._adjustedPoints[0][1] };
14695
+ const p2 = { x: this._adjustedPoints[1][0], y: this._adjustedPoints[1][1] };
14696
+ for (let j = 0; j < polyPoints.length; j++) {
14697
+ const q1 = polyPoints[j];
14698
+ const q2 = polyPoints[(j + 1) % polyPoints.length];
14699
+ if (KritzelGeometryHelper.intersectLines(p1, p2, q1, q2)) {
14700
+ return true;
14701
+ }
14702
+ // Check distance from polygon edges to line segment
14703
+ const d1 = this.pointToLineSegmentDistance(q1.x, q1.y, p1.x, p1.y, p2.x, p2.y);
14704
+ const d2 = this.pointToLineSegmentDistance(q2.x, q2.y, p1.x, p1.y, p2.x, p2.y);
14705
+ const d3 = this.pointToLineSegmentDistance(p1.x, p1.y, q1.x, q1.y, q2.x, q2.y);
14706
+ const d4 = this.pointToLineSegmentDistance(p2.x, p2.y, q1.x, q1.y, q2.x, q2.y);
14707
+ const minD = Math.min(d1, d2, d3, d4);
14708
+ if (minD <= halfStroke) {
14709
+ return true;
14710
+ }
14711
+ }
14712
+ return false;
14713
+ }
14714
+ updatePosition(x, y) {
14715
+ this.translateX = x;
14716
+ this.translateY = y;
14717
+ this._adjustedPoints = null;
14718
+ this._core.store.state.objects.update(this);
14719
+ }
14720
+ /**
14721
+ * Updates a specific endpoint of the line (start or end).
14722
+ * The coordinates are in viewBox-local space (relative to x, y origin).
14723
+ * This method recalculates the bounding box dimensions.
14724
+ *
14725
+ * @param handleType - Which endpoint to update: 'start' or 'end'
14726
+ * @param newX - New X coordinate in viewBox-local space
14727
+ * @param newY - New Y coordinate in viewBox-local space
14728
+ */
14729
+ updateEndpoint(handleType, newX, newY) {
14730
+ // Update the appropriate endpoint
14731
+ if (handleType === 'start') {
14732
+ this.startX = newX;
14733
+ this.startY = newY;
14734
+ }
14735
+ else {
14736
+ this.endX = newX;
14737
+ this.endY = newY;
14738
+ }
14739
+ const oldWidth = this.width;
14740
+ const oldHeight = this.height;
14741
+ // Recalculate the bounding box (viewBox dimensions)
14742
+ const { minX, minY, maxX, maxY } = this.calculateBoundingBox();
14743
+ // Calculate the change in origin (in local coordinates)
14744
+ const deltaX = minX - this.x;
14745
+ const deltaY = minY - this.y;
14746
+ const newWidth = maxX - minX;
14747
+ const newHeight = maxY - minY;
14748
+ const deltaWidth = newWidth - oldWidth;
14749
+ const deltaHeight = newHeight - oldHeight;
14750
+ const deltaCx = deltaWidth / 2;
14751
+ const deltaCy = deltaHeight / 2;
14752
+ // Update viewBox dimensions
14753
+ this.x = minX;
14754
+ this.y = minY;
14755
+ this.width = newWidth;
14756
+ this.height = newHeight;
14757
+ // Rotate the delta to world coordinates before applying to translate
14758
+ const cos = Math.cos(this.rotation);
14759
+ const sin = Math.sin(this.rotation);
14760
+ // We need to compensate for:
14761
+ // 1. The shift of the top-left corner (deltaX, deltaY)
14762
+ // 2. The shift of the center point (deltaCx, deltaCy) which affects rotation pivot
14763
+ // Formula: DeltaT = R(DeltaPos + DeltaCenter) - DeltaCenter
14764
+ const vx = deltaX + deltaCx;
14765
+ const vy = deltaY + deltaCy;
14766
+ const rotatedVx = vx * cos - vy * sin;
14767
+ const rotatedVy = vx * sin + vy * cos;
14768
+ const correctionX = rotatedVx - deltaCx;
14769
+ const correctionY = rotatedVy - deltaCy;
14770
+ // Adjust translateX/Y to compensate for the origin shift
14771
+ // so the line stays visually in the same position
14772
+ this.translateX += correctionX / this.scale;
14773
+ this.translateY += correctionY / this.scale;
14774
+ // Clear cached adjusted points
14775
+ this._adjustedPoints = null;
14776
+ this._core.store.state.objects.update(this);
14777
+ }
14778
+ updateControlPoint(newX, newY) {
14779
+ this.controlX = newX;
14780
+ this.controlY = newY;
14781
+ const oldWidth = this.width;
14782
+ const oldHeight = this.height;
14783
+ // Recalculate the bounding box (viewBox dimensions)
14784
+ const { minX, minY, maxX, maxY } = this.calculateBoundingBox();
14785
+ // Calculate the change in origin (in local coordinates)
14786
+ const deltaX = minX - this.x;
14787
+ const deltaY = minY - this.y;
14788
+ const newWidth = maxX - minX;
14789
+ const newHeight = maxY - minY;
14790
+ const deltaWidth = newWidth - oldWidth;
14791
+ const deltaHeight = newHeight - oldHeight;
14792
+ const deltaCx = deltaWidth / 2;
14793
+ const deltaCy = deltaHeight / 2;
14794
+ // Update viewBox dimensions
14795
+ this.x = minX;
14796
+ this.y = minY;
14797
+ this.width = newWidth;
14798
+ this.height = newHeight;
14799
+ // Rotate the delta to world coordinates before applying to translate
14800
+ const cos = Math.cos(this.rotation);
14801
+ const sin = Math.sin(this.rotation);
14802
+ // We need to compensate for:
14803
+ // 1. The shift of the top-left corner (deltaX, deltaY)
14804
+ // 2. The shift of the center point (deltaCx, deltaCy) which affects rotation pivot
14805
+ // Formula: DeltaT = R(DeltaPos + DeltaCenter) - DeltaCenter
14806
+ const vx = deltaX + deltaCx;
14807
+ const vy = deltaY + deltaCy;
14808
+ const rotatedVx = vx * cos - vy * sin;
14809
+ const rotatedVy = vx * sin + vy * cos;
14810
+ const correctionX = rotatedVx - deltaCx;
14811
+ const correctionY = rotatedVy - deltaCy;
14812
+ // Adjust translateX/Y to compensate for the origin shift
14813
+ // so the line stays visually in the same position
14814
+ this.translateX += correctionX / this.scale;
14815
+ this.translateY += correctionY / this.scale;
14816
+ // Clear cached adjusted points
14817
+ this._adjustedPoints = null;
14818
+ this._core.store.state.objects.update(this);
14819
+ }
14820
+ computeAdjustedPoints() {
14821
+ const points = [
14822
+ [this.startX, this.startY],
14823
+ [this.endX, this.endY],
14824
+ ];
14825
+ const angle = this.rotation;
14826
+ const cos = Math.cos(angle);
14827
+ const sin = Math.sin(angle);
14828
+ const xs = points.map(p => p[0]);
14829
+ const ys = points.map(p => p[1]);
14830
+ const pivot = {
14831
+ x: (Math.min(...xs) + Math.max(...xs)) / 2,
14832
+ y: (Math.min(...ys) + Math.max(...ys)) / 2,
14833
+ };
14834
+ const { x: cx, y: cy } = pivot;
14835
+ const rotatedPoints = points.map(([x, y]) => {
14836
+ const dx = x - cx;
14837
+ const dy = y - cy;
14838
+ return [cx + dx * cos - dy * sin, cy + dx * sin + dy * cos];
14839
+ });
14840
+ return rotatedPoints.map(([px, py]) => [(px - this.x) / this.scale + this.translateX, (py - this.y) / this.scale + this.translateY]);
14841
+ }
14842
+ pointToLineSegmentDistance(x, y, x1, y1, x2, y2) {
14843
+ const A = x - x1;
14844
+ const B = y - y1;
14845
+ const C = x2 - x1;
14846
+ const D = y2 - y1;
14847
+ const dot = A * C + B * D;
14848
+ const len_sq = C * C + D * D;
14849
+ let param = -1;
14850
+ if (len_sq !== 0) {
14851
+ param = dot / len_sq;
14852
+ }
14853
+ let xx, yy;
14854
+ if (param < 0) {
14855
+ xx = x1;
14856
+ yy = y1;
14857
+ }
14858
+ else if (param > 1) {
14859
+ xx = x2;
14860
+ yy = y2;
14861
+ }
14862
+ else {
14863
+ xx = x1 + param * C;
14864
+ yy = y1 + param * D;
14865
+ }
14866
+ const dx = x - xx;
14867
+ const dy = y - yy;
14868
+ return Math.sqrt(dx * dx + dy * dy);
14869
+ }
14870
+ /**
14871
+ * Calculates the minimum distance from a point to a quadratic Bezier curve.
14872
+ * Uses sampling along the curve to find the closest point.
14873
+ */
14874
+ pointToBezierDistance(x, y) {
14875
+ if (this._adjustedPoints === null) {
14876
+ this._adjustedPoints = this.computeAdjustedPoints();
14877
+ }
14878
+ const p0 = this._adjustedPoints[0];
14879
+ const p2 = this._adjustedPoints[1];
14880
+ // Calculate the adjusted control point
14881
+ const controlAdjusted = this.computeAdjustedControlPoint();
14882
+ let minDistance = Infinity;
14883
+ const samples = 20; // Number of samples along the curve
14884
+ for (let i = 0; i <= samples; i++) {
14885
+ const t = i / samples;
14886
+ const oneMinusT = 1 - t;
14887
+ // Quadratic Bezier: B(t) = (1-t)²P₀ + 2(1-t)tP₁ + t²P₂
14888
+ const bx = oneMinusT * oneMinusT * p0[0] + 2 * oneMinusT * t * controlAdjusted[0] + t * t * p2[0];
14889
+ const by = oneMinusT * oneMinusT * p0[1] + 2 * oneMinusT * t * controlAdjusted[1] + t * t * p2[1];
14890
+ const dx = x - bx;
14891
+ const dy = y - by;
14892
+ const distance = Math.sqrt(dx * dx + dy * dy);
14893
+ if (distance < minDistance) {
14894
+ minDistance = distance;
14895
+ }
14896
+ }
14897
+ return minDistance;
14898
+ }
14899
+ /**
14900
+ * Computes the adjusted control point in world coordinates, accounting for rotation and translation.
14901
+ */
14902
+ computeAdjustedControlPoint() {
14903
+ if (this.controlX === undefined || this.controlY === undefined) {
14904
+ // Return midpoint if no control point
14905
+ return [(this.startX + this.endX) / 2, (this.startY + this.endY) / 2];
14906
+ }
14907
+ const angle = this.rotation;
14908
+ const cos = Math.cos(angle);
14909
+ const sin = Math.sin(angle);
14910
+ // Calculate pivot (center of bounding box for endpoints)
14911
+ const xs = [this.startX, this.endX];
14912
+ const ys = [this.startY, this.endY];
14913
+ const pivot = {
14914
+ x: (Math.min(...xs) + Math.max(...xs)) / 2,
14915
+ y: (Math.min(...ys) + Math.max(...ys)) / 2,
14916
+ };
14917
+ const { x: cx, y: cy } = pivot;
14918
+ // Rotate control point around pivot
14919
+ const dx = this.controlX - cx;
14920
+ const dy = this.controlY - cy;
14921
+ const rotatedX = cx + dx * cos - dy * sin;
14922
+ const rotatedY = cy + dx * sin + dy * cos;
14923
+ // Transform to world coordinates
14924
+ return [(rotatedX - this.x) / this.scale + this.translateX, (rotatedY - this.y) / this.scale + this.translateY];
14925
+ }
14926
+ get rotatedPolygon() {
14927
+ const padding = this.padding;
14928
+ // Use the bounding box which accounts for curve extrema
14929
+ const { minX, minY, maxX, maxY } = KritzelLine.calculateBoundingBox(this.startX, this.startY, this.endX, this.endY, this.controlX, this.controlY, this.strokeWidth);
14930
+ // Convert to local coordinates (relative to this.x, this.y)
14931
+ const localMinX = minX - this.x + padding;
14932
+ const localMinY = minY - this.y + padding;
14933
+ const localMaxX = maxX - this.x + padding;
14934
+ const localMaxY = maxY - this.y + padding;
14935
+ const c1 = { x: localMinX, y: localMinY }; // top-left
14936
+ const c2 = { x: localMaxX, y: localMinY }; // top-right
14937
+ const c3 = { x: localMaxX, y: localMaxY }; // bottom-right
14938
+ const c4 = { x: localMinX, y: localMaxY }; // bottom-left
14939
+ const cx = this.totalWidth / 2;
14940
+ const cy = this.totalHeight / 2;
14941
+ const angle = this.rotation;
14942
+ const cos = Math.cos(angle);
14943
+ const sin = Math.sin(angle);
14944
+ const transform = (p) => {
14945
+ const rx = (p.x - cx) * cos - (p.y - cy) * sin + cx;
14946
+ const ry = (p.x - cx) * sin + (p.y - cy) * cos + cy;
14947
+ return {
14948
+ x: rx / this.scale + this.translateX,
14949
+ y: ry / this.scale + this.translateY,
14950
+ };
14951
+ };
14952
+ return {
14953
+ topLeft: transform(c1),
14954
+ topRight: transform(c2),
14955
+ bottomRight: transform(c3),
14956
+ bottomLeft: transform(c4),
14957
+ };
14958
+ }
14959
+ /**
14960
+ * Calculates the tight bounding box for a quadratic Bezier curve or straight line.
14961
+ * For curves, it finds the extrema points where the derivative equals zero.
14962
+ */
14963
+ static calculateBoundingBox(startX, startY, endX, endY, controlX, controlY, strokeWidth) {
14964
+ let minX = Math.min(startX, endX);
14965
+ let minY = Math.min(startY, endY);
14966
+ let maxX = Math.max(startX, endX);
14967
+ let maxY = Math.max(startY, endY);
14968
+ if (controlX !== undefined && controlY !== undefined) {
14969
+ // For a quadratic Bezier: B(t) = (1-t)²P₀ + 2(1-t)tP₁ + t²P₂
14970
+ // Derivative: B'(t) = 2(1-t)(P₁-P₀) + 2t(P₂-P₁) = 0
14971
+ // Solving for t: t = (P₀ - P₁) / (P₀ - 2P₁ + P₂)
14972
+ // Find extrema for X
14973
+ const aX = startX - 2 * controlX + endX;
14974
+ if (aX !== 0) {
14975
+ const tX = (startX - controlX) / aX;
14976
+ if (tX > 0 && tX < 1) {
14977
+ const extremaX = (1 - tX) * (1 - tX) * startX + 2 * (1 - tX) * tX * controlX + tX * tX * endX;
14978
+ minX = Math.min(minX, extremaX);
14979
+ maxX = Math.max(maxX, extremaX);
14980
+ }
14981
+ }
14982
+ // Find extrema for Y
14983
+ const aY = startY - 2 * controlY + endY;
14984
+ if (aY !== 0) {
14985
+ const tY = (startY - controlY) / aY;
14986
+ if (tY > 0 && tY < 1) {
14987
+ const extremaY = (1 - tY) * (1 - tY) * startY + 2 * (1 - tY) * tY * controlY + tY * tY * endY;
14988
+ minY = Math.min(minY, extremaY);
14989
+ maxY = Math.max(maxY, extremaY);
14990
+ }
14991
+ }
14992
+ }
14993
+ const halfStroke = strokeWidth / 2;
14994
+ return {
14995
+ minX: minX - halfStroke,
14996
+ minY: minY - halfStroke,
14997
+ maxX: maxX + halfStroke,
14998
+ maxY: maxY + halfStroke,
14999
+ };
15000
+ }
15001
+ calculateBoundingBox() {
15002
+ return KritzelLine.calculateBoundingBox(this.startX, this.startY, this.endX, this.endY, this.controlX, this.controlY, this.strokeWidth);
15003
+ }
15004
+ updateDimensions() {
15005
+ const { minX, minY, maxX, maxY } = this.calculateBoundingBox();
15006
+ this.width = maxX - minX;
15007
+ this.height = maxY - minY;
15008
+ this.x = minX;
15009
+ this.y = minY;
15010
+ this.translateX = (this.x + this.translateX) / this.scale;
15011
+ this.translateY = (this.y + this.translateY) / this.scale;
15012
+ }
15013
+ /** Get unique marker ID for SVG defs at the start of the line */
15014
+ get startMarkerId() {
15015
+ return `arrow-start-${this.id}`;
15016
+ }
15017
+ /** Get unique marker ID for SVG defs at the end of the line */
15018
+ get endMarkerId() {
15019
+ return `arrow-end-${this.id}`;
15020
+ }
15021
+ /** Get the arrow size for start or end, defaulting to strokeWidth * 3 */
15022
+ getArrowSize(end) {
15023
+ const config = end === 'start' ? this.arrows?.start : this.arrows?.end;
15024
+ return config?.size ?? this.strokeWidth * 3;
15025
+ }
15026
+ /** Get the arrow fill color for start or end, defaulting to stroke color */
15027
+ getArrowFill(end) {
15028
+ const config = end === 'start' ? this.arrows?.start : this.arrows?.end;
15029
+ return config?.fill ?? this.stroke;
15030
+ }
15031
+ /**
15032
+ * Generate SVG path data for an arrow head based on the given style.
15033
+ * The path is designed to fit in a 10x10 viewBox with the tip at (10, 5).
15034
+ * @param style The arrow head style
15035
+ * @returns SVG path data string
15036
+ */
15037
+ getArrowPath(style = 'triangle') {
15038
+ switch (style) {
15039
+ case 'triangle':
15040
+ return 'M 0 0 L 10 5 L 0 10 z';
15041
+ case 'open':
15042
+ return 'M 0 0 L 10 5 L 0 10';
15043
+ case 'diamond':
15044
+ return 'M 0 5 L 5 0 L 10 5 L 5 10 z';
15045
+ case 'circle':
15046
+ return 'M 5,0 A 5,5 0 1,1 5,10 A 5,5 0 1,1 5,0';
15047
+ default:
15048
+ return 'M 0 0 L 10 5 L 0 10 z';
15049
+ }
15050
+ }
15051
+ /** Check if start arrow is enabled */
15052
+ get hasStartArrow() {
15053
+ return this.arrows?.start?.enabled === true;
15054
+ }
15055
+ /** Check if end arrow is enabled */
15056
+ get hasEndArrow() {
15057
+ return this.arrows?.end?.enabled === true;
15058
+ }
15059
+ }
15060
+
14455
15061
  var KritzelMouseButton;
14456
15062
  (function (KritzelMouseButton) {
14457
15063
  KritzelMouseButton[KritzelMouseButton["Left"] = 0] = "Left";
@@ -14676,198 +15282,318 @@ class KritzelBrushTool extends KritzelBaseTool {
14676
15282
  }
14677
15283
  }
14678
15284
 
14679
- class KritzelEraserTool extends KritzelBaseTool {
14680
- touchStartTimeout = null;
14681
- constructor(core) {
14682
- super(core);
14683
- }
14684
- handlePointerDown(event) {
14685
- if (event.pointerType === 'mouse') {
14686
- if (KritzelEventHelper.isLeftClick(event)) {
14687
- this._core.store.state.isErasing = true;
14688
- }
14689
- }
14690
- if (event.pointerType === 'touch') {
14691
- this.touchStartTimeout = setTimeout(() => {
14692
- if (this._core.store.state.pointers.size === 1 && !this._core.store.state.isScaling) {
14693
- this._core.store.state.isErasing = true;
14694
- }
14695
- }, 80);
14696
- }
15285
+ class KritzelSelectionGroup extends KritzelBaseObject {
15286
+ __class__ = 'KritzelSelectionGroup';
15287
+ // Store only object IDs instead of full objects
15288
+ objectIds = [];
15289
+ // Store snapshots of object state for transformations (rotation, resize)
15290
+ unchangedObjectSnapshots = new Map();
15291
+ snapshotRotation = 0;
15292
+ minX;
15293
+ maxX;
15294
+ minY;
15295
+ maxY;
15296
+ // Getter to retrieve actual objects from the store by their IDs
15297
+ get objects() {
15298
+ return this.objectIds
15299
+ .map(id => {
15300
+ const found = this._core.store.state.objects.filter(obj => obj.id === id);
15301
+ return found.length > 0 ? found[0] : null;
15302
+ })
15303
+ .filter(obj => obj !== null);
14697
15304
  }
14698
- handlePointerMove(event) {
14699
- if (event.pointerType === 'mouse') {
14700
- if (this._core.store.state.isErasing) {
14701
- const selectedObjects = this._core.getObjectsFromPointerEvent(event, '.object');
14702
- if (selectedObjects.length === 0)
14703
- return;
14704
- const x = this._core.store.state.pointerX;
14705
- const y = this._core.store.state.pointerY;
14706
- selectedObjects.forEach(selectedObject => {
14707
- selectedObject.markedForRemoval = selectedObject.hitTest(x, y);
14708
- });
14709
- this._core.rerender();
14710
- }
14711
- }
14712
- if (event.pointerType === 'touch') {
14713
- if (this._core.store.state.pointers.size === 1 && this._core.store.state.isErasing) {
14714
- const shadowRoot = this._core.store.state.host?.shadowRoot;
14715
- if (!shadowRoot)
14716
- return;
14717
- const selectedObjects = this._core.getObjectsFromPointerEvent(event, '.object');
14718
- if (selectedObjects.length === 0)
14719
- return;
14720
- const x = this._core.store.state.pointerX;
14721
- const y = this._core.store.state.pointerY;
14722
- selectedObjects.forEach(selectedObject => {
14723
- selectedObject.markedForRemoval = selectedObject.hitTest(x, y);
14724
- });
14725
- this._core.rerender();
14726
- }
14727
- }
15305
+ get length() {
15306
+ return this.objectIds.length;
14728
15307
  }
14729
- handlePointerUp(event) {
14730
- if (event.pointerType === 'mouse') {
14731
- if (this._core.store.state.isErasing) {
14732
- const objectsToRemove = this._core.store.allObjects.filter(object => object.markedForRemoval);
14733
- objectsToRemove.forEach(object => {
14734
- object.markedForRemoval = false;
14735
- this._core.removeObject(object);
14736
- });
14737
- if (objectsToRemove.length > 0) {
14738
- this._core.rerender();
14739
- }
14740
- this._core.store.state.isErasing = false;
14741
- this._core.engine.emitObjectsChange();
14742
- }
15308
+ static create(core) {
15309
+ const object = new KritzelSelectionGroup();
15310
+ object._core = core;
15311
+ object.id = object.generateId();
15312
+ object.workspaceId = core.store.state.activeWorkspace.id;
15313
+ object.scale = core.store.state.scale;
15314
+ object.zIndex = 99999;
15315
+ return object;
15316
+ }
15317
+ addOrRemove(object) {
15318
+ const index = this.objectIds.findIndex(id => id === object.id);
15319
+ if (index === -1) {
15320
+ this.objectIds.push(object.id);
14743
15321
  }
14744
- if (event.pointerType === 'touch') {
14745
- clearTimeout(this.touchStartTimeout);
14746
- if (this._core.store.state.isErasing) {
14747
- const objectsToRemove = this._core.store.allObjects.filter(object => object.markedForRemoval);
14748
- objectsToRemove.forEach(object => {
14749
- object.markedForRemoval = false;
14750
- this._core.removeObject(object);
14751
- });
14752
- if (objectsToRemove.length > 0) {
14753
- this._core.rerender();
14754
- }
14755
- this._core.store.state.isErasing = false;
14756
- this._core.engine.emitObjectsChange();
14757
- }
15322
+ else {
15323
+ this.objectIds.splice(index, 1);
14758
15324
  }
15325
+ this.captureUnchangedSnapshots();
15326
+ this.refreshObjectDimensions();
14759
15327
  }
14760
- }
14761
-
14762
- /**
14763
- * Browser Image Compression
14764
- * v2.0.2
14765
- * by Donald <donaldcwl@gmail.com>
14766
- * https://github.com/Donaldcwl/browser-image-compression
14767
- */
14768
-
14769
- function _mergeNamespaces(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(r){if("default"!==r&&!(r in e)){var i=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(e,r,i.get?i:{enumerable:true,get:function(){return t[r]}});}}));})),Object.freeze(e)}function copyExifWithoutOrientation(e,t){return new Promise((function(r,i){let o;return getApp1Segment(e).then((function(e){try{return o=e,r(new Blob([t.slice(0,2),o,t.slice(2)],{type:"image/jpeg"}))}catch(e){return i(e)}}),i)}))}const getApp1Segment=e=>new Promise(((t,r)=>{const i=new FileReader;i.addEventListener("load",(({target:{result:e}})=>{const i=new DataView(e);let o=0;if(65496!==i.getUint16(o))return r("not a valid JPEG");for(o+=2;;){const a=i.getUint16(o);if(65498===a)break;const s=i.getUint16(o+2);if(65505===a&&1165519206===i.getUint32(o+4)){const a=o+10;let f;switch(i.getUint16(a)){case 18761:f=true;break;case 19789:f=false;break;default:return r("TIFF header contains invalid endian")}if(42!==i.getUint16(a+2,f))return r("TIFF header contains invalid version");const l=i.getUint32(a+4,f),c=a+l+2+12*i.getUint16(a+l,f);for(let e=a+l+2;e<c;e+=12){if(274==i.getUint16(e,f)){if(3!==i.getUint16(e+2,f))return r("Orientation data type is invalid");if(1!==i.getUint32(e+4,f))return r("Orientation data count is invalid");i.setUint16(e+8,1,f);break}}return t(e.slice(o,o+2+s))}o+=2+s;}return t(new Blob)})),i.readAsArrayBuffer(e);}));var e={},t={get exports(){return e},set exports(t){e=t;}};!function(e){var r,i,UZIP={};t.exports=UZIP,UZIP.parse=function(e,t){for(var r=UZIP.bin.readUshort,i=UZIP.bin.readUint,o=0,a={},s=new Uint8Array(e),f=s.length-4;101010256!=i(s,f);)f--;o=f;o+=4;var l=r(s,o+=4);r(s,o+=2);var c=i(s,o+=2),u=i(s,o+=4);o+=4,o=u;for(var h=0;h<l;h++){i(s,o),o+=4,o+=4,o+=4,i(s,o+=4);c=i(s,o+=4);var d=i(s,o+=4),A=r(s,o+=4),g=r(s,o+2),p=r(s,o+4);o+=6;var m=i(s,o+=8);o+=4,o+=A+g+p,UZIP._readLocal(s,m,a,c,d,t);}return a},UZIP._readLocal=function(e,t,r,i,o,a){var s=UZIP.bin.readUshort,f=UZIP.bin.readUint;f(e,t),s(e,t+=4),s(e,t+=2);var l=s(e,t+=2);f(e,t+=2),f(e,t+=4),t+=4;var c=s(e,t+=8),u=s(e,t+=2);t+=2;var h=UZIP.bin.readUTF8(e,t,c);if(t+=c,t+=u,a)r[h]={size:o,csize:i};else {var d=new Uint8Array(e.buffer,t);if(0==l)r[h]=new Uint8Array(d.buffer.slice(t,t+i));else {if(8!=l)throw "unknown compression method: "+l;var A=new Uint8Array(o);UZIP.inflateRaw(d,A),r[h]=A;}}},UZIP.inflateRaw=function(e,t){return UZIP.F.inflate(e,t)},UZIP.inflate=function(e,t){return UZIP.inflateRaw(new Uint8Array(e.buffer,e.byteOffset+2,e.length-6),t)},UZIP.deflate=function(e,t){null==t&&(t={level:6});var r=0,i=new Uint8Array(50+Math.floor(1.1*e.length));i[r]=120,i[r+1]=156,r+=2,r=UZIP.F.deflateRaw(e,i,r,t.level);var o=UZIP.adler(e,0,e.length);return i[r+0]=o>>>24&255,i[r+1]=o>>>16&255,i[r+2]=o>>>8&255,i[r+3]=o>>>0&255,new Uint8Array(i.buffer,0,r+4)},UZIP.deflateRaw=function(e,t){null==t&&(t={level:6});var r=new Uint8Array(50+Math.floor(1.1*e.length)),i=UZIP.F.deflateRaw(e,r,i,t.level);return new Uint8Array(r.buffer,0,i)},UZIP.encode=function(e,t){null==t&&(t=false);var r=0,i=UZIP.bin.writeUint,o=UZIP.bin.writeUshort,a={};for(var s in e){var f=!UZIP._noNeed(s)&&!t,l=e[s],c=UZIP.crc.crc(l,0,l.length);a[s]={cpr:f,usize:l.length,crc:c,file:f?UZIP.deflateRaw(l):l};}for(var s in a)r+=a[s].file.length+30+46+2*UZIP.bin.sizeUTF8(s);r+=22;var u=new Uint8Array(r),h=0,d=[];for(var s in a){var A=a[s];d.push(h),h=UZIP._writeHeader(u,h,s,A,0);}var g=0,p=h;for(var s in a){A=a[s];d.push(h),h=UZIP._writeHeader(u,h,s,A,1,d[g++]);}var m=h-p;return i(u,h,101010256),h+=4,o(u,h+=4,g),o(u,h+=2,g),i(u,h+=2,m),i(u,h+=4,p),h+=4,h+=2,u.buffer},UZIP._noNeed=function(e){var t=e.split(".").pop().toLowerCase();return -1!="png,jpg,jpeg,zip".indexOf(t)},UZIP._writeHeader=function(e,t,r,i,o,a){var s=UZIP.bin.writeUint,f=UZIP.bin.writeUshort,l=i.file;return s(e,t,0==o?67324752:33639248),t+=4,1==o&&(t+=2),f(e,t,20),f(e,t+=2,0),f(e,t+=2,i.cpr?8:0),s(e,t+=2,0),s(e,t+=4,i.crc),s(e,t+=4,l.length),s(e,t+=4,i.usize),f(e,t+=4,UZIP.bin.sizeUTF8(r)),f(e,t+=2,0),t+=2,1==o&&(t+=2,t+=2,s(e,t+=6,a),t+=4),t+=UZIP.bin.writeUTF8(e,t,r),0==o&&(e.set(l,t),t+=l.length),t},UZIP.crc={table:function(){for(var e=new Uint32Array(256),t=0;t<256;t++){for(var r=t,i=0;i<8;i++)1&r?r=3988292384^r>>>1:r>>>=1;e[t]=r;}return e}(),update:function(e,t,r,i){for(var o=0;o<i;o++)e=UZIP.crc.table[255&(e^t[r+o])]^e>>>8;return e},crc:function(e,t,r){return 4294967295^UZIP.crc.update(4294967295,e,t,r)}},UZIP.adler=function(e,t,r){for(var i=1,o=0,a=t,s=t+r;a<s;){for(var f=Math.min(a+5552,s);a<f;)o+=i+=e[a++];i%=65521,o%=65521;}return o<<16|i},UZIP.bin={readUshort:function(e,t){return e[t]|e[t+1]<<8},writeUshort:function(e,t,r){e[t]=255&r,e[t+1]=r>>8&255;},readUint:function(e,t){return 16777216*e[t+3]+(e[t+2]<<16|e[t+1]<<8|e[t])},writeUint:function(e,t,r){e[t]=255&r,e[t+1]=r>>8&255,e[t+2]=r>>16&255,e[t+3]=r>>24&255;},readASCII:function(e,t,r){for(var i="",o=0;o<r;o++)i+=String.fromCharCode(e[t+o]);return i},writeASCII:function(e,t,r){for(var i=0;i<r.length;i++)e[t+i]=r.charCodeAt(i);},pad:function(e){return e.length<2?"0"+e:e},readUTF8:function(e,t,r){for(var i,o="",a=0;a<r;a++)o+="%"+UZIP.bin.pad(e[t+a].toString(16));try{i=decodeURIComponent(o);}catch(i){return UZIP.bin.readASCII(e,t,r)}return i},writeUTF8:function(e,t,r){for(var i=r.length,o=0,a=0;a<i;a++){var s=r.charCodeAt(a);if(0==(4294967168&s))e[t+o]=s,o++;else if(0==(4294965248&s))e[t+o]=192|s>>6,e[t+o+1]=128|s>>0&63,o+=2;else if(0==(4294901760&s))e[t+o]=224|s>>12,e[t+o+1]=128|s>>6&63,e[t+o+2]=128|s>>0&63,o+=3;else {if(0!=(4292870144&s))throw "e";e[t+o]=240|s>>18,e[t+o+1]=128|s>>12&63,e[t+o+2]=128|s>>6&63,e[t+o+3]=128|s>>0&63,o+=4;}}return o},sizeUTF8:function(e){for(var t=e.length,r=0,i=0;i<t;i++){var o=e.charCodeAt(i);if(0==(4294967168&o))r++;else if(0==(4294965248&o))r+=2;else if(0==(4294901760&o))r+=3;else {if(0!=(4292870144&o))throw "e";r+=4;}}return r}},UZIP.F={},UZIP.F.deflateRaw=function(e,t,r,i){var o=[[0,0,0,0,0],[4,4,8,4,0],[4,5,16,8,0],[4,6,16,16,0],[4,10,16,32,0],[8,16,32,32,0],[8,16,128,128,0],[8,32,128,256,0],[32,128,258,1024,1],[32,258,258,4096,1]][i],a=UZIP.F.U,s=UZIP.F._goodIndex;var f=UZIP.F._putsE,l=0,c=r<<3,u=0,h=e.length;if(0==i){for(;l<h;){f(t,c,l+(_=Math.min(65535,h-l))==h?1:0),c=UZIP.F._copyExact(e,l,_,t,c+8),l+=_;}return c>>>3}var d=a.lits,A=a.strt,g=a.prev,p=0,m=0,w=0,v=0,b=0,y=0;for(h>2&&(A[y=UZIP.F._hash(e,0)]=0),l=0;l<h;l++){if(b=y,l+1<h-2){y=UZIP.F._hash(e,l+1);var E=l+1&32767;g[E]=A[y],A[y]=E;}if(u<=l){(p>14e3||m>26697)&&h-l>100&&(u<l&&(d[p]=l-u,p+=2,u=l),c=UZIP.F._writeBlock(l==h-1||u==h?1:0,d,p,v,e,w,l-w,t,c),p=m=v=0,w=l);var F=0;l<h-2&&(F=UZIP.F._bestMatch(e,l,g,b,Math.min(o[2],h-l),o[3]));var _=F>>>16,B=65535&F;if(0!=F){B=65535&F;var U=s(_=F>>>16,a.of0);a.lhst[257+U]++;var C=s(B,a.df0);a.dhst[C]++,v+=a.exb[U]+a.dxb[C],d[p]=_<<23|l-u,d[p+1]=B<<16|U<<8|C,p+=2,u=l+_;}else a.lhst[e[l]]++;m++;}}for(w==l&&0!=e.length||(u<l&&(d[p]=l-u,p+=2,u=l),c=UZIP.F._writeBlock(1,d,p,v,e,w,l-w,t,c),p=0,m=0,p=m=v=0,w=l);0!=(7&c);)c++;return c>>>3},UZIP.F._bestMatch=function(e,t,r,i,o,a){var s=32767&t,f=r[s],l=s-f+32768&32767;if(f==s||i!=UZIP.F._hash(e,t-l))return 0;for(var c=0,u=0,h=Math.min(32767,t);l<=h&&0!=--a&&f!=s;){if(0==c||e[t+c]==e[t+c-l]){var d=UZIP.F._howLong(e,t,l);if(d>c){if(u=l,(c=d)>=o)break;l+2<d&&(d=l+2);for(var A=0,g=0;g<d-2;g++){var p=t-l+g+32768&32767,m=p-r[p]+32768&32767;m>A&&(A=m,f=p);}}}l+=(s=f)-(f=r[s])+32768&32767;}return c<<16|u},UZIP.F._howLong=function(e,t,r){if(e[t]!=e[t-r]||e[t+1]!=e[t+1-r]||e[t+2]!=e[t+2-r])return 0;var i=t,o=Math.min(e.length,t+258);for(t+=3;t<o&&e[t]==e[t-r];)t++;return t-i},UZIP.F._hash=function(e,t){return (e[t]<<8|e[t+1])+(e[t+2]<<4)&65535},UZIP.saved=0,UZIP.F._writeBlock=function(e,t,r,i,o,a,s,f,l){var c,u,h,d,A,g,p,m,w,v=UZIP.F.U,b=UZIP.F._putsF,y=UZIP.F._putsE;v.lhst[256]++,u=(c=UZIP.F.getTrees())[0],h=c[1],d=c[2],A=c[3],g=c[4],p=c[5],m=c[6],w=c[7];var E=32+(0==(l+3&7)?0:8-(l+3&7))+(s<<3),F=i+UZIP.F.contSize(v.fltree,v.lhst)+UZIP.F.contSize(v.fdtree,v.dhst),_=i+UZIP.F.contSize(v.ltree,v.lhst)+UZIP.F.contSize(v.dtree,v.dhst);_+=14+3*p+UZIP.F.contSize(v.itree,v.ihst)+(2*v.ihst[16]+3*v.ihst[17]+7*v.ihst[18]);for(var B=0;B<286;B++)v.lhst[B]=0;for(B=0;B<30;B++)v.dhst[B]=0;for(B=0;B<19;B++)v.ihst[B]=0;var U=E<F&&E<_?0:F<_?1:2;if(b(f,l,e),b(f,l+1,U),l+=3,0==U){for(;0!=(7&l);)l++;l=UZIP.F._copyExact(o,a,s,f,l);}else {var C,I;if(1==U&&(C=v.fltree,I=v.fdtree),2==U){UZIP.F.makeCodes(v.ltree,u),UZIP.F.revCodes(v.ltree,u),UZIP.F.makeCodes(v.dtree,h),UZIP.F.revCodes(v.dtree,h),UZIP.F.makeCodes(v.itree,d),UZIP.F.revCodes(v.itree,d),C=v.ltree,I=v.dtree,y(f,l,A-257),y(f,l+=5,g-1),y(f,l+=5,p-4),l+=4;for(var Q=0;Q<p;Q++)y(f,l+3*Q,v.itree[1+(v.ordr[Q]<<1)]);l+=3*p,l=UZIP.F._codeTiny(m,v.itree,f,l),l=UZIP.F._codeTiny(w,v.itree,f,l);}for(var M=a,x=0;x<r;x+=2){for(var S=t[x],R=S>>>23,T=M+(8388607&S);M<T;)l=UZIP.F._writeLit(o[M++],C,f,l);if(0!=R){var O=t[x+1],P=O>>16,H=O>>8&255,L=255&O;y(f,l=UZIP.F._writeLit(257+H,C,f,l),R-v.of0[H]),l+=v.exb[H],b(f,l=UZIP.F._writeLit(L,I,f,l),P-v.df0[L]),l+=v.dxb[L],M+=R;}}l=UZIP.F._writeLit(256,C,f,l);}return l},UZIP.F._copyExact=function(e,t,r,i,o){var a=o>>>3;return i[a]=r,i[a+1]=r>>>8,i[a+2]=255-i[a],i[a+3]=255-i[a+1],a+=4,i.set(new Uint8Array(e.buffer,t,r),a),o+(r+4<<3)},UZIP.F.getTrees=function(){for(var e=UZIP.F.U,t=UZIP.F._hufTree(e.lhst,e.ltree,15),r=UZIP.F._hufTree(e.dhst,e.dtree,15),i=[],o=UZIP.F._lenCodes(e.ltree,i),a=[],s=UZIP.F._lenCodes(e.dtree,a),f=0;f<i.length;f+=2)e.ihst[i[f]]++;for(f=0;f<a.length;f+=2)e.ihst[a[f]]++;for(var l=UZIP.F._hufTree(e.ihst,e.itree,7),c=19;c>4&&0==e.itree[1+(e.ordr[c-1]<<1)];)c--;return [t,r,l,o,s,c,i,a]},UZIP.F.getSecond=function(e){for(var t=[],r=0;r<e.length;r+=2)t.push(e[r+1]);return t},UZIP.F.nonZero=function(e){for(var t="",r=0;r<e.length;r+=2)0!=e[r+1]&&(t+=(r>>1)+",");return t},UZIP.F.contSize=function(e,t){for(var r=0,i=0;i<t.length;i++)r+=t[i]*e[1+(i<<1)];return r},UZIP.F._codeTiny=function(e,t,r,i){for(var o=0;o<e.length;o+=2){var a=e[o],s=e[o+1];i=UZIP.F._writeLit(a,t,r,i);var f=16==a?2:17==a?3:7;a>15&&(UZIP.F._putsE(r,i,s,f),i+=f);}return i},UZIP.F._lenCodes=function(e,t){for(var r=e.length;2!=r&&0==e[r-1];)r-=2;for(var i=0;i<r;i+=2){var o=e[i+1],a=i+3<r?e[i+3]:-1,s=i+5<r?e[i+5]:-1,f=0==i?-1:e[i-1];if(0==o&&a==o&&s==o){for(var l=i+5;l+2<r&&e[l+2]==o;)l+=2;(c=Math.min(l+1-i>>>1,138))<11?t.push(17,c-3):t.push(18,c-11),i+=2*c-2;}else if(o==f&&a==o&&s==o){for(l=i+5;l+2<r&&e[l+2]==o;)l+=2;var c=Math.min(l+1-i>>>1,6);t.push(16,c-3),i+=2*c-2;}else t.push(o,0);}return r>>>1},UZIP.F._hufTree=function(e,t,r){var i=[],o=e.length,a=t.length,s=0;for(s=0;s<a;s+=2)t[s]=0,t[s+1]=0;for(s=0;s<o;s++)0!=e[s]&&i.push({lit:s,f:e[s]});var f=i.length,l=i.slice(0);if(0==f)return 0;if(1==f){var c=i[0].lit;l=0==c?1:0;return t[1+(c<<1)]=1,t[1+(l<<1)]=1,1}i.sort((function(e,t){return e.f-t.f}));var u=i[0],h=i[1],d=0,A=1,g=2;for(i[0]={lit:-1,f:u.f+h.f,l:u,r:h,d:0};A!=f-1;)u=d!=A&&(g==f||i[d].f<i[g].f)?i[d++]:i[g++],h=d!=A&&(g==f||i[d].f<i[g].f)?i[d++]:i[g++],i[A++]={lit:-1,f:u.f+h.f,l:u,r:h};var p=UZIP.F.setDepth(i[A-1],0);for(p>r&&(UZIP.F.restrictDepth(l,r,p),p=r),s=0;s<f;s++)t[1+(l[s].lit<<1)]=l[s].d;return p},UZIP.F.setDepth=function(e,t){return -1!=e.lit?(e.d=t,t):Math.max(UZIP.F.setDepth(e.l,t+1),UZIP.F.setDepth(e.r,t+1))},UZIP.F.restrictDepth=function(e,t,r){var i=0,o=1<<r-t,a=0;for(e.sort((function(e,t){return t.d==e.d?e.f-t.f:t.d-e.d})),i=0;i<e.length&&e[i].d>t;i++){var s=e[i].d;e[i].d=t,a+=o-(1<<r-s);}for(a>>>=r-t;a>0;){(s=e[i].d)<t?(e[i].d++,a-=1<<t-s-1):i++;}for(;i>=0;i--)e[i].d==t&&a<0&&(e[i].d--,a++);0!=a&&console.log("debt left");},UZIP.F._goodIndex=function(e,t){var r=0;return t[16|r]<=e&&(r|=16),t[8|r]<=e&&(r|=8),t[4|r]<=e&&(r|=4),t[2|r]<=e&&(r|=2),t[1|r]<=e&&(r|=1),r},UZIP.F._writeLit=function(e,t,r,i){return UZIP.F._putsF(r,i,t[e<<1]),i+t[1+(e<<1)]},UZIP.F.inflate=function(e,t){var r=Uint8Array;if(3==e[0]&&0==e[1])return t||new r(0);var i=UZIP.F,o=i._bitsF,a=i._bitsE,s=i._decodeTiny,f=i.makeCodes,l=i.codes2map,c=i._get17,u=i.U,h=null==t;h&&(t=new r(e.length>>>2<<3));for(var d,A,g=0,p=0,m=0,w=0,v=0,b=0,y=0,E=0,F=0;0==g;)if(g=o(e,F,1),p=o(e,F+1,2),F+=3,0!=p){if(h&&(t=UZIP.F._check(t,E+(1<<17))),1==p&&(d=u.flmap,A=u.fdmap,b=511,y=31),2==p){m=a(e,F,5)+257,w=a(e,F+5,5)+1,v=a(e,F+10,4)+4,F+=14;for(var _=0;_<38;_+=2)u.itree[_]=0,u.itree[_+1]=0;var B=1;for(_=0;_<v;_++){var U=a(e,F+3*_,3);u.itree[1+(u.ordr[_]<<1)]=U,U>B&&(B=U);}F+=3*v,f(u.itree,B),l(u.itree,B,u.imap),d=u.lmap,A=u.dmap,F=s(u.imap,(1<<B)-1,m+w,e,F,u.ttree);var C=i._copyOut(u.ttree,0,m,u.ltree);b=(1<<C)-1;var I=i._copyOut(u.ttree,m,w,u.dtree);y=(1<<I)-1,f(u.ltree,C),l(u.ltree,C,d),f(u.dtree,I),l(u.dtree,I,A);}for(;;){var Q=d[c(e,F)&b];F+=15&Q;var M=Q>>>4;if(M>>>8==0)t[E++]=M;else {if(256==M)break;var x=E+M-254;if(M>264){var S=u.ldef[M-257];x=E+(S>>>3)+a(e,F,7&S),F+=7&S;}var R=A[c(e,F)&y];F+=15&R;var T=R>>>4,O=u.ddef[T],P=(O>>>4)+o(e,F,15&O);for(F+=15&O,h&&(t=UZIP.F._check(t,E+(1<<17)));E<x;)t[E]=t[E++-P],t[E]=t[E++-P],t[E]=t[E++-P],t[E]=t[E++-P];E=x;}}}else {0!=(7&F)&&(F+=8-(7&F));var H=4+(F>>>3),L=e[H-4]|e[H-3]<<8;h&&(t=UZIP.F._check(t,E+L)),t.set(new r(e.buffer,e.byteOffset+H,L),E),F=H+L<<3,E+=L;}return t.length==E?t:t.slice(0,E)},UZIP.F._check=function(e,t){var r=e.length;if(t<=r)return e;var i=new Uint8Array(Math.max(r<<1,t));return i.set(e,0),i},UZIP.F._decodeTiny=function(e,t,r,i,o,a){for(var s=UZIP.F._bitsE,f=UZIP.F._get17,l=0;l<r;){var c=e[f(i,o)&t];o+=15&c;var u=c>>>4;if(u<=15)a[l]=u,l++;else {var h=0,d=0;16==u?(d=3+s(i,o,2),o+=2,h=a[l-1]):17==u?(d=3+s(i,o,3),o+=3):18==u&&(d=11+s(i,o,7),o+=7);for(var A=l+d;l<A;)a[l]=h,l++;}}return o},UZIP.F._copyOut=function(e,t,r,i){for(var o=0,a=0,s=i.length>>>1;a<r;){var f=e[a+t];i[a<<1]=0,i[1+(a<<1)]=f,f>o&&(o=f),a++;}for(;a<s;)i[a<<1]=0,i[1+(a<<1)]=0,a++;return o},UZIP.F.makeCodes=function(e,t){for(var r,i,o,a,s=UZIP.F.U,f=e.length,l=s.bl_count,c=0;c<=t;c++)l[c]=0;for(c=1;c<f;c+=2)l[e[c]]++;var u=s.next_code;for(r=0,l[0]=0,i=1;i<=t;i++)r=r+l[i-1]<<1,u[i]=r;for(o=0;o<f;o+=2)0!=(a=e[o+1])&&(e[o]=u[a],u[a]++);},UZIP.F.codes2map=function(e,t,r){for(var i=e.length,o=UZIP.F.U.rev15,a=0;a<i;a+=2)if(0!=e[a+1])for(var s=a>>1,f=e[a+1],l=s<<4|f,c=t-f,u=e[a]<<c,h=u+(1<<c);u!=h;){r[o[u]>>>15-t]=l,u++;}},UZIP.F.revCodes=function(e,t){for(var r=UZIP.F.U.rev15,i=15-t,o=0;o<e.length;o+=2){var a=e[o]<<t-e[o+1];e[o]=r[a]>>>i;}},UZIP.F._putsE=function(e,t,r){r<<=7&t;var i=t>>>3;e[i]|=r,e[i+1]|=r>>>8;},UZIP.F._putsF=function(e,t,r){r<<=7&t;var i=t>>>3;e[i]|=r,e[i+1]|=r>>>8,e[i+2]|=r>>>16;},UZIP.F._bitsE=function(e,t,r){return (e[t>>>3]|e[1+(t>>>3)]<<8)>>>(7&t)&(1<<r)-1},UZIP.F._bitsF=function(e,t,r){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16)>>>(7&t)&(1<<r)-1},UZIP.F._get17=function(e,t){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16)>>>(7&t)},UZIP.F._get25=function(e,t){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16|e[3+(t>>>3)]<<24)>>>(7&t)},UZIP.F.U=(r=Uint16Array,i=Uint32Array,{next_code:new r(16),bl_count:new r(16),ordr:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],of0:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],exb:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],ldef:new r(32),df0:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],dxb:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],ddef:new i(32),flmap:new r(512),fltree:[],fdmap:new r(32),fdtree:[],lmap:new r(32768),ltree:[],ttree:[],dmap:new r(32768),dtree:[],imap:new r(512),itree:[],rev15:new r(32768),lhst:new i(286),dhst:new i(30),ihst:new i(19),lits:new i(15e3),strt:new r(65536),prev:new r(32768)}),function(){for(var e=UZIP.F.U,t=0;t<32768;t++){var r=t;r=(4278255360&(r=(4042322160&(r=(3435973836&(r=(2863311530&r)>>>1|(1431655765&r)<<1))>>>2|(858993459&r)<<2))>>>4|(252645135&r)<<4))>>>8|(16711935&r)<<8,e.rev15[t]=(r>>>16|r<<16)>>>17;}function pushV(e,t,r){for(;0!=t--;)e.push(0,r);}for(t=0;t<32;t++)e.ldef[t]=e.of0[t]<<3|e.exb[t],e.ddef[t]=e.df0[t]<<4|e.dxb[t];pushV(e.fltree,144,8),pushV(e.fltree,112,9),pushV(e.fltree,24,7),pushV(e.fltree,8,8),UZIP.F.makeCodes(e.fltree,9),UZIP.F.codes2map(e.fltree,9,e.flmap),UZIP.F.revCodes(e.fltree,9),pushV(e.fdtree,32,5),UZIP.F.makeCodes(e.fdtree,5),UZIP.F.codes2map(e.fdtree,5,e.fdmap),UZIP.F.revCodes(e.fdtree,5),pushV(e.itree,19,0),pushV(e.ltree,286,0),pushV(e.dtree,30,0),pushV(e.ttree,320,0);}();}();var UZIP=_mergeNamespaces({__proto__:null,default:e},[e]);const UPNG=function(){var e={nextZero(e,t){for(;0!=e[t];)t++;return t},readUshort:(e,t)=>e[t]<<8|e[t+1],writeUshort(e,t,r){e[t]=r>>8&255,e[t+1]=255&r;},readUint:(e,t)=>16777216*e[t]+(e[t+1]<<16|e[t+2]<<8|e[t+3]),writeUint(e,t,r){e[t]=r>>24&255,e[t+1]=r>>16&255,e[t+2]=r>>8&255,e[t+3]=255&r;},readASCII(e,t,r){let i="";for(let o=0;o<r;o++)i+=String.fromCharCode(e[t+o]);return i},writeASCII(e,t,r){for(let i=0;i<r.length;i++)e[t+i]=r.charCodeAt(i);},readBytes(e,t,r){const i=[];for(let o=0;o<r;o++)i.push(e[t+o]);return i},pad:e=>e.length<2?`0${e}`:e,readUTF8(t,r,i){let o,a="";for(let o=0;o<i;o++)a+=`%${e.pad(t[r+o].toString(16))}`;try{o=decodeURIComponent(a);}catch(o){return e.readASCII(t,r,i)}return o}};function decodeImage(t,r,i,o){const a=r*i,s=_getBPP(o),f=Math.ceil(r*s/8),l=new Uint8Array(4*a),c=new Uint32Array(l.buffer),{ctype:u}=o,{depth:h}=o,d=e.readUshort;if(6==u){const e=a<<2;if(8==h)for(var A=0;A<e;A+=4)l[A]=t[A],l[A+1]=t[A+1],l[A+2]=t[A+2],l[A+3]=t[A+3];if(16==h)for(A=0;A<e;A++)l[A]=t[A<<1];}else if(2==u){const e=o.tabs.tRNS;if(null==e){if(8==h)for(A=0;A<a;A++){var g=3*A;c[A]=255<<24|t[g+2]<<16|t[g+1]<<8|t[g];}if(16==h)for(A=0;A<a;A++){g=6*A;c[A]=255<<24|t[g+4]<<16|t[g+2]<<8|t[g];}}else {var p=e[0];const r=e[1],i=e[2];if(8==h)for(A=0;A<a;A++){var m=A<<2;g=3*A;c[A]=255<<24|t[g+2]<<16|t[g+1]<<8|t[g],t[g]==p&&t[g+1]==r&&t[g+2]==i&&(l[m+3]=0);}if(16==h)for(A=0;A<a;A++){m=A<<2,g=6*A;c[A]=255<<24|t[g+4]<<16|t[g+2]<<8|t[g],d(t,g)==p&&d(t,g+2)==r&&d(t,g+4)==i&&(l[m+3]=0);}}}else if(3==u){const e=o.tabs.PLTE,s=o.tabs.tRNS,c=s?s.length:0;if(1==h)for(var w=0;w<i;w++){var v=w*f,b=w*r;for(A=0;A<r;A++){m=b+A<<2;var y=3*(E=t[v+(A>>3)]>>7-((7&A)<<0)&1);l[m]=e[y],l[m+1]=e[y+1],l[m+2]=e[y+2],l[m+3]=E<c?s[E]:255;}}if(2==h)for(w=0;w<i;w++)for(v=w*f,b=w*r,A=0;A<r;A++){m=b+A<<2,y=3*(E=t[v+(A>>2)]>>6-((3&A)<<1)&3);l[m]=e[y],l[m+1]=e[y+1],l[m+2]=e[y+2],l[m+3]=E<c?s[E]:255;}if(4==h)for(w=0;w<i;w++)for(v=w*f,b=w*r,A=0;A<r;A++){m=b+A<<2,y=3*(E=t[v+(A>>1)]>>4-((1&A)<<2)&15);l[m]=e[y],l[m+1]=e[y+1],l[m+2]=e[y+2],l[m+3]=E<c?s[E]:255;}if(8==h)for(A=0;A<a;A++){var E;m=A<<2,y=3*(E=t[A]);l[m]=e[y],l[m+1]=e[y+1],l[m+2]=e[y+2],l[m+3]=E<c?s[E]:255;}}else if(4==u){if(8==h)for(A=0;A<a;A++){m=A<<2;var F=t[_=A<<1];l[m]=F,l[m+1]=F,l[m+2]=F,l[m+3]=t[_+1];}if(16==h)for(A=0;A<a;A++){var _;m=A<<2,F=t[_=A<<2];l[m]=F,l[m+1]=F,l[m+2]=F,l[m+3]=t[_+2];}}else if(0==u)for(p=o.tabs.tRNS?o.tabs.tRNS:-1,w=0;w<i;w++){const e=w*f,i=w*r;if(1==h)for(var B=0;B<r;B++){var U=(F=255*(t[e+(B>>>3)]>>>7-(7&B)&1))==255*p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}else if(2==h)for(B=0;B<r;B++){U=(F=85*(t[e+(B>>>2)]>>>6-((3&B)<<1)&3))==85*p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}else if(4==h)for(B=0;B<r;B++){U=(F=17*(t[e+(B>>>1)]>>>4-((1&B)<<2)&15))==17*p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}else if(8==h)for(B=0;B<r;B++){U=(F=t[e+B])==p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}else if(16==h)for(B=0;B<r;B++){F=t[e+(B<<1)],U=d(t,e+(B<<1))==p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}}return l}function _decompress(e,r,i,o){const a=_getBPP(e),s=Math.ceil(i*a/8),f=new Uint8Array((s+1+e.interlace)*o);return r=e.tabs.CgBI?t(r,f):_inflate(r,f),0==e.interlace?r=_filterZero(r,e,0,i,o):1==e.interlace&&(r=function _readInterlace(e,t){const r=t.width,i=t.height,o=_getBPP(t),a=o>>3,s=Math.ceil(r*o/8),f=new Uint8Array(i*s);let l=0;const c=[0,0,4,0,2,0,1],u=[0,4,0,2,0,1,0],h=[8,8,8,4,4,2,2],d=[8,8,4,4,2,2,1];let A=0;for(;A<7;){const p=h[A],m=d[A];let w=0,v=0,b=c[A];for(;b<i;)b+=p,v++;let y=u[A];for(;y<r;)y+=m,w++;const E=Math.ceil(w*o/8);_filterZero(e,t,l,w,v);let F=0,_=c[A];for(;_<i;){let t=u[A],i=l+F*E<<3;for(;t<r;){var g;if(1==o)g=(g=e[i>>3])>>7-(7&i)&1,f[_*s+(t>>3)]|=g<<7-((7&t)<<0);if(2==o)g=(g=e[i>>3])>>6-(7&i)&3,f[_*s+(t>>2)]|=g<<6-((3&t)<<1);if(4==o)g=(g=e[i>>3])>>4-(7&i)&15,f[_*s+(t>>1)]|=g<<4-((1&t)<<2);if(o>=8){const r=_*s+t*a;for(let t=0;t<a;t++)f[r+t]=e[(i>>3)+t];}i+=o,t+=m;}F++,_+=p;}w*v!=0&&(l+=v*(1+E)),A+=1;}return f}(r,e)),r}function _inflate(e,r){return t(new Uint8Array(e.buffer,2,e.length-6),r)}var t=function(){const e={H:{}};return e.H.N=function(t,r){const i=Uint8Array;let o,a,s=0,f=0,l=0,c=0,u=0,h=0,d=0,A=0,g=0;if(3==t[0]&&0==t[1])return r||new i(0);const p=e.H,m=p.b,w=p.e,v=p.R,b=p.n,y=p.A,E=p.Z,F=p.m,_=null==r;for(_&&(r=new i(t.length>>>2<<5));0==s;)if(s=m(t,g,1),f=m(t,g+1,2),g+=3,0!=f){if(_&&(r=e.H.W(r,A+(1<<17))),1==f&&(o=F.J,a=F.h,h=511,d=31),2==f){l=w(t,g,5)+257,c=w(t,g+5,5)+1,u=w(t,g+10,4)+4,g+=14;let e=1;for(var B=0;B<38;B+=2)F.Q[B]=0,F.Q[B+1]=0;for(B=0;B<u;B++){const r=w(t,g+3*B,3);F.Q[1+(F.X[B]<<1)]=r,r>e&&(e=r);}g+=3*u,b(F.Q,e),y(F.Q,e,F.u),o=F.w,a=F.d,g=v(F.u,(1<<e)-1,l+c,t,g,F.v);const r=p.V(F.v,0,l,F.C);h=(1<<r)-1;const i=p.V(F.v,l,c,F.D);d=(1<<i)-1,b(F.C,r),y(F.C,r,o),b(F.D,i),y(F.D,i,a);}for(;;){const e=o[E(t,g)&h];g+=15&e;const i=e>>>4;if(i>>>8==0)r[A++]=i;else {if(256==i)break;{let e=A+i-254;if(i>264){const r=F.q[i-257];e=A+(r>>>3)+w(t,g,7&r),g+=7&r;}const o=a[E(t,g)&d];g+=15&o;const s=o>>>4,f=F.c[s],l=(f>>>4)+m(t,g,15&f);for(g+=15&f;A<e;)r[A]=r[A++-l],r[A]=r[A++-l],r[A]=r[A++-l],r[A]=r[A++-l];A=e;}}}}else {0!=(7&g)&&(g+=8-(7&g));const o=4+(g>>>3),a=t[o-4]|t[o-3]<<8;_&&(r=e.H.W(r,A+a)),r.set(new i(t.buffer,t.byteOffset+o,a),A),g=o+a<<3,A+=a;}return r.length==A?r:r.slice(0,A)},e.H.W=function(e,t){const r=e.length;if(t<=r)return e;const i=new Uint8Array(r<<1);return i.set(e,0),i},e.H.R=function(t,r,i,o,a,s){const f=e.H.e,l=e.H.Z;let c=0;for(;c<i;){const e=t[l(o,a)&r];a+=15&e;const i=e>>>4;if(i<=15)s[c]=i,c++;else {let e=0,t=0;16==i?(t=3+f(o,a,2),a+=2,e=s[c-1]):17==i?(t=3+f(o,a,3),a+=3):18==i&&(t=11+f(o,a,7),a+=7);const r=c+t;for(;c<r;)s[c]=e,c++;}}return a},e.H.V=function(e,t,r,i){let o=0,a=0;const s=i.length>>>1;for(;a<r;){const r=e[a+t];i[a<<1]=0,i[1+(a<<1)]=r,r>o&&(o=r),a++;}for(;a<s;)i[a<<1]=0,i[1+(a<<1)]=0,a++;return o},e.H.n=function(t,r){const i=e.H.m,o=t.length;let a,s,f;let l;const c=i.j;for(var u=0;u<=r;u++)c[u]=0;for(u=1;u<o;u+=2)c[t[u]]++;const h=i.K;for(a=0,c[0]=0,s=1;s<=r;s++)a=a+c[s-1]<<1,h[s]=a;for(f=0;f<o;f+=2)l=t[f+1],0!=l&&(t[f]=h[l],h[l]++);},e.H.A=function(t,r,i){const o=t.length,a=e.H.m.r;for(let e=0;e<o;e+=2)if(0!=t[e+1]){const o=e>>1,s=t[e+1],f=o<<4|s,l=r-s;let c=t[e]<<l;const u=c+(1<<l);for(;c!=u;){i[a[c]>>>15-r]=f,c++;}}},e.H.l=function(t,r){const i=e.H.m.r,o=15-r;for(let e=0;e<t.length;e+=2){const a=t[e]<<r-t[e+1];t[e]=i[a]>>>o;}},e.H.M=function(e,t,r){r<<=7&t;const i=t>>>3;e[i]|=r,e[i+1]|=r>>>8;},e.H.I=function(e,t,r){r<<=7&t;const i=t>>>3;e[i]|=r,e[i+1]|=r>>>8,e[i+2]|=r>>>16;},e.H.e=function(e,t,r){return (e[t>>>3]|e[1+(t>>>3)]<<8)>>>(7&t)&(1<<r)-1},e.H.b=function(e,t,r){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16)>>>(7&t)&(1<<r)-1},e.H.Z=function(e,t){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16)>>>(7&t)},e.H.i=function(e,t){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16|e[3+(t>>>3)]<<24)>>>(7&t)},e.H.m=function(){const e=Uint16Array,t=Uint32Array;return {K:new e(16),j:new e(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new e(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new t(32),J:new e(512),_:[],h:new e(32),$:[],w:new e(32768),C:[],v:[],d:new e(32768),D:[],u:new e(512),Q:[],r:new e(32768),s:new t(286),Y:new t(30),a:new t(19),t:new t(15e3),k:new e(65536),g:new e(32768)}}(),function(){const t=e.H.m;for(var r=0;r<32768;r++){let e=r;e=(2863311530&e)>>>1|(1431655765&e)<<1,e=(3435973836&e)>>>2|(858993459&e)<<2,e=(4042322160&e)>>>4|(252645135&e)<<4,e=(4278255360&e)>>>8|(16711935&e)<<8,t.r[r]=(e>>>16|e<<16)>>>17;}function n(e,t,r){for(;0!=t--;)e.push(0,r);}for(r=0;r<32;r++)t.q[r]=t.S[r]<<3|t.T[r],t.c[r]=t.p[r]<<4|t.z[r];n(t._,144,8),n(t._,112,9),n(t._,24,7),n(t._,8,8),e.H.n(t._,9),e.H.A(t._,9,t.J),e.H.l(t._,9),n(t.$,32,5),e.H.n(t.$,5),e.H.A(t.$,5,t.h),e.H.l(t.$,5),n(t.Q,19,0),n(t.C,286,0),n(t.D,30,0),n(t.v,320,0);}(),e.H.N}();function _getBPP(e){return [1,null,3,1,2,null,4][e.ctype]*e.depth}function _filterZero(e,t,r,i,o){let a=_getBPP(t);const s=Math.ceil(i*a/8);let f,l;a=Math.ceil(a/8);let c=e[r],u=0;if(c>1&&(e[r]=[0,0,1][c-2]),3==c)for(u=a;u<s;u++)e[u+1]=e[u+1]+(e[u+1-a]>>>1)&255;for(let t=0;t<o;t++)if(f=r+t*s,l=f+t+1,c=e[l-1],u=0,0==c)for(;u<s;u++)e[f+u]=e[l+u];else if(1==c){for(;u<a;u++)e[f+u]=e[l+u];for(;u<s;u++)e[f+u]=e[l+u]+e[f+u-a];}else if(2==c)for(;u<s;u++)e[f+u]=e[l+u]+e[f+u-s];else if(3==c){for(;u<a;u++)e[f+u]=e[l+u]+(e[f+u-s]>>>1);for(;u<s;u++)e[f+u]=e[l+u]+(e[f+u-s]+e[f+u-a]>>>1);}else {for(;u<a;u++)e[f+u]=e[l+u]+_paeth(0,e[f+u-s],0);for(;u<s;u++)e[f+u]=e[l+u]+_paeth(e[f+u-a],e[f+u-s],e[f+u-a-s]);}return e}function _paeth(e,t,r){const i=e+t-r,o=i-e,a=i-t,s=i-r;return o*o<=a*a&&o*o<=s*s?e:a*a<=s*s?t:r}function _IHDR(t,r,i){i.width=e.readUint(t,r),r+=4,i.height=e.readUint(t,r),r+=4,i.depth=t[r],r++,i.ctype=t[r],r++,i.compress=t[r],r++,i.filter=t[r],r++,i.interlace=t[r],r++;}function _copyTile(e,t,r,i,o,a,s,f,l){const c=Math.min(t,o),u=Math.min(r,a);let h=0,d=0;for(let r=0;r<u;r++)for(let a=0;a<c;a++)if(s>=0&&f>=0?(h=r*t+a<<2,d=(f+r)*o+s+a<<2):(h=(-f+r)*t-s+a<<2,d=r*o+a<<2),0==l)i[d]=e[h],i[d+1]=e[h+1],i[d+2]=e[h+2],i[d+3]=e[h+3];else if(1==l){var A=e[h+3]*(1/255),g=e[h]*A,p=e[h+1]*A,m=e[h+2]*A,w=i[d+3]*(1/255),v=i[d]*w,b=i[d+1]*w,y=i[d+2]*w;const t=1-A,r=A+w*t,o=0==r?0:1/r;i[d+3]=255*r,i[d+0]=(g+v*t)*o,i[d+1]=(p+b*t)*o,i[d+2]=(m+y*t)*o;}else if(2==l){A=e[h+3],g=e[h],p=e[h+1],m=e[h+2],w=i[d+3],v=i[d],b=i[d+1],y=i[d+2];A==w&&g==v&&p==b&&m==y?(i[d]=0,i[d+1]=0,i[d+2]=0,i[d+3]=0):(i[d]=g,i[d+1]=p,i[d+2]=m,i[d+3]=A);}else if(3==l){A=e[h+3],g=e[h],p=e[h+1],m=e[h+2],w=i[d+3],v=i[d],b=i[d+1],y=i[d+2];if(A==w&&g==v&&p==b&&m==y)continue;if(A<220&&w>20)return false}return true}return {decode:function decode(r){const i=new Uint8Array(r);let o=8;const a=e,s=a.readUshort,f=a.readUint,l={tabs:{},frames:[]},c=new Uint8Array(i.length);let u,h=0,d=0;const A=[137,80,78,71,13,10,26,10];for(var g=0;g<8;g++)if(i[g]!=A[g])throw "The input is not a PNG file!";for(;o<i.length;){const e=a.readUint(i,o);o+=4;const r=a.readASCII(i,o,4);if(o+=4,"IHDR"==r)_IHDR(i,o,l);else if("iCCP"==r){for(var p=o;0!=i[p];)p++;a.readASCII(i,o,p-o);const s=i.slice(p+2,o+e);let f=null;try{f=_inflate(s);}catch(e){f=t(s);}l.tabs[r]=f;}else if("CgBI"==r)l.tabs[r]=i.slice(o,o+4);else if("IDAT"==r){for(g=0;g<e;g++)c[h+g]=i[o+g];h+=e;}else if("acTL"==r)l.tabs[r]={num_frames:f(i,o),num_plays:f(i,o+4)},u=new Uint8Array(i.length);else if("fcTL"==r){if(0!=d)(E=l.frames[l.frames.length-1]).data=_decompress(l,u.slice(0,d),E.rect.width,E.rect.height),d=0;const e={x:f(i,o+12),y:f(i,o+16),width:f(i,o+4),height:f(i,o+8)};let t=s(i,o+22);t=s(i,o+20)/(0==t?100:t);const r={rect:e,delay:Math.round(1e3*t),dispose:i[o+24],blend:i[o+25]};l.frames.push(r);}else if("fdAT"==r){for(g=0;g<e-4;g++)u[d+g]=i[o+g+4];d+=e-4;}else if("pHYs"==r)l.tabs[r]=[a.readUint(i,o),a.readUint(i,o+4),i[o+8]];else if("cHRM"==r){l.tabs[r]=[];for(g=0;g<8;g++)l.tabs[r].push(a.readUint(i,o+4*g));}else if("tEXt"==r||"zTXt"==r){null==l.tabs[r]&&(l.tabs[r]={});var m=a.nextZero(i,o),w=a.readASCII(i,o,m-o),v=o+e-m-1;if("tEXt"==r)y=a.readASCII(i,m+1,v);else {var b=_inflate(i.slice(m+2,m+2+v));y=a.readUTF8(b,0,b.length);}l.tabs[r][w]=y;}else if("iTXt"==r){null==l.tabs[r]&&(l.tabs[r]={});m=0,p=o;m=a.nextZero(i,p);w=a.readASCII(i,p,m-p);const t=i[p=m+1];var y;p+=2,m=a.nextZero(i,p),a.readASCII(i,p,m-p),p=m+1,m=a.nextZero(i,p),a.readUTF8(i,p,m-p);v=e-((p=m+1)-o);if(0==t)y=a.readUTF8(i,p,v);else {b=_inflate(i.slice(p,p+v));y=a.readUTF8(b,0,b.length);}l.tabs[r][w]=y;}else if("PLTE"==r)l.tabs[r]=a.readBytes(i,o,e);else if("hIST"==r){const e=l.tabs.PLTE.length/3;l.tabs[r]=[];for(g=0;g<e;g++)l.tabs[r].push(s(i,o+2*g));}else if("tRNS"==r)3==l.ctype?l.tabs[r]=a.readBytes(i,o,e):0==l.ctype?l.tabs[r]=s(i,o):2==l.ctype&&(l.tabs[r]=[s(i,o),s(i,o+2),s(i,o+4)]);else if("gAMA"==r)l.tabs[r]=a.readUint(i,o)/1e5;else if("sRGB"==r)l.tabs[r]=i[o];else if("bKGD"==r)0==l.ctype||4==l.ctype?l.tabs[r]=[s(i,o)]:2==l.ctype||6==l.ctype?l.tabs[r]=[s(i,o),s(i,o+2),s(i,o+4)]:3==l.ctype&&(l.tabs[r]=i[o]);else if("IEND"==r)break;o+=e,a.readUint(i,o),o+=4;}var E;return 0!=d&&((E=l.frames[l.frames.length-1]).data=_decompress(l,u.slice(0,d),E.rect.width,E.rect.height)),l.data=_decompress(l,c,l.width,l.height),delete l.compress,delete l.interlace,delete l.filter,l},toRGBA8:function toRGBA8(e){const t=e.width,r=e.height;if(null==e.tabs.acTL)return [decodeImage(e.data,t,r,e).buffer];const i=[];null==e.frames[0].data&&(e.frames[0].data=e.data);const o=t*r*4,a=new Uint8Array(o),s=new Uint8Array(o),f=new Uint8Array(o);for(let c=0;c<e.frames.length;c++){const u=e.frames[c],h=u.rect.x,d=u.rect.y,A=u.rect.width,g=u.rect.height,p=decodeImage(u.data,A,g,e);if(0!=c)for(var l=0;l<o;l++)f[l]=a[l];if(0==u.blend?_copyTile(p,A,g,a,t,r,h,d,0):1==u.blend&&_copyTile(p,A,g,a,t,r,h,d,1),i.push(a.buffer.slice(0)),0==u.dispose);else if(1==u.dispose)_copyTile(s,A,g,a,t,r,h,d,0);else if(2==u.dispose)for(l=0;l<o;l++)a[l]=f[l];}return i},_paeth:_paeth,_copyTile:_copyTile,_bin:e}}();!function(){const{_copyTile:e}=UPNG,{_bin:t}=UPNG,r=UPNG._paeth;var i={table:function(){const e=new Uint32Array(256);for(let t=0;t<256;t++){let r=t;for(let e=0;e<8;e++)1&r?r=3988292384^r>>>1:r>>>=1;e[t]=r;}return e}(),update(e,t,r,o){for(let a=0;a<o;a++)e=i.table[255&(e^t[r+a])]^e>>>8;return e},crc:(e,t,r)=>4294967295^i.update(4294967295,e,t,r)};function addErr(e,t,r,i){t[r]+=e[0]*i>>4,t[r+1]+=e[1]*i>>4,t[r+2]+=e[2]*i>>4,t[r+3]+=e[3]*i>>4;}function N(e){return Math.max(0,Math.min(255,e))}function D(e,t){const r=e[0]-t[0],i=e[1]-t[1],o=e[2]-t[2],a=e[3]-t[3];return r*r+i*i+o*o+a*a}function dither(e,t,r,i,o,a,s){null==s&&(s=1);const f=i.length,l=[];for(var c=0;c<f;c++){const e=i[c];l.push([e>>>0&255,e>>>8&255,e>>>16&255,e>>>24&255]);}for(c=0;c<f;c++){let e=4294967295;for(var u=0,h=0;h<f;h++){var d=D(l[c],l[h]);h!=c&&d<e&&(e=d,u=h);}}const A=new Uint32Array(o.buffer),g=new Int16Array(t*r*4),p=[0,8,2,10,12,4,14,6,3,11,1,9,15,7,13,5];for(c=0;c<p.length;c++)p[c]=255*((p[c]+.5)/16-.5);for(let o=0;o<r;o++)for(let w=0;w<t;w++){var m;c=4*(o*t+w);if(2!=s)m=[N(e[c]+g[c]),N(e[c+1]+g[c+1]),N(e[c+2]+g[c+2]),N(e[c+3]+g[c+3])];else {d=p[4*(3&o)+(3&w)];m=[N(e[c]+d),N(e[c+1]+d),N(e[c+2]+d),N(e[c+3]+d)];}u=0;let v=16777215;for(h=0;h<f;h++){const e=D(m,l[h]);e<v&&(v=e,u=h);}const b=l[u],y=[m[0]-b[0],m[1]-b[1],m[2]-b[2],m[3]-b[3]];1==s&&(w!=t-1&&addErr(y,g,c+4,7),o!=r-1&&(0!=w&&addErr(y,g,c+4*t-4,3),addErr(y,g,c+4*t,5),w!=t-1&&addErr(y,g,c+4*t+4,1))),a[c>>2]=u,A[c>>2]=i[u];}}function _main(e,r,o,a,s){null==s&&(s={});const{crc:f}=i,l=t.writeUint,c=t.writeUshort,u=t.writeASCII;let h=8;const d=e.frames.length>1;let A,g=false,p=33+(d?20:0);if(null!=s.sRGB&&(p+=13),null!=s.pHYs&&(p+=21),null!=s.iCCP&&(A=pako.deflate(s.iCCP),p+=21+A.length+4),3==e.ctype){for(var m=e.plte.length,w=0;w<m;w++)e.plte[w]>>>24!=255&&(g=true);p+=8+3*m+4+(g?8+1*m+4:0);}for(var v=0;v<e.frames.length;v++){d&&(p+=38),p+=(F=e.frames[v]).cimg.length+12,0!=v&&(p+=4);}p+=12;const b=new Uint8Array(p),y=[137,80,78,71,13,10,26,10];for(w=0;w<8;w++)b[w]=y[w];if(l(b,h,13),h+=4,u(b,h,"IHDR"),h+=4,l(b,h,r),h+=4,l(b,h,o),h+=4,b[h]=e.depth,h++,b[h]=e.ctype,h++,b[h]=0,h++,b[h]=0,h++,b[h]=0,h++,l(b,h,f(b,h-17,17)),h+=4,null!=s.sRGB&&(l(b,h,1),h+=4,u(b,h,"sRGB"),h+=4,b[h]=s.sRGB,h++,l(b,h,f(b,h-5,5)),h+=4),null!=s.iCCP){const e=13+A.length;l(b,h,e),h+=4,u(b,h,"iCCP"),h+=4,u(b,h,"ICC profile"),h+=11,h+=2,b.set(A,h),h+=A.length,l(b,h,f(b,h-(e+4),e+4)),h+=4;}if(null!=s.pHYs&&(l(b,h,9),h+=4,u(b,h,"pHYs"),h+=4,l(b,h,s.pHYs[0]),h+=4,l(b,h,s.pHYs[1]),h+=4,b[h]=s.pHYs[2],h++,l(b,h,f(b,h-13,13)),h+=4),d&&(l(b,h,8),h+=4,u(b,h,"acTL"),h+=4,l(b,h,e.frames.length),h+=4,l(b,h,null!=s.loop?s.loop:0),h+=4,l(b,h,f(b,h-12,12)),h+=4),3==e.ctype){l(b,h,3*(m=e.plte.length)),h+=4,u(b,h,"PLTE"),h+=4;for(w=0;w<m;w++){const t=3*w,r=e.plte[w],i=255&r,o=r>>>8&255,a=r>>>16&255;b[h+t+0]=i,b[h+t+1]=o,b[h+t+2]=a;}if(h+=3*m,l(b,h,f(b,h-3*m-4,3*m+4)),h+=4,g){l(b,h,m),h+=4,u(b,h,"tRNS"),h+=4;for(w=0;w<m;w++)b[h+w]=e.plte[w]>>>24&255;h+=m,l(b,h,f(b,h-m-4,m+4)),h+=4;}}let E=0;for(v=0;v<e.frames.length;v++){var F=e.frames[v];d&&(l(b,h,26),h+=4,u(b,h,"fcTL"),h+=4,l(b,h,E++),h+=4,l(b,h,F.rect.width),h+=4,l(b,h,F.rect.height),h+=4,l(b,h,F.rect.x),h+=4,l(b,h,F.rect.y),h+=4,c(b,h,a[v]),h+=2,c(b,h,1e3),h+=2,b[h]=F.dispose,h++,b[h]=F.blend,h++,l(b,h,f(b,h-30,30)),h+=4);const t=F.cimg;l(b,h,(m=t.length)+(0==v?0:4)),h+=4;const r=h;u(b,h,0==v?"IDAT":"fdAT"),h+=4,0!=v&&(l(b,h,E++),h+=4),b.set(t,h),h+=m,l(b,h,f(b,r,h-r)),h+=4;}return l(b,h,0),h+=4,u(b,h,"IEND"),h+=4,l(b,h,f(b,h-4,4)),h+=4,b.buffer}function compressPNG(e,t,r){for(let i=0;i<e.frames.length;i++){const o=e.frames[i];const a=o.rect.height,s=new Uint8Array(a*o.bpl+a);o.cimg=_filterZero(o.img,a,o.bpp,o.bpl,s,t,r);}}function compress(t,r,i,o,a){const s=a[0],f=a[1],l=a[2],c=a[3],u=a[4],h=a[5];let d=6,A=8,g=255;for(var p=0;p<t.length;p++){const e=new Uint8Array(t[p]);for(var m=e.length,w=0;w<m;w+=4)g&=e[w+3];}const v=255!=g,b=function framize(t,r,i,o,a,s){const f=[];for(var l=0;l<t.length;l++){const h=new Uint8Array(t[l]),A=new Uint32Array(h.buffer);var c;let g=0,p=0,m=r,w=i,v=o?1:0;if(0!=l){const b=s||o||1==l||0!=f[l-2].dispose?1:2;let y=0,E=1e9;for(let e=0;e<b;e++){var u=new Uint8Array(t[l-1-e]);const o=new Uint32Array(t[l-1-e]);let s=r,f=i,c=-1,h=-1;for(let e=0;e<i;e++)for(let t=0;t<r;t++){A[d=e*r+t]!=o[d]&&(t<s&&(s=t),t>c&&(c=t),e<f&&(f=e),e>h&&(h=e));} -1==c&&(s=f=c=h=0),a&&(1==(1&s)&&s--,1==(1&f)&&f--);const v=(c-s+1)*(h-f+1);v<E&&(E=v,y=e,g=s,p=f,m=c-s+1,w=h-f+1);}u=new Uint8Array(t[l-1-y]);1==y&&(f[l-1].dispose=2),c=new Uint8Array(m*w*4),e(u,r,i,c,m,w,-g,-p,0),v=e(h,r,i,c,m,w,-g,-p,3)?1:0,1==v?_prepareDiff(h,r,i,c,{x:g,y:p,width:m,height:w}):e(h,r,i,c,m,w,-g,-p,0);}else c=h.slice(0);f.push({rect:{x:g,y:p,width:m,height:w},img:c,blend:v,dispose:0});}if(o)for(l=0;l<f.length;l++){if(1==(A=f[l]).blend)continue;const e=A.rect,o=f[l-1].rect,s=Math.min(e.x,o.x),c=Math.min(e.y,o.y),u={x:s,y:c,width:Math.max(e.x+e.width,o.x+o.width)-s,height:Math.max(e.y+e.height,o.y+o.height)-c};f[l-1].dispose=1,l-1!=0&&_updateFrame(t,r,i,f,l-1,u,a),_updateFrame(t,r,i,f,l,u,a);}let h=0;if(1!=t.length)for(var d=0;d<f.length;d++){var A;h+=(A=f[d]).rect.width*A.rect.height;}return f}(t,r,i,s,f,l),y={},E=[],F=[];if(0!=o){const e=[];for(w=0;w<b.length;w++)e.push(b[w].img.buffer);const t=function concatRGBA(e){let t=0;for(var r=0;r<e.length;r++)t+=e[r].byteLength;const i=new Uint8Array(t);let o=0;for(r=0;r<e.length;r++){const t=new Uint8Array(e[r]),a=t.length;for(let e=0;e<a;e+=4){let r=t[e],a=t[e+1],s=t[e+2];const f=t[e+3];0==f&&(r=a=s=0),i[o+e]=r,i[o+e+1]=a,i[o+e+2]=s,i[o+e+3]=f;}o+=a;}return i.buffer}(e),r=quantize(t,o);for(w=0;w<r.plte.length;w++)E.push(r.plte[w].est.rgba);let i=0;for(w=0;w<b.length;w++){const e=(B=b[w]).img.length;var _=new Uint8Array(r.inds.buffer,i>>2,e>>2);F.push(_);const t=new Uint8Array(r.abuf,i,e);h&&dither(B.img,B.rect.width,B.rect.height,E,t,_),B.img.set(t),i+=e;}}else for(p=0;p<b.length;p++){var B=b[p];const e=new Uint32Array(B.img.buffer);var U=B.rect.width;m=e.length,_=new Uint8Array(m);F.push(_);for(w=0;w<m;w++){const t=e[w];if(0!=w&&t==e[w-1])_[w]=_[w-1];else if(w>U&&t==e[w-U])_[w]=_[w-U];else {let e=y[t];if(null==e&&(y[t]=e=E.length,E.push(t),E.length>=300))break;_[w]=e;}}}const C=E.length;C<=256&&0==u&&(A=C<=2?1:C<=4?2:C<=16?4:8,A=Math.max(A,c));for(p=0;p<b.length;p++){(B=b[p]).rect.x;U=B.rect.width;const e=B.rect.height;let t=B.img;let r=4*U,i=4;if(C<=256&&0==u){r=Math.ceil(A*U/8);var I=new Uint8Array(r*e);const o=F[p];for(let t=0;t<e;t++){w=t*r;const e=t*U;if(8==A)for(var Q=0;Q<U;Q++)I[w+Q]=o[e+Q];else if(4==A)for(Q=0;Q<U;Q++)I[w+(Q>>1)]|=o[e+Q]<<4-4*(1&Q);else if(2==A)for(Q=0;Q<U;Q++)I[w+(Q>>2)]|=o[e+Q]<<6-2*(3&Q);else if(1==A)for(Q=0;Q<U;Q++)I[w+(Q>>3)]|=o[e+Q]<<7-1*(7&Q);}t=I,d=3,i=1;}else if(0==v&&1==b.length){I=new Uint8Array(U*e*3);const o=U*e;for(w=0;w<o;w++){const e=3*w,r=4*w;I[e]=t[r],I[e+1]=t[r+1],I[e+2]=t[r+2];}t=I,d=2,i=3,r=3*U;}B.img=t,B.bpl=r,B.bpp=i;}return {ctype:d,depth:A,plte:E,frames:b}}function _updateFrame(t,r,i,o,a,s,f){const l=Uint8Array,c=Uint32Array,u=new l(t[a-1]),h=new c(t[a-1]),d=a+1<t.length?new l(t[a+1]):null,A=new l(t[a]),g=new c(A.buffer);let p=r,m=i,w=-1,v=-1;for(let e=0;e<s.height;e++)for(let t=0;t<s.width;t++){const i=s.x+t,f=s.y+e,l=f*r+i,c=g[l];0==c||0==o[a-1].dispose&&h[l]==c&&(null==d||0!=d[4*l+3])||(i<p&&(p=i),i>w&&(w=i),f<m&&(m=f),f>v&&(v=f));} -1==w&&(p=m=w=v=0),f&&(1==(1&p)&&p--,1==(1&m)&&m--),s={x:p,y:m,width:w-p+1,height:v-m+1};const b=o[a];b.rect=s,b.blend=1,b.img=new Uint8Array(s.width*s.height*4),0==o[a-1].dispose?(e(u,r,i,b.img,s.width,s.height,-s.x,-s.y,0),_prepareDiff(A,r,i,b.img,s)):e(A,r,i,b.img,s.width,s.height,-s.x,-s.y,0);}function _prepareDiff(t,r,i,o,a){e(t,r,i,o,a.width,a.height,-a.x,-a.y,2);}function _filterZero(e,t,r,i,o,a,s){const f=[];let l,c=[0,1,2,3,4];-1!=a?c=[a]:(t*i>5e5||1==r)&&(c=[0]),s&&(l={level:0});const u=UZIP;for(var h=0;h<c.length;h++){for(let a=0;a<t;a++)_filterLine(o,e,a,i,r,c[h]);f.push(u.deflate(o,l));}let d,A=1e9;for(h=0;h<f.length;h++)f[h].length<A&&(d=h,A=f[h].length);return f[d]}function _filterLine(e,t,i,o,a,s){const f=i*o;let l=f+i;if(e[l]=s,l++,0==s)if(o<500)for(var c=0;c<o;c++)e[l+c]=t[f+c];else e.set(new Uint8Array(t.buffer,f,o),l);else if(1==s){for(c=0;c<a;c++)e[l+c]=t[f+c];for(c=a;c<o;c++)e[l+c]=t[f+c]-t[f+c-a]+256&255;}else if(0==i){for(c=0;c<a;c++)e[l+c]=t[f+c];if(2==s)for(c=a;c<o;c++)e[l+c]=t[f+c];if(3==s)for(c=a;c<o;c++)e[l+c]=t[f+c]-(t[f+c-a]>>1)+256&255;if(4==s)for(c=a;c<o;c++)e[l+c]=t[f+c]-r(t[f+c-a],0,0)+256&255;}else {if(2==s)for(c=0;c<o;c++)e[l+c]=t[f+c]+256-t[f+c-o]&255;if(3==s){for(c=0;c<a;c++)e[l+c]=t[f+c]+256-(t[f+c-o]>>1)&255;for(c=a;c<o;c++)e[l+c]=t[f+c]+256-(t[f+c-o]+t[f+c-a]>>1)&255;}if(4==s){for(c=0;c<a;c++)e[l+c]=t[f+c]+256-r(0,t[f+c-o],0)&255;for(c=a;c<o;c++)e[l+c]=t[f+c]+256-r(t[f+c-a],t[f+c-o],t[f+c-a-o])&255;}}}function quantize(e,t){const r=new Uint8Array(e),i=r.slice(0),o=new Uint32Array(i.buffer),a=getKDtree(i,t),s=a[0],f=a[1],l=r.length,c=new Uint8Array(l>>2);let u;if(r.length<2e7)for(var h=0;h<l;h+=4){u=getNearest(s,d=r[h]*(1/255),A=r[h+1]*(1/255),g=r[h+2]*(1/255),p=r[h+3]*(1/255)),c[h>>2]=u.ind,o[h>>2]=u.est.rgba;}else for(h=0;h<l;h+=4){var d=r[h]*(1/255),A=r[h+1]*(1/255),g=r[h+2]*(1/255),p=r[h+3]*(1/255);for(u=s;u.left;)u=planeDst(u.est,d,A,g,p)<=0?u.left:u.right;c[h>>2]=u.ind,o[h>>2]=u.est.rgba;}return {abuf:i.buffer,inds:c,plte:f}}function getKDtree(e,t,r){null==r&&(r=1e-4);const i=new Uint32Array(e.buffer),o={i0:0,i1:e.length,bst:null,est:null,tdst:0,left:null,right:null};o.bst=stats(e,o.i0,o.i1),o.est=estats(o.bst);const a=[o];for(;a.length<t;){let t=0,o=0;for(var s=0;s<a.length;s++)a[s].est.L>t&&(t=a[s].est.L,o=s);if(t<r)break;const f=a[o],l=splitPixels(e,i,f.i0,f.i1,f.est.e,f.est.eMq255);if(f.i0>=l||f.i1<=l){f.est.L=0;continue}const c={i0:f.i0,i1:l,bst:null,est:null,tdst:0,left:null,right:null};c.bst=stats(e,c.i0,c.i1),c.est=estats(c.bst);const u={i0:l,i1:f.i1,bst:null,est:null,tdst:0,left:null,right:null};u.bst={R:[],m:[],N:f.bst.N-c.bst.N};for(s=0;s<16;s++)u.bst.R[s]=f.bst.R[s]-c.bst.R[s];for(s=0;s<4;s++)u.bst.m[s]=f.bst.m[s]-c.bst.m[s];u.est=estats(u.bst),f.left=c,f.right=u,a[o]=c,a.push(u);}a.sort(((e,t)=>t.bst.N-e.bst.N));for(s=0;s<a.length;s++)a[s].ind=s;return [o,a]}function getNearest(e,t,r,i,o){if(null==e.left)return e.tdst=function dist(e,t,r,i,o){const a=t-e[0],s=r-e[1],f=i-e[2],l=o-e[3];return a*a+s*s+f*f+l*l}(e.est.q,t,r,i,o),e;const a=planeDst(e.est,t,r,i,o);let s=e.left,f=e.right;a>0&&(s=e.right,f=e.left);const l=getNearest(s,t,r,i,o);if(l.tdst<=a*a)return l;const c=getNearest(f,t,r,i,o);return c.tdst<l.tdst?c:l}function planeDst(e,t,r,i,o){const{e:a}=e;return a[0]*t+a[1]*r+a[2]*i+a[3]*o-e.eMq}function splitPixels(e,t,r,i,o,a){for(i-=4;r<i;){for(;vecDot(e,r,o)<=a;)r+=4;for(;vecDot(e,i,o)>a;)i-=4;if(r>=i)break;const s=t[r>>2];t[r>>2]=t[i>>2],t[i>>2]=s,r+=4,i-=4;}for(;vecDot(e,r,o)>a;)r-=4;return r+4}function vecDot(e,t,r){return e[t]*r[0]+e[t+1]*r[1]+e[t+2]*r[2]+e[t+3]*r[3]}function stats(e,t,r){const i=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],o=[0,0,0,0],a=r-t>>2;for(let a=t;a<r;a+=4){const t=e[a]*(1/255),r=e[a+1]*(1/255),s=e[a+2]*(1/255),f=e[a+3]*(1/255);o[0]+=t,o[1]+=r,o[2]+=s,o[3]+=f,i[0]+=t*t,i[1]+=t*r,i[2]+=t*s,i[3]+=t*f,i[5]+=r*r,i[6]+=r*s,i[7]+=r*f,i[10]+=s*s,i[11]+=s*f,i[15]+=f*f;}return i[4]=i[1],i[8]=i[2],i[9]=i[6],i[12]=i[3],i[13]=i[7],i[14]=i[11],{R:i,m:o,N:a}}function estats(e){const{R:t}=e,{m:r}=e,{N:i}=e,a=r[0],s=r[1],f=r[2],l=r[3],c=0==i?0:1/i,u=[t[0]-a*a*c,t[1]-a*s*c,t[2]-a*f*c,t[3]-a*l*c,t[4]-s*a*c,t[5]-s*s*c,t[6]-s*f*c,t[7]-s*l*c,t[8]-f*a*c,t[9]-f*s*c,t[10]-f*f*c,t[11]-f*l*c,t[12]-l*a*c,t[13]-l*s*c,t[14]-l*f*c,t[15]-l*l*c],h=u,d=o;let A=[Math.random(),Math.random(),Math.random(),Math.random()],g=0,p=0;if(0!=i)for(let e=0;e<16&&(A=d.multVec(h,A),p=Math.sqrt(d.dot(A,A)),A=d.sml(1/p,A),!(0!=e&&Math.abs(p-g)<1e-9));e++)g=p;const m=[a*c,s*c,f*c,l*c];return {Cov:u,q:m,e:A,L:g,eMq255:d.dot(d.sml(255,m),A),eMq:d.dot(A,m),rgba:(Math.round(255*m[3])<<24|Math.round(255*m[2])<<16|Math.round(255*m[1])<<8|Math.round(255*m[0])<<0)>>>0}}var o={multVec:(e,t)=>[e[0]*t[0]+e[1]*t[1]+e[2]*t[2]+e[3]*t[3],e[4]*t[0]+e[5]*t[1]+e[6]*t[2]+e[7]*t[3],e[8]*t[0]+e[9]*t[1]+e[10]*t[2]+e[11]*t[3],e[12]*t[0]+e[13]*t[1]+e[14]*t[2]+e[15]*t[3]],dot:(e,t)=>e[0]*t[0]+e[1]*t[1]+e[2]*t[2]+e[3]*t[3],sml:(e,t)=>[e*t[0],e*t[1],e*t[2],e*t[3]]};UPNG.encode=function encode(e,t,r,i,o,a,s){null==i&&(i=0),null==s&&(s=false);const f=compress(e,t,r,i,[false,false,false,0,s,false]);return compressPNG(f,-1),_main(f,t,r,o,a)},UPNG.encodeLL=function encodeLL(e,t,r,i,o,a,s,f){const l={ctype:0+(1==i?0:2)+(0==o?0:4),depth:a,frames:[]},c=(i+o)*a,u=c*t;for(let i=0;i<e.length;i++)l.frames.push({rect:{x:0,y:0,width:t,height:r},img:new Uint8Array(e[i]),blend:0,dispose:1,bpp:Math.ceil(c/8),bpl:Math.ceil(u/8)});return compressPNG(l,0,true),_main(l,t,r,s,f)},UPNG.encode.compress=compress,UPNG.encode.dither=dither,UPNG.quantize=quantize,UPNG.quantize.getKDtree=getKDtree,UPNG.quantize.getNearest=getNearest;}();const r={toArrayBuffer(e,t){const i=e.width,o=e.height,a=i<<2,s=e.getContext("2d").getImageData(0,0,i,o),f=new Uint32Array(s.data.buffer),l=(32*i+31)/32<<2,c=l*o,u=122+c,h=new ArrayBuffer(u),d=new DataView(h),A=1<<20;let g,p,m,w,v=A,b=0,y=0,E=0;function set16(e){d.setUint16(y,e,true),y+=2;}function set32(e){d.setUint32(y,e,true),y+=4;}function seek(e){y+=e;}set16(19778),set32(u),seek(4),set32(122),set32(108),set32(i),set32(-o>>>0),set16(1),set16(32),set32(3),set32(c),set32(2835),set32(2835),seek(8),set32(16711680),set32(65280),set32(255),set32(4278190080),set32(1466527264),function convert(){for(;b<o&&v>0;){for(w=122+b*l,g=0;g<a;)v--,p=f[E++],m=p>>>24,d.setUint32(w+g,p<<8|m),g+=4;b++;}E<f.length?(v=A,setTimeout(convert,r._dly)):t(h);}();},toBlob(e,t){this.toArrayBuffer(e,(e=>{t(new Blob([e],{type:"image/bmp"}));}));},_dly:9};var i={CHROME:"CHROME",FIREFOX:"FIREFOX",DESKTOP_SAFARI:"DESKTOP_SAFARI",IE:"IE",IOS:"IOS",ETC:"ETC"},o={[i.CHROME]:16384,[i.FIREFOX]:11180,[i.DESKTOP_SAFARI]:16384,[i.IE]:8192,[i.IOS]:4096,[i.ETC]:8192};const a="undefined"!=typeof window,s="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope,f=a&&window.cordova&&window.cordova.require&&window.cordova.require("cordova/modulemapper"),CustomFile=(a||s)&&(f&&f.getOriginalSymbol(window,"File")||"undefined"!=typeof File&&File),CustomFileReader=(a||s)&&(f&&f.getOriginalSymbol(window,"FileReader")||"undefined"!=typeof FileReader&&FileReader);function getFilefromDataUrl(e,t,r=Date.now()){return new Promise((i=>{const o=e.split(","),a=o[0].match(/:(.*?);/)[1],s=globalThis.atob(o[1]);let f=s.length;const l=new Uint8Array(f);for(;f--;)l[f]=s.charCodeAt(f);const c=new Blob([l],{type:a});c.name=t,c.lastModified=r,i(c);}))}function getDataUrlFromFile(e){return new Promise(((t,r)=>{const i=new CustomFileReader;i.onload=()=>t(i.result),i.onerror=e=>r(e),i.readAsDataURL(e);}))}function loadImage(e){return new Promise(((t,r)=>{const i=new Image;i.onload=()=>t(i),i.onerror=e=>r(e),i.src=e;}))}function getBrowserName(){if(void 0!==getBrowserName.cachedResult)return getBrowserName.cachedResult;let e=i.ETC;const{userAgent:t}=navigator;return /Chrom(e|ium)/i.test(t)?e=i.CHROME:/iP(ad|od|hone)/i.test(t)&&/WebKit/i.test(t)?e=i.IOS:/Safari/i.test(t)?e=i.DESKTOP_SAFARI:/Firefox/i.test(t)?e=i.FIREFOX:(/MSIE/i.test(t)||true==!!document.documentMode)&&(e=i.IE),getBrowserName.cachedResult=e,getBrowserName.cachedResult}function approximateBelowMaximumCanvasSizeOfBrowser(e,t){const r=getBrowserName(),i=o[r];let a=e,s=t,f=a*s;const l=a>s?s/a:a/s;for(;f>i*i;){const e=(i+a)/2,t=(i+s)/2;e<t?(s=t,a=t*l):(s=e*l,a=e),f=a*s;}return {width:a,height:s}}function getNewCanvasAndCtx(e,t){let r,i;try{if(r=new OffscreenCanvas(e,t),i=r.getContext("2d"),null===i)throw new Error("getContext of OffscreenCanvas returns null")}catch(e){r=document.createElement("canvas"),i=r.getContext("2d");}return r.width=e,r.height=t,[r,i]}function drawImageInCanvas(e,t){const{width:r,height:i}=approximateBelowMaximumCanvasSizeOfBrowser(e.width,e.height),[o,a]=getNewCanvasAndCtx(r,i);return t&&/jpe?g/.test(t)&&(a.fillStyle="white",a.fillRect(0,0,o.width,o.height)),a.drawImage(e,0,0,o.width,o.height),o}function isIOS(){return void 0!==isIOS.cachedResult||(isIOS.cachedResult=["iPad Simulator","iPhone Simulator","iPod Simulator","iPad","iPhone","iPod"].includes(navigator.platform)||navigator.userAgent.includes("Mac")&&"undefined"!=typeof document&&"ontouchend"in document),isIOS.cachedResult}function drawFileInCanvas(e,t={}){return new Promise((function(r,o){let a,s;var $Try_2_Post=function(){try{return s=drawImageInCanvas(a,t.fileType||e.type),r([a,s])}catch(e){return o(e)}},$Try_2_Catch=function(t){try{var $Try_3_Catch=function(e){try{throw e}catch(e){return o(e)}};try{let t;return getDataUrlFromFile(e).then((function(e){try{return t=e,loadImage(t).then((function(e){try{return a=e,function(){try{return $Try_2_Post()}catch(e){return o(e)}}()}catch(e){return $Try_3_Catch(e)}}),$Try_3_Catch)}catch(e){return $Try_3_Catch(e)}}),$Try_3_Catch)}catch(e){$Try_3_Catch(e);}}catch(e){return o(e)}};try{if(isIOS()||[i.DESKTOP_SAFARI,i.MOBILE_SAFARI].includes(getBrowserName()))throw new Error("Skip createImageBitmap on IOS and Safari");return createImageBitmap(e).then((function(e){try{return a=e,$Try_2_Post()}catch(e){return $Try_2_Catch()}}),$Try_2_Catch)}catch(e){$Try_2_Catch();}}))}function canvasToFile(e,t,i,o,a=1){return new Promise((function(s,f){let l;if("image/png"===t){let c,u,h;return c=e.getContext("2d"),({data:u}=c.getImageData(0,0,e.width,e.height)),h=UPNG.encode([u.buffer],e.width,e.height,4096*a),l=new Blob([h],{type:t}),l.name=i,l.lastModified=o,$If_4.call(this)}{if("image/bmp"===t)return new Promise((t=>r.toBlob(e,t))).then(function(e){try{return l=e,l.name=i,l.lastModified=o,$If_5.call(this)}catch(e){return f(e)}}.bind(this),f);{if("function"==typeof OffscreenCanvas&&e instanceof OffscreenCanvas)return e.convertToBlob({type:t,quality:a}).then(function(e){try{return l=e,l.name=i,l.lastModified=o,$If_6.call(this)}catch(e){return f(e)}}.bind(this),f);{let d;return d=e.toDataURL(t,a),getFilefromDataUrl(d,i,o).then(function(e){try{return l=e,$If_6.call(this)}catch(e){return f(e)}}.bind(this),f)}function $If_6(){return $If_5.call(this)}}function $If_5(){return $If_4.call(this)}}function $If_4(){return s(l)}}))}function cleanupCanvasMemory(e){e.width=0,e.height=0;}function isAutoOrientationInBrowser(){return new Promise((function(e,t){let i,o,a,s;return void 0!==isAutoOrientationInBrowser.cachedResult?e(isAutoOrientationInBrowser.cachedResult):(getFilefromDataUrl("data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAAAAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/xABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==","test.jpg",Date.now()).then((function(r){try{return i=r,drawFileInCanvas(i).then((function(r){try{return o=r[1],canvasToFile(o,i.type,i.name,i.lastModified).then((function(r){try{return a=r,cleanupCanvasMemory(o),drawFileInCanvas(a).then((function(r){try{return s=r[0],isAutoOrientationInBrowser.cachedResult=1===s.width&&2===s.height,e(isAutoOrientationInBrowser.cachedResult)}catch(e){return t(e)}}),t)}catch(e){return t(e)}}),t)}catch(e){return t(e)}}),t)}catch(e){return t(e)}}),t))}))}function getExifOrientation(e){return new Promise(((t,r)=>{const i=new CustomFileReader;i.onload=e=>{const r=new DataView(e.target.result);if(65496!=r.getUint16(0,false))return t(-2);const i=r.byteLength;let o=2;for(;o<i;){if(r.getUint16(o+2,false)<=8)return t(-1);const e=r.getUint16(o,false);if(o+=2,65505==e){if(1165519206!=r.getUint32(o+=2,false))return t(-1);const e=18761==r.getUint16(o+=6,false);o+=r.getUint32(o+4,e);const i=r.getUint16(o,e);o+=2;for(let a=0;a<i;a++)if(274==r.getUint16(o+12*a,e))return t(r.getUint16(o+12*a+8,e))}else {if(65280!=(65280&e))break;o+=r.getUint16(o,false);}}return t(-1)},i.onerror=e=>r(e),i.readAsArrayBuffer(e);}))}function handleMaxWidthOrHeight(e,t){const{width:r}=e,{height:i}=e,{maxWidthOrHeight:o}=t;let a,s=e;return isFinite(o)&&(r>o||i>o)&&([s,a]=getNewCanvasAndCtx(r,i),r>i?(s.width=o,s.height=i/r*o):(s.width=r/i*o,s.height=o),a.drawImage(e,0,0,s.width,s.height),cleanupCanvasMemory(e)),s}function followExifOrientation(e,t){const{width:r}=e,{height:i}=e,[o,a]=getNewCanvasAndCtx(r,i);switch(t>4&&t<9?(o.width=i,o.height=r):(o.width=r,o.height=i),t){case 2:a.transform(-1,0,0,1,r,0);break;case 3:a.transform(-1,0,0,-1,r,i);break;case 4:a.transform(1,0,0,-1,0,i);break;case 5:a.transform(0,1,1,0,0,0);break;case 6:a.transform(0,1,-1,0,i,0);break;case 7:a.transform(0,-1,-1,0,i,r);break;case 8:a.transform(0,-1,1,0,0,r);}return a.drawImage(e,0,0,r,i),cleanupCanvasMemory(e),o}function compress(e,t,r=0){return new Promise((function(i,o){let a,s,f,l,c,u,h,d,A,g,p,m,w,v,b,y,E,F,_,B;function incProgress(e=5){if(t.signal&&t.signal.aborted)throw t.signal.reason;a+=e,t.onProgress(Math.min(a,100));}function setProgress(e){if(t.signal&&t.signal.aborted)throw t.signal.reason;a=Math.min(Math.max(e,a),100),t.onProgress(a);}return a=r,s=t.maxIteration||10,f=1024*t.maxSizeMB*1024,incProgress(),drawFileInCanvas(e,t).then(function(r){try{return [,l]=r,incProgress(),c=handleMaxWidthOrHeight(l,t),incProgress(),new Promise((function(r,i){var o;if(!(o=t.exifOrientation))return getExifOrientation(e).then(function(e){try{return o=e,$If_2.call(this)}catch(e){return i(e)}}.bind(this),i);function $If_2(){return r(o)}return $If_2.call(this)})).then(function(r){try{return u=r,incProgress(),isAutoOrientationInBrowser().then(function(r){try{return h=r?c:followExifOrientation(c,u),incProgress(),d=t.initialQuality||1,A=t.fileType||e.type,canvasToFile(h,A,e.name,e.lastModified,d).then(function(r){try{{if(g=r,incProgress(),p=g.size>f,m=g.size>e.size,!p&&!m)return setProgress(100),i(g);var a;function $Loop_3(){if(s--&&(b>f||b>w)){let t,r;return t=B?.95*_.width:_.width,r=B?.95*_.height:_.height,[E,F]=getNewCanvasAndCtx(t,r),F.drawImage(_,0,0,t,r),d*="image/png"===A?.85:.95,canvasToFile(E,A,e.name,e.lastModified,d).then((function(e){try{return y=e,cleanupCanvasMemory(_),_=E,b=y.size,setProgress(Math.min(99,Math.floor((v-b)/(v-f)*100))),$Loop_3}catch(e){return o(e)}}),o)}return [1]}return w=e.size,v=g.size,b=v,_=h,B=!t.alwaysKeepResolution&&p,(a=function(e){for(;e;){if(e.then)return void e.then(a,o);try{if(e.pop){if(e.length)return e.pop()?$Loop_3_exit.call(this):e;e=$Loop_3;}else e=e.call(this);}catch(e){return o(e)}}}.bind(this))($Loop_3);function $Loop_3_exit(){return cleanupCanvasMemory(_),cleanupCanvasMemory(E),cleanupCanvasMemory(c),cleanupCanvasMemory(h),cleanupCanvasMemory(l),setProgress(100),i(y)}}}catch(u){return o(u)}}.bind(this),o)}catch(e){return o(e)}}.bind(this),o)}catch(e){return o(e)}}.bind(this),o)}catch(e){return o(e)}}.bind(this),o)}))}const l="\nlet scriptImported = false\nself.addEventListener('message', async (e) => {\n const { file, id, imageCompressionLibUrl, options } = e.data\n options.onProgress = (progress) => self.postMessage({ progress, id })\n try {\n if (!scriptImported) {\n // console.log('[worker] importScripts', imageCompressionLibUrl)\n self.importScripts(imageCompressionLibUrl)\n scriptImported = true\n }\n // console.log('[worker] self', self)\n const compressedFile = await imageCompression(file, options)\n self.postMessage({ file: compressedFile, id })\n } catch (e) {\n // console.error('[worker] error', e)\n self.postMessage({ error: e.message + '\\n' + e.stack, id })\n }\n})\n";let c;function compressOnWebWorker(e,t){return new Promise(((r,i)=>{c||(c=function createWorkerScriptURL(e){const t=[];return t.push(e),URL.createObjectURL(new Blob(t))}(l));const o=new Worker(c);o.addEventListener("message",(function handler(e){if(t.signal&&t.signal.aborted)o.terminate();else if(void 0===e.data.progress){if(e.data.error)return i(new Error(e.data.error)),void o.terminate();r(e.data.file),o.terminate();}else t.onProgress(e.data.progress);})),o.addEventListener("error",i),t.signal&&t.signal.addEventListener("abort",(()=>{i(t.signal.reason),o.terminate();})),o.postMessage({file:e,imageCompressionLibUrl:t.libURL,options:{...t,onProgress:void 0,signal:void 0}});}))}function imageCompression(e,t){return new Promise((function(r,i){let o,a,s,f,l,c;if(o={...t},s=0,({onProgress:f}=o),o.maxSizeMB=o.maxSizeMB||Number.POSITIVE_INFINITY,l="boolean"!=typeof o.useWebWorker||o.useWebWorker,delete o.useWebWorker,o.onProgress=e=>{s=e,"function"==typeof f&&f(s);},!(e instanceof Blob||e instanceof CustomFile))return i(new Error("The file given is not an instance of Blob or File"));if(!/^image/.test(e.type))return i(new Error("The file given is not an image"));if(c="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope,!l||"function"!=typeof Worker||c)return compress(e,o).then(function(e){try{return a=e,$If_4.call(this)}catch(e){return i(e)}}.bind(this),i);var u=function(){try{return $If_4.call(this)}catch(e){return i(e)}}.bind(this),$Try_1_Catch=function(t){try{return compress(e,o).then((function(e){try{return a=e,u()}catch(e){return i(e)}}),i)}catch(e){return i(e)}};try{return o.libURL=o.libURL||"https://cdn.jsdelivr.net/npm/browser-image-compression@2.0.2/dist/browser-image-compression.js",compressOnWebWorker(e,o).then((function(e){try{return a=e,u()}catch(e){return $Try_1_Catch()}}),$Try_1_Catch)}catch(e){$Try_1_Catch();}function $If_4(){try{a.name=e.name,a.lastModified=e.lastModified;}catch(e){}try{o.preserveExif&&"image/jpeg"===e.type&&(!o.fileType||o.fileType&&o.fileType===e.type)&&(a=copyExifWithoutOrientation(e,a));}catch(e){}return r(a)}}))}imageCompression.getDataUrlFromFile=getDataUrlFromFile,imageCompression.getFilefromDataUrl=getFilefromDataUrl,imageCompression.loadImage=loadImage,imageCompression.drawImageInCanvas=drawImageInCanvas,imageCompression.drawFileInCanvas=drawFileInCanvas,imageCompression.canvasToFile=canvasToFile,imageCompression.getExifOrientation=getExifOrientation,imageCompression.handleMaxWidthOrHeight=handleMaxWidthOrHeight,imageCompression.followExifOrientation=followExifOrientation,imageCompression.cleanupCanvasMemory=cleanupCanvasMemory,imageCompression.isAutoOrientationInBrowser=isAutoOrientationInBrowser,imageCompression.approximateBelowMaximumCanvasSizeOfBrowser=approximateBelowMaximumCanvasSizeOfBrowser,imageCompression.copyExifWithoutOrientation=copyExifWithoutOrientation,imageCompression.getBrowserName=getBrowserName,imageCompression.version="2.0.2";
14770
-
14771
- class KritzelImageTool extends KritzelBaseTool {
14772
- fileInput = null;
14773
- maxCompressionSize = 300;
14774
- constructor(core) {
14775
- super(core);
14776
- this.setupFileInput();
15328
+ deselectAllChildren() {
15329
+ this.objects.forEach(obj => (obj.isSelected = false));
14777
15330
  }
14778
- onActivate() {
14779
- this.openFilePicker();
15331
+ updateWorkspaceId(workspaceId) {
15332
+ this.workspaceId = workspaceId;
15333
+ this.objects.forEach(obj => (obj.workspaceId = workspaceId));
14780
15334
  }
14781
- openFilePicker() {
14782
- if (this._core.store.isDisabled) {
14783
- return;
14784
- }
14785
- this.fileInput.click();
15335
+ updateZIndices(startZIndex) {
15336
+ this.objects.forEach((obj, i) => {
15337
+ obj.zIndex = startZIndex + i;
15338
+ });
14786
15339
  }
14787
- setupFileInput() {
14788
- this.fileInput = document.createElement('input');
14789
- this.fileInput.type = 'file';
14790
- this.fileInput.accept = 'image/*';
14791
- this.fileInput.style.display = 'none';
14792
- this.fileInput.addEventListener('change', this.handleFileSelect.bind(this));
14793
- this.fileInput.addEventListener('cancel', this.handleCancel.bind(this));
14794
- document.body.appendChild(this.fileInput);
15340
+ updatePosition(x, y) {
15341
+ this.objects.forEach(obj => {
15342
+ const deltaX = obj.translateX - this.translateX;
15343
+ const deltaY = obj.translateY - this.translateY;
15344
+ obj.updatePosition(x + deltaX, y + deltaY);
15345
+ });
15346
+ // Update snapshots
15347
+ this.unchangedObjectSnapshots.forEach(snapshot => {
15348
+ const deltaX = snapshot.translateX - this.translateX;
15349
+ const deltaY = snapshot.translateY - this.translateY;
15350
+ snapshot.translateX = x + deltaX;
15351
+ snapshot.translateY = x + deltaY;
15352
+ });
15353
+ this.translateX = x;
15354
+ this.translateY = y;
15355
+ this._core.store.state.objects.update(this);
14795
15356
  }
14796
- handleFileSelect(event) {
14797
- const input = event.target;
14798
- if (input.files && input.files[0]) {
14799
- const file = input.files[0];
14800
- imageCompression(file, {
14801
- maxWidthOrHeight: this.maxCompressionSize,
14802
- })
14803
- .then(compressedFile => {
14804
- this.readFile(compressedFile);
14805
- })
14806
- .catch(error => {
14807
- console.error('Error during image compression or processing:', error);
14808
- this.handleCancel();
15357
+ /**
15358
+ * Capture snapshots of current object states for undo/redo operations
15359
+ */
15360
+ captureUnchangedSnapshots() {
15361
+ this.unchangedObjectSnapshots.clear();
15362
+ this.snapshotRotation = this.rotation;
15363
+ this.objects.forEach(obj => {
15364
+ this.unchangedObjectSnapshots.set(obj.id, {
15365
+ id: obj.id,
15366
+ translateX: obj.translateX,
15367
+ translateY: obj.translateY,
15368
+ rotation: obj.rotation,
15369
+ width: obj.width,
15370
+ height: obj.height,
15371
+ totalWidth: obj.totalWidth,
15372
+ totalHeight: obj.totalHeight,
15373
+ scale: obj.scale,
14809
15374
  });
15375
+ });
15376
+ }
15377
+ serialize() {
15378
+ const { _core, _elementRef, element, totalWidth, totalHeight, unchangedObjectSnapshots, ...remainingProps } = this;
15379
+ const clonedProps = structuredClone(remainingProps);
15380
+ if (element && typeof element === 'object' && 'nodeType' in element && element.nodeType === 1) {
15381
+ clonedProps.element = element.cloneNode(true);
14810
15382
  }
14811
- else {
14812
- console.info('File selection cancelled by user.');
14813
- this.handleCancel();
14814
- }
14815
- if (input) {
14816
- input.value = '';
15383
+ // Convert Map to plain object for serialization
15384
+ clonedProps.unchangedObjectSnapshots = Object.fromEntries(this.unchangedObjectSnapshots);
15385
+ return clonedProps;
15386
+ }
15387
+ deserialize(object) {
15388
+ // First, deserialize all base properties using parent's deserialize
15389
+ super.deserialize(object);
15390
+ // Restore the Map from serialized object
15391
+ if (object.unchangedObjectSnapshots) {
15392
+ this.unchangedObjectSnapshots = new Map(Object.entries(object.unchangedObjectSnapshots));
14817
15393
  }
15394
+ return this;
14818
15395
  }
14819
- readFile(file) {
14820
- const reader = new FileReader();
14821
- reader.onload = e => {
14822
- const img = new Image();
14823
- img.src = e.target?.result;
14824
- img.onload = () => this.createKritzelImage(img);
14825
- };
14826
- reader.readAsDataURL(file);
15396
+ update() {
15397
+ // Only update the selection group itself
15398
+ // Child objects are already updated during move/resize/rotate operations
15399
+ // Updating them again here would create redundant y.js updates
15400
+ this._core.store.state.objects.update(this);
14827
15401
  }
14828
- createKritzelImage(img) {
14829
- const image = KritzelImage.create(this._core);
14830
- const { scaledWidth, scaledHeight } = image.calculateScaledDimensions(img);
14831
- image.src = img.src;
14832
- image.width = scaledWidth;
14833
- image.height = scaledHeight;
14834
- image.zIndex = this._core.store.currentZIndex;
14835
- image.centerInViewport();
14836
- this.addImageToStore(image);
14837
- return image;
15402
+ move(startX, startY, endX, endY) {
15403
+ const deltaX = (startX - endX) / this._core.store.state.scale;
15404
+ const deltaY = (startY - endY) / this._core.store.state.scale;
15405
+ this.translateX += deltaX;
15406
+ this.translateY += deltaY;
15407
+ this._core.store.state.objects.transaction(() => {
15408
+ this._core.store.state.objects.update(this);
15409
+ this.objects.forEach(obj => {
15410
+ obj.move(startX, startY, endX, endY);
15411
+ // Update any lines that are anchored to this object
15412
+ this._core.anchorManager.updateAnchorsForObject(obj.id);
15413
+ });
15414
+ });
15415
+ // Update snapshots
15416
+ this.unchangedObjectSnapshots.forEach(snapshot => {
15417
+ snapshot.translateX += deltaX;
15418
+ snapshot.translateY += deltaY;
15419
+ });
14838
15420
  }
14839
- addImageToStore(image) {
14840
- this._core.addObject(image);
14841
- this._core.store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
14842
- this._core.selectObjects([image]);
14843
- this._core.engine.emitObjectsChange();
15421
+ resize(x, y, width, height) {
15422
+ const widthScaleFactor = width / this.width;
15423
+ const heightScaleFactor = height / this.height;
15424
+ // Calculate old center
15425
+ const oldCenterX = this.translateX + this.totalWidth / 2 / this.scale;
15426
+ const oldCenterY = this.translateY + this.totalHeight / 2 / this.scale;
15427
+ // Calculate new center
15428
+ const newTotalWidth = width + this.padding * 2;
15429
+ const newTotalHeight = height + this.padding * 2;
15430
+ const newCenterX = x + newTotalWidth / 2 / this.scale;
15431
+ const newCenterY = y + newTotalHeight / 2 / this.scale;
15432
+ const rotation = this.rotation;
15433
+ const cos = Math.cos(-rotation);
15434
+ const sin = Math.sin(-rotation);
15435
+ const cosR = Math.cos(rotation);
15436
+ const sinR = Math.sin(rotation);
15437
+ this._core.store.state.objects.transaction(() => {
15438
+ this.objects.forEach(child => {
15439
+ // Calculate child center
15440
+ const childCenterX = child.translateX + child.totalWidth / 2 / child.scale;
15441
+ const childCenterY = child.translateY + child.totalHeight / 2 / child.scale;
15442
+ // Vector from old group center to child center
15443
+ const dx = childCenterX - oldCenterX;
15444
+ const dy = childCenterY - oldCenterY;
15445
+ // Rotate to local space (align with group axes)
15446
+ const localX = dx * cos - dy * sin;
15447
+ const localY = dx * sin + dy * cos;
15448
+ // Scale in local space
15449
+ const scaledLocalX = localX * widthScaleFactor;
15450
+ const scaledLocalY = localY * heightScaleFactor;
15451
+ // Rotate back to world space
15452
+ const rotatedX = scaledLocalX * cosR - scaledLocalY * sinR;
15453
+ const rotatedY = scaledLocalX * sinR + scaledLocalY * cosR;
15454
+ // New child center
15455
+ const newChildCenterX = newCenterX + rotatedX;
15456
+ const newChildCenterY = newCenterY + rotatedY;
15457
+ // New child top-left
15458
+ // Calculate relative rotation
15459
+ const relativeRotation = child.rotation - rotation;
15460
+ const cosRel = Math.cos(relativeRotation);
15461
+ const sinRel = Math.sin(relativeRotation);
15462
+ // Project the group's scale factors onto the child's local axes
15463
+ // We use absolute values because scaling is magnitude-based
15464
+ // If the child is aligned (0 deg), cos=1, sin=0 -> scales match
15465
+ // If the child is 90 deg, cos=0, sin=1 -> scales swap
15466
+ const newChildWidthScale = Math.sqrt(Math.pow(widthScaleFactor * cosRel, 2) + Math.pow(heightScaleFactor * sinRel, 2));
15467
+ const newChildHeightScale = Math.sqrt(Math.pow(widthScaleFactor * sinRel, 2) + Math.pow(heightScaleFactor * cosRel, 2));
15468
+ const updatedWidth = child.width * newChildWidthScale;
15469
+ const updatedHeight = child.height * newChildHeightScale;
15470
+ const updatedTotalWidth = updatedWidth + child.padding * 2;
15471
+ const updatedTotalHeight = updatedHeight + child.padding * 2;
15472
+ const updatedX = newChildCenterX - updatedTotalWidth / 2 / child.scale;
15473
+ const updatedY = newChildCenterY - updatedTotalHeight / 2 / child.scale;
15474
+ child.resize(updatedX, updatedY, updatedWidth, updatedHeight);
15475
+ // Update any lines that are anchored to this child object
15476
+ this._core.anchorManager.updateAnchorsForObject(child.id);
15477
+ });
15478
+ // Refresh dimensions and update the SelectionGroup to propagate changes to other tabs
15479
+ this.refreshObjectDimensions();
15480
+ this.captureUnchangedSnapshots();
15481
+ this._core.store.state.objects.update(this);
15482
+ });
14844
15483
  }
14845
- handleCancel() {
14846
- this._core.store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
15484
+ rotate(value) {
15485
+ this.rotation = value;
15486
+ const centerX = this.translateX + this.totalWidth / 2 / this.scale;
15487
+ const centerY = this.translateY + this.totalHeight / 2 / this.scale;
15488
+ const angle = value - this.snapshotRotation;
15489
+ const cos = Math.cos(angle);
15490
+ const sin = Math.sin(angle);
15491
+ this._core.store.state.objects.transaction(() => {
15492
+ // Update the SelectionGroup itself to propagate rotation to other tabs
15493
+ this._core.store.state.objects.update(this);
15494
+ this.objects.forEach(child => {
15495
+ const unchangedSnapshot = this.unchangedObjectSnapshots.get(child.id);
15496
+ if (!unchangedSnapshot)
15497
+ return;
15498
+ const offsetX = this.getOffsetXToCenterFromSnapshot(unchangedSnapshot);
15499
+ const offsetY = this.getOffsetYToCenterFromSnapshot(unchangedSnapshot);
15500
+ const rotatedX = cos * offsetX - sin * offsetY;
15501
+ const rotatedY = sin * offsetX + cos * offsetY;
15502
+ child.translateX = centerX + rotatedX - child.totalWidth / 2 / child.scale;
15503
+ child.translateY = centerY + rotatedY - child.totalHeight / 2 / child.scale;
15504
+ child.rotate(this.objects.length === 1 ? value : unchangedSnapshot.rotation + angle);
15505
+ });
15506
+ });
15507
+ }
15508
+ copy() {
15509
+ const selectionGroup = KritzelSelectionGroup.create(this._core);
15510
+ this.objects
15511
+ .sort((a, b) => a.zIndex - b.zIndex)
15512
+ .forEach(obj => {
15513
+ const copiedObject = obj.copy();
15514
+ selectionGroup.addOrRemove(copiedObject);
15515
+ });
15516
+ selectionGroup.captureUnchangedSnapshots();
15517
+ if (this.objects.length === 1) {
15518
+ selectionGroup.rotation = this.objects[0].rotation;
15519
+ }
15520
+ return selectionGroup;
15521
+ }
15522
+ refreshObjectDimensions() {
15523
+ if (this.objects.length === 1) {
15524
+ const obj = this.objects[0];
15525
+ this.minX = obj.boundingBox.x / this.scale;
15526
+ this.maxX = obj.boundingBox.x / this.scale + obj.boundingBox.width;
15527
+ this.minY = obj.boundingBox.y / this.scale;
15528
+ this.maxY = obj.boundingBox.y / this.scale + obj.boundingBox.height;
15529
+ this.translateX = (this.minX - this.padding) * this.scale;
15530
+ this.translateY = (this.minY - this.padding) * this.scale;
15531
+ this.width = (this.maxX - this.minX - this.padding) * this.scale;
15532
+ this.height = (this.maxY - this.minY - this.padding) * this.scale;
15533
+ }
15534
+ else {
15535
+ const rotation = this.rotation;
15536
+ const cos = Math.cos(-rotation);
15537
+ const sin = Math.sin(-rotation);
15538
+ let minX = Infinity;
15539
+ let maxX = -Infinity;
15540
+ let minY = Infinity;
15541
+ let maxY = -Infinity;
15542
+ this.objects.forEach(obj => {
15543
+ const polygon = obj.rotatedPolygon;
15544
+ const corners = [polygon.topLeft, polygon.topRight, polygon.bottomRight, polygon.bottomLeft];
15545
+ corners.forEach(corner => {
15546
+ // Rotate corner into local space (aligned with group rotation)
15547
+ const rx = corner.x * cos - corner.y * sin;
15548
+ const ry = corner.x * sin + corner.y * cos;
15549
+ if (rx < minX)
15550
+ minX = rx;
15551
+ if (rx > maxX)
15552
+ maxX = rx;
15553
+ if (ry < minY)
15554
+ minY = ry;
15555
+ if (ry > maxY)
15556
+ maxY = ry;
15557
+ });
15558
+ });
15559
+ // Dimensions in world units (unrotated)
15560
+ const worldWidth = maxX - minX;
15561
+ const worldHeight = maxY - minY;
15562
+ this.width = (worldWidth - this.padding) * this.scale;
15563
+ this.height = (worldHeight - this.padding) * this.scale;
15564
+ // Center of the box in rotated space
15565
+ const cRx = (minX + maxX) / 2;
15566
+ const cRy = (minY + maxY) / 2;
15567
+ // Rotate center back to world space
15568
+ const cosR = Math.cos(rotation);
15569
+ const sinR = Math.sin(rotation);
15570
+ const cx = cRx * cosR - cRy * sinR;
15571
+ const cy = cRx * sinR + cRy * cosR;
15572
+ this.translateX = cx - (this.width / this.scale + 2 * this.padding) / 2;
15573
+ this.translateY = cy - (this.height / this.scale + 2 * this.padding) / 2;
15574
+ }
15575
+ this._core.store.state.objects.update(this);
15576
+ }
15577
+ getOffsetXToCenterFromSnapshot(snapshot) {
15578
+ const objCenterX = snapshot.translateX + snapshot.totalWidth / snapshot.scale / 2;
15579
+ const groupCenterX = this.translateX + this.totalWidth / this.scale / 2;
15580
+ return objCenterX - groupCenterX;
15581
+ }
15582
+ getOffsetYToCenterFromSnapshot(snapshot) {
15583
+ const objCenterY = snapshot.translateY + snapshot.totalHeight / snapshot.scale / 2;
15584
+ const groupCenterY = this.translateY + this.totalHeight / this.scale / 2;
15585
+ return objCenterY - groupCenterY;
14847
15586
  }
14848
15587
  }
14849
15588
 
14850
- class KritzelTextTool extends KritzelBaseTool {
14851
- fontFamily = 'Arial';
14852
- fontSize = 16;
14853
- fontColor = '#000000';
14854
- palette = [
14855
- '#000000',
14856
- '#FFFFFF',
14857
- '#FF0000',
14858
- '#00FF00',
14859
- '#0000FF',
14860
- '#FFFF00',
14861
- '#FF00FF',
14862
- '#00FFFF',
14863
- '#808080',
14864
- '#C0C0C0',
14865
- '#800000',
14866
- '#008000',
14867
- '#000080',
14868
- '#808000',
14869
- '#800080',
14870
- ];
15589
+ class KritzelLineTool extends KritzelBaseTool {
15590
+ color = '#000000';
15591
+ size = 4;
15592
+ palettes = ['#000000', '#FFFFFF', '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF', '#808080', '#C0C0C0', '#800000', '#008000', '#000080', '#808000', '#800080'];
15593
+ /** Arrow head configuration for lines created with this tool */
15594
+ arrows;
15595
+ _startX = 0;
15596
+ _startY = 0;
14871
15597
  constructor(core) {
14872
15598
  super(core);
14873
15599
  }
@@ -14876,867 +15602,1040 @@ class KritzelTextTool extends KritzelBaseTool {
14876
15602
  event.preventDefault();
14877
15603
  }
14878
15604
  if (event.pointerType === 'mouse') {
14879
- const path = event.composedPath().slice(1);
14880
- const objectElement = path.find(element => element.classList && element.classList.contains('object'));
14881
- const object = this._core.findObjectById(objectElement?.id);
14882
- const activeText = this._core.store.activeText;
14883
- if (activeText === null && object instanceof KritzelText) {
14884
- object.edit(event);
14885
- return;
14886
- }
14887
- if (activeText !== null && object instanceof KritzelText) {
14888
- activeText.save();
14889
- object.edit(event);
14890
- return;
15605
+ if (KritzelEventHelper.isLeftClick(event)) {
15606
+ this._core.store.state.isDrawing = true;
15607
+ const x = event.clientX - this._core.store.offsetX;
15608
+ const y = event.clientY - this._core.store.offsetY;
15609
+ this._startX = x;
15610
+ this._startY = y;
15611
+ const line = KritzelLine.create(this._core, {
15612
+ startX: x,
15613
+ startY: y,
15614
+ endX: x,
15615
+ endY: y,
15616
+ translateX: -this._core.store.state.translateX,
15617
+ translateY: -this._core.store.state.translateY,
15618
+ scale: this._core.store.state.scale,
15619
+ stroke: this.color,
15620
+ strokeWidth: this.size,
15621
+ arrows: this.arrows,
15622
+ });
15623
+ line.isCompleted = false;
15624
+ this._core.store.state.objects.insert(line);
14891
15625
  }
14892
- if (activeText !== null && object instanceof KritzelText === false) {
14893
- this._core.resetActiveText();
14894
- this._core.store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
14895
- return;
15626
+ }
15627
+ if (event.pointerType === 'touch') {
15628
+ const activePointers = Array.from(this._core.store.state.pointers.values());
15629
+ if (activePointers.length === 1) {
15630
+ const x = Math.round(activePointers[0].clientX - this._core.store.offsetX);
15631
+ const y = Math.round(activePointers[0].clientY - this._core.store.offsetY);
15632
+ this._core.store.state.isDrawing = true;
15633
+ this._startX = x;
15634
+ this._startY = y;
15635
+ const line = KritzelLine.create(this._core, {
15636
+ startX: x,
15637
+ startY: y,
15638
+ endX: x,
15639
+ endY: y,
15640
+ translateX: -this._core.store.state.translateX,
15641
+ translateY: -this._core.store.state.translateY,
15642
+ scale: this._core.store.state.scale,
15643
+ stroke: this.color,
15644
+ strokeWidth: this.size,
15645
+ arrows: this.arrows,
15646
+ });
15647
+ line.isCompleted = false;
15648
+ this._core.store.state.objects.insert(line);
14896
15649
  }
14897
- if (KritzelEventHelper.isLeftClick(event) === false) {
14898
- return;
15650
+ }
15651
+ }
15652
+ handlePointerMove(event) {
15653
+ if (event.cancelable) {
15654
+ event.preventDefault();
15655
+ }
15656
+ if (event.pointerType === 'mouse') {
15657
+ if (this._core.store.state.isDrawing) {
15658
+ const currentLine = this._core.store.currentLine;
15659
+ if (currentLine) {
15660
+ const x = event.clientX - this._core.store.offsetX;
15661
+ const y = event.clientY - this._core.store.offsetY;
15662
+ const updatedLine = KritzelLine.create(this._core, {
15663
+ startX: this._startX,
15664
+ startY: this._startY,
15665
+ endX: x,
15666
+ endY: y,
15667
+ translateX: -this._core.store.state.translateX,
15668
+ translateY: -this._core.store.state.translateY,
15669
+ scale: this._core.store.state.scale,
15670
+ stroke: this.color,
15671
+ strokeWidth: this.size,
15672
+ arrows: this.arrows,
15673
+ });
15674
+ updatedLine.id = currentLine.id;
15675
+ updatedLine.workspaceId = currentLine.workspaceId;
15676
+ updatedLine.isCompleted = false;
15677
+ this._core.store.state.objects.update(updatedLine);
15678
+ }
14899
15679
  }
14900
- const clientX = event.clientX - this._core.store.offsetX;
14901
- const clientY = event.clientY - this._core.store.offsetY;
14902
- const text = KritzelText.create(this._core, this.fontSize, this.fontFamily);
14903
- text.fontColor = this.fontColor;
14904
- text.translateX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
14905
- text.translateY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
14906
- text.zIndex = this._core.store.currentZIndex;
14907
- this._core.store.state.objects.insert(text);
14908
- this._core.rerender();
14909
- text.edit(event);
14910
15680
  }
14911
15681
  if (event.pointerType === 'touch') {
14912
15682
  const activePointers = Array.from(this._core.store.state.pointers.values());
14913
- const path = event.composedPath().slice(1);
14914
- const objectElement = path.find(element => element.classList && element.classList.contains('object'));
14915
- const object = this._core.findObjectById(objectElement?.id);
14916
- const activeText = this._core.store.activeText;
14917
- if (activeText === null && object instanceof KritzelText) {
14918
- object.edit(event);
14919
- return;
14920
- }
14921
- if (activeText !== null && object instanceof KritzelText) {
14922
- activeText.save();
14923
- object.edit(event);
14924
- return;
14925
- }
14926
- if (activeText !== null && object instanceof KritzelText === false) {
14927
- this._core.resetActiveText();
14928
- this._core.store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
14929
- return;
14930
- }
14931
- if (activePointers.length > 1) {
14932
- return;
15683
+ if (activePointers.length === 1) {
15684
+ const currentLine = this._core.store.currentLine;
15685
+ if (currentLine) {
15686
+ const x = Math.round(activePointers[0].clientX - this._core.store.offsetX);
15687
+ const y = Math.round(activePointers[0].clientY - this._core.store.offsetY);
15688
+ const updatedLine = KritzelLine.create(this._core, {
15689
+ startX: this._startX,
15690
+ startY: this._startY,
15691
+ endX: x,
15692
+ endY: y,
15693
+ translateX: -this._core.store.state.translateX,
15694
+ translateY: -this._core.store.state.translateY,
15695
+ scale: this._core.store.state.scale,
15696
+ stroke: this.color,
15697
+ strokeWidth: this.size,
15698
+ arrows: this.arrows,
15699
+ });
15700
+ updatedLine.id = currentLine.id;
15701
+ updatedLine.workspaceId = currentLine.workspaceId;
15702
+ updatedLine.isCompleted = false;
15703
+ this._core.store.state.objects.update(updatedLine);
15704
+ }
14933
15705
  }
14934
- const clientX = Math.round(activePointers[0].clientX - this._core.store.offsetX);
14935
- const clientY = Math.round(activePointers[0].clientY - this._core.store.offsetY);
14936
- const text = KritzelText.create(this._core, this.fontSize, this.fontFamily);
14937
- text.fontColor = this.fontColor;
14938
- text.translateX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
14939
- text.translateY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
14940
- text.zIndex = this._core.store.currentZIndex;
14941
- this._core.store.state.objects.insert(text);
14942
- this._core.rerender();
14943
- text.edit(event);
14944
15706
  }
14945
15707
  }
14946
15708
  handlePointerUp(event) {
14947
15709
  if (event.cancelable) {
14948
15710
  event.preventDefault();
14949
15711
  }
14950
- this._core.store.activeText?.edit(event);
15712
+ if (event.pointerType === 'mouse') {
15713
+ if (this._core.store.state.isDrawing) {
15714
+ this._core.store.state.isDrawing = false;
15715
+ const currentLine = this._core.store.currentLine;
15716
+ if (currentLine) {
15717
+ currentLine.isCompleted = true;
15718
+ currentLine.zIndex = this._core.store.currentZIndex;
15719
+ this._core.store.state.objects.update(currentLine);
15720
+ this._core.engine.emitObjectsChange();
15721
+ // Switch to selection tool and select the drawn line
15722
+ this.selectLineAndSwitchTool(currentLine);
15723
+ }
15724
+ }
15725
+ }
15726
+ if (event.pointerType === 'touch') {
15727
+ if (this._core.store.state.isDrawing) {
15728
+ this._core.store.state.isDrawing = false;
15729
+ const currentLine = this._core.store.currentLine;
15730
+ if (currentLine) {
15731
+ currentLine.isCompleted = true;
15732
+ currentLine.zIndex = this._core.store.currentZIndex;
15733
+ this._core.store.state.objects.update(currentLine);
15734
+ this._core.engine.emitObjectsChange();
15735
+ // Switch to selection tool and select the drawn line
15736
+ this.selectLineAndSwitchTool(currentLine);
15737
+ }
15738
+ }
15739
+ }
14951
15740
  }
14952
- }
14953
-
14954
- class KritzelBaseHandler {
14955
- _core;
14956
- constructor(core) {
14957
- this._core = core;
15741
+ selectLineAndSwitchTool(line) {
15742
+ // Create a selection group with the drawn line
15743
+ const selectionGroup = KritzelSelectionGroup.create(this._core);
15744
+ selectionGroup.addOrRemove(line);
15745
+ selectionGroup.isSelected = true;
15746
+ selectionGroup.rotation = line.rotation;
15747
+ // Add the selection group
15748
+ this._core.addSelectionGroup(selectionGroup);
15749
+ // Switch to selection tool
15750
+ this._core.store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
15751
+ // Rerender
15752
+ this._core.rerender();
14958
15753
  }
14959
15754
  }
14960
15755
 
14961
- class KritzelMoveHandler extends KritzelBaseHandler {
14962
- dragStartX;
14963
- dragStartY;
14964
- startX;
14965
- startY;
14966
- endX;
14967
- endY;
14968
- hasMoved = false;
14969
- trackedPointerId = null;
15756
+ class KritzelEraserTool extends KritzelBaseTool {
15757
+ touchStartTimeout = null;
14970
15758
  constructor(core) {
14971
15759
  super(core);
14972
15760
  }
14973
- reset() {
14974
- this.dragStartX = 0;
14975
- this.dragStartY = 0;
14976
- this.startX = 0;
14977
- this.startY = 0;
14978
- this.endX = 0;
14979
- this.endY = 0;
14980
- this.hasMoved = false;
14981
- this.trackedPointerId = null;
14982
- }
14983
- cancelPendingDrag() {
14984
- this._core.store.state.isDragging = false;
14985
- this.reset();
14986
- }
14987
15761
  handlePointerDown(event) {
14988
15762
  if (event.pointerType === 'mouse') {
14989
15763
  if (KritzelEventHelper.isLeftClick(event)) {
14990
- const selectionGroup = this._core.store.selectionGroup;
14991
- if (selectionGroup?.isSelected && !this._core.store.state.isResizeHandleSelected && !this._core.store.state.isRotationHandleSelected) {
14992
- const clientX = event.clientX - this._core.store.offsetX;
14993
- const clientY = event.clientY - this._core.store.offsetY;
14994
- this._core.store.state.isDragging = true;
14995
- this.dragStartX = clientX;
14996
- this.dragStartY = clientY;
14997
- this.startX = this.dragStartX;
14998
- this.startY = this.dragStartY;
14999
- this.trackedPointerId = event.pointerId;
15000
- }
15001
- else {
15002
- this.trackedPointerId = null;
15003
- }
15004
- }
15005
- else {
15006
- this.trackedPointerId = null;
15764
+ this._core.store.state.isErasing = true;
15007
15765
  }
15008
15766
  }
15009
15767
  if (event.pointerType === 'touch') {
15010
- const activePointers = Array.from(this._core.store.state.pointers.values());
15011
- if (this._core.store.state.pointers.size === 1) {
15012
- if (this._core.store.state.isScaling) {
15013
- this.trackedPointerId = null;
15014
- return;
15015
- }
15016
- const selectionGroup = this._core.store.selectionGroup;
15017
- if (selectionGroup?.isSelected && !this._core.store.state.isResizeHandleSelected && !this._core.store.state.isRotationHandleSelected) {
15018
- const x = Math.round(activePointers[0].clientX - this._core.store.offsetX);
15019
- const y = Math.round(activePointers[0].clientY - this._core.store.offsetY);
15020
- this.dragStartX = x;
15021
- this.dragStartY = y;
15022
- this.startX = x;
15023
- this.startY = y;
15024
- this.trackedPointerId = event.pointerId;
15025
- }
15026
- else {
15027
- this.trackedPointerId = null;
15768
+ this.touchStartTimeout = setTimeout(() => {
15769
+ if (this._core.store.state.pointers.size === 1 && !this._core.store.state.isScaling) {
15770
+ this._core.store.state.isErasing = true;
15028
15771
  }
15029
- }
15772
+ }, 80);
15030
15773
  }
15031
15774
  }
15032
15775
  handlePointerMove(event) {
15033
- if (this.trackedPointerId === null || this.trackedPointerId !== event.pointerId) {
15034
- return;
15035
- }
15036
15776
  if (event.pointerType === 'mouse') {
15037
- const selectionGroup = this._core.store.selectionGroup;
15038
- if (this._core.store.state.isDragging && selectionGroup) {
15039
- const clientX = event.clientX - this._core.store.offsetX;
15040
- const clientY = event.clientY - this._core.store.offsetY;
15041
- this.endX = clientX;
15042
- this.endY = clientY;
15043
- const moveDeltaX = Math.abs(clientX - this.startX);
15044
- const moveDeltaY = Math.abs(clientY - this.startY);
15045
- const moveThreshold = 5;
15046
- if (moveDeltaX > moveThreshold || moveDeltaY > moveThreshold) {
15047
- selectionGroup.move(clientX, clientY, this.dragStartX, this.dragStartY);
15048
- this.dragStartX = clientX;
15049
- this.dragStartY = clientY;
15050
- this.hasMoved = true;
15051
- }
15777
+ if (this._core.store.state.isErasing) {
15778
+ const selectedObjects = this._core.getObjectsFromPointerEvent(event, '.object');
15779
+ if (selectedObjects.length === 0)
15780
+ return;
15781
+ const x = this._core.store.state.pointerX;
15782
+ const y = this._core.store.state.pointerY;
15783
+ selectedObjects.forEach(selectedObject => {
15784
+ selectedObject.markedForRemoval = selectedObject.hitTest(x, y);
15785
+ });
15786
+ this._core.rerender();
15052
15787
  }
15053
15788
  }
15054
15789
  if (event.pointerType === 'touch') {
15055
- const activePointers = Array.from(this._core.store.state.pointers.values());
15056
- const selectionGroup = this._core.store.selectionGroup;
15057
- if (this._core.store.state.pointers.size === 1 &&
15058
- selectionGroup &&
15059
- !this._core.store.state.isResizeHandleSelected &&
15060
- !this._core.store.state.isRotationHandleSelected &&
15061
- !this._core.store.state.isScaling) {
15062
- const x = Math.round(activePointers[0].clientX - this._core.store.offsetX);
15063
- const y = Math.round(activePointers[0].clientY - this._core.store.offsetY);
15064
- this._core.store.state.isDragging = true;
15065
- this.endX = x;
15066
- this.endY = y;
15067
- const moveDeltaX = Math.abs(x - this.startX);
15068
- const moveDeltaY = Math.abs(y - this.startY);
15069
- const moveThreshold = 5;
15070
- if (moveDeltaX > moveThreshold || moveDeltaY > moveThreshold) {
15071
- clearTimeout(this._core.store.state.longTouchTimeout);
15072
- selectionGroup.move(x, y, this.dragStartX, this.dragStartY);
15073
- this.dragStartX = x;
15074
- this.dragStartY = y;
15075
- this.hasMoved = true;
15076
- }
15790
+ if (this._core.store.state.pointers.size === 1 && this._core.store.state.isErasing) {
15791
+ const shadowRoot = this._core.store.state.host?.shadowRoot;
15792
+ if (!shadowRoot)
15793
+ return;
15794
+ const selectedObjects = this._core.getObjectsFromPointerEvent(event, '.object');
15795
+ if (selectedObjects.length === 0)
15796
+ return;
15797
+ const x = this._core.store.state.pointerX;
15798
+ const y = this._core.store.state.pointerY;
15799
+ selectedObjects.forEach(selectedObject => {
15800
+ selectedObject.markedForRemoval = selectedObject.hitTest(x, y);
15801
+ });
15802
+ this._core.rerender();
15077
15803
  }
15078
15804
  }
15079
15805
  }
15080
15806
  handlePointerUp(event) {
15081
- if (this.trackedPointerId === null || this.trackedPointerId !== event.pointerId) {
15082
- return;
15083
- }
15084
15807
  if (event.pointerType === 'mouse') {
15085
- if (this._core.store.state.isDragging) {
15086
- this._core.store.state.isDragging = false;
15087
- if (this.hasMoved) {
15088
- this._core.store.selectionGroup.update();
15089
- this._core.engine.emitObjectsChange();
15090
- this._core.store.state.hasObjectsChanged = true;
15808
+ if (this._core.store.state.isErasing) {
15809
+ const objectsToRemove = this._core.store.allObjects.filter(object => object.markedForRemoval);
15810
+ objectsToRemove.forEach(object => {
15811
+ object.markedForRemoval = false;
15812
+ this._core.removeObject(object);
15813
+ });
15814
+ if (objectsToRemove.length > 0) {
15815
+ this._core.rerender();
15091
15816
  }
15817
+ this._core.store.state.isErasing = false;
15818
+ this._core.engine.emitObjectsChange();
15092
15819
  }
15093
15820
  }
15094
15821
  if (event.pointerType === 'touch') {
15095
- if (this._core.store.state.isDragging) {
15096
- this._core.store.state.isDragging = false;
15097
- if (this.hasMoved) {
15098
- this._core.store.selectionGroup.update();
15099
- this._core.engine.emitObjectsChange();
15100
- this._core.store.state.hasObjectsChanged = true;
15822
+ clearTimeout(this.touchStartTimeout);
15823
+ if (this._core.store.state.isErasing) {
15824
+ const objectsToRemove = this._core.store.allObjects.filter(object => object.markedForRemoval);
15825
+ objectsToRemove.forEach(object => {
15826
+ object.markedForRemoval = false;
15827
+ this._core.removeObject(object);
15828
+ });
15829
+ if (objectsToRemove.length > 0) {
15830
+ this._core.rerender();
15101
15831
  }
15832
+ this._core.store.state.isErasing = false;
15833
+ this._core.engine.emitObjectsChange();
15102
15834
  }
15103
15835
  }
15104
- this.reset();
15105
15836
  }
15106
15837
  }
15107
15838
 
15108
- var KritzelHandleType;
15109
- (function (KritzelHandleType) {
15110
- KritzelHandleType["TopLeft"] = "top-left";
15111
- KritzelHandleType["TopRight"] = "top-right";
15112
- KritzelHandleType["BottomLeft"] = "bottom-left";
15113
- KritzelHandleType["BottomRight"] = "bottom-right";
15114
- })(KritzelHandleType || (KritzelHandleType = {}));
15839
+ /**
15840
+ * Browser Image Compression
15841
+ * v2.0.2
15842
+ * by Donald <donaldcwl@gmail.com>
15843
+ * https://github.com/Donaldcwl/browser-image-compression
15844
+ */
15115
15845
 
15116
- class KritzelResizeHandler extends KritzelBaseHandler {
15117
- initialMouseX = 0;
15118
- initialMouseY = 0;
15119
- initialSize = { x: 0, y: 0, width: 0, height: 0 };
15120
- newSize = { x: 0, y: 0, width: 0, height: 0 };
15121
- hasResized = false;
15846
+ function _mergeNamespaces(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(r){if("default"!==r&&!(r in e)){var i=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(e,r,i.get?i:{enumerable:true,get:function(){return t[r]}});}}));})),Object.freeze(e)}function copyExifWithoutOrientation(e,t){return new Promise((function(r,i){let o;return getApp1Segment(e).then((function(e){try{return o=e,r(new Blob([t.slice(0,2),o,t.slice(2)],{type:"image/jpeg"}))}catch(e){return i(e)}}),i)}))}const getApp1Segment=e=>new Promise(((t,r)=>{const i=new FileReader;i.addEventListener("load",(({target:{result:e}})=>{const i=new DataView(e);let o=0;if(65496!==i.getUint16(o))return r("not a valid JPEG");for(o+=2;;){const a=i.getUint16(o);if(65498===a)break;const s=i.getUint16(o+2);if(65505===a&&1165519206===i.getUint32(o+4)){const a=o+10;let f;switch(i.getUint16(a)){case 18761:f=true;break;case 19789:f=false;break;default:return r("TIFF header contains invalid endian")}if(42!==i.getUint16(a+2,f))return r("TIFF header contains invalid version");const l=i.getUint32(a+4,f),c=a+l+2+12*i.getUint16(a+l,f);for(let e=a+l+2;e<c;e+=12){if(274==i.getUint16(e,f)){if(3!==i.getUint16(e+2,f))return r("Orientation data type is invalid");if(1!==i.getUint32(e+4,f))return r("Orientation data count is invalid");i.setUint16(e+8,1,f);break}}return t(e.slice(o,o+2+s))}o+=2+s;}return t(new Blob)})),i.readAsArrayBuffer(e);}));var e={},t={get exports(){return e},set exports(t){e=t;}};!function(e){var r,i,UZIP={};t.exports=UZIP,UZIP.parse=function(e,t){for(var r=UZIP.bin.readUshort,i=UZIP.bin.readUint,o=0,a={},s=new Uint8Array(e),f=s.length-4;101010256!=i(s,f);)f--;o=f;o+=4;var l=r(s,o+=4);r(s,o+=2);var c=i(s,o+=2),u=i(s,o+=4);o+=4,o=u;for(var h=0;h<l;h++){i(s,o),o+=4,o+=4,o+=4,i(s,o+=4);c=i(s,o+=4);var d=i(s,o+=4),A=r(s,o+=4),g=r(s,o+2),p=r(s,o+4);o+=6;var m=i(s,o+=8);o+=4,o+=A+g+p,UZIP._readLocal(s,m,a,c,d,t);}return a},UZIP._readLocal=function(e,t,r,i,o,a){var s=UZIP.bin.readUshort,f=UZIP.bin.readUint;f(e,t),s(e,t+=4),s(e,t+=2);var l=s(e,t+=2);f(e,t+=2),f(e,t+=4),t+=4;var c=s(e,t+=8),u=s(e,t+=2);t+=2;var h=UZIP.bin.readUTF8(e,t,c);if(t+=c,t+=u,a)r[h]={size:o,csize:i};else {var d=new Uint8Array(e.buffer,t);if(0==l)r[h]=new Uint8Array(d.buffer.slice(t,t+i));else {if(8!=l)throw "unknown compression method: "+l;var A=new Uint8Array(o);UZIP.inflateRaw(d,A),r[h]=A;}}},UZIP.inflateRaw=function(e,t){return UZIP.F.inflate(e,t)},UZIP.inflate=function(e,t){return UZIP.inflateRaw(new Uint8Array(e.buffer,e.byteOffset+2,e.length-6),t)},UZIP.deflate=function(e,t){null==t&&(t={level:6});var r=0,i=new Uint8Array(50+Math.floor(1.1*e.length));i[r]=120,i[r+1]=156,r+=2,r=UZIP.F.deflateRaw(e,i,r,t.level);var o=UZIP.adler(e,0,e.length);return i[r+0]=o>>>24&255,i[r+1]=o>>>16&255,i[r+2]=o>>>8&255,i[r+3]=o>>>0&255,new Uint8Array(i.buffer,0,r+4)},UZIP.deflateRaw=function(e,t){null==t&&(t={level:6});var r=new Uint8Array(50+Math.floor(1.1*e.length)),i=UZIP.F.deflateRaw(e,r,i,t.level);return new Uint8Array(r.buffer,0,i)},UZIP.encode=function(e,t){null==t&&(t=false);var r=0,i=UZIP.bin.writeUint,o=UZIP.bin.writeUshort,a={};for(var s in e){var f=!UZIP._noNeed(s)&&!t,l=e[s],c=UZIP.crc.crc(l,0,l.length);a[s]={cpr:f,usize:l.length,crc:c,file:f?UZIP.deflateRaw(l):l};}for(var s in a)r+=a[s].file.length+30+46+2*UZIP.bin.sizeUTF8(s);r+=22;var u=new Uint8Array(r),h=0,d=[];for(var s in a){var A=a[s];d.push(h),h=UZIP._writeHeader(u,h,s,A,0);}var g=0,p=h;for(var s in a){A=a[s];d.push(h),h=UZIP._writeHeader(u,h,s,A,1,d[g++]);}var m=h-p;return i(u,h,101010256),h+=4,o(u,h+=4,g),o(u,h+=2,g),i(u,h+=2,m),i(u,h+=4,p),h+=4,h+=2,u.buffer},UZIP._noNeed=function(e){var t=e.split(".").pop().toLowerCase();return -1!="png,jpg,jpeg,zip".indexOf(t)},UZIP._writeHeader=function(e,t,r,i,o,a){var s=UZIP.bin.writeUint,f=UZIP.bin.writeUshort,l=i.file;return s(e,t,0==o?67324752:33639248),t+=4,1==o&&(t+=2),f(e,t,20),f(e,t+=2,0),f(e,t+=2,i.cpr?8:0),s(e,t+=2,0),s(e,t+=4,i.crc),s(e,t+=4,l.length),s(e,t+=4,i.usize),f(e,t+=4,UZIP.bin.sizeUTF8(r)),f(e,t+=2,0),t+=2,1==o&&(t+=2,t+=2,s(e,t+=6,a),t+=4),t+=UZIP.bin.writeUTF8(e,t,r),0==o&&(e.set(l,t),t+=l.length),t},UZIP.crc={table:function(){for(var e=new Uint32Array(256),t=0;t<256;t++){for(var r=t,i=0;i<8;i++)1&r?r=3988292384^r>>>1:r>>>=1;e[t]=r;}return e}(),update:function(e,t,r,i){for(var o=0;o<i;o++)e=UZIP.crc.table[255&(e^t[r+o])]^e>>>8;return e},crc:function(e,t,r){return 4294967295^UZIP.crc.update(4294967295,e,t,r)}},UZIP.adler=function(e,t,r){for(var i=1,o=0,a=t,s=t+r;a<s;){for(var f=Math.min(a+5552,s);a<f;)o+=i+=e[a++];i%=65521,o%=65521;}return o<<16|i},UZIP.bin={readUshort:function(e,t){return e[t]|e[t+1]<<8},writeUshort:function(e,t,r){e[t]=255&r,e[t+1]=r>>8&255;},readUint:function(e,t){return 16777216*e[t+3]+(e[t+2]<<16|e[t+1]<<8|e[t])},writeUint:function(e,t,r){e[t]=255&r,e[t+1]=r>>8&255,e[t+2]=r>>16&255,e[t+3]=r>>24&255;},readASCII:function(e,t,r){for(var i="",o=0;o<r;o++)i+=String.fromCharCode(e[t+o]);return i},writeASCII:function(e,t,r){for(var i=0;i<r.length;i++)e[t+i]=r.charCodeAt(i);},pad:function(e){return e.length<2?"0"+e:e},readUTF8:function(e,t,r){for(var i,o="",a=0;a<r;a++)o+="%"+UZIP.bin.pad(e[t+a].toString(16));try{i=decodeURIComponent(o);}catch(i){return UZIP.bin.readASCII(e,t,r)}return i},writeUTF8:function(e,t,r){for(var i=r.length,o=0,a=0;a<i;a++){var s=r.charCodeAt(a);if(0==(4294967168&s))e[t+o]=s,o++;else if(0==(4294965248&s))e[t+o]=192|s>>6,e[t+o+1]=128|s>>0&63,o+=2;else if(0==(4294901760&s))e[t+o]=224|s>>12,e[t+o+1]=128|s>>6&63,e[t+o+2]=128|s>>0&63,o+=3;else {if(0!=(4292870144&s))throw "e";e[t+o]=240|s>>18,e[t+o+1]=128|s>>12&63,e[t+o+2]=128|s>>6&63,e[t+o+3]=128|s>>0&63,o+=4;}}return o},sizeUTF8:function(e){for(var t=e.length,r=0,i=0;i<t;i++){var o=e.charCodeAt(i);if(0==(4294967168&o))r++;else if(0==(4294965248&o))r+=2;else if(0==(4294901760&o))r+=3;else {if(0!=(4292870144&o))throw "e";r+=4;}}return r}},UZIP.F={},UZIP.F.deflateRaw=function(e,t,r,i){var o=[[0,0,0,0,0],[4,4,8,4,0],[4,5,16,8,0],[4,6,16,16,0],[4,10,16,32,0],[8,16,32,32,0],[8,16,128,128,0],[8,32,128,256,0],[32,128,258,1024,1],[32,258,258,4096,1]][i],a=UZIP.F.U,s=UZIP.F._goodIndex;var f=UZIP.F._putsE,l=0,c=r<<3,u=0,h=e.length;if(0==i){for(;l<h;){f(t,c,l+(_=Math.min(65535,h-l))==h?1:0),c=UZIP.F._copyExact(e,l,_,t,c+8),l+=_;}return c>>>3}var d=a.lits,A=a.strt,g=a.prev,p=0,m=0,w=0,v=0,b=0,y=0;for(h>2&&(A[y=UZIP.F._hash(e,0)]=0),l=0;l<h;l++){if(b=y,l+1<h-2){y=UZIP.F._hash(e,l+1);var E=l+1&32767;g[E]=A[y],A[y]=E;}if(u<=l){(p>14e3||m>26697)&&h-l>100&&(u<l&&(d[p]=l-u,p+=2,u=l),c=UZIP.F._writeBlock(l==h-1||u==h?1:0,d,p,v,e,w,l-w,t,c),p=m=v=0,w=l);var F=0;l<h-2&&(F=UZIP.F._bestMatch(e,l,g,b,Math.min(o[2],h-l),o[3]));var _=F>>>16,B=65535&F;if(0!=F){B=65535&F;var U=s(_=F>>>16,a.of0);a.lhst[257+U]++;var C=s(B,a.df0);a.dhst[C]++,v+=a.exb[U]+a.dxb[C],d[p]=_<<23|l-u,d[p+1]=B<<16|U<<8|C,p+=2,u=l+_;}else a.lhst[e[l]]++;m++;}}for(w==l&&0!=e.length||(u<l&&(d[p]=l-u,p+=2,u=l),c=UZIP.F._writeBlock(1,d,p,v,e,w,l-w,t,c),p=0,m=0,p=m=v=0,w=l);0!=(7&c);)c++;return c>>>3},UZIP.F._bestMatch=function(e,t,r,i,o,a){var s=32767&t,f=r[s],l=s-f+32768&32767;if(f==s||i!=UZIP.F._hash(e,t-l))return 0;for(var c=0,u=0,h=Math.min(32767,t);l<=h&&0!=--a&&f!=s;){if(0==c||e[t+c]==e[t+c-l]){var d=UZIP.F._howLong(e,t,l);if(d>c){if(u=l,(c=d)>=o)break;l+2<d&&(d=l+2);for(var A=0,g=0;g<d-2;g++){var p=t-l+g+32768&32767,m=p-r[p]+32768&32767;m>A&&(A=m,f=p);}}}l+=(s=f)-(f=r[s])+32768&32767;}return c<<16|u},UZIP.F._howLong=function(e,t,r){if(e[t]!=e[t-r]||e[t+1]!=e[t+1-r]||e[t+2]!=e[t+2-r])return 0;var i=t,o=Math.min(e.length,t+258);for(t+=3;t<o&&e[t]==e[t-r];)t++;return t-i},UZIP.F._hash=function(e,t){return (e[t]<<8|e[t+1])+(e[t+2]<<4)&65535},UZIP.saved=0,UZIP.F._writeBlock=function(e,t,r,i,o,a,s,f,l){var c,u,h,d,A,g,p,m,w,v=UZIP.F.U,b=UZIP.F._putsF,y=UZIP.F._putsE;v.lhst[256]++,u=(c=UZIP.F.getTrees())[0],h=c[1],d=c[2],A=c[3],g=c[4],p=c[5],m=c[6],w=c[7];var E=32+(0==(l+3&7)?0:8-(l+3&7))+(s<<3),F=i+UZIP.F.contSize(v.fltree,v.lhst)+UZIP.F.contSize(v.fdtree,v.dhst),_=i+UZIP.F.contSize(v.ltree,v.lhst)+UZIP.F.contSize(v.dtree,v.dhst);_+=14+3*p+UZIP.F.contSize(v.itree,v.ihst)+(2*v.ihst[16]+3*v.ihst[17]+7*v.ihst[18]);for(var B=0;B<286;B++)v.lhst[B]=0;for(B=0;B<30;B++)v.dhst[B]=0;for(B=0;B<19;B++)v.ihst[B]=0;var U=E<F&&E<_?0:F<_?1:2;if(b(f,l,e),b(f,l+1,U),l+=3,0==U){for(;0!=(7&l);)l++;l=UZIP.F._copyExact(o,a,s,f,l);}else {var C,I;if(1==U&&(C=v.fltree,I=v.fdtree),2==U){UZIP.F.makeCodes(v.ltree,u),UZIP.F.revCodes(v.ltree,u),UZIP.F.makeCodes(v.dtree,h),UZIP.F.revCodes(v.dtree,h),UZIP.F.makeCodes(v.itree,d),UZIP.F.revCodes(v.itree,d),C=v.ltree,I=v.dtree,y(f,l,A-257),y(f,l+=5,g-1),y(f,l+=5,p-4),l+=4;for(var Q=0;Q<p;Q++)y(f,l+3*Q,v.itree[1+(v.ordr[Q]<<1)]);l+=3*p,l=UZIP.F._codeTiny(m,v.itree,f,l),l=UZIP.F._codeTiny(w,v.itree,f,l);}for(var M=a,x=0;x<r;x+=2){for(var S=t[x],R=S>>>23,T=M+(8388607&S);M<T;)l=UZIP.F._writeLit(o[M++],C,f,l);if(0!=R){var O=t[x+1],P=O>>16,H=O>>8&255,L=255&O;y(f,l=UZIP.F._writeLit(257+H,C,f,l),R-v.of0[H]),l+=v.exb[H],b(f,l=UZIP.F._writeLit(L,I,f,l),P-v.df0[L]),l+=v.dxb[L],M+=R;}}l=UZIP.F._writeLit(256,C,f,l);}return l},UZIP.F._copyExact=function(e,t,r,i,o){var a=o>>>3;return i[a]=r,i[a+1]=r>>>8,i[a+2]=255-i[a],i[a+3]=255-i[a+1],a+=4,i.set(new Uint8Array(e.buffer,t,r),a),o+(r+4<<3)},UZIP.F.getTrees=function(){for(var e=UZIP.F.U,t=UZIP.F._hufTree(e.lhst,e.ltree,15),r=UZIP.F._hufTree(e.dhst,e.dtree,15),i=[],o=UZIP.F._lenCodes(e.ltree,i),a=[],s=UZIP.F._lenCodes(e.dtree,a),f=0;f<i.length;f+=2)e.ihst[i[f]]++;for(f=0;f<a.length;f+=2)e.ihst[a[f]]++;for(var l=UZIP.F._hufTree(e.ihst,e.itree,7),c=19;c>4&&0==e.itree[1+(e.ordr[c-1]<<1)];)c--;return [t,r,l,o,s,c,i,a]},UZIP.F.getSecond=function(e){for(var t=[],r=0;r<e.length;r+=2)t.push(e[r+1]);return t},UZIP.F.nonZero=function(e){for(var t="",r=0;r<e.length;r+=2)0!=e[r+1]&&(t+=(r>>1)+",");return t},UZIP.F.contSize=function(e,t){for(var r=0,i=0;i<t.length;i++)r+=t[i]*e[1+(i<<1)];return r},UZIP.F._codeTiny=function(e,t,r,i){for(var o=0;o<e.length;o+=2){var a=e[o],s=e[o+1];i=UZIP.F._writeLit(a,t,r,i);var f=16==a?2:17==a?3:7;a>15&&(UZIP.F._putsE(r,i,s,f),i+=f);}return i},UZIP.F._lenCodes=function(e,t){for(var r=e.length;2!=r&&0==e[r-1];)r-=2;for(var i=0;i<r;i+=2){var o=e[i+1],a=i+3<r?e[i+3]:-1,s=i+5<r?e[i+5]:-1,f=0==i?-1:e[i-1];if(0==o&&a==o&&s==o){for(var l=i+5;l+2<r&&e[l+2]==o;)l+=2;(c=Math.min(l+1-i>>>1,138))<11?t.push(17,c-3):t.push(18,c-11),i+=2*c-2;}else if(o==f&&a==o&&s==o){for(l=i+5;l+2<r&&e[l+2]==o;)l+=2;var c=Math.min(l+1-i>>>1,6);t.push(16,c-3),i+=2*c-2;}else t.push(o,0);}return r>>>1},UZIP.F._hufTree=function(e,t,r){var i=[],o=e.length,a=t.length,s=0;for(s=0;s<a;s+=2)t[s]=0,t[s+1]=0;for(s=0;s<o;s++)0!=e[s]&&i.push({lit:s,f:e[s]});var f=i.length,l=i.slice(0);if(0==f)return 0;if(1==f){var c=i[0].lit;l=0==c?1:0;return t[1+(c<<1)]=1,t[1+(l<<1)]=1,1}i.sort((function(e,t){return e.f-t.f}));var u=i[0],h=i[1],d=0,A=1,g=2;for(i[0]={lit:-1,f:u.f+h.f,l:u,r:h,d:0};A!=f-1;)u=d!=A&&(g==f||i[d].f<i[g].f)?i[d++]:i[g++],h=d!=A&&(g==f||i[d].f<i[g].f)?i[d++]:i[g++],i[A++]={lit:-1,f:u.f+h.f,l:u,r:h};var p=UZIP.F.setDepth(i[A-1],0);for(p>r&&(UZIP.F.restrictDepth(l,r,p),p=r),s=0;s<f;s++)t[1+(l[s].lit<<1)]=l[s].d;return p},UZIP.F.setDepth=function(e,t){return -1!=e.lit?(e.d=t,t):Math.max(UZIP.F.setDepth(e.l,t+1),UZIP.F.setDepth(e.r,t+1))},UZIP.F.restrictDepth=function(e,t,r){var i=0,o=1<<r-t,a=0;for(e.sort((function(e,t){return t.d==e.d?e.f-t.f:t.d-e.d})),i=0;i<e.length&&e[i].d>t;i++){var s=e[i].d;e[i].d=t,a+=o-(1<<r-s);}for(a>>>=r-t;a>0;){(s=e[i].d)<t?(e[i].d++,a-=1<<t-s-1):i++;}for(;i>=0;i--)e[i].d==t&&a<0&&(e[i].d--,a++);0!=a&&console.log("debt left");},UZIP.F._goodIndex=function(e,t){var r=0;return t[16|r]<=e&&(r|=16),t[8|r]<=e&&(r|=8),t[4|r]<=e&&(r|=4),t[2|r]<=e&&(r|=2),t[1|r]<=e&&(r|=1),r},UZIP.F._writeLit=function(e,t,r,i){return UZIP.F._putsF(r,i,t[e<<1]),i+t[1+(e<<1)]},UZIP.F.inflate=function(e,t){var r=Uint8Array;if(3==e[0]&&0==e[1])return t||new r(0);var i=UZIP.F,o=i._bitsF,a=i._bitsE,s=i._decodeTiny,f=i.makeCodes,l=i.codes2map,c=i._get17,u=i.U,h=null==t;h&&(t=new r(e.length>>>2<<3));for(var d,A,g=0,p=0,m=0,w=0,v=0,b=0,y=0,E=0,F=0;0==g;)if(g=o(e,F,1),p=o(e,F+1,2),F+=3,0!=p){if(h&&(t=UZIP.F._check(t,E+(1<<17))),1==p&&(d=u.flmap,A=u.fdmap,b=511,y=31),2==p){m=a(e,F,5)+257,w=a(e,F+5,5)+1,v=a(e,F+10,4)+4,F+=14;for(var _=0;_<38;_+=2)u.itree[_]=0,u.itree[_+1]=0;var B=1;for(_=0;_<v;_++){var U=a(e,F+3*_,3);u.itree[1+(u.ordr[_]<<1)]=U,U>B&&(B=U);}F+=3*v,f(u.itree,B),l(u.itree,B,u.imap),d=u.lmap,A=u.dmap,F=s(u.imap,(1<<B)-1,m+w,e,F,u.ttree);var C=i._copyOut(u.ttree,0,m,u.ltree);b=(1<<C)-1;var I=i._copyOut(u.ttree,m,w,u.dtree);y=(1<<I)-1,f(u.ltree,C),l(u.ltree,C,d),f(u.dtree,I),l(u.dtree,I,A);}for(;;){var Q=d[c(e,F)&b];F+=15&Q;var M=Q>>>4;if(M>>>8==0)t[E++]=M;else {if(256==M)break;var x=E+M-254;if(M>264){var S=u.ldef[M-257];x=E+(S>>>3)+a(e,F,7&S),F+=7&S;}var R=A[c(e,F)&y];F+=15&R;var T=R>>>4,O=u.ddef[T],P=(O>>>4)+o(e,F,15&O);for(F+=15&O,h&&(t=UZIP.F._check(t,E+(1<<17)));E<x;)t[E]=t[E++-P],t[E]=t[E++-P],t[E]=t[E++-P],t[E]=t[E++-P];E=x;}}}else {0!=(7&F)&&(F+=8-(7&F));var H=4+(F>>>3),L=e[H-4]|e[H-3]<<8;h&&(t=UZIP.F._check(t,E+L)),t.set(new r(e.buffer,e.byteOffset+H,L),E),F=H+L<<3,E+=L;}return t.length==E?t:t.slice(0,E)},UZIP.F._check=function(e,t){var r=e.length;if(t<=r)return e;var i=new Uint8Array(Math.max(r<<1,t));return i.set(e,0),i},UZIP.F._decodeTiny=function(e,t,r,i,o,a){for(var s=UZIP.F._bitsE,f=UZIP.F._get17,l=0;l<r;){var c=e[f(i,o)&t];o+=15&c;var u=c>>>4;if(u<=15)a[l]=u,l++;else {var h=0,d=0;16==u?(d=3+s(i,o,2),o+=2,h=a[l-1]):17==u?(d=3+s(i,o,3),o+=3):18==u&&(d=11+s(i,o,7),o+=7);for(var A=l+d;l<A;)a[l]=h,l++;}}return o},UZIP.F._copyOut=function(e,t,r,i){for(var o=0,a=0,s=i.length>>>1;a<r;){var f=e[a+t];i[a<<1]=0,i[1+(a<<1)]=f,f>o&&(o=f),a++;}for(;a<s;)i[a<<1]=0,i[1+(a<<1)]=0,a++;return o},UZIP.F.makeCodes=function(e,t){for(var r,i,o,a,s=UZIP.F.U,f=e.length,l=s.bl_count,c=0;c<=t;c++)l[c]=0;for(c=1;c<f;c+=2)l[e[c]]++;var u=s.next_code;for(r=0,l[0]=0,i=1;i<=t;i++)r=r+l[i-1]<<1,u[i]=r;for(o=0;o<f;o+=2)0!=(a=e[o+1])&&(e[o]=u[a],u[a]++);},UZIP.F.codes2map=function(e,t,r){for(var i=e.length,o=UZIP.F.U.rev15,a=0;a<i;a+=2)if(0!=e[a+1])for(var s=a>>1,f=e[a+1],l=s<<4|f,c=t-f,u=e[a]<<c,h=u+(1<<c);u!=h;){r[o[u]>>>15-t]=l,u++;}},UZIP.F.revCodes=function(e,t){for(var r=UZIP.F.U.rev15,i=15-t,o=0;o<e.length;o+=2){var a=e[o]<<t-e[o+1];e[o]=r[a]>>>i;}},UZIP.F._putsE=function(e,t,r){r<<=7&t;var i=t>>>3;e[i]|=r,e[i+1]|=r>>>8;},UZIP.F._putsF=function(e,t,r){r<<=7&t;var i=t>>>3;e[i]|=r,e[i+1]|=r>>>8,e[i+2]|=r>>>16;},UZIP.F._bitsE=function(e,t,r){return (e[t>>>3]|e[1+(t>>>3)]<<8)>>>(7&t)&(1<<r)-1},UZIP.F._bitsF=function(e,t,r){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16)>>>(7&t)&(1<<r)-1},UZIP.F._get17=function(e,t){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16)>>>(7&t)},UZIP.F._get25=function(e,t){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16|e[3+(t>>>3)]<<24)>>>(7&t)},UZIP.F.U=(r=Uint16Array,i=Uint32Array,{next_code:new r(16),bl_count:new r(16),ordr:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],of0:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],exb:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],ldef:new r(32),df0:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],dxb:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],ddef:new i(32),flmap:new r(512),fltree:[],fdmap:new r(32),fdtree:[],lmap:new r(32768),ltree:[],ttree:[],dmap:new r(32768),dtree:[],imap:new r(512),itree:[],rev15:new r(32768),lhst:new i(286),dhst:new i(30),ihst:new i(19),lits:new i(15e3),strt:new r(65536),prev:new r(32768)}),function(){for(var e=UZIP.F.U,t=0;t<32768;t++){var r=t;r=(4278255360&(r=(4042322160&(r=(3435973836&(r=(2863311530&r)>>>1|(1431655765&r)<<1))>>>2|(858993459&r)<<2))>>>4|(252645135&r)<<4))>>>8|(16711935&r)<<8,e.rev15[t]=(r>>>16|r<<16)>>>17;}function pushV(e,t,r){for(;0!=t--;)e.push(0,r);}for(t=0;t<32;t++)e.ldef[t]=e.of0[t]<<3|e.exb[t],e.ddef[t]=e.df0[t]<<4|e.dxb[t];pushV(e.fltree,144,8),pushV(e.fltree,112,9),pushV(e.fltree,24,7),pushV(e.fltree,8,8),UZIP.F.makeCodes(e.fltree,9),UZIP.F.codes2map(e.fltree,9,e.flmap),UZIP.F.revCodes(e.fltree,9),pushV(e.fdtree,32,5),UZIP.F.makeCodes(e.fdtree,5),UZIP.F.codes2map(e.fdtree,5,e.fdmap),UZIP.F.revCodes(e.fdtree,5),pushV(e.itree,19,0),pushV(e.ltree,286,0),pushV(e.dtree,30,0),pushV(e.ttree,320,0);}();}();var UZIP=_mergeNamespaces({__proto__:null,default:e},[e]);const UPNG=function(){var e={nextZero(e,t){for(;0!=e[t];)t++;return t},readUshort:(e,t)=>e[t]<<8|e[t+1],writeUshort(e,t,r){e[t]=r>>8&255,e[t+1]=255&r;},readUint:(e,t)=>16777216*e[t]+(e[t+1]<<16|e[t+2]<<8|e[t+3]),writeUint(e,t,r){e[t]=r>>24&255,e[t+1]=r>>16&255,e[t+2]=r>>8&255,e[t+3]=255&r;},readASCII(e,t,r){let i="";for(let o=0;o<r;o++)i+=String.fromCharCode(e[t+o]);return i},writeASCII(e,t,r){for(let i=0;i<r.length;i++)e[t+i]=r.charCodeAt(i);},readBytes(e,t,r){const i=[];for(let o=0;o<r;o++)i.push(e[t+o]);return i},pad:e=>e.length<2?`0${e}`:e,readUTF8(t,r,i){let o,a="";for(let o=0;o<i;o++)a+=`%${e.pad(t[r+o].toString(16))}`;try{o=decodeURIComponent(a);}catch(o){return e.readASCII(t,r,i)}return o}};function decodeImage(t,r,i,o){const a=r*i,s=_getBPP(o),f=Math.ceil(r*s/8),l=new Uint8Array(4*a),c=new Uint32Array(l.buffer),{ctype:u}=o,{depth:h}=o,d=e.readUshort;if(6==u){const e=a<<2;if(8==h)for(var A=0;A<e;A+=4)l[A]=t[A],l[A+1]=t[A+1],l[A+2]=t[A+2],l[A+3]=t[A+3];if(16==h)for(A=0;A<e;A++)l[A]=t[A<<1];}else if(2==u){const e=o.tabs.tRNS;if(null==e){if(8==h)for(A=0;A<a;A++){var g=3*A;c[A]=255<<24|t[g+2]<<16|t[g+1]<<8|t[g];}if(16==h)for(A=0;A<a;A++){g=6*A;c[A]=255<<24|t[g+4]<<16|t[g+2]<<8|t[g];}}else {var p=e[0];const r=e[1],i=e[2];if(8==h)for(A=0;A<a;A++){var m=A<<2;g=3*A;c[A]=255<<24|t[g+2]<<16|t[g+1]<<8|t[g],t[g]==p&&t[g+1]==r&&t[g+2]==i&&(l[m+3]=0);}if(16==h)for(A=0;A<a;A++){m=A<<2,g=6*A;c[A]=255<<24|t[g+4]<<16|t[g+2]<<8|t[g],d(t,g)==p&&d(t,g+2)==r&&d(t,g+4)==i&&(l[m+3]=0);}}}else if(3==u){const e=o.tabs.PLTE,s=o.tabs.tRNS,c=s?s.length:0;if(1==h)for(var w=0;w<i;w++){var v=w*f,b=w*r;for(A=0;A<r;A++){m=b+A<<2;var y=3*(E=t[v+(A>>3)]>>7-((7&A)<<0)&1);l[m]=e[y],l[m+1]=e[y+1],l[m+2]=e[y+2],l[m+3]=E<c?s[E]:255;}}if(2==h)for(w=0;w<i;w++)for(v=w*f,b=w*r,A=0;A<r;A++){m=b+A<<2,y=3*(E=t[v+(A>>2)]>>6-((3&A)<<1)&3);l[m]=e[y],l[m+1]=e[y+1],l[m+2]=e[y+2],l[m+3]=E<c?s[E]:255;}if(4==h)for(w=0;w<i;w++)for(v=w*f,b=w*r,A=0;A<r;A++){m=b+A<<2,y=3*(E=t[v+(A>>1)]>>4-((1&A)<<2)&15);l[m]=e[y],l[m+1]=e[y+1],l[m+2]=e[y+2],l[m+3]=E<c?s[E]:255;}if(8==h)for(A=0;A<a;A++){var E;m=A<<2,y=3*(E=t[A]);l[m]=e[y],l[m+1]=e[y+1],l[m+2]=e[y+2],l[m+3]=E<c?s[E]:255;}}else if(4==u){if(8==h)for(A=0;A<a;A++){m=A<<2;var F=t[_=A<<1];l[m]=F,l[m+1]=F,l[m+2]=F,l[m+3]=t[_+1];}if(16==h)for(A=0;A<a;A++){var _;m=A<<2,F=t[_=A<<2];l[m]=F,l[m+1]=F,l[m+2]=F,l[m+3]=t[_+2];}}else if(0==u)for(p=o.tabs.tRNS?o.tabs.tRNS:-1,w=0;w<i;w++){const e=w*f,i=w*r;if(1==h)for(var B=0;B<r;B++){var U=(F=255*(t[e+(B>>>3)]>>>7-(7&B)&1))==255*p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}else if(2==h)for(B=0;B<r;B++){U=(F=85*(t[e+(B>>>2)]>>>6-((3&B)<<1)&3))==85*p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}else if(4==h)for(B=0;B<r;B++){U=(F=17*(t[e+(B>>>1)]>>>4-((1&B)<<2)&15))==17*p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}else if(8==h)for(B=0;B<r;B++){U=(F=t[e+B])==p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}else if(16==h)for(B=0;B<r;B++){F=t[e+(B<<1)],U=d(t,e+(B<<1))==p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}}return l}function _decompress(e,r,i,o){const a=_getBPP(e),s=Math.ceil(i*a/8),f=new Uint8Array((s+1+e.interlace)*o);return r=e.tabs.CgBI?t(r,f):_inflate(r,f),0==e.interlace?r=_filterZero(r,e,0,i,o):1==e.interlace&&(r=function _readInterlace(e,t){const r=t.width,i=t.height,o=_getBPP(t),a=o>>3,s=Math.ceil(r*o/8),f=new Uint8Array(i*s);let l=0;const c=[0,0,4,0,2,0,1],u=[0,4,0,2,0,1,0],h=[8,8,8,4,4,2,2],d=[8,8,4,4,2,2,1];let A=0;for(;A<7;){const p=h[A],m=d[A];let w=0,v=0,b=c[A];for(;b<i;)b+=p,v++;let y=u[A];for(;y<r;)y+=m,w++;const E=Math.ceil(w*o/8);_filterZero(e,t,l,w,v);let F=0,_=c[A];for(;_<i;){let t=u[A],i=l+F*E<<3;for(;t<r;){var g;if(1==o)g=(g=e[i>>3])>>7-(7&i)&1,f[_*s+(t>>3)]|=g<<7-((7&t)<<0);if(2==o)g=(g=e[i>>3])>>6-(7&i)&3,f[_*s+(t>>2)]|=g<<6-((3&t)<<1);if(4==o)g=(g=e[i>>3])>>4-(7&i)&15,f[_*s+(t>>1)]|=g<<4-((1&t)<<2);if(o>=8){const r=_*s+t*a;for(let t=0;t<a;t++)f[r+t]=e[(i>>3)+t];}i+=o,t+=m;}F++,_+=p;}w*v!=0&&(l+=v*(1+E)),A+=1;}return f}(r,e)),r}function _inflate(e,r){return t(new Uint8Array(e.buffer,2,e.length-6),r)}var t=function(){const e={H:{}};return e.H.N=function(t,r){const i=Uint8Array;let o,a,s=0,f=0,l=0,c=0,u=0,h=0,d=0,A=0,g=0;if(3==t[0]&&0==t[1])return r||new i(0);const p=e.H,m=p.b,w=p.e,v=p.R,b=p.n,y=p.A,E=p.Z,F=p.m,_=null==r;for(_&&(r=new i(t.length>>>2<<5));0==s;)if(s=m(t,g,1),f=m(t,g+1,2),g+=3,0!=f){if(_&&(r=e.H.W(r,A+(1<<17))),1==f&&(o=F.J,a=F.h,h=511,d=31),2==f){l=w(t,g,5)+257,c=w(t,g+5,5)+1,u=w(t,g+10,4)+4,g+=14;let e=1;for(var B=0;B<38;B+=2)F.Q[B]=0,F.Q[B+1]=0;for(B=0;B<u;B++){const r=w(t,g+3*B,3);F.Q[1+(F.X[B]<<1)]=r,r>e&&(e=r);}g+=3*u,b(F.Q,e),y(F.Q,e,F.u),o=F.w,a=F.d,g=v(F.u,(1<<e)-1,l+c,t,g,F.v);const r=p.V(F.v,0,l,F.C);h=(1<<r)-1;const i=p.V(F.v,l,c,F.D);d=(1<<i)-1,b(F.C,r),y(F.C,r,o),b(F.D,i),y(F.D,i,a);}for(;;){const e=o[E(t,g)&h];g+=15&e;const i=e>>>4;if(i>>>8==0)r[A++]=i;else {if(256==i)break;{let e=A+i-254;if(i>264){const r=F.q[i-257];e=A+(r>>>3)+w(t,g,7&r),g+=7&r;}const o=a[E(t,g)&d];g+=15&o;const s=o>>>4,f=F.c[s],l=(f>>>4)+m(t,g,15&f);for(g+=15&f;A<e;)r[A]=r[A++-l],r[A]=r[A++-l],r[A]=r[A++-l],r[A]=r[A++-l];A=e;}}}}else {0!=(7&g)&&(g+=8-(7&g));const o=4+(g>>>3),a=t[o-4]|t[o-3]<<8;_&&(r=e.H.W(r,A+a)),r.set(new i(t.buffer,t.byteOffset+o,a),A),g=o+a<<3,A+=a;}return r.length==A?r:r.slice(0,A)},e.H.W=function(e,t){const r=e.length;if(t<=r)return e;const i=new Uint8Array(r<<1);return i.set(e,0),i},e.H.R=function(t,r,i,o,a,s){const f=e.H.e,l=e.H.Z;let c=0;for(;c<i;){const e=t[l(o,a)&r];a+=15&e;const i=e>>>4;if(i<=15)s[c]=i,c++;else {let e=0,t=0;16==i?(t=3+f(o,a,2),a+=2,e=s[c-1]):17==i?(t=3+f(o,a,3),a+=3):18==i&&(t=11+f(o,a,7),a+=7);const r=c+t;for(;c<r;)s[c]=e,c++;}}return a},e.H.V=function(e,t,r,i){let o=0,a=0;const s=i.length>>>1;for(;a<r;){const r=e[a+t];i[a<<1]=0,i[1+(a<<1)]=r,r>o&&(o=r),a++;}for(;a<s;)i[a<<1]=0,i[1+(a<<1)]=0,a++;return o},e.H.n=function(t,r){const i=e.H.m,o=t.length;let a,s,f;let l;const c=i.j;for(var u=0;u<=r;u++)c[u]=0;for(u=1;u<o;u+=2)c[t[u]]++;const h=i.K;for(a=0,c[0]=0,s=1;s<=r;s++)a=a+c[s-1]<<1,h[s]=a;for(f=0;f<o;f+=2)l=t[f+1],0!=l&&(t[f]=h[l],h[l]++);},e.H.A=function(t,r,i){const o=t.length,a=e.H.m.r;for(let e=0;e<o;e+=2)if(0!=t[e+1]){const o=e>>1,s=t[e+1],f=o<<4|s,l=r-s;let c=t[e]<<l;const u=c+(1<<l);for(;c!=u;){i[a[c]>>>15-r]=f,c++;}}},e.H.l=function(t,r){const i=e.H.m.r,o=15-r;for(let e=0;e<t.length;e+=2){const a=t[e]<<r-t[e+1];t[e]=i[a]>>>o;}},e.H.M=function(e,t,r){r<<=7&t;const i=t>>>3;e[i]|=r,e[i+1]|=r>>>8;},e.H.I=function(e,t,r){r<<=7&t;const i=t>>>3;e[i]|=r,e[i+1]|=r>>>8,e[i+2]|=r>>>16;},e.H.e=function(e,t,r){return (e[t>>>3]|e[1+(t>>>3)]<<8)>>>(7&t)&(1<<r)-1},e.H.b=function(e,t,r){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16)>>>(7&t)&(1<<r)-1},e.H.Z=function(e,t){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16)>>>(7&t)},e.H.i=function(e,t){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16|e[3+(t>>>3)]<<24)>>>(7&t)},e.H.m=function(){const e=Uint16Array,t=Uint32Array;return {K:new e(16),j:new e(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new e(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new t(32),J:new e(512),_:[],h:new e(32),$:[],w:new e(32768),C:[],v:[],d:new e(32768),D:[],u:new e(512),Q:[],r:new e(32768),s:new t(286),Y:new t(30),a:new t(19),t:new t(15e3),k:new e(65536),g:new e(32768)}}(),function(){const t=e.H.m;for(var r=0;r<32768;r++){let e=r;e=(2863311530&e)>>>1|(1431655765&e)<<1,e=(3435973836&e)>>>2|(858993459&e)<<2,e=(4042322160&e)>>>4|(252645135&e)<<4,e=(4278255360&e)>>>8|(16711935&e)<<8,t.r[r]=(e>>>16|e<<16)>>>17;}function n(e,t,r){for(;0!=t--;)e.push(0,r);}for(r=0;r<32;r++)t.q[r]=t.S[r]<<3|t.T[r],t.c[r]=t.p[r]<<4|t.z[r];n(t._,144,8),n(t._,112,9),n(t._,24,7),n(t._,8,8),e.H.n(t._,9),e.H.A(t._,9,t.J),e.H.l(t._,9),n(t.$,32,5),e.H.n(t.$,5),e.H.A(t.$,5,t.h),e.H.l(t.$,5),n(t.Q,19,0),n(t.C,286,0),n(t.D,30,0),n(t.v,320,0);}(),e.H.N}();function _getBPP(e){return [1,null,3,1,2,null,4][e.ctype]*e.depth}function _filterZero(e,t,r,i,o){let a=_getBPP(t);const s=Math.ceil(i*a/8);let f,l;a=Math.ceil(a/8);let c=e[r],u=0;if(c>1&&(e[r]=[0,0,1][c-2]),3==c)for(u=a;u<s;u++)e[u+1]=e[u+1]+(e[u+1-a]>>>1)&255;for(let t=0;t<o;t++)if(f=r+t*s,l=f+t+1,c=e[l-1],u=0,0==c)for(;u<s;u++)e[f+u]=e[l+u];else if(1==c){for(;u<a;u++)e[f+u]=e[l+u];for(;u<s;u++)e[f+u]=e[l+u]+e[f+u-a];}else if(2==c)for(;u<s;u++)e[f+u]=e[l+u]+e[f+u-s];else if(3==c){for(;u<a;u++)e[f+u]=e[l+u]+(e[f+u-s]>>>1);for(;u<s;u++)e[f+u]=e[l+u]+(e[f+u-s]+e[f+u-a]>>>1);}else {for(;u<a;u++)e[f+u]=e[l+u]+_paeth(0,e[f+u-s],0);for(;u<s;u++)e[f+u]=e[l+u]+_paeth(e[f+u-a],e[f+u-s],e[f+u-a-s]);}return e}function _paeth(e,t,r){const i=e+t-r,o=i-e,a=i-t,s=i-r;return o*o<=a*a&&o*o<=s*s?e:a*a<=s*s?t:r}function _IHDR(t,r,i){i.width=e.readUint(t,r),r+=4,i.height=e.readUint(t,r),r+=4,i.depth=t[r],r++,i.ctype=t[r],r++,i.compress=t[r],r++,i.filter=t[r],r++,i.interlace=t[r],r++;}function _copyTile(e,t,r,i,o,a,s,f,l){const c=Math.min(t,o),u=Math.min(r,a);let h=0,d=0;for(let r=0;r<u;r++)for(let a=0;a<c;a++)if(s>=0&&f>=0?(h=r*t+a<<2,d=(f+r)*o+s+a<<2):(h=(-f+r)*t-s+a<<2,d=r*o+a<<2),0==l)i[d]=e[h],i[d+1]=e[h+1],i[d+2]=e[h+2],i[d+3]=e[h+3];else if(1==l){var A=e[h+3]*(1/255),g=e[h]*A,p=e[h+1]*A,m=e[h+2]*A,w=i[d+3]*(1/255),v=i[d]*w,b=i[d+1]*w,y=i[d+2]*w;const t=1-A,r=A+w*t,o=0==r?0:1/r;i[d+3]=255*r,i[d+0]=(g+v*t)*o,i[d+1]=(p+b*t)*o,i[d+2]=(m+y*t)*o;}else if(2==l){A=e[h+3],g=e[h],p=e[h+1],m=e[h+2],w=i[d+3],v=i[d],b=i[d+1],y=i[d+2];A==w&&g==v&&p==b&&m==y?(i[d]=0,i[d+1]=0,i[d+2]=0,i[d+3]=0):(i[d]=g,i[d+1]=p,i[d+2]=m,i[d+3]=A);}else if(3==l){A=e[h+3],g=e[h],p=e[h+1],m=e[h+2],w=i[d+3],v=i[d],b=i[d+1],y=i[d+2];if(A==w&&g==v&&p==b&&m==y)continue;if(A<220&&w>20)return false}return true}return {decode:function decode(r){const i=new Uint8Array(r);let o=8;const a=e,s=a.readUshort,f=a.readUint,l={tabs:{},frames:[]},c=new Uint8Array(i.length);let u,h=0,d=0;const A=[137,80,78,71,13,10,26,10];for(var g=0;g<8;g++)if(i[g]!=A[g])throw "The input is not a PNG file!";for(;o<i.length;){const e=a.readUint(i,o);o+=4;const r=a.readASCII(i,o,4);if(o+=4,"IHDR"==r)_IHDR(i,o,l);else if("iCCP"==r){for(var p=o;0!=i[p];)p++;a.readASCII(i,o,p-o);const s=i.slice(p+2,o+e);let f=null;try{f=_inflate(s);}catch(e){f=t(s);}l.tabs[r]=f;}else if("CgBI"==r)l.tabs[r]=i.slice(o,o+4);else if("IDAT"==r){for(g=0;g<e;g++)c[h+g]=i[o+g];h+=e;}else if("acTL"==r)l.tabs[r]={num_frames:f(i,o),num_plays:f(i,o+4)},u=new Uint8Array(i.length);else if("fcTL"==r){if(0!=d)(E=l.frames[l.frames.length-1]).data=_decompress(l,u.slice(0,d),E.rect.width,E.rect.height),d=0;const e={x:f(i,o+12),y:f(i,o+16),width:f(i,o+4),height:f(i,o+8)};let t=s(i,o+22);t=s(i,o+20)/(0==t?100:t);const r={rect:e,delay:Math.round(1e3*t),dispose:i[o+24],blend:i[o+25]};l.frames.push(r);}else if("fdAT"==r){for(g=0;g<e-4;g++)u[d+g]=i[o+g+4];d+=e-4;}else if("pHYs"==r)l.tabs[r]=[a.readUint(i,o),a.readUint(i,o+4),i[o+8]];else if("cHRM"==r){l.tabs[r]=[];for(g=0;g<8;g++)l.tabs[r].push(a.readUint(i,o+4*g));}else if("tEXt"==r||"zTXt"==r){null==l.tabs[r]&&(l.tabs[r]={});var m=a.nextZero(i,o),w=a.readASCII(i,o,m-o),v=o+e-m-1;if("tEXt"==r)y=a.readASCII(i,m+1,v);else {var b=_inflate(i.slice(m+2,m+2+v));y=a.readUTF8(b,0,b.length);}l.tabs[r][w]=y;}else if("iTXt"==r){null==l.tabs[r]&&(l.tabs[r]={});m=0,p=o;m=a.nextZero(i,p);w=a.readASCII(i,p,m-p);const t=i[p=m+1];var y;p+=2,m=a.nextZero(i,p),a.readASCII(i,p,m-p),p=m+1,m=a.nextZero(i,p),a.readUTF8(i,p,m-p);v=e-((p=m+1)-o);if(0==t)y=a.readUTF8(i,p,v);else {b=_inflate(i.slice(p,p+v));y=a.readUTF8(b,0,b.length);}l.tabs[r][w]=y;}else if("PLTE"==r)l.tabs[r]=a.readBytes(i,o,e);else if("hIST"==r){const e=l.tabs.PLTE.length/3;l.tabs[r]=[];for(g=0;g<e;g++)l.tabs[r].push(s(i,o+2*g));}else if("tRNS"==r)3==l.ctype?l.tabs[r]=a.readBytes(i,o,e):0==l.ctype?l.tabs[r]=s(i,o):2==l.ctype&&(l.tabs[r]=[s(i,o),s(i,o+2),s(i,o+4)]);else if("gAMA"==r)l.tabs[r]=a.readUint(i,o)/1e5;else if("sRGB"==r)l.tabs[r]=i[o];else if("bKGD"==r)0==l.ctype||4==l.ctype?l.tabs[r]=[s(i,o)]:2==l.ctype||6==l.ctype?l.tabs[r]=[s(i,o),s(i,o+2),s(i,o+4)]:3==l.ctype&&(l.tabs[r]=i[o]);else if("IEND"==r)break;o+=e,a.readUint(i,o),o+=4;}var E;return 0!=d&&((E=l.frames[l.frames.length-1]).data=_decompress(l,u.slice(0,d),E.rect.width,E.rect.height)),l.data=_decompress(l,c,l.width,l.height),delete l.compress,delete l.interlace,delete l.filter,l},toRGBA8:function toRGBA8(e){const t=e.width,r=e.height;if(null==e.tabs.acTL)return [decodeImage(e.data,t,r,e).buffer];const i=[];null==e.frames[0].data&&(e.frames[0].data=e.data);const o=t*r*4,a=new Uint8Array(o),s=new Uint8Array(o),f=new Uint8Array(o);for(let c=0;c<e.frames.length;c++){const u=e.frames[c],h=u.rect.x,d=u.rect.y,A=u.rect.width,g=u.rect.height,p=decodeImage(u.data,A,g,e);if(0!=c)for(var l=0;l<o;l++)f[l]=a[l];if(0==u.blend?_copyTile(p,A,g,a,t,r,h,d,0):1==u.blend&&_copyTile(p,A,g,a,t,r,h,d,1),i.push(a.buffer.slice(0)),0==u.dispose);else if(1==u.dispose)_copyTile(s,A,g,a,t,r,h,d,0);else if(2==u.dispose)for(l=0;l<o;l++)a[l]=f[l];}return i},_paeth:_paeth,_copyTile:_copyTile,_bin:e}}();!function(){const{_copyTile:e}=UPNG,{_bin:t}=UPNG,r=UPNG._paeth;var i={table:function(){const e=new Uint32Array(256);for(let t=0;t<256;t++){let r=t;for(let e=0;e<8;e++)1&r?r=3988292384^r>>>1:r>>>=1;e[t]=r;}return e}(),update(e,t,r,o){for(let a=0;a<o;a++)e=i.table[255&(e^t[r+a])]^e>>>8;return e},crc:(e,t,r)=>4294967295^i.update(4294967295,e,t,r)};function addErr(e,t,r,i){t[r]+=e[0]*i>>4,t[r+1]+=e[1]*i>>4,t[r+2]+=e[2]*i>>4,t[r+3]+=e[3]*i>>4;}function N(e){return Math.max(0,Math.min(255,e))}function D(e,t){const r=e[0]-t[0],i=e[1]-t[1],o=e[2]-t[2],a=e[3]-t[3];return r*r+i*i+o*o+a*a}function dither(e,t,r,i,o,a,s){null==s&&(s=1);const f=i.length,l=[];for(var c=0;c<f;c++){const e=i[c];l.push([e>>>0&255,e>>>8&255,e>>>16&255,e>>>24&255]);}for(c=0;c<f;c++){let e=4294967295;for(var u=0,h=0;h<f;h++){var d=D(l[c],l[h]);h!=c&&d<e&&(e=d,u=h);}}const A=new Uint32Array(o.buffer),g=new Int16Array(t*r*4),p=[0,8,2,10,12,4,14,6,3,11,1,9,15,7,13,5];for(c=0;c<p.length;c++)p[c]=255*((p[c]+.5)/16-.5);for(let o=0;o<r;o++)for(let w=0;w<t;w++){var m;c=4*(o*t+w);if(2!=s)m=[N(e[c]+g[c]),N(e[c+1]+g[c+1]),N(e[c+2]+g[c+2]),N(e[c+3]+g[c+3])];else {d=p[4*(3&o)+(3&w)];m=[N(e[c]+d),N(e[c+1]+d),N(e[c+2]+d),N(e[c+3]+d)];}u=0;let v=16777215;for(h=0;h<f;h++){const e=D(m,l[h]);e<v&&(v=e,u=h);}const b=l[u],y=[m[0]-b[0],m[1]-b[1],m[2]-b[2],m[3]-b[3]];1==s&&(w!=t-1&&addErr(y,g,c+4,7),o!=r-1&&(0!=w&&addErr(y,g,c+4*t-4,3),addErr(y,g,c+4*t,5),w!=t-1&&addErr(y,g,c+4*t+4,1))),a[c>>2]=u,A[c>>2]=i[u];}}function _main(e,r,o,a,s){null==s&&(s={});const{crc:f}=i,l=t.writeUint,c=t.writeUshort,u=t.writeASCII;let h=8;const d=e.frames.length>1;let A,g=false,p=33+(d?20:0);if(null!=s.sRGB&&(p+=13),null!=s.pHYs&&(p+=21),null!=s.iCCP&&(A=pako.deflate(s.iCCP),p+=21+A.length+4),3==e.ctype){for(var m=e.plte.length,w=0;w<m;w++)e.plte[w]>>>24!=255&&(g=true);p+=8+3*m+4+(g?8+1*m+4:0);}for(var v=0;v<e.frames.length;v++){d&&(p+=38),p+=(F=e.frames[v]).cimg.length+12,0!=v&&(p+=4);}p+=12;const b=new Uint8Array(p),y=[137,80,78,71,13,10,26,10];for(w=0;w<8;w++)b[w]=y[w];if(l(b,h,13),h+=4,u(b,h,"IHDR"),h+=4,l(b,h,r),h+=4,l(b,h,o),h+=4,b[h]=e.depth,h++,b[h]=e.ctype,h++,b[h]=0,h++,b[h]=0,h++,b[h]=0,h++,l(b,h,f(b,h-17,17)),h+=4,null!=s.sRGB&&(l(b,h,1),h+=4,u(b,h,"sRGB"),h+=4,b[h]=s.sRGB,h++,l(b,h,f(b,h-5,5)),h+=4),null!=s.iCCP){const e=13+A.length;l(b,h,e),h+=4,u(b,h,"iCCP"),h+=4,u(b,h,"ICC profile"),h+=11,h+=2,b.set(A,h),h+=A.length,l(b,h,f(b,h-(e+4),e+4)),h+=4;}if(null!=s.pHYs&&(l(b,h,9),h+=4,u(b,h,"pHYs"),h+=4,l(b,h,s.pHYs[0]),h+=4,l(b,h,s.pHYs[1]),h+=4,b[h]=s.pHYs[2],h++,l(b,h,f(b,h-13,13)),h+=4),d&&(l(b,h,8),h+=4,u(b,h,"acTL"),h+=4,l(b,h,e.frames.length),h+=4,l(b,h,null!=s.loop?s.loop:0),h+=4,l(b,h,f(b,h-12,12)),h+=4),3==e.ctype){l(b,h,3*(m=e.plte.length)),h+=4,u(b,h,"PLTE"),h+=4;for(w=0;w<m;w++){const t=3*w,r=e.plte[w],i=255&r,o=r>>>8&255,a=r>>>16&255;b[h+t+0]=i,b[h+t+1]=o,b[h+t+2]=a;}if(h+=3*m,l(b,h,f(b,h-3*m-4,3*m+4)),h+=4,g){l(b,h,m),h+=4,u(b,h,"tRNS"),h+=4;for(w=0;w<m;w++)b[h+w]=e.plte[w]>>>24&255;h+=m,l(b,h,f(b,h-m-4,m+4)),h+=4;}}let E=0;for(v=0;v<e.frames.length;v++){var F=e.frames[v];d&&(l(b,h,26),h+=4,u(b,h,"fcTL"),h+=4,l(b,h,E++),h+=4,l(b,h,F.rect.width),h+=4,l(b,h,F.rect.height),h+=4,l(b,h,F.rect.x),h+=4,l(b,h,F.rect.y),h+=4,c(b,h,a[v]),h+=2,c(b,h,1e3),h+=2,b[h]=F.dispose,h++,b[h]=F.blend,h++,l(b,h,f(b,h-30,30)),h+=4);const t=F.cimg;l(b,h,(m=t.length)+(0==v?0:4)),h+=4;const r=h;u(b,h,0==v?"IDAT":"fdAT"),h+=4,0!=v&&(l(b,h,E++),h+=4),b.set(t,h),h+=m,l(b,h,f(b,r,h-r)),h+=4;}return l(b,h,0),h+=4,u(b,h,"IEND"),h+=4,l(b,h,f(b,h-4,4)),h+=4,b.buffer}function compressPNG(e,t,r){for(let i=0;i<e.frames.length;i++){const o=e.frames[i];const a=o.rect.height,s=new Uint8Array(a*o.bpl+a);o.cimg=_filterZero(o.img,a,o.bpp,o.bpl,s,t,r);}}function compress(t,r,i,o,a){const s=a[0],f=a[1],l=a[2],c=a[3],u=a[4],h=a[5];let d=6,A=8,g=255;for(var p=0;p<t.length;p++){const e=new Uint8Array(t[p]);for(var m=e.length,w=0;w<m;w+=4)g&=e[w+3];}const v=255!=g,b=function framize(t,r,i,o,a,s){const f=[];for(var l=0;l<t.length;l++){const h=new Uint8Array(t[l]),A=new Uint32Array(h.buffer);var c;let g=0,p=0,m=r,w=i,v=o?1:0;if(0!=l){const b=s||o||1==l||0!=f[l-2].dispose?1:2;let y=0,E=1e9;for(let e=0;e<b;e++){var u=new Uint8Array(t[l-1-e]);const o=new Uint32Array(t[l-1-e]);let s=r,f=i,c=-1,h=-1;for(let e=0;e<i;e++)for(let t=0;t<r;t++){A[d=e*r+t]!=o[d]&&(t<s&&(s=t),t>c&&(c=t),e<f&&(f=e),e>h&&(h=e));} -1==c&&(s=f=c=h=0),a&&(1==(1&s)&&s--,1==(1&f)&&f--);const v=(c-s+1)*(h-f+1);v<E&&(E=v,y=e,g=s,p=f,m=c-s+1,w=h-f+1);}u=new Uint8Array(t[l-1-y]);1==y&&(f[l-1].dispose=2),c=new Uint8Array(m*w*4),e(u,r,i,c,m,w,-g,-p,0),v=e(h,r,i,c,m,w,-g,-p,3)?1:0,1==v?_prepareDiff(h,r,i,c,{x:g,y:p,width:m,height:w}):e(h,r,i,c,m,w,-g,-p,0);}else c=h.slice(0);f.push({rect:{x:g,y:p,width:m,height:w},img:c,blend:v,dispose:0});}if(o)for(l=0;l<f.length;l++){if(1==(A=f[l]).blend)continue;const e=A.rect,o=f[l-1].rect,s=Math.min(e.x,o.x),c=Math.min(e.y,o.y),u={x:s,y:c,width:Math.max(e.x+e.width,o.x+o.width)-s,height:Math.max(e.y+e.height,o.y+o.height)-c};f[l-1].dispose=1,l-1!=0&&_updateFrame(t,r,i,f,l-1,u,a),_updateFrame(t,r,i,f,l,u,a);}let h=0;if(1!=t.length)for(var d=0;d<f.length;d++){var A;h+=(A=f[d]).rect.width*A.rect.height;}return f}(t,r,i,s,f,l),y={},E=[],F=[];if(0!=o){const e=[];for(w=0;w<b.length;w++)e.push(b[w].img.buffer);const t=function concatRGBA(e){let t=0;for(var r=0;r<e.length;r++)t+=e[r].byteLength;const i=new Uint8Array(t);let o=0;for(r=0;r<e.length;r++){const t=new Uint8Array(e[r]),a=t.length;for(let e=0;e<a;e+=4){let r=t[e],a=t[e+1],s=t[e+2];const f=t[e+3];0==f&&(r=a=s=0),i[o+e]=r,i[o+e+1]=a,i[o+e+2]=s,i[o+e+3]=f;}o+=a;}return i.buffer}(e),r=quantize(t,o);for(w=0;w<r.plte.length;w++)E.push(r.plte[w].est.rgba);let i=0;for(w=0;w<b.length;w++){const e=(B=b[w]).img.length;var _=new Uint8Array(r.inds.buffer,i>>2,e>>2);F.push(_);const t=new Uint8Array(r.abuf,i,e);h&&dither(B.img,B.rect.width,B.rect.height,E,t,_),B.img.set(t),i+=e;}}else for(p=0;p<b.length;p++){var B=b[p];const e=new Uint32Array(B.img.buffer);var U=B.rect.width;m=e.length,_=new Uint8Array(m);F.push(_);for(w=0;w<m;w++){const t=e[w];if(0!=w&&t==e[w-1])_[w]=_[w-1];else if(w>U&&t==e[w-U])_[w]=_[w-U];else {let e=y[t];if(null==e&&(y[t]=e=E.length,E.push(t),E.length>=300))break;_[w]=e;}}}const C=E.length;C<=256&&0==u&&(A=C<=2?1:C<=4?2:C<=16?4:8,A=Math.max(A,c));for(p=0;p<b.length;p++){(B=b[p]).rect.x;U=B.rect.width;const e=B.rect.height;let t=B.img;let r=4*U,i=4;if(C<=256&&0==u){r=Math.ceil(A*U/8);var I=new Uint8Array(r*e);const o=F[p];for(let t=0;t<e;t++){w=t*r;const e=t*U;if(8==A)for(var Q=0;Q<U;Q++)I[w+Q]=o[e+Q];else if(4==A)for(Q=0;Q<U;Q++)I[w+(Q>>1)]|=o[e+Q]<<4-4*(1&Q);else if(2==A)for(Q=0;Q<U;Q++)I[w+(Q>>2)]|=o[e+Q]<<6-2*(3&Q);else if(1==A)for(Q=0;Q<U;Q++)I[w+(Q>>3)]|=o[e+Q]<<7-1*(7&Q);}t=I,d=3,i=1;}else if(0==v&&1==b.length){I=new Uint8Array(U*e*3);const o=U*e;for(w=0;w<o;w++){const e=3*w,r=4*w;I[e]=t[r],I[e+1]=t[r+1],I[e+2]=t[r+2];}t=I,d=2,i=3,r=3*U;}B.img=t,B.bpl=r,B.bpp=i;}return {ctype:d,depth:A,plte:E,frames:b}}function _updateFrame(t,r,i,o,a,s,f){const l=Uint8Array,c=Uint32Array,u=new l(t[a-1]),h=new c(t[a-1]),d=a+1<t.length?new l(t[a+1]):null,A=new l(t[a]),g=new c(A.buffer);let p=r,m=i,w=-1,v=-1;for(let e=0;e<s.height;e++)for(let t=0;t<s.width;t++){const i=s.x+t,f=s.y+e,l=f*r+i,c=g[l];0==c||0==o[a-1].dispose&&h[l]==c&&(null==d||0!=d[4*l+3])||(i<p&&(p=i),i>w&&(w=i),f<m&&(m=f),f>v&&(v=f));} -1==w&&(p=m=w=v=0),f&&(1==(1&p)&&p--,1==(1&m)&&m--),s={x:p,y:m,width:w-p+1,height:v-m+1};const b=o[a];b.rect=s,b.blend=1,b.img=new Uint8Array(s.width*s.height*4),0==o[a-1].dispose?(e(u,r,i,b.img,s.width,s.height,-s.x,-s.y,0),_prepareDiff(A,r,i,b.img,s)):e(A,r,i,b.img,s.width,s.height,-s.x,-s.y,0);}function _prepareDiff(t,r,i,o,a){e(t,r,i,o,a.width,a.height,-a.x,-a.y,2);}function _filterZero(e,t,r,i,o,a,s){const f=[];let l,c=[0,1,2,3,4];-1!=a?c=[a]:(t*i>5e5||1==r)&&(c=[0]),s&&(l={level:0});const u=UZIP;for(var h=0;h<c.length;h++){for(let a=0;a<t;a++)_filterLine(o,e,a,i,r,c[h]);f.push(u.deflate(o,l));}let d,A=1e9;for(h=0;h<f.length;h++)f[h].length<A&&(d=h,A=f[h].length);return f[d]}function _filterLine(e,t,i,o,a,s){const f=i*o;let l=f+i;if(e[l]=s,l++,0==s)if(o<500)for(var c=0;c<o;c++)e[l+c]=t[f+c];else e.set(new Uint8Array(t.buffer,f,o),l);else if(1==s){for(c=0;c<a;c++)e[l+c]=t[f+c];for(c=a;c<o;c++)e[l+c]=t[f+c]-t[f+c-a]+256&255;}else if(0==i){for(c=0;c<a;c++)e[l+c]=t[f+c];if(2==s)for(c=a;c<o;c++)e[l+c]=t[f+c];if(3==s)for(c=a;c<o;c++)e[l+c]=t[f+c]-(t[f+c-a]>>1)+256&255;if(4==s)for(c=a;c<o;c++)e[l+c]=t[f+c]-r(t[f+c-a],0,0)+256&255;}else {if(2==s)for(c=0;c<o;c++)e[l+c]=t[f+c]+256-t[f+c-o]&255;if(3==s){for(c=0;c<a;c++)e[l+c]=t[f+c]+256-(t[f+c-o]>>1)&255;for(c=a;c<o;c++)e[l+c]=t[f+c]+256-(t[f+c-o]+t[f+c-a]>>1)&255;}if(4==s){for(c=0;c<a;c++)e[l+c]=t[f+c]+256-r(0,t[f+c-o],0)&255;for(c=a;c<o;c++)e[l+c]=t[f+c]+256-r(t[f+c-a],t[f+c-o],t[f+c-a-o])&255;}}}function quantize(e,t){const r=new Uint8Array(e),i=r.slice(0),o=new Uint32Array(i.buffer),a=getKDtree(i,t),s=a[0],f=a[1],l=r.length,c=new Uint8Array(l>>2);let u;if(r.length<2e7)for(var h=0;h<l;h+=4){u=getNearest(s,d=r[h]*(1/255),A=r[h+1]*(1/255),g=r[h+2]*(1/255),p=r[h+3]*(1/255)),c[h>>2]=u.ind,o[h>>2]=u.est.rgba;}else for(h=0;h<l;h+=4){var d=r[h]*(1/255),A=r[h+1]*(1/255),g=r[h+2]*(1/255),p=r[h+3]*(1/255);for(u=s;u.left;)u=planeDst(u.est,d,A,g,p)<=0?u.left:u.right;c[h>>2]=u.ind,o[h>>2]=u.est.rgba;}return {abuf:i.buffer,inds:c,plte:f}}function getKDtree(e,t,r){null==r&&(r=1e-4);const i=new Uint32Array(e.buffer),o={i0:0,i1:e.length,bst:null,est:null,tdst:0,left:null,right:null};o.bst=stats(e,o.i0,o.i1),o.est=estats(o.bst);const a=[o];for(;a.length<t;){let t=0,o=0;for(var s=0;s<a.length;s++)a[s].est.L>t&&(t=a[s].est.L,o=s);if(t<r)break;const f=a[o],l=splitPixels(e,i,f.i0,f.i1,f.est.e,f.est.eMq255);if(f.i0>=l||f.i1<=l){f.est.L=0;continue}const c={i0:f.i0,i1:l,bst:null,est:null,tdst:0,left:null,right:null};c.bst=stats(e,c.i0,c.i1),c.est=estats(c.bst);const u={i0:l,i1:f.i1,bst:null,est:null,tdst:0,left:null,right:null};u.bst={R:[],m:[],N:f.bst.N-c.bst.N};for(s=0;s<16;s++)u.bst.R[s]=f.bst.R[s]-c.bst.R[s];for(s=0;s<4;s++)u.bst.m[s]=f.bst.m[s]-c.bst.m[s];u.est=estats(u.bst),f.left=c,f.right=u,a[o]=c,a.push(u);}a.sort(((e,t)=>t.bst.N-e.bst.N));for(s=0;s<a.length;s++)a[s].ind=s;return [o,a]}function getNearest(e,t,r,i,o){if(null==e.left)return e.tdst=function dist(e,t,r,i,o){const a=t-e[0],s=r-e[1],f=i-e[2],l=o-e[3];return a*a+s*s+f*f+l*l}(e.est.q,t,r,i,o),e;const a=planeDst(e.est,t,r,i,o);let s=e.left,f=e.right;a>0&&(s=e.right,f=e.left);const l=getNearest(s,t,r,i,o);if(l.tdst<=a*a)return l;const c=getNearest(f,t,r,i,o);return c.tdst<l.tdst?c:l}function planeDst(e,t,r,i,o){const{e:a}=e;return a[0]*t+a[1]*r+a[2]*i+a[3]*o-e.eMq}function splitPixels(e,t,r,i,o,a){for(i-=4;r<i;){for(;vecDot(e,r,o)<=a;)r+=4;for(;vecDot(e,i,o)>a;)i-=4;if(r>=i)break;const s=t[r>>2];t[r>>2]=t[i>>2],t[i>>2]=s,r+=4,i-=4;}for(;vecDot(e,r,o)>a;)r-=4;return r+4}function vecDot(e,t,r){return e[t]*r[0]+e[t+1]*r[1]+e[t+2]*r[2]+e[t+3]*r[3]}function stats(e,t,r){const i=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],o=[0,0,0,0],a=r-t>>2;for(let a=t;a<r;a+=4){const t=e[a]*(1/255),r=e[a+1]*(1/255),s=e[a+2]*(1/255),f=e[a+3]*(1/255);o[0]+=t,o[1]+=r,o[2]+=s,o[3]+=f,i[0]+=t*t,i[1]+=t*r,i[2]+=t*s,i[3]+=t*f,i[5]+=r*r,i[6]+=r*s,i[7]+=r*f,i[10]+=s*s,i[11]+=s*f,i[15]+=f*f;}return i[4]=i[1],i[8]=i[2],i[9]=i[6],i[12]=i[3],i[13]=i[7],i[14]=i[11],{R:i,m:o,N:a}}function estats(e){const{R:t}=e,{m:r}=e,{N:i}=e,a=r[0],s=r[1],f=r[2],l=r[3],c=0==i?0:1/i,u=[t[0]-a*a*c,t[1]-a*s*c,t[2]-a*f*c,t[3]-a*l*c,t[4]-s*a*c,t[5]-s*s*c,t[6]-s*f*c,t[7]-s*l*c,t[8]-f*a*c,t[9]-f*s*c,t[10]-f*f*c,t[11]-f*l*c,t[12]-l*a*c,t[13]-l*s*c,t[14]-l*f*c,t[15]-l*l*c],h=u,d=o;let A=[Math.random(),Math.random(),Math.random(),Math.random()],g=0,p=0;if(0!=i)for(let e=0;e<16&&(A=d.multVec(h,A),p=Math.sqrt(d.dot(A,A)),A=d.sml(1/p,A),!(0!=e&&Math.abs(p-g)<1e-9));e++)g=p;const m=[a*c,s*c,f*c,l*c];return {Cov:u,q:m,e:A,L:g,eMq255:d.dot(d.sml(255,m),A),eMq:d.dot(A,m),rgba:(Math.round(255*m[3])<<24|Math.round(255*m[2])<<16|Math.round(255*m[1])<<8|Math.round(255*m[0])<<0)>>>0}}var o={multVec:(e,t)=>[e[0]*t[0]+e[1]*t[1]+e[2]*t[2]+e[3]*t[3],e[4]*t[0]+e[5]*t[1]+e[6]*t[2]+e[7]*t[3],e[8]*t[0]+e[9]*t[1]+e[10]*t[2]+e[11]*t[3],e[12]*t[0]+e[13]*t[1]+e[14]*t[2]+e[15]*t[3]],dot:(e,t)=>e[0]*t[0]+e[1]*t[1]+e[2]*t[2]+e[3]*t[3],sml:(e,t)=>[e*t[0],e*t[1],e*t[2],e*t[3]]};UPNG.encode=function encode(e,t,r,i,o,a,s){null==i&&(i=0),null==s&&(s=false);const f=compress(e,t,r,i,[false,false,false,0,s,false]);return compressPNG(f,-1),_main(f,t,r,o,a)},UPNG.encodeLL=function encodeLL(e,t,r,i,o,a,s,f){const l={ctype:0+(1==i?0:2)+(0==o?0:4),depth:a,frames:[]},c=(i+o)*a,u=c*t;for(let i=0;i<e.length;i++)l.frames.push({rect:{x:0,y:0,width:t,height:r},img:new Uint8Array(e[i]),blend:0,dispose:1,bpp:Math.ceil(c/8),bpl:Math.ceil(u/8)});return compressPNG(l,0,true),_main(l,t,r,s,f)},UPNG.encode.compress=compress,UPNG.encode.dither=dither,UPNG.quantize=quantize,UPNG.quantize.getKDtree=getKDtree,UPNG.quantize.getNearest=getNearest;}();const r={toArrayBuffer(e,t){const i=e.width,o=e.height,a=i<<2,s=e.getContext("2d").getImageData(0,0,i,o),f=new Uint32Array(s.data.buffer),l=(32*i+31)/32<<2,c=l*o,u=122+c,h=new ArrayBuffer(u),d=new DataView(h),A=1<<20;let g,p,m,w,v=A,b=0,y=0,E=0;function set16(e){d.setUint16(y,e,true),y+=2;}function set32(e){d.setUint32(y,e,true),y+=4;}function seek(e){y+=e;}set16(19778),set32(u),seek(4),set32(122),set32(108),set32(i),set32(-o>>>0),set16(1),set16(32),set32(3),set32(c),set32(2835),set32(2835),seek(8),set32(16711680),set32(65280),set32(255),set32(4278190080),set32(1466527264),function convert(){for(;b<o&&v>0;){for(w=122+b*l,g=0;g<a;)v--,p=f[E++],m=p>>>24,d.setUint32(w+g,p<<8|m),g+=4;b++;}E<f.length?(v=A,setTimeout(convert,r._dly)):t(h);}();},toBlob(e,t){this.toArrayBuffer(e,(e=>{t(new Blob([e],{type:"image/bmp"}));}));},_dly:9};var i={CHROME:"CHROME",FIREFOX:"FIREFOX",DESKTOP_SAFARI:"DESKTOP_SAFARI",IE:"IE",IOS:"IOS",ETC:"ETC"},o={[i.CHROME]:16384,[i.FIREFOX]:11180,[i.DESKTOP_SAFARI]:16384,[i.IE]:8192,[i.IOS]:4096,[i.ETC]:8192};const a="undefined"!=typeof window,s="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope,f=a&&window.cordova&&window.cordova.require&&window.cordova.require("cordova/modulemapper"),CustomFile=(a||s)&&(f&&f.getOriginalSymbol(window,"File")||"undefined"!=typeof File&&File),CustomFileReader=(a||s)&&(f&&f.getOriginalSymbol(window,"FileReader")||"undefined"!=typeof FileReader&&FileReader);function getFilefromDataUrl(e,t,r=Date.now()){return new Promise((i=>{const o=e.split(","),a=o[0].match(/:(.*?);/)[1],s=globalThis.atob(o[1]);let f=s.length;const l=new Uint8Array(f);for(;f--;)l[f]=s.charCodeAt(f);const c=new Blob([l],{type:a});c.name=t,c.lastModified=r,i(c);}))}function getDataUrlFromFile(e){return new Promise(((t,r)=>{const i=new CustomFileReader;i.onload=()=>t(i.result),i.onerror=e=>r(e),i.readAsDataURL(e);}))}function loadImage(e){return new Promise(((t,r)=>{const i=new Image;i.onload=()=>t(i),i.onerror=e=>r(e),i.src=e;}))}function getBrowserName(){if(void 0!==getBrowserName.cachedResult)return getBrowserName.cachedResult;let e=i.ETC;const{userAgent:t}=navigator;return /Chrom(e|ium)/i.test(t)?e=i.CHROME:/iP(ad|od|hone)/i.test(t)&&/WebKit/i.test(t)?e=i.IOS:/Safari/i.test(t)?e=i.DESKTOP_SAFARI:/Firefox/i.test(t)?e=i.FIREFOX:(/MSIE/i.test(t)||true==!!document.documentMode)&&(e=i.IE),getBrowserName.cachedResult=e,getBrowserName.cachedResult}function approximateBelowMaximumCanvasSizeOfBrowser(e,t){const r=getBrowserName(),i=o[r];let a=e,s=t,f=a*s;const l=a>s?s/a:a/s;for(;f>i*i;){const e=(i+a)/2,t=(i+s)/2;e<t?(s=t,a=t*l):(s=e*l,a=e),f=a*s;}return {width:a,height:s}}function getNewCanvasAndCtx(e,t){let r,i;try{if(r=new OffscreenCanvas(e,t),i=r.getContext("2d"),null===i)throw new Error("getContext of OffscreenCanvas returns null")}catch(e){r=document.createElement("canvas"),i=r.getContext("2d");}return r.width=e,r.height=t,[r,i]}function drawImageInCanvas(e,t){const{width:r,height:i}=approximateBelowMaximumCanvasSizeOfBrowser(e.width,e.height),[o,a]=getNewCanvasAndCtx(r,i);return t&&/jpe?g/.test(t)&&(a.fillStyle="white",a.fillRect(0,0,o.width,o.height)),a.drawImage(e,0,0,o.width,o.height),o}function isIOS(){return void 0!==isIOS.cachedResult||(isIOS.cachedResult=["iPad Simulator","iPhone Simulator","iPod Simulator","iPad","iPhone","iPod"].includes(navigator.platform)||navigator.userAgent.includes("Mac")&&"undefined"!=typeof document&&"ontouchend"in document),isIOS.cachedResult}function drawFileInCanvas(e,t={}){return new Promise((function(r,o){let a,s;var $Try_2_Post=function(){try{return s=drawImageInCanvas(a,t.fileType||e.type),r([a,s])}catch(e){return o(e)}},$Try_2_Catch=function(t){try{var $Try_3_Catch=function(e){try{throw e}catch(e){return o(e)}};try{let t;return getDataUrlFromFile(e).then((function(e){try{return t=e,loadImage(t).then((function(e){try{return a=e,function(){try{return $Try_2_Post()}catch(e){return o(e)}}()}catch(e){return $Try_3_Catch(e)}}),$Try_3_Catch)}catch(e){return $Try_3_Catch(e)}}),$Try_3_Catch)}catch(e){$Try_3_Catch(e);}}catch(e){return o(e)}};try{if(isIOS()||[i.DESKTOP_SAFARI,i.MOBILE_SAFARI].includes(getBrowserName()))throw new Error("Skip createImageBitmap on IOS and Safari");return createImageBitmap(e).then((function(e){try{return a=e,$Try_2_Post()}catch(e){return $Try_2_Catch()}}),$Try_2_Catch)}catch(e){$Try_2_Catch();}}))}function canvasToFile(e,t,i,o,a=1){return new Promise((function(s,f){let l;if("image/png"===t){let c,u,h;return c=e.getContext("2d"),({data:u}=c.getImageData(0,0,e.width,e.height)),h=UPNG.encode([u.buffer],e.width,e.height,4096*a),l=new Blob([h],{type:t}),l.name=i,l.lastModified=o,$If_4.call(this)}{if("image/bmp"===t)return new Promise((t=>r.toBlob(e,t))).then(function(e){try{return l=e,l.name=i,l.lastModified=o,$If_5.call(this)}catch(e){return f(e)}}.bind(this),f);{if("function"==typeof OffscreenCanvas&&e instanceof OffscreenCanvas)return e.convertToBlob({type:t,quality:a}).then(function(e){try{return l=e,l.name=i,l.lastModified=o,$If_6.call(this)}catch(e){return f(e)}}.bind(this),f);{let d;return d=e.toDataURL(t,a),getFilefromDataUrl(d,i,o).then(function(e){try{return l=e,$If_6.call(this)}catch(e){return f(e)}}.bind(this),f)}function $If_6(){return $If_5.call(this)}}function $If_5(){return $If_4.call(this)}}function $If_4(){return s(l)}}))}function cleanupCanvasMemory(e){e.width=0,e.height=0;}function isAutoOrientationInBrowser(){return new Promise((function(e,t){let i,o,a,s;return void 0!==isAutoOrientationInBrowser.cachedResult?e(isAutoOrientationInBrowser.cachedResult):(getFilefromDataUrl("data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAAAAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/xABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==","test.jpg",Date.now()).then((function(r){try{return i=r,drawFileInCanvas(i).then((function(r){try{return o=r[1],canvasToFile(o,i.type,i.name,i.lastModified).then((function(r){try{return a=r,cleanupCanvasMemory(o),drawFileInCanvas(a).then((function(r){try{return s=r[0],isAutoOrientationInBrowser.cachedResult=1===s.width&&2===s.height,e(isAutoOrientationInBrowser.cachedResult)}catch(e){return t(e)}}),t)}catch(e){return t(e)}}),t)}catch(e){return t(e)}}),t)}catch(e){return t(e)}}),t))}))}function getExifOrientation(e){return new Promise(((t,r)=>{const i=new CustomFileReader;i.onload=e=>{const r=new DataView(e.target.result);if(65496!=r.getUint16(0,false))return t(-2);const i=r.byteLength;let o=2;for(;o<i;){if(r.getUint16(o+2,false)<=8)return t(-1);const e=r.getUint16(o,false);if(o+=2,65505==e){if(1165519206!=r.getUint32(o+=2,false))return t(-1);const e=18761==r.getUint16(o+=6,false);o+=r.getUint32(o+4,e);const i=r.getUint16(o,e);o+=2;for(let a=0;a<i;a++)if(274==r.getUint16(o+12*a,e))return t(r.getUint16(o+12*a+8,e))}else {if(65280!=(65280&e))break;o+=r.getUint16(o,false);}}return t(-1)},i.onerror=e=>r(e),i.readAsArrayBuffer(e);}))}function handleMaxWidthOrHeight(e,t){const{width:r}=e,{height:i}=e,{maxWidthOrHeight:o}=t;let a,s=e;return isFinite(o)&&(r>o||i>o)&&([s,a]=getNewCanvasAndCtx(r,i),r>i?(s.width=o,s.height=i/r*o):(s.width=r/i*o,s.height=o),a.drawImage(e,0,0,s.width,s.height),cleanupCanvasMemory(e)),s}function followExifOrientation(e,t){const{width:r}=e,{height:i}=e,[o,a]=getNewCanvasAndCtx(r,i);switch(t>4&&t<9?(o.width=i,o.height=r):(o.width=r,o.height=i),t){case 2:a.transform(-1,0,0,1,r,0);break;case 3:a.transform(-1,0,0,-1,r,i);break;case 4:a.transform(1,0,0,-1,0,i);break;case 5:a.transform(0,1,1,0,0,0);break;case 6:a.transform(0,1,-1,0,i,0);break;case 7:a.transform(0,-1,-1,0,i,r);break;case 8:a.transform(0,-1,1,0,0,r);}return a.drawImage(e,0,0,r,i),cleanupCanvasMemory(e),o}function compress(e,t,r=0){return new Promise((function(i,o){let a,s,f,l,c,u,h,d,A,g,p,m,w,v,b,y,E,F,_,B;function incProgress(e=5){if(t.signal&&t.signal.aborted)throw t.signal.reason;a+=e,t.onProgress(Math.min(a,100));}function setProgress(e){if(t.signal&&t.signal.aborted)throw t.signal.reason;a=Math.min(Math.max(e,a),100),t.onProgress(a);}return a=r,s=t.maxIteration||10,f=1024*t.maxSizeMB*1024,incProgress(),drawFileInCanvas(e,t).then(function(r){try{return [,l]=r,incProgress(),c=handleMaxWidthOrHeight(l,t),incProgress(),new Promise((function(r,i){var o;if(!(o=t.exifOrientation))return getExifOrientation(e).then(function(e){try{return o=e,$If_2.call(this)}catch(e){return i(e)}}.bind(this),i);function $If_2(){return r(o)}return $If_2.call(this)})).then(function(r){try{return u=r,incProgress(),isAutoOrientationInBrowser().then(function(r){try{return h=r?c:followExifOrientation(c,u),incProgress(),d=t.initialQuality||1,A=t.fileType||e.type,canvasToFile(h,A,e.name,e.lastModified,d).then(function(r){try{{if(g=r,incProgress(),p=g.size>f,m=g.size>e.size,!p&&!m)return setProgress(100),i(g);var a;function $Loop_3(){if(s--&&(b>f||b>w)){let t,r;return t=B?.95*_.width:_.width,r=B?.95*_.height:_.height,[E,F]=getNewCanvasAndCtx(t,r),F.drawImage(_,0,0,t,r),d*="image/png"===A?.85:.95,canvasToFile(E,A,e.name,e.lastModified,d).then((function(e){try{return y=e,cleanupCanvasMemory(_),_=E,b=y.size,setProgress(Math.min(99,Math.floor((v-b)/(v-f)*100))),$Loop_3}catch(e){return o(e)}}),o)}return [1]}return w=e.size,v=g.size,b=v,_=h,B=!t.alwaysKeepResolution&&p,(a=function(e){for(;e;){if(e.then)return void e.then(a,o);try{if(e.pop){if(e.length)return e.pop()?$Loop_3_exit.call(this):e;e=$Loop_3;}else e=e.call(this);}catch(e){return o(e)}}}.bind(this))($Loop_3);function $Loop_3_exit(){return cleanupCanvasMemory(_),cleanupCanvasMemory(E),cleanupCanvasMemory(c),cleanupCanvasMemory(h),cleanupCanvasMemory(l),setProgress(100),i(y)}}}catch(u){return o(u)}}.bind(this),o)}catch(e){return o(e)}}.bind(this),o)}catch(e){return o(e)}}.bind(this),o)}catch(e){return o(e)}}.bind(this),o)}))}const l="\nlet scriptImported = false\nself.addEventListener('message', async (e) => {\n const { file, id, imageCompressionLibUrl, options } = e.data\n options.onProgress = (progress) => self.postMessage({ progress, id })\n try {\n if (!scriptImported) {\n // console.log('[worker] importScripts', imageCompressionLibUrl)\n self.importScripts(imageCompressionLibUrl)\n scriptImported = true\n }\n // console.log('[worker] self', self)\n const compressedFile = await imageCompression(file, options)\n self.postMessage({ file: compressedFile, id })\n } catch (e) {\n // console.error('[worker] error', e)\n self.postMessage({ error: e.message + '\\n' + e.stack, id })\n }\n})\n";let c;function compressOnWebWorker(e,t){return new Promise(((r,i)=>{c||(c=function createWorkerScriptURL(e){const t=[];return t.push(e),URL.createObjectURL(new Blob(t))}(l));const o=new Worker(c);o.addEventListener("message",(function handler(e){if(t.signal&&t.signal.aborted)o.terminate();else if(void 0===e.data.progress){if(e.data.error)return i(new Error(e.data.error)),void o.terminate();r(e.data.file),o.terminate();}else t.onProgress(e.data.progress);})),o.addEventListener("error",i),t.signal&&t.signal.addEventListener("abort",(()=>{i(t.signal.reason),o.terminate();})),o.postMessage({file:e,imageCompressionLibUrl:t.libURL,options:{...t,onProgress:void 0,signal:void 0}});}))}function imageCompression(e,t){return new Promise((function(r,i){let o,a,s,f,l,c;if(o={...t},s=0,({onProgress:f}=o),o.maxSizeMB=o.maxSizeMB||Number.POSITIVE_INFINITY,l="boolean"!=typeof o.useWebWorker||o.useWebWorker,delete o.useWebWorker,o.onProgress=e=>{s=e,"function"==typeof f&&f(s);},!(e instanceof Blob||e instanceof CustomFile))return i(new Error("The file given is not an instance of Blob or File"));if(!/^image/.test(e.type))return i(new Error("The file given is not an image"));if(c="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope,!l||"function"!=typeof Worker||c)return compress(e,o).then(function(e){try{return a=e,$If_4.call(this)}catch(e){return i(e)}}.bind(this),i);var u=function(){try{return $If_4.call(this)}catch(e){return i(e)}}.bind(this),$Try_1_Catch=function(t){try{return compress(e,o).then((function(e){try{return a=e,u()}catch(e){return i(e)}}),i)}catch(e){return i(e)}};try{return o.libURL=o.libURL||"https://cdn.jsdelivr.net/npm/browser-image-compression@2.0.2/dist/browser-image-compression.js",compressOnWebWorker(e,o).then((function(e){try{return a=e,u()}catch(e){return $Try_1_Catch()}}),$Try_1_Catch)}catch(e){$Try_1_Catch();}function $If_4(){try{a.name=e.name,a.lastModified=e.lastModified;}catch(e){}try{o.preserveExif&&"image/jpeg"===e.type&&(!o.fileType||o.fileType&&o.fileType===e.type)&&(a=copyExifWithoutOrientation(e,a));}catch(e){}return r(a)}}))}imageCompression.getDataUrlFromFile=getDataUrlFromFile,imageCompression.getFilefromDataUrl=getFilefromDataUrl,imageCompression.loadImage=loadImage,imageCompression.drawImageInCanvas=drawImageInCanvas,imageCompression.drawFileInCanvas=drawFileInCanvas,imageCompression.canvasToFile=canvasToFile,imageCompression.getExifOrientation=getExifOrientation,imageCompression.handleMaxWidthOrHeight=handleMaxWidthOrHeight,imageCompression.followExifOrientation=followExifOrientation,imageCompression.cleanupCanvasMemory=cleanupCanvasMemory,imageCompression.isAutoOrientationInBrowser=isAutoOrientationInBrowser,imageCompression.approximateBelowMaximumCanvasSizeOfBrowser=approximateBelowMaximumCanvasSizeOfBrowser,imageCompression.copyExifWithoutOrientation=copyExifWithoutOrientation,imageCompression.getBrowserName=getBrowserName,imageCompression.version="2.0.2";
15847
+
15848
+ class KritzelImageTool extends KritzelBaseTool {
15849
+ fileInput = null;
15850
+ maxCompressionSize = 300;
15122
15851
  constructor(core) {
15123
15852
  super(core);
15853
+ this.setupFileInput();
15124
15854
  }
15125
- reset() {
15126
- this.initialMouseX = 0;
15127
- this.initialMouseY = 0;
15128
- this.initialSize = { x: 0, y: 0, width: 0, height: 0 };
15129
- this.newSize = { x: 0, y: 0, width: 0, height: 0 };
15130
- this.hasResized = false;
15855
+ onActivate() {
15856
+ this.openFilePicker();
15131
15857
  }
15132
- handlePointerDown(event) {
15133
- if (event.pointerType === 'mouse') {
15134
- if (KritzelEventHelper.isLeftClick(event)) {
15135
- const selectionGroup = this._core.store.selectionGroup;
15136
- if (selectionGroup && this._core.store.state.isResizeHandleSelected) {
15137
- const clientX = event.clientX - this._core.store.offsetX;
15138
- const clientY = event.clientY - this._core.store.offsetY;
15139
- this._core.store.state.isResizing = true;
15140
- this.initialMouseX = clientX;
15141
- this.initialMouseY = clientY;
15142
- this.initialSize.width = selectionGroup.width;
15143
- this.initialSize.height = selectionGroup.height;
15144
- this.initialSize.x = selectionGroup.translateX;
15145
- this.initialSize.y = selectionGroup.translateY;
15146
- }
15147
- }
15148
- }
15149
- if (event.pointerType === 'touch') {
15150
- const activePointers = Array.from(this._core.store.state.pointers.values());
15151
- const firstTouch = activePointers[0];
15152
- if (!firstTouch) {
15153
- return;
15154
- }
15155
- if (activePointers.length === 1) {
15156
- const selectionGroup = this._core.store.selectionGroup;
15157
- if (selectionGroup && this._core.store.state.isResizeHandleSelected) {
15158
- const clientX = Math.round(firstTouch.clientX - this._core.store.offsetX);
15159
- const clientY = Math.round(firstTouch.clientY - this._core.store.offsetY);
15160
- this._core.store.state.isResizing = true;
15161
- this.initialMouseX = clientX;
15162
- this.initialMouseY = clientY;
15163
- this.initialSize.width = selectionGroup.width;
15164
- this.initialSize.height = selectionGroup.height;
15165
- this.initialSize.x = selectionGroup.translateX;
15166
- this.initialSize.y = selectionGroup.translateY;
15167
- clearTimeout(this._core.store.state.longTouchTimeout);
15168
- }
15169
- }
15858
+ openFilePicker() {
15859
+ if (this._core.store.isDisabled) {
15860
+ return;
15170
15861
  }
15862
+ this.fileInput.click();
15171
15863
  }
15172
- handlePointerMove(event) {
15173
- if (event.pointerType === 'mouse') {
15174
- const selectionGroup = this._core.store.selectionGroup;
15175
- if (this._core.store.state.isResizing && selectionGroup) {
15176
- const clientX = event.clientX - this._core.store.offsetX;
15177
- const clientY = event.clientY - this._core.store.offsetY;
15178
- const dx = clientX - this.initialMouseX;
15179
- const dy = clientY - this.initialMouseY;
15180
- const resizeDeltaX = Math.abs(dx);
15181
- const resizeDeltaY = Math.abs(dy);
15182
- const resizeThreshold = 5;
15183
- if (resizeDeltaX > resizeThreshold || resizeDeltaY > resizeThreshold) {
15184
- this.hasResized = true;
15185
- }
15186
- if (!this.hasResized) {
15187
- return;
15188
- }
15189
- const rotation = selectionGroup.rotation;
15190
- const sin = Math.sin(rotation);
15191
- const cos = Math.cos(rotation);
15192
- const activeScale = selectionGroup.scale || this._core.store.state.scale;
15193
- // Calculate delta in local unrotated space
15194
- // We rotate the screen delta by -rotation to align with the object's axes
15195
- const localDx = dx * cos + dy * sin;
15196
- const localDy = -dx * sin + dy * cos;
15197
- // Calculate the center of the selection group before resize
15198
- const initialCenterX = this.initialSize.x + this.initialSize.width / activeScale / 2;
15199
- const initialCenterY = this.initialSize.y + this.initialSize.height / activeScale / 2;
15200
- // The center moves by half of the screen delta (scaled)
15201
- // This is true regardless of rotation because the resize happens symmetrically around the center
15202
- // relative to the fixed point logic
15203
- const newCenterX = initialCenterX + dx / activeScale / 2;
15204
- const newCenterY = initialCenterY + dy / activeScale / 2;
15205
- switch (this._core.store.state.resizeHandleType) {
15206
- case KritzelHandleType.TopLeft:
15207
- this.newSize.width = this.initialSize.width - localDx;
15208
- this.newSize.height = this.initialSize.height - localDy;
15209
- break;
15210
- case KritzelHandleType.TopRight:
15211
- this.newSize.width = this.initialSize.width + localDx;
15212
- this.newSize.height = this.initialSize.height - localDy;
15213
- break;
15214
- case KritzelHandleType.BottomLeft:
15215
- this.newSize.width = this.initialSize.width - localDx;
15216
- this.newSize.height = this.initialSize.height + localDy;
15217
- break;
15218
- case KritzelHandleType.BottomRight:
15219
- this.newSize.width = this.initialSize.width + localDx;
15220
- this.newSize.height = this.initialSize.height + localDy;
15221
- break;
15222
- }
15223
- this.newSize.x = newCenterX - this.newSize.width / activeScale / 2;
15224
- this.newSize.y = newCenterY - this.newSize.height / activeScale / 2;
15225
- selectionGroup.resize(this.newSize.x, this.newSize.y, this.newSize.width, this.newSize.height);
15226
- }
15227
- }
15228
- if (event.pointerType === 'touch') {
15229
- const activePointers = Array.from(this._core.store.state.pointers.values());
15230
- const oneFingerTouch = activePointers[0];
15231
- if (!oneFingerTouch) {
15232
- return;
15233
- }
15234
- const selectionGroup = this._core.store.selectionGroup;
15235
- if (this._core.store.state.isResizing && selectionGroup) {
15236
- const clientX = Math.round(oneFingerTouch.clientX - this._core.store.offsetX);
15237
- const clientY = Math.round(oneFingerTouch.clientY - this._core.store.offsetY);
15238
- const dx = clientX - this.initialMouseX;
15239
- const dy = clientY - this.initialMouseY;
15240
- const resizeDeltaX = Math.abs(dx);
15241
- const resizeDeltaY = Math.abs(dy);
15242
- const resizeThreshold = 5;
15243
- if (resizeDeltaX > resizeThreshold || resizeDeltaY > resizeThreshold) {
15244
- clearTimeout(this._core.store.state.longTouchTimeout);
15245
- this.hasResized = true;
15246
- }
15247
- if (!this.hasResized) {
15248
- return;
15249
- }
15250
- const rotation = selectionGroup.rotation;
15251
- const sin = Math.sin(rotation);
15252
- const cos = Math.cos(rotation);
15253
- const activeScale = selectionGroup.scale || this._core.store.state.scale;
15254
- const localDx = dx * cos + dy * sin;
15255
- const localDy = -dx * sin + dy * cos;
15256
- const initialCenterX = this.initialSize.x + this.initialSize.width / activeScale / 2;
15257
- const initialCenterY = this.initialSize.y + this.initialSize.height / activeScale / 2;
15258
- const newCenterX = initialCenterX + dx / activeScale / 2;
15259
- const newCenterY = initialCenterY + dy / activeScale / 2;
15260
- switch (this._core.store.state.resizeHandleType) {
15261
- case KritzelHandleType.TopLeft:
15262
- this.newSize.width = this.initialSize.width - localDx;
15263
- this.newSize.height = this.initialSize.height - localDy;
15264
- break;
15265
- case KritzelHandleType.TopRight:
15266
- this.newSize.width = this.initialSize.width + localDx;
15267
- this.newSize.height = this.initialSize.height - localDy;
15268
- break;
15269
- case KritzelHandleType.BottomLeft:
15270
- this.newSize.width = this.initialSize.width - localDx;
15271
- this.newSize.height = this.initialSize.height + localDy;
15272
- break;
15273
- case KritzelHandleType.BottomRight:
15274
- this.newSize.width = this.initialSize.width + localDx;
15275
- this.newSize.height = this.initialSize.height + localDy;
15276
- break;
15277
- }
15278
- this.newSize.x = newCenterX - this.newSize.width / activeScale / 2;
15279
- this.newSize.y = newCenterY - this.newSize.height / activeScale / 2;
15280
- selectionGroup.resize(this.newSize.x, this.newSize.y, this.newSize.width, this.newSize.height);
15281
- }
15282
- }
15864
+ setupFileInput() {
15865
+ this.fileInput = document.createElement('input');
15866
+ this.fileInput.type = 'file';
15867
+ this.fileInput.accept = 'image/*';
15868
+ this.fileInput.style.display = 'none';
15869
+ this.fileInput.addEventListener('change', this.handleFileSelect.bind(this));
15870
+ this.fileInput.addEventListener('cancel', this.handleCancel.bind(this));
15871
+ document.body.appendChild(this.fileInput);
15283
15872
  }
15284
- handlePointerUp(event) {
15285
- if (event.pointerType === 'mouse') {
15286
- if (this._core.store.state.isResizing) {
15287
- this._core.store.state.isResizing = false;
15288
- if (this.hasResized) {
15289
- this._core.store.selectionGroup.update();
15290
- this._core.engine.emitObjectsChange();
15291
- this._core.store.state.hasObjectsChanged = true;
15292
- }
15293
- this.reset();
15294
- }
15873
+ handleFileSelect(event) {
15874
+ const input = event.target;
15875
+ if (input.files && input.files[0]) {
15876
+ const file = input.files[0];
15877
+ imageCompression(file, {
15878
+ maxWidthOrHeight: this.maxCompressionSize,
15879
+ })
15880
+ .then(compressedFile => {
15881
+ this.readFile(compressedFile);
15882
+ })
15883
+ .catch(error => {
15884
+ console.error('Error during image compression or processing:', error);
15885
+ this.handleCancel();
15886
+ });
15295
15887
  }
15296
- if (event.pointerType === 'touch') {
15297
- if (this._core.store.state.isResizing) {
15298
- this._core.store.state.isResizing = false;
15299
- if (this.hasResized) {
15300
- this._core.store.selectionGroup.update();
15301
- this._core.engine.emitObjectsChange();
15302
- this._core.store.state.hasObjectsChanged = true;
15303
- }
15304
- this.reset();
15305
- clearTimeout(this._core.store.state.longTouchTimeout);
15306
- }
15888
+ else {
15889
+ console.info('File selection cancelled by user.');
15890
+ this.handleCancel();
15891
+ }
15892
+ if (input) {
15893
+ input.value = '';
15307
15894
  }
15308
15895
  }
15896
+ readFile(file) {
15897
+ const reader = new FileReader();
15898
+ reader.onload = e => {
15899
+ const img = new Image();
15900
+ img.src = e.target?.result;
15901
+ img.onload = () => this.createKritzelImage(img);
15902
+ };
15903
+ reader.readAsDataURL(file);
15904
+ }
15905
+ createKritzelImage(img) {
15906
+ const image = KritzelImage.create(this._core);
15907
+ const { scaledWidth, scaledHeight } = image.calculateScaledDimensions(img);
15908
+ image.src = img.src;
15909
+ image.width = scaledWidth;
15910
+ image.height = scaledHeight;
15911
+ image.zIndex = this._core.store.currentZIndex;
15912
+ image.centerInViewport();
15913
+ this.addImageToStore(image);
15914
+ return image;
15915
+ }
15916
+ addImageToStore(image) {
15917
+ this._core.addObject(image);
15918
+ this._core.store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
15919
+ this._core.selectObjects([image]);
15920
+ this._core.engine.emitObjectsChange();
15921
+ }
15922
+ handleCancel() {
15923
+ this._core.store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
15924
+ }
15309
15925
  }
15310
15926
 
15311
- class KritzelRotationHandler extends KritzelBaseHandler {
15312
- initialRotation = 0;
15313
- rotation = 0;
15314
- unchangedObjects = [];
15315
- initialSelectionGroupRotation = 0;
15927
+ class KritzelTextTool extends KritzelBaseTool {
15928
+ fontFamily = 'Arial';
15929
+ fontSize = 16;
15930
+ fontColor = '#000000';
15931
+ palette = [
15932
+ '#000000',
15933
+ '#FFFFFF',
15934
+ '#FF0000',
15935
+ '#00FF00',
15936
+ '#0000FF',
15937
+ '#FFFF00',
15938
+ '#FF00FF',
15939
+ '#00FFFF',
15940
+ '#808080',
15941
+ '#C0C0C0',
15942
+ '#800000',
15943
+ '#008000',
15944
+ '#000080',
15945
+ '#808000',
15946
+ '#800080',
15947
+ ];
15316
15948
  constructor(core) {
15317
15949
  super(core);
15318
15950
  }
15319
- reset() {
15320
- this.initialRotation = 0;
15321
- this.rotation = 0;
15322
- this.unchangedObjects = [];
15323
- }
15324
15951
  handlePointerDown(event) {
15952
+ if (event.cancelable) {
15953
+ event.preventDefault();
15954
+ }
15325
15955
  if (event.pointerType === 'mouse') {
15326
- if (KritzelEventHelper.isLeftClick(event)) {
15327
- const selectionGroup = this._core.store.selectionGroup;
15328
- if (selectionGroup && this._core.store.state.isRotationHandleSelected) {
15329
- const clientX = event.clientX - this._core.store.offsetX;
15330
- const clientY = event.clientY - this._core.store.offsetY;
15331
- this._core.store.state.isRotating = true;
15332
- const centerX = selectionGroup.translateX + selectionGroup.width / 2 / this._core.store.state.scale;
15333
- const centerY = selectionGroup.translateY + selectionGroup.height / 2 / this._core.store.state.scale;
15334
- const cursorX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
15335
- const cursorY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
15336
- this.initialSelectionGroupRotation = selectionGroup.rotation;
15337
- this.initialRotation = Math.atan2(centerY - cursorY, centerX - cursorX) - selectionGroup.rotation;
15338
- this.unchangedObjects = selectionGroup.objects.map(obj => obj.clone());
15339
- }
15956
+ const path = event.composedPath().slice(1);
15957
+ const objectElement = path.find(element => element.classList && element.classList.contains('object'));
15958
+ const object = this._core.findObjectById(objectElement?.id);
15959
+ const activeText = this._core.store.activeText;
15960
+ if (activeText === null && object instanceof KritzelText) {
15961
+ object.edit(event);
15962
+ return;
15340
15963
  }
15341
- }
15964
+ if (activeText !== null && object instanceof KritzelText) {
15965
+ activeText.save();
15966
+ object.edit(event);
15967
+ return;
15968
+ }
15969
+ if (activeText !== null && object instanceof KritzelText === false) {
15970
+ this._core.resetActiveText();
15971
+ this._core.store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
15972
+ return;
15973
+ }
15974
+ if (KritzelEventHelper.isLeftClick(event) === false) {
15975
+ return;
15976
+ }
15977
+ const clientX = event.clientX - this._core.store.offsetX;
15978
+ const clientY = event.clientY - this._core.store.offsetY;
15979
+ const text = KritzelText.create(this._core, this.fontSize, this.fontFamily);
15980
+ text.fontColor = this.fontColor;
15981
+ text.translateX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
15982
+ text.translateY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
15983
+ text.zIndex = this._core.store.currentZIndex;
15984
+ this._core.store.state.objects.insert(text);
15985
+ this._core.rerender();
15986
+ text.edit(event);
15987
+ }
15342
15988
  if (event.pointerType === 'touch') {
15343
15989
  const activePointers = Array.from(this._core.store.state.pointers.values());
15344
- const firstTouch = activePointers[0];
15345
- if (!firstTouch) {
15990
+ const path = event.composedPath().slice(1);
15991
+ const objectElement = path.find(element => element.classList && element.classList.contains('object'));
15992
+ const object = this._core.findObjectById(objectElement?.id);
15993
+ const activeText = this._core.store.activeText;
15994
+ if (activeText === null && object instanceof KritzelText) {
15995
+ object.edit(event);
15346
15996
  return;
15347
15997
  }
15348
- if (activePointers.length === 1) {
15998
+ if (activeText !== null && object instanceof KritzelText) {
15999
+ activeText.save();
16000
+ object.edit(event);
16001
+ return;
16002
+ }
16003
+ if (activeText !== null && object instanceof KritzelText === false) {
16004
+ this._core.resetActiveText();
16005
+ this._core.store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
16006
+ return;
16007
+ }
16008
+ if (activePointers.length > 1) {
16009
+ return;
16010
+ }
16011
+ const clientX = Math.round(activePointers[0].clientX - this._core.store.offsetX);
16012
+ const clientY = Math.round(activePointers[0].clientY - this._core.store.offsetY);
16013
+ const text = KritzelText.create(this._core, this.fontSize, this.fontFamily);
16014
+ text.fontColor = this.fontColor;
16015
+ text.translateX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
16016
+ text.translateY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
16017
+ text.zIndex = this._core.store.currentZIndex;
16018
+ this._core.store.state.objects.insert(text);
16019
+ this._core.rerender();
16020
+ text.edit(event);
16021
+ }
16022
+ }
16023
+ handlePointerUp(event) {
16024
+ if (event.cancelable) {
16025
+ event.preventDefault();
16026
+ }
16027
+ this._core.store.activeText?.edit(event);
16028
+ }
16029
+ }
16030
+
16031
+ class KritzelIconRegistry {
16032
+ static registry = new Map();
16033
+ static register(name, svgContent) {
16034
+ if (this.registry.has(name)) {
16035
+ console.warn(`[IconRegistry] Icon "${name}" is already registered. It will be overwritten.`);
16036
+ }
16037
+ this.registry.set(name, svgContent);
16038
+ }
16039
+ static get(name) {
16040
+ return this.registry.get(name);
16041
+ }
16042
+ static registerIcons(icons) {
16043
+ for (const name in icons) {
16044
+ if (Object.prototype.hasOwnProperty.call(icons, name)) {
16045
+ this.register(name, icons[name]);
16046
+ }
16047
+ }
16048
+ }
16049
+ static has(name) {
16050
+ return this.registry.has(name);
16051
+ }
16052
+ }
16053
+ KritzelIconRegistry.registerIcons({
16054
+ 'cursor': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z"/></svg>',
16055
+ 'pen': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/></svg>',
16056
+ 'arrow': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="19" x2="19" y2="5"/><path d="M15 5h4v4"/></svg>',
16057
+ 'highlighter': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-highlighter-icon lucide-highlighter"><path d="m9 11-6 6v3h9l3-3"/><path d="m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4"/></svg>',
16058
+ 'eraser': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21"/><path d="M22 21H7"/><path d="m5 11 9 9"/></svg>',
16059
+ 'type': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" x2="15" y1="20" y2="20"/><line x1="12" x2="12" y1="4" y2="20"/></svg>',
16060
+ 'image': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>',
16061
+ 'chevron-down': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>',
16062
+ 'chevron-up': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>',
16063
+ 'copy': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy-icon lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>',
16064
+ 'paste': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clipboard-paste-icon lucide-clipboard-paste"><path d="M11 14h10"/><path d="M16 4h2a2 2 0 0 1 2 2v1.344"/><path d="m17 18 4-4-4-4"/><path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 1.793-1.113"/><rect x="8" y="2" width="8" height="4" rx="1"/></svg>',
16065
+ 'cut': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-scissors-icon lucide-scissors"><circle cx="6" cy="6" r="3"/><path d="M8.12 8.12 12 12"/><path d="M20 4 8.12 15.88"/><circle cx="6" cy="18" r="3"/><path d="M14.8 14.8 20 20"/></svg>',
16066
+ 'delete': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-trash2-icon lucide-trash-2"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>',
16067
+ 'bring-to-front': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-up-to-line-icon lucide-arrow-up-to-line"><path d="M5 3h14"/><path d="m18 13-6-6-6 6"/><path d="M12 7v14"/></svg>',
16068
+ 'send-to-back': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-down-to-line-icon lucide-arrow-down-to-line"><path d="M12 17V3"/><path d="m6 11 6 6 6-6"/><path d="M19 21H5"/></svg>',
16069
+ 'select-all': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-mouse-pointer-icon lucide-square-mouse-pointer"><path d="M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z"/><path d="M21 11V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6"/></svg>',
16070
+ 'download': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download-icon lucide-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>',
16071
+ 'undo': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-undo-icon lucide-undo"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/></svg>',
16072
+ 'redo': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-redo-icon lucide-redo"><path d="M21 7v6h-6"/><path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7"/></svg>',
16073
+ 'plus': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-plus-icon lucide-plus"><path d="M5 12h14"/><path d="M12 5v14"/></svg>',
16074
+ 'ellipsis-vertical': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-ellipsis-vertical-icon lucide-ellipsis-vertical"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>',
16075
+ 'x': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',
16076
+ 'check': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg>',
16077
+ 'move-vertical': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-move-vertical-icon lucide-move-vertical" version="1.1" id="svg3"> <defs id="defs3" /> <path d="M12 2v20" id="path1" /> <path style="fill:#ffffff;stroke-width:2.5;stroke-dasharray:none;stroke-linejoin:round;paint-order:stroke fill markers" d="m 11.735575,22.661865 c -0.09259,-0.02798 -0.204674,-0.07661 -0.249076,-0.108068 -0.04441,-0.03147 -1.167275,-0.979853 -2.4952713,-2.10755 -1.8557024,-1.57581 -2.4300904,-2.079639 -2.4817336,-2.17687 -0.086514,-0.162885 -0.089504,-0.422449 -0.00664,-0.576334 0.1483053,-0.275409 0.437667,-0.436207 0.7830634,-0.435147 0.3692925,0.0011 0.3517326,-0.01122 2.168748,1.525599 L 11.12348,20.194964 V 11.999996 3.8050256 L 9.4546663,5.2164943 C 7.6376509,6.7533118 7.6552109,6.7409594 7.2859184,6.7420935 6.6681409,6.7439906 6.253658,6.1955854 6.5159903,5.723396 6.5738626,5.6192278 7.1368766,5.1267427 9.0629381,3.4955044 11.738128,1.2298067 11.640395,1.3026868 12.00355,1.3026868 c 0.363154,0 0.265421,-0.07288 2.940611,2.1928176 1.926062,1.6312383 2.489076,2.1237234 2.546948,2.2278916 0.262332,0.4721894 -0.15215,1.0205946 -0.769928,1.0186975 -0.369293,-0.00114 -0.351733,0.011218 -2.168748,-1.5255992 L 12.88362,3.8050256 v 8.1949704 8.194968 l 1.668813,-1.411469 c 1.817015,-1.536817 1.799455,-1.524464 2.168748,-1.525599 0.617772,-0.0019 1.032269,0.546521 0.769928,1.018687 -0.103474,0.18623 -4.919006,4.273935 -5.130582,4.355136 -0.20796,0.07981 -0.425829,0.09033 -0.624952,0.03014 z" id="path4" /> </svg>',
16078
+ 'hand': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-hand-icon lucide-hand" version="1.1" id="svg4"> <defs id="defs4" /> <path d="M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2" id="path1" /> <path d="M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2" id="path2" /> <path d="M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8" id="path3" /> <path d="M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15" id="path4" /> <path style="fill:#ffffff;stroke-width:0.0423032" d="M 11.478261,21.222477 C 9.6854059,21.139331 8.3341788,20.647346 7.1297169,19.639159 6.9698298,19.505327 5.949644,18.508708 4.8626374,17.42445 3.0780314,15.644357 2.8775851,15.435367 2.7968392,15.270602 2.5896561,14.847836 2.616354,14.436649 2.8771894,14.033085 c 0.136522,-0.211226 0.3837159,-0.398688 0.6367632,-0.482897 0.1529977,-0.05091 0.2326803,-0.05992 0.4470908,-0.05054 0.2250407,0.0098 0.2876577,0.02439 0.4554568,0.105827 0.1747854,0.08483 0.2933718,0.192702 1.1675186,1.062064 0.7182021,0.714271 1.0062041,0.982633 1.0998825,1.024878 0.1680197,0.07577 0.4756439,0.07817 0.6368269,0.005 0.1370772,-0.06226 0.2976691,-0.220818 0.3691296,-0.364463 0.072561,-0.145855 0.071573,-0.543545 -0.00169,-0.681911 -0.028747,-0.05429 -0.2476439,-0.296131 -0.4864385,-0.537426 l -0.4341716,-0.438718 0.00753,-3.989014 0.00753,-3.9890137 0.089246,-0.1883791 c 0.244787,-0.516692 0.7711718,-0.809716 1.3059705,-0.7269981 0.3973687,0.061462 0.7569953,0.3284904 0.9363122,0.6952277 l 0.093853,0.1919473 0.014101,2.4958872 0.014101,2.4958877 0.067385,0.149123 c 0.1186861,0.262654 0.4140438,0.457222 0.6940724,0.457222 0.2764172,0 0.5690532,-0.187563 0.6965992,-0.446482 l 0.06486,-0.131661 0.0141,-3.4970626 0.0141,-3.4970623 0.08982,-0.1896121 C 11.096301,3.0422103 11.506844,2.7755634 12,2.7755634 c 0.493156,0 0.903699,0.2666469 1.122868,0.7293016 l 0.08982,0.1896121 0.0141,3.2432432 c 0.01405,3.2315947 0.01432,3.2437077 0.07397,3.3726737 0.124721,0.269649 0.355908,0.424566 0.661411,0.443206 0.237954,0.01452 0.429018,-0.0627 0.591626,-0.239109 0.223655,-0.242637 0.208338,-0.06565 0.224113,-2.5896966 l 0.0141,-2.2561693 0.09385,-0.1919473 c 0.179317,-0.3667373 0.538944,-0.6337662 0.936313,-0.6952277 0.609359,-0.09425 1.208067,0.3054956 1.370981,0.9153772 0.03013,0.1127929 0.03773,0.6662436 0.038,2.7657391 3.74e-4,2.9328416 -0.008,2.8034316 0.197044,3.0364016 0.234927,0.266892 0.603828,0.337117 0.920407,0.175213 0.181933,-0.09304 0.329759,-0.261686 0.376309,-0.4293 0.01848,-0.06654 0.02929,-0.683932 0.0295,-1.684364 1.78e-4,-0.8783075 0.01239,-1.6530128 0.02751,-1.745346 0.08579,-0.5238478 0.505382,-0.9420803 1.039546,-1.0361716 0.607538,-0.1070155 1.25615,0.3485846 1.385876,0.973471 0.02211,0.1064847 0.02843,1.1397236 0.02169,3.5455556 -0.0093,3.324725 -0.01078,3.403075 -0.07062,3.770606 -0.126399,0.776213 -0.328814,1.41352 -0.669031,2.106456 -0.36657,0.746612 -0.72118,1.250303 -1.297841,1.843464 -1.185731,1.21966 -2.604527,1.933174 -4.300822,2.162889 -0.38234,0.05178 -2.604621,0.0785 -3.412456,0.04104 z" id="path16" /> </svg>',
16079
+ 'hand-grab': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-hand-grab-icon lucide-hand-grab" version="1.1" id="svg5"> <defs id="defs5" /> <path d="M18 11.5V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v1.4" id="path1" /> <path d="M14 10V8a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2" id="path2" /> <path d="M10 9.9V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v5" id="path3" /> <path d="M6 14a2 2 0 0 0-2-2a2 2 0 0 0-2 2" id="path4" /> <path d="M18 11a2 2 0 1 1 4 0v3a8 8 0 0 1-8 8h-4a8 8 0 0 1-8-8 2 2 0 1 1 4 0" id="path5" /> <path style="fill:#ffffff;stroke-width:0.0423032" d="M 9.5887192,21.221364 C 8.0526856,21.128375 6.6533474,20.587008 5.4571093,19.622953 5.3019976,19.497947 5.0101058,19.225967 4.8084606,19.018552 4.2300382,18.423579 3.8474097,17.879011 3.4960953,17.150756 3.1017715,16.333345 2.9014937,15.633198 2.8052391,14.735605 c -0.097577,-0.909922 -0.019834,-1.263323 0.3569794,-1.622742 0.753018,-0.718257 1.9463784,-0.256949 2.0619296,0.797066 0.037839,0.345154 0.089701,0.477674 0.2472582,0.631806 0.2464521,0.241095 0.5958008,0.287445 0.9036474,0.119894 0.1478567,-0.08047 0.2303871,-0.16866 0.3266699,-0.349056 0.050118,-0.0939 0.05337,-0.2326 0.066784,-2.848413 L 6.7826087,8.7144536 6.8458627,8.559342 C 6.9736603,8.2459558 7.2463773,7.9734987 7.5581669,7.8477164 c 0.1253804,-0.050581 0.2118709,-0.062705 0.4512338,-0.063254 0.275392,-6.312e-4 0.3094658,0.00564 0.4867271,0.089609 0.264676,0.1253746 0.4771417,0.334346 0.6070085,0.5970252 l 0.1048543,0.212087 0.017338,0.7488899 c 0.019395,0.8377165 0.026343,0.8713745 0.2242006,1.0860245 0.1591067,0.172611 0.3522021,0.249908 0.5883018,0.235502 0.305503,-0.01864 0.53669,-0.173557 0.661411,-0.443206 0.05734,-0.123963 0.0605,-0.181852 0.07486,-1.3703236 0.01626,-1.3456629 0.0141,-1.3267758 0.182667,-1.5938092 0.257891,-0.4085268 0.77099,-0.6442307 1.228943,-0.5645435 0.471012,0.08196 0.850379,0.4102836 0.989676,0.8565175 0.04663,0.149383 0.0514,0.2860903 0.0514,1.4721724 0,0.8344844 0.01108,1.3591794 0.03064,1.4503554 0.04109,0.191585 0.179844,0.390224 0.342505,0.490321 0.27721,0.170587 0.693425,0.126085 0.92529,-0.09893 0.228986,-0.222224 0.234357,-0.25046 0.251582,-1.3227071 l 0.0152,-0.9463484 0.104855,-0.2120434 c 0.129872,-0.2626357 0.342372,-0.471626 0.607008,-0.5969817 0.177262,-0.083967 0.211335,-0.09024 0.486727,-0.089609 0.239363,5.486e-4 0.325854,0.012673 0.451234,0.063254 0.31179,0.1257823 0.584507,0.3982394 0.712304,0.7116256 0.0621,0.1522783 0.06351,0.1824139 0.07736,1.649824 0.01408,1.492435 0.0142,1.494914 0.07947,1.627432 0.0771,0.156535 0.2216,0.291061 0.395058,0.367789 0.09695,0.04289 0.169789,0.05275 0.325757,0.04411 0.230544,-0.01277 0.363779,-0.06826 0.506411,-0.21089 0.177062,-0.177061 0.198302,-0.25307 0.219359,-0.784959 0.02002,-0.505773 0.05012,-0.6549 0.175297,-0.868492 0.498409,-0.850471 1.728484,-0.8041941 2.173478,0.08177 0.131751,0.262312 0.134447,0.313526 0.122615,2.328965 -0.0104,1.771638 -0.01517,1.923481 -0.0714,2.273746 -0.123689,0.770512 -0.327108,1.411813 -0.668163,2.106456 -0.36657,0.746612 -0.72118,1.250303 -1.297841,1.843464 -1.180268,1.214041 -2.600612,1.930754 -4.287083,2.163284 -0.28697,0.03957 -0.685446,0.04782 -2.636536,0.05459 -1.26416,0.0044 -2.4698005,-0.0024 -2.6792012,-0.01507 z" id="path6" /> </svg>',
16080
+ 'mouse-pointer': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mouse-pointer2-icon lucide-mouse-pointer-2" version="1.1" id="svg1"> <defs id="defs1" /> <path d="M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z" id="path1" /> <path style="fill:#ffffff;stroke-width:0.0972651;stroke-linejoin:round;paint-order:stroke fill markers" d="M 7.8509196,12.314844 C 6.127071,8.1920568 4.7235325,4.8125504 4.7319449,4.8048299 c 0.01847,-0.016951 15.4787861,6.0801241 15.4610371,6.0973581 -0.0069,0.0067 -1.32633,0.341226 -2.93219,0.743509 -1.858829,0.465652 -3.011462,0.772855 -3.172161,0.845453 -0.688426,0.310999 -1.245788,0.879167 -1.498145,1.527185 -0.06251,0.160512 -0.440389,1.529576 -0.839733,3.042364 -0.399346,1.512788 -0.734966,2.750433 -0.745822,2.750322 -0.01086,-1.09e-4 -1.430163,-3.373391 -3.1540114,-7.496177 z" id="path2" /> <path style="fill:#ffffff;stroke-width:0.0705053;stroke-linejoin:round;paint-order:stroke fill markers" d="M 10.777831,19.240865 C 9.3899089,15.977493 4.7567738,4.8648478 4.7760705,4.8455511 c 0.013084,-0.013084 4.7618079,1.8507077 10.7773965,4.2299406 4.467495,1.7669453 4.558837,1.8037523 4.533848,1.8269883 -0.01183,0.011 -0.96064,0.257045 -2.108467,0.546768 -3.445281,0.869623 -3.777157,0.960825 -4.075205,1.119907 -0.597122,0.31871 -1.103666,0.864485 -1.309236,1.410635 -0.09642,0.256167 -0.312339,1.032806 -0.943226,3.392713 -0.641066,2.39798 -0.629171,2.354884 -0.649929,2.354849 -0.0091,-1.5e-5 -0.10963,-0.218934 -0.223421,-0.486487 z" id="path6" /> </svg>',
16081
+ 'pointer': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pointer-icon lucide-pointer" version="1.1" id="svg5"> <defs id="defs5" /> <path d="M22 14a8 8 0 0 1-8 8" id="path1" /> <path d="M18 11v-1a2 2 0 0 0-2-2a2 2 0 0 0-2 2" id="path2" /> <path d="M14 10V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v1" id="path3" /> <path d="M10 9.5V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v10" id="path4" /> <path d="M18 11a2 2 0 1 1 4 0v3a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15" id="path5" /> <path style="fill:#ffffff;stroke-width:0.0930233;stroke-linejoin:round;paint-order:stroke fill markers" d="M 11.2,21.203517 C 9.5092536,21.041493 8.4838422,20.667261 7.3116279,19.784423 7.1888372,19.691945 6.139155,18.670017 4.9790008,17.513473 2.5649891,15.106974 2.6562686,15.220427 2.6869266,14.664621 c 0.020277,-0.367608 0.1272548,-0.601802 0.3822766,-0.836873 0.2609241,-0.240511 0.4631523,-0.319555 0.8191689,-0.320185 0.509345,-9.02e-4 0.5815472,0.04845 1.7102825,1.168976 0.532833,0.528958 1.0223742,0.98871 1.0878697,1.021671 0.1699959,0.08555 0.4741793,0.07658 0.6733897,-0.01985 0.303161,-0.146758 0.4720469,-0.543298 0.3791811,-0.890307 -0.036373,-0.135913 -0.1250717,-0.246717 -0.506083,-0.632208 l -0.4619807,-0.467411 0.00983,-5.002356 0.00983,-5.0023561 0.080405,-0.1739784 C 6.9849846,3.2633288 7.2288893,3.0140593 7.4738043,2.8937849 7.6531903,2.8056908 7.7297209,2.7906977 8,2.7906977 c 0.2702791,0 0.3468097,0.014993 0.5261957,0.1030872 0.244915,0.1202744 0.4888197,0.3695439 0.6027014,0.6159576 0.079998,0.1730976 0.080499,0.1905559 0.09901,3.448397 0.020472,3.6031325 0.00418,3.3665775 0.2478407,3.5980265 0.2810043,0.266918 0.6822132,0.283308 1.0083162,0.04119 0.232339,-0.172501 0.25913,-0.280173 0.275933,-1.1089849 0.01456,-0.7180841 0.01869,-0.7520168 0.117805,-0.9674419 0.454637,-0.9881395 1.789759,-0.9881395 2.244396,0 0.09912,0.2154251 0.103248,0.2493578 0.117805,0.9674419 0.0168,0.8288119 0.04359,0.9364839 0.275933,1.1089849 0.326103,0.242117 0.727312,0.225727 1.008316,-0.04119 0.160426,-0.152384 0.218977,-0.294978 0.250819,-0.610832 C 14.845142,9.2502593 15.336916,8.7840275 16,8.7840275 c 0.478554,0 0.847992,0.2240614 1.082489,0.6565225 l 0.126813,0.2338686 0.01869,0.7813954 c 0.02069,0.865066 0.03258,0.913549 0.276595,1.127795 0.239244,0.210059 0.56665,0.239738 0.88018,0.07979 0.219744,-0.112104 0.324993,-0.301479 0.383515,-0.690054 0.06437,-0.427406 0.125794,-0.56694 0.357358,-0.811785 C 19.38604,9.8862229 19.614832,9.7882106 20,9.7869898 c 0.380153,-0.0012 0.636641,0.1145641 0.905451,0.4086842 0.318537,0.348531 0.317259,0.340596 0.332734,2.064791 0.02055,2.290001 -0.04576,2.983129 -0.383655,4.009983 -0.878739,2.670467 -3.284504,4.613238 -6.090273,4.918188 -0.322415,0.03504 -3.227312,0.04717 -3.564257,0.01488 z" id="path6" /> <path style="fill:#ffffff;stroke-width:0.0705053;stroke-linejoin:round;paint-order:stroke fill markers" d="m 11.942854,21.190957 c -1.015003,-0.01704 -1.131723,-0.02688 -1.806535,-0.1522 C 9.5352183,20.927122 8.8225664,20.681951 8.319624,20.413766 7.7570149,20.113766 7.3758537,19.827607 6.7836127,19.260598 5.774345,18.294328 3.1746828,15.68636 2.994305,15.459184 2.7288345,15.124838 2.6970674,15.049109 2.7026798,14.763988 c 0.00827,-0.420163 0.1162058,-0.679868 0.3844475,-0.925026 0.2382303,-0.217729 0.416008,-0.293073 0.7303102,-0.309513 0.5101451,-0.02668 0.6329728,0.04799 1.5378542,0.934977 0.8479274,0.831158 1.2429087,1.199656 1.342282,1.252283 0.11819,0.06259 0.4442817,0.06081 0.6122724,-0.0033 0.1482348,-0.05661 0.3410696,-0.248342 0.4107195,-0.408368 0.063024,-0.144802 0.06179,-0.498 -0.00216,-0.618376 C 7.6917315,14.63641 7.4736023,14.388937 7.2336742,14.136678 L 6.797441,13.678026 6.7970753,8.6903755 6.7967098,3.7027253 6.9021002,3.502167 C 7.0320375,3.2548956 7.2343175,3.0487286 7.4743519,2.9189189 c 0.1797064,-0.097185 0.1877434,-0.098707 0.5209477,-0.098707 0.3169743,0 0.349151,0.00524 0.5076381,0.082665 0.2190605,0.1070166 0.4871239,0.3748528 0.5931342,0.592632 l 0.081613,0.1676584 0.019992,1.3834579 c 0.010995,0.7609018 0.021403,2.0560783 0.023128,2.87817 0.00341,1.6233162 0.028863,2.2876938 0.093598,2.4426258 0.089047,0.213119 0.4255042,0.419889 0.683247,0.419889 0.1563141,0 0.3731941,-0.08265 0.5187231,-0.197689 0.205546,-0.162474 0.225694,-0.250142 0.256177,-1.1146856 C 10.79418,8.8613623 10.8052,8.7492253 10.85668,8.6186115 11.053264,8.1198873 11.50183,7.7978848 12,7.7978848 c 0.49817,0 0.946736,0.3220025 1.143315,0.8207259 0.05148,0.1306138 0.0625,0.2427508 0.08413,0.8563239 0.02897,0.8216044 0.05497,0.9507864 0.218548,1.0859664 0.341252,0.282005 0.756809,0.293493 1.057036,0.02922 0.169206,-0.148941 0.234219,-0.2921 0.288276,-0.6347773 0.103115,-0.6536616 0.472737,-1.0621772 1.030077,-1.1384664 0.547738,-0.074975 0.986339,0.1608863 1.276145,0.6862578 l 0.105758,0.1917223 7.68e-4,0.3699606 c 0.0011,0.553623 0.04553,1.179579 0.09141,1.289176 0.04428,0.105777 0.219161,0.276182 0.364423,0.355092 0.06913,0.03755 0.155512,0.05067 0.333059,0.05055 0.204058,-1.3e-4 0.260695,-0.01118 0.380729,-0.07426 0.239564,-0.12591 0.320531,-0.265583 0.407786,-0.703455 0.08382,-0.420648 0.125337,-0.527172 0.279883,-0.718174 0.321102,-0.3968443 0.77117,-0.5469022 1.259123,-0.4198065 0.21344,0.055594 0.39756,0.1739295 0.578279,0.3716645 0.286251,0.313205 0.299377,0.389392 0.324444,1.883099 0.02888,1.721047 -0.03379,2.808669 -0.20314,3.525265 -0.307591,1.301561 -0.970113,2.493177 -1.91312,3.440943 -1.060413,1.065767 -2.34115,1.742909 -3.849589,2.035329 -0.348478,0.06755 -0.462014,0.07524 -1.340342,0.09078 -0.527787,0.0093 -1.416154,0.0093 -1.974148,-6.4e-5 z" id="path7" /> </svg>'
16082
+ });
16083
+
16084
+ class KritzelCursorHelper {
16085
+ static _pointerCursor = null;
16086
+ /**
16087
+ * Returns the custom pointer cursor CSS value.
16088
+ * This can be used instead of `cursor: pointer` for consistent styling.
16089
+ */
16090
+ static getPointerCursor() {
16091
+ if (!this._pointerCursor) {
16092
+ this._pointerCursor = this.getCursor({ iconName: 'pointer' });
16093
+ }
16094
+ return this._pointerCursor;
16095
+ }
16096
+ /**
16097
+ * Returns a custom cursor CSS value with support for rotation.
16098
+ * The icon is retrieved from the KritzelIconRegistry.
16099
+ */
16100
+ static getCursor(options) {
16101
+ const iconName = options.iconName === 'default' ? 'mouse-pointer' : options.iconName;
16102
+ const iconSvg = KritzelIconRegistry.get(iconName);
16103
+ if (!iconSvg) {
16104
+ console.warn(`Icon "${iconName}" not found in registry.`);
16105
+ return 'auto';
16106
+ }
16107
+ const size = options.size || 24;
16108
+ const rotation = options.rotation || 0;
16109
+ const color = options.color || 'black';
16110
+ // Default cursor (mouse-pointer) has hotspot at (4, 4), others at center
16111
+ const hotspot = options.hotspot || (options.iconName === 'default' ? { x: 4, y: 4 } : { x: size / 2, y: size / 2 });
16112
+ // Modify the SVG string to set size and color
16113
+ // We replace width and height to match the requested size
16114
+ // We replace currentColor with the requested color
16115
+ let content = iconSvg
16116
+ .replace(/width="\d+"/, `width="${size}"`)
16117
+ .replace(/height="\d+"/, `height="${size}"`)
16118
+ .replace(/currentColor/g, color);
16119
+ // Create the SVG string
16120
+ // We rotate around the center of the SVG canvas
16121
+ const center = size / 2;
16122
+ // We use a group to apply the rotation
16123
+ const svg = `
16124
+ <svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
16125
+ <g transform="rotate(${rotation} ${center} ${center})">
16126
+ ${content}
16127
+ </g>
16128
+ </svg>
16129
+ `;
16130
+ // Encode the SVG for use in a data URI
16131
+ // We need to be careful with encoding to ensure it works across browsers
16132
+ const encodedSvg = encodeURIComponent(svg.replace(/\s+/g, ' ').trim())
16133
+ .replace(/'/g, '%27')
16134
+ .replace(/"/g, '%22');
16135
+ const dataUri = `data:image/svg+xml;charset=utf-8,${encodedSvg}`;
16136
+ // Return the cursor style string
16137
+ return `url('${dataUri}') ${hotspot.x} ${hotspot.y}, auto`;
16138
+ }
16139
+ }
16140
+
16141
+ class KritzelBaseHandler {
16142
+ _core;
16143
+ constructor(core) {
16144
+ this._core = core;
16145
+ }
16146
+ }
16147
+
16148
+ class KritzelMoveHandler extends KritzelBaseHandler {
16149
+ dragStartX;
16150
+ dragStartY;
16151
+ startX;
16152
+ startY;
16153
+ endX;
16154
+ endY;
16155
+ hasMoved = false;
16156
+ trackedPointerId = null;
16157
+ constructor(core) {
16158
+ super(core);
16159
+ }
16160
+ reset() {
16161
+ this.dragStartX = 0;
16162
+ this.dragStartY = 0;
16163
+ this.startX = 0;
16164
+ this.startY = 0;
16165
+ this.endX = 0;
16166
+ this.endY = 0;
16167
+ this.hasMoved = false;
16168
+ this.trackedPointerId = null;
16169
+ }
16170
+ cancelPendingDrag() {
16171
+ this._core.store.state.isDragging = false;
16172
+ this.reset();
16173
+ }
16174
+ handlePointerDown(event) {
16175
+ if (event.pointerType === 'mouse') {
16176
+ if (KritzelEventHelper.isLeftClick(event)) {
15349
16177
  const selectionGroup = this._core.store.selectionGroup;
15350
- if (selectionGroup && this._core.store.state.isRotationHandleSelected) {
15351
- const clientX = Math.round(firstTouch.clientX - this._core.store.offsetX);
15352
- const clientY = Math.round(firstTouch.clientY - this._core.store.offsetY);
15353
- this._core.store.state.isRotating = true;
15354
- const centerX = selectionGroup.translateX + selectionGroup.width / 2 / this._core.store.state.scale;
15355
- const centerY = selectionGroup.translateY + selectionGroup.height / 2 / this._core.store.state.scale;
15356
- const cursorX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
15357
- const cursorY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
15358
- this.initialSelectionGroupRotation = selectionGroup.rotation;
15359
- this.initialRotation = Math.atan2(centerY - cursorY, centerX - cursorX) - selectionGroup.rotation;
15360
- this.unchangedObjects = selectionGroup.objects.map(obj => obj.clone());
15361
- clearTimeout(this._core.store.state.longTouchTimeout);
16178
+ if (selectionGroup?.isSelected && !this._core.store.state.isResizeHandleSelected && !this._core.store.state.isRotationHandleSelected && !this._core.store.state.isLineHandleSelected) {
16179
+ const clientX = event.clientX - this._core.store.offsetX;
16180
+ const clientY = event.clientY - this._core.store.offsetY;
16181
+ this._core.store.state.isDragging = true;
16182
+ this.dragStartX = clientX;
16183
+ this.dragStartY = clientY;
16184
+ this.startX = this.dragStartX;
16185
+ this.startY = this.dragStartY;
16186
+ this.trackedPointerId = event.pointerId;
16187
+ }
16188
+ else {
16189
+ this.trackedPointerId = null;
16190
+ }
16191
+ }
16192
+ else {
16193
+ this.trackedPointerId = null;
16194
+ }
16195
+ }
16196
+ if (event.pointerType === 'touch') {
16197
+ const activePointers = Array.from(this._core.store.state.pointers.values());
16198
+ if (this._core.store.state.pointers.size === 1) {
16199
+ if (this._core.store.state.isScaling) {
16200
+ this.trackedPointerId = null;
16201
+ return;
16202
+ }
16203
+ const selectionGroup = this._core.store.selectionGroup;
16204
+ if (selectionGroup?.isSelected && !this._core.store.state.isResizeHandleSelected && !this._core.store.state.isRotationHandleSelected && !this._core.store.state.isLineHandleSelected) {
16205
+ const x = Math.round(activePointers[0].clientX - this._core.store.offsetX);
16206
+ const y = Math.round(activePointers[0].clientY - this._core.store.offsetY);
16207
+ this.dragStartX = x;
16208
+ this.dragStartY = y;
16209
+ this.startX = x;
16210
+ this.startY = y;
16211
+ this.trackedPointerId = event.pointerId;
16212
+ }
16213
+ else {
16214
+ this.trackedPointerId = null;
15362
16215
  }
15363
16216
  }
15364
16217
  }
15365
16218
  }
15366
16219
  handlePointerMove(event) {
16220
+ if (this.trackedPointerId === null || this.trackedPointerId !== event.pointerId) {
16221
+ return;
16222
+ }
15367
16223
  if (event.pointerType === 'mouse') {
15368
16224
  const selectionGroup = this._core.store.selectionGroup;
15369
- if (this._core.store.state.isRotating && selectionGroup) {
16225
+ if (this._core.store.state.isDragging && selectionGroup) {
15370
16226
  const clientX = event.clientX - this._core.store.offsetX;
15371
16227
  const clientY = event.clientY - this._core.store.offsetY;
15372
- const groupCenterX = selectionGroup.translateX + selectionGroup.width / 2 / this._core.store.state.scale;
15373
- const groupCenterY = selectionGroup.translateY + selectionGroup.height / 2 / this._core.store.state.scale;
15374
- const cursorX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
15375
- const cursorY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
15376
- const currentRotation = Math.atan2(groupCenterY - cursorY, groupCenterX - cursorX);
15377
- this.rotation = currentRotation - this.initialRotation;
15378
- selectionGroup.rotate(this.rotation);
16228
+ this.endX = clientX;
16229
+ this.endY = clientY;
16230
+ const moveDeltaX = Math.abs(clientX - this.startX);
16231
+ const moveDeltaY = Math.abs(clientY - this.startY);
16232
+ const moveThreshold = 5;
16233
+ if (moveDeltaX > moveThreshold || moveDeltaY > moveThreshold) {
16234
+ selectionGroup.move(clientX, clientY, this.dragStartX, this.dragStartY);
16235
+ this.dragStartX = clientX;
16236
+ this.dragStartY = clientY;
16237
+ this.hasMoved = true;
16238
+ }
15379
16239
  }
15380
16240
  }
15381
16241
  if (event.pointerType === 'touch') {
15382
16242
  const activePointers = Array.from(this._core.store.state.pointers.values());
15383
- const firstTouch = activePointers[0];
15384
- if (!firstTouch) {
15385
- return;
15386
- }
15387
16243
  const selectionGroup = this._core.store.selectionGroup;
15388
- if (this._core.store.state.isRotating && selectionGroup) {
15389
- const clientX = Math.round(firstTouch.clientX - this._core.store.offsetX);
15390
- const clientY = Math.round(firstTouch.clientY - this._core.store.offsetY);
15391
- const groupCenterX = selectionGroup.translateX + selectionGroup.width / 2 / this._core.store.state.scale;
15392
- const groupCenterY = selectionGroup.translateY + selectionGroup.height / 2 / this._core.store.state.scale;
15393
- const cursorX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
15394
- const cursorY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
15395
- const currentRotation = Math.atan2(groupCenterY - cursorY, groupCenterX - cursorX);
15396
- this.rotation = currentRotation - this.initialRotation;
15397
- selectionGroup.rotate(this.rotation);
15398
- clearTimeout(this._core.store.state.longTouchTimeout);
16244
+ if (this._core.store.state.pointers.size === 1 &&
16245
+ selectionGroup &&
16246
+ !this._core.store.state.isResizeHandleSelected &&
16247
+ !this._core.store.state.isRotationHandleSelected &&
16248
+ !this._core.store.state.isScaling) {
16249
+ const x = Math.round(activePointers[0].clientX - this._core.store.offsetX);
16250
+ const y = Math.round(activePointers[0].clientY - this._core.store.offsetY);
16251
+ this._core.store.state.isDragging = true;
16252
+ this.endX = x;
16253
+ this.endY = y;
16254
+ const moveDeltaX = Math.abs(x - this.startX);
16255
+ const moveDeltaY = Math.abs(y - this.startY);
16256
+ const moveThreshold = 5;
16257
+ if (moveDeltaX > moveThreshold || moveDeltaY > moveThreshold) {
16258
+ clearTimeout(this._core.store.state.longTouchTimeout);
16259
+ selectionGroup.move(x, y, this.dragStartX, this.dragStartY);
16260
+ this.dragStartX = x;
16261
+ this.dragStartY = y;
16262
+ this.hasMoved = true;
16263
+ }
15399
16264
  }
15400
16265
  }
15401
16266
  }
15402
16267
  handlePointerUp(event) {
16268
+ if (this.trackedPointerId === null || this.trackedPointerId !== event.pointerId) {
16269
+ return;
16270
+ }
15403
16271
  if (event.pointerType === 'mouse') {
15404
- if (this._core.store.state.isRotating) {
15405
- this._core.store.selectionGroup.update();
15406
- this._core.engine.emitObjectsChange();
15407
- this._core.store.state.isRotating = false;
15408
- this._core.store.state.hasObjectsChanged = true;
15409
- this.reset();
16272
+ if (this._core.store.state.isDragging) {
16273
+ this._core.store.state.isDragging = false;
16274
+ if (this.hasMoved) {
16275
+ this._core.store.selectionGroup.update();
16276
+ this._core.engine.emitObjectsChange();
16277
+ this._core.store.state.hasObjectsChanged = true;
16278
+ }
15410
16279
  }
15411
16280
  }
15412
16281
  if (event.pointerType === 'touch') {
15413
- if (this._core.store.state.isRotating) {
15414
- this._core.store.selectionGroup.update();
15415
- this._core.engine.emitObjectsChange();
15416
- this._core.store.state.isRotating = false;
15417
- this._core.store.state.hasObjectsChanged = true;
15418
- this.reset();
15419
- clearTimeout(this._core.store.state.longTouchTimeout);
16282
+ if (this._core.store.state.isDragging) {
16283
+ this._core.store.state.isDragging = false;
16284
+ if (this.hasMoved) {
16285
+ this._core.store.selectionGroup.update();
16286
+ this._core.engine.emitObjectsChange();
16287
+ this._core.store.state.hasObjectsChanged = true;
16288
+ }
15420
16289
  }
15421
16290
  }
16291
+ this.reset();
15422
16292
  }
15423
16293
  }
15424
16294
 
15425
- class KritzelSelectionBox extends KritzelBaseObject {
15426
- __class__ = 'KritzelSelectionBox';
15427
- static create(core) {
15428
- const object = new KritzelSelectionBox();
15429
- object._core = core;
15430
- object.id = object.generateId();
15431
- object.workspaceId = core.store.state.activeWorkspace.id;
15432
- object.scale = core.store.state.scale;
15433
- object.zIndex = 99999;
15434
- object.backgroundColor = 'var(--kritzel-selection-box-background-color, rgba(0, 122, 255, 0.2))';
15435
- object.borderColor = 'var(--kritzel-selection-box-border-color, rgba(0, 122, 255, 0.5))';
15436
- object.borderWidth = 2;
15437
- object.height = 0;
15438
- object.width = 0;
15439
- return object;
15440
- }
15441
- }
16295
+ var KritzelHandleType;
16296
+ (function (KritzelHandleType) {
16297
+ KritzelHandleType["TopLeft"] = "top-left";
16298
+ KritzelHandleType["TopRight"] = "top-right";
16299
+ KritzelHandleType["BottomLeft"] = "bottom-left";
16300
+ KritzelHandleType["BottomRight"] = "bottom-right";
16301
+ })(KritzelHandleType || (KritzelHandleType = {}));
15442
16302
 
15443
- class KritzelSelectionGroup extends KritzelBaseObject {
15444
- __class__ = 'KritzelSelectionGroup';
15445
- // Store only object IDs instead of full objects
15446
- objectIds = [];
15447
- // Store snapshots of object state for transformations (rotation, resize)
15448
- unchangedObjectSnapshots = new Map();
15449
- snapshotRotation = 0;
15450
- minX;
15451
- maxX;
15452
- minY;
15453
- maxY;
15454
- // Getter to retrieve actual objects from the store by their IDs
15455
- get objects() {
15456
- return this.objectIds
15457
- .map(id => {
15458
- const found = this._core.store.state.objects.filter(obj => obj.id === id);
15459
- return found.length > 0 ? found[0] : null;
15460
- })
15461
- .filter(obj => obj !== null);
15462
- }
15463
- get length() {
15464
- return this.objectIds.length;
16303
+ class KritzelResizeHandler extends KritzelBaseHandler {
16304
+ initialMouseX = 0;
16305
+ initialMouseY = 0;
16306
+ initialSize = { x: 0, y: 0, width: 0, height: 0 };
16307
+ newSize = { x: 0, y: 0, width: 0, height: 0 };
16308
+ hasResized = false;
16309
+ constructor(core) {
16310
+ super(core);
15465
16311
  }
15466
- static create(core) {
15467
- const object = new KritzelSelectionGroup();
15468
- object._core = core;
15469
- object.id = object.generateId();
15470
- object.workspaceId = core.store.state.activeWorkspace.id;
15471
- object.scale = core.store.state.scale;
15472
- object.zIndex = 99999;
15473
- return object;
16312
+ reset() {
16313
+ this.initialMouseX = 0;
16314
+ this.initialMouseY = 0;
16315
+ this.initialSize = { x: 0, y: 0, width: 0, height: 0 };
16316
+ this.newSize = { x: 0, y: 0, width: 0, height: 0 };
16317
+ this.hasResized = false;
15474
16318
  }
15475
- addOrRemove(object) {
15476
- const index = this.objectIds.findIndex(id => id === object.id);
15477
- if (index === -1) {
15478
- this.objectIds.push(object.id);
16319
+ handlePointerDown(event) {
16320
+ if (event.pointerType === 'mouse') {
16321
+ if (KritzelEventHelper.isLeftClick(event)) {
16322
+ const selectionGroup = this._core.store.selectionGroup;
16323
+ if (selectionGroup && this._core.store.state.isResizeHandleSelected) {
16324
+ const clientX = event.clientX - this._core.store.offsetX;
16325
+ const clientY = event.clientY - this._core.store.offsetY;
16326
+ this._core.store.state.isResizing = true;
16327
+ this.initialMouseX = clientX;
16328
+ this.initialMouseY = clientY;
16329
+ this.initialSize.width = selectionGroup.width;
16330
+ this.initialSize.height = selectionGroup.height;
16331
+ this.initialSize.x = selectionGroup.translateX;
16332
+ this.initialSize.y = selectionGroup.translateY;
16333
+ }
16334
+ }
15479
16335
  }
15480
- else {
15481
- this.objectIds.splice(index, 1);
16336
+ if (event.pointerType === 'touch') {
16337
+ const activePointers = Array.from(this._core.store.state.pointers.values());
16338
+ const firstTouch = activePointers[0];
16339
+ if (!firstTouch) {
16340
+ return;
16341
+ }
16342
+ if (activePointers.length === 1) {
16343
+ const selectionGroup = this._core.store.selectionGroup;
16344
+ if (selectionGroup && this._core.store.state.isResizeHandleSelected) {
16345
+ const clientX = Math.round(firstTouch.clientX - this._core.store.offsetX);
16346
+ const clientY = Math.round(firstTouch.clientY - this._core.store.offsetY);
16347
+ this._core.store.state.isResizing = true;
16348
+ this.initialMouseX = clientX;
16349
+ this.initialMouseY = clientY;
16350
+ this.initialSize.width = selectionGroup.width;
16351
+ this.initialSize.height = selectionGroup.height;
16352
+ this.initialSize.x = selectionGroup.translateX;
16353
+ this.initialSize.y = selectionGroup.translateY;
16354
+ clearTimeout(this._core.store.state.longTouchTimeout);
16355
+ }
16356
+ }
15482
16357
  }
15483
- this.captureUnchangedSnapshots();
15484
- this.refreshObjectDimensions();
15485
16358
  }
15486
- deselectAllChildren() {
15487
- this.objects.forEach(obj => (obj.isSelected = false));
15488
- }
15489
- updateWorkspaceId(workspaceId) {
15490
- this.workspaceId = workspaceId;
15491
- this.objects.forEach(obj => (obj.workspaceId = workspaceId));
15492
- }
15493
- updateZIndices(startZIndex) {
15494
- this.objects.forEach((obj, i) => {
15495
- obj.zIndex = startZIndex + i;
15496
- });
15497
- }
15498
- updatePosition(x, y) {
15499
- this.objects.forEach(obj => {
15500
- const deltaX = obj.translateX - this.translateX;
15501
- const deltaY = obj.translateY - this.translateY;
15502
- obj.updatePosition(x + deltaX, y + deltaY);
15503
- });
15504
- // Update snapshots
15505
- this.unchangedObjectSnapshots.forEach(snapshot => {
15506
- const deltaX = snapshot.translateX - this.translateX;
15507
- const deltaY = snapshot.translateY - this.translateY;
15508
- snapshot.translateX = x + deltaX;
15509
- snapshot.translateY = x + deltaY;
15510
- });
15511
- this.translateX = x;
15512
- this.translateY = y;
15513
- this._core.store.state.objects.update(this);
15514
- }
15515
- /**
15516
- * Capture snapshots of current object states for undo/redo operations
15517
- */
15518
- captureUnchangedSnapshots() {
15519
- this.unchangedObjectSnapshots.clear();
15520
- this.snapshotRotation = this.rotation;
15521
- this.objects.forEach(obj => {
15522
- this.unchangedObjectSnapshots.set(obj.id, {
15523
- id: obj.id,
15524
- translateX: obj.translateX,
15525
- translateY: obj.translateY,
15526
- rotation: obj.rotation,
15527
- width: obj.width,
15528
- height: obj.height,
15529
- totalWidth: obj.totalWidth,
15530
- totalHeight: obj.totalHeight,
15531
- scale: obj.scale,
15532
- });
15533
- });
15534
- }
15535
- serialize() {
15536
- const { _core, _elementRef, element, totalWidth, totalHeight, unchangedObjectSnapshots, ...remainingProps } = this;
15537
- const clonedProps = structuredClone(remainingProps);
15538
- if (element && typeof element === 'object' && 'nodeType' in element && element.nodeType === 1) {
15539
- clonedProps.element = element.cloneNode(true);
16359
+ handlePointerMove(event) {
16360
+ if (event.pointerType === 'mouse') {
16361
+ const selectionGroup = this._core.store.selectionGroup;
16362
+ if (this._core.store.state.isResizing && selectionGroup) {
16363
+ const clientX = event.clientX - this._core.store.offsetX;
16364
+ const clientY = event.clientY - this._core.store.offsetY;
16365
+ const dx = clientX - this.initialMouseX;
16366
+ const dy = clientY - this.initialMouseY;
16367
+ const resizeDeltaX = Math.abs(dx);
16368
+ const resizeDeltaY = Math.abs(dy);
16369
+ const resizeThreshold = 5;
16370
+ if (resizeDeltaX > resizeThreshold || resizeDeltaY > resizeThreshold) {
16371
+ this.hasResized = true;
16372
+ }
16373
+ if (!this.hasResized) {
16374
+ return;
16375
+ }
16376
+ const rotation = selectionGroup.rotation;
16377
+ const sin = Math.sin(rotation);
16378
+ const cos = Math.cos(rotation);
16379
+ const objectScale = selectionGroup.scale || 1;
16380
+ const currentScale = this._core.store.state.scale;
16381
+ // Calculate delta in local unrotated space
16382
+ // We rotate the screen delta by -rotation to align with the object's axes
16383
+ const localDx = (dx * cos + dy * sin) / currentScale;
16384
+ const localDy = (-dx * sin + dy * cos) / currentScale;
16385
+ // Calculate the center of the selection group before resize
16386
+ const initialCenterX = this.initialSize.x + this.initialSize.width / objectScale / 2;
16387
+ const initialCenterY = this.initialSize.y + this.initialSize.height / objectScale / 2;
16388
+ // The center moves by half of the screen delta (scaled)
16389
+ // This is true regardless of rotation because the resize happens symmetrically around the center
16390
+ // relative to the fixed point logic
16391
+ const newCenterX = initialCenterX + dx / currentScale / 2;
16392
+ const newCenterY = initialCenterY + dy / currentScale / 2;
16393
+ switch (this._core.store.state.resizeHandleType) {
16394
+ case KritzelHandleType.TopLeft:
16395
+ this.newSize.width = this.initialSize.width - localDx * objectScale;
16396
+ this.newSize.height = this.initialSize.height - localDy * objectScale;
16397
+ break;
16398
+ case KritzelHandleType.TopRight:
16399
+ this.newSize.width = this.initialSize.width + localDx * objectScale;
16400
+ this.newSize.height = this.initialSize.height - localDy * objectScale;
16401
+ break;
16402
+ case KritzelHandleType.BottomLeft:
16403
+ this.newSize.width = this.initialSize.width - localDx * objectScale;
16404
+ this.newSize.height = this.initialSize.height + localDy * objectScale;
16405
+ break;
16406
+ case KritzelHandleType.BottomRight:
16407
+ this.newSize.width = this.initialSize.width + localDx * objectScale;
16408
+ this.newSize.height = this.initialSize.height + localDy * objectScale;
16409
+ break;
16410
+ }
16411
+ this.newSize.x = newCenterX - this.newSize.width / objectScale / 2;
16412
+ this.newSize.y = newCenterY - this.newSize.height / objectScale / 2;
16413
+ selectionGroup.resize(this.newSize.x, this.newSize.y, this.newSize.width, this.newSize.height);
16414
+ }
15540
16415
  }
15541
- // Convert Map to plain object for serialization
15542
- clonedProps.unchangedObjectSnapshots = Object.fromEntries(this.unchangedObjectSnapshots);
15543
- return clonedProps;
15544
- }
15545
- deserialize(object) {
15546
- // First, deserialize all base properties using parent's deserialize
15547
- super.deserialize(object);
15548
- // Restore the Map from serialized object
15549
- if (object.unchangedObjectSnapshots) {
15550
- this.unchangedObjectSnapshots = new Map(Object.entries(object.unchangedObjectSnapshots));
16416
+ if (event.pointerType === 'touch') {
16417
+ const activePointers = Array.from(this._core.store.state.pointers.values());
16418
+ const oneFingerTouch = activePointers[0];
16419
+ if (!oneFingerTouch) {
16420
+ return;
16421
+ }
16422
+ const selectionGroup = this._core.store.selectionGroup;
16423
+ if (this._core.store.state.isResizing && selectionGroup) {
16424
+ const clientX = Math.round(oneFingerTouch.clientX - this._core.store.offsetX);
16425
+ const clientY = Math.round(oneFingerTouch.clientY - this._core.store.offsetY);
16426
+ const dx = clientX - this.initialMouseX;
16427
+ const dy = clientY - this.initialMouseY;
16428
+ const resizeDeltaX = Math.abs(dx);
16429
+ const resizeDeltaY = Math.abs(dy);
16430
+ const resizeThreshold = 5;
16431
+ if (resizeDeltaX > resizeThreshold || resizeDeltaY > resizeThreshold) {
16432
+ clearTimeout(this._core.store.state.longTouchTimeout);
16433
+ this.hasResized = true;
16434
+ }
16435
+ if (!this.hasResized) {
16436
+ return;
16437
+ }
16438
+ const rotation = selectionGroup.rotation;
16439
+ const sin = Math.sin(rotation);
16440
+ const cos = Math.cos(rotation);
16441
+ const objectScale = selectionGroup.scale || 1;
16442
+ const currentScale = this._core.store.state.scale;
16443
+ // Calculate delta in local unrotated space
16444
+ // We rotate the screen delta by -rotation to align with the object's axes
16445
+ const localDx = (dx * cos + dy * sin) / currentScale;
16446
+ const localDy = (-dx * sin + dy * cos) / currentScale;
16447
+ // Calculate the center of the selection group before resize
16448
+ const initialCenterX = this.initialSize.x + this.initialSize.width / objectScale / 2;
16449
+ const initialCenterY = this.initialSize.y + this.initialSize.height / objectScale / 2;
16450
+ // The center moves by half of the screen delta (scaled)
16451
+ // This is true regardless of rotation because the resize happens symmetrically around the center
16452
+ // relative to the fixed point logic
16453
+ const newCenterX = initialCenterX + dx / currentScale / 2;
16454
+ const newCenterY = initialCenterY + dy / currentScale / 2;
16455
+ switch (this._core.store.state.resizeHandleType) {
16456
+ case KritzelHandleType.TopLeft:
16457
+ this.newSize.width = this.initialSize.width - localDx * objectScale;
16458
+ this.newSize.height = this.initialSize.height - localDy * objectScale;
16459
+ break;
16460
+ case KritzelHandleType.TopRight:
16461
+ this.newSize.width = this.initialSize.width + localDx * objectScale;
16462
+ this.newSize.height = this.initialSize.height - localDy * objectScale;
16463
+ break;
16464
+ case KritzelHandleType.BottomLeft:
16465
+ this.newSize.width = this.initialSize.width - localDx * objectScale;
16466
+ this.newSize.height = this.initialSize.height + localDy * objectScale;
16467
+ break;
16468
+ case KritzelHandleType.BottomRight:
16469
+ this.newSize.width = this.initialSize.width + localDx * objectScale;
16470
+ this.newSize.height = this.initialSize.height + localDy * objectScale;
16471
+ break;
16472
+ }
16473
+ this.newSize.x = newCenterX - this.newSize.width / objectScale / 2;
16474
+ this.newSize.y = newCenterY - this.newSize.height / objectScale / 2;
16475
+ selectionGroup.resize(this.newSize.x, this.newSize.y, this.newSize.width, this.newSize.height);
16476
+ }
15551
16477
  }
15552
- return this;
15553
- }
15554
- update() {
15555
- // Only update the selection group itself
15556
- // Child objects are already updated during move/resize/rotate operations
15557
- // Updating them again here would create redundant y.js updates
15558
- this._core.store.state.objects.update(this);
15559
16478
  }
15560
- move(startX, startY, endX, endY) {
15561
- const deltaX = (startX - endX) / this._core.store.state.scale;
15562
- const deltaY = (startY - endY) / this._core.store.state.scale;
15563
- this.translateX += deltaX;
15564
- this.translateY += deltaY;
15565
- this._core.store.state.objects.transaction(() => {
15566
- this._core.store.state.objects.update(this);
15567
- this.objects.forEach(obj => {
15568
- obj.move(startX, startY, endX, endY);
15569
- });
15570
- });
15571
- // Update snapshots
15572
- this.unchangedObjectSnapshots.forEach(snapshot => {
15573
- snapshot.translateX += deltaX;
15574
- snapshot.translateY += deltaY;
15575
- });
16479
+ handlePointerUp(event) {
16480
+ if (event.pointerType === 'mouse') {
16481
+ if (this._core.store.state.isResizing) {
16482
+ this._core.store.state.isResizing = false;
16483
+ if (this.hasResized) {
16484
+ this._core.store.selectionGroup.update();
16485
+ this._core.engine.emitObjectsChange();
16486
+ this._core.store.state.hasObjectsChanged = true;
16487
+ }
16488
+ this.reset();
16489
+ }
16490
+ }
16491
+ if (event.pointerType === 'touch') {
16492
+ if (this._core.store.state.isResizing) {
16493
+ this._core.store.state.isResizing = false;
16494
+ if (this.hasResized) {
16495
+ this._core.store.selectionGroup.update();
16496
+ this._core.engine.emitObjectsChange();
16497
+ this._core.store.state.hasObjectsChanged = true;
16498
+ }
16499
+ this.reset();
16500
+ clearTimeout(this._core.store.state.longTouchTimeout);
16501
+ }
16502
+ }
15576
16503
  }
15577
- resize(x, y, width, height) {
15578
- const widthScaleFactor = width / this.width;
15579
- const heightScaleFactor = height / this.height;
15580
- // Calculate old center
15581
- const oldCenterX = this.translateX + this.totalWidth / 2 / this.scale;
15582
- const oldCenterY = this.translateY + this.totalHeight / 2 / this.scale;
15583
- // Calculate new center
15584
- const newTotalWidth = width + this.padding * 2;
15585
- const newTotalHeight = height + this.padding * 2;
15586
- const newCenterX = x + newTotalWidth / 2 / this.scale;
15587
- const newCenterY = y + newTotalHeight / 2 / this.scale;
15588
- const rotation = this.rotation;
15589
- const cos = Math.cos(-rotation);
15590
- const sin = Math.sin(-rotation);
15591
- const cosR = Math.cos(rotation);
15592
- const sinR = Math.sin(rotation);
15593
- this._core.store.state.objects.transaction(() => {
15594
- this.objects.forEach(child => {
15595
- // Calculate child center
15596
- const childCenterX = child.translateX + child.totalWidth / 2 / child.scale;
15597
- const childCenterY = child.translateY + child.totalHeight / 2 / child.scale;
15598
- // Vector from old group center to child center
15599
- const dx = childCenterX - oldCenterX;
15600
- const dy = childCenterY - oldCenterY;
15601
- // Rotate to local space (align with group axes)
15602
- const localX = dx * cos - dy * sin;
15603
- const localY = dx * sin + dy * cos;
15604
- // Scale in local space
15605
- const scaledLocalX = localX * widthScaleFactor;
15606
- const scaledLocalY = localY * heightScaleFactor;
15607
- // Rotate back to world space
15608
- const rotatedX = scaledLocalX * cosR - scaledLocalY * sinR;
15609
- const rotatedY = scaledLocalX * sinR + scaledLocalY * cosR;
15610
- // New child center
15611
- const newChildCenterX = newCenterX + rotatedX;
15612
- const newChildCenterY = newCenterY + rotatedY;
15613
- // New child top-left
15614
- // Calculate relative rotation
15615
- const relativeRotation = child.rotation - rotation;
15616
- const cosRel = Math.cos(relativeRotation);
15617
- const sinRel = Math.sin(relativeRotation);
15618
- // Project the group's scale factors onto the child's local axes
15619
- // We use absolute values because scaling is magnitude-based
15620
- // If the child is aligned (0 deg), cos=1, sin=0 -> scales match
15621
- // If the child is 90 deg, cos=0, sin=1 -> scales swap
15622
- const newChildWidthScale = Math.sqrt(Math.pow(widthScaleFactor * cosRel, 2) + Math.pow(heightScaleFactor * sinRel, 2));
15623
- const newChildHeightScale = Math.sqrt(Math.pow(widthScaleFactor * sinRel, 2) + Math.pow(heightScaleFactor * cosRel, 2));
15624
- const updatedWidth = child.width * newChildWidthScale;
15625
- const updatedHeight = child.height * newChildHeightScale;
15626
- const updatedTotalWidth = updatedWidth + child.padding * 2;
15627
- const updatedTotalHeight = updatedHeight + child.padding * 2;
15628
- const updatedX = newChildCenterX - updatedTotalWidth / 2 / child.scale;
15629
- const updatedY = newChildCenterY - updatedTotalHeight / 2 / child.scale;
15630
- child.resize(updatedX, updatedY, updatedWidth, updatedHeight);
15631
- });
15632
- // Refresh dimensions and update the SelectionGroup to propagate changes to other tabs
15633
- this.refreshObjectDimensions();
15634
- this.captureUnchangedSnapshots();
15635
- this._core.store.state.objects.update(this);
15636
- });
16504
+ }
16505
+
16506
+ class KritzelRotationHandler extends KritzelBaseHandler {
16507
+ initialRotation = 0;
16508
+ rotation = 0;
16509
+ unchangedObjects = [];
16510
+ initialSelectionGroupRotation = 0;
16511
+ constructor(core) {
16512
+ super(core);
15637
16513
  }
15638
- rotate(value) {
15639
- this.rotation = value;
15640
- const centerX = this.translateX + this.totalWidth / 2 / this.scale;
15641
- const centerY = this.translateY + this.totalHeight / 2 / this.scale;
15642
- const angle = value - this.snapshotRotation;
15643
- const cos = Math.cos(angle);
15644
- const sin = Math.sin(angle);
15645
- this._core.store.state.objects.transaction(() => {
15646
- // Update the SelectionGroup itself to propagate rotation to other tabs
15647
- this._core.store.state.objects.update(this);
15648
- this.objects.forEach(child => {
15649
- const unchangedSnapshot = this.unchangedObjectSnapshots.get(child.id);
15650
- if (!unchangedSnapshot)
15651
- return;
15652
- const offsetX = this.getOffsetXToCenterFromSnapshot(unchangedSnapshot);
15653
- const offsetY = this.getOffsetYToCenterFromSnapshot(unchangedSnapshot);
15654
- const rotatedX = cos * offsetX - sin * offsetY;
15655
- const rotatedY = sin * offsetX + cos * offsetY;
15656
- child.translateX = centerX + rotatedX - child.totalWidth / 2 / child.scale;
15657
- child.translateY = centerY + rotatedY - child.totalHeight / 2 / child.scale;
15658
- child.rotate(this.objects.length === 1 ? value : unchangedSnapshot.rotation + angle);
15659
- });
15660
- });
16514
+ reset() {
16515
+ this.initialRotation = 0;
16516
+ this.rotation = 0;
16517
+ this.unchangedObjects = [];
15661
16518
  }
15662
- copy() {
15663
- const selectionGroup = KritzelSelectionGroup.create(this._core);
15664
- this.objects
15665
- .sort((a, b) => a.zIndex - b.zIndex)
15666
- .forEach(obj => {
15667
- const copiedObject = obj.copy();
15668
- selectionGroup.addOrRemove(copiedObject);
15669
- });
15670
- selectionGroup.captureUnchangedSnapshots();
15671
- if (this.objects.length === 1) {
15672
- selectionGroup.rotation = this.objects[0].rotation;
16519
+ handlePointerDown(event) {
16520
+ if (event.pointerType === 'mouse') {
16521
+ if (KritzelEventHelper.isLeftClick(event)) {
16522
+ const selectionGroup = this._core.store.selectionGroup;
16523
+ if (selectionGroup && this._core.store.state.isRotationHandleSelected) {
16524
+ const clientX = event.clientX - this._core.store.offsetX;
16525
+ const clientY = event.clientY - this._core.store.offsetY;
16526
+ this._core.store.state.isRotating = true;
16527
+ const objectScale = selectionGroup.scale || 1;
16528
+ const centerX = selectionGroup.translateX + selectionGroup.width / 2 / objectScale;
16529
+ const centerY = selectionGroup.translateY + selectionGroup.height / 2 / objectScale;
16530
+ const cursorX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
16531
+ const cursorY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
16532
+ this.initialSelectionGroupRotation = selectionGroup.rotation;
16533
+ this.initialRotation = Math.atan2(centerY - cursorY, centerX - cursorX) - selectionGroup.rotation;
16534
+ this.unchangedObjects = selectionGroup.objects.map(obj => obj.clone());
16535
+ }
16536
+ }
16537
+ }
16538
+ if (event.pointerType === 'touch') {
16539
+ const activePointers = Array.from(this._core.store.state.pointers.values());
16540
+ const firstTouch = activePointers[0];
16541
+ if (!firstTouch) {
16542
+ return;
16543
+ }
16544
+ if (activePointers.length === 1) {
16545
+ const selectionGroup = this._core.store.selectionGroup;
16546
+ if (selectionGroup && this._core.store.state.isRotationHandleSelected) {
16547
+ const clientX = Math.round(firstTouch.clientX - this._core.store.offsetX);
16548
+ const clientY = Math.round(firstTouch.clientY - this._core.store.offsetY);
16549
+ this._core.store.state.isRotating = true;
16550
+ const objectScale = selectionGroup.scale || 1;
16551
+ const centerX = selectionGroup.translateX + selectionGroup.width / 2 / objectScale;
16552
+ const centerY = selectionGroup.translateY + selectionGroup.height / 2 / objectScale;
16553
+ const cursorX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
16554
+ const cursorY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
16555
+ this.initialSelectionGroupRotation = selectionGroup.rotation;
16556
+ this.initialRotation = Math.atan2(centerY - cursorY, centerX - cursorX) - selectionGroup.rotation;
16557
+ this.unchangedObjects = selectionGroup.objects.map(obj => obj.clone());
16558
+ clearTimeout(this._core.store.state.longTouchTimeout);
16559
+ }
16560
+ }
15673
16561
  }
15674
- return selectionGroup;
15675
16562
  }
15676
- refreshObjectDimensions() {
15677
- if (this.objects.length === 1) {
15678
- const obj = this.objects[0];
15679
- this.minX = obj.boundingBox.x / this.scale;
15680
- this.maxX = obj.boundingBox.x / this.scale + obj.boundingBox.width;
15681
- this.minY = obj.boundingBox.y / this.scale;
15682
- this.maxY = obj.boundingBox.y / this.scale + obj.boundingBox.height;
15683
- this.translateX = (this.minX - this.padding) * this.scale;
15684
- this.translateY = (this.minY - this.padding) * this.scale;
15685
- this.width = (this.maxX - this.minX - this.padding) * this.scale;
15686
- this.height = (this.maxY - this.minY - this.padding) * this.scale;
16563
+ handlePointerMove(event) {
16564
+ if (event.pointerType === 'mouse') {
16565
+ const selectionGroup = this._core.store.selectionGroup;
16566
+ if (this._core.store.state.isRotating && selectionGroup) {
16567
+ const clientX = event.clientX - this._core.store.offsetX;
16568
+ const clientY = event.clientY - this._core.store.offsetY;
16569
+ const objectScale = selectionGroup.scale || 1;
16570
+ const groupCenterX = selectionGroup.translateX + selectionGroup.width / 2 / objectScale;
16571
+ const groupCenterY = selectionGroup.translateY + selectionGroup.height / 2 / objectScale;
16572
+ const cursorX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
16573
+ const cursorY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
16574
+ const currentRotation = Math.atan2(groupCenterY - cursorY, groupCenterX - cursorX);
16575
+ this.rotation = currentRotation - this.initialRotation;
16576
+ selectionGroup.rotate(this.rotation);
16577
+ }
15687
16578
  }
15688
- else {
15689
- const rotation = this.rotation;
15690
- const cos = Math.cos(-rotation);
15691
- const sin = Math.sin(-rotation);
15692
- let minX = Infinity;
15693
- let maxX = -Infinity;
15694
- let minY = Infinity;
15695
- let maxY = -Infinity;
15696
- this.objects.forEach(obj => {
15697
- const polygon = obj.rotatedPolygon;
15698
- const corners = [polygon.topLeft, polygon.topRight, polygon.bottomRight, polygon.bottomLeft];
15699
- corners.forEach(corner => {
15700
- // Rotate corner into local space (aligned with group rotation)
15701
- const rx = corner.x * cos - corner.y * sin;
15702
- const ry = corner.x * sin + corner.y * cos;
15703
- if (rx < minX)
15704
- minX = rx;
15705
- if (rx > maxX)
15706
- maxX = rx;
15707
- if (ry < minY)
15708
- minY = ry;
15709
- if (ry > maxY)
15710
- maxY = ry;
15711
- });
15712
- });
15713
- // Dimensions in world units (unrotated)
15714
- const worldWidth = maxX - minX;
15715
- const worldHeight = maxY - minY;
15716
- this.width = (worldWidth - this.padding) * this.scale;
15717
- this.height = (worldHeight - this.padding) * this.scale;
15718
- // Center of the box in rotated space
15719
- const cRx = (minX + maxX) / 2;
15720
- const cRy = (minY + maxY) / 2;
15721
- // Rotate center back to world space
15722
- const cosR = Math.cos(rotation);
15723
- const sinR = Math.sin(rotation);
15724
- const cx = cRx * cosR - cRy * sinR;
15725
- const cy = cRx * sinR + cRy * cosR;
15726
- this.translateX = cx - (this.width / this.scale + 2 * this.padding) / 2;
15727
- this.translateY = cy - (this.height / this.scale + 2 * this.padding) / 2;
16579
+ if (event.pointerType === 'touch') {
16580
+ const activePointers = Array.from(this._core.store.state.pointers.values());
16581
+ const firstTouch = activePointers[0];
16582
+ if (!firstTouch) {
16583
+ return;
16584
+ }
16585
+ const selectionGroup = this._core.store.selectionGroup;
16586
+ if (this._core.store.state.isRotating && selectionGroup) {
16587
+ const clientX = Math.round(firstTouch.clientX - this._core.store.offsetX);
16588
+ const clientY = Math.round(firstTouch.clientY - this._core.store.offsetY);
16589
+ const objectScale = selectionGroup.scale || 1;
16590
+ const groupCenterX = selectionGroup.translateX + selectionGroup.width / 2 / objectScale;
16591
+ const groupCenterY = selectionGroup.translateY + selectionGroup.height / 2 / objectScale;
16592
+ const cursorX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
16593
+ const cursorY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
16594
+ const currentRotation = Math.atan2(groupCenterY - cursorY, groupCenterX - cursorX);
16595
+ this.rotation = currentRotation - this.initialRotation;
16596
+ selectionGroup.rotate(this.rotation);
16597
+ clearTimeout(this._core.store.state.longTouchTimeout);
16598
+ }
15728
16599
  }
15729
- this._core.store.state.objects.update(this);
15730
16600
  }
15731
- getOffsetXToCenterFromSnapshot(snapshot) {
15732
- const objCenterX = snapshot.translateX + snapshot.totalWidth / snapshot.scale / 2;
15733
- const groupCenterX = this.translateX + this.totalWidth / this.scale / 2;
15734
- return objCenterX - groupCenterX;
16601
+ handlePointerUp(event) {
16602
+ if (event.pointerType === 'mouse') {
16603
+ if (this._core.store.state.isRotating) {
16604
+ this._core.store.selectionGroup.update();
16605
+ this._core.engine.emitObjectsChange();
16606
+ this._core.store.state.isRotating = false;
16607
+ this._core.store.state.hasObjectsChanged = true;
16608
+ this.reset();
16609
+ }
16610
+ }
16611
+ if (event.pointerType === 'touch') {
16612
+ if (this._core.store.state.isRotating) {
16613
+ this._core.store.selectionGroup.update();
16614
+ this._core.engine.emitObjectsChange();
16615
+ this._core.store.state.isRotating = false;
16616
+ this._core.store.state.hasObjectsChanged = true;
16617
+ this.reset();
16618
+ clearTimeout(this._core.store.state.longTouchTimeout);
16619
+ }
16620
+ }
15735
16621
  }
15736
- getOffsetYToCenterFromSnapshot(snapshot) {
15737
- const objCenterY = snapshot.translateY + snapshot.totalHeight / snapshot.scale / 2;
15738
- const groupCenterY = this.translateY + this.totalHeight / this.scale / 2;
15739
- return objCenterY - groupCenterY;
16622
+ }
16623
+
16624
+ class KritzelSelectionBox extends KritzelBaseObject {
16625
+ __class__ = 'KritzelSelectionBox';
16626
+ static create(core) {
16627
+ const object = new KritzelSelectionBox();
16628
+ object._core = core;
16629
+ object.id = object.generateId();
16630
+ object.workspaceId = core.store.state.activeWorkspace.id;
16631
+ object.scale = core.store.state.scale;
16632
+ object.zIndex = 99999;
16633
+ object.backgroundColor = 'var(--kritzel-selection-box-background-color, rgba(0, 122, 255, 0.2))';
16634
+ object.borderColor = 'var(--kritzel-selection-box-border-color, rgba(0, 122, 255, 0.5))';
16635
+ object.borderWidth = 2;
16636
+ object.height = 0;
16637
+ object.width = 0;
16638
+ return object;
15740
16639
  }
15741
16640
  }
15742
16641
 
@@ -15998,12 +16897,393 @@ class KritzelHoverHandler extends KritzelBaseHandler {
15998
16897
  }
15999
16898
  }
16000
16899
 
16900
+ class KritzelLineHandleHandler extends KritzelBaseHandler {
16901
+ initialMouseX = 0;
16902
+ initialMouseY = 0;
16903
+ initialStartX = 0;
16904
+ initialStartY = 0;
16905
+ initialEndX = 0;
16906
+ initialEndY = 0;
16907
+ initialControlX;
16908
+ initialControlY;
16909
+ initialTranslateX = 0;
16910
+ initialTranslateY = 0;
16911
+ hasMoved = false;
16912
+ /** Current snap target during drag, if any */
16913
+ currentSnapTarget = null;
16914
+ constructor(core) {
16915
+ super(core);
16916
+ }
16917
+ reset() {
16918
+ this.initialMouseX = 0;
16919
+ this.initialMouseY = 0;
16920
+ this.initialStartX = 0;
16921
+ this.initialStartY = 0;
16922
+ this.initialEndX = 0;
16923
+ this.initialEndY = 0;
16924
+ this.initialControlX = undefined;
16925
+ this.initialControlY = undefined;
16926
+ this.initialTranslateX = 0;
16927
+ this.initialTranslateY = 0;
16928
+ this.hasMoved = false;
16929
+ this.currentSnapTarget = null;
16930
+ this._core.anchorManager.clearSnapCandidate();
16931
+ }
16932
+ handlePointerDown(event) {
16933
+ if (event.pointerType === 'mouse') {
16934
+ if (KritzelEventHelper.isLeftClick(event)) {
16935
+ this.startHandleDrag(event);
16936
+ }
16937
+ }
16938
+ if (event.pointerType === 'touch') {
16939
+ const activePointers = Array.from(this._core.store.state.pointers.values());
16940
+ if (activePointers.length === 1) {
16941
+ this.startHandleDrag(event);
16942
+ }
16943
+ }
16944
+ }
16945
+ startHandleDrag(event) {
16946
+ const line = this.getSelectedLine();
16947
+ if (!line || !this._core.store.state.isLineHandleSelected) {
16948
+ return;
16949
+ }
16950
+ const clientX = event.clientX - this._core.store.offsetX;
16951
+ const clientY = event.clientY - this._core.store.offsetY;
16952
+ this._core.store.state.isLineHandleDragging = true;
16953
+ this.initialMouseX = clientX;
16954
+ this.initialMouseY = clientY;
16955
+ // Store initial line state
16956
+ this.initialStartX = line.startX;
16957
+ this.initialStartY = line.startY;
16958
+ this.initialEndX = line.endX;
16959
+ this.initialEndY = line.endY;
16960
+ this.initialControlX = line.controlX;
16961
+ this.initialControlY = line.controlY;
16962
+ this.initialTranslateX = line.translateX;
16963
+ this.initialTranslateY = line.translateY;
16964
+ // Remove existing anchor for this endpoint when starting to drag
16965
+ const handleType = this._core.store.state.lineHandleType;
16966
+ if (handleType === 'start' || handleType === 'end') {
16967
+ this._core.anchorManager.removeAnchor(line.id, handleType);
16968
+ }
16969
+ clearTimeout(this._core.store.state.longTouchTimeout);
16970
+ }
16971
+ handlePointerMove(event) {
16972
+ const line = this.getSelectedLine();
16973
+ if (!line || !this._core.store.state.isLineHandleDragging) {
16974
+ return;
16975
+ }
16976
+ const clientX = event.clientX - this._core.store.offsetX;
16977
+ const clientY = event.clientY - this._core.store.offsetY;
16978
+ const dx = clientX - this.initialMouseX;
16979
+ const dy = clientY - this.initialMouseY;
16980
+ const moveThreshold = 3;
16981
+ if (Math.abs(dx) > moveThreshold || Math.abs(dy) > moveThreshold) {
16982
+ this.hasMoved = true;
16983
+ }
16984
+ if (!this.hasMoved) {
16985
+ return;
16986
+ }
16987
+ const viewportScale = this._core.store.state.scale;
16988
+ const objectScale = line.scale || 1;
16989
+ const rotation = line.rotation;
16990
+ // Convert mouse delta to world coordinates
16991
+ const worldDx = dx / viewportScale;
16992
+ const worldDy = dy / viewportScale;
16993
+ // Rotate the delta into the line's local coordinate system
16994
+ const cos = Math.cos(-rotation);
16995
+ const sin = Math.sin(-rotation);
16996
+ const localDx = (worldDx * cos - worldDy * sin) * objectScale;
16997
+ const localDy = (worldDx * sin + worldDy * cos) * objectScale;
16998
+ const handleType = this._core.store.state.lineHandleType;
16999
+ if (handleType === 'start') {
17000
+ // Calculate world position directly from mouse position for snap detection
17001
+ const worldX = (clientX - this._core.store.state.translateX) / viewportScale;
17002
+ const worldY = (clientY - this._core.store.state.translateY) / viewportScale;
17003
+ // Get other endpoint's anchor to prevent anchoring both to same object
17004
+ const otherAnchorId = line.endAnchor?.objectId;
17005
+ // Check for snap target
17006
+ const snapTarget = this._core.anchorManager.findSnapTarget(worldX, worldY, line.id, otherAnchorId);
17007
+ this.currentSnapTarget = snapTarget;
17008
+ if (snapTarget) {
17009
+ // Snap to target center - convert world coords to local
17010
+ const localCoords = this.worldToLineLocal(line, snapTarget.centerX, snapTarget.centerY);
17011
+ this.updateLineEndpoint(line, localCoords.x, localCoords.y, this.initialEndX, this.initialEndY);
17012
+ // Get the other endpoint's world position for edge intersection calculation
17013
+ const otherEndpointWorld = this.lineLocalToWorld(line, this.initialEndX, this.initialEndY);
17014
+ // Calculate edge intersection point using AnchorManager to support curves
17015
+ const targetObject = this._core.store.allNonSelectionObjects.find(obj => obj.id === snapTarget.objectId);
17016
+ let edgeX;
17017
+ let edgeY;
17018
+ let t;
17019
+ if (targetObject) {
17020
+ const clipInfo = this._core.anchorManager.computeAnchorClipInfo(line, 'start', targetObject);
17021
+ if (clipInfo) {
17022
+ edgeX = clipInfo.worldX;
17023
+ edgeY = clipInfo.worldY;
17024
+ t = clipInfo.t;
17025
+ }
17026
+ }
17027
+ // Calculate control point in world coordinates if it exists
17028
+ let controlWorld = undefined;
17029
+ if (line.controlX !== undefined && line.controlY !== undefined) {
17030
+ controlWorld = this.lineLocalToWorld(line, line.controlX, line.controlY);
17031
+ }
17032
+ // Update snap indicator
17033
+ this._core.anchorManager.setSnapCandidate({
17034
+ objectId: snapTarget.objectId,
17035
+ endpoint: 'start',
17036
+ centerX: snapTarget.centerX,
17037
+ centerY: snapTarget.centerY,
17038
+ lineEndpointX: otherEndpointWorld.x,
17039
+ lineEndpointY: otherEndpointWorld.y,
17040
+ controlX: controlWorld?.x,
17041
+ controlY: controlWorld?.y,
17042
+ t,
17043
+ edgeX,
17044
+ edgeY,
17045
+ lineStroke: line.stroke,
17046
+ lineStrokeWidth: line.strokeWidth / line.scale,
17047
+ arrowOffset: line.hasStartArrow ? line.getArrowSize('start') / line.scale : undefined,
17048
+ arrowStyle: line.hasStartArrow ? line.arrows?.start?.style : undefined,
17049
+ arrowFill: line.hasStartArrow ? line.getArrowFill('start') : undefined,
17050
+ });
17051
+ }
17052
+ else {
17053
+ // No snap - use regular position
17054
+ const newStartX = this.initialStartX + localDx;
17055
+ const newStartY = this.initialStartY + localDy;
17056
+ this.updateLineEndpoint(line, newStartX, newStartY, this.initialEndX, this.initialEndY);
17057
+ // Clear snap indicator
17058
+ this._core.anchorManager.clearSnapCandidate();
17059
+ }
17060
+ }
17061
+ else if (handleType === 'end') {
17062
+ // Calculate world position directly from mouse position for snap detection
17063
+ const worldX = (clientX - this._core.store.state.translateX) / viewportScale;
17064
+ const worldY = (clientY - this._core.store.state.translateY) / viewportScale;
17065
+ // Get other endpoint's anchor to prevent anchoring both to same object
17066
+ const otherAnchorId = line.startAnchor?.objectId;
17067
+ // Check for snap target
17068
+ const snapTarget = this._core.anchorManager.findSnapTarget(worldX, worldY, line.id, otherAnchorId);
17069
+ this.currentSnapTarget = snapTarget;
17070
+ if (snapTarget) {
17071
+ // Snap to target center - convert world coords to local
17072
+ const localCoords = this.worldToLineLocal(line, snapTarget.centerX, snapTarget.centerY);
17073
+ this.updateLineEndpoint(line, this.initialStartX, this.initialStartY, localCoords.x, localCoords.y);
17074
+ // Get the other endpoint's world position for edge intersection calculation
17075
+ const otherEndpointWorld = this.lineLocalToWorld(line, this.initialStartX, this.initialStartY);
17076
+ // Calculate edge intersection point using AnchorManager to support curves
17077
+ const targetObject = this._core.store.allNonSelectionObjects.find(obj => obj.id === snapTarget.objectId);
17078
+ let edgeX;
17079
+ let edgeY;
17080
+ let t;
17081
+ if (targetObject) {
17082
+ const clipInfo = this._core.anchorManager.computeAnchorClipInfo(line, 'end', targetObject);
17083
+ if (clipInfo) {
17084
+ edgeX = clipInfo.worldX;
17085
+ edgeY = clipInfo.worldY;
17086
+ t = clipInfo.t;
17087
+ }
17088
+ }
17089
+ // Calculate control point in world coordinates if it exists
17090
+ let controlWorld = undefined;
17091
+ if (line.controlX !== undefined && line.controlY !== undefined) {
17092
+ controlWorld = this.lineLocalToWorld(line, line.controlX, line.controlY);
17093
+ }
17094
+ // Update snap indicator
17095
+ this._core.anchorManager.setSnapCandidate({
17096
+ objectId: snapTarget.objectId,
17097
+ endpoint: 'end',
17098
+ centerX: snapTarget.centerX,
17099
+ centerY: snapTarget.centerY,
17100
+ lineEndpointX: otherEndpointWorld.x,
17101
+ lineEndpointY: otherEndpointWorld.y,
17102
+ controlX: controlWorld?.x,
17103
+ controlY: controlWorld?.y,
17104
+ t,
17105
+ edgeX,
17106
+ edgeY,
17107
+ lineStroke: line.stroke,
17108
+ lineStrokeWidth: line.strokeWidth / line.scale,
17109
+ arrowOffset: line.hasEndArrow ? line.getArrowSize('end') / line.scale : undefined,
17110
+ arrowStyle: line.hasEndArrow ? line.arrows?.end?.style : undefined,
17111
+ arrowFill: line.hasEndArrow ? line.getArrowFill('end') : undefined,
17112
+ });
17113
+ }
17114
+ else {
17115
+ // No snap - use regular position
17116
+ const newEndX = this.initialEndX + localDx;
17117
+ const newEndY = this.initialEndY + localDy;
17118
+ this.updateLineEndpoint(line, this.initialStartX, this.initialStartY, newEndX, newEndY);
17119
+ // Clear snap indicator
17120
+ this._core.anchorManager.clearSnapCandidate();
17121
+ }
17122
+ }
17123
+ else if (handleType === 'center') {
17124
+ const startControlX = this.initialControlX ?? (this.initialStartX + this.initialEndX) / 2;
17125
+ const startControlY = this.initialControlY ?? (this.initialStartY + this.initialEndY) / 2;
17126
+ const newControlX = startControlX + localDx * 2;
17127
+ const newControlY = startControlY + localDy * 2;
17128
+ // Check distance to line segment for snapping back to straight
17129
+ const x1 = line.startX;
17130
+ const y1 = line.startY;
17131
+ const x2 = line.endX;
17132
+ const y2 = line.endY;
17133
+ const A = newControlX - x1;
17134
+ const B = newControlY - y1;
17135
+ const C = x2 - x1;
17136
+ const D = y2 - y1;
17137
+ const dot = A * C + B * D;
17138
+ const len_sq = C * C + D * D;
17139
+ let param = -1;
17140
+ if (len_sq !== 0) {
17141
+ param = dot / len_sq;
17142
+ }
17143
+ let xx, yy;
17144
+ if (param < 0) {
17145
+ xx = x1;
17146
+ yy = y1;
17147
+ }
17148
+ else if (param > 1) {
17149
+ xx = x2;
17150
+ yy = y2;
17151
+ }
17152
+ else {
17153
+ xx = x1 + param * C;
17154
+ yy = y1 + param * D;
17155
+ }
17156
+ const dx = newControlX - xx;
17157
+ const dy = newControlY - yy;
17158
+ const distance = Math.sqrt(dx * dx + dy * dy);
17159
+ // Threshold for snapping back to straight line
17160
+ const snapToStraightThreshold = 15;
17161
+ if (distance < snapToStraightThreshold) {
17162
+ line.updateControlPoint(undefined, undefined);
17163
+ }
17164
+ else {
17165
+ line.updateControlPoint(newControlX, newControlY);
17166
+ }
17167
+ }
17168
+ this._core.rerender();
17169
+ }
17170
+ /**
17171
+ * Converts world coordinates to a line's local coordinate system.
17172
+ */
17173
+ worldToLineLocal(line, worldX, worldY) {
17174
+ const dx = (worldX - line.translateX) * line.scale;
17175
+ const dy = (worldY - line.translateY) * line.scale;
17176
+ const cx = line.totalWidth / 2;
17177
+ const cy = line.totalHeight / 2;
17178
+ const cos = Math.cos(-line.rotation);
17179
+ const sin = Math.sin(-line.rotation);
17180
+ const rotatedX = (dx - cx) * cos - (dy - cy) * sin + cx;
17181
+ const rotatedY = (dx - cx) * sin + (dy - cy) * cos + cy;
17182
+ return {
17183
+ x: rotatedX + line.x,
17184
+ y: rotatedY + line.y,
17185
+ };
17186
+ }
17187
+ /**
17188
+ * Converts a line's local coordinate system to world coordinates.
17189
+ */
17190
+ lineLocalToWorld(line, localX, localY) {
17191
+ const px = localX - line.x;
17192
+ const py = localY - line.y;
17193
+ const cx = line.totalWidth / 2;
17194
+ const cy = line.totalHeight / 2;
17195
+ const cos = Math.cos(line.rotation);
17196
+ const sin = Math.sin(line.rotation);
17197
+ const rotatedX = (px - cx) * cos - (py - cy) * sin + cx;
17198
+ const rotatedY = (px - cx) * sin + (py - cy) * cos + cy;
17199
+ return {
17200
+ x: rotatedX / line.scale + line.translateX,
17201
+ y: rotatedY / line.scale + line.translateY,
17202
+ };
17203
+ }
17204
+ updateLineEndpoint(line, startX, startY, endX, endY) {
17205
+ // Calculate new bounding box using the static method that accounts for curve extrema
17206
+ const { minX, minY, maxX, maxY } = KritzelLine.calculateBoundingBox(startX, startY, endX, endY, line.controlX, line.controlY, line.strokeWidth);
17207
+ const newWidth = maxX - minX;
17208
+ const newHeight = maxY - minY;
17209
+ // Calculate old bounding box
17210
+ const { minX: oldMinX, minY: oldMinY, maxX: oldMaxX, maxY: oldMaxY, } = KritzelLine.calculateBoundingBox(this.initialStartX, this.initialStartY, this.initialEndX, this.initialEndY, this.initialControlX, this.initialControlY, line.strokeWidth);
17211
+ const oldWidth = oldMaxX - oldMinX;
17212
+ const oldHeight = oldMaxY - oldMinY;
17213
+ // Calculate center shift in local space
17214
+ const shiftX = minX - oldMinX;
17215
+ const shiftY = minY - oldMinY;
17216
+ const deltaCx = shiftX + (newWidth - oldWidth) / 2;
17217
+ const deltaCy = shiftY + (newHeight - oldHeight) / 2;
17218
+ // Convert the center shift to world coordinates
17219
+ const rotation = line.rotation;
17220
+ const cos = Math.cos(rotation);
17221
+ const sin = Math.sin(rotation);
17222
+ const scale = 1 / line.scale;
17223
+ const rotatedDeltaCx = deltaCx * cos - deltaCy * sin;
17224
+ const rotatedDeltaCy = deltaCx * sin + deltaCy * cos;
17225
+ // Calculate new translate
17226
+ const newTranslateX = this.initialTranslateX + scale * (oldWidth - newWidth) / 2 + scale * rotatedDeltaCx;
17227
+ const newTranslateY = this.initialTranslateY + scale * (oldHeight - newHeight) / 2 + scale * rotatedDeltaCy;
17228
+ // Update the line properties
17229
+ line.startX = startX;
17230
+ line.startY = startY;
17231
+ line.endX = endX;
17232
+ line.endY = endY;
17233
+ line.x = minX;
17234
+ line.y = minY;
17235
+ line.width = newWidth;
17236
+ line.height = newHeight;
17237
+ // Adjust translateX/Y to compensate for the bounding box shift
17238
+ line.translateX = newTranslateX;
17239
+ line.translateY = newTranslateY;
17240
+ // Clear cached adjusted points
17241
+ line._adjustedPoints = null;
17242
+ this._core.store.state.objects.update(line);
17243
+ }
17244
+ handlePointerUp(_event) {
17245
+ if (this._core.store.state.isLineHandleDragging) {
17246
+ const line = this.getSelectedLine();
17247
+ const handleType = this._core.store.state.lineHandleType;
17248
+ if (line && this.hasMoved) {
17249
+ // If we were snapped to a target, create the anchor
17250
+ if (this.currentSnapTarget && (handleType === 'start' || handleType === 'end')) {
17251
+ this._core.anchorManager.setAnchor(line.id, handleType, this.currentSnapTarget.objectId);
17252
+ }
17253
+ // Refresh the selection group dimensions after line endpoint change
17254
+ const selectionGroup = this._core.store.selectionGroup;
17255
+ if (selectionGroup) {
17256
+ selectionGroup.refreshObjectDimensions();
17257
+ selectionGroup.update();
17258
+ this._core.engine.emitObjectsChange();
17259
+ this._core.store.state.hasObjectsChanged = true;
17260
+ }
17261
+ }
17262
+ this._core.store.state.isLineHandleDragging = false;
17263
+ this.reset();
17264
+ this._core.rerender();
17265
+ }
17266
+ }
17267
+ getSelectedLine() {
17268
+ const selectionGroup = this._core.store.selectionGroup;
17269
+ if (!selectionGroup || selectionGroup.objects.length !== 1) {
17270
+ return null;
17271
+ }
17272
+ const object = selectionGroup.objects[0];
17273
+ if (object instanceof KritzelLine) {
17274
+ return object;
17275
+ }
17276
+ return null;
17277
+ }
17278
+ }
17279
+
16001
17280
  class KritzelSelectionTool extends KritzelBaseTool {
16002
17281
  selectionHandler;
16003
17282
  moveHandler;
16004
17283
  hoverHandler;
16005
17284
  resizeHandler;
16006
17285
  rotationHandler;
17286
+ lineHandleHandler;
16007
17287
  constructor(core) {
16008
17288
  super(core);
16009
17289
  this.selectionHandler = new KritzelSelectionHandler(this._core);
@@ -16011,6 +17291,7 @@ class KritzelSelectionTool extends KritzelBaseTool {
16011
17291
  this.hoverHandler = new KritzelHoverHandler(this._core);
16012
17292
  this.resizeHandler = new KritzelResizeHandler(this._core);
16013
17293
  this.rotationHandler = new KritzelRotationHandler(this._core);
17294
+ this.lineHandleHandler = new KritzelLineHandleHandler(this._core);
16014
17295
  }
16015
17296
  handlePointerDown(event) {
16016
17297
  if (event.cancelable) {
@@ -16020,14 +17301,17 @@ class KritzelSelectionTool extends KritzelBaseTool {
16020
17301
  if (KritzelEventHelper.isLeftClick(event)) {
16021
17302
  this._core.store.state.isResizeHandleSelected = this.isHandleSelected(event);
16022
17303
  this._core.store.state.isRotationHandleSelected = this.isRotationHandleSelected(event);
17304
+ this._core.store.state.isLineHandleSelected = this.isLineHandleSelected(event);
16023
17305
  this._core.store.state.resizeHandleType = this.getHandleType(event);
17306
+ this._core.store.state.lineHandleType = this.getLineHandleType(event);
16024
17307
  const selectedObject = this.getSelectedObject(event);
16025
17308
  const selectionGroup = this._core.store.selectionGroup;
16026
17309
  const isDifferentObject = selectedObject && selectionGroup && selectedObject.id !== selectionGroup.id;
16027
17310
  if ((selectedObject === null || isDifferentObject) &&
16028
17311
  selectionGroup &&
16029
17312
  !this._core.store.state.isResizeHandleSelected &&
16030
- !this._core.store.state.isRotationHandleSelected) {
17313
+ !this._core.store.state.isRotationHandleSelected &&
17314
+ !this._core.store.state.isLineHandleSelected) {
16031
17315
  this._core.removeSelectionGroup();
16032
17316
  this._core.rerender();
16033
17317
  }
@@ -16035,6 +17319,7 @@ class KritzelSelectionTool extends KritzelBaseTool {
16035
17319
  return;
16036
17320
  }
16037
17321
  }
17322
+ this.lineHandleHandler.handlePointerDown(event);
16038
17323
  this.moveHandler.handlePointerDown(event);
16039
17324
  this.selectionHandler.handlePointerDown(event);
16040
17325
  this.resizeHandler.handlePointerDown(event);
@@ -16048,7 +17333,9 @@ class KritzelSelectionTool extends KritzelBaseTool {
16048
17333
  if (this._core.store.state.pointers.size === 1) {
16049
17334
  this._core.store.state.isResizeHandleSelected = this.isHandleSelected(event);
16050
17335
  this._core.store.state.isRotationHandleSelected = this.isRotationHandleSelected(event);
17336
+ this._core.store.state.isLineHandleSelected = this.isLineHandleSelected(event);
16051
17337
  this._core.store.state.resizeHandleType = this.getHandleType(event);
17338
+ this._core.store.state.lineHandleType = this.getLineHandleType(event);
16052
17339
  const selectedObject = this.getSelectedObject(event);
16053
17340
  const selectionGroup = this._core.store.selectionGroup;
16054
17341
  const isDifferentObject = selectedObject && selectionGroup && selectedObject.id !== selectionGroup.id;
@@ -16058,11 +17345,13 @@ class KritzelSelectionTool extends KritzelBaseTool {
16058
17345
  if ((selectedObject === null || isDifferentObject) &&
16059
17346
  selectionGroup &&
16060
17347
  !this._core.store.state.isResizeHandleSelected &&
16061
- !this._core.store.state.isRotationHandleSelected) {
17348
+ !this._core.store.state.isRotationHandleSelected &&
17349
+ !this._core.store.state.isLineHandleSelected) {
16062
17350
  this._core.removeSelectionGroup();
16063
17351
  this._core.rerender();
16064
17352
  }
16065
17353
  }
17354
+ this.lineHandleHandler.handlePointerDown(event);
16066
17355
  this.rotationHandler.handlePointerDown(event);
16067
17356
  this.resizeHandler.handlePointerDown(event);
16068
17357
  this.moveHandler.handlePointerDown(event);
@@ -16075,6 +17364,7 @@ class KritzelSelectionTool extends KritzelBaseTool {
16075
17364
  event.preventDefault();
16076
17365
  }
16077
17366
  if (event.pointerType === 'mouse') {
17367
+ this.lineHandleHandler.handlePointerMove(event);
16078
17368
  this.moveHandler.handlePointerMove(event);
16079
17369
  this.hoverHandler.handlePointerMove(event);
16080
17370
  this.selectionHandler.handlePointerMove(event);
@@ -16086,6 +17376,7 @@ class KritzelSelectionTool extends KritzelBaseTool {
16086
17376
  if (this._core.store.state.isScaling === true) {
16087
17377
  return;
16088
17378
  }
17379
+ this.lineHandleHandler.handlePointerMove(event);
16089
17380
  this.rotationHandler.handlePointerMove(event);
16090
17381
  this.resizeHandler.handlePointerMove(event);
16091
17382
  this.moveHandler.handlePointerMove(event);
@@ -16098,6 +17389,7 @@ class KritzelSelectionTool extends KritzelBaseTool {
16098
17389
  event.preventDefault();
16099
17390
  }
16100
17391
  if (event.pointerType === 'mouse') {
17392
+ this.lineHandleHandler.handlePointerUp(event);
16101
17393
  this.moveHandler.handlePointerUp(event);
16102
17394
  this.resizeHandler.handlePointerUp(event);
16103
17395
  this.rotationHandler.handlePointerUp(event);
@@ -16108,6 +17400,7 @@ class KritzelSelectionTool extends KritzelBaseTool {
16108
17400
  if (this._core.store.state.isScaling === true) {
16109
17401
  return;
16110
17402
  }
17403
+ this.lineHandleHandler.handlePointerUp(event);
16111
17404
  this.rotationHandler.handlePointerUp(event);
16112
17405
  this.resizeHandler.handlePointerUp(event);
16113
17406
  this.moveHandler.handlePointerUp(event);
@@ -16137,20 +17430,38 @@ class KritzelSelectionTool extends KritzelBaseTool {
16137
17430
  const shadowRoot = this._core.store.state.host?.shadowRoot;
16138
17431
  if (!shadowRoot)
16139
17432
  return;
16140
- const elementAtPoint = shadowRoot.elementFromPoint(event.clientX, event.clientY);
16141
- const handle = elementAtPoint?.closest('.resize-handle-overlay');
17433
+ const elementsAtPoint = shadowRoot.elementsFromPoint(event.clientX, event.clientY);
17434
+ const handle = elementsAtPoint.find(el => el.classList.contains('resize-handle-overlay'));
16142
17435
  return handle?.classList[1];
16143
17436
  }
16144
17437
  isHandleSelected(event) {
16145
17438
  const shadowRoot = this._core.store.state.host?.shadowRoot;
16146
17439
  if (!shadowRoot)
16147
17440
  return false;
16148
- const elementAtPoint = shadowRoot.elementFromPoint(event.clientX, event.clientY);
16149
- return !!elementAtPoint?.classList.contains('resize-handle-overlay');
17441
+ const elementsAtPoint = shadowRoot.elementsFromPoint(event.clientX, event.clientY);
17442
+ return elementsAtPoint.some(el => el.classList.contains('resize-handle-overlay'));
17443
+ }
17444
+ isRotationHandleSelected(event) {
17445
+ const shadowRoot = this._core.store.state.host?.shadowRoot;
17446
+ if (!shadowRoot)
17447
+ return false;
17448
+ const elementsAtPoint = shadowRoot.elementsFromPoint(event.clientX, event.clientY);
17449
+ return elementsAtPoint.some(el => el.classList.contains('rotation-handle-overlay'));
17450
+ }
17451
+ isLineHandleSelected(event) {
17452
+ const shadowRoot = this._core.store.state.host?.shadowRoot;
17453
+ if (!shadowRoot)
17454
+ return false;
17455
+ const elementsAtPoint = shadowRoot.elementsFromPoint(event.clientX, event.clientY);
17456
+ return elementsAtPoint.some(el => el.classList.contains('selection-line-handle-overlay'));
16150
17457
  }
16151
- isRotationHandleSelected(event) {
16152
- const path = event.composedPath();
16153
- return !!path.find(element => element.classList && element.classList.contains('rotation-handle-overlay'));
17458
+ getLineHandleType(event) {
17459
+ const shadowRoot = this._core.store.state.host?.shadowRoot;
17460
+ if (!shadowRoot)
17461
+ return;
17462
+ const elementsAtPoint = shadowRoot.elementsFromPoint(event.clientX, event.clientY);
17463
+ const handle = elementsAtPoint.find(el => el.classList.contains('selection-line-handle-overlay'));
17464
+ return handle?.classList[1];
16154
17465
  }
16155
17466
  }
16156
17467
 
@@ -31199,6 +32510,881 @@ class KritzelAppStateMap {
31199
32510
  }
31200
32511
  }
31201
32512
 
32513
+ class KritzelClassHelper {
32514
+ static isInstanceOf(object, className) {
32515
+ return !!object && object.__class__ === className;
32516
+ }
32517
+ }
32518
+
32519
+ /**
32520
+ * Manages anchor connections between line endpoints and other objects.
32521
+ * Maintains a runtime index for efficient reverse lookups and handles
32522
+ * snap detection during line endpoint dragging.
32523
+ */
32524
+ class KritzelAnchorManager {
32525
+ _core;
32526
+ /**
32527
+ * Runtime index mapping objectId to the lines anchored to it.
32528
+ * This is derived from the anchor properties on lines and rebuilt as needed.
32529
+ */
32530
+ _anchorIndex = new Map();
32531
+ constructor(core) {
32532
+ this._core = core;
32533
+ }
32534
+ // ============================================
32535
+ // Anchor CRUD Operations
32536
+ // ============================================
32537
+ /**
32538
+ * Sets an anchor from a line endpoint to a target object's center.
32539
+ * Updates both the line's anchor property and the internal index.
32540
+ */
32541
+ setAnchor(lineId, endpoint, targetObjectId) {
32542
+ const line = this.getLineById(lineId);
32543
+ if (!line) {
32544
+ return;
32545
+ }
32546
+ // Prevent anchoring both endpoints to the same object
32547
+ if (endpoint === 'start' && line.endAnchor?.objectId === targetObjectId) {
32548
+ return;
32549
+ }
32550
+ if (endpoint === 'end' && line.startAnchor?.objectId === targetObjectId) {
32551
+ return;
32552
+ }
32553
+ // Remove existing anchor if any
32554
+ this.removeAnchor(lineId, endpoint);
32555
+ // Set the anchor on the line
32556
+ const anchor = { objectId: targetObjectId };
32557
+ if (endpoint === 'start') {
32558
+ line.startAnchor = anchor;
32559
+ }
32560
+ else {
32561
+ line.endAnchor = anchor;
32562
+ }
32563
+ // Update the index
32564
+ this.addToIndex(targetObjectId, lineId, endpoint);
32565
+ // Snap the endpoint to the target's center
32566
+ this.snapEndpointToObject(line, endpoint, targetObjectId);
32567
+ // Persist the change
32568
+ this._core.store.state.objects.update(line);
32569
+ }
32570
+ /**
32571
+ * Removes an anchor from a line endpoint.
32572
+ * Updates both the line's anchor property and the internal index.
32573
+ */
32574
+ removeAnchor(lineId, endpoint) {
32575
+ const line = this.getLineById(lineId);
32576
+ if (!line) {
32577
+ return;
32578
+ }
32579
+ const anchor = endpoint === 'start' ? line.startAnchor : line.endAnchor;
32580
+ if (!anchor) {
32581
+ return;
32582
+ }
32583
+ // Remove from index
32584
+ this.removeFromIndex(anchor.objectId, lineId, endpoint);
32585
+ // Clear the anchor on the line
32586
+ if (endpoint === 'start') {
32587
+ line.startAnchor = undefined;
32588
+ }
32589
+ else {
32590
+ line.endAnchor = undefined;
32591
+ }
32592
+ }
32593
+ /**
32594
+ * Gets the anchor for a specific line endpoint.
32595
+ */
32596
+ getAnchor(lineId, endpoint) {
32597
+ const line = this.getLineById(lineId);
32598
+ if (!line) {
32599
+ return null;
32600
+ }
32601
+ const anchor = endpoint === 'start' ? line.startAnchor : line.endAnchor;
32602
+ return anchor ?? null;
32603
+ }
32604
+ // ============================================
32605
+ // Query Operations
32606
+ // ============================================
32607
+ /**
32608
+ * Gets all lines that have an endpoint anchored to the specified object.
32609
+ */
32610
+ getLinesAnchoredTo(objectId) {
32611
+ const entries = this._anchorIndex.get(objectId);
32612
+ return entries ? Array.from(entries) : [];
32613
+ }
32614
+ // ============================================
32615
+ // Position Updates
32616
+ // ============================================
32617
+ /**
32618
+ * Updates all line endpoints that are anchored to the specified object.
32619
+ * Should be called when an object moves.
32620
+ */
32621
+ updateAnchorsForObject(objectId) {
32622
+ const entries = this.getLinesAnchoredTo(objectId);
32623
+ if (entries.length === 0) {
32624
+ return;
32625
+ }
32626
+ const targetObject = this.getObjectById(objectId);
32627
+ if (!targetObject) {
32628
+ return;
32629
+ }
32630
+ for (const entry of entries) {
32631
+ const line = this.getLineById(entry.lineId);
32632
+ if (!line) {
32633
+ continue;
32634
+ }
32635
+ this.snapEndpointToObject(line, entry.endpoint, objectId);
32636
+ }
32637
+ }
32638
+ /**
32639
+ * Snaps a line endpoint to an object's center.
32640
+ */
32641
+ snapEndpointToObject(line, endpoint, targetObjectId) {
32642
+ const targetObject = this.getObjectById(targetObjectId);
32643
+ if (!targetObject) {
32644
+ return;
32645
+ }
32646
+ // Get target center in world coordinates
32647
+ const targetCenterX = targetObject.centerX;
32648
+ const targetCenterY = targetObject.centerY;
32649
+ // Convert world coordinates to line's local coordinate system
32650
+ const localCoords = this.worldToLineLocal(line, targetCenterX, targetCenterY);
32651
+ // Update the endpoint
32652
+ line.updateEndpoint(endpoint, localCoords.x, localCoords.y);
32653
+ }
32654
+ /**
32655
+ * Converts world coordinates to a line's local coordinate system.
32656
+ */
32657
+ worldToLineLocal(line, worldX, worldY) {
32658
+ // Get line center in world coordinates
32659
+ const cx = line.centerX;
32660
+ const cy = line.centerY;
32661
+ // Translate to center
32662
+ const dx = worldX - cx;
32663
+ const dy = worldY - cy;
32664
+ // Apply inverse rotation
32665
+ const cos = Math.cos(-line.rotation);
32666
+ const sin = Math.sin(-line.rotation);
32667
+ const rotatedX = dx * cos - dy * sin;
32668
+ const rotatedY = dx * sin + dy * cos;
32669
+ // Calculate local coordinates relative to the unrotated bounding box top-left
32670
+ const relativeX = rotatedX + (line.totalWidth / 2) / line.scale;
32671
+ const relativeY = rotatedY + (line.totalHeight / 2) / line.scale;
32672
+ // Convert to internal coordinates
32673
+ const localX = relativeX * line.scale + line.x;
32674
+ const localY = relativeY * line.scale + line.y;
32675
+ return { x: localX, y: localY };
32676
+ }
32677
+ // ============================================
32678
+ // Snap Detection
32679
+ // ============================================
32680
+ /**
32681
+ * Finds the nearest object that can be snapped to within the snap threshold.
32682
+ * Returns null if no suitable snap target is found.
32683
+ *
32684
+ * @param worldX - X coordinate in world space
32685
+ * @param worldY - Y coordinate in world space
32686
+ * @param excludeLineId - ID of the line being edited (to exclude from snap targets)
32687
+ * @param otherEndpointAnchorId - ID of object anchored to the other endpoint (to prevent same-object anchoring)
32688
+ */
32689
+ findSnapTarget(worldX, worldY, excludeLineId, otherEndpointAnchorId) {
32690
+ let bestTarget = null;
32691
+ let highestZIndex = -Infinity;
32692
+ const objects = this._core.store.allNonSelectionObjects;
32693
+ for (const object of objects) {
32694
+ // Skip the line being edited
32695
+ if (object.id === excludeLineId) {
32696
+ continue;
32697
+ }
32698
+ // Skip if this is the object anchored to the other endpoint
32699
+ if (otherEndpointAnchorId && object.id === otherEndpointAnchorId) {
32700
+ continue;
32701
+ }
32702
+ // Skip non-anchorable objects
32703
+ if (!this.isAnchorable(object)) {
32704
+ continue;
32705
+ }
32706
+ // Check if point is inside the object's rotated polygon
32707
+ const polygon = object.rotatedPolygon;
32708
+ const points = [polygon.topLeft, polygon.topRight, polygon.bottomRight, polygon.bottomLeft];
32709
+ if (KritzelGeometryHelper.isPointInPolygon({ x: worldX, y: worldY }, points)) {
32710
+ // If inside, check if this object is "above" the current best target
32711
+ if (object.zIndex > highestZIndex) {
32712
+ highestZIndex = object.zIndex;
32713
+ bestTarget = {
32714
+ objectId: object.id,
32715
+ centerX: object.centerX,
32716
+ centerY: object.centerY,
32717
+ };
32718
+ }
32719
+ }
32720
+ }
32721
+ return bestTarget;
32722
+ }
32723
+ /**
32724
+ * Sets the current snap candidate for visual feedback.
32725
+ */
32726
+ setSnapCandidate(candidate) {
32727
+ this._core.store.state.snapCandidate = candidate;
32728
+ this._core.rerender();
32729
+ }
32730
+ /**
32731
+ * Gets the current snap candidate.
32732
+ */
32733
+ getSnapCandidate() {
32734
+ return this._core.store.state.snapCandidate ?? null;
32735
+ }
32736
+ /**
32737
+ * Clears the snap candidate.
32738
+ */
32739
+ clearSnapCandidate() {
32740
+ this._core.store.state.snapCandidate = null;
32741
+ this._core.rerender();
32742
+ }
32743
+ // ============================================
32744
+ // Render Data
32745
+ // ============================================
32746
+ /**
32747
+ * Gets render data for anchor lines visualization.
32748
+ * Returns null if there's no selected line with anchors or if snap is in progress.
32749
+ */
32750
+ getAnchorLinesRenderData() {
32751
+ const selectionGroup = this._core.store.selectionGroup;
32752
+ if (!selectionGroup || selectionGroup.objects.length !== 1)
32753
+ return null;
32754
+ const selectedObject = selectionGroup.objects[0];
32755
+ if (!KritzelClassHelper.isInstanceOf(selectedObject, 'KritzelLine'))
32756
+ return null;
32757
+ const line = selectedObject;
32758
+ const startAnchorViz = this.computeAnchorVisualization(line, 'start');
32759
+ const endAnchorViz = this.computeAnchorVisualization(line, 'end');
32760
+ if (!startAnchorViz && !endAnchorViz)
32761
+ return null;
32762
+ const scale = this._core.store.state.scale;
32763
+ const lineStrokeWidth = line.strokeWidth / line.scale;
32764
+ const indicatorStrokeWidth = `${2 / scale}`;
32765
+ const dashLength = Math.max(lineStrokeWidth * 2, 4 / scale);
32766
+ const dashArray = `${dashLength} ${dashLength}`;
32767
+ const indicatorRadius = 8 / scale;
32768
+ return {
32769
+ lineStrokeWidth,
32770
+ indicatorStrokeWidth,
32771
+ dashArray,
32772
+ indicatorRadius,
32773
+ startAnchorViz,
32774
+ endAnchorViz,
32775
+ };
32776
+ }
32777
+ /**
32778
+ * Gets render data for snap indicator visualization.
32779
+ * Returns null if there's no snap candidate.
32780
+ */
32781
+ getSnapIndicatorRenderData() {
32782
+ const snapCandidate = this.getSnapCandidate();
32783
+ if (!snapCandidate)
32784
+ return null;
32785
+ const scale = this._core.store.state.scale;
32786
+ const indicatorRadius = 8 / scale;
32787
+ const indicatorStrokeWidth = `${2 / scale}`;
32788
+ const lineStrokeWidth = snapCandidate.lineStrokeWidth
32789
+ ? `${snapCandidate.lineStrokeWidth}`
32790
+ : `${4 / scale}`;
32791
+ const lineStrokeWidthNum = snapCandidate.lineStrokeWidth || (4 / scale);
32792
+ const dashLength = Math.max(lineStrokeWidthNum * 2, 4 / scale);
32793
+ const dashArray = `${dashLength} ${dashLength}`;
32794
+ const lineStroke = snapCandidate.lineStroke || '#000000';
32795
+ let solidLineEndX = snapCandidate.edgeX;
32796
+ let solidLineEndY = snapCandidate.edgeY;
32797
+ let arrowPoints;
32798
+ if (snapCandidate.arrowOffset && snapCandidate.edgeX !== undefined && snapCandidate.edgeY !== undefined) {
32799
+ const dx = snapCandidate.lineEndpointX - snapCandidate.edgeX;
32800
+ const dy = snapCandidate.lineEndpointY - snapCandidate.edgeY;
32801
+ const length = Math.sqrt(dx * dx + dy * dy);
32802
+ if (length > snapCandidate.arrowOffset) {
32803
+ solidLineEndX = snapCandidate.edgeX + (dx / length) * snapCandidate.arrowOffset;
32804
+ solidLineEndY = snapCandidate.edgeY + (dy / length) * snapCandidate.arrowOffset;
32805
+ }
32806
+ // Calculate arrow head points
32807
+ // Direction from line endpoint to edge (arrow direction)
32808
+ const arrowDx = snapCandidate.edgeX - snapCandidate.lineEndpointX;
32809
+ const arrowDy = snapCandidate.edgeY - snapCandidate.lineEndpointY;
32810
+ const arrowLengthTotal = Math.sqrt(arrowDx * arrowDx + arrowDy * arrowDy);
32811
+ if (arrowLengthTotal > 0) {
32812
+ const ux = arrowDx / arrowLengthTotal;
32813
+ const uy = arrowDy / arrowLengthTotal;
32814
+ // Perpendicular vector
32815
+ const px = -uy;
32816
+ const py = ux;
32817
+ // Arrow dimensions
32818
+ const arrowLength = snapCandidate.arrowOffset;
32819
+ const arrowWidth = arrowLength; // 1:1 ratio
32820
+ // Arrow tip at edge
32821
+ const tipX = snapCandidate.edgeX;
32822
+ const tipY = snapCandidate.edgeY;
32823
+ // Arrow base
32824
+ const baseX = tipX - ux * arrowLength;
32825
+ const baseY = tipY - uy * arrowLength;
32826
+ // Arrow wings
32827
+ const leftX = baseX + px * arrowWidth / 2;
32828
+ const leftY = baseY + py * arrowWidth / 2;
32829
+ const rightX = baseX - px * arrowWidth / 2;
32830
+ const rightY = baseY - py * arrowWidth / 2;
32831
+ arrowPoints = `${tipX},${tipY} ${leftX},${leftY} ${rightX},${rightY}`;
32832
+ }
32833
+ }
32834
+ return {
32835
+ indicatorRadius,
32836
+ indicatorStrokeWidth,
32837
+ lineStrokeWidth,
32838
+ dashArray,
32839
+ lineStroke,
32840
+ centerX: snapCandidate.centerX,
32841
+ centerY: snapCandidate.centerY,
32842
+ lineEndpointX: snapCandidate.lineEndpointX,
32843
+ lineEndpointY: snapCandidate.lineEndpointY,
32844
+ edgeX: snapCandidate.edgeX,
32845
+ edgeY: snapCandidate.edgeY,
32846
+ arrowOffset: snapCandidate.arrowOffset,
32847
+ arrowStyle: snapCandidate.arrowStyle,
32848
+ arrowFill: snapCandidate.arrowFill,
32849
+ solidLineEndX,
32850
+ solidLineEndY,
32851
+ arrowPoints,
32852
+ snapLinePath: (() => {
32853
+ if (snapCandidate.controlX !== undefined &&
32854
+ snapCandidate.controlY !== undefined &&
32855
+ snapCandidate.t !== undefined) {
32856
+ const startT = snapCandidate.endpoint === 'start' ? 1 - snapCandidate.t : snapCandidate.t;
32857
+ // Ensure meaningful range
32858
+ if (startT >= 1)
32859
+ return undefined;
32860
+ const segment = this.extractQuadraticSegment({ x: snapCandidate.lineEndpointX, y: snapCandidate.lineEndpointY }, { x: snapCandidate.controlX, y: snapCandidate.controlY }, { x: snapCandidate.centerX, y: snapCandidate.centerY }, startT, 1);
32861
+ return `M ${segment.start.x} ${segment.start.y} Q ${segment.control.x} ${segment.control.y} ${segment.end.x} ${segment.end.y}`;
32862
+ }
32863
+ return undefined;
32864
+ })(),
32865
+ };
32866
+ }
32867
+ // ============================================
32868
+ // Cleanup Operations
32869
+ // ============================================
32870
+ /**
32871
+ * Handles cleanup when an object is deleted.
32872
+ * Detaches all lines that were anchored to the deleted object.
32873
+ */
32874
+ handleObjectDeleted(objectId) {
32875
+ const entries = this.getLinesAnchoredTo(objectId);
32876
+ for (const entry of entries) {
32877
+ this.removeAnchor(entry.lineId, entry.endpoint);
32878
+ // Update the line to persist the change
32879
+ const line = this.getLineById(entry.lineId);
32880
+ if (line) {
32881
+ this._core.store.state.objects.update(line);
32882
+ }
32883
+ }
32884
+ // Remove the object from the index
32885
+ this._anchorIndex.delete(objectId);
32886
+ }
32887
+ /**
32888
+ * Handles cleanup when a line is deleted.
32889
+ * Removes all anchor index entries for the line.
32890
+ */
32891
+ handleLineDeleted(lineId) {
32892
+ const line = this.getLineById(lineId);
32893
+ if (!line) {
32894
+ return;
32895
+ }
32896
+ if (line.startAnchor) {
32897
+ this.removeFromIndex(line.startAnchor.objectId, lineId, 'start');
32898
+ }
32899
+ if (line.endAnchor) {
32900
+ this.removeFromIndex(line.endAnchor.objectId, lineId, 'end');
32901
+ }
32902
+ }
32903
+ // ============================================
32904
+ // Index Management
32905
+ // ============================================
32906
+ /**
32907
+ * Rebuilds the anchor index from all lines in the object map.
32908
+ * Should be called after loading/deserializing objects.
32909
+ */
32910
+ rebuildIndex() {
32911
+ this._anchorIndex.clear();
32912
+ const objects = this._core.store.allObjects;
32913
+ for (const object of objects) {
32914
+ if (object instanceof KritzelLine) {
32915
+ if (object.startAnchor) {
32916
+ this.addToIndex(object.startAnchor.objectId, object.id, 'start');
32917
+ }
32918
+ if (object.endAnchor) {
32919
+ this.addToIndex(object.endAnchor.objectId, object.id, 'end');
32920
+ }
32921
+ }
32922
+ }
32923
+ }
32924
+ /**
32925
+ * Adds an entry to the anchor index.
32926
+ */
32927
+ addToIndex(objectId, lineId, endpoint) {
32928
+ if (!this._anchorIndex.has(objectId)) {
32929
+ this._anchorIndex.set(objectId, new Set());
32930
+ }
32931
+ const entries = this._anchorIndex.get(objectId);
32932
+ // Check if entry already exists
32933
+ for (const entry of entries) {
32934
+ if (entry.lineId === lineId && entry.endpoint === endpoint) {
32935
+ return;
32936
+ }
32937
+ }
32938
+ entries.add({ lineId, endpoint });
32939
+ }
32940
+ /**
32941
+ * Removes an entry from the anchor index.
32942
+ */
32943
+ removeFromIndex(objectId, lineId, endpoint) {
32944
+ const entries = this._anchorIndex.get(objectId);
32945
+ if (!entries) {
32946
+ return;
32947
+ }
32948
+ for (const entry of entries) {
32949
+ if (entry.lineId === lineId && entry.endpoint === endpoint) {
32950
+ entries.delete(entry);
32951
+ break;
32952
+ }
32953
+ }
32954
+ // Clean up empty sets
32955
+ if (entries.size === 0) {
32956
+ this._anchorIndex.delete(objectId);
32957
+ }
32958
+ }
32959
+ // ============================================
32960
+ // Helper Methods
32961
+ // ============================================
32962
+ /**
32963
+ * Gets a line by its ID.
32964
+ */
32965
+ getLineById(lineId) {
32966
+ const objects = this._core.store.state.objects.filter(o => o.id === lineId);
32967
+ if (objects.length === 0) {
32968
+ return null;
32969
+ }
32970
+ const object = objects[0];
32971
+ return object instanceof KritzelLine ? object : null;
32972
+ }
32973
+ /**
32974
+ * Gets an object by its ID.
32975
+ */
32976
+ getObjectById(objectId) {
32977
+ const objects = this._core.store.state.objects.filter(o => o.id === objectId);
32978
+ return objects.length > 0 ? objects[0] : null;
32979
+ }
32980
+ findAnchorTarget(line, endpoint) {
32981
+ const anchor = endpoint === 'start' ? line.startAnchor : line.endAnchor;
32982
+ if (!anchor) {
32983
+ return null;
32984
+ }
32985
+ return this._core.store.allNonSelectionObjects.find(obj => obj.id === anchor.objectId) ?? null;
32986
+ }
32987
+ // ============================================
32988
+ // Visualization Helpers
32989
+ // ============================================
32990
+ /**
32991
+ * Computes anchor visualization data for a line with anchors.
32992
+ * Returns edge intersection points for rendering dashed lines from edge to center.
32993
+ */
32994
+ computeAnchorVisualization(line, endpoint) {
32995
+ const anchor = endpoint === 'start' ? line.startAnchor : line.endAnchor;
32996
+ if (!anchor)
32997
+ return null;
32998
+ const targetObject = this.findAnchorTarget(line, endpoint);
32999
+ if (!targetObject)
33000
+ return null;
33001
+ const clipInfo = this.computeAnchorClipInfo(line, endpoint, targetObject);
33002
+ if (!clipInfo)
33003
+ return null;
33004
+ const centerX = targetObject.centerX;
33005
+ const centerY = targetObject.centerY;
33006
+ return {
33007
+ edgeX: clipInfo.worldX,
33008
+ edgeY: clipInfo.worldY,
33009
+ centerX,
33010
+ centerY,
33011
+ pathD: this.buildAnchorPath(line, endpoint, clipInfo, targetObject) ?? undefined,
33012
+ };
33013
+ }
33014
+ /**
33015
+ * Computes a clipped line path that stops at anchor edges instead of going to anchor centers.
33016
+ * When arrows are present on anchored ends, the line is shortened so the arrow tip appears at the edge.
33017
+ * Returns the SVG path 'd' attribute string, or the original path if no clipping is needed.
33018
+ * @param line The line object
33019
+ * @param offsetByViewBox If true, subtracts line.x and line.y from coordinates (for selection UI)
33020
+ */
33021
+ computeClippedLinePath(line, offsetByViewBox = false) {
33022
+ // Check for snap candidate first - this applies during drag operations even without anchors
33023
+ const snapCandidate = this.getSnapCandidate();
33024
+ const selectionGroup = this._core.store.selectionGroup;
33025
+ const hasActiveSnapCandidate = snapCandidate && selectionGroup && selectionGroup.objects.length === 1 && selectionGroup.objects[0].id === line.id;
33026
+ // If no anchors and no active snap candidate, return original path (with or without offset)
33027
+ if (!line.startAnchor && !line.endAnchor && !hasActiveSnapCandidate) {
33028
+ if (offsetByViewBox) {
33029
+ if (line.controlX !== undefined && line.controlY !== undefined) {
33030
+ return `M ${line.startX - line.x} ${line.startY - line.y} Q ${line.controlX - line.x} ${line.controlY - line.y} ${line.endX - line.x} ${line.endY - line.y}`;
33031
+ }
33032
+ return `M ${line.startX - line.x} ${line.startY - line.y} L ${line.endX - line.x} ${line.endY - line.y}`;
33033
+ }
33034
+ return line.d;
33035
+ }
33036
+ const startTarget = line.startAnchor ? this.findAnchorTarget(line, 'start') : null;
33037
+ const endTarget = line.endAnchor ? this.findAnchorTarget(line, 'end') : null;
33038
+ let startClip = startTarget ? this.computeAnchorClipInfo(line, 'start', startTarget) : null;
33039
+ let endClip = endTarget ? this.computeAnchorClipInfo(line, 'end', endTarget) : null;
33040
+ // Apply snap candidate if present and matches this line
33041
+ if (hasActiveSnapCandidate) {
33042
+ if (snapCandidate.edgeX !== undefined && snapCandidate.edgeY !== undefined) {
33043
+ const localEdge = this.lineWorldToLocal(line, snapCandidate.edgeX, snapCandidate.edgeY);
33044
+ const clipInfo = {
33045
+ worldX: snapCandidate.edgeX,
33046
+ worldY: snapCandidate.edgeY,
33047
+ localX: localEdge.x,
33048
+ localY: localEdge.y,
33049
+ t: snapCandidate.t
33050
+ };
33051
+ if (snapCandidate.endpoint === 'start') {
33052
+ startClip = clipInfo;
33053
+ }
33054
+ else {
33055
+ endClip = clipInfo;
33056
+ }
33057
+ }
33058
+ }
33059
+ // Apply offset if requested (for selection UI which uses coordinates relative to 0,0)
33060
+ const offsetX = offsetByViewBox ? line.x : 0;
33061
+ const offsetY = offsetByViewBox ? line.y : 0;
33062
+ // Handle curved lines by splitting the quadratic at the clipped parameters
33063
+ if (line.controlX !== undefined && line.controlY !== undefined) {
33064
+ let startT = startClip?.t ?? 0;
33065
+ let endT = endClip?.t ?? 1;
33066
+ // Adjust for start arrow
33067
+ if (startClip && line.hasStartArrow) {
33068
+ const arrowOffset = line.getArrowSize('start');
33069
+ const speed = this.evaluateDerivativeSpeedAtT(line, startT);
33070
+ if (speed > 0) {
33071
+ startT += arrowOffset / speed;
33072
+ }
33073
+ }
33074
+ // Adjust for end arrow
33075
+ if (endClip && line.hasEndArrow) {
33076
+ const arrowOffset = line.getArrowSize('end');
33077
+ const speed = this.evaluateDerivativeSpeedAtT(line, endT);
33078
+ if (speed > 0) {
33079
+ endT -= arrowOffset / speed;
33080
+ }
33081
+ }
33082
+ // Ensure valid range
33083
+ if (startT < 0)
33084
+ startT = 0;
33085
+ if (endT > 1)
33086
+ endT = 1;
33087
+ // Handle overlap or invalid range
33088
+ if (endT <= startT) {
33089
+ // If the arrow adjustment caused them to cross, or they were already crossed/close
33090
+ // We can either effectively hide the line, or just clamp them.
33091
+ // If we clamp them to the midpoint, the line becomes a point.
33092
+ // Let's ensure a minimal gap or just set them equal.
33093
+ const mid = (startT + endT) / 2;
33094
+ startT = mid;
33095
+ endT = mid;
33096
+ }
33097
+ const segment = this.extractQuadraticSegment({ x: line.startX, y: line.startY }, { x: line.controlX, y: line.controlY }, { x: line.endX, y: line.endY }, startT, endT);
33098
+ return `M ${segment.start.x - offsetX} ${segment.start.y - offsetY} Q ${segment.control.x - offsetX} ${segment.control.y - offsetY} ${segment.end.x - offsetX} ${segment.end.y - offsetY}`;
33099
+ }
33100
+ // Straight lines fall back to linear interpolation
33101
+ let startX = startClip?.localX ?? line.startX;
33102
+ let startY = startClip?.localY ?? line.startY;
33103
+ let endX = endClip?.localX ?? line.endX;
33104
+ let endY = endClip?.localY ?? line.endY;
33105
+ if (startClip && line.hasStartArrow) {
33106
+ const arrowOffset = line.getArrowSize('start');
33107
+ const dx = endX - startX;
33108
+ const dy = endY - startY;
33109
+ const length = Math.sqrt(dx * dx + dy * dy);
33110
+ if (length > arrowOffset) {
33111
+ startX += (dx / length) * arrowOffset;
33112
+ startY += (dy / length) * arrowOffset;
33113
+ }
33114
+ }
33115
+ if (endClip && line.hasEndArrow) {
33116
+ const arrowOffset = line.getArrowSize('end');
33117
+ const dx = startX - endX;
33118
+ const dy = startY - endY;
33119
+ const length = Math.sqrt(dx * dx + dy * dy);
33120
+ if (length > arrowOffset) {
33121
+ endX += (dx / length) * arrowOffset;
33122
+ endY += (dy / length) * arrowOffset;
33123
+ }
33124
+ }
33125
+ return `M ${startX - offsetX} ${startY - offsetY} L ${endX - offsetX} ${endY - offsetY}`;
33126
+ }
33127
+ computeAnchorClipInfo(line, endpoint, targetObject) {
33128
+ if (line.controlX !== undefined && line.controlY !== undefined) {
33129
+ return this.computeCurvedClipInfo(line, endpoint, targetObject);
33130
+ }
33131
+ return this.computeStraightClipInfo(line, endpoint, targetObject);
33132
+ }
33133
+ computeStraightClipInfo(line, endpoint, targetObject) {
33134
+ const reference = endpoint === 'start'
33135
+ ? this.lineLocalToWorld(line, line.endX, line.endY)
33136
+ : this.lineLocalToWorld(line, line.startX, line.startY);
33137
+ const edgeIntersection = KritzelGeometryHelper.getLinePolygonIntersection(reference, { x: targetObject.centerX, y: targetObject.centerY }, targetObject.rotatedPolygon);
33138
+ if (!edgeIntersection) {
33139
+ return null;
33140
+ }
33141
+ const localEdge = this.lineWorldToLocal(line, edgeIntersection.x, edgeIntersection.y);
33142
+ const totalLength = Math.sqrt(Math.pow(line.endX - line.startX, 2) +
33143
+ Math.pow(line.endY - line.startY, 2));
33144
+ const distanceFromStart = Math.sqrt(Math.pow(localEdge.x - line.startX, 2) +
33145
+ Math.pow(localEdge.y - line.startY, 2));
33146
+ const t = totalLength > 0 ? distanceFromStart / totalLength : endpoint === 'start' ? 0 : 1;
33147
+ return {
33148
+ localX: localEdge.x,
33149
+ localY: localEdge.y,
33150
+ worldX: edgeIntersection.x,
33151
+ worldY: edgeIntersection.y,
33152
+ t,
33153
+ };
33154
+ }
33155
+ computeCurvedClipInfo(line, endpoint, targetObject) {
33156
+ const polygonPoints = this.getPolygonPoints(targetObject.rotatedPolygon);
33157
+ const exitPoint = this.findCurveExitPoint(line, endpoint, polygonPoints);
33158
+ if (exitPoint) {
33159
+ return exitPoint;
33160
+ }
33161
+ const reference = endpoint === 'start'
33162
+ ? this.lineLocalToWorld(line, line.endX, line.endY)
33163
+ : this.lineLocalToWorld(line, line.startX, line.startY);
33164
+ const fallbackIntersection = KritzelGeometryHelper.getLinePolygonIntersection(reference, { x: targetObject.centerX, y: targetObject.centerY }, targetObject.rotatedPolygon);
33165
+ if (!fallbackIntersection) {
33166
+ return null;
33167
+ }
33168
+ const localEdge = this.lineWorldToLocal(line, fallbackIntersection.x, fallbackIntersection.y);
33169
+ const approxT = this.approximateParameterForWorldPoint(line, fallbackIntersection);
33170
+ return {
33171
+ localX: localEdge.x,
33172
+ localY: localEdge.y,
33173
+ worldX: fallbackIntersection.x,
33174
+ worldY: fallbackIntersection.y,
33175
+ t: approxT,
33176
+ };
33177
+ }
33178
+ findCurveExitPoint(line, endpoint, polygonPoints) {
33179
+ const steps = 64;
33180
+ const initialT = endpoint === 'start' ? 0 : 1;
33181
+ const initialSample = this.evaluateLineAtT(line, initialT);
33182
+ let prevInside = KritzelGeometryHelper.isPointInPolygon({ x: initialSample.worldX, y: initialSample.worldY }, polygonPoints);
33183
+ let prevT = initialT;
33184
+ for (let i = 1; i <= steps; i++) {
33185
+ const t = endpoint === 'start' ? i / steps : 1 - i / steps;
33186
+ const sample = this.evaluateLineAtT(line, t);
33187
+ const inside = KritzelGeometryHelper.isPointInPolygon({ x: sample.worldX, y: sample.worldY }, polygonPoints);
33188
+ if (prevInside && !inside) {
33189
+ const refinedT = this.refineCurveExitParameter(line, polygonPoints, prevT, t);
33190
+ const refinedPoint = this.evaluateLineAtT(line, refinedT);
33191
+ return {
33192
+ localX: refinedPoint.localX,
33193
+ localY: refinedPoint.localY,
33194
+ worldX: refinedPoint.worldX,
33195
+ worldY: refinedPoint.worldY,
33196
+ t: refinedT,
33197
+ };
33198
+ }
33199
+ prevInside = inside;
33200
+ prevT = t;
33201
+ }
33202
+ return null;
33203
+ }
33204
+ refineCurveExitParameter(line, polygonPoints, insideT, outsideT) {
33205
+ let tInside = insideT;
33206
+ let tOutside = outsideT;
33207
+ for (let i = 0; i < 8; i++) {
33208
+ const mid = (tInside + tOutside) / 2;
33209
+ const sample = this.evaluateLineAtT(line, mid);
33210
+ const inside = KritzelGeometryHelper.isPointInPolygon({ x: sample.worldX, y: sample.worldY }, polygonPoints);
33211
+ if (inside) {
33212
+ tInside = mid;
33213
+ }
33214
+ else {
33215
+ tOutside = mid;
33216
+ }
33217
+ }
33218
+ return (tInside + tOutside) / 2;
33219
+ }
33220
+ approximateParameterForWorldPoint(line, target) {
33221
+ const steps = 80;
33222
+ let bestT = 0;
33223
+ let bestDistance = Infinity;
33224
+ for (let i = 0; i <= steps; i++) {
33225
+ const t = i / steps;
33226
+ const sample = this.evaluateLineAtT(line, t);
33227
+ const distance = Math.hypot(sample.worldX - target.x, sample.worldY - target.y);
33228
+ if (distance < bestDistance) {
33229
+ bestDistance = distance;
33230
+ bestT = t;
33231
+ }
33232
+ }
33233
+ return bestT;
33234
+ }
33235
+ evaluateLineAtT(line, t) {
33236
+ const clampedT = Math.max(0, Math.min(1, t));
33237
+ let localX;
33238
+ let localY;
33239
+ if (line.controlX !== undefined && line.controlY !== undefined) {
33240
+ const oneMinusT = 1 - clampedT;
33241
+ localX = oneMinusT * oneMinusT * line.startX + 2 * oneMinusT * clampedT * line.controlX + clampedT * clampedT * line.endX;
33242
+ localY = oneMinusT * oneMinusT * line.startY + 2 * oneMinusT * clampedT * line.controlY + clampedT * clampedT * line.endY;
33243
+ }
33244
+ else {
33245
+ localX = line.startX + (line.endX - line.startX) * clampedT;
33246
+ localY = line.startY + (line.endY - line.startY) * clampedT;
33247
+ }
33248
+ const world = this.lineLocalToWorld(line, localX, localY);
33249
+ return { t: clampedT, localX, localY, worldX: world.x, worldY: world.y };
33250
+ }
33251
+ evaluateDerivativeSpeedAtT(line, t) {
33252
+ const clampedT = Math.max(0, Math.min(1, t));
33253
+ if (line.controlX !== undefined && line.controlY !== undefined) {
33254
+ // Quadratic Bezier derivative: B'(t) = 2(1-t)(P1-P0) + 2t(P2-P1)
33255
+ const p0x = line.startX;
33256
+ const p0y = line.startY;
33257
+ const p1x = line.controlX;
33258
+ const p1y = line.controlY;
33259
+ const p2x = line.endX;
33260
+ const p2y = line.endY;
33261
+ const dx = 2 * (1 - clampedT) * (p1x - p0x) + 2 * clampedT * (p2x - p1x);
33262
+ const dy = 2 * (1 - clampedT) * (p1y - p0y) + 2 * clampedT * (p2y - p1y);
33263
+ return Math.sqrt(dx * dx + dy * dy);
33264
+ }
33265
+ else {
33266
+ // Straight line speed is constant length
33267
+ const dx = line.endX - line.startX;
33268
+ const dy = line.endY - line.startY;
33269
+ return Math.sqrt(dx * dx + dy * dy);
33270
+ }
33271
+ }
33272
+ extractQuadraticSegment(start, control, end, tStart, tEnd) {
33273
+ let normalizedStart = Math.max(0, Math.min(1, tStart));
33274
+ let normalizedEnd = Math.max(0, Math.min(1, tEnd));
33275
+ if (normalizedEnd < normalizedStart) {
33276
+ const swap = normalizedStart;
33277
+ normalizedStart = normalizedEnd;
33278
+ normalizedEnd = swap;
33279
+ }
33280
+ let segment = { start, control, end };
33281
+ if (normalizedStart > 0) {
33282
+ const split = this.splitQuadraticSegment(segment, normalizedStart);
33283
+ segment = split.right;
33284
+ const remainingRange = 1 - normalizedStart;
33285
+ normalizedEnd = remainingRange > 0 ? (normalizedEnd - normalizedStart) / remainingRange : 1;
33286
+ }
33287
+ if (normalizedEnd < 1) {
33288
+ const split = this.splitQuadraticSegment(segment, normalizedEnd);
33289
+ segment = split.left;
33290
+ }
33291
+ return segment;
33292
+ }
33293
+ splitQuadraticSegment(segment, t) {
33294
+ const clampedT = Math.max(0, Math.min(1, t));
33295
+ const p0 = segment.start;
33296
+ const p1 = segment.control;
33297
+ const p2 = segment.end;
33298
+ const p01 = this.lerpPoint(p0, p1, clampedT);
33299
+ const p12 = this.lerpPoint(p1, p2, clampedT);
33300
+ const p012 = this.lerpPoint(p01, p12, clampedT);
33301
+ return {
33302
+ left: { start: p0, control: p01, end: p012 },
33303
+ right: { start: p012, control: p12, end: p2 },
33304
+ };
33305
+ }
33306
+ lerpPoint(a, b, t) {
33307
+ return {
33308
+ x: a.x + (b.x - a.x) * t,
33309
+ y: a.y + (b.y - a.y) * t,
33310
+ };
33311
+ }
33312
+ buildAnchorPath(line, endpoint, clipInfo, targetObject) {
33313
+ if (line.controlX === undefined || line.controlY === undefined || clipInfo.t === undefined) {
33314
+ return `M ${clipInfo.worldX} ${clipInfo.worldY} L ${targetObject.centerX} ${targetObject.centerY}`;
33315
+ }
33316
+ const startT = endpoint === 'start' ? 0 : clipInfo.t;
33317
+ const endT = endpoint === 'start' ? clipInfo.t : 1;
33318
+ if (endT <= startT) {
33319
+ return `M ${clipInfo.worldX} ${clipInfo.worldY} L ${targetObject.centerX} ${targetObject.centerY}`;
33320
+ }
33321
+ const segment = this.extractQuadraticSegment({ x: line.startX, y: line.startY }, { x: line.controlX, y: line.controlY }, { x: line.endX, y: line.endY }, startT, endT);
33322
+ const reverse = endpoint === 'start';
33323
+ return this.buildWorldQuadraticPath(line, segment, reverse);
33324
+ }
33325
+ buildWorldQuadraticPath(line, segment, reverse = false) {
33326
+ const startPoint = reverse ? segment.end : segment.start;
33327
+ const endPoint = reverse ? segment.start : segment.end;
33328
+ const controlPoint = segment.control;
33329
+ const startWorld = this.lineLocalToWorld(line, startPoint.x, startPoint.y);
33330
+ const controlWorld = this.lineLocalToWorld(line, controlPoint.x, controlPoint.y);
33331
+ const endWorld = this.lineLocalToWorld(line, endPoint.x, endPoint.y);
33332
+ return `M ${startWorld.x} ${startWorld.y} Q ${controlWorld.x} ${controlWorld.y} ${endWorld.x} ${endWorld.y}`;
33333
+ }
33334
+ getPolygonPoints(polygon) {
33335
+ return [polygon.topLeft, polygon.topRight, polygon.bottomRight, polygon.bottomLeft];
33336
+ }
33337
+ /**
33338
+ * Converts local line coordinates to world coordinates.
33339
+ */
33340
+ lineLocalToWorld(line, localX, localY) {
33341
+ const px = localX - line.x;
33342
+ const py = localY - line.y;
33343
+ const cx = line.totalWidth / 2;
33344
+ const cy = line.totalHeight / 2;
33345
+ const cos = Math.cos(line.rotation);
33346
+ const sin = Math.sin(line.rotation);
33347
+ const rotatedX = (px - cx) * cos - (py - cy) * sin + cx;
33348
+ const rotatedY = (px - cx) * sin + (py - cy) * cos + cy;
33349
+ return {
33350
+ x: rotatedX / line.scale + line.translateX,
33351
+ y: rotatedY / line.scale + line.translateY,
33352
+ };
33353
+ }
33354
+ /**
33355
+ * Converts world coordinates to line's local SVG coordinates.
33356
+ */
33357
+ lineWorldToLocal(line, worldX, worldY) {
33358
+ const dx = (worldX - line.translateX) * line.scale;
33359
+ const dy = (worldY - line.translateY) * line.scale;
33360
+ const cx = line.totalWidth / 2;
33361
+ const cy = line.totalHeight / 2;
33362
+ const cos = Math.cos(-line.rotation);
33363
+ const sin = Math.sin(-line.rotation);
33364
+ const rotatedX = (dx - cx) * cos - (dy - cy) * sin + cx;
33365
+ const rotatedY = (dx - cx) * sin + (dy - cy) * cos + cy;
33366
+ return {
33367
+ x: rotatedX + line.x,
33368
+ y: rotatedY + line.y,
33369
+ };
33370
+ }
33371
+ /**
33372
+ * Checks if an object can be used as an anchor target.
33373
+ */
33374
+ isAnchorable(object) {
33375
+ // Exclude selection-related objects
33376
+ if (object instanceof KritzelSelectionBox || object instanceof KritzelSelectionGroup) {
33377
+ return false;
33378
+ }
33379
+ // Exclude line objects - lines cannot be anchored to other lines
33380
+ if (object instanceof KritzelLine) {
33381
+ return false;
33382
+ }
33383
+ // All other visible objects can be anchored to
33384
+ return object.isVisible;
33385
+ }
33386
+ }
33387
+
31202
33388
  const DEFAULT_BRUSH_CONFIG = {
31203
33389
  type: 'pen',
31204
33390
  color: '#000000',
@@ -31291,7 +33477,41 @@ const DEFAULT_TEXT_CONFIG = {
31291
33477
  ],
31292
33478
  };
31293
33479
 
31294
- export { Doc as $, isNode as A, min$2 as B, pow as C, HocuspocusProviderWebsocket as D, KritzelPath as E, KritzelImage as F, KritzelBrushTool as G, HocuspocusProvider as H, KritzelEraserTool as I, KritzelImageTool as J, KritzelText as K, KritzelTextTool as L, KritzelSelectionTool as M, IndexedDBSyncProvider as N, Observable$1 as O, KritzelAppStateMap as P, KritzelWorkspace as Q, DEFAULT_BRUSH_CONFIG as R, DEFAULT_TEXT_CONFIG as S, KritzelDevicesHelper as T, KritzelMouseButton as U, KritzelKeyboardHelper as V, KritzelBaseHandler as W, KritzelToolRegistry as X, KritzelSelectionBox as Y, KritzelSelectionGroup as Z, KritzelBaseObject as _, writeVarUint8Array$2 as a, DEFAULT_SYNC_CONFIG as a0, UndoManager as a1, ObjectHelper as a2, KritzelEventHelper as a3, KritzelBaseTool as a4, readVarUint8Array$2 as b, applyUpdate as c, encodeStateVector as d, encodeStateAsUpdate as e, createEncoder$1 as f, createDecoder$1 as g, create$8 as h, fromBase64 as i, toBase64 as j, createUint8ArrayFromArrayBuffer as k, offChange as l, readVarString$2 as m, floor$2 as n, onChange as o, getUnixTime$1 as p, equalityDeep$1 as q, readVarUint$2 as r, setIfUndefined$1 as s, toUint8Array$1 as t, writeVarString$2 as u, varStorage as v, writeVarUint$2 as w, map as x, ObservableV2 as y, length$3 as z };
31295
- //# sourceMappingURL=default-text-tool.config-DzqpOikl.js.map
33480
+ const DEFAULT_LINE_TOOL_CONFIG = {
33481
+ color: '#000000',
33482
+ size: 4,
33483
+ palette: [
33484
+ '#000000',
33485
+ '#ff5252',
33486
+ '#ffbc00',
33487
+ '#00c853',
33488
+ '#0000FF',
33489
+ '#d500f9',
33490
+ '#fafafa',
33491
+ '#a52714',
33492
+ '#ee8100',
33493
+ '#558b2f',
33494
+ '#01579b',
33495
+ '#8e24aa',
33496
+ '#90a4ae',
33497
+ '#ff4081',
33498
+ '#ff6e40',
33499
+ '#aeea00',
33500
+ '#304ffe',
33501
+ '#7c4dff',
33502
+ '#cfd8dc',
33503
+ '#f8bbd0',
33504
+ '#ffccbc',
33505
+ '#f0f4c3',
33506
+ '#b3e5fc',
33507
+ '#e1bee7',
33508
+ ],
33509
+ arrows: {
33510
+ end: { enabled: true, style: 'triangle' },
33511
+ },
33512
+ };
33513
+
33514
+ export { KritzelKeyboardHelper as $, isNode as A, min$2 as B, pow as C, HocuspocusProviderWebsocket as D, KritzelPath as E, KritzelImage as F, KritzelLine as G, HocuspocusProvider as H, KritzelBrushTool as I, KritzelLineTool as J, KritzelText as K, KritzelEraserTool as L, KritzelImageTool as M, KritzelTextTool as N, Observable$1 as O, KritzelCursorHelper as P, KritzelSelectionTool as Q, IndexedDBSyncProvider as R, KritzelAppStateMap as S, KritzelWorkspace as T, KritzelAnchorManager as U, DEFAULT_BRUSH_CONFIG as V, DEFAULT_TEXT_CONFIG as W, DEFAULT_LINE_TOOL_CONFIG as X, KritzelDevicesHelper as Y, KritzelMouseButton as Z, KritzelIconRegistry as _, writeVarUint8Array$2 as a, KritzelBaseHandler as a0, KritzelToolRegistry as a1, KritzelSelectionBox as a2, KritzelSelectionGroup as a3, KritzelBaseObject as a4, Doc as a5, DEFAULT_SYNC_CONFIG as a6, UndoManager as a7, ObjectHelper as a8, KritzelEventHelper as a9, KritzelBaseTool as aa, KritzelClassHelper as ab, readVarUint8Array$2 as b, applyUpdate as c, encodeStateVector as d, encodeStateAsUpdate as e, createEncoder$1 as f, createDecoder$1 as g, create$8 as h, fromBase64 as i, toBase64 as j, createUint8ArrayFromArrayBuffer as k, offChange as l, readVarString$2 as m, floor$2 as n, onChange as o, getUnixTime$1 as p, equalityDeep$1 as q, readVarUint$2 as r, setIfUndefined$1 as s, toUint8Array$1 as t, writeVarString$2 as u, varStorage as v, writeVarUint$2 as w, map as x, ObservableV2 as y, length$3 as z };
33515
+ //# sourceMappingURL=default-line-tool.config-C35m-d1Y.js.map
31296
33516
 
31297
- //# sourceMappingURL=default-text-tool.config-DzqpOikl.js.map
33517
+ //# sourceMappingURL=default-line-tool.config-C35m-d1Y.js.map