Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 270 lines 8.3 kB view raw view rendered
1# Unifont Character Width Fix 2 3## Problem Summary 4 5The ghost hint text in `prompt.mjs` was showing incorrect spacing for different languages, particularly: 6- Japanese appearing "too spaved" (too much spacing) 7- Hindi background width slightly too wide 8- Arabic background width wrong 9- Language labels mii dnt see it animting all the saligned 10 11## Root Cause 12_ 13**Hardcoded character widths** instead of using actual BDF glyph widths: 14 151. **In prompt.mjs (lines 3280-3294)**: 16 - Manually calculated text width using regex to detect CJK characters 17 - Assumed 16px for CJK, 8px for others 18 - Added special cases for Hindi (+12px padding) 19 - This didn't match actual rendered text width 20 212. **In type.mjs (lines 559)**: 22 - `getAdvance()` method was hardcoded to return 8px for **all** unifont characters 23 - This was added to "prevent marquee jank when glyphs load asynchronously" 24 - But it prevented using actual BDF DWIDTH values from the font file 25 263. **Reality**: 27 - Unifont BDF file contains actual glyph widths in DWIDTH field 28 - CJK characters are typically 16px wide in unifont 29 - But NOT ALL characters in Unicode ranges are the same width 30 - Some Hindi/Arabic characters may have different actual widths 31 32## Architecture Understanding 33 34### Text Rendering Pipeline 35 36``` 37write() → text.box() → typeface.print() → $.printLine() 38``` 39 401. **write()**: Entry point for text rendering (disk.mjs) 412. **text.box()**: Layout calculation using `tf.getAdvance(char)` for each character 423. **typeface.print()**: Character-by-character rendering with proper spacing 434. **$.printLine()**: Low-level pixel rendering using BDF glyph data 44 45### BDF Font System 46 47- **BDF files**: Located at `/workspaces/aesthetic-computer/system/public/assets/type/` 48 - `unifont-16.0.03.bdf.gz` (compressed) 49 - `MatrixChunky8.bdf` 50 51- **BDF endpoint**: `/system/netlify/functions/bdf-glyph.js` 52 - Fetches and parses BDF file 53 - Extracts glyph data including DWIDTH (advance width) 54 - Returns JSON with: 55 - `resolution`: [width, height] 56 - `advance`: DWIDTH.x value (character advance width) 57 - `commands`: Drawing commands (points/lines) 58 59- **Glyph loading**: Asynchronous via network requests 60 - Typeface.load() preloads common characters 61 - On-demand loading for other characters 62 - Glyphs cached in `this.glyphs[char]` 63 64### Advance Width Calculation 65 66**type.mjs - getAdvance() method**: 67 68```javascript 69getAdvance(char) { 70 if (!char) return this.blockWidth || 4; 71 72 // OLD CODE (incorrect): 73 // if (this.name === "unifont" ...) return 8; // Always 8px! 74 75 // NEW CODE (correct): 76 if (this.name === "unifont" || this.data?.bdfFont === "unifont-16.0.03") { 77 const glyph = this.glyphs?.[char]; 78 if (glyph && typeof glyph.advance === "number") { 79 return glyph.advance; // Use actual BDF DWIDTH 80 } 81 return 8; // Fallback during async loading 82 } 83 84 // Check advance cache 85 if (this.advanceCache.has(char)) { 86 return this.advanceCache.get(char); 87 } 88 89 // Try to get advance from glyph data 90 const glyph = this.glyphs?.[char]; 91 if (glyph && typeof glyph.advance === "number") { 92 return glyph.advance; 93 } 94 95 // Fallbacks... 96} 97``` 98 99## Solution 100 101### 1. Updated type.mjs getAdvance() (line 553-574) 102 103**Changed from**: 104```javascript 105if (this.name === "unifont" || this.data?.bdfFont === "unifont-16.0.03") { 106 return 8; // Hardcoded! 107} 108``` 109 110**Changed to**: 111```javascript 112if (this.name === "unifont" || this.data?.bdfFont === "unifont-16.0.03") { 113 const glyph = this.glyphs?.[char]; 114 if (glyph && typeof glyph.advance === "number") { 115 return glyph.advance; // Use actual BDF DWIDTH 116 } 117 return 8; // Fallback during loading 118} 119``` 120 121**Why this works**: 122- Uses actual BDF advance widths when glyphs are loaded 123- Still prevents "marquee jank" by falling back to 8px during async loading 124- Allows CJK characters to be 16px, Latin to be 8px, etc. 125 126### 2. Updated prompt.mjs text width calculation (line 3277-3286) 127 128**Changed from**: 129```javascript 130// Manual calculation with regex 131let textWidth = 0; 132let hasHindi = false; 133for (let i = 0; i < cleanGhostText.length; i++) { 134 const char = cleanGhostText[i]; 135 const isDoubleWidth = /[\u4E00-\u9FFF...]/.test(char); 136 const isHindi = /[\u0900-\u097F]/.test(char); 137 if (isHindi) hasHindi = true; 138 textWidth += isDoubleWidth ? 16 : 8; 139} 140const bgWidth = hasHindi ? textWidth + 12 : textWidth + 4; 141``` 142 143**Changed to**: 144```javascript 145// Use text.box API for accurate measurement 146const textMeasurement = api.text.box( 147 cleanGhostText, 148 { x: 0, y: 0 }, 149 undefined, 150 1, 151 false, 152 "unifont" 153); 154const textWidth = textMeasurement?.box?.width || (cleanGhostText.length * 8); 155const bgWidth = textWidth + 4; // Consistent 4px padding 156``` 157 158**Why this works**: 159- `text.box()` calls `getAdvance()` for each character 160- Gets actual widths from loaded BDF glyphs 161- No more special cases needed for different scripts 162- Background box width now matches actual rendered text 163 164### 3. Updated prompt.mjs per-character positioning (line 3295-3316) 165 166**Changed from**: 167```javascript 168let charX = textX; 169for (let i = 0; i < cleanGhostText.length; i++) { 170 const char = cleanGhostText[i]; 171 // ... render char ... 172 const charWidth = /[\u4E00-\u9FFF...]/.test(char) ? 16 : 8; // Regex! 173 charX += charWidth; 174} 175``` 176 177**Changed to**: 178```javascript 179let charX = textX; 180for (let i = 0; i < cleanGhostText.length; i++) { 181 const char = cleanGhostText[i]; 182 // ... render char ... 183 const charMeasurement = api.text.box( 184 char, 185 { x: 0, y: 0 }, 186 undefined, 187 1, 188 false, 189 "unifont" 190 ); 191 const charWidth = charMeasurement?.box?.width || 8; 192 charX += charWidth; 193} 194``` 195 196**Why this works**: 197- Each character measured individually 198- Uses actual BDF advance width 199- Accurate positioning for rainbow effect 200- Language label now aligns correctly with text right edge 201 202## Benefits 203 2041. **Accurate spacing**: All languages now space correctly based on actual font metrics 2052. **No special cases**: Removed Hindi/Arabic special padding logic 2063. **Future-proof**: Works for any character unifont supports 2074. **Maintains performance**: Still uses 8px fallback during async loading 208 209## Technical Notes 210 211### BDF DWIDTH Field 212 213From BDF specification: 214``` 215DWIDTH x y 216``` 217- `x`: Character advance width (horizontal spacing to next character) 218- `y`: Usually 0 (vertical advance for vertical text layouts) 219 220Example from unifont: 221- Latin 'A': DWIDTH 8 0 222- CJK '中': DWIDTH 16 0 223- Hindi 'स': DWIDTH 8 0 224- Arabic 'د': DWIDTH 8 0 225 226### Why Regex Approach Failed 227 228Unicode ranges don't guarantee uniform character widths: 229- Not all CJK block characters are 16px wide 230- Some punctuation in CJK ranges might be 8px 231- Combining marks have different advances 232- Font designer determines actual widths per glyph 233 234### Performance Considerations 235 236Calling `text.box()` per character in a loop adds overhead, but: 237- Only happens for ghost hint (max ~10 characters) 238- Only when curtain is up and prompt is empty 239- Benefit of accurate spacing outweighs cost 240- Could optimize later with single measurement + char offsets 241 242## Testing Checklist 243 244- [ ] Test all 14 languages for correct spacing 245- [ ] Verify background box width matches text width 246- [ ] Check language labels align with text right edge 247- [ ] Ensure rainbow colors apply to correct character positions 248- [ ] Test with very long translations (wrap behavior) 249- [ ] Verify no "marquee jank" during initial glyph loading 250 251## Related Files 252 253- `/workspaces/aesthetic-computer/system/public/aesthetic.computer/lib/type.mjs` 254 - Line 553-574: `getAdvance()` method 255 256- `/workspaces/aesthetic-computer/system/public/aesthetic.computer/disks/prompt.mjs` 257 - Line 3277-3286: Text width calculation 258 - Line 3295-3316: Per-character positioning 259 260- `/workspaces/aesthetic-computer/system/public/aesthetic.computer/disks/common/fonts.mjs` 261 - Line 107-111: Unifont font definition 262 263- `/workspaces/aesthetic-computer/system/netlify/functions/bdf-glyph.js` 264 - BDF parsing and glyph data extraction 265 266## References 267 268- [BDF Font Format Specification](https://adobe-type-tools.github.io/font-tech-notes/pdfs/5005.BDF_Spec.pdf) 269- [GNU Unifont Documentation](http://unifoundry.com/unifont/) 270- Previous fix: `plans/unifont-moods-fix.md` (forced 8px width for all)