quickwin 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +197 -0
- package/README.md +196 -0
- package/examples/PdfCanvas.js +67 -0
- package/examples/PdfCanvas.tsx +83 -0
- package/examples/pdf_preview2.js +431 -0
- package/examples/pdf_preview2.ts +375 -0
- package/examples/pdf_viewer.js +17623 -0
- package/examples/pdf_viewer.tsx +279 -0
- package/examples/test_gallery.js +17452 -0
- package/examples/test_gallery.tsx +285 -0
- package/examples/test_react_lazy.js +17336 -0
- package/examples/test_react_lazy.tsx +63 -0
- package/examples/test_react_listbox.js +17339 -0
- package/examples/test_react_listbox.tsx +71 -0
- package/examples/test_root_window.js +17313 -0
- package/examples/test_root_window.tsx +41 -0
- package/examples/test_tab.js +17339 -0
- package/examples/test_tab.tsx +75 -0
- package/examples/tray_demo.js +79 -0
- package/examples/tray_demo.tsx +81 -0
- package/lib/assert.js +8 -0
- package/lib/assert.ts +7 -0
- package/lib/cache-utils.js +43 -0
- package/lib/cache-utils.ts +40 -0
- package/lib/dumpRects.js +25 -0
- package/lib/dumpRects.ts +25 -0
- package/lib/fetch.js +490 -0
- package/lib/fetch.ts +502 -0
- package/lib/http-common.js +342 -0
- package/lib/http-common.ts +399 -0
- package/lib/http-server.js +416 -0
- package/lib/http-server.ts +424 -0
- package/lib/polyfill.js +156 -0
- package/lib/polyfill.ts +171 -0
- package/lib/react-qw/components/Button.js +8 -0
- package/lib/react-qw/components/CheckBox.js +31 -0
- package/lib/react-qw/components/ComboBox.js +42 -0
- package/lib/react-qw/components/DateTimePicker.js +96 -0
- package/lib/react-qw/components/Input.js +50 -0
- package/lib/react-qw/components/Link.js +43 -0
- package/lib/react-qw/components/ListBox.js +48 -0
- package/lib/react-qw/components/ListView.js +328 -0
- package/lib/react-qw/components/PathPicker.js +136 -0
- package/lib/react-qw/components/ProgressBar.js +22 -0
- package/lib/react-qw/components/RadioButton.js +31 -0
- package/lib/react-qw/components/ScrollView.js +139 -0
- package/lib/react-qw/components/Slider.js +45 -0
- package/lib/react-qw/components/Tab.js +61 -0
- package/lib/react-qw/components/Tooltip.js +55 -0
- package/lib/react-qw/components/TreeView.js +130 -0
- package/lib/react-qw/components/index.js +16 -0
- package/lib/react-qw/components/index.ts +17 -0
- package/lib/react-qw/index.js +73 -0
- package/lib/react-qw/index.ts +90 -0
- package/lib/react-qw/jsx.d.ts +34 -0
- package/lib/react-qw/layout.js +82 -0
- package/lib/react-qw/layout.ts +111 -0
- package/lib/react-qw/props.js +27 -0
- package/lib/react-qw/props.ts +36 -0
- package/lib/react-qw/reconciler.js +333 -0
- package/lib/react-qw/reconciler.ts +373 -0
- package/lib/stream.js +2 -0
- package/lib/stream.ts +22 -0
- package/lib/text-codec.js +197 -0
- package/lib/text-codec.ts +171 -0
- package/lib/text-measure.js +52 -0
- package/lib/text-measure.ts +54 -0
- package/lib/url.js +278 -0
- package/lib/url.ts +201 -0
- package/lib/vendor/web-streams/ponyfill.d.mts +829 -0
- package/lib/vendor/web-streams/ponyfill.mjs +8 -0
- package/lib/websocket.js +541 -0
- package/lib/websocket.ts +555 -0
- package/package.json +44 -0
- package/quickwin.d.ts +594 -0
- package/quickwin_const.d.ts +900 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin.exe +0 -0
- package/test/add.wasm +0 -0
- package/test/complex.wasm +0 -0
- package/test/complex_imports.wasm +0 -0
- package/test/debug/http_server_e2e.js +31 -0
- package/test/debug/minwasm.js +52 -0
- package/test/debug/mupdf_debug.js +104 -0
- package/test/debug/wasm.js +122 -0
- package/test/debug/wasm_export_debug.js +64 -0
- package/test/debug/wasm_names.js +46 -0
- package/test/global_imports.wasm +0 -0
- package/test/import_func.wasm +0 -0
- package/test/imports.wasm +0 -0
- package/test/run.js +91 -0
- package/test/run.ts +96 -0
- package/test/sjlj.wasm +0 -0
- package/test/test_basic.js +7 -0
- package/test/test_basic.ts +9 -0
- package/test/test_basic_fetch.js +34 -0
- package/test/test_basic_fetch.ts +33 -0
- package/test/test_brotli.js +48 -0
- package/test/test_brotli.ts +52 -0
- package/test/test_fetch_cache.js +169 -0
- package/test/test_fetch_cache.ts +187 -0
- package/test/test_ffi.js +157 -0
- package/test/test_ffi.ts +174 -0
- package/test/test_frame_encoding.js +128 -0
- package/test/test_frame_encoding.ts +132 -0
- package/test/test_helper.js +84 -0
- package/test/test_helper.ts +80 -0
- package/test/test_http_import.js +78 -0
- package/test/test_http_import.ts +74 -0
- package/test/test_http_server.js +142 -0
- package/test/test_http_server.ts +136 -0
- package/test/test_import_dir.js +4 -0
- package/test/test_import_dir.ts +5 -0
- package/test/test_ipv6.js +134 -0
- package/test/test_ipv6.ts +129 -0
- package/test/test_jsx.js +1314 -0
- package/test/test_meta_url.js +3 -0
- package/test/test_meta_url.ts +4 -0
- package/test/test_mupdf_render.js +69 -0
- package/test/test_mupdf_render.ts +74 -0
- package/test/test_mupdf_twice.js +77 -0
- package/test/test_mupdf_twice.ts +81 -0
- package/test/test_mupdf_wasm.js +33 -0
- package/test/test_mupdf_wasm.ts +30 -0
- package/test/test_net_event.js +57 -0
- package/test/test_net_event.ts +55 -0
- package/test/test_net_fetch.js +259 -0
- package/test/test_net_fetch.ts +238 -0
- package/test/test_net_websocket.js +180 -0
- package/test/test_net_websocket.ts +164 -0
- package/test/test_polyfill.js +144 -0
- package/test/test_polyfill.ts +159 -0
- package/test/test_react_complex.js +17440 -0
- package/test/test_react_counter.js +17331 -0
- package/test/test_react_flex.js +17643 -0
- package/test/test_react_hidden.js +17336 -0
- package/test/test_react_listview_link.js +17371 -0
- package/test/test_react_render.js +17326 -0
- package/test/test_url.js +173 -0
- package/test/test_url.ts +183 -0
- package/test/test_wasm_basic.js +85 -0
- package/test/test_wasm_basic.ts +73 -0
- package/test/test_wasm_import_global.js +41 -0
- package/test/test_wasm_import_global.ts +45 -0
- package/test/test_wasm_sjlj.js +145 -0
- package/test/test_wasm_sjlj.ts +125 -0
- package/test/test_wasm_types.js +97 -0
- package/test/test_wasm_types.ts +124 -0
- package/test/test_worker.js +21 -0
- package/test/test_worker.ts +24 -0
- package/test/test_worker_fetch.js +26 -0
- package/test/test_worker_fetch.ts +27 -0
- package/test/test_worker_net.js +51 -0
- package/test/test_worker_net.ts +54 -0
- package/test/test_worker_wasm_concurrent.js +44 -0
- package/test/test_worker_wasm_concurrent.ts +45 -0
- package/test/types.wasm +0 -0
- package/test/worker_fetch.js +13 -0
- package/test/worker_fetch.ts +13 -0
- package/test/worker_helper.js +1 -0
- package/test/worker_helper.ts +1 -0
- package/test/worker_http.js +18 -0
- package/test/worker_http.ts +20 -0
- package/test/worker_import_lib.js +12 -0
- package/test/worker_import_lib.ts +13 -0
- package/test/worker_module.js +16 -0
- package/test/worker_module.ts +17 -0
- package/test/worker_wasm_concurrent.js +22 -0
- package/test/worker_wasm_concurrent.ts +22 -0
- package/tsconfig.json +27 -0
- package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -0
- package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -0
- package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
- package/vendor/mupdf-wasm/mupdf.d.ts +939 -0
- package/vendor/mupdf-wasm/mupdf.js +3317 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
export {} // make this a module so declare global works
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface TextDecoder<T extends _Label | undefined = _Label | undefined> extends TextDecoderImpl<T> {}
|
|
5
|
+
var TextDecoder: typeof TextDecoderImpl
|
|
6
|
+
|
|
7
|
+
interface TextEncoder<T extends _Label | undefined = _Label | undefined> extends TextEncoderImpl<T> {}
|
|
8
|
+
var TextEncoder: typeof TextEncoderImpl
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const _labelMap = {
|
|
12
|
+
'utf-8': 'utf-8',
|
|
13
|
+
'utf8': 'utf-8',
|
|
14
|
+
'unicode-1-1-utf-8': 'utf-8',
|
|
15
|
+
'utf-16': 'utf-16le',
|
|
16
|
+
'utf-16le': 'utf-16le',
|
|
17
|
+
'utf-16be': 'utf-16be',
|
|
18
|
+
} as const
|
|
19
|
+
|
|
20
|
+
type _Label = keyof typeof _labelMap
|
|
21
|
+
type _Encoding = typeof _labelMap[_Label]
|
|
22
|
+
type _Result<T> = T extends _Label ? typeof _labelMap[T] : 'utf-8'
|
|
23
|
+
|
|
24
|
+
function _normalizeLabel<T extends _Label | undefined>(label: T): _Result<T> {
|
|
25
|
+
if (!label) return 'utf-8' as _Result<T>
|
|
26
|
+
const result = _labelMap[label.trim().toLowerCase() as _Label]
|
|
27
|
+
if (result) return result as _Result<T>
|
|
28
|
+
throw new RangeError(`The encoding label "${label}" is not supported`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function _decodeUTF8(buf: Uint8Array, fatal: boolean): string {
|
|
32
|
+
let out = ''
|
|
33
|
+
for (let i = 0; i < buf.length; ) {
|
|
34
|
+
const b = buf[i++]!
|
|
35
|
+
if (b < 0x80) { out += String.fromCharCode(b); continue }
|
|
36
|
+
if (b < 0xC0) { out += fatal ? (() => { throw new TypeError('unexpected continuation byte') })() : '\uFFFD'; continue }
|
|
37
|
+
let c: number, min: number
|
|
38
|
+
if (b < 0xE0) {
|
|
39
|
+
if (i >= buf.length) { out += fatal ? (() => { throw new TypeError('incomplete utf-8') })() : '\uFFFD'; break }
|
|
40
|
+
const b2 = buf[i++]!
|
|
41
|
+
if ((b2 & 0xC0) !== 0x80) { out += '\uFFFD'; continue }
|
|
42
|
+
c = ((b & 0x1F) << 6) | (b2 & 0x3F)
|
|
43
|
+
min = 0x80
|
|
44
|
+
} else if (b < 0xF0) {
|
|
45
|
+
if (i + 1 >= buf.length) { out += fatal ? (() => { throw new TypeError('incomplete utf-8') })() : '\uFFFD'; break }
|
|
46
|
+
const b2 = buf[i++]!; const b3 = buf[i++]!
|
|
47
|
+
if (((b2 & 0xC0) !== 0x80) || ((b3 & 0xC0) !== 0x80)) { out += '\uFFFD'; continue }
|
|
48
|
+
c = ((b & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F)
|
|
49
|
+
min = 0x800
|
|
50
|
+
} else if (b < 0xF8) {
|
|
51
|
+
if (i + 2 >= buf.length) { out += fatal ? (() => { throw new TypeError('incomplete utf-8') })() : '\uFFFD'; break }
|
|
52
|
+
const b2 = buf[i++]!; const b3 = buf[i++]!; const b4 = buf[i++]!
|
|
53
|
+
if (((b2 & 0xC0) !== 0x80) || ((b3 & 0xC0) !== 0x80) || ((b4 & 0xC0) !== 0x80)) { out += '\uFFFD'; continue }
|
|
54
|
+
c = ((b & 0x07) << 18) | ((b2 & 0x3F) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F)
|
|
55
|
+
min = 0x10000
|
|
56
|
+
} else { out += fatal ? (() => { throw new TypeError('invalid utf-8 start') })() : '\uFFFD'; continue }
|
|
57
|
+
if (c < min || (c >= 0xD800 && c <= 0xDFFF) || c > 0x10FFFF) {
|
|
58
|
+
out += fatal ? (() => { throw new TypeError('invalid utf-8 codepoint') })() : '\uFFFD'; continue
|
|
59
|
+
}
|
|
60
|
+
if (c <= 0xFFFF) out += String.fromCharCode(c)
|
|
61
|
+
else { c -= 0x10000; out += String.fromCharCode(0xD800 | (c >> 10), 0xDC00 | (c & 0x3FF)) }
|
|
62
|
+
}
|
|
63
|
+
return out
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function _decodeUTF16(buf: Uint8Array, le: boolean, fatal: boolean): string {
|
|
67
|
+
const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)
|
|
68
|
+
let out = ''
|
|
69
|
+
for (let i = 0; i + 1 < buf.length; i += 2) {
|
|
70
|
+
let c = dv.getUint16(i, le)
|
|
71
|
+
if (c >= 0xD800 && c <= 0xDBFF) {
|
|
72
|
+
i += 2
|
|
73
|
+
if (i + 1 >= buf.length) {
|
|
74
|
+
out += fatal ? (() => { throw new TypeError('truncated surrogate pair') })() : '\uFFFD'; break
|
|
75
|
+
}
|
|
76
|
+
const c2 = dv.getUint16(i, le)
|
|
77
|
+
if (c2 >= 0xDC00 && c2 <= 0xDFFF) {
|
|
78
|
+
out += String.fromCharCode(c, c2)
|
|
79
|
+
} else {
|
|
80
|
+
out += fatal ? (() => { throw new TypeError('invalid surrogate pair') })() : '\uFFFD'
|
|
81
|
+
i -= 2
|
|
82
|
+
}
|
|
83
|
+
} else if (c >= 0xDC00 && c <= 0xDFFF) {
|
|
84
|
+
out += fatal ? (() => { throw new TypeError('unexpected trailing surrogate') })() : '\uFFFD'
|
|
85
|
+
} else {
|
|
86
|
+
out += String.fromCharCode(c)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return out
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
class TextDecoderImpl<T extends _Label | undefined = _Label | undefined> {
|
|
93
|
+
_encoding: _Encoding
|
|
94
|
+
_fatal: boolean
|
|
95
|
+
_ignoreBOM: boolean
|
|
96
|
+
|
|
97
|
+
constructor(label?: T, options?: { fatal?: boolean, ignoreBOM?: boolean }) {
|
|
98
|
+
this._encoding = _normalizeLabel(label)
|
|
99
|
+
this._fatal = options?.fatal ?? false
|
|
100
|
+
this._ignoreBOM = options?.ignoreBOM ?? false
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
get encoding(): _Result<T> { return this._encoding as _Result<T> }
|
|
104
|
+
get fatal(): boolean { return this._fatal }
|
|
105
|
+
get ignoreBOM(): boolean { return this._ignoreBOM }
|
|
106
|
+
|
|
107
|
+
decode(buffer?: ArrayBufferView | ArrayBuffer | null): string {
|
|
108
|
+
if (!buffer) return ''
|
|
109
|
+
const arr = buffer instanceof Uint8Array ? buffer
|
|
110
|
+
: buffer instanceof ArrayBuffer ? new Uint8Array(buffer)
|
|
111
|
+
: new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)
|
|
112
|
+
|
|
113
|
+
if (this._encoding === 'utf-8') {
|
|
114
|
+
return _decodeUTF8(arr, this._fatal)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const le = this._encoding === 'utf-16le'
|
|
118
|
+
let offset = 0
|
|
119
|
+
|
|
120
|
+
if (arr.length >= 2 && !this._ignoreBOM) {
|
|
121
|
+
const bomLo = le ? arr[0]! : arr[1]!
|
|
122
|
+
const bomHi = le ? arr[1]! : arr[0]!
|
|
123
|
+
const bom = (bomHi << 8) | bomLo
|
|
124
|
+
if (bom === 0xFEFF || bom === 0xFFFE) {
|
|
125
|
+
offset = 2
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return _decodeUTF16(arr.slice(offset), le, this._fatal)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class TextEncoderImpl<T extends _Label | undefined = _Label | undefined> {
|
|
134
|
+
_encoding: _Encoding
|
|
135
|
+
|
|
136
|
+
constructor(label?: T) {
|
|
137
|
+
this._encoding = _normalizeLabel(label)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get encoding(): _Result<T> { return this._encoding as _Result<T> }
|
|
141
|
+
|
|
142
|
+
encode(input?: string): Uint8Array<ArrayBuffer> {
|
|
143
|
+
if (!input) return new Uint8Array(0)
|
|
144
|
+
|
|
145
|
+
if (this._encoding === 'utf-8') {
|
|
146
|
+
const bytes: number[] = []
|
|
147
|
+
for (let i = 0; i < input.length; i++) {
|
|
148
|
+
let c = input.charCodeAt(i)
|
|
149
|
+
if (c < 0x80) { bytes.push(c) }
|
|
150
|
+
else if (c < 0x800) { bytes.push(0xC0 | (c >> 6), 0x80 | (c & 0x3F)) }
|
|
151
|
+
else if (c < 0xD800 || c >= 0xE000) { bytes.push(0xE0 | (c >> 12), 0x80 | ((c >> 6) & 0x3F), 0x80 | (c & 0x3F)) }
|
|
152
|
+
else { i++; const c2 = input.charCodeAt(i); c = 0x10000 + ((c & 0x3FF) << 10) | (c2 & 0x3FF); bytes.push(0xF0 | (c >> 18), 0x80 | ((c >> 12) & 0x3F), 0x80 | ((c >> 6) & 0x3F), 0x80 | (c & 0x3F)) }
|
|
153
|
+
}
|
|
154
|
+
return new Uint8Array(bytes)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const le = this._encoding === 'utf-16le'
|
|
158
|
+
const buf = new ArrayBuffer(input.length * 2)
|
|
159
|
+
const dv = new DataView(buf)
|
|
160
|
+
for (let i = 0; i < input.length; i++) dv.setUint16(i * 2, input.charCodeAt(i), le)
|
|
161
|
+
return new Uint8Array(buf)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (typeof globalThis.TextDecoder === 'undefined') {
|
|
166
|
+
globalThis.TextDecoder = TextDecoderImpl
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (typeof globalThis.TextEncoder === 'undefined') {
|
|
170
|
+
globalThis.TextEncoder = TextEncoderImpl
|
|
171
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as ffi from 'ffi';
|
|
2
|
+
import * as win from 'win';
|
|
3
|
+
import * as gui from 'gui';
|
|
4
|
+
function loadProc(lib, name) {
|
|
5
|
+
const ptr = win.GetProcAddress(lib, name);
|
|
6
|
+
if (!ptr)
|
|
7
|
+
throw new Error(`Cannot load ${name}`);
|
|
8
|
+
return ptr;
|
|
9
|
+
}
|
|
10
|
+
const user32 = win.LoadLibrary('user32.dll');
|
|
11
|
+
const gdi32 = win.LoadLibrary('gdi32.dll');
|
|
12
|
+
if (!user32 || !gdi32)
|
|
13
|
+
throw new Error('Failed to load system DLLs');
|
|
14
|
+
const DrawTextW = loadProc(user32, 'DrawTextW');
|
|
15
|
+
const GetDC = loadProc(user32, 'GetDC');
|
|
16
|
+
const ReleaseDC = loadProc(user32, 'ReleaseDC');
|
|
17
|
+
const SelectObject = loadProc(gdi32, 'SelectObject');
|
|
18
|
+
const FFI_TYPE_UINT64 = ffi.FFI_TYPE_UINT64;
|
|
19
|
+
const FFI_S32 = ffi.FFI_TYPE_SINT32;
|
|
20
|
+
const FFI_PTR = ffi.FFI_TYPE_POINTER;
|
|
21
|
+
export function measureText(hdc, text, maxWidth) {
|
|
22
|
+
const textBuf = new TextEncoder('utf-16le').encode(text + '\0').buffer;
|
|
23
|
+
const rect = new ArrayBuffer(16);
|
|
24
|
+
const dv = new DataView(rect);
|
|
25
|
+
dv.setInt32(8, maxWidth, true);
|
|
26
|
+
ffi.ffiCall(DrawTextW, [FFI_TYPE_UINT64, FFI_PTR, FFI_S32, FFI_PTR, FFI_S32], [hdc, textBuf, -1, rect, 1024 /* gui.DrawTextFlag.CALCRECT */], FFI_TYPE_UINT64);
|
|
27
|
+
return { width: dv.getInt32(8, true), height: dv.getInt32(12, true) };
|
|
28
|
+
}
|
|
29
|
+
export function getButtonIdealSize(hwnd) {
|
|
30
|
+
const size = new ArrayBuffer(8);
|
|
31
|
+
gui.SendMessage(hwnd, 5633 /* gui.ButtonExtMsg.GETIDEALSIZE */, 0, ffi.bufferPtr(size));
|
|
32
|
+
const dv = new DataView(size);
|
|
33
|
+
return { width: dv.getInt32(0, true), height: dv.getInt32(4, true) };
|
|
34
|
+
}
|
|
35
|
+
export function measureTextForHwnd(hwnd, text) {
|
|
36
|
+
const hdc = ffi.ffiCall(GetDC, [FFI_TYPE_UINT64], [hwnd], FFI_TYPE_UINT64);
|
|
37
|
+
if (!hdc)
|
|
38
|
+
return { width: 0, height: 0 };
|
|
39
|
+
const hFont = gui.SendMessage(hwnd, 49 /* gui.WmMsg.GETFONT */, 0, 0);
|
|
40
|
+
const oldFont = hFont ? ffi.ffiCall(SelectObject, [FFI_TYPE_UINT64, FFI_TYPE_UINT64], [hdc, hFont], FFI_TYPE_UINT64) : 0;
|
|
41
|
+
const result = measureText(hdc, text, 0);
|
|
42
|
+
if (hFont)
|
|
43
|
+
ffi.ffiCall(SelectObject, [FFI_TYPE_UINT64, FFI_TYPE_UINT64], [hdc, oldFont], FFI_TYPE_UINT64);
|
|
44
|
+
ffi.ffiCall(ReleaseDC, [FFI_TYPE_UINT64, FFI_TYPE_UINT64], [hwnd, hdc], FFI_S32);
|
|
45
|
+
const wr = gui.GetWindowRect(hwnd);
|
|
46
|
+
const cr = gui.GetClientRect(hwnd);
|
|
47
|
+
if (wr && cr) {
|
|
48
|
+
result.width += (wr.right - wr.left) - (cr.right - cr.left);
|
|
49
|
+
result.height += (wr.bottom - wr.top) - (cr.bottom - cr.top);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as ffi from 'ffi'
|
|
2
|
+
import * as win from 'win'
|
|
3
|
+
import * as gui from 'gui'
|
|
4
|
+
|
|
5
|
+
function loadProc(lib: win.HMODULE, name: string): number {
|
|
6
|
+
const ptr = win.GetProcAddress(lib, name)
|
|
7
|
+
if (!ptr) throw new Error(`Cannot load ${name}`)
|
|
8
|
+
return ptr
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const user32 = win.LoadLibrary('user32.dll')
|
|
12
|
+
const gdi32 = win.LoadLibrary('gdi32.dll')
|
|
13
|
+
if (!user32 || !gdi32) throw new Error('Failed to load system DLLs')
|
|
14
|
+
const DrawTextW = loadProc(user32, 'DrawTextW')
|
|
15
|
+
const GetDC = loadProc(user32, 'GetDC')
|
|
16
|
+
const ReleaseDC = loadProc(user32, 'ReleaseDC')
|
|
17
|
+
const SelectObject = loadProc(gdi32, 'SelectObject')
|
|
18
|
+
|
|
19
|
+
const FFI_TYPE_UINT64 = ffi.FFI_TYPE_UINT64
|
|
20
|
+
const FFI_S32 = ffi.FFI_TYPE_SINT32
|
|
21
|
+
const FFI_PTR = ffi.FFI_TYPE_POINTER
|
|
22
|
+
|
|
23
|
+
export function measureText(hdc: number, text: string, maxWidth: number): { width: number; height: number } {
|
|
24
|
+
const textBuf = new TextEncoder('utf-16le').encode(text + '\0').buffer
|
|
25
|
+
const rect = new ArrayBuffer(16)
|
|
26
|
+
const dv = new DataView(rect)
|
|
27
|
+
dv.setInt32(8, maxWidth, true)
|
|
28
|
+
ffi.ffiCall(DrawTextW, [FFI_TYPE_UINT64, FFI_PTR, FFI_S32, FFI_PTR, FFI_S32], [hdc, textBuf, -1, rect, gui.DrawTextFlag.CALCRECT], FFI_TYPE_UINT64)
|
|
29
|
+
return { width: dv.getInt32(8, true), height: dv.getInt32(12, true) }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getButtonIdealSize(hwnd: gui.HWND): { width: number; height: number } {
|
|
33
|
+
const size = new ArrayBuffer(8)
|
|
34
|
+
gui.SendMessage(hwnd, gui.ButtonExtMsg.GETIDEALSIZE, 0, ffi.bufferPtr(size))
|
|
35
|
+
const dv = new DataView(size)
|
|
36
|
+
return { width: dv.getInt32(0, true), height: dv.getInt32(4, true) }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function measureTextForHwnd(hwnd: gui.HWND, text: string): { width: number; height: number } {
|
|
40
|
+
const hdc = ffi.ffiCall(GetDC, [FFI_TYPE_UINT64], [hwnd], FFI_TYPE_UINT64)
|
|
41
|
+
if (!hdc) return { width: 0, height: 0 }
|
|
42
|
+
const hFont = gui.SendMessage(hwnd, gui.WmMsg.GETFONT, 0, 0)
|
|
43
|
+
const oldFont = hFont ? ffi.ffiCall(SelectObject, [FFI_TYPE_UINT64, FFI_TYPE_UINT64], [hdc, hFont], FFI_TYPE_UINT64) : 0
|
|
44
|
+
const result = measureText(hdc, text, 0)
|
|
45
|
+
if (hFont) ffi.ffiCall(SelectObject, [FFI_TYPE_UINT64, FFI_TYPE_UINT64], [hdc, oldFont], FFI_TYPE_UINT64)
|
|
46
|
+
ffi.ffiCall(ReleaseDC, [FFI_TYPE_UINT64, FFI_TYPE_UINT64], [hwnd, hdc], FFI_S32)
|
|
47
|
+
const wr = gui.GetWindowRect(hwnd)
|
|
48
|
+
const cr = gui.GetClientRect(hwnd)
|
|
49
|
+
if (wr && cr) {
|
|
50
|
+
result.width += (wr.right - wr.left) - (cr.right - cr.left)
|
|
51
|
+
result.height += (wr.bottom - wr.top) - (cr.bottom - cr.top)
|
|
52
|
+
}
|
|
53
|
+
return result
|
|
54
|
+
}
|
package/lib/url.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
function _encode(s) { return encodeURIComponent(s); }
|
|
2
|
+
function _decode(s) {
|
|
3
|
+
try {
|
|
4
|
+
return decodeURIComponent(s.replace(/\+/g, ' '));
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return s;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function _normalizePath(path) {
|
|
11
|
+
if (!path)
|
|
12
|
+
return '/';
|
|
13
|
+
const parts = path.split('/');
|
|
14
|
+
const out = [];
|
|
15
|
+
let hasRoot = path[0] === '/';
|
|
16
|
+
if (hasRoot)
|
|
17
|
+
out.push('');
|
|
18
|
+
for (const p of parts) {
|
|
19
|
+
if (p === '' || p === '.')
|
|
20
|
+
continue;
|
|
21
|
+
if (p === '..') {
|
|
22
|
+
const last = out[out.length - 1];
|
|
23
|
+
if (last !== undefined && last !== '')
|
|
24
|
+
out.pop();
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
out.push(p);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (path.endsWith('/') && out[out.length - 1] !== '')
|
|
31
|
+
out.push('');
|
|
32
|
+
const result = out.join('/');
|
|
33
|
+
if (hasRoot && !result.startsWith('/'))
|
|
34
|
+
return '/' + result;
|
|
35
|
+
return result || '/';
|
|
36
|
+
}
|
|
37
|
+
function _parseURL(url) {
|
|
38
|
+
const r = { scheme: '', user: '', pass: '', host: '', port: '', path: '/', query: '', fragment: '' };
|
|
39
|
+
const fi = url.indexOf('#');
|
|
40
|
+
if (fi >= 0) {
|
|
41
|
+
r.fragment = url.slice(fi + 1);
|
|
42
|
+
url = url.slice(0, fi);
|
|
43
|
+
}
|
|
44
|
+
const qi = url.indexOf('?');
|
|
45
|
+
if (qi >= 0) {
|
|
46
|
+
r.query = url.slice(qi + 1);
|
|
47
|
+
url = url.slice(0, qi);
|
|
48
|
+
}
|
|
49
|
+
const sm = url.match(/^([a-zA-Z][a-zA-Z0-9+\-.]*):(.*)$/);
|
|
50
|
+
if (sm) {
|
|
51
|
+
r.scheme = sm[1].toLowerCase();
|
|
52
|
+
url = sm[2];
|
|
53
|
+
}
|
|
54
|
+
if (url.startsWith('//')) {
|
|
55
|
+
url = url.slice(2);
|
|
56
|
+
const si = url.indexOf('/');
|
|
57
|
+
let auth;
|
|
58
|
+
if (si < 0) {
|
|
59
|
+
auth = url;
|
|
60
|
+
url = '';
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
auth = url.slice(0, si);
|
|
64
|
+
url = url.slice(si);
|
|
65
|
+
}
|
|
66
|
+
const ai = auth.lastIndexOf('@');
|
|
67
|
+
if (ai >= 0) {
|
|
68
|
+
const ui = auth.slice(0, ai);
|
|
69
|
+
auth = auth.slice(ai + 1);
|
|
70
|
+
const ci = ui.indexOf(':');
|
|
71
|
+
if (ci >= 0) {
|
|
72
|
+
r.user = _decode(ui.slice(0, ci));
|
|
73
|
+
r.pass = _decode(ui.slice(ci + 1));
|
|
74
|
+
}
|
|
75
|
+
else
|
|
76
|
+
r.user = _decode(ui);
|
|
77
|
+
}
|
|
78
|
+
if (auth.startsWith('[')) {
|
|
79
|
+
const cb = auth.indexOf(']');
|
|
80
|
+
r.host = auth.slice(1, cb).toLowerCase();
|
|
81
|
+
const pp = auth.slice(cb + 1);
|
|
82
|
+
if (pp.startsWith(':'))
|
|
83
|
+
r.port = pp.slice(1);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const ci = auth.lastIndexOf(':');
|
|
87
|
+
if (ci >= 0 && ci === auth.lastIndexOf(':')) {
|
|
88
|
+
r.host = auth.slice(0, ci).toLowerCase();
|
|
89
|
+
r.port = auth.slice(ci + 1);
|
|
90
|
+
}
|
|
91
|
+
else
|
|
92
|
+
r.host = auth.toLowerCase();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
r.path = url;
|
|
96
|
+
if (r.host && !r.path.startsWith('/'))
|
|
97
|
+
r.path = '/' + r.path;
|
|
98
|
+
return r;
|
|
99
|
+
}
|
|
100
|
+
class URLSearchParamsImpl {
|
|
101
|
+
_list = [];
|
|
102
|
+
constructor(init) {
|
|
103
|
+
if (typeof init === 'string') {
|
|
104
|
+
const s = init.startsWith('?') ? init.slice(1) : init;
|
|
105
|
+
if (!s)
|
|
106
|
+
return;
|
|
107
|
+
for (const p of s.split('&')) {
|
|
108
|
+
const eq = p.indexOf('=');
|
|
109
|
+
if (eq < 0) {
|
|
110
|
+
this._list.push([_decode(p), '']);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
this._list.push([_decode(p.slice(0, eq)), _decode(p.slice(eq + 1))]);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (init) {
|
|
117
|
+
if (Array.isArray(init))
|
|
118
|
+
for (const [k, v] of init)
|
|
119
|
+
this._list.push([String(k), String(v)]);
|
|
120
|
+
else
|
|
121
|
+
for (const k of Object.keys(init))
|
|
122
|
+
this._list.push([k, String(init[k])]);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
append(key, value) { this._list.push([key, value]); }
|
|
126
|
+
delete(key) { this._list = this._list.filter(([k]) => k !== key); }
|
|
127
|
+
get(key) { for (const [k, v] of this._list)
|
|
128
|
+
if (k === key)
|
|
129
|
+
return v; return null; }
|
|
130
|
+
getAll(key) { return this._list.filter(([k]) => k === key).map(([, v]) => v); }
|
|
131
|
+
has(key) { return this._list.some(([k]) => k === key); }
|
|
132
|
+
set(key, value) {
|
|
133
|
+
let f = false;
|
|
134
|
+
for (let i = 0; i < this._list.length; i++) {
|
|
135
|
+
if (this._list[i][0] === key) {
|
|
136
|
+
if (!f) {
|
|
137
|
+
this._list[i][1] = value;
|
|
138
|
+
f = true;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
this._list.splice(i, 1);
|
|
142
|
+
i--;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!f)
|
|
147
|
+
this._list.push([key, value]);
|
|
148
|
+
}
|
|
149
|
+
sort() { this._list.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0); }
|
|
150
|
+
forEach(fn) { for (const [k, v] of this._list)
|
|
151
|
+
fn(v, k); }
|
|
152
|
+
*keys() { for (const [k] of this._list)
|
|
153
|
+
yield k; }
|
|
154
|
+
*values() { for (const [, v] of this._list)
|
|
155
|
+
yield v; }
|
|
156
|
+
*entries() { for (const item of this._list)
|
|
157
|
+
yield [item[0], item[1]]; }
|
|
158
|
+
*[Symbol.iterator]() { for (const item of this._list)
|
|
159
|
+
yield item; }
|
|
160
|
+
toString() { return this._list.map(([k, v]) => _encode(k) + '=' + _encode(v)).join('&'); }
|
|
161
|
+
get size() { return this._list.length; }
|
|
162
|
+
}
|
|
163
|
+
if (typeof globalThis.URLSearchParams === 'undefined') {
|
|
164
|
+
globalThis.URLSearchParams = URLSearchParamsImpl;
|
|
165
|
+
}
|
|
166
|
+
class URLImpl {
|
|
167
|
+
_scheme = '';
|
|
168
|
+
_username = '';
|
|
169
|
+
_password = '';
|
|
170
|
+
_hostname = '';
|
|
171
|
+
_port = '';
|
|
172
|
+
_pathname = '/';
|
|
173
|
+
_query = '';
|
|
174
|
+
_fragment = '';
|
|
175
|
+
_searchParams = new URLSearchParamsImpl();
|
|
176
|
+
constructor(url, base) {
|
|
177
|
+
let p = _parseURL(url);
|
|
178
|
+
if (p.scheme) {
|
|
179
|
+
this._apply(p);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (base === undefined)
|
|
183
|
+
throw new TypeError('Invalid URL');
|
|
184
|
+
const b = new URLImpl(base);
|
|
185
|
+
if (p.host) {
|
|
186
|
+
p.scheme = b._scheme;
|
|
187
|
+
this._apply(p);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (!p.path && !p.query) {
|
|
191
|
+
this._apply({
|
|
192
|
+
scheme: b._scheme, user: b._username, pass: b._password,
|
|
193
|
+
host: b._hostname, port: b._port,
|
|
194
|
+
path: b._pathname, query: b._query, fragment: p.fragment || ''
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
p.scheme = b._scheme;
|
|
199
|
+
if (!p.user)
|
|
200
|
+
p.user = b._username;
|
|
201
|
+
if (!p.pass)
|
|
202
|
+
p.pass = b._password;
|
|
203
|
+
if (!p.host)
|
|
204
|
+
p.host = b._hostname;
|
|
205
|
+
if (!p.port)
|
|
206
|
+
p.port = b._port;
|
|
207
|
+
if (!p.fragment)
|
|
208
|
+
p.fragment = '';
|
|
209
|
+
if (p.path && !p.path.startsWith('/')) {
|
|
210
|
+
const bd = b._pathname.substring(0, b._pathname.lastIndexOf('/') + 1);
|
|
211
|
+
p.path = _normalizePath(bd + p.path);
|
|
212
|
+
}
|
|
213
|
+
else if (!p.path) {
|
|
214
|
+
p.path = b._pathname;
|
|
215
|
+
if (!p.query)
|
|
216
|
+
p.query = b._query;
|
|
217
|
+
}
|
|
218
|
+
this._apply(p);
|
|
219
|
+
}
|
|
220
|
+
_apply(p) {
|
|
221
|
+
this._scheme = p.scheme;
|
|
222
|
+
this._username = p.user;
|
|
223
|
+
this._password = p.pass;
|
|
224
|
+
this._hostname = p.host;
|
|
225
|
+
this._port = p.port;
|
|
226
|
+
this._pathname = _normalizePath(p.path || '/');
|
|
227
|
+
this._query = p.query;
|
|
228
|
+
this._fragment = p.fragment;
|
|
229
|
+
this._searchParams = new URLSearchParamsImpl(this._query);
|
|
230
|
+
}
|
|
231
|
+
get href() { return this.toString(); }
|
|
232
|
+
get protocol() { return this._scheme + ':'; }
|
|
233
|
+
get hostname() { return this._hostname; }
|
|
234
|
+
get port() { return this._port; }
|
|
235
|
+
get pathname() { return this._pathname; }
|
|
236
|
+
get search() { return this._query ? '?' + this._query : ''; }
|
|
237
|
+
get hash() { return this._fragment ? '#' + this._fragment : ''; }
|
|
238
|
+
get host() {
|
|
239
|
+
const h = this._hostname;
|
|
240
|
+
const ipv6 = h.includes(':');
|
|
241
|
+
return (ipv6 ? '[' + h + ']' : h) + (this._port ? ':' + this._port : '');
|
|
242
|
+
}
|
|
243
|
+
get origin() {
|
|
244
|
+
const h = this._hostname;
|
|
245
|
+
const ipv6 = h.includes(':');
|
|
246
|
+
return this._scheme + '://' + (ipv6 ? '[' + h + ']' : h) + (this._port ? ':' + this._port : '');
|
|
247
|
+
}
|
|
248
|
+
get username() { return this._username; }
|
|
249
|
+
get password() { return this._password; }
|
|
250
|
+
get searchParams() { return this._searchParams; }
|
|
251
|
+
toString() {
|
|
252
|
+
let s = this._scheme + ':';
|
|
253
|
+
const special = ['http', 'https', 'ws', 'wss', 'ftp', 'file'];
|
|
254
|
+
if (this._hostname || (special.includes(this._scheme) && this._pathname.startsWith('/'))) {
|
|
255
|
+
s += '//';
|
|
256
|
+
if (this._username) {
|
|
257
|
+
s += _encode(this._username);
|
|
258
|
+
if (this._password)
|
|
259
|
+
s += ':' + _encode(this._password);
|
|
260
|
+
s += '@';
|
|
261
|
+
}
|
|
262
|
+
s += this._hostname.includes(':') ? '[' + this._hostname + ']' : this._hostname;
|
|
263
|
+
if (this._port)
|
|
264
|
+
s += ':' + this._port;
|
|
265
|
+
}
|
|
266
|
+
s += this._pathname;
|
|
267
|
+
if (this._query)
|
|
268
|
+
s += '?' + this._query;
|
|
269
|
+
if (this._fragment)
|
|
270
|
+
s += '#' + this._fragment;
|
|
271
|
+
return s;
|
|
272
|
+
}
|
|
273
|
+
toJSON() { return this.toString(); }
|
|
274
|
+
}
|
|
275
|
+
if (typeof globalThis.URL === 'undefined') {
|
|
276
|
+
globalThis.URL = URLImpl;
|
|
277
|
+
}
|
|
278
|
+
export {};
|