|
| 1 | +# Font metrics for accurate column widths |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The file explorer uses a font metrics system to calculate accurate character widths for optimal column sizing in Brief |
| 6 | +mode. This ensures filenames are never truncated unnecessarily while avoiding excessive column widths. |
| 7 | + |
| 8 | +## How it works |
| 9 | + |
| 10 | +### Measurement phase (first app run) |
| 11 | + |
| 12 | +1. **Frontend measurement**: On first app start, the system measures ~67,000 characters using the Canvas API |
| 13 | + - Coverage: Basic Multilingual Plane (BMP) + common emoji |
| 14 | + - Includes CJK, Cyrillic, Arabic, Indic scripts, Latin Extended |
| 15 | + - Takes ~100-300ms, runs in background using `requestIdleCallback` |
| 16 | + |
| 17 | +2. **Binary storage**: Measurements are serialized using bincode2 and saved to disk |
| 18 | + - Location: `~/Library/Application Support/com.veszelovszki.rusty-commander/font-metrics/system-400-12.bin` |
| 19 | + - Size: ~426KB (500KB theoretical max) |
| 20 | + - Load time: ~5ms |
| 21 | + |
| 22 | +### Width calculation (every directory load) |
| 23 | + |
| 24 | +1. **Rust calculates max width**: During `list_directory_start()`, Rust: |
| 25 | + - Iterates through all filenames |
| 26 | + - Sums character widths using cached metrics |
| 27 | + - Returns the maximum width alongside `listingId` and `totalCount` |
| 28 | + |
| 29 | +2. **Frontend uses width**: BriefList receives `maxFilenameWidth` and: |
| 30 | + - Uses it for column width when available |
| 31 | + - Falls back to estimation (`containerWidth / 3`) if metrics unavailable |
| 32 | + - Automatically adapts columns to actual content |
| 33 | + |
| 34 | +## Performance |
| 35 | + |
| 36 | +- **Measurement**: ~100-300ms (one-time, on first run) |
| 37 | +- **Load from disk**: ~5ms (on subsequent runs) |
| 38 | +- **Width calculation**: ~1-10ms per directory (depends on file count) |
| 39 | +- **Total impact**: Negligible for directories under 100k files |
| 40 | + |
| 41 | +## Code organization |
| 42 | + |
| 43 | +``` |
| 44 | +src-tauri/src/ |
| 45 | + └── font_metrics/ |
| 46 | + └── mod.rs # Core metrics storage and calculation |
| 47 | +
|
| 48 | +src/lib/ |
| 49 | + └── font-metrics/ |
| 50 | + ├── index.ts # Public API |
| 51 | + └── measure.ts # Canvas measurement |
| 52 | +``` |
| 53 | + |
| 54 | +## Font configuration |
| 55 | + |
| 56 | +Currently hardcoded to match CSS: |
| 57 | + |
| 58 | +- **Font family**: system font stack (`-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif`) |
| 59 | +- **Font weight**: 400 (normal) |
| 60 | +- **Font size**: 12px (`--font-size-sm`) |
| 61 | +- **Font ID**: `system-400-12` |
| 62 | + |
| 63 | +When font settings become user-configurable, the frontend will automatically re-measure and the cache key will be |
| 64 | +updated. |
| 65 | + |
| 66 | +## Example |
| 67 | + |
| 68 | +For a directory with files: |
| 69 | + |
| 70 | +- `README.md` (9 chars × 7.2px avg = 65px) |
| 71 | +- `package.json` (12 chars × 7.2px avg = 86px) |
| 72 | +- `中文文件.txt` (7 chars × 12px avg = 84px) |
| 73 | + |
| 74 | +The system calculates the max width as 86px, ensuring all filenames fit without truncation while keeping columns |
| 75 | +compact. |
| 76 | + |
| 77 | +## Limitations |
| 78 | + |
| 79 | +- Only supports a single font configuration at a time |
| 80 | +- Unmeasured characters (rare Unicode) fall back to average width |
| 81 | +- Column width is fixed for the entire directory (not per-column) |
0 commit comments