// PETSCII image rendering — a fallback when the terminal has no kitty graphics, // and a per-pane toggle. Ported in spirit from caioluders/petsciinator: split the // image into character cells and match each cell to the Unicode block/sextant glyph // + a C64 foreground/background color pair that best reproduces the cell's pixels // (minimum per-pixel color error). Pure: only std + the raw RGBA bytes (no vaxis), // so the matcher is unit-testable on its own. const std = @import("std"); // The match palette is supplied by the caller (mode-dependent): the Commodore 64 // colors below by default, or the terminal's own ANSI palette. Cells store fg/bg as // palette indices; image.draw paints them as C64 RGB or as indexed colors per mode. // This is Pepto's canonical C64 palette. pub const commodore = [16][3]u8{ .{ 0x00, 0x00, 0x00 }, // 0 black .{ 0xff, 0xff, 0xff }, // 1 white .{ 0x68, 0x37, 0x2b }, // 2 red .{ 0x70, 0xa4, 0xb2 }, // 3 cyan .{ 0x6f, 0x3d, 0x86 }, // 4 purple .{ 0x58, 0x8d, 0x43 }, // 5 green .{ 0x35, 0x28, 0x79 }, // 6 blue .{ 0xb8, 0xc7, 0x6f }, // 7 yellow .{ 0x6f, 0x4f, 0x25 }, // 8 orange .{ 0x43, 0x39, 0x00 }, // 9 brown .{ 0x9a, 0x67, 0x59 }, // 10 light red .{ 0x44, 0x44, 0x44 }, // 11 dark grey .{ 0x6c, 0x6c, 0x6c }, // 12 grey .{ 0x9a, 0xd2, 0x84 }, // 13 light green .{ 0x6c, 0x5e, 0xb5 }, // 14 light blue .{ 0x95, 0x95, 0x95 }, // 15 light grey }; const Palette = [16][3]u8; // One rendered character cell: the UTF-8 bytes of the chosen glyph (stored inline // so the slice handed to the retained vaxis screen outlives the frame) + palette // fg/bg indices. pub const Cell = struct { glyph: [4]u8 = .{ ' ', 0, 0, 0 }, glen: u3 = 1, fg: u4 = 1, bg: u4 = 0, }; pub const Grid = struct { cells: []Cell, gw: usize, gh: usize }; fn dist2(a: [3]u8, b: [3]u8) u32 { const dr = @as(i32, a[0]) - b[0]; const dg = @as(i32, a[1]) - b[1]; const db = @as(i32, a[2]) - b[2]; return @intCast(dr * dr + dg * dg + db * db); } fn nearest(px: [3]u8, pal: Palette) u4 { var best: u4 = 0; var bestd: u32 = std.math.maxInt(u32); for (pal, 0..) |c, i| { const d = dist2(px, c); if (d < bestd) { bestd = d; best = @intCast(i); } } return best; } // ---- glyph set: each is a codepoint + an 8x8 ink bitmap (bit y*8+x set = fg) ---- const Glyph = struct { cp: u21, bits: u64 }; // The Unicode codepoint for a 2x3 sextant pattern. Bits: 1=upper-left, 2=upper-right, // 4=mid-left, 8=mid-right, 16=lower-left, 32=lower-right. The four patterns that // coincide with existing block characters are mapped to those instead. fn sextantCp(p: u6) u21 { return switch (p) { 0 => ' ', 21 => 0x258C, // left half ▌ 42 => 0x2590, // right half ▐ 63 => 0x2588, // full block █ else => blk: { var off: u21 = @as(u21, p) - 1; if (p > 21) off -= 1; if (p > 42) off -= 1; break :blk 0x1FB00 + off; // Symbols for Legacy Computing sextants }, }; } // the 8x8 ink bitmap for a sextant pattern (2 cols x 3 rows of subcells). fn sextantBits(p: u6) u64 { var bits: u64 = 0; var y: usize = 0; while (y < 8) : (y += 1) { const band = (y * 3) / 8; // 0,0,0,1,1,1,2,2 var x: usize = 0; while (x < 8) : (x += 1) { const col: usize = if (x < 4) 0 else 1; const sub: u6 = @intCast(band * 2 + col); if ((p >> sub) & 1 != 0) bits |= @as(u64, 1) << @intCast(y * 8 + x); } } return bits; } // pack 8 row-bytes (bit x set, x=0 leftmost) into the 8x8 bitmap. fn rows(r: [8]u8) u64 { var b: u64 = 0; for (r, 0..) |row, y| b |= @as(u64, row) << @intCast(y * 8); return b; } // the horizontal half blocks and the ten 2x2 quadrants — sextants are 2x3, so they // can't express an exact 4-row half or a quarter; these fill that gap. const block_glyphs = [_]Glyph{ .{ .cp = 0x2580, .bits = rows(.{ 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0 }) }, // ▀ top half .{ .cp = 0x2584, .bits = rows(.{ 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff }) }, // ▄ bottom half .{ .cp = 0x2598, .bits = rows(.{ 0x0f, 0x0f, 0x0f, 0x0f, 0, 0, 0, 0 }) }, // ▘ TL .{ .cp = 0x259d, .bits = rows(.{ 0xf0, 0xf0, 0xf0, 0xf0, 0, 0, 0, 0 }) }, // ▝ TR .{ .cp = 0x2596, .bits = rows(.{ 0, 0, 0, 0, 0x0f, 0x0f, 0x0f, 0x0f }) }, // ▖ BL .{ .cp = 0x2597, .bits = rows(.{ 0, 0, 0, 0, 0xf0, 0xf0, 0xf0, 0xf0 }) }, // ▗ BR .{ .cp = 0x259a, .bits = rows(.{ 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0 }) }, // ▚ TL+BR .{ .cp = 0x259e, .bits = rows(.{ 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f }) }, // ▞ TR+BL .{ .cp = 0x259b, .bits = rows(.{ 0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x0f, 0x0f }) }, // ▛ ¬BR .{ .cp = 0x259c, .bits = rows(.{ 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0 }) }, // ▜ ¬BL .{ .cp = 0x2599, .bits = rows(.{ 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff }) }, // ▙ ¬TR .{ .cp = 0x259f, .bits = rows(.{ 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff }) }, // ▟ ¬TL }; // line/diagonal glyphs add the characteristic PETSCII "drawn" look on edges. const line_glyphs = [_]Glyph{ .{ .cp = 0x2500, .bits = rows(.{ 0, 0, 0, 0xff, 0xff, 0, 0, 0 }) }, // ─ .{ .cp = 0x2502, .bits = rows(.{ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18 }) }, // │ .{ .cp = 0x253c, .bits = rows(.{ 0x18, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0x18 }) }, // ┼ .{ .cp = 0x2572, .bits = rows(.{ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }) }, // ╲ .{ .cp = 0x2571, .bits = rows(.{ 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }) }, // ╱ .{ .cp = 0x2573, .bits = rows(.{ 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81 }) }, // ╳ }; // Real 8x8 font bitmaps for the printable ASCII range (extracted from the cp850-8x8 // console font). This is how petsciinator's charset works: the FULL character set is // matched, each glyph scored against the cell by its actual bitmap (ink = fg), then // rendered by its codepoint — the terminal draws the letter in its own font. (bit // y*8+x set = ink; x=0 leftmost. Generated, not hand-drawn.) const ascii_glyphs = [_]Glyph{ .{ .cp = 0x21, .bits = 0x00180018183c3c18 }, // ! .{ .cp = 0x22, .bits = 0x0000000000246666 }, // " .{ .cp = 0x23, .bits = 0x0036367f367f3636 }, // # .{ .cp = 0x24, .bits = 0x00183e603c067c18 }, // $ .{ .cp = 0x25, .bits = 0x0063660c18336300 }, // % .{ .cp = 0x26, .bits = 0x006e333b6e1c361c }, // & .{ .cp = 0x27, .bits = 0x00000000000c1818 }, // ' .{ .cp = 0x28, .bits = 0x0030180c0c0c1830 }, // ( .{ .cp = 0x29, .bits = 0x000c18303030180c }, // ) .{ .cp = 0x2a, .bits = 0x0000663cff3c6600 }, // * .{ .cp = 0x2b, .bits = 0x000018187e181800 }, // + .{ .cp = 0x2c, .bits = 0x0c18180000000000 }, // , .{ .cp = 0x2d, .bits = 0x000000007e000000 }, // - .{ .cp = 0x2e, .bits = 0x0018180000000000 }, // . .{ .cp = 0x2f, .bits = 0x000103060c183060 }, // / .{ .cp = 0x30, .bits = 0x001c36636b63361c }, // 0 .{ .cp = 0x31, .bits = 0x007e181818181c18 }, // 1 .{ .cp = 0x32, .bits = 0x007f660c3860633e }, // 2 .{ .cp = 0x33, .bits = 0x003e63603c60633e }, // 3 .{ .cp = 0x34, .bits = 0x0078307f33363c38 }, // 4 .{ .cp = 0x35, .bits = 0x003e63603f03037f }, // 5 .{ .cp = 0x36, .bits = 0x003e63633f03061c }, // 6 .{ .cp = 0x37, .bits = 0x000c0c0c1830637f }, // 7 .{ .cp = 0x38, .bits = 0x003e63633e63633e }, // 8 .{ .cp = 0x39, .bits = 0x001e30607e63633e }, // 9 .{ .cp = 0x3a, .bits = 0x0018180000181800 }, // : .{ .cp = 0x3b, .bits = 0x0c18180000181800 }, // ; .{ .cp = 0x3c, .bits = 0x006030180c183060 }, // < .{ .cp = 0x3d, .bits = 0x00007e00007e0000 }, // = .{ .cp = 0x3e, .bits = 0x00060c1830180c06 }, // > .{ .cp = 0x3f, .bits = 0x001800181830633e }, // ? .{ .cp = 0x40, .bits = 0x001e037b7b7b633e }, // @ .{ .cp = 0x41, .bits = 0x006363637f63361c }, // A .{ .cp = 0x42, .bits = 0x003f66663e66663f }, // B .{ .cp = 0x43, .bits = 0x003c66030303663c }, // C .{ .cp = 0x44, .bits = 0x001f36666666361f }, // D .{ .cp = 0x45, .bits = 0x007f46161e16467f }, // E .{ .cp = 0x46, .bits = 0x000f06161e16467f }, // F .{ .cp = 0x47, .bits = 0x005c66730303663c }, // G .{ .cp = 0x48, .bits = 0x006363637f636363 }, // H .{ .cp = 0x49, .bits = 0x003c18181818183c }, // I .{ .cp = 0x4a, .bits = 0x001e333330303078 }, // J .{ .cp = 0x4b, .bits = 0x006766361e366667 }, // K .{ .cp = 0x4c, .bits = 0x007f66460606060f }, // L .{ .cp = 0x4d, .bits = 0x0063636b7f7f7763 }, // M .{ .cp = 0x4e, .bits = 0x006363737b6f6763 }, // N .{ .cp = 0x4f, .bits = 0x003e63636363633e }, // O .{ .cp = 0x50, .bits = 0x000f06063e66663f }, // P .{ .cp = 0x51, .bits = 0x703e73636363633e }, // Q .{ .cp = 0x52, .bits = 0x006766363e66663f }, // R .{ .cp = 0x53, .bits = 0x003c6630180c663c }, // S .{ .cp = 0x54, .bits = 0x003c1818185a7e7e }, // T .{ .cp = 0x55, .bits = 0x003e636363636363 }, // U .{ .cp = 0x56, .bits = 0x001c366363636363 }, // V .{ .cp = 0x57, .bits = 0x00367f6b6b636363 }, // W .{ .cp = 0x58, .bits = 0x006363361c366363 }, // X .{ .cp = 0x59, .bits = 0x003c18183c666666 }, // Y .{ .cp = 0x5a, .bits = 0x007f664c1831637f }, // Z .{ .cp = 0x5b, .bits = 0x003c0c0c0c0c0c3c }, // [ .{ .cp = 0x5c, .bits = 0x00406030180c0603 }, // \\ .{ .cp = 0x5d, .bits = 0x003c30303030303c }, // ] .{ .cp = 0x5e, .bits = 0x0000000063361c08 }, // ^ .{ .cp = 0x5f, .bits = 0xff00000000000000 }, // _ .{ .cp = 0x60, .bits = 0x000000000030180c }, // ` .{ .cp = 0x61, .bits = 0x006e333e301e0000 }, // a .{ .cp = 0x62, .bits = 0x003b6666663e0607 }, // b .{ .cp = 0x63, .bits = 0x003e6303633e0000 }, // c .{ .cp = 0x64, .bits = 0x006e3333333e3038 }, // d .{ .cp = 0x65, .bits = 0x003e037f633e0000 }, // e .{ .cp = 0x66, .bits = 0x000f06061f06663c }, // f .{ .cp = 0x67, .bits = 0x1f303e33336e0000 }, // g .{ .cp = 0x68, .bits = 0x006766666e360607 }, // h .{ .cp = 0x69, .bits = 0x003c1818181c0018 }, // i .{ .cp = 0x6a, .bits = 0x3c66666060600060 }, // j .{ .cp = 0x6b, .bits = 0x0067361e36660607 }, // k .{ .cp = 0x6c, .bits = 0x003c18181818181c }, // l .{ .cp = 0x6d, .bits = 0x006b6b6b7f370000 }, // m .{ .cp = 0x6e, .bits = 0x00666666663b0000 }, // n .{ .cp = 0x6f, .bits = 0x003e6363633e0000 }, // o .{ .cp = 0x70, .bits = 0x0f063e66663b0000 }, // p .{ .cp = 0x71, .bits = 0x78303e33336e0000 }, // q .{ .cp = 0x72, .bits = 0x000f06066e3b0000 }, // r .{ .cp = 0x73, .bits = 0x003f603e037e0000 }, // s .{ .cp = 0x74, .bits = 0x00386c0c0c3f0c0c }, // t .{ .cp = 0x75, .bits = 0x006e333333330000 }, // u .{ .cp = 0x76, .bits = 0x001c366363630000 }, // v .{ .cp = 0x77, .bits = 0x00367f6b6b630000 }, // w .{ .cp = 0x78, .bits = 0x0063361c36630000 }, // x .{ .cp = 0x79, .bits = 0x3f607e6363630000 }, // y .{ .cp = 0x7a, .bits = 0x007e4c18327e0000 }, // z .{ .cp = 0x7b, .bits = 0x007018180e181870 }, // { .{ .cp = 0x7c, .bits = 0x0018181818181818 }, // | .{ .cp = 0x7d, .bits = 0x000e18187018180e }, // } .{ .cp = 0x7e, .bits = 0x0000000000003b6e }, // ~ }; // the block glyph table (sextants + half/quadrant blocks + line glyphs), built at // comptime, and the same set extended with the ASCII glyphs. `render(ascii=…)` picks. const glyphs = blk: { @setEvalBranchQuota(100000); var list: [64 + block_glyphs.len + line_glyphs.len]Glyph = undefined; var p: usize = 0; while (p < 64) : (p += 1) list[p] = .{ .cp = sextantCp(@intCast(p)), .bits = sextantBits(@intCast(p)) }; for (block_glyphs, 0..) |bg, i| list[64 + i] = bg; for (line_glyphs, 0..) |lg, i| list[64 + block_glyphs.len + i] = lg; break :blk list; }; const glyphs_ascii = glyphs ++ ascii_glyphs; // match one 8x8 RGB cell to the best (glyph, fg, bg). Candidate colors are the // most-common palette colors among the 64 pixels; for each ordered pair the glyph // cost is base(all-bg) + sum over the glyph's ink bits of (dist_fg - dist_bg). fn matchCell(cell: *const [64][3]u8, pal: Palette, gset: []const Glyph) Cell { var counts = [_]u16{0} ** 16; for (cell) |px| counts[nearest(px, pal)] += 1; var cand: [4]u4 = undefined; var ncand: usize = 0; var used = [_]bool{false} ** 16; while (ncand < 4) : (ncand += 1) { var best: ?usize = null; for (counts, 0..) |c, i| { if (used[i] or c == 0) continue; if (best == null or c > counts[best.?]) best = i; } if (best) |bi| { cand[ncand] = @intCast(bi); used[bi] = true; } else break; } if (ncand == 0) return .{}; // can't happen (64 pixels), but keep it total if (ncand == 1) return encode(' ', cand[0], cand[0]); // solid color var best_cost: i64 = std.math.maxInt(i64); var best = encode(' ', cand[0], cand[0]); var fi: usize = 0; while (fi < ncand) : (fi += 1) { var bi: usize = 0; while (bi < ncand) : (bi += 1) { if (fi == bi) continue; const fg = cand[fi]; const bg = cand[bi]; var dfg: [64]u32 = undefined; var dbg: [64]u32 = undefined; var base: i64 = 0; for (cell, 0..) |px, p| { dfg[p] = dist2(px, pal[fg]); dbg[p] = dist2(px, pal[bg]); base += dbg[p]; } for (gset) |g| { var delta: i64 = 0; var bits = g.bits; while (bits != 0) : (bits &= bits - 1) { const p: usize = @ctz(bits); delta += @as(i64, dfg[p]) - @as(i64, dbg[p]); } const cost = base + delta; if (cost < best_cost) { best_cost = cost; best = encode(g.cp, fg, bg); } } } } return best; } fn encode(cp: u21, fg: u4, bg: u4) Cell { var c = Cell{ .fg = fg, .bg = bg }; const n = std.unicode.utf8Encode(cp, &c.glyph) catch 1; c.glen = @intCast(n); return c; } // Render `rgba` (iw x ih, 4 bytes/px) into a grid of at most cols x rows cells, // preserving the image aspect with the terminal cell aspect (~1:2) corrected. // Caller owns Grid.cells (gpa). pub fn render(gpa: std.mem.Allocator, rgba: []const u8, iw: usize, ih: usize, cols: usize, rows_: usize, pal: Palette, ascii: bool) !Grid { if (iw == 0 or ih == 0 or cols == 0 or rows_ == 0) return .{ .cells = try gpa.alloc(Cell, 0), .gw = 0, .gh = 0 }; const gset: []const Glyph = if (ascii) &glyphs_ascii else &glyphs; // contain-fit; cells are ~twice as tall as wide, so a row spans 2 width-units. var gw = cols; var gh = (cols * ih) / (2 * iw); if (gh > rows_) { gh = rows_; gw = (rows_ * 2 * iw) / ih; } gw = std.math.clamp(gw, 1, cols); gh = std.math.clamp(gh, 1, rows_); const cells = try gpa.alloc(Cell, gw * gh); var cy: usize = 0; while (cy < gh) : (cy += 1) { const ry0 = cy * ih / gh; const ry1 = @max(ry0 + 1, (cy + 1) * ih / gh); var cx: usize = 0; while (cx < gw) : (cx += 1) { const rx0 = cx * iw / gw; const rx1 = @max(rx0 + 1, (cx + 1) * iw / gw); var cell: [64][3]u8 = undefined; var sy: usize = 0; while (sy < 8) : (sy += 1) { const py0 = ry0 + sy * (ry1 - ry0) / 8; const py1 = @max(py0 + 1, ry0 + (sy + 1) * (ry1 - ry0) / 8); var sx: usize = 0; while (sx < 8) : (sx += 1) { const px0 = rx0 + sx * (rx1 - rx0) / 8; const px1 = @max(px0 + 1, rx0 + (sx + 1) * (rx1 - rx0) / 8); var rs: usize = 0; var gs: usize = 0; var bs: usize = 0; var n: usize = 0; var yy = py0; while (yy < py1 and yy < ih) : (yy += 1) { var xx = px0; while (xx < px1 and xx < iw) : (xx += 1) { const i = (yy * iw + xx) * 4; rs += rgba[i]; gs += rgba[i + 1]; bs += rgba[i + 2]; n += 1; } } if (n == 0) n = 1; cell[sy * 8 + sx] = .{ @intCast(rs / n), @intCast(gs / n), @intCast(bs / n) }; } } cells[cy * gw + cx] = matchCell(&cell, pal, gset); } } return .{ .cells = cells, .gw = gw, .gh = gh }; } test "sextant codepoints: blocks + endpoints" { try std.testing.expectEqual(@as(u21, ' '), sextantCp(0)); try std.testing.expectEqual(@as(u21, 0x2588), sextantCp(63)); try std.testing.expectEqual(@as(u21, 0x258C), sextantCp(21)); try std.testing.expectEqual(@as(u21, 0x2590), sextantCp(42)); try std.testing.expectEqual(@as(u21, 0x1FB00), sextantCp(1)); // first sextant try std.testing.expectEqual(@as(u21, 0x1FB3B), sextantCp(62)); // last sextant } test "matchCell: solid color -> space on that bg" { var cell: [64][3]u8 = undefined; for (&cell) |*p| p.* = commodore[5]; // all green const m = matchCell(&cell, commodore, &glyphs); try std.testing.expectEqual(@as(u4, 5), m.bg); try std.testing.expectEqual(@as(u8, ' '), m.glyph[0]); } test "matchCell: clean top/bottom split picks the two colors" { var cell: [64][3]u8 = undefined; for (0..64) |p| cell[p] = if (p < 32) commodore[1] else commodore[6]; // white over blue const m = matchCell(&cell, commodore, &glyphs); // both palette colors must be chosen (in some fg/bg order) const a = @as(u4, @min(m.fg, m.bg)); const b = @as(u4, @max(m.fg, m.bg)); try std.testing.expectEqual(@as(u4, 1), a); try std.testing.expectEqual(@as(u4, 6), b); // a clean top/bottom split must resolve to a real block glyph (the top-4-rows // half block ▀), never a blank cell. const cp = std.unicode.utf8Decode(m.glyph[0..m.glen]) catch 0; try std.testing.expectEqual(@as(u21, 0x2580), cp); } test "ascii option only enables the ascii glyphs" { // the ascii glyph codepoints must be reachable exactly when ascii is on. var seen_block = false; var seen_ascii = false; for (glyphs) |g| if (g.cp == '#') { seen_block = true; }; for (glyphs_ascii) |g| if (g.cp == '#') { seen_ascii = true; }; try std.testing.expect(!seen_block); // '#' is an ascii-only glyph try std.testing.expect(seen_ascii); try std.testing.expectEqual(glyphs.len + ascii_glyphs.len, glyphs_ascii.len); } test "ascii glyph is chosen when a cell has its exact shape" { // a cell shaped exactly like the font 'S' (ink=white on black) must match 'S' // with ascii on (cost 0), and fall back to some block glyph with ascii off. const s_bits: u64 = 0x003c6630180c663c; var cell: [64][3]u8 = undefined; for (0..64) |p| cell[p] = if ((s_bits >> @intCast(p)) & 1 != 0) commodore[1] else commodore[0]; const on = matchCell(&cell, commodore, &glyphs_ascii); try std.testing.expectEqual(@as(u21, 'S'), std.unicode.utf8Decode(on.glyph[0..on.glen]) catch 0); const off = matchCell(&cell, commodore, &glyphs); try std.testing.expect((std.unicode.utf8Decode(off.glyph[0..off.glen]) catch 0) != 'S'); } test "render: tiny image produces a grid within bounds" { const a = std.testing.allocator; // 2x2 checker, RGBA var img = [_]u8{0} ** (2 * 2 * 4); img[0] = 255; img[1] = 255; img[2] = 255; img[3] = 255; // (0,0) white img[(3) * 4 + 0] = 255; img[(3) * 4 + 1] = 255; img[(3) * 4 + 2] = 255; img[(3) * 4 + 3] = 255; // (1,1) white const g = try render(a, &img, 2, 2, 10, 10, commodore, true); defer a.free(g.cells); try std.testing.expect(g.gw >= 1 and g.gw <= 10); try std.testing.expect(g.gh >= 1 and g.gh <= 10); try std.testing.expectEqual(g.gw * g.gh, g.cells.len); }