Spacing tools
These tools help you review and optionally apply consistent sidebearings across a set of glyphs in a Glyphs font.
review_spacingcomputes measurements and returns suggested metrics (no mutation).apply_spacinguses the same logic to apply those suggestions (with a required confirmation gate).
The intent is to make spacing more systematic and repeatable, while keeping you in control through:
- clear per-glyph reporting, and
- conservative safety defaults (skip rules + clamping).
What the tools change (and what they don’t)
Changes
When applied, these tools adjust:
- LSB (left sidebearing)
- RSB (right sidebearing)
- optionally width (mainly for tabular figures / fixed-width targets)
Does not change
- Outlines (paths)
- Kerning
- Components (except they may be included in measurement)
- Anchors
Core idea (plain language)
For each glyph layer (glyph + master):
- Choose a vertical measurement band (
yMin..yMax) using a reference glyph. - At regular
ysteps (frequency), cast a horizontal “measurement line” through the glyph. - For each
y, find the first intersection from the left and the last intersection from the right. - Convert those intersections into two numbers:
- how much the glyph intrudes to the left of its “left edge” in that band,
- how much it intrudes to the right of its “right edge” in that band.
- Integrate (sum) these values across the band to get left/right “area” measurements.
- Compare the measured areas to a target area, then compute suggested LSB/RSB changes.
This is designed to approximate how much “white space” a reader perceives around a glyph, not just the extreme sidebearings at one height.
Reference glyphs and the measurement band
Many glyphs need different vertical measurement ranges:
- Lowercase letters typically want a band around the x-height region.
- Uppercase letters may want a taller band.
- Punctuation and symbols can require special handling.
These tools use a reference glyph to define the band:
- Find the reference layer’s bounds in the same master.
- Take
yMin..yMaxfrom those bounds. - Extend it by
over(as a percent of xHeight) above and below.
If a glyph doesn’t overlap enough with the band (coverage ratio), it is skipped to avoid nonsense measurements.
Important safety skips (by default)
The tools skip glyphs/layers when they are likely to be unsafe or misleading to auto-space:
- Empty layers (no paths and no components).
- Combining marks / nonspacing marks (they should typically stay width 0).
- Auto-aligned component layers (
layer.isAligned == true) whenskipAutoAligned=true. - Metrics keys:
- If both left and right metrics keys are present and
respectMetricsKeys=true, the glyph is skipped. - If only one side has a metrics key, the tool will keep that side unchanged and only suggest the other side.
- If both left and right metrics keys are present and
- Insufficient vertical coverage (the glyph doesn’t meaningfully overlap the band).
You’ll see the exact skip reason in each result entry.
Parameters
You can pass parameters as:
defaults(per-call), and/or- font-level or master-level custom parameters (read automatically when present):
- Canonical (recommended):
cx.ap.spacingArea,cx.ap.spacingDepth,cx.ap.spacingOver,cx.ap.spacingFreq - Legacy aliases (read-only compatibility):
gmcpSpacingArea,gmcpSpacingDepth,gmcpSpacingOver,gmcpSpacingFreqparamArea,paramDepth,paramOver,paramFreq
- Canonical (recommended):
Best way to set parameters (recommended)
Use custom parameters inside the .glyphs file for values that should be consistent for everyone using the font:
- good for:
cx.ap.spacingArea,cx.ap.spacingDepth,cx.ap.spacingOver,cx.ap.spacingFreq - benefits: travels with the font, persists across sessions, and can differ per master
Use a JSON config file for values you want to version-control and share as “spacing presets”:
- good for:
rules, plus anydefaultsyou want to keep in sync across a team - benefits: diffable, reviewable, easy to keep multiple presets (e.g. “text”, “display”, “UI”)
In practice, a good split is:
- In the font (custom parameters):
cx.ap.spacingArea/Depth/Over/Freq(recommended) - In a file:
rules+ any per-project defaults likereferenceGlyph,italicMode,minCoverageRatio, tabular settings
Precedence (what wins)
For each numeric parameter (area, depth, over, frequency) the tools resolve values in this exact order:
- Per-call
defaults(if provided) - Master custom parameter (canonical
cx.ap.spacing*) - Master custom parameter (legacy
gmcpSpacing*) - Master custom parameter (legacy
param*) - Font custom parameter (canonical
cx.ap.spacing*) - Font custom parameter (legacy
gmcpSpacing*) - Font custom parameter (legacy
param*) - Internal default
Setting spacing params in Glyphs (UI)
You can set the parameters in Glyphs’ UI:
- Font-level:
File → Font Info… → Font → Custom Parameters - Per-master:
File → Font Info… → Masters → (select a master) → Custom Parameters
Add any of these keys (case-sensitive). Recommended canonical names:
cx.ap.spacingArea(number)cx.ap.spacingDepth(number, percent of xHeight)cx.ap.spacingOver(number, percent of xHeight)cx.ap.spacingFreq(number, y sampling step in units)
Legacy aliases (still read, but not recommended for new work):
gmcpSpacingArea,gmcpSpacingDepth,gmcpSpacingOver,gmcpSpacingFreqparamArea,paramDepth,paramOver,paramFreq
Font-level parameters act as defaults; master-level parameters override them.
HT compatibility mode (legacy param* keys)
If you previously used HTLetterspacer, your masters (or font) may already contain:
paramArea,paramDepth,paramOver,paramFreq
review_spacing / apply_spacing will automatically read these as legacy keys when the canonical cx.ap.spacing* keys are not present.
Migration (recommended): write canonical keys once (so future tools and collaborators see the branded keys in Font Info), then save:
{
"font_index": 0,
"scope": "font",
"params": { "area": 400, "depth": 15, "over": 0, "frequency": 5 }
}
Then call save_font to persist.
Setting spacing params in Glyphs (Macro Panel script)
If you want to write them into the font programmatically, paste this into Glyphs’ Macro Panel:
from GlyphsApp import Glyphs
font = Glyphs.font
if not font:
raise RuntimeError("No active font")
# Font-wide defaults
font.customParameters["cx.ap.spacingArea"] = 400
font.customParameters["cx.ap.spacingDepth"] = 15
font.customParameters["cx.ap.spacingOver"] = 0
font.customParameters["cx.ap.spacingFreq"] = 5
# Optional: override per master
for master in font.masters:
if master.name == "Display":
master.customParameters["cx.ap.spacingArea"] = 440
Setting parameters via MCP (set_spacing_params)
If you prefer not to use the UI or Macro Panel, use the MCP tool set_spacing_params (it does not auto-save).
Font-level defaults:
{
"font_index": 0,
"scope": "font",
"params": { "area": 400, "depth": 15, "over": 0, "frequency": 5 }
}
Override one master:
{
"font_index": 0,
"scope": "master",
"master_id": "MASTER_ID_HERE",
"params": { "area": 440 }
}
Delete a value (unset):
{
"font_index": 0,
"scope": "font",
"params": { "over": null }
}
Preview changes without writing:
{
"font_index": 0,
"scope": "font",
"params": { "area": 420 },
"dry_run": true
}
To persist after setting values, call save_font.
Using a text file (JSON) for defaults + rules
There’s an example config file at:
./spacing-config.example.json
Recommended pattern:
- Keep your preferred
defaults+rulesin a JSON file in your repo. - When calling
review_spacing/apply_spacing, load that JSON in your client and pass:defaults: config.defaultsrules: config.rules
The spacing tools do not currently read rules from the font automatically; passing them explicitly keeps the behavior transparent and reproducible.
defaults fields
All values are optional; omitted fields fall back to internal defaults.
area(number, default400)- Controls the “target white-space area” magnitude.
- Larger values generally produce looser spacing.
depth(number, percent of xHeight, default15)- Limits how far measurements are allowed to reach “into” openings.
- Helps avoid letting deep counters dominate results.
over(number, percent of xHeight, default0)- Extends the measurement band above/below the reference glyph’s bounds.
frequency(number, default5)- Sampling step in font units along the y-axis.
- Smaller values are slower but can be more stable.
referenceGlyph(string, default"x")- Fallback reference when no rule overrides it.
- Special value
"*"means “use the glyph itself as reference”.
factor(number, default1.0)- Global multiplier applied to the target area (usually overridden by rules).
italicMode("deslant"or"none", default"deslant")- When
"deslant", measurements are corrected using masteritalicAngle.
- When
includeComponents(bool, defaulttrue)- Whether to include components in intersection measurement.
respectMetricsKeys(bool, defaulttrue)skipAutoAligned(bool, defaulttrue)minCoverageRatio(number0..1, default0.7)tabularMode(bool, defaultfalse)- If true, and if a width target is available, the tool will keep width fixed by distributing width adjustments across LSB/RSB.
tabularWidth(number or null, defaultnull)- If set, becomes the fixed width target used by
tabularMode.
- If set, becomes the fixed width target used by
includeSamples(bool, defaultfalse)- If true,
review_spacingincludes per-y sample arrays (larger payload).
- If true,
Rules (per-glyph overrides)
The rules parameter is a list of objects. The best matching rule wins.
Each rule may include:
script(string or"*")category(string or"*")subCategory(string or"*")nameRegex(regex string, optional) ornameFilter(substring, optional)referenceGlyph(string;"*"uses the glyph itself)factor(number)
Matching behavior
- A rule matches when all specified fields match.
- More specific rules win over wildcard rules.
- If two rules are equally specific, the later rule in the list wins (so you can append overrides).
Tool reference
review_spacing
Computes a per-glyph spacing report and suggestions.
Inputs
font_index(int, default0)glyph_names(list of strings, optional)- If omitted, the tool uses the currently selected glyphs in Glyphs only if
font_indexis the active font.
- If omitted, the tool uses the currently selected glyphs in Glyphs only if
master_id(string, optional)- If omitted, evaluates all masters.
rules(list, optional)defaults(object, optional)debug(object, optional)- Currently supports
includeSamples.
- Currently supports
Output
okbooleansummarycounts and effective defaultsresultslist of per-layer entries:status:"ok" | "skipped" | "error"reason(when skipped/error)currentmetricsreferenceband infomeasuredareas/extremestargetvaluessuggestedmetricsdeltavs currentwarningslist
Metric types
current.width/lsb/rsb,suggested.width/lsb/rsb, anddelta.width/lsb/rsbare always integers (font units).- Other numeric fields (
measured.*,target.*,reference.*, etc.) may be floats.
apply_spacing
Applies the suggestions computed by the same engine.
Safety gates
- If
confirm=falseanddry_run=false, the tool refuses to run. - Use
dry_run=truefirst to preview.
Inputs
Same as review_spacing, plus:
clamp(object, optional)maxDeltaLSB(default150)maxDeltaRSB(default150)minLSB(default-200)minRSB(default-200)
confirm(bool, defaultfalse)dry_run(bool, defaultfalse)
Output
okbooleansummaryresults(the same analysis results)appliedlist (only when actually applied) with before/after metrics
set_spacing_guides
Adds or clears glyph-level guides (layer.guides) that visualize the spacing model’s geometry:
- the vertical measurement band (
yMin..yMax), and (by default) - the key x′ boundaries used to compute negative space (zone edges, depth clamp, and average whitespace).
This is purely a visualization aid. It:
- writes guides into glyph layers (so they travel with the
.glyphsfile), - does not change metrics,
- does not auto-save.
Inputs
font_index(int, default0)glyph_names(list of strings, optional)- If omitted, uses selected glyphs in Glyphs (active font). If nothing is selected, it falls back to a small “diagnostic set”:
n,H,zero,o,O,period,comma.
- If omitted, uses selected glyphs in Glyphs (active font). If nothing is selected, it falls back to a small “diagnostic set”:
master_scope(string, default"current")"current": only the currently selected master"all": all masters"master": a specific master viamaster_id
master_id(string, required whenmaster_scope="master")mode("add"or"clear", default"add")reference_glyph(string, default"x")- Special value
"*"means “use the glyph itself”.
- Special value
style("band" | "model" | "full", default"model")"band": only the vertical measurement band (two horizontal guides)."model": band + the most didactic x′ boundaries (zone, depth clamp, measured vs target averages)."full":"model"plus extra reference bounds and full extremes (more clutter).
dry_run(bool, defaultfalse)
Output
okbooleansummarycountsresultslist with per-glyph/per-master actions, the computed band, and guide names
Notes
- To see them in the editor, enable
View → Show Guides. - By default,
mode="add"clears previously created spacing guides for the target layers (so it’s idempotent). - In italic masters with
italicMode="deslant", x′ boundary guides are drawn as slanted lines so they represent a constant deslanted-x across the band (i.e. they match the model’s coordinate space).
What the guides mean (didactic)
Band (yMin/yMax)
cx.ap.spacing.band:min/cx.ap.spacing.band:max- The exact vertical region the model samples at
frequencysteps.
- The exact vertical region the model samples at
Zone edges (what counts as “the edge”)
cx.ap.spacing.zone:L/cx.ap.spacing.zone:R- The model’s left/right “reference edges” inside the band (
lExtreme/rExtreme), expressed in x′ space (deslanted if italic).
- The model’s left/right “reference edges” inside the band (
Depth clamp (how far into openings we measure)
cx.ap.spacing.depth:L/cx.ap.spacing.depth:R- The clamp limits derived from
spacingDepth(percent of x-height), starting from the zone edges. - If the glyph has deep counters, the clamp prevents extreme “inward” measurements from dominating.
- The clamp limits derived from
Average whitespace (measured vs target)
cx.ap.spacing.avg.measured:L/cx.ap.spacing.avg.measured:R- Where the current average whitespace sits on each side (area ÷ height).
cx.ap.spacing.avg.target:L/cx.ap.spacing.avg.target:R- Where the model wants the average whitespace to sit, based on
spacingArea(after scaling by UPM/x-height).
- Where the model wants the average whitespace to sit, based on
Only in style="full"
cx.ap.spacing.ref:min/cx.ap.spacing.ref:max- The raw reference bounds without
spacingOver(useful to understand whatoveris doing).
- The raw reference bounds without
cx.ap.spacing.full:L/cx.ap.spacing.full:R- The full extremes across the glyph’s own bounds (used for overshoot compensation in the engine).
Recommended workflow
- Select glyphs you want to space (or pass
glyph_namesexplicitly). - Run
review_spacing:- Check skip reasons.
- Look at the largest
deltaoutliers.
- Tune
rulesand/ordefaults:- pick better reference glyphs for punctuation and symbols
- adjust
factorper class
- Run
apply_spacing(dry_run=true)to confirm clamps and diffs. - Run
apply_spacing(confirm=true)to apply. - Repeat: re-run
review_spacingto confirm deltas are near zero.
Practical examples
Review selected glyphs (active font)
Call:
{
"font_index": 0
}
Review specific glyphs across all masters
{
"font_index": 0,
"glyph_names": ["H", "O", "n", "o", "period", "comma"],
"defaults": {
"referenceGlyph": "n",
"frequency": 5,
"area": 420
}
}
Apply with a conservative clamp (two-step)
Dry run:
{
"font_index": 0,
"glyph_names": ["H", "O", "n", "o"],
"dry_run": true,
"clamp": { "maxDeltaLSB": 80, "maxDeltaRSB": 80 }
}
Apply:
{
"font_index": 0,
"glyph_names": ["H", "O", "n", "o"],
"confirm": true,
"clamp": { "maxDeltaLSB": 80, "maxDeltaRSB": 80 }
}
Limitations and notes
- Measurements rely on
layer.intersectionsBetweenPoints(...), which behaves like the Glyphs measurement tool; unusual outlines, open paths, or complex overlaps can produce sparse or noisy intersections. - Auto-spacing does not replace human review. Use text strings and proofing after applying.
- If your font heavily uses metrics keys or auto-aligned component glyphs, many layers will be skipped by design.
Prompt templates (copy/paste)
Note: tool name prefixes vary by client. If your tools aren’t named
glyphs-app-mcp__*, replace that prefix with whatever your MCP client shows.
HTspacer-style spacing pass (review → dry-run → apply)
You are a meticulous spacing assistant for a type designer working in Glyphs.
Rules:
- Never auto-save.
- Never mutate without a dry run first.
- Keep changes conservative (prefer clamping).
- If glyph_names isn't provided, use my current selection in the active font.
Task: Review and (optionally) apply spacing suggestions to improve rhythm and consistency.
1) Call glyphs-app-mcp__review_spacing:
{"font_index":0}
2) Summarize:
- Top 15 layers by |delta.lsb| + |delta.rsb| (and why they’re outliers)
- Skipped layers grouped by reason (metrics keys, low coverage, etc.)
- Any warnings that suggest a bad measurement band / reference glyph
3) Call glyphs-app-mcp__apply_spacing (dry run with a conservative clamp):
{"font_index":0,"dry_run":true,"clamp":{"maxDeltaLSB":80,"maxDeltaRSB":80,"minLSB":-50,"minRSB":-50}}
4) If I say “apply”, call apply_spacing again with confirm=true using the same clamp.
5) If I say “save”, call glyphs-app-mcp__save_font.
Tabular figures spacing (fixed width)
I want tabular figures to keep a fixed width by distributing width changes evenly across LSB/RSB.
First, select your figure glyphs in Glyphs (Font view), then:
1) Call glyphs-app-mcp__review_spacing:
{"font_index":0,"defaults":{"tabularMode":true,"tabularWidth":600,"referenceGlyph":"zero"}}
2) Call glyphs-app-mcp__apply_spacing (dry run):
{"font_index":0,"dry_run":true,"defaults":{"tabularMode":true,"tabularWidth":600,"referenceGlyph":"zero"},"clamp":{"maxDeltaLSB":60,"maxDeltaRSB":60,"minLSB":-50,"minRSB":-50}}
3) If I approve, call apply_spacing again with confirm=true using the same args.
Visualize the spacing model with guides (expert)
Paste this into your LLM/client as a single prompt. It is designed to be safe, practical, and typography-oriented.
Role: You are a meticulous spacing assistant for a type designer working in Glyphs.
Rules: Never auto-save. Usedry_runbefore mutating tools. Prefer a small diagnostic glyph set unless the user provides specific glyphs.
Task: Add spacing-model guides so I can visually confirm what the spacing engine is measuring (band, zone edges, depth clamp, and measured-vs-target averages) in the current master, then tell me what to look for.
- Call
glyphs-app-mcp__set_spacing_guideswith:
{
"font_index": 0,
"master_scope": "current",
"mode": "add",
"style": "model",
"reference_glyph": "x"
}
- Tell me exactly how to visualize them in Glyphs:
- Turn on
View → Show Guides. - Open the diagnostic glyphs (e.g.
n,H,zero,o,O,period,comma) and verify:- The two horizontal guides match the intended measurement band for the current master.
- The x′ guides are meaningful:
zone:L/Rroughly bracket the “main body” of the glyph inside the band.depth:L/Rshow how far into counters/openings the model is allowed to “look”.avg.measured:*vsavg.target:*shows whether the glyph is currently too tight/loose on each side.
- In italics (if any), confirm x′ guides are slanted (they should represent constant deslanted-x across the band).
- If the band looks wrong, help me correct it:
- If it’s too tall/short: suggest adjusting
cx.ap.spacingOver. - If it’s using an unhelpful reference: suggest a better
reference_glyph(e.g.nfor lowercase,Hfor uppercase,zerofor figures) and re-runset_spacing_guides. - If the depth clamp looks wrong: suggest adjusting
cx.ap.spacingDepth(percent of x-height). - If target averages look too tight/loose globally: suggest adjusting
cx.ap.spacingArea.
- When I say “clear guides”, call:
{
"font_index": 0,
"master_scope": "all",
"mode": "clear"
}