quickwin 2.3.0 → 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.
Files changed (99) hide show
  1. package/README.en.md +1 -2
  2. package/README.md +1 -2
  3. package/constants.ts +22 -0
  4. package/enum_map.d.ts +4 -0
  5. package/examples/PdfCanvas.js +1 -1
  6. package/examples/PdfCanvas.tsx +2 -2
  7. package/examples/execd/main.js +491 -217
  8. package/examples/pdf_preview2.js +76 -67
  9. package/examples/pdf_preview2.ts +84 -64
  10. package/examples/pdf_viewer.js +4932 -1029
  11. package/examples/pdf_viewer.tsx +11 -22
  12. package/examples/test_gallery.js +308 -143
  13. package/examples/test_gallery.tsx +12 -7
  14. package/examples/test_react_lazy.js +295 -133
  15. package/examples/test_react_lazy.tsx +4 -3
  16. package/examples/test_react_listbox.js +293 -132
  17. package/examples/test_react_listbox.tsx +3 -2
  18. package/examples/test_root_window.js +283 -126
  19. package/examples/test_tab.js +295 -133
  20. package/examples/test_tab.tsx +4 -3
  21. package/examples/tray_demo.js +37 -30
  22. package/examples/tray_demo.tsx +39 -32
  23. package/lib/bitmap.js +40 -0
  24. package/lib/bitmap.ts +39 -0
  25. package/lib/certs.js +31 -3
  26. package/lib/certs.ts +32 -3
  27. package/lib/dpi.js +30 -0
  28. package/lib/dpi.ts +31 -0
  29. package/lib/dumpRects.js +14 -7
  30. package/lib/dumpRects.ts +14 -6
  31. package/lib/ffi/bind.js +5 -4
  32. package/lib/ffi/bind.ts +9 -8
  33. package/lib/http-server.js +50 -25
  34. package/lib/http-server.ts +51 -23
  35. package/lib/react-qw/components/DateTimePicker.js +6 -7
  36. package/lib/react-qw/components/Input.js +5 -1
  37. package/lib/react-qw/components/ListView.js +8 -6
  38. package/lib/react-qw/components/ScrollView.js +28 -22
  39. package/lib/react-qw/index.js +7 -5
  40. package/lib/react-qw/index.ts +5 -5
  41. package/lib/react-qw/props.js +3 -3
  42. package/lib/react-qw/props.ts +3 -3
  43. package/lib/react-qw/reconciler.js +12 -17
  44. package/lib/react-qw/reconciler.ts +12 -16
  45. package/lib/text-measure.js +9 -6
  46. package/lib/text-measure.ts +9 -6
  47. package/lib/windows/comctl32.js +3 -2
  48. package/lib/windows/comctl32.ts +3 -2
  49. package/lib/windows/gdi32.js +28 -6
  50. package/lib/windows/gdi32.ts +30 -6
  51. package/lib/windows/kernel32.js +9 -0
  52. package/lib/windows/kernel32.ts +9 -0
  53. package/lib/windows/shell32.js +11 -2
  54. package/lib/windows/shell32.ts +13 -2
  55. package/lib/windows/structs.js +51 -0
  56. package/lib/windows/structs.ts +55 -0
  57. package/lib/windows/user32.js +6 -5
  58. package/lib/windows/user32.ts +6 -5
  59. package/main.js +13 -5
  60. package/package.json +2 -2
  61. package/quickwin.d.ts +13 -115
  62. package/qwin-nowasm-x86.exe +0 -0
  63. package/qwin-nowasm.exe +0 -0
  64. package/qwin-x86.exe +0 -0
  65. package/qwin.exe +0 -0
  66. package/test/bench_ffi_callraw.js +3 -4
  67. package/test/bench_ffi_callraw.ts +3 -4
  68. package/test/debug/mupdf_debug.js +4 -22
  69. package/test/debug/wasm_export_debug.js +2 -12
  70. package/test/debug/wasm_names.js +3 -13
  71. package/test/example.pdf +0 -0
  72. package/test/mupdf-cdn.js +40 -0
  73. package/test/mupdf-cdn.ts +43 -0
  74. package/test/test_ffi.js +8 -9
  75. package/test/test_ffi.ts +8 -9
  76. package/test/test_ffi_abi.js +6 -6
  77. package/test/test_ffi_abi.ts +8 -8
  78. package/test/test_ffi_windows.js +82 -11
  79. package/test/test_ffi_windows.ts +85 -11
  80. package/test/test_gdi_draw.js +3 -3
  81. package/test/test_gdi_draw.ts +4 -4
  82. package/test/test_mupdf_render.js +4 -22
  83. package/test/test_mupdf_render.ts +4 -23
  84. package/test/test_mupdf_twice.js +4 -19
  85. package/test/test_mupdf_twice.ts +4 -20
  86. package/test/test_mupdf_wasm.js +10 -4
  87. package/test/test_mupdf_wasm.ts +6 -4
  88. package/test/test_react_complex.js +316 -140
  89. package/test/test_react_counter.js +295 -133
  90. package/test/test_react_flex.js +297 -138
  91. package/test/test_react_hidden.js +296 -134
  92. package/test/test_react_listview_link.js +288 -131
  93. package/test/test_react_notify.js +290 -133
  94. package/test/test_react_render.js +295 -133
  95. package/vendor/mupdf-wasm/mupdf-wasm.d.ts +0 -571
  96. package/vendor/mupdf-wasm/mupdf-wasm.js +0 -2749
  97. package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
  98. package/vendor/mupdf-wasm/mupdf.d.ts +0 -939
  99. package/vendor/mupdf-wasm/mupdf.js +0 -3317
@@ -1,8 +1,11 @@
1
- import { NULL } from '../lib/ffi/ctype.js';
1
+ import { NULL, PTR_SIZE, PtrArrayBuffer } from '../lib/ffi/ctype.js';
2
2
  import { GetDesktopWindow, CreatePopupMenu, DestroyMenu, GetDC, ReleaseDC, LoadImage, LoadImageOrdinal, SetScrollInfo, GetScrollInfo, } from '../lib/windows/user32.js';
3
- import { GetDeviceCaps, CreateSolidBrush, DeleteObject, CreateFontIndirect, GetObject, SelectObject, CreateCompatibleDC, DeleteDC, } from '../lib/windows/gdi32.js';
4
- import { ImageListCreate, ImageListDestroy } from '../lib/windows/comctl32.js';
5
- import { LOGFONTW, SCROLLINFO } from '../lib/windows/structs.js';
3
+ import { GetDeviceCaps, CreateSolidBrush, DeleteObject, CreateFontIndirect, GetObject, SelectObject, CreateCompatibleDC, CreateCompatibleBitmap, DeleteDC, GetDIBits, } from '../lib/windows/gdi32.js';
4
+ import { ImageListCreate, ImageListAdd, ImageListDestroy } from '../lib/windows/comctl32.js';
5
+ import { ShellNotifyIcon } from '../lib/windows/shell32.js';
6
+ import { BITMAPINFOHEADER, LOGFONTW, NONCLIENTMETRICSW, NOTIFYICONDATAW, SCROLLINFO } from '../lib/windows/structs.js';
7
+ import { createSystemDpiFont, getScaleFactor } from '../lib/dpi.js';
8
+ import { createBitmapFromPixels } from '../lib/bitmap.js';
6
9
  export const suite = {
7
10
  run(t) {
8
11
  // 本文件 import 三个 dll 模块即在加载时执行全部 bind:proc 名错误 → GetProcAddress 失败 →
@@ -61,18 +64,28 @@ export const suite = {
61
64
  const hdc = GetDC(NULL);
62
65
  t.checkTrue('GetDC(NULL) 非 0', hdc !== 0);
63
66
  if (hdc !== 0)
64
- t.checkTrue('GetDeviceCaps(hdc, LOGPIXELSX) > 0', GetDeviceCaps(hdc, 88) > 0);
67
+ t.checkTrue('GetDeviceCaps(hdc, LOGPIXELSX) > 0', GetDeviceCaps(hdc, 88 /* DeviceCap.LOGPIXELSX */) > 0);
65
68
  t.checkTrue('ReleaseDC(NULL, hdc) 成功', hdc !== 0 && ReleaseDC(NULL, hdc) !== 0);
66
69
  t.section('user32: 弹出菜单往返');
67
70
  const pm = CreatePopupMenu();
68
71
  t.checkTrue('CreatePopupMenu 非 0', pm !== 0);
69
72
  if (pm !== 0)
70
73
  t.checkTrue('DestroyMenu(pm) 成功', DestroyMenu(pm) !== 0);
71
- t.section('comctl32: ImageList 创建-销毁往返');
72
- const himl = ImageListCreate(16, 16, 4, 0, 0); // ILC_COLOR32=4;initial/grow=0 默认
73
- t.checkTrue('ImageListCreate(16, 16, ILC_COLOR32) 非 0', himl !== 0);
74
- if (himl !== 0)
74
+ t.section('comctl32: ImageList 创建-加位图-销毁往返(hbmMask 位传 NULL)');
75
+ const himl = ImageListCreate(16, 16, 24 /* ImageListFlag.COLOR24 */, 0, 0); // initial/grow=0 默认
76
+ t.checkTrue('ImageListCreate(16, 16, ILC_COLOR24) 非 0', himl !== 0);
77
+ if (himl !== 0) {
78
+ const hdcMem = CreateCompatibleDC(NULL);
79
+ const hbm = hdcMem !== 0 ? CreateCompatibleBitmap(hdcMem, 16, 16) : 0;
80
+ t.checkTrue('CreateCompatibleBitmap(16, 16) 非 0', hbm !== 0);
81
+ if (hbm !== 0)
82
+ t.check('ImageListAdd(himl, hbm, NULL) → 索引 0', 0, ImageListAdd(himl, hbm, NULL));
83
+ if (hbm !== 0)
84
+ DeleteObject(hbm);
85
+ if (hdcMem !== 0)
86
+ DeleteDC(hdcMem);
75
87
  t.checkTrue('ImageListDestroy(himl) 成功', ImageListDestroy(himl) !== 0);
88
+ }
76
89
  t.section('user32: LoadImage 双变体(string 自动编码 / ordinal MAKEINTRESOURCE)');
77
90
  // Win32 IMAGE_* 真值:BITMAP=0 CURSOR=1 ICON=2;LR_SHARED=0x8000(注意 ImageType d.ts 当前值与此不一致)
78
91
  t.checkTrue('LoadImageOrdinal(NULL, IDI_APPLICATION, IMAGE_ICON, LR_SHARED) 非 0', LoadImageOrdinal(NULL, 32512, 2, 0, 0, 0x8000) !== 0);
@@ -95,7 +108,65 @@ export const suite = {
95
108
  t.check('decode 回读 cbSize = encode 初值', SCROLLINFO.size, info.cbSize);
96
109
  t.check('decode 回读 fMask = encode 初值', 0x0001, info.fMask);
97
110
  t.checkTrue('读回字段为 number 形态', typeof info.nMin === 'number' && typeof info.nMax === 'number');
98
- // CallWindowProc / ShowScrollBar / ImageListAdd 仅由 import 的模块加载覆盖 proc 名:
99
- // CallWindowProc 跨线程窗口过程不安全、ShowScrollBar 有副作用、ImageListAdd 需位图源
111
+ // CallWindowProc / ShowScrollBar 仅由 import 的模块加载覆盖 proc 名:
112
+ // CallWindowProc 跨线程窗口过程不安全、ShowScrollBar 有副作用
113
+ // NONCLIENTMETRICS 的 cbSize/uiParam 随系统变(Vista+ 504 / XP·2003 500),
114
+ // 尺寸回退逻辑在 lib/dpi;此处只锁结构布局与端到端效果(见下 section)。
115
+ t.check('NONCLIENTMETRICSW.size == 504(4 LOGFONTW + 12 DWORD)', 504, NONCLIENTMETRICSW.size);
116
+ t.section('lib/dpi: 下沉到 JS 的 DPI / 系统字体复合逻辑');
117
+ t.checkTrue('getScaleFactor() ≥ 0.5', getScaleFactor() >= 0.5);
118
+ const dpiFont = createSystemDpiFont();
119
+ t.checkTrue('createSystemDpiFont() 非 NULL(SPI 尺寸回退后仍取到系统消息字体)', dpiFont !== 0);
120
+ if (dpiFont) {
121
+ const lf = LOGFONTW.alloc();
122
+ t.checkTrue('GetObject(dpiFont, LOGFONTW.size) 回读成功', GetObject(dpiFont, LOGFONTW.size, lf) === LOGFONTW.size);
123
+ const fo = LOGFONTW.decode(lf);
124
+ t.checkTrue('系统消息字体 lfFaceName 非空(结构确被 SPI 填过)', typeof fo.lfFaceName === 'string' && fo.lfFaceName.length > 0);
125
+ t.checkTrue('DeleteObject(系统字体) 成功', DeleteObject(dpiFont) !== 0);
126
+ }
127
+ t.section('shell32: Shell_NotifyIconW 托盘图标增删(NOTIFYICONDATAW 对象直传)');
128
+ // 尺寸基准来自 mingw 头文件实测(x86 956 / x64 976,随 hWnd/hIcon/hBalloonIcon 位宽)
129
+ t.check('NOTIFYICONDATAW.size == mingw sizeof(x86 956 / x64 976)', PTR_SIZE === 4 ? 956 : 976, NOTIFYICONDATAW.size);
130
+ const nid = NOTIFYICONDATAW.encode({ cbSize: NOTIFYICONDATAW.size, szTip: 'quickwin', szInfoTitle: 'qw' });
131
+ const nidBack = NOTIFYICONDATAW.decode(nid);
132
+ t.check('宽字符串糖 szTip 编解码往返', 'quickwin', nidBack.szTip);
133
+ t.check('宽字符串糖 szInfoTitle 编解码往返', 'qw', nidBack.szInfoTitle);
134
+ const trayIcon = LoadImageOrdinal(NULL, 32512 /* IDI.APPLICATION */, 1 /* ImageType.ICON */, 0, 0, 32768 /* LoadImageFlag.SHARED */);
135
+ t.checkTrue('LoadImageOrdinal(IDI.APPLICATION, SHARED) 非 0', trayIcon !== 0);
136
+ const trayHwnd = GetDesktopWindow();
137
+ t.checkTrue('ShellNotifyIcon(ADD) 成功(Vista+ 尺寸在 XP/Win7 均被接受)', ShellNotifyIcon(0 /* NotifyIconCmd.ADD */, {
138
+ cbSize: NOTIFYICONDATAW.size, hWnd: trayHwnd, uID: 77,
139
+ uFlags: 1 /* NotifyIconFlag.MESSAGE */ | 2 /* NotifyIconFlag.ICON */ | 4 /* NotifyIconFlag.TIP */,
140
+ uCallbackMessage: 0x8001, hIcon: trayIcon, szTip: 'quickwin test',
141
+ }) !== 0);
142
+ t.checkTrue('ShellNotifyIcon(DELETE) 成功(只需 hWnd+uID)', ShellNotifyIcon(2 /* NotifyIconCmd.DELETE */, { cbSize: NOTIFYICONDATAW.size, hWnd: trayHwnd, uID: 77 }) !== 0);
143
+ t.section('lib/bitmap: 按像素建 32bpp DIB 段(原 gui.CreateBitmapFromPixels)');
144
+ const nw = 4, nh = 2;
145
+ const px = new ArrayBuffer(nw * nh * 4);
146
+ const px8 = new Uint8Array(px);
147
+ for (let i = 0; i < px8.length; i++)
148
+ px8[i] = (i * 37 + 11) & 0xff;
149
+ const hbm = createBitmapFromPixels(nw, nh, px);
150
+ t.checkTrue('createBitmapFromPixels(4x2) 非 NULL', hbm !== 0);
151
+ if (hbm) {
152
+ const bmi = BITMAPINFOHEADER.encode({
153
+ biSize: BITMAPINFOHEADER.size, biWidth: nw, biHeight: -nh,
154
+ biPlanes: 1, biBitCount: 32, biSizeImage: nw * nh * 4,
155
+ });
156
+ const back = new PtrArrayBuffer(nw * nh * 4);
157
+ const dc = GetDC(NULL);
158
+ const rows = dc ? GetDIBits(dc, hbm, 0, nh, back, bmi.ptr, 0 /* ColorUse.RGB_COLORS */) : 0;
159
+ if (dc)
160
+ ReleaseDC(NULL, dc);
161
+ t.check('GetDIBits 取回全部扫描行', nh, rows);
162
+ const back8 = new Uint8Array(back);
163
+ let same = rows === nh;
164
+ for (let i = 0; i < back8.length; i++)
165
+ if (back8[i] !== px8[i])
166
+ same = false;
167
+ t.checkTrue('像素逐字节一致(含 alpha,与旧 C 的 memcpy 等价)', same);
168
+ t.checkTrue('DeleteObject(位图) 成功', DeleteObject(hbm) !== 0);
169
+ }
170
+ t.checkTrue('非法入参 → NULL(宽/高 ≤ 0、像素缓冲不足,同旧 C 语义)', createBitmapFromPixels(0, nh, px) === NULL && createBitmapFromPixels(nw, nh, new ArrayBuffer(4)) === NULL);
100
171
  },
101
172
  };
@@ -1,14 +1,18 @@
1
1
  import { Tester } from './test_helper.js'
2
- import { NULL } from '../lib/ffi/ctype.js'
2
+ import { NULL, PTR_SIZE, PtrArrayBuffer } from '../lib/ffi/ctype.js'
3
3
  import {
4
4
  GetDesktopWindow, CreatePopupMenu, DestroyMenu, GetDC, ReleaseDC,
5
5
  LoadImage, LoadImageOrdinal, SetScrollInfo, GetScrollInfo,
6
6
  } from '../lib/windows/user32.js'
7
7
  import {
8
- GetDeviceCaps, CreateSolidBrush, DeleteObject, CreateFontIndirect, GetObject, SelectObject, CreateCompatibleDC, DeleteDC,
8
+ GetDeviceCaps, CreateSolidBrush, DeleteObject, CreateFontIndirect, GetObject, SelectObject, CreateCompatibleDC, CreateCompatibleBitmap, DeleteDC, GetDIBits,
9
9
  } from '../lib/windows/gdi32.js'
10
- import { ImageListCreate, ImageListDestroy } from '../lib/windows/comctl32.js'
11
- import { LOGFONTW, SCROLLINFO } from '../lib/windows/structs.js'
10
+ import { ImageListCreate, ImageListAdd, ImageListDestroy } from '../lib/windows/comctl32.js'
11
+ import { ShellNotifyIcon } from '../lib/windows/shell32.js'
12
+ import { BITMAPINFOHEADER, LOGFONTW, NONCLIENTMETRICSW, NOTIFYICONDATAW, SCROLLINFO } from '../lib/windows/structs.js'
13
+ import { ColorUse, DeviceCap, IDI, ImageListFlag, ImageType, LoadImageFlag, NotifyIconCmd, NotifyIconFlag } from '../constants'
14
+ import { createSystemDpiFont, getScaleFactor } from '../lib/dpi.js'
15
+ import { createBitmapFromPixels } from '../lib/bitmap.js'
12
16
 
13
17
  export const suite = {
14
18
  run(t: Tester) {
@@ -69,7 +73,7 @@ export const suite = {
69
73
  t.section('gdi32: GetDeviceCaps(<HDC>ptr 跨 user32 品牌互认)')
70
74
  const hdc = GetDC(NULL)
71
75
  t.checkTrue('GetDC(NULL) 非 0', hdc !== 0)
72
- if (hdc !== 0) t.checkTrue('GetDeviceCaps(hdc, LOGPIXELSX) > 0', GetDeviceCaps(hdc, 88) > 0)
76
+ if (hdc !== 0) t.checkTrue('GetDeviceCaps(hdc, LOGPIXELSX) > 0', GetDeviceCaps(hdc, DeviceCap.LOGPIXELSX) > 0)
73
77
  t.checkTrue('ReleaseDC(NULL, hdc) 成功', hdc !== 0 && ReleaseDC(NULL, hdc) !== 0)
74
78
 
75
79
  t.section('user32: 弹出菜单往返')
@@ -77,10 +81,18 @@ export const suite = {
77
81
  t.checkTrue('CreatePopupMenu 非 0', pm !== 0)
78
82
  if (pm !== 0) t.checkTrue('DestroyMenu(pm) 成功', DestroyMenu(pm) !== 0)
79
83
 
80
- t.section('comctl32: ImageList 创建-销毁往返')
81
- const himl = ImageListCreate(16, 16, 4, 0, 0) // ILC_COLOR32=4;initial/grow=0 默认
82
- t.checkTrue('ImageListCreate(16, 16, ILC_COLOR32) 非 0', himl !== 0)
83
- if (himl !== 0) t.checkTrue('ImageListDestroy(himl) 成功', ImageListDestroy(himl) !== 0)
84
+ t.section('comctl32: ImageList 创建-加位图-销毁往返(hbmMask 位传 NULL)')
85
+ const himl = ImageListCreate(16, 16, ImageListFlag.COLOR24, 0, 0) // initial/grow=0 默认
86
+ t.checkTrue('ImageListCreate(16, 16, ILC_COLOR24) 非 0', himl !== 0)
87
+ if (himl !== 0) {
88
+ const hdcMem = CreateCompatibleDC(NULL)
89
+ const hbm = hdcMem !== 0 ? CreateCompatibleBitmap(hdcMem, 16, 16) : 0
90
+ t.checkTrue('CreateCompatibleBitmap(16, 16) 非 0', hbm !== 0)
91
+ if (hbm !== 0) t.check('ImageListAdd(himl, hbm, NULL) → 索引 0', 0, ImageListAdd(himl, hbm, NULL))
92
+ if (hbm !== 0) DeleteObject(hbm)
93
+ if (hdcMem !== 0) DeleteDC(hdcMem)
94
+ t.checkTrue('ImageListDestroy(himl) 成功', ImageListDestroy(himl) !== 0)
95
+ }
84
96
 
85
97
  t.section('user32: LoadImage 双变体(string 自动编码 / ordinal MAKEINTRESOURCE)')
86
98
  // Win32 IMAGE_* 真值:BITMAP=0 CURSOR=1 ICON=2;LR_SHARED=0x8000(注意 ImageType d.ts 当前值与此不一致)
@@ -107,7 +119,69 @@ export const suite = {
107
119
  t.check('decode 回读 cbSize = encode 初值', SCROLLINFO.size, info.cbSize)
108
120
  t.check('decode 回读 fMask = encode 初值', 0x0001, info.fMask)
109
121
  t.checkTrue('读回字段为 number 形态', typeof info.nMin === 'number' && typeof info.nMax === 'number')
110
- // CallWindowProc / ShowScrollBar / ImageListAdd 仅由 import 的模块加载覆盖 proc 名:
111
- // CallWindowProc 跨线程窗口过程不安全、ShowScrollBar 有副作用、ImageListAdd 需位图源
122
+ // CallWindowProc / ShowScrollBar 仅由 import 的模块加载覆盖 proc 名:
123
+ // CallWindowProc 跨线程窗口过程不安全、ShowScrollBar 有副作用
124
+
125
+ // NONCLIENTMETRICS 的 cbSize/uiParam 随系统变(Vista+ 504 / XP·2003 500),
126
+ // 尺寸回退逻辑在 lib/dpi;此处只锁结构布局与端到端效果(见下 section)。
127
+ t.check('NONCLIENTMETRICSW.size == 504(4 LOGFONTW + 12 DWORD)', 504, NONCLIENTMETRICSW.size)
128
+
129
+ t.section('lib/dpi: 下沉到 JS 的 DPI / 系统字体复合逻辑')
130
+ t.checkTrue('getScaleFactor() ≥ 0.5', getScaleFactor() >= 0.5)
131
+ const dpiFont = createSystemDpiFont()
132
+ t.checkTrue('createSystemDpiFont() 非 NULL(SPI 尺寸回退后仍取到系统消息字体)', dpiFont !== 0)
133
+ if (dpiFont) {
134
+ const lf = LOGFONTW.alloc()
135
+ t.checkTrue('GetObject(dpiFont, LOGFONTW.size) 回读成功', GetObject(dpiFont, LOGFONTW.size, lf) === LOGFONTW.size)
136
+ const fo = LOGFONTW.decode(lf)
137
+ t.checkTrue('系统消息字体 lfFaceName 非空(结构确被 SPI 填过)', typeof fo.lfFaceName === 'string' && fo.lfFaceName.length > 0)
138
+ t.checkTrue('DeleteObject(系统字体) 成功', DeleteObject(dpiFont) !== 0)
139
+ }
140
+
141
+ t.section('shell32: Shell_NotifyIconW 托盘图标增删(NOTIFYICONDATAW 对象直传)')
142
+ // 尺寸基准来自 mingw 头文件实测(x86 956 / x64 976,随 hWnd/hIcon/hBalloonIcon 位宽)
143
+ t.check('NOTIFYICONDATAW.size == mingw sizeof(x86 956 / x64 976)',
144
+ PTR_SIZE === 4 ? 956 : 976, NOTIFYICONDATAW.size)
145
+ const nid = NOTIFYICONDATAW.encode({ cbSize: NOTIFYICONDATAW.size, szTip: 'quickwin', szInfoTitle: 'qw' })
146
+ const nidBack = NOTIFYICONDATAW.decode(nid)
147
+ t.check('宽字符串糖 szTip 编解码往返', 'quickwin', nidBack.szTip)
148
+ t.check('宽字符串糖 szInfoTitle 编解码往返', 'qw', nidBack.szInfoTitle)
149
+
150
+ const trayIcon = LoadImageOrdinal(NULL, IDI.APPLICATION, ImageType.ICON, 0, 0, LoadImageFlag.SHARED)
151
+ t.checkTrue('LoadImageOrdinal(IDI.APPLICATION, SHARED) 非 0', trayIcon !== 0)
152
+ const trayHwnd = GetDesktopWindow()
153
+ t.checkTrue('ShellNotifyIcon(ADD) 成功(Vista+ 尺寸在 XP/Win7 均被接受)', ShellNotifyIcon(NotifyIconCmd.ADD, {
154
+ cbSize: NOTIFYICONDATAW.size, hWnd: trayHwnd, uID: 77,
155
+ uFlags: NotifyIconFlag.MESSAGE | NotifyIconFlag.ICON | NotifyIconFlag.TIP,
156
+ uCallbackMessage: 0x8001, hIcon: trayIcon, szTip: 'quickwin test',
157
+ }) !== 0)
158
+ t.checkTrue('ShellNotifyIcon(DELETE) 成功(只需 hWnd+uID)',
159
+ ShellNotifyIcon(NotifyIconCmd.DELETE, { cbSize: NOTIFYICONDATAW.size, hWnd: trayHwnd, uID: 77 }) !== 0)
160
+
161
+ t.section('lib/bitmap: 按像素建 32bpp DIB 段(原 gui.CreateBitmapFromPixels)')
162
+ const nw = 4, nh = 2
163
+ const px = new ArrayBuffer(nw * nh * 4)
164
+ const px8 = new Uint8Array(px)
165
+ for (let i = 0; i < px8.length; i++) px8[i] = (i * 37 + 11) & 0xff
166
+ const hbm = createBitmapFromPixels(nw, nh, px)
167
+ t.checkTrue('createBitmapFromPixels(4x2) 非 NULL', hbm !== 0)
168
+ if (hbm) {
169
+ const bmi = BITMAPINFOHEADER.encode({
170
+ biSize: BITMAPINFOHEADER.size, biWidth: nw, biHeight: -nh,
171
+ biPlanes: 1, biBitCount: 32, biSizeImage: nw * nh * 4,
172
+ })
173
+ const back = new PtrArrayBuffer<'BYTE'>(nw * nh * 4)
174
+ const dc = GetDC(NULL)
175
+ const rows = dc ? GetDIBits(dc, hbm, 0, nh, back, bmi.ptr, ColorUse.RGB_COLORS) : 0
176
+ if (dc) ReleaseDC(NULL, dc)
177
+ t.check('GetDIBits 取回全部扫描行', nh, rows)
178
+ const back8 = new Uint8Array(back)
179
+ let same = rows === nh
180
+ for (let i = 0; i < back8.length; i++) if (back8[i] !== px8[i]) same = false
181
+ t.checkTrue('像素逐字节一致(含 alpha,与旧 C 的 memcpy 等价)', same)
182
+ t.checkTrue('DeleteObject(位图) 成功', DeleteObject(hbm) !== 0)
183
+ }
184
+ t.checkTrue('非法入参 → NULL(宽/高 ≤ 0、像素缓冲不足,同旧 C 语义)',
185
+ createBitmapFromPixels(0, nh, px) === NULL && createBitmapFromPixels(nw, nh, new ArrayBuffer(4)) === NULL)
112
186
  },
113
187
  }
@@ -38,7 +38,7 @@ export const suite = {
38
38
  // 失败置哨兵(px() 返回 -1)—— 像素断言 FAIL 出可诊断值,不炸整个 suite。
39
39
  let pxOk = true;
40
40
  const readPx = () => {
41
- pxOk = GetDIBits(mem, bmp, 0, H, pxB, bmi.ptr, 0) === H;
41
+ pxOk = GetDIBits(mem, bmp, 0, H, pxB, bmi.ptr, 0 /* ColorUse.RGB_COLORS */) === H;
42
42
  };
43
43
  const px = (x, y) => {
44
44
  if (!pxOk)
@@ -435,13 +435,13 @@ export const suite = {
435
435
  biSize: BITMAPINFOHEADER.size, biWidth: 4, biHeight: -4,
436
436
  biPlanes: 1, biBitCount: 32, biSizeImage: 4 * 4 * 4,
437
437
  });
438
- t.checkTrue('SetDIBitsToDevice ok', SetDIBitsToDevice(mem, 20, 20, 4, 4, 0, 0, 0, 4, dibBits, dibBmi, 0) === 4);
438
+ t.checkTrue('SetDIBitsToDevice ok', SetDIBitsToDevice(mem, 20, 20, 4, 4, 0, 0, 0, 4, dibBits, dibBmi, 0 /* ColorUse.RGB_COLORS */) === 4);
439
439
  readPx();
440
440
  t.check('dib top rows red (top-down)', RED, px(21, 21));
441
441
  t.check('dib bottom rows blue', BLUE, px(21, 23));
442
442
  t.check('dib outside untouched', WHITE, px(30, 21));
443
443
  clearWhite();
444
- t.checkTrue('StretchDIBits ok', StretchDIBits(mem, 20, 20, 40, 40, 0, 0, 4, 4, dibBits, dibBmi, 0, 13369376 /* RasterOp.SRCCOPY */) === 4);
444
+ t.checkTrue('StretchDIBits ok', StretchDIBits(mem, 20, 20, 40, 40, 0, 0, 4, 4, dibBits, dibBmi, 0 /* ColorUse.RGB_COLORS */, 13369376 /* RasterOp.SRCCOPY */) === 4);
445
445
  readPx();
446
446
  t.check('stretched top half red', RED, px(40, 30));
447
447
  t.check('stretched bottom half blue', BLUE, px(40, 50));
@@ -13,7 +13,7 @@ import {
13
13
  } from '../lib/windows/gdi32.js'
14
14
  import { AlphaBlend, TransparentBlt } from '../lib/windows/msimg32.js'
15
15
  import { BITMAPINFOHEADER, POINT, SIZE, TEXTMETRICW } from '../lib/windows/structs.js'
16
- import { ArcDirection, BackgroundMode, HatchStyle, PenStyle, PolyFillMode, RasterMode, RasterOp, StockObject, StretchBltMode, TextAlign, TextOutOptions } from '../constants'
16
+ import { ArcDirection, BackgroundMode, ColorUse, HatchStyle, PenStyle, PolyFillMode, RasterMode, RasterOp, StockObject, StretchBltMode, TextAlign, TextOutOptions } from '../constants'
17
17
 
18
18
  // 像素比较统一用 COLORREF 形(0x00BBGGRR)—— 与画图入参同形,读回直接对比。
19
19
  const RED = 0x000000ff
@@ -51,7 +51,7 @@ export const suite = {
51
51
  // 失败置哨兵(px() 返回 -1)—— 像素断言 FAIL 出可诊断值,不炸整个 suite。
52
52
  let pxOk = true
53
53
  const readPx = (): void => {
54
- pxOk = GetDIBits(mem, bmp, 0, H, pxB, bmi.ptr, 0) === H
54
+ pxOk = GetDIBits(mem, bmp, 0, H, pxB, bmi.ptr, ColorUse.RGB_COLORS) === H
55
55
  }
56
56
  const px = (x: number, y: number): number => {
57
57
  if (!pxOk) return -1
@@ -469,14 +469,14 @@ export const suite = {
469
469
  biPlanes: 1, biBitCount: 32, biSizeImage: 4 * 4 * 4,
470
470
  })
471
471
  t.checkTrue('SetDIBitsToDevice ok',
472
- SetDIBitsToDevice(mem, 20, 20, 4, 4, 0, 0, 0, 4, dibBits, dibBmi, 0) === 4)
472
+ SetDIBitsToDevice(mem, 20, 20, 4, 4, 0, 0, 0, 4, dibBits, dibBmi, ColorUse.RGB_COLORS) === 4)
473
473
  readPx()
474
474
  t.check('dib top rows red (top-down)', RED, px(21, 21))
475
475
  t.check('dib bottom rows blue', BLUE, px(21, 23))
476
476
  t.check('dib outside untouched', WHITE, px(30, 21))
477
477
  clearWhite()
478
478
  t.checkTrue('StretchDIBits ok',
479
- StretchDIBits(mem, 20, 20, 40, 40, 0, 0, 4, 4, dibBits, dibBmi, 0,
479
+ StretchDIBits(mem, 20, 20, 40, 40, 0, 0, 4, 4, dibBits, dibBmi, ColorUse.RGB_COLORS,
480
480
  RasterOp.SRCCOPY) === 4)
481
481
  readPx()
482
482
  t.check('stretched top half red', RED, px(40, 30))
@@ -1,31 +1,13 @@
1
1
  import * as std from 'std';
2
2
  import '../lib/polyfill.js';
3
- function loadWasmBytes(path) {
4
- const fp = std.open(path, 'rb');
5
- if (!fp)
6
- throw new Error('Cannot open: ' + path);
7
- fp.seek(0, 2);
8
- const size = fp.tell();
9
- fp.seek(0, 0);
10
- const buffer = new ArrayBuffer(size);
11
- fp.read(buffer, 0, size);
12
- fp.close();
13
- return buffer;
14
- }
15
- function setupMupdfModule(wasmPath) {
16
- const wasmBinary = loadWasmBytes(wasmPath);
17
- globalThis["$libmupdf_wasm_Module"] = {
18
- wasmBinary,
19
- locateFile: (p) => p
20
- };
21
- }
3
+ import { MUPDF_JS_URL, preloadMupdfWasm } from './mupdf-cdn.js';
22
4
  export const suite = {
23
5
  name: 'mupdf-render',
24
6
  run: async (t) => {
25
7
  try {
26
- setupMupdfModule('./vendor/mupdf-wasm/mupdf-wasm.wasm');
27
- const mupdf = await import('../vendor/mupdf-wasm/mupdf.js');
28
- const fp = std.open('./example.pdf', 'rb');
8
+ await preloadMupdfWasm();
9
+ const mupdf = await import(MUPDF_JS_URL);
10
+ const fp = std.open('./test/example.pdf', 'rb');
29
11
  if (!fp) {
30
12
  console.log('SKIP: no example.pdf');
31
13
  return;
@@ -1,36 +1,17 @@
1
1
  import * as std from 'std'
2
2
  import { Tester } from './test_helper.js'
3
3
  import '../lib/polyfill.js'
4
-
5
- function loadWasmBytes(path: string): ArrayBuffer {
6
- const fp = std.open(path, 'rb')
7
- if (!fp) throw new Error('Cannot open: ' + path)
8
- fp.seek(0, 2)
9
- const size = fp.tell()
10
- fp.seek(0, 0)
11
- const buffer = new ArrayBuffer(size)
12
- fp.read(buffer, 0, size)
13
- fp.close()
14
- return buffer
15
- }
16
-
17
- function setupMupdfModule(wasmPath: string) {
18
- const wasmBinary = loadWasmBytes(wasmPath)
19
- ;(globalThis as any)["$libmupdf_wasm_Module"] = {
20
- wasmBinary,
21
- locateFile: (p: string) => p
22
- }
23
- }
4
+ import { MUPDF_JS_URL, preloadMupdfWasm } from './mupdf-cdn.js'
24
5
 
25
6
  export const suite = {
26
7
  name: 'mupdf-render',
27
8
  run: async (t: Tester): Promise<void> => {
28
9
  try {
29
- setupMupdfModule('./vendor/mupdf-wasm/mupdf-wasm.wasm')
10
+ await preloadMupdfWasm()
30
11
 
31
- const mupdf: typeof import('../vendor/mupdf-wasm/mupdf.js') = await import('../vendor/mupdf-wasm/mupdf.js')
12
+ const mupdf: typeof import('@quickwinlabs/mupdf-wasm') = await import(MUPDF_JS_URL)
32
13
 
33
- const fp = std.open('./example.pdf', 'rb')
14
+ const fp = std.open('./test/example.pdf', 'rb')
34
15
  if (!fp) { console.log('SKIP: no example.pdf'); return }
35
16
  fp.seek(0, 2)
36
17
  const size = fp.tell()
@@ -1,32 +1,17 @@
1
1
  import '../lib/polyfill.js';
2
2
  import * as std from 'std';
3
- function loadWasmBytes(path) {
4
- const fp = std.open(path, 'rb');
5
- if (!fp)
6
- throw new Error('Cannot open: ' + path);
7
- fp.seek(0, 2);
8
- const size = fp.tell();
9
- fp.seek(0, 0);
10
- const buffer = new ArrayBuffer(size);
11
- fp.read(buffer, 0, size);
12
- fp.close();
13
- return buffer;
14
- }
3
+ import { MUPDF_JS_URL, preloadMupdfWasm } from './mupdf-cdn.js';
15
4
  export const suite = {
16
5
  name: 'mupdf-twice',
17
6
  run: async (t) => {
18
7
  try {
19
8
  console.log(' Loading WASM binary...');
20
- const wasmBinary = loadWasmBytes('./vendor/mupdf-wasm/mupdf-wasm.wasm');
9
+ const wasmBinary = await preloadMupdfWasm();
21
10
  console.log(' WASM binary loaded, size:', wasmBinary.byteLength);
22
- globalThis["$libmupdf_wasm_Module"] = {
23
- wasmBinary,
24
- locateFile: (p) => p
25
- };
26
11
  console.log(' Importing mupdf module...');
27
- const mupdf = await import('../vendor/mupdf-wasm/mupdf.js');
12
+ const mupdf = await import(MUPDF_JS_URL);
28
13
  console.log(' mupdf module imported ok');
29
- const fp = std.open('./example.pdf', 'rb');
14
+ const fp = std.open('./test/example.pdf', 'rb');
30
15
  if (!fp) {
31
16
  console.log('SKIP: no example.pdf');
32
17
  return;
@@ -1,37 +1,21 @@
1
1
  import '../lib/polyfill.js'
2
2
  import * as std from 'std'
3
3
  import { Tester } from './test_helper.js'
4
-
5
- function loadWasmBytes(path: string): ArrayBuffer {
6
- const fp = std.open(path, 'rb')
7
- if (!fp) throw new Error('Cannot open: ' + path)
8
- fp.seek(0, 2)
9
- const size = fp.tell()
10
- fp.seek(0, 0)
11
- const buffer = new ArrayBuffer(size)
12
- fp.read(buffer, 0, size)
13
- fp.close()
14
- return buffer
15
- }
4
+ import { MUPDF_JS_URL, preloadMupdfWasm } from './mupdf-cdn.js'
16
5
 
17
6
  export const suite = {
18
7
  name: 'mupdf-twice',
19
8
  run: async (t: Tester): Promise<void> => {
20
9
  try {
21
10
  console.log(' Loading WASM binary...')
22
- const wasmBinary = loadWasmBytes('./vendor/mupdf-wasm/mupdf-wasm.wasm')
11
+ const wasmBinary = await preloadMupdfWasm()
23
12
  console.log(' WASM binary loaded, size:', wasmBinary.byteLength)
24
13
 
25
- ;(globalThis as any)["$libmupdf_wasm_Module"] = {
26
- wasmBinary,
27
- locateFile: (p: string) => p
28
- }
29
-
30
14
  console.log(' Importing mupdf module...')
31
- const mupdf: typeof import('../vendor/mupdf-wasm/mupdf.js') = await import('../vendor/mupdf-wasm/mupdf.js')
15
+ const mupdf: typeof import('@quickwinlabs/mupdf-wasm') = await import(MUPDF_JS_URL)
32
16
  console.log(' mupdf module imported ok')
33
17
 
34
- const fp = std.open('./example.pdf', 'rb')
18
+ const fp = std.open('./test/example.pdf', 'rb')
35
19
  if (!fp) { console.log('SKIP: no example.pdf'); return }
36
20
  fp.seek(0, 2)
37
21
  const size = fp.tell()
@@ -1,9 +1,15 @@
1
- import { readWasmFile } from './test_helper.js';
1
+ import { fetchMupdfWasm } from './mupdf-cdn.js';
2
2
  export const suite = {
3
- name: 'vendor/mupdf-wasm',
4
- run: (t) => {
3
+ name: 'mupdf-wasm',
4
+ run: async (t) => {
5
5
  t.section('validate binary');
6
- const buf = readWasmFile('../vendor/mupdf-wasm/mupdf-wasm.wasm');
6
+ let buf = null;
7
+ try {
8
+ buf = await fetchMupdfWasm();
9
+ }
10
+ catch (e) {
11
+ console.log('mupdf wasm fetch failed: ' + String(e));
12
+ }
7
13
  if (!buf) {
8
14
  t.fail++;
9
15
  return;
@@ -1,10 +1,12 @@
1
- import { Tester, readWasmFile } from './test_helper.js'
1
+ import { Tester } from './test_helper.js'
2
+ import { fetchMupdfWasm } from './mupdf-cdn.js'
2
3
 
3
4
  export const suite = {
4
- name: 'vendor/mupdf-wasm',
5
- run: (t: Tester) => {
5
+ name: 'mupdf-wasm',
6
+ run: async (t: Tester) => {
6
7
  t.section('validate binary')
7
- const buf = readWasmFile('../vendor/mupdf-wasm/mupdf-wasm.wasm')
8
+ let buf: ArrayBuffer | null = null
9
+ try { buf = await fetchMupdfWasm() } catch (e) { console.log('mupdf wasm fetch failed: ' + String(e)) }
8
10
  if (!buf) { t.fail++; return }
9
11
  t.check('file size > 0', true, buf.byteLength > 0)
10
12
  const header = new Uint8Array(buf)