├── .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 |
--------------------------------------------------------------------------------