From 8b2f9acfb45bc27f3e41d5eb8c33dee733088c90 Mon Sep 17 00:00:00 2001 From: nick black Date: Thu, 30 Jan 2025 09:13:30 -0500 Subject: [PATCH] Implement draw_octant() and yQuads() for U16.0 --- src/font/sprite/Box.zig | 95 ++++++++++++- src/font/sprite/Face.zig | 2 + src/font/sprite/octants.txt | 234 +++++++++++++++++++++++++++++++ src/font/sprite/testdata/Box.ppm | Bin 1048593 -> 1048593 bytes 4 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 src/font/sprite/octants.txt diff --git a/src/font/sprite/Box.zig b/src/font/sprite/Box.zig index ba7caa26a..2cd3d929b 100644 --- a/src/font/sprite/Box.zig +++ b/src/font/sprite/Box.zig @@ -184,6 +184,10 @@ const SmoothMosaic = packed struct(u10) { } }; +// Octant range, inclusive +const octant_min = 0x1cd00; +const octant_max = 0x1cde5; + // Utility names for common fractions const one_eighth: f64 = 0.125; const one_quarter: f64 = 0.25; @@ -581,6 +585,8 @@ fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void 0x1fb00...0x1fb3b => self.draw_sextant(canvas, cp), + octant_min...octant_max => self.draw_octant(canvas, cp), + // '🬼' 0x1fb3c => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from( \\... @@ -2484,6 +2490,65 @@ fn draw_sextant(self: Box, canvas: *font.sprite.Canvas, cp: u32) void { if (sex.br) self.rect(canvas, x_halfs[1], y_thirds[1], self.metrics.cell_width, self.metrics.cell_height); } +fn draw_octant(self: Box, canvas: *font.sprite.Canvas, cp: u32) void { + assert(cp >= octant_min and cp <= octant_max); + + // Octant representation. We use the funny numeric string keys + // so its easier to parse the actual name used in the Symbols for + // Legacy Computing spec. + const Octant = packed struct(u8) { + @"1": bool = false, + @"2": bool = false, + @"3": bool = false, + @"4": bool = false, + @"5": bool = false, + @"6": bool = false, + @"7": bool = false, + @"8": bool = false, + }; + + // Parse the octant data. This is all done at comptime so this is + // static data that is embedded in the binary. + const octants_len = octant_max - octant_min + 1; + const octants: [octants_len]Octant = comptime octants: { + @setEvalBranchQuota(10_000); + + var result: [octants_len]Octant = .{.{}} ** octants_len; + var i: usize = 0; + + const data = @embedFile("octants.txt"); + var it = std.mem.splitScalar(u8, data, '\n'); + while (it.next()) |line| { + // Skip comments + if (line.len == 0 or line[0] == '#') continue; + + const current = &result[i]; + i += 1; + + // Octants are in the format "BLOCK OCTANT-1235". The numbers + // at the end are keys into our packed struct. Since we're + // at comptime we can metaprogram it all. + const idx = std.mem.indexOfScalar(u8, line, '-').?; + for (line[idx + 1 ..]) |c| @field(current, &.{c}) = true; + } + + assert(i == octants_len); + break :octants result; + }; + + const x_halfs = self.xHalfs(); + const y_quads = self.yQuads(); + const oct = octants[cp - octant_min]; + if (oct.@"1") self.rect(canvas, 0, 0, x_halfs[0], y_quads[0]); + if (oct.@"2") self.rect(canvas, x_halfs[1], 0, self.metrics.cell_width, y_quads[0]); + if (oct.@"3") self.rect(canvas, 0, y_quads[0], x_halfs[0], y_quads[1]); + if (oct.@"4") self.rect(canvas, x_halfs[1], y_quads[0], self.metrics.cell_width, y_quads[1]); + if (oct.@"5") self.rect(canvas, 0, y_quads[1], x_halfs[0], y_quads[2]); + if (oct.@"6") self.rect(canvas, x_halfs[1], y_quads[1], self.metrics.cell_width, y_quads[2]); + if (oct.@"7") self.rect(canvas, 0, y_quads[2], x_halfs[0], self.metrics.cell_height); + if (oct.@"8") self.rect(canvas, x_halfs[1], y_quads[2], self.metrics.cell_width, self.metrics.cell_height); +} + fn xHalfs(self: Box) [2]u32 { return .{ @as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(self.metrics.cell_width)) / 2))), @@ -2500,6 +2565,21 @@ fn yThirds(self: Box) [2]u32 { }; } +// assume octants might be striped across multiple rows of cells. to maximize +// distance between excess pixellines, we want (1) an arbitrary region (there +// will be a pattern of 1'-3-1'-3-1'-3 no matter what), (2) discontiguous +// regions (0 and 2 or 1 and 3), and (3) an arbitrary three regions (there will +// be a pattern of 3-1-3-1-3-1 no matter what). +fn yQuads(self: Box) [3]u32 { + return switch (@mod(self.metrics.cell_height, 4)) { + 0 => .{ self.metrics.cell_height / 4, 2 * self.metrics.cell_height / 4, 3 * self.metrics.cell_height / 4 }, + 1 => .{ self.metrics.cell_height / 4, 2 * self.metrics.cell_height / 4 + 1, 3 * self.metrics.cell_height / 4 }, + 2 => .{ self.metrics.cell_height / 4 + 1, 2 * self.metrics.cell_height / 4, 3 * self.metrics.cell_height / 4 + 1 }, + 3 => .{ self.metrics.cell_height / 4 + 1, 2 * self.metrics.cell_height / 4 + 1, 3 * self.metrics.cell_height / 4 }, + else => unreachable, + }; +} + fn draw_smooth_mosaic( self: Box, canvas: *font.sprite.Canvas, @@ -3064,7 +3144,7 @@ fn testRenderAll(self: Box, alloc: Allocator, atlas: *font.Atlas) !void { ); } - // Symbols for Legacy Computing Supplement. + // Symbols for Legacy Computing Supplement: Quadrants // 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯 cp = 0x1cc21; while (cp <= 0x1cc2f) : (cp += 1) { @@ -3077,6 +3157,19 @@ fn testRenderAll(self: Box, alloc: Allocator, atlas: *font.Atlas) !void { else => {}, } } + + // Symbols for Legacy Computing Supplement: Octants + cp = 0x1CD00; + while (cp <= 0x1CDE5) : (cp += 1) { + switch (cp) { + 0x1CD00...0x1CDE5 => _ = try self.renderGlyph( + alloc, + atlas, + cp, + ), + else => {}, + } + } } test "render all sprites" { diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index cebf44429..f15423ada 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -236,6 +236,8 @@ const Kind = enum { // (Geometric Shapes) // 🯠 🯡 🯢 🯣 🯤 🯥 🯦 🯧 🯨 🯩 🯪 🯫 🯬 🯭 🯮 🯯 0x1FBCE...0x1FBEF, + // (Octants) + 0x1CD00...0x1CDE5, => .box, // Branch drawing character set, used for drawing git-like diff --git a/src/font/sprite/octants.txt b/src/font/sprite/octants.txt new file mode 100644 index 000000000..db79aa2c6 --- /dev/null +++ b/src/font/sprite/octants.txt @@ -0,0 +1,234 @@ +# This is the list of all the octants for the Symbols for Legacy +# Computing block. It is used at comptime to generate the lookup +# table for drawing them since we weren't able to discern a +# mathematical pattern for them. +BLOCK OCTANT-3 +BLOCK OCTANT-23 +BLOCK OCTANT-123 +BLOCK OCTANT-4 +BLOCK OCTANT-14 +BLOCK OCTANT-124 +BLOCK OCTANT-34 +BLOCK OCTANT-134 +BLOCK OCTANT-234 +BLOCK OCTANT-5 +BLOCK OCTANT-15 +BLOCK OCTANT-25 +BLOCK OCTANT-125 +BLOCK OCTANT-135 +BLOCK OCTANT-235 +BLOCK OCTANT-1235 +BLOCK OCTANT-45 +BLOCK OCTANT-145 +BLOCK OCTANT-245 +BLOCK OCTANT-1245 +BLOCK OCTANT-345 +BLOCK OCTANT-1345 +BLOCK OCTANT-2345 +BLOCK OCTANT-12345 +BLOCK OCTANT-6 +BLOCK OCTANT-16 +BLOCK OCTANT-26 +BLOCK OCTANT-126 +BLOCK OCTANT-36 +BLOCK OCTANT-136 +BLOCK OCTANT-236 +BLOCK OCTANT-1236 +BLOCK OCTANT-146 +BLOCK OCTANT-246 +BLOCK OCTANT-1246 +BLOCK OCTANT-346 +BLOCK OCTANT-1346 +BLOCK OCTANT-2346 +BLOCK OCTANT-12346 +BLOCK OCTANT-56 +BLOCK OCTANT-156 +BLOCK OCTANT-256 +BLOCK OCTANT-1256 +BLOCK OCTANT-356 +BLOCK OCTANT-1356 +BLOCK OCTANT-2356 +BLOCK OCTANT-12356 +BLOCK OCTANT-456 +BLOCK OCTANT-1456 +BLOCK OCTANT-2456 +BLOCK OCTANT-12456 +BLOCK OCTANT-3456 +BLOCK OCTANT-13456 +BLOCK OCTANT-23456 +BLOCK OCTANT-17 +BLOCK OCTANT-27 +BLOCK OCTANT-127 +BLOCK OCTANT-37 +BLOCK OCTANT-137 +BLOCK OCTANT-237 +BLOCK OCTANT-1237 +BLOCK OCTANT-47 +BLOCK OCTANT-147 +BLOCK OCTANT-247 +BLOCK OCTANT-1247 +BLOCK OCTANT-347 +BLOCK OCTANT-1347 +BLOCK OCTANT-2347 +BLOCK OCTANT-12347 +BLOCK OCTANT-157 +BLOCK OCTANT-257 +BLOCK OCTANT-1257 +BLOCK OCTANT-357 +BLOCK OCTANT-2357 +BLOCK OCTANT-12357 +BLOCK OCTANT-457 +BLOCK OCTANT-1457 +BLOCK OCTANT-12457 +BLOCK OCTANT-3457 +BLOCK OCTANT-13457 +BLOCK OCTANT-23457 +BLOCK OCTANT-67 +BLOCK OCTANT-167 +BLOCK OCTANT-267 +BLOCK OCTANT-1267 +BLOCK OCTANT-367 +BLOCK OCTANT-1367 +BLOCK OCTANT-2367 +BLOCK OCTANT-12367 +BLOCK OCTANT-467 +BLOCK OCTANT-1467 +BLOCK OCTANT-2467 +BLOCK OCTANT-12467 +BLOCK OCTANT-3467 +BLOCK OCTANT-13467 +BLOCK OCTANT-23467 +BLOCK OCTANT-123467 +BLOCK OCTANT-567 +BLOCK OCTANT-1567 +BLOCK OCTANT-2567 +BLOCK OCTANT-12567 +BLOCK OCTANT-3567 +BLOCK OCTANT-13567 +BLOCK OCTANT-23567 +BLOCK OCTANT-123567 +BLOCK OCTANT-4567 +BLOCK OCTANT-14567 +BLOCK OCTANT-24567 +BLOCK OCTANT-124567 +BLOCK OCTANT-34567 +BLOCK OCTANT-134567 +BLOCK OCTANT-234567 +BLOCK OCTANT-1234567 +BLOCK OCTANT-18 +BLOCK OCTANT-28 +BLOCK OCTANT-128 +BLOCK OCTANT-38 +BLOCK OCTANT-138 +BLOCK OCTANT-238 +BLOCK OCTANT-1238 +BLOCK OCTANT-48 +BLOCK OCTANT-148 +BLOCK OCTANT-248 +BLOCK OCTANT-1248 +BLOCK OCTANT-348 +BLOCK OCTANT-1348 +BLOCK OCTANT-2348 +BLOCK OCTANT-12348 +BLOCK OCTANT-58 +BLOCK OCTANT-158 +BLOCK OCTANT-258 +BLOCK OCTANT-1258 +BLOCK OCTANT-358 +BLOCK OCTANT-1358 +BLOCK OCTANT-2358 +BLOCK OCTANT-12358 +BLOCK OCTANT-458 +BLOCK OCTANT-1458 +BLOCK OCTANT-2458 +BLOCK OCTANT-12458 +BLOCK OCTANT-3458 +BLOCK OCTANT-13458 +BLOCK OCTANT-23458 +BLOCK OCTANT-123458 +BLOCK OCTANT-168 +BLOCK OCTANT-268 +BLOCK OCTANT-1268 +BLOCK OCTANT-368 +BLOCK OCTANT-2368 +BLOCK OCTANT-12368 +BLOCK OCTANT-468 +BLOCK OCTANT-1468 +BLOCK OCTANT-12468 +BLOCK OCTANT-3468 +BLOCK OCTANT-13468 +BLOCK OCTANT-23468 +BLOCK OCTANT-568 +BLOCK OCTANT-1568 +BLOCK OCTANT-2568 +BLOCK OCTANT-12568 +BLOCK OCTANT-3568 +BLOCK OCTANT-13568 +BLOCK OCTANT-23568 +BLOCK OCTANT-123568 +BLOCK OCTANT-4568 +BLOCK OCTANT-14568 +BLOCK OCTANT-24568 +BLOCK OCTANT-124568 +BLOCK OCTANT-34568 +BLOCK OCTANT-134568 +BLOCK OCTANT-234568 +BLOCK OCTANT-1234568 +BLOCK OCTANT-178 +BLOCK OCTANT-278 +BLOCK OCTANT-1278 +BLOCK OCTANT-378 +BLOCK OCTANT-1378 +BLOCK OCTANT-2378 +BLOCK OCTANT-12378 +BLOCK OCTANT-478 +BLOCK OCTANT-1478 +BLOCK OCTANT-2478 +BLOCK OCTANT-12478 +BLOCK OCTANT-3478 +BLOCK OCTANT-13478 +BLOCK OCTANT-23478 +BLOCK OCTANT-123478 +BLOCK OCTANT-578 +BLOCK OCTANT-1578 +BLOCK OCTANT-2578 +BLOCK OCTANT-12578 +BLOCK OCTANT-3578 +BLOCK OCTANT-13578 +BLOCK OCTANT-23578 +BLOCK OCTANT-123578 +BLOCK OCTANT-4578 +BLOCK OCTANT-14578 +BLOCK OCTANT-24578 +BLOCK OCTANT-124578 +BLOCK OCTANT-34578 +BLOCK OCTANT-134578 +BLOCK OCTANT-234578 +BLOCK OCTANT-1234578 +BLOCK OCTANT-678 +BLOCK OCTANT-1678 +BLOCK OCTANT-2678 +BLOCK OCTANT-12678 +BLOCK OCTANT-3678 +BLOCK OCTANT-13678 +BLOCK OCTANT-23678 +BLOCK OCTANT-123678 +BLOCK OCTANT-4678 +BLOCK OCTANT-14678 +BLOCK OCTANT-24678 +BLOCK OCTANT-124678 +BLOCK OCTANT-34678 +BLOCK OCTANT-134678 +BLOCK OCTANT-234678 +BLOCK OCTANT-1234678 +BLOCK OCTANT-15678 +BLOCK OCTANT-25678 +BLOCK OCTANT-125678 +BLOCK OCTANT-35678 +BLOCK OCTANT-235678 +BLOCK OCTANT-1235678 +BLOCK OCTANT-45678 +BLOCK OCTANT-145678 +BLOCK OCTANT-1245678 +BLOCK OCTANT-1345678 +BLOCK OCTANT-2345678 diff --git a/src/font/sprite/testdata/Box.ppm b/src/font/sprite/testdata/Box.ppm index 36519a1e95fe6c28c0644e58953c5246624f52c1..0feb3ebe49e45f69c4e877b1a086a56822f4af20 100644 GIT binary patch delta 22334 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 X89umPNWM5|4r{W=P$4%ebTR<|!(8LM delta 8711 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