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,186 @@
|
|
|
1
|
+
import type { Diagnostic, DwfDocument, RenderablePage } from './types.js';
|
|
2
|
+
import type { ZipPackage } from './zip.js';
|
|
3
|
+
import type { OpcPackageView } from './opc.js';
|
|
4
|
+
import type { PathCommand } from '../render/xpsPath.js';
|
|
5
|
+
import type { Matrix2D } from '../render/style.js';
|
|
6
|
+
export interface LoadedDwfDocument extends DwfDocument {
|
|
7
|
+
zip?: ZipPackage;
|
|
8
|
+
opc?: OpcPackageView;
|
|
9
|
+
pageData: PageData[];
|
|
10
|
+
}
|
|
11
|
+
export type PageData = XpsPageData | W2dTextPageData | ImagePageData | W3dPageData | UnsupportedPageData;
|
|
12
|
+
export interface BasePageData extends RenderablePage {
|
|
13
|
+
diagnostics: Diagnostic[];
|
|
14
|
+
}
|
|
15
|
+
export interface XpsPageData extends BasePageData {
|
|
16
|
+
kind: 'xps-fixed-page';
|
|
17
|
+
sourcePath: string;
|
|
18
|
+
}
|
|
19
|
+
export interface W2dPrimitiveBase {
|
|
20
|
+
stroke?: string;
|
|
21
|
+
fill?: string;
|
|
22
|
+
lineWidth?: number;
|
|
23
|
+
matrix?: Matrix2D;
|
|
24
|
+
}
|
|
25
|
+
export type W2dPrimitive = (W2dPrimitiveBase & {
|
|
26
|
+
type: 'polyline';
|
|
27
|
+
points: number[];
|
|
28
|
+
}) | (W2dPrimitiveBase & {
|
|
29
|
+
type: 'polygon';
|
|
30
|
+
points: number[];
|
|
31
|
+
}) | (W2dPrimitiveBase & {
|
|
32
|
+
type: 'path';
|
|
33
|
+
commands: PathCommand[];
|
|
34
|
+
}) | (W2dPrimitiveBase & {
|
|
35
|
+
type: 'text';
|
|
36
|
+
x: number;
|
|
37
|
+
y: number;
|
|
38
|
+
text: string;
|
|
39
|
+
size?: number;
|
|
40
|
+
}) | (W2dPrimitiveBase & {
|
|
41
|
+
type: 'rect';
|
|
42
|
+
x: number;
|
|
43
|
+
y: number;
|
|
44
|
+
width: number;
|
|
45
|
+
height: number;
|
|
46
|
+
});
|
|
47
|
+
export interface W2dTextPageData extends BasePageData {
|
|
48
|
+
kind: 'w2d-text';
|
|
49
|
+
sourcePath: string;
|
|
50
|
+
primitives: W2dPrimitive[];
|
|
51
|
+
bounds?: {
|
|
52
|
+
minX: number;
|
|
53
|
+
minY: number;
|
|
54
|
+
maxX: number;
|
|
55
|
+
maxY: number;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export interface ImagePageData extends BasePageData {
|
|
59
|
+
kind: 'image';
|
|
60
|
+
sourcePath: string;
|
|
61
|
+
mediaType: string;
|
|
62
|
+
}
|
|
63
|
+
export interface W3dMaterialData {
|
|
64
|
+
id: string;
|
|
65
|
+
name?: string;
|
|
66
|
+
color: [number, number, number];
|
|
67
|
+
opacity?: number;
|
|
68
|
+
roughness?: number;
|
|
69
|
+
metalness?: number;
|
|
70
|
+
textureId?: string;
|
|
71
|
+
textureName?: string;
|
|
72
|
+
doubleSided?: boolean;
|
|
73
|
+
source?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface W3dTextureData {
|
|
76
|
+
id: string;
|
|
77
|
+
name?: string;
|
|
78
|
+
path: string;
|
|
79
|
+
mediaType?: string;
|
|
80
|
+
width?: number;
|
|
81
|
+
height?: number;
|
|
82
|
+
role?: string;
|
|
83
|
+
}
|
|
84
|
+
export interface W3dSceneNodeData {
|
|
85
|
+
id: string;
|
|
86
|
+
label: string;
|
|
87
|
+
contentRefs: string[];
|
|
88
|
+
meshIds: string[];
|
|
89
|
+
children: W3dSceneNodeData[];
|
|
90
|
+
properties?: Record<string, string>;
|
|
91
|
+
}
|
|
92
|
+
export interface W3dPmiAnnotationData {
|
|
93
|
+
id: string;
|
|
94
|
+
type: 'text' | 'leader' | 'markup' | 'unknown';
|
|
95
|
+
label?: string;
|
|
96
|
+
text?: string;
|
|
97
|
+
targetRef?: string;
|
|
98
|
+
position?: [number, number, number];
|
|
99
|
+
points?: Array<[number, number, number]>;
|
|
100
|
+
properties?: Record<string, string>;
|
|
101
|
+
}
|
|
102
|
+
export interface W3dViewData {
|
|
103
|
+
id: string;
|
|
104
|
+
label: string;
|
|
105
|
+
camera?: {
|
|
106
|
+
position?: [number, number, number];
|
|
107
|
+
target?: [number, number, number];
|
|
108
|
+
up?: [number, number, number];
|
|
109
|
+
field?: [number, number, number];
|
|
110
|
+
projection?: 'perspective' | 'orthographic' | string;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export interface W3dAnimationData {
|
|
114
|
+
id: string;
|
|
115
|
+
name: string;
|
|
116
|
+
targetRef?: string;
|
|
117
|
+
keyframes?: Array<{
|
|
118
|
+
time: number;
|
|
119
|
+
matrix?: number[];
|
|
120
|
+
visibility?: boolean;
|
|
121
|
+
}>;
|
|
122
|
+
}
|
|
123
|
+
export interface W3dMeshData {
|
|
124
|
+
id: string;
|
|
125
|
+
name: string;
|
|
126
|
+
positions: Float32Array;
|
|
127
|
+
normals?: Float32Array;
|
|
128
|
+
indices: Uint16Array | Uint32Array;
|
|
129
|
+
edgeIndices?: Uint16Array | Uint32Array;
|
|
130
|
+
vertexCount: number;
|
|
131
|
+
triangleCount: number;
|
|
132
|
+
bounds: {
|
|
133
|
+
min: [number, number, number];
|
|
134
|
+
max: [number, number, number];
|
|
135
|
+
};
|
|
136
|
+
color?: [number, number, number];
|
|
137
|
+
materialId?: string;
|
|
138
|
+
selectionRefs?: string[];
|
|
139
|
+
nodeId?: string;
|
|
140
|
+
sourceStart: number;
|
|
141
|
+
sourceEnd: number;
|
|
142
|
+
decodeKind: 'uncompressed' | `quantized-${number}` | `edgebreaker-v${number}`;
|
|
143
|
+
}
|
|
144
|
+
export interface W3dModelData {
|
|
145
|
+
kind: 'w3d-model';
|
|
146
|
+
title: string;
|
|
147
|
+
sourcePath?: string;
|
|
148
|
+
meshes: W3dMeshData[];
|
|
149
|
+
bounds: {
|
|
150
|
+
min: [number, number, number];
|
|
151
|
+
max: [number, number, number];
|
|
152
|
+
center: [number, number, number];
|
|
153
|
+
radius: number;
|
|
154
|
+
};
|
|
155
|
+
stats: {
|
|
156
|
+
meshCount: number;
|
|
157
|
+
vertexCount: number;
|
|
158
|
+
triangleCount: number;
|
|
159
|
+
decodedBytes: number;
|
|
160
|
+
edgeCount?: number;
|
|
161
|
+
materialCount?: number;
|
|
162
|
+
textureCount?: number;
|
|
163
|
+
pmiCount?: number;
|
|
164
|
+
nodeCount?: number;
|
|
165
|
+
};
|
|
166
|
+
materials?: W3dMaterialData[];
|
|
167
|
+
textures?: W3dTextureData[];
|
|
168
|
+
sceneTree?: W3dSceneNodeData[];
|
|
169
|
+
pmi?: W3dPmiAnnotationData[];
|
|
170
|
+
views?: W3dViewData[];
|
|
171
|
+
animations?: W3dAnimationData[];
|
|
172
|
+
initialView?: W3dViewData;
|
|
173
|
+
metadata?: Record<string, string>;
|
|
174
|
+
diagnostics: Diagnostic[];
|
|
175
|
+
}
|
|
176
|
+
export interface W3dPageData extends BasePageData {
|
|
177
|
+
kind: 'w3d-model';
|
|
178
|
+
sourcePath: string;
|
|
179
|
+
model: W3dModelData;
|
|
180
|
+
}
|
|
181
|
+
export interface UnsupportedPageData extends BasePageData {
|
|
182
|
+
kind: 'unsupported';
|
|
183
|
+
sourcePath?: string;
|
|
184
|
+
reason: string;
|
|
185
|
+
}
|
|
186
|
+
export declare function makeLoadedDocument(base: DwfDocument, pageData: PageData[], zip?: ZipPackage, opc?: OpcPackageView): LoadedDwfDocument;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function makeLoadedDocument(base, pageData, zip, opc) {
|
|
2
|
+
return {
|
|
3
|
+
...base,
|
|
4
|
+
pages: pageData.map(p => ({ id: p.id, name: p.name, kind: p.kind, width: p.width, height: p.height, sourcePath: p.sourcePath, diagnostics: p.diagnostics })),
|
|
5
|
+
pageData,
|
|
6
|
+
zip,
|
|
7
|
+
opc
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { diag } from './types.js';
|
|
2
|
+
import { bytesLookTextual, decodeUtf8, extname, mimeFromPath, normalizePath, parseNumberList, parseXml } from './util.js';
|
|
3
|
+
import { makeLoadedDocument } from './document.js';
|
|
4
|
+
import { parseW2dText } from './w2dText.js';
|
|
5
|
+
import { parseW2dBinary } from './w2dBinary.js';
|
|
6
|
+
export function isProbablyClassicDwf(zip) {
|
|
7
|
+
return zip.entries.some(e => /\.w2d$/i.test(e.name) || /manifest\.xml$/i.test(e.name) || /descriptor\.xml$/i.test(e.name) || /\.hsf$/i.test(e.name));
|
|
8
|
+
}
|
|
9
|
+
export async function openClassicDwf(zip) {
|
|
10
|
+
const diagnostics = [];
|
|
11
|
+
const pageData = [];
|
|
12
|
+
const resources = zip.entries.map(e => ({ path: e.name, mediaType: mimeFromPath(e.name), size: e.uncompressedSize }));
|
|
13
|
+
const manifestEntry = zip.entries.find(e => /(^|\/)manifest\.xml$/i.test(e.name));
|
|
14
|
+
let manifestSections = [];
|
|
15
|
+
if (manifestEntry) {
|
|
16
|
+
try {
|
|
17
|
+
manifestSections = parseManifestSections(decodeUtf8(await zip.read(manifestEntry)), manifestEntry.name);
|
|
18
|
+
diagnostics.push(diag('info', 'DWF_MANIFEST_FOUND', `Found DWF manifest with ${manifestSections.length} section(s).`, manifestEntry.name));
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
diagnostics.push(diag('warning', 'DWF_MANIFEST_PARSE_FAILED', `Failed to parse DWF manifest: ${String(err)}`, manifestEntry.name));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
diagnostics.push(diag('info', 'DWF_NO_MANIFEST', 'No manifest.xml was found; falling back to resource discovery.'));
|
|
26
|
+
}
|
|
27
|
+
if (manifestSections.length > 0) {
|
|
28
|
+
for (const section of manifestSections) {
|
|
29
|
+
const mainGraphic = resourceByRole(section, /2d\s+streaming\s+graphics/i) ?? section.resources.find(r => /\.w2d$/i.test(r.path));
|
|
30
|
+
if (!mainGraphic)
|
|
31
|
+
continue;
|
|
32
|
+
const descriptorResource = resourceByRole(section, /^descriptor$/i) ?? { path: normalizePath(section.name + '/descriptor.xml') };
|
|
33
|
+
let descriptor = {};
|
|
34
|
+
if (descriptorResource && zip.has(descriptorResource.path)) {
|
|
35
|
+
try {
|
|
36
|
+
descriptor = parseDescriptorInfo(decodeUtf8(await zip.read(descriptorResource.path)), mainGraphic.path);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
diagnostics.push(diag('warning', 'DWF_DESCRIPTOR_PARSE_FAILED', `Failed to parse descriptor for ${section.title}: ${String(err)}`, descriptorResource.path));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const pageDiagnostics = [];
|
|
43
|
+
const primitives = [];
|
|
44
|
+
try {
|
|
45
|
+
const parsed = await parseW2dResource(zip, mainGraphic.path);
|
|
46
|
+
primitives.push(...parsed.primitives);
|
|
47
|
+
pageDiagnostics.push(...parsed.diagnostics);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
pageDiagnostics.push(diag('warning', 'DWF_W2D_READ_FAILED', `Failed to read or parse ${mainGraphic.path}: ${String(err)}`, mainGraphic.path));
|
|
51
|
+
}
|
|
52
|
+
for (const markupXml of section.resources.filter(r => /markup\s+private/i.test(r.role ?? '') && /\.xml$/i.test(r.path))) {
|
|
53
|
+
if (!zip.has(markupXml.path))
|
|
54
|
+
continue;
|
|
55
|
+
try {
|
|
56
|
+
const parsed = parseMarkupPrivateXml(decodeUtf8(await zip.read(markupXml.path)), markupXml.path, descriptor.w2dTransform);
|
|
57
|
+
primitives.push(...parsed.primitives);
|
|
58
|
+
pageDiagnostics.push(...parsed.diagnostics);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
pageDiagnostics.push(diag('warning', 'DWF_MARKUP_XML_PARSE_FAILED', `Failed to parse markup private XML: ${String(err)}`, markupXml.path));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (primitives.length > 0) {
|
|
65
|
+
const bounds = computeBounds(primitives);
|
|
66
|
+
const width = bounds ? Math.max(1, bounds.maxX - bounds.minX) : descriptor.paperWidth ?? 1000;
|
|
67
|
+
const height = bounds ? Math.max(1, bounds.maxY - bounds.minY) : descriptor.paperHeight ?? 1000;
|
|
68
|
+
pageData.push({
|
|
69
|
+
id: `page-${pageData.length + 1}`,
|
|
70
|
+
name: section.title || mainGraphic.title || basename(mainGraphic.path),
|
|
71
|
+
kind: 'w2d-text',
|
|
72
|
+
sourcePath: mainGraphic.path,
|
|
73
|
+
width,
|
|
74
|
+
height,
|
|
75
|
+
primitives,
|
|
76
|
+
bounds,
|
|
77
|
+
diagnostics: pageDiagnostics
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const thumb = resourceByRole(section, /thumbnail|preview/i) ?? section.resources.find(r => /\.(png|jpe?g|gif|bmp)$/i.test(r.path));
|
|
82
|
+
if (thumb && zip.has(thumb.path)) {
|
|
83
|
+
pageData.push({
|
|
84
|
+
id: `image-${pageData.length + 1}`,
|
|
85
|
+
name: section.title || basename(thumb.path),
|
|
86
|
+
kind: 'image',
|
|
87
|
+
sourcePath: thumb.path,
|
|
88
|
+
mediaType: thumb.mediaType ?? mimeFromPath(thumb.path) ?? 'image/png',
|
|
89
|
+
width: 1000,
|
|
90
|
+
height: 1000,
|
|
91
|
+
diagnostics: [...pageDiagnostics, diag('warning', 'DWF_IMAGE_FALLBACK', 'Vector stream did not produce geometry; using section thumbnail as fallback.', thumb.path)]
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (pageData.length === 0) {
|
|
98
|
+
await discoverW2dResources(zip, pageData, diagnostics);
|
|
99
|
+
}
|
|
100
|
+
const hsfEntries = zip.entries.filter(e => /\.hsf$/i.test(e.name));
|
|
101
|
+
if (hsfEntries.length > 0) {
|
|
102
|
+
diagnostics.push(diag('warning', 'DWF_3D_HSF_DETECTED', `Detected ${hsfEntries.length} HSF/3D resource(s). This frontend build reports them but does not render 3D HSF.`, hsfEntries[0]?.name));
|
|
103
|
+
if (pageData.length === 0) {
|
|
104
|
+
pageData.push({
|
|
105
|
+
id: 'unsupported-3d',
|
|
106
|
+
name: 'Unsupported 3D DWF',
|
|
107
|
+
kind: 'unsupported',
|
|
108
|
+
width: 1000,
|
|
109
|
+
height: 1000,
|
|
110
|
+
sourcePath: hsfEntries[0]?.name,
|
|
111
|
+
reason: '3D HSF resource detected; not implemented in this pure frontend native renderer build.',
|
|
112
|
+
diagnostics: [diag('warning', 'DWF_3D_HSF_UNSUPPORTED', '3D HSF rendering is not implemented in this build.', hsfEntries[0]?.name)]
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (pageData.length === 0) {
|
|
117
|
+
const imageEntry = chooseBestImage(zip);
|
|
118
|
+
if (imageEntry) {
|
|
119
|
+
pageData.push({
|
|
120
|
+
id: 'image-1',
|
|
121
|
+
name: basename(imageEntry.name),
|
|
122
|
+
kind: 'image',
|
|
123
|
+
sourcePath: imageEntry.name,
|
|
124
|
+
mediaType: mimeFromPath(imageEntry.name) ?? 'image/png',
|
|
125
|
+
width: 1000,
|
|
126
|
+
height: 1000,
|
|
127
|
+
diagnostics: [diag('info', 'DWF_IMAGE_FALLBACK', 'No supported vector page was found; using an embedded preview/image resource.', imageEntry.name)]
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (pageData.length === 0) {
|
|
132
|
+
pageData.push({
|
|
133
|
+
id: 'unsupported-1',
|
|
134
|
+
name: 'Unsupported DWF package',
|
|
135
|
+
kind: 'unsupported',
|
|
136
|
+
width: 1000,
|
|
137
|
+
height: 1000,
|
|
138
|
+
reason: 'No supported W2D, XPS FixedPage, or preview image resource was found.',
|
|
139
|
+
diagnostics: [diag('error', 'DWF_NO_SUPPORTED_PAGE', 'No supported renderable DWF resource was found.')]
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const base = {
|
|
143
|
+
kind: 'dwf',
|
|
144
|
+
pages: [],
|
|
145
|
+
resources,
|
|
146
|
+
diagnostics,
|
|
147
|
+
packageEntries: zip.entries.map(e => e.name)
|
|
148
|
+
};
|
|
149
|
+
return makeLoadedDocument(base, pageData, zip, undefined);
|
|
150
|
+
}
|
|
151
|
+
async function discoverW2dResources(zip, pageData, diagnostics) {
|
|
152
|
+
const w2dEntries = zip.entries.filter(e => /\.w2d$/i.test(e.name));
|
|
153
|
+
for (const entry of w2dEntries) {
|
|
154
|
+
try {
|
|
155
|
+
const parsed = await parseW2dResource(zip, entry.name);
|
|
156
|
+
const bounds = parsed.bounds;
|
|
157
|
+
const width = bounds ? Math.max(1, bounds.maxX - bounds.minX) : 1000;
|
|
158
|
+
const height = bounds ? Math.max(1, bounds.maxY - bounds.minY) : 1000;
|
|
159
|
+
if (parsed.primitives.length > 0) {
|
|
160
|
+
pageData.push({
|
|
161
|
+
id: `page-${pageData.length + 1}`,
|
|
162
|
+
name: basename(entry.name),
|
|
163
|
+
kind: 'w2d-text',
|
|
164
|
+
sourcePath: entry.name,
|
|
165
|
+
width,
|
|
166
|
+
height,
|
|
167
|
+
primitives: parsed.primitives,
|
|
168
|
+
bounds,
|
|
169
|
+
diagnostics: parsed.diagnostics
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
diagnostics.push(diag('warning', 'DWF_W2D_READ_FAILED', `Failed to read ${entry.name}: ${String(err)}`, entry.name));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function parseW2dResource(zip, path) {
|
|
179
|
+
const bytes = await zip.read(path);
|
|
180
|
+
if (bytesLookTextual(bytes))
|
|
181
|
+
return parseW2dText(bytes, path);
|
|
182
|
+
return parseW2dBinary(bytes, path);
|
|
183
|
+
}
|
|
184
|
+
function parseManifestSections(xml, sourcePath) {
|
|
185
|
+
const doc = parseXml(xml, sourcePath);
|
|
186
|
+
const sections = Array.from(doc.getElementsByTagName('*')).filter(el => local(el) === 'Section');
|
|
187
|
+
return sections.map((section, index) => {
|
|
188
|
+
const name = attrAny(section, 'name') ?? `section-${index + 1}`;
|
|
189
|
+
const title = attrAny(section, 'title') ?? name;
|
|
190
|
+
const resources = Array.from(section.getElementsByTagName('*'))
|
|
191
|
+
.filter(el => local(el).endsWith('Resource') || local(el) === 'Resource')
|
|
192
|
+
.map(el => ({
|
|
193
|
+
path: normalizePath(attrAny(el, 'href') ?? ''),
|
|
194
|
+
role: attrAny(el, 'role'),
|
|
195
|
+
mediaType: attrAny(el, 'mime'),
|
|
196
|
+
title: attrAny(el, 'title')
|
|
197
|
+
}))
|
|
198
|
+
.filter(r => r.path.length > 0);
|
|
199
|
+
return { name, title, resources };
|
|
200
|
+
}).filter(s => s.resources.length > 0);
|
|
201
|
+
}
|
|
202
|
+
function parseDescriptorInfo(xml, mainGraphicPath) {
|
|
203
|
+
const doc = parseXml(xml, 'descriptor.xml');
|
|
204
|
+
const paper = Array.from(doc.getElementsByTagName('*')).find(el => local(el) === 'Paper');
|
|
205
|
+
const info = {};
|
|
206
|
+
if (paper) {
|
|
207
|
+
info.paperWidth = numAttr(paper, 'width');
|
|
208
|
+
info.paperHeight = numAttr(paper, 'height');
|
|
209
|
+
const clip = attrAny(paper, 'clip');
|
|
210
|
+
if (clip)
|
|
211
|
+
info.clip = parseNumberList(clip);
|
|
212
|
+
}
|
|
213
|
+
const graphics = Array.from(doc.getElementsByTagName('*')).filter(el => /GraphicResource$/i.test(local(el)) || local(el) === 'GraphicResource');
|
|
214
|
+
const main = graphics.find(el => normalizePath(attrAny(el, 'href') ?? '') === normalizePath(mainGraphicPath))
|
|
215
|
+
?? graphics.find(el => /2d\s+streaming\s+graphics/i.test(attrAny(el, 'role') ?? ''));
|
|
216
|
+
const transform = main ? parseNumberList(attrAny(main, 'transform') ?? '') : [];
|
|
217
|
+
if (transform.length >= 14) {
|
|
218
|
+
info.w2dTransform = { sx: transform[0] || 1, sy: transform[5] || transform[0] || 1, tx: transform[12] || 0, ty: transform[13] || 0 };
|
|
219
|
+
}
|
|
220
|
+
return info;
|
|
221
|
+
}
|
|
222
|
+
function parseMarkupPrivateXml(xml, sourcePath, transform) {
|
|
223
|
+
const doc = parseXml(xml, sourcePath);
|
|
224
|
+
const primitives = [];
|
|
225
|
+
const toLogical = (x, y) => transform
|
|
226
|
+
? { x: (x - transform.tx) / transform.sx, y: (y - transform.ty) / transform.sy }
|
|
227
|
+
: { x, y };
|
|
228
|
+
const pointsAttr = (el) => parseNumberList(attrAny(el, 'Points') ?? attrAny(el, 'points') ?? '');
|
|
229
|
+
for (const el of Array.from(doc.getElementsByTagName('*'))) {
|
|
230
|
+
const name = local(el);
|
|
231
|
+
if (name === 'Polyline2D' || name === 'Leader') {
|
|
232
|
+
const nums = pointsAttr(el);
|
|
233
|
+
if (nums.length >= 4) {
|
|
234
|
+
const pts = [];
|
|
235
|
+
for (let i = 0; i + 1 < nums.length; i += 2) {
|
|
236
|
+
const p = toLogical(nums[i], nums[i + 1]);
|
|
237
|
+
pts.push(p.x, p.y);
|
|
238
|
+
}
|
|
239
|
+
const style = styleFromMarkup(el);
|
|
240
|
+
primitives.push({ type: 'polyline', points: pts, stroke: style.stroke, lineWidth: style.lineWidth });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else if (name === 'Tag' || name === 'Target') {
|
|
244
|
+
const cx = numAttr(el, 'CenterX'), cy = numAttr(el, 'CenterY'), w = numAttr(el, 'Width'), h = numAttr(el, 'Height');
|
|
245
|
+
if ([cx, cy, w, h].every(n => n !== undefined)) {
|
|
246
|
+
const p1 = toLogical(cx - w / 2, cy - h / 2);
|
|
247
|
+
const p2 = toLogical(cx + w / 2, cy - h / 2);
|
|
248
|
+
const p3 = toLogical(cx + w / 2, cy + h / 2);
|
|
249
|
+
const p4 = toLogical(cx - w / 2, cy + h / 2);
|
|
250
|
+
const style = styleFromMarkup(el);
|
|
251
|
+
primitives.push({ type: 'polygon', points: [p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y], stroke: style.stroke, fill: style.fill, lineWidth: style.lineWidth });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else if (name === 'Text') {
|
|
255
|
+
const x = numAttr(el, 'X'), y = numAttr(el, 'Y');
|
|
256
|
+
if (x !== undefined && y !== undefined) {
|
|
257
|
+
const phrases = Array.from(el.getElementsByTagName('*')).filter(child => local(child) === 'TextPhrase');
|
|
258
|
+
const text = phrases.map(p => attrAny(p, 'String') ?? '').filter(Boolean).join('\n');
|
|
259
|
+
if (text) {
|
|
260
|
+
const p = toLogical(x, y);
|
|
261
|
+
const heightPaper = numAttr(el, 'Height');
|
|
262
|
+
const fontHeight = phrases.length ? numAttr(phrases[0], 'FontHeight') : undefined;
|
|
263
|
+
const size = transform && heightPaper ? Math.max(10, Math.abs(heightPaper / transform.sy)) : (fontHeight ?? 12);
|
|
264
|
+
const colorAttr = phrases.length ? attrAny(phrases[0], 'Color') : undefined;
|
|
265
|
+
primitives.push({ type: 'text', x: p.x, y: p.y, text, size, fill: colorAttr ? signedIntColor(colorAttr) : styleFromMarkup(el).stroke });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const diagnostics = [];
|
|
271
|
+
if (primitives.length > 0)
|
|
272
|
+
diagnostics.push(diag('info', 'DWF_MARKUP_XML_PARSED', `Parsed ${primitives.length} markup primitive(s) from markup private XML.`, sourcePath));
|
|
273
|
+
return { primitives, diagnostics };
|
|
274
|
+
}
|
|
275
|
+
function styleFromMarkup(el) {
|
|
276
|
+
let stroke = '#0000ff';
|
|
277
|
+
let fill;
|
|
278
|
+
let lineWidth = 1;
|
|
279
|
+
const props = Array.from(el.getElementsByTagName('*')).filter(e => local(e) === 'FormatProperty');
|
|
280
|
+
for (const p of props) {
|
|
281
|
+
const name = (attrAny(p, 'Name') ?? '').toUpperCase();
|
|
282
|
+
const enabled = (attrAny(p, 'Enabled') ?? 'true').toLowerCase() !== 'false';
|
|
283
|
+
const value = attrAny(p, 'ModifierUIntVal') ?? attrAny(p, 'UIntValue') ?? attrAny(p, 'DblValue') ?? attrAny(p, 'IntValue');
|
|
284
|
+
if (!enabled || value === undefined)
|
|
285
|
+
continue;
|
|
286
|
+
if (name === 'LINECOLOR')
|
|
287
|
+
stroke = signedIntColor(value);
|
|
288
|
+
if (name === 'FILLCOLOR')
|
|
289
|
+
fill = signedIntColor(value);
|
|
290
|
+
if (name === 'LINEWEIGHT')
|
|
291
|
+
lineWidth = Math.max(1, Number(value) || lineWidth);
|
|
292
|
+
if (name === 'NOFILL' && /true/i.test(attrAny(p, 'BoolValue') ?? ''))
|
|
293
|
+
fill = undefined;
|
|
294
|
+
}
|
|
295
|
+
return { stroke, fill, lineWidth };
|
|
296
|
+
}
|
|
297
|
+
function signedIntColor(value) {
|
|
298
|
+
const n = Number(value);
|
|
299
|
+
if (!Number.isFinite(n))
|
|
300
|
+
return '#000000';
|
|
301
|
+
const u = n >>> 0;
|
|
302
|
+
const r = (u >>> 16) & 255;
|
|
303
|
+
const g = (u >>> 8) & 255;
|
|
304
|
+
const b = u & 255;
|
|
305
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
306
|
+
}
|
|
307
|
+
function computeBounds(primitives) {
|
|
308
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
309
|
+
const add = (x, y) => { minX = Math.min(minX, x); minY = Math.min(minY, y); maxX = Math.max(maxX, x); maxY = Math.max(maxY, y); };
|
|
310
|
+
for (const p of primitives) {
|
|
311
|
+
if ('points' in p) {
|
|
312
|
+
for (let i = 0; i + 1 < p.points.length; i += 2)
|
|
313
|
+
add(p.points[i], p.points[i + 1]);
|
|
314
|
+
}
|
|
315
|
+
else if (p.type === 'rect') {
|
|
316
|
+
add(p.x, p.y);
|
|
317
|
+
add(p.x + p.width, p.y + p.height);
|
|
318
|
+
}
|
|
319
|
+
else if (p.type === 'text') {
|
|
320
|
+
const size = p.size ?? 12;
|
|
321
|
+
add(p.x, p.y);
|
|
322
|
+
add(p.x, p.y + size);
|
|
323
|
+
}
|
|
324
|
+
else if (p.type === 'path') {
|
|
325
|
+
for (const c of p.commands) {
|
|
326
|
+
if ('x' in c && 'y' in c)
|
|
327
|
+
add(c.x, c.y);
|
|
328
|
+
if ('x1' in c && 'y1' in c)
|
|
329
|
+
add(c.x1, c.y1);
|
|
330
|
+
if ('x2' in c && 'y2' in c)
|
|
331
|
+
add(c.x2, c.y2);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return Number.isFinite(minX) ? { minX, minY, maxX, maxY } : undefined;
|
|
336
|
+
}
|
|
337
|
+
function resourceByRole(section, re) {
|
|
338
|
+
return section.resources.find(r => re.test(r.role ?? ''));
|
|
339
|
+
}
|
|
340
|
+
function attrAny(el, name) {
|
|
341
|
+
return el.getAttribute(name) ?? el.getAttribute(`dwf:${name}`) ?? el.getAttribute(`ePlot:${name}`) ?? Array.from(el.attributes).find(a => a.localName === name)?.value ?? undefined;
|
|
342
|
+
}
|
|
343
|
+
function local(el) {
|
|
344
|
+
const n = el.localName || el.nodeName;
|
|
345
|
+
const i = n.indexOf(':');
|
|
346
|
+
return i >= 0 ? n.slice(i + 1) : n;
|
|
347
|
+
}
|
|
348
|
+
function numAttr(el, name) {
|
|
349
|
+
const raw = attrAny(el, name);
|
|
350
|
+
if (raw === undefined)
|
|
351
|
+
return undefined;
|
|
352
|
+
const n = Number(raw);
|
|
353
|
+
return Number.isFinite(n) ? n : undefined;
|
|
354
|
+
}
|
|
355
|
+
function basename(path) {
|
|
356
|
+
const i = path.lastIndexOf('/');
|
|
357
|
+
return i >= 0 ? path.slice(i + 1) : path;
|
|
358
|
+
}
|
|
359
|
+
function chooseBestImage(zip) {
|
|
360
|
+
const images = zip.entries.filter(e => /\.(png|jpe?g|gif|bmp)$/i.test(e.name));
|
|
361
|
+
if (images.length === 0)
|
|
362
|
+
return undefined;
|
|
363
|
+
return images.sort((a, b) => scoreImage(b.name, b.uncompressedSize) - scoreImage(a.name, a.uncompressedSize))[0];
|
|
364
|
+
}
|
|
365
|
+
function scoreImage(path, size) {
|
|
366
|
+
let s = Math.min(size, 10000000) / 1000;
|
|
367
|
+
if (/thumbnail|preview/i.test(path))
|
|
368
|
+
s += 5000;
|
|
369
|
+
if (extname(path) === 'png')
|
|
370
|
+
s += 100;
|
|
371
|
+
return s;
|
|
372
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { OpcRelationship } from './opc.js';
|
|
2
|
+
import { ZipPackage } from './zip.js';
|
|
3
|
+
import { type LoadedDwfDocument } from './document.js';
|
|
4
|
+
export declare function openDwfx(zip: ZipPackage): Promise<LoadedDwfDocument>;
|
|
5
|
+
export declare function isProbablyDwfx(zip: ZipPackage): boolean;
|
|
6
|
+
export declare function getRelationshipById(rels: OpcRelationship[], id: string): OpcRelationship | undefined;
|