@flyfish-dev/dwf-viewer 0.5.0 → 0.6.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 +21 -6
- package/PRODUCTION_3D_NOTES.md +4 -0
- package/README.md +101 -36
- package/dist/format/types.d.ts +6 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/render/PageRenderer.d.ts +5 -0
- package/dist/render/PageRenderer.js +1 -0
- package/dist/render/W2dRenderer.d.ts +2 -1
- package/dist/render/W2dRenderer.js +16 -13
- package/dist/render/WebGlW2dBackend.d.ts +2 -1
- package/dist/render/WebGlW2dBackend.js +14 -9
- package/dist/render/WebGlXpsBackend.d.ts +38 -0
- package/dist/render/WebGlXpsBackend.js +541 -0
- package/dist/render/XpsRenderer.d.ts +16 -1
- package/dist/render/XpsRenderer.js +270 -25
- package/dist/render/cadLineStyle.d.ts +32 -0
- package/dist/render/cadLineStyle.js +59 -0
- package/dist/viewer/DwfViewer.d.ts +13 -0
- package/dist/viewer/DwfViewer.js +66 -30
- package/package.json +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
- Added WebGL-accelerated XPS/DWFx 2D vector rendering through `WebGlXpsBackend`.
|
|
6
|
+
- Added XPS ODTTF embedded font deobfuscation/loading for CAD-like text appearance.
|
|
7
|
+
- Tuned demo defaults for dense CAD overview linework and lower DPR memory pressure.
|
|
8
|
+
- Added curated Autodesk floor-plan DWFx demo entry with default A03 sheet.
|
|
9
|
+
- Coalesced viewer render requests to avoid zoom/pan render pile-ups.
|
|
10
|
+
- Kept Cloudflare demo assets versioned under `dist-v0.6.0` to prevent stale browser module caches.
|
|
11
|
+
|
|
12
|
+
## 0.5.1
|
|
13
|
+
|
|
14
|
+
- Added CAD adaptive line-weight rendering for XPS FixedPage, W2D Canvas/WASM, and W2D WebGL paths.
|
|
15
|
+
- Added overview text LOD culling to avoid dense annotation blobs at fit-to-page while preserving text when zoomed in.
|
|
16
|
+
- Added embedded XPS TrueType font loading for Glyphs when browsers support the FontFace API.
|
|
17
|
+
- Added demo line-weight mode selector: CAD adaptive, hairline, physical.
|
|
18
|
+
- Versioned demo dist assets under `dist-v0.5.1` to prevent stale browser module caches.
|
|
19
|
+
|
|
3
20
|
## 0.5.0
|
|
4
21
|
|
|
5
|
-
- Prepared repository for public npm release and Cloudflare Pages demo deployment.
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
- Switched the project license to `AGPL-3.0-only` and added `NOTICE`.
|
|
9
|
-
- Normalized GitHub repository metadata for `flyfish-dev/dwf-viewer`.
|
|
22
|
+
- Prepared the repository for public npm release and Cloudflare Pages demo deployment.
|
|
23
|
+
- Published normalized packages as `dwf-viewer` and `@flyfish-dev/dwf-viewer`.
|
|
24
|
+
- Adopted `AGPL-3.0-only` with `NOTICE` for strict open-source distribution.
|
|
10
25
|
- Added curated, de-duplicated demo example manifest.
|
|
11
26
|
- Removed production-success info diagnostics from W3D/eModel pages so the Robot Arm demo renders with zero page diagnostics.
|
|
12
|
-
- Added static demo build pipeline
|
|
27
|
+
- Added static demo build pipeline, package validation scripts, CI, and publish helpers.
|
package/PRODUCTION_3D_NOTES.md
CHANGED
|
@@ -46,3 +46,7 @@ Serve the WASM file with `application/wasm`. The Cloudflare Pages demo writes `_
|
|
|
46
46
|
## Unsupported semantics
|
|
47
47
|
|
|
48
48
|
Unsupported historical HSF opcodes, advanced CAD display-list behavior, and full Design Review parity are tracked as incremental parser work. The rendering contract is: exact-enough supported semantics are displayed, unsupported semantics are diagnosed, and known-failing 3D geometry is not hidden by image fallback.
|
|
49
|
+
|
|
50
|
+
## 2D production rendering update
|
|
51
|
+
|
|
52
|
+
Version 0.6.0 adds WebGL acceleration for XPS/DWFx vector geometry and keeps text/images in a Canvas overlay. This avoids CPU redraw bottlenecks when zooming dense sheets and keeps GPU memory bounded through scene cache limits. Embedded ODTTF fonts are deobfuscated and loaded through FontFace where supported, producing CAD-like text weights in the demo.
|
package/README.md
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
# DWF Viewer
|
|
2
2
|
|
|
3
|
-
Pure frontend DWF/DWFx viewer for browsers. It parses DWF/DWFx packages locally
|
|
3
|
+
Pure frontend DWF/DWFx CAD viewer for browsers. It parses DWF/DWFx packages locally and renders common 2D and 3D content without a server-side CAD conversion service.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Links
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
| Entry | URL |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Online demo | https://dwf-viewer-demo.pages.dev/ |
|
|
10
|
+
| Repository | https://github.com/flyfish-dev/dwf-viewer |
|
|
11
|
+
| npm | https://www.npmjs.com/package/dwf-viewer |
|
|
12
|
+
| scoped npm | https://www.npmjs.com/package/@flyfish-dev/dwf-viewer |
|
|
13
|
+
|
|
14
|
+
Current version: `0.6.0`
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
DWF and DWFx are still common in manufacturing, construction, field service, engineering archives, PLM, after-sales systems, and document management products. Many web systems can preview PDF, Office files, and images, but DWF often falls back to desktop viewers, server-side conversion, or static thumbnails.
|
|
19
|
+
|
|
20
|
+
DWF Viewer is built for the browser-native path: parse the package, decode the sheet/model data, and render it directly inside a web application. This keeps integration simple for private deployments and reduces conversion queues, temporary files, and infrastructure coupling.
|
|
8
21
|
|
|
9
|
-
Supported
|
|
22
|
+
## Supported Paths
|
|
10
23
|
|
|
11
24
|
| Format / content | Status |
|
|
12
25
|
|---|---|
|
|
13
26
|
| DWF 6+ ZIP container | Supported |
|
|
14
27
|
| DWFx / OPC package | Supported |
|
|
15
|
-
| XPS FixedPage 2D sheets | Supported common subset |
|
|
28
|
+
| XPS FixedPage 2D sheets | Supported common subset with WebGL vector acceleration and Canvas text/image overlay |
|
|
16
29
|
| Classic binary WHIP!/W2D 2D sheets | Supported for core geometry/text/images used by Autodesk samples |
|
|
30
|
+
| Textual W2D pages | Supported for smoke tests and simple sheets |
|
|
17
31
|
| W3D/HSF 3D eModel shell geometry | Supported: uncompressed, CS_TRIVIAL, and Edgebreaker shell meshes |
|
|
18
32
|
| Three.js adapter | Supported |
|
|
33
|
+
| Built-in WebGL 3D renderer | Supported through `DwfViewer` |
|
|
19
34
|
| WASM raster fallback | Supported for 2D vector rasterization |
|
|
20
35
|
| eModel metadata | Materials, textures, scene tree, saved views, PMI/animation data containers |
|
|
21
36
|
|
|
@@ -31,7 +46,7 @@ npm install @flyfish-dev/dwf-viewer three
|
|
|
31
46
|
|
|
32
47
|
`three` is an optional peer dependency. It is required only when you use `createThreeGroupFromW3d()` directly. The built-in `DwfViewer` uses its own WebGL 3D renderer.
|
|
33
48
|
|
|
34
|
-
## Basic
|
|
49
|
+
## Basic Browser Usage
|
|
35
50
|
|
|
36
51
|
```ts
|
|
37
52
|
import 'dwf-viewer/styles.css';
|
|
@@ -41,10 +56,15 @@ const viewer = new DwfViewer(document.getElementById('viewer')!, {
|
|
|
41
56
|
wasmUrl: '/dwfv-render.wasm',
|
|
42
57
|
preferWebgl: true,
|
|
43
58
|
preferWasm: true,
|
|
44
|
-
maxDevicePixelRatio:
|
|
45
|
-
maxCanvasPixels:
|
|
46
|
-
maxGpuCacheBytes:
|
|
47
|
-
maxCachedScenes:
|
|
59
|
+
maxDevicePixelRatio: 1.5,
|
|
60
|
+
maxCanvasPixels: 12_000_000,
|
|
61
|
+
maxGpuCacheBytes: 192 * 1024 * 1024,
|
|
62
|
+
maxCachedScenes: 4,
|
|
63
|
+
lineWeightMode: 'adaptive',
|
|
64
|
+
minStrokeCssPx: 0.42,
|
|
65
|
+
maxOverviewStrokeCssPx: 0.9,
|
|
66
|
+
minTextCssPx: 1.05,
|
|
67
|
+
minFilledAreaCssPx: 0.04
|
|
48
68
|
});
|
|
49
69
|
|
|
50
70
|
await viewer.load(file);
|
|
@@ -56,7 +76,47 @@ Copy the WASM asset from the package into your public assets directory:
|
|
|
56
76
|
cp node_modules/dwf-viewer/public/dwfv-render.wasm public/dwfv-render.wasm
|
|
57
77
|
```
|
|
58
78
|
|
|
59
|
-
|
|
79
|
+
For the scoped package:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
cp node_modules/@flyfish-dev/dwf-viewer/public/dwfv-render.wasm public/dwfv-render.wasm
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## WebGL 2D Rendering
|
|
86
|
+
|
|
87
|
+
Version `0.6.0` adds WebGL-accelerated XPS/DWFx 2D vector rendering through `WebGlXpsBackend`, alongside the existing W2D WebGL path.
|
|
88
|
+
|
|
89
|
+
The built-in viewer uses WebGL for Classic W2D and DWFx/XPS vector geometry when `preferWebgl` is enabled. Geometry is uploaded to GPU buffers and cached by page and zoom bucket; pan operations update shader uniforms. Text, images, and XPS image brushes stay on the transparent Canvas overlay so browser font rendering and bitmap decoding remain reliable.
|
|
90
|
+
|
|
91
|
+
For dense architectural sheets, the demo defaults are tuned for CAD review: 1.5 maximum DPR, a 12M-pixel canvas cap, adaptive thin-line overview, and render coalescing during wheel/pointer interaction.
|
|
92
|
+
|
|
93
|
+
## CAD Line-Weight Rendering
|
|
94
|
+
|
|
95
|
+
The default 2D rendering mode is `lineWeightMode: 'adaptive'`. At fit-to-page, linework is normalized toward screen-space hairlines so dense drawings remain readable; as zoom increases, original DWF/XPS line weights return progressively.
|
|
96
|
+
|
|
97
|
+
Available modes:
|
|
98
|
+
|
|
99
|
+
| Mode | Behavior |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `adaptive` | Default. Overview thin-line rendering with zoom-aware recovery of source line weights. |
|
|
102
|
+
| `hairline` | Force strokes to a visible CSS-pixel hairline. Useful for dense plans and review thumbnails. |
|
|
103
|
+
| `physical` | Preserve source stroke widths. Useful for print-fidelity comparisons; dense sheets can look heavy when zoomed out. |
|
|
104
|
+
|
|
105
|
+
Related options:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
new DwfViewer(el, {
|
|
109
|
+
lineWeightMode: 'adaptive',
|
|
110
|
+
minStrokeCssPx: 0.42,
|
|
111
|
+
maxOverviewStrokeCssPx: 0.9,
|
|
112
|
+
minTextCssPx: 1.05,
|
|
113
|
+
minFilledAreaCssPx: 0.04
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
XPS/DWFx `Glyphs` use embedded TrueType fonts when the browser allows `FontFace` loading. Very small text is skipped in adaptive overview mode and appears normally when zoomed in; this avoids unreadable annotation blocks in dense architectural sheets.
|
|
118
|
+
|
|
119
|
+
## Three.js Integration
|
|
60
120
|
|
|
61
121
|
```ts
|
|
62
122
|
import * as THREE from 'three';
|
|
@@ -69,7 +129,6 @@ if (page?.kind === 'w3d-model') {
|
|
|
69
129
|
const group = createThreeGroupFromW3d(page, THREE, {
|
|
70
130
|
showFeatureEdges: true,
|
|
71
131
|
textureResolver(texture) {
|
|
72
|
-
// Return a THREE.Texture if your app wants to bind DWFx texture resources.
|
|
73
132
|
return undefined;
|
|
74
133
|
}
|
|
75
134
|
});
|
|
@@ -77,7 +136,7 @@ if (page?.kind === 'w3d-model') {
|
|
|
77
136
|
}
|
|
78
137
|
```
|
|
79
138
|
|
|
80
|
-
## Local
|
|
139
|
+
## Local Development
|
|
81
140
|
|
|
82
141
|
```bash
|
|
83
142
|
npm install
|
|
@@ -92,14 +151,15 @@ Then open:
|
|
|
92
151
|
http://127.0.0.1:8080/
|
|
93
152
|
```
|
|
94
153
|
|
|
95
|
-
## Example
|
|
154
|
+
## Example Set
|
|
96
155
|
|
|
97
156
|
The demo examples are listed in `examples/manifest.json` and are intentionally de-duplicated:
|
|
98
157
|
|
|
99
158
|
| Example | Purpose |
|
|
100
159
|
|---|---|
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
160
|
+
| `autodesk-floor-plans.dwfx` | Multi-page architectural DWFx/XPS sample, defaulting to A03 First Floor Plan for WebGL XPS, embedded font, and thin-line overview validation |
|
|
161
|
+
| `robot-arm.dwfx` | 3D W3D/HSF eModel with shell meshes, scene tree, materials, textures, and saved views |
|
|
162
|
+
| `blocks-and-tables.dwf` | Binary WHIP!/W2D ePlot sample |
|
|
103
163
|
| `minimal-xps.dwfx` | Small DWFx/XPS FixedPage sample |
|
|
104
164
|
| `text-w2d.dwf` | Textual W2D smoke-test sample |
|
|
105
165
|
|
|
@@ -109,27 +169,24 @@ Run:
|
|
|
109
169
|
npm run check:examples
|
|
110
170
|
```
|
|
111
171
|
|
|
112
|
-
## NPM
|
|
172
|
+
## NPM Publishing Checklist
|
|
113
173
|
|
|
114
174
|
```bash
|
|
115
175
|
npm run clean
|
|
176
|
+
npm run build
|
|
177
|
+
npm run validate:production
|
|
178
|
+
npm run check:package
|
|
116
179
|
npm run publish:all
|
|
117
180
|
```
|
|
118
181
|
|
|
119
|
-
|
|
182
|
+
The package is published as both `dwf-viewer` and `@flyfish-dev/dwf-viewer`.
|
|
120
183
|
|
|
121
|
-
|
|
122
|
-
npm run publish:all -- --dry-run
|
|
123
|
-
npm run publish:all -- --otp=123456
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
GitHub release publishing can use provenance through the included workflow and `NPM_TOKEN`.
|
|
127
|
-
|
|
128
|
-
## Cloudflare Pages demo
|
|
184
|
+
## Cloudflare Pages Demo
|
|
129
185
|
|
|
130
186
|
The repository includes `wrangler.toml` with:
|
|
131
187
|
|
|
132
188
|
```toml
|
|
189
|
+
name = "dwf-viewer-demo"
|
|
133
190
|
pages_build_output_dir = "./demo-dist"
|
|
134
191
|
```
|
|
135
192
|
|
|
@@ -144,11 +201,10 @@ Root directory: /
|
|
|
144
201
|
Direct upload:
|
|
145
202
|
|
|
146
203
|
```bash
|
|
147
|
-
npm run
|
|
148
|
-
npx wrangler pages deploy demo-dist
|
|
204
|
+
npm run deploy:pages
|
|
149
205
|
```
|
|
150
206
|
|
|
151
|
-
`build:demo` produces a static directory containing
|
|
207
|
+
`build:demo` produces a static directory containing demo HTML/JS, a versioned `dist-v*` directory, `public/dwfv-render.wasm`, `styles`, and curated examples. Versioned dist assets prevent stale browser module caches after releases.
|
|
152
208
|
|
|
153
209
|
## Public API
|
|
154
210
|
|
|
@@ -159,6 +215,7 @@ openDwfDocument(input, options?)
|
|
|
159
215
|
DwfViewer
|
|
160
216
|
PageRenderer
|
|
161
217
|
WebGlW2dBackend
|
|
218
|
+
WebGlXpsBackend
|
|
162
219
|
ThreeW3dRenderer
|
|
163
220
|
createThreeGroupFromW3d(page, THREE, options?)
|
|
164
221
|
```
|
|
@@ -168,6 +225,7 @@ Types:
|
|
|
168
225
|
```ts
|
|
169
226
|
LoadedDwfDocument
|
|
170
227
|
PageData
|
|
228
|
+
XpsPageData
|
|
171
229
|
W3dPageData
|
|
172
230
|
W3dModelData
|
|
173
231
|
W3dMeshData
|
|
@@ -176,15 +234,20 @@ Diagnostic
|
|
|
176
234
|
RenderStats
|
|
177
235
|
```
|
|
178
236
|
|
|
179
|
-
## Production
|
|
237
|
+
## Production Behavior
|
|
180
238
|
|
|
181
|
-
The production validation target is strict for the bundled
|
|
239
|
+
The production validation target is strict for the bundled samples:
|
|
182
240
|
|
|
183
241
|
```text
|
|
242
|
+
Robot Arm:
|
|
184
243
|
page kind: w3d-model
|
|
185
244
|
meshes >= 30
|
|
186
245
|
triangles >= 40,000
|
|
187
|
-
|
|
246
|
+
non-info diagnostics: 0
|
|
247
|
+
|
|
248
|
+
Autodesk Floor Plans:
|
|
249
|
+
page kind: xps-fixed-page
|
|
250
|
+
pages >= 18
|
|
188
251
|
non-info diagnostics: 0
|
|
189
252
|
```
|
|
190
253
|
|
|
@@ -194,10 +257,12 @@ Run:
|
|
|
194
257
|
npm run validate:production
|
|
195
258
|
```
|
|
196
259
|
|
|
197
|
-
##
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
DWF Viewer is licensed under `AGPL-3.0-only`. See `LICENSE` and `NOTICE`.
|
|
198
263
|
|
|
199
|
-
|
|
264
|
+
Commercial use, private forks, and second-development integrations must comply with the license and preserve attribution. Contributions are welcome through Issues and Pull Requests.
|
|
200
265
|
|
|
201
|
-
##
|
|
266
|
+
## Known Boundaries
|
|
202
267
|
|
|
203
|
-
|
|
268
|
+
This is a pure frontend implementation and is not affiliated with Autodesk Design Review or HOOPS Exchange. The parser is intentionally fail-closed for unsupported historical HSF/W2D opcode semantics: it should show explicit diagnostics instead of silently drawing guessed geometry. Current production coverage includes the core 2D/3D rendering paths needed by the bundled samples and extension points for materials, textures, PMI, animation, and selection tree metadata.
|
package/dist/format/types.d.ts
CHANGED
|
@@ -45,6 +45,11 @@ export interface InflateProvider {
|
|
|
45
45
|
}
|
|
46
46
|
export interface PageRenderOptions {
|
|
47
47
|
pageIndex?: number;
|
|
48
|
+
lineWeightMode?: 'adaptive' | 'physical' | 'hairline';
|
|
49
|
+
minStrokeCssPx?: number;
|
|
50
|
+
maxOverviewStrokeCssPx?: number;
|
|
51
|
+
minTextCssPx?: number;
|
|
52
|
+
minFilledAreaCssPx?: number;
|
|
48
53
|
preferWebgl?: boolean;
|
|
49
54
|
preferWasm?: boolean;
|
|
50
55
|
wasmUrl?: string;
|
|
@@ -53,7 +58,7 @@ export interface PageRenderOptions {
|
|
|
53
58
|
maxCachedScenes?: number;
|
|
54
59
|
}
|
|
55
60
|
export interface RenderStats {
|
|
56
|
-
backend: 'canvas2d' | 'wasm-raster' | 'webgl' | 'threejs-webgl' | 'image' | 'unsupported';
|
|
61
|
+
backend: 'canvas2d' | 'wasm-raster' | 'webgl' | 'webgl-xps' | 'threejs-webgl' | 'image' | 'unsupported';
|
|
57
62
|
commands: number;
|
|
58
63
|
warnings: Diagnostic[];
|
|
59
64
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export type { DwfViewerOptions, LoadOptions } from './viewer/DwfViewer.js';
|
|
|
6
6
|
export { PageRenderer } from './render/PageRenderer.js';
|
|
7
7
|
export { WasmRasterBackend } from './wasm/WasmRasterBackend.js';
|
|
8
8
|
export { WebGlW2dBackend } from './render/WebGlW2dBackend.js';
|
|
9
|
+
export { WebGlXpsBackend } from './render/WebGlXpsBackend.js';
|
|
9
10
|
export { ThreeW3dRenderer } from './render/ThreeW3dRenderer.js';
|
|
10
11
|
export { createThreeGroupFromW3d } from './render/ThreeJsSceneAdapter.js';
|
|
11
12
|
export type { LoadedDwfDocument, PageData, XpsPageData, W2dTextPageData, ImagePageData, UnsupportedPageData, W3dPageData, W3dModelData, W3dMeshData, W2dPrimitive } from './format/document.js';
|
package/dist/index.js
CHANGED
|
@@ -5,5 +5,6 @@ export { DwfViewer } from './viewer/DwfViewer.js';
|
|
|
5
5
|
export { PageRenderer } from './render/PageRenderer.js';
|
|
6
6
|
export { WasmRasterBackend } from './wasm/WasmRasterBackend.js';
|
|
7
7
|
export { WebGlW2dBackend } from './render/WebGlW2dBackend.js';
|
|
8
|
+
export { WebGlXpsBackend } from './render/WebGlXpsBackend.js';
|
|
8
9
|
export { ThreeW3dRenderer } from './render/ThreeW3dRenderer.js';
|
|
9
10
|
export { createThreeGroupFromW3d } from './render/ThreeJsSceneAdapter.js';
|
|
@@ -2,6 +2,11 @@ import { type RenderStats } from '../format/types.js';
|
|
|
2
2
|
import type { LoadedDwfDocument } from '../format/document.js';
|
|
3
3
|
export interface GenericRenderOptions {
|
|
4
4
|
zoom?: number;
|
|
5
|
+
lineWeightMode?: 'adaptive' | 'physical' | 'hairline';
|
|
6
|
+
minStrokeCssPx?: number;
|
|
7
|
+
maxOverviewStrokeCssPx?: number;
|
|
8
|
+
minTextCssPx?: number;
|
|
9
|
+
minFilledAreaCssPx?: number;
|
|
5
10
|
panX?: number;
|
|
6
11
|
panY?: number;
|
|
7
12
|
preferWebgl?: boolean;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type RenderStats } from '../format/types.js';
|
|
2
2
|
import type { W2dTextPageData } from '../format/document.js';
|
|
3
|
-
|
|
3
|
+
import { type CadLineStyleOptions } from './cadLineStyle.js';
|
|
4
|
+
export interface W2dRenderOptions extends CadLineStyleOptions {
|
|
4
5
|
zoom?: number;
|
|
5
6
|
panX?: number;
|
|
6
7
|
panY?: number;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { actionableDiagnostics, diag } from '../format/types.js';
|
|
2
2
|
import { applyPathToCanvas, flattenPath } from './xpsPath.js';
|
|
3
3
|
import { multiplyMatrix, parseBrushColor, transformPoint } from './style.js';
|
|
4
|
+
import { adaptiveStrokeUserWidth, canvasDpr, estimateMatrixScale, shouldDrawTextByPixelSize } from './cadLineStyle.js';
|
|
4
5
|
import { matrixForW2d } from './viewport.js';
|
|
5
6
|
import { WasmRasterBackend } from '../wasm/WasmRasterBackend.js';
|
|
6
7
|
import { WebGlW2dBackend } from './WebGlW2dBackend.js';
|
|
@@ -30,6 +31,7 @@ export class W2dRenderer {
|
|
|
30
31
|
if (!ctx)
|
|
31
32
|
throw new Error('CanvasRenderingContext2D is not available.');
|
|
32
33
|
const pageMatrix = matrixForW2d(page, canvas.width, canvas.height, options.zoom, options.panX, options.panY);
|
|
34
|
+
const runtime = { dpr: canvasDpr(canvas), zoom: options.zoom ?? 1 };
|
|
33
35
|
let commands = 0;
|
|
34
36
|
if (options.preferWasm) {
|
|
35
37
|
try {
|
|
@@ -37,11 +39,11 @@ export class W2dRenderer {
|
|
|
37
39
|
await this.wasm.init();
|
|
38
40
|
this.wasm.begin(canvas.width, canvas.height, bg);
|
|
39
41
|
for (const p of page.primitives)
|
|
40
|
-
commands += this.drawPrimitiveWasm(p, pageMatrix);
|
|
42
|
+
commands += this.drawPrimitiveWasm(p, pageMatrix, options, runtime);
|
|
41
43
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
42
44
|
ctx.putImageData(this.wasm.toImageData(), 0, 0);
|
|
43
45
|
for (const p of page.primitives.filter(p => p.type === 'text'))
|
|
44
|
-
commands += this.drawPrimitiveCanvas(ctx, p, pageMatrix);
|
|
46
|
+
commands += this.drawPrimitiveCanvas(ctx, p, pageMatrix, options, runtime);
|
|
45
47
|
return { backend: 'wasm-raster', commands, warnings };
|
|
46
48
|
}
|
|
47
49
|
catch (err) {
|
|
@@ -54,7 +56,7 @@ export class W2dRenderer {
|
|
|
54
56
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
55
57
|
ctx.restore();
|
|
56
58
|
for (const p of page.primitives)
|
|
57
|
-
commands += this.drawPrimitiveCanvas(ctx, p, pageMatrix);
|
|
59
|
+
commands += this.drawPrimitiveCanvas(ctx, p, pageMatrix, options, runtime);
|
|
58
60
|
return { backend: 'canvas2d', commands, warnings };
|
|
59
61
|
}
|
|
60
62
|
dispose() {
|
|
@@ -70,14 +72,14 @@ export class W2dRenderer {
|
|
|
70
72
|
}
|
|
71
73
|
return this.webgl;
|
|
72
74
|
}
|
|
73
|
-
drawPrimitiveCanvas(ctx, p, pageMatrix) {
|
|
75
|
+
drawPrimitiveCanvas(ctx, p, pageMatrix, options, runtime) {
|
|
74
76
|
const matrix = multiplyMatrix(pageMatrix, p.matrix ?? { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 });
|
|
75
77
|
const stroke = parseBrushColor(p.stroke ?? '#000000') ?? '#000000';
|
|
76
78
|
const fill = parseBrushColor(p.fill);
|
|
77
79
|
if (p.type === 'text') {
|
|
78
80
|
const [x, y] = transformPoint(matrix, p.x, p.y);
|
|
79
|
-
const screenSize = Math.max(
|
|
80
|
-
if (
|
|
81
|
+
const screenSize = Math.max(1, Math.abs((p.size ?? 12) * estimateScale(matrix)));
|
|
82
|
+
if (!shouldDrawTextByPixelSize(p.size ?? 12, matrix, options, runtime) || x > ctx.canvas.width + 64 || y > ctx.canvas.height + 64 || x < -ctx.canvas.width || y < -ctx.canvas.height)
|
|
81
83
|
return 0;
|
|
82
84
|
ctx.save();
|
|
83
85
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
@@ -91,7 +93,7 @@ export class W2dRenderer {
|
|
|
91
93
|
}
|
|
92
94
|
ctx.save();
|
|
93
95
|
ctx.setTransform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f);
|
|
94
|
-
ctx.lineWidth =
|
|
96
|
+
ctx.lineWidth = adaptiveStrokeUserWidth(p.lineWidth ?? 1, matrix, options, runtime);
|
|
95
97
|
if (p.type === 'polyline') {
|
|
96
98
|
if (p.points.length >= 4) {
|
|
97
99
|
ctx.beginPath();
|
|
@@ -144,25 +146,26 @@ export class W2dRenderer {
|
|
|
144
146
|
ctx.restore();
|
|
145
147
|
return 1;
|
|
146
148
|
}
|
|
147
|
-
drawPrimitiveWasm(p, pageMatrix) {
|
|
149
|
+
drawPrimitiveWasm(p, pageMatrix, options, runtime) {
|
|
148
150
|
if (!this.wasm || p.type === 'text')
|
|
149
151
|
return 0;
|
|
150
152
|
const matrix = multiplyMatrix(pageMatrix, p.matrix ?? { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 });
|
|
151
|
-
const scale =
|
|
153
|
+
const scale = estimateMatrixScale(matrix);
|
|
154
|
+
const screenStroke = (w) => adaptiveStrokeUserWidth(w ?? 1, matrix, options, runtime) * scale;
|
|
152
155
|
if (p.type === 'polyline') {
|
|
153
|
-
this.wasm.drawPolyline(p.points, matrix, parseBrushColor(p.stroke ?? '#000000'), (p.lineWidth
|
|
156
|
+
this.wasm.drawPolyline(p.points, matrix, parseBrushColor(p.stroke ?? '#000000'), screenStroke(p.lineWidth));
|
|
154
157
|
}
|
|
155
158
|
else if (p.type === 'polygon') {
|
|
156
159
|
this.wasm.drawPolygon(p.points, matrix, parseBrushColor(p.fill));
|
|
157
160
|
if (p.stroke)
|
|
158
|
-
this.wasm.drawPolyline(closePoints(p.points), matrix, parseBrushColor(p.stroke), (p.lineWidth
|
|
161
|
+
this.wasm.drawPolyline(closePoints(p.points), matrix, parseBrushColor(p.stroke), screenStroke(p.lineWidth));
|
|
159
162
|
}
|
|
160
163
|
else if (p.type === 'rect') {
|
|
161
164
|
const pts = [p.x, p.y, p.x + p.width, p.y, p.x + p.width, p.y + p.height, p.x, p.y + p.height, p.x, p.y];
|
|
162
165
|
if (p.fill)
|
|
163
166
|
this.wasm.drawPolygon(pts, matrix, parseBrushColor(p.fill));
|
|
164
167
|
if (p.stroke)
|
|
165
|
-
this.wasm.drawPolyline(pts, matrix, parseBrushColor(p.stroke), (p.lineWidth
|
|
168
|
+
this.wasm.drawPolyline(pts, matrix, parseBrushColor(p.stroke), screenStroke(p.lineWidth));
|
|
166
169
|
}
|
|
167
170
|
else if (p.type === 'path') {
|
|
168
171
|
const subs = flattenPath(p.commands, 0.5);
|
|
@@ -174,7 +177,7 @@ export class W2dRenderer {
|
|
|
174
177
|
this.wasm.drawPolygon(s.points, matrix, fill);
|
|
175
178
|
if (stroke)
|
|
176
179
|
for (const s of subs)
|
|
177
|
-
this.wasm.drawPolyline(s.points, matrix, stroke, (p.lineWidth
|
|
180
|
+
this.wasm.drawPolyline(s.points, matrix, stroke, screenStroke(p.lineWidth));
|
|
178
181
|
}
|
|
179
182
|
return 1;
|
|
180
183
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Diagnostic } from '../format/types.js';
|
|
2
2
|
import type { W2dTextPageData } from '../format/document.js';
|
|
3
|
-
|
|
3
|
+
import { type CadLineStyleOptions } from './cadLineStyle.js';
|
|
4
|
+
export interface WebGlW2dRenderOptions extends CadLineStyleOptions {
|
|
4
5
|
zoom?: number;
|
|
5
6
|
panX?: number;
|
|
6
7
|
panY?: number;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { diag } from '../format/types.js';
|
|
2
2
|
import { flattenPath } from './xpsPath.js';
|
|
3
3
|
import { colorToRgba32, multiplyMatrix, transformPoint } from './style.js';
|
|
4
|
+
import { adaptiveStrokeUserWidth, canvasDpr, estimateMatrixScale } from './cadLineStyle.js';
|
|
4
5
|
import { matrixForW2d } from './viewport.js';
|
|
5
6
|
const VERTEX_STRIDE = 12;
|
|
6
7
|
const DEFAULT_MAX_GPU_CACHE_BYTES = 96 * 1024 * 1024;
|
|
@@ -38,11 +39,13 @@ export class WebGlW2dBackend {
|
|
|
38
39
|
return { commands: 0, warnings, gpuBytes: this.gpuBytes, vertexCount: 0, textCount: 0, cacheHit: true };
|
|
39
40
|
}
|
|
40
41
|
this.resize(targetCanvas.width, targetCanvas.height);
|
|
41
|
-
const
|
|
42
|
+
const pageMatrix = matrixForW2d(page, this.canvas.width, this.canvas.height, options.zoom, options.panX, options.panY);
|
|
43
|
+
const runtime = { dpr: canvasDpr(targetCanvas), zoom: options.zoom ?? 1 };
|
|
44
|
+
const key = sceneKey(page, pageMatrix, options);
|
|
42
45
|
let scene = this.scenes.get(key);
|
|
43
46
|
const cacheHit = !!scene;
|
|
44
47
|
if (!scene) {
|
|
45
|
-
scene = this.compileScene(page, key, options);
|
|
48
|
+
scene = this.compileScene(page, key, options, pageMatrix, runtime);
|
|
46
49
|
this.scenes.set(key, scene);
|
|
47
50
|
this.gpuBytes += scene.gpuBytes;
|
|
48
51
|
this.evictIfNeeded(options);
|
|
@@ -59,7 +62,6 @@ export class WebGlW2dBackend {
|
|
|
59
62
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
60
63
|
gl.clearColor(bg[0], bg[1], bg[2], bg[3]);
|
|
61
64
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
62
|
-
const pageMatrix = matrixForW2d(page, this.canvas.width, this.canvas.height, options.zoom, options.panX, options.panY);
|
|
63
65
|
gl.useProgram(this.program);
|
|
64
66
|
gl.bindBuffer(gl.ARRAY_BUFFER, scene.buffer);
|
|
65
67
|
gl.enableVertexAttribArray(this.aPos);
|
|
@@ -103,7 +105,7 @@ export class WebGlW2dBackend {
|
|
|
103
105
|
this.canvas.height = height;
|
|
104
106
|
}
|
|
105
107
|
}
|
|
106
|
-
compileScene(page, key, options) {
|
|
108
|
+
compileScene(page, key, options, pageMatrix, runtime) {
|
|
107
109
|
const writer = new VertexWriter();
|
|
108
110
|
let primitiveCount = 0;
|
|
109
111
|
let textCount = 0;
|
|
@@ -113,7 +115,7 @@ export class WebGlW2dBackend {
|
|
|
113
115
|
continue;
|
|
114
116
|
}
|
|
115
117
|
primitiveCount++;
|
|
116
|
-
appendPrimitive(writer, p);
|
|
118
|
+
appendPrimitive(writer, p, pageMatrix, options, runtime);
|
|
117
119
|
}
|
|
118
120
|
const bufferBytes = writer.byteLength;
|
|
119
121
|
const maxBytes = options.maxGpuCacheBytes ?? DEFAULT_MAX_GPU_CACHE_BYTES;
|
|
@@ -187,8 +189,10 @@ export class WebGlW2dBackend {
|
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
191
|
const IDENTITY_MATRIX = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
|
|
190
|
-
function sceneKey(page) {
|
|
191
|
-
|
|
192
|
+
function sceneKey(page, pageMatrix, options) {
|
|
193
|
+
const mode = options.lineWeightMode ?? 'adaptive';
|
|
194
|
+
const scaleBucket = mode === 'physical' ? 'physical' : String(Math.round(Math.log2(Math.max(1e-12, estimateMatrixScale(pageMatrix))) * 8));
|
|
195
|
+
return `${page.id}|${page.sourcePath}|${page.primitives.length}|lw:${mode}:${scaleBucket}`;
|
|
192
196
|
}
|
|
193
197
|
class VertexWriter {
|
|
194
198
|
constructor() {
|
|
@@ -226,10 +230,11 @@ class VertexWriter {
|
|
|
226
230
|
this.view = new DataView(this.buffer);
|
|
227
231
|
}
|
|
228
232
|
}
|
|
229
|
-
function appendPrimitive(writer, p) {
|
|
233
|
+
function appendPrimitive(writer, p, pageMatrix, options, runtime) {
|
|
230
234
|
const m = p.matrix ?? IDENTITY_MATRIX;
|
|
231
235
|
const matrixScale = estimateScale(m);
|
|
232
|
-
const
|
|
236
|
+
const fullMatrix = multiplyMatrix(pageMatrix, m);
|
|
237
|
+
const lineWidth = Math.max(0.01, adaptiveStrokeUserWidth(p.lineWidth ?? 1, fullMatrix, options, runtime) * matrixScale);
|
|
233
238
|
if (p.type === 'polyline') {
|
|
234
239
|
const color = rgbaBytes(p.stroke ?? '#000000');
|
|
235
240
|
appendPolyline(writer, transformPointsArray(p.points, m), lineWidth, color);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Diagnostic } from '../format/types.js';
|
|
2
|
+
import type { XpsPageData } from '../format/document.js';
|
|
3
|
+
import { type CadLineStyleOptions } from './cadLineStyle.js';
|
|
4
|
+
export interface WebGlXpsRenderOptions extends CadLineStyleOptions {
|
|
5
|
+
zoom?: number;
|
|
6
|
+
panX?: number;
|
|
7
|
+
panY?: number;
|
|
8
|
+
background?: string;
|
|
9
|
+
maxGpuCacheBytes?: number;
|
|
10
|
+
maxCachedScenes?: number;
|
|
11
|
+
compositeToTarget?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface WebGlXpsRenderResult {
|
|
14
|
+
commands: number;
|
|
15
|
+
warnings: Diagnostic[];
|
|
16
|
+
gpuBytes: number;
|
|
17
|
+
vertexCount: number;
|
|
18
|
+
pathCount: number;
|
|
19
|
+
cacheHit: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare class WebGlXpsBackend {
|
|
22
|
+
private readonly canvas;
|
|
23
|
+
private readonly gl;
|
|
24
|
+
private readonly program;
|
|
25
|
+
private readonly aPos;
|
|
26
|
+
private readonly aColor;
|
|
27
|
+
private readonly uMatrix;
|
|
28
|
+
private readonly uViewport;
|
|
29
|
+
private readonly scenes;
|
|
30
|
+
private gpuBytes;
|
|
31
|
+
private tick;
|
|
32
|
+
constructor(canvas?: HTMLCanvasElement);
|
|
33
|
+
render(page: XpsPageData, root: Element, targetCanvas: HTMLCanvasElement, options?: WebGlXpsRenderOptions): WebGlXpsRenderResult;
|
|
34
|
+
dispose(): void;
|
|
35
|
+
private resize;
|
|
36
|
+
private compileScene;
|
|
37
|
+
private evictIfNeeded;
|
|
38
|
+
}
|