code wiki / (root) / nx_paint_text.nx

nx_paint_text.nx source

↩ module page · 259 lines · 9967 B

1// nx_paint_text.nx -- rasterize an ASCII text string into a 2// Framebuffer using nx_font_bitmap_5x7. Phase 4 sixth primitive of 3// NISHI_BROWSER_ROADMAP. First text-rendering primitive of the 4// bits-up browser substrate. 5// 6// Algorithm: 7// For each char in the text string, lookup its 7 row bit patterns 8// from nx_font_bitmap_5x7, then for each row scan the 5 columns 9// and set a pixel for each "on" bit. 10// 11// Cell stride: 6 columns per character (5 glyph + 1 inter-char 12// space). Vertical advance: 0 (single line; nx_paint_text_lines 13// queued for wrapping). 14// 15// Pixels are written via direct framebuffer access; bounds-checked 16// per pixel (pixels off-screen are silently skipped). For 17// sub-pixel-aware AA glyph edges, use nx_paint_text_aa (queued). 18// 19// What it does NOT handle yet (Phase 4b queued, explicit in header): 20// - line wrapping (caller's responsibility for now) 21// - vertical line advance for multi-line text 22// - Unicode beyond ASCII (multi-byte UTF-8 decode) 23// - kerning pairs / proportional spacing (current is fixed cell) 24// - bold / italic / underline emphasis 25// - text-align / text-decoration CSS support 26// - anti-aliased glyph edges (current 1-bit-per-pixel) 27// - scale != 1x (would need block-fill per pixel; Phase 4c) 28// 29// Per cardinal feedback-honest-perf-verdict-no-aspirational-claims: 30// gap list is EXACT in this header. 31// 32// genealogy_id: classical_bitmap_font_rasterizer + 33// substrate_browser_phase_4_text 34// lineage_id: nishi_browser_paint_text_v1 35 36import "nx_syscalls.nx" 37import "nx_css_color_decode.nx" 38import "nx_paint_solid_rect.nx" 39import "nx_font_bitmap_5x7.nx" 40 41const NX_PAINT_TEXT_CELL_W: i64 = 6 // 5 glyph + 1 inter-char space 42const NX_PAINT_TEXT_CELL_H: i64 = 7 43 44// ---- helpers ---- 45 46// Write one foreground pixel at (px, py) in the framebuffer, if in 47// bounds. Returns 1 if the pixel was written, 0 otherwise. 48func _paint_text_pixel(fb: *Framebuffer, px: i64, py: i64, 49 r: i64, g: i64, b: i64, a: i64) -> i64 { 50 if px < 0 { return 0 } 51 if py < 0 { return 0 } 52 if px >= fb.width { return 0 } 53 if py >= fb.height { return 0 } 54 let off: i64 = (py * fb.width + px) * 4 55 fb.pixels[off + 0] = r as u8 56 fb.pixels[off + 1] = g as u8 57 fb.pixels[off + 2] = b as u8 58 fb.pixels[off + 3] = a as u8 59 return 1 60} 61 62// Rasterize one glyph at (cell_x, cell_y). Returns the count of 63// pixels actually set (post-clipping). 64func _paint_text_glyph(fb: *Framebuffer, 65 cell_x: i64, cell_y: i64, 66 ascii_char: i64, 67 r: i64, g: i64, b: i64, a: i64) -> i64 { 68 var written: i64 = 0 69 var row: i64 = 0 70 while row < 7 { 71 let bits: i64 = nx_font_5x7_glyph_row(ascii_char, row) 72 if bits < 0 { 73 // Unsupported char -- skip the whole glyph cell. 74 row = 7 75 } else { 76 var col: i64 = 0 77 while col < 5 { 78 // Leftmost pixel is bit 4 (0x10), so shift by (4 - col). 79 let shift: i64 = 4 - col 80 let bit: i64 = (bits >> shift) & 1 81 if bit == 1 { 82 written = written + _paint_text_pixel(fb, 83 cell_x + col, cell_y + row, r, g, b, a) 84 } 85 col = col + 1 86 } 87 row = row + 1 88 } 89 } 90 return written 91} 92 93// ---- public API ---- 94 95// Paint `text` (text_len bytes of ASCII) starting at (x, y) using 96// the 5x7 bitmap font in the given color. Returns total pixels set. 97// 98// Characters not supported by nx_font_5x7_glyph_row consume their 99// cell width but emit no pixels (so layout is preserved). 100func nx_paint_text(fb: *Framebuffer, 101 x: i64, y: i64, 102 text: *u8, text_len: i64, 103 color: *CssColor) -> i64 { 104 if text_len <= 0 { return 0 } 105 106 let r: i64 = color.r & 255 107 let g: i64 = color.g & 255 108 let b: i64 = color.b & 255 109 let a: i64 = color.a & 255 110 if a == 0 { return 0 } // fully transparent foreground 111 112 var total: i64 = 0 113 var i: i64 = 0 114 var line_idx: i64 = 0 115 var col_idx: i64 = 0 116 while i < text_len { 117 let c: i64 = (text[i] as i64) & 255 118 if c == 10 { // '\n' newline 119 line_idx = line_idx + 1 120 col_idx = 0 121 } else { 122 let cell_x: i64 = x + col_idx * NX_PAINT_TEXT_CELL_W 123 let cell_y: i64 = y + line_idx * NX_PAINT_TEXT_CELL_H 124 total = total + _paint_text_glyph(fb, cell_x, cell_y, c, r, g, b, a) 125 col_idx = col_idx + 1 126 } 127 i = i + 1 128 } 129 return total 130} 131 132// Paint `text` at integer `scale` (1 = same as nx_paint_text). Each glyph pixel becomes a 133// scale x scale block; cell stride = 6*scale. Used for CSS font-size hierarchy (big headings) with 134// the 5x7 bitmap font -- integer scaling keeps glyphs crisp (no resample blur). Single line only 135// (newline ignored). Returns pixels set. 136func nx_paint_text_scaled(fb: *Framebuffer, 137 x: i64, y: i64, 138 text: *u8, text_len: i64, 139 color: *CssColor, scale: i64) -> i64 { 140 if scale <= 1 { return nx_paint_text(fb, x, y, text, text_len, color) } 141 if text_len <= 0 { return 0 } 142 let r: i64 = color.r & 255 143 let g: i64 = color.g & 255 144 let b: i64 = color.b & 255 145 let a: i64 = color.a & 255 146 if a == 0 { return 0 } 147 let cw: i64 = NX_PAINT_TEXT_CELL_W * scale 148 var total: i64 = 0 149 var i: i64 = 0 150 var col_idx: i64 = 0 151 while i < text_len { 152 let c: i64 = (text[i] as i64) & 255 153 if c == 10 { 154 col_idx = col_idx + 1 // single line: treat newline as a blank cell 155 } else { 156 let cell_x: i64 = x + col_idx * cw 157 var row: i64 = 0 158 while row < 7 { 159 let bits: i64 = nx_font_5x7_glyph_row(c, row) 160 if bits < 0 { row = 7 } 161 else { 162 var col: i64 = 0 163 while col < 5 { 164 let shift: i64 = 4 - col 165 let bit: i64 = (bits >> shift) & 1 166 if bit == 1 { 167 var dy: i64 = 0 168 while dy < scale { 169 var dx: i64 = 0 170 while dx < scale { 171 total = total + _paint_text_pixel(fb, 172 cell_x + col * scale + dx, y + row * scale + dy, r, g, b, a) 173 dx = dx + 1 174 } 175 dy = dy + 1 176 } 177 } 178 col = col + 1 179 } 180 row = row + 1 181 } 182 } 183 col_idx = col_idx + 1 184 } 185 i = i + 1 186 } 187 return total 188} 189 190// Paint `text` with integer `scale` + faux BOLD (thicken each set pixel by one column to the right) + 191// faux ITALIC (per-row rightward slant: top rows lean further right). bold=0/italic=0 + scale matches 192// nx_paint_text_scaled exactly. Used for CSS font-weight:bold / font-style:italic via the cascade -- 193// the 5x7 bitmap has no real bold/italic faces, so we synthesize them (legible at 1x; crisp scaled). 194func nx_paint_text_styled(fb: *Framebuffer, 195 x: i64, y: i64, 196 text: *u8, text_len: i64, 197 color: *CssColor, scale: i64, 198 bold: i64, italic: i64) -> i64 { 199 if text_len <= 0 { return 0 } 200 var sc: i64 = scale 201 if sc < 1 { sc = 1 } 202 let r: i64 = color.r & 255 203 let g: i64 = color.g & 255 204 let b: i64 = color.b & 255 205 let a: i64 = color.a & 255 206 if a == 0 { return 0 } 207 let cw: i64 = NX_PAINT_TEXT_CELL_W * sc 208 var total: i64 = 0 209 var i: i64 = 0 210 var col_idx: i64 = 0 211 while i < text_len { 212 let c: i64 = (text[i] as i64) & 255 213 if c == 10 { 214 col_idx = col_idx + 1 215 } else { 216 let cell_x: i64 = x + col_idx * cw 217 var row: i64 = 0 218 while row < 7 { 219 let bits: i64 = nx_font_5x7_glyph_row(c, row) 220 if bits < 0 { row = 7 } 221 else { 222 var slant: i64 = 0 223 if italic == 1 { slant = ((6 - row) * sc) / 3 } // top rows lean right (~2*sc max) 224 var col: i64 = 0 225 while col < 5 { 226 let shift: i64 = 4 - col 227 let bit: i64 = (bits >> shift) & 1 228 if bit == 1 { 229 let bx: i64 = cell_x + col * sc + slant 230 let by: i64 = y + row * sc 231 var dy: i64 = 0 232 while dy < sc { 233 var dx: i64 = 0 234 while dx < sc { 235 total = total + _paint_text_pixel(fb, bx + dx, by + dy, r, g, b, a) 236 if bold == 1 { total = total + _paint_text_pixel(fb, bx + dx + 1, by + dy, r, g, b, a) } 237 dx = dx + 1 238 } 239 dy = dy + 1 240 } 241 } 242 col = col + 1 243 } 244 row = row + 1 245 } 246 } 247 col_idx = col_idx + 1 248 } 249 i = i + 1 250 } 251 return total 252} 253 254// Convenience: report the rendered cell-width of a string in pixels. 255// Useful for text-align centering when nx_layout grows it. 256func nx_paint_text_measure(text_len: i64) -> i64 { 257 if text_len <= 0 { return 0 } 258 return text_len * NX_PAINT_TEXT_CELL_W 259}