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,166 @@
|
|
|
1
|
+
import { diag } from './types.js';
|
|
2
|
+
import { decodeUtf8, parseNumberList } from './util.js';
|
|
3
|
+
import { parsePathData } from '../render/xpsPath.js';
|
|
4
|
+
export function parseW2dText(bytes, sourcePath) {
|
|
5
|
+
const text = decodeUtf8(bytes);
|
|
6
|
+
const diagnostics = [];
|
|
7
|
+
const primitives = [];
|
|
8
|
+
let stroke = '#000000';
|
|
9
|
+
let fill;
|
|
10
|
+
let lineWidth = 1;
|
|
11
|
+
let parsedLines = 0;
|
|
12
|
+
// Accept SVG-ish payloads sometimes embedded by converters/test fixtures.
|
|
13
|
+
if (/<svg[\s>]/i.test(text)) {
|
|
14
|
+
const pathMatches = text.matchAll(/<path\b[^>]*\bd=["']([^"']+)["'][^>]*>/gi);
|
|
15
|
+
for (const m of pathMatches) {
|
|
16
|
+
const tag = m[0];
|
|
17
|
+
const d = m[1];
|
|
18
|
+
primitives.push({ type: 'path', commands: parsePathData(d), stroke: attr(tag, 'stroke') ?? stroke, fill: attr(tag, 'fill') ?? undefined, lineWidth: Number(attr(tag, 'stroke-width') ?? lineWidth) });
|
|
19
|
+
parsedLines++;
|
|
20
|
+
}
|
|
21
|
+
const polyMatches = text.matchAll(/<(polyline|polygon)\b[^>]*\bpoints=["']([^"']+)["'][^>]*>/gi);
|
|
22
|
+
for (const m of polyMatches) {
|
|
23
|
+
const tag = m[0];
|
|
24
|
+
const nums = parseNumberList(m[2]);
|
|
25
|
+
if (nums.length >= 4)
|
|
26
|
+
primitives.push({ type: m[1].toLowerCase() === 'polygon' ? 'polygon' : 'polyline', points: nums, stroke: attr(tag, 'stroke') ?? stroke, fill: attr(tag, 'fill') ?? undefined, lineWidth: Number(attr(tag, 'stroke-width') ?? lineWidth) });
|
|
27
|
+
parsedLines++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const lines = text.split(/\r?\n/);
|
|
31
|
+
for (const rawLine of lines) {
|
|
32
|
+
const line = rawLine.trim();
|
|
33
|
+
if (!line || line.startsWith('#') || line.startsWith('//'))
|
|
34
|
+
continue;
|
|
35
|
+
const lower = line.toLowerCase();
|
|
36
|
+
const nums = parseNumberList(line);
|
|
37
|
+
if (/^(color|stroke|pen|wt_color)\b/i.test(line)) {
|
|
38
|
+
stroke = parseColorToken(line) ?? stroke;
|
|
39
|
+
parsedLines++;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (/^(fill|brush|wt_fill)\b/i.test(line)) {
|
|
43
|
+
fill = parseColorToken(line) ?? fill ?? '#000000';
|
|
44
|
+
parsedLines++;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (/^(nofill|fillnone)\b/i.test(line)) {
|
|
48
|
+
fill = undefined;
|
|
49
|
+
parsedLines++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (/^(linewidth|lineweight|weight|wt_line_weight)\b/i.test(line) && nums.length >= 1) {
|
|
53
|
+
lineWidth = Math.max(0.2, nums[0]);
|
|
54
|
+
parsedLines++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if ((/^path\b/i.test(line) || /\bPath\s*Data\b/.test(line)) && /[MmLlCcQqAaZz]/.test(line)) {
|
|
58
|
+
const d = line.replace(/^path\b[:=]?/i, '').replace(/^.*?Data\s*=\s*/i, '').replace(/^['"]|['"]$/g, '');
|
|
59
|
+
primitives.push({ type: 'path', commands: parsePathData(d), stroke, fill, lineWidth });
|
|
60
|
+
parsedLines++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (/^(line|wt_line)\b/i.test(line) && nums.length >= 4) {
|
|
64
|
+
primitives.push({ type: 'polyline', points: nums.slice(0, 4), stroke, lineWidth });
|
|
65
|
+
parsedLines++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if ((/^(polyline|wt_polyline|pline)\b/i.test(line) || /\bPolyline\b/i.test(line)) && nums.length >= 4) {
|
|
69
|
+
primitives.push({ type: 'polyline', points: normalizePointList(nums), stroke, lineWidth });
|
|
70
|
+
parsedLines++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if ((/^(polygon|wt_polygon)\b/i.test(line) || /\bPolygon\b/i.test(line)) && nums.length >= 6) {
|
|
74
|
+
primitives.push({ type: 'polygon', points: normalizePointList(nums), stroke, fill: fill ?? '#000000', lineWidth });
|
|
75
|
+
parsedLines++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (/^(rect|rectangle|wt_rectangle)\b/i.test(line) && nums.length >= 4) {
|
|
79
|
+
primitives.push({ type: 'rect', x: nums[0], y: nums[1], width: nums[2], height: nums[3], stroke, fill, lineWidth });
|
|
80
|
+
parsedLines++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (/^(circle|ellipse)\b/i.test(line) && nums.length >= 3) {
|
|
84
|
+
const [x, y, r] = nums;
|
|
85
|
+
const segments = 72;
|
|
86
|
+
const pts = [];
|
|
87
|
+
for (let i = 0; i <= segments; i++) {
|
|
88
|
+
const a = Math.PI * 2 * i / segments;
|
|
89
|
+
pts.push(x + Math.cos(a) * r, y + Math.sin(a) * r);
|
|
90
|
+
}
|
|
91
|
+
primitives.push({ type: 'polygon', points: pts, stroke, fill, lineWidth });
|
|
92
|
+
parsedLines++;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (/^(text|wt_text)\b/i.test(line) && nums.length >= 2) {
|
|
96
|
+
const x = nums[0], y = nums[1];
|
|
97
|
+
const size = nums.length >= 3 ? nums[2] : 12;
|
|
98
|
+
const quoted = line.match(/["']([^"']+)["']/)?.[1];
|
|
99
|
+
const textPayload = quoted ?? line.replace(/^(text|wt_text)\b/i, '').replace(/[-+]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][-+]?\d+)?/g, '').trim();
|
|
100
|
+
primitives.push({ type: 'text', x, y, size, text: textPayload || 'Text', fill: stroke });
|
|
101
|
+
parsedLines++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (lower.includes('wt_') || lower.includes('whip') || lower.includes('dwf'))
|
|
105
|
+
parsedLines++;
|
|
106
|
+
}
|
|
107
|
+
const bounds = computeBounds(primitives);
|
|
108
|
+
if (primitives.length === 0) {
|
|
109
|
+
diagnostics.push(diag('warning', 'W2D_TEXT_NO_GEOMETRY', `Textual W2D parser did not find supported geometry commands in ${sourcePath}.`, sourcePath));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
diagnostics.push(diag('info', 'W2D_TEXT_PARSED', `Parsed ${primitives.length} supported vector primitives from ${sourcePath}.`, sourcePath));
|
|
113
|
+
}
|
|
114
|
+
if (parsedLines === 0) {
|
|
115
|
+
diagnostics.push(diag('warning', 'W2D_TEXT_UNKNOWN_DIALECT', 'The file is textual, but does not look like the supported WHIP/W2D textual subset.', sourcePath));
|
|
116
|
+
}
|
|
117
|
+
return { primitives, diagnostics, bounds };
|
|
118
|
+
}
|
|
119
|
+
function normalizePointList(nums) {
|
|
120
|
+
// Some WHIP dumps include a point count before coordinates. Strip it when it matches.
|
|
121
|
+
if (nums.length >= 5 && Number.isInteger(nums[0]) && (nums.length - 1) === nums[0] * 2)
|
|
122
|
+
return nums.slice(1);
|
|
123
|
+
return nums;
|
|
124
|
+
}
|
|
125
|
+
function attr(tag, name) {
|
|
126
|
+
return tag.match(new RegExp(`${name}=["']([^"']+)["']`, 'i'))?.[1];
|
|
127
|
+
}
|
|
128
|
+
function parseColorToken(line) {
|
|
129
|
+
const hex = line.match(/#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})\b/i)?.[0];
|
|
130
|
+
if (hex)
|
|
131
|
+
return hex;
|
|
132
|
+
const nums = parseNumberList(line);
|
|
133
|
+
if (nums.length >= 3)
|
|
134
|
+
return `rgb(${Math.max(0, Math.min(255, nums[0]))}, ${Math.max(0, Math.min(255, nums[1]))}, ${Math.max(0, Math.min(255, nums[2]))})`;
|
|
135
|
+
const m = line.match(/\b(black|white|red|green|blue|yellow|cyan|magenta|gray|grey)\b/i);
|
|
136
|
+
return m?.[1];
|
|
137
|
+
}
|
|
138
|
+
function computeBounds(primitives) {
|
|
139
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
140
|
+
const add = (x, y) => { minX = Math.min(minX, x); minY = Math.min(minY, y); maxX = Math.max(maxX, x); maxY = Math.max(maxY, y); };
|
|
141
|
+
for (const p of primitives) {
|
|
142
|
+
if ('points' in p) {
|
|
143
|
+
for (let i = 0; i + 1 < p.points.length; i += 2)
|
|
144
|
+
add(p.points[i], p.points[i + 1]);
|
|
145
|
+
}
|
|
146
|
+
else if (p.type === 'rect') {
|
|
147
|
+
add(p.x, p.y);
|
|
148
|
+
add(p.x + p.width, p.y + p.height);
|
|
149
|
+
}
|
|
150
|
+
else if (p.type === 'text') {
|
|
151
|
+
add(p.x, p.y);
|
|
152
|
+
add(p.x + p.text.length * (p.size ?? 12) * 0.6, p.y + (p.size ?? 12));
|
|
153
|
+
}
|
|
154
|
+
else if (p.type === 'path') {
|
|
155
|
+
for (const c of p.commands) {
|
|
156
|
+
if ('x' in c && 'y' in c)
|
|
157
|
+
add(c.x, c.y);
|
|
158
|
+
if ('x1' in c && 'y1' in c)
|
|
159
|
+
add(c.x1, c.y1);
|
|
160
|
+
if ('x2' in c && 'y2' in c)
|
|
161
|
+
add(c.x2, c.y2);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return Number.isFinite(minX) ? { minX, minY, maxX, maxY } : undefined;
|
|
166
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { W3dModelData } from './document.js';
|
|
2
|
+
export interface W3dParseOptions {
|
|
3
|
+
title?: string;
|
|
4
|
+
sourcePath?: string;
|
|
5
|
+
maxMeshes?: number;
|
|
6
|
+
maxTriangles?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseW3dModel(bytes: Uint8Array, options?: W3dParseOptions): Promise<W3dModelData>;
|