quickwin 2.3.1 → 3.0.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/README.en.md +1 -2
- package/README.md +1 -2
- package/constants.ts +15 -0
- package/enum_map.d.ts +3 -0
- package/examples/PdfCanvas.js +1 -1
- package/examples/PdfCanvas.tsx +2 -2
- package/examples/execd/main.js +334 -188
- package/examples/pdf_preview2.js +76 -67
- package/examples/pdf_preview2.ts +84 -64
- package/examples/pdf_viewer.js +4932 -1029
- package/examples/pdf_viewer.tsx +11 -22
- package/examples/test_gallery.js +308 -143
- package/examples/test_gallery.tsx +12 -7
- package/examples/test_react_lazy.js +295 -133
- package/examples/test_react_lazy.tsx +4 -3
- package/examples/test_react_listbox.js +293 -132
- package/examples/test_react_listbox.tsx +3 -2
- package/examples/test_root_window.js +283 -126
- package/examples/test_tab.js +295 -133
- package/examples/test_tab.tsx +4 -3
- package/examples/tray_demo.js +37 -30
- package/examples/tray_demo.tsx +39 -32
- package/lib/bitmap.js +40 -0
- package/lib/bitmap.ts +39 -0
- package/lib/certs.js +31 -3
- package/lib/certs.ts +32 -3
- package/lib/dpi.js +30 -0
- package/lib/dpi.ts +31 -0
- package/lib/dumpRects.js +14 -7
- package/lib/dumpRects.ts +14 -6
- package/lib/ffi/bind.js +5 -4
- package/lib/ffi/bind.ts +9 -8
- package/lib/react-qw/components/DateTimePicker.js +6 -7
- package/lib/react-qw/components/Input.js +5 -1
- package/lib/react-qw/components/ListView.js +8 -6
- package/lib/react-qw/components/ScrollView.js +28 -22
- package/lib/react-qw/index.js +7 -5
- package/lib/react-qw/index.ts +5 -5
- package/lib/react-qw/props.js +3 -3
- package/lib/react-qw/props.ts +3 -3
- package/lib/react-qw/reconciler.js +12 -17
- package/lib/react-qw/reconciler.ts +12 -16
- package/lib/text-measure.js +9 -6
- package/lib/text-measure.ts +9 -6
- package/lib/windows/comctl32.js +3 -2
- package/lib/windows/comctl32.ts +3 -2
- package/lib/windows/gdi32.js +28 -6
- package/lib/windows/gdi32.ts +30 -6
- package/lib/windows/kernel32.js +9 -0
- package/lib/windows/kernel32.ts +9 -0
- package/lib/windows/shell32.js +11 -2
- package/lib/windows/shell32.ts +13 -2
- package/lib/windows/structs.js +51 -0
- package/lib/windows/structs.ts +55 -0
- package/lib/windows/user32.js +6 -5
- package/lib/windows/user32.ts +6 -5
- package/main.js +13 -5
- package/package.json +2 -2
- package/quickwin.d.ts +13 -115
- package/qwin-nowasm-x86.exe +0 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin-x86.exe +0 -0
- package/qwin.exe +0 -0
- package/test/bench_ffi_callraw.js +3 -4
- package/test/bench_ffi_callraw.ts +3 -4
- package/test/debug/mupdf_debug.js +4 -22
- package/test/debug/wasm_export_debug.js +2 -12
- package/test/debug/wasm_names.js +3 -13
- package/test/example.pdf +0 -0
- package/test/mupdf-cdn.js +40 -0
- package/test/mupdf-cdn.ts +43 -0
- package/test/test_ffi.js +8 -9
- package/test/test_ffi.ts +8 -9
- package/test/test_ffi_abi.js +6 -6
- package/test/test_ffi_abi.ts +8 -8
- package/test/test_ffi_windows.js +82 -11
- package/test/test_ffi_windows.ts +85 -11
- package/test/test_gdi_draw.js +3 -3
- package/test/test_gdi_draw.ts +4 -4
- package/test/test_mupdf_render.js +4 -22
- package/test/test_mupdf_render.ts +4 -23
- package/test/test_mupdf_twice.js +4 -19
- package/test/test_mupdf_twice.ts +4 -20
- package/test/test_mupdf_wasm.js +10 -4
- package/test/test_mupdf_wasm.ts +6 -4
- package/test/test_react_complex.js +316 -140
- package/test/test_react_counter.js +295 -133
- package/test/test_react_flex.js +297 -138
- package/test/test_react_hidden.js +296 -134
- package/test/test_react_listview_link.js +288 -131
- package/test/test_react_notify.js +290 -133
- package/test/test_react_render.js +295 -133
- package/vendor/mupdf-wasm/mupdf-wasm.d.ts +0 -571
- package/vendor/mupdf-wasm/mupdf-wasm.js +0 -2749
- package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
- package/vendor/mupdf-wasm/mupdf.d.ts +0 -939
- package/vendor/mupdf-wasm/mupdf.js +0 -3317
package/examples/pdf_preview2.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { PtrArrayBuffer } from '../lib/ffi/ctype.js';
|
|
1
|
+
import { PtrArrayBuffer, NULL } from '../lib/ffi/ctype.js';
|
|
2
2
|
import '../lib/polyfill.js';
|
|
3
3
|
import * as std from 'std';
|
|
4
4
|
import * as gui from 'gui';
|
|
5
|
+
import { MUPDF_JS_URL, preloadMupdfWasm } from '../test/mupdf-cdn.js';
|
|
5
6
|
import { assertNonNullable } from '../lib/assert.js';
|
|
6
7
|
import { WCHAR } from '../lib/ffi/bind.js';
|
|
7
|
-
import { BITMAPINFOHEADER, OPENFILENAMEW } from '../lib/windows/structs.js';
|
|
8
|
+
import { BITMAPINFOHEADER, OPENFILENAMEW, RECT, SCROLLINFO } from '../lib/windows/structs.js';
|
|
8
9
|
import { GetOpenFileName } from '../lib/windows/comdlg32.js';
|
|
9
|
-
import { GetDC, ReleaseDC } from '../lib/windows/user32.js';
|
|
10
|
+
import { GetClientRect, GetDC, GetScrollInfo, GetWindowLongPtr, GetWindowText, GetWindowTextLength, InvalidateRect, MessageBox, PostQuitMessage, ReleaseDC, SetScrollInfo, SetWindowPos, SetWindowLongPtr, SetWindowText, ShowScrollBar, ShowWindow } from '../lib/windows/user32.js';
|
|
10
11
|
import { PatBlt, SetDIBitsToDevice } from '../lib/windows/gdi32.js';
|
|
11
12
|
const WHITENESS = 0x00FF0062;
|
|
12
13
|
function makeBitmapInfo(w, h) {
|
|
@@ -33,21 +34,9 @@ function clearCachedDoc() {
|
|
|
33
34
|
cachedPath = '';
|
|
34
35
|
}
|
|
35
36
|
async function loadMupdf() {
|
|
36
|
-
const wasmPath = './vendor/mupdf-wasm/mupdf-wasm.wasm';
|
|
37
|
-
const fp = std.open(wasmPath, 'rb');
|
|
38
|
-
if (!fp) {
|
|
39
|
-
std.printf('Error: cannot open %s\n', wasmPath);
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
fp.seek(0, 2);
|
|
43
|
-
const size = fp.tell();
|
|
44
|
-
fp.seek(0, 0);
|
|
45
|
-
const buf = new ArrayBuffer(size);
|
|
46
|
-
fp.read(buf, 0, size);
|
|
47
|
-
fp.close();
|
|
48
|
-
(globalThis).$libmupdf_wasm_Module = { wasmBinary: buf, locateFile: (p) => p };
|
|
49
37
|
try {
|
|
50
|
-
|
|
38
|
+
await preloadMupdfWasm();
|
|
39
|
+
return await import(MUPDF_JS_URL);
|
|
51
40
|
}
|
|
52
41
|
catch (e) {
|
|
53
42
|
std.printf('Error: mupdf load failed: %s\n', String(e));
|
|
@@ -152,79 +141,87 @@ function updateScrollRange() {
|
|
|
152
141
|
assertNonNullable(hwndContent);
|
|
153
142
|
if (!currentPixmap)
|
|
154
143
|
return;
|
|
155
|
-
const
|
|
156
|
-
if (!
|
|
144
|
+
const crBuf = RECT.alloc();
|
|
145
|
+
if (!GetClientRect(hwndCanvas, crBuf.ptr))
|
|
157
146
|
return;
|
|
158
|
-
const
|
|
147
|
+
const { left, top, right, bottom } = RECT.decode(crBuf);
|
|
148
|
+
const cw = right - left, ch = bottom - top;
|
|
159
149
|
const maxX = Math.max(0, currentPixmap.w - cw);
|
|
160
150
|
const maxY = Math.max(0, currentPixmap.h - ch);
|
|
161
151
|
scrollX = Math.min(scrollX, maxX);
|
|
162
152
|
scrollY = Math.min(scrollY, maxY);
|
|
163
153
|
if (currentPixmap.w > cw) {
|
|
164
|
-
const style =
|
|
154
|
+
const style = GetWindowLongPtr(hwndCanvas, -16 /* Gwlp.STYLE */);
|
|
165
155
|
if (!(style & 1048576 /* WindowStyle.HSCROLL */)) {
|
|
166
|
-
|
|
167
|
-
|
|
156
|
+
SetWindowLongPtr(hwndCanvas, -16 /* Gwlp.STYLE */, style | 1048576 /* WindowStyle.HSCROLL */);
|
|
157
|
+
SetWindowPos(hwndCanvas, 0 /* SetWindowPosHwnd.TOP */, 0, 0, 0, 0, 2 /* SetWindowPosFlag.SWP_NOMOVE */ | 1 /* SetWindowPosFlag.SWP_NOSIZE */ | 4 /* SetWindowPosFlag.SWP_NOZORDER */ | 32 /* SetWindowPosFlag.SWP_FRAMECHANGED */);
|
|
168
158
|
}
|
|
169
|
-
|
|
170
|
-
|
|
159
|
+
SetScrollInfo(hwndCanvas, 0 /* ScrollBar.HORZ */, {
|
|
160
|
+
cbSize: SCROLLINFO.size, fMask: 1 /* ScrollInfoFlag.RANGE */ | 2 /* ScrollInfoFlag.PAGE */ | 4 /* ScrollInfoFlag.POS */,
|
|
161
|
+
nMin: 0, nMax: currentPixmap.w - 1, nPage: cw, nPos: scrollX,
|
|
162
|
+
}, 1);
|
|
163
|
+
ShowScrollBar(hwndCanvas, 0 /* ScrollBar.HORZ */, 1);
|
|
171
164
|
}
|
|
172
165
|
else {
|
|
173
166
|
scrollX = 0;
|
|
174
|
-
const style =
|
|
167
|
+
const style = GetWindowLongPtr(hwndCanvas, -16 /* Gwlp.STYLE */);
|
|
175
168
|
if (style & 1048576 /* WindowStyle.HSCROLL */) {
|
|
176
|
-
|
|
177
|
-
|
|
169
|
+
SetWindowLongPtr(hwndCanvas, -16 /* Gwlp.STYLE */, style & ~1048576 /* WindowStyle.HSCROLL */);
|
|
170
|
+
SetWindowPos(hwndCanvas, 0 /* SetWindowPosHwnd.TOP */, 0, 0, 0, 0, 2 /* SetWindowPosFlag.SWP_NOMOVE */ | 1 /* SetWindowPosFlag.SWP_NOSIZE */ | 4 /* SetWindowPosFlag.SWP_NOZORDER */ | 32 /* SetWindowPosFlag.SWP_FRAMECHANGED */);
|
|
178
171
|
}
|
|
179
|
-
|
|
172
|
+
ShowScrollBar(hwndCanvas, 0 /* ScrollBar.HORZ */, 0);
|
|
180
173
|
}
|
|
181
174
|
if (currentPixmap.h > ch) {
|
|
182
|
-
|
|
183
|
-
|
|
175
|
+
SetScrollInfo(hwndCanvas, 1 /* ScrollBar.VERT */, {
|
|
176
|
+
cbSize: SCROLLINFO.size, fMask: 1 /* ScrollInfoFlag.RANGE */ | 2 /* ScrollInfoFlag.PAGE */ | 4 /* ScrollInfoFlag.POS */,
|
|
177
|
+
nMin: 0, nMax: currentPixmap.h - 1, nPage: ch, nPos: scrollY,
|
|
178
|
+
}, 1);
|
|
179
|
+
ShowScrollBar(hwndCanvas, 1 /* ScrollBar.VERT */, 1);
|
|
184
180
|
}
|
|
185
181
|
else {
|
|
186
182
|
scrollY = 0;
|
|
187
|
-
|
|
183
|
+
ShowScrollBar(hwndCanvas, 1 /* ScrollBar.VERT */, 0);
|
|
188
184
|
}
|
|
189
|
-
|
|
185
|
+
SetWindowPos(hwndContent, 0 /* SetWindowPosHwnd.TOP */, -scrollX, -scrollY, 0, 0, 1 /* SetWindowPosFlag.SWP_NOSIZE */ | 4 /* SetWindowPosFlag.SWP_NOZORDER */);
|
|
190
186
|
}
|
|
191
187
|
function showPdf(mupdf, path, pageIdx) {
|
|
192
188
|
assertNonNullable(hwndMain);
|
|
193
189
|
assertNonNullable(hwndContent);
|
|
194
190
|
const pix = renderPdfPage(mupdf, path, pageIdx);
|
|
195
191
|
if (!pix) {
|
|
196
|
-
|
|
192
|
+
MessageBox(NULL, '渲染 PDF 失败', 'JS Alert', 0);
|
|
197
193
|
return;
|
|
198
194
|
}
|
|
199
195
|
currentPixmap = pix;
|
|
200
196
|
currentPage = pageIdx;
|
|
201
197
|
totalPages = pix.totalPages;
|
|
202
|
-
|
|
198
|
+
SetWindowText(hwndMain, 'PDF 预览 - 第 ' + (pageIdx + 1) + '/' + totalPages + ' 页');
|
|
203
199
|
scrollX = 0;
|
|
204
200
|
scrollY = 0;
|
|
205
|
-
|
|
201
|
+
SetWindowPos(hwndContent, 0 /* SetWindowPosHwnd.TOP */, 0, 0, pix.w, pix.h, 4 /* SetWindowPosFlag.SWP_NOZORDER */ | 2 /* SetWindowPosFlag.SWP_NOMOVE */);
|
|
206
202
|
updateScrollRange();
|
|
207
|
-
|
|
203
|
+
InvalidateRect(hwndContent, NULL, 1);
|
|
208
204
|
}
|
|
209
205
|
function doScroll(dx, dy) {
|
|
210
206
|
assertNonNullable(hwndCanvas);
|
|
211
207
|
assertNonNullable(hwndContent);
|
|
212
|
-
const
|
|
213
|
-
if (!
|
|
208
|
+
const crBuf = RECT.alloc();
|
|
209
|
+
if (!GetClientRect(hwndCanvas, crBuf.ptr) || !currentPixmap)
|
|
214
210
|
return;
|
|
215
|
-
const
|
|
211
|
+
const { left, top, right, bottom } = RECT.decode(crBuf);
|
|
212
|
+
const cw = right - left, ch = bottom - top;
|
|
216
213
|
const maxX = Math.max(0, currentPixmap.w - cw);
|
|
217
214
|
const maxY = Math.max(0, currentPixmap.h - ch);
|
|
218
215
|
scrollX = Math.max(0, Math.min(maxX, scrollX + dx));
|
|
219
216
|
scrollY = Math.max(0, Math.min(maxY, scrollY + dy));
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
217
|
+
SetWindowPos(hwndContent, 0 /* SetWindowPosHwnd.TOP */, -scrollX, -scrollY, 0, 0, 1 /* SetWindowPosFlag.SWP_NOSIZE */ | 4 /* SetWindowPosFlag.SWP_NOZORDER */);
|
|
218
|
+
SetScrollInfo(hwndCanvas, 0 /* ScrollBar.HORZ */, { cbSize: SCROLLINFO.size, fMask: 4 /* ScrollInfoFlag.POS */, nPos: scrollX }, 1);
|
|
219
|
+
SetScrollInfo(hwndCanvas, 1 /* ScrollBar.VERT */, { cbSize: SCROLLINFO.size, fMask: 4 /* ScrollInfoFlag.POS */, nPos: scrollY }, 1);
|
|
223
220
|
}
|
|
224
221
|
async function main() {
|
|
225
222
|
const mupdf = await loadMupdf();
|
|
226
223
|
if (!mupdf) {
|
|
227
|
-
|
|
224
|
+
MessageBox(NULL, '加载 mupdf WASM 失败', 'JS Alert', 0);
|
|
228
225
|
return;
|
|
229
226
|
}
|
|
230
227
|
const WS_CHILD = 1073741824 /* WindowStyle.CHILD */;
|
|
@@ -241,15 +238,16 @@ async function main() {
|
|
|
241
238
|
assertNonNullable(hwndBtnPrev);
|
|
242
239
|
assertNonNullable(hwndBtnNext);
|
|
243
240
|
if (msg === 2 /* WmMsg.DESTROY */) {
|
|
244
|
-
|
|
241
|
+
PostQuitMessage(0);
|
|
245
242
|
return 0;
|
|
246
243
|
}
|
|
247
244
|
if (msg === 5 /* WmMsg.SIZE */) {
|
|
248
|
-
const
|
|
249
|
-
if (
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
245
|
+
const crBuf = RECT.alloc();
|
|
246
|
+
if (GetClientRect(hwnd, crBuf.ptr)) {
|
|
247
|
+
const { left, top, right, bottom } = RECT.decode(crBuf);
|
|
248
|
+
const cw = right - left, ch = bottom - top;
|
|
249
|
+
SetWindowPos(hwndCanvas, 0 /* SetWindowPosHwnd.TOP */, 0, 50, cw, Math.max(1, ch - 50), 4 /* SetWindowPosFlag.SWP_NOZORDER */);
|
|
250
|
+
SetWindowPos(hwndEdit, 0 /* SetWindowPosHwnd.TOP */, 0, 0, Math.max(100, cw - 320), 0, 2 /* SetWindowPosFlag.SWP_NOMOVE */ | 4 /* SetWindowPosFlag.SWP_NOZORDER */);
|
|
253
251
|
}
|
|
254
252
|
return gui.DefWindowProc(hwnd, msg, wParam, lParam);
|
|
255
253
|
}
|
|
@@ -258,17 +256,23 @@ async function main() {
|
|
|
258
256
|
if (hCtrl === hwndBtnOpen) {
|
|
259
257
|
const path = openPdfFileDialog();
|
|
260
258
|
if (path) {
|
|
261
|
-
|
|
259
|
+
SetWindowText(hwndEdit, path);
|
|
262
260
|
showPdf(mupdf, path, 0);
|
|
263
261
|
}
|
|
264
262
|
}
|
|
265
263
|
else if (hCtrl === hwndBtnPrev) {
|
|
266
|
-
const
|
|
264
|
+
const cap = GetWindowTextLength(hwndEdit) + 1;
|
|
265
|
+
const buf = WCHAR.alloc(cap);
|
|
266
|
+
GetWindowText(hwndEdit, buf, cap);
|
|
267
|
+
const path = WCHAR.decode(buf);
|
|
267
268
|
if (path && currentPage > 0)
|
|
268
269
|
showPdf(mupdf, path, currentPage - 1);
|
|
269
270
|
}
|
|
270
271
|
else if (hCtrl === hwndBtnNext) {
|
|
271
|
-
const
|
|
272
|
+
const cap = GetWindowTextLength(hwndEdit) + 1;
|
|
273
|
+
const buf = WCHAR.alloc(cap);
|
|
274
|
+
GetWindowText(hwndEdit, buf, cap);
|
|
275
|
+
const path = WCHAR.decode(buf);
|
|
272
276
|
if (path && currentPage < totalPages - 1)
|
|
273
277
|
showPdf(mupdf, path, currentPage + 1);
|
|
274
278
|
}
|
|
@@ -278,7 +282,7 @@ async function main() {
|
|
|
278
282
|
});
|
|
279
283
|
hwndMain = gui.CreateWindow('PdfViewer2', 'PDF 预览 2', 13565952 /* WindowStyle.OVERLAPPEDWINDOW */ | 33554432 /* WindowStyle.CLIPCHILDREN */, 100, 100, 1100, 800, null, null);
|
|
280
284
|
if (!hwndMain) {
|
|
281
|
-
|
|
285
|
+
MessageBox(NULL, '创建主窗口失败', 'JS Alert', 0);
|
|
282
286
|
return;
|
|
283
287
|
}
|
|
284
288
|
// 控件直接挂在主窗口上
|
|
@@ -309,7 +313,7 @@ async function main() {
|
|
|
309
313
|
const hdc = GetDC(hwnd);
|
|
310
314
|
if (hdc) {
|
|
311
315
|
const bmi = makeBitmapInfo(pm.w, pm.h);
|
|
312
|
-
SetDIBitsToDevice(hdc, 0, 0, pm.w, pm.h, 0, 0, 0, pm.h, pm.data, bmi, 0);
|
|
316
|
+
SetDIBitsToDevice(hdc, 0, 0, pm.w, pm.h, 0, 0, 0, pm.h, pm.data, bmi, 0 /* ColorUse.RGB_COLORS */);
|
|
313
317
|
ReleaseDC(hwnd, hdc);
|
|
314
318
|
}
|
|
315
319
|
return 0;
|
|
@@ -321,9 +325,10 @@ async function main() {
|
|
|
321
325
|
if (msg === 20 /* WmMsg.ERASEBKGND */)
|
|
322
326
|
return 1;
|
|
323
327
|
if (msg === 15 /* WmMsg.PAINT */) {
|
|
324
|
-
const
|
|
325
|
-
if (
|
|
326
|
-
const
|
|
328
|
+
const crBuf = RECT.alloc();
|
|
329
|
+
if (GetClientRect(hwnd, crBuf.ptr)) {
|
|
330
|
+
const { left, top, right, bottom } = RECT.decode(crBuf);
|
|
331
|
+
const cw = right - left, ch = bottom - top;
|
|
327
332
|
const hdc = GetDC(hwnd);
|
|
328
333
|
if (hdc) {
|
|
329
334
|
PatBlt(hdc, 0, 0, cw, ch, WHITENESS);
|
|
@@ -335,8 +340,10 @@ async function main() {
|
|
|
335
340
|
}
|
|
336
341
|
if (msg === 277 /* WmMsg.VSCROLL */ || msg === 276 /* WmMsg.HSCROLL */) {
|
|
337
342
|
const bar = msg === 277 /* WmMsg.VSCROLL */ ? 1 /* ScrollBar.VERT */ : 0 /* ScrollBar.HORZ */;
|
|
338
|
-
const
|
|
339
|
-
|
|
343
|
+
const si = SCROLLINFO.encode({ cbSize: SCROLLINFO.size, fMask: 23 /* ScrollInfoFlag.ALL */ });
|
|
344
|
+
GetScrollInfo(hwnd, bar, si.ptr);
|
|
345
|
+
const info = SCROLLINFO.decode(si);
|
|
346
|
+
const maxPos = info.nMax - info.nPage + 1;
|
|
340
347
|
const code = wParam & 0xFFFF;
|
|
341
348
|
let newPos = bar === 1 /* ScrollBar.VERT */ ? scrollY : scrollX;
|
|
342
349
|
if (code === 5 /* ScrollCmd.THUMBTRACK */ || code === 4 /* ScrollCmd.THUMBPOSITION */)
|
|
@@ -346,9 +353,9 @@ async function main() {
|
|
|
346
353
|
else if (code === 1 /* ScrollCmd.LINEDOWN */)
|
|
347
354
|
newPos += 20;
|
|
348
355
|
else if (code === 2 /* ScrollCmd.PAGEUP */)
|
|
349
|
-
newPos -= info.
|
|
356
|
+
newPos -= info.nPage;
|
|
350
357
|
else if (code === 3 /* ScrollCmd.PAGEDOWN */)
|
|
351
|
-
newPos += info.
|
|
358
|
+
newPos += info.nPage;
|
|
352
359
|
else
|
|
353
360
|
return 0;
|
|
354
361
|
newPos = Math.max(0, Math.min(maxPos, newPos));
|
|
@@ -363,8 +370,10 @@ async function main() {
|
|
|
363
370
|
const wheel = raw >= 0x8000 ? raw - 0x10000 : raw;
|
|
364
371
|
const isHorz = (wParam & 4 /* MouseKeyFlag.MK_SHIFT */) !== 0;
|
|
365
372
|
const bar = isHorz ? 0 /* ScrollBar.HORZ */ : 1 /* ScrollBar.VERT */;
|
|
366
|
-
const
|
|
367
|
-
|
|
373
|
+
const si = SCROLLINFO.encode({ cbSize: SCROLLINFO.size, fMask: 23 /* ScrollInfoFlag.ALL */ });
|
|
374
|
+
GetScrollInfo(hwnd, bar, si.ptr);
|
|
375
|
+
const info = SCROLLINFO.decode(si);
|
|
376
|
+
const maxPos = info.nMax - info.nPage + 1;
|
|
368
377
|
let newPos = (isHorz ? scrollX : scrollY) - Math.round(wheel * 40 / 120);
|
|
369
378
|
newPos = Math.max(0, Math.min(maxPos, newPos));
|
|
370
379
|
if (isHorz)
|
|
@@ -379,11 +388,11 @@ async function main() {
|
|
|
379
388
|
}
|
|
380
389
|
return gui.DefWindowProc(hwnd, msg, wParam, lParam);
|
|
381
390
|
});
|
|
382
|
-
|
|
383
|
-
const test = std.open('example.pdf', 'rb');
|
|
391
|
+
ShowWindow(hwndMain, 1 /* ShowWindowCmd.SHOWNORMAL */);
|
|
392
|
+
const test = std.open('test/example.pdf', 'rb');
|
|
384
393
|
if (test) {
|
|
385
394
|
test.close();
|
|
386
|
-
showPdf(mupdf, 'example.pdf', 0);
|
|
395
|
+
showPdf(mupdf, 'test/example.pdf', 0);
|
|
387
396
|
}
|
|
388
397
|
}
|
|
389
398
|
main();
|
package/examples/pdf_preview2.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { PtrArrayBuffer } from '../lib/ffi/ctype.js'
|
|
1
|
+
import { PtrArrayBuffer, NULL } from '../lib/ffi/ctype.js'
|
|
2
2
|
import '../lib/polyfill.js'
|
|
3
3
|
import * as std from 'std'
|
|
4
4
|
import * as gui from 'gui'
|
|
5
|
-
import type { Document, Page, Pixmap } from '
|
|
5
|
+
import type { Document, Page, Pixmap } from '@quickwinlabs/mupdf-wasm'
|
|
6
|
+
import { MUPDF_JS_URL, preloadMupdfWasm } from '../test/mupdf-cdn.js'
|
|
6
7
|
import { assertNonNullable } from '../lib/assert.js'
|
|
7
8
|
import { WCHAR } from '../lib/ffi/bind.js'
|
|
8
|
-
import { BITMAPINFOHEADER, OPENFILENAMEW } from '../lib/windows/structs.js'
|
|
9
|
+
import { BITMAPINFOHEADER, OPENFILENAMEW, RECT, SCROLLINFO } from '../lib/windows/structs.js'
|
|
9
10
|
import { GetOpenFileName } from '../lib/windows/comdlg32.js'
|
|
10
|
-
import { GetDC, ReleaseDC } from '../lib/windows/user32.js'
|
|
11
|
+
import { GetClientRect, GetDC, GetScrollInfo, GetWindowLongPtr, GetWindowText, GetWindowTextLength, InvalidateRect, MessageBox, PostQuitMessage, ReleaseDC, SetScrollInfo, SetWindowPos, SetWindowLongPtr, SetWindowText, ShowScrollBar, ShowWindow } from '../lib/windows/user32.js'
|
|
11
12
|
import { PatBlt, SetDIBitsToDevice } from '../lib/windows/gdi32.js'
|
|
12
|
-
import { Gwlp, MouseKeyFlag, ScrollBar, ScrollCmd, SetWindowPosFlag, SetWindowPosHwnd, WindowStyle, WmMsg } from '../constants'
|
|
13
|
+
import { ColorUse, Gwlp, MouseKeyFlag, ScrollBar, ScrollCmd, ScrollInfoFlag, SetWindowPosFlag, SetWindowPosHwnd, ShowWindowCmd, WindowStyle, WmMsg } from '../constants'
|
|
13
14
|
|
|
14
|
-
type MuPdf = typeof import('
|
|
15
|
+
type MuPdf = typeof import('@quickwinlabs/mupdf-wasm')
|
|
15
16
|
|
|
16
17
|
const WHITENESS = 0x00FF0062
|
|
17
18
|
|
|
@@ -41,14 +42,13 @@ function clearCachedDoc(): void {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
async function loadMupdf(): Promise<MuPdf | null> {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
catch (e) { std.printf('Error: mupdf load failed: %s\n', String(e)); return null }
|
|
45
|
+
try {
|
|
46
|
+
await preloadMupdfWasm()
|
|
47
|
+
return await import(MUPDF_JS_URL)
|
|
48
|
+
} catch (e) {
|
|
49
|
+
std.printf('Error: mupdf load failed: %s\n', String(e))
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function renderPdfPage(mupdf: MuPdf, filePath: string, pageIndex: number): PixmapInfo & { totalPages: number } | null {
|
|
@@ -113,73 +113,81 @@ function openPdfFileDialog(): string | null {
|
|
|
113
113
|
function updateScrollRange(): void {
|
|
114
114
|
assertNonNullable(hwndCanvas); assertNonNullable(hwndContent)
|
|
115
115
|
if (!currentPixmap) return
|
|
116
|
-
const
|
|
117
|
-
if (!
|
|
118
|
-
const
|
|
116
|
+
const crBuf = RECT.alloc()
|
|
117
|
+
if (!GetClientRect(hwndCanvas, crBuf.ptr)) return
|
|
118
|
+
const { left, top, right, bottom } = RECT.decode(crBuf)
|
|
119
|
+
const cw = right - left, ch = bottom - top
|
|
119
120
|
const maxX = Math.max(0, currentPixmap.w - cw)
|
|
120
121
|
const maxY = Math.max(0, currentPixmap.h - ch)
|
|
121
122
|
scrollX = Math.min(scrollX, maxX)
|
|
122
123
|
scrollY = Math.min(scrollY, maxY)
|
|
123
124
|
if (currentPixmap.w > cw) {
|
|
124
|
-
const style =
|
|
125
|
+
const style = GetWindowLongPtr(hwndCanvas, Gwlp.STYLE)
|
|
125
126
|
if (!(style & WindowStyle.HSCROLL)) {
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
SetWindowLongPtr(hwndCanvas, Gwlp.STYLE, style | WindowStyle.HSCROLL)
|
|
128
|
+
SetWindowPos(hwndCanvas, SetWindowPosHwnd.TOP, 0, 0, 0, 0,
|
|
128
129
|
SetWindowPosFlag.SWP_NOMOVE | SetWindowPosFlag.SWP_NOSIZE |
|
|
129
130
|
SetWindowPosFlag.SWP_NOZORDER | SetWindowPosFlag.SWP_FRAMECHANGED)
|
|
130
131
|
}
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
SetScrollInfo(hwndCanvas, ScrollBar.HORZ, {
|
|
133
|
+
cbSize: SCROLLINFO.size, fMask: ScrollInfoFlag.RANGE | ScrollInfoFlag.PAGE | ScrollInfoFlag.POS,
|
|
134
|
+
nMin: 0, nMax: currentPixmap.w - 1, nPage: cw, nPos: scrollX,
|
|
135
|
+
}, 1)
|
|
136
|
+
ShowScrollBar(hwndCanvas, ScrollBar.HORZ, 1)
|
|
133
137
|
} else {
|
|
134
138
|
scrollX = 0
|
|
135
|
-
const style =
|
|
139
|
+
const style = GetWindowLongPtr(hwndCanvas, Gwlp.STYLE)
|
|
136
140
|
if (style & WindowStyle.HSCROLL) {
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
SetWindowLongPtr(hwndCanvas, Gwlp.STYLE, style & ~WindowStyle.HSCROLL)
|
|
142
|
+
SetWindowPos(hwndCanvas, SetWindowPosHwnd.TOP, 0, 0, 0, 0,
|
|
139
143
|
SetWindowPosFlag.SWP_NOMOVE | SetWindowPosFlag.SWP_NOSIZE |
|
|
140
144
|
SetWindowPosFlag.SWP_NOZORDER | SetWindowPosFlag.SWP_FRAMECHANGED)
|
|
141
145
|
}
|
|
142
|
-
|
|
146
|
+
ShowScrollBar(hwndCanvas, ScrollBar.HORZ, 0)
|
|
143
147
|
}
|
|
144
148
|
if (currentPixmap.h > ch) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
SetScrollInfo(hwndCanvas, ScrollBar.VERT, {
|
|
150
|
+
cbSize: SCROLLINFO.size, fMask: ScrollInfoFlag.RANGE | ScrollInfoFlag.PAGE | ScrollInfoFlag.POS,
|
|
151
|
+
nMin: 0, nMax: currentPixmap.h - 1, nPage: ch, nPos: scrollY,
|
|
152
|
+
}, 1)
|
|
153
|
+
ShowScrollBar(hwndCanvas, ScrollBar.VERT, 1)
|
|
154
|
+
} else { scrollY = 0; ShowScrollBar(hwndCanvas, ScrollBar.VERT, 0) }
|
|
155
|
+
SetWindowPos(hwndContent, SetWindowPosHwnd.TOP, -scrollX, -scrollY, 0, 0,
|
|
149
156
|
SetWindowPosFlag.SWP_NOSIZE | SetWindowPosFlag.SWP_NOZORDER)
|
|
150
157
|
}
|
|
151
158
|
|
|
152
159
|
function showPdf(mupdf: MuPdf, path: string, pageIdx: number): void {
|
|
153
160
|
assertNonNullable(hwndMain); assertNonNullable(hwndContent)
|
|
154
161
|
const pix = renderPdfPage(mupdf, path, pageIdx)
|
|
155
|
-
if (!pix) {
|
|
162
|
+
if (!pix) { MessageBox(NULL, '渲染 PDF 失败', 'JS Alert', 0); return }
|
|
156
163
|
currentPixmap = pix; currentPage = pageIdx; totalPages = pix.totalPages
|
|
157
|
-
|
|
164
|
+
SetWindowText(hwndMain, 'PDF 预览 - 第 ' + (pageIdx + 1) + '/' + totalPages + ' 页')
|
|
158
165
|
scrollX = 0; scrollY = 0
|
|
159
|
-
|
|
166
|
+
SetWindowPos(hwndContent, SetWindowPosHwnd.TOP, 0, 0, pix.w, pix.h,
|
|
160
167
|
SetWindowPosFlag.SWP_NOZORDER | SetWindowPosFlag.SWP_NOMOVE)
|
|
161
168
|
updateScrollRange()
|
|
162
|
-
|
|
169
|
+
InvalidateRect(hwndContent, NULL, 1)
|
|
163
170
|
}
|
|
164
171
|
|
|
165
172
|
function doScroll(dx: number, dy: number): void {
|
|
166
173
|
assertNonNullable(hwndCanvas); assertNonNullable(hwndContent)
|
|
167
|
-
const
|
|
168
|
-
if (!
|
|
169
|
-
const
|
|
174
|
+
const crBuf = RECT.alloc()
|
|
175
|
+
if (!GetClientRect(hwndCanvas, crBuf.ptr) || !currentPixmap) return
|
|
176
|
+
const { left, top, right, bottom } = RECT.decode(crBuf)
|
|
177
|
+
const cw = right - left, ch = bottom - top
|
|
170
178
|
const maxX = Math.max(0, currentPixmap.w - cw)
|
|
171
179
|
const maxY = Math.max(0, currentPixmap.h - ch)
|
|
172
180
|
scrollX = Math.max(0, Math.min(maxX, scrollX + dx))
|
|
173
181
|
scrollY = Math.max(0, Math.min(maxY, scrollY + dy))
|
|
174
|
-
|
|
182
|
+
SetWindowPos(hwndContent, SetWindowPosHwnd.TOP, -scrollX, -scrollY, 0, 0,
|
|
175
183
|
SetWindowPosFlag.SWP_NOSIZE | SetWindowPosFlag.SWP_NOZORDER)
|
|
176
|
-
|
|
177
|
-
|
|
184
|
+
SetScrollInfo(hwndCanvas, ScrollBar.HORZ, { cbSize: SCROLLINFO.size, fMask: ScrollInfoFlag.POS, nPos: scrollX }, 1)
|
|
185
|
+
SetScrollInfo(hwndCanvas, ScrollBar.VERT, { cbSize: SCROLLINFO.size, fMask: ScrollInfoFlag.POS, nPos: scrollY }, 1)
|
|
178
186
|
}
|
|
179
187
|
|
|
180
188
|
async function main(): Promise<void> {
|
|
181
189
|
const mupdf = await loadMupdf()
|
|
182
|
-
if (!mupdf) {
|
|
190
|
+
if (!mupdf) { MessageBox(NULL, '加载 mupdf WASM 失败', 'JS Alert', 0); return }
|
|
183
191
|
|
|
184
192
|
const WS_CHILD = WindowStyle.CHILD
|
|
185
193
|
const WS_VIS = WindowStyle.VISIBLE
|
|
@@ -192,13 +200,14 @@ let hwndBtnNext: gui.HWND | null = null
|
|
|
192
200
|
|
|
193
201
|
gui.RegisterClass('PdfViewer2', (hwnd, msg, wParam, lParam) => {
|
|
194
202
|
assertNonNullable(hwndCanvas); assertNonNullable(hwndEdit); assertNonNullable(hwndBtnOpen); assertNonNullable(hwndBtnPrev); assertNonNullable(hwndBtnNext)
|
|
195
|
-
if (msg === WmMsg.DESTROY) {
|
|
203
|
+
if (msg === WmMsg.DESTROY) { PostQuitMessage(0); return 0 }
|
|
196
204
|
if (msg === WmMsg.SIZE) {
|
|
197
|
-
const
|
|
198
|
-
if (
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
205
|
+
const crBuf = RECT.alloc()
|
|
206
|
+
if (GetClientRect(hwnd, crBuf.ptr)) {
|
|
207
|
+
const { left, top, right, bottom } = RECT.decode(crBuf)
|
|
208
|
+
const cw = right - left, ch = bottom - top
|
|
209
|
+
SetWindowPos(hwndCanvas, SetWindowPosHwnd.TOP, 0, 50, cw, Math.max(1, ch - 50), SetWindowPosFlag.SWP_NOZORDER)
|
|
210
|
+
SetWindowPos(hwndEdit, SetWindowPosHwnd.TOP, 0, 0, Math.max(100, cw - 320), 0,
|
|
202
211
|
SetWindowPosFlag.SWP_NOMOVE | SetWindowPosFlag.SWP_NOZORDER)
|
|
203
212
|
}
|
|
204
213
|
return gui.DefWindowProc(hwnd, msg, wParam, lParam)
|
|
@@ -207,12 +216,18 @@ let hwndBtnNext: gui.HWND | null = null
|
|
|
207
216
|
const hCtrl = lParam
|
|
208
217
|
if (hCtrl === hwndBtnOpen) {
|
|
209
218
|
const path = openPdfFileDialog()
|
|
210
|
-
if (path) {
|
|
219
|
+
if (path) { SetWindowText(hwndEdit, path); showPdf(mupdf, path, 0) }
|
|
211
220
|
} else if (hCtrl === hwndBtnPrev) {
|
|
212
|
-
const
|
|
221
|
+
const cap = GetWindowTextLength(hwndEdit) + 1
|
|
222
|
+
const buf = WCHAR.alloc(cap)
|
|
223
|
+
GetWindowText(hwndEdit, buf, cap)
|
|
224
|
+
const path = WCHAR.decode(buf)
|
|
213
225
|
if (path && currentPage > 0) showPdf(mupdf, path, currentPage - 1)
|
|
214
226
|
} else if (hCtrl === hwndBtnNext) {
|
|
215
|
-
const
|
|
227
|
+
const cap = GetWindowTextLength(hwndEdit) + 1
|
|
228
|
+
const buf = WCHAR.alloc(cap)
|
|
229
|
+
GetWindowText(hwndEdit, buf, cap)
|
|
230
|
+
const path = WCHAR.decode(buf)
|
|
216
231
|
if (path && currentPage < totalPages - 1) showPdf(mupdf, path, currentPage + 1)
|
|
217
232
|
}
|
|
218
233
|
return 0
|
|
@@ -222,7 +237,7 @@ let hwndBtnNext: gui.HWND | null = null
|
|
|
222
237
|
hwndMain = gui.CreateWindow('PdfViewer2', 'PDF 预览 2',
|
|
223
238
|
WindowStyle.OVERLAPPEDWINDOW | WindowStyle.CLIPCHILDREN,
|
|
224
239
|
100, 100, 1100, 800, null, null)!
|
|
225
|
-
if (!hwndMain) {
|
|
240
|
+
if (!hwndMain) { MessageBox(NULL, '创建主窗口失败', 'JS Alert', 0); return }
|
|
226
241
|
|
|
227
242
|
// 控件直接挂在主窗口上
|
|
228
243
|
hwndEdit = gui.CreateWindow('EDIT', '',
|
|
@@ -265,7 +280,7 @@ let hwndBtnNext: gui.HWND | null = null
|
|
|
265
280
|
const hdc = GetDC(hwnd)
|
|
266
281
|
if (hdc) {
|
|
267
282
|
const bmi = makeBitmapInfo(pm.w, pm.h)
|
|
268
|
-
SetDIBitsToDevice(hdc, 0, 0, pm.w, pm.h, 0, 0, 0, pm.h, pm.data, bmi,
|
|
283
|
+
SetDIBitsToDevice(hdc, 0, 0, pm.w, pm.h, 0, 0, 0, pm.h, pm.data, bmi, ColorUse.RGB_COLORS)
|
|
269
284
|
ReleaseDC(hwnd, hdc)
|
|
270
285
|
}
|
|
271
286
|
return 0
|
|
@@ -277,9 +292,10 @@ let hwndBtnNext: gui.HWND | null = null
|
|
|
277
292
|
gui.SetWindowProc(hwndCanvas, (hwnd: gui.HWND, msg: number, wParam: number, lParam: number): number => {
|
|
278
293
|
if (msg === WmMsg.ERASEBKGND) return 1
|
|
279
294
|
if (msg === WmMsg.PAINT) {
|
|
280
|
-
const
|
|
281
|
-
if (
|
|
282
|
-
const
|
|
295
|
+
const crBuf = RECT.alloc()
|
|
296
|
+
if (GetClientRect(hwnd, crBuf.ptr)) {
|
|
297
|
+
const { left, top, right, bottom } = RECT.decode(crBuf)
|
|
298
|
+
const cw = right - left, ch = bottom - top
|
|
283
299
|
const hdc = GetDC(hwnd)
|
|
284
300
|
if (hdc) {
|
|
285
301
|
PatBlt(hdc, 0, 0, cw, ch, WHITENESS)
|
|
@@ -291,16 +307,18 @@ let hwndBtnNext: gui.HWND | null = null
|
|
|
291
307
|
}
|
|
292
308
|
if (msg === WmMsg.VSCROLL || msg === WmMsg.HSCROLL) {
|
|
293
309
|
const bar = msg === WmMsg.VSCROLL ? ScrollBar.VERT : ScrollBar.HORZ
|
|
294
|
-
const
|
|
295
|
-
|
|
310
|
+
const si = SCROLLINFO.encode({ cbSize: SCROLLINFO.size, fMask: ScrollInfoFlag.ALL })
|
|
311
|
+
GetScrollInfo(hwnd, bar, si.ptr)
|
|
312
|
+
const info = SCROLLINFO.decode(si)
|
|
313
|
+
const maxPos = info.nMax - info.nPage + 1
|
|
296
314
|
const code = wParam & 0xFFFF
|
|
297
315
|
let newPos = bar === ScrollBar.VERT ? scrollY : scrollX
|
|
298
316
|
if (code === ScrollCmd.THUMBTRACK || code === ScrollCmd.THUMBPOSITION)
|
|
299
317
|
newPos = Math.max(0, Math.min(maxPos, (wParam >> 16) & 0xFFFF))
|
|
300
318
|
else if (code === ScrollCmd.LINEUP) newPos -= 20
|
|
301
319
|
else if (code === ScrollCmd.LINEDOWN) newPos += 20
|
|
302
|
-
else if (code === ScrollCmd.PAGEUP) newPos -= info.
|
|
303
|
-
else if (code === ScrollCmd.PAGEDOWN) newPos += info.
|
|
320
|
+
else if (code === ScrollCmd.PAGEUP) newPos -= info.nPage
|
|
321
|
+
else if (code === ScrollCmd.PAGEDOWN) newPos += info.nPage
|
|
304
322
|
else return 0
|
|
305
323
|
newPos = Math.max(0, Math.min(maxPos, newPos))
|
|
306
324
|
if (bar === ScrollBar.VERT) doScroll(0, newPos - scrollY)
|
|
@@ -312,8 +330,10 @@ let hwndBtnNext: gui.HWND | null = null
|
|
|
312
330
|
const wheel = raw >= 0x8000 ? raw - 0x10000 : raw
|
|
313
331
|
const isHorz = (wParam & MouseKeyFlag.MK_SHIFT) !== 0
|
|
314
332
|
const bar = isHorz ? ScrollBar.HORZ : ScrollBar.VERT
|
|
315
|
-
const
|
|
316
|
-
|
|
333
|
+
const si = SCROLLINFO.encode({ cbSize: SCROLLINFO.size, fMask: ScrollInfoFlag.ALL })
|
|
334
|
+
GetScrollInfo(hwnd, bar, si.ptr)
|
|
335
|
+
const info = SCROLLINFO.decode(si)
|
|
336
|
+
const maxPos = info.nMax - info.nPage + 1
|
|
317
337
|
let newPos = (isHorz ? scrollX : scrollY) - Math.round(wheel * 40 / 120)
|
|
318
338
|
newPos = Math.max(0, Math.min(maxPos, newPos))
|
|
319
339
|
if (isHorz) doScroll(newPos - scrollX, 0)
|
|
@@ -324,10 +344,10 @@ let hwndBtnNext: gui.HWND | null = null
|
|
|
324
344
|
return gui.DefWindowProc(hwnd, msg, wParam, lParam)
|
|
325
345
|
})
|
|
326
346
|
|
|
327
|
-
|
|
347
|
+
ShowWindow(hwndMain, ShowWindowCmd.SHOWNORMAL)
|
|
328
348
|
|
|
329
|
-
const test = std.open('example.pdf', 'rb')
|
|
330
|
-
if (test) { test.close(); showPdf(mupdf, 'example.pdf', 0) }
|
|
349
|
+
const test = std.open('test/example.pdf', 'rb')
|
|
350
|
+
if (test) { test.close(); showPdf(mupdf, 'test/example.pdf', 0) }
|
|
331
351
|
}
|
|
332
352
|
|
|
333
353
|
main()
|