js-draw 0.15.2 → 0.16.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/.github/ISSUE_TEMPLATE/translation.yml +56 -0
- package/CHANGELOG.md +6 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +11 -0
- package/dist/src/Editor.js +52 -4
- package/dist/src/EditorImage.d.ts +11 -11
- package/dist/src/EditorImage.js +54 -18
- package/dist/src/Viewport.d.ts +5 -0
- package/dist/src/Viewport.js +11 -0
- package/dist/src/components/AbstractComponent.d.ts +1 -0
- package/dist/src/components/AbstractComponent.js +5 -0
- package/dist/src/components/ImageBackground.d.ts +2 -1
- package/dist/src/components/ImageBackground.js +8 -1
- package/dist/src/localizations/es.js +1 -1
- package/dist/src/toolbar/HTMLToolbar.d.ts +25 -2
- package/dist/src/toolbar/HTMLToolbar.js +127 -15
- package/dist/src/toolbar/IconProvider.d.ts +2 -0
- package/dist/src/toolbar/IconProvider.js +44 -0
- package/dist/src/toolbar/localization.d.ts +5 -0
- package/dist/src/toolbar/localization.js +5 -0
- package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
- package/dist/src/toolbar/widgets/ActionButtonWidget.js +5 -1
- package/dist/src/toolbar/widgets/BaseToolWidget.d.ts +1 -1
- package/dist/src/toolbar/widgets/BaseToolWidget.js +2 -1
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +7 -2
- package/dist/src/toolbar/widgets/BaseWidget.js +23 -1
- package/dist/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +19 -0
- package/dist/src/toolbar/widgets/DocumentPropertiesWidget.js +135 -0
- package/dist/src/toolbar/widgets/OverflowWidget.d.ts +25 -0
- package/dist/src/toolbar/widgets/OverflowWidget.js +65 -0
- package/dist/src/toolbar/widgets/lib.d.ts +1 -0
- package/dist/src/toolbar/widgets/lib.js +1 -0
- package/dist/src/tools/PasteHandler.js +2 -2
- package/dist/src/tools/SelectionTool/Selection.d.ts +2 -1
- package/dist/src/tools/SelectionTool/Selection.js +2 -1
- package/package.json +1 -1
- package/src/Editor.loadFrom.test.ts +24 -0
- package/src/Editor.ts +59 -4
- package/src/EditorImage.ts +66 -23
- package/src/Viewport.ts +13 -0
- package/src/components/AbstractComponent.ts +6 -0
- package/src/components/ImageBackground.test.ts +35 -0
- package/src/components/ImageBackground.ts +10 -1
- package/src/localizations/es.ts +8 -0
- package/src/math/Mat33.test.ts +30 -5
- package/src/rendering/renderers/CanvasRenderer.ts +1 -1
- package/src/toolbar/HTMLToolbar.ts +164 -16
- package/src/toolbar/IconProvider.ts +46 -0
- package/src/toolbar/localization.ts +10 -0
- package/src/toolbar/toolbar.css +2 -0
- package/src/toolbar/widgets/ActionButtonWidget.ts +5 -0
- package/src/toolbar/widgets/BaseToolWidget.ts +3 -1
- package/src/toolbar/widgets/BaseWidget.ts +34 -2
- package/src/toolbar/widgets/DocumentPropertiesWidget.ts +185 -0
- package/src/toolbar/widgets/OverflowWidget.css +9 -0
- package/src/toolbar/widgets/OverflowWidget.ts +83 -0
- package/src/toolbar/widgets/lib.ts +2 -1
- package/src/tools/PasteHandler.ts +3 -2
- package/src/tools/SelectionTool/Selection.ts +2 -1
@@ -0,0 +1,65 @@
|
|
1
|
+
import BaseWidget from './BaseWidget';
|
2
|
+
export default class OverflowWidget extends BaseWidget {
|
3
|
+
constructor(editor, localizationTable) {
|
4
|
+
var _a;
|
5
|
+
super(editor, 'overflow-widget', localizationTable);
|
6
|
+
this.overflowChildren = [];
|
7
|
+
// Make the dropdown openable
|
8
|
+
this.container.classList.add('dropdownShowable');
|
9
|
+
(_a = this.overflowContainer) !== null && _a !== void 0 ? _a : (this.overflowContainer = document.createElement('div'));
|
10
|
+
}
|
11
|
+
getTitle() {
|
12
|
+
return this.localizationTable.toggleOverflow;
|
13
|
+
}
|
14
|
+
createIcon() {
|
15
|
+
return this.editor.icons.makeOverflowIcon();
|
16
|
+
}
|
17
|
+
handleClick() {
|
18
|
+
this.setDropdownVisible(!this.isDropdownVisible());
|
19
|
+
}
|
20
|
+
fillDropdown(dropdown) {
|
21
|
+
var _a;
|
22
|
+
(_a = this.overflowContainer) !== null && _a !== void 0 ? _a : (this.overflowContainer = document.createElement('div'));
|
23
|
+
if (this.overflowContainer.parentElement) {
|
24
|
+
this.overflowContainer.remove();
|
25
|
+
}
|
26
|
+
this.overflowContainer.classList.add('toolbar-overflow-widget-overflow-list');
|
27
|
+
dropdown.appendChild(this.overflowContainer);
|
28
|
+
return true;
|
29
|
+
}
|
30
|
+
/**
|
31
|
+
* Removes all `BaseWidget`s from this and returns them.
|
32
|
+
*/
|
33
|
+
clearChildren() {
|
34
|
+
this.overflowContainer.replaceChildren();
|
35
|
+
const overflowChildren = this.overflowChildren;
|
36
|
+
this.overflowChildren = [];
|
37
|
+
return overflowChildren;
|
38
|
+
}
|
39
|
+
getChildWidgets() {
|
40
|
+
return [...this.overflowChildren];
|
41
|
+
}
|
42
|
+
hasAsChild(widget) {
|
43
|
+
for (const otherWidget of this.overflowChildren) {
|
44
|
+
if (widget === otherWidget) {
|
45
|
+
return true;
|
46
|
+
}
|
47
|
+
}
|
48
|
+
return false;
|
49
|
+
}
|
50
|
+
/**
|
51
|
+
* Adds `widget` to this.
|
52
|
+
* `widget`'s previous parent is still responsible
|
53
|
+
* for serializing/deserializing its state.
|
54
|
+
*/
|
55
|
+
addToOverflow(widget) {
|
56
|
+
this.overflowChildren.push(widget);
|
57
|
+
widget.addTo(this.overflowContainer);
|
58
|
+
widget.setIsToplevel(false);
|
59
|
+
}
|
60
|
+
// This always returns false.
|
61
|
+
// Don't try to move the overflow menu to itself.
|
62
|
+
canBeInOverflowMenu() {
|
63
|
+
return false;
|
64
|
+
}
|
65
|
+
}
|
@@ -7,3 +7,4 @@ export { default as HandToolWidget } from './HandToolWidget';
|
|
7
7
|
export { default as SelectionToolWidget } from './SelectionToolWidget';
|
8
8
|
export { default as EraserToolWidget } from './EraserToolWidget';
|
9
9
|
export { default as InsertImageWidget } from './InsertImageWidget';
|
10
|
+
export { default as DocumentPropertiesWidget } from './DocumentPropertiesWidget';
|
@@ -7,3 +7,4 @@ export { default as HandToolWidget } from './HandToolWidget';
|
|
7
7
|
export { default as SelectionToolWidget } from './SelectionToolWidget';
|
8
8
|
export { default as EraserToolWidget } from './EraserToolWidget';
|
9
9
|
export { default as InsertImageWidget } from './InsertImageWidget';
|
10
|
+
export { default as DocumentPropertiesWidget } from './DocumentPropertiesWidget';
|
@@ -7,9 +7,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8
8
|
});
|
9
9
|
};
|
10
|
-
import
|
10
|
+
import TextComponent from '../components/TextComponent';
|
11
11
|
import SVGLoader from '../SVGLoader';
|
12
|
-
import
|
12
|
+
import Mat33 from '../math/Mat33';
|
13
13
|
import BaseTool from './BaseTool';
|
14
14
|
import TextTool from './TextTool';
|
15
15
|
import Color4 from '../Color4';
|
@@ -3,7 +3,8 @@
|
|
3
3
|
* @packageDocumentation
|
4
4
|
*/
|
5
5
|
import Editor from '../../Editor';
|
6
|
-
import
|
6
|
+
import Mat33 from '../../math/Mat33';
|
7
|
+
import Rect2 from '../../math/Rect2';
|
7
8
|
import { Point2 } from '../../math/Vec2';
|
8
9
|
import Pointer from '../../Pointer';
|
9
10
|
import AbstractComponent from '../../components/AbstractComponent';
|
@@ -13,7 +13,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
13
13
|
};
|
14
14
|
var _a;
|
15
15
|
import SerializableCommand from '../../commands/SerializableCommand';
|
16
|
-
import
|
16
|
+
import Mat33 from '../../math/Mat33';
|
17
|
+
import Rect2 from '../../math/Rect2';
|
17
18
|
import { Vec2 } from '../../math/Vec2';
|
18
19
|
import SelectionHandle, { HandleShape, handleSize } from './SelectionHandle';
|
19
20
|
import { cssPrefix } from './SelectionTool';
|
package/package.json
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
import Color4 from './Color4';
|
2
|
+
import { imageBackgroundCSSClassName } from './components/ImageBackground';
|
3
|
+
import { RestyleableComponent } from './lib';
|
4
|
+
import SVGLoader from './SVGLoader';
|
5
|
+
import createEditor from './testing/createEditor';
|
6
|
+
|
7
|
+
describe('Editor.loadFrom', () => {
|
8
|
+
it('should remove existing BackgroundComponents when loading new BackgroundComponents', async () => {
|
9
|
+
const editor = createEditor();
|
10
|
+
await editor.dispatch(editor.setBackgroundColor(Color4.red));
|
11
|
+
|
12
|
+
let backgroundComponents = editor.image.getBackgroundComponents();
|
13
|
+
expect(backgroundComponents).toHaveLength(1);
|
14
|
+
expect((backgroundComponents[0] as RestyleableComponent).getStyle().color).objEq(Color4.red);
|
15
|
+
|
16
|
+
await editor.loadFrom(SVGLoader.fromString(`<svg viewBox='0 0 100 100'>
|
17
|
+
<path class='${imageBackgroundCSSClassName}' d='m0,0 L100,0 L100,100 L0,100 z' fill='#000'/>
|
18
|
+
</svg>`, true));
|
19
|
+
|
20
|
+
backgroundComponents = editor.image.getBackgroundComponents();
|
21
|
+
expect(backgroundComponents).toHaveLength(1);
|
22
|
+
expect((backgroundComponents[0] as RestyleableComponent).getStyle().color).objEq(Color4.black);
|
23
|
+
});
|
24
|
+
});
|
package/src/Editor.ts
CHANGED
@@ -26,6 +26,8 @@ import fileToBase64 from './util/fileToBase64';
|
|
26
26
|
import uniteCommands from './commands/uniteCommands';
|
27
27
|
import SelectionTool from './tools/SelectionTool/SelectionTool';
|
28
28
|
import AbstractComponent from './components/AbstractComponent';
|
29
|
+
import Erase from './commands/Erase';
|
30
|
+
import ImageBackground, { BackgroundType } from './components/ImageBackground';
|
29
31
|
|
30
32
|
type HTMLPointerEventType = 'pointerdown'|'pointermove'|'pointerup'|'pointercancel';
|
31
33
|
type HTMLPointerEventFilter = (eventName: HTMLPointerEventType, event: PointerEvent)=>boolean;
|
@@ -355,9 +357,10 @@ export class Editor {
|
|
355
357
|
this.viewport.updateScreenSize(
|
356
358
|
Vec2.of(this.display.width, this.display.height)
|
357
359
|
);
|
360
|
+
this.queueRerender();
|
358
361
|
});
|
359
362
|
|
360
|
-
|
363
|
+
const handleResize = () => {
|
361
364
|
this.notifier.dispatch(EditorEventType.DisplayResized, {
|
362
365
|
kind: EditorEventType.DisplayResized,
|
363
366
|
newSize: Vec2.of(
|
@@ -365,8 +368,14 @@ export class Editor {
|
|
365
368
|
this.display.height
|
366
369
|
),
|
367
370
|
});
|
368
|
-
|
369
|
-
|
371
|
+
};
|
372
|
+
|
373
|
+
if ('ResizeObserver' in (window as any)) {
|
374
|
+
const resizeObserver = new ResizeObserver(handleResize);
|
375
|
+
resizeObserver.observe(this.container);
|
376
|
+
} else {
|
377
|
+
addEventListener('resize', handleResize);
|
378
|
+
}
|
370
379
|
|
371
380
|
this.accessibilityControlArea.addEventListener('input', () => {
|
372
381
|
this.accessibilityControlArea.value = '';
|
@@ -935,7 +944,7 @@ export class Editor {
|
|
935
944
|
}
|
936
945
|
|
937
946
|
public toSVG(): SVGElement {
|
938
|
-
const importExportViewport = this.image.getImportExportViewport();
|
947
|
+
const importExportViewport = this.image.getImportExportViewport().getTemporaryClone();
|
939
948
|
const svgNameSpace = 'http://www.w3.org/2000/svg';
|
940
949
|
const result = document.createElementNS(svgNameSpace, 'svg');
|
941
950
|
const renderer = new SVGRenderer(result, importExportViewport);
|
@@ -966,10 +975,18 @@ export class Editor {
|
|
966
975
|
return result;
|
967
976
|
}
|
968
977
|
|
978
|
+
/**
|
979
|
+
* Load editor data from an `ImageLoader` (e.g. an {@link SVGLoader}).
|
980
|
+
*
|
981
|
+
* @see loadFromSVG
|
982
|
+
*/
|
969
983
|
public async loadFrom(loader: ImageLoader) {
|
970
984
|
this.showLoadingWarning(0);
|
971
985
|
this.display.setDraftMode(true);
|
972
986
|
|
987
|
+
const originalBackgrounds = this.image.getBackgroundComponents();
|
988
|
+
const eraseBackgroundCommand = new Erase(originalBackgrounds);
|
989
|
+
|
973
990
|
await loader.start(async (component) => {
|
974
991
|
await this.dispatchNoAnnounce(EditorImage.addElement(component));
|
975
992
|
}, (countProcessed: number, totalToProcess: number) => {
|
@@ -984,12 +1001,50 @@ export class Editor {
|
|
984
1001
|
this.dispatchNoAnnounce(this.setImportExportRect(importExportRect), false);
|
985
1002
|
this.dispatchNoAnnounce(this.viewport.zoomTo(importExportRect), false);
|
986
1003
|
});
|
1004
|
+
|
1005
|
+
// Ensure that we don't have multiple overlapping BackgroundComponents. Remove
|
1006
|
+
// old BackgroundComponents.
|
1007
|
+
// Overlapping BackgroundComponents may cause changing the background color to
|
1008
|
+
// not work properly.
|
1009
|
+
if (this.image.getBackgroundComponents().length !== originalBackgrounds.length) {
|
1010
|
+
await this.dispatchNoAnnounce(eraseBackgroundCommand);
|
1011
|
+
}
|
1012
|
+
|
987
1013
|
this.hideLoadingWarning();
|
988
1014
|
|
989
1015
|
this.display.setDraftMode(false);
|
990
1016
|
this.queueRerender();
|
991
1017
|
}
|
992
1018
|
|
1019
|
+
private getTopmostBackgroundComponent(): ImageBackground|null {
|
1020
|
+
let background: ImageBackground|null = null;
|
1021
|
+
|
1022
|
+
// Find a background component, if one exists.
|
1023
|
+
// Use the last (topmost) background component if there are multiple.
|
1024
|
+
for (const component of this.image.getBackgroundComponents()) {
|
1025
|
+
if (component instanceof ImageBackground) {
|
1026
|
+
background = component;
|
1027
|
+
}
|
1028
|
+
}
|
1029
|
+
|
1030
|
+
return background;
|
1031
|
+
}
|
1032
|
+
|
1033
|
+
/**
|
1034
|
+
* Set the background color of the image.
|
1035
|
+
*/
|
1036
|
+
public setBackgroundColor(color: Color4): Command {
|
1037
|
+
let background = this.getTopmostBackgroundComponent();
|
1038
|
+
|
1039
|
+
if (!background) {
|
1040
|
+
const backgroundType = color.eq(Color4.transparent) ? BackgroundType.None : BackgroundType.SolidColor;
|
1041
|
+
background = new ImageBackground(backgroundType, color);
|
1042
|
+
return this.image.addElement(background);
|
1043
|
+
} else {
|
1044
|
+
return background.updateStyle({ color });
|
1045
|
+
}
|
1046
|
+
}
|
1047
|
+
|
993
1048
|
// Returns the size of the visible region of the output SVG
|
994
1049
|
public getImportExportRect(): Rect2 {
|
995
1050
|
return this.image.getImportExportViewport().visibleRect;
|
package/src/EditorImage.ts
CHANGED
@@ -25,6 +25,7 @@ export type EditorImageNotifier = EventDispatcher<EditorImageEventType, { image:
|
|
25
25
|
// Handles lookup/storage of elements in the image
|
26
26
|
export default class EditorImage {
|
27
27
|
private root: ImageNode;
|
28
|
+
private background: ImageNode;
|
28
29
|
private componentsById: Record<string, AbstractComponent>;
|
29
30
|
|
30
31
|
/** Viewport for the exported/imported image. */
|
@@ -36,6 +37,7 @@ export default class EditorImage {
|
|
36
37
|
// @internal
|
37
38
|
public constructor() {
|
38
39
|
this.root = new ImageNode();
|
40
|
+
this.background = new ImageNode();
|
39
41
|
this.componentsById = {};
|
40
42
|
|
41
43
|
this.notifier = new EventDispatcher();
|
@@ -56,7 +58,7 @@ export default class EditorImage {
|
|
56
58
|
return this.importExportViewport;
|
57
59
|
}
|
58
60
|
|
59
|
-
public setImportExportRect(imageRect: Rect2) {
|
61
|
+
public setImportExportRect(imageRect: Rect2): Command {
|
60
62
|
const importExportViewport = this.getImportExportViewport();
|
61
63
|
const origSize = importExportViewport.visibleRect.size;
|
62
64
|
const origTransform = importExportViewport.canvasToScreenTransform;
|
@@ -82,15 +84,27 @@ export default class EditorImage {
|
|
82
84
|
};
|
83
85
|
}
|
84
86
|
|
85
|
-
// Returns
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
87
|
+
// Returns all components that make up the background of this image. These
|
88
|
+
// components are rendered below all other components.
|
89
|
+
public getBackgroundComponents(): AbstractComponent[] {
|
90
|
+
const result = [];
|
91
|
+
|
92
|
+
const leaves = this.background.getLeaves();
|
93
|
+
sortLeavesByZIndex(leaves);
|
94
|
+
|
95
|
+
for (const leaf of leaves) {
|
96
|
+
const content = leaf.getContent();
|
97
|
+
|
98
|
+
if (content) {
|
99
|
+
result.push(content);
|
91
100
|
}
|
92
101
|
}
|
93
|
-
return
|
102
|
+
return result;
|
103
|
+
}
|
104
|
+
|
105
|
+
// Returns the parent of the given element, if it exists.
|
106
|
+
public findParent(elem: AbstractComponent): ImageNode|null {
|
107
|
+
return this.background.getChildWithContent(elem) ?? this.root.getChildWithContent(elem);
|
94
108
|
}
|
95
109
|
|
96
110
|
// Forces a re-render of `elem` when the image is next re-rendered as a whole.
|
@@ -108,22 +122,22 @@ export default class EditorImage {
|
|
108
122
|
|
109
123
|
/** @internal */
|
110
124
|
public renderWithCache(screenRenderer: AbstractRenderer, cache: RenderingCache, viewport: Viewport) {
|
125
|
+
this.background.render(screenRenderer, viewport.visibleRect);
|
111
126
|
cache.render(screenRenderer, this.root, viewport);
|
112
127
|
}
|
113
128
|
|
114
|
-
/**
|
115
|
-
|
116
|
-
|
129
|
+
/**
|
130
|
+
* Renders all nodes visible from `viewport` (or all nodes if `viewport = null`)
|
131
|
+
* @internal
|
132
|
+
*/
|
133
|
+
public render(renderer: AbstractRenderer, viewport: Viewport|null) {
|
134
|
+
this.background.render(renderer, viewport?.visibleRect);
|
135
|
+
this.root.render(renderer, viewport?.visibleRect);
|
117
136
|
}
|
118
137
|
|
119
138
|
/** Renders all nodes, even ones not within the viewport. @internal */
|
120
139
|
public renderAll(renderer: AbstractRenderer) {
|
121
|
-
|
122
|
-
sortLeavesByZIndex(leaves);
|
123
|
-
|
124
|
-
for (const leaf of leaves) {
|
125
|
-
leaf.getContent()!.render(renderer, leaf.getBBox());
|
126
|
-
}
|
140
|
+
this.render(renderer, null);
|
127
141
|
}
|
128
142
|
|
129
143
|
/** @returns all elements in the image, sorted by z-index. This can be slow for large images. */
|
@@ -160,12 +174,23 @@ export default class EditorImage {
|
|
160
174
|
elem.onAddToImage(this);
|
161
175
|
|
162
176
|
this.componentsById[elem.getId()] = elem;
|
163
|
-
|
177
|
+
|
178
|
+
// If a background component, add to the background. Else,
|
179
|
+
// add to the normal component tree.
|
180
|
+
const parentTree = elem.isBackground() ? this.background : this.root;
|
181
|
+
return parentTree.addLeaf(elem);
|
164
182
|
}
|
165
183
|
|
166
184
|
private removeElementDirectly(element: AbstractComponent) {
|
167
185
|
const container = this.findParent(element);
|
168
186
|
container?.remove();
|
187
|
+
|
188
|
+
if (container) {
|
189
|
+
this.onDestroyElement(element);
|
190
|
+
return true;
|
191
|
+
}
|
192
|
+
|
193
|
+
return false;
|
169
194
|
}
|
170
195
|
|
171
196
|
/**
|
@@ -180,7 +205,7 @@ export default class EditorImage {
|
|
180
205
|
}
|
181
206
|
|
182
207
|
/** @see EditorImage.addElement */
|
183
|
-
public addElement(elem: AbstractComponent, applyByFlattening
|
208
|
+
public addElement(elem: AbstractComponent, applyByFlattening?: boolean) {
|
184
209
|
return EditorImage.addElement(elem, applyByFlattening);
|
185
210
|
}
|
186
211
|
|
@@ -325,6 +350,19 @@ export class ImageNode {
|
|
325
350
|
return result;
|
326
351
|
}
|
327
352
|
|
353
|
+
// Returns the child of this with the target content or `null` if no
|
354
|
+
// such child exists.
|
355
|
+
public getChildWithContent(target: AbstractComponent): ImageNode|null {
|
356
|
+
const candidates = this.getLeavesIntersectingRegion(target.getBBox());
|
357
|
+
for (const candidate of candidates) {
|
358
|
+
if (candidate.getContent() === target) {
|
359
|
+
return candidate;
|
360
|
+
}
|
361
|
+
}
|
362
|
+
|
363
|
+
return null;
|
364
|
+
}
|
365
|
+
|
328
366
|
// Returns a list of leaves with this as an ancestor.
|
329
367
|
// Like getLeavesInRegion, but does not check whether ancestors are in a given rectangle
|
330
368
|
public getLeaves(): ImageNode[] {
|
@@ -458,6 +496,8 @@ export class ImageNode {
|
|
458
496
|
|
459
497
|
// Remove this node and all of its children
|
460
498
|
public remove() {
|
499
|
+
this.content?.onRemoveFromImage();
|
500
|
+
|
461
501
|
if (!this.parent) {
|
462
502
|
this.content = null;
|
463
503
|
this.children = [];
|
@@ -465,8 +505,6 @@ export class ImageNode {
|
|
465
505
|
return;
|
466
506
|
}
|
467
507
|
|
468
|
-
this.content?.onRemoveFromImage();
|
469
|
-
|
470
508
|
const oldChildCount = this.parent.children.length;
|
471
509
|
this.parent.children = this.parent.children.filter(node => {
|
472
510
|
return node !== this;
|
@@ -489,8 +527,13 @@ export class ImageNode {
|
|
489
527
|
this.children = [];
|
490
528
|
}
|
491
529
|
|
492
|
-
public render(renderer: AbstractRenderer, visibleRect
|
493
|
-
|
530
|
+
public render(renderer: AbstractRenderer, visibleRect?: Rect2) {
|
531
|
+
let leaves;
|
532
|
+
if (visibleRect) {
|
533
|
+
leaves = this.getLeavesIntersectingRegion(visibleRect, rect => renderer.isTooSmallToRender(rect));
|
534
|
+
} else {
|
535
|
+
leaves = this.getLeaves();
|
536
|
+
}
|
494
537
|
sortLeavesByZIndex(leaves);
|
495
538
|
|
496
539
|
for (const leaf of leaves) {
|
package/src/Viewport.ts
CHANGED
@@ -88,6 +88,19 @@ export class Viewport {
|
|
88
88
|
this.screenRect = Rect2.empty;
|
89
89
|
}
|
90
90
|
|
91
|
+
/**
|
92
|
+
* @returns a temporary copy of `this` that does not notify when modified. This is
|
93
|
+
* useful when rendering with a temporarily different viewport.
|
94
|
+
*/
|
95
|
+
public getTemporaryClone(): Viewport {
|
96
|
+
const result = new Viewport(() => {});
|
97
|
+
result.transform = this.transform;
|
98
|
+
result.inverseTransform = this.inverseTransform;
|
99
|
+
result.screenRect = this.screenRect;
|
100
|
+
|
101
|
+
return result;
|
102
|
+
}
|
103
|
+
|
91
104
|
// @internal
|
92
105
|
public updateScreenSize(screenSize: Vec2) {
|
93
106
|
this.screenRect = this.screenRect.resizedTo(screenSize);
|
@@ -156,6 +156,12 @@ export default abstract class AbstractComponent {
|
|
156
156
|
return true;
|
157
157
|
}
|
158
158
|
|
159
|
+
// @returns true iff this component should be added to the background, rather than the
|
160
|
+
// foreground of the image.
|
161
|
+
public isBackground(): boolean {
|
162
|
+
return false;
|
163
|
+
}
|
164
|
+
|
159
165
|
// @returns an approximation of the proportional time it takes to render this component.
|
160
166
|
// This is intended to be a rough estimate, but, for example, a stroke with two points sould have
|
161
167
|
// a renderingWeight approximately twice that of a stroke with one point.
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import Color4 from '../Color4';
|
2
|
+
import { Path, Rect2 } from '../math/lib';
|
3
|
+
import createEditor from '../testing/createEditor';
|
4
|
+
import ImageBackground, { BackgroundType, imageBackgroundCSSClassName } from './ImageBackground';
|
5
|
+
|
6
|
+
describe('ImageBackground', () => {
|
7
|
+
it('should render to fill exported SVG', () => {
|
8
|
+
const editor = createEditor();
|
9
|
+
const background = new ImageBackground(BackgroundType.SolidColor, Color4.green);
|
10
|
+
editor.image.addElement(
|
11
|
+
background
|
12
|
+
).apply(editor);
|
13
|
+
|
14
|
+
const expectedImportExportRect = new Rect2(-10, 10, 15, 20);
|
15
|
+
editor.setImportExportRect(expectedImportExportRect).apply(editor);
|
16
|
+
expect(editor.getImportExportRect()).objEq(expectedImportExportRect);
|
17
|
+
|
18
|
+
expect(background.getBBox()).objEq(expectedImportExportRect);
|
19
|
+
|
20
|
+
const rendered = editor.toSVG();
|
21
|
+
const renderedBackground = rendered.querySelector(`.${imageBackgroundCSSClassName}`);
|
22
|
+
|
23
|
+
if (renderedBackground === null) {
|
24
|
+
throw new Error('ImageBackground did not render in exported SVG');
|
25
|
+
}
|
26
|
+
|
27
|
+
expect(renderedBackground.tagName.toLowerCase()).toBe('path');
|
28
|
+
|
29
|
+
const pathString = renderedBackground.getAttribute('d')!;
|
30
|
+
expect(pathString).not.toBeNull();
|
31
|
+
|
32
|
+
const path = Path.fromString(pathString);
|
33
|
+
expect(path.bbox).objEq(editor.getImportExportRect());
|
34
|
+
});
|
35
|
+
});
|
@@ -50,7 +50,7 @@ export default class ImageBackground extends AbstractComponent implements Restyl
|
|
50
50
|
}
|
51
51
|
|
52
52
|
// @internal
|
53
|
-
public forceStyle(style: ComponentStyle,
|
53
|
+
public forceStyle(style: ComponentStyle, editor: Editor | null): void {
|
54
54
|
const fill = style.color;
|
55
55
|
|
56
56
|
if (!fill) {
|
@@ -63,6 +63,11 @@ export default class ImageBackground extends AbstractComponent implements Restyl
|
|
63
63
|
} else {
|
64
64
|
this.backgroundType = BackgroundType.SolidColor;
|
65
65
|
}
|
66
|
+
|
67
|
+
if (editor) {
|
68
|
+
editor.image.queueRerenderOf(this);
|
69
|
+
editor.queueRerender();
|
70
|
+
}
|
66
71
|
}
|
67
72
|
|
68
73
|
public onAddToImage(image: EditorImage) {
|
@@ -124,6 +129,10 @@ export default class ImageBackground extends AbstractComponent implements Restyl
|
|
124
129
|
return false;
|
125
130
|
}
|
126
131
|
|
132
|
+
public isBackground(): boolean {
|
133
|
+
return true;
|
134
|
+
}
|
135
|
+
|
127
136
|
protected serializeToJSON() {
|
128
137
|
return {
|
129
138
|
mainColor: this.mainColor.toHexString(),
|
package/src/localizations/es.ts
CHANGED
@@ -57,10 +57,18 @@ const localization: EditorLocalization = {
|
|
57
57
|
eraserTool: 'Borrador',
|
58
58
|
textTool: 'Texto',
|
59
59
|
enterTextToInsert: 'Entra texto',
|
60
|
+
textSize: 'Tamaño',
|
60
61
|
rerenderAsText: 'Redibuja la pantalla al texto',
|
62
|
+
lockRotation: 'Bloquea rotación',
|
61
63
|
image: 'Imagen',
|
62
64
|
imageSize: (size: number, units: string) => `Tamaño del imagen: ${size} ${units}`,
|
63
65
|
imageLoadError: (message: string)=> `Error cargando imagen: ${message}`,
|
66
|
+
toggleOverflow: 'Más',
|
67
|
+
|
68
|
+
documentProperties: 'Fondo',
|
69
|
+
imageWidthOption: 'Ancho: ',
|
70
|
+
imageHeightOption: 'Alto: ',
|
71
|
+
backgroundColor: 'Color de fondo: '
|
64
72
|
};
|
65
73
|
|
66
74
|
export default localization;
|
package/src/math/Mat33.test.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import Mat33 from './Mat33';
|
2
|
-
import { Vec2 } from './Vec2';
|
2
|
+
import { Point2, Vec2 } from './Vec2';
|
3
3
|
import Vec3 from './Vec3';
|
4
4
|
|
5
5
|
|
@@ -130,7 +130,7 @@ describe('Mat33 tests', () => {
|
|
130
130
|
|
131
131
|
const starterTransform = new Mat33(
|
132
132
|
-0.2588190451025205, -0.9659258262890688, 923.7645204565603,
|
133
|
-
0.9659258262890688,
|
133
|
+
0.9659258262890688, -0.2588190451025205, -49.829447083761465,
|
134
134
|
0, 0, 1
|
135
135
|
);
|
136
136
|
expect(starterTransform.invertable()).toBe(true);
|
@@ -152,11 +152,36 @@ describe('Mat33 tests', () => {
|
|
152
152
|
).objEq(Vec2.unitX, fuzz);
|
153
153
|
});
|
154
154
|
|
155
|
+
it('z-rotation matrix inverses should undo the z-rotation', () => {
|
156
|
+
const testCases: Array<[ number, Point2 ]> = [
|
157
|
+
[ Math.PI / 2, Vec2.zero ],
|
158
|
+
[ Math.PI, Vec2.of(1, 1) ],
|
159
|
+
[ -Math.PI, Vec2.of(1, 1) ],
|
160
|
+
[ -Math.PI * 2, Vec2.of(1, 1) ],
|
161
|
+
[ -Math.PI * 2, Vec2.of(123, 456) ],
|
162
|
+
[ -Math.PI / 4, Vec2.of(123, 456) ],
|
163
|
+
[ 0.1, Vec2.of(1, 2) ],
|
164
|
+
];
|
165
|
+
|
166
|
+
const fuzz = 0.00001;
|
167
|
+
for (const [ angle, center ] of testCases) {
|
168
|
+
const mat = Mat33.zRotation(angle, center);
|
169
|
+
expect(mat.inverse().rightMul(mat)).objEq(Mat33.identity, fuzz);
|
170
|
+
expect(mat.rightMul(mat.inverse())).objEq(Mat33.identity, fuzz);
|
171
|
+
}
|
172
|
+
});
|
173
|
+
|
155
174
|
it('z-rotation should preserve given origin', () => {
|
156
|
-
const
|
157
|
-
|
175
|
+
const testCases: Array<[ number, Point2 ]> = [
|
176
|
+
[ 6.205048847547065, Vec2.of(75.16363373235318, 104.29870408043762) ],
|
177
|
+
[ 1.234, Vec2.of(-56, 789) ],
|
178
|
+
[ -Math.PI, Vec2.of(-56, 789) ],
|
179
|
+
[ -Math.PI / 2, Vec2.of(-0.001, 1.0002) ],
|
180
|
+
];
|
158
181
|
|
159
|
-
|
182
|
+
for (const [angle, rotationOrigin] of testCases) {
|
183
|
+
expect(Mat33.zRotation(angle, rotationOrigin).transformVec2(rotationOrigin)).objEq(rotationOrigin);
|
184
|
+
}
|
160
185
|
});
|
161
186
|
|
162
187
|
it('should correctly apply a mapping to all components', () => {
|