More Sprite Glyphs (#7755)

Follow-up to #7732, but even more to come, these were just some low
hanging fruit which it would be nice to merge early.

Adds these characters from the Symbols for Legacy Computing Supplement
block:
```
𜰛 𜰜 𜰝 𜰞

𜰰𜰱𜰲𜰳
𜰴  𜰷 𜰵𜰶 𜸖𜸘
𜰸  𜰻 𜰹𜰺 𜸗𜸙
𜰼𜰽𜰾𜰿
```
How this looks in Ghostty:
|Main (via font)|This PR (via sprites)|
|-|-|
|<img width="256" alt="image"
src="https://github.com/user-attachments/assets/f6000984-7e4a-4ec0-b282-9d0905bd54ed"
/>|<img width="256" alt="image"
src="https://github.com/user-attachments/assets/0d880458-3025-4e42-b2ba-cf84f540d503"
/>|

This PR also adjusts the way block quadrants are drawn for better
alignment with other block elements, this matches how we handle sextants
already.

### Diffs
|Range|||||
|-|-|-|-|-|
|U+1CC00...U+1CCFF|![sprite_face_diff-U+1CC00
U+1CCFF-9x17+1](https://github.com/user-attachments/assets/1adfded7-bd08-414b-8965-dfc07c5c31f8)|![sprite_face_diff-U+1CC00
U+1CCFF-11x21+2](https://github.com/user-attachments/assets/836cd64e-5013-47c8-b819-ac391f630579)|![sprite_face_diff-U+1CC00
U+1CCFF-12x24+3](https://github.com/user-attachments/assets/91483e65-6fc8-401c-b5ed-e5a97d7dd5ac)|![sprite_face_diff-U+1CC00
U+1CCFF-18x36+4](https://github.com/user-attachments/assets/02d7b2fe-bbf5-4431-a3b7-d7ab3ab94714)|
|U+1CE00..U+1CEFF|![sprite_face_diff-U+1CE00
U+1CEFF-9x17+1](https://github.com/user-attachments/assets/aa690cda-a8f3-4307-9f90-bb6ad9c9e7d2)|![sprite_face_diff-U+1CE00
U+1CEFF-11x21+2](https://github.com/user-attachments/assets/e96fd3db-9ed4-40e2-9770-86b9921717b0)|![sprite_face_diff-U+1CE00
U+1CEFF-12x24+3](https://github.com/user-attachments/assets/799859eb-0ff6-4f0e-8f69-6c0c5e1e1c04)|![sprite_face_diff-U+1CE00
U+1CEFF-18x36+4](https://github.com/user-attachments/assets/5049ca8b-2502-470d-853f-f600cd9d235c)|
|U+2500...U+25FF|![sprite_face_diff-U+2500
U+25FF-9x17+1](https://github.com/user-attachments/assets/37467f96-eaaa-4951-8d56-87ad614fc44e)|![sprite_face_diff-U+2500
U+25FF-11x21+2](https://github.com/user-attachments/assets/776e25e8-b257-4ec2-9f0a-98cb3e08aaa2)|||
pull/7757/head
Qwerasd 2025-07-01 14:04:01 -06:00 committed by GitHub
commit 5c4a30d85f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 237 additions and 6 deletions

View File

@ -15,6 +15,8 @@ const common = @import("common.zig");
const Shade = common.Shade;
const Quads = common.Quads;
const Alignment = common.Alignment;
const xHalfs = common.xHalfs;
const yHalfs = common.yHalfs;
const rect = common.rect;
const font = @import("../../main.zig");
@ -174,11 +176,11 @@ fn quadrant(
canvas: *font.sprite.Canvas,
comptime quads: Quads,
) void {
const center_x = metrics.cell_width / 2 + metrics.cell_width % 2;
const center_y = metrics.cell_height / 2 + metrics.cell_height % 2;
const x_halfs = xHalfs(metrics);
const y_halfs = yHalfs(metrics);
if (quads.tl) rect(metrics, canvas, 0, 0, center_x, center_y);
if (quads.tr) rect(metrics, canvas, center_x, 0, metrics.cell_width, center_y);
if (quads.bl) rect(metrics, canvas, 0, center_y, center_x, metrics.cell_height);
if (quads.br) rect(metrics, canvas, center_x, center_y, metrics.cell_width, metrics.cell_height);
if (quads.tl) rect(metrics, canvas, 0, 0, x_halfs[0], y_halfs[0]);
if (quads.tr) rect(metrics, canvas, x_halfs[1], 0, metrics.cell_width, y_halfs[0]);
if (quads.bl) rect(metrics, canvas, 0, y_halfs[1], x_halfs[0], metrics.cell_height);
if (quads.br) rect(metrics, canvas, x_halfs[1], y_halfs[1], metrics.cell_width, metrics.cell_height);
}

View File

@ -204,6 +204,14 @@ pub fn xHalfs(metrics: font.Metrics) [2]u32 {
return .{ half_width, metrics.cell_width - half_width };
}
/// yHalfs[0] should be used as the bottom edge of a top-aligned half.
/// yHalfs[1] should be used as the top edge of a bottom-aligned half.
pub fn yHalfs(metrics: font.Metrics) [2]u32 {
const float_height: f64 = @floatFromInt(metrics.cell_height);
const half_height: u32 = @intFromFloat(@round(0.5 * float_height));
return .{ half_height, metrics.cell_height - half_height };
}
/// Use these values as such:
/// yThirds[0] bottom edge of the first third.
/// yThirds[1] top edge of the second third.

View File

@ -61,6 +61,8 @@ const xHalfs = common.xHalfs;
const yQuads = common.yQuads;
const rect = common.rect;
const box = @import("box.zig");
const font = @import("../../main.zig");
const octant_min = 0x1cd00;
@ -192,6 +194,135 @@ pub fn draw1CC21_1CC2F(
);
}
/// Twelfth and Quarter circle pieces.
/// 𜰰 𜰱 𜰲 𜰳 𜰴 𜰵 𜰶 𜰷 𜰸 𜰹 𜰺 𜰻 𜰼 𜰽 𜰾 𜰿
///
/// 𜰰𜰱𜰲𜰳
/// 𜰴𜰵𜰶𜰷
/// 𜰸𜰹𜰺𜰻
/// 𜰼𜰽𜰾𜰿
///
/// These are actually ellipses, sized to touch
/// the edge of their enclosing set of cells.
pub fn draw1CC30_1CC3F(
cp: u32,
canvas: *font.sprite.Canvas,
width: u32,
height: u32,
metrics: font.Metrics,
) !void {
switch (cp) {
// 𜰰 UPPER LEFT TWELFTH CIRCLE
0x1CC30 => try circlePiece(canvas, width, height, metrics, 0, 0, 2, 2),
// 𜰱 UPPER CENTRE LEFT TWELFTH CIRCLE
0x1CC31 => try circlePiece(canvas, width, height, metrics, 1, 0, 2, 2),
// 𜰲 UPPER CENTRE RIGHT TWELFTH CIRCLE
0x1CC32 => try circlePiece(canvas, width, height, metrics, 2, 0, 2, 2),
// 𜰳 UPPER RIGHT TWELFTH CIRCLE
0x1CC33 => try circlePiece(canvas, width, height, metrics, 3, 0, 2, 2),
// 𜰴 UPPER MIDDLE LEFT TWELFTH CIRCLE
0x1CC34 => try circlePiece(canvas, width, height, metrics, 0, 1, 2, 2),
// 𜰵 UPPER LEFT QUARTER CIRCLE
0x1CC35 => try circlePiece(canvas, width, height, metrics, 0, 0, 1, 1),
// 𜰶 UPPER RIGHT QUARTER CIRCLE
0x1CC36 => try circlePiece(canvas, width, height, metrics, 1, 0, 1, 1),
// 𜰷 UPPER MIDDLE RIGHT TWELFTH CIRCLE
0x1CC37 => try circlePiece(canvas, width, height, metrics, 3, 1, 2, 2),
// 𜰸 LOWER MIDDLE LEFT TWELFTH CIRCLE
0x1CC38 => try circlePiece(canvas, width, height, metrics, 0, 2, 2, 2),
// 𜰹 LOWER LEFT QUARTER CIRCLE
0x1CC39 => try circlePiece(canvas, width, height, metrics, 0, 1, 1, 1),
// 𜰺 LOWER RIGHT QUARTER CIRCLE
0x1CC3A => try circlePiece(canvas, width, height, metrics, 1, 1, 1, 1),
// 𜰻 LOWER MIDDLE RIGHT TWELFTH CIRCLE
0x1CC3B => try circlePiece(canvas, width, height, metrics, 3, 2, 2, 2),
// 𜰼 LOWER LEFT TWELFTH CIRCLE
0x1CC3C => try circlePiece(canvas, width, height, metrics, 0, 3, 2, 2),
// 𜰽 LOWER CENTRE LEFT TWELFTH CIRCLE
0x1CC3D => try circlePiece(canvas, width, height, metrics, 1, 3, 2, 2),
// 𜰾 LOWER CENTRE RIGHT TWELFTH CIRCLE
0x1CC3E => try circlePiece(canvas, width, height, metrics, 2, 3, 2, 2),
// 𜰿 LOWER RIGHT TWELFTH CIRCLE
0x1CC3F => try circlePiece(canvas, width, height, metrics, 3, 3, 2, 2),
else => unreachable,
}
}
/// TODO: These two characters should be easy, but it's not clear how they're
/// meant to align with adjacent cells, what characters they're meant to
/// be used with:
/// - 1CC1F 𜰟 BOX DRAWINGS DOUBLE DIAGONAL UPPER RIGHT TO LOWER LEFT
/// - 1CC20 𜰠 BOX DRAWINGS DOUBLE DIAGONAL UPPER LEFT TO LOWER RIGHT
pub fn draw1CC1B_1CC1E(
cp: u32,
canvas: *font.sprite.Canvas,
width: u32,
height: u32,
metrics: font.Metrics,
) !void {
const w: i32 = @intCast(width);
const h: i32 = @intCast(height);
const t: i32 = @intCast(metrics.box_thickness);
switch (cp) {
// 𜰛 BOX DRAWINGS LIGHT HORIZONTAL AND UPPER RIGHT
0x1CC1B => {
box.linesChar(metrics, canvas, .{ .left = .light, .right = .light });
canvas.box(w - t, 0, w, @divFloor(h, 2), .on);
},
// 𜰜 BOX DRAWINGS LIGHT HORIZONTAL AND LOWER RIGHT
0x1CC1C => {
box.linesChar(metrics, canvas, .{ .left = .light, .right = .light });
canvas.box(w - t, @divFloor(h, 2), w, h, .on);
},
// 𜰝 BOX DRAWINGS LIGHT TOP AND UPPER LEFT
0x1CC1D => {
canvas.box(0, 0, w, t, .on);
canvas.box(0, 0, t, @divFloor(h, 2), .on);
},
// 𜰞 BOX DRAWINGS LIGHT BOTTOM AND LOWER LEFT
0x1CC1E => {
canvas.box(0, h - t, w, h, .on);
canvas.box(0, @divFloor(h, 2), t, h, .on);
},
else => unreachable,
}
}
pub fn draw1CE16_1CE19(
cp: u32,
canvas: *font.sprite.Canvas,
width: u32,
height: u32,
metrics: font.Metrics,
) !void {
const w: i32 = @intCast(width);
const h: i32 = @intCast(height);
const t: i32 = @intCast(metrics.box_thickness);
switch (cp) {
// 𜸖 BOX DRAWINGS LIGHT VERTICAL AND TOP RIGHT
0x1CE16 => {
box.linesChar(metrics, canvas, .{ .up = .light, .down = .light });
canvas.box(@divFloor(w, 2), 0, w, t, .on);
},
// 𜸗 BOX DRAWINGS LIGHT VERTICAL AND BOTTOM RIGHT
0x1CE17 => {
box.linesChar(metrics, canvas, .{ .up = .light, .down = .light });
canvas.box(@divFloor(w, 2), h - t, w, h, .on);
},
// 𜸘 BOX DRAWINGS LIGHT VERTICAL AND TOP LEFT
0x1CE18 => {
box.linesChar(metrics, canvas, .{ .up = .light, .down = .light });
canvas.box(0, 0, @divFloor(w, 2), t, .on);
},
// 𜸙 BOX DRAWINGS LIGHT VERTICAL AND BOTTOM LEFT
0x1CE19 => {
box.linesChar(metrics, canvas, .{ .up = .light, .down = .light });
canvas.box(0, h - t, @divFloor(w, 2), h, .on);
},
else => unreachable,
}
}
/// Separated Block Sextants
pub fn draw1CE51_1CE8F(
cp: u32,
@ -271,3 +402,93 @@ pub fn draw1CE51_1CE8F(
.on,
);
}
fn circlePiece(
canvas: *font.sprite.Canvas,
width: u32,
height: u32,
metrics: font.Metrics,
x: f64,
y: f64,
w: f64,
h: f64,
) !void {
// Radius in pixels of the arc we need to draw.
const wdth: f64 = @as(f64, @floatFromInt(width)) * w;
const hght: f64 = @as(f64, @floatFromInt(height)) * h;
// Position in pixels (rather than cells) for x/y
const xp: f64 = @as(f64, @floatFromInt(width)) * x;
const yp: f64 = @as(f64, @floatFromInt(height)) * y;
// Set the clip so we don't include anything outside of the cell.
canvas.clip_left = canvas.padding_x;
canvas.clip_right = canvas.padding_x;
canvas.clip_top = canvas.padding_y;
canvas.clip_bottom = canvas.padding_y;
// Coefficient for approximating a circular arc.
const c: f64 = (std.math.sqrt2 - 1.0) * 4.0 / 3.0;
const cw = c * wdth;
const ch = c * hght;
const thick: f64 = @floatFromInt(metrics.box_thickness);
const ht = thick * 0.5;
var path = canvas.staticPath(2);
if (xp < wdth) {
if (yp < hght) {
// Upper left arc.
path.moveTo(wdth - xp, ht - yp);
path.curveTo(
wdth - cw - xp,
ht - yp,
ht - xp,
hght - ch - yp,
ht - xp,
hght - yp,
);
} else {
// Lower left arc.
path.moveTo(ht - xp, hght - yp);
path.curveTo(
ht - xp,
hght + ch - yp,
wdth - cw - xp,
hght * 2 - ht - yp,
wdth - xp,
hght * 2 - ht - yp,
);
}
} else {
if (yp < hght) {
// Upper right arc.
path.moveTo(wdth - xp, ht - yp);
path.curveTo(
wdth + cw - xp,
ht - yp,
wdth * 2 - ht - xp,
hght - ch - yp,
wdth * 2 - ht - xp,
hght - yp,
);
} else {
// Lower right arc.
path.moveTo(wdth * 2 - ht - xp, hght - yp);
path.curveTo(
wdth * 2 - ht - xp,
hght + ch - yp,
wdth + cw - xp,
hght * 2 - ht - yp,
wdth - xp,
hght * 2 - ht - yp,
);
}
}
try canvas.strokePath(path.wrapped_path, .{
.line_cap_mode = .butt,
.line_width = @floatFromInt(metrics.box_thickness),
}, .on);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 B

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 B

After

Width:  |  Height:  |  Size: 632 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 741 B

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 B

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB