quickwin 1.0.1 → 1.1.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.
Files changed (79) 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.js +25 -3
  5. package/examples/exec_server.ts +35 -17
  6. package/examples/pdf_preview2.ts +375 -375
  7. package/examples/pdf_viewer.tsx +279 -279
  8. package/examples/test_gallery.tsx +285 -285
  9. package/examples/test_react_lazy.tsx +63 -63
  10. package/examples/test_react_listbox.tsx +71 -71
  11. package/examples/test_root_window.tsx +41 -41
  12. package/examples/test_tab.tsx +75 -75
  13. package/examples/tray_demo.tsx +81 -81
  14. package/lib/assert.ts +6 -6
  15. package/lib/cache-utils.ts +40 -40
  16. package/lib/dumpRects.ts +25 -25
  17. package/lib/fetch.ts +502 -502
  18. package/lib/http-common.ts +399 -399
  19. package/lib/http-server.ts +424 -424
  20. package/lib/polyfill.ts +171 -171
  21. package/lib/react-qw/components/index.ts +17 -17
  22. package/lib/react-qw/index.ts +90 -90
  23. package/lib/react-qw/jsx.d.ts +34 -34
  24. package/lib/react-qw/layout.ts +111 -111
  25. package/lib/react-qw/props.ts +36 -36
  26. package/lib/react-qw/reconciler.ts +373 -373
  27. package/lib/stream.ts +21 -21
  28. package/lib/text-codec.ts +171 -171
  29. package/lib/text-measure.ts +54 -54
  30. package/lib/url.ts +201 -201
  31. package/lib/vendor/web-streams/ponyfill.d.mts +829 -829
  32. package/lib/vendor/web-streams/ponyfill.mjs +8 -8
  33. package/lib/websocket.js +8 -0
  34. package/lib/websocket.ts +563 -555
  35. package/package.json +50 -44
  36. package/qwin-nowasm-x86.exe +0 -0
  37. package/qwin-nowasm.exe +0 -0
  38. package/qwin-x86.exe +0 -0
  39. package/qwin.exe +0 -0
  40. package/test/run.ts +96 -96
  41. package/test/test_basic.ts +9 -9
  42. package/test/test_basic_fetch.ts +33 -33
  43. package/test/test_brotli.ts +52 -52
  44. package/test/test_fetch_cache.ts +187 -187
  45. package/test/test_ffi.ts +174 -174
  46. package/test/test_frame_encoding.ts +132 -132
  47. package/test/test_helper.ts +80 -80
  48. package/test/test_http_import.ts +74 -74
  49. package/test/test_http_server.ts +136 -136
  50. package/test/test_import_dir.ts +5 -5
  51. package/test/test_ipv6.ts +129 -129
  52. package/test/test_meta_url.ts +4 -4
  53. package/test/test_mupdf_render.ts +74 -74
  54. package/test/test_mupdf_twice.ts +81 -81
  55. package/test/test_mupdf_wasm.ts +30 -30
  56. package/test/test_net_event.ts +55 -55
  57. package/test/test_net_fetch.ts +238 -238
  58. package/test/test_net_websocket.ts +164 -164
  59. package/test/test_polyfill.ts +159 -159
  60. package/test/test_url.ts +183 -183
  61. package/test/test_wasm_basic.ts +73 -73
  62. package/test/test_wasm_import_global.ts +45 -45
  63. package/test/test_wasm_sjlj.ts +125 -125
  64. package/test/test_wasm_types.ts +124 -124
  65. package/test/test_worker.ts +24 -24
  66. package/test/test_worker_fetch.ts +27 -27
  67. package/test/test_worker_net.ts +54 -54
  68. package/test/test_worker_wasm_concurrent.ts +45 -45
  69. package/test/worker_fetch.ts +13 -13
  70. package/test/worker_helper.ts +1 -1
  71. package/test/worker_http.ts +20 -20
  72. package/test/worker_import_lib.ts +13 -13
  73. package/test/worker_module.ts +17 -17
  74. package/test/worker_wasm_concurrent.ts +22 -22
  75. package/tsconfig.json +27 -27
  76. package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -571
  77. package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -2749
  78. package/vendor/mupdf-wasm/mupdf.d.ts +939 -939
  79. package/vendor/mupdf-wasm/mupdf.js +3317 -3317
@@ -1,279 +1,279 @@
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 { useState } from 'react'
8
- import { render, Button, Input, ScrollView } from '../lib/react-qw/index.js'
9
- import { PdfCanvas } from './PdfCanvas.js'
10
-
11
- type MuPdf = typeof import('../vendor/mupdf-wasm/mupdf.js')
12
-
13
- const _user32 = win.LoadLibrary('user32.dll')
14
- const _gdi32 = win.LoadLibrary('gdi32.dll')
15
- const _comdlg32 = win.LoadLibrary('comdlg32.dll')
16
- if (!_user32 || !_gdi32 || !_comdlg32) {
17
- gui.MessageBox('Failed to load system libraries')
18
- std.exit(0)
19
- }
20
-
21
- function loadProc(lib: win.HMODULE, name: string): number {
22
- const ptr = win.GetProcAddress(lib, name)
23
- if (!ptr) throw new Error('Failed to load ' + name)
24
- return ptr
25
- }
26
-
27
- const GetOpenFileNameW_ = loadProc(_comdlg32, 'GetOpenFileNameW')
28
- const FFI_PTR = ffi.FFI_TYPE_POINTER
29
- const FFI_U32 = ffi.FFI_TYPE_UINT32
30
-
31
- function strToWide(str: string): ArrayBuffer {
32
- const buf = new ArrayBuffer((str.length + 1) * 2)
33
- const dv = new DataView(buf)
34
- for (let i = 0; i < str.length; i++) dv.setUint16(i * 2, str.charCodeAt(i), true)
35
- return buf
36
- }
37
-
38
- function wideToStr(buf: ArrayBuffer): string {
39
- const dv = new DataView(buf)
40
- const chars: number[] = []
41
- for (let i = 0; i < buf.byteLength; i += 2) {
42
- const c = dv.getUint16(i, true)
43
- if (c === 0) break
44
- chars.push(c)
45
- }
46
- return String.fromCharCode(...chars)
47
- }
48
-
49
- function setPtr(dv: DataView, off: number, ptr: number): void {
50
- dv.setUint32(off, ptr & 0xFFFFFFFF, true)
51
- dv.setUint32(off + 4, Math.floor(ptr / 0x100000000), true)
52
- }
53
-
54
- function openPdfFileDialog(owner: number): string | null {
55
- const structBuf = new ArrayBuffer(152)
56
- const sv = new DataView(structBuf)
57
- const fileBuf = new ArrayBuffer(260 * 2)
58
- const filterWide = strToWide('PDF Files\0*.pdf\0All Files\0*.*\0\0')
59
-
60
- sv.setUint32(0, 152, true)
61
- sv.setUint32(8, owner & 0xFFFFFFFF, true)
62
- sv.setUint32(12, Math.floor(owner / 0x100000000), true)
63
-
64
- setPtr(sv, 24, ffi.bufferPtr(filterWide))
65
- setPtr(sv, 48, ffi.bufferPtr(fileBuf))
66
- sv.setUint32(56, 260, true)
67
-
68
- sv.setUint32(96, 0x1000 | 0x0800 | 0x0004, true)
69
-
70
- const ret = ffi.ffiCall(GetOpenFileNameW_, [FFI_PTR], [structBuf], FFI_U32)
71
- if (!ret) return null
72
-
73
- const path = wideToStr(fileBuf)
74
- return path.length > 0 ? path : null
75
- }
76
-
77
- async function loadMupdf(): Promise<MuPdf | null> {
78
- const wasmPath = './vendor/mupdf-wasm/mupdf-wasm.wasm'
79
- const fp = std.open(wasmPath, 'rb')
80
- if (!fp) { std.printf('Error: cannot open %s\n', wasmPath); return null }
81
- fp.seek(0, 2)
82
- const size = fp.tell()
83
- fp.seek(0, 0)
84
- const buf = new ArrayBuffer(size)
85
- fp.read(buf, 0, size)
86
- fp.close()
87
-
88
- ;(globalThis).$libmupdf_wasm_Module = {
89
- wasmBinary: buf,
90
- locateFile: (p: string) => p
91
- }
92
- try {
93
- return await import('../vendor/mupdf-wasm/mupdf.js')
94
- } catch (e) {
95
- std.printf('Error: mupdf load failed: %s\n', String(e))
96
- return null
97
- }
98
- }
99
-
100
- interface PixmapInfo {
101
- data: ArrayBuffer
102
- w: number
103
- h: number
104
- }
105
-
106
- let cachedPath = ''
107
- let cachedDoc: Document | null = null
108
- let cachedTotalPages = 0
109
-
110
- function renderPdfPage(mupdf: MuPdf, filePath: string, pageIndex: number): PixmapInfo & { totalPages: number } | null {
111
- if (filePath !== cachedPath) {
112
- if (cachedDoc) { try { cachedDoc.destroy() } catch {} }
113
- cachedDoc = null
114
- cachedPath = ''
115
- cachedTotalPages = 0
116
-
117
- const fp = std.open(filePath, 'rb')
118
- if (!fp) { std.printf('Error: cannot open %s\n', filePath); return null }
119
- let buf: ArrayBuffer | null = null
120
- try {
121
- fp.seek(0, 2)
122
- const size = fp.tell()
123
- fp.seek(0, 0)
124
- buf = new ArrayBuffer(size)
125
- fp.read(buf, 0, size)
126
- } finally {
127
- fp.close()
128
- }
129
- if (!buf) return null
130
- try {
131
- cachedDoc = mupdf.Document.openDocument(new Uint8Array(buf), 'application/pdf')
132
- cachedTotalPages = cachedDoc.countPages()
133
- cachedPath = filePath
134
- } catch (e) {
135
- std.printf('Error opening document: %s\n', String(e))
136
- return null
137
- }
138
- }
139
-
140
- if (!cachedDoc) return null
141
- if (pageIndex >= cachedTotalPages) return null
142
-
143
- let page: Page | null = null
144
- let pixmap: Pixmap | null = null
145
- try {
146
- page = cachedDoc.loadPage(pageIndex)
147
- const scale = 1.5
148
- pixmap = page.toPixmap(
149
- mupdf.Matrix.scale(scale, scale),
150
- mupdf.ColorSpace.DeviceRGB,
151
- false
152
- )
153
- if (!pixmap) return null
154
-
155
- const srcPixels = pixmap.getPixels()
156
- const srcStride = pixmap.getStride()
157
- const w = pixmap.getWidth()
158
- const h = pixmap.getHeight()
159
- const dibStride = Math.floor((w * 3 + 3) / 4) * 4
160
-
161
- const dibSize = h * dibStride
162
- const dibBuffer = new ArrayBuffer(dibSize)
163
- const dib = new Uint8Array(dibBuffer)
164
-
165
- for (let y = 0; y < h; y++) {
166
- const srcOff = y * srcStride
167
- const dstOff = y * dibStride
168
- for (let x = 0; x < w; x++) {
169
- const sx = srcOff + x * 3
170
- const dx = dstOff + x * 3
171
- dib[dx] = srcPixels[sx + 2]!
172
- dib[dx + 1] = srcPixels[sx + 1]!
173
- dib[dx + 2] = srcPixels[sx]!
174
- }
175
- }
176
-
177
- return { data: dibBuffer, w, h, totalPages: cachedTotalPages }
178
- } catch (e) {
179
- std.printf('Error rendering: %s\n', String(e))
180
- if (cachedDoc) { try { cachedDoc.destroy() } catch {} }
181
- cachedDoc = null; cachedPath = ''; cachedTotalPages = 0
182
- return null
183
- } finally {
184
- if (pixmap) { try { pixmap.destroy() } catch {} }
185
- if (page) { try { page.destroy() } catch {} }
186
- }
187
- }
188
-
189
- function App({ mupdf, mainHwnd: hwnd }: { mupdf: MuPdf; mainHwnd: number }) {
190
- const [filePath, setFilePath] = useState('')
191
- const [currentPage, setCurrentPage] = useState(0)
192
- const [pixmap, setPixmap] = useState<PixmapInfo | undefined>(undefined)
193
- const [totalPages, setTotalPages] = useState(0)
194
-
195
- function loadPage(path: string, page: number) {
196
- const result = renderPdfPage(mupdf, path, page)
197
- if (result) {
198
- setPixmap(result)
199
- setCurrentPage(page)
200
- setTotalPages(result.totalPages)
201
- } else {
202
- gui.MessageBox('Failed to render PDF page')
203
- }
204
- }
205
-
206
- function handleOpen() {
207
- const path = openPdfFileDialog(hwnd)
208
- if (path) {
209
- setFilePath(path)
210
- loadPage(path, 0)
211
- }
212
- }
213
-
214
- function handlePrev() {
215
- if (currentPage > 0) loadPage(filePath, currentPage - 1)
216
- }
217
-
218
- function handleNext() {
219
- if (currentPage < totalPages - 1) loadPage(filePath, currentPage + 1)
220
- }
221
-
222
- return (
223
- <w type="STATIC"
224
- ws={gui.WindowStyle.VISIBLE | gui.WindowStyle.CLIPCHILDREN}
225
- style={{ flexDirection: 'column', gap: 4, width: 1180, height: 720 }}
226
- >
227
- <w type="STATIC" ws={gui.WindowStyle.VISIBLE}
228
- style={{ flexDirection: 'row', gap: 6, alignItems: 'stretch', height: 28 }}
229
- >
230
- <w type="STATIC" style={{ width: 6 }} />
231
- <Button onClick={handleOpen} style={{ width: 80 }}>Open</Button>
232
- <Input value={filePath} placeholder="File path..." readonly style={{ flexGrow: 1 }} />
233
- <Button onClick={handlePrev} disabled={currentPage <= 0} style={{ width: 60 }}>Prev</Button>
234
- <w type="STATIC" ws={gui.WindowStyle.VISIBLE}
235
- text={totalPages > 0 ? `Page ${currentPage + 1} / ${totalPages}` : 'No file'}
236
- style={{ width: 120 }}
237
- />
238
- <Button onClick={handleNext} disabled={currentPage >= totalPages - 1} style={{ width: 60 }}>Next</Button>
239
- <w type="STATIC" style={{ width: 6 }} />
240
- </w>
241
-
242
- <ScrollView style={{ flexGrow: 1 }}>
243
- {pixmap ? (
244
- <PdfCanvas
245
- pixmap={pixmap}
246
- style={{ width: pixmap.w, height: pixmap.h }}
247
- />
248
- ) : (
249
- <w type="STATIC" ws={gui.WindowStyle.VISIBLE}
250
- text="Open a PDF file to preview"
251
- style={{ width: 300, height: 30 }}
252
- />
253
- )}
254
- </ScrollView>
255
- </w>
256
- )
257
- }
258
-
259
- async function main() {
260
- const mupdf = await loadMupdf()
261
- if (!mupdf) {
262
- gui.MessageBox('Loading mupdf WASM failed.\nEnsure vendor/mupdf-wasm/ is present.')
263
- return
264
- }
265
-
266
- gui.RegisterClass('PdfViewerWin')
267
- const hwnd = gui.CreateWindow(
268
- 'PdfViewerWin', 'PDF Viewer',
269
- gui.WindowStyle.OVERLAPPEDWINDOW,
270
- 100, 100, 1200, 800, null, null
271
- )
272
-
273
- if (hwnd) {
274
- render(<App mupdf={mupdf} mainHwnd={hwnd} />, hwnd)
275
- setTimeout(() => gui.ShowWindow(hwnd), 0)
276
- }
277
- }
278
-
279
- 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 { useState } from 'react'
8
+ import { render, Button, Input, ScrollView } from '../lib/react-qw/index.js'
9
+ import { PdfCanvas } from './PdfCanvas.js'
10
+
11
+ type MuPdf = typeof import('../vendor/mupdf-wasm/mupdf.js')
12
+
13
+ const _user32 = win.LoadLibrary('user32.dll')
14
+ const _gdi32 = win.LoadLibrary('gdi32.dll')
15
+ const _comdlg32 = win.LoadLibrary('comdlg32.dll')
16
+ if (!_user32 || !_gdi32 || !_comdlg32) {
17
+ gui.MessageBox('Failed to load system libraries')
18
+ std.exit(0)
19
+ }
20
+
21
+ function loadProc(lib: win.HMODULE, name: string): number {
22
+ const ptr = win.GetProcAddress(lib, name)
23
+ if (!ptr) throw new Error('Failed to load ' + name)
24
+ return ptr
25
+ }
26
+
27
+ const GetOpenFileNameW_ = loadProc(_comdlg32, 'GetOpenFileNameW')
28
+ const FFI_PTR = ffi.FFI_TYPE_POINTER
29
+ const FFI_U32 = ffi.FFI_TYPE_UINT32
30
+
31
+ function strToWide(str: string): ArrayBuffer {
32
+ const buf = new ArrayBuffer((str.length + 1) * 2)
33
+ const dv = new DataView(buf)
34
+ for (let i = 0; i < str.length; i++) dv.setUint16(i * 2, str.charCodeAt(i), true)
35
+ return buf
36
+ }
37
+
38
+ function wideToStr(buf: ArrayBuffer): string {
39
+ const dv = new DataView(buf)
40
+ const chars: number[] = []
41
+ for (let i = 0; i < buf.byteLength; i += 2) {
42
+ const c = dv.getUint16(i, true)
43
+ if (c === 0) break
44
+ chars.push(c)
45
+ }
46
+ return String.fromCharCode(...chars)
47
+ }
48
+
49
+ function setPtr(dv: DataView, off: number, ptr: number): void {
50
+ dv.setUint32(off, ptr & 0xFFFFFFFF, true)
51
+ dv.setUint32(off + 4, Math.floor(ptr / 0x100000000), true)
52
+ }
53
+
54
+ function openPdfFileDialog(owner: number): string | null {
55
+ const structBuf = new ArrayBuffer(152)
56
+ const sv = new DataView(structBuf)
57
+ const fileBuf = new ArrayBuffer(260 * 2)
58
+ const filterWide = strToWide('PDF Files\0*.pdf\0All Files\0*.*\0\0')
59
+
60
+ sv.setUint32(0, 152, true)
61
+ sv.setUint32(8, owner & 0xFFFFFFFF, true)
62
+ sv.setUint32(12, Math.floor(owner / 0x100000000), true)
63
+
64
+ setPtr(sv, 24, ffi.bufferPtr(filterWide))
65
+ setPtr(sv, 48, ffi.bufferPtr(fileBuf))
66
+ sv.setUint32(56, 260, true)
67
+
68
+ sv.setUint32(96, 0x1000 | 0x0800 | 0x0004, true)
69
+
70
+ const ret = ffi.ffiCall(GetOpenFileNameW_, [FFI_PTR], [structBuf], FFI_U32)
71
+ if (!ret) return null
72
+
73
+ const path = wideToStr(fileBuf)
74
+ return path.length > 0 ? path : null
75
+ }
76
+
77
+ async function loadMupdf(): Promise<MuPdf | null> {
78
+ const wasmPath = './vendor/mupdf-wasm/mupdf-wasm.wasm'
79
+ const fp = std.open(wasmPath, 'rb')
80
+ if (!fp) { std.printf('Error: cannot open %s\n', wasmPath); return null }
81
+ fp.seek(0, 2)
82
+ const size = fp.tell()
83
+ fp.seek(0, 0)
84
+ const buf = new ArrayBuffer(size)
85
+ fp.read(buf, 0, size)
86
+ fp.close()
87
+
88
+ ;(globalThis).$libmupdf_wasm_Module = {
89
+ wasmBinary: buf,
90
+ locateFile: (p: string) => p
91
+ }
92
+ try {
93
+ return await import('../vendor/mupdf-wasm/mupdf.js')
94
+ } catch (e) {
95
+ std.printf('Error: mupdf load failed: %s\n', String(e))
96
+ return null
97
+ }
98
+ }
99
+
100
+ interface PixmapInfo {
101
+ data: ArrayBuffer
102
+ w: number
103
+ h: number
104
+ }
105
+
106
+ let cachedPath = ''
107
+ let cachedDoc: Document | null = null
108
+ let cachedTotalPages = 0
109
+
110
+ function renderPdfPage(mupdf: MuPdf, filePath: string, pageIndex: number): PixmapInfo & { totalPages: number } | null {
111
+ if (filePath !== cachedPath) {
112
+ if (cachedDoc) { try { cachedDoc.destroy() } catch {} }
113
+ cachedDoc = null
114
+ cachedPath = ''
115
+ cachedTotalPages = 0
116
+
117
+ const fp = std.open(filePath, 'rb')
118
+ if (!fp) { std.printf('Error: cannot open %s\n', filePath); return null }
119
+ let buf: ArrayBuffer | null = null
120
+ try {
121
+ fp.seek(0, 2)
122
+ const size = fp.tell()
123
+ fp.seek(0, 0)
124
+ buf = new ArrayBuffer(size)
125
+ fp.read(buf, 0, size)
126
+ } finally {
127
+ fp.close()
128
+ }
129
+ if (!buf) return null
130
+ try {
131
+ cachedDoc = mupdf.Document.openDocument(new Uint8Array(buf), 'application/pdf')
132
+ cachedTotalPages = cachedDoc.countPages()
133
+ cachedPath = filePath
134
+ } catch (e) {
135
+ std.printf('Error opening document: %s\n', String(e))
136
+ return null
137
+ }
138
+ }
139
+
140
+ if (!cachedDoc) return null
141
+ if (pageIndex >= cachedTotalPages) return null
142
+
143
+ let page: Page | null = null
144
+ let pixmap: Pixmap | null = null
145
+ try {
146
+ page = cachedDoc.loadPage(pageIndex)
147
+ const scale = 1.5
148
+ pixmap = page.toPixmap(
149
+ mupdf.Matrix.scale(scale, scale),
150
+ mupdf.ColorSpace.DeviceRGB,
151
+ false
152
+ )
153
+ if (!pixmap) return null
154
+
155
+ const srcPixels = pixmap.getPixels()
156
+ const srcStride = pixmap.getStride()
157
+ const w = pixmap.getWidth()
158
+ const h = pixmap.getHeight()
159
+ const dibStride = Math.floor((w * 3 + 3) / 4) * 4
160
+
161
+ const dibSize = h * dibStride
162
+ const dibBuffer = new ArrayBuffer(dibSize)
163
+ const dib = new Uint8Array(dibBuffer)
164
+
165
+ for (let y = 0; y < h; y++) {
166
+ const srcOff = y * srcStride
167
+ const dstOff = y * dibStride
168
+ for (let x = 0; x < w; x++) {
169
+ const sx = srcOff + x * 3
170
+ const dx = dstOff + x * 3
171
+ dib[dx] = srcPixels[sx + 2]!
172
+ dib[dx + 1] = srcPixels[sx + 1]!
173
+ dib[dx + 2] = srcPixels[sx]!
174
+ }
175
+ }
176
+
177
+ return { data: dibBuffer, w, h, totalPages: cachedTotalPages }
178
+ } catch (e) {
179
+ std.printf('Error rendering: %s\n', String(e))
180
+ if (cachedDoc) { try { cachedDoc.destroy() } catch {} }
181
+ cachedDoc = null; cachedPath = ''; cachedTotalPages = 0
182
+ return null
183
+ } finally {
184
+ if (pixmap) { try { pixmap.destroy() } catch {} }
185
+ if (page) { try { page.destroy() } catch {} }
186
+ }
187
+ }
188
+
189
+ function App({ mupdf, mainHwnd: hwnd }: { mupdf: MuPdf; mainHwnd: number }) {
190
+ const [filePath, setFilePath] = useState('')
191
+ const [currentPage, setCurrentPage] = useState(0)
192
+ const [pixmap, setPixmap] = useState<PixmapInfo | undefined>(undefined)
193
+ const [totalPages, setTotalPages] = useState(0)
194
+
195
+ function loadPage(path: string, page: number) {
196
+ const result = renderPdfPage(mupdf, path, page)
197
+ if (result) {
198
+ setPixmap(result)
199
+ setCurrentPage(page)
200
+ setTotalPages(result.totalPages)
201
+ } else {
202
+ gui.MessageBox('Failed to render PDF page')
203
+ }
204
+ }
205
+
206
+ function handleOpen() {
207
+ const path = openPdfFileDialog(hwnd)
208
+ if (path) {
209
+ setFilePath(path)
210
+ loadPage(path, 0)
211
+ }
212
+ }
213
+
214
+ function handlePrev() {
215
+ if (currentPage > 0) loadPage(filePath, currentPage - 1)
216
+ }
217
+
218
+ function handleNext() {
219
+ if (currentPage < totalPages - 1) loadPage(filePath, currentPage + 1)
220
+ }
221
+
222
+ return (
223
+ <w type="STATIC"
224
+ ws={gui.WindowStyle.VISIBLE | gui.WindowStyle.CLIPCHILDREN}
225
+ style={{ flexDirection: 'column', gap: 4, width: 1180, height: 720 }}
226
+ >
227
+ <w type="STATIC" ws={gui.WindowStyle.VISIBLE}
228
+ style={{ flexDirection: 'row', gap: 6, alignItems: 'stretch', height: 28 }}
229
+ >
230
+ <w type="STATIC" style={{ width: 6 }} />
231
+ <Button onClick={handleOpen} style={{ width: 80 }}>Open</Button>
232
+ <Input value={filePath} placeholder="File path..." readonly style={{ flexGrow: 1 }} />
233
+ <Button onClick={handlePrev} disabled={currentPage <= 0} style={{ width: 60 }}>Prev</Button>
234
+ <w type="STATIC" ws={gui.WindowStyle.VISIBLE}
235
+ text={totalPages > 0 ? `Page ${currentPage + 1} / ${totalPages}` : 'No file'}
236
+ style={{ width: 120 }}
237
+ />
238
+ <Button onClick={handleNext} disabled={currentPage >= totalPages - 1} style={{ width: 60 }}>Next</Button>
239
+ <w type="STATIC" style={{ width: 6 }} />
240
+ </w>
241
+
242
+ <ScrollView style={{ flexGrow: 1 }}>
243
+ {pixmap ? (
244
+ <PdfCanvas
245
+ pixmap={pixmap}
246
+ style={{ width: pixmap.w, height: pixmap.h }}
247
+ />
248
+ ) : (
249
+ <w type="STATIC" ws={gui.WindowStyle.VISIBLE}
250
+ text="Open a PDF file to preview"
251
+ style={{ width: 300, height: 30 }}
252
+ />
253
+ )}
254
+ </ScrollView>
255
+ </w>
256
+ )
257
+ }
258
+
259
+ async function main() {
260
+ const mupdf = await loadMupdf()
261
+ if (!mupdf) {
262
+ gui.MessageBox('Loading mupdf WASM failed.\nEnsure vendor/mupdf-wasm/ is present.')
263
+ return
264
+ }
265
+
266
+ gui.RegisterClass('PdfViewerWin')
267
+ const hwnd = gui.CreateWindow(
268
+ 'PdfViewerWin', 'PDF Viewer',
269
+ gui.WindowStyle.OVERLAPPEDWINDOW,
270
+ 100, 100, 1200, 800, null, null
271
+ )
272
+
273
+ if (hwnd) {
274
+ render(<App mupdf={mupdf} mainHwnd={hwnd} />, hwnd)
275
+ setTimeout(() => gui.ShowWindow(hwnd), 0)
276
+ }
277
+ }
278
+
279
+ main()