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 zcmeHPe{@vUoj-5#-n@I?o6L-mjDRM&Arbiz%-CSDM=V)Hb5=|lyISK0aC*ot#85I? zROsPHRhd*6Fwv{V}{)h(@3tE)AyRc=*U-3Qdie%5wY z_Cn-QLZST~c{L=qO4AJXb?CaF6gV!yh&sh!Uv;!(5ZFIW(tx8S)tAqMN7Xx}oW_)E zb6!n(LWD>zaLT(5zAT%00 zr9@vk3&=Qw!ww;lcbWxtcz6-(Sst=Aesec2VO=@Vc%N$~0Qm(&n%9%l#SLn6qEAl@yPL^~JK zw)M1t$Ghc+Bo;2rs8PL>?z9GS!2Bnihz%DQ*aVQ<<2Plk?9Q;s>#Z z$aVHY56w`j1aOH4`)c5Xq-?>?g0!^QpV8#xse;NcyT2OU-=l(mx>5zbue9F0G z^Hnacoa-Z0;-^PJe0Wx*xs%tH}CLRZS*%|;q9;dD+n9#o0C zob&l~B^PU7fHn4xwW_-ZZdL?js80bap9H%nxL(aMICps|fO%QH6!nGjao5;ysTVUP zF74?^+3pZ`2fl#4CZzm{IPOV1BXXdz&XHj_JRS-`_#w%cO7#V)1E-v@LGWD8XJZSE z1(--FB$|~9sD4elIdv3NV?(%+L$#5BlGWzgukMv9@U*GQO62LgX$P_#3LNG%%Jt}= zoL8+l%0*84f*od);EWusVFv5ATq%lmLs};2hTzYYjo{m*tTotsIm#6Bx->5dD8puaM0hUru7ZU}A-ql60J|0C*Ng@Nw9SlaYuuFRp5D%CGFGn{@znITEV{E&O z5GC7V!;ZTpaiE0nx!wRRGpPpc3y25W&ncS0JR@lN8q(;{a+qk)u9B5_DMcXxXm26` zD1MNX;w1o){lsIiT8o|`X#h6dNOag*MV{gQo~=dsl4*eTkGqAUmJwimKx zF*}MxN~*xTG&z~!?070nCN9+^5P2+_oHpD@zGEgQ@xZx5Q^4BV*l*d^;9EYE21M+Y z)jm`PzY^XUXwT&IM03Ywo0x&7*ezGWHCHxJ?1dom{pT?ah(->&hF(J>p^5rJ3cRY4 z5`+~qq2xg{xrqdL@q?t=1-&mS3rx%yIVU$q=l*uf;0%jL%ka(7GW^4685&2+@U1}^ zXxlcL4^0uGw=v?y{6<6;=O0E$a~4NE2kooKb9}*LWV|aHJ|&w|6`GNJmK+WCy(L`P zeUX%)v|1a$@wsbA$>^EZkx8!T_LH8MJNy1tiuyxxcG>?lGO~Xj8QG?hk^Rfa$P6bL zZ=c}WCqmrZQARKJ{Srg^#f&!aotOO(ju8#(iiV&25ct+U;)U=!I?Z6>NO3%z*-9p2 zMO2`vj8I;>i<}YP71+YZEa=X>B)l&ipge#Td$v>WkH{Y-@U0-TVr+(l`FbQwFOvCr z&|V{Z2OVcL^rr#{zd_l6fnEx42RbSAIB`Sm9x@Mns|m3z(9!rC@RJc0?|hIrKviHL zkiFPO5FY8Uc_}HfCgUa(cyz5IrCF?;Cte%%!paEAhei8H3C>?}67ghbnJ*k4jHuV5 z1p$MJk3@>IYxpTi2!G35{|dcZ%)vM(?>I|U#Q^KNNhoh@v~5$|ew>%-5L`tj4-ni% z+%B|T=fNZ7774oEl{^q`!Yq7F(O|<&dhIY0(oD0*FmHgW5fi>m=BW5}j!V>Vm9r1X z3odL}NQJ4de1$G{;cV;B5hGO?A6j~F$|4qc@OPx2;*fLu7~JWgkghUX2c@t*Hs z_7d4OW!5nX3y{?W;kC5Z`ch7ZlRu-Syl4F=+4$in=@XK{R$GE=QQ-wsFl(lht&R+h z9yoP~GT!qP{l-}NIr4vRjQsh$^BMXF+ec2jj~$mi&~=bj;C6T^e|{TPT-G=SC2ZjT z)<*rV7`tDxs>FJ}cL(*Qw{74re4j3spy!9Cg7N(8FVe#jbUsGh(e_>$esx^dE%nRz zz8}&>67+mONe#_^P$;kOpa)DA5!*2~T>up~EPkH806jY_(FanZ)96%KvXAbCo;Hi_ z)f8Q{y*Hl+{+*tcpl6%eJ&xb|Q+mX*FrNp0BD$ZUDxcIvKX&%KLeENk{LkrmXXERl zu{*u-h-my}dgFhH#&~+;QT&5<-PQ@v**8(Jqj%BrEWg0kDhdP{|_au7&|NB=dBu_ z*h3ZP&-dvW2{ye)eQ-;G=HoXf=c zHL&7ObSH#PQ4ds4(By+<+NY9Ll?$<|tua`ggQ@-#`jTmNnLI3QKhokb%82%7$zVRq zo+haKJ8ehXMLGi>xJYBXz@wd$tQ_Iv*SWMa5-;>)>y-KGM1GxHG!$t4k}%8 z#0w{A{Sy5BW>w=}uhuW|!t1s35)PGe?>Oze!~+Z|x~6J6_Xo7UNIXz(H%t-@QdrWE@WH1k%Eu0-=KNcWuSn{oc975;lH3%z; z;Qm=!9Tcz7bO_JV?zJmvS0kOBtX|7$dE0WLz>G%-g$_Ij3^&pyh&)VZw>1#m?(1Aj z^U=gHSaGXX2h;FU-Jr^sY{d;R_9fvy9o@FnbelQc&s@!1R^ z-*$q(ksd;C`_pG&E<7+>>#+2e!pmhul(|w^-$-?=9?6fbSu-uQDo6lp?;fm^I^Ovp z^ljo9iL%=+r?R=1QJLSM3d9?!8`l4p5{P@X@4~Jkt;k?cr+aNoj>SEu-=L~BDWz8G zu_wg|Pr80huY+gHh#w+lWG1|&X{~Tk(*nl$Qj9Mh=HBPH9JNdo(BMFFIxktP8@0N% z0DR+JdO7vFP_Ii1z;7n0mrd_Y@OH7*+IEo!F!{0!H}kpoX+1VptFmTNJgd;fe#2n* zS*hGi$!3mCoTXE*ZgISLh4y|1z||MlT_!#DW)kC4--3s$$ z!b;0a9;_Wh=R=Xx>_W4~nnBSq+suN;j>~R{ET(qJc_6+!E9ekY zZbyK?AZGNmEhl&20i4%5#t?O^0^_R!dY`51(yQklt9s6Y-kGlZq54hP521DRDTtSm z5eMyGms-Sve%P{%)B!W-?dVbAOKD5n{;sVX`QSpXHPm>2hC=0(T$OB7DAJ+R8Cc=^LVY|GFteqNgFa>n*Id%@T_?onKf#5^4R z>(rB;_Yyy#hv3;&L|itfEeO8_99P=4+F&i2{=I5Nq@Tdo*@K9&FXdT>>yM1R9iL%E N3;w@cV{DV_{{X{W delta 8711 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