quickwin 1.0.2 → 1.1.1

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.
Files changed (78) hide show
  1. package/README.en.md +204 -204
  2. package/README.md +203 -203
  3. package/examples/PdfCanvas.tsx +83 -83
  4. package/examples/exec_server.ts +35 -35
  5. package/examples/pdf_preview2.ts +375 -375
  6. package/examples/pdf_viewer.tsx +279 -279
  7. package/examples/test_gallery.tsx +285 -285
  8. package/examples/test_react_lazy.tsx +63 -63
  9. package/examples/test_react_listbox.tsx +71 -71
  10. package/examples/test_root_window.tsx +41 -41
  11. package/examples/test_tab.tsx +75 -75
  12. package/examples/tray_demo.tsx +81 -81
  13. package/lib/assert.ts +6 -6
  14. package/lib/cache-utils.ts +40 -40
  15. package/lib/dumpRects.ts +25 -25
  16. package/lib/fetch.ts +502 -502
  17. package/lib/http-common.ts +399 -399
  18. package/lib/http-server.ts +424 -424
  19. package/lib/polyfill.ts +171 -171
  20. package/lib/react-qw/components/index.ts +17 -17
  21. package/lib/react-qw/index.ts +90 -90
  22. package/lib/react-qw/jsx.d.ts +34 -34
  23. package/lib/react-qw/layout.ts +111 -111
  24. package/lib/react-qw/props.ts +36 -36
  25. package/lib/react-qw/reconciler.ts +373 -373
  26. package/lib/stream.ts +21 -21
  27. package/lib/text-codec.ts +171 -171
  28. package/lib/text-measure.ts +54 -54
  29. package/lib/url.ts +201 -201
  30. package/lib/vendor/web-streams/ponyfill.d.mts +829 -829
  31. package/lib/vendor/web-streams/ponyfill.mjs +8 -8
  32. package/lib/websocket.js +8 -0
  33. package/lib/websocket.ts +563 -555
  34. package/package.json +50 -44
  35. package/qwin-nowasm-x86.exe +0 -0
  36. package/qwin-nowasm.exe +0 -0
  37. package/qwin-x86.exe +0 -0
  38. package/qwin.exe +0 -0
  39. package/test/run.ts +96 -96
  40. package/test/test_basic.ts +9 -9
  41. package/test/test_basic_fetch.ts +33 -33
  42. package/test/test_brotli.ts +52 -52
  43. package/test/test_fetch_cache.ts +187 -187
  44. package/test/test_ffi.ts +174 -174
  45. package/test/test_frame_encoding.ts +132 -132
  46. package/test/test_helper.ts +80 -80
  47. package/test/test_http_import.ts +74 -74
  48. package/test/test_http_server.ts +136 -136
  49. package/test/test_import_dir.ts +5 -5
  50. package/test/test_ipv6.ts +129 -129
  51. package/test/test_meta_url.ts +4 -4
  52. package/test/test_mupdf_render.ts +74 -74
  53. package/test/test_mupdf_twice.ts +81 -81
  54. package/test/test_mupdf_wasm.ts +30 -30
  55. package/test/test_net_event.ts +55 -55
  56. package/test/test_net_fetch.ts +238 -238
  57. package/test/test_net_websocket.ts +164 -164
  58. package/test/test_polyfill.ts +159 -159
  59. package/test/test_url.ts +183 -183
  60. package/test/test_wasm_basic.ts +73 -73
  61. package/test/test_wasm_import_global.ts +45 -45
  62. package/test/test_wasm_sjlj.ts +125 -125
  63. package/test/test_wasm_types.ts +124 -124
  64. package/test/test_worker.ts +24 -24
  65. package/test/test_worker_fetch.ts +27 -27
  66. package/test/test_worker_net.ts +54 -54
  67. package/test/test_worker_wasm_concurrent.ts +45 -45
  68. package/test/worker_fetch.ts +13 -13
  69. package/test/worker_helper.ts +1 -1
  70. package/test/worker_http.ts +20 -20
  71. package/test/worker_import_lib.ts +13 -13
  72. package/test/worker_module.ts +17 -17
  73. package/test/worker_wasm_concurrent.ts +22 -22
  74. package/tsconfig.json +27 -27
  75. package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -571
  76. package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -2749
  77. package/vendor/mupdf-wasm/mupdf.d.ts +939 -939
  78. package/vendor/mupdf-wasm/mupdf.js +3317 -3317
@@ -1,375 +1,375 @@
1
- import '../lib/polyfill.js'
2
- import * as std from 'std'
3
- import * as gui from 'gui'
4
- import * as win from 'win'
5
- import * as ffi from 'ffi'
6
- import type { Document, Page, Pixmap } from '../vendor/mupdf-wasm/mupdf.js'
7
- import { assertNonNullable } from '../lib/assert.js'
8
-
9
- const FFI_PTR = ffi.FFI_TYPE_POINTER
10
- const FFI_U32 = ffi.FFI_TYPE_UINT32
11
- const FFI_S32 = ffi.FFI_TYPE_SINT32
12
-
13
- const _user32 = win.LoadLibrary('user32.dll')
14
- const _gdi32 = win.LoadLibrary('gdi32.dll')
15
- const _comdlg32 = win.LoadLibrary('comdlg32.dll')
16
-
17
- type MuPdf = typeof import('../vendor/mupdf-wasm/mupdf.js')
18
- if (!(_user32 && _gdi32 && _comdlg32)) std.exit(0)
19
-
20
- function loadProc(lib: win.HMODULE, name: string): number {
21
- const ptr = win.GetProcAddress(lib, name)
22
- if (!ptr) { std.printf('Error: cannot load %s\n', name); std.exit(1) }
23
- return ptr
24
- }
25
-
26
- const GetOpenFileNameW = loadProc(_comdlg32, 'GetOpenFileNameW')
27
- const SetDIBitsToDevice = loadProc(_gdi32, 'SetDIBitsToDevice')
28
- const GetDC = loadProc(_user32, 'GetDC')
29
- const ReleaseDC = loadProc(_user32, 'ReleaseDC')
30
- const PatBlt = loadProc(_gdi32, 'PatBlt')
31
- const WHITENESS = 0x00FF0062
32
-
33
- function makeBitmapInfo(w: number, h: number): ArrayBuffer {
34
- const bmi = new ArrayBuffer(40)
35
- const bv = new DataView(bmi)
36
- bv.setUint32(0, 40, true)
37
- bv.setInt32(4, w, true)
38
- bv.setInt32(8, -h, true)
39
- bv.setUint16(12, 1, true)
40
- bv.setUint16(14, 24, true)
41
- return bmi
42
- }
43
-
44
- function strToWide(str: string): ArrayBuffer {
45
- return new TextEncoder('utf-16le').encode(str + '\0').buffer
46
- }
47
-
48
- function wideToStr(buf: ArrayBuffer): string {
49
- const dv = new DataView(buf)
50
- const chars: number[] = []
51
- for (let i = 0; i < buf.byteLength; i += 2) {
52
- const c = dv.getUint16(i, true); if (c === 0) break
53
- chars.push(c)
54
- }
55
- return String.fromCharCode(...chars)
56
- }
57
-
58
- function setPtr(dv: DataView, off: number, ptr: number): void {
59
- dv.setUint32(off, ptr & 0xFFFFFFFF, true)
60
- dv.setUint32(off + 4, Math.floor(ptr / 0x100000000), true)
61
- }
62
-
63
- interface PixmapInfo {
64
- data: ArrayBuffer; w: number; h: number
65
- }
66
-
67
- let hwndMain: gui.HWND | null = null
68
- let hwndEdit: gui.HWND | null = null
69
- let hwndCanvas: gui.HWND | null = null
70
- let hwndContent: gui.HWND | null = null
71
- let currentPixmap: PixmapInfo | null = null
72
- let currentPage = 0, totalPages = 0
73
- let scrollX = 0, scrollY = 0
74
-
75
- let cachedPath = ''
76
- let cachedDoc: Document | null = null
77
-
78
- function clearCachedDoc(): void {
79
- if (cachedDoc) { try { cachedDoc.destroy() } catch {} }
80
- cachedDoc = null; cachedPath = ''
81
- }
82
-
83
- async function loadMupdf(): Promise<MuPdf | null> {
84
- const wasmPath = './vendor/mupdf-wasm/mupdf-wasm.wasm'
85
- const fp = std.open(wasmPath, 'rb')
86
- if (!fp) { std.printf('Error: cannot open %s\n', wasmPath); return null }
87
- fp.seek(0, 2); const size = fp.tell(); fp.seek(0, 0)
88
- const buf = new ArrayBuffer(size); fp.read(buf, 0, size); fp.close()
89
- ;(globalThis).$libmupdf_wasm_Module = { wasmBinary: buf, locateFile: (p: string) => p }
90
- try { return await import('../vendor/mupdf-wasm/mupdf.js') }
91
- catch (e) { std.printf('Error: mupdf load failed: %s\n', String(e)); return null }
92
- }
93
-
94
- function renderPdfPage(mupdf: MuPdf, filePath: string, pageIndex: number): PixmapInfo & { totalPages: number } | null {
95
- if (filePath !== cachedPath) {
96
- clearCachedDoc()
97
- const fp = std.open(filePath, 'rb')
98
- if (!fp) { std.printf('Error: cannot open %s\n', filePath); return null }
99
- let buf: ArrayBuffer | null = null
100
- try {
101
- fp.seek(0, 2); const size = fp.tell(); fp.seek(0, 0)
102
- buf = new ArrayBuffer(size); fp.read(buf, 0, size)
103
- } finally { fp.close() }
104
- if (!buf) return null
105
- try {
106
- cachedDoc = mupdf.Document.openDocument(new Uint8Array(buf), 'application/pdf')
107
- cachedPath = filePath
108
- } catch (e) { std.printf('Error opening document: %s\n', String(e)); return null }
109
- }
110
- if (!cachedDoc) return null
111
- let page: Page | null = null; let pixmap: Pixmap | null = null
112
- try {
113
- page = cachedDoc.loadPage(pageIndex)
114
- pixmap = page.toPixmap(mupdf.Matrix.scale(1.5, 1.5), mupdf.ColorSpace.DeviceRGB, false)
115
- if (!pixmap) return null
116
- const srcPixels = pixmap.getPixels()
117
- const srcStride = pixmap.getStride()
118
- const w = pixmap.getWidth(), h = pixmap.getHeight()
119
- const dibStride = Math.floor((w * 3 + 3) / 4) * 4
120
- const dib = new Uint8Array(h * dibStride)
121
- for (let y = 0; y < h; y++) {
122
- const srcOff = y * srcStride, dstOff = y * dibStride
123
- for (let x = 0; x < w; x++) {
124
- const sx = srcOff + x * 3, dx = dstOff + x * 3
125
- dib[dx] = srcPixels[sx + 2]!; dib[dx + 1] = srcPixels[sx + 1]!; dib[dx + 2] = srcPixels[sx]!
126
- }
127
- }
128
- const totalPages = cachedDoc.countPages()
129
- return { data: dib.buffer, w, h, totalPages }
130
- } catch (e) { std.printf('Error rendering: %s\n', String(e)); clearCachedDoc(); return null }
131
- finally { if (pixmap) try { pixmap.destroy() } catch {}; if (page) try { page.destroy() } catch {} }
132
- }
133
-
134
- function openPdfFileDialog(): string | null {
135
- assertNonNullable(hwndMain)
136
- const structBuf = new ArrayBuffer(152), sv = new DataView(structBuf)
137
- const fileBuf = new ArrayBuffer(260 * 2)
138
- const filterWide = strToWide('PDF Files\0*.pdf\0All Files\0*.*\0\0')
139
- sv.setUint32(0, 152, true)
140
- sv.setUint32(8, hwndMain & 0xFFFFFFFF, true)
141
- sv.setUint32(12, Math.floor(hwndMain / 0x100000000), true)
142
- setPtr(sv, 24, ffi.bufferPtr(filterWide))
143
- setPtr(sv, 48, ffi.bufferPtr(fileBuf))
144
- sv.setUint32(56, 260, true)
145
- sv.setUint32(96, 0x1000 | 0x0800 | 0x0004, true)
146
- const ret = ffi.ffiCall(GetOpenFileNameW, [FFI_PTR], [structBuf], FFI_U32)
147
- return ret ? wideToStr(fileBuf) : null
148
- }
149
-
150
- function updateScrollRange(): void {
151
- assertNonNullable(hwndCanvas); assertNonNullable(hwndContent)
152
- if (!currentPixmap) return
153
- const cr = gui.GetClientRect(hwndCanvas)
154
- if (!cr) return
155
- const cw = cr.right - cr.left, ch = cr.bottom - cr.top
156
- const maxX = Math.max(0, currentPixmap.w - cw)
157
- const maxY = Math.max(0, currentPixmap.h - ch)
158
- scrollX = Math.min(scrollX, maxX)
159
- scrollY = Math.min(scrollY, maxY)
160
- if (currentPixmap.w > cw) {
161
- const style = gui.GetWindowLongPtr(hwndCanvas, gui.Gwlp.STYLE) | 0
162
- if (!(style & gui.WindowStyle.HSCROLL)) {
163
- gui.SetWindowLongPtr(hwndCanvas, gui.Gwlp.STYLE, style | gui.WindowStyle.HSCROLL)
164
- gui.SetWindowPos(hwndCanvas, gui.SetWindowPosHwnd.TOP, 0, 0, 0, 0,
165
- gui.SetWindowPosFlag.SWP_NOMOVE | gui.SetWindowPosFlag.SWP_NOSIZE |
166
- gui.SetWindowPosFlag.SWP_NOZORDER | gui.SetWindowPosFlag.SWP_FRAMECHANGED)
167
- }
168
- gui.SetScrollInfo(hwndCanvas, gui.ScrollBar.HORZ, { min: 0, max: currentPixmap.w - 1, page: cw, pos: scrollX }, true)
169
- gui.ShowScrollBar(hwndCanvas, gui.ScrollBar.HORZ, true)
170
- } else {
171
- scrollX = 0
172
- const style = gui.GetWindowLongPtr(hwndCanvas, gui.Gwlp.STYLE) | 0
173
- if (style & gui.WindowStyle.HSCROLL) {
174
- gui.SetWindowLongPtr(hwndCanvas, gui.Gwlp.STYLE, style & ~gui.WindowStyle.HSCROLL)
175
- gui.SetWindowPos(hwndCanvas, gui.SetWindowPosHwnd.TOP, 0, 0, 0, 0,
176
- gui.SetWindowPosFlag.SWP_NOMOVE | gui.SetWindowPosFlag.SWP_NOSIZE |
177
- gui.SetWindowPosFlag.SWP_NOZORDER | gui.SetWindowPosFlag.SWP_FRAMECHANGED)
178
- }
179
- gui.ShowScrollBar(hwndCanvas, gui.ScrollBar.HORZ, false)
180
- }
181
- if (currentPixmap.h > ch) {
182
- gui.SetScrollInfo(hwndCanvas, gui.ScrollBar.VERT, { min: 0, max: currentPixmap.h - 1, page: ch, pos: scrollY }, true)
183
- gui.ShowScrollBar(hwndCanvas, gui.ScrollBar.VERT, true)
184
- } else { scrollY = 0; gui.ShowScrollBar(hwndCanvas, gui.ScrollBar.VERT, false) }
185
- gui.SetWindowPos(hwndContent, gui.SetWindowPosHwnd.TOP, -scrollX, -scrollY, 0, 0,
186
- gui.SetWindowPosFlag.SWP_NOSIZE | gui.SetWindowPosFlag.SWP_NOZORDER)
187
- }
188
-
189
- function showPdf(mupdf: MuPdf, path: string, pageIdx: number): void {
190
- assertNonNullable(hwndMain); assertNonNullable(hwndContent)
191
- const pix = renderPdfPage(mupdf, path, pageIdx)
192
- if (!pix) { gui.MessageBox('渲染 PDF 失败'); return }
193
- currentPixmap = pix; currentPage = pageIdx; totalPages = pix.totalPages
194
- gui.SetWindowText(hwndMain, 'PDF 预览 - 第 ' + (pageIdx + 1) + '/' + totalPages + ' 页')
195
- scrollX = 0; scrollY = 0
196
- gui.SetWindowPos(hwndContent, gui.SetWindowPosHwnd.TOP, 0, 0, pix.w, pix.h,
197
- gui.SetWindowPosFlag.SWP_NOZORDER | gui.SetWindowPosFlag.SWP_NOMOVE)
198
- updateScrollRange()
199
- gui.InvalidateRect(hwndContent, null, true)
200
- }
201
-
202
- function doScroll(dx: number, dy: number): void {
203
- assertNonNullable(hwndCanvas); assertNonNullable(hwndContent)
204
- const cr = gui.GetClientRect(hwndCanvas)
205
- if (!cr || !currentPixmap) return
206
- const cw = cr.right - cr.left, ch = cr.bottom - cr.top
207
- const maxX = Math.max(0, currentPixmap.w - cw)
208
- const maxY = Math.max(0, currentPixmap.h - ch)
209
- scrollX = Math.max(0, Math.min(maxX, scrollX + dx))
210
- scrollY = Math.max(0, Math.min(maxY, scrollY + dy))
211
- gui.SetWindowPos(hwndContent, gui.SetWindowPosHwnd.TOP, -scrollX, -scrollY, 0, 0,
212
- gui.SetWindowPosFlag.SWP_NOSIZE | gui.SetWindowPosFlag.SWP_NOZORDER)
213
- gui.SetScrollInfo(hwndCanvas, gui.ScrollBar.HORZ, { pos: scrollX }, true)
214
- gui.SetScrollInfo(hwndCanvas, gui.ScrollBar.VERT, { pos: scrollY }, true)
215
- }
216
-
217
- async function main(): Promise<void> {
218
- const mupdf = await loadMupdf()
219
- if (!mupdf) { gui.MessageBox('加载 mupdf WASM 失败'); return }
220
-
221
- const WS_CHILD = gui.WindowStyle.CHILD
222
- const WS_VIS = gui.WindowStyle.VISIBLE
223
- const WS_BORDER = gui.WindowStyle.BORDER
224
- const ctrlY = 12, ctrlH = 26, gap = 4, btnW = 80, btnPageW = 72
225
-
226
- let hwndBtnOpen: gui.HWND | null = null
227
- let hwndBtnPrev: gui.HWND | null = null
228
- let hwndBtnNext: gui.HWND | null = null
229
-
230
- gui.RegisterClass('PdfViewer2', (hwnd, msg, wParam, lParam) => {
231
- assertNonNullable(hwndCanvas); assertNonNullable(hwndEdit); assertNonNullable(hwndBtnOpen); assertNonNullable(hwndBtnPrev); assertNonNullable(hwndBtnNext)
232
- if (msg === gui.WmMsg.DESTROY) { gui.PostQuitMessage(0); return 0 }
233
- if (msg === gui.WmMsg.SIZE) {
234
- const cr = gui.GetClientRect(hwnd)
235
- if (cr) {
236
- const cw = cr.right - cr.left, ch = cr.bottom - cr.top
237
- gui.SetWindowPos(hwndCanvas, gui.SetWindowPosHwnd.TOP, 0, 50, cw, Math.max(1, ch - 50), gui.SetWindowPosFlag.SWP_NOZORDER)
238
- gui.SetWindowPos(hwndEdit, gui.SetWindowPosHwnd.TOP, 0, 0, Math.max(100, cw - 320), 0,
239
- gui.SetWindowPosFlag.SWP_NOMOVE | gui.SetWindowPosFlag.SWP_NOZORDER)
240
- }
241
- return gui.DefWindowProc(hwnd, msg, wParam, lParam)
242
- }
243
- if (msg === gui.WmMsg.COMMAND) {
244
- const hCtrl = lParam
245
- if (hCtrl === hwndBtnOpen) {
246
- const path = openPdfFileDialog()
247
- if (path) { gui.SetWindowText(hwndEdit, path); showPdf(mupdf, path, 0) }
248
- } else if (hCtrl === hwndBtnPrev) {
249
- const path = gui.GetWindowText(hwndEdit)
250
- if (path && currentPage > 0) showPdf(mupdf, path, currentPage - 1)
251
- } else if (hCtrl === hwndBtnNext) {
252
- const path = gui.GetWindowText(hwndEdit)
253
- if (path && currentPage < totalPages - 1) showPdf(mupdf, path, currentPage + 1)
254
- }
255
- return 0
256
- }
257
- return gui.DefWindowProc(hwnd, msg, wParam, lParam)
258
- })
259
- hwndMain = gui.CreateWindow('PdfViewer2', 'PDF 预览 2',
260
- gui.WindowStyle.OVERLAPPEDWINDOW | gui.WindowStyle.CLIPCHILDREN,
261
- 100, 100, 1100, 800, null, null)!
262
- if (!hwndMain) { gui.MessageBox('创建主窗口失败'); return }
263
-
264
- // 控件直接挂在主窗口上
265
- hwndEdit = gui.CreateWindow('EDIT', '',
266
- WS_CHILD | WS_VIS | WS_BORDER,
267
- ctrlY + btnW + gap, ctrlY, 480, ctrlH, hwndMain, null)!
268
- hwndBtnOpen = gui.CreateWindow('BUTTON', '打开 PDF',
269
- WS_CHILD | WS_VIS,
270
- ctrlY, ctrlY, btnW, ctrlH, hwndMain, null)!
271
- hwndBtnPrev = gui.CreateWindow('BUTTON', '上一页',
272
- WS_CHILD | WS_VIS,
273
- ctrlY + btnW + gap + 480 + gap, ctrlY, btnPageW, ctrlH, hwndMain, null)!
274
- hwndBtnNext = gui.CreateWindow('BUTTON', '下一页',
275
- WS_CHILD | WS_VIS,
276
- ctrlY + btnW + gap + 480 + gap + btnPageW + gap, ctrlY, btnPageW, ctrlH, hwndMain, null)!
277
-
278
- // 画布(带滚动条)
279
- hwndCanvas = gui.CreateWindow('STATIC', '',
280
- WS_CHILD | WS_VIS | gui.WindowStyle.CLIPCHILDREN | gui.WindowStyle.VSCROLL | gui.WindowStyle.HSCROLL,
281
- 0, 50, 1100, 750, hwndMain, null)!
282
-
283
- // 内容窗口(画 pixmap)
284
- hwndContent = gui.CreateWindow('STATIC', '',
285
- WS_CHILD | WS_VIS, 0, 0, 100, 100, hwndCanvas, null)!
286
-
287
- assertNonNullable(hwndMain)
288
- assertNonNullable(hwndEdit)
289
- assertNonNullable(hwndCanvas)
290
- assertNonNullable(hwndContent)
291
- assertNonNullable(hwndBtnOpen)
292
- assertNonNullable(hwndBtnPrev)
293
- assertNonNullable(hwndBtnNext)
294
-
295
- // 内容窗口 PAINT
296
- gui.SetWindowProc(hwndContent, (hwnd: gui.HWND, msg: number, wParam: number, lParam: number): number => {
297
- if (msg === gui.WmMsg.ERASEBKGND) return 1
298
- if (msg === gui.WmMsg.PAINT) {
299
- gui.DefWindowProc(hwnd, msg, wParam, lParam)
300
- const pm = currentPixmap
301
- if (!pm) return 0
302
- const hdc = ffi.ffiCall(GetDC, [ffi.FFI_TYPE_UINT64], [hwnd], ffi.FFI_TYPE_UINT64)
303
- if (hdc) {
304
- const bmi = makeBitmapInfo(pm.w, pm.h)
305
- ffi.ffiCall(SetDIBitsToDevice, [
306
- ffi.FFI_TYPE_UINT64, FFI_S32, FFI_S32, FFI_U32, FFI_U32,
307
- FFI_S32, FFI_S32, FFI_U32, FFI_U32,
308
- FFI_PTR, FFI_PTR, FFI_U32
309
- ], [hdc, 0, 0, pm.w, pm.h, 0, 0, 0, pm.h, pm.data, bmi, 0], FFI_S32)
310
- ffi.ffiCall(ReleaseDC, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_UINT64], [hwnd, hdc], FFI_S32)
311
- }
312
- return 0
313
- }
314
- return gui.DefWindowProc(hwnd, msg, wParam, lParam)
315
- })
316
-
317
- // 画布消息处理(PAINT 填白、滚动条、滚轮)
318
- gui.SetWindowProc(hwndCanvas, (hwnd: gui.HWND, msg: number, wParam: number, lParam: number): number => {
319
- if (msg === gui.WmMsg.ERASEBKGND) return 1
320
- if (msg === gui.WmMsg.PAINT) {
321
- const cr = gui.GetClientRect(hwnd)
322
- if (cr) {
323
- const cw = cr.right - cr.left, ch = cr.bottom - cr.top
324
- const hdc = ffi.ffiCall(GetDC, [ffi.FFI_TYPE_UINT64], [hwnd], ffi.FFI_TYPE_UINT64)
325
- if (hdc) {
326
- ffi.ffiCall(PatBlt, [ffi.FFI_TYPE_UINT64, FFI_S32, FFI_S32, FFI_S32, FFI_S32, FFI_U32],
327
- [hdc, 0, 0, cw, ch, WHITENESS], FFI_U32)
328
- ffi.ffiCall(ReleaseDC, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_UINT64], [hwnd, hdc], FFI_S32)
329
- }
330
- }
331
- gui.DefWindowProc(hwnd, msg, wParam, lParam)
332
- return 0
333
- }
334
- if (msg === gui.WmMsg.VSCROLL || msg === gui.WmMsg.HSCROLL) {
335
- const bar = msg === gui.WmMsg.VSCROLL ? gui.ScrollBar.VERT : gui.ScrollBar.HORZ
336
- const info = gui.GetScrollInfo(hwnd, bar)
337
- const maxPos = info.max - info.page + 1
338
- const code = wParam & 0xFFFF
339
- let newPos = bar === gui.ScrollBar.VERT ? scrollY : scrollX
340
- if (code === gui.ScrollCmd.THUMBTRACK || code === gui.ScrollCmd.THUMBPOSITION)
341
- newPos = Math.max(0, Math.min(maxPos, (wParam >> 16) & 0xFFFF))
342
- else if (code === gui.ScrollCmd.LINEUP) newPos -= 20
343
- else if (code === gui.ScrollCmd.LINEDOWN) newPos += 20
344
- else if (code === gui.ScrollCmd.PAGEUP) newPos -= info.page
345
- else if (code === gui.ScrollCmd.PAGEDOWN) newPos += info.page
346
- else return 0
347
- newPos = Math.max(0, Math.min(maxPos, newPos))
348
- if (bar === gui.ScrollBar.VERT) doScroll(0, newPos - scrollY)
349
- else doScroll(newPos - scrollX, 0)
350
- return 0
351
- }
352
- if (msg === gui.WmMsg.MOUSEWHEEL) {
353
- const raw = (wParam >>> 16) & 0xFFFF
354
- const wheel = raw >= 0x8000 ? raw - 0x10000 : raw
355
- const isHorz = (wParam & gui.MouseKeyFlag.MK_SHIFT) !== 0
356
- const bar = isHorz ? gui.ScrollBar.HORZ : gui.ScrollBar.VERT
357
- const info = gui.GetScrollInfo(hwnd, bar)
358
- const maxPos = info.max - info.page + 1
359
- let newPos = (isHorz ? scrollX : scrollY) - Math.round(wheel * 40 / 120)
360
- newPos = Math.max(0, Math.min(maxPos, newPos))
361
- if (isHorz) doScroll(newPos - scrollX, 0)
362
- else doScroll(0, newPos - scrollY)
363
- return 0
364
- }
365
- if (msg === gui.WmMsg.SIZE) { updateScrollRange(); return gui.DefWindowProc(hwnd, msg, wParam, lParam) }
366
- return gui.DefWindowProc(hwnd, msg, wParam, lParam)
367
- })
368
-
369
- gui.ShowWindow(hwndMain)
370
-
371
- const test = std.open('example.pdf', 'rb')
372
- if (test) { test.close(); showPdf(mupdf, 'example.pdf', 0) }
373
- }
374
-
375
- main()
1
+ import '../lib/polyfill.js'
2
+ import * as std from 'std'
3
+ import * as gui from 'gui'
4
+ import * as win from 'win'
5
+ import * as ffi from 'ffi'
6
+ import type { Document, Page, Pixmap } from '../vendor/mupdf-wasm/mupdf.js'
7
+ import { assertNonNullable } from '../lib/assert.js'
8
+
9
+ const FFI_PTR = ffi.FFI_TYPE_POINTER
10
+ const FFI_U32 = ffi.FFI_TYPE_UINT32
11
+ const FFI_S32 = ffi.FFI_TYPE_SINT32
12
+
13
+ const _user32 = win.LoadLibrary('user32.dll')
14
+ const _gdi32 = win.LoadLibrary('gdi32.dll')
15
+ const _comdlg32 = win.LoadLibrary('comdlg32.dll')
16
+
17
+ type MuPdf = typeof import('../vendor/mupdf-wasm/mupdf.js')
18
+ if (!(_user32 && _gdi32 && _comdlg32)) std.exit(0)
19
+
20
+ function loadProc(lib: win.HMODULE, name: string): number {
21
+ const ptr = win.GetProcAddress(lib, name)
22
+ if (!ptr) { std.printf('Error: cannot load %s\n', name); std.exit(1) }
23
+ return ptr
24
+ }
25
+
26
+ const GetOpenFileNameW = loadProc(_comdlg32, 'GetOpenFileNameW')
27
+ const SetDIBitsToDevice = loadProc(_gdi32, 'SetDIBitsToDevice')
28
+ const GetDC = loadProc(_user32, 'GetDC')
29
+ const ReleaseDC = loadProc(_user32, 'ReleaseDC')
30
+ const PatBlt = loadProc(_gdi32, 'PatBlt')
31
+ const WHITENESS = 0x00FF0062
32
+
33
+ function makeBitmapInfo(w: number, h: number): ArrayBuffer {
34
+ const bmi = new ArrayBuffer(40)
35
+ const bv = new DataView(bmi)
36
+ bv.setUint32(0, 40, true)
37
+ bv.setInt32(4, w, true)
38
+ bv.setInt32(8, -h, true)
39
+ bv.setUint16(12, 1, true)
40
+ bv.setUint16(14, 24, true)
41
+ return bmi
42
+ }
43
+
44
+ function strToWide(str: string): ArrayBuffer {
45
+ return new TextEncoder('utf-16le').encode(str + '\0').buffer
46
+ }
47
+
48
+ function wideToStr(buf: ArrayBuffer): string {
49
+ const dv = new DataView(buf)
50
+ const chars: number[] = []
51
+ for (let i = 0; i < buf.byteLength; i += 2) {
52
+ const c = dv.getUint16(i, true); if (c === 0) break
53
+ chars.push(c)
54
+ }
55
+ return String.fromCharCode(...chars)
56
+ }
57
+
58
+ function setPtr(dv: DataView, off: number, ptr: number): void {
59
+ dv.setUint32(off, ptr & 0xFFFFFFFF, true)
60
+ dv.setUint32(off + 4, Math.floor(ptr / 0x100000000), true)
61
+ }
62
+
63
+ interface PixmapInfo {
64
+ data: ArrayBuffer; w: number; h: number
65
+ }
66
+
67
+ let hwndMain: gui.HWND | null = null
68
+ let hwndEdit: gui.HWND | null = null
69
+ let hwndCanvas: gui.HWND | null = null
70
+ let hwndContent: gui.HWND | null = null
71
+ let currentPixmap: PixmapInfo | null = null
72
+ let currentPage = 0, totalPages = 0
73
+ let scrollX = 0, scrollY = 0
74
+
75
+ let cachedPath = ''
76
+ let cachedDoc: Document | null = null
77
+
78
+ function clearCachedDoc(): void {
79
+ if (cachedDoc) { try { cachedDoc.destroy() } catch {} }
80
+ cachedDoc = null; cachedPath = ''
81
+ }
82
+
83
+ async function loadMupdf(): Promise<MuPdf | null> {
84
+ const wasmPath = './vendor/mupdf-wasm/mupdf-wasm.wasm'
85
+ const fp = std.open(wasmPath, 'rb')
86
+ if (!fp) { std.printf('Error: cannot open %s\n', wasmPath); return null }
87
+ fp.seek(0, 2); const size = fp.tell(); fp.seek(0, 0)
88
+ const buf = new ArrayBuffer(size); fp.read(buf, 0, size); fp.close()
89
+ ;(globalThis).$libmupdf_wasm_Module = { wasmBinary: buf, locateFile: (p: string) => p }
90
+ try { return await import('../vendor/mupdf-wasm/mupdf.js') }
91
+ catch (e) { std.printf('Error: mupdf load failed: %s\n', String(e)); return null }
92
+ }
93
+
94
+ function renderPdfPage(mupdf: MuPdf, filePath: string, pageIndex: number): PixmapInfo & { totalPages: number } | null {
95
+ if (filePath !== cachedPath) {
96
+ clearCachedDoc()
97
+ const fp = std.open(filePath, 'rb')
98
+ if (!fp) { std.printf('Error: cannot open %s\n', filePath); return null }
99
+ let buf: ArrayBuffer | null = null
100
+ try {
101
+ fp.seek(0, 2); const size = fp.tell(); fp.seek(0, 0)
102
+ buf = new ArrayBuffer(size); fp.read(buf, 0, size)
103
+ } finally { fp.close() }
104
+ if (!buf) return null
105
+ try {
106
+ cachedDoc = mupdf.Document.openDocument(new Uint8Array(buf), 'application/pdf')
107
+ cachedPath = filePath
108
+ } catch (e) { std.printf('Error opening document: %s\n', String(e)); return null }
109
+ }
110
+ if (!cachedDoc) return null
111
+ let page: Page | null = null; let pixmap: Pixmap | null = null
112
+ try {
113
+ page = cachedDoc.loadPage(pageIndex)
114
+ pixmap = page.toPixmap(mupdf.Matrix.scale(1.5, 1.5), mupdf.ColorSpace.DeviceRGB, false)
115
+ if (!pixmap) return null
116
+ const srcPixels = pixmap.getPixels()
117
+ const srcStride = pixmap.getStride()
118
+ const w = pixmap.getWidth(), h = pixmap.getHeight()
119
+ const dibStride = Math.floor((w * 3 + 3) / 4) * 4
120
+ const dib = new Uint8Array(h * dibStride)
121
+ for (let y = 0; y < h; y++) {
122
+ const srcOff = y * srcStride, dstOff = y * dibStride
123
+ for (let x = 0; x < w; x++) {
124
+ const sx = srcOff + x * 3, dx = dstOff + x * 3
125
+ dib[dx] = srcPixels[sx + 2]!; dib[dx + 1] = srcPixels[sx + 1]!; dib[dx + 2] = srcPixels[sx]!
126
+ }
127
+ }
128
+ const totalPages = cachedDoc.countPages()
129
+ return { data: dib.buffer, w, h, totalPages }
130
+ } catch (e) { std.printf('Error rendering: %s\n', String(e)); clearCachedDoc(); return null }
131
+ finally { if (pixmap) try { pixmap.destroy() } catch {}; if (page) try { page.destroy() } catch {} }
132
+ }
133
+
134
+ function openPdfFileDialog(): string | null {
135
+ assertNonNullable(hwndMain)
136
+ const structBuf = new ArrayBuffer(152), sv = new DataView(structBuf)
137
+ const fileBuf = new ArrayBuffer(260 * 2)
138
+ const filterWide = strToWide('PDF Files\0*.pdf\0All Files\0*.*\0\0')
139
+ sv.setUint32(0, 152, true)
140
+ sv.setUint32(8, hwndMain & 0xFFFFFFFF, true)
141
+ sv.setUint32(12, Math.floor(hwndMain / 0x100000000), true)
142
+ setPtr(sv, 24, ffi.bufferPtr(filterWide))
143
+ setPtr(sv, 48, ffi.bufferPtr(fileBuf))
144
+ sv.setUint32(56, 260, true)
145
+ sv.setUint32(96, 0x1000 | 0x0800 | 0x0004, true)
146
+ const ret = ffi.ffiCall(GetOpenFileNameW, [FFI_PTR], [structBuf], FFI_U32)
147
+ return ret ? wideToStr(fileBuf) : null
148
+ }
149
+
150
+ function updateScrollRange(): void {
151
+ assertNonNullable(hwndCanvas); assertNonNullable(hwndContent)
152
+ if (!currentPixmap) return
153
+ const cr = gui.GetClientRect(hwndCanvas)
154
+ if (!cr) return
155
+ const cw = cr.right - cr.left, ch = cr.bottom - cr.top
156
+ const maxX = Math.max(0, currentPixmap.w - cw)
157
+ const maxY = Math.max(0, currentPixmap.h - ch)
158
+ scrollX = Math.min(scrollX, maxX)
159
+ scrollY = Math.min(scrollY, maxY)
160
+ if (currentPixmap.w > cw) {
161
+ const style = gui.GetWindowLongPtr(hwndCanvas, gui.Gwlp.STYLE) | 0
162
+ if (!(style & gui.WindowStyle.HSCROLL)) {
163
+ gui.SetWindowLongPtr(hwndCanvas, gui.Gwlp.STYLE, style | gui.WindowStyle.HSCROLL)
164
+ gui.SetWindowPos(hwndCanvas, gui.SetWindowPosHwnd.TOP, 0, 0, 0, 0,
165
+ gui.SetWindowPosFlag.SWP_NOMOVE | gui.SetWindowPosFlag.SWP_NOSIZE |
166
+ gui.SetWindowPosFlag.SWP_NOZORDER | gui.SetWindowPosFlag.SWP_FRAMECHANGED)
167
+ }
168
+ gui.SetScrollInfo(hwndCanvas, gui.ScrollBar.HORZ, { min: 0, max: currentPixmap.w - 1, page: cw, pos: scrollX }, true)
169
+ gui.ShowScrollBar(hwndCanvas, gui.ScrollBar.HORZ, true)
170
+ } else {
171
+ scrollX = 0
172
+ const style = gui.GetWindowLongPtr(hwndCanvas, gui.Gwlp.STYLE) | 0
173
+ if (style & gui.WindowStyle.HSCROLL) {
174
+ gui.SetWindowLongPtr(hwndCanvas, gui.Gwlp.STYLE, style & ~gui.WindowStyle.HSCROLL)
175
+ gui.SetWindowPos(hwndCanvas, gui.SetWindowPosHwnd.TOP, 0, 0, 0, 0,
176
+ gui.SetWindowPosFlag.SWP_NOMOVE | gui.SetWindowPosFlag.SWP_NOSIZE |
177
+ gui.SetWindowPosFlag.SWP_NOZORDER | gui.SetWindowPosFlag.SWP_FRAMECHANGED)
178
+ }
179
+ gui.ShowScrollBar(hwndCanvas, gui.ScrollBar.HORZ, false)
180
+ }
181
+ if (currentPixmap.h > ch) {
182
+ gui.SetScrollInfo(hwndCanvas, gui.ScrollBar.VERT, { min: 0, max: currentPixmap.h - 1, page: ch, pos: scrollY }, true)
183
+ gui.ShowScrollBar(hwndCanvas, gui.ScrollBar.VERT, true)
184
+ } else { scrollY = 0; gui.ShowScrollBar(hwndCanvas, gui.ScrollBar.VERT, false) }
185
+ gui.SetWindowPos(hwndContent, gui.SetWindowPosHwnd.TOP, -scrollX, -scrollY, 0, 0,
186
+ gui.SetWindowPosFlag.SWP_NOSIZE | gui.SetWindowPosFlag.SWP_NOZORDER)
187
+ }
188
+
189
+ function showPdf(mupdf: MuPdf, path: string, pageIdx: number): void {
190
+ assertNonNullable(hwndMain); assertNonNullable(hwndContent)
191
+ const pix = renderPdfPage(mupdf, path, pageIdx)
192
+ if (!pix) { gui.MessageBox('渲染 PDF 失败'); return }
193
+ currentPixmap = pix; currentPage = pageIdx; totalPages = pix.totalPages
194
+ gui.SetWindowText(hwndMain, 'PDF 预览 - 第 ' + (pageIdx + 1) + '/' + totalPages + ' 页')
195
+ scrollX = 0; scrollY = 0
196
+ gui.SetWindowPos(hwndContent, gui.SetWindowPosHwnd.TOP, 0, 0, pix.w, pix.h,
197
+ gui.SetWindowPosFlag.SWP_NOZORDER | gui.SetWindowPosFlag.SWP_NOMOVE)
198
+ updateScrollRange()
199
+ gui.InvalidateRect(hwndContent, null, true)
200
+ }
201
+
202
+ function doScroll(dx: number, dy: number): void {
203
+ assertNonNullable(hwndCanvas); assertNonNullable(hwndContent)
204
+ const cr = gui.GetClientRect(hwndCanvas)
205
+ if (!cr || !currentPixmap) return
206
+ const cw = cr.right - cr.left, ch = cr.bottom - cr.top
207
+ const maxX = Math.max(0, currentPixmap.w - cw)
208
+ const maxY = Math.max(0, currentPixmap.h - ch)
209
+ scrollX = Math.max(0, Math.min(maxX, scrollX + dx))
210
+ scrollY = Math.max(0, Math.min(maxY, scrollY + dy))
211
+ gui.SetWindowPos(hwndContent, gui.SetWindowPosHwnd.TOP, -scrollX, -scrollY, 0, 0,
212
+ gui.SetWindowPosFlag.SWP_NOSIZE | gui.SetWindowPosFlag.SWP_NOZORDER)
213
+ gui.SetScrollInfo(hwndCanvas, gui.ScrollBar.HORZ, { pos: scrollX }, true)
214
+ gui.SetScrollInfo(hwndCanvas, gui.ScrollBar.VERT, { pos: scrollY }, true)
215
+ }
216
+
217
+ async function main(): Promise<void> {
218
+ const mupdf = await loadMupdf()
219
+ if (!mupdf) { gui.MessageBox('加载 mupdf WASM 失败'); return }
220
+
221
+ const WS_CHILD = gui.WindowStyle.CHILD
222
+ const WS_VIS = gui.WindowStyle.VISIBLE
223
+ const WS_BORDER = gui.WindowStyle.BORDER
224
+ const ctrlY = 12, ctrlH = 26, gap = 4, btnW = 80, btnPageW = 72
225
+
226
+ let hwndBtnOpen: gui.HWND | null = null
227
+ let hwndBtnPrev: gui.HWND | null = null
228
+ let hwndBtnNext: gui.HWND | null = null
229
+
230
+ gui.RegisterClass('PdfViewer2', (hwnd, msg, wParam, lParam) => {
231
+ assertNonNullable(hwndCanvas); assertNonNullable(hwndEdit); assertNonNullable(hwndBtnOpen); assertNonNullable(hwndBtnPrev); assertNonNullable(hwndBtnNext)
232
+ if (msg === gui.WmMsg.DESTROY) { gui.PostQuitMessage(0); return 0 }
233
+ if (msg === gui.WmMsg.SIZE) {
234
+ const cr = gui.GetClientRect(hwnd)
235
+ if (cr) {
236
+ const cw = cr.right - cr.left, ch = cr.bottom - cr.top
237
+ gui.SetWindowPos(hwndCanvas, gui.SetWindowPosHwnd.TOP, 0, 50, cw, Math.max(1, ch - 50), gui.SetWindowPosFlag.SWP_NOZORDER)
238
+ gui.SetWindowPos(hwndEdit, gui.SetWindowPosHwnd.TOP, 0, 0, Math.max(100, cw - 320), 0,
239
+ gui.SetWindowPosFlag.SWP_NOMOVE | gui.SetWindowPosFlag.SWP_NOZORDER)
240
+ }
241
+ return gui.DefWindowProc(hwnd, msg, wParam, lParam)
242
+ }
243
+ if (msg === gui.WmMsg.COMMAND) {
244
+ const hCtrl = lParam
245
+ if (hCtrl === hwndBtnOpen) {
246
+ const path = openPdfFileDialog()
247
+ if (path) { gui.SetWindowText(hwndEdit, path); showPdf(mupdf, path, 0) }
248
+ } else if (hCtrl === hwndBtnPrev) {
249
+ const path = gui.GetWindowText(hwndEdit)
250
+ if (path && currentPage > 0) showPdf(mupdf, path, currentPage - 1)
251
+ } else if (hCtrl === hwndBtnNext) {
252
+ const path = gui.GetWindowText(hwndEdit)
253
+ if (path && currentPage < totalPages - 1) showPdf(mupdf, path, currentPage + 1)
254
+ }
255
+ return 0
256
+ }
257
+ return gui.DefWindowProc(hwnd, msg, wParam, lParam)
258
+ })
259
+ hwndMain = gui.CreateWindow('PdfViewer2', 'PDF 预览 2',
260
+ gui.WindowStyle.OVERLAPPEDWINDOW | gui.WindowStyle.CLIPCHILDREN,
261
+ 100, 100, 1100, 800, null, null)!
262
+ if (!hwndMain) { gui.MessageBox('创建主窗口失败'); return }
263
+
264
+ // 控件直接挂在主窗口上
265
+ hwndEdit = gui.CreateWindow('EDIT', '',
266
+ WS_CHILD | WS_VIS | WS_BORDER,
267
+ ctrlY + btnW + gap, ctrlY, 480, ctrlH, hwndMain, null)!
268
+ hwndBtnOpen = gui.CreateWindow('BUTTON', '打开 PDF',
269
+ WS_CHILD | WS_VIS,
270
+ ctrlY, ctrlY, btnW, ctrlH, hwndMain, null)!
271
+ hwndBtnPrev = gui.CreateWindow('BUTTON', '上一页',
272
+ WS_CHILD | WS_VIS,
273
+ ctrlY + btnW + gap + 480 + gap, ctrlY, btnPageW, ctrlH, hwndMain, null)!
274
+ hwndBtnNext = gui.CreateWindow('BUTTON', '下一页',
275
+ WS_CHILD | WS_VIS,
276
+ ctrlY + btnW + gap + 480 + gap + btnPageW + gap, ctrlY, btnPageW, ctrlH, hwndMain, null)!
277
+
278
+ // 画布(带滚动条)
279
+ hwndCanvas = gui.CreateWindow('STATIC', '',
280
+ WS_CHILD | WS_VIS | gui.WindowStyle.CLIPCHILDREN | gui.WindowStyle.VSCROLL | gui.WindowStyle.HSCROLL,
281
+ 0, 50, 1100, 750, hwndMain, null)!
282
+
283
+ // 内容窗口(画 pixmap)
284
+ hwndContent = gui.CreateWindow('STATIC', '',
285
+ WS_CHILD | WS_VIS, 0, 0, 100, 100, hwndCanvas, null)!
286
+
287
+ assertNonNullable(hwndMain)
288
+ assertNonNullable(hwndEdit)
289
+ assertNonNullable(hwndCanvas)
290
+ assertNonNullable(hwndContent)
291
+ assertNonNullable(hwndBtnOpen)
292
+ assertNonNullable(hwndBtnPrev)
293
+ assertNonNullable(hwndBtnNext)
294
+
295
+ // 内容窗口 PAINT
296
+ gui.SetWindowProc(hwndContent, (hwnd: gui.HWND, msg: number, wParam: number, lParam: number): number => {
297
+ if (msg === gui.WmMsg.ERASEBKGND) return 1
298
+ if (msg === gui.WmMsg.PAINT) {
299
+ gui.DefWindowProc(hwnd, msg, wParam, lParam)
300
+ const pm = currentPixmap
301
+ if (!pm) return 0
302
+ const hdc = ffi.ffiCall(GetDC, [ffi.FFI_TYPE_UINT64], [hwnd], ffi.FFI_TYPE_UINT64)
303
+ if (hdc) {
304
+ const bmi = makeBitmapInfo(pm.w, pm.h)
305
+ ffi.ffiCall(SetDIBitsToDevice, [
306
+ ffi.FFI_TYPE_UINT64, FFI_S32, FFI_S32, FFI_U32, FFI_U32,
307
+ FFI_S32, FFI_S32, FFI_U32, FFI_U32,
308
+ FFI_PTR, FFI_PTR, FFI_U32
309
+ ], [hdc, 0, 0, pm.w, pm.h, 0, 0, 0, pm.h, pm.data, bmi, 0], FFI_S32)
310
+ ffi.ffiCall(ReleaseDC, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_UINT64], [hwnd, hdc], FFI_S32)
311
+ }
312
+ return 0
313
+ }
314
+ return gui.DefWindowProc(hwnd, msg, wParam, lParam)
315
+ })
316
+
317
+ // 画布消息处理(PAINT 填白、滚动条、滚轮)
318
+ gui.SetWindowProc(hwndCanvas, (hwnd: gui.HWND, msg: number, wParam: number, lParam: number): number => {
319
+ if (msg === gui.WmMsg.ERASEBKGND) return 1
320
+ if (msg === gui.WmMsg.PAINT) {
321
+ const cr = gui.GetClientRect(hwnd)
322
+ if (cr) {
323
+ const cw = cr.right - cr.left, ch = cr.bottom - cr.top
324
+ const hdc = ffi.ffiCall(GetDC, [ffi.FFI_TYPE_UINT64], [hwnd], ffi.FFI_TYPE_UINT64)
325
+ if (hdc) {
326
+ ffi.ffiCall(PatBlt, [ffi.FFI_TYPE_UINT64, FFI_S32, FFI_S32, FFI_S32, FFI_S32, FFI_U32],
327
+ [hdc, 0, 0, cw, ch, WHITENESS], FFI_U32)
328
+ ffi.ffiCall(ReleaseDC, [ffi.FFI_TYPE_UINT64, ffi.FFI_TYPE_UINT64], [hwnd, hdc], FFI_S32)
329
+ }
330
+ }
331
+ gui.DefWindowProc(hwnd, msg, wParam, lParam)
332
+ return 0
333
+ }
334
+ if (msg === gui.WmMsg.VSCROLL || msg === gui.WmMsg.HSCROLL) {
335
+ const bar = msg === gui.WmMsg.VSCROLL ? gui.ScrollBar.VERT : gui.ScrollBar.HORZ
336
+ const info = gui.GetScrollInfo(hwnd, bar)
337
+ const maxPos = info.max - info.page + 1
338
+ const code = wParam & 0xFFFF
339
+ let newPos = bar === gui.ScrollBar.VERT ? scrollY : scrollX
340
+ if (code === gui.ScrollCmd.THUMBTRACK || code === gui.ScrollCmd.THUMBPOSITION)
341
+ newPos = Math.max(0, Math.min(maxPos, (wParam >> 16) & 0xFFFF))
342
+ else if (code === gui.ScrollCmd.LINEUP) newPos -= 20
343
+ else if (code === gui.ScrollCmd.LINEDOWN) newPos += 20
344
+ else if (code === gui.ScrollCmd.PAGEUP) newPos -= info.page
345
+ else if (code === gui.ScrollCmd.PAGEDOWN) newPos += info.page
346
+ else return 0
347
+ newPos = Math.max(0, Math.min(maxPos, newPos))
348
+ if (bar === gui.ScrollBar.VERT) doScroll(0, newPos - scrollY)
349
+ else doScroll(newPos - scrollX, 0)
350
+ return 0
351
+ }
352
+ if (msg === gui.WmMsg.MOUSEWHEEL) {
353
+ const raw = (wParam >>> 16) & 0xFFFF
354
+ const wheel = raw >= 0x8000 ? raw - 0x10000 : raw
355
+ const isHorz = (wParam & gui.MouseKeyFlag.MK_SHIFT) !== 0
356
+ const bar = isHorz ? gui.ScrollBar.HORZ : gui.ScrollBar.VERT
357
+ const info = gui.GetScrollInfo(hwnd, bar)
358
+ const maxPos = info.max - info.page + 1
359
+ let newPos = (isHorz ? scrollX : scrollY) - Math.round(wheel * 40 / 120)
360
+ newPos = Math.max(0, Math.min(maxPos, newPos))
361
+ if (isHorz) doScroll(newPos - scrollX, 0)
362
+ else doScroll(0, newPos - scrollY)
363
+ return 0
364
+ }
365
+ if (msg === gui.WmMsg.SIZE) { updateScrollRange(); return gui.DefWindowProc(hwnd, msg, wParam, lParam) }
366
+ return gui.DefWindowProc(hwnd, msg, wParam, lParam)
367
+ })
368
+
369
+ gui.ShowWindow(hwndMain)
370
+
371
+ const test = std.open('example.pdf', 'rb')
372
+ if (test) { test.close(); showPdf(mupdf, 'example.pdf', 0) }
373
+ }
374
+
375
+ main()