js-draw 1.7.2 → 1.9.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 (68) hide show
  1. package/LICENSE +1 -1
  2. package/dist/Editor.css +1 -0
  3. package/dist/bundle.js +2 -2
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/EventDispatcher.js +2 -1
  6. package/dist/cjs/SVGLoader.d.ts +1 -1
  7. package/dist/cjs/SVGLoader.js +11 -2
  8. package/dist/cjs/Viewport.d.ts +2 -0
  9. package/dist/cjs/Viewport.js +2 -0
  10. package/dist/cjs/components/AbstractComponent.d.ts +9 -0
  11. package/dist/cjs/components/AbstractComponent.js +11 -0
  12. package/dist/cjs/components/Stroke.d.ts +3 -0
  13. package/dist/cjs/components/Stroke.js +55 -1
  14. package/dist/cjs/image/EditorImage.d.ts +20 -0
  15. package/dist/cjs/image/EditorImage.js +48 -3
  16. package/dist/cjs/rendering/Display.d.ts +9 -0
  17. package/dist/cjs/rendering/Display.js +39 -5
  18. package/dist/cjs/rendering/RenderablePathSpec.d.ts +20 -2
  19. package/dist/cjs/rendering/RenderablePathSpec.js +72 -9
  20. package/dist/cjs/rendering/caching/CacheRecord.js +5 -3
  21. package/dist/cjs/rendering/caching/CacheRecordManager.js +3 -4
  22. package/dist/cjs/rendering/caching/RenderingCache.d.ts +1 -0
  23. package/dist/cjs/rendering/caching/RenderingCache.js +4 -0
  24. package/dist/cjs/rendering/caching/RenderingCacheNode.js +19 -11
  25. package/dist/cjs/rendering/caching/types.d.ts +1 -0
  26. package/dist/cjs/rendering/renderers/AbstractRenderer.d.ts +10 -0
  27. package/dist/cjs/rendering/renderers/AbstractRenderer.js +12 -3
  28. package/dist/cjs/rendering/renderers/CanvasRenderer.js +5 -2
  29. package/dist/cjs/rendering/renderers/SVGRenderer.d.ts +0 -10
  30. package/dist/cjs/rendering/renderers/SVGRenderer.js +0 -14
  31. package/dist/cjs/testing/startPinchGesture.d.ts +14 -0
  32. package/dist/cjs/testing/startPinchGesture.js +41 -0
  33. package/dist/cjs/tools/PanZoom.d.ts +4 -0
  34. package/dist/cjs/tools/PanZoom.js +21 -2
  35. package/dist/cjs/version.js +1 -1
  36. package/dist/mjs/EventDispatcher.mjs +2 -1
  37. package/dist/mjs/SVGLoader.d.ts +1 -1
  38. package/dist/mjs/SVGLoader.mjs +11 -2
  39. package/dist/mjs/Viewport.d.ts +2 -0
  40. package/dist/mjs/Viewport.mjs +2 -0
  41. package/dist/mjs/components/AbstractComponent.d.ts +9 -0
  42. package/dist/mjs/components/AbstractComponent.mjs +11 -0
  43. package/dist/mjs/components/Stroke.d.ts +3 -0
  44. package/dist/mjs/components/Stroke.mjs +56 -2
  45. package/dist/mjs/image/EditorImage.d.ts +20 -0
  46. package/dist/mjs/image/EditorImage.mjs +46 -2
  47. package/dist/mjs/rendering/Display.d.ts +9 -0
  48. package/dist/mjs/rendering/Display.mjs +39 -5
  49. package/dist/mjs/rendering/RenderablePathSpec.d.ts +20 -2
  50. package/dist/mjs/rendering/RenderablePathSpec.mjs +71 -9
  51. package/dist/mjs/rendering/caching/CacheRecord.mjs +5 -3
  52. package/dist/mjs/rendering/caching/CacheRecordManager.mjs +3 -4
  53. package/dist/mjs/rendering/caching/RenderingCache.d.ts +1 -0
  54. package/dist/mjs/rendering/caching/RenderingCache.mjs +4 -0
  55. package/dist/mjs/rendering/caching/RenderingCacheNode.mjs +20 -12
  56. package/dist/mjs/rendering/caching/types.d.ts +1 -0
  57. package/dist/mjs/rendering/renderers/AbstractRenderer.d.ts +10 -0
  58. package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +12 -3
  59. package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +5 -2
  60. package/dist/mjs/rendering/renderers/SVGRenderer.d.ts +0 -10
  61. package/dist/mjs/rendering/renderers/SVGRenderer.mjs +0 -14
  62. package/dist/mjs/testing/startPinchGesture.d.ts +14 -0
  63. package/dist/mjs/testing/startPinchGesture.mjs +36 -0
  64. package/dist/mjs/tools/PanZoom.d.ts +4 -0
  65. package/dist/mjs/tools/PanZoom.mjs +21 -2
  66. package/dist/mjs/version.mjs +1 -1
  67. package/package.json +4 -4
  68. package/src/Editor.scss +2 -0
@@ -1,5 +1,4 @@
1
1
  import CacheRecord from './CacheRecord.mjs';
2
- const debugMode = false;
3
2
  export class CacheRecordManager {
4
3
  constructor(cacheProps) {
5
4
  // Fixed-size array: Cache blocks are assigned indicies into [cachedCanvases].
@@ -16,19 +15,19 @@ export class CacheRecordManager {
16
15
  const record = new CacheRecord(onDealloc, this.cacheState);
17
16
  record.setRenderingRegion(drawTo);
18
17
  this.cacheRecords.push(record);
19
- if (debugMode) {
18
+ if (this.cacheState.debugMode) {
20
19
  console.log('[Cache] Cache spaces used: ', this.cacheRecords.length, ' of ', this.maxCanvases);
21
20
  }
22
21
  return record;
23
22
  }
24
23
  else {
25
24
  const lru = this.getLeastRecentlyUsedRecord();
26
- if (debugMode) {
25
+ if (this.cacheState.debugMode) {
27
26
  console.log('[Cache] Re-alloc. Times allocated: ', lru.allocCount, '\nLast used cycle: ', lru.getLastUsedCycle(), '\nCurrent cycle: ', this.cacheState.currentRenderingCycle);
28
27
  }
29
28
  lru.realloc(onDealloc);
30
29
  lru.setRenderingRegion(drawTo);
31
- if (debugMode) {
30
+ if (this.cacheState.debugMode) {
32
31
  console.log('[Cache] Now re-alloc\'d. Last used cycle: ', lru.getLastUsedCycle());
33
32
  console.assert(lru['cacheState'] === this.cacheState, '[Cache] Unequal cache states! cacheState should be a shared object!');
34
33
  }
@@ -9,4 +9,5 @@ export default class RenderingCache {
9
9
  constructor(cacheProps: CacheProps);
10
10
  render(screenRenderer: AbstractRenderer, image: ImageNode, viewport: Viewport): void;
11
11
  getDebugInfo(): string;
12
+ setIsDebugMode(debugMode: boolean): void;
12
13
  }
@@ -8,6 +8,7 @@ export default class RenderingCache {
8
8
  props: cacheProps,
9
9
  currentRenderingCycle: 0,
10
10
  recordManager: this.recordManager,
11
+ debugMode: false,
11
12
  };
12
13
  this.recordManager.setSharedState(this.sharedState);
13
14
  }
@@ -44,4 +45,7 @@ export default class RenderingCache {
44
45
  getDebugInfo() {
45
46
  return this.recordManager.getDebugInfo();
46
47
  }
48
+ setIsDebugMode(debugMode) {
49
+ this.sharedState.debugMode = debugMode;
50
+ }
47
51
  }
@@ -1,10 +1,8 @@
1
1
  // A cache record with sub-nodes.
2
- import { sortLeavesByZIndex } from '../../image/EditorImage.mjs';
2
+ import { computeFirstIndexToRender, sortLeavesByZIndex } from '../../image/EditorImage.mjs';
3
3
  import { Rect2, Color4 } from '@js-draw/math';
4
4
  // 3x3 divisions for each node.
5
5
  const cacheDivisionSize = 3;
6
- // True: Show rendering updates.
7
- const debugMode = false;
8
6
  export default class RenderingCacheNode {
9
7
  constructor(region, cacheState) {
10
8
  this.region = region;
@@ -161,8 +159,8 @@ export default class RenderingCacheNode {
161
159
  items.forEach(item => item.render(screenRenderer, viewport.visibleRect));
162
160
  return;
163
161
  }
164
- if (debugMode) {
165
- screenRenderer.drawRect(this.region, 0.5 * viewport.getSizeOfPixelOnCanvas(), { fill: Color4.yellow });
162
+ if (this.cacheState.debugMode) {
163
+ screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: Color4.yellow });
166
164
  }
167
165
  // Could we render direclty from [this] or do we need to recurse?
168
166
  const couldRender = this.renderingWouldBeHighEnoughResolution(viewport);
@@ -197,7 +195,9 @@ export default class RenderingCacheNode {
197
195
  }
198
196
  let leafApproxRenderTime = 0;
199
197
  for (const leaf of leavesByIds) {
200
- leafApproxRenderTime += leaf.getContent().getProportionalRenderingTime();
198
+ if (!tooSmallToRender(leaf.getBBox())) {
199
+ leafApproxRenderTime += leaf.getContent().getProportionalRenderingTime();
200
+ }
201
201
  }
202
202
  // Is it worth it to render the items?
203
203
  if (leafApproxRenderTime > this.cacheState.props.minProportionalRenderTimePerCache) {
@@ -233,26 +233,30 @@ export default class RenderingCacheNode {
233
233
  this.renderedMaxZIndex = zIndex;
234
234
  }
235
235
  }
236
- if (debugMode) {
237
- screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: Color4.clay });
236
+ if (this.cacheState.debugMode) {
237
+ // Clay for adding new elements
238
+ screenRenderer.drawRect(this.region, 2 * viewport.getSizeOfPixelOnCanvas(), { fill: Color4.clay });
238
239
  }
239
240
  }
240
241
  }
241
- else if (debugMode) {
242
+ else if (this.cacheState.debugMode) {
242
243
  console.log('Decided on a full re-render. Reason: At least one of the following is false:', '\n leafIds.length > this.renderedIds.length: ', leafIds.length > this.renderedIds.length, '\n this.allRenderedIdsIn(leafIds): ', this.allRenderedIdsIn(leafIds), '\n this.renderedMaxZIndex !== null: ', this.renderedMaxZIndex !== null, '\n\nthis.rerenderedIds: ', this.renderedIds, ', leafIds: ', leafIds);
243
244
  }
244
245
  if (fullRerenderNeeded) {
245
246
  thisRenderer = this.cachedRenderer.startRender();
246
247
  thisRenderer.clear();
247
248
  this.renderedMaxZIndex = null;
248
- for (const leaf of leaves) {
249
+ const startIndex = computeFirstIndexToRender(leaves, this.region);
250
+ for (let i = startIndex; i < leaves.length; i++) {
251
+ const leaf = leaves[i];
249
252
  const content = leaf.getContent();
250
253
  this.renderedMaxZIndex ??= content.getZIndex();
251
254
  this.renderedMaxZIndex = Math.max(this.renderedMaxZIndex, content.getZIndex());
252
255
  leaf.render(thisRenderer, this.region);
253
256
  }
254
- if (debugMode) {
255
- screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: Color4.red });
257
+ if (this.cacheState.debugMode) {
258
+ // Red for full rerender
259
+ screenRenderer.drawRect(this.region, 3 * viewport.getSizeOfPixelOnCanvas(), { fill: Color4.red });
256
260
  }
257
261
  }
258
262
  this.renderedIds = leafIds;
@@ -269,6 +273,10 @@ export default class RenderingCacheNode {
269
273
  leaf.render(screenRenderer, this.region.intersection(viewport.visibleRect));
270
274
  }
271
275
  screenRenderer.endObject();
276
+ if (this.cacheState.debugMode) {
277
+ // Green for no cache needed render
278
+ screenRenderer.drawRect(this.region, 2 * viewport.getSizeOfPixelOnCanvas(), { fill: Color4.green });
279
+ }
272
280
  }
273
281
  }
274
282
  else {
@@ -16,4 +16,5 @@ export interface CacheState {
16
16
  currentRenderingCycle: number;
17
17
  props: CacheProps;
18
18
  recordManager: CacheRecordManager;
19
+ debugMode: boolean;
19
20
  }
@@ -20,6 +20,11 @@ export default abstract class AbstractRenderer {
20
20
  private selfTransform;
21
21
  private transformStack;
22
22
  protected constructor(viewport: Viewport);
23
+ /**
24
+ * this.canvasToScreen, etc. should be used instead of the corresponding
25
+ * methods on `Viewport`, because the viewport may not accurately reflect
26
+ * what is rendered.
27
+ */
23
28
  protected getViewport(): Viewport;
24
29
  abstract displaySize(): Vec2;
25
30
  abstract clear(): void;
@@ -73,5 +78,10 @@ export default abstract class AbstractRenderer {
73
78
  getCanvasToScreenTransform(): Mat33;
74
79
  canvasToScreen(vec: Vec2): Vec2;
75
80
  getSizeOfCanvasPixelOnScreen(): number;
81
+ private visibleRectOverride;
82
+ /**
83
+ * @internal
84
+ */
85
+ overrideVisibleRect(rect: Rect2 | null): void;
76
86
  getVisibleRect(): Rect2;
77
87
  }
@@ -15,8 +15,11 @@ export default class AbstractRenderer {
15
15
  this.objectLevel = 0;
16
16
  this.currentPaths = null;
17
17
  }
18
- // this.canvasToScreen, etc. should be used instead of the corresponding
19
- // methods on Viewport.
18
+ /**
19
+ * this.canvasToScreen, etc. should be used instead of the corresponding
20
+ * methods on `Viewport`, because the viewport may not accurately reflect
21
+ * what is rendered.
22
+ */
20
23
  getViewport() { return this.viewport; }
21
24
  setDraftMode(_draftMode) { }
22
25
  flushPath() {
@@ -158,12 +161,18 @@ export default class AbstractRenderer {
158
161
  getSizeOfCanvasPixelOnScreen() {
159
162
  return this.getCanvasToScreenTransform().transformVec3(Vec2.unitX).length();
160
163
  }
164
+ /**
165
+ * @internal
166
+ */
167
+ overrideVisibleRect(rect) {
168
+ this.visibleRectOverride = rect;
169
+ }
161
170
  // Returns the region in canvas space that is visible within the viewport this
162
171
  // canvas is rendering to.
163
172
  //
164
173
  // Note that in some cases this might not be the same as the `visibleRect` given
165
174
  // to components in their `render` method.
166
175
  getVisibleRect() {
167
- return this.viewport.visibleRect;
176
+ return this.visibleRectOverride ?? this.viewport.visibleRect;
168
177
  }
169
178
  }
@@ -77,7 +77,10 @@ export default class CanvasRenderer extends AbstractRenderer {
77
77
  return Vec2.of(this.ctx.canvas.clientWidth, this.ctx.canvas.clientHeight);
78
78
  }
79
79
  clear() {
80
+ this.ctx.save();
81
+ this.ctx.resetTransform();
80
82
  this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
83
+ this.ctx.restore();
81
84
  }
82
85
  beginPath(startPoint) {
83
86
  startPoint = this.canvasToScreen(startPoint);
@@ -144,7 +147,7 @@ export default class CanvasRenderer extends AbstractRenderer {
144
147
  return;
145
148
  }
146
149
  // If part of a huge object, it might be worth trimming the path
147
- const visibleRect = this.getViewport().visibleRect;
150
+ const visibleRect = this.getVisibleRect();
148
151
  if (this.currentObjectBBox?.containsRect(visibleRect)) {
149
152
  // Try to trim/remove parts of the path outside of the bounding box.
150
153
  path = visualEquivalent(path, visibleRect);
@@ -184,7 +187,7 @@ export default class CanvasRenderer extends AbstractRenderer {
184
187
  if (!this.ignoringObject && clip) {
185
188
  // Don't clip if it would only remove content already trimmed by
186
189
  // the edge of the screen.
187
- const clippedIsOutsideScreen = boundingBox.containsRect(this.getViewport().visibleRect);
190
+ const clippedIsOutsideScreen = boundingBox.containsRect(this.getVisibleRect());
188
191
  if (!clippedIsOutsideScreen) {
189
192
  this.clipLevels.push(this.objectLevel);
190
193
  this.ctx.save();
@@ -59,16 +59,6 @@ export default class SVGRenderer extends AbstractRenderer {
59
59
  drawPoints(...points: Point2[]): void;
60
60
  drawSVGElem(elem: SVGElement): void;
61
61
  isTooSmallToRender(_rect: Rect2): boolean;
62
- private visibleRectOverride;
63
- /**
64
- * Overrides the visible region returned by `getVisibleRect`.
65
- *
66
- * This is useful when the `viewport`'s transform has been modified,
67
- * for example, to compensate for storing part of the image's
68
- * transformation in an SVG property.
69
- */
70
- private overrideVisibleRect;
71
- getVisibleRect(): Rect2;
72
62
  /**
73
63
  * Creates a new SVG element and `SVGRenerer` with `width`, `height`, `viewBox`,
74
64
  * and other metadata attributes set for the given `Viewport`.
@@ -36,7 +36,6 @@ export default class SVGRenderer extends AbstractRenderer {
36
36
  this.textContainer = null;
37
37
  this.textContainerTransform = null;
38
38
  this.textParentStyle = defaultTextStyle;
39
- this.visibleRectOverride = null;
40
39
  this.clear();
41
40
  this.addStyleSheet();
42
41
  }
@@ -339,19 +338,6 @@ export default class SVGRenderer extends AbstractRenderer {
339
338
  isTooSmallToRender(_rect) {
340
339
  return false;
341
340
  }
342
- /**
343
- * Overrides the visible region returned by `getVisibleRect`.
344
- *
345
- * This is useful when the `viewport`'s transform has been modified,
346
- * for example, to compensate for storing part of the image's
347
- * transformation in an SVG property.
348
- */
349
- overrideVisibleRect(newRect) {
350
- this.visibleRectOverride = newRect;
351
- }
352
- getVisibleRect() {
353
- return this.visibleRectOverride ?? super.getVisibleRect();
354
- }
355
341
  /**
356
342
  * Creates a new SVG element and `SVGRenerer` with `width`, `height`, `viewBox`,
357
343
  * and other metadata attributes set for the given `Viewport`.
@@ -0,0 +1,14 @@
1
+ import type Editor from '../Editor';
2
+ import { Point2 } from '@js-draw/math';
3
+ /**
4
+ * Creates two pointers and sends the touch {@link InputEvtType.PointerDownEvt}s for them.
5
+ *
6
+ * Returns an object that allows continuing or ending the gesture.
7
+ *
8
+ * `initialRotation` should be in radians.
9
+ */
10
+ declare const startPinchGesture: (editor: Editor, center: Point2, initialDistance: number, initialRotation: number) => {
11
+ update(center: Point2, distance: number, rotation: number): void;
12
+ end(): void;
13
+ };
14
+ export default startPinchGesture;
@@ -0,0 +1,36 @@
1
+ import { Mat33, Vec2 } from '@js-draw/math';
2
+ import sendTouchEvent from './sendTouchEvent.mjs';
3
+ import { InputEvtType } from '../inputEvents.mjs';
4
+ /**
5
+ * Creates two pointers and sends the touch {@link InputEvtType.PointerDownEvt}s for them.
6
+ *
7
+ * Returns an object that allows continuing or ending the gesture.
8
+ *
9
+ * `initialRotation` should be in radians.
10
+ */
11
+ const startPinchGesture = (editor, center, initialDistance, initialRotation) => {
12
+ const computeTouchPoints = (center, distance, rotation) => {
13
+ const halfDisplacement = Mat33.zRotation(rotation).transformVec2(Vec2.of(0, distance / 2));
14
+ const point1 = center.plus(halfDisplacement);
15
+ const point2 = center.minus(halfDisplacement);
16
+ return [point1, point2];
17
+ };
18
+ let [touchPoint1, touchPoint2] = computeTouchPoints(center, initialDistance, initialRotation);
19
+ let firstPointer = sendTouchEvent(editor, InputEvtType.PointerDownEvt, touchPoint1);
20
+ let secondPointer = sendTouchEvent(editor, InputEvtType.PointerDownEvt, touchPoint2, [firstPointer]);
21
+ return {
22
+ update(center, distance, rotation) {
23
+ const eventType = InputEvtType.PointerMoveEvt;
24
+ const [newPoint1, newPoint2] = computeTouchPoints(center, distance, rotation);
25
+ touchPoint1 = newPoint1;
26
+ touchPoint2 = newPoint2;
27
+ firstPointer = sendTouchEvent(editor, eventType, newPoint1, [secondPointer]);
28
+ secondPointer = sendTouchEvent(editor, eventType, newPoint2, [firstPointer]);
29
+ },
30
+ end() {
31
+ sendTouchEvent(editor, InputEvtType.PointerUpEvt, touchPoint1, [secondPointer]);
32
+ sendTouchEvent(editor, InputEvtType.PointerUpEvt, touchPoint2);
33
+ },
34
+ };
35
+ };
36
+ export default startPinchGesture;
@@ -21,6 +21,9 @@ export default class PanZoom extends BaseTool {
21
21
  private editor;
22
22
  private mode;
23
23
  private transform;
24
+ private readonly initialRotationSnapAngle;
25
+ private readonly afterRotationStartSnapAngle;
26
+ private readonly pinchZoomStartThreshold;
24
27
  private startDist;
25
28
  private lastDist;
26
29
  private lastScreenCenter;
@@ -29,6 +32,7 @@ export default class PanZoom extends BaseTool {
29
32
  private initialTouchAngle;
30
33
  private initialViewportRotation;
31
34
  private isScaling;
35
+ private isRotating;
32
36
  private inertialScroller;
33
37
  private velocity;
34
38
  constructor(editor: Editor, mode: PanZoomMode, description: string);
@@ -65,12 +65,19 @@ export default class PanZoom extends BaseTool {
65
65
  this.editor = editor;
66
66
  this.mode = mode;
67
67
  this.transform = null;
68
+ // Constants
69
+ // initialRotationSnapAngle is larger than afterRotationStartSnapAngle to
70
+ // make it more difficult to start rotating (and easier to continue rotating).
71
+ this.initialRotationSnapAngle = 0.22; // radians
72
+ this.afterRotationStartSnapAngle = 0.07; // radians
73
+ this.pinchZoomStartThreshold = 1.08; // scale factor
68
74
  this.lastPointerDownTimestamp = 0;
69
75
  this.initialTouchAngle = 0;
70
76
  this.initialViewportRotation = 0;
71
77
  // Set to `true` only when scaling has started (if two fingers are down and have moved
72
78
  // far enough).
73
79
  this.isScaling = false;
80
+ this.isRotating = false;
74
81
  this.inertialScroller = null;
75
82
  this.velocity = null;
76
83
  }
@@ -112,6 +119,9 @@ export default class PanZoom extends BaseTool {
112
119
  this.initialTouchAngle = angle;
113
120
  this.initialViewportRotation = this.editor.viewport.getRotationAngle();
114
121
  this.isScaling = false;
122
+ // We're initially rotated if `initialViewportRotation` isn't near a multiple of pi/2.
123
+ // In other words, if sin(2 initialViewportRotation) is near zero.
124
+ this.isRotating = Math.abs(Math.sin(this.initialViewportRotation * 2)) > 1e-3;
115
125
  handlingGesture = true;
116
126
  }
117
127
  else if (pointers.length === 1 && ((this.mode & PanZoomMode.OneFingerTouchGestures && allAreTouch)
@@ -168,7 +178,9 @@ export default class PanZoom extends BaseTool {
168
178
  const roundedFullRotation = Math.round(fullRotation / snapToMultipleOf) * snapToMultipleOf;
169
179
  // The maximum angle for which we snap the given angle to a multiple of
170
180
  // `snapToMultipleOf`.
171
- const maxSnapAngle = 0.07;
181
+ // Use a smaller snap angle if already rotated (to avoid pinch zoom gestures from
182
+ // starting rotation).
183
+ const maxSnapAngle = this.isRotating ? this.afterRotationStartSnapAngle : this.initialRotationSnapAngle;
172
184
  // Snap the rotation
173
185
  if (Math.abs(fullRotation - roundedFullRotation) < maxSnapAngle) {
174
186
  fullRotation = roundedFullRotation;
@@ -191,6 +203,11 @@ export default class PanZoom extends BaseTool {
191
203
  else {
192
204
  deltaRotation = this.toSnappedRotationDelta(angle);
193
205
  }
206
+ // If any rotation, make a note of this (affects rotation snap
207
+ // angles).
208
+ if (Math.abs(deltaRotation) > 1e-8) {
209
+ this.isRotating = true;
210
+ }
194
211
  this.updateVelocity(screenCenter);
195
212
  let scaleFactor = 1;
196
213
  if (this.isScaling) {
@@ -199,7 +216,9 @@ export default class PanZoom extends BaseTool {
199
216
  else {
200
217
  const initialScaleFactor = dist / this.startDist;
201
218
  // Only start scaling if scaling done so far exceeds some threshold.
202
- if (initialScaleFactor > 1.05 || initialScaleFactor < 0.95) {
219
+ const upperBound = this.pinchZoomStartThreshold;
220
+ const lowerBound = 1 / this.pinchZoomStartThreshold;
221
+ if (initialScaleFactor > upperBound || initialScaleFactor < lowerBound) {
203
222
  scaleFactor = initialScaleFactor;
204
223
  this.isScaling = true;
205
224
  }
@@ -1,3 +1,3 @@
1
1
  export default {
2
- number: '1.7.2',
2
+ number: '1.9.0',
3
3
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "1.7.2",
3
+ "version": "1.9.0",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -55,7 +55,7 @@
55
55
  "license": "MIT",
56
56
  "private": false,
57
57
  "scripts": {
58
- "dist-test": "npm run build && cd dist-test/test_imports && npm install && npm run test",
58
+ "dist-test": "cd dist-test/test_imports && npm install && npm run test",
59
59
  "dist": "npm run build && npm run dist-test",
60
60
  "build": "rm -rf ./dist/* && mkdir -p dist && build-tool build",
61
61
  "watch": "rm -rf ./dist/* && mkdir -p dist && build-tool watch",
@@ -64,7 +64,7 @@
64
64
  "postpack": "ts-node tools/copyREADME.ts revert"
65
65
  },
66
66
  "dependencies": {
67
- "@js-draw/math": "^1.7.0",
67
+ "@js-draw/math": "^1.9.0",
68
68
  "@melloware/coloris": "0.21.0"
69
69
  },
70
70
  "devDependencies": {
@@ -86,5 +86,5 @@
86
86
  "freehand",
87
87
  "svg"
88
88
  ],
89
- "gitHead": "e1677b6e40bd385e1ce40643fd10f064847adf5b"
89
+ "gitHead": "e824c37e9f216852cf096976e3a74fb4f177ead3"
90
90
  }
package/src/Editor.scss CHANGED
@@ -118,6 +118,8 @@
118
118
  text-align: center;
119
119
  font-size: 2em;
120
120
 
121
+ text-shadow: 0px 0px 1px var(--background-color-1);
122
+
121
123
  bottom: 0;
122
124
  left: 0;
123
125
  right: 0;