js-draw 0.1.11 → 0.2.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/.eslintrc.js +1 -0
- package/.firebaserc +5 -0
- package/.github/workflows/firebase-hosting-merge.yml +25 -0
- package/.github/workflows/firebase-hosting-pull-request.yml +22 -0
- package/.github/workflows/github-pages.yml +52 -0
- package/CHANGELOG.md +13 -0
- package/README.md +11 -6
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.d.ts +19 -0
- package/dist/src/Color4.js +24 -3
- package/dist/src/Editor.d.ts +133 -4
- package/dist/src/Editor.js +124 -27
- package/dist/src/EditorImage.d.ts +8 -3
- package/dist/src/EditorImage.js +42 -26
- package/dist/src/EventDispatcher.d.ts +18 -0
- package/dist/src/EventDispatcher.js +19 -4
- package/dist/src/Pointer.d.ts +1 -1
- package/dist/src/Pointer.js +4 -3
- package/dist/src/SVGLoader.d.ts +1 -1
- package/dist/src/SVGLoader.js +14 -6
- package/dist/src/UndoRedoHistory.js +15 -2
- package/dist/src/Viewport.d.ts +8 -25
- package/dist/src/Viewport.js +18 -10
- package/dist/src/bundle/bundled.d.ts +1 -2
- package/dist/src/bundle/bundled.js +1 -2
- package/dist/src/commands/Command.d.ts +2 -2
- package/dist/src/commands/Command.js +4 -4
- package/dist/src/commands/Duplicate.d.ts +2 -2
- package/dist/src/commands/Duplicate.js +4 -5
- package/dist/src/commands/Erase.d.ts +2 -2
- package/dist/src/commands/Erase.js +7 -6
- package/dist/src/commands/SerializableCommand.d.ts +4 -5
- package/dist/src/commands/SerializableCommand.js +12 -4
- package/dist/src/commands/invertCommand.d.ts +4 -0
- package/dist/src/commands/invertCommand.js +44 -0
- package/dist/src/commands/lib.d.ts +6 -0
- package/dist/src/commands/lib.js +6 -0
- package/dist/src/commands/localization.d.ts +2 -1
- package/dist/src/commands/localization.js +1 -0
- package/dist/src/components/AbstractComponent.d.ts +16 -11
- package/dist/src/components/AbstractComponent.js +28 -17
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +4 -4
- package/dist/src/components/SVGGlobalAttributesObject.js +8 -2
- package/dist/src/components/Stroke.d.ts +16 -6
- package/dist/src/components/Stroke.js +12 -9
- package/dist/src/components/Text.d.ts +5 -5
- package/dist/src/components/Text.js +9 -9
- package/dist/src/components/UnknownSVGObject.d.ts +4 -4
- package/dist/src/components/UnknownSVGObject.js +7 -2
- package/dist/src/components/builders/ArrowBuilder.d.ts +1 -1
- package/dist/src/components/builders/ArrowBuilder.js +1 -1
- package/dist/src/components/builders/FreehandLineBuilder.d.ts +8 -3
- package/dist/src/components/builders/FreehandLineBuilder.js +142 -71
- package/dist/src/components/builders/LineBuilder.d.ts +1 -1
- package/dist/src/components/builders/LineBuilder.js +1 -1
- package/dist/src/components/builders/RectangleBuilder.d.ts +1 -1
- package/dist/src/components/builders/RectangleBuilder.js +3 -3
- package/dist/src/components/builders/types.d.ts +1 -1
- package/dist/src/components/lib.d.ts +4 -0
- package/dist/src/components/lib.js +4 -0
- package/dist/src/lib.d.ts +25 -0
- package/dist/src/lib.js +25 -0
- package/dist/src/localization.d.ts +1 -0
- package/dist/src/localization.js +5 -1
- package/dist/src/localizations/es.js +1 -1
- package/dist/src/{geometry → math}/LineSegment2.d.ts +0 -0
- package/dist/src/{geometry → math}/LineSegment2.js +0 -0
- package/dist/src/math/Mat33.d.ts +78 -0
- package/dist/src/{geometry → math}/Mat33.js +48 -20
- package/dist/src/{geometry → math}/Path.d.ts +2 -1
- package/dist/src/{geometry → math}/Path.js +59 -52
- package/dist/src/{geometry → math}/Rect2.d.ts +2 -2
- package/dist/src/{geometry → math}/Rect2.js +0 -0
- package/dist/src/{geometry → math}/Vec2.d.ts +0 -0
- package/dist/src/{geometry → math}/Vec2.js +0 -0
- package/dist/src/math/Vec3.d.ts +96 -0
- package/dist/src/{geometry → math}/Vec3.js +63 -15
- package/dist/src/math/lib.d.ts +7 -0
- package/dist/src/math/lib.js +7 -0
- package/dist/src/math/rounding.d.ts +3 -0
- package/dist/src/math/rounding.js +121 -0
- package/dist/src/rendering/Display.d.ts +47 -1
- package/dist/src/rendering/Display.js +60 -15
- package/dist/src/rendering/caching/CacheRecord.d.ts +3 -2
- package/dist/src/rendering/caching/CacheRecord.js +4 -1
- package/dist/src/rendering/caching/CacheRecordManager.d.ts +5 -4
- package/dist/src/rendering/caching/CacheRecordManager.js +16 -4
- package/dist/src/rendering/caching/RenderingCache.d.ts +2 -3
- package/dist/src/rendering/caching/RenderingCache.js +10 -11
- package/dist/src/rendering/caching/RenderingCacheNode.d.ts +2 -1
- package/dist/src/rendering/caching/RenderingCacheNode.js +18 -7
- package/dist/src/rendering/caching/testUtils.js +1 -1
- package/dist/src/rendering/caching/types.d.ts +2 -4
- package/dist/src/rendering/localization.d.ts +2 -0
- package/dist/src/rendering/localization.js +2 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +4 -4
- package/dist/src/rendering/renderers/AbstractRenderer.js +2 -2
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +4 -4
- package/dist/src/rendering/renderers/CanvasRenderer.js +2 -2
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +4 -4
- package/dist/src/rendering/renderers/DummyRenderer.js +1 -1
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +3 -3
- package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +5 -3
- package/dist/src/rendering/renderers/TextOnlyRenderer.js +13 -3
- package/dist/src/toolbar/HTMLToolbar.js +1 -0
- package/dist/src/toolbar/icons.d.ts +3 -0
- package/dist/src/toolbar/icons.js +142 -132
- package/dist/src/toolbar/localization.d.ts +2 -1
- package/dist/src/toolbar/localization.js +2 -1
- package/dist/src/toolbar/makeColorInput.js +3 -2
- package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +13 -0
- package/dist/src/toolbar/widgets/ActionButtonWidget.js +21 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +2 -0
- package/dist/src/toolbar/widgets/HandToolWidget.js +3 -3
- package/dist/src/toolbar/widgets/PenWidget.js +1 -0
- package/dist/src/toolbar/widgets/SelectionWidget.d.ts +0 -1
- package/dist/src/toolbar/widgets/SelectionWidget.js +23 -30
- package/dist/src/tools/Eraser.js +1 -1
- package/dist/src/tools/PanZoom.d.ts +1 -1
- package/dist/src/tools/PanZoom.js +24 -14
- package/dist/src/tools/Pen.d.ts +1 -2
- package/dist/src/tools/Pen.js +8 -1
- package/dist/src/tools/PipetteTool.js +1 -0
- package/dist/src/tools/SelectionTool.d.ts +3 -3
- package/dist/src/tools/SelectionTool.js +51 -28
- package/dist/src/tools/TextTool.js +1 -1
- package/dist/src/types.d.ts +21 -10
- package/dist/src/types.js +7 -5
- package/firebase.json +16 -0
- package/package.json +118 -101
- package/src/Color4.ts +23 -2
- package/src/Editor.ts +181 -37
- package/src/EditorImage.test.ts +2 -4
- package/src/EditorImage.ts +46 -28
- package/src/EventDispatcher.ts +21 -6
- package/src/Pointer.ts +4 -3
- package/src/SVGLoader.ts +14 -6
- package/src/UndoRedoHistory.ts +18 -2
- package/src/Viewport.ts +23 -18
- package/src/bundle/bundled.ts +1 -2
- package/src/commands/Command.ts +5 -5
- package/src/commands/Duplicate.ts +4 -5
- package/src/commands/Erase.ts +7 -6
- package/src/commands/SerializableCommand.ts +17 -9
- package/src/commands/invertCommand.ts +51 -0
- package/src/commands/lib.ts +14 -0
- package/src/commands/localization.ts +3 -1
- package/src/components/AbstractComponent.ts +35 -24
- package/src/components/SVGGlobalAttributesObject.ts +11 -4
- package/src/components/Stroke.test.ts +4 -6
- package/src/components/Stroke.ts +15 -11
- package/src/components/Text.test.ts +2 -2
- package/src/components/Text.ts +9 -10
- package/src/components/UnknownSVGObject.ts +10 -4
- package/src/components/builders/ArrowBuilder.ts +2 -2
- package/src/components/builders/FreehandLineBuilder.ts +190 -80
- package/src/components/builders/LineBuilder.ts +2 -2
- package/src/components/builders/RectangleBuilder.ts +3 -3
- package/src/components/builders/types.ts +1 -1
- package/src/components/lib.ts +9 -0
- package/src/lib.ts +28 -0
- package/src/localization.ts +6 -0
- package/src/localizations/es.ts +2 -1
- package/src/{geometry → math}/LineSegment2.test.ts +0 -0
- package/src/{geometry → math}/LineSegment2.ts +0 -0
- package/src/{geometry → math}/Mat33.test.ts +0 -0
- package/src/{geometry → math}/Mat33.ts +48 -20
- package/src/{geometry → math}/Path.fromString.test.ts +0 -0
- package/src/{geometry → math}/Path.test.ts +0 -0
- package/src/{geometry → math}/Path.toString.test.ts +11 -2
- package/src/{geometry → math}/Path.ts +61 -58
- package/src/{geometry → math}/Rect2.test.ts +0 -0
- package/src/{geometry → math}/Rect2.ts +2 -2
- package/src/{geometry → math}/Vec2.test.ts +0 -0
- package/src/{geometry → math}/Vec2.ts +0 -0
- package/src/{geometry → math}/Vec3.test.ts +0 -0
- package/src/{geometry → math}/Vec3.ts +64 -16
- package/src/math/lib.ts +15 -0
- package/src/math/rounding.test.ts +40 -0
- package/src/math/rounding.ts +147 -0
- package/src/rendering/Display.ts +63 -15
- package/src/rendering/caching/CacheRecord.test.ts +3 -3
- package/src/rendering/caching/CacheRecord.ts +6 -2
- package/src/rendering/caching/CacheRecordManager.ts +34 -8
- package/src/rendering/caching/RenderingCache.test.ts +3 -3
- package/src/rendering/caching/RenderingCache.ts +11 -16
- package/src/rendering/caching/RenderingCacheNode.ts +23 -7
- package/src/rendering/caching/testUtils.ts +1 -1
- package/src/rendering/caching/types.ts +2 -7
- package/src/rendering/localization.ts +4 -0
- package/src/rendering/renderers/AbstractRenderer.ts +4 -4
- package/src/rendering/renderers/CanvasRenderer.ts +5 -5
- package/src/rendering/renderers/DummyRenderer.test.ts +2 -2
- package/src/rendering/renderers/DummyRenderer.ts +4 -4
- package/src/rendering/renderers/SVGRenderer.ts +10 -4
- package/src/rendering/renderers/TextOnlyRenderer.ts +17 -6
- package/src/toolbar/HTMLToolbar.ts +1 -0
- package/src/toolbar/icons.ts +157 -137
- package/src/toolbar/localization.ts +4 -2
- package/src/toolbar/makeColorInput.ts +3 -2
- package/src/toolbar/toolbar.css +1 -1
- package/src/toolbar/widgets/ActionButtonWidget.ts +31 -0
- package/src/toolbar/widgets/BaseWidget.ts +2 -0
- package/src/toolbar/widgets/HandToolWidget.ts +3 -3
- package/src/toolbar/widgets/PenWidget.ts +2 -0
- package/src/toolbar/widgets/SelectionWidget.ts +46 -41
- package/src/tools/Eraser.ts +2 -2
- package/src/tools/PanZoom.ts +28 -17
- package/src/tools/Pen.ts +11 -2
- package/src/tools/PipetteTool.ts +2 -0
- package/src/tools/SelectionTool.test.ts +2 -4
- package/src/tools/SelectionTool.ts +52 -24
- package/src/tools/TextTool.ts +2 -2
- package/src/tools/UndoRedoShortcut.test.ts +1 -1
- package/src/types.ts +23 -7
- package/tsconfig.json +4 -1
- package/typedoc.json +20 -0
- package/dist/src/geometry/Mat33.d.ts +0 -32
- package/dist/src/geometry/Vec3.d.ts +0 -34
@@ -0,0 +1,147 @@
|
|
1
|
+
// @packageDocumentation @internal
|
2
|
+
|
3
|
+
// Clean up stringified numbers
|
4
|
+
const cleanUpNumber = (text: string) => {
|
5
|
+
// Regular expression substitions can be somewhat expensive. Only do them
|
6
|
+
// if necessary.
|
7
|
+
const lastChar = text.charAt(text.length - 1);
|
8
|
+
if (lastChar === '0' || lastChar === '.') {
|
9
|
+
// Remove trailing zeroes
|
10
|
+
text = text.replace(/([.]\d*[^0]+)0+$/, '$1');
|
11
|
+
text = text.replace(/[.]0+$/, '.');
|
12
|
+
|
13
|
+
// Remove trailing period
|
14
|
+
text = text.replace(/[.]$/, '');
|
15
|
+
|
16
|
+
if (text === '-0') {
|
17
|
+
return '0';
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
const firstChar = text.charAt(0);
|
22
|
+
if (firstChar === '0' || firstChar === '-') {
|
23
|
+
// Remove unnecessary leading zeroes.
|
24
|
+
text = text.replace(/^(0+)[.]/, '.');
|
25
|
+
text = text.replace(/^-(0+)[.]/, '-.');
|
26
|
+
}
|
27
|
+
|
28
|
+
return text;
|
29
|
+
};
|
30
|
+
|
31
|
+
export const toRoundedString = (num: number): string => {
|
32
|
+
// Try to remove rounding errors. If the number ends in at least three/four zeroes
|
33
|
+
// (or nines) just one or two digits, it's probably a rounding error.
|
34
|
+
const fixRoundingUpExp = /^([-]?\d*\.\d{3,})0{4,}\d{1,2}$/;
|
35
|
+
const hasRoundingDownExp = /^([-]?)(\d*)\.(\d{3,}9{4,})\d{1,2}$/;
|
36
|
+
|
37
|
+
let text = num.toString(10);
|
38
|
+
if (text.indexOf('.') === -1) {
|
39
|
+
return text;
|
40
|
+
}
|
41
|
+
|
42
|
+
const roundingDownMatch = hasRoundingDownExp.exec(text);
|
43
|
+
if (roundingDownMatch) {
|
44
|
+
const negativeSign = roundingDownMatch[1];
|
45
|
+
const postDecimalString = roundingDownMatch[3];
|
46
|
+
const lastDigit = parseInt(postDecimalString.charAt(postDecimalString.length - 1), 10);
|
47
|
+
const postDecimal = parseInt(postDecimalString, 10);
|
48
|
+
const preDecimal = parseInt(roundingDownMatch[2], 10);
|
49
|
+
|
50
|
+
const origPostDecimalString = roundingDownMatch[3];
|
51
|
+
|
52
|
+
let newPostDecimal = (postDecimal + 10 - lastDigit).toString();
|
53
|
+
let carry = 0;
|
54
|
+
if (newPostDecimal.length > postDecimal.toString().length) {
|
55
|
+
// Left-shift
|
56
|
+
newPostDecimal = newPostDecimal.substring(1);
|
57
|
+
carry = 1;
|
58
|
+
}
|
59
|
+
|
60
|
+
// parseInt(...).toString() removes leading zeroes. Add them back.
|
61
|
+
while (newPostDecimal.length < origPostDecimalString.length) {
|
62
|
+
newPostDecimal = carry.toString(10) + newPostDecimal;
|
63
|
+
carry = 0;
|
64
|
+
}
|
65
|
+
|
66
|
+
text = `${negativeSign + (preDecimal + carry).toString()}.${newPostDecimal}`;
|
67
|
+
}
|
68
|
+
|
69
|
+
text = text.replace(fixRoundingUpExp, '$1');
|
70
|
+
|
71
|
+
return cleanUpNumber(text);
|
72
|
+
};
|
73
|
+
|
74
|
+
const numberExp = /^([-]?)(\d*)[.](\d+)$/;
|
75
|
+
export const getLenAfterDecimal = (numberAsString: string) => {
|
76
|
+
const numberMatch = numberExp.exec(numberAsString);
|
77
|
+
if (!numberMatch) {
|
78
|
+
// If not a match, either the number is exponential notation (or is something
|
79
|
+
// like NaN or Infinity)
|
80
|
+
if (numberAsString.search(/[eE]/) !== -1 || /^[a-zA-Z]+$/.exec(numberAsString)) {
|
81
|
+
return -1;
|
82
|
+
// Or it has no decimal point
|
83
|
+
} else {
|
84
|
+
return 0;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
const afterDecimalLen = numberMatch[3].length;
|
89
|
+
return afterDecimalLen;
|
90
|
+
};
|
91
|
+
|
92
|
+
// [reference] should be a string representation of a base-10 number (no exponential (e.g. 10e10))
|
93
|
+
export const toStringOfSamePrecision = (num: number, ...references: string[]): string => {
|
94
|
+
const text = num.toString(10);
|
95
|
+
const textMatch = numberExp.exec(text);
|
96
|
+
if (!textMatch) {
|
97
|
+
return text;
|
98
|
+
}
|
99
|
+
|
100
|
+
let decimalPlaces = -1;
|
101
|
+
for (const reference of references) {
|
102
|
+
decimalPlaces = Math.max(getLenAfterDecimal(reference), decimalPlaces);
|
103
|
+
}
|
104
|
+
|
105
|
+
if (decimalPlaces === -1) {
|
106
|
+
return toRoundedString(num);
|
107
|
+
}
|
108
|
+
|
109
|
+
// Make text's after decimal length match [afterDecimalLen].
|
110
|
+
let postDecimal = textMatch[3].substring(0, decimalPlaces);
|
111
|
+
let preDecimal = textMatch[2];
|
112
|
+
const nextDigit = textMatch[3].charAt(decimalPlaces);
|
113
|
+
|
114
|
+
if (nextDigit !== '') {
|
115
|
+
const asNumber = parseInt(nextDigit, 10);
|
116
|
+
if (asNumber >= 5) {
|
117
|
+
// Don't attempt to parseInt() an empty string.
|
118
|
+
if (postDecimal.length > 0) {
|
119
|
+
const leadingZeroMatch = /^(0+)(\d*)$/.exec(postDecimal);
|
120
|
+
|
121
|
+
let leadingZeroes = '';
|
122
|
+
let postLeading = postDecimal;
|
123
|
+
if (leadingZeroMatch) {
|
124
|
+
leadingZeroes = leadingZeroMatch[1];
|
125
|
+
postLeading = leadingZeroMatch[2];
|
126
|
+
}
|
127
|
+
|
128
|
+
postDecimal = (parseInt(postDecimal) + 1).toString();
|
129
|
+
|
130
|
+
// If postDecimal got longer, remove leading zeroes if possible
|
131
|
+
if (postDecimal.length > postLeading.length && leadingZeroes.length > 0) {
|
132
|
+
leadingZeroes = leadingZeroes.substring(1);
|
133
|
+
}
|
134
|
+
|
135
|
+
postDecimal = leadingZeroes + postDecimal;
|
136
|
+
}
|
137
|
+
|
138
|
+
if (postDecimal.length === 0 || postDecimal.length > decimalPlaces) {
|
139
|
+
preDecimal = (parseInt(preDecimal) + 1).toString();
|
140
|
+
postDecimal = postDecimal.substring(1);
|
141
|
+
}
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
const negativeSign = textMatch[1];
|
146
|
+
return cleanUpNumber(`${negativeSign}${preDecimal}.${postDecimal}`);
|
147
|
+
};
|
package/src/rendering/Display.ts
CHANGED
@@ -1,9 +1,24 @@
|
|
1
|
+
/**
|
2
|
+
* Handles `HTMLCanvasElement`s (or other drawing surfaces if being used) used to display the editor's contents.
|
3
|
+
*
|
4
|
+
* @example
|
5
|
+
* ```
|
6
|
+
* const editor = new Editor(document.body);
|
7
|
+
* const w = editor.display.width;
|
8
|
+
* const h = editor.display.height;
|
9
|
+
* const center = Vec2.of(w / 2, h / 2);
|
10
|
+
* const colorAtCenter = editor.display.getColorAt(center);
|
11
|
+
* ```
|
12
|
+
*
|
13
|
+
* @packageDocumentation
|
14
|
+
*/
|
15
|
+
|
1
16
|
import AbstractRenderer from './renderers/AbstractRenderer';
|
2
17
|
import CanvasRenderer from './renderers/CanvasRenderer';
|
3
18
|
import { Editor } from '../Editor';
|
4
19
|
import { EditorEventType } from '../types';
|
5
20
|
import DummyRenderer from './renderers/DummyRenderer';
|
6
|
-
import { Point2, Vec2 } from '../
|
21
|
+
import { Point2, Vec2 } from '../math/Vec2';
|
7
22
|
import RenderingCache from './caching/RenderingCache';
|
8
23
|
import TextOnlyRenderer from './renderers/TextOnlyRenderer';
|
9
24
|
import Color4 from '../Color4';
|
@@ -18,10 +33,12 @@ export default class Display {
|
|
18
33
|
private dryInkRenderer: AbstractRenderer;
|
19
34
|
private wetInkRenderer: AbstractRenderer;
|
20
35
|
private textRenderer: TextOnlyRenderer;
|
36
|
+
private textRerenderOutput: HTMLElement|null = null;
|
21
37
|
private cache: RenderingCache;
|
22
38
|
private resizeSurfacesCallback?: ()=> void;
|
23
39
|
private flattenCallback?: ()=> void;
|
24
40
|
|
41
|
+
/** @internal */
|
25
42
|
public constructor(
|
26
43
|
private editor: Editor, mode: RenderingMode, private parent: HTMLElement|null
|
27
44
|
) {
|
@@ -59,10 +76,10 @@ export default class Display {
|
|
59
76
|
return this.dryInkRenderer.canRenderFromWithoutDataLoss(renderer);
|
60
77
|
},
|
61
78
|
blockResolution: cacheBlockResolution,
|
62
|
-
cacheSize: 500 * 500 * 4 *
|
79
|
+
cacheSize: 500 * 500 * 4 * 150,
|
63
80
|
maxScale: 1.5,
|
64
|
-
minComponentsPerCache:
|
65
|
-
minComponentsToUseCache:
|
81
|
+
minComponentsPerCache: 45,
|
82
|
+
minComponentsToUseCache: 105,
|
66
83
|
});
|
67
84
|
|
68
85
|
this.editor.notifier.on(EditorEventType.DisplayResized, event => {
|
@@ -74,9 +91,11 @@ export default class Display {
|
|
74
91
|
});
|
75
92
|
}
|
76
93
|
|
77
|
-
|
78
|
-
|
79
|
-
|
94
|
+
/**
|
95
|
+
* @returns the visible width of the display (e.g. how much
|
96
|
+
* space the display's element takes up in the x direction
|
97
|
+
* in the DOM).
|
98
|
+
*/
|
80
99
|
public get width(): number {
|
81
100
|
return this.dryInkRenderer.displaySize().x;
|
82
101
|
}
|
@@ -85,10 +104,15 @@ export default class Display {
|
|
85
104
|
return this.dryInkRenderer.displaySize().y;
|
86
105
|
}
|
87
106
|
|
107
|
+
/** @internal */
|
88
108
|
public getCache() {
|
89
109
|
return this.cache;
|
90
110
|
}
|
91
111
|
|
112
|
+
/**
|
113
|
+
* @returns the color at the given point on the dry ink renderer, or `null` if `screenPos`
|
114
|
+
* is not on the display.
|
115
|
+
*/
|
92
116
|
public getColorAt = (_screenPos: Point2): Color4|null => {
|
93
117
|
return null;
|
94
118
|
};
|
@@ -158,20 +182,35 @@ export default class Display {
|
|
158
182
|
rerenderButton.classList.add('rerenderButton');
|
159
183
|
rerenderButton.innerText = this.editor.localization.rerenderAsText;
|
160
184
|
|
161
|
-
|
162
|
-
|
185
|
+
this.textRerenderOutput = document.createElement('div');
|
186
|
+
this.textRerenderOutput.setAttribute('aria-live', 'polite');
|
163
187
|
|
164
188
|
rerenderButton.onclick = () => {
|
165
|
-
this.
|
166
|
-
this.editor.image.render(this.textRenderer, this.editor.viewport);
|
167
|
-
rerenderOutput.innerText = this.textRenderer.getDescription();
|
189
|
+
this.rerenderAsText();
|
168
190
|
};
|
169
191
|
|
170
|
-
textRendererOutputContainer.replaceChildren(rerenderButton,
|
192
|
+
textRendererOutputContainer.replaceChildren(rerenderButton, this.textRerenderOutput);
|
171
193
|
this.editor.createHTMLOverlay(textRendererOutputContainer);
|
172
194
|
}
|
173
195
|
|
174
|
-
|
196
|
+
/**
|
197
|
+
* Rerenders the text-based display.
|
198
|
+
* The text-based display is intended for screen readers and can be navigated to by pressing `tab`.
|
199
|
+
*/
|
200
|
+
public rerenderAsText() {
|
201
|
+
this.textRenderer.clear();
|
202
|
+
this.editor.image.render(this.textRenderer, this.editor.viewport);
|
203
|
+
|
204
|
+
if (this.textRerenderOutput) {
|
205
|
+
this.textRerenderOutput.innerText = this.textRenderer.getDescription();
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
/**
|
210
|
+
* Clears the drawing surfaces and otherwise prepares for a rerender.
|
211
|
+
*
|
212
|
+
* @returns the dry ink renderer.
|
213
|
+
*/
|
175
214
|
public startRerender(): AbstractRenderer {
|
176
215
|
this.resizeSurfacesCallback?.();
|
177
216
|
this.wetInkRenderer.clear();
|
@@ -180,19 +219,28 @@ export default class Display {
|
|
180
219
|
return this.dryInkRenderer;
|
181
220
|
}
|
182
221
|
|
222
|
+
/**
|
223
|
+
* If `draftMode`, the dry ink renderer is configured to render
|
224
|
+
* low-quality output.
|
225
|
+
*/
|
183
226
|
public setDraftMode(draftMode: boolean) {
|
184
227
|
this.dryInkRenderer.setDraftMode(draftMode);
|
185
228
|
}
|
186
229
|
|
230
|
+
/** @internal */
|
187
231
|
public getDryInkRenderer(): AbstractRenderer {
|
188
232
|
return this.dryInkRenderer;
|
189
233
|
}
|
190
234
|
|
235
|
+
/**
|
236
|
+
* @returns The renderer used for showing action previews (e.g. an unfinished stroke).
|
237
|
+
* The `wetInkRenderer`'s surface is stacked above the `dryInkRenderer`'s.
|
238
|
+
*/
|
191
239
|
public getWetInkRenderer(): AbstractRenderer {
|
192
240
|
return this.wetInkRenderer;
|
193
241
|
}
|
194
242
|
|
195
|
-
|
243
|
+
/** Re-renders the contents of the wetInkRenderer onto the dryInkRenderer. */
|
196
244
|
public flatten() {
|
197
245
|
this.flattenCallback?.();
|
198
246
|
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
/* @jest-environment jsdom */
|
2
2
|
|
3
|
-
import Rect2 from '../../
|
4
|
-
import { Vec2 } from '../../
|
3
|
+
import Rect2 from '../../math/Rect2';
|
4
|
+
import { Vec2 } from '../../math/Vec2';
|
5
5
|
import CacheRecord from './CacheRecord';
|
6
6
|
import { createCache } from './testUtils';
|
7
7
|
|
@@ -11,7 +11,7 @@ describe('CacheRecord', () => {
|
|
11
11
|
const { cache } = createCache(undefined, {
|
12
12
|
blockResolution,
|
13
13
|
});
|
14
|
-
const state = cache
|
14
|
+
const state = cache['sharedState'];
|
15
15
|
|
16
16
|
const record = new CacheRecord(() => {}, state);
|
17
17
|
|
@@ -1,5 +1,5 @@
|
|
1
|
-
import Mat33 from '../../
|
2
|
-
import Rect2 from '../../
|
1
|
+
import Mat33 from '../../math/Mat33';
|
2
|
+
import Rect2 from '../../math/Rect2';
|
3
3
|
import AbstractRenderer from '../renderers/AbstractRenderer';
|
4
4
|
import { BeforeDeallocCallback, CacheState } from './types';
|
5
5
|
|
@@ -11,6 +11,9 @@ export default class CacheRecord {
|
|
11
11
|
private lastUsedCycle: number;
|
12
12
|
private allocd: boolean = false;
|
13
13
|
|
14
|
+
// For debugging
|
15
|
+
public allocCount: number = 0;
|
16
|
+
|
14
17
|
public constructor(
|
15
18
|
private onBeforeDeallocCallback: BeforeDeallocCallback|null,
|
16
19
|
private cacheState: CacheState,
|
@@ -46,6 +49,7 @@ export default class CacheRecord {
|
|
46
49
|
this.allocd = true;
|
47
50
|
this.onBeforeDeallocCallback = newDeallocCallback;
|
48
51
|
this.lastUsedCycle = this.cacheState.currentRenderingCycle;
|
52
|
+
this.allocCount ++;
|
49
53
|
}
|
50
54
|
|
51
55
|
public getLastUsedCycle(): number {
|
@@ -1,38 +1,64 @@
|
|
1
|
-
import { BeforeDeallocCallback,
|
1
|
+
import { BeforeDeallocCallback, CacheProps, CacheState } from './types';
|
2
2
|
import CacheRecord from './CacheRecord';
|
3
|
-
import Rect2 from '../../
|
3
|
+
import Rect2 from '../../math/Rect2';
|
4
4
|
|
5
|
+
const debugMode = false;
|
5
6
|
|
6
7
|
export class CacheRecordManager {
|
7
8
|
// Fixed-size array: Cache blocks are assigned indicies into [cachedCanvases].
|
8
9
|
private cacheRecords: CacheRecord[] = [];
|
9
10
|
private maxCanvases: number;
|
11
|
+
private cacheState: CacheState;
|
10
12
|
|
11
|
-
public constructor(
|
12
|
-
const cacheProps = cacheState.props;
|
13
|
+
public constructor(cacheProps: CacheProps) {
|
13
14
|
this.maxCanvases = Math.ceil(
|
14
15
|
// Assuming four components per pixel:
|
15
16
|
cacheProps.cacheSize / 4 / cacheProps.blockResolution.x / cacheProps.blockResolution.y
|
16
17
|
);
|
17
18
|
}
|
18
19
|
|
20
|
+
public setSharedState(state: CacheState) {
|
21
|
+
this.cacheState = state;
|
22
|
+
}
|
23
|
+
|
19
24
|
public allocCanvas(drawTo: Rect2, onDealloc: BeforeDeallocCallback): CacheRecord {
|
20
25
|
if (this.cacheRecords.length < this.maxCanvases) {
|
21
26
|
const record: CacheRecord = new CacheRecord(
|
22
27
|
onDealloc,
|
23
|
-
|
24
|
-
...this.cacheState,
|
25
|
-
recordManager: this,
|
26
|
-
},
|
28
|
+
this.cacheState,
|
27
29
|
);
|
28
30
|
record.setRenderingRegion(drawTo);
|
29
31
|
this.cacheRecords.push(record);
|
30
32
|
|
33
|
+
if (debugMode) {
|
34
|
+
console.log('[Cache] Cache spaces used: ', this.cacheRecords.length, ' of ', this.maxCanvases);
|
35
|
+
}
|
36
|
+
|
31
37
|
return record;
|
32
38
|
} else {
|
33
39
|
const lru = this.getLeastRecentlyUsedRecord()!;
|
40
|
+
|
41
|
+
if (debugMode) {
|
42
|
+
console.log(
|
43
|
+
'[Cache] Re-alloc. Times allocated: ', lru.allocCount,
|
44
|
+
'\nLast used cycle: ', lru.getLastUsedCycle(),
|
45
|
+
'\nCurrent cycle: ', this.cacheState.currentRenderingCycle
|
46
|
+
);
|
47
|
+
}
|
48
|
+
|
34
49
|
lru.realloc(onDealloc);
|
35
50
|
lru.setRenderingRegion(drawTo);
|
51
|
+
|
52
|
+
if (debugMode) {
|
53
|
+
console.log(
|
54
|
+
'[Cache] Now re-alloc\'d. Last used cycle: ', lru.getLastUsedCycle()
|
55
|
+
);
|
56
|
+
console.assert(
|
57
|
+
lru['cacheState'] === this.cacheState,
|
58
|
+
'[Cache] Unequal cache states! cacheState should be a shared object!'
|
59
|
+
);
|
60
|
+
}
|
61
|
+
|
36
62
|
return lru;
|
37
63
|
}
|
38
64
|
}
|
@@ -3,11 +3,11 @@
|
|
3
3
|
import DummyRenderer from '../renderers/DummyRenderer';
|
4
4
|
import { createCache } from './testUtils';
|
5
5
|
import Stroke from '../../components/Stroke';
|
6
|
-
import Path from '../../
|
6
|
+
import Path from '../../math/Path';
|
7
7
|
import Color4 from '../../Color4';
|
8
8
|
import EditorImage from '../../EditorImage';
|
9
9
|
import Viewport from '../../Viewport';
|
10
|
-
import Mat33 from '../../
|
10
|
+
import Mat33 from '../../math/Mat33';
|
11
11
|
|
12
12
|
describe('RenderingCache', () => {
|
13
13
|
const testPath = Path.fromString('M0,0 l100,500 l-20,20 L-100,-100');
|
@@ -35,7 +35,7 @@ describe('RenderingCache', () => {
|
|
35
35
|
expect(lastRenderer).not.toBeNull();
|
36
36
|
expect(lastRenderer!.renderedPathCount).toBe(1);
|
37
37
|
|
38
|
-
editor.dispatch(
|
38
|
+
editor.dispatch(Viewport.transformBy(Mat33.scaling2D(0.1)));
|
39
39
|
editor.image.renderWithCache(screenRenderer, cache, editor.viewport);
|
40
40
|
expect(allocdRenderers).toBe(1);
|
41
41
|
expect(lastRenderer!.renderedPathCount).toBe(1);
|
@@ -1,49 +1,44 @@
|
|
1
1
|
import { ImageNode } from '../../EditorImage';
|
2
|
-
import Rect2 from '../../
|
2
|
+
import Rect2 from '../../math/Rect2';
|
3
3
|
import Viewport from '../../Viewport';
|
4
4
|
import AbstractRenderer from '../renderers/AbstractRenderer';
|
5
5
|
import RenderingCacheNode from './RenderingCacheNode';
|
6
6
|
import { CacheRecordManager } from './CacheRecordManager';
|
7
|
-
import { CacheProps, CacheState
|
7
|
+
import { CacheProps, CacheState } from './types';
|
8
8
|
|
9
9
|
export default class RenderingCache {
|
10
|
-
private
|
10
|
+
private sharedState: CacheState;
|
11
11
|
private recordManager: CacheRecordManager;
|
12
12
|
private rootNode: RenderingCacheNode|null;
|
13
13
|
|
14
14
|
public constructor(cacheProps: CacheProps) {
|
15
|
-
this.
|
15
|
+
this.recordManager = new CacheRecordManager(cacheProps);
|
16
|
+
this.sharedState = {
|
16
17
|
props: cacheProps,
|
17
18
|
currentRenderingCycle: 0,
|
18
|
-
};
|
19
|
-
this.recordManager = new CacheRecordManager(this.partialSharedState);
|
20
|
-
}
|
21
|
-
|
22
|
-
public getSharedState(): CacheState {
|
23
|
-
return {
|
24
|
-
...this.partialSharedState,
|
25
19
|
recordManager: this.recordManager,
|
26
20
|
};
|
21
|
+
this.recordManager.setSharedState(this.sharedState);
|
27
22
|
}
|
28
23
|
|
29
24
|
public render(screenRenderer: AbstractRenderer, image: ImageNode, viewport: Viewport) {
|
30
25
|
const visibleRect = viewport.visibleRect;
|
31
|
-
this.
|
26
|
+
this.sharedState.currentRenderingCycle ++;
|
32
27
|
|
33
28
|
// If we can't use the cache,
|
34
|
-
if (!this.
|
29
|
+
if (!this.sharedState.props.isOfCorrectType(screenRenderer)) {
|
35
30
|
image.render(screenRenderer, visibleRect);
|
36
31
|
return;
|
37
32
|
}
|
38
33
|
|
39
34
|
if (!this.rootNode) {
|
40
35
|
// Adjust the node so that it has the correct aspect ratio
|
41
|
-
const res = this.
|
36
|
+
const res = this.sharedState.props.blockResolution;
|
42
37
|
|
43
38
|
const topLeft = visibleRect.topLeft;
|
44
39
|
this.rootNode = new RenderingCacheNode(
|
45
40
|
new Rect2(topLeft.x, topLeft.y, res.x, res.y),
|
46
|
-
this.
|
41
|
+
this.sharedState
|
47
42
|
);
|
48
43
|
}
|
49
44
|
|
@@ -54,7 +49,7 @@ export default class RenderingCache {
|
|
54
49
|
this.rootNode = this.rootNode!.smallestChildContaining(visibleRect) ?? this.rootNode;
|
55
50
|
|
56
51
|
const visibleLeaves = image.getLeavesIntersectingRegion(viewport.visibleRect, rect => screenRenderer.isTooSmallToRender(rect));
|
57
|
-
if (visibleLeaves.length > this.
|
52
|
+
if (visibleLeaves.length > this.sharedState.props.minComponentsToUseCache) {
|
58
53
|
this.rootNode!.renderItems(screenRenderer, [ image ], viewport);
|
59
54
|
} else {
|
60
55
|
image.render(screenRenderer, visibleRect);
|
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
import Color4 from '../../Color4';
|
5
5
|
import { ImageNode, sortLeavesByZIndex } from '../../EditorImage';
|
6
|
-
import Rect2 from '../../
|
6
|
+
import Rect2 from '../../math/Rect2';
|
7
7
|
import Viewport from '../../Viewport';
|
8
8
|
import AbstractRenderer from '../renderers/AbstractRenderer';
|
9
9
|
import CacheRecord from './CacheRecord';
|
@@ -151,12 +151,15 @@ export default class RenderingCacheNode {
|
|
151
151
|
return result;
|
152
152
|
}
|
153
153
|
|
154
|
-
|
155
|
-
|
154
|
+
// Returns true iff all elems of this.renderedIds are in sortedIds.
|
155
|
+
// sortedIds should be sorted by z-index (or some other order, so long as they are
|
156
|
+
// sorted by the same thing as this.renderedIds.)
|
157
|
+
private allRenderedIdsIn(sortedIds: number[]) {
|
158
|
+
if (this.renderedIds.length > sortedIds.length) {
|
156
159
|
return false;
|
157
160
|
}
|
158
161
|
|
159
|
-
for (let i = 0; i <
|
162
|
+
for (let i = 0; i < this.renderedIds.length; i++) {
|
160
163
|
if (sortedIds[i] !== this.renderedIds[i]) {
|
161
164
|
return false;
|
162
165
|
}
|
@@ -165,6 +168,14 @@ export default class RenderingCacheNode {
|
|
165
168
|
return true;
|
166
169
|
}
|
167
170
|
|
171
|
+
private renderingIsUpToDate(sortedIds: number[]) {
|
172
|
+
if (this.cachedRenderer === null || sortedIds.length !== this.renderedIds.length) {
|
173
|
+
return false;
|
174
|
+
}
|
175
|
+
|
176
|
+
return this.allRenderedIdsIn(sortedIds);
|
177
|
+
}
|
178
|
+
|
168
179
|
// Render all [items] within [viewport]
|
169
180
|
public renderItems(screenRenderer: AbstractRenderer, items: ImageNode[], viewport: Viewport) {
|
170
181
|
if (
|
@@ -238,8 +249,7 @@ export default class RenderingCacheNode {
|
|
238
249
|
}
|
239
250
|
|
240
251
|
// Is it worth it to render the items?
|
241
|
-
// TODO:
|
242
|
-
// TODO: Determine whether it is 'worth it' to cache this depending on rendering time.
|
252
|
+
// TODO: Consider replacing this with something performace based.
|
243
253
|
if (leavesByIds.length > this.cacheState.props.minComponentsPerCache) {
|
244
254
|
let fullRerenderNeeded = true;
|
245
255
|
if (!this.cachedRenderer) {
|
@@ -247,7 +257,7 @@ export default class RenderingCacheNode {
|
|
247
257
|
this.region,
|
248
258
|
() => this.onRegionDealloc()
|
249
259
|
);
|
250
|
-
} else if (leavesByIds.length > this.renderedIds.length && this.renderedMaxZIndex !== null) {
|
260
|
+
} else if (leavesByIds.length > this.renderedIds.length && this.allRenderedIdsIn(leafIds) && this.renderedMaxZIndex !== null) {
|
251
261
|
// We often don't need to do a full re-render even if something's changed.
|
252
262
|
// Check whether we can just draw on top of the existing cache.
|
253
263
|
const newLeaves = [];
|
@@ -287,6 +297,12 @@ export default class RenderingCacheNode {
|
|
287
297
|
screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: Color4.clay });
|
288
298
|
}
|
289
299
|
}
|
300
|
+
} else if (debugMode) {
|
301
|
+
console.log('Decided on a full re-render. Reason: At least one of the following is false:',
|
302
|
+
'\n leafIds.length > this.renderedIds.length: ', leafIds.length > this.renderedIds.length,
|
303
|
+
'\n this.allRenderedIdsIn(leafIds): ', this.allRenderedIdsIn(leafIds),
|
304
|
+
'\n this.renderedMaxZIndex !== null: ', this.renderedMaxZIndex !== null,
|
305
|
+
'\n\nthis.rerenderedIds: ', this.renderedIds, ', leafIds: ', leafIds);
|
290
306
|
}
|
291
307
|
|
292
308
|
if (fullRerenderNeeded) {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Vec2 } from '../../
|
1
|
+
import { Vec2 } from '../../math/Vec2';
|
2
2
|
import AbstractRenderer from '../renderers/AbstractRenderer';
|
3
3
|
import { CacheRecordManager } from './CacheRecordManager';
|
4
4
|
|
@@ -27,13 +27,8 @@ export interface CacheProps {
|
|
27
27
|
minComponentsToUseCache: number;
|
28
28
|
}
|
29
29
|
|
30
|
-
|
31
|
-
// we need to separate partial/non-partial state.
|
32
|
-
export interface PartialCacheState {
|
30
|
+
export interface CacheState {
|
33
31
|
currentRenderingCycle: number;
|
34
32
|
props: CacheProps;
|
35
|
-
}
|
36
|
-
|
37
|
-
export interface CacheState extends PartialCacheState {
|
38
33
|
recordManager: CacheRecordManager;
|
39
34
|
}
|
@@ -1,10 +1,14 @@
|
|
1
1
|
|
2
2
|
export interface TextRendererLocalization {
|
3
|
+
pathNodeCount(pathCount: number): string;
|
4
|
+
textNodeCount(nodeCount: number): string;
|
3
5
|
textNode(content: string): string;
|
4
6
|
rerenderAsText: string;
|
5
7
|
}
|
6
8
|
|
7
9
|
export const defaultTextRendererLocalization: TextRendererLocalization = {
|
10
|
+
pathNodeCount: (count: number) => `There are ${count} visible path objects.`,
|
11
|
+
textNodeCount: (count: number) => `There are ${count} visible text nodes.`,
|
8
12
|
textNode: (content: string) => `Text: ${content}`,
|
9
13
|
rerenderAsText: 'Re-render as text',
|
10
14
|
};
|
@@ -1,9 +1,9 @@
|
|
1
1
|
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
2
2
|
import { TextStyle } from '../../components/Text';
|
3
|
-
import Mat33 from '../../
|
4
|
-
import Path, { PathCommand, PathCommandType } from '../../
|
5
|
-
import Rect2 from '../../
|
6
|
-
import { Point2, Vec2 } from '../../
|
3
|
+
import Mat33 from '../../math/Mat33';
|
4
|
+
import Path, { PathCommand, PathCommandType } from '../../math/Path';
|
5
|
+
import Rect2 from '../../math/Rect2';
|
6
|
+
import { Point2, Vec2 } from '../../math/Vec2';
|
7
7
|
import Viewport from '../../Viewport';
|
8
8
|
import RenderingStyle, { stylesEqual } from '../RenderingStyle';
|
9
9
|
|
@@ -1,9 +1,9 @@
|
|
1
1
|
import Color4 from '../../Color4';
|
2
2
|
import Text, { TextStyle } from '../../components/Text';
|
3
|
-
import Mat33 from '../../
|
4
|
-
import Rect2 from '../../
|
5
|
-
import { Point2, Vec2 } from '../../
|
6
|
-
import Vec3 from '../../
|
3
|
+
import Mat33 from '../../math/Mat33';
|
4
|
+
import Rect2 from '../../math/Rect2';
|
5
|
+
import { Point2, Vec2 } from '../../math/Vec2';
|
6
|
+
import Vec3 from '../../math/Vec3';
|
7
7
|
import Viewport from '../../Viewport';
|
8
8
|
import RenderingStyle from '../RenderingStyle';
|
9
9
|
import AbstractRenderer, { RenderablePathSpec } from './AbstractRenderer';
|
@@ -64,7 +64,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
64
64
|
this.minRenderSizeAnyDimen = 2;
|
65
65
|
} else {
|
66
66
|
this.minSquareCurveApproxDist = 1;
|
67
|
-
this.minRenderSizeBothDimens =
|
67
|
+
this.minRenderSizeBothDimens = 0.5;
|
68
68
|
this.minRenderSizeAnyDimen = 0;
|
69
69
|
}
|
70
70
|
}
|