js-draw 0.1.9 → 0.1.12
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/CHANGELOG.md +18 -0
- package/README.md +15 -3
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +7 -2
- package/dist/src/Editor.js +74 -25
- package/dist/src/EditorImage.d.ts +1 -1
- package/dist/src/EditorImage.js +2 -2
- package/dist/src/Pointer.d.ts +1 -1
- package/dist/src/Pointer.js +1 -1
- package/dist/src/SVGLoader.d.ts +1 -1
- package/dist/src/SVGLoader.js +14 -6
- package/dist/src/UndoRedoHistory.js +3 -0
- package/dist/src/Viewport.d.ts +8 -25
- package/dist/src/Viewport.js +17 -10
- package/dist/src/bundle/bundled.d.ts +2 -1
- package/dist/src/bundle/bundled.js +2 -1
- package/dist/src/commands/Command.d.ts +2 -2
- package/dist/src/commands/Command.js +4 -4
- package/dist/src/commands/Duplicate.d.ts +1 -1
- package/dist/src/commands/Duplicate.js +1 -1
- package/dist/src/commands/Erase.d.ts +1 -1
- package/dist/src/commands/Erase.js +1 -1
- package/dist/src/commands/localization.d.ts +1 -1
- package/dist/src/components/AbstractComponent.d.ts +3 -3
- package/dist/src/components/AbstractComponent.js +2 -2
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +3 -3
- package/dist/src/components/SVGGlobalAttributesObject.js +1 -1
- package/dist/src/components/Stroke.d.ts +4 -4
- package/dist/src/components/Stroke.js +2 -2
- package/dist/src/components/Text.d.ts +3 -3
- package/dist/src/components/Text.js +3 -3
- package/dist/src/components/UnknownSVGObject.d.ts +3 -3
- package/dist/src/components/UnknownSVGObject.js +1 -1
- 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/localization.d.ts +1 -0
- package/dist/src/localization.js +5 -1
- package/dist/src/localizations/en.d.ts +3 -0
- package/dist/src/localizations/en.js +4 -0
- package/dist/src/localizations/es.d.ts +3 -0
- package/dist/src/localizations/es.js +18 -0
- package/dist/src/localizations/getLocalizationTable.d.ts +3 -0
- package/dist/src/localizations/getLocalizationTable.js +43 -0
- package/dist/src/{geometry → math}/LineSegment2.d.ts +1 -0
- package/dist/src/{geometry → math}/LineSegment2.js +16 -0
- package/dist/src/{geometry → math}/Mat33.d.ts +0 -0
- package/dist/src/{geometry → math}/Mat33.js +0 -0
- package/dist/src/{geometry → math}/Path.d.ts +2 -1
- package/dist/src/{geometry → math}/Path.js +58 -51
- package/dist/src/{geometry → math}/Rect2.d.ts +1 -0
- package/dist/src/{geometry → math}/Rect2.js +16 -0
- package/dist/src/{geometry → math}/Vec2.d.ts +0 -0
- package/dist/src/{geometry → math}/Vec2.js +0 -0
- package/dist/src/{geometry → math}/Vec3.d.ts +1 -1
- package/dist/src/{geometry → math}/Vec3.js +1 -1
- package/dist/src/math/rounding.d.ts +3 -0
- package/dist/src/math/rounding.js +120 -0
- package/dist/src/rendering/Display.d.ts +3 -1
- package/dist/src/rendering/Display.js +16 -10
- package/dist/src/rendering/caching/CacheRecord.d.ts +2 -2
- package/dist/src/rendering/caching/CacheRecord.js +1 -1
- package/dist/src/rendering/caching/CacheRecordManager.d.ts +1 -1
- package/dist/src/rendering/caching/RenderingCache.js +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +5 -7
- package/dist/src/toolbar/icons.d.ts +3 -0
- package/dist/src/toolbar/icons.js +152 -142
- package/dist/src/toolbar/localization.d.ts +4 -1
- package/dist/src/toolbar/localization.js +4 -1
- package/dist/src/toolbar/makeColorInput.js +2 -1
- 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 +31 -0
- package/dist/src/toolbar/widgets/HandToolWidget.js +10 -3
- package/dist/src/toolbar/widgets/SelectionWidget.d.ts +0 -1
- package/dist/src/toolbar/widgets/SelectionWidget.js +23 -30
- package/dist/src/tools/BaseTool.d.ts +2 -1
- package/dist/src/tools/BaseTool.js +3 -0
- package/dist/src/tools/Eraser.js +1 -1
- package/dist/src/tools/PanZoom.d.ts +3 -2
- package/dist/src/tools/PanZoom.js +32 -16
- package/dist/src/tools/SelectionTool.d.ts +11 -4
- package/dist/src/tools/SelectionTool.js +135 -23
- package/dist/src/tools/TextTool.js +1 -1
- package/dist/src/tools/ToolController.js +6 -2
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/dist/src/types.d.ts +13 -6
- package/dist/src/types.js +1 -0
- package/package.json +9 -1
- package/src/Editor.ts +86 -24
- package/src/EditorImage.test.ts +2 -4
- package/src/EditorImage.ts +2 -2
- package/src/Pointer.ts +1 -1
- package/src/SVGLoader.ts +14 -6
- package/src/UndoRedoHistory.ts +4 -0
- package/src/Viewport.ts +21 -17
- package/src/bundle/bundled.ts +2 -1
- package/src/commands/Command.ts +5 -5
- package/src/commands/Duplicate.ts +1 -1
- package/src/commands/Erase.ts +1 -1
- package/src/commands/localization.ts +1 -1
- package/src/components/AbstractComponent.ts +4 -4
- package/src/components/SVGGlobalAttributesObject.ts +3 -3
- package/src/components/Stroke.test.ts +3 -5
- package/src/components/Stroke.ts +4 -4
- package/src/components/Text.test.ts +2 -2
- package/src/components/Text.ts +3 -3
- package/src/components/UnknownSVGObject.ts +3 -3
- 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/localization.ts +6 -0
- package/src/localizations/en.ts +8 -0
- package/src/localizations/es.ts +63 -0
- package/src/localizations/getLocalizationTable.test.ts +27 -0
- package/src/localizations/getLocalizationTable.ts +53 -0
- package/src/{geometry → math}/LineSegment2.test.ts +15 -0
- package/src/{geometry → math}/LineSegment2.ts +20 -0
- package/src/{geometry → math}/Mat33.test.ts +0 -0
- package/src/{geometry → math}/Mat33.ts +0 -0
- 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 +60 -57
- package/src/{geometry → math}/Rect2.test.ts +20 -7
- package/src/{geometry → math}/Rect2.ts +19 -1
- 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 +2 -2
- package/src/math/rounding.test.ts +40 -0
- package/src/math/rounding.ts +145 -0
- package/src/rendering/Display.ts +18 -10
- package/src/rendering/caching/CacheRecord.test.ts +2 -2
- package/src/rendering/caching/CacheRecord.ts +2 -2
- package/src/rendering/caching/CacheRecordManager.ts +1 -1
- package/src/rendering/caching/RenderingCache.test.ts +3 -3
- package/src/rendering/caching/RenderingCache.ts +1 -1
- package/src/rendering/caching/RenderingCacheNode.ts +23 -7
- package/src/rendering/caching/testUtils.ts +1 -1
- package/src/rendering/caching/types.ts +1 -1
- package/src/rendering/localization.ts +4 -0
- package/src/rendering/renderers/AbstractRenderer.ts +4 -4
- package/src/rendering/renderers/CanvasRenderer.ts +4 -4
- 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 +5 -8
- package/src/toolbar/icons.ts +167 -147
- package/src/toolbar/localization.ts +8 -2
- package/src/toolbar/makeColorInput.ts +2 -1
- package/src/toolbar/toolbar.css +7 -3
- package/src/toolbar/widgets/ActionButtonWidget.ts +31 -0
- package/src/toolbar/widgets/BaseWidget.ts +36 -0
- package/src/toolbar/widgets/HandToolWidget.ts +14 -3
- package/src/toolbar/widgets/SelectionWidget.ts +46 -41
- package/src/tools/BaseTool.ts +5 -1
- package/src/tools/Eraser.ts +2 -2
- package/src/tools/PanZoom.ts +39 -18
- package/src/tools/SelectionTool.test.ts +26 -5
- package/src/tools/SelectionTool.ts +162 -27
- package/src/tools/TextTool.ts +2 -2
- package/src/tools/ToolController.ts +6 -2
- package/src/tools/UndoRedoShortcut.test.ts +1 -1
- package/src/tools/localization.ts +2 -0
- package/src/types.ts +14 -5
- package/dist-test/test-dist-bundle.html +0 -42
@@ -15,7 +15,7 @@ describe('Path.toString', () => {
|
|
15
15
|
point: Vec2.of(0.3, 0.4),
|
16
16
|
},
|
17
17
|
]);
|
18
|
-
expect(path.toString()).toBe('
|
18
|
+
expect(path.toString()).toBe('M.1,.2L.3,.4');
|
19
19
|
});
|
20
20
|
|
21
21
|
it('should fix rounding errors', () => {
|
@@ -30,7 +30,8 @@ describe('Path.toString', () => {
|
|
30
30
|
point: Vec2.of(184.00482359999998, 1)
|
31
31
|
}
|
32
32
|
]);
|
33
|
-
|
33
|
+
|
34
|
+
expect(path.toString()).toBe('M.1,.2Q9999,-11 .0003,1.4L184.0048236,1');
|
34
35
|
});
|
35
36
|
|
36
37
|
it('should not remove trailing zeroes before decimal points', () => {
|
@@ -40,6 +41,14 @@ describe('Path.toString', () => {
|
|
40
41
|
point: Vec2.of(30.0001, 40.000000001),
|
41
42
|
},
|
42
43
|
]);
|
44
|
+
|
43
45
|
expect(path.toString()).toBe('M1000,2000000L30.0001,40');
|
44
46
|
});
|
47
|
+
|
48
|
+
it('deserialized path should serialize to the same/similar path, but with rounded components', () => {
|
49
|
+
const path1 = Path.fromString('M100,100 L101,101 Q102,102 90.000000001,89.99999999 Z');
|
50
|
+
expect(path1.toString()).toBe([
|
51
|
+
'M100,100', 'L101,101', 'Q102,102 90,90', 'L100,100'
|
52
|
+
].join(''));
|
53
|
+
});
|
45
54
|
});
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { Bezier } from 'bezier-js';
|
2
2
|
import { RenderablePathSpec } from '../rendering/renderers/AbstractRenderer';
|
3
3
|
import RenderingStyle from '../rendering/RenderingStyle';
|
4
|
+
import { toRoundedString, toStringOfSamePrecision } from './rounding';
|
4
5
|
import LineSegment2 from './LineSegment2';
|
5
6
|
import Mat33 from './Mat33';
|
6
7
|
import Rect2 from './Rect2';
|
@@ -170,8 +171,8 @@ export default class Path {
|
|
170
171
|
return result;
|
171
172
|
}
|
172
173
|
|
173
|
-
public
|
174
|
-
const startPoint =
|
174
|
+
public mapPoints(mapping: (point: Point2)=>Point2): Path {
|
175
|
+
const startPoint = mapping(this.startPoint);
|
175
176
|
const newParts: PathCommand[] = [];
|
176
177
|
|
177
178
|
let exhaustivenessCheck: never;
|
@@ -181,22 +182,22 @@ export default class Path {
|
|
181
182
|
case PathCommandType.LineTo:
|
182
183
|
newParts.push({
|
183
184
|
kind: part.kind,
|
184
|
-
point:
|
185
|
+
point: mapping(part.point),
|
185
186
|
});
|
186
187
|
break;
|
187
188
|
case PathCommandType.CubicBezierTo:
|
188
189
|
newParts.push({
|
189
190
|
kind: part.kind,
|
190
|
-
controlPoint1:
|
191
|
-
controlPoint2:
|
192
|
-
endPoint:
|
191
|
+
controlPoint1: mapping(part.controlPoint1),
|
192
|
+
controlPoint2: mapping(part.controlPoint2),
|
193
|
+
endPoint: mapping(part.endPoint),
|
193
194
|
});
|
194
195
|
break;
|
195
196
|
case PathCommandType.QuadraticBezierTo:
|
196
197
|
newParts.push({
|
197
198
|
kind: part.kind,
|
198
|
-
controlPoint:
|
199
|
-
endPoint:
|
199
|
+
controlPoint: mapping(part.controlPoint),
|
200
|
+
endPoint: mapping(part.endPoint),
|
200
201
|
});
|
201
202
|
break;
|
202
203
|
default:
|
@@ -208,6 +209,10 @@ export default class Path {
|
|
208
209
|
return new Path(startPoint, newParts);
|
209
210
|
}
|
210
211
|
|
212
|
+
public transformedBy(affineTransfm: Mat33): Path {
|
213
|
+
return this.mapPoints(point => affineTransfm.transformVec2(point));
|
214
|
+
}
|
215
|
+
|
211
216
|
// Creates a new path by joining [other] to the end of this path
|
212
217
|
public union(other: Path|null): Path {
|
213
218
|
if (!other) {
|
@@ -280,71 +285,69 @@ export default class Path {
|
|
280
285
|
}
|
281
286
|
|
282
287
|
public toString(): string {
|
283
|
-
|
288
|
+
// Hueristic: Try to determine whether converting absolute to relative commands is worth it.
|
289
|
+
// If we're near (0, 0), it probably isn't worth it and if bounding boxes are large,
|
290
|
+
// it also probably isn't worth it.
|
291
|
+
const makeRelativeCommands =
|
292
|
+
Math.abs(this.bbox.topLeft.x) > 10 && Math.abs(this.bbox.size.x) < 2
|
293
|
+
&& Math.abs(this.bbox.topLeft.y) > 10 && Math.abs(this.bbox.size.y) < 2;
|
294
|
+
|
295
|
+
return Path.toString(this.startPoint, this.parts, !makeRelativeCommands);
|
284
296
|
}
|
285
297
|
|
286
298
|
public serialize(): string {
|
287
299
|
return this.toString();
|
288
300
|
}
|
289
301
|
|
290
|
-
|
302
|
+
// [onlyAbsCommands]: True if we should avoid converting absolute coordinates to relative offsets -- such
|
303
|
+
// conversions can lead to smaller output strings, but also take time.
|
304
|
+
public static toString(startPoint: Point2, parts: PathCommand[], onlyAbsCommands: boolean = true): string {
|
291
305
|
const result: string[] = [];
|
292
306
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
const
|
297
|
-
const
|
298
|
-
|
299
|
-
|
300
|
-
if (text.indexOf('.') === -1) {
|
301
|
-
return text;
|
302
|
-
}
|
307
|
+
let prevPoint: Point2|undefined;
|
308
|
+
const addCommand = (command: string, ...points: Point2[]) => {
|
309
|
+
const absoluteCommandParts: string[] = [];
|
310
|
+
const relativeCommandParts: string[] = [];
|
311
|
+
const makeAbsCommand = !prevPoint || onlyAbsCommands;
|
312
|
+
const roundedPrevX = prevPoint ? toRoundedString(prevPoint.x) : '';
|
313
|
+
const roundedPrevY = prevPoint ? toRoundedString(prevPoint.y) : '';
|
303
314
|
|
304
|
-
const
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
}
|
315
|
+
for (const point of points) {
|
316
|
+
// Relative commands are often shorter as strings than absolute commands.
|
317
|
+
if (!makeAbsCommand) {
|
318
|
+
const xComponentRelative = toStringOfSamePrecision(point.x - prevPoint!.x, roundedPrevX, roundedPrevY);
|
319
|
+
const yComponentRelative = toStringOfSamePrecision(point.y - prevPoint!.y, roundedPrevX, roundedPrevY);
|
320
|
+
|
321
|
+
// No need for an additional separator if it starts with a '-'
|
322
|
+
if (yComponentRelative.charAt(0) === '-') {
|
323
|
+
relativeCommandParts.push(`${xComponentRelative}${yComponentRelative}`);
|
324
|
+
} else {
|
325
|
+
relativeCommandParts.push(`${xComponentRelative},${yComponentRelative}`);
|
326
|
+
}
|
327
|
+
} else {
|
328
|
+
const xComponent = toRoundedString(point.x);
|
329
|
+
const yComponent = toRoundedString(point.y);
|
320
330
|
|
321
|
-
|
322
|
-
while (newPostDecimal.length < origPostDecimalString.length) {
|
323
|
-
newPostDecimal = carry.toString(10) + newPostDecimal;
|
324
|
-
carry = 0;
|
331
|
+
absoluteCommandParts.push(`${xComponent},${yComponent}`);
|
325
332
|
}
|
326
|
-
|
327
|
-
text = `${negativeSign + (preDecimal + carry).toString()}.${newPostDecimal}`;
|
328
333
|
}
|
329
334
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
+
let commandString;
|
336
|
+
if (makeAbsCommand) {
|
337
|
+
commandString = `${command}${absoluteCommandParts.join(' ')}`;
|
338
|
+
} else {
|
339
|
+
commandString = `${command.toLowerCase()}${relativeCommandParts.join(' ')}`;
|
340
|
+
}
|
335
341
|
|
336
|
-
//
|
337
|
-
|
338
|
-
|
342
|
+
// Don't add no-ops.
|
343
|
+
if (commandString === 'l0,0') {
|
344
|
+
return;
|
345
|
+
}
|
346
|
+
result.push(commandString);
|
339
347
|
|
340
|
-
|
341
|
-
|
342
|
-
for (const point of points) {
|
343
|
-
const xComponent = toRoundedString(point.x);
|
344
|
-
const yComponent = toRoundedString(point.y);
|
345
|
-
parts.push(`${xComponent},${yComponent}`);
|
348
|
+
if (points.length > 0) {
|
349
|
+
prevPoint = points[points.length - 1];
|
346
350
|
}
|
347
|
-
result.push(`${command}${parts.join(' ')}`);
|
348
351
|
};
|
349
352
|
|
350
353
|
addCommand('M', startPoint);
|
@@ -147,14 +147,27 @@ describe('Rect2', () => {
|
|
147
147
|
it('division of empty square', () => {
|
148
148
|
expect(Rect2.empty.divideIntoGrid(1000, 10000).length).toBe(1);
|
149
149
|
});
|
150
|
+
|
151
|
+
it('division of rectangle', () => {
|
152
|
+
expect(new Rect2(0, 0, 2, 1).divideIntoGrid(2, 2)).toMatchObject(
|
153
|
+
[
|
154
|
+
new Rect2(0, 0, 1, 0.5), new Rect2(1, 0, 1, 0.5),
|
155
|
+
new Rect2(0, 0.5, 1, 0.5), new Rect2(1, 0.5, 1, 0.5),
|
156
|
+
]
|
157
|
+
);
|
158
|
+
});
|
150
159
|
});
|
151
160
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
161
|
+
describe('should correctly return the closest point on the edge of a rectangle', () => {
|
162
|
+
it('with the unit square', () => {
|
163
|
+
const rect = Rect2.unitSquare;
|
164
|
+
expect(rect.getClosestPointOnBoundaryTo(Vec2.zero)).objEq(Vec2.zero);
|
165
|
+
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(-1, -1))).objEq(Vec2.zero);
|
166
|
+
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(-1, 0.5))).objEq(Vec2.of(0, 0.5));
|
167
|
+
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(1, 0.5))).objEq(Vec2.of(1, 0.5));
|
168
|
+
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(0.6, 0.6))).objEq(Vec2.of(1, 0.6));
|
169
|
+
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(2, 0.5))).objEq(Vec2.of(1, 0.5));
|
170
|
+
expect(rect.getClosestPointOnBoundaryTo(Vec2.of(0.6, 0.6))).objEq(Vec2.of(1, 0.6));
|
171
|
+
});
|
159
172
|
});
|
160
173
|
});
|
@@ -51,6 +51,7 @@ export default class Rect2 {
|
|
51
51
|
return new Rect2(vec.x + this.x, vec.y + this.y, this.w, this.h);
|
52
52
|
}
|
53
53
|
|
54
|
+
// Returns a copy of this with the given size (but same top-left).
|
54
55
|
public resizedTo(size: Vec2): Rect2 {
|
55
56
|
return new Rect2(this.x, this.y, size.x, size.y);
|
56
57
|
}
|
@@ -163,6 +164,23 @@ export default class Rect2 {
|
|
163
164
|
);
|
164
165
|
}
|
165
166
|
|
167
|
+
public getClosestPointOnBoundaryTo(target: Point2) {
|
168
|
+
const closestEdgePoints = this.getEdges().map(edge => {
|
169
|
+
return edge.closestPointTo(target);
|
170
|
+
});
|
171
|
+
|
172
|
+
let closest: Point2|null = null;
|
173
|
+
let closestDist: number|null = null;
|
174
|
+
for (const point of closestEdgePoints) {
|
175
|
+
const dist = point.minus(target).length();
|
176
|
+
if (closestDist === null || dist < closestDist) {
|
177
|
+
closest = point;
|
178
|
+
closestDist = dist;
|
179
|
+
}
|
180
|
+
}
|
181
|
+
return closest!;
|
182
|
+
}
|
183
|
+
|
166
184
|
public get corners(): Point2[] {
|
167
185
|
return [
|
168
186
|
this.bottomRight,
|
@@ -172,7 +190,7 @@ export default class Rect2 {
|
|
172
190
|
];
|
173
191
|
}
|
174
192
|
|
175
|
-
public get maxDimension()
|
193
|
+
public get maxDimension() {
|
176
194
|
return Math.max(this.w, this.h);
|
177
195
|
}
|
178
196
|
|
File without changes
|
File without changes
|
File without changes
|
@@ -115,9 +115,9 @@ export default class Vec3 {
|
|
115
115
|
}
|
116
116
|
|
117
117
|
// Returns a vector with each component acted on by [fn]
|
118
|
-
public map(fn: (component: number)=> number): Vec3 {
|
118
|
+
public map(fn: (component: number, index: number)=> number): Vec3 {
|
119
119
|
return Vec3.of(
|
120
|
-
fn(this.x), fn(this.y), fn(this.z)
|
120
|
+
fn(this.x, 0), fn(this.y, 1), fn(this.z, 2)
|
121
121
|
);
|
122
122
|
}
|
123
123
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { toRoundedString, toStringOfSamePrecision } from './rounding';
|
2
|
+
|
3
|
+
describe('toRoundedString', () => {
|
4
|
+
it('should round up numbers endings similar to .999999999999999', () => {
|
5
|
+
expect(toRoundedString(0.999999999)).toBe('1');
|
6
|
+
expect(toRoundedString(0.899999999)).toBe('.9');
|
7
|
+
expect(toRoundedString(9.999999999)).toBe('10');
|
8
|
+
expect(toRoundedString(-10.999999999)).toBe('-11');
|
9
|
+
});
|
10
|
+
|
11
|
+
it('should round up numbers similar to 10.999999998', () => {
|
12
|
+
expect(toRoundedString(10.999999998)).toBe('11');
|
13
|
+
});
|
14
|
+
|
15
|
+
// Handling this creates situations with potential error:
|
16
|
+
//it('should round strings with multiple digits after the ending decimal points', () => {
|
17
|
+
// expect(toRoundedString(292.2 - 292.8)).toBe('-0.6');
|
18
|
+
//});
|
19
|
+
|
20
|
+
it('should round down strings ending endings similar to .00000001', () => {
|
21
|
+
expect(toRoundedString(10.00000001)).toBe('10');
|
22
|
+
});
|
23
|
+
});
|
24
|
+
|
25
|
+
it('toStringOfSamePrecision', () => {
|
26
|
+
expect(toStringOfSamePrecision(1.23456, '1.12')).toBe('1.23');
|
27
|
+
expect(toStringOfSamePrecision(1.23456, '1.1')).toBe('1.2');
|
28
|
+
expect(toStringOfSamePrecision(1.23456, '1.1', '5.32')).toBe('1.23');
|
29
|
+
expect(toStringOfSamePrecision(-1.23456, '1.1', '5.32')).toBe('-1.23');
|
30
|
+
expect(toStringOfSamePrecision(-1.99999, '1.1', '5.32')).toBe('-2');
|
31
|
+
expect(toStringOfSamePrecision(1.99999, '1.1', '5.32')).toBe('2');
|
32
|
+
expect(toStringOfSamePrecision(1.89999, '1.1', '5.32')).toBe('1.9');
|
33
|
+
expect(toStringOfSamePrecision(9.99999999, '-1.1234')).toBe('10');
|
34
|
+
expect(toStringOfSamePrecision(9.999999998999996, '100')).toBe('10');
|
35
|
+
expect(toStringOfSamePrecision(0.000012345, '0.000012')).toBe('.000012');
|
36
|
+
expect(toStringOfSamePrecision(0.000012645, '.000012')).toBe('.000013');
|
37
|
+
expect(toStringOfSamePrecision(-0.09999999999999432, '291.3')).toBe('-.1');
|
38
|
+
expect(toStringOfSamePrecision(-0.9999999999999432, '291.3')).toBe('-1');
|
39
|
+
expect(toStringOfSamePrecision(9998.9, '.1', '-11')).toBe('9998.9');
|
40
|
+
});
|
@@ -0,0 +1,145 @@
|
|
1
|
+
// Clean up stringified numbers
|
2
|
+
const cleanUpNumber = (text: string) => {
|
3
|
+
// Regular expression substitions can be somewhat expensive. Only do them
|
4
|
+
// if necessary.
|
5
|
+
const lastChar = text.charAt(text.length - 1);
|
6
|
+
if (lastChar === '0' || lastChar === '.') {
|
7
|
+
// Remove trailing zeroes
|
8
|
+
text = text.replace(/([.]\d*[^0]+)0+$/, '$1');
|
9
|
+
text = text.replace(/[.]0+$/, '.');
|
10
|
+
|
11
|
+
// Remove trailing period
|
12
|
+
text = text.replace(/[.]$/, '');
|
13
|
+
|
14
|
+
if (text === '-0') {
|
15
|
+
return '0';
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
const firstChar = text.charAt(0);
|
20
|
+
if (firstChar === '0' || firstChar === '-') {
|
21
|
+
// Remove unnecessary leading zeroes.
|
22
|
+
text = text.replace(/^(0+)[.]/, '.');
|
23
|
+
text = text.replace(/^-(0+)[.]/, '-.');
|
24
|
+
}
|
25
|
+
|
26
|
+
return text;
|
27
|
+
};
|
28
|
+
|
29
|
+
export const toRoundedString = (num: number): string => {
|
30
|
+
// Try to remove rounding errors. If the number ends in at least three/four zeroes
|
31
|
+
// (or nines) just one or two digits, it's probably a rounding error.
|
32
|
+
const fixRoundingUpExp = /^([-]?\d*\.\d{3,})0{4,}\d{1,2}$/;
|
33
|
+
const hasRoundingDownExp = /^([-]?)(\d*)\.(\d{3,}9{4,})\d{1,2}$/;
|
34
|
+
|
35
|
+
let text = num.toString(10);
|
36
|
+
if (text.indexOf('.') === -1) {
|
37
|
+
return text;
|
38
|
+
}
|
39
|
+
|
40
|
+
const roundingDownMatch = hasRoundingDownExp.exec(text);
|
41
|
+
if (roundingDownMatch) {
|
42
|
+
const negativeSign = roundingDownMatch[1];
|
43
|
+
const postDecimalString = roundingDownMatch[3];
|
44
|
+
const lastDigit = parseInt(postDecimalString.charAt(postDecimalString.length - 1), 10);
|
45
|
+
const postDecimal = parseInt(postDecimalString, 10);
|
46
|
+
const preDecimal = parseInt(roundingDownMatch[2], 10);
|
47
|
+
|
48
|
+
const origPostDecimalString = roundingDownMatch[3];
|
49
|
+
|
50
|
+
let newPostDecimal = (postDecimal + 10 - lastDigit).toString();
|
51
|
+
let carry = 0;
|
52
|
+
if (newPostDecimal.length > postDecimal.toString().length) {
|
53
|
+
// Left-shift
|
54
|
+
newPostDecimal = newPostDecimal.substring(1);
|
55
|
+
carry = 1;
|
56
|
+
}
|
57
|
+
|
58
|
+
// parseInt(...).toString() removes leading zeroes. Add them back.
|
59
|
+
while (newPostDecimal.length < origPostDecimalString.length) {
|
60
|
+
newPostDecimal = carry.toString(10) + newPostDecimal;
|
61
|
+
carry = 0;
|
62
|
+
}
|
63
|
+
|
64
|
+
text = `${negativeSign + (preDecimal + carry).toString()}.${newPostDecimal}`;
|
65
|
+
}
|
66
|
+
|
67
|
+
text = text.replace(fixRoundingUpExp, '$1');
|
68
|
+
|
69
|
+
return cleanUpNumber(text);
|
70
|
+
};
|
71
|
+
|
72
|
+
const numberExp = /^([-]?)(\d*)[.](\d+)$/;
|
73
|
+
export const getLenAfterDecimal = (numberAsString: string) => {
|
74
|
+
const numberMatch = numberExp.exec(numberAsString);
|
75
|
+
if (!numberMatch) {
|
76
|
+
// If not a match, either the number is exponential notation (or is something
|
77
|
+
// like NaN or Infinity)
|
78
|
+
if (numberAsString.search(/[eE]/) !== -1 || /^[a-zA-Z]+$/.exec(numberAsString)) {
|
79
|
+
return -1;
|
80
|
+
// Or it has no decimal point
|
81
|
+
} else {
|
82
|
+
return 0;
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
const afterDecimalLen = numberMatch[3].length;
|
87
|
+
return afterDecimalLen;
|
88
|
+
};
|
89
|
+
|
90
|
+
// [reference] should be a string representation of a base-10 number (no exponential (e.g. 10e10))
|
91
|
+
export const toStringOfSamePrecision = (num: number, ...references: string[]): string => {
|
92
|
+
const text = num.toString(10);
|
93
|
+
const textMatch = numberExp.exec(text);
|
94
|
+
if (!textMatch) {
|
95
|
+
return text;
|
96
|
+
}
|
97
|
+
|
98
|
+
let decimalPlaces = -1;
|
99
|
+
for (const reference of references) {
|
100
|
+
decimalPlaces = Math.max(getLenAfterDecimal(reference), decimalPlaces);
|
101
|
+
}
|
102
|
+
|
103
|
+
if (decimalPlaces === -1) {
|
104
|
+
return toRoundedString(num);
|
105
|
+
}
|
106
|
+
|
107
|
+
// Make text's after decimal length match [afterDecimalLen].
|
108
|
+
let postDecimal = textMatch[3].substring(0, decimalPlaces);
|
109
|
+
let preDecimal = textMatch[2];
|
110
|
+
const nextDigit = textMatch[3].charAt(decimalPlaces);
|
111
|
+
|
112
|
+
if (nextDigit !== '') {
|
113
|
+
const asNumber = parseInt(nextDigit, 10);
|
114
|
+
if (asNumber >= 5) {
|
115
|
+
// Don't attempt to parseInt() an empty string.
|
116
|
+
if (postDecimal.length > 0) {
|
117
|
+
const leadingZeroMatch = /^(0+)(\d*)$/.exec(postDecimal);
|
118
|
+
|
119
|
+
let leadingZeroes = '';
|
120
|
+
let postLeading = postDecimal;
|
121
|
+
if (leadingZeroMatch) {
|
122
|
+
leadingZeroes = leadingZeroMatch[1];
|
123
|
+
postLeading = leadingZeroMatch[2];
|
124
|
+
}
|
125
|
+
|
126
|
+
postDecimal = (parseInt(postDecimal) + 1).toString();
|
127
|
+
|
128
|
+
// If postDecimal got longer, remove leading zeroes if possible
|
129
|
+
if (postDecimal.length > postLeading.length && leadingZeroes.length > 0) {
|
130
|
+
leadingZeroes = leadingZeroes.substring(1);
|
131
|
+
}
|
132
|
+
|
133
|
+
postDecimal = leadingZeroes + postDecimal;
|
134
|
+
}
|
135
|
+
|
136
|
+
if (postDecimal.length === 0 || postDecimal.length > decimalPlaces) {
|
137
|
+
preDecimal = (parseInt(preDecimal) + 1).toString();
|
138
|
+
postDecimal = postDecimal.substring(1);
|
139
|
+
}
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
const negativeSign = textMatch[1];
|
144
|
+
return cleanUpNumber(`${negativeSign}${preDecimal}.${postDecimal}`);
|
145
|
+
};
|
package/src/rendering/Display.ts
CHANGED
@@ -3,7 +3,7 @@ import CanvasRenderer from './renderers/CanvasRenderer';
|
|
3
3
|
import { Editor } from '../Editor';
|
4
4
|
import { EditorEventType } from '../types';
|
5
5
|
import DummyRenderer from './renderers/DummyRenderer';
|
6
|
-
import { Point2, Vec2 } from '../
|
6
|
+
import { Point2, Vec2 } from '../math/Vec2';
|
7
7
|
import RenderingCache from './caching/RenderingCache';
|
8
8
|
import TextOnlyRenderer from './renderers/TextOnlyRenderer';
|
9
9
|
import Color4 from '../Color4';
|
@@ -18,6 +18,7 @@ export default class Display {
|
|
18
18
|
private dryInkRenderer: AbstractRenderer;
|
19
19
|
private wetInkRenderer: AbstractRenderer;
|
20
20
|
private textRenderer: TextOnlyRenderer;
|
21
|
+
private textRerenderOutput: HTMLElement|null = null;
|
21
22
|
private cache: RenderingCache;
|
22
23
|
private resizeSurfacesCallback?: ()=> void;
|
23
24
|
private flattenCallback?: ()=> void;
|
@@ -59,10 +60,10 @@ export default class Display {
|
|
59
60
|
return this.dryInkRenderer.canRenderFromWithoutDataLoss(renderer);
|
60
61
|
},
|
61
62
|
blockResolution: cacheBlockResolution,
|
62
|
-
cacheSize: 500 * 500 * 4 *
|
63
|
+
cacheSize: 500 * 500 * 4 * 220,
|
63
64
|
maxScale: 1.5,
|
64
|
-
minComponentsPerCache:
|
65
|
-
minComponentsToUseCache:
|
65
|
+
minComponentsPerCache: 45,
|
66
|
+
minComponentsToUseCache: 105,
|
66
67
|
});
|
67
68
|
|
68
69
|
this.editor.notifier.on(EditorEventType.DisplayResized, event => {
|
@@ -158,19 +159,26 @@ export default class Display {
|
|
158
159
|
rerenderButton.classList.add('rerenderButton');
|
159
160
|
rerenderButton.innerText = this.editor.localization.rerenderAsText;
|
160
161
|
|
161
|
-
|
162
|
-
|
162
|
+
this.textRerenderOutput = document.createElement('div');
|
163
|
+
this.textRerenderOutput.setAttribute('aria-live', 'polite');
|
163
164
|
|
164
165
|
rerenderButton.onclick = () => {
|
165
|
-
this.
|
166
|
-
this.editor.image.render(this.textRenderer, this.editor.viewport);
|
167
|
-
rerenderOutput.innerText = this.textRenderer.getDescription();
|
166
|
+
this.rerenderAsText();
|
168
167
|
};
|
169
168
|
|
170
|
-
textRendererOutputContainer.replaceChildren(rerenderButton,
|
169
|
+
textRendererOutputContainer.replaceChildren(rerenderButton, this.textRerenderOutput);
|
171
170
|
this.editor.createHTMLOverlay(textRendererOutputContainer);
|
172
171
|
}
|
173
172
|
|
173
|
+
public rerenderAsText() {
|
174
|
+
this.textRenderer.clear();
|
175
|
+
this.editor.image.render(this.textRenderer, this.editor.viewport);
|
176
|
+
|
177
|
+
if (this.textRerenderOutput) {
|
178
|
+
this.textRerenderOutput.innerText = this.textRenderer.getDescription();
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
174
182
|
// Clears the drawing surfaces and otherwise prepares for a rerender.
|
175
183
|
public startRerender(): AbstractRenderer {
|
176
184
|
this.resizeSurfacesCallback?.();
|
@@ -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
|
|
@@ -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
|
|
@@ -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,5 +1,5 @@
|
|
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';
|