gloomberb 0.2.0 → 0.3.2

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 (311) hide show
  1. package/README.md +20 -19
  2. package/bin/gloomberb +3 -0
  3. package/package.json +5 -4
  4. package/patches/@opentui%2Fcore@0.1.90.patch +356 -0
  5. package/src/app.tsx +1365 -0
  6. package/src/brokers/sync-broker-instance.test.ts +100 -0
  7. package/src/brokers/sync-broker-instance.ts +341 -0
  8. package/src/cli/commands/plugins.ts +173 -0
  9. package/src/cli/commands/portfolio.ts +210 -0
  10. package/src/cli/commands/search.ts +120 -0
  11. package/src/cli/commands/ticker.ts +375 -0
  12. package/src/cli/commands/watchlist.ts +210 -0
  13. package/src/cli/context.ts +62 -0
  14. package/src/cli/errors.ts +13 -0
  15. package/src/cli/helpers.ts +85 -0
  16. package/src/cli/index.ts +143 -0
  17. package/src/cli/ticker-resolution.ts +39 -0
  18. package/src/cli/types.ts +15 -0
  19. package/src/cli.test.ts +361 -0
  20. package/src/components/chart/chart-data.ts +239 -0
  21. package/src/components/chart/chart-renderer.ts +1199 -0
  22. package/src/components/chart/chart-types.ts +82 -0
  23. package/src/components/chart/chart-viewport.ts +21 -0
  24. package/src/components/chart/chart.test.ts +439 -0
  25. package/src/components/chart/comparison-chart-data.test.ts +109 -0
  26. package/src/components/chart/comparison-chart-data.ts +316 -0
  27. package/src/components/chart/comparison-chart-renderer.test.ts +84 -0
  28. package/src/components/chart/comparison-chart-renderer.ts +354 -0
  29. package/src/components/chart/comparison-stock-chart.tsx +1486 -0
  30. package/src/components/chart/cursor-motion.ts +48 -0
  31. package/src/components/chart/native/chart-rasterizer.ts +732 -0
  32. package/src/components/chart/native/kitty-adapter.ts +12 -0
  33. package/src/components/chart/native/kitty-manager.ts +142 -0
  34. package/src/components/chart/native/kitty-protocol.ts +110 -0
  35. package/src/components/chart/native/kitty-support.ts +71 -0
  36. package/src/components/chart/native/kitty.test.ts +620 -0
  37. package/src/components/chart/native/renderer-selection.ts +24 -0
  38. package/src/components/chart/native/surface-manager.ts +225 -0
  39. package/src/components/chart/native/surface-sync.ts +34 -0
  40. package/src/components/chart/stock-chart-renderer-switch.test.tsx +165 -0
  41. package/src/components/chart/stock-chart.test.ts +107 -0
  42. package/src/components/chart/stock-chart.tsx +1556 -0
  43. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +137 -0
  44. package/src/components/command-bar/command-bar.test.tsx +1546 -0
  45. package/src/components/command-bar/command-bar.tsx +4841 -0
  46. package/src/components/command-bar/command-registry.ts +269 -0
  47. package/src/components/command-bar/helpers.test.ts +224 -0
  48. package/src/components/command-bar/helpers.ts +371 -0
  49. package/src/components/command-bar/root-shortcuts.ts +158 -0
  50. package/src/components/command-bar/view-model.test.ts +152 -0
  51. package/src/components/command-bar/view-model.ts +185 -0
  52. package/src/components/command-bar/workflow-ops.test.ts +70 -0
  53. package/src/components/command-bar/workflow-ops.ts +433 -0
  54. package/src/components/command-bar/workflow-types.ts +127 -0
  55. package/src/components/detail-feed-view.test.tsx +105 -0
  56. package/src/components/detail-feed-view.tsx +295 -0
  57. package/src/components/index.ts +32 -0
  58. package/src/components/layout/floating-pane.tsx +49 -0
  59. package/src/components/layout/header.tsx +151 -0
  60. package/src/components/layout/pane-header.tsx +44 -0
  61. package/src/components/layout/pane.tsx +49 -0
  62. package/src/components/layout/pixel-mouse-mode.test.ts +122 -0
  63. package/src/components/layout/pixel-mouse.test.ts +40 -0
  64. package/src/components/layout/shell.test.tsx +651 -0
  65. package/src/components/layout/shell.tsx +1200 -0
  66. package/src/components/layout/status-bar.test.tsx +173 -0
  67. package/src/components/layout/status-bar.tsx +138 -0
  68. package/src/components/markdown-editor.tsx +134 -0
  69. package/src/components/markdown-text.tsx +218 -0
  70. package/src/components/onboarding/onboarding-wizard.test.tsx +184 -0
  71. package/src/components/onboarding/onboarding-wizard.tsx +1365 -0
  72. package/src/components/pane-settings-dialog.tsx +468 -0
  73. package/src/components/pane-template-wizard.test.tsx +52 -0
  74. package/src/components/pane-template-wizard.tsx +199 -0
  75. package/src/components/price-selector-dialog.tsx +195 -0
  76. package/src/components/spinner.tsx +2 -0
  77. package/src/components/tab-bar.tsx +17 -0
  78. package/src/components/ticker-badge-text.test.tsx +160 -0
  79. package/src/components/ticker-badge-text.tsx +58 -0
  80. package/src/components/ticker-badge.tsx +88 -0
  81. package/src/components/ticker-list-table.tsx +188 -0
  82. package/src/components/toggle-list.tsx +74 -0
  83. package/src/components/ui/button.tsx +77 -0
  84. package/src/components/ui/data-table.tsx +210 -0
  85. package/src/components/ui/external-link.tsx +22 -0
  86. package/src/components/ui/fields.tsx +187 -0
  87. package/src/components/ui/frame.tsx +73 -0
  88. package/src/components/ui/index.ts +53 -0
  89. package/src/components/ui/list-view.tsx +166 -0
  90. package/src/components/ui/loading.tsx +70 -0
  91. package/src/components/ui/status.tsx +83 -0
  92. package/src/components/ui/tabs.tsx +47 -0
  93. package/src/components/ui/toggle.tsx +164 -0
  94. package/src/components/ui/ui.test.tsx +134 -0
  95. package/src/data/app-persistence.ts +25 -0
  96. package/src/data/config-store.test.ts +347 -0
  97. package/src/data/config-store.ts +507 -0
  98. package/src/data/plugin-state-store.ts +102 -0
  99. package/src/data/resource-store.ts +323 -0
  100. package/src/data/session-store.ts +52 -0
  101. package/src/data/sqlite-cache.test.ts +103 -0
  102. package/src/data/sqlite-database.ts +62 -0
  103. package/src/data/sqlite-json.ts +19 -0
  104. package/src/data/ticker-repository.ts +45 -0
  105. package/src/data/ticker-store.ts +46 -0
  106. package/src/index.tsx +67 -0
  107. package/src/market-data/coordinator.test.ts +369 -0
  108. package/src/market-data/coordinator.ts +580 -0
  109. package/src/market-data/hooks.tsx +234 -0
  110. package/src/market-data/query-store.ts +28 -0
  111. package/src/market-data/request-types.test.ts +64 -0
  112. package/src/market-data/request-types.ts +70 -0
  113. package/src/market-data/result-types.ts +56 -0
  114. package/src/market-data/selectors.ts +166 -0
  115. package/src/market-data/trace.ts +7 -0
  116. package/src/pane-settings.test.ts +55 -0
  117. package/src/pane-settings.ts +73 -0
  118. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +350 -0
  119. package/src/plugins/builtin/ai/gloomberb-cli.ts +70 -0
  120. package/src/plugins/builtin/ai/index.test.tsx +139 -0
  121. package/src/plugins/builtin/ai/index.tsx +86 -0
  122. package/src/plugins/builtin/ai/providers.ts +99 -0
  123. package/src/plugins/builtin/ai/runner.ts +82 -0
  124. package/src/plugins/builtin/ai/screener-contract.test.ts +117 -0
  125. package/src/plugins/builtin/ai/screener-contract.ts +174 -0
  126. package/src/plugins/builtin/ai/screener-pane.test.tsx +365 -0
  127. package/src/plugins/builtin/ai/screener-pane.tsx +1161 -0
  128. package/src/plugins/builtin/ai/settings.ts +79 -0
  129. package/src/plugins/builtin/ai/ticker-context.ts +64 -0
  130. package/src/plugins/builtin/ai/utils.ts +71 -0
  131. package/src/plugins/builtin/ask-ai.test.tsx +258 -0
  132. package/src/plugins/builtin/ask-ai.tsx +9 -0
  133. package/src/plugins/builtin/chat-controller.test.ts +217 -0
  134. package/src/plugins/builtin/chat-controller.ts +340 -0
  135. package/src/plugins/builtin/chat.test.tsx +451 -0
  136. package/src/plugins/builtin/chat.tsx +620 -0
  137. package/src/plugins/builtin/comparison-chart.test.tsx +277 -0
  138. package/src/plugins/builtin/comparison-chart.tsx +169 -0
  139. package/src/plugins/builtin/data-providers.ts +16 -0
  140. package/src/plugins/builtin/debug.tsx +351 -0
  141. package/src/plugins/builtin/help.test.tsx +110 -0
  142. package/src/plugins/builtin/help.tsx +281 -0
  143. package/src/plugins/builtin/layout-manager.tsx +264 -0
  144. package/src/plugins/builtin/manual-entry.tsx +25 -0
  145. package/src/plugins/builtin/news.tsx +143 -0
  146. package/src/plugins/builtin/notes-files.ts +58 -0
  147. package/src/plugins/builtin/notes.tsx +396 -0
  148. package/src/plugins/builtin/options-availability.test.tsx +179 -0
  149. package/src/plugins/builtin/options-availability.ts +158 -0
  150. package/src/plugins/builtin/options.tsx +364 -0
  151. package/src/plugins/builtin/portfolio-list/header.tsx +227 -0
  152. package/src/plugins/builtin/portfolio-list/index.tsx +99 -0
  153. package/src/plugins/builtin/portfolio-list/metrics.ts +460 -0
  154. package/src/plugins/builtin/portfolio-list/pane.tsx +533 -0
  155. package/src/plugins/builtin/portfolio-list/settings.ts +270 -0
  156. package/src/plugins/builtin/portfolio-list/summary.tsx +270 -0
  157. package/src/plugins/builtin/portfolio-list/table.tsx +64 -0
  158. package/src/plugins/builtin/portfolio-list.test.ts +74 -0
  159. package/src/plugins/builtin/portfolio-list.test.tsx +1164 -0
  160. package/src/plugins/builtin/portfolio-metrics.test.ts +224 -0
  161. package/src/plugins/builtin/quote-monitor.test.tsx +114 -0
  162. package/src/plugins/builtin/sec.tsx +250 -0
  163. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +43 -0
  164. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +390 -0
  165. package/src/plugins/builtin/ticker-detail/index.tsx +80 -0
  166. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +242 -0
  167. package/src/plugins/builtin/ticker-detail/pane.tsx +183 -0
  168. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +83 -0
  169. package/src/plugins/builtin/ticker-detail/settings.ts +132 -0
  170. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +193 -0
  171. package/src/plugins/builtin/ticker-detail.test.tsx +578 -0
  172. package/src/plugins/builtin/yahoo.ts +18 -0
  173. package/src/plugins/event-bus.ts +37 -0
  174. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +35 -0
  175. package/src/plugins/ibkr/account-cache.test.ts +101 -0
  176. package/src/plugins/ibkr/account-cache.ts +137 -0
  177. package/src/plugins/ibkr/broker-adapter.ts +168 -0
  178. package/src/plugins/ibkr/config.test.ts +68 -0
  179. package/src/plugins/ibkr/config.ts +262 -0
  180. package/src/plugins/ibkr/dialogs.tsx +83 -0
  181. package/src/plugins/ibkr/flex.test.ts +107 -0
  182. package/src/plugins/ibkr/flex.ts +267 -0
  183. package/src/plugins/ibkr/fundamental-parser.ts +149 -0
  184. package/src/plugins/ibkr/gateway-helpers.ts +28 -0
  185. package/src/plugins/ibkr/gateway-selection.ts +51 -0
  186. package/src/plugins/ibkr/gateway-service.test.ts +450 -0
  187. package/src/plugins/ibkr/gateway-service.ts +2228 -0
  188. package/src/plugins/ibkr/index.test.tsx +183 -0
  189. package/src/plugins/ibkr/index.tsx +153 -0
  190. package/src/plugins/ibkr/instance-selection.test.ts +121 -0
  191. package/src/plugins/ibkr/instance-selection.ts +36 -0
  192. package/src/plugins/ibkr/price-normalization.test.ts +38 -0
  193. package/src/plugins/ibkr/price-normalization.ts +59 -0
  194. package/src/plugins/ibkr/trade-badge.tsx +27 -0
  195. package/src/plugins/ibkr/trade-tab.test.tsx +225 -0
  196. package/src/plugins/ibkr/trade-tab.tsx +1050 -0
  197. package/src/plugins/ibkr/trade-utils.test.ts +175 -0
  198. package/src/plugins/ibkr/trade-utils.ts +149 -0
  199. package/src/plugins/ibkr/trading-pane.tsx +438 -0
  200. package/src/plugins/ibkr/trading-state.test.ts +109 -0
  201. package/src/plugins/ibkr/trading-state.ts +339 -0
  202. package/src/plugins/loader.ts +71 -0
  203. package/src/plugins/pane-manager.test.ts +103 -0
  204. package/src/plugins/pane-manager.ts +974 -0
  205. package/src/plugins/plugin-persistence.ts +85 -0
  206. package/src/plugins/plugin-runtime.test.tsx +185 -0
  207. package/src/plugins/plugin-runtime.tsx +175 -0
  208. package/src/plugins/prediction-markets/cache.ts +114 -0
  209. package/src/plugins/prediction-markets/categories.ts +223 -0
  210. package/src/plugins/prediction-markets/chart.tsx +512 -0
  211. package/src/plugins/prediction-markets/columns.ts +133 -0
  212. package/src/plugins/prediction-markets/controller-data.ts +614 -0
  213. package/src/plugins/prediction-markets/controller.ts +725 -0
  214. package/src/plugins/prediction-markets/detail/book.tsx +118 -0
  215. package/src/plugins/prediction-markets/detail/outcomes.tsx +102 -0
  216. package/src/plugins/prediction-markets/detail/overview.tsx +73 -0
  217. package/src/plugins/prediction-markets/detail/pane.tsx +308 -0
  218. package/src/plugins/prediction-markets/detail/rules.tsx +16 -0
  219. package/src/plugins/prediction-markets/detail/shared.tsx +29 -0
  220. package/src/plugins/prediction-markets/detail/trades.tsx +30 -0
  221. package/src/plugins/prediction-markets/detail.test.tsx +47 -0
  222. package/src/plugins/prediction-markets/index.tsx +102 -0
  223. package/src/plugins/prediction-markets/keyboard.ts +64 -0
  224. package/src/plugins/prediction-markets/launch.test.ts +126 -0
  225. package/src/plugins/prediction-markets/launch.ts +240 -0
  226. package/src/plugins/prediction-markets/metrics.ts +311 -0
  227. package/src/plugins/prediction-markets/navigation.ts +90 -0
  228. package/src/plugins/prediction-markets/outcome-order.ts +19 -0
  229. package/src/plugins/prediction-markets/pane.test.tsx +425 -0
  230. package/src/plugins/prediction-markets/pane.tsx +294 -0
  231. package/src/plugins/prediction-markets/plugin.test.tsx +570 -0
  232. package/src/plugins/prediction-markets/rows.ts +229 -0
  233. package/src/plugins/prediction-markets/services/fetch.ts +97 -0
  234. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +555 -0
  235. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +660 -0
  236. package/src/plugins/prediction-markets/services/polymarket-ws.ts +108 -0
  237. package/src/plugins/prediction-markets/settings.ts +164 -0
  238. package/src/plugins/prediction-markets/status.ts +24 -0
  239. package/src/plugins/prediction-markets/table.tsx +84 -0
  240. package/src/plugins/prediction-markets/test-helpers.tsx +636 -0
  241. package/src/plugins/prediction-markets/types.ts +186 -0
  242. package/src/plugins/registry.ts +633 -0
  243. package/src/sources/exchange-rates.ts +18 -0
  244. package/src/sources/gloomberb-cloud.test.ts +199 -0
  245. package/src/sources/gloomberb-cloud.ts +326 -0
  246. package/src/sources/provider-errors.ts +19 -0
  247. package/src/sources/provider-router.test.ts +1476 -0
  248. package/src/sources/provider-router.ts +1405 -0
  249. package/src/sources/sec-edgar.test.ts +286 -0
  250. package/src/sources/sec-edgar.ts +526 -0
  251. package/src/sources/yahoo-finance.test.ts +14 -0
  252. package/src/sources/yahoo-finance.ts +1017 -0
  253. package/src/state/app-activity.test.ts +41 -0
  254. package/src/state/app-activity.ts +106 -0
  255. package/src/state/app-bootstrap.test.ts +471 -0
  256. package/src/state/app-bootstrap.ts +308 -0
  257. package/src/state/app-context.test.ts +406 -0
  258. package/src/state/app-context.test.tsx +162 -0
  259. package/src/state/app-context.tsx +981 -0
  260. package/src/state/selectors.ts +67 -0
  261. package/src/state/session-persistence.test.ts +128 -0
  262. package/src/state/session-persistence.ts +161 -0
  263. package/src/state/ticker-refresh-queue.test.ts +46 -0
  264. package/src/state/ticker-refresh-queue.ts +40 -0
  265. package/src/state/use-inline-tickers.ts +174 -0
  266. package/src/state/use-quote-streaming.test.ts +37 -0
  267. package/src/state/use-quote-streaming.test.tsx +70 -0
  268. package/src/state/use-quote-streaming.ts +80 -0
  269. package/src/state/use-session-snapshot.ts +84 -0
  270. package/src/test-support/data-provider.ts +22 -0
  271. package/src/theme/color-utils.ts +67 -0
  272. package/src/theme/colors.ts +128 -0
  273. package/src/theme/themes.test.ts +45 -0
  274. package/src/theme/themes.ts +491 -0
  275. package/src/types/broker.ts +105 -0
  276. package/src/types/config.ts +444 -0
  277. package/src/types/data-provider.ts +81 -0
  278. package/src/types/financials.ts +142 -0
  279. package/src/types/instrument.ts +29 -0
  280. package/src/types/persistence.ts +21 -0
  281. package/src/types/plugin.ts +354 -0
  282. package/src/types/ticker.ts +58 -0
  283. package/src/types/trading.ts +85 -0
  284. package/src/updater.test.ts +282 -0
  285. package/src/updater.ts +303 -0
  286. package/src/utils/api-client.ts +666 -0
  287. package/src/utils/ascii-chart.ts +172 -0
  288. package/src/utils/broker-instances.ts +47 -0
  289. package/src/utils/cli-output.ts +83 -0
  290. package/src/utils/currency-units.test.ts +49 -0
  291. package/src/utils/currency-units.ts +82 -0
  292. package/src/utils/debug-log.ts +133 -0
  293. package/src/utils/exchange-rate-map.ts +8 -0
  294. package/src/utils/format.ts +158 -0
  295. package/src/utils/fuzzy-search.ts +34 -0
  296. package/src/utils/market-status.ts +95 -0
  297. package/src/utils/options.ts +86 -0
  298. package/src/utils/price-history.ts +39 -0
  299. package/src/utils/quote-time.test.ts +22 -0
  300. package/src/utils/quote-time.ts +50 -0
  301. package/src/utils/sec.test.ts +55 -0
  302. package/src/utils/sec.ts +45 -0
  303. package/src/utils/ticker-list.test.ts +22 -0
  304. package/src/utils/ticker-list.ts +35 -0
  305. package/src/utils/ticker-metadata.ts +51 -0
  306. package/src/utils/ticker-search.test.ts +227 -0
  307. package/src/utils/ticker-search.ts +764 -0
  308. package/src/utils/ticker-tokenizer.test.ts +48 -0
  309. package/src/utils/ticker-tokenizer.ts +72 -0
  310. package/src/version.ts +1 -0
  311. package/install.mjs +0 -68
@@ -0,0 +1,732 @@
1
+ import type { PixelResolution } from "@opentui/core";
2
+ import { computeGridLines, type ChartScene } from "../chart-renderer";
3
+ import type { ComparisonChartScene } from "../comparison-chart-renderer";
4
+ import type { ChartRenderMode } from "../chart-types";
5
+
6
+ interface RgbaColor {
7
+ r: number;
8
+ g: number;
9
+ b: number;
10
+ a: number;
11
+ }
12
+
13
+ export interface CellRect {
14
+ x: number;
15
+ y: number;
16
+ width: number;
17
+ height: number;
18
+ }
19
+
20
+ export interface PixelRect {
21
+ x: number;
22
+ y: number;
23
+ width: number;
24
+ height: number;
25
+ }
26
+
27
+ export interface NativeChartBitmap {
28
+ width: number;
29
+ height: number;
30
+ pixels: Uint8Array;
31
+ }
32
+
33
+ export interface NativeCrosshairOverlay {
34
+ width: number;
35
+ height: number;
36
+ chartRows: number;
37
+ pixelX: number | null;
38
+ pixelY: number | null;
39
+ colors: {
40
+ crosshairColor: string;
41
+ };
42
+ }
43
+
44
+ export interface NativePlacement {
45
+ column: number;
46
+ row: number;
47
+ cols: number;
48
+ rows: number;
49
+ cropX: number;
50
+ cropY: number;
51
+ cropWidth: number;
52
+ cropHeight: number;
53
+ }
54
+
55
+ const clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(max, value));
56
+
57
+ function smoothstep(edge0: number, edge1: number, value: number): number {
58
+ const range = edge1 - edge0;
59
+ if (range === 0) return value < edge0 ? 0 : 1;
60
+ const t = clamp((value - edge0) / range, 0, 1);
61
+ return t * t * (3 - 2 * t);
62
+ }
63
+
64
+ function parseHex(hex: string, alpha = 1): RgbaColor {
65
+ const normalized = hex.replace("#", "");
66
+ return {
67
+ r: parseInt(normalized.slice(0, 2), 16),
68
+ g: parseInt(normalized.slice(2, 4), 16),
69
+ b: parseInt(normalized.slice(4, 6), 16),
70
+ a: Math.round(clamp(alpha, 0, 1) * 255),
71
+ };
72
+ }
73
+
74
+ function blendPixel(
75
+ data: Uint8Array,
76
+ width: number,
77
+ height: number,
78
+ x: number,
79
+ y: number,
80
+ color: RgbaColor,
81
+ opacity = 1,
82
+ ) {
83
+ if (x < 0 || y < 0 || x >= width || y >= height) return;
84
+
85
+ const alpha = clamp((color.a / 255) * opacity, 0, 1);
86
+ if (alpha <= 0) return;
87
+
88
+ const index = (y * width + x) * 4;
89
+ const dstAlpha = data[index + 3]! / 255;
90
+ const outAlpha = alpha + dstAlpha * (1 - alpha);
91
+
92
+ if (outAlpha <= 0) return;
93
+
94
+ const dstFactor = dstAlpha * (1 - alpha);
95
+ data[index] = Math.round((color.r * alpha + data[index]! * dstFactor) / outAlpha);
96
+ data[index + 1] = Math.round((color.g * alpha + data[index + 1]! * dstFactor) / outAlpha);
97
+ data[index + 2] = Math.round((color.b * alpha + data[index + 2]! * dstFactor) / outAlpha);
98
+ data[index + 3] = Math.round(outAlpha * 255);
99
+ }
100
+
101
+ function fillBackground(data: Uint8Array, width: number, height: number, color: RgbaColor) {
102
+ for (let index = 0; index < width * height; index += 1) {
103
+ const offset = index * 4;
104
+ data[offset] = color.r;
105
+ data[offset + 1] = color.g;
106
+ data[offset + 2] = color.b;
107
+ data[offset + 3] = color.a;
108
+ }
109
+ }
110
+
111
+ function drawLine(
112
+ data: Uint8Array,
113
+ width: number,
114
+ height: number,
115
+ x0: number,
116
+ y0: number,
117
+ x1: number,
118
+ y1: number,
119
+ color: RgbaColor,
120
+ thickness: number,
121
+ ) {
122
+ const half = thickness / 2;
123
+ const minX = Math.floor(Math.min(x0, x1) - half - 1);
124
+ const maxX = Math.ceil(Math.max(x0, x1) + half + 1);
125
+ const minY = Math.floor(Math.min(y0, y1) - half - 1);
126
+ const maxY = Math.ceil(Math.max(y0, y1) + half + 1);
127
+ const dx = x1 - x0;
128
+ const dy = y1 - y0;
129
+ const segmentLengthSq = dx * dx + dy * dy || 1;
130
+
131
+ for (let py = minY; py <= maxY; py++) {
132
+ for (let px = minX; px <= maxX; px++) {
133
+ const cx = px + 0.5;
134
+ const cy = py + 0.5;
135
+ const projection = clamp(((cx - x0) * dx + (cy - y0) * dy) / segmentLengthSq, 0, 1);
136
+ const nearestX = x0 + dx * projection;
137
+ const nearestY = y0 + dy * projection;
138
+ const distance = Math.hypot(cx - nearestX, cy - nearestY);
139
+ const coverage = 1 - smoothstep(half, half + 1.1, distance);
140
+ if (coverage > 0) {
141
+ blendPixel(data, width, height, px, py, color, coverage);
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ function fillRect(
148
+ data: Uint8Array,
149
+ width: number,
150
+ height: number,
151
+ left: number,
152
+ top: number,
153
+ right: number,
154
+ bottom: number,
155
+ color: RgbaColor,
156
+ opacity = 1,
157
+ ) {
158
+ for (let y = Math.max(Math.floor(top), 0); y <= Math.min(Math.ceil(bottom), height - 1); y++) {
159
+ for (let x = Math.max(Math.floor(left), 0); x <= Math.min(Math.ceil(right), width - 1); x++) {
160
+ blendPixel(data, width, height, x, y, color, opacity);
161
+ }
162
+ }
163
+ }
164
+
165
+ function drawCircle(
166
+ data: Uint8Array,
167
+ width: number,
168
+ height: number,
169
+ centerX: number,
170
+ centerY: number,
171
+ radius: number,
172
+ color: RgbaColor,
173
+ ) {
174
+ const minX = Math.floor(centerX - radius - 1);
175
+ const maxX = Math.ceil(centerX + radius + 1);
176
+ const minY = Math.floor(centerY - radius - 1);
177
+ const maxY = Math.ceil(centerY + radius + 1);
178
+
179
+ for (let py = minY; py <= maxY; py++) {
180
+ for (let px = minX; px <= maxX; px++) {
181
+ const distance = Math.hypot(px + 0.5 - centerX, py + 0.5 - centerY);
182
+ const coverage = 1 - smoothstep(radius - 0.65, radius + 0.65, distance);
183
+ if (coverage > 0) {
184
+ blendPixel(data, width, height, px, py, color, coverage);
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ function drawWickOutsideBody(
191
+ data: Uint8Array,
192
+ width: number,
193
+ height: number,
194
+ x: number,
195
+ highY: number,
196
+ lowY: number,
197
+ bodyTop: number,
198
+ bodyBottom: number,
199
+ color: RgbaColor,
200
+ ) {
201
+ if (highY < bodyTop) {
202
+ drawLine(data, width, height, x, highY, x, bodyTop, color, 1.2);
203
+ }
204
+ if (bodyBottom < lowY) {
205
+ drawLine(data, width, height, x, bodyBottom, x, lowY, color, 1.2);
206
+ }
207
+ }
208
+
209
+ function drawAreaFill(
210
+ data: Uint8Array,
211
+ width: number,
212
+ height: number,
213
+ yByColumn: Float32Array,
214
+ bottom: number,
215
+ color: RgbaColor,
216
+ ) {
217
+ for (let x = 0; x < yByColumn.length; x++) {
218
+ const yTop = yByColumn[x]!;
219
+ if (!Number.isFinite(yTop)) continue;
220
+ const distance = Math.max(bottom - yTop, 1);
221
+ for (let y = Math.max(Math.floor(yTop), 0); y <= Math.min(Math.ceil(bottom), height - 1); y++) {
222
+ const fade = 1 - (y - yTop) / distance;
223
+ blendPixel(data, width, height, x, y, color, 0.08 + fade * 0.32);
224
+ }
225
+ }
226
+ }
227
+
228
+ function lerp(a: number, b: number, t: number): number {
229
+ return a + (b - a) * t;
230
+ }
231
+
232
+ function getChartPixelLayout(scene: ChartScene, pixelWidth: number, pixelHeight: number) {
233
+ const volumeHeight = scene.showVolume ? Math.max(Math.round((scene.volumeHeight / Math.max(scene.height, 1)) * pixelHeight), 1) : 0;
234
+ const plotHeight = Math.max(pixelHeight - volumeHeight, 1);
235
+ return {
236
+ plotLeft: 0,
237
+ plotTop: 0,
238
+ plotRight: Math.max(pixelWidth - 1, 0),
239
+ plotBottom: Math.max(plotHeight - 1, 0),
240
+ volumeTop: plotHeight,
241
+ volumeBottom: Math.max(pixelHeight - 1, plotHeight),
242
+ };
243
+ }
244
+
245
+ function getComparisonPixelLayout(pixelWidth: number, pixelHeight: number) {
246
+ return {
247
+ plotLeft: 0,
248
+ plotTop: 0,
249
+ plotRight: Math.max(pixelWidth - 1, 0),
250
+ plotBottom: Math.max(pixelHeight - 1, 0),
251
+ };
252
+ }
253
+
254
+ function projectX(index: number, count: number, left: number, right: number): number {
255
+ if (count <= 1) return (left + right) / 2;
256
+ return lerp(left, right, index / (count - 1));
257
+ }
258
+
259
+ function projectY(value: number, min: number, max: number, top: number, bottom: number): number {
260
+ const range = max - min || 1;
261
+ return lerp(bottom, top, (value - min) / range);
262
+ }
263
+
264
+ function drawPriceGrid(
265
+ data: Uint8Array,
266
+ width: number,
267
+ height: number,
268
+ scene: ChartScene,
269
+ top: number,
270
+ bottom: number,
271
+ ) {
272
+ const gridLines = computeGridLines(scene.min, scene.max, top, bottom, 3);
273
+ const color = parseHex(scene.colors.gridColor, 0.28);
274
+
275
+ for (const line of gridLines) {
276
+ drawLine(data, width, height, 0, line.y, width - 1, line.y, color, 1);
277
+ }
278
+ }
279
+
280
+ function drawLineSeries(
281
+ data: Uint8Array,
282
+ width: number,
283
+ height: number,
284
+ scene: ChartScene,
285
+ top: number,
286
+ bottom: number,
287
+ ) {
288
+ const glow = parseHex(scene.colors.lineColor, 0.2);
289
+ const line = parseHex(scene.colors.lineColor, 0.95);
290
+ const fill = parseHex(scene.colors.fillColor, 0.7);
291
+ const yByColumn = new Float32Array(width).fill(Number.POSITIVE_INFINITY);
292
+
293
+ for (let index = 0; index < scene.points.length - 1; index++) {
294
+ const current = scene.points[index]!;
295
+ const next = scene.points[index + 1]!;
296
+ const x0 = projectX(index, scene.points.length, 0, width - 1);
297
+ const x1 = projectX(index + 1, scene.points.length, 0, width - 1);
298
+ const y0 = projectY(current.close, scene.min, scene.max, top, bottom);
299
+ const y1 = projectY(next.close, scene.min, scene.max, top, bottom);
300
+
301
+ const start = Math.max(Math.floor(Math.min(x0, x1)), 0);
302
+ const end = Math.min(Math.ceil(Math.max(x0, x1)), width - 1);
303
+ for (let x = start; x <= end; x++) {
304
+ const t = x1 === x0 ? 0 : (x - x0) / (x1 - x0);
305
+ const y = lerp(y0, y1, clamp(t, 0, 1));
306
+ yByColumn[x] = Math.min(yByColumn[x]!, y);
307
+ }
308
+
309
+ if (scene.mode === "area") {
310
+ drawLine(data, width, height, x0, y0, x1, y1, glow, 4);
311
+ drawLine(data, width, height, x0, y0, x1, y1, line, 1.75);
312
+ } else {
313
+ drawLine(data, width, height, x0, y0, x1, y1, glow, 3.2);
314
+ drawLine(data, width, height, x0, y0, x1, y1, line, 1.4);
315
+ }
316
+ }
317
+
318
+ if (scene.mode === "area") {
319
+ drawAreaFill(data, width, height, yByColumn, bottom, fill);
320
+ }
321
+ }
322
+
323
+ function drawCandles(
324
+ data: Uint8Array,
325
+ width: number,
326
+ height: number,
327
+ scene: ChartScene,
328
+ top: number,
329
+ bottom: number,
330
+ mode: Extract<ChartRenderMode, "candles" | "ohlc">,
331
+ ) {
332
+ const spacing = width / Math.max(scene.points.length, 1);
333
+ const bodyWidth = clamp(spacing * 0.58, 2, Math.max(spacing - 1, 2));
334
+ const tickLength = clamp(spacing * 0.34, 2, Math.max(spacing * 0.48, 2));
335
+ const horizontalPad = mode === "ohlc"
336
+ ? Math.ceil(Math.max(tickLength - 1, 0))
337
+ : Math.ceil(bodyWidth / 2);
338
+ const plotLeft = horizontalPad;
339
+ const plotRight = Math.max(width - 1 - horizontalPad, plotLeft);
340
+
341
+ for (let index = 0; index < scene.points.length; index++) {
342
+ const point = scene.points[index]!;
343
+ const x = projectX(index, scene.points.length, plotLeft, plotRight);
344
+ const highY = projectY(point.high, scene.min, scene.max, top, bottom);
345
+ const lowY = projectY(point.low, scene.min, scene.max, top, bottom);
346
+ const openY = projectY(point.open, scene.min, scene.max, top, bottom);
347
+ const closeY = projectY(point.close, scene.min, scene.max, top, bottom);
348
+ const isUp = point.close >= point.open;
349
+
350
+ const wickColor = parseHex(isUp ? scene.colors.wickUp : scene.colors.wickDown, 0.92);
351
+ const bodyColor = parseHex(isUp ? scene.colors.candleUp : scene.colors.candleDown, 1);
352
+
353
+ if (mode === "ohlc") {
354
+ drawLine(data, width, height, x, highY, x, lowY, wickColor, 1.2);
355
+ drawLine(data, width, height, x - tickLength, openY, x, openY, bodyColor, 1.4);
356
+ drawLine(data, width, height, x, closeY, x + tickLength, closeY, bodyColor, 1.4);
357
+ continue;
358
+ }
359
+
360
+ const bodyTop = Math.min(openY, closeY);
361
+ const bodyBottom = Math.max(openY, closeY);
362
+ if (Math.abs(bodyBottom - bodyTop) < 1.25) {
363
+ drawLine(data, width, height, x, highY, x, lowY, wickColor, 1.2);
364
+ drawLine(data, width, height, x - bodyWidth / 2, bodyTop, x + bodyWidth / 2, bodyTop, bodyColor, 1.5);
365
+ } else {
366
+ drawWickOutsideBody(data, width, height, x, highY, lowY, bodyTop, bodyBottom, wickColor);
367
+ fillRect(data, width, height, x - bodyWidth / 2, bodyTop, x + bodyWidth / 2, bodyBottom, bodyColor, 1);
368
+ }
369
+ }
370
+ }
371
+
372
+ function drawVolume(
373
+ data: Uint8Array,
374
+ width: number,
375
+ height: number,
376
+ scene: ChartScene,
377
+ top: number,
378
+ bottom: number,
379
+ ) {
380
+ if (!scene.showVolume || bottom < top) return;
381
+
382
+ const volumes = scene.points.map((point) => point.volume);
383
+ const maxVolume = Math.max(...volumes, 1);
384
+ const spacing = width / Math.max(scene.points.length, 1);
385
+ const barWidth = clamp(spacing * 0.72, 1, Math.max(spacing, 1));
386
+
387
+ for (let index = 0; index < scene.points.length; index++) {
388
+ const point = scene.points[index]!;
389
+ const heightRatio = point.volume / maxVolume;
390
+ if (heightRatio <= 0) continue;
391
+ const x = projectX(index, scene.points.length, 0, width - 1);
392
+ const barTop = lerp(bottom, top, heightRatio);
393
+ const isUp = scene.mode === "candles" || scene.mode === "ohlc"
394
+ ? point.close >= point.open
395
+ : index === 0 || point.close >= scene.points[index - 1]!.close;
396
+ const color = parseHex(isUp ? scene.colors.volumeUp : scene.colors.volumeDown, 0.6);
397
+ fillRect(data, width, height, x - barWidth / 2, barTop, x + barWidth / 2, bottom, color, 0.8);
398
+ }
399
+ }
400
+
401
+ function drawCrosshairOverlay(
402
+ data: Uint8Array,
403
+ width: number,
404
+ height: number,
405
+ overlay: NativeCrosshairOverlay,
406
+ plotTop: number,
407
+ plotBottom: number,
408
+ ) {
409
+ if (overlay.pixelX === null || overlay.pixelY === null) return;
410
+
411
+ const x = clamp(overlay.pixelX, 0, Math.max(width - 1, 0));
412
+ const y = clamp(overlay.pixelY, plotTop, plotBottom);
413
+
414
+ const lineColor = parseHex(overlay.colors.crosshairColor, 0.78);
415
+ const focusColor = parseHex(overlay.colors.crosshairColor, 0.32);
416
+ drawLine(data, width, height, x, 0, x, height - 1, lineColor, 1.05);
417
+ drawLine(data, width, height, 0, y, width - 1, y, lineColor, 1.05);
418
+ drawCircle(data, width, height, x, y, 2.1, focusColor);
419
+ }
420
+
421
+ function getOverlayPlotBottom(overlay: NativeCrosshairOverlay, pixelHeight: number): number {
422
+ if (pixelHeight <= 1 || overlay.height <= 0) return Math.max(pixelHeight - 1, 0);
423
+ const plotHeight = Math.max(Math.round((overlay.chartRows / Math.max(overlay.height, 1)) * pixelHeight), 1);
424
+ return Math.max(Math.min(plotHeight - 1, pixelHeight - 1), 0);
425
+ }
426
+
427
+ export function renderNativeChartBase(scene: ChartScene, pixelWidth: number, pixelHeight: number): NativeChartBitmap {
428
+ const pixels = new Uint8Array(pixelWidth * pixelHeight * 4);
429
+ if (scene.points.length === 0 || pixelWidth <= 0 || pixelHeight <= 0) {
430
+ return { width: Math.max(pixelWidth, 1), height: Math.max(pixelHeight, 1), pixels };
431
+ }
432
+
433
+ fillBackground(pixels, pixelWidth, pixelHeight, parseHex(scene.colors.bgColor, 1));
434
+ const layout = getChartPixelLayout(scene, pixelWidth, pixelHeight);
435
+ drawPriceGrid(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom);
436
+
437
+ switch (scene.mode) {
438
+ case "area":
439
+ case "line":
440
+ drawLineSeries(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom);
441
+ break;
442
+ case "candles":
443
+ drawCandles(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom, "candles");
444
+ break;
445
+ case "ohlc":
446
+ drawCandles(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom, "ohlc");
447
+ break;
448
+ }
449
+
450
+ drawVolume(pixels, pixelWidth, pixelHeight, scene, layout.volumeTop, layout.volumeBottom);
451
+
452
+ return { width: pixelWidth, height: pixelHeight, pixels };
453
+ }
454
+
455
+ interface ComparisonPathSample {
456
+ x: number;
457
+ y: number;
458
+ }
459
+
460
+ function buildComparisonPath(
461
+ scene: ComparisonChartScene,
462
+ series: ComparisonChartScene["series"][number],
463
+ width: number,
464
+ top: number,
465
+ bottom: number,
466
+ ) {
467
+ const path: Array<ComparisonPathSample | null> = [];
468
+ const yByColumn = new Float32Array(width).fill(Number.POSITIVE_INFINITY);
469
+
470
+ for (let index = 0; index < series.points.length; index += 1) {
471
+ const point = series.points[index]!;
472
+ if (point.value === null) {
473
+ path.push(null);
474
+ continue;
475
+ }
476
+
477
+ const x = projectX(index, Math.max(scene.dates.length, 1), 0, width - 1);
478
+ const y = projectY(point.value, scene.min, scene.max, top, bottom);
479
+ path.push({ x, y });
480
+
481
+ const roundedX = clamp(Math.round(x), 0, Math.max(width - 1, 0));
482
+ yByColumn[roundedX] = Math.min(yByColumn[roundedX]!, y);
483
+
484
+ const previous = path[index - 1] ?? null;
485
+ if (!previous) continue;
486
+
487
+ const start = Math.max(Math.floor(Math.min(previous.x, x)), 0);
488
+ const end = Math.min(Math.ceil(Math.max(previous.x, x)), width - 1);
489
+ for (let px = start; px <= end; px += 1) {
490
+ const t = x === previous.x ? 0 : (px - previous.x) / (x - previous.x);
491
+ const interpolatedY = lerp(previous.y, y, clamp(t, 0, 1));
492
+ yByColumn[px] = Math.min(yByColumn[px]!, interpolatedY);
493
+ }
494
+ }
495
+
496
+ return { path, yByColumn };
497
+ }
498
+
499
+ function drawComparisonLinePath(
500
+ data: Uint8Array,
501
+ width: number,
502
+ height: number,
503
+ path: Array<ComparisonPathSample | null>,
504
+ color: RgbaColor,
505
+ glow: RgbaColor,
506
+ ) {
507
+ let previous: ComparisonPathSample | null = null;
508
+
509
+ for (const sample of path) {
510
+ if (!sample) {
511
+ previous = null;
512
+ continue;
513
+ }
514
+
515
+ if (previous) {
516
+ drawLine(data, width, height, previous.x, previous.y, sample.x, sample.y, glow, 3.2);
517
+ drawLine(data, width, height, previous.x, previous.y, sample.x, sample.y, color, 1.4);
518
+ } else {
519
+ drawLine(data, width, height, sample.x, sample.y, sample.x, sample.y, color, 1.4);
520
+ }
521
+
522
+ previous = sample;
523
+ }
524
+ }
525
+
526
+ export function renderNativeComparisonChartBase(
527
+ scene: ComparisonChartScene,
528
+ pixelWidth: number,
529
+ pixelHeight: number,
530
+ ): NativeChartBitmap {
531
+ const pixels = new Uint8Array(pixelWidth * pixelHeight * 4);
532
+ if (scene.series.length === 0 || scene.dates.length === 0 || pixelWidth <= 0 || pixelHeight <= 0) {
533
+ return { width: Math.max(pixelWidth, 1), height: Math.max(pixelHeight, 1), pixels };
534
+ }
535
+
536
+ fillBackground(pixels, pixelWidth, pixelHeight, parseHex(scene.colors.bgColor, 1));
537
+ const layout = getComparisonPixelLayout(pixelWidth, pixelHeight);
538
+ drawPriceGrid(
539
+ pixels,
540
+ pixelWidth,
541
+ pixelHeight,
542
+ {
543
+ min: scene.min,
544
+ max: scene.max,
545
+ colors: { gridColor: scene.colors.gridColor },
546
+ } as ChartScene,
547
+ layout.plotTop,
548
+ layout.plotBottom,
549
+ );
550
+
551
+ const selectedSeries = scene.selectedSeries;
552
+ const orderedSeries = [
553
+ ...scene.series.filter((entry) => entry.symbol !== selectedSeries?.symbol),
554
+ ...(selectedSeries ? [selectedSeries] : []),
555
+ ];
556
+
557
+ const paths = orderedSeries.map((series) => ({
558
+ series,
559
+ ...buildComparisonPath(scene, series, pixelWidth, layout.plotTop, layout.plotBottom),
560
+ }));
561
+
562
+ if (scene.mode === "area") {
563
+ for (const entry of paths) {
564
+ drawAreaFill(
565
+ pixels,
566
+ pixelWidth,
567
+ pixelHeight,
568
+ entry.yByColumn,
569
+ layout.plotBottom,
570
+ parseHex(entry.series.fillColor, 0.7),
571
+ );
572
+ }
573
+ }
574
+
575
+ for (const entry of paths) {
576
+ drawComparisonLinePath(
577
+ pixels,
578
+ pixelWidth,
579
+ pixelHeight,
580
+ entry.path,
581
+ parseHex(entry.series.color, 0.96),
582
+ parseHex(entry.series.color, scene.mode === "area" ? 0.18 : 0.22),
583
+ );
584
+ }
585
+
586
+ return { width: pixelWidth, height: pixelHeight, pixels };
587
+ }
588
+
589
+ export function renderNativeCrosshairOverlay(
590
+ overlay: NativeCrosshairOverlay,
591
+ pixelWidth: number,
592
+ pixelHeight: number,
593
+ ): NativeChartBitmap {
594
+ const pixels = new Uint8Array(Math.max(pixelWidth, 1) * Math.max(pixelHeight, 1) * 4);
595
+ if (pixelWidth <= 0 || pixelHeight <= 0) {
596
+ return { width: Math.max(pixelWidth, 1), height: Math.max(pixelHeight, 1), pixels };
597
+ }
598
+
599
+ const plotBottom = getOverlayPlotBottom(overlay, pixelHeight);
600
+ drawCrosshairOverlay(pixels, pixelWidth, pixelHeight, overlay, 0, plotBottom);
601
+ return { width: pixelWidth, height: pixelHeight, pixels };
602
+ }
603
+
604
+ export function intersectCellRects(a: CellRect, b: CellRect): CellRect | null {
605
+ const x = Math.max(a.x, b.x);
606
+ const y = Math.max(a.y, b.y);
607
+ const right = Math.min(a.x + a.width, b.x + b.width);
608
+ const bottom = Math.min(a.y + a.height, b.y + b.height);
609
+ if (right <= x || bottom <= y) return null;
610
+ return {
611
+ x,
612
+ y,
613
+ width: right - x,
614
+ height: bottom - y,
615
+ };
616
+ }
617
+
618
+ export function subtractCellRect(rect: CellRect, cut: CellRect): CellRect[] {
619
+ const intersection = intersectCellRects(rect, cut);
620
+ if (!intersection) return [rect];
621
+
622
+ const fragments: CellRect[] = [];
623
+ const rectRight = rect.x + rect.width;
624
+ const rectBottom = rect.y + rect.height;
625
+ const cutRight = intersection.x + intersection.width;
626
+ const cutBottom = intersection.y + intersection.height;
627
+
628
+ if (intersection.y > rect.y) {
629
+ fragments.push({
630
+ x: rect.x,
631
+ y: rect.y,
632
+ width: rect.width,
633
+ height: intersection.y - rect.y,
634
+ });
635
+ }
636
+
637
+ if (cutBottom < rectBottom) {
638
+ fragments.push({
639
+ x: rect.x,
640
+ y: cutBottom,
641
+ width: rect.width,
642
+ height: rectBottom - cutBottom,
643
+ });
644
+ }
645
+
646
+ if (intersection.x > rect.x) {
647
+ fragments.push({
648
+ x: rect.x,
649
+ y: intersection.y,
650
+ width: intersection.x - rect.x,
651
+ height: intersection.height,
652
+ });
653
+ }
654
+
655
+ if (cutRight < rectRight) {
656
+ fragments.push({
657
+ x: cutRight,
658
+ y: intersection.y,
659
+ width: rectRight - cutRight,
660
+ height: intersection.height,
661
+ });
662
+ }
663
+
664
+ return fragments.filter((fragment) => fragment.width > 0 && fragment.height > 0);
665
+ }
666
+
667
+ export function subtractCellRects(rects: CellRect[], cut: CellRect): CellRect[] {
668
+ return rects.flatMap((rect) => subtractCellRect(rect, cut));
669
+ }
670
+
671
+ export function excludeCellRects(rect: CellRect, cuts: CellRect[]): CellRect[] {
672
+ let fragments = [rect];
673
+ for (const cut of cuts) {
674
+ fragments = subtractCellRects(fragments, cut);
675
+ if (fragments.length === 0) break;
676
+ }
677
+ return fragments;
678
+ }
679
+
680
+ export function computeBitmapSize(rect: CellRect, resolution: PixelResolution, terminalWidth: number, terminalHeight: number) {
681
+ const cellWidth = resolution.width / Math.max(terminalWidth, 1);
682
+ const cellHeight = resolution.height / Math.max(terminalHeight, 1);
683
+ return {
684
+ cellWidth,
685
+ cellHeight,
686
+ pixelWidth: Math.max(1, Math.round(rect.width * cellWidth)),
687
+ pixelHeight: Math.max(1, Math.round(rect.height * cellHeight)),
688
+ };
689
+ }
690
+
691
+ export function computeNativePlacement(
692
+ rect: CellRect,
693
+ visibleRect: CellRect,
694
+ bitmap: NativeChartBitmap,
695
+ resolution: PixelResolution,
696
+ terminalWidth: number,
697
+ terminalHeight: number,
698
+ ): NativePlacement | null {
699
+ const cellWidth = resolution.width / Math.max(terminalWidth, 1);
700
+ const cellHeight = resolution.height / Math.max(terminalHeight, 1);
701
+ const clipped = intersectCellRects(rect, visibleRect);
702
+ if (!clipped) return null;
703
+
704
+ const cropX = Math.max(0, Math.round((clipped.x - rect.x) * cellWidth));
705
+ const cropY = Math.max(0, Math.round((clipped.y - rect.y) * cellHeight));
706
+ const cropWidth = Math.max(1, Math.min(bitmap.width - cropX, Math.round(clipped.width * cellWidth)));
707
+ const cropHeight = Math.max(1, Math.min(bitmap.height - cropY, Math.round(clipped.height * cellHeight)));
708
+
709
+ return {
710
+ column: clipped.x + 1,
711
+ row: clipped.y + 1,
712
+ cols: clipped.width,
713
+ rows: clipped.height,
714
+ cropX,
715
+ cropY,
716
+ cropWidth,
717
+ cropHeight,
718
+ };
719
+ }
720
+
721
+ export function computeNativePlacements(
722
+ rect: CellRect,
723
+ visibleRects: CellRect[],
724
+ bitmap: NativeChartBitmap,
725
+ resolution: PixelResolution,
726
+ terminalWidth: number,
727
+ terminalHeight: number,
728
+ ): NativePlacement[] {
729
+ return visibleRects
730
+ .map((visibleRect) => computeNativePlacement(rect, visibleRect, bitmap, resolution, terminalWidth, terminalHeight))
731
+ .filter((placement): placement is NativePlacement => placement !== null);
732
+ }