├── .gitignore ├── LICENSE ├── README.md ├── algo.typ ├── examples ├── examples.pdf └── examples.typ ├── test ├── assertions │ ├── assert_comment_in_algo.typ │ ├── assert_d_in_algo.typ │ ├── assert_i_in_algo.typ │ ├── assert_no-emph_in_algo.typ │ ├── assert_nonnegative_indent.typ │ ├── assert_raw_text_in_code.typ │ └── assert_single_raw_text_in_code.typ ├── test.pdf └── test.typ └── typst.toml /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | !.gitignore 4 | !algo.typ 5 | !README.md 6 | !typst.toml 7 | 8 | !examples 9 | !examples/examples.typ 10 | !examples/examples.pdf 11 | 12 | !test 13 | !test/test.typ 14 | !test/test.pdf 15 | !test/assertions 16 | !test/assertions/*.typ 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Andrew Sen 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Algo 2 | 3 | A Typst library for writing algorithms. On Typst v0.6.0+ you can import the `algo` package: 4 | 5 | ```typst 6 | #import "@preview/algo:0.3.6": algo, i, d, comment, code 7 | ``` 8 | 9 | Otherwise, add the `algo.typ` file to your project and import it as normal: 10 | 11 | ```typst 12 | #import "algo.typ": algo, i, d, comment, code 13 | ``` 14 | 15 | Use the `algo` function for writing pseudocode and the `code` function for writing code blocks with line numbers. Check out the [examples](#examples) below for a quick overview. See the [usage](#usage) section to read about all the options each function has. 16 | 17 | ## Examples 18 | 19 | Here's a basic use of `algo`: 20 | 21 | ```typst 22 | #algo( 23 | title: "Fib", 24 | parameters: ("n",) 25 | )[ 26 | if $n < 0$:#i\ // use #i to indent the following lines 27 | return null#d\ // use #d to dedent the following lines 28 | if $n = 0$ or $n = 1$:#i #comment[you can also]\ 29 | return $n$#d #comment[add comments!]\ 30 | return #smallcaps("Fib")$(n-1) +$ #smallcaps("Fib")$(n-2)$ 31 | ] 32 | ``` 33 | 34 | 35 | 36 |
37 | 38 | Here's a use of `algo` without a title, parameters, line numbers, or syntax highlighting: 39 | 40 | ```typst 41 | #algo( 42 | line-numbers: false, 43 | strong-keywords: false 44 | )[ 45 | if $n < 0$:#i\ 46 | return null#d\ 47 | if $n = 0$ or $n = 1$:#i\ 48 | return $n$#d\ 49 | \ 50 | let $x <- 0$\ 51 | let $y <- 1$\ 52 | for $i <- 2$ to $n-1$:#i #comment[so dynamic!]\ 53 | let $z <- x+y$\ 54 | $x <- y$\ 55 | $y <- z$#d\ 56 | \ 57 | return $x+y$ 58 | ] 59 | ``` 60 | 61 | 62 | 63 |
64 | 65 | And here's `algo` with more styling options: 66 | 67 | ```typst 68 | #algo( 69 | title: [ // note that title and parameters 70 | #set text(size: 15pt) // can be content 71 | #emph(smallcaps("Fib")) 72 | ], 73 | parameters: ([#math.italic("n")],), 74 | comment-prefix: [#sym.triangle.stroked.r ], 75 | comment-styles: (fill: rgb(100%, 0%, 0%)), 76 | indent-size: 15pt, 77 | indent-guides: 1pt + gray, 78 | row-gutter: 5pt, 79 | column-gutter: 5pt, 80 | inset: 5pt, 81 | stroke: 2pt + black, 82 | fill: none, 83 | )[ 84 | if $n < 0$:#i\ 85 | return null#d\ 86 | if $n = 0$ or $n = 1$:#i\ 87 | return $n$#d\ 88 | \ 89 | let $x <- 0$\ 90 | let $y <- 1$\ 91 | for $i <- 2$ to $n-1$:#i #comment[so dynamic!]\ 92 | let $z <- x+y$\ 93 | $x <- y$\ 94 | $y <- z$#d\ 95 | \ 96 | return $x+y$ 97 | ] 98 | ``` 99 | 100 | 101 | 102 |   103 | 104 | Here's a basic use of `code`: 105 | 106 | ````typst 107 | #code()[ 108 | ```py 109 | def fib(n): 110 | if n < 0: 111 | return None 112 | if n == 0 or n == 1: # this comment is 113 | return n # normal raw text 114 | return fib(n-1) + fib(n-2) 115 | ``` 116 | ] 117 | ```` 118 | 119 | 120 | 121 |
122 | 123 | And here's `code` with some styling options: 124 | 125 | ````typst 126 | #code( 127 | indent-guides: 1pt + gray, 128 | row-gutter: 5pt, 129 | column-gutter: 5pt, 130 | inset: 5pt, 131 | stroke: 2pt + black, 132 | fill: none, 133 | )[ 134 | ```py 135 | def fib(n): 136 | if n < 0: 137 | return None 138 | if n == 0 or n == 1: # this comment is 139 | return n # normal raw text 140 | return fib(n-1) + fib(n-2) 141 | ``` 142 | ] 143 | ```` 144 | 145 | 146 | 147 | ## Usage 148 | 149 | ### `algo` 150 | 151 | Makes a pseudocode element. 152 | 153 | ```typst 154 | algo( 155 | body, 156 | header: none, 157 | title: none, 158 | parameters: (), 159 | line-numbers: true, 160 | strong-keywords: true, 161 | keywords: _algo-default-keywords, // see below 162 | comment-prefix: "// ", 163 | indent-size: 20pt, 164 | indent-guides: none, 165 | indent-guides-offset: 0pt, 166 | row-gutter: 10pt, 167 | column-gutter: 10pt, 168 | inset: 10pt, 169 | fill: rgb(98%, 98%, 98%), 170 | stroke: 1pt + rgb(50%, 50%, 50%), 171 | radius: 0pt, 172 | breakable: false, 173 | block-align: center, 174 | main-text-styles: (:), 175 | comment-styles: (fill: rgb(45%, 45%, 45%)), 176 | line-number-styles: (:) 177 | ) 178 | ``` 179 | 180 | **Parameters:** 181 | 182 | * `body`: `content` — Main algorithm content. 183 | 184 | * `header`: `content` — Algorithm header. If specified, `title` and `parameters` are ignored. 185 | 186 | * `title`: `string` or `content` — Algorithm title. Ignored if `header` is specified. 187 | 188 | * `Parameters`: `array` — List of algorithm parameters. Elements can be `string` or `content` values. `string` values will automatically be displayed in math mode. Ignored if `header` is specified. 189 | 190 | * `line-numbers`: `boolean` — Whether to display line numbers. 191 | 192 | * `strong-keywords`: `boolean` — Whether to strongly emphasize keywords. 193 | 194 | * `keywords`: `array` — List of terms to receive strong emphasis. Elements must be `string` values. Ignored if `strong-keywords` is `false`. 195 | 196 | The default list of keywords is stored in `_algo-default-keywords`. This list contains the following terms: 197 | 198 | ``` 199 | ("if", "else", "then", "while", "for", 200 | "repeat", "do", "until", ":", "end", 201 | "and", "or", "not", "in", "to", 202 | "down", "let", "return", "goto") 203 | ``` 204 | 205 | Note that for each of the above terms, `_algo-default-keywords` also contains the uppercase form of the term (e.g. "for" and "For"). 206 | 207 | * `comment-prefix`: `content` — What to prepend comments with. 208 | 209 | * `indent-size`: `length` — Size of line indentations. 210 | 211 | * `indent-guides`: `stroke` — Stroke for indent guides. 212 | 213 | * `indent-guides-offset`: `length` — Horizontal offset of indent guides. 214 | 215 | * `row-gutter`: `length` — Space between lines. 216 | 217 | * `column-gutter`: `length` — Space between line numbers, text, and comments. 218 | 219 | * `inset`: `length` — Size of inner padding. 220 | 221 | * `fill`: `color` — Fill color. 222 | 223 | * `stroke`: `stroke` — Stroke for the element's border. 224 | 225 | * `radius`: `length` — Corner radius. 226 | 227 | * `breakable`: `boolean` — Whether the element can break across pages. WARNING: indent guides may look off when broken across pages. 228 | 229 | * `block-align`: `none` or `alignment` or `2d alignment` — Alignment of the `algo` on the page. Using `none` will cause the internal `block` element to be returned as-is. 230 | 231 | * `main-text-styles`: `dictionary` — Styling options for the main algorithm text. Supports all parameters in Typst's native `text` function. 232 | 233 | * `comment-styles`: `dictionary` — Styling options for comment text. Supports all parameters in Typst's native `text` function. 234 | 235 | * `line-number-styles`: `dictionary` — Styling options for line numbers. Supports all parameters in Typst's native `text` function. 236 | 237 | ### `i` and `d` 238 | 239 | For use in an `algo` body. `#i` indents all following lines and `#d` dedents all following lines. 240 | 241 | ### `comment` 242 | 243 | For use in an `algo` body. Adds a comment to the line in which it's placed. 244 | 245 | ```typst 246 | comment( 247 | body, 248 | inline: false 249 | ) 250 | ``` 251 | 252 | **Parameters:** 253 | 254 | * `body`: `content` — Comment content. 255 | 256 | * `inline`: `boolean` — If true, the comment is displayed in place rather than on the right side. 257 | 258 | NOTE: inline comments will respect both `main-text-styles` and `comment-styles`, preferring `comment-styles` when the two conflict. 259 | 260 | NOTE: to make inline comments insensitive to `strong-keywords`, strong emphasis is disabled within them. This can be circumvented via the `text` function: 261 | 262 | ```typst 263 | #comment(inline: true)[#text(weight: 700)[...]] 264 | ``` 265 | 266 | ### `no-emph` 267 | 268 | For use in an `algo` body. Prevents the passed content from being strongly emphasized. If a word appears in your algorithm both as a keyword and as normal text, you may escape the non-keyword usages via this function. 269 | 270 | ```typst 271 | no-emph( 272 | body 273 | ) 274 | ``` 275 | 276 | **Parameters:** 277 | 278 | * `body`: `content` — Content to display without emphasis. 279 | 280 | ### `code` 281 | 282 | Makes a code block element. 283 | 284 | ```typst 285 | code( 286 | body, 287 | line-numbers: true, 288 | indent-guides: none, 289 | indent-guides-offset: 0pt, 290 | tab-size: auto, 291 | row-gutter: 10pt, 292 | column-gutter: 10pt, 293 | inset: 10pt, 294 | fill: rgb(98%, 98%, 98%), 295 | stroke: 1pt + rgb(50%, 50%, 50%), 296 | radius: 0pt, 297 | breakable: false, 298 | block-align: center, 299 | main-text-styles: (:), 300 | line-number-styles: (:) 301 | ) 302 | ``` 303 | 304 | **Parameters:** 305 | 306 | * `body`: `content` — Main content. Expects `raw` text. 307 | 308 | * `line-numbers`: `boolean` — Whether to display line numbers. 309 | 310 | * `indent-guides`: `stroke` — Stroke for indent guides. 311 | 312 | * `indent-guides-offset`: `length` — Horizontal offset of indent guides. 313 | 314 | * `tab-size`: `integer` — Amount of spaces that should be considered an indent. If unspecified, the tab size is determined automatically from the first instance of starting whitespace. 315 | 316 | * `row-gutter`: `length` — Space between lines. 317 | 318 | * `column-gutter`: `length` — Space between line numbers and text. 319 | 320 | * `inset`: `length` — Size of inner padding. 321 | 322 | * `fill`: `color` — Fill color. 323 | 324 | * `stroke`: `stroke` — Stroke for the element's border. 325 | 326 | * `radius`: `length` — Corner radius. 327 | 328 | * `breakable`: `boolean` — Whether the element can break across pages. WARNING: indent guides may look off when broken across pages. 329 | 330 | * `block-align`: `none` or `alignment` or `2d alignment` — Alignment of the `code` on the page. Using `none` will cause the internal `block` element to be returned as-is. 331 | 332 | * `main-text-styles`: `dictionary` — Styling options for the main raw text. Supports all parameters in Typst's native `text` function. 333 | 334 | * `line-number-styles`: `dictionary` — Styling options for line numbers. Supports all parameters in Typst's native `text` function. 335 | 336 | ## Contributing 337 | 338 | PRs are welcome! And if you encounter any bugs or have any requests/ideas, feel free to open an issue. 339 | -------------------------------------------------------------------------------- /algo.typ: -------------------------------------------------------------------------------- 1 | // counter to track the number of algo elements 2 | // used as an id when accessing: 3 | // _algo-comment-lists 4 | #let _algo-id-ckey = "_algo-id" 5 | 6 | // state value for storing current comment-prefix passed to algo 7 | #let _algo-comment-prefix = state("_algo-comment-prefix", []) 8 | 9 | // state value for storing current comment-styles passed to algo 10 | #let _algo-comment-styles = state("_algo-comment-styles", (:)) 11 | 12 | // counter to track the number of lines in an algo element 13 | #let _algo-line-ckey = "_algo-line" 14 | 15 | // state value to track the current indent level in an algo element 16 | #let _algo-indent-level = state("_algo-indent-level", 0) 17 | 18 | // state value to track whether the current context is an algo element 19 | #let _algo-in-algo-context = state("_algo-in-algo-context", false) 20 | 21 | // state value for storing algo comments 22 | // dictionary that maps algo ids (as strings) to a dictionary that maps 23 | // line indexes (as strings) to the comment appearing on that line 24 | #let _algo-comment-dicts = state("_algo-comment-dicts", (:)) 25 | 26 | // list of default keywords that will be highlighted by strong-keywords 27 | #let _algo-default-keywords = ( 28 | // branch delimiters 29 | "if", 30 | "else", 31 | "then", 32 | 33 | // loop delimiters 34 | "while", 35 | "for", 36 | "repeat", 37 | "do", 38 | "until", 39 | 40 | // general delimiters 41 | ":", 42 | "end", 43 | 44 | // conditional expressions 45 | "and", 46 | "or", 47 | "not", 48 | "in", 49 | 50 | // loop conditions 51 | "to", 52 | "down", 53 | 54 | // misc 55 | "let", 56 | "return", 57 | "goto", 58 | ).map(kw => { 59 | // add uppercase words to list 60 | if kw.starts-with(regex("\w")) { 61 | (kw, str.from-unicode(str.to-unicode(kw.first()) - 32) + kw.slice(1)) 62 | } else { 63 | (kw,) 64 | } 65 | }).fold((), (acc, e) => acc + e) 66 | 67 | // constants for measuring text height 68 | #let _alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 69 | #let _numerals = "0123456789" 70 | #let _special-characters = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" 71 | #let _alphanumerics = _alphabet + _numerals 72 | #let _ascii = _alphanumerics + _special-characters 73 | 74 | 75 | // Makes assertion where message is automatically prepended with "algo: ". 76 | // 77 | // Parameters: 78 | // condition: Condition to assert is true. 79 | // message: Message to return if asssertion fails. 80 | #let _algo-assert(condition, message: "") = { 81 | assert(condition, message: "algo: " + message) 82 | } 83 | 84 | 85 | // Given data about a line in an algo or code, creates the 86 | // indent guides that should appear on that line. 87 | // 88 | // Parameters: 89 | // stroke: Stroke for drawing indent guides. 90 | // offset: Horizontal offset of indent guides. 91 | // indent-level: The indent level on the given line. 92 | // indent-size: The absolute length of a single indent. 93 | // row-height: The absolute height of the containing row of the given line. 94 | // block-inset: The absolute inset of the block containing all the lines. 95 | // Used when determining the length of an indent guide that appears 96 | // on the top or bottom of the block. 97 | // row-gutter: The absolute gap between lines. 98 | // Used when determining the length of an indent guide that appears 99 | // next to other lines. 100 | // is-first-line: Whether the given line is the first line in the block. 101 | // is-last-line: Whether the given line is the last line in the block. 102 | // If so, the length of the indent guide will depend on block-inset. 103 | #let _indent-guides( 104 | stroke, 105 | offset, 106 | indent-level, 107 | indent-size, 108 | row-height, 109 | block-inset, 110 | row-gutter, 111 | is-first-line, 112 | is-last-line, 113 | ) = { 114 | let stroke-width = stroke.thickness 115 | 116 | // lines are drawn relative to the top left of the bounding box for text 117 | // backset determines how far up the starting point should be moved 118 | let backset = if is-first-line { 119 | 0pt 120 | } else { 121 | row-gutter / 2 122 | } 123 | 124 | // determine how far the line should extend 125 | let stroke-length = backset + row-height + ( 126 | if is-last-line { 127 | calc.min(block-inset / 2, row-height / 4) 128 | } else { 129 | row-gutter / 2 130 | } 131 | ) 132 | 133 | // draw the indent guide for each indent level on the given line 134 | for j in range(indent-level) { 135 | box( 136 | height: row-height, 137 | width: 0pt, 138 | align( 139 | start + top, 140 | place( 141 | dx: indent-size * j + stroke-width / 2 + 0.5pt + offset, 142 | dy: -backset, 143 | line( 144 | length: stroke-length, 145 | angle: 90deg, 146 | stroke: stroke 147 | ) 148 | ) 149 | ) 150 | ) 151 | } 152 | } 153 | 154 | 155 | // Returns header to be displayed above algorithm content. 156 | // 157 | // Parameters: 158 | // header: Algorithm header. Overrides title and parameters. 159 | // title: Algorithm title. Ignored if header is not none. 160 | // Parameters: Array of parameters. Ignored if header is not none. 161 | #let _build-algo-header(header, title, parameters) = { 162 | if header != none { 163 | header 164 | } else { 165 | set align(start) 166 | 167 | if title != none { 168 | set text(1.1em) 169 | 170 | if type(title) == str { 171 | underline(smallcaps(title)) 172 | } else { 173 | title 174 | } 175 | 176 | if parameters.len() == 0 { 177 | $()$ 178 | } 179 | } 180 | 181 | if parameters != () { 182 | set text(1.1em) 183 | 184 | $($ 185 | 186 | for (i, param) in parameters.enumerate() { 187 | if type(param) == str { 188 | $italic(param)$ 189 | } else { 190 | param 191 | } 192 | 193 | if i < parameters.len() - 1 { 194 | [, ] 195 | } 196 | } 197 | 198 | $)$ 199 | } 200 | 201 | if title != none or parameters != () { 202 | [:] 203 | } 204 | } 205 | } 206 | 207 | 208 | // Create indent guides for a given line of an algo element. 209 | // Given the content of the line, calculates size of the content 210 | // and creates indent guides of sufficient length. 211 | // 212 | // Parameters: 213 | // indent-guides: Stroke for drawing indent guides. 214 | // indent-guides-offset: Horizontal offset of indent guides. 215 | // content: The main text that appears on the given line. 216 | // line-index: The 0-based index of the given line. 217 | // num-lines: The total number of lines in the current element. 218 | // indent-level: The indent level at the given line. 219 | // indent-size: The indent size used in the current element. 220 | // block-inset: The inset of the current element. 221 | // row-gutter: The row-gutter of the current element. 222 | // main-text-styles: Dictionary of styling options for the algorithm steps. 223 | // comment-styles: Dictionary of styling options for comment text. 224 | // line-number-styles: Dictionary of styling options for the line numbers. 225 | #let _algo-indent-guides( 226 | indent-guides, 227 | indent-guides-offset, 228 | content, 229 | line-index, 230 | num-lines, 231 | indent-level, 232 | indent-size, 233 | block-inset, 234 | row-gutter, 235 | main-text-styles, 236 | comment-styles, 237 | line-number-styles, 238 | ) = context { 239 | let id-str = str(counter(_algo-id-ckey).at(here()).at(0)) 240 | let line-index-str = str(line-index) 241 | let comment-dicts = _algo-comment-dicts.final() 242 | let comment-content = comment-dicts.at(id-str, default: (:)) 243 | .at(line-index-str, default: []) 244 | 245 | // heuristically determine the height of the containing table row 246 | let row-height = calc.max( 247 | // height of main content 248 | measure( 249 | { 250 | set text(..main-text-styles) 251 | _alphanumerics 252 | content 253 | } 254 | ).height, 255 | 256 | // height of comment 257 | measure( 258 | { 259 | set text(..comment-styles) 260 | comment-content 261 | } 262 | ).height, 263 | 264 | // height of line numbers 265 | measure( 266 | { 267 | set text(..line-number-styles) 268 | _numerals 269 | } 270 | ).height 271 | ) 272 | 273 | // converting input parameters to absolute lengths 274 | let indent-size-abs = measure( 275 | rect(width: indent-size) 276 | ).width 277 | 278 | let block-inset-abs = measure( 279 | rect(width: block-inset) 280 | ).width 281 | 282 | let row-gutter-abs = measure( 283 | rect(width: row-gutter) 284 | ).width 285 | 286 | let is-first-line = line-index == 0 287 | let is-last-line = line-index == num-lines - 1 288 | 289 | // display indent guides at the current line 290 | _indent-guides( 291 | indent-guides, 292 | indent-guides-offset, 293 | indent-level, 294 | indent-size-abs, 295 | row-height, 296 | block-inset-abs, 297 | row-gutter-abs, 298 | is-first-line, 299 | is-last-line 300 | ) 301 | } 302 | 303 | 304 | // Returns list of content values, where each element is 305 | // a line from the algo body 306 | // 307 | // Parameters: 308 | // body: Algorithm content. 309 | #let _get-algo-lines(body) = { 310 | if not body.has("children") { 311 | return () 312 | } 313 | 314 | // concatenate consecutive non-whitespace elements 315 | // i.e. just combine everything that definitely aren't on separate lines 316 | let text-and-whitespaces = { 317 | let joined-children = () 318 | let temp = [] 319 | 320 | for child in body.children { 321 | if ( 322 | child == [ ] 323 | or child == linebreak() 324 | or child == parbreak() 325 | ){ 326 | if temp != [] { 327 | joined-children.push(temp) 328 | temp = [] 329 | } 330 | 331 | joined-children.push(child) 332 | } else { 333 | temp += child 334 | } 335 | } 336 | 337 | if temp != [] { 338 | joined-children.push(temp) 339 | } 340 | 341 | joined-children 342 | } 343 | 344 | // filter out non-meaningful whitespace elements 345 | let text-and-breaks = text-and-whitespaces.filter( 346 | elem => elem != [ ] and elem != parbreak() 347 | ) 348 | 349 | // handling meaningful whitespace 350 | // make final list of empty and non-empty lines 351 | let lines = { 352 | let joined-lines = () 353 | let line-parts = [] 354 | let num-linebreaks = 0 355 | 356 | for elem in text-and-breaks { 357 | if elem == linebreak() { 358 | if line-parts != [] { 359 | joined-lines.push(line-parts) 360 | line-parts = [] 361 | } 362 | 363 | num-linebreaks += 1 364 | 365 | if num-linebreaks > 1 { 366 | joined-lines.push([]) 367 | } 368 | } else { 369 | line-parts += [#elem ] 370 | num-linebreaks = 0 371 | } 372 | } 373 | 374 | if line-parts != [] { 375 | joined-lines.push(line-parts) 376 | } 377 | 378 | joined-lines 379 | } 380 | 381 | return lines 382 | } 383 | 384 | 385 | // Returns list of algorithm lines with strongly emphasized keywords, 386 | // correct indentation, and indent guides. 387 | // 388 | // Parameters: 389 | // lines: List of algorithm lines from _get-algo-lines(). 390 | // strong-keywords: Whether to have bold keywords. 391 | // keywords: List of terms to receive strong emphasis if 392 | // strong-keywords is true. 393 | // indent-size: Size of line indentations. 394 | // indent-guides: Stroke for indent guides. 395 | // indent-guides-offset: Horizontal offset of indent guides. 396 | // inset: Inner padding. 397 | // row-gutter: Space between lines. 398 | // main-text-styles: Dictionary of styling options for the algorithm steps. 399 | // comment-styles: Dictionary of styling options for comment text. 400 | // line-number-styles: Dictionary of styling options for the line numbers. 401 | #let _build-formatted-algo-lines( 402 | lines, 403 | strong-keywords, 404 | keywords, 405 | indent-size, 406 | indent-guides, 407 | indent-guides-offset, 408 | inset, 409 | row-gutter, 410 | main-text-styles, 411 | comment-styles, 412 | line-number-styles 413 | ) = { 414 | // regex for detecting keywords 415 | let keyword-regex = "\b{start}(?:" 416 | 417 | for kw in keywords { 418 | keyword-regex += kw.trim() + "|" 419 | } 420 | 421 | keyword-regex = keyword-regex.replace(regex("\|$"), "") 422 | keyword-regex += ")\b{end}" 423 | 424 | let formatted-lines = () 425 | 426 | for (i, line) in lines.enumerate() { 427 | let formatted-line = { 428 | // bold keywords 429 | show regex(keyword-regex): it => { 430 | if strong-keywords { 431 | strong(it) 432 | } else { 433 | it 434 | } 435 | } 436 | 437 | context { 438 | let indent-level = _algo-indent-level.get() 439 | 440 | if indent-guides != none { 441 | _algo-indent-guides( 442 | indent-guides, 443 | indent-guides-offset, 444 | line, 445 | i, 446 | lines.len(), 447 | indent-level, 448 | indent-size, 449 | inset, 450 | row-gutter, 451 | main-text-styles, 452 | comment-styles, 453 | line-number-styles 454 | ) 455 | } 456 | 457 | box(pad( 458 | left: indent-size * indent-level, 459 | line 460 | )) 461 | } 462 | 463 | counter(_algo-line-ckey).step() 464 | } 465 | 466 | formatted-lines.push(formatted-line) 467 | } 468 | 469 | return formatted-lines 470 | } 471 | 472 | 473 | // Layouts algo content in a table. 474 | // 475 | // Parameters: 476 | // formatted-lines: List of formatted algorithm lines. 477 | // line-numbers: Whether to have line numbers. 478 | // comment-prefix: Content to prepend comments with. 479 | // row-gutter: Space between lines. 480 | // column-gutter: Space between line numbers, text, and comments. 481 | // main-text-styles: Dictionary of styling options for the algorithm steps. 482 | // comment-styles: Dictionary of styling options for comment text. 483 | // line-number-styles: Dictionary of styling options for the line numbers. 484 | #let _build-algo-table( 485 | formatted-lines, 486 | line-numbers, 487 | comment-prefix, 488 | row-gutter, 489 | column-gutter, 490 | main-text-styles, 491 | comment-styles, 492 | line-number-styles, 493 | ) = context { 494 | let id-str = str(counter(_algo-id-ckey).at(here()).at(0)) 495 | let comment-dicts = _algo-comment-dicts.final() 496 | let has-comments = id-str in comment-dicts 497 | 498 | let comment-contents = if has-comments { 499 | let comments = comment-dicts.at(id-str) 500 | 501 | range(formatted-lines.len()).map(i => { 502 | let index-str = str(i) 503 | 504 | if index-str in comments { 505 | comments.at(index-str) 506 | } else { 507 | none 508 | } 509 | }) 510 | } else { 511 | none 512 | } 513 | 514 | let num-columns = 1 + int(line-numbers) + int(has-comments) 515 | 516 | let align-func = { 517 | let alignments = () 518 | 519 | if line-numbers { 520 | alignments.push(right + horizon) 521 | } 522 | 523 | alignments.push(left + bottom) 524 | 525 | if has-comments { 526 | alignments.push(left + bottom) 527 | } 528 | 529 | (x, _) => alignments.at(x) 530 | } 531 | 532 | let table-data = () 533 | 534 | for (i, line) in formatted-lines.enumerate() { 535 | if line-numbers { 536 | let line-number = i + 1 537 | 538 | table-data.push({ 539 | set text(..line-number-styles) 540 | str(line-number) 541 | }) 542 | } 543 | 544 | table-data.push({ 545 | set text(..main-text-styles) 546 | line 547 | }) 548 | 549 | if has-comments { 550 | if comment-contents.at(i) == none { 551 | table-data.push([]) 552 | } else { 553 | table-data.push({ 554 | set text(..comment-styles) 555 | comment-prefix 556 | comment-contents.at(i) 557 | }) 558 | } 559 | } 560 | } 561 | 562 | table( 563 | columns: num-columns, 564 | row-gutter: row-gutter, 565 | column-gutter: column-gutter, 566 | align: align-func, 567 | stroke: none, 568 | inset: 0pt, 569 | ..table-data 570 | ) 571 | } 572 | 573 | 574 | // Asserts that the current context is an algo element. 575 | // Returns the provided message if the assertion fails. 576 | #let _assert-in-algo(message) = context { 577 | let is-in-algo = _algo-in-algo-context.get() 578 | _algo-assert(is-in-algo, message: message) 579 | } 580 | 581 | 582 | // Increases indent in an algo element. 583 | // All uses of #i within a line will be 584 | // applied to the next line. 585 | #let i = { 586 | _assert-in-algo("cannot use #i outside an algo element") 587 | _algo-indent-level.update(n => n + 1) 588 | } 589 | 590 | 591 | // Decreases indent in an algo element. 592 | // All uses of #d within a line will be 593 | // applied to the next line. 594 | #let d = { 595 | _assert-in-algo("cannot use #d outside an algo element") 596 | 597 | context { 598 | let n = _algo-indent-level.get() 599 | _algo-assert(n - 1 >= 0, message: "dedented too much") 600 | } 601 | 602 | _algo-indent-level.update(n => n - 1) 603 | } 604 | 605 | 606 | // Prevents internal content from being strongly emphasized. 607 | // 608 | // Parameters: 609 | // body: Content. 610 | #let no-emph(body) = { 611 | _assert-in-algo("cannot use #no-emph outside an algo element") 612 | set strong(delta: 0) 613 | body 614 | } 615 | 616 | 617 | // Adds a comment to a line in an algo body. 618 | // 619 | // Parameters: 620 | // body: Comment content. 621 | // inline: Whether the comment should be displayed in place. 622 | #let comment( 623 | body, 624 | inline: false, 625 | ) = { 626 | _assert-in-algo("cannot use #comment outside an algo element") 627 | 628 | context { 629 | if inline { 630 | let comment-prefix = _algo-comment-prefix.get() 631 | let comment-styles = _algo-comment-styles.get() 632 | set text(..comment-styles) 633 | comment-prefix 634 | no-emph(body) 635 | } else { 636 | let id-str = str(counter(_algo-id-ckey).at(here()).at(0)) 637 | let line-index-str = str(counter(_algo-line-ckey).at(here()).at(0)) 638 | 639 | _algo-comment-dicts.update(comment-dicts => { 640 | let comments = comment-dicts.at(id-str, default: (:)) 641 | let ongoing-comment = comments.at(line-index-str, default: []) 642 | let comment-content = ongoing-comment + body 643 | comments.insert(line-index-str, comment-content) 644 | comment-dicts.insert(id-str, comments) 645 | comment-dicts 646 | }) 647 | } 648 | } 649 | } 650 | 651 | 652 | // Displays an algorithm in a block element. 653 | // 654 | // Parameters: 655 | // body: Algorithm content. 656 | // header: Algorithm header. Overrides title and parameters. 657 | // title: Algorithm title. Ignored if header is not none. 658 | // Parameters: Array of parameters. Ignored if header is not none. 659 | // line-numbers: Whether to have line numbers. 660 | // strong-keywords: Whether to have bold keywords. 661 | // keywords: List of terms to receive strong emphasis if 662 | // strong-keywords is true. 663 | // comment-prefix: Content to prepend comments with. 664 | // indent-size: Size of line indentations. 665 | // indent-guides: Stroke for indent guides. 666 | // indent-guides-offset: Horizontal offset of indent guides. 667 | // row-gutter: Space between lines. 668 | // column-gutter: Space between line numbers and text. 669 | // inset: Inner padding. 670 | // fill: Fill color. 671 | // stroke: Border stroke. 672 | // radius: Corner radius. 673 | // breakable: Whether the element should be breakable across pages. 674 | // Warning: indent guides may look off when broken across pages. 675 | // block-align: Alignment of block. Use none for no alignment. 676 | // main-text-styles: Dictionary of styling options for the algorithm steps. 677 | // Supports any parameter in Typst's native text function. 678 | // comment-styles: Dictionary of styling options for comment text. 679 | // Supports any parameter in Typst's native text function. 680 | // line-number-styles: Dictionary of styling options for the line numbers. 681 | // Supports any parameter in Typst's native text function. 682 | #let algo( 683 | body, 684 | header: none, 685 | title: none, 686 | parameters: (), 687 | line-numbers: true, 688 | strong-keywords: true, 689 | keywords: _algo-default-keywords, 690 | comment-prefix: "// ", 691 | indent-size: 20pt, 692 | indent-guides: none, 693 | indent-guides-offset: 0pt, 694 | row-gutter: 10pt, 695 | column-gutter: 10pt, 696 | inset: 10pt, 697 | fill: rgb(98%, 98%, 98%), 698 | stroke: 1pt + rgb(50%, 50%, 50%), 699 | radius: 0pt, 700 | breakable: false, 701 | block-align: center, 702 | main-text-styles: (:), 703 | comment-styles: ("fill": rgb(45%, 45%, 45%)), 704 | line-number-styles: (:), 705 | ) = { 706 | counter(_algo-id-ckey).step() 707 | counter(_algo-line-ckey).update(0) 708 | _algo-comment-prefix.update(comment-prefix) 709 | _algo-comment-styles.update(comment-styles) 710 | _algo-indent-level.update(0) 711 | _algo-in-algo-context.update(true) 712 | 713 | let algo-header = _build-algo-header(header, title, parameters) 714 | 715 | let lines = _get-algo-lines(body) 716 | let formatted-lines = _build-formatted-algo-lines( 717 | lines, 718 | strong-keywords, 719 | keywords, 720 | indent-size, 721 | indent-guides, 722 | indent-guides-offset, 723 | inset, 724 | row-gutter, 725 | main-text-styles, 726 | comment-styles, 727 | line-number-styles, 728 | ) 729 | 730 | let algo-table = _build-algo-table( 731 | formatted-lines, 732 | line-numbers, 733 | comment-prefix, 734 | row-gutter, 735 | column-gutter, 736 | main-text-styles, 737 | comment-styles, 738 | line-number-styles, 739 | ) 740 | 741 | let algo-block = block( 742 | width: auto, 743 | height: auto, 744 | fill: fill, 745 | stroke: stroke, 746 | radius: radius, 747 | inset: inset, 748 | outset: 0pt, 749 | breakable: breakable 750 | )[ 751 | #set align(start + top) 752 | #algo-header 753 | #v(weak: true, row-gutter) 754 | #align(left, algo-table) 755 | ] 756 | 757 | // display content 758 | set par(justify: false) 759 | 760 | if block-align != none { 761 | align(block-align, algo-block) 762 | } else { 763 | algo-block 764 | } 765 | 766 | _algo-in-algo-context.update(false) 767 | } 768 | 769 | 770 | // Returns tuple of lengths: 771 | // - height of text (baseline to cap-height) 772 | // - height of ascenders 773 | // - height of descenders 774 | // 775 | // Parameters: 776 | // main-text-styles: Dictionary of styling options for the source code. 777 | #let _get-code-text-height( 778 | main-text-styles 779 | ) = { 780 | let styled-ascii = { 781 | show raw: set text(..main-text-styles) 782 | raw(_ascii) 783 | } 784 | 785 | let text-height = measure({ 786 | show raw: set text(top-edge: "cap-height", bottom-edge: "baseline") 787 | styled-ascii 788 | }).height 789 | 790 | let text-and-ascender-height = measure({ 791 | show raw: set text(top-edge: "ascender", bottom-edge: "baseline") 792 | styled-ascii 793 | }).height 794 | 795 | let text-and-descender-height = measure({ 796 | show raw: set text(top-edge: "cap-height", bottom-edge: "descender") 797 | styled-ascii 798 | }).height 799 | 800 | return ( 801 | text-height, 802 | text-and-ascender-height - text-height, 803 | text-and-descender-height - text-height, 804 | ) 805 | } 806 | 807 | 808 | // Determines tab size being used by the given text. 809 | // Searches for the first line that starts with whitespace and 810 | // returns the number of spaces the line starts with. If no 811 | // such line is found, -1 is returned. 812 | // 813 | // Parameters: 814 | // line-strs: Array of strings, where each string is a line from the 815 | // provided raw text. 816 | #let _get-code-tab-size(line-strs) = { 817 | for line in line-strs { 818 | let starting-whitespace = line.replace(regex("\t"), "").find(regex("^ +")) 819 | 820 | if starting-whitespace != none { 821 | return starting-whitespace.len() 822 | } 823 | } 824 | 825 | return -1 826 | } 827 | 828 | 829 | // Determines the indent level at each line of the given text. 830 | // Returns a list of integers, where the ith integer is the indent 831 | // level of the ith line. 832 | // 833 | // Parameters: 834 | // line-strs: Array of strings, where each string is a line from the 835 | // provided raw text. 836 | // tab-size: tab-size used by the given code 837 | #let _get-code-indent-levels(line-strs, tab-size) = { 838 | line-strs.map(line => { 839 | let starting-whitespace = line.replace(regex("\t"), "").find(regex("^ +")) 840 | 841 | if starting-whitespace == none { 842 | 0 843 | } else { 844 | calc.floor(starting-whitespace.len() / tab-size) 845 | } 846 | }) 847 | } 848 | 849 | 850 | // Returns list of tuples, where the ith tuple contains: 851 | // - a list of boxed clips of each line-wrapped component of the ith line 852 | // - an integer indicating the indent level of the ith line 853 | // 854 | // Parameters: 855 | // raw-text: Raw text block. 856 | // line-numbers: Whether there are line numbers. 857 | // column-gutter: Space between line numbers and text. 858 | // inset: Inner padding of containing block. 859 | // main-text-styles: Dictionary of styling options for the source code. 860 | // line-number-styles: Dictionary of styling options for the line numbers. 861 | // text-height: Height of raw text, baseline to cap-height. 862 | // ascender-height: Height of raw text ascenders. 863 | // descender-height: Height of raw text descenders. 864 | // indent-levels: List of integers indicating indent levels of each line. 865 | // container-size: Size of the outer container. 866 | // styles: Active styles. 867 | #let _get-code-line-data( 868 | raw-text, 869 | line-numbers, 870 | column-gutter, 871 | inset, 872 | main-text-styles, 873 | line-number-styles, 874 | text-height, 875 | ascender-height, 876 | descender-height, 877 | indent-levels, 878 | container-size 879 | ) = { 880 | let line-spacing = 100pt 881 | let line-strs = raw-text.text.split("\n") 882 | let num-lines = line-strs.len() 883 | let container-width = container-size.width 884 | 885 | let line-number-col-width = measure({ 886 | set text(..line-number-styles) 887 | "0" * (calc.floor(calc.log(num-lines)) + 1) 888 | }).width 889 | 890 | let max-text-area-width = ( 891 | container-size.width - inset * 2 - if line-numbers { 892 | (column-gutter + line-number-col-width) 893 | } else { 894 | 0pt 895 | } 896 | ) 897 | 898 | let max-text-width = measure({ 899 | show raw: set text(..main-text-styles) 900 | raw-text 901 | }).width 902 | 903 | let real-text-width = calc.min(max-text-width, max-text-area-width) 904 | 905 | let styled-raw-text = { 906 | show raw: set text(..main-text-styles) 907 | set par(leading: line-spacing) 908 | block(width: real-text-width, raw-text) 909 | } 910 | 911 | let line-data = () 912 | let line-count = 0 913 | 914 | for i in range(num-lines) { 915 | let indent-level = indent-levels.at(i) 916 | 917 | let line-width = measure({ 918 | show raw: set text(..main-text-styles) 919 | raw(line-strs.at(i)) 920 | }).width 921 | 922 | let line-wrapped-components = () 923 | 924 | for j in range(calc.max(1, calc.ceil(line-width / real-text-width))) { 925 | let is-wrapped = j > 0 926 | let real-indent-level = if is-wrapped {0} else {indent-level} 927 | 928 | let line-clip = { 929 | set align(start + top) 930 | 931 | box(move( 932 | dy: descender-height * 0.5, 933 | box( 934 | width: real-text-width, 935 | height: text-height + ascender-height + descender-height, 936 | clip: true, 937 | move( 938 | dy: -((text-height+line-spacing) * line-count) + ascender-height, 939 | styled-raw-text 940 | ) 941 | ) 942 | )) 943 | } 944 | 945 | line-wrapped-components.push(line-clip) 946 | line-count += 1 947 | } 948 | 949 | line-data.push((line-wrapped-components, indent-level)) 950 | } 951 | 952 | return line-data 953 | } 954 | 955 | 956 | // Create indent guides for a given line of a code element. 957 | // Given the content of the line, calculates size of the content 958 | // and creates indent guides of sufficient length. 959 | // 960 | // Parameters: 961 | // indent-guides: Stroke for drawing indent guides. 962 | // indent-guides-offset: Horizontal offset of indent guides. 963 | // content: The main content that appears on the given line. 964 | // line-index: The 0-based index of the given line. 965 | // num-lines: The total number of lines in the current element. 966 | // indent-level: The indent level at the given line. 967 | // tab-size: Amount of spaces that should be considered an indent. 968 | // block-inset: The inset of the current element. 969 | // row-gutter: The row-gutter of the current element. 970 | // main-text-styles: Dictionary of styling options for the source code. 971 | // Supports any parameter in Typst's native text function. 972 | // line-number-styles: Dictionary of styling options for the line numbers. 973 | // Supports any parameter in Typst's native text function. 974 | #let _code-indent-guides( 975 | indent-guides, 976 | indent-guides-offset, 977 | content, 978 | line-index, 979 | num-lines, 980 | indent-level, 981 | tab-size, 982 | block-inset, 983 | row-gutter, 984 | main-text-styles, 985 | line-number-styles, 986 | ) = { 987 | // heuristically determine the height of the row 988 | let row-height = calc.max( 989 | // height of content 990 | measure(content).height, 991 | 992 | // height of raw text 993 | measure({ 994 | show raw: set text(..main-text-styles) 995 | raw(_ascii) 996 | }).height, 997 | 998 | // height of line numbers 999 | measure({ 1000 | set text(..line-number-styles) 1001 | _numerals 1002 | }).height 1003 | ) 1004 | 1005 | let indent-size = measure({ 1006 | show raw: set text(..main-text-styles) 1007 | raw("a" * tab-size) 1008 | }).width 1009 | 1010 | // converting input parameters to absolute lengths 1011 | let block-inset-abs = measure(rect(width: block-inset)).width 1012 | let row-gutter-abs = measure(rect(width: row-gutter)).width 1013 | 1014 | let is-first-line = line-index == 0 1015 | let is-last-line = line-index == num-lines - 1 1016 | 1017 | // display indent guides at the current line 1018 | _indent-guides( 1019 | indent-guides, 1020 | indent-guides-offset, 1021 | indent-level, 1022 | indent-size, 1023 | row-height, 1024 | block-inset-abs, 1025 | row-gutter-abs, 1026 | is-first-line, 1027 | is-last-line 1028 | ) 1029 | } 1030 | 1031 | 1032 | // Layouts code content in a table. 1033 | // 1034 | // Parameters: 1035 | // line-data: Data received from _get-code-line-data(). 1036 | // indent-levels: List of indent levels from _get-code-indent-levels(). 1037 | // line-numbers: Whether to have line numbers. 1038 | // indent-guides: Stroke for indent guides. 1039 | // indent-guides-offset: Horizontal offset of indent guides. 1040 | // tab-size: Amount of spaces that should be considered an indent. 1041 | // row-gutter: Space between lines. 1042 | // column-gutter: Space between line numbers and text. 1043 | // inset: Inner padding. 1044 | // main-text-styles: Dictionary of styling options for the source code. 1045 | // line-number-styles: Dictionary of styling options for the line numbers. 1046 | #let _build-code-table( 1047 | line-data, 1048 | indent-levels, 1049 | line-numbers, 1050 | indent-guides, 1051 | indent-guides-offset, 1052 | tab-size, 1053 | row-gutter, 1054 | column-gutter, 1055 | inset, 1056 | main-text-styles, 1057 | line-number-styles, 1058 | ) = { 1059 | let flattened-line-data = line-data.fold((), (acc, e) => { 1060 | let line-wrapped-components = e.at(0) 1061 | let indent-level = e.at(1) 1062 | 1063 | for (i, line-clip) in line-wrapped-components.enumerate() { 1064 | let is-wrapped = i > 0 1065 | let real-indent-level = if is-wrapped {0} else {indent-level} 1066 | acc.push((line-clip, is-wrapped, real-indent-level)) 1067 | } 1068 | 1069 | acc 1070 | }) 1071 | 1072 | let table-data = () 1073 | 1074 | for (i, info) in flattened-line-data.enumerate() { 1075 | let line-clip = info.at(0) 1076 | let is-wrapped = info.at(1) 1077 | let indent-level = info.at(2) 1078 | 1079 | if line-numbers { 1080 | if is-wrapped { 1081 | table-data.push([]) 1082 | } else { 1083 | table-data.push({ 1084 | set text(..line-number-styles) 1085 | str(i + 1) 1086 | }) 1087 | } 1088 | } 1089 | 1090 | let content = { 1091 | if indent-guides != none { 1092 | _code-indent-guides( 1093 | indent-guides, 1094 | indent-guides-offset, 1095 | line-clip, 1096 | i, 1097 | flattened-line-data.len(), 1098 | indent-level, 1099 | tab-size, 1100 | inset, 1101 | row-gutter, 1102 | main-text-styles, 1103 | line-number-styles 1104 | ) 1105 | } 1106 | 1107 | box(line-clip) 1108 | } 1109 | 1110 | table-data.push(content) 1111 | } 1112 | 1113 | table( 1114 | columns: if line-numbers {2} else {1}, 1115 | inset: 0pt, 1116 | stroke: none, 1117 | fill: none, 1118 | row-gutter: row-gutter, 1119 | column-gutter: column-gutter, 1120 | align: if line-numbers { 1121 | (x, _) => (right+horizon, left+bottom).at(x) 1122 | } else { 1123 | left 1124 | }, 1125 | ..table-data 1126 | ) 1127 | } 1128 | 1129 | 1130 | // Displays code in a block element. 1131 | // 1132 | // Parameters: 1133 | // body: Raw text. 1134 | // line-numbers: Whether to have line numbers. 1135 | // indent-guides: Stroke for indent guides. 1136 | // indent-guides-offset: Horizontal offset of indent guides. 1137 | // tab-size: Amount of spaces that should be considered an indent. 1138 | // Determined automatically if unspecified. 1139 | // row-gutter: Space between lines. 1140 | // column-gutter: Space between line numbers and text. 1141 | // inset: Inner padding. 1142 | // fill: Fill color. 1143 | // stroke: Border stroke. 1144 | // radius: Corner radius. 1145 | // breakable: Whether the element should be breakable across pages. 1146 | // Warning: indent guides may look off when broken across pages. 1147 | // block-align: Alignment of block. Use none for no alignment. 1148 | // main-text-styles: Dictionary of styling options for the source code. 1149 | // Supports any parameter in Typst's native text function. 1150 | // line-number-styles: Dictionary of styling options for the line numbers. 1151 | // Supports any parameter in Typst's native text function. 1152 | #let code( 1153 | body, 1154 | line-numbers: true, 1155 | indent-guides: none, 1156 | indent-guides-offset: 0pt, 1157 | tab-size: auto, 1158 | row-gutter: 10pt, 1159 | column-gutter: 10pt, 1160 | inset: 10pt, 1161 | fill: rgb(98%, 98%, 98%), 1162 | stroke: 1pt + rgb(50%, 50%, 50%), 1163 | radius: 0pt, 1164 | breakable: false, 1165 | block-align: center, 1166 | main-text-styles: (:), 1167 | line-number-styles: (:), 1168 | ) = { layout(size => { 1169 | let raw-text = if body.func() == raw { 1170 | body 1171 | } else if body != [] and body.has("children") { 1172 | let raw-children = body.children.filter(e => e.func() == raw) 1173 | 1174 | _algo-assert( 1175 | raw-children.len() > 0, 1176 | message: "must provide raw text to code" 1177 | ) 1178 | 1179 | _algo-assert( 1180 | raw-children.len() == 1, 1181 | message: "cannot pass multiple raw text blocks to code" 1182 | ) 1183 | 1184 | raw-children.first() 1185 | } else { 1186 | return 1187 | } 1188 | 1189 | if raw-text.text == "" { 1190 | return 1191 | } 1192 | 1193 | let line-strs = raw-text.text.split("\n") 1194 | 1195 | let (text-height, asc-height, desc-height) = _get-code-text-height(main-text-styles) 1196 | 1197 | let real-row-gutter = calc.max(0pt, row-gutter - asc-height - desc-height) 1198 | 1199 | let real-tab-size = if tab-size == auto { 1200 | _get-code-tab-size(line-strs) 1201 | } else { 1202 | tab-size 1203 | } 1204 | 1205 | // no indents exist, so ignore indent-guides 1206 | let (real-indent-guides, indent-levels) = if real-tab-size == -1 { 1207 | (none, (0,) * line-strs.len()) 1208 | } else { 1209 | (indent-guides, _get-code-indent-levels(line-strs, real-tab-size)) 1210 | } 1211 | 1212 | let line-data = _get-code-line-data( 1213 | raw-text, 1214 | line-numbers, 1215 | column-gutter, 1216 | inset, 1217 | main-text-styles, 1218 | line-number-styles, 1219 | text-height, 1220 | asc-height, 1221 | desc-height, 1222 | indent-levels, 1223 | size 1224 | ) 1225 | 1226 | let code-table = _build-code-table( 1227 | line-data, 1228 | indent-levels, 1229 | line-numbers, 1230 | real-indent-guides, 1231 | indent-guides-offset, 1232 | real-tab-size, 1233 | real-row-gutter, 1234 | column-gutter, 1235 | inset, 1236 | main-text-styles, 1237 | line-number-styles, 1238 | ) 1239 | 1240 | // build block 1241 | let code-block = block( 1242 | width: auto, 1243 | fill: fill, 1244 | stroke: stroke, 1245 | radius: radius, 1246 | inset: inset, 1247 | breakable: breakable 1248 | )[ 1249 | #set align(start + top) 1250 | #code-table 1251 | ] 1252 | 1253 | // display content 1254 | set par(justify: false) 1255 | 1256 | if block-align != none { 1257 | align(block-align, code-block) 1258 | } else { 1259 | code-block 1260 | } 1261 | })} 1262 | -------------------------------------------------------------------------------- /examples/examples.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/platformer/typst-algorithms/0564557ab06f36f76a239285bb66f60c5f9df9d3/examples/examples.pdf -------------------------------------------------------------------------------- /examples/examples.typ: -------------------------------------------------------------------------------- 1 | // check out the README to see all the options 2 | // for algo and code 3 | #import "@preview/algo:0.3.1": algo, i, d, comment, code 4 | 5 | 6 | #algo( 7 | title: "Fib", 8 | parameters: ("n",) 9 | )[ 10 | if $n < 0$:#i\ // use #i to indent the following lines 11 | return null#d\ // use #d to dedent the following lines 12 | if $n = 0$ or $n = 1$:#i #comment[you can also]\ 13 | return $n$#d #comment[add comments!]\ 14 | return #smallcaps("Fib")$(n-1) +$ #smallcaps("Fib")$(n-2)$ 15 | ] 16 | 17 | 18 | #v(3em) 19 | 20 | 21 | #algo( 22 | line-numbers: false, 23 | strong-keywords: false 24 | )[ 25 | if $n < 0$:#i\ 26 | return null#d\ 27 | if $n = 0$ or $n = 1$:#i\ 28 | return $n$#d\ 29 | \ 30 | let $x <- 0$\ 31 | let $y <- 1$\ 32 | for $i <- 2$ to $n-1$:#i #comment[so dynamic!]\ 33 | let $z <- x+y$\ 34 | $x <- y$\ 35 | $y <- z$#d\ 36 | \ 37 | return $x+y$ 38 | ] 39 | 40 | 41 | #v(3em) 42 | 43 | 44 | #algo( 45 | title: [ // note that title and parameters 46 | #set text(size: 15pt) // can be content 47 | #emph(smallcaps("Fib")) 48 | ], 49 | parameters: ([#math.italic("n")],), 50 | comment-prefix: [#sym.triangle.stroked.r ], 51 | comment-styles: (fill: rgb(100%, 0%, 0%)), 52 | indent-size: 15pt, 53 | indent-guides: 1pt + gray, 54 | row-gutter: 5pt, 55 | column-gutter: 5pt, 56 | inset: 5pt, 57 | stroke: 2pt + black, 58 | fill: none, 59 | )[ 60 | if $n < 0$:#i\ 61 | return null#d\ 62 | if $n = 0$ or $n = 1$:#i\ 63 | return $n$#d\ 64 | \ 65 | let $x <- 0$\ 66 | let $y <- 1$\ 67 | for $i <- 2$ to $n-1$:#i #comment[so dynamic!]\ 68 | let $z <- x+y$\ 69 | $x <- y$\ 70 | $y <- z$#d\ 71 | \ 72 | return $x+y$ 73 | ] 74 | 75 | 76 | #pagebreak() 77 | 78 | 79 | #code()[ 80 | ```py 81 | def fib(n): 82 | if n < 0: 83 | return None 84 | if n == 0 or n == 1: # this comment is 85 | return n # normal raw text 86 | return fib(n-1) + fib(n-2) 87 | ``` 88 | ] 89 | 90 | 91 | #v(3em) 92 | 93 | 94 | #code( 95 | indent-guides: 1pt + gray, 96 | row-gutter: 5pt, 97 | column-gutter: 5pt, 98 | inset: 5pt, 99 | stroke: 2pt + black, 100 | fill: none, 101 | )[ 102 | ```py 103 | def fib(n): 104 | if n < 0: 105 | return None 106 | if n == 0 or n == 1: # this comment is 107 | return n # normal raw text 108 | return fib(n-1) + fib(n-2) 109 | ``` 110 | ] 111 | -------------------------------------------------------------------------------- /test/assertions/assert_comment_in_algo.typ: -------------------------------------------------------------------------------- 1 | #import "../../algo.typ": comment 2 | 3 | #comment[] 4 | -------------------------------------------------------------------------------- /test/assertions/assert_d_in_algo.typ: -------------------------------------------------------------------------------- 1 | #import "../../algo.typ": d 2 | 3 | #d 4 | -------------------------------------------------------------------------------- /test/assertions/assert_i_in_algo.typ: -------------------------------------------------------------------------------- 1 | #import "../../algo.typ": i 2 | 3 | #i 4 | -------------------------------------------------------------------------------- /test/assertions/assert_no-emph_in_algo.typ: -------------------------------------------------------------------------------- 1 | #import "../../algo.typ": no-emph 2 | 3 | #no-emph[] 4 | -------------------------------------------------------------------------------- /test/assertions/assert_nonnegative_indent.typ: -------------------------------------------------------------------------------- 1 | #import "../../algo.typ": algo, i, d, comment 2 | 3 | #algo( 4 | title: "Fib", 5 | parameters: ("n",) 6 | )[ 7 | if $n < 0$:#i\ 8 | return null#d\ 9 | if $n = 0$ or $n = 1$:#i #comment[you can also]\ 10 | return $n$#d#d #comment[add comments!]\ // excess dedent on this line 11 | return #smallcaps("Fib")$(n-1) +$ #smallcaps("Fib")$(n-2)$ 12 | ] 13 | -------------------------------------------------------------------------------- /test/assertions/assert_raw_text_in_code.typ: -------------------------------------------------------------------------------- 1 | #import "../../algo.typ": code 2 | 3 | #code[ 4 | abc 5 | ] 6 | -------------------------------------------------------------------------------- /test/assertions/assert_single_raw_text_in_code.typ: -------------------------------------------------------------------------------- 1 | #import "../../algo.typ": code 2 | 3 | #code[ 4 | ``` 5 | blah blah blah 6 | ``` 7 | ``` 8 | blah blah blah 9 | ``` 10 | ] 11 | -------------------------------------------------------------------------------- /test/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/platformer/typst-algorithms/0564557ab06f36f76a239285bb66f60c5f9df9d3/test/test.pdf -------------------------------------------------------------------------------- /test/test.typ: -------------------------------------------------------------------------------- 1 | #import "../algo.typ": algo, i, d, comment, code 2 | 3 | == Plain `algo` and `code` 4 | 5 | #table( 6 | columns: 2, 7 | stroke: none, 8 | align: (x, _) => (right, left).at(x), 9 | "title:", "\"Floyd-Warshall\" (algo only)", 10 | "parameters:", "(\"V\", \"E\", \"w\") (algo only)" 11 | ) 12 | 13 | #algo( 14 | title: "Floyd-Warshall", 15 | parameters: ("V", "E", "w"), 16 | )[ 17 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 18 | For $(u,v)$ in $E$:#i\ 19 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 20 | For $v$ in $V$:#i\ 21 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 22 | \ 23 | For $k <- 1$ to $|V|$:#i\ 24 | For $i <- 1$ to $|V|$:#i\ 25 | For $j <- 1$ to $|V|$:#i\ 26 | #comment(inline: true)[if new path is shorter, reduce distance]\ 27 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 28 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 29 | \ 30 | Return $"dist"$ 31 | ] 32 | 33 | #code()[ 34 | ```py 35 | def floyd_warshall(G): 36 | # let G be an adjacency matrix 37 | dist = G 38 | 39 | for k in range(len(G)): 40 | for i in range(len(G)): 41 | for j in range(len(G)): 42 | if dist[i][j] > dist[i][k] + dist[k][j]: 43 | dist[i][j] = dist[i][k] + dist[k][j] 44 | 45 | return dist 46 | ``` 47 | ] 48 | 49 | #show heading: it => { 50 | pagebreak() 51 | it 52 | } 53 | 54 | == Basic styling parameters 55 | 56 | #table( 57 | columns: 2, 58 | stroke: none, 59 | align: (x, _) => (right, left).at(x), 60 | "fill:", "none", 61 | "stroke:", "2pt + black", 62 | "radius:", "10pt", 63 | "row-gutter:", "8pt", 64 | "column-gutter:", "8pt", 65 | "inset:", "15pt", 66 | "indent-size:", "12pt (algo only)", 67 | "indent-guides:", "1pt + gray", 68 | "indent-guides-offset:", "4pt", 69 | "comment-prefix:", "[#sym.triangle ] (algo only)" 70 | ) 71 | 72 | #algo( 73 | title: "Floyd-Warshall", 74 | parameters: ("V", "E", "w"), 75 | fill: none, 76 | stroke: 2pt + black, 77 | radius: 10pt, 78 | row-gutter: 8pt, 79 | column-gutter: 8pt, 80 | inset: 15pt, 81 | indent-size: 12pt, 82 | indent-guides: 1pt + gray, 83 | indent-guides-offset: 4pt, 84 | comment-prefix: [#sym.triangle ] 85 | )[ 86 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 87 | For $(u,v)$ in $E$:#i\ 88 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 89 | For $v$ in $V$:#i\ 90 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 91 | \ 92 | For $k <- 1$ to $|V|$:#i\ 93 | For $i <- 1$ to $|V|$:#i\ 94 | For $j <- 1$ to $|V|$:#i\ 95 | #comment(inline: true)[if new path is shorter, reduce distance]\ 96 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 97 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 98 | \ 99 | Return $"dist"$ 100 | ] 101 | 102 | #code( 103 | fill: none, 104 | stroke: 2pt + black, 105 | radius: 10pt, 106 | row-gutter: 8pt, 107 | column-gutter: 8pt, 108 | inset: 15pt, 109 | indent-guides: 1pt + gray, 110 | indent-guides-offset: 4pt, 111 | )[ 112 | ```py 113 | def floyd_warshall(G): 114 | # let G be an adjacency matrix 115 | dist = G 116 | 117 | for k in range(len(G)): 118 | for i in range(len(G)): 119 | for j in range(len(G)): 120 | if dist[i][j] > dist[i][k] + dist[k][j]: 121 | dist[i][j] = dist[i][k] + dist[k][j] 122 | 123 | return dist 124 | ``` 125 | ] 126 | 127 | == Empty bodies 128 | 129 | #linebreak() 130 | 131 | #algo()[] 132 | 133 | #code()[] 134 | 135 | == `code` with empty raw text 136 | 137 | #linebreak() 138 | 139 | #code()[#raw("")] 140 | 141 | == `code` with empty raw block 142 | 143 | #linebreak() 144 | 145 | #code()[#raw("", block: true)] 146 | 147 | == `code` with non-sequence raw block 148 | 149 | #linebreak() 150 | 151 | #code()[```py 152 | def floyd_warshall(G): 153 | # let G be an adjacency matrix 154 | dist = G 155 | 156 | for k in range(len(G)): 157 | for i in range(len(G)): 158 | for j in range(len(G)): 159 | if dist[i][j] > dist[i][k] + dist[k][j]: 160 | dist[i][j] = dist[i][k] + dist[k][j] 161 | 162 | return dist 163 | ```] 164 | 165 | == Indent guides with line wrapping 166 | 167 | #table( 168 | columns: 2, 169 | stroke: none, 170 | align: (x, _) => (right, left).at(x), 171 | "indent-guides:", "1pt + black", 172 | ) 173 | 174 | #algo( 175 | title: "Floyd-Warshall", 176 | parameters: ("V", "E", "w"), 177 | indent-guides: 1pt + black, 178 | )[ 179 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 180 | For $(u,v)$ in $E$:#i\ 181 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 182 | For $v$ in $V$:#i\ 183 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 184 | \ 185 | For $k <- 1$ to $|V|$:#i\ 186 | For $i <- 1$ to $|V|$:#i\ 187 | For $j <- 1$ to $|V|$:#i\ 188 | #comment(inline: true)[if new path is shorter, reduce distance]\ 189 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 190 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$\ 191 | blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah#d#d#d#d\ 192 | \ 193 | Return $"dist"$ 194 | ] 195 | 196 | #code( 197 | indent-guides: 1pt + black 198 | )[ 199 | ```py 200 | def floyd_warshall(G): 201 | # let G be an adjacency matrix 202 | dist = G 203 | 204 | for k in range(len(G)): 205 | for i in range(len(G)): 206 | for j in range(len(G)): 207 | if dist[i][j] > dist[i][k] + dist[k][j]: 208 | dist[i][j] = dist[i][k] + dist[k][j] 209 | blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah 210 | 211 | return dist 212 | ``` 213 | ] 214 | 215 | == `code` indent guides with custom tab size 216 | 217 | #table( 218 | columns: 2, 219 | stroke: none, 220 | align: (x, _) => (right, left).at(x), 221 | "indent-guides:", "1pt + black", 222 | "tab-size:", "2" 223 | ) 224 | 225 | #code( 226 | indent-guides: 1pt + black, 227 | tab-size: 2, 228 | )[ 229 | ```py 230 | def floyd_warshall( 231 | G 232 | ): 233 | # let G be an adjacency matrix 234 | dist = G 235 | 236 | for k in range(len(G)): 237 | for i in range(len(G)): 238 | for j in range(len(G)): 239 | if dist[i][j] > dist[i][k] + dist[k][j]: 240 | dist[i][j] = dist[i][k] + dist[k][j] 241 | 242 | return dist 243 | ``` 244 | ] 245 | 246 | == No line numbers 247 | 248 | #table( 249 | columns: 2, 250 | stroke: none, 251 | align: (x, _) => (right, left).at(x), 252 | "line-numbers:", "false" 253 | ) 254 | 255 | #algo( 256 | title: "Floyd-Warshall", 257 | parameters: ("V", "E", "w"), 258 | line-numbers: false, 259 | )[ 260 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 261 | For $(u,v)$ in $E$:#i\ 262 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 263 | For $v$ in $V$:#i\ 264 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 265 | \ 266 | For $k <- 1$ to $|V|$:#i\ 267 | For $i <- 1$ to $|V|$:#i\ 268 | For $j <- 1$ to $|V|$:#i\ 269 | #comment(inline: true)[if new path is shorter, reduce distance]\ 270 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 271 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 272 | \ 273 | Return $"dist"$ 274 | ] 275 | 276 | #code( 277 | line-numbers: false, 278 | )[ 279 | ```py 280 | def floyd_warshall(G): 281 | # let G be an adjacency matrix 282 | dist = G 283 | 284 | for k in range(len(G)): 285 | for i in range(len(G)): 286 | for j in range(len(G)): 287 | if dist[i][j] > dist[i][k] + dist[k][j]: 288 | dist[i][j] = dist[i][k] + dist[k][j] 289 | 290 | return dist 291 | ``` 292 | ] 293 | 294 | == `algo` without keywords 295 | 296 | #table( 297 | columns: 2, 298 | stroke: none, 299 | align: (x, _) => (right, left).at(x), 300 | "strong-keywords:", "false" 301 | ) 302 | 303 | #algo( 304 | title: "Floyd-Warshall", 305 | parameters: ("V", "E", "w"), 306 | strong-keywords: false, 307 | )[ 308 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 309 | For $(u,v)$ in $E$:#i\ 310 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 311 | For $v$ in $V$:#i\ 312 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 313 | \ 314 | For $k <- 1$ to $|V|$:#i\ 315 | For $i <- 1$ to $|V|$:#i\ 316 | For $j <- 1$ to $|V|$:#i\ 317 | #comment(inline: true)[if new path is shorter, reduce distance]\ 318 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 319 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 320 | \ 321 | Return $"dist"$ 322 | ] 323 | 324 | == `algo` with custom keywords 325 | 326 | #table( 327 | columns: 2, 328 | stroke: none, 329 | align: (x, _) => (right, left).at(x), 330 | "keywords:", "(\"in\", \"to\", \"hello world\")" 331 | ) 332 | 333 | #algo( 334 | title: "Floyd-Warshall", 335 | parameters: ("V", "E", "w"), 336 | keywords: ("in", "to", "hello world") 337 | )[ 338 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 339 | For $(u,v)$ in $E$:#i\ 340 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 341 | For $v$ in $V$:#i\ 342 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 343 | \ 344 | For $k <- 1$ to $|V|$:#i\ 345 | For $i <- 1$ to $|V|$:#i\ 346 | For $j <- 1$ to $|V|$:#i\ 347 | #comment(inline: true)[if new path is shorter, reduce distance]\ 348 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 349 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 350 | \ 351 | blah blah hello world blah blah\ 352 | Return $"dist"$ 353 | ] 354 | 355 | == `algo` without title 356 | 357 | #table( 358 | columns: 2, 359 | stroke: none, 360 | align: (x, _) => (right, left).at(x), 361 | "title:", "none" 362 | ) 363 | 364 | #algo( 365 | parameters: ("V", "E", "w"), 366 | )[ 367 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 368 | For $(u,v)$ in $E$:#i\ 369 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 370 | For $v$ in $V$:#i\ 371 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 372 | \ 373 | For $k <- 1$ to $|V|$:#i\ 374 | For $i <- 1$ to $|V|$:#i\ 375 | For $j <- 1$ to $|V|$:#i\ 376 | #comment(inline: true)[if new path is shorter, reduce distance]\ 377 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 378 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 379 | \ 380 | Return $"dist"$ 381 | ] 382 | 383 | == `algo` without parameters 384 | 385 | #table( 386 | columns: 2, 387 | stroke: none, 388 | align: (x, _) => (right, left).at(x), 389 | "parameters:", "()" 390 | ) 391 | 392 | #algo( 393 | title: "Floyd-Warshall", 394 | )[ 395 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 396 | For $(u,v)$ in $E$:#i\ 397 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 398 | For $v$ in $V$:#i\ 399 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 400 | \ 401 | For $k <- 1$ to $|V|$:#i\ 402 | For $i <- 1$ to $|V|$:#i\ 403 | For $j <- 1$ to $|V|$:#i\ 404 | #comment(inline: true)[if new path is shorter, reduce distance]\ 405 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 406 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 407 | \ 408 | Return $"dist"$ 409 | ] 410 | 411 | == `algo` without header 412 | 413 | #table( 414 | columns: 2, 415 | stroke: none, 416 | align: (x, _) => (right, left).at(x), 417 | "title:", "none", 418 | "parameters:", "()" 419 | ) 420 | 421 | #algo()[ 422 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 423 | For $(u,v)$ in $E$:#i\ 424 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 425 | For $v$ in $V$:#i\ 426 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 427 | \ 428 | For $k <- 1$ to $|V|$:#i\ 429 | For $i <- 1$ to $|V|$:#i\ 430 | For $j <- 1$ to $|V|$:#i\ 431 | #comment(inline: true)[if new path is shorter, reduce distance]\ 432 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 433 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 434 | \ 435 | Return $"dist"$ 436 | ] 437 | 438 | 439 | == `algo` with content-type parameters 440 | 441 | #table( 442 | columns: 2, 443 | stroke: none, 444 | align: (x, _) => (right, left).at(x), 445 | "parameters:", "([#text(blue, [V])], [#text(red, [E])], [#text(green, [w])])" 446 | ) 447 | 448 | #algo( 449 | title: "Floyd-Warshall", 450 | parameters: ([#text(blue, [V])], [#text(red, [E])], [#text(green, [w])]), 451 | )[ 452 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 453 | For $(u,v)$ in $E$:#i\ 454 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 455 | For $v$ in $V$:#i\ 456 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 457 | \ 458 | For $k <- 1$ to $|V|$:#i\ 459 | For $i <- 1$ to $|V|$:#i\ 460 | For $j <- 1$ to $|V|$:#i\ 461 | #comment(inline: true)[if new path is shorter, reduce distance]\ 462 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 463 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 464 | \ 465 | Return $"dist"$ 466 | ] 467 | 468 | == `algo` with content-type title 469 | 470 | #table( 471 | columns: 2, 472 | stroke: none, 473 | align: (x, _) => (right, left).at(x), 474 | "title:", "[#set text(red);Floyd-Warshall]" 475 | ) 476 | 477 | #algo( 478 | title: [#set text(red);Floyd-Warshall] 479 | )[ 480 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 481 | For $(u,v)$ in $E$:#i\ 482 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 483 | For $v$ in $V$:#i\ 484 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 485 | \ 486 | For $k <- 1$ to $|V|$:#i\ 487 | For $i <- 1$ to $|V|$:#i\ 488 | For $j <- 1$ to $|V|$:#i\ 489 | #comment(inline: true)[if new path is shorter, reduce distance]\ 490 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 491 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 492 | \ 493 | Return $"dist"$ 494 | ] 495 | 496 | == `algo` with custom header 497 | 498 | #linebreak() 499 | 500 | #algo( 501 | header: { 502 | strong("Floyd-Warshall Algorithm") 503 | move(dx: 18pt, table( 504 | columns: 2, 505 | align: (x, _) => (right, left).at(x), 506 | stroke: none, 507 | inset: 0pt, 508 | row-gutter: 10pt, 509 | column-gutter: 10pt, 510 | strong("Inputs:"), 511 | [ 512 | graph $G=(V,E)$\ 513 | weight function $w: E -> RR$ 514 | ], 515 | strong("Outputs:"), 516 | [distance matrix $"dist"$] 517 | )) 518 | align(center, line(length: 330pt)) 519 | } 520 | )[ 521 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 522 | For $(u,v)$ in $E$:#i\ 523 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 524 | For $v$ in $V$:#i\ 525 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 526 | \ 527 | For $k <- 1$ to $|V|$:#i\ 528 | For $i <- 1$ to $|V|$:#i\ 529 | For $j <- 1$ to $|V|$:#i\ 530 | #comment(inline: true)[if new path is shorter, reduce distance]\ 531 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 532 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 533 | \ 534 | Return $"dist"$ 535 | ] 536 | 537 | == Text styling 538 | 539 | #table( 540 | columns: 2, 541 | stroke: none, 542 | align: (x, _) => (right, left).at(x), 543 | "main-text-styles:", "(fill: green)", 544 | "line-number-styles:", "(fill: red)", 545 | "comment-styles:", "(fill: blue) (algo only)" 546 | ) 547 | 548 | #algo( 549 | title: "Floyd-Warshall", 550 | parameters: ("V", "E", "w"), 551 | main-text-styles: (fill: green), 552 | line-number-styles: (fill: red), 553 | comment-styles: (fill: blue) 554 | )[ 555 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 556 | For $(u,v)$ in $E$:#i\ 557 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 558 | For $v$ in $V$:#i\ 559 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 560 | \ 561 | For $k <- 1$ to $|V|$:#i\ 562 | For $i <- 1$ to $|V|$:#i\ 563 | For $j <- 1$ to $|V|$:#i\ 564 | #comment(inline: true)[if new path is shorter, reduce distance]\ 565 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 566 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 567 | \ 568 | Return $"dist"$ 569 | ] 570 | 571 | #code( 572 | main-text-styles: (fill: green), 573 | line-number-styles: (fill: red) 574 | )[ 575 | ```py 576 | def floyd_warshall(G): 577 | # let G be an adjacency matrix 578 | dist = G 579 | 580 | for k in range(len(G)): 581 | for i in range(len(G)): 582 | for j in range(len(G)): 583 | if dist[i][j] > dist[i][k] + dist[k][j]: 584 | dist[i][j] = dist[i][k] + dist[k][j] 585 | 586 | return dist 587 | ``` 588 | ] 589 | 590 | == Indent guides with big main text 591 | 592 | #table( 593 | columns: 2, 594 | stroke: none, 595 | align: (x, _) => (right, left).at(x), 596 | "indent-guides:", "1pt + black", 597 | "main-text-styles:", "(size: 15pt)" 598 | ) 599 | 600 | #algo( 601 | title: "Floyd-Warshall", 602 | parameters: ("V", "E", "w"), 603 | indent-guides: 1pt + black, 604 | main-text-styles: (size: 15pt), 605 | )[ 606 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 607 | For $(u,v)$ in $E$:#i\ 608 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 609 | For $v$ in $V$:#i\ 610 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 611 | \ 612 | For $k <- 1$ to $|V|$:#i\ 613 | For $i <- 1$ to $|V|$:#i\ 614 | For $j <- 1$ to $|V|$:#i\ 615 | #comment(inline: true)[if new path is shorter, reduce distance]\ 616 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 617 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 618 | \ 619 | Return $"dist"$ 620 | ] 621 | 622 | #code( 623 | indent-guides: 1pt + black, 624 | main-text-styles: (size: 15pt) 625 | )[ 626 | ```py 627 | def floyd_warshall(G): 628 | # let G be an adjacency matrix 629 | dist = G 630 | 631 | for k in range(len(G)): 632 | for i in range(len(G)): 633 | for j in range(len(G)): 634 | if dist[i][j] > dist[i][k] + dist[k][j]: 635 | dist[i][j] = dist[i][k] + dist[k][j] 636 | 637 | return dist 638 | ``` 639 | ] 640 | 641 | == Indent guides with big line numbers 642 | 643 | #table( 644 | columns: 2, 645 | stroke: none, 646 | align: (x, _) => (right, left).at(x), 647 | "indent-guides:", "1pt + black", 648 | "line-number-styles:", "(size: 15pt)" 649 | ) 650 | 651 | #algo( 652 | title: "Floyd-Warshall", 653 | parameters: ("V", "E", "w"), 654 | indent-guides: 1pt + black, 655 | line-number-styles: (size: 15pt), 656 | )[ 657 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 658 | For $(u,v)$ in $E$:#i\ 659 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 660 | For $v$ in $V$:#i\ 661 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 662 | \ 663 | For $k <- 1$ to $|V|$:#i\ 664 | For $i <- 1$ to $|V|$:#i\ 665 | For $j <- 1$ to $|V|$:#i\ 666 | #comment(inline: true)[if new path is shorter, reduce distance]\ 667 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 668 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 669 | \ 670 | Return $"dist"$ 671 | ] 672 | 673 | #code( 674 | indent-guides: 1pt + black, 675 | line-number-styles: (size: 15pt) 676 | )[ 677 | ```py 678 | def floyd_warshall(G): 679 | # let G be an adjacency matrix 680 | dist = G 681 | 682 | for k in range(len(G)): 683 | for i in range(len(G)): 684 | for j in range(len(G)): 685 | if dist[i][j] > dist[i][k] + dist[k][j]: 686 | dist[i][j] = dist[i][k] + dist[k][j] 687 | 688 | return dist 689 | ``` 690 | ] 691 | 692 | == `algo` indent guides with big comments 693 | 694 | #table( 695 | columns: 2, 696 | stroke: none, 697 | align: (x, _) => (right, left).at(x), 698 | "indent-guides:", "1pt + black", 699 | "comment-styles:", "(size: 15pt)" 700 | ) 701 | 702 | #algo( 703 | title: "Floyd-Warshall", 704 | parameters: ("V", "E", "w"), 705 | indent-guides: 1pt + black, 706 | comment-styles: (size: 15pt), 707 | )[ 708 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 709 | For $(u,v)$ in $E$:#i\ 710 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 711 | For $v$ in $V$:#i\ 712 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 713 | \ 714 | For $k <- 1$ to $|V|$:#i\ 715 | For $i <- 1$ to $|V|$:#i\ 716 | For $j <- 1$ to $|V|$:#i\ 717 | #comment(inline: true)[if new path is shorter, reduce distance]\ 718 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 719 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 720 | \ 721 | Return $"dist"$ 722 | ] 723 | 724 | == Alignment 725 | 726 | #table( 727 | columns: 2, 728 | stroke: none, 729 | align: (x, _) => (right, left).at(x), 730 | "indent-guides:", "1pt + black", 731 | "block-align:", "bottom + right" 732 | ) 733 | 734 | #algo( 735 | title: "Floyd-Warshall", 736 | parameters: ("V", "E", "w"), 737 | indent-guides: 1pt + black, 738 | block-align: bottom + right, 739 | )[ 740 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 741 | For $(u,v)$ in $E$:#i\ 742 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 743 | For $v$ in $V$:#i\ 744 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 745 | \ 746 | For $k <- 1$ to $|V|$:#i\ 747 | For $i <- 1$ to $|V|$:#i\ 748 | For $j <- 1$ to $|V|$:#i\ 749 | #comment(inline: true)[if new path is shorter, reduce distance]\ 750 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 751 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 752 | \ 753 | Return $"dist"$ 754 | ] 755 | 756 | #pagebreak() 757 | 758 | #code( 759 | indent-guides: 1pt + black, 760 | block-align: bottom + right, 761 | )[ 762 | ```py 763 | def floyd_warshall(G): 764 | # let G be an adjacency matrix 765 | dist = G 766 | 767 | for k in range(len(G)): 768 | for i in range(len(G)): 769 | for j in range(len(G)): 770 | if dist[i][j] > dist[i][k] + dist[k][j]: 771 | dist[i][j] = dist[i][k] + dist[k][j] 772 | 773 | return dist 774 | ``` 775 | ] 776 | 777 | == Breakable 778 | 779 | #table( 780 | columns: 2, 781 | stroke: none, 782 | align: (x, _) => (right, left).at(x), 783 | "indent-guides:", "1pt + black", 784 | "breakable:", "true" 785 | ) 786 | 787 | #v(450pt) 788 | #algo( 789 | title: "Floyd-Warshall", 790 | parameters: ("V", "E", "w"), 791 | indent-guides: 1pt + black, 792 | breakable: true, 793 | )[ 794 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 795 | For $(u,v)$ in $E$:#i\ 796 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 797 | For $v$ in $V$:#i\ 798 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 799 | \ 800 | For $k <- 1$ to $|V|$:#i\ 801 | For $i <- 1$ to $|V|$:#i\ 802 | For $j <- 1$ to $|V|$:#i\ 803 | #comment(inline: true)[if new path is shorter, reduce distance]\ 804 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 805 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 806 | \ 807 | Return $"dist"$ 808 | ] 809 | 810 | #v(460pt) 811 | #code( 812 | indent-guides: 1pt + black, 813 | breakable: true, 814 | )[ 815 | ```py 816 | def floyd_warshall(G): 817 | # let G be an adjacency matrix 818 | dist = G 819 | 820 | for k in range(len(G)): 821 | for i in range(len(G)): 822 | for j in range(len(G)): 823 | if dist[i][j] > dist[i][k] + dist[k][j]: 824 | dist[i][j] = dist[i][k] + dist[k][j] 825 | 826 | return dist 827 | ``` 828 | ] 829 | 830 | == Broken indent guides with small inset 831 | 832 | #table( 833 | columns: 2, 834 | stroke: none, 835 | align: (x, _) => (right, left).at(x), 836 | "row-gutter:", "15pt", 837 | "inset:", "3pt", 838 | "indent-guides:", "1pt + black", 839 | "breakable:", "true" 840 | ) 841 | 842 | #v(380pt) 843 | #algo( 844 | title: "Floyd-Warshall", 845 | parameters: ("V", "E", "w"), 846 | row-gutter: 15pt, 847 | inset: 3pt, 848 | indent-guides: 1pt + black, 849 | breakable: true, 850 | )[ 851 | Let $"dist"[u,v] <- infinity$ for $u,v$ in $V$\ 852 | For $(u,v)$ in $E$:#i\ 853 | $"dist"[u,v] <- w(u,v)$ #comment[edge weights] #d\ 854 | For $v$ in $V$:#i\ 855 | $"dist"[v,v] <- 0$ #comment[base case] #d\ 856 | \ 857 | For $k <- 1$ to $|V|$:#i\ 858 | For $i <- 1$ to $|V|$:#i\ 859 | For $j <- 1$ to $|V|$:#i\ 860 | #comment(inline: true)[if new path is shorter, reduce distance]\ 861 | If $"dist"[i,j] > "dist"[i,k] + "dist"[k,j]$:#i\ 862 | $"dist"[i,j] <- "dist"[i,k] + "dist"[k,j]$#d#d#d#d\ 863 | \ 864 | Return $"dist"$ 865 | ] 866 | 867 | #v(420pt) 868 | #code( 869 | row-gutter: 15pt, 870 | inset: 3pt, 871 | indent-guides: 1pt + black, 872 | breakable: true, 873 | )[ 874 | ```py 875 | def floyd_warshall(G): 876 | # let G be an adjacency matrix 877 | dist = G 878 | 879 | for k in range(len(G)): 880 | for i in range(len(G)): 881 | for j in range(len(G)): 882 | if dist[i][j] > dist[i][k] + dist[k][j]: 883 | dist[i][j] = dist[i][k] + dist[k][j] 884 | 885 | return dist 886 | ``` 887 | ] 888 | -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "algo" 3 | version = "0.3.6" 4 | entrypoint = "algo.typ" 5 | authors = ["platformer "] 6 | license = "MIT" 7 | description = "Beautifully typeset algorithms." 8 | repository = "https://github.com/platformer/typst-algorithms" 9 | keywords = ["algorithm", "pseudocode", "code"] 10 | categories = ["components"] 11 | disciplines = ["computer-science"] 12 | --------------------------------------------------------------------------------