summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitchell Hashimoto <m@mitchellh.com>2025-02-13 09:21:11 -0800
committerGitHub <noreply@github.com>2025-02-13 09:21:11 -0800
commit2a29f71b2cf6f4db0d18da9fe2fb75564f3709ea (patch)
treea20454f5c95abef6005deab771adb5fb203f36d2
parent6f4716fd60407d459b18ada65f0a01ad02d702b4 (diff)
parent8b2f9acfb45bc27f3e41d5eb8c33dee733088c90 (diff)
draw octants directly rather than relying on font (#5433)
* implement `yQuads()` and `draw_octant()`, pretty obvious extensions of existing code. to allocate up to 3 potential remainder lines, consider that octants will often appear in a rectangular subset of the terminal. we want the distributed excess uniformly distributed across such a region. so: * one excess row: break symmetry in any direction (pick an arbitrary tetrad and use it everywhere) * two excess rows: go to alternating tetrads * three excess rows: break symmetry, do not use three contiguous tetrads * our `Octant`s are octary arrays of `bool`, provided as a somewhat opaque constant table * the 8-line copy-and-paste draw based on the `Octant` is not the prettiest thing in the known universe * we could generalize `draw_sextant()` and `draw_octant()` like notcurses did, almost certainly * oh bird thou never wert with that said, i don't think `draw_octant()` is actually being called lol, so let's not merge this yet. happy to hear early feedback, though.
-rw-r--r--src/font/sprite/Box.zig95
-rw-r--r--src/font/sprite/Face.zig2
-rw-r--r--src/font/sprite/octants.txt234
-rw-r--r--src/font/sprite/testdata/Box.ppmbin1048593 -> 1048593 bytes
4 files changed, 330 insertions, 1 deletions
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 36519a1e9..0feb3ebe4 100644
--- a/src/font/sprite/testdata/Box.ppm
+++ b/src/font/sprite/testdata/Box.ppm
Binary files differ