pdf-quiz-generator/docs/image-overlays.md
Daniel 809ac2fca6 feat: figures are thumbnails that open into a viewer, with source and overlay
A 2,000px radiograph written into an article rendered at whatever width it
happened to be — a wall of greyscale in the middle of a sentence, four
megabytes to draw it, and no way to look at it properly. Every image in prose
is now a 256px thumbnail with the author's label under it, and a click gives it
the screen.

The viewer puts what is known about the figure beside it: its title, what it
shows, and where it came from. `media_assets` gains `source` and `source_url`
for that — a citation belongs to the file, because the same figure used in
three articles is cited the same way in all three, and a licence that turns out
to be wrong is one row to fix rather than three paragraphs to find. Asked for
when the figure is opened, not when the page is drawn.

And `overlay`: the regions an educator has marked, as vector shapes in
normalised coordinates on the unit square, so one drawing is correct in a
thumbnail, in the viewer and on a projector. Off until the learner turns it on
— marks shown before they have looked answer the question for them. Vectors
rather than a second burnt-in picture, for four reasons written down in
docs/image-overlays.md. The tool that draws them is next; this is the storage,
the contract and the reader's half.

On a narrow screen the description stacks above the image rather than beside
it, where it can be read before scrolling to the picture.

Also here: `classify_question_difficulty`, which labelled all 2,924 questions
in batches of twenty-five against a written rubric — 622 easy, 1,634 medium,
668 hard, no failed batches. The column had been NULL on every row since it
existed. Migration l2c3d4e5f6a7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
2026-09-12 20:33:00 +02:00

2.4 KiB
Raw Permalink Blame History

Image overlays

An educator can mark regions on an image — the lead lines on a knee film, the level of a narrowing, the border of a lesion — and a learner sees them only when they ask. This is the contract for how those marks are stored and drawn.

Why vectors and not a second picture

The obvious implementation is a second PNG with the marks burnt in, shown instead of the original. It fails four ways: it cannot be turned off (which is the whole point — the learner should look first and check second), it cannot be corrected without redrawing, it doubles the storage for every marked image, and it is correct at exactly one size. Vectors have none of those problems and draw as crisply on a phone as on a monitor.

The shape of the data

media_assets.overlay, JSON, null when nothing is marked:

{
  "shapes": [
    {
      "kind": "path",
      "points": [[0.31, 0.52], [0.34, 0.51], [0.38, 0.53]],
      "color": "#5eead4",
      "width": 0.006,
      "label": "Dense metaphyseal bands"
    },
    { "kind": "rect", "x": 0.12, "y": 0.4, "w": 0.2, "h": 0.15, "color": "#5eead4" },
    { "kind": "ellipse", "cx": 0.5, "cy": 0.5, "rx": 0.1, "ry": 0.08, "color": "#f0a53d" },
    { "kind": "arrow", "points": [[0.2, 0.2], [0.4, 0.35]], "color": "#5eead4" }
  ]
}

Every coordinate is normalised to 01 against the image's own width and height — never pixels. That is what makes one drawing correct in a 256px thumbnail, in the viewer, and on a projector. width is normalised too, against the image width, so a stroke stays the same relative weight.

Four kinds, and no more without a reason: path (freehand or polyline, the one that traces an anatomical edge), rect, ellipse, arrow (a path of exactly two points, drawn with a head). label is optional on any shape and is what a screen reader is given.

Drawing it

An SVG with viewBox="0 0 1 1" and preserveAspectRatio="none", absolutely positioned over the image at the same size. Because the viewBox is the unit square, the stored numbers are the SVG's own coordinates and no conversion is needed at any size.

Rules

  • Off by default. The learner sees the image, and turns the overlay on. Marks shown before the learner has looked answer the question for them.
  • The original is never modified. Nothing here writes to the image file.
  • An overlay is not a caption. What the marks mean belongs in the description; the overlay says where.