Racket racket/draw and pict — measurement on the device, extent on the scene
Category: device abstraction under an FP layer. Last reviewed: August 23, 2026. Pinned at c2f36f53 (racket/draw) and f96ef6a7 (racket/pict).
Two layers, surveyed together because neither is legible alone. racket/draw is a 72-member device-context interface with bitmap, PostScript, SVG, PDF, recording and printer implementations; pict is a purely functional image algebra layered directly on top of it. The pair is the survey's clearest dissenter from F1 — Racket puts text measurement on the device context — and simultaneously its fullest confirmation of F7, because it ships all three extents the finding separates: the device reports its drawing area, a pict carries a self-describing layout box, and record-dc% reports the ink extent of a recorded scene.
| Language | Racket |
| License | Apache-2.0 OR MIT (LICENSE) |
| Repository | racket/draw, racket/pict (separate packages) |
| Documentation | draw-doc/scribblings/draw/, pict-doc/pict/scribblings/pict.scrbl |
| Category | device abstraction under an FP layer |
| Pinned revision | c2f36f533ea6ee3f6400bf352d3d511dbf659916 / f96ef6a7c26dbe38c1c8c958a9fd7e25bd521fbc |
| Seam | dc<%> — a racket/class interface, 72 members, contracted |
| Backends shipped | bitmap-dc%, post-script-dc%, pdf-dc%, svg-dc%, record-dc%; printer-dc% and canvas DCs live in racket/gui |
| Implementation | Cairo + Pango behind every backend |
Overview
What it solves
One drawing vocabulary for a screen canvas, an offscreen bitmap, a PostScript, PDF or SVG file, and a printer — plus a recorder that replays into any of them. The guide states the model plainly:
The
racket/drawlibrary provides a drawing API that is based on the PostScript drawing model. It supports line drawing, shape filling, bitmap copying, alpha blending, and affine transformations (i.e., scale, rotation, and translation).
That sentence predicts almost everything below. The seam's nouns are pens, brushes, paths, fonts and transformations — a document imaging model, not a widget model — which is why no scrollbar appears in it (Q3) and why appearance is a device state machine rather than a per-command payload (Q6).
Design philosophy
Racket is unusually honest about the leak. The guide's portability section:
Drawing effects are not completely portable across platforms, across different classes that implement
dc<%>, or different kinds of bitmaps. Fonts and text, especially, can vary across platforms and types of DC, but so can the precise set of pixels touched by drawing a line.
Above that sits pict, whose philosophy is the opposite of a device's:
The size information for a pict is computed when the pict is created. This strategy supports programs that create new picts though arbitrarily complex computations on the size and shape of existing picts.
Extent is eagerly computed and stored in the value. That is what makes the combinator algebra pure arithmetic, and it is the reason a pict needs a device only at its leaves.
How it works
The seam is an interface value, not a class hierarchy
dc<%> is defined once, as data, in dc-intf.rkt. Because racket/class interfaces are first-class values and Racket contracts are first-class too, the declaration carries a machine-checked signature per method:
(define dc<%>
(interface ()
[draw-text (->*m (string? real? real?)
(any/c exact-nonnegative-integer? real?)
void?)]
[start-alpha (->m real? void?)
#:public (lambda (a) (void))]
[get-size (->m (values (and/c real? (not/c negative?))
(and/c real? (not/c negative?))))]
[get-text-extent (->*m (string?)
((or/c (is-a?/c font%) #f) any/c exact-nonnegative-integer?)
(values (and/c real? (not/c negative?)) (and/c real? (not/c negative?))
(and/c real? (not/c negative?)) (and/c real? (not/c negative?))))]
...))Seventy-two members, of which fifteen are drawing operations; the rest are state accessors, transformation control, document lifecycle and measurement. Note #:public on start-alpha/end-alpha: a Racket interface may carry a default method body, so a backend that cannot group-composite silently inherits a no-op — Slint's default-trait-method device, inside a nominal interface.
There is a second, private seam underneath
The surveyed subject is really two seams. Everything public goes through dc<%>; every backend is built by applying a shared dc-mixin to a backend object that implements the internal dc-backend<%> (dc.rkt). That inner interface is where capability lives:
;; can-mask-bitmap? : -> bool
;; Return #t if bitmap drawing with a mask is supported.
;; It's not supported for PostScirpt output, for example.
can-mask-bitmap?
;; get-hairline-width
;; Gets the pen width to use in place of 0 in 'smoothed mode
get-hairline-width
;; method get-font-metrics-key : real real -> integer
;; Gets a font-merics key for the current scale. 0 is always a
;; safe result, but the default is to return 1 for an unscaled dc.
get-font-metrics-key(sic on both typos, quoted verbatim from dc.rkt.) So the capability booleans exist, they are per-device, and the framework — the one shared dc-mixin — reads them and degrades once. The application never sees them. That is the framework slot in F4's list of six places a lowering can live, with the query made private.
pict is an algebra over four numbers
A pict is a struct whose field comments are its specification (pict.rkt):
(define-struct in:pict (draw ; drawing instructions
width ; total width
height ; total height >= ascent + desecnt
ascent ; portion of height above top baseline
descent ; portion of height below bottom baseline
children ; list of child records
panbox ; panorama box, computed on demand
last) ; a descendent for the bottom-right
#:reflection-name 'pict
#:mutable
...)draw is an S-expression command list interpreted by a private render procedure against a dc<%>; the dc constructor makes "an arbitrary self-rendering pict" (pict.scrbl) from a closure plus author-declared w h a d. The eight *-append combinators and the fifteen *-superimpose combinators are each generated from one parameterised factory that does nothing but arithmetic on those four numbers plus a child transform record. No device is consulted during composition — only at the text leaf.
Q1 — measurement unit, and who answers
Racket is the survey's dissenter. get-text-extent is a method on the drawing context, returning four device-space reals (width, height, descent, extra vertical space) in whatever units that device uses. Measurement is therefore not merely device-flavoured, it is device-addressed: you must hold a DC to ask.
The design leans on an assumption it states out loud:
The drawing context installed in this parameter need not be the same as the ultimate drawing context, but it should measure text in the same way. Under normal circumstances, font metrics are the same for all drawing contexts, so the default value of
dc-for-text-sizeis abitmap-dc%that draws to a 1-by-1 bitmap.
dc-for-text-size is a parameter whose default is literally (make-object bitmap-dc% (make-bitmap 1 1)) (pict.rkt) — a real Cairo surface allocated for no reason but to hold font metrics. pict's text constructor dereferences it and fails hard when it is #f, with (error 'text "no dc<%> object installed for sizing").
IMPORTANT
The library contradicts its own reassurance. get-font-metrics-key returns a device-class identity, and the three concrete backends return three different constants for the unscaled case: 1 from the default backend (dc.rkt), 2 from post-script-dc% (post-script-dc.rkt), 3 from svg-dc% (svg-dc.rkt), and 0 — "no key available" — whenever the DC is scaled. The public documentation says the key is "valid across all dc<%> instances, even among different classes" (dc-intf.scrbl), which is exactly what makes it a metric-universe tag: two DCs may share metrics iff they share a non-zero key. So dc-for-text-size's bitmap default (key 1) is provably the wrong measurer for a pict destined for PostScript (key 2), and the library ships no mechanism that notices.
Everything downstream of a measurement is baked into immutable pict extents at construction time, so a mis-chosen sizing DC yields a layout that is silently wrong on the target and cannot be re-derived. Racket does not refute F1; it is the one priced dissent in it — a global device parameter, a wasted 1×1 surface, and an unenforced obligation to match metric universes by hand. sparkles:ui puts measure on the painter too, and pays a different bill for it: friction §1 is the same decision met from the unit side rather than the addressing side.
One benefit is real: a caller that does hold the target DC gets that device's own shaping. get-text-extent's combine-mode selects among "each character separately", 'grapheme clusters, and full ligature/kerning/bidi shaping (dc-intf.scrbl) — three measurement fidelities named by the caller, resolved by the device.
Q2 — is the contract stated in one place?
Yes, and this is Racket's strongest result. The whole contract is one value, dc<%>, with a runtime-checked signature on every member; there is no __traits(compiles) equivalent anywhere and nothing is discovered by probing. Every backend implements all 72 members, totally.
The cost is that totality is a fiction maintained three different ways, and the docs enumerate each:
| Mechanism | Example | Where stated |
|---|---|---|
| Interface default body | start-alpha/end-alpha are #:public (lambda (a) (void)); "The dc<%> implementation has no effect" | dc-intf.rkt, dc-intf.scrbl |
| Documented silent lossiness | "For post-script-dc% and pdf-dc% output, opacity from an alpha channel … is rounded to full transparency or opacity" | dc-intf.scrbl |
| Silent no-op on bad state | a bitmap-dc% with no bitmap installed: "If any other bitmap-dc% method is called before a bitmap is selected, the method call is ignored" | bitmap-dc-class.scrbl |
| Hard exception on bad state | "Attempts to use a drawing method outside of an active page raises an exception" | blurbs.rkt (the shared PrintNote) |
| Private capability predicate | can-mask-bitmap? returns #f for PostScript, and the framework degrades | dc.rkt, post-script-dc.rkt |
Two of those are worth transplanting.
The lifecycle discipline is mechanised, not documented. doc+page-check-mixin (page-dc.rkt) wraps a backend with a three-state machine (#f → 'doc → 'page) and a macro, check-page-active, listing exactly the fifteen methods that require an active page — the thirteen draw-*, plus clear and erase. That list is the seam's real drawing subset, separated from its query/state subset in one place, machine-readable, applied by mixing rather than by convention. get-text-extent, get-char-width and get-char-height are deliberately outside it, and bitmap-dc% names the same three as callable without a bitmap installed.
Three real capability queries do exist on the public seam, all of them returning a value rather than requiring a probe: ok? (is this DC usable at all), get-gl-context (returns #f when unsupported), and glyph-exists? (per-character, and the docs note it is a property of the DC type, not of the font passed in).
Q3, Q6 — semantic operations and where appearance lives
Neither. The seam carries no semantic widget — no scrollbar, no focus ring, no text input, no shadow — and no per-command appearance either. Appearance is device state: set-pen, set-brush, set-font, set-alpha, set-smoothing, set-text-foreground, set-text-background, set-text-mode, set-clipping-region, plus the transformation stack. A draw-rectangle call takes four reals and nothing else.
That is the PostScript model, and it is one of the cheaper encodings F9 counts: Slint carries the role in the method name, egui resolves by tessellation, sparkles:ui stores the resolved fields each primitive paints from and, on six of its eight payloads, a semantic Slot beside them — Racket carries appearance out of band entirely, so the marginal cost of an operation is zero fields.
The consequence shows up in record-dc%, which must save and restore the target's entire state around a replay — pen, brush, font, smoothing, text mode, background, text background, text foreground, alpha, clipping region, origin, scale, rotation and initial matrix, enumerated one by one in generate-drawer/restore (record-dc.rkt) — to honour the documented invariant that "all settings in the target drawing context … are as before the replay" (record-dc-class.scrbl).
Out-of-band appearance makes commands cheap and composition expensive. For sparkles:ui, whose display list is replayed into several backends and compared op-by-op by RecordingCanvas, the transposed choice — resolved appearance on the operation rather than on the device — is what keeps the stream comparable without a state machine to unwind first. That prices friction §6 rather than resolving it: the seam pays for the role as well as the appearance, and the fact that a Visual is reconstructed through visualOf rather than stored makes the hedge cheap, not decided.
Q4 — command shape
dc<%> dispatches through methods; there is no reified command value in the seam itself. But record-dc% reifies the stream anyway, in two representations at once, and the technique is the most transferable thing in this subject.
record-dc-mixin (record-dc.rkt) is generated by a define/record macro that, for each recorded method, emits an override which calls super and then stores both a replay closure and a serialisable datum:
#'(define/override (name arg-formal ...)
(begin0
(super name arg-id ...)
(when (continue-recording?)
(let (arg-bind ...)
(record (lambda (dc) (send dc name arg-id ...))
(lambda () (list 'name (arg-convert ... arg-id) ...)))))))get-recorded-procedure returns the composed closure; get-recorded-datum returns "a value that can be printed with write and re-read with read" (record-dc-class.scrbl), reconstituted by recorded-datum->procedure. The tag is the method's own name symbol and the payload is the argument list — a sum type by construction, with no dead fields, obtained without anyone writing a DrawOp declaration. The macro's per-argument spec is a triple of clone- / convert- / unconvert- functions, so the encoding of each payload type is declared once beside the operation.
F3 refined: a method-dispatch seam and a reified, comparable, serialisable stream are compatible, provided the recorder is generated from the seam's declaration instead of being a parallel data type kept in sync by hand. The macro also settles F3's open trade by construction — each command's payload is exactly its argument list, so nothing is as wide as the widest operation, and the set is still closed because the method set is.
sparkles:ui derives what it can and declares the rest by hand. OpKind is computed from DrawOp by an eight-arm match!, so those two cannot disagree — F11 applied. The declaration that stands apart is isCanvas itself, which names five methods while the interpreter calls eight kinds, probing the other four with __traits(compiles) at each site: friction §2 is exactly the drift a recorder generated from one declaration makes impossible.
Q5 — sub-unit placement
Coordinates are real? throughout, so a band along an edge is never spelled as a compass direction (friction §5). Continuous coordinates do not thereby dissolve the sub-unit question — they move it onto the device, which is F6 in one subject. What Racket adds beyond the other continuous-coordinate subjects is a named snapping policy for it:
set-smoothingtakes'unsmoothed,'smoothedor'aligned. Both'unsmoothedand'aligned"adjust drawing coordinates to match pixel boundaries" (dc-intf.scrbl).set-alignment-scalesays what grid to snap to: the default1.0"means that drawing coordinates and pen sizes are aligned to integer values", while "An alignment scale of2.0aligns drawing coordinates to half-integer values" — for a bitmap with a backing scale of2.0(dc-intf.scrbl).- A pen of width
0means "hairline", and the device decides what that is. Indc.rkt, a zero width becomes1/effective-scale-xin aligned mode and otherwise(get-hairline-width effective-scale-x); the default backend returns1/sx(one device unit) andpost-script-dc%overrides it to(/ 1.0 (* cx 4))— a quarter of a device unit, because PostScript's device unit is a point, not a pixel.
That last bullet is the survey's second independent confirmation of F6's "a named fidelity plus a queried device unit", arriving from the opposite direction to Notcurses: the caller writes 0, meaning as thin as this device can honestly draw, and four backends answer differently — and get-hairline-width is the queried unit that makes the name resolvable. sparkles:ui's RuleEdge names six positions where a hairline width supplied by the backend would name none.
Q7 — payload ownership
Live drawing borrows: draw-text takes a string?, draw-bitmap takes a bitmap%, and neither is retained past the call.
Recording is where ownership is decided, and record-dc% decides it per argument, twice over. Each recorded parameter names up to three functions — clone-id, convert-id, unconvert-id — so the same argument gets a deep-copy encoding for in-memory replay (clone-bitmap, clone-pen, clone-color) and a plain-data encoding for serialisation (convert-color reduces a color% to (list r g b a); unconvert-pen rebuilds one through the-pen-list's interning find-or-create-pen). Strings become immutable on the way in.
Two consequences worth carrying. A recorded command can outlive not just the frame but the process — a stronger property than any other subject offers, and what makes a recorded drawing a golden-test artefact rather than a golden-test output. And retention is bounded on purpose: set-recording-limit plus continue-recording? stop recording past an accumulated size (record-dc.rkt), where RecordingCanvas collects into an unbounded GC array and CmdBuffer reports a length that nothing caps.
Racket agrees with F8 and shows the shape of the agreement: per-argument, and different on the two paths. sparkles:ui splits the same way — CmdBuffer.textRun copies its bytes into a FrameArena, which is what makes a scope source safe to draw with, while RecordingCanvas interns on the collected heap so its operations outlive the call that drew them. What Racket has and the arena path does not is a payload that survives the buffer: friction §7 is the rule an operation is valid while the buffer that built it is alive and unreset, stated on the type and therefore enforceable, but still a borrow — which is the retain boundary UI-O4 holds open. Racket's convert-/unconvert- pair is what closing it looks like: a plain-data encoding declared beside the operation, chosen per argument.
Q8 — extent query
Racket answers this three times, and the three answers are different things. It is the fullest confirmation of F7 in the survey.
| Query | Owner | What it means |
|---|---|---|
get-size | the device | the destination drawing area — F7's surface question |
get-ink-extent | the recording | the bounding box of what was actually drawn |
pict-width/-height/-ascent/-descent | the scene value | the declared layout box, computed at construction |
get-size is the uncontroversial one: the docs enumerate it per backend (window client size, selected bitmap size — "or 0 if no bitmap is selected" — or the page drawing area), and post-script-dc% computes it from paper size minus margins over the configured scale (post-script-dc.rkt).
get-ink-extent is the second question, answered separately. record-dc% takes a record-ink? initialisation argument; when true it allocates a Cairo recording surface and returns the extent from cairo_recording_surface_ink_extents. The docs draw a distinction that a scan over operation rects cannot make:
Bounding drawing "ink" takes into account the visible effect of drawing with different pen widths and the shape of drawn text, as opposed to just collecting path coordinates and nominal text extents.
It is opt-in, precisely because it costs a surface; without it the method raises rather than guessing. Note also why a recorder needs a live Cairo context even when it never paints: "We need a cairo context and surface to measure text, at least" (record-dc.rkt) — Q1's dissent charging rent on Q8's answer.
On the pict side there are again two: the declared box (width/height) and the panbox, the "panorama box, computed on demand" by panorama-box! — a memoised recursive union over children that is larger than the declared box whenever a child draws outside its bounds. convert-bounds-padding defaults to '(3 3 3 3) "to accommodate a small amount of drawing outside the pict's bounding box" when converting to PNG or PDF (pict.scrbl).
So "extent" is three questions, not one — the surface extent (who allocates), the layout extent (who arranges) and the ink extent (who crops). Racket ships all three, with the expensive one behind a flag, and it sits on the maintained-at-construction side of F7's axis for the layout one: a pict's box is a field, fixed when the pict is built, and even the panorama box is memoised rather than re-walked. sparkles:ui answers none of the three from the seam. CmdBuffer reports an operation count and a run's cell extent; nothing on it, the display list or the arena reports the extent of a built stream, so a caller that needs painted bounds folds op.rect itself (friction §8) — the derived-by-scan side of the same axis, and a scan that reads a TextRun's declared cell advance rather than its ink.
Strengths
- One declaration, contract-checked, no probing.
dc<%>is a value; a backend author reads one file, and a violation is a contract error at the boundary. - Degradation lives in the framework, once, driven by private per-device predicates (
can-mask-bitmap?,get-hairline-width,dc-adjust-smoothing,collapse-bitmap-b&w?) rather than in each caller. - The recorder is generated from the seam, so the reified command stream cannot drift from the method set, and it serialises.
- Hairline as a device-supplied width (pen width
0) instead of an enumerated position. - Command/query partition is mechanised by
check-page-active's explicit fifteen-method list. pictproves an image algebra needs no device for composition — only for its text leaves — with extent as an ordinary immutable field.
Weaknesses
- Measurement on the device forces a global sizing parameter.
dc-for-text-sizeis process state; getting it wrong is silent and unrecoverable, because pict extents are frozen at construction. - The metric-universe assumption is stated but unenforced.
get-font-metrics-keyproves PostScript and SVG measure differently from bitmaps; nothing checks that the sizing DC and the target DC share a key. - Capability is private. An application cannot ask whether alpha will survive; it can only read that PostScript rounds it to 0 or 1.
- Silent no-ops for a mis-sequenced
bitmap-dc%. "the method call is ignored" is the least debuggable degrade in the survey. - 72 members is a high floor for a new backend, even with
dc-mixindoing most of the work. - Out-of-band appearance makes replay expensive: fourteen state settings saved and restored around every recorded drawing.
Key design decisions and trade-offs
| Decision | Rationale | Trade-off |
|---|---|---|
get-text-extent on dc<%> | the device is the only thing that truly knows its shaping and metrics | needs a DC to measure at all; forces pict's global dc-for-text-size parameter |
| One total 72-member interface, no capability query | callers write one code path; the contract is a single readable value | totality is a fiction, upheld by no-ops, rounding and documented lossiness |
| Capability predicates on a private backend seam | framework degrades once, consistently | applications cannot negotiate or refuse a degrade |
| Appearance as device state, not per-command fields | commands stay minimal; matches the PostScript model | replay must save/restore fourteen settings; no per-op semantic role survives |
Pen width 0 = device-defined hairline | each device names its own thinnest honest stroke | a caller cannot ask how thin that is before drawing |
| Extent stored in the pict at construction | composition is pure arithmetic over w h a d; arbitrary layout computation | a wrong sizing DC bakes a wrong layout permanently; declared box ≠ ink box |
| Recorder generated by macro over the method set | reified stream cannot drift; serialisable and replayable | recording is a mixin over a live backend, so it still needs a Cairo context |
Ink extent behind record-ink? | costs a recording surface, so opt in | absent by default; the method raises rather than approximating |
Bearing on the proposal
- Q1 stays decided, but for a sharper reason. Racket is the one surveyed subject that puts measurement on the painter, and it pays with a global parameter, a wasted surface, and an unchecked correspondence between sizing device and target device. That does not falsify F1 — it is the priced dissent inside it.
Size measure(const(char)[])onisCanvasis the same placement; friction §1 is the bill in our units, and Racket is the bill in the other currency. - Steal
get-font-metrics-keyoutright. A small integer identifying a metric universe,0meaning "not cacheable", lets a toolkit cache measured text across backends and detect the case where a layout was measured against the wrong one. Whatever font abstraction replacesmeasureonisCanvasshould carry one. Nothing else in the survey has this. - Generate the recorder from the seam declaration. Friction §2 is the gap between the five methods
isCanvaschecks and the eight kinds the interpreter calls.DrawOp.kindis the cheap half of the answer: an eight-armmatch!rather than a stored tag, so no walker can read a kind the payload contradicts, which is F11 in miniature.record-dc-mixinshows how far the same move goes: derive the conforming backend, the op encoding and the method set from one declaration and none of the three can disagree. It also settles F3's live trade for free — a tag that is the operation's own name, carrying only its live arguments, with no operation as wide as the widest. - Replace
RuleEdgewith a backend-supplied hairline width, not more enumerators. Racket's pen-width-0convention plusget-hairline-width(1/sxon screen,1/(4·sx)for PostScript) is F6's "a named fidelity plus a queried device unit" in its simplest possible form. The caller names the fidelity; the device answers with the unit. That is the pattern friction §5 needs whether or not we adopt continuous coordinates. - Partition the seam into commands and queries, mechanically.
check-page-active's explicit fifteen-method list is what friction §2 wants: the drawing subset named in one place rather than inferred. Ours would additionally make clear which members aRecordingCanvasmust record and which it must answer. - Confirms F7 in full, and names the owner of each third. Racket ships surface extent (
get-size), ink extent (get-ink-extent, opt-in), and layout extent (the pict fields) — andpictcannot function without the third. Take the same split: the surface extent belongs to the surface; the ink extent is a legitimate scene query worth paying for only when asked; the layout extent belongs to the layout pass, which is where friction §8's offscreen consumer should get it.skia-canvas-render.dfoldsop.rectacross the stream, which reaches for the third answer through the second's door and gets a cell advance instead of an ink box. - Bound the recorder.
CmdBufferreports its operation count and nothing acts on it.set-recording-limitcosts almost nothing and turns an unboundedDrawOp[]into a degradation with a stated ceiling — which is also what keeps F12's parity oracle affordable to keep inspecting. - Do not adopt out-of-band appearance. Friction §6 calls storing a resolved appearance beside a semantic
Slota hedge, and F9 counts seven encodings that cost less. Racket is the cheapest of them and shows its full bill — fourteen state settings saved and restored around every replay, and no semantic role surviving into the stream at all, which is fatal for the HTML interpreter that re-resolves class names from the role. For a seam whose whole justification is a comparable op stream, per-op payload is the right side of this trade even though it is the more expensive one per command. The saving to chase is the one the seam taken here — store what a primitive paints from and deriveVisualat the asking — not the transposition.
Sources
dc-intf.rkt— thedc<%>declaration: 72 contracted members,#:publicdefaults onstart-alpha/end-alpha.dc.rkt—dc-backend<%>, the private capability seam, anddc-mixin, the shared degrading implementation.record-dc.rkt—define/record, the dual closure+datum encoding,set-recording-limit,get-ink-extent.post-script-dc.rkt,svg-dc.rkt— per-deviceget-font-metrics-key,can-mask-bitmap?,get-hairline-width,get-size.page-dc.rkt—doc+page-check-mixinand the fifteen-methodcheck-page-activelist.dc-intf.scrbl,bitmap-dc-class.scrbl,record-dc-class.scrbl,post-script-dc-class.scrbl,svg-dc-class.scrbl,guide.scrbl,blurbs.rkt— the documented capability differences and thePrintNotelifecycle rule.pict.rkt— the pict struct,dc-for-text-size,text, the append/superimpose factories,panorama-box!.pict.scrbl— the bounding-box model,dc-for-text-size,convert-bounds-padding, the combinator reference.printer-dc-class.scrbl—printer-dc%, adc<%>implementation that lives in another package.
Revisions pinned with gh api repos/<owner>/<repo>/commits/master --jq .sha and every path verified by fetching it at that SHA from raw.githubusercontent.com.