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,425 @@
|
|
|
1
|
+
import { diag } from './types.js';
|
|
2
|
+
import { createOpcView } from './opc.js';
|
|
3
|
+
import { decodeUtf8, localName, mimeFromPath, parseXml, normalizePath, resolvePart } from './util.js';
|
|
4
|
+
import { makeLoadedDocument } from './document.js';
|
|
5
|
+
import { parseW3dModel } from './w3d.js';
|
|
6
|
+
import { enrichW3dModelFromEModelResources } from './emodelMetadata.js';
|
|
7
|
+
const XPS_FIXED_REP_TYPES = new Set([
|
|
8
|
+
'http://schemas.microsoft.com/xps/2005/06/fixedrepresentation',
|
|
9
|
+
'http://schemas.openxps.org/oxps/v1.0/fixedrepresentation'
|
|
10
|
+
]);
|
|
11
|
+
export async function openDwfx(zip) {
|
|
12
|
+
const opc = await createOpcView(zip);
|
|
13
|
+
const diagnostics = [];
|
|
14
|
+
const pageData = [];
|
|
15
|
+
// DWFx is an OPC package, but many Autodesk-produced DWFx files are not
|
|
16
|
+
// pure XPS documents. They may contain a DWF manifest with ePlot/eModel
|
|
17
|
+
// sections. Prefer that manifest when present so synthetic XPS notice pages
|
|
18
|
+
// are not shown as real drawings, and so eModel-only packages can still open
|
|
19
|
+
// with their embedded thumbnail/preview image.
|
|
20
|
+
const manifestSections = await collectDwfxManifestPages(opc, pageData, diagnostics);
|
|
21
|
+
if (pageData.length === 0) {
|
|
22
|
+
const fdseqs = await findFixedDocumentSequences(opc, diagnostics);
|
|
23
|
+
for (const fdseqPath of fdseqs) {
|
|
24
|
+
try {
|
|
25
|
+
const fdseqXml = await opc.readText(fdseqPath);
|
|
26
|
+
const fdseqDoc = parseXml(fdseqXml, fdseqPath);
|
|
27
|
+
const docRefs = Array.from(fdseqDoc.getElementsByTagName('*')).filter(e => localName(e) === 'DocumentReference');
|
|
28
|
+
if (docRefs.length === 0)
|
|
29
|
+
diagnostics.push(diag('warning', 'DWFX_NO_DOCREF', `No DocumentReference elements found in ${fdseqPath}.`, fdseqPath));
|
|
30
|
+
for (const docRef of docRefs) {
|
|
31
|
+
const source = docRef.getAttribute('Source');
|
|
32
|
+
if (!source)
|
|
33
|
+
continue;
|
|
34
|
+
const fdocPath = resolvePart(fdseqPath, source);
|
|
35
|
+
await collectFixedDocumentPages(opc, fdocPath, pageData, diagnostics);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
diagnostics.push(diag('warning', 'DWFX_FDSEQ_READ_FAILED', `Failed to read fixed document sequence: ${String(err)}`, fdseqPath));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (pageData.length === 0) {
|
|
44
|
+
// DWFx produced by some tools omits the package-level relationship; fall back to extension scan.
|
|
45
|
+
const fpages = zip.entries.filter(e => /\.fpage$/i.test(e.name));
|
|
46
|
+
for (const [index, entry] of fpages.entries()) {
|
|
47
|
+
const size = await readFixedPageSize(opc, entry.name, diagnostics);
|
|
48
|
+
pageData.push({
|
|
49
|
+
id: `page-${index + 1}`,
|
|
50
|
+
name: entry.name.split('/').pop() ?? `Page ${index + 1}`,
|
|
51
|
+
kind: 'xps-fixed-page',
|
|
52
|
+
sourcePath: entry.name,
|
|
53
|
+
width: size.width,
|
|
54
|
+
height: size.height,
|
|
55
|
+
diagnostics: []
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (pageData.length === 0) {
|
|
60
|
+
const has3d = zip.entries.some(e => /\.(w3d|hsf)$/i.test(e.name)) || manifestSections.some(s => /emodel/i.test(s.type));
|
|
61
|
+
pageData.push({
|
|
62
|
+
id: 'unsupported-1',
|
|
63
|
+
name: has3d ? 'Unsupported 3D DWFx package' : 'Unsupported DWFx package',
|
|
64
|
+
kind: 'unsupported',
|
|
65
|
+
width: 1000,
|
|
66
|
+
height: 1000,
|
|
67
|
+
reason: has3d
|
|
68
|
+
? 'This DWFx package contains 3D eModel/W3D content but no supported 2D FixedPage or preview image resource was found.'
|
|
69
|
+
: 'No XPS FixedPage parts or supported DWF manifest preview resources were found.',
|
|
70
|
+
diagnostics: [diag('error', 'DWFX_NO_PAGES', 'No supported renderable page was found in this DWFx package.')]
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const base = {
|
|
74
|
+
kind: 'dwfx',
|
|
75
|
+
pages: [],
|
|
76
|
+
resources: opc.resources,
|
|
77
|
+
diagnostics,
|
|
78
|
+
packageEntries: zip.entries.map(e => e.name)
|
|
79
|
+
};
|
|
80
|
+
return makeLoadedDocument(base, pageData, zip, opc);
|
|
81
|
+
}
|
|
82
|
+
export function isProbablyDwfx(zip) {
|
|
83
|
+
return zip.has('[Content_Types].xml') && (zip.has('_rels/.rels') || zip.entries.some(e => /\.(fpage|dwfseq)$/i.test(e.name)));
|
|
84
|
+
}
|
|
85
|
+
async function collectDwfxManifestPages(opc, pageData, diagnostics) {
|
|
86
|
+
const manifestPaths = await findDwfManifestPaths(opc, diagnostics);
|
|
87
|
+
const allSections = [];
|
|
88
|
+
for (const manifestPath of manifestPaths) {
|
|
89
|
+
try {
|
|
90
|
+
const xml = await opc.readText(manifestPath);
|
|
91
|
+
const sections = parseDwfManifestSections(xml, manifestPath);
|
|
92
|
+
allSections.push(...sections);
|
|
93
|
+
diagnostics.push(diag('info', 'DWFX_DWF_MANIFEST_FOUND', `Found DWFx DWF manifest with ${sections.length} section(s).`, manifestPath));
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
diagnostics.push(diag('warning', 'DWFX_DWF_MANIFEST_PARSE_FAILED', `Failed to parse DWFx DWF manifest: ${String(err)}`, manifestPath));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// First create real 2D drawing pages. Do not include eModel thumbnails when
|
|
100
|
+
// ePlot pages are available; otherwise the first page can become a 3D preview
|
|
101
|
+
// instead of the first sheet.
|
|
102
|
+
for (const section of allSections.filter(s => isEPlotSection(s))) {
|
|
103
|
+
const fixedPage = resourceByRole(section, /2d\s+streaming\s+graphics/i)
|
|
104
|
+
?? section.resources.find(r => /\.fpage$/i.test(r.path));
|
|
105
|
+
if (!fixedPage || !/\.fpage$/i.test(stripQuery(fixedPage.path)))
|
|
106
|
+
continue;
|
|
107
|
+
const pagePath = stripQuery(fixedPage.path);
|
|
108
|
+
if (!opc.zip.has(pagePath))
|
|
109
|
+
continue;
|
|
110
|
+
const size = await readFixedPageSize(opc, pagePath, diagnostics);
|
|
111
|
+
pageData.push({
|
|
112
|
+
id: `page-${pageData.length + 1}`,
|
|
113
|
+
name: section.title || fixedPage.title || basename(pagePath),
|
|
114
|
+
kind: 'xps-fixed-page',
|
|
115
|
+
sourcePath: pagePath,
|
|
116
|
+
width: Number.isFinite(size.width) && size.width > 0 ? size.width : 1000,
|
|
117
|
+
height: Number.isFinite(size.height) && size.height > 0 ? size.height : 1000,
|
|
118
|
+
diagnostics: []
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (pageData.length > 0)
|
|
122
|
+
return allSections;
|
|
123
|
+
// eModel-only DWFx packages carry W3D/HSF geometry plus optional thumbnails.
|
|
124
|
+
// Production policy: when a W3D/HSF resource is present, treat that resource as
|
|
125
|
+
// authoritative. Do not silently fall back to a thumbnail if decoding fails;
|
|
126
|
+
// otherwise a real 3D model can be mistaken for a 2D image preview.
|
|
127
|
+
for (const section of allSections.filter(s => isEModelSection(s))) {
|
|
128
|
+
const preview = chooseSectionPreviewResource(section, opc.zip);
|
|
129
|
+
const w3d = resourceByRole(section, /3d\s+streaming\s+graphics/i) ?? section.resources.find(r => /\.(w3d|hsf)$/i.test(r.path));
|
|
130
|
+
if (w3d && opc.zip.has(w3d.path)) {
|
|
131
|
+
try {
|
|
132
|
+
const model = await parseW3dModel(await opc.zip.read(w3d.path), { title: section.title || w3d.title || basename(w3d.path), sourcePath: w3d.path });
|
|
133
|
+
await enrichW3dModelFromEModelResources(model, section.resources, opc);
|
|
134
|
+
if (model.meshes.length > 0) {
|
|
135
|
+
pageData.push({
|
|
136
|
+
id: `w3d-${pageData.length + 1}`,
|
|
137
|
+
name: section.title || w3d.title || basename(w3d.path),
|
|
138
|
+
kind: 'w3d-model',
|
|
139
|
+
sourcePath: w3d.path,
|
|
140
|
+
width: 1000,
|
|
141
|
+
height: 1000,
|
|
142
|
+
model,
|
|
143
|
+
diagnostics: model.diagnostics
|
|
144
|
+
});
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
pageData.push({
|
|
148
|
+
id: `unsupported-3d-${pageData.length + 1}`,
|
|
149
|
+
name: section.title || 'Unsupported 3D eModel',
|
|
150
|
+
kind: 'unsupported',
|
|
151
|
+
width: 1000,
|
|
152
|
+
height: 1000,
|
|
153
|
+
sourcePath: w3d.path,
|
|
154
|
+
reason: 'A W3D/HSF 3D stream exists, but no renderable shell geometry was decoded. The package is intentionally not shown as a thumbnail so this failure is visible during production validation.',
|
|
155
|
+
diagnostics: [
|
|
156
|
+
...model.diagnostics,
|
|
157
|
+
diag('warning', 'DWFX_3D_W3D_UNSUPPORTED', 'Detected a 3D W3D resource, but no renderable shell geometry was decoded.', w3d.path)
|
|
158
|
+
]
|
|
159
|
+
});
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
pageData.push({
|
|
164
|
+
id: `unsupported-3d-${pageData.length + 1}`,
|
|
165
|
+
name: section.title || 'Unsupported 3D eModel',
|
|
166
|
+
kind: 'unsupported',
|
|
167
|
+
width: 1000,
|
|
168
|
+
height: 1000,
|
|
169
|
+
sourcePath: w3d.path,
|
|
170
|
+
reason: `A W3D/HSF 3D stream exists, but geometry parsing failed: ${String(err)}`,
|
|
171
|
+
diagnostics: [diag('warning', 'DWFX_W3D_PARSE_FAILED', `Failed to parse W3D geometry: ${String(err)}`, w3d.path)]
|
|
172
|
+
});
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Only eModel sections without a W3D/HSF geometry stream may be represented
|
|
177
|
+
// by their thumbnail/preview image.
|
|
178
|
+
if (preview && opc.zip.has(preview.path)) {
|
|
179
|
+
const bytes = await opc.zip.read(preview.path);
|
|
180
|
+
const size = imageSizeFromBytes(bytes) ?? { width: 1000, height: 1000 };
|
|
181
|
+
pageData.push({
|
|
182
|
+
id: `image-${pageData.length + 1}`,
|
|
183
|
+
name: section.title || preview.title || basename(preview.path),
|
|
184
|
+
kind: 'image',
|
|
185
|
+
sourcePath: preview.path,
|
|
186
|
+
mediaType: preview.mediaType ?? mimeFromPath(preview.path) ?? 'image/png',
|
|
187
|
+
width: size.width,
|
|
188
|
+
height: size.height,
|
|
189
|
+
diagnostics: [
|
|
190
|
+
diag('info', 'DWFX_3D_PREVIEW_ONLY', 'No W3D/HSF geometry stream was declared; showing the embedded section preview image.', preview.path)
|
|
191
|
+
]
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return allSections;
|
|
196
|
+
}
|
|
197
|
+
async function findDwfManifestPaths(opc, diagnostics) {
|
|
198
|
+
const out = new Set();
|
|
199
|
+
const add = (p) => { if (p)
|
|
200
|
+
out.add(normalizePath(stripQuery(p))); };
|
|
201
|
+
// Package root relationship -> DWFDocumentSequence.dwfseq -> ManifestReference.
|
|
202
|
+
const dwfseqs = new Set();
|
|
203
|
+
try {
|
|
204
|
+
const rootRels = await opc.getRelationships('');
|
|
205
|
+
for (const rel of rootRels) {
|
|
206
|
+
if (/documentsequence/i.test(rel.type) || /\.dwfseq$/i.test(rel.resolvedTarget))
|
|
207
|
+
dwfseqs.add(normalizePath(rel.resolvedTarget));
|
|
208
|
+
if (/document$/i.test(rel.type) && /manifest\.xml$/i.test(rel.resolvedTarget))
|
|
209
|
+
add(rel.resolvedTarget);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
diagnostics.push(diag('warning', 'DWFX_DWFSEQ_RELS_FAILED', `Failed to inspect DWFx package relationships: ${String(err)}`));
|
|
214
|
+
}
|
|
215
|
+
for (const e of opc.zip.entries)
|
|
216
|
+
if (/\.dwfseq$/i.test(e.name))
|
|
217
|
+
dwfseqs.add(e.name);
|
|
218
|
+
for (const dwfseqPath of dwfseqs) {
|
|
219
|
+
if (!opc.zip.has(dwfseqPath))
|
|
220
|
+
continue;
|
|
221
|
+
try {
|
|
222
|
+
const xml = await opc.readText(dwfseqPath);
|
|
223
|
+
const doc = parseXml(xml, dwfseqPath);
|
|
224
|
+
const refs = Array.from(doc.getElementsByTagName('*')).filter(e => localName(e) === 'ManifestReference');
|
|
225
|
+
for (const ref of refs)
|
|
226
|
+
add(ref.getAttribute('Source') ?? ref.getAttribute('source') ?? undefined);
|
|
227
|
+
const rels = await opc.getRelationships(dwfseqPath).catch(() => []);
|
|
228
|
+
for (const rel of rels)
|
|
229
|
+
if (/document/i.test(rel.type) || /manifest\.xml$/i.test(rel.resolvedTarget))
|
|
230
|
+
add(rel.resolvedTarget);
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
diagnostics.push(diag('warning', 'DWFX_DWFSEQ_READ_FAILED', `Failed to read DWFDocumentSequence ${dwfseqPath}: ${String(err)}`, dwfseqPath));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Last-resort scan. Limit to DWF manifests under /dwf/documents to avoid
|
|
237
|
+
// treating unrelated XML as a manifest.
|
|
238
|
+
for (const e of opc.zip.entries) {
|
|
239
|
+
if (/^dwf\/documents\/.*\/manifest\.xml$/i.test(e.name))
|
|
240
|
+
add(e.name);
|
|
241
|
+
}
|
|
242
|
+
return Array.from(out).filter(p => opc.zip.has(p));
|
|
243
|
+
}
|
|
244
|
+
async function findFixedDocumentSequences(opc, diagnostics) {
|
|
245
|
+
const out = new Set();
|
|
246
|
+
try {
|
|
247
|
+
const rootRels = await opc.getRelationships('');
|
|
248
|
+
for (const rel of rootRels) {
|
|
249
|
+
if (XPS_FIXED_REP_TYPES.has(rel.type) || /fixedrepresentation/i.test(rel.type) || /\.fdseq$/i.test(rel.resolvedTarget)) {
|
|
250
|
+
out.add(normalizePath(rel.resolvedTarget));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
diagnostics.push(diag('warning', 'DWFX_ROOT_RELS_FAILED', `Failed to read package relationships: ${String(err)}`));
|
|
256
|
+
}
|
|
257
|
+
for (const o of opc.overrides) {
|
|
258
|
+
if (/fixeddocumentsequence/i.test(o.contentType) || /\.fdseq$/i.test(o.partName))
|
|
259
|
+
out.add(o.partName);
|
|
260
|
+
}
|
|
261
|
+
for (const e of opc.zip.entries) {
|
|
262
|
+
if (/\.fdseq$/i.test(e.name))
|
|
263
|
+
out.add(e.name);
|
|
264
|
+
}
|
|
265
|
+
return Array.from(out);
|
|
266
|
+
}
|
|
267
|
+
async function collectFixedDocumentPages(opc, fdocPath, pageData, diagnostics) {
|
|
268
|
+
let xml;
|
|
269
|
+
try {
|
|
270
|
+
xml = await opc.readText(fdocPath);
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
diagnostics.push(diag('warning', 'DWFX_FDOC_READ_FAILED', `Failed to read fixed document ${fdocPath}: ${String(err)}`, fdocPath));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const doc = parseXml(xml, fdocPath);
|
|
277
|
+
const pageContents = Array.from(doc.getElementsByTagName('*')).filter(e => localName(e) === 'PageContent');
|
|
278
|
+
for (const pageContent of pageContents) {
|
|
279
|
+
const source = pageContent.getAttribute('Source');
|
|
280
|
+
if (!source)
|
|
281
|
+
continue;
|
|
282
|
+
let pagePath = resolvePart(fdocPath, stripQuery(source));
|
|
283
|
+
const relId = source.startsWith('#') ? source.slice(1) : undefined;
|
|
284
|
+
if (relId) {
|
|
285
|
+
const rels = await opc.getRelationships(fdocPath);
|
|
286
|
+
const rel = rels.find(r => r.id === relId);
|
|
287
|
+
if (rel)
|
|
288
|
+
pagePath = rel.resolvedTarget;
|
|
289
|
+
}
|
|
290
|
+
pagePath = stripQuery(pagePath);
|
|
291
|
+
const widthAttr = pageContent.getAttribute('Width');
|
|
292
|
+
const heightAttr = pageContent.getAttribute('Height');
|
|
293
|
+
const size = widthAttr && heightAttr ? { width: Number(widthAttr), height: Number(heightAttr) } : await readFixedPageSize(opc, pagePath, diagnostics);
|
|
294
|
+
const index = pageData.length + 1;
|
|
295
|
+
pageData.push({
|
|
296
|
+
id: `page-${index}`,
|
|
297
|
+
name: `Page ${index}`,
|
|
298
|
+
kind: 'xps-fixed-page',
|
|
299
|
+
sourcePath: pagePath,
|
|
300
|
+
width: Number.isFinite(size.width) && size.width > 0 ? size.width : 1000,
|
|
301
|
+
height: Number.isFinite(size.height) && size.height > 0 ? size.height : 1000,
|
|
302
|
+
diagnostics: []
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async function readFixedPageSize(opc, pagePath, diagnostics) {
|
|
307
|
+
try {
|
|
308
|
+
const clean = stripQuery(pagePath);
|
|
309
|
+
const xml = decodeUtf8(await opc.readBytes(clean));
|
|
310
|
+
const doc = parseXml(xml, clean);
|
|
311
|
+
const root = doc.documentElement ?? Array.from(doc.getElementsByTagName('*'))[0];
|
|
312
|
+
return { width: Number(root?.getAttribute('Width') ?? 1000), height: Number(root?.getAttribute('Height') ?? 1000) };
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
diagnostics.push(diag('warning', 'DWFX_PAGE_SIZE_FAILED', `Failed to read FixedPage size: ${String(err)}`, pagePath));
|
|
316
|
+
return { width: 1000, height: 1000 };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function parseDwfManifestSections(xml, sourcePath) {
|
|
320
|
+
const doc = parseXml(xml, sourcePath);
|
|
321
|
+
const sections = Array.from(doc.getElementsByTagName('*')).filter(el => localName(el) === 'Section');
|
|
322
|
+
return sections.map((section, index) => {
|
|
323
|
+
const name = attrAny(section, 'name') ?? `section-${index + 1}`;
|
|
324
|
+
const title = attrAny(section, 'title') ?? name;
|
|
325
|
+
const type = attrAny(section, 'type') ?? '';
|
|
326
|
+
const resources = Array.from(section.getElementsByTagName('*'))
|
|
327
|
+
.filter(el => localName(el).endsWith('Resource') || localName(el) === 'Resource')
|
|
328
|
+
.map(el => {
|
|
329
|
+
const rawPath = attrAny(el, 'href') ?? '';
|
|
330
|
+
return {
|
|
331
|
+
path: normalizePath(stripQuery(rawPath)),
|
|
332
|
+
role: attrAny(el, 'role'),
|
|
333
|
+
mediaType: attrAny(el, 'mime'),
|
|
334
|
+
title: attrAny(el, 'title'),
|
|
335
|
+
size: numberAttr(el, 'size')
|
|
336
|
+
};
|
|
337
|
+
})
|
|
338
|
+
.filter(r => r.path.length > 0);
|
|
339
|
+
return { type, name, title, resources };
|
|
340
|
+
}).filter(s => s.resources.length > 0 || s.type.length > 0);
|
|
341
|
+
}
|
|
342
|
+
function isEPlotSection(section) {
|
|
343
|
+
return /(^|\.)ePlot$/i.test(section.type) || /com\.autodesk\.dwf\.ePlot$/i.test(section.type);
|
|
344
|
+
}
|
|
345
|
+
function isEModelSection(section) {
|
|
346
|
+
return /(^|\.)eModel$/i.test(section.type) || /com\.autodesk\.dwf\.eModel$/i.test(section.type);
|
|
347
|
+
}
|
|
348
|
+
function chooseSectionPreviewResource(section, zip) {
|
|
349
|
+
const candidates = section.resources.filter(r => /^(image\/|application\/octet-stream)/i.test(r.mediaType ?? '') || /\.(png|jpe?g|gif|bmp)$/i.test(r.path));
|
|
350
|
+
const thumbnails = candidates.filter(r => /thumbnail|preview/i.test(r.role ?? '') || /thumbnail|preview/i.test(r.title ?? ''));
|
|
351
|
+
const pool = thumbnails.length > 0 ? thumbnails : candidates.filter(r => !/texture/i.test(r.role ?? ''));
|
|
352
|
+
const finalPool = pool.length > 0 ? pool : candidates;
|
|
353
|
+
return finalPool
|
|
354
|
+
.filter(r => zip.has(r.path))
|
|
355
|
+
.sort((a, b) => scorePreview(b) - scorePreview(a))[0];
|
|
356
|
+
}
|
|
357
|
+
function scorePreview(r) {
|
|
358
|
+
let s = r.size ?? 0;
|
|
359
|
+
if (/thumbnail/i.test(r.role ?? ''))
|
|
360
|
+
s += 5000000;
|
|
361
|
+
if (/preview/i.test(r.role ?? ''))
|
|
362
|
+
s += 4000000;
|
|
363
|
+
if (/icon/i.test(r.role ?? ''))
|
|
364
|
+
s -= 2000000;
|
|
365
|
+
if (/texture/i.test(r.role ?? ''))
|
|
366
|
+
s -= 3000000;
|
|
367
|
+
return s;
|
|
368
|
+
}
|
|
369
|
+
function attrAny(el, name) {
|
|
370
|
+
return el.getAttribute(name) ?? el.getAttribute(`dwf:${name}`) ?? el.getAttribute(`ePlot:${name}`) ?? el.getAttribute(`eModel:${name}`) ?? Array.from(el.attributes).find(a => a.localName === name)?.value ?? undefined;
|
|
371
|
+
}
|
|
372
|
+
function numberAttr(el, name) {
|
|
373
|
+
const raw = attrAny(el, name);
|
|
374
|
+
if (raw === undefined)
|
|
375
|
+
return undefined;
|
|
376
|
+
const n = Number(raw);
|
|
377
|
+
return Number.isFinite(n) ? n : undefined;
|
|
378
|
+
}
|
|
379
|
+
function resourceByRole(section, re) {
|
|
380
|
+
return section.resources.find(r => re.test(r.role ?? ''));
|
|
381
|
+
}
|
|
382
|
+
function stripQuery(path) {
|
|
383
|
+
return normalizePath((path.split(/[?#]/)[0] ?? path).replace(/^\//, ''));
|
|
384
|
+
}
|
|
385
|
+
function basename(path) {
|
|
386
|
+
const clean = stripQuery(path);
|
|
387
|
+
const i = clean.lastIndexOf('/');
|
|
388
|
+
return i >= 0 ? clean.slice(i + 1) : clean;
|
|
389
|
+
}
|
|
390
|
+
function imageSizeFromBytes(bytes) {
|
|
391
|
+
if (bytes.length >= 24 && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) {
|
|
392
|
+
return { width: be32(bytes, 16), height: be32(bytes, 20) };
|
|
393
|
+
}
|
|
394
|
+
if (bytes.length >= 4 && bytes[0] === 0xff && bytes[1] === 0xd8) {
|
|
395
|
+
let p = 2;
|
|
396
|
+
while (p + 9 < bytes.length) {
|
|
397
|
+
if (bytes[p] !== 0xff) {
|
|
398
|
+
p++;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
const marker = bytes[p + 1];
|
|
402
|
+
p += 2;
|
|
403
|
+
while (bytes[p] === 0xff)
|
|
404
|
+
p++;
|
|
405
|
+
if (marker === 0xd9 || marker === 0xda)
|
|
406
|
+
break;
|
|
407
|
+
if (p + 2 > bytes.length)
|
|
408
|
+
break;
|
|
409
|
+
const len = (bytes[p] << 8) | bytes[p + 1];
|
|
410
|
+
if (len < 2 || p + len > bytes.length)
|
|
411
|
+
break;
|
|
412
|
+
if ((marker >= 0xc0 && marker <= 0xc3) || (marker >= 0xc5 && marker <= 0xc7) || (marker >= 0xc9 && marker <= 0xcb) || (marker >= 0xcd && marker <= 0xcf)) {
|
|
413
|
+
return { width: (bytes[p + 5] << 8) | bytes[p + 6], height: (bytes[p + 3] << 8) | bytes[p + 4] };
|
|
414
|
+
}
|
|
415
|
+
p += len;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
function be32(bytes, offset) {
|
|
421
|
+
return ((bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]) >>> 0;
|
|
422
|
+
}
|
|
423
|
+
export function getRelationshipById(rels, id) {
|
|
424
|
+
return rels.find(r => r.id === id);
|
|
425
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { OpcPackageView } from './opc.js';
|
|
2
|
+
import type { W3dModelData } from './document.js';
|
|
3
|
+
export interface EModelResourceLike {
|
|
4
|
+
path: string;
|
|
5
|
+
role?: string;
|
|
6
|
+
mediaType?: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
size?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function enrichW3dModelFromEModelResources(model: W3dModelData, resources: EModelResourceLike[], opc: OpcPackageView): Promise<void>;
|