fabricjs-document-engine 1.2.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 (81) hide show
  1. package/README.md +31 -0
  2. package/README.zh-CN.md +32 -0
  3. package/dist/commands/text-edit.cjs +223 -0
  4. package/dist/commands/text-edit.d.cts +23 -0
  5. package/dist/commands/text-edit.d.ts +23 -0
  6. package/dist/commands/text-edit.js +218 -0
  7. package/dist/fabric/object-registry.cjs +7 -1
  8. package/dist/fabric/object-registry.js +7 -1
  9. package/dist/filters/filter-worker.cjs +281 -0
  10. package/dist/filters/filter-worker.d.cts +26 -0
  11. package/dist/filters/filter-worker.d.ts +26 -0
  12. package/dist/filters/filter-worker.js +281 -0
  13. package/dist/filters/pipeline.cjs +91 -0
  14. package/dist/filters/pipeline.d.cts +35 -0
  15. package/dist/filters/pipeline.d.ts +35 -0
  16. package/dist/filters/pipeline.js +87 -0
  17. package/dist/filters/worker-entry.cjs +78 -0
  18. package/dist/filters/worker-entry.d.cts +29 -0
  19. package/dist/filters/worker-entry.d.ts +29 -0
  20. package/dist/filters/worker-entry.js +78 -0
  21. package/dist/filters.cjs +9 -0
  22. package/dist/filters.d.cts +3 -0
  23. package/dist/filters.d.ts +3 -0
  24. package/dist/filters.js +3 -0
  25. package/dist/import/svg-import.cjs +102 -0
  26. package/dist/import/svg-import.d.cts +1 -0
  27. package/dist/import/svg-import.d.ts +1 -0
  28. package/dist/import/svg-import.js +103 -1
  29. package/dist/index.cjs +7 -0
  30. package/dist/index.d.cts +2 -1
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index.js +2 -1
  33. package/dist/performance/batch.cjs +31 -0
  34. package/dist/performance/batch.d.cts +4 -0
  35. package/dist/performance/batch.d.ts +4 -0
  36. package/dist/performance/batch.js +31 -0
  37. package/dist/performance/dirty-regions.cjs +378 -0
  38. package/dist/performance/dirty-regions.d.cts +23 -0
  39. package/dist/performance/dirty-regions.d.ts +23 -0
  40. package/dist/performance/dirty-regions.js +378 -0
  41. package/dist/performance/monitor.cjs +60 -0
  42. package/dist/performance/monitor.d.cts +20 -0
  43. package/dist/performance/monitor.d.ts +20 -0
  44. package/dist/performance/monitor.js +60 -0
  45. package/dist/performance/spatial-index.cjs +78 -0
  46. package/dist/performance/spatial-index.d.cts +21 -0
  47. package/dist/performance/spatial-index.d.ts +21 -0
  48. package/dist/performance/spatial-index.js +78 -0
  49. package/dist/performance.cjs +9 -0
  50. package/dist/performance.d.cts +5 -0
  51. package/dist/performance.d.ts +5 -0
  52. package/dist/performance.js +5 -0
  53. package/dist/text/bounded-textbox.cjs +306 -0
  54. package/dist/text/bounded-textbox.d.cts +53 -0
  55. package/dist/text/bounded-textbox.d.ts +53 -0
  56. package/dist/text/bounded-textbox.js +306 -0
  57. package/dist/text/mobile-input.cjs +146 -0
  58. package/dist/text/mobile-input.d.cts +19 -0
  59. package/dist/text/mobile-input.d.ts +19 -0
  60. package/dist/text/mobile-input.js +145 -0
  61. package/dist/text/register.cjs +17 -0
  62. package/dist/text/register.d.cts +5 -0
  63. package/dist/text/register.d.ts +5 -0
  64. package/dist/text/register.js +16 -0
  65. package/dist/text/shaping.cjs +125 -0
  66. package/dist/text/shaping.d.cts +28 -0
  67. package/dist/text/shaping.d.ts +28 -0
  68. package/dist/text/shaping.js +121 -0
  69. package/dist/text/vertical-orientation.cjs +192 -0
  70. package/dist/text/vertical-orientation.d.cts +10 -0
  71. package/dist/text/vertical-orientation.d.ts +10 -0
  72. package/dist/text/vertical-orientation.js +190 -0
  73. package/dist/text/vertical-text.cjs +336 -0
  74. package/dist/text/vertical-text.d.cts +54 -0
  75. package/dist/text/vertical-text.d.ts +54 -0
  76. package/dist/text/vertical-text.js +336 -0
  77. package/dist/text.cjs +21 -0
  78. package/dist/text.d.cts +7 -0
  79. package/dist/text.d.ts +7 -0
  80. package/dist/text.js +7 -0
  81. package/package.json +31 -1
package/dist/index.cjs CHANGED
@@ -12,6 +12,7 @@ const require_batch_render = require("./export/batch-render.cjs");
12
12
  const require_document_state_store = require("./state/document-state-store.cjs");
13
13
  const require_clipboard = require("./commands/clipboard.cjs");
14
14
  const require_layers = require("./commands/layers.cjs");
15
+ const require_text_edit = require("./commands/text-edit.cjs");
15
16
  exports.CLIPBOARD_FORMAT = require_clipboard.CLIPBOARD_FORMAT;
16
17
  exports.CURRENT_SCHEMA_VERSION = require_document_format.CURRENT_SCHEMA_VERSION;
17
18
  exports.DocumentEngineError = require_errors.DocumentEngineError;
@@ -23,6 +24,7 @@ exports.createClipboard = require_clipboard.createClipboard;
23
24
  exports.createConflictError = require_errors.createConflictError;
24
25
  exports.createDocumentEngine = require_create_document_engine.createDocumentEngine;
25
26
  exports.createDocumentStateStore = require_document_state_store.createDocumentStateStore;
27
+ exports.createTextCommands = require_text_edit.createTextCommands;
26
28
  exports.detectSchemaVersion = require_migrate_document.detectSchemaVersion;
27
29
  exports.downloadExport = require_download_export.downloadExport;
28
30
  exports.getLayers = require_layers.getLayers;
@@ -30,9 +32,14 @@ exports.isDocumentEngineError = require_errors.isDocumentEngineError;
30
32
  exports.isSafeImageUrl = require_content_limits.isSafeImageUrl;
31
33
  exports.migrateDocument = require_migrate_document.migrateDocument;
32
34
  exports.moveToIndex = require_layers.moveToIndex;
35
+ exports.readStyleRuns = require_text_edit.readStyleRuns;
33
36
  exports.refuseUnsafeImageUrls = require_content_limits.refuseUnsafeImageUrls;
34
37
  exports.renderDocuments = require_batch_render.renderDocuments;
38
+ exports.replaceTextRange = require_text_edit.replaceTextRange;
35
39
  exports.secureDocument = require_content_limits.secureDocument;
36
40
  exports.sendBackward = require_layers.sendBackward;
37
41
  exports.sendToBack = require_layers.sendToBack;
42
+ exports.setTextRangeStyle = require_text_edit.setTextRangeStyle;
43
+ exports.shiftStyleRuns = require_text_edit.shiftStyleRuns;
38
44
  exports.validateDocument = require_validate_document.validateDocument;
45
+ exports.writeStyleRuns = require_text_edit.writeStyleRuns;
package/dist/index.d.cts CHANGED
@@ -29,4 +29,5 @@ import { Migration, MigrationContext, MigrationResult, detectSchemaVersion, migr
29
29
  import { DocumentState, DocumentStateStore, createDocumentStateStore } from "./state/document-state-store.cjs";
30
30
  import { CLIPBOARD_FORMAT, Clipboard, ClipboardContent, ClipboardOptions, PasteOptions, createClipboard } from "./commands/clipboard.cjs";
31
31
  import { LayerInfo, LayerOptions, LayerPin, bringForward, bringToFront, getLayers, moveToIndex, sendBackward, sendToBack } from "./commands/layers.cjs";
32
- export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CLIPBOARD_FORMAT, CURRENT_SCHEMA_VERSION, type Clipboard, type ClipboardContent, type ClipboardOptions, type ContentLimits, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentState, type DocumentStateStore, type DocumentStorage, type DocumentVersion, type DownloadableExport, type ExportArea, type ExportBackground, type ExportFormat, type ExportOptions, type ExportPreflight, type ExportProblem, type ExportProblemCode, type ExportRect, type ExportResult, type FabricDocument, type FontAsset, type FontLoader, type FontSource, type HistoryOptions, type HistoryState, type ImageAsset, type ImageFailureReason, type ImageLoadFailure, type ImportOptions, type InterruptedLoad, type KeyboardShortcutOptions, type LayerInfo, type LayerOptions, type LayerPin, type LoadOptions, type LoadProgress, type LoadStage, type Migration, type MigrationContext, type MigrationResult, type NewDocumentOptions, type NewDocumentRequest, type PageChanges, type PasteOptions, type RecoveryOptions, type RecoveryRecord, type RecoveryStore, type RenderDocumentsOptions, type RenderedDocument, type RestoreRecoveryOptions, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type SvgExportOptions, type SvgImportOptions, type SvgImportResult, type SvgImportWarning, type SvgImportWarningCode, type TransactionOptions, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, type VersionKind, type VersionOptions, type VersionStorage, type VersionSummary, bindKeyboardShortcuts, bindUnsavedChangesWarning, bringForward, bringToFront, createClipboard, createConflictError, createDocumentEngine, createDocumentStateStore, detectSchemaVersion, downloadExport, getLayers, isDocumentEngineError, isSafeImageUrl, migrateDocument, moveToIndex, refuseUnsafeImageUrls, renderDocuments, secureDocument, sendBackward, sendToBack, validateDocument };
32
+ import { EditableTextObject, InsertedStyle, StyleRun, TextCommands, createTextCommands, readStyleRuns, replaceTextRange, setTextRangeStyle, shiftStyleRuns, writeStyleRuns } from "./commands/text-edit.cjs";
33
+ export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CLIPBOARD_FORMAT, CURRENT_SCHEMA_VERSION, type Clipboard, type ClipboardContent, type ClipboardOptions, type ContentLimits, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentState, type DocumentStateStore, type DocumentStorage, type DocumentVersion, type DownloadableExport, type EditableTextObject, type ExportArea, type ExportBackground, type ExportFormat, type ExportOptions, type ExportPreflight, type ExportProblem, type ExportProblemCode, type ExportRect, type ExportResult, type FabricDocument, type FontAsset, type FontLoader, type FontSource, type HistoryOptions, type HistoryState, type ImageAsset, type ImageFailureReason, type ImageLoadFailure, type ImportOptions, type InsertedStyle, type InterruptedLoad, type KeyboardShortcutOptions, type LayerInfo, type LayerOptions, type LayerPin, type LoadOptions, type LoadProgress, type LoadStage, type Migration, type MigrationContext, type MigrationResult, type NewDocumentOptions, type NewDocumentRequest, type PageChanges, type PasteOptions, type RecoveryOptions, type RecoveryRecord, type RecoveryStore, type RenderDocumentsOptions, type RenderedDocument, type RestoreRecoveryOptions, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type StyleRun, type SvgExportOptions, type SvgImportOptions, type SvgImportResult, type SvgImportWarning, type SvgImportWarningCode, type TextCommands, type TransactionOptions, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, type VersionKind, type VersionOptions, type VersionStorage, type VersionSummary, bindKeyboardShortcuts, bindUnsavedChangesWarning, bringForward, bringToFront, createClipboard, createConflictError, createDocumentEngine, createDocumentStateStore, createTextCommands, detectSchemaVersion, downloadExport, getLayers, isDocumentEngineError, isSafeImageUrl, migrateDocument, moveToIndex, readStyleRuns, refuseUnsafeImageUrls, renderDocuments, replaceTextRange, secureDocument, sendBackward, sendToBack, setTextRangeStyle, shiftStyleRuns, validateDocument, writeStyleRuns };
package/dist/index.d.ts CHANGED
@@ -29,4 +29,5 @@ import { Migration, MigrationContext, MigrationResult, detectSchemaVersion, migr
29
29
  import { DocumentState, DocumentStateStore, createDocumentStateStore } from "./state/document-state-store.js";
30
30
  import { CLIPBOARD_FORMAT, Clipboard, ClipboardContent, ClipboardOptions, PasteOptions, createClipboard } from "./commands/clipboard.js";
31
31
  import { LayerInfo, LayerOptions, LayerPin, bringForward, bringToFront, getLayers, moveToIndex, sendBackward, sendToBack } from "./commands/layers.js";
32
- export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CLIPBOARD_FORMAT, CURRENT_SCHEMA_VERSION, type Clipboard, type ClipboardContent, type ClipboardOptions, type ContentLimits, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentState, type DocumentStateStore, type DocumentStorage, type DocumentVersion, type DownloadableExport, type ExportArea, type ExportBackground, type ExportFormat, type ExportOptions, type ExportPreflight, type ExportProblem, type ExportProblemCode, type ExportRect, type ExportResult, type FabricDocument, type FontAsset, type FontLoader, type FontSource, type HistoryOptions, type HistoryState, type ImageAsset, type ImageFailureReason, type ImageLoadFailure, type ImportOptions, type InterruptedLoad, type KeyboardShortcutOptions, type LayerInfo, type LayerOptions, type LayerPin, type LoadOptions, type LoadProgress, type LoadStage, type Migration, type MigrationContext, type MigrationResult, type NewDocumentOptions, type NewDocumentRequest, type PageChanges, type PasteOptions, type RecoveryOptions, type RecoveryRecord, type RecoveryStore, type RenderDocumentsOptions, type RenderedDocument, type RestoreRecoveryOptions, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type SvgExportOptions, type SvgImportOptions, type SvgImportResult, type SvgImportWarning, type SvgImportWarningCode, type TransactionOptions, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, type VersionKind, type VersionOptions, type VersionStorage, type VersionSummary, bindKeyboardShortcuts, bindUnsavedChangesWarning, bringForward, bringToFront, createClipboard, createConflictError, createDocumentEngine, createDocumentStateStore, detectSchemaVersion, downloadExport, getLayers, isDocumentEngineError, isSafeImageUrl, migrateDocument, moveToIndex, refuseUnsafeImageUrls, renderDocuments, secureDocument, sendBackward, sendToBack, validateDocument };
32
+ import { EditableTextObject, InsertedStyle, StyleRun, TextCommands, createTextCommands, readStyleRuns, replaceTextRange, setTextRangeStyle, shiftStyleRuns, writeStyleRuns } from "./commands/text-edit.js";
33
+ export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CLIPBOARD_FORMAT, CURRENT_SCHEMA_VERSION, type Clipboard, type ClipboardContent, type ClipboardOptions, type ContentLimits, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentState, type DocumentStateStore, type DocumentStorage, type DocumentVersion, type DownloadableExport, type EditableTextObject, type ExportArea, type ExportBackground, type ExportFormat, type ExportOptions, type ExportPreflight, type ExportProblem, type ExportProblemCode, type ExportRect, type ExportResult, type FabricDocument, type FontAsset, type FontLoader, type FontSource, type HistoryOptions, type HistoryState, type ImageAsset, type ImageFailureReason, type ImageLoadFailure, type ImportOptions, type InsertedStyle, type InterruptedLoad, type KeyboardShortcutOptions, type LayerInfo, type LayerOptions, type LayerPin, type LoadOptions, type LoadProgress, type LoadStage, type Migration, type MigrationContext, type MigrationResult, type NewDocumentOptions, type NewDocumentRequest, type PageChanges, type PasteOptions, type RecoveryOptions, type RecoveryRecord, type RecoveryStore, type RenderDocumentsOptions, type RenderedDocument, type RestoreRecoveryOptions, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type StyleRun, type SvgExportOptions, type SvgImportOptions, type SvgImportResult, type SvgImportWarning, type SvgImportWarningCode, type TextCommands, type TransactionOptions, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, type VersionKind, type VersionOptions, type VersionStorage, type VersionSummary, bindKeyboardShortcuts, bindUnsavedChangesWarning, bringForward, bringToFront, createClipboard, createConflictError, createDocumentEngine, createDocumentStateStore, createTextCommands, detectSchemaVersion, downloadExport, getLayers, isDocumentEngineError, isSafeImageUrl, migrateDocument, moveToIndex, readStyleRuns, refuseUnsafeImageUrls, renderDocuments, replaceTextRange, secureDocument, sendBackward, sendToBack, setTextRangeStyle, shiftStyleRuns, validateDocument, writeStyleRuns };
package/dist/index.js CHANGED
@@ -11,4 +11,5 @@ import { renderDocuments } from "./export/batch-render.js";
11
11
  import { createDocumentStateStore } from "./state/document-state-store.js";
12
12
  import { CLIPBOARD_FORMAT, createClipboard } from "./commands/clipboard.js";
13
13
  import { bringForward, bringToFront, getLayers, moveToIndex, sendBackward, sendToBack } from "./commands/layers.js";
14
- export { CLIPBOARD_FORMAT, CURRENT_SCHEMA_VERSION, DocumentEngineError, bindKeyboardShortcuts, bindUnsavedChangesWarning, bringForward, bringToFront, createClipboard, createConflictError, createDocumentEngine, createDocumentStateStore, detectSchemaVersion, downloadExport, getLayers, isDocumentEngineError, isSafeImageUrl, migrateDocument, moveToIndex, refuseUnsafeImageUrls, renderDocuments, secureDocument, sendBackward, sendToBack, validateDocument };
14
+ import { createTextCommands, readStyleRuns, replaceTextRange, setTextRangeStyle, shiftStyleRuns, writeStyleRuns } from "./commands/text-edit.js";
15
+ export { CLIPBOARD_FORMAT, CURRENT_SCHEMA_VERSION, DocumentEngineError, bindKeyboardShortcuts, bindUnsavedChangesWarning, bringForward, bringToFront, createClipboard, createConflictError, createDocumentEngine, createDocumentStateStore, createTextCommands, detectSchemaVersion, downloadExport, getLayers, isDocumentEngineError, isSafeImageUrl, migrateDocument, moveToIndex, readStyleRuns, refuseUnsafeImageUrls, renderDocuments, replaceTextRange, secureDocument, sendBackward, sendToBack, setTextRangeStyle, shiftStyleRuns, validateDocument, writeStyleRuns };
@@ -0,0 +1,31 @@
1
+ //#region src/performance/batch.ts
2
+ const depths = /* @__PURE__ */ new WeakMap();
3
+ function batchCanvasUpdates(canvas, work) {
4
+ const state = depths.get(canvas) ?? {
5
+ depth: 0,
6
+ renderOnAddRemove: canvas.renderOnAddRemove
7
+ };
8
+ if (state.depth === 0) state.renderOnAddRemove = canvas.renderOnAddRemove;
9
+ state.depth += 1;
10
+ depths.set(canvas, state);
11
+ canvas.renderOnAddRemove = false;
12
+ const finish = () => {
13
+ state.depth -= 1;
14
+ if (state.depth > 0) return;
15
+ depths.delete(canvas);
16
+ canvas.renderOnAddRemove = state.renderOnAddRemove;
17
+ canvas.requestRenderAll();
18
+ };
19
+ let result;
20
+ try {
21
+ result = work();
22
+ } catch (error) {
23
+ finish();
24
+ throw error;
25
+ }
26
+ if (result instanceof Promise) return result.finally(finish);
27
+ finish();
28
+ return result;
29
+ }
30
+ //#endregion
31
+ exports.batchCanvasUpdates = batchCanvasUpdates;
@@ -0,0 +1,4 @@
1
+ import { StaticCanvas } from "fabric";
2
+ //#region src/performance/batch.d.ts
3
+ export declare function batchCanvasUpdates<Result>(canvas: StaticCanvas, work: () => Result): Result;
4
+ //#endregion
@@ -0,0 +1,4 @@
1
+ import { StaticCanvas } from "fabric";
2
+ //#region src/performance/batch.d.ts
3
+ export declare function batchCanvasUpdates<Result>(canvas: StaticCanvas, work: () => Result): Result;
4
+ //#endregion
@@ -0,0 +1,31 @@
1
+ //#region src/performance/batch.ts
2
+ const depths = /* @__PURE__ */ new WeakMap();
3
+ function batchCanvasUpdates(canvas, work) {
4
+ const state = depths.get(canvas) ?? {
5
+ depth: 0,
6
+ renderOnAddRemove: canvas.renderOnAddRemove
7
+ };
8
+ if (state.depth === 0) state.renderOnAddRemove = canvas.renderOnAddRemove;
9
+ state.depth += 1;
10
+ depths.set(canvas, state);
11
+ canvas.renderOnAddRemove = false;
12
+ const finish = () => {
13
+ state.depth -= 1;
14
+ if (state.depth > 0) return;
15
+ depths.delete(canvas);
16
+ canvas.renderOnAddRemove = state.renderOnAddRemove;
17
+ canvas.requestRenderAll();
18
+ };
19
+ let result;
20
+ try {
21
+ result = work();
22
+ } catch (error) {
23
+ finish();
24
+ throw error;
25
+ }
26
+ if (result instanceof Promise) return result.finally(finish);
27
+ finish();
28
+ return result;
29
+ }
30
+ //#endregion
31
+ export { batchCanvasUpdates };
@@ -0,0 +1,378 @@
1
+ const require_spatial_index = require("./spatial-index.cjs");
2
+ //#region src/performance/dirty-regions.ts
3
+ const UNBOUNDED_COMPOSITES = /* @__PURE__ */ new Set([
4
+ "source-in",
5
+ "source-out",
6
+ "destination-in",
7
+ "destination-atop",
8
+ "copy"
9
+ ]);
10
+ function union(first, second) {
11
+ const left = Math.min(first.left, second.left);
12
+ const top = Math.min(first.top, second.top);
13
+ return {
14
+ left,
15
+ top,
16
+ width: Math.max(first.left + first.width, second.left + second.width) - left,
17
+ height: Math.max(first.top + first.height, second.top + second.height) - top
18
+ };
19
+ }
20
+ function intersects(first, second, gap = 0) {
21
+ return first.left <= second.left + second.width + gap && second.left <= first.left + first.width + gap && first.top <= second.top + second.height + gap && second.top <= first.top + first.height + gap;
22
+ }
23
+ const area = (rect) => Math.max(0, rect.width) * Math.max(0, rect.height);
24
+ function overhang(object) {
25
+ const self = object;
26
+ let x = 0;
27
+ let y = 0;
28
+ if (self.stroke && self.strokeWidth) x = y = self.strokeWidth * Math.max(1, self.strokeMiterLimit ?? 4) / 2;
29
+ if (typeof self.fontSize === "number" && typeof self.text === "string") {
30
+ x = Math.max(x, self.fontSize * .5);
31
+ y = Math.max(y, self.fontSize * .5);
32
+ }
33
+ for (const child of self._objects ?? []) {
34
+ const inner = overhang(child);
35
+ const childShadow = shadowReach(child);
36
+ x = Math.max(x, inner.x + childShadow.x);
37
+ y = Math.max(y, inner.y + childShadow.y);
38
+ }
39
+ return {
40
+ x,
41
+ y
42
+ };
43
+ }
44
+ function shadowReach(object) {
45
+ const shadow = object.shadow;
46
+ if (!shadow) return {
47
+ x: 0,
48
+ y: 0
49
+ };
50
+ return {
51
+ x: Math.abs(shadow.offsetX) + shadow.blur * 2,
52
+ y: Math.abs(shadow.offsetY) + shadow.blur * 2
53
+ };
54
+ }
55
+ function drawnBounds(object) {
56
+ object.setCoords();
57
+ const box = object.getBoundingRect();
58
+ const scaling = object.getObjectScaling();
59
+ const scaleX = Math.abs(scaling.x);
60
+ const scaleY = Math.abs(scaling.y);
61
+ const reach = overhang(object);
62
+ const shadow = shadowReach(object);
63
+ const nonScaling = Boolean(object.shadow?.nonScaling);
64
+ const grow = Math.max(reach.x * scaleX, reach.y * scaleY) + Math.max(shadow.x, shadow.y) * (nonScaling ? 1 : Math.max(scaleX, scaleY));
65
+ let bounds = {
66
+ left: box.left - grow,
67
+ top: box.top - grow,
68
+ width: box.width + grow * 2,
69
+ height: box.height + grow * 2
70
+ };
71
+ for (const child of object._objects ?? []) bounds = union(bounds, drawnBounds(child));
72
+ return bounds;
73
+ }
74
+ function fingerprint(object, index, into = []) {
75
+ const self = object;
76
+ const matrix = object.calcTransformMatrix();
77
+ const shadow = self.shadow;
78
+ into.push(index, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], self.width, self.height, self.visible, self.opacity, self.strokeWidth, self.globalCompositeOperation, self._element, self.clipPath, shadow, shadow?.blur, shadow?.offsetX, shadow?.offsetY);
79
+ self._objects?.forEach((child, position) => fingerprint(child, position, into));
80
+ return into;
81
+ }
82
+ function isDirty(object) {
83
+ const self = object;
84
+ return Boolean(self.dirty) || (self._objects ?? []).some(isDirty);
85
+ }
86
+ function sameFingerprint(first, second) {
87
+ if (first.length !== second.length) return false;
88
+ for (let index = 0; index < first.length; index += 1) if (first[index] !== second[index]) return false;
89
+ return true;
90
+ }
91
+ function toViewport(rect, matrix) {
92
+ const [a, b, c, d, e, f] = matrix;
93
+ const xs = [];
94
+ const ys = [];
95
+ for (const [x, y] of [
96
+ [rect.left, rect.top],
97
+ [rect.left + rect.width, rect.top],
98
+ [rect.left, rect.top + rect.height],
99
+ [rect.left + rect.width, rect.top + rect.height]
100
+ ]) {
101
+ xs.push(a * x + c * y + e);
102
+ ys.push(b * x + d * y + f);
103
+ }
104
+ const left = Math.min(...xs);
105
+ const top = Math.min(...ys);
106
+ return {
107
+ left,
108
+ top,
109
+ width: Math.max(...xs) - left,
110
+ height: Math.max(...ys) - top
111
+ };
112
+ }
113
+ function toScene(rect, matrix) {
114
+ const [a, b, c, d, e, f] = matrix;
115
+ const determinant = a * d - b * c;
116
+ return toViewport(rect, [
117
+ d / determinant,
118
+ -b / determinant,
119
+ -c / determinant,
120
+ a / determinant,
121
+ (c * f - d * e) / determinant,
122
+ (b * e - a * f) / determinant
123
+ ]);
124
+ }
125
+ function controlsArea(canvas) {
126
+ const active = canvas.getActiveObject?.();
127
+ if (!active) return {
128
+ key: [],
129
+ rect: void 0
130
+ };
131
+ const self = active;
132
+ const points = Object.values(self.oCoords ?? {});
133
+ if (points.length === 0 || !self.hasControls && !self.hasBorders) return {
134
+ key: [active],
135
+ rect: void 0
136
+ };
137
+ const reach = Math.max(self.cornerSize ?? 13, self.touchCornerSize ?? 0) / 2 + (self.padding ?? 0) + 4;
138
+ let left = Infinity;
139
+ let top = Infinity;
140
+ let right = -Infinity;
141
+ let bottom = -Infinity;
142
+ const key = [
143
+ active,
144
+ self.hasControls,
145
+ self.hasBorders
146
+ ];
147
+ for (const point of points) {
148
+ left = Math.min(left, point.x);
149
+ top = Math.min(top, point.y);
150
+ right = Math.max(right, point.x);
151
+ bottom = Math.max(bottom, point.y);
152
+ key.push(point.x, point.y);
153
+ }
154
+ return {
155
+ key,
156
+ rect: {
157
+ left: left - reach,
158
+ top: top - reach,
159
+ width: right - left + reach * 2,
160
+ height: bottom - top + reach * 2
161
+ }
162
+ };
163
+ }
164
+ function mergeRects(rects, limit) {
165
+ if (rects.length > 64) return [rects.reduce(union)];
166
+ const merged = [...rects];
167
+ let changed = true;
168
+ while (changed) {
169
+ changed = false;
170
+ for (let first = 0; first < merged.length && !changed; first += 1) for (let second = first + 1; second < merged.length; second += 1) if (intersects(merged[first], merged[second], 8)) {
171
+ merged[first] = union(merged[first], merged[second]);
172
+ merged.splice(second, 1);
173
+ changed = true;
174
+ break;
175
+ }
176
+ }
177
+ while (merged.length > limit) {
178
+ let best = [0, 1];
179
+ let bestGrowth = Infinity;
180
+ for (let first = 0; first < merged.length; first += 1) for (let second = first + 1; second < merged.length; second += 1) {
181
+ const growth = area(union(merged[first], merged[second])) - area(merged[first]) - area(merged[second]);
182
+ if (growth < bestGrowth) {
183
+ bestGrowth = growth;
184
+ best = [first, second];
185
+ }
186
+ }
187
+ merged[best[0]] = union(merged[best[0]], merged[best[1]]);
188
+ merged.splice(best[1], 1);
189
+ }
190
+ return merged;
191
+ }
192
+ function enableDirtyRegionRendering(canvas, options = {}) {
193
+ const self = canvas;
194
+ const fullRatio = options.fullRedrawRatio ?? .4;
195
+ const maxRegions = Math.max(1, options.maxRegions ?? 4);
196
+ const index = require_spatial_index.createSpatialIndex({ cellSize: options.cellSize ?? 256 });
197
+ const states = /* @__PURE__ */ new Map();
198
+ const unbounded = /* @__PURE__ */ new Set();
199
+ const original = self.renderCanvas;
200
+ const hadOwn = Object.prototype.hasOwnProperty.call(canvas, "renderCanvas");
201
+ const stats = {
202
+ frames: 0,
203
+ fullFrames: 0,
204
+ skippedFrames: 0,
205
+ lastObjectsDrawn: 0,
206
+ lastAreaRatio: 0,
207
+ lastRegions: []
208
+ };
209
+ let frame = 0;
210
+ let forceFull = true;
211
+ let lastPage = [];
212
+ let lastControls = {
213
+ key: [],
214
+ rect: void 0
215
+ };
216
+ const pageKey = () => [
217
+ ...self.viewportTransform,
218
+ self.width,
219
+ self.height,
220
+ self.getRetinaScaling(),
221
+ self.backgroundColor,
222
+ self.backgroundImage,
223
+ self.backgroundImage?.dirty,
224
+ self.overlayColor,
225
+ self.overlayImage,
226
+ self.overlayImage?.dirty,
227
+ self.clipPath,
228
+ self.clipPath?.dirty
229
+ ];
230
+ function track(object, position) {
231
+ const print = fingerprint(object, position);
232
+ const state = states.get(object);
233
+ if (state) {
234
+ state.seen = frame;
235
+ if (!isDirty(object) && sameFingerprint(state.fingerprint, print)) return [];
236
+ }
237
+ const bounds = drawnBounds(object);
238
+ states.set(object, {
239
+ fingerprint: print,
240
+ bounds,
241
+ seen: frame
242
+ });
243
+ index.set(object, bounds);
244
+ const composite = object.globalCompositeOperation ?? "source-over";
245
+ if (UNBOUNDED_COMPOSITES.has(composite)) unbounded.add(object);
246
+ else unbounded.delete(object);
247
+ return state ? [state.bounds, bounds] : [bounds];
248
+ }
249
+ function drawFull(ctx, objects) {
250
+ stats.fullFrames += 1;
251
+ stats.lastObjectsDrawn = objects.length;
252
+ stats.lastAreaRatio = 1;
253
+ stats.lastRegions = [];
254
+ original.call(canvas, ctx, objects);
255
+ }
256
+ function renderCanvas(ctx, objects) {
257
+ if (ctx !== self.getContext()) {
258
+ original.call(canvas, ctx, objects);
259
+ return;
260
+ }
261
+ frame += 1;
262
+ stats.frames += 1;
263
+ const changed = [];
264
+ self._objects.forEach((object, position) => changed.push(...track(object, position)));
265
+ for (const [object, state] of states) {
266
+ if (state.seen === frame) continue;
267
+ changed.push(state.bounds);
268
+ states.delete(object);
269
+ index.delete(object);
270
+ unbounded.delete(object);
271
+ }
272
+ const page = pageKey();
273
+ const controls = controlsArea(self);
274
+ const pageChanged = !sameFingerprint(page, lastPage) || page.length !== lastPage.length;
275
+ const controlsChanged = !sameFingerprint(controls.key, lastControls.key) || controls.key.length !== lastControls.key.length;
276
+ const viewportChanged = [];
277
+ if (controlsChanged) {
278
+ if (lastControls.rect) viewportChanged.push(lastControls.rect);
279
+ if (controls.rect) viewportChanged.push(controls.rect);
280
+ const before = lastControls.key[0];
281
+ const after = controls.key[0];
282
+ for (const object of [before, after]) {
283
+ const state = object ? states.get(object) : void 0;
284
+ if (state) changed.push(state.bounds);
285
+ }
286
+ }
287
+ lastPage = page;
288
+ lastControls = controls;
289
+ if (forceFull || pageChanged) {
290
+ forceFull = false;
291
+ drawFull(ctx, objects);
292
+ return;
293
+ }
294
+ const matrix = self.viewportTransform;
295
+ const retina = self.getRetinaScaling();
296
+ const canvasRect = {
297
+ left: 0,
298
+ top: 0,
299
+ width: self.width,
300
+ height: self.height
301
+ };
302
+ const regions = [];
303
+ for (const rect of [...changed.map((scene) => toViewport(scene, matrix)), ...viewportChanged]) {
304
+ const pad = 2 / retina;
305
+ const left = Math.max(0, Math.floor((rect.left - pad) * retina) / retina);
306
+ const top = Math.max(0, Math.floor((rect.top - pad) * retina) / retina);
307
+ const right = Math.min(self.width, Math.ceil((rect.left + rect.width + pad) * retina) / retina);
308
+ const bottom = Math.min(self.height, Math.ceil((rect.top + rect.height + pad) * retina) / retina);
309
+ if (right > left && bottom > top) regions.push({
310
+ left,
311
+ top,
312
+ width: right - left,
313
+ height: bottom - top
314
+ });
315
+ }
316
+ if (regions.length === 0) {
317
+ stats.skippedFrames += 1;
318
+ stats.lastObjectsDrawn = 0;
319
+ stats.lastAreaRatio = 0;
320
+ stats.lastRegions = [];
321
+ self.fire("before:render", { ctx });
322
+ self.fire("after:render", { ctx });
323
+ return;
324
+ }
325
+ const merged = mergeRects(regions, maxRegions);
326
+ const drawnArea = merged.reduce((sum, rect) => sum + area(rect), 0);
327
+ if (drawnArea > area(canvasRect) * fullRatio) {
328
+ drawFull(ctx, objects);
329
+ return;
330
+ }
331
+ const wanted = new Set(unbounded);
332
+ for (const rect of merged) {
333
+ const pad = 2 / retina;
334
+ const scene = toScene({
335
+ left: rect.left - pad,
336
+ top: rect.top - pad,
337
+ width: rect.width + pad * 2,
338
+ height: rect.height + pad * 2
339
+ }, matrix);
340
+ index.query(scene).forEach((object) => {
341
+ const bounds = states.get(object)?.bounds;
342
+ if (!bounds || intersects(bounds, scene)) wanted.add(object);
343
+ });
344
+ }
345
+ const toDraw = objects.filter((object) => wanted.has(object));
346
+ stats.lastObjectsDrawn = toDraw.length;
347
+ stats.lastAreaRatio = drawnArea / area(canvasRect);
348
+ stats.lastRegions = merged;
349
+ ctx.save();
350
+ ctx.beginPath();
351
+ for (const rect of merged) ctx.rect(rect.left, rect.top, rect.width, rect.height);
352
+ ctx.clip();
353
+ try {
354
+ original.call(canvas, ctx, toDraw);
355
+ } finally {
356
+ ctx.restore();
357
+ }
358
+ }
359
+ self.renderCanvas = renderCanvas;
360
+ return {
361
+ invalidate() {
362
+ forceFull = true;
363
+ },
364
+ stats: () => ({
365
+ ...stats,
366
+ lastRegions: [...stats.lastRegions]
367
+ }),
368
+ disable() {
369
+ if (self.renderCanvas !== renderCanvas) return;
370
+ if (hadOwn) self.renderCanvas = original;
371
+ else delete canvas.renderCanvas;
372
+ index.clear();
373
+ states.clear();
374
+ }
375
+ };
376
+ }
377
+ //#endregion
378
+ exports.enableDirtyRegionRendering = enableDirtyRegionRendering;
@@ -0,0 +1,23 @@
1
+ import { Rect } from "./spatial-index.cjs";
2
+ import { StaticCanvas } from "fabric";
3
+ //#region src/performance/dirty-regions.d.ts
4
+ export interface DirtyRegionOptions {
5
+ fullRedrawRatio?: number;
6
+ maxRegions?: number;
7
+ cellSize?: number;
8
+ }
9
+ export interface DirtyRegionStats {
10
+ frames: number;
11
+ fullFrames: number;
12
+ skippedFrames: number;
13
+ lastObjectsDrawn: number;
14
+ lastAreaRatio: number;
15
+ lastRegions: Rect[];
16
+ }
17
+ export interface DirtyRegionRenderer {
18
+ invalidate(): void;
19
+ stats(): DirtyRegionStats;
20
+ disable(): void;
21
+ }
22
+ export declare function enableDirtyRegionRendering(canvas: StaticCanvas, options?: DirtyRegionOptions): DirtyRegionRenderer;
23
+ //#endregion
@@ -0,0 +1,23 @@
1
+ import { Rect as Rect$1 } from "./spatial-index.js";
2
+ import { StaticCanvas } from "fabric";
3
+ //#region src/performance/dirty-regions.d.ts
4
+ export interface DirtyRegionOptions {
5
+ fullRedrawRatio?: number;
6
+ maxRegions?: number;
7
+ cellSize?: number;
8
+ }
9
+ export interface DirtyRegionStats {
10
+ frames: number;
11
+ fullFrames: number;
12
+ skippedFrames: number;
13
+ lastObjectsDrawn: number;
14
+ lastAreaRatio: number;
15
+ lastRegions: Rect$1[];
16
+ }
17
+ export interface DirtyRegionRenderer {
18
+ invalidate(): void;
19
+ stats(): DirtyRegionStats;
20
+ disable(): void;
21
+ }
22
+ export declare function enableDirtyRegionRendering(canvas: StaticCanvas, options?: DirtyRegionOptions): DirtyRegionRenderer;
23
+ //#endregion