fabric-vectr 6.7.12 → 6.7.14
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 +45 -21
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.mjs +45 -21
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +45 -21
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +45 -21
- package/dist/index.node.mjs.map +1 -1
- package/dist/package.json.min.mjs +1 -1
- package/dist/package.json.mjs +1 -1
- package/dist/src/Pattern/Pattern.d.ts.map +1 -1
- package/dist/src/Pattern/Pattern.min.mjs +1 -1
- package/dist/src/Pattern/Pattern.min.mjs.map +1 -1
- package/dist/src/Pattern/Pattern.mjs +5 -1
- package/dist/src/Pattern/Pattern.mjs.map +1 -1
- package/dist/src/canvas/StaticCanvas.d.ts.map +1 -1
- package/dist/src/canvas/StaticCanvas.min.mjs +1 -1
- package/dist/src/canvas/StaticCanvas.min.mjs.map +1 -1
- package/dist/src/canvas/StaticCanvas.mjs +15 -12
- package/dist/src/canvas/StaticCanvas.mjs.map +1 -1
- package/dist/src/shapes/Image.d.ts +2 -2
- package/dist/src/shapes/Image.d.ts.map +1 -1
- package/dist/src/shapes/Image.min.mjs +1 -1
- package/dist/src/shapes/Image.min.mjs.map +1 -1
- package/dist/src/shapes/Image.mjs +4 -2
- package/dist/src/shapes/Image.mjs.map +1 -1
- package/dist/src/shapes/Object/FabricObjectSVGExportMixin.d.ts.map +1 -1
- package/dist/src/shapes/Object/FabricObjectSVGExportMixin.min.mjs +1 -1
- package/dist/src/shapes/Object/FabricObjectSVGExportMixin.min.mjs.map +1 -1
- package/dist/src/shapes/Object/FabricObjectSVGExportMixin.mjs +5 -1
- package/dist/src/shapes/Object/FabricObjectSVGExportMixin.mjs.map +1 -1
- package/dist/src/util/misc/objectEnlive.d.ts +6 -1
- package/dist/src/util/misc/objectEnlive.d.ts.map +1 -1
- package/dist/src/util/misc/objectEnlive.min.mjs +1 -1
- package/dist/src/util/misc/objectEnlive.min.mjs.map +1 -1
- package/dist/src/util/misc/objectEnlive.mjs +16 -5
- package/dist/src/util/misc/objectEnlive.mjs.map +1 -1
- package/dist-extensions/src/Pattern/Pattern.d.ts.map +1 -1
- package/dist-extensions/src/canvas/StaticCanvas.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Image.d.ts +2 -2
- package/dist-extensions/src/shapes/Image.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Object/FabricObjectSVGExportMixin.d.ts.map +1 -1
- package/dist-extensions/src/util/misc/objectEnlive.d.ts +6 -1
- package/dist-extensions/src/util/misc/objectEnlive.d.ts.map +1 -1
- package/package.json +33 -33
- package/src/Pattern/Pattern.spec.ts +63 -0
- package/src/Pattern/Pattern.ts +3 -1
- package/src/canvas/StaticCanvas.spec.ts +23 -0
- package/src/canvas/StaticCanvas.ts +19 -12
- package/src/shapes/Image.spec.ts +21 -2
- package/src/shapes/Image.ts +24 -12
- package/src/shapes/Object/FabricObjectSVGExportMixin.ts +5 -1
- package/src/shapes/Text/TextSVGExportMixin.spec.ts +16 -0
- package/src/util/misc/objectEnlive.spec.ts +49 -2
- package/src/util/misc/objectEnlive.ts +29 -5
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as objectEnlive from '../util/misc/objectEnlive';
|
|
2
|
+
import { Pattern } from './Pattern';
|
|
3
|
+
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
|
|
6
|
+
describe('Pattern', () => {
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
vi.restoreAllMocks();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('enlives from serialized object', async () => {
|
|
12
|
+
const pattern = await Pattern.fromObject({
|
|
13
|
+
type: 'pattern',
|
|
14
|
+
source: '',
|
|
15
|
+
repeat: 'repeat-x',
|
|
16
|
+
offsetX: 12,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(pattern).toBeInstanceOf(Pattern);
|
|
20
|
+
expect(pattern.repeat).toBe('repeat-x');
|
|
21
|
+
expect(pattern.offsetX).toBe(12);
|
|
22
|
+
expect(pattern.sourceToString()).toBe('');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('falls back to an empty source when the pattern image fails to load', async () => {
|
|
26
|
+
const loadImageSpy = vi.spyOn(objectEnlive, 'loadImage');
|
|
27
|
+
const emptyImage = new Image();
|
|
28
|
+
loadImageSpy.mockResolvedValueOnce(emptyImage);
|
|
29
|
+
|
|
30
|
+
const pattern = await Pattern.fromObject({
|
|
31
|
+
type: 'pattern',
|
|
32
|
+
source: 'bad-url',
|
|
33
|
+
repeat: 'repeat',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(pattern).toBeInstanceOf(Pattern);
|
|
37
|
+
expect(pattern.source).toBe(emptyImage);
|
|
38
|
+
expect(loadImageSpy).toHaveBeenCalledWith('bad-url', {
|
|
39
|
+
crossOrigin: undefined,
|
|
40
|
+
fallbackToEmptyImage: true,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('preserves abort errors', async () => {
|
|
45
|
+
const controller = new AbortController();
|
|
46
|
+
const loadImageSpy = vi.spyOn(objectEnlive, 'loadImage');
|
|
47
|
+
const abortError = new Error('aborted');
|
|
48
|
+
|
|
49
|
+
controller.abort();
|
|
50
|
+
loadImageSpy.mockRejectedValueOnce(abortError);
|
|
51
|
+
|
|
52
|
+
await expect(
|
|
53
|
+
Pattern.fromObject(
|
|
54
|
+
{
|
|
55
|
+
type: 'pattern',
|
|
56
|
+
source: 'bad-url',
|
|
57
|
+
repeat: 'repeat',
|
|
58
|
+
},
|
|
59
|
+
{ signal: controller.signal },
|
|
60
|
+
),
|
|
61
|
+
).rejects.toBe(abortError);
|
|
62
|
+
});
|
|
63
|
+
});
|
package/src/Pattern/Pattern.ts
CHANGED
|
@@ -208,9 +208,11 @@ export class Pattern {
|
|
|
208
208
|
}: SerializedPatternOptions,
|
|
209
209
|
options?: Abortable,
|
|
210
210
|
): Promise<Pattern> {
|
|
211
|
+
const { crossOrigin } = otherOptions;
|
|
211
212
|
const img = await loadImage(source, {
|
|
212
213
|
...options,
|
|
213
|
-
crossOrigin
|
|
214
|
+
crossOrigin,
|
|
215
|
+
fallbackToEmptyImage: true,
|
|
214
216
|
});
|
|
215
217
|
return new this({
|
|
216
218
|
...otherOptions,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { StaticCanvas } from './StaticCanvas';
|
|
2
|
+
import { Group } from '../shapes/Group';
|
|
3
|
+
import { Path } from '../shapes/Path';
|
|
4
|
+
import { FabricText } from '../shapes/Text/Text';
|
|
2
5
|
|
|
3
6
|
import { it, expect, describe } from 'vitest';
|
|
4
7
|
|
|
@@ -24,4 +27,24 @@ describe('StaticCanvas', () => {
|
|
|
24
27
|
*/
|
|
25
28
|
expect(dataURL).toMatch(/^data:image\/(webp|png)/);
|
|
26
29
|
});
|
|
30
|
+
|
|
31
|
+
it('exports text on path inside group to svg defs', () => {
|
|
32
|
+
const canvas = new StaticCanvas(undefined, { width: 300, height: 300 });
|
|
33
|
+
const textPath = new FabricText('Text On Path', {
|
|
34
|
+
id: 'group-text-path',
|
|
35
|
+
path: new Path('M 0 0 L 120 0'),
|
|
36
|
+
fontSize: 20,
|
|
37
|
+
});
|
|
38
|
+
const group = new Group([textPath], {
|
|
39
|
+
left: 40,
|
|
40
|
+
top: 60,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
canvas.add(group);
|
|
44
|
+
|
|
45
|
+
const svg = canvas.toSVG();
|
|
46
|
+
|
|
47
|
+
expect(svg).toContain('<textPath href="#TEXTPATH_group-text-path" ');
|
|
48
|
+
expect(svg).toContain('id="TEXTPATH_group-text-path"');
|
|
49
|
+
});
|
|
27
50
|
});
|
|
@@ -1083,22 +1083,28 @@ export class StaticCanvas<
|
|
|
1083
1083
|
*/
|
|
1084
1084
|
_createPathForText() {
|
|
1085
1085
|
const pathMarkups: string[] = [];
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
objects
|
|
1090
|
-
|
|
1091
|
-
|
|
1086
|
+
const objects: FabricObject[] = [];
|
|
1087
|
+
|
|
1088
|
+
this._objects.forEach(function add(object) {
|
|
1089
|
+
objects.push(object);
|
|
1090
|
+
if (isCollection(object)) {
|
|
1091
|
+
object._objects.forEach(add);
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
objects.forEach((instance) => {
|
|
1092
1096
|
if (instance.excludeFromExport) {
|
|
1093
|
-
|
|
1097
|
+
return;
|
|
1094
1098
|
}
|
|
1095
1099
|
|
|
1096
1100
|
if (!isTextObject(instance) || !instance.path) {
|
|
1097
|
-
|
|
1101
|
+
return;
|
|
1098
1102
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1103
|
+
|
|
1104
|
+
const pathId = `TEXTPATH_${(instance as any).id}`;
|
|
1105
|
+
const pathMarkup = instance.path._toSVG();
|
|
1106
|
+
const index = pathMarkup.indexOf('COMMON_PARTS');
|
|
1107
|
+
|
|
1102
1108
|
// 加上id, 和路径偏移
|
|
1103
1109
|
pathMarkup[index] = [
|
|
1104
1110
|
'id="' + pathId + '" ',
|
|
@@ -1109,7 +1115,8 @@ export class StaticCanvas<
|
|
|
1109
1115
|
')" ',
|
|
1110
1116
|
].join('');
|
|
1111
1117
|
pathMarkups.push(pathMarkup.join(''));
|
|
1112
|
-
}
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1113
1120
|
return pathMarkups.join('\n');
|
|
1114
1121
|
}
|
|
1115
1122
|
|
package/src/shapes/Image.spec.ts
CHANGED
|
@@ -2,11 +2,14 @@ import { FabricImage } from './Image';
|
|
|
2
2
|
import { Shadow } from '../Shadow';
|
|
3
3
|
import { Brightness } from '../filters/Brightness';
|
|
4
4
|
import { loadSVGFromString } from '../parser/loadSVGFromString';
|
|
5
|
+
import * as objectEnlive from '../util/misc/objectEnlive';
|
|
5
6
|
|
|
6
7
|
const mockImage = new Image(100, 100);
|
|
7
8
|
|
|
8
|
-
vi.mock('../util/misc/objectEnlive', () => {
|
|
9
|
-
const all = vi.importActual('../util/misc/objectEnlive')
|
|
9
|
+
vi.mock('../util/misc/objectEnlive', async () => {
|
|
10
|
+
const all = await vi.importActual<typeof import('../util/misc/objectEnlive')>(
|
|
11
|
+
'../util/misc/objectEnlive',
|
|
12
|
+
);
|
|
10
13
|
return {
|
|
11
14
|
...all,
|
|
12
15
|
loadImage: vi.fn(async (src) => {
|
|
@@ -28,6 +31,22 @@ vi.mock('../filters/FilterBackend', () => ({
|
|
|
28
31
|
}));
|
|
29
32
|
|
|
30
33
|
describe('FabricImage', () => {
|
|
34
|
+
test('fromObject requests empty-image fallback for deserialization', async () => {
|
|
35
|
+
const image = await FabricImage.fromObject({
|
|
36
|
+
type: 'image',
|
|
37
|
+
src: 'broken-image-url',
|
|
38
|
+
crossOrigin: null,
|
|
39
|
+
cropX: 0,
|
|
40
|
+
cropY: 0,
|
|
41
|
+
} as any);
|
|
42
|
+
|
|
43
|
+
expect(image).toBeInstanceOf(FabricImage);
|
|
44
|
+
expect(objectEnlive.loadImage).toHaveBeenCalledWith('broken-image-url', {
|
|
45
|
+
crossOrigin: null,
|
|
46
|
+
fallbackToEmptyImage: true,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
31
50
|
describe('Svg export', () => {
|
|
32
51
|
test('It exports an svg with styles for an image with stroke', () => {
|
|
33
52
|
const imgElement = new Image(200, 200);
|
package/src/shapes/Image.ts
CHANGED
|
@@ -799,21 +799,33 @@ export class FabricImage<
|
|
|
799
799
|
options?: Abortable,
|
|
800
800
|
) {
|
|
801
801
|
return Promise.all([
|
|
802
|
-
loadImage(src!, {
|
|
802
|
+
loadImage(src!, {
|
|
803
|
+
...options,
|
|
804
|
+
crossOrigin,
|
|
805
|
+
fallbackToEmptyImage: true,
|
|
806
|
+
}),
|
|
803
807
|
f && enlivenObjects<BaseFilter<string>>(f, options),
|
|
804
808
|
// TODO: redundant - handled by enlivenObjectEnlivables
|
|
805
|
-
rf && enlivenObjects<
|
|
809
|
+
rf && enlivenObjects<Resize>([rf], options),
|
|
806
810
|
enlivenObjectEnlivables(object, options),
|
|
807
|
-
]).then(
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
811
|
+
]).then(
|
|
812
|
+
([el, filters = [], resizeFilters = [], hydratedProps = {}]: [
|
|
813
|
+
HTMLImageElement,
|
|
814
|
+
BaseFilter<string, Record<string, any>>[]?,
|
|
815
|
+
Resize[]?,
|
|
816
|
+
Record<string, any>?,
|
|
817
|
+
]) => {
|
|
818
|
+
const [resizeFilter] = resizeFilters;
|
|
819
|
+
return new this(el, {
|
|
820
|
+
...object,
|
|
821
|
+
// TODO: this creates a difference between image creation and restoring from JSON
|
|
822
|
+
src,
|
|
823
|
+
filters,
|
|
824
|
+
resizeFilter,
|
|
825
|
+
...hydratedProps,
|
|
826
|
+
});
|
|
827
|
+
},
|
|
828
|
+
);
|
|
817
829
|
}
|
|
818
830
|
|
|
819
831
|
/**
|
|
@@ -304,7 +304,12 @@ export class FabricObjectSVGExportMixin {
|
|
|
304
304
|
].join('');
|
|
305
305
|
|
|
306
306
|
// James added shadow 放在上面
|
|
307
|
+
// 文本等调用方已经通过 withShadow 挂载 filter 时,避免再额外复制一份可见对象,
|
|
308
|
+
// 但仍然需要输出 filter 定义,否则普通 SVG 会丢失阴影。
|
|
307
309
|
if (shadow) {
|
|
310
|
+
markup.push(shadow.toSVG(this));
|
|
311
|
+
}
|
|
312
|
+
if (shadow && !withShadow) {
|
|
308
313
|
const styleInfoWithShadow = 'style="' + this.getSvgStyles(false) + '" ';
|
|
309
314
|
const commonPiecesWithShadow = [
|
|
310
315
|
styleInfoWithShadow,
|
|
@@ -317,7 +322,6 @@ export class FabricObjectSVGExportMixin {
|
|
|
317
322
|
objectMarkupCopy[index] = commonPiecesWithShadow;
|
|
318
323
|
|
|
319
324
|
markup.push(objectMarkupCopy.join(''));
|
|
320
|
-
markup.push(shadow.toSVG(this));
|
|
321
325
|
}
|
|
322
326
|
// objectMarkup中是导出主对象(如path)的svg,index下标是style,放在commonPieces
|
|
323
327
|
objectMarkup[index] = commonPieces;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FabricText } from './Text';
|
|
2
|
+
import { Shadow } from '../../Shadow';
|
|
2
3
|
|
|
3
4
|
import { describe, expect, it } from 'vitest';
|
|
4
5
|
|
|
@@ -28,4 +29,19 @@ describe('TextSvgExport', () => {
|
|
|
28
29
|
);
|
|
29
30
|
expect(svgStyles.includes('stroke="none"')).toBe(false);
|
|
30
31
|
});
|
|
32
|
+
|
|
33
|
+
it('exports shadowed text without duplicating text markup', () => {
|
|
34
|
+
const myText = new FabricText('text', {
|
|
35
|
+
shadow: new Shadow({
|
|
36
|
+
color: 'rgba(0, 0, 0, 0.4)',
|
|
37
|
+
blur: 6,
|
|
38
|
+
offsetX: 4,
|
|
39
|
+
offsetY: 4,
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
const svgString = myText.toSVG();
|
|
43
|
+
expect((svgString.match(/<text /g) || []).length).toBe(1);
|
|
44
|
+
expect(svgString.includes('filter: url(#SVGID_')).toBe(true);
|
|
45
|
+
expect(svgString.includes('<filter id="SVGID_')).toBe(true);
|
|
46
|
+
});
|
|
31
47
|
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as dom from './dom';
|
|
2
|
+
import { enlivenObjects, loadImage } from './objectEnlive';
|
|
2
3
|
import { Rect, type RectProps } from '../../shapes/Rect';
|
|
3
4
|
import { Shadow } from '../../Shadow';
|
|
4
5
|
import { classRegistry } from '../../ClassRegistry';
|
|
6
|
+
import { FabricError } from '../internals/console';
|
|
5
7
|
|
|
6
|
-
import { describe, expect, it } from 'vitest';
|
|
8
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
7
9
|
|
|
8
10
|
const mockedRectWithCustomProperty = {
|
|
9
11
|
type: 'rect',
|
|
@@ -68,3 +70,48 @@ describe('enlivenObjects', () => {
|
|
|
68
70
|
expect(rect.custom3).toBeInstanceOf(Test);
|
|
69
71
|
});
|
|
70
72
|
});
|
|
73
|
+
|
|
74
|
+
const createMockImage = () => {
|
|
75
|
+
let currentSrc = '';
|
|
76
|
+
return {
|
|
77
|
+
onload: null as null | (() => void),
|
|
78
|
+
onerror: null as null | (() => void),
|
|
79
|
+
crossOrigin: null as string | null,
|
|
80
|
+
complete: false,
|
|
81
|
+
naturalWidth: 0,
|
|
82
|
+
naturalHeight: 0,
|
|
83
|
+
get src() {
|
|
84
|
+
return currentSrc;
|
|
85
|
+
},
|
|
86
|
+
set src(value: string) {
|
|
87
|
+
currentSrc = value;
|
|
88
|
+
if (value === 'bad-url') {
|
|
89
|
+
queueMicrotask(() => this.onerror?.());
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
} as unknown as HTMLImageElement;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
describe('loadImage', () => {
|
|
96
|
+
afterEach(() => {
|
|
97
|
+
vi.restoreAllMocks();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('rejects loading errors by default', async () => {
|
|
101
|
+
vi.spyOn(dom, 'createImage').mockReturnValue(createMockImage());
|
|
102
|
+
|
|
103
|
+
await expect(loadImage('bad-url')).rejects.toEqual(
|
|
104
|
+
new FabricError('Error loading bad-url'),
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('falls back to an empty image when requested', async () => {
|
|
109
|
+
const mockImage = createMockImage();
|
|
110
|
+
vi.spyOn(dom, 'createImage').mockReturnValue(mockImage);
|
|
111
|
+
|
|
112
|
+
const image = await loadImage('bad-url', { fallbackToEmptyImage: true });
|
|
113
|
+
|
|
114
|
+
expect(image).toBe(mockImage);
|
|
115
|
+
expect(image.src).toBe('');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -10,7 +10,7 @@ import { createImage } from './dom';
|
|
|
10
10
|
import { classRegistry } from '../../ClassRegistry';
|
|
11
11
|
import type { BaseFilter } from '../../filters/BaseFilter';
|
|
12
12
|
import type { FabricObject as BaseFabricObject } from '../../shapes/Object/Object';
|
|
13
|
-
import { FabricError, SignalAbortedError } from '../internals/console';
|
|
13
|
+
import { FabricError, SignalAbortedError, log } from '../internals/console';
|
|
14
14
|
import type { Shadow } from '../../Shadow';
|
|
15
15
|
|
|
16
16
|
export type LoadImageOptions = Abortable & {
|
|
@@ -18,6 +18,12 @@ export type LoadImageOptions = Abortable & {
|
|
|
18
18
|
* cors value for the image loading, default to anonymous
|
|
19
19
|
*/
|
|
20
20
|
crossOrigin?: TCrossOrigin;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolve with an empty image instead of rejecting when the image fails to load.
|
|
24
|
+
* Useful for deserialization flows where one bad asset should not fail the entire document.
|
|
25
|
+
*/
|
|
26
|
+
fallbackToEmptyImage?: boolean;
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
/**
|
|
@@ -28,7 +34,11 @@ export type LoadImageOptions = Abortable & {
|
|
|
28
34
|
*/
|
|
29
35
|
export const loadImage = (
|
|
30
36
|
url: string,
|
|
31
|
-
{
|
|
37
|
+
{
|
|
38
|
+
signal,
|
|
39
|
+
crossOrigin = null,
|
|
40
|
+
fallbackToEmptyImage = false,
|
|
41
|
+
}: LoadImageOptions = {},
|
|
32
42
|
) =>
|
|
33
43
|
new Promise<HTMLImageElement>(function (resolve, reject) {
|
|
34
44
|
if (signal && signal.aborted) {
|
|
@@ -43,9 +53,12 @@ export const loadImage = (
|
|
|
43
53
|
};
|
|
44
54
|
signal.addEventListener('abort', abort, { once: true });
|
|
45
55
|
}
|
|
46
|
-
const
|
|
56
|
+
const cleanup = function () {
|
|
47
57
|
img.onload = img.onerror = null;
|
|
48
58
|
abort && signal?.removeEventListener('abort', abort);
|
|
59
|
+
};
|
|
60
|
+
const done = function () {
|
|
61
|
+
cleanup();
|
|
49
62
|
resolve(img);
|
|
50
63
|
};
|
|
51
64
|
if (!url) {
|
|
@@ -54,8 +67,19 @@ export const loadImage = (
|
|
|
54
67
|
}
|
|
55
68
|
img.onload = done;
|
|
56
69
|
img.onerror = function () {
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
const failedSrc = img.src;
|
|
71
|
+
cleanup();
|
|
72
|
+
if (fallbackToEmptyImage) {
|
|
73
|
+
log(
|
|
74
|
+
'warn',
|
|
75
|
+
'Image failed to load, continuing with an empty image source',
|
|
76
|
+
failedSrc,
|
|
77
|
+
);
|
|
78
|
+
img.src = '';
|
|
79
|
+
resolve(img);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
reject(new FabricError(`Error loading ${failedSrc}`));
|
|
59
83
|
};
|
|
60
84
|
crossOrigin && (img.crossOrigin = crossOrigin);
|
|
61
85
|
img.src = url;
|