ducjs 2.3.0 → 2.4.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/dist/flatbuffers/duc/document-grid-config.d.ts +3 -1
- package/dist/flatbuffers/duc/document-grid-config.js +10 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/lazy-files.d.ts +84 -0
- package/dist/lazy-files.js +207 -0
- package/dist/parse.d.ts +34 -2
- package/dist/parse.js +203 -0
- package/dist/restore/restoreElements.js +12 -1
- package/dist/serialize.js +1 -0
- package/dist/types/elements/index.d.ts +4 -4
- package/dist/types/elements/typeChecks.d.ts +4 -1
- package/dist/types/elements/typeChecks.js +6 -0
- package/dist/utils/constants.d.ts +18 -14
- package/dist/utils/constants.js +28 -15
- package/dist/utils/elements/freedrawElement.d.ts +6 -0
- package/dist/utils/elements/freedrawElement.js +28 -11
- package/dist/utils/elements/newElement.js +12 -3
- package/dist/utils/elements/textElement.d.ts +3 -3
- package/dist/utils/elements/textElement.js +43 -16
- package/dist/utils/state/index.js +1 -1
- package/package.json +1 -1
|
@@ -11,12 +11,14 @@ export declare class DocumentGridConfig {
|
|
|
11
11
|
gapY(): number;
|
|
12
12
|
alignItems(): DOCUMENT_GRID_ALIGN_ITEMS | null;
|
|
13
13
|
firstPageAlone(): boolean;
|
|
14
|
+
scale(): number;
|
|
14
15
|
static startDocumentGridConfig(builder: flatbuffers.Builder): void;
|
|
15
16
|
static addColumns(builder: flatbuffers.Builder, columns: number): void;
|
|
16
17
|
static addGapX(builder: flatbuffers.Builder, gapX: number): void;
|
|
17
18
|
static addGapY(builder: flatbuffers.Builder, gapY: number): void;
|
|
18
19
|
static addAlignItems(builder: flatbuffers.Builder, alignItems: DOCUMENT_GRID_ALIGN_ITEMS): void;
|
|
19
20
|
static addFirstPageAlone(builder: flatbuffers.Builder, firstPageAlone: boolean): void;
|
|
21
|
+
static addScale(builder: flatbuffers.Builder, scale: number): void;
|
|
20
22
|
static endDocumentGridConfig(builder: flatbuffers.Builder): flatbuffers.Offset;
|
|
21
|
-
static createDocumentGridConfig(builder: flatbuffers.Builder, columns: number, gapX: number, gapY: number, alignItems: DOCUMENT_GRID_ALIGN_ITEMS | null, firstPageAlone: boolean): flatbuffers.Offset;
|
|
23
|
+
static createDocumentGridConfig(builder: flatbuffers.Builder, columns: number, gapX: number, gapY: number, alignItems: DOCUMENT_GRID_ALIGN_ITEMS | null, firstPageAlone: boolean, scale: number): flatbuffers.Offset;
|
|
22
24
|
}
|
|
@@ -38,8 +38,12 @@ export class DocumentGridConfig {
|
|
|
38
38
|
const offset = this.bb.__offset(this.bb_pos, 12);
|
|
39
39
|
return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false;
|
|
40
40
|
}
|
|
41
|
+
scale() {
|
|
42
|
+
const offset = this.bb.__offset(this.bb_pos, 14);
|
|
43
|
+
return offset ? this.bb.readFloat64(this.bb_pos + offset) : 0.0;
|
|
44
|
+
}
|
|
41
45
|
static startDocumentGridConfig(builder) {
|
|
42
|
-
builder.startObject(
|
|
46
|
+
builder.startObject(6);
|
|
43
47
|
}
|
|
44
48
|
static addColumns(builder, columns) {
|
|
45
49
|
builder.addFieldInt32(0, columns, 0);
|
|
@@ -56,11 +60,14 @@ export class DocumentGridConfig {
|
|
|
56
60
|
static addFirstPageAlone(builder, firstPageAlone) {
|
|
57
61
|
builder.addFieldInt8(4, +firstPageAlone, +false);
|
|
58
62
|
}
|
|
63
|
+
static addScale(builder, scale) {
|
|
64
|
+
builder.addFieldFloat64(5, scale, 0.0);
|
|
65
|
+
}
|
|
59
66
|
static endDocumentGridConfig(builder) {
|
|
60
67
|
const offset = builder.endObject();
|
|
61
68
|
return offset;
|
|
62
69
|
}
|
|
63
|
-
static createDocumentGridConfig(builder, columns, gapX, gapY, alignItems, firstPageAlone) {
|
|
70
|
+
static createDocumentGridConfig(builder, columns, gapX, gapY, alignItems, firstPageAlone, scale) {
|
|
64
71
|
DocumentGridConfig.startDocumentGridConfig(builder);
|
|
65
72
|
DocumentGridConfig.addColumns(builder, columns);
|
|
66
73
|
DocumentGridConfig.addGapX(builder, gapX);
|
|
@@ -68,6 +75,7 @@ export class DocumentGridConfig {
|
|
|
68
75
|
if (alignItems !== null)
|
|
69
76
|
DocumentGridConfig.addAlignItems(builder, alignItems);
|
|
70
77
|
DocumentGridConfig.addFirstPageAlone(builder, firstPageAlone);
|
|
78
|
+
DocumentGridConfig.addScale(builder, scale);
|
|
71
79
|
return DocumentGridConfig.endDocumentGridConfig(builder);
|
|
72
80
|
}
|
|
73
81
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export * as DucBin from "./flatbuffers/duc";
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./utils";
|
|
4
|
-
export * from "./serialize";
|
|
2
|
+
export * from "./lazy-files";
|
|
5
3
|
export * from "./parse";
|
|
6
4
|
export * from "./restore";
|
|
5
|
+
export * from "./serialize";
|
|
7
6
|
export * from "./technical";
|
|
7
|
+
export * from "./types";
|
|
8
|
+
export * from "./utils";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export * as DucBin from "./flatbuffers/duc";
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./utils";
|
|
4
|
-
export * from "./serialize";
|
|
2
|
+
export * from "./lazy-files";
|
|
5
3
|
export * from "./parse";
|
|
6
4
|
export * from "./restore";
|
|
5
|
+
export * from "./serialize";
|
|
7
6
|
export * from "./technical";
|
|
7
|
+
export * from "./types";
|
|
8
|
+
export * from "./utils";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LazyExternalFileStore — Zero-copy, on-demand access to external file data from a FlatBuffer.
|
|
3
|
+
*
|
|
4
|
+
* Instead of eagerly parsing and copying every external file's binary data into JS memory,
|
|
5
|
+
* this store keeps a reference to the original FlatBuffer Uint8Array and reads file bytes
|
|
6
|
+
* only when explicitly requested. FlatBuffer `dataArray()` returns a zero-copy view
|
|
7
|
+
* (a Uint8Array pointing into the original buffer), so no allocation occurs until the
|
|
8
|
+
* consumer actually needs the data.
|
|
9
|
+
*
|
|
10
|
+
* Memory lifecycle:
|
|
11
|
+
* 1. On parse: only metadata (~200 bytes per file) enters JS heap.
|
|
12
|
+
* 2. On demand: `getFileData(fileId)` reads the zero-copy slice from the buffer.
|
|
13
|
+
* 3. The caller (renderer/worker) uses the data, then lets it GC naturally.
|
|
14
|
+
* 4. If the store is released, the buffer reference is dropped, freeing everything.
|
|
15
|
+
*
|
|
16
|
+
* This is the key to supporting 1000s of external files without RAM bloat.
|
|
17
|
+
*/
|
|
18
|
+
import type { DucExternalFileData, DucExternalFileMetadata, DucExternalFiles } from "./types";
|
|
19
|
+
export type ExternalFileMetadataMap = Record<string, DucExternalFileMetadata>;
|
|
20
|
+
export declare class LazyExternalFileStore {
|
|
21
|
+
private _buffer;
|
|
22
|
+
private _byteBuffer;
|
|
23
|
+
private _dataState;
|
|
24
|
+
/** Map from file id → lazy entry */
|
|
25
|
+
private _entries;
|
|
26
|
+
/** Map from element key → file id (the external_files vector uses element id as key) */
|
|
27
|
+
private _keyToFileId;
|
|
28
|
+
/**
|
|
29
|
+
* Files that were added at runtime (e.g. user uploading a new image).
|
|
30
|
+
* These aren't in the original FlatBuffer so we hold their data directly.
|
|
31
|
+
*/
|
|
32
|
+
private _runtimeFiles;
|
|
33
|
+
constructor(buffer: Uint8Array);
|
|
34
|
+
private _indexMetadata;
|
|
35
|
+
/** Total number of external files */
|
|
36
|
+
get size(): number;
|
|
37
|
+
/** Whether a file with the given id exists */
|
|
38
|
+
has(fileId: string): boolean;
|
|
39
|
+
/** Get metadata only (no binary data copied) — ~200 bytes per file */
|
|
40
|
+
getMetadata(fileId: string): DucExternalFileMetadata | null;
|
|
41
|
+
/** Get all metadata entries (for UI listing, etc.) */
|
|
42
|
+
getAllMetadata(): ExternalFileMetadataMap;
|
|
43
|
+
/**
|
|
44
|
+
* Get full file data (metadata + binary bytes) ON DEMAND.
|
|
45
|
+
*
|
|
46
|
+
* For files from the original FlatBuffer, this returns a zero-copy Uint8Array
|
|
47
|
+
* view into the original buffer — no allocation for the file bytes themselves.
|
|
48
|
+
* The view is valid as long as this store hasn't been released.
|
|
49
|
+
*
|
|
50
|
+
* For runtime-added files, returns the data directly.
|
|
51
|
+
*/
|
|
52
|
+
getFileData(fileId: string): DucExternalFileData | null;
|
|
53
|
+
/**
|
|
54
|
+
* Get a detached copy of the file data (allocates new ArrayBuffer).
|
|
55
|
+
* Use this when you need to transfer data to a worker or keep it beyond store lifetime.
|
|
56
|
+
*/
|
|
57
|
+
getFileDataCopy(fileId: string): DucExternalFileData | null;
|
|
58
|
+
/**
|
|
59
|
+
* Add a file at runtime (user upload, paste, etc.).
|
|
60
|
+
* These files are held in memory since they aren't in the FlatBuffer.
|
|
61
|
+
*/
|
|
62
|
+
addRuntimeFile(fileData: DucExternalFileData): void;
|
|
63
|
+
/** Remove a runtime-added file */
|
|
64
|
+
removeRuntimeFile(fileId: string): void;
|
|
65
|
+
/**
|
|
66
|
+
* Export all files as a standard DucExternalFiles record.
|
|
67
|
+
* This COPIES all file data eagerly — use only for serialization.
|
|
68
|
+
*/
|
|
69
|
+
toExternalFiles(): DucExternalFiles;
|
|
70
|
+
/**
|
|
71
|
+
* Merge runtime files from the given DucExternalFiles map.
|
|
72
|
+
* Only adds files not already present in the store.
|
|
73
|
+
*/
|
|
74
|
+
mergeFiles(files: DucExternalFiles): void;
|
|
75
|
+
/** Estimated RAM usage for metadata only (not counting the backing buffer) */
|
|
76
|
+
get estimatedMetadataBytes(): number;
|
|
77
|
+
/**
|
|
78
|
+
* Release the FlatBuffer reference. After this, only runtime-added files remain accessible.
|
|
79
|
+
* Call this when switching documents or when the store is no longer needed.
|
|
80
|
+
*/
|
|
81
|
+
release(): void;
|
|
82
|
+
/** Whether the store has been released */
|
|
83
|
+
get isReleased(): boolean;
|
|
84
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LazyExternalFileStore — Zero-copy, on-demand access to external file data from a FlatBuffer.
|
|
3
|
+
*
|
|
4
|
+
* Instead of eagerly parsing and copying every external file's binary data into JS memory,
|
|
5
|
+
* this store keeps a reference to the original FlatBuffer Uint8Array and reads file bytes
|
|
6
|
+
* only when explicitly requested. FlatBuffer `dataArray()` returns a zero-copy view
|
|
7
|
+
* (a Uint8Array pointing into the original buffer), so no allocation occurs until the
|
|
8
|
+
* consumer actually needs the data.
|
|
9
|
+
*
|
|
10
|
+
* Memory lifecycle:
|
|
11
|
+
* 1. On parse: only metadata (~200 bytes per file) enters JS heap.
|
|
12
|
+
* 2. On demand: `getFileData(fileId)` reads the zero-copy slice from the buffer.
|
|
13
|
+
* 3. The caller (renderer/worker) uses the data, then lets it GC naturally.
|
|
14
|
+
* 4. If the store is released, the buffer reference is dropped, freeing everything.
|
|
15
|
+
*
|
|
16
|
+
* This is the key to supporting 1000s of external files without RAM bloat.
|
|
17
|
+
*/
|
|
18
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
19
|
+
var t = {};
|
|
20
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
21
|
+
t[p] = s[p];
|
|
22
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
23
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
24
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
25
|
+
t[p[i]] = s[p[i]];
|
|
26
|
+
}
|
|
27
|
+
return t;
|
|
28
|
+
};
|
|
29
|
+
import * as flatbuffers from "flatbuffers";
|
|
30
|
+
import { ExportedDataState as ExportedDataStateFb } from "./flatbuffers/duc";
|
|
31
|
+
export class LazyExternalFileStore {
|
|
32
|
+
constructor(buffer) {
|
|
33
|
+
/** Map from file id → lazy entry */
|
|
34
|
+
this._entries = new Map();
|
|
35
|
+
/** Map from element key → file id (the external_files vector uses element id as key) */
|
|
36
|
+
this._keyToFileId = new Map();
|
|
37
|
+
/**
|
|
38
|
+
* Files that were added at runtime (e.g. user uploading a new image).
|
|
39
|
+
* These aren't in the original FlatBuffer so we hold their data directly.
|
|
40
|
+
*/
|
|
41
|
+
this._runtimeFiles = new Map();
|
|
42
|
+
this._buffer = buffer;
|
|
43
|
+
this._byteBuffer = new flatbuffers.ByteBuffer(buffer);
|
|
44
|
+
this._dataState = ExportedDataStateFb.getRootAsExportedDataState(this._byteBuffer);
|
|
45
|
+
this._indexMetadata();
|
|
46
|
+
console.info(`[LazyExternalFileStore] indexed ${this._entries.size} files from ${buffer.byteLength} byte buffer, ids: [${[...this._entries.keys()].map(k => k.slice(0, 12)).join(', ')}]`);
|
|
47
|
+
}
|
|
48
|
+
_indexMetadata() {
|
|
49
|
+
if (!this._dataState)
|
|
50
|
+
return;
|
|
51
|
+
const count = this._dataState.externalFilesLength();
|
|
52
|
+
for (let i = 0; i < count; i++) {
|
|
53
|
+
const entry = this._dataState.externalFiles(i);
|
|
54
|
+
if (!entry)
|
|
55
|
+
continue;
|
|
56
|
+
const key = entry.key();
|
|
57
|
+
const fileData = entry.value();
|
|
58
|
+
if (!key || !fileData)
|
|
59
|
+
continue;
|
|
60
|
+
const id = fileData.id();
|
|
61
|
+
if (!id)
|
|
62
|
+
continue;
|
|
63
|
+
const metadata = {
|
|
64
|
+
id,
|
|
65
|
+
mimeType: fileData.mimeType() || "application/octet-stream",
|
|
66
|
+
created: Number(fileData.created()),
|
|
67
|
+
lastRetrieved: Number(fileData.lastRetrieved()) || undefined,
|
|
68
|
+
};
|
|
69
|
+
const lazyEntry = { metadata, vectorIndex: i };
|
|
70
|
+
this._entries.set(id, lazyEntry);
|
|
71
|
+
this._keyToFileId.set(key, id);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Total number of external files */
|
|
75
|
+
get size() {
|
|
76
|
+
return this._entries.size + this._runtimeFiles.size;
|
|
77
|
+
}
|
|
78
|
+
/** Whether a file with the given id exists */
|
|
79
|
+
has(fileId) {
|
|
80
|
+
return this._entries.has(fileId) || this._runtimeFiles.has(fileId);
|
|
81
|
+
}
|
|
82
|
+
/** Get metadata only (no binary data copied) — ~200 bytes per file */
|
|
83
|
+
getMetadata(fileId) {
|
|
84
|
+
var _a, _b;
|
|
85
|
+
const runtime = this._runtimeFiles.get(fileId);
|
|
86
|
+
if (runtime) {
|
|
87
|
+
const { data: _ } = runtime, meta = __rest(runtime, ["data"]);
|
|
88
|
+
return meta;
|
|
89
|
+
}
|
|
90
|
+
return (_b = (_a = this._entries.get(fileId)) === null || _a === void 0 ? void 0 : _a.metadata) !== null && _b !== void 0 ? _b : null;
|
|
91
|
+
}
|
|
92
|
+
/** Get all metadata entries (for UI listing, etc.) */
|
|
93
|
+
getAllMetadata() {
|
|
94
|
+
const result = {};
|
|
95
|
+
for (const [id, entry] of this._entries) {
|
|
96
|
+
result[id] = entry.metadata;
|
|
97
|
+
}
|
|
98
|
+
for (const [id, file] of this._runtimeFiles) {
|
|
99
|
+
const { data: _ } = file, meta = __rest(file, ["data"]);
|
|
100
|
+
result[id] = meta;
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get full file data (metadata + binary bytes) ON DEMAND.
|
|
106
|
+
*
|
|
107
|
+
* For files from the original FlatBuffer, this returns a zero-copy Uint8Array
|
|
108
|
+
* view into the original buffer — no allocation for the file bytes themselves.
|
|
109
|
+
* The view is valid as long as this store hasn't been released.
|
|
110
|
+
*
|
|
111
|
+
* For runtime-added files, returns the data directly.
|
|
112
|
+
*/
|
|
113
|
+
getFileData(fileId) {
|
|
114
|
+
const runtime = this._runtimeFiles.get(fileId);
|
|
115
|
+
if (runtime)
|
|
116
|
+
return runtime;
|
|
117
|
+
const entry = this._entries.get(fileId);
|
|
118
|
+
if (!entry || !this._dataState)
|
|
119
|
+
return null;
|
|
120
|
+
const fbEntry = this._dataState.externalFiles(entry.vectorIndex);
|
|
121
|
+
if (!fbEntry)
|
|
122
|
+
return null;
|
|
123
|
+
const fileData = fbEntry.value();
|
|
124
|
+
if (!fileData)
|
|
125
|
+
return null;
|
|
126
|
+
const data = fileData.dataArray();
|
|
127
|
+
if (!data)
|
|
128
|
+
return null;
|
|
129
|
+
return Object.assign(Object.assign({}, entry.metadata), { data });
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get a detached copy of the file data (allocates new ArrayBuffer).
|
|
133
|
+
* Use this when you need to transfer data to a worker or keep it beyond store lifetime.
|
|
134
|
+
*/
|
|
135
|
+
getFileDataCopy(fileId) {
|
|
136
|
+
const fileDataRef = this.getFileData(fileId);
|
|
137
|
+
if (!fileDataRef)
|
|
138
|
+
return null;
|
|
139
|
+
return Object.assign(Object.assign({}, fileDataRef), { data: new Uint8Array(fileDataRef.data) });
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Add a file at runtime (user upload, paste, etc.).
|
|
143
|
+
* These files are held in memory since they aren't in the FlatBuffer.
|
|
144
|
+
*/
|
|
145
|
+
addRuntimeFile(fileData) {
|
|
146
|
+
this._runtimeFiles.set(fileData.id, fileData);
|
|
147
|
+
}
|
|
148
|
+
/** Remove a runtime-added file */
|
|
149
|
+
removeRuntimeFile(fileId) {
|
|
150
|
+
this._runtimeFiles.delete(fileId);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Export all files as a standard DucExternalFiles record.
|
|
154
|
+
* This COPIES all file data eagerly — use only for serialization.
|
|
155
|
+
*/
|
|
156
|
+
toExternalFiles() {
|
|
157
|
+
const result = {};
|
|
158
|
+
for (const [key, fileId] of this._keyToFileId) {
|
|
159
|
+
const fileData = this.getFileData(fileId);
|
|
160
|
+
if (fileData) {
|
|
161
|
+
result[key] = fileData;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
for (const [id, file] of this._runtimeFiles) {
|
|
165
|
+
result[id] = file;
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Merge runtime files from the given DucExternalFiles map.
|
|
171
|
+
* Only adds files not already present in the store.
|
|
172
|
+
*/
|
|
173
|
+
mergeFiles(files) {
|
|
174
|
+
for (const [_key, fileData] of Object.entries(files)) {
|
|
175
|
+
if (!this.has(fileData.id)) {
|
|
176
|
+
this.addRuntimeFile(fileData);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/** Estimated RAM usage for metadata only (not counting the backing buffer) */
|
|
181
|
+
get estimatedMetadataBytes() {
|
|
182
|
+
var _a, _b;
|
|
183
|
+
let bytes = 0;
|
|
184
|
+
for (const [, entry] of this._entries) {
|
|
185
|
+
bytes += 200 + entry.metadata.id.length * 2 + entry.metadata.mimeType.length * 2;
|
|
186
|
+
}
|
|
187
|
+
for (const [, file] of this._runtimeFiles) {
|
|
188
|
+
bytes += 200 + ((_b = (_a = file.data) === null || _a === void 0 ? void 0 : _a.byteLength) !== null && _b !== void 0 ? _b : 0);
|
|
189
|
+
}
|
|
190
|
+
return bytes;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Release the FlatBuffer reference. After this, only runtime-added files remain accessible.
|
|
194
|
+
* Call this when switching documents or when the store is no longer needed.
|
|
195
|
+
*/
|
|
196
|
+
release() {
|
|
197
|
+
this._buffer = null;
|
|
198
|
+
this._byteBuffer = null;
|
|
199
|
+
this._dataState = null;
|
|
200
|
+
this._entries.clear();
|
|
201
|
+
this._keyToFileId.clear();
|
|
202
|
+
}
|
|
203
|
+
/** Whether the store has been released */
|
|
204
|
+
get isReleased() {
|
|
205
|
+
return this._buffer === null;
|
|
206
|
+
}
|
|
207
|
+
}
|
package/dist/parse.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { FileSystemHandle } from 'browser-fs-access';
|
|
2
|
-
import { CustomHatchPattern as CustomHatchPatternFb, DimensionToleranceStyle as DimensionToleranceStyleFb, DucBlock as DucBlockFb, DucDimensionStyle as DucDimensionStyleFb, DucDocStyle as DucDocStyleFb, DucExternalFileEntry, DucFeatureControlFrameStyle as DucFeatureControlFrameStyleFb, DucGlobalState as DucGlobalStateFb, DucGroup as DucGroupFb, DucHatchStyle as DucHatchStyleFb, DucHead as DucHeadFb, DucImageFilter as DucImageFilterFb, DucLayer as DucLayerFb, DucLeaderStyle as DucLeaderStyleFb, DucLine as DucLineFb, DucLineReference as DucLineReferenceFb, DucLocalState as DucLocalStateFb, DucPath as DucPathFb, DucPlotStyle as DucPlotStyleFb, DucPointBinding as DucPointBindingFb, DucPoint as DucPointFb, DucRegion as DucRegionFb, DucStackLikeStyles as DucStackLikeStylesFb, DucTableCellStyle as DucTableCellStyleFb, DucTableStyle as DucTableStyleFb, DucTextStyle as DucTextStyleFb, DucViewportStyle as DucViewportStyleFb, DucXRayStyle as DucXRayStyleFb,
|
|
2
|
+
import { CustomHatchPattern as CustomHatchPatternFb, DimensionToleranceStyle as DimensionToleranceStyleFb, DocumentGridConfig as DocumentGridConfigFb, DucBlock as DucBlockFb, DucDimensionStyle as DucDimensionStyleFb, DucDocStyle as DucDocStyleFb, DucExternalFileEntry, DucFeatureControlFrameStyle as DucFeatureControlFrameStyleFb, DucGlobalState as DucGlobalStateFb, DucGroup as DucGroupFb, DucHatchStyle as DucHatchStyleFb, DucHead as DucHeadFb, DucImageFilter as DucImageFilterFb, DucLayer as DucLayerFb, DucLeaderStyle as DucLeaderStyleFb, DucLine as DucLineFb, DucLineReference as DucLineReferenceFb, DucLocalState as DucLocalStateFb, DucPath as DucPathFb, DucPlotStyle as DucPlotStyleFb, DucPointBinding as DucPointBindingFb, DucPoint as DucPointFb, DucRegion as DucRegionFb, DucStackLikeStyles as DucStackLikeStylesFb, DucTableCellStyle as DucTableCellStyleFb, DucTableStyle as DucTableStyleFb, DucTextStyle as DucTextStyleFb, DucViewportStyle as DucViewportStyleFb, DucXRayStyle as DucXRayStyleFb, ElementBackground as ElementBackgroundFb, ElementContentBase as ElementContentBaseFb, ElementStroke as ElementStrokeFb, ElementWrapper, ExportedDataState as ExportedDataStateFb, GeometricPoint as GeometricPointFb, HatchPatternLine as HatchPatternLineFb, Margins as MarginsFb, PrimaryUnits as PrimaryUnitsFb, Standard as StandardFb, StrokeSides as StrokeSidesFb, StrokeStyle as StrokeStyleFb, TilingProperties as TilingPropertiesFb, VersionGraph as VersionGraphFb, _DucElementBase as _DucElementBaseFb, _DucElementStylesBase as _DucElementStylesBaseFb, _DucLinearElementBase as _DucLinearElementBaseFb, _DucStackBase as _DucStackBaseFb, _DucStackElementBase as _DucStackElementBaseFb } from "./flatbuffers/duc";
|
|
3
3
|
import { RestoreConfig, RestoredDataState } from "./restore";
|
|
4
4
|
import { Standard, StandardUnits } from "./technical";
|
|
5
|
-
import { CustomHatchPattern, Dictionary, DocumentGridConfig, DucBlock, DucDimensionStyle, DucDocStyle, DucElement, DucExternalFiles, DucFeatureControlFrameStyle, DucGlobalState, DucGroup, DucHatchStyle, DucHead, DucImageFilter, DucLayer, DucLeaderStyle, DucLine, DucLineReference, DucLocalState, DucPath, DucPlotStyle, DucPoint, DucPointBinding, DucRegion, DucStackLikeStyles, DucTableCellStyle, DucTableStyle, DucTextStyle, DucViewportStyle, DucXRayStyle, ElementBackground, ElementContentBase, ElementStroke, GeometricPoint, HatchPatternLine, PlotLayout, StrokeSides, StrokeStyle, TilingProperties, VersionGraph, _DucElementBase, _DucElementStylesBase, _DucLinearElementBase, _DucStackBase, _DucStackElementBase } from "./types";
|
|
5
|
+
import { CustomHatchPattern, Dictionary, DocumentGridConfig, DucBlock, DucDimensionStyle, DucDocStyle, DucElement, DucExternalFileMetadata, DucExternalFiles, DucFeatureControlFrameStyle, DucGlobalState, DucGroup, DucHatchStyle, DucHead, DucImageFilter, DucLayer, DucLeaderStyle, DucLine, DucLineReference, DucLocalState, DucPath, DucPlotStyle, DucPoint, DucPointBinding, DucRegion, DucStackLikeStyles, DucTableCellStyle, DucTableStyle, DucTextStyle, DucViewportStyle, DucXRayStyle, ElementBackground, ElementContentBase, ElementStroke, GeometricPoint, HatchPatternLine, PlotLayout, StrokeSides, StrokeStyle, TilingProperties, VersionGraph, _DucElementBase, _DucElementStylesBase, _DucLinearElementBase, _DucStackBase, _DucStackElementBase } from "./types";
|
|
6
6
|
export declare function parseGeometricPoint(point: GeometricPointFb): GeometricPoint;
|
|
7
7
|
export declare function parsePoint(point: DucPointFb): DucPoint;
|
|
8
8
|
export declare function parseMargins(margins: MarginsFb): PlotLayout["margins"];
|
|
@@ -43,6 +43,14 @@ export declare function parseElementFromBinary(wrapper: ElementWrapper): DucElem
|
|
|
43
43
|
export declare function parseBlockFromBinary(block: DucBlockFb): DucBlock;
|
|
44
44
|
export declare function parseDictionaryFromBinary(data: ExportedDataStateFb): Dictionary;
|
|
45
45
|
export declare function parseExternalFilesFromBinary(entry: DucExternalFileEntry): DucExternalFiles;
|
|
46
|
+
/**
|
|
47
|
+
* Parse only metadata (no binary data) from an external file entry.
|
|
48
|
+
* Used by the lazy file store to avoid copying file bytes into JS memory.
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseExternalFileMetadataFromBinary(entry: DucExternalFileEntry): {
|
|
51
|
+
key: string;
|
|
52
|
+
metadata: DucExternalFileMetadata;
|
|
53
|
+
} | null;
|
|
46
54
|
export declare function parseGlobalStateFromBinary(state: DucGlobalStateFb): DucGlobalState;
|
|
47
55
|
export declare function parseGroupFromBinary(group: DucGroupFb): DucGroup;
|
|
48
56
|
export declare function parseLayerFromBinary(layer: DucLayerFb): DucLayer;
|
|
@@ -53,3 +61,27 @@ export declare function parseStandardFromBinary(standard: StandardFb): Standard;
|
|
|
53
61
|
export declare function parseThumbnailFromBinary(data: ExportedDataStateFb): Uint8Array | undefined;
|
|
54
62
|
export declare function parseVersionGraphFromBinary(graph: VersionGraphFb | null): VersionGraph | null;
|
|
55
63
|
export declare const parseDuc: (blob: Blob | File, fileHandle?: FileSystemHandle | null, restoreConfig?: RestoreConfig) => Promise<RestoredDataState>;
|
|
64
|
+
import { LazyExternalFileStore } from "./lazy-files";
|
|
65
|
+
export type LazyRestoredDataState = Omit<RestoredDataState, 'files'> & {
|
|
66
|
+
/** Lazy file store: only metadata is in memory, file bytes are read on-demand from the buffer */
|
|
67
|
+
lazyFileStore: LazyExternalFileStore;
|
|
68
|
+
/** Legacy `files` field — always empty. Use lazyFileStore instead. */
|
|
69
|
+
files: DucExternalFiles;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Parse a .duc binary with lazy external file loading.
|
|
73
|
+
*
|
|
74
|
+
* This is identical to `parseDuc` except:
|
|
75
|
+
* - External file BYTES are NOT copied into JS memory.
|
|
76
|
+
* - Only file metadata (~200 bytes per file) is parsed.
|
|
77
|
+
* - A `LazyExternalFileStore` is returned for on-demand data access.
|
|
78
|
+
* - The store holds a reference to the original Uint8Array buffer.
|
|
79
|
+
*
|
|
80
|
+
* Memory comparison for 500 PDFs averaging 5MB each:
|
|
81
|
+
* - parseDuc: files field holds 2.5GB of ArrayBuffer data in JS heap
|
|
82
|
+
* - parseDucLazy: ~100KB of metadata + the original buffer (referenced, not copied)
|
|
83
|
+
*
|
|
84
|
+
* @param buffer - The raw .duc file bytes (from storage/IndexedDB/filesystem)
|
|
85
|
+
* @param restoreConfig - Optional restore configuration
|
|
86
|
+
*/
|
|
87
|
+
export declare const parseDucLazy: (buffer: Uint8Array, restoreConfig?: RestoreConfig) => Promise<LazyRestoredDataState>;
|
package/dist/parse.js
CHANGED
|
@@ -86,6 +86,7 @@ export function parseDocumentGridConfig(gridConfig) {
|
|
|
86
86
|
return 'start';
|
|
87
87
|
})(),
|
|
88
88
|
firstPageAlone: gridConfig.firstPageAlone(),
|
|
89
|
+
scale: gridConfig.scale(),
|
|
89
90
|
};
|
|
90
91
|
}
|
|
91
92
|
export function parseHead(head) {
|
|
@@ -282,6 +283,7 @@ function parsePdfElement(element) {
|
|
|
282
283
|
gapY: 0,
|
|
283
284
|
alignItems: 'start',
|
|
284
285
|
firstPageAlone: false,
|
|
286
|
+
scale: 1,
|
|
285
287
|
} });
|
|
286
288
|
}
|
|
287
289
|
function parseMermaidElement(element) {
|
|
@@ -587,6 +589,7 @@ function parseDocElement(element) {
|
|
|
587
589
|
gapY: 0,
|
|
588
590
|
alignItems: 'start',
|
|
589
591
|
firstPageAlone: false,
|
|
592
|
+
scale: 1,
|
|
590
593
|
} });
|
|
591
594
|
}
|
|
592
595
|
function parseModelElement(element) {
|
|
@@ -907,6 +910,28 @@ export function parseExternalFilesFromBinary(entry) {
|
|
|
907
910
|
}
|
|
908
911
|
};
|
|
909
912
|
}
|
|
913
|
+
/**
|
|
914
|
+
* Parse only metadata (no binary data) from an external file entry.
|
|
915
|
+
* Used by the lazy file store to avoid copying file bytes into JS memory.
|
|
916
|
+
*/
|
|
917
|
+
export function parseExternalFileMetadataFromBinary(entry) {
|
|
918
|
+
const fileData = entry.value();
|
|
919
|
+
const key = entry.key();
|
|
920
|
+
if (!fileData || !key)
|
|
921
|
+
return null;
|
|
922
|
+
const id = fileData.id();
|
|
923
|
+
if (!id)
|
|
924
|
+
return null;
|
|
925
|
+
return {
|
|
926
|
+
key,
|
|
927
|
+
metadata: {
|
|
928
|
+
id,
|
|
929
|
+
mimeType: fileData.mimeType() || "application/octet-stream",
|
|
930
|
+
created: Number(fileData.created()),
|
|
931
|
+
lastRetrieved: Number(fileData.lastRetrieved()) || undefined,
|
|
932
|
+
},
|
|
933
|
+
};
|
|
934
|
+
}
|
|
910
935
|
export function parseGlobalStateFromBinary(state) {
|
|
911
936
|
return {
|
|
912
937
|
name: state.name(),
|
|
@@ -1459,3 +1484,181 @@ export const parseDuc = (blob_1, ...args_1) => __awaiter(void 0, [blob_1, ...arg
|
|
|
1459
1484
|
};
|
|
1460
1485
|
});
|
|
1461
1486
|
// #endregion
|
|
1487
|
+
// #region LAZY ROOT PARSER
|
|
1488
|
+
import { LazyExternalFileStore } from "./lazy-files";
|
|
1489
|
+
/**
|
|
1490
|
+
* Parse a .duc binary with lazy external file loading.
|
|
1491
|
+
*
|
|
1492
|
+
* This is identical to `parseDuc` except:
|
|
1493
|
+
* - External file BYTES are NOT copied into JS memory.
|
|
1494
|
+
* - Only file metadata (~200 bytes per file) is parsed.
|
|
1495
|
+
* - A `LazyExternalFileStore` is returned for on-demand data access.
|
|
1496
|
+
* - The store holds a reference to the original Uint8Array buffer.
|
|
1497
|
+
*
|
|
1498
|
+
* Memory comparison for 500 PDFs averaging 5MB each:
|
|
1499
|
+
* - parseDuc: files field holds 2.5GB of ArrayBuffer data in JS heap
|
|
1500
|
+
* - parseDucLazy: ~100KB of metadata + the original buffer (referenced, not copied)
|
|
1501
|
+
*
|
|
1502
|
+
* @param buffer - The raw .duc file bytes (from storage/IndexedDB/filesystem)
|
|
1503
|
+
* @param restoreConfig - Optional restore configuration
|
|
1504
|
+
*/
|
|
1505
|
+
export const parseDucLazy = (buffer_1, ...args_1) => __awaiter(void 0, [buffer_1, ...args_1], void 0, function* (buffer, restoreConfig = {}) {
|
|
1506
|
+
var _a;
|
|
1507
|
+
if (!buffer || buffer.byteLength === 0) {
|
|
1508
|
+
throw new Error('Invalid DUC buffer: empty file');
|
|
1509
|
+
}
|
|
1510
|
+
const byteBuffer = new flatbuffers.ByteBuffer(buffer);
|
|
1511
|
+
let data;
|
|
1512
|
+
try {
|
|
1513
|
+
data = ExportedDataState.getRootAsExportedDataState(byteBuffer);
|
|
1514
|
+
}
|
|
1515
|
+
catch (e) {
|
|
1516
|
+
throw new Error('Invalid DUC buffer: cannot read root table');
|
|
1517
|
+
}
|
|
1518
|
+
const legacyVersion = data.versionLegacy();
|
|
1519
|
+
if (legacyVersion) {
|
|
1520
|
+
throw new Error(`Unsupported DUC version: ${legacyVersion}. Please use version ducjs@2.0.1 or lower to support this file.`);
|
|
1521
|
+
}
|
|
1522
|
+
const localState = data.ducLocalState();
|
|
1523
|
+
const parsedLocalState = localState && parseLocalStateFromBinary(localState);
|
|
1524
|
+
const globalState = data.ducGlobalState();
|
|
1525
|
+
const parsedGlobalState = globalState && parseGlobalStateFromBinary(globalState);
|
|
1526
|
+
// Parse elements
|
|
1527
|
+
const elements = [];
|
|
1528
|
+
for (let i = 0; i < data.elementsLength(); i++) {
|
|
1529
|
+
const e = data.elements(i);
|
|
1530
|
+
if (e) {
|
|
1531
|
+
const element = parseElementFromBinary(e);
|
|
1532
|
+
if (element) {
|
|
1533
|
+
elements.push(element);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
// Create lazy file store — only metadata is parsed, no file bytes copied
|
|
1538
|
+
const lazyFileStore = new LazyExternalFileStore(buffer);
|
|
1539
|
+
// Parse blocks
|
|
1540
|
+
const blocks = [];
|
|
1541
|
+
for (let i = 0; i < data.blocksLength(); i++) {
|
|
1542
|
+
const block = data.blocks(i);
|
|
1543
|
+
if (block) {
|
|
1544
|
+
const parsedBlock = parseBlockFromBinary(block);
|
|
1545
|
+
if (parsedBlock) {
|
|
1546
|
+
blocks.push(parsedBlock);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
// Parse block instances
|
|
1551
|
+
const blockInstances = [];
|
|
1552
|
+
for (let i = 0; i < data.blockInstancesLength(); i++) {
|
|
1553
|
+
const blockInstance = data.blockInstances(i);
|
|
1554
|
+
if (blockInstance) {
|
|
1555
|
+
const parsedBlockInstance = parseBlockInstance(blockInstance);
|
|
1556
|
+
if (parsedBlockInstance) {
|
|
1557
|
+
blockInstances.push(parsedBlockInstance);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
// Parse block collections
|
|
1562
|
+
const blockCollections = [];
|
|
1563
|
+
for (let i = 0; i < data.blockCollectionsLength(); i++) {
|
|
1564
|
+
const blockCollection = data.blockCollections(i);
|
|
1565
|
+
if (blockCollection) {
|
|
1566
|
+
const parsedBlockCollection = parseBlockCollection(blockCollection);
|
|
1567
|
+
if (parsedBlockCollection) {
|
|
1568
|
+
blockCollections.push(parsedBlockCollection);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
// Parse groups
|
|
1573
|
+
const groups = [];
|
|
1574
|
+
for (let i = 0; i < data.groupsLength(); i++) {
|
|
1575
|
+
const group = data.groups(i);
|
|
1576
|
+
if (group) {
|
|
1577
|
+
const parsedGroup = parseGroupFromBinary(group);
|
|
1578
|
+
if (parsedGroup) {
|
|
1579
|
+
groups.push(parsedGroup);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
// Parse dictionary
|
|
1584
|
+
const dictionary = parseDictionaryFromBinary(data);
|
|
1585
|
+
// Parse thumbnail
|
|
1586
|
+
const thumbnail = parseThumbnailFromBinary(data);
|
|
1587
|
+
// Parse version graph
|
|
1588
|
+
const versionGraphData = data.versionGraph();
|
|
1589
|
+
const versionGraph = parseVersionGraphFromBinary(versionGraphData);
|
|
1590
|
+
// Parse regions
|
|
1591
|
+
const regions = [];
|
|
1592
|
+
for (let i = 0; i < data.regionsLength(); i++) {
|
|
1593
|
+
const region = data.regions(i);
|
|
1594
|
+
if (region) {
|
|
1595
|
+
const parsedRegion = parseRegionFromBinary(region);
|
|
1596
|
+
if (parsedRegion) {
|
|
1597
|
+
regions.push(parsedRegion);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
// Parse layers
|
|
1602
|
+
const layers = [];
|
|
1603
|
+
for (let i = 0; i < data.layersLength(); i++) {
|
|
1604
|
+
const layer = data.layers(i);
|
|
1605
|
+
if (layer) {
|
|
1606
|
+
const parsedLayer = parseLayerFromBinary(layer);
|
|
1607
|
+
if (parsedLayer) {
|
|
1608
|
+
layers.push(parsedLayer);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
// Parse standards
|
|
1613
|
+
const standards = [];
|
|
1614
|
+
for (let i = 0; i < data.standardsLength(); i++) {
|
|
1615
|
+
const standard = data.standards(i);
|
|
1616
|
+
if (standard) {
|
|
1617
|
+
const parsedStandard = parseStandardFromBinary(standard);
|
|
1618
|
+
if (parsedStandard) {
|
|
1619
|
+
standards.push(parsedStandard);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
const exportData = {
|
|
1624
|
+
thumbnail,
|
|
1625
|
+
dictionary,
|
|
1626
|
+
elements: elements,
|
|
1627
|
+
localState: parsedLocalState,
|
|
1628
|
+
globalState: parsedGlobalState,
|
|
1629
|
+
blocks,
|
|
1630
|
+
blockInstances,
|
|
1631
|
+
blockCollections,
|
|
1632
|
+
groups,
|
|
1633
|
+
regions,
|
|
1634
|
+
layers,
|
|
1635
|
+
standards,
|
|
1636
|
+
files: {}, // empty — use lazyFileStore
|
|
1637
|
+
versionGraph: versionGraph !== null && versionGraph !== void 0 ? versionGraph : undefined,
|
|
1638
|
+
id: (_a = data.id()) !== null && _a !== void 0 ? _a : nanoid(),
|
|
1639
|
+
};
|
|
1640
|
+
const sanitized = restore(exportData, {
|
|
1641
|
+
syncInvalidIndices: (elements) => elements,
|
|
1642
|
+
repairBindings: true,
|
|
1643
|
+
refreshDimensions: false,
|
|
1644
|
+
}, restoreConfig);
|
|
1645
|
+
return {
|
|
1646
|
+
thumbnail: sanitized.thumbnail,
|
|
1647
|
+
dictionary: sanitized.dictionary,
|
|
1648
|
+
elements: sanitized.elements,
|
|
1649
|
+
localState: sanitized.localState,
|
|
1650
|
+
globalState: sanitized.globalState,
|
|
1651
|
+
files: {},
|
|
1652
|
+
lazyFileStore,
|
|
1653
|
+
blocks: sanitized.blocks,
|
|
1654
|
+
blockInstances: sanitized.blockInstances,
|
|
1655
|
+
groups: sanitized.groups,
|
|
1656
|
+
regions: sanitized.regions,
|
|
1657
|
+
layers: sanitized.layers,
|
|
1658
|
+
blockCollections: sanitized.blockCollections,
|
|
1659
|
+
standards: sanitized.standards,
|
|
1660
|
+
versionGraph: sanitized.versionGraph,
|
|
1661
|
+
id: sanitized.id,
|
|
1662
|
+
};
|
|
1663
|
+
});
|
|
1664
|
+
// #endregion
|
|
@@ -90,6 +90,10 @@ const restoreElement = (element, currentScope, restoredBlocks, localState, globa
|
|
|
90
90
|
case "text": {
|
|
91
91
|
let fontSize = element.fontSize;
|
|
92
92
|
let fontFamily = element.fontFamily;
|
|
93
|
+
// Restore condition: if font family is "10", change to DEFAULT_FONT_FAMILY
|
|
94
|
+
if (fontFamily === "10") {
|
|
95
|
+
fontFamily = DEFAULT_FONT_FAMILY;
|
|
96
|
+
}
|
|
93
97
|
if ("font" in element) {
|
|
94
98
|
try {
|
|
95
99
|
const fontParts = String(element.font).split(" ");
|
|
@@ -975,6 +979,7 @@ const restoreDocumentGridConfig = (gridConfig) => {
|
|
|
975
979
|
gapY: 0,
|
|
976
980
|
alignItems: "start",
|
|
977
981
|
firstPageAlone: false,
|
|
982
|
+
scale: 1,
|
|
978
983
|
};
|
|
979
984
|
}
|
|
980
985
|
return {
|
|
@@ -985,6 +990,7 @@ const restoreDocumentGridConfig = (gridConfig) => {
|
|
|
985
990
|
? gridConfig.alignItems
|
|
986
991
|
: "start",
|
|
987
992
|
firstPageAlone: typeof gridConfig.firstPageAlone === "boolean" ? gridConfig.firstPageAlone : false,
|
|
993
|
+
scale: typeof gridConfig.scale === "number" ? gridConfig.scale : 1,
|
|
988
994
|
};
|
|
989
995
|
};
|
|
990
996
|
const getFontFamilyByName = (fontFamilyName) => {
|
|
@@ -1272,10 +1278,15 @@ const restoreFcfDatumDefinition = (def, elementScope, currentScope, restoredBloc
|
|
|
1272
1278
|
const restoreTextStyle = (style, currentScope) => {
|
|
1273
1279
|
var _a, _b, _c;
|
|
1274
1280
|
const defaultLineHeight = 1.15;
|
|
1281
|
+
// Restore condition: if font family is "10", change to DEFAULT_FONT_FAMILY
|
|
1282
|
+
let fontFamily = style === null || style === void 0 ? void 0 : style.fontFamily;
|
|
1283
|
+
if (fontFamily === "10") {
|
|
1284
|
+
fontFamily = DEFAULT_FONT_FAMILY;
|
|
1285
|
+
}
|
|
1275
1286
|
return {
|
|
1276
1287
|
// Text-specific styles
|
|
1277
1288
|
isLtr: isValidBoolean(style === null || style === void 0 ? void 0 : style.isLtr, true),
|
|
1278
|
-
fontFamily: getFontFamilyByName(
|
|
1289
|
+
fontFamily: getFontFamilyByName(fontFamily),
|
|
1279
1290
|
bigFontFamily: isValidString(style === null || style === void 0 ? void 0 : style.bigFontFamily, "sans-serif"),
|
|
1280
1291
|
textAlign: isValidTextAlignValue(style === null || style === void 0 ? void 0 : style.textAlign),
|
|
1281
1292
|
verticalAlign: isValidVerticalAlignValue(style === null || style === void 0 ? void 0 : style.verticalAlign),
|
package/dist/serialize.js
CHANGED
|
@@ -756,6 +756,7 @@ function writeDocumentGridConfig(b, config, usv) {
|
|
|
756
756
|
})();
|
|
757
757
|
Duc.DocumentGridConfig.addAlignItems(b, alignItems);
|
|
758
758
|
Duc.DocumentGridConfig.addFirstPageAlone(b, config.firstPageAlone);
|
|
759
|
+
Duc.DocumentGridConfig.addScale(b, config.scale);
|
|
759
760
|
return Duc.DocumentGridConfig.endDocumentGridConfig(b);
|
|
760
761
|
}
|
|
761
762
|
function writeText(b, e, usv) {
|
|
@@ -325,6 +325,7 @@ export type DocumentGridConfig = {
|
|
|
325
325
|
gapY: number;
|
|
326
326
|
alignItems: 'start' | 'center' | 'end';
|
|
327
327
|
firstPageAlone: boolean;
|
|
328
|
+
scale: number;
|
|
328
329
|
};
|
|
329
330
|
export type DucPdfElement = _DucElementBase & {
|
|
330
331
|
type: "pdf";
|
|
@@ -470,9 +471,8 @@ export type DucImageElement = _DucElementBase & {
|
|
|
470
471
|
export type InitializedDucImageElement = MarkNonNullable<DucImageElement, "fileId">;
|
|
471
472
|
export type FontFamilyKeys = keyof typeof FONT_FAMILY;
|
|
472
473
|
export type FontFamilyValues = typeof FONT_FAMILY[FontFamilyKeys];
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
};
|
|
474
|
+
/** Font family identifier — any valid CSS font-family string (Google Font name, system font, etc.) */
|
|
475
|
+
export type FontString = string;
|
|
476
476
|
export type TextAlign = ValueOf<typeof TEXT_ALIGN>;
|
|
477
477
|
export type VerticalAlign = ValueOf<typeof VERTICAL_ALIGN>;
|
|
478
478
|
export type LineSpacingType = ValueOf<typeof LINE_SPACING_TYPE>;
|
|
@@ -486,7 +486,7 @@ export type DucTextStyle = {
|
|
|
486
486
|
/**
|
|
487
487
|
* The primary font family to use for the text
|
|
488
488
|
*/
|
|
489
|
-
fontFamily:
|
|
489
|
+
fontFamily: FontString;
|
|
490
490
|
/**
|
|
491
491
|
* Fallback font family for broader compatibility across all systems and languages
|
|
492
492
|
* Useful for emojis, non-latin characters, etc.
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { ElementOrToolType } from "..";
|
|
2
2
|
import type { MarkNonNullable } from "../utility-types";
|
|
3
3
|
import { Bounds, LineSegment, TuplePoint } from "../geometryTypes";
|
|
4
|
-
import type { DucArrowElement, DucBindableElement, DucElbowArrowElement, DucElement, DucElementType, DucEmbeddableElement, DucFlowchartNodeElement, DucFrameElement, DucFrameLikeElement, DucFreeDrawElement, DucImageElement, DucLinearElement, DucPlotElement, DucTableElement, DucPointBinding, DucTextContainer, DucTextElement, DucTextElementWithContainer, FixedPointBinding, InitializedDucImageElement, DucNonSelectionElement, DucEllipseElement, DucPolygonElement, NonDeleted, DucIframeLikeElement } from "./";
|
|
4
|
+
import type { DucArrowElement, DucBindableElement, DucDocElement, DucElbowArrowElement, DucElement, DucElementType, DucEmbeddableElement, DucFlowchartNodeElement, DucFrameElement, DucFrameLikeElement, DucFreeDrawElement, DucImageElement, DucLinearElement, DucPdfElement, DucPlotElement, DucTableElement, DucPointBinding, DucTextContainer, DucTextElement, DucTextElementWithContainer, FixedPointBinding, InitializedDucImageElement, DucNonSelectionElement, DucEllipseElement, DucPolygonElement, NonDeleted, DucIframeLikeElement } from "./";
|
|
5
5
|
export declare const isInitializedImageElement: (element: DucElement | null) => element is InitializedDucImageElement;
|
|
6
6
|
export declare const isImageElement: (element: DucElement | null) => element is DucImageElement;
|
|
7
|
+
export declare const isPdfElement: (element: DucElement | null) => element is DucPdfElement;
|
|
8
|
+
export type DucPdfLikeElement = DucPdfElement | DucDocElement;
|
|
9
|
+
export declare const isPdfLikeElement: (element: DucElement | null) => element is DucPdfLikeElement;
|
|
7
10
|
export declare const isEmbeddableElement: (element: DucElement | null | undefined) => element is DucEmbeddableElement;
|
|
8
11
|
export declare const isTableElement: (element: DucElement | null) => element is DucTableElement;
|
|
9
12
|
export declare const isIframeLikeElement: (element: DucElement | null) => element is DucIframeLikeElement;
|
|
@@ -5,6 +5,12 @@ export const isInitializedImageElement = (element) => {
|
|
|
5
5
|
export const isImageElement = (element) => {
|
|
6
6
|
return !!element && element.type === "image";
|
|
7
7
|
};
|
|
8
|
+
export const isPdfElement = (element) => {
|
|
9
|
+
return !!element && element.type === "pdf";
|
|
10
|
+
};
|
|
11
|
+
export const isPdfLikeElement = (element) => {
|
|
12
|
+
return !!element && (element.type === "pdf" || element.type === "doc");
|
|
13
|
+
};
|
|
8
14
|
export const isEmbeddableElement = (element) => {
|
|
9
15
|
return !!element && element.type === "embeddable";
|
|
10
16
|
};
|
|
@@ -89,24 +89,28 @@ export declare const HIDE_FRAME_NAME_ZOOM_THRESHOLD = 0.18;
|
|
|
89
89
|
export declare const DEFAULT_PROPORTIONAL_RADIUS = 0.25;
|
|
90
90
|
export declare const DEFAULT_ADAPTIVE_RADIUS = 32;
|
|
91
91
|
/**
|
|
92
|
-
*
|
|
92
|
+
* Font family identifiers. Values are the actual CSS font-family names
|
|
93
|
+
* so they can be passed directly to Google Fonts / Canvas2D.
|
|
93
94
|
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* - https://drafts.csswg.org/css-fonts-4/#font-family-prop
|
|
97
|
-
* - https://learn.microsoft.com/en-us/typography/opentype/spec/ibmfc
|
|
95
|
+
* For backward compatibility with old files that stored numeric IDs,
|
|
96
|
+
* use `LEGACY_FONT_ID_TO_NAME` to resolve them.
|
|
98
97
|
*/
|
|
99
98
|
export declare const FONT_FAMILY: {
|
|
100
|
-
Virgil:
|
|
101
|
-
Helvetica:
|
|
102
|
-
Cascadia:
|
|
103
|
-
Excalifont:
|
|
104
|
-
Nunito:
|
|
105
|
-
"Lilita One":
|
|
106
|
-
"Comic Shanns":
|
|
107
|
-
"Liberation Sans":
|
|
108
|
-
"Roboto Mono":
|
|
99
|
+
readonly Virgil: "Virgil";
|
|
100
|
+
readonly Helvetica: "Helvetica";
|
|
101
|
+
readonly Cascadia: "Cascadia";
|
|
102
|
+
readonly Excalifont: "Excalifont";
|
|
103
|
+
readonly Nunito: "Nunito";
|
|
104
|
+
readonly "Lilita One": "Lilita One";
|
|
105
|
+
readonly "Comic Shanns": "Comic Shanns";
|
|
106
|
+
readonly "Liberation Sans": "Liberation Sans";
|
|
107
|
+
readonly "Roboto Mono": "Roboto Mono";
|
|
109
108
|
};
|
|
109
|
+
/**
|
|
110
|
+
* Reverse mapping from legacy numeric font IDs to font family names.
|
|
111
|
+
* Used when loading old .duc files that encoded fontFamily as a number.
|
|
112
|
+
*/
|
|
113
|
+
export declare const LEGACY_FONT_ID_TO_NAME: Record<number, string>;
|
|
110
114
|
export declare const WINDOWS_EMOJI_FALLBACK_FONT = "Segoe UI Emoji";
|
|
111
115
|
export declare const DEFAULT_VERSION = "{version}";
|
|
112
116
|
export declare const MIN_FONT_SIZE = 1;
|
package/dist/utils/constants.js
CHANGED
|
@@ -87,24 +87,37 @@ export const DEFAULT_PROPORTIONAL_RADIUS = 0.25;
|
|
|
87
87
|
// Fixed radius for the ADAPTIVE_RADIUS algorithm. In pixels.
|
|
88
88
|
export const DEFAULT_ADAPTIVE_RADIUS = 32;
|
|
89
89
|
/**
|
|
90
|
-
*
|
|
90
|
+
* Font family identifiers. Values are the actual CSS font-family names
|
|
91
|
+
* so they can be passed directly to Google Fonts / Canvas2D.
|
|
91
92
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* - https://drafts.csswg.org/css-fonts-4/#font-family-prop
|
|
95
|
-
* - https://learn.microsoft.com/en-us/typography/opentype/spec/ibmfc
|
|
93
|
+
* For backward compatibility with old files that stored numeric IDs,
|
|
94
|
+
* use `LEGACY_FONT_ID_TO_NAME` to resolve them.
|
|
96
95
|
*/
|
|
97
96
|
export const FONT_FAMILY = {
|
|
98
|
-
Virgil:
|
|
99
|
-
Helvetica:
|
|
100
|
-
Cascadia:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
|
|
97
|
+
Virgil: "Virgil",
|
|
98
|
+
Helvetica: "Helvetica",
|
|
99
|
+
Cascadia: "Cascadia",
|
|
100
|
+
Excalifont: "Excalifont",
|
|
101
|
+
Nunito: "Nunito",
|
|
102
|
+
"Lilita One": "Lilita One",
|
|
103
|
+
"Comic Shanns": "Comic Shanns",
|
|
104
|
+
"Liberation Sans": "Liberation Sans",
|
|
105
|
+
"Roboto Mono": "Roboto Mono",
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Reverse mapping from legacy numeric font IDs to font family names.
|
|
109
|
+
* Used when loading old .duc files that encoded fontFamily as a number.
|
|
110
|
+
*/
|
|
111
|
+
export const LEGACY_FONT_ID_TO_NAME = {
|
|
112
|
+
1: "Virgil",
|
|
113
|
+
2: "Helvetica",
|
|
114
|
+
3: "Cascadia",
|
|
115
|
+
5: "Excalifont",
|
|
116
|
+
6: "Nunito",
|
|
117
|
+
7: "Lilita One",
|
|
118
|
+
8: "Comic Shanns",
|
|
119
|
+
9: "Liberation Sans",
|
|
120
|
+
10: "Roboto Mono",
|
|
108
121
|
};
|
|
109
122
|
export const WINDOWS_EMOJI_FALLBACK_FONT = "Segoe UI Emoji";
|
|
110
123
|
export const DEFAULT_VERSION = "{version}";
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import { DucFreeDrawElement } from "../../types/elements";
|
|
2
2
|
export declare function getFreeDrawSvgPath(element: DucFreeDrawElement): string;
|
|
3
|
+
/**
|
|
4
|
+
* Returns the raw outline polygon points from perfect-freehand.
|
|
5
|
+
* Each point is [x, y]. The result forms a closed polygon that
|
|
6
|
+
* represents the visual shape of the freedraw stroke.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getFreeDrawStrokePoints(element: DucFreeDrawElement): number[][];
|
|
@@ -22,16 +22,8 @@ function getSvgPathFromStroke(points) {
|
|
|
22
22
|
.join(" ")
|
|
23
23
|
.replace(TO_FIXED_PRECISION, "$1");
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (element.points.length === 0) {
|
|
28
|
-
return "";
|
|
29
|
-
}
|
|
30
|
-
const inputPoints = element.simulatePressure
|
|
31
|
-
? element.points.map(({ x, y }, i) => [x.scoped, y.scoped, element.pressures[i]])
|
|
32
|
-
: element.points.map(({ x, y }) => [x.scoped, y.scoped]);
|
|
33
|
-
// Consider changing the options for simulated pressure vs real pressure
|
|
34
|
-
const options = {
|
|
25
|
+
function buildStrokeOptions(element) {
|
|
26
|
+
return {
|
|
35
27
|
size: element.size.scoped,
|
|
36
28
|
simulatePressure: element.simulatePressure,
|
|
37
29
|
thinning: element.thinning,
|
|
@@ -40,7 +32,32 @@ export function getFreeDrawSvgPath(element) {
|
|
|
40
32
|
easing: element.easing,
|
|
41
33
|
start: element.start || undefined,
|
|
42
34
|
end: element.end || undefined,
|
|
43
|
-
last: !!element.lastCommittedPoint,
|
|
35
|
+
last: !!element.lastCommittedPoint,
|
|
44
36
|
};
|
|
37
|
+
}
|
|
38
|
+
function buildInputPoints(element) {
|
|
39
|
+
return element.simulatePressure
|
|
40
|
+
? element.points.map(({ x, y }, i) => [x.scoped, y.scoped, element.pressures[i]])
|
|
41
|
+
: element.points.map(({ x, y }) => [x.scoped, y.scoped]);
|
|
42
|
+
}
|
|
43
|
+
export function getFreeDrawSvgPath(element) {
|
|
44
|
+
if (element.points.length === 0) {
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
const inputPoints = buildInputPoints(element);
|
|
48
|
+
const options = buildStrokeOptions(element);
|
|
45
49
|
return getSvgPathFromStroke(getStroke(inputPoints, options));
|
|
46
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Returns the raw outline polygon points from perfect-freehand.
|
|
53
|
+
* Each point is [x, y]. The result forms a closed polygon that
|
|
54
|
+
* represents the visual shape of the freedraw stroke.
|
|
55
|
+
*/
|
|
56
|
+
export function getFreeDrawStrokePoints(element) {
|
|
57
|
+
if (element.points.length === 0) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
const inputPoints = buildInputPoints(element);
|
|
61
|
+
const options = buildStrokeOptions(element);
|
|
62
|
+
return getStroke(inputPoints, options);
|
|
63
|
+
}
|
|
@@ -123,13 +123,22 @@ export const newTextElement = (currentScope, opts) => {
|
|
|
123
123
|
const textAlign = opts.textAlign || DEFAULT_TEXT_ALIGN;
|
|
124
124
|
const verticalAlign = opts.verticalAlign || DEFAULT_VERTICAL_ALIGN;
|
|
125
125
|
const offsets = getTextElementPositionOffsets({ textAlign, verticalAlign }, metrics);
|
|
126
|
+
// Minimum dimensions: at least 1px wide, at least one line high (NaN-safe)
|
|
127
|
+
const rawMinLineHeight = fontSize.value * lineHeight;
|
|
128
|
+
const minLineHeight = (Number.isFinite(rawMinLineHeight) && rawMinLineHeight > 0)
|
|
129
|
+
? rawMinLineHeight
|
|
130
|
+
: DEFAULT_FONT_SIZE * lineHeight;
|
|
131
|
+
const finalWidth = (Number.isFinite(metrics.width) && metrics.width > 0) ? metrics.width : 1;
|
|
132
|
+
const finalHeight = (Number.isFinite(metrics.height) && metrics.height > 0)
|
|
133
|
+
? Math.max(metrics.height, minLineHeight)
|
|
134
|
+
: minLineHeight;
|
|
126
135
|
const x = getPrecisionValueFromRaw(opts.x.value - offsets.x, scope, currentScope);
|
|
127
136
|
const y = getPrecisionValueFromRaw(opts.y.value - offsets.y, scope, currentScope);
|
|
128
137
|
return Object.assign(Object.assign({}, _newElementBase("text", currentScope, Object.assign(Object.assign({}, opts), { x, y }))), { type: "text", text,
|
|
129
138
|
fontSize,
|
|
130
139
|
fontFamily,
|
|
131
140
|
textAlign,
|
|
132
|
-
verticalAlign, width: getPrecisionValueFromRaw(
|
|
141
|
+
verticalAlign, width: getPrecisionValueFromRaw(finalWidth, scope, currentScope), height: getPrecisionValueFromRaw(finalHeight, scope, currentScope), containerId: opts.containerId || null, originalText: (_b = opts.originalText) !== null && _b !== void 0 ? _b : text, autoResize: (_c = opts.autoResize) !== null && _c !== void 0 ? _c : true, lineHeight,
|
|
133
142
|
// DucTextStyle properties
|
|
134
143
|
isLtr: (_d = opts.isLtr) !== null && _d !== void 0 ? _d : true, bigFontFamily: opts.bigFontFamily || "sans-serif", lineSpacing: opts.lineSpacing || { type: LINE_SPACING_TYPE.MULTIPLE, value: lineHeight }, obliqueAngle: opts.obliqueAngle || 0, paperTextHeight: opts.paperTextHeight, widthFactor: opts.widthFactor || 1, isUpsideDown: (_e = opts.isUpsideDown) !== null && _e !== void 0 ? _e : false, isBackwards: (_f = opts.isBackwards) !== null && _f !== void 0 ? _f : false, dynamic: opts.dynamic || [] });
|
|
135
144
|
};
|
|
@@ -153,11 +162,11 @@ export const newImageElement = (currentScope, opts) => {
|
|
|
153
162
|
export const newTableElement = (currentScope, opts) => (Object.assign(Object.assign(Object.assign({}, _newElementBase("table", currentScope, opts)), getDefaultTableData(currentScope)), { type: "table" }));
|
|
154
163
|
export const newDocElement = (currentScope, opts) => {
|
|
155
164
|
var _a, _b, _c, _d;
|
|
156
|
-
return (Object.assign(Object.assign({}, _newElementBase("doc", currentScope, opts)), { type: "doc", text: opts.text || "", dynamic: opts.dynamic || [], flowDirection: opts.flowDirection || TEXT_FLOW_DIRECTION.TOP_TO_BOTTOM, columns: opts.columns || { type: COLUMN_TYPE.NO_COLUMNS, definitions: [], autoHeight: true }, autoResize: (_a = opts.autoResize) !== null && _a !== void 0 ? _a : true, fileId: null, gridConfig: { columns: 1, gapX: 0, gapY: 0, alignItems: 'start', firstPageAlone: false },
|
|
165
|
+
return (Object.assign(Object.assign({}, _newElementBase("doc", currentScope, opts)), { type: "doc", text: opts.text || "", dynamic: opts.dynamic || [], flowDirection: opts.flowDirection || TEXT_FLOW_DIRECTION.TOP_TO_BOTTOM, columns: opts.columns || { type: COLUMN_TYPE.NO_COLUMNS, definitions: [], autoHeight: true }, autoResize: (_a = opts.autoResize) !== null && _a !== void 0 ? _a : true, fileId: null, gridConfig: { columns: 1, gapX: 0, gapY: 0, alignItems: 'start', firstPageAlone: false, scale: 1 },
|
|
157
166
|
// DucDocStyle properties
|
|
158
167
|
isLtr: (_b = opts.isLtr) !== null && _b !== void 0 ? _b : true, fontFamily: opts.fontFamily || DEFAULT_FONT_FAMILY, bigFontFamily: opts.bigFontFamily || "sans-serif", textAlign: opts.textAlign || DEFAULT_TEXT_ALIGN, verticalAlign: opts.verticalAlign || DEFAULT_VERTICAL_ALIGN, lineHeight: opts.lineHeight || 1.2, lineSpacing: opts.lineSpacing || { type: LINE_SPACING_TYPE.MULTIPLE, value: 1.2 }, obliqueAngle: opts.obliqueAngle || 0, fontSize: opts.fontSize || getPrecisionValueFromRaw(DEFAULT_FONT_SIZE, currentScope, currentScope), paperTextHeight: opts.paperTextHeight, widthFactor: opts.widthFactor || 1, isUpsideDown: (_c = opts.isUpsideDown) !== null && _c !== void 0 ? _c : false, isBackwards: (_d = opts.isBackwards) !== null && _d !== void 0 ? _d : false, paragraph: opts.paragraph || { firstLineIndent: getPrecisionValueFromRaw(0, currentScope, currentScope), hangingIndent: getPrecisionValueFromRaw(0, currentScope, currentScope), leftIndent: getPrecisionValueFromRaw(0, currentScope, currentScope), rightIndent: getPrecisionValueFromRaw(0, currentScope, currentScope), spaceBefore: getPrecisionValueFromRaw(0, currentScope, currentScope), spaceAfter: getPrecisionValueFromRaw(0, currentScope, currentScope), tabStops: [] }, stackFormat: opts.stackFormat || { autoStack: false, stackChars: [], properties: { upperScale: 0.7, lowerScale: 0.7, alignment: STACKED_TEXT_ALIGN.CENTER } } }));
|
|
159
168
|
};
|
|
160
|
-
export const newPdfElement = (currentScope, opts) => (Object.assign(Object.assign({ fileId: null, gridConfig: { columns: 1, gapX: 0, gapY: 0, alignItems: 'start', firstPageAlone: false } }, _newElementBase("pdf", currentScope, opts)), { type: "pdf" }));
|
|
169
|
+
export const newPdfElement = (currentScope, opts) => (Object.assign(Object.assign({ fileId: null, gridConfig: { columns: 1, gapX: 0, gapY: 0, alignItems: 'start', firstPageAlone: false, scale: 1 } }, _newElementBase("pdf", currentScope, opts)), { type: "pdf" }));
|
|
161
170
|
export const newMermaidElement = (currentScope, opts) => (Object.assign(Object.assign({ source: "", theme: undefined, svgPath: null }, _newElementBase("mermaid", currentScope, opts)), { type: "mermaid" }));
|
|
162
171
|
export const newXRayElement = (currentScope, opts) => (Object.assign(Object.assign({ origin: { x: getPrecisionValueFromRaw(0, currentScope, currentScope), y: getPrecisionValueFromRaw(0, currentScope, currentScope) }, direction: { x: getPrecisionValueFromRaw(1, currentScope, currentScope), y: getPrecisionValueFromRaw(0, currentScope, currentScope) }, startFromOrigin: false, color: '#FF00FF' }, _newElementBase("xray", currentScope, opts)), { type: "xray" }));
|
|
163
172
|
export const newLeaderElement = (currentScope, opts) => {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { SupportedMeasures } from "../../technical/scopes";
|
|
1
2
|
import { DucLocalState, RawValue, Scope, ScopedValue } from "../../types";
|
|
2
3
|
import { DucElement, DucElementType, DucTextContainer, DucTextElement, DucTextElementWithContainer, ElementsMap, FontFamilyValues, FontString, NonDeletedDucElement } from "../../types/elements";
|
|
3
4
|
import { GeometricPoint } from "../../types/geometryTypes";
|
|
4
5
|
import { ExtractSetType } from "../../types/utility-types";
|
|
5
6
|
import { getBoundTextElementPosition } from "./linearElement";
|
|
6
|
-
import { SupportedMeasures } from "../../technical/scopes";
|
|
7
7
|
export declare const computeBoundTextPosition: (container: DucElement, boundTextElement: DucTextElementWithContainer, elementsMap: ElementsMap, currentScope: SupportedMeasures) => {
|
|
8
8
|
x: ScopedValue;
|
|
9
9
|
y: ScopedValue;
|
|
@@ -49,12 +49,12 @@ export declare const getMinTextElementWidth: (font: FontString, lineHeight: DucT
|
|
|
49
49
|
/** retrieves text from text elements and concatenates to a single string */
|
|
50
50
|
export declare const getTextFromElements: (elements: readonly DucElement[], separator?: string) => string;
|
|
51
51
|
export declare const getFontFamilyString: ({ fontFamily, }: {
|
|
52
|
-
fontFamily: FontFamilyValues;
|
|
52
|
+
fontFamily: FontFamilyValues | string;
|
|
53
53
|
}) => string;
|
|
54
54
|
/** returns fontSize+fontFamily string for assignment to DOM elements */
|
|
55
55
|
export declare const getFontString: ({ fontSize, fontFamily, }: {
|
|
56
56
|
fontSize: DucTextElement["fontSize"];
|
|
57
|
-
fontFamily: FontFamilyValues;
|
|
57
|
+
fontFamily: FontFamilyValues | string;
|
|
58
58
|
}) => FontString;
|
|
59
59
|
/** computes element x/y offset based on textAlign/verticalAlign */
|
|
60
60
|
export declare const getTextElementPositionOffsets: (opts: {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { TEXT_ALIGN, VERTICAL_ALIGN } from "../../flatbuffers/duc";
|
|
2
|
+
import { getPrecisionValueFromRaw, getScopedBezierPointFromDucPoint } from "../../technical/scopes";
|
|
2
3
|
import { isArrowElement, isBoundToContainer, isTextElement } from "../../types/elements/typeChecks";
|
|
3
4
|
import { getContainerElement, getElementAbsoluteCoords, getResizedElementAbsoluteCoords } from "../bounds";
|
|
4
|
-
import { ARROW_LABEL_FONT_SIZE_TO_MIN_WIDTH_RATIO, ARROW_LABEL_WIDTH_FRACTION, BOUND_TEXT_PADDING, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE,
|
|
5
|
-
import { getBoundTextElementPosition, getPointGlobalCoordinates, getPointsGlobalCoordinates, getSegmentMidPoint } from "./linearElement";
|
|
5
|
+
import { ARROW_LABEL_FONT_SIZE_TO_MIN_WIDTH_RATIO, ARROW_LABEL_WIDTH_FRACTION, BOUND_TEXT_PADDING, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, LEGACY_FONT_ID_TO_NAME, WINDOWS_EMOJI_FALLBACK_FONT } from "../constants";
|
|
6
6
|
import { adjustXYWithRotation } from "../math";
|
|
7
7
|
import { normalizeText } from "../normalize";
|
|
8
|
-
import {
|
|
8
|
+
import { getBoundTextElementPosition, getPointGlobalCoordinates, getPointsGlobalCoordinates, getSegmentMidPoint } from "./linearElement";
|
|
9
9
|
export const computeBoundTextPosition = (container, boundTextElement, elementsMap, currentScope) => {
|
|
10
10
|
if (isArrowElement(container)) {
|
|
11
11
|
const coords = getBoundTextElementPosition(container, boundTextElement, elementsMap, currentScope);
|
|
@@ -54,10 +54,19 @@ export const measureText = (text, font, lineHeight, currentScope) => {
|
|
|
54
54
|
// lines would be stripped from computation
|
|
55
55
|
.map((x) => x || " ")
|
|
56
56
|
.join("\n");
|
|
57
|
-
const
|
|
57
|
+
const parsedFontSize = parseFloat(font);
|
|
58
|
+
// Guard: if font string produced an unparseable size (NaN) or zero,
|
|
59
|
+
// fall back to DEFAULT_FONT_SIZE so measurements are never degenerate.
|
|
60
|
+
const safeFontSize = (Number.isFinite(parsedFontSize) && parsedFontSize > 0)
|
|
61
|
+
? parsedFontSize
|
|
62
|
+
: DEFAULT_FONT_SIZE;
|
|
63
|
+
const fontSize = getPrecisionValueFromRaw(safeFontSize, currentScope, currentScope);
|
|
58
64
|
const height = getTextHeight(text, fontSize, lineHeight);
|
|
59
65
|
const width = getTextWidth(text, font);
|
|
60
|
-
return
|
|
66
|
+
// Defensive: ensure we never return 0 or NaN dimensions
|
|
67
|
+
const safeWidth = (Number.isFinite(width) && width > 0) ? width : 1;
|
|
68
|
+
const safeHeight = (Number.isFinite(height) && height > 0) ? height : (safeFontSize * lineHeight);
|
|
69
|
+
return { width: safeWidth, height: safeHeight };
|
|
61
70
|
};
|
|
62
71
|
/**
|
|
63
72
|
* We calculate the line height from the font size and the unitless line height,
|
|
@@ -104,15 +113,15 @@ const getLineWidth = (text, font, forceAdvanceWidth, isTestEnv) => {
|
|
|
104
113
|
// fallback to advance width if the actual width is zero, i.e. on text editing start
|
|
105
114
|
// or when actual width does not respect whitespace chars, i.e. spaces
|
|
106
115
|
// otherwise actual width should always be bigger
|
|
107
|
-
return Math.max(actualWidth, advanceWidth);
|
|
116
|
+
return Math.ceil(Math.max(actualWidth, advanceWidth));
|
|
108
117
|
}
|
|
109
118
|
// since in test env the canvas measureText algo
|
|
110
119
|
// doesn't measure text and instead just returns number of
|
|
111
120
|
// characters hence we assume that each letteris 10px
|
|
112
121
|
if (isTestEnv) {
|
|
113
|
-
return advanceWidth * 10;
|
|
122
|
+
return Math.ceil(advanceWidth * 10);
|
|
114
123
|
}
|
|
115
|
-
return advanceWidth;
|
|
124
|
+
return Math.ceil(advanceWidth);
|
|
116
125
|
};
|
|
117
126
|
export const getTextWidth = (text, font, forceAdvanceWidth) => {
|
|
118
127
|
const lines = splitIntoLines(text);
|
|
@@ -470,14 +479,20 @@ export const getTextFromElements = (elements, separator = "\n\n") => {
|
|
|
470
479
|
return text;
|
|
471
480
|
};
|
|
472
481
|
export const getFontFamilyString = ({ fontFamily, }) => {
|
|
473
|
-
// Handle
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (
|
|
477
|
-
return `${
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
|
|
482
|
+
// Handle legacy numeric font IDs from old files
|
|
483
|
+
if (typeof fontFamily === "number") {
|
|
484
|
+
const name = LEGACY_FONT_ID_TO_NAME[fontFamily];
|
|
485
|
+
if (name)
|
|
486
|
+
return `${name}, ${WINDOWS_EMOJI_FALLBACK_FONT}`;
|
|
487
|
+
return WINDOWS_EMOJI_FALLBACK_FONT;
|
|
488
|
+
}
|
|
489
|
+
// Handle stringified numeric IDs (e.g. "10")
|
|
490
|
+
const parsed = Number(fontFamily);
|
|
491
|
+
if (!Number.isNaN(parsed) && LEGACY_FONT_ID_TO_NAME[parsed]) {
|
|
492
|
+
return `${LEGACY_FONT_ID_TO_NAME[parsed]}, ${WINDOWS_EMOJI_FALLBACK_FONT}`;
|
|
493
|
+
}
|
|
494
|
+
// New path: fontFamily is already a string name
|
|
495
|
+
return `${fontFamily}, ${WINDOWS_EMOJI_FALLBACK_FONT}`;
|
|
481
496
|
};
|
|
482
497
|
/** returns fontSize+fontFamily string for assignment to DOM elements */
|
|
483
498
|
export const getFontString = ({ fontSize, fontFamily, }) => {
|
|
@@ -504,6 +519,18 @@ export const refreshTextDimensions = (textElement, container, elementsMap, curre
|
|
|
504
519
|
: textElement.width.scoped);
|
|
505
520
|
}
|
|
506
521
|
const dimensions = getAdjustedDimensions(textElement, elementsMap, text, currentScope);
|
|
522
|
+
// Defensive minimums — ensure height is always at least one line.
|
|
523
|
+
// Use negated >= to also catch NaN (NaN < x is always false).
|
|
524
|
+
const rawMinLineHeight = textElement.fontSize.value * textElement.lineHeight;
|
|
525
|
+
const minLineHeight = (Number.isFinite(rawMinLineHeight) && rawMinLineHeight > 0)
|
|
526
|
+
? rawMinLineHeight
|
|
527
|
+
: DEFAULT_FONT_SIZE * textElement.lineHeight;
|
|
528
|
+
if (!(dimensions.height >= minLineHeight)) {
|
|
529
|
+
dimensions.height = minLineHeight;
|
|
530
|
+
}
|
|
531
|
+
if (!(dimensions.width > 0)) {
|
|
532
|
+
dimensions.width = (textElement.autoResize ? 1 : textElement.width.value);
|
|
533
|
+
}
|
|
507
534
|
return Object.assign({ text }, dimensions);
|
|
508
535
|
};
|
|
509
536
|
export const splitIntoLines = (text) => {
|
|
@@ -78,7 +78,7 @@ export const getDefaultGlobalState = () => {
|
|
|
78
78
|
return {
|
|
79
79
|
name: null,
|
|
80
80
|
viewBackgroundColor: typeof window !== "undefined" ? (window.matchMedia("(prefers-color-scheme: dark)").matches ? COLOR_PALETTE.night : COLOR_PALETTE.white) : COLOR_PALETTE.white,
|
|
81
|
-
scopeExponentThreshold:
|
|
81
|
+
scopeExponentThreshold: 3,
|
|
82
82
|
mainScope: NEUTRAL_SCOPE,
|
|
83
83
|
dashSpacingScale: 1,
|
|
84
84
|
isDashSpacingAffectedByViewportScale: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ducjs",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.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",
|