fabricjs-document-engine 1.1.0 → 1.3.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.
Files changed (146) hide show
  1. package/README.md +70 -1
  2. package/README.zh-CN.md +71 -1
  3. package/dist/assets/asset-pipeline.cjs +10 -8
  4. package/dist/assets/asset-pipeline.d.cts +1 -1
  5. package/dist/assets/asset-pipeline.d.ts +1 -1
  6. package/dist/assets/asset-pipeline.js +10 -8
  7. package/dist/assets/image-check.cjs +4 -3
  8. package/dist/assets/image-check.d.cts +1 -1
  9. package/dist/assets/image-check.d.ts +1 -1
  10. package/dist/assets/image-check.js +4 -3
  11. package/dist/commands/clipboard.cjs +5 -0
  12. package/dist/commands/clipboard.js +5 -0
  13. package/dist/commands/text-edit.cjs +223 -0
  14. package/dist/commands/text-edit.d.cts +23 -0
  15. package/dist/commands/text-edit.d.ts +23 -0
  16. package/dist/commands/text-edit.js +218 -0
  17. package/dist/document/document-format.d.cts +15 -0
  18. package/dist/document/document-format.d.ts +15 -0
  19. package/dist/document/validate-document.cjs +8 -0
  20. package/dist/document/validate-document.js +8 -0
  21. package/dist/engine/create-document-engine.cjs +115 -21
  22. package/dist/engine/create-document-engine.d.cts +24 -6
  23. package/dist/engine/create-document-engine.d.ts +24 -6
  24. package/dist/engine/create-document-engine.js +116 -22
  25. package/dist/engine/errors.d.cts +1 -1
  26. package/dist/engine/errors.d.ts +1 -1
  27. package/dist/export/export-options.cjs +3 -0
  28. package/dist/export/export-options.d.cts +6 -0
  29. package/dist/export/export-options.d.ts +6 -0
  30. package/dist/export/export-options.js +3 -0
  31. package/dist/export/preflight-export.d.cts +1 -1
  32. package/dist/export/preflight-export.d.ts +1 -1
  33. package/dist/export/render-export.cjs +42 -4
  34. package/dist/export/render-export.js +43 -5
  35. package/dist/export/svg/clip-masks.cjs +39 -0
  36. package/dist/export/svg/clip-masks.js +37 -0
  37. package/dist/export/svg/embed-assets.cjs +1 -0
  38. package/dist/export/svg/embed-assets.js +1 -0
  39. package/dist/export/svg/raster-object.cjs +82 -0
  40. package/dist/export/svg/raster-object.js +81 -0
  41. package/dist/{pdf → export/svg}/text-decorations.cjs +3 -3
  42. package/dist/{pdf → export/svg}/text-decorations.js +3 -3
  43. package/dist/export/svg/text-on-path.cjs +0 -10
  44. package/dist/export/svg/text-on-path.js +1 -10
  45. package/dist/fabric/fabric-adapter.cjs +5 -3
  46. package/dist/fabric/fabric-adapter.js +5 -3
  47. package/dist/fabric/object-registry.cjs +7 -1
  48. package/dist/fabric/object-registry.js +7 -1
  49. package/dist/fabric/page-state.cjs +69 -0
  50. package/dist/fabric/page-state.js +64 -0
  51. package/dist/filters/filter-worker.cjs +281 -0
  52. package/dist/filters/filter-worker.d.cts +26 -0
  53. package/dist/filters/filter-worker.d.ts +26 -0
  54. package/dist/filters/filter-worker.js +281 -0
  55. package/dist/filters/pipeline.cjs +91 -0
  56. package/dist/filters/pipeline.d.cts +35 -0
  57. package/dist/filters/pipeline.d.ts +35 -0
  58. package/dist/filters/pipeline.js +87 -0
  59. package/dist/filters/worker-entry.cjs +78 -0
  60. package/dist/filters/worker-entry.d.cts +29 -0
  61. package/dist/filters/worker-entry.d.ts +29 -0
  62. package/dist/filters/worker-entry.js +78 -0
  63. package/dist/filters.cjs +9 -0
  64. package/dist/filters.d.cts +3 -0
  65. package/dist/filters.d.ts +3 -0
  66. package/dist/filters.js +3 -0
  67. package/dist/history/apply-state.cjs +19 -2
  68. package/dist/history/apply-state.js +19 -2
  69. package/dist/history/create-history.cjs +53 -13
  70. package/dist/history/create-history.d.cts +8 -0
  71. package/dist/history/create-history.d.ts +8 -0
  72. package/dist/history/create-history.js +53 -13
  73. package/dist/history/snapshot.cjs +10 -1
  74. package/dist/history/snapshot.js +10 -2
  75. package/dist/import/svg-import.cjs +102 -0
  76. package/dist/import/svg-import.d.cts +1 -0
  77. package/dist/import/svg-import.d.ts +1 -0
  78. package/dist/import/svg-import.js +103 -1
  79. package/dist/index.cjs +7 -0
  80. package/dist/index.d.cts +4 -3
  81. package/dist/index.d.ts +4 -3
  82. package/dist/index.js +2 -1
  83. package/dist/migrations/migrate-document.cjs +2 -1
  84. package/dist/migrations/migrate-document.js +2 -1
  85. package/dist/pdf/export-pdf.cjs +8 -67
  86. package/dist/pdf/export-pdf.js +8 -67
  87. package/dist/performance/batch.cjs +31 -0
  88. package/dist/performance/batch.d.cts +4 -0
  89. package/dist/performance/batch.d.ts +4 -0
  90. package/dist/performance/batch.js +31 -0
  91. package/dist/performance/dirty-regions.cjs +378 -0
  92. package/dist/performance/dirty-regions.d.cts +23 -0
  93. package/dist/performance/dirty-regions.d.ts +23 -0
  94. package/dist/performance/dirty-regions.js +378 -0
  95. package/dist/performance/monitor.cjs +60 -0
  96. package/dist/performance/monitor.d.cts +20 -0
  97. package/dist/performance/monitor.d.ts +20 -0
  98. package/dist/performance/monitor.js +60 -0
  99. package/dist/performance/spatial-index.cjs +78 -0
  100. package/dist/performance/spatial-index.d.cts +21 -0
  101. package/dist/performance/spatial-index.d.ts +21 -0
  102. package/dist/performance/spatial-index.js +78 -0
  103. package/dist/performance.cjs +9 -0
  104. package/dist/performance.d.cts +5 -0
  105. package/dist/performance.d.ts +5 -0
  106. package/dist/performance.js +5 -0
  107. package/dist/recovery/recovery-controller.cjs +148 -29
  108. package/dist/recovery/recovery-controller.d.cts +8 -0
  109. package/dist/recovery/recovery-controller.d.ts +8 -0
  110. package/dist/recovery/recovery-controller.js +148 -29
  111. package/dist/security/content-limits.cjs +43 -3
  112. package/dist/security/content-limits.d.cts +8 -0
  113. package/dist/security/content-limits.d.ts +8 -0
  114. package/dist/security/content-limits.js +39 -4
  115. package/dist/text/bounded-textbox.cjs +306 -0
  116. package/dist/text/bounded-textbox.d.cts +53 -0
  117. package/dist/text/bounded-textbox.d.ts +53 -0
  118. package/dist/text/bounded-textbox.js +306 -0
  119. package/dist/text/mobile-input.cjs +146 -0
  120. package/dist/text/mobile-input.d.cts +19 -0
  121. package/dist/text/mobile-input.d.ts +19 -0
  122. package/dist/text/mobile-input.js +145 -0
  123. package/dist/text/register.cjs +17 -0
  124. package/dist/text/register.d.cts +5 -0
  125. package/dist/text/register.d.ts +5 -0
  126. package/dist/text/register.js +16 -0
  127. package/dist/text/shaping.cjs +125 -0
  128. package/dist/text/shaping.d.cts +28 -0
  129. package/dist/text/shaping.d.ts +28 -0
  130. package/dist/text/shaping.js +121 -0
  131. package/dist/text/vertical-orientation.cjs +192 -0
  132. package/dist/text/vertical-orientation.d.cts +10 -0
  133. package/dist/text/vertical-orientation.d.ts +10 -0
  134. package/dist/text/vertical-orientation.js +190 -0
  135. package/dist/text/vertical-text.cjs +336 -0
  136. package/dist/text/vertical-text.d.cts +54 -0
  137. package/dist/text/vertical-text.d.ts +54 -0
  138. package/dist/text/vertical-text.js +336 -0
  139. package/dist/text.cjs +21 -0
  140. package/dist/text.d.cts +7 -0
  141. package/dist/text.d.ts +7 -0
  142. package/dist/text.js +7 -0
  143. package/dist/util/concurrency.cjs +16 -3
  144. package/dist/util/concurrency.js +16 -3
  145. package/package.json +31 -1
  146. package/schema/document-v1.schema.json +5 -1
@@ -0,0 +1,223 @@
1
+ //#region src/commands/text-edit.ts
2
+ function sameStyle(a, b) {
3
+ if (a === b) return true;
4
+ const keys = Object.keys(a);
5
+ if (keys.length !== Object.keys(b).length) return false;
6
+ return keys.every((key) => a[key] === b[key]);
7
+ }
8
+ function hasStyle(style) {
9
+ return style !== void 0 && style !== null && Object.keys(style).length > 0;
10
+ }
11
+ function readStyleRuns(graphemes, styles) {
12
+ const runs = [];
13
+ let line = 0;
14
+ let char = 0;
15
+ for (let index = 0; index < graphemes.length; index += 1) {
16
+ if (graphemes[index] === "\n") {
17
+ line += 1;
18
+ char = 0;
19
+ continue;
20
+ }
21
+ const style = styles?.[line]?.[char];
22
+ char += 1;
23
+ if (!hasStyle(style)) continue;
24
+ const last = runs[runs.length - 1];
25
+ if (last && last.end === index && sameStyle(last.style, style)) last.end = index + 1;
26
+ else runs.push({
27
+ start: index,
28
+ end: index + 1,
29
+ style
30
+ });
31
+ }
32
+ return runs;
33
+ }
34
+ function writeStyleRuns(graphemes, runs) {
35
+ const styles = {};
36
+ let line = 0;
37
+ let char = 0;
38
+ let next = 0;
39
+ for (let index = 0; index < graphemes.length; index += 1) {
40
+ var _line;
41
+ if (graphemes[index] === "\n") {
42
+ line += 1;
43
+ char = 0;
44
+ continue;
45
+ }
46
+ while (next < runs.length && runs[next].end <= index) next += 1;
47
+ const run = runs[next];
48
+ if (run && run.start <= index) (styles[_line = line] ?? (styles[_line] = {}))[char] = { ...run.style };
49
+ char += 1;
50
+ }
51
+ return styles;
52
+ }
53
+ function shiftStyleRuns(runs, start, end, insertedLength, style) {
54
+ const delta = insertedLength - (end - start);
55
+ const shifted = [];
56
+ let inherited;
57
+ for (const run of runs) if (run.end <= start) {
58
+ const copy = { ...run };
59
+ if (copy.end === start) inherited = copy;
60
+ shifted.push(copy);
61
+ } else if (run.start >= end) shifted.push({
62
+ start: run.start + delta,
63
+ end: run.end + delta,
64
+ style: run.style
65
+ });
66
+ else {
67
+ if (run.start < start) {
68
+ inherited = {
69
+ start: run.start,
70
+ end: start,
71
+ style: run.style
72
+ };
73
+ shifted.push(inherited);
74
+ }
75
+ if (run.end > end) shifted.push({
76
+ start: end + delta,
77
+ end: run.end + delta,
78
+ style: run.style
79
+ });
80
+ }
81
+ if (insertedLength === 0) return shifted;
82
+ if (style === void 0) {
83
+ if (inherited) inherited.end += insertedLength;
84
+ return shifted;
85
+ }
86
+ if (!hasStyle(style)) return shifted;
87
+ const inserted = {
88
+ start,
89
+ end: start + insertedLength,
90
+ style
91
+ };
92
+ const at = shifted.findIndex((run) => run.start >= start);
93
+ if (at === -1) shifted.push(inserted);
94
+ else shifted.splice(at, 0, inserted);
95
+ return shifted;
96
+ }
97
+ function clamp(value, low, high) {
98
+ return Math.min(Math.max(value, low), high);
99
+ }
100
+ function moveIndex(index, start, end, insertedLength) {
101
+ if (index <= start) return index;
102
+ if (index >= end) return index + insertedLength - (end - start);
103
+ return start + insertedLength;
104
+ }
105
+ function replaceTextRange(target, start, end, insert, style) {
106
+ const text = target;
107
+ const graphemes = text._text ?? text.graphemeSplit(text.text);
108
+ const from = clamp(Math.floor(start), 0, graphemes.length);
109
+ const to = clamp(Math.floor(end), from, graphemes.length);
110
+ const inserted = insert ? text.graphemeSplit(insert) : [];
111
+ const runs = shiftStyleRuns(readStyleRuns(graphemes, text.styles), from, to, inserted.length, style);
112
+ const next = [
113
+ ...graphemes.slice(0, from),
114
+ ...inserted,
115
+ ...graphemes.slice(to)
116
+ ];
117
+ const selectionStart = moveIndex(text.selectionStart ?? 0, from, to, inserted.length);
118
+ const selectionEnd = moveIndex(text.selectionEnd ?? 0, from, to, inserted.length);
119
+ applyTextAndStyles(text, next, runs);
120
+ text.selectionStart = selectionStart;
121
+ text.selectionEnd = Math.max(selectionStart, selectionEnd);
122
+ announceTextChange(text);
123
+ }
124
+ function setTextRangeStyle(target, start, end, style) {
125
+ const text = target;
126
+ const graphemes = text._text ?? text.graphemeSplit(text.text);
127
+ const from = clamp(Math.floor(start), 0, graphemes.length);
128
+ const to = clamp(Math.floor(end), from, graphemes.length);
129
+ if (from === to) return;
130
+ const runs = readStyleRuns(graphemes, text.styles);
131
+ const result = [];
132
+ let cursor = from;
133
+ for (const run of runs) {
134
+ if (run.end <= from || run.start >= to) {
135
+ if (run.start >= to && cursor < to) {
136
+ result.push({
137
+ start: cursor,
138
+ end: to,
139
+ style: { ...style }
140
+ });
141
+ cursor = to;
142
+ }
143
+ result.push(run);
144
+ continue;
145
+ }
146
+ if (run.start < from) result.push({
147
+ start: run.start,
148
+ end: from,
149
+ style: run.style
150
+ });
151
+ const overlapStart = Math.max(run.start, from);
152
+ if (cursor < overlapStart) result.push({
153
+ start: cursor,
154
+ end: overlapStart,
155
+ style: { ...style }
156
+ });
157
+ const overlapEnd = Math.min(run.end, to);
158
+ result.push({
159
+ start: overlapStart,
160
+ end: overlapEnd,
161
+ style: {
162
+ ...run.style,
163
+ ...style
164
+ }
165
+ });
166
+ cursor = overlapEnd;
167
+ if (run.end > to) result.push({
168
+ start: to,
169
+ end: run.end,
170
+ style: run.style
171
+ });
172
+ }
173
+ if (cursor < to) result.push({
174
+ start: cursor,
175
+ end: to,
176
+ style: { ...style }
177
+ });
178
+ applyTextAndStyles(text, graphemes, result);
179
+ announceTextChange(text);
180
+ }
181
+ function applyTextAndStyles(text, graphemes, runs) {
182
+ const value = graphemes.join("");
183
+ text.styles = writeStyleRuns(graphemes, runs);
184
+ if (text.text !== value) text.set("text", value);
185
+ else text.initDimensions();
186
+ text.set("dirty", true);
187
+ text.setCoords();
188
+ }
189
+ function announceTextChange(text) {
190
+ if (text.isEditing && text.hiddenTextarea && !text.inCompositionMode) {
191
+ text.hiddenTextarea.value = text.text;
192
+ text._updateTextarea?.();
193
+ }
194
+ text.fire("changed");
195
+ text.canvas?.fire("text:changed", { target: text });
196
+ text.canvas?.requestRenderAll();
197
+ }
198
+ function createTextCommands(engine) {
199
+ return {
200
+ insertText(target, index, text, style) {
201
+ if (!text) return;
202
+ engine.transaction("Insert text", () => replaceTextRange(target, index, index, text, style));
203
+ },
204
+ deleteText(target, start, end) {
205
+ if (end <= start) return;
206
+ engine.transaction("Delete text", () => replaceTextRange(target, start, end, ""));
207
+ },
208
+ replaceText(target, start, end, text, style) {
209
+ engine.transaction("Replace text", () => replaceTextRange(target, start, end, text, style));
210
+ },
211
+ setTextStyle(target, start, end, style) {
212
+ if (end <= start) return;
213
+ engine.transaction("Style text", () => setTextRangeStyle(target, start, end, style));
214
+ }
215
+ };
216
+ }
217
+ //#endregion
218
+ exports.createTextCommands = createTextCommands;
219
+ exports.readStyleRuns = readStyleRuns;
220
+ exports.replaceTextRange = replaceTextRange;
221
+ exports.setTextRangeStyle = setTextRangeStyle;
222
+ exports.shiftStyleRuns = shiftStyleRuns;
223
+ exports.writeStyleRuns = writeStyleRuns;
@@ -0,0 +1,23 @@
1
+ import { DocumentEngine } from "../engine/create-document-engine.cjs";
2
+ import { IText, TextStyleDeclaration, Textbox } from "fabric";
3
+ //#region src/commands/text-edit.d.ts
4
+ export type EditableTextObject = IText | Textbox;
5
+ export interface StyleRun {
6
+ start: number;
7
+ end: number;
8
+ style: TextStyleDeclaration;
9
+ }
10
+ export type InsertedStyle = TextStyleDeclaration | null | undefined;
11
+ export declare function readStyleRuns(graphemes: readonly string[], styles: IText["styles"]): StyleRun[];
12
+ export declare function writeStyleRuns(graphemes: readonly string[], runs: readonly StyleRun[]): IText["styles"];
13
+ export declare function shiftStyleRuns(runs: readonly StyleRun[], start: number, end: number, insertedLength: number, style?: InsertedStyle): StyleRun[];
14
+ export declare function replaceTextRange(target: EditableTextObject, start: number, end: number, insert: string, style?: InsertedStyle): void;
15
+ export declare function setTextRangeStyle(target: EditableTextObject, start: number, end: number, style: TextStyleDeclaration): void;
16
+ export interface TextCommands {
17
+ insertText(target: EditableTextObject, index: number, text: string, style?: InsertedStyle): void;
18
+ deleteText(target: EditableTextObject, start: number, end: number): void;
19
+ replaceText(target: EditableTextObject, start: number, end: number, text: string, style?: InsertedStyle): void;
20
+ setTextStyle(target: EditableTextObject, start: number, end: number, style: TextStyleDeclaration): void;
21
+ }
22
+ export declare function createTextCommands(engine: DocumentEngine): TextCommands;
23
+ //#endregion
@@ -0,0 +1,23 @@
1
+ import { DocumentEngine } from "../engine/create-document-engine.js";
2
+ import { IText, TextStyleDeclaration, Textbox } from "fabric";
3
+ //#region src/commands/text-edit.d.ts
4
+ export type EditableTextObject = IText | Textbox;
5
+ export interface StyleRun {
6
+ start: number;
7
+ end: number;
8
+ style: TextStyleDeclaration;
9
+ }
10
+ export type InsertedStyle = TextStyleDeclaration | null | undefined;
11
+ export declare function readStyleRuns(graphemes: readonly string[], styles: IText["styles"]): StyleRun[];
12
+ export declare function writeStyleRuns(graphemes: readonly string[], runs: readonly StyleRun[]): IText["styles"];
13
+ export declare function shiftStyleRuns(runs: readonly StyleRun[], start: number, end: number, insertedLength: number, style?: InsertedStyle): StyleRun[];
14
+ export declare function replaceTextRange(target: EditableTextObject, start: number, end: number, insert: string, style?: InsertedStyle): void;
15
+ export declare function setTextRangeStyle(target: EditableTextObject, start: number, end: number, style: TextStyleDeclaration): void;
16
+ export interface TextCommands {
17
+ insertText(target: EditableTextObject, index: number, text: string, style?: InsertedStyle): void;
18
+ deleteText(target: EditableTextObject, start: number, end: number): void;
19
+ replaceText(target: EditableTextObject, start: number, end: number, text: string, style?: InsertedStyle): void;
20
+ setTextStyle(target: EditableTextObject, start: number, end: number, style: TextStyleDeclaration): void;
21
+ }
22
+ export declare function createTextCommands(engine: DocumentEngine): TextCommands;
23
+ //#endregion
@@ -0,0 +1,218 @@
1
+ //#region src/commands/text-edit.ts
2
+ function sameStyle(a, b) {
3
+ if (a === b) return true;
4
+ const keys = Object.keys(a);
5
+ if (keys.length !== Object.keys(b).length) return false;
6
+ return keys.every((key) => a[key] === b[key]);
7
+ }
8
+ function hasStyle(style) {
9
+ return style !== void 0 && style !== null && Object.keys(style).length > 0;
10
+ }
11
+ function readStyleRuns(graphemes, styles) {
12
+ const runs = [];
13
+ let line = 0;
14
+ let char = 0;
15
+ for (let index = 0; index < graphemes.length; index += 1) {
16
+ if (graphemes[index] === "\n") {
17
+ line += 1;
18
+ char = 0;
19
+ continue;
20
+ }
21
+ const style = styles?.[line]?.[char];
22
+ char += 1;
23
+ if (!hasStyle(style)) continue;
24
+ const last = runs[runs.length - 1];
25
+ if (last && last.end === index && sameStyle(last.style, style)) last.end = index + 1;
26
+ else runs.push({
27
+ start: index,
28
+ end: index + 1,
29
+ style
30
+ });
31
+ }
32
+ return runs;
33
+ }
34
+ function writeStyleRuns(graphemes, runs) {
35
+ const styles = {};
36
+ let line = 0;
37
+ let char = 0;
38
+ let next = 0;
39
+ for (let index = 0; index < graphemes.length; index += 1) {
40
+ var _line;
41
+ if (graphemes[index] === "\n") {
42
+ line += 1;
43
+ char = 0;
44
+ continue;
45
+ }
46
+ while (next < runs.length && runs[next].end <= index) next += 1;
47
+ const run = runs[next];
48
+ if (run && run.start <= index) (styles[_line = line] ?? (styles[_line] = {}))[char] = { ...run.style };
49
+ char += 1;
50
+ }
51
+ return styles;
52
+ }
53
+ function shiftStyleRuns(runs, start, end, insertedLength, style) {
54
+ const delta = insertedLength - (end - start);
55
+ const shifted = [];
56
+ let inherited;
57
+ for (const run of runs) if (run.end <= start) {
58
+ const copy = { ...run };
59
+ if (copy.end === start) inherited = copy;
60
+ shifted.push(copy);
61
+ } else if (run.start >= end) shifted.push({
62
+ start: run.start + delta,
63
+ end: run.end + delta,
64
+ style: run.style
65
+ });
66
+ else {
67
+ if (run.start < start) {
68
+ inherited = {
69
+ start: run.start,
70
+ end: start,
71
+ style: run.style
72
+ };
73
+ shifted.push(inherited);
74
+ }
75
+ if (run.end > end) shifted.push({
76
+ start: end + delta,
77
+ end: run.end + delta,
78
+ style: run.style
79
+ });
80
+ }
81
+ if (insertedLength === 0) return shifted;
82
+ if (style === void 0) {
83
+ if (inherited) inherited.end += insertedLength;
84
+ return shifted;
85
+ }
86
+ if (!hasStyle(style)) return shifted;
87
+ const inserted = {
88
+ start,
89
+ end: start + insertedLength,
90
+ style
91
+ };
92
+ const at = shifted.findIndex((run) => run.start >= start);
93
+ if (at === -1) shifted.push(inserted);
94
+ else shifted.splice(at, 0, inserted);
95
+ return shifted;
96
+ }
97
+ function clamp(value, low, high) {
98
+ return Math.min(Math.max(value, low), high);
99
+ }
100
+ function moveIndex(index, start, end, insertedLength) {
101
+ if (index <= start) return index;
102
+ if (index >= end) return index + insertedLength - (end - start);
103
+ return start + insertedLength;
104
+ }
105
+ function replaceTextRange(target, start, end, insert, style) {
106
+ const text = target;
107
+ const graphemes = text._text ?? text.graphemeSplit(text.text);
108
+ const from = clamp(Math.floor(start), 0, graphemes.length);
109
+ const to = clamp(Math.floor(end), from, graphemes.length);
110
+ const inserted = insert ? text.graphemeSplit(insert) : [];
111
+ const runs = shiftStyleRuns(readStyleRuns(graphemes, text.styles), from, to, inserted.length, style);
112
+ const next = [
113
+ ...graphemes.slice(0, from),
114
+ ...inserted,
115
+ ...graphemes.slice(to)
116
+ ];
117
+ const selectionStart = moveIndex(text.selectionStart ?? 0, from, to, inserted.length);
118
+ const selectionEnd = moveIndex(text.selectionEnd ?? 0, from, to, inserted.length);
119
+ applyTextAndStyles(text, next, runs);
120
+ text.selectionStart = selectionStart;
121
+ text.selectionEnd = Math.max(selectionStart, selectionEnd);
122
+ announceTextChange(text);
123
+ }
124
+ function setTextRangeStyle(target, start, end, style) {
125
+ const text = target;
126
+ const graphemes = text._text ?? text.graphemeSplit(text.text);
127
+ const from = clamp(Math.floor(start), 0, graphemes.length);
128
+ const to = clamp(Math.floor(end), from, graphemes.length);
129
+ if (from === to) return;
130
+ const runs = readStyleRuns(graphemes, text.styles);
131
+ const result = [];
132
+ let cursor = from;
133
+ for (const run of runs) {
134
+ if (run.end <= from || run.start >= to) {
135
+ if (run.start >= to && cursor < to) {
136
+ result.push({
137
+ start: cursor,
138
+ end: to,
139
+ style: { ...style }
140
+ });
141
+ cursor = to;
142
+ }
143
+ result.push(run);
144
+ continue;
145
+ }
146
+ if (run.start < from) result.push({
147
+ start: run.start,
148
+ end: from,
149
+ style: run.style
150
+ });
151
+ const overlapStart = Math.max(run.start, from);
152
+ if (cursor < overlapStart) result.push({
153
+ start: cursor,
154
+ end: overlapStart,
155
+ style: { ...style }
156
+ });
157
+ const overlapEnd = Math.min(run.end, to);
158
+ result.push({
159
+ start: overlapStart,
160
+ end: overlapEnd,
161
+ style: {
162
+ ...run.style,
163
+ ...style
164
+ }
165
+ });
166
+ cursor = overlapEnd;
167
+ if (run.end > to) result.push({
168
+ start: to,
169
+ end: run.end,
170
+ style: run.style
171
+ });
172
+ }
173
+ if (cursor < to) result.push({
174
+ start: cursor,
175
+ end: to,
176
+ style: { ...style }
177
+ });
178
+ applyTextAndStyles(text, graphemes, result);
179
+ announceTextChange(text);
180
+ }
181
+ function applyTextAndStyles(text, graphemes, runs) {
182
+ const value = graphemes.join("");
183
+ text.styles = writeStyleRuns(graphemes, runs);
184
+ if (text.text !== value) text.set("text", value);
185
+ else text.initDimensions();
186
+ text.set("dirty", true);
187
+ text.setCoords();
188
+ }
189
+ function announceTextChange(text) {
190
+ if (text.isEditing && text.hiddenTextarea && !text.inCompositionMode) {
191
+ text.hiddenTextarea.value = text.text;
192
+ text._updateTextarea?.();
193
+ }
194
+ text.fire("changed");
195
+ text.canvas?.fire("text:changed", { target: text });
196
+ text.canvas?.requestRenderAll();
197
+ }
198
+ function createTextCommands(engine) {
199
+ return {
200
+ insertText(target, index, text, style) {
201
+ if (!text) return;
202
+ engine.transaction("Insert text", () => replaceTextRange(target, index, index, text, style));
203
+ },
204
+ deleteText(target, start, end) {
205
+ if (end <= start) return;
206
+ engine.transaction("Delete text", () => replaceTextRange(target, start, end, ""));
207
+ },
208
+ replaceText(target, start, end, text, style) {
209
+ engine.transaction("Replace text", () => replaceTextRange(target, start, end, text, style));
210
+ },
211
+ setTextStyle(target, start, end, style) {
212
+ if (end <= start) return;
213
+ engine.transaction("Style text", () => setTextRangeStyle(target, start, end, style));
214
+ }
215
+ };
216
+ }
217
+ //#endregion
218
+ export { createTextCommands, readStyleRuns, replaceTextRange, setTextRangeStyle, shiftStyleRuns, writeStyleRuns };
@@ -11,7 +11,16 @@ export interface SerializedFabricObject {
11
11
  export interface DocumentCanvas {
12
12
  width: number;
13
13
  height: number;
14
+ /** A color, gradient or pattern behind everything. */
14
15
  background?: unknown;
16
+ /** A Fabric image drawn behind every object. */
17
+ backgroundImage?: SerializedFabricObject;
18
+ /** A color, gradient or pattern drawn over every object. */
19
+ overlay?: unknown;
20
+ /** A Fabric image drawn over every object. */
21
+ overlayImage?: SerializedFabricObject;
22
+ /** A Fabric object that clips the whole canvas. */
23
+ clipPath?: SerializedFabricObject;
15
24
  }
16
25
  export interface FabricDocument {
17
26
  schemaVersion: number;
@@ -30,5 +39,11 @@ export interface DocumentInfo {
30
39
  createdAt: string;
31
40
  updatedAt: string;
32
41
  metadata: Record<string, unknown>;
42
+ /**
43
+ * Goes up each time the canvas shows a different document: a load, a new
44
+ * document or a restore. Async work started under one session must not
45
+ * change the canvas in another. Set by `getDocumentInfo()`.
46
+ */
47
+ session?: number;
33
48
  }
34
49
  //#endregion
@@ -11,7 +11,16 @@ export interface SerializedFabricObject {
11
11
  export interface DocumentCanvas {
12
12
  width: number;
13
13
  height: number;
14
+ /** A color, gradient or pattern behind everything. */
14
15
  background?: unknown;
16
+ /** A Fabric image drawn behind every object. */
17
+ backgroundImage?: SerializedFabricObject;
18
+ /** A color, gradient or pattern drawn over every object. */
19
+ overlay?: unknown;
20
+ /** A Fabric image drawn over every object. */
21
+ overlayImage?: SerializedFabricObject;
22
+ /** A Fabric object that clips the whole canvas. */
23
+ clipPath?: SerializedFabricObject;
15
24
  }
16
25
  export interface FabricDocument {
17
26
  schemaVersion: number;
@@ -30,5 +39,11 @@ export interface DocumentInfo {
30
39
  createdAt: string;
31
40
  updatedAt: string;
32
41
  metadata: Record<string, unknown>;
42
+ /**
43
+ * Goes up each time the canvas shows a different document: a load, a new
44
+ * document or a restore. Async work started under one session must not
45
+ * change the canvas in another. Set by `getDocumentInfo()`.
46
+ */
47
+ session?: number;
33
48
  }
34
49
  //#endregion
@@ -67,6 +67,14 @@ function validateDocument(value) {
67
67
  else {
68
68
  if (!isPositiveNumber(value.canvas.width)) issues.push(invalid("canvas.width", "must be a positive number"));
69
69
  if (!isPositiveNumber(value.canvas.height)) issues.push(invalid("canvas.height", "must be a positive number"));
70
+ for (const field of [
71
+ "backgroundImage",
72
+ "overlayImage",
73
+ "clipPath"
74
+ ]) {
75
+ const pageObject = value.canvas[field];
76
+ if (pageObject !== void 0 && pageObject !== null) validateObjectList([pageObject], `canvas.${field}`, issues);
77
+ }
70
78
  }
71
79
  validateObjectList(value.objects, "objects", issues);
72
80
  return issues;
@@ -67,6 +67,14 @@ function validateDocument(value) {
67
67
  else {
68
68
  if (!isPositiveNumber(value.canvas.width)) issues.push(invalid("canvas.width", "must be a positive number"));
69
69
  if (!isPositiveNumber(value.canvas.height)) issues.push(invalid("canvas.height", "must be a positive number"));
70
+ for (const field of [
71
+ "backgroundImage",
72
+ "overlayImage",
73
+ "clipPath"
74
+ ]) {
75
+ const pageObject = value.canvas[field];
76
+ if (pageObject !== void 0 && pageObject !== null) validateObjectList([pageObject], `canvas.${field}`, issues);
77
+ }
70
78
  }
71
79
  validateObjectList(value.objects, "objects", issues);
72
80
  return issues;