paseo-ratex-markdown 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/LICENSE +21 -0
  2. package/OVERVIEW.md +23 -0
  3. package/README.md +165 -2
  4. package/assets/icon.png +0 -0
  5. package/assets/screenshot-compact.png +0 -0
  6. package/assets/screenshot-dark.png +0 -0
  7. package/assets/screenshot-light.png +0 -0
  8. package/client/capabilities.ts +10 -0
  9. package/client/document-context.ts +26 -0
  10. package/client/document.tsx +295 -0
  11. package/client/file-panel.tsx +172 -0
  12. package/client/image.tsx +76 -0
  13. package/client/links.ts +36 -0
  14. package/client/markdown/ast.ts +42 -0
  15. package/client/markdown/front-matter.ts +45 -0
  16. package/client/markdown/math-syntax.ts +236 -0
  17. package/client/markdown/parse.ts +326 -0
  18. package/client/math/formula.tsx +156 -0
  19. package/client/math/loader.ts +67 -0
  20. package/client/math/native.ts +117 -0
  21. package/client/math/svg.ts +139 -0
  22. package/client/styles.ts +128 -0
  23. package/client/web.ts +58 -0
  24. package/client/workspace-panel.tsx +316 -0
  25. package/index.client.tsx +28 -0
  26. package/index.server.ts +17 -0
  27. package/node_modules/entities/LICENSE +11 -0
  28. package/node_modules/entities/dist/decode-codepoint.d.ts +28 -0
  29. package/node_modules/entities/dist/decode-codepoint.d.ts.map +1 -0
  30. package/node_modules/entities/dist/decode-codepoint.js +61 -0
  31. package/node_modules/entities/dist/decode-codepoint.js.map +1 -0
  32. package/node_modules/entities/dist/decode.d.ts +227 -0
  33. package/node_modules/entities/dist/decode.d.ts.map +1 -0
  34. package/node_modules/entities/dist/decode.js +1132 -0
  35. package/node_modules/entities/dist/decode.js.map +1 -0
  36. package/node_modules/entities/dist/encode.d.ts +26 -0
  37. package/node_modules/entities/dist/encode.d.ts.map +1 -0
  38. package/node_modules/entities/dist/encode.js +295 -0
  39. package/node_modules/entities/dist/encode.js.map +1 -0
  40. package/node_modules/entities/dist/escape.d.ts +60 -0
  41. package/node_modules/entities/dist/escape.d.ts.map +1 -0
  42. package/node_modules/entities/dist/escape.js +172 -0
  43. package/node_modules/entities/dist/escape.js.map +1 -0
  44. package/node_modules/entities/dist/generated/decode-data-html.d.ts +3 -0
  45. package/node_modules/entities/dist/generated/decode-data-html.d.ts.map +1 -0
  46. package/node_modules/entities/dist/generated/decode-data-html.js +5 -0
  47. package/node_modules/entities/dist/generated/decode-data-html.js.map +1 -0
  48. package/node_modules/entities/dist/generated/decode-data-xml.d.ts +3 -0
  49. package/node_modules/entities/dist/generated/decode-data-xml.d.ts.map +1 -0
  50. package/node_modules/entities/dist/generated/decode-data-xml.js +7 -0
  51. package/node_modules/entities/dist/generated/decode-data-xml.js.map +1 -0
  52. package/node_modules/entities/dist/generated/encode-html.d.ts +3 -0
  53. package/node_modules/entities/dist/generated/encode-html.d.ts.map +1 -0
  54. package/node_modules/entities/dist/generated/encode-html.js +3 -0
  55. package/node_modules/entities/dist/generated/encode-html.js.map +1 -0
  56. package/node_modules/entities/dist/index.d.ts +89 -0
  57. package/node_modules/entities/dist/index.d.ts.map +1 -0
  58. package/node_modules/entities/dist/index.js +87 -0
  59. package/node_modules/entities/dist/index.js.map +1 -0
  60. package/node_modules/entities/dist/internal/bin-trie-flags.d.ts +48 -0
  61. package/node_modules/entities/dist/internal/bin-trie-flags.d.ts.map +1 -0
  62. package/node_modules/entities/dist/internal/bin-trie-flags.js +49 -0
  63. package/node_modules/entities/dist/internal/bin-trie-flags.js.map +1 -0
  64. package/node_modules/entities/dist/internal/decode-shared.d.ts +38 -0
  65. package/node_modules/entities/dist/internal/decode-shared.d.ts.map +1 -0
  66. package/node_modules/entities/dist/internal/decode-shared.js +195 -0
  67. package/node_modules/entities/dist/internal/decode-shared.js.map +1 -0
  68. package/node_modules/entities/package.json +83 -0
  69. package/node_modules/entities/readme.md +131 -0
  70. package/node_modules/entities/src/decode-codepoint.ts +69 -0
  71. package/node_modules/entities/src/decode.ts +1368 -0
  72. package/node_modules/entities/src/encode.ts +337 -0
  73. package/node_modules/entities/src/escape.ts +187 -0
  74. package/node_modules/entities/src/generated/decode-data-html.ts +12 -0
  75. package/node_modules/entities/src/generated/decode-data-xml.ts +7 -0
  76. package/node_modules/entities/src/generated/encode-html.ts +3 -0
  77. package/node_modules/entities/src/index.ts +160 -0
  78. package/node_modules/entities/src/internal/bin-trie-flags.ts +47 -0
  79. package/node_modules/entities/src/internal/decode-shared.ts +209 -0
  80. package/node_modules/linkify-it/LICENSE +22 -0
  81. package/node_modules/linkify-it/README.md +158 -0
  82. package/node_modules/linkify-it/build/index.cjs.js +651 -0
  83. package/node_modules/linkify-it/build/index.cjs.js.map +1 -0
  84. package/node_modules/linkify-it/build/index.d.ts +236 -0
  85. package/node_modules/linkify-it/build/index.mjs +538 -0
  86. package/node_modules/linkify-it/build/index.mjs.map +1 -0
  87. package/node_modules/linkify-it/package.json +66 -0
  88. package/node_modules/markdown-it/LICENSE +22 -0
  89. package/node_modules/markdown-it/README.md +44 -0
  90. package/node_modules/markdown-it/bin/markdown-it.mjs +107 -0
  91. package/node_modules/markdown-it/dist/browser/markdown-it.esm.min.mjs +3064 -0
  92. package/node_modules/markdown-it/dist/browser/markdown-it.esm.min.mjs.map +1 -0
  93. package/node_modules/markdown-it/dist/browser/markdown-it.umd.min.js +14 -0
  94. package/node_modules/markdown-it/dist/browser/markdown-it.umd.min.js.map +1 -0
  95. package/node_modules/markdown-it/dist/markdown-it.cjs.js +4562 -0
  96. package/node_modules/markdown-it/dist/markdown-it.cjs.js.map +1 -0
  97. package/node_modules/markdown-it/dist/markdown-it.d.cts +19 -0
  98. package/node_modules/markdown-it/dist/markdown-it.d.mts +951 -0
  99. package/node_modules/markdown-it/dist/markdown-it.mjs +3935 -0
  100. package/node_modules/markdown-it/dist/markdown-it.mjs.map +1 -0
  101. package/node_modules/markdown-it/node_modules/argparse/LICENSE +47 -0
  102. package/node_modules/markdown-it/node_modules/argparse/README.md +89 -0
  103. package/node_modules/markdown-it/node_modules/argparse/lib/_colorize.js +72 -0
  104. package/node_modules/markdown-it/node_modules/argparse/lib/argparse.d.ts +292 -0
  105. package/node_modules/markdown-it/node_modules/argparse/lib/argparse.js +3820 -0
  106. package/node_modules/markdown-it/node_modules/argparse/lib/difflib.js +190 -0
  107. package/node_modules/markdown-it/node_modules/argparse/lib/sub.js +67 -0
  108. package/node_modules/markdown-it/node_modules/argparse/lib/textwrap.js +440 -0
  109. package/node_modules/markdown-it/node_modules/argparse/package.json +38 -0
  110. package/node_modules/markdown-it/package.json +111 -0
  111. package/node_modules/markdown-it-footnote/LICENSE +22 -0
  112. package/node_modules/markdown-it-footnote/README.md +135 -0
  113. package/node_modules/markdown-it-footnote/dist/index.cjs.js +304 -0
  114. package/node_modules/markdown-it-footnote/dist/markdown-it-footnote.js +299 -0
  115. package/node_modules/markdown-it-footnote/dist/markdown-it-footnote.min.js +2 -0
  116. package/node_modules/markdown-it-footnote/index.mjs +353 -0
  117. package/node_modules/markdown-it-footnote/package.json +48 -0
  118. package/node_modules/mdurl/LICENSE +45 -0
  119. package/node_modules/mdurl/README.md +102 -0
  120. package/node_modules/mdurl/build/index.cjs.js +534 -0
  121. package/node_modules/mdurl/index.mjs +11 -0
  122. package/node_modules/mdurl/lib/decode.mjs +112 -0
  123. package/node_modules/mdurl/lib/encode.mjs +89 -0
  124. package/node_modules/mdurl/lib/format.mjs +21 -0
  125. package/node_modules/mdurl/lib/index.d.ts +28 -0
  126. package/node_modules/mdurl/lib/parse.mjs +308 -0
  127. package/node_modules/mdurl/package.json +43 -0
  128. package/node_modules/opentype.js/LICENSE +20 -0
  129. package/node_modules/opentype.js/README.md +577 -0
  130. package/node_modules/opentype.js/bin/ot +84 -0
  131. package/node_modules/opentype.js/dist/opentype.js +16333 -0
  132. package/node_modules/opentype.js/dist/opentype.min.js +4 -0
  133. package/node_modules/opentype.js/dist/opentype.min.js.map +7 -0
  134. package/node_modules/opentype.js/dist/opentype.min.mjs +3 -0
  135. package/node_modules/opentype.js/dist/opentype.min.mjs.map +7 -0
  136. package/node_modules/opentype.js/dist/opentype.mjs +16308 -0
  137. package/node_modules/opentype.js/package.json +51 -0
  138. package/node_modules/punycode.js/LICENSE-MIT.txt +20 -0
  139. package/node_modules/punycode.js/README.md +148 -0
  140. package/node_modules/punycode.js/package.json +58 -0
  141. package/node_modules/punycode.js/punycode.es6.js +444 -0
  142. package/node_modules/punycode.js/punycode.js +443 -0
  143. package/node_modules/ratex-wasm/LICENSE +85 -0
  144. package/node_modules/ratex-wasm/README.md +92 -0
  145. package/node_modules/ratex-wasm/README.zh-CN.md +99 -0
  146. package/node_modules/ratex-wasm/dist/index.d.ts +68 -0
  147. package/node_modules/ratex-wasm/dist/index.d.ts.map +1 -0
  148. package/node_modules/ratex-wasm/dist/index.js +145 -0
  149. package/node_modules/ratex-wasm/dist/ratex-formula.d.ts +31 -0
  150. package/node_modules/ratex-wasm/dist/ratex-formula.d.ts.map +1 -0
  151. package/node_modules/ratex-wasm/dist/ratex-formula.js +135 -0
  152. package/node_modules/ratex-wasm/dist/renderer.d.ts +21 -0
  153. package/node_modules/ratex-wasm/dist/renderer.d.ts.map +1 -0
  154. package/node_modules/ratex-wasm/dist/renderer.js +169 -0
  155. package/node_modules/ratex-wasm/dist/types.d.ts +87 -0
  156. package/node_modules/ratex-wasm/dist/types.d.ts.map +1 -0
  157. package/node_modules/ratex-wasm/dist/types.js +5 -0
  158. package/node_modules/ratex-wasm/fonts/KaTeX_AMS-Regular.ttf +0 -0
  159. package/node_modules/ratex-wasm/fonts/KaTeX_AMS-Regular.woff +0 -0
  160. package/node_modules/ratex-wasm/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  161. package/node_modules/ratex-wasm/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  162. package/node_modules/ratex-wasm/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  163. package/node_modules/ratex-wasm/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  164. package/node_modules/ratex-wasm/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  165. package/node_modules/ratex-wasm/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  166. package/node_modules/ratex-wasm/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  167. package/node_modules/ratex-wasm/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  168. package/node_modules/ratex-wasm/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  169. package/node_modules/ratex-wasm/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  170. package/node_modules/ratex-wasm/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  171. package/node_modules/ratex-wasm/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  172. package/node_modules/ratex-wasm/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  173. package/node_modules/ratex-wasm/fonts/KaTeX_Main-Bold.ttf +0 -0
  174. package/node_modules/ratex-wasm/fonts/KaTeX_Main-Bold.woff +0 -0
  175. package/node_modules/ratex-wasm/fonts/KaTeX_Main-Bold.woff2 +0 -0
  176. package/node_modules/ratex-wasm/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  177. package/node_modules/ratex-wasm/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  178. package/node_modules/ratex-wasm/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  179. package/node_modules/ratex-wasm/fonts/KaTeX_Main-Italic.ttf +0 -0
  180. package/node_modules/ratex-wasm/fonts/KaTeX_Main-Italic.woff +0 -0
  181. package/node_modules/ratex-wasm/fonts/KaTeX_Main-Italic.woff2 +0 -0
  182. package/node_modules/ratex-wasm/fonts/KaTeX_Main-Regular.ttf +0 -0
  183. package/node_modules/ratex-wasm/fonts/KaTeX_Main-Regular.woff +0 -0
  184. package/node_modules/ratex-wasm/fonts/KaTeX_Main-Regular.woff2 +0 -0
  185. package/node_modules/ratex-wasm/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  186. package/node_modules/ratex-wasm/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  187. package/node_modules/ratex-wasm/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  188. package/node_modules/ratex-wasm/fonts/KaTeX_Math-Italic.ttf +0 -0
  189. package/node_modules/ratex-wasm/fonts/KaTeX_Math-Italic.woff +0 -0
  190. package/node_modules/ratex-wasm/fonts/KaTeX_Math-Italic.woff2 +0 -0
  191. package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  192. package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  193. package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  194. package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  195. package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  196. package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  197. package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  198. package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  199. package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  200. package/node_modules/ratex-wasm/fonts/KaTeX_Script-Regular.ttf +0 -0
  201. package/node_modules/ratex-wasm/fonts/KaTeX_Script-Regular.woff +0 -0
  202. package/node_modules/ratex-wasm/fonts/KaTeX_Script-Regular.woff2 +0 -0
  203. package/node_modules/ratex-wasm/fonts/KaTeX_Size1-Regular.ttf +0 -0
  204. package/node_modules/ratex-wasm/fonts/KaTeX_Size1-Regular.woff +0 -0
  205. package/node_modules/ratex-wasm/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  206. package/node_modules/ratex-wasm/fonts/KaTeX_Size2-Regular.ttf +0 -0
  207. package/node_modules/ratex-wasm/fonts/KaTeX_Size2-Regular.woff +0 -0
  208. package/node_modules/ratex-wasm/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  209. package/node_modules/ratex-wasm/fonts/KaTeX_Size3-Regular.ttf +0 -0
  210. package/node_modules/ratex-wasm/fonts/KaTeX_Size3-Regular.woff +0 -0
  211. package/node_modules/ratex-wasm/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  212. package/node_modules/ratex-wasm/fonts/KaTeX_Size4-Regular.ttf +0 -0
  213. package/node_modules/ratex-wasm/fonts/KaTeX_Size4-Regular.woff +0 -0
  214. package/node_modules/ratex-wasm/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  215. package/node_modules/ratex-wasm/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  216. package/node_modules/ratex-wasm/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  217. package/node_modules/ratex-wasm/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  218. package/node_modules/ratex-wasm/fonts.css +126 -0
  219. package/node_modules/ratex-wasm/package.json +50 -0
  220. package/node_modules/ratex-wasm/pkg/LICENSE +22 -0
  221. package/node_modules/ratex-wasm/pkg/package.json +22 -0
  222. package/node_modules/ratex-wasm/pkg/ratex_wasm.d.ts +75 -0
  223. package/node_modules/ratex-wasm/pkg/ratex_wasm.js +359 -0
  224. package/node_modules/ratex-wasm/pkg/ratex_wasm_bg.wasm +0 -0
  225. package/node_modules/ratex-wasm/pkg/ratex_wasm_bg.wasm.d.ts +10 -0
  226. package/node_modules/ratex-wasm/src/index.ts +223 -0
  227. package/node_modules/ratex-wasm/src/ratex-formula.ts +154 -0
  228. package/node_modules/ratex-wasm/src/renderer.ts +204 -0
  229. package/node_modules/ratex-wasm/src/types.ts +79 -0
  230. package/node_modules/uc.micro/LICENSE.txt +20 -0
  231. package/node_modules/uc.micro/README.md +31 -0
  232. package/node_modules/uc.micro/build/index.cjs.js +15 -0
  233. package/node_modules/uc.micro/build/index.d.ts +6 -0
  234. package/node_modules/uc.micro/build/index.mjs +8 -0
  235. package/node_modules/uc.micro/package.json +32 -0
  236. package/npm-shrinkwrap.json +4192 -0
  237. package/package.json +69 -4
  238. package/paseo-plugin.json +20 -0
  239. package/scripts/embed-assets.mjs +124 -0
  240. package/scripts/prepare-plugin.mjs +46 -0
  241. package/server/files.ts +176 -0
  242. package/server/glyphs.ts +108 -0
  243. package/server/ratex.ts +57 -0
  244. package/server/render.ts +69 -0
  245. package/server/scene.ts +165 -0
  246. package/server/vendor/katex-glyphs.ts +45 -0
  247. package/server/vendor/ratex-wasm-bytes.ts +3 -0
  248. package/shared/contracts.ts +77 -0
  249. package/shared/scene.ts +85 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ZedRover
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/OVERVIEW.md ADDED
@@ -0,0 +1,23 @@
1
+ RaTeX Markdown renders Markdown files with their LaTeX math typeset by RaTeX, a KaTeX-compatible math engine written in Rust. Inline math (`$…$`, `\(…\)`) sits on the text baseline, display math (`$$…$$`, `\[…\]`, `math` code fences) is centered and scrolls sideways when it is wider than the panel, and chemistry with `\ce` works too. The rest of the document renders as well: headings, lists and task lists, quotes, tables, code blocks, footnotes, YAML front matter and images.
2
+
3
+ ## Opening a file
4
+
5
+ On Paseo 0.13 and later, opening a `.md` or `.markdown` file offers **Markdown (RaTeX)** alongside Paseo's own Markdown and Source panels, and **Don't ask me again** makes it the default. On Paseo 0.11 and 0.12, which have no file panels, run **Open Markdown preview (RaTeX)** from the Command Center in a workspace and pick a file from the list. The open document follows edits on disk.
6
+
7
+ ## How it works
8
+
9
+ The daemon lays out each formula with the bundled `ratex-wasm` module and uses embedded KaTeX glyph outlines, so nothing is downloaded at runtime. The app draws each formula as a vector image that follows the Paseo theme. Selecting text across a formula copies its TeX source. A formula RaTeX cannot parse is shown as source with the error.
10
+
11
+ ## Setup and requirements
12
+
13
+ Paseo 0.11.0 or later. Installing from npm or Git needs Node.js and npm on the daemon host. The npm package includes its embedded math engine and glyph outlines; a Git installation prepares them from locked dependencies.
14
+
15
+ Desktop and web clients render formulas. iOS and Android clients render them where Paseo draws inline SVG icons (0.13 and later) and show the TeX source otherwise.
16
+
17
+ ## Data access
18
+
19
+ The daemon reads only Markdown and image files inside the directory of the workspace being viewed, after resolving symlinks, and refuses paths outside it. It makes no network requests. Images with `http` or `https` addresses in a document are loaded by the app directly. Links open in the system browser only for `http`, `https` and `mailto` addresses. Raw HTML in a document is never interpreted: HTML blocks show as source, and inline tags are either dropped with their text kept (formatting such as `<kbd>`) or shown as written.
20
+
21
+ ## Limitations
22
+
23
+ Code blocks are not syntax highlighted and Mermaid diagrams appear as code. Images inside links, such as badges, appear as their label. Characters outside the KaTeX fonts, such as CJK in `\text{}`, use the client's system fonts.
package/README.md CHANGED
@@ -1,3 +1,166 @@
1
- # Temporary Holding Version
1
+ # RaTeX Markdown for Paseo
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Open Markdown files in [Paseo](https://paseo.sh) with their LaTeX math typeset by
4
+ [RaTeX](https://github.com/erweixin/RaTeX), a KaTeX-compatible math engine written in Rust.
5
+
6
+ ![A Markdown document with inline and display math in the dark theme](assets/screenshot-dark.png)
7
+
8
+ ## Features
9
+
10
+ - Inline math with `$…$`, `\(…\)` and GitHub's `` $`…`$ ``; display math with `$$…$$`, `\[…\]`
11
+ and ```` ```math ```` fences. Prices such as `$5 and $10` stay text.
12
+ - Everything RaTeX supports: KaTeX math syntax, environments (`aligned`, `cases`, matrices),
13
+ chemistry with `\ce` and `\pu`, and `\color`.
14
+ - The rest of the document renders too: headings, emphasis, links, lists and task lists,
15
+ quotes, tables with alignment, code blocks, footnotes, YAML front matter and images.
16
+ - Formulas are vector images drawn from the KaTeX fonts, so they stay sharp at any zoom and
17
+ follow the Paseo theme. Wide equations scroll sideways instead of shrinking.
18
+ - Selecting text across a formula copies its TeX source.
19
+ - Edits on disk show up while the document is open.
20
+ - An invalid formula shows its source and RaTeX's error instead of breaking the page.
21
+
22
+ | Light theme, as a file panel | Phone width |
23
+ | --- | --- |
24
+ | ![The same document in the light theme](assets/screenshot-light.png) | ![The document on a narrow screen](assets/screenshot-compact.png) |
25
+
26
+ ## How it opens Markdown
27
+
28
+ Paseo's plugin API decides where the plugin can draw:
29
+
30
+ - **Paseo 0.13 and later** ([file panels](https://github.com/getpaseo/paseo/pull/7411), merged
31
+ after 0.12.0): opening a
32
+ `.md` or `.markdown` file offers **Markdown (RaTeX)** next to Paseo's own Markdown and Source
33
+ panels. Pick it and check **Don't ask me again** to make it the default for Markdown files.
34
+ - **Paseo 0.11 and 0.12**: press <kbd>⌘K</kbd> / <kbd>Ctrl+K</kbd> in a workspace and run
35
+ **Open Markdown preview (RaTeX)**. The panel lists the workspace's Markdown files; pick one to
36
+ read it. Relative links to other Markdown files open in the same panel.
37
+
38
+ The plugin detects file panel support at runtime, so the same release works on both.
39
+
40
+ ## Requirements
41
+
42
+ | Component | Requirement |
43
+ | --- | --- |
44
+ | Paseo | 0.11.0 or later, daemon and app |
45
+ | Daemon host | Node.js and npm on the daemon's `PATH`, for npm or Git installation |
46
+ | Clients | Desktop and web render formulas. iOS and Android render them on hosts whose `Icon` draws inline SVG (Paseo 0.13 and later) and show the TeX source on older ones. |
47
+
48
+ Plugins must be enabled on the daemon under **Settings → Plugins → Enable plugins**. Plugins
49
+ are trusted code: the server half runs on the daemon host and the client half inside Paseo.
50
+
51
+ ## Install
52
+
53
+ From npm:
54
+
55
+ ```bash
56
+ paseo plugin install npm:paseo-ratex-markdown
57
+ paseo plugin ls
58
+ ```
59
+
60
+ From GitHub (the daemon runs `npm ci` and generates the embedded assets during installation):
61
+
62
+ ```bash
63
+ paseo plugin add git:ZedRover/paseo-ratex-markdown
64
+ ```
65
+
66
+ You can also paste either source into **Settings → Plugins → Plugin source**. The plugin ID,
67
+ the npm package and the repository share one name: update with
68
+ `paseo plugin update paseo-ratex-markdown`.
69
+
70
+ ## Math syntax
71
+
72
+ | Written as | Renders as |
73
+ | --- | --- |
74
+ | `$x^2$`, `\(x^2\)`, `` $`x^2`$ `` | inline math |
75
+ | `$$ … $$` and `\[ … \]` on their own lines | display math |
76
+ | `$$x^2$$` inside a paragraph | display math between the paragraph's halves, as on GitHub |
77
+ | ```` ```math ```` fenced block | display math |
78
+
79
+ Rules that keep prose intact:
80
+
81
+ - An inline `$` must not be followed by a space, the closing `$` must not follow a space or
82
+ precede a digit, and a `$` that starts a word ends the attempt. `$5 and $10` is text.
83
+ Inline math may wrap onto the next line, as formatters such as Prettier wrap prose.
84
+ - `\$` is a literal dollar sign, and `\[1\]` inside a sentence is an escaped bracket, as in
85
+ CommonMark. Code spans and code blocks are never math.
86
+ - Display math cannot cross a blank line, so a stray `$$` does not swallow the document.
87
+ - Formulas longer than 4096 characters are shown as source.
88
+
89
+ ## How it works
90
+
91
+ ```text
92
+ Markdown text ──► markdown-it + math rules (client) ──► document tree
93
+ │ formulas, batched
94
+ ▼
95
+ daemon: RaTeX WASM lays out each formula (DisplayList)
96
+ precomputed KaTeX glyph outlines
97
+ │ scene: glyphs, rules, paths
98
+ ▼
99
+ web/desktop: one SVG <img> per formula iOS/Android: one inline SVG icon per glyph
100
+ ```
101
+
102
+ The daemon half embeds the `ratex-wasm` binary and KaTeX glyph outlines, so nothing is fetched at
103
+ runtime. During preparation, opentype.js extracts the outlines from the pinned fonts; the
104
+ runtime reads those paths directly without a font parser. Its SVG output matches RaTeX's own
105
+ `ratex-svg` renderer pixel for pixel on the test formulas. Layouts are cached on both sides,
106
+ so a theme change repaints without new requests,
107
+ and an edit only lays out the formulas that changed.
108
+
109
+ ## Privacy and data access
110
+
111
+ - The daemon reads only Markdown files (`.md`, `.markdown`) and images inside the workspace
112
+ directory that Paseo reports for the open workspace, after resolving symlinks. Paths that
113
+ leave the workspace are refused.
114
+ - Formulas are laid out on the daemon; nothing is sent to other services.
115
+ - Images with `http(s)` addresses load directly on the client, as in any Markdown viewer.
116
+ - Links open in the system browser only for `http`, `https` and `mailto`.
117
+ - Raw HTML is never interpreted. HTML blocks show as source; inline formatting tags such as
118
+ `<kbd>` or `<sup>` are dropped and their text kept, `<br>` breaks the line, `<img>` shows its
119
+ label, and any other tag shows as written.
120
+
121
+ ## Limitations
122
+
123
+ - Code blocks are not syntax highlighted, and Mermaid diagrams show as code.
124
+ - Inline images inside links or text (such as badges) show as their label; standalone images
125
+ render as blocks.
126
+ - Characters outside the KaTeX fonts, such as CJK inside `\text{}`, are drawn with the
127
+ client's system fonts.
128
+ - Paseo 0.12 and earlier have no file panels, so there documents open in the plugin's own
129
+ panel instead of the file tab.
130
+
131
+ ## Development
132
+
133
+ ```bash
134
+ npm ci # installs dependencies and embeds the RaTeX assets (npm run prepare)
135
+ npm run typecheck
136
+ npm test
137
+ paseo plugin install "$PWD" # try it on your local daemon
138
+ paseo plugin reload paseo-ratex-markdown
139
+ ```
140
+
141
+ `npm run test:browser` renders the plugin in Chrome with react-native-web, the way Paseo
142
+ evaluates client bundles. Install the browser tools outside the plugin and point at them:
143
+
144
+ ```bash
145
+ npm install --prefix /tmp/ratex-browser esbuild puppeteer-core react@19.1.0 react-dom@19.1.0 react-native-web@0.21 @tanstack/react-query zod lucide-static
146
+ RATEX_BROWSER_DEPS=/tmp/ratex-browser CHROME_BIN=/usr/bin/google-chrome npm run test:browser
147
+ ```
148
+
149
+ Set `CHROME_CDP_URL` instead of `CHROME_BIN` to use a running Chrome, `RATEX_SCREENSHOTS=dir`
150
+ to save screenshots, and `RATEX_CLIENT_BUNDLE=file` to test a bundle taken from a daemon's
151
+ plugin catalog.
152
+
153
+ `npm run check:package` checks the files `npm pack` would publish, including the size of
154
+ reachable runtime dependencies and declarations against the registry's 2 MB budget.
155
+
156
+ ## Credits
157
+
158
+ - [RaTeX](https://github.com/erweixin/RaTeX) by erweixin, MIT: parsing and layout (`ratex-wasm`).
159
+ - [KaTeX fonts](https://github.com/KaTeX/KaTeX), SIL Open Font License 1.1, shipped in `ratex-wasm`.
160
+ - [markdown-it](https://github.com/markdown-it/markdown-it) and
161
+ [markdown-it-footnote](https://github.com/markdown-it/markdown-it-footnote), MIT.
162
+ - [opentype.js](https://github.com/opentypejs/opentype.js), MIT.
163
+
164
+ ## License
165
+
166
+ [MIT](LICENSE)
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,10 @@
1
+ import * as sdk from "@getpaseo/plugin";
2
+
3
+ /**
4
+ * Whether the host's `Icon` draws inline SVG (`{ svg }`), the only way plugin code can draw
5
+ * vectors on iOS and Android. Paseo ships the SVG validator in the same release, so its export
6
+ * from the host's own copy of the SDK marks support.
7
+ */
8
+ export function hostDrawsSvgIcons(): boolean {
9
+ return typeof (sdk as Record<string, unknown>).findSvgIconProblem === "function";
10
+ }
@@ -0,0 +1,26 @@
1
+ import type { PluginTheme } from "@getpaseo/plugin";
2
+ import { createContext, useContext } from "react";
3
+ import type { DocumentStyles } from "./styles";
4
+
5
+ export type ImageResult = { uri: string } | { error: string };
6
+
7
+ /** What a hosting panel provides to the documents it shows. */
8
+ export type DocumentEnvironment = {
9
+ theme: PluginTheme;
10
+ layout: { compact: boolean; platform: "ios" | "android" | "web" };
11
+ /** Identifies the document, so relative image references are cached per document. */
12
+ documentId: string;
13
+ /** Reads a local image through the daemon; null where the panel cannot reach files. */
14
+ loadImage: ((src: string) => Promise<ImageResult>) | null;
15
+ openLink(href: string): void;
16
+ };
17
+
18
+ export type DocumentContextValue = DocumentEnvironment & { styles: DocumentStyles };
19
+
20
+ export const DocumentContext = createContext<DocumentContextValue | null>(null);
21
+
22
+ export function useDocument(): DocumentContextValue {
23
+ const value = useContext(DocumentContext);
24
+ if (!value) throw new Error("Markdown blocks must render inside a MarkdownDocument");
25
+ return value;
26
+ }
@@ -0,0 +1,295 @@
1
+ import { Icon } from "@getpaseo/plugin/client/react-native";
2
+ import { useMemo, type ReactNode } from "react";
3
+ import { ScrollView, Text, View } from "react-native";
4
+ import type { Align, Block, FrontMatter, Inline, ListItem } from "./markdown/ast";
5
+ import { parseMarkdown } from "./markdown/parse";
6
+ import { DocumentContext, useDocument, type DocumentContextValue, type DocumentEnvironment } from "./document-context";
7
+ import { DocumentImage } from "./image";
8
+ import { Formula, FormulaLoaderProvider } from "./math/formula";
9
+ import { BODY_FONT_SIZE, documentStyles, headingSize } from "./styles";
10
+
11
+ const BULLETS = ["•", "◦", "▪"];
12
+
13
+ /** A Markdown document with its math typeset by RaTeX. The hosting panel owns scrolling. */
14
+ export function MarkdownDocument({ source, environment }: { source: string; environment: DocumentEnvironment }) {
15
+ const tree = useMemo(() => parseMarkdown(source), [source]);
16
+ const { theme, layout } = environment;
17
+ const styles = useMemo(() => documentStyles(theme.colors, layout.compact), [theme, layout.compact]);
18
+ const context = useMemo<DocumentContextValue>(() => ({ ...environment, styles }), [environment, styles]);
19
+ return (
20
+ <FormulaLoaderProvider>
21
+ <DocumentContext.Provider value={context}>
22
+ <View style={styles.document}>
23
+ {tree.frontMatter ? <FrontMatterView frontMatter={tree.frontMatter} /> : null}
24
+ <Blocks blocks={tree.blocks} depth={0} />
25
+ </View>
26
+ </DocumentContext.Provider>
27
+ </FormulaLoaderProvider>
28
+ );
29
+ }
30
+
31
+ function Blocks({ blocks, depth, tight = false }: { blocks: Block[]; depth: number; tight?: boolean }) {
32
+ const { styles } = useDocument();
33
+ return (
34
+ <View style={tight ? styles.tightBlocks : styles.blocks}>
35
+ {blocks.map((block, index) => (
36
+ <BlockView key={index} block={block} depth={depth} />
37
+ ))}
38
+ </View>
39
+ );
40
+ }
41
+
42
+ function BlockView({ block, depth }: { block: Block; depth: number }) {
43
+ const context = useDocument();
44
+ const { styles, theme, layout } = context;
45
+ switch (block.type) {
46
+ case "heading":
47
+ return (
48
+ <View>
49
+ <Text selectable accessibilityRole="header" style={styles.heading(block.level)}>
50
+ {renderInlines(block.children, context, headingSize(block.level))}
51
+ </Text>
52
+ {block.level <= 2 ? <View style={styles.headingRule} /> : null}
53
+ </View>
54
+ );
55
+ case "paragraph":
56
+ return (
57
+ <Text selectable style={styles.paragraph}>
58
+ {renderInlines(block.children, context, BODY_FONT_SIZE)}
59
+ </Text>
60
+ );
61
+ case "math":
62
+ return (
63
+ <Formula
64
+ latex={block.latex}
65
+ source={block.source}
66
+ display
67
+ fontSize={BODY_FONT_SIZE}
68
+ color={theme.colors.foreground}
69
+ platform={layout.platform}
70
+ styles={styles}
71
+ />
72
+ );
73
+ case "image":
74
+ return <DocumentImage src={block.src} alt={block.alt} title={block.title} />;
75
+ case "code":
76
+ return <CodeBlock language={block.language} text={block.text} />;
77
+ case "blockquote":
78
+ return (
79
+ <View style={styles.blockquote}>
80
+ <Blocks blocks={block.children} depth={depth} />
81
+ </View>
82
+ );
83
+ case "list":
84
+ return (
85
+ <View style={block.loose ? styles.blocks : styles.tightBlocks}>
86
+ {block.items.map((item, index) => (
87
+ <View key={index} style={styles.listItem}>
88
+ <ListMarker item={item} label={block.ordered ? `${block.start + index}.` : BULLETS[depth % BULLETS.length]} />
89
+ <View style={styles.listContent}>
90
+ <Blocks blocks={item.children} depth={depth + 1} tight={!block.loose} />
91
+ </View>
92
+ </View>
93
+ ))}
94
+ </View>
95
+ );
96
+ case "table":
97
+ return <TableView align={block.align} header={block.header} rows={block.rows} />;
98
+ case "html":
99
+ return (
100
+ <Text selectable style={styles.html}>
101
+ {block.text}
102
+ </Text>
103
+ );
104
+ case "rule":
105
+ return <View style={styles.rule} />;
106
+ case "footnotes":
107
+ return (
108
+ <View style={styles.footnotes}>
109
+ <View style={styles.rule} />
110
+ {block.items.map((item) => (
111
+ <View key={item.label} style={styles.footnote}>
112
+ <Text style={styles.footnoteLabel}>{item.label}.</Text>
113
+ <View style={styles.listContent}>
114
+ <Blocks blocks={item.children} depth={depth} tight />
115
+ </View>
116
+ </View>
117
+ ))}
118
+ </View>
119
+ );
120
+ }
121
+ }
122
+
123
+ function ListMarker({ item, label }: { item: ListItem; label: string }) {
124
+ const { styles, theme } = useDocument();
125
+ return (
126
+ <View style={styles.listMarker}>
127
+ {item.checked === null ? (
128
+ <Text style={styles.listMarkerText}>{label}</Text>
129
+ ) : (
130
+ <View style={{ paddingTop: 4 }} accessibilityLabel={item.checked ? "Done" : "Not done"}>
131
+ <Icon
132
+ name={item.checked ? "SquareCheck" : "Square"}
133
+ size={16}
134
+ color={item.checked ? theme.colors.accent : theme.colors.foregroundMuted}
135
+ />
136
+ </View>
137
+ )}
138
+ </View>
139
+ );
140
+ }
141
+
142
+ function CodeBlock({ language, text }: { language: string; text: string }) {
143
+ const { styles } = useDocument();
144
+ return (
145
+ <View style={styles.codeBlock}>
146
+ {language ? <Text style={styles.codeLanguage}>{language}</Text> : null}
147
+ <ScrollView horizontal showsHorizontalScrollIndicator>
148
+ <Text selectable style={styles.codeText}>
149
+ {text}
150
+ </Text>
151
+ </ScrollView>
152
+ </View>
153
+ );
154
+ }
155
+
156
+ function TableView({ align, header, rows }: { align: Align[]; header: Inline[][]; rows: Inline[][][] }) {
157
+ const context = useDocument();
158
+ const { styles } = context;
159
+ const widths = useMemo(() => columnWidths(header, rows), [header, rows]);
160
+ const cell = (content: Inline[], column: number, heading: boolean, first: boolean) => (
161
+ <View
162
+ key={column}
163
+ style={[
164
+ styles.tableCell,
165
+ { width: widths[column] },
166
+ first && { borderTopWidth: 0 },
167
+ column === header.length - 1 && { borderRightWidth: 0 },
168
+ ]}
169
+ >
170
+ <Text selectable style={[heading ? styles.tableHeaderText : styles.paragraph, { textAlign: align[column] ?? "left" }]}>
171
+ {renderInlines(content, context, BODY_FONT_SIZE)}
172
+ </Text>
173
+ </View>
174
+ );
175
+ return (
176
+ <ScrollView horizontal showsHorizontalScrollIndicator>
177
+ <View style={styles.table}>
178
+ <View style={styles.tableHeaderRow}>{header.map((content, column) => cell(content, column, true, true))}</View>
179
+ {rows.map((row, index) => (
180
+ <View key={index} style={styles.tableRow}>
181
+ {header.map((_, column) => cell(row[column] ?? [], column, false, false))}
182
+ </View>
183
+ ))}
184
+ </View>
185
+ </ScrollView>
186
+ );
187
+ }
188
+
189
+ function FrontMatterView({ frontMatter }: { frontMatter: FrontMatter }) {
190
+ const { styles } = useDocument();
191
+ if (!frontMatter.rows) return <CodeBlock language="yaml" text={frontMatter.raw} />;
192
+ if (frontMatter.rows.length === 0) return null;
193
+ return (
194
+ <View style={styles.frontMatter}>
195
+ {frontMatter.rows.map((row, index) => (
196
+ <View key={index} style={[styles.frontMatterRow, index === 0 && { borderTopWidth: 0 }]}>
197
+ <View style={styles.frontMatterKey}>
198
+ <Text selectable style={styles.frontMatterKeyText}>
199
+ {row.key}
200
+ </Text>
201
+ </View>
202
+ <View style={styles.frontMatterValue}>
203
+ <Text selectable style={styles.paragraph}>
204
+ {row.value}
205
+ </Text>
206
+ </View>
207
+ </View>
208
+ ))}
209
+ </View>
210
+ );
211
+ }
212
+
213
+ function renderInlines(nodes: Inline[], context: DocumentContextValue, fontSize: number): ReactNode[] {
214
+ const { styles, theme, layout } = context;
215
+ return nodes.map((node, index) => {
216
+ switch (node.type) {
217
+ case "text":
218
+ return node.text;
219
+ case "softbreak":
220
+ return " ";
221
+ case "break":
222
+ return "\n";
223
+ case "strong":
224
+ case "em":
225
+ case "del":
226
+ return (
227
+ <Text key={index} style={styles[node.type]}>
228
+ {renderInlines(node.children, context, fontSize)}
229
+ </Text>
230
+ );
231
+ case "code":
232
+ return (
233
+ <Text key={index} style={styles.inlineCode}>
234
+ {node.text}
235
+ </Text>
236
+ );
237
+ case "link":
238
+ return (
239
+ <Text key={index} style={styles.link} accessibilityRole="link" onPress={() => context.openLink(node.href)}>
240
+ {renderInlines(node.children, context, fontSize)}
241
+ </Text>
242
+ );
243
+ case "image":
244
+ // Images nested in links or text (badges, icons) keep their place as their label.
245
+ return (
246
+ <Text key={index} style={styles.muted}>
247
+ {`[${node.alt || "image"}]`}
248
+ </Text>
249
+ );
250
+ case "math":
251
+ return (
252
+ <Formula
253
+ key={index}
254
+ latex={node.latex}
255
+ source={node.source}
256
+ display={false}
257
+ fontSize={fontSize}
258
+ color={theme.colors.foreground}
259
+ platform={layout.platform}
260
+ styles={styles}
261
+ />
262
+ );
263
+ case "footnoteRef":
264
+ return (
265
+ <Text key={index} style={styles.footnoteRef}>
266
+ {`[${node.label}]`}
267
+ </Text>
268
+ );
269
+ }
270
+ });
271
+ }
272
+
273
+ /** Columns sized from their longest cell, within limits; a wide table scrolls sideways. */
274
+ function columnWidths(header: Inline[][], rows: Inline[][][]): number[] {
275
+ const widths = header.map(() => 0);
276
+ for (const row of [header, ...rows]) {
277
+ row.forEach((cell, column) => {
278
+ if (column < widths.length) widths[column] = Math.max(widths[column], textLength(cell));
279
+ });
280
+ }
281
+ return widths.map((length) => Math.min(Math.max(length * 7.5 + 24, 64), 320));
282
+ }
283
+
284
+ function textLength(nodes: Inline[]): number {
285
+ let length = 0;
286
+ for (const node of nodes) {
287
+ if (node.type === "text") length += node.text.length;
288
+ else if (node.type === "code") length += node.text.length * 1.1;
289
+ else if (node.type === "math") length += node.latex.length * 0.6;
290
+ else if (node.type === "image") length += node.alt.length + 2;
291
+ else if ("children" in node) length += textLength(node.children);
292
+ else length += 1;
293
+ }
294
+ return length;
295
+ }