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,400 @@
|
|
|
1
|
+
import { diag } from '../format/types.js';
|
|
2
|
+
import { flattenPath } from './xpsPath.js';
|
|
3
|
+
import { colorToRgba32, multiplyMatrix, transformPoint } from './style.js';
|
|
4
|
+
import { matrixForW2d } from './viewport.js';
|
|
5
|
+
const VERTEX_STRIDE = 12;
|
|
6
|
+
const DEFAULT_MAX_GPU_CACHE_BYTES = 96 * 1024 * 1024;
|
|
7
|
+
const DEFAULT_MAX_CACHED_SCENES = 3;
|
|
8
|
+
export class WebGlW2dBackend {
|
|
9
|
+
constructor(canvas) {
|
|
10
|
+
this.scenes = new Map();
|
|
11
|
+
this.gpuBytes = 0;
|
|
12
|
+
this.tick = 0;
|
|
13
|
+
this.canvas = canvas ?? document.createElement('canvas');
|
|
14
|
+
const gl = this.canvas.getContext('webgl', {
|
|
15
|
+
alpha: false,
|
|
16
|
+
antialias: true,
|
|
17
|
+
depth: false,
|
|
18
|
+
stencil: false,
|
|
19
|
+
preserveDrawingBuffer: true,
|
|
20
|
+
powerPreference: 'high-performance'
|
|
21
|
+
});
|
|
22
|
+
if (!gl)
|
|
23
|
+
throw new Error('WebGLRenderingContext is not available.');
|
|
24
|
+
this.gl = gl;
|
|
25
|
+
this.program = createProgram(gl, VERTEX_SHADER, FRAGMENT_SHADER);
|
|
26
|
+
this.aPos = gl.getAttribLocation(this.program, 'a_pos');
|
|
27
|
+
this.aColor = gl.getAttribLocation(this.program, 'a_color');
|
|
28
|
+
const matrix = gl.getUniformLocation(this.program, 'u_matrix');
|
|
29
|
+
const viewport = gl.getUniformLocation(this.program, 'u_viewport');
|
|
30
|
+
if (this.aPos < 0 || this.aColor < 0 || !matrix || !viewport)
|
|
31
|
+
throw new Error('Failed to resolve WebGL shader locations.');
|
|
32
|
+
this.uMatrix = matrix;
|
|
33
|
+
this.uViewport = viewport;
|
|
34
|
+
}
|
|
35
|
+
render(page, targetCanvas, options = {}) {
|
|
36
|
+
const warnings = [];
|
|
37
|
+
if (targetCanvas.width <= 0 || targetCanvas.height <= 0) {
|
|
38
|
+
return { commands: 0, warnings, gpuBytes: this.gpuBytes, vertexCount: 0, textCount: 0, cacheHit: true };
|
|
39
|
+
}
|
|
40
|
+
this.resize(targetCanvas.width, targetCanvas.height);
|
|
41
|
+
const key = sceneKey(page);
|
|
42
|
+
let scene = this.scenes.get(key);
|
|
43
|
+
const cacheHit = !!scene;
|
|
44
|
+
if (!scene) {
|
|
45
|
+
scene = this.compileScene(page, key, options);
|
|
46
|
+
this.scenes.set(key, scene);
|
|
47
|
+
this.gpuBytes += scene.gpuBytes;
|
|
48
|
+
this.evictIfNeeded(options);
|
|
49
|
+
// Scene can be evicted only if it is not the newly needed scene; get again for safety.
|
|
50
|
+
scene = this.scenes.get(key) ?? scene;
|
|
51
|
+
}
|
|
52
|
+
scene.lastUsed = ++this.tick;
|
|
53
|
+
const gl = this.gl;
|
|
54
|
+
const bg = rgba01(options.background ?? '#ffffff');
|
|
55
|
+
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
56
|
+
gl.disable(gl.DEPTH_TEST);
|
|
57
|
+
gl.disable(gl.CULL_FACE);
|
|
58
|
+
gl.enable(gl.BLEND);
|
|
59
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
60
|
+
gl.clearColor(bg[0], bg[1], bg[2], bg[3]);
|
|
61
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
62
|
+
const pageMatrix = matrixForW2d(page, this.canvas.width, this.canvas.height, options.zoom, options.panX, options.panY);
|
|
63
|
+
gl.useProgram(this.program);
|
|
64
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, scene.buffer);
|
|
65
|
+
gl.enableVertexAttribArray(this.aPos);
|
|
66
|
+
gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, VERTEX_STRIDE, 0);
|
|
67
|
+
gl.enableVertexAttribArray(this.aColor);
|
|
68
|
+
gl.vertexAttribPointer(this.aColor, 4, gl.UNSIGNED_BYTE, true, VERTEX_STRIDE, 8);
|
|
69
|
+
gl.uniform4f(this.uMatrix, pageMatrix.a, pageMatrix.b, pageMatrix.c, pageMatrix.d);
|
|
70
|
+
gl.uniform4f(this.uViewport, pageMatrix.e, pageMatrix.f, this.canvas.width, this.canvas.height);
|
|
71
|
+
gl.drawArrays(gl.TRIANGLES, 0, scene.vertexCount);
|
|
72
|
+
const ctx = targetCanvas.getContext('2d');
|
|
73
|
+
if (!ctx)
|
|
74
|
+
throw new Error('CanvasRenderingContext2D is not available for WebGL text overlay.');
|
|
75
|
+
ctx.save();
|
|
76
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
77
|
+
ctx.clearRect(0, 0, targetCanvas.width, targetCanvas.height);
|
|
78
|
+
if (options.compositeToTarget ?? true)
|
|
79
|
+
ctx.drawImage(this.canvas, 0, 0);
|
|
80
|
+
this.drawTextOverlay(ctx, page, pageMatrix);
|
|
81
|
+
ctx.restore();
|
|
82
|
+
if (scene.vertexCount === 0 && page.primitives.some(p => p.type !== 'text')) {
|
|
83
|
+
warnings.push(diag('warning', 'WEBGL_EMPTY_SCENE', 'WebGL scene contained no drawable geometry; Canvas/WASM fallback may be required.', page.sourcePath));
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
commands: scene.primitiveCount,
|
|
87
|
+
warnings,
|
|
88
|
+
gpuBytes: this.gpuBytes,
|
|
89
|
+
vertexCount: scene.vertexCount,
|
|
90
|
+
textCount: scene.textCount,
|
|
91
|
+
cacheHit
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
dispose() {
|
|
95
|
+
for (const scene of this.scenes.values())
|
|
96
|
+
this.gl.deleteBuffer(scene.buffer);
|
|
97
|
+
this.scenes.clear();
|
|
98
|
+
this.gpuBytes = 0;
|
|
99
|
+
}
|
|
100
|
+
resize(width, height) {
|
|
101
|
+
if (this.canvas.width !== width || this.canvas.height !== height) {
|
|
102
|
+
this.canvas.width = width;
|
|
103
|
+
this.canvas.height = height;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
compileScene(page, key, options) {
|
|
107
|
+
const writer = new VertexWriter();
|
|
108
|
+
let primitiveCount = 0;
|
|
109
|
+
let textCount = 0;
|
|
110
|
+
for (const p of page.primitives) {
|
|
111
|
+
if (p.type === 'text') {
|
|
112
|
+
textCount++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
primitiveCount++;
|
|
116
|
+
appendPrimitive(writer, p);
|
|
117
|
+
}
|
|
118
|
+
const bufferBytes = writer.byteLength;
|
|
119
|
+
const maxBytes = options.maxGpuCacheBytes ?? DEFAULT_MAX_GPU_CACHE_BYTES;
|
|
120
|
+
if (bufferBytes > maxBytes) {
|
|
121
|
+
throw new Error(`WebGL scene buffer would require ${formatBytes(bufferBytes)}, exceeding maxGpuCacheBytes=${formatBytes(maxBytes)}.`);
|
|
122
|
+
}
|
|
123
|
+
const gl = this.gl;
|
|
124
|
+
const buffer = gl.createBuffer();
|
|
125
|
+
if (!buffer)
|
|
126
|
+
throw new Error('Failed to allocate WebGLBuffer.');
|
|
127
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
128
|
+
gl.bufferData(gl.ARRAY_BUFFER, writer.toArrayBuffer(), gl.STATIC_DRAW);
|
|
129
|
+
const err = gl.getError();
|
|
130
|
+
if (err !== gl.NO_ERROR) {
|
|
131
|
+
gl.deleteBuffer(buffer);
|
|
132
|
+
throw new Error(`WebGL buffer upload failed: 0x${err.toString(16)}.`);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
key,
|
|
136
|
+
buffer,
|
|
137
|
+
vertexCount: writer.vertexCount,
|
|
138
|
+
gpuBytes: bufferBytes,
|
|
139
|
+
primitiveCount,
|
|
140
|
+
textCount,
|
|
141
|
+
lastUsed: ++this.tick
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
evictIfNeeded(options) {
|
|
145
|
+
const maxBytes = options.maxGpuCacheBytes ?? DEFAULT_MAX_GPU_CACHE_BYTES;
|
|
146
|
+
const maxScenes = options.maxCachedScenes ?? DEFAULT_MAX_CACHED_SCENES;
|
|
147
|
+
while (this.scenes.size > Math.max(1, maxScenes) || this.gpuBytes > maxBytes) {
|
|
148
|
+
let oldest;
|
|
149
|
+
for (const scene of this.scenes.values()) {
|
|
150
|
+
if (!oldest || scene.lastUsed < oldest.lastUsed)
|
|
151
|
+
oldest = scene;
|
|
152
|
+
}
|
|
153
|
+
if (!oldest)
|
|
154
|
+
break;
|
|
155
|
+
this.scenes.delete(oldest.key);
|
|
156
|
+
this.gl.deleteBuffer(oldest.buffer);
|
|
157
|
+
this.gpuBytes -= oldest.gpuBytes;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
drawTextOverlay(ctx, page, pageMatrix) {
|
|
161
|
+
const canvasW = this.canvas.width;
|
|
162
|
+
const canvasH = this.canvas.height;
|
|
163
|
+
for (const p of page.primitives) {
|
|
164
|
+
if (p.type !== 'text')
|
|
165
|
+
continue;
|
|
166
|
+
const matrix = multiplyMatrix(pageMatrix, p.matrix ?? IDENTITY_MATRIX);
|
|
167
|
+
const [x, y] = transformPoint(matrix, p.x, p.y);
|
|
168
|
+
const scale = estimateScale(matrix);
|
|
169
|
+
const screenSize = Math.max(2, Math.min(768, Math.abs((p.size ?? 12) * scale)));
|
|
170
|
+
if (screenSize < 2.5)
|
|
171
|
+
continue;
|
|
172
|
+
const lines = p.text.split(/\n/);
|
|
173
|
+
const longest = lines.reduce((m, line) => Math.max(m, line.length), 0);
|
|
174
|
+
const roughWidth = Math.max(24, longest * screenSize * 0.65);
|
|
175
|
+
const roughHeight = Math.max(screenSize, lines.length * screenSize * 1.15);
|
|
176
|
+
if (x + roughWidth < -64 || x > canvasW + 64 || y + roughHeight < -64 || y - roughHeight > canvasH + 64)
|
|
177
|
+
continue;
|
|
178
|
+
ctx.save();
|
|
179
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
180
|
+
ctx.fillStyle = p.fill ?? p.stroke ?? '#000000';
|
|
181
|
+
ctx.font = `${screenSize}px sans-serif`;
|
|
182
|
+
ctx.textBaseline = 'alphabetic';
|
|
183
|
+
for (let i = 0; i < lines.length; i++)
|
|
184
|
+
ctx.fillText(lines[i] ?? '', x, y + i * screenSize * 1.15);
|
|
185
|
+
ctx.restore();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const IDENTITY_MATRIX = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
|
|
190
|
+
function sceneKey(page) {
|
|
191
|
+
return `${page.id}|${page.sourcePath}|${page.primitives.length}`;
|
|
192
|
+
}
|
|
193
|
+
class VertexWriter {
|
|
194
|
+
constructor() {
|
|
195
|
+
this.buffer = new ArrayBuffer(64 * 1024);
|
|
196
|
+
this.view = new DataView(this.buffer);
|
|
197
|
+
this.vertexCount = 0;
|
|
198
|
+
}
|
|
199
|
+
get byteLength() { return this.vertexCount * VERTEX_STRIDE; }
|
|
200
|
+
push(x, y, color) {
|
|
201
|
+
if (!Number.isFinite(x) || !Number.isFinite(y))
|
|
202
|
+
return;
|
|
203
|
+
const offset = this.byteLength;
|
|
204
|
+
this.ensure(VERTEX_STRIDE);
|
|
205
|
+
this.view.setFloat32(offset, x, true);
|
|
206
|
+
this.view.setFloat32(offset + 4, y, true);
|
|
207
|
+
this.view.setUint8(offset + 8, color.r);
|
|
208
|
+
this.view.setUint8(offset + 9, color.g);
|
|
209
|
+
this.view.setUint8(offset + 10, color.b);
|
|
210
|
+
this.view.setUint8(offset + 11, color.a);
|
|
211
|
+
this.vertexCount++;
|
|
212
|
+
}
|
|
213
|
+
toArrayBuffer() {
|
|
214
|
+
return this.buffer.slice(0, this.byteLength);
|
|
215
|
+
}
|
|
216
|
+
ensure(extraBytes) {
|
|
217
|
+
const needed = this.byteLength + extraBytes;
|
|
218
|
+
if (needed <= this.buffer.byteLength)
|
|
219
|
+
return;
|
|
220
|
+
let next = this.buffer.byteLength;
|
|
221
|
+
while (next < needed)
|
|
222
|
+
next *= 2;
|
|
223
|
+
const newBuffer = new ArrayBuffer(next);
|
|
224
|
+
new Uint8Array(newBuffer).set(new Uint8Array(this.buffer, 0, this.byteLength));
|
|
225
|
+
this.buffer = newBuffer;
|
|
226
|
+
this.view = new DataView(this.buffer);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function appendPrimitive(writer, p) {
|
|
230
|
+
const m = p.matrix ?? IDENTITY_MATRIX;
|
|
231
|
+
const matrixScale = estimateScale(m);
|
|
232
|
+
const lineWidth = Math.max(0.1, (p.lineWidth ?? 1) * matrixScale);
|
|
233
|
+
if (p.type === 'polyline') {
|
|
234
|
+
const color = rgbaBytes(p.stroke ?? '#000000');
|
|
235
|
+
appendPolyline(writer, transformPointsArray(p.points, m), lineWidth, color);
|
|
236
|
+
}
|
|
237
|
+
else if (p.type === 'polygon') {
|
|
238
|
+
const pts = transformPointsArray(p.points, m);
|
|
239
|
+
if (p.fill)
|
|
240
|
+
appendPolygonFan(writer, pts, rgbaBytes(p.fill));
|
|
241
|
+
if (p.stroke)
|
|
242
|
+
appendPolyline(writer, closePoints(pts), lineWidth, rgbaBytes(p.stroke));
|
|
243
|
+
}
|
|
244
|
+
else if (p.type === 'rect') {
|
|
245
|
+
const pts = transformPointsArray([
|
|
246
|
+
p.x, p.y,
|
|
247
|
+
p.x + p.width, p.y,
|
|
248
|
+
p.x + p.width, p.y + p.height,
|
|
249
|
+
p.x, p.y + p.height
|
|
250
|
+
], m);
|
|
251
|
+
if (p.fill)
|
|
252
|
+
appendPolygonFan(writer, pts, rgbaBytes(p.fill));
|
|
253
|
+
if (p.stroke)
|
|
254
|
+
appendPolyline(writer, closePoints(pts), lineWidth, rgbaBytes(p.stroke));
|
|
255
|
+
}
|
|
256
|
+
else if (p.type === 'path') {
|
|
257
|
+
const subs = flattenPath(p.commands, 0.5);
|
|
258
|
+
const fill = p.fill ? rgbaBytes(p.fill) : undefined;
|
|
259
|
+
const stroke = p.stroke ? rgbaBytes(p.stroke) : undefined;
|
|
260
|
+
for (const sub of subs) {
|
|
261
|
+
const pts = transformPointsArray(sub.points, m);
|
|
262
|
+
if (fill && (sub.closed || pts.length >= 6))
|
|
263
|
+
appendPolygonFan(writer, pts, fill);
|
|
264
|
+
if (stroke)
|
|
265
|
+
appendPolyline(writer, pts, lineWidth, stroke);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function transformPointsArray(points, m) {
|
|
270
|
+
const out = [];
|
|
271
|
+
for (let i = 0; i + 1 < points.length; i += 2) {
|
|
272
|
+
const [x, y] = transformPoint(m, points[i] ?? 0, points[i + 1] ?? 0);
|
|
273
|
+
out.push({ x, y });
|
|
274
|
+
}
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
function appendPolygonFan(writer, pts, color) {
|
|
278
|
+
if (pts.length < 3)
|
|
279
|
+
return;
|
|
280
|
+
const p0 = pts[0];
|
|
281
|
+
for (let i = 1; i + 1 < pts.length; i++) {
|
|
282
|
+
const p1 = pts[i];
|
|
283
|
+
const p2 = pts[i + 1];
|
|
284
|
+
if (triangleAreaAbs(p0, p1, p2) < 1e-6)
|
|
285
|
+
continue;
|
|
286
|
+
writer.push(p0.x, p0.y, color);
|
|
287
|
+
writer.push(p1.x, p1.y, color);
|
|
288
|
+
writer.push(p2.x, p2.y, color);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function appendPolyline(writer, pts, width, color) {
|
|
292
|
+
if (pts.length < 2)
|
|
293
|
+
return;
|
|
294
|
+
const half = Math.max(0.05, width * 0.5);
|
|
295
|
+
for (let i = 0; i + 1 < pts.length; i++)
|
|
296
|
+
appendLineSegment(writer, pts[i], pts[i + 1], half, color);
|
|
297
|
+
}
|
|
298
|
+
function appendLineSegment(writer, p0, p1, half, color) {
|
|
299
|
+
const dx = p1.x - p0.x;
|
|
300
|
+
const dy = p1.y - p0.y;
|
|
301
|
+
const len = Math.hypot(dx, dy);
|
|
302
|
+
if (len <= 1e-9)
|
|
303
|
+
return;
|
|
304
|
+
const nx = -dy / len * half;
|
|
305
|
+
const ny = dx / len * half;
|
|
306
|
+
const ax = p0.x + nx, ay = p0.y + ny;
|
|
307
|
+
const bx = p0.x - nx, by = p0.y - ny;
|
|
308
|
+
const cx = p1.x - nx, cy = p1.y - ny;
|
|
309
|
+
const dx2 = p1.x + nx, dy2 = p1.y + ny;
|
|
310
|
+
writer.push(ax, ay, color);
|
|
311
|
+
writer.push(bx, by, color);
|
|
312
|
+
writer.push(cx, cy, color);
|
|
313
|
+
writer.push(ax, ay, color);
|
|
314
|
+
writer.push(cx, cy, color);
|
|
315
|
+
writer.push(dx2, dy2, color);
|
|
316
|
+
}
|
|
317
|
+
function closePoints(pts) {
|
|
318
|
+
if (pts.length < 2)
|
|
319
|
+
return pts;
|
|
320
|
+
const first = pts[0];
|
|
321
|
+
const last = pts[pts.length - 1];
|
|
322
|
+
if (first.x === last.x && first.y === last.y)
|
|
323
|
+
return pts;
|
|
324
|
+
return [...pts, { x: first.x, y: first.y }];
|
|
325
|
+
}
|
|
326
|
+
function triangleAreaAbs(a, b, c) {
|
|
327
|
+
return Math.abs((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x));
|
|
328
|
+
}
|
|
329
|
+
function estimateScale(m) {
|
|
330
|
+
const sx = Math.hypot(m.a, m.b);
|
|
331
|
+
const sy = Math.hypot(m.c, m.d);
|
|
332
|
+
return Math.max(1e-9, (sx + sy) * 0.5);
|
|
333
|
+
}
|
|
334
|
+
function rgbaBytes(css) {
|
|
335
|
+
const packed = colorToRgba32(css, 0xff000000);
|
|
336
|
+
return { r: packed & 255, g: (packed >>> 8) & 255, b: (packed >>> 16) & 255, a: (packed >>> 24) & 255 };
|
|
337
|
+
}
|
|
338
|
+
function rgba01(css) {
|
|
339
|
+
const c = rgbaBytes(css);
|
|
340
|
+
return [c.r / 255, c.g / 255, c.b / 255, c.a / 255];
|
|
341
|
+
}
|
|
342
|
+
function formatBytes(bytes) {
|
|
343
|
+
if (bytes < 1024)
|
|
344
|
+
return `${bytes} B`;
|
|
345
|
+
if (bytes < 1024 * 1024)
|
|
346
|
+
return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
347
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MiB`;
|
|
348
|
+
}
|
|
349
|
+
function createProgram(gl, vertexSource, fragmentSource) {
|
|
350
|
+
const vertex = compileShader(gl, gl.VERTEX_SHADER, vertexSource);
|
|
351
|
+
const fragment = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
|
|
352
|
+
const program = gl.createProgram();
|
|
353
|
+
if (!program)
|
|
354
|
+
throw new Error('Failed to create WebGLProgram.');
|
|
355
|
+
gl.attachShader(program, vertex);
|
|
356
|
+
gl.attachShader(program, fragment);
|
|
357
|
+
gl.linkProgram(program);
|
|
358
|
+
gl.deleteShader(vertex);
|
|
359
|
+
gl.deleteShader(fragment);
|
|
360
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
361
|
+
const log = gl.getProgramInfoLog(program) ?? 'unknown link error';
|
|
362
|
+
gl.deleteProgram(program);
|
|
363
|
+
throw new Error(`WebGL program link failed: ${log}`);
|
|
364
|
+
}
|
|
365
|
+
return program;
|
|
366
|
+
}
|
|
367
|
+
function compileShader(gl, type, source) {
|
|
368
|
+
const shader = gl.createShader(type);
|
|
369
|
+
if (!shader)
|
|
370
|
+
throw new Error('Failed to create WebGLShader.');
|
|
371
|
+
gl.shaderSource(shader, source);
|
|
372
|
+
gl.compileShader(shader);
|
|
373
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
374
|
+
const log = gl.getShaderInfoLog(shader) ?? 'unknown shader compile error';
|
|
375
|
+
gl.deleteShader(shader);
|
|
376
|
+
throw new Error(`WebGL shader compile failed: ${log}`);
|
|
377
|
+
}
|
|
378
|
+
return shader;
|
|
379
|
+
}
|
|
380
|
+
const VERTEX_SHADER = `
|
|
381
|
+
attribute vec2 a_pos;
|
|
382
|
+
attribute vec4 a_color;
|
|
383
|
+
uniform vec4 u_matrix;
|
|
384
|
+
uniform vec4 u_viewport;
|
|
385
|
+
varying vec4 v_color;
|
|
386
|
+
void main() {
|
|
387
|
+
float x = u_matrix.x * a_pos.x + u_matrix.z * a_pos.y + u_viewport.x;
|
|
388
|
+
float y = u_matrix.y * a_pos.x + u_matrix.w * a_pos.y + u_viewport.y;
|
|
389
|
+
vec2 clip = vec2((x / u_viewport.z) * 2.0 - 1.0, 1.0 - (y / u_viewport.w) * 2.0);
|
|
390
|
+
gl_Position = vec4(clip, 0.0, 1.0);
|
|
391
|
+
v_color = a_color;
|
|
392
|
+
}
|
|
393
|
+
`;
|
|
394
|
+
const FRAGMENT_SHADER = `
|
|
395
|
+
precision mediump float;
|
|
396
|
+
varying vec4 v_color;
|
|
397
|
+
void main() {
|
|
398
|
+
gl_FragColor = v_color;
|
|
399
|
+
}
|
|
400
|
+
`;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type RenderStats } from '../format/types.js';
|
|
2
|
+
import type { LoadedDwfDocument, XpsPageData } from '../format/document.js';
|
|
3
|
+
export interface XpsRenderOptions {
|
|
4
|
+
zoom?: number;
|
|
5
|
+
panX?: number;
|
|
6
|
+
panY?: number;
|
|
7
|
+
preferWasm?: boolean;
|
|
8
|
+
wasmUrl?: string;
|
|
9
|
+
background?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class XpsRenderer {
|
|
12
|
+
private readonly document;
|
|
13
|
+
private wasm?;
|
|
14
|
+
constructor(document: LoadedDwfDocument);
|
|
15
|
+
render(page: XpsPageData, canvas: HTMLCanvasElement, options?: XpsRenderOptions): Promise<RenderStats>;
|
|
16
|
+
private renderElementToCanvas;
|
|
17
|
+
private renderElementToWasm;
|
|
18
|
+
private drawImageResource;
|
|
19
|
+
private drawImageBrush;
|
|
20
|
+
}
|