@glassly/miniapp 0.3.0-dev.1 → 0.3.0-dev.460
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.md +219 -74
- package/dist/background/index.d.ts +14 -8
- package/dist/background/index.d.ts.map +1 -1
- package/dist/background/index.js +4 -0
- package/dist/background/index.js.map +1 -1
- package/dist/background/register.d.ts +1 -1
- package/dist/background/register.js +1 -1
- package/dist/display/index.d.ts +23 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +17 -0
- package/dist/display/index.js.map +1 -0
- package/dist/display/measurer/TextMeasurer.d.ts +160 -0
- package/dist/display/measurer/TextMeasurer.d.ts.map +1 -0
- package/dist/display/measurer/TextMeasurer.js +272 -0
- package/dist/display/measurer/TextMeasurer.js.map +1 -0
- package/dist/display/measurer/index.d.ts +10 -0
- package/dist/display/measurer/index.d.ts.map +1 -0
- package/dist/display/measurer/index.js +11 -0
- package/dist/display/measurer/index.js.map +1 -0
- package/dist/display/measurer/script-detection.d.ts +53 -0
- package/dist/display/measurer/script-detection.d.ts.map +1 -0
- package/dist/display/measurer/script-detection.js +184 -0
- package/dist/display/measurer/script-detection.js.map +1 -0
- package/dist/display/pixelArt.d.ts +22 -0
- package/dist/display/pixelArt.d.ts.map +1 -0
- package/dist/display/pixelArt.js +126 -0
- package/dist/display/pixelArt.js.map +1 -0
- package/dist/display/profiles/g1.d.ts +33 -0
- package/dist/display/profiles/g1.d.ts.map +1 -0
- package/dist/display/profiles/g1.js +214 -0
- package/dist/display/profiles/g1.js.map +1 -0
- package/dist/display/profiles/g2.d.ts +13 -0
- package/dist/display/profiles/g2.d.ts.map +1 -0
- package/dist/display/profiles/g2.js +34 -0
- package/dist/display/profiles/g2.js.map +1 -0
- package/dist/display/profiles/index.d.ts +12 -0
- package/dist/display/profiles/index.d.ts.map +1 -0
- package/dist/display/profiles/index.js +15 -0
- package/dist/display/profiles/index.js.map +1 -0
- package/dist/display/profiles/nex.d.ts +10 -0
- package/dist/display/profiles/nex.d.ts.map +1 -0
- package/dist/display/profiles/nex.js +150 -0
- package/dist/display/profiles/nex.js.map +1 -0
- package/dist/display/profiles/types.d.ts +103 -0
- package/dist/display/profiles/types.d.ts.map +1 -0
- package/dist/display/profiles/types.js +2 -0
- package/dist/display/profiles/types.js.map +1 -0
- package/dist/display/profiles/z100.d.ts +33 -0
- package/dist/display/profiles/z100.d.ts.map +1 -0
- package/dist/display/profiles/z100.js +182 -0
- package/dist/display/profiles/z100.js.map +1 -0
- package/dist/display/scene/types.d.ts +292 -0
- package/dist/display/scene/types.d.ts.map +1 -0
- package/dist/display/scene/types.js +0 -0
- package/dist/display/scene/types.js.map +1 -0
- package/dist/display/wrapper/TextWrapper.d.ts +102 -0
- package/dist/display/wrapper/TextWrapper.d.ts.map +1 -0
- package/dist/display/wrapper/TextWrapper.js +469 -0
- package/dist/display/wrapper/TextWrapper.js.map +1 -0
- package/dist/display/wrapper/index.d.ts +12 -0
- package/dist/display/wrapper/index.d.ts.map +1 -0
- package/dist/display/wrapper/index.js +12 -0
- package/dist/display/wrapper/index.js.map +1 -0
- package/dist/display/wrapper/types.d.ts +75 -0
- package/dist/display/wrapper/types.d.ts.map +1 -0
- package/dist/display/wrapper/types.js +11 -0
- package/dist/display/wrapper/types.js.map +1 -0
- package/dist/globals.d.ts +5 -5
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +1 -1
- package/dist/globals.js.map +1 -1
- package/dist/hardware.d.ts +4 -18
- package/dist/hardware.d.ts.map +1 -1
- package/dist/hardware.js +2 -13
- package/dist/hardware.js.map +1 -1
- package/dist/index.d.ts +16 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/knownApps.d.ts +26 -0
- package/dist/knownApps.d.ts.map +1 -0
- package/dist/knownApps.js +140 -0
- package/dist/knownApps.js.map +1 -0
- package/dist/mirror/canvas.d.ts +22 -0
- package/dist/mirror/canvas.d.ts.map +1 -0
- package/dist/mirror/canvas.js +311 -0
- package/dist/mirror/canvas.js.map +1 -0
- package/dist/mirror/geometry.d.ts +69 -0
- package/dist/mirror/geometry.d.ts.map +1 -0
- package/dist/mirror/geometry.js +75 -0
- package/dist/mirror/geometry.js.map +1 -0
- package/dist/mirror/index.d.ts +3 -0
- package/dist/mirror/index.d.ts.map +1 -0
- package/dist/mirror/index.js +2 -0
- package/dist/mirror/index.js.map +1 -0
- package/dist/mirror/types.d.ts +40 -0
- package/dist/mirror/types.d.ts.map +1 -0
- package/dist/mirror/types.js +2 -0
- package/dist/mirror/types.js.map +1 -0
- package/dist/modules/auth.d.ts +1 -1
- package/dist/modules/auth.d.ts.map +1 -1
- package/dist/modules/auth.js +2 -2
- package/dist/modules/auth.js.map +1 -1
- package/dist/modules/compass.d.ts +47 -0
- package/dist/modules/compass.d.ts.map +1 -0
- package/dist/modules/compass.js +39 -0
- package/dist/modules/compass.js.map +1 -0
- package/dist/modules/display.d.ts +192 -8
- package/dist/modules/display.d.ts.map +1 -1
- package/dist/modules/display.js +40 -3
- package/dist/modules/display.js.map +1 -1
- package/dist/modules/events.d.ts +30 -2
- package/dist/modules/events.d.ts.map +1 -1
- package/dist/modules/events.js.map +1 -1
- package/dist/modules/input.d.ts +40 -1
- package/dist/modules/input.d.ts.map +1 -1
- package/dist/modules/input.js +45 -1
- package/dist/modules/input.js.map +1 -1
- package/dist/modules/latest-render-scheduler.d.ts +19 -0
- package/dist/modules/latest-render-scheduler.d.ts.map +1 -0
- package/dist/modules/latest-render-scheduler.js +54 -0
- package/dist/modules/latest-render-scheduler.js.map +1 -0
- package/dist/modules/lightSensor.d.ts +44 -0
- package/dist/modules/lightSensor.d.ts.map +1 -0
- package/dist/modules/lightSensor.js +33 -0
- package/dist/modules/lightSensor.js.map +1 -0
- package/dist/modules/mic.d.ts +3 -2
- package/dist/modules/mic.d.ts.map +1 -1
- package/dist/modules/mic.js +3 -2
- package/dist/modules/mic.js.map +1 -1
- package/dist/modules/miniapps.d.ts +2 -0
- package/dist/modules/miniapps.d.ts.map +1 -1
- package/dist/modules/miniapps.js.map +1 -1
- package/dist/modules/mirror.d.ts +16 -0
- package/dist/modules/mirror.d.ts.map +1 -0
- package/dist/modules/mirror.js +40 -0
- package/dist/modules/mirror.js.map +1 -0
- package/dist/modules/mono-text-rows.d.ts +20 -0
- package/dist/modules/mono-text-rows.d.ts.map +1 -0
- package/dist/modules/mono-text-rows.js +26 -0
- package/dist/modules/mono-text-rows.js.map +1 -0
- package/dist/modules/navigation.d.ts.map +1 -1
- package/dist/modules/navigation.js +21 -14
- package/dist/modules/navigation.js.map +1 -1
- package/dist/modules/phone.d.ts +26 -1
- package/dist/modules/phone.d.ts.map +1 -1
- package/dist/modules/phone.js +28 -0
- package/dist/modules/phone.js.map +1 -1
- package/dist/modules/settings.d.ts +45 -0
- package/dist/modules/settings.d.ts.map +1 -0
- package/dist/modules/settings.js +54 -0
- package/dist/modules/settings.js.map +1 -0
- package/dist/modules/system.d.ts +21 -0
- package/dist/modules/system.d.ts.map +1 -1
- package/dist/modules/system.js +12 -0
- package/dist/modules/system.js.map +1 -1
- package/dist/modules/transcription.d.ts +3 -2
- package/dist/modules/transcription.d.ts.map +1 -1
- package/dist/modules/transcription.js +3 -2
- package/dist/modules/transcription.js.map +1 -1
- package/dist/protocol.d.ts +65 -5
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +66 -5
- package/dist/protocol.js.map +1 -1
- package/dist/react/MirrorCanvas.d.ts +10 -0
- package/dist/react/MirrorCanvas.d.ts.map +1 -0
- package/dist/react/MirrorCanvas.js +23 -0
- package/dist/react/MirrorCanvas.js.map +1 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/useColorScheme.d.ts +2 -3
- package/dist/react/useColorScheme.d.ts.map +1 -1
- package/dist/react/useColorScheme.js +15 -8
- package/dist/react/useColorScheme.js.map +1 -1
- package/dist/react/useRpc.js +2 -2
- package/dist/react/useRpc.js.map +1 -1
- package/dist/react/useSafeArea.d.ts.map +1 -1
- package/dist/react/useSafeArea.js +2 -2
- package/dist/react/useSafeArea.js.map +1 -1
- package/dist/session.d.ts +50 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +35 -6
- package/dist/session.js.map +1 -1
- package/dist/transport/auto.d.ts +2 -2
- package/dist/transport/auto.js +2 -2
- package/dist/transport/dispatch.d.ts +1 -1
- package/dist/transport/dispatch.js +1 -1
- package/dist/transport/mock.d.ts +4 -4
- package/dist/transport/mock.d.ts.map +1 -1
- package/dist/transport/mock.js +31 -7
- package/dist/transport/mock.js.map +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/package.json +19 -3
- package/src/background/index.ts +26 -4
- package/src/background/register.ts +1 -1
- package/src/display/index.ts +83 -0
- package/src/display/measurer/TextMeasurer.ts +346 -0
- package/src/display/measurer/index.ts +21 -0
- package/src/display/measurer/script-detection.ts +218 -0
- package/src/display/pixelArt.ts +141 -0
- package/src/display/profiles/g1.ts +237 -0
- package/src/display/profiles/g2.ts +39 -0
- package/src/display/profiles/index.ts +28 -0
- package/src/display/profiles/nex.ts +166 -0
- package/src/display/profiles/types.ts +132 -0
- package/src/display/profiles/z100.ts +198 -0
- package/src/display/scene/types.ts +335 -0
- package/src/display/wrapper/TextWrapper.ts +546 -0
- package/src/display/wrapper/index.ts +15 -0
- package/src/display/wrapper/types.ts +102 -0
- package/src/globals.ts +5 -5
- package/src/hardware.ts +6 -21
- package/src/index.ts +34 -6
- package/src/knownApps.ts +170 -0
- package/src/mirror/canvas.ts +322 -0
- package/src/mirror/geometry.ts +128 -0
- package/src/mirror/index.ts +2 -0
- package/src/mirror/types.ts +38 -0
- package/src/modules/auth.ts +2 -2
- package/src/modules/compass.ts +69 -0
- package/src/modules/display.ts +222 -12
- package/src/modules/events.ts +27 -1
- package/src/modules/input.ts +55 -2
- package/src/modules/latest-render-scheduler.ts +55 -0
- package/src/modules/lightSensor.ts +61 -0
- package/src/modules/mic.ts +3 -2
- package/src/modules/miniapps.ts +2 -0
- package/src/modules/mirror.ts +42 -0
- package/src/modules/mono-text-rows.ts +30 -0
- package/src/modules/navigation.ts +20 -14
- package/src/modules/phone.ts +40 -1
- package/src/modules/settings.ts +64 -0
- package/src/modules/system.ts +30 -1
- package/src/modules/transcription.ts +3 -2
- package/src/protocol.ts +72 -5
- package/src/react/MirrorCanvas.tsx +35 -0
- package/src/react/index.ts +3 -0
- package/src/react/useColorScheme.ts +17 -8
- package/src/react/useRpc.ts +2 -2
- package/src/react/useSafeArea.ts +2 -2
- package/src/session.ts +78 -8
- package/src/transport/auto.ts +2 -2
- package/src/transport/dispatch.ts +1 -1
- package/src/transport/mock.ts +42 -11
- package/src/ui/index.ts +3 -0
- package/dist/modules/heading.d.ts +0 -16
- package/dist/modules/heading.d.ts.map +0 -1
- package/dist/modules/heading.js +0 -20
- package/dist/modules/heading.js.map +0 -1
- package/src/modules/heading.ts +0 -23
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display profile for a specific glasses model.
|
|
3
|
+
* All text measurement and wrapping derives from this configuration.
|
|
4
|
+
*/
|
|
5
|
+
export interface DisplayProfile {
|
|
6
|
+
/** Unique identifier for this glasses model */
|
|
7
|
+
id: string
|
|
8
|
+
|
|
9
|
+
/** Human-readable name */
|
|
10
|
+
name: string
|
|
11
|
+
|
|
12
|
+
/** Display width in pixels */
|
|
13
|
+
displayWidthPx: number
|
|
14
|
+
|
|
15
|
+
/** Display height in pixels (if applicable) */
|
|
16
|
+
displayHeightPx?: number
|
|
17
|
+
|
|
18
|
+
/** Maximum number of lines that can be displayed */
|
|
19
|
+
maxLines: number
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* CALIBRATED line height in px for box-height→line-count math (scene
|
|
23
|
+
* pipeline). When absent, host-side height clipping is skipped entirely and
|
|
24
|
+
* text wraps to the box width with the firmware clipping vertically in-box
|
|
25
|
+
* (legacy behavior) — it is deliberately NOT derived from canvas math, which
|
|
26
|
+
* proved wrong on hardware (see profileLineHeightPx in scene/process.ts).
|
|
27
|
+
*/
|
|
28
|
+
lineHeightPx?: number
|
|
29
|
+
|
|
30
|
+
/** Maximum safe payload size in bytes (for BLE transmission) */
|
|
31
|
+
maxPayloadBytes: number
|
|
32
|
+
|
|
33
|
+
/** BLE chunk size for transmission */
|
|
34
|
+
bleChunkSize: number
|
|
35
|
+
|
|
36
|
+
/** Font metrics for text measurement */
|
|
37
|
+
fontMetrics: FontMetrics
|
|
38
|
+
|
|
39
|
+
/** Optional constraints */
|
|
40
|
+
constraints?: DisplayConstraints
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Font metrics for pixel-accurate text measurement.
|
|
45
|
+
*/
|
|
46
|
+
export interface FontMetrics {
|
|
47
|
+
/**
|
|
48
|
+
* Map of character to glyph width in pixels.
|
|
49
|
+
* Keys are single characters.
|
|
50
|
+
*/
|
|
51
|
+
glyphWidths: Map<string, number>
|
|
52
|
+
|
|
53
|
+
/** Default glyph width for unmapped characters */
|
|
54
|
+
defaultGlyphWidth: number
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Formula to convert glyph width to rendered pixel width.
|
|
58
|
+
* G1 example: (glyphWidth + 1) * 2
|
|
59
|
+
*/
|
|
60
|
+
renderFormula: (glyphWidth: number) => number
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Uniform-width scripts - verified to render all characters at same width.
|
|
64
|
+
* These are NOT averages - they are the actual uniform width in RENDERED pixels.
|
|
65
|
+
*/
|
|
66
|
+
uniformScripts: UniformScriptWidths
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Fallback configuration for unmapped characters.
|
|
70
|
+
*/
|
|
71
|
+
fallback: FallbackConfig
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Uniform width scripts - these scripts render all characters at the same width.
|
|
76
|
+
* Values are in RENDERED pixels (after applying renderFormula if applicable).
|
|
77
|
+
* Verified through hardware testing.
|
|
78
|
+
*/
|
|
79
|
+
export interface UniformScriptWidths {
|
|
80
|
+
/** Chinese, Japanese Kanji - all chars same width */
|
|
81
|
+
cjk: number
|
|
82
|
+
/** Japanese Hiragana - all chars same width */
|
|
83
|
+
hiragana: number
|
|
84
|
+
/** Japanese Katakana - all chars same width */
|
|
85
|
+
katakana: number
|
|
86
|
+
/** Korean Hangul - all chars same width */
|
|
87
|
+
korean: number
|
|
88
|
+
/** Russian, etc. - all chars same width */
|
|
89
|
+
cyrillic: number
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Fallback strategy for unmapped characters.
|
|
94
|
+
*/
|
|
95
|
+
export interface FallbackConfig {
|
|
96
|
+
/**
|
|
97
|
+
* Max known Latin width for safe fallback (in rendered pixels).
|
|
98
|
+
* Using max ensures we never overflow (worst case: slight under-utilization).
|
|
99
|
+
*/
|
|
100
|
+
latinMaxWidth: number
|
|
101
|
+
|
|
102
|
+
/** What to do with completely unknown characters */
|
|
103
|
+
unknownBehavior: "useLatinMax" | "throw" | "filter"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Optional display constraints.
|
|
108
|
+
*/
|
|
109
|
+
export interface DisplayConstraints {
|
|
110
|
+
/** Minimum characters before allowing hyphen break */
|
|
111
|
+
minCharsBeforeHyphen?: number
|
|
112
|
+
|
|
113
|
+
/** Characters that should not appear at start of line (kinsoku) */
|
|
114
|
+
noStartChars?: string[]
|
|
115
|
+
|
|
116
|
+
/** Characters that should not appear at end of line (kinsoku) */
|
|
117
|
+
noEndChars?: string[]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Script type for character classification.
|
|
122
|
+
*/
|
|
123
|
+
export type ScriptType =
|
|
124
|
+
| "latin"
|
|
125
|
+
| "cjk"
|
|
126
|
+
| "hiragana"
|
|
127
|
+
| "katakana"
|
|
128
|
+
| "korean"
|
|
129
|
+
| "cyrillic"
|
|
130
|
+
| "numbers"
|
|
131
|
+
| "punctuation"
|
|
132
|
+
| "unsupported"
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import {DisplayProfile} from "./types"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vuzix Z100 glyph widths - extracted from NotoSans-Regular.ttf at 21px
|
|
5
|
+
*
|
|
6
|
+
* These are pixel widths at the default font size used by the Z100 display.
|
|
7
|
+
* The Z100 uses Noto Sans as its system font.
|
|
8
|
+
*
|
|
9
|
+
* Extracted using: node scripts/extract-font-metrics.js NotoSans-Regular.ttf 21
|
|
10
|
+
*/
|
|
11
|
+
const Z100_GLYPH_WIDTHS: Record<string, number> = {
|
|
12
|
+
// Space and punctuation
|
|
13
|
+
" ": 5,
|
|
14
|
+
"!": 6,
|
|
15
|
+
'"': 9,
|
|
16
|
+
"#": 14,
|
|
17
|
+
"$": 12,
|
|
18
|
+
"%": 17,
|
|
19
|
+
"&": 15,
|
|
20
|
+
"'": 5,
|
|
21
|
+
"(": 6,
|
|
22
|
+
")": 6,
|
|
23
|
+
"*": 12,
|
|
24
|
+
"+": 12,
|
|
25
|
+
",": 6,
|
|
26
|
+
"-": 7, // Hyphen width - important for line breaking
|
|
27
|
+
".": 6,
|
|
28
|
+
"/": 8,
|
|
29
|
+
|
|
30
|
+
// Numbers
|
|
31
|
+
"0": 12,
|
|
32
|
+
"1": 12,
|
|
33
|
+
"2": 12,
|
|
34
|
+
"3": 12,
|
|
35
|
+
"4": 12,
|
|
36
|
+
"5": 12,
|
|
37
|
+
"6": 12,
|
|
38
|
+
"7": 12,
|
|
39
|
+
"8": 12,
|
|
40
|
+
"9": 12,
|
|
41
|
+
|
|
42
|
+
// More punctuation
|
|
43
|
+
":": 6,
|
|
44
|
+
";": 6,
|
|
45
|
+
"<": 12,
|
|
46
|
+
"=": 12,
|
|
47
|
+
">": 12,
|
|
48
|
+
"?": 9,
|
|
49
|
+
"@": 19,
|
|
50
|
+
|
|
51
|
+
// Uppercase
|
|
52
|
+
"A": 13,
|
|
53
|
+
"B": 14,
|
|
54
|
+
"C": 13,
|
|
55
|
+
"D": 15,
|
|
56
|
+
"E": 12,
|
|
57
|
+
"F": 11,
|
|
58
|
+
"G": 15,
|
|
59
|
+
"H": 16,
|
|
60
|
+
"I": 7,
|
|
61
|
+
"J": 6,
|
|
62
|
+
"K": 13,
|
|
63
|
+
"L": 11,
|
|
64
|
+
"M": 19,
|
|
65
|
+
"N": 16,
|
|
66
|
+
"O": 16,
|
|
67
|
+
"P": 13,
|
|
68
|
+
"Q": 16,
|
|
69
|
+
"R": 13,
|
|
70
|
+
"S": 12,
|
|
71
|
+
"T": 12,
|
|
72
|
+
"U": 15,
|
|
73
|
+
"V": 13,
|
|
74
|
+
"W": 20,
|
|
75
|
+
"X": 12,
|
|
76
|
+
"Y": 12,
|
|
77
|
+
"Z": 12,
|
|
78
|
+
|
|
79
|
+
// Brackets & special
|
|
80
|
+
"[": 7,
|
|
81
|
+
"\\": 8,
|
|
82
|
+
"]": 7,
|
|
83
|
+
"^": 12,
|
|
84
|
+
"_": 9,
|
|
85
|
+
"`": 6,
|
|
86
|
+
|
|
87
|
+
// Lowercase
|
|
88
|
+
"a": 12,
|
|
89
|
+
"b": 13,
|
|
90
|
+
"c": 10,
|
|
91
|
+
"d": 13,
|
|
92
|
+
"e": 12,
|
|
93
|
+
"f": 7,
|
|
94
|
+
"g": 13,
|
|
95
|
+
"h": 13,
|
|
96
|
+
"i": 5,
|
|
97
|
+
"j": 5,
|
|
98
|
+
"k": 11,
|
|
99
|
+
"l": 5,
|
|
100
|
+
"m": 20,
|
|
101
|
+
"n": 13,
|
|
102
|
+
"o": 13,
|
|
103
|
+
"p": 13,
|
|
104
|
+
"q": 13,
|
|
105
|
+
"r": 9,
|
|
106
|
+
"s": 10,
|
|
107
|
+
"t": 8,
|
|
108
|
+
"u": 13,
|
|
109
|
+
"v": 11,
|
|
110
|
+
"w": 17,
|
|
111
|
+
"x": 11,
|
|
112
|
+
"y": 11,
|
|
113
|
+
"z": 10,
|
|
114
|
+
|
|
115
|
+
// More special
|
|
116
|
+
"{": 8,
|
|
117
|
+
"|": 12,
|
|
118
|
+
"}": 8,
|
|
119
|
+
"~": 12,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Vuzix Z100 Smart Glasses Display Profile
|
|
124
|
+
*
|
|
125
|
+
* The Z100 has a green monochrome display with 640x480 resolution.
|
|
126
|
+
* It uses the Vuzix Ultralite SDK and Noto Sans font family.
|
|
127
|
+
*
|
|
128
|
+
* Display specs:
|
|
129
|
+
* - Resolution: 640x480
|
|
130
|
+
* - Usable text width: ~390px (empirically tested - SDK applies margins)
|
|
131
|
+
* - Color: Green monochrome
|
|
132
|
+
* - Max text lines: 7
|
|
133
|
+
* - Font: Noto Sans Regular at 21px
|
|
134
|
+
* - No bitmap support via text commands
|
|
135
|
+
* - Adjustable brightness
|
|
136
|
+
*
|
|
137
|
+
* NOTE: The Vuzix Ultralite SDK handles text rendering internally and applies
|
|
138
|
+
* its own margins/padding. The usable text width is significantly less than
|
|
139
|
+
* the physical 640px resolution. Empirical testing shows ~42 characters of
|
|
140
|
+
* mixed text (387px measured) fits on one line before the SDK wraps.
|
|
141
|
+
*/
|
|
142
|
+
export const Z100_PROFILE: DisplayProfile = {
|
|
143
|
+
id: "vuzix-z100",
|
|
144
|
+
name: "Vuzix Z100",
|
|
145
|
+
|
|
146
|
+
// Display dimensions
|
|
147
|
+
// Empirically tested: ~42 chars of mixed text (387px) fits before SDK wraps
|
|
148
|
+
// Using 390px to account for measurement variance
|
|
149
|
+
displayWidthPx: 390,
|
|
150
|
+
maxLines: 7,
|
|
151
|
+
|
|
152
|
+
// BLE constraints
|
|
153
|
+
// Z100 uses Vuzix Ultralite SDK which handles chunking internally
|
|
154
|
+
// These are conservative values for direct BLE if needed
|
|
155
|
+
maxPayloadBytes: 512,
|
|
156
|
+
bleChunkSize: 180,
|
|
157
|
+
|
|
158
|
+
// Font metrics - Noto Sans Regular at 21px
|
|
159
|
+
fontMetrics: {
|
|
160
|
+
glyphWidths: new Map(Object.entries(Z100_GLYPH_WIDTHS)),
|
|
161
|
+
defaultGlyphWidth: 12, // Average width from extracted metrics
|
|
162
|
+
// Z100 uses direct pixel widths, no render formula needed
|
|
163
|
+
renderFormula: (glyphWidth: number) => glyphWidth,
|
|
164
|
+
|
|
165
|
+
// Uniform-width scripts for CJK and other non-Latin
|
|
166
|
+
// Noto Sans CJK characters are typically wider
|
|
167
|
+
uniformScripts: {
|
|
168
|
+
cjk: 21, // Full-width CJK characters
|
|
169
|
+
hiragana: 21,
|
|
170
|
+
katakana: 21,
|
|
171
|
+
korean: 21,
|
|
172
|
+
cyrillic: 14, // Cyrillic is similar to Latin in Noto Sans
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
fallback: {
|
|
176
|
+
latinMaxWidth: 20, // 'W' and 'm' are the widest at 20px
|
|
177
|
+
unknownBehavior: "useLatinMax",
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
constraints: {
|
|
182
|
+
minCharsBeforeHyphen: 3,
|
|
183
|
+
noStartChars: [".", ",", "!", "?", ":", ";", ")", "]", "}", "。", ",", "!", "?", ":", ";", ")", "】", "」"],
|
|
184
|
+
noEndChars: ["(", "[", "{", "(", "【", "「"],
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get the hyphen width for Z100 in pixels.
|
|
190
|
+
* Used for line breaking calculations.
|
|
191
|
+
*/
|
|
192
|
+
export const Z100_HYPHEN_WIDTH_PX = 7
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get the space width for Z100 in pixels.
|
|
196
|
+
* Used for column alignment calculations.
|
|
197
|
+
*/
|
|
198
|
+
export const Z100_SPACE_WIDTH_PX = 5
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scene pipeline types — the host half of the miniapp `render()` display API.
|
|
3
|
+
*
|
|
4
|
+
* A miniapp describes a full frame ("scene") of positioned elements; the host
|
|
5
|
+
* clamps/budgets/wraps it, diffs it against the last frame sent to the device,
|
|
6
|
+
* and ships ONE annotated SceneFrame over the bridge. SGCs either walk the
|
|
7
|
+
* per-element annotations (G2, Mentra Display) or serialize the whole frame
|
|
8
|
+
* (full-frame devices). Spec: notes/miniapp-display-render-implementation-spec.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface SceneBox {
|
|
12
|
+
x: number
|
|
13
|
+
y: number
|
|
14
|
+
w: number
|
|
15
|
+
h: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Canvas corner a box's x/y are measured from (insets grow inward). */
|
|
19
|
+
export type SceneAnchor = "top-left" | "top-right" | "bottom-left" | "bottom-right"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A box as it arrives from the miniapp SDK: optionally corner-anchored.
|
|
23
|
+
* `resolveAnchors` turns these into plain top-left SceneBoxes against the
|
|
24
|
+
* device's real canvas before any processing/degrading — anchors never reach
|
|
25
|
+
* FrameElement, the differ, or the wire.
|
|
26
|
+
*/
|
|
27
|
+
export interface SceneBoxInput extends SceneBox {
|
|
28
|
+
anchor?: SceneAnchor
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type SceneBreakMode = "character" | "character-no-hyphen" | "word" | "strict-word"
|
|
32
|
+
|
|
33
|
+
export interface SceneTextStyle {
|
|
34
|
+
/** Border width in px (0/absent = none). */
|
|
35
|
+
border?: number
|
|
36
|
+
/** Border corner radius in px. */
|
|
37
|
+
radius?: number
|
|
38
|
+
/** What happens to text that doesn't fit the box after wrapping. Default "clip". */
|
|
39
|
+
overflow?: "clip" | "ellipsis"
|
|
40
|
+
/** Line-break policy for wrapping. */
|
|
41
|
+
breakMode?: SceneBreakMode
|
|
42
|
+
/**
|
|
43
|
+
* "mono": fixed-pitch at `fontSize` px, lines taken as given (no host wrap).
|
|
44
|
+
* "builtin": drawn by the glasses' own 20 px font engine (Glassly CFW G2);
|
|
45
|
+
* lines taken as given, other devices fall back to the default face.
|
|
46
|
+
*/
|
|
47
|
+
font?: "default" | "mono" | "builtin"
|
|
48
|
+
fontSize?: number
|
|
49
|
+
/** Gray level 0..15 for device-drawn text. */
|
|
50
|
+
color?: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface SceneRectStyle {
|
|
54
|
+
border?: number
|
|
55
|
+
radius?: number
|
|
56
|
+
/** Solid fill instead of an outline. */
|
|
57
|
+
fill?: boolean
|
|
58
|
+
/** Gray level 0..15 (15 = brightest). Default 15. */
|
|
59
|
+
color?: number
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Closed shapes (circle, triangle, quad): outline of `border` px, or filled. */
|
|
63
|
+
export interface SceneShapeStyle {
|
|
64
|
+
border?: number
|
|
65
|
+
fill?: boolean
|
|
66
|
+
color?: number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Images: gray level 0..15 the pixels scale to (15 = as drawn); tweens on devices that fade textures. */
|
|
70
|
+
export interface SceneImageStyle {
|
|
71
|
+
color?: number
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Open strokes (line, bezier, arc). */
|
|
75
|
+
export interface SceneStrokeStyle {
|
|
76
|
+
width?: number
|
|
77
|
+
color?: number
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ScenePoint {
|
|
81
|
+
x: number
|
|
82
|
+
y: number
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type SceneEasing = "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out"
|
|
86
|
+
|
|
87
|
+
/** Animate geometry changes of an element with a stable id (devices with `animation`). */
|
|
88
|
+
export interface SceneTransition {
|
|
89
|
+
durationMs: number
|
|
90
|
+
easing?: SceneEasing
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type SceneShape = "rect" | "circle" | "arc" | "pie" | "line" | "triangle" | "quad" | "bezier" | "svg"
|
|
94
|
+
|
|
95
|
+
/** A scene element as it arrives from the miniapp SDK (pre-processing). */
|
|
96
|
+
export type SceneElementInput = (
|
|
97
|
+
| {type: "text"; id?: string; box: SceneBoxInput; text: string; style?: SceneTextStyle; transition?: SceneTransition}
|
|
98
|
+
| {
|
|
99
|
+
type: "image"
|
|
100
|
+
id?: string
|
|
101
|
+
box: SceneBoxInput
|
|
102
|
+
data: string
|
|
103
|
+
style?: SceneImageStyle
|
|
104
|
+
transition?: SceneTransition
|
|
105
|
+
}
|
|
106
|
+
| {type: "rect"; id?: string; box: SceneBoxInput; style?: SceneRectStyle; transition?: SceneTransition}
|
|
107
|
+
| {type: "circle"; id?: string; box: SceneBoxInput; style?: SceneShapeStyle; transition?: SceneTransition}
|
|
108
|
+
| {
|
|
109
|
+
type: "arc"
|
|
110
|
+
id?: string
|
|
111
|
+
box: SceneBoxInput
|
|
112
|
+
startDeg: number
|
|
113
|
+
endDeg: number
|
|
114
|
+
style?: SceneStrokeStyle
|
|
115
|
+
transition?: SceneTransition
|
|
116
|
+
}
|
|
117
|
+
| {
|
|
118
|
+
type: "pie"
|
|
119
|
+
id?: string
|
|
120
|
+
box: SceneBoxInput
|
|
121
|
+
startDeg: number
|
|
122
|
+
endDeg: number
|
|
123
|
+
style?: {color?: number}
|
|
124
|
+
transition?: SceneTransition
|
|
125
|
+
}
|
|
126
|
+
| {
|
|
127
|
+
type: "line"
|
|
128
|
+
id?: string
|
|
129
|
+
points: ScenePoint[]
|
|
130
|
+
box?: SceneBoxInput
|
|
131
|
+
style?: SceneStrokeStyle
|
|
132
|
+
transition?: SceneTransition
|
|
133
|
+
}
|
|
134
|
+
| {
|
|
135
|
+
type: "triangle"
|
|
136
|
+
id?: string
|
|
137
|
+
points: ScenePoint[]
|
|
138
|
+
box?: SceneBoxInput
|
|
139
|
+
style?: SceneShapeStyle
|
|
140
|
+
transition?: SceneTransition
|
|
141
|
+
}
|
|
142
|
+
| {
|
|
143
|
+
type: "quad"
|
|
144
|
+
id?: string
|
|
145
|
+
points: ScenePoint[]
|
|
146
|
+
box?: SceneBoxInput
|
|
147
|
+
style?: SceneShapeStyle
|
|
148
|
+
transition?: SceneTransition
|
|
149
|
+
}
|
|
150
|
+
| {
|
|
151
|
+
type: "bezier"
|
|
152
|
+
id?: string
|
|
153
|
+
points: ScenePoint[]
|
|
154
|
+
box?: SceneBoxInput
|
|
155
|
+
style?: SceneStrokeStyle
|
|
156
|
+
transition?: SceneTransition
|
|
157
|
+
}
|
|
158
|
+
| {
|
|
159
|
+
type: "svg"
|
|
160
|
+
id?: string
|
|
161
|
+
box: SceneBoxInput
|
|
162
|
+
d: string
|
|
163
|
+
scale?: number
|
|
164
|
+
fillRule?: "nonzero" | "evenodd"
|
|
165
|
+
style?: {color?: number}
|
|
166
|
+
transition?: SceneTransition
|
|
167
|
+
}
|
|
168
|
+
) & {
|
|
169
|
+
/** Clockwise degrees, unwrapped (0 to 360 makes a full turn). Geometric shapes and SVG only. */
|
|
170
|
+
rotation?: number
|
|
171
|
+
/** Fixed pivot in canvas pixels; defaults to the target box center. */
|
|
172
|
+
pivot?: ScenePoint
|
|
173
|
+
/** Stereo depth in canvas pixels (±MAX_SCENE_DEPTH): positive is nearer the viewer. */
|
|
174
|
+
depth?: number
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export type SceneElementType = SceneElementInput["type"]
|
|
178
|
+
|
|
179
|
+
/** Largest per-lens disparity an element may ask for, in canvas pixels. */
|
|
180
|
+
export const MAX_SCENE_DEPTH = 16
|
|
181
|
+
|
|
182
|
+
export type SceneElementStyle =
|
|
183
|
+
| SceneTextStyle
|
|
184
|
+
| SceneRectStyle
|
|
185
|
+
| SceneShapeStyle
|
|
186
|
+
| SceneStrokeStyle
|
|
187
|
+
| SceneImageStyle
|
|
188
|
+
| {color?: number}
|
|
189
|
+
|
|
190
|
+
/** Elements positioned by a point list; the host derives `box` (their bounding box) before processing. */
|
|
191
|
+
export const POINT_SHAPE_TYPES: readonly SceneElementType[] = ["line", "triangle", "quad", "bezier"]
|
|
192
|
+
|
|
193
|
+
/** Point counts each point shape accepts. */
|
|
194
|
+
export const POINT_SHAPE_ARITY: Record<string, readonly number[]> = {
|
|
195
|
+
line: [2],
|
|
196
|
+
triangle: [3],
|
|
197
|
+
quad: [4],
|
|
198
|
+
bezier: [3, 4],
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export type SceneChange = "created" | "updated" | "moved" | "unchanged"
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* A processed element inside a SceneFrame: id definitely assigned, box clamped,
|
|
205
|
+
* text pre-wrapped (newlines inserted), annotated with what changed vs the last
|
|
206
|
+
* frame sent to this device.
|
|
207
|
+
*/
|
|
208
|
+
export interface FrameElement {
|
|
209
|
+
/** SVG path and its checked firmware command stream (Q4 coordinates). */
|
|
210
|
+
d?: string
|
|
211
|
+
path?: number[]
|
|
212
|
+
pathScale?: number
|
|
213
|
+
fillRule?: "nonzero" | "evenodd"
|
|
214
|
+
rotation?: number
|
|
215
|
+
pivot?: ScenePoint
|
|
216
|
+
/** Stereo disparity in px (left lens +depth, right lens -depth); absent = the frame's plane. */
|
|
217
|
+
depth?: number
|
|
218
|
+
|
|
219
|
+
id: string
|
|
220
|
+
type: SceneElementType
|
|
221
|
+
box: SceneBox
|
|
222
|
+
text?: string
|
|
223
|
+
/** Image pixels, base64 (PNG documented; SGCs decode → re-encode to wire format and scale to box). */
|
|
224
|
+
data?: string
|
|
225
|
+
style?: SceneElementStyle
|
|
226
|
+
/** Point shapes: the points, in canvas pixels; `box` is their bounding box. */
|
|
227
|
+
points?: ScenePoint[]
|
|
228
|
+
startDeg?: number
|
|
229
|
+
endDeg?: number
|
|
230
|
+
transition?: SceneTransition
|
|
231
|
+
change: SceneChange
|
|
232
|
+
/** Hash of the element's content (text/data + style) — lets SGCs skip BLE re-upload without retaining pixels. */
|
|
233
|
+
contentHash: string
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** The unit that crosses the JS→native bridge: one whole frame per render(). */
|
|
237
|
+
export interface SceneFrame {
|
|
238
|
+
appId: string
|
|
239
|
+
view: "main" | "dashboard"
|
|
240
|
+
/** Bumps on replay / app switch / reconnect; a bumped epoch bypasses native dedup. */
|
|
241
|
+
sceneEpoch: number
|
|
242
|
+
/**
|
|
243
|
+
* Host-wide monotonic frame number. Native reports it back in
|
|
244
|
+
* `scene_frame_presented` once the device has the frame; a report for N
|
|
245
|
+
* also covers every earlier frame (the native pacer folds unsent frames
|
|
246
|
+
* into the next one, so their content is on the glasses by then).
|
|
247
|
+
*/
|
|
248
|
+
seq: number
|
|
249
|
+
/** True when the device is expected to rebuild from scratch (all elements arrive "created"). */
|
|
250
|
+
replay?: boolean
|
|
251
|
+
elements: FrameElement[]
|
|
252
|
+
/** Element ids present in the previous frame but absent now — host-computed; apps never send removes. */
|
|
253
|
+
removed: string[]
|
|
254
|
+
/** Canvas the boxes were clamped against — lets a full-frame SGC place a legacy-sized frame correctly. */
|
|
255
|
+
canvas?: {width: number; height: number}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Typed display capabilities block (host-side mirror of the SDK's
|
|
260
|
+
* DisplayCapabilities). All limits are DATA — the pipeline acts on them
|
|
261
|
+
* generically; no device names appear in pipeline code.
|
|
262
|
+
*/
|
|
263
|
+
export interface SceneDisplayCapabilities {
|
|
264
|
+
/** Public drawable canvas in px (the device's safe area, not the physical panel). */
|
|
265
|
+
width: number
|
|
266
|
+
height: number
|
|
267
|
+
/** False ⇒ scenes degrade to legacy text layouts host-side; only sugar-shaped content renders. */
|
|
268
|
+
canPosition: boolean
|
|
269
|
+
/** Text-element budget. Rects share this pool on container-based devices. */
|
|
270
|
+
maxTextElements: number
|
|
271
|
+
maxImageElements: number
|
|
272
|
+
/** Max per-image dimensions the device accepts (box-level check), if limited. */
|
|
273
|
+
maxImagePx?: {width: number; height: number}
|
|
274
|
+
shapes: SceneShape[]
|
|
275
|
+
/** True ⇒ element transitions animate on the device. */
|
|
276
|
+
animation: boolean
|
|
277
|
+
rotation?: boolean
|
|
278
|
+
/** True ⇒ per-element `depth` is drawn as a lens disparity (otherwise stripped). */
|
|
279
|
+
depth?: boolean
|
|
280
|
+
intensityLevels: number
|
|
281
|
+
partialUpdate: boolean
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Result of processing a scene — feeds the awaitable render() result. */
|
|
285
|
+
export interface SceneProcessResult {
|
|
286
|
+
frame: SceneFrame
|
|
287
|
+
degraded: boolean
|
|
288
|
+
/** Ids (or synthesized ids) of elements dropped by budget/bounds/image limits. */
|
|
289
|
+
dropped: string[]
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Cheap stable string hash (FNV-1a, 32-bit) — content identity for diff + native dedup. */
|
|
293
|
+
export function contentHash(...parts: (string | number | undefined)[]): string {
|
|
294
|
+
let h = 0x811c9dc5
|
|
295
|
+
for (const part of parts) {
|
|
296
|
+
const s = part === undefined ? "" : String(part)
|
|
297
|
+
for (let i = 0; i < s.length; i++) {
|
|
298
|
+
h ^= s.charCodeAt(i)
|
|
299
|
+
h = Math.imul(h, 0x01000193)
|
|
300
|
+
}
|
|
301
|
+
h ^= 0x1f
|
|
302
|
+
h = Math.imul(h, 0x01000193)
|
|
303
|
+
}
|
|
304
|
+
return (h >>> 0).toString(36)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function boxesEqual(a: SceneBox, b: SceneBox): boolean {
|
|
308
|
+
return a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Content identity of an element (text/data/points/angles + style + depth),
|
|
313
|
+
* excluding its box. A nonzero depth joins the hash so a depth change is a
|
|
314
|
+
* change (the box alone does not move); zero/absent leaves the hash as before.
|
|
315
|
+
*/
|
|
316
|
+
export function elementContentHash(el: {
|
|
317
|
+
type: SceneElementType
|
|
318
|
+
text?: string
|
|
319
|
+
data?: string
|
|
320
|
+
style?: SceneElementStyle
|
|
321
|
+
points?: ScenePoint[]
|
|
322
|
+
startDeg?: number
|
|
323
|
+
endDeg?: number
|
|
324
|
+
depth?: number
|
|
325
|
+
}): string {
|
|
326
|
+
const style = el.style ?? {}
|
|
327
|
+
const styleKey = Object.keys(style)
|
|
328
|
+
.sort()
|
|
329
|
+
.map((k) => `${k}=${(style as Record<string, unknown>)[k]}`)
|
|
330
|
+
.join(",")
|
|
331
|
+
const pointsKey = el.points?.map((p) => `${p.x},${p.y}`).join(";")
|
|
332
|
+
const angleKey = el.startDeg !== undefined || el.endDeg !== undefined ? `${el.startDeg}-${el.endDeg}` : undefined
|
|
333
|
+
const depthKey = el.depth ? [`depth=${el.depth}`] : []
|
|
334
|
+
return contentHash(el.type, el.text, el.data, styleKey, pointsKey, angleKey, ...depthKey)
|
|
335
|
+
}
|