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,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()