ReadingTheme is every text style and gap the page is set with, derived once
from the palette, the faces and the
Reading Scale
(lib/api/render/reading_theme.dart).
It is the reader's own vocabulary rather than a rendering package's, because
the distinctions that matter here are not ones a general-purpose style sheet
has (lib/api/render/reading_theme.dart): a width for prose and a wider
one for code, gaps expressed in lines rather than pixels, and figures that
differ between running text and tables.
It owns style values and the two column widths. It owns no layout — that is Document View — and it defines no colours or faces of its own; those come from the theme the reader picked, through Theme Binding.
ReadingTheme.of(context, scale, mode: mode) reads the palette, the typefaces,
the selected ReadingMode, the brightness
and MediaQuery.textScalerOf(context), then derives everything
(lib/api/render/reading_theme.dart). Accessibility scaling is part of
the page geometry, not an afterthought applied only when Text paints.
The leading comes from the face, not from the scale. It is derived from the
face's own cap height, descender and x-height by
Font Metrics (lib/api/render/reading_theme.dart), so
the rhythm follows the face rather than a constant
(lib/api/render/reading_theme.dart). renderedBase is the body size as
it is actually set, after x-height normalisation and accessibility scaling,
and the beat is measured
from that rather than from the size asked for — otherwise the grid is counted
in a unit the page never uses
(lib/api/render/reading_theme.dart, lib/api/render/reading_theme.dart).
Two refinements that should never be noticed. Light type on a dark ground
optically thickens and closes up, so a dark theme is tracked a hair looser —
0.008 of the body size (lib/api/render/reading_theme.dart). Serif prose uses
Alegreya's old-style figures, which have ascenders and descenders like lowercase
letters, so a number in a sentence sits in the line rather than standing up
out of it. Inter 4.001 has no old-style set, so Sans mode explicitly uses
proportional lining figures instead. Tables in both modes use tabular lining
figures so their columns agree (lib/api/render/reading_theme.dart). Code is given a
slashed zero, because zero and capital O are the pair a reader of technical
documents most often has to tell apart. Standard, contextual and discretionary
ligatures are disabled: source such as ===, --- and repeated Markdown marks
must show how many characters the author wrote rather than joining into a
display glyph
(lib/api/render/reading_theme.dart).
Tone as a third cue, under size and weight. The scale runs h1 darkest,
down through the body, to h5 and h6 sitting just back from it — small
headings should recede rather than compete with the sentence beneath them
(lib/api/render/reading_theme.dart). Two things about how it is
written. It is a distance from the running text towards ink or away from
it, never a lightness: on a dark page "more emphatic" means lighter, and the
same numbers have to work both ways round
(lib/api/render/reading_theme.dart). And the body itself is never
dimmed — contrast is what legibility rests on, and a paragraph is the thing
being read (lib/api/render/reading_theme.dart). h4 therefore sits
with the text rather than above it.
Headings use natural display leading: [1.14, 1.18, 1.24, 1.30, 1.40]
from h1 through h5, then the face's measured body leading at h6. Display
lines close up as they grow and open gradually as they approach running text
(lib/api/render/reading_theme.dart). The line is deliberately not
snapped here; Document View reconciles the completed
shaped block, after fallback scripts and inline roles have established its real
height. Every level takes the same weight: size already says which level a
heading is, and a second, quieter bold would repeat the signal. Tracking
tightens as the size grows; at body size h6 leans on a little extra tracking
instead (lib/api/render/reading_theme.dart).
Quoting. ReadingTheme.quoting(theme) returns the same page one shade
back, for matter inside a quotation
(lib/api/render/reading_theme.dart). The rule down its left already
says it is quoted; the colour is the second and last signal.
Inline roles. linkFor(base) preserves the complete surrounding style and
adds only link signals. inlineCodeFor(base) measures the surrounding face,
steps back one logical pixel, and normalises Geist Mono from that target, so
headings and tables retain their hierarchy without a hidden percentage
(lib/api/render/reading_theme.dart). Mono and its contrast-safe code colour
already identify the technical token; it has no underline because that mark is
reserved for content the reader can follow. The text uses the accent;
if that is below 4.5:1 against paper, _contrastSafeAccent finds the smallest
mix toward ink that meets the threshold.
Mathematics. mathSizeFor(base) treats a KaTeX em as another measured
face rather than trusting equal nominal sizes. It reduces the surrounding role
to its quoted letter size, then normalises from KaTeX Main's measured 0.431 em
x-height. Inline notation therefore retains a heading, table or prose role;
display notation uses body size and lets mathematical display style establish
its hierarchy (lib/api/render/reading_theme.dart).
Annotations. ReadingTheme.footnotes(theme) derives a complete reading
theme exactly two logical pixels below the running prose while preserving the
same face, x-height normalisation and measured leading. Definition content can
therefore reuse every ordinary block renderer at a subordinate role; Document
View reconciles the completed annotation section back to the parent beat
(lib/api/render/reading_theme.dart).
The two widths (lib/api/render/reading_theme.dart):
proseWidth(available)— the measure, or the room available if that is narrower. Everything on the page lines up against this, including the breadcrumb in the reading pane.wideWidth(available)— up to 1.35 × prose, for code and tables. They are not bound by the measure, because a line of code is as long as it is, but they still belong to the same page.
The rhythm. Displayed departures return in whole beats; consecutive spaced
paragraphs use a half-beat and indented paragraphs use none. A strut holds every
line box to one so inline code cannot push it off the grid
(lib/api/render/reading_theme.dart); see Vertical Rhythm.
| In | Type | From |
|---|---|---|
context |
BuildContext |
For palette, typefaces, brightness and the active TextScaler |
scale |
ReadingScale |
ReaderController.readingScale |
Out: body, code, quote, marker, tableHead, tableBody, headings
(lib/api/render/reading_theme.dart), the role-aware linkFor and
inline-code and math-size rules, the derived footnotes annotation role
(lib/api/render/reading_theme.dart), leading,
renderedBase, rendered em and indent, the two widths, strutFor, and the
rhythm members baseline, snap, blockGap and the single external-spacing
contract spaceAfter(current, next) (lib/api/render/reading_theme.dart).
None today. It is derived state, rebuilt from the theme and the scale.
@immutable (lib/api/render/reading_theme.dart) and rebuilt whenever the
palette, the faces or the scale change — which is to say whenever the reader
changes theme or text size. Built in the reading pane
and handed down; nothing caches it.
Nothing throws. heading(level) clamps its argument to 1–6
(lib/api/render/reading_theme.dart), so a malformed level renders as h1
or h6 rather than crashing. Both widths clamp to the room available, so a very
narrow window gets a narrow column rather than an overflow. The compact shell
keeps side panels over that column instead of squeezing it.
The tonal scale — [0.26, 0.16, 0.07, 0.0] towards ink and 0.22 away from it
(lib/api/render/reading_theme.dart) — is judged rather than measured,
and is the pair of numbers to revisit if the hierarchy ever reads too flat or
too stepped. The 1.35 wide-block factor is a judgement too. Per-theme
typography would enter here as a theme-owned ReadingScale.