@ccheever/exact-ibex-runtime 0.1.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/package.json +63 -0
- package/src/abort/AbortController.ts +23 -0
- package/src/abort/AbortSignal.ts +152 -0
- package/src/abort/index.ts +2 -0
- package/src/accessibility.ts +12 -0
- package/src/arraybuffer-detach.ts +109 -0
- package/src/base64/base64.ts +168 -0
- package/src/base64/index.ts +1 -0
- package/src/blob/Blob.ts +259 -0
- package/src/blob/File.ts +59 -0
- package/src/blob/FormData.ts +323 -0
- package/src/blob/index.ts +3 -0
- package/src/bootstrap.ts +1946 -0
- package/src/broadcast/BroadcastChannel.ts +280 -0
- package/src/broadcast/index.ts +5 -0
- package/src/cache/Cache.ts +349 -0
- package/src/cache/CacheStorage.ts +89 -0
- package/src/cache/index.ts +27 -0
- package/src/camera/index.ts +6202 -0
- package/src/camera/processor.worker.ts +194 -0
- package/src/camera/scene.ts +195 -0
- package/src/clipboard/Clipboard.ts +129 -0
- package/src/clipboard/ClipboardItem.ts +97 -0
- package/src/clipboard/index.ts +6 -0
- package/src/clone/index.ts +1 -0
- package/src/clone/structuredClone.ts +389 -0
- package/src/clone/transferableSymbols.ts +2 -0
- package/src/compression/CompressionStream.ts +146 -0
- package/src/compression/DecompressionStream.ts +342 -0
- package/src/compression/index.ts +4 -0
- package/src/console/Console.ts +341 -0
- package/src/console/index.ts +2 -0
- package/src/core/accessibility-state.ts +263 -0
- package/src/core/accessibility.ts +184 -0
- package/src/core/agent-state.ts +37 -0
- package/src/core/diagnostics-logs.ts +144 -0
- package/src/core/host-call-bridge.ts +16 -0
- package/src/core/i18n-helpers.ts +189 -0
- package/src/core/locale-state.ts +253 -0
- package/src/core/locale.ts +95 -0
- package/src/crypto/Crypto.ts +2743 -0
- package/src/crypto/index.ts +1 -0
- package/src/diagnostics/logs.ts +7 -0
- package/src/encoding/TextDecoder.ts +1181 -0
- package/src/encoding/TextDecoderStream.ts +58 -0
- package/src/encoding/TextEncoder.ts +180 -0
- package/src/encoding/TextEncoderStream.ts +39 -0
- package/src/encoding/index.ts +8 -0
- package/src/events/CloseEvent.ts +91 -0
- package/src/events/DOMException.ts +409 -0
- package/src/events/ErrorEvent.ts +39 -0
- package/src/events/Event.ts +151 -0
- package/src/events/EventTarget.ts +280 -0
- package/src/events/FocusEvent.ts +27 -0
- package/src/events/KeyboardEvent.ts +46 -0
- package/src/events/MessageEvent.ts +61 -0
- package/src/events/ProgressEvent.ts +33 -0
- package/src/events/PromiseRejectionEvent.ts +31 -0
- package/src/events/index.ts +52 -0
- package/src/eventsource/EventSource.ts +371 -0
- package/src/eventsource/index.ts +2 -0
- package/src/fetch/Headers.ts +642 -0
- package/src/fetch/Request.ts +760 -0
- package/src/fetch/Response.ts +543 -0
- package/src/fetch/body.ts +1256 -0
- package/src/fetch/cookie-jar.ts +566 -0
- package/src/fetch/demo.ts +207 -0
- package/src/fetch/errors.ts +101 -0
- package/src/fetch/fetch.ts +2610 -0
- package/src/fetch/index.ts +101 -0
- package/src/fetch/native-bridge.ts +65 -0
- package/src/fetch/types.ts +258 -0
- package/src/filereader/FileReader.ts +236 -0
- package/src/filereader/index.ts +1 -0
- package/src/fs/Dirent.ts +39 -0
- package/src/fs/ExactFile.ts +450 -0
- package/src/fs/Stats.ts +80 -0
- package/src/fs/index.ts +944 -0
- package/src/fs/promises.ts +386 -0
- package/src/fs/shared.ts +328 -0
- package/src/http-server/index.js +697 -0
- package/src/http-server/index.ts +27 -0
- package/src/identity.generated.ts +14 -0
- package/src/index.ts +283 -0
- package/src/indexeddb/IDBCursor.ts +188 -0
- package/src/indexeddb/IDBDatabase.ts +343 -0
- package/src/indexeddb/IDBFactory.ts +269 -0
- package/src/indexeddb/IDBIndex.ts +194 -0
- package/src/indexeddb/IDBKeyRange.ts +109 -0
- package/src/indexeddb/IDBObjectStore.ts +468 -0
- package/src/indexeddb/IDBRequest.ts +163 -0
- package/src/indexeddb/IDBTransaction.ts +207 -0
- package/src/indexeddb/index.ts +34 -0
- package/src/indexeddb/utils.ts +52 -0
- package/src/inspect/index.ts +1 -0
- package/src/inspect/inspect.ts +465 -0
- package/src/internal/detect.ts +104 -0
- package/src/locale.ts +10 -0
- package/src/location/index.ts +1059 -0
- package/src/locks/LockManager.ts +460 -0
- package/src/locks/index.ts +12 -0
- package/src/media/VideoFrame.ts +58 -0
- package/src/messaging/MessageChannel.ts +31 -0
- package/src/messaging/MessagePort.ts +180 -0
- package/src/messaging/index.ts +2 -0
- package/src/messaging.ts +247 -0
- package/src/native/NativeModules.ts +354 -0
- package/src/native/index.ts +1 -0
- package/src/navigator/Navigator.ts +351 -0
- package/src/navigator/index.ts +1 -0
- package/src/node/Buffer.ts +1786 -0
- package/src/node/index.ts +4 -0
- package/src/node/path.ts +495 -0
- package/src/node/process.ts +2528 -0
- package/src/performance/Performance.ts +532 -0
- package/src/performance/index.ts +21 -0
- package/src/polyfills/array.ts +236 -0
- package/src/polyfills/arraybuffer.ts +172 -0
- package/src/polyfills/groupby.ts +85 -0
- package/src/polyfills/index.ts +85 -0
- package/src/polyfills/intl.ts +1956 -0
- package/src/polyfills/iterator.ts +479 -0
- package/src/polyfills/promise.ts +37 -0
- package/src/polyfills/set.ts +245 -0
- package/src/polyfills/string.ts +85 -0
- package/src/polyfills/typedarray.ts +110 -0
- package/src/promise-rejection-tracking.ts +464 -0
- package/src/react-native/index.ts +388 -0
- package/src/runtime-entry.ts +55 -0
- package/src/scheduling/AnimationFrame.ts +105 -0
- package/src/scheduling/IdleCallback.ts +167 -0
- package/src/scheduling/index.ts +13 -0
- package/src/security/Capabilities.ts +1146 -0
- package/src/security/Permissions.ts +392 -0
- package/src/security/capability-bits.generated.ts +63 -0
- package/src/security/index.ts +16 -0
- package/src/sqlite/Database.ts +456 -0
- package/src/sqlite/Statement.ts +206 -0
- package/src/sqlite/constants.ts +79 -0
- package/src/sqlite/errors.ts +25 -0
- package/src/sqlite/index.ts +34 -0
- package/src/sqlite/module.js +438 -0
- package/src/storage/Storage.ts +291 -0
- package/src/storage/StorageManager.ts +91 -0
- package/src/storage/index.ts +3 -0
- package/src/stream-compat.ts +47 -0
- package/src/streams/ReadableStream.ts +4131 -0
- package/src/streams/TransformStream.ts +375 -0
- package/src/streams/WritableStream.ts +866 -0
- package/src/streams/index.ts +41 -0
- package/src/timers/Timers.ts +296 -0
- package/src/timers/index.ts +11 -0
- package/src/url/URL.ts +656 -0
- package/src/url/URLPattern.ts +850 -0
- package/src/url/URLSearchParams.ts +244 -0
- package/src/url/index.ts +9 -0
- package/src/websocket/WebSocket.ts +770 -0
- package/src/websocket/WebSocketError.ts +52 -0
- package/src/websocket/WebSocketStream.ts +628 -0
- package/src/websocket/index.ts +7 -0
- package/src/window/index.ts +872 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Camera frame processor worker.
|
|
3
|
+
*
|
|
4
|
+
* The worker evaluates the user-supplied processor function source once and
|
|
5
|
+
* then executes it for each frame with a minimal `Frame` facade that matches
|
|
6
|
+
* the RFC shape closely enough for web camera labs and automated tests.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type CameraPreviewMode = "native" | "processed";
|
|
10
|
+
type CameraResizeMode = "cover" | "contain" | "stretch";
|
|
11
|
+
|
|
12
|
+
interface FrameMessage {
|
|
13
|
+
type: "frame";
|
|
14
|
+
id: number;
|
|
15
|
+
inputBuffer: ArrayBuffer;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
previewMode: CameraPreviewMode;
|
|
20
|
+
resizeMode: CameraResizeMode;
|
|
21
|
+
mirror: boolean;
|
|
22
|
+
targetWidth: number;
|
|
23
|
+
targetHeight: number;
|
|
24
|
+
isCaptureFrame: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let processorFn: ((frame: unknown) => unknown) | null = null;
|
|
28
|
+
let currentCallIndex = 0;
|
|
29
|
+
const fpsMemory = new Map<string, { timestamp: number; value: unknown }>();
|
|
30
|
+
|
|
31
|
+
function computeDrawRect(
|
|
32
|
+
sourceWidth: number,
|
|
33
|
+
sourceHeight: number,
|
|
34
|
+
targetWidth: number,
|
|
35
|
+
targetHeight: number,
|
|
36
|
+
resizeMode: CameraResizeMode,
|
|
37
|
+
) {
|
|
38
|
+
if (
|
|
39
|
+
resizeMode === "stretch" ||
|
|
40
|
+
sourceWidth <= 0 ||
|
|
41
|
+
sourceHeight <= 0 ||
|
|
42
|
+
targetWidth <= 0 ||
|
|
43
|
+
targetHeight <= 0
|
|
44
|
+
) {
|
|
45
|
+
return { x: 0, y: 0, width: targetWidth, height: targetHeight };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const sourceAspect = sourceWidth / sourceHeight;
|
|
49
|
+
const targetAspect = targetWidth / targetHeight;
|
|
50
|
+
const cover = resizeMode === "cover";
|
|
51
|
+
const sourceIsWider = sourceAspect > targetAspect;
|
|
52
|
+
|
|
53
|
+
if (cover ? sourceIsWider : !sourceIsWider) {
|
|
54
|
+
const height = targetHeight;
|
|
55
|
+
const width = height * sourceAspect;
|
|
56
|
+
return {
|
|
57
|
+
x: (targetWidth - width) / 2,
|
|
58
|
+
y: 0,
|
|
59
|
+
width,
|
|
60
|
+
height,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const width = targetWidth;
|
|
65
|
+
const height = width / sourceAspect;
|
|
66
|
+
return {
|
|
67
|
+
x: 0,
|
|
68
|
+
y: (targetHeight - height) / 2,
|
|
69
|
+
width,
|
|
70
|
+
height,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function sensorToViewRect(
|
|
75
|
+
rect: { x: number; y: number; width: number; height: number },
|
|
76
|
+
message: FrameMessage,
|
|
77
|
+
) {
|
|
78
|
+
const drawRect = computeDrawRect(
|
|
79
|
+
message.width,
|
|
80
|
+
message.height,
|
|
81
|
+
message.targetWidth,
|
|
82
|
+
message.targetHeight,
|
|
83
|
+
message.resizeMode,
|
|
84
|
+
);
|
|
85
|
+
const mapped = {
|
|
86
|
+
x: drawRect.x + (rect.x / message.width) * drawRect.width,
|
|
87
|
+
y: drawRect.y + (rect.y / message.height) * drawRect.height,
|
|
88
|
+
width: (rect.width / message.width) * drawRect.width,
|
|
89
|
+
height: (rect.height / message.height) * drawRect.height,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
if (message.mirror) {
|
|
93
|
+
mapped.x = message.targetWidth - (mapped.x + mapped.width);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
x: mapped.x / message.targetWidth,
|
|
98
|
+
y: mapped.y / message.targetHeight,
|
|
99
|
+
width: mapped.width / message.targetWidth,
|
|
100
|
+
height: mapped.height / message.targetHeight,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function runAtTargetFps(targetFps: number, fn: () => unknown): unknown {
|
|
105
|
+
const key = `${targetFps}:${currentCallIndex++}`;
|
|
106
|
+
const last = fpsMemory.get(key);
|
|
107
|
+
const frameBudget = 1000 / Math.max(1, targetFps);
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
if (last && now - last.timestamp < frameBudget) {
|
|
110
|
+
return last.value;
|
|
111
|
+
}
|
|
112
|
+
const value = fn();
|
|
113
|
+
fpsMemory.set(key, { timestamp: now, value });
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function installRuntimeGlobals(): void {
|
|
118
|
+
(globalThis as typeof globalThis & {
|
|
119
|
+
runAtTargetFps?: typeof runAtTargetFps;
|
|
120
|
+
}).runAtTargetFps = runAtTargetFps;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
installRuntimeGlobals();
|
|
124
|
+
|
|
125
|
+
self.onmessage = (event: MessageEvent) => {
|
|
126
|
+
const payload = event.data as
|
|
127
|
+
| { type: "init"; source: string }
|
|
128
|
+
| FrameMessage;
|
|
129
|
+
|
|
130
|
+
if (payload.type === "init") {
|
|
131
|
+
try {
|
|
132
|
+
// We intentionally evaluate the source in the worker so that processor
|
|
133
|
+
// execution stays off the main thread. The source comes from application
|
|
134
|
+
// code running in the same bundle, not from remote input.
|
|
135
|
+
processorFn = (0, eval)(`(${payload.source})`) as (frame: unknown) => unknown;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error("[CameraWorker] Failed to compile processor:", error);
|
|
138
|
+
processorFn = null;
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!processorFn) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
currentCallIndex = 0;
|
|
149
|
+
const input = payload.inputBuffer;
|
|
150
|
+
const writable =
|
|
151
|
+
payload.previewMode === "processed" || payload.isCaptureFrame
|
|
152
|
+
? input.slice(0)
|
|
153
|
+
: null;
|
|
154
|
+
|
|
155
|
+
const frame = {
|
|
156
|
+
width: payload.width,
|
|
157
|
+
height: payload.height,
|
|
158
|
+
timestamp: payload.timestamp,
|
|
159
|
+
orientation: payload.width >= payload.height ? "landscape-left" : "portrait",
|
|
160
|
+
isCaptureFrame: payload.isCaptureFrame,
|
|
161
|
+
pixelFormat: "bgra",
|
|
162
|
+
rawPixelFormat: "bgra",
|
|
163
|
+
toArrayBuffer() {
|
|
164
|
+
return input;
|
|
165
|
+
},
|
|
166
|
+
toWritableBuffer() {
|
|
167
|
+
return writable;
|
|
168
|
+
},
|
|
169
|
+
sensorToView(rect: { x: number; y: number; width: number; height: number }) {
|
|
170
|
+
return sensorToViewRect(rect, payload);
|
|
171
|
+
},
|
|
172
|
+
getDepthData() {
|
|
173
|
+
return null;
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const result = processorFn(frame);
|
|
178
|
+
self.postMessage(
|
|
179
|
+
{
|
|
180
|
+
type: "result",
|
|
181
|
+
id: payload.id,
|
|
182
|
+
result,
|
|
183
|
+
outputBuffer: writable ?? undefined,
|
|
184
|
+
},
|
|
185
|
+
writable ? [writable] : [],
|
|
186
|
+
);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
self.postMessage({
|
|
189
|
+
type: "error",
|
|
190
|
+
id: payload.id,
|
|
191
|
+
message: error instanceof Error ? error.message : String(error),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
export interface SceneRectLike {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface SceneBarcodeLike {
|
|
9
|
+
type: string;
|
|
10
|
+
data: string;
|
|
11
|
+
bounds: SceneRectLike;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SceneFaceLike {
|
|
15
|
+
bounds: SceneRectLike;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SceneTextLike {
|
|
19
|
+
value: string;
|
|
20
|
+
bounds: SceneRectLike;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SceneObjectLike {
|
|
24
|
+
label: string;
|
|
25
|
+
bounds: SceneRectLike;
|
|
26
|
+
confidence: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function clamp01(value: number): number {
|
|
30
|
+
return Math.min(1, Math.max(0, value));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function clampSceneRect(rect: SceneRectLike): SceneRectLike {
|
|
34
|
+
const x = clamp01(rect.x);
|
|
35
|
+
const y = clamp01(rect.y);
|
|
36
|
+
const right = clamp01(rect.x + Math.max(0, rect.width));
|
|
37
|
+
const bottom = clamp01(rect.y + Math.max(0, rect.height));
|
|
38
|
+
return {
|
|
39
|
+
x,
|
|
40
|
+
y,
|
|
41
|
+
width: Math.max(0, right - x),
|
|
42
|
+
height: Math.max(0, bottom - y),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sceneRectArea(rect: SceneRectLike): number {
|
|
47
|
+
return Math.max(0, rect.width) * Math.max(0, rect.height);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function unionSceneRects(rects: SceneRectLike[]): SceneRectLike {
|
|
51
|
+
const clamped = rects.map(clampSceneRect);
|
|
52
|
+
const minX = Math.min(...clamped.map((rect) => rect.x));
|
|
53
|
+
const minY = Math.min(...clamped.map((rect) => rect.y));
|
|
54
|
+
const maxX = Math.max(...clamped.map((rect) => rect.x + rect.width));
|
|
55
|
+
const maxY = Math.max(...clamped.map((rect) => rect.y + rect.height));
|
|
56
|
+
return clampSceneRect({
|
|
57
|
+
x: minX,
|
|
58
|
+
y: minY,
|
|
59
|
+
width: maxX - minX,
|
|
60
|
+
height: maxY - minY,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function sceneRectIntersectionArea(a: SceneRectLike, b: SceneRectLike): number {
|
|
65
|
+
const left = Math.max(a.x, b.x);
|
|
66
|
+
const top = Math.max(a.y, b.y);
|
|
67
|
+
const right = Math.min(a.x + a.width, b.x + b.width);
|
|
68
|
+
const bottom = Math.min(a.y + a.height, b.y + b.height);
|
|
69
|
+
return Math.max(0, right - left) * Math.max(0, bottom - top);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function sceneRectIou(a: SceneRectLike, b: SceneRectLike): number {
|
|
73
|
+
const intersection = sceneRectIntersectionArea(a, b);
|
|
74
|
+
if (intersection === 0) {
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
const union = sceneRectArea(a) + sceneRectArea(b) - intersection;
|
|
78
|
+
return union <= 0 ? 0 : intersection / union;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function sceneRectOverlapRatio(a: SceneRectLike, b: SceneRectLike): number {
|
|
82
|
+
const intersection = sceneRectIntersectionArea(a, b);
|
|
83
|
+
if (intersection === 0) {
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
const smallerArea = Math.min(sceneRectArea(a), sceneRectArea(b));
|
|
87
|
+
return smallerArea <= 0 ? 0 : intersection / smallerArea;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function mergeSceneObjects(candidates: SceneObjectLike[]): SceneObjectLike[] {
|
|
91
|
+
const merged: SceneObjectLike[] = [];
|
|
92
|
+
// Normalize and sort first so downstream merges keep the highest-confidence
|
|
93
|
+
// label/bounds pair when multiple detectors describe the same real-world
|
|
94
|
+
// subject (for example OCR + rectangle detection both finding a document).
|
|
95
|
+
const sorted = candidates
|
|
96
|
+
.map((candidate) => ({
|
|
97
|
+
...candidate,
|
|
98
|
+
bounds: clampSceneRect(candidate.bounds),
|
|
99
|
+
confidence: clamp01(candidate.confidence),
|
|
100
|
+
}))
|
|
101
|
+
.filter((candidate) => sceneRectArea(candidate.bounds) > 0)
|
|
102
|
+
.sort((left, right) => right.confidence - left.confidence);
|
|
103
|
+
|
|
104
|
+
for (const candidate of sorted) {
|
|
105
|
+
const existing = merged.find((entry) => {
|
|
106
|
+
if (entry.label !== candidate.label) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return (
|
|
110
|
+
sceneRectIou(entry.bounds, candidate.bounds) >= 0.4 ||
|
|
111
|
+
sceneRectOverlapRatio(entry.bounds, candidate.bounds) >= 0.8
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
if (existing) {
|
|
115
|
+
existing.bounds = unionSceneRects([existing.bounds, candidate.bounds]);
|
|
116
|
+
existing.confidence = Math.max(existing.confidence, candidate.confidence);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
merged.push(candidate);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return merged;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildDocumentCandidatesFromText(text: SceneTextLike[]): SceneObjectLike[] {
|
|
126
|
+
const candidates = text.filter((entry) => entry.value.trim().length > 0);
|
|
127
|
+
if (candidates.length === 0) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const rects = candidates.map((entry) => clampSceneRect(entry.bounds));
|
|
132
|
+
const union = unionSceneRects(rects);
|
|
133
|
+
const totalArea = rects.reduce((sum, rect) => sum + sceneRectArea(rect), 0);
|
|
134
|
+
// Treat dense or large text blocks as a document-like region. Small, isolated
|
|
135
|
+
// snippets stay labeled as plain text so agent consumers can distinguish a
|
|
136
|
+
// button label from a full sheet of paper.
|
|
137
|
+
const looksLikeDocument =
|
|
138
|
+
candidates.length > 1 || union.width >= 0.35 || union.height >= 0.28 || totalArea >= 0.08;
|
|
139
|
+
|
|
140
|
+
if (looksLikeDocument) {
|
|
141
|
+
return [
|
|
142
|
+
{
|
|
143
|
+
label: "document",
|
|
144
|
+
bounds: union,
|
|
145
|
+
confidence: candidates.length > 1 ? 0.72 : 0.64,
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return candidates.map((entry) => ({
|
|
151
|
+
label: "text",
|
|
152
|
+
bounds: clampSceneRect(entry.bounds),
|
|
153
|
+
confidence: 0.58,
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function deriveSceneObjectsFromSignals(input: {
|
|
158
|
+
barcodes?: SceneBarcodeLike[] | null;
|
|
159
|
+
faces?: SceneFaceLike[] | null;
|
|
160
|
+
text?: SceneTextLike[] | null;
|
|
161
|
+
documentBounds?: SceneRectLike[] | null;
|
|
162
|
+
}): SceneObjectLike[] {
|
|
163
|
+
const candidates: SceneObjectLike[] = [];
|
|
164
|
+
|
|
165
|
+
// The public `objects` surface is intentionally derived from the detector
|
|
166
|
+
// signals we already have. That keeps the API useful on platforms that lack
|
|
167
|
+
// a dedicated generic object detector while still reporting bounded subjects
|
|
168
|
+
// like people, documents, and barcodes in a stable way.
|
|
169
|
+
for (const barcode of input.barcodes ?? []) {
|
|
170
|
+
candidates.push({
|
|
171
|
+
label: "barcode",
|
|
172
|
+
bounds: barcode.bounds,
|
|
173
|
+
confidence: 0.94,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const face of input.faces ?? []) {
|
|
178
|
+
candidates.push({
|
|
179
|
+
label: "person",
|
|
180
|
+
bounds: face.bounds,
|
|
181
|
+
confidence: 0.86,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const bounds of input.documentBounds ?? []) {
|
|
186
|
+
candidates.push({
|
|
187
|
+
label: "document",
|
|
188
|
+
bounds,
|
|
189
|
+
confidence: 0.7,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
candidates.push(...buildDocumentCandidatesFromText(input.text ?? []));
|
|
194
|
+
return mergeSceneObjects(candidates);
|
|
195
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clipboard - Web Clipboard API implementation for Ibex runtime
|
|
3
|
+
*
|
|
4
|
+
* Provides async read/write access to the system clipboard.
|
|
5
|
+
* By default, stores data in memory. When native bridge functions
|
|
6
|
+
* (__exactClipboardRead / __exactClipboardWrite) are available,
|
|
7
|
+
* they are used for actual system clipboard access.
|
|
8
|
+
*
|
|
9
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Clipboard
|
|
10
|
+
* @see https://w3c.github.io/clipboard-apis/#clipboard-interface
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { ClipboardItem } from './ClipboardItem';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The Clipboard interface provides read and write access to the contents
|
|
17
|
+
* of the system clipboard, allowing cut, copy, and paste operations in
|
|
18
|
+
* web applications.
|
|
19
|
+
*
|
|
20
|
+
* This implementation falls back to in-memory storage when native clipboard
|
|
21
|
+
* access is not available. Native bridge functions can be plugged in via:
|
|
22
|
+
* - globalThis.__exactClipboardRead: () => string
|
|
23
|
+
* - globalThis.__exactClipboardWrite: (text: string) => void
|
|
24
|
+
*/
|
|
25
|
+
export class Clipboard {
|
|
26
|
+
/** In-memory fallback for clipboard text. */
|
|
27
|
+
#text: string = '';
|
|
28
|
+
|
|
29
|
+
/** In-memory fallback for clipboard items. */
|
|
30
|
+
#items: ClipboardItem[] = [];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Read text from the clipboard.
|
|
34
|
+
*
|
|
35
|
+
* Uses the native bridge if available (__exactClipboardRead),
|
|
36
|
+
* otherwise returns from in-memory storage.
|
|
37
|
+
*
|
|
38
|
+
* @returns A Promise resolving to the clipboard text content.
|
|
39
|
+
*/
|
|
40
|
+
async readText(): Promise<string> {
|
|
41
|
+
const g = globalThis as any;
|
|
42
|
+
if (typeof g.__exactClipboardRead === 'function') {
|
|
43
|
+
return String(g.__exactClipboardRead());
|
|
44
|
+
}
|
|
45
|
+
return this.#text;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Write text to the clipboard.
|
|
50
|
+
*
|
|
51
|
+
* Uses the native bridge if available (__exactClipboardWrite),
|
|
52
|
+
* otherwise stores in memory.
|
|
53
|
+
*
|
|
54
|
+
* @param text - The text to write to the clipboard.
|
|
55
|
+
*/
|
|
56
|
+
async writeText(text: string): Promise<void> {
|
|
57
|
+
const g = globalThis as any;
|
|
58
|
+
if (typeof g.__exactClipboardWrite === 'function') {
|
|
59
|
+
g.__exactClipboardWrite(String(text));
|
|
60
|
+
}
|
|
61
|
+
this.#text = String(text);
|
|
62
|
+
// Also update the items to keep them in sync
|
|
63
|
+
this.#items = [
|
|
64
|
+
new ClipboardItem({ 'text/plain': text }),
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Read structured data from the clipboard as ClipboardItem[].
|
|
70
|
+
*
|
|
71
|
+
* For now, returns items from the in-memory store. If only text was
|
|
72
|
+
* written, wraps it as a ClipboardItem with 'text/plain' type.
|
|
73
|
+
*
|
|
74
|
+
* @returns A Promise resolving to an array of ClipboardItems.
|
|
75
|
+
*/
|
|
76
|
+
async read(): Promise<ClipboardItem[]> {
|
|
77
|
+
// If native bridge is available, read text and wrap it
|
|
78
|
+
const g = globalThis as any;
|
|
79
|
+
if (typeof g.__exactClipboardRead === 'function') {
|
|
80
|
+
const text = String(g.__exactClipboardRead());
|
|
81
|
+
if (text) {
|
|
82
|
+
return [new ClipboardItem({ 'text/plain': text })];
|
|
83
|
+
}
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Return a copy of the stored items
|
|
88
|
+
return [...this.#items];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Write structured data to the clipboard.
|
|
93
|
+
*
|
|
94
|
+
* Stores the items in memory. If any item contains 'text/plain',
|
|
95
|
+
* also updates the text representation for readText() compatibility.
|
|
96
|
+
*
|
|
97
|
+
* @param items - An array of ClipboardItems to write.
|
|
98
|
+
*/
|
|
99
|
+
async write(items: ClipboardItem[]): Promise<void> {
|
|
100
|
+
if (!Array.isArray(items)) {
|
|
101
|
+
throw new TypeError('Clipboard.write: argument must be an array of ClipboardItems');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.#items = [...items];
|
|
105
|
+
|
|
106
|
+
// Sync text representation from the first text/plain item
|
|
107
|
+
for (const item of items) {
|
|
108
|
+
if (item.types.includes('text/plain')) {
|
|
109
|
+
try {
|
|
110
|
+
const blob = await item.getType('text/plain');
|
|
111
|
+
this.#text = await blob.text();
|
|
112
|
+
|
|
113
|
+
// Also write to native if available
|
|
114
|
+
const g = globalThis as any;
|
|
115
|
+
if (typeof g.__exactClipboardWrite === 'function') {
|
|
116
|
+
g.__exactClipboardWrite(this.#text);
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
// Ignore errors extracting text
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
get [Symbol.toStringTag](): string {
|
|
127
|
+
return 'Clipboard';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClipboardItem - Web Clipboard API ClipboardItem implementation
|
|
3
|
+
*
|
|
4
|
+
* Represents a single item on the clipboard, potentially with multiple
|
|
5
|
+
* representations (e.g., text/plain and text/html for the same content).
|
|
6
|
+
*
|
|
7
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem
|
|
8
|
+
* @see https://w3c.github.io/clipboard-apis/#clipboarditem
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A ClipboardItem represents data that can be placed on or read from
|
|
13
|
+
* the system clipboard. Each item can have multiple MIME-typed representations.
|
|
14
|
+
*/
|
|
15
|
+
export class ClipboardItem {
|
|
16
|
+
readonly #items: Map<string, Blob | string | Promise<Blob | string>>;
|
|
17
|
+
readonly #types: readonly string[];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a new ClipboardItem.
|
|
21
|
+
*
|
|
22
|
+
* @param items - A record mapping MIME types to their data. Values can be
|
|
23
|
+
* Blob, string, or Promise resolving to either.
|
|
24
|
+
* @param options - Optional settings (presentationStyle, etc.) - reserved for future use.
|
|
25
|
+
*/
|
|
26
|
+
constructor(
|
|
27
|
+
items: Record<string, Blob | string | Promise<Blob | string>>,
|
|
28
|
+
_options?: { presentationStyle?: 'unspecified' | 'inline' | 'attachment' },
|
|
29
|
+
) {
|
|
30
|
+
if (!items || typeof items !== 'object') {
|
|
31
|
+
throw new TypeError('ClipboardItem constructor: items must be an object');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const keys = Object.keys(items);
|
|
35
|
+
if (keys.length === 0) {
|
|
36
|
+
throw new TypeError('ClipboardItem constructor: items must not be empty');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.#items = new Map();
|
|
40
|
+
for (let i = 0; i < keys.length; i++) {
|
|
41
|
+
const type = keys[i];
|
|
42
|
+
this.#items.set(type, items[type]);
|
|
43
|
+
}
|
|
44
|
+
this.#types = Object.freeze([...keys]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The MIME types available for this clipboard item.
|
|
49
|
+
*/
|
|
50
|
+
get types(): readonly string[] {
|
|
51
|
+
return this.#types;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Retrieve the data for a specific MIME type as a Blob.
|
|
56
|
+
*
|
|
57
|
+
* @param type - The MIME type to retrieve.
|
|
58
|
+
* @returns A Promise resolving to a Blob containing the data.
|
|
59
|
+
* @throws DOMException with name "NotFoundError" if the type is not available.
|
|
60
|
+
*/
|
|
61
|
+
async getType(type: string): Promise<Blob> {
|
|
62
|
+
const data = this.#items.get(type);
|
|
63
|
+
if (data === undefined) {
|
|
64
|
+
throw new DOMException(
|
|
65
|
+
`ClipboardItem.getType: type '${type}' not found`,
|
|
66
|
+
'NotFoundError',
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const resolved = await data;
|
|
71
|
+
|
|
72
|
+
if (typeof resolved === 'string') {
|
|
73
|
+
return new Blob([resolved], { type });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (resolved instanceof Blob) {
|
|
77
|
+
return resolved;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
throw new TypeError(
|
|
81
|
+
`ClipboardItem.getType: unexpected data type for '${type}'`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get [Symbol.toStringTag](): string {
|
|
86
|
+
return 'ClipboardItem';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// DOMException polyfill for this module
|
|
91
|
+
class DOMException extends Error {
|
|
92
|
+
readonly code: number = 0;
|
|
93
|
+
constructor(message: string, name: string) {
|
|
94
|
+
super(message);
|
|
95
|
+
this.name = name;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { structuredClone, type StructuredSerializeOptions } from './structuredClone';
|