Callouts
A callout marks one region. shotlist works out the position, extends the canvas if the label needs room outside the image, and moves a label that would overlap one already drawn. The canvas also grows for a box or a numbered disc that would otherwise be cut by the edge of the shot.
| Field | Default | What it does |
|---|---|---|
mark | required | Which mark to draw on |
text | — | Label text: one string, or a list with a line each |
n | — | Number shown in a disc, for matching a numbered list in the prose |
place | auto | auto, left, right, top, bottom, or corner for a disc |
badge | tl | Which anchor, with place: corner: tl tc tr ml mr bl bc br |
box | true | Whether to draw the outline |
inside | see below | Whether the label or disc sits over the shot |
dx | 0 | Nudge across, in image pixels |
dy | 0 | Nudge down, in image pixels |
pad | style's | Distance between the outline and the element |
gap | style's | Distance between the label and the outline |
Where a label goes
place defaults to auto, which picks a side. A label to the left or right grows the canvas by its width, one above or below by its height — on a wide shot that is a difference of hundreds of pixels. auto weighs that against how far the arrow would have to travel, and against whether its path would cross another mark or a masked region.
It also decides whether the label goes over the shot or in a margin, by reading the pixels the label would cover and counting how many of them are far from that region's own average colour. A flat panel scores as empty however dark it is, and so does a gradient; text or a chart over either does not. Over the shot costs no canvas and needs only a stub of an arrow, so it wins wherever the shot turns out to have nothing there. When the pixels cannot be read at all, the decision falls back to the geometry.
What it cannot judge is what that detail is for. An arrow drawn across a paragraph is not something it knows to avoid, so name a side when the shot needs one — an explicit place is obeyed exactly, and so is an explicit inside.
Left unsaid, a disc (place: corner) sits inside, on the box; a label on a named side sits outside, in a margin the canvas grows to make.
Numbered discs
numbered: [a, b, c] is shorthand for one numbered disc per mark, in the order given.
Written as a mapping instead, marks: carries that list and every other key is applied to all of them: box, badge, inside, dx, dy and pad. There is no place among them — a numbered disc is always corner, and badge is which corner. Discs sit inside the box unless inside: false pushes them clear.
marks:
header: { css: '.statblock-header' }
abilities: { css: '.abilities' }
defenses: { css: '.defenses' }
numbered:
marks: [header, abilities, defenses]
box: false # number the sections without outlining them
badge: ml # anchored to the middle of the left edge
inside: false # pushed clear, into the margin Masking a region
To keep a region out of the image, list a query for it under mask. It is painted over before the callouts are drawn. Use it for the part of a shot that differs on every re-shoot — a clock, a live total, a face — which would otherwise make --check report a change every time. check: false is the blunt version: it turns the check off for the whole image, where a mask leaves the rest of it checkable.
name: dashboard
clip: { css: '.panel' }
mask:
- { within: clip, css: '.updated-at' }
- { css: '.avatar' } Key a mask on something that survives the value changing. A class, a test id, a position — never the content itself. { text: $42.00 } matches the figure you are hiding today and nothing at all tomorrow, when it reads $51.00. That failure is loud rather than silent — a mask matching nothing stops the run — but it does mean a mask written against the content breaks the run rather than degrading.
Any query works, so an element with no class and no test id is still reachable. A mask covers every element its query matches, not the first: a page has three avatars far more often than it has one. Naming pick or nth says you mean a single element and still gets one.
mask:
- { within: clip, css: span, nth: 2 } # the third span, whatever it says
- { within: clip, child: 2 } # the third child of the clip
- { rect: [172, 84, 52, 20] } # a literal box, measured once The regions are painted before the callouts are drawn, so a callout may still point at, and outline, a masked box. Masking only helps if the rest of the shot holds still: a value that changes width reflows what is beside it, and no mask covers that — give the column a width, or mask the row. With source: file there is no page to query, so a mask is a literal rect, the same as a mark.