|
| 1 | +#import "@preview/t4t:0.3.2": get |
| 2 | + |
| 3 | +#let note-descent = state("note-descent", (:)) |
| 4 | + |
| 5 | +#let todo-outline(title: "List of Todos") = context { |
| 6 | + show outline.entry.where( |
| 7 | + level: 1 |
| 8 | + ): it => { |
| 9 | + // box with color, followed by content |
| 10 | + [#it.body.children.first() #it.body.children.last()] |
| 11 | + box(width: 1fr, repeat[.]) |
| 12 | + it.page |
| 13 | + } |
| 14 | + // only show if there actually are any todos |
| 15 | + if (counter(figure.where(kind: "todo")).final().first() > 0) { |
| 16 | + page(outline(target: figure.where(kind: "todo"), title: title)) |
| 17 | + } |
| 18 | +} |
| 19 | + |
| 20 | +#let _get-current-descent(descents-dict, page-number: auto) = { |
| 21 | + if page-number == auto { |
| 22 | + page-number = descents-dict.keys().at(-1, default: "0") |
| 23 | + } else { |
| 24 | + page-number = str(page-number) |
| 25 | + } |
| 26 | + (page-number, descents-dict.at(page-number, default: (left: 0pt, right: 0pt))) |
| 27 | +} |
| 28 | + |
| 29 | +#let _update-descent(side, dy, anchor-y, note-rect, page-number) = { |
| 30 | + let height = measure(note-rect).height |
| 31 | + // let dy = measure(v(dy + height)).height + anchor-y // needed if any values or not just pt. |
| 32 | + let dy = dy + height + anchor-y |
| 33 | + note-descent.update(old => { |
| 34 | + let (cnt, props) = _get-current-descent(old, page-number: page-number) |
| 35 | + props.insert(side, dy) |
| 36 | + old.insert(cnt, props) |
| 37 | + old |
| 38 | + }) |
| 39 | +} |
| 40 | + |
| 41 | +// invisible figure, s.t. we can reference it in the outline |
| 42 | +#let _todo-outline-entry(color, body) = hide( |
| 43 | + box( |
| 44 | + height: 0pt, |
| 45 | + width: 0pt, |
| 46 | + figure( |
| 47 | + none, |
| 48 | + kind: "todo", |
| 49 | + // colored box in outline |
| 50 | + supplement: box(fill: color, height: 11pt, width: 11pt, stroke: black + .5pt), |
| 51 | + caption: get.text(body), |
| 52 | + outlined: true, |
| 53 | + ) |
| 54 | + ) |
| 55 | +) |
| 56 | + |
| 57 | +#let _get-margin(side: auto, page-num) = { |
| 58 | + let w = if page.flipped { |
| 59 | + page.height |
| 60 | + } else { |
| 61 | + page.width |
| 62 | + } |
| 63 | + let default-margin = 2.5 / 21 * w |
| 64 | + let defaults = ( |
| 65 | + "left": default-margin, |
| 66 | + "right": default-margin |
| 67 | + ) |
| 68 | + // no margin is specified at all |
| 69 | + if (page.margin == auto) { |
| 70 | + return defaults |
| 71 | + } |
| 72 | + |
| 73 | + if ("right" in page.margin.keys() and page.margin.right != auto) { |
| 74 | + defaults.at("right") = page.margin.right |
| 75 | + } |
| 76 | + if ("left" in page.margin.keys() and page.margin.left != auto) { |
| 77 | + defaults.at("left") = page.margin.left |
| 78 | + } |
| 79 | + if ("inside" in page.margin.keys() and page.margin.inside != auto) { |
| 80 | + if (calc.odd(page-num)) { |
| 81 | + defaults.at("left") = page.margin.inside |
| 82 | + } else { |
| 83 | + defaults.at("right") = page.margin.inside |
| 84 | + } |
| 85 | + } |
| 86 | + if ("outside" in page.margin.keys() and page.margin.outside != auto) { |
| 87 | + if (calc.even(page-num)) { |
| 88 | + defaults.at("left") = page.margin.outside |
| 89 | + } else { |
| 90 | + defaults.at("right") = page.margin.outside |
| 91 | + } |
| 92 | + } |
| 93 | + return defaults |
| 94 | +} |
| 95 | + |
| 96 | +#let _todo-right(body, dy, anchor-x, anchor-y, font-size, font-color, color) = { |
| 97 | + let w = if page.flipped { |
| 98 | + page.height |
| 99 | + } else { |
| 100 | + page.width |
| 101 | + } |
| 102 | + let m = _get-margin(side: right, here().page()).right |
| 103 | + let dist-to-margin = w - anchor-x - m |
| 104 | + |
| 105 | + let path-pts = ( |
| 106 | + (0pt, -.2em), |
| 107 | + (0pt, 2pt), |
| 108 | + (dist-to-margin - 2pt, 2pt), |
| 109 | + (dist-to-margin - 2pt, dy + 2pt), |
| 110 | + (dist-to-margin + 2pt, dy + 2pt), |
| 111 | + ) |
| 112 | + |
| 113 | + dy += 1pt // todo-spacing |
| 114 | + let note-rect = rect( |
| 115 | + stroke: .5pt, |
| 116 | + fill: color, |
| 117 | + width: m - 10pt, // todo-margin |
| 118 | + inset: 4pt, |
| 119 | + radius: 4pt, |
| 120 | + text(size: font-size, |
| 121 | + fill: font-color, |
| 122 | + body) |
| 123 | + ) |
| 124 | + // Boxing prevents forced paragraph breaks |
| 125 | + box[ |
| 126 | + #place(path(stroke: 1pt + color, ..path-pts)) |
| 127 | + #place(dx: dist-to-margin + 2pt, dy: dy - 10pt, note-rect) // lift todo a bit |
| 128 | + #_todo-outline-entry(color, body) |
| 129 | + ] |
| 130 | + _update-descent("right", dy, anchor-y, note-rect, here().page()) |
| 131 | +} |
| 132 | + |
| 133 | +#let _todo-left(body, dy, anchor-x, anchor-y, font-size, font-color, color) = { |
| 134 | + let w = page.width |
| 135 | + let m = _get-margin(side: left, here().page()).left |
| 136 | + let dist-to-margin = - anchor-x |
| 137 | + |
| 138 | + let path-pts = ( |
| 139 | + (0pt, -.2em), |
| 140 | + (0pt, 2pt), |
| 141 | + (dist-to-margin + m - 8pt, 2pt), |
| 142 | + (dist-to-margin + m - 8pt, 2pt), |
| 143 | + (dist-to-margin + m - 8pt, dy + 2pt), |
| 144 | + (dist-to-margin + m - 10pt - 2pt, dy + 2pt), // todo-margin |
| 145 | + ) |
| 146 | + |
| 147 | + dy += 1pt // todo-spacing |
| 148 | + let note-rect = rect( |
| 149 | + stroke: .5pt, |
| 150 | + fill: color, |
| 151 | + width: m - 10pt, // todo-margin |
| 152 | + inset: 4pt, |
| 153 | + radius: 4pt, |
| 154 | + text(size: font-size, body) |
| 155 | + ) |
| 156 | + |
| 157 | + let foo = measure(note-rect) |
| 158 | + // Boxing prevents forced paragraph breaks |
| 159 | + box[ |
| 160 | + #place(path(stroke: color, ..path-pts)) |
| 161 | + #place(dx: dist-to-margin - 2pt, dy: dy - 10pt, note-rect) // lift todo a bit |
| 162 | + #_todo-outline-entry(color, body) |
| 163 | + ] |
| 164 | + _update-descent("left", dy, anchor-y, note-rect, here().page()) |
| 165 | +} |
| 166 | + |
| 167 | +#let todo(side: auto, font-size: 9pt, font-color: black, color: orange, body) = [ |
| 168 | + #h(0pt) // ensure here().position() accounts for indented paragraphs |
| 169 | + #context { |
| 170 | + let pos = here().position() |
| 171 | + let margin = _get-margin(side: side, pos.page) |
| 172 | + // shadow argument |
| 173 | + let side = if (side == auto) { |
| 174 | + let left = margin.at("left") |
| 175 | + let right = margin.at("right") |
| 176 | + if (left > right) { |
| 177 | + "left" |
| 178 | + } else { |
| 179 | + "right" |
| 180 | + } |
| 181 | + } else { |
| 182 | + repr(side) |
| 183 | + } |
| 184 | + let margin-func = if (side == "right") { |
| 185 | + _todo-right |
| 186 | + } else { |
| 187 | + _todo-left |
| 188 | + } |
| 189 | + let (anchor-x, anchor-y) = (pos.x - 5pt, pos.y) |
| 190 | + |
| 191 | + let (cur-page, descents) = _get-current-descent(note-descent.get(), page-number: pos.page) |
| 192 | + let cur-descent = descents.at(side) |
| 193 | + let dy = calc.max(0pt, cur-descent - pos.y) |
| 194 | + |
| 195 | + margin-func( |
| 196 | + body, |
| 197 | + dy, |
| 198 | + anchor-x, |
| 199 | + anchor-y, |
| 200 | + font-size, |
| 201 | + font-color, |
| 202 | + color |
| 203 | + ) |
| 204 | + } |
| 205 | +] |
0 commit comments