@glassly/miniapp 0.3.0-dev.2 → 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.
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,141 @@
1
+ /**
2
+ * Pixel-art icons for scene `image` elements.
3
+ *
4
+ * An icon is a list of equal-length rows, one character per pixel: "." is
5
+ * black (gray 0), "#" is full ink (15), and a hex digit is that gray level.
6
+ * The rows encode to a grayscale PNG once per icon (base64, memoized by
7
+ * identity), so every element built from the same rows carries byte-identical
8
+ * `data` and the glasses cache one texture for all of them.
9
+ */
10
+ import type {SceneElementInput} from "./scene/types"
11
+
12
+ export type PixelArt = readonly string[]
13
+
14
+ const encoded = new WeakMap<PixelArt, string>()
15
+
16
+ export function pixelArtSize(rows: PixelArt): {w: number; h: number} {
17
+ return {w: rows[0]?.length ?? 0, h: rows.length}
18
+ }
19
+
20
+ /** Gray level 0..15 of one pixel character. */
21
+ export function pixelLevel(ch: string): number {
22
+ if (ch === ".") return 0
23
+ if (ch === "#") return 15
24
+ const level = parseInt(ch, 16)
25
+ if (Number.isNaN(level)) throw new Error(`pixel art: unknown pixel "${ch}"`)
26
+ return level
27
+ }
28
+
29
+ /** The icon as a base64 8-bit grayscale PNG. */
30
+ export function pixelArtPng(rows: PixelArt): string {
31
+ const cached = encoded.get(rows)
32
+ if (cached !== undefined) return cached
33
+ const {w, h} = pixelArtSize(rows)
34
+ if (w === 0 || h === 0) throw new Error("pixel art: empty icon")
35
+ const raw = new Uint8Array((w + 1) * h)
36
+ rows.forEach((row, y) => {
37
+ if (row.length !== w) throw new Error(`pixel art: row ${y} is ${row.length} wide, expected ${w}`)
38
+ for (let x = 0; x < w; x++) raw[y * (w + 1) + 1 + x] = pixelLevel(row[x]) * 17
39
+ })
40
+ const png = encodePng(w, h, raw)
41
+ const out = base64(png)
42
+ encoded.set(rows, out)
43
+ return out
44
+ }
45
+
46
+ /** An image element drawing `rows` with its top-left at (x, y); `color` is the ink level (absent = full). */
47
+ export function pixelArtElement(id: string, rows: PixelArt, x: number, y: number, color?: number): SceneElementInput {
48
+ const {w, h} = pixelArtSize(rows)
49
+ return {
50
+ type: "image",
51
+ id,
52
+ box: {x, y, w, h},
53
+ data: pixelArtPng(rows),
54
+ ...(color === undefined ? {} : {style: {color}}),
55
+ }
56
+ }
57
+
58
+ // ---- PNG (grayscale, stored deflate) ---------------------------------------
59
+
60
+ const CRC_TABLE = (() => {
61
+ const table = new Uint32Array(256)
62
+ for (let n = 0; n < 256; n++) {
63
+ let c = n
64
+ for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1
65
+ table[n] = c >>> 0
66
+ }
67
+ return table
68
+ })()
69
+
70
+ function crc32(bytes: Uint8Array): number {
71
+ let c = 0xffffffff
72
+ for (const b of bytes) c = CRC_TABLE[(c ^ b) & 0xff] ^ (c >>> 8)
73
+ return (c ^ 0xffffffff) >>> 0
74
+ }
75
+
76
+ function adler32(bytes: Uint8Array): number {
77
+ let a = 1
78
+ let b = 0
79
+ for (const byte of bytes) {
80
+ a = (a + byte) % 65521
81
+ b = (b + a) % 65521
82
+ }
83
+ return ((b << 16) | a) >>> 0
84
+ }
85
+
86
+ function u32(value: number): number[] {
87
+ return [(value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff]
88
+ }
89
+
90
+ function chunk(type: string, body: number[]): number[] {
91
+ const typed = [...type].map((ch) => ch.charCodeAt(0))
92
+ const crc = crc32(Uint8Array.from([...typed, ...body]))
93
+ return [...u32(body.length), ...typed, ...body, ...u32(crc)]
94
+ }
95
+
96
+ /** zlib stream of stored (uncompressed) blocks, 65535 bytes each at most. */
97
+ function zlibStored(raw: Uint8Array): number[] {
98
+ const out = [0x78, 0x01]
99
+ for (let offset = 0; offset < raw.length || offset === 0; offset += 65535) {
100
+ const block = raw.subarray(offset, Math.min(raw.length, offset + 65535))
101
+ const last = offset + block.length >= raw.length
102
+ out.push(last ? 1 : 0, block.length & 0xff, block.length >>> 8, ~block.length & 0xff, (~block.length >>> 8) & 0xff)
103
+ for (const b of block) out.push(b)
104
+ if (last) break
105
+ }
106
+ out.push(...u32(adler32(raw)))
107
+ return out
108
+ }
109
+
110
+ function encodePng(w: number, h: number, raw: Uint8Array): Uint8Array {
111
+ const header = [...u32(w), ...u32(h), 8, 0, 0, 0, 0] // 8-bit grayscale, deflate, no interlace
112
+ return Uint8Array.from([
113
+ 0x89,
114
+ 0x50,
115
+ 0x4e,
116
+ 0x47,
117
+ 0x0d,
118
+ 0x0a,
119
+ 0x1a,
120
+ 0x0a,
121
+ ...chunk("IHDR", header),
122
+ ...chunk("IDAT", zlibStored(raw)),
123
+ ...chunk("IEND", []),
124
+ ])
125
+ }
126
+
127
+ const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
128
+
129
+ function base64(bytes: Uint8Array): string {
130
+ let out = ""
131
+ for (let i = 0; i < bytes.length; i += 3) {
132
+ const a = bytes[i]
133
+ const b = i + 1 < bytes.length ? bytes[i + 1] : 0
134
+ const c = i + 2 < bytes.length ? bytes[i + 2] : 0
135
+ const triple = (a << 16) | (b << 8) | c
136
+ out += B64[(triple >>> 18) & 63] + B64[(triple >>> 12) & 63]
137
+ out += i + 1 < bytes.length ? B64[(triple >>> 6) & 63] : "="
138
+ out += i + 2 < bytes.length ? B64[triple & 63] : "="
139
+ }
140
+ return out
141
+ }
@@ -0,0 +1,237 @@
1
+ import {DisplayProfile} from "./types"
2
+
3
+ /**
4
+ * Complete G1 glyph widths from G1FontLoaderKt
5
+ * These are GLYPH widths - multiply using renderFormula to get rendered pixels.
6
+ * Rendered width = (glyphWidth + 1) * 2
7
+ */
8
+ const G1_GLYPH_WIDTHS: Record<string, number> = {
9
+ // Punctuation & Symbols
10
+ " ": 2,
11
+ "!": 1,
12
+ '"': 2,
13
+ "#": 6,
14
+ "$": 5,
15
+ "%": 6,
16
+ "&": 7,
17
+ "'": 1,
18
+ "(": 2,
19
+ ")": 2,
20
+ "*": 3,
21
+ "+": 4,
22
+ ",": 1,
23
+ "-": 4, // Hyphen: 4px glyph → 10px rendered (CRITICAL for breaking!)
24
+ ".": 1,
25
+ "/": 3,
26
+ "\u00b7": 1, // Middle dot (notification "sender \u00b7 thread" titles); width assumed to match the period
27
+
28
+ // Numbers
29
+ "0": 5,
30
+ "1": 3,
31
+ "2": 5,
32
+ "3": 5,
33
+ "4": 5,
34
+ "5": 5,
35
+ "6": 5,
36
+ "7": 5,
37
+ "8": 5,
38
+ "9": 5,
39
+
40
+ // More punctuation
41
+ ":": 1,
42
+ ";": 1,
43
+ "<": 4,
44
+ "=": 4,
45
+ ">": 4,
46
+ "?": 5,
47
+ "@": 7,
48
+
49
+ // Uppercase
50
+ "A": 6,
51
+ "B": 5,
52
+ "C": 5,
53
+ "D": 5,
54
+ "E": 4,
55
+ "F": 4,
56
+ "G": 5,
57
+ "H": 5,
58
+ "I": 2,
59
+ "J": 3,
60
+ "K": 5,
61
+ "L": 4,
62
+ "M": 7,
63
+ "N": 5,
64
+ "O": 5,
65
+ "P": 5,
66
+ "Q": 5,
67
+ "R": 5,
68
+ "S": 5,
69
+ "T": 5,
70
+ "U": 5,
71
+ "V": 6,
72
+ "W": 7,
73
+ "X": 6,
74
+ "Y": 6,
75
+ "Z": 5,
76
+
77
+ // Brackets & special
78
+ "[": 2,
79
+ "\\": 3,
80
+ "]": 2,
81
+ "^": 4,
82
+ "_": 3,
83
+ "`": 2,
84
+
85
+ // Lowercase
86
+ "a": 5,
87
+ "b": 4,
88
+ "c": 4,
89
+ "d": 4,
90
+ "e": 4,
91
+ "f": 4,
92
+ "g": 4,
93
+ "h": 4,
94
+ "i": 1,
95
+ "j": 2,
96
+ "k": 4,
97
+ "l": 1,
98
+ "m": 7,
99
+ "n": 4,
100
+ "o": 4,
101
+ "p": 4,
102
+ "q": 4,
103
+ "r": 3,
104
+ "s": 4,
105
+ "t": 3,
106
+ "u": 5,
107
+ "v": 5,
108
+ "w": 7,
109
+ "x": 5,
110
+ "y": 5,
111
+ "z": 4,
112
+
113
+ // More special
114
+ "{": 3,
115
+ "|": 1,
116
+ "}": 3,
117
+ "~": 7,
118
+ }
119
+
120
+ /**
121
+ * Even Realities G1 Smart Glasses Display Profile
122
+ *
123
+ * Verified through empirical testing with actual hardware.
124
+ * See: line-width-debug-tool/line-width-spec.md
125
+ */
126
+ export const G1_PROFILE: DisplayProfile = {
127
+ id: "even-realities-g1",
128
+ name: "Even Realities G1",
129
+
130
+ // Display dimensions
131
+ displayWidthPx: 576,
132
+ maxLines: 5,
133
+
134
+ // BLE constraints
135
+ maxPayloadBytes: 390,
136
+ bleChunkSize: 176,
137
+
138
+ // Font metrics
139
+ fontMetrics: {
140
+ // Glyph widths from G1FontLoaderKt
141
+ glyphWidths: new Map(Object.entries(G1_GLYPH_WIDTHS)),
142
+
143
+ // Default glyph width for unmapped Latin characters (use max to be safe)
144
+ defaultGlyphWidth: 7,
145
+
146
+ // Rendered width = (glyphWidth + 1) * 2
147
+ renderFormula: (glyphWidth: number) => (glyphWidth + 1) * 2,
148
+
149
+ // Uniform-width scripts - ALL characters in these scripts render at this exact width
150
+ // These are verified values in RENDERED pixels, NOT averages
151
+ uniformScripts: {
152
+ cjk: 18, // ALL Chinese/Japanese Kanji chars = 18px (verified)
153
+ hiragana: 18, // ALL Hiragana chars = 18px (verified)
154
+ katakana: 18, // ALL Katakana chars = 18px (verified)
155
+ korean: 24, // ALL Korean Hangul chars = 24px (verified)
156
+ cyrillic: 18, // ALL Cyrillic chars = 18px (verified)
157
+ },
158
+
159
+ // Fallback for unmapped Latin characters
160
+ // Uses MAX width to guarantee no overflow (safe under-utilization)
161
+ fallback: {
162
+ latinMaxWidth: 16, // Max Latin = 'm', 'w' at (7+1)*2 = 16px
163
+ unknownBehavior: "useLatinMax",
164
+ },
165
+ },
166
+
167
+ constraints: {
168
+ minCharsBeforeHyphen: 3,
169
+ // Kinsoku: Characters that should not appear at start of line
170
+ noStartChars: [".", ",", "!", "?", ":", ";", ")", "]", "}", "。", ",", "!", "?", ":", ";", ")", "】", "」"],
171
+ // Kinsoku: Characters that should not appear at end of line
172
+ noEndChars: ["(", "[", "{", "(", "【", "「"],
173
+ },
174
+ }
175
+
176
+ /**
177
+ * G1 Profile for LEGACY mobile clients that have their own wrapping logic.
178
+ *
179
+ * Old mobile clients re-wrap text received from the cloud, causing double-wrapping.
180
+ * This profile uses a reduced display width (~522px instead of 576px) so that
181
+ * when the mobile client re-wraps, the result still fits within 5 lines.
182
+ *
183
+ * Use this profile when:
184
+ * - Mobile client version < X.X.X (has old wrapping logic)
185
+ * - You see text getting cut off or exceeding 5 lines
186
+ *
187
+ * Once all clients are updated, this can be deprecated.
188
+ */
189
+ export const G1_PROFILE_LEGACY: DisplayProfile = {
190
+ id: "even-realities-g1-legacy",
191
+ name: "Even Realities G1 (Legacy Client Compatibility)",
192
+
193
+ // Reduced display width to prevent double-wrapping overflow
194
+ // Old mobile client wraps at ~90% of true width
195
+ displayWidthPx: 420,
196
+ maxLines: 5,
197
+
198
+ // BLE constraints (same as standard)
199
+ maxPayloadBytes: 390,
200
+ bleChunkSize: 176,
201
+
202
+ // Font metrics (same as standard)
203
+ fontMetrics: {
204
+ glyphWidths: new Map(Object.entries(G1_GLYPH_WIDTHS)),
205
+ defaultGlyphWidth: 7,
206
+ renderFormula: (glyphWidth: number) => (glyphWidth + 1) * 2,
207
+ uniformScripts: {
208
+ cjk: 18,
209
+ hiragana: 18,
210
+ katakana: 18,
211
+ korean: 24,
212
+ cyrillic: 18,
213
+ },
214
+ fallback: {
215
+ latinMaxWidth: 16,
216
+ unknownBehavior: "useLatinMax",
217
+ },
218
+ },
219
+
220
+ constraints: {
221
+ minCharsBeforeHyphen: 3,
222
+ noStartChars: [".", ",", "!", "?", ":", ";", ")", "]", "}", "。", ",", "!", "?", ":", ";", ")", "】", "」"],
223
+ noEndChars: ["(", "[", "{", "(", "【", "「"],
224
+ },
225
+ }
226
+
227
+ /**
228
+ * Get the hyphen width for G1 in rendered pixels.
229
+ * Hyphen glyph = 4px → rendered = (4+1)*2 = 10px
230
+ */
231
+ export const G1_HYPHEN_WIDTH_PX = 10
232
+
233
+ /**
234
+ * Get the space width for G1 in rendered pixels.
235
+ * Space glyph = 2px → rendered = (2+1)*2 = 6px
236
+ */
237
+ export const G1_SPACE_WIDTH_PX = 6
@@ -0,0 +1,39 @@
1
+ import {DisplayProfile} from "./types"
2
+
3
+ /**
4
+ * Even Realities G2 Smart Glasses Display Profile
5
+ *
6
+ * G2 uses the same display hardware as G1 (green monochrome, ~640x200).
7
+ * Glyph widths and rendering formula are identical to G1.
8
+ * The protocol differs (EvenHub protobuf vs G1 binary) but display
9
+ * characteristics are the same.
10
+ */
11
+
12
+ // G2 uses the same glyph widths as G1 (same display hardware/font)
13
+ import {G1_PROFILE} from "./g1"
14
+
15
+ export const G2_PROFILE: DisplayProfile = {
16
+ ...G1_PROFILE,
17
+ id: "even-realities-g2",
18
+ name: "Even Realities G2",
19
+ // G2 fits more vertical text than G1 — allow up to 8 lines before the
20
+ // wrapper truncates (G1 stays at its inherited 5).
21
+ maxLines: 8,
22
+ // Hardware-calibrated 2026-07-03: a 28px container overflowed one line of
23
+ // the firmware font (triggering the fw's overflow indicator tick); 40px is
24
+ // clean. With this set, the scene pipeline height-clips text so a box is
25
+ // never handed more lines than fit.
26
+ lineHeightPx: 40,
27
+ }
28
+
29
+ /**
30
+ * Get the hyphen width for G2 in rendered pixels.
31
+ * Same as G1: Hyphen glyph = 4px → rendered = (4+1)*2 = 10px
32
+ */
33
+ export const G2_HYPHEN_WIDTH_PX = 10
34
+
35
+ /**
36
+ * Get the space width for G2 in rendered pixels.
37
+ * Same as G1: Space glyph = 2px → rendered = (2+1)*2 = 6px
38
+ */
39
+ export const G2_SPACE_WIDTH_PX = 6
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Display Profiles
3
+ *
4
+ * Hardware-specific configurations for smart glasses displays.
5
+ * Each profile defines display dimensions, font metrics, and constraints.
6
+ */
7
+
8
+ // Types
9
+ export type {
10
+ DisplayProfile,
11
+ FontMetrics,
12
+ UniformScriptWidths,
13
+ FallbackConfig,
14
+ DisplayConstraints,
15
+ ScriptType,
16
+ } from "./types"
17
+
18
+ // G1 Profiles
19
+ export {G1_PROFILE, G1_PROFILE_LEGACY, G1_HYPHEN_WIDTH_PX, G1_SPACE_WIDTH_PX} from "./g1"
20
+
21
+ // Z100 Profile
22
+ export {Z100_PROFILE, Z100_HYPHEN_WIDTH_PX, Z100_SPACE_WIDTH_PX} from "./z100"
23
+
24
+ // G2 Profile
25
+ export {G2_PROFILE, G2_HYPHEN_WIDTH_PX, G2_SPACE_WIDTH_PX} from "./g2"
26
+
27
+ // Mentra Nex Profile (a.k.a. Mentra Display)
28
+ export {NEX_PROFILE, NEX_HYPHEN_WIDTH_PX, NEX_SPACE_WIDTH_PX} from "./nex"
@@ -0,0 +1,166 @@
1
+ import {DisplayProfile} from "./types"
2
+
3
+ /**
4
+ * Mentra Nex glyph widths
5
+ */
6
+ const NEX_GLYPH_WIDTHS: Record<string, number> = {
7
+ // Punctuation & Symbols
8
+ " ": 5,
9
+ "!": 5,
10
+ '"': 8,
11
+ "#": 14,
12
+ "$": 12,
13
+ "%": 18,
14
+ "&": 14,
15
+ "'": 5,
16
+ "(": 7,
17
+ ")": 7,
18
+ "*": 8,
19
+ "+": 12,
20
+ ",": 5,
21
+ "-": 8,
22
+ ".": 5,
23
+ "/": 7,
24
+
25
+ // Numbers
26
+ "0": 12,
27
+ "1": 12,
28
+ "2": 12,
29
+ "3": 12,
30
+ "4": 12,
31
+ "5": 12,
32
+ "6": 12,
33
+ "7": 12,
34
+ "8": 12,
35
+ "9": 12,
36
+
37
+ // More punctuation
38
+ ":": 6,
39
+ ";": 6,
40
+ "<": 12,
41
+ "=": 12,
42
+ ">": 12,
43
+ "?": 9,
44
+ "@": 18,
45
+
46
+ // Uppercase
47
+ "A": 12,
48
+ "B": 13,
49
+ "C": 12,
50
+ "D": 13,
51
+ "E": 11,
52
+ "F": 11,
53
+ "G": 14,
54
+ "H": 14,
55
+ "I": 8,
56
+ "J": 10,
57
+ "K": 12,
58
+ "L": 10,
59
+ "M": 16,
60
+ "N": 14,
61
+ "O": 14,
62
+ "P": 12,
63
+ "Q": 14,
64
+ "R": 13,
65
+ "S": 11,
66
+ "T": 11,
67
+ "U": 13,
68
+ "V": 12,
69
+ "W": 18,
70
+ "X": 12,
71
+ "Y": 11,
72
+ "Z": 11,
73
+
74
+ // Brackets & special
75
+ "[": 6,
76
+ "\\": 7,
77
+ "]": 6,
78
+ "^": 12,
79
+ "_": 11,
80
+ "`": 12,
81
+
82
+ // Lowercase
83
+ "a": 11,
84
+ "b": 11,
85
+ "c": 10,
86
+ "d": 11,
87
+ "e": 11,
88
+ "f": 6,
89
+ "g": 11,
90
+ "h": 11,
91
+ "i": 5,
92
+ "j": 5,
93
+ "k": 10,
94
+ "l": 5,
95
+ "m": 17,
96
+ "n": 11,
97
+ "o": 11,
98
+ "p": 11,
99
+ "q": 11,
100
+ "r": 7,
101
+ "s": 10,
102
+ "t": 7,
103
+ "u": 11,
104
+ "v": 10,
105
+ "w": 15,
106
+ "x": 10,
107
+ "y": 10,
108
+ "z": 9,
109
+
110
+ // More special
111
+ "{": 7,
112
+ "|": 6,
113
+ "}": 7,
114
+ "~": 12,
115
+ }
116
+
117
+ /**
118
+ * Mentra Nex Smart Glasses Display Profile
119
+ *
120
+ * Also known as "Mentra Display" - a new Glassly glasses model with display capabilities.
121
+ */
122
+ export const NEX_PROFILE: DisplayProfile = {
123
+ id: "mentra-nex",
124
+ name: "Mentra Nex",
125
+
126
+ // Display dimensions — 500x220 usable region centered in the 640x480 A6N
127
+ // panel (firmware mos_display_config.c: margins 70/130). Canvas coordinates
128
+ // are relative to this virtual screen; the physical panel is bigger, 500x220
129
+ // is the public drawable canvas.
130
+ displayWidthPx: 480,
131
+ displayHeightPx: 220,
132
+ maxLines: 8,
133
+
134
+ maxPayloadBytes: 450, // Matches firmware MAX_TEXT_LEN / DisplayText.text cap; delivered via fragmented BLE transport
135
+ bleChunkSize: 176,
136
+
137
+ // Font metrics
138
+ fontMetrics: {
139
+ glyphWidths: new Map(Object.entries(NEX_GLYPH_WIDTHS)),
140
+ defaultGlyphWidth: 16,
141
+ renderFormula: (glyphWidth: number) => glyphWidth,
142
+
143
+ uniformScripts: {
144
+ cjk: 20,
145
+ hiragana: 18,
146
+ katakana: 18,
147
+ korean: 24,
148
+ cyrillic: 18,
149
+ },
150
+
151
+ fallback: {
152
+ latinMaxWidth: 16,
153
+ unknownBehavior: "useLatinMax",
154
+ },
155
+ },
156
+
157
+ constraints: {
158
+ minCharsBeforeHyphen: 3,
159
+ noStartChars: [".", ",", "!", "?", ":", ";", ")", "]", "}", "。", ",", "!", "?", ":", ";", ")", "】", "」"],
160
+ noEndChars: ["(", "[", "{", "(", "【", "「"],
161
+ },
162
+ }
163
+
164
+ export const NEX_HYPHEN_WIDTH_PX = 8
165
+
166
+ export const NEX_SPACE_WIDTH_PX = 4