js-draw 0.11.2 → 0.11.3

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.
@@ -244,17 +244,7 @@ export class ImageNode {
244
244
  this.bbox = this.content.getBBox();
245
245
  }
246
246
  else {
247
- this.bbox = Rect2.empty;
248
- let isFirst = true;
249
- for (const child of this.children) {
250
- if (isFirst) {
251
- this.bbox = child.getBBox();
252
- isFirst = false;
253
- }
254
- else {
255
- this.bbox = this.bbox.union(child.getBBox());
256
- }
257
- }
247
+ this.bbox = Rect2.union(...this.children.map(child => child.getBBox()));
258
248
  }
259
249
  if (bubbleUp && !oldBBox.eq(this.bbox)) {
260
250
  (_a = this.parent) === null || _a === void 0 ? void 0 : _a.recomputeBBox(true);
@@ -45,6 +45,7 @@ export default class Rect2 {
45
45
  toString(): string;
46
46
  static fromCorners(corner1: Point2, corner2: Point2): Rect2;
47
47
  static bboxOf(points: Point2[], margin?: number): Rect2;
48
+ static union(...rects: Rect2[]): Rect2;
48
49
  static of(template: RectTemplate): Rect2;
49
50
  static empty: Rect2;
50
51
  static unitSquare: Rect2;
@@ -197,6 +197,26 @@ export default class Rect2 {
197
197
  }
198
198
  return Rect2.fromCorners(Vec2.of(minX - margin, minY - margin), Vec2.of(maxX + margin, maxY + margin));
199
199
  }
200
+ // @returns a rectangle that contains all of the given rectangles, the bounding box
201
+ // of the given rectangles.
202
+ static union(...rects) {
203
+ if (rects.length === 0) {
204
+ return Rect2.empty;
205
+ }
206
+ const firstRect = rects[0];
207
+ let minX = firstRect.topLeft.x;
208
+ let minY = firstRect.topLeft.y;
209
+ let maxX = firstRect.bottomRight.x;
210
+ let maxY = firstRect.bottomRight.y;
211
+ for (let i = 1; i < rects.length; i++) {
212
+ const rect = rects[i];
213
+ minX = Math.min(minX, rect.topLeft.x);
214
+ minY = Math.min(minY, rect.topLeft.y);
215
+ maxX = Math.max(maxX, rect.bottomRight.x);
216
+ maxY = Math.max(maxY, rect.bottomRight.y);
217
+ }
218
+ return new Rect2(minX, minY, maxX - minX, maxY - minY);
219
+ }
200
220
  static of(template) {
201
221
  var _a, _b, _c, _d;
202
222
  const width = (_b = (_a = template.width) !== null && _a !== void 0 ? _a : template.w) !== null && _b !== void 0 ? _b : 0;
@@ -353,17 +353,19 @@ export default class IconProvider {
353
353
  return icon;
354
354
  }
355
355
  makeIconFromFactory(pen, factory) {
356
- const toolThickness = pen.getThickness();
356
+ // Increase the thickness we use to generate the icon less with larger actual thicknesses.
357
+ // We want the icon to be recognisable with a large range of thicknesses.
358
+ const thickness = Math.sqrt(pen.getThickness()) * 3;
357
359
  const nowTime = (new Date()).getTime();
358
360
  const startPoint = {
359
361
  pos: Vec2.of(10, 10),
360
- width: toolThickness / 5,
362
+ width: thickness,
361
363
  color: pen.getColor(),
362
364
  time: nowTime - 100,
363
365
  };
364
366
  const endPoint = {
365
367
  pos: Vec2.of(90, 90),
366
- width: toolThickness / 5,
368
+ width: thickness,
367
369
  color: pen.getColor(),
368
370
  time: nowTime,
369
371
  };
@@ -63,11 +63,19 @@ export default class TextTool extends BaseTool {
63
63
  // Estimate
64
64
  return style.size * 2 / 3;
65
65
  }
66
- flushInput() {
66
+ // Take input from this' textInputElem and add it to the EditorImage.
67
+ // If [removeInput], the HTML input element is removed. Otherwise, its value
68
+ // is cleared.
69
+ flushInput(removeInput = true) {
67
70
  if (this.textInputElem && this.textTargetPosition) {
68
71
  const content = this.textInputElem.value.trimEnd();
69
- this.textInputElem.remove();
70
- this.textInputElem = null;
72
+ if (removeInput) {
73
+ this.textInputElem.remove();
74
+ this.textInputElem = null;
75
+ }
76
+ else {
77
+ this.textInputElem.value = '';
78
+ }
71
79
  if (content === '') {
72
80
  return;
73
81
  }
@@ -134,9 +142,12 @@ export default class TextTool extends BaseTool {
134
142
  }
135
143
  };
136
144
  this.textInputElem.onblur = () => {
137
- // Don't remove the input within the context of a blur event handler.
138
- // Doing so causes errors.
139
- setTimeout(() => this.flushInput(), 0);
145
+ // Delay removing the input -- flushInput may be called within a blur()
146
+ // event handler
147
+ const removeInput = false;
148
+ this.flushInput(removeInput);
149
+ const input = this.textInputElem;
150
+ setTimeout(() => input === null || input === void 0 ? void 0 : input.remove(), 0);
140
151
  };
141
152
  this.textInputElem.onkeyup = (evt) => {
142
153
  var _a, _b;
@@ -21,13 +21,13 @@ export default class ToolController {
21
21
  this.primaryToolGroup = primaryToolGroup;
22
22
  const panZoomTool = new PanZoom(editor, PanZoomMode.TwoFingerTouchGestures | PanZoomMode.RightClickDrags, localization.touchPanTool);
23
23
  const keyboardPanZoomTool = new PanZoom(editor, PanZoomMode.Keyboard, localization.keyboardPanZoom);
24
- const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 16 });
24
+ const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 8 });
25
25
  const primaryTools = [
26
26
  // Three pens
27
27
  primaryPenTool,
28
28
  new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 4 }),
29
- // Highlighter-like pen with width=64
30
- new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }, makePressureSensitiveFreehandLineBuilder),
29
+ // Highlighter-like pen with width=40
30
+ new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 40 }, makePressureSensitiveFreehandLineBuilder),
31
31
  new Eraser(editor, localization.eraserTool),
32
32
  new SelectionTool(editor, localization.selectionTool),
33
33
  new TextTool(editor, localization.textTool, localization),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.11.2",
3
+ "version": "0.11.3",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "main": "./dist/src/lib.d.ts",
6
6
  "types": "./dist/src/lib.js",
@@ -320,17 +320,7 @@ export class ImageNode {
320
320
  if (this.content !== null) {
321
321
  this.bbox = this.content.getBBox();
322
322
  } else {
323
- this.bbox = Rect2.empty;
324
- let isFirst = true;
325
-
326
- for (const child of this.children) {
327
- if (isFirst) {
328
- this.bbox = child.getBBox();
329
- isFirst = false;
330
- } else {
331
- this.bbox = this.bbox.union(child.getBBox());
332
- }
333
- }
323
+ this.bbox = Rect2.union(...this.children.map(child => child.getBBox()));
334
324
  }
335
325
 
336
326
  if (bubbleUp && !oldBBox.eq(this.bbox)) {
@@ -46,6 +46,28 @@ describe('Rect2', () => {
46
46
  expect(Rect2.empty.union(Rect2.empty)).objEq(Rect2.empty);
47
47
  });
48
48
 
49
+ it('should handle empty unions', () => {
50
+ expect(Rect2.union()).toStrictEqual(Rect2.empty);
51
+ });
52
+
53
+ it('should correctly union multiple rectangles', () => {
54
+ expect(Rect2.union(new Rect2(0, 0, 1, 1), new Rect2(1, 1, 2, 2))).objEq(
55
+ new Rect2(0, 0, 3, 3)
56
+ );
57
+
58
+ expect(
59
+ Rect2.union(new Rect2(-1, 0, 1, 1), new Rect2(1, 1, 2, 2), new Rect2(1, 10, 1, 0.1))
60
+ ).objEq(
61
+ new Rect2(-1, 0, 4, 10.1)
62
+ );
63
+
64
+ expect(
65
+ Rect2.union(new Rect2(-1, 0, 1, 1), new Rect2(1, -11.1, 2, 2), new Rect2(1, 10, 1, 0.1))
66
+ ).objEq(
67
+ new Rect2(-1, -11.1, 4, 21.2)
68
+ );
69
+ });
70
+
49
71
  it('should contain points that are within a rectangle', () => {
50
72
  expect(new Rect2(-1, -1, 2, 2).containsPoint(Vec2.zero)).toBe(true);
51
73
  expect(new Rect2(-1, -1, 0, 0).containsPoint(Vec2.zero)).toBe(false);
package/src/math/Rect2.ts CHANGED
@@ -278,6 +278,32 @@ export default class Rect2 {
278
278
  );
279
279
  }
280
280
 
281
+ // @returns a rectangle that contains all of the given rectangles, the bounding box
282
+ // of the given rectangles.
283
+ public static union(...rects: Rect2[]): Rect2 {
284
+ if (rects.length === 0) {
285
+ return Rect2.empty;
286
+ }
287
+
288
+ const firstRect = rects[0];
289
+ let minX: number = firstRect.topLeft.x;
290
+ let minY: number = firstRect.topLeft.y;
291
+ let maxX: number = firstRect.bottomRight.x;
292
+ let maxY: number = firstRect.bottomRight.y;
293
+
294
+ for (let i = 1; i < rects.length; i++) {
295
+ const rect = rects[i];
296
+ minX = Math.min(minX, rect.topLeft.x);
297
+ minY = Math.min(minY, rect.topLeft.y);
298
+ maxX = Math.max(maxX, rect.bottomRight.x);
299
+ maxY = Math.max(maxY, rect.bottomRight.y);
300
+ }
301
+
302
+ return new Rect2(
303
+ minX, minY, maxX - minX, maxY - minY,
304
+ );
305
+ }
306
+
281
307
  public static of(template: RectTemplate) {
282
308
  const width = template.width ?? template.w ?? 0;
283
309
  const height = template.height ?? template.h ?? 0;
@@ -407,18 +407,20 @@ export default class IconProvider {
407
407
  }
408
408
 
409
409
  public makeIconFromFactory(pen: Pen, factory: ComponentBuilderFactory): IconType {
410
- const toolThickness = pen.getThickness();
410
+ // Increase the thickness we use to generate the icon less with larger actual thicknesses.
411
+ // We want the icon to be recognisable with a large range of thicknesses.
412
+ const thickness = Math.sqrt(pen.getThickness()) * 3;
411
413
 
412
414
  const nowTime = (new Date()).getTime();
413
415
  const startPoint: StrokeDataPoint = {
414
416
  pos: Vec2.of(10, 10),
415
- width: toolThickness / 5,
417
+ width: thickness,
416
418
  color: pen.getColor(),
417
419
  time: nowTime - 100,
418
420
  };
419
421
  const endPoint: StrokeDataPoint = {
420
422
  pos: Vec2.of(90, 90),
421
- width: toolThickness / 5,
423
+ width: thickness,
422
424
  color: pen.getColor(),
423
425
  time: nowTime,
424
426
  };
@@ -73,11 +73,19 @@ export default class TextTool extends BaseTool {
73
73
  return style.size * 2 / 3;
74
74
  }
75
75
 
76
- private flushInput() {
76
+ // Take input from this' textInputElem and add it to the EditorImage.
77
+ // If [removeInput], the HTML input element is removed. Otherwise, its value
78
+ // is cleared.
79
+ private flushInput(removeInput: boolean = true) {
77
80
  if (this.textInputElem && this.textTargetPosition) {
78
81
  const content = this.textInputElem.value.trimEnd();
79
- this.textInputElem.remove();
80
- this.textInputElem = null;
82
+
83
+ if (removeInput) {
84
+ this.textInputElem.remove();
85
+ this.textInputElem = null;
86
+ } else {
87
+ this.textInputElem.value = '';
88
+ }
81
89
 
82
90
  if (content === '') {
83
91
  return;
@@ -166,9 +174,13 @@ export default class TextTool extends BaseTool {
166
174
  }
167
175
  };
168
176
  this.textInputElem.onblur = () => {
169
- // Don't remove the input within the context of a blur event handler.
170
- // Doing so causes errors.
171
- setTimeout(() => this.flushInput(), 0);
177
+ // Delay removing the input -- flushInput may be called within a blur()
178
+ // event handler
179
+ const removeInput = false;
180
+ this.flushInput(removeInput);
181
+
182
+ const input = this.textInputElem;
183
+ setTimeout(() => input?.remove(), 0);
172
184
  };
173
185
  this.textInputElem.onkeyup = (evt) => {
174
186
  if (evt.key === 'Enter' && !evt.shiftKey) {
@@ -29,14 +29,14 @@ export default class ToolController {
29
29
 
30
30
  const panZoomTool = new PanZoom(editor, PanZoomMode.TwoFingerTouchGestures | PanZoomMode.RightClickDrags, localization.touchPanTool);
31
31
  const keyboardPanZoomTool = new PanZoom(editor, PanZoomMode.Keyboard, localization.keyboardPanZoom);
32
- const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 16 });
32
+ const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 8 });
33
33
  const primaryTools = [
34
34
  // Three pens
35
35
  primaryPenTool,
36
36
  new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 4 }),
37
37
 
38
- // Highlighter-like pen with width=64
39
- new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }, makePressureSensitiveFreehandLineBuilder),
38
+ // Highlighter-like pen with width=40
39
+ new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 40 }, makePressureSensitiveFreehandLineBuilder),
40
40
 
41
41
  new Eraser(editor, localization.eraserTool),
42
42
  new SelectionTool(editor, localization.selectionTool),