|
| 1 | +--- |
| 2 | +description: Scrivener coding rules. Applied on every edit to files under scrivener/. |
| 3 | +globs: scrivener/** |
| 4 | +--- |
| 5 | + |
| 6 | +# Scrivener Rules |
| 7 | + |
| 8 | +## No Style in Code |
| 9 | + |
| 10 | +Every visual value - colors, sizes, spacing, padding - comes from |
| 11 | +a style YAML file (e.g. `styles/default.yaml`) or is proportional to |
| 12 | +body_size via `sp()`. Zero hardcoded appearance numbers in Python. |
| 13 | +Use `cfg["key"]` not `cfg.get("key", "#hex")` for values that exist |
| 14 | +in the style. No color hex strings as fallback defaults. If a key is |
| 15 | +missing, let it crash - the style is incomplete. Styles are flat YAML |
| 16 | +files in `styles/`; derived styles use `inherits: base` for cascade. |
| 17 | + |
| 18 | +## All Spacing is Proportional |
| 19 | + |
| 20 | +Use `sp(cfg, r)` for every spacing value. Never bare `Spacer(1, 4)` or |
| 21 | +`spaceAfter=6`. The function is in `lib/config.py`: |
| 22 | + |
| 23 | +```python |
| 24 | +def sp(cfg, r): |
| 25 | + """Spacing proportional to body_size.""" |
| 26 | + return cfg.get("body_size", 11) * r |
| 27 | +``` |
| 28 | + |
| 29 | +Ratios use the form `N/11` so the original value is visible: |
| 30 | +`sp(cfg, 4/11)` produces 4pt at body_size 11. |
| 31 | + |
| 32 | +Values already in style.yaml (heading spacing, list indent, etc.) are |
| 33 | +absolute and stay as-is - the user controls them directly. |
| 34 | + |
| 35 | +## No Duplicated Drawing Logic |
| 36 | + |
| 37 | +The accent-bar-plus-background-box pattern lives in `AccentBox` |
| 38 | +(`lib/flowables.py`). Both front-matter and blockquotes use it. |
| 39 | +Do not recreate this pattern elsewhere. |
| 40 | + |
| 41 | +## Do Not Add YAML Keys Without Asking |
| 42 | + |
| 43 | +Adding new keys to style.yaml is a last resort. Before adding a key, |
| 44 | +ask the user. Derive values from existing keys whenever possible. |
| 45 | +Example: the title uses headings.h1.scale - it does not get its own |
| 46 | +font_scale key. |
| 47 | + |
| 48 | +## Short Variable Names |
| 49 | + |
| 50 | +Prefer `bs`, `cfg`, `fm`, `lh`, `cb`, `bq`, `s`, `r`, `w`, `h`. |
| 51 | +Spell out only when meaning is unclear from context. |
| 52 | + |
| 53 | +## File Map |
| 54 | + |
| 55 | +- `scrivener.py` - CLI entry point. Argparse, help text, main(). |
| 56 | + No rendering logic. |
| 57 | +- `lib/__init__.py` - Package marker. `escape_xml()` utility. |
| 58 | +- `lib/builder.py` - `build_pdf()` orchestration. Wires config, |
| 59 | + fonts, renderer, and ReportLab document. |
| 60 | +- `lib/flowables.py` - `AccentBox`, `AccentRule`, `PageChrome`. |
| 61 | + Stable drawing primitives. |
| 62 | +- `lib/fonts.py` - Font cache, variable-font instantiation, |
| 63 | + ReportLab registration. `ensure_lazy()`, `ensure_code_family()`. |
| 64 | +- `lib/colors.py` - Color math. `hex_to_hsl`, `resolve_accent`, |
| 65 | + `derive_mid`, `resolve_colors`. |
| 66 | +- `lib/config.py` - Style loading with `inherits:` cascade, |
| 67 | + `deep_merge()`, font manifest with logical IDs, |
| 68 | + `resolve_font_files()`, option handling, front-matter extraction, |
| 69 | + config merge, `sp()`, `load_logo`, `list_images()`. |
| 70 | +- `lib/highlight.py` - Syntax highlighting via Pygments. Single |
| 71 | + `highlight()` function returns ReportLab XML markup. |
| 72 | +- `lib/renderer.py` - `ASTRenderer`. AST-to-flowable conversion. |
| 73 | + The largest module. |
0 commit comments