@hprint/core 0.0.1-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.
Files changed (40) hide show
  1. package/dist/ContextMenu.d.ts +25 -0
  2. package/dist/ContextMenu.d.ts.map +1 -0
  3. package/dist/Editor.d.ts +35 -0
  4. package/dist/Editor.d.ts.map +1 -0
  5. package/dist/Instance.d.ts +77 -0
  6. package/dist/Instance.d.ts.map +1 -0
  7. package/dist/ServersPlugin.d.ts +71 -0
  8. package/dist/ServersPlugin.d.ts.map +1 -0
  9. package/dist/index.css +1 -0
  10. package/dist/index.d.ts +10 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +569 -0
  13. package/dist/index.mjs +22130 -0
  14. package/dist/interface/Editor.d.ts +35 -0
  15. package/dist/interface/Editor.d.ts.map +1 -0
  16. package/dist/objects/CustomRect.d.ts +3 -0
  17. package/dist/objects/CustomRect.d.ts.map +1 -0
  18. package/dist/objects/CustomTextbox.d.ts +3 -0
  19. package/dist/objects/CustomTextbox.d.ts.map +1 -0
  20. package/dist/plugin.d.ts +30 -0
  21. package/dist/plugin.d.ts.map +1 -0
  22. package/dist/utils/fabric-history.d.ts +2 -0
  23. package/dist/utils/fabric-history.d.ts.map +1 -0
  24. package/dist/utils/utils.d.ts +61 -0
  25. package/dist/utils/utils.d.ts.map +1 -0
  26. package/package.json +46 -0
  27. package/src/ContextMenu.js +277 -0
  28. package/src/Editor.ts +215 -0
  29. package/src/Instance.ts +79 -0
  30. package/src/ServersPlugin.ts +387 -0
  31. package/src/index.ts +11 -0
  32. package/src/interface/Editor.ts +56 -0
  33. package/src/objects/CustomRect.js +21 -0
  34. package/src/objects/CustomTextbox.js +165 -0
  35. package/src/plugin.ts +88 -0
  36. package/src/styles/contextMenu.css +60 -0
  37. package/src/utils/fabric-history.js +232 -0
  38. package/src/utils/utils.ts +165 -0
  39. package/tsconfig.json +10 -0
  40. package/vite.config.ts +29 -0
@@ -0,0 +1,56 @@
1
+ import type Editor from '@hprint/core';
2
+
3
+ // IEditor类型包含插件实例,Editor不包含插件实例
4
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
5
+ export interface IEditor extends Editor {}
6
+
7
+ // 生命周期事件类型
8
+ export type IEditorHooksType =
9
+ | 'hookImportBefore'
10
+ | 'hookImportAfter'
11
+ | 'hookSaveBefore'
12
+ | 'hookSaveAfter'
13
+ | 'hookTransform';
14
+
15
+ // 插件实例
16
+ export declare class IPluginTempl {
17
+ constructor(
18
+ canvas: fabric.Canvas,
19
+ editor: IEditor,
20
+ options?: IPluginOption
21
+ );
22
+ static pluginName: string;
23
+ static events: string[];
24
+ static apis: string[];
25
+ hotkeyEvent?: (name: string, e: KeyboardEvent) => void;
26
+ hookImportBefore?: (...args: unknown[]) => Promise<unknown>;
27
+ hookImportAfter?: (...args: unknown[]) => Promise<unknown>;
28
+ hookSaveBefore?: (...args: unknown[]) => Promise<unknown>;
29
+ hookSaveAfter?: (...args: unknown[]) => Promise<unknown>;
30
+ hookTransform?: (...args: unknown[]) => Promise<unknown>;
31
+ [propName: string]: any;
32
+ canvas?: fabric.Canvas;
33
+ editor?: IEditor;
34
+ }
35
+
36
+ export declare interface IPluginOption {
37
+ [propName: string]: unknown | undefined;
38
+ }
39
+
40
+ declare class IPluginClass2 extends IPluginTempl {
41
+ constructor();
42
+ }
43
+ // 插件class
44
+ export declare interface IPluginClass {
45
+ new (
46
+ canvas: fabric.Canvas,
47
+ editor: Editor,
48
+ options?: IPluginOption
49
+ ): IPluginClass2;
50
+ }
51
+
52
+ export declare interface IPluginMenu {
53
+ text: string;
54
+ command?: () => void;
55
+ child?: IPluginMenu[];
56
+ }
@@ -0,0 +1,21 @@
1
+ import { fabric } from 'fabric';
2
+
3
+ fabric.Rect = fabric.util.createClass(fabric.Rect, {
4
+ type: 'rect',
5
+ initialize: function (options) {
6
+ options || (options = {});
7
+ this.callSuper('initialize', options);
8
+ },
9
+ _render(ctx) {
10
+ const roundValue = this.roundValue || 0;
11
+ this.rx = (1 / this.scaleX) * roundValue;
12
+ this.ry = (1 / this.scaleY) * roundValue;
13
+ this.callSuper('_render', ctx);
14
+ },
15
+ });
16
+
17
+ fabric.Rect.fromObject = function (object, callback) {
18
+ return fabric.Object._fromObject('Rect', object, callback);
19
+ };
20
+
21
+ export default fabric.Rect;
@@ -0,0 +1,165 @@
1
+ /*
2
+ * 文本框,修改两端对齐逻辑
3
+ */
4
+ import { fabric } from 'fabric';
5
+
6
+ fabric.Textbox = fabric.util.createClass(fabric.Textbox, {
7
+ type: 'textbox',
8
+
9
+ _renderChars: function (method, ctx, line, left, top, lineIndex) {
10
+ // set proper line offset
11
+ var lineHeight = this.getHeightOfLine(lineIndex),
12
+ isJustify = this.textAlign.indexOf('justify') !== -1,
13
+ actualStyle,
14
+ nextStyle,
15
+ charsToRender = '',
16
+ charBox,
17
+ boxWidth = 0,
18
+ timeToRender,
19
+ path = this.path,
20
+ shortCut =
21
+ !isJustify &&
22
+ this.charSpacing === 0 &&
23
+ this.isEmptyStyles(lineIndex) &&
24
+ !path,
25
+ isLtr = this.direction === 'ltr',
26
+ sign = this.direction === 'ltr' ? 1 : -1,
27
+ drawingLeft,
28
+ currentDirection = ctx.canvas.getAttribute('dir');
29
+ ctx.save();
30
+ if (currentDirection !== this.direction) {
31
+ ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
32
+ ctx.direction = isLtr ? 'ltr' : 'rtl';
33
+ ctx.textAlign = isLtr ? 'left' : 'right';
34
+ }
35
+ top -= (lineHeight * this._fontSizeFraction) / this.lineHeight;
36
+ if (shortCut) {
37
+ // render all the line in one pass without checking
38
+ // drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex);
39
+ this._renderChar(
40
+ method,
41
+ ctx,
42
+ lineIndex,
43
+ 0,
44
+ line.join(''),
45
+ left,
46
+ top,
47
+ lineHeight
48
+ );
49
+ ctx.restore();
50
+ return;
51
+ }
52
+ for (var i = 0, len = line.length - 1; i <= len; i++) {
53
+ timeToRender = i === len || this.charSpacing || path;
54
+ charsToRender += line[i];
55
+ charBox = this.__charBounds[lineIndex][i];
56
+ if (boxWidth === 0) {
57
+ left += sign * (charBox.kernedWidth - charBox.width);
58
+ boxWidth += charBox.width;
59
+ } else {
60
+ boxWidth += charBox.kernedWidth;
61
+ }
62
+ if (isJustify && !timeToRender) {
63
+ if (this._reSpaceAndTab.test(line[i])) {
64
+ timeToRender = true;
65
+ }
66
+ }
67
+ if (!timeToRender) {
68
+ // if we have charSpacing, we render char by char
69
+ actualStyle =
70
+ actualStyle ||
71
+ this.getCompleteStyleDeclaration(lineIndex, i);
72
+ nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1);
73
+ timeToRender = fabric.util.hasStyleChanged(
74
+ actualStyle,
75
+ nextStyle,
76
+ false
77
+ );
78
+ }
79
+ // if (timeToRender) {
80
+ if (path) {
81
+ ctx.save();
82
+ ctx.translate(charBox.renderLeft, charBox.renderTop);
83
+ ctx.rotate(charBox.angle);
84
+ this._renderChar(
85
+ method,
86
+ ctx,
87
+ lineIndex,
88
+ i,
89
+ charsToRender,
90
+ -boxWidth / 2,
91
+ 0,
92
+ lineHeight
93
+ );
94
+ ctx.restore();
95
+ } else {
96
+ drawingLeft = left;
97
+ this._renderChar(
98
+ method,
99
+ ctx,
100
+ lineIndex,
101
+ i,
102
+ charsToRender,
103
+ drawingLeft,
104
+ top,
105
+ lineHeight
106
+ );
107
+ }
108
+ charsToRender = '';
109
+ actualStyle = nextStyle;
110
+ left += sign * boxWidth;
111
+ boxWidth = 0;
112
+ // }
113
+ }
114
+ ctx.restore();
115
+ },
116
+ enlargeSpaces: function () {
117
+ var diffSpace,
118
+ currentLineWidth,
119
+ numberOfSpaces,
120
+ accumulatedSpace,
121
+ line,
122
+ charBound,
123
+ spaces;
124
+ for (var i = 0, len = this._textLines.length; i < len; i++) {
125
+ if (
126
+ this.textAlign !== 'justify' &&
127
+ (i === len - 1 || this.isEndOfWrapping(i))
128
+ ) {
129
+ continue;
130
+ }
131
+ accumulatedSpace = 0;
132
+ line = this._textLines[i];
133
+ currentLineWidth = this.getLineWidth(i);
134
+ if (
135
+ currentLineWidth < this.width &&
136
+ (spaces = this.textLines[i].split('')) &&
137
+ spaces.length > 1
138
+ ) {
139
+ numberOfSpaces = spaces.length;
140
+ diffSpace =
141
+ (this.width - currentLineWidth) / (numberOfSpaces - 1);
142
+ for (var j = 0, jlen = line.length; j <= jlen; j++) {
143
+ charBound = this.__charBounds[i][j];
144
+ // if (this._reSpaceAndTab.test(line[j])) {
145
+ charBound.left += accumulatedSpace;
146
+ if (j < jlen - 1) {
147
+ charBound.width += diffSpace;
148
+ charBound.kernedWidth += diffSpace;
149
+ accumulatedSpace += diffSpace;
150
+ }
151
+ // } else {
152
+ // charBound.left += accumulatedSpace;
153
+ // }
154
+ }
155
+ }
156
+ }
157
+ },
158
+ });
159
+
160
+ fabric.Textbox.fromObject = function (options, callback) {
161
+ const { text } = options;
162
+ return callback(new fabric.Textbox(text, options));
163
+ };
164
+
165
+ export default fabric.Rect;
package/src/plugin.ts ADDED
@@ -0,0 +1,88 @@
1
+ import Editor from './Editor';
2
+ type IEditor = Editor;
3
+
4
+ class FontPlugin {
5
+ public canvas: fabric.Canvas;
6
+ public editor: IEditor;
7
+ // 插件名称
8
+ static pluginName = 'FontPlugin';
9
+ // 挂载API名称
10
+ static apis = ['downFontByJSON'];
11
+ // 发布事件
12
+ static events = ['textEvent1', 'textEvent2'];
13
+ // 快捷键 keyCode hotkeys-js
14
+ public hotkeys: string[] = ['backspace', 'space'];
15
+ // 私有属性
16
+ repoSrc: string;
17
+
18
+ constructor(
19
+ canvas: fabric.Canvas,
20
+ editor: IEditor,
21
+ config: { repoSrc: string }
22
+ ) {
23
+ // 初始化
24
+ this.canvas = canvas;
25
+ this.editor = editor;
26
+ // 可插入外部配置
27
+ this.repoSrc = config.repoSrc;
28
+ }
29
+
30
+ // 钩子函数 hookImportAfter/hookSaveBefore/hookSaveAfter Promise
31
+ hookImportBefore(json: string) {
32
+ return this.downFontByJSON(json);
33
+ }
34
+
35
+ // 挂载API方法
36
+ downFontByJSON() {
37
+ //
38
+ }
39
+
40
+ // 私有方法 + 发布事件
41
+ _createFontCSS() {
42
+ const params = [];
43
+ this.editor.emit('textEvent1', params);
44
+ }
45
+
46
+ // 右键菜单
47
+ contextMenu() {
48
+ const selectedMode = this.editor.getSelectMode();
49
+ if (selectedMode === SelectMode.ONE) {
50
+ return [
51
+ null, // 分割线
52
+ {
53
+ text: '翻转',
54
+ hotkey: '❯',
55
+ subitems: [
56
+ {
57
+ text: '水平翻转',
58
+ hotkey: '|',
59
+ onclick: () => this.flip('X'),
60
+ },
61
+ {
62
+ text: '垂直翻转',
63
+ hotkey: '-',
64
+ onclick: () => this.flip('Y'),
65
+ },
66
+ ],
67
+ },
68
+ ];
69
+ }
70
+ }
71
+
72
+ // 快捷键
73
+ hotkeyEvent(eventName: string, { type }: KeyboardEvent) {
74
+ // eventName:hotkeys中的属性 backspace、space
75
+ // type:keyUp keyDown
76
+ // code:hotkeys-js Code
77
+ if (eventName === 'backspace' && type === 'keydown') {
78
+ this.del();
79
+ }
80
+ }
81
+
82
+ // 注销
83
+ destroy() {
84
+ console.log('pluginDestroy');
85
+ }
86
+ }
87
+
88
+ export default FontPlugin;
@@ -0,0 +1,60 @@
1
+ .context {
2
+ display: inline-block;
3
+ position: fixed;
4
+ top: 0px;
5
+ left: 0px;
6
+ min-width: 270px;
7
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
8
+ color: #fff;
9
+ background: #262933;
10
+ font-size: 9pt;
11
+ border: 1px solid #333333;
12
+ border-radius: 6px;
13
+ box-shadow: 2px 2px 2px -1px rgba(0, 0, 0, 0.5);
14
+ padding: 3px 0px;
15
+ -webkit-touch-callout: none;
16
+ -webkit-user-select: none;
17
+ -khtml-user-select: none;
18
+ -moz-user-select: none;
19
+ -ms-user-select: none;
20
+ user-select: none;
21
+ z-index: 999;
22
+ }
23
+
24
+ .context .item {
25
+ padding: 4px 19px;
26
+ cursor: default;
27
+ color: inherit;
28
+ }
29
+
30
+ .context .item:hover {
31
+ background: #2777ff;
32
+ }
33
+
34
+ .context .item:hover .hotkey {
35
+ color: #fff;
36
+ }
37
+
38
+ .context .disabled {
39
+ color: #878b90;
40
+ }
41
+
42
+ .context .disabled:hover {
43
+ background: inherit;
44
+ }
45
+
46
+ .context .disabled:hover .hotkey {
47
+ color: #878b90;
48
+ }
49
+
50
+ .context .separator {
51
+ margin: 4px 0px;
52
+ height: 0;
53
+ padding: 0;
54
+ border-top: 1px solid #454545;
55
+ }
56
+
57
+ .hotkey {
58
+ color: #878b90;
59
+ float: right;
60
+ }
@@ -0,0 +1,232 @@
1
+ import { fabric } from 'fabric';
2
+
3
+ fabric.Canvas.prototype.initialize = (function (originalFn) {
4
+ return function (...args) {
5
+ originalFn.call(this, ...args);
6
+ this._historyInit();
7
+ return this;
8
+ };
9
+ })(fabric.Canvas.prototype.initialize);
10
+
11
+ /**
12
+ * Override the dispose function for the _historyDispose();
13
+ */
14
+ fabric.Canvas.prototype.dispose = (function (originalFn) {
15
+ return function (...args) {
16
+ originalFn.call(this, ...args);
17
+ this._historyDispose();
18
+ return this;
19
+ };
20
+ })(fabric.Canvas.prototype.dispose);
21
+
22
+ /**
23
+ * Returns current state of the string of the canvas
24
+ */
25
+ fabric.Canvas.prototype._historyNext = function () {
26
+ return JSON.stringify(this.toDatalessJSON(this.extraProps));
27
+ };
28
+
29
+ /**
30
+ * Returns an object with fabricjs event mappings
31
+ */
32
+ fabric.Canvas.prototype._historyEvents = function () {
33
+ return {
34
+ 'object:added': (e) => this._historySaveAction(e),
35
+ 'object:removed': (e) => this._historySaveAction(e),
36
+ 'object:modified': (e) => this._historySaveAction(e),
37
+ 'object:skewing': (e) => this._historySaveAction(e),
38
+ };
39
+ };
40
+
41
+ /**
42
+ * Initialization of the plugin
43
+ */
44
+ fabric.Canvas.prototype._historyInit = function () {
45
+ this.historyStack = [];
46
+ this.historyIndex = 0;
47
+ this.historyMaxLength = 100;
48
+ this.extraProps = [
49
+ 'id',
50
+ 'gradientAngle',
51
+ 'selectable',
52
+ 'hasControls',
53
+ 'linkData',
54
+ 'editable',
55
+ 'extensionType',
56
+ 'extension',
57
+ ];
58
+ this.historyNextState = this._historyNext();
59
+ // 需要两次操作的标记,为true时表示当前操作记录为最新记录,需要撤销两步,因为最顶层的是当前的最新记录,undo一次后后置为false
60
+ this.isLatestHistoryState = true;
61
+ // 正在读取历史记录的标记,为 true 时不允许 undo/redo
62
+ this.isLoadingHistory = false;
63
+
64
+ this.on(this._historyEvents());
65
+ };
66
+
67
+ /**
68
+ * Remove the custom event listeners
69
+ */
70
+ fabric.Canvas.prototype._historyDispose = function () {
71
+ this.off(this._historyEvents());
72
+ };
73
+
74
+ /**
75
+ * It pushes the state of the canvas into history stack
76
+ */
77
+ fabric.Canvas.prototype._historySaveAction = function (e) {
78
+ if (this.historyProcessing) return;
79
+ if (!e || (e.target && !e.target.excludeFromExport)) {
80
+ const json = this._historyNext();
81
+ // 当前操作记录非最新记录,更新记录前需要校正历史索引,不然会丢失一个记录(undo时撤销了两次记录)。理论上不会超出历史记录上限,不过还是加了限制
82
+ !this.isLatestHistoryState &&
83
+ (this.isLatestHistoryState = true) &&
84
+ this.historyIndex < this.historyMaxLength &&
85
+ this.historyIndex++;
86
+ // 每次的最新操作都要清空历史索引之后的记录,防止redo旧记录,不然可能会redo之前某个阶段的操作记录
87
+ this.historyStack.length > this.historyIndex &&
88
+ this.historyStack.splice(this.historyIndex);
89
+ // 最多保存 historyMaxLength 条记录
90
+ if (this.historyIndex >= this.historyMaxLength)
91
+ this.historyStack.shift();
92
+ this.historyIndex < this.historyMaxLength && this.historyIndex++;
93
+ this.historyStack.push(json);
94
+ this.historyNextState = this._historyNext();
95
+ this.fire('history:append', { json });
96
+ }
97
+ };
98
+
99
+ /**
100
+ * Undo to latest history.
101
+ * Pop the latest state of the history. Re-render.
102
+ * Also, pushes into redo history.
103
+ */
104
+ fabric.Canvas.prototype.undo = function (callback) {
105
+ if (this.isLoadingHistory) return;
106
+ if (this.historyIndex <= 0) return;
107
+ // The undo process will render the new states of the objects
108
+ // Therefore, object:added and object:modified events will triggered again
109
+ // To ignore those events, we are setting a flag.
110
+ this.historyProcessing = true;
111
+
112
+ // 当前操作记录为最新记录,需要撤销两步,因为最顶层的是当前的最新记录
113
+ this.isLatestHistoryState &&
114
+ this.historyIndex-- &&
115
+ (this.isLatestHistoryState = false);
116
+ const history = this.historyStack[--this.historyIndex];
117
+ if (history) {
118
+ // Push the current state to the redo history
119
+ this.historyNextState = history;
120
+ this._loadHistory(history, 'history:undo', callback);
121
+ } else {
122
+ console.log(1111);
123
+ this.historyIndex < 0 && (this.historyIndex = 0);
124
+ this.historyProcessing = false;
125
+ }
126
+ };
127
+
128
+ /**
129
+ * Redo to latest undo history.
130
+ */
131
+ fabric.Canvas.prototype.redo = function (callback) {
132
+ if (this.isLoadingHistory) return;
133
+ if (this.historyIndex >= this.historyStack.length) return;
134
+ // The undo process will render the new states of the objects
135
+ // Therefore, object:added and object:modified events will triggered again
136
+ // To ignore those events, we are setting a flag.
137
+ this.historyProcessing = true;
138
+ // 当前操作记录不是最新记录(被撤销过),需要恢复两步,抵消最初撤销时撤销两步的操作
139
+ !this.isLatestHistoryState &&
140
+ ++this.historyIndex &&
141
+ (this.isLatestHistoryState = true);
142
+ const history = this.historyStack[this.historyIndex];
143
+ if (history) {
144
+ // Every redo action is actually a new action to the undo history
145
+ this.historyNextState = history;
146
+ this._loadHistory(history, 'history:redo', callback);
147
+ this.historyIndex++;
148
+ } else {
149
+ this.historyProcessing = false;
150
+ }
151
+ };
152
+
153
+ // loadFromJSON 是异步操作,所以通过 isLoadingHistory = true 表示历史读取中,不可 undo/redo,
154
+ // 不然当页面复杂且快速 undo/redo 多次后,可能会在之前的历史上 redo/undo
155
+ fabric.Canvas.prototype._loadHistory = function (history, event, callback) {
156
+ this.isLoadingHistory = true;
157
+ var that = this;
158
+
159
+ // 需要把历史记录中的 workspace 的 evented 属性设置为 false,否则会导致历史记录恢复后,鼠标悬浮 workspace 出现可操作的样式
160
+ const workspaceHistory = history.objects?.find(
161
+ (item) => item.id === 'workspace'
162
+ );
163
+ workspaceHistory && (workspaceHistory.evented = false);
164
+
165
+ this.loadFromJSON(history, function () {
166
+ that.renderAll();
167
+ that.fire(event);
168
+ that.historyProcessing = false;
169
+ that.isLoadingHistory = false;
170
+
171
+ if (callback && typeof callback === 'function') callback();
172
+ });
173
+ };
174
+
175
+ /**
176
+ * Clear undo and redo history stacks
177
+ */
178
+ fabric.Canvas.prototype.clearHistory = function (type) {
179
+ const one = this.historyStack.pop();
180
+ if (!type || !one) {
181
+ this.historyStack = [];
182
+ this.historyIndex = 0;
183
+ this.fire('history:clear');
184
+ } else {
185
+ this.historyStack = [one];
186
+ this.historyIndex = 1;
187
+ this.fire('history:clear');
188
+ }
189
+ this.isLatestHistoryState = true;
190
+ };
191
+
192
+ fabric.Canvas.prototype.clearUndo = function () {
193
+ this.historyStack.splice(this.historyIndex);
194
+ };
195
+
196
+ // 如果在做一些操作之后,需要撤销上一步的操作并刷新历史记录(想在监听modified事件后做些额外的操作并记录操作后的历史),可以调用这个方法
197
+ fabric.Canvas.prototype.refreshHistory = function () {
198
+ this.historyProcessing = false;
199
+ this.historyStack.splice(--this.historyIndex);
200
+ this._historySaveAction();
201
+ };
202
+
203
+ /**
204
+ * On the history
205
+ */
206
+ fabric.Canvas.prototype.onHistory = function () {
207
+ this.historyProcessing = false;
208
+
209
+ this._historySaveAction();
210
+ };
211
+
212
+ /**
213
+ * Check if there are actions that can be undone
214
+ */
215
+
216
+ fabric.Canvas.prototype.canUndo = function () {
217
+ return this.historyIndex > 0;
218
+ };
219
+
220
+ /**
221
+ * Check if there are actions that can be redone
222
+ */
223
+ fabric.Canvas.prototype.canRedo = function () {
224
+ return this.historyStack.length > this.historyIndex;
225
+ };
226
+
227
+ /**
228
+ * Off the history
229
+ */
230
+ fabric.Canvas.prototype.offHistory = function () {
231
+ this.historyProcessing = true;
232
+ };