manga-renderer 1.0.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.
Files changed (148) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +118 -0
  3. package/dist/index.d.ts +28 -0
  4. package/dist/index.js +184 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/book/book.d.ts +133 -0
  7. package/dist/lib/book/book.js +329 -0
  8. package/dist/lib/book/book.js.map +1 -0
  9. package/dist/lib/book/envelope.d.ts +26 -0
  10. package/dist/lib/book/envelope.js +158 -0
  11. package/dist/lib/book/envelope.js.map +1 -0
  12. package/dist/lib/book/imagePacking.d.ts +10 -0
  13. package/dist/lib/book/imagePacking.js +213 -0
  14. package/dist/lib/book/imagePacking.js.map +1 -0
  15. package/dist/lib/book/layout.d.ts +41 -0
  16. package/dist/lib/book/layout.js +14 -0
  17. package/dist/lib/book/layout.js.map +1 -0
  18. package/dist/lib/book/notebook.d.ts +72 -0
  19. package/dist/lib/book/notebook.js +30 -0
  20. package/dist/lib/book/notebook.js.map +1 -0
  21. package/dist/lib/book/richChat.d.ts +36 -0
  22. package/dist/lib/book/richChat.js +87 -0
  23. package/dist/lib/book/richChat.js.map +1 -0
  24. package/dist/lib/book/storyboard.d.ts +125 -0
  25. package/dist/lib/book/storyboard.js +44 -0
  26. package/dist/lib/book/storyboard.js.map +1 -0
  27. package/dist/lib/layeredCanvas/dataModels/bubble.d.ts +96 -0
  28. package/dist/lib/layeredCanvas/dataModels/bubble.js +630 -0
  29. package/dist/lib/layeredCanvas/dataModels/bubble.js.map +1 -0
  30. package/dist/lib/layeredCanvas/dataModels/effect.d.ts +25 -0
  31. package/dist/lib/layeredCanvas/dataModels/effect.js +104 -0
  32. package/dist/lib/layeredCanvas/dataModels/effect.js.map +1 -0
  33. package/dist/lib/layeredCanvas/dataModels/film.d.ts +44 -0
  34. package/dist/lib/layeredCanvas/dataModels/film.js +172 -0
  35. package/dist/lib/layeredCanvas/dataModels/film.js.map +1 -0
  36. package/dist/lib/layeredCanvas/dataModels/frameTree.d.ts +99 -0
  37. package/dist/lib/layeredCanvas/dataModels/frameTree.js +861 -0
  38. package/dist/lib/layeredCanvas/dataModels/frameTree.js.map +1 -0
  39. package/dist/lib/layeredCanvas/dataModels/media.d.ts +39 -0
  40. package/dist/lib/layeredCanvas/dataModels/media.js +69 -0
  41. package/dist/lib/layeredCanvas/dataModels/media.js.map +1 -0
  42. package/dist/lib/layeredCanvas/layers/SampleLayer.d.ts +6 -0
  43. package/dist/lib/layeredCanvas/layers/SampleLayer.js +22 -0
  44. package/dist/lib/layeredCanvas/layers/SampleLayer.js.map +1 -0
  45. package/dist/lib/layeredCanvas/layers/arrayLayer.d.ts +49 -0
  46. package/dist/lib/layeredCanvas/layers/arrayLayer.js +364 -0
  47. package/dist/lib/layeredCanvas/layers/arrayLayer.js.map +1 -0
  48. package/dist/lib/layeredCanvas/layers/bubbleLayer.d.ts +97 -0
  49. package/dist/lib/layeredCanvas/layers/bubbleLayer.js +1418 -0
  50. package/dist/lib/layeredCanvas/layers/bubbleLayer.js.map +1 -0
  51. package/dist/lib/layeredCanvas/layers/floorLayer.d.ts +13 -0
  52. package/dist/lib/layeredCanvas/layers/floorLayer.js +64 -0
  53. package/dist/lib/layeredCanvas/layers/floorLayer.js.map +1 -0
  54. package/dist/lib/layeredCanvas/layers/frameLayer.d.ts +106 -0
  55. package/dist/lib/layeredCanvas/layers/frameLayer.js +1283 -0
  56. package/dist/lib/layeredCanvas/layers/frameLayer.js.map +1 -0
  57. package/dist/lib/layeredCanvas/layers/inlinePainterLayer.d.ts +33 -0
  58. package/dist/lib/layeredCanvas/layers/inlinePainterLayer.js +222 -0
  59. package/dist/lib/layeredCanvas/layers/inlinePainterLayer.js.map +1 -0
  60. package/dist/lib/layeredCanvas/layers/paperRendererLayer.d.ts +64 -0
  61. package/dist/lib/layeredCanvas/layers/paperRendererLayer.js +443 -0
  62. package/dist/lib/layeredCanvas/layers/paperRendererLayer.js.map +1 -0
  63. package/dist/lib/layeredCanvas/layers/undoLayer.d.ts +8 -0
  64. package/dist/lib/layeredCanvas/layers/undoLayer.js +24 -0
  65. package/dist/lib/layeredCanvas/layers/undoLayer.js.map +1 -0
  66. package/dist/lib/layeredCanvas/system/keyCache.d.ts +4 -0
  67. package/dist/lib/layeredCanvas/system/keyCache.js +61 -0
  68. package/dist/lib/layeredCanvas/system/keyCache.js.map +1 -0
  69. package/dist/lib/layeredCanvas/system/layeredCanvas.d.ts +172 -0
  70. package/dist/lib/layeredCanvas/system/layeredCanvas.js +597 -0
  71. package/dist/lib/layeredCanvas/system/layeredCanvas.js.map +1 -0
  72. package/dist/lib/layeredCanvas/system/paperArray.d.ts +29 -0
  73. package/dist/lib/layeredCanvas/system/paperArray.js +108 -0
  74. package/dist/lib/layeredCanvas/system/paperArray.js.map +1 -0
  75. package/dist/lib/layeredCanvas/tools/draw/bubbleGraphic.d.ts +7 -0
  76. package/dist/lib/layeredCanvas/tools/draw/bubbleGraphic.js +728 -0
  77. package/dist/lib/layeredCanvas/tools/draw/bubbleGraphic.js.map +1 -0
  78. package/dist/lib/layeredCanvas/tools/draw/clickableIcon.d.ts +42 -0
  79. package/dist/lib/layeredCanvas/tools/draw/clickableIcon.js +129 -0
  80. package/dist/lib/layeredCanvas/tools/draw/clickableIcon.js.map +1 -0
  81. package/dist/lib/layeredCanvas/tools/draw/drawFilmStack.d.ts +4 -0
  82. package/dist/lib/layeredCanvas/tools/draw/drawFilmStack.js +53 -0
  83. package/dist/lib/layeredCanvas/tools/draw/drawFilmStack.js.map +1 -0
  84. package/dist/lib/layeredCanvas/tools/draw/drawText.d.ts +8 -0
  85. package/dist/lib/layeredCanvas/tools/draw/drawText.js +45 -0
  86. package/dist/lib/layeredCanvas/tools/draw/drawText.js.map +1 -0
  87. package/dist/lib/layeredCanvas/tools/draw/richText.d.ts +15 -0
  88. package/dist/lib/layeredCanvas/tools/draw/richText.js +172 -0
  89. package/dist/lib/layeredCanvas/tools/draw/richText.js.map +1 -0
  90. package/dist/lib/layeredCanvas/tools/draw/selectionFrame.d.ts +5 -0
  91. package/dist/lib/layeredCanvas/tools/draw/selectionFrame.js +63 -0
  92. package/dist/lib/layeredCanvas/tools/draw/selectionFrame.js.map +1 -0
  93. package/dist/lib/layeredCanvas/tools/draw/typeSetting.d.ts +16 -0
  94. package/dist/lib/layeredCanvas/tools/draw/typeSetting.js +2 -0
  95. package/dist/lib/layeredCanvas/tools/draw/typeSetting.js.map +1 -0
  96. package/dist/lib/layeredCanvas/tools/draw/verticalText.d.ts +3 -0
  97. package/dist/lib/layeredCanvas/tools/draw/verticalText.js +230 -0
  98. package/dist/lib/layeredCanvas/tools/draw/verticalText.js.map +1 -0
  99. package/dist/lib/layeredCanvas/tools/focusKeeper.d.ts +8 -0
  100. package/dist/lib/layeredCanvas/tools/focusKeeper.js +15 -0
  101. package/dist/lib/layeredCanvas/tools/focusKeeper.js.map +1 -0
  102. package/dist/lib/layeredCanvas/tools/frameExamples.d.ts +382 -0
  103. package/dist/lib/layeredCanvas/tools/frameExamples.js +758 -0
  104. package/dist/lib/layeredCanvas/tools/frameExamples.js.map +1 -0
  105. package/dist/lib/layeredCanvas/tools/geometry/bubbleGeometry.d.ts +12 -0
  106. package/dist/lib/layeredCanvas/tools/geometry/bubbleGeometry.js +117 -0
  107. package/dist/lib/layeredCanvas/tools/geometry/bubbleGeometry.js.map +1 -0
  108. package/dist/lib/layeredCanvas/tools/geometry/convertPoint.d.ts +2 -0
  109. package/dist/lib/layeredCanvas/tools/geometry/convertPoint.js +25 -0
  110. package/dist/lib/layeredCanvas/tools/geometry/convertPoint.js.map +1 -0
  111. package/dist/lib/layeredCanvas/tools/geometry/geometry.d.ts +69 -0
  112. package/dist/lib/layeredCanvas/tools/geometry/geometry.js +408 -0
  113. package/dist/lib/layeredCanvas/tools/geometry/geometry.js.map +1 -0
  114. package/dist/lib/layeredCanvas/tools/geometry/geometry.vitest.d.ts +1 -0
  115. package/dist/lib/layeredCanvas/tools/geometry/geometry.vitest.js +58 -0
  116. package/dist/lib/layeredCanvas/tools/geometry/geometry.vitest.js.map +1 -0
  117. package/dist/lib/layeredCanvas/tools/geometry/quickHull.d.ts +10 -0
  118. package/dist/lib/layeredCanvas/tools/geometry/quickHull.js +91 -0
  119. package/dist/lib/layeredCanvas/tools/geometry/quickHull.js.map +1 -0
  120. package/dist/lib/layeredCanvas/tools/geometry/trapezoid.d.ts +18 -0
  121. package/dist/lib/layeredCanvas/tools/geometry/trapezoid.js +105 -0
  122. package/dist/lib/layeredCanvas/tools/geometry/trapezoid.js.map +1 -0
  123. package/dist/lib/layeredCanvas/tools/grid.d.ts +7 -0
  124. package/dist/lib/layeredCanvas/tools/grid.js +16 -0
  125. package/dist/lib/layeredCanvas/tools/grid.js.map +1 -0
  126. package/dist/lib/layeredCanvas/tools/haiku.d.ts +1 -0
  127. package/dist/lib/layeredCanvas/tools/haiku.js +17 -0
  128. package/dist/lib/layeredCanvas/tools/haiku.js.map +1 -0
  129. package/dist/lib/layeredCanvas/tools/imageUtil.d.ts +8 -0
  130. package/dist/lib/layeredCanvas/tools/imageUtil.js +58 -0
  131. package/dist/lib/layeredCanvas/tools/imageUtil.js.map +1 -0
  132. package/dist/lib/layeredCanvas/tools/kinsoku.d.ts +26 -0
  133. package/dist/lib/layeredCanvas/tools/kinsoku.js +124 -0
  134. package/dist/lib/layeredCanvas/tools/kinsoku.js.map +1 -0
  135. package/dist/lib/layeredCanvas/tools/misc.d.ts +25 -0
  136. package/dist/lib/layeredCanvas/tools/misc.js +46 -0
  137. package/dist/lib/layeredCanvas/tools/misc.js.map +1 -0
  138. package/dist/lib/layeredCanvas/tools/perfectFreehandUtil.d.ts +2 -0
  139. package/dist/lib/layeredCanvas/tools/perfectFreehandUtil.js +21 -0
  140. package/dist/lib/layeredCanvas/tools/perfectFreehandUtil.js.map +1 -0
  141. package/dist/lib/layeredCanvas/tools/pictureControl.d.ts +4 -0
  142. package/dist/lib/layeredCanvas/tools/pictureControl.js +24 -0
  143. package/dist/lib/layeredCanvas/tools/pictureControl.js.map +1 -0
  144. package/dist/lib/layeredCanvas/tools/rectHandle.d.ts +6 -0
  145. package/dist/lib/layeredCanvas/tools/rectHandle.js +17 -0
  146. package/dist/lib/layeredCanvas/tools/rectHandle.js.map +1 -0
  147. package/dist/tsconfig.tsbuildinfo +1 -0
  148. package/package.json +64 -0
@@ -0,0 +1,1418 @@
1
+ import { LayerBase, sequentializePointer } from "../system/layeredCanvas";
2
+ import { keyDownFlags } from "../system/keyCache";
3
+ import { ClickableIcon } from "../tools/draw/clickableIcon";
4
+ import { Bubble, bubbleOptionSets } from "../dataModels/bubble";
5
+ import { tailCoordToWorldCoord, worldCoordToTailCoord } from "../tools/geometry/bubbleGeometry";
6
+ import { translate, scale } from "../tools/pictureControl";
7
+ import { add2D, scale2D, computeConstraintedRect, scaleRect, minimumBoundingScale, getRectCenter } from "../tools/geometry/geometry";
8
+ import { getHaiku } from '../tools/haiku';
9
+ import { ulid } from 'ulid';
10
+ import { rectToTrapezoid } from "../tools/geometry/trapezoid";
11
+ import { Film, FilmStackTransformer, calculateMinimumBoundingRect } from "../dataModels/film";
12
+ import { ImageMedia } from "../dataModels/media";
13
+ import { drawFilmStackBorders } from "../tools/draw/drawFilmStack";
14
+ import { createCanvasFromBlob, makePlainImage } from "../tools/imageUtil";
15
+ import { drawSelectionFrame, calculateSheetRect, drawSheet } from "../tools/draw/selectionFrame";
16
+ import { Grid } from "../tools/grid";
17
+ const iconUnit = [26, 26];
18
+ const SHEET_Y_MARGIN = 32;
19
+ export class DefaultBubbleSlot {
20
+ bubble;
21
+ constructor(bubble) {
22
+ this.bubble = bubble;
23
+ }
24
+ }
25
+ export class BubbleLayer extends LayerBase {
26
+ viewport;
27
+ focusKeeper;
28
+ renderLayer;
29
+ bubbles;
30
+ fold = 0;
31
+ onFocus;
32
+ onCommit;
33
+ onRevert;
34
+ onPotentialCrossPage;
35
+ defaultBubbleSlot;
36
+ creatingBubble;
37
+ optionEditActive;
38
+ selected;
39
+ lit;
40
+ handle;
41
+ createBubbleIcon;
42
+ dragIcon;
43
+ offsetIcon;
44
+ zPlusIcon;
45
+ zMinusIcon;
46
+ removeIcon;
47
+ rotateIcon;
48
+ imageScaleLockIcon;
49
+ scaleIcon;
50
+ optionIcons;
51
+ constructor(viewport, focusKeeper, renderLayer, defaultBubbleSlot, bubbles, fold, onFocus, onCommit, onRevert, onPotentialCrossPage) {
52
+ super();
53
+ this.viewport = viewport;
54
+ this.focusKeeper = focusKeeper;
55
+ this.renderLayer = renderLayer;
56
+ this.bubbles = bubbles;
57
+ this.onFocus = onFocus;
58
+ this.onCommit = onCommit;
59
+ this.onRevert = onRevert;
60
+ this.onPotentialCrossPage = onPotentialCrossPage;
61
+ this.defaultBubbleSlot = defaultBubbleSlot;
62
+ this.creatingBubble = null;
63
+ this.optionEditActive = {};
64
+ this.selected = null;
65
+ this.lit = null;
66
+ this.handle = null;
67
+ const unit = iconUnit;
68
+ const mp = () => this.paper.matrix;
69
+ this.createBubbleIcon = new ClickableIcon(["bubbleLayer/bubble.png"], [64, 64], [0, 1], "ドラッグで作成", () => this.interactable, mp);
70
+ this.createBubbleIcon.position = [0, -16];
71
+ this.dragIcon = new ClickableIcon(["bubbleLayer/drag.png"], unit, [0.5, 0], "ドラッグで移動", () => this.interactable && this.selected != null, mp);
72
+ this.offsetIcon = new ClickableIcon(["bubbleLayer/bubble-offset.png"], unit, [0.5, 0], "ドラッグで位置調整", () => this.interactable && this.selected != null, mp);
73
+ this.zPlusIcon = new ClickableIcon(["bubbleLayer/bubble-zplus.png"], unit, [0, 0], "フキダシ順で手前", () => this.interactable && this.selected != null, mp);
74
+ this.zMinusIcon = new ClickableIcon(["bubbleLayer/bubble-zminus.png"], unit, [0, 0], "フキダシ順で奥", () => this.interactable && this.selected != null, mp);
75
+ this.removeIcon = new ClickableIcon(["bubbleLayer/remove.png"], unit, [1, 0], "削除", () => this.interactable && this.selected != null, mp);
76
+ this.rotateIcon = new ClickableIcon(["bubbleLayer/bubble-rotate.png"], unit, [0.5, 1], "左右ドラッグで回転", () => this.interactable && this.selected != null, mp);
77
+ this.imageScaleLockIcon = new ClickableIcon(["bubbleLayer/bubble-unlock.png", "bubbleLayer/bubble-lock.png"], unit, [1, 1], "スケール同期", () => this.interactable && 0 < (this.selected?.filmStack.films.length ?? 0), mp);
78
+ this.imageScaleLockIcon.index = 0;
79
+ this.scaleIcon = new ClickableIcon(["bubbleLayer/bubble-scale.png"], unit, [1, 1], "ドラッグでスケール", () => this.interactable && this.selected != null && 0 < this.selected?.filmStack.films.length, mp);
80
+ this.optionIcons = {};
81
+ this.optionIcons.tail = new ClickableIcon(["bubbleLayer/tail-tip.png"], unit, [0.5, 0.5], "ドラッグでしっぽ", () => this.interactable && this.selected != null, mp);
82
+ this.optionIcons.curve = new ClickableIcon(["bubbleLayer/tail-mid.png"], unit, [0.5, 0.5], "ドラッグでしっぽのカーブ", () => this.interactable && this.selected != null, mp);
83
+ this.optionIcons.unite = new ClickableIcon(["bubbleLayer/unite.png"], unit, [0.5, 1], "ドラッグで他のフキダシと結合", () => this.interactable && this.selected != null, mp);
84
+ this.optionIcons.circle = new ClickableIcon(["bubbleLayer/circle.png"], unit, [0.5, 0.5], "ドラッグで円定義", () => this.interactable && this.selected != null, mp);
85
+ this.optionIcons.radius = new ClickableIcon(["bubbleLayer/radius.png"], unit, [0.5, 0.5], "ドラッグで円半径", () => this.interactable && this.selected != null, mp);
86
+ for (let icon of [this.dragIcon, this.offsetIcon, this.zPlusIcon, this.zMinusIcon, this.removeIcon, this.rotateIcon, this.imageScaleLockIcon, this.scaleIcon]) {
87
+ if (icon instanceof ClickableIcon) {
88
+ icon.shadowColor = "#242";
89
+ }
90
+ }
91
+ for (let icon of [this.optionIcons.tail, this.optionIcons.curve, this.optionIcons.unite, this.optionIcons.circle, this.optionIcons.radius]) {
92
+ if (icon instanceof ClickableIcon) {
93
+ icon.shadowColor = "#842";
94
+ }
95
+ }
96
+ this.setFold(fold);
97
+ focusKeeper.subscribe(this.changeFocus.bind(this));
98
+ }
99
+ rebuildPageLayouts(matrix) {
100
+ if (this.selected != null) {
101
+ this.relayoutIcons();
102
+ }
103
+ }
104
+ prerender() {
105
+ const bubbles = [...this.bubbles];
106
+ if (this.creatingBubble) {
107
+ bubbles.push(this.creatingBubble);
108
+ }
109
+ this.renderLayer.setBubbles(bubbles);
110
+ }
111
+ render(ctx, depth) {
112
+ if (depth !== 1) {
113
+ return;
114
+ }
115
+ this.createBubbleIcon.render(ctx);
116
+ if (this.interactable && this.lit) {
117
+ this.drawLitUI(ctx, this.lit);
118
+ }
119
+ if (this.interactable && this.selected) {
120
+ try {
121
+ this.drawSheet(ctx, this.selected.getPhysicalRegularizedRect(this.getPaperSize()));
122
+ this.drawSelectedUI(ctx, this.selected);
123
+ this.drawOptionHandles(ctx, this.selected);
124
+ this.drawOptionUI(ctx, this.selected);
125
+ this.dragIcon.render(ctx);
126
+ this.offsetIcon.render(ctx);
127
+ this.zPlusIcon.render(ctx);
128
+ this.zMinusIcon.render(ctx);
129
+ this.removeIcon.render(ctx);
130
+ this.rotateIcon.render(ctx);
131
+ this.imageScaleLockIcon.render(ctx);
132
+ this.scaleIcon.render(ctx);
133
+ }
134
+ catch (e) {
135
+ console.log(e, this.optionEditActive, this.selected, this.selected?.optionContext);
136
+ throw e;
137
+ }
138
+ }
139
+ if (this.interactable && this.creatingBubble) {
140
+ this.drawSelectedUI(ctx, this.creatingBubble);
141
+ }
142
+ }
143
+ calculateSheetRect(rect) {
144
+ return calculateSheetRect(rect, [160, 160], SHEET_Y_MARGIN, 1 / this.paper.matrix.a);
145
+ }
146
+ drawSheet(ctx, rect) {
147
+ const corners = rectToTrapezoid(rect);
148
+ drawSheet(ctx, corners, this.calculateSheetRect(rect), "rgba(64, 128, 64, 0.7)");
149
+ }
150
+ drawLitUI(ctx, bubble) {
151
+ const [x, y, w, h] = bubble.getPhysicalRegularizedRect(this.getPaperSize());
152
+ // 選択枠描画
153
+ ctx.save();
154
+ ctx.lineWidth = 3;
155
+ ctx.strokeStyle = "rgba(255, 0, 255, 0.7)";
156
+ ctx.strokeRect(x, y, w, h);
157
+ ctx.restore();
158
+ }
159
+ drawSelectedUI(ctx, bubble) {
160
+ const paperSize = this.getPaperSize();
161
+ const [x, y, w, h] = bubble.getPhysicalRegularizedRect(paperSize);
162
+ // 選択枠描画
163
+ const corners = {
164
+ topLeft: [x, y],
165
+ topRight: [x + w, y],
166
+ bottomLeft: [x, y + h],
167
+ bottomRight: [x + w, y + h],
168
+ };
169
+ drawSelectionFrame(ctx, "rgba(0, 192, 0, 1)", corners, 3, 5);
170
+ // フィルムの枠
171
+ ctx.save();
172
+ ctx.translate(x + w * 0.5, y + h * 0.5);
173
+ ctx.rotate((-bubble.rotation * Math.PI) / 180);
174
+ drawFilmStackBorders(ctx, bubble.filmStack, paperSize);
175
+ ctx.restore();
176
+ // 操作対象のハンドルを強調表示
177
+ if (this.handle != null) {
178
+ ctx.save();
179
+ ctx.fillStyle = "rgba(0, 255, 255, 0.7)";
180
+ const handleRect = bubble.getHandleRect(paperSize, this.handle);
181
+ if (handleRect) {
182
+ ctx.fillRect(...handleRect);
183
+ }
184
+ ctx.restore();
185
+ }
186
+ }
187
+ drawOptionHandles(ctx, bubble) {
188
+ const paperSize = this.getPaperSize();
189
+ const bubbleCenter = bubble.getPhysicalCenter(paperSize);
190
+ const [x, y, w, h] = bubble.getPhysicalRegularizedRect(paperSize);
191
+ const rect = [x + 10, y + 10, w - 20, h - 20];
192
+ const cp = (ro, ou) => ClickableIcon.calcPosition(rect, iconUnit, ro, ou);
193
+ const rp = (p) => [bubbleCenter[0] + p[0], bubbleCenter[1] + p[1]];
194
+ const tailMidCoord = () => tailCoordToWorldCoord(bubbleCenter, bubble.optionContext.tailTip, bubble.optionContext.tailMid);
195
+ const optionSet = bubble.optionSet;
196
+ for (const option of Object.keys(optionSet)) {
197
+ let icon;
198
+ switch (option) {
199
+ case "tailTip":
200
+ icon = this.optionIcons[optionSet.tailTip.icon];
201
+ icon.position = rp(bubble.optionContext.tailTip);
202
+ icon.render(ctx);
203
+ break;
204
+ case "tailMid":
205
+ (() => {
206
+ icon = this.optionIcons[optionSet.tailMid.icon];
207
+ icon.position = tailMidCoord();
208
+ icon.render(ctx);
209
+ })();
210
+ break;
211
+ case "link":
212
+ icon = this.optionIcons[optionSet.link.icon];
213
+ icon.position = cp([0.5, 1], [0, 0]);
214
+ icon.render(ctx);
215
+ break;
216
+ case "focalPoint":
217
+ icon = this.optionIcons[optionSet.focalPoint.icon];
218
+ icon.position = rp(bubble.optionContext.focalPoint);
219
+ icon.render(ctx);
220
+ break;
221
+ case "focalRange":
222
+ (() => {
223
+ icon = this.optionIcons[optionSet.focalRange.icon];
224
+ const [px, py] = rp(bubble.optionContext.focalPoint);
225
+ const [rx, ry] = bubble.optionContext.focalRange;
226
+ icon.position = [px + rx, py + ry];
227
+ icon.render(ctx);
228
+ })();
229
+ break;
230
+ }
231
+ }
232
+ }
233
+ drawOptionUI(ctx, bubble) {
234
+ const paperSize = this.getPaperSize();
235
+ const bubbleCenter = bubble.getPhysicalCenter(paperSize);
236
+ if (this.optionEditActive.tail) {
237
+ const [cx, cy] = bubbleCenter;
238
+ ctx.lineWidth = 3;
239
+ ctx.strokeStyle = "rgba(0, 0, 255, 0.3)";
240
+ ctx.beginPath();
241
+ ctx.moveTo(cx, cy);
242
+ ctx.lineTo(cx + bubble.optionContext.tailTip[0], cy + bubble.optionContext.tailTip[1]);
243
+ ctx.stroke();
244
+ }
245
+ if (this.optionEditActive.focal) {
246
+ const [cx, cy] = bubbleCenter;
247
+ ctx.lineWidth = 3;
248
+ ctx.strokeStyle = "rgba(0, 0, 255, 0.3)";
249
+ ctx.beginPath();
250
+ const fp = bubble.optionContext.focalPoint;
251
+ const [px, py] = [cx + fp[0], cy + fp[1]];
252
+ ctx.moveTo(px, py);
253
+ ctx.lineTo(px + bubble.optionContext.focalRange[0], py + bubble.optionContext.focalRange[1]);
254
+ ctx.stroke();
255
+ }
256
+ for (let b of this.bubbles) {
257
+ if (b === bubble) {
258
+ continue;
259
+ }
260
+ const c = b.getPhysicalCenter(paperSize);
261
+ if (this.getGroupMaster(b) === this.getGroupMaster(bubble)) {
262
+ ctx.strokeStyle = "rgba(255, 0, 255, 0.3)";
263
+ ctx.lineWidth = 3;
264
+ ctx.beginPath();
265
+ ctx.moveTo(...c);
266
+ ctx.lineTo(...bubbleCenter);
267
+ ctx.stroke();
268
+ }
269
+ }
270
+ if (this.optionEditActive.link) {
271
+ ctx.strokeStyle = "rgba(255, 0, 255, 0.3)";
272
+ for (let b of this.bubbles) {
273
+ if (b === bubble) {
274
+ continue;
275
+ }
276
+ if (!b.optionSet.link) {
277
+ continue;
278
+ }
279
+ if (this.getGroupMaster(b) === this.getGroupMaster(bubble)) {
280
+ continue;
281
+ }
282
+ ctx.lineWidth = 5;
283
+ const rect = b.getPhysicalRegularizedRect(paperSize);
284
+ ctx.strokeRect(...rect);
285
+ }
286
+ const [cx, cy] = this.optionIcons[bubble.optionSet.link.icon].center;
287
+ ctx.lineWidth = 3;
288
+ ctx.strokeStyle = "rgba(0, 0, 255, 0.3)";
289
+ ctx.beginPath();
290
+ ctx.moveTo(cx, cy);
291
+ ctx.lineTo(cx + bubble.optionContext.link[0], cy + bubble.optionContext.link[1]);
292
+ ctx.stroke();
293
+ }
294
+ }
295
+ pointerHover(p) {
296
+ if (!p) {
297
+ return false;
298
+ }
299
+ if (keyDownFlags["Space"]) {
300
+ return false;
301
+ }
302
+ this.lit = null;
303
+ if (this.createBubbleIcon.hintIfContains(p, this.hint)) {
304
+ return true;
305
+ }
306
+ const paperSize = this.getPaperSize();
307
+ if (this.selected) {
308
+ this.handle = this.selected.getHandleAt(paperSize, p);
309
+ if (this.removeIcon.hintIfContains(p, this.hint) ||
310
+ this.rotateIcon.hintIfContains(p, this.hint) ||
311
+ this.dragIcon.hintIfContains(p, this.hint) ||
312
+ this.offsetIcon.hintIfContains(p, this.hint) ||
313
+ this.zMinusIcon.hintIfContains(p, this.hint) ||
314
+ this.zPlusIcon.hintIfContains(p, this.hint) ||
315
+ this.imageScaleLockIcon.hintIfContains(p, this.hint) ||
316
+ this.scaleIcon.hintIfContains(p, this.hint) ||
317
+ this.hintOptionIcon(this.selected.shape, p)) {
318
+ this.handle = null;
319
+ }
320
+ else if (this.selected.contains(paperSize, p)) {
321
+ this.hint(null, null);
322
+ }
323
+ if (this.handle || this.selected.contains(paperSize, p)) {
324
+ this.redraw();
325
+ return true;
326
+ }
327
+ }
328
+ for (let bubble of this.bubbles) {
329
+ if (bubble.contains(paperSize, p)) {
330
+ const r = bubble.getPhysicalRegularizedRect(paperSize);
331
+ this.hint(r, "Alt+ドラッグで移動、クリックで選択");
332
+ this.lit = bubble;
333
+ this.redraw();
334
+ return true;
335
+ }
336
+ }
337
+ return false;
338
+ }
339
+ async keyDown(position, event) {
340
+ if (!this.interactable) {
341
+ return false;
342
+ }
343
+ if (event.code === "KeyX" && event.ctrlKey) {
344
+ if (!this.selected) {
345
+ return false;
346
+ }
347
+ console.log("cut");
348
+ this.copyBubble();
349
+ this.removeBubble(this.selected);
350
+ return true;
351
+ }
352
+ if (event.code === "KeyC" && event.ctrlKey) {
353
+ if (!this.selected) {
354
+ return false;
355
+ }
356
+ console.log("copy");
357
+ this.copyBubble();
358
+ return true;
359
+ }
360
+ if (event.code === "KeyV" && event.ctrlKey) {
361
+ console.log("paste");
362
+ return await this.pasteBubble(position);
363
+ }
364
+ // カーソルキー
365
+ if (this.selected) {
366
+ if (event.code === 'ArrowLeft') {
367
+ if (this.selected.optionSet.randomSeed) {
368
+ this.selected.optionContext.randomSeed -= 1;
369
+ if (this.selected.optionContext.randomSeed < 0) {
370
+ this.selected.optionContext.randomSeed = 100;
371
+ }
372
+ this.onCommit(true);
373
+ this.redraw();
374
+ return true;
375
+ }
376
+ }
377
+ if (event.code === 'ArrowRight') {
378
+ if (this.selected.optionSet.randomSeed) {
379
+ this.selected.optionContext.randomSeed += 1;
380
+ if (100 < this.selected.optionContext.randomSeed) {
381
+ this.selected.optionContext.randomSeed = 0;
382
+ }
383
+ this.onCommit(true);
384
+ this.redraw();
385
+ return true;
386
+ }
387
+ }
388
+ }
389
+ return false;
390
+ }
391
+ copyBubble() {
392
+ if (this.selected) {
393
+ const t = Bubble.decompile(this.selected);
394
+ navigator.clipboard.writeText(JSON.stringify(t, null, 2)).then(() => {
395
+ console.log("copied");
396
+ });
397
+ }
398
+ }
399
+ async pasteBubble(position) {
400
+ console.log("pasteBubble");
401
+ try {
402
+ const items = await navigator.clipboard.read();
403
+ for (let item of items) {
404
+ for (let type of item.types) {
405
+ if (type === "text/plain") {
406
+ const blob = await item.getType(type);
407
+ const text = await blob.text();
408
+ this.createTextBubble(text);
409
+ return true;
410
+ }
411
+ else if (type.startsWith("image/")) {
412
+ const paperSize = this.getPaperSize();
413
+ for (let bubble of this.bubbles) {
414
+ if (bubble.contains(paperSize, position)) {
415
+ const canvas = await createCanvasFromBlob(await item.getType(type));
416
+ this.receiveImage(bubble, canvas);
417
+ return true;
418
+ }
419
+ }
420
+ return false;
421
+ return true;
422
+ }
423
+ }
424
+ }
425
+ }
426
+ catch (err) {
427
+ console.error('ユーザが拒否、もしくはなんらかの理由で失敗', err);
428
+ }
429
+ return false;
430
+ }
431
+ createTextBubble(text) {
432
+ const paperSize = this.getPaperSize();
433
+ try {
434
+ const b = Bubble.compile(paperSize, JSON.parse(text));
435
+ const bubbleSize = b.getPhysicalSize(paperSize);
436
+ b.parent = null;
437
+ b.uuid = ulid();
438
+ const x = Math.random() * (paperSize[0] - bubbleSize[0]);
439
+ const y = Math.random() * (paperSize[1] - bubbleSize[1]);
440
+ b.setPhysicalRect(paperSize, [x, y, ...bubbleSize]);
441
+ this.bubbles.push(b);
442
+ this.selectBubble(b);
443
+ return [b];
444
+ }
445
+ catch (e) {
446
+ let cursorX = 10;
447
+ let cursorY = 10;
448
+ let lineHeight = 0;
449
+ let lastBubble = null;
450
+ const bubbles = [];
451
+ for (let s of text.split(/\n\s*\n/)) {
452
+ const b = this.defaultBubbleSlot.bubble.clone(false);
453
+ b.parent = null;
454
+ b.text = s;
455
+ b.filmStack.films = [];
456
+ const size = b.calculateFitSize(paperSize);
457
+ if (cursorX + size[0] > paperSize[0]) {
458
+ cursorX = 10;
459
+ cursorY += lineHeight + 10;
460
+ lineHeight = 0;
461
+ }
462
+ const x = cursorX;
463
+ const y = cursorY;
464
+ b.setPhysicalRect(paperSize, [x, y, ...size]);
465
+ b.text = s;
466
+ b.initOptions();
467
+ bubbles.push(b);
468
+ this.bubbles.push(b);
469
+ cursorX += size[0] + 10;
470
+ lineHeight = Math.max(lineHeight, size[1]);
471
+ lastBubble = b;
472
+ }
473
+ this.selectBubble(lastBubble);
474
+ return bubbles;
475
+ }
476
+ }
477
+ createImageBubble(canvas) {
478
+ const bubble = new Bubble();
479
+ const paperSize = this.getPaperSize();
480
+ const imageSize = [canvas.width, canvas.height];
481
+ const x = Math.random() * (paperSize[0] - imageSize[0]);
482
+ const y = Math.random() * (paperSize[1] - imageSize[1]);
483
+ bubble.setPhysicalRect(paperSize, [x, y, ...imageSize]);
484
+ bubble.forceEnoughSize(paperSize);
485
+ bubble.shape = "none";
486
+ bubble.initOptions();
487
+ bubble.text = "";
488
+ const minImageDimension = Math.min(...imageSize);
489
+ const minPageDimension = Math.min(paperSize[0], paperSize[1]);
490
+ const n_scale = 1 / (minPageDimension / minImageDimension);
491
+ const film = this.newImageFilm(canvas);
492
+ film.n_scale = n_scale;
493
+ bubble.filmStack.films.push(film);
494
+ this.bubbles.push(bubble);
495
+ this.onCommit();
496
+ this.selectBubble(bubble);
497
+ }
498
+ removeBubble(bubble) {
499
+ const index = this.bubbles.indexOf(bubble);
500
+ this.bubbles.splice(index, 1);
501
+ for (let b of this.bubbles) {
502
+ if (b.parent === bubble.uuid) {
503
+ b.parent = null;
504
+ }
505
+ }
506
+ this.unfocus();
507
+ this.onCommit();
508
+ }
509
+ hintOptionIcon(shape, p) {
510
+ const optionSet = bubbleOptionSets[shape];
511
+ for (const option of Object.keys(optionSet)) {
512
+ const icon = this.optionIcons[optionSet[option].icon];
513
+ if (!icon) {
514
+ continue;
515
+ }
516
+ if (icon.hintIfContains(p, this.hint)) {
517
+ return true;
518
+ }
519
+ }
520
+ return false;
521
+ }
522
+ isOnOptionIcon(shape, p) {
523
+ const optionSet = bubbleOptionSets[shape];
524
+ for (const option of Object.keys(optionSet)) {
525
+ const icon = this.optionIcons[optionSet[option].icon];
526
+ if (icon.contains(p)) {
527
+ return true;
528
+ }
529
+ }
530
+ return false;
531
+ }
532
+ pick(point) {
533
+ const paperSize = this.getPaperSize();
534
+ const picked = [];
535
+ for (let bubble of this.bubbles.toReversed()) {
536
+ if (bubble.contains(paperSize, point)) {
537
+ picked.push({
538
+ selected: bubble === this.selected,
539
+ action: () => { this.selectBubble(bubble); }
540
+ });
541
+ }
542
+ }
543
+ return picked;
544
+ }
545
+ acceptDepths() {
546
+ return [0, 1];
547
+ }
548
+ accepts(point, _button, depth) {
549
+ console.log("bubble accepts", depth);
550
+ if (!this.interactable) {
551
+ return null;
552
+ }
553
+ if (keyDownFlags["Space"]) {
554
+ return null;
555
+ }
556
+ if (keyDownFlags["KeyF"]) {
557
+ return { action: "create" };
558
+ }
559
+ if (keyDownFlags["KeyG"]) {
560
+ return { action: "create-surface" };
561
+ }
562
+ if (this.createBubbleIcon.contains(point)) {
563
+ return { action: "create" };
564
+ }
565
+ if (depth == 1) {
566
+ return this.acceptsForeground(point, _button);
567
+ }
568
+ else {
569
+ return this.acceptsBackground(point, _button);
570
+ }
571
+ }
572
+ acceptsForeground(point, _button) {
573
+ const paperSize = this.getPaperSize();
574
+ if (this.selected) {
575
+ const bubble = this.selected;
576
+ if (this.removeIcon.contains(point)) {
577
+ return { action: "remove", bubble };
578
+ }
579
+ else if (this.rotateIcon.contains(point)) {
580
+ return { action: "rotate", bubble };
581
+ }
582
+ else if (this.dragIcon.contains(point)) {
583
+ return { action: "move", bubble };
584
+ }
585
+ else if (this.offsetIcon.contains(point)) {
586
+ return { action: "offset", bubble };
587
+ }
588
+ else if (this.zMinusIcon.contains(point)) {
589
+ return { action: "z-minus", bubble };
590
+ }
591
+ else if (this.zPlusIcon.contains(point)) {
592
+ return { action: "z-plus", bubble };
593
+ }
594
+ else if (this.imageScaleLockIcon.contains(point)) {
595
+ return { action: "image-scale-lock", bubble };
596
+ }
597
+ else if (this.scaleIcon.contains(point)) {
598
+ return { action: "image-scale", bubble };
599
+ }
600
+ else {
601
+ const icon = this.getOptionIconAt(bubble.shape, point);
602
+ if (icon) {
603
+ return { action: "options-" + icon, bubble };
604
+ }
605
+ }
606
+ const handle = bubble.getHandleAt(paperSize, point);
607
+ if (handle) {
608
+ return { action: "resize", bubble, handle };
609
+ }
610
+ const gm = this.getGroupMaster(bubble);
611
+ if (0 < gm.filmStack.films.length && !gm.scaleLock && bubble.contains(paperSize, point)) {
612
+ if (keyDownFlags["ControlLeft"] || keyDownFlags["ControlRight"]) {
613
+ return { action: "image-scale", bubble: gm };
614
+ }
615
+ else if (!keyDownFlags["AltLeft"] && !keyDownFlags["AltRight"]) {
616
+ return { action: "image-move", bubble: gm };
617
+ }
618
+ }
619
+ // 貫通
620
+ if (bubble.contains(paperSize, point)) {
621
+ this.pierce();
622
+ return { action: "pierce" };
623
+ }
624
+ }
625
+ return null;
626
+ }
627
+ acceptsBackground(point, _button) {
628
+ const paperSize = this.getPaperSize();
629
+ if (keyDownFlags["KeyQ"] || keyDownFlags["AltLeft"] || keyDownFlags["AltRight"] || keyDownFlags["KeyS"]) {
630
+ for (let bubble of this.bubbles.toReversed()) {
631
+ if (!bubble.contains(paperSize, point)) {
632
+ continue;
633
+ }
634
+ if (keyDownFlags["KeyQ"]) {
635
+ return { action: "remove", bubble };
636
+ }
637
+ else if (keyDownFlags["AltLeft"] || keyDownFlags["AltRight"]) {
638
+ return { action: "move", bubble };
639
+ }
640
+ else if (keyDownFlags["KeyS"]) {
641
+ return { action: "copy-style", bubble };
642
+ }
643
+ }
644
+ }
645
+ for (let bubble of this.bubbles.toReversed()) {
646
+ if (!bubble.contains(paperSize, point)) {
647
+ continue;
648
+ }
649
+ return { action: "select", bubble };
650
+ }
651
+ return null;
652
+ }
653
+ changeFocus(layer) {
654
+ if (layer != this) {
655
+ // unfocus呼ぶと再帰呼び出しになるので注意
656
+ if (this.selected) {
657
+ this.selected = null;
658
+ this.onFocus(null);
659
+ }
660
+ }
661
+ }
662
+ getOptionIconAt(shape, p) {
663
+ const optionSet = bubbleOptionSets[shape];
664
+ for (const option of Object.keys(optionSet)) {
665
+ const icon = this.optionIcons[optionSet[option].icon];
666
+ if (!icon) {
667
+ continue;
668
+ }
669
+ if (icon.contains(p)) {
670
+ return option;
671
+ }
672
+ }
673
+ return null;
674
+ }
675
+ unfocus() {
676
+ if (this.selected) {
677
+ this.onFocus(null);
678
+ this.focusKeeper.setFocus(null);
679
+ this.selected = null;
680
+ this.redraw();
681
+ }
682
+ }
683
+ async *pointer(dragStart, payload) {
684
+ this.hint(null, null);
685
+ if (payload.action === "create") {
686
+ yield* this.createBubble(dragStart, false);
687
+ }
688
+ else if (payload.action === "create-surface") {
689
+ yield* this.createBubble(dragStart, true);
690
+ }
691
+ else if (payload.action === "move") {
692
+ yield* this.moveBubble(dragStart, payload.bubble);
693
+ }
694
+ else if (payload.action === "offset") {
695
+ yield* this.offsetBubbleText(dragStart, payload.bubble);
696
+ }
697
+ else if (payload.action === "select") {
698
+ this.selectBubble(payload.bubble);
699
+ }
700
+ else if (payload.action === "resize") {
701
+ yield* this.resizeBubble(dragStart, payload.bubble, payload.handle);
702
+ }
703
+ else if (payload.action === "z-plus") {
704
+ const bubble = payload.bubble;
705
+ const index = this.bubbles.indexOf(bubble);
706
+ if (index < this.bubbles.length - 1) {
707
+ this.bubbles.splice(index, 1);
708
+ this.bubbles.push(bubble);
709
+ this.redraw();
710
+ }
711
+ }
712
+ else if (payload.action === "z-minus") {
713
+ const bubble = payload.bubble;
714
+ const index = this.bubbles.indexOf(bubble);
715
+ if (0 < index) {
716
+ this.bubbles.splice(index, 1);
717
+ this.bubbles.unshift(bubble);
718
+ this.redraw();
719
+ }
720
+ }
721
+ else if (payload.action === "remove") {
722
+ this.removeBubble(payload.bubble);
723
+ this.redraw();
724
+ }
725
+ else if (payload.action === "copy-style") {
726
+ yield* this.copyStyle(dragStart, payload.bubble);
727
+ this.redraw();
728
+ }
729
+ else if (payload.action === "rotate") {
730
+ yield* this.rotateBubble(dragStart, payload.bubble);
731
+ }
732
+ else if (payload.action === "image-drop") {
733
+ const bubble = payload.bubble;
734
+ bubble.image = null;
735
+ this.redraw();
736
+ }
737
+ else if (payload.action === "image-scale-lock") {
738
+ console.log("image-scale-lock");
739
+ this.toggleScaleLock(payload.bubble);
740
+ }
741
+ else if (payload.action === "image-move") {
742
+ yield* this.translateImage(dragStart, payload.bubble);
743
+ }
744
+ else if (payload.action === "image-scale") {
745
+ yield* this.scaleImage(dragStart, payload.bubble);
746
+ }
747
+ else if (payload.action === "options-tailTip") {
748
+ yield* this.optionsTailTip(dragStart, payload.bubble);
749
+ }
750
+ else if (payload.action === "options-tailMid") {
751
+ yield* this.optionsTailMid(dragStart, payload.bubble);
752
+ }
753
+ else if (payload.action === "options-link") {
754
+ yield* this.optionsLink(dragStart, payload.bubble);
755
+ }
756
+ else if (payload.action === "options-focalPoint") {
757
+ yield* this.optionsFocalPoint(dragStart, payload.bubble);
758
+ }
759
+ else if (payload.action === "options-focalRange") {
760
+ yield* this.optionsFocalRange(dragStart, payload.bubble);
761
+ }
762
+ }
763
+ async receiveImage(bubble, canvas) {
764
+ const paperSize = this.getPaperSize();
765
+ const film = this.newImageFilm(canvas);
766
+ bubble.filmStack.films.push(film);
767
+ const bubbleSize = bubble.getPhysicalSize(paperSize);
768
+ const scale = minimumBoundingScale(film.media.size, bubbleSize);
769
+ film.setShiftedScale(paperSize, scale);
770
+ bubble.gallery.push(canvas);
771
+ this.onFocus(bubble);
772
+ this.focusKeeper.setFocus(this);
773
+ this.onCommit();
774
+ }
775
+ ;
776
+ dropped(position, canvas) {
777
+ if (!this.interactable) {
778
+ return false;
779
+ }
780
+ if (this.createBubbleIcon.contains(position)) {
781
+ this.createImageBubble(canvas);
782
+ this.onCommit();
783
+ return true;
784
+ }
785
+ const paperSize = this.getPaperSize();
786
+ if (this.selected && this.selected.contains(paperSize, position)) {
787
+ this.receiveImage(this.selected, canvas);
788
+ return true;
789
+ }
790
+ for (let bubble of this.bubbles.toReversed()) {
791
+ if (bubble.contains(paperSize, position)) {
792
+ this.receiveImage(bubble, canvas);
793
+ return true;
794
+ }
795
+ }
796
+ return false;
797
+ }
798
+ doubleClicked(p) {
799
+ if (!this.interactable) {
800
+ return false;
801
+ }
802
+ const paperSize = this.getPaperSize();
803
+ for (let bubble of this.bubbles) {
804
+ if (bubble.contains(paperSize, p)) {
805
+ const size = bubble.calculateFitSize(paperSize);
806
+ bubble.setPhysicalSize(paperSize, size);
807
+ this.onCommit();
808
+ this.relayoutIcons();
809
+ return true;
810
+ }
811
+ }
812
+ const q = Bubble.normalizedPosition(paperSize, p);
813
+ const bubble = this.defaultBubbleSlot.bubble.clone(false);
814
+ bubble.parent = null;
815
+ bubble.filmStack.films = [];
816
+ bubble.n_p0 = [q[0] - 0.12, q[1] - 0.12];
817
+ bubble.n_p1 = [q[0] + 0.12, q[1] + 0.12];
818
+ //bubble.n_p0 = [0,0];
819
+ //bubble.n_p1 = [1,1];
820
+ bubble.initOptions();
821
+ bubble.text = getHaiku();
822
+ this.bubbles.push(bubble);
823
+ this.onCommit();
824
+ this.selectBubble(bubble);
825
+ return true;
826
+ }
827
+ relayoutIcons() {
828
+ const paperSize = this.getPaperSize();
829
+ const rscale = 1 / this.paper.matrix.a;
830
+ const unit = scale2D([...iconUnit], rscale);
831
+ const bubbleRect = this.selected.getPhysicalRegularizedRect(paperSize);
832
+ const grid = new Grid(this.calculateSheetRect(bubbleRect), -10, unit);
833
+ const cp = grid.calcPosition.bind(grid);
834
+ this.dragIcon.position = cp([0.5, 0], [0, 0]);
835
+ this.offsetIcon.position = add2D(cp([0.5, 0.25], [0, 0]), this.selected.getPhysicalOffset(paperSize));
836
+ this.zPlusIcon.position = cp([0, 0], [1, 0]);
837
+ this.zMinusIcon.position = cp([0, 0], [0, 0]);
838
+ this.removeIcon.position = cp([1, 0], [0, 0]);
839
+ this.rotateIcon.position = cp([0.5, 1], [0, 0]);
840
+ this.imageScaleLockIcon.position = cp([1, 1], [0, 0]);
841
+ this.imageScaleLockIcon.index = this.selected.scaleLock ? 1 : 0;
842
+ this.scaleIcon.position = cp([1, 1], [-2, 0]);
843
+ }
844
+ *createBubble(dragStart, createsSurface) {
845
+ const paperSize = this.getPaperSize();
846
+ this.unfocus();
847
+ const bubble = this.defaultBubbleSlot.bubble.clone(false);
848
+ bubble.parent = null;
849
+ bubble.filmStack.films = [];
850
+ bubble.setPhysicalRect(paperSize, [dragStart[0], dragStart[1], 0, 0]);
851
+ bubble.text = getHaiku();
852
+ bubble.initOptions();
853
+ if (createsSurface) {
854
+ bubble.shape = "none";
855
+ bubble.text = "";
856
+ }
857
+ this.creatingBubble = bubble;
858
+ let p;
859
+ try {
860
+ while ((p = yield)) {
861
+ bubble.n_p1 = Bubble.normalizedPosition(paperSize, p);
862
+ this.redraw();
863
+ }
864
+ this.creatingBubble = null;
865
+ bubble.regularize();
866
+ if (bubble.hasEnoughSize(paperSize)) {
867
+ this.bubbles.push(bubble);
868
+ this.onCommit();
869
+ if (createsSurface) {
870
+ const size = bubble.getPhysicalSize(paperSize);
871
+ const film = this.newMinmumBoundingFilm(size);
872
+ bubble.filmStack.films.push(film);
873
+ }
874
+ }
875
+ this.selectBubble(bubble);
876
+ }
877
+ catch (e) {
878
+ if (e === "cancel") {
879
+ this.creatingBubble = null;
880
+ }
881
+ }
882
+ }
883
+ *moveBubble(dragStart, bubble) {
884
+ const paperSize = this.getPaperSize();
885
+ const [x, y, w, h] = bubble.getPhysicalRegularizedRect(paperSize);
886
+ const [dx, dy] = [dragStart[0] - x, dragStart[1] - y];
887
+ let p;
888
+ try {
889
+ while ((p = yield)) {
890
+ bubble.setPhysicalRect(paperSize, [p[0] - dx, p[1] - dy, w, h]);
891
+ if (bubble === this.selected) {
892
+ this.relayoutIcons();
893
+ }
894
+ this.redraw();
895
+ }
896
+ this.onPotentialCrossPage(bubble);
897
+ this.onCommit();
898
+ }
899
+ catch (e) {
900
+ if (e === "cancel") {
901
+ this.selected = null;
902
+ this.onRevert();
903
+ }
904
+ }
905
+ }
906
+ *copyStyle(_dragStart, bubble) {
907
+ const paperSize = this.getPaperSize();
908
+ let p, last;
909
+ try {
910
+ while (p = yield) {
911
+ last = p;
912
+ this.lit = null;
913
+ for (let i = this.bubbles.length - 1; 0 <= i; i--) {
914
+ const b = this.bubbles[i];
915
+ if (b.contains(paperSize, last)) {
916
+ this.lit = b;
917
+ break;
918
+ }
919
+ }
920
+ this.redraw();
921
+ }
922
+ this.lit = null;
923
+ for (let i = this.bubbles.length - 1; 0 <= i; i--) {
924
+ const b = this.bubbles[i];
925
+ if (b.contains(paperSize, last)) {
926
+ b.copyStyleFrom(bubble);
927
+ this.onCommit();
928
+ break;
929
+ }
930
+ }
931
+ }
932
+ catch (e) {
933
+ if (e === "cancel") {
934
+ }
935
+ }
936
+ }
937
+ *rotateBubble(dragStart, bubble) {
938
+ const originalRotation = bubble.rotation;
939
+ const s = dragStart;
940
+ let p;
941
+ try {
942
+ while ((p = yield)) {
943
+ const op = (p[0] - s[0]);
944
+ bubble.rotation = Math.max(-180, Math.min(180, originalRotation + op * 0.2));
945
+ this.redraw();
946
+ }
947
+ this.onCommit();
948
+ }
949
+ catch (e) {
950
+ if (e === "cancel") {
951
+ this.selected = null;
952
+ this.onRevert();
953
+ }
954
+ }
955
+ }
956
+ *offsetBubbleText(dragStart, bubble) {
957
+ const paperSize = this.getPaperSize();
958
+ const q = bubble.getPhysicalOffset(paperSize);
959
+ let p;
960
+ try {
961
+ while ((p = yield)) {
962
+ bubble.setPhysicalOffset(paperSize, [q[0] + p[0] - dragStart[0], q[1] + p[1] - dragStart[1]]);
963
+ if (bubble === this.selected) {
964
+ this.relayoutIcons();
965
+ }
966
+ this.redraw();
967
+ }
968
+ this.onCommit();
969
+ }
970
+ catch (e) {
971
+ if (e === "cancel") {
972
+ this.selected = null;
973
+ this.onRevert();
974
+ }
975
+ }
976
+ }
977
+ *resizeBubble(dragStart, bubble, handle) {
978
+ try {
979
+ const paperSize = this.getPaperSize();
980
+ const [q0, q1] = bubble.regularized();
981
+ let ir = calculateMinimumBoundingRect(paperSize, bubble.filmStack.films);
982
+ // computeConstraintedRectは十分に大きいときには反応しないので、小さくしておく
983
+ if (ir) {
984
+ ir = scaleRect(ir, 0.01);
985
+ }
986
+ const transformer = new FilmStackTransformer(paperSize, bubble.filmStack.films);
987
+ let p;
988
+ while ((p = yield)) {
989
+ const pp = Bubble.normalizedPosition(paperSize, p);
990
+ this.resizeBubbleAux(bubble, handle, q0, q1, pp);
991
+ if (ir && bubble.scaleLock) {
992
+ // イメージの位置を中央に固定し、フキダシの大きさにイメージを合わせる
993
+ const bubbleRect = bubble.getPhysicalRect(paperSize);
994
+ const { scale } = computeConstraintedRect(ir, bubbleRect);
995
+ transformer.scale(scale * 0.01);
996
+ }
997
+ this.relayoutIcons();
998
+ this.redraw();
999
+ }
1000
+ bubble.regularize();
1001
+ if (!bubble.hasEnoughSize(paperSize)) {
1002
+ throw "cancel";
1003
+ }
1004
+ this.onPotentialCrossPage(bubble);
1005
+ this.onCommit();
1006
+ }
1007
+ catch (e) {
1008
+ if (e === "cancel") {
1009
+ this.selected = null;
1010
+ this.onRevert();
1011
+ }
1012
+ else {
1013
+ console.error(e);
1014
+ }
1015
+ }
1016
+ }
1017
+ resizeBubbleAux(bubble, handle, q0, q1, p) {
1018
+ const paperSize = this.getPaperSize();
1019
+ if (bubble.scaleLock) {
1020
+ const rwfar = (os, ns) => {
1021
+ return this.resizeWithFixedAspectRatio(os, ns);
1022
+ };
1023
+ const qq0 = Bubble.denormalizedPosition(paperSize, q0);
1024
+ const qq1 = Bubble.denormalizedPosition(paperSize, q1);
1025
+ const pp = Bubble.denormalizedPosition(paperSize, p);
1026
+ const [cx, cy] = [(qq0[0] + qq1[0]) / 2, (qq0[1] + qq1[1]) / 2];
1027
+ const originalSize = [qq1[0] - qq0[0], qq1[1] - qq0[1]];
1028
+ let userSize;
1029
+ let w, h, scale;
1030
+ switch (handle) {
1031
+ case "topLeft":
1032
+ userSize = [qq1[0] - pp[0], qq1[1] - pp[1]];
1033
+ [w, h] = rwfar(originalSize, userSize);
1034
+ bubble.n_p0 = Bubble.normalizedPosition(paperSize, [qq1[0] - w, qq1[1] - h]);
1035
+ break;
1036
+ case "topRight":
1037
+ userSize = [pp[0] - qq0[0], qq1[1] - pp[1]];
1038
+ [w, h] = rwfar(originalSize, userSize);
1039
+ bubble.n_p0 = Bubble.normalizedPosition(paperSize, [qq0[0], qq1[1] - h]);
1040
+ bubble.n_p1 = Bubble.normalizedPosition(paperSize, [qq0[0] + w, qq1[1]]);
1041
+ break;
1042
+ case "bottomLeft":
1043
+ userSize = [qq1[0] - pp[0], pp[1] - qq0[1]];
1044
+ [w, h] = rwfar(originalSize, userSize);
1045
+ bubble.n_p0 = Bubble.normalizedPosition(paperSize, [qq1[0] - w, qq0[1]]);
1046
+ bubble.n_p1 = Bubble.normalizedPosition(paperSize, [qq1[0], qq0[1] + h]);
1047
+ break;
1048
+ case "bottomRight":
1049
+ userSize = [pp[0] - qq0[0], pp[1] - qq0[1]];
1050
+ [w, h] = rwfar(originalSize, userSize);
1051
+ bubble.n_p1 = Bubble.normalizedPosition(paperSize, [qq0[0] + w, qq0[1] + h]);
1052
+ break;
1053
+ case "top":
1054
+ userSize = [originalSize[0], qq1[1] - pp[1]];
1055
+ scale = userSize[1] / originalSize[1];
1056
+ [w, h] = [originalSize[0] * scale, userSize[1]];
1057
+ bubble.n_p0 = Bubble.normalizedPosition(paperSize, [cx - w / 2, qq1[1] - h]);
1058
+ bubble.n_p1 = Bubble.normalizedPosition(paperSize, [cx + w / 2, qq1[1]]);
1059
+ break;
1060
+ case "bottom":
1061
+ userSize = [originalSize[0], pp[1] - qq0[1]];
1062
+ scale = userSize[1] / originalSize[1];
1063
+ [w, h] = [originalSize[0] * scale, userSize[1]];
1064
+ bubble.n_p0 = Bubble.normalizedPosition(paperSize, [cx - w / 2, qq0[1]]);
1065
+ bubble.n_p1 = Bubble.normalizedPosition(paperSize, [cx + w / 2, qq0[1] + h]);
1066
+ break;
1067
+ case "left":
1068
+ userSize = [qq1[0] - pp[0], originalSize[1]];
1069
+ scale = userSize[0] / originalSize[0];
1070
+ [w, h] = [userSize[0], originalSize[1] * scale];
1071
+ bubble.n_p0 = Bubble.normalizedPosition(paperSize, [qq1[0] - w, cy - h / 2]);
1072
+ bubble.n_p1 = Bubble.normalizedPosition(paperSize, [qq1[0], cy + h / 2]);
1073
+ break;
1074
+ case "right":
1075
+ userSize = [pp[0] - qq0[0], originalSize[1]];
1076
+ scale = userSize[0] / originalSize[0];
1077
+ [w, h] = [userSize[0], originalSize[1] * scale];
1078
+ bubble.n_p0 = Bubble.normalizedPosition(paperSize, [qq0[0], cy - h / 2]);
1079
+ bubble.n_p1 = Bubble.normalizedPosition(paperSize, [qq0[0] + w, cy + h / 2]);
1080
+ break;
1081
+ }
1082
+ }
1083
+ else {
1084
+ switch (handle) {
1085
+ case "topLeft":
1086
+ bubble.n_p0 = p;
1087
+ break;
1088
+ case "topRight":
1089
+ bubble.n_p0 = [q0[0], p[1]];
1090
+ bubble.n_p1 = [p[0], q1[1]];
1091
+ break;
1092
+ case "bottomLeft":
1093
+ bubble.n_p0 = [p[0], q0[1]];
1094
+ bubble.n_p1 = [q1[0], p[1]];
1095
+ break;
1096
+ case "bottomRight":
1097
+ bubble.n_p1 = p;
1098
+ break;
1099
+ case "top":
1100
+ bubble.n_p0 = [q0[0], p[1]];
1101
+ break;
1102
+ case "bottom":
1103
+ bubble.n_p1 = [q1[0], p[1]];
1104
+ break;
1105
+ case "left":
1106
+ bubble.n_p0 = [p[0], q0[1]];
1107
+ break;
1108
+ case "right":
1109
+ bubble.n_p1 = [p[0], q1[1]];
1110
+ break;
1111
+ }
1112
+ }
1113
+ }
1114
+ *translateImage(dragStart, bubble) {
1115
+ // クリック判定と兼用
1116
+ const startingTime = performance.now();
1117
+ const paperSize = this.getPaperSize();
1118
+ const films = bubble.filmStack.getOperationTargetFilms();
1119
+ const origins = films.map(film => film.getShiftedTranslation(paperSize));
1120
+ try {
1121
+ let lastq = null;
1122
+ yield* translate(dragStart, (q) => {
1123
+ lastq = [...q];
1124
+ films.forEach((film, i) => {
1125
+ film.setShiftedTranslation(paperSize, [origins[i][0] + q[0], origins[i][1] + q[1]]);
1126
+ });
1127
+ this.redraw();
1128
+ });
1129
+ if (lastq[0] !== 0 || lastq[1] !== 0) {
1130
+ this.onCommit();
1131
+ }
1132
+ }
1133
+ catch (e) {
1134
+ if (e === "cancel") {
1135
+ this.onRevert();
1136
+ }
1137
+ }
1138
+ if (performance.now() - startingTime < 200) {
1139
+ // クリック判定
1140
+ this.pierce();
1141
+ }
1142
+ }
1143
+ *scaleImage(dragStart, bubble) {
1144
+ const paperSize = this.getPaperSize();
1145
+ const films = bubble.filmStack.getOperationTargetFilms();
1146
+ try {
1147
+ const transformer = new FilmStackTransformer(paperSize, films);
1148
+ yield* scale(this.getPaperSize(), dragStart, (q) => {
1149
+ transformer.scale(Math.max(q[0], q[1]));
1150
+ this.redraw();
1151
+ });
1152
+ this.onCommit();
1153
+ }
1154
+ catch (e) {
1155
+ if (e === "cancel") {
1156
+ this.onRevert();
1157
+ }
1158
+ }
1159
+ }
1160
+ *optionsTailTip(p, bubble) {
1161
+ const s = bubble.optionContext.tailTip;
1162
+ try {
1163
+ const q = p;
1164
+ while (p = yield) {
1165
+ this.optionEditActive.tail = true;
1166
+ bubble.optionContext.tailTip = [s[0] + p[0] - q[0], s[1] + p[1] - q[1]];
1167
+ this.redraw();
1168
+ }
1169
+ }
1170
+ catch (e) {
1171
+ console.log(e);
1172
+ if (e === "cancel") {
1173
+ bubble.optionContext.tailTip = [0, 0];
1174
+ }
1175
+ }
1176
+ finally {
1177
+ this.optionEditActive.tail = false;
1178
+ this.redraw();
1179
+ }
1180
+ }
1181
+ *optionsTailMid(p, bubble) {
1182
+ const paperSize = this.getPaperSize();
1183
+ console.log("optionsTailMid");
1184
+ // bubble.centerを原点(O)とし、
1185
+ // X軸: O->tailTip Y軸: O->pependicular(O->tailTip)座標系の座標
1186
+ // この座標系をtail座標系と呼ぶ
1187
+ const s = bubble.optionContext.tailMid;
1188
+ try {
1189
+ while (p = yield) {
1190
+ this.optionEditActive.tail = true;
1191
+ const c = bubble.getPhysicalCenter(paperSize);
1192
+ bubble.optionContext.tailMid = worldCoordToTailCoord(c, bubble.optionContext.tailTip, p);
1193
+ this.redraw();
1194
+ }
1195
+ }
1196
+ catch (e) {
1197
+ console.log(e);
1198
+ if (e === "cancel") {
1199
+ bubble.optionContext.tailMid = s;
1200
+ this.redraw();
1201
+ }
1202
+ }
1203
+ finally {
1204
+ this.optionEditActive.tail = false;
1205
+ this.redraw();
1206
+ }
1207
+ }
1208
+ *optionsLink(p, bubble) {
1209
+ const paperSize = this.getPaperSize();
1210
+ try {
1211
+ const q = p;
1212
+ let drop = null;
1213
+ while (p = yield) {
1214
+ this.optionEditActive.link = true;
1215
+ bubble.optionContext.link = [p[0] - q[0], p[1] - q[1]];
1216
+ this.redraw();
1217
+ drop = p;
1218
+ }
1219
+ if (drop) {
1220
+ console.log("drop");
1221
+ for (let i = this.bubbles.length - 1; 0 <= i; i--) {
1222
+ const b = this.bubbles[i];
1223
+ if (b !== bubble && b.contains(paperSize, drop) && b.canLink()) {
1224
+ if (this.getGroupMaster(bubble) === this.getGroupMaster(b)) {
1225
+ if (b.parent) {
1226
+ b.parent = null;
1227
+ }
1228
+ else {
1229
+ bubble.parent = null;
1230
+ }
1231
+ this.redraw();
1232
+ }
1233
+ else {
1234
+ this.mergeGroup(this.getGroup(bubble), this.getGroup(b));
1235
+ this.redraw();
1236
+ }
1237
+ this.onCommit();
1238
+ break;
1239
+ }
1240
+ }
1241
+ }
1242
+ }
1243
+ catch (e) {
1244
+ if (e === "cancel") {
1245
+ bubble.optionContext.link = [0, 0];
1246
+ }
1247
+ }
1248
+ finally {
1249
+ this.optionEditActive.link = false;
1250
+ this.redraw();
1251
+ }
1252
+ }
1253
+ *optionsFocalPoint(p, bubble) {
1254
+ const s = bubble.optionContext.focalPoint;
1255
+ try {
1256
+ const q = p;
1257
+ while (p = yield) {
1258
+ this.optionEditActive.focal = true;
1259
+ bubble.optionContext.focalPoint = [s[0] + p[0] - q[0], s[1] + p[1] - q[1]];
1260
+ this.redraw();
1261
+ }
1262
+ }
1263
+ catch (e) {
1264
+ if (e === "cancel") {
1265
+ bubble.optionContext.focalPoint = s;
1266
+ this.redraw();
1267
+ }
1268
+ }
1269
+ finally {
1270
+ this.optionEditActive.focal = false;
1271
+ this.redraw();
1272
+ }
1273
+ }
1274
+ *optionsFocalRange(p, bubble) {
1275
+ const s = bubble.optionContext.focalRange;
1276
+ try {
1277
+ const q = p;
1278
+ while (p = yield) {
1279
+ this.optionEditActive.focal = true;
1280
+ bubble.optionContext.focalRange = [s[0] + p[0] - q[0], s[1] + p[1] - q[1]];
1281
+ this.redraw();
1282
+ }
1283
+ }
1284
+ catch (e) {
1285
+ if (e === "cancel") {
1286
+ bubble.optionContext.focalRange = s;
1287
+ this.redraw();
1288
+ }
1289
+ }
1290
+ finally {
1291
+ this.optionEditActive.focal = false;
1292
+ this.redraw();
1293
+ }
1294
+ }
1295
+ getGroup(bubble) {
1296
+ const group = [bubble];
1297
+ let modified = true;
1298
+ while (modified) {
1299
+ modified = false;
1300
+ for (let i = 0; i < group.length; i++) {
1301
+ const b = group[i];
1302
+ for (let j = 0; j < this.bubbles.length; j++) {
1303
+ const b2 = this.bubbles[j];
1304
+ if (b2.linkedTo(b) && !group.includes(b2)) {
1305
+ group.push(b2);
1306
+ modified = true;
1307
+ }
1308
+ }
1309
+ }
1310
+ }
1311
+ return group;
1312
+ }
1313
+ regularizeGroup(g) {
1314
+ // parent1つに集約する
1315
+ const parent = g[0];
1316
+ for (let i = 1; i < g.length; i++) {
1317
+ const child = g[i];
1318
+ child.linkTo(parent);
1319
+ }
1320
+ }
1321
+ mergeGroup(g1, g2) {
1322
+ const g = g1.concat(g2);
1323
+ this.regularizeGroup(g);
1324
+ }
1325
+ getGroupMaster(bubble) {
1326
+ if (bubble.parent) {
1327
+ const parent = this.bubbles.find((b) => b.uuid === bubble.parent);
1328
+ if (parent) {
1329
+ return parent;
1330
+ }
1331
+ }
1332
+ return bubble;
1333
+ }
1334
+ resizeWithFixedAspectRatio(originalSize, newSize) {
1335
+ const [originalWidth, originalHeight] = originalSize;
1336
+ const [newWidth, newHeight] = newSize;
1337
+ const scaleFactorX = newWidth / originalWidth;
1338
+ const scaleFactorY = newHeight / originalHeight;
1339
+ const dominantScaleFactor = Math.max(scaleFactorX, scaleFactorY);
1340
+ const scaledWidth = Math.round(originalWidth * dominantScaleFactor);
1341
+ const scaledHeight = Math.round(originalHeight * dominantScaleFactor);
1342
+ return [scaledWidth, scaledHeight];
1343
+ }
1344
+ toggleScaleLock(bubble) {
1345
+ bubble.scaleLock = !bubble.scaleLock;
1346
+ this.imageScaleLockIcon.index = bubble.scaleLock ? 1 : 0;
1347
+ if (bubble.scaleLock) {
1348
+ const paperSize = this.getPaperSize();
1349
+ {
1350
+ const transformer = new FilmStackTransformer(paperSize, bubble.filmStack.films);
1351
+ // [0]のscaleを1とする
1352
+ const scale = bubble.filmStack.films[0].getShiftedScale(paperSize);
1353
+ transformer.scale(1 / scale);
1354
+ }
1355
+ const ir = calculateMinimumBoundingRect(paperSize, bubble.filmStack.films);
1356
+ const origins = bubble.filmStack.films.map(film => film.getShiftedTranslation(paperSize));
1357
+ const move = getRectCenter(ir);
1358
+ const bubbleSize = bubble.getPhysicalSize(paperSize);
1359
+ const [w, h] = this.resizeWithFixedAspectRatio([ir[2], ir[3]], bubbleSize);
1360
+ const newScale = w / ir[2];
1361
+ console.log("newScale", newScale);
1362
+ const [cx, cy] = bubble.getPhysicalCenter(paperSize);
1363
+ bubble.n_p0 = Bubble.normalizedPosition(paperSize, [cx - w / 2, cy - h / 2]);
1364
+ bubble.n_p1 = Bubble.normalizedPosition(paperSize, [cx + w / 2, cy + h / 2]);
1365
+ bubble.filmStack.films.forEach((film, i) => {
1366
+ film.setShiftedTranslation(paperSize, [origins[i][0] + move[0], origins[i][1] + move[1]]);
1367
+ });
1368
+ {
1369
+ const transformer = new FilmStackTransformer(paperSize, bubble.filmStack.films);
1370
+ transformer.scale(newScale);
1371
+ }
1372
+ this.relayoutIcons();
1373
+ }
1374
+ this.redraw();
1375
+ }
1376
+ selectBubble(bubble) {
1377
+ for (let a of Object.keys(this.optionEditActive)) {
1378
+ if (this.optionEditActive[a]) {
1379
+ this.redraw();
1380
+ return;
1381
+ }
1382
+ }
1383
+ this.unfocus();
1384
+ this.selected = bubble;
1385
+ this.relayoutIcons();
1386
+ this.onFocus(this.selected);
1387
+ this.focusKeeper.setFocus(this);
1388
+ this.redraw();
1389
+ }
1390
+ renderDepths() { return [1]; }
1391
+ get interactable() { return this.mode == null; }
1392
+ setFold(n) {
1393
+ this.fold = n;
1394
+ if (this.fold === 1) {
1395
+ this.createBubbleIcon.pivot = [1, 0];
1396
+ }
1397
+ else {
1398
+ this.createBubbleIcon.pivot = [0, 1];
1399
+ }
1400
+ }
1401
+ newImageFilm(canvas) {
1402
+ const film = new Film(new ImageMedia(canvas));
1403
+ return film;
1404
+ }
1405
+ newMinmumBoundingFilm([w, h]) {
1406
+ // 256ごとに切り上げ
1407
+ const unit = 16;
1408
+ const [w2, h2] = [Math.ceil(w / unit) * unit, Math.ceil(h / unit) * unit];
1409
+ console.log(w, h, w2, h2);
1410
+ const image = makePlainImage(w2, h2, "#ffffff00");
1411
+ const film = this.newImageFilm(image);
1412
+ film.setShiftedScale(this.getPaperSize(), 1);
1413
+ console.log("n_scale", film.n_scale);
1414
+ return film;
1415
+ }
1416
+ }
1417
+ sequentializePointer(BubbleLayer);
1418
+ //# sourceMappingURL=bubbleLayer.js.map