quickwin 3.0.0 → 3.0.2

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 (253) hide show
  1. package/README.en.md +3 -0
  2. package/README.md +2 -0
  3. package/lib/abort.d.ts +21 -0
  4. package/lib/assert.d.ts +2 -0
  5. package/lib/bitmap.d.ts +7 -0
  6. package/lib/cache-utils.d.ts +6 -0
  7. package/lib/certs.d.ts +1 -0
  8. package/lib/dpi.d.ts +9 -0
  9. package/lib/dumpRects.d.ts +2 -0
  10. package/lib/exec-log.d.ts +23 -0
  11. package/lib/fetch.d.ts +4 -0
  12. package/lib/ffi/bind.d.ts +70 -0
  13. package/lib/ffi/ctype.d.ts +117 -0
  14. package/lib/ffi/index.d.ts +2 -0
  15. package/lib/ffi/struct.d.ts +174 -0
  16. package/lib/http-common.d.ts +143 -0
  17. package/lib/http-server.d.ts +59 -0
  18. package/lib/polyfill.d.ts +22 -0
  19. package/lib/react-qw/components/Button.d.ts +9 -0
  20. package/lib/react-qw/components/CheckBox.d.ts +11 -0
  21. package/lib/react-qw/components/ComboBox.d.ts +11 -0
  22. package/lib/react-qw/components/DateTimePicker.d.ts +15 -0
  23. package/lib/react-qw/components/Input.d.ts +15 -0
  24. package/lib/react-qw/components/Link.d.ts +12 -0
  25. package/lib/react-qw/components/ListBox.d.ts +13 -0
  26. package/lib/react-qw/components/ListView.d.ts +37 -0
  27. package/lib/react-qw/components/PathPicker.d.ts +23 -0
  28. package/lib/react-qw/components/ProgressBar.d.ts +9 -0
  29. package/lib/react-qw/components/RadioButton.d.ts +11 -0
  30. package/lib/react-qw/components/ScrollView.d.ts +9 -0
  31. package/lib/react-qw/components/Slider.d.ts +12 -0
  32. package/lib/react-qw/components/Tab.d.ts +14 -0
  33. package/lib/react-qw/components/Tooltip.d.ts +7 -0
  34. package/lib/react-qw/components/TreeView.d.ts +18 -0
  35. package/lib/react-qw/components/index.d.ts +16 -0
  36. package/lib/react-qw/index.d.ts +24 -0
  37. package/lib/react-qw/layout.d.ts +29 -0
  38. package/lib/react-qw/nmhdr.d.ts +1 -0
  39. package/lib/react-qw/props.d.ts +2 -0
  40. package/lib/react-qw/reconciler.d.ts +21 -0
  41. package/lib/stream.d.ts +20 -0
  42. package/lib/text-codec.d.ts +39 -0
  43. package/lib/text-measure.d.ts +14 -0
  44. package/lib/url.d.ts +54 -0
  45. package/lib/websocket.d.ts +73 -0
  46. package/lib/windows/{advapi32.ts → advapi32.d.ts} +6 -21
  47. package/lib/windows/comctl32.d.ts +14 -0
  48. package/lib/windows/comdlg32.d.ts +4 -0
  49. package/lib/windows/{gdi32.ts → gdi32.d.ts} +161 -98
  50. package/lib/windows/{kernel32.ts → kernel32.d.ts} +138 -115
  51. package/lib/windows/{msimg32.ts → msimg32.d.ts} +11 -14
  52. package/lib/windows/ntdll.d.ts +5 -0
  53. package/lib/windows/ole32.d.ts +3 -0
  54. package/lib/windows/{psapi.ts → psapi.d.ts} +9 -18
  55. package/lib/windows/{shell32.ts → shell32.d.ts} +25 -24
  56. package/lib/windows/structs.d.ts +3141 -0
  57. package/lib/windows/{user32.ts → user32.d.ts} +196 -193
  58. package/lib/windows/version.d.ts +27 -0
  59. package/lib/windows/{winspool.ts → winspool.d.ts} +18 -32
  60. package/package.json +15 -8
  61. package/qwin-nowasm-x86.exe +0 -0
  62. package/qwin-nowasm.exe +0 -0
  63. package/qwin-x86.exe +0 -0
  64. package/qwin.exe +0 -0
  65. package/examples/PdfCanvas.js +0 -38
  66. package/examples/PdfCanvas.tsx +0 -54
  67. package/examples/execd/main.js +0 -23379
  68. package/examples/execd/tray.js +0 -192
  69. package/examples/execd/worker.js +0 -122
  70. package/examples/pdf_preview2.js +0 -398
  71. package/examples/pdf_preview2.ts +0 -353
  72. package/examples/pdf_viewer.js +0 -22515
  73. package/examples/pdf_viewer.tsx +0 -228
  74. package/examples/test_gallery.js +0 -18692
  75. package/examples/test_gallery.tsx +0 -291
  76. package/examples/test_react_lazy.js +0 -18560
  77. package/examples/test_react_lazy.tsx +0 -65
  78. package/examples/test_react_listbox.js +0 -18562
  79. package/examples/test_react_listbox.tsx +0 -73
  80. package/examples/test_root_window.js +0 -18532
  81. package/examples/test_root_window.tsx +0 -41
  82. package/examples/test_tab.js +0 -18563
  83. package/examples/test_tab.tsx +0 -77
  84. package/examples/tray_demo.js +0 -86
  85. package/examples/tray_demo.tsx +0 -89
  86. package/lib/abort.ts +0 -69
  87. package/lib/assert.ts +0 -7
  88. package/lib/bitmap.ts +0 -39
  89. package/lib/cache-utils.ts +0 -40
  90. package/lib/certs.ts +0 -258
  91. package/lib/dpi.ts +0 -31
  92. package/lib/dumpRects.ts +0 -34
  93. package/lib/exec-log.ts +0 -30
  94. package/lib/fetch.ts +0 -589
  95. package/lib/ffi/bind.ts +0 -540
  96. package/lib/ffi/ctype.ts +0 -371
  97. package/lib/ffi/index.ts +0 -6
  98. package/lib/ffi/struct.ts +0 -780
  99. package/lib/http-common.ts +0 -441
  100. package/lib/http-server.ts +0 -604
  101. package/lib/polyfill.ts +0 -171
  102. package/lib/react-qw/components/index.ts +0 -17
  103. package/lib/react-qw/index.ts +0 -96
  104. package/lib/react-qw/layout.ts +0 -111
  105. package/lib/react-qw/nmhdr.ts +0 -7
  106. package/lib/react-qw/props.ts +0 -41
  107. package/lib/react-qw/reconciler.ts +0 -379
  108. package/lib/stream.ts +0 -22
  109. package/lib/text-codec.ts +0 -171
  110. package/lib/text-measure.ts +0 -44
  111. package/lib/url.ts +0 -201
  112. package/lib/websocket.ts +0 -600
  113. package/lib/windows/comctl32.ts +0 -20
  114. package/lib/windows/comdlg32.ts +0 -10
  115. package/lib/windows/ntdll.ts +0 -11
  116. package/lib/windows/ole32.ts +0 -9
  117. package/lib/windows/structs.ts +0 -807
  118. package/lib/windows/version.ts +0 -35
  119. package/test/add.wasm +0 -0
  120. package/test/bench_ffi_callraw.js +0 -235
  121. package/test/bench_ffi_callraw.ts +0 -235
  122. package/test/bench_ffi_overhead.js +0 -139
  123. package/test/bench_ffi_overhead.ts +0 -141
  124. package/test/bench_sendmessage.js +0 -104
  125. package/test/bench_sendmessage.ts +0 -100
  126. package/test/bench_sendmessage_nonnull.js +0 -187
  127. package/test/bench_sendmessage_nonnull.ts +0 -188
  128. package/test/complex.wasm +0 -0
  129. package/test/complex_imports.wasm +0 -0
  130. package/test/debug/http_server_e2e.js +0 -31
  131. package/test/debug/minwasm.js +0 -52
  132. package/test/debug/mupdf_debug.js +0 -86
  133. package/test/debug/wasm.js +0 -122
  134. package/test/debug/wasm_export_debug.js +0 -54
  135. package/test/debug/wasm_names.js +0 -36
  136. package/test/example.pdf +0 -0
  137. package/test/global_imports.wasm +0 -0
  138. package/test/import_func.wasm +0 -0
  139. package/test/imports.wasm +0 -0
  140. package/test/mupdf-cdn.js +0 -40
  141. package/test/mupdf-cdn.ts +0 -43
  142. package/test/run.js +0 -111
  143. package/test/run.ts +0 -116
  144. package/test/sjlj.wasm +0 -0
  145. package/test/test_basic.js +0 -7
  146. package/test/test_basic.ts +0 -9
  147. package/test/test_basic_fetch.js +0 -63
  148. package/test/test_basic_fetch.ts +0 -59
  149. package/test/test_brotli.js +0 -48
  150. package/test/test_brotli.ts +0 -52
  151. package/test/test_fetch_cache.js +0 -169
  152. package/test/test_fetch_cache.ts +0 -187
  153. package/test/test_ffi.js +0 -157
  154. package/test/test_ffi.ts +0 -170
  155. package/test/test_ffi_abi.js +0 -290
  156. package/test/test_ffi_abi.ts +0 -274
  157. package/test/test_ffi_array.js +0 -116
  158. package/test/test_ffi_array.ts +0 -141
  159. package/test/test_ffi_bind.js +0 -656
  160. package/test/test_ffi_bind.ts +0 -670
  161. package/test/test_ffi_struct.js +0 -779
  162. package/test/test_ffi_struct.ts +0 -828
  163. package/test/test_ffi_struct_layout.js +0 -495
  164. package/test/test_ffi_struct_layout.ts +0 -543
  165. package/test/test_ffi_user32.js +0 -103
  166. package/test/test_ffi_user32.ts +0 -113
  167. package/test/test_ffi_windows.js +0 -172
  168. package/test/test_ffi_windows.ts +0 -187
  169. package/test/test_files.js +0 -102
  170. package/test/test_files.ts +0 -121
  171. package/test/test_frame_encoding.js +0 -128
  172. package/test/test_frame_encoding.ts +0 -132
  173. package/test/test_gdi_draw.js +0 -681
  174. package/test/test_gdi_draw.ts +0 -725
  175. package/test/test_helper.js +0 -91
  176. package/test/test_helper.ts +0 -88
  177. package/test/test_http_import.js +0 -96
  178. package/test/test_http_import.ts +0 -93
  179. package/test/test_http_server.js +0 -214
  180. package/test/test_http_server.ts +0 -211
  181. package/test/test_import_dir.js +0 -4
  182. package/test/test_import_dir.ts +0 -5
  183. package/test/test_ipv6.js +0 -134
  184. package/test/test_ipv6.ts +0 -130
  185. package/test/test_jsx.js +0 -1314
  186. package/test/test_meta_url.js +0 -3
  187. package/test/test_meta_url.ts +0 -4
  188. package/test/test_mupdf_render.js +0 -51
  189. package/test/test_mupdf_render.ts +0 -55
  190. package/test/test_mupdf_twice.js +0 -62
  191. package/test/test_mupdf_twice.ts +0 -65
  192. package/test/test_mupdf_wasm.js +0 -39
  193. package/test/test_mupdf_wasm.ts +0 -32
  194. package/test/test_net_event.js +0 -57
  195. package/test/test_net_event.ts +0 -56
  196. package/test/test_net_fetch.js +0 -384
  197. package/test/test_net_fetch.ts +0 -373
  198. package/test/test_net_websocket.js +0 -180
  199. package/test/test_net_websocket.ts +0 -164
  200. package/test/test_polyfill.js +0 -144
  201. package/test/test_polyfill.ts +0 -159
  202. package/test/test_printer.js +0 -137
  203. package/test/test_printer.ts +0 -148
  204. package/test/test_proc.js +0 -141
  205. package/test/test_proc.ts +0 -163
  206. package/test/test_react_complex.js +0 -18678
  207. package/test/test_react_counter.js +0 -18555
  208. package/test/test_react_flex.js +0 -18871
  209. package/test/test_react_hidden.js +0 -18560
  210. package/test/test_react_listview_link.js +0 -18590
  211. package/test/test_react_notify.js +0 -18797
  212. package/test/test_react_render.js +0 -18550
  213. package/test/test_registry.js +0 -74
  214. package/test/test_registry.ts +0 -95
  215. package/test/test_sysinfo.js +0 -122
  216. package/test/test_sysinfo.ts +0 -141
  217. package/test/test_url.js +0 -173
  218. package/test/test_url.ts +0 -183
  219. package/test/test_wasm_basic.js +0 -85
  220. package/test/test_wasm_basic.ts +0 -73
  221. package/test/test_wasm_import_global.js +0 -41
  222. package/test/test_wasm_import_global.ts +0 -45
  223. package/test/test_wasm_sjlj.js +0 -145
  224. package/test/test_wasm_sjlj.ts +0 -125
  225. package/test/test_wasm_types.js +0 -97
  226. package/test/test_wasm_types.ts +0 -124
  227. package/test/test_wolfssl_read.js +0 -203
  228. package/test/test_wolfssl_read.ts +0 -184
  229. package/test/test_worker.js +0 -21
  230. package/test/test_worker.ts +0 -24
  231. package/test/test_worker_concurrent.js +0 -45
  232. package/test/test_worker_concurrent.ts +0 -56
  233. package/test/test_worker_data_url.js +0 -46
  234. package/test/test_worker_data_url.ts +0 -51
  235. package/test/test_worker_fetch.js +0 -26
  236. package/test/test_worker_fetch.ts +0 -27
  237. package/test/test_worker_net.js +0 -69
  238. package/test/test_worker_net.ts +0 -66
  239. package/test/test_worker_wasm_concurrent.js +0 -44
  240. package/test/test_worker_wasm_concurrent.ts +0 -45
  241. package/test/types.wasm +0 -0
  242. package/test/worker_fetch.js +0 -13
  243. package/test/worker_fetch.ts +0 -13
  244. package/test/worker_helper.js +0 -1
  245. package/test/worker_helper.ts +0 -1
  246. package/test/worker_http.js +0 -18
  247. package/test/worker_http.ts +0 -20
  248. package/test/worker_import_lib.js +0 -12
  249. package/test/worker_import_lib.ts +0 -13
  250. package/test/worker_module.js +0 -16
  251. package/test/worker_module.ts +0 -17
  252. package/test/worker_wasm_concurrent.js +0 -22
  253. package/test/worker_wasm_concurrent.ts +0 -22
@@ -1,725 +0,0 @@
1
- import { Tester } from './test_helper.js'
2
- import { NULL, PtrArrayBuffer, type Ptr } from '../lib/ffi/ctype.js'
3
- import { FrameRect, GetDC, InvertRect, ReleaseDC } from '../lib/windows/user32.js'
4
- import {
5
- Arc, Chord, CreateBitmap, CreateCompatibleBitmap, CreateCompatibleDC, CreateFontIndirect,
6
- CreateHatchBrush, CreatePatternBrush, CreatePen, CreateSolidBrush, DeleteDC,
7
- DeleteObject, Ellipse, ExtTextOut, GetArcDirection, GetDIBits, GetStockObject,
8
- GetStretchBltMode, GetTextExtentPoint32, GetTextMetrics, LineTo, MaskBlt, MoveToEx,
9
- PatBlt, Pie, PlgBlt, Polygon, Polyline, Rectangle, RestoreDC, RoundRect, SaveDC,
10
- SelectObject, SetArcDirection, SetBkColor, SetBkMode, SetDIBitsToDevice,
11
- SetPolyFillMode, SetROP2,
12
- SetStretchBltMode, SetTextAlign, SetTextColor, TextOut, StretchBlt, StretchDIBits,
13
- } from '../lib/windows/gdi32.js'
14
- import { AlphaBlend, TransparentBlt } from '../lib/windows/msimg32.js'
15
- import { BITMAPINFOHEADER, POINT, SIZE, TEXTMETRICW } from '../lib/windows/structs.js'
16
- import { ArcDirection, BackgroundMode, ColorUse, HatchStyle, PenStyle, PolyFillMode, RasterMode, RasterOp, StockObject, StretchBltMode, TextAlign, TextOutOptions } from '../constants'
17
-
18
- // 像素比较统一用 COLORREF 形(0x00BBGGRR)—— 与画图入参同形,读回直接对比。
19
- const RED = 0x000000ff
20
- const GREEN = 0x0000ff00
21
- const BLUE = 0x00ff0000
22
- const WHITE = 0x00ffffff
23
- const BLACK = 0x00000000
24
- const W = 320
25
- const H = 240
26
-
27
- export const suite = {
28
- name: 'gdi-draw',
29
- run: (t: Tester) => {
30
- // —— 基座:屏幕 DC → 内存 DC + 32bpp 位图(与 execd/worker 截屏同型)——
31
- t.section('setup: memory DC + 32bpp bitmap')
32
- const screen = GetDC(NULL)
33
- const mem = CreateCompatibleDC(screen)
34
- const bmp = screen ? CreateCompatibleBitmap(screen, W, H) : 0
35
- if (!screen || !mem || !bmp) {
36
- t.checkTrue('GDI setup (GetDC / CreateCompatibleDC / CreateCompatibleBitmap)', false)
37
- return
38
- }
39
- const oldBmp = SelectObject(mem, bmp)
40
- const pxB = new PtrArrayBuffer(W * H * 4)
41
- const pxU8 = new Uint8Array(pxB)
42
- const bmi = BITMAPINFOHEADER.encode({
43
- biSize: BITMAPINFOHEADER.size,
44
- biWidth: W,
45
- biHeight: -H, // 负 = 顶向下
46
- biPlanes: 1,
47
- biBitCount: 32,
48
- biSizeImage: W * H * 4,
49
- })
50
- // 读回当前位图像素(emitDib 同模式:32bpp BI_RGB = [B,G,R,x] 字节序)。
51
- // 失败置哨兵(px() 返回 -1)—— 像素断言 FAIL 出可诊断值,不炸整个 suite。
52
- let pxOk = true
53
- const readPx = (): void => {
54
- pxOk = GetDIBits(mem, bmp, 0, H, pxB, bmi.ptr, ColorUse.RGB_COLORS) === H
55
- }
56
- const px = (x: number, y: number): number => {
57
- if (!pxOk) return -1
58
- const i = (y * W + x) * 4
59
- return (pxU8[i]! << 16) | (pxU8[i + 1]! << 8) | pxU8[i + 2]! // B,G,R → COLORREF
60
- }
61
- const clearWhite = (): void => {
62
- PatBlt(mem, 0, 0, W, H, RasterOp.WHITENESS) // 与画刷无关的整白
63
- readPx()
64
- }
65
- clearWhite()
66
- t.check('baseline = white', WHITE, px(5, 5))
67
- t.checkTrue('GetDIBits full read', pxOk)
68
-
69
- t.section('state: return-previous semantics')
70
- t.check('fresh DC default bkMode = OPAQUE',
71
- BackgroundMode.OPAQUE, SetBkMode(mem, BackgroundMode.OPAQUE))
72
- t.check('SetBkMode returns previous', BackgroundMode.OPAQUE,
73
- SetBkMode(mem, BackgroundMode.TRANSPARENT))
74
- SetBkMode(mem, BackgroundMode.OPAQUE)
75
- SetTextColor(mem, RED) // 返回默认色(不假设)
76
- t.check('SetTextColor returns previous', RED, SetTextColor(mem, BLUE))
77
- SetTextColor(mem, 0)
78
- SetBkColor(mem, RED)
79
- t.check('SetBkColor returns previous', RED, SetBkColor(mem, WHITE))
80
-
81
- t.section('type layer: enum nominal + position forms')
82
- // 类型专用块(只过 tsc、不执行)
83
- const typeOnly: unknown = () => {
84
- // @ts-expect-error 跨枚举 nominal:BackgroundMode 成员进不了 PenStyle 位
85
- CreatePen(BackgroundMode.TRANSPARENT, 1, 0)
86
- // @ts-expect-error '<HDC>ptr!' 禁空位不收 NULL
87
- Rectangle(NULL, 0, 0, 1, 1)
88
- // @ts-expect-error encoder 数组位不收裸对象(单对象对 C 是越界基址)
89
- Polyline(0 as Ptr<'HDC'>, { x: 1, y: 2 }, 1)
90
- // @ts-expect-error MoveToEx 出参位刻意不注册 encoder:只收 .ptr / NULL,不收对象形
91
- MoveToEx(0 as Ptr<'HDC'>, 0, 0, { x: 1, y: 2 })
92
- // @ts-expect-error <WCHAR>ptr 文本位收 string / NULL,不收裸数字
93
- TextOut(0 as Ptr<'HDC'>, 0, 0, 123, 1)
94
- // @ts-expect-error 跨枚举 nominal:PenStyle 成员进不了 StockObject 位
95
- GetStockObject(PenStyle.SOLID)
96
- // @ts-expect-error 跨枚举 nominal:HatchStyle 成员进不了 PolyFillMode 位
97
- SetPolyFillMode(0 as Ptr<'HDC'>, HatchStyle.CROSS)
98
- // @ts-expect-error u32@RasterOp 标注拦枚举外光栅码(字面量不匹配成员值)
99
- PatBlt(0 as Ptr<'HDC'>, 0, 0, 1, 1, 0x123456)
100
- // @ts-expect-error '<SIZE>ptr!' 出参位不收 NULL
101
- GetTextExtentPoint32(0 as Ptr<'HDC'>, 'x', 1, NULL)
102
- // @ts-expect-error 跨枚举 nominal:StretchBltMode 成员进不了 ArcDirection 位
103
- SetArcDirection(0 as Ptr<'HDC'>, StretchBltMode.HALFTONE)
104
- }
105
- t.checkTrue('type-only block wired', typeof typeOnly === 'function')
106
-
107
- t.section('SaveDC/RestoreDC: state stack rollback')
108
- // 先定基态(断言不依赖前面 section 的收尾值)
109
- SetTextColor(mem, 0)
110
- SetBkMode(mem, BackgroundMode.OPAQUE)
111
- SetBkColor(mem, WHITE)
112
- const saved = SaveDC(mem)
113
- t.checkTrue(`SaveDC state id ≠ 0 (${saved})`, saved !== 0)
114
- // 打乱:三项绘图状态 + 换一支笔
115
- SetTextColor(mem, RED)
116
- SetBkMode(mem, BackgroundMode.TRANSPARENT)
117
- SetBkColor(mem, BLUE)
118
- const junkPen = CreatePen(PenStyle.DASH, 1, GREEN)
119
- SelectObject(mem, junkPen)
120
- t.checkTrue('RestoreDC ok', RestoreDC(mem, saved) !== 0)
121
- DeleteObject(junkPen) // 恢复后已卸下,可安全删
122
- // 逐项回滚断言:各 Set 返回的就是回滚后的当前值
123
- t.check('text color rolled back', 0, SetTextColor(mem, RED))
124
- SetTextColor(mem, 0)
125
- t.check('bkMode rolled back', BackgroundMode.OPAQUE,
126
- SetBkMode(mem, BackgroundMode.TRANSPARENT))
127
- SetBkMode(mem, BackgroundMode.OPAQUE)
128
- t.check('bkColor rolled back', WHITE, SetBkColor(mem, RED))
129
- SetBkColor(mem, WHITE)
130
-
131
- t.section('Rectangle: brush fill (inside/outside pixels)')
132
- clearWhite()
133
- const brush = CreateSolidBrush(RED)
134
- t.checkTrue('brush created', !!brush)
135
- const oldBrush = SelectObject(mem, brush)
136
- Rectangle(mem, 10, 10, 50, 30)
137
- readPx()
138
- t.check('inside = red', RED, px(20, 20))
139
- t.check('outside left-top = white', WHITE, px(9, 9))
140
- t.check('outside right-bottom = white', WHITE, px(60, 40))
141
- SelectObject(mem, oldBrush)
142
- DeleteObject(brush)
143
-
144
- t.section('Polyline: encoder array position + NULL passthrough')
145
- clearWhite()
146
- const pen = CreatePen(PenStyle.SOLID, 1, GREEN)
147
- t.checkTrue('pen created', !!pen)
148
- const oldPen = SelectObject(mem, pen)
149
- t.checkTrue('Polyline success',
150
- Polyline(mem, [{ x: 0, y: 100 }, { x: 100, y: 100 }], 2) !== 0)
151
- readPx()
152
- t.check('on-line = green', GREEN, px(50, 100))
153
- t.check('row above = white', WHITE, px(50, 99))
154
- t.check('row below = white', WHITE, px(50, 101))
155
- // 空位语义:encoder 对 [] fail-loud(PAB(0) 永不产出),NULL 由调用方显式传
156
- // —— C 拒收 NULL 返回 0。
157
- let arrErr = ''
158
- try { Polyline(mem, [], 0) } catch (e) { arrErr = String(e) }
159
- t.checkTrue('[] fail-loud at encoder (never PAB(0))', arrErr.includes('non-empty'))
160
- t.check('NULL position (C returns 0)', 0, Polyline(mem, NULL, 0))
161
-
162
- t.section('MoveToEx/LineTo: out-param + line pixels')
163
- t.checkTrue('MoveToEx NULL lppt ok', MoveToEx(mem, 10, 10, NULL) !== 0)
164
- t.checkTrue('LineTo ok', LineTo(mem, 10, 50) !== 0) // 竖线,不含端点 → 行 10..49
165
- readPx()
166
- t.check('on-line = green', GREEN, px(10, 30))
167
- t.check('col-1 = white', WHITE, px(9, 30))
168
- t.check('col+1 = white', WHITE, px(11, 30))
169
- // 当前位置现为 (10,50):MoveToEx 落新位 (77,88),旧位置写进出参(.ptr 直通通路)
170
- const prev = POINT.alloc()
171
- t.checkTrue('MoveToEx out slot ok', MoveToEx(mem, 77, 88, prev.ptr) !== 0)
172
- const oldPos = POINT.decode(prev.ptr)
173
- t.check('MoveToEx out.x', 10, oldPos.x)
174
- t.check('MoveToEx out.y', 50, oldPos.y)
175
- LineTo(mem, 77, 120) // 从 (77,88) 起画 → 列 77
176
- readPx()
177
- t.check('current position moved', GREEN, px(77, 100))
178
- SelectObject(mem, oldPen)
179
- DeleteObject(pen)
180
-
181
- t.section('SetROP2: R2_XORPEN reversible')
182
- PatBlt(mem, 0, 0, W, H, RasterOp.BLACKNESS) // 黑底:黑^绿=绿,绿^绿=黑
183
- t.check('default raster mode = R2_COPYPEN', RasterMode.R2_COPYPEN,
184
- SetROP2(mem, RasterMode.R2_XORPEN)) // 返回旧值 = 默认,同时设为 XOR
185
- const xorPen = CreatePen(PenStyle.SOLID, 1, GREEN)
186
- const xorOld = SelectObject(mem, xorPen)
187
- MoveToEx(mem, 10, 200, NULL)
188
- LineTo(mem, 100, 200)
189
- readPx()
190
- t.check('first pass = green (black XOR green)', GREEN, px(50, 200))
191
- MoveToEx(mem, 10, 200, NULL)
192
- LineTo(mem, 100, 200) // 同路径重画 → XOR 抵消
193
- readPx()
194
- t.check('second pass cancels back to black', BLACK, px(50, 200))
195
- SelectObject(mem, xorOld)
196
- DeleteObject(xorPen)
197
- SetROP2(mem, RasterMode.R2_COPYPEN) // 恢复
198
-
199
- t.section('Polygon: fill + outline (auto-close)')
200
- clearWhite()
201
- const pBrush = CreateSolidBrush(BLUE)
202
- const oldPB = SelectObject(mem, pBrush)
203
- t.checkTrue('Polygon success',
204
- Polygon(mem, [{ x: 200, y: 20 }, { x: 260, y: 20 }, { x: 230, y: 70 }], 3) !== 0)
205
- readPx()
206
- t.check('inside triangle = blue', BLUE, px(230, 40))
207
- t.check('above triangle = white', WHITE, px(230, 10))
208
- t.check('left of triangle = white', WHITE, px(180, 40))
209
- SelectObject(mem, oldPB)
210
- DeleteObject(pBrush)
211
-
212
- t.section('Ellipse / RoundRect: fill')
213
- clearWhite()
214
- const sBrush = CreateSolidBrush(RED)
215
- const oldSB = SelectObject(mem, sBrush)
216
- Ellipse(mem, 100, 100, 140, 140)
217
- readPx()
218
- t.check('ellipse center = red', RED, px(120, 120))
219
- t.check('ellipse corner = white', WHITE, px(101, 101))
220
- RoundRect(mem, 200, 100, 240, 140, 10, 10)
221
- readPx()
222
- t.check('roundrect center = red', RED, px(220, 120))
223
- t.check('rounded corner cut = white', WHITE, px(201, 101))
224
- SelectObject(mem, oldSB)
225
- DeleteObject(sBrush)
226
-
227
- t.section('Arc/Chord/Pie: angular shapes')
228
- // 圆 (100,100)-(200,200) 心 (150,150) r=50;起 12 点 (150,100) → 终 9 点 (100,150):
229
- // GDI 默认逆时针 12→11→10→9 = 左上 90° 弧(SetArcDirection 可改,见 MSDN Arc)
230
- clearWhite()
231
- const arcPen = CreatePen(PenStyle.SOLID, 1, RED)
232
- const arcOldPen = SelectObject(mem, arcPen)
233
- Arc(mem, 100, 100, 200, 200, 150, 100, 100, 150)
234
- readPx()
235
- let arcRed = 0 // 扫圆环带(圆心距 48..52)数红 = 弧存在
236
- for (let y = 98; y < 203; y++) {
237
- for (let x = 98; x < 203; x++) {
238
- const dx = x - 150
239
- const dy = y - 150
240
- const d2 = dx * dx + dy * dy
241
- if (d2 >= 48 * 48 && d2 <= 52 * 52 && px(x, y) === RED) arcRed++
242
- }
243
- }
244
- t.checkTrue(`arc ring pixels (${arcRed})`, arcRed > 20)
245
- t.check('arc interior untouched', WHITE, px(150, 150))
246
- SelectObject(mem, arcOldPen)
247
- DeleteObject(arcPen)
248
-
249
- clearWhite()
250
- const pieBrush = CreateSolidBrush(RED)
251
- const pieOldB = SelectObject(mem, pieBrush)
252
- Pie(mem, 100, 100, 200, 200, 150, 100, 100, 150)
253
- readPx()
254
- t.check('pie sector filled near apex', RED, px(140, 140)) // 45° 夹角内部(顶点 (150,150)
255
- t.check('pie swept quadrant filled', RED, px(118, 128)) // 恰在半径边界行上不填——挪开)
256
- t.check('pie opposite side empty', WHITE, px(180, 180))
257
- SelectObject(mem, pieOldB)
258
- DeleteObject(pieBrush)
259
-
260
- clearWhite()
261
- const chBrush = CreateSolidBrush(RED)
262
- const chOldB = SelectObject(mem, chBrush)
263
- Chord(mem, 100, 100, 200, 200, 150, 100, 100, 150)
264
- readPx()
265
- // 弦 (150,100)-(100,150):直线 x+y=250;左上小弧侧 = x+y < 250 一侧
266
- t.check('chord band filled (arc side)', RED, px(118, 128)) // 246 < 250、圆内
267
- t.check('chord excludes center side', WHITE, px(150, 150)) // 300 > 250 → 弦另一侧
268
- SelectObject(mem, chOldB)
269
- DeleteObject(chBrush)
270
-
271
- t.section('SetArcDirection/GetArcDirection: sweep flip')
272
- clearWhite()
273
- const dirBrush = CreateSolidBrush(RED)
274
- const dirOldB = SelectObject(mem, dirBrush)
275
- t.check('default arc direction = counterclockwise', ArcDirection.COUNTERCLOCKWISE,
276
- GetArcDirection(mem))
277
- t.check('SetArcDirection returns previous', ArcDirection.COUNTERCLOCKWISE,
278
- SetArcDirection(mem, ArcDirection.CLOCKWISE))
279
- Pie(mem, 100, 100, 200, 200, 150, 100, 100, 150) // 起 12 点终 9 点:顺时针 → 右下 270°
280
- readPx()
281
- t.check('clockwise sweep fills opposite side', RED, px(180, 180))
282
- t.check('clockwise sweep skips upper-left', WHITE, px(118, 128))
283
- SetArcDirection(mem, ArcDirection.COUNTERCLOCKWISE) // 恢复默认
284
- SelectObject(mem, dirOldB)
285
- DeleteObject(dirBrush)
286
-
287
- t.section('SetPolyFillMode: ALTERNATE vs WINDING (pentagram)')
288
- clearWhite()
289
- // 5 外顶点按 {5/2} 跳连(P0→P2→P4→P1→P3)= 真自相交五角星
290
- const V: { x: number, y: number }[] = []
291
- for (let i = 0; i < 5; i++) {
292
- const a = -Math.PI / 2 + (i * 2 * Math.PI) / 5
293
- V.push({ x: Math.round(150 + 55 * Math.cos(a)), y: Math.round(150 + 55 * Math.sin(a)) })
294
- }
295
- const star = [V[0]!, V[2]!, V[4]!, V[1]!, V[3]!]
296
- t.check('default fill mode = ALTERNATE', PolyFillMode.ALTERNATE,
297
- SetPolyFillMode(mem, PolyFillMode.ALTERNATE))
298
- const starBrush = CreateSolidBrush(RED)
299
- const starOldB = SelectObject(mem, starBrush)
300
- Polygon(mem, star, 5)
301
- readPx()
302
- let starRed = 0
303
- for (let y = 90; y < 210; y++) {
304
- for (let x = 90; x < 210; x++) {
305
- if (px(x, y) === RED) starRed++
306
- }
307
- }
308
- t.checkTrue(`ALTERNATE: star drawn (${starRed}px)`, starRed > 200)
309
- t.check('ALTERNATE: center hollow (even-odd)', WHITE, px(150, 150))
310
- SetPolyFillMode(mem, PolyFillMode.WINDING)
311
- clearWhite()
312
- Polygon(mem, star, 5)
313
- readPx()
314
- t.check('WINDING: center filled (nonzero)', RED, px(150, 150))
315
- SelectObject(mem, starOldB)
316
- DeleteObject(starBrush)
317
- SetPolyFillMode(mem, PolyFillMode.ALTERNATE) // 恢复默认
318
-
319
- t.section('GetStockObject: stock objects, outline-only shape')
320
- clearWhite()
321
- const hollow = GetStockObject(StockObject.NULL_BRUSH)
322
- const stkPen = GetStockObject(StockObject.BLACK_PEN)
323
- t.checkTrue(`stock handles non-zero (0x${hollow.toString(16)}, 0x${stkPen.toString(16)})`,
324
- hollow !== 0 && stkPen !== 0)
325
- const stockOldBrush = SelectObject(mem, hollow)
326
- const stockOldPen = SelectObject(mem, stkPen)
327
- Rectangle(mem, 100, 100, 150, 140) // NULL_BRUSH → 只描边不填充
328
- readPx()
329
- let innerNz = 0 // 内部远离边线,应全白
330
- for (let y = 105; y < 135; y++) {
331
- for (let x = 105; x < 145; x++) {
332
- if (px(x, y) !== WHITE) innerNz++
333
- }
334
- }
335
- t.check('outline-only: interior untouched', 0, innerNz)
336
- // 边界带扫描黑像素(不猜具体哪列——右/下边的 off-by-one 交给扫描容错)
337
- let edgeBlack = 0
338
- for (let y = 100; y < 140; y++) {
339
- for (let x = 100; x < 150; x++) {
340
- if ((x === 100 || y === 100 || x === 149 || y === 139) && px(x, y) === BLACK) edgeBlack++
341
- }
342
- }
343
- t.checkTrue(`stock pen outline drawn (${edgeBlack}px)`, edgeBlack > 20)
344
- SelectObject(mem, stockOldBrush)
345
- SelectObject(mem, stockOldPen)
346
- // stock 对象是系统共享的,不 DeleteObject
347
-
348
- t.section('CreateHatchBrush: textured fill')
349
- clearWhite()
350
- SetBkMode(mem, BackgroundMode.TRANSPARENT) // 间隙透底 = 白
351
- const hatch = CreateHatchBrush(HatchStyle.HORIZONTAL, RED)
352
- t.checkTrue('hatch brush created', hatch !== 0)
353
- const hatchOldB = SelectObject(mem, hatch)
354
- Rectangle(mem, 20, 20, 120, 60)
355
- readPx()
356
- let hRed = 0
357
- let hWhite = 0
358
- let hOther = 0
359
- for (let y = 30; y < 50; y++) {
360
- for (let x = 30; x < 110; x++) {
361
- const c = px(x, y)
362
- if (c === RED) hRed++
363
- else if (c === WHITE) hWhite++
364
- else hOther++
365
- }
366
- }
367
- t.checkTrue(`hatch lines present (red=${hRed})`, hRed > 50)
368
- t.checkTrue(`transparent gaps (white=${hWhite})`, hWhite > 50)
369
- t.check('no other colors in hatch', 0, hOther)
370
- SelectObject(mem, hatchOldB)
371
- DeleteObject(hatch)
372
- SetBkMode(mem, BackgroundMode.OPAQUE)
373
-
374
- t.section('FrameRect/InvertRect: border and invert (user32)')
375
- clearWhite()
376
- const frBrush = CreateSolidBrush(RED)
377
- FrameRect(mem, { left: 10, top: 10, right: 50, bottom: 40 }, frBrush)
378
- readPx()
379
- t.check('frame top edge red', RED, px(30, 10))
380
- t.check('frame left edge red', RED, px(10, 25))
381
- t.check('frame interior white', WHITE, px(30, 25))
382
- DeleteObject(frBrush)
383
-
384
- clearWhite()
385
- InvertRect(mem, { left: 20, top: 20, right: 60, bottom: 50 })
386
- readPx()
387
- t.check('invert white to black', BLACK, px(40, 35))
388
- t.check('outside rect untouched', WHITE, px(5, 5))
389
- InvertRect(mem, { left: 20, top: 20, right: 60, bottom: 50 }) // 再反一次可逆
390
- readPx()
391
- t.check('second invert restores white', WHITE, px(40, 35))
392
-
393
- t.section('StretchBlt: scaled copy + mode roundtrip')
394
- clearWhite()
395
- const stTmp = CreateCompatibleDC(mem)
396
- if (!stTmp) {
397
- t.checkTrue('CreateCompatibleDC (stretch source)', false)
398
- return
399
- }
400
- const stBmp = CreateCompatibleBitmap(mem, 4, 4)
401
- const stOldBmp = SelectObject(stTmp, stBmp)
402
- const stFill = CreateSolidBrush(RED)
403
- SelectObject(stTmp, stFill)
404
- PatBlt(stTmp, 0, 0, 4, 4, RasterOp.PATCOPY) // 源 4×4 全红
405
- t.checkTrue('StretchBlt ok',
406
- StretchBlt(mem, 10, 10, 40, 40, stTmp, 0, 0, 4, 4, RasterOp.SRCCOPY) !== 0)
407
- readPx()
408
- t.check('scaled center red', RED, px(30, 30))
409
- t.check('scaled near-origin red', RED, px(11, 11))
410
- t.check('outside target untouched', WHITE, px(60, 30))
411
- const stMode0 = GetStretchBltMode(mem)
412
- t.check('SetStretchBltMode returns current', stMode0,
413
- SetStretchBltMode(mem, StretchBltMode.HALFTONE))
414
- t.check('GetStretchBltMode = HALFTONE', StretchBltMode.HALFTONE, GetStretchBltMode(mem))
415
- SelectObject(stTmp, stOldBmp)
416
- DeleteObject(stFill)
417
- DeleteObject(stBmp)
418
- DeleteDC(stTmp)
419
-
420
- t.section('CreatePatternBrush: tiled bitmap fill')
421
- clearWhite()
422
- const pkTmp = CreateCompatibleDC(mem)
423
- if (!pkTmp) {
424
- t.checkTrue('CreateCompatibleDC (pattern source)', false)
425
- return
426
- }
427
- const pkBmp = CreateCompatibleBitmap(mem, 4, 4)
428
- const pkOldBmp = SelectObject(pkTmp, pkBmp)
429
- const pkRed = CreateSolidBrush(RED)
430
- SelectObject(pkTmp, pkRed)
431
- PatBlt(pkTmp, 0, 0, 4, 4, RasterOp.PATCOPY) // 红底
432
- const pkWhite = CreateSolidBrush(WHITE)
433
- SelectObject(pkTmp, pkWhite)
434
- PatBlt(pkTmp, 2, 0, 2, 2, RasterOp.PATCOPY) // 挖两白角 = 2×2 棋盘
435
- PatBlt(pkTmp, 0, 2, 2, 2, RasterOp.PATCOPY)
436
- SelectObject(pkTmp, pkOldBmp) // 位图退出选入状态
437
- const pkPat = CreatePatternBrush(pkBmp)
438
- t.checkTrue('pattern brush created', pkPat !== 0)
439
- const pkPatOld = SelectObject(mem, pkPat)
440
- Rectangle(mem, 20, 20, 120, 120)
441
- readPx()
442
- t.check('tile (0,0) cell red', RED, px(24, 24)) // 平铺按 (x%4, y%4) 棋盘
443
- t.check('tile (2,0) cell white', WHITE, px(26, 24))
444
- t.check('tile (0,2) cell white', WHITE, px(24, 26))
445
- t.check('tile (2,2) cell red', RED, px(26, 26))
446
- t.check('tiled far cell red (25 cycles)', RED, px(100, 100)) // 100%4=0
447
- SelectObject(mem, pkPatOld)
448
- DeleteObject(pkPat)
449
- DeleteObject(pkBmp)
450
- DeleteObject(pkRed)
451
- DeleteObject(pkWhite)
452
- DeleteDC(pkTmp)
453
-
454
- t.section('SetDIBitsToDevice/StretchDIBits: raw DIB buffer blit')
455
- clearWhite()
456
- // 4×4 RGBA 缓冲:上 2 行红、下 2 行蓝(biHeight<0 顶向下——专测行序不颠倒)
457
- const dibBits = new PtrArrayBuffer(4 * 4 * 4)
458
- const dibU8 = new Uint8Array(dibBits)
459
- for (let y = 0; y < 4; y++) {
460
- for (let x = 0; x < 4; x++) {
461
- const i = (y * 4 + x) * 4 // 32bpp BI_RGB = [B,G,R,x]
462
- dibU8[i] = y < 2 ? 0 : 255 // B:下半蓝
463
- dibU8[i + 1] = 0
464
- dibU8[i + 2] = y < 2 ? 255 : 0 // R:上半红
465
- }
466
- }
467
- const dibBmi = BITMAPINFOHEADER.encode({
468
- biSize: BITMAPINFOHEADER.size, biWidth: 4, biHeight: -4,
469
- biPlanes: 1, biBitCount: 32, biSizeImage: 4 * 4 * 4,
470
- })
471
- t.checkTrue('SetDIBitsToDevice ok',
472
- SetDIBitsToDevice(mem, 20, 20, 4, 4, 0, 0, 0, 4, dibBits, dibBmi, ColorUse.RGB_COLORS) === 4)
473
- readPx()
474
- t.check('dib top rows red (top-down)', RED, px(21, 21))
475
- t.check('dib bottom rows blue', BLUE, px(21, 23))
476
- t.check('dib outside untouched', WHITE, px(30, 21))
477
- clearWhite()
478
- t.checkTrue('StretchDIBits ok',
479
- StretchDIBits(mem, 20, 20, 40, 40, 0, 0, 4, 4, dibBits, dibBmi, ColorUse.RGB_COLORS,
480
- RasterOp.SRCCOPY) === 4)
481
- readPx()
482
- t.check('stretched top half red', RED, px(40, 30))
483
- t.check('stretched bottom half blue', BLUE, px(40, 50))
484
- t.check('stretched outside untouched', WHITE, px(65, 30))
485
-
486
- t.section('PlgBlt: parallelogram mapping')
487
- clearWhite()
488
- const plgTmp = CreateCompatibleDC(mem)
489
- if (!plgTmp) { t.checkTrue('CreateCompatibleDC (plg src)', false); return }
490
- const plgBmp = CreateCompatibleBitmap(mem, 40, 40)
491
- const plgOldB = SelectObject(plgTmp, plgBmp)
492
- const plgRed = CreateSolidBrush(RED)
493
- SelectObject(plgTmp, plgRed)
494
- PatBlt(plgTmp, 0, 0, 40, 40, RasterOp.PATCOPY) // 源 40×40 全红
495
- // 三点 TL(40,60) TR(160,60) BL(70,160) → 方向 s(120,0)+t(30,100),中心 (115,110)
496
- t.checkTrue('PlgBlt ok',
497
- PlgBlt(mem, [{ x: 40, y: 60 }, { x: 160, y: 60 }, { x: 70, y: 160 }],
498
- plgTmp, 0, 0, 40, 40, NULL, 0, 0) !== 0)
499
- readPx()
500
- t.check('parallelogram center red', RED, px(115, 110))
501
- t.check('left of slanted edge white', WHITE, px(40, 160))
502
- SelectObject(plgTmp, plgOldB)
503
- DeleteObject(plgRed)
504
- DeleteObject(plgBmp)
505
- DeleteDC(plgTmp)
506
-
507
- t.section('CreateBitmap + MaskBlt: monochrome row mask')
508
- clearWhite()
509
- const mkTmp = CreateCompatibleDC(mem)
510
- if (!mkTmp) { t.checkTrue('CreateCompatibleDC (mask src)', false); return }
511
- const mkBmp = CreateCompatibleBitmap(mem, 4, 4)
512
- const mkOldB = SelectObject(mkTmp, mkBmp)
513
- const mkRed = CreateSolidBrush(RED)
514
- SelectObject(mkTmp, mkRed)
515
- PatBlt(mkTmp, 0, 0, 4, 4, RasterOp.PATCOPY) // 源 4×4 全红
516
- // 1bpp 掩码 4×4:行按 word(2B) 对齐(CreateBitmap 惯例,非 DIB 的 4B)——
517
- // 行 0/2 = 0xFF(白)、行 1/3 = 0x00(黑)
518
- const mkBits = new PtrArrayBuffer(8)
519
- const mkU8 = new Uint8Array(mkBits)
520
- mkU8[0] = 0xFF
521
- mkU8[4] = 0xFF
522
- const mask = CreateBitmap(4, 4, 1, 1, mkBits)
523
- t.checkTrue('CreateBitmap(1bpp) ok', mask !== 0)
524
- PatBlt(mem, 30, 30, 4, 4, RasterOp.BLACKNESS) // 目标底黑 → back 白可辨
525
- // MAKEROP4(fore=SRCCOPY 取源, back=WHITENESS 画白):back 码在 bit31..24、fore 在 bit23..16
526
- const rop4 = (((RasterOp.WHITENESS << 8) & 0xFF000000) | RasterOp.SRCCOPY) >>> 0
527
- t.checkTrue('MaskBlt ok',
528
- MaskBlt(mem, 30, 30, 4, 4, mkTmp, 0, 0, mask, 0, 0, rop4) !== 0)
529
- readPx()
530
- t.check('mask row0 = fore (red)', RED, px(31, 30))
531
- t.check('mask row1 = back (white)', WHITE, px(31, 31))
532
- t.check('mask row2 = fore (red)', RED, px(31, 32))
533
- t.check('mask row3 = back (white)', WHITE, px(31, 33))
534
- SelectObject(mkTmp, mkOldB)
535
- DeleteObject(mask)
536
- DeleteObject(mkRed)
537
- DeleteObject(mkBmp)
538
- DeleteDC(mkTmp)
539
-
540
- t.section('TransparentBlt: color-key transparency (msimg32)')
541
- clearWhite()
542
- const trTmp = CreateCompatibleDC(mem)
543
- if (!trTmp) { t.checkTrue('CreateCompatibleDC (tr src)', false); return }
544
- const trBmp = CreateCompatibleBitmap(mem, 40, 16)
545
- const trOldB = SelectObject(trTmp, trBmp)
546
- const trRed = CreateSolidBrush(RED)
547
- SelectObject(trTmp, trRed)
548
- PatBlt(trTmp, 0, 0, 20, 16, RasterOp.PATCOPY) // 左半红
549
- const trBlue = CreateSolidBrush(BLUE)
550
- SelectObject(trTmp, trBlue)
551
- PatBlt(trTmp, 20, 0, 20, 16, RasterOp.PATCOPY) // 右半蓝 = 键色
552
- t.checkTrue('TransparentBlt ok',
553
- TransparentBlt(mem, 20, 20, 40, 16, trTmp, 0, 0, 40, 16, BLUE) !== 0)
554
- readPx()
555
- t.check('non-key pixels copied (red)', RED, px(25, 25))
556
- t.check('key pixels transparent (white shows through)', WHITE, px(55, 25))
557
- SelectObject(trTmp, trOldB)
558
- DeleteObject(trRed)
559
- DeleteObject(trBlue)
560
- DeleteObject(trBmp)
561
- DeleteDC(trTmp)
562
-
563
- t.section('AlphaBlend: constant alpha (by-value BLENDFUNCTION)')
564
- // BLENDFUNCTION 按值传(<BLENDFUNCTION> 位,4B 聚合体)——错绑成 <N>ptr 会把
565
- // 地址当 4 字节位图参数,GDI 读到垃圾,本段必然失败(真实消费者回归网)。
566
- clearWhite()
567
- const abTmp = CreateCompatibleDC(mem)
568
- if (!abTmp) { t.checkTrue('CreateCompatibleDC (ab src)', false); return }
569
- const abBmp = CreateCompatibleBitmap(mem, 40, 16)
570
- const abOldB = SelectObject(abTmp, abBmp)
571
- const abGreen = CreateSolidBrush(GREEN)
572
- SelectObject(abTmp, abGreen)
573
- PatBlt(abTmp, 0, 0, 40, 16, RasterOp.PATCOPY) // 源 = 纯绿(不透明)
574
- // 完全覆盖:SourceConstantAlpha=255、无调制(BlendFlags=0)、BlendOp=AC_SRC_OVER(0)
575
- t.checkTrue('AlphaBlend full alpha ok',
576
- AlphaBlend(mem, 20, 20, 40, 16, abTmp, 0, 0, 40, 16,
577
- { BlendOp: 0, BlendFlags: 0, SourceConstantAlpha: 255, AlphaFormat: 0 }) !== 0)
578
- readPx()
579
- t.check('dest = src green (alpha 255 exact)', GREEN, px(25, 25))
580
- // 半透明 ca=128:GDI 实测(AlphaFormat=0)按 premultiplied 公式
581
- // out = src + dst*(255-ca)/255 —— 源值保留、目标衰减(MS Answers 已知行为,
582
- // 与文档的非预乘公式不同):R/B = 255*127/255 ≈ 127、G = 255,±3 盖取整差异
583
- t.checkTrue('AlphaBlend half alpha ok',
584
- AlphaBlend(mem, 80, 20, 40, 16, abTmp, 0, 0, 40, 16,
585
- { BlendOp: 0, BlendFlags: 0, SourceConstantAlpha: 128, AlphaFormat: 0 }) !== 0)
586
- readPx()
587
- const abHalf = px(85, 25)
588
- const abR = abHalf & 0xff, abG = (abHalf >> 8) & 0xff, abB = (abHalf >> 16) & 0xff
589
- t.checkTrue(`half-alpha R ≈127 (${abR})`, Math.abs(abR - 127) <= 3)
590
- t.checkTrue(`half-alpha G ≈255 (src kept) (${abG})`, Math.abs(abG - 255) <= 3)
591
- t.checkTrue(`half-alpha B ≈127 (${abB})`, Math.abs(abB - 127) <= 3)
592
- // ca=0:文档「image is transparent」→ 目标不变;premultiplied 公式下
593
- // saturate(src+dst) 也落在白 —— 两种解读殊途同归,锚点确定
594
- t.checkTrue('AlphaBlend zero alpha ok',
595
- AlphaBlend(mem, 140, 20, 40, 16, abTmp, 0, 0, 40, 16,
596
- { BlendOp: 0, BlendFlags: 0, SourceConstantAlpha: 0, AlphaFormat: 0 }) !== 0)
597
- readPx()
598
- t.check('dest unchanged (alpha 0)', WHITE, px(145, 25))
599
- SelectObject(abTmp, abOldB)
600
- DeleteObject(abGreen)
601
- DeleteObject(abBmp)
602
- DeleteDC(abTmp)
603
-
604
- t.section('TextOut: transparent background + text color')
605
- clearWhite()
606
- SetBkColor(mem, BLUE) // 衬底设蓝:若 TRANSPARENT 未生效,字底必现蓝块
607
- SetBkMode(mem, BackgroundMode.TRANSPARENT)
608
- SetTextColor(mem, RED)
609
- t.checkTrue('TextOut success', TextOut(mem, 20, 100, 'Hello GDI', 9) !== 0)
610
- readPx()
611
- let redPx = 0
612
- let bluePx = 0
613
- // 扫描框放宽:对齐默认形(TA_TOP 或 TA_BASELINE)都能罩住字形
614
- for (let y = 84; y < 124; y++) {
615
- for (let x = 16; x < 150; x++) {
616
- const c = px(x, y)
617
- if (c === RED) redPx++
618
- else if (c === BLUE) bluePx++
619
- }
620
- }
621
- t.checkTrue(`glyph pixels rendered (red=${redPx})`, redPx > 20)
622
- t.checkTrue(`transparent bg: zero blue backdrop (blue=${bluePx})`, bluePx === 0)
623
- SetBkMode(mem, BackgroundMode.OPAQUE)
624
- SetBkColor(mem, WHITE)
625
- SetTextColor(mem, 0)
626
-
627
- t.section('text metrics/align: extent + SetTextAlign + ExtTextOut')
628
- clearWhite()
629
- // (a) 量字:出参 SIZE(alloc().ptr 进、decode 出)
630
- const sz = SIZE.alloc()
631
- t.checkTrue('GetTextExtentPoint32 ok',
632
- GetTextExtentPoint32(mem, 'Hello GDI', 9, sz.ptr) !== 0)
633
- const ext = SIZE.decode(sz.ptr)
634
- t.checkTrue(`extent cx sane (${ext.cx})`, (ext.cx ?? 0) >= 30 && (ext.cx ?? 0) <= 300)
635
- t.checkTrue(`extent cy sane (${ext.cy})`, (ext.cy ?? 0) >= 8 && (ext.cy ?? 0) <= 64)
636
-
637
- // (b) 行高度量(出参 TEXTMETRICW,与 (a) 同一默认字体)
638
- const tmSlot = TEXTMETRICW.alloc()
639
- t.checkTrue('GetTextMetrics ok', GetTextMetrics(mem, tmSlot.ptr) !== 0)
640
- const tmv = TEXTMETRICW.decode(tmSlot.ptr)
641
- t.checkTrue(`tmHeight sane (${tmv.tmHeight})`,
642
- (tmv.tmHeight ?? 0) >= 8 && (tmv.tmHeight ?? 0) <= 64)
643
- t.checkTrue(`tmAscent/descent sane (${tmv.tmAscent}/${tmv.tmDescent})`,
644
- (tmv.tmAscent ?? 0) > 0 && (tmv.tmDescent ?? 0) >= 0)
645
- t.checkTrue(`tmAveCharWidth sane (${tmv.tmAveCharWidth})`,
646
- (tmv.tmAveCharWidth ?? 0) >= 4 && (tmv.tmAveCharWidth ?? 0) <= 48)
647
-
648
- // (c) SetTextAlign 右对齐:字形全部落在锚点左侧
649
- SetTextColor(mem, RED)
650
- t.check('default align = 0 (LEFT|TOP|NOUPDATECP)', 0,
651
- SetTextAlign(mem, TextAlign.TA_RIGHT))
652
- TextOut(mem, 200, 100, 'Right', 5)
653
- readPx()
654
- let rRight = 0
655
- let rWrong = 0
656
- for (let y = 96; y < 116; y++) {
657
- for (let x = 140; x < 260; x++) {
658
- const c = px(x, y)
659
- if (c === RED) {
660
- if (x < 200) rRight++
661
- else if (x > 200) rWrong++
662
- }
663
- }
664
- }
665
- t.checkTrue(`right-aligned glyphs left of anchor (red=${rRight})`, rRight > 10)
666
- t.check('no glyphs right of anchor', 0, rWrong)
667
- SetTextAlign(mem, TextAlign.TA_LEFT) // 恢复 0
668
- SetTextColor(mem, 0)
669
-
670
- // (d) ExtTextOut ETO_OPAQUE 衬底(lprc 对象形直传 = encoder 入参位)
671
- SetTextColor(mem, RED)
672
- SetBkColor(mem, BLUE)
673
- t.checkTrue('ExtTextOut ETO_OPAQUE ok',
674
- ExtTextOut(mem, 20, 100, TextOutOptions.ETO_OPAQUE,
675
- { left: 18, top: 98, right: 100, bottom: 118 }, 'Opaque', 6, NULL) !== 0)
676
- readPx()
677
- let oBlue = 0
678
- let oRed = 0
679
- for (let y = 99; y < 117; y++) {
680
- for (let x = 19; x < 99; x++) {
681
- const c = px(x, y)
682
- if (c === BLUE) oBlue++
683
- else if (c === RED) oRed++
684
- }
685
- }
686
- t.checkTrue(`opaque backdrop painted (blue=${oBlue})`, oBlue > 500)
687
- t.checkTrue(`glyphs over backdrop (red=${oRed})`, oRed > 10)
688
- SetBkColor(mem, WHITE)
689
- SetTextColor(mem, 0)
690
-
691
- t.section('CreateFontIndirect: scaled glyphs')
692
- clearWhite()
693
- SetTextColor(mem, RED)
694
- TextOut(mem, 20, 40, 'Big', 3)
695
- readPx()
696
- let smPx = 0
697
- for (let y = 35; y < 95; y++) {
698
- for (let x = 15; x < 150; x++) {
699
- if (px(x, y) === RED) smPx++
700
- }
701
- }
702
- const bigFont = CreateFontIndirect({ lfHeight: -48, lfFaceName: 'Arial' })
703
- t.checkTrue('scaled font created', bigFont !== 0)
704
- const bigOld = SelectObject(mem, bigFont)
705
- clearWhite()
706
- TextOut(mem, 20, 40, 'Big', 3)
707
- readPx()
708
- let bgPx = 0
709
- for (let y = 35; y < 95; y++) {
710
- for (let x = 15; x < 150; x++) {
711
- if (px(x, y) === RED) bgPx++
712
- }
713
- }
714
- t.checkTrue(`scaled glyphs bigger (${bgPx} vs ${smPx})`, bgPx > smPx * 2)
715
- SelectObject(mem, bigOld)
716
- DeleteObject(bigFont)
717
- SetTextColor(mem, 0)
718
-
719
- t.section('cleanup')
720
- SelectObject(mem, oldBmp)
721
- t.checkTrue('DeleteObject(bitmap)', DeleteObject(bmp) !== 0)
722
- t.checkTrue('DeleteDC', DeleteDC(mem) !== 0)
723
- t.checkTrue('ReleaseDC', ReleaseDC(NULL, screen) !== 0)
724
- }
725
- }