@daniel156161/prism 0.2.78 → 0.2.82

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 (204) hide show
  1. package/dist/pi/pi-env.d.ts +1 -1
  2. package/dist/pi/pi-env.js +1 -1
  3. package/dist/pi/pi-patch-interactive.js +8 -5
  4. package/dist/pi/pi-patch-interactive.js.map +1 -1
  5. package/dist/prism-extensions/integrations/ai-memory-errors.d.ts +29 -0
  6. package/dist/prism-extensions/integrations/ai-memory-errors.js +106 -0
  7. package/dist/prism-extensions/integrations/ai-memory-errors.js.map +1 -0
  8. package/dist/prism-extensions/integrations/ai-memory-http.d.ts +28 -0
  9. package/dist/prism-extensions/integrations/ai-memory-http.js +92 -0
  10. package/dist/prism-extensions/integrations/ai-memory-http.js.map +1 -0
  11. package/dist/prism-extensions/integrations/ai-memory-system.d.ts +5 -0
  12. package/dist/prism-extensions/integrations/ai-memory-system.js +205 -123
  13. package/dist/prism-extensions/integrations/ai-memory-system.js.map +1 -1
  14. package/dist/prism-extensions/integrations/ai-memory-write-preview.d.ts +36 -0
  15. package/dist/prism-extensions/integrations/ai-memory-write-preview.js +67 -0
  16. package/dist/prism-extensions/integrations/ai-memory-write-preview.js.map +1 -0
  17. package/dist/prism-extensions/integrations/research-tools.js +3 -3
  18. package/dist/prism-extensions/integrations/research-tools.js.map +1 -1
  19. package/dist/prism-extensions/integrations/youtube.d.ts +2 -2
  20. package/dist/prism-extensions/integrations/youtube.js +6 -7
  21. package/dist/prism-extensions/integrations/youtube.js.map +1 -1
  22. package/dist/prism-extensions/ui/collapsed-text-rendering.d.ts +4 -2
  23. package/dist/prism-extensions/ui/collapsed-text-rendering.js +12 -7
  24. package/dist/prism-extensions/ui/collapsed-text-rendering.js.map +1 -1
  25. package/node_modules/@earendil-works/pi-agent-core/README.md +4 -4
  26. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  27. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +5 -1
  28. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  29. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  30. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +3 -0
  31. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  32. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +6 -0
  33. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  34. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  35. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  36. package/node_modules/@earendil-works/pi-ai/README.md +8 -1
  37. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  38. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +1 -0
  39. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
  40. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +2 -0
  41. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  42. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +2 -0
  43. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  44. package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts.map +1 -1
  45. package/node_modules/@earendil-works/pi-ai/dist/providers/all.js +2 -0
  46. package/node_modules/@earendil-works/pi-ai/dist/providers/all.js.map +1 -1
  47. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  48. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  49. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  50. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  51. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-individual.json +1 -0
  52. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  53. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.d.ts +3 -0
  54. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.d.ts.map +1 -0
  55. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.js +15 -0
  56. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.js.map +1 -0
  57. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.models.d.ts +4 -0
  58. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.models.d.ts.map +1 -0
  59. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.models.js +6 -0
  60. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-individual.models.js.map +1 -0
  61. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +1 -1
  62. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  63. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  64. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  65. package/node_modules/@earendil-works/pi-client/CHANGELOG.md +1 -1
  66. package/node_modules/@earendil-works/pi-client/package.json +2 -2
  67. package/node_modules/@earendil-works/pi-coding-agent/CHANGELOG.md +31 -2
  68. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  69. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +3 -3
  70. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js.map +1 -1
  71. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-check.d.ts +19 -0
  72. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-check.d.ts.map +1 -0
  73. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-check.js +50 -0
  74. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-check.js.map +1 -0
  75. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-command.d.ts +24 -0
  76. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-command.d.ts.map +1 -0
  77. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-command.js +102 -0
  78. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/auth-command.js.map +1 -0
  79. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/credential-print.d.ts +4 -14
  80. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/credential-print.d.ts.map +1 -1
  81. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/credential-print.js +38 -79
  82. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/credential-print.js.map +1 -1
  83. package/node_modules/@earendil-works/pi-coding-agent/dist/cli.js +1 -1
  84. package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +11 -6
  85. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js +1 -1
  86. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +0 -5
  87. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
  88. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  89. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js +79 -2
  90. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  91. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js +1 -3
  92. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts +5 -0
  93. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  94. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  95. package/node_modules/@earendil-works/pi-coding-agent/dist/core/keybindings.d.ts +8 -0
  96. package/node_modules/@earendil-works/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  97. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  98. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +1 -0
  99. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  100. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.d.ts +2 -0
  101. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.d.ts.map +1 -1
  102. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.js +3 -1
  103. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.js.map +1 -1
  104. package/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.js +1 -1
  105. package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +1 -1
  106. package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +37 -39
  107. package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js +16 -20
  108. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.d.ts +4 -0
  109. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  110. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js +6 -4
  111. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  112. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.d.ts +4 -0
  113. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  114. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +11 -7
  115. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  116. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.d.ts +4 -0
  117. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.d.ts.map +1 -1
  118. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.js +5 -1
  119. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.js.map +1 -1
  120. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.d.ts +4 -0
  121. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.d.ts.map +1 -1
  122. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.js +5 -1
  123. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.js.map +1 -1
  124. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.d.ts +4 -0
  125. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.d.ts.map +1 -1
  126. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +5 -1
  127. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js.map +1 -1
  128. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.d.ts +4 -0
  129. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
  130. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +6 -2
  131. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js.map +1 -1
  132. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.d.ts +4 -0
  133. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  134. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +6 -2
  135. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  136. package/node_modules/@earendil-works/pi-coding-agent/dist/main.d.ts.map +1 -1
  137. package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +57 -21
  138. package/node_modules/@earendil-works/pi-coding-agent/dist/main.js.map +1 -1
  139. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/session-selector.js +0 -4
  140. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
  141. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  142. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +48 -233
  143. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  144. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  145. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js +11 -3
  146. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  147. package/node_modules/@earendil-works/pi-coding-agent/dist/server/create-harness.d.ts +26 -0
  148. package/node_modules/@earendil-works/pi-coding-agent/dist/server/create-harness.d.ts.map +1 -0
  149. package/node_modules/@earendil-works/pi-coding-agent/dist/server/create-harness.js +104 -0
  150. package/node_modules/@earendil-works/pi-coding-agent/dist/server/create-harness.js.map +1 -0
  151. package/node_modules/@earendil-works/pi-coding-agent/docs/extensions.md +3 -2
  152. package/node_modules/@earendil-works/pi-coding-agent/docs/keybindings.md +9 -5
  153. package/node_modules/@earendil-works/pi-coding-agent/docs/providers.md +8 -1
  154. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  155. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-anthropic/package.json +1 -1
  156. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  157. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/package-lock.json +2 -2
  158. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/gondolin/package.json +1 -1
  159. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/sandbox/package-lock.json +2 -2
  160. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/sandbox/package.json +1 -1
  161. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/with-deps/package-lock.json +2 -2
  162. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/with-deps/package.json +1 -1
  163. package/node_modules/@earendil-works/pi-coding-agent/package.json +7 -7
  164. package/node_modules/@earendil-works/pi-protocol/package.json +1 -1
  165. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  166. package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js +1 -1
  167. package/node_modules/@earendil-works/pi-tui/dist/keybindings.d.ts +10 -0
  168. package/node_modules/@earendil-works/pi-tui/dist/keybindings.d.ts.map +1 -1
  169. package/node_modules/@earendil-works/pi-tui/dist/keybindings.js +8 -0
  170. package/node_modules/@earendil-works/pi-tui/dist/keybindings.js.map +1 -1
  171. package/node_modules/@earendil-works/pi-tui/dist/latex.d.ts.map +1 -1
  172. package/node_modules/@earendil-works/pi-tui/dist/latex.js +188 -48
  173. package/node_modules/@earendil-works/pi-tui/dist/latex.js.map +1 -1
  174. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.d.ts +12 -0
  175. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.d.ts.map +1 -1
  176. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js +146 -12
  177. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js.map +1 -1
  178. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  179. package/node_modules/@types/node/README.md +1 -1
  180. package/node_modules/@types/node/child_process.d.ts +0 -1
  181. package/node_modules/@types/node/crypto.d.ts +6 -6
  182. package/node_modules/@types/node/dns/promises.d.ts +5 -5
  183. package/node_modules/@types/node/dns.d.ts +5 -5
  184. package/node_modules/@types/node/fs.d.ts +7 -0
  185. package/node_modules/@types/node/http.d.ts +30 -0
  186. package/node_modules/@types/node/http2.d.ts +4 -4
  187. package/node_modules/@types/node/os.d.ts +30 -30
  188. package/node_modules/@types/node/package.json +2 -2
  189. package/node_modules/@types/node/perf_hooks.d.ts +2 -1
  190. package/node_modules/@types/node/process.d.ts +42 -43
  191. package/node_modules/@types/node/quic.d.ts +1049 -84
  192. package/node_modules/@types/node/sqlite.d.ts +14 -1
  193. package/node_modules/@types/node/stream/iter.d.ts +3 -2
  194. package/node_modules/@types/node/test.d.ts +61 -0
  195. package/node_modules/@types/node/v8.d.ts +20 -23
  196. package/package.json +6 -6
  197. package/src/prism-extensions/integrations/ai-memory-errors.ts +112 -0
  198. package/src/prism-extensions/integrations/ai-memory-http.ts +114 -0
  199. package/src/prism-extensions/integrations/ai-memory-system.ts +221 -132
  200. package/src/prism-extensions/integrations/ai-memory-write-preview.ts +83 -0
  201. package/src/prism-extensions/integrations/research-tools.ts +3 -3
  202. package/src/prism-extensions/integrations/youtube.ts +6 -6
  203. package/src/prism-extensions/ui/collapsed-text-rendering.ts +14 -8
  204. package/node_modules/@earendil-works/pi-coding-agent/dist/core/prism-session-db.js +0 -128
@@ -1 +1 @@
1
- {"version":3,"file":"latex.d.ts","sourceRoot":"","sources":["../src/latex.ts"],"names":[],"mappings":"AA6qCA,MAAM,WAAW,kBAAkB;IAClC,wFAAwF;IACxF,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,GAAG,SAAS,CAkBhG","sourcesContent":["import { visibleWidth } from \"./utils.ts\";\n\nconst SYMBOLS: Readonly<Record<string, string>> = {\n\talpha: \"α\",\n\tbeta: \"β\",\n\tgamma: \"γ\",\n\tdelta: \"δ\",\n\tepsilon: \"ϵ\",\n\tvarepsilon: \"ε\",\n\tzeta: \"ζ\",\n\teta: \"η\",\n\ttheta: \"θ\",\n\tvartheta: \"ϑ\",\n\tiota: \"ι\",\n\tkappa: \"κ\",\n\tvarkappa: \"ϰ\",\n\tlambda: \"λ\",\n\tmu: \"μ\",\n\tnu: \"ν\",\n\txi: \"ξ\",\n\tpi: \"π\",\n\tvarpi: \"ϖ\",\n\trho: \"ρ\",\n\tvarrho: \"ϱ\",\n\tsigma: \"σ\",\n\tvarsigma: \"ς\",\n\ttau: \"τ\",\n\tupsilon: \"υ\",\n\tphi: \"ϕ\",\n\tvarphi: \"φ\",\n\tchi: \"χ\",\n\tpsi: \"ψ\",\n\tomega: \"ω\",\n\tGamma: \"Γ\",\n\tDelta: \"Δ\",\n\tTheta: \"Θ\",\n\tLambda: \"Λ\",\n\tXi: \"Ξ\",\n\tPi: \"Π\",\n\tSigma: \"Σ\",\n\tUpsilon: \"Υ\",\n\tPhi: \"Φ\",\n\tPsi: \"Ψ\",\n\tOmega: \"Ω\",\n\tpm: \"±\",\n\tmp: \"∓\",\n\ttimes: \"×\",\n\tdiv: \"÷\",\n\tcdot: \"·\",\n\tast: \"∗\",\n\tstar: \"⋆\",\n\tcirc: \"∘\",\n\tbullet: \"•\",\n\toplus: \"⊕\",\n\tominus: \"⊖\",\n\totimes: \"⊗\",\n\toslash: \"⊘\",\n\todot: \"⊙\",\n\tbigcirc: \"○\",\n\tdagger: \"†\",\n\tddagger: \"‡\",\n\tamalg: \"⨿\",\n\tuplus: \"⊎\",\n\tsqcap: \"⊓\",\n\tsqcup: \"⊔\",\n\ttriangleleft: \"◁\",\n\ttriangleright: \"▷\",\n\twr: \"≀\",\n\tcap: \"∩\",\n\tcup: \"∪\",\n\tbigcap: \"⋂\",\n\tbigcup: \"⋃\",\n\tbigwedge: \"⋀\",\n\tbigvee: \"⋁\",\n\tbigsqcup: \"⨆\",\n\tbiguplus: \"⨄\",\n\tbigoplus: \"⨁\",\n\tbigotimes: \"⨂\",\n\tbigodot: \"⨀\",\n\tsetminus: \"∖\",\n\tin: \"∈\",\n\tnotin: \"∉\",\n\tni: \"∋\",\n\tsubset: \"⊂\",\n\tsupset: \"⊃\",\n\tsubseteq: \"⊆\",\n\tsupseteq: \"⊇\",\n\tsqsubset: \"⊏\",\n\tsqsupset: \"⊐\",\n\tsqsubseteq: \"⊑\",\n\tsqsupseteq: \"⊒\",\n\tprec: \"≺\",\n\tpreceq: \"≼\",\n\tsucc: \"≻\",\n\tsucceq: \"≽\",\n\tll: \"≪\",\n\tgg: \"≫\",\n\tle: \"≤\",\n\tleq: \"≤\",\n\tleqslant: \"≤\",\n\tge: \"≥\",\n\tgeq: \"≥\",\n\tgeqslant: \"≥\",\n\tne: \"≠\",\n\tneq: \"≠\",\n\tequiv: \"≡\",\n\tapprox: \"≈\",\n\tsim: \"∼\",\n\tsimeq: \"≃\",\n\tcong: \"≅\",\n\tasymp: \"≍\",\n\tdoteq: \"≐\",\n\tpropto: \"∝\",\n\tparallel: \"∥\",\n\tperp: \"⊥\",\n\tmid: \"∣\",\n\tvdash: \"⊢\",\n\tdashv: \"⊣\",\n\tmodels: \"⊨\",\n\tVdash: \"⊩\",\n\tVvdash: \"⊪\",\n\tnvdash: \"⊬\",\n\tnvDash: \"⊭\",\n\tforall: \"∀\",\n\texists: \"∃\",\n\tnexists: \"∄\",\n\tneg: \"¬\",\n\tland: \"∧\",\n\twedge: \"∧\",\n\tlor: \"∨\",\n\tvee: \"∨\",\n\tto: \"→\",\n\trightarrow: \"→\",\n\tlongrightarrow: \"→\",\n\tleftarrow: \"←\",\n\tlongleftarrow: \"←\",\n\tgets: \"←\",\n\tleftrightarrow: \"↔\",\n\tlongleftrightarrow: \"↔\",\n\thookleftarrow: \"↩\",\n\thookrightarrow: \"↪\",\n\ttwoheadleftarrow: \"↞\",\n\ttwoheadrightarrow: \"↠\",\n\tleftharpoonup: \"↼\",\n\tleftharpoondown: \"↽\",\n\trightharpoonup: \"⇀\",\n\trightharpoondown: \"⇁\",\n\trightleftharpoons: \"⇌\",\n\tleftrightharpoons: \"⇋\",\n\tnearrow: \"↗\",\n\tsearrow: \"↘\",\n\tswarrow: \"↙\",\n\tnwarrow: \"↖\",\n\trightsquigarrow: \"⇝\",\n\tleadsto: \"⇝\",\n\tRightarrow: \"⇒\",\n\tLongrightarrow: \"⇒\",\n\tLeftarrow: \"⇐\",\n\tLongleftarrow: \"⇐\",\n\tLeftrightarrow: \"⇔\",\n\tLongleftrightarrow: \"⇔\",\n\timplies: \"⇒\",\n\tiff: \"⇔\",\n\tmapsto: \"↦\",\n\tlongmapsto: \"↦\",\n\tuparrow: \"↑\",\n\tdownarrow: \"↓\",\n\tpartial: \"∂\",\n\tnabla: \"∇\",\n\tint: \"∫\",\n\tiint: \"∬\",\n\tiiint: \"∭\",\n\toint: \"∮\",\n\tsum: \"∑\",\n\tprod: \"∏\",\n\tcoprod: \"∐\",\n\tinfty: \"∞\",\n\temptyset: \"∅\",\n\tvarnothing: \"∅\",\n\tangle: \"∠\",\n\ttherefore: \"∴\",\n\tbecause: \"∵\",\n\taleph: \"ℵ\",\n\tbeth: \"ℶ\",\n\tgimel: \"ℷ\",\n\tdaleth: \"ℸ\",\n\ttop: \"⊤\",\n\tbot: \"⊥\",\n\ttriangle: \"△\",\n\tsquare: \"□\",\n\tlozenge: \"◊\",\n\tcheckmark: \"✓\",\n\tcomplement: \"∁\",\n\twp: \"℘\",\n\tprime: \"′\",\n\tldots: \"…\",\n\tdots: \"…\",\n\tcdots: \"⋯\",\n\tvdots: \"⋮\",\n\tddots: \"⋱\",\n\tell: \"ℓ\",\n\thbar: \"ℏ\",\n\tIm: \"ℑ\",\n\tRe: \"ℜ\",\n\tlangle: \"⟨\",\n\trangle: \"⟩\",\n\tvert: \"|\",\n\tlvert: \"|\",\n\trvert: \"|\",\n\tVert: \"‖\",\n\tlVert: \"‖\",\n\trVert: \"‖\",\n\tlbrace: \"{\",\n\trbrace: \"}\",\n\tbackslash: \"\\\\\",\n\tlfloor: \"⌊\",\n\trfloor: \"⌋\",\n\tlceil: \"⌈\",\n\trceil: \"⌉\",\n\tcolon: \":\",\n};\n\nconst NAMED_OPERATORS = new Set([\n\t\"arccos\",\n\t\"arcsin\",\n\t\"arctan\",\n\t\"arg\",\n\t\"cos\",\n\t\"cosh\",\n\t\"cot\",\n\t\"coth\",\n\t\"csc\",\n\t\"deg\",\n\t\"det\",\n\t\"dim\",\n\t\"exp\",\n\t\"gcd\",\n\t\"hom\",\n\t\"inf\",\n\t\"ker\",\n\t\"lg\",\n\t\"lim\",\n\t\"liminf\",\n\t\"limsup\",\n\t\"ln\",\n\t\"log\",\n\t\"max\",\n\t\"min\",\n\t\"Pr\",\n\t\"sec\",\n\t\"sin\",\n\t\"sinh\",\n\t\"sup\",\n\t\"tan\",\n\t\"tanh\",\n]);\n\nconst LIMIT_OPERATORS = new Set([\n\t\"argmax\",\n\t\"argmin\",\n\t\"inf\",\n\t\"injlim\",\n\t\"lim\",\n\t\"liminf\",\n\t\"limsup\",\n\t\"max\",\n\t\"min\",\n\t\"projlim\",\n\t\"sup\",\n]);\n\nconst DISPLAY_LIMIT_SYMBOLS = new Set([\n\t\"bigcap\",\n\t\"bigcup\",\n\t\"bigodot\",\n\t\"bigoplus\",\n\t\"bigotimes\",\n\t\"bigsqcup\",\n\t\"biguplus\",\n\t\"bigvee\",\n\t\"bigwedge\",\n\t\"coprod\",\n\t\"int\",\n\t\"iint\",\n\t\"iiint\",\n\t\"oint\",\n\t\"prod\",\n\t\"sum\",\n]);\n\nconst NEGATED_SYMBOLS: Readonly<Record<string, string>> = {\n\t\"<\": \"≮\",\n\t\">\": \"≯\",\n\t\"=\": \"≠\",\n\t\"∈\": \"∉\",\n\t\"∋\": \"∌\",\n\t\"∣\": \"∤\",\n\t\"∥\": \"∦\",\n\t\"∼\": \"≁\",\n\t\"≃\": \"≄\",\n\t\"≅\": \"≇\",\n\t\"≈\": \"≉\",\n\t\"≡\": \"≢\",\n\t\"≤\": \"≰\",\n\t\"≥\": \"≱\",\n\t\"≺\": \"⊀\",\n\t\"≻\": \"⊁\",\n\t\"⊂\": \"⊄\",\n\t\"⊃\": \"⊅\",\n\t\"⊆\": \"⊈\",\n\t\"⊇\": \"⊉\",\n\t\"⊢\": \"⊬\",\n\t\"⊨\": \"⊭\",\n\t\"↔\": \"↮\",\n\t\"←\": \"↚\",\n\t\"→\": \"↛\",\n\t\"⇒\": \"⇏\",\n\t\"⇐\": \"⇍\",\n\t\"⇔\": \"⇎\",\n\t\"≼\": \"⋠\",\n\t\"≽\": \"⋡\",\n};\n\nconst BLACKBOARD: Readonly<Record<string, string>> = {\n\tC: \"ℂ\",\n\tH: \"ℍ\",\n\tN: \"ℕ\",\n\tP: \"ℙ\",\n\tQ: \"ℚ\",\n\tR: \"ℝ\",\n\tZ: \"ℤ\",\n};\n\nconst SUPERSCRIPTS: Readonly<Record<string, string>> = {\n\t\"0\": \"⁰\",\n\t\"1\": \"¹\",\n\t\"2\": \"²\",\n\t\"3\": \"³\",\n\t\"4\": \"⁴\",\n\t\"5\": \"⁵\",\n\t\"6\": \"⁶\",\n\t\"7\": \"⁷\",\n\t\"8\": \"⁸\",\n\t\"9\": \"⁹\",\n\t\"+\": \"⁺\",\n\t\"-\": \"⁻\",\n\t\"=\": \"⁼\",\n\t\"(\": \"⁽\",\n\t\")\": \"⁾\",\n\ta: \"ᵃ\",\n\tb: \"ᵇ\",\n\tc: \"ᶜ\",\n\td: \"ᵈ\",\n\te: \"ᵉ\",\n\tf: \"ᶠ\",\n\tg: \"ᵍ\",\n\th: \"ʰ\",\n\ti: \"ⁱ\",\n\tj: \"ʲ\",\n\tk: \"ᵏ\",\n\tl: \"ˡ\",\n\tm: \"ᵐ\",\n\tn: \"ⁿ\",\n\to: \"ᵒ\",\n\tp: \"ᵖ\",\n\tr: \"ʳ\",\n\ts: \"ˢ\",\n\tt: \"ᵗ\",\n\tu: \"ᵘ\",\n\tv: \"ᵛ\",\n\tw: \"ʷ\",\n\tx: \"ˣ\",\n\ty: \"ʸ\",\n\tz: \"ᶻ\",\n};\n\nconst SUBSCRIPTS: Readonly<Record<string, string>> = {\n\t\"0\": \"₀\",\n\t\"1\": \"₁\",\n\t\"2\": \"₂\",\n\t\"3\": \"₃\",\n\t\"4\": \"₄\",\n\t\"5\": \"₅\",\n\t\"6\": \"₆\",\n\t\"7\": \"₇\",\n\t\"8\": \"₈\",\n\t\"9\": \"₉\",\n\t\"+\": \"₊\",\n\t\"-\": \"₋\",\n\t\"=\": \"₌\",\n\t\"(\": \"₍\",\n\t\")\": \"₎\",\n\ta: \"ₐ\",\n\te: \"ₑ\",\n\th: \"ₕ\",\n\ti: \"ᵢ\",\n\tj: \"ⱼ\",\n\tk: \"ₖ\",\n\tl: \"ₗ\",\n\tm: \"ₘ\",\n\tn: \"ₙ\",\n\to: \"ₒ\",\n\tp: \"ₚ\",\n\tr: \"ᵣ\",\n\ts: \"ₛ\",\n\tt: \"ₜ\",\n\tu: \"ᵤ\",\n\tv: \"ᵥ\",\n\tx: \"ₓ\",\n};\n\nconst SPACING_COMMANDS = new Set([\n\t\",\",\n\t\":\",\n\t\";\",\n\t\" \",\n\t\">\",\n\t\"enspace\",\n\t\"enskip\",\n\t\"medspace\",\n\t\"quad\",\n\t\"qquad\",\n\t\"thickspace\",\n\t\"thinspace\",\n]);\nconst NEGATIVE_SPACING_COMMANDS = new Set([\"!\", \"negmedspace\", \"negthickspace\", \"negthinspace\"]);\nconst NEGATIVE_SPACE = \"\\u0000\";\nconst IGNORED_COMMANDS = new Set([\n\t\"displaystyle\",\n\t\"limits\",\n\t\"nolimits\",\n\t\"scriptstyle\",\n\t\"scriptscriptstyle\",\n\t\"textstyle\",\n]);\nconst SIZE_COMMANDS = new Set([\n\t\"big\",\n\t\"Big\",\n\t\"bigg\",\n\t\"Bigg\",\n\t\"bigl\",\n\t\"Bigl\",\n\t\"biggl\",\n\t\"Biggl\",\n\t\"bigr\",\n\t\"Bigr\",\n\t\"biggr\",\n\t\"Biggr\",\n]);\nconst PLAIN_WRAPPERS = new Set([\n\t\"emph\",\n\t\"mathcal\",\n\t\"mathbf\",\n\t\"mathfrak\",\n\t\"mathit\",\n\t\"mathrm\",\n\t\"mathnormal\",\n\t\"mathscr\",\n\t\"mathsf\",\n\t\"mathtt\",\n\t\"mathup\",\n\t\"mbox\",\n\t\"overbrace\",\n\t\"pmb\",\n\t\"smash\",\n\t\"substack\",\n\t\"text\",\n\t\"textbf\",\n\t\"textit\",\n\t\"textmd\",\n\t\"textnormal\",\n\t\"textrm\",\n\t\"textsc\",\n\t\"textsf\",\n\t\"textsl\",\n\t\"texttt\",\n\t\"textup\",\n\t\"underbrace\",\n\t\"bm\",\n\t\"boldsymbol\",\n]);\nconst ACCENTS: Readonly<Record<string, string>> = {\n\tacute: \"\\u0301\",\n\tbar: \"\\u0305\",\n\tbreve: \"\\u0306\",\n\tcheck: \"\\u030c\",\n\tddot: \"\\u0308\",\n\tdot: \"\\u0307\",\n\tgrave: \"\\u0300\",\n\that: \"\\u0302\",\n\tmathring: \"\\u030a\",\n\toverleftarrow: \"\\u20d6\",\n\toverleftrightarrow: \"\\u20e1\",\n\toverline: \"\\u0305\",\n\toverrightarrow: \"\\u20d7\",\n\ttilde: \"\\u0303\",\n\tunderline: \"\\u0332\",\n\tvec: \"\\u20d7\",\n\twidehat: \"\\u0302\",\n\twidetilde: \"\\u0303\",\n};\n\nfunction replaceCharacters(value: string, replacements: Readonly<Record<string, string>>): string | undefined {\n\tlet result = \"\";\n\tfor (const character of value) {\n\t\tconst replacement = replacements[character];\n\t\tif (replacement === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\t\tresult += replacement;\n\t}\n\treturn result;\n}\n\nfunction formatScript(value: string, kind: \"sub\" | \"sup\"): string {\n\tvalue = value.trim();\n\tconst replacements = kind === \"sub\" ? SUBSCRIPTS : SUPERSCRIPTS;\n\tconst unicode = replaceCharacters(value, replacements);\n\tif (unicode !== undefined) {\n\t\treturn unicode;\n\t}\n\n\tconst prefix = kind === \"sub\" ? \"_\" : \"^\";\n\tif (Array.from(value).length === 1 || (kind === \"sub\" && /^[A-Za-z]+$/.test(value))) {\n\t\treturn `${prefix}${value}`;\n\t}\n\treturn `${prefix}(${value})`;\n}\n\nfunction formatFraction(numerator: string, denominator: string): string {\n\tnumerator = numerator.trim();\n\tdenominator = denominator.trim();\n\tconst simpleNumerator = /^[\\p{L}\\p{N}.]+$/u.test(numerator);\n\tconst simpleDenominator = /^[\\p{N}.]+$/u.test(denominator) || Array.from(denominator).length === 1;\n\treturn `${simpleNumerator ? numerator : `(${numerator})`}/${simpleDenominator ? denominator : `(${denominator})`}`;\n}\n\nfunction formatRoot(value: string, symbol = \"√\"): string {\n\tvalue = value.trim();\n\treturn /^[\\p{L}\\p{N}.]+$/u.test(value) ? `${symbol}${value}` : `${symbol}(${value})`;\n}\n\nfunction normalizeOutput(value: string): string {\n\treturn value\n\t\t.split(\"\\n\")\n\t\t.map((line) => line.replace(/[ \\t]+/g, \" \").trim())\n\t\t.filter((line, index, lines) => line.length > 0 || (index > 0 && index < lines.length - 1))\n\t\t.join(\"\\n\")\n\t\t.trim();\n}\n\ninterface FractionNode {\n\ttype: \"fraction\";\n\tnumerator: string;\n\tdenominator: string;\n}\n\ninterface OperatorNode {\n\ttype: \"operator\";\n\toperator: string;\n\tlower?: string;\n\tupper?: string;\n}\n\ntype LayoutNode = FractionNode | OperatorNode;\n\ninterface Layout {\n\tlines: string[];\n\twidth: number;\n\tbaseline: number;\n}\n\nconst LAYOUT_MARKER_START = \"\\u{f0000}\";\nconst LAYOUT_MARKER_END = \"\\u{f0001}\";\nconst LAYOUT_MARKER_PATTERN = /\\u{f0000}(\\d+)\\u{f0001}/gu;\nconst PROTECTED_SPACE = \"\\u00a0\";\n\nfunction padLayoutLine(line: string, width: number, centered = false): string {\n\tconst padding = Math.max(0, width - visibleWidth(line));\n\tconst left = centered ? Math.floor(padding / 2) : 0;\n\treturn `${\" \".repeat(left)}${line}${\" \".repeat(padding - left)}`;\n}\n\nfunction joinLayouts(layouts: readonly Layout[]): Layout {\n\tif (layouts.length === 0) {\n\t\treturn { lines: [\"\"], width: 0, baseline: 0 };\n\t}\n\tconst baseline = Math.max(...layouts.map((layout) => layout.baseline));\n\tconst below = Math.max(...layouts.map((layout) => layout.lines.length - layout.baseline - 1));\n\tconst lines: string[] = [];\n\tfor (let row = 0; row <= baseline + below; row++) {\n\t\tlet line = \"\";\n\t\tfor (const layout of layouts) {\n\t\t\tconst sourceRow = row - baseline + layout.baseline;\n\t\t\tline +=\n\t\t\t\tsourceRow >= 0 && sourceRow < layout.lines.length\n\t\t\t\t\t? padLayoutLine(layout.lines[sourceRow] ?? \"\", layout.width)\n\t\t\t\t\t: \" \".repeat(layout.width);\n\t\t}\n\t\tlines.push(line.trimEnd());\n\t}\n\treturn {\n\t\tlines,\n\t\twidth: layouts.reduce((width, layout) => width + layout.width, 0),\n\t\tbaseline,\n\t};\n}\n\nfunction renderLayout(source: string, nodes: readonly LayoutNode[]): Layout {\n\tconst renderedLines: string[] = [];\n\tlet firstBaseline = 0;\n\tfor (const sourceLine of source.split(\"\\n\")) {\n\t\tconst layouts: Layout[] = [];\n\t\tlet position = 0;\n\t\tlet previousWasNode = false;\n\t\tfor (const match of sourceLine.matchAll(LAYOUT_MARKER_PATTERN)) {\n\t\t\tconst index = match.index;\n\t\t\tif (index > position) {\n\t\t\t\tconst sliced = sourceLine.slice(position, index);\n\t\t\t\tconst text = (previousWasNode ? sliced.trimStart() : sliced).trimEnd();\n\t\t\t\tlayouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });\n\t\t\t}\n\t\t\tconst node = nodes[Number(match[1])];\n\t\t\tif (!node) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (node.type === \"fraction\") {\n\t\t\t\tconst numerator = renderLayout(node.numerator, nodes);\n\t\t\t\tconst denominator = renderLayout(node.denominator, nodes);\n\t\t\t\tconst contentWidth = Math.max(numerator.width, denominator.width, 1);\n\t\t\t\tconst width = contentWidth + 2;\n\t\t\t\tlayouts.push({\n\t\t\t\t\tlines: [\n\t\t\t\t\t\t...numerator.lines.map((line) => padLayoutLine(line, width, true)),\n\t\t\t\t\t\t` ${\"─\".repeat(contentWidth)} `,\n\t\t\t\t\t\t...denominator.lines.map((line) => padLayoutLine(line, width, true)),\n\t\t\t\t\t],\n\t\t\t\t\twidth,\n\t\t\t\t\tbaseline: numerator.lines.length,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconst contentWidth = Math.max(\n\t\t\t\t\tvisibleWidth(node.operator),\n\t\t\t\t\tnode.lower === undefined ? 0 : visibleWidth(node.lower),\n\t\t\t\t\tnode.upper === undefined ? 0 : visibleWidth(node.upper),\n\t\t\t\t);\n\t\t\t\tconst lines: string[] = [];\n\t\t\t\tif (node.upper !== undefined) {\n\t\t\t\t\tlines.push(`${padLayoutLine(node.upper, contentWidth, true)} `);\n\t\t\t\t}\n\t\t\t\tlines.push(`${padLayoutLine(node.operator, contentWidth, true)} `);\n\t\t\t\tif (node.lower !== undefined) {\n\t\t\t\t\tlines.push(`${padLayoutLine(node.lower, contentWidth, true)} `);\n\t\t\t\t}\n\t\t\t\tlayouts.push({\n\t\t\t\t\tlines,\n\t\t\t\t\twidth: contentWidth + 1,\n\t\t\t\t\tbaseline: node.upper === undefined ? 0 : 1,\n\t\t\t\t});\n\t\t\t}\n\t\t\tposition = index + match[0].length;\n\t\t\tpreviousWasNode = true;\n\t\t}\n\t\tif (position < sourceLine.length) {\n\t\t\tconst sliced = sourceLine.slice(position);\n\t\t\tconst text = previousWasNode ? sliced.trimStart() : sliced;\n\t\t\tlayouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });\n\t\t}\n\t\tconst lineLayout = joinLayouts(layouts);\n\t\tif (renderedLines.length === 0) {\n\t\t\tfirstBaseline = lineLayout.baseline;\n\t\t}\n\t\trenderedLines.push(...lineLayout.lines);\n\t}\n\treturn {\n\t\tlines: renderedLines,\n\t\twidth: Math.max(0, ...renderedLines.map((line) => visibleWidth(line))),\n\t\tbaseline: firstBaseline,\n\t};\n}\n\nclass LatexParser {\n\tprivate readonly source: string;\n\tprivate readonly layoutNodes: LayoutNode[] | undefined;\n\tprivate position = 0;\n\tprivate supported = true;\n\tprivate stackFractions = true;\n\n\tconstructor(source: string, layoutNodes?: LayoutNode[]) {\n\t\tthis.source = source;\n\t\tthis.layoutNodes = layoutNodes;\n\t}\n\n\trender(): string | undefined {\n\t\tconst rendered = this.parseSequence();\n\t\tif (!this.supported || this.position !== this.source.length) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn normalizeOutput(rendered);\n\t}\n\n\tprivate parseSequence(endCharacter?: string): string {\n\t\tlet result = \"\";\n\t\twhile (this.position < this.source.length) {\n\t\t\tconst character = this.source[this.position];\n\t\t\tif (endCharacter && character === endCharacter) {\n\t\t\t\tthis.position++;\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tif (character === \"}\") {\n\t\t\t\tthis.supported = false;\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tif (character === \"{\") {\n\t\t\t\tthis.position++;\n\t\t\t\tresult += this.parseSequence(\"}\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (character === \"\\\\\") {\n\t\t\t\tconst command = this.parseCommand();\n\t\t\t\tif (command === NEGATIVE_SPACE) {\n\t\t\t\t\tresult = result.trimEnd();\n\t\t\t\t} else {\n\t\t\t\t\tresult += command;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (character === \"^\" || character === \"_\") {\n\t\t\t\tthis.position++;\n\t\t\t\tresult = result.trimEnd();\n\t\t\t\tresult += formatScript(this.parseRequiredArgument(false), character === \"_\" ? \"sub\" : \"sup\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (/\\s/.test(character)) {\n\t\t\t\tresult += this.parseWhitespace();\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (character === \"&\") {\n\t\t\t\tthis.position++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (character === \"~\") {\n\t\t\t\tthis.position++;\n\t\t\t\tresult += \" \";\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tresult += character;\n\t\t\tthis.position++;\n\t\t}\n\n\t\tif (endCharacter) {\n\t\t\tthis.supported = false;\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate parseWhitespace(): string {\n\t\twhile (this.position < this.source.length && /\\s/.test(this.source[this.position] ?? \"\")) {\n\t\t\tthis.position++;\n\t\t}\n\t\treturn \" \";\n\t}\n\n\tprivate parseCommand(): string {\n\t\tthis.position++;\n\t\tif (this.position >= this.source.length) {\n\t\t\tthis.supported = false;\n\t\t\treturn \"\";\n\t\t}\n\n\t\tlet command = \"\";\n\t\tconst first = this.source[this.position] ?? \"\";\n\t\tif (/[A-Za-z]/.test(first)) {\n\t\t\tconst start = this.position;\n\t\t\twhile (this.position < this.source.length && /[A-Za-z]/.test(this.source[this.position] ?? \"\")) {\n\t\t\t\tthis.position++;\n\t\t\t}\n\t\t\tcommand = this.source.slice(start, this.position);\n\t\t} else {\n\t\t\tcommand = first;\n\t\t\tthis.position++;\n\t\t}\n\n\t\tif (command === \"\\\\\") {\n\t\t\treturn \"\\n\";\n\t\t}\n\t\tif (SPACING_COMMANDS.has(command)) {\n\t\t\treturn \" \";\n\t\t}\n\t\tif (NEGATIVE_SPACING_COMMANDS.has(command)) {\n\t\t\treturn NEGATIVE_SPACE;\n\t\t}\n\t\tif (IGNORED_COMMANDS.has(command)) {\n\t\t\treturn \"\";\n\t\t}\n\t\tif (\n\t\t\tcommand === \"{\" ||\n\t\t\tcommand === \"}\" ||\n\t\t\tcommand === \"$\" ||\n\t\t\tcommand === \"%\" ||\n\t\t\tcommand === \"#\" ||\n\t\t\tcommand === \"_\" ||\n\t\t\tcommand === \"&\"\n\t\t) {\n\t\t\treturn command;\n\t\t}\n\t\tif (command === \"|\") {\n\t\t\treturn \"‖\";\n\t\t}\n\t\tif (command === \"not\") {\n\t\t\tconst value = this.parseRequiredArgument(false).trim();\n\t\t\tconst negated = NEGATED_SYMBOLS[value];\n\t\t\tif (negated !== undefined) {\n\t\t\t\treturn negated;\n\t\t\t}\n\t\t\tconst characters = Array.from(value);\n\t\t\tif (characters.length === 0) {\n\t\t\t\tthis.supported = false;\n\t\t\t\treturn \"\";\n\t\t\t}\n\t\t\treturn `${characters[0]}\\u0338${characters.slice(1).join(\"\")}`;\n\t\t}\n\t\tif (LIMIT_OPERATORS.has(command)) {\n\t\t\treturn this.parseOperator(command, \"bracket\", true, true);\n\t\t}\n\n\t\tconst symbol = SYMBOLS[command];\n\t\tif (symbol !== undefined) {\n\t\t\treturn DISPLAY_LIMIT_SYMBOLS.has(command) ? this.parseOperator(symbol, \"script\", true) : symbol;\n\t\t}\n\t\tif (NAMED_OPERATORS.has(command)) {\n\t\t\treturn ` ${command} `;\n\t\t}\n\t\tif (SIZE_COMMANDS.has(command)) {\n\t\t\treturn \"\";\n\t\t}\n\t\tif (command === \"left\" || command === \"middle\" || command === \"right\") {\n\t\t\tif (this.source[this.position] === \".\") {\n\t\t\t\tthis.position++;\n\t\t\t}\n\t\t\treturn \"\";\n\t\t}\n\t\tif (command === \"frac\" || command === \"dfrac\" || command === \"tfrac\") {\n\t\t\tconst shouldStack = this.layoutNodes !== undefined && this.stackFractions && command !== \"tfrac\";\n\t\t\tconst numerator = this.parseRequiredArgument(!shouldStack);\n\t\t\tconst denominator = this.parseRequiredArgument(!shouldStack);\n\t\t\tif (shouldStack && this.layoutNodes) {\n\t\t\t\tconst index =\n\t\t\t\t\tthis.layoutNodes.push({\n\t\t\t\t\t\ttype: \"fraction\",\n\t\t\t\t\t\tnumerator: normalizeOutput(numerator),\n\t\t\t\t\t\tdenominator: normalizeOutput(denominator),\n\t\t\t\t\t}) - 1;\n\t\t\t\treturn `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;\n\t\t\t}\n\t\t\treturn formatFraction(numerator, denominator);\n\t\t}\n\t\tif (command === \"sqrt\") {\n\t\t\tconst degree = this.parseOptionalArgument()?.trim();\n\t\t\tconst value = this.parseRequiredArgument();\n\t\t\tif (degree === undefined || degree === \"2\") {\n\t\t\t\treturn formatRoot(value);\n\t\t\t}\n\t\t\tif (degree === \"3\") {\n\t\t\t\treturn formatRoot(value, \"∛\");\n\t\t\t}\n\t\t\tif (degree === \"4\") {\n\t\t\t\treturn formatRoot(value, \"∜\");\n\t\t\t}\n\t\t\treturn `${formatScript(degree, \"sup\")}${formatRoot(value)}`;\n\t\t}\n\t\tif (command === \"boxed\" || command === \"fbox\") {\n\t\t\treturn `[${this.parseRequiredArgument().trim()}]`;\n\t\t}\n\t\tif (command === \"binom\" || command === \"dbinom\" || command === \"tbinom\") {\n\t\t\treturn `(${this.parseRequiredArgument()} choose ${this.parseRequiredArgument()})`;\n\t\t}\n\t\tconst accent = ACCENTS[command];\n\t\tif (accent !== undefined) {\n\t\t\tconst value = this.parseRequiredArgument();\n\t\t\treturn Array.from(value).length === 1 ? `${value}${accent}` : `${command}(${value})`;\n\t\t}\n\t\tif (command === \"mathbb\") {\n\t\t\tconst value = this.parseRequiredArgument();\n\t\t\treturn Array.from(value, (character) => BLACKBOARD[character] ?? character).join(\"\");\n\t\t}\n\t\tif (command === \"operatorname\") {\n\t\t\tconst starred = this.source[this.position] === \"*\";\n\t\t\tif (starred) {\n\t\t\t\tthis.position++;\n\t\t\t}\n\t\t\tconst operator = normalizeOutput(this.parseRequiredArgument()).trim();\n\t\t\treturn this.parseOperator(operator, \"bracket\", starred, true);\n\t\t}\n\t\tif (command === \"mod\" || command === \"bmod\") {\n\t\t\treturn \" mod \";\n\t\t}\n\t\tif (command === \"pmod\" || command === \"pod\") {\n\t\t\tconst value = this.parseRequiredArgument().trim();\n\t\t\treturn command === \"pmod\" ? ` (mod ${value})` : ` (${value})`;\n\t\t}\n\t\tif (command === \"overset\" || command === \"stackrel\") {\n\t\t\tconst upper = this.parseRequiredArgument();\n\t\t\tconst value = this.parseRequiredArgument().trim();\n\t\t\treturn `${value}${formatScript(upper, \"sup\")}`;\n\t\t}\n\t\tif (command === \"underset\") {\n\t\t\tconst lower = this.parseRequiredArgument();\n\t\t\tconst value = this.parseRequiredArgument().trim();\n\t\t\treturn `${value}${formatScript(lower, \"sub\")}`;\n\t\t}\n\t\tif (PLAIN_WRAPPERS.has(command)) {\n\t\t\tconst value = this.parseRequiredArgument();\n\t\t\treturn command.startsWith(\"text\") || command === \"mbox\" ? value : value.trim();\n\t\t}\n\t\tif (command === \"begin\") {\n\t\t\treturn this.parseEnvironment();\n\t\t}\n\t\tif (command === \"end\") {\n\t\t\tthis.supported = false;\n\t\t\treturn \"\";\n\t\t}\n\n\t\tthis.supported = false;\n\t\treturn `\\\\${command}`;\n\t}\n\n\tprivate parseOperator(\n\t\toperator: string,\n\t\tinlineLowerStyle: \"bracket\" | \"script\",\n\t\tdisplayLimits: boolean,\n\t\tspaced = false,\n\t): string {\n\t\tlet useDisplayLimits = displayLimits;\n\t\tlet modifierPosition = this.position;\n\t\twhile (modifierPosition < this.source.length && /[ \\t]/.test(this.source[modifierPosition] ?? \"\")) {\n\t\t\tmodifierPosition++;\n\t\t}\n\t\tconst modifier = /^\\\\(limits|nolimits)(?![A-Za-z])/.exec(this.source.slice(modifierPosition));\n\t\tif (modifier) {\n\t\t\tuseDisplayLimits = modifier[1] === \"limits\";\n\t\t\tthis.position = modifierPosition + modifier[0].length;\n\t\t}\n\n\t\tlet lower: string | undefined;\n\t\tlet upper: string | undefined;\n\t\twhile (true) {\n\t\t\tlet scriptPosition = this.position;\n\t\t\twhile (scriptPosition < this.source.length && /[ \\t]/.test(this.source[scriptPosition] ?? \"\")) {\n\t\t\t\tscriptPosition++;\n\t\t\t}\n\t\t\tconst kind = this.source[scriptPosition];\n\t\t\tif (kind !== \"_\" && kind !== \"^\") {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tthis.position = scriptPosition + 1;\n\t\t\tconst value = normalizeOutput(this.parseRequiredArgument(false)).replaceAll(\" \", \"\");\n\t\t\tif (kind === \"_\") {\n\t\t\t\tif (lower !== undefined) {\n\t\t\t\t\tthis.supported = false;\n\t\t\t\t}\n\t\t\t\tlower = value;\n\t\t\t} else {\n\t\t\t\tif (upper !== undefined) {\n\t\t\t\t\tthis.supported = false;\n\t\t\t\t}\n\t\t\t\tupper = value;\n\t\t\t}\n\t\t}\n\n\t\tif (this.layoutNodes && useDisplayLimits && (lower !== undefined || upper !== undefined)) {\n\t\t\tconst index = this.layoutNodes.push({ type: \"operator\", operator, lower, upper }) - 1;\n\t\t\treturn `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;\n\t\t}\n\n\t\tlet rendered = operator;\n\t\tif (lower !== undefined) {\n\t\t\trendered += inlineLowerStyle === \"bracket\" ? `[${lower}]` : formatScript(lower, \"sub\");\n\t\t}\n\t\tif (upper !== undefined) {\n\t\t\trendered += formatScript(upper, \"sup\");\n\t\t}\n\t\treturn spaced ? ` ${rendered} ` : rendered;\n\t}\n\n\tprivate parseRequiredArgument(stackFractions = true): string {\n\t\tconst previousStackFractions = this.stackFractions;\n\t\tthis.stackFractions = previousStackFractions && stackFractions;\n\t\tconst value = this.parseRequiredArgumentValue();\n\t\tthis.stackFractions = previousStackFractions;\n\t\treturn value;\n\t}\n\n\tprivate parseRequiredArgumentValue(): string {\n\t\twhile (this.position < this.source.length && /[ \\t]/.test(this.source[this.position] ?? \"\")) {\n\t\t\tthis.position++;\n\t\t}\n\t\tif (this.position >= this.source.length) {\n\t\t\tthis.supported = false;\n\t\t\treturn \"\";\n\t\t}\n\t\tif (this.source[this.position] === \"{\") {\n\t\t\tthis.position++;\n\t\t\treturn this.parseSequence(\"}\");\n\t\t}\n\t\tif (this.source[this.position] === \"\\\\\") {\n\t\t\treturn this.parseCommand();\n\t\t}\n\t\tconst value = this.source[this.position] ?? \"\";\n\t\tthis.position++;\n\t\treturn value;\n\t}\n\n\tprivate parseOptionalArgument(): string | undefined {\n\t\twhile (this.position < this.source.length && /[ \\t]/.test(this.source[this.position] ?? \"\")) {\n\t\t\tthis.position++;\n\t\t}\n\t\tif (this.source[this.position] !== \"[\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst end = this.source.indexOf(\"]\", this.position + 1);\n\t\tif (end < 0) {\n\t\t\tthis.supported = false;\n\t\t\treturn undefined;\n\t\t}\n\t\tconst value = this.source.slice(this.position + 1, end);\n\t\tthis.position = end + 1;\n\t\treturn this.renderNested(value);\n\t}\n\n\tprivate readRawGroup(): string | undefined {\n\t\twhile (this.position < this.source.length && /[ \\t]/.test(this.source[this.position] ?? \"\")) {\n\t\t\tthis.position++;\n\t\t}\n\t\tif (this.source[this.position] !== \"{\") {\n\t\t\tthis.supported = false;\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst start = ++this.position;\n\t\tlet depth = 1;\n\t\twhile (this.position < this.source.length) {\n\t\t\tconst character = this.source[this.position];\n\t\t\tif (character === \"\\\\\") {\n\t\t\t\tthis.position += 2;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (character === \"{\") depth++;\n\t\t\tif (character === \"}\") depth--;\n\t\t\tif (depth === 0) {\n\t\t\t\tconst value = this.source.slice(start, this.position);\n\t\t\t\tthis.position++;\n\t\t\t\treturn value;\n\t\t\t}\n\t\t\tthis.position++;\n\t\t}\n\t\tthis.supported = false;\n\t\treturn undefined;\n\t}\n\n\tprivate splitEnvironmentRows(body: string): string[] {\n\t\treturn body.split(/\\\\\\\\(?:\\[[^\\]\\n]*\\])?/);\n\t}\n\n\tprivate parseEnvironment(): string {\n\t\tconst environment = this.readRawGroup();\n\t\tif (!environment) {\n\t\t\treturn \"\";\n\t\t}\n\t\tconst endMarker = `\\\\end{${environment}}`;\n\t\tconst end = this.source.indexOf(endMarker, this.position);\n\t\tif (end < 0) {\n\t\t\tthis.supported = false;\n\t\t\treturn \"\";\n\t\t}\n\t\tconst body = this.source.slice(this.position, end);\n\t\tthis.position = end + endMarker.length;\n\n\t\tif (environment === \"equation\" || environment === \"equation*\" || environment === \"displaymath\") {\n\t\t\treturn this.renderNested(body).trim();\n\t\t}\n\n\t\tif (\n\t\t\tenvironment === \"aligned\" ||\n\t\t\tenvironment === \"align\" ||\n\t\t\tenvironment === \"align*\" ||\n\t\t\tenvironment === \"alignedat\" ||\n\t\t\tenvironment === \"alignat\" ||\n\t\t\tenvironment === \"alignat*\" ||\n\t\t\tenvironment === \"gather\" ||\n\t\t\tenvironment === \"gathered\" ||\n\t\t\tenvironment === \"multline\" ||\n\t\t\tenvironment === \"multline*\" ||\n\t\t\tenvironment === \"split\"\n\t\t) {\n\t\t\tconst alignedAt = [\"alignedat\", \"alignat\", \"alignat*\"].includes(environment);\n\t\t\tconst alignedBody = alignedAt ? body.replace(/^\\s*\\{[^}]*\\}/, \"\") : body;\n\t\t\treturn this.splitEnvironmentRows(alignedBody)\n\t\t\t\t.map((row) => {\n\t\t\t\t\tconst cells = row.split(\"&\");\n\t\t\t\t\tconst source = alignedAt\n\t\t\t\t\t\t? Array.from({ length: Math.ceil(cells.length / 2) }, (_, index) =>\n\t\t\t\t\t\t\t\tcells.slice(index * 2, index * 2 + 2).join(\"\"),\n\t\t\t\t\t\t\t).join(\" \")\n\t\t\t\t\t\t: cells.join(\"\");\n\t\t\t\t\treturn this.renderNested(source).trim();\n\t\t\t\t})\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\"\\n\");\n\t\t}\n\n\t\tif (environment === \"cases\" || environment === \"cases*\") {\n\t\t\tconst rows = this.splitEnvironmentRows(body)\n\t\t\t\t.map((row) => row.split(\"&\").map((cell) => this.renderNested(cell, false).trim()))\n\t\t\t\t.filter((row) => row.some(Boolean));\n\t\t\treturn rows\n\t\t\t\t.map((row, index) => {\n\t\t\t\t\tconst value = (row[0] ?? \"\").replace(/,\\s*$/, \"\");\n\t\t\t\t\tconst condition = row[1] ?? \"\";\n\t\t\t\t\tconst delimiter = index === 0 ? \"⎧\" : index === rows.length - 1 ? \"⎩\" : \"⎨\";\n\t\t\t\t\tconst conditionPrefix = /^(?:if|when|for|otherwise)\\b/i.test(condition) ? \" \" : \" if \";\n\t\t\t\t\treturn `${delimiter} ${value}${condition ? `${conditionPrefix}${condition}` : \"\"}`;\n\t\t\t\t})\n\t\t\t\t.join(\"\\n\");\n\t\t}\n\n\t\tif (\n\t\t\t[\"array\", \"matrix\", \"smallmatrix\", \"pmatrix\", \"bmatrix\", \"Bmatrix\", \"vmatrix\", \"Vmatrix\"].includes(environment)\n\t\t) {\n\t\t\tconst matrixBody = environment === \"array\" ? body.replace(/^\\s*\\{[^}]*\\}/, \"\") : body;\n\t\t\treturn this.renderMatrix(environment, matrixBody);\n\t\t}\n\n\t\tthis.supported = false;\n\t\treturn body;\n\t}\n\n\tprivate renderMatrix(environment: string, body: string): string {\n\t\tconst matrix = this.splitEnvironmentRows(body)\n\t\t\t.map((row) => row.split(\"&\").map((cell) => this.renderNested(cell, false).trim()))\n\t\t\t.filter((row) => row.some(Boolean));\n\t\tconst columnCount = Math.max(0, ...matrix.map((row) => row.length));\n\t\tconst columnWidths = Array.from({ length: columnCount }, (_, column) =>\n\t\t\tMath.max(0, ...matrix.map((row) => visibleWidth(row[column] ?? \"\"))),\n\t\t);\n\t\tconst rows = matrix.map((row) =>\n\t\t\tArray.from({ length: columnCount }, (_, column) => {\n\t\t\t\tconst cell = row[column] ?? \"\";\n\t\t\t\treturn `${cell}${PROTECTED_SPACE.repeat(Math.max(0, (columnWidths[column] ?? 0) - visibleWidth(cell)))}`;\n\t\t\t}).join(\" │ \"),\n\t\t);\n\t\tif (environment === \"array\" || environment === \"matrix\" || environment === \"smallmatrix\") {\n\t\t\treturn rows.join(\"\\n\");\n\t\t}\n\n\t\tconst delimiters: Readonly<Record<string, readonly [string, string, string, string, string, string]>> = {\n\t\t\tpmatrix: [\"⎛\", \"⎞\", \"⎜\", \"⎟\", \"⎝\", \"⎠\"],\n\t\t\tbmatrix: [\"⎡\", \"⎤\", \"⎢\", \"⎥\", \"⎣\", \"⎦\"],\n\t\t\tBmatrix: [\"⎧\", \"⎫\", \"⎨\", \"⎬\", \"⎩\", \"⎭\"],\n\t\t\tvmatrix: [\"│\", \"│\", \"│\", \"│\", \"│\", \"│\"],\n\t\t\tVmatrix: [\"║\", \"║\", \"║\", \"║\", \"║\", \"║\"],\n\t\t};\n\t\tconst delimiter = delimiters[environment];\n\t\tif (!delimiter) {\n\t\t\tthis.supported = false;\n\t\t\treturn rows.join(\"\\n\");\n\t\t}\n\t\tif (rows.length === 1) {\n\t\t\treturn `${delimiter[0]} ${rows[0]} ${delimiter[1]}`;\n\t\t}\n\t\treturn rows\n\t\t\t.map((row, index) => {\n\t\t\t\tconst left = index === 0 ? delimiter[0] : index === rows.length - 1 ? delimiter[4] : delimiter[2];\n\t\t\t\tconst right = index === 0 ? delimiter[1] : index === rows.length - 1 ? delimiter[5] : delimiter[3];\n\t\t\t\treturn `${left} ${row} ${right}`;\n\t\t\t})\n\t\t\t.join(\"\\n\");\n\t}\n\n\tprivate renderNested(source: string, stackFractions = true): string {\n\t\tconst rendered = new LatexParser(source, stackFractions ? this.layoutNodes : undefined).render();\n\t\tif (rendered === undefined) {\n\t\t\tthis.supported = false;\n\t\t\treturn source;\n\t\t}\n\t\treturn rendered;\n\t}\n}\n\nexport interface RenderLatexOptions {\n\t/** Stack fractions and operator limits vertically for display math (default: false). */\n\tdisplay?: boolean;\n}\n\n/**\n * Render a basic LaTeX math expression as terminal-friendly Unicode text.\n * Returns undefined when the expression contains unsupported or malformed syntax.\n */\nexport function renderLatex(source: string, options: RenderLatexOptions = {}): string | undefined {\n\tconst layoutNodes: LayoutNode[] | undefined = options.display ? [] : undefined;\n\tconst rendered = new LatexParser(source, layoutNodes).render();\n\tif (rendered === undefined) {\n\t\treturn undefined;\n\t}\n\tif (!layoutNodes || layoutNodes.length === 0) {\n\t\treturn rendered.replaceAll(PROTECTED_SPACE, \" \");\n\t}\n\tconst lines = renderLayout(rendered, layoutNodes).lines;\n\tconst indentation = Math.min(\n\t\t...lines.filter((line) => line.trim()).map((line) => line.length - line.trimStart().length),\n\t);\n\treturn lines\n\t\t.map((line) => line.slice(indentation).trimEnd())\n\t\t.join(\"\\n\")\n\t\t.trimEnd()\n\t\t.replaceAll(PROTECTED_SPACE, \" \");\n}\n"]}
1
+ {"version":3,"file":"latex.d.ts","sourceRoot":"","sources":["../src/latex.ts"],"names":[],"mappings":"AAi0CA,MAAM,WAAW,kBAAkB;IAClC,wFAAwF;IACxF,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,GAAG,SAAS,CAkBhG","sourcesContent":["import { visibleWidth } from \"./utils.ts\";\n\nconst SYMBOLS: Readonly<Record<string, string>> = {\n\talpha: \"α\",\n\tbeta: \"β\",\n\tgamma: \"γ\",\n\tdelta: \"δ\",\n\tepsilon: \"ϵ\",\n\tvarepsilon: \"ε\",\n\tzeta: \"ζ\",\n\teta: \"η\",\n\ttheta: \"θ\",\n\tvartheta: \"ϑ\",\n\tiota: \"ι\",\n\tkappa: \"κ\",\n\tvarkappa: \"ϰ\",\n\tlambda: \"λ\",\n\tmu: \"μ\",\n\tnu: \"ν\",\n\txi: \"ξ\",\n\tpi: \"π\",\n\tvarpi: \"ϖ\",\n\trho: \"ρ\",\n\tvarrho: \"ϱ\",\n\tsigma: \"σ\",\n\tvarsigma: \"ς\",\n\ttau: \"τ\",\n\tupsilon: \"υ\",\n\tphi: \"ϕ\",\n\tvarphi: \"φ\",\n\tchi: \"χ\",\n\tpsi: \"ψ\",\n\tomega: \"ω\",\n\tGamma: \"Γ\",\n\tDelta: \"Δ\",\n\tTheta: \"Θ\",\n\tLambda: \"Λ\",\n\tXi: \"Ξ\",\n\tPi: \"Π\",\n\tSigma: \"Σ\",\n\tUpsilon: \"Υ\",\n\tPhi: \"Φ\",\n\tPsi: \"Ψ\",\n\tOmega: \"Ω\",\n\tpm: \"±\",\n\tmp: \"∓\",\n\ttimes: \"×\",\n\tdiv: \"÷\",\n\tcdot: \"·\",\n\tast: \"∗\",\n\tstar: \"⋆\",\n\tcirc: \"∘\",\n\tbullet: \"•\",\n\toplus: \"⊕\",\n\tominus: \"⊖\",\n\totimes: \"⊗\",\n\toslash: \"⊘\",\n\todot: \"⊙\",\n\tbigcirc: \"○\",\n\tdagger: \"†\",\n\tddagger: \"‡\",\n\tamalg: \"⨿\",\n\tuplus: \"⊎\",\n\tsqcap: \"⊓\",\n\tsqcup: \"⊔\",\n\ttriangleleft: \"◁\",\n\ttriangleright: \"▷\",\n\twr: \"≀\",\n\tcap: \"∩\",\n\tcup: \"∪\",\n\tbigcap: \"⋂\",\n\tbigcup: \"⋃\",\n\tbigwedge: \"⋀\",\n\tbigvee: \"⋁\",\n\tbigsqcup: \"⨆\",\n\tbiguplus: \"⨄\",\n\tbigoplus: \"⨁\",\n\tbigotimes: \"⨂\",\n\tbigodot: \"⨀\",\n\tsetminus: \"∖\",\n\tin: \"∈\",\n\tnotin: \"∉\",\n\tni: \"∋\",\n\tsubset: \"⊂\",\n\tsupset: \"⊃\",\n\tsubseteq: \"⊆\",\n\tsupseteq: \"⊇\",\n\tsqsubset: \"⊏\",\n\tsqsupset: \"⊐\",\n\tsqsubseteq: \"⊑\",\n\tsqsupseteq: \"⊒\",\n\tprec: \"≺\",\n\tpreceq: \"≼\",\n\tsucc: \"≻\",\n\tsucceq: \"≽\",\n\tll: \"≪\",\n\tgg: \"≫\",\n\tle: \"≤\",\n\tleq: \"≤\",\n\tleqslant: \"≤\",\n\tge: \"≥\",\n\tgeq: \"≥\",\n\tgeqslant: \"≥\",\n\tne: \"≠\",\n\tneq: \"≠\",\n\tequiv: \"≡\",\n\tapprox: \"≈\",\n\tsim: \"∼\",\n\tsimeq: \"≃\",\n\tcong: \"≅\",\n\tasymp: \"≍\",\n\tdoteq: \"≐\",\n\tpropto: \"∝\",\n\tparallel: \"∥\",\n\tperp: \"⊥\",\n\tmid: \"∣\",\n\tvdash: \"⊢\",\n\tdashv: \"⊣\",\n\tmodels: \"⊨\",\n\tVdash: \"⊩\",\n\tVvdash: \"⊪\",\n\tnvdash: \"⊬\",\n\tnvDash: \"⊭\",\n\tforall: \"∀\",\n\texists: \"∃\",\n\tnexists: \"∄\",\n\tneg: \"¬\",\n\tland: \"∧\",\n\twedge: \"∧\",\n\tlor: \"∨\",\n\tvee: \"∨\",\n\tto: \"→\",\n\trightarrow: \"→\",\n\tlongrightarrow: \"→\",\n\tleftarrow: \"←\",\n\tlongleftarrow: \"←\",\n\tgets: \"←\",\n\tleftrightarrow: \"↔\",\n\tlongleftrightarrow: \"↔\",\n\thookleftarrow: \"↩\",\n\thookrightarrow: \"↪\",\n\ttwoheadleftarrow: \"↞\",\n\ttwoheadrightarrow: \"↠\",\n\tleftharpoonup: \"↼\",\n\tleftharpoondown: \"↽\",\n\trightharpoonup: \"⇀\",\n\trightharpoondown: \"⇁\",\n\trightleftharpoons: \"⇌\",\n\tleftrightharpoons: \"⇋\",\n\tnearrow: \"↗\",\n\tsearrow: \"↘\",\n\tswarrow: \"↙\",\n\tnwarrow: \"↖\",\n\trightsquigarrow: \"⇝\",\n\tleadsto: \"⇝\",\n\tRightarrow: \"⇒\",\n\tLongrightarrow: \"⇒\",\n\tLeftarrow: \"⇐\",\n\tLongleftarrow: \"⇐\",\n\tLeftrightarrow: \"⇔\",\n\tLongleftrightarrow: \"⇔\",\n\timplies: \"⇒\",\n\tiff: \"⇔\",\n\tmapsto: \"↦\",\n\tlongmapsto: \"↦\",\n\tuparrow: \"↑\",\n\tdownarrow: \"↓\",\n\tpartial: \"∂\",\n\tnabla: \"∇\",\n\tint: \"∫\",\n\tiint: \"∬\",\n\tiiint: \"∭\",\n\toint: \"∮\",\n\tsum: \"∑\",\n\tprod: \"∏\",\n\tcoprod: \"∐\",\n\tinfty: \"∞\",\n\temptyset: \"∅\",\n\tvarnothing: \"∅\",\n\tangle: \"∠\",\n\ttherefore: \"∴\",\n\tbecause: \"∵\",\n\taleph: \"ℵ\",\n\tbeth: \"ℶ\",\n\tgimel: \"ℷ\",\n\tdaleth: \"ℸ\",\n\ttop: \"⊤\",\n\tbot: \"⊥\",\n\ttriangle: \"△\",\n\tsquare: \"□\",\n\tlozenge: \"◊\",\n\tcheckmark: \"✓\",\n\tcomplement: \"∁\",\n\twp: \"℘\",\n\tprime: \"′\",\n\tldots: \"…\",\n\tdots: \"…\",\n\tcdots: \"⋯\",\n\tvdots: \"⋮\",\n\tddots: \"⋱\",\n\tell: \"ℓ\",\n\thbar: \"ℏ\",\n\tIm: \"ℑ\",\n\tRe: \"ℜ\",\n\tlangle: \"⟨\",\n\trangle: \"⟩\",\n\tvert: \"|\",\n\tlvert: \"|\",\n\trvert: \"|\",\n\tVert: \"‖\",\n\tlVert: \"‖\",\n\trVert: \"‖\",\n\tlbrace: \"{\",\n\trbrace: \"}\",\n\tbackslash: \"\\\\\",\n\tlfloor: \"⌊\",\n\trfloor: \"⌋\",\n\tlceil: \"⌈\",\n\trceil: \"⌉\",\n\tcolon: \":\",\n};\n\nconst NAMED_OPERATORS = new Set([\n\t\"arccos\",\n\t\"arcsin\",\n\t\"arctan\",\n\t\"arg\",\n\t\"cos\",\n\t\"cosh\",\n\t\"cot\",\n\t\"coth\",\n\t\"csc\",\n\t\"deg\",\n\t\"det\",\n\t\"dim\",\n\t\"exp\",\n\t\"gcd\",\n\t\"hom\",\n\t\"inf\",\n\t\"ker\",\n\t\"lg\",\n\t\"lim\",\n\t\"liminf\",\n\t\"limsup\",\n\t\"ln\",\n\t\"log\",\n\t\"max\",\n\t\"min\",\n\t\"Pr\",\n\t\"sec\",\n\t\"sin\",\n\t\"sinh\",\n\t\"sup\",\n\t\"tan\",\n\t\"tanh\",\n]);\n\nconst LIMIT_OPERATORS = new Set([\n\t\"argmax\",\n\t\"argmin\",\n\t\"inf\",\n\t\"injlim\",\n\t\"lim\",\n\t\"liminf\",\n\t\"limsup\",\n\t\"max\",\n\t\"min\",\n\t\"projlim\",\n\t\"sup\",\n]);\n\nconst DISPLAY_LIMIT_SYMBOLS = new Set([\n\t\"bigcap\",\n\t\"bigcup\",\n\t\"bigodot\",\n\t\"bigoplus\",\n\t\"bigotimes\",\n\t\"bigsqcup\",\n\t\"biguplus\",\n\t\"bigvee\",\n\t\"bigwedge\",\n\t\"coprod\",\n\t\"int\",\n\t\"iint\",\n\t\"iiint\",\n\t\"oint\",\n\t\"prod\",\n\t\"sum\",\n]);\n\nconst RELATION_COMMANDS = new Set([\n\t\"Leftarrow\",\n\t\"Leftrightarrow\",\n\t\"Longleftarrow\",\n\t\"Longleftrightarrow\",\n\t\"Longrightarrow\",\n\t\"Rightarrow\",\n\t\"Vdash\",\n\t\"Vvdash\",\n\t\"approx\",\n\t\"asymp\",\n\t\"cong\",\n\t\"dashv\",\n\t\"doteq\",\n\t\"downarrow\",\n\t\"equiv\",\n\t\"ge\",\n\t\"geq\",\n\t\"geqslant\",\n\t\"gets\",\n\t\"gg\",\n\t\"hookleftarrow\",\n\t\"hookrightarrow\",\n\t\"iff\",\n\t\"implies\",\n\t\"in\",\n\t\"leadsto\",\n\t\"le\",\n\t\"leftarrow\",\n\t\"leftharpoondown\",\n\t\"leftharpoonup\",\n\t\"leftrightarrow\",\n\t\"leftrightharpoons\",\n\t\"leq\",\n\t\"leqslant\",\n\t\"ll\",\n\t\"longleftarrow\",\n\t\"longleftrightarrow\",\n\t\"longmapsto\",\n\t\"longrightarrow\",\n\t\"mapsto\",\n\t\"mid\",\n\t\"models\",\n\t\"ne\",\n\t\"nearrow\",\n\t\"neq\",\n\t\"ni\",\n\t\"notin\",\n\t\"nvdash\",\n\t\"nvDash\",\n\t\"nwarrow\",\n\t\"parallel\",\n\t\"perp\",\n\t\"prec\",\n\t\"preceq\",\n\t\"propto\",\n\t\"rightharpoondown\",\n\t\"rightharpoonup\",\n\t\"rightleftharpoons\",\n\t\"rightarrow\",\n\t\"rightsquigarrow\",\n\t\"searrow\",\n\t\"sim\",\n\t\"simeq\",\n\t\"sqsubset\",\n\t\"sqsubseteq\",\n\t\"sqsupset\",\n\t\"sqsupseteq\",\n\t\"subset\",\n\t\"subseteq\",\n\t\"succ\",\n\t\"succeq\",\n\t\"supset\",\n\t\"supseteq\",\n\t\"swarrow\",\n\t\"to\",\n\t\"triangleleft\",\n\t\"triangleright\",\n\t\"twoheadleftarrow\",\n\t\"twoheadrightarrow\",\n\t\"uparrow\",\n\t\"vdash\",\n]);\n\nconst NEGATED_SYMBOLS: Readonly<Record<string, string>> = {\n\t\"<\": \"≮\",\n\t\">\": \"≯\",\n\t\"=\": \"≠\",\n\t\"∈\": \"∉\",\n\t\"∋\": \"∌\",\n\t\"∣\": \"∤\",\n\t\"∥\": \"∦\",\n\t\"∼\": \"≁\",\n\t\"≃\": \"≄\",\n\t\"≅\": \"≇\",\n\t\"≈\": \"≉\",\n\t\"≡\": \"≢\",\n\t\"≤\": \"≰\",\n\t\"≥\": \"≱\",\n\t\"≺\": \"⊀\",\n\t\"≻\": \"⊁\",\n\t\"⊂\": \"⊄\",\n\t\"⊃\": \"⊅\",\n\t\"⊆\": \"⊈\",\n\t\"⊇\": \"⊉\",\n\t\"⊢\": \"⊬\",\n\t\"⊨\": \"⊭\",\n\t\"↔\": \"↮\",\n\t\"←\": \"↚\",\n\t\"→\": \"↛\",\n\t\"⇒\": \"⇏\",\n\t\"⇐\": \"⇍\",\n\t\"⇔\": \"⇎\",\n\t\"≼\": \"⋠\",\n\t\"≽\": \"⋡\",\n};\n\nconst BLACKBOARD: Readonly<Record<string, string>> = {\n\tC: \"ℂ\",\n\tH: \"ℍ\",\n\tN: \"ℕ\",\n\tP: \"ℙ\",\n\tQ: \"ℚ\",\n\tR: \"ℝ\",\n\tZ: \"ℤ\",\n};\n\nconst SUPERSCRIPTS: Readonly<Record<string, string>> = {\n\t\"0\": \"⁰\",\n\t\"1\": \"¹\",\n\t\"2\": \"²\",\n\t\"3\": \"³\",\n\t\"4\": \"⁴\",\n\t\"5\": \"⁵\",\n\t\"6\": \"⁶\",\n\t\"7\": \"⁷\",\n\t\"8\": \"⁸\",\n\t\"9\": \"⁹\",\n\t\"+\": \"⁺\",\n\t\"-\": \"⁻\",\n\t\"=\": \"⁼\",\n\t\"(\": \"⁽\",\n\t\")\": \"⁾\",\n\ta: \"ᵃ\",\n\tb: \"ᵇ\",\n\tc: \"ᶜ\",\n\td: \"ᵈ\",\n\te: \"ᵉ\",\n\tf: \"ᶠ\",\n\tg: \"ᵍ\",\n\th: \"ʰ\",\n\ti: \"ⁱ\",\n\tj: \"ʲ\",\n\tk: \"ᵏ\",\n\tl: \"ˡ\",\n\tm: \"ᵐ\",\n\tn: \"ⁿ\",\n\to: \"ᵒ\",\n\tp: \"ᵖ\",\n\tr: \"ʳ\",\n\ts: \"ˢ\",\n\tt: \"ᵗ\",\n\tu: \"ᵘ\",\n\tv: \"ᵛ\",\n\tw: \"ʷ\",\n\tx: \"ˣ\",\n\ty: \"ʸ\",\n\tz: \"ᶻ\",\n};\n\nconst SUBSCRIPTS: Readonly<Record<string, string>> = {\n\t\"0\": \"₀\",\n\t\"1\": \"₁\",\n\t\"2\": \"₂\",\n\t\"3\": \"₃\",\n\t\"4\": \"₄\",\n\t\"5\": \"₅\",\n\t\"6\": \"₆\",\n\t\"7\": \"₇\",\n\t\"8\": \"₈\",\n\t\"9\": \"₉\",\n\t\"+\": \"₊\",\n\t\"-\": \"₋\",\n\t\"=\": \"₌\",\n\t\"(\": \"₍\",\n\t\")\": \"₎\",\n\ta: \"ₐ\",\n\te: \"ₑ\",\n\th: \"ₕ\",\n\ti: \"ᵢ\",\n\tj: \"ⱼ\",\n\tk: \"ₖ\",\n\tl: \"ₗ\",\n\tm: \"ₘ\",\n\tn: \"ₙ\",\n\to: \"ₒ\",\n\tp: \"ₚ\",\n\tr: \"ᵣ\",\n\ts: \"ₛ\",\n\tt: \"ₜ\",\n\tu: \"ᵤ\",\n\tv: \"ᵥ\",\n\tx: \"ₓ\",\n};\n\nconst SPACING_COMMANDS = new Set([\n\t\",\",\n\t\":\",\n\t\";\",\n\t\" \",\n\t\">\",\n\t\"enspace\",\n\t\"enskip\",\n\t\"medspace\",\n\t\"quad\",\n\t\"qquad\",\n\t\"thickspace\",\n\t\"thinspace\",\n]);\nconst NEGATIVE_SPACING_COMMANDS = new Set([\"!\", \"negmedspace\", \"negthickspace\", \"negthinspace\"]);\nconst NEGATIVE_SPACE = \"\\u0000\";\nconst IGNORED_COMMANDS = new Set([\n\t\"displaystyle\",\n\t\"limits\",\n\t\"nolimits\",\n\t\"scriptstyle\",\n\t\"scriptscriptstyle\",\n\t\"textstyle\",\n]);\nconst SIZE_COMMANDS = new Set([\n\t\"big\",\n\t\"Big\",\n\t\"bigg\",\n\t\"Bigg\",\n\t\"bigl\",\n\t\"Bigl\",\n\t\"biggl\",\n\t\"Biggl\",\n\t\"bigr\",\n\t\"Bigr\",\n\t\"biggr\",\n\t\"Biggr\",\n]);\nconst PLAIN_WRAPPERS = new Set([\n\t\"emph\",\n\t\"mathcal\",\n\t\"mathbf\",\n\t\"mathfrak\",\n\t\"mathit\",\n\t\"mathrm\",\n\t\"mathnormal\",\n\t\"mathscr\",\n\t\"mathsf\",\n\t\"mathtt\",\n\t\"mathup\",\n\t\"mbox\",\n\t\"overbrace\",\n\t\"pmb\",\n\t\"smash\",\n\t\"substack\",\n\t\"text\",\n\t\"textbf\",\n\t\"textit\",\n\t\"textmd\",\n\t\"textnormal\",\n\t\"textrm\",\n\t\"textsc\",\n\t\"textsf\",\n\t\"textsl\",\n\t\"texttt\",\n\t\"textup\",\n\t\"underbrace\",\n\t\"bm\",\n\t\"boldsymbol\",\n]);\nconst ACCENTS: Readonly<Record<string, string>> = {\n\tacute: \"\\u0301\",\n\tbar: \"\\u0305\",\n\tbreve: \"\\u0306\",\n\tcheck: \"\\u030c\",\n\tddot: \"\\u0308\",\n\tdot: \"\\u0307\",\n\tgrave: \"\\u0300\",\n\that: \"\\u0302\",\n\tmathring: \"\\u030a\",\n\toverleftarrow: \"\\u20d6\",\n\toverleftrightarrow: \"\\u20e1\",\n\toverline: \"\\u0305\",\n\toverrightarrow: \"\\u20d7\",\n\ttilde: \"\\u0303\",\n\tunderline: \"\\u0332\",\n\tvec: \"\\u20d7\",\n\twidehat: \"\\u0302\",\n\twidetilde: \"\\u0303\",\n};\n\nfunction replaceCharacters(value: string, replacements: Readonly<Record<string, string>>): string | undefined {\n\tlet result = \"\";\n\tfor (const character of value) {\n\t\tconst replacement = replacements[character];\n\t\tif (replacement === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\t\tresult += replacement;\n\t}\n\treturn result;\n}\n\nfunction formatScript(value: string, kind: \"sub\" | \"sup\"): string {\n\tvalue = value.trim();\n\tconst replacements = kind === \"sub\" ? SUBSCRIPTS : SUPERSCRIPTS;\n\tconst unicode = replaceCharacters(value.replace(/\\s*([=+-])\\s*/g, \"$1\"), replacements);\n\tif (unicode !== undefined) {\n\t\treturn unicode;\n\t}\n\n\tconst prefix = kind === \"sub\" ? \"_\" : \"^\";\n\tif (Array.from(value).length === 1 || (kind === \"sub\" && /^[A-Za-z]+$/.test(value))) {\n\t\treturn `${prefix}${value}`;\n\t}\n\treturn `${prefix}(${value})`;\n}\n\nfunction formatFraction(numerator: string, denominator: string): string {\n\tnumerator = numerator.trim();\n\tdenominator = denominator.trim();\n\tconst simpleNumerator = /^[\\p{L}\\p{N}.]+$/u.test(numerator);\n\tconst simpleDenominator = /^[\\p{N}.]+$/u.test(denominator) || Array.from(denominator).length === 1;\n\treturn `${simpleNumerator ? numerator : `(${numerator})`}/${simpleDenominator ? denominator : `(${denominator})`}`;\n}\n\nfunction formatRoot(value: string, symbol = \"√\"): string {\n\tvalue = value.trim();\n\treturn /^[\\p{L}\\p{N}.]+$/u.test(value) ? `${symbol}${value}` : `${symbol}(${value})`;\n}\n\nconst NAMED_OPERATOR_START = \"\\u{f0004}\";\nconst NAMED_OPERATOR_END = \"\\u{f0005}\";\nconst NAMED_OPERATOR_LEFT_SPACING_PATTERN = /(?<=[\\p{L}\\p{N})\\]}\\u{f0001}])\\u{f0004}/gu;\nconst NAMED_OPERATOR_RIGHT_SPACING_PATTERN = /\\u{f0005}(?=[\\p{L}\\p{N}√\\u{f0000}])/gu;\n\nfunction normalizeOutput(value: string): string {\n\treturn value\n\t\t.replace(NAMED_OPERATOR_LEFT_SPACING_PATTERN, \" \")\n\t\t.replaceAll(NAMED_OPERATOR_START, \"\")\n\t\t.replace(NAMED_OPERATOR_RIGHT_SPACING_PATTERN, \" \")\n\t\t.replaceAll(NAMED_OPERATOR_END, \"\")\n\t\t.split(\"\\n\")\n\t\t.map((line) => line.replace(/[ \\t]+/g, \" \").trim())\n\t\t.filter((line, index, lines) => line.length > 0 || (index > 0 && index < lines.length - 1))\n\t\t.join(\"\\n\")\n\t\t.trim();\n}\n\ninterface FractionNode {\n\ttype: \"fraction\";\n\tnumerator: string;\n\tdenominator: string;\n}\n\ninterface OperatorNode {\n\ttype: \"operator\";\n\toperator: string;\n\tlower?: string;\n\tupper?: string;\n}\n\ninterface MatrixNode {\n\ttype: \"matrix\";\n\tlines: string[];\n\tbaseline: number;\n}\n\ntype LayoutNode = FractionNode | OperatorNode | MatrixNode;\n\ninterface Layout {\n\tlines: string[];\n\twidth: number;\n\tbaseline: number;\n}\n\nconst LAYOUT_MARKER_START = \"\\u{f0000}\";\nconst LAYOUT_MARKER_END = \"\\u{f0001}\";\nconst LAYOUT_MARKER_PATTERN = /\\u{f0000}(\\d+)\\u{f0001}/gu;\nconst TRAILING_LAYOUT_MARKER_PATTERN = /\\u{f0000}(\\d+)\\u{f0001}$/u;\nconst PROTECTED_SPACE = \"\\u{f0002}\";\n\nfunction padLayoutLine(line: string, width: number, centered = false): string {\n\tconst padding = Math.max(0, width - visibleWidth(line));\n\tconst left = centered ? Math.floor(padding / 2) : 0;\n\treturn `${\" \".repeat(left)}${line}${\" \".repeat(padding - left)}`;\n}\n\nfunction joinLayouts(layouts: readonly Layout[]): Layout {\n\tif (layouts.length === 0) {\n\t\treturn { lines: [\"\"], width: 0, baseline: 0 };\n\t}\n\tconst baseline = Math.max(...layouts.map((layout) => layout.baseline));\n\tconst below = Math.max(...layouts.map((layout) => layout.lines.length - layout.baseline - 1));\n\tconst lines: string[] = [];\n\tfor (let row = 0; row <= baseline + below; row++) {\n\t\tlet line = \"\";\n\t\tfor (const layout of layouts) {\n\t\t\tconst sourceRow = row - baseline + layout.baseline;\n\t\t\tline +=\n\t\t\t\tsourceRow >= 0 && sourceRow < layout.lines.length\n\t\t\t\t\t? padLayoutLine(layout.lines[sourceRow] ?? \"\", layout.width)\n\t\t\t\t\t: \" \".repeat(layout.width);\n\t\t}\n\t\tlines.push(line.trimEnd());\n\t}\n\treturn {\n\t\tlines,\n\t\twidth: layouts.reduce((width, layout) => width + layout.width, 0),\n\t\tbaseline,\n\t};\n}\n\nfunction renderLayout(source: string, nodes: readonly LayoutNode[]): Layout {\n\tconst renderedLines: string[] = [];\n\tlet firstBaseline = 0;\n\tfor (const sourceLine of source.split(\"\\n\")) {\n\t\tconst layouts: Layout[] = [];\n\t\tlet position = 0;\n\t\tlet previousNode: LayoutNode | undefined;\n\t\tfor (const match of sourceLine.matchAll(LAYOUT_MARKER_PATTERN)) {\n\t\t\tconst index = match.index;\n\t\t\tconst node = nodes[Number(match[1])];\n\t\t\tif (!node) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (index > position) {\n\t\t\t\tconst sliced = sourceLine.slice(position, index);\n\t\t\t\tconst trimmed = (previousNode ? sliced.trimStart() : sliced).trimEnd();\n\t\t\t\tconst preserveLeadingSpace = previousNode?.type === \"matrix\" && /^\\s/.test(sliced);\n\t\t\t\tconst preserveTrailingSpace = node.type === \"matrix\" && /\\s$/.test(sliced);\n\t\t\t\tconst text = trimmed\n\t\t\t\t\t? `${preserveLeadingSpace ? \" \" : \"\"}${trimmed}${preserveTrailingSpace ? \" \" : \"\"}`\n\t\t\t\t\t: preserveLeadingSpace || preserveTrailingSpace\n\t\t\t\t\t\t? \" \"\n\t\t\t\t\t\t: \"\";\n\t\t\t\tlayouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });\n\t\t\t}\n\t\t\tif (node.type === \"fraction\") {\n\t\t\t\tconst numerator = renderLayout(node.numerator, nodes);\n\t\t\t\tconst denominator = renderLayout(node.denominator, nodes);\n\t\t\t\tconst contentWidth = Math.max(numerator.width, denominator.width, 1);\n\t\t\t\tconst width = contentWidth + 2;\n\t\t\t\tlayouts.push({\n\t\t\t\t\tlines: [\n\t\t\t\t\t\t...numerator.lines.map((line) => padLayoutLine(line, width, true)),\n\t\t\t\t\t\t` ${\"─\".repeat(contentWidth)} `,\n\t\t\t\t\t\t...denominator.lines.map((line) => padLayoutLine(line, width, true)),\n\t\t\t\t\t],\n\t\t\t\t\twidth,\n\t\t\t\t\tbaseline: numerator.lines.length,\n\t\t\t\t});\n\t\t\t} else if (node.type === \"operator\") {\n\t\t\t\tconst contentWidth = Math.max(\n\t\t\t\t\tvisibleWidth(node.operator),\n\t\t\t\t\tnode.lower === undefined ? 0 : visibleWidth(node.lower),\n\t\t\t\t\tnode.upper === undefined ? 0 : visibleWidth(node.upper),\n\t\t\t\t);\n\t\t\t\tconst lines: string[] = [];\n\t\t\t\tif (node.upper !== undefined) {\n\t\t\t\t\tlines.push(`${padLayoutLine(node.upper, contentWidth, true)} `);\n\t\t\t\t}\n\t\t\t\tlines.push(`${padLayoutLine(node.operator, contentWidth, true)} `);\n\t\t\t\tif (node.lower !== undefined) {\n\t\t\t\t\tlines.push(`${padLayoutLine(node.lower, contentWidth, true)} `);\n\t\t\t\t}\n\t\t\t\tlayouts.push({\n\t\t\t\t\tlines,\n\t\t\t\t\twidth: contentWidth + 1,\n\t\t\t\t\tbaseline: node.upper === undefined ? 0 : 1,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconst width = Math.max(0, ...node.lines.map((line) => visibleWidth(line)));\n\t\t\t\tlayouts.push({\n\t\t\t\t\tlines: node.lines.map((line) => padLayoutLine(line, width)),\n\t\t\t\t\twidth,\n\t\t\t\t\tbaseline: node.baseline,\n\t\t\t\t});\n\t\t\t}\n\t\t\tposition = index + match[0].length;\n\t\t\tpreviousNode = node;\n\t\t}\n\t\tif (position < sourceLine.length) {\n\t\t\tconst sliced = sourceLine.slice(position);\n\t\t\tconst trimmed = previousNode ? sliced.trimStart() : sliced;\n\t\t\tconst text = previousNode?.type === \"matrix\" && /^\\s/.test(sliced) ? ` ${trimmed}` : trimmed;\n\t\t\tlayouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });\n\t\t}\n\t\tconst lineLayout = joinLayouts(layouts);\n\t\tif (renderedLines.length === 0) {\n\t\t\tfirstBaseline = lineLayout.baseline;\n\t\t}\n\t\trenderedLines.push(...lineLayout.lines);\n\t}\n\treturn {\n\t\tlines: renderedLines,\n\t\twidth: Math.max(0, ...renderedLines.map((line) => visibleWidth(line))),\n\t\tbaseline: firstBaseline,\n\t};\n}\n\nclass LatexParser {\n\tprivate readonly source: string;\n\tprivate readonly layoutNodes: LayoutNode[];\n\tprivate readonly display: boolean;\n\tprivate position = 0;\n\tprivate supported = true;\n\tprivate stackFractions = true;\n\n\tconstructor(source: string, layoutNodes: LayoutNode[], display: boolean) {\n\t\tthis.source = source;\n\t\tthis.layoutNodes = layoutNodes;\n\t\tthis.display = display;\n\t}\n\n\trender(): string | undefined {\n\t\tconst rendered = this.parseSequence();\n\t\tif (!this.supported || this.position !== this.source.length) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn normalizeOutput(rendered);\n\t}\n\n\tprivate parseSequence(endCharacter?: string): string {\n\t\tlet result = \"\";\n\t\twhile (this.position < this.source.length) {\n\t\t\tconst character = this.source[this.position];\n\t\t\tif (endCharacter && character === endCharacter) {\n\t\t\t\tthis.position++;\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tif (character === \"}\") {\n\t\t\t\tthis.supported = false;\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tif (character === \"{\") {\n\t\t\t\tthis.position++;\n\t\t\t\tresult += this.parseSequence(\"}\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (character === \"\\\\\") {\n\t\t\t\tconst command = this.parseCommand();\n\t\t\t\tif (command === NEGATIVE_SPACE) {\n\t\t\t\t\tresult = result.trimEnd();\n\t\t\t\t\tif (result.endsWith(NAMED_OPERATOR_END)) {\n\t\t\t\t\t\tresult = result.slice(0, -NAMED_OPERATOR_END.length);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tresult += command;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (character === \"^\" || character === \"_\") {\n\t\t\t\tthis.position++;\n\t\t\t\tresult = result.trimEnd();\n\t\t\t\tconst script = formatScript(this.parseRequiredArgument(false), character === \"_\" ? \"sub\" : \"sup\");\n\t\t\t\tif (result.endsWith(NAMED_OPERATOR_END)) {\n\t\t\t\t\tresult = `${result.slice(0, -NAMED_OPERATOR_END.length)}${script}${NAMED_OPERATOR_END}`;\n\t\t\t\t} else {\n\t\t\t\t\tresult += script;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (/\\s/.test(character)) {\n\t\t\t\tresult += this.parseWhitespace();\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (character === \"=\" || character === \"<\" || character === \">\") {\n\t\t\t\tresult = `${result.trimEnd()} ${character} `;\n\t\t\t\tthis.position++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (character === \"&\") {\n\t\t\t\tthis.position++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (character === \"~\") {\n\t\t\t\tthis.position++;\n\t\t\t\tresult += \" \";\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (character === \".\") {\n\t\t\t\tconst marker = TRAILING_LAYOUT_MARKER_PATTERN.exec(result);\n\t\t\t\tconst node = marker ? this.layoutNodes[Number(marker[1])] : undefined;\n\t\t\t\tif (node?.type === \"matrix\") {\n\t\t\t\t\tconst lastLine = node.lines.length - 1;\n\t\t\t\t\tnode.lines[lastLine] = `${node.lines[lastLine] ?? \"\"}${character}`;\n\t\t\t\t\tthis.position++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresult += character;\n\t\t\tthis.position++;\n\t\t}\n\n\t\tif (endCharacter) {\n\t\t\tthis.supported = false;\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate parseWhitespace(): string {\n\t\twhile (this.position < this.source.length && /\\s/.test(this.source[this.position] ?? \"\")) {\n\t\t\tthis.position++;\n\t\t}\n\t\treturn \" \";\n\t}\n\n\tprivate parseCommand(): string {\n\t\tthis.position++;\n\t\tif (this.position >= this.source.length) {\n\t\t\tthis.supported = false;\n\t\t\treturn \"\";\n\t\t}\n\n\t\tlet command = \"\";\n\t\tconst first = this.source[this.position] ?? \"\";\n\t\tif (/[A-Za-z]/.test(first)) {\n\t\t\tconst start = this.position;\n\t\t\twhile (this.position < this.source.length && /[A-Za-z]/.test(this.source[this.position] ?? \"\")) {\n\t\t\t\tthis.position++;\n\t\t\t}\n\t\t\tcommand = this.source.slice(start, this.position);\n\t\t} else {\n\t\t\tcommand = first;\n\t\t\tthis.position++;\n\t\t}\n\n\t\tif (command === \"\\\\\") {\n\t\t\treturn \"\\n\";\n\t\t}\n\t\tif (SPACING_COMMANDS.has(command)) {\n\t\t\treturn \" \";\n\t\t}\n\t\tif (NEGATIVE_SPACING_COMMANDS.has(command)) {\n\t\t\treturn NEGATIVE_SPACE;\n\t\t}\n\t\tif (IGNORED_COMMANDS.has(command)) {\n\t\t\treturn \"\";\n\t\t}\n\t\tif (\n\t\t\tcommand === \"{\" ||\n\t\t\tcommand === \"}\" ||\n\t\t\tcommand === \"$\" ||\n\t\t\tcommand === \"%\" ||\n\t\t\tcommand === \"#\" ||\n\t\t\tcommand === \"_\" ||\n\t\t\tcommand === \"&\"\n\t\t) {\n\t\t\treturn command;\n\t\t}\n\t\tif (command === \"|\") {\n\t\t\treturn \"‖\";\n\t\t}\n\t\tif (command === \"not\") {\n\t\t\tconst value = this.parseRequiredArgument(false).trim();\n\t\t\tconst negated = NEGATED_SYMBOLS[value];\n\t\t\tif (negated !== undefined) {\n\t\t\t\treturn ` ${negated} `;\n\t\t\t}\n\t\t\tconst characters = Array.from(value);\n\t\t\tif (characters.length === 0) {\n\t\t\t\tthis.supported = false;\n\t\t\t\treturn \"\";\n\t\t\t}\n\t\t\treturn ` ${characters[0]}\\u0338${characters.slice(1).join(\"\")} `;\n\t\t}\n\t\tif (LIMIT_OPERATORS.has(command)) {\n\t\t\treturn this.parseOperator(command, \"bracket\", true, true);\n\t\t}\n\n\t\tconst symbol = SYMBOLS[command];\n\t\tif (symbol !== undefined) {\n\t\t\tif (DISPLAY_LIMIT_SYMBOLS.has(command)) {\n\t\t\t\treturn this.parseOperator(symbol, \"script\", true);\n\t\t\t}\n\t\t\treturn command === \"cdot\" || command === \"times\" || RELATION_COMMANDS.has(command) ? ` ${symbol} ` : symbol;\n\t\t}\n\t\tif (NAMED_OPERATORS.has(command)) {\n\t\t\treturn `${NAMED_OPERATOR_START}${command}${NAMED_OPERATOR_END}`;\n\t\t}\n\t\tif (SIZE_COMMANDS.has(command)) {\n\t\t\treturn \"\";\n\t\t}\n\t\tif (command === \"left\" || command === \"middle\" || command === \"right\") {\n\t\t\tif (this.source[this.position] === \".\") {\n\t\t\t\tthis.position++;\n\t\t\t}\n\t\t\treturn \"\";\n\t\t}\n\t\tif (command === \"frac\" || command === \"dfrac\" || command === \"tfrac\") {\n\t\t\tconst shouldStack = this.display && this.stackFractions && command !== \"tfrac\";\n\t\t\tconst numerator = this.parseRequiredArgument(!shouldStack);\n\t\t\tconst denominator = this.parseRequiredArgument(!shouldStack);\n\t\t\tif (shouldStack) {\n\t\t\t\tconst index =\n\t\t\t\t\tthis.layoutNodes.push({\n\t\t\t\t\t\ttype: \"fraction\",\n\t\t\t\t\t\tnumerator: normalizeOutput(numerator),\n\t\t\t\t\t\tdenominator: normalizeOutput(denominator),\n\t\t\t\t\t}) - 1;\n\t\t\t\treturn `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;\n\t\t\t}\n\t\t\treturn formatFraction(numerator, denominator);\n\t\t}\n\t\tif (command === \"sqrt\") {\n\t\t\tconst degree = this.parseOptionalArgument()?.trim();\n\t\t\tconst value = this.parseRequiredArgument();\n\t\t\tif (degree === undefined || degree === \"2\") {\n\t\t\t\treturn formatRoot(value);\n\t\t\t}\n\t\t\tif (degree === \"3\") {\n\t\t\t\treturn formatRoot(value, \"∛\");\n\t\t\t}\n\t\t\tif (degree === \"4\") {\n\t\t\t\treturn formatRoot(value, \"∜\");\n\t\t\t}\n\t\t\treturn `${formatScript(degree, \"sup\")}${formatRoot(value)}`;\n\t\t}\n\t\tif (command === \"boxed\" || command === \"fbox\") {\n\t\t\treturn `[${this.parseRequiredArgument().trim()}]`;\n\t\t}\n\t\tif (command === \"binom\" || command === \"dbinom\" || command === \"tbinom\") {\n\t\t\treturn `(${this.parseRequiredArgument()} choose ${this.parseRequiredArgument()})`;\n\t\t}\n\t\tconst accent = ACCENTS[command];\n\t\tif (accent !== undefined) {\n\t\t\tconst value = this.parseRequiredArgument();\n\t\t\treturn Array.from(value).length === 1 ? `${value}${accent}` : `${command}(${value})`;\n\t\t}\n\t\tif (command === \"mathbb\") {\n\t\t\tconst value = this.parseRequiredArgument();\n\t\t\treturn Array.from(value, (character) => BLACKBOARD[character] ?? character).join(\"\");\n\t\t}\n\t\tif (command === \"operatorname\") {\n\t\t\tconst starred = this.source[this.position] === \"*\";\n\t\t\tif (starred) {\n\t\t\t\tthis.position++;\n\t\t\t}\n\t\t\tconst operator = normalizeOutput(this.parseRequiredArgument()).trim();\n\t\t\treturn this.parseOperator(operator, \"bracket\", starred, true);\n\t\t}\n\t\tif (command === \"mod\" || command === \"bmod\") {\n\t\t\treturn \" mod \";\n\t\t}\n\t\tif (command === \"pmod\" || command === \"pod\") {\n\t\t\tconst value = this.parseRequiredArgument().trim();\n\t\t\treturn command === \"pmod\" ? ` (mod ${value})` : ` (${value})`;\n\t\t}\n\t\tif (command === \"overset\" || command === \"stackrel\") {\n\t\t\tconst upper = this.parseRequiredArgument();\n\t\t\tconst value = this.parseRequiredArgument().trim();\n\t\t\treturn `${value}${formatScript(upper, \"sup\")}`;\n\t\t}\n\t\tif (command === \"underset\") {\n\t\t\tconst lower = this.parseRequiredArgument();\n\t\t\tconst value = this.parseRequiredArgument().trim();\n\t\t\treturn `${value}${formatScript(lower, \"sub\")}`;\n\t\t}\n\t\tif (PLAIN_WRAPPERS.has(command)) {\n\t\t\tconst value = this.parseRequiredArgument();\n\t\t\treturn command.startsWith(\"text\") || command === \"mbox\" ? value : value.trim();\n\t\t}\n\t\tif (command === \"begin\") {\n\t\t\treturn this.parseEnvironment();\n\t\t}\n\t\tif (command === \"end\") {\n\t\t\tthis.supported = false;\n\t\t\treturn \"\";\n\t\t}\n\n\t\tthis.supported = false;\n\t\treturn `\\\\${command}`;\n\t}\n\n\tprivate parseOperator(\n\t\toperator: string,\n\t\tinlineLowerStyle: \"bracket\" | \"script\",\n\t\tdisplayLimits: boolean,\n\t\tspaced = false,\n\t): string {\n\t\tlet useDisplayLimits = displayLimits;\n\t\tlet modifierPosition = this.position;\n\t\twhile (modifierPosition < this.source.length && /[ \\t]/.test(this.source[modifierPosition] ?? \"\")) {\n\t\t\tmodifierPosition++;\n\t\t}\n\t\tconst modifier = /^\\\\(limits|nolimits)(?![A-Za-z])/.exec(this.source.slice(modifierPosition));\n\t\tif (modifier) {\n\t\t\tuseDisplayLimits = modifier[1] === \"limits\";\n\t\t\tthis.position = modifierPosition + modifier[0].length;\n\t\t}\n\n\t\tlet lower: string | undefined;\n\t\tlet upper: string | undefined;\n\t\twhile (true) {\n\t\t\tlet scriptPosition = this.position;\n\t\t\twhile (scriptPosition < this.source.length && /[ \\t]/.test(this.source[scriptPosition] ?? \"\")) {\n\t\t\t\tscriptPosition++;\n\t\t\t}\n\t\t\tconst kind = this.source[scriptPosition];\n\t\t\tif (kind !== \"_\" && kind !== \"^\") {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tthis.position = scriptPosition + 1;\n\t\t\tconst value = normalizeOutput(this.parseRequiredArgument(false)).replaceAll(\" \", \"\");\n\t\t\tif (kind === \"_\") {\n\t\t\t\tif (lower !== undefined) {\n\t\t\t\t\tthis.supported = false;\n\t\t\t\t}\n\t\t\t\tlower = value;\n\t\t\t} else {\n\t\t\t\tif (upper !== undefined) {\n\t\t\t\t\tthis.supported = false;\n\t\t\t\t}\n\t\t\t\tupper = value;\n\t\t\t}\n\t\t}\n\n\t\tif (this.display && useDisplayLimits && (lower !== undefined || upper !== undefined)) {\n\t\t\tconst index = this.layoutNodes.push({ type: \"operator\", operator, lower, upper }) - 1;\n\t\t\treturn `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;\n\t\t}\n\n\t\tlet rendered = operator;\n\t\tif (lower !== undefined) {\n\t\t\trendered += inlineLowerStyle === \"bracket\" ? `[${lower}]` : formatScript(lower, \"sub\");\n\t\t}\n\t\tif (upper !== undefined) {\n\t\t\trendered += formatScript(upper, \"sup\");\n\t\t}\n\t\treturn spaced ? ` ${rendered} ` : rendered;\n\t}\n\n\tprivate parseRequiredArgument(stackFractions = true): string {\n\t\tconst previousStackFractions = this.stackFractions;\n\t\tthis.stackFractions = previousStackFractions && stackFractions;\n\t\tconst value = this.parseRequiredArgumentValue();\n\t\tthis.stackFractions = previousStackFractions;\n\t\treturn value;\n\t}\n\n\tprivate parseRequiredArgumentValue(): string {\n\t\twhile (this.position < this.source.length && /[ \\t]/.test(this.source[this.position] ?? \"\")) {\n\t\t\tthis.position++;\n\t\t}\n\t\tif (this.position >= this.source.length) {\n\t\t\tthis.supported = false;\n\t\t\treturn \"\";\n\t\t}\n\t\tif (this.source[this.position] === \"{\") {\n\t\t\tthis.position++;\n\t\t\treturn this.parseSequence(\"}\");\n\t\t}\n\t\tif (this.source[this.position] === \"\\\\\") {\n\t\t\treturn this.parseCommand();\n\t\t}\n\t\tconst value = this.source[this.position] ?? \"\";\n\t\tthis.position++;\n\t\treturn value;\n\t}\n\n\tprivate parseOptionalArgument(): string | undefined {\n\t\twhile (this.position < this.source.length && /[ \\t]/.test(this.source[this.position] ?? \"\")) {\n\t\t\tthis.position++;\n\t\t}\n\t\tif (this.source[this.position] !== \"[\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst end = this.source.indexOf(\"]\", this.position + 1);\n\t\tif (end < 0) {\n\t\t\tthis.supported = false;\n\t\t\treturn undefined;\n\t\t}\n\t\tconst value = this.source.slice(this.position + 1, end);\n\t\tthis.position = end + 1;\n\t\treturn this.renderNested(value);\n\t}\n\n\tprivate readRawGroup(): string | undefined {\n\t\twhile (this.position < this.source.length && /[ \\t]/.test(this.source[this.position] ?? \"\")) {\n\t\t\tthis.position++;\n\t\t}\n\t\tif (this.source[this.position] !== \"{\") {\n\t\t\tthis.supported = false;\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst start = ++this.position;\n\t\tlet depth = 1;\n\t\twhile (this.position < this.source.length) {\n\t\t\tconst character = this.source[this.position];\n\t\t\tif (character === \"\\\\\") {\n\t\t\t\tthis.position += 2;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (character === \"{\") depth++;\n\t\t\tif (character === \"}\") depth--;\n\t\t\tif (depth === 0) {\n\t\t\t\tconst value = this.source.slice(start, this.position);\n\t\t\t\tthis.position++;\n\t\t\t\treturn value;\n\t\t\t}\n\t\t\tthis.position++;\n\t\t}\n\t\tthis.supported = false;\n\t\treturn undefined;\n\t}\n\n\tprivate splitEnvironmentRows(body: string): string[] {\n\t\treturn body.split(/\\\\\\\\(?:\\[[^\\]\\n]*\\])?/);\n\t}\n\n\tprivate parseEnvironment(): string {\n\t\tconst environment = this.readRawGroup();\n\t\tif (!environment) {\n\t\t\treturn \"\";\n\t\t}\n\t\tconst endMarker = `\\\\end{${environment}}`;\n\t\tconst end = this.source.indexOf(endMarker, this.position);\n\t\tif (end < 0) {\n\t\t\tthis.supported = false;\n\t\t\treturn \"\";\n\t\t}\n\t\tconst body = this.source.slice(this.position, end);\n\t\tthis.position = end + endMarker.length;\n\n\t\tif (environment === \"equation\" || environment === \"equation*\" || environment === \"displaymath\") {\n\t\t\treturn this.renderNested(body).trim();\n\t\t}\n\n\t\tif (\n\t\t\tenvironment === \"aligned\" ||\n\t\t\tenvironment === \"align\" ||\n\t\t\tenvironment === \"align*\" ||\n\t\t\tenvironment === \"alignedat\" ||\n\t\t\tenvironment === \"alignat\" ||\n\t\t\tenvironment === \"alignat*\" ||\n\t\t\tenvironment === \"gather\" ||\n\t\t\tenvironment === \"gathered\" ||\n\t\t\tenvironment === \"multline\" ||\n\t\t\tenvironment === \"multline*\" ||\n\t\t\tenvironment === \"split\"\n\t\t) {\n\t\t\tconst alignedAt = [\"alignedat\", \"alignat\", \"alignat*\"].includes(environment);\n\t\t\tconst alignedBody = alignedAt ? body.replace(/^\\s*\\{[^}]*\\}/, \"\") : body;\n\t\t\treturn this.splitEnvironmentRows(alignedBody)\n\t\t\t\t.map((row) => {\n\t\t\t\t\tconst cells = row.split(\"&\");\n\t\t\t\t\tconst source = alignedAt\n\t\t\t\t\t\t? Array.from({ length: Math.ceil(cells.length / 2) }, (_, index) =>\n\t\t\t\t\t\t\t\tcells.slice(index * 2, index * 2 + 2).join(\"\"),\n\t\t\t\t\t\t\t).join(\" \")\n\t\t\t\t\t\t: cells.join(\"\");\n\t\t\t\t\treturn this.renderNested(source).trim();\n\t\t\t\t})\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\"\\n\");\n\t\t}\n\n\t\tif (environment === \"cases\" || environment === \"cases*\") {\n\t\t\tconst rows = this.splitEnvironmentRows(body)\n\t\t\t\t.map((row) => row.split(\"&\").map((cell) => this.renderNested(cell, false).trim()))\n\t\t\t\t.filter((row) => row.some(Boolean));\n\t\t\treturn rows\n\t\t\t\t.map((row, index) => {\n\t\t\t\t\tconst value = (row[0] ?? \"\").replace(/,\\s*$/, \"\");\n\t\t\t\t\tconst condition = row[1] ?? \"\";\n\t\t\t\t\tconst delimiter = index === 0 ? \"⎧\" : index === rows.length - 1 ? \"⎩\" : \"⎨\";\n\t\t\t\t\tconst conditionPrefix = /^(?:if|when|for|otherwise)\\b/i.test(condition) ? \" \" : \" if \";\n\t\t\t\t\treturn `${delimiter} ${value}${condition ? `${conditionPrefix}${condition}` : \"\"}`;\n\t\t\t\t})\n\t\t\t\t.join(\"\\n\");\n\t\t}\n\n\t\tif (\n\t\t\t[\"array\", \"matrix\", \"smallmatrix\", \"pmatrix\", \"bmatrix\", \"Bmatrix\", \"vmatrix\", \"Vmatrix\"].includes(environment)\n\t\t) {\n\t\t\tconst matrixBody = environment === \"array\" ? body.replace(/^\\s*\\{[^}]*\\}/, \"\") : body;\n\t\t\treturn this.renderMatrix(environment, matrixBody);\n\t\t}\n\n\t\tthis.supported = false;\n\t\treturn body;\n\t}\n\n\tprivate renderMatrix(environment: string, body: string): string {\n\t\tconst matrix = this.splitEnvironmentRows(body)\n\t\t\t.map((row) => row.split(\"&\").map((cell) => this.renderNested(cell, false).trim()))\n\t\t\t.filter((row) => row.some(Boolean));\n\t\tconst columnCount = Math.max(0, ...matrix.map((row) => row.length));\n\t\tconst columnWidths = Array.from({ length: columnCount }, (_, column) =>\n\t\t\tMath.max(0, ...matrix.map((row) => visibleWidth(row[column] ?? \"\"))),\n\t\t);\n\t\tconst rows = matrix.map((row) =>\n\t\t\tArray.from({ length: columnCount }, (_, column) => {\n\t\t\t\tconst cell = row[column] ?? \"\";\n\t\t\t\treturn `${cell}${PROTECTED_SPACE.repeat(Math.max(0, (columnWidths[column] ?? 0) - visibleWidth(cell)))}`;\n\t\t\t}).join(\" │ \"),\n\t\t);\n\n\t\tlet lines: string[];\n\t\tif (environment === \"array\" || environment === \"matrix\" || environment === \"smallmatrix\") {\n\t\t\tlines = rows;\n\t\t} else {\n\t\t\tconst delimiters: Readonly<Record<string, readonly [string, string, string, string, string, string]>> = {\n\t\t\t\tpmatrix: [\"⎛\", \"⎞\", \"⎜\", \"⎟\", \"⎝\", \"⎠\"],\n\t\t\t\tbmatrix: [\"⎡\", \"⎤\", \"⎢\", \"⎥\", \"⎣\", \"⎦\"],\n\t\t\t\tBmatrix: [\"⎧\", \"⎫\", \"⎨\", \"⎬\", \"⎩\", \"⎭\"],\n\t\t\t\tvmatrix: [\"│\", \"│\", \"│\", \"│\", \"│\", \"│\"],\n\t\t\t\tVmatrix: [\"║\", \"║\", \"║\", \"║\", \"║\", \"║\"],\n\t\t\t};\n\t\t\tconst delimiter = delimiters[environment];\n\t\t\tif (!delimiter) {\n\t\t\t\tthis.supported = false;\n\t\t\t\treturn rows.join(\"\\n\");\n\t\t\t}\n\t\t\tlines = rows.map((row, index) => {\n\t\t\t\tconst left = index === 0 ? delimiter[0] : index === rows.length - 1 ? delimiter[4] : delimiter[2];\n\t\t\t\tconst right = index === 0 ? delimiter[1] : index === rows.length - 1 ? delimiter[5] : delimiter[3];\n\t\t\t\treturn `${left} ${row} ${right}`;\n\t\t\t});\n\t\t}\n\n\t\tif (lines.length <= 1) {\n\t\t\treturn lines[0] ?? \"\";\n\t\t}\n\t\tconst index = this.layoutNodes.push({ type: \"matrix\", lines, baseline: 0 }) - 1;\n\t\treturn `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;\n\t}\n\n\tprivate renderNested(source: string, stackFractions = true): string {\n\t\tconst rendered = new LatexParser(source, this.layoutNodes, this.display && stackFractions).render();\n\t\tif (rendered === undefined) {\n\t\t\tthis.supported = false;\n\t\t\treturn source;\n\t\t}\n\t\treturn rendered;\n\t}\n}\n\nexport interface RenderLatexOptions {\n\t/** Stack fractions and operator limits vertically for display math (default: false). */\n\tdisplay?: boolean;\n}\n\n/**\n * Render a basic LaTeX math expression as terminal-friendly Unicode text.\n * Returns undefined when the expression contains unsupported or malformed syntax.\n */\nexport function renderLatex(source: string, options: RenderLatexOptions = {}): string | undefined {\n\tconst layoutNodes: LayoutNode[] = [];\n\tconst rendered = new LatexParser(source, layoutNodes, options.display === true).render();\n\tif (rendered === undefined) {\n\t\treturn undefined;\n\t}\n\tif (layoutNodes.length === 0) {\n\t\treturn rendered.replaceAll(PROTECTED_SPACE, \" \");\n\t}\n\tconst lines = renderLayout(rendered, layoutNodes).lines;\n\tconst indentation = Math.min(\n\t\t...lines.filter((line) => line.trim()).map((line) => line.length - line.trimStart().length),\n\t);\n\treturn lines\n\t\t.map((line) => line.slice(indentation).trimEnd())\n\t\t.join(\"\\n\")\n\t\t.trimEnd()\n\t\t.replaceAll(PROTECTED_SPACE, \" \");\n}\n"]}
@@ -283,6 +283,89 @@ const DISPLAY_LIMIT_SYMBOLS = new Set([
283
283
  "prod",
284
284
  "sum",
285
285
  ]);
286
+ const RELATION_COMMANDS = new Set([
287
+ "Leftarrow",
288
+ "Leftrightarrow",
289
+ "Longleftarrow",
290
+ "Longleftrightarrow",
291
+ "Longrightarrow",
292
+ "Rightarrow",
293
+ "Vdash",
294
+ "Vvdash",
295
+ "approx",
296
+ "asymp",
297
+ "cong",
298
+ "dashv",
299
+ "doteq",
300
+ "downarrow",
301
+ "equiv",
302
+ "ge",
303
+ "geq",
304
+ "geqslant",
305
+ "gets",
306
+ "gg",
307
+ "hookleftarrow",
308
+ "hookrightarrow",
309
+ "iff",
310
+ "implies",
311
+ "in",
312
+ "leadsto",
313
+ "le",
314
+ "leftarrow",
315
+ "leftharpoondown",
316
+ "leftharpoonup",
317
+ "leftrightarrow",
318
+ "leftrightharpoons",
319
+ "leq",
320
+ "leqslant",
321
+ "ll",
322
+ "longleftarrow",
323
+ "longleftrightarrow",
324
+ "longmapsto",
325
+ "longrightarrow",
326
+ "mapsto",
327
+ "mid",
328
+ "models",
329
+ "ne",
330
+ "nearrow",
331
+ "neq",
332
+ "ni",
333
+ "notin",
334
+ "nvdash",
335
+ "nvDash",
336
+ "nwarrow",
337
+ "parallel",
338
+ "perp",
339
+ "prec",
340
+ "preceq",
341
+ "propto",
342
+ "rightharpoondown",
343
+ "rightharpoonup",
344
+ "rightleftharpoons",
345
+ "rightarrow",
346
+ "rightsquigarrow",
347
+ "searrow",
348
+ "sim",
349
+ "simeq",
350
+ "sqsubset",
351
+ "sqsubseteq",
352
+ "sqsupset",
353
+ "sqsupseteq",
354
+ "subset",
355
+ "subseteq",
356
+ "succ",
357
+ "succeq",
358
+ "supset",
359
+ "supseteq",
360
+ "swarrow",
361
+ "to",
362
+ "triangleleft",
363
+ "triangleright",
364
+ "twoheadleftarrow",
365
+ "twoheadrightarrow",
366
+ "uparrow",
367
+ "vdash",
368
+ ]);
286
369
  const NEGATED_SYMBOLS = {
287
370
  "<": "≮",
288
371
  ">": "≯",
@@ -504,7 +587,7 @@ function replaceCharacters(value, replacements) {
504
587
  function formatScript(value, kind) {
505
588
  value = value.trim();
506
589
  const replacements = kind === "sub" ? SUBSCRIPTS : SUPERSCRIPTS;
507
- const unicode = replaceCharacters(value, replacements);
590
+ const unicode = replaceCharacters(value.replace(/\s*([=+-])\s*/g, "$1"), replacements);
508
591
  if (unicode !== undefined) {
509
592
  return unicode;
510
593
  }
@@ -525,8 +608,16 @@ function formatRoot(value, symbol = "√") {
525
608
  value = value.trim();
526
609
  return /^[\p{L}\p{N}.]+$/u.test(value) ? `${symbol}${value}` : `${symbol}(${value})`;
527
610
  }
611
+ const NAMED_OPERATOR_START = "\u{f0004}";
612
+ const NAMED_OPERATOR_END = "\u{f0005}";
613
+ const NAMED_OPERATOR_LEFT_SPACING_PATTERN = /(?<=[\p{L}\p{N})\]}\u{f0001}])\u{f0004}/gu;
614
+ const NAMED_OPERATOR_RIGHT_SPACING_PATTERN = /\u{f0005}(?=[\p{L}\p{N}√\u{f0000}])/gu;
528
615
  function normalizeOutput(value) {
529
616
  return value
617
+ .replace(NAMED_OPERATOR_LEFT_SPACING_PATTERN, " ")
618
+ .replaceAll(NAMED_OPERATOR_START, "")
619
+ .replace(NAMED_OPERATOR_RIGHT_SPACING_PATTERN, " ")
620
+ .replaceAll(NAMED_OPERATOR_END, "")
530
621
  .split("\n")
531
622
  .map((line) => line.replace(/[ \t]+/g, " ").trim())
532
623
  .filter((line, index, lines) => line.length > 0 || (index > 0 && index < lines.length - 1))
@@ -536,7 +627,8 @@ function normalizeOutput(value) {
536
627
  const LAYOUT_MARKER_START = "\u{f0000}";
537
628
  const LAYOUT_MARKER_END = "\u{f0001}";
538
629
  const LAYOUT_MARKER_PATTERN = /\u{f0000}(\d+)\u{f0001}/gu;
539
- const PROTECTED_SPACE = "\u00a0";
630
+ const TRAILING_LAYOUT_MARKER_PATTERN = /\u{f0000}(\d+)\u{f0001}$/u;
631
+ const PROTECTED_SPACE = "\u{f0002}";
540
632
  function padLayoutLine(line, width, centered = false) {
541
633
  const padding = Math.max(0, width - visibleWidth(line));
542
634
  const left = centered ? Math.floor(padding / 2) : 0;
@@ -572,18 +664,25 @@ function renderLayout(source, nodes) {
572
664
  for (const sourceLine of source.split("\n")) {
573
665
  const layouts = [];
574
666
  let position = 0;
575
- let previousWasNode = false;
667
+ let previousNode;
576
668
  for (const match of sourceLine.matchAll(LAYOUT_MARKER_PATTERN)) {
577
669
  const index = match.index;
578
- if (index > position) {
579
- const sliced = sourceLine.slice(position, index);
580
- const text = (previousWasNode ? sliced.trimStart() : sliced).trimEnd();
581
- layouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });
582
- }
583
670
  const node = nodes[Number(match[1])];
584
671
  if (!node) {
585
672
  continue;
586
673
  }
674
+ if (index > position) {
675
+ const sliced = sourceLine.slice(position, index);
676
+ const trimmed = (previousNode ? sliced.trimStart() : sliced).trimEnd();
677
+ const preserveLeadingSpace = previousNode?.type === "matrix" && /^\s/.test(sliced);
678
+ const preserveTrailingSpace = node.type === "matrix" && /\s$/.test(sliced);
679
+ const text = trimmed
680
+ ? `${preserveLeadingSpace ? " " : ""}${trimmed}${preserveTrailingSpace ? " " : ""}`
681
+ : preserveLeadingSpace || preserveTrailingSpace
682
+ ? " "
683
+ : "";
684
+ layouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });
685
+ }
587
686
  if (node.type === "fraction") {
588
687
  const numerator = renderLayout(node.numerator, nodes);
589
688
  const denominator = renderLayout(node.denominator, nodes);
@@ -599,7 +698,7 @@ function renderLayout(source, nodes) {
599
698
  baseline: numerator.lines.length,
600
699
  });
601
700
  }
602
- else {
701
+ else if (node.type === "operator") {
603
702
  const contentWidth = Math.max(visibleWidth(node.operator), node.lower === undefined ? 0 : visibleWidth(node.lower), node.upper === undefined ? 0 : visibleWidth(node.upper));
604
703
  const lines = [];
605
704
  if (node.upper !== undefined) {
@@ -615,12 +714,21 @@ function renderLayout(source, nodes) {
615
714
  baseline: node.upper === undefined ? 0 : 1,
616
715
  });
617
716
  }
717
+ else {
718
+ const width = Math.max(0, ...node.lines.map((line) => visibleWidth(line)));
719
+ layouts.push({
720
+ lines: node.lines.map((line) => padLayoutLine(line, width)),
721
+ width,
722
+ baseline: node.baseline,
723
+ });
724
+ }
618
725
  position = index + match[0].length;
619
- previousWasNode = true;
726
+ previousNode = node;
620
727
  }
621
728
  if (position < sourceLine.length) {
622
729
  const sliced = sourceLine.slice(position);
623
- const text = previousWasNode ? sliced.trimStart() : sliced;
730
+ const trimmed = previousNode ? sliced.trimStart() : sliced;
731
+ const text = previousNode?.type === "matrix" && /^\s/.test(sliced) ? ` ${trimmed}` : trimmed;
624
732
  layouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });
625
733
  }
626
734
  const lineLayout = joinLayouts(layouts);
@@ -638,12 +746,14 @@ function renderLayout(source, nodes) {
638
746
  class LatexParser {
639
747
  source;
640
748
  layoutNodes;
749
+ display;
641
750
  position = 0;
642
751
  supported = true;
643
752
  stackFractions = true;
644
- constructor(source, layoutNodes) {
753
+ constructor(source, layoutNodes, display) {
645
754
  this.source = source;
646
755
  this.layoutNodes = layoutNodes;
756
+ this.display = display;
647
757
  }
648
758
  render() {
649
759
  const rendered = this.parseSequence();
@@ -673,6 +783,9 @@ class LatexParser {
673
783
  const command = this.parseCommand();
674
784
  if (command === NEGATIVE_SPACE) {
675
785
  result = result.trimEnd();
786
+ if (result.endsWith(NAMED_OPERATOR_END)) {
787
+ result = result.slice(0, -NAMED_OPERATOR_END.length);
788
+ }
676
789
  }
677
790
  else {
678
791
  result += command;
@@ -682,13 +795,24 @@ class LatexParser {
682
795
  if (character === "^" || character === "_") {
683
796
  this.position++;
684
797
  result = result.trimEnd();
685
- result += formatScript(this.parseRequiredArgument(false), character === "_" ? "sub" : "sup");
798
+ const script = formatScript(this.parseRequiredArgument(false), character === "_" ? "sub" : "sup");
799
+ if (result.endsWith(NAMED_OPERATOR_END)) {
800
+ result = `${result.slice(0, -NAMED_OPERATOR_END.length)}${script}${NAMED_OPERATOR_END}`;
801
+ }
802
+ else {
803
+ result += script;
804
+ }
686
805
  continue;
687
806
  }
688
807
  if (/\s/.test(character)) {
689
808
  result += this.parseWhitespace();
690
809
  continue;
691
810
  }
811
+ if (character === "=" || character === "<" || character === ">") {
812
+ result = `${result.trimEnd()} ${character} `;
813
+ this.position++;
814
+ continue;
815
+ }
692
816
  if (character === "&") {
693
817
  this.position++;
694
818
  continue;
@@ -698,6 +822,16 @@ class LatexParser {
698
822
  result += " ";
699
823
  continue;
700
824
  }
825
+ if (character === ".") {
826
+ const marker = TRAILING_LAYOUT_MARKER_PATTERN.exec(result);
827
+ const node = marker ? this.layoutNodes[Number(marker[1])] : undefined;
828
+ if (node?.type === "matrix") {
829
+ const lastLine = node.lines.length - 1;
830
+ node.lines[lastLine] = `${node.lines[lastLine] ?? ""}${character}`;
831
+ this.position++;
832
+ continue;
833
+ }
834
+ }
701
835
  result += character;
702
836
  this.position++;
703
837
  }
@@ -759,24 +893,27 @@ class LatexParser {
759
893
  const value = this.parseRequiredArgument(false).trim();
760
894
  const negated = NEGATED_SYMBOLS[value];
761
895
  if (negated !== undefined) {
762
- return negated;
896
+ return ` ${negated} `;
763
897
  }
764
898
  const characters = Array.from(value);
765
899
  if (characters.length === 0) {
766
900
  this.supported = false;
767
901
  return "";
768
902
  }
769
- return `${characters[0]}\u0338${characters.slice(1).join("")}`;
903
+ return ` ${characters[0]}\u0338${characters.slice(1).join("")} `;
770
904
  }
771
905
  if (LIMIT_OPERATORS.has(command)) {
772
906
  return this.parseOperator(command, "bracket", true, true);
773
907
  }
774
908
  const symbol = SYMBOLS[command];
775
909
  if (symbol !== undefined) {
776
- return DISPLAY_LIMIT_SYMBOLS.has(command) ? this.parseOperator(symbol, "script", true) : symbol;
910
+ if (DISPLAY_LIMIT_SYMBOLS.has(command)) {
911
+ return this.parseOperator(symbol, "script", true);
912
+ }
913
+ return command === "cdot" || command === "times" || RELATION_COMMANDS.has(command) ? ` ${symbol} ` : symbol;
777
914
  }
778
915
  if (NAMED_OPERATORS.has(command)) {
779
- return ` ${command} `;
916
+ return `${NAMED_OPERATOR_START}${command}${NAMED_OPERATOR_END}`;
780
917
  }
781
918
  if (SIZE_COMMANDS.has(command)) {
782
919
  return "";
@@ -788,10 +925,10 @@ class LatexParser {
788
925
  return "";
789
926
  }
790
927
  if (command === "frac" || command === "dfrac" || command === "tfrac") {
791
- const shouldStack = this.layoutNodes !== undefined && this.stackFractions && command !== "tfrac";
928
+ const shouldStack = this.display && this.stackFractions && command !== "tfrac";
792
929
  const numerator = this.parseRequiredArgument(!shouldStack);
793
930
  const denominator = this.parseRequiredArgument(!shouldStack);
794
- if (shouldStack && this.layoutNodes) {
931
+ if (shouldStack) {
795
932
  const index = this.layoutNodes.push({
796
933
  type: "fraction",
797
934
  numerator: normalizeOutput(numerator),
@@ -906,7 +1043,7 @@ class LatexParser {
906
1043
  upper = value;
907
1044
  }
908
1045
  }
909
- if (this.layoutNodes && useDisplayLimits && (lower !== undefined || upper !== undefined)) {
1046
+ if (this.display && useDisplayLimits && (lower !== undefined || upper !== undefined)) {
910
1047
  const index = this.layoutNodes.push({ type: "operator", operator, lower, upper }) - 1;
911
1048
  return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
912
1049
  }
@@ -1065,34 +1202,37 @@ class LatexParser {
1065
1202
  const cell = row[column] ?? "";
1066
1203
  return `${cell}${PROTECTED_SPACE.repeat(Math.max(0, (columnWidths[column] ?? 0) - visibleWidth(cell)))}`;
1067
1204
  }).join(" │ "));
1205
+ let lines;
1068
1206
  if (environment === "array" || environment === "matrix" || environment === "smallmatrix") {
1069
- return rows.join("\n");
1070
- }
1071
- const delimiters = {
1072
- pmatrix: ["⎛", "⎞", "⎜", "⎟", "⎝", "⎠"],
1073
- bmatrix: ["", "", "", "", "", ""],
1074
- Bmatrix: ["", "", "", "", "", ""],
1075
- vmatrix: ["", "", "", "", "", ""],
1076
- Vmatrix: ["", "", "", "", "", ""],
1077
- };
1078
- const delimiter = delimiters[environment];
1079
- if (!delimiter) {
1080
- this.supported = false;
1081
- return rows.join("\n");
1082
- }
1083
- if (rows.length === 1) {
1084
- return `${delimiter[0]} ${rows[0]} ${delimiter[1]}`;
1085
- }
1086
- return rows
1087
- .map((row, index) => {
1088
- const left = index === 0 ? delimiter[0] : index === rows.length - 1 ? delimiter[4] : delimiter[2];
1089
- const right = index === 0 ? delimiter[1] : index === rows.length - 1 ? delimiter[5] : delimiter[3];
1090
- return `${left} ${row} ${right}`;
1091
- })
1092
- .join("\n");
1207
+ lines = rows;
1208
+ }
1209
+ else {
1210
+ const delimiters = {
1211
+ pmatrix: ["", "", "", "", "", ""],
1212
+ bmatrix: ["", "", "", "", "", ""],
1213
+ Bmatrix: ["", "", "", "", "", ""],
1214
+ vmatrix: ["", "", "", "", "", ""],
1215
+ Vmatrix: ["║", "║", "║", "║", "║", "║"],
1216
+ };
1217
+ const delimiter = delimiters[environment];
1218
+ if (!delimiter) {
1219
+ this.supported = false;
1220
+ return rows.join("\n");
1221
+ }
1222
+ lines = rows.map((row, index) => {
1223
+ const left = index === 0 ? delimiter[0] : index === rows.length - 1 ? delimiter[4] : delimiter[2];
1224
+ const right = index === 0 ? delimiter[1] : index === rows.length - 1 ? delimiter[5] : delimiter[3];
1225
+ return `${left} ${row} ${right}`;
1226
+ });
1227
+ }
1228
+ if (lines.length <= 1) {
1229
+ return lines[0] ?? "";
1230
+ }
1231
+ const index = this.layoutNodes.push({ type: "matrix", lines, baseline: 0 }) - 1;
1232
+ return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
1093
1233
  }
1094
1234
  renderNested(source, stackFractions = true) {
1095
- const rendered = new LatexParser(source, stackFractions ? this.layoutNodes : undefined).render();
1235
+ const rendered = new LatexParser(source, this.layoutNodes, this.display && stackFractions).render();
1096
1236
  if (rendered === undefined) {
1097
1237
  this.supported = false;
1098
1238
  return source;
@@ -1105,12 +1245,12 @@ class LatexParser {
1105
1245
  * Returns undefined when the expression contains unsupported or malformed syntax.
1106
1246
  */
1107
1247
  export function renderLatex(source, options = {}) {
1108
- const layoutNodes = options.display ? [] : undefined;
1109
- const rendered = new LatexParser(source, layoutNodes).render();
1248
+ const layoutNodes = [];
1249
+ const rendered = new LatexParser(source, layoutNodes, options.display === true).render();
1110
1250
  if (rendered === undefined) {
1111
1251
  return undefined;
1112
1252
  }
1113
- if (!layoutNodes || layoutNodes.length === 0) {
1253
+ if (layoutNodes.length === 0) {
1114
1254
  return rendered.replaceAll(PROTECTED_SPACE, " ");
1115
1255
  }
1116
1256
  const lines = renderLayout(rendered, layoutNodes).lines;