js-draw 1.8.0 → 1.9.1

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 (35) hide show
  1. package/LICENSE +1 -1
  2. package/dist/bundle.js +2 -2
  3. package/dist/cjs/Editor.js +24 -13
  4. package/dist/cjs/SVGLoader.d.ts +1 -1
  5. package/dist/cjs/SVGLoader.js +11 -2
  6. package/dist/cjs/Viewport.d.ts +6 -0
  7. package/dist/cjs/Viewport.js +6 -1
  8. package/dist/cjs/image/EditorImage.d.ts +20 -0
  9. package/dist/cjs/image/EditorImage.js +40 -11
  10. package/dist/cjs/rendering/Display.d.ts +9 -0
  11. package/dist/cjs/rendering/Display.js +47 -6
  12. package/dist/cjs/rendering/caching/CacheRecordManager.js +3 -4
  13. package/dist/cjs/rendering/caching/RenderingCache.d.ts +1 -0
  14. package/dist/cjs/rendering/caching/RenderingCache.js +4 -0
  15. package/dist/cjs/rendering/caching/RenderingCacheNode.js +19 -11
  16. package/dist/cjs/rendering/caching/types.d.ts +1 -0
  17. package/dist/cjs/rendering/renderers/CanvasRenderer.js +3 -0
  18. package/dist/cjs/version.js +1 -1
  19. package/dist/mjs/Editor.mjs +24 -13
  20. package/dist/mjs/SVGLoader.d.ts +1 -1
  21. package/dist/mjs/SVGLoader.mjs +11 -2
  22. package/dist/mjs/Viewport.d.ts +6 -0
  23. package/dist/mjs/Viewport.mjs +6 -1
  24. package/dist/mjs/image/EditorImage.d.ts +20 -0
  25. package/dist/mjs/image/EditorImage.mjs +38 -10
  26. package/dist/mjs/rendering/Display.d.ts +9 -0
  27. package/dist/mjs/rendering/Display.mjs +47 -6
  28. package/dist/mjs/rendering/caching/CacheRecordManager.mjs +3 -4
  29. package/dist/mjs/rendering/caching/RenderingCache.d.ts +1 -0
  30. package/dist/mjs/rendering/caching/RenderingCache.mjs +4 -0
  31. package/dist/mjs/rendering/caching/RenderingCacheNode.mjs +20 -12
  32. package/dist/mjs/rendering/caching/types.d.ts +1 -0
  33. package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +3 -0
  34. package/dist/mjs/version.mjs +1 -1
  35. package/package.json +4 -4
@@ -50,6 +50,11 @@ export declare class Viewport {
50
50
  private getScaleFactorToNearestPowerOf;
51
51
  /** Returns the size of a grid cell (in canvas units) as used by {@link snapToGrid}. */
52
52
  static getGridSize(scaleFactor: number): number;
53
+ /**
54
+ * Snaps `canvasPos` to the nearest grid cell corner.
55
+ *
56
+ * @see {@link getGridSize} and {@link getScaleFactorToNearestPowerOf}.
57
+ */
53
58
  snapToGrid(canvasPos: Point2): Vec3;
54
59
  /** Returns the size of one screen pixel in canvas units. */
55
60
  getSizeOfPixelOnCanvas(): number;
@@ -65,6 +70,7 @@ export declare class Viewport {
65
70
  * its original location. This is useful for preparing data for base-10 conversion.
66
71
  */
67
72
  static roundPoint<T extends Point2 | number>(point: T, tolerance: number): PointDataType<T>;
73
+ /** Round a point with a tolerance of ±1 screen unit. */
68
74
  roundPoint(point: Point2): Point2;
69
75
  static roundScaleRatio(scaleRatio: number, roundAmount?: number): number;
70
76
  computeZoomToTransform(toMakeVisible: Rect2, allowZoomIn?: boolean, allowZoomOut?: boolean): Mat33;
@@ -97,6 +97,11 @@ export class Viewport {
97
97
  static getGridSize(scaleFactor) {
98
98
  return 50 / scaleFactor;
99
99
  }
100
+ /**
101
+ * Snaps `canvasPos` to the nearest grid cell corner.
102
+ *
103
+ * @see {@link getGridSize} and {@link getScaleFactorToNearestPowerOf}.
104
+ */
100
105
  snapToGrid(canvasPos) {
101
106
  const scaleFactor = this.getScaleFactorToNearestPowerOf(2);
102
107
  const snapCoordinate = (coordinate) => {
@@ -133,7 +138,7 @@ export class Viewport {
133
138
  }
134
139
  return point.map(roundComponent);
135
140
  }
136
- // Round a point with a tolerance of ±1 screen unit.
141
+ /** Round a point with a tolerance of ±1 screen unit. */
137
142
  roundPoint(point) {
138
143
  return Viewport.roundPoint(point, 1 / this.getScaleFactor());
139
144
  }
@@ -123,8 +123,28 @@ export default class EditorImage {
123
123
  */
124
124
  private setExportRectDirectly;
125
125
  private onExportViewportChanged;
126
+ /**
127
+ * @internal
128
+ *
129
+ * Enables debug mode for **all** `EditorImage`s.
130
+ *
131
+ * **Only use for debugging**.
132
+ *
133
+ * @internal
134
+ */
135
+ setDebugMode(newDebugMode: boolean): void;
126
136
  private static SetImportExportRectCommand;
127
137
  }
138
+ /**
139
+ * Determines the first index in `sortedLeaves` that needs to be rendered
140
+ * (based on occlusion -- everything before that index can be skipped and
141
+ * produce a visually-equivalent image).
142
+ *
143
+ * Does nothing if visibleRect is not provided
144
+ *
145
+ * @internal
146
+ */
147
+ export declare const computeFirstIndexToRender: (sortedLeaves: Array<ImageNode>, visibleRect?: Rect2) => number;
128
148
  type TooSmallToRenderCheck = (rect: Rect2) => boolean;
129
149
  /**
130
150
  * Part of the Editor's image. Does not handle fullscreen/invisible components.
@@ -19,7 +19,7 @@ export var EditorImageEventType;
19
19
  EditorImageEventType[EditorImageEventType["ExportViewportChanged"] = 0] = "ExportViewportChanged";
20
20
  EditorImageEventType[EditorImageEventType["AutoresizeModeChanged"] = 1] = "AutoresizeModeChanged";
21
21
  })(EditorImageEventType || (EditorImageEventType = {}));
22
- const debugMode = false;
22
+ let debugMode = false;
23
23
  // Handles lookup/storage of elements in the image
24
24
  class EditorImage {
25
25
  // @internal
@@ -271,6 +271,18 @@ class EditorImage {
271
271
  });
272
272
  }
273
273
  }
274
+ /**
275
+ * @internal
276
+ *
277
+ * Enables debug mode for **all** `EditorImage`s.
278
+ *
279
+ * **Only use for debugging**.
280
+ *
281
+ * @internal
282
+ */
283
+ setDebugMode(newDebugMode) {
284
+ debugMode = newDebugMode;
285
+ }
274
286
  }
275
287
  _a = EditorImage;
276
288
  // A Command that can access private [EditorImage] functionality
@@ -410,6 +422,30 @@ EditorImage.SetImportExportRectCommand = (_c = class extends SerializableCommand
410
422
  })(),
411
423
  _c);
412
424
  export default EditorImage;
425
+ /**
426
+ * Determines the first index in `sortedLeaves` that needs to be rendered
427
+ * (based on occlusion -- everything before that index can be skipped and
428
+ * produce a visually-equivalent image).
429
+ *
430
+ * Does nothing if visibleRect is not provided
431
+ *
432
+ * @internal
433
+ */
434
+ export const computeFirstIndexToRender = (sortedLeaves, visibleRect) => {
435
+ let startIndex = 0;
436
+ if (visibleRect) {
437
+ for (let i = sortedLeaves.length - 1; i >= 1; i--) {
438
+ if (
439
+ // Check for occlusion
440
+ sortedLeaves[i].getBBox().containsRect(visibleRect)
441
+ && sortedLeaves[i].getContent()?.occludesEverythingBelowWhenRenderedInRect(visibleRect)) {
442
+ startIndex = i;
443
+ break;
444
+ }
445
+ }
446
+ }
447
+ return startIndex;
448
+ };
413
449
  /**
414
450
  * Part of the Editor's image. Does not handle fullscreen/invisible components.
415
451
  * @internal
@@ -666,15 +702,7 @@ export class ImageNode {
666
702
  // If some components hide others (and we're permitted to simplify,
667
703
  // which is true in the case of visibleRect being defined), then only
668
704
  // draw the non-hidden components:
669
- let startIndex = 0;
670
- if (visibleRect) {
671
- for (let i = leaves.length - 1; i >= 1; i--) {
672
- if (leaves[i].getContent()?.occludesEverythingBelowWhenRenderedInRect(visibleRect)) {
673
- startIndex = i;
674
- break;
675
- }
676
- }
677
- }
705
+ const startIndex = computeFirstIndexToRender(leaves);
678
706
  for (let i = startIndex; i < leaves.length; i++) {
679
707
  const leaf = leaves[i];
680
708
  // Leaves by definition have content
@@ -26,6 +26,7 @@ export default class Display {
26
26
  private textRenderer;
27
27
  private textRerenderOutput;
28
28
  private cache;
29
+ private devicePixelRatio;
29
30
  private resizeSurfacesCallback?;
30
31
  private flattenCallback?;
31
32
  /** @internal */
@@ -46,6 +47,14 @@ export default class Display {
46
47
  getColorAt: (_screenPos: Point2) => Color4 | null;
47
48
  private initializeCanvasRendering;
48
49
  private initializeTextRendering;
50
+ /**
51
+ * Sets the device-pixel-ratio.
52
+ *
53
+ * Intended for debugging. Users do not need to call this manually.
54
+ *
55
+ * @internal
56
+ */
57
+ setDevicePixelRatio(dpr: number): Promise<void> | undefined;
49
58
  /**
50
59
  * Rerenders the text-based display.
51
60
  * The text-based display is intended for screen readers and can be navigated to by pressing `tab`.
@@ -28,6 +28,7 @@ export default class Display {
28
28
  this.editor = editor;
29
29
  this.parent = parent;
30
30
  this.textRerenderOutput = null;
31
+ this.devicePixelRatio = window.devicePixelRatio ?? 1;
31
32
  /**
32
33
  * @returns the color at the given point on the dry ink renderer, or `null` if `screenPos`
33
34
  * is not on the display.
@@ -112,16 +113,32 @@ export default class Display {
112
113
  this.parent.appendChild(wetInkCanvas);
113
114
  }
114
115
  this.resizeSurfacesCallback = () => {
116
+ const expectedWidth = (canvas) => {
117
+ return Math.ceil(canvas.clientWidth * this.devicePixelRatio);
118
+ };
119
+ const expectedHeight = (canvas) => {
120
+ return Math.ceil(canvas.clientHeight * this.devicePixelRatio);
121
+ };
115
122
  const hasSizeMismatch = (canvas) => {
116
- return canvas.clientHeight !== canvas.height || canvas.clientWidth !== canvas.width;
123
+ return expectedHeight(canvas) !== canvas.height || expectedWidth(canvas) !== canvas.width;
117
124
  };
118
125
  // Ensure that the drawing surfaces sizes match the
119
126
  // canvas' sizes to prevent stretching.
120
127
  if (hasSizeMismatch(dryInkCanvas) || hasSizeMismatch(wetInkCanvas)) {
121
- dryInkCanvas.width = dryInkCanvas.clientWidth;
122
- dryInkCanvas.height = dryInkCanvas.clientHeight;
123
- wetInkCanvas.width = wetInkCanvas.clientWidth;
124
- wetInkCanvas.height = wetInkCanvas.clientHeight;
128
+ dryInkCanvas.width = expectedWidth(dryInkCanvas);
129
+ dryInkCanvas.height = expectedHeight(dryInkCanvas);
130
+ wetInkCanvas.width = expectedWidth(wetInkCanvas);
131
+ wetInkCanvas.height = expectedHeight(wetInkCanvas);
132
+ // Ensure correct drawing operations on high-resolution screens.
133
+ // See
134
+ // https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas#scaling_for_high_resolution_displays
135
+ //
136
+ // This scaling causes the rendering contexts to automatically convert
137
+ // between screen coordinates and pixel coordinates.
138
+ wetInkCtx.resetTransform();
139
+ dryInkCtx.resetTransform();
140
+ dryInkCtx.scale(this.devicePixelRatio, this.devicePixelRatio);
141
+ wetInkCtx.scale(this.devicePixelRatio, this.devicePixelRatio);
125
142
  this.editor.notifier.dispatch(EditorEventType.DisplayResized, {
126
143
  kind: EditorEventType.DisplayResized,
127
144
  newSize: Vec2.of(this.width, this.height),
@@ -130,10 +147,17 @@ export default class Display {
130
147
  };
131
148
  this.resizeSurfacesCallback();
132
149
  this.flattenCallback = () => {
150
+ dryInkCtx.save();
151
+ dryInkCtx.resetTransform();
133
152
  dryInkCtx.drawImage(wetInkCanvas, 0, 0);
153
+ dryInkCtx.restore();
134
154
  };
135
155
  this.getColorAt = (screenPos) => {
136
- const pixel = dryInkCtx.getImageData(screenPos.x, screenPos.y, 1, 1);
156
+ // getImageData isn't affected by a transformation matrix -- we need to
157
+ // pre-transform screenPos to convert it from screen coordinates into pixel
158
+ // coordinates.
159
+ const adjustedScreenPos = screenPos.times(this.devicePixelRatio);
160
+ const pixel = dryInkCtx.getImageData(adjustedScreenPos.x, adjustedScreenPos.y, 1, 1);
137
161
  const data = pixel?.data;
138
162
  if (data) {
139
163
  const color = Color4.ofRGBA(data[0] / 255, data[1] / 255, data[2] / 255, data[3] / 255);
@@ -156,6 +180,23 @@ export default class Display {
156
180
  textRendererOutputContainer.replaceChildren(rerenderButton, this.textRerenderOutput);
157
181
  this.editor.createHTMLOverlay(textRendererOutputContainer);
158
182
  }
183
+ /**
184
+ * Sets the device-pixel-ratio.
185
+ *
186
+ * Intended for debugging. Users do not need to call this manually.
187
+ *
188
+ * @internal
189
+ */
190
+ setDevicePixelRatio(dpr) {
191
+ const minDpr = 0.001;
192
+ const maxDpr = 10;
193
+ if (isFinite(dpr) && dpr >= minDpr && dpr <= maxDpr && dpr !== this.devicePixelRatio) {
194
+ this.devicePixelRatio = dpr;
195
+ this.resizeSurfacesCallback?.();
196
+ return this.editor.queueRerender();
197
+ }
198
+ return undefined;
199
+ }
159
200
  /**
160
201
  * Rerenders the text-based display.
161
202
  * The text-based display is intended for screen readers and can be navigated to by pressing `tab`.
@@ -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
  }
@@ -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);
@@ -1,3 +1,3 @@
1
1
  export default {
2
- number: '1.8.0',
2
+ number: '1.9.1',
3
3
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
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.8.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": "89f7991833dec5c17ce0890321286198ff7b1900"
89
+ "gitHead": "b4bae7b437b2ce4ba8378a062b8e3959dca0f26e"
90
90
  }