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.
Files changed (97) hide show
  1. package/README.en.md +1 -2
  2. package/README.md +1 -2
  3. package/constants.ts +15 -0
  4. package/enum_map.d.ts +3 -0
  5. package/examples/PdfCanvas.js +1 -1
  6. package/examples/PdfCanvas.tsx +2 -2
  7. package/examples/execd/main.js +334 -188
  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/react-qw/components/DateTimePicker.js +6 -7
  34. package/lib/react-qw/components/Input.js +5 -1
  35. package/lib/react-qw/components/ListView.js +8 -6
  36. package/lib/react-qw/components/ScrollView.js +28 -22
  37. package/lib/react-qw/index.js +7 -5
  38. package/lib/react-qw/index.ts +5 -5
  39. package/lib/react-qw/props.js +3 -3
  40. package/lib/react-qw/props.ts +3 -3
  41. package/lib/react-qw/reconciler.js +12 -17
  42. package/lib/react-qw/reconciler.ts +12 -16
  43. package/lib/text-measure.js +9 -6
  44. package/lib/text-measure.ts +9 -6
  45. package/lib/windows/comctl32.js +3 -2
  46. package/lib/windows/comctl32.ts +3 -2
  47. package/lib/windows/gdi32.js +28 -6
  48. package/lib/windows/gdi32.ts +30 -6
  49. package/lib/windows/kernel32.js +9 -0
  50. package/lib/windows/kernel32.ts +9 -0
  51. package/lib/windows/shell32.js +11 -2
  52. package/lib/windows/shell32.ts +13 -2
  53. package/lib/windows/structs.js +51 -0
  54. package/lib/windows/structs.ts +55 -0
  55. package/lib/windows/user32.js +6 -5
  56. package/lib/windows/user32.ts +6 -5
  57. package/main.js +13 -5
  58. package/package.json +2 -2
  59. package/quickwin.d.ts +13 -115
  60. package/qwin-nowasm-x86.exe +0 -0
  61. package/qwin-nowasm.exe +0 -0
  62. package/qwin-x86.exe +0 -0
  63. package/qwin.exe +0 -0
  64. package/test/bench_ffi_callraw.js +3 -4
  65. package/test/bench_ffi_callraw.ts +3 -4
  66. package/test/debug/mupdf_debug.js +4 -22
  67. package/test/debug/wasm_export_debug.js +2 -12
  68. package/test/debug/wasm_names.js +3 -13
  69. package/test/example.pdf +0 -0
  70. package/test/mupdf-cdn.js +40 -0
  71. package/test/mupdf-cdn.ts +43 -0
  72. package/test/test_ffi.js +8 -9
  73. package/test/test_ffi.ts +8 -9
  74. package/test/test_ffi_abi.js +6 -6
  75. package/test/test_ffi_abi.ts +8 -8
  76. package/test/test_ffi_windows.js +82 -11
  77. package/test/test_ffi_windows.ts +85 -11
  78. package/test/test_gdi_draw.js +3 -3
  79. package/test/test_gdi_draw.ts +4 -4
  80. package/test/test_mupdf_render.js +4 -22
  81. package/test/test_mupdf_render.ts +4 -23
  82. package/test/test_mupdf_twice.js +4 -19
  83. package/test/test_mupdf_twice.ts +4 -20
  84. package/test/test_mupdf_wasm.js +10 -4
  85. package/test/test_mupdf_wasm.ts +6 -4
  86. package/test/test_react_complex.js +316 -140
  87. package/test/test_react_counter.js +295 -133
  88. package/test/test_react_flex.js +297 -138
  89. package/test/test_react_hidden.js +296 -134
  90. package/test/test_react_listview_link.js +288 -131
  91. package/test/test_react_notify.js +290 -133
  92. package/test/test_react_render.js +295 -133
  93. package/vendor/mupdf-wasm/mupdf-wasm.d.ts +0 -571
  94. package/vendor/mupdf-wasm/mupdf-wasm.js +0 -2749
  95. package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
  96. package/vendor/mupdf-wasm/mupdf.d.ts +0 -939
  97. package/vendor/mupdf-wasm/mupdf.js +0 -3317
@@ -1,25 +1,7 @@
1
1
  import * as std from 'std';
2
- function loadWasmBytes(path) {
3
- const fp = std.open(path, 'rb');
4
- if (!fp)
5
- throw new Error('Cannot open: ' + path);
6
- fp.seek(0, 2);
7
- const size = fp.tell();
8
- fp.seek(0, 0);
9
- const buffer = new ArrayBuffer(size);
10
- fp.read(buffer, 0, size);
11
- fp.close();
12
- return buffer;
13
- }
14
- function setupMupdfModule(wasmPath) {
15
- const wasmBinary = loadWasmBytes(wasmPath);
16
- globalThis["$libmupdf_wasm_Module"] = {
17
- wasmBinary,
18
- locateFile: (p) => p
19
- };
20
- }
21
- setupMupdfModule('./vendor/mupdf-wasm/mupdf-wasm.wasm');
22
- await import('../../vendor/mupdf-wasm/mupdf.js');
2
+ import { MUPDF_JS_URL, preloadMupdfWasm } from '../mupdf-cdn.js';
3
+ await preloadMupdfWasm();
4
+ await import(MUPDF_JS_URL);
23
5
  // Debug the instance exports
24
6
  const inst = globalThis["$wasm_inst"];
25
7
  const mod = globalThis["$wasm_mod"];
@@ -39,7 +21,7 @@ console.log('Sf index:', sf_idx, 'Tf index:', tf_idx);
39
21
  console.log('instance exports Sf:', typeof inst.exports['Sf']);
40
22
  console.log('instance exports Tf:', typeof inst.exports['Tf']);
41
23
  // Load a simple WASM to verify bridge works
42
- const fp = std.open('./example.pdf', 'rb');
24
+ const fp = std.open('./test/example.pdf', 'rb');
43
25
  if (!fp) {
44
26
  console.log('cannot open example.pdf');
45
27
  std.exit(1);
@@ -1,15 +1,5 @@
1
- import * as std from 'std';
2
- const fp = std.open('./vendor/mupdf-wasm/mupdf-wasm.wasm', 'rb');
3
- if (!fp) {
4
- console.log('cannot open vendor/mupdf-wasm/mupdf-wasm.wasm');
5
- std.exit(1);
6
- }
7
- fp.seek(0, 2);
8
- const size = fp.tell();
9
- fp.seek(0, 0);
10
- const buf = new ArrayBuffer(size);
11
- fp.read(buf, 0, size);
12
- fp.close();
1
+ import { fetchMupdfWasm } from '../mupdf-cdn.js';
2
+ const buf = await fetchMupdfWasm();
13
3
  const module = new WebAssembly.Module(buf);
14
4
  const exports = WebAssembly.Module.exports(module);
15
5
  const imports = WebAssembly.Module.imports(module);
@@ -1,16 +1,6 @@
1
- import * as std from 'std';
2
- // Load the vendor/mupdf-wasm binary using our pre-load mechanism
3
- const fp = std.open('./vendor/mupdf-wasm/mupdf-wasm.wasm', 'rb');
4
- if (!fp) {
5
- console.log('cannot open vendor/mupdf-wasm/mupdf-wasm.wasm');
6
- std.exit(1);
7
- }
8
- fp.seek(0, 2);
9
- const size = fp.tell();
10
- fp.seek(0, 0);
11
- const buf = new ArrayBuffer(size);
12
- fp.read(buf, 0, size);
13
- fp.close();
1
+ import { fetchMupdfWasm } from '../mupdf-cdn.js';
2
+ // Fetch the mupdf-wasm binary over HTTPS (process-wide HTTP cache applies)
3
+ const buf = await fetchMupdfWasm();
14
4
  // Check exports for Tf
15
5
  const module = new WebAssembly.Module(buf);
16
6
  const exports = WebAssembly.Module.exports(module);
Binary file
@@ -0,0 +1,40 @@
1
+ // ── mupdf-wasm CDN loading ──
2
+ //
3
+ // The runtime loads mupdf from unpkg; the npm package itself only ships
4
+ // types (devDependency @quickwinlabs/mupdf-wasm resolved through the
5
+ // package's exports types mapping — no local vendor copy involved).
6
+ // MUPDF_VERSION must stay in sync with the devDependency in package.json
7
+ // when bumping.
8
+ //
9
+ // Importing this module wires up the HTTPS stack (certs + fetch): the
10
+ // wasm bytes travel through the process-wide HTTP cache next to the
11
+ // executable, so the first fetch is the only network hit.
12
+ import '../lib/certs.js';
13
+ import '../lib/fetch.js';
14
+ export const MUPDF_VERSION = '1.28.0';
15
+ const BASE = `https://unpkg.com/@quickwinlabs/mupdf-wasm@${MUPDF_VERSION}`;
16
+ export const MUPDF_JS_URL = `${BASE}/mupdf.js`;
17
+ export const MUPDF_WASM_URL = `${BASE}/mupdf-wasm.wasm`;
18
+ /** Fetch the wasm binary over HTTPS (HTTP-cached, no filesystem involved). */
19
+ export async function fetchMupdfWasm() {
20
+ const resp = await fetch(MUPDF_WASM_URL);
21
+ if (resp.status !== 200)
22
+ throw new Error(`mupdf wasm fetch failed: HTTP ${resp.status}`);
23
+ const wasmBinary = await resp.arrayBuffer();
24
+ if (wasmBinary.byteLength < 1_000_000)
25
+ throw new Error(`mupdf wasm truncated: ${wasmBinary.byteLength} bytes`);
26
+ return wasmBinary;
27
+ }
28
+ /**
29
+ * Fetch the wasm binary and pre-wire it as $libmupdf_wasm_Module so the
30
+ * mupdf.js module factory instantiates from memory (same injection the
31
+ * local-vendor loading used).
32
+ */
33
+ export async function preloadMupdfWasm() {
34
+ const wasmBinary = await fetchMupdfWasm();
35
+ globalThis.$libmupdf_wasm_Module = {
36
+ wasmBinary,
37
+ locateFile: (p) => p,
38
+ };
39
+ return wasmBinary;
40
+ }
@@ -0,0 +1,43 @@
1
+ // ── mupdf-wasm CDN loading ──
2
+ //
3
+ // The runtime loads mupdf from unpkg; the npm package itself only ships
4
+ // types (devDependency @quickwinlabs/mupdf-wasm resolved through the
5
+ // package's exports types mapping — no local vendor copy involved).
6
+ // MUPDF_VERSION must stay in sync with the devDependency in package.json
7
+ // when bumping.
8
+ //
9
+ // Importing this module wires up the HTTPS stack (certs + fetch): the
10
+ // wasm bytes travel through the process-wide HTTP cache next to the
11
+ // executable, so the first fetch is the only network hit.
12
+
13
+ import '../lib/certs.js'
14
+ import '../lib/fetch.js'
15
+
16
+ export const MUPDF_VERSION = '1.28.0'
17
+ const BASE = `https://unpkg.com/@quickwinlabs/mupdf-wasm@${MUPDF_VERSION}`
18
+
19
+ export const MUPDF_JS_URL = `${BASE}/mupdf.js`
20
+ export const MUPDF_WASM_URL = `${BASE}/mupdf-wasm.wasm`
21
+
22
+ /** Fetch the wasm binary over HTTPS (HTTP-cached, no filesystem involved). */
23
+ export async function fetchMupdfWasm(): Promise<ArrayBuffer> {
24
+ const resp = await fetch(MUPDF_WASM_URL)
25
+ if (resp.status !== 200) throw new Error(`mupdf wasm fetch failed: HTTP ${resp.status}`)
26
+ const wasmBinary = await resp.arrayBuffer()
27
+ if (wasmBinary.byteLength < 1_000_000) throw new Error(`mupdf wasm truncated: ${wasmBinary.byteLength} bytes`)
28
+ return wasmBinary
29
+ }
30
+
31
+ /**
32
+ * Fetch the wasm binary and pre-wire it as $libmupdf_wasm_Module so the
33
+ * mupdf.js module factory instantiates from memory (same injection the
34
+ * local-vendor loading used).
35
+ */
36
+ export async function preloadMupdfWasm(): Promise<ArrayBuffer> {
37
+ const wasmBinary = await fetchMupdfWasm()
38
+ ;(globalThis as any).$libmupdf_wasm_Module = {
39
+ wasmBinary,
40
+ locateFile: (p: string) => p,
41
+ }
42
+ return wasmBinary
43
+ }
package/test/test_ffi.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import * as std from 'std';
2
- import * as win from 'win';
3
2
  import * as ffi from 'ffi';
4
3
  import * as os from 'os';
5
4
  import { bind, bindLib } from '../lib/ffi/bind.js';
@@ -72,12 +71,12 @@ export const suite = {
72
71
  name: 'ffi',
73
72
  run: (t) => {
74
73
  t.section('EnumPrinters');
75
- const hWinspool = win.LoadLibrary('winspool.drv');
76
- t.checkTrue('LoadLibrary("winspool.drv") succeeds', hWinspool !== null);
74
+ const hWinspool = ffi.LoadLibrary('winspool.drv');
75
+ t.checkTrue('LoadLibrary("winspool.drv") succeeds', !!hWinspool);
77
76
  if (!hWinspool)
78
77
  return;
79
- const enumPrinters = win.GetProcAddress(hWinspool, 'EnumPrintersW');
80
- t.checkTrue('GetProcAddress("EnumPrintersW") succeeds', enumPrinters !== null);
78
+ const enumPrinters = ffi.GetProcAddress(hWinspool, 'EnumPrintersW');
79
+ t.checkTrue('GetProcAddress("EnumPrintersW") succeeds', !!enumPrinters);
81
80
  if (!enumPrinters)
82
81
  return;
83
82
  const { EnumPrintersW: EnumPrinters } = bindLib('winspool.drv', {
@@ -141,11 +140,11 @@ export const suite = {
141
140
  std.printf(' no printers installed (skip)\n');
142
141
  }
143
142
  t.section('PTR accepts raw number handles (XP 32-bit fix)');
144
- const hUser32 = win.LoadLibrary('user32.dll');
145
- t.checkTrue('LoadLibrary("user32.dll") succeeds', hUser32 !== null);
143
+ const hUser32 = ffi.LoadLibrary('user32.dll');
144
+ t.checkTrue('LoadLibrary("user32.dll") succeeds', !!hUser32);
146
145
  if (hUser32) {
147
- const getDC = win.GetProcAddress(hUser32, 'GetDC');
148
- t.checkTrue('GetProcAddress("GetDC") succeeds', getDC !== null);
146
+ const getDC = ffi.GetProcAddress(hUser32, 'GetDC');
147
+ t.checkTrue('GetProcAddress("GetDC") succeeds', !!getDC);
149
148
  if (getDC) {
150
149
  const GetDC = bind('user32.dll', 'GetDC', '<>ptr -> <>ptr');
151
150
  const screenDc = GetDC(0);
package/test/test_ffi.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import * as std from 'std'
2
- import * as win from 'win'
3
2
  import * as ffi from 'ffi'
4
3
  import * as os from 'os'
5
4
  import { bind, bindLib } from '../lib/ffi/bind.js'
@@ -75,12 +74,12 @@ export const suite = {
75
74
  name: 'ffi',
76
75
  run: (t: Tester) => {
77
76
  t.section('EnumPrinters')
78
- const hWinspool = win.LoadLibrary('winspool.drv')
79
- t.checkTrue('LoadLibrary("winspool.drv") succeeds', hWinspool !== null)
77
+ const hWinspool = ffi.LoadLibrary('winspool.drv')
78
+ t.checkTrue('LoadLibrary("winspool.drv") succeeds', !!hWinspool)
80
79
  if (!hWinspool) return
81
80
 
82
- const enumPrinters = win.GetProcAddress(hWinspool, 'EnumPrintersW')
83
- t.checkTrue('GetProcAddress("EnumPrintersW") succeeds', enumPrinters !== null)
81
+ const enumPrinters = ffi.GetProcAddress(hWinspool, 'EnumPrintersW')
82
+ t.checkTrue('GetProcAddress("EnumPrintersW") succeeds', !!enumPrinters)
84
83
  if (!enumPrinters) return
85
84
 
86
85
  const { EnumPrintersW: EnumPrinters } = bindLib('winspool.drv', {
@@ -154,11 +153,11 @@ export const suite = {
154
153
  }
155
154
 
156
155
  t.section('PTR accepts raw number handles (XP 32-bit fix)')
157
- const hUser32 = win.LoadLibrary('user32.dll')
158
- t.checkTrue('LoadLibrary("user32.dll") succeeds', hUser32 !== null)
156
+ const hUser32 = ffi.LoadLibrary('user32.dll')
157
+ t.checkTrue('LoadLibrary("user32.dll") succeeds', !!hUser32)
159
158
  if (hUser32) {
160
- const getDC = win.GetProcAddress(hUser32, 'GetDC')
161
- t.checkTrue('GetProcAddress("GetDC") succeeds', getDC !== null)
159
+ const getDC = ffi.GetProcAddress(hUser32, 'GetDC')
160
+ t.checkTrue('GetProcAddress("GetDC") succeeds', !!getDC)
162
161
  if (getDC) {
163
162
  const GetDC = bind('user32.dll', 'GetDC', '<>ptr -> <>ptr')
164
163
  const screenDc = GetDC(0)
@@ -1,5 +1,5 @@
1
1
  import * as std from 'std';
2
- import * as win from 'win';
2
+ import * as ffi from 'ffi';
3
3
  import * as os from 'os';
4
4
  import { bind } from '../lib/ffi/bind.js';
5
5
  import { struct } from '../lib/ffi/struct.js';
@@ -29,15 +29,15 @@ function readAscii(buf, len) {
29
29
  return s;
30
30
  }
31
31
  function has(dll, name) {
32
- return !!dll && !!win.GetProcAddress(dll, name);
32
+ return !!dll && !!ffi.GetProcAddress(dll, name);
33
33
  }
34
34
  export const suite = {
35
35
  name: 'ffi-abi',
36
36
  run: (t) => {
37
- const k32 = win.LoadLibrary('kernel32.dll');
38
- const usr = win.LoadLibrary('user32.dll');
39
- const gdi = win.LoadLibrary('gdi32.dll');
40
- const crt = win.LoadLibrary('msvcrt.dll');
37
+ const k32 = ffi.LoadLibrary('kernel32.dll');
38
+ const usr = ffi.LoadLibrary('user32.dll');
39
+ const gdi = ffi.LoadLibrary('gdi32.dll');
40
+ const crt = ffi.LoadLibrary('msvcrt.dll');
41
41
  t.section('param count 0 ( -> u32)');
42
42
  if (has(k32, 'GetTickCount')) {
43
43
  const getTickCount = bind('kernel32.dll', 'GetTickCount', ' -> u32');
@@ -1,9 +1,9 @@
1
1
  import * as std from 'std'
2
- import * as win from 'win'
2
+ import * as ffi from 'ffi'
3
3
  import * as os from 'os'
4
4
  import { bind } from '../lib/ffi/bind.js'
5
5
  import { struct } from '../lib/ffi/struct.js'
6
- import { NULL, PtrArrayBuffer } from '../lib/ffi/ctype.js'
6
+ import { NULL, PtrArrayBuffer, type MaybePtr } from '../lib/ffi/ctype.js'
7
7
  import { POINT, RECT } from '../lib/windows/structs.js'
8
8
  import { Tester } from './test_helper.js'
9
9
 
@@ -31,17 +31,17 @@ function readAscii(buf: ArrayBuffer, len: number): string {
31
31
  return s
32
32
  }
33
33
 
34
- function has(dll: win.HMODULE | null, name: string): boolean {
35
- return !!dll && !!win.GetProcAddress(dll, name)
34
+ function has(dll: MaybePtr<'HMODULE'>, name: string): boolean {
35
+ return !!dll && !!ffi.GetProcAddress(dll, name)
36
36
  }
37
37
 
38
38
  export const suite = {
39
39
  name: 'ffi-abi',
40
40
  run: (t: Tester) => {
41
- const k32 = win.LoadLibrary('kernel32.dll')
42
- const usr = win.LoadLibrary('user32.dll')
43
- const gdi = win.LoadLibrary('gdi32.dll')
44
- const crt = win.LoadLibrary('msvcrt.dll')
41
+ const k32 = ffi.LoadLibrary('kernel32.dll')
42
+ const usr = ffi.LoadLibrary('user32.dll')
43
+ const gdi = ffi.LoadLibrary('gdi32.dll')
44
+ const crt = ffi.LoadLibrary('msvcrt.dll')
45
45
 
46
46
  t.section('param count 0 ( -> u32)')
47
47
  if (has(k32, 'GetTickCount')) {
@@ -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))