@hprint/plugins 0.0.9-alpha.0 → 0.0.9-alpha.1
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/dist/index.js +3 -3
- package/dist/index.mjs +102 -42
- package/dist/src/plugins/ActualContentLayoutPlugin.d.ts +3 -0
- package/dist/src/plugins/ActualContentLayoutPlugin.d.ts.map +1 -1
- package/dist/src/plugins/HistoryPlugin.d.ts +1 -0
- package/dist/src/plugins/HistoryPlugin.d.ts.map +1 -1
- package/dist/src/plugins/ImageTextListPlugin.d.ts.map +1 -1
- package/dist/src/plugins/QrCodePlugin.d.ts +1 -0
- package/dist/src/plugins/QrCodePlugin.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/plugins/ActualContentLayoutPlugin.ts +246 -213
- package/src/plugins/HistoryPlugin.ts +52 -12
- package/src/plugins/ImageTextListPlugin.ts +541 -540
- package/src/plugins/QrCodePlugin.ts +33 -15
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
import { fabric } from '@hprint/core';
|
|
2
|
-
import type { IEditor, IPluginTempl } from '@hprint/core';
|
|
3
|
-
|
|
4
|
-
export interface ActualContentLayoutSettings {
|
|
5
|
-
actualContentLayout?: boolean;
|
|
6
|
-
overflowMode?: 'clip' | 'expand';
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
type IPlugin = Pick<ActualContentLayoutPlugin, 'applyActualContentLayout'>;
|
|
10
|
-
|
|
11
|
-
declare module '@hprint/core' {
|
|
12
|
-
interface IEditor extends IPlugin {}
|
|
13
|
-
}
|
|
14
|
-
|
|
1
|
+
import { fabric } from '@hprint/core';
|
|
2
|
+
import type { IEditor, IPluginTempl } from '@hprint/core';
|
|
3
|
+
|
|
4
|
+
export interface ActualContentLayoutSettings {
|
|
5
|
+
actualContentLayout?: boolean;
|
|
6
|
+
overflowMode?: 'clip' | 'expand';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type IPlugin = Pick<ActualContentLayoutPlugin, 'applyActualContentLayout'>;
|
|
10
|
+
|
|
11
|
+
declare module '@hprint/core' {
|
|
12
|
+
interface IEditor extends IPlugin {}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
15
|
type LayoutEntry = {
|
|
16
16
|
object: fabric.Object;
|
|
17
17
|
index: number;
|
|
18
18
|
originalTop: number;
|
|
19
19
|
originalBottom: number;
|
|
20
20
|
};
|
|
21
|
-
|
|
22
|
-
class ActualContentLayoutPlugin implements IPluginTempl {
|
|
23
|
-
static pluginName = 'ActualContentLayoutPlugin';
|
|
24
|
-
static apis = ['applyActualContentLayout'];
|
|
25
|
-
|
|
26
|
-
constructor(
|
|
27
|
-
public canvas: fabric.Canvas,
|
|
28
|
-
public editor: IEditor
|
|
29
|
-
) {}
|
|
30
|
-
|
|
31
|
-
private layoutOrigins = new WeakMap<
|
|
32
|
-
fabric.Object,
|
|
33
|
-
{ top: number; height: number }
|
|
34
|
-
>();
|
|
35
|
-
|
|
36
|
-
async hookTransformObjectEnd(...args: unknown[]) {
|
|
37
|
-
const { originObject, fabricObject } = args[0] as {
|
|
38
|
-
originObject: any;
|
|
39
|
-
fabricObject: fabric.Object;
|
|
40
|
-
};
|
|
41
|
-
if (!this.isPrintableObject(originObject)) return;
|
|
42
|
-
|
|
43
|
-
const mmPerPx = Number(this.editor.getSizeByUnit?.(1, 'mm')) || 1;
|
|
44
|
-
this.layoutOrigins.set(fabricObject, {
|
|
45
|
-
top: this.getOriginalTop(originObject, fabricObject, mmPerPx),
|
|
46
|
-
height: this.getOriginalHeight(
|
|
47
|
-
originObject,
|
|
48
|
-
fabricObject,
|
|
49
|
-
mmPerPx
|
|
50
|
-
),
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
21
|
+
|
|
22
|
+
class ActualContentLayoutPlugin implements IPluginTempl {
|
|
23
|
+
static pluginName = 'ActualContentLayoutPlugin';
|
|
24
|
+
static apis = ['applyActualContentLayout'];
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
public canvas: fabric.Canvas,
|
|
28
|
+
public editor: IEditor
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
private layoutOrigins = new WeakMap<
|
|
32
|
+
fabric.Object,
|
|
33
|
+
{ top: number; height: number }
|
|
34
|
+
>();
|
|
35
|
+
|
|
36
|
+
async hookTransformObjectEnd(...args: unknown[]) {
|
|
37
|
+
const { originObject, fabricObject } = args[0] as {
|
|
38
|
+
originObject: any;
|
|
39
|
+
fabricObject: fabric.Object;
|
|
40
|
+
};
|
|
41
|
+
if (!this.isPrintableObject(originObject)) return;
|
|
42
|
+
|
|
43
|
+
const mmPerPx = Number(this.editor.getSizeByUnit?.(1, 'mm')) || 1;
|
|
44
|
+
this.layoutOrigins.set(fabricObject, {
|
|
45
|
+
top: this.getOriginalTop(originObject, fabricObject, mmPerPx),
|
|
46
|
+
height: this.getOriginalHeight(
|
|
47
|
+
originObject,
|
|
48
|
+
fabricObject,
|
|
49
|
+
mmPerPx
|
|
50
|
+
),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
54
|
applyActualContentLayout(templateContent: any, templateHeight: number) {
|
|
55
55
|
const settings = (templateContent?.templateSettings ||
|
|
56
56
|
{}) as ActualContentLayoutSettings;
|
|
@@ -60,173 +60,198 @@ class ActualContentLayoutPlugin implements IPluginTempl {
|
|
|
60
60
|
.getObjects()
|
|
61
61
|
.filter(this.isPrintableObject);
|
|
62
62
|
const mmPerPx = Number(this.editor.getSizeByUnit?.(1, 'mm')) || 1;
|
|
63
|
+
const sourceObjects = this.getTemplateSourceObjects(templateContent);
|
|
63
64
|
|
|
64
65
|
fabricObjects.forEach((object) => this.preparePrintLayoutObject(object));
|
|
65
66
|
|
|
66
67
|
const entries = fabricObjects.map((object, index) => {
|
|
67
68
|
const origin = this.layoutOrigins.get(object);
|
|
69
|
+
const source = this.getSourceObject(object, sourceObjects);
|
|
68
70
|
const originalTop =
|
|
69
|
-
origin?.top ??
|
|
71
|
+
origin?.top ??
|
|
72
|
+
this.getOriginalTop(source, object, mmPerPx);
|
|
70
73
|
const originalHeight =
|
|
71
74
|
origin?.height ??
|
|
72
|
-
|
|
73
|
-
mmPerPx;
|
|
75
|
+
this.getOriginalHeight(source, object, mmPerPx);
|
|
74
76
|
return {
|
|
75
77
|
object,
|
|
76
78
|
index,
|
|
77
79
|
originalTop,
|
|
78
|
-
originalBottom: originalTop + originalHeight,
|
|
79
|
-
};
|
|
80
|
-
}) as LayoutEntry[];
|
|
81
|
-
|
|
82
|
-
entries.sort(
|
|
83
|
-
(a, b) => a.originalTop - b.originalTop || a.index - b.index
|
|
84
|
-
);
|
|
85
|
-
const rows: LayoutEntry[][] = [];
|
|
86
|
-
const sameRowTolerance = 0.01;
|
|
87
|
-
entries.forEach((entry) => {
|
|
88
|
-
const row = rows[rows.length - 1];
|
|
89
|
-
if (
|
|
90
|
-
row &&
|
|
91
|
-
Math.abs(row[0].originalTop - entry.originalTop) <=
|
|
92
|
-
sameRowTolerance
|
|
93
|
-
) {
|
|
94
|
-
row.push(entry);
|
|
95
|
-
} else {
|
|
96
|
-
rows.push([entry]);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
let previousOriginalBottom: number | undefined;
|
|
101
|
-
let previousActualBottom: number | undefined;
|
|
102
|
-
let pendingGap = 0;
|
|
103
|
-
let lastVisibleOriginalBottom = 0;
|
|
104
|
-
|
|
105
|
-
rows.forEach((row) => {
|
|
106
|
-
const rowOriginalTop = row[0].originalTop;
|
|
107
|
-
const rowOriginalBottom = Math.max(
|
|
108
|
-
...row.map((entry) => entry.originalBottom)
|
|
109
|
-
);
|
|
110
|
-
if (previousOriginalBottom !== undefined) {
|
|
111
|
-
pendingGap += Math.max(
|
|
112
|
-
0,
|
|
113
|
-
rowOriginalTop - previousOriginalBottom
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
previousOriginalBottom = rowOriginalBottom;
|
|
117
|
-
|
|
118
|
-
const visibleEntries = row.filter(
|
|
119
|
-
(entry) => !this.isEmptyLayoutObject(entry.object)
|
|
120
|
-
);
|
|
121
|
-
if (!visibleEntries.length) return;
|
|
122
|
-
|
|
123
|
-
const targetTop =
|
|
124
|
-
previousActualBottom === undefined
|
|
125
|
-
? rowOriginalTop
|
|
126
|
-
: previousActualBottom + pendingGap;
|
|
127
|
-
visibleEntries.forEach((entry) => {
|
|
128
|
-
entry.object.set('top', targetTop / mmPerPx);
|
|
129
|
-
entry.object.setCoords();
|
|
130
|
-
});
|
|
131
|
-
previousActualBottom = Math.max(
|
|
132
|
-
...visibleEntries.map(
|
|
133
|
-
(entry) =>
|
|
134
|
-
(Number(entry.object.top || 0) +
|
|
135
|
-
this.getActualLayoutHeight(entry.object)) *
|
|
136
|
-
mmPerPx
|
|
137
|
-
)
|
|
138
|
-
);
|
|
139
|
-
lastVisibleOriginalBottom = rowOriginalBottom;
|
|
140
|
-
pendingGap = 0;
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
this.canvas.requestRenderAll();
|
|
144
|
-
if (
|
|
145
|
-
settings.overflowMode !== 'expand' ||
|
|
146
|
-
previousActualBottom === undefined
|
|
147
|
-
) {
|
|
148
|
-
return templateHeight;
|
|
149
|
-
}
|
|
150
|
-
const bottomSpace = Math.max(
|
|
151
|
-
0,
|
|
152
|
-
templateHeight - lastVisibleOriginalBottom
|
|
153
|
-
);
|
|
154
|
-
return Math.max(
|
|
155
|
-
templateHeight,
|
|
156
|
-
previousActualBottom + bottomSpace
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
80
|
+
originalBottom: originalTop + originalHeight,
|
|
81
|
+
};
|
|
82
|
+
}) as LayoutEntry[];
|
|
83
|
+
|
|
84
|
+
entries.sort(
|
|
85
|
+
(a, b) => a.originalTop - b.originalTop || a.index - b.index
|
|
86
|
+
);
|
|
87
|
+
const rows: LayoutEntry[][] = [];
|
|
88
|
+
const sameRowTolerance = 0.01;
|
|
89
|
+
entries.forEach((entry) => {
|
|
90
|
+
const row = rows[rows.length - 1];
|
|
91
|
+
if (
|
|
92
|
+
row &&
|
|
93
|
+
Math.abs(row[0].originalTop - entry.originalTop) <=
|
|
94
|
+
sameRowTolerance
|
|
95
|
+
) {
|
|
96
|
+
row.push(entry);
|
|
97
|
+
} else {
|
|
98
|
+
rows.push([entry]);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
let previousOriginalBottom: number | undefined;
|
|
103
|
+
let previousActualBottom: number | undefined;
|
|
104
|
+
let pendingGap = 0;
|
|
105
|
+
let lastVisibleOriginalBottom = 0;
|
|
106
|
+
|
|
107
|
+
rows.forEach((row) => {
|
|
108
|
+
const rowOriginalTop = row[0].originalTop;
|
|
109
|
+
const rowOriginalBottom = Math.max(
|
|
110
|
+
...row.map((entry) => entry.originalBottom)
|
|
111
|
+
);
|
|
112
|
+
if (previousOriginalBottom !== undefined) {
|
|
113
|
+
pendingGap += Math.max(
|
|
114
|
+
0,
|
|
115
|
+
rowOriginalTop - previousOriginalBottom
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
previousOriginalBottom = rowOriginalBottom;
|
|
119
|
+
|
|
120
|
+
const visibleEntries = row.filter(
|
|
121
|
+
(entry) => !this.isEmptyLayoutObject(entry.object)
|
|
122
|
+
);
|
|
123
|
+
if (!visibleEntries.length) return;
|
|
124
|
+
|
|
125
|
+
const targetTop =
|
|
126
|
+
previousActualBottom === undefined
|
|
127
|
+
? rowOriginalTop
|
|
128
|
+
: previousActualBottom + pendingGap;
|
|
129
|
+
visibleEntries.forEach((entry) => {
|
|
130
|
+
entry.object.set('top', targetTop / mmPerPx);
|
|
131
|
+
entry.object.setCoords();
|
|
132
|
+
});
|
|
133
|
+
previousActualBottom = Math.max(
|
|
134
|
+
...visibleEntries.map(
|
|
135
|
+
(entry) =>
|
|
136
|
+
(Number(entry.object.top || 0) +
|
|
137
|
+
this.getActualLayoutHeight(entry.object)) *
|
|
138
|
+
mmPerPx
|
|
139
|
+
)
|
|
140
|
+
);
|
|
141
|
+
lastVisibleOriginalBottom = rowOriginalBottom;
|
|
142
|
+
pendingGap = 0;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
this.canvas.requestRenderAll();
|
|
146
|
+
if (
|
|
147
|
+
settings.overflowMode !== 'expand' ||
|
|
148
|
+
previousActualBottom === undefined
|
|
149
|
+
) {
|
|
150
|
+
return templateHeight;
|
|
151
|
+
}
|
|
152
|
+
const bottomSpace = Math.max(
|
|
153
|
+
0,
|
|
154
|
+
templateHeight - lastVisibleOriginalBottom
|
|
155
|
+
);
|
|
156
|
+
return Math.max(
|
|
157
|
+
templateHeight,
|
|
158
|
+
previousActualBottom + bottomSpace
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
160
162
|
private getOriginalTop(
|
|
161
163
|
source: any,
|
|
162
164
|
object: fabric.Object,
|
|
163
165
|
mmPerPx: number
|
|
164
166
|
) {
|
|
165
|
-
const top = Number(source?.top);
|
|
166
|
-
if (Number.isFinite(top)) return top * mmPerPx;
|
|
167
|
-
return this.getOriginMmValue(
|
|
168
|
-
source,
|
|
169
|
-
'top',
|
|
170
|
-
Number(object.top || 0) * mmPerPx
|
|
167
|
+
const top = Number(source?.top);
|
|
168
|
+
if (Number.isFinite(top)) return top * mmPerPx;
|
|
169
|
+
return this.getOriginMmValue(
|
|
170
|
+
source,
|
|
171
|
+
'top',
|
|
172
|
+
Number(object.top || 0) * mmPerPx
|
|
171
173
|
);
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
private getOriginalHeight(
|
|
175
177
|
source: any,
|
|
176
|
-
object: fabric.Object,
|
|
177
|
-
mmPerPx: number
|
|
178
|
-
) {
|
|
179
|
-
const height = Number(source?.height);
|
|
180
|
-
const scaleY = Number(source?.scaleY ?? 1);
|
|
181
|
-
if (Number.isFinite(height) && Number.isFinite(scaleY)) {
|
|
182
|
-
return Math.abs(height * scaleY) * mmPerPx;
|
|
183
|
-
}
|
|
184
|
-
return this.getOriginMmValue(
|
|
185
|
-
source,
|
|
186
|
-
'height',
|
|
187
|
-
Number(object.getScaledHeight?.() || object.height || 0) *
|
|
188
|
-
mmPerPx
|
|
178
|
+
object: fabric.Object,
|
|
179
|
+
mmPerPx: number
|
|
180
|
+
) {
|
|
181
|
+
const height = Number(source?.height);
|
|
182
|
+
const scaleY = Number(source?.scaleY ?? 1);
|
|
183
|
+
if (Number.isFinite(height) && Number.isFinite(scaleY)) {
|
|
184
|
+
return Math.abs(height * scaleY) * mmPerPx;
|
|
185
|
+
}
|
|
186
|
+
return this.getOriginMmValue(
|
|
187
|
+
source,
|
|
188
|
+
'height',
|
|
189
|
+
Number(object.getScaledHeight?.() || object.height || 0) *
|
|
190
|
+
mmPerPx
|
|
189
191
|
);
|
|
190
192
|
}
|
|
191
193
|
|
|
192
|
-
private
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (
|
|
199
|
-
originalValue === undefined ||
|
|
200
|
-
originalValue === null ||
|
|
201
|
-
originalValue === ''
|
|
202
|
-
) {
|
|
203
|
-
return fallback;
|
|
204
|
-
}
|
|
205
|
-
const value = Number(originalValue);
|
|
206
|
-
return Number.isFinite(value) ? value : fallback;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
private preparePrintLayoutObject(object: any) {
|
|
210
|
-
if (object?.extensionType !== 'imageTextList') return;
|
|
211
|
-
if (object.extension?._clipContent === true) return;
|
|
212
|
-
|
|
213
|
-
// Design canvases can opt into clipping, but print/layout canvases should
|
|
214
|
-
// measure and render the natural image-text content height.
|
|
215
|
-
if (object.clipPath) object.set('clipPath', undefined);
|
|
216
|
-
object.set({
|
|
217
|
-
objectCaching: false,
|
|
218
|
-
dirty: true,
|
|
194
|
+
private getTemplateSourceObjects(templateContent: any) {
|
|
195
|
+
const sourceObjects = new Map<string, any>();
|
|
196
|
+
if (!Array.isArray(templateContent?.objects)) return sourceObjects;
|
|
197
|
+
templateContent.objects.forEach((source: any, index: number) => {
|
|
198
|
+
if (source?.id) sourceObjects.set(`id:${source.id}`, source);
|
|
199
|
+
sourceObjects.set(`index:${index}`, source);
|
|
219
200
|
});
|
|
201
|
+
return sourceObjects;
|
|
220
202
|
}
|
|
221
203
|
|
|
222
|
-
private
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
204
|
+
private getSourceObject(
|
|
205
|
+
object: any,
|
|
206
|
+
sourceObjects: Map<string, any>
|
|
207
|
+
) {
|
|
208
|
+
if (object?.id) {
|
|
209
|
+
const source = sourceObjects.get(`id:${object.id}`);
|
|
210
|
+
if (source) return source;
|
|
211
|
+
}
|
|
212
|
+
const objects = this.canvas.getObjects().filter(this.isPrintableObject);
|
|
213
|
+
const index = objects.indexOf(object);
|
|
214
|
+
return sourceObjects.get(`index:${index}`);
|
|
228
215
|
}
|
|
229
|
-
|
|
216
|
+
|
|
217
|
+
private getOriginMmValue(
|
|
218
|
+
source: any,
|
|
219
|
+
field: 'top' | 'height',
|
|
220
|
+
fallback: number
|
|
221
|
+
) {
|
|
222
|
+
const originalValue = source?._originSize?.mm?.[field];
|
|
223
|
+
if (
|
|
224
|
+
originalValue === undefined ||
|
|
225
|
+
originalValue === null ||
|
|
226
|
+
originalValue === ''
|
|
227
|
+
) {
|
|
228
|
+
return fallback;
|
|
229
|
+
}
|
|
230
|
+
const value = Number(originalValue);
|
|
231
|
+
return Number.isFinite(value) ? value : fallback;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private preparePrintLayoutObject(object: any) {
|
|
235
|
+
if (object?.extensionType !== 'imageTextList') return;
|
|
236
|
+
if (object.extension?._clipContent === true) return;
|
|
237
|
+
|
|
238
|
+
// Design canvases can opt into clipping, but print/layout canvases should
|
|
239
|
+
// measure and render the natural image-text content height.
|
|
240
|
+
if (object.clipPath) object.set('clipPath', undefined);
|
|
241
|
+
object.set({
|
|
242
|
+
objectCaching: false,
|
|
243
|
+
dirty: true,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private isPrintableObject(object: any) {
|
|
248
|
+
return (
|
|
249
|
+
object?.id !== 'workspace' &&
|
|
250
|
+
object?.id !== 'coverMask' &&
|
|
251
|
+
object?.type !== 'GuideLine'
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
230
255
|
private isEmptyLayoutObject(object: any) {
|
|
231
256
|
if (object?.visible === false) return true;
|
|
232
257
|
const field = object?.extension?._field_;
|
|
@@ -235,42 +260,50 @@ class ActualContentLayoutPlugin implements IPluginTempl {
|
|
|
235
260
|
return String(object.text ?? '') === '';
|
|
236
261
|
}
|
|
237
262
|
if (['barcode', 'qrcode'].includes(object.extensionType)) {
|
|
238
|
-
return
|
|
263
|
+
return (
|
|
264
|
+
String(object.extension?.value ?? '') === '' &&
|
|
265
|
+
!this.hasVisibleImageSource(object)
|
|
266
|
+
);
|
|
239
267
|
}
|
|
240
268
|
if (object.extensionType === 'imageTextList') {
|
|
241
269
|
return (
|
|
242
270
|
!Array.isArray(object.extension?.items) ||
|
|
243
|
-
object.extension.items.length === 0
|
|
244
|
-
);
|
|
271
|
+
object.extension.items.length === 0
|
|
272
|
+
);
|
|
245
273
|
}
|
|
246
274
|
return false;
|
|
247
275
|
}
|
|
248
276
|
|
|
277
|
+
private hasVisibleImageSource(object: any) {
|
|
278
|
+
const src = String(object?.getSrc?.() ?? object?.src ?? '');
|
|
279
|
+
return src !== '' && /^data:image\//i.test(src);
|
|
280
|
+
}
|
|
281
|
+
|
|
249
282
|
private getActualLayoutHeight(object: any) {
|
|
250
283
|
if (object?.extensionType !== 'imageTextList') {
|
|
251
284
|
return Number(
|
|
252
|
-
object.getScaledHeight?.() || object.height || 0
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const children = object.getObjects?.() || object._objects || [];
|
|
257
|
-
const contentChildren = children.slice(1);
|
|
258
|
-
if (!contentChildren.length) return 0;
|
|
259
|
-
|
|
260
|
-
const groupHeight = Number(object.height || 0);
|
|
261
|
-
const groupScaleY = Math.abs(Number(object.scaleY ?? 1));
|
|
262
|
-
const contentBottom = Math.max(
|
|
263
|
-
...contentChildren.map(
|
|
264
|
-
(child: any) =>
|
|
265
|
-
Number(child.top || 0) +
|
|
266
|
-
groupHeight / 2 +
|
|
267
|
-
Number(
|
|
268
|
-
child.getScaledHeight?.() || child.height || 0
|
|
269
|
-
)
|
|
270
|
-
)
|
|
271
|
-
);
|
|
272
|
-
return Math.max(0, contentBottom) * groupScaleY;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export default ActualContentLayoutPlugin;
|
|
285
|
+
object.getScaledHeight?.() || object.height || 0
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const children = object.getObjects?.() || object._objects || [];
|
|
290
|
+
const contentChildren = children.slice(1);
|
|
291
|
+
if (!contentChildren.length) return 0;
|
|
292
|
+
|
|
293
|
+
const groupHeight = Number(object.height || 0);
|
|
294
|
+
const groupScaleY = Math.abs(Number(object.scaleY ?? 1));
|
|
295
|
+
const contentBottom = Math.max(
|
|
296
|
+
...contentChildren.map(
|
|
297
|
+
(child: any) =>
|
|
298
|
+
Number(child.top || 0) +
|
|
299
|
+
groupHeight / 2 +
|
|
300
|
+
Number(
|
|
301
|
+
child.getScaledHeight?.() || child.height || 0
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
);
|
|
305
|
+
return Math.max(0, contentBottom) * groupScaleY;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export default ActualContentLayoutPlugin;
|
|
@@ -86,7 +86,11 @@ class HistoryPlugin implements IPluginTempl {
|
|
|
86
86
|
// 加载状态
|
|
87
87
|
private _loadState(state: string, eventName: string, callback?: callback) {
|
|
88
88
|
this.isLoading = true;
|
|
89
|
-
this.isProcessing = true;
|
|
89
|
+
this.isProcessing = true;
|
|
90
|
+
const activeIds = this.canvas
|
|
91
|
+
.getActiveObjects()
|
|
92
|
+
.map((object: any) => object?.id)
|
|
93
|
+
.filter(Boolean);
|
|
90
94
|
|
|
91
95
|
// 处理 workspace 的特殊情况
|
|
92
96
|
const parsedState = JSON.parse(state);
|
|
@@ -97,17 +101,53 @@ class HistoryPlugin implements IPluginTempl {
|
|
|
97
101
|
workspace.evented = false;
|
|
98
102
|
}
|
|
99
103
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
const hookPromises: Promise<void>[] = [];
|
|
105
|
+
this.canvas.loadFromJSON(
|
|
106
|
+
state,
|
|
107
|
+
() => {
|
|
108
|
+
Promise.all(hookPromises).then(() => {
|
|
109
|
+
this.restoreActiveObjects(activeIds);
|
|
110
|
+
this.canvas.renderAll();
|
|
111
|
+
this.canvas.fire(eventName);
|
|
112
|
+
this.isProcessing = false;
|
|
113
|
+
this.isLoading = false;
|
|
114
|
+
callback?.();
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
(originObject: any, fabricObject: fabric.Object) => {
|
|
118
|
+
hookPromises.push(
|
|
119
|
+
new Promise((resolve) => {
|
|
120
|
+
this.editor.hooksEntity.hookTransformObjectEnd.callAsync(
|
|
121
|
+
{ originObject, fabricObject },
|
|
122
|
+
() => resolve()
|
|
123
|
+
);
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private restoreActiveObjects(activeIds: string[]) {
|
|
131
|
+
this.canvas.discardActiveObject();
|
|
132
|
+
if (!activeIds.length) return;
|
|
133
|
+
|
|
134
|
+
const objects = this.canvas
|
|
135
|
+
.getObjects()
|
|
136
|
+
.filter((object: any) => activeIds.includes(object?.id));
|
|
137
|
+
if (objects.length === 1) {
|
|
138
|
+
this.canvas.setActiveObject(objects[0]);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (objects.length > 1) {
|
|
142
|
+
const selection = new fabric.ActiveSelection(objects, {
|
|
143
|
+
canvas: this.canvas,
|
|
144
|
+
});
|
|
145
|
+
this.canvas.setActiveObject(selection);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 获取历史记录状态
|
|
150
|
+
private getState() {
|
|
111
151
|
return {
|
|
112
152
|
undoCount: this.currentIndex - 1,
|
|
113
153
|
redoCount: this.stack.length - this.currentIndex,
|