js-draw 0.7.0 → 0.7.2
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 +9 -0
- package/CONTRIBUTING.md +75 -0
- package/dist/bundle.js +1 -1
- package/dist/src/SVGLoader.js +1 -0
- package/dist/src/rendering/renderers/SVGRenderer.js +19 -7
- package/dist/src/tools/TextTool.js +7 -2
- package/package.json +1 -1
- package/src/SVGLoader.ts +1 -0
- package/src/rendering/renderers/SVGRenderer.ts +23 -7
- package/src/tools/TextTool.ts +9 -2
package/dist/src/SVGLoader.js
CHANGED
@@ -177,6 +177,7 @@ export default class SVGLoader {
|
|
177
177
|
else if (child.nodeType === Node.ELEMENT_NODE) {
|
178
178
|
const subElem = child;
|
179
179
|
if (subElem.tagName.toLowerCase() === 'tspan') {
|
180
|
+
// FIXME: tspan's (x, y) components are absolute, not relative to the parent.
|
180
181
|
contentList.push(this.makeText(subElem));
|
181
182
|
}
|
182
183
|
else {
|
@@ -83,11 +83,14 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
83
83
|
}
|
84
84
|
this.lastPathString.push(path.toString());
|
85
85
|
}
|
86
|
-
// Apply [elemTransform] to [elem].
|
87
|
-
|
86
|
+
// Apply [elemTransform] to [elem]. Uses both a `matrix` and `.x`, `.y` properties if `setXY` is true.
|
87
|
+
// Otherwise, just uses a `matrix`.
|
88
|
+
transformFrom(elemTransform, elem, inCanvasSpace = false, setXY = true) {
|
88
89
|
let transform = !inCanvasSpace ? this.getCanvasToScreenTransform().rightMul(elemTransform) : elemTransform;
|
89
90
|
const translation = transform.transformVec2(Vec2.zero);
|
90
|
-
|
91
|
+
if (setXY) {
|
92
|
+
transform = transform.rightMul(Mat33.translation(translation.times(-1)));
|
93
|
+
}
|
91
94
|
if (!transform.eq(Mat33.identity)) {
|
92
95
|
elem.style.transform = `matrix(
|
93
96
|
${transform.a1}, ${transform.b1},
|
@@ -98,8 +101,10 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
98
101
|
else {
|
99
102
|
elem.style.transform = '';
|
100
103
|
}
|
101
|
-
|
102
|
-
|
104
|
+
if (setXY) {
|
105
|
+
elem.setAttribute('x', `${toRoundedString(translation.x)}`);
|
106
|
+
elem.setAttribute('y', `${toRoundedString(translation.y)}`);
|
107
|
+
}
|
103
108
|
}
|
104
109
|
drawText(text, transform, style) {
|
105
110
|
var _a;
|
@@ -120,7 +125,10 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
120
125
|
if (!this.textContainer) {
|
121
126
|
const container = document.createElementNS(svgNameSpace, 'text');
|
122
127
|
container.appendChild(document.createTextNode(text));
|
123
|
-
|
128
|
+
// Don't set .x/.y properties (just use .style.transform).
|
129
|
+
// Child nodes aren't translated by .x/.y properties, but are by .style.transform.
|
130
|
+
const setXY = false;
|
131
|
+
this.transformFrom(transform, container, true, setXY);
|
124
132
|
applyTextStyles(container, style);
|
125
133
|
this.elem.appendChild(container);
|
126
134
|
(_a = this.objectElems) === null || _a === void 0 ? void 0 : _a.push(container);
|
@@ -133,8 +141,12 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
133
141
|
const elem = document.createElementNS(svgNameSpace, 'tspan');
|
134
142
|
elem.appendChild(document.createTextNode(text));
|
135
143
|
this.textContainer.appendChild(elem);
|
144
|
+
// Make .x/.y relative to the parent.
|
136
145
|
transform = this.textContainerTransform.inverse().rightMul(transform);
|
137
|
-
|
146
|
+
// .style.transform does nothing to tspan elements. As such, we need to set x/y:
|
147
|
+
const translation = transform.transformVec2(Vec2.zero);
|
148
|
+
elem.setAttribute('x', `${toRoundedString(translation.x)}`);
|
149
|
+
elem.setAttribute('y', `${toRoundedString(translation.y)}`);
|
138
150
|
applyTextStyles(elem, style);
|
139
151
|
}
|
140
152
|
}
|
@@ -39,6 +39,7 @@ export default class TextTool extends BaseTool {
|
|
39
39
|
background-color: rgba(0, 0, 0, 0);
|
40
40
|
|
41
41
|
white-space: pre;
|
42
|
+
overflow: hidden;
|
42
43
|
|
43
44
|
padding: 0;
|
44
45
|
margin: 0;
|
@@ -118,10 +119,12 @@ export default class TextTool extends BaseTool {
|
|
118
119
|
this.textInputElem = document.createElement('textarea');
|
119
120
|
this.textInputElem.value = initialText;
|
120
121
|
this.textInputElem.style.display = 'inline-block';
|
121
|
-
this.textTargetPosition = textCanvasPos;
|
122
|
+
this.textTargetPosition = this.editor.viewport.roundPoint(textCanvasPos);
|
122
123
|
this.textRotation = -this.editor.viewport.getRotationAngle();
|
123
124
|
this.textScale = Vec2.of(1, 1).times(this.editor.viewport.getSizeOfPixelOnCanvas());
|
124
125
|
this.updateTextInput();
|
126
|
+
// Update the input size/position/etc. after the placeHolder has had time to appear.
|
127
|
+
setTimeout(() => this.updateTextInput(), 0);
|
125
128
|
this.textInputElem.oninput = () => {
|
126
129
|
if (this.textInputElem) {
|
127
130
|
this.textInputElem.style.width = `${this.textInputElem.scrollWidth}px`;
|
@@ -169,6 +172,8 @@ export default class TextTool extends BaseTool {
|
|
169
172
|
const testRegion = Rect2.fromCorners(canvasPos.minus(halfTestRegionSize), canvasPos.plus(halfTestRegionSize));
|
170
173
|
const targetNodes = this.editor.image.getElementsIntersectingRegion(testRegion);
|
171
174
|
const targetTextNodes = targetNodes.filter(node => node instanceof TextComponent);
|
175
|
+
// End any TextNodes we're currently editing.
|
176
|
+
this.flushInput();
|
172
177
|
if (targetTextNodes.length > 0) {
|
173
178
|
const targetNode = targetTextNodes[targetTextNodes.length - 1];
|
174
179
|
this.setTextStyle(targetNode.getTextStyle());
|
@@ -224,7 +229,7 @@ export default class TextTool extends BaseTool {
|
|
224
229
|
}
|
225
230
|
setTextStyle(style) {
|
226
231
|
// Copy the style — we may change parts of it.
|
227
|
-
this.textStyle = Object.assign({}, style);
|
232
|
+
this.textStyle = Object.assign(Object.assign({}, style), { renderingStyle: Object.assign({}, style.renderingStyle) });
|
228
233
|
this.dispatchUpdateEvent();
|
229
234
|
}
|
230
235
|
}
|
package/package.json
CHANGED
package/src/SVGLoader.ts
CHANGED
@@ -218,6 +218,7 @@ export default class SVGLoader implements ImageLoader {
|
|
218
218
|
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
219
219
|
const subElem = child as SVGElement;
|
220
220
|
if (subElem.tagName.toLowerCase() === 'tspan') {
|
221
|
+
// FIXME: tspan's (x, y) components are absolute, not relative to the parent.
|
221
222
|
contentList.push(this.makeText(subElem as SVGTSpanElement));
|
222
223
|
} else {
|
223
224
|
throw new Error(`Unrecognized text child element: ${subElem}`);
|
@@ -99,11 +99,15 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
99
99
|
this.lastPathString.push(path.toString());
|
100
100
|
}
|
101
101
|
|
102
|
-
// Apply [elemTransform] to [elem].
|
103
|
-
|
102
|
+
// Apply [elemTransform] to [elem]. Uses both a `matrix` and `.x`, `.y` properties if `setXY` is true.
|
103
|
+
// Otherwise, just uses a `matrix`.
|
104
|
+
private transformFrom(elemTransform: Mat33, elem: SVGElement, inCanvasSpace: boolean = false, setXY: boolean = true) {
|
104
105
|
let transform = !inCanvasSpace ? this.getCanvasToScreenTransform().rightMul(elemTransform) : elemTransform;
|
105
106
|
const translation = transform.transformVec2(Vec2.zero);
|
106
|
-
|
107
|
+
|
108
|
+
if (setXY) {
|
109
|
+
transform = transform.rightMul(Mat33.translation(translation.times(-1)));
|
110
|
+
}
|
107
111
|
|
108
112
|
if (!transform.eq(Mat33.identity)) {
|
109
113
|
elem.style.transform = `matrix(
|
@@ -115,8 +119,10 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
115
119
|
elem.style.transform = '';
|
116
120
|
}
|
117
121
|
|
118
|
-
|
119
|
-
|
122
|
+
if (setXY) {
|
123
|
+
elem.setAttribute('x', `${toRoundedString(translation.x)}`);
|
124
|
+
elem.setAttribute('y', `${toRoundedString(translation.y)}`);
|
125
|
+
}
|
120
126
|
}
|
121
127
|
|
122
128
|
private textContainer: SVGTextElement|null = null;
|
@@ -140,7 +146,11 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
140
146
|
if (!this.textContainer) {
|
141
147
|
const container = document.createElementNS(svgNameSpace, 'text');
|
142
148
|
container.appendChild(document.createTextNode(text));
|
143
|
-
|
149
|
+
|
150
|
+
// Don't set .x/.y properties (just use .style.transform).
|
151
|
+
// Child nodes aren't translated by .x/.y properties, but are by .style.transform.
|
152
|
+
const setXY = false;
|
153
|
+
this.transformFrom(transform, container, true, setXY);
|
144
154
|
applyTextStyles(container, style);
|
145
155
|
|
146
156
|
this.elem.appendChild(container);
|
@@ -154,8 +164,14 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
154
164
|
elem.appendChild(document.createTextNode(text));
|
155
165
|
this.textContainer.appendChild(elem);
|
156
166
|
|
167
|
+
// Make .x/.y relative to the parent.
|
157
168
|
transform = this.textContainerTransform!.inverse().rightMul(transform);
|
158
|
-
|
169
|
+
|
170
|
+
// .style.transform does nothing to tspan elements. As such, we need to set x/y:
|
171
|
+
const translation = transform.transformVec2(Vec2.zero);
|
172
|
+
elem.setAttribute('x', `${toRoundedString(translation.x)}`);
|
173
|
+
elem.setAttribute('y', `${toRoundedString(translation.y)}`);
|
174
|
+
|
159
175
|
applyTextStyles(elem, style);
|
160
176
|
}
|
161
177
|
}
|
package/src/tools/TextTool.ts
CHANGED
@@ -47,6 +47,7 @@ export default class TextTool extends BaseTool {
|
|
47
47
|
background-color: rgba(0, 0, 0, 0);
|
48
48
|
|
49
49
|
white-space: pre;
|
50
|
+
overflow: hidden;
|
50
51
|
|
51
52
|
padding: 0;
|
52
53
|
margin: 0;
|
@@ -147,11 +148,14 @@ export default class TextTool extends BaseTool {
|
|
147
148
|
this.textInputElem = document.createElement('textarea');
|
148
149
|
this.textInputElem.value = initialText;
|
149
150
|
this.textInputElem.style.display = 'inline-block';
|
150
|
-
this.textTargetPosition = textCanvasPos;
|
151
|
+
this.textTargetPosition = this.editor.viewport.roundPoint(textCanvasPos);
|
151
152
|
this.textRotation = -this.editor.viewport.getRotationAngle();
|
152
153
|
this.textScale = Vec2.of(1, 1).times(this.editor.viewport.getSizeOfPixelOnCanvas());
|
153
154
|
this.updateTextInput();
|
154
155
|
|
156
|
+
// Update the input size/position/etc. after the placeHolder has had time to appear.
|
157
|
+
setTimeout(() => this.updateTextInput(), 0);
|
158
|
+
|
155
159
|
this.textInputElem.oninput = () => {
|
156
160
|
if (this.textInputElem) {
|
157
161
|
this.textInputElem.style.width = `${this.textInputElem.scrollWidth}px`;
|
@@ -206,6 +210,9 @@ export default class TextTool extends BaseTool {
|
|
206
210
|
const targetNodes = this.editor.image.getElementsIntersectingRegion(testRegion);
|
207
211
|
const targetTextNodes = targetNodes.filter(node => node instanceof TextComponent) as TextComponent[];
|
208
212
|
|
213
|
+
// End any TextNodes we're currently editing.
|
214
|
+
this.flushInput();
|
215
|
+
|
209
216
|
if (targetTextNodes.length > 0) {
|
210
217
|
const targetNode = targetTextNodes[targetTextNodes.length - 1];
|
211
218
|
this.setTextStyle(targetNode.getTextStyle());
|
@@ -286,7 +293,7 @@ export default class TextTool extends BaseTool {
|
|
286
293
|
|
287
294
|
private setTextStyle(style: TextStyle) {
|
288
295
|
// Copy the style — we may change parts of it.
|
289
|
-
this.textStyle = {...style};
|
296
|
+
this.textStyle = { ...style, renderingStyle: { ...style.renderingStyle } };
|
290
297
|
this.dispatchUpdateEvent();
|
291
298
|
}
|
292
299
|
}
|