ducjs 3.1.1 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p align="center">
4
4
  <br/>
5
- <a href="https://duc.ducflair.com" target="_blank"><img width="256px" src="https://raw.githubusercontent.com/ducflair/assets/refs/heads/main/src/duc/duc-extended.png" /></a>
5
+ <a href="https://duc.ducflair.com" target="_blank"><img width="256px" src="https://cdn.jsdelivr.net/gh/ducflair/assets@main/src/duc/duc-extended.png" /></a>
6
6
  <p align="center">2D CAD File Format</p>
7
7
  <p align="center" style="align: center;">
8
8
  <a href="https://www.npmjs.com/package/ducjs"><img src="https://shields.io/badge/NPM-cc3534?logo=Npm&logoColor=white&style=round-square" alt="NPM" /></a>
@@ -279,7 +279,7 @@ export function readVersionGraph(duc_buf) {
279
279
  * @returns {number}
280
280
  */
281
281
  export function getCurrentSchemaVersion() {
282
- return 3000001;
282
+ return 3000002;
283
283
  }
284
284
 
285
285
  /**
Binary file
package/dist/enums.d.ts CHANGED
@@ -95,11 +95,6 @@ export declare enum IMAGE_STATUS {
95
95
  SAVED = 11,
96
96
  ERROR = 12
97
97
  }
98
- export declare enum PRUNING_LEVEL {
99
- CONSERVATIVE = 10,
100
- BALANCED = 20,
101
- AGGRESSIVE = 30
102
- }
103
98
  export declare enum BOOLEAN_OPERATION {
104
99
  UNION = 10,
105
100
  SUBTRACT = 11,
package/dist/enums.js CHANGED
@@ -109,12 +109,6 @@ export var IMAGE_STATUS;
109
109
  IMAGE_STATUS[IMAGE_STATUS["SAVED"] = 11] = "SAVED";
110
110
  IMAGE_STATUS[IMAGE_STATUS["ERROR"] = 12] = "ERROR";
111
111
  })(IMAGE_STATUS || (IMAGE_STATUS = {}));
112
- export var PRUNING_LEVEL;
113
- (function (PRUNING_LEVEL) {
114
- PRUNING_LEVEL[PRUNING_LEVEL["CONSERVATIVE"] = 10] = "CONSERVATIVE";
115
- PRUNING_LEVEL[PRUNING_LEVEL["BALANCED"] = 20] = "BALANCED";
116
- PRUNING_LEVEL[PRUNING_LEVEL["AGGRESSIVE"] = 30] = "AGGRESSIVE";
117
- })(PRUNING_LEVEL || (PRUNING_LEVEL = {}));
118
112
  export var BOOLEAN_OPERATION;
119
113
  (function (BOOLEAN_OPERATION) {
120
114
  BOOLEAN_OPERATION[BOOLEAN_OPERATION["UNION"] = 10] = "UNION";
@@ -1,4 +1,4 @@
1
- import type { DucExternalFile, DucExternalFiles, ExternalFileRevision } from "./types";
1
+ import type { DucExternalFile, DucExternalFiles, ExternalFileLoaded, ResolvedFileData, ExternalFilesData } from "./types";
2
2
  export type LazyFileMetadata = {
3
3
  id: string;
4
4
  mimeType: string;
@@ -27,19 +27,21 @@ export declare class LazyExternalFileStore {
27
27
  /** Get metadata for all files. */
28
28
  getAllMetadata(): LazyFileMetadata[];
29
29
  /** Fetch the full file (including data blobs for all revisions) for a specific file. */
30
- getFile(fileId: string): DucExternalFile | null;
30
+ getFile(fileId: string): ExternalFileLoaded | null;
31
31
  /** Get the active revision data for a specific file. */
32
- getFileData(fileId: string): ExternalFileRevision | null;
32
+ getFileData(fileId: string): ResolvedFileData | null;
33
33
  /** Fetch active revision data and return a copy of the data buffer (safe for transfer). */
34
- getFileDataCopy(fileId: string): ExternalFileRevision | null;
34
+ getFileDataCopy(fileId: string): ResolvedFileData | null;
35
35
  /** Add a file at runtime (not persisted in .duc until next serialize). */
36
- addRuntimeFile(fileId: string, file: DucExternalFile): void;
36
+ addRuntimeFile(fileId: string, file: DucExternalFile, data: Record<string, Uint8Array>): void;
37
37
  /** Remove a runtime file. */
38
38
  removeRuntimeFile(fileId: string): boolean;
39
- /** Export all files eagerly as a DucExternalFiles record. */
39
+ /** Export all files metadata as a DucExternalFiles record. */
40
40
  toExternalFiles(): DucExternalFiles;
41
+ /** Export all revision data blobs as an ExternalFilesData record. */
42
+ toExternalFilesData(): ExternalFilesData;
41
43
  /** Merge files from another source. Adds missing files and merges new revisions into existing ones. */
42
- mergeFiles(files: DucExternalFiles): void;
44
+ mergeFiles(files: DucExternalFiles, filesData?: ExternalFilesData): void;
43
45
  /** Release the underlying buffer to free memory. */
44
46
  release(): void;
45
47
  private getMetadataMap;
@@ -1,3 +1,14 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { wasmGetExternalFile, wasmListExternalFiles } from "./wasm";
2
13
  /**
3
14
  * Provides lazy access to external files embedded inside a `.duc` buffer.
@@ -76,11 +87,16 @@ export class LazyExternalFileStore {
76
87
  }
77
88
  /** Get the active revision data for a specific file. */
78
89
  getFileData(fileId) {
79
- var _a;
80
- const file = this.getFile(fileId);
81
- if (!file)
90
+ const loaded = this.getFile(fileId);
91
+ if (!loaded)
92
+ return null;
93
+ const meta = loaded.revisions[loaded.activeRevisionId];
94
+ if (!meta)
82
95
  return null;
83
- return (_a = file.revisions[file.activeRevisionId]) !== null && _a !== void 0 ? _a : null;
96
+ const dataBlob = loaded.data[loaded.activeRevisionId];
97
+ if (!dataBlob)
98
+ return null;
99
+ return { data: dataBlob, mimeType: meta.mimeType };
84
100
  }
85
101
  /** Fetch active revision data and return a copy of the data buffer (safe for transfer). */
86
102
  getFileDataCopy(fileId) {
@@ -90,53 +106,97 @@ export class LazyExternalFileStore {
90
106
  return Object.assign(Object.assign({}, data), { data: new Uint8Array(data.data) });
91
107
  }
92
108
  /** Add a file at runtime (not persisted in .duc until next serialize). */
93
- addRuntimeFile(fileId, file) {
94
- this.runtimeFiles.set(fileId, file);
109
+ addRuntimeFile(fileId, file, data) {
110
+ this.runtimeFiles.set(fileId, Object.assign(Object.assign({}, file), { data }));
95
111
  }
96
112
  /** Remove a runtime file. */
97
113
  removeRuntimeFile(fileId) {
98
114
  return this.runtimeFiles.delete(fileId);
99
115
  }
100
- /** Export all files eagerly as a DucExternalFiles record. */
116
+ /** Export all files metadata as a DucExternalFiles record. */
101
117
  toExternalFiles() {
102
118
  const result = {};
103
119
  if (this.buffer) {
104
120
  for (const [id] of this.getMetadataMap()) {
105
- const file = this.getFile(id);
106
- if (file) {
121
+ const loaded = this.getFile(id);
122
+ if (loaded) {
123
+ const { data: _ } = loaded, file = __rest(loaded, ["data"]);
107
124
  result[id] = file;
108
125
  }
109
126
  }
110
127
  }
111
- for (const [id, file] of this.runtimeFiles) {
128
+ for (const [id, loaded] of this.runtimeFiles) {
129
+ const { data: _ } = loaded, file = __rest(loaded, ["data"]);
112
130
  result[id] = file;
113
131
  }
114
132
  return result;
115
133
  }
134
+ /** Export all revision data blobs as an ExternalFilesData record. */
135
+ toExternalFilesData() {
136
+ const result = {};
137
+ if (this.buffer) {
138
+ for (const [id] of this.getMetadataMap()) {
139
+ const loaded = this.getFile(id);
140
+ if (loaded) {
141
+ for (const [revId, blob] of Object.entries(loaded.data)) {
142
+ result[revId] = blob;
143
+ }
144
+ }
145
+ }
146
+ }
147
+ for (const [, loaded] of this.runtimeFiles) {
148
+ for (const [revId, blob] of Object.entries(loaded.data)) {
149
+ result[revId] = blob;
150
+ }
151
+ }
152
+ return result;
153
+ }
116
154
  /** Merge files from another source. Adds missing files and merges new revisions into existing ones. */
117
- mergeFiles(files) {
155
+ mergeFiles(files, filesData) {
118
156
  var _a, _b, _c;
119
157
  for (const [id, file] of Object.entries(files)) {
120
- if (!this.has(id)) {
121
- this.runtimeFiles.set(id, file);
122
- continue;
123
- }
124
158
  const existing = (_a = this.runtimeFiles.get(id)) !== null && _a !== void 0 ? _a : this.getFile(id);
125
159
  if (!existing) {
126
- this.runtimeFiles.set(id, file);
160
+ const dataMap = {};
161
+ if (filesData) {
162
+ for (const revId of Object.keys(file.revisions)) {
163
+ if (filesData[revId]) {
164
+ dataMap[revId] = filesData[revId];
165
+ }
166
+ }
167
+ }
168
+ this.runtimeFiles.set(id, Object.assign(Object.assign({}, file), { data: dataMap }));
127
169
  continue;
128
170
  }
129
- // Merge: add any new revisions that don't exist yet, and update metadata
130
171
  let merged = false;
131
172
  const mergedRevisions = Object.assign({}, existing.revisions);
173
+ const mergedData = Object.assign({}, existing.data);
132
174
  for (const [revId, rev] of Object.entries(file.revisions)) {
133
175
  if (!mergedRevisions[revId]) {
176
+ mergedRevisions[revId] = rev;
177
+ if (filesData === null || filesData === void 0 ? void 0 : filesData[revId]) {
178
+ mergedData[revId] = filesData[revId];
179
+ }
180
+ merged = true;
181
+ continue;
182
+ }
183
+ if ((filesData === null || filesData === void 0 ? void 0 : filesData[revId]) && mergedData[revId] !== filesData[revId]) {
184
+ mergedData[revId] = filesData[revId];
185
+ merged = true;
186
+ }
187
+ if (rev.sizeBytes !== mergedRevisions[revId].sizeBytes ||
188
+ rev.lastRetrieved !== mergedRevisions[revId].lastRetrieved ||
189
+ rev.created !== mergedRevisions[revId].created ||
190
+ rev.mimeType !== mergedRevisions[revId].mimeType ||
191
+ rev.sourceName !== mergedRevisions[revId].sourceName ||
192
+ rev.message !== mergedRevisions[revId].message) {
134
193
  mergedRevisions[revId] = rev;
135
194
  merged = true;
136
195
  }
137
196
  }
138
- if (merged || file.updated > existing.updated) {
139
- this.runtimeFiles.set(id, Object.assign(Object.assign({}, existing), { activeRevisionId: file.activeRevisionId, updated: Math.max(file.updated, existing.updated), version: Math.max((_b = file.version) !== null && _b !== void 0 ? _b : 0, (_c = existing.version) !== null && _c !== void 0 ? _c : 0), revisions: mergedRevisions }));
197
+ const activeRevisionChanged = file.activeRevisionId !== existing.activeRevisionId;
198
+ if (merged || activeRevisionChanged || file.updated > existing.updated) {
199
+ this.runtimeFiles.set(id, Object.assign(Object.assign({}, existing), { activeRevisionId: file.activeRevisionId, updated: Math.max(file.updated, existing.updated), version: Math.max((_b = file.version) !== null && _b !== void 0 ? _b : 0, (_c = existing.version) !== null && _c !== void 0 ? _c : 0), revisions: mergedRevisions, data: mergedData }));
140
200
  }
141
201
  }
142
202
  }
package/dist/parse.js CHANGED
@@ -80,7 +80,9 @@ export function parseDucLazy(buffer, elementsConfig, restoreConfig) {
80
80
  const originalVG = data.versionGraph;
81
81
  const lazyFileStore = new LazyExternalFileStore(buffer);
82
82
  const files = {};
83
- const restored = restore(Object.assign(Object.assign({}, data), { files,
83
+ const filesData = {};
84
+ const restored = restore(Object.assign(Object.assign({}, data), { files,
85
+ filesData,
84
86
  // Preserve versionGraph from Rust separately; do not run it through restore()
85
87
  versionGraph: undefined }), elementsConfig !== null && elementsConfig !== void 0 ? elementsConfig : { syncInvalidIndices: (els) => els }, restoreConfig);
86
88
  restored.lazyFileStore = lazyFileStore;
@@ -1,5 +1,5 @@
1
1
  import { BLENDING } from "../enums";
2
- import type { Checkpoint, Delta, Dictionary, DucExternalFiles, DucGlobalState, ImportedDataState, LibraryItems, PrecisionValue, Scope, VersionGraph } from "../types";
2
+ import type { Checkpoint, Delta, Dictionary, DucExternalFiles, DucGlobalState, ExternalFilesData, ImportedDataState, LibraryItems, PrecisionValue, Scope, VersionGraph } from "../types";
3
3
  import { DucLocalState } from "../types";
4
4
  import type { _DucStackBase, BezierMirroring, DucBlock, DucBlockCollection, DucBlockInstance, DucElement, DucGroup, DucHead, DucLayer, DucRegion, ElementBackground, ElementContentBase, ElementStroke, ExternalFileId, FillStyle, ImageStatus, LineHead, OrderedDucElement, StrokeCap, StrokeJoin, StrokePreference, StrokeSidePreference, StrokeStyle, TextAlign, VerticalAlign } from "../types/elements";
5
5
  import { Percentage, Radian } from "../types/geometryTypes";
@@ -13,6 +13,7 @@ export type RestoredDataState = {
13
13
  localState: RestoredLocalState;
14
14
  globalState: DucGlobalState;
15
15
  files: DucExternalFiles;
16
+ filesData: ExternalFilesData;
16
17
  blocks: DucBlock[];
17
18
  blockInstances: DucBlockInstance[];
18
19
  blockCollections: DucBlockCollection[];
@@ -47,6 +48,11 @@ export type RestoreConfig = {
47
48
  };
48
49
  export declare const restore: (data: ImportedDataState | null, elementsConfig: ElementsConfig, restoreConfig?: RestoreConfig) => RestoredDataState;
49
50
  export declare const restoreFiles: (importedFiles: unknown) => DucExternalFiles;
51
+ /**
52
+ * Restore external file data blobs from the split `filesData` field.
53
+ * Also extracts data from legacy inline `revisions[].data` when present.
54
+ */
55
+ export declare const restoreFilesData: (importedFilesData: unknown, importedFiles: unknown) => ExternalFilesData;
50
56
  export declare const restoreDictionary: (importedDictionary: unknown) => Dictionary;
51
57
  /**
52
58
  * Restores the groups array from imported data, ensuring each item
@@ -1,6 +1,6 @@
1
1
  import { nanoid } from "nanoid";
2
2
  import tinycolor from "tinycolor2";
3
- import { BEZIER_MIRRORING, BLENDING, BOOLEAN_OPERATION, ELEMENT_CONTENT_PREFERENCE, IMAGE_STATUS, LINE_HEAD, PRUNING_LEVEL, STROKE_CAP, STROKE_JOIN, STROKE_PLACEMENT, STROKE_PREFERENCE, STROKE_SIDE_PREFERENCE, TEXT_ALIGN, VERTICAL_ALIGN, } from "../enums";
3
+ import { BEZIER_MIRRORING, BLENDING, BOOLEAN_OPERATION, ELEMENT_CONTENT_PREFERENCE, IMAGE_STATUS, LINE_HEAD, STROKE_CAP, STROKE_JOIN, STROKE_PLACEMENT, STROKE_PREFERENCE, STROKE_SIDE_PREFERENCE, TEXT_ALIGN, VERTICAL_ALIGN, } from "../enums";
4
4
  import { getPrecisionScope } from "../technical/measurements";
5
5
  import { getPrecisionValueFromRaw, getPrecisionValueFromScoped, NEUTRAL_SCOPE, ScaleFactors } from "../technical/scopes";
6
6
  import { base64ToUint8Array, getDefaultGlobalState, getDefaultLocalState, getZoom, isEncodedFunctionString, isFiniteNumber, reviveEncodedFunction, } from "../utils";
@@ -37,11 +37,11 @@ export const restore = (data, elementsConfig, restoreConfig = {}) => {
37
37
  localState: restoredLocalState,
38
38
  globalState: restoredGlobalState,
39
39
  files: restoreFiles(data === null || data === void 0 ? void 0 : data.files),
40
+ filesData: restoreFilesData(data === null || data === void 0 ? void 0 : data.filesData, data === null || data === void 0 ? void 0 : data.files),
40
41
  id: restoredId,
41
42
  };
42
43
  };
43
44
  export const restoreFiles = (importedFiles) => {
44
- var _a, _b;
45
45
  if (!importedFiles || typeof importedFiles !== "object") {
46
46
  return {};
47
47
  }
@@ -70,20 +70,17 @@ export const restoreFiles = (importedFiles) => {
70
70
  const r = rev;
71
71
  const revId = isValidString(r.id);
72
72
  const mimeType = isValidString(r.mimeType);
73
- const dataSource = (_a = r.data) !== null && _a !== void 0 ? _a : r.dataURL;
74
- const data = isValidUint8Array(dataSource);
75
- if (!revId || !mimeType || !data)
73
+ if (!revId || !mimeType)
76
74
  continue;
77
75
  restoredRevisions[revKey] = {
78
76
  id: revId,
79
- sizeBytes: isFiniteNumber(r.sizeBytes) ? r.sizeBytes : data.byteLength,
77
+ sizeBytes: isFiniteNumber(r.sizeBytes) ? r.sizeBytes : 0,
80
78
  checksum: isValidString(r.checksum) || undefined,
81
79
  sourceName: isValidString(r.sourceName) || undefined,
82
80
  mimeType,
83
81
  message: isValidString(r.message) || undefined,
84
82
  created: isFiniteNumber(r.created) ? r.created : Date.now(),
85
83
  lastRetrieved: isFiniteNumber(r.lastRetrieved) ? r.lastRetrieved : undefined,
86
- data,
87
84
  };
88
85
  }
89
86
  if (Object.keys(restoredRevisions).length === 0)
@@ -99,15 +96,12 @@ export const restoreFiles = (importedFiles) => {
99
96
  }
100
97
  // Legacy flat format: DucExternalFileData — wrap in a single-revision DucExternalFile.
101
98
  let legacyData = fd;
102
- // Handle the nested { key, value: { ... } } structure from old Rust serde output.
103
99
  if (fd.value && typeof fd.value === "object") {
104
100
  legacyData = fd.value;
105
101
  }
106
102
  const id = isValidExternalFileId(legacyData.id);
107
103
  const mimeType = isValidString(legacyData.mimeType);
108
- const dataSource = (_b = legacyData.data) !== null && _b !== void 0 ? _b : legacyData.dataURL;
109
- const data = isValidUint8Array(dataSource);
110
- if (!id || !mimeType || !data)
104
+ if (!id || !mimeType)
111
105
  continue;
112
106
  const revId = `${id}_rev1`;
113
107
  const created = isFiniteNumber(legacyData.created) ? legacyData.created : Date.now();
@@ -119,19 +113,87 @@ export const restoreFiles = (importedFiles) => {
119
113
  revisions: {
120
114
  [revId]: {
121
115
  id: revId,
122
- sizeBytes: data.byteLength,
116
+ sizeBytes: 0,
123
117
  mimeType,
124
118
  created,
125
119
  lastRetrieved: isFiniteNumber(legacyData.lastRetrieved)
126
120
  ? legacyData.lastRetrieved
127
121
  : undefined,
128
- data,
129
122
  },
130
123
  },
131
124
  };
132
125
  }
133
126
  return restoredFiles;
134
127
  };
128
+ /**
129
+ * Restore external file data blobs from the split `filesData` field.
130
+ * Also extracts data from legacy inline `revisions[].data` when present.
131
+ */
132
+ export const restoreFilesData = (importedFilesData, importedFiles) => {
133
+ var _a, _b;
134
+ const result = {};
135
+ // 1. Restore from the explicit filesData field (new format)
136
+ if (importedFilesData && typeof importedFilesData === "object") {
137
+ const fd = importedFilesData;
138
+ for (const revId in fd) {
139
+ if (!Object.prototype.hasOwnProperty.call(fd, revId))
140
+ continue;
141
+ const data = isValidUint8Array(fd[revId]);
142
+ if (data) {
143
+ result[revId] = data;
144
+ }
145
+ }
146
+ }
147
+ // 2. Extract data from legacy inline revisions (backward compatibility)
148
+ if (importedFiles && typeof importedFiles === "object") {
149
+ const files = importedFiles;
150
+ for (const key in files) {
151
+ if (!Object.prototype.hasOwnProperty.call(files, key))
152
+ continue;
153
+ const fileData = files[key];
154
+ if (!fileData || typeof fileData !== "object")
155
+ continue;
156
+ const fd = fileData;
157
+ if (fd.revisions && typeof fd.revisions === "object") {
158
+ const rawRevisions = fd.revisions;
159
+ for (const revKey in rawRevisions) {
160
+ if (!Object.prototype.hasOwnProperty.call(rawRevisions, revKey))
161
+ continue;
162
+ if (result[revKey])
163
+ continue; // filesData takes precedence
164
+ const rev = rawRevisions[revKey];
165
+ if (!rev || typeof rev !== "object")
166
+ continue;
167
+ const r = rev;
168
+ const dataSource = (_a = r.data) !== null && _a !== void 0 ? _a : r.dataURL;
169
+ const data = isValidUint8Array(dataSource);
170
+ if (data) {
171
+ result[revKey] = data;
172
+ }
173
+ }
174
+ }
175
+ else {
176
+ // Legacy flat format
177
+ let legacyData = fd;
178
+ if (fd.value && typeof fd.value === "object") {
179
+ legacyData = fd.value;
180
+ }
181
+ const id = isValidExternalFileId(legacyData.id);
182
+ if (!id)
183
+ continue;
184
+ const revId = `${id}_rev1`;
185
+ if (result[revId])
186
+ continue;
187
+ const dataSource = (_b = legacyData.data) !== null && _b !== void 0 ? _b : legacyData.dataURL;
188
+ const data = isValidUint8Array(dataSource);
189
+ if (data) {
190
+ result[revId] = data;
191
+ }
192
+ }
193
+ }
194
+ }
195
+ return result;
196
+ };
135
197
  export const restoreDictionary = (importedDictionary) => {
136
198
  if (!importedDictionary || typeof importedDictionary !== "object") {
137
199
  return {};
@@ -377,10 +439,7 @@ const restoreBlockMetadata = (metadata) => {
377
439
  export const restoreGlobalState = (importedState = {}) => {
378
440
  var _a, _b, _c;
379
441
  const defaults = getDefaultGlobalState();
380
- return Object.assign(Object.assign({}, defaults), { name: (_a = importedState === null || importedState === void 0 ? void 0 : importedState.name) !== null && _a !== void 0 ? _a : defaults.name, viewBackgroundColor: (_b = importedState === null || importedState === void 0 ? void 0 : importedState.viewBackgroundColor) !== null && _b !== void 0 ? _b : defaults.viewBackgroundColor, mainScope: (_c = isValidAppStateScopeValue(importedState === null || importedState === void 0 ? void 0 : importedState.mainScope)) !== null && _c !== void 0 ? _c : defaults.mainScope, scopeExponentThreshold: isValidAppStateScopeExponentThresholdValue(importedState === null || importedState === void 0 ? void 0 : importedState.scopeExponentThreshold, defaults.scopeExponentThreshold), pruningLevel: (importedState === null || importedState === void 0 ? void 0 : importedState.pruningLevel) &&
381
- Object.values(PRUNING_LEVEL).includes(importedState.pruningLevel)
382
- ? importedState.pruningLevel
383
- : PRUNING_LEVEL.BALANCED });
442
+ return Object.assign(Object.assign({}, defaults), { name: (_a = importedState === null || importedState === void 0 ? void 0 : importedState.name) !== null && _a !== void 0 ? _a : defaults.name, viewBackgroundColor: (_b = importedState === null || importedState === void 0 ? void 0 : importedState.viewBackgroundColor) !== null && _b !== void 0 ? _b : defaults.viewBackgroundColor, mainScope: (_c = isValidAppStateScopeValue(importedState === null || importedState === void 0 ? void 0 : importedState.mainScope)) !== null && _c !== void 0 ? _c : defaults.mainScope, scopeExponentThreshold: isValidAppStateScopeExponentThresholdValue(importedState === null || importedState === void 0 ? void 0 : importedState.scopeExponentThreshold, defaults.scopeExponentThreshold) });
384
443
  };
385
444
  /**
386
445
  * Restores the user's local session state from imported data.
@@ -447,9 +506,6 @@ export const restoreVersionGraph = (importedGraph) => {
447
506
  chainCount: isFiniteNumber(importedMetadata === null || importedMetadata === void 0 ? void 0 : importedMetadata.chainCount) && importedMetadata.chainCount >= 1
448
507
  ? importedMetadata.chainCount
449
508
  : 1,
450
- lastPruned: isFiniteNumber(importedMetadata === null || importedMetadata === void 0 ? void 0 : importedMetadata.lastPruned)
451
- ? importedMetadata.lastPruned
452
- : 0,
453
509
  totalSize: isFiniteNumber(importedMetadata === null || importedMetadata === void 0 ? void 0 : importedMetadata.totalSize) &&
454
510
  importedMetadata.totalSize >= 0
455
511
  ? importedMetadata.totalSize
package/dist/transform.js CHANGED
@@ -395,7 +395,7 @@ function normalizeLocalStateForRust(localState) {
395
395
  function normalizeGlobalStateForRust(globalState) {
396
396
  if (!globalState || typeof globalState !== "object")
397
397
  return globalState;
398
- return Object.assign(Object.assign({}, globalState), { scopeExponentThreshold: toInteger(globalState.scopeExponentThreshold, 4), pruningLevel: toInteger(globalState.pruningLevel, 20) });
398
+ return Object.assign(Object.assign({}, globalState), { scopeExponentThreshold: toInteger(globalState.scopeExponentThreshold, 4) });
399
399
  }
400
400
  function easingFnToName(fn) {
401
401
  var _a;
@@ -2,11 +2,10 @@ export * from "./elements";
2
2
  export * from "./geometryTypes";
3
3
  export * from "./typeChecks";
4
4
  export * from "./utility-types";
5
- import { PRUNING_LEVEL } from "../enums";
6
5
  import { SupportedMeasures } from "../technical/scopes";
7
- import { DucBindableElement, DucBlock, DucBlockCollection, DucBlockInstance, DucElement, DucElementType, DucGroup, DucIframeLikeElement, DucLayer, DucLinearElement, DucPoint, DucRegion, DucTextElement, ElementBackground, ElementStroke, ExternalFileId, FontFamilyValues, LineHead, NonDeleted, TextAlign } from "./elements";
8
- import { GeometricPoint, Percentage, Radian } from "./geometryTypes";
9
- import { MarkOptional, MaybePromise, ValueOf } from "./utility-types";
6
+ import { DucBindableElement, DucBlock, DucBlockCollection, DucBlockInstance, DucElement, DucElementType, DucGroup, DucIframeLikeElement, DucLayer, DucLinearElement, DucRegion, DucTextElement, ElementBackground, ElementStroke, ExternalFileId, FontFamilyValues, LineHead, NonDeleted, TextAlign } from "./elements";
7
+ import { Percentage } from "./geometryTypes";
8
+ import { MarkOptional, MaybePromise } from "./utility-types";
10
9
  /**
11
10
  * Root data structure for the stored data state
12
11
  */
@@ -28,6 +27,8 @@ export interface ExportedDataState {
28
27
  regions: readonly DucRegion[];
29
28
  layers: readonly DucLayer[];
30
29
  files: DucExternalFiles | undefined;
30
+ /** Revision data blobs keyed by revision id. Separated from metadata for lazy loading. */
31
+ filesData: ExternalFilesData | undefined;
31
32
  /** In case it is needed to embed the version control into the file format */
32
33
  versionGraph: VersionGraph | undefined;
33
34
  /** Actual file id */
@@ -51,35 +52,8 @@ export type Identifier = {
51
52
  export type Dictionary = {
52
53
  [key: string]: string;
53
54
  };
54
- export type DucView = {
55
- scrollX: PrecisionValue;
56
- scrollY: PrecisionValue;
57
- zoom: Zoom;
58
- twistAngle: Radian;
59
- /** The specific spot on that plane that you want to be in the middle of your screen when this view is active */
60
- centerPoint: DucPoint;
61
- scope: Scope;
62
- };
63
- /**
64
- * Defines a 2D User Coordinate System (UCS), a movable coordinate system
65
- * that establishes a local origin and rotation for drawing. All coordinates
66
- * within this UCS are relative to its origin and angle.
67
- */
68
- export type DucUcs = {
69
- /**
70
- * The origin point of the UCS in World Coordinate System (WCS) coordinates.
71
- * This defines the (0,0) point of the new local system.
72
- */
73
- origin: GeometricPoint;
74
- /**
75
- * The rotation angle of the UCS's X-axis, measured in radians,
76
- * relative to the World Coordinate System's X-axis.
77
- * An angle of 0 means the UCS is aligned with the WCS.
78
- */
79
- angle: Radian;
80
- };
81
55
  export type Scope = SupportedMeasures;
82
- export type ExternalFileRevision = {
56
+ export type ExternalFileRevisionMeta = {
83
57
  id: string;
84
58
  sizeBytes: number;
85
59
  /** Content hash for integrity checks and optional deduplication. */
@@ -96,19 +70,31 @@ export type ExternalFileRevision = {
96
70
  * the scene. Used to determine whether to delete unused files from storage.
97
71
  */
98
72
  lastRetrieved?: number;
99
- /** The actual file content bytes. */
73
+ };
74
+ /**
75
+ * Minimal resolved file data for rendering — just the bytes and their MIME type.
76
+ * Used by renderers and export pipelines that need active revision data.
77
+ */
78
+ export type ResolvedFileData = {
100
79
  data: Uint8Array;
80
+ mimeType: string;
101
81
  };
102
82
  export type DucExternalFile = {
103
83
  id: ExternalFileId;
104
84
  activeRevisionId: string;
105
85
  /** Epoch ms when the logical file was last mutated (revision added or active changed). */
106
86
  updated: number;
107
- /** All revisions of this file, keyed by their id. */
108
- revisions: Record<string, ExternalFileRevision>;
87
+ /** All revisions of this file, keyed by their id (metadata only, no data blobs). */
88
+ revisions: Record<string, ExternalFileRevisionMeta>;
109
89
  version?: number;
110
90
  };
111
91
  export type DucExternalFiles = Record<ExternalFileId, DucExternalFile>;
92
+ /** Revision data blobs keyed by revision id. */
93
+ export type ExternalFilesData = Record<string, Uint8Array>;
94
+ /** A fully-loaded external file including its revision data blobs. */
95
+ export type ExternalFileLoaded = DucExternalFile & {
96
+ data: Record<string, Uint8Array>;
97
+ };
112
98
  export type SuggestedBinding = NonDeleted<DucBindableElement> | SuggestedPointBinding;
113
99
  export type SuggestedPointBinding = [
114
100
  NonDeleted<DucLinearElement>,
@@ -140,8 +126,6 @@ export type DucGlobalState = {
140
126
  * This value defines a +/- tolerance range around the exponent of the current scope.
141
127
  */
142
128
  scopeExponentThreshold: number;
143
- /** The level of pruning to the versions from the version graph. */
144
- pruningLevel: PruningLevel;
145
129
  };
146
130
  export type DucLocalState = {
147
131
  /**
@@ -277,7 +261,6 @@ export type EmbedsValidationStatus = Map<DucIframeLikeElement["id"], boolean>;
277
261
  export type ElementsPendingErasure = Set<DucElement["id"]>;
278
262
  export type PendingDucElements = DucElement[];
279
263
  export type VersionId = string;
280
- export type PruningLevel = ValueOf<typeof PRUNING_LEVEL>;
281
264
  export interface VersionBase {
282
265
  id: VersionId;
283
266
  parentId: VersionId | null;
@@ -323,7 +306,6 @@ export interface VersionGraphMetadata {
323
306
  currentVersion: number;
324
307
  currentSchemaVersion: number;
325
308
  chainCount: number;
326
- lastPruned: number;
327
309
  totalSize: number;
328
310
  }
329
311
  export interface VersionGraph {
@@ -17,7 +17,6 @@ export declare const ELEMENT_TRANSLATE_AMOUNT = 1;
17
17
  export declare const TEXT_TO_CENTER_SNAP_THRESHOLD = 30;
18
18
  export declare const SHIFT_LOCKING_ANGLE: number;
19
19
  export declare const DEFAULT_GRID_SIZE = 1;
20
- export declare const DEFAULT_GRID_STEP = 20;
21
20
  export declare const SVG_NS = "http://www.w3.org/2000/svg";
22
21
  export declare const IMAGE_MIME_TYPES: {
23
22
  readonly svg: "image/svg+xml";
@@ -20,7 +20,6 @@ export const ELEMENT_TRANSLATE_AMOUNT = 1;
20
20
  export const TEXT_TO_CENTER_SNAP_THRESHOLD = 30;
21
21
  export const SHIFT_LOCKING_ANGLE = Math.PI / 12;
22
22
  export const DEFAULT_GRID_SIZE = 1;
23
- export const DEFAULT_GRID_STEP = 20;
24
23
  export const SVG_NS = "http://www.w3.org/2000/svg";
25
24
  export const IMAGE_MIME_TYPES = {
26
25
  svg: "image/svg+xml",
@@ -1,8 +1,6 @@
1
1
  import type { NormalizedZoomValue, ScopedValue } from "../types";
2
2
  import { DucElement, FixedPoint } from "../types/elements";
3
3
  export declare const getNormalizedZoom: (zoom: number) => NormalizedZoomValue;
4
- export declare const getNormalizedGridSize: (gridStep: number) => number;
5
- export declare const getNormalizedGridStep: (gridStep: number) => number;
6
4
  export declare const normalizeFixedPoint: <T extends FixedPoint | null>(fixedPoint: T) => T extends null ? null : FixedPoint;
7
5
  export declare const getNormalizedDimensions: (element: Pick<DucElement, "width" | "height" | "x" | "y">) => {
8
6
  width: ScopedValue;
@@ -3,12 +3,6 @@ import { clamp } from "./math";
3
3
  export const getNormalizedZoom = (zoom) => {
4
4
  return clamp(zoom, MIN_ZOOM, MAX_ZOOM);
5
5
  };
6
- export const getNormalizedGridSize = (gridStep) => {
7
- return clamp(Math.round(gridStep), 1, 100);
8
- };
9
- export const getNormalizedGridStep = (gridStep) => {
10
- return clamp(Math.round(gridStep), 1, 100);
11
- };
12
6
  export const normalizeFixedPoint = (fixedPoint) => {
13
7
  // Do not allow a precise 0.5 for fixed point ratio
14
8
  // to avoid jumping arrow heading due to floating point imprecision
@@ -1,5 +1,5 @@
1
1
  import { isFiniteNumber } from "..";
2
- import { PRUNING_LEVEL, TEXT_ALIGN } from "../../enums";
2
+ import { TEXT_ALIGN } from "../../enums";
3
3
  import { isValidPrecisionScopeValue } from "../../restore/restoreDataState";
4
4
  import { getPrecisionValueFromRaw, getScaledZoomValueForScope, getScopedZoomValue, NEUTRAL_SCOPE, } from "../../technical/scopes";
5
5
  import { COLOR_PALETTE, DEFAULT_ELEMENT_PROPS, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, } from "../constants";
@@ -26,7 +26,6 @@ export const getDefaultGlobalState = () => {
26
26
  viewBackgroundColor: typeof window !== "undefined" ? (window.matchMedia("(prefers-color-scheme: dark)").matches ? COLOR_PALETTE.night : COLOR_PALETTE.white) : COLOR_PALETTE.white,
27
27
  scopeExponentThreshold: 3,
28
28
  mainScope: NEUTRAL_SCOPE,
29
- pruningLevel: PRUNING_LEVEL.BALANCED,
30
29
  };
31
30
  };
32
31
  export const getDefaultLocalState = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ducjs",
3
- "version": "3.1.1",
3
+ "version": "3.2.0",
4
4
  "description": "The duc 2D CAD file format is a cornerstone of our advanced design system, conceived to cater to professionals seeking precision and efficiency in their design work.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",