@glassly/miniapp 0.3.0-dev.2 → 0.3.0-dev.462

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/README.md +219 -74
  2. package/dist/background/index.d.ts +14 -8
  3. package/dist/background/index.d.ts.map +1 -1
  4. package/dist/background/index.js +4 -0
  5. package/dist/background/index.js.map +1 -1
  6. package/dist/background/register.d.ts +1 -1
  7. package/dist/background/register.js +1 -1
  8. package/dist/display/index.d.ts +23 -0
  9. package/dist/display/index.d.ts.map +1 -0
  10. package/dist/display/index.js +17 -0
  11. package/dist/display/index.js.map +1 -0
  12. package/dist/display/measurer/TextMeasurer.d.ts +160 -0
  13. package/dist/display/measurer/TextMeasurer.d.ts.map +1 -0
  14. package/dist/display/measurer/TextMeasurer.js +272 -0
  15. package/dist/display/measurer/TextMeasurer.js.map +1 -0
  16. package/dist/display/measurer/index.d.ts +10 -0
  17. package/dist/display/measurer/index.d.ts.map +1 -0
  18. package/dist/display/measurer/index.js +11 -0
  19. package/dist/display/measurer/index.js.map +1 -0
  20. package/dist/display/measurer/script-detection.d.ts +53 -0
  21. package/dist/display/measurer/script-detection.d.ts.map +1 -0
  22. package/dist/display/measurer/script-detection.js +184 -0
  23. package/dist/display/measurer/script-detection.js.map +1 -0
  24. package/dist/display/pixelArt.d.ts +22 -0
  25. package/dist/display/pixelArt.d.ts.map +1 -0
  26. package/dist/display/pixelArt.js +126 -0
  27. package/dist/display/pixelArt.js.map +1 -0
  28. package/dist/display/profiles/g1.d.ts +33 -0
  29. package/dist/display/profiles/g1.d.ts.map +1 -0
  30. package/dist/display/profiles/g1.js +214 -0
  31. package/dist/display/profiles/g1.js.map +1 -0
  32. package/dist/display/profiles/g2.d.ts +13 -0
  33. package/dist/display/profiles/g2.d.ts.map +1 -0
  34. package/dist/display/profiles/g2.js +34 -0
  35. package/dist/display/profiles/g2.js.map +1 -0
  36. package/dist/display/profiles/index.d.ts +12 -0
  37. package/dist/display/profiles/index.d.ts.map +1 -0
  38. package/dist/display/profiles/index.js +15 -0
  39. package/dist/display/profiles/index.js.map +1 -0
  40. package/dist/display/profiles/nex.d.ts +10 -0
  41. package/dist/display/profiles/nex.d.ts.map +1 -0
  42. package/dist/display/profiles/nex.js +150 -0
  43. package/dist/display/profiles/nex.js.map +1 -0
  44. package/dist/display/profiles/types.d.ts +103 -0
  45. package/dist/display/profiles/types.d.ts.map +1 -0
  46. package/dist/display/profiles/types.js +2 -0
  47. package/dist/display/profiles/types.js.map +1 -0
  48. package/dist/display/profiles/z100.d.ts +33 -0
  49. package/dist/display/profiles/z100.d.ts.map +1 -0
  50. package/dist/display/profiles/z100.js +182 -0
  51. package/dist/display/profiles/z100.js.map +1 -0
  52. package/dist/display/scene/types.d.ts +292 -0
  53. package/dist/display/scene/types.d.ts.map +1 -0
  54. package/dist/display/scene/types.js +0 -0
  55. package/dist/display/scene/types.js.map +1 -0
  56. package/dist/display/wrapper/TextWrapper.d.ts +102 -0
  57. package/dist/display/wrapper/TextWrapper.d.ts.map +1 -0
  58. package/dist/display/wrapper/TextWrapper.js +469 -0
  59. package/dist/display/wrapper/TextWrapper.js.map +1 -0
  60. package/dist/display/wrapper/index.d.ts +12 -0
  61. package/dist/display/wrapper/index.d.ts.map +1 -0
  62. package/dist/display/wrapper/index.js +12 -0
  63. package/dist/display/wrapper/index.js.map +1 -0
  64. package/dist/display/wrapper/types.d.ts +75 -0
  65. package/dist/display/wrapper/types.d.ts.map +1 -0
  66. package/dist/display/wrapper/types.js +11 -0
  67. package/dist/display/wrapper/types.js.map +1 -0
  68. package/dist/globals.d.ts +5 -5
  69. package/dist/globals.d.ts.map +1 -1
  70. package/dist/globals.js +1 -1
  71. package/dist/globals.js.map +1 -1
  72. package/dist/hardware.d.ts +4 -18
  73. package/dist/hardware.d.ts.map +1 -1
  74. package/dist/hardware.js +2 -13
  75. package/dist/hardware.js.map +1 -1
  76. package/dist/index.d.ts +16 -10
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +3 -1
  79. package/dist/index.js.map +1 -1
  80. package/dist/knownApps.d.ts +26 -0
  81. package/dist/knownApps.d.ts.map +1 -0
  82. package/dist/knownApps.js +140 -0
  83. package/dist/knownApps.js.map +1 -0
  84. package/dist/mirror/canvas.d.ts +22 -0
  85. package/dist/mirror/canvas.d.ts.map +1 -0
  86. package/dist/mirror/canvas.js +311 -0
  87. package/dist/mirror/canvas.js.map +1 -0
  88. package/dist/mirror/geometry.d.ts +69 -0
  89. package/dist/mirror/geometry.d.ts.map +1 -0
  90. package/dist/mirror/geometry.js +75 -0
  91. package/dist/mirror/geometry.js.map +1 -0
  92. package/dist/mirror/index.d.ts +3 -0
  93. package/dist/mirror/index.d.ts.map +1 -0
  94. package/dist/mirror/index.js +2 -0
  95. package/dist/mirror/index.js.map +1 -0
  96. package/dist/mirror/types.d.ts +40 -0
  97. package/dist/mirror/types.d.ts.map +1 -0
  98. package/dist/mirror/types.js +2 -0
  99. package/dist/mirror/types.js.map +1 -0
  100. package/dist/modules/auth.d.ts +1 -1
  101. package/dist/modules/auth.d.ts.map +1 -1
  102. package/dist/modules/auth.js +2 -2
  103. package/dist/modules/auth.js.map +1 -1
  104. package/dist/modules/compass.d.ts +47 -0
  105. package/dist/modules/compass.d.ts.map +1 -0
  106. package/dist/modules/compass.js +39 -0
  107. package/dist/modules/compass.js.map +1 -0
  108. package/dist/modules/display.d.ts +192 -8
  109. package/dist/modules/display.d.ts.map +1 -1
  110. package/dist/modules/display.js +40 -3
  111. package/dist/modules/display.js.map +1 -1
  112. package/dist/modules/events.d.ts +30 -2
  113. package/dist/modules/events.d.ts.map +1 -1
  114. package/dist/modules/events.js.map +1 -1
  115. package/dist/modules/input.d.ts +40 -1
  116. package/dist/modules/input.d.ts.map +1 -1
  117. package/dist/modules/input.js +45 -1
  118. package/dist/modules/input.js.map +1 -1
  119. package/dist/modules/latest-render-scheduler.d.ts +19 -0
  120. package/dist/modules/latest-render-scheduler.d.ts.map +1 -0
  121. package/dist/modules/latest-render-scheduler.js +54 -0
  122. package/dist/modules/latest-render-scheduler.js.map +1 -0
  123. package/dist/modules/lightSensor.d.ts +44 -0
  124. package/dist/modules/lightSensor.d.ts.map +1 -0
  125. package/dist/modules/lightSensor.js +33 -0
  126. package/dist/modules/lightSensor.js.map +1 -0
  127. package/dist/modules/mic.d.ts +3 -2
  128. package/dist/modules/mic.d.ts.map +1 -1
  129. package/dist/modules/mic.js +3 -2
  130. package/dist/modules/mic.js.map +1 -1
  131. package/dist/modules/miniapps.d.ts +2 -0
  132. package/dist/modules/miniapps.d.ts.map +1 -1
  133. package/dist/modules/miniapps.js.map +1 -1
  134. package/dist/modules/mirror.d.ts +16 -0
  135. package/dist/modules/mirror.d.ts.map +1 -0
  136. package/dist/modules/mirror.js +40 -0
  137. package/dist/modules/mirror.js.map +1 -0
  138. package/dist/modules/mono-text-rows.d.ts +20 -0
  139. package/dist/modules/mono-text-rows.d.ts.map +1 -0
  140. package/dist/modules/mono-text-rows.js +26 -0
  141. package/dist/modules/mono-text-rows.js.map +1 -0
  142. package/dist/modules/navigation.d.ts.map +1 -1
  143. package/dist/modules/navigation.js +21 -14
  144. package/dist/modules/navigation.js.map +1 -1
  145. package/dist/modules/phone.d.ts +26 -1
  146. package/dist/modules/phone.d.ts.map +1 -1
  147. package/dist/modules/phone.js +28 -0
  148. package/dist/modules/phone.js.map +1 -1
  149. package/dist/modules/settings.d.ts +45 -0
  150. package/dist/modules/settings.d.ts.map +1 -0
  151. package/dist/modules/settings.js +54 -0
  152. package/dist/modules/settings.js.map +1 -0
  153. package/dist/modules/system.d.ts +21 -0
  154. package/dist/modules/system.d.ts.map +1 -1
  155. package/dist/modules/system.js +12 -0
  156. package/dist/modules/system.js.map +1 -1
  157. package/dist/modules/transcription.d.ts +3 -2
  158. package/dist/modules/transcription.d.ts.map +1 -1
  159. package/dist/modules/transcription.js +3 -2
  160. package/dist/modules/transcription.js.map +1 -1
  161. package/dist/protocol.d.ts +65 -5
  162. package/dist/protocol.d.ts.map +1 -1
  163. package/dist/protocol.js +66 -5
  164. package/dist/protocol.js.map +1 -1
  165. package/dist/react/MirrorCanvas.d.ts +10 -0
  166. package/dist/react/MirrorCanvas.d.ts.map +1 -0
  167. package/dist/react/MirrorCanvas.js +23 -0
  168. package/dist/react/MirrorCanvas.js.map +1 -0
  169. package/dist/react/index.d.ts +2 -0
  170. package/dist/react/index.d.ts.map +1 -1
  171. package/dist/react/index.js +1 -0
  172. package/dist/react/index.js.map +1 -1
  173. package/dist/react/useColorScheme.d.ts +2 -3
  174. package/dist/react/useColorScheme.d.ts.map +1 -1
  175. package/dist/react/useColorScheme.js +15 -8
  176. package/dist/react/useColorScheme.js.map +1 -1
  177. package/dist/react/useRpc.js +2 -2
  178. package/dist/react/useRpc.js.map +1 -1
  179. package/dist/react/useSafeArea.d.ts.map +1 -1
  180. package/dist/react/useSafeArea.js +2 -2
  181. package/dist/react/useSafeArea.js.map +1 -1
  182. package/dist/session.d.ts +50 -4
  183. package/dist/session.d.ts.map +1 -1
  184. package/dist/session.js +35 -6
  185. package/dist/session.js.map +1 -1
  186. package/dist/transport/auto.d.ts +2 -2
  187. package/dist/transport/auto.js +2 -2
  188. package/dist/transport/dispatch.d.ts +1 -1
  189. package/dist/transport/dispatch.js +1 -1
  190. package/dist/transport/mock.d.ts +4 -4
  191. package/dist/transport/mock.d.ts.map +1 -1
  192. package/dist/transport/mock.js +31 -7
  193. package/dist/transport/mock.js.map +1 -1
  194. package/dist/ui/index.d.ts +2 -0
  195. package/dist/ui/index.d.ts.map +1 -1
  196. package/dist/ui/index.js +1 -0
  197. package/dist/ui/index.js.map +1 -1
  198. package/package.json +19 -3
  199. package/src/background/index.ts +26 -4
  200. package/src/background/register.ts +1 -1
  201. package/src/display/index.ts +83 -0
  202. package/src/display/measurer/TextMeasurer.ts +346 -0
  203. package/src/display/measurer/index.ts +21 -0
  204. package/src/display/measurer/script-detection.ts +218 -0
  205. package/src/display/pixelArt.ts +141 -0
  206. package/src/display/profiles/g1.ts +237 -0
  207. package/src/display/profiles/g2.ts +39 -0
  208. package/src/display/profiles/index.ts +28 -0
  209. package/src/display/profiles/nex.ts +166 -0
  210. package/src/display/profiles/types.ts +132 -0
  211. package/src/display/profiles/z100.ts +198 -0
  212. package/src/display/scene/types.ts +335 -0
  213. package/src/display/wrapper/TextWrapper.ts +546 -0
  214. package/src/display/wrapper/index.ts +15 -0
  215. package/src/display/wrapper/types.ts +102 -0
  216. package/src/globals.ts +5 -5
  217. package/src/hardware.ts +6 -21
  218. package/src/index.ts +34 -6
  219. package/src/knownApps.ts +170 -0
  220. package/src/mirror/canvas.ts +322 -0
  221. package/src/mirror/geometry.ts +128 -0
  222. package/src/mirror/index.ts +2 -0
  223. package/src/mirror/types.ts +38 -0
  224. package/src/modules/auth.ts +2 -2
  225. package/src/modules/compass.ts +69 -0
  226. package/src/modules/display.ts +222 -12
  227. package/src/modules/events.ts +27 -1
  228. package/src/modules/input.ts +55 -2
  229. package/src/modules/latest-render-scheduler.ts +55 -0
  230. package/src/modules/lightSensor.ts +61 -0
  231. package/src/modules/mic.ts +3 -2
  232. package/src/modules/miniapps.ts +2 -0
  233. package/src/modules/mirror.ts +42 -0
  234. package/src/modules/mono-text-rows.ts +30 -0
  235. package/src/modules/navigation.ts +20 -14
  236. package/src/modules/phone.ts +40 -1
  237. package/src/modules/settings.ts +64 -0
  238. package/src/modules/system.ts +30 -1
  239. package/src/modules/transcription.ts +3 -2
  240. package/src/protocol.ts +72 -5
  241. package/src/react/MirrorCanvas.tsx +35 -0
  242. package/src/react/index.ts +3 -0
  243. package/src/react/useColorScheme.ts +17 -8
  244. package/src/react/useRpc.ts +2 -2
  245. package/src/react/useSafeArea.ts +2 -2
  246. package/src/session.ts +78 -8
  247. package/src/transport/auto.ts +2 -2
  248. package/src/transport/dispatch.ts +1 -1
  249. package/src/transport/mock.ts +42 -11
  250. package/src/ui/index.ts +3 -0
  251. package/dist/modules/heading.d.ts +0 -16
  252. package/dist/modules/heading.d.ts.map +0 -1
  253. package/dist/modules/heading.js +0 -20
  254. package/dist/modules/heading.js.map +0 -1
  255. 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
+ }