@typecad/ui 0.1.0-alpha.1

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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1443 -0
  3. package/dist/engine-index.d.ts +7 -0
  4. package/dist/engine-index.js +41 -0
  5. package/dist/index.d.ts +165 -0
  6. package/dist/index.js +42 -0
  7. package/dist/preview/build-program.d.ts +7 -0
  8. package/dist/preview/build-program.js +665 -0
  9. package/dist/preview/host-gfx.d.ts +90 -0
  10. package/dist/preview/host-gfx.js +552 -0
  11. package/dist/preview/host-ui-runtime.d.ts +241 -0
  12. package/dist/preview/host-ui-runtime.js +3236 -0
  13. package/dist/preview/types.d.ts +83 -0
  14. package/dist/preview/types.js +1 -0
  15. package/dist/types.d.ts +108 -0
  16. package/dist/types.js +10 -0
  17. package/dist/ui-engine/block-layout.d.ts +8 -0
  18. package/dist/ui-engine/block-layout.js +95 -0
  19. package/dist/ui-engine/color.d.ts +49 -0
  20. package/dist/ui-engine/color.js +273 -0
  21. package/dist/ui-engine/css-parser.d.ts +155 -0
  22. package/dist/ui-engine/css-parser.js +1058 -0
  23. package/dist/ui-engine/default-keyboards.d.ts +3 -0
  24. package/dist/ui-engine/default-keyboards.js +53 -0
  25. package/dist/ui-engine/easing.d.ts +12 -0
  26. package/dist/ui-engine/easing.js +76 -0
  27. package/dist/ui-engine/font-assets.d.ts +50 -0
  28. package/dist/ui-engine/font-assets.js +431 -0
  29. package/dist/ui-engine/html-parser.d.ts +116 -0
  30. package/dist/ui-engine/html-parser.js +284 -0
  31. package/dist/ui-engine/image-assets.d.ts +21 -0
  32. package/dist/ui-engine/image-assets.js +125 -0
  33. package/dist/ui-engine/inline-parser.d.ts +30 -0
  34. package/dist/ui-engine/inline-parser.js +66 -0
  35. package/dist/ui-engine/keyframes.d.ts +3 -0
  36. package/dist/ui-engine/keyframes.js +39 -0
  37. package/dist/ui-engine/layout-engine.d.ts +35 -0
  38. package/dist/ui-engine/layout-engine.js +203 -0
  39. package/dist/ui-engine/model.d.ts +239 -0
  40. package/dist/ui-engine/model.js +987 -0
  41. package/dist/ui-engine/rich-layout.d.ts +51 -0
  42. package/dist/ui-engine/rich-layout.js +220 -0
  43. package/dist/ui-engine/run-types.d.ts +13 -0
  44. package/dist/ui-engine/run-types.js +1 -0
  45. package/dist/ui-engine/runtime-header/antialiasing.d.ts +1 -0
  46. package/dist/ui-engine/runtime-header/antialiasing.js +141 -0
  47. package/dist/ui-engine/runtime-header/blend-bodies.d.ts +1 -0
  48. package/dist/ui-engine/runtime-header/blend-bodies.js +39 -0
  49. package/dist/ui-engine/runtime-header/canvas-helpers.d.ts +1 -0
  50. package/dist/ui-engine/runtime-header/canvas-helpers.js +155 -0
  51. package/dist/ui-engine/runtime-header/canvas-scrollbar.d.ts +1 -0
  52. package/dist/ui-engine/runtime-header/canvas-scrollbar.js +105 -0
  53. package/dist/ui-engine/runtime-header/color-mono-refresh.d.ts +1 -0
  54. package/dist/ui-engine/runtime-header/color-mono-refresh.js +154 -0
  55. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.d.ts +1 -0
  56. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +169 -0
  57. package/dist/ui-engine/runtime-header/display-shim.d.ts +1 -0
  58. package/dist/ui-engine/runtime-header/display-shim.js +104 -0
  59. package/dist/ui-engine/runtime-header/forward-decls.d.ts +1 -0
  60. package/dist/ui-engine/runtime-header/forward-decls.js +137 -0
  61. package/dist/ui-engine/runtime-header/guard-close.d.ts +1 -0
  62. package/dist/ui-engine/runtime-header/guard-close.js +8 -0
  63. package/dist/ui-engine/runtime-header/image-drawing.d.ts +1 -0
  64. package/dist/ui-engine/runtime-header/image-drawing.js +300 -0
  65. package/dist/ui-engine/runtime-header/init-press-input.d.ts +1 -0
  66. package/dist/ui-engine/runtime-header/init-press-input.js +126 -0
  67. package/dist/ui-engine/runtime-header/keyboard.d.ts +1 -0
  68. package/dist/ui-engine/runtime-header/keyboard.js +342 -0
  69. package/dist/ui-engine/runtime-header/node-decoration.d.ts +1 -0
  70. package/dist/ui-engine/runtime-header/node-decoration.js +212 -0
  71. package/dist/ui-engine/runtime-header/paint-order-coords.d.ts +1 -0
  72. package/dist/ui-engine/runtime-header/paint-order-coords.js +190 -0
  73. package/dist/ui-engine/runtime-header/paint-rects-repair.d.ts +1 -0
  74. package/dist/ui-engine/runtime-header/paint-rects-repair.js +553 -0
  75. package/dist/ui-engine/runtime-header/scroll-physics.d.ts +1 -0
  76. package/dist/ui-engine/runtime-header/scroll-physics.js +161 -0
  77. package/dist/ui-engine/runtime-header/state-bindings-nav.d.ts +1 -0
  78. package/dist/ui-engine/runtime-header/state-bindings-nav.js +176 -0
  79. package/dist/ui-engine/runtime-header/structs.d.ts +1 -0
  80. package/dist/ui-engine/runtime-header/structs.js +220 -0
  81. package/dist/ui-engine/runtime-header/text-rendering.d.ts +1 -0
  82. package/dist/ui-engine/runtime-header/text-rendering.js +715 -0
  83. package/dist/ui-engine/runtime-header/tick/bindings-phase.d.ts +1 -0
  84. package/dist/ui-engine/runtime-header/tick/bindings-phase.js +103 -0
  85. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.d.ts +1 -0
  86. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +1051 -0
  87. package/dist/ui-engine/runtime-header/tick/flush-phase.d.ts +1 -0
  88. package/dist/ui-engine/runtime-header/tick/flush-phase.js +17 -0
  89. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.d.ts +1 -0
  90. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +15 -0
  91. package/dist/ui-engine/runtime-header/tick/transitions-phase.d.ts +1 -0
  92. package/dist/ui-engine/runtime-header/tick/transitions-phase.js +213 -0
  93. package/dist/ui-engine/runtime-header/tick-motion-helpers.d.ts +1 -0
  94. package/dist/ui-engine/runtime-header/tick-motion-helpers.js +26 -0
  95. package/dist/ui-engine/runtime-header/tick.d.ts +1 -0
  96. package/dist/ui-engine/runtime-header/tick.js +17 -0
  97. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.d.ts +1 -0
  98. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +406 -0
  99. package/dist/ui-engine/runtime-header/types-defines.d.ts +1 -0
  100. package/dist/ui-engine/runtime-header/types-defines.js +79 -0
  101. package/dist/ui-engine/runtime-header.d.ts +1 -0
  102. package/dist/ui-engine/runtime-header.js +69 -0
  103. package/dist/ui-engine/scroll-memory-diagnostics.d.ts +16 -0
  104. package/dist/ui-engine/scroll-memory-diagnostics.js +58 -0
  105. package/dist/ui-engine/select-engine.d.ts +4 -0
  106. package/dist/ui-engine/select-engine.js +19 -0
  107. package/dist/ui-engine/style-resolver.d.ts +77 -0
  108. package/dist/ui-engine/style-resolver.js +341 -0
  109. package/dist/ui-engine/text-layout.d.ts +19 -0
  110. package/dist/ui-engine/text-layout.js +92 -0
  111. package/dist/ui-engine/transpile-ui.d.ts +11 -0
  112. package/dist/ui-engine/transpile-ui.js +33 -0
  113. package/dist/ui-engine/ua-stylesheet.d.ts +3 -0
  114. package/dist/ui-engine/ua-stylesheet.js +56 -0
  115. package/dist/ui-engine/ui-file-splitter.d.ts +14 -0
  116. package/dist/ui-engine/ui-file-splitter.js +47 -0
  117. package/dist/ui-engine/ui-lowering.d.ts +33 -0
  118. package/dist/ui-engine/ui-lowering.js +390 -0
  119. package/dist/ui-engine/ui-registry.d.ts +81 -0
  120. package/dist/ui-engine/ui-registry.js +429 -0
  121. package/dist/ui-engine/yoga-layout.d.ts +12 -0
  122. package/dist/ui-engine/yoga-layout.js +314 -0
  123. package/package.json +86 -0
  124. package/src/engine-index.ts +51 -0
  125. package/src/index.ts +232 -0
  126. package/src/preview/build-program.ts +734 -0
  127. package/src/preview/host-gfx.ts +580 -0
  128. package/src/preview/host-ui-runtime.ts +3245 -0
  129. package/src/preview/types.ts +96 -0
  130. package/src/types.ts +127 -0
  131. package/src/ui-engine/block-layout.ts +104 -0
  132. package/src/ui-engine/color.ts +263 -0
  133. package/src/ui-engine/css-parser.ts +1003 -0
  134. package/src/ui-engine/default-keyboards.ts +58 -0
  135. package/src/ui-engine/easing.ts +53 -0
  136. package/src/ui-engine/font-assets.ts +512 -0
  137. package/src/ui-engine/html-parser.ts +397 -0
  138. package/src/ui-engine/image-assets.ts +170 -0
  139. package/src/ui-engine/inline-parser.ts +78 -0
  140. package/src/ui-engine/keyframes.ts +47 -0
  141. package/src/ui-engine/layout-engine.ts +228 -0
  142. package/src/ui-engine/model.ts +1225 -0
  143. package/src/ui-engine/rich-layout.ts +257 -0
  144. package/src/ui-engine/run-types.ts +14 -0
  145. package/src/ui-engine/runtime-header/antialiasing.ts +141 -0
  146. package/src/ui-engine/runtime-header/blend-bodies.ts +39 -0
  147. package/src/ui-engine/runtime-header/canvas-helpers.ts +155 -0
  148. package/src/ui-engine/runtime-header/canvas-scrollbar.ts +105 -0
  149. package/src/ui-engine/runtime-header/color-mono-refresh.ts +154 -0
  150. package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +169 -0
  151. package/src/ui-engine/runtime-header/display-shim.ts +104 -0
  152. package/src/ui-engine/runtime-header/forward-decls.ts +137 -0
  153. package/src/ui-engine/runtime-header/guard-close.ts +8 -0
  154. package/src/ui-engine/runtime-header/image-drawing.ts +300 -0
  155. package/src/ui-engine/runtime-header/init-press-input.ts +126 -0
  156. package/src/ui-engine/runtime-header/keyboard.ts +342 -0
  157. package/src/ui-engine/runtime-header/node-decoration.ts +212 -0
  158. package/src/ui-engine/runtime-header/paint-order-coords.ts +190 -0
  159. package/src/ui-engine/runtime-header/paint-rects-repair.ts +553 -0
  160. package/src/ui-engine/runtime-header/scroll-physics.ts +161 -0
  161. package/src/ui-engine/runtime-header/state-bindings-nav.ts +176 -0
  162. package/src/ui-engine/runtime-header/structs.ts +220 -0
  163. package/src/ui-engine/runtime-header/text-rendering.ts +715 -0
  164. package/src/ui-engine/runtime-header/tick/bindings-phase.ts +103 -0
  165. package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +1051 -0
  166. package/src/ui-engine/runtime-header/tick/flush-phase.ts +17 -0
  167. package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +15 -0
  168. package/src/ui-engine/runtime-header/tick/transitions-phase.ts +213 -0
  169. package/src/ui-engine/runtime-header/tick-motion-helpers.ts +26 -0
  170. package/src/ui-engine/runtime-header/tick.ts +18 -0
  171. package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +406 -0
  172. package/src/ui-engine/runtime-header/types-defines.ts +79 -0
  173. package/src/ui-engine/runtime-header.ts +71 -0
  174. package/src/ui-engine/scroll-memory-diagnostics.ts +79 -0
  175. package/src/ui-engine/select-engine.ts +21 -0
  176. package/src/ui-engine/style-resolver.ts +416 -0
  177. package/src/ui-engine/text-layout.ts +114 -0
  178. package/src/ui-engine/transpile-ui.ts +45 -0
  179. package/src/ui-engine/ua-stylesheet.ts +60 -0
  180. package/src/ui-engine/ui-file-splitter.ts +59 -0
  181. package/src/ui-engine/ui-lowering.ts +459 -0
  182. package/src/ui-engine/ui-registry.ts +530 -0
  183. package/src/ui-engine/yoga-layout.ts +309 -0
@@ -0,0 +1,90 @@
1
+ export declare const GFX_FONT_BYTES = 1280;
2
+ export interface ImageDataLike {
3
+ readonly data: Uint8ClampedArray;
4
+ readonly width: number;
5
+ readonly height: number;
6
+ }
7
+ export declare function rgb565ToRgb888(color: number): {
8
+ r: number;
9
+ g: number;
10
+ b: number;
11
+ };
12
+ /** Quantize a packed RGB888 value to RGB565 (uint16). */
13
+ export declare function rgb888To565(color: number): number;
14
+ /** Reconstruct a packed RGB888 value from a 565 value (lossy). */
15
+ export declare function rgb565To888(color: number): number;
16
+ export declare function blendRgb565(fg: number, bg: number, opacity: number): number;
17
+ /** Blend two RGB888 colors by opacity (0-100). Added for Phase 2 (RGB888/RGB666
18
+ * targets); unused in Phase 1, whose TFT path keeps 565 values and blends via
19
+ * blendRgb565 above for byte-identity with the device runtime. */
20
+ export declare function blendRgb888(fg: number, bg: number, opacity: number): number;
21
+ export declare class HostAdafruitGFX {
22
+ readonly width: number;
23
+ readonly height: number;
24
+ private readonly font;
25
+ readonly buffer: Uint32Array;
26
+ private cursorX;
27
+ private cursorY;
28
+ private textColor;
29
+ private textBgColor;
30
+ private monoSnap;
31
+ private textSizeX;
32
+ private textSizeY;
33
+ private wrap;
34
+ private clipRect;
35
+ constructor(width: number, height: number, font?: Uint8Array);
36
+ begin(): void;
37
+ /** Enable 1-bit mono snapping (e-ink targets). When on, draw primitives snap
38
+ * every color to black/white by luminance, matching the device's
39
+ * UI_MAYBE_SNAP_MONO565. Uses the same threshold as toMono (0.27). */
40
+ setMonoSnap(on: boolean): void;
41
+ private snap;
42
+ setRotation(_rotation: number): void;
43
+ setClipRect(rect: {
44
+ x: number;
45
+ y: number;
46
+ w: number;
47
+ h: number;
48
+ } | undefined): void;
49
+ getClipRect(): {
50
+ x: number;
51
+ y: number;
52
+ w: number;
53
+ h: number;
54
+ } | undefined;
55
+ withClipRect<T>(rect: {
56
+ x: number;
57
+ y: number;
58
+ w: number;
59
+ h: number;
60
+ } | undefined, fn: () => T): T;
61
+ private insideClip;
62
+ drawPixel(x: number, y: number, color: number): void;
63
+ writePixel(x: number, y: number, color: number): void;
64
+ writeFillRect(x: number, y: number, w: number, h: number, color: number): void;
65
+ fillRect(x: number, y: number, w: number, h: number, color: number): void;
66
+ fillScreen(color: number): void;
67
+ drawFastHLine(x: number, y: number, w: number, color: number): void;
68
+ writeFastHLine(x: number, y: number, w: number, color: number): void;
69
+ drawFastVLine(x: number, y: number, h: number, color: number): void;
70
+ writeFastVLine(x: number, y: number, h: number, color: number): void;
71
+ drawRect(x: number, y: number, w: number, h: number, color: number): void;
72
+ drawRoundRect(x: number, y: number, w: number, h: number, r: number, color: number): void;
73
+ fillRoundRect(x: number, y: number, w: number, h: number, r: number, color: number): void;
74
+ drawCircle(x0: number, y0: number, r: number, color: number): void;
75
+ private drawCircleHelper;
76
+ fillCircle(x0: number, y0: number, r: number, color: number): void;
77
+ private fillCircleHelper;
78
+ drawLine(x0: number, y0: number, x1: number, y1: number, color: number): void;
79
+ setCursor(x: number, y: number): void;
80
+ setTextColor(color: number, bg?: number): void;
81
+ setTextSize(size: number, sy?: number): void;
82
+ setTextWrap(wrap: boolean): void;
83
+ print(value: unknown): void;
84
+ textWidth(value: unknown, size?: number): number;
85
+ textHeight(size?: number): number;
86
+ drawAntialiasedText(value: unknown, x: number, y: number, color: number, bg: number, size: number): void;
87
+ write(c: number): void;
88
+ drawChar(x: number, y: number, c: number, color: number, bg: number, sizeX: number, sizeY?: number): void;
89
+ toRgbaBytes(): Uint8ClampedArray;
90
+ }
@@ -0,0 +1,552 @@
1
+ export const GFX_FONT_BYTES = 1280;
2
+ export function rgb565ToRgb888(color) {
3
+ const r5 = (color >> 11) & 0x1f;
4
+ const g6 = (color >> 5) & 0x3f;
5
+ const b5 = color & 0x1f;
6
+ return {
7
+ r: (r5 << 3) | (r5 >> 2),
8
+ g: (g6 << 2) | (g6 >> 4),
9
+ b: (b5 << 3) | (b5 >> 2),
10
+ };
11
+ }
12
+ /** Quantize a packed RGB888 value to RGB565 (uint16). */
13
+ export function rgb888To565(color) {
14
+ const r = (color >> 16) & 0xff, g = (color >> 8) & 0xff, b = color & 0xff;
15
+ return ((r & 0xf8) << 8) | ((g & 0xfc) << 3) | (b >> 3);
16
+ }
17
+ /** Reconstruct a packed RGB888 value from a 565 value (lossy). */
18
+ export function rgb565To888(color) {
19
+ const { r, g, b } = rgb565ToRgb888(color);
20
+ return (r << 16) | (g << 8) | b;
21
+ }
22
+ export function blendRgb565(fg, bg, opacity) {
23
+ if (opacity >= 100)
24
+ return fg & 0xffff;
25
+ if (opacity <= 0)
26
+ return bg & 0xffff;
27
+ // Blend in 888 internally for higher precision (parity with the device's
28
+ // ui_blend565): unpack 565→888, blend at 8-bit, re-quantize to 565.
29
+ const fr5 = (fg >> 11) & 0x1f, fg6 = (fg >> 5) & 0x3f, fb5 = fg & 0x1f;
30
+ const br5 = (bg >> 11) & 0x1f, bg6 = (bg >> 5) & 0x3f, bb5 = bg & 0x1f;
31
+ const fr8 = (fr5 << 3) | (fr5 >> 2), fg8 = (fg6 << 2) | (fg6 >> 4), fb8 = (fb5 << 3) | (fb5 >> 2);
32
+ const br8 = (br5 << 3) | (br5 >> 2), bg8 = (bg6 << 2) | (bg6 >> 4), bb8 = (bb5 << 3) | (bb5 >> 2);
33
+ const r = Math.trunc((fr8 * opacity + br8 * (100 - opacity)) / 100);
34
+ const g = Math.trunc((fg8 * opacity + bg8 * (100 - opacity)) / 100);
35
+ const b = Math.trunc((fb8 * opacity + bb8 * (100 - opacity)) / 100);
36
+ return (((r >> 3) & 0x1f) << 11) | (((g >> 2) & 0x3f) << 5) | ((b >> 3) & 0x1f);
37
+ }
38
+ /** Blend two RGB888 colors by opacity (0-100). Added for Phase 2 (RGB888/RGB666
39
+ * targets); unused in Phase 1, whose TFT path keeps 565 values and blends via
40
+ * blendRgb565 above for byte-identity with the device runtime. */
41
+ export function blendRgb888(fg, bg, opacity) {
42
+ if (opacity >= 100)
43
+ return fg & 0xffffff;
44
+ if (opacity <= 0)
45
+ return bg & 0xffffff;
46
+ const fr = (fg >> 16) & 0xff, fg8 = (fg >> 8) & 0xff, fb = fg & 0xff;
47
+ const br = (bg >> 16) & 0xff, bg8 = (bg >> 8) & 0xff, bb = bg & 0xff;
48
+ const r = Math.trunc((fr * opacity + br * (100 - opacity)) / 100);
49
+ const g = Math.trunc((fg8 * opacity + bg8 * (100 - opacity)) / 100);
50
+ const b = Math.trunc((fb * opacity + bb * (100 - opacity)) / 100);
51
+ return ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff);
52
+ }
53
+ export class HostAdafruitGFX {
54
+ width;
55
+ height;
56
+ font;
57
+ buffer;
58
+ cursorX = 0;
59
+ cursorY = 0;
60
+ textColor = 0xffffff;
61
+ textBgColor = 0xffffff;
62
+ // When true (mono/e-ink target), draw primitives snap color values to black/
63
+ // white by luminance — mirrors the device's UI_MAYBE_SNAP_MONO565 choke-point.
64
+ monoSnap = false;
65
+ textSizeX = 1;
66
+ textSizeY = 1;
67
+ wrap = true;
68
+ clipRect;
69
+ constructor(width, height, font = new Uint8Array(GFX_FONT_BYTES)) {
70
+ this.width = width;
71
+ this.height = height;
72
+ this.font = font;
73
+ this.buffer = new Uint32Array(width * height);
74
+ }
75
+ begin() {
76
+ // Hardware driver compatibility hook.
77
+ }
78
+ /** Enable 1-bit mono snapping (e-ink targets). When on, draw primitives snap
79
+ * every color to black/white by luminance, matching the device's
80
+ * UI_MAYBE_SNAP_MONO565. Uses the same threshold as toMono (0.27). */
81
+ setMonoSnap(on) {
82
+ this.monoSnap = on;
83
+ }
84
+ snap(c) {
85
+ if (!this.monoSnap)
86
+ return c;
87
+ // Pre-snapped mono values (0=black, 1=white from the transpiler's
88
+ // rgb888ToMono) pass through directly — the luminance threshold would
89
+ // misclassify 1 as black (r=0,g=0,b=1 → luminance=114 < 68850).
90
+ if (c === 0 || c === 0x0000)
91
+ return 0x000000;
92
+ if (c === 1 || c === 0x0001 || c === 0xffff || c === 0xffffff)
93
+ return 0xffffff;
94
+ const r = (c >> 16) & 0xff, g = (c >> 8) & 0xff, b = c & 0xff;
95
+ return (299 * r + 587 * g + 114 * b >= 68850) ? 0xffffff : 0x000000;
96
+ }
97
+ setRotation(_rotation) {
98
+ // The preview framebuffer is already in display-profile coordinates.
99
+ }
100
+ setClipRect(rect) {
101
+ this.clipRect = rect ? {
102
+ x: Math.trunc(rect.x),
103
+ y: Math.trunc(rect.y),
104
+ w: Math.max(0, Math.trunc(rect.w)),
105
+ h: Math.max(0, Math.trunc(rect.h)),
106
+ } : undefined;
107
+ }
108
+ getClipRect() {
109
+ return this.clipRect ? { ...this.clipRect } : undefined;
110
+ }
111
+ withClipRect(rect, fn) {
112
+ const previous = this.clipRect;
113
+ this.setClipRect(rect);
114
+ try {
115
+ return fn();
116
+ }
117
+ finally {
118
+ this.clipRect = previous;
119
+ }
120
+ }
121
+ insideClip(x, y) {
122
+ const clip = this.clipRect;
123
+ return !clip || (x >= clip.x && y >= clip.y && x < clip.x + clip.w && y < clip.y + clip.h);
124
+ }
125
+ drawPixel(x, y, color) {
126
+ x = Math.trunc(x);
127
+ y = Math.trunc(y);
128
+ if (x < 0 || y < 0 || x >= this.width || y >= this.height)
129
+ return;
130
+ if (!this.insideClip(x, y))
131
+ return;
132
+ this.buffer[y * this.width + x] = this.snap(color) & 0xffffff;
133
+ }
134
+ writePixel(x, y, color) {
135
+ this.drawPixel(x, y, color);
136
+ }
137
+ writeFillRect(x, y, w, h, color) {
138
+ this.fillRect(x, y, w, h, color);
139
+ }
140
+ fillRect(x, y, w, h, color) {
141
+ x = Math.trunc(x);
142
+ y = Math.trunc(y);
143
+ w = Math.trunc(w);
144
+ h = Math.trunc(h);
145
+ if (w <= 0 || h <= 0)
146
+ return;
147
+ let x0 = x;
148
+ let y0 = y;
149
+ let x1 = x + w;
150
+ let y1 = y + h;
151
+ if (x0 < 0)
152
+ x0 = 0;
153
+ if (y0 < 0)
154
+ y0 = 0;
155
+ if (x1 > this.width)
156
+ x1 = this.width;
157
+ if (y1 > this.height)
158
+ y1 = this.height;
159
+ if (this.clipRect) {
160
+ x0 = Math.max(x0, this.clipRect.x);
161
+ y0 = Math.max(y0, this.clipRect.y);
162
+ x1 = Math.min(x1, this.clipRect.x + this.clipRect.w);
163
+ y1 = Math.min(y1, this.clipRect.y + this.clipRect.h);
164
+ }
165
+ if (x0 >= x1 || y0 >= y1)
166
+ return;
167
+ const c = this.snap(color) & 0xffffff;
168
+ for (let yy = y0; yy < y1; yy++) {
169
+ this.buffer.fill(c, yy * this.width + x0, yy * this.width + x1);
170
+ }
171
+ }
172
+ fillScreen(color) {
173
+ this.buffer.fill(this.snap(color) & 0xffffff);
174
+ }
175
+ drawFastHLine(x, y, w, color) {
176
+ this.fillRect(x, y, w, 1, color);
177
+ }
178
+ writeFastHLine(x, y, w, color) {
179
+ this.drawFastHLine(x, y, w, color);
180
+ }
181
+ drawFastVLine(x, y, h, color) {
182
+ this.fillRect(x, y, 1, h, color);
183
+ }
184
+ writeFastVLine(x, y, h, color) {
185
+ this.drawFastVLine(x, y, h, color);
186
+ }
187
+ drawRect(x, y, w, h, color) {
188
+ if (w <= 0 || h <= 0)
189
+ return;
190
+ this.drawFastHLine(x, y, w, color);
191
+ this.drawFastHLine(x, y + h - 1, w, color);
192
+ this.drawFastVLine(x, y, h, color);
193
+ this.drawFastVLine(x + w - 1, y, h, color);
194
+ }
195
+ drawRoundRect(x, y, w, h, r, color) {
196
+ x = Math.trunc(x);
197
+ y = Math.trunc(y);
198
+ w = Math.trunc(w);
199
+ h = Math.trunc(h);
200
+ r = Math.max(0, Math.trunc(r));
201
+ if (w <= 0 || h <= 0)
202
+ return;
203
+ if (r <= 0) {
204
+ this.drawRect(x, y, w, h, color);
205
+ return;
206
+ }
207
+ r = Math.min(r, Math.trunc(Math.min(w, h) / 2));
208
+ this.drawFastHLine(x + r, y, w - 2 * r, color);
209
+ this.drawFastHLine(x + r, y + h - 1, w - 2 * r, color);
210
+ this.drawFastVLine(x, y + r, h - 2 * r, color);
211
+ this.drawFastVLine(x + w - 1, y + r, h - 2 * r, color);
212
+ this.drawCircleHelper(x + r, y + r, r, 1, color);
213
+ this.drawCircleHelper(x + w - r - 1, y + r, r, 2, color);
214
+ this.drawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, color);
215
+ this.drawCircleHelper(x + r, y + h - r - 1, r, 8, color);
216
+ }
217
+ fillRoundRect(x, y, w, h, r, color) {
218
+ x = Math.trunc(x);
219
+ y = Math.trunc(y);
220
+ w = Math.trunc(w);
221
+ h = Math.trunc(h);
222
+ r = Math.max(0, Math.trunc(r));
223
+ if (w <= 0 || h <= 0)
224
+ return;
225
+ if (r <= 0) {
226
+ this.fillRect(x, y, w, h, color);
227
+ return;
228
+ }
229
+ r = Math.min(r, Math.trunc(Math.min(w, h) / 2));
230
+ this.fillRect(x + r, y, w - 2 * r, h, color);
231
+ const delta = Math.max(0, h - 2 * r - 1);
232
+ this.fillCircleHelper(x + w - r - 1, y + r, r, 1, delta, color);
233
+ this.fillCircleHelper(x + r, y + r, r, 2, delta, color);
234
+ }
235
+ drawCircle(x0, y0, r, color) {
236
+ x0 = Math.trunc(x0);
237
+ y0 = Math.trunc(y0);
238
+ r = Math.trunc(r);
239
+ let f = 1 - r;
240
+ let ddFx = 1;
241
+ let ddFy = -2 * r;
242
+ let x = 0;
243
+ let y = r;
244
+ this.drawPixel(x0, y0 + r, color);
245
+ this.drawPixel(x0, y0 - r, color);
246
+ this.drawPixel(x0 + r, y0, color);
247
+ this.drawPixel(x0 - r, y0, color);
248
+ while (x < y) {
249
+ if (f >= 0) {
250
+ y--;
251
+ ddFy += 2;
252
+ f += ddFy;
253
+ }
254
+ x++;
255
+ ddFx += 2;
256
+ f += ddFx;
257
+ this.drawPixel(x0 + x, y0 + y, color);
258
+ this.drawPixel(x0 - x, y0 + y, color);
259
+ this.drawPixel(x0 + x, y0 - y, color);
260
+ this.drawPixel(x0 - x, y0 - y, color);
261
+ this.drawPixel(x0 + y, y0 + x, color);
262
+ this.drawPixel(x0 - y, y0 + x, color);
263
+ this.drawPixel(x0 + y, y0 - x, color);
264
+ this.drawPixel(x0 - y, y0 - x, color);
265
+ }
266
+ }
267
+ drawCircleHelper(x0, y0, r, cornername, color) {
268
+ x0 = Math.trunc(x0);
269
+ y0 = Math.trunc(y0);
270
+ r = Math.trunc(r);
271
+ let f = 1 - r;
272
+ let ddFx = 1;
273
+ let ddFy = -2 * r;
274
+ let x = 0;
275
+ let y = r;
276
+ while (x < y) {
277
+ if (f >= 0) {
278
+ y--;
279
+ ddFy += 2;
280
+ f += ddFy;
281
+ }
282
+ x++;
283
+ ddFx += 2;
284
+ f += ddFx;
285
+ if (cornername & 0x4) {
286
+ this.drawPixel(x0 + x, y0 + y, color);
287
+ this.drawPixel(x0 + y, y0 + x, color);
288
+ }
289
+ if (cornername & 0x2) {
290
+ this.drawPixel(x0 + x, y0 - y, color);
291
+ this.drawPixel(x0 + y, y0 - x, color);
292
+ }
293
+ if (cornername & 0x8) {
294
+ this.drawPixel(x0 - y, y0 + x, color);
295
+ this.drawPixel(x0 - x, y0 + y, color);
296
+ }
297
+ if (cornername & 0x1) {
298
+ this.drawPixel(x0 - y, y0 - x, color);
299
+ this.drawPixel(x0 - x, y0 - y, color);
300
+ }
301
+ }
302
+ }
303
+ fillCircle(x0, y0, r, color) {
304
+ x0 = Math.trunc(x0);
305
+ y0 = Math.trunc(y0);
306
+ r = Math.trunc(r);
307
+ this.drawFastVLine(x0, y0 - r, 2 * r + 1, color);
308
+ this.fillCircleHelper(x0, y0, r, 3, 0, color);
309
+ }
310
+ fillCircleHelper(x0, y0, r, corners, delta, color) {
311
+ let f = 1 - r;
312
+ let ddFx = 1;
313
+ let ddFy = -2 * r;
314
+ let x = 0;
315
+ let y = r;
316
+ let px = x;
317
+ let py = y;
318
+ delta++;
319
+ while (x < y) {
320
+ if (f >= 0) {
321
+ y--;
322
+ ddFy += 2;
323
+ f += ddFy;
324
+ }
325
+ x++;
326
+ ddFx += 2;
327
+ f += ddFx;
328
+ if (x < y + 1) {
329
+ if (corners & 1)
330
+ this.drawFastVLine(x0 + x, y0 - y, 2 * y + delta, color);
331
+ if (corners & 2)
332
+ this.drawFastVLine(x0 - x, y0 - y, 2 * y + delta, color);
333
+ }
334
+ if (y !== py) {
335
+ if (corners & 1)
336
+ this.drawFastVLine(x0 + py, y0 - px, 2 * px + delta, color);
337
+ if (corners & 2)
338
+ this.drawFastVLine(x0 - py, y0 - px, 2 * px + delta, color);
339
+ py = y;
340
+ }
341
+ px = x;
342
+ }
343
+ }
344
+ drawLine(x0, y0, x1, y1, color) {
345
+ x0 = Math.trunc(x0);
346
+ y0 = Math.trunc(y0);
347
+ x1 = Math.trunc(x1);
348
+ y1 = Math.trunc(y1);
349
+ const steep = Math.abs(y1 - y0) > Math.abs(x1 - x0);
350
+ if (steep) {
351
+ [x0, y0] = [y0, x0];
352
+ [x1, y1] = [y1, x1];
353
+ }
354
+ if (x0 > x1) {
355
+ [x0, x1] = [x1, x0];
356
+ [y0, y1] = [y1, y0];
357
+ }
358
+ const dx = x1 - x0;
359
+ const dy = Math.abs(y1 - y0);
360
+ let err = Math.trunc(dx / 2);
361
+ const ystep = y0 < y1 ? 1 : -1;
362
+ for (; x0 <= x1; x0++) {
363
+ if (steep)
364
+ this.drawPixel(y0, x0, color);
365
+ else
366
+ this.drawPixel(x0, y0, color);
367
+ err -= dy;
368
+ if (err < 0) {
369
+ y0 += ystep;
370
+ err += dx;
371
+ }
372
+ }
373
+ }
374
+ setCursor(x, y) {
375
+ this.cursorX = Math.trunc(x);
376
+ this.cursorY = Math.trunc(y);
377
+ }
378
+ setTextColor(color, bg) {
379
+ this.textColor = color & 0xffffff;
380
+ this.textBgColor = bg === undefined ? this.textColor : bg & 0xffffff;
381
+ }
382
+ setTextSize(size, sy) {
383
+ this.textSizeX = Math.max(1, Math.trunc(size));
384
+ this.textSizeY = Math.max(1, Math.trunc(sy ?? size));
385
+ }
386
+ setTextWrap(wrap) {
387
+ this.wrap = wrap;
388
+ }
389
+ print(value) {
390
+ const text = String(value ?? "");
391
+ for (let i = 0; i < text.length; i++) {
392
+ this.write(text.charCodeAt(i) & 0xff);
393
+ }
394
+ }
395
+ textWidth(value, size = this.textSizeX) {
396
+ return String(value ?? "").length * Math.max(1, Math.trunc(size)) * 6;
397
+ }
398
+ textHeight(size = this.textSizeY) {
399
+ return Math.max(1, Math.trunc(size)) * 8;
400
+ }
401
+ drawAntialiasedText(value, x, y, color, bg, size) {
402
+ const text = String(value ?? "");
403
+ if (!text)
404
+ return;
405
+ size = Math.max(1, Math.trunc(size));
406
+ const w = this.textWidth(text, size);
407
+ const h = this.textHeight(size);
408
+ if (w <= 0 || h <= 0 || (color & 0xffff) === (bg & 0xffff)) {
409
+ this.setCursor(x, y);
410
+ this.setTextColor(color, bg);
411
+ this.setTextSize(size);
412
+ this.setTextWrap(false);
413
+ this.print(text);
414
+ return;
415
+ }
416
+ const destX = Math.trunc(x);
417
+ const destY = Math.trunc(y);
418
+ const activeClip = this.clipRect ?? { x: 0, y: 0, w: this.width, h: this.height };
419
+ const clipX0 = Math.max(destX, activeClip.x, 0);
420
+ const clipY0 = Math.max(destY, activeClip.y, 0);
421
+ const clipX1 = Math.min(destX + w, activeClip.x + activeClip.w, this.width);
422
+ const clipY1 = Math.min(destY + h, activeClip.y + activeClip.h, this.height);
423
+ if (clipX0 >= clipX1 || clipY0 >= clipY1)
424
+ return;
425
+ const localX0 = clipX0 - destX;
426
+ const localY0 = clipY0 - destY;
427
+ const localX1 = clipX1 - destX;
428
+ const localY1 = clipY1 - destY;
429
+ const src = new HostAdafruitGFX(w, h, this.font);
430
+ src.fillScreen(bg);
431
+ src.setCursor(0, 0);
432
+ src.setTextColor(color, bg);
433
+ src.setTextSize(size);
434
+ src.setTextWrap(false);
435
+ src.print(text);
436
+ const fg = color & 0xffff;
437
+ const background = bg & 0xffff;
438
+ const out = new Uint16Array(w * h);
439
+ out.fill(background);
440
+ const foregroundNeighbors = (px, py, radius = 1) => {
441
+ let count = 0;
442
+ for (let dy = -radius; dy <= radius; dy++) {
443
+ const yy = py + dy;
444
+ if (yy < 0 || yy >= h)
445
+ continue;
446
+ for (let dx = -radius; dx <= radius; dx++) {
447
+ const xx = px + dx;
448
+ if (xx < 0 || xx >= w)
449
+ continue;
450
+ if (src.buffer[yy * w + xx] === fg)
451
+ count++;
452
+ }
453
+ }
454
+ return count;
455
+ };
456
+ const coverageFor = (neighbors, outerNeighbors, isFg) => {
457
+ if (isFg) {
458
+ if (size <= 1)
459
+ return neighbors >= 4 ? 100 : 96;
460
+ if (size === 2)
461
+ return neighbors >= 8 ? 100 : neighbors >= 5 ? 96 : 92;
462
+ return neighbors >= 8 ? 100 : neighbors >= 6 ? 96 : neighbors >= 4 ? 90 : 84;
463
+ }
464
+ if (neighbors === 0) {
465
+ return size >= 3 && outerNeighbors > 0 ? Math.min(14, outerNeighbors * 2) : 0;
466
+ }
467
+ const step = size <= 1 ? 4 : size === 2 ? 6 : 8;
468
+ const cap = size <= 1 ? 18 : size === 2 ? 28 : 38;
469
+ return Math.min(cap, neighbors * step);
470
+ };
471
+ for (let yy = localY0; yy < localY1; yy++) {
472
+ for (let xx = localX0; xx < localX1; xx++) {
473
+ const px = src.buffer[yy * w + xx];
474
+ const neighbors = foregroundNeighbors(xx, yy);
475
+ const outerNeighbors = size >= 3 && px !== fg && neighbors === 0 ? foregroundNeighbors(xx, yy, 2) : 0;
476
+ const coverage = coverageFor(neighbors, outerNeighbors, px === fg);
477
+ out[yy * w + xx] = coverage === 0 ? background : blendRgb565(fg, background, coverage);
478
+ }
479
+ }
480
+ for (let yy = localY0; yy < localY1; yy++) {
481
+ for (let xx = localX0; xx < localX1; xx++) {
482
+ this.drawPixel(destX + xx, destY + yy, out[yy * w + xx]);
483
+ }
484
+ }
485
+ }
486
+ write(c) {
487
+ if (c === 10) {
488
+ this.cursorX = 0;
489
+ this.cursorY += this.textSizeY * 8;
490
+ return;
491
+ }
492
+ if (c === 13)
493
+ return;
494
+ if (this.wrap && this.cursorX + this.textSizeX * 6 > this.width) {
495
+ this.cursorX = 0;
496
+ this.cursorY += this.textSizeY * 8;
497
+ }
498
+ this.drawChar(this.cursorX, this.cursorY, c, this.textColor, this.textBgColor, this.textSizeX, this.textSizeY);
499
+ this.cursorX += this.textSizeX * 6;
500
+ }
501
+ drawChar(x, y, c, color, bg, sizeX, sizeY = sizeX) {
502
+ if (x >= this.width || y >= this.height || x + 6 * sizeX - 1 < 0 || y + 8 * sizeY - 1 < 0)
503
+ return;
504
+ if (c >= 176)
505
+ c++;
506
+ for (let i = 0; i < 5; i++) {
507
+ let line = this.font[c * 5 + i] ?? 0;
508
+ for (let j = 0; j < 8; j++, line >>= 1) {
509
+ if (line & 1) {
510
+ if (sizeX === 1 && sizeY === 1)
511
+ this.writePixel(x + i, y + j, color);
512
+ else
513
+ this.writeFillRect(x + i * sizeX, y + j * sizeY, sizeX, sizeY, color);
514
+ }
515
+ else if (bg !== color) {
516
+ if (sizeX === 1 && sizeY === 1)
517
+ this.writePixel(x + i, y + j, bg);
518
+ else
519
+ this.writeFillRect(x + i * sizeX, y + j * sizeY, sizeX, sizeY, bg);
520
+ }
521
+ }
522
+ }
523
+ if (bg !== color) {
524
+ if (sizeX === 1 && sizeY === 1)
525
+ this.writeFastVLine(x + 5, y, 8, bg);
526
+ else
527
+ this.writeFillRect(x + 5 * sizeX, y, sizeX, 8 * sizeY, bg);
528
+ }
529
+ }
530
+ toRgbaBytes() {
531
+ const out = new Uint8ClampedArray(this.buffer.length * 4);
532
+ for (let i = 0; i < this.buffer.length; i++) {
533
+ const c = this.buffer[i];
534
+ const p = i * 4;
535
+ if (this.monoSnap) {
536
+ // Mono: values are already 0xffffff/0x000000 — emit directly.
537
+ out[p] = (c >> 16) & 0xff;
538
+ out[p + 1] = (c >> 8) & 0xff;
539
+ out[p + 2] = c & 0xff;
540
+ }
541
+ else {
542
+ // 565 target: unpack to 888.
543
+ const { r, g, b } = rgb565ToRgb888(c);
544
+ out[p] = r;
545
+ out[p + 1] = g;
546
+ out[p + 2] = b;
547
+ }
548
+ out[p + 3] = 255;
549
+ }
550
+ return out;
551
+ }
552
+ }