├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── img ├── 1.png ├── 2.png ├── 3.png └── 4.png ├── test ├── demo.pdf ├── themes.pdf ├── test.txt ├── demo.typ ├── themes.typ └── color.txt ├── typst.toml ├── CHANGELOG.md ├── LICENSE ├── .github └── workflows │ └── update-package.yml ├── README.md └── ansi-render.typ /.gitignore: -------------------------------------------------------------------------------- 1 | ansi_render.pdf -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2 3 | } -------------------------------------------------------------------------------- /img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gobidev/typst-ansi-render/master/img/1.png -------------------------------------------------------------------------------- /img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gobidev/typst-ansi-render/master/img/2.png -------------------------------------------------------------------------------- /img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gobidev/typst-ansi-render/master/img/3.png -------------------------------------------------------------------------------- /img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gobidev/typst-ansi-render/master/img/4.png -------------------------------------------------------------------------------- /test/demo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gobidev/typst-ansi-render/master/test/demo.pdf -------------------------------------------------------------------------------- /test/themes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gobidev/typst-ansi-render/master/test/themes.pdf -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ansi-render" 3 | version = "0.4.1" 4 | entrypoint = "ansi-render.typ" 5 | authors = ["8LWXpg"] 6 | license = "MIT" 7 | description = "provides a simple way to render text with ANSI escape sequences." 8 | repository = "https://github.com/8LWXpg/typst-ansi-render" 9 | -------------------------------------------------------------------------------- /test/test.txt: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------- 2 | NameError Traceback (most recent call last) 3 | Cell In[9], line 1 4 | ----> 1 this_will_error 5 | 6 | NameError: name 'this_will_error' is not defined -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "watch", 8 | "type": "shell", 9 | "command": "typst", 10 | "args": [ 11 | "w", 12 | "test/demo.typ", 13 | "--root", 14 | "${workspaceFolder}" 15 | ], 16 | "group": { 17 | "kind": "build", 18 | "isDefault": true 19 | } 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 📝 2 | 3 | ## [0.4.1] - 2023-09-22 4 | 5 | ### Changed 6 | 7 | * Changed default font size to `9pt` 8 | * Prevent set from outside of the function 9 | 10 | ## [0.4.0] - 2023-09-13 11 | 12 | ### Added 13 | 14 | * Added most options from [`block`]([https://](https://typst.app/docs/reference/layout/block/)) function 15 | * Added `vscode-light` theme 16 | 17 | ### Changed 18 | 19 | * Changed outmost layout from `rect` to `block` 20 | * Changed default theme to `VSCode Light` 21 | 22 | ## [0.3.0] - 2023-09-09 23 | 24 | ### Added 25 | 26 | * Added `radius` option, default is `3pt` 27 | 28 | ### Changed 29 | 30 | * Changed default font size to `10pt` 31 | * Changed default font to `Cascadia Code` 32 | * Changed default theme to `Solarized Light` 33 | 34 | ## [0.2.0] 2023-08-05 35 | 36 | ### Changed 37 | 38 | * Changed coding style to kebab-case and two spaces 39 | 40 | ## [0.1.0] 2023-07-02 41 | 42 | first release 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 8LWXpg 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. 22 | -------------------------------------------------------------------------------- /.github/workflows/update-package.yml: -------------------------------------------------------------------------------- 1 | name: Update package 2 | 3 | # push a new tag before running this action 4 | on: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | push: 9 | name: push to package 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | with: 14 | fetch-depth: 0 15 | 16 | - name: get latest tag 17 | id: latest_tag 18 | uses: WyriHaximus/github-action-get-previous-tag@v1 19 | 20 | - name: set tag 21 | id: parsed_tag 22 | run: | 23 | version=${{ steps.latest_tag.outputs.tag }} 24 | echo "tag=${version:1}" >> $GITHUB_OUTPUT 25 | 26 | - name: push 27 | uses: nkoppel/push-files-to-another-repository@v1.1.1 28 | # repo PAT 29 | env: 30 | API_TOKEN_GITHUB: ${{ secrets.PERSONAL_TOKEN }} 31 | with: 32 | commit-email: v3ak6xhthk@gmail.com 33 | source-files: ansi-render.typ CHANGELOG.md LICENSE README.md typst.toml 34 | destination-username: ${{ github.repository_owner }} 35 | destination-repository: packages 36 | destination-directory: packages/preview/ansi-render/${{ steps.parsed_tag.outputs.tag }} 37 | destination-branch: main 38 | -------------------------------------------------------------------------------- /test/demo.typ: -------------------------------------------------------------------------------- 1 | #import "../ansi-render.typ": * 2 | 3 | = Render text directly 4 | #ansi-render( 5 | "\u{1b}[38;2;255;0;0mThis text is red.\u{1b}[0m 6 | \u{1b}[48;2;0;255;0mThis background is green.\u{1b}[0m 7 | \u{1b}[38;2;255;255;255m\u{1b}[48;2;0;0;255mThis text is white on a blue background.\u{1b}[0m 8 | \u{1b}[1mThis text is bold.\u{1b}[0m 9 | \u{1b}[4mThis text is underlined.\u{1b}[0m 10 | \u{1b}[38;2;255;165;0m\u{1b}[48;2;255;255;0mThis text is orange on a yellow background.\u{1b}[0m 11 | ", 12 | inset: 5pt, 13 | radius: 3pt, 14 | theme: terminal-themes.vscode 15 | ) 16 | 17 | #ansi-render( 18 | "\u{1b}[38;5;196mRed text\u{1b}[0m 19 | \u{1b}[48;5;27mBlue background\u{1b}[0m 20 | \u{1b}[38;5;226;48;5;18mYellow text on blue background\u{1b}[0m 21 | \u{1b}[7mInverted text\u{1b}[0m 22 | \u{1b}[38;5;208;48;5;237mOrange text on gray background\u{1b}[0m 23 | \u{1b}[38;5;39;48;5;208mBlue text on orange background\u{1b}[0m 24 | \u{1b}[38;5;255;48;5;0mWhite text on black background\u{1b}[0m", 25 | inset: 5pt, 26 | radius: 3pt, 27 | theme: terminal-themes.vscode 28 | ) 29 | 30 | #ansi-render( 31 | "\u{1b}[31;1mHello \u{1b}[7mWorld\u{1b}[0m 32 | 33 | \u{1b}[53;4;36mOver and \u{1b}[35m Under! 34 | \u{1b}[7;90mreverse\u{1b}[101m and \u{1b}[94;27mreverse", 35 | inset: 5pt, 36 | radius: 3pt, 37 | theme: terminal-themes.vscode 38 | ) 39 | 40 | = Render text from a file 41 | #ansi-render(read("test.txt"), inset: 5pt, radius: 3pt) 42 | 43 | = Uses the font supports ligatures 44 | #ansi-render(read("test.txt"), font: "CaskaydiaCove Nerd Font Mono", inset: 5pt, radius: 3pt, theme: terminal-themes.putty) 45 | -------------------------------------------------------------------------------- /test/themes.typ: -------------------------------------------------------------------------------- 1 | #import "../ansi-render.typ": * 2 | 3 | = List of built-in themes 4 | == VSCode 5 | #ansi-render(read("color.txt"), size: 8pt, inset: 5pt, radius: 3pt, theme: terminal-themes.vscode) 6 | == VSCode Light 7 | #ansi-render(read("color.txt"), size: 8pt, inset: 5pt, radius: 3pt, theme: terminal-themes.vscode-light) 8 | == Putty 9 | #ansi-render(read("color.txt"), size: 8pt, inset: 5pt, radius: 3pt, theme: terminal-themes.putty) 10 | == Campbell 11 | #ansi-render(read("color.txt"), size: 8pt, inset: 5pt, radius: 3pt, theme: terminal-themes.campbell) 12 | == Campbell Powershell 13 | #ansi-render(read("color.txt"), size: 8pt, inset: 5pt, radius: 3pt, theme: terminal-themes.campbell-powershell) 14 | == Vintage 15 | #ansi-render(read("color.txt"), size: 8pt, inset: 5pt, radius: 3pt, theme: terminal-themes.vintage) 16 | == One Half Dark 17 | #ansi-render(read("color.txt"), size: 8pt, inset: 5pt, radius: 3pt, theme: terminal-themes.one-half-dark) 18 | == One Half Light 19 | #ansi-render(read("color.txt"), size: 8pt, inset: 5pt, radius: 3pt, theme: terminal-themes.one-half-light) 20 | == Solarized Dark 21 | #ansi-render(read("color.txt"), size: 8pt, inset: 5pt, radius: 3pt, theme: terminal-themes.solarized-dark) 22 | == Solarized Light 23 | #ansi-render(read("color.txt"), size: 8pt, inset: 5pt, radius: 3pt, theme: terminal-themes.solarized-light) 24 | == Tango Dark 25 | #ansi-render(read("color.txt"), size: 8pt, inset: 5pt, radius: 3pt, theme: terminal-themes.tango-dark) 26 | == Tango Light 27 | #ansi-render(read("color.txt"), size: 8pt, inset: 5pt, radius: 3pt, theme: terminal-themes.tango-light) -------------------------------------------------------------------------------- /test/color.txt: -------------------------------------------------------------------------------- 1 | 40m 41m 42m 43m 44m 45m 46m 47m 2 | 30m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  3 | 31m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  4 | 32m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  5 | 33m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  6 | 34m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  7 | 35m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  8 | 36m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  9 | 37m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  10 | 11 | 100m 101m 102m 103m 104m 105m 106m 107m 12 | 90m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  13 | 91m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  14 | 92m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  15 | 93m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  16 | 94m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  17 | 95m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  18 | 96m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  19 | 97m  gYw   gYw   gYw   gYw   gYw   gYw   gYw   gYw  20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ANSI Escape Sequence Renderer 2 | 3 | 4 | GitHub manifest version (path) 5 | 6 | 7 | GitHub Repo stars 8 | 9 | 10 | GitHub 11 | 12 | 13 | This script provides a simple way to render text with ANSI escape sequences. Package `ansi-render` provides a function `ansi-render`, and a dictionary of themes `terminal-themes`. 14 | 15 | contribution is welcomed! 16 | 17 | ## Usage 18 | 19 | ```typst 20 | #import "@preview/ansi-render:0.4.1": * 21 | 22 | #ansi-render( 23 | string, 24 | font: string, 25 | size: length, 26 | width: auto or relative length, 27 | height: auto or relative length, 28 | breakable: boolean, 29 | radius: relative length or dictionary, 30 | inset: relative length or dictionary, 31 | outset: relative length or dictionary, 32 | spacing: relative length or fraction, 33 | above: relative length or fraction, 34 | below: relative length or fraction, 35 | clip: boolean, 36 | theme: terminal-themes.theme, 37 | ) 38 | ``` 39 | 40 | ### Parameters 41 | 42 | most parameters comes from [`block`]([https://](https://typst.app/docs/reference/layout/block/)) function, adjust the layout outmost block. 43 | 44 | - `string` - string with ANSI escape sequences 45 | - `font` - font name, default is `Cascadia Code` 46 | - `size` - font size, default is `9pt` 47 | - `theme` - theme, default is `vscode-light` 48 | - parameters from [`block`]([https://](https://typst.app/docs/reference/layout/block/)) function with the same default value, change to adjust the outmost layout: 49 | - `width` 50 | - `height` 51 | - `breakable` 52 | - `radius` 53 | - `inset` 54 | - `outset` 55 | - `spacing` 56 | - `above` 57 | - `below` 58 | - `clip` 59 | 60 | ## Themes 61 | 62 | see [themes](https://github.com/8LWXpg/typst-ansi-render/blob/master/test/themes.pdf) 63 | 64 | ## Demo 65 | 66 | see [demo.typ](https://github.com/8LWXpg/typst-ansi-render/blob/master/test/demo.typ) [demo.pdf](https://github.com/8LWXpg/typst-ansi-render/blob/master/test/demo.pdf) 67 | 68 | ```typst 69 | #ansi-render( 70 | "\u{1b}[38;2;255;0;0mThis text is red.\u{1b}[0m 71 | \u{1b}[48;2;0;255;0mThis background is green.\u{1b}[0m 72 | \u{1b}[38;2;255;255;255m\u{1b}[48;2;0;0;255mThis text is white on a blue background.\u{1b}[0m 73 | \u{1b}[1mThis text is bold.\u{1b}[0m 74 | \u{1b}[4mThis text is underlined.\u{1b}[0m 75 | \u{1b}[38;2;255;165;0m\u{1b}[48;2;255;255;0mThis text is orange on a yellow background.\u{1b}[0m 76 | ", 77 | theme: terminal-themes.vscode 78 | ) 79 | ``` 80 | 81 | ![1.png](https://github.com/8LWXpg/typst-ansi-render/blob/master/img/1.png) 82 | 83 | ```typst 84 | #ansi-render( 85 | "\u{1b}[38;5;196mRed text\u{1b}[0m 86 | \u{1b}[48;5;27mBlue background\u{1b}[0m 87 | \u{1b}[38;5;226;48;5;18mYellow text on blue background\u{1b}[0m 88 | \u{1b}[7mInverted text\u{1b}[0m 89 | \u{1b}[38;5;208;48;5;237mOrange text on gray background\u{1b}[0m 90 | \u{1b}[38;5;39;48;5;208mBlue text on orange background\u{1b}[0m 91 | \u{1b}[38;5;255;48;5;0mWhite text on black background\u{1b}[0m", 92 | theme: terminal-themes.vscode 93 | ) 94 | ``` 95 | 96 | ![2.png](https://github.com/8LWXpg/typst-ansi-render/blob/master/img/2.png) 97 | 98 | ```typst 99 | #ansi-render( 100 | "\u{1b}[31;1mHello \u{1b}[7mWorld\u{1b}[0m 101 | 102 | \u{1b}[53;4;36mOver and \u{1b}[35m Under! 103 | \u{1b}[7;90mreverse\u{1b}[101m and \u{1b}[94;27mreverse", 104 | theme: terminal-themes.vscode 105 | ) 106 | ``` 107 | 108 | ![3.png](https://github.com/8LWXpg/typst-ansi-render/blob/master/img/3.png) 109 | 110 | ```typst 111 | // uses the font that supports ligatures 112 | #ansi-render(read("test.txt"), font: "CaskaydiaCove Nerd Font Mono", theme: terminal-themes.putty) 113 | ``` 114 | 115 | ![4.png](https://github.com/8LWXpg/typst-ansi-render/blob/master/img/4.png) 116 | -------------------------------------------------------------------------------- /ansi-render.typ: -------------------------------------------------------------------------------- 1 | // add your theme here! 2 | #let terminal-themes = ( 3 | // vscode terminal theme 4 | vscode: ( 5 | black: rgb(0, 0, 0), 6 | red: rgb(205, 49, 49), 7 | green: rgb(13, 188, 121), 8 | yellow: rgb(229, 229, 16), 9 | blue: rgb(36, 114, 200), 10 | magenta: rgb(188, 63, 188), 11 | cyan: rgb(17, 168, 205), 12 | white: rgb(229, 229, 229), 13 | gray: rgb(102, 102, 102), 14 | bright-red: rgb(214, 76, 76), 15 | bright-green: rgb(35, 209, 139), 16 | bright-yellow: rgb(245, 245, 67), 17 | bright-blue: rgb(59, 142, 234), 18 | bright-magenta: rgb(214, 112, 214), 19 | bright-cyan: rgb(41, 184, 219), 20 | bright-white: rgb(229, 229, 229), 21 | default-text: rgb(229, 229, 229), // white 22 | default-bg: rgb(0, 0, 0), // black 23 | ), 24 | 25 | // vscode light theme 26 | vscode-light: ( 27 | black: rgb("#F8F8F8"), 28 | red: rgb("#CD3131"), 29 | green: rgb("#00BC00"), 30 | yellow: rgb("#949800"), 31 | blue: rgb("#0451A5"), 32 | magenta: rgb("#BC05BC"), 33 | cyan: rgb("#0598BC"), 34 | white: rgb("#555555"), 35 | gray: rgb("#666666"), 36 | bright-red: rgb("#CD3131"), 37 | bright-green: rgb("#14CE14"), 38 | bright-yellow: rgb("#B5BA00"), 39 | bright-blue: rgb("#0451A5"), 40 | bright-magenta: rgb("#BC05BC"), 41 | bright-cyan: rgb("#0598BC"), 42 | bright-white: rgb("#A5A5A5"), 43 | default-text: rgb("#A5A5A5"), // white 44 | default-bg: rgb("#F8F8F8"), // black 45 | ), 46 | 47 | // putty terminal theme 48 | putty: ( 49 | black: rgb(0, 0, 0), 50 | red: rgb(187, 0, 0), 51 | green: rgb(0, 187, 0), 52 | yellow: rgb(187, 187, 0), 53 | blue: rgb(0, 0, 187), 54 | magenta: rgb(187, 0, 187), 55 | cyan: rgb(0, 187, 187), 56 | white: rgb(187, 187, 187), 57 | gray: rgb(85, 85, 85), 58 | bright-red: rgb(255, 0, 0), 59 | bright-green: rgb(0, 255, 0), 60 | bright-yellow: rgb(255, 255, 0), 61 | bright-blue: rgb(0, 0, 255), 62 | bright-magenta: rgb(255, 0, 255), 63 | bright-cyan: rgb(0, 255, 255), 64 | bright-white: rgb(255, 255, 255), 65 | default-text: rgb(187, 187, 187), // white 66 | default-bg: rgb(0, 0, 0), // black 67 | ), 68 | 69 | // themes from Windows Terminal 70 | campbell: ( 71 | black: rgb("#0C0C0C"), 72 | red: rgb("#C50F1F"), 73 | green: rgb("#13A10E"), 74 | yellow: rgb("#C19C00"), 75 | blue: rgb("#0037DA"), 76 | magenta: rgb("#881798"), 77 | cyan: rgb("#3A96DD"), 78 | white: rgb("#CCCCCC"), 79 | gray: rgb("#767676"), 80 | bright-red: rgb("#E74856"), 81 | bright-green: rgb("#16C60C"), 82 | bright-yellow: rgb("#F9F1A5"), 83 | bright-blue: rgb("#3B78FF"), 84 | bright-magenta: rgb("#B4009E"), 85 | bright-cyan: rgb("#61D6D6"), 86 | bright-white: rgb("#F2F2F2"), 87 | default-text: rgb("#CCCCCC"), 88 | default-bg: rgb("#0C0C0C"), 89 | ), 90 | 91 | campbell-powershell: ( 92 | black: rgb("#0C0C0C"), 93 | red: rgb("#C50F1F"), 94 | green: rgb("#13A10E"), 95 | yellow: rgb("#C19C00"), 96 | blue: rgb("#0037DA"), 97 | magenta: rgb("#881798"), 98 | cyan: rgb("#3A96DD"), 99 | white: rgb("#CCCCCC"), 100 | gray: rgb("#767676"), 101 | bright-red: rgb("#E74856"), 102 | bright-green: rgb("#16C60C"), 103 | bright-yellow: rgb("#F9F1A5"), 104 | bright-blue: rgb("#3B78FF"), 105 | bright-magenta: rgb("#B4009E"), 106 | bright-cyan: rgb("#61D6D6"), 107 | bright-white: rgb("#F2F2F2"), 108 | default-text: rgb("#CCCCCC"), 109 | default-bg: rgb("#012456"), 110 | ), 111 | 112 | vintage: ( 113 | black: rgb("#000000"), 114 | red: rgb("#800000"), 115 | green: rgb("#008000"), 116 | yellow: rgb("#808000"), 117 | blue: rgb("#000080"), 118 | magenta: rgb("#800080"), 119 | cyan: rgb("#008080"), 120 | white: rgb("#C0C0C0"), 121 | gray: rgb("#808080"), 122 | bright-red: rgb("#FF0000"), 123 | bright-green: rgb("#00FF00"), 124 | bright-yellow: rgb("#FFFF00"), 125 | bright-blue: rgb("#0000FF"), 126 | bright-magenta: rgb("#FF00FF"), 127 | bright-cyan: rgb("#00FFFF"), 128 | bright-white: rgb("#FFFFFF"), 129 | default-text: rgb("#C0C0C0"), 130 | default-bg: rgb("#000000"), 131 | ), 132 | 133 | one-half-dark: ( 134 | black: rgb("#282C34"), 135 | red: rgb("#E06C75"), 136 | green: rgb("#98C379"), 137 | yellow: rgb("#E5C07B"), 138 | blue: rgb("#61AFEF"), 139 | magenta: rgb("#C678DD"), 140 | cyan: rgb("#56B6C2"), 141 | white: rgb("#DCDFE4"), 142 | gray: rgb("#5A6374"), 143 | bright-red: rgb("#E06C75"), 144 | bright-green: rgb("#98C379"), 145 | bright-yellow: rgb("#E5C07B"), 146 | bright-blue: rgb("#61AFEF"), 147 | bright-magenta: rgb("#C678DD"), 148 | bright-cyan: rgb("#56B6C2"), 149 | bright-white: rgb("#DCDFE4"), 150 | default-text: rgb("#DCDFE4"), 151 | default-bg: rgb("#282C34"), 152 | ), 153 | 154 | one-half-light: ( 155 | black: rgb("#383A42"), 156 | red: rgb("#E45649"), 157 | green: rgb("#50A14F"), 158 | yellow: rgb("#C18301"), 159 | blue: rgb("#0184BC"), 160 | magenta: rgb("#A626A4"), 161 | cyan: rgb("#0997B3"), 162 | white: rgb("#FAFAFA"), 163 | gray: rgb("#4F525D"), 164 | bright-red: rgb("#DF6C75"), 165 | bright-green: rgb("#98C379"), 166 | bright-yellow: rgb("#E4C07A"), 167 | bright-blue: rgb("#61AFEF"), 168 | bright-magenta: rgb("#C577DD"), 169 | bright-cyan: rgb("#56B5C1"), 170 | bright-white: rgb("#FFFFFF"), 171 | default-text: rgb("#383A42"), 172 | default-bg: rgb("#FAFAFA"), 173 | ), 174 | 175 | solarized-dark: ( 176 | black: rgb("#002B36"), 177 | red: rgb("#DC322F"), 178 | green: rgb("#859900"), 179 | yellow: rgb("#B58900"), 180 | blue: rgb("#268BD2"), 181 | magenta: rgb("#D33682"), 182 | cyan: rgb("#2AA198"), 183 | white: rgb("#EEE8D5"), 184 | gray: rgb("#073642"), 185 | bright-red: rgb("#CB4B16"), 186 | bright-green: rgb("#586E75"), 187 | bright-yellow: rgb("#657B83"), 188 | bright-blue: rgb("#839496"), 189 | bright-magenta: rgb("#6C71C4"), 190 | bright-cyan: rgb("#93A1A1"), 191 | bright-white: rgb("#FDF6E3"), 192 | default-text: rgb("#839496"), 193 | default-bg: rgb("#002B36"), 194 | ), 195 | 196 | solarized-light: ( 197 | black: rgb("#002B36"), 198 | red: rgb("#DC322F"), 199 | green: rgb("#859900"), 200 | yellow: rgb("#B58900"), 201 | blue: rgb("#268BD2"), 202 | magenta: rgb("#D33682"), 203 | cyan: rgb("#2AA198"), 204 | white: rgb("#EEE8D5"), 205 | gray: rgb("#073642"), 206 | bright-red: rgb("#CB4B16"), 207 | bright-green: rgb("#586E75"), 208 | bright-yellow: rgb("#657B83"), 209 | bright-blue: rgb("#839496"), 210 | bright-magenta: rgb("#6C71C4"), 211 | bright-cyan: rgb("#93A1A1"), 212 | bright-white: rgb("#FDF6E3"), 213 | default-text: rgb("#657B83"), 214 | default-bg: rgb("#FDF6E3"), 215 | ), 216 | 217 | tango-dark: ( 218 | black: rgb("#000000"), 219 | red: rgb("#CC0000"), 220 | green: rgb("#4E9A06"), 221 | yellow: rgb("#C4A000"), 222 | blue: rgb("#3465A4"), 223 | magenta: rgb("#75507B"), 224 | cyan: rgb("#06989A"), 225 | white: rgb("#D3D7CF"), 226 | gray: rgb("#555753"), 227 | bright-red: rgb("#EF2929"), 228 | bright-green: rgb("#8AE234"), 229 | bright-yellow: rgb("#FCE94F"), 230 | bright-blue: rgb("#729FCF"), 231 | bright-magenta: rgb("#AD7FA8"), 232 | bright-cyan: rgb("#34E2E2"), 233 | bright-white: rgb("#EEEEEC"), 234 | default-text: rgb("#D3D7CF"), 235 | default-bg: rgb("#000000"), 236 | ), 237 | 238 | tango-light: ( 239 | black: rgb("#000000"), 240 | red: rgb("#CC0000"), 241 | green: rgb("#4E9A06"), 242 | yellow: rgb("#C4A000"), 243 | blue: rgb("#3465A4"), 244 | magenta: rgb("#75507B"), 245 | cyan: rgb("#06989A"), 246 | white: rgb("#D3D7CF"), 247 | gray: rgb("#555753"), 248 | bright-red: rgb("#EF2929"), 249 | bright-green: rgb("#8AE234"), 250 | bright-yellow: rgb("#FCE94F"), 251 | bright-blue: rgb("#729FCF"), 252 | bright-magenta: rgb("#AD7FA8"), 253 | bright-cyan: rgb("#34E2E2"), 254 | bright-white: rgb("#EEEEEC"), 255 | default-text: rgb("#555753"), 256 | default-bg: rgb("#FFFFFF"), 257 | ), 258 | ) 259 | 260 | // ansi rendering function 261 | #let ansi-render( 262 | body, 263 | font: "Cascadia Code", 264 | size: 9pt, 265 | width: auto, 266 | height: auto, 267 | breakable: true, 268 | radius: 0pt, 269 | inset: 0pt, 270 | outset: 0pt, 271 | spacing: 1.2em, 272 | above: 1.2em, 273 | below: 1.2em, 274 | clip: false, 275 | theme: terminal-themes.vscode-light) = { 276 | // dict with text style 277 | let match-text = ( 278 | "1": (weight: "bold"), 279 | "3": (style: "italic"), 280 | "23": (style: "normal"), 281 | "30": (fill: theme.black), 282 | "31": (fill: theme.red), 283 | "32": (fill: theme.green), 284 | "33": (fill: theme.yellow), 285 | "34": (fill: theme.blue), 286 | "35": (fill: theme.magenta), 287 | "36": (fill: theme.cyan), 288 | "37": (fill: theme.white), 289 | "39": (fill: theme.default-text), 290 | "90": (fill: theme.gray), 291 | "91": (fill: theme.bright-red), 292 | "92": (fill: theme.bright-green), 293 | "93": (fill: theme.bright-yellow), 294 | "94": (fill: theme.bright-blue), 295 | "95": (fill: theme.bright-magenta), 296 | "96": (fill: theme.bright-cyan), 297 | "97": (fill: theme.bright-white), 298 | "default": (weight: "regular", style: "normal", fill: theme.default-text) 299 | ) 300 | // dict with background style 301 | let match-bg = ( 302 | "40": (fill: theme.black), 303 | "41": (fill: theme.red), 304 | "42": (fill: theme.green), 305 | "43": (fill: theme.yellow), 306 | "44": (fill: theme.blue), 307 | "45": (fill: theme.magenta), 308 | "46": (fill: theme.cyan), 309 | "47": (fill: theme.white), 310 | "49": (fill: theme.default-bg), 311 | "100": (fill: theme.gray), 312 | "101": (fill: theme.bright-red), 313 | "102": (fill: theme.bright-green), 314 | "103": (fill: theme.bright-yellow), 315 | "104": (fill: theme.bright-blue), 316 | "105": (fill: theme.bright-magenta), 317 | "106": (fill: theme.bright-cyan), 318 | "107": (fill: theme.bright-white), 319 | "default": (fill: theme.default-bg) 320 | ) 321 | 322 | let match-options(opt) = { 323 | // parse 38;5 48;5 324 | let parse-8bit-color(num) = { 325 | num = int(num) 326 | let colors = (0, 95, 135, 175, 215, 255) 327 | if num <= 7 { match-text.at(str(num+30)) } 328 | else if num <= 15 { match-bg.at(str(num+32)) } 329 | else if num <= 231 { 330 | num -= 16 331 | let (r, g, b) = (colors.at(int(num/36)), colors.at(calc.rem(int(num/6),6)), colors.at(calc.rem(num,6))) 332 | (fill: rgb(r, g, b)) 333 | } else { 334 | num -= 232 335 | let (r, g, b) = (8+10*num, 8+10*num, 8+10*num) 336 | (fill: rgb(r, g, b)) 337 | } 338 | } 339 | 340 | let (opt-text, opt-bg) = ((:), (:)) 341 | let (ul, ol, reverse, last) = (none, none, none, none) 342 | let count = 0 343 | let color = (0, 0, 0) 344 | 345 | // match options 346 | for i in opt { 347 | if last == "382" or last =="482" { 348 | color.at(count) = int(i) 349 | count += 1 350 | if count == 3 { 351 | if last == "382" { opt-text += (fill: rgb(..color)) } 352 | else { opt-bg += (fill: rgb(..color)) } 353 | count = 0 354 | last = none 355 | } 356 | continue 357 | } 358 | else if last == "385" { 359 | opt-text += parse-8bit-color(i) 360 | last = none 361 | continue 362 | } 363 | else if last == "485" { 364 | opt-bg += parse-8bit-color(i) 365 | last = none 366 | continue 367 | } 368 | else if i == "0" { 369 | opt-text += match-text.default 370 | opt-bg += match-bg.default 371 | ul = false 372 | ol = false 373 | reverse = false 374 | } 375 | else if i in match-bg.keys() { opt-bg += match-bg.at(i) } 376 | else if i in match-text.keys() { opt-text += match-text.at(i) } 377 | else if i == "4" { ul = true } 378 | else if i == "24" { ul = false } 379 | else if i == "53" { ol = true } 380 | else if i == "55" { ol = false } 381 | else if i == "7" { reverse = true } 382 | else if i == "27" { reverse = false } 383 | else if i == "38" or i == "48" { 384 | last = i 385 | continue 386 | } 387 | else if i == "2" or i == "5" { 388 | if last == "38" or last == "48" { 389 | last += i 390 | count = 0 391 | continue 392 | } 393 | } 394 | last = none 395 | } 396 | (text: opt-text, bg: opt-bg, ul: ul, ol: ol, reverse: reverse) 397 | } 398 | 399 | let parse-option(body) = { 400 | let arr = () 401 | let cur = 0 402 | for map in body.matches(regex("\x1b\[([0-9;]*)m([^\x1b]*)")) { 403 | // loop through all matches 404 | let str = map.captures.at(1) 405 | // split the string by newline and preserve newline 406 | let split = str.split("\n") 407 | for (k, v) in split.enumerate() { 408 | if k != split.len()-1 { 409 | v = v + "\n" 410 | } 411 | let temp = (v, ()) 412 | for option in map.captures.at(0).split(";") { 413 | temp.at(1).push(option) 414 | } 415 | arr.push(temp) 416 | } 417 | cur += 1 418 | } 419 | arr 420 | } 421 | 422 | set text(..(match-text.default), font: font, size: size, top-edge: "ascender", bottom-edge: "descender") 423 | set par(leading: 0em) 424 | 425 | let option = ( 426 | text: match-text.default, 427 | bg: match-bg.default, 428 | ul: false, 429 | ol: false, 430 | reverse: false 431 | ) 432 | show: block.with( 433 | ..(match-bg.default), 434 | width: width, 435 | height: height, 436 | breakable: breakable, 437 | radius: radius, 438 | inset: inset, 439 | outset: outset, 440 | spacing: spacing, 441 | above: above, 442 | below: below, 443 | clip: clip, 444 | ) 445 | // prevent set from outside of the function 446 | set box( 447 | width: auto, 448 | height: auto, 449 | baseline: 0pt, 450 | fill: none, 451 | stroke: none, 452 | radius: 0pt, 453 | inset: 0pt, 454 | outset: 0pt, 455 | clip: false, 456 | ) 457 | // work around for rendering first line without escape sequence 458 | body = "\u{1b}[0m" + body 459 | for (str, opt) in parse-option(body) { 460 | let m = match-options(opt) 461 | option.text += m.text 462 | option.bg += m.bg 463 | if m.reverse != none { option.reverse = m.reverse } 464 | if option.reverse { (option.text.fill, option.bg.fill) = (option.bg.fill, option.text.fill) } 465 | if m.ul != none { option.ul = m.ul } 466 | if m.ol != none { option.ol = m.ol } 467 | 468 | // work around for trailing whitespace with under/overline 469 | str = str.replace(regex("([ \t]+)$"), m => m.captures.at(0) + "\u{200b}") 470 | { 471 | show: box.with(..option.bg) 472 | show: text.with(..option.text) 473 | show: c => if option.ul { 474 | underline(c) 475 | } else { 476 | c 477 | } 478 | show: c => if option.ol { 479 | overline(c) 480 | } else { 481 | c 482 | } 483 | [#str] 484 | } 485 | // fill trailing newlines 486 | let s = str.find(regex("\n+$")) 487 | if s != none { 488 | for i in s { 489 | linebreak() 490 | } 491 | } 492 | } 493 | } 494 | --------------------------------------------------------------------------------