samcan 0.0.2 → 0.1.0
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.
- package/{license → LICENSE} +3 -1
- package/README.md +3 -0
- package/dist/camera/index.cjs +370 -0
- package/dist/camera/index.d.cts +54 -0
- package/dist/camera/index.d.ts +54 -0
- package/dist/camera/index.js +70 -0
- package/dist/document/index.cjs +735 -0
- package/dist/document/index.d.cts +174 -0
- package/dist/document/index.d.ts +174 -0
- package/dist/document/index.js +161 -0
- package/dist/editor/index.cjs +5007 -0
- package/dist/editor/index.d.cts +374 -0
- package/dist/editor/index.d.ts +374 -0
- package/dist/editor/index.js +2401 -0
- package/dist/engine/index.cjs +3752 -0
- package/dist/engine/index.d.cts +183 -0
- package/dist/engine/index.d.ts +183 -0
- package/dist/engine/index.js +111 -0
- package/dist/index.cjs +8899 -0
- package/dist/index.d.cts +962 -0
- package/dist/index.d.ts +962 -46
- package/dist/index.js +1008 -49
- package/dist/math/index.cjs +2230 -0
- package/dist/math/index.d.cts +302 -0
- package/dist/math/index.d.ts +302 -0
- package/dist/math/index.js +1109 -0
- package/dist/renderer/index.cjs +1865 -0
- package/dist/renderer/index.d.cts +91 -0
- package/dist/renderer/index.d.ts +91 -0
- package/dist/renderer/index.js +89 -0
- package/dist/shared/chunk-35pkr0zs.js +110 -0
- package/dist/shared/chunk-7sr7q84y.js +80 -0
- package/dist/shared/chunk-87399bz7.js +89 -0
- package/dist/shared/chunk-8ynnz57m.js +129 -0
- package/dist/shared/chunk-d6gxvapt.js +500 -0
- package/dist/shared/chunk-hewznwbd.js +305 -0
- package/dist/shared/chunk-jdvrd5tv.js +623 -0
- package/dist/shared/chunk-rvag1j46.js +461 -0
- package/dist/shared/chunk-rzxyjwk8.js +397 -0
- package/dist/shared/chunk-s3qdcmcq.js +1392 -0
- package/dist/shared/chunk-v53jprrn.js +68 -0
- package/dist/shared/chunk-wa3772hp.js +117 -0
- package/dist/spatial/index.cjs +954 -0
- package/dist/spatial/index.d.cts +136 -0
- package/dist/spatial/index.d.ts +136 -0
- package/dist/spatial/index.js +301 -0
- package/dist/stroke/index.cjs +1010 -0
- package/dist/stroke/index.d.cts +143 -0
- package/dist/stroke/index.d.ts +143 -0
- package/dist/stroke/index.js +343 -0
- package/package.json +122 -58
- package/dist/bundle.js +0 -5888
- package/dist/core/animation/animationruntime.d.ts +0 -238
- package/dist/core/animation/animationruntime.d.ts.map +0 -1
- package/dist/core/animation/animationruntime.js +0 -530
- package/dist/core/animation/animationruntime.js.map +0 -1
- package/dist/core/animation/animationstate.d.ts +0 -85
- package/dist/core/animation/animationstate.d.ts.map +0 -1
- package/dist/core/animation/animationstate.js +0 -119
- package/dist/core/animation/animationstate.js.map +0 -1
- package/dist/core/animation/animationtrack.d.ts +0 -55
- package/dist/core/animation/animationtrack.d.ts.map +0 -1
- package/dist/core/animation/animationtrack.js +0 -185
- package/dist/core/animation/animationtrack.js.map +0 -1
- package/dist/core/animation/easing.d.ts +0 -80
- package/dist/core/animation/easing.d.ts.map +0 -1
- package/dist/core/animation/easing.js +0 -126
- package/dist/core/animation/easing.js.map +0 -1
- package/dist/core/animation/index.d.ts +0 -11
- package/dist/core/animation/index.d.ts.map +0 -1
- package/dist/core/animation/index.js +0 -10
- package/dist/core/animation/index.js.map +0 -1
- package/dist/core/animation/interpolator.d.ts +0 -82
- package/dist/core/animation/interpolator.d.ts.map +0 -1
- package/dist/core/animation/interpolator.js +0 -108
- package/dist/core/animation/interpolator.js.map +0 -1
- package/dist/core/animation/keyframe.d.ts +0 -52
- package/dist/core/animation/keyframe.d.ts.map +0 -1
- package/dist/core/animation/keyframe.js +0 -65
- package/dist/core/animation/keyframe.js.map +0 -1
- package/dist/core/animation/logger.d.ts +0 -8
- package/dist/core/animation/logger.d.ts.map +0 -1
- package/dist/core/animation/logger.js +0 -20
- package/dist/core/animation/logger.js.map +0 -1
- package/dist/core/animation/statemachine.d.ts +0 -178
- package/dist/core/animation/statemachine.d.ts.map +0 -1
- package/dist/core/animation/statemachine.js +0 -378
- package/dist/core/animation/statemachine.js.map +0 -1
- package/dist/core/animation/statetransition.d.ts +0 -142
- package/dist/core/animation/statetransition.d.ts.map +0 -1
- package/dist/core/animation/statetransition.js +0 -189
- package/dist/core/animation/statetransition.js.map +0 -1
- package/dist/core/animation/timeline.d.ts +0 -62
- package/dist/core/animation/timeline.d.ts.map +0 -1
- package/dist/core/animation/timeline.js +0 -102
- package/dist/core/animation/timeline.js.map +0 -1
- package/dist/core/api.d.ts +0 -245
- package/dist/core/api.d.ts.map +0 -1
- package/dist/core/api.js +0 -369
- package/dist/core/api.js.map +0 -1
- package/dist/core/asset/assetmanager.d.ts +0 -196
- package/dist/core/asset/assetmanager.d.ts.map +0 -1
- package/dist/core/asset/assetmanager.js +0 -502
- package/dist/core/asset/assetmanager.js.map +0 -1
- package/dist/core/asset/index.d.ts +0 -3
- package/dist/core/asset/index.d.ts.map +0 -1
- package/dist/core/asset/index.js +0 -3
- package/dist/core/asset/index.js.map +0 -1
- package/dist/core/asset/types.d.ts +0 -36
- package/dist/core/asset/types.d.ts.map +0 -1
- package/dist/core/asset/types.js +0 -1
- package/dist/core/asset/types.js.map +0 -1
- package/dist/core/command/basecommand.d.ts +0 -29
- package/dist/core/command/basecommand.d.ts.map +0 -1
- package/dist/core/command/basecommand.js +0 -36
- package/dist/core/command/basecommand.js.map +0 -1
- package/dist/core/command/command.d.ts +0 -55
- package/dist/core/command/command.d.ts.map +0 -1
- package/dist/core/command/command.js +0 -1
- package/dist/core/command/command.js.map +0 -1
- package/dist/core/command/commandhistory.d.ts +0 -76
- package/dist/core/command/commandhistory.d.ts.map +0 -1
- package/dist/core/command/commandhistory.js +0 -122
- package/dist/core/command/commandhistory.js.map +0 -1
- package/dist/core/command/editorcommands.d.ts +0 -108
- package/dist/core/command/editorcommands.d.ts.map +0 -1
- package/dist/core/command/editorcommands.js +0 -274
- package/dist/core/command/editorcommands.js.map +0 -1
- package/dist/core/command/index.d.ts +0 -5
- package/dist/core/command/index.d.ts.map +0 -1
- package/dist/core/command/index.js +0 -5
- package/dist/core/command/index.js.map +0 -1
- package/dist/core/editor/events/emitter.d.ts +0 -45
- package/dist/core/editor/events/emitter.d.ts.map +0 -1
- package/dist/core/editor/events/emitter.js +0 -88
- package/dist/core/editor/events/emitter.js.map +0 -1
- package/dist/core/error/animationerror.d.ts +0 -27
- package/dist/core/error/animationerror.d.ts.map +0 -1
- package/dist/core/error/animationerror.js +0 -41
- package/dist/core/error/animationerror.js.map +0 -1
- package/dist/core/error/asseterror.d.ts +0 -32
- package/dist/core/error/asseterror.d.ts.map +0 -1
- package/dist/core/error/asseterror.js +0 -48
- package/dist/core/error/asseterror.js.map +0 -1
- package/dist/core/error/index.d.ts +0 -7
- package/dist/core/error/index.d.ts.map +0 -1
- package/dist/core/error/index.js +0 -7
- package/dist/core/error/index.js.map +0 -1
- package/dist/core/error/pluginerror.d.ts +0 -22
- package/dist/core/error/pluginerror.d.ts.map +0 -1
- package/dist/core/error/pluginerror.js +0 -33
- package/dist/core/error/pluginerror.js.map +0 -1
- package/dist/core/error/renderererror.d.ts +0 -26
- package/dist/core/error/renderererror.d.ts.map +0 -1
- package/dist/core/error/renderererror.js +0 -40
- package/dist/core/error/renderererror.js.map +0 -1
- package/dist/core/error/samcanerror.d.ts +0 -72
- package/dist/core/error/samcanerror.d.ts.map +0 -1
- package/dist/core/error/samcanerror.js +0 -108
- package/dist/core/error/samcanerror.js.map +0 -1
- package/dist/core/error/serializationerror.d.ts +0 -31
- package/dist/core/error/serializationerror.d.ts.map +0 -1
- package/dist/core/error/serializationerror.js +0 -46
- package/dist/core/error/serializationerror.js.map +0 -1
- package/dist/core/index.d.ts +0 -12
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -12
- package/dist/core/index.js.map +0 -1
- package/dist/core/math/color.d.ts +0 -56
- package/dist/core/math/color.d.ts.map +0 -1
- package/dist/core/math/color.js +0 -125
- package/dist/core/math/color.js.map +0 -1
- package/dist/core/math/index.d.ts +0 -14
- package/dist/core/math/index.d.ts.map +0 -1
- package/dist/core/math/index.js +0 -14
- package/dist/core/math/index.js.map +0 -1
- package/dist/core/math/matrix.d.ts +0 -101
- package/dist/core/math/matrix.d.ts.map +0 -1
- package/dist/core/math/matrix.js +0 -177
- package/dist/core/math/matrix.js.map +0 -1
- package/dist/core/math/paint.d.ts +0 -104
- package/dist/core/math/paint.d.ts.map +0 -1
- package/dist/core/math/paint.js +0 -204
- package/dist/core/math/paint.js.map +0 -1
- package/dist/core/math/path/index.d.ts +0 -94
- package/dist/core/math/path/index.d.ts.map +0 -1
- package/dist/core/math/path/index.js +0 -180
- package/dist/core/math/path/index.js.map +0 -1
- package/dist/core/math/path/operations.d.ts +0 -38
- package/dist/core/math/path/operations.d.ts.map +0 -1
- package/dist/core/math/path/operations.js +0 -149
- package/dist/core/math/path/operations.js.map +0 -1
- package/dist/core/math/pool.d.ts +0 -54
- package/dist/core/math/pool.d.ts.map +0 -1
- package/dist/core/math/pool.js +0 -97
- package/dist/core/math/pool.js.map +0 -1
- package/dist/core/math/pools.d.ts +0 -29
- package/dist/core/math/pools.d.ts.map +0 -1
- package/dist/core/math/pools.js +0 -50
- package/dist/core/math/pools.js.map +0 -1
- package/dist/core/math/rectangle.d.ts +0 -68
- package/dist/core/math/rectangle.d.ts.map +0 -1
- package/dist/core/math/rectangle.js +0 -124
- package/dist/core/math/rectangle.js.map +0 -1
- package/dist/core/math/utils.d.ts +0 -68
- package/dist/core/math/utils.d.ts.map +0 -1
- package/dist/core/math/utils.js +0 -110
- package/dist/core/math/utils.js.map +0 -1
- package/dist/core/math/vector2.d.ts +0 -85
- package/dist/core/math/vector2.d.ts.map +0 -1
- package/dist/core/math/vector2.js +0 -134
- package/dist/core/math/vector2.js.map +0 -1
- package/dist/core/plugin/animationcontroller.d.ts +0 -43
- package/dist/core/plugin/animationcontroller.d.ts.map +0 -1
- package/dist/core/plugin/animationcontroller.js +0 -11
- package/dist/core/plugin/animationcontroller.js.map +0 -1
- package/dist/core/plugin/index.d.ts +0 -6
- package/dist/core/plugin/index.d.ts.map +0 -1
- package/dist/core/plugin/index.js +0 -4
- package/dist/core/plugin/index.js.map +0 -1
- package/dist/core/plugin/plugin.d.ts +0 -53
- package/dist/core/plugin/plugin.d.ts.map +0 -1
- package/dist/core/plugin/plugin.js +0 -29
- package/dist/core/plugin/plugin.js.map +0 -1
- package/dist/core/plugin/pluginregistry.d.ts +0 -71
- package/dist/core/plugin/pluginregistry.d.ts.map +0 -1
- package/dist/core/plugin/pluginregistry.js +0 -143
- package/dist/core/plugin/pluginregistry.js.map +0 -1
- package/dist/core/renderer/batchmanager.d.ts +0 -68
- package/dist/core/renderer/batchmanager.d.ts.map +0 -1
- package/dist/core/renderer/batchmanager.js +0 -82
- package/dist/core/renderer/batchmanager.js.map +0 -1
- package/dist/core/renderer/canvas2drenderer.d.ts +0 -139
- package/dist/core/renderer/canvas2drenderer.d.ts.map +0 -1
- package/dist/core/renderer/canvas2drenderer.js +0 -499
- package/dist/core/renderer/canvas2drenderer.js.map +0 -1
- package/dist/core/renderer/dirtyregionmanager.d.ts +0 -54
- package/dist/core/renderer/dirtyregionmanager.d.ts.map +0 -1
- package/dist/core/renderer/dirtyregionmanager.js +0 -129
- package/dist/core/renderer/dirtyregionmanager.js.map +0 -1
- package/dist/core/renderer/index.d.ts +0 -8
- package/dist/core/renderer/index.d.ts.map +0 -1
- package/dist/core/renderer/index.js +0 -6
- package/dist/core/renderer/index.js.map +0 -1
- package/dist/core/renderer/renderer.d.ts +0 -154
- package/dist/core/renderer/renderer.d.ts.map +0 -1
- package/dist/core/renderer/renderer.js +0 -1
- package/dist/core/renderer/renderer.js.map +0 -1
- package/dist/core/renderer/rendererfactory.d.ts +0 -66
- package/dist/core/renderer/rendererfactory.d.ts.map +0 -1
- package/dist/core/renderer/rendererfactory.js +0 -219
- package/dist/core/renderer/rendererfactory.js.map +0 -1
- package/dist/core/renderer/webglrenderer.d.ts +0 -185
- package/dist/core/renderer/webglrenderer.d.ts.map +0 -1
- package/dist/core/renderer/webglrenderer.js +0 -1007
- package/dist/core/renderer/webglrenderer.js.map +0 -1
- package/dist/core/scene/index.d.ts +0 -4
- package/dist/core/scene/index.d.ts.map +0 -1
- package/dist/core/scene/index.js +0 -4
- package/dist/core/scene/index.js.map +0 -1
- package/dist/core/scene/node.d.ts +0 -162
- package/dist/core/scene/node.d.ts.map +0 -1
- package/dist/core/scene/node.js +0 -402
- package/dist/core/scene/node.js.map +0 -1
- package/dist/core/scene/nodes/artboard.d.ts +0 -42
- package/dist/core/scene/nodes/artboard.d.ts.map +0 -1
- package/dist/core/scene/nodes/artboard.js +0 -64
- package/dist/core/scene/nodes/artboard.js.map +0 -1
- package/dist/core/scene/nodes/groupnode.d.ts +0 -10
- package/dist/core/scene/nodes/groupnode.d.ts.map +0 -1
- package/dist/core/scene/nodes/groupnode.js +0 -12
- package/dist/core/scene/nodes/groupnode.js.map +0 -1
- package/dist/core/scene/nodes/imagenode.d.ts +0 -38
- package/dist/core/scene/nodes/imagenode.d.ts.map +0 -1
- package/dist/core/scene/nodes/imagenode.js +0 -77
- package/dist/core/scene/nodes/imagenode.js.map +0 -1
- package/dist/core/scene/nodes/index.d.ts +0 -5
- package/dist/core/scene/nodes/index.d.ts.map +0 -1
- package/dist/core/scene/nodes/index.js +0 -5
- package/dist/core/scene/nodes/index.js.map +0 -1
- package/dist/core/scene/nodes/shapenode.d.ts +0 -76
- package/dist/core/scene/nodes/shapenode.d.ts.map +0 -1
- package/dist/core/scene/nodes/shapenode.js +0 -212
- package/dist/core/scene/nodes/shapenode.js.map +0 -1
- package/dist/core/scene/transform.d.ts +0 -27
- package/dist/core/scene/transform.d.ts.map +0 -1
- package/dist/core/scene/transform.js +0 -52
- package/dist/core/scene/transform.js.map +0 -1
- package/dist/core/serialization/index.d.ts +0 -3
- package/dist/core/serialization/index.d.ts.map +0 -1
- package/dist/core/serialization/index.js +0 -2
- package/dist/core/serialization/index.js.map +0 -1
- package/dist/core/serialization/serializer.d.ts +0 -323
- package/dist/core/serialization/serializer.d.ts.map +0 -1
- package/dist/core/serialization/serializer.js +0 -1173
- package/dist/core/serialization/serializer.js.map +0 -1
- package/dist/core/serialization/types.d.ts +0 -242
- package/dist/core/serialization/types.d.ts.map +0 -1
- package/dist/core/serialization/types.js +0 -1
- package/dist/core/serialization/types.js.map +0 -1
- package/dist/core/timing/clock.d.ts +0 -43
- package/dist/core/timing/clock.d.ts.map +0 -1
- package/dist/core/timing/clock.js +0 -78
- package/dist/core/timing/clock.js.map +0 -1
- package/dist/core/timing/index.d.ts +0 -3
- package/dist/core/timing/index.d.ts.map +0 -1
- package/dist/core/timing/index.js +0 -3
- package/dist/core/timing/index.js.map +0 -1
- package/dist/core/timing/scheduler.d.ts +0 -72
- package/dist/core/timing/scheduler.d.ts.map +0 -1
- package/dist/core/timing/scheduler.js +0 -163
- package/dist/core/timing/scheduler.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/wrapper/react/index.d.ts +0 -5
- package/dist/wrapper/react/index.d.ts.map +0 -1
- package/dist/wrapper/react/index.js +0 -3
- package/dist/wrapper/react/index.js.map +0 -1
- package/dist/wrapper/react/samcan-player.d.ts +0 -26
- package/dist/wrapper/react/samcan-player.d.ts.map +0 -1
- package/dist/wrapper/react/samcan-player.js +0 -19
- package/dist/wrapper/react/samcan-player.js.map +0 -1
- package/dist/wrapper/react/use-samcan-player.d.ts +0 -33
- package/dist/wrapper/react/use-samcan-player.d.ts.map +0 -1
- package/dist/wrapper/react/use-samcan-player.js +0 -65
- package/dist/wrapper/react/use-samcan-player.js.map +0 -1
- package/readme.md +0 -96
|
@@ -0,0 +1,5007 @@
|
|
|
1
|
+
var import_node_module = require("node:module");
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
7
|
+
var __toCommonJS = (from) => {
|
|
8
|
+
var entry = __moduleCache.get(from), desc;
|
|
9
|
+
if (entry)
|
|
10
|
+
return entry;
|
|
11
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
13
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
14
|
+
get: () => from[key],
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
}));
|
|
17
|
+
__moduleCache.set(from, entry);
|
|
18
|
+
return entry;
|
|
19
|
+
};
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, {
|
|
23
|
+
get: all[name],
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// editor/index.ts
|
|
31
|
+
var exports_editor = {};
|
|
32
|
+
__export(exports_editor, {
|
|
33
|
+
uninstall_editor_plugin_editor: () => uninstall_editor_plugin_editor,
|
|
34
|
+
ungroup_selection_editor: () => ungroup_selection_editor,
|
|
35
|
+
undo_action_editor: () => undo_action_editor,
|
|
36
|
+
snap_point_editor: () => snap_point_editor,
|
|
37
|
+
set_tool_editor: () => set_tool_editor,
|
|
38
|
+
selection_handles_for_elements_editor: () => selection_handles_for_elements_editor,
|
|
39
|
+
selection_handles_editor: () => selection_handles_editor,
|
|
40
|
+
render_editor: () => render_editor,
|
|
41
|
+
register_editor_tool_editor: () => register_editor_tool_editor,
|
|
42
|
+
redo_action_editor: () => redo_action_editor,
|
|
43
|
+
pointer_up_editor: () => pointer_up_editor,
|
|
44
|
+
pointer_move_editor: () => pointer_move_editor,
|
|
45
|
+
pointer_down_editor: () => pointer_down_editor,
|
|
46
|
+
paste_clipboard_editor: () => paste_clipboard_editor,
|
|
47
|
+
key_up_editor: () => key_up_editor,
|
|
48
|
+
key_down_editor: () => key_down_editor,
|
|
49
|
+
is_text_editing_editor: () => is_text_editing_editor,
|
|
50
|
+
install_editor_plugin_editor: () => install_editor_plugin_editor,
|
|
51
|
+
hover_editor: () => hover_editor,
|
|
52
|
+
hit_selection_handles_editor: () => hit_selection_handles_editor,
|
|
53
|
+
hit_selection_handle_editor: () => hit_selection_handle_editor,
|
|
54
|
+
group_selection_editor: () => group_selection_editor,
|
|
55
|
+
element_hit_at_point_editor: () => element_hit_at_point_editor,
|
|
56
|
+
editor_tool_text: () => editor_tool_text,
|
|
57
|
+
editor_tool_select: () => editor_tool_select,
|
|
58
|
+
editor_tool_rectangle: () => editor_tool_rectangle,
|
|
59
|
+
editor_tool_line: () => editor_tool_line,
|
|
60
|
+
editor_tool_image_place: () => editor_tool_image_place,
|
|
61
|
+
editor_tool_hand: () => editor_tool_hand,
|
|
62
|
+
editor_tool_frame: () => editor_tool_frame,
|
|
63
|
+
editor_tool_ellipse: () => editor_tool_ellipse,
|
|
64
|
+
editor_tool_draw: () => editor_tool_draw,
|
|
65
|
+
editor_tool_arrow: () => editor_tool_arrow,
|
|
66
|
+
double_click_editor: () => double_click_editor,
|
|
67
|
+
delete_selection_editor: () => delete_selection_editor,
|
|
68
|
+
cut_selection_editor: () => cut_selection_editor,
|
|
69
|
+
cursor_editor: () => cursor_editor,
|
|
70
|
+
create_grid_snapper_editor: () => create_grid_snapper_editor,
|
|
71
|
+
create_element_smart_snapper_editor: () => create_element_smart_snapper_editor,
|
|
72
|
+
create_editor: () => create_editor,
|
|
73
|
+
copy_selection_editor: () => copy_selection_editor,
|
|
74
|
+
contains_point_editor: () => contains_point_editor,
|
|
75
|
+
commit_text_edit_editor: () => commit_text_edit_editor,
|
|
76
|
+
cancel_editor: () => cancel_editor,
|
|
77
|
+
begin_text_edit_editor: () => begin_text_edit_editor
|
|
78
|
+
});
|
|
79
|
+
module.exports = __toCommonJS(exports_editor);
|
|
80
|
+
|
|
81
|
+
// editor/types.ts
|
|
82
|
+
var editor_tool_select = 0;
|
|
83
|
+
var editor_tool_hand = 1;
|
|
84
|
+
var editor_tool_draw = 2;
|
|
85
|
+
var editor_tool_rectangle = 3;
|
|
86
|
+
var editor_tool_ellipse = 4;
|
|
87
|
+
var editor_tool_line = 5;
|
|
88
|
+
var editor_tool_arrow = 6;
|
|
89
|
+
var editor_tool_text = 7;
|
|
90
|
+
var editor_tool_image_place = 8;
|
|
91
|
+
var editor_tool_frame = 9;
|
|
92
|
+
// document/element.ts
|
|
93
|
+
var element_type_stroke = 0;
|
|
94
|
+
var element_type_shape = 1;
|
|
95
|
+
var element_type_image = 2;
|
|
96
|
+
var element_type_text = 3;
|
|
97
|
+
var shape_type_rectangle = 0;
|
|
98
|
+
var shape_type_ellipse = 1;
|
|
99
|
+
var shape_type_line = 2;
|
|
100
|
+
var shape_type_arrow = 3;
|
|
101
|
+
var shape_type_frame = 4;
|
|
102
|
+
var text_align_left = 0;
|
|
103
|
+
var text_align_center = 1;
|
|
104
|
+
var text_align_right = 2;
|
|
105
|
+
function create_stroke_element(id, bounds, z_index, layer_id, points, pressure, color, width, group_id = null) {
|
|
106
|
+
return {
|
|
107
|
+
type: element_type_stroke,
|
|
108
|
+
id,
|
|
109
|
+
bounds: [bounds[0], bounds[1], bounds[2], bounds[3]],
|
|
110
|
+
z_index,
|
|
111
|
+
layer_id,
|
|
112
|
+
group_id,
|
|
113
|
+
points: points.map((p) => [p[0], p[1]]),
|
|
114
|
+
pressure: pressure === null ? null : [...pressure],
|
|
115
|
+
color: [color[0], color[1], color[2], color[3]],
|
|
116
|
+
width,
|
|
117
|
+
simplified_points: null,
|
|
118
|
+
spline: null
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function create_shape_element(id, bounds, z_index, layer_id, shape_type, fill_color, stroke_color, stroke_width, start_point, end_point, group_id = null) {
|
|
122
|
+
return {
|
|
123
|
+
type: element_type_shape,
|
|
124
|
+
id,
|
|
125
|
+
bounds: [bounds[0], bounds[1], bounds[2], bounds[3]],
|
|
126
|
+
z_index,
|
|
127
|
+
layer_id,
|
|
128
|
+
group_id,
|
|
129
|
+
shape_type,
|
|
130
|
+
fill_color: fill_color === null ? null : [fill_color[0], fill_color[1], fill_color[2], fill_color[3]],
|
|
131
|
+
stroke_color: stroke_color === null ? null : [stroke_color[0], stroke_color[1], stroke_color[2], stroke_color[3]],
|
|
132
|
+
stroke_width,
|
|
133
|
+
start_point: start_point === null ? null : [start_point[0], start_point[1]],
|
|
134
|
+
end_point: end_point === null ? null : [end_point[0], end_point[1]]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function create_image_element(id, bounds, z_index, layer_id, src, original_width, original_height, opacity, group_id = null, asset_id = null) {
|
|
138
|
+
return {
|
|
139
|
+
type: element_type_image,
|
|
140
|
+
id,
|
|
141
|
+
bounds: [bounds[0], bounds[1], bounds[2], bounds[3]],
|
|
142
|
+
z_index,
|
|
143
|
+
layer_id,
|
|
144
|
+
group_id,
|
|
145
|
+
asset_id,
|
|
146
|
+
src,
|
|
147
|
+
original_width,
|
|
148
|
+
original_height,
|
|
149
|
+
opacity,
|
|
150
|
+
loaded: false,
|
|
151
|
+
load_error: false,
|
|
152
|
+
bitmap: null
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function create_text_element(id, bounds, z_index, layer_id, content, font_family, font_size, color, align, group_id = null) {
|
|
156
|
+
return {
|
|
157
|
+
type: element_type_text,
|
|
158
|
+
id,
|
|
159
|
+
bounds: [bounds[0], bounds[1], bounds[2], bounds[3]],
|
|
160
|
+
z_index,
|
|
161
|
+
layer_id,
|
|
162
|
+
group_id,
|
|
163
|
+
content,
|
|
164
|
+
font_family,
|
|
165
|
+
font_size,
|
|
166
|
+
color: [color[0], color[1], color[2], color[3]],
|
|
167
|
+
align
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function clone_element(el) {
|
|
171
|
+
if (el.type === element_type_stroke) {
|
|
172
|
+
const stroke = el;
|
|
173
|
+
return create_stroke_element(stroke.id, stroke.bounds, stroke.z_index, stroke.layer_id, stroke.points, stroke.pressure, stroke.color, stroke.width, stroke.group_id);
|
|
174
|
+
} else if (el.type === element_type_shape) {
|
|
175
|
+
const shape = el;
|
|
176
|
+
return create_shape_element(shape.id, shape.bounds, shape.z_index, shape.layer_id, shape.shape_type, shape.fill_color, shape.stroke_color, shape.stroke_width, shape.start_point, shape.end_point, shape.group_id);
|
|
177
|
+
} else if (el.type === element_type_image) {
|
|
178
|
+
const image = el;
|
|
179
|
+
const cloned = create_image_element(image.id, image.bounds, image.z_index, image.layer_id, image.src, image.original_width, image.original_height, image.opacity, image.group_id, image.asset_id);
|
|
180
|
+
cloned.loaded = image.loaded;
|
|
181
|
+
cloned.load_error = image.load_error;
|
|
182
|
+
cloned.bitmap = image.bitmap;
|
|
183
|
+
return cloned;
|
|
184
|
+
} else {
|
|
185
|
+
const text = el;
|
|
186
|
+
return create_text_element(text.id, text.bounds, text.z_index, text.layer_id, text.content, text.font_family, text.font_size, text.color, text.align, text.group_id);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function id_of_element(el) {
|
|
190
|
+
return el.id;
|
|
191
|
+
}
|
|
192
|
+
function bounds_of_element(el) {
|
|
193
|
+
return [el.bounds[0], el.bounds[1], el.bounds[2], el.bounds[3]];
|
|
194
|
+
}
|
|
195
|
+
function z_index_of_element(el) {
|
|
196
|
+
return el.z_index;
|
|
197
|
+
}
|
|
198
|
+
function layer_id_of_element(el) {
|
|
199
|
+
return el.layer_id;
|
|
200
|
+
}
|
|
201
|
+
function type_of_element(el) {
|
|
202
|
+
return el.type;
|
|
203
|
+
}
|
|
204
|
+
function update_element_bounds(el, bounds) {
|
|
205
|
+
const cloned = clone_element(el);
|
|
206
|
+
const mutable = cloned;
|
|
207
|
+
mutable.bounds = [bounds[0], bounds[1], bounds[2], bounds[3]];
|
|
208
|
+
return cloned;
|
|
209
|
+
}
|
|
210
|
+
function update_element_z_index(el, z_index) {
|
|
211
|
+
const cloned = clone_element(el);
|
|
212
|
+
const mutable = cloned;
|
|
213
|
+
mutable.z_index = z_index;
|
|
214
|
+
return cloned;
|
|
215
|
+
}
|
|
216
|
+
function update_element_layer_id(el, layer_id) {
|
|
217
|
+
const cloned = clone_element(el);
|
|
218
|
+
const mutable = cloned;
|
|
219
|
+
mutable.layer_id = layer_id;
|
|
220
|
+
return cloned;
|
|
221
|
+
}
|
|
222
|
+
function update_stroke_points(el, points) {
|
|
223
|
+
return {
|
|
224
|
+
...el,
|
|
225
|
+
points: points.map((p) => [p[0], p[1]]),
|
|
226
|
+
simplified_points: null,
|
|
227
|
+
spline: null
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function update_stroke_simplified_points(el, simplified) {
|
|
231
|
+
return {
|
|
232
|
+
...el,
|
|
233
|
+
simplified_points: simplified === null ? null : simplified.map((p) => [p[0], p[1]])
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function update_stroke_spline(el, spline_data) {
|
|
237
|
+
return {
|
|
238
|
+
...el,
|
|
239
|
+
spline: spline_data
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function update_image_loaded(el, loaded, bitmap, load_error = false) {
|
|
243
|
+
return {
|
|
244
|
+
...el,
|
|
245
|
+
loaded,
|
|
246
|
+
load_error,
|
|
247
|
+
bitmap
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function update_image_asset_id(el, asset_id) {
|
|
251
|
+
return {
|
|
252
|
+
...el,
|
|
253
|
+
asset_id
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function update_text_content(el, content) {
|
|
257
|
+
return {
|
|
258
|
+
...el,
|
|
259
|
+
content
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function equals_element_identity(a, b) {
|
|
263
|
+
if (a.type !== b.type || a.id !== b.id) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
if (a.z_index !== b.z_index || a.layer_id !== b.layer_id) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
if (a.group_id !== b.group_id) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
if (a.bounds[0] !== b.bounds[0] || a.bounds[1] !== b.bounds[1] || a.bounds[2] !== b.bounds[2] || a.bounds[3] !== b.bounds[3]) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// math/rectangle.ts
|
|
279
|
+
function create_rectangle(x = 0, y = 0, width = 0, height = 0) {
|
|
280
|
+
return [x, y, width, height];
|
|
281
|
+
}
|
|
282
|
+
function clone_rectangle(r) {
|
|
283
|
+
return [r[0], r[1], r[2], r[3]];
|
|
284
|
+
}
|
|
285
|
+
function from_center_rectangle(center, width, height, out) {
|
|
286
|
+
out[0] = center[0] - width / 2;
|
|
287
|
+
out[1] = center[1] - height / 2;
|
|
288
|
+
out[2] = width;
|
|
289
|
+
out[3] = height;
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
function top_of_rectangle(r) {
|
|
293
|
+
return r[1] + r[3];
|
|
294
|
+
}
|
|
295
|
+
function left_of_rectangle(r) {
|
|
296
|
+
return r[0];
|
|
297
|
+
}
|
|
298
|
+
function right_of_rectangle(r) {
|
|
299
|
+
return r[0] + r[2];
|
|
300
|
+
}
|
|
301
|
+
function bottom_of_rectangle(r) {
|
|
302
|
+
return r[1];
|
|
303
|
+
}
|
|
304
|
+
function center_of_rectangle(r, out) {
|
|
305
|
+
out[0] = r[0] + r[2] / 2;
|
|
306
|
+
out[1] = r[1] + r[3] / 2;
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
function width_of_rectangle(r) {
|
|
310
|
+
return r[2];
|
|
311
|
+
}
|
|
312
|
+
function height_of_rectangle(r) {
|
|
313
|
+
return r[3];
|
|
314
|
+
}
|
|
315
|
+
function area_of_rectangle(r) {
|
|
316
|
+
return r[2] * r[3];
|
|
317
|
+
}
|
|
318
|
+
function set_rectangle(x, y, width, height, out) {
|
|
319
|
+
out[0] = x;
|
|
320
|
+
out[1] = y;
|
|
321
|
+
out[2] = width;
|
|
322
|
+
out[3] = height;
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
function translate_rectangle(r, dx, dy, out) {
|
|
326
|
+
out[0] = r[0] + dx;
|
|
327
|
+
out[1] = r[1] + dy;
|
|
328
|
+
out[2] = r[2];
|
|
329
|
+
out[3] = r[3];
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
function scale_rectangle(r, scale, out) {
|
|
333
|
+
out[0] = r[0] * scale;
|
|
334
|
+
out[1] = r[1] * scale;
|
|
335
|
+
out[2] = r[2] * scale;
|
|
336
|
+
out[3] = r[3] * scale;
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
339
|
+
function contains_point_rectangle(r, x, y) {
|
|
340
|
+
return x >= r[0] && x <= r[0] + r[2] && y >= r[1] && y <= r[1] + r[3];
|
|
341
|
+
}
|
|
342
|
+
function contains_rectangle(r1, r2) {
|
|
343
|
+
return r2[0] >= r1[0] && r2[1] >= r1[1] && r2[0] + r2[2] <= r1[0] + r1[2] && r2[1] + r2[3] <= r1[1] + r1[3];
|
|
344
|
+
}
|
|
345
|
+
function intersects_rectangle(r1, r2) {
|
|
346
|
+
return r1[0] < r2[0] + r2[2] && r1[0] + r1[2] > r2[0] && r1[1] < r2[1] + r2[3] && r1[1] + r1[3] > r2[1];
|
|
347
|
+
}
|
|
348
|
+
function intersection_rectangle(r1, r2, out) {
|
|
349
|
+
const x1 = Math.max(r1[0], r2[0]);
|
|
350
|
+
const y1 = Math.max(r1[1], r2[1]);
|
|
351
|
+
const x2 = Math.min(r1[0] + r1[2], r2[0] + r2[2]);
|
|
352
|
+
const y2 = Math.min(r1[1] + r1[3], r2[1] + r2[3]);
|
|
353
|
+
if (x2 <= x1 || y2 <= y1) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
out[0] = x1;
|
|
357
|
+
out[1] = y1;
|
|
358
|
+
out[2] = x2 - x1;
|
|
359
|
+
out[3] = y2 - y1;
|
|
360
|
+
return out;
|
|
361
|
+
}
|
|
362
|
+
function union_rectangle(r1, r2, out) {
|
|
363
|
+
const x1 = Math.min(r1[0], r2[0]);
|
|
364
|
+
const y1 = Math.min(r1[1], r2[1]);
|
|
365
|
+
const x2 = Math.max(r1[0] + r1[2], r2[0] + r2[2]);
|
|
366
|
+
const y2 = Math.max(r1[1] + r1[3], r2[1] + r2[3]);
|
|
367
|
+
out[0] = x1;
|
|
368
|
+
out[1] = y1;
|
|
369
|
+
out[2] = x2 - x1;
|
|
370
|
+
out[3] = y2 - y1;
|
|
371
|
+
return out;
|
|
372
|
+
}
|
|
373
|
+
function expand_rectangle(r, amount, out) {
|
|
374
|
+
out[0] = r[0] - amount;
|
|
375
|
+
out[1] = r[1] - amount;
|
|
376
|
+
out[2] = r[2] + amount * 2;
|
|
377
|
+
out[3] = r[3] + amount * 2;
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
380
|
+
function is_empty_rectangle(r) {
|
|
381
|
+
return r[2] <= 0 || r[3] <= 0;
|
|
382
|
+
}
|
|
383
|
+
function equals_rectangle(r1, r2) {
|
|
384
|
+
return r1[0] === r2[0] && r1[1] === r2[1] && r1[2] === r2[2] && r1[3] === r2[3];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// math/quadtree.ts
|
|
388
|
+
function create_quadtree_point(x, y, data = null) {
|
|
389
|
+
return [x, y, data];
|
|
390
|
+
}
|
|
391
|
+
function x_of_quadtree_point(point) {
|
|
392
|
+
return point[0];
|
|
393
|
+
}
|
|
394
|
+
function y_of_quadtree_point(point) {
|
|
395
|
+
return point[1];
|
|
396
|
+
}
|
|
397
|
+
function data_of_quadtree_point(point) {
|
|
398
|
+
return point[2];
|
|
399
|
+
}
|
|
400
|
+
function create_quadtree_node(boundary, capacity = 4, depth = 0) {
|
|
401
|
+
return {
|
|
402
|
+
boundary: [boundary[0], boundary[1], boundary[2], boundary[3]],
|
|
403
|
+
points: [],
|
|
404
|
+
children: null,
|
|
405
|
+
capacity: Math.max(1, capacity),
|
|
406
|
+
depth: Math.max(0, depth)
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function create_quadtree(boundary, capacity = 4, max_depth = 8) {
|
|
410
|
+
const normalized_capacity = Math.max(1, capacity);
|
|
411
|
+
const normalized_max_depth = Math.max(1, max_depth);
|
|
412
|
+
return {
|
|
413
|
+
root: create_quadtree_node(boundary, normalized_capacity, 0),
|
|
414
|
+
capacity: normalized_capacity,
|
|
415
|
+
max_depth: normalized_max_depth
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function is_leaf_quadtree_node(node) {
|
|
419
|
+
return node.children === null;
|
|
420
|
+
}
|
|
421
|
+
function point_count_quadtree_node(node) {
|
|
422
|
+
if (is_leaf_quadtree_node(node)) {
|
|
423
|
+
return node.points.length;
|
|
424
|
+
}
|
|
425
|
+
let total = 0;
|
|
426
|
+
if (node.children !== null) {
|
|
427
|
+
for (let i = 0;i < 4; i = i + 1) {
|
|
428
|
+
const child = node.children[i];
|
|
429
|
+
if (child) {
|
|
430
|
+
total = total + point_count_quadtree_node(child);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return total;
|
|
435
|
+
}
|
|
436
|
+
function subdivide_quadtree_node(node, max_depth) {
|
|
437
|
+
if (node.children !== null) {
|
|
438
|
+
return node;
|
|
439
|
+
}
|
|
440
|
+
const boundary = node.boundary;
|
|
441
|
+
const half_width = boundary[2] / 2;
|
|
442
|
+
const half_height = boundary[3] / 2;
|
|
443
|
+
const x = boundary[0];
|
|
444
|
+
const y = boundary[1];
|
|
445
|
+
const next_depth = node.depth + 1;
|
|
446
|
+
const new_children = [
|
|
447
|
+
create_quadtree_node(create_rectangle(x, y, half_width, half_height), node.capacity, next_depth),
|
|
448
|
+
create_quadtree_node(create_rectangle(x + half_width, y, half_width, half_height), node.capacity, next_depth),
|
|
449
|
+
create_quadtree_node(create_rectangle(x, y + half_height, half_width, half_height), node.capacity, next_depth),
|
|
450
|
+
create_quadtree_node(create_rectangle(x + half_width, y + half_height, half_width, half_height), node.capacity, next_depth)
|
|
451
|
+
];
|
|
452
|
+
let updated_children = [...new_children];
|
|
453
|
+
for (let i = 0;i < node.points.length; i = i + 1) {
|
|
454
|
+
const point = node.points[i];
|
|
455
|
+
if (point) {
|
|
456
|
+
for (let j = 0;j < 4; j = j + 1) {
|
|
457
|
+
const child = updated_children[j];
|
|
458
|
+
if (child) {
|
|
459
|
+
const child_result = insert_quadtree_node(child, point, max_depth);
|
|
460
|
+
if (child_result !== null) {
|
|
461
|
+
updated_children[j] = child_result;
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
boundary: node.boundary,
|
|
470
|
+
points: [],
|
|
471
|
+
children: updated_children,
|
|
472
|
+
capacity: node.capacity,
|
|
473
|
+
depth: node.depth
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function insert_quadtree_node(node, point, max_depth) {
|
|
477
|
+
if (!contains_point_rectangle(node.boundary, point[0], point[1])) {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
if (is_leaf_quadtree_node(node)) {
|
|
481
|
+
if (node.points.length < node.capacity || node.depth >= max_depth) {
|
|
482
|
+
const new_points = [...node.points, [point[0], point[1], point[2]]];
|
|
483
|
+
return {
|
|
484
|
+
boundary: node.boundary,
|
|
485
|
+
points: new_points,
|
|
486
|
+
children: null,
|
|
487
|
+
capacity: node.capacity,
|
|
488
|
+
depth: node.depth
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
const subdivided = subdivide_quadtree_node(node, max_depth);
|
|
492
|
+
return insert_quadtree_node(subdivided, point, max_depth);
|
|
493
|
+
}
|
|
494
|
+
if (node.children !== null && node.children.length === 4) {
|
|
495
|
+
const new_children = [...node.children];
|
|
496
|
+
for (let i = 0;i < 4; i = i + 1) {
|
|
497
|
+
const child = node.children[i];
|
|
498
|
+
if (child) {
|
|
499
|
+
const child_result = insert_quadtree_node(child, point, max_depth);
|
|
500
|
+
if (child_result !== null) {
|
|
501
|
+
new_children[i] = child_result;
|
|
502
|
+
return {
|
|
503
|
+
boundary: node.boundary,
|
|
504
|
+
points: node.points,
|
|
505
|
+
children: new_children,
|
|
506
|
+
capacity: node.capacity,
|
|
507
|
+
depth: node.depth
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
function insert_quadtree(tree, point) {
|
|
516
|
+
const new_root = insert_quadtree_node(tree.root, point, tree.max_depth);
|
|
517
|
+
if (new_root !== null) {
|
|
518
|
+
return {
|
|
519
|
+
root: new_root,
|
|
520
|
+
capacity: tree.capacity,
|
|
521
|
+
max_depth: tree.max_depth
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
return tree;
|
|
525
|
+
}
|
|
526
|
+
function query_range_quadtree_node(node, range, out_results) {
|
|
527
|
+
if (!intersects_rectangle(node.boundary, range)) {
|
|
528
|
+
return out_results;
|
|
529
|
+
}
|
|
530
|
+
if (is_leaf_quadtree_node(node)) {
|
|
531
|
+
for (let i = 0;i < node.points.length; i = i + 1) {
|
|
532
|
+
const point = node.points[i];
|
|
533
|
+
if (point && contains_point_rectangle(range, point[0], point[1])) {
|
|
534
|
+
out_results.push([point[0], point[1], point[2]]);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return out_results;
|
|
538
|
+
}
|
|
539
|
+
if (node.children !== null && node.children.length === 4) {
|
|
540
|
+
for (let i = 0;i < 4; i = i + 1) {
|
|
541
|
+
const child = node.children[i];
|
|
542
|
+
if (child) {
|
|
543
|
+
query_range_quadtree_node(child, range, out_results);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return out_results;
|
|
548
|
+
}
|
|
549
|
+
function query_range_quadtree(tree, range, out_results) {
|
|
550
|
+
return query_range_quadtree_node(tree.root, range, out_results);
|
|
551
|
+
}
|
|
552
|
+
function query_radius_quadtree_node(node, center_x, center_y, radius, out_results) {
|
|
553
|
+
const radius_squared = radius * radius;
|
|
554
|
+
const query_bounds = create_rectangle(center_x - radius, center_y - radius, radius * 2, radius * 2);
|
|
555
|
+
const candidates = [];
|
|
556
|
+
query_range_quadtree_node(node, query_bounds, candidates);
|
|
557
|
+
for (let i = 0;i < candidates.length; i = i + 1) {
|
|
558
|
+
const point = candidates[i];
|
|
559
|
+
if (point) {
|
|
560
|
+
const dx = point[0] - center_x;
|
|
561
|
+
const dy = point[1] - center_y;
|
|
562
|
+
const distance_squared = dx * dx + dy * dy;
|
|
563
|
+
if (distance_squared <= radius_squared) {
|
|
564
|
+
out_results.push([point[0], point[1], point[2]]);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return out_results;
|
|
569
|
+
}
|
|
570
|
+
function query_radius_quadtree(tree, center_x, center_y, radius, out_results) {
|
|
571
|
+
return query_radius_quadtree_node(tree.root, center_x, center_y, radius, out_results);
|
|
572
|
+
}
|
|
573
|
+
function find_nearest_quadtree(tree, x, y, max_distance = Number.POSITIVE_INFINITY) {
|
|
574
|
+
return find_nearest_quadtree_node(tree.root, x, y, max_distance);
|
|
575
|
+
}
|
|
576
|
+
function find_nearest_quadtree_node(node, x, y, max_distance = Number.POSITIVE_INFINITY) {
|
|
577
|
+
let best_point = null;
|
|
578
|
+
let best_distance_squared = max_distance * max_distance;
|
|
579
|
+
function search_node_recursive(search_node) {
|
|
580
|
+
if (is_leaf_quadtree_node(search_node)) {
|
|
581
|
+
for (let i = 0;i < search_node.points.length; i = i + 1) {
|
|
582
|
+
const point = search_node.points[i];
|
|
583
|
+
if (point) {
|
|
584
|
+
const dx = point[0] - x;
|
|
585
|
+
const dy = point[1] - y;
|
|
586
|
+
const distance_squared = dx * dx + dy * dy;
|
|
587
|
+
if (distance_squared < best_distance_squared) {
|
|
588
|
+
best_distance_squared = distance_squared;
|
|
589
|
+
best_point = [point[0], point[1], point[2]];
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
} else if (search_node.children !== null && search_node.children.length === 4) {
|
|
594
|
+
const child_distances = [];
|
|
595
|
+
for (let i = 0;i < 4; i = i + 1) {
|
|
596
|
+
const child = search_node.children[i];
|
|
597
|
+
if (child) {
|
|
598
|
+
const boundary = child.boundary;
|
|
599
|
+
const closest_x = Math.max(boundary[0], Math.min(x, boundary[0] + boundary[2]));
|
|
600
|
+
const closest_y = Math.max(boundary[1], Math.min(y, boundary[1] + boundary[3]));
|
|
601
|
+
const dx = x - closest_x;
|
|
602
|
+
const dy = y - closest_y;
|
|
603
|
+
const distance = dx * dx + dy * dy;
|
|
604
|
+
if (distance < best_distance_squared) {
|
|
605
|
+
child_distances.push({ node: child, distance });
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
child_distances.sort((a, b) => a.distance - b.distance);
|
|
610
|
+
for (let i = 0;i < child_distances.length; i = i + 1) {
|
|
611
|
+
const child_info = child_distances[i];
|
|
612
|
+
if (child_info && child_info.distance < best_distance_squared) {
|
|
613
|
+
search_node_recursive(child_info.node);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
search_node_recursive(node);
|
|
619
|
+
return best_point;
|
|
620
|
+
}
|
|
621
|
+
function remove_quadtree_node(node, point) {
|
|
622
|
+
if (!contains_point_rectangle(node.boundary, point[0], point[1])) {
|
|
623
|
+
return node;
|
|
624
|
+
}
|
|
625
|
+
if (is_leaf_quadtree_node(node)) {
|
|
626
|
+
const new_points = [];
|
|
627
|
+
let removed = false;
|
|
628
|
+
for (let i = 0;i < node.points.length; i = i + 1) {
|
|
629
|
+
const existing = node.points[i];
|
|
630
|
+
if (existing && existing[0] === point[0] && existing[1] === point[1] && !removed) {
|
|
631
|
+
removed = true;
|
|
632
|
+
} else if (existing) {
|
|
633
|
+
new_points.push([existing[0], existing[1], existing[2]]);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return {
|
|
637
|
+
boundary: node.boundary,
|
|
638
|
+
points: new_points,
|
|
639
|
+
children: null,
|
|
640
|
+
capacity: node.capacity,
|
|
641
|
+
depth: node.depth
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
if (node.children !== null && node.children.length === 4) {
|
|
645
|
+
const new_children = [...node.children];
|
|
646
|
+
let changed = false;
|
|
647
|
+
for (let i = 0;i < 4; i = i + 1) {
|
|
648
|
+
const child = node.children[i];
|
|
649
|
+
if (child) {
|
|
650
|
+
const new_child = remove_quadtree_node(child, point);
|
|
651
|
+
if (new_child !== child) {
|
|
652
|
+
new_children[i] = new_child;
|
|
653
|
+
changed = true;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (changed) {
|
|
658
|
+
let total_points = 0;
|
|
659
|
+
for (let i = 0;i < 4; i = i + 1) {
|
|
660
|
+
const child = new_children[i];
|
|
661
|
+
if (child) {
|
|
662
|
+
total_points = total_points + point_count_quadtree_node(child);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (total_points <= node.capacity) {
|
|
666
|
+
const all_points = [];
|
|
667
|
+
collect_all_points_quadtree_node({ ...node, children: new_children }, all_points);
|
|
668
|
+
return {
|
|
669
|
+
boundary: node.boundary,
|
|
670
|
+
points: all_points,
|
|
671
|
+
children: null,
|
|
672
|
+
capacity: node.capacity,
|
|
673
|
+
depth: node.depth
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
boundary: node.boundary,
|
|
678
|
+
points: node.points,
|
|
679
|
+
children: new_children,
|
|
680
|
+
capacity: node.capacity,
|
|
681
|
+
depth: node.depth
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return node;
|
|
686
|
+
}
|
|
687
|
+
function remove_quadtree(tree, point) {
|
|
688
|
+
const new_root = remove_quadtree_node(tree.root, point);
|
|
689
|
+
return {
|
|
690
|
+
root: new_root,
|
|
691
|
+
capacity: tree.capacity,
|
|
692
|
+
max_depth: tree.max_depth
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
function collect_all_points_quadtree_node(node, out_points) {
|
|
696
|
+
if (is_leaf_quadtree_node(node)) {
|
|
697
|
+
for (let i = 0;i < node.points.length; i = i + 1) {
|
|
698
|
+
const point = node.points[i];
|
|
699
|
+
if (point) {
|
|
700
|
+
out_points.push([point[0], point[1], point[2]]);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
} else if (node.children !== null && node.children.length === 4) {
|
|
704
|
+
for (let i = 0;i < 4; i = i + 1) {
|
|
705
|
+
const child = node.children[i];
|
|
706
|
+
if (child) {
|
|
707
|
+
collect_all_points_quadtree_node(child, out_points);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return out_points;
|
|
712
|
+
}
|
|
713
|
+
function clear_quadtree(tree) {
|
|
714
|
+
return {
|
|
715
|
+
root: create_quadtree_node(tree.root.boundary, tree.capacity, 0),
|
|
716
|
+
capacity: tree.capacity,
|
|
717
|
+
max_depth: tree.max_depth
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function point_count_quadtree(tree) {
|
|
721
|
+
return point_count_quadtree_node(tree.root);
|
|
722
|
+
}
|
|
723
|
+
function depth_of_quadtree_node(node) {
|
|
724
|
+
if (is_leaf_quadtree_node(node)) {
|
|
725
|
+
return node.depth;
|
|
726
|
+
}
|
|
727
|
+
let max_depth = node.depth;
|
|
728
|
+
if (node.children !== null && node.children.length === 4) {
|
|
729
|
+
for (let i = 0;i < 4; i = i + 1) {
|
|
730
|
+
const child = node.children[i];
|
|
731
|
+
if (child) {
|
|
732
|
+
const child_depth = depth_of_quadtree_node(child);
|
|
733
|
+
if (child_depth > max_depth) {
|
|
734
|
+
max_depth = child_depth;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return max_depth;
|
|
740
|
+
}
|
|
741
|
+
function depth_of_quadtree(tree) {
|
|
742
|
+
return depth_of_quadtree_node(tree.root);
|
|
743
|
+
}
|
|
744
|
+
function is_empty_quadtree(tree) {
|
|
745
|
+
return point_count_quadtree(tree) === 0;
|
|
746
|
+
}
|
|
747
|
+
function bounds_of_quadtree(tree, out_bounds) {
|
|
748
|
+
out_bounds[0] = tree.root.boundary[0];
|
|
749
|
+
out_bounds[1] = tree.root.boundary[1];
|
|
750
|
+
out_bounds[2] = tree.root.boundary[2];
|
|
751
|
+
out_bounds[3] = tree.root.boundary[3];
|
|
752
|
+
return out_bounds;
|
|
753
|
+
}
|
|
754
|
+
function find_k_nearest_quadtree(tree, x, y, k, max_distance, out_results) {
|
|
755
|
+
if (point_count_quadtree(tree) <= k * 2) {
|
|
756
|
+
const all_points = [];
|
|
757
|
+
collect_all_points_quadtree_node(tree.root, all_points);
|
|
758
|
+
const distances2 = [];
|
|
759
|
+
for (let i = 0;i < all_points.length; i = i + 1) {
|
|
760
|
+
const point = all_points[i];
|
|
761
|
+
if (point) {
|
|
762
|
+
const dx = point[0] - x;
|
|
763
|
+
const dy = point[1] - y;
|
|
764
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
765
|
+
if (distance <= max_distance) {
|
|
766
|
+
distances2.push({ point, distance });
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
distances2.sort((a, b) => a.distance - b.distance);
|
|
771
|
+
const limit2 = Math.min(k, distances2.length);
|
|
772
|
+
for (let i = 0;i < limit2; i = i + 1) {
|
|
773
|
+
const item = distances2[i];
|
|
774
|
+
if (item && item.point) {
|
|
775
|
+
out_results.push([item.point[0], item.point[1], item.point[2]]);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return out_results;
|
|
779
|
+
}
|
|
780
|
+
let current_radius = 100;
|
|
781
|
+
const bounds = [0, 0, 0, 0];
|
|
782
|
+
bounds_of_quadtree(tree, bounds);
|
|
783
|
+
const max_possible_radius = Math.sqrt(bounds[2] * bounds[2] + bounds[3] * bounds[3]);
|
|
784
|
+
while (current_radius <= max_possible_radius) {
|
|
785
|
+
const candidates = [];
|
|
786
|
+
query_radius_quadtree(tree, x, y, Math.min(current_radius, max_distance), candidates);
|
|
787
|
+
if (candidates.length >= k) {
|
|
788
|
+
const distances2 = [];
|
|
789
|
+
for (let i = 0;i < candidates.length; i = i + 1) {
|
|
790
|
+
const point = candidates[i];
|
|
791
|
+
if (point) {
|
|
792
|
+
const dx = point[0] - x;
|
|
793
|
+
const dy = point[1] - y;
|
|
794
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
795
|
+
if (distance <= max_distance) {
|
|
796
|
+
distances2.push({ point, distance });
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
distances2.sort((a, b) => a.distance - b.distance);
|
|
801
|
+
const limit2 = Math.min(k, distances2.length);
|
|
802
|
+
for (let i = 0;i < limit2; i = i + 1) {
|
|
803
|
+
const item = distances2[i];
|
|
804
|
+
if (item && item.point) {
|
|
805
|
+
out_results.push([item.point[0], item.point[1], item.point[2]]);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return out_results;
|
|
809
|
+
}
|
|
810
|
+
current_radius = current_radius * 2;
|
|
811
|
+
}
|
|
812
|
+
const final_candidates = [];
|
|
813
|
+
query_radius_quadtree(tree, x, y, max_distance, final_candidates);
|
|
814
|
+
const distances = [];
|
|
815
|
+
for (let i = 0;i < final_candidates.length; i = i + 1) {
|
|
816
|
+
const point = final_candidates[i];
|
|
817
|
+
if (point) {
|
|
818
|
+
const dx = point[0] - x;
|
|
819
|
+
const dy = point[1] - y;
|
|
820
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
821
|
+
distances.push({ point, distance });
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
distances.sort((a, b) => a.distance - b.distance);
|
|
825
|
+
const limit = Math.min(k, distances.length);
|
|
826
|
+
for (let i = 0;i < limit; i = i + 1) {
|
|
827
|
+
const item = distances[i];
|
|
828
|
+
if (item && item.point) {
|
|
829
|
+
out_results.push([item.point[0], item.point[1], item.point[2]]);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return out_results;
|
|
833
|
+
}
|
|
834
|
+
function detect_collisions_quadtree(tree, point, radius, out_collisions) {
|
|
835
|
+
return query_radius_quadtree(tree, point[0], point[1], radius, out_collisions);
|
|
836
|
+
}
|
|
837
|
+
function detect_collisions_rectangle_quadtree(tree, rect, out_collisions) {
|
|
838
|
+
return query_range_quadtree(tree, rect, out_collisions);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// document/query.ts
|
|
842
|
+
function elements_in_bounds_document(doc, bounds) {
|
|
843
|
+
const results = [];
|
|
844
|
+
for (const el of doc.elements.values()) {
|
|
845
|
+
const el_bounds = el.bounds;
|
|
846
|
+
if (el_bounds[0] < bounds[0] + bounds[2] && el_bounds[0] + el_bounds[2] > bounds[0] && el_bounds[1] < bounds[1] + bounds[3] && el_bounds[1] + el_bounds[3] > bounds[1]) {
|
|
847
|
+
results.push(el);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return results;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// engine/shared.ts
|
|
854
|
+
var default_background = [1, 1, 1, 1];
|
|
855
|
+
var default_element_fill = [0.95, 0.97, 1, 1];
|
|
856
|
+
var default_element_stroke = [0.18, 0.22, 0.31, 1];
|
|
857
|
+
var min_zoom_engine = 0.0001;
|
|
858
|
+
var max_zoom_engine = 1e6;
|
|
859
|
+
var scratch_camera = [0, 0, 1, 0];
|
|
860
|
+
var scratch_frustum = [0, 0, 0, 0, 0, 0];
|
|
861
|
+
var scratch_transform = [1, 0, 0, 1, 0, 0];
|
|
862
|
+
var scratch_world = [0, 0];
|
|
863
|
+
var scratch_screen = [0, 0];
|
|
864
|
+
|
|
865
|
+
// camera/projection.ts
|
|
866
|
+
function world_to_screen_camera(c, v, world_pos, out) {
|
|
867
|
+
const dx = world_pos[0] - c[0];
|
|
868
|
+
const dy = world_pos[1] - c[1];
|
|
869
|
+
const cos_r = Math.cos(c[3]);
|
|
870
|
+
const sin_r = Math.sin(c[3]);
|
|
871
|
+
const rotated_x = dx * cos_r - dy * sin_r;
|
|
872
|
+
const rotated_y = dx * sin_r + dy * cos_r;
|
|
873
|
+
const screen_x = rotated_x * c[2] + v[0] / 2;
|
|
874
|
+
const screen_y = rotated_y * c[2] + v[1] / 2;
|
|
875
|
+
out[0] = screen_x;
|
|
876
|
+
out[1] = screen_y;
|
|
877
|
+
return out;
|
|
878
|
+
}
|
|
879
|
+
function screen_to_world_camera(c, v, screen_pos, out) {
|
|
880
|
+
if (c[2] <= 0) {
|
|
881
|
+
out[0] = c[0];
|
|
882
|
+
out[1] = c[1];
|
|
883
|
+
return out;
|
|
884
|
+
}
|
|
885
|
+
const dx = screen_pos[0] - v[0] / 2;
|
|
886
|
+
const dy = screen_pos[1] - v[1] / 2;
|
|
887
|
+
const cos_r = Math.cos(-c[3]);
|
|
888
|
+
const sin_r = Math.sin(-c[3]);
|
|
889
|
+
const scaled_x = dx / c[2];
|
|
890
|
+
const scaled_y = dy / c[2];
|
|
891
|
+
const rotated_x = scaled_x * cos_r - scaled_y * sin_r;
|
|
892
|
+
const rotated_y = scaled_x * sin_r + scaled_y * cos_r;
|
|
893
|
+
out[0] = rotated_x + c[0];
|
|
894
|
+
out[1] = rotated_y + c[1];
|
|
895
|
+
return out;
|
|
896
|
+
}
|
|
897
|
+
function view_transform_camera(c, out) {
|
|
898
|
+
const cos_r = Math.cos(c[3]);
|
|
899
|
+
const sin_r = Math.sin(c[3]);
|
|
900
|
+
out[0] = cos_r * c[2];
|
|
901
|
+
out[1] = sin_r * c[2];
|
|
902
|
+
out[2] = -sin_r * c[2];
|
|
903
|
+
out[3] = cos_r * c[2];
|
|
904
|
+
out[4] = -c[0] * cos_r * c[2] + c[1] * sin_r * c[2];
|
|
905
|
+
out[5] = -c[0] * sin_r * c[2] - c[1] * cos_r * c[2];
|
|
906
|
+
return out;
|
|
907
|
+
}
|
|
908
|
+
function inverse_view_transform_camera(c, out) {
|
|
909
|
+
const cos_r = Math.cos(c[3]);
|
|
910
|
+
const sin_r = Math.sin(c[3]);
|
|
911
|
+
const safe_zoom = c[2] <= 0 ? 0.0001 : c[2];
|
|
912
|
+
const inverse_zoom = 1 / safe_zoom;
|
|
913
|
+
out[0] = cos_r * inverse_zoom;
|
|
914
|
+
out[1] = -sin_r * inverse_zoom;
|
|
915
|
+
out[2] = sin_r * inverse_zoom;
|
|
916
|
+
out[3] = cos_r * inverse_zoom;
|
|
917
|
+
out[4] = c[0];
|
|
918
|
+
out[5] = c[1];
|
|
919
|
+
return out;
|
|
920
|
+
}
|
|
921
|
+
function camera_frustum(c, v, out) {
|
|
922
|
+
if (c[2] <= 0) {
|
|
923
|
+
out[0] = c[0];
|
|
924
|
+
out[1] = c[0];
|
|
925
|
+
out[2] = c[1];
|
|
926
|
+
out[3] = c[1];
|
|
927
|
+
out[4] = 0;
|
|
928
|
+
out[5] = Number.MAX_VALUE;
|
|
929
|
+
return out;
|
|
930
|
+
}
|
|
931
|
+
const half_width = v[0] / (2 * c[2]);
|
|
932
|
+
const half_height = v[1] / (2 * c[2]);
|
|
933
|
+
if (c[3] === 0) {
|
|
934
|
+
out[0] = c[0] - half_width;
|
|
935
|
+
out[1] = c[0] + half_width;
|
|
936
|
+
out[2] = c[1] + half_height;
|
|
937
|
+
out[3] = c[1] - half_height;
|
|
938
|
+
out[4] = 0;
|
|
939
|
+
out[5] = Number.MAX_VALUE;
|
|
940
|
+
return out;
|
|
941
|
+
}
|
|
942
|
+
const cos_r = Math.abs(Math.cos(c[3]));
|
|
943
|
+
const sin_r = Math.abs(Math.sin(c[3]));
|
|
944
|
+
const x_extent = half_width * cos_r + half_height * sin_r;
|
|
945
|
+
const y_extent = half_width * sin_r + half_height * cos_r;
|
|
946
|
+
out[0] = c[0] - x_extent;
|
|
947
|
+
out[1] = c[0] + x_extent;
|
|
948
|
+
out[2] = c[1] + y_extent;
|
|
949
|
+
out[3] = c[1] - y_extent;
|
|
950
|
+
out[4] = 0;
|
|
951
|
+
out[5] = Number.MAX_VALUE;
|
|
952
|
+
return out;
|
|
953
|
+
}
|
|
954
|
+
function fit_camera_to_bounds(bounds, v, margin = 0.1, out) {
|
|
955
|
+
const bounds_width = bounds[2];
|
|
956
|
+
const bounds_height = bounds[3];
|
|
957
|
+
if (bounds_width <= 0 || bounds_height <= 0) {
|
|
958
|
+
out[0] = bounds[0];
|
|
959
|
+
out[1] = bounds[1];
|
|
960
|
+
out[2] = 1;
|
|
961
|
+
out[3] = 0;
|
|
962
|
+
return out;
|
|
963
|
+
}
|
|
964
|
+
const viewport_width = v[0] * (1 - margin * 2);
|
|
965
|
+
const viewport_height = v[1] * (1 - margin * 2);
|
|
966
|
+
if (viewport_width <= 0 || viewport_height <= 0) {
|
|
967
|
+
out[0] = bounds[0] + bounds_width / 2;
|
|
968
|
+
out[1] = bounds[1] + bounds_height / 2;
|
|
969
|
+
out[2] = 1;
|
|
970
|
+
out[3] = 0;
|
|
971
|
+
return out;
|
|
972
|
+
}
|
|
973
|
+
const zoom_x = viewport_width / bounds_width;
|
|
974
|
+
const zoom_y = viewport_height / bounds_height;
|
|
975
|
+
const zoom = Math.min(zoom_x, zoom_y);
|
|
976
|
+
const center_x = bounds[0] + bounds_width / 2;
|
|
977
|
+
const center_y = bounds[1] + bounds_height / 2;
|
|
978
|
+
out[0] = center_x;
|
|
979
|
+
out[1] = center_y;
|
|
980
|
+
out[2] = zoom;
|
|
981
|
+
out[3] = 0;
|
|
982
|
+
return out;
|
|
983
|
+
}
|
|
984
|
+
function contains_point_camera_frustum(c, v, world_x, world_y) {
|
|
985
|
+
const dx = world_x - c[0];
|
|
986
|
+
const dy = world_y - c[1];
|
|
987
|
+
const cos_r = Math.cos(-c[3]);
|
|
988
|
+
const sin_r = Math.sin(-c[3]);
|
|
989
|
+
const rotated_x = dx * cos_r - dy * sin_r;
|
|
990
|
+
const rotated_y = dx * sin_r + dy * cos_r;
|
|
991
|
+
const scaled_x = rotated_x * c[2];
|
|
992
|
+
const scaled_y = rotated_y * c[2];
|
|
993
|
+
const half_width = v[0] / 2;
|
|
994
|
+
const half_height = v[1] / 2;
|
|
995
|
+
return scaled_x >= -half_width && scaled_x <= half_width && scaled_y >= -half_height && scaled_y <= half_height;
|
|
996
|
+
}
|
|
997
|
+
function width_of_camera_world(c, v) {
|
|
998
|
+
const safe_zoom = c[2] <= 0 ? 0.0001 : c[2];
|
|
999
|
+
return v[0] / safe_zoom;
|
|
1000
|
+
}
|
|
1001
|
+
function height_of_camera_world(c, v) {
|
|
1002
|
+
const safe_zoom = c[2] <= 0 ? 0.0001 : c[2];
|
|
1003
|
+
return v[1] / safe_zoom;
|
|
1004
|
+
}
|
|
1005
|
+
function pixel_size_of_camera(c) {
|
|
1006
|
+
const safe_zoom = c[2] <= 0 ? 0.0001 : c[2];
|
|
1007
|
+
return 1 / safe_zoom;
|
|
1008
|
+
}
|
|
1009
|
+
function pixel_ratio_of_viewport(v) {
|
|
1010
|
+
return v[2];
|
|
1011
|
+
}
|
|
1012
|
+
function device_width_of_viewport(v) {
|
|
1013
|
+
return v[0] * v[2];
|
|
1014
|
+
}
|
|
1015
|
+
function device_height_of_viewport(v) {
|
|
1016
|
+
return v[1] * v[2];
|
|
1017
|
+
}
|
|
1018
|
+
function world_to_device_camera(c, v, world_pos, out) {
|
|
1019
|
+
world_to_screen_camera(c, v, world_pos, out);
|
|
1020
|
+
out[0] = out[0] * v[2];
|
|
1021
|
+
out[1] = out[1] * v[2];
|
|
1022
|
+
return out;
|
|
1023
|
+
}
|
|
1024
|
+
function device_to_world_camera(c, v, device_pos, out) {
|
|
1025
|
+
const screen_pos = [device_pos[0] / v[2], device_pos[1] / v[2]];
|
|
1026
|
+
return screen_to_world_camera(c, v, screen_pos, out);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// camera/camera.ts
|
|
1030
|
+
function create_camera(x = 0, y = 0, zoom = 1, rotation = 0) {
|
|
1031
|
+
return [x, y, zoom, rotation];
|
|
1032
|
+
}
|
|
1033
|
+
function clone_camera(c) {
|
|
1034
|
+
return [c[0], c[1], c[2], c[3]];
|
|
1035
|
+
}
|
|
1036
|
+
function copy_camera(c, out) {
|
|
1037
|
+
out[0] = c[0];
|
|
1038
|
+
out[1] = c[1];
|
|
1039
|
+
out[2] = c[2];
|
|
1040
|
+
out[3] = c[3];
|
|
1041
|
+
return out;
|
|
1042
|
+
}
|
|
1043
|
+
function set_camera(x, y, zoom, rotation, out) {
|
|
1044
|
+
out[0] = x;
|
|
1045
|
+
out[1] = y;
|
|
1046
|
+
out[2] = zoom;
|
|
1047
|
+
out[3] = rotation;
|
|
1048
|
+
return out;
|
|
1049
|
+
}
|
|
1050
|
+
function x_of_camera(c) {
|
|
1051
|
+
return c[0];
|
|
1052
|
+
}
|
|
1053
|
+
function y_of_camera(c) {
|
|
1054
|
+
return c[1];
|
|
1055
|
+
}
|
|
1056
|
+
function zoom_of_camera(c) {
|
|
1057
|
+
return c[2];
|
|
1058
|
+
}
|
|
1059
|
+
function rotation_of_camera(c) {
|
|
1060
|
+
return c[3];
|
|
1061
|
+
}
|
|
1062
|
+
function position_of_camera(c, out) {
|
|
1063
|
+
out[0] = c[0];
|
|
1064
|
+
out[1] = c[1];
|
|
1065
|
+
return out;
|
|
1066
|
+
}
|
|
1067
|
+
function pan_camera(c, dx, dy, out) {
|
|
1068
|
+
out[0] = c[0] + dx;
|
|
1069
|
+
out[1] = c[1] + dy;
|
|
1070
|
+
out[2] = c[2];
|
|
1071
|
+
out[3] = c[3];
|
|
1072
|
+
return out;
|
|
1073
|
+
}
|
|
1074
|
+
function zoom_camera(c, factor, out) {
|
|
1075
|
+
out[0] = c[0];
|
|
1076
|
+
out[1] = c[1];
|
|
1077
|
+
out[2] = c[2] * factor;
|
|
1078
|
+
out[3] = c[3];
|
|
1079
|
+
return out;
|
|
1080
|
+
}
|
|
1081
|
+
function zoom_at_camera(c, factor, anchor, out) {
|
|
1082
|
+
const world_dx = anchor.world_x - c[0];
|
|
1083
|
+
const world_dy = anchor.world_y - c[1];
|
|
1084
|
+
const new_zoom = c[2] * factor;
|
|
1085
|
+
const new_world_x = anchor.world_x - world_dx / factor;
|
|
1086
|
+
const new_world_y = anchor.world_y - world_dy / factor;
|
|
1087
|
+
out[0] = new_world_x;
|
|
1088
|
+
out[1] = new_world_y;
|
|
1089
|
+
out[2] = new_zoom;
|
|
1090
|
+
out[3] = c[3];
|
|
1091
|
+
return out;
|
|
1092
|
+
}
|
|
1093
|
+
function rotate_camera(c, angle, out) {
|
|
1094
|
+
out[0] = c[0];
|
|
1095
|
+
out[1] = c[1];
|
|
1096
|
+
out[2] = c[2];
|
|
1097
|
+
out[3] = c[3] + angle;
|
|
1098
|
+
return out;
|
|
1099
|
+
}
|
|
1100
|
+
function clamp_camera(c, limits, out) {
|
|
1101
|
+
const clamped_zoom = Math.max(limits.min_zoom, Math.min(limits.max_zoom, c[2]));
|
|
1102
|
+
const clamped_x = Math.max(limits.min_x, Math.min(limits.max_x, c[0]));
|
|
1103
|
+
const clamped_y = Math.max(limits.min_y, Math.min(limits.max_y, c[1]));
|
|
1104
|
+
out[0] = clamped_x;
|
|
1105
|
+
out[1] = clamped_y;
|
|
1106
|
+
out[2] = clamped_zoom;
|
|
1107
|
+
out[3] = c[3];
|
|
1108
|
+
return out;
|
|
1109
|
+
}
|
|
1110
|
+
function lerp_camera(a, b, t, out) {
|
|
1111
|
+
const clamped_t = Math.max(0, Math.min(1, t));
|
|
1112
|
+
out[0] = a[0] + clamped_t * (b[0] - a[0]);
|
|
1113
|
+
out[1] = a[1] + clamped_t * (b[1] - a[1]);
|
|
1114
|
+
out[2] = a[2] + clamped_t * (b[2] - a[2]);
|
|
1115
|
+
let rotation_diff = b[3] - a[3];
|
|
1116
|
+
while (rotation_diff > Math.PI) {
|
|
1117
|
+
rotation_diff = rotation_diff - 2 * Math.PI;
|
|
1118
|
+
}
|
|
1119
|
+
while (rotation_diff < -Math.PI) {
|
|
1120
|
+
rotation_diff = rotation_diff + 2 * Math.PI;
|
|
1121
|
+
}
|
|
1122
|
+
out[3] = a[3] + clamped_t * rotation_diff;
|
|
1123
|
+
return out;
|
|
1124
|
+
}
|
|
1125
|
+
function smooth_damp_camera(current, target, velocity, smooth_time, delta_time, out) {
|
|
1126
|
+
const omega = 2 / Math.max(0.0001, smooth_time);
|
|
1127
|
+
const x = omega * delta_time;
|
|
1128
|
+
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
|
|
1129
|
+
const delta_x = current[0] - target[0];
|
|
1130
|
+
const delta_y = current[1] - target[1];
|
|
1131
|
+
const delta_zoom = current[2] - target[2];
|
|
1132
|
+
let rotation_diff = target[3] - current[3];
|
|
1133
|
+
while (rotation_diff > Math.PI) {
|
|
1134
|
+
rotation_diff = rotation_diff - 2 * Math.PI;
|
|
1135
|
+
}
|
|
1136
|
+
while (rotation_diff < -Math.PI) {
|
|
1137
|
+
rotation_diff = rotation_diff + 2 * Math.PI;
|
|
1138
|
+
}
|
|
1139
|
+
const delta_rotation = -rotation_diff;
|
|
1140
|
+
const temp_x = (velocity[0] + omega * delta_x) * delta_time;
|
|
1141
|
+
const temp_y = (velocity[1] + omega * delta_y) * delta_time;
|
|
1142
|
+
const temp_zoom = (velocity[2] + omega * delta_zoom) * delta_time;
|
|
1143
|
+
const temp_rotation = (velocity[3] + omega * delta_rotation) * delta_time;
|
|
1144
|
+
const new_velocity_x = (velocity[0] - omega * temp_x) * exp;
|
|
1145
|
+
const new_velocity_y = (velocity[1] - omega * temp_y) * exp;
|
|
1146
|
+
const new_velocity_zoom = (velocity[2] - omega * temp_zoom) * exp;
|
|
1147
|
+
const new_velocity_rotation = (velocity[3] - omega * temp_rotation) * exp;
|
|
1148
|
+
velocity[0] = new_velocity_x;
|
|
1149
|
+
velocity[1] = new_velocity_y;
|
|
1150
|
+
velocity[2] = new_velocity_zoom;
|
|
1151
|
+
velocity[3] = new_velocity_rotation;
|
|
1152
|
+
const result_x = target[0] + (delta_x + temp_x) * exp;
|
|
1153
|
+
const result_y = target[1] + (delta_y + temp_y) * exp;
|
|
1154
|
+
const result_zoom = target[2] + (delta_zoom + temp_zoom) * exp;
|
|
1155
|
+
const result_rotation = target[3] + (delta_rotation + temp_rotation) * exp;
|
|
1156
|
+
out[0] = result_x;
|
|
1157
|
+
out[1] = result_y;
|
|
1158
|
+
out[2] = result_zoom;
|
|
1159
|
+
out[3] = result_rotation;
|
|
1160
|
+
return out;
|
|
1161
|
+
}
|
|
1162
|
+
function equals_camera(c1, c2, epsilon = 0.0001) {
|
|
1163
|
+
return Math.abs(c1[0] - c2[0]) < epsilon && Math.abs(c1[1] - c2[1]) < epsilon && Math.abs(c1[2] - c2[2]) < epsilon && Math.abs(c1[3] - c2[3]) < epsilon;
|
|
1164
|
+
}
|
|
1165
|
+
function is_identity_camera(c) {
|
|
1166
|
+
return c[0] === 0 && c[1] === 0 && c[2] === 1 && c[3] === 0;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// engine/camera.ts
|
|
1170
|
+
function set_camera_engine(state, x, y, zoom, rotation = 0) {
|
|
1171
|
+
set_camera(sanitize_number_engine(x, state.camera[0]), sanitize_number_engine(y, state.camera[1]), sanitize_zoom_engine(zoom, state.camera[2]), sanitize_number_engine(rotation, state.camera[3]), state.camera);
|
|
1172
|
+
return state;
|
|
1173
|
+
}
|
|
1174
|
+
function pan_engine(state, dx, dy) {
|
|
1175
|
+
pan_camera(state.camera, dx, dy, scratch_camera);
|
|
1176
|
+
copy_camera_into_engine(state, scratch_camera);
|
|
1177
|
+
return state;
|
|
1178
|
+
}
|
|
1179
|
+
function zoom_engine(state, factor) {
|
|
1180
|
+
zoom_camera(state.camera, factor, scratch_camera);
|
|
1181
|
+
copy_camera_into_engine(state, scratch_camera);
|
|
1182
|
+
return state;
|
|
1183
|
+
}
|
|
1184
|
+
function zoom_at_engine(state, factor, screen_x, screen_y) {
|
|
1185
|
+
scratch_screen[0] = screen_x;
|
|
1186
|
+
scratch_screen[1] = screen_y;
|
|
1187
|
+
screen_to_world_camera(state.camera, state.viewport, scratch_screen, scratch_world);
|
|
1188
|
+
zoom_at_camera(state.camera, factor, {
|
|
1189
|
+
screen_x,
|
|
1190
|
+
screen_y,
|
|
1191
|
+
world_x: scratch_world[0],
|
|
1192
|
+
world_y: scratch_world[1]
|
|
1193
|
+
}, scratch_camera);
|
|
1194
|
+
copy_camera_into_engine(state, scratch_camera);
|
|
1195
|
+
return state;
|
|
1196
|
+
}
|
|
1197
|
+
function resize_engine(state, width, height, pixel_ratio = Math.max(1, globalThis.devicePixelRatio || 1)) {
|
|
1198
|
+
const safe_width = Math.max(1, width);
|
|
1199
|
+
const safe_height = Math.max(1, height);
|
|
1200
|
+
const safe_pixel_ratio = Math.max(1, pixel_ratio);
|
|
1201
|
+
state.viewport[0] = safe_width;
|
|
1202
|
+
state.viewport[1] = safe_height;
|
|
1203
|
+
state.viewport[2] = safe_pixel_ratio;
|
|
1204
|
+
state.renderer.resize(safe_width, safe_height, safe_pixel_ratio);
|
|
1205
|
+
return state;
|
|
1206
|
+
}
|
|
1207
|
+
function world_to_screen_engine(state, world, out) {
|
|
1208
|
+
return world_to_screen_camera(state.camera, state.viewport, world, out);
|
|
1209
|
+
}
|
|
1210
|
+
function screen_to_world_engine(state, screen, out) {
|
|
1211
|
+
return screen_to_world_camera(state.camera, state.viewport, screen, out);
|
|
1212
|
+
}
|
|
1213
|
+
function visible_bounds_world_engine(state) {
|
|
1214
|
+
camera_frustum(state.camera, state.viewport, scratch_frustum);
|
|
1215
|
+
return [
|
|
1216
|
+
scratch_frustum[0],
|
|
1217
|
+
scratch_frustum[3],
|
|
1218
|
+
scratch_frustum[1] - scratch_frustum[0],
|
|
1219
|
+
scratch_frustum[2] - scratch_frustum[3]
|
|
1220
|
+
];
|
|
1221
|
+
}
|
|
1222
|
+
function camera_to_screen_transform_engine(c, v, out) {
|
|
1223
|
+
const cos_r = Math.cos(c[3]);
|
|
1224
|
+
const sin_r = Math.sin(c[3]);
|
|
1225
|
+
const zoom = c[2];
|
|
1226
|
+
out[0] = cos_r * zoom;
|
|
1227
|
+
out[1] = sin_r * zoom;
|
|
1228
|
+
out[2] = -sin_r * zoom;
|
|
1229
|
+
out[3] = cos_r * zoom;
|
|
1230
|
+
out[4] = v[0] / 2 - c[0] * cos_r * zoom + c[1] * sin_r * zoom;
|
|
1231
|
+
out[5] = v[1] / 2 - c[0] * sin_r * zoom - c[1] * cos_r * zoom;
|
|
1232
|
+
return out;
|
|
1233
|
+
}
|
|
1234
|
+
function sanitize_number_engine(value, fallback) {
|
|
1235
|
+
if (Number.isFinite(value)) {
|
|
1236
|
+
return value;
|
|
1237
|
+
}
|
|
1238
|
+
return Number.isFinite(fallback) ? fallback : 0;
|
|
1239
|
+
}
|
|
1240
|
+
function sanitize_zoom_engine(value, fallback) {
|
|
1241
|
+
const safe_value = Number.isFinite(value) ? value : fallback;
|
|
1242
|
+
if (!Number.isFinite(safe_value) || safe_value <= 0) {
|
|
1243
|
+
return 1;
|
|
1244
|
+
}
|
|
1245
|
+
return Math.min(max_zoom_engine, Math.max(min_zoom_engine, safe_value));
|
|
1246
|
+
}
|
|
1247
|
+
function copy_camera_into_engine(state, value) {
|
|
1248
|
+
state.camera[0] = sanitize_number_engine(value[0], state.camera[0]);
|
|
1249
|
+
state.camera[1] = sanitize_number_engine(value[1], state.camera[1]);
|
|
1250
|
+
state.camera[2] = sanitize_zoom_engine(value[2], state.camera[2]);
|
|
1251
|
+
state.camera[3] = sanitize_number_engine(value[3], state.camera[3]);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// math/color.ts
|
|
1255
|
+
function create_color(r = 0, g = 0, b = 0, a = 1) {
|
|
1256
|
+
return [
|
|
1257
|
+
Math.max(0, Math.min(1, r)),
|
|
1258
|
+
Math.max(0, Math.min(1, g)),
|
|
1259
|
+
Math.max(0, Math.min(1, b)),
|
|
1260
|
+
Math.max(0, Math.min(1, a))
|
|
1261
|
+
];
|
|
1262
|
+
}
|
|
1263
|
+
function clone_color(c) {
|
|
1264
|
+
return [c[0], c[1], c[2], c[3]];
|
|
1265
|
+
}
|
|
1266
|
+
function set_color(r, g, b, a = 1, out) {
|
|
1267
|
+
out[0] = Math.max(0, Math.min(1, r));
|
|
1268
|
+
out[1] = Math.max(0, Math.min(1, g));
|
|
1269
|
+
out[2] = Math.max(0, Math.min(1, b));
|
|
1270
|
+
out[3] = Math.max(0, Math.min(1, a));
|
|
1271
|
+
return out;
|
|
1272
|
+
}
|
|
1273
|
+
function copy_color(c, out) {
|
|
1274
|
+
out[0] = c[0];
|
|
1275
|
+
out[1] = c[1];
|
|
1276
|
+
out[2] = c[2];
|
|
1277
|
+
out[3] = c[3];
|
|
1278
|
+
return out;
|
|
1279
|
+
}
|
|
1280
|
+
function from_rgb_color(r, g, b, a = 255) {
|
|
1281
|
+
return create_color(r / 255, g / 255, b / 255, a / 255);
|
|
1282
|
+
}
|
|
1283
|
+
function from_hex_color(hex) {
|
|
1284
|
+
const cleaned = hex.replace("#", "");
|
|
1285
|
+
const r = parseInt(cleaned.substring(0, 2), 16) / 255;
|
|
1286
|
+
const g = parseInt(cleaned.substring(2, 4), 16) / 255;
|
|
1287
|
+
const b = parseInt(cleaned.substring(4, 6), 16) / 255;
|
|
1288
|
+
const a = cleaned.length === 8 ? parseInt(cleaned.substring(6, 8), 16) / 255 : 1;
|
|
1289
|
+
return create_color(r, g, b, a);
|
|
1290
|
+
}
|
|
1291
|
+
function to_rgba_string_color(c) {
|
|
1292
|
+
return `rgba(${Math.round(c[0] * 255)}, ${Math.round(c[1] * 255)}, ${Math.round(c[2] * 255)}, ${c[3]})`;
|
|
1293
|
+
}
|
|
1294
|
+
function to_hex_string_color(c) {
|
|
1295
|
+
const r = Math.round(c[0] * 255).toString(16).padStart(2, "0");
|
|
1296
|
+
const g = Math.round(c[1] * 255).toString(16).padStart(2, "0");
|
|
1297
|
+
const b = Math.round(c[2] * 255).toString(16).padStart(2, "0");
|
|
1298
|
+
const a = Math.round(c[3] * 255).toString(16).padStart(2, "0");
|
|
1299
|
+
return c[3] === 1 ? `#${r}${g}${b}` : `#${r}${g}${b}${a}`;
|
|
1300
|
+
}
|
|
1301
|
+
function lerp_color(c1, c2, t, out) {
|
|
1302
|
+
out[0] = c1[0] + (c2[0] - c1[0]) * t;
|
|
1303
|
+
out[1] = c1[1] + (c2[1] - c1[1]) * t;
|
|
1304
|
+
out[2] = c1[2] + (c2[2] - c1[2]) * t;
|
|
1305
|
+
out[3] = c1[3] + (c2[3] - c1[3]) * t;
|
|
1306
|
+
return out;
|
|
1307
|
+
}
|
|
1308
|
+
function equals_color(c1, c2, epsilon = 0.0001) {
|
|
1309
|
+
return Math.abs(c1[0] - c2[0]) < epsilon && Math.abs(c1[1] - c2[1]) < epsilon && Math.abs(c1[2] - c2[2]) < epsilon && Math.abs(c1[3] - c2[3]) < epsilon;
|
|
1310
|
+
}
|
|
1311
|
+
function white_color() {
|
|
1312
|
+
return [1, 1, 1, 1];
|
|
1313
|
+
}
|
|
1314
|
+
function black_color() {
|
|
1315
|
+
return [0, 0, 0, 1];
|
|
1316
|
+
}
|
|
1317
|
+
function transparent_color() {
|
|
1318
|
+
return [0, 0, 0, 0];
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// engine/elements.ts
|
|
1322
|
+
var loaded_images_engine = new Map;
|
|
1323
|
+
var text_bitmap_cache_engine = new Map;
|
|
1324
|
+
var text_bitmap_cache_limit_engine = 128;
|
|
1325
|
+
function render_element_engine(drawer, el, asset_src = null) {
|
|
1326
|
+
if (el.type === element_type_stroke) {
|
|
1327
|
+
render_stroke_element_engine(drawer, el);
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
if (el.type === element_type_shape) {
|
|
1331
|
+
render_shape_element_engine(drawer, el);
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
if (el.type === element_type_image) {
|
|
1335
|
+
render_image_element_engine(drawer, el, asset_src);
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
if (el.type === element_type_text) {
|
|
1339
|
+
render_text_element_engine(drawer, el);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
function render_stroke_element_engine(drawer, el) {
|
|
1343
|
+
const style = {
|
|
1344
|
+
fill: null,
|
|
1345
|
+
stroke: el.color,
|
|
1346
|
+
stroke_width: el.width,
|
|
1347
|
+
line_cap: 1,
|
|
1348
|
+
line_join: 1,
|
|
1349
|
+
miter_limit: 10,
|
|
1350
|
+
alpha: el.color[3]
|
|
1351
|
+
};
|
|
1352
|
+
const points = el.simplified_points && el.simplified_points.length > 1 ? el.simplified_points : el.points;
|
|
1353
|
+
if (points.length === 1) {
|
|
1354
|
+
const p = points[0];
|
|
1355
|
+
if (p !== undefined) {
|
|
1356
|
+
drawer.draw_circle([p[0], p[1], Math.max(0.5, el.width * 0.5)], style);
|
|
1357
|
+
}
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
if (points.length > 1) {
|
|
1361
|
+
drawer.draw_polyline(points, style);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
function render_shape_element_engine(drawer, el) {
|
|
1365
|
+
const style = {
|
|
1366
|
+
fill: el.fill_color,
|
|
1367
|
+
stroke: el.stroke_color,
|
|
1368
|
+
stroke_width: el.stroke_width,
|
|
1369
|
+
line_cap: 1,
|
|
1370
|
+
line_join: 1,
|
|
1371
|
+
miter_limit: 10,
|
|
1372
|
+
alpha: 1
|
|
1373
|
+
};
|
|
1374
|
+
if (el.shape_type === shape_type_rectangle) {
|
|
1375
|
+
drawer.draw_rectangle(el.bounds, style);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
if (el.shape_type === shape_type_frame) {
|
|
1379
|
+
const header_height = Math.max(20, Math.min(36, el.bounds[3] * 0.18));
|
|
1380
|
+
const header_style = {
|
|
1381
|
+
fill: style.stroke,
|
|
1382
|
+
stroke: style.stroke,
|
|
1383
|
+
stroke_width: Math.max(1, style.stroke_width),
|
|
1384
|
+
line_cap: style.line_cap,
|
|
1385
|
+
line_join: style.line_join,
|
|
1386
|
+
miter_limit: style.miter_limit,
|
|
1387
|
+
alpha: 0.12
|
|
1388
|
+
};
|
|
1389
|
+
const outline_style = {
|
|
1390
|
+
...style,
|
|
1391
|
+
fill: null,
|
|
1392
|
+
alpha: 1
|
|
1393
|
+
};
|
|
1394
|
+
drawer.draw_rectangle(el.bounds, outline_style);
|
|
1395
|
+
drawer.draw_rectangle([el.bounds[0], el.bounds[1], el.bounds[2], header_height], header_style);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (el.shape_type === shape_type_ellipse) {
|
|
1399
|
+
const cx = el.bounds[0] + el.bounds[2] / 2;
|
|
1400
|
+
const cy = el.bounds[1] + el.bounds[3] / 2;
|
|
1401
|
+
const rx = Math.max(0.5, Math.abs(el.bounds[2]) / 2);
|
|
1402
|
+
const ry = Math.max(0.5, Math.abs(el.bounds[3]) / 2);
|
|
1403
|
+
const segments = 40;
|
|
1404
|
+
const points = [];
|
|
1405
|
+
for (let i = 0;i <= segments; i = i + 1) {
|
|
1406
|
+
const t = i / segments * Math.PI * 2;
|
|
1407
|
+
points.push([cx + Math.cos(t) * rx, cy + Math.sin(t) * ry]);
|
|
1408
|
+
}
|
|
1409
|
+
drawer.draw_polyline(points, style);
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
if (el.shape_type === shape_type_line || el.shape_type === shape_type_arrow) {
|
|
1413
|
+
const from = el.start_point ?? [el.bounds[0], el.bounds[1]];
|
|
1414
|
+
const to = el.end_point ?? [el.bounds[0] + el.bounds[2], el.bounds[1] + el.bounds[3]];
|
|
1415
|
+
drawer.draw_line(from, to, style);
|
|
1416
|
+
if (el.shape_type === shape_type_arrow) {
|
|
1417
|
+
draw_arrowhead_engine(drawer, from, to, style);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
function render_image_element_engine(drawer, el, asset_src) {
|
|
1422
|
+
const image = resolve_image_source_engine(el, asset_src);
|
|
1423
|
+
const opacity = Math.max(0, Math.min(1, el.opacity));
|
|
1424
|
+
if (image.status === "loaded" && image.source !== null) {
|
|
1425
|
+
drawer.draw_image(image.source, fit_image_bounds_engine(el.bounds, image.source), opacity);
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
const style = {
|
|
1429
|
+
fill: default_element_fill,
|
|
1430
|
+
stroke: default_element_stroke,
|
|
1431
|
+
stroke_width: 1,
|
|
1432
|
+
line_cap: 1,
|
|
1433
|
+
line_join: 1,
|
|
1434
|
+
miter_limit: 10,
|
|
1435
|
+
alpha: opacity
|
|
1436
|
+
};
|
|
1437
|
+
drawer.draw_rectangle(el.bounds, style);
|
|
1438
|
+
if (image.status === "error") {
|
|
1439
|
+
const mark_style = {
|
|
1440
|
+
fill: null,
|
|
1441
|
+
stroke: default_element_stroke,
|
|
1442
|
+
stroke_width: 1.5,
|
|
1443
|
+
line_cap: 1,
|
|
1444
|
+
line_join: 1,
|
|
1445
|
+
miter_limit: 10,
|
|
1446
|
+
alpha: opacity
|
|
1447
|
+
};
|
|
1448
|
+
const inset = Math.max(8, Math.min(el.bounds[2], el.bounds[3]) * 0.12);
|
|
1449
|
+
const x0 = el.bounds[0] + inset;
|
|
1450
|
+
const y0 = el.bounds[1] + inset;
|
|
1451
|
+
const x1 = el.bounds[0] + el.bounds[2] - inset;
|
|
1452
|
+
const y1 = el.bounds[1] + el.bounds[3] - inset;
|
|
1453
|
+
drawer.draw_line([x0, y0], [x1, y1], mark_style);
|
|
1454
|
+
drawer.draw_line([x1, y0], [x0, y1], mark_style);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
function render_text_element_engine(drawer, el) {
|
|
1458
|
+
const key = [
|
|
1459
|
+
el.content,
|
|
1460
|
+
el.font_family,
|
|
1461
|
+
el.font_size,
|
|
1462
|
+
el.color.join(","),
|
|
1463
|
+
el.align,
|
|
1464
|
+
Math.max(1, Math.round(el.bounds[2])),
|
|
1465
|
+
Math.max(1, Math.round(el.bounds[3])),
|
|
1466
|
+
Math.max(1, Math.round(drawer.pixel_ratio))
|
|
1467
|
+
].join("|");
|
|
1468
|
+
let bitmap = text_bitmap_cache_engine.get(key);
|
|
1469
|
+
if (bitmap === undefined) {
|
|
1470
|
+
bitmap = rasterize_text_engine(el, Math.max(1, Math.round(drawer.pixel_ratio)));
|
|
1471
|
+
text_bitmap_cache_engine.set(key, bitmap);
|
|
1472
|
+
trim_text_bitmap_cache_engine();
|
|
1473
|
+
}
|
|
1474
|
+
drawer.draw_image(bitmap, el.bounds, 1);
|
|
1475
|
+
}
|
|
1476
|
+
function resolve_image_source_engine(el, asset_src) {
|
|
1477
|
+
if (el.bitmap !== null) {
|
|
1478
|
+
return { status: "loaded", source: el.bitmap };
|
|
1479
|
+
}
|
|
1480
|
+
const src = (asset_src ?? el.src).trim();
|
|
1481
|
+
if (src.length === 0 || typeof Image === "undefined") {
|
|
1482
|
+
return { status: "empty", source: null };
|
|
1483
|
+
}
|
|
1484
|
+
let cached = loaded_images_engine.get(src);
|
|
1485
|
+
if (cached === undefined) {
|
|
1486
|
+
const image = new Image;
|
|
1487
|
+
cached = { image, status: "loading" };
|
|
1488
|
+
loaded_images_engine.set(src, cached);
|
|
1489
|
+
image.onload = () => {
|
|
1490
|
+
const current = loaded_images_engine.get(src);
|
|
1491
|
+
if (current !== undefined)
|
|
1492
|
+
current.status = "loaded";
|
|
1493
|
+
};
|
|
1494
|
+
image.onerror = () => {
|
|
1495
|
+
const current = loaded_images_engine.get(src);
|
|
1496
|
+
if (current !== undefined)
|
|
1497
|
+
current.status = "error";
|
|
1498
|
+
};
|
|
1499
|
+
image.src = src;
|
|
1500
|
+
return { status: "loading", source: null };
|
|
1501
|
+
}
|
|
1502
|
+
if (cached.status === "loaded") {
|
|
1503
|
+
return { status: "loaded", source: cached.image };
|
|
1504
|
+
}
|
|
1505
|
+
return { status: cached.status, source: null };
|
|
1506
|
+
}
|
|
1507
|
+
function fit_image_bounds_engine(bounds, source) {
|
|
1508
|
+
const source_w = source instanceof HTMLImageElement ? source.naturalWidth : source.width;
|
|
1509
|
+
const source_h = source instanceof HTMLImageElement ? source.naturalHeight : source.height;
|
|
1510
|
+
const x = bounds[0];
|
|
1511
|
+
const y = bounds[1];
|
|
1512
|
+
const w = Math.max(1, bounds[2]);
|
|
1513
|
+
const h = Math.max(1, bounds[3]);
|
|
1514
|
+
if (source_w <= 0 || source_h <= 0) {
|
|
1515
|
+
return [x, y, w, h];
|
|
1516
|
+
}
|
|
1517
|
+
const scale = Math.min(w / source_w, h / source_h);
|
|
1518
|
+
const draw_w = source_w * scale;
|
|
1519
|
+
const draw_h = source_h * scale;
|
|
1520
|
+
const draw_x = x + (w - draw_w) * 0.5;
|
|
1521
|
+
const draw_y = y + (h - draw_h) * 0.5;
|
|
1522
|
+
return [draw_x, draw_y, draw_w, draw_h];
|
|
1523
|
+
}
|
|
1524
|
+
function draw_arrowhead_engine(drawer, from, to, style) {
|
|
1525
|
+
const dx = to[0] - from[0];
|
|
1526
|
+
const dy = to[1] - from[1];
|
|
1527
|
+
const len = Math.hypot(dx, dy);
|
|
1528
|
+
if (len < 0.0001)
|
|
1529
|
+
return;
|
|
1530
|
+
const ux = dx / len;
|
|
1531
|
+
const uy = dy / len;
|
|
1532
|
+
const size = Math.max(8, style.stroke_width * 5);
|
|
1533
|
+
const wing_angle = Math.PI / 7;
|
|
1534
|
+
const cos = Math.cos(wing_angle);
|
|
1535
|
+
const sin = Math.sin(wing_angle);
|
|
1536
|
+
const lx = to[0] - (ux * cos - uy * sin) * size;
|
|
1537
|
+
const ly = to[1] - (uy * cos + ux * sin) * size;
|
|
1538
|
+
const rx = to[0] - (ux * cos + uy * sin) * size;
|
|
1539
|
+
const ry = to[1] - (uy * cos - ux * sin) * size;
|
|
1540
|
+
drawer.draw_line(to, [lx, ly], style);
|
|
1541
|
+
drawer.draw_line(to, [rx, ry], style);
|
|
1542
|
+
}
|
|
1543
|
+
function rasterize_text_engine(el, scale) {
|
|
1544
|
+
const canvas = document.createElement("canvas");
|
|
1545
|
+
const width = Math.max(1, Math.ceil(el.bounds[2] * scale));
|
|
1546
|
+
const height = Math.max(1, Math.ceil(el.bounds[3] * scale));
|
|
1547
|
+
canvas.width = width;
|
|
1548
|
+
canvas.height = height;
|
|
1549
|
+
const ctx = canvas.getContext("2d");
|
|
1550
|
+
if (ctx === null)
|
|
1551
|
+
return canvas;
|
|
1552
|
+
const font_size = Math.max(1, el.font_size);
|
|
1553
|
+
const line_height = font_size * 1.3;
|
|
1554
|
+
const padding = Math.max(4, Math.round(font_size * 0.35));
|
|
1555
|
+
const max_text_width = Math.max(1, el.bounds[2] - padding * 2);
|
|
1556
|
+
ctx.scale(scale, scale);
|
|
1557
|
+
ctx.font = `${font_size}px ${el.font_family}`;
|
|
1558
|
+
ctx.textBaseline = "top";
|
|
1559
|
+
ctx.fillStyle = to_rgba_string_color(el.color);
|
|
1560
|
+
const lines = wrap_text_lines_engine(ctx, el.content, max_text_width);
|
|
1561
|
+
const base_x = el.bounds[0];
|
|
1562
|
+
const base_y = el.bounds[1];
|
|
1563
|
+
const text_top = base_y + padding;
|
|
1564
|
+
const text_bottom = base_y + el.bounds[3] - padding;
|
|
1565
|
+
let y = text_top;
|
|
1566
|
+
for (let i = 0;i < lines.length; i = i + 1) {
|
|
1567
|
+
const line = lines[i];
|
|
1568
|
+
if (line === undefined)
|
|
1569
|
+
continue;
|
|
1570
|
+
if (y + line_height > text_bottom + 0.5)
|
|
1571
|
+
break;
|
|
1572
|
+
const line_width = ctx.measureText(line).width;
|
|
1573
|
+
let x = base_x + padding;
|
|
1574
|
+
if (el.align === text_align_center) {
|
|
1575
|
+
x = base_x + el.bounds[2] * 0.5 - line_width * 0.5;
|
|
1576
|
+
} else if (el.align === text_align_right) {
|
|
1577
|
+
x = base_x + el.bounds[2] - padding - line_width;
|
|
1578
|
+
}
|
|
1579
|
+
ctx.fillText(line, x - base_x, y - base_y);
|
|
1580
|
+
y = y + line_height;
|
|
1581
|
+
}
|
|
1582
|
+
return canvas;
|
|
1583
|
+
}
|
|
1584
|
+
function wrap_text_lines_engine(ctx, content, max_width) {
|
|
1585
|
+
const normalized = content.length > 0 ? content : "";
|
|
1586
|
+
const paragraphs = normalized.split(/\r?\n/);
|
|
1587
|
+
const lines = [];
|
|
1588
|
+
for (let i = 0;i < paragraphs.length; i = i + 1) {
|
|
1589
|
+
const paragraph = paragraphs[i];
|
|
1590
|
+
if (paragraph === undefined)
|
|
1591
|
+
continue;
|
|
1592
|
+
if (paragraph.length === 0) {
|
|
1593
|
+
lines.push("");
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1596
|
+
const words = paragraph.split(/(\s+)/).filter((word) => word.length > 0);
|
|
1597
|
+
let current = "";
|
|
1598
|
+
for (let j = 0;j < words.length; j = j + 1) {
|
|
1599
|
+
const word = words[j];
|
|
1600
|
+
if (word === undefined)
|
|
1601
|
+
continue;
|
|
1602
|
+
const candidate = current.length === 0 ? word : `${current}${word}`;
|
|
1603
|
+
if (ctx.measureText(candidate).width <= max_width || current.length === 0) {
|
|
1604
|
+
current = candidate;
|
|
1605
|
+
} else {
|
|
1606
|
+
lines.push(current.trimEnd());
|
|
1607
|
+
current = word.trimStart();
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
lines.push(current.length > 0 ? current : "");
|
|
1611
|
+
}
|
|
1612
|
+
return lines;
|
|
1613
|
+
}
|
|
1614
|
+
function trim_text_bitmap_cache_engine() {
|
|
1615
|
+
if (text_bitmap_cache_engine.size <= text_bitmap_cache_limit_engine) {
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
const remove_count = Math.ceil(text_bitmap_cache_engine.size / 3);
|
|
1619
|
+
let removed = 0;
|
|
1620
|
+
for (const key of text_bitmap_cache_engine.keys()) {
|
|
1621
|
+
text_bitmap_cache_engine.delete(key);
|
|
1622
|
+
removed = removed + 1;
|
|
1623
|
+
if (removed >= remove_count)
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// engine/render.ts
|
|
1629
|
+
function render_engine(state) {
|
|
1630
|
+
render_engine_with_overlay(state, null);
|
|
1631
|
+
}
|
|
1632
|
+
function render_engine_with_overlay(state, overlay) {
|
|
1633
|
+
const view_bounds = visible_bounds_world_engine(state);
|
|
1634
|
+
const visible_elements = elements_in_bounds_document(state.document, view_bounds);
|
|
1635
|
+
const layer_info = build_layer_render_info_map_engine(state.document);
|
|
1636
|
+
const renderable_elements = [];
|
|
1637
|
+
for (let i = 0;i < visible_elements.length; i = i + 1) {
|
|
1638
|
+
const el = visible_elements[i];
|
|
1639
|
+
if (el === undefined) {
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
const info = layer_info.get(el.layer_id);
|
|
1643
|
+
if (info === undefined || info.visible) {
|
|
1644
|
+
renderable_elements.push(el);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
renderable_elements.sort((a, b) => {
|
|
1648
|
+
const a_order = layer_info.get(a.layer_id)?.order ?? Number.MAX_SAFE_INTEGER;
|
|
1649
|
+
const b_order = layer_info.get(b.layer_id)?.order ?? Number.MAX_SAFE_INTEGER;
|
|
1650
|
+
if (a_order !== b_order) {
|
|
1651
|
+
return a_order - b_order;
|
|
1652
|
+
}
|
|
1653
|
+
return a.z_index - b.z_index;
|
|
1654
|
+
});
|
|
1655
|
+
state.renderer.begin_frame();
|
|
1656
|
+
state.renderer.clear(state.background);
|
|
1657
|
+
camera_to_screen_transform_engine(state.camera, state.viewport, scratch_transform);
|
|
1658
|
+
state.renderer.set_transform(scratch_transform);
|
|
1659
|
+
if (state.background_image !== null) {
|
|
1660
|
+
state.renderer.draw_image(state.background_image, [0, 0, state.background_image.width, state.background_image.height], 1);
|
|
1661
|
+
}
|
|
1662
|
+
for (let i = 0;i < renderable_elements.length; i = i + 1) {
|
|
1663
|
+
const el = renderable_elements[i];
|
|
1664
|
+
if (el !== undefined) {
|
|
1665
|
+
const asset_src = el.type === element_type_image && el.asset_id !== null ? state.document.assets.get(el.asset_id)?.src ?? null : null;
|
|
1666
|
+
render_element_engine(state.renderer, el, asset_src);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
if (overlay !== null) {
|
|
1670
|
+
overlay(state.renderer);
|
|
1671
|
+
}
|
|
1672
|
+
state.renderer.end_frame();
|
|
1673
|
+
}
|
|
1674
|
+
function build_layer_render_info_map_engine(doc) {
|
|
1675
|
+
const map = new Map;
|
|
1676
|
+
for (let i = 0;i < doc.layers.length; i = i + 1) {
|
|
1677
|
+
const layer = doc.layers[i];
|
|
1678
|
+
if (layer !== undefined) {
|
|
1679
|
+
map.set(layer.id, { order: i, visible: layer.visible });
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
return map;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// document/layer.ts
|
|
1686
|
+
function create_layer(id, name, visible = true, locked = false, opacity = 1) {
|
|
1687
|
+
return {
|
|
1688
|
+
id,
|
|
1689
|
+
name,
|
|
1690
|
+
visible,
|
|
1691
|
+
locked,
|
|
1692
|
+
opacity: Math.max(0, Math.min(1, opacity)),
|
|
1693
|
+
element_ids: []
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
function clone_layer(l) {
|
|
1697
|
+
return {
|
|
1698
|
+
id: l.id,
|
|
1699
|
+
name: l.name,
|
|
1700
|
+
visible: l.visible,
|
|
1701
|
+
locked: l.locked,
|
|
1702
|
+
opacity: l.opacity,
|
|
1703
|
+
element_ids: [...l.element_ids]
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
function id_of_layer(l) {
|
|
1707
|
+
return l.id;
|
|
1708
|
+
}
|
|
1709
|
+
function name_of_layer(l) {
|
|
1710
|
+
return l.name;
|
|
1711
|
+
}
|
|
1712
|
+
function is_visible_layer(l) {
|
|
1713
|
+
return l.visible;
|
|
1714
|
+
}
|
|
1715
|
+
function is_locked_layer(l) {
|
|
1716
|
+
return l.locked;
|
|
1717
|
+
}
|
|
1718
|
+
function opacity_of_layer(l) {
|
|
1719
|
+
return l.opacity;
|
|
1720
|
+
}
|
|
1721
|
+
function element_ids_of_layer(l) {
|
|
1722
|
+
return l.element_ids;
|
|
1723
|
+
}
|
|
1724
|
+
function element_count_of_layer(l) {
|
|
1725
|
+
return l.element_ids.length;
|
|
1726
|
+
}
|
|
1727
|
+
function update_layer_name(l, name) {
|
|
1728
|
+
return {
|
|
1729
|
+
...l,
|
|
1730
|
+
name
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
function update_layer_visibility(l, visible) {
|
|
1734
|
+
return {
|
|
1735
|
+
...l,
|
|
1736
|
+
visible
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
function update_layer_locked(l, locked) {
|
|
1740
|
+
return {
|
|
1741
|
+
...l,
|
|
1742
|
+
locked
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
function update_layer_opacity(l, opacity) {
|
|
1746
|
+
return {
|
|
1747
|
+
...l,
|
|
1748
|
+
opacity: Math.max(0, Math.min(1, opacity))
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
function add_element_id_to_layer(l, element_id) {
|
|
1752
|
+
if (l.element_ids.includes(element_id)) {
|
|
1753
|
+
return l;
|
|
1754
|
+
}
|
|
1755
|
+
return {
|
|
1756
|
+
...l,
|
|
1757
|
+
element_ids: [...l.element_ids, element_id]
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
function remove_element_id_from_layer(l, element_id) {
|
|
1761
|
+
const index = l.element_ids.indexOf(element_id);
|
|
1762
|
+
if (index === -1) {
|
|
1763
|
+
return l;
|
|
1764
|
+
}
|
|
1765
|
+
const new_ids = [...l.element_ids];
|
|
1766
|
+
new_ids.splice(index, 1);
|
|
1767
|
+
return {
|
|
1768
|
+
...l,
|
|
1769
|
+
element_ids: new_ids
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
function has_element_id_in_layer(l, element_id) {
|
|
1773
|
+
return l.element_ids.includes(element_id);
|
|
1774
|
+
}
|
|
1775
|
+
function reorder_element_ids_in_layer(l, element_ids) {
|
|
1776
|
+
const existing_set = new Set(l.element_ids);
|
|
1777
|
+
const filtered_ids = element_ids.filter((id) => existing_set.has(id));
|
|
1778
|
+
const new_ids_set = new Set(filtered_ids);
|
|
1779
|
+
const remaining_ids = l.element_ids.filter((id) => !new_ids_set.has(id));
|
|
1780
|
+
return {
|
|
1781
|
+
...l,
|
|
1782
|
+
element_ids: [...remaining_ids, ...filtered_ids]
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
function equals_layer(a, b) {
|
|
1786
|
+
if (a.id !== b.id || a.name !== b.name) {
|
|
1787
|
+
return false;
|
|
1788
|
+
}
|
|
1789
|
+
if (a.visible !== b.visible || a.locked !== b.locked) {
|
|
1790
|
+
return false;
|
|
1791
|
+
}
|
|
1792
|
+
if (a.opacity !== b.opacity) {
|
|
1793
|
+
return false;
|
|
1794
|
+
}
|
|
1795
|
+
if (a.element_ids.length !== b.element_ids.length) {
|
|
1796
|
+
return false;
|
|
1797
|
+
}
|
|
1798
|
+
for (let i = 0;i < a.element_ids.length; i = i + 1) {
|
|
1799
|
+
if (a.element_ids[i] !== b.element_ids[i]) {
|
|
1800
|
+
return false;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
return true;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// document/document.ts
|
|
1807
|
+
function create_document(id, active_layer_id) {
|
|
1808
|
+
return {
|
|
1809
|
+
id,
|
|
1810
|
+
elements: new Map,
|
|
1811
|
+
layers: [],
|
|
1812
|
+
active_layer_id,
|
|
1813
|
+
assets: new Map,
|
|
1814
|
+
bounds: [0, 0, 0, 0],
|
|
1815
|
+
element_count: 0
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
function clone_document(doc) {
|
|
1819
|
+
const new_elements = new Map;
|
|
1820
|
+
for (const [id, el] of doc.elements) {
|
|
1821
|
+
new_elements.set(id, el);
|
|
1822
|
+
}
|
|
1823
|
+
return {
|
|
1824
|
+
id: doc.id,
|
|
1825
|
+
elements: new_elements,
|
|
1826
|
+
layers: doc.layers.map((l) => ({ ...l, element_ids: [...l.element_ids] })),
|
|
1827
|
+
active_layer_id: doc.active_layer_id,
|
|
1828
|
+
assets: new Map(doc.assets),
|
|
1829
|
+
bounds: [doc.bounds[0], doc.bounds[1], doc.bounds[2], doc.bounds[3]],
|
|
1830
|
+
element_count: doc.element_count
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
function id_of_document(doc) {
|
|
1834
|
+
return doc.id;
|
|
1835
|
+
}
|
|
1836
|
+
function elements_map_of_document(doc) {
|
|
1837
|
+
return doc.elements;
|
|
1838
|
+
}
|
|
1839
|
+
function layers_of_document(doc) {
|
|
1840
|
+
return doc.layers;
|
|
1841
|
+
}
|
|
1842
|
+
function active_layer_id_of_document(doc) {
|
|
1843
|
+
return doc.active_layer_id;
|
|
1844
|
+
}
|
|
1845
|
+
function assets_of_document(doc) {
|
|
1846
|
+
return doc.assets;
|
|
1847
|
+
}
|
|
1848
|
+
function bounds_of_document(doc) {
|
|
1849
|
+
return [doc.bounds[0], doc.bounds[1], doc.bounds[2], doc.bounds[3]];
|
|
1850
|
+
}
|
|
1851
|
+
function element_count_of_document(doc) {
|
|
1852
|
+
return doc.element_count;
|
|
1853
|
+
}
|
|
1854
|
+
function get_element_by_id_document(doc, element_id) {
|
|
1855
|
+
return doc.elements.get(element_id) || null;
|
|
1856
|
+
}
|
|
1857
|
+
function get_layer_by_id_document(doc, layer_id) {
|
|
1858
|
+
for (let i = 0;i < doc.layers.length; i = i + 1) {
|
|
1859
|
+
const layer = doc.layers[i];
|
|
1860
|
+
if (layer && layer.id === layer_id) {
|
|
1861
|
+
return layer;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
return null;
|
|
1865
|
+
}
|
|
1866
|
+
function add_element_document(doc, el) {
|
|
1867
|
+
const layer = get_layer_by_id_document(doc, el.layer_id);
|
|
1868
|
+
if (layer === null) {
|
|
1869
|
+
return doc;
|
|
1870
|
+
}
|
|
1871
|
+
const new_elements = new Map(doc.elements);
|
|
1872
|
+
new_elements.set(el.id, el);
|
|
1873
|
+
const new_layers = doc.layers.map((l) => {
|
|
1874
|
+
if (l.id === el.layer_id) {
|
|
1875
|
+
return add_element_id_to_layer(l, el.id);
|
|
1876
|
+
}
|
|
1877
|
+
return l;
|
|
1878
|
+
});
|
|
1879
|
+
const new_bounds = calculate_bounds_document_internal(new_elements);
|
|
1880
|
+
return {
|
|
1881
|
+
id: doc.id,
|
|
1882
|
+
elements: new_elements,
|
|
1883
|
+
layers: new_layers,
|
|
1884
|
+
active_layer_id: doc.active_layer_id,
|
|
1885
|
+
assets: doc.assets,
|
|
1886
|
+
bounds: new_bounds,
|
|
1887
|
+
element_count: new_elements.size
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
function remove_element_document(doc, element_id) {
|
|
1891
|
+
const el = doc.elements.get(element_id);
|
|
1892
|
+
if (el === undefined) {
|
|
1893
|
+
return doc;
|
|
1894
|
+
}
|
|
1895
|
+
const new_elements = new Map(doc.elements);
|
|
1896
|
+
new_elements.delete(element_id);
|
|
1897
|
+
const new_layers = doc.layers.map((l) => {
|
|
1898
|
+
if (l.id === el.layer_id) {
|
|
1899
|
+
return remove_element_id_from_layer(l, element_id);
|
|
1900
|
+
}
|
|
1901
|
+
return l;
|
|
1902
|
+
});
|
|
1903
|
+
const new_bounds = calculate_bounds_document_internal(new_elements);
|
|
1904
|
+
return {
|
|
1905
|
+
id: doc.id,
|
|
1906
|
+
elements: new_elements,
|
|
1907
|
+
layers: new_layers,
|
|
1908
|
+
active_layer_id: doc.active_layer_id,
|
|
1909
|
+
assets: doc.assets,
|
|
1910
|
+
bounds: new_bounds,
|
|
1911
|
+
element_count: new_elements.size
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
function update_element_document(doc, element_id, new_element) {
|
|
1915
|
+
if (!doc.elements.has(element_id)) {
|
|
1916
|
+
return doc;
|
|
1917
|
+
}
|
|
1918
|
+
const old_element = doc.elements.get(element_id);
|
|
1919
|
+
if (old_element === undefined) {
|
|
1920
|
+
return doc;
|
|
1921
|
+
}
|
|
1922
|
+
const new_elements = new Map(doc.elements);
|
|
1923
|
+
new_elements.set(element_id, new_element);
|
|
1924
|
+
let new_layers = doc.layers;
|
|
1925
|
+
if (old_element.layer_id !== new_element.layer_id) {
|
|
1926
|
+
new_layers = doc.layers.map((l) => {
|
|
1927
|
+
if (l.id === old_element.layer_id) {
|
|
1928
|
+
return remove_element_id_from_layer(l, element_id);
|
|
1929
|
+
}
|
|
1930
|
+
if (l.id === new_element.layer_id) {
|
|
1931
|
+
return add_element_id_to_layer(l, element_id);
|
|
1932
|
+
}
|
|
1933
|
+
return l;
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
const new_bounds = calculate_bounds_document_internal(new_elements);
|
|
1937
|
+
return {
|
|
1938
|
+
id: doc.id,
|
|
1939
|
+
elements: new_elements,
|
|
1940
|
+
layers: new_layers,
|
|
1941
|
+
active_layer_id: doc.active_layer_id,
|
|
1942
|
+
assets: doc.assets,
|
|
1943
|
+
bounds: new_bounds,
|
|
1944
|
+
element_count: new_elements.size
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
function add_layer_document(doc, layer) {
|
|
1948
|
+
const existing = get_layer_by_id_document(doc, layer.id);
|
|
1949
|
+
if (existing !== null) {
|
|
1950
|
+
return doc;
|
|
1951
|
+
}
|
|
1952
|
+
return {
|
|
1953
|
+
id: doc.id,
|
|
1954
|
+
elements: doc.elements,
|
|
1955
|
+
layers: [...doc.layers, layer],
|
|
1956
|
+
active_layer_id: doc.active_layer_id,
|
|
1957
|
+
assets: doc.assets,
|
|
1958
|
+
bounds: doc.bounds,
|
|
1959
|
+
element_count: doc.element_count
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
function remove_layer_document(doc, layer_id) {
|
|
1963
|
+
const layer = get_layer_by_id_document(doc, layer_id);
|
|
1964
|
+
if (layer === null) {
|
|
1965
|
+
return doc;
|
|
1966
|
+
}
|
|
1967
|
+
const new_elements = new Map(doc.elements);
|
|
1968
|
+
for (const element_id of layer.element_ids) {
|
|
1969
|
+
new_elements.delete(element_id);
|
|
1970
|
+
}
|
|
1971
|
+
const new_layers = doc.layers.filter((l) => l.id !== layer_id);
|
|
1972
|
+
let new_active_layer_id = doc.active_layer_id;
|
|
1973
|
+
if (doc.active_layer_id === layer_id) {
|
|
1974
|
+
if (new_layers.length > 0) {
|
|
1975
|
+
const first_layer = new_layers[0];
|
|
1976
|
+
if (first_layer !== undefined) {
|
|
1977
|
+
new_active_layer_id = first_layer.id;
|
|
1978
|
+
} else {
|
|
1979
|
+
new_active_layer_id = "";
|
|
1980
|
+
}
|
|
1981
|
+
} else {
|
|
1982
|
+
new_active_layer_id = "";
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
const new_bounds = calculate_bounds_document_internal(new_elements);
|
|
1986
|
+
return {
|
|
1987
|
+
id: doc.id,
|
|
1988
|
+
elements: new_elements,
|
|
1989
|
+
layers: new_layers,
|
|
1990
|
+
active_layer_id: new_active_layer_id,
|
|
1991
|
+
assets: doc.assets,
|
|
1992
|
+
bounds: new_bounds,
|
|
1993
|
+
element_count: new_elements.size
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
function reorder_layers_document(doc, layer_ids) {
|
|
1997
|
+
const id_set = new Set(layer_ids);
|
|
1998
|
+
const existing_ids = doc.layers.map((l) => l.id);
|
|
1999
|
+
const missing_ids = existing_ids.filter((id) => !id_set.has(id));
|
|
2000
|
+
const ordered_layers = [];
|
|
2001
|
+
const layer_map = new Map(doc.layers.map((l) => [l.id, l]));
|
|
2002
|
+
for (const id of layer_ids) {
|
|
2003
|
+
const layer = layer_map.get(id);
|
|
2004
|
+
if (layer) {
|
|
2005
|
+
ordered_layers.push(layer);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
for (const id of missing_ids) {
|
|
2009
|
+
const layer = layer_map.get(id);
|
|
2010
|
+
if (layer) {
|
|
2011
|
+
ordered_layers.push(layer);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
return {
|
|
2015
|
+
id: doc.id,
|
|
2016
|
+
elements: doc.elements,
|
|
2017
|
+
layers: ordered_layers,
|
|
2018
|
+
active_layer_id: doc.active_layer_id,
|
|
2019
|
+
assets: doc.assets,
|
|
2020
|
+
bounds: doc.bounds,
|
|
2021
|
+
element_count: doc.element_count
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
function set_active_layer_document(doc, layer_id) {
|
|
2025
|
+
const layer = get_layer_by_id_document(doc, layer_id);
|
|
2026
|
+
if (layer === null) {
|
|
2027
|
+
return doc;
|
|
2028
|
+
}
|
|
2029
|
+
return {
|
|
2030
|
+
id: doc.id,
|
|
2031
|
+
elements: doc.elements,
|
|
2032
|
+
layers: doc.layers,
|
|
2033
|
+
active_layer_id: layer_id,
|
|
2034
|
+
assets: doc.assets,
|
|
2035
|
+
bounds: doc.bounds,
|
|
2036
|
+
element_count: doc.element_count
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
function get_elements_in_layer_document(doc, layer_id) {
|
|
2040
|
+
const layer = get_layer_by_id_document(doc, layer_id);
|
|
2041
|
+
if (layer === null) {
|
|
2042
|
+
return [];
|
|
2043
|
+
}
|
|
2044
|
+
const elements = [];
|
|
2045
|
+
for (const element_id of layer.element_ids) {
|
|
2046
|
+
const el = doc.elements.get(element_id);
|
|
2047
|
+
if (el !== undefined) {
|
|
2048
|
+
elements.push(el);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
return elements;
|
|
2052
|
+
}
|
|
2053
|
+
function get_all_elements_document(doc) {
|
|
2054
|
+
return Array.from(doc.elements.values());
|
|
2055
|
+
}
|
|
2056
|
+
function calculate_bounds_document_internal(elements) {
|
|
2057
|
+
if (elements.size === 0) {
|
|
2058
|
+
return [0, 0, 0, 0];
|
|
2059
|
+
}
|
|
2060
|
+
let min_x = Infinity;
|
|
2061
|
+
let min_y = Infinity;
|
|
2062
|
+
let max_x = -Infinity;
|
|
2063
|
+
let max_y = -Infinity;
|
|
2064
|
+
for (const el of elements.values()) {
|
|
2065
|
+
const bounds = el.bounds;
|
|
2066
|
+
min_x = Math.min(min_x, bounds[0]);
|
|
2067
|
+
min_y = Math.min(min_y, bounds[1]);
|
|
2068
|
+
max_x = Math.max(max_x, bounds[0] + bounds[2]);
|
|
2069
|
+
max_y = Math.max(max_y, bounds[1] + bounds[3]);
|
|
2070
|
+
}
|
|
2071
|
+
if (min_x === Infinity) {
|
|
2072
|
+
return [0, 0, 0, 0];
|
|
2073
|
+
}
|
|
2074
|
+
return [min_x, min_y, max_x - min_x, max_y - min_y];
|
|
2075
|
+
}
|
|
2076
|
+
function recalculate_bounds_document(doc) {
|
|
2077
|
+
const new_bounds = calculate_bounds_document_internal(doc.elements);
|
|
2078
|
+
return {
|
|
2079
|
+
id: doc.id,
|
|
2080
|
+
elements: doc.elements,
|
|
2081
|
+
layers: doc.layers,
|
|
2082
|
+
active_layer_id: doc.active_layer_id,
|
|
2083
|
+
assets: doc.assets,
|
|
2084
|
+
bounds: new_bounds,
|
|
2085
|
+
element_count: doc.element_count
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
function clear_document(doc) {
|
|
2089
|
+
return {
|
|
2090
|
+
id: doc.id,
|
|
2091
|
+
elements: new Map,
|
|
2092
|
+
layers: doc.layers.map((l) => ({ ...l, element_ids: [] })),
|
|
2093
|
+
active_layer_id: doc.active_layer_id,
|
|
2094
|
+
assets: doc.assets,
|
|
2095
|
+
bounds: [0, 0, 0, 0],
|
|
2096
|
+
element_count: 0
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
function add_image_asset_document(doc, asset) {
|
|
2100
|
+
const assets = new Map(doc.assets);
|
|
2101
|
+
assets.set(asset.id, { ...asset });
|
|
2102
|
+
return {
|
|
2103
|
+
...doc,
|
|
2104
|
+
assets
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
function get_image_asset_document(doc, asset_id) {
|
|
2108
|
+
return doc.assets.get(asset_id) ?? null;
|
|
2109
|
+
}
|
|
2110
|
+
function remove_image_asset_document(doc, asset_id) {
|
|
2111
|
+
if (!doc.assets.has(asset_id))
|
|
2112
|
+
return doc;
|
|
2113
|
+
const assets = new Map(doc.assets);
|
|
2114
|
+
assets.delete(asset_id);
|
|
2115
|
+
return {
|
|
2116
|
+
...doc,
|
|
2117
|
+
assets
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
// editor/history.ts
|
|
2122
|
+
function begin_transaction_editor(state, label) {
|
|
2123
|
+
if (state.active_transaction !== null) {
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
state.active_transaction = {
|
|
2127
|
+
label,
|
|
2128
|
+
before: clone_document(state.engine.document)
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
function commit_transaction_editor(state) {
|
|
2132
|
+
if (state.active_transaction === null) {
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
const before = state.active_transaction.before;
|
|
2136
|
+
const after = state.engine.document;
|
|
2137
|
+
if (before !== after) {
|
|
2138
|
+
state.undo_stack.push(before);
|
|
2139
|
+
state.redo_stack = [];
|
|
2140
|
+
}
|
|
2141
|
+
state.active_transaction = null;
|
|
2142
|
+
}
|
|
2143
|
+
function cancel_transaction_editor(state) {
|
|
2144
|
+
if (state.active_transaction === null) {
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2147
|
+
state.engine.document = state.active_transaction.before;
|
|
2148
|
+
state.active_transaction = null;
|
|
2149
|
+
}
|
|
2150
|
+
function undo_editor(state) {
|
|
2151
|
+
if (state.active_transaction !== null) {
|
|
2152
|
+
return false;
|
|
2153
|
+
}
|
|
2154
|
+
const previous = state.undo_stack.pop();
|
|
2155
|
+
if (previous === undefined) {
|
|
2156
|
+
return false;
|
|
2157
|
+
}
|
|
2158
|
+
state.redo_stack.push(clone_document(state.engine.document));
|
|
2159
|
+
state.engine.document = previous;
|
|
2160
|
+
return true;
|
|
2161
|
+
}
|
|
2162
|
+
function redo_editor(state) {
|
|
2163
|
+
if (state.active_transaction !== null) {
|
|
2164
|
+
return false;
|
|
2165
|
+
}
|
|
2166
|
+
const next = state.redo_stack.pop();
|
|
2167
|
+
if (next === undefined) {
|
|
2168
|
+
return false;
|
|
2169
|
+
}
|
|
2170
|
+
state.undo_stack.push(clone_document(state.engine.document));
|
|
2171
|
+
state.engine.document = next;
|
|
2172
|
+
return true;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
// editor/clone.ts
|
|
2176
|
+
function clone_element_editor(el) {
|
|
2177
|
+
if (el.type === element_type_stroke) {
|
|
2178
|
+
const stroke = el;
|
|
2179
|
+
return {
|
|
2180
|
+
...stroke,
|
|
2181
|
+
bounds: [stroke.bounds[0], stroke.bounds[1], stroke.bounds[2], stroke.bounds[3]],
|
|
2182
|
+
points: stroke.points.map((point) => [point[0], point[1]]),
|
|
2183
|
+
pressure: stroke.pressure === null ? null : [...stroke.pressure],
|
|
2184
|
+
color: [stroke.color[0], stroke.color[1], stroke.color[2], stroke.color[3]],
|
|
2185
|
+
simplified_points: stroke.simplified_points === null ? null : stroke.simplified_points.map((point) => [point[0], point[1]]),
|
|
2186
|
+
spline: stroke.spline
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
if (el.type === element_type_shape) {
|
|
2190
|
+
const shape = el;
|
|
2191
|
+
return {
|
|
2192
|
+
...shape,
|
|
2193
|
+
bounds: [shape.bounds[0], shape.bounds[1], shape.bounds[2], shape.bounds[3]],
|
|
2194
|
+
fill_color: shape.fill_color === null ? null : [shape.fill_color[0], shape.fill_color[1], shape.fill_color[2], shape.fill_color[3]],
|
|
2195
|
+
stroke_color: shape.stroke_color === null ? null : [
|
|
2196
|
+
shape.stroke_color[0],
|
|
2197
|
+
shape.stroke_color[1],
|
|
2198
|
+
shape.stroke_color[2],
|
|
2199
|
+
shape.stroke_color[3]
|
|
2200
|
+
],
|
|
2201
|
+
start_point: shape.start_point === null ? null : [shape.start_point[0], shape.start_point[1]],
|
|
2202
|
+
end_point: shape.end_point === null ? null : [shape.end_point[0], shape.end_point[1]]
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
if (el.type === element_type_image) {
|
|
2206
|
+
const image = el;
|
|
2207
|
+
return {
|
|
2208
|
+
...image,
|
|
2209
|
+
bounds: [image.bounds[0], image.bounds[1], image.bounds[2], image.bounds[3]]
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
const text = el;
|
|
2213
|
+
return {
|
|
2214
|
+
...text,
|
|
2215
|
+
bounds: [text.bounds[0], text.bounds[1], text.bounds[2], text.bounds[3]],
|
|
2216
|
+
color: [text.color[0], text.color[1], text.color[2], text.color[3]]
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
// math/simplify.ts
|
|
2221
|
+
function create_simplify_result() {
|
|
2222
|
+
return {
|
|
2223
|
+
points: [],
|
|
2224
|
+
original_count: 0,
|
|
2225
|
+
simplified_count: 0,
|
|
2226
|
+
reduction_ratio: 0
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
function clone_simplify_result(result) {
|
|
2230
|
+
return {
|
|
2231
|
+
points: result.points.map((point) => [point[0], point[1]]),
|
|
2232
|
+
original_count: result.original_count,
|
|
2233
|
+
simplified_count: result.simplified_count,
|
|
2234
|
+
reduction_ratio: result.reduction_ratio
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
function copy_simplify_result(result, out) {
|
|
2238
|
+
out.points = result.points.map((point) => [point[0], point[1]]);
|
|
2239
|
+
out.original_count = result.original_count;
|
|
2240
|
+
out.simplified_count = result.simplified_count;
|
|
2241
|
+
out.reduction_ratio = result.reduction_ratio;
|
|
2242
|
+
return out;
|
|
2243
|
+
}
|
|
2244
|
+
function perpendicular_distance_to_line(point, line_start, line_end) {
|
|
2245
|
+
const dx = line_end[0] - line_start[0];
|
|
2246
|
+
const dy = line_end[1] - line_start[1];
|
|
2247
|
+
if (dx === 0 && dy === 0) {
|
|
2248
|
+
const px = point[0] - line_start[0];
|
|
2249
|
+
const py = point[1] - line_start[1];
|
|
2250
|
+
return Math.sqrt(px * px + py * py);
|
|
2251
|
+
}
|
|
2252
|
+
const numerator = Math.abs(dy * point[0] - dx * point[1] + line_end[0] * line_start[1] - line_end[1] * line_start[0]);
|
|
2253
|
+
const denominator = Math.sqrt(dx * dx + dy * dy);
|
|
2254
|
+
return numerator / denominator;
|
|
2255
|
+
}
|
|
2256
|
+
function find_farthest_point(points, start_index, end_index, out_result) {
|
|
2257
|
+
let max_distance = 0;
|
|
2258
|
+
let farthest_index = -1;
|
|
2259
|
+
const line_start = points[start_index];
|
|
2260
|
+
const line_end = points[end_index];
|
|
2261
|
+
for (let i = start_index + 1;i < end_index; i++) {
|
|
2262
|
+
const distance = perpendicular_distance_to_line(points[i], line_start, line_end);
|
|
2263
|
+
if (distance > max_distance) {
|
|
2264
|
+
max_distance = distance;
|
|
2265
|
+
farthest_index = i;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
out_result.index = farthest_index;
|
|
2269
|
+
out_result.distance = max_distance;
|
|
2270
|
+
return out_result;
|
|
2271
|
+
}
|
|
2272
|
+
function douglas_peucker_recursive(points, start_index, end_index, epsilon, keep_flags) {
|
|
2273
|
+
if (end_index <= start_index + 1) {
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
const farthest_result = { index: -1, distance: 0 };
|
|
2277
|
+
find_farthest_point(points, start_index, end_index, farthest_result);
|
|
2278
|
+
if (farthest_result.index === -1 || farthest_result.distance <= epsilon) {
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
keep_flags[farthest_result.index] = true;
|
|
2282
|
+
douglas_peucker_recursive(points, start_index, farthest_result.index, epsilon, keep_flags);
|
|
2283
|
+
douglas_peucker_recursive(points, farthest_result.index, end_index, epsilon, keep_flags);
|
|
2284
|
+
}
|
|
2285
|
+
function simplify_douglas_peucker(points, epsilon, out_points) {
|
|
2286
|
+
if (points.length <= 2) {
|
|
2287
|
+
for (let i = 0;i < points.length; i++) {
|
|
2288
|
+
const p = points[i];
|
|
2289
|
+
const out = out_points[i];
|
|
2290
|
+
if (out) {
|
|
2291
|
+
out[0] = p[0];
|
|
2292
|
+
out[1] = p[1];
|
|
2293
|
+
} else {
|
|
2294
|
+
out_points[i] = [p[0], p[1]];
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
return points.length;
|
|
2298
|
+
}
|
|
2299
|
+
const keep_flags = Array.from({ length: points.length }, () => false);
|
|
2300
|
+
keep_flags[0] = true;
|
|
2301
|
+
keep_flags[points.length - 1] = true;
|
|
2302
|
+
douglas_peucker_recursive(points, 0, points.length - 1, epsilon, keep_flags);
|
|
2303
|
+
let result_count = 0;
|
|
2304
|
+
for (let i = 0;i < points.length; i++) {
|
|
2305
|
+
if (keep_flags[i]) {
|
|
2306
|
+
const p = points[i];
|
|
2307
|
+
const out = out_points[result_count];
|
|
2308
|
+
if (out) {
|
|
2309
|
+
out[0] = p[0];
|
|
2310
|
+
out[1] = p[1];
|
|
2311
|
+
} else {
|
|
2312
|
+
out_points[result_count] = [p[0], p[1]];
|
|
2313
|
+
}
|
|
2314
|
+
result_count = result_count + 1;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
return result_count;
|
|
2318
|
+
}
|
|
2319
|
+
function simplify_douglas_peucker_detailed(points, epsilon, out_result) {
|
|
2320
|
+
const temp_points = Array.from({ length: points.length }, () => [0, 0]);
|
|
2321
|
+
const simplified_count = simplify_douglas_peucker(points, epsilon, temp_points);
|
|
2322
|
+
out_result.points = temp_points.slice(0, simplified_count);
|
|
2323
|
+
out_result.original_count = points.length;
|
|
2324
|
+
out_result.simplified_count = simplified_count;
|
|
2325
|
+
out_result.reduction_ratio = points.length > 0 ? (points.length - simplified_count) / points.length : 0;
|
|
2326
|
+
return out_result;
|
|
2327
|
+
}
|
|
2328
|
+
function estimate_epsilon_from_points(points, tolerance_factor = 0.1) {
|
|
2329
|
+
if (points.length < 2) {
|
|
2330
|
+
return 1;
|
|
2331
|
+
}
|
|
2332
|
+
let total_distance = 0;
|
|
2333
|
+
let segment_count = 0;
|
|
2334
|
+
for (let i = 1;i < points.length; i++) {
|
|
2335
|
+
const dx = points[i][0] - points[i - 1][0];
|
|
2336
|
+
const dy = points[i][1] - points[i - 1][1];
|
|
2337
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
2338
|
+
if (distance > 0) {
|
|
2339
|
+
total_distance = total_distance + distance;
|
|
2340
|
+
segment_count = segment_count + 1;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
if (segment_count === 0) {
|
|
2344
|
+
return 1;
|
|
2345
|
+
}
|
|
2346
|
+
const average_distance = total_distance / segment_count;
|
|
2347
|
+
return average_distance * tolerance_factor;
|
|
2348
|
+
}
|
|
2349
|
+
function polyline_length(points) {
|
|
2350
|
+
if (points.length < 2) {
|
|
2351
|
+
return 0;
|
|
2352
|
+
}
|
|
2353
|
+
let total_length = 0;
|
|
2354
|
+
for (let i = 1;i < points.length; i++) {
|
|
2355
|
+
const dx = points[i][0] - points[i - 1][0];
|
|
2356
|
+
const dy = points[i][1] - points[i - 1][1];
|
|
2357
|
+
total_length = total_length + Math.sqrt(dx * dx + dy * dy);
|
|
2358
|
+
}
|
|
2359
|
+
return total_length;
|
|
2360
|
+
}
|
|
2361
|
+
function polyline_area_under_curve(points) {
|
|
2362
|
+
if (points.length < 2) {
|
|
2363
|
+
return 0;
|
|
2364
|
+
}
|
|
2365
|
+
let area = 0;
|
|
2366
|
+
for (let i = 1;i < points.length; i++) {
|
|
2367
|
+
const x1 = points[i - 1][0];
|
|
2368
|
+
const y1 = points[i - 1][1];
|
|
2369
|
+
const x2 = points[i][0];
|
|
2370
|
+
const y2 = points[i][1];
|
|
2371
|
+
area = area + (x2 - x1) * (y1 + y2) * 0.5;
|
|
2372
|
+
}
|
|
2373
|
+
return Math.abs(area);
|
|
2374
|
+
}
|
|
2375
|
+
function validate_simplified_polyline(points) {
|
|
2376
|
+
if (points.length < 4) {
|
|
2377
|
+
return true;
|
|
2378
|
+
}
|
|
2379
|
+
for (let i = 0;i < points.length - 3; i++) {
|
|
2380
|
+
for (let j = i + 2;j < points.length - 1; j++) {
|
|
2381
|
+
const p1 = points[i];
|
|
2382
|
+
const p2 = points[i + 1];
|
|
2383
|
+
const p3 = points[j];
|
|
2384
|
+
const p4 = points[j + 1];
|
|
2385
|
+
if (line_segments_intersect(p1, p2, p3, p4)) {
|
|
2386
|
+
return false;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
return true;
|
|
2391
|
+
}
|
|
2392
|
+
function line_segments_intersect(p1, p2, p3, p4) {
|
|
2393
|
+
const d1 = direction(p3, p4, p1);
|
|
2394
|
+
const d2 = direction(p3, p4, p2);
|
|
2395
|
+
const d3 = direction(p1, p2, p3);
|
|
2396
|
+
const d4 = direction(p1, p2, p4);
|
|
2397
|
+
if ((d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0)) {
|
|
2398
|
+
return true;
|
|
2399
|
+
}
|
|
2400
|
+
if (d1 === 0 && on_segment(p3, p1, p4))
|
|
2401
|
+
return true;
|
|
2402
|
+
if (d2 === 0 && on_segment(p3, p2, p4))
|
|
2403
|
+
return true;
|
|
2404
|
+
if (d3 === 0 && on_segment(p1, p3, p2))
|
|
2405
|
+
return true;
|
|
2406
|
+
if (d4 === 0 && on_segment(p1, p4, p2))
|
|
2407
|
+
return true;
|
|
2408
|
+
return false;
|
|
2409
|
+
}
|
|
2410
|
+
function direction(pi, pj, pk) {
|
|
2411
|
+
return (pk[0] - pi[0]) * (pj[1] - pi[1]) - (pj[0] - pi[0]) * (pk[1] - pi[1]);
|
|
2412
|
+
}
|
|
2413
|
+
function on_segment(pi, pj, pk) {
|
|
2414
|
+
return Math.min(pi[0], pk[0]) <= pj[0] && pj[0] <= Math.max(pi[0], pk[0]) && Math.min(pi[1], pk[1]) <= pj[1] && pj[1] <= Math.max(pi[1], pk[1]);
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// math/spline.ts
|
|
2418
|
+
function create_spline() {
|
|
2419
|
+
return {
|
|
2420
|
+
segments_x: [],
|
|
2421
|
+
segments_y: [],
|
|
2422
|
+
point_count: 0
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
function clone_spline(s) {
|
|
2426
|
+
return {
|
|
2427
|
+
segments_x: s.segments_x.map((seg) => ({ ...seg })),
|
|
2428
|
+
segments_y: s.segments_y.map((seg) => ({ ...seg })),
|
|
2429
|
+
point_count: s.point_count
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
function build_natural_spline_1d(points, t_values) {
|
|
2433
|
+
const n = points.length;
|
|
2434
|
+
if (n < 2)
|
|
2435
|
+
return [];
|
|
2436
|
+
if (n === 2) {
|
|
2437
|
+
const slope = (points[1] - points[0]) / (t_values[1] - t_values[0]);
|
|
2438
|
+
return [
|
|
2439
|
+
{
|
|
2440
|
+
a: points[0],
|
|
2441
|
+
b: slope,
|
|
2442
|
+
c: 0,
|
|
2443
|
+
d: 0,
|
|
2444
|
+
x_start: t_values[0],
|
|
2445
|
+
x_end: t_values[1]
|
|
2446
|
+
}
|
|
2447
|
+
];
|
|
2448
|
+
}
|
|
2449
|
+
const h = Array.from({ length: n - 1 }, () => 0);
|
|
2450
|
+
for (let i = 0;i < n - 1; i++) {
|
|
2451
|
+
h[i] = t_values[i + 1] - t_values[i];
|
|
2452
|
+
}
|
|
2453
|
+
const alpha = Array.from({ length: n - 1 }, () => 0);
|
|
2454
|
+
for (let i = 1;i < n - 1; i++) {
|
|
2455
|
+
alpha[i] = 3 / h[i] * (points[i + 1] - points[i]) - 3 / h[i - 1] * (points[i] - points[i - 1]);
|
|
2456
|
+
}
|
|
2457
|
+
const l = Array.from({ length: n }, () => 0);
|
|
2458
|
+
const mu = Array.from({ length: n }, () => 0);
|
|
2459
|
+
const z = Array.from({ length: n }, () => 0);
|
|
2460
|
+
l[0] = 1;
|
|
2461
|
+
mu[0] = 0;
|
|
2462
|
+
z[0] = 0;
|
|
2463
|
+
for (let i = 1;i < n - 1; i++) {
|
|
2464
|
+
l[i] = 2 * (t_values[i + 1] - t_values[i - 1]) - h[i - 1] * mu[i - 1];
|
|
2465
|
+
mu[i] = h[i] / l[i];
|
|
2466
|
+
z[i] = (alpha[i] - h[i - 1] * z[i - 1]) / l[i];
|
|
2467
|
+
}
|
|
2468
|
+
l[n - 1] = 1;
|
|
2469
|
+
z[n - 1] = 0;
|
|
2470
|
+
const c = Array.from({ length: n }, () => 0);
|
|
2471
|
+
c[n - 1] = 0;
|
|
2472
|
+
for (let j = n - 2;j >= 0; j--) {
|
|
2473
|
+
c[j] = z[j] - mu[j] * c[j + 1];
|
|
2474
|
+
}
|
|
2475
|
+
const segments = [];
|
|
2476
|
+
for (let i = 0;i < n - 1; i++) {
|
|
2477
|
+
const a = points[i];
|
|
2478
|
+
const b = (points[i + 1] - points[i]) / h[i] - h[i] * (c[i + 1] + 2 * c[i]) / 3;
|
|
2479
|
+
const d = (c[i + 1] - c[i]) / (3 * h[i]);
|
|
2480
|
+
segments.push({
|
|
2481
|
+
a,
|
|
2482
|
+
b,
|
|
2483
|
+
c: c[i],
|
|
2484
|
+
d,
|
|
2485
|
+
x_start: t_values[i],
|
|
2486
|
+
x_end: t_values[i + 1]
|
|
2487
|
+
});
|
|
2488
|
+
}
|
|
2489
|
+
return segments;
|
|
2490
|
+
}
|
|
2491
|
+
function build_natural_spline_from_points(points, spline) {
|
|
2492
|
+
const n = points.length;
|
|
2493
|
+
if (n < 2) {
|
|
2494
|
+
spline.segments_x = [];
|
|
2495
|
+
spline.segments_y = [];
|
|
2496
|
+
spline.point_count = 0;
|
|
2497
|
+
return spline;
|
|
2498
|
+
}
|
|
2499
|
+
const t_values = Array.from({ length: n }, () => 0);
|
|
2500
|
+
t_values[0] = 0;
|
|
2501
|
+
for (let i = 1;i < n; i++) {
|
|
2502
|
+
const dx = points[i][0] - points[i - 1][0];
|
|
2503
|
+
const dy = points[i][1] - points[i - 1][1];
|
|
2504
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
2505
|
+
t_values[i] = t_values[i - 1] + distance;
|
|
2506
|
+
}
|
|
2507
|
+
const x_coords = points.map((p) => p[0]);
|
|
2508
|
+
const y_coords = points.map((p) => p[1]);
|
|
2509
|
+
spline.segments_x = build_natural_spline_1d(x_coords, t_values);
|
|
2510
|
+
spline.segments_y = build_natural_spline_1d(y_coords, t_values);
|
|
2511
|
+
spline.point_count = n;
|
|
2512
|
+
return spline;
|
|
2513
|
+
}
|
|
2514
|
+
function evaluate_spline_segment(segment, t) {
|
|
2515
|
+
const dt = t - segment.x_start;
|
|
2516
|
+
return segment.a + segment.b * dt + segment.c * dt * dt + segment.d * dt * dt * dt;
|
|
2517
|
+
}
|
|
2518
|
+
function evaluate_spline_at_t(spline, t, out) {
|
|
2519
|
+
if (spline.segments_x.length === 0 || spline.segments_y.length === 0) {
|
|
2520
|
+
return null;
|
|
2521
|
+
}
|
|
2522
|
+
let segment_x = null;
|
|
2523
|
+
let segment_y = null;
|
|
2524
|
+
for (let i = 0;i < spline.segments_x.length; i++) {
|
|
2525
|
+
const seg_x = spline.segments_x[i];
|
|
2526
|
+
const seg_y = spline.segments_y[i];
|
|
2527
|
+
if (t >= seg_x.x_start && t <= seg_x.x_end) {
|
|
2528
|
+
segment_x = seg_x;
|
|
2529
|
+
segment_y = seg_y;
|
|
2530
|
+
break;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
if (!segment_x || !segment_y) {
|
|
2534
|
+
const last_idx = spline.segments_x.length - 1;
|
|
2535
|
+
if (t > spline.segments_x[last_idx].x_end) {
|
|
2536
|
+
segment_x = spline.segments_x[last_idx];
|
|
2537
|
+
segment_y = spline.segments_y[last_idx];
|
|
2538
|
+
t = segment_x.x_end;
|
|
2539
|
+
} else {
|
|
2540
|
+
segment_x = spline.segments_x[0];
|
|
2541
|
+
segment_y = spline.segments_y[0];
|
|
2542
|
+
t = segment_x.x_start;
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
out[0] = evaluate_spline_segment(segment_x, t);
|
|
2546
|
+
out[1] = evaluate_spline_segment(segment_y, t);
|
|
2547
|
+
return out;
|
|
2548
|
+
}
|
|
2549
|
+
function evaluate_spline_at_points(spline, t_values, out_points) {
|
|
2550
|
+
let valid_count = 0;
|
|
2551
|
+
const temp_point = [0, 0];
|
|
2552
|
+
for (let i = 0;i < t_values.length; i++) {
|
|
2553
|
+
const t_val = t_values[i];
|
|
2554
|
+
if (evaluate_spline_at_t(spline, t_val, temp_point)) {
|
|
2555
|
+
out_points[valid_count][0] = temp_point[0];
|
|
2556
|
+
out_points[valid_count][1] = temp_point[1];
|
|
2557
|
+
valid_count = valid_count + 1;
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
return valid_count;
|
|
2561
|
+
}
|
|
2562
|
+
function get_spline_length(spline, steps = 100) {
|
|
2563
|
+
if (spline.segments_x.length === 0)
|
|
2564
|
+
return 0;
|
|
2565
|
+
const first_segment = spline.segments_x[0];
|
|
2566
|
+
const last_segment = spline.segments_x[spline.segments_x.length - 1];
|
|
2567
|
+
const t_start = first_segment.x_start;
|
|
2568
|
+
const t_end = last_segment.x_end;
|
|
2569
|
+
const dt = (t_end - t_start) / steps;
|
|
2570
|
+
let length = 0;
|
|
2571
|
+
const prev = [0, 0];
|
|
2572
|
+
const curr = [0, 0];
|
|
2573
|
+
evaluate_spline_at_t(spline, t_start, prev);
|
|
2574
|
+
for (let i = 1;i <= steps; i++) {
|
|
2575
|
+
const t = t_start + i * dt;
|
|
2576
|
+
if (evaluate_spline_at_t(spline, t, curr)) {
|
|
2577
|
+
const dx = curr[0] - prev[0];
|
|
2578
|
+
const dy = curr[1] - prev[1];
|
|
2579
|
+
length = length + Math.sqrt(dx * dx + dy * dy);
|
|
2580
|
+
prev[0] = curr[0];
|
|
2581
|
+
prev[1] = curr[1];
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
return length;
|
|
2585
|
+
}
|
|
2586
|
+
function simplify_points_for_spline(points, tolerance = 1) {
|
|
2587
|
+
if (points.length <= 2)
|
|
2588
|
+
return [...points];
|
|
2589
|
+
const simplified = [points[0]];
|
|
2590
|
+
for (let i = 1;i < points.length - 1; i++) {
|
|
2591
|
+
const prev = points[i - 1];
|
|
2592
|
+
const curr = points[i];
|
|
2593
|
+
const next = points[i + 1];
|
|
2594
|
+
const dx1 = curr[0] - prev[0];
|
|
2595
|
+
const dy1 = curr[1] - prev[1];
|
|
2596
|
+
const dx2 = next[0] - curr[0];
|
|
2597
|
+
const dy2 = next[1] - curr[1];
|
|
2598
|
+
const dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
|
|
2599
|
+
const dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
|
2600
|
+
if (dist1 < tolerance && dist2 < tolerance) {
|
|
2601
|
+
if (dist1 > 0 && dist2 > 0) {
|
|
2602
|
+
const dot = (dx1 * dx2 + dy1 * dy2) / (dist1 * dist2);
|
|
2603
|
+
if (dot > 0.99) {
|
|
2604
|
+
continue;
|
|
2605
|
+
}
|
|
2606
|
+
} else {
|
|
2607
|
+
continue;
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
simplified.push(curr);
|
|
2611
|
+
}
|
|
2612
|
+
simplified.push(points[points.length - 1]);
|
|
2613
|
+
return simplified;
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
// stroke/process.ts
|
|
2617
|
+
var default_simplify_epsilon = 1;
|
|
2618
|
+
var default_lod_simplify_factor = 3;
|
|
2619
|
+
function simplify_stroke_points(points, epsilon = default_simplify_epsilon, out) {
|
|
2620
|
+
return simplify_douglas_peucker(points, epsilon, out);
|
|
2621
|
+
}
|
|
2622
|
+
function simplify_stroke_points_auto(points, out) {
|
|
2623
|
+
const epsilon = estimate_epsilon_from_points(points, 0.1);
|
|
2624
|
+
return simplify_douglas_peucker(points, epsilon, out);
|
|
2625
|
+
}
|
|
2626
|
+
function fit_spline_to_stroke(points, out_spline) {
|
|
2627
|
+
if (points.length < 2) {
|
|
2628
|
+
out_spline.segments_x = [];
|
|
2629
|
+
out_spline.segments_y = [];
|
|
2630
|
+
out_spline.point_count = 0;
|
|
2631
|
+
return out_spline;
|
|
2632
|
+
}
|
|
2633
|
+
const simplified_for_spline = simplify_points_for_spline(points, 2);
|
|
2634
|
+
return build_natural_spline_from_points(simplified_for_spline, out_spline);
|
|
2635
|
+
}
|
|
2636
|
+
function compute_stroke_bounds(points, width, out) {
|
|
2637
|
+
if (points.length === 0) {
|
|
2638
|
+
out[0] = 0;
|
|
2639
|
+
out[1] = 0;
|
|
2640
|
+
out[2] = 0;
|
|
2641
|
+
out[3] = 0;
|
|
2642
|
+
return out;
|
|
2643
|
+
}
|
|
2644
|
+
let min_x = Infinity;
|
|
2645
|
+
let min_y = Infinity;
|
|
2646
|
+
let max_x = -Infinity;
|
|
2647
|
+
let max_y = -Infinity;
|
|
2648
|
+
for (let i = 0;i < points.length; i++) {
|
|
2649
|
+
const point = points[i];
|
|
2650
|
+
if (point === undefined)
|
|
2651
|
+
continue;
|
|
2652
|
+
min_x = Math.min(min_x, point[0]);
|
|
2653
|
+
min_y = Math.min(min_y, point[1]);
|
|
2654
|
+
max_x = Math.max(max_x, point[0]);
|
|
2655
|
+
max_y = Math.max(max_y, point[1]);
|
|
2656
|
+
}
|
|
2657
|
+
const half_width = width / 2;
|
|
2658
|
+
out[0] = min_x - half_width;
|
|
2659
|
+
out[1] = min_y - half_width;
|
|
2660
|
+
out[2] = max_x - min_x + width;
|
|
2661
|
+
out[3] = max_y - min_y + width;
|
|
2662
|
+
return out;
|
|
2663
|
+
}
|
|
2664
|
+
function process_stroke(points, pressure, style, out_simplified, out_spline) {
|
|
2665
|
+
let epsilon = default_simplify_epsilon;
|
|
2666
|
+
if (pressure && pressure.length > 1 && style.pressure_sensitivity > 0) {
|
|
2667
|
+
const variance = calculate_pressure_variance(pressure);
|
|
2668
|
+
epsilon = epsilon / (1 + variance * style.pressure_sensitivity * 2);
|
|
2669
|
+
}
|
|
2670
|
+
const simplified_count = simplify_stroke_points(points, epsilon, out_simplified);
|
|
2671
|
+
const simplified = out_simplified.slice(0, simplified_count);
|
|
2672
|
+
fit_spline_to_stroke(simplified, out_spline);
|
|
2673
|
+
return {
|
|
2674
|
+
simplified,
|
|
2675
|
+
spline: out_spline
|
|
2676
|
+
};
|
|
2677
|
+
}
|
|
2678
|
+
function calculate_pressure_variance(pressure) {
|
|
2679
|
+
if (pressure.length < 2)
|
|
2680
|
+
return 0;
|
|
2681
|
+
let sum = 0;
|
|
2682
|
+
let sum_sq = 0;
|
|
2683
|
+
for (let i = 0;i < pressure.length; i++) {
|
|
2684
|
+
const p = pressure[i];
|
|
2685
|
+
sum += p;
|
|
2686
|
+
sum_sq += p * p;
|
|
2687
|
+
}
|
|
2688
|
+
const mean = sum / pressure.length;
|
|
2689
|
+
return Math.sqrt(Math.max(0, sum_sq / pressure.length - mean * mean));
|
|
2690
|
+
}
|
|
2691
|
+
function get_simplified_for_lod(points, lod_level, out) {
|
|
2692
|
+
const lod_epsilon = default_simplify_epsilon * (lod_level * default_lod_simplify_factor);
|
|
2693
|
+
const simplified_count = simplify_douglas_peucker(points, lod_epsilon, out);
|
|
2694
|
+
return out.slice(0, simplified_count);
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
// editor/transform.ts
|
|
2698
|
+
function move_element_editor(el, dx, dy) {
|
|
2699
|
+
return apply_transform_element_editor(el, (point) => [point[0] + dx, point[1] + dy]);
|
|
2700
|
+
}
|
|
2701
|
+
function transform_elements_editor(base_document, ids, transform_point) {
|
|
2702
|
+
let doc = base_document;
|
|
2703
|
+
for (let i = 0;i < ids.length; i = i + 1) {
|
|
2704
|
+
const id = ids[i];
|
|
2705
|
+
if (id === undefined)
|
|
2706
|
+
continue;
|
|
2707
|
+
const base_element = get_element_by_id_document(base_document, id);
|
|
2708
|
+
if (base_element === null)
|
|
2709
|
+
continue;
|
|
2710
|
+
doc = update_element_document(doc, id, apply_transform_element_editor(base_element, transform_point));
|
|
2711
|
+
}
|
|
2712
|
+
return doc;
|
|
2713
|
+
}
|
|
2714
|
+
function angle_to_center_editor(center, point) {
|
|
2715
|
+
return Math.atan2(point[1] - center[1], point[0] - center[0]);
|
|
2716
|
+
}
|
|
2717
|
+
function apply_transform_element_editor(el, map_point) {
|
|
2718
|
+
if (el.type === element_type_stroke)
|
|
2719
|
+
return transform_stroke_editor(el, map_point);
|
|
2720
|
+
if (el.type === element_type_shape)
|
|
2721
|
+
return transform_shape_editor(el, map_point);
|
|
2722
|
+
if (el.type === element_type_image)
|
|
2723
|
+
return transform_image_editor(el, map_point);
|
|
2724
|
+
if (el.type === element_type_text)
|
|
2725
|
+
return transform_text_editor(el, map_point);
|
|
2726
|
+
return clone_element_editor(el);
|
|
2727
|
+
}
|
|
2728
|
+
function transform_stroke_editor(stroke, map_point) {
|
|
2729
|
+
const out = clone_element_editor(stroke);
|
|
2730
|
+
out.points = out.points.map((point) => map_point(point));
|
|
2731
|
+
out.simplified_points = out.simplified_points === null ? null : out.simplified_points.map((point) => map_point(point));
|
|
2732
|
+
const bounds = [0, 0, 0, 0];
|
|
2733
|
+
compute_stroke_bounds(out.points, out.width, bounds);
|
|
2734
|
+
out.bounds = bounds;
|
|
2735
|
+
out.spline = null;
|
|
2736
|
+
return out;
|
|
2737
|
+
}
|
|
2738
|
+
function transform_shape_editor(shape, map_point) {
|
|
2739
|
+
const out = clone_element_editor(shape);
|
|
2740
|
+
if ((out.shape_type === shape_type_line || out.shape_type === shape_type_arrow) && out.start_point !== null && out.end_point !== null) {
|
|
2741
|
+
out.start_point = map_point(out.start_point);
|
|
2742
|
+
out.end_point = map_point(out.end_point);
|
|
2743
|
+
out.bounds = bounds_from_points_editor([out.start_point, out.end_point]);
|
|
2744
|
+
return out;
|
|
2745
|
+
}
|
|
2746
|
+
out.bounds = transform_bounds_editor(out.bounds, map_point);
|
|
2747
|
+
return out;
|
|
2748
|
+
}
|
|
2749
|
+
function transform_image_editor(image, map_point) {
|
|
2750
|
+
const out = clone_element_editor(image);
|
|
2751
|
+
out.bounds = transform_bounds_editor(out.bounds, map_point);
|
|
2752
|
+
return out;
|
|
2753
|
+
}
|
|
2754
|
+
function transform_text_editor(text, map_point) {
|
|
2755
|
+
const out = clone_element_editor(text);
|
|
2756
|
+
out.bounds = transform_bounds_editor(out.bounds, map_point);
|
|
2757
|
+
return out;
|
|
2758
|
+
}
|
|
2759
|
+
function transform_bounds_editor(bounds, map_point) {
|
|
2760
|
+
const p1 = map_point([bounds[0], bounds[1]]);
|
|
2761
|
+
const p2 = map_point([bounds[0] + bounds[2], bounds[1]]);
|
|
2762
|
+
const p3 = map_point([bounds[0], bounds[1] + bounds[3]]);
|
|
2763
|
+
const p4 = map_point([bounds[0] + bounds[2], bounds[1] + bounds[3]]);
|
|
2764
|
+
return bounds_from_points_editor([p1, p2, p3, p4]);
|
|
2765
|
+
}
|
|
2766
|
+
function bounds_from_points_editor(points) {
|
|
2767
|
+
let min_x = Infinity;
|
|
2768
|
+
let min_y = Infinity;
|
|
2769
|
+
let max_x = -Infinity;
|
|
2770
|
+
let max_y = -Infinity;
|
|
2771
|
+
for (let i = 0;i < points.length; i = i + 1) {
|
|
2772
|
+
const point = points[i];
|
|
2773
|
+
if (point === undefined)
|
|
2774
|
+
continue;
|
|
2775
|
+
min_x = Math.min(min_x, point[0]);
|
|
2776
|
+
min_y = Math.min(min_y, point[1]);
|
|
2777
|
+
max_x = Math.max(max_x, point[0]);
|
|
2778
|
+
max_y = Math.max(max_y, point[1]);
|
|
2779
|
+
}
|
|
2780
|
+
if (min_x === Infinity)
|
|
2781
|
+
return [0, 0, 1, 1];
|
|
2782
|
+
return [min_x, min_y, Math.max(1, max_x - min_x), Math.max(1, max_y - min_y)];
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
// renderer/style.ts
|
|
2786
|
+
var line_cap_butt = 0;
|
|
2787
|
+
var line_cap_round = 1;
|
|
2788
|
+
var line_cap_square = 2;
|
|
2789
|
+
var line_join_miter = 0;
|
|
2790
|
+
var line_join_round = 1;
|
|
2791
|
+
var line_join_bevel = 2;
|
|
2792
|
+
function create_draw_style() {
|
|
2793
|
+
return {
|
|
2794
|
+
fill: null,
|
|
2795
|
+
stroke: null,
|
|
2796
|
+
stroke_width: 1,
|
|
2797
|
+
line_cap: line_cap_round,
|
|
2798
|
+
line_join: line_join_round,
|
|
2799
|
+
miter_limit: 10,
|
|
2800
|
+
alpha: 1
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
function clone_draw_style(s) {
|
|
2804
|
+
return {
|
|
2805
|
+
fill: s.fill,
|
|
2806
|
+
stroke: s.stroke,
|
|
2807
|
+
stroke_width: s.stroke_width,
|
|
2808
|
+
line_cap: s.line_cap,
|
|
2809
|
+
line_join: s.line_join,
|
|
2810
|
+
miter_limit: s.miter_limit,
|
|
2811
|
+
alpha: s.alpha
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
function copy_draw_style(s, out) {
|
|
2815
|
+
const mutable_out = out;
|
|
2816
|
+
mutable_out.fill = s.fill;
|
|
2817
|
+
mutable_out.stroke = s.stroke;
|
|
2818
|
+
mutable_out.stroke_width = s.stroke_width;
|
|
2819
|
+
mutable_out.line_cap = s.line_cap;
|
|
2820
|
+
mutable_out.line_join = s.line_join;
|
|
2821
|
+
mutable_out.miter_limit = s.miter_limit;
|
|
2822
|
+
mutable_out.alpha = s.alpha;
|
|
2823
|
+
return out;
|
|
2824
|
+
}
|
|
2825
|
+
function set_draw_style(fill, stroke, stroke_width, line_cap, line_join, miter_limit, alpha, out) {
|
|
2826
|
+
const mutable_out = out;
|
|
2827
|
+
mutable_out.fill = fill;
|
|
2828
|
+
mutable_out.stroke = stroke;
|
|
2829
|
+
mutable_out.stroke_width = stroke_width;
|
|
2830
|
+
mutable_out.line_cap = line_cap;
|
|
2831
|
+
mutable_out.line_join = line_join;
|
|
2832
|
+
mutable_out.miter_limit = miter_limit;
|
|
2833
|
+
mutable_out.alpha = alpha;
|
|
2834
|
+
return out;
|
|
2835
|
+
}
|
|
2836
|
+
function with_fill_draw_style(s, fill, out) {
|
|
2837
|
+
const mutable_out = out;
|
|
2838
|
+
mutable_out.fill = fill;
|
|
2839
|
+
mutable_out.stroke = s.stroke;
|
|
2840
|
+
mutable_out.stroke_width = s.stroke_width;
|
|
2841
|
+
mutable_out.line_cap = s.line_cap;
|
|
2842
|
+
mutable_out.line_join = s.line_join;
|
|
2843
|
+
mutable_out.miter_limit = s.miter_limit;
|
|
2844
|
+
mutable_out.alpha = s.alpha;
|
|
2845
|
+
return out;
|
|
2846
|
+
}
|
|
2847
|
+
function with_stroke_draw_style(s, stroke, stroke_width, out) {
|
|
2848
|
+
const mutable_out = out;
|
|
2849
|
+
mutable_out.fill = s.fill;
|
|
2850
|
+
mutable_out.stroke = stroke;
|
|
2851
|
+
mutable_out.stroke_width = stroke_width;
|
|
2852
|
+
mutable_out.line_cap = s.line_cap;
|
|
2853
|
+
mutable_out.line_join = s.line_join;
|
|
2854
|
+
mutable_out.miter_limit = s.miter_limit;
|
|
2855
|
+
mutable_out.alpha = s.alpha;
|
|
2856
|
+
return out;
|
|
2857
|
+
}
|
|
2858
|
+
function with_alpha_draw_style(s, alpha, out) {
|
|
2859
|
+
const mutable_out = out;
|
|
2860
|
+
mutable_out.fill = s.fill;
|
|
2861
|
+
mutable_out.stroke = s.stroke;
|
|
2862
|
+
mutable_out.stroke_width = s.stroke_width;
|
|
2863
|
+
mutable_out.line_cap = s.line_cap;
|
|
2864
|
+
mutable_out.line_join = s.line_join;
|
|
2865
|
+
mutable_out.miter_limit = s.miter_limit;
|
|
2866
|
+
mutable_out.alpha = alpha;
|
|
2867
|
+
return out;
|
|
2868
|
+
}
|
|
2869
|
+
function with_line_cap_draw_style(s, line_cap, out) {
|
|
2870
|
+
const mutable_out = out;
|
|
2871
|
+
mutable_out.fill = s.fill;
|
|
2872
|
+
mutable_out.stroke = s.stroke;
|
|
2873
|
+
mutable_out.stroke_width = s.stroke_width;
|
|
2874
|
+
mutable_out.line_cap = line_cap;
|
|
2875
|
+
mutable_out.line_join = s.line_join;
|
|
2876
|
+
mutable_out.miter_limit = s.miter_limit;
|
|
2877
|
+
mutable_out.alpha = s.alpha;
|
|
2878
|
+
return out;
|
|
2879
|
+
}
|
|
2880
|
+
function with_line_join_draw_style(s, line_join, miter_limit, out) {
|
|
2881
|
+
const mutable_out = out;
|
|
2882
|
+
mutable_out.fill = s.fill;
|
|
2883
|
+
mutable_out.stroke = s.stroke;
|
|
2884
|
+
mutable_out.stroke_width = s.stroke_width;
|
|
2885
|
+
mutable_out.line_cap = s.line_cap;
|
|
2886
|
+
mutable_out.line_join = line_join;
|
|
2887
|
+
mutable_out.miter_limit = miter_limit;
|
|
2888
|
+
mutable_out.alpha = s.alpha;
|
|
2889
|
+
return out;
|
|
2890
|
+
}
|
|
2891
|
+
function equals_draw_style(a, b) {
|
|
2892
|
+
const fill_equal = a.fill === null && b.fill === null || a.fill !== null && b.fill !== null && equals_color(a.fill, b.fill);
|
|
2893
|
+
const stroke_equal = a.stroke === null && b.stroke === null || a.stroke !== null && b.stroke !== null && equals_color(a.stroke, b.stroke);
|
|
2894
|
+
return fill_equal && stroke_equal && a.stroke_width === b.stroke_width && a.line_cap === b.line_cap && a.line_join === b.line_join && a.miter_limit === b.miter_limit && a.alpha === b.alpha;
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
// editor/styles.ts
|
|
2898
|
+
var overlay_selection_style = {
|
|
2899
|
+
fill: [0.2, 0.5, 1, 0.15],
|
|
2900
|
+
stroke: [0.2, 0.5, 1, 0.95],
|
|
2901
|
+
stroke_width: 1,
|
|
2902
|
+
line_cap: line_cap_round,
|
|
2903
|
+
line_join: line_join_round,
|
|
2904
|
+
miter_limit: 10,
|
|
2905
|
+
alpha: 1
|
|
2906
|
+
};
|
|
2907
|
+
var overlay_marquee_style = {
|
|
2908
|
+
fill: [0.2, 0.5, 1, 0.07],
|
|
2909
|
+
stroke: [0.2, 0.5, 1, 0.7],
|
|
2910
|
+
stroke_width: 1,
|
|
2911
|
+
line_cap: line_cap_round,
|
|
2912
|
+
line_join: line_join_round,
|
|
2913
|
+
miter_limit: 10,
|
|
2914
|
+
alpha: 1
|
|
2915
|
+
};
|
|
2916
|
+
var overlay_hover_style = {
|
|
2917
|
+
fill: null,
|
|
2918
|
+
stroke: [0.2, 0.5, 1, 0.65],
|
|
2919
|
+
stroke_width: 1,
|
|
2920
|
+
line_cap: line_cap_round,
|
|
2921
|
+
line_join: line_join_round,
|
|
2922
|
+
miter_limit: 10,
|
|
2923
|
+
alpha: 1
|
|
2924
|
+
};
|
|
2925
|
+
var overlay_handle_style = {
|
|
2926
|
+
fill: [1, 1, 1, 1],
|
|
2927
|
+
stroke: [0.2, 0.5, 1, 0.95],
|
|
2928
|
+
stroke_width: 1,
|
|
2929
|
+
line_cap: line_cap_round,
|
|
2930
|
+
line_join: line_join_round,
|
|
2931
|
+
miter_limit: 10,
|
|
2932
|
+
alpha: 1
|
|
2933
|
+
};
|
|
2934
|
+
var default_shape_fill = [0.95, 0.95, 0.95, 1];
|
|
2935
|
+
var default_shape_stroke = [0.15, 0.15, 0.15, 1];
|
|
2936
|
+
var default_text_color = [0.1, 0.1, 0.1, 1];
|
|
2937
|
+
var default_stroke_color = [0.1, 0.1, 0.1, 1];
|
|
2938
|
+
|
|
2939
|
+
// editor/hitmath.ts
|
|
2940
|
+
function contains_with_padding_editor(bounds, x, y, padding) {
|
|
2941
|
+
return x >= bounds[0] - padding && x <= bounds[0] + bounds[2] + padding && y >= bounds[1] - padding && y <= bounds[1] + bounds[3] + padding;
|
|
2942
|
+
}
|
|
2943
|
+
function distance_point_to_segment_editor(px, py, ax, ay, bx, by) {
|
|
2944
|
+
const dx = bx - ax;
|
|
2945
|
+
const dy = by - ay;
|
|
2946
|
+
if (dx === 0 && dy === 0) {
|
|
2947
|
+
return Math.hypot(px - ax, py - ay);
|
|
2948
|
+
}
|
|
2949
|
+
const length_sq = dx * dx + dy * dy;
|
|
2950
|
+
const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / length_sq));
|
|
2951
|
+
const cx = ax + t * dx;
|
|
2952
|
+
const cy = ay + t * dy;
|
|
2953
|
+
return Math.hypot(px - cx, py - cy);
|
|
2954
|
+
}
|
|
2955
|
+
function contains_ellipse_editor(bounds, x, y, padding) {
|
|
2956
|
+
const cx = bounds[0] + bounds[2] / 2;
|
|
2957
|
+
const cy = bounds[1] + bounds[3] / 2;
|
|
2958
|
+
const rx = Math.max(0.001, bounds[2] / 2 + padding);
|
|
2959
|
+
const ry = Math.max(0.001, bounds[3] / 2 + padding);
|
|
2960
|
+
const nx = (x - cx) / rx;
|
|
2961
|
+
const ny = (y - cy) / ry;
|
|
2962
|
+
return nx * nx + ny * ny <= 1;
|
|
2963
|
+
}
|
|
2964
|
+
function contains_polyline_editor(points, x, y, radius) {
|
|
2965
|
+
if (points.length === 0) {
|
|
2966
|
+
return false;
|
|
2967
|
+
}
|
|
2968
|
+
if (points.length === 1) {
|
|
2969
|
+
const p = points[0];
|
|
2970
|
+
return p !== undefined && Math.hypot(x - p[0], y - p[1]) <= radius;
|
|
2971
|
+
}
|
|
2972
|
+
for (let i = 1;i < points.length; i = i + 1) {
|
|
2973
|
+
const a = points[i - 1];
|
|
2974
|
+
const b = points[i];
|
|
2975
|
+
if (a === undefined || b === undefined) {
|
|
2976
|
+
continue;
|
|
2977
|
+
}
|
|
2978
|
+
if (distance_point_to_segment_editor(x, y, a[0], a[1], b[0], b[1]) <= radius) {
|
|
2979
|
+
return true;
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
return false;
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
// editor/textlayout.ts
|
|
2986
|
+
var measure_canvas_editor = typeof document !== "undefined" ? document.createElement("canvas") : null;
|
|
2987
|
+
var measure_context_editor = measure_canvas_editor?.getContext("2d") ?? null;
|
|
2988
|
+
function build_text_layout_editor(el, content) {
|
|
2989
|
+
const font_size = Math.max(1, el.font_size);
|
|
2990
|
+
const line_height = font_size * 1.3;
|
|
2991
|
+
const padding = Math.max(4, Math.round(font_size * 0.35));
|
|
2992
|
+
const max_text_width = Math.max(1, el.bounds[2] - padding * 2);
|
|
2993
|
+
const lines = [];
|
|
2994
|
+
const ctx = measure_context_editor;
|
|
2995
|
+
if (ctx === null) {
|
|
2996
|
+
lines.push({
|
|
2997
|
+
text: content,
|
|
2998
|
+
start: 0,
|
|
2999
|
+
end: content.length,
|
|
3000
|
+
width: content.length * font_size * 0.6
|
|
3001
|
+
});
|
|
3002
|
+
return { lines, line_height, padding, max_text_width };
|
|
3003
|
+
}
|
|
3004
|
+
ctx.font = `${font_size}px ${el.font_family}`;
|
|
3005
|
+
const paragraphs = content.split(`
|
|
3006
|
+
`);
|
|
3007
|
+
let cursor = 0;
|
|
3008
|
+
for (let p = 0;p < paragraphs.length; p = p + 1) {
|
|
3009
|
+
const paragraph = paragraphs[p] ?? "";
|
|
3010
|
+
if (paragraph.length === 0) {
|
|
3011
|
+
lines.push({ text: "", start: cursor, end: cursor, width: 0 });
|
|
3012
|
+
cursor = cursor + 1;
|
|
3013
|
+
continue;
|
|
3014
|
+
}
|
|
3015
|
+
let local = 0;
|
|
3016
|
+
while (local < paragraph.length) {
|
|
3017
|
+
const remaining = paragraph.length - local;
|
|
3018
|
+
let take = remaining;
|
|
3019
|
+
while (take > 1) {
|
|
3020
|
+
const slice = paragraph.slice(local, local + take);
|
|
3021
|
+
if (ctx.measureText(slice).width <= max_text_width)
|
|
3022
|
+
break;
|
|
3023
|
+
take = take - 1;
|
|
3024
|
+
}
|
|
3025
|
+
if (take <= 0)
|
|
3026
|
+
take = 1;
|
|
3027
|
+
if (take < remaining) {
|
|
3028
|
+
const space = paragraph.lastIndexOf(" ", local + take - 1);
|
|
3029
|
+
if (space >= local) {
|
|
3030
|
+
take = Math.max(1, space - local + 1);
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
const text = paragraph.slice(local, local + take).replace(/\s+$/g, "");
|
|
3034
|
+
const width = ctx.measureText(text).width;
|
|
3035
|
+
const start = cursor + local;
|
|
3036
|
+
const end = start + take;
|
|
3037
|
+
lines.push({ text, start, end, width });
|
|
3038
|
+
local = local + take;
|
|
3039
|
+
}
|
|
3040
|
+
cursor = cursor + paragraph.length + 1;
|
|
3041
|
+
}
|
|
3042
|
+
if (lines.length === 0) {
|
|
3043
|
+
lines.push({ text: "", start: 0, end: 0, width: 0 });
|
|
3044
|
+
}
|
|
3045
|
+
return { lines, line_height, padding, max_text_width };
|
|
3046
|
+
}
|
|
3047
|
+
function line_index_for_caret_editor(layout, index) {
|
|
3048
|
+
if (layout.lines.length === 0)
|
|
3049
|
+
return 0;
|
|
3050
|
+
for (let i = 0;i < layout.lines.length; i = i + 1) {
|
|
3051
|
+
const line = layout.lines[i];
|
|
3052
|
+
if (line === undefined)
|
|
3053
|
+
continue;
|
|
3054
|
+
if (index >= line.start && index <= line.end)
|
|
3055
|
+
return i;
|
|
3056
|
+
}
|
|
3057
|
+
return Math.max(0, layout.lines.length - 1);
|
|
3058
|
+
}
|
|
3059
|
+
function line_caret_x_editor(el, layout, line_index, caret_index) {
|
|
3060
|
+
const line = layout.lines[line_index];
|
|
3061
|
+
if (line === undefined)
|
|
3062
|
+
return 0;
|
|
3063
|
+
const local = Math.max(0, Math.min(line.text.length, caret_index - line.start));
|
|
3064
|
+
return measure_text_width_editor(el, line.text.slice(0, local));
|
|
3065
|
+
}
|
|
3066
|
+
function nearest_caret_for_x_editor(el, layout, line_index, x) {
|
|
3067
|
+
const line = layout.lines[line_index];
|
|
3068
|
+
if (line === undefined)
|
|
3069
|
+
return 0;
|
|
3070
|
+
let best_index = line.start;
|
|
3071
|
+
let best_distance = Infinity;
|
|
3072
|
+
for (let i = 0;i <= line.text.length; i = i + 1) {
|
|
3073
|
+
const width = measure_text_width_editor(el, line.text.slice(0, i));
|
|
3074
|
+
const distance = Math.abs(width - x);
|
|
3075
|
+
if (distance < best_distance) {
|
|
3076
|
+
best_distance = distance;
|
|
3077
|
+
best_index = line.start + i;
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
return Math.max(line.start, Math.min(line.end, best_index));
|
|
3081
|
+
}
|
|
3082
|
+
function caret_index_from_point_editor(el, content, world_x, world_y) {
|
|
3083
|
+
const layout = build_text_layout_editor(el, content);
|
|
3084
|
+
const local_x = world_x - el.bounds[0] - layout.padding;
|
|
3085
|
+
const local_y = world_y - el.bounds[1] - layout.padding;
|
|
3086
|
+
const line_index = Math.max(0, Math.min(layout.lines.length - 1, Math.floor(local_y / Math.max(1, layout.line_height))));
|
|
3087
|
+
return nearest_caret_for_x_editor(el, layout, line_index, local_x);
|
|
3088
|
+
}
|
|
3089
|
+
function measure_text_width_editor(el, text) {
|
|
3090
|
+
const ctx = measure_context_editor;
|
|
3091
|
+
if (ctx === null)
|
|
3092
|
+
return text.length * Math.max(1, el.font_size) * 0.6;
|
|
3093
|
+
ctx.font = `${Math.max(1, el.font_size)}px ${el.font_family}`;
|
|
3094
|
+
return ctx.measureText(text).width;
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
// editor/hittest.ts
|
|
3098
|
+
function element_hit_at_point_editor(doc, x, y, padding = 4) {
|
|
3099
|
+
let topmost = null;
|
|
3100
|
+
let top_z = -Infinity;
|
|
3101
|
+
for (const el of doc.elements.values()) {
|
|
3102
|
+
if (!element_contains_point_editor(el, x, y, padding)) {
|
|
3103
|
+
continue;
|
|
3104
|
+
}
|
|
3105
|
+
if (el.z_index >= top_z) {
|
|
3106
|
+
top_z = el.z_index;
|
|
3107
|
+
topmost = el;
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
return topmost;
|
|
3111
|
+
}
|
|
3112
|
+
function element_contains_point_editor(el, x, y, padding) {
|
|
3113
|
+
if (el.type === element_type_stroke) {
|
|
3114
|
+
return stroke_contains_point_editor(el, x, y, padding);
|
|
3115
|
+
}
|
|
3116
|
+
if (el.type === element_type_shape) {
|
|
3117
|
+
return shape_contains_point_editor(el, x, y, padding);
|
|
3118
|
+
}
|
|
3119
|
+
if (el.type === element_type_text) {
|
|
3120
|
+
return text_contains_point_editor(el, x, y, padding);
|
|
3121
|
+
}
|
|
3122
|
+
return contains_with_padding_editor(el.bounds, x, y, padding);
|
|
3123
|
+
}
|
|
3124
|
+
function stroke_contains_point_editor(stroke, x, y, padding) {
|
|
3125
|
+
const radius = Math.max(1, stroke.width / 2 + padding);
|
|
3126
|
+
const points = stroke.simplified_points !== null && stroke.simplified_points.length > 0 ? stroke.simplified_points : stroke.points;
|
|
3127
|
+
return contains_polyline_editor(points, x, y, radius);
|
|
3128
|
+
}
|
|
3129
|
+
function shape_contains_point_editor(shape, x, y, padding) {
|
|
3130
|
+
if (shape.shape_type === shape_type_ellipse) {
|
|
3131
|
+
return contains_ellipse_editor(shape.bounds, x, y, padding);
|
|
3132
|
+
}
|
|
3133
|
+
if (shape.shape_type === shape_type_line || shape.shape_type === shape_type_arrow) {
|
|
3134
|
+
const a = shape.start_point ?? [shape.bounds[0], shape.bounds[1]];
|
|
3135
|
+
const b = shape.end_point ?? [
|
|
3136
|
+
shape.bounds[0] + shape.bounds[2],
|
|
3137
|
+
shape.bounds[1] + shape.bounds[3]
|
|
3138
|
+
];
|
|
3139
|
+
const radius = Math.max(1, shape.stroke_width / 2 + padding);
|
|
3140
|
+
if (distance_point_to_segment_editor(x, y, a[0], a[1], b[0], b[1]) <= radius) {
|
|
3141
|
+
return true;
|
|
3142
|
+
}
|
|
3143
|
+
if (shape.shape_type === shape_type_arrow) {
|
|
3144
|
+
const hit_head = arrowhead_contains_point_editor(a, b, x, y, radius);
|
|
3145
|
+
if (hit_head)
|
|
3146
|
+
return true;
|
|
3147
|
+
}
|
|
3148
|
+
return false;
|
|
3149
|
+
}
|
|
3150
|
+
if (shape.shape_type === shape_type_frame) {
|
|
3151
|
+
return frame_contains_point_editor(shape, x, y, padding);
|
|
3152
|
+
}
|
|
3153
|
+
return contains_with_padding_editor(shape.bounds, x, y, padding);
|
|
3154
|
+
}
|
|
3155
|
+
function arrowhead_contains_point_editor(from, to, x, y, radius) {
|
|
3156
|
+
const dx = to[0] - from[0];
|
|
3157
|
+
const dy = to[1] - from[1];
|
|
3158
|
+
const len = Math.hypot(dx, dy);
|
|
3159
|
+
if (len < 0.0001)
|
|
3160
|
+
return false;
|
|
3161
|
+
const ux = dx / len;
|
|
3162
|
+
const uy = dy / len;
|
|
3163
|
+
const size = Math.max(8, radius * 4);
|
|
3164
|
+
const wing_angle = Math.PI / 7;
|
|
3165
|
+
const cos = Math.cos(wing_angle);
|
|
3166
|
+
const sin = Math.sin(wing_angle);
|
|
3167
|
+
const lx = to[0] - (ux * cos - uy * sin) * size;
|
|
3168
|
+
const ly = to[1] - (uy * cos + ux * sin) * size;
|
|
3169
|
+
const rx = to[0] - (ux * cos + uy * sin) * size;
|
|
3170
|
+
const ry = to[1] - (uy * cos - ux * sin) * size;
|
|
3171
|
+
return distance_point_to_segment_editor(x, y, to[0], to[1], lx, ly) <= radius || distance_point_to_segment_editor(x, y, to[0], to[1], rx, ry) <= radius;
|
|
3172
|
+
}
|
|
3173
|
+
function frame_contains_point_editor(shape, x, y, padding) {
|
|
3174
|
+
const bx = shape.bounds[0];
|
|
3175
|
+
const by = shape.bounds[1];
|
|
3176
|
+
const bw = Math.max(1, shape.bounds[2]);
|
|
3177
|
+
const bh = Math.max(1, shape.bounds[3]);
|
|
3178
|
+
const stroke = Math.max(1, shape.stroke_width + padding);
|
|
3179
|
+
const header_height = Math.max(20, Math.min(36, bh * 0.18));
|
|
3180
|
+
const in_outer = contains_with_padding_editor([bx, by, bw, bh], x, y, padding);
|
|
3181
|
+
if (!in_outer)
|
|
3182
|
+
return false;
|
|
3183
|
+
const in_inner = x >= bx + stroke && x <= bx + bw - stroke && y >= by + stroke && y <= by + bh - stroke;
|
|
3184
|
+
const in_header_band = x >= bx - padding && x <= bx + bw + padding && y >= by - padding && y <= by + header_height + padding;
|
|
3185
|
+
return !in_inner || in_header_band;
|
|
3186
|
+
}
|
|
3187
|
+
function text_contains_point_editor(text, x, y, padding) {
|
|
3188
|
+
if (!contains_with_padding_editor(text.bounds, x, y, padding))
|
|
3189
|
+
return false;
|
|
3190
|
+
if (text.content.length === 0)
|
|
3191
|
+
return true;
|
|
3192
|
+
const layout = build_text_layout_editor(text, text.content);
|
|
3193
|
+
const local_x = x - text.bounds[0];
|
|
3194
|
+
const local_y = y - text.bounds[1];
|
|
3195
|
+
const top = layout.padding;
|
|
3196
|
+
const bottom = text.bounds[3] - layout.padding;
|
|
3197
|
+
if (local_y < top - padding || local_y > bottom + padding)
|
|
3198
|
+
return false;
|
|
3199
|
+
for (let i = 0;i < layout.lines.length; i = i + 1) {
|
|
3200
|
+
const line = layout.lines[i];
|
|
3201
|
+
if (line === undefined)
|
|
3202
|
+
continue;
|
|
3203
|
+
const y0 = layout.padding + i * layout.line_height;
|
|
3204
|
+
const y1 = y0 + layout.line_height;
|
|
3205
|
+
if (local_y < y0 - padding || local_y > y1 + padding)
|
|
3206
|
+
continue;
|
|
3207
|
+
let x0 = layout.padding;
|
|
3208
|
+
if (text.align === text_align_center) {
|
|
3209
|
+
x0 = text.bounds[2] * 0.5 - line.width * 0.5;
|
|
3210
|
+
} else if (text.align === text_align_right) {
|
|
3211
|
+
x0 = text.bounds[2] - layout.padding - line.width;
|
|
3212
|
+
}
|
|
3213
|
+
const x1 = x0 + line.width;
|
|
3214
|
+
if (local_x >= x0 - padding && local_x <= x1 + padding)
|
|
3215
|
+
return true;
|
|
3216
|
+
}
|
|
3217
|
+
return false;
|
|
3218
|
+
}
|
|
3219
|
+
|
|
3220
|
+
// editor/selection.ts
|
|
3221
|
+
function selection_bounds_editor(doc, ids) {
|
|
3222
|
+
if (ids.length === 0) {
|
|
3223
|
+
return null;
|
|
3224
|
+
}
|
|
3225
|
+
let min_x = Infinity;
|
|
3226
|
+
let min_y = Infinity;
|
|
3227
|
+
let max_x = -Infinity;
|
|
3228
|
+
let max_y = -Infinity;
|
|
3229
|
+
let found = false;
|
|
3230
|
+
for (let i = 0;i < ids.length; i = i + 1) {
|
|
3231
|
+
const id = ids[i];
|
|
3232
|
+
if (id === undefined) {
|
|
3233
|
+
continue;
|
|
3234
|
+
}
|
|
3235
|
+
const element = get_element_by_id_document(doc, id);
|
|
3236
|
+
if (element === null) {
|
|
3237
|
+
continue;
|
|
3238
|
+
}
|
|
3239
|
+
min_x = Math.min(min_x, element.bounds[0]);
|
|
3240
|
+
min_y = Math.min(min_y, element.bounds[1]);
|
|
3241
|
+
max_x = Math.max(max_x, element.bounds[0] + element.bounds[2]);
|
|
3242
|
+
max_y = Math.max(max_y, element.bounds[1] + element.bounds[3]);
|
|
3243
|
+
found = true;
|
|
3244
|
+
}
|
|
3245
|
+
if (!found) {
|
|
3246
|
+
return null;
|
|
3247
|
+
}
|
|
3248
|
+
return [min_x, min_y, max_x - min_x, max_y - min_y];
|
|
3249
|
+
}
|
|
3250
|
+
function marquee_rectangle_editor(origin_x, origin_y, current_x, current_y) {
|
|
3251
|
+
const x = Math.min(origin_x, current_x);
|
|
3252
|
+
const y = Math.min(origin_y, current_y);
|
|
3253
|
+
const width = Math.abs(current_x - origin_x);
|
|
3254
|
+
const height = Math.abs(current_y - origin_y);
|
|
3255
|
+
return [x, y, width, height];
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
// editor/hit.ts
|
|
3259
|
+
var handle_size = 10;
|
|
3260
|
+
var rotate_offset = 28;
|
|
3261
|
+
function selection_handles_editor(bounds) {
|
|
3262
|
+
return box_handles_editor(bounds, true);
|
|
3263
|
+
}
|
|
3264
|
+
function selection_handles_for_elements_editor(doc, selected_ids) {
|
|
3265
|
+
const bounds = selection_bounds_editor(doc, selected_ids);
|
|
3266
|
+
if (bounds === null) {
|
|
3267
|
+
return [];
|
|
3268
|
+
}
|
|
3269
|
+
if (selected_ids.length !== 1) {
|
|
3270
|
+
return box_handles_editor(bounds, true);
|
|
3271
|
+
}
|
|
3272
|
+
const id = selected_ids[0];
|
|
3273
|
+
if (id === undefined) {
|
|
3274
|
+
return box_handles_editor(bounds, true);
|
|
3275
|
+
}
|
|
3276
|
+
const element = get_element_by_id_document(doc, id);
|
|
3277
|
+
if (element === null) {
|
|
3278
|
+
return box_handles_editor(bounds, true);
|
|
3279
|
+
}
|
|
3280
|
+
return element_specific_handles_editor(element, bounds);
|
|
3281
|
+
}
|
|
3282
|
+
function element_specific_handles_editor(element, bounds) {
|
|
3283
|
+
if (element.type === element_type_shape) {
|
|
3284
|
+
const shape = element;
|
|
3285
|
+
if (shape.shape_type === shape_type_line || shape.shape_type === shape_type_arrow) {
|
|
3286
|
+
const start = shape.start_point ?? [bounds[0], bounds[1]];
|
|
3287
|
+
const end = shape.end_point ?? [bounds[0] + bounds[2], bounds[1] + bounds[3]];
|
|
3288
|
+
return [
|
|
3289
|
+
point_handle_editor("start", start[0], start[1]),
|
|
3290
|
+
point_handle_editor("end", end[0], end[1])
|
|
3291
|
+
];
|
|
3292
|
+
}
|
|
3293
|
+
return box_handles_editor(bounds, true);
|
|
3294
|
+
}
|
|
3295
|
+
if (element.type === element_type_image) {
|
|
3296
|
+
return box_handles_editor(bounds, false, ["nw", "ne", "se", "sw"]);
|
|
3297
|
+
}
|
|
3298
|
+
if (element.type === element_type_text) {
|
|
3299
|
+
return box_handles_editor(bounds, false, ["w", "e"]);
|
|
3300
|
+
}
|
|
3301
|
+
return box_handles_editor(bounds, true);
|
|
3302
|
+
}
|
|
3303
|
+
function box_handles_editor(bounds, include_rotate, ids) {
|
|
3304
|
+
const x = bounds[0];
|
|
3305
|
+
const y = bounds[1];
|
|
3306
|
+
const w = bounds[2];
|
|
3307
|
+
const h = bounds[3];
|
|
3308
|
+
const cx = x + w / 2;
|
|
3309
|
+
const cy = y + h / 2;
|
|
3310
|
+
const hs = handle_size / 2;
|
|
3311
|
+
const all = [
|
|
3312
|
+
{ id: "nw", bounds: [x - hs, y - hs, handle_size, handle_size] },
|
|
3313
|
+
{ id: "n", bounds: [cx - hs, y - hs, handle_size, handle_size] },
|
|
3314
|
+
{ id: "ne", bounds: [x + w - hs, y - hs, handle_size, handle_size] },
|
|
3315
|
+
{ id: "e", bounds: [x + w - hs, cy - hs, handle_size, handle_size] },
|
|
3316
|
+
{ id: "se", bounds: [x + w - hs, y + h - hs, handle_size, handle_size] },
|
|
3317
|
+
{ id: "s", bounds: [cx - hs, y + h - hs, handle_size, handle_size] },
|
|
3318
|
+
{ id: "sw", bounds: [x - hs, y + h - hs, handle_size, handle_size] },
|
|
3319
|
+
{ id: "w", bounds: [x - hs, cy - hs, handle_size, handle_size] }
|
|
3320
|
+
];
|
|
3321
|
+
if (include_rotate) {
|
|
3322
|
+
all.push({ id: "rotate", bounds: [cx - hs, y - rotate_offset - hs, handle_size, handle_size] });
|
|
3323
|
+
}
|
|
3324
|
+
if (ids === undefined) {
|
|
3325
|
+
return all;
|
|
3326
|
+
}
|
|
3327
|
+
return all.filter((handle) => ids.includes(handle.id));
|
|
3328
|
+
}
|
|
3329
|
+
function hit_selection_handle_editor(bounds, point) {
|
|
3330
|
+
const handles = selection_handles_editor(bounds);
|
|
3331
|
+
for (let i = 0;i < handles.length; i = i + 1) {
|
|
3332
|
+
const handle = handles[i];
|
|
3333
|
+
if (handle === undefined) {
|
|
3334
|
+
continue;
|
|
3335
|
+
}
|
|
3336
|
+
if (contains_point_editor(handle.bounds, point)) {
|
|
3337
|
+
return handle;
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
return null;
|
|
3341
|
+
}
|
|
3342
|
+
function hit_selection_handles_editor(handles, point) {
|
|
3343
|
+
for (let i = 0;i < handles.length; i = i + 1) {
|
|
3344
|
+
const handle = handles[i];
|
|
3345
|
+
if (handle !== undefined && contains_point_editor(handle.bounds, point)) {
|
|
3346
|
+
return handle;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
return null;
|
|
3350
|
+
}
|
|
3351
|
+
function point_handle_editor(id, x, y) {
|
|
3352
|
+
const hs = handle_size / 2;
|
|
3353
|
+
return { id, bounds: [x - hs, y - hs, handle_size, handle_size] };
|
|
3354
|
+
}
|
|
3355
|
+
function contains_point_editor(bounds, point) {
|
|
3356
|
+
return point[0] >= bounds[0] && point[0] <= bounds[0] + bounds[2] && point[1] >= bounds[1] && point[1] <= bounds[1] + bounds[3];
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
// editor/shared.ts
|
|
3360
|
+
function current_tool_impl_editor(state) {
|
|
3361
|
+
return state.tools.get(state.current_tool) ?? state.tools.get(editor_tool_select);
|
|
3362
|
+
}
|
|
3363
|
+
function next_id_editor(state, prefix) {
|
|
3364
|
+
state.id_counter = state.id_counter + 1;
|
|
3365
|
+
return `${prefix}_${Date.now()}_${state.id_counter}`;
|
|
3366
|
+
}
|
|
3367
|
+
function next_z_index_editor(doc) {
|
|
3368
|
+
let max = -1;
|
|
3369
|
+
for (const item of doc.elements.values()) {
|
|
3370
|
+
if (item.z_index > max) {
|
|
3371
|
+
max = item.z_index;
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
return max + 1;
|
|
3375
|
+
}
|
|
3376
|
+
function cleanup_selection_editor(state) {
|
|
3377
|
+
const ids = Array.from(state.selected_element_ids.values());
|
|
3378
|
+
for (let i = 0;i < ids.length; i = i + 1) {
|
|
3379
|
+
const id = ids[i];
|
|
3380
|
+
if (id !== undefined && get_element_by_id_document(state.engine.document, id) === null) {
|
|
3381
|
+
state.selected_element_ids.delete(id);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
function update_pointer_editor(state, input) {
|
|
3386
|
+
state.pointer_screen[0] = input.screen[0];
|
|
3387
|
+
state.pointer_screen[1] = input.screen[1];
|
|
3388
|
+
state.has_pointer_input = true;
|
|
3389
|
+
screen_to_world_engine(state.engine, input.screen, state.pointer_world);
|
|
3390
|
+
}
|
|
3391
|
+
function update_hover_editor(state) {
|
|
3392
|
+
const hit = element_hit_at_point_editor(state.engine.document, state.pointer_world[0], state.pointer_world[1]);
|
|
3393
|
+
state.hovered_element_id = hit?.id ?? null;
|
|
3394
|
+
}
|
|
3395
|
+
function update_select_hover_editor(state) {
|
|
3396
|
+
state.transient_guides = [];
|
|
3397
|
+
const selected_ids = Array.from(state.selected_element_ids.values());
|
|
3398
|
+
const handles = selection_handles_for_elements_editor(state.engine.document, selected_ids);
|
|
3399
|
+
state.active_handle = hit_selection_handles_editor(handles, state.pointer_world)?.id ?? null;
|
|
3400
|
+
update_hover_editor(state);
|
|
3401
|
+
}
|
|
3402
|
+
function rectangle_from_points_editor(a, b, keep_square) {
|
|
3403
|
+
const x1 = a[0];
|
|
3404
|
+
const y1 = a[1];
|
|
3405
|
+
let x2 = b[0];
|
|
3406
|
+
let y2 = b[1];
|
|
3407
|
+
if (keep_square) {
|
|
3408
|
+
const dx = x2 - x1;
|
|
3409
|
+
const dy = y2 - y1;
|
|
3410
|
+
const size = Math.max(Math.abs(dx), Math.abs(dy));
|
|
3411
|
+
x2 = x1 + Math.sign(dx || 1) * size;
|
|
3412
|
+
y2 = y1 + Math.sign(dy || 1) * size;
|
|
3413
|
+
}
|
|
3414
|
+
return [
|
|
3415
|
+
Math.min(x1, x2),
|
|
3416
|
+
Math.min(y1, y2),
|
|
3417
|
+
Math.max(1, Math.abs(x2 - x1)),
|
|
3418
|
+
Math.max(1, Math.abs(y2 - y1))
|
|
3419
|
+
];
|
|
3420
|
+
}
|
|
3421
|
+
function shape_name_for_tool_editor(tool) {
|
|
3422
|
+
if (tool === editor_tool_rectangle)
|
|
3423
|
+
return "rectangle";
|
|
3424
|
+
if (tool === editor_tool_ellipse)
|
|
3425
|
+
return "ellipse";
|
|
3426
|
+
if (tool === editor_tool_line)
|
|
3427
|
+
return "line";
|
|
3428
|
+
if (tool === editor_tool_arrow)
|
|
3429
|
+
return "arrow";
|
|
3430
|
+
if (tool === editor_tool_frame)
|
|
3431
|
+
return "frame";
|
|
3432
|
+
return "shape";
|
|
3433
|
+
}
|
|
3434
|
+
function shape_type_for_tool_editor(tool) {
|
|
3435
|
+
if (tool === editor_tool_rectangle)
|
|
3436
|
+
return shape_type_rectangle;
|
|
3437
|
+
if (tool === editor_tool_ellipse)
|
|
3438
|
+
return shape_type_ellipse;
|
|
3439
|
+
if (tool === editor_tool_line)
|
|
3440
|
+
return shape_type_line;
|
|
3441
|
+
if (tool === editor_tool_arrow)
|
|
3442
|
+
return shape_type_arrow;
|
|
3443
|
+
return shape_type_frame;
|
|
3444
|
+
}
|
|
3445
|
+
function grouped_ids_for_element_editor(doc, element_id) {
|
|
3446
|
+
const element = get_element_by_id_document(doc, element_id);
|
|
3447
|
+
if (element === null || element.group_id === null) {
|
|
3448
|
+
return element === null ? [] : [element_id];
|
|
3449
|
+
}
|
|
3450
|
+
const ids = [];
|
|
3451
|
+
for (const item of doc.elements.values()) {
|
|
3452
|
+
if (item.group_id === element.group_id)
|
|
3453
|
+
ids.push(item.id);
|
|
3454
|
+
}
|
|
3455
|
+
return ids;
|
|
3456
|
+
}
|
|
3457
|
+
function expand_ids_with_groups_editor(doc, ids) {
|
|
3458
|
+
const out = new Set;
|
|
3459
|
+
for (let i = 0;i < ids.length; i = i + 1) {
|
|
3460
|
+
const id = ids[i];
|
|
3461
|
+
if (id === undefined)
|
|
3462
|
+
continue;
|
|
3463
|
+
const group_ids = grouped_ids_for_element_editor(doc, id);
|
|
3464
|
+
if (group_ids.length === 0)
|
|
3465
|
+
continue;
|
|
3466
|
+
for (let j = 0;j < group_ids.length; j = j + 1) {
|
|
3467
|
+
const group_id = group_ids[j];
|
|
3468
|
+
if (group_id !== undefined)
|
|
3469
|
+
out.add(group_id);
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
return Array.from(out);
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
// editor/ops.ts
|
|
3476
|
+
function update_shape_preview_editor(state, drag_state, keep_square) {
|
|
3477
|
+
const el = get_element_by_id_document(state.engine.document, drag_state.element_id);
|
|
3478
|
+
if (el === null || el.type !== element_type_shape)
|
|
3479
|
+
return;
|
|
3480
|
+
const rect = rectangle_from_points_editor(drag_state.origin_world, drag_state.current_world, keep_square);
|
|
3481
|
+
const shape = clone_element_editor(el);
|
|
3482
|
+
shape.bounds = rect;
|
|
3483
|
+
if (shape.shape_type === shape_type_line || shape.shape_type === shape_type_arrow) {
|
|
3484
|
+
const constrained = keep_square ? constrain_line_angle_editor(drag_state.origin_world, drag_state.current_world) : [drag_state.current_world[0], drag_state.current_world[1]];
|
|
3485
|
+
const end_x = constrained[0];
|
|
3486
|
+
const end_y = constrained[1];
|
|
3487
|
+
if (end_x === undefined || end_y === undefined) {
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
3490
|
+
shape.start_point = [drag_state.origin_world[0], drag_state.origin_world[1]];
|
|
3491
|
+
shape.end_point = [end_x, end_y];
|
|
3492
|
+
const end_point = shape.end_point;
|
|
3493
|
+
shape.bounds = [
|
|
3494
|
+
Math.min(shape.start_point[0], end_point[0]),
|
|
3495
|
+
Math.min(shape.start_point[1], end_point[1]),
|
|
3496
|
+
Math.max(1, Math.abs(end_point[0] - shape.start_point[0])),
|
|
3497
|
+
Math.max(1, Math.abs(end_point[1] - shape.start_point[1]))
|
|
3498
|
+
];
|
|
3499
|
+
}
|
|
3500
|
+
state.engine.document = update_element_document(state.engine.document, el.id, shape);
|
|
3501
|
+
}
|
|
3502
|
+
function constrain_line_angle_editor(origin, target) {
|
|
3503
|
+
const dx = target[0] - origin[0];
|
|
3504
|
+
const dy = target[1] - origin[1];
|
|
3505
|
+
const length = Math.hypot(dx, dy);
|
|
3506
|
+
if (length === 0) {
|
|
3507
|
+
return [target[0], target[1]];
|
|
3508
|
+
}
|
|
3509
|
+
const angle = Math.atan2(dy, dx);
|
|
3510
|
+
const snap = Math.PI / 4;
|
|
3511
|
+
const snapped = Math.round(angle / snap) * snap;
|
|
3512
|
+
return [origin[0] + Math.cos(snapped) * length, origin[1] + Math.sin(snapped) * length];
|
|
3513
|
+
}
|
|
3514
|
+
function update_box_resize_editor(state, element_id, origin_world, current_world) {
|
|
3515
|
+
const el = get_element_by_id_document(state.engine.document, element_id);
|
|
3516
|
+
if (el === null)
|
|
3517
|
+
return;
|
|
3518
|
+
const rect = rectangle_from_points_editor(origin_world, current_world, false);
|
|
3519
|
+
const updated = clone_element_editor(el);
|
|
3520
|
+
updated.bounds = [rect[0], rect[1], Math.max(rect[2], 2), Math.max(rect[3], 2)];
|
|
3521
|
+
state.engine.document = update_element_document(state.engine.document, element_id, updated);
|
|
3522
|
+
}
|
|
3523
|
+
function insert_drawn_stroke_editor(state, points, pressure) {
|
|
3524
|
+
if (points.length === 0)
|
|
3525
|
+
return;
|
|
3526
|
+
const simplified_out = Array.from({ length: points.length }, () => [0, 0]);
|
|
3527
|
+
const spline = create_spline();
|
|
3528
|
+
const processed = process_stroke(points, pressure, {
|
|
3529
|
+
color: default_stroke_color,
|
|
3530
|
+
width: 2,
|
|
3531
|
+
opacity: 1,
|
|
3532
|
+
pressure_sensitivity: 0.5
|
|
3533
|
+
}, simplified_out, spline);
|
|
3534
|
+
const stroke_bounds = [0, 0, 0, 0];
|
|
3535
|
+
compute_stroke_bounds(points, 2, stroke_bounds);
|
|
3536
|
+
const element_id = next_id_editor(state, "stroke");
|
|
3537
|
+
const stroke = create_stroke_element(element_id, stroke_bounds, next_z_index_editor(state.engine.document), state.engine.document.active_layer_id, points, pressure, default_stroke_color, 2);
|
|
3538
|
+
stroke.simplified_points = processed.simplified;
|
|
3539
|
+
stroke.spline = processed.spline;
|
|
3540
|
+
state.engine.document = add_element_document(state.engine.document, stroke);
|
|
3541
|
+
state.selected_element_ids.clear();
|
|
3542
|
+
state.selected_element_ids.add(element_id);
|
|
3543
|
+
}
|
|
3544
|
+
function insert_elements_with_offset_editor(state, elements, dx, dy) {
|
|
3545
|
+
if (elements.length === 0)
|
|
3546
|
+
return false;
|
|
3547
|
+
const id_map = new Map;
|
|
3548
|
+
for (let i = 0;i < elements.length; i = i + 1) {
|
|
3549
|
+
const item = elements[i];
|
|
3550
|
+
if (item !== undefined)
|
|
3551
|
+
id_map.set(item.id, next_id_editor(state, "el"));
|
|
3552
|
+
}
|
|
3553
|
+
let doc = state.engine.document;
|
|
3554
|
+
state.selected_element_ids.clear();
|
|
3555
|
+
for (let i = 0;i < elements.length; i = i + 1) {
|
|
3556
|
+
const item = elements[i];
|
|
3557
|
+
if (item === undefined)
|
|
3558
|
+
continue;
|
|
3559
|
+
const clone = move_element_editor(item, dx, dy);
|
|
3560
|
+
const new_id = id_map.get(item.id);
|
|
3561
|
+
if (new_id === undefined)
|
|
3562
|
+
continue;
|
|
3563
|
+
clone.id = new_id;
|
|
3564
|
+
clone.z_index = next_z_index_editor(doc);
|
|
3565
|
+
doc = add_element_document(doc, clone);
|
|
3566
|
+
state.selected_element_ids.add(new_id);
|
|
3567
|
+
}
|
|
3568
|
+
state.engine.document = doc;
|
|
3569
|
+
return true;
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
// editor/actions.ts
|
|
3573
|
+
function copy_selection_editor(state) {
|
|
3574
|
+
const selected_ids = Array.from(state.selected_element_ids.values());
|
|
3575
|
+
if (selected_ids.length === 0)
|
|
3576
|
+
return false;
|
|
3577
|
+
const payload_elements = [];
|
|
3578
|
+
for (let i = 0;i < selected_ids.length; i = i + 1) {
|
|
3579
|
+
const id = selected_ids[i];
|
|
3580
|
+
if (id === undefined)
|
|
3581
|
+
continue;
|
|
3582
|
+
const el = get_element_by_id_document(state.engine.document, id);
|
|
3583
|
+
if (el !== null)
|
|
3584
|
+
payload_elements.push(clone_element_editor(el));
|
|
3585
|
+
}
|
|
3586
|
+
if (payload_elements.length === 0)
|
|
3587
|
+
return false;
|
|
3588
|
+
const bounds = selection_bounds_editor(state.engine.document, selected_ids);
|
|
3589
|
+
const anchor = bounds === null ? [state.pointer_world[0], state.pointer_world[1]] : [bounds[0] + bounds[2] / 2, bounds[1] + bounds[3] / 2];
|
|
3590
|
+
state.clipboard_payload = { elements: payload_elements, anchor };
|
|
3591
|
+
return true;
|
|
3592
|
+
}
|
|
3593
|
+
function cut_selection_editor(state) {
|
|
3594
|
+
return copy_selection_editor(state) ? delete_selection_editor(state) : false;
|
|
3595
|
+
}
|
|
3596
|
+
function paste_clipboard_editor(state, world_anchor) {
|
|
3597
|
+
if (state.clipboard_payload === null || state.clipboard_payload.elements.length === 0)
|
|
3598
|
+
return false;
|
|
3599
|
+
const anchor = world_anchor ?? paste_anchor_editor(state);
|
|
3600
|
+
const dx = anchor[0] - state.clipboard_payload.anchor[0] + 16;
|
|
3601
|
+
const dy = anchor[1] - state.clipboard_payload.anchor[1] + 16;
|
|
3602
|
+
begin_transaction_editor(state, "paste");
|
|
3603
|
+
const ok = insert_elements_with_offset_editor(state, state.clipboard_payload.elements, dx, dy);
|
|
3604
|
+
if (!ok) {
|
|
3605
|
+
cancel_transaction_editor(state);
|
|
3606
|
+
return false;
|
|
3607
|
+
}
|
|
3608
|
+
commit_transaction_editor(state);
|
|
3609
|
+
return true;
|
|
3610
|
+
}
|
|
3611
|
+
function duplicate_selection_editor(state) {
|
|
3612
|
+
return copy_selection_editor(state) ? paste_clipboard_editor(state, state.pointer_world) : false;
|
|
3613
|
+
}
|
|
3614
|
+
function paste_anchor_editor(state) {
|
|
3615
|
+
if (state.has_pointer_input) {
|
|
3616
|
+
return [state.pointer_world[0], state.pointer_world[1]];
|
|
3617
|
+
}
|
|
3618
|
+
const center_screen = [state.engine.viewport[0] / 2, state.engine.viewport[1] / 2];
|
|
3619
|
+
const center_world = [0, 0];
|
|
3620
|
+
screen_to_world_engine(state.engine, center_screen, center_world);
|
|
3621
|
+
return center_world;
|
|
3622
|
+
}
|
|
3623
|
+
function delete_selection_editor(state) {
|
|
3624
|
+
if (state.selected_element_ids.size === 0)
|
|
3625
|
+
return false;
|
|
3626
|
+
begin_transaction_editor(state, "delete");
|
|
3627
|
+
const selected_ids = Array.from(state.selected_element_ids.values());
|
|
3628
|
+
let doc = state.engine.document;
|
|
3629
|
+
for (let i = 0;i < selected_ids.length; i = i + 1) {
|
|
3630
|
+
const id = selected_ids[i];
|
|
3631
|
+
if (id !== undefined)
|
|
3632
|
+
doc = remove_element_document(doc, id);
|
|
3633
|
+
}
|
|
3634
|
+
state.engine.document = doc;
|
|
3635
|
+
state.selected_element_ids.clear();
|
|
3636
|
+
commit_transaction_editor(state);
|
|
3637
|
+
return true;
|
|
3638
|
+
}
|
|
3639
|
+
function undo_action_editor(state) {
|
|
3640
|
+
const ok = undo_editor(state);
|
|
3641
|
+
if (ok)
|
|
3642
|
+
cleanup_selection_editor(state);
|
|
3643
|
+
return ok;
|
|
3644
|
+
}
|
|
3645
|
+
function redo_action_editor(state) {
|
|
3646
|
+
const ok = redo_editor(state);
|
|
3647
|
+
if (ok)
|
|
3648
|
+
cleanup_selection_editor(state);
|
|
3649
|
+
return ok;
|
|
3650
|
+
}
|
|
3651
|
+
function group_selection_editor(state) {
|
|
3652
|
+
const raw_ids = Array.from(state.selected_element_ids.values());
|
|
3653
|
+
const ids = expand_ids_with_groups_editor(state.engine.document, raw_ids);
|
|
3654
|
+
if (ids.length < 2) {
|
|
3655
|
+
return false;
|
|
3656
|
+
}
|
|
3657
|
+
begin_transaction_editor(state, "group");
|
|
3658
|
+
const group_id = next_id_editor(state, "group");
|
|
3659
|
+
let doc = state.engine.document;
|
|
3660
|
+
for (let i = 0;i < ids.length; i = i + 1) {
|
|
3661
|
+
const id = ids[i];
|
|
3662
|
+
if (id === undefined)
|
|
3663
|
+
continue;
|
|
3664
|
+
const element = get_element_by_id_document(doc, id);
|
|
3665
|
+
if (element === null)
|
|
3666
|
+
continue;
|
|
3667
|
+
doc = update_element_document(doc, id, { ...element, group_id });
|
|
3668
|
+
}
|
|
3669
|
+
state.engine.document = doc;
|
|
3670
|
+
state.selected_element_ids.clear();
|
|
3671
|
+
for (let i = 0;i < ids.length; i = i + 1) {
|
|
3672
|
+
const id = ids[i];
|
|
3673
|
+
if (id !== undefined)
|
|
3674
|
+
state.selected_element_ids.add(id);
|
|
3675
|
+
}
|
|
3676
|
+
commit_transaction_editor(state);
|
|
3677
|
+
return true;
|
|
3678
|
+
}
|
|
3679
|
+
function ungroup_selection_editor(state) {
|
|
3680
|
+
const raw_ids = Array.from(state.selected_element_ids.values());
|
|
3681
|
+
const ids = expand_ids_with_groups_editor(state.engine.document, raw_ids);
|
|
3682
|
+
if (ids.length === 0) {
|
|
3683
|
+
return false;
|
|
3684
|
+
}
|
|
3685
|
+
begin_transaction_editor(state, "ungroup");
|
|
3686
|
+
let doc = state.engine.document;
|
|
3687
|
+
let changed = false;
|
|
3688
|
+
for (let i = 0;i < ids.length; i = i + 1) {
|
|
3689
|
+
const id = ids[i];
|
|
3690
|
+
if (id === undefined)
|
|
3691
|
+
continue;
|
|
3692
|
+
const element = get_element_by_id_document(doc, id);
|
|
3693
|
+
if (element === null || element.group_id === null)
|
|
3694
|
+
continue;
|
|
3695
|
+
changed = true;
|
|
3696
|
+
doc = update_element_document(doc, id, { ...element, group_id: null });
|
|
3697
|
+
}
|
|
3698
|
+
if (!changed) {
|
|
3699
|
+
cancel_transaction_editor(state);
|
|
3700
|
+
return false;
|
|
3701
|
+
}
|
|
3702
|
+
state.engine.document = doc;
|
|
3703
|
+
commit_transaction_editor(state);
|
|
3704
|
+
return true;
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
// editor/keys.ts
|
|
3708
|
+
function handle_global_key_editor(state, input) {
|
|
3709
|
+
const normalized = normalize_key_editor(input.key);
|
|
3710
|
+
const mod = !!input.meta || !!input.ctrl;
|
|
3711
|
+
if (mod && normalized === "z" && !input.shift)
|
|
3712
|
+
return undo_action_editor(state);
|
|
3713
|
+
if (mod && normalized === "y" || mod && normalized === "z" && !!input.shift)
|
|
3714
|
+
return redo_action_editor(state);
|
|
3715
|
+
if (mod && normalized === "c")
|
|
3716
|
+
return copy_selection_editor(state);
|
|
3717
|
+
if (mod && normalized === "x")
|
|
3718
|
+
return cut_selection_editor(state);
|
|
3719
|
+
if (mod && normalized === "v")
|
|
3720
|
+
return paste_clipboard_editor(state);
|
|
3721
|
+
if (mod && normalized === "d")
|
|
3722
|
+
return duplicate_selection_editor(state);
|
|
3723
|
+
if (mod && normalized === "g" && !!input.shift)
|
|
3724
|
+
return ungroup_selection_editor(state);
|
|
3725
|
+
if (mod && normalized === "g")
|
|
3726
|
+
return group_selection_editor(state);
|
|
3727
|
+
if (normalized === "delete" || normalized === "backspace")
|
|
3728
|
+
return delete_selection_editor(state);
|
|
3729
|
+
if (normalized === "escape") {
|
|
3730
|
+
current_tool_impl_editor(state).cancel(state);
|
|
3731
|
+
return true;
|
|
3732
|
+
}
|
|
3733
|
+
if (normalized === " ") {
|
|
3734
|
+
if (state.current_tool !== editor_tool_hand) {
|
|
3735
|
+
state.previous_tool_before_hand = state.current_tool;
|
|
3736
|
+
state.current_tool = editor_tool_hand;
|
|
3737
|
+
}
|
|
3738
|
+
return true;
|
|
3739
|
+
}
|
|
3740
|
+
if (normalized === "v")
|
|
3741
|
+
return set_tool_shortcut_editor(state, editor_tool_select);
|
|
3742
|
+
if (normalized === "h")
|
|
3743
|
+
return set_tool_shortcut_editor(state, editor_tool_hand);
|
|
3744
|
+
if (normalized === "p")
|
|
3745
|
+
return set_tool_shortcut_editor(state, editor_tool_draw);
|
|
3746
|
+
if (normalized === "r")
|
|
3747
|
+
return set_tool_shortcut_editor(state, editor_tool_rectangle);
|
|
3748
|
+
if (normalized === "e")
|
|
3749
|
+
return set_tool_shortcut_editor(state, editor_tool_ellipse);
|
|
3750
|
+
if (normalized === "l")
|
|
3751
|
+
return set_tool_shortcut_editor(state, editor_tool_line);
|
|
3752
|
+
if (normalized === "a")
|
|
3753
|
+
return set_tool_shortcut_editor(state, editor_tool_arrow);
|
|
3754
|
+
if (normalized === "f")
|
|
3755
|
+
return set_tool_shortcut_editor(state, editor_tool_frame);
|
|
3756
|
+
if (normalized === "t")
|
|
3757
|
+
return set_tool_shortcut_editor(state, editor_tool_text);
|
|
3758
|
+
if (normalized === "i")
|
|
3759
|
+
return set_tool_shortcut_editor(state, editor_tool_image_place);
|
|
3760
|
+
return false;
|
|
3761
|
+
}
|
|
3762
|
+
function normalize_key_editor(key) {
|
|
3763
|
+
return key.toLowerCase();
|
|
3764
|
+
}
|
|
3765
|
+
function set_tool_shortcut_editor(state, tool) {
|
|
3766
|
+
if (!state.tools.has(tool) || state.current_tool === tool)
|
|
3767
|
+
return true;
|
|
3768
|
+
current_tool_impl_editor(state).cancel(state);
|
|
3769
|
+
state.current_tool = tool;
|
|
3770
|
+
return true;
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
// editor/overlay.ts
|
|
3774
|
+
function draw_editor_overlay(state, drawer) {
|
|
3775
|
+
draw_hover_overlay_editor(state, drawer);
|
|
3776
|
+
draw_selection_overlay_editor(state, drawer);
|
|
3777
|
+
draw_marquee_overlay_editor(state, drawer);
|
|
3778
|
+
draw_guides_overlay_editor(state, drawer);
|
|
3779
|
+
draw_text_edit_overlay_editor(state, drawer);
|
|
3780
|
+
}
|
|
3781
|
+
function draw_hover_overlay_editor(state, drawer) {
|
|
3782
|
+
if (state.hovered_element_id === null || state.selected_element_ids.has(state.hovered_element_id)) {
|
|
3783
|
+
return;
|
|
3784
|
+
}
|
|
3785
|
+
const hovered = get_element_by_id_document(state.engine.document, state.hovered_element_id);
|
|
3786
|
+
if (hovered !== null) {
|
|
3787
|
+
drawer.draw_rectangle(hovered.bounds, overlay_hover_style);
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
function draw_selection_overlay_editor(state, drawer) {
|
|
3791
|
+
const selected_ids = Array.from(state.selected_element_ids.values());
|
|
3792
|
+
const selection_bounds = selection_bounds_editor(state.engine.document, selected_ids);
|
|
3793
|
+
if (selection_bounds === null) {
|
|
3794
|
+
return;
|
|
3795
|
+
}
|
|
3796
|
+
drawer.draw_rectangle(selection_bounds, overlay_selection_style);
|
|
3797
|
+
const handles = selection_handles_for_elements_editor(state.engine.document, selected_ids);
|
|
3798
|
+
for (let i = 0;i < handles.length; i = i + 1) {
|
|
3799
|
+
const handle = handles[i];
|
|
3800
|
+
if (handle !== undefined) {
|
|
3801
|
+
drawer.draw_rectangle(handle.bounds, overlay_handle_style);
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
function draw_marquee_overlay_editor(state, drawer) {
|
|
3806
|
+
if (state.marquee_state === null) {
|
|
3807
|
+
return;
|
|
3808
|
+
}
|
|
3809
|
+
const marquee = marquee_rectangle_editor(state.marquee_state.origin_world[0], state.marquee_state.origin_world[1], state.marquee_state.current_world[0], state.marquee_state.current_world[1]);
|
|
3810
|
+
drawer.draw_rectangle(marquee, overlay_marquee_style);
|
|
3811
|
+
}
|
|
3812
|
+
function draw_guides_overlay_editor(state, drawer) {
|
|
3813
|
+
for (let i = 0;i < state.transient_guides.length; i = i + 1) {
|
|
3814
|
+
const guide = state.transient_guides[i];
|
|
3815
|
+
if (guide !== undefined) {
|
|
3816
|
+
drawer.draw_rectangle(guide, overlay_hover_style);
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
function draw_text_edit_overlay_editor(state, drawer) {
|
|
3821
|
+
if (state.text_edit === null) {
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
3824
|
+
const element = get_element_by_id_document(state.engine.document, state.text_edit.element_id);
|
|
3825
|
+
if (element === null || element.type !== element_type_text)
|
|
3826
|
+
return;
|
|
3827
|
+
const draft = state.text_edit.draft;
|
|
3828
|
+
const layout = build_text_layout_editor(element, draft);
|
|
3829
|
+
const anchor = state.text_edit.anchor;
|
|
3830
|
+
const caret = state.text_edit.caret;
|
|
3831
|
+
if (anchor !== null && anchor !== caret) {
|
|
3832
|
+
const start = Math.min(anchor, caret);
|
|
3833
|
+
const end = Math.max(anchor, caret);
|
|
3834
|
+
draw_text_selection_editor(drawer, element, layout, start, end);
|
|
3835
|
+
}
|
|
3836
|
+
draw_text_caret_editor(drawer, element, layout, caret);
|
|
3837
|
+
drawer.draw_rectangle(element.bounds, overlay_handle_style);
|
|
3838
|
+
}
|
|
3839
|
+
function draw_text_selection_editor(drawer, element, layout, start, end) {
|
|
3840
|
+
for (let i = 0;i < layout.lines.length; i = i + 1) {
|
|
3841
|
+
const line = layout.lines[i];
|
|
3842
|
+
if (line === undefined)
|
|
3843
|
+
continue;
|
|
3844
|
+
if (end <= line.start || start >= line.end)
|
|
3845
|
+
continue;
|
|
3846
|
+
const local_start = Math.max(0, start - line.start);
|
|
3847
|
+
const local_end = Math.max(local_start, Math.min(line.text.length, end - line.start));
|
|
3848
|
+
const x0 = line_x_offset_editor(element, layout, line.width) + measure_line_prefix_width_editor(line.text, local_start, element);
|
|
3849
|
+
const x1 = line_x_offset_editor(element, layout, line.width) + measure_line_prefix_width_editor(line.text, local_end, element);
|
|
3850
|
+
const y = element.bounds[1] + layout.padding + i * layout.line_height;
|
|
3851
|
+
drawer.draw_rectangle([x0, y, Math.max(1, x1 - x0), layout.line_height], overlay_marquee_style);
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
function draw_text_caret_editor(drawer, element, layout, caret) {
|
|
3855
|
+
const line_index = line_index_for_caret_editor(layout, caret);
|
|
3856
|
+
const line = layout.lines[line_index];
|
|
3857
|
+
if (line === undefined)
|
|
3858
|
+
return;
|
|
3859
|
+
const local = Math.max(0, Math.min(line.text.length, caret - line.start));
|
|
3860
|
+
const x = line_x_offset_editor(element, layout, line.width) + measure_line_prefix_width_editor(line.text, local, element);
|
|
3861
|
+
const y = element.bounds[1] + layout.padding + line_index * layout.line_height;
|
|
3862
|
+
drawer.draw_rectangle([x, y, 1.5, layout.line_height], text_caret_style_editor);
|
|
3863
|
+
}
|
|
3864
|
+
function line_x_offset_editor(element, layout, line_width) {
|
|
3865
|
+
if (element.align === text_align_center) {
|
|
3866
|
+
return element.bounds[0] + element.bounds[2] * 0.5 - line_width * 0.5;
|
|
3867
|
+
}
|
|
3868
|
+
if (element.align === text_align_right) {
|
|
3869
|
+
return element.bounds[0] + element.bounds[2] - layout.padding - line_width;
|
|
3870
|
+
}
|
|
3871
|
+
return element.bounds[0] + layout.padding;
|
|
3872
|
+
}
|
|
3873
|
+
function measure_line_prefix_width_editor(text, length, element) {
|
|
3874
|
+
const ctx = selection_measure_context_editor;
|
|
3875
|
+
if (ctx === null)
|
|
3876
|
+
return Math.max(0, length) * 8;
|
|
3877
|
+
const size = Math.max(1, element.font_size);
|
|
3878
|
+
const family = element.font_family;
|
|
3879
|
+
ctx.font = `${size}px ${family}`;
|
|
3880
|
+
return ctx.measureText(text.slice(0, Math.max(0, length))).width;
|
|
3881
|
+
}
|
|
3882
|
+
var selection_measure_canvas_editor = typeof document !== "undefined" ? document.createElement("canvas") : null;
|
|
3883
|
+
var selection_measure_context_editor = selection_measure_canvas_editor?.getContext("2d") ?? null;
|
|
3884
|
+
var text_caret_style_editor = {
|
|
3885
|
+
fill: [0.2, 0.5, 1, 0.95],
|
|
3886
|
+
stroke: null,
|
|
3887
|
+
stroke_width: 0,
|
|
3888
|
+
line_cap: 1,
|
|
3889
|
+
line_join: 1,
|
|
3890
|
+
miter_limit: 10,
|
|
3891
|
+
alpha: 1
|
|
3892
|
+
};
|
|
3893
|
+
|
|
3894
|
+
// editor/tools/draw.ts
|
|
3895
|
+
function create_draw_tool_editor() {
|
|
3896
|
+
return {
|
|
3897
|
+
id: editor_tool_draw,
|
|
3898
|
+
name: "draw",
|
|
3899
|
+
pointer_down: (state) => {
|
|
3900
|
+
begin_transaction_editor(state, "draw");
|
|
3901
|
+
state.drag_state = {
|
|
3902
|
+
kind: "draw",
|
|
3903
|
+
points: [[state.pointer_world[0], state.pointer_world[1]]],
|
|
3904
|
+
pressure: [1]
|
|
3905
|
+
};
|
|
3906
|
+
state.pointer_capture = true;
|
|
3907
|
+
},
|
|
3908
|
+
pointer_move: (state) => {
|
|
3909
|
+
if (state.drag_state === null || state.drag_state.kind !== "draw")
|
|
3910
|
+
return;
|
|
3911
|
+
state.drag_state.points.push([state.pointer_world[0], state.pointer_world[1]]);
|
|
3912
|
+
state.drag_state.pressure.push(1);
|
|
3913
|
+
},
|
|
3914
|
+
pointer_up: (state) => {
|
|
3915
|
+
if (state.drag_state !== null && state.drag_state.kind === "draw") {
|
|
3916
|
+
insert_drawn_stroke_editor(state, state.drag_state.points, state.drag_state.pressure);
|
|
3917
|
+
commit_transaction_editor(state);
|
|
3918
|
+
}
|
|
3919
|
+
state.drag_state = null;
|
|
3920
|
+
state.pointer_capture = false;
|
|
3921
|
+
},
|
|
3922
|
+
double_click: () => {},
|
|
3923
|
+
key_down: () => {},
|
|
3924
|
+
cancel: (state) => {
|
|
3925
|
+
cancel_transaction_editor(state);
|
|
3926
|
+
state.drag_state = null;
|
|
3927
|
+
state.pointer_capture = false;
|
|
3928
|
+
},
|
|
3929
|
+
hover: () => {},
|
|
3930
|
+
cursor: () => "crosshair",
|
|
3931
|
+
overlay: (state, drawer) => {
|
|
3932
|
+
if (state.drag_state?.kind === "draw" && state.drag_state.points.length > 1) {
|
|
3933
|
+
drawer.draw_polyline(state.drag_state.points, overlay_hover_style);
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
};
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3939
|
+
// editor/tools/hand.ts
|
|
3940
|
+
function create_hand_tool_editor() {
|
|
3941
|
+
return {
|
|
3942
|
+
id: editor_tool_hand,
|
|
3943
|
+
name: "hand",
|
|
3944
|
+
pointer_down: (state) => {
|
|
3945
|
+
state.drag_state = {
|
|
3946
|
+
kind: "pan",
|
|
3947
|
+
origin_screen: [state.pointer_screen[0], state.pointer_screen[1]],
|
|
3948
|
+
current_screen: [state.pointer_screen[0], state.pointer_screen[1]]
|
|
3949
|
+
};
|
|
3950
|
+
state.pointer_capture = true;
|
|
3951
|
+
},
|
|
3952
|
+
pointer_move: (state) => {
|
|
3953
|
+
if (state.drag_state === null || state.drag_state.kind !== "pan")
|
|
3954
|
+
return;
|
|
3955
|
+
const dx_screen = state.pointer_screen[0] - state.drag_state.current_screen[0];
|
|
3956
|
+
const dy_screen = state.pointer_screen[1] - state.drag_state.current_screen[1];
|
|
3957
|
+
state.drag_state.current_screen = [state.pointer_screen[0], state.pointer_screen[1]];
|
|
3958
|
+
const zoom = Math.max(0.001, state.engine.camera[2]);
|
|
3959
|
+
pan_engine(state.engine, -dx_screen / zoom, -dy_screen / zoom);
|
|
3960
|
+
},
|
|
3961
|
+
pointer_up: (state) => {
|
|
3962
|
+
state.drag_state = null;
|
|
3963
|
+
state.pointer_capture = false;
|
|
3964
|
+
},
|
|
3965
|
+
double_click: () => {},
|
|
3966
|
+
key_down: () => {},
|
|
3967
|
+
cancel: (state) => {
|
|
3968
|
+
state.drag_state = null;
|
|
3969
|
+
state.pointer_capture = false;
|
|
3970
|
+
},
|
|
3971
|
+
hover: () => {},
|
|
3972
|
+
cursor: () => "grab",
|
|
3973
|
+
overlay: () => {}
|
|
3974
|
+
};
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
// editor/snap.ts
|
|
3978
|
+
function snap_point_editor(state, point, context, meta) {
|
|
3979
|
+
if (state.snappers.length === 0) {
|
|
3980
|
+
state.transient_guides = [];
|
|
3981
|
+
return [point[0], point[1]];
|
|
3982
|
+
}
|
|
3983
|
+
let best_point = [point[0], point[1]];
|
|
3984
|
+
const guides = [];
|
|
3985
|
+
for (let i = 0;i < state.snappers.length; i = i + 1) {
|
|
3986
|
+
const snapper = state.snappers[i];
|
|
3987
|
+
if (snapper === undefined)
|
|
3988
|
+
continue;
|
|
3989
|
+
const result = snapper(state, best_point, context, meta);
|
|
3990
|
+
if (result === null)
|
|
3991
|
+
continue;
|
|
3992
|
+
best_point = [result.point[0], result.point[1]];
|
|
3993
|
+
for (let j = 0;j < result.guides.length; j = j + 1) {
|
|
3994
|
+
const guide = result.guides[j];
|
|
3995
|
+
if (guide !== undefined)
|
|
3996
|
+
guides.push(guide);
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
state.transient_guides = guides;
|
|
4000
|
+
return best_point;
|
|
4001
|
+
}
|
|
4002
|
+
function create_grid_snapper_editor(grid_size = 16, tolerance = 6) {
|
|
4003
|
+
const safe_grid = Math.max(1, grid_size);
|
|
4004
|
+
const safe_tol = Math.max(0.1, tolerance);
|
|
4005
|
+
return (_state, point) => {
|
|
4006
|
+
const gx = Math.round(point[0] / safe_grid) * safe_grid;
|
|
4007
|
+
const gy = Math.round(point[1] / safe_grid) * safe_grid;
|
|
4008
|
+
const dx = Math.abs(gx - point[0]);
|
|
4009
|
+
const dy = Math.abs(gy - point[1]);
|
|
4010
|
+
const snapped_x = dx <= safe_tol ? gx : point[0];
|
|
4011
|
+
const snapped_y = dy <= safe_tol ? gy : point[1];
|
|
4012
|
+
if (snapped_x === point[0] && snapped_y === point[1]) {
|
|
4013
|
+
return null;
|
|
4014
|
+
}
|
|
4015
|
+
const guides = [];
|
|
4016
|
+
if (snapped_x !== point[0])
|
|
4017
|
+
guides.push([snapped_x - 0.25, -1e5, 0.5, 200000]);
|
|
4018
|
+
if (snapped_y !== point[1])
|
|
4019
|
+
guides.push([-1e5, snapped_y - 0.25, 200000, 0.5]);
|
|
4020
|
+
return { point: [snapped_x, snapped_y], guides };
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
4023
|
+
function create_element_smart_snapper_editor(tolerance = 6) {
|
|
4024
|
+
const safe_tol = Math.max(0.1, tolerance);
|
|
4025
|
+
return (state, point, context, meta) => {
|
|
4026
|
+
if (context === "rotate") {
|
|
4027
|
+
return null;
|
|
4028
|
+
}
|
|
4029
|
+
const candidates_x = [];
|
|
4030
|
+
const candidates_y = [];
|
|
4031
|
+
for (const element of state.engine.document.elements.values()) {
|
|
4032
|
+
if (state.selected_element_ids.has(element.id)) {
|
|
4033
|
+
continue;
|
|
4034
|
+
}
|
|
4035
|
+
const bounds = element.bounds;
|
|
4036
|
+
candidates_x.push(bounds[0], bounds[0] + bounds[2], bounds[0] + bounds[2] / 2);
|
|
4037
|
+
candidates_y.push(bounds[1], bounds[1] + bounds[3], bounds[1] + bounds[3] / 2);
|
|
4038
|
+
}
|
|
4039
|
+
const subject = meta?.subject_bounds;
|
|
4040
|
+
const snapped_x = subject ? closest_bounds_snap_delta_editor([subject[0], subject[0] + subject[2], subject[0] + subject[2] / 2], candidates_x, safe_tol) : closest_axis_snap_editor(point[0], candidates_x, safe_tol);
|
|
4041
|
+
const snapped_y = subject ? closest_bounds_snap_delta_editor([subject[1], subject[1] + subject[3], subject[1] + subject[3] / 2], candidates_y, safe_tol) : closest_axis_snap_editor(point[1], candidates_y, safe_tol);
|
|
4042
|
+
if (snapped_x === null && snapped_y === null) {
|
|
4043
|
+
return null;
|
|
4044
|
+
}
|
|
4045
|
+
const out_x = subject ? point[0] + (snapped_x?.delta ?? 0) : snapped_x?.value ?? point[0];
|
|
4046
|
+
const out_y = subject ? point[1] + (snapped_y?.delta ?? 0) : snapped_y?.value ?? point[1];
|
|
4047
|
+
const guides = [];
|
|
4048
|
+
if (snapped_x !== null) {
|
|
4049
|
+
guides.push([snapped_x.value - 0.25, -1e5, 0.5, 200000]);
|
|
4050
|
+
}
|
|
4051
|
+
if (snapped_y !== null) {
|
|
4052
|
+
guides.push([-1e5, snapped_y.value - 0.25, 200000, 0.5]);
|
|
4053
|
+
}
|
|
4054
|
+
return { point: [out_x, out_y], guides };
|
|
4055
|
+
};
|
|
4056
|
+
}
|
|
4057
|
+
function closest_axis_snap_editor(value, candidates, tolerance) {
|
|
4058
|
+
let best = null;
|
|
4059
|
+
let best_delta = Infinity;
|
|
4060
|
+
for (let i = 0;i < candidates.length; i = i + 1) {
|
|
4061
|
+
const candidate = candidates[i];
|
|
4062
|
+
if (candidate === undefined) {
|
|
4063
|
+
continue;
|
|
4064
|
+
}
|
|
4065
|
+
const delta = Math.abs(candidate - value);
|
|
4066
|
+
if (delta <= tolerance && delta < best_delta) {
|
|
4067
|
+
best_delta = delta;
|
|
4068
|
+
best = { value: candidate, delta: candidate - value };
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
return best;
|
|
4072
|
+
}
|
|
4073
|
+
function closest_bounds_snap_delta_editor(subject_values, candidates, tolerance) {
|
|
4074
|
+
let best = null;
|
|
4075
|
+
let best_delta = Infinity;
|
|
4076
|
+
for (let i = 0;i < subject_values.length; i = i + 1) {
|
|
4077
|
+
const subject = subject_values[i];
|
|
4078
|
+
if (subject === undefined)
|
|
4079
|
+
continue;
|
|
4080
|
+
for (let j = 0;j < candidates.length; j = j + 1) {
|
|
4081
|
+
const candidate = candidates[j];
|
|
4082
|
+
if (candidate === undefined)
|
|
4083
|
+
continue;
|
|
4084
|
+
const delta = candidate - subject;
|
|
4085
|
+
const abs = Math.abs(delta);
|
|
4086
|
+
if (abs <= tolerance && abs < best_delta) {
|
|
4087
|
+
best_delta = abs;
|
|
4088
|
+
best = { value: candidate, delta };
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
4092
|
+
return best;
|
|
4093
|
+
}
|
|
4094
|
+
|
|
4095
|
+
// editor/tools/image.ts
|
|
4096
|
+
function create_image_tool_editor() {
|
|
4097
|
+
return {
|
|
4098
|
+
id: editor_tool_image_place,
|
|
4099
|
+
name: "image_place",
|
|
4100
|
+
pointer_down: (state) => {
|
|
4101
|
+
begin_transaction_editor(state, "image");
|
|
4102
|
+
const element_id = next_id_editor(state, "image");
|
|
4103
|
+
const x = state.pointer_world[0];
|
|
4104
|
+
const y = state.pointer_world[1];
|
|
4105
|
+
const image = create_image_element(element_id, [x, y, 220, 140], next_z_index_editor(state.engine.document), state.engine.document.active_layer_id, "", 0, 0, 1);
|
|
4106
|
+
state.engine.document = add_element_document(state.engine.document, image);
|
|
4107
|
+
state.selected_element_ids.clear();
|
|
4108
|
+
state.selected_element_ids.add(element_id);
|
|
4109
|
+
state.drag_state = { kind: "image", origin_world: [x, y], current_world: [x, y], element_id };
|
|
4110
|
+
state.pointer_capture = true;
|
|
4111
|
+
},
|
|
4112
|
+
pointer_move: (state) => {
|
|
4113
|
+
if (state.drag_state === null || state.drag_state.kind !== "image")
|
|
4114
|
+
return;
|
|
4115
|
+
state.drag_state.current_world = snap_point_editor(state, [state.pointer_world[0], state.pointer_world[1]], "image");
|
|
4116
|
+
update_box_resize_editor(state, state.drag_state.element_id, state.drag_state.origin_world, state.drag_state.current_world);
|
|
4117
|
+
},
|
|
4118
|
+
pointer_up: (state) => {
|
|
4119
|
+
if (state.drag_state?.kind === "image")
|
|
4120
|
+
commit_transaction_editor(state);
|
|
4121
|
+
state.drag_state = null;
|
|
4122
|
+
state.pointer_capture = false;
|
|
4123
|
+
},
|
|
4124
|
+
double_click: () => {},
|
|
4125
|
+
key_down: () => {},
|
|
4126
|
+
cancel: (state) => {
|
|
4127
|
+
cancel_transaction_editor(state);
|
|
4128
|
+
state.drag_state = null;
|
|
4129
|
+
state.pointer_capture = false;
|
|
4130
|
+
},
|
|
4131
|
+
hover: () => {},
|
|
4132
|
+
cursor: () => "copy",
|
|
4133
|
+
overlay: () => {}
|
|
4134
|
+
};
|
|
4135
|
+
}
|
|
4136
|
+
|
|
4137
|
+
// editor/tools/lineresize.ts
|
|
4138
|
+
function apply_line_endpoint_resize_editor(state) {
|
|
4139
|
+
if (state.drag_state === null || state.drag_state.kind !== "resize") {
|
|
4140
|
+
return false;
|
|
4141
|
+
}
|
|
4142
|
+
if (state.drag_state.handle !== "start" && state.drag_state.handle !== "end") {
|
|
4143
|
+
return false;
|
|
4144
|
+
}
|
|
4145
|
+
if (state.drag_state.resized_element_ids.length !== 1) {
|
|
4146
|
+
return false;
|
|
4147
|
+
}
|
|
4148
|
+
const element_id = state.drag_state.resized_element_ids[0];
|
|
4149
|
+
if (element_id === undefined) {
|
|
4150
|
+
return false;
|
|
4151
|
+
}
|
|
4152
|
+
const base = get_element_by_id_document(state.drag_state.base_document, element_id);
|
|
4153
|
+
if (base === null || base.type !== element_type_shape) {
|
|
4154
|
+
return false;
|
|
4155
|
+
}
|
|
4156
|
+
if (base.shape_type !== shape_type_line && base.shape_type !== shape_type_arrow) {
|
|
4157
|
+
return false;
|
|
4158
|
+
}
|
|
4159
|
+
const updated = clone_element_editor(base);
|
|
4160
|
+
const shape = updated.type === element_type_shape ? updated : null;
|
|
4161
|
+
if (shape === null) {
|
|
4162
|
+
return false;
|
|
4163
|
+
}
|
|
4164
|
+
if (shape.start_point === null) {
|
|
4165
|
+
shape.start_point = [shape.bounds[0], shape.bounds[1]];
|
|
4166
|
+
}
|
|
4167
|
+
if (shape.end_point === null) {
|
|
4168
|
+
shape.end_point = [shape.bounds[0] + shape.bounds[2], shape.bounds[1] + shape.bounds[3]];
|
|
4169
|
+
}
|
|
4170
|
+
if (state.drag_state.handle === "start") {
|
|
4171
|
+
shape.start_point = [state.drag_state.current_world[0], state.drag_state.current_world[1]];
|
|
4172
|
+
} else {
|
|
4173
|
+
shape.end_point = [state.drag_state.current_world[0], state.drag_state.current_world[1]];
|
|
4174
|
+
}
|
|
4175
|
+
shape.bounds = [
|
|
4176
|
+
Math.min(shape.start_point[0], shape.end_point[0]),
|
|
4177
|
+
Math.min(shape.start_point[1], shape.end_point[1]),
|
|
4178
|
+
Math.max(1, Math.abs(shape.end_point[0] - shape.start_point[0])),
|
|
4179
|
+
Math.max(1, Math.abs(shape.end_point[1] - shape.start_point[1]))
|
|
4180
|
+
];
|
|
4181
|
+
state.engine.document = update_element_document(state.drag_state.base_document, element_id, shape);
|
|
4182
|
+
return true;
|
|
4183
|
+
}
|
|
4184
|
+
|
|
4185
|
+
// editor/tools/selectops.ts
|
|
4186
|
+
var drag_threshold_screen_editor = 4;
|
|
4187
|
+
function start_move_editor(state, ids) {
|
|
4188
|
+
const expanded_ids = expand_move_ids_for_frames_editor(state.engine.document.elements, ids);
|
|
4189
|
+
const selection_bounds = selection_bounds_editor(state.engine.document, expanded_ids) ?? [
|
|
4190
|
+
0,
|
|
4191
|
+
0,
|
|
4192
|
+
1,
|
|
4193
|
+
1
|
|
4194
|
+
];
|
|
4195
|
+
state.drag_state = {
|
|
4196
|
+
kind: "move",
|
|
4197
|
+
origin_world: [state.pointer_world[0], state.pointer_world[1]],
|
|
4198
|
+
current_world: [state.pointer_world[0], state.pointer_world[1]],
|
|
4199
|
+
base_document: clone_document(state.engine.document),
|
|
4200
|
+
moved_element_ids: expanded_ids,
|
|
4201
|
+
selection_bounds
|
|
4202
|
+
};
|
|
4203
|
+
state.pointer_capture = true;
|
|
4204
|
+
}
|
|
4205
|
+
function exceeds_drag_threshold_editor(origin, current) {
|
|
4206
|
+
const dx = current[0] - origin[0];
|
|
4207
|
+
const dy = current[1] - origin[1];
|
|
4208
|
+
return dx * dx + dy * dy >= drag_threshold_screen_editor * drag_threshold_screen_editor;
|
|
4209
|
+
}
|
|
4210
|
+
function apply_move_drag_editor(state) {
|
|
4211
|
+
if (state.drag_state === null || state.drag_state.kind !== "move")
|
|
4212
|
+
return;
|
|
4213
|
+
const dx = state.drag_state.current_world[0] - state.drag_state.origin_world[0];
|
|
4214
|
+
const dy = state.drag_state.current_world[1] - state.drag_state.origin_world[1];
|
|
4215
|
+
state.engine.document = transform_elements_editor(state.drag_state.base_document, state.drag_state.moved_element_ids, (point) => [point[0] + dx, point[1] + dy]);
|
|
4216
|
+
}
|
|
4217
|
+
function apply_marquee_selection_editor(state, marquee_state) {
|
|
4218
|
+
const rect = marquee_rectangle_editor(marquee_state.origin_world[0], marquee_state.origin_world[1], marquee_state.current_world[0], marquee_state.current_world[1]);
|
|
4219
|
+
if (!marquee_state.add_mode)
|
|
4220
|
+
state.selected_element_ids.clear();
|
|
4221
|
+
const in_bounds = elements_in_bounds_document(state.engine.document, rect);
|
|
4222
|
+
const hit_ids = [];
|
|
4223
|
+
for (let i = 0;i < in_bounds.length; i = i + 1) {
|
|
4224
|
+
const el = in_bounds[i];
|
|
4225
|
+
if (el !== undefined)
|
|
4226
|
+
hit_ids.push(el.id);
|
|
4227
|
+
}
|
|
4228
|
+
const expanded = expand_ids_with_groups_editor(state.engine.document, hit_ids);
|
|
4229
|
+
for (let i = 0;i < expanded.length; i = i + 1) {
|
|
4230
|
+
const id = expanded[i];
|
|
4231
|
+
if (id !== undefined)
|
|
4232
|
+
state.selected_element_ids.add(id);
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
function apply_resize_drag_editor(state) {
|
|
4236
|
+
if (state.drag_state === null || state.drag_state.kind !== "resize")
|
|
4237
|
+
return;
|
|
4238
|
+
if (apply_line_endpoint_resize_editor(state))
|
|
4239
|
+
return;
|
|
4240
|
+
const base = state.drag_state.selection_bounds;
|
|
4241
|
+
const left = base[0];
|
|
4242
|
+
const right = base[0] + base[2];
|
|
4243
|
+
const top = base[1];
|
|
4244
|
+
const bottom = base[1] + base[3];
|
|
4245
|
+
const width = Math.max(0.0001, right - left);
|
|
4246
|
+
const height = Math.max(0.0001, bottom - top);
|
|
4247
|
+
const c = state.drag_state.current_world;
|
|
4248
|
+
const handle = state.drag_state.handle;
|
|
4249
|
+
const centered = state.drag_state.centered;
|
|
4250
|
+
const keep_aspect = state.drag_state.keep_aspect;
|
|
4251
|
+
const anchor_x = centered ? left + width / 2 : handle.includes("w") ? right : handle.includes("e") ? left : left + width / 2;
|
|
4252
|
+
const anchor_y = centered ? top + height / 2 : handle.includes("n") ? bottom : handle.includes("s") ? top : top + height / 2;
|
|
4253
|
+
let sx = 1;
|
|
4254
|
+
let sy = 1;
|
|
4255
|
+
if (handle.includes("w"))
|
|
4256
|
+
sx = centered ? Math.abs(c[0] - anchor_x) * 2 / width : (right - c[0]) / width;
|
|
4257
|
+
if (handle.includes("e"))
|
|
4258
|
+
sx = centered ? Math.abs(c[0] - anchor_x) * 2 / width : (c[0] - left) / width;
|
|
4259
|
+
if (handle.includes("n"))
|
|
4260
|
+
sy = centered ? Math.abs(c[1] - anchor_y) * 2 / height : (bottom - c[1]) / height;
|
|
4261
|
+
if (handle.includes("s"))
|
|
4262
|
+
sy = centered ? Math.abs(c[1] - anchor_y) * 2 / height : (c[1] - top) / height;
|
|
4263
|
+
sx = Math.max(0.0001, sx);
|
|
4264
|
+
sy = Math.max(0.0001, sy);
|
|
4265
|
+
if (keep_aspect) {
|
|
4266
|
+
const has_x = handle.includes("w") || handle.includes("e");
|
|
4267
|
+
const has_y = handle.includes("n") || handle.includes("s");
|
|
4268
|
+
const scale = !has_y ? sx : !has_x ? sy : Math.max(sx, sy);
|
|
4269
|
+
sx = scale;
|
|
4270
|
+
sy = scale;
|
|
4271
|
+
}
|
|
4272
|
+
const nl = anchor_x - (anchor_x - left) * sx;
|
|
4273
|
+
const nt = anchor_y - (anchor_y - top) * sy;
|
|
4274
|
+
state.engine.document = transform_elements_editor(state.drag_state.base_document, state.drag_state.resized_element_ids, (point) => [nl + (point[0] - left) * sx, nt + (point[1] - top) * sy]);
|
|
4275
|
+
}
|
|
4276
|
+
function apply_rotate_drag_editor(state) {
|
|
4277
|
+
if (state.drag_state === null || state.drag_state.kind !== "rotate")
|
|
4278
|
+
return;
|
|
4279
|
+
const center = state.drag_state.center;
|
|
4280
|
+
const current_angle = angle_to_center_editor(center, state.drag_state.current_world);
|
|
4281
|
+
let delta = current_angle - state.drag_state.start_angle;
|
|
4282
|
+
if (state.drag_state.snap_angle) {
|
|
4283
|
+
const snap = Math.PI / 12;
|
|
4284
|
+
delta = Math.round(delta / snap) * snap;
|
|
4285
|
+
}
|
|
4286
|
+
const sin_angle = Math.sin(delta);
|
|
4287
|
+
const cos_angle = Math.cos(delta);
|
|
4288
|
+
state.engine.document = transform_elements_editor(state.drag_state.base_document, state.drag_state.rotated_element_ids, (point) => {
|
|
4289
|
+
const dx = point[0] - center[0];
|
|
4290
|
+
const dy = point[1] - center[1];
|
|
4291
|
+
return [
|
|
4292
|
+
center[0] + dx * cos_angle - dy * sin_angle,
|
|
4293
|
+
center[1] + dx * sin_angle + dy * cos_angle
|
|
4294
|
+
];
|
|
4295
|
+
});
|
|
4296
|
+
}
|
|
4297
|
+
function expand_move_ids_for_frames_editor(elements, selected_ids) {
|
|
4298
|
+
const out = new Set;
|
|
4299
|
+
const selected_frames = [];
|
|
4300
|
+
for (let i = 0;i < selected_ids.length; i = i + 1) {
|
|
4301
|
+
const id = selected_ids[i];
|
|
4302
|
+
if (id === undefined)
|
|
4303
|
+
continue;
|
|
4304
|
+
const element = elements.get(id);
|
|
4305
|
+
if (element === undefined)
|
|
4306
|
+
continue;
|
|
4307
|
+
out.add(id);
|
|
4308
|
+
if (element.type === element_type_shape && element.shape_type === shape_type_frame) {
|
|
4309
|
+
selected_frames.push(element);
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
if (selected_frames.length === 0) {
|
|
4313
|
+
return Array.from(out);
|
|
4314
|
+
}
|
|
4315
|
+
for (const element of elements.values()) {
|
|
4316
|
+
if (out.has(element.id))
|
|
4317
|
+
continue;
|
|
4318
|
+
const cx = element.bounds[0] + element.bounds[2] / 2;
|
|
4319
|
+
const cy = element.bounds[1] + element.bounds[3] / 2;
|
|
4320
|
+
for (let i = 0;i < selected_frames.length; i = i + 1) {
|
|
4321
|
+
const frame = selected_frames[i];
|
|
4322
|
+
if (frame === undefined)
|
|
4323
|
+
continue;
|
|
4324
|
+
if (contains_point_in_bounds_editor(frame.bounds, cx, cy)) {
|
|
4325
|
+
out.add(element.id);
|
|
4326
|
+
break;
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
return Array.from(out);
|
|
4331
|
+
}
|
|
4332
|
+
function contains_point_in_bounds_editor(bounds, x, y) {
|
|
4333
|
+
return x >= bounds[0] && x <= bounds[0] + bounds[2] && y >= bounds[1] && y <= bounds[1] + bounds[3];
|
|
4334
|
+
}
|
|
4335
|
+
|
|
4336
|
+
// editor/textops.ts
|
|
4337
|
+
function move_caret_line_editor(state, direction2, extend) {
|
|
4338
|
+
if (state.text_edit === null)
|
|
4339
|
+
return false;
|
|
4340
|
+
const element = get_element_by_id_document(state.engine.document, state.text_edit.element_id);
|
|
4341
|
+
if (element === null || element.type !== element_type_text)
|
|
4342
|
+
return false;
|
|
4343
|
+
const previous_caret = state.text_edit.caret;
|
|
4344
|
+
const caret = state.text_edit.caret;
|
|
4345
|
+
const layout = build_text_layout_editor(element, state.text_edit.draft);
|
|
4346
|
+
const line_index = line_index_for_caret_editor(layout, caret);
|
|
4347
|
+
const desired_x = state.text_edit.preferred_column ?? line_caret_x_editor(element, layout, line_index, caret);
|
|
4348
|
+
const target_line = Math.max(0, Math.min(layout.lines.length - 1, line_index + (direction2 < 0 ? -1 : 1)));
|
|
4349
|
+
const target = nearest_caret_for_x_editor(element, layout, target_line, desired_x);
|
|
4350
|
+
collapse_selection_if_needed_editor(state, direction2 < 0 ? "start" : "end", extend);
|
|
4351
|
+
state.text_edit.caret = target;
|
|
4352
|
+
update_anchor_editor(state, extend, previous_caret);
|
|
4353
|
+
state.text_edit.preferred_column = desired_x;
|
|
4354
|
+
return true;
|
|
4355
|
+
}
|
|
4356
|
+
function select_all_text_editor(state) {
|
|
4357
|
+
if (state.text_edit === null)
|
|
4358
|
+
return false;
|
|
4359
|
+
state.text_edit.anchor = 0;
|
|
4360
|
+
state.text_edit.caret = state.text_edit.draft.length;
|
|
4361
|
+
state.text_edit.preferred_column = null;
|
|
4362
|
+
return true;
|
|
4363
|
+
}
|
|
4364
|
+
function delete_selection_text_editor(state) {
|
|
4365
|
+
if (state.text_edit === null || state.text_edit.anchor === null)
|
|
4366
|
+
return false;
|
|
4367
|
+
const start = Math.min(state.text_edit.anchor, state.text_edit.caret);
|
|
4368
|
+
const end = Math.max(state.text_edit.anchor, state.text_edit.caret);
|
|
4369
|
+
if (start === end)
|
|
4370
|
+
return false;
|
|
4371
|
+
state.text_edit.draft = `${state.text_edit.draft.slice(0, start)}${state.text_edit.draft.slice(end)}`;
|
|
4372
|
+
state.text_edit.caret = start;
|
|
4373
|
+
state.text_edit.anchor = null;
|
|
4374
|
+
return true;
|
|
4375
|
+
}
|
|
4376
|
+
function collapse_selection_if_needed_editor(state, edge, extend) {
|
|
4377
|
+
if (state.text_edit === null || extend || state.text_edit.anchor === null)
|
|
4378
|
+
return;
|
|
4379
|
+
const start = Math.min(state.text_edit.anchor, state.text_edit.caret);
|
|
4380
|
+
const end = Math.max(state.text_edit.anchor, state.text_edit.caret);
|
|
4381
|
+
if (start === end) {
|
|
4382
|
+
state.text_edit.anchor = null;
|
|
4383
|
+
return;
|
|
4384
|
+
}
|
|
4385
|
+
state.text_edit.caret = edge === "start" ? start : end;
|
|
4386
|
+
state.text_edit.anchor = null;
|
|
4387
|
+
}
|
|
4388
|
+
function update_anchor_editor(state, extend, previous_caret) {
|
|
4389
|
+
if (state.text_edit === null)
|
|
4390
|
+
return;
|
|
4391
|
+
if (extend) {
|
|
4392
|
+
if (state.text_edit.anchor === null)
|
|
4393
|
+
state.text_edit.anchor = previous_caret;
|
|
4394
|
+
return;
|
|
4395
|
+
}
|
|
4396
|
+
state.text_edit.anchor = null;
|
|
4397
|
+
}
|
|
4398
|
+
|
|
4399
|
+
// editor/textedit.ts
|
|
4400
|
+
function begin_text_edit_editor(state, element_id, caret_from_world) {
|
|
4401
|
+
const element = get_element_by_id_document(state.engine.document, element_id);
|
|
4402
|
+
if (element === null || element.type !== element_type_text) {
|
|
4403
|
+
return false;
|
|
4404
|
+
}
|
|
4405
|
+
const initial_caret = caret_from_world === undefined ? element.content.length : caret_index_from_point_editor(element, element.content, caret_from_world[0], caret_from_world[1]);
|
|
4406
|
+
state.text_edit = {
|
|
4407
|
+
element_id,
|
|
4408
|
+
draft: element.content,
|
|
4409
|
+
original: element.content,
|
|
4410
|
+
caret: initial_caret,
|
|
4411
|
+
anchor: null,
|
|
4412
|
+
preferred_column: null
|
|
4413
|
+
};
|
|
4414
|
+
state.selected_element_ids.clear();
|
|
4415
|
+
state.selected_element_ids.add(element_id);
|
|
4416
|
+
return true;
|
|
4417
|
+
}
|
|
4418
|
+
function is_text_editing_editor(state) {
|
|
4419
|
+
return state.text_edit !== null;
|
|
4420
|
+
}
|
|
4421
|
+
function commit_text_edit_editor(state) {
|
|
4422
|
+
if (state.text_edit === null)
|
|
4423
|
+
return false;
|
|
4424
|
+
const editing = state.text_edit;
|
|
4425
|
+
const element = get_element_by_id_document(state.engine.document, editing.element_id);
|
|
4426
|
+
if (element === null || element.type !== element_type_text) {
|
|
4427
|
+
state.text_edit = null;
|
|
4428
|
+
return false;
|
|
4429
|
+
}
|
|
4430
|
+
if (editing.draft !== editing.original) {
|
|
4431
|
+
begin_transaction_editor(state, "text_edit");
|
|
4432
|
+
state.engine.document = update_element_document(state.engine.document, element.id, {
|
|
4433
|
+
...element,
|
|
4434
|
+
content: editing.draft
|
|
4435
|
+
});
|
|
4436
|
+
commit_transaction_editor(state);
|
|
4437
|
+
}
|
|
4438
|
+
state.text_edit = null;
|
|
4439
|
+
return true;
|
|
4440
|
+
}
|
|
4441
|
+
function cancel_text_edit_editor(state) {
|
|
4442
|
+
if (state.text_edit === null)
|
|
4443
|
+
return false;
|
|
4444
|
+
state.text_edit = null;
|
|
4445
|
+
return true;
|
|
4446
|
+
}
|
|
4447
|
+
function handle_text_key_editor(state, input) {
|
|
4448
|
+
if (state.text_edit === null)
|
|
4449
|
+
return false;
|
|
4450
|
+
const key = input.key;
|
|
4451
|
+
if (key === "Escape")
|
|
4452
|
+
return cancel_text_edit_editor(state);
|
|
4453
|
+
if (key === "Enter" && !!input.ctrl)
|
|
4454
|
+
return commit_text_edit_editor(state);
|
|
4455
|
+
if (key === "ArrowLeft")
|
|
4456
|
+
return move_caret_editor(state, -1, !!input.shift);
|
|
4457
|
+
if (key === "ArrowRight")
|
|
4458
|
+
return move_caret_editor(state, 1, !!input.shift);
|
|
4459
|
+
if (key === "ArrowUp")
|
|
4460
|
+
return move_caret_line_editor(state, -1, !!input.shift);
|
|
4461
|
+
if (key === "ArrowDown")
|
|
4462
|
+
return move_caret_line_editor(state, 1, !!input.shift);
|
|
4463
|
+
if (key === "Home")
|
|
4464
|
+
return set_caret_editor(state, 0, !!input.shift);
|
|
4465
|
+
if (key === "End")
|
|
4466
|
+
return set_caret_editor(state, state.text_edit.draft.length, !!input.shift);
|
|
4467
|
+
if ((key === "a" || key === "A") && (input.ctrl || input.meta))
|
|
4468
|
+
return select_all_text_editor(state);
|
|
4469
|
+
if (key === "Backspace")
|
|
4470
|
+
return backspace_editor(state);
|
|
4471
|
+
if (key === "Delete")
|
|
4472
|
+
return delete_editor(state);
|
|
4473
|
+
if (key === "Enter")
|
|
4474
|
+
return insert_text_editor(state, `
|
|
4475
|
+
`);
|
|
4476
|
+
if (key === "Tab")
|
|
4477
|
+
return insert_text_editor(state, "\t");
|
|
4478
|
+
if (!input.ctrl && !input.meta && !input.alt && key.length === 1) {
|
|
4479
|
+
return insert_text_editor(state, key);
|
|
4480
|
+
}
|
|
4481
|
+
return true;
|
|
4482
|
+
}
|
|
4483
|
+
function move_caret_editor(state, delta, extend) {
|
|
4484
|
+
if (state.text_edit === null)
|
|
4485
|
+
return false;
|
|
4486
|
+
const previous_caret = state.text_edit.caret;
|
|
4487
|
+
collapse_selection_if_needed_editor(state, delta < 0 ? "start" : "end", extend);
|
|
4488
|
+
const next = clamp_caret_editor(state.text_edit.caret + delta, state.text_edit.draft.length);
|
|
4489
|
+
state.text_edit.caret = next;
|
|
4490
|
+
update_anchor_editor(state, extend, previous_caret);
|
|
4491
|
+
state.text_edit.preferred_column = null;
|
|
4492
|
+
return true;
|
|
4493
|
+
}
|
|
4494
|
+
function set_caret_editor(state, value, extend) {
|
|
4495
|
+
if (state.text_edit === null)
|
|
4496
|
+
return false;
|
|
4497
|
+
const previous_caret = state.text_edit.caret;
|
|
4498
|
+
collapse_selection_if_needed_editor(state, value <= state.text_edit.caret ? "start" : "end", extend);
|
|
4499
|
+
state.text_edit.caret = clamp_caret_editor(value, state.text_edit.draft.length);
|
|
4500
|
+
update_anchor_editor(state, extend, previous_caret);
|
|
4501
|
+
state.text_edit.preferred_column = null;
|
|
4502
|
+
return true;
|
|
4503
|
+
}
|
|
4504
|
+
function backspace_editor(state) {
|
|
4505
|
+
if (state.text_edit === null)
|
|
4506
|
+
return false;
|
|
4507
|
+
if (delete_selection_text_editor(state))
|
|
4508
|
+
return true;
|
|
4509
|
+
const caret = state.text_edit.caret;
|
|
4510
|
+
if (caret <= 0)
|
|
4511
|
+
return true;
|
|
4512
|
+
const before = state.text_edit.draft.slice(0, caret - 1);
|
|
4513
|
+
const after = state.text_edit.draft.slice(caret);
|
|
4514
|
+
state.text_edit.draft = `${before}${after}`;
|
|
4515
|
+
state.text_edit.caret = caret - 1;
|
|
4516
|
+
state.text_edit.anchor = null;
|
|
4517
|
+
state.text_edit.preferred_column = null;
|
|
4518
|
+
return true;
|
|
4519
|
+
}
|
|
4520
|
+
function delete_editor(state) {
|
|
4521
|
+
if (state.text_edit === null)
|
|
4522
|
+
return false;
|
|
4523
|
+
if (delete_selection_text_editor(state))
|
|
4524
|
+
return true;
|
|
4525
|
+
const caret = state.text_edit.caret;
|
|
4526
|
+
if (caret >= state.text_edit.draft.length)
|
|
4527
|
+
return true;
|
|
4528
|
+
const before = state.text_edit.draft.slice(0, caret);
|
|
4529
|
+
const after = state.text_edit.draft.slice(caret + 1);
|
|
4530
|
+
state.text_edit.draft = `${before}${after}`;
|
|
4531
|
+
state.text_edit.anchor = null;
|
|
4532
|
+
state.text_edit.preferred_column = null;
|
|
4533
|
+
return true;
|
|
4534
|
+
}
|
|
4535
|
+
function insert_text_editor(state, value) {
|
|
4536
|
+
if (state.text_edit === null)
|
|
4537
|
+
return false;
|
|
4538
|
+
delete_selection_text_editor(state);
|
|
4539
|
+
const caret = state.text_edit.caret;
|
|
4540
|
+
const before = state.text_edit.draft.slice(0, caret);
|
|
4541
|
+
const after = state.text_edit.draft.slice(caret);
|
|
4542
|
+
state.text_edit.draft = `${before}${value}${after}`;
|
|
4543
|
+
state.text_edit.caret = caret + value.length;
|
|
4544
|
+
state.text_edit.anchor = null;
|
|
4545
|
+
state.text_edit.preferred_column = null;
|
|
4546
|
+
return true;
|
|
4547
|
+
}
|
|
4548
|
+
function clamp_caret_editor(value, length) {
|
|
4549
|
+
return Math.max(0, Math.min(length, value));
|
|
4550
|
+
}
|
|
4551
|
+
|
|
4552
|
+
// editor/tools/selectview.ts
|
|
4553
|
+
function handle_select_double_click_editor(state) {
|
|
4554
|
+
const hit = element_hit_at_point_editor(state.engine.document, state.pointer_world[0], state.pointer_world[1]);
|
|
4555
|
+
if (hit !== null && hit.type === element_type_text) {
|
|
4556
|
+
begin_text_edit_editor(state, hit.id, [state.pointer_world[0], state.pointer_world[1]]);
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
function handle_select_pointer_up_reset_editor(state) {
|
|
4560
|
+
state.drag_state = null;
|
|
4561
|
+
state.marquee_state = null;
|
|
4562
|
+
state.active_handle = null;
|
|
4563
|
+
state.pointer_capture = false;
|
|
4564
|
+
}
|
|
4565
|
+
function cursor_select_editor(state) {
|
|
4566
|
+
if (state.drag_state?.kind === "move")
|
|
4567
|
+
return "grabbing";
|
|
4568
|
+
if (state.drag_state?.kind === "pending_move")
|
|
4569
|
+
return "grab";
|
|
4570
|
+
if (state.drag_state?.kind === "resize")
|
|
4571
|
+
return "nwse-resize";
|
|
4572
|
+
if (state.drag_state?.kind === "rotate")
|
|
4573
|
+
return "crosshair";
|
|
4574
|
+
if (state.active_handle === "rotate")
|
|
4575
|
+
return "crosshair";
|
|
4576
|
+
if (state.active_handle === "start" || state.active_handle === "end")
|
|
4577
|
+
return "crosshair";
|
|
4578
|
+
if (["nw", "se"].includes(state.active_handle ?? ""))
|
|
4579
|
+
return "nwse-resize";
|
|
4580
|
+
if (["ne", "sw"].includes(state.active_handle ?? ""))
|
|
4581
|
+
return "nesw-resize";
|
|
4582
|
+
if (["n", "s"].includes(state.active_handle ?? ""))
|
|
4583
|
+
return "ns-resize";
|
|
4584
|
+
if (["e", "w"].includes(state.active_handle ?? ""))
|
|
4585
|
+
return "ew-resize";
|
|
4586
|
+
return "default";
|
|
4587
|
+
}
|
|
4588
|
+
|
|
4589
|
+
// editor/tools/select.ts
|
|
4590
|
+
function create_select_tool_editor() {
|
|
4591
|
+
return {
|
|
4592
|
+
id: editor_tool_select,
|
|
4593
|
+
name: "select",
|
|
4594
|
+
pointer_down: (state, input) => {
|
|
4595
|
+
const selected_ids = Array.from(state.selected_element_ids.values());
|
|
4596
|
+
const selected_bounds = selection_bounds_editor(state.engine.document, selected_ids);
|
|
4597
|
+
const handles = selection_handles_for_elements_editor(state.engine.document, selected_ids);
|
|
4598
|
+
const handle_hit = hit_selection_handles_editor(handles, state.pointer_world);
|
|
4599
|
+
if (handle_hit !== null) {
|
|
4600
|
+
state.active_handle = handle_hit.id;
|
|
4601
|
+
begin_transaction_editor(state, handle_hit.id === "rotate" ? "rotate" : "resize");
|
|
4602
|
+
if (handle_hit.id === "rotate") {
|
|
4603
|
+
const center = [
|
|
4604
|
+
selected_bounds[0] + selected_bounds[2] / 2,
|
|
4605
|
+
selected_bounds[1] + selected_bounds[3] / 2
|
|
4606
|
+
];
|
|
4607
|
+
state.drag_state = {
|
|
4608
|
+
kind: "rotate",
|
|
4609
|
+
origin_world: [state.pointer_world[0], state.pointer_world[1]],
|
|
4610
|
+
current_world: [state.pointer_world[0], state.pointer_world[1]],
|
|
4611
|
+
base_document: clone_document(state.engine.document),
|
|
4612
|
+
rotated_element_ids: selected_ids,
|
|
4613
|
+
selection_bounds: [
|
|
4614
|
+
selected_bounds[0],
|
|
4615
|
+
selected_bounds[1],
|
|
4616
|
+
selected_bounds[2],
|
|
4617
|
+
selected_bounds[3]
|
|
4618
|
+
],
|
|
4619
|
+
center,
|
|
4620
|
+
start_angle: Math.atan2(state.pointer_world[1] - center[1], state.pointer_world[0] - center[0]),
|
|
4621
|
+
snap_angle: false
|
|
4622
|
+
};
|
|
4623
|
+
} else {
|
|
4624
|
+
state.drag_state = {
|
|
4625
|
+
kind: "resize",
|
|
4626
|
+
origin_world: [state.pointer_world[0], state.pointer_world[1]],
|
|
4627
|
+
current_world: [state.pointer_world[0], state.pointer_world[1]],
|
|
4628
|
+
base_document: clone_document(state.engine.document),
|
|
4629
|
+
resized_element_ids: selected_ids,
|
|
4630
|
+
selection_bounds: [
|
|
4631
|
+
selected_bounds[0],
|
|
4632
|
+
selected_bounds[1],
|
|
4633
|
+
selected_bounds[2],
|
|
4634
|
+
selected_bounds[3]
|
|
4635
|
+
],
|
|
4636
|
+
handle: handle_hit.id,
|
|
4637
|
+
keep_aspect: false,
|
|
4638
|
+
centered: false
|
|
4639
|
+
};
|
|
4640
|
+
}
|
|
4641
|
+
state.pointer_capture = true;
|
|
4642
|
+
return;
|
|
4643
|
+
}
|
|
4644
|
+
const hit = element_hit_at_point_editor(state.engine.document, state.pointer_world[0], state.pointer_world[1]);
|
|
4645
|
+
const is_shift = !!input.shift;
|
|
4646
|
+
if (hit !== null) {
|
|
4647
|
+
const hit_ids = grouped_ids_for_element_editor(state.engine.document, hit.id);
|
|
4648
|
+
if (is_shift) {
|
|
4649
|
+
const all_selected = hit_ids.every((id) => state.selected_element_ids.has(id));
|
|
4650
|
+
for (let i = 0;i < hit_ids.length; i = i + 1) {
|
|
4651
|
+
const id = hit_ids[i];
|
|
4652
|
+
if (id === undefined)
|
|
4653
|
+
continue;
|
|
4654
|
+
if (all_selected)
|
|
4655
|
+
state.selected_element_ids.delete(id);
|
|
4656
|
+
else
|
|
4657
|
+
state.selected_element_ids.add(id);
|
|
4658
|
+
}
|
|
4659
|
+
} else if (!state.selected_element_ids.has(hit.id) || state.selected_element_ids.size > hit_ids.length) {
|
|
4660
|
+
state.selected_element_ids.clear();
|
|
4661
|
+
for (let i = 0;i < hit_ids.length; i = i + 1) {
|
|
4662
|
+
const id = hit_ids[i];
|
|
4663
|
+
if (id !== undefined)
|
|
4664
|
+
state.selected_element_ids.add(id);
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
const selected = expand_ids_with_groups_editor(state.engine.document, Array.from(state.selected_element_ids.values()));
|
|
4668
|
+
if (selected.length > 0) {
|
|
4669
|
+
state.drag_state = {
|
|
4670
|
+
kind: "pending_move",
|
|
4671
|
+
origin_world: [state.pointer_world[0], state.pointer_world[1]],
|
|
4672
|
+
origin_screen: [state.pointer_screen[0], state.pointer_screen[1]],
|
|
4673
|
+
selected_element_ids: selected
|
|
4674
|
+
};
|
|
4675
|
+
state.pointer_capture = true;
|
|
4676
|
+
}
|
|
4677
|
+
return;
|
|
4678
|
+
}
|
|
4679
|
+
if (!is_shift)
|
|
4680
|
+
state.selected_element_ids.clear();
|
|
4681
|
+
state.drag_state = {
|
|
4682
|
+
kind: "pending_marquee",
|
|
4683
|
+
origin_world: [state.pointer_world[0], state.pointer_world[1]],
|
|
4684
|
+
origin_screen: [state.pointer_screen[0], state.pointer_screen[1]],
|
|
4685
|
+
add_mode: is_shift
|
|
4686
|
+
};
|
|
4687
|
+
state.pointer_capture = true;
|
|
4688
|
+
},
|
|
4689
|
+
pointer_move: (state, input) => {
|
|
4690
|
+
if (state.drag_state === null)
|
|
4691
|
+
return update_select_hover_editor(state);
|
|
4692
|
+
if (state.drag_state.kind === "pending_move") {
|
|
4693
|
+
const should_start = exceeds_drag_threshold_editor(state.drag_state.origin_screen, state.pointer_screen);
|
|
4694
|
+
if (!should_start) {
|
|
4695
|
+
return;
|
|
4696
|
+
}
|
|
4697
|
+
begin_transaction_editor(state, "move");
|
|
4698
|
+
start_move_editor(state, state.drag_state.selected_element_ids);
|
|
4699
|
+
}
|
|
4700
|
+
if (state.drag_state.kind === "pending_marquee") {
|
|
4701
|
+
const should_start = exceeds_drag_threshold_editor(state.drag_state.origin_screen, state.pointer_screen);
|
|
4702
|
+
if (!should_start) {
|
|
4703
|
+
return;
|
|
4704
|
+
}
|
|
4705
|
+
const marquee_state = {
|
|
4706
|
+
kind: "marquee",
|
|
4707
|
+
origin_world: [state.drag_state.origin_world[0], state.drag_state.origin_world[1]],
|
|
4708
|
+
current_world: [state.pointer_world[0], state.pointer_world[1]],
|
|
4709
|
+
add_mode: state.drag_state.add_mode
|
|
4710
|
+
};
|
|
4711
|
+
state.drag_state = marquee_state;
|
|
4712
|
+
state.marquee_state = marquee_state;
|
|
4713
|
+
}
|
|
4714
|
+
if (state.drag_state.kind === "move") {
|
|
4715
|
+
const raw = [state.pointer_world[0], state.pointer_world[1]];
|
|
4716
|
+
const dx = raw[0] - state.drag_state.origin_world[0];
|
|
4717
|
+
const dy = raw[1] - state.drag_state.origin_world[1];
|
|
4718
|
+
const b = state.drag_state.selection_bounds;
|
|
4719
|
+
state.drag_state.current_world = snap_point_editor(state, raw, "move", {
|
|
4720
|
+
subject_bounds: [b[0] + dx, b[1] + dy, b[2], b[3]]
|
|
4721
|
+
});
|
|
4722
|
+
return apply_move_drag_editor(state);
|
|
4723
|
+
}
|
|
4724
|
+
if (state.drag_state.kind === "resize") {
|
|
4725
|
+
state.drag_state.current_world = snap_point_editor(state, [state.pointer_world[0], state.pointer_world[1]], "resize");
|
|
4726
|
+
state.drag_state.keep_aspect = !!input.shift;
|
|
4727
|
+
state.drag_state.centered = !!input.alt;
|
|
4728
|
+
return apply_resize_drag_editor(state);
|
|
4729
|
+
}
|
|
4730
|
+
if (state.drag_state.kind === "rotate") {
|
|
4731
|
+
state.drag_state.current_world = [state.pointer_world[0], state.pointer_world[1]];
|
|
4732
|
+
state.drag_state.snap_angle = !!input.shift;
|
|
4733
|
+
return apply_rotate_drag_editor(state);
|
|
4734
|
+
}
|
|
4735
|
+
if (state.drag_state.kind === "marquee") {
|
|
4736
|
+
state.drag_state.current_world = [state.pointer_world[0], state.pointer_world[1]];
|
|
4737
|
+
state.marquee_state = state.drag_state;
|
|
4738
|
+
}
|
|
4739
|
+
},
|
|
4740
|
+
pointer_up: (state) => {
|
|
4741
|
+
if (state.drag_state !== null && (state.drag_state.kind === "pending_move" || state.drag_state.kind === "pending_marquee")) {
|
|
4742
|
+
handle_select_pointer_up_reset_editor(state);
|
|
4743
|
+
return;
|
|
4744
|
+
}
|
|
4745
|
+
if (state.drag_state !== null && state.drag_state.kind !== "marquee")
|
|
4746
|
+
commit_transaction_editor(state);
|
|
4747
|
+
if (state.drag_state !== null && state.drag_state.kind === "marquee") {
|
|
4748
|
+
apply_marquee_selection_editor(state, state.drag_state);
|
|
4749
|
+
}
|
|
4750
|
+
handle_select_pointer_up_reset_editor(state);
|
|
4751
|
+
},
|
|
4752
|
+
double_click: (state) => handle_select_double_click_editor(state),
|
|
4753
|
+
key_down: () => {},
|
|
4754
|
+
cancel: (state) => {
|
|
4755
|
+
if (state.drag_state !== null && (state.drag_state.kind === "move" || state.drag_state.kind === "resize" || state.drag_state.kind === "rotate"))
|
|
4756
|
+
cancel_transaction_editor(state);
|
|
4757
|
+
state.drag_state = null;
|
|
4758
|
+
state.marquee_state = null;
|
|
4759
|
+
state.active_handle = null;
|
|
4760
|
+
state.pointer_capture = false;
|
|
4761
|
+
},
|
|
4762
|
+
hover: (state) => update_select_hover_editor(state),
|
|
4763
|
+
cursor: (state) => cursor_select_editor(state),
|
|
4764
|
+
overlay: () => {}
|
|
4765
|
+
};
|
|
4766
|
+
}
|
|
4767
|
+
|
|
4768
|
+
// editor/tools/shape.ts
|
|
4769
|
+
function create_shape_tool_editor(tool) {
|
|
4770
|
+
return {
|
|
4771
|
+
id: tool,
|
|
4772
|
+
name: shape_name_for_tool_editor(tool),
|
|
4773
|
+
pointer_down: (state) => {
|
|
4774
|
+
begin_transaction_editor(state, shape_name_for_tool_editor(tool));
|
|
4775
|
+
const element_id = next_id_editor(state, "shape");
|
|
4776
|
+
const shape_type = shape_type_for_tool_editor(tool);
|
|
4777
|
+
const start = [state.pointer_world[0], state.pointer_world[1]];
|
|
4778
|
+
const is_box = shape_type === shape_type_rectangle || shape_type === shape_type_ellipse || shape_type === shape_type_frame;
|
|
4779
|
+
const fill = shape_type === shape_type_frame ? null : default_shape_fill;
|
|
4780
|
+
const shape = create_shape_element(element_id, [start[0], start[1], 1, 1], next_z_index_editor(state.engine.document), state.engine.document.active_layer_id, shape_type, fill, default_shape_stroke, 1.5, is_box ? null : start, is_box ? null : start);
|
|
4781
|
+
state.engine.document = add_element_document(state.engine.document, shape);
|
|
4782
|
+
state.selected_element_ids.clear();
|
|
4783
|
+
state.selected_element_ids.add(element_id);
|
|
4784
|
+
state.drag_state = {
|
|
4785
|
+
kind: "shape",
|
|
4786
|
+
origin_world: [start[0], start[1]],
|
|
4787
|
+
current_world: [start[0], start[1]],
|
|
4788
|
+
element_id,
|
|
4789
|
+
shape_tool: tool
|
|
4790
|
+
};
|
|
4791
|
+
state.pointer_capture = true;
|
|
4792
|
+
},
|
|
4793
|
+
pointer_move: (state, input) => {
|
|
4794
|
+
if (state.drag_state === null || state.drag_state.kind !== "shape")
|
|
4795
|
+
return;
|
|
4796
|
+
state.drag_state.current_world = snap_point_editor(state, [state.pointer_world[0], state.pointer_world[1]], "shape");
|
|
4797
|
+
update_shape_preview_editor(state, state.drag_state, !!input.shift);
|
|
4798
|
+
},
|
|
4799
|
+
pointer_up: (state) => {
|
|
4800
|
+
if (state.drag_state?.kind === "shape")
|
|
4801
|
+
commit_transaction_editor(state);
|
|
4802
|
+
state.drag_state = null;
|
|
4803
|
+
state.pointer_capture = false;
|
|
4804
|
+
},
|
|
4805
|
+
double_click: () => {},
|
|
4806
|
+
key_down: () => {},
|
|
4807
|
+
cancel: (state) => {
|
|
4808
|
+
cancel_transaction_editor(state);
|
|
4809
|
+
state.drag_state = null;
|
|
4810
|
+
state.pointer_capture = false;
|
|
4811
|
+
},
|
|
4812
|
+
hover: () => {},
|
|
4813
|
+
cursor: () => "crosshair",
|
|
4814
|
+
overlay: () => {}
|
|
4815
|
+
};
|
|
4816
|
+
}
|
|
4817
|
+
|
|
4818
|
+
// editor/tools/text.ts
|
|
4819
|
+
function create_text_tool_editor() {
|
|
4820
|
+
return {
|
|
4821
|
+
id: editor_tool_text,
|
|
4822
|
+
name: "text",
|
|
4823
|
+
pointer_down: (state) => {
|
|
4824
|
+
begin_transaction_editor(state, "text");
|
|
4825
|
+
const element_id = next_id_editor(state, "text");
|
|
4826
|
+
const x = state.pointer_world[0];
|
|
4827
|
+
const y = state.pointer_world[1];
|
|
4828
|
+
const text = create_text_element(element_id, [x, y, 220, 40], next_z_index_editor(state.engine.document), state.engine.document.active_layer_id, "Text", "sans-serif", 18, default_text_color, 0);
|
|
4829
|
+
state.engine.document = add_element_document(state.engine.document, text);
|
|
4830
|
+
state.selected_element_ids.clear();
|
|
4831
|
+
state.selected_element_ids.add(element_id);
|
|
4832
|
+
state.drag_state = { kind: "text", origin_world: [x, y], current_world: [x, y], element_id };
|
|
4833
|
+
state.pointer_capture = true;
|
|
4834
|
+
},
|
|
4835
|
+
pointer_move: (state) => {
|
|
4836
|
+
if (state.drag_state === null || state.drag_state.kind !== "text")
|
|
4837
|
+
return;
|
|
4838
|
+
state.drag_state.current_world = snap_point_editor(state, [state.pointer_world[0], state.pointer_world[1]], "text");
|
|
4839
|
+
update_box_resize_editor(state, state.drag_state.element_id, state.drag_state.origin_world, state.drag_state.current_world);
|
|
4840
|
+
},
|
|
4841
|
+
pointer_up: (state) => {
|
|
4842
|
+
if (state.drag_state?.kind === "text") {
|
|
4843
|
+
const element_id = state.drag_state.element_id;
|
|
4844
|
+
commit_transaction_editor(state);
|
|
4845
|
+
begin_text_edit_editor(state, element_id);
|
|
4846
|
+
}
|
|
4847
|
+
state.drag_state = null;
|
|
4848
|
+
state.pointer_capture = false;
|
|
4849
|
+
},
|
|
4850
|
+
double_click: () => {},
|
|
4851
|
+
key_down: () => {},
|
|
4852
|
+
cancel: (state) => {
|
|
4853
|
+
cancel_transaction_editor(state);
|
|
4854
|
+
state.drag_state = null;
|
|
4855
|
+
state.pointer_capture = false;
|
|
4856
|
+
},
|
|
4857
|
+
hover: () => {},
|
|
4858
|
+
cursor: () => "text",
|
|
4859
|
+
overlay: () => {}
|
|
4860
|
+
};
|
|
4861
|
+
}
|
|
4862
|
+
|
|
4863
|
+
// editor/registry.ts
|
|
4864
|
+
function create_tool_registry_editor() {
|
|
4865
|
+
const tools = new Map;
|
|
4866
|
+
register_tool_in_map_editor(tools, create_select_tool_editor());
|
|
4867
|
+
register_tool_in_map_editor(tools, create_hand_tool_editor());
|
|
4868
|
+
register_tool_in_map_editor(tools, create_draw_tool_editor());
|
|
4869
|
+
register_tool_in_map_editor(tools, create_shape_tool_editor(editor_tool_rectangle));
|
|
4870
|
+
register_tool_in_map_editor(tools, create_shape_tool_editor(editor_tool_ellipse));
|
|
4871
|
+
register_tool_in_map_editor(tools, create_shape_tool_editor(editor_tool_line));
|
|
4872
|
+
register_tool_in_map_editor(tools, create_shape_tool_editor(editor_tool_arrow));
|
|
4873
|
+
register_tool_in_map_editor(tools, create_shape_tool_editor(editor_tool_frame));
|
|
4874
|
+
register_tool_in_map_editor(tools, create_text_tool_editor());
|
|
4875
|
+
register_tool_in_map_editor(tools, create_image_tool_editor());
|
|
4876
|
+
return tools;
|
|
4877
|
+
}
|
|
4878
|
+
function register_tool_editor(state, tool) {
|
|
4879
|
+
register_tool_in_map_editor(state.tools, tool);
|
|
4880
|
+
}
|
|
4881
|
+
function install_plugin_editor(state, plugin) {
|
|
4882
|
+
const ids = [];
|
|
4883
|
+
for (let i = 0;i < plugin.tools.length; i = i + 1) {
|
|
4884
|
+
const tool = plugin.tools[i];
|
|
4885
|
+
if (tool === undefined)
|
|
4886
|
+
continue;
|
|
4887
|
+
register_tool_in_map_editor(state.tools, tool);
|
|
4888
|
+
ids.push(tool.id);
|
|
4889
|
+
}
|
|
4890
|
+
state.plugin_tools.set(plugin.id, ids);
|
|
4891
|
+
}
|
|
4892
|
+
function uninstall_plugin_editor(state, plugin_id) {
|
|
4893
|
+
const ids = state.plugin_tools.get(plugin_id);
|
|
4894
|
+
if (ids === undefined)
|
|
4895
|
+
return;
|
|
4896
|
+
for (let i = 0;i < ids.length; i = i + 1) {
|
|
4897
|
+
const id = ids[i];
|
|
4898
|
+
if (id !== undefined)
|
|
4899
|
+
state.tools.delete(id);
|
|
4900
|
+
}
|
|
4901
|
+
state.plugin_tools.delete(plugin_id);
|
|
4902
|
+
}
|
|
4903
|
+
function register_tool_in_map_editor(tools, tool) {
|
|
4904
|
+
tools.set(tool.id, tool);
|
|
4905
|
+
}
|
|
4906
|
+
|
|
4907
|
+
// editor/create.ts
|
|
4908
|
+
function create_editor(state, options = {}) {
|
|
4909
|
+
const editor_state = {
|
|
4910
|
+
engine: state,
|
|
4911
|
+
current_tool: options.initial_tool ?? editor_tool_select,
|
|
4912
|
+
previous_tool_before_hand: null,
|
|
4913
|
+
selected_element_ids: new Set,
|
|
4914
|
+
hovered_element_id: null,
|
|
4915
|
+
active_handle: null,
|
|
4916
|
+
pointer_capture: false,
|
|
4917
|
+
drag_state: null,
|
|
4918
|
+
marquee_state: null,
|
|
4919
|
+
clipboard_payload: null,
|
|
4920
|
+
active_transaction: null,
|
|
4921
|
+
undo_stack: [],
|
|
4922
|
+
redo_stack: [],
|
|
4923
|
+
transient_guides: [],
|
|
4924
|
+
pointer_world: [0, 0],
|
|
4925
|
+
pointer_screen: [0, 0],
|
|
4926
|
+
has_pointer_input: false,
|
|
4927
|
+
text_edit: null,
|
|
4928
|
+
snappers: options.snappers ?? [create_element_smart_snapper_editor()],
|
|
4929
|
+
tools: create_tool_registry_editor(),
|
|
4930
|
+
plugin_tools: new Map,
|
|
4931
|
+
id_counter: 0
|
|
4932
|
+
};
|
|
4933
|
+
const plugins = options.plugins ?? [];
|
|
4934
|
+
for (let i = 0;i < plugins.length; i = i + 1) {
|
|
4935
|
+
const plugin = plugins[i];
|
|
4936
|
+
if (plugin !== undefined)
|
|
4937
|
+
install_plugin_editor(editor_state, plugin);
|
|
4938
|
+
}
|
|
4939
|
+
return editor_state;
|
|
4940
|
+
}
|
|
4941
|
+
function render_editor(state) {
|
|
4942
|
+
render_engine_with_overlay(state.engine, (drawer) => {
|
|
4943
|
+
draw_editor_overlay(state, drawer);
|
|
4944
|
+
current_tool_impl_editor(state).overlay(state, drawer);
|
|
4945
|
+
});
|
|
4946
|
+
}
|
|
4947
|
+
function set_tool_editor(state, tool) {
|
|
4948
|
+
if (!state.tools.has(tool) || state.current_tool === tool)
|
|
4949
|
+
return;
|
|
4950
|
+
cancel_editor(state);
|
|
4951
|
+
state.current_tool = tool;
|
|
4952
|
+
}
|
|
4953
|
+
function register_editor_tool_editor(state, tool) {
|
|
4954
|
+
register_tool_editor(state, tool);
|
|
4955
|
+
}
|
|
4956
|
+
function install_editor_plugin_editor(state, plugin) {
|
|
4957
|
+
install_plugin_editor(state, plugin);
|
|
4958
|
+
}
|
|
4959
|
+
function uninstall_editor_plugin_editor(state, plugin_id) {
|
|
4960
|
+
uninstall_plugin_editor(state, plugin_id);
|
|
4961
|
+
}
|
|
4962
|
+
function cursor_editor(state) {
|
|
4963
|
+
return current_tool_impl_editor(state).cursor(state);
|
|
4964
|
+
}
|
|
4965
|
+
function pointer_down_editor(state, input) {
|
|
4966
|
+
update_pointer_editor(state, input);
|
|
4967
|
+
if (is_text_editing_editor(state)) {
|
|
4968
|
+
commit_text_edit_editor(state);
|
|
4969
|
+
}
|
|
4970
|
+
current_tool_impl_editor(state).pointer_down(state, input);
|
|
4971
|
+
}
|
|
4972
|
+
function pointer_move_editor(state, input) {
|
|
4973
|
+
update_pointer_editor(state, input);
|
|
4974
|
+
current_tool_impl_editor(state).pointer_move(state, input);
|
|
4975
|
+
}
|
|
4976
|
+
function pointer_up_editor(state, input) {
|
|
4977
|
+
update_pointer_editor(state, input);
|
|
4978
|
+
current_tool_impl_editor(state).pointer_up(state, input);
|
|
4979
|
+
state.transient_guides = [];
|
|
4980
|
+
}
|
|
4981
|
+
function hover_editor(state, input) {
|
|
4982
|
+
update_pointer_editor(state, input);
|
|
4983
|
+
current_tool_impl_editor(state).hover(state, input);
|
|
4984
|
+
}
|
|
4985
|
+
function double_click_editor(state, input) {
|
|
4986
|
+
update_pointer_editor(state, input);
|
|
4987
|
+
current_tool_impl_editor(state).double_click(state, input);
|
|
4988
|
+
}
|
|
4989
|
+
function key_down_editor(state, input) {
|
|
4990
|
+
if (handle_text_key_editor(state, input))
|
|
4991
|
+
return;
|
|
4992
|
+
if (is_text_editing_editor(state))
|
|
4993
|
+
return;
|
|
4994
|
+
if (!handle_global_key_editor(state, input))
|
|
4995
|
+
current_tool_impl_editor(state).key_down(state, input);
|
|
4996
|
+
}
|
|
4997
|
+
function key_up_editor(state, input) {
|
|
4998
|
+
if (normalize_key_editor(input.key) === " " && state.previous_tool_before_hand !== null) {
|
|
4999
|
+
state.current_tool = state.previous_tool_before_hand;
|
|
5000
|
+
state.previous_tool_before_hand = null;
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
function cancel_editor(state) {
|
|
5004
|
+
if (cancel_text_edit_editor(state))
|
|
5005
|
+
return;
|
|
5006
|
+
current_tool_impl_editor(state).cancel(state);
|
|
5007
|
+
}
|