@hprint/plugins 0.0.7 → 0.0.9-alpha.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/dist/index.js +44 -44
- package/dist/index.mjs +6957 -6550
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/plugins/ActualContentLayoutPlugin.d.ts +29 -0
- package/dist/src/plugins/ActualContentLayoutPlugin.d.ts.map +1 -0
- package/dist/src/plugins/CopyPlugin.d.ts.map +1 -1
- package/dist/src/plugins/ImageTextListPlugin.d.ts +68 -0
- package/dist/src/plugins/ImageTextListPlugin.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/assets/style/resizePlugin.css +27 -27
- package/src/index.ts +11 -5
- package/src/objects/Arrow.js +47 -47
- package/src/objects/ThinTailArrow.js +50 -50
- package/src/plugins/ActualContentLayoutPlugin.ts +276 -0
- package/src/plugins/ControlsPlugin.ts +413 -413
- package/src/plugins/ControlsRotatePlugin.ts +111 -111
- package/src/plugins/CopyPlugin.ts +260 -258
- package/src/plugins/DeleteHotKeyPlugin.ts +57 -57
- package/src/plugins/DrawLinePlugin.ts +162 -162
- package/src/plugins/DrawPolygonPlugin.ts +205 -205
- package/src/plugins/DringPlugin.ts +125 -125
- package/src/plugins/FlipPlugin.ts +59 -59
- package/src/plugins/FontPlugin.ts +165 -165
- package/src/plugins/FreeDrawPlugin.ts +49 -49
- package/src/plugins/GroupPlugin.ts +82 -82
- package/src/plugins/GroupTextEditorPlugin.ts +198 -198
- package/src/plugins/HistoryPlugin.ts +181 -181
- package/src/plugins/ImageStroke.ts +121 -121
- package/src/plugins/ImageTextListPlugin.ts +540 -0
- package/src/plugins/LayerPlugin.ts +108 -108
- package/src/plugins/MaskPlugin.ts +155 -155
- package/src/plugins/MaterialPlugin.ts +224 -224
- package/src/plugins/MiddleMousePlugin.ts +45 -45
- package/src/plugins/MoveHotKeyPlugin.ts +46 -46
- package/src/plugins/PathTextPlugin.ts +89 -89
- package/src/plugins/PolygonModifyPlugin.ts +224 -224
- package/src/plugins/PrintPlugin.ts +81 -81
- package/src/plugins/PsdPlugin.ts +52 -52
- package/src/plugins/SimpleClipImagePlugin.ts +244 -244
- package/src/types/eventType.ts +11 -11
- package/src/utils/psd.js +432 -432
- package/src/utils/ruler/guideline.ts +145 -145
- package/src/utils/ruler/index.ts +91 -91
- package/src/utils/ruler/utils.ts +162 -162
- package/tsconfig.json +10 -10
- package/vite.config.ts +29 -29
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { fabric } from '@hprint/core';
|
|
2
|
+
import type { IEditor, IPluginTempl } from '@hprint/core';
|
|
3
|
+
import { getUnit, convertSingle, formatOriginValues } from '../utils/units';
|
|
4
|
+
|
|
5
|
+
export type ImageTextListLayout =
|
|
6
|
+
| 'item-vertical'
|
|
7
|
+
| 'icon-text-split'
|
|
8
|
+
| 'icon-only'
|
|
9
|
+
| 'text-only';
|
|
10
|
+
|
|
11
|
+
export interface ImageTextListItem {
|
|
12
|
+
src: string;
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ImageTextListOptions {
|
|
17
|
+
items?: ImageTextListItem[];
|
|
18
|
+
_renderItems?: ImageTextListItem[];
|
|
19
|
+
_clipContent?: boolean;
|
|
20
|
+
layout?: ImageTextListLayout;
|
|
21
|
+
width?: number;
|
|
22
|
+
height?: number;
|
|
23
|
+
iconSize?: number;
|
|
24
|
+
horizontalGap?: number;
|
|
25
|
+
verticalGap?: number;
|
|
26
|
+
itemGap?: number;
|
|
27
|
+
fontFamily?: string;
|
|
28
|
+
fontSize?: number;
|
|
29
|
+
fontWeight?: string;
|
|
30
|
+
fontStyle?: string;
|
|
31
|
+
underline?: boolean | string;
|
|
32
|
+
linethrough?: boolean | string;
|
|
33
|
+
textAlign?: 'left' | 'center' | 'right' | 'justify';
|
|
34
|
+
textWrap?: boolean;
|
|
35
|
+
lineHeight?: number;
|
|
36
|
+
charSpacing?: number;
|
|
37
|
+
color?: string;
|
|
38
|
+
[key: string]: any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type ImageTextListGroup = fabric.Group & {
|
|
42
|
+
extensionType?: string;
|
|
43
|
+
extension?: ImageTextListOptions;
|
|
44
|
+
_originSize?: Record<string, any>;
|
|
45
|
+
setExtension?: (fields: Record<string, any>) => Promise<void>;
|
|
46
|
+
setExtensionByUnit?: (fields: Record<string, any>) => Promise<void>;
|
|
47
|
+
setByUnit?: (field: string, value: any) => Promise<any>;
|
|
48
|
+
__imageTextListModified?: () => void;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type IPlugin = Pick<
|
|
52
|
+
ImageTextListPlugin,
|
|
53
|
+
'createImageTextList' | 'initImageTextListEvents' | 'refreshImageTextList'
|
|
54
|
+
>;
|
|
55
|
+
|
|
56
|
+
declare module '@hprint/core' {
|
|
57
|
+
interface IEditor extends IPlugin {}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const DEFAULT_OPTIONS: Required<
|
|
61
|
+
Pick<
|
|
62
|
+
ImageTextListOptions,
|
|
63
|
+
| 'layout'
|
|
64
|
+
| 'width'
|
|
65
|
+
| 'iconSize'
|
|
66
|
+
| 'horizontalGap'
|
|
67
|
+
| 'verticalGap'
|
|
68
|
+
| 'itemGap'
|
|
69
|
+
| 'fontFamily'
|
|
70
|
+
| 'fontSize'
|
|
71
|
+
| 'fontWeight'
|
|
72
|
+
| 'fontStyle'
|
|
73
|
+
| 'underline'
|
|
74
|
+
| 'linethrough'
|
|
75
|
+
| 'textAlign'
|
|
76
|
+
| 'textWrap'
|
|
77
|
+
| 'lineHeight'
|
|
78
|
+
| 'charSpacing'
|
|
79
|
+
| 'color'
|
|
80
|
+
>
|
|
81
|
+
> = {
|
|
82
|
+
layout: 'item-vertical',
|
|
83
|
+
width: 30,
|
|
84
|
+
iconSize: 5,
|
|
85
|
+
horizontalGap: 1.5,
|
|
86
|
+
verticalGap: 1.5,
|
|
87
|
+
itemGap: 1.5,
|
|
88
|
+
fontFamily: 'Microsoft YaHei',
|
|
89
|
+
fontSize: 3,
|
|
90
|
+
fontWeight: 'normal',
|
|
91
|
+
fontStyle: 'normal',
|
|
92
|
+
underline: false,
|
|
93
|
+
linethrough: false,
|
|
94
|
+
textAlign: 'left',
|
|
95
|
+
textWrap: true,
|
|
96
|
+
lineHeight: 1.5,
|
|
97
|
+
charSpacing: 0,
|
|
98
|
+
color: '#000000',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
class ImageTextListPlugin implements IPluginTempl {
|
|
102
|
+
static pluginName = 'ImageTextListPlugin';
|
|
103
|
+
static apis = [
|
|
104
|
+
'createImageTextList',
|
|
105
|
+
'initImageTextListEvents',
|
|
106
|
+
'refreshImageTextList',
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
constructor(
|
|
110
|
+
public canvas: fabric.Canvas,
|
|
111
|
+
public editor: IEditor
|
|
112
|
+
) {}
|
|
113
|
+
|
|
114
|
+
async hookTransform(object: any) {
|
|
115
|
+
if (object.extensionType !== 'imageTextList') return;
|
|
116
|
+
const left = object.left;
|
|
117
|
+
const top = object.top;
|
|
118
|
+
const group = await this.buildGroup(object.extension || {});
|
|
119
|
+
const transformed = group.toObject(this.editor.getExtensionKey?.() || []);
|
|
120
|
+
Object.assign(object, transformed, {
|
|
121
|
+
left,
|
|
122
|
+
top,
|
|
123
|
+
extensionType: 'imageTextList',
|
|
124
|
+
extension: this.normalizeOptions(object.extension || {}),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async hookTransformObjectEnd(...args: unknown[]) {
|
|
129
|
+
const { originObject, fabricObject } = args[0] as {
|
|
130
|
+
originObject: any;
|
|
131
|
+
fabricObject: ImageTextListGroup;
|
|
132
|
+
};
|
|
133
|
+
if (originObject.extensionType === 'imageTextList') {
|
|
134
|
+
this.initImageTextListEvents(fabricObject);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async createImageTextList(
|
|
139
|
+
items: ImageTextListItem[],
|
|
140
|
+
options: ImageTextListOptions = {}
|
|
141
|
+
): Promise<ImageTextListGroup> {
|
|
142
|
+
const extension = this.normalizeOptions({ ...options, items });
|
|
143
|
+
const group = await this.buildGroup(extension);
|
|
144
|
+
group.set({
|
|
145
|
+
extensionType: 'imageTextList',
|
|
146
|
+
extension,
|
|
147
|
+
} as any);
|
|
148
|
+
this.updateOriginSize(group, extension);
|
|
149
|
+
this.initImageTextListEvents(group);
|
|
150
|
+
return group;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
initImageTextListEvents(group: ImageTextListGroup) {
|
|
154
|
+
group.setExtension = async (fields: Record<string, any>) => {
|
|
155
|
+
const extension = this.normalizeOptions({
|
|
156
|
+
...(group.get('extension') || {}),
|
|
157
|
+
...(fields || {}),
|
|
158
|
+
});
|
|
159
|
+
group.set('extension', extension);
|
|
160
|
+
await this.refreshImageTextList(group);
|
|
161
|
+
};
|
|
162
|
+
group.setExtensionByUnit = group.setExtension;
|
|
163
|
+
|
|
164
|
+
this.editor.addSetAndSyncByUnit?.(group);
|
|
165
|
+
const originalSetByUnit = group.setByUnit?.bind(group);
|
|
166
|
+
if (originalSetByUnit) {
|
|
167
|
+
group.setByUnit = async (field: string, value: any) => {
|
|
168
|
+
if (field === 'width' || field === 'height') {
|
|
169
|
+
const extension = this.normalizeOptions({
|
|
170
|
+
...(group.get('extension') || {}),
|
|
171
|
+
[field]: Number(value),
|
|
172
|
+
});
|
|
173
|
+
group.set('extension', extension);
|
|
174
|
+
await this.refreshImageTextList(group);
|
|
175
|
+
return group;
|
|
176
|
+
}
|
|
177
|
+
return originalSetByUnit(field, value);
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (group.__imageTextListModified) {
|
|
182
|
+
group.off('modified', group.__imageTextListModified);
|
|
183
|
+
}
|
|
184
|
+
group.__imageTextListModified = () => {
|
|
185
|
+
const scaleX = group.scaleX || 1;
|
|
186
|
+
const scaleY = group.scaleY || 1;
|
|
187
|
+
if (scaleX === 1 && scaleY === 1) return;
|
|
188
|
+
const extension = this.normalizeOptions(group.get('extension') || {});
|
|
189
|
+
const widthPx = Math.max(1, (group.width || 1) * scaleX);
|
|
190
|
+
const heightPx = Math.max(1, (group.height || 1) * scaleY);
|
|
191
|
+
group.set({ scaleX: 1, scaleY: 1 });
|
|
192
|
+
group.set('extension', {
|
|
193
|
+
...extension,
|
|
194
|
+
width:
|
|
195
|
+
getUnit(this.editor) === 'px'
|
|
196
|
+
? widthPx
|
|
197
|
+
: this.editor.getSizeByUnit(widthPx),
|
|
198
|
+
height:
|
|
199
|
+
getUnit(this.editor) === 'px'
|
|
200
|
+
? heightPx
|
|
201
|
+
: this.editor.getSizeByUnit(heightPx),
|
|
202
|
+
});
|
|
203
|
+
void this.refreshImageTextList(group);
|
|
204
|
+
};
|
|
205
|
+
group.on('modified', group.__imageTextListModified);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async refreshImageTextList(group: ImageTextListGroup) {
|
|
209
|
+
const currentExtension = this.normalizeOptions(group.get('extension') || {});
|
|
210
|
+
const extension = {
|
|
211
|
+
...currentExtension,
|
|
212
|
+
_clipContent:
|
|
213
|
+
currentExtension._clipContent === true ||
|
|
214
|
+
Boolean(group.clipPath),
|
|
215
|
+
};
|
|
216
|
+
const left = group.left;
|
|
217
|
+
const top = group.top;
|
|
218
|
+
const replacement = await this.buildGroup(extension);
|
|
219
|
+
const children = replacement.getObjects();
|
|
220
|
+
|
|
221
|
+
(group as any)._objects = children;
|
|
222
|
+
children.forEach((child) => {
|
|
223
|
+
child.group = group;
|
|
224
|
+
});
|
|
225
|
+
(replacement as any)._objects = [];
|
|
226
|
+
|
|
227
|
+
group.set({
|
|
228
|
+
left,
|
|
229
|
+
top,
|
|
230
|
+
width: replacement.width,
|
|
231
|
+
height: replacement.height,
|
|
232
|
+
scaleX: 1,
|
|
233
|
+
scaleY: 1,
|
|
234
|
+
visible: replacement.visible,
|
|
235
|
+
clipPath: replacement.clipPath,
|
|
236
|
+
objectCaching: Boolean(replacement.clipPath),
|
|
237
|
+
dirty: true,
|
|
238
|
+
});
|
|
239
|
+
group.set('extension', extension);
|
|
240
|
+
this.updateOriginSize(group, extension);
|
|
241
|
+
group.setCoords();
|
|
242
|
+
this.canvas.requestRenderAll();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private normalizeOptions(options: ImageTextListOptions) {
|
|
246
|
+
return {
|
|
247
|
+
...DEFAULT_OPTIONS,
|
|
248
|
+
...options,
|
|
249
|
+
items: Array.isArray(options.items) ? options.items : [],
|
|
250
|
+
} as ImageTextListOptions;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private toPx(value: number | undefined) {
|
|
254
|
+
return convertSingle(Number(value) || 0, getUnit(this.editor));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private getItems(options: ImageTextListOptions) {
|
|
258
|
+
return (
|
|
259
|
+
options._renderItems?.length
|
|
260
|
+
? options._renderItems
|
|
261
|
+
: options.items || []
|
|
262
|
+
).filter((item) => item && (item.src || item.name));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private createText(
|
|
266
|
+
text: string,
|
|
267
|
+
options: ImageTextListOptions,
|
|
268
|
+
width?: number
|
|
269
|
+
) {
|
|
270
|
+
const fontSize = Math.max(1, this.toPx(options.fontSize));
|
|
271
|
+
const charSpacingPx = Math.max(0, this.toPx(options.charSpacing));
|
|
272
|
+
const common = {
|
|
273
|
+
fontFamily: options.fontFamily,
|
|
274
|
+
fontSize,
|
|
275
|
+
fontWeight: options.fontWeight as any,
|
|
276
|
+
fontStyle: options.fontStyle as any,
|
|
277
|
+
underline: Boolean(options.underline),
|
|
278
|
+
linethrough: Boolean(options.linethrough),
|
|
279
|
+
fill: options.color,
|
|
280
|
+
textAlign: options.textAlign,
|
|
281
|
+
lineHeight: Number(options.lineHeight) || 1,
|
|
282
|
+
charSpacing: (charSpacingPx / fontSize) * 1000,
|
|
283
|
+
splitByGrapheme: true,
|
|
284
|
+
selectable: false,
|
|
285
|
+
evented: false,
|
|
286
|
+
objectCaching: false,
|
|
287
|
+
};
|
|
288
|
+
const textObject = width && options.textWrap !== false
|
|
289
|
+
? new fabric.Textbox(text || '', { ...common, width })
|
|
290
|
+
: new fabric.Text(text || '', common);
|
|
291
|
+
textObject.initDimensions();
|
|
292
|
+
textObject.setCoords();
|
|
293
|
+
return textObject;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private loadImage(src: string, size: number) {
|
|
297
|
+
return new Promise<fabric.Image | null>((resolve) => {
|
|
298
|
+
if (!src) return resolve(null);
|
|
299
|
+
fabric.Image.fromURL(
|
|
300
|
+
src,
|
|
301
|
+
(image) => {
|
|
302
|
+
image.set({
|
|
303
|
+
scaleX: size / Math.max(1, image.width || 1),
|
|
304
|
+
scaleY: size / Math.max(1, image.height || 1),
|
|
305
|
+
selectable: false,
|
|
306
|
+
evented: false,
|
|
307
|
+
objectCaching: false,
|
|
308
|
+
});
|
|
309
|
+
resolve(image);
|
|
310
|
+
},
|
|
311
|
+
{ crossOrigin: 'anonymous' }
|
|
312
|
+
);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private async buildGroup(options: ImageTextListOptions) {
|
|
317
|
+
const normalized = this.normalizeOptions(options);
|
|
318
|
+
const items = this.getItems(normalized);
|
|
319
|
+
const width = Math.max(1, this.toPx(normalized.width));
|
|
320
|
+
if (!items.length) {
|
|
321
|
+
return this.createGroup([], width, 1, false);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const layout = normalized.layout || DEFAULT_OPTIONS.layout;
|
|
325
|
+
const showIcon = layout !== 'text-only';
|
|
326
|
+
const showText = layout !== 'icon-only';
|
|
327
|
+
const iconSize = Math.max(1, this.toPx(normalized.iconSize));
|
|
328
|
+
const horizontalGap = Math.max(0, this.toPx(normalized.horizontalGap));
|
|
329
|
+
const verticalGap = Math.max(0, this.toPx(normalized.verticalGap));
|
|
330
|
+
const itemGap = Math.max(0, this.toPx(normalized.itemGap));
|
|
331
|
+
const images = showIcon
|
|
332
|
+
? await Promise.all(items.map((item) => this.loadImage(item.src, iconSize)))
|
|
333
|
+
: items.map(() => null);
|
|
334
|
+
const objects: fabric.Object[] = [];
|
|
335
|
+
let cursorY = 0;
|
|
336
|
+
|
|
337
|
+
if (layout === 'icon-text-split') {
|
|
338
|
+
const iconRows = this.flowRows(
|
|
339
|
+
items.map(() => iconSize),
|
|
340
|
+
width,
|
|
341
|
+
horizontalGap
|
|
342
|
+
);
|
|
343
|
+
iconRows.forEach((row) => {
|
|
344
|
+
row.items.forEach((cell) => {
|
|
345
|
+
const image = images[cell.index];
|
|
346
|
+
if (image) {
|
|
347
|
+
image.set({ left: cell.x, top: cursorY });
|
|
348
|
+
objects.push(image);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
cursorY += iconSize + verticalGap;
|
|
352
|
+
});
|
|
353
|
+
items.forEach((item) => {
|
|
354
|
+
const text = this.createText(item.name || '', normalized, width);
|
|
355
|
+
text.set({ left: 0, top: cursorY });
|
|
356
|
+
objects.push(text);
|
|
357
|
+
cursorY += text.getScaledHeight() + itemGap;
|
|
358
|
+
});
|
|
359
|
+
} else if (layout === 'item-vertical') {
|
|
360
|
+
const textLeft = iconSize + horizontalGap;
|
|
361
|
+
const textWidth = Math.max(1, width - textLeft);
|
|
362
|
+
items.forEach((item, index) => {
|
|
363
|
+
const image = images[index];
|
|
364
|
+
const text = this.createText(
|
|
365
|
+
item.name || '',
|
|
366
|
+
normalized,
|
|
367
|
+
textWidth
|
|
368
|
+
);
|
|
369
|
+
const textHeight = text.getScaledHeight();
|
|
370
|
+
const rowHeight = Math.max(iconSize, textHeight);
|
|
371
|
+
if (image) {
|
|
372
|
+
image.set({
|
|
373
|
+
left: 0,
|
|
374
|
+
top: cursorY + (rowHeight - iconSize) / 2,
|
|
375
|
+
});
|
|
376
|
+
objects.push(image);
|
|
377
|
+
}
|
|
378
|
+
text.set({
|
|
379
|
+
left: textLeft,
|
|
380
|
+
top: cursorY + (rowHeight - textHeight) / 2,
|
|
381
|
+
});
|
|
382
|
+
objects.push(text);
|
|
383
|
+
cursorY += rowHeight + verticalGap;
|
|
384
|
+
});
|
|
385
|
+
} else {
|
|
386
|
+
const textObjects = items.map((item) =>
|
|
387
|
+
showText ? this.createText(item.name || '', normalized) : null
|
|
388
|
+
);
|
|
389
|
+
const itemWidths = items.map((_, index) => {
|
|
390
|
+
const textWidth = textObjects[index]?.getScaledWidth() || 0;
|
|
391
|
+
if (showIcon && showText)
|
|
392
|
+
return iconSize + horizontalGap + textWidth;
|
|
393
|
+
return showIcon ? iconSize : textWidth;
|
|
394
|
+
});
|
|
395
|
+
const rows = this.flowRows(itemWidths, width, horizontalGap);
|
|
396
|
+
rows.forEach((row) => {
|
|
397
|
+
const rowHeight = Math.max(
|
|
398
|
+
showIcon ? iconSize : 0,
|
|
399
|
+
...row.items.map(
|
|
400
|
+
(cell) => textObjects[cell.index]?.getScaledHeight() || 0
|
|
401
|
+
)
|
|
402
|
+
);
|
|
403
|
+
row.items.forEach((cell) => {
|
|
404
|
+
let x = cell.x;
|
|
405
|
+
const image = images[cell.index];
|
|
406
|
+
const text = textObjects[cell.index];
|
|
407
|
+
if (showIcon && image) {
|
|
408
|
+
image.set({
|
|
409
|
+
left: x,
|
|
410
|
+
top: cursorY + (rowHeight - iconSize) / 2,
|
|
411
|
+
});
|
|
412
|
+
objects.push(image);
|
|
413
|
+
x += iconSize + (showText ? horizontalGap : 0);
|
|
414
|
+
}
|
|
415
|
+
if (showText && text) {
|
|
416
|
+
text.set({
|
|
417
|
+
left: x,
|
|
418
|
+
top: cursorY + (rowHeight - text.getScaledHeight()) / 2,
|
|
419
|
+
});
|
|
420
|
+
objects.push(text);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
cursorY += rowHeight + verticalGap;
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const trailingGap =
|
|
428
|
+
layout === 'icon-text-split' ? itemGap : verticalGap;
|
|
429
|
+
const naturalHeight = Math.max(1, cursorY - trailingGap);
|
|
430
|
+
const configuredHeight = Number(normalized.height);
|
|
431
|
+
const height =
|
|
432
|
+
configuredHeight > 0
|
|
433
|
+
? Math.max(1, this.toPx(configuredHeight))
|
|
434
|
+
: naturalHeight;
|
|
435
|
+
return this.createGroup(
|
|
436
|
+
objects,
|
|
437
|
+
width,
|
|
438
|
+
height,
|
|
439
|
+
true,
|
|
440
|
+
normalized._clipContent
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private createGroup(
|
|
445
|
+
objects: fabric.Object[],
|
|
446
|
+
width: number,
|
|
447
|
+
height: number,
|
|
448
|
+
visible: boolean,
|
|
449
|
+
clipContent = false
|
|
450
|
+
) {
|
|
451
|
+
const boundary = new fabric.Rect({
|
|
452
|
+
left: 0,
|
|
453
|
+
top: 0,
|
|
454
|
+
width,
|
|
455
|
+
height,
|
|
456
|
+
fill: 'rgba(0,0,0,0)',
|
|
457
|
+
strokeWidth: 0,
|
|
458
|
+
selectable: false,
|
|
459
|
+
evented: false,
|
|
460
|
+
});
|
|
461
|
+
const group = new fabric.Group([boundary], {
|
|
462
|
+
width,
|
|
463
|
+
height,
|
|
464
|
+
visible,
|
|
465
|
+
// Fabric 5 renders clipPath through the object cache.
|
|
466
|
+
objectCaching: clipContent,
|
|
467
|
+
subTargetCheck: false,
|
|
468
|
+
clipPath: clipContent
|
|
469
|
+
? new fabric.Rect({
|
|
470
|
+
width,
|
|
471
|
+
height,
|
|
472
|
+
originX: 'center',
|
|
473
|
+
originY: 'center',
|
|
474
|
+
})
|
|
475
|
+
: undefined,
|
|
476
|
+
}) as ImageTextListGroup;
|
|
477
|
+
|
|
478
|
+
// Keep the configured boundary fixed. Overflowing content starts at the
|
|
479
|
+
// group's top edge and grows downward without changing the group size.
|
|
480
|
+
objects.forEach((object) => {
|
|
481
|
+
object.set({
|
|
482
|
+
left: (object.left || 0) - width / 2,
|
|
483
|
+
top: (object.top || 0) - height / 2,
|
|
484
|
+
});
|
|
485
|
+
object.group = group;
|
|
486
|
+
});
|
|
487
|
+
(group as any)._objects.push(...objects);
|
|
488
|
+
group.setCoords();
|
|
489
|
+
return group;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private flowRows(widths: number[], maxWidth: number, gap: number) {
|
|
493
|
+
const rows: Array<{
|
|
494
|
+
items: Array<{ index: number; x: number; width: number }>;
|
|
495
|
+
}> = [];
|
|
496
|
+
let row = {
|
|
497
|
+
items: [] as Array<{ index: number; x: number; width: number }>,
|
|
498
|
+
};
|
|
499
|
+
let x = 0;
|
|
500
|
+
widths.forEach((rawWidth, index) => {
|
|
501
|
+
const width = Math.min(Math.max(1, rawWidth), maxWidth);
|
|
502
|
+
if (row.items.length && x + width > maxWidth) {
|
|
503
|
+
rows.push(row);
|
|
504
|
+
row = { items: [] };
|
|
505
|
+
x = 0;
|
|
506
|
+
}
|
|
507
|
+
row.items.push({ index, x, width });
|
|
508
|
+
x += width + gap;
|
|
509
|
+
});
|
|
510
|
+
if (row.items.length) rows.push(row);
|
|
511
|
+
return rows;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
private updateOriginSize(
|
|
515
|
+
group: ImageTextListGroup,
|
|
516
|
+
extension: ImageTextListOptions
|
|
517
|
+
) {
|
|
518
|
+
const unit = getUnit(this.editor);
|
|
519
|
+
const origin = group._originSize || {};
|
|
520
|
+
group._originSize = {
|
|
521
|
+
...origin,
|
|
522
|
+
[unit]: formatOriginValues(
|
|
523
|
+
{
|
|
524
|
+
...(origin[unit] || {}),
|
|
525
|
+
width: extension.width,
|
|
526
|
+
height:
|
|
527
|
+
extension.height ??
|
|
528
|
+
(unit === 'px'
|
|
529
|
+
? group.height
|
|
530
|
+
: this.editor.getSizeByUnit(group.height || 1)),
|
|
531
|
+
},
|
|
532
|
+
(this.editor as any).getPrecision?.()
|
|
533
|
+
),
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
destroy() {}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export default ImageTextListPlugin;
|