mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 00:36:07 +03:00
3093 lines
102 KiB
Zig
3093 lines
102 KiB
Zig
//! This file contains functions for drawing the box drawing characters
|
||
//! (https://en.wikipedia.org/wiki/Box-drawing_character) and related
|
||
//! characters that are provided by the terminal.
|
||
//!
|
||
//! The box drawing logic is based off similar logic in Kitty and Foot.
|
||
//! The primary drawing code was originally ported directly and slightly
|
||
//! modified from Foot (https://codeberg.org/dnkl/foot/). Foot is licensed
|
||
//! under the MIT license and is copyright 2019 Daniel Eklöf.
|
||
//!
|
||
//! The modifications made were primarily around spacing, DPI calculations,
|
||
//! and adapting the code to our atlas model. Further, more extensive changes
|
||
//! were made, refactoring the line characters to all share a single unified
|
||
//! function (draw_lines), as well as many of the fractional block characters
|
||
//! which now use draw_block instead of dedicated separate functions.
|
||
//!
|
||
//! Additional characters from Unicode 16.0 and beyond are original work.
|
||
const Box = @This();
|
||
|
||
const std = @import("std");
|
||
const assert = std.debug.assert;
|
||
const Allocator = std.mem.Allocator;
|
||
|
||
const z2d = @import("z2d");
|
||
|
||
const font = @import("../main.zig");
|
||
const Sprite = @import("../sprite.zig").Sprite;
|
||
|
||
const log = std.log.scoped(.box_font);
|
||
|
||
/// The cell width and height because the boxes are fit perfectly
|
||
/// into a cell so that they all properly connect with zero spacing.
|
||
width: u32,
|
||
height: u32,
|
||
|
||
/// Base thickness value for lines of the box. This is in pixels. If you
|
||
/// want to do any DPI scaling, it is expected to be done earlier.
|
||
thickness: u32,
|
||
|
||
/// The thickness of a line.
|
||
const Thickness = enum {
|
||
super_light,
|
||
light,
|
||
heavy,
|
||
|
||
/// Calculate the real height of a line based on its thickness
|
||
/// and a base thickness value. The base thickness value is expected
|
||
/// to be in pixels.
|
||
fn height(self: Thickness, base: u32) u32 {
|
||
return switch (self) {
|
||
.super_light => @max(base / 2, 1),
|
||
.light => base,
|
||
.heavy => base * 2,
|
||
};
|
||
}
|
||
};
|
||
|
||
/// Specification of a traditional intersection-style line/box-drawing char,
|
||
/// which can have a different style of line from each edge to the center.
|
||
const Lines = packed struct(u8) {
|
||
up: Style = .none,
|
||
right: Style = .none,
|
||
down: Style = .none,
|
||
left: Style = .none,
|
||
|
||
const Style = enum(u2) {
|
||
none,
|
||
light,
|
||
heavy,
|
||
double,
|
||
};
|
||
};
|
||
|
||
/// Specification of a quadrants char, which has each of the
|
||
/// 4 quadrants of the character cell either filled or empty.
|
||
const Quads = packed struct(u4) {
|
||
tl: bool = false,
|
||
tr: bool = false,
|
||
bl: bool = false,
|
||
br: bool = false,
|
||
};
|
||
|
||
/// Specification of a branch drawing node, which consists of a
|
||
/// circle which is either empty or filled, and lines connecting
|
||
/// optionally between the circle and each of the 4 edges.
|
||
const BranchNode = packed struct(u5) {
|
||
up: bool = false,
|
||
right: bool = false,
|
||
down: bool = false,
|
||
left: bool = false,
|
||
filled: bool = false,
|
||
};
|
||
|
||
/// Alignment of a figure within a cell
|
||
const Alignment = struct {
|
||
horizontal: enum {
|
||
left,
|
||
right,
|
||
center,
|
||
} = .center,
|
||
|
||
vertical: enum {
|
||
top,
|
||
bottom,
|
||
middle,
|
||
} = .middle,
|
||
|
||
const upper: Alignment = .{ .vertical = .top };
|
||
const lower: Alignment = .{ .vertical = .bottom };
|
||
const left: Alignment = .{ .horizontal = .left };
|
||
const right: Alignment = .{ .horizontal = .right };
|
||
|
||
const upper_left: Alignment = .{ .vertical = .top, .horizontal = .left };
|
||
const upper_right: Alignment = .{ .vertical = .top, .horizontal = .right };
|
||
const lower_left: Alignment = .{ .vertical = .bottom, .horizontal = .left };
|
||
const lower_right: Alignment = .{ .vertical = .bottom, .horizontal = .right };
|
||
|
||
const center: Alignment = .{};
|
||
|
||
const upper_center = upper;
|
||
const lower_center = lower;
|
||
const middle_left = left;
|
||
const middle_right = right;
|
||
const middle_center: Alignment = center;
|
||
|
||
const top = upper;
|
||
const bottom = lower;
|
||
const center_top = top;
|
||
const center_bottom = bottom;
|
||
|
||
const top_left = upper_left;
|
||
const top_right = upper_right;
|
||
const bottom_left = lower_left;
|
||
const bottom_right = lower_right;
|
||
};
|
||
|
||
const Corner = enum(u2) {
|
||
tl,
|
||
tr,
|
||
bl,
|
||
br,
|
||
};
|
||
|
||
const Edge = enum(u2) {
|
||
top,
|
||
left,
|
||
bottom,
|
||
right,
|
||
};
|
||
|
||
const SmoothMosaic = packed struct(u10) {
|
||
tl: bool,
|
||
ul: bool,
|
||
ll: bool,
|
||
bl: bool,
|
||
bc: bool,
|
||
br: bool,
|
||
lr: bool,
|
||
ur: bool,
|
||
tr: bool,
|
||
tc: bool,
|
||
|
||
fn from(comptime pattern: *const [15:0]u8) SmoothMosaic {
|
||
return .{
|
||
.tl = pattern[0] == '#',
|
||
|
||
.ul = pattern[4] == '#' and
|
||
(pattern[0] != '#' or pattern[8] != '#'),
|
||
|
||
.ll = pattern[8] == '#' and
|
||
(pattern[4] != '#' or pattern[12] != '#'),
|
||
|
||
.bl = pattern[12] == '#',
|
||
|
||
.bc = pattern[13] == '#' and
|
||
(pattern[12] != '#' or pattern[14] != '#'),
|
||
|
||
.br = pattern[14] == '#',
|
||
|
||
.lr = pattern[10] == '#' and
|
||
(pattern[14] != '#' or pattern[6] != '#'),
|
||
|
||
.ur = pattern[6] == '#' and
|
||
(pattern[10] != '#' or pattern[2] != '#'),
|
||
|
||
.tr = pattern[2] == '#',
|
||
|
||
.tc = pattern[1] == '#' and
|
||
(pattern[2] != '#' or pattern[0] != '#'),
|
||
};
|
||
}
|
||
};
|
||
|
||
// Utility names for common fractions
|
||
const one_eighth: f64 = 0.125;
|
||
const one_quarter: f64 = 0.25;
|
||
const one_third: f64 = (1.0 / 3.0);
|
||
const three_eighths: f64 = 0.375;
|
||
const half: f64 = 0.5;
|
||
const five_eighths: f64 = 0.625;
|
||
const two_thirds: f64 = (2.0 / 3.0);
|
||
const three_quarters: f64 = 0.75;
|
||
const seven_eighths: f64 = 0.875;
|
||
|
||
/// Shades
|
||
const Shade = enum(u8) {
|
||
off = 0x00,
|
||
light = 0x40,
|
||
medium = 0x80,
|
||
dark = 0xc0,
|
||
on = 0xff,
|
||
|
||
_,
|
||
};
|
||
|
||
pub fn renderGlyph(
|
||
self: Box,
|
||
alloc: Allocator,
|
||
atlas: *font.Atlas,
|
||
cp: u32,
|
||
) !font.Glyph {
|
||
// Create the canvas we'll use to draw
|
||
var canvas = try font.sprite.Canvas.init(alloc, self.width, self.height);
|
||
defer canvas.deinit(alloc);
|
||
|
||
// Perform the actual drawing
|
||
try self.draw(alloc, &canvas, cp);
|
||
|
||
// Write the drawing to the atlas
|
||
const region = try canvas.writeAtlas(alloc, atlas);
|
||
|
||
// Our coordinates start at the BOTTOM for our renderers so we have to
|
||
// specify an offset of the full height because we rendered a full size
|
||
// cell.
|
||
const offset_y = @as(i32, @intCast(self.height));
|
||
|
||
return font.Glyph{
|
||
.width = self.width,
|
||
.height = self.height,
|
||
.offset_x = 0,
|
||
.offset_y = offset_y,
|
||
.atlas_x = region.x,
|
||
.atlas_y = region.y,
|
||
.advance_x = @floatFromInt(self.width),
|
||
};
|
||
}
|
||
|
||
/// Returns true if this codepoint should be rendered with the
|
||
/// width/height set to unadjusted values.
|
||
pub fn unadjustedCodepoint(cp: u32) bool {
|
||
return switch (cp) {
|
||
@intFromEnum(Sprite.cursor_rect),
|
||
@intFromEnum(Sprite.cursor_hollow_rect),
|
||
@intFromEnum(Sprite.cursor_bar),
|
||
=> true,
|
||
|
||
else => false,
|
||
};
|
||
}
|
||
|
||
fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void {
|
||
_ = alloc;
|
||
switch (cp) {
|
||
// '─'
|
||
0x2500 => self.draw_lines(canvas, .{ .left = .light, .right = .light }),
|
||
// '━'
|
||
0x2501 => self.draw_lines(canvas, .{ .left = .heavy, .right = .heavy }),
|
||
// '│'
|
||
0x2502 => self.draw_lines(canvas, .{ .up = .light, .down = .light }),
|
||
// '┃'
|
||
0x2503 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy }),
|
||
// '┄'
|
||
0x2504 => self.draw_light_triple_dash_horizontal(canvas),
|
||
// '┅'
|
||
0x2505 => self.draw_heavy_triple_dash_horizontal(canvas),
|
||
// '┆'
|
||
0x2506 => self.draw_light_triple_dash_vertical(canvas),
|
||
// '┇'
|
||
0x2507 => self.draw_heavy_triple_dash_vertical(canvas),
|
||
// '┈'
|
||
0x2508 => self.draw_light_quadruple_dash_horizontal(canvas),
|
||
// '┉'
|
||
0x2509 => self.draw_heavy_quadruple_dash_horizontal(canvas),
|
||
// '┊'
|
||
0x250a => self.draw_light_quadruple_dash_vertical(canvas),
|
||
// '┋'
|
||
0x250b => self.draw_heavy_quadruple_dash_vertical(canvas),
|
||
// '┌'
|
||
0x250c => self.draw_lines(canvas, .{ .down = .light, .right = .light }),
|
||
// '┍'
|
||
0x250d => self.draw_lines(canvas, .{ .down = .light, .right = .heavy }),
|
||
// '┎'
|
||
0x250e => self.draw_lines(canvas, .{ .down = .heavy, .right = .light }),
|
||
// '┏'
|
||
0x250f => self.draw_lines(canvas, .{ .down = .heavy, .right = .heavy }),
|
||
|
||
// '┐'
|
||
0x2510 => self.draw_lines(canvas, .{ .down = .light, .left = .light }),
|
||
// '┑'
|
||
0x2511 => self.draw_lines(canvas, .{ .down = .light, .left = .heavy }),
|
||
// '┒'
|
||
0x2512 => self.draw_lines(canvas, .{ .down = .heavy, .left = .light }),
|
||
// '┓'
|
||
0x2513 => self.draw_lines(canvas, .{ .down = .heavy, .left = .heavy }),
|
||
// '└'
|
||
0x2514 => self.draw_lines(canvas, .{ .up = .light, .right = .light }),
|
||
// '┕'
|
||
0x2515 => self.draw_lines(canvas, .{ .up = .light, .right = .heavy }),
|
||
// '┖'
|
||
0x2516 => self.draw_lines(canvas, .{ .up = .heavy, .right = .light }),
|
||
// '┗'
|
||
0x2517 => self.draw_lines(canvas, .{ .up = .heavy, .right = .heavy }),
|
||
// '┘'
|
||
0x2518 => self.draw_lines(canvas, .{ .up = .light, .left = .light }),
|
||
// '┙'
|
||
0x2519 => self.draw_lines(canvas, .{ .up = .light, .left = .heavy }),
|
||
// '┚'
|
||
0x251a => self.draw_lines(canvas, .{ .up = .heavy, .left = .light }),
|
||
// '┛'
|
||
0x251b => self.draw_lines(canvas, .{ .up = .heavy, .left = .heavy }),
|
||
// '├'
|
||
0x251c => self.draw_lines(canvas, .{ .up = .light, .down = .light, .right = .light }),
|
||
// '┝'
|
||
0x251d => self.draw_lines(canvas, .{ .up = .light, .down = .light, .right = .heavy }),
|
||
// '┞'
|
||
0x251e => self.draw_lines(canvas, .{ .up = .heavy, .right = .light, .down = .light }),
|
||
// '┟'
|
||
0x251f => self.draw_lines(canvas, .{ .down = .heavy, .right = .light, .up = .light }),
|
||
|
||
// '┠'
|
||
0x2520 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .right = .light }),
|
||
// '┡'
|
||
0x2521 => self.draw_lines(canvas, .{ .down = .light, .right = .heavy, .up = .heavy }),
|
||
// '┢'
|
||
0x2522 => self.draw_lines(canvas, .{ .up = .light, .right = .heavy, .down = .heavy }),
|
||
// '┣'
|
||
0x2523 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .right = .heavy }),
|
||
// '┤'
|
||
0x2524 => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .light }),
|
||
// '┥'
|
||
0x2525 => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .heavy }),
|
||
// '┦'
|
||
0x2526 => self.draw_lines(canvas, .{ .up = .heavy, .left = .light, .down = .light }),
|
||
// '┧'
|
||
0x2527 => self.draw_lines(canvas, .{ .down = .heavy, .left = .light, .up = .light }),
|
||
// '┨'
|
||
0x2528 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .light }),
|
||
// '┩'
|
||
0x2529 => self.draw_lines(canvas, .{ .down = .light, .left = .heavy, .up = .heavy }),
|
||
// '┪'
|
||
0x252a => self.draw_lines(canvas, .{ .up = .light, .left = .heavy, .down = .heavy }),
|
||
// '┫'
|
||
0x252b => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .heavy }),
|
||
// '┬'
|
||
0x252c => self.draw_lines(canvas, .{ .down = .light, .left = .light, .right = .light }),
|
||
// '┭'
|
||
0x252d => self.draw_lines(canvas, .{ .left = .heavy, .right = .light, .down = .light }),
|
||
// '┮'
|
||
0x252e => self.draw_lines(canvas, .{ .right = .heavy, .left = .light, .down = .light }),
|
||
// '┯'
|
||
0x252f => self.draw_lines(canvas, .{ .down = .light, .left = .heavy, .right = .heavy }),
|
||
|
||
// '┰'
|
||
0x2530 => self.draw_lines(canvas, .{ .down = .heavy, .left = .light, .right = .light }),
|
||
// '┱'
|
||
0x2531 => self.draw_lines(canvas, .{ .right = .light, .left = .heavy, .down = .heavy }),
|
||
// '┲'
|
||
0x2532 => self.draw_lines(canvas, .{ .left = .light, .right = .heavy, .down = .heavy }),
|
||
// '┳'
|
||
0x2533 => self.draw_lines(canvas, .{ .down = .heavy, .left = .heavy, .right = .heavy }),
|
||
// '┴'
|
||
0x2534 => self.draw_lines(canvas, .{ .up = .light, .left = .light, .right = .light }),
|
||
// '┵'
|
||
0x2535 => self.draw_lines(canvas, .{ .left = .heavy, .right = .light, .up = .light }),
|
||
// '┶'
|
||
0x2536 => self.draw_lines(canvas, .{ .right = .heavy, .left = .light, .up = .light }),
|
||
// '┷'
|
||
0x2537 => self.draw_lines(canvas, .{ .up = .light, .left = .heavy, .right = .heavy }),
|
||
// '┸'
|
||
0x2538 => self.draw_lines(canvas, .{ .up = .heavy, .left = .light, .right = .light }),
|
||
// '┹'
|
||
0x2539 => self.draw_lines(canvas, .{ .right = .light, .left = .heavy, .up = .heavy }),
|
||
// '┺'
|
||
0x253a => self.draw_lines(canvas, .{ .left = .light, .right = .heavy, .up = .heavy }),
|
||
// '┻'
|
||
0x253b => self.draw_lines(canvas, .{ .up = .heavy, .left = .heavy, .right = .heavy }),
|
||
// '┼'
|
||
0x253c => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .light, .right = .light }),
|
||
// '┽'
|
||
0x253d => self.draw_lines(canvas, .{ .left = .heavy, .right = .light, .up = .light, .down = .light }),
|
||
// '┾'
|
||
0x253e => self.draw_lines(canvas, .{ .right = .heavy, .left = .light, .up = .light, .down = .light }),
|
||
// '┿'
|
||
0x253f => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .heavy, .right = .heavy }),
|
||
|
||
// '╀'
|
||
0x2540 => self.draw_lines(canvas, .{ .up = .heavy, .down = .light, .left = .light, .right = .light }),
|
||
// '╁'
|
||
0x2541 => self.draw_lines(canvas, .{ .down = .heavy, .up = .light, .left = .light, .right = .light }),
|
||
// '╂'
|
||
0x2542 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .light, .right = .light }),
|
||
// '╃'
|
||
0x2543 => self.draw_lines(canvas, .{ .left = .heavy, .up = .heavy, .right = .light, .down = .light }),
|
||
// '╄'
|
||
0x2544 => self.draw_lines(canvas, .{ .right = .heavy, .up = .heavy, .left = .light, .down = .light }),
|
||
// '╅'
|
||
0x2545 => self.draw_lines(canvas, .{ .left = .heavy, .down = .heavy, .right = .light, .up = .light }),
|
||
// '╆'
|
||
0x2546 => self.draw_lines(canvas, .{ .right = .heavy, .down = .heavy, .left = .light, .up = .light }),
|
||
// '╇'
|
||
0x2547 => self.draw_lines(canvas, .{ .down = .light, .up = .heavy, .left = .heavy, .right = .heavy }),
|
||
// '╈'
|
||
0x2548 => self.draw_lines(canvas, .{ .up = .light, .down = .heavy, .left = .heavy, .right = .heavy }),
|
||
// '╉'
|
||
0x2549 => self.draw_lines(canvas, .{ .right = .light, .left = .heavy, .up = .heavy, .down = .heavy }),
|
||
// '╊'
|
||
0x254a => self.draw_lines(canvas, .{ .left = .light, .right = .heavy, .up = .heavy, .down = .heavy }),
|
||
// '╋'
|
||
0x254b => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .heavy, .right = .heavy }),
|
||
// '╌'
|
||
0x254c => self.draw_light_double_dash_horizontal(canvas),
|
||
// '╍'
|
||
0x254d => self.draw_heavy_double_dash_horizontal(canvas),
|
||
// '╎'
|
||
0x254e => self.draw_light_double_dash_vertical(canvas),
|
||
// '╏'
|
||
0x254f => self.draw_heavy_double_dash_vertical(canvas),
|
||
|
||
// '═'
|
||
0x2550 => self.draw_lines(canvas, .{ .left = .double, .right = .double }),
|
||
// '║'
|
||
0x2551 => self.draw_lines(canvas, .{ .up = .double, .down = .double }),
|
||
// '╒'
|
||
0x2552 => self.draw_lines(canvas, .{ .down = .light, .right = .double }),
|
||
// '╓'
|
||
0x2553 => self.draw_lines(canvas, .{ .down = .double, .right = .light }),
|
||
// '╔'
|
||
0x2554 => self.draw_lines(canvas, .{ .down = .double, .right = .double }),
|
||
// '╕'
|
||
0x2555 => self.draw_lines(canvas, .{ .down = .light, .left = .double }),
|
||
// '╖'
|
||
0x2556 => self.draw_lines(canvas, .{ .down = .double, .left = .light }),
|
||
// '╗'
|
||
0x2557 => self.draw_lines(canvas, .{ .down = .double, .left = .double }),
|
||
// '╘'
|
||
0x2558 => self.draw_lines(canvas, .{ .up = .light, .right = .double }),
|
||
// '╙'
|
||
0x2559 => self.draw_lines(canvas, .{ .up = .double, .right = .light }),
|
||
// '╚'
|
||
0x255a => self.draw_lines(canvas, .{ .up = .double, .right = .double }),
|
||
// '╛'
|
||
0x255b => self.draw_lines(canvas, .{ .up = .light, .left = .double }),
|
||
// '╜'
|
||
0x255c => self.draw_lines(canvas, .{ .up = .double, .left = .light }),
|
||
// '╝'
|
||
0x255d => self.draw_lines(canvas, .{ .up = .double, .left = .double }),
|
||
// '╞'
|
||
0x255e => self.draw_lines(canvas, .{ .up = .light, .down = .light, .right = .double }),
|
||
// '╟'
|
||
0x255f => self.draw_lines(canvas, .{ .up = .double, .down = .double, .right = .light }),
|
||
|
||
// '╠'
|
||
0x2560 => self.draw_lines(canvas, .{ .up = .double, .down = .double, .right = .double }),
|
||
// '╡'
|
||
0x2561 => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .double }),
|
||
// '╢'
|
||
0x2562 => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .light }),
|
||
// '╣'
|
||
0x2563 => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .double }),
|
||
// '╤'
|
||
0x2564 => self.draw_lines(canvas, .{ .down = .light, .left = .double, .right = .double }),
|
||
// '╥'
|
||
0x2565 => self.draw_lines(canvas, .{ .down = .double, .left = .light, .right = .light }),
|
||
// '╦'
|
||
0x2566 => self.draw_lines(canvas, .{ .down = .double, .left = .double, .right = .double }),
|
||
// '╧'
|
||
0x2567 => self.draw_lines(canvas, .{ .up = .light, .left = .double, .right = .double }),
|
||
// '╨'
|
||
0x2568 => self.draw_lines(canvas, .{ .up = .double, .left = .light, .right = .light }),
|
||
// '╩'
|
||
0x2569 => self.draw_lines(canvas, .{ .up = .double, .left = .double, .right = .double }),
|
||
// '╪'
|
||
0x256a => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .double, .right = .double }),
|
||
// '╫'
|
||
0x256b => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .light, .right = .light }),
|
||
// '╬'
|
||
0x256c => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .double, .right = .double }),
|
||
// '╭'
|
||
0x256d => try self.draw_arc(canvas, .br, .light),
|
||
// '╮'
|
||
0x256e => try self.draw_arc(canvas, .bl, .light),
|
||
// '╯'
|
||
0x256f => try self.draw_arc(canvas, .tl, .light),
|
||
|
||
// '╰'
|
||
0x2570 => try self.draw_arc(canvas, .tr, .light),
|
||
// '╱'
|
||
0x2571 => self.draw_light_diagonal_upper_right_to_lower_left(canvas),
|
||
// '╲'
|
||
0x2572 => self.draw_light_diagonal_upper_left_to_lower_right(canvas),
|
||
// '╳'
|
||
0x2573 => self.draw_light_diagonal_cross(canvas),
|
||
// '╴'
|
||
0x2574 => self.draw_lines(canvas, .{ .left = .light }),
|
||
// '╵'
|
||
0x2575 => self.draw_lines(canvas, .{ .up = .light }),
|
||
// '╶'
|
||
0x2576 => self.draw_lines(canvas, .{ .right = .light }),
|
||
// '╷'
|
||
0x2577 => self.draw_lines(canvas, .{ .down = .light }),
|
||
// '╸'
|
||
0x2578 => self.draw_lines(canvas, .{ .left = .heavy }),
|
||
// '╹'
|
||
0x2579 => self.draw_lines(canvas, .{ .up = .heavy }),
|
||
// '╺'
|
||
0x257a => self.draw_lines(canvas, .{ .right = .heavy }),
|
||
// '╻'
|
||
0x257b => self.draw_lines(canvas, .{ .down = .heavy }),
|
||
// '╼'
|
||
0x257c => self.draw_lines(canvas, .{ .left = .light, .right = .heavy }),
|
||
// '╽'
|
||
0x257d => self.draw_lines(canvas, .{ .up = .light, .down = .heavy }),
|
||
// '╾'
|
||
0x257e => self.draw_lines(canvas, .{ .left = .heavy, .right = .light }),
|
||
// '╿'
|
||
0x257f => self.draw_lines(canvas, .{ .up = .heavy, .down = .light }),
|
||
|
||
// '▀' UPPER HALF BLOCK
|
||
0x2580 => self.draw_block(canvas, Alignment.upper, 1, half),
|
||
// '▁' LOWER ONE EIGHTH BLOCK
|
||
0x2581 => self.draw_block(canvas, Alignment.lower, 1, one_eighth),
|
||
// '▂' LOWER ONE QUARTER BLOCK
|
||
0x2582 => self.draw_block(canvas, Alignment.lower, 1, one_quarter),
|
||
// '▃' LOWER THREE EIGHTHS BLOCK
|
||
0x2583 => self.draw_block(canvas, Alignment.lower, 1, three_eighths),
|
||
// '▄' LOWER HALF BLOCK
|
||
0x2584 => self.draw_block(canvas, Alignment.lower, 1, half),
|
||
// '▅' LOWER FIVE EIGHTHS BLOCK
|
||
0x2585 => self.draw_block(canvas, Alignment.lower, 1, five_eighths),
|
||
// '▆' LOWER THREE QUARTERS BLOCK
|
||
0x2586 => self.draw_block(canvas, Alignment.lower, 1, three_quarters),
|
||
// '▇' LOWER SEVEN EIGHTHS BLOCK
|
||
0x2587 => self.draw_block(canvas, Alignment.lower, 1, seven_eighths),
|
||
// '█' FULL BLOCK
|
||
0x2588 => self.draw_full_block(canvas),
|
||
// '▉' LEFT SEVEN EIGHTHS BLOCK
|
||
0x2589 => self.draw_block(canvas, Alignment.left, seven_eighths, 1),
|
||
// '▊' LEFT THREE QUARTERS BLOCK
|
||
0x258a => self.draw_block(canvas, Alignment.left, three_quarters, 1),
|
||
// '▋' LEFT FIVE EIGHTHS BLOCK
|
||
0x258b => self.draw_block(canvas, Alignment.left, five_eighths, 1),
|
||
// '▌' LEFT HALF BLOCK
|
||
0x258c => self.draw_block(canvas, Alignment.left, half, 1),
|
||
// '▍' LEFT THREE EIGHTHS BLOCK
|
||
0x258d => self.draw_block(canvas, Alignment.left, three_eighths, 1),
|
||
// '▎' LEFT ONE QUARTER BLOCK
|
||
0x258e => self.draw_block(canvas, Alignment.left, one_quarter, 1),
|
||
// '▏' LEFT ONE EIGHTH BLOCK
|
||
0x258f => self.draw_block(canvas, Alignment.left, one_eighth, 1),
|
||
|
||
// '▐' RIGHT HALF BLOCK
|
||
0x2590 => self.draw_block(canvas, Alignment.right, half, 1),
|
||
// '░'
|
||
0x2591 => self.draw_light_shade(canvas),
|
||
// '▒'
|
||
0x2592 => self.draw_medium_shade(canvas),
|
||
// '▓'
|
||
0x2593 => self.draw_dark_shade(canvas),
|
||
// '▔' UPPER ONE EIGHTH BLOCK
|
||
0x2594 => self.draw_block(canvas, Alignment.upper, 1, one_eighth),
|
||
// '▕' RIGHT ONE EIGHTH BLOCK
|
||
0x2595 => self.draw_block(canvas, Alignment.right, one_eighth, 1),
|
||
// '▖'
|
||
0x2596 => self.draw_quadrant(canvas, .{ .bl = true }),
|
||
// '▗'
|
||
0x2597 => self.draw_quadrant(canvas, .{ .br = true }),
|
||
// '▘'
|
||
0x2598 => self.draw_quadrant(canvas, .{ .tl = true }),
|
||
// '▙'
|
||
0x2599 => self.draw_quadrant(canvas, .{ .tl = true, .bl = true, .br = true }),
|
||
// '▚'
|
||
0x259a => self.draw_quadrant(canvas, .{ .tl = true, .br = true }),
|
||
// '▛'
|
||
0x259b => self.draw_quadrant(canvas, .{ .tl = true, .tr = true, .bl = true }),
|
||
// '▜'
|
||
0x259c => self.draw_quadrant(canvas, .{ .tl = true, .tr = true, .br = true }),
|
||
// '▝'
|
||
0x259d => self.draw_quadrant(canvas, .{ .tr = true }),
|
||
// '▞'
|
||
0x259e => self.draw_quadrant(canvas, .{ .tr = true, .bl = true }),
|
||
// '▟'
|
||
0x259f => self.draw_quadrant(canvas, .{ .tr = true, .bl = true, .br = true }),
|
||
|
||
0x2800...0x28ff => self.draw_braille(canvas, cp),
|
||
|
||
0x1fb00...0x1fb3b => self.draw_sextant(canvas, cp),
|
||
|
||
// '🬼'
|
||
0x1fb3c => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\...
|
||
\\...
|
||
\\#..
|
||
\\##.
|
||
)),
|
||
// '🬽'
|
||
0x1fb3d => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\...
|
||
\\...
|
||
\\#\.
|
||
\\###
|
||
)),
|
||
// '🬾'
|
||
0x1fb3e => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\...
|
||
\\#..
|
||
\\#\.
|
||
\\##.
|
||
)),
|
||
// '🬿'
|
||
0x1fb3f => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\...
|
||
\\#..
|
||
\\##.
|
||
\\###
|
||
)),
|
||
// '🭀'
|
||
0x1fb40 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\#..
|
||
\\#..
|
||
\\##.
|
||
\\##.
|
||
)),
|
||
|
||
// '🭁'
|
||
0x1fb41 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\/##
|
||
\\###
|
||
\\###
|
||
\\###
|
||
)),
|
||
// '🭂'
|
||
0x1fb42 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\./#
|
||
\\###
|
||
\\###
|
||
\\###
|
||
)),
|
||
// '🭃'
|
||
0x1fb43 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\.##
|
||
\\.##
|
||
\\###
|
||
\\###
|
||
)),
|
||
// '🭄'
|
||
0x1fb44 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\..#
|
||
\\.##
|
||
\\###
|
||
\\###
|
||
)),
|
||
// '🭅'
|
||
0x1fb45 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\.##
|
||
\\.##
|
||
\\.##
|
||
\\###
|
||
)),
|
||
// '🭆'
|
||
0x1fb46 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\...
|
||
\\./#
|
||
\\###
|
||
\\###
|
||
)),
|
||
|
||
// '🭇'
|
||
0x1fb47 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\...
|
||
\\...
|
||
\\..#
|
||
\\.##
|
||
)),
|
||
// '🭈'
|
||
0x1fb48 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\...
|
||
\\...
|
||
\\./#
|
||
\\###
|
||
)),
|
||
// '🭉'
|
||
0x1fb49 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\...
|
||
\\..#
|
||
\\./#
|
||
\\.##
|
||
)),
|
||
// '🭊'
|
||
0x1fb4a => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\...
|
||
\\..#
|
||
\\.##
|
||
\\###
|
||
)),
|
||
// '🭋'
|
||
0x1fb4b => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\..#
|
||
\\..#
|
||
\\.##
|
||
\\.##
|
||
)),
|
||
|
||
// '🭌'
|
||
0x1fb4c => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\##\
|
||
\\###
|
||
\\###
|
||
\\###
|
||
)),
|
||
// '🭍'
|
||
0x1fb4d => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\#\.
|
||
\\###
|
||
\\###
|
||
\\###
|
||
)),
|
||
// '🭎'
|
||
0x1fb4e => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\##.
|
||
\\##.
|
||
\\###
|
||
\\###
|
||
)),
|
||
// '🭏'
|
||
0x1fb4f => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\#..
|
||
\\##.
|
||
\\###
|
||
\\###
|
||
)),
|
||
// '🭐'
|
||
0x1fb50 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\##.
|
||
\\##.
|
||
\\##.
|
||
\\###
|
||
)),
|
||
// '🭑'
|
||
0x1fb51 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\...
|
||
\\#\.
|
||
\\###
|
||
\\###
|
||
)),
|
||
|
||
// '🭒'
|
||
0x1fb52 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\###
|
||
\\###
|
||
\\\##
|
||
)),
|
||
// '🭓'
|
||
0x1fb53 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\###
|
||
\\###
|
||
\\.\#
|
||
)),
|
||
// '🭔'
|
||
0x1fb54 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\###
|
||
\\.##
|
||
\\.##
|
||
)),
|
||
// '🭕'
|
||
0x1fb55 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\###
|
||
\\.##
|
||
\\..#
|
||
)),
|
||
// '🭖'
|
||
0x1fb56 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\.##
|
||
\\.##
|
||
\\.##
|
||
)),
|
||
|
||
// '🭗'
|
||
0x1fb57 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\##.
|
||
\\#..
|
||
\\...
|
||
\\...
|
||
)),
|
||
// '🭘'
|
||
0x1fb58 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\#/.
|
||
\\...
|
||
\\...
|
||
)),
|
||
// '🭙'
|
||
0x1fb59 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\##.
|
||
\\#/.
|
||
\\#..
|
||
\\...
|
||
)),
|
||
// '🭚'
|
||
0x1fb5a => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\##.
|
||
\\#..
|
||
\\...
|
||
)),
|
||
// '🭛'
|
||
0x1fb5b => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\##.
|
||
\\##.
|
||
\\#..
|
||
\\#..
|
||
)),
|
||
|
||
// '🭜'
|
||
0x1fb5c => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\###
|
||
\\#/.
|
||
\\...
|
||
)),
|
||
// '🭝'
|
||
0x1fb5d => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\###
|
||
\\###
|
||
\\##/
|
||
)),
|
||
// '🭞'
|
||
0x1fb5e => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\###
|
||
\\###
|
||
\\#/.
|
||
)),
|
||
// '🭟'
|
||
0x1fb5f => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\###
|
||
\\##.
|
||
\\##.
|
||
)),
|
||
// '🭠'
|
||
0x1fb60 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\###
|
||
\\##.
|
||
\\#..
|
||
)),
|
||
// '🭡'
|
||
0x1fb61 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\##.
|
||
\\##.
|
||
\\##.
|
||
)),
|
||
|
||
// '🭢'
|
||
0x1fb62 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\.##
|
||
\\..#
|
||
\\...
|
||
\\...
|
||
)),
|
||
// '🭣'
|
||
0x1fb63 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\.\#
|
||
\\...
|
||
\\...
|
||
)),
|
||
// '🭤'
|
||
0x1fb64 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\.##
|
||
\\.\#
|
||
\\..#
|
||
\\...
|
||
)),
|
||
// '🭥'
|
||
0x1fb65 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\.##
|
||
\\..#
|
||
\\...
|
||
)),
|
||
// '🭦'
|
||
0x1fb66 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\.##
|
||
\\.##
|
||
\\..#
|
||
\\..#
|
||
)),
|
||
// '🭧'
|
||
0x1fb67 => try self.draw_smooth_mosaic(canvas, SmoothMosaic.from(
|
||
\\###
|
||
\\###
|
||
\\.\#
|
||
\\...
|
||
)),
|
||
|
||
// '🭨'
|
||
0x1fb68 => {
|
||
try self.draw_edge_triangle(canvas, .left);
|
||
canvas.invert();
|
||
},
|
||
// '🭩'
|
||
0x1fb69 => {
|
||
try self.draw_edge_triangle(canvas, .top);
|
||
canvas.invert();
|
||
},
|
||
// '🭪'
|
||
0x1fb6a => {
|
||
try self.draw_edge_triangle(canvas, .right);
|
||
canvas.invert();
|
||
},
|
||
// '🭫'
|
||
0x1fb6b => {
|
||
try self.draw_edge_triangle(canvas, .bottom);
|
||
canvas.invert();
|
||
},
|
||
// '🭬'
|
||
0x1fb6c => try self.draw_edge_triangle(canvas, .left),
|
||
// '🭭'
|
||
0x1fb6d => try self.draw_edge_triangle(canvas, .top),
|
||
// '🭮'
|
||
0x1fb6e => try self.draw_edge_triangle(canvas, .right),
|
||
// '🭯'
|
||
0x1fb6f => try self.draw_edge_triangle(canvas, .bottom),
|
||
|
||
// '🭰'
|
||
0x1fb70 => self.draw_vertical_one_eighth_block_n(canvas, 1),
|
||
// '🭱'
|
||
0x1fb71 => self.draw_vertical_one_eighth_block_n(canvas, 2),
|
||
// '🭲'
|
||
0x1fb72 => self.draw_vertical_one_eighth_block_n(canvas, 3),
|
||
// '🭳'
|
||
0x1fb73 => self.draw_vertical_one_eighth_block_n(canvas, 4),
|
||
// '🭴'
|
||
0x1fb74 => self.draw_vertical_one_eighth_block_n(canvas, 5),
|
||
// '🭵'
|
||
0x1fb75 => self.draw_vertical_one_eighth_block_n(canvas, 6),
|
||
|
||
// '🭶'
|
||
0x1fb76 => self.draw_horizontal_one_eighth_block_n(canvas, 1),
|
||
// '🭷'
|
||
0x1fb77 => self.draw_horizontal_one_eighth_block_n(canvas, 2),
|
||
// '🭸'
|
||
0x1fb78 => self.draw_horizontal_one_eighth_block_n(canvas, 3),
|
||
// '🭹'
|
||
0x1fb79 => self.draw_horizontal_one_eighth_block_n(canvas, 4),
|
||
// '🭺'
|
||
0x1fb7a => self.draw_horizontal_one_eighth_block_n(canvas, 5),
|
||
// '🭻'
|
||
0x1fb7b => self.draw_horizontal_one_eighth_block_n(canvas, 6),
|
||
|
||
// '🮂' UPPER ONE QUARTER BLOCK
|
||
0x1fb82 => self.draw_block(canvas, Alignment.upper, 1, one_quarter),
|
||
// '🮃' UPPER THREE EIGHTHS BLOCK
|
||
0x1fb83 => self.draw_block(canvas, Alignment.upper, 1, three_eighths),
|
||
// '🮄' UPPER FIVE EIGHTHS BLOCK
|
||
0x1fb84 => self.draw_block(canvas, Alignment.upper, 1, five_eighths),
|
||
// '🮅' UPPER THREE QUARTERS BLOCK
|
||
0x1fb85 => self.draw_block(canvas, Alignment.upper, 1, three_quarters),
|
||
// '🮆' UPPER SEVEN EIGHTHS BLOCK
|
||
0x1fb86 => self.draw_block(canvas, Alignment.upper, 1, seven_eighths),
|
||
|
||
// '🭼' LEFT AND LOWER ONE EIGHTH BLOCK
|
||
0x1fb7c => {
|
||
self.draw_block(canvas, Alignment.left, one_eighth, 1);
|
||
self.draw_block(canvas, Alignment.lower, 1, one_eighth);
|
||
},
|
||
// '🭽' LEFT AND UPPER ONE EIGHTH BLOCK
|
||
0x1fb7d => {
|
||
self.draw_block(canvas, Alignment.left, one_eighth, 1);
|
||
self.draw_block(canvas, Alignment.upper, 1, one_eighth);
|
||
},
|
||
// '🭾' RIGHT AND UPPER ONE EIGHTH BLOCK
|
||
0x1fb7e => {
|
||
self.draw_block(canvas, Alignment.right, one_eighth, 1);
|
||
self.draw_block(canvas, Alignment.upper, 1, one_eighth);
|
||
},
|
||
// '🭿' RIGHT AND LOWER ONE EIGHTH BLOCK
|
||
0x1fb7f => {
|
||
self.draw_block(canvas, Alignment.right, one_eighth, 1);
|
||
self.draw_block(canvas, Alignment.lower, 1, one_eighth);
|
||
},
|
||
// '🮀' UPPER AND LOWER ONE EIGHTH BLOCK
|
||
0x1fb80 => {
|
||
self.draw_block(canvas, Alignment.upper, 1, one_eighth);
|
||
self.draw_block(canvas, Alignment.lower, 1, one_eighth);
|
||
},
|
||
// '🮁'
|
||
0x1fb81 => self.draw_horizontal_one_eighth_1358_block(canvas),
|
||
|
||
// '🮇' RIGHT ONE QUARTER BLOCK
|
||
0x1fb87 => self.draw_block(canvas, Alignment.right, one_quarter, 1),
|
||
// '🮈' RIGHT THREE EIGHTHS BLOCK
|
||
0x1fb88 => self.draw_block(canvas, Alignment.right, three_eighths, 1),
|
||
// '🮉' RIGHT FIVE EIGHTHS BLOCK
|
||
0x1fb89 => self.draw_block(canvas, Alignment.right, five_eighths, 1),
|
||
// '🮊' RIGHT THREE QUARTERS BLOCK
|
||
0x1fb8a => self.draw_block(canvas, Alignment.right, three_quarters, 1),
|
||
// '🮋' RIGHT SEVEN EIGHTHS BLOCK
|
||
0x1fb8b => self.draw_block(canvas, Alignment.right, seven_eighths, 1),
|
||
// '🮌'
|
||
0x1fb8c => self.draw_block_shade(canvas, Alignment.left, half, 1, .medium),
|
||
// '🮍'
|
||
0x1fb8d => self.draw_block_shade(canvas, Alignment.right, half, 1, .medium),
|
||
// '🮎'
|
||
0x1fb8e => self.draw_block_shade(canvas, Alignment.upper, 1, half, .medium),
|
||
// '🮏'
|
||
0x1fb8f => self.draw_block_shade(canvas, Alignment.lower, 1, half, .medium),
|
||
|
||
// '🮐'
|
||
0x1fb90 => self.draw_medium_shade(canvas),
|
||
// '🮑'
|
||
0x1fb91 => {
|
||
self.draw_medium_shade(canvas);
|
||
self.draw_block(canvas, Alignment.upper, 1, half);
|
||
},
|
||
// '🮒'
|
||
0x1fb92 => {
|
||
self.draw_medium_shade(canvas);
|
||
self.draw_block(canvas, Alignment.lower, 1, half);
|
||
},
|
||
// '🮔'
|
||
0x1fb94 => {
|
||
self.draw_medium_shade(canvas);
|
||
self.draw_block(canvas, Alignment.right, half, 1);
|
||
},
|
||
// '🮕'
|
||
0x1fb95 => self.draw_checkerboard_fill(canvas, 0),
|
||
// '🮖'
|
||
0x1fb96 => self.draw_checkerboard_fill(canvas, 1),
|
||
// '🮗'
|
||
0x1fb97 => {
|
||
self.draw_horizontal_one_eighth_block_n(canvas, 2);
|
||
self.draw_horizontal_one_eighth_block_n(canvas, 3);
|
||
self.draw_horizontal_one_eighth_block_n(canvas, 6);
|
||
self.draw_horizontal_one_eighth_block_n(canvas, 7);
|
||
},
|
||
// '🮘'
|
||
0x1fb98 => self.draw_upper_left_to_lower_right_fill(canvas),
|
||
// '🮙'
|
||
0x1fb99 => self.draw_upper_right_to_lower_left_fill(canvas),
|
||
// '🮚'
|
||
0x1fb9a => {
|
||
try self.draw_edge_triangle(canvas, .top);
|
||
try self.draw_edge_triangle(canvas, .bottom);
|
||
},
|
||
// '🮛'
|
||
0x1fb9b => {
|
||
try self.draw_edge_triangle(canvas, .left);
|
||
try self.draw_edge_triangle(canvas, .right);
|
||
},
|
||
// '🮜'
|
||
0x1fb9c => self.draw_corner_triangle_shade(canvas, .tl, .medium),
|
||
// '🮝'
|
||
0x1fb9d => self.draw_corner_triangle_shade(canvas, .tr, .medium),
|
||
// '🮞'
|
||
0x1fb9e => self.draw_corner_triangle_shade(canvas, .br, .medium),
|
||
// '🮟'
|
||
0x1fb9f => self.draw_corner_triangle_shade(canvas, .bl, .medium),
|
||
|
||
// '🮠'
|
||
0x1fba0 => self.draw_corner_diagonal_lines(canvas, .{ .tl = true }),
|
||
// '🮡'
|
||
0x1fba1 => self.draw_corner_diagonal_lines(canvas, .{ .tr = true }),
|
||
// '🮢'
|
||
0x1fba2 => self.draw_corner_diagonal_lines(canvas, .{ .bl = true }),
|
||
// '🮣'
|
||
0x1fba3 => self.draw_corner_diagonal_lines(canvas, .{ .br = true }),
|
||
// '🮤'
|
||
0x1fba4 => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .bl = true }),
|
||
// '🮥'
|
||
0x1fba5 => self.draw_corner_diagonal_lines(canvas, .{ .tr = true, .br = true }),
|
||
// '🮦'
|
||
0x1fba6 => self.draw_corner_diagonal_lines(canvas, .{ .bl = true, .br = true }),
|
||
// '🮧'
|
||
0x1fba7 => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true }),
|
||
// '🮨'
|
||
0x1fba8 => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .br = true }),
|
||
// '🮩'
|
||
0x1fba9 => self.draw_corner_diagonal_lines(canvas, .{ .tr = true, .bl = true }),
|
||
// '🮪'
|
||
0x1fbaa => self.draw_corner_diagonal_lines(canvas, .{ .tr = true, .bl = true, .br = true }),
|
||
// '🮫'
|
||
0x1fbab => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .bl = true, .br = true }),
|
||
// '🮬'
|
||
0x1fbac => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true, .br = true }),
|
||
// '🮭'
|
||
0x1fbad => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true, .bl = true }),
|
||
// '🮮'
|
||
0x1fbae => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true, .bl = true, .br = true }),
|
||
// '🮯'
|
||
0x1fbaf => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .light, .right = .light }),
|
||
|
||
// '🮽'
|
||
0x1fbbd => {
|
||
self.draw_light_diagonal_cross(canvas);
|
||
canvas.invert();
|
||
},
|
||
// '🮾'
|
||
0x1fbbe => {
|
||
self.draw_corner_diagonal_lines(canvas, .{ .br = true });
|
||
canvas.invert();
|
||
},
|
||
// '🮿'
|
||
0x1fbbf => {
|
||
self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true, .bl = true, .br = true });
|
||
canvas.invert();
|
||
},
|
||
|
||
// ''
|
||
0x1fbce => self.draw_block(canvas, Alignment.left, two_thirds, 1),
|
||
// ''
|
||
0x1fbcf => self.draw_block(canvas, Alignment.left, one_third, 1),
|
||
// ''
|
||
0x1fbd0 => self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.middle_right,
|
||
Alignment.lower_left,
|
||
),
|
||
// ''
|
||
0x1fbd1 => self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.upper_right,
|
||
Alignment.middle_left,
|
||
),
|
||
// ''
|
||
0x1fbd2 => self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.upper_left,
|
||
Alignment.middle_right,
|
||
),
|
||
// ''
|
||
0x1fbd3 => self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.middle_left,
|
||
Alignment.lower_right,
|
||
),
|
||
// ''
|
||
0x1fbd4 => self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.upper_left,
|
||
Alignment.lower_center,
|
||
),
|
||
// ''
|
||
0x1fbd5 => self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.upper_center,
|
||
Alignment.lower_right,
|
||
),
|
||
// ''
|
||
0x1fbd6 => self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.upper_right,
|
||
Alignment.lower_center,
|
||
),
|
||
// ''
|
||
0x1fbd7 => self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.upper_center,
|
||
Alignment.lower_left,
|
||
),
|
||
// ''
|
||
0x1fbd8 => {
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.upper_left,
|
||
Alignment.middle_center,
|
||
);
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.middle_center,
|
||
Alignment.upper_right,
|
||
);
|
||
},
|
||
// ''
|
||
0x1fbd9 => {
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.upper_right,
|
||
Alignment.middle_center,
|
||
);
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.middle_center,
|
||
Alignment.lower_right,
|
||
);
|
||
},
|
||
// ''
|
||
0x1fbda => {
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.lower_left,
|
||
Alignment.middle_center,
|
||
);
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.middle_center,
|
||
Alignment.lower_right,
|
||
);
|
||
},
|
||
// ''
|
||
0x1fbdb => {
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.upper_left,
|
||
Alignment.middle_center,
|
||
);
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.middle_center,
|
||
Alignment.lower_left,
|
||
);
|
||
},
|
||
// ''
|
||
0x1fbdc => {
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.upper_left,
|
||
Alignment.lower_center,
|
||
);
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.lower_center,
|
||
Alignment.upper_right,
|
||
);
|
||
},
|
||
// ''
|
||
0x1fbdd => {
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.upper_right,
|
||
Alignment.middle_left,
|
||
);
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.middle_left,
|
||
Alignment.lower_right,
|
||
);
|
||
},
|
||
// ''
|
||
0x1fbde => {
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.lower_left,
|
||
Alignment.upper_center,
|
||
);
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.upper_center,
|
||
Alignment.lower_right,
|
||
);
|
||
},
|
||
// ''
|
||
0x1fbdf => {
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.upper_left,
|
||
Alignment.middle_right,
|
||
);
|
||
self.draw_cell_diagonal(
|
||
canvas,
|
||
Alignment.middle_right,
|
||
Alignment.lower_left,
|
||
);
|
||
},
|
||
|
||
// ''
|
||
0x1fbe0 => self.draw_circle(canvas, Alignment.top, false),
|
||
// ''
|
||
0x1fbe1 => self.draw_circle(canvas, Alignment.right, false),
|
||
// ''
|
||
0x1fbe2 => self.draw_circle(canvas, Alignment.bottom, false),
|
||
// ''
|
||
0x1fbe3 => self.draw_circle(canvas, Alignment.left, false),
|
||
// ''
|
||
0x1fbe4 => self.draw_block(canvas, Alignment.upper_center, 0.5, 0.5),
|
||
// ''
|
||
0x1fbe5 => self.draw_block(canvas, Alignment.lower_center, 0.5, 0.5),
|
||
// ''
|
||
0x1fbe6 => self.draw_block(canvas, Alignment.middle_left, 0.5, 0.5),
|
||
// ''
|
||
0x1fbe7 => self.draw_block(canvas, Alignment.middle_right, 0.5, 0.5),
|
||
// ''
|
||
0x1fbe8 => self.draw_circle(canvas, Alignment.top, true),
|
||
// ''
|
||
0x1fbe9 => self.draw_circle(canvas, Alignment.right, true),
|
||
// ''
|
||
0x1fbea => self.draw_circle(canvas, Alignment.bottom, true),
|
||
// ''
|
||
0x1fbeb => self.draw_circle(canvas, Alignment.left, true),
|
||
// ''
|
||
0x1fbec => self.draw_circle(canvas, Alignment.top_right, true),
|
||
// ''
|
||
0x1fbed => self.draw_circle(canvas, Alignment.bottom_left, true),
|
||
// ''
|
||
0x1fbee => self.draw_circle(canvas, Alignment.bottom_right, true),
|
||
// ''
|
||
0x1fbef => self.draw_circle(canvas, Alignment.top_left, true),
|
||
|
||
// (Below:)
|
||
// Branch drawing character set, used for drawing git-like
|
||
// graphs in the terminal. Originally implemented in Kitty.
|
||
// Ref:
|
||
// - https://github.com/kovidgoyal/kitty/pull/7681
|
||
// - https://github.com/kovidgoyal/kitty/pull/7805
|
||
// NOTE: Kitty is GPL licensed, and its code was not referenced
|
||
// for these characters, only the loose specification of
|
||
// the character set in the pull request descriptions.
|
||
//
|
||
// TODO(qwerasd): This should be in another file, but really the
|
||
// general organization of the sprite font code
|
||
// needs to be reworked eventually.
|
||
//
|
||
//
|
||
//
|
||
//
|
||
//
|
||
|
||
// ''
|
||
0x0f5d0 => self.hline_middle(canvas, .light),
|
||
// ''
|
||
0x0f5d1 => self.vline_middle(canvas, .light),
|
||
// ''
|
||
0x0f5d2 => self.draw_fading_line(canvas, .right, .light),
|
||
// ''
|
||
0x0f5d3 => self.draw_fading_line(canvas, .left, .light),
|
||
// ''
|
||
0x0f5d4 => self.draw_fading_line(canvas, .bottom, .light),
|
||
// ''
|
||
0x0f5d5 => self.draw_fading_line(canvas, .top, .light),
|
||
// ''
|
||
0x0f5d6 => try self.draw_arc(canvas, .br, .light),
|
||
// ''
|
||
0x0f5d7 => try self.draw_arc(canvas, .bl, .light),
|
||
// ''
|
||
0x0f5d8 => try self.draw_arc(canvas, .tr, .light),
|
||
// ''
|
||
0x0f5d9 => try self.draw_arc(canvas, .tl, .light),
|
||
// ''
|
||
0x0f5da => {
|
||
self.vline_middle(canvas, .light);
|
||
try self.draw_arc(canvas, .tr, .light);
|
||
},
|
||
// ''
|
||
0x0f5db => {
|
||
self.vline_middle(canvas, .light);
|
||
try self.draw_arc(canvas, .br, .light);
|
||
},
|
||
// ''
|
||
0x0f5dc => {
|
||
try self.draw_arc(canvas, .tr, .light);
|
||
try self.draw_arc(canvas, .br, .light);
|
||
},
|
||
// ''
|
||
0x0f5dd => {
|
||
self.vline_middle(canvas, .light);
|
||
try self.draw_arc(canvas, .tl, .light);
|
||
},
|
||
// ''
|
||
0x0f5de => {
|
||
self.vline_middle(canvas, .light);
|
||
try self.draw_arc(canvas, .bl, .light);
|
||
},
|
||
// ''
|
||
0x0f5df => {
|
||
try self.draw_arc(canvas, .tl, .light);
|
||
try self.draw_arc(canvas, .bl, .light);
|
||
},
|
||
|
||
// ''
|
||
0x0f5e0 => {
|
||
try self.draw_arc(canvas, .bl, .light);
|
||
self.hline_middle(canvas, .light);
|
||
},
|
||
// ''
|
||
0x0f5e1 => {
|
||
try self.draw_arc(canvas, .br, .light);
|
||
self.hline_middle(canvas, .light);
|
||
},
|
||
// ''
|
||
0x0f5e2 => {
|
||
try self.draw_arc(canvas, .br, .light);
|
||
try self.draw_arc(canvas, .bl, .light);
|
||
},
|
||
// ''
|
||
0x0f5e3 => {
|
||
try self.draw_arc(canvas, .tl, .light);
|
||
self.hline_middle(canvas, .light);
|
||
},
|
||
// ''
|
||
0x0f5e4 => {
|
||
try self.draw_arc(canvas, .tr, .light);
|
||
self.hline_middle(canvas, .light);
|
||
},
|
||
// ''
|
||
0x0f5e5 => {
|
||
try self.draw_arc(canvas, .tr, .light);
|
||
try self.draw_arc(canvas, .tl, .light);
|
||
},
|
||
// ''
|
||
0x0f5e6 => {
|
||
self.vline_middle(canvas, .light);
|
||
try self.draw_arc(canvas, .tl, .light);
|
||
try self.draw_arc(canvas, .tr, .light);
|
||
},
|
||
// ''
|
||
0x0f5e7 => {
|
||
self.vline_middle(canvas, .light);
|
||
try self.draw_arc(canvas, .bl, .light);
|
||
try self.draw_arc(canvas, .br, .light);
|
||
},
|
||
// ''
|
||
0x0f5e8 => {
|
||
self.hline_middle(canvas, .light);
|
||
try self.draw_arc(canvas, .bl, .light);
|
||
try self.draw_arc(canvas, .tl, .light);
|
||
},
|
||
// ''
|
||
0x0f5e9 => {
|
||
self.hline_middle(canvas, .light);
|
||
try self.draw_arc(canvas, .tr, .light);
|
||
try self.draw_arc(canvas, .br, .light);
|
||
},
|
||
// ''
|
||
0x0f5ea => {
|
||
self.vline_middle(canvas, .light);
|
||
try self.draw_arc(canvas, .tl, .light);
|
||
try self.draw_arc(canvas, .br, .light);
|
||
},
|
||
// ''
|
||
0x0f5eb => {
|
||
self.vline_middle(canvas, .light);
|
||
try self.draw_arc(canvas, .tr, .light);
|
||
try self.draw_arc(canvas, .bl, .light);
|
||
},
|
||
// ''
|
||
0x0f5ec => {
|
||
self.hline_middle(canvas, .light);
|
||
try self.draw_arc(canvas, .tl, .light);
|
||
try self.draw_arc(canvas, .br, .light);
|
||
},
|
||
// ''
|
||
0x0f5ed => {
|
||
self.hline_middle(canvas, .light);
|
||
try self.draw_arc(canvas, .tr, .light);
|
||
try self.draw_arc(canvas, .bl, .light);
|
||
},
|
||
// ''
|
||
0x0f5ee => self.draw_branch_node(canvas, .{ .filled = true }, .light),
|
||
// ''
|
||
0x0f5ef => self.draw_branch_node(canvas, .{}, .light),
|
||
|
||
// ''
|
||
0x0f5f0 => self.draw_branch_node(canvas, .{
|
||
.right = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5f1 => self.draw_branch_node(canvas, .{
|
||
.right = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5f2 => self.draw_branch_node(canvas, .{
|
||
.left = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5f3 => self.draw_branch_node(canvas, .{
|
||
.left = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5f4 => self.draw_branch_node(canvas, .{
|
||
.left = true,
|
||
.right = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5f5 => self.draw_branch_node(canvas, .{
|
||
.left = true,
|
||
.right = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5f6 => self.draw_branch_node(canvas, .{
|
||
.down = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5f7 => self.draw_branch_node(canvas, .{
|
||
.down = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5f8 => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5f9 => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5fa => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.down = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5fb => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.down = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5fc => self.draw_branch_node(canvas, .{
|
||
.right = true,
|
||
.down = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5fd => self.draw_branch_node(canvas, .{
|
||
.right = true,
|
||
.down = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5fe => self.draw_branch_node(canvas, .{
|
||
.left = true,
|
||
.down = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f5ff => self.draw_branch_node(canvas, .{
|
||
.left = true,
|
||
.down = true,
|
||
}, .light),
|
||
|
||
// ''
|
||
0x0f600 => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.right = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f601 => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.right = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f602 => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.left = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f603 => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.left = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f604 => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.down = true,
|
||
.right = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f605 => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.down = true,
|
||
.right = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f606 => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.down = true,
|
||
.left = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f607 => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.down = true,
|
||
.left = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f608 => self.draw_branch_node(canvas, .{
|
||
.down = true,
|
||
.left = true,
|
||
.right = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f609 => self.draw_branch_node(canvas, .{
|
||
.down = true,
|
||
.left = true,
|
||
.right = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f60a => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.left = true,
|
||
.right = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f60b => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.left = true,
|
||
.right = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f60c => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.down = true,
|
||
.left = true,
|
||
.right = true,
|
||
.filled = true,
|
||
}, .light),
|
||
// ''
|
||
0x0f60d => self.draw_branch_node(canvas, .{
|
||
.up = true,
|
||
.down = true,
|
||
.left = true,
|
||
.right = true,
|
||
}, .light),
|
||
|
||
// Not official box characters but special characters we hide
|
||
// in the high bits of a unicode codepoint.
|
||
@intFromEnum(Sprite.cursor_rect) => self.draw_cursor_rect(canvas),
|
||
@intFromEnum(Sprite.cursor_hollow_rect) => self.draw_cursor_hollow_rect(canvas),
|
||
@intFromEnum(Sprite.cursor_bar) => self.draw_cursor_bar(canvas),
|
||
|
||
else => return error.InvalidCodepoint,
|
||
}
|
||
}
|
||
|
||
fn draw_lines(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
lines: Lines,
|
||
) void {
|
||
const light_px = Thickness.light.height(self.thickness);
|
||
const heavy_px = Thickness.heavy.height(self.thickness);
|
||
|
||
// Top of light horizontal strokes
|
||
const h_light_top = (self.height -| light_px) / 2;
|
||
// Bottom of light horizontal strokes
|
||
const h_light_bottom = h_light_top +| light_px;
|
||
|
||
// Top of heavy horizontal strokes
|
||
const h_heavy_top = (self.height -| heavy_px) / 2;
|
||
// Bottom of heavy horizontal strokes
|
||
const h_heavy_bottom = h_heavy_top +| heavy_px;
|
||
|
||
// Top of the top doubled horizontal stroke (bottom is `h_light_top`)
|
||
const h_double_top = h_light_top -| light_px;
|
||
// Bottom of the bottom doubled horizontal stroke (top is `h_light_bottom`)
|
||
const h_double_bottom = h_light_bottom +| light_px;
|
||
|
||
// Left of light vertical strokes
|
||
const v_light_left = (self.width -| light_px) / 2;
|
||
// Right of light vertical strokes
|
||
const v_light_right = v_light_left +| light_px;
|
||
|
||
// Left of heavy vertical strokes
|
||
const v_heavy_left = (self.width -| heavy_px) / 2;
|
||
// Right of heavy vertical strokes
|
||
const v_heavy_right = v_heavy_left +| heavy_px;
|
||
|
||
// Left of the left doubled vertical stroke (right is `v_light_left`)
|
||
const v_double_left = v_light_left -| light_px;
|
||
// Right of the right doubled vertical stroke (left is `v_light_right`)
|
||
const v_double_right = v_light_right +| light_px;
|
||
|
||
// The bottom of the up line
|
||
const up_bottom = if (lines.left == .heavy or lines.right == .heavy)
|
||
h_heavy_bottom
|
||
else if (lines.left != lines.right or lines.down == lines.up)
|
||
if (lines.left == .double or lines.right == .double)
|
||
h_double_bottom
|
||
else
|
||
h_light_bottom
|
||
else if (lines.left == .none and lines.right == .none)
|
||
h_light_bottom
|
||
else
|
||
h_light_top;
|
||
|
||
// The top of the down line
|
||
const down_top = if (lines.left == .heavy or lines.right == .heavy)
|
||
h_heavy_top
|
||
else if (lines.left != lines.right or lines.up == lines.down)
|
||
if (lines.left == .double or lines.right == .double)
|
||
h_double_top
|
||
else
|
||
h_light_top
|
||
else if (lines.left == .none and lines.right == .none)
|
||
h_light_top
|
||
else
|
||
h_light_bottom;
|
||
|
||
// The right of the left line
|
||
const left_right = if (lines.up == .heavy or lines.down == .heavy)
|
||
v_heavy_right
|
||
else if (lines.up != lines.down or lines.left == lines.right)
|
||
if (lines.up == .double or lines.down == .double)
|
||
v_double_right
|
||
else
|
||
v_light_right
|
||
else if (lines.up == .none and lines.down == .none)
|
||
v_light_right
|
||
else
|
||
v_light_left;
|
||
|
||
// The left of the right line
|
||
const right_left = if (lines.up == .heavy or lines.down == .heavy)
|
||
v_heavy_left
|
||
else if (lines.up != lines.down or lines.right == lines.left)
|
||
if (lines.up == .double or lines.down == .double)
|
||
v_double_left
|
||
else
|
||
v_light_left
|
||
else if (lines.up == .none and lines.down == .none)
|
||
v_light_left
|
||
else
|
||
v_light_right;
|
||
|
||
switch (lines.up) {
|
||
.none => {},
|
||
.light => self.rect(canvas, v_light_left, 0, v_light_right, up_bottom),
|
||
.heavy => self.rect(canvas, v_heavy_left, 0, v_heavy_right, up_bottom),
|
||
.double => {
|
||
const left_bottom = if (lines.left == .double) h_light_top else up_bottom;
|
||
const right_bottom = if (lines.right == .double) h_light_top else up_bottom;
|
||
|
||
self.rect(canvas, v_double_left, 0, v_light_left, left_bottom);
|
||
self.rect(canvas, v_light_right, 0, v_double_right, right_bottom);
|
||
},
|
||
}
|
||
|
||
switch (lines.right) {
|
||
.none => {},
|
||
.light => self.rect(canvas, right_left, h_light_top, self.width, h_light_bottom),
|
||
.heavy => self.rect(canvas, right_left, h_heavy_top, self.width, h_heavy_bottom),
|
||
.double => {
|
||
const top_left = if (lines.up == .double) v_light_right else right_left;
|
||
const bottom_left = if (lines.down == .double) v_light_right else right_left;
|
||
|
||
self.rect(canvas, top_left, h_double_top, self.width, h_light_top);
|
||
self.rect(canvas, bottom_left, h_light_bottom, self.width, h_double_bottom);
|
||
},
|
||
}
|
||
|
||
switch (lines.down) {
|
||
.none => {},
|
||
.light => self.rect(canvas, v_light_left, down_top, v_light_right, self.height),
|
||
.heavy => self.rect(canvas, v_heavy_left, down_top, v_heavy_right, self.height),
|
||
.double => {
|
||
const left_top = if (lines.left == .double) h_light_bottom else down_top;
|
||
const right_top = if (lines.right == .double) h_light_bottom else down_top;
|
||
|
||
self.rect(canvas, v_double_left, left_top, v_light_left, self.height);
|
||
self.rect(canvas, v_light_right, right_top, v_double_right, self.height);
|
||
},
|
||
}
|
||
|
||
switch (lines.left) {
|
||
.none => {},
|
||
.light => self.rect(canvas, 0, h_light_top, left_right, h_light_bottom),
|
||
.heavy => self.rect(canvas, 0, h_heavy_top, left_right, h_heavy_bottom),
|
||
.double => {
|
||
const top_right = if (lines.up == .double) v_light_left else left_right;
|
||
const bottom_right = if (lines.down == .double) v_light_left else left_right;
|
||
|
||
self.rect(canvas, 0, h_double_top, top_right, h_light_top);
|
||
self.rect(canvas, 0, h_light_bottom, bottom_right, h_double_bottom);
|
||
},
|
||
}
|
||
}
|
||
|
||
fn draw_light_triple_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_dash_horizontal(
|
||
canvas,
|
||
3,
|
||
Thickness.light.height(self.thickness),
|
||
@max(4, Thickness.light.height(self.thickness)),
|
||
);
|
||
}
|
||
|
||
fn draw_heavy_triple_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_dash_horizontal(
|
||
canvas,
|
||
3,
|
||
Thickness.heavy.height(self.thickness),
|
||
@max(4, Thickness.light.height(self.thickness)),
|
||
);
|
||
}
|
||
|
||
fn draw_light_triple_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_dash_vertical(
|
||
canvas,
|
||
3,
|
||
Thickness.light.height(self.thickness),
|
||
@max(4, Thickness.light.height(self.thickness)),
|
||
);
|
||
}
|
||
|
||
fn draw_heavy_triple_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_dash_vertical(
|
||
canvas,
|
||
3,
|
||
Thickness.heavy.height(self.thickness),
|
||
@max(4, Thickness.light.height(self.thickness)),
|
||
);
|
||
}
|
||
|
||
fn draw_light_quadruple_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_dash_horizontal(
|
||
canvas,
|
||
4,
|
||
Thickness.light.height(self.thickness),
|
||
@max(4, Thickness.light.height(self.thickness)),
|
||
);
|
||
}
|
||
|
||
fn draw_heavy_quadruple_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_dash_horizontal(
|
||
canvas,
|
||
4,
|
||
Thickness.heavy.height(self.thickness),
|
||
@max(4, Thickness.light.height(self.thickness)),
|
||
);
|
||
}
|
||
|
||
fn draw_light_quadruple_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_dash_vertical(
|
||
canvas,
|
||
4,
|
||
Thickness.light.height(self.thickness),
|
||
@max(4, Thickness.light.height(self.thickness)),
|
||
);
|
||
}
|
||
|
||
fn draw_heavy_quadruple_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_dash_vertical(
|
||
canvas,
|
||
4,
|
||
Thickness.heavy.height(self.thickness),
|
||
@max(4, Thickness.light.height(self.thickness)),
|
||
);
|
||
}
|
||
|
||
fn draw_light_double_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_dash_horizontal(
|
||
canvas,
|
||
2,
|
||
Thickness.light.height(self.thickness),
|
||
Thickness.light.height(self.thickness),
|
||
);
|
||
}
|
||
|
||
fn draw_heavy_double_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_dash_horizontal(
|
||
canvas,
|
||
2,
|
||
Thickness.heavy.height(self.thickness),
|
||
Thickness.heavy.height(self.thickness),
|
||
);
|
||
}
|
||
|
||
fn draw_light_double_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_dash_vertical(
|
||
canvas,
|
||
2,
|
||
Thickness.light.height(self.thickness),
|
||
Thickness.heavy.height(self.thickness),
|
||
);
|
||
}
|
||
|
||
fn draw_heavy_double_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_dash_vertical(
|
||
canvas,
|
||
2,
|
||
Thickness.heavy.height(self.thickness),
|
||
Thickness.heavy.height(self.thickness),
|
||
);
|
||
}
|
||
|
||
fn draw_light_diagonal_upper_right_to_lower_left(self: Box, canvas: *font.sprite.Canvas) void {
|
||
canvas.line(.{
|
||
.p0 = .{ .x = @floatFromInt(self.width), .y = 0 },
|
||
.p1 = .{ .x = 0, .y = @floatFromInt(self.height) },
|
||
}, @floatFromInt(Thickness.light.height(self.thickness)), .on) catch {};
|
||
}
|
||
|
||
fn draw_light_diagonal_upper_left_to_lower_right(self: Box, canvas: *font.sprite.Canvas) void {
|
||
canvas.line(.{
|
||
.p0 = .{ .x = 0, .y = 0 },
|
||
.p1 = .{
|
||
.x = @floatFromInt(self.width),
|
||
.y = @floatFromInt(self.height),
|
||
},
|
||
}, @floatFromInt(Thickness.light.height(self.thickness)), .on) catch {};
|
||
}
|
||
|
||
fn draw_light_diagonal_cross(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_light_diagonal_upper_right_to_lower_left(canvas);
|
||
self.draw_light_diagonal_upper_left_to_lower_right(canvas);
|
||
}
|
||
|
||
fn draw_block(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
comptime alignment: Alignment,
|
||
comptime width: f64,
|
||
comptime height: f64,
|
||
) void {
|
||
self.draw_block_shade(canvas, alignment, width, height, .on);
|
||
}
|
||
|
||
fn draw_block_shade(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
comptime alignment: Alignment,
|
||
comptime width: f64,
|
||
comptime height: f64,
|
||
comptime shade: Shade,
|
||
) void {
|
||
const float_width: f64 = @floatFromInt(self.width);
|
||
const float_height: f64 = @floatFromInt(self.height);
|
||
|
||
const w: u32 = @intFromFloat(@round(float_width * width));
|
||
const h: u32 = @intFromFloat(@round(float_height * height));
|
||
|
||
const x = switch (alignment.horizontal) {
|
||
.left => 0,
|
||
.right => self.width - w,
|
||
.center => (self.width - w) / 2,
|
||
};
|
||
const y = switch (alignment.vertical) {
|
||
.top => 0,
|
||
.bottom => self.height - h,
|
||
.middle => (self.height - h) / 2,
|
||
};
|
||
|
||
canvas.rect(.{
|
||
.x = x,
|
||
.y = y,
|
||
.width = w,
|
||
.height = h,
|
||
}, @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade))));
|
||
}
|
||
|
||
fn draw_corner_triangle_shade(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
comptime corner: Corner,
|
||
comptime shade: Shade,
|
||
) void {
|
||
const x0, const y0, const x1, const y1, const x2, const y2 = switch (corner) {
|
||
.tl => .{ 0, 0, 0, self.height, self.width, 0 },
|
||
.tr => .{ 0, 0, self.width, self.height, self.width, 0 },
|
||
.bl => .{ 0, 0, 0, self.height, self.width, self.height },
|
||
.br => .{ 0, self.height, self.width, self.height, self.width, 0 },
|
||
};
|
||
|
||
canvas.triangle(.{
|
||
.p0 = .{ .x = @floatFromInt(x0), .y = @floatFromInt(y0) },
|
||
.p1 = .{ .x = @floatFromInt(x1), .y = @floatFromInt(y1) },
|
||
.p2 = .{ .x = @floatFromInt(x2), .y = @floatFromInt(y2) },
|
||
}, @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade)))) catch {};
|
||
}
|
||
|
||
fn draw_full_block(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.rect(canvas, 0, 0, self.width, self.height);
|
||
}
|
||
|
||
fn draw_vertical_one_eighth_block_n(self: Box, canvas: *font.sprite.Canvas, n: u32) void {
|
||
const x = @as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(n)) * @as(f64, @floatFromInt(self.width)) / 8)));
|
||
const w = @as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(self.width)) / 8)));
|
||
self.rect(canvas, x, 0, x + w, self.height);
|
||
}
|
||
|
||
fn draw_checkerboard_fill(self: Box, canvas: *font.sprite.Canvas, parity: u1) void {
|
||
const float_width: f64 = @floatFromInt(self.width);
|
||
const float_height: f64 = @floatFromInt(self.height);
|
||
const x_size: usize = 4;
|
||
const y_size: usize = @intFromFloat(@round(4 * (float_height / float_width)));
|
||
for (0..x_size) |x| {
|
||
const x0 = (self.width * x) / x_size;
|
||
const x1 = (self.width * (x + 1)) / x_size;
|
||
for (0..y_size) |y| {
|
||
const y0 = (self.height * y) / y_size;
|
||
const y1 = (self.height * (y + 1)) / y_size;
|
||
if ((x + y) % 2 == parity) {
|
||
canvas.rect(.{
|
||
.x = @intCast(x0),
|
||
.y = @intCast(y0),
|
||
.width = @intCast(x1 -| x0),
|
||
.height = @intCast(y1 -| y0),
|
||
}, .on);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
fn draw_upper_left_to_lower_right_fill(self: Box, canvas: *font.sprite.Canvas) void {
|
||
const thick_px = Thickness.light.height(self.thickness);
|
||
const line_count = self.width / (2 * thick_px);
|
||
|
||
const float_width: f64 = @floatFromInt(self.width);
|
||
const float_height: f64 = @floatFromInt(self.height);
|
||
const float_thick: f64 = @floatFromInt(thick_px);
|
||
const stride = @round(float_width / @as(f64, @floatFromInt(line_count)));
|
||
|
||
for (0..line_count * 2 + 1) |_i| {
|
||
const i = @as(i32, @intCast(_i)) - @as(i32, @intCast(line_count));
|
||
const top_x = @as(f64, @floatFromInt(i)) * stride;
|
||
const bottom_x = float_width + top_x;
|
||
canvas.line(.{
|
||
.p0 = .{ .x = top_x, .y = 0 },
|
||
.p1 = .{ .x = bottom_x, .y = float_height },
|
||
}, float_thick, .on) catch {};
|
||
}
|
||
}
|
||
|
||
fn draw_upper_right_to_lower_left_fill(self: Box, canvas: *font.sprite.Canvas) void {
|
||
const thick_px = Thickness.light.height(self.thickness);
|
||
const line_count = self.width / (2 * thick_px);
|
||
|
||
const float_width: f64 = @floatFromInt(self.width);
|
||
const float_height: f64 = @floatFromInt(self.height);
|
||
const float_thick: f64 = @floatFromInt(thick_px);
|
||
const stride = @round(float_width / @as(f64, @floatFromInt(line_count)));
|
||
|
||
for (0..line_count * 2 + 1) |_i| {
|
||
const i = @as(i32, @intCast(_i)) - @as(i32, @intCast(line_count));
|
||
const bottom_x = @as(f64, @floatFromInt(i)) * stride;
|
||
const top_x = float_width + bottom_x;
|
||
canvas.line(.{
|
||
.p0 = .{ .x = top_x, .y = 0 },
|
||
.p1 = .{ .x = bottom_x, .y = float_height },
|
||
}, float_thick, .on) catch {};
|
||
}
|
||
}
|
||
|
||
fn draw_corner_diagonal_lines(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
comptime corners: Quads,
|
||
) void {
|
||
const thick_px = Thickness.light.height(self.thickness);
|
||
|
||
const float_width: f64 = @floatFromInt(self.width);
|
||
const float_height: f64 = @floatFromInt(self.height);
|
||
const float_thick: f64 = @floatFromInt(thick_px);
|
||
const center_x: f64 = @floatFromInt(self.width / 2 + self.width % 2);
|
||
const center_y: f64 = @floatFromInt(self.height / 2 + self.height % 2);
|
||
|
||
if (corners.tl) canvas.line(.{
|
||
.p0 = .{ .x = center_x, .y = 0 },
|
||
.p1 = .{ .x = 0, .y = center_y },
|
||
}, float_thick, .on) catch {};
|
||
|
||
if (corners.tr) canvas.line(.{
|
||
.p0 = .{ .x = center_x, .y = 0 },
|
||
.p1 = .{ .x = float_width, .y = center_y },
|
||
}, float_thick, .on) catch {};
|
||
|
||
if (corners.bl) canvas.line(.{
|
||
.p0 = .{ .x = center_x, .y = float_height },
|
||
.p1 = .{ .x = 0, .y = center_y },
|
||
}, float_thick, .on) catch {};
|
||
|
||
if (corners.br) canvas.line(.{
|
||
.p0 = .{ .x = center_x, .y = float_height },
|
||
.p1 = .{ .x = float_width, .y = center_y },
|
||
}, float_thick, .on) catch {};
|
||
}
|
||
|
||
fn draw_cell_diagonal(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
comptime from: Alignment,
|
||
comptime to: Alignment,
|
||
) void {
|
||
const float_width: f64 = @floatFromInt(self.width);
|
||
const float_height: f64 = @floatFromInt(self.height);
|
||
|
||
const x0: f64 = switch (from.horizontal) {
|
||
.left => 0,
|
||
.right => float_width,
|
||
.center => float_width / 2,
|
||
};
|
||
const y0: f64 = switch (from.vertical) {
|
||
.top => 0,
|
||
.bottom => float_height,
|
||
.middle => float_height / 2,
|
||
};
|
||
const x1: f64 = switch (to.horizontal) {
|
||
.left => 0,
|
||
.right => float_width,
|
||
.center => float_width / 2,
|
||
};
|
||
const y1: f64 = switch (to.vertical) {
|
||
.top => 0,
|
||
.bottom => float_height,
|
||
.middle => float_height / 2,
|
||
};
|
||
|
||
self.draw_line(
|
||
canvas,
|
||
.{ .x = x0, .y = y0 },
|
||
.{ .x = x1, .y = y1 },
|
||
.light,
|
||
) catch {};
|
||
}
|
||
|
||
fn draw_fading_line(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
comptime to: Edge,
|
||
comptime thickness: Thickness,
|
||
) void {
|
||
const thick_px = thickness.height(self.thickness);
|
||
const float_width: f64 = @floatFromInt(self.width);
|
||
const float_height: f64 = @floatFromInt(self.height);
|
||
|
||
// Top of horizontal strokes
|
||
const h_top = (self.height -| thick_px) / 2;
|
||
// Bottom of horizontal strokes
|
||
const h_bottom = h_top +| thick_px;
|
||
// Left of vertical strokes
|
||
const v_left = (self.width -| thick_px) / 2;
|
||
// Right of vertical strokes
|
||
const v_right = v_left +| thick_px;
|
||
|
||
// If we're fading to the top or left, we start with 0.0
|
||
// and increment up as we progress, otherwise we start
|
||
// at 255.0 and increment down (negative).
|
||
var color: f64 = switch (to) {
|
||
.top, .left => 0.0,
|
||
.bottom, .right => 255.0,
|
||
};
|
||
const inc: f64 = 255.0 / switch (to) {
|
||
.top => float_height,
|
||
.bottom => -float_height,
|
||
.left => float_width,
|
||
.right => -float_width,
|
||
};
|
||
|
||
switch (to) {
|
||
.top, .bottom => {
|
||
for (0..self.height) |y| {
|
||
for (v_left..v_right) |x| {
|
||
canvas.pixel(
|
||
@intCast(x),
|
||
@intCast(y),
|
||
@enumFromInt(@as(u8, @intFromFloat(@round(color)))),
|
||
);
|
||
}
|
||
color += inc;
|
||
}
|
||
},
|
||
.left, .right => {
|
||
for (0..self.width) |x| {
|
||
for (h_top..h_bottom) |y| {
|
||
canvas.pixel(
|
||
@intCast(x),
|
||
@intCast(y),
|
||
@enumFromInt(@as(u8, @intFromFloat(@round(color)))),
|
||
);
|
||
}
|
||
color += inc;
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
fn draw_branch_node(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
node: BranchNode,
|
||
comptime thickness: Thickness,
|
||
) void {
|
||
const thick_px = thickness.height(self.thickness);
|
||
const float_width: f64 = @floatFromInt(self.width);
|
||
const float_height: f64 = @floatFromInt(self.height);
|
||
const float_thick: f64 = @floatFromInt(thick_px);
|
||
|
||
// Top of horizontal strokes
|
||
const h_top = (self.height -| thick_px) / 2;
|
||
// Bottom of horizontal strokes
|
||
const h_bottom = h_top +| thick_px;
|
||
// Left of vertical strokes
|
||
const v_left = (self.width -| thick_px) / 2;
|
||
// Right of vertical strokes
|
||
const v_right = v_left +| thick_px;
|
||
|
||
// We calculate the center of the circle this way
|
||
// to ensure it aligns with box drawing characters
|
||
// since the lines are sometimes off center to
|
||
// make sure they aren't split between pixels.
|
||
const cx: f64 = @as(f64, @floatFromInt(v_left)) + float_thick / 2;
|
||
const cy: f64 = @as(f64, @floatFromInt(h_top)) + float_thick / 2;
|
||
// The radius needs to be the smallest distance from the center to an edge.
|
||
const r: f64 = @min(
|
||
@min(cx, cy),
|
||
@min(float_width - cx, float_height - cy),
|
||
);
|
||
|
||
var ctx: z2d.Context = .{
|
||
.surface = canvas.sfc,
|
||
.pattern = .{
|
||
.opaque_pattern = .{
|
||
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } },
|
||
},
|
||
},
|
||
.line_width = float_thick,
|
||
};
|
||
|
||
var path = z2d.Path.init(canvas.alloc);
|
||
defer path.deinit();
|
||
|
||
// These @intFromFloat casts shouldn't ever fail since r can never
|
||
// be greater than cx or cy, so when subtracting it from them the
|
||
// result can never be negative.
|
||
if (node.up)
|
||
self.rect(canvas, v_left, 0, v_right, @intFromFloat(@ceil(cy - r)));
|
||
if (node.right)
|
||
self.rect(canvas, @intFromFloat(@floor(cx + r)), h_top, self.width, h_bottom);
|
||
if (node.down)
|
||
self.rect(canvas, v_left, @intFromFloat(@floor(cy + r)), v_right, self.height);
|
||
if (node.left)
|
||
self.rect(canvas, 0, h_top, @intFromFloat(@ceil(cx - r)), h_bottom);
|
||
|
||
if (node.filled) {
|
||
path.arc(cx, cy, r, 0, std.math.pi * 2, false, null) catch return;
|
||
path.close() catch return;
|
||
ctx.fill(canvas.alloc, path) catch return;
|
||
} else {
|
||
path.arc(cx, cy, r - float_thick / 2, 0, std.math.pi * 2, false, null) catch return;
|
||
path.close() catch return;
|
||
ctx.stroke(canvas.alloc, path) catch return;
|
||
}
|
||
}
|
||
|
||
fn draw_circle(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
comptime position: Alignment,
|
||
comptime filled: bool,
|
||
) void {
|
||
const float_width: f64 = @floatFromInt(self.width);
|
||
const float_height: f64 = @floatFromInt(self.height);
|
||
|
||
const x: f64 = switch (position.horizontal) {
|
||
.left => 0,
|
||
.right => float_width,
|
||
.center => float_width / 2,
|
||
};
|
||
const y: f64 = switch (position.vertical) {
|
||
.top => 0,
|
||
.bottom => float_height,
|
||
.middle => float_height / 2,
|
||
};
|
||
const r: f64 = 0.5 * @min(float_width, float_height);
|
||
|
||
var ctx: z2d.Context = .{
|
||
.surface = canvas.sfc,
|
||
.pattern = .{
|
||
.opaque_pattern = .{
|
||
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } },
|
||
},
|
||
},
|
||
.line_width = @floatFromInt(Thickness.light.height(self.thickness)),
|
||
};
|
||
|
||
var path = z2d.Path.init(canvas.alloc);
|
||
defer path.deinit();
|
||
|
||
if (filled) {
|
||
path.arc(x, y, r, 0, std.math.pi * 2, false, null) catch return;
|
||
path.close() catch return;
|
||
ctx.fill(canvas.alloc, path) catch return;
|
||
} else {
|
||
path.arc(x, y, r - ctx.line_width / 2, 0, std.math.pi * 2, false, null) catch return;
|
||
path.close() catch return;
|
||
ctx.stroke(canvas.alloc, path) catch return;
|
||
}
|
||
}
|
||
|
||
fn draw_line(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
p0: font.sprite.Point(f64),
|
||
p1: font.sprite.Point(f64),
|
||
comptime thickness: Thickness,
|
||
) !void {
|
||
canvas.line(
|
||
.{ .p0 = p0, .p1 = p1 },
|
||
@floatFromInt(thickness.height(self.thickness)),
|
||
.on,
|
||
) catch {};
|
||
}
|
||
|
||
fn draw_shade(self: Box, canvas: *font.sprite.Canvas, v: u16) void {
|
||
canvas.rect((font.sprite.Box(u32){
|
||
.p0 = .{ .x = 0, .y = 0 },
|
||
.p1 = .{
|
||
.x = self.width,
|
||
.y = self.height,
|
||
},
|
||
}).rect(), @as(font.sprite.Color, @enumFromInt(v)));
|
||
}
|
||
|
||
fn draw_light_shade(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_shade(canvas, 0x40);
|
||
}
|
||
|
||
fn draw_medium_shade(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_shade(canvas, 0x80);
|
||
}
|
||
|
||
fn draw_dark_shade(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_shade(canvas, 0xc0);
|
||
}
|
||
|
||
fn draw_horizontal_one_eighth_block_n(self: Box, canvas: *font.sprite.Canvas, n: u32) void {
|
||
const h = @as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(self.height)) / 8)));
|
||
const y = @min(
|
||
self.height -| h,
|
||
@as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(n)) * @as(f64, @floatFromInt(self.height)) / 8))),
|
||
);
|
||
self.rect(canvas, 0, y, self.width, y + h);
|
||
}
|
||
|
||
fn draw_horizontal_one_eighth_1358_block(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.draw_horizontal_one_eighth_block_n(canvas, 0);
|
||
self.draw_horizontal_one_eighth_block_n(canvas, 2);
|
||
self.draw_horizontal_one_eighth_block_n(canvas, 4);
|
||
self.draw_horizontal_one_eighth_block_n(canvas, 7);
|
||
}
|
||
|
||
fn draw_quadrant(self: Box, canvas: *font.sprite.Canvas, comptime quads: Quads) void {
|
||
const center_x = self.width / 2 + self.width % 2;
|
||
const center_y = self.height / 2 + self.height % 2;
|
||
|
||
if (quads.tl) self.rect(canvas, 0, 0, center_x, center_y);
|
||
if (quads.tr) self.rect(canvas, center_x, 0, self.width, center_y);
|
||
if (quads.bl) self.rect(canvas, 0, center_y, center_x, self.height);
|
||
if (quads.br) self.rect(canvas, center_x, center_y, self.width, self.height);
|
||
}
|
||
|
||
fn draw_braille(self: Box, canvas: *font.sprite.Canvas, cp: u32) void {
|
||
var w: u32 = @min(self.width / 4, self.height / 8);
|
||
var x_spacing: u32 = self.width / 4;
|
||
var y_spacing: u32 = self.height / 8;
|
||
var x_margin: u32 = x_spacing / 2;
|
||
var y_margin: u32 = y_spacing / 2;
|
||
|
||
var x_px_left: u32 = self.width - 2 * x_margin - x_spacing - 2 * w;
|
||
var y_px_left: u32 = self.height - 2 * y_margin - 3 * y_spacing - 4 * w;
|
||
|
||
// First, try hard to ensure the DOT width is non-zero
|
||
if (x_px_left >= 2 and y_px_left >= 4 and w == 0) {
|
||
w += 1;
|
||
x_px_left -= 2;
|
||
y_px_left -= 4;
|
||
}
|
||
|
||
// Second, prefer a non-zero margin
|
||
if (x_px_left >= 2 and x_margin == 0) {
|
||
x_margin = 1;
|
||
x_px_left -= 2;
|
||
}
|
||
if (y_px_left >= 2 and y_margin == 0) {
|
||
y_margin = 1;
|
||
y_px_left -= 2;
|
||
}
|
||
|
||
// Third, increase spacing
|
||
if (x_px_left >= 1) {
|
||
x_spacing += 1;
|
||
x_px_left -= 1;
|
||
}
|
||
if (y_px_left >= 3) {
|
||
y_spacing += 1;
|
||
y_px_left -= 3;
|
||
}
|
||
|
||
// Fourth, margins (“spacing”, but on the sides)
|
||
if (x_px_left >= 2) {
|
||
x_margin += 1;
|
||
x_px_left -= 2;
|
||
}
|
||
if (y_px_left >= 2) {
|
||
y_margin += 1;
|
||
y_px_left -= 2;
|
||
}
|
||
|
||
// Last - increase dot width
|
||
if (x_px_left >= 2 and y_px_left >= 4) {
|
||
w += 1;
|
||
x_px_left -= 2;
|
||
y_px_left -= 4;
|
||
}
|
||
|
||
assert(x_px_left <= 1 or y_px_left <= 1);
|
||
assert(2 * x_margin + 2 * w + x_spacing <= self.width);
|
||
assert(2 * y_margin + 4 * w + 3 * y_spacing <= self.height);
|
||
|
||
const x = [2]u32{ x_margin, x_margin + w + x_spacing };
|
||
const y = y: {
|
||
var y: [4]u32 = undefined;
|
||
y[0] = y_margin;
|
||
y[1] = y[0] + w + y_spacing;
|
||
y[2] = y[1] + w + y_spacing;
|
||
y[3] = y[2] + w + y_spacing;
|
||
break :y y;
|
||
};
|
||
|
||
assert(cp >= 0x2800);
|
||
assert(cp <= 0x28ff);
|
||
const sym = cp - 0x2800;
|
||
|
||
// Left side
|
||
if (sym & 1 > 0)
|
||
self.rect(canvas, x[0], y[0], x[0] + w, y[0] + w);
|
||
if (sym & 2 > 0)
|
||
self.rect(canvas, x[0], y[1], x[0] + w, y[1] + w);
|
||
if (sym & 4 > 0)
|
||
self.rect(canvas, x[0], y[2], x[0] + w, y[2] + w);
|
||
|
||
// Right side
|
||
if (sym & 8 > 0)
|
||
self.rect(canvas, x[1], y[0], x[1] + w, y[0] + w);
|
||
if (sym & 16 > 0)
|
||
self.rect(canvas, x[1], y[1], x[1] + w, y[1] + w);
|
||
if (sym & 32 > 0)
|
||
self.rect(canvas, x[1], y[2], x[1] + w, y[2] + w);
|
||
|
||
// 8-dot patterns
|
||
if (sym & 64 > 0)
|
||
self.rect(canvas, x[0], y[3], x[0] + w, y[3] + w);
|
||
if (sym & 128 > 0)
|
||
self.rect(canvas, x[1], y[3], x[1] + w, y[3] + w);
|
||
}
|
||
|
||
fn draw_sextant(self: Box, canvas: *font.sprite.Canvas, cp: u32) void {
|
||
const Sextants = packed struct(u6) {
|
||
tl: bool,
|
||
tr: bool,
|
||
ml: bool,
|
||
mr: bool,
|
||
bl: bool,
|
||
br: bool,
|
||
};
|
||
|
||
assert(cp >= 0x1fb00 and cp <= 0x1fb3b);
|
||
const idx = cp - 0x1fb00;
|
||
const sex: Sextants = @bitCast(@as(u6, @intCast(
|
||
idx + (idx / 0x14) + 1,
|
||
)));
|
||
|
||
const x_halfs = self.xHalfs();
|
||
const y_thirds = self.yThirds();
|
||
|
||
if (sex.tl) self.rect(canvas, 0, 0, x_halfs[0], y_thirds[0]);
|
||
if (sex.tr) self.rect(canvas, x_halfs[1], 0, self.width, y_thirds[0]);
|
||
if (sex.ml) self.rect(canvas, 0, y_thirds[0], x_halfs[0], y_thirds[1]);
|
||
if (sex.mr) self.rect(canvas, x_halfs[1], y_thirds[0], self.width, y_thirds[1]);
|
||
if (sex.bl) self.rect(canvas, 0, y_thirds[1], x_halfs[0], self.height);
|
||
if (sex.br) self.rect(canvas, x_halfs[1], y_thirds[1], self.width, self.height);
|
||
}
|
||
|
||
fn xHalfs(self: Box) [2]u32 {
|
||
return .{
|
||
@as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(self.width)) / 2))),
|
||
@as(u32, @intFromFloat(@as(f64, @floatFromInt(self.width)) / 2)),
|
||
};
|
||
}
|
||
|
||
fn yThirds(self: Box) [2]u32 {
|
||
return switch (@mod(self.height, 3)) {
|
||
0 => .{ self.height / 3, 2 * self.height / 3 },
|
||
1 => .{ self.height / 3, 2 * self.height / 3 + 1 },
|
||
2 => .{ self.height / 3 + 1, 2 * self.height / 3 },
|
||
else => unreachable,
|
||
};
|
||
}
|
||
|
||
fn draw_smooth_mosaic(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
mosaic: SmoothMosaic,
|
||
) !void {
|
||
const y_thirds = self.yThirds();
|
||
const top: f64 = 0.0;
|
||
const upper: f64 = @floatFromInt(y_thirds[0]);
|
||
const lower: f64 = @floatFromInt(y_thirds[1]);
|
||
const bottom: f64 = @floatFromInt(self.height);
|
||
const left: f64 = 0.0;
|
||
const center: f64 = @round(@as(f64, @floatFromInt(self.width)) / 2);
|
||
const right: f64 = @floatFromInt(self.width);
|
||
|
||
var path = z2d.Path.init(canvas.alloc);
|
||
defer path.deinit();
|
||
|
||
if (mosaic.tl) try path.lineTo(left, top);
|
||
if (mosaic.ul) try path.lineTo(left, upper);
|
||
if (mosaic.ll) try path.lineTo(left, lower);
|
||
if (mosaic.bl) try path.lineTo(left, bottom);
|
||
if (mosaic.bc) try path.lineTo(center, bottom);
|
||
if (mosaic.br) try path.lineTo(right, bottom);
|
||
if (mosaic.lr) try path.lineTo(right, lower);
|
||
if (mosaic.ur) try path.lineTo(right, upper);
|
||
if (mosaic.tr) try path.lineTo(right, top);
|
||
if (mosaic.tc) try path.lineTo(center, top);
|
||
try path.close();
|
||
|
||
var ctx: z2d.Context = .{
|
||
.surface = canvas.sfc,
|
||
.pattern = .{
|
||
.opaque_pattern = .{
|
||
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } },
|
||
},
|
||
},
|
||
};
|
||
|
||
try ctx.fill(canvas.alloc, path);
|
||
}
|
||
|
||
fn draw_edge_triangle(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
comptime edge: Edge,
|
||
) !void {
|
||
const upper: f64 = 0.0;
|
||
const middle: f64 = @round(@as(f64, @floatFromInt(self.height)) / 2);
|
||
const lower: f64 = @floatFromInt(self.height);
|
||
const left: f64 = 0.0;
|
||
const center: f64 = @round(@as(f64, @floatFromInt(self.width)) / 2);
|
||
const right: f64 = @floatFromInt(self.width);
|
||
|
||
var path = z2d.Path.init(canvas.alloc);
|
||
defer path.deinit();
|
||
|
||
const x0, const y0, const x1, const y1 = switch (edge) {
|
||
.top => .{ right, upper, left, upper },
|
||
.left => .{ left, upper, left, lower },
|
||
.bottom => .{ left, lower, right, lower },
|
||
.right => .{ right, lower, right, upper },
|
||
};
|
||
|
||
try path.moveTo(center, middle);
|
||
try path.lineTo(x0, y0);
|
||
try path.lineTo(x1, y1);
|
||
try path.close();
|
||
|
||
var ctx: z2d.Context = .{
|
||
.surface = canvas.sfc,
|
||
.pattern = .{
|
||
.opaque_pattern = .{
|
||
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } },
|
||
},
|
||
},
|
||
};
|
||
|
||
try ctx.fill(canvas.alloc, path);
|
||
}
|
||
|
||
fn draw_arc(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
comptime corner: Corner,
|
||
comptime thickness: Thickness,
|
||
) !void {
|
||
const thick_px = thickness.height(self.thickness);
|
||
const float_width: f64 = @floatFromInt(self.width);
|
||
const float_height: f64 = @floatFromInt(self.height);
|
||
const float_thick: f64 = @floatFromInt(thick_px);
|
||
const center_x: f64 = @as(f64, @floatFromInt((self.width -| thick_px) / 2)) + float_thick / 2;
|
||
const center_y: f64 = @as(f64, @floatFromInt((self.height -| thick_px) / 2)) + float_thick / 2;
|
||
|
||
const r = @min(float_width, float_height) / 2;
|
||
|
||
// Fraction away from the center to place the middle control points,
|
||
const s: f64 = 0.25;
|
||
|
||
var ctx: z2d.Context = .{
|
||
.surface = canvas.sfc,
|
||
.pattern = .{
|
||
.opaque_pattern = .{
|
||
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } },
|
||
},
|
||
},
|
||
.line_width = float_thick,
|
||
.line_cap_mode = .round,
|
||
};
|
||
|
||
var path = z2d.Path.init(canvas.alloc);
|
||
defer path.deinit();
|
||
|
||
switch (corner) {
|
||
.tl => {
|
||
try path.moveTo(center_x, 0);
|
||
try path.lineTo(center_x, center_y - r);
|
||
try path.curveTo(
|
||
center_x,
|
||
center_y - s * r,
|
||
center_x - s * r,
|
||
center_y,
|
||
center_x - r,
|
||
center_y,
|
||
);
|
||
try path.lineTo(0, center_y);
|
||
},
|
||
.tr => {
|
||
try path.moveTo(center_x, 0);
|
||
try path.lineTo(center_x, center_y - r);
|
||
try path.curveTo(
|
||
center_x,
|
||
center_y - s * r,
|
||
center_x + s * r,
|
||
center_y,
|
||
center_x + r,
|
||
center_y,
|
||
);
|
||
try path.lineTo(float_width, center_y);
|
||
},
|
||
.bl => {
|
||
try path.moveTo(center_x, float_height);
|
||
try path.lineTo(center_x, center_y + r);
|
||
try path.curveTo(
|
||
center_x,
|
||
center_y + s * r,
|
||
center_x - s * r,
|
||
center_y,
|
||
center_x - r,
|
||
center_y,
|
||
);
|
||
try path.lineTo(0, center_y);
|
||
},
|
||
.br => {
|
||
try path.moveTo(center_x, float_height);
|
||
try path.lineTo(center_x, center_y + r);
|
||
try path.curveTo(
|
||
center_x,
|
||
center_y + s * r,
|
||
center_x + s * r,
|
||
center_y,
|
||
center_x + r,
|
||
center_y,
|
||
);
|
||
try path.lineTo(float_width, center_y);
|
||
},
|
||
}
|
||
try ctx.stroke(canvas.alloc, path);
|
||
}
|
||
|
||
fn draw_dash_horizontal(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
count: u8,
|
||
thick_px: u32,
|
||
desired_gap: u32,
|
||
) void {
|
||
assert(count >= 2 and count <= 4);
|
||
|
||
// +------------+
|
||
// | |
|
||
// | |
|
||
// | |
|
||
// | |
|
||
// | -- -- -- |
|
||
// | |
|
||
// | |
|
||
// | |
|
||
// | |
|
||
// +------------+
|
||
// Our dashed line should be made such that when tiled horizontally
|
||
// it creates one consistent line with no uneven gap or segment sizes.
|
||
// In order to make sure this is the case, we should have half-sized
|
||
// gaps on the left and right so that it is centered properly.
|
||
|
||
// For N dashes, there are N - 1 gaps between them, but we also have
|
||
// half-sized gaps on either side, adding up to N total gaps.
|
||
const gap_count = count;
|
||
|
||
// We need at least 1 pixel for each gap and each dash, if we don't
|
||
// have that then we can't draw our dashed line correctly so we just
|
||
// draw a solid line and return.
|
||
if (self.width < count + gap_count) {
|
||
self.hline_middle(canvas, .light);
|
||
return;
|
||
}
|
||
|
||
// We never want the gaps to take up more than 50% of the space,
|
||
// because if they do the dashes are too small and look wrong.
|
||
const gap_width = @min(desired_gap, self.width / (2 * count));
|
||
const total_gap_width = gap_count * gap_width;
|
||
const total_dash_width = self.width - total_gap_width;
|
||
const dash_width = total_dash_width / count;
|
||
const remaining = total_dash_width % count;
|
||
|
||
assert(dash_width * count + gap_width * gap_count + remaining == self.width);
|
||
|
||
// Our dashes should be centered vertically.
|
||
const y: u32 = (self.height -| thick_px) / 2;
|
||
|
||
// We start at half a gap from the left edge, in order to center
|
||
// our dashes properly.
|
||
var x: u32 = gap_width / 2;
|
||
|
||
// We'll distribute the extra space in to dash widths, 1px at a
|
||
// time. We prefer this to making gaps larger since that is much
|
||
// more visually obvious.
|
||
var extra: u32 = remaining;
|
||
|
||
for (0..count) |_| {
|
||
var x1 = x + dash_width;
|
||
// We distribute left-over size in to dash widths,
|
||
// since it's less obvious there than in the gaps.
|
||
if (extra > 0) {
|
||
extra -= 1;
|
||
x1 += 1;
|
||
}
|
||
self.hline(canvas, x, x1, y, thick_px);
|
||
// Advance by the width of the dash we drew and the width
|
||
// of a gap to get the the start of the next dash.
|
||
x = x1 + gap_width;
|
||
}
|
||
}
|
||
|
||
fn draw_dash_vertical(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
comptime count: u8,
|
||
thick_px: u32,
|
||
desired_gap: u32,
|
||
) void {
|
||
assert(count >= 2 and count <= 4);
|
||
|
||
// +-----------+
|
||
// | | |
|
||
// | | |
|
||
// | |
|
||
// | | |
|
||
// | | |
|
||
// | |
|
||
// | | |
|
||
// | | |
|
||
// | |
|
||
// +-----------+
|
||
// Our dashed line should be made such that when tiled vertically it
|
||
// it creates one consistent line with no uneven gap or segment sizes.
|
||
// In order to make sure this is the case, we should have an extra gap
|
||
// gap at the bottom.
|
||
//
|
||
// A single full-sized extra gap is preferred to two half-sized ones for
|
||
// vertical to allow better joining to solid characters without creating
|
||
// visible half-sized gaps. Unlike horizontal, centering is a lot less
|
||
// important, visually.
|
||
|
||
// Because of the extra gap at the bottom, there are as many gaps as
|
||
// there are dashes.
|
||
const gap_count = count;
|
||
|
||
// We need at least 1 pixel for each gap and each dash, if we don't
|
||
// have that then we can't draw our dashed line correctly so we just
|
||
// draw a solid line and return.
|
||
if (self.height < count + gap_count) {
|
||
self.vline_middle(canvas, .light);
|
||
return;
|
||
}
|
||
|
||
// We never want the gaps to take up more than 50% of the space,
|
||
// because if they do the dashes are too small and look wrong.
|
||
const gap_height = @min(desired_gap, self.height / (2 * count));
|
||
const total_gap_height = gap_count * gap_height;
|
||
const total_dash_height = self.height - total_gap_height;
|
||
const dash_height = total_dash_height / count;
|
||
const remaining = total_dash_height % count;
|
||
|
||
assert(dash_height * count + gap_height * gap_count + remaining == self.height);
|
||
|
||
// Our dashes should be centered horizontally.
|
||
const x: u32 = (self.width -| thick_px) / 2;
|
||
|
||
// We start at the top of the cell.
|
||
var y: u32 = 0;
|
||
|
||
// We'll distribute the extra space in to dash heights, 1px at a
|
||
// time. We prefer this to making gaps larger since that is much
|
||
// more visually obvious.
|
||
var extra: u32 = remaining;
|
||
|
||
inline for (0..count) |_| {
|
||
var y1 = y + dash_height;
|
||
// We distribute left-over size in to dash widths,
|
||
// since it's less obvious there than in the gaps.
|
||
if (extra > 0) {
|
||
extra -= 1;
|
||
y1 += 1;
|
||
}
|
||
self.vline(canvas, y, y1, x, thick_px);
|
||
// Advance by the height of the dash we drew and the height
|
||
// of a gap to get the the start of the next dash.
|
||
y = y1 + gap_height;
|
||
}
|
||
}
|
||
|
||
fn draw_cursor_rect(self: Box, canvas: *font.sprite.Canvas) void {
|
||
self.rect(canvas, 0, 0, self.width, self.height);
|
||
}
|
||
|
||
fn draw_cursor_hollow_rect(self: Box, canvas: *font.sprite.Canvas) void {
|
||
const thick_px = Thickness.super_light.height(self.thickness);
|
||
|
||
self.vline(canvas, 0, self.height, 0, thick_px);
|
||
self.vline(canvas, 0, self.height, self.width -| thick_px, thick_px);
|
||
self.hline(canvas, 0, self.width, 0, thick_px);
|
||
self.hline(canvas, 0, self.width, self.height -| thick_px, thick_px);
|
||
}
|
||
|
||
fn draw_cursor_bar(self: Box, canvas: *font.sprite.Canvas) void {
|
||
const thick_px = Thickness.light.height(self.thickness);
|
||
|
||
self.vline(canvas, 0, self.height, 0, thick_px);
|
||
}
|
||
|
||
fn vline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void {
|
||
const thick_px = thickness.height(self.thickness);
|
||
self.vline(canvas, 0, self.height, (self.width -| thick_px) / 2, thick_px);
|
||
}
|
||
|
||
fn hline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void {
|
||
const thick_px = thickness.height(self.thickness);
|
||
self.hline(canvas, 0, self.width, (self.height -| thick_px) / 2, thick_px);
|
||
}
|
||
|
||
fn vline(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
y1: u32,
|
||
y2: u32,
|
||
x: u32,
|
||
thickness_px: u32,
|
||
) void {
|
||
canvas.rect((font.sprite.Box(u32){ .p0 = .{
|
||
.x = @min(@max(x, 0), self.width),
|
||
.y = @min(@max(y1, 0), self.height),
|
||
}, .p1 = .{
|
||
.x = @min(@max(x + thickness_px, 0), self.width),
|
||
.y = @min(@max(y2, 0), self.height),
|
||
} }).rect(), .on);
|
||
}
|
||
|
||
fn hline(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
x1: u32,
|
||
x2: u32,
|
||
y: u32,
|
||
thickness_px: u32,
|
||
) void {
|
||
canvas.rect((font.sprite.Box(u32){ .p0 = .{
|
||
.x = @min(@max(x1, 0), self.width),
|
||
.y = @min(@max(y, 0), self.height),
|
||
}, .p1 = .{
|
||
.x = @min(@max(x2, 0), self.width),
|
||
.y = @min(@max(y + thickness_px, 0), self.height),
|
||
} }).rect(), .on);
|
||
}
|
||
|
||
fn rect(
|
||
self: Box,
|
||
canvas: *font.sprite.Canvas,
|
||
x1: u32,
|
||
y1: u32,
|
||
x2: u32,
|
||
y2: u32,
|
||
) void {
|
||
canvas.rect((font.sprite.Box(u32){ .p0 = .{
|
||
.x = @min(@max(x1, 0), self.width),
|
||
.y = @min(@max(y1, 0), self.height),
|
||
}, .p1 = .{
|
||
.x = @min(@max(x2, 0), self.width),
|
||
.y = @min(@max(y2, 0), self.height),
|
||
} }).rect(), .on);
|
||
}
|
||
|
||
test "all" {
|
||
const testing = std.testing;
|
||
const alloc = testing.allocator;
|
||
|
||
var cp: u32 = 0x2500;
|
||
const end = 0x259f;
|
||
while (cp <= end) : (cp += 1) {
|
||
var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale);
|
||
defer atlas_grayscale.deinit(alloc);
|
||
|
||
const face: Box = .{ .width = 18, .height = 36, .thickness = 2 };
|
||
const glyph = try face.renderGlyph(
|
||
alloc,
|
||
&atlas_grayscale,
|
||
cp,
|
||
);
|
||
try testing.expectEqual(@as(u32, face.width), glyph.width);
|
||
try testing.expectEqual(@as(u32, face.height), glyph.height);
|
||
}
|
||
}
|
||
|
||
fn testRenderAll(self: Box, alloc: Allocator, atlas: *font.Atlas) !void {
|
||
// Box Drawing and Block Elements.
|
||
var cp: u32 = 0x2500;
|
||
while (cp <= 0x259f) : (cp += 1) {
|
||
_ = try self.renderGlyph(
|
||
alloc,
|
||
atlas,
|
||
cp,
|
||
);
|
||
}
|
||
|
||
// Braille
|
||
cp = 0x2800;
|
||
while (cp <= 0x28ff) : (cp += 1) {
|
||
_ = try self.renderGlyph(
|
||
alloc,
|
||
atlas,
|
||
cp,
|
||
);
|
||
}
|
||
|
||
// Symbols for Legacy Computing.
|
||
cp = 0x1fb00;
|
||
while (cp <= 0x1fbef) : (cp += 1) {
|
||
switch (cp) {
|
||
// (Block Mosaics / "Sextants")
|
||
// 🬀 🬁 🬂 🬃 🬄 🬅 🬆 🬇 🬈 🬉 🬊 🬋 🬌 🬍 🬎 🬏 🬐 🬑 🬒 🬓 🬔 🬕 🬖 🬗 🬘 🬙 🬚 🬛 🬜 🬝 🬞 🬟 🬠
|
||
// 🬡 🬢 🬣 🬤 🬥 🬦 🬧 🬨 🬩 🬪 🬫 🬬 🬭 🬮 🬯 🬰 🬱 🬲 🬳 🬴 🬵 🬶 🬷 🬸 🬹 🬺 🬻
|
||
// (Smooth Mosaics)
|
||
// 🬼 🬽 🬾 🬿 🭀 🭁 🭂 🭃 🭄 🭅 🭆
|
||
// 🭇 🭈 🭉 🭊 🭋 🭌 🭍 🭎 🭏 🭐 🭑
|
||
// 🭒 🭓 🭔 🭕 🭖 🭗 🭘 🭙 🭚 🭛 🭜
|
||
// 🭝 🭞 🭟 🭠 🭡 🭢 🭣 🭤 🭥 🭦 🭧
|
||
// 🭨 🭩 🭪 🭫 🭬 🭭 🭮 🭯
|
||
// (Block Elements)
|
||
// 🭰 🭱 🭲 🭳 🭴 🭵 🭶 🭷 🭸 🭹 🭺 🭻
|
||
// 🭼 🭽 🭾 🭿 🮀 🮁
|
||
// 🮂 🮃 🮄 🮅 🮆
|
||
// 🮇 🮈 🮉 🮊 🮋
|
||
// (Rectangular Shade Characters)
|
||
// 🮌 🮍 🮎 🮏 🮐 🮑 🮒
|
||
0x1FB00...0x1FB92,
|
||
// (Rectangular Shade Characters)
|
||
// 🮔
|
||
// (Fill Characters)
|
||
// 🮕 🮖 🮗
|
||
// (Diagonal Fill Characters)
|
||
// 🮘 🮙
|
||
// (Smooth Mosaics)
|
||
// 🮚 🮛
|
||
// (Triangular Shade Characters)
|
||
// 🮜 🮝 🮞 🮟
|
||
// (Character Cell Diagonals)
|
||
// 🮠 🮡 🮢 🮣 🮤 🮥 🮦 🮧 🮨 🮩 🮪 🮫 🮬 🮭 🮮
|
||
// (Light Solid Line With Stroke)
|
||
// 🮯
|
||
0x1FB94...0x1FBAF,
|
||
// (Negative Terminal Characters)
|
||
// 🮽 🮾 🮿
|
||
0x1FBBD...0x1FBBF,
|
||
// (Block Elements)
|
||
//
|
||
// (Character Cell Diagonals)
|
||
//
|
||
// (Geometric Shapes)
|
||
//
|
||
0x1FBCE...0x1FBEF,
|
||
=> _ = try self.renderGlyph(
|
||
alloc,
|
||
atlas,
|
||
cp,
|
||
),
|
||
else => {},
|
||
}
|
||
}
|
||
|
||
// Branch drawing character set, used for drawing git-like
|
||
// graphs in the terminal. Originally implemented in Kitty.
|
||
// Ref:
|
||
// - https://github.com/kovidgoyal/kitty/pull/7681
|
||
// - https://github.com/kovidgoyal/kitty/pull/7805
|
||
// NOTE: Kitty is GPL licensed, and its code was not referenced
|
||
// for these characters, only the loose specification of
|
||
// the character set in the pull request descriptions.
|
||
//
|
||
// TODO(qwerasd): This should be in another file, but really the
|
||
// general organization of the sprite font code
|
||
// needs to be reworked eventually.
|
||
//
|
||
//
|
||
//
|
||
//
|
||
//
|
||
cp = 0xf5d0;
|
||
while (cp <= 0xf60d) : (cp += 1) {
|
||
_ = try self.renderGlyph(
|
||
alloc,
|
||
atlas,
|
||
cp,
|
||
);
|
||
}
|
||
}
|
||
|
||
test "render all sprites" {
|
||
// Renders all sprites to an atlas and compares
|
||
// it to a ground truth for regression testing.
|
||
|
||
const testing = std.testing;
|
||
const alloc = testing.allocator;
|
||
|
||
var atlas_grayscale = try font.Atlas.init(alloc, 1024, .grayscale);
|
||
defer atlas_grayscale.deinit(alloc);
|
||
|
||
// Even cell size and thickness
|
||
try (Box{
|
||
.width = 18,
|
||
.height = 36,
|
||
.thickness = 2,
|
||
}).testRenderAll(alloc, &atlas_grayscale);
|
||
|
||
// Odd cell size and thickness
|
||
try (Box{
|
||
.width = 9,
|
||
.height = 15,
|
||
.thickness = 1,
|
||
}).testRenderAll(alloc, &atlas_grayscale);
|
||
|
||
const ground_truth = @embedFile("./testdata/Box.ppm");
|
||
|
||
var stream = std.io.changeDetectionStream(ground_truth, std.io.null_writer);
|
||
try atlas_grayscale.dump(stream.writer());
|
||
|
||
if (stream.changeDetected()) {
|
||
log.err(
|
||
\\
|
||
\\!! [Box.zig] Change detected from ground truth!
|
||
\\!! Dumping ./Box_test.ppm and ./Box_test_diff.ppm
|
||
\\!! Please check changes and update Box.ppm in testdata if intended.
|
||
,
|
||
.{},
|
||
);
|
||
|
||
const ppm = try std.fs.cwd().createFile("Box_test.ppm", .{});
|
||
defer ppm.close();
|
||
try atlas_grayscale.dump(ppm.writer());
|
||
|
||
const diff = try std.fs.cwd().createFile("Box_test_diff.ppm", .{});
|
||
defer diff.close();
|
||
var writer = diff.writer();
|
||
try writer.print(
|
||
\\P6
|
||
\\{d} {d}
|
||
\\255
|
||
\\
|
||
, .{ atlas_grayscale.size, atlas_grayscale.size });
|
||
for (ground_truth[try diff.getPos()..], atlas_grayscale.data) |a, b| {
|
||
if (a == b) {
|
||
try writer.writeByteNTimes(a / 3, 3);
|
||
} else {
|
||
try writer.writeByte(a);
|
||
try writer.writeByte(b);
|
||
try writer.writeByte(0);
|
||
}
|
||
}
|
||
}
|
||
}
|