PDF Typography Skill
Version: 0.1.1
Created: 2026-04-08
Author: Dojo Genesis
Purpose: Fine-tune the Pretext typography engine for optimal PDF output across different content types, audiences, and aesthetic goals.
I. The Philosophy: Measure Twice, Render Once
Pretext does one thing exceptionally well: it measures text on a real Canvas surface, then reports exact dimensions. This means every line break, every paragraph spacing, every margin is computed from actual glyph metrics — not CSS approximations. Typography configuration is the lever that controls how this precision manifests in the final PDF.
II. When to Use This Skill
- Content-specific tuning: Code needs different settings than prose. Chat needs different settings than reports.
- Audience adaptation: A client-facing PDF needs different typography than an internal code review.
- Brand alignment: Match typography to organization design tokens or brand guidelines.
- Disposition mapping: Map ADA agent personality traits to typographic choices (DojoChat export).
III. Steps
Step 1: Identify Content Type
Each content type has an optimal typography profile:
| Content Type |
Body Font |
Code Font |
Size |
Line Height |
Max Width |
| Prose (default) |
Inter |
Recursive |
11pt |
1.5 |
468pt (6.5in) |
| Code review |
Inter |
Recursive MONO=1 |
9pt |
1.3 |
504pt (7in) |
| Chat export |
Inter Variable |
Recursive |
13pt |
1.45 |
420pt (5.8in) |
| Presentation |
Inter |
Recursive |
14pt |
1.6 |
432pt (6in) |
| Reference manual |
Inter |
Recursive |
10pt |
1.4 |
468pt (6.5in) |
Step 2: Configure Font Stack
The Pretext bridge supports three font families with variable axes:
Inter (body text):
- Axes:
opsz (optical size), wght (weight)
- Range: 100-900 weight, 14-32 optical size
- Default: 400 weight, 16 optical size
Recursive (code):
- Axes:
MONO (monospace), CASL (casual), slnt (slant), wght (weight)
- For code:
MONO=1, CASL=0, slnt=0, wght=400
- For casual:
MONO=0, CASL=1, slnt=-15, wght=400
Fraunces (display/headings):
- Axes:
SOFT (softness), WONK (wonkiness), opsz (optical size), wght (weight)
- For headings:
SOFT=50, WONK=1, opsz=48, wght=700
Step 3: Set Page Geometry
interface PageGeometry {
size: 'letter' | 'a4' | 'legal'; // Default: 'letter'
margins: {
top: number; // Default: 72pt (1in)
right: number; // Default: 72pt
bottom: number; // Default: 72pt
left: number; // Default: 72pt
};
columns: 1 | 2; // Default: 1
headerHeight: number; // Default: 36pt
footerHeight: number; // Default: 36pt
}
Step 4: Configure Pretext Measurement
Pretext's prepare() and layout() functions take specific parameters:
// One-time per text block
const prepared = pretext.prepare(text, fontDescriptor);
// Fast re-layout at any width
const layout = pretext.layout(prepared, maxWidth, lineHeight);
// Returns: { width, height, lines[] }
Key: the fontDescriptor must match the actual font loaded in the Canvas context. Mismatches cause measurement drift.
Step 5: Apply and Validate
After configuration, export a test page and verify:
- Body text line length is 45-75 characters (optimal readability)
- Code blocks don't overflow margins
- Headings have appropriate visual weight
- Page breaks respect orphan/widow rules (min 2 lines)
IV. Quality Checklist
Before handing a typography configuration to pdf-export, verify:
V. Reflection Questions
- Is the content primarily prose, code, or mixed? Each has different optimal settings.
- Who is the audience? Technical readers tolerate denser typography; general audiences need more whitespace.
- Does the content have a brand or design system to align with?
- Are there accessibility requirements (minimum font size, contrast ratios)?
VI. Common Mistakes
- Using system-ui in Canvas context. Canvas and DOM resolve
system-ui differently on macOS. Always specify exact font family names.
- Ignoring optical sizing. Inter and Fraunces have
opsz axes — using body optical size for headings produces thin, fragile letterforms.
- Setting line height too tight for code. Code blocks with
1.0 line height make indentation guides invisible. Minimum 1.3 for code.
- Mixing measurement contexts. Pretext measures in CSS pixels. PDF uses points. The bridge handles conversion — don't double-convert.
VII. Variations
Disposition-Driven Typography (DojoChat)
Map ADA agent personality traits to typographic choices:
function dispositionToTypography(disposition: Disposition): TypographyConfig {
return {
fontFamily: disposition.tone === 'formal' ? 'Inter' : 'Recursive',
fontSize: mapVerbosity(disposition.verbosity), // verbose → smaller
lineHeight: mapDepth(disposition.depth), // deep → tighter
maxBubbleWidth: mapBrevity(disposition.brevity), // brief → narrower
fontVariationSettings: buildVariations(disposition),
};
}
Two-Column Reference
For dense reference material (API docs, skill catalogs):
{
columns: 2,
fontSize: 9,
lineHeight: 1.3,
margins: { top: 54, right: 54, bottom: 54, left: 54 }
}
VIII. Example
Scenario: Configure typography for exporting a Go backend specification as a client-facing PDF.
- Content type: Mixed prose + code → start with prose defaults
- Audience: Client (non-technical stakeholders) → increase font to 12pt, line height 1.6
- Code blocks: Go syntax → Recursive MONO=1, 10pt, 1.35 line height
- Headings: Fraunces 700, SOFT=30, WONK=1 for professional warmth
- Page: Letter, 1-inch margins, single column
- Validate: Body line length = 58 characters (within 45-75 range)
IX. See Also
pdf-export — Main export workflow that consumes typography configuration
- Pretext source:
@chenglou/pretext — Canvas-based typography engine
- DojoChat disposition mapper:
src/lib/disposition/typography.ts
Output
A TypographyConfig object ready for consumption by the Pretext prepare()/layout() pipeline, plus page geometry settings for the PDF renderer. Concretely: validated font descriptors, line height values per content zone (body/code/headings), page margins in points, and column count. When used with pdf-export, the output becomes embedded in the rendered PDF — font choices and spacing are baked into the byte stream, not post-processable.
Examples
Scenario 1 — Client-facing Go spec PDF (mixed prose + code):
Content type: prose primary, code secondary. Audience: non-technical client.
- Body: Inter 12pt, opsz=16, line height 1.6
- Code: Recursive MONO=1, 10pt, line height 1.35
- Headings: Fraunces 700, SOFT=30, WONK=1
- Page: Letter, 72pt margins, single column
- Validate: body line ~58 chars (within 45-75 target)
Scenario 2 — Dense code review PDF:
Content type: code-primary (diff output, function signatures). Audience: senior engineers.
- Body: Inter 9pt, opsz=14, line height 1.3
- Code: Recursive MONO=1, 9pt, line height 1.3 (same size as body — intentional)
- Headings: Inter 700, no Fraunces
- Page: Letter, 54pt margins, potentially 2-column
- Validate: code blocks fit within 504pt max width without overflow
Edge Cases
- Canvas vs. DOM font resolution on macOS:
system-ui resolves to different faces in Canvas and DOM contexts. Always pass explicit family names (Inter, Recursive, Fraunces) to fontDescriptor.
- Optical sizing mismatches at large scales:
Inter at 48pt (heading) with opsz=16 (body default) produces thin, fragile letterforms. Set opsz to match intended display size.
- Mixed content with extremely long code lines: If source code has 120+ char lines and the page is letter-width, either reduce font size or switch to landscape. The layout engine will not auto-truncate — overflow is clipped.
- Recursive
CASL axis in monospace mode: Setting MONO=1, CASL=1 simultaneously produces undefined intermediate rendering on some PDF engines. Use CASL=0 when MONO=1.
- Two-column layout with tall code blocks: Page-break logic does not split code blocks across columns. A single tall block will overflow into the gutter.
Anti-Patterns
- Specifying
system-ui in any Pretext context. Breaks cross-machine reproducibility and causes Canvas/DOM measurement drift. Use explicit font family names.
- Ignoring
opsz at heading sizes. Using body opsz (14-16) for large headings (24-48pt) produces optically incorrect letterforms. Set opsz to match render size.
- Setting line height below 1.3 for code. At
1.0-1.2, indentation guides and leading dots between columns become invisible. Minimum 1.3 for monospace content.
- Double-converting pt/px units. Pretext measures in CSS pixels internally; the bridge converts to PDF points. Manually converting before passing to the API results in double-scaling (typically 72/96 = 0.75x error).
- Applying a single TypographyConfig across all zones. Body, code, captions, and headings each need separate configuration. A one-size config either makes code unreadable or body text too large.
1---2name: pdf-typography3description: Configure Pretext typography settings for PDF export — font stacks, sizing, line height, page geometry, and disposition-driven typography mapping. Use when defaults need tuning for specific content types or audiences.4---56# PDF Typography Skill78**Version:** 0.1.19**Created:** 2026-04-0810**Author:** Dojo Genesis11**Purpose:** Fine-tune the Pretext typography engine for optimal PDF output across different content types, audiences, and aesthetic goals.1213---1415## I. The Philosophy: Measure Twice, Render Once1617Pretext does one thing exceptionally well: it measures text on a real Canvas surface, then reports exact dimensions. This means every line break, every paragraph spacing, every margin is computed from actual glyph metrics — not CSS approximations. Typography configuration is the lever that controls how this precision manifests in the final PDF.1819---2021## II. When to Use This Skill2223- **Content-specific tuning:** Code needs different settings than prose. Chat needs different settings than reports.24- **Audience adaptation:** A client-facing PDF needs different typography than an internal code review.25- **Brand alignment:** Match typography to organization design tokens or brand guidelines.26- **Disposition mapping:** Map ADA agent personality traits to typographic choices (DojoChat export).2728---2930## III. Steps3132### Step 1: Identify Content Type3334Each content type has an optimal typography profile:3536| Content Type | Body Font | Code Font | Size | Line Height | Max Width |37|-------------|-----------|-----------|------|-------------|-----------|38| Prose (default) | Inter | Recursive | 11pt | 1.5 | 468pt (6.5in) |39| Code review | Inter | Recursive MONO=1 | 9pt | 1.3 | 504pt (7in) |40| Chat export | Inter Variable | Recursive | 13pt | 1.45 | 420pt (5.8in) |41| Presentation | Inter | Recursive | 14pt | 1.6 | 432pt (6in) |42| Reference manual | Inter | Recursive | 10pt | 1.4 | 468pt (6.5in) |4344### Step 2: Configure Font Stack4546The Pretext bridge supports three font families with variable axes:4748**Inter** (body text):49- Axes: `opsz` (optical size), `wght` (weight)50- Range: 100-900 weight, 14-32 optical size51- Default: 400 weight, 16 optical size5253**Recursive** (code):54- Axes: `MONO` (monospace), `CASL` (casual), `slnt` (slant), `wght` (weight)55- For code: `MONO=1, CASL=0, slnt=0, wght=400`56- For casual: `MONO=0, CASL=1, slnt=-15, wght=400`5758**Fraunces** (display/headings):59- Axes: `SOFT` (softness), `WONK` (wonkiness), `opsz` (optical size), `wght` (weight)60- For headings: `SOFT=50, WONK=1, opsz=48, wght=700`6162### Step 3: Set Page Geometry6364```typescript65interface PageGeometry {66 size: 'letter' | 'a4' | 'legal'; // Default: 'letter'67 margins: {68 top: number; // Default: 72pt (1in)69 right: number; // Default: 72pt70 bottom: number; // Default: 72pt71 left: number; // Default: 72pt72 };73 columns: 1 | 2; // Default: 174 headerHeight: number; // Default: 36pt75 footerHeight: number; // Default: 36pt76}77```7879### Step 4: Configure Pretext Measurement8081Pretext's `prepare()` and `layout()` functions take specific parameters:8283```typescript84// One-time per text block85const prepared = pretext.prepare(text, fontDescriptor);8687// Fast re-layout at any width88const layout = pretext.layout(prepared, maxWidth, lineHeight);89// Returns: { width, height, lines[] }90```9192Key: the `fontDescriptor` must match the actual font loaded in the Canvas context. Mismatches cause measurement drift.9394### Step 5: Apply and Validate9596After configuration, export a test page and verify:97- Body text line length is 45-75 characters (optimal readability)98- Code blocks don't overflow margins99- Headings have appropriate visual weight100- Page breaks respect orphan/widow rules (min 2 lines)101102---103104## IV. Quality Checklist105106Before handing a typography configuration to `pdf-export`, verify:107108- [ ] Content type was identified (prose / code / chat / mixed) and the matching profile was used as the starting point — not the generic default109- [ ] Body text line length will fall in the 45-75 character range at the chosen font size and margin110- [ ] Code blocks use Recursive with `MONO=1` and minimum `1.3` line height111- [ ] Heading optical size (`opsz`) matches the intended render size, not the body default112- [ ] No `system-ui` in any `fontDescriptor` — explicit family names only (`Inter`, `Recursive`, `Fraunces`)113- [ ] Measurement units are consistent: pt/px conversion is handled by the bridge exactly once114- [ ] Orphan/widow rule is respected: minimum 2 lines before or after a page break115- [ ] Two-column layouts were checked for tall code blocks that cannot be split across columns116- [ ] For DojoChat exports: `dispositionToTypography` was called per agent, not a single shared config applied to all bubbles117- [ ] A test page was rendered and inspected before committing to the full export118119---120121## V. Reflection Questions122123- Is the content primarily prose, code, or mixed? Each has different optimal settings.124- Who is the audience? Technical readers tolerate denser typography; general audiences need more whitespace.125- Does the content have a brand or design system to align with?126- Are there accessibility requirements (minimum font size, contrast ratios)?127128---129130## VI. Common Mistakes131132- **Using system-ui in Canvas context.** Canvas and DOM resolve `system-ui` differently on macOS. Always specify exact font family names.133- **Ignoring optical sizing.** Inter and Fraunces have `opsz` axes — using body optical size for headings produces thin, fragile letterforms.134- **Setting line height too tight for code.** Code blocks with `1.0` line height make indentation guides invisible. Minimum `1.3` for code.135- **Mixing measurement contexts.** Pretext measures in CSS pixels. PDF uses points. The bridge handles conversion — don't double-convert.136137---138139## VII. Variations140141### Disposition-Driven Typography (DojoChat)142143Map ADA agent personality traits to typographic choices:144145```typescript146function dispositionToTypography(disposition: Disposition): TypographyConfig {147 return {148 fontFamily: disposition.tone === 'formal' ? 'Inter' : 'Recursive',149 fontSize: mapVerbosity(disposition.verbosity), // verbose → smaller150 lineHeight: mapDepth(disposition.depth), // deep → tighter151 maxBubbleWidth: mapBrevity(disposition.brevity), // brief → narrower152 fontVariationSettings: buildVariations(disposition),153 };154}155```156157### Two-Column Reference158159For dense reference material (API docs, skill catalogs):160```typescript161{162 columns: 2,163 fontSize: 9,164 lineHeight: 1.3,165 margins: { top: 54, right: 54, bottom: 54, left: 54 }166}167```168169---170171## VIII. Example172173**Scenario:** Configure typography for exporting a Go backend specification as a client-facing PDF.1741751. Content type: Mixed prose + code → start with prose defaults1762. Audience: Client (non-technical stakeholders) → increase font to 12pt, line height 1.61773. Code blocks: Go syntax → Recursive MONO=1, 10pt, 1.35 line height1784. Headings: Fraunces 700, SOFT=30, WONK=1 for professional warmth1795. Page: Letter, 1-inch margins, single column1806. Validate: Body line length = 58 characters (within 45-75 range)181182---183184## IX. See Also185186- `pdf-export` — Main export workflow that consumes typography configuration187- Pretext source: `@chenglou/pretext` — Canvas-based typography engine188- DojoChat disposition mapper: `src/lib/disposition/typography.ts`189190---191192## Output193194A `TypographyConfig` object ready for consumption by the Pretext `prepare()`/`layout()` pipeline, plus page geometry settings for the PDF renderer. Concretely: validated font descriptors, line height values per content zone (body/code/headings), page margins in points, and column count. When used with `pdf-export`, the output becomes embedded in the rendered PDF — font choices and spacing are baked into the byte stream, not post-processable.195196---197198## Examples199200**Scenario 1 — Client-facing Go spec PDF (mixed prose + code):**201Content type: prose primary, code secondary. Audience: non-technical client.202- Body: Inter 12pt, opsz=16, line height 1.6203- Code: Recursive MONO=1, 10pt, line height 1.35204- Headings: Fraunces 700, SOFT=30, WONK=1205- Page: Letter, 72pt margins, single column206- Validate: body line ~58 chars (within 45-75 target)207208**Scenario 2 — Dense code review PDF:**209Content type: code-primary (diff output, function signatures). Audience: senior engineers.210- Body: Inter 9pt, opsz=14, line height 1.3211- Code: Recursive MONO=1, 9pt, line height 1.3 (same size as body — intentional)212- Headings: Inter 700, no Fraunces213- Page: Letter, 54pt margins, potentially 2-column214- Validate: code blocks fit within 504pt max width without overflow215216---217218## Edge Cases219220- **Canvas vs. DOM font resolution on macOS:** `system-ui` resolves to different faces in Canvas and DOM contexts. Always pass explicit family names (`Inter`, `Recursive`, `Fraunces`) to `fontDescriptor`.221- **Optical sizing mismatches at large scales:** `Inter` at 48pt (heading) with `opsz=16` (body default) produces thin, fragile letterforms. Set `opsz` to match intended display size.222- **Mixed content with extremely long code lines:** If source code has 120+ char lines and the page is letter-width, either reduce font size or switch to landscape. The layout engine will not auto-truncate — overflow is clipped.223- **Recursive `CASL` axis in monospace mode:** Setting `MONO=1, CASL=1` simultaneously produces undefined intermediate rendering on some PDF engines. Use `CASL=0` when `MONO=1`.224- **Two-column layout with tall code blocks:** Page-break logic does not split code blocks across columns. A single tall block will overflow into the gutter.225226---227228## Anti-Patterns229230- **Specifying `system-ui` in any Pretext context.** Breaks cross-machine reproducibility and causes Canvas/DOM measurement drift. Use explicit font family names.231- **Ignoring `opsz` at heading sizes.** Using body `opsz` (14-16) for large headings (24-48pt) produces optically incorrect letterforms. Set `opsz` to match render size.232- **Setting line height below 1.3 for code.** At `1.0-1.2`, indentation guides and leading dots between columns become invisible. Minimum `1.3` for monospace content.233- **Double-converting pt/px units.** Pretext measures in CSS pixels internally; the bridge converts to PDF points. Manually converting before passing to the API results in double-scaling (typically 72/96 = 0.75x error).234- **Applying a single TypographyConfig across all zones.** Body, code, captions, and headings each need separate configuration. A one-size config either makes code unreadable or body text too large.