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.
- package/LICENSE +1 -1
- package/dist/Editor.css +1 -0
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/EventDispatcher.js +2 -1
- package/dist/cjs/SVGLoader.d.ts +1 -1
- package/dist/cjs/SVGLoader.js +11 -2
- package/dist/cjs/Viewport.d.ts +2 -0
- package/dist/cjs/Viewport.js +2 -0
- package/dist/cjs/components/AbstractComponent.d.ts +9 -0
- package/dist/cjs/components/AbstractComponent.js +11 -0
- package/dist/cjs/components/Stroke.d.ts +3 -0
- package/dist/cjs/components/Stroke.js +55 -1
- package/dist/cjs/image/EditorImage.d.ts +20 -0
- package/dist/cjs/image/EditorImage.js +48 -3
- package/dist/cjs/rendering/Display.d.ts +9 -0
- package/dist/cjs/rendering/Display.js +39 -5
- package/dist/cjs/rendering/RenderablePathSpec.d.ts +20 -2
- package/dist/cjs/rendering/RenderablePathSpec.js +72 -9
- package/dist/cjs/rendering/caching/CacheRecord.js +5 -3
- package/dist/cjs/rendering/caching/CacheRecordManager.js +3 -4
- package/dist/cjs/rendering/caching/RenderingCache.d.ts +1 -0
- package/dist/cjs/rendering/caching/RenderingCache.js +4 -0
- package/dist/cjs/rendering/caching/RenderingCacheNode.js +19 -11
- package/dist/cjs/rendering/caching/types.d.ts +1 -0
- package/dist/cjs/rendering/renderers/AbstractRenderer.d.ts +10 -0
- package/dist/cjs/rendering/renderers/AbstractRenderer.js +12 -3
- package/dist/cjs/rendering/renderers/CanvasRenderer.js +5 -2
- package/dist/cjs/rendering/renderers/SVGRenderer.d.ts +0 -10
- package/dist/cjs/rendering/renderers/SVGRenderer.js +0 -14
- package/dist/cjs/testing/startPinchGesture.d.ts +14 -0
- package/dist/cjs/testing/startPinchGesture.js +41 -0
- package/dist/cjs/tools/PanZoom.d.ts +4 -0
- package/dist/cjs/tools/PanZoom.js +21 -2
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/EventDispatcher.mjs +2 -1
- package/dist/mjs/SVGLoader.d.ts +1 -1
- package/dist/mjs/SVGLoader.mjs +11 -2
- package/dist/mjs/Viewport.d.ts +2 -0
- package/dist/mjs/Viewport.mjs +2 -0
- package/dist/mjs/components/AbstractComponent.d.ts +9 -0
- package/dist/mjs/components/AbstractComponent.mjs +11 -0
- package/dist/mjs/components/Stroke.d.ts +3 -0
- package/dist/mjs/components/Stroke.mjs +56 -2
- package/dist/mjs/image/EditorImage.d.ts +20 -0
- package/dist/mjs/image/EditorImage.mjs +46 -2
- package/dist/mjs/rendering/Display.d.ts +9 -0
- package/dist/mjs/rendering/Display.mjs +39 -5
- package/dist/mjs/rendering/RenderablePathSpec.d.ts +20 -2
- package/dist/mjs/rendering/RenderablePathSpec.mjs +71 -9
- package/dist/mjs/rendering/caching/CacheRecord.mjs +5 -3
- package/dist/mjs/rendering/caching/CacheRecordManager.mjs +3 -4
- package/dist/mjs/rendering/caching/RenderingCache.d.ts +1 -0
- package/dist/mjs/rendering/caching/RenderingCache.mjs +4 -0
- package/dist/mjs/rendering/caching/RenderingCacheNode.mjs +20 -12
- package/dist/mjs/rendering/caching/types.d.ts +1 -0
- package/dist/mjs/rendering/renderers/AbstractRenderer.d.ts +10 -0
- package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +12 -3
- package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +5 -2
- package/dist/mjs/rendering/renderers/SVGRenderer.d.ts +0 -10
- package/dist/mjs/rendering/renderers/SVGRenderer.mjs +0 -14
- package/dist/mjs/testing/startPinchGesture.d.ts +14 -0
- package/dist/mjs/testing/startPinchGesture.mjs +36 -0
- package/dist/mjs/tools/PanZoom.d.ts +4 -0
- package/dist/mjs/tools/PanZoom.mjs +21 -2
- package/dist/mjs/version.mjs +1 -1
- package/package.json +4 -4
- package/src/Editor.scss +2 -0
@@ -34,6 +34,7 @@ class Display {
|
|
34
34
|
this.editor = editor;
|
35
35
|
this.parent = parent;
|
36
36
|
this.textRerenderOutput = null;
|
37
|
+
this.devicePixelRatio = window.devicePixelRatio ?? 1;
|
37
38
|
/**
|
38
39
|
* @returns the color at the given point on the dry ink renderer, or `null` if `screenPos`
|
39
40
|
* is not on the display.
|
@@ -118,16 +119,29 @@ class Display {
|
|
118
119
|
this.parent.appendChild(wetInkCanvas);
|
119
120
|
}
|
120
121
|
this.resizeSurfacesCallback = () => {
|
122
|
+
const expectedWidth = (canvas) => {
|
123
|
+
return Math.ceil(canvas.clientWidth * this.devicePixelRatio);
|
124
|
+
};
|
125
|
+
const expectedHeight = (canvas) => {
|
126
|
+
return Math.ceil(canvas.clientHeight * this.devicePixelRatio);
|
127
|
+
};
|
121
128
|
const hasSizeMismatch = (canvas) => {
|
122
|
-
return canvas
|
129
|
+
return expectedHeight(canvas) !== canvas.height || expectedWidth(canvas) !== canvas.width;
|
123
130
|
};
|
124
131
|
// Ensure that the drawing surfaces sizes match the
|
125
132
|
// canvas' sizes to prevent stretching.
|
126
133
|
if (hasSizeMismatch(dryInkCanvas) || hasSizeMismatch(wetInkCanvas)) {
|
127
|
-
dryInkCanvas.width = dryInkCanvas
|
128
|
-
dryInkCanvas.height = dryInkCanvas
|
129
|
-
wetInkCanvas.width = wetInkCanvas
|
130
|
-
wetInkCanvas.height = wetInkCanvas
|
134
|
+
dryInkCanvas.width = expectedWidth(dryInkCanvas);
|
135
|
+
dryInkCanvas.height = expectedHeight(dryInkCanvas);
|
136
|
+
wetInkCanvas.width = expectedWidth(wetInkCanvas);
|
137
|
+
wetInkCanvas.height = expectedHeight(wetInkCanvas);
|
138
|
+
// Ensure correct drawing operations on high-resolution screens.
|
139
|
+
// See
|
140
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas#scaling_for_high_resolution_displays
|
141
|
+
wetInkCtx.resetTransform();
|
142
|
+
dryInkCtx.resetTransform();
|
143
|
+
dryInkCtx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
144
|
+
wetInkCtx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
131
145
|
this.editor.notifier.dispatch(types_1.EditorEventType.DisplayResized, {
|
132
146
|
kind: types_1.EditorEventType.DisplayResized,
|
133
147
|
newSize: math_1.Vec2.of(this.width, this.height),
|
@@ -136,7 +150,10 @@ class Display {
|
|
136
150
|
};
|
137
151
|
this.resizeSurfacesCallback();
|
138
152
|
this.flattenCallback = () => {
|
153
|
+
dryInkCtx.save();
|
154
|
+
dryInkCtx.resetTransform();
|
139
155
|
dryInkCtx.drawImage(wetInkCanvas, 0, 0);
|
156
|
+
dryInkCtx.restore();
|
140
157
|
};
|
141
158
|
this.getColorAt = (screenPos) => {
|
142
159
|
const pixel = dryInkCtx.getImageData(screenPos.x, screenPos.y, 1, 1);
|
@@ -162,6 +179,23 @@ class Display {
|
|
162
179
|
textRendererOutputContainer.replaceChildren(rerenderButton, this.textRerenderOutput);
|
163
180
|
this.editor.createHTMLOverlay(textRendererOutputContainer);
|
164
181
|
}
|
182
|
+
/**
|
183
|
+
* Sets the device-pixel-ratio.
|
184
|
+
*
|
185
|
+
* Intended for debugging. Users do not need to call this manually.
|
186
|
+
*
|
187
|
+
* @internal
|
188
|
+
*/
|
189
|
+
setDevicePixelRatio(dpr) {
|
190
|
+
const minDpr = 0.001;
|
191
|
+
const maxDpr = 10;
|
192
|
+
if (isFinite(dpr) && dpr >= minDpr && dpr <= maxDpr && dpr !== this.devicePixelRatio) {
|
193
|
+
this.devicePixelRatio = dpr;
|
194
|
+
this.resizeSurfacesCallback?.();
|
195
|
+
return this.editor.queueRerender();
|
196
|
+
}
|
197
|
+
return undefined;
|
198
|
+
}
|
165
199
|
/**
|
166
200
|
* Rerenders the text-based display.
|
167
201
|
* The text-based display is intended for screen readers and can be navigated to by pressing `tab`.
|
@@ -6,11 +6,29 @@ interface RenderablePathSpec {
|
|
6
6
|
style: RenderingStyle;
|
7
7
|
path?: Path;
|
8
8
|
}
|
9
|
+
interface RenderablePathSpecWithPath extends RenderablePathSpec {
|
10
|
+
path: Path;
|
11
|
+
}
|
9
12
|
/** Converts a renderable path (a path with a `startPoint`, `commands`, and `style`). */
|
10
13
|
export declare const pathFromRenderable: (renderable: RenderablePathSpec) => Path;
|
11
|
-
export declare const pathToRenderable: (path: Path, style: RenderingStyle) =>
|
14
|
+
export declare const pathToRenderable: (path: Path, style: RenderingStyle) => RenderablePathSpecWithPath;
|
15
|
+
interface RectangleSimplificationResult {
|
16
|
+
rectangle: Rect2;
|
17
|
+
path: RenderablePathSpecWithPath;
|
18
|
+
fullScreen: boolean;
|
19
|
+
}
|
20
|
+
/**
|
21
|
+
* Tries to simplify the given path to a fullscreen rectangle.
|
22
|
+
* Returns `null` on failure.
|
23
|
+
*
|
24
|
+
* @internal
|
25
|
+
*/
|
26
|
+
export declare const simplifyPathToFullScreenOrEmpty: (renderablePath: RenderablePathSpec, visibleRect: Rect2, options?: {
|
27
|
+
fastCheck: boolean;
|
28
|
+
expensiveCheck: boolean;
|
29
|
+
}) => RectangleSimplificationResult | null;
|
12
30
|
/**
|
13
31
|
* @returns a Path that, when rendered, looks roughly equivalent to the given path.
|
14
32
|
*/
|
15
|
-
export declare const visualEquivalent: (renderablePath: RenderablePathSpec, visibleRect: Rect2) =>
|
33
|
+
export declare const visualEquivalent: (renderablePath: RenderablePathSpec, visibleRect: Rect2) => RenderablePathSpecWithPath;
|
16
34
|
export default RenderablePathSpec;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.visualEquivalent = exports.pathToRenderable = exports.pathFromRenderable = void 0;
|
3
|
+
exports.visualEquivalent = exports.simplifyPathToFullScreenOrEmpty = exports.pathToRenderable = exports.pathFromRenderable = void 0;
|
4
4
|
const math_1 = require("@js-draw/math");
|
5
5
|
/** Converts a renderable path (a path with a `startPoint`, `commands`, and `style`). */
|
6
6
|
const pathFromRenderable = (renderable) => {
|
@@ -20,33 +20,90 @@ const pathToRenderable = (path, style) => {
|
|
20
20
|
};
|
21
21
|
exports.pathToRenderable = pathToRenderable;
|
22
22
|
/**
|
23
|
-
*
|
23
|
+
* Fills the optional `path` field in `RenderablePathSpec`
|
24
|
+
* with `path` if not already filled
|
24
25
|
*/
|
25
|
-
const
|
26
|
+
const pathIncluded = (renderablePath, path) => {
|
27
|
+
if (renderablePath.path) {
|
28
|
+
return renderablePath;
|
29
|
+
}
|
30
|
+
return {
|
31
|
+
...renderablePath,
|
32
|
+
path,
|
33
|
+
};
|
34
|
+
};
|
35
|
+
/**
|
36
|
+
* Tries to simplify the given path to a fullscreen rectangle.
|
37
|
+
* Returns `null` on failure.
|
38
|
+
*
|
39
|
+
* @internal
|
40
|
+
*/
|
41
|
+
const simplifyPathToFullScreenOrEmpty = (renderablePath, visibleRect, options = { fastCheck: true, expensiveCheck: true }) => {
|
26
42
|
const path = (0, exports.pathFromRenderable)(renderablePath);
|
27
43
|
const strokeWidth = renderablePath.style.stroke?.width ?? 0;
|
28
44
|
const onlyStroked = strokeWidth > 0 && renderablePath.style.fill.a === 0;
|
29
45
|
const styledPathBBox = path.bbox.grownBy(strokeWidth);
|
30
46
|
// Are we close enough to the path that it fills the entire screen?
|
31
|
-
|
32
|
-
&& renderablePath.style.stroke
|
47
|
+
const isOnlyStrokedAndCouldFillScreen = (onlyStroked
|
33
48
|
&& strokeWidth > visibleRect.maxDimension
|
34
|
-
&& styledPathBBox.containsRect(visibleRect))
|
49
|
+
&& styledPathBBox.containsRect(visibleRect));
|
50
|
+
if (options.fastCheck && isOnlyStrokedAndCouldFillScreen && renderablePath.style.stroke) {
|
35
51
|
const strokeRadius = strokeWidth / 2;
|
52
|
+
// Are we completely within the stroke?
|
36
53
|
// Do a fast, but with many false negatives, check.
|
37
54
|
for (const point of path.startEndPoints()) {
|
38
55
|
// If within the strokeRadius of any point
|
39
56
|
if (visibleRect.isWithinRadiusOf(strokeRadius, point)) {
|
40
|
-
return
|
57
|
+
return {
|
58
|
+
rectangle: visibleRect,
|
59
|
+
path: (0, exports.pathToRenderable)(math_1.Path.fromRect(visibleRect), { fill: renderablePath.style.stroke.color }),
|
60
|
+
fullScreen: true,
|
61
|
+
};
|
41
62
|
}
|
42
63
|
}
|
43
64
|
}
|
65
|
+
// Try filtering again, but with slightly more expensive checks
|
66
|
+
if (options.expensiveCheck &&
|
67
|
+
isOnlyStrokedAndCouldFillScreen && renderablePath.style.stroke
|
68
|
+
&& strokeWidth > visibleRect.maxDimension * 3) {
|
69
|
+
const signedDist = path.signedDistance(visibleRect.center, strokeWidth / 2);
|
70
|
+
const margin = strokeWidth / 6;
|
71
|
+
if (signedDist < -visibleRect.maxDimension / 2 - margin) {
|
72
|
+
return {
|
73
|
+
path: (0, exports.pathToRenderable)(math_1.Path.fromRect(visibleRect), { fill: renderablePath.style.stroke.color }),
|
74
|
+
rectangle: visibleRect,
|
75
|
+
fullScreen: true,
|
76
|
+
};
|
77
|
+
}
|
78
|
+
else if (signedDist > visibleRect.maxDimension / 2 + margin) {
|
79
|
+
return {
|
80
|
+
path: (0, exports.pathToRenderable)(math_1.Path.empty, { fill: math_1.Color4.transparent }),
|
81
|
+
rectangle: math_1.Rect2.empty,
|
82
|
+
fullScreen: false,
|
83
|
+
};
|
84
|
+
}
|
85
|
+
}
|
86
|
+
return null;
|
87
|
+
};
|
88
|
+
exports.simplifyPathToFullScreenOrEmpty = simplifyPathToFullScreenOrEmpty;
|
89
|
+
/**
|
90
|
+
* @returns a Path that, when rendered, looks roughly equivalent to the given path.
|
91
|
+
*/
|
92
|
+
const visualEquivalent = (renderablePath, visibleRect) => {
|
93
|
+
const path = (0, exports.pathFromRenderable)(renderablePath);
|
94
|
+
const strokeWidth = renderablePath.style.stroke?.width ?? 0;
|
95
|
+
const onlyStroked = strokeWidth > 0 && renderablePath.style.fill.a === 0;
|
96
|
+
const styledPathBBox = path.bbox.grownBy(strokeWidth);
|
97
|
+
let rectangleSimplification = (0, exports.simplifyPathToFullScreenOrEmpty)(renderablePath, visibleRect, { fastCheck: true, expensiveCheck: false, });
|
98
|
+
if (rectangleSimplification) {
|
99
|
+
return rectangleSimplification.path;
|
100
|
+
}
|
44
101
|
// Scale the expanded rect --- the visual equivalent is only close for huge strokes.
|
45
102
|
const expandedRect = visibleRect.grownBy(strokeWidth)
|
46
103
|
.transformedBoundingBox(math_1.Mat33.scaling2D(4, visibleRect.center));
|
47
104
|
// TODO: Handle simplifying very small paths.
|
48
105
|
if (expandedRect.containsRect(styledPathBBox)) {
|
49
|
-
return renderablePath;
|
106
|
+
return pathIncluded(renderablePath, path);
|
50
107
|
}
|
51
108
|
const parts = [];
|
52
109
|
let startPoint = path.startPoint;
|
@@ -81,6 +138,12 @@ const visualEquivalent = (renderablePath, visibleRect) => {
|
|
81
138
|
}
|
82
139
|
startPoint = endPoint;
|
83
140
|
}
|
84
|
-
|
141
|
+
const newPath = new math_1.Path(path.startPoint, parts);
|
142
|
+
const newStyle = renderablePath.style;
|
143
|
+
rectangleSimplification = (0, exports.simplifyPathToFullScreenOrEmpty)(renderablePath, visibleRect, { fastCheck: false, expensiveCheck: true, });
|
144
|
+
if (rectangleSimplification) {
|
145
|
+
return rectangleSimplification.path;
|
146
|
+
}
|
147
|
+
return (0, exports.pathToRenderable)(newPath, newStyle);
|
85
148
|
};
|
86
149
|
exports.visualEquivalent = visualEquivalent;
|
@@ -49,9 +49,11 @@ class CacheRecord {
|
|
49
49
|
return transform;
|
50
50
|
}
|
51
51
|
setRenderingRegion(drawTo) {
|
52
|
-
this.
|
53
|
-
|
54
|
-
|
52
|
+
const transform = this.getTransform(drawTo);
|
53
|
+
this.renderer.setTransform(transform);
|
54
|
+
// The visible region may be slightly larger than where we're actually drawing
|
55
|
+
// to (because of rounding).
|
56
|
+
this.renderer.overrideVisibleRect(drawTo.grownBy(1 / transform.getScaleFactor()));
|
55
57
|
}
|
56
58
|
}
|
57
59
|
exports.default = CacheRecord;
|
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
6
|
exports.CacheRecordManager = void 0;
|
7
7
|
const CacheRecord_1 = __importDefault(require("./CacheRecord"));
|
8
|
-
const debugMode = false;
|
9
8
|
class CacheRecordManager {
|
10
9
|
constructor(cacheProps) {
|
11
10
|
// Fixed-size array: Cache blocks are assigned indicies into [cachedCanvases].
|
@@ -22,19 +21,19 @@ class CacheRecordManager {
|
|
22
21
|
const record = new CacheRecord_1.default(onDealloc, this.cacheState);
|
23
22
|
record.setRenderingRegion(drawTo);
|
24
23
|
this.cacheRecords.push(record);
|
25
|
-
if (debugMode) {
|
24
|
+
if (this.cacheState.debugMode) {
|
26
25
|
console.log('[Cache] Cache spaces used: ', this.cacheRecords.length, ' of ', this.maxCanvases);
|
27
26
|
}
|
28
27
|
return record;
|
29
28
|
}
|
30
29
|
else {
|
31
30
|
const lru = this.getLeastRecentlyUsedRecord();
|
32
|
-
if (debugMode) {
|
31
|
+
if (this.cacheState.debugMode) {
|
33
32
|
console.log('[Cache] Re-alloc. Times allocated: ', lru.allocCount, '\nLast used cycle: ', lru.getLastUsedCycle(), '\nCurrent cycle: ', this.cacheState.currentRenderingCycle);
|
34
33
|
}
|
35
34
|
lru.realloc(onDealloc);
|
36
35
|
lru.setRenderingRegion(drawTo);
|
37
|
-
if (debugMode) {
|
36
|
+
if (this.cacheState.debugMode) {
|
38
37
|
console.log('[Cache] Now re-alloc\'d. Last used cycle: ', lru.getLastUsedCycle());
|
39
38
|
console.assert(lru['cacheState'] === this.cacheState, '[Cache] Unequal cache states! cacheState should be a shared object!');
|
40
39
|
}
|
@@ -13,6 +13,7 @@ class RenderingCache {
|
|
13
13
|
props: cacheProps,
|
14
14
|
currentRenderingCycle: 0,
|
15
15
|
recordManager: this.recordManager,
|
16
|
+
debugMode: false,
|
16
17
|
};
|
17
18
|
this.recordManager.setSharedState(this.sharedState);
|
18
19
|
}
|
@@ -49,5 +50,8 @@ class RenderingCache {
|
|
49
50
|
getDebugInfo() {
|
50
51
|
return this.recordManager.getDebugInfo();
|
51
52
|
}
|
53
|
+
setIsDebugMode(debugMode) {
|
54
|
+
this.sharedState.debugMode = debugMode;
|
55
|
+
}
|
52
56
|
}
|
53
57
|
exports.default = RenderingCache;
|
@@ -5,8 +5,6 @@ const EditorImage_1 = require("../../image/EditorImage");
|
|
5
5
|
const math_1 = require("@js-draw/math");
|
6
6
|
// 3x3 divisions for each node.
|
7
7
|
const cacheDivisionSize = 3;
|
8
|
-
// True: Show rendering updates.
|
9
|
-
const debugMode = false;
|
10
8
|
class RenderingCacheNode {
|
11
9
|
constructor(region, cacheState) {
|
12
10
|
this.region = region;
|
@@ -163,8 +161,8 @@ class RenderingCacheNode {
|
|
163
161
|
items.forEach(item => item.render(screenRenderer, viewport.visibleRect));
|
164
162
|
return;
|
165
163
|
}
|
166
|
-
if (debugMode) {
|
167
|
-
screenRenderer.drawRect(this.region,
|
164
|
+
if (this.cacheState.debugMode) {
|
165
|
+
screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: math_1.Color4.yellow });
|
168
166
|
}
|
169
167
|
// Could we render direclty from [this] or do we need to recurse?
|
170
168
|
const couldRender = this.renderingWouldBeHighEnoughResolution(viewport);
|
@@ -199,7 +197,9 @@ class RenderingCacheNode {
|
|
199
197
|
}
|
200
198
|
let leafApproxRenderTime = 0;
|
201
199
|
for (const leaf of leavesByIds) {
|
202
|
-
|
200
|
+
if (!tooSmallToRender(leaf.getBBox())) {
|
201
|
+
leafApproxRenderTime += leaf.getContent().getProportionalRenderingTime();
|
202
|
+
}
|
203
203
|
}
|
204
204
|
// Is it worth it to render the items?
|
205
205
|
if (leafApproxRenderTime > this.cacheState.props.minProportionalRenderTimePerCache) {
|
@@ -235,26 +235,30 @@ class RenderingCacheNode {
|
|
235
235
|
this.renderedMaxZIndex = zIndex;
|
236
236
|
}
|
237
237
|
}
|
238
|
-
if (debugMode) {
|
239
|
-
|
238
|
+
if (this.cacheState.debugMode) {
|
239
|
+
// Clay for adding new elements
|
240
|
+
screenRenderer.drawRect(this.region, 2 * viewport.getSizeOfPixelOnCanvas(), { fill: math_1.Color4.clay });
|
240
241
|
}
|
241
242
|
}
|
242
243
|
}
|
243
|
-
else if (debugMode) {
|
244
|
+
else if (this.cacheState.debugMode) {
|
244
245
|
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);
|
245
246
|
}
|
246
247
|
if (fullRerenderNeeded) {
|
247
248
|
thisRenderer = this.cachedRenderer.startRender();
|
248
249
|
thisRenderer.clear();
|
249
250
|
this.renderedMaxZIndex = null;
|
250
|
-
|
251
|
+
const startIndex = (0, EditorImage_1.computeFirstIndexToRender)(leaves, this.region);
|
252
|
+
for (let i = startIndex; i < leaves.length; i++) {
|
253
|
+
const leaf = leaves[i];
|
251
254
|
const content = leaf.getContent();
|
252
255
|
this.renderedMaxZIndex ??= content.getZIndex();
|
253
256
|
this.renderedMaxZIndex = Math.max(this.renderedMaxZIndex, content.getZIndex());
|
254
257
|
leaf.render(thisRenderer, this.region);
|
255
258
|
}
|
256
|
-
if (debugMode) {
|
257
|
-
|
259
|
+
if (this.cacheState.debugMode) {
|
260
|
+
// Red for full rerender
|
261
|
+
screenRenderer.drawRect(this.region, 3 * viewport.getSizeOfPixelOnCanvas(), { fill: math_1.Color4.red });
|
258
262
|
}
|
259
263
|
}
|
260
264
|
this.renderedIds = leafIds;
|
@@ -271,6 +275,10 @@ class RenderingCacheNode {
|
|
271
275
|
leaf.render(screenRenderer, this.region.intersection(viewport.visibleRect));
|
272
276
|
}
|
273
277
|
screenRenderer.endObject();
|
278
|
+
if (this.cacheState.debugMode) {
|
279
|
+
// Green for no cache needed render
|
280
|
+
screenRenderer.drawRect(this.region, 2 * viewport.getSizeOfPixelOnCanvas(), { fill: math_1.Color4.green });
|
281
|
+
}
|
274
282
|
}
|
275
283
|
}
|
276
284
|
else {
|
@@ -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
|
}
|
@@ -17,8 +17,11 @@ class AbstractRenderer {
|
|
17
17
|
this.objectLevel = 0;
|
18
18
|
this.currentPaths = null;
|
19
19
|
}
|
20
|
-
|
21
|
-
|
20
|
+
/**
|
21
|
+
* this.canvasToScreen, etc. should be used instead of the corresponding
|
22
|
+
* methods on `Viewport`, because the viewport may not accurately reflect
|
23
|
+
* what is rendered.
|
24
|
+
*/
|
22
25
|
getViewport() { return this.viewport; }
|
23
26
|
setDraftMode(_draftMode) { }
|
24
27
|
flushPath() {
|
@@ -160,13 +163,19 @@ class AbstractRenderer {
|
|
160
163
|
getSizeOfCanvasPixelOnScreen() {
|
161
164
|
return this.getCanvasToScreenTransform().transformVec3(math_1.Vec2.unitX).length();
|
162
165
|
}
|
166
|
+
/**
|
167
|
+
* @internal
|
168
|
+
*/
|
169
|
+
overrideVisibleRect(rect) {
|
170
|
+
this.visibleRectOverride = rect;
|
171
|
+
}
|
163
172
|
// Returns the region in canvas space that is visible within the viewport this
|
164
173
|
// canvas is rendering to.
|
165
174
|
//
|
166
175
|
// Note that in some cases this might not be the same as the `visibleRect` given
|
167
176
|
// to components in their `render` method.
|
168
177
|
getVisibleRect() {
|
169
|
-
return this.viewport.visibleRect;
|
178
|
+
return this.visibleRectOverride ?? this.viewport.visibleRect;
|
170
179
|
}
|
171
180
|
}
|
172
181
|
exports.default = AbstractRenderer;
|
@@ -82,7 +82,10 @@ class CanvasRenderer extends AbstractRenderer_1.default {
|
|
82
82
|
return math_1.Vec2.of(this.ctx.canvas.clientWidth, this.ctx.canvas.clientHeight);
|
83
83
|
}
|
84
84
|
clear() {
|
85
|
+
this.ctx.save();
|
86
|
+
this.ctx.resetTransform();
|
85
87
|
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
88
|
+
this.ctx.restore();
|
86
89
|
}
|
87
90
|
beginPath(startPoint) {
|
88
91
|
startPoint = this.canvasToScreen(startPoint);
|
@@ -149,7 +152,7 @@ class CanvasRenderer extends AbstractRenderer_1.default {
|
|
149
152
|
return;
|
150
153
|
}
|
151
154
|
// If part of a huge object, it might be worth trimming the path
|
152
|
-
const visibleRect = this.
|
155
|
+
const visibleRect = this.getVisibleRect();
|
153
156
|
if (this.currentObjectBBox?.containsRect(visibleRect)) {
|
154
157
|
// Try to trim/remove parts of the path outside of the bounding box.
|
155
158
|
path = (0, RenderablePathSpec_1.visualEquivalent)(path, visibleRect);
|
@@ -189,7 +192,7 @@ class CanvasRenderer extends AbstractRenderer_1.default {
|
|
189
192
|
if (!this.ignoringObject && clip) {
|
190
193
|
// Don't clip if it would only remove content already trimmed by
|
191
194
|
// the edge of the screen.
|
192
|
-
const clippedIsOutsideScreen = boundingBox.containsRect(this.
|
195
|
+
const clippedIsOutsideScreen = boundingBox.containsRect(this.getVisibleRect());
|
193
196
|
if (!clippedIsOutsideScreen) {
|
194
197
|
this.clipLevels.push(this.objectLevel);
|
195
198
|
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`.
|
@@ -42,7 +42,6 @@ class SVGRenderer extends AbstractRenderer_1.default {
|
|
42
42
|
this.textContainer = null;
|
43
43
|
this.textContainerTransform = null;
|
44
44
|
this.textParentStyle = defaultTextStyle;
|
45
|
-
this.visibleRectOverride = null;
|
46
45
|
this.clear();
|
47
46
|
this.addStyleSheet();
|
48
47
|
}
|
@@ -345,19 +344,6 @@ class SVGRenderer extends AbstractRenderer_1.default {
|
|
345
344
|
isTooSmallToRender(_rect) {
|
346
345
|
return false;
|
347
346
|
}
|
348
|
-
/**
|
349
|
-
* Overrides the visible region returned by `getVisibleRect`.
|
350
|
-
*
|
351
|
-
* This is useful when the `viewport`'s transform has been modified,
|
352
|
-
* for example, to compensate for storing part of the image's
|
353
|
-
* transformation in an SVG property.
|
354
|
-
*/
|
355
|
-
overrideVisibleRect(newRect) {
|
356
|
-
this.visibleRectOverride = newRect;
|
357
|
-
}
|
358
|
-
getVisibleRect() {
|
359
|
-
return this.visibleRectOverride ?? super.getVisibleRect();
|
360
|
-
}
|
361
347
|
/**
|
362
348
|
* Creates a new SVG element and `SVGRenerer` with `width`, `height`, `viewBox`,
|
363
349
|
* 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,41 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const math_1 = require("@js-draw/math");
|
7
|
+
const sendTouchEvent_1 = __importDefault(require("./sendTouchEvent"));
|
8
|
+
const inputEvents_1 = require("../inputEvents");
|
9
|
+
/**
|
10
|
+
* Creates two pointers and sends the touch {@link InputEvtType.PointerDownEvt}s for them.
|
11
|
+
*
|
12
|
+
* Returns an object that allows continuing or ending the gesture.
|
13
|
+
*
|
14
|
+
* `initialRotation` should be in radians.
|
15
|
+
*/
|
16
|
+
const startPinchGesture = (editor, center, initialDistance, initialRotation) => {
|
17
|
+
const computeTouchPoints = (center, distance, rotation) => {
|
18
|
+
const halfDisplacement = math_1.Mat33.zRotation(rotation).transformVec2(math_1.Vec2.of(0, distance / 2));
|
19
|
+
const point1 = center.plus(halfDisplacement);
|
20
|
+
const point2 = center.minus(halfDisplacement);
|
21
|
+
return [point1, point2];
|
22
|
+
};
|
23
|
+
let [touchPoint1, touchPoint2] = computeTouchPoints(center, initialDistance, initialRotation);
|
24
|
+
let firstPointer = (0, sendTouchEvent_1.default)(editor, inputEvents_1.InputEvtType.PointerDownEvt, touchPoint1);
|
25
|
+
let secondPointer = (0, sendTouchEvent_1.default)(editor, inputEvents_1.InputEvtType.PointerDownEvt, touchPoint2, [firstPointer]);
|
26
|
+
return {
|
27
|
+
update(center, distance, rotation) {
|
28
|
+
const eventType = inputEvents_1.InputEvtType.PointerMoveEvt;
|
29
|
+
const [newPoint1, newPoint2] = computeTouchPoints(center, distance, rotation);
|
30
|
+
touchPoint1 = newPoint1;
|
31
|
+
touchPoint2 = newPoint2;
|
32
|
+
firstPointer = (0, sendTouchEvent_1.default)(editor, eventType, newPoint1, [secondPointer]);
|
33
|
+
secondPointer = (0, sendTouchEvent_1.default)(editor, eventType, newPoint2, [firstPointer]);
|
34
|
+
},
|
35
|
+
end() {
|
36
|
+
(0, sendTouchEvent_1.default)(editor, inputEvents_1.InputEvtType.PointerUpEvt, touchPoint1, [secondPointer]);
|
37
|
+
(0, sendTouchEvent_1.default)(editor, inputEvents_1.InputEvtType.PointerUpEvt, touchPoint2);
|
38
|
+
},
|
39
|
+
};
|
40
|
+
};
|
41
|
+
exports.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);
|