@flyfish-dev/dwf-viewer 0.5.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/CHANGELOG.md +12 -0
- package/LICENSE +235 -0
- package/NOTICE +10 -0
- package/PRODUCTION_3D_NOTES.md +48 -0
- package/README.md +203 -0
- package/dist/format/document.d.ts +186 -0
- package/dist/format/document.js +9 -0
- package/dist/format/dwf.d.ts +4 -0
- package/dist/format/dwf.js +372 -0
- package/dist/format/dwfx.d.ts +6 -0
- package/dist/format/dwfx.js +425 -0
- package/dist/format/emodelMetadata.d.ts +10 -0
- package/dist/format/emodelMetadata.js +368 -0
- package/dist/format/inflate.d.ts +4 -0
- package/dist/format/inflate.js +28 -0
- package/dist/format/opc.d.ts +28 -0
- package/dist/format/opc.js +85 -0
- package/dist/format/open.d.ts +3 -0
- package/dist/format/open.js +69 -0
- package/dist/format/types.d.ts +61 -0
- package/dist/format/types.js +6 -0
- package/dist/format/util.d.ts +18 -0
- package/dist/format/util.js +324 -0
- package/dist/format/w2dBinary.d.ts +19 -0
- package/dist/format/w2dBinary.js +629 -0
- package/dist/format/w2dText.d.ts +13 -0
- package/dist/format/w2dText.js +166 -0
- package/dist/format/w3d.d.ts +8 -0
- package/dist/format/w3d.js +826 -0
- package/dist/format/zip.d.ts +30 -0
- package/dist/format/zip.js +141 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +9 -0
- package/dist/render/PageRenderer.d.ts +27 -0
- package/dist/render/PageRenderer.js +92 -0
- package/dist/render/ThreeJsSceneAdapter.d.ts +29 -0
- package/dist/render/ThreeJsSceneAdapter.js +52 -0
- package/dist/render/ThreeW3dRenderer.d.ts +24 -0
- package/dist/render/ThreeW3dRenderer.js +372 -0
- package/dist/render/W2dRenderer.d.ts +24 -0
- package/dist/render/W2dRenderer.js +198 -0
- package/dist/render/WebGlW2dBackend.d.ts +38 -0
- package/dist/render/WebGlW2dBackend.js +400 -0
- package/dist/render/XpsRenderer.d.ts +20 -0
- package/dist/render/XpsRenderer.js +310 -0
- package/dist/render/style.d.ts +16 -0
- package/dist/render/style.js +115 -0
- package/dist/render/viewport.d.ts +16 -0
- package/dist/render/viewport.js +27 -0
- package/dist/render/xpsPath.d.ts +41 -0
- package/dist/render/xpsPath.js +335 -0
- package/dist/viewer/DwfViewer.d.ts +69 -0
- package/dist/viewer/DwfViewer.js +386 -0
- package/dist/wasm/WasmRasterBackend.d.ts +21 -0
- package/dist/wasm/WasmRasterBackend.js +84 -0
- package/package.json +91 -0
- package/public/dwfv-render.wasm +0 -0
- package/styles/dwf-viewer.css +51 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
export const textDecoder = new TextDecoder('utf-8');
|
|
2
|
+
export const asciiDecoder = new TextDecoder('ascii');
|
|
3
|
+
export function decodeUtf8(bytes) {
|
|
4
|
+
return textDecoder.decode(bytes);
|
|
5
|
+
}
|
|
6
|
+
export function decodeUtf16Le(bytes) {
|
|
7
|
+
return new TextDecoder('utf-16le').decode(bytes);
|
|
8
|
+
}
|
|
9
|
+
export function normalizePath(path) {
|
|
10
|
+
const parts = [];
|
|
11
|
+
const normalized = path.replace(/\\/g, '/');
|
|
12
|
+
for (const part of normalized.split('/')) {
|
|
13
|
+
if (!part || part === '.')
|
|
14
|
+
continue;
|
|
15
|
+
if (part === '..')
|
|
16
|
+
parts.pop();
|
|
17
|
+
else
|
|
18
|
+
parts.push(part);
|
|
19
|
+
}
|
|
20
|
+
return parts.join('/');
|
|
21
|
+
}
|
|
22
|
+
export function dirname(path) {
|
|
23
|
+
const n = normalizePath(path);
|
|
24
|
+
const i = n.lastIndexOf('/');
|
|
25
|
+
return i < 0 ? '' : n.slice(0, i);
|
|
26
|
+
}
|
|
27
|
+
export function resolvePart(basePath, target) {
|
|
28
|
+
if (!target)
|
|
29
|
+
return normalizePath(basePath);
|
|
30
|
+
if (target.startsWith('/'))
|
|
31
|
+
return normalizePath(target.slice(1));
|
|
32
|
+
const baseDir = dirname(basePath);
|
|
33
|
+
return normalizePath((baseDir ? baseDir + '/' : '') + target);
|
|
34
|
+
}
|
|
35
|
+
export function stripNamespace(name) {
|
|
36
|
+
const i = name.indexOf(':');
|
|
37
|
+
return i >= 0 ? name.slice(i + 1) : name;
|
|
38
|
+
}
|
|
39
|
+
export function localName(el) {
|
|
40
|
+
return stripNamespace(el.localName || el.nodeName);
|
|
41
|
+
}
|
|
42
|
+
export function getAttr(el, name) {
|
|
43
|
+
return el.getAttribute(name) ?? el.getAttributeNS(null, name) ?? undefined;
|
|
44
|
+
}
|
|
45
|
+
export function childElements(el) {
|
|
46
|
+
return Array.from(el.childNodes).filter((n) => n.nodeType === 1);
|
|
47
|
+
}
|
|
48
|
+
export function parseXml(xml, source = 'document.xml') {
|
|
49
|
+
if (typeof DOMParser !== 'undefined') {
|
|
50
|
+
const doc = new DOMParser().parseFromString(xml, 'application/xml');
|
|
51
|
+
const err = doc.getElementsByTagName('parsererror')[0];
|
|
52
|
+
if (err)
|
|
53
|
+
throw new Error(`XML parse error in ${source}: ${err.textContent?.slice(0, 180) ?? 'unknown error'}`);
|
|
54
|
+
return doc;
|
|
55
|
+
}
|
|
56
|
+
// Node.js and some worker-like runtimes do not expose DOMParser. Keep the core
|
|
57
|
+
// package dependency-free by falling back to a small XML tree parser that implements
|
|
58
|
+
// only the DOM surface used by the DWF/DWFx readers: getElementsByTagName,
|
|
59
|
+
// getAttribute, attributes, localName, nodeName, nodeType, childNodes, and textContent.
|
|
60
|
+
try {
|
|
61
|
+
return parseXmlFallback(xml);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
throw new Error(`XML parse error in ${source}: ${String(err)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
class SimpleXmlElement {
|
|
68
|
+
constructor(name, attrs) {
|
|
69
|
+
this.nodeType = 1;
|
|
70
|
+
this.childNodes = [];
|
|
71
|
+
this.nodeName = name;
|
|
72
|
+
this.localName = stripNamespace(name);
|
|
73
|
+
this.attributes = attrs;
|
|
74
|
+
}
|
|
75
|
+
get textContent() {
|
|
76
|
+
return this.childNodes.map(n => n.textContent ?? '').join('');
|
|
77
|
+
}
|
|
78
|
+
get children() {
|
|
79
|
+
return this.childNodes.filter((n) => n instanceof SimpleXmlElement);
|
|
80
|
+
}
|
|
81
|
+
getAttribute(name) {
|
|
82
|
+
return this.attributes.find(a => a.name === name || a.nodeName === name)?.value ?? null;
|
|
83
|
+
}
|
|
84
|
+
getAttributeNS(_ns, name) {
|
|
85
|
+
return this.attributes.find(a => a.localName === name)?.value ?? null;
|
|
86
|
+
}
|
|
87
|
+
getElementsByTagName(name) {
|
|
88
|
+
const out = [];
|
|
89
|
+
const visit = (el) => {
|
|
90
|
+
if (name === '*' || el.nodeName === name || el.localName === name)
|
|
91
|
+
out.push(el);
|
|
92
|
+
for (const child of el.childNodes)
|
|
93
|
+
if (child instanceof SimpleXmlElement)
|
|
94
|
+
visit(child);
|
|
95
|
+
};
|
|
96
|
+
for (const child of this.childNodes)
|
|
97
|
+
if (child instanceof SimpleXmlElement)
|
|
98
|
+
visit(child);
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
class SimpleXmlText {
|
|
103
|
+
constructor(textContent) {
|
|
104
|
+
this.textContent = textContent;
|
|
105
|
+
this.nodeType = 3;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
class SimpleXmlDocument {
|
|
109
|
+
constructor() {
|
|
110
|
+
this.nodeType = 9;
|
|
111
|
+
this.childNodes = [];
|
|
112
|
+
}
|
|
113
|
+
get textContent() {
|
|
114
|
+
return this.childNodes.map(n => n.textContent ?? '').join('');
|
|
115
|
+
}
|
|
116
|
+
get children() {
|
|
117
|
+
return this.childNodes.filter((n) => n instanceof SimpleXmlElement);
|
|
118
|
+
}
|
|
119
|
+
get documentElement() {
|
|
120
|
+
return this.children[0];
|
|
121
|
+
}
|
|
122
|
+
getElementsByTagName(name) {
|
|
123
|
+
const out = [];
|
|
124
|
+
const visit = (el) => {
|
|
125
|
+
if (name === '*' || el.nodeName === name || el.localName === name)
|
|
126
|
+
out.push(el);
|
|
127
|
+
for (const child of el.childNodes)
|
|
128
|
+
if (child instanceof SimpleXmlElement)
|
|
129
|
+
visit(child);
|
|
130
|
+
};
|
|
131
|
+
for (const child of this.childNodes)
|
|
132
|
+
if (child instanceof SimpleXmlElement)
|
|
133
|
+
visit(child);
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function parseXmlFallback(xml) {
|
|
138
|
+
const doc = new SimpleXmlDocument();
|
|
139
|
+
const stack = [doc];
|
|
140
|
+
let pos = 0;
|
|
141
|
+
while (pos < xml.length) {
|
|
142
|
+
const lt = xml.indexOf('<', pos);
|
|
143
|
+
if (lt < 0) {
|
|
144
|
+
appendText(stack[stack.length - 1], xml.slice(pos));
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
if (lt > pos)
|
|
148
|
+
appendText(stack[stack.length - 1], xml.slice(pos, lt));
|
|
149
|
+
if (xml.startsWith('<!--', lt)) {
|
|
150
|
+
const end = xml.indexOf('-->', lt + 4);
|
|
151
|
+
if (end < 0)
|
|
152
|
+
throw new Error('Unclosed XML comment.');
|
|
153
|
+
pos = end + 3;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (xml.startsWith('<![CDATA[', lt)) {
|
|
157
|
+
const end = xml.indexOf(']]>', lt + 9);
|
|
158
|
+
if (end < 0)
|
|
159
|
+
throw new Error('Unclosed XML CDATA section.');
|
|
160
|
+
appendText(stack[stack.length - 1], xml.slice(lt + 9, end));
|
|
161
|
+
pos = end + 3;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (xml.startsWith('<?', lt)) {
|
|
165
|
+
const end = xml.indexOf('?>', lt + 2);
|
|
166
|
+
if (end < 0)
|
|
167
|
+
throw new Error('Unclosed XML processing instruction.');
|
|
168
|
+
pos = end + 2;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (xml.startsWith('<!', lt)) {
|
|
172
|
+
const end = xml.indexOf('>', lt + 2);
|
|
173
|
+
if (end < 0)
|
|
174
|
+
throw new Error('Unclosed XML declaration.');
|
|
175
|
+
pos = end + 1;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const gt = findTagEnd(xml, lt + 1);
|
|
179
|
+
if (gt < 0)
|
|
180
|
+
throw new Error('Unclosed XML tag.');
|
|
181
|
+
let tag = xml.slice(lt + 1, gt).trim();
|
|
182
|
+
pos = gt + 1;
|
|
183
|
+
if (!tag)
|
|
184
|
+
continue;
|
|
185
|
+
if (tag[0] === '/') {
|
|
186
|
+
const name = tag.slice(1).trim().split(/\s+/)[0] ?? '';
|
|
187
|
+
let popped = stack.pop();
|
|
188
|
+
while (popped && popped instanceof SimpleXmlElement && popped.nodeName !== name && popped.localName !== stripNamespace(name) && stack.length > 0) {
|
|
189
|
+
popped = stack.pop();
|
|
190
|
+
}
|
|
191
|
+
if (stack.length === 0)
|
|
192
|
+
stack.push(doc);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const selfClosing = tag.endsWith('/');
|
|
196
|
+
if (selfClosing)
|
|
197
|
+
tag = tag.slice(0, -1).trim();
|
|
198
|
+
const space = tag.search(/\s/);
|
|
199
|
+
const name = space < 0 ? tag : tag.slice(0, space);
|
|
200
|
+
const attrSource = space < 0 ? '' : tag.slice(space + 1);
|
|
201
|
+
const el = new SimpleXmlElement(name, parseAttrs(attrSource));
|
|
202
|
+
const parent = stack[stack.length - 1];
|
|
203
|
+
el.parentNode = parent;
|
|
204
|
+
parent.childNodes.push(el);
|
|
205
|
+
if (!selfClosing)
|
|
206
|
+
stack.push(el);
|
|
207
|
+
}
|
|
208
|
+
return doc;
|
|
209
|
+
}
|
|
210
|
+
function appendText(parent, raw) {
|
|
211
|
+
if (!raw)
|
|
212
|
+
return;
|
|
213
|
+
const text = decodeXmlEntities(raw);
|
|
214
|
+
if (text.trim().length === 0)
|
|
215
|
+
return;
|
|
216
|
+
parent.childNodes.push(new SimpleXmlText(text));
|
|
217
|
+
}
|
|
218
|
+
function findTagEnd(xml, start) {
|
|
219
|
+
let quote = '';
|
|
220
|
+
for (let i = start; i < xml.length; i++) {
|
|
221
|
+
const c = xml[i];
|
|
222
|
+
if (quote) {
|
|
223
|
+
if (c === quote)
|
|
224
|
+
quote = '';
|
|
225
|
+
}
|
|
226
|
+
else if (c === '"' || c === "'") {
|
|
227
|
+
quote = c;
|
|
228
|
+
}
|
|
229
|
+
else if (c === '>') {
|
|
230
|
+
return i;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return -1;
|
|
234
|
+
}
|
|
235
|
+
function parseAttrs(input) {
|
|
236
|
+
const attrs = [];
|
|
237
|
+
const re = /([^\s=]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
238
|
+
let m;
|
|
239
|
+
while ((m = re.exec(input))) {
|
|
240
|
+
const name = m[1];
|
|
241
|
+
const value = decodeXmlEntities(m[2] ?? m[3] ?? '');
|
|
242
|
+
attrs.push({ name, nodeName: name, localName: stripNamespace(name), value });
|
|
243
|
+
}
|
|
244
|
+
return attrs;
|
|
245
|
+
}
|
|
246
|
+
function decodeXmlEntities(input) {
|
|
247
|
+
return input.replace(/&(#x[0-9a-fA-F]+|#\d+|amp|lt|gt|quot|apos);/g, (_m, ent) => {
|
|
248
|
+
if (ent === 'amp')
|
|
249
|
+
return '&';
|
|
250
|
+
if (ent === 'lt')
|
|
251
|
+
return '<';
|
|
252
|
+
if (ent === 'gt')
|
|
253
|
+
return '>';
|
|
254
|
+
if (ent === 'quot')
|
|
255
|
+
return '"';
|
|
256
|
+
if (ent === 'apos')
|
|
257
|
+
return "'";
|
|
258
|
+
if (ent.startsWith('#x'))
|
|
259
|
+
return String.fromCodePoint(Number.parseInt(ent.slice(2), 16));
|
|
260
|
+
if (ent.startsWith('#'))
|
|
261
|
+
return String.fromCodePoint(Number.parseInt(ent.slice(1), 10));
|
|
262
|
+
return _m;
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
export function bytesLookTextual(bytes, sampleLength = 2048) {
|
|
266
|
+
const n = Math.min(sampleLength, bytes.length);
|
|
267
|
+
if (n === 0)
|
|
268
|
+
return true;
|
|
269
|
+
let printable = 0;
|
|
270
|
+
let control = 0;
|
|
271
|
+
for (let i = 0; i < n; i++) {
|
|
272
|
+
const b = bytes[i];
|
|
273
|
+
if (b === 0)
|
|
274
|
+
return false;
|
|
275
|
+
if (b === 9 || b === 10 || b === 13 || (b >= 32 && b <= 126) || b >= 160)
|
|
276
|
+
printable++;
|
|
277
|
+
else
|
|
278
|
+
control++;
|
|
279
|
+
}
|
|
280
|
+
return printable / n > 0.85 && control < 20;
|
|
281
|
+
}
|
|
282
|
+
export function extname(path) {
|
|
283
|
+
const clean = path.split(/[?#]/)[0] ?? path;
|
|
284
|
+
const slash = clean.lastIndexOf('/');
|
|
285
|
+
const dot = clean.lastIndexOf('.');
|
|
286
|
+
return dot > slash ? clean.slice(dot + 1).toLowerCase() : '';
|
|
287
|
+
}
|
|
288
|
+
export function mimeFromPath(path) {
|
|
289
|
+
switch (extname(path)) {
|
|
290
|
+
case 'png': return 'image/png';
|
|
291
|
+
case 'jpg':
|
|
292
|
+
case 'jpeg': return 'image/jpeg';
|
|
293
|
+
case 'gif': return 'image/gif';
|
|
294
|
+
case 'bmp': return 'image/bmp';
|
|
295
|
+
case 'tif':
|
|
296
|
+
case 'tiff': return 'image/tiff';
|
|
297
|
+
case 'w2d': return 'application/x-dwf-w2d';
|
|
298
|
+
case 'hsf': return 'model/vnd.hsf';
|
|
299
|
+
case 'fpage': return 'application/vnd.ms-package.xps-fixeddocumentsequence+xml';
|
|
300
|
+
case 'fdseq': return 'application/vnd.ms-package.xps-fixeddocumentsequence+xml';
|
|
301
|
+
case 'fdoc': return 'application/vnd.ms-package.xps-fixeddocument+xml';
|
|
302
|
+
default: return undefined;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
export async function blobToImage(bytes, type) {
|
|
306
|
+
const blob = new Blob([bytes], { type });
|
|
307
|
+
if ('createImageBitmap' in globalThis) {
|
|
308
|
+
return await createImageBitmap(blob);
|
|
309
|
+
}
|
|
310
|
+
return await new Promise((resolve, reject) => {
|
|
311
|
+
const url = URL.createObjectURL(blob);
|
|
312
|
+
const img = new Image();
|
|
313
|
+
img.onload = () => { URL.revokeObjectURL(url); resolve(img); };
|
|
314
|
+
img.onerror = () => { URL.revokeObjectURL(url); reject(new Error('Failed to decode image')); };
|
|
315
|
+
img.src = url;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
export function clamp(n, lo, hi) {
|
|
319
|
+
return Math.max(lo, Math.min(hi, n));
|
|
320
|
+
}
|
|
321
|
+
export function parseNumberList(input) {
|
|
322
|
+
const matches = input.match(/[-+]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][-+]?\d+)?/g);
|
|
323
|
+
return matches ? matches.map(Number).filter(Number.isFinite) : [];
|
|
324
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Diagnostic } from './types.js';
|
|
2
|
+
import type { W2dPrimitive } from './document.js';
|
|
3
|
+
export interface W2dBinaryParseResult {
|
|
4
|
+
primitives: W2dPrimitive[];
|
|
5
|
+
diagnostics: Diagnostic[];
|
|
6
|
+
bounds?: {
|
|
7
|
+
minX: number;
|
|
8
|
+
minY: number;
|
|
9
|
+
maxX: number;
|
|
10
|
+
maxY: number;
|
|
11
|
+
};
|
|
12
|
+
opcodes: number;
|
|
13
|
+
unsupportedOpcodes: Array<{
|
|
14
|
+
offset: number;
|
|
15
|
+
opcode: number | string;
|
|
16
|
+
message: string;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
export declare function parseW2dBinary(bytes: Uint8Array, sourcePath: string): W2dBinaryParseResult;
|