diff --git a/.gitmodules b/.gitmodules index eb3e87bf2..7250a6c86 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "vendor/cimgui"] path = vendor/cimgui url = https://github.com/cimgui/cimgui.git +[submodule "vendor/pixman"] + path = vendor/pixman + url = https://github.com/freedesktop/pixman.git diff --git a/build.zig b/build.zig index 24a58d14c..eba845825 100644 --- a/build.zig +++ b/build.zig @@ -12,6 +12,7 @@ const libuv = @import("pkg/libuv/build.zig"); const libpng = @import("pkg/libpng/build.zig"); const macos = @import("pkg/macos/build.zig"); const objc = @import("pkg/objc/build.zig"); +const pixman = @import("pkg/pixman/build.zig"); const stb_image_resize = @import("pkg/stb_image_resize/build.zig"); const utf8proc = @import("pkg/utf8proc/build.zig"); const zlib = @import("pkg/zlib/build.zig"); @@ -211,6 +212,7 @@ fn addDeps( step.addPackage(imgui.pkg); step.addPackage(glfw.pkg); step.addPackage(libuv.pkg); + step.addPackage(pixman.pkg); step.addPackage(stb_image_resize.pkg); step.addPackage(utf8proc.pkg); @@ -263,6 +265,7 @@ fn addDeps( step.linkSystemLibrary("harfbuzz"); step.linkSystemLibrary("libpng"); step.linkSystemLibrary("libuv"); + step.linkSystemLibrary("pixman-1"); step.linkSystemLibrary("zlib"); if (enable_fontconfig) step.linkSystemLibrary("fontconfig"); @@ -307,6 +310,10 @@ fn addDeps( }); system_sdk.include(b, harfbuzz_step, .{}); + // Pixman + const pixman_step = try pixman.link(b, step, .{}); + _ = pixman_step; + // Libuv const libuv_step = try libuv.link(b, step); system_sdk.include(b, libuv_step, .{}); diff --git a/conformance/blocks.zig b/conformance/blocks.zig new file mode 100644 index 000000000..6ba5e0663 --- /dev/null +++ b/conformance/blocks.zig @@ -0,0 +1,99 @@ +//! Outputs various box glyphs for testing. +const std = @import("std"); + +pub fn main() !void { + const stdout = std.io.getStdOut().writer(); + + // Box Drawing + { + try stdout.print("\x1b[4mBox Drawing\x1b[0m\n", .{}); + var i: usize = 0x2500; + var step: usize = 32; + while (i <= 0x257F) : (i += step) { + var j: usize = 0; + while (j < step) : (j += 1) { + try stdout.print("{u} ", .{@intCast(u21, i + j)}); + } + + try stdout.print("\n\n", .{}); + } + } + + // Block Elements + { + try stdout.print("\x1b[4mBlock Elements\x1b[0m\n", .{}); + var i: usize = 0x2580; + var step: usize = 32; + while (i <= 0x259f) : (i += step) { + var j: usize = 0; + while (j < step) : (j += 1) { + try stdout.print("{u} ", .{@intCast(u21, i + j)}); + } + + try stdout.print("\n\n", .{}); + } + } + + // Braille Elements + { + try stdout.print("\x1b[4mBraille\x1b[0m\n", .{}); + var i: usize = 0x2800; + var step: usize = 32; + while (i <= 0x28FF) : (i += step) { + var j: usize = 0; + while (j < step) : (j += 1) { + try stdout.print("{u} ", .{@intCast(u21, i + j)}); + } + + try stdout.print("\n\n", .{}); + } + } + + { + try stdout.print("\x1b[4mSextants\x1b[0m\n", .{}); + var i: usize = 0x1FB00; + var step: usize = 32; + const end = 0x1FB3B; + while (i <= end) : (i += step) { + var j: usize = 0; + while (j < step) : (j += 1) { + const v = i + j; + if (v <= end) try stdout.print("{u} ", .{@intCast(u21, v)}); + } + + try stdout.print("\n\n", .{}); + } + } + + { + try stdout.print("\x1b[4mWedge Triangles\x1b[0m\n", .{}); + var i: usize = 0x1FB3C; + var step: usize = 32; + const end = 0x1FB6B; + while (i <= end) : (i += step) { + var j: usize = 0; + while (j < step) : (j += 1) { + const v = i + j; + if (v <= end) try stdout.print("{u} ", .{@intCast(u21, v)}); + } + + try stdout.print("\n\n", .{}); + } + } + + { + try stdout.print("\x1b[4mOther\x1b[0m\n", .{}); + var i: usize = 0x1FB70; + var step: usize = 32; + const end = 0x1FB8B; + while (i <= end) : (i += step) { + var j: usize = 0; + while (j < step) : (j += 1) { + const v = i + j; + if (v <= end) try stdout.print("{u} ", .{@intCast(u21, v)}); + } + + try stdout.print("\n\n", .{}); + } + } +} diff --git a/nix/devshell.nix b/nix/devshell.nix index ea35aa601..3fe3ff966 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -31,6 +31,7 @@ , libXi , libXinerama , libXrandr +, pixman , zlib }: let @@ -87,6 +88,7 @@ in mkShell rec { harfbuzz libpng libuv + pixman zlib libX11 diff --git a/pkg/pixman/build.zig b/pkg/pixman/build.zig new file mode 100644 index 000000000..6f5d35648 --- /dev/null +++ b/pkg/pixman/build.zig @@ -0,0 +1,141 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +/// Directories with our includes. +const root = thisDir() ++ "../../../vendor/pixman/"; +const include_path = root ++ "pixman/"; +const include_path_self = thisDir(); + +pub const include_paths = .{ include_path, include_path_self }; + +pub const pkg = std.build.Pkg{ + .name = "pixman", + .source = .{ .path = thisDir() ++ "/main.zig" }, +}; + +fn thisDir() []const u8 { + return std.fs.path.dirname(@src().file) orelse "."; +} + +pub const Options = struct {}; + +pub fn build(b: *std.build.Builder) !void { + const target = b.standardTargetOptions(.{}); + const mode = b.standardReleaseOptions(); + + const tests = b.addTestExe("pixman-test", "main.zig"); + tests.setBuildMode(mode); + tests.setTarget(target); + _ = try link(b, tests, .{}); + tests.install(); + + const test_step = b.step("test", "Run tests"); + const tests_run = tests.run(); + test_step.dependOn(&tests_run.step); +} + +pub fn link( + b: *std.build.Builder, + step: *std.build.LibExeObjStep, + opt: Options, +) !*std.build.LibExeObjStep { + const lib = try buildPixman(b, step, opt); + step.linkLibrary(lib); + step.addIncludePath(include_path); + step.addIncludePath(include_path_self); + return lib; +} + +pub fn buildPixman( + b: *std.build.Builder, + step: *std.build.LibExeObjStep, + opt: Options, +) !*std.build.LibExeObjStep { + _ = opt; + + const target = step.target; + const lib = b.addStaticLibrary("pixman", null); + lib.setTarget(step.target); + lib.setBuildMode(step.build_mode); + + // Include + lib.addIncludePath(include_path); + lib.addIncludePath(include_path_self); + + // Link + lib.linkLibC(); + if (!target.isWindows()) { + lib.linkSystemLibrary("pthread"); + } + + // Compile + var flags = std.ArrayList([]const u8).init(b.allocator); + defer flags.deinit(); + + try flags.appendSlice(&.{ + "-DHAVE_SIGACTION=1", + "-DHAVE_ALARM=1", + "-DHAVE_MPROTECT=1", + "-DHAVE_GETPAGESIZE=1", + "-DHAVE_MMAP=1", + "-DHAVE_GETISAX=1", + "-DHAVE_GETTIMEOFDAY=1", + + "-DHAVE_FENV_H=1", + "-DHAVE_SYS_MMAN_H=1", + "-DHAVE_UNISTD_H=1", + + "-DSIZEOF_LONG=8", + "-DPACKAGE=foo", + + // There is ubsan + "-fno-sanitize=undefined", + "-fno-sanitize-trap=undefined", + }); + + if (!target.isWindows()) { + try flags.appendSlice(&.{ + "-DHAVE_PTHREADS=1", + + "-DHAVE_POSIX_MEMALIGN=1", + }); + } + + // C files + lib.addCSourceFiles(srcs, flags.items); + + return lib; +} + +const srcs = &.{ + root ++ "pixman/pixman.c", + root ++ "pixman/pixman-access.c", + root ++ "pixman/pixman-access-accessors.c", + root ++ "pixman/pixman-bits-image.c", + root ++ "pixman/pixman-combine32.c", + root ++ "pixman/pixman-combine-float.c", + root ++ "pixman/pixman-conical-gradient.c", + root ++ "pixman/pixman-filter.c", + root ++ "pixman/pixman-x86.c", + root ++ "pixman/pixman-mips.c", + root ++ "pixman/pixman-arm.c", + root ++ "pixman/pixman-ppc.c", + root ++ "pixman/pixman-edge.c", + root ++ "pixman/pixman-edge-accessors.c", + root ++ "pixman/pixman-fast-path.c", + root ++ "pixman/pixman-glyph.c", + root ++ "pixman/pixman-general.c", + root ++ "pixman/pixman-gradient-walker.c", + root ++ "pixman/pixman-image.c", + root ++ "pixman/pixman-implementation.c", + root ++ "pixman/pixman-linear-gradient.c", + root ++ "pixman/pixman-matrix.c", + root ++ "pixman/pixman-noop.c", + root ++ "pixman/pixman-radial-gradient.c", + root ++ "pixman/pixman-region16.c", + root ++ "pixman/pixman-region32.c", + root ++ "pixman/pixman-solid-fill.c", + root ++ "pixman/pixman-timer.c", + root ++ "pixman/pixman-trap.c", + root ++ "pixman/pixman-utils.c", +}; diff --git a/pkg/pixman/c.zig b/pkg/pixman/c.zig new file mode 100644 index 000000000..04972bc40 --- /dev/null +++ b/pkg/pixman/c.zig @@ -0,0 +1,3 @@ +pub usingnamespace @cImport({ + @cInclude("pixman.h"); +}); diff --git a/pkg/pixman/error.zig b/pkg/pixman/error.zig new file mode 100644 index 000000000..2fa569aed --- /dev/null +++ b/pkg/pixman/error.zig @@ -0,0 +1,4 @@ +pub const Error = error{ + // Pixman doesn't really have errors so we just have a single error. + PixmanFailure, +}; diff --git a/pkg/pixman/format.zig b/pkg/pixman/format.zig new file mode 100644 index 000000000..39dc704f3 --- /dev/null +++ b/pkg/pixman/format.zig @@ -0,0 +1,118 @@ +const std = @import("std"); +const c = @import("c.zig"); +const pixman = @import("main.zig"); + +pub const FormatCode = enum(c_uint) { + // 128bpp formats + rgba_float = c.PIXMAN_FORMAT_BYTE(128, c.PIXMAN_TYPE_RGBA_FLOAT, 32, 32, 32, 32), + + // 96bpp formats + rgb_float = c.PIXMAN_FORMAT_BYTE(96, c.PIXMAN_TYPE_RGBA_FLOAT, 0, 32, 32, 32), + + // 32bpp formats + a8r8g8b8 = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_ARGB, 8, 8, 8, 8), + x8r8g8b8 = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_ARGB, 0, 8, 8, 8), + a8b8g8r8 = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_ABGR, 8, 8, 8, 8), + x8b8g8r8 = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_ABGR, 0, 8, 8, 8), + b8g8r8a8 = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_BGRA, 8, 8, 8, 8), + b8g8r8x8 = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_BGRA, 0, 8, 8, 8), + r8g8b8a8 = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_RGBA, 8, 8, 8, 8), + r8g8b8x8 = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_RGBA, 0, 8, 8, 8), + x14r6g6b6 = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_ARGB, 0, 6, 6, 6), + x2r10g10b10 = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_ARGB, 0, 10, 10, 10), + a2r10g10b10 = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_ARGB, 2, 10, 10, 10), + x2b10g10r10 = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_ABGR, 0, 10, 10, 10), + a2b10g10r10 = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_ABGR, 2, 10, 10, 10), + + // sRGB formats + a8r8g8b8_sRGB = c.PIXMAN_FORMAT(32, c.PIXMAN_TYPE_ARGB_SRGB, 8, 8, 8, 8), + r8g8b8_sRGB = c.PIXMAN_FORMAT(24, c.PIXMAN_TYPE_ARGB_SRGB, 0, 8, 8, 8), + + // 24bpp formats + r8g8b8 = c.PIXMAN_FORMAT(24, c.PIXMAN_TYPE_ARGB, 0, 8, 8, 8), + b8g8r8 = c.PIXMAN_FORMAT(24, c.PIXMAN_TYPE_ABGR, 0, 8, 8, 8), + + // 16bpp formats + r5g6b5 = c.PIXMAN_FORMAT(16, c.PIXMAN_TYPE_ARGB, 0, 5, 6, 5), + b5g6r5 = c.PIXMAN_FORMAT(16, c.PIXMAN_TYPE_ABGR, 0, 5, 6, 5), + + a1r5g5b5 = c.PIXMAN_FORMAT(16, c.PIXMAN_TYPE_ARGB, 1, 5, 5, 5), + x1r5g5b5 = c.PIXMAN_FORMAT(16, c.PIXMAN_TYPE_ARGB, 0, 5, 5, 5), + a1b5g5r5 = c.PIXMAN_FORMAT(16, c.PIXMAN_TYPE_ABGR, 1, 5, 5, 5), + x1b5g5r5 = c.PIXMAN_FORMAT(16, c.PIXMAN_TYPE_ABGR, 0, 5, 5, 5), + a4r4g4b4 = c.PIXMAN_FORMAT(16, c.PIXMAN_TYPE_ARGB, 4, 4, 4, 4), + x4r4g4b4 = c.PIXMAN_FORMAT(16, c.PIXMAN_TYPE_ARGB, 0, 4, 4, 4), + a4b4g4r4 = c.PIXMAN_FORMAT(16, c.PIXMAN_TYPE_ABGR, 4, 4, 4, 4), + x4b4g4r4 = c.PIXMAN_FORMAT(16, c.PIXMAN_TYPE_ABGR, 0, 4, 4, 4), + + // 8bpp formats + a8 = c.PIXMAN_FORMAT(8, c.PIXMAN_TYPE_A, 8, 0, 0, 0), + r3g3b2 = c.PIXMAN_FORMAT(8, c.PIXMAN_TYPE_ARGB, 0, 3, 3, 2), + b2g3r3 = c.PIXMAN_FORMAT(8, c.PIXMAN_TYPE_ABGR, 0, 3, 3, 2), + a2r2g2b2 = c.PIXMAN_FORMAT(8, c.PIXMAN_TYPE_ARGB, 2, 2, 2, 2), + a2b2g2r2 = c.PIXMAN_FORMAT(8, c.PIXMAN_TYPE_ABGR, 2, 2, 2, 2), + + c8 = c.PIXMAN_FORMAT(8, c.PIXMAN_TYPE_COLOR, 0, 0, 0, 0), + g8 = c.PIXMAN_FORMAT(8, c.PIXMAN_TYPE_GRAY, 0, 0, 0, 0), + + x4a4 = c.PIXMAN_FORMAT(8, c.PIXMAN_TYPE_A, 4, 0, 0, 0), + + // c8/g8 equivalent + // x4c4 = c.PIXMAN_FORMAT(8, c.PIXMAN_TYPE_COLOR, 0, 0, 0, 0), + // x4g4 = c.PIXMAN_FORMAT(8, c.PIXMAN_TYPE_GRAY, 0, 0, 0, 0), + + // 4bpp formats + a4 = c.PIXMAN_FORMAT(4, c.PIXMAN_TYPE_A, 4, 0, 0, 0), + r1g2b1 = c.PIXMAN_FORMAT(4, c.PIXMAN_TYPE_ARGB, 0, 1, 2, 1), + b1g2r1 = c.PIXMAN_FORMAT(4, c.PIXMAN_TYPE_ABGR, 0, 1, 2, 1), + a1r1g1b1 = c.PIXMAN_FORMAT(4, c.PIXMAN_TYPE_ARGB, 1, 1, 1, 1), + a1b1g1r1 = c.PIXMAN_FORMAT(4, c.PIXMAN_TYPE_ABGR, 1, 1, 1, 1), + + c4 = c.PIXMAN_FORMAT(4, c.PIXMAN_TYPE_COLOR, 0, 0, 0, 0), + g4 = c.PIXMAN_FORMAT(4, c.PIXMAN_TYPE_GRAY, 0, 0, 0, 0), + + // 1bpp formats + a1 = c.PIXMAN_FORMAT(1, c.PIXMAN_TYPE_A, 1, 0, 0, 0), + + g1 = c.PIXMAN_FORMAT(1, c.PIXMAN_TYPE_GRAY, 0, 0, 0, 0), + + // YUV formats + yuy2 = c.PIXMAN_FORMAT(16, c.PIXMAN_TYPE_YUY2, 0, 0, 0, 0), + yv12 = c.PIXMAN_FORMAT(12, c.PIXMAN_TYPE_YV12, 0, 0, 0, 0), + + pub inline fn bpp(self: FormatCode) u32 { + return self.reshift(24, 8); + } + + /// Calculates a valid stride for the bpp and width. Based on Cairo. + pub fn strideForWidth(self: FormatCode, width: u32) c_int { + const alignment = @sizeOf(u32); + const val = @intCast(c_int, (self.bpp() * width + 7) / 8); + return val + (alignment - 1) & -alignment; + } + + // Converted from pixman.h + fn reshift(self: FormatCode, ofs: u5, num: u5) u32 { + const val = @enumToInt(self); + const v1 = val >> ofs; + const v2 = @as(c_uint, 1) << num; + const v3 = @intCast(u5, (val >> 22) & 3); + return ((v1 & (v2 - 1)) << v3); + } +}; + +test "bpp" { + const testing = std.testing; + + try testing.expectEqual(@as(u32, 1), FormatCode.g1.bpp()); + try testing.expectEqual(@as(u32, 4), FormatCode.g4.bpp()); + try testing.expectEqual(@as(u32, 8), FormatCode.g8.bpp()); +} + +test "stride" { + const testing = std.testing; + + try testing.expectEqual(@as(c_int, 4), FormatCode.g1.strideForWidth(10)); + try testing.expectEqual(@as(c_int, 8), FormatCode.g4.strideForWidth(10)); + try testing.expectEqual(@as(c_int, 12), FormatCode.g8.strideForWidth(10)); +} diff --git a/pkg/pixman/image.zig b/pkg/pixman/image.zig new file mode 100644 index 000000000..caccc65a9 --- /dev/null +++ b/pkg/pixman/image.zig @@ -0,0 +1,207 @@ +const std = @import("std"); +const c = @import("c.zig"); +const pixman = @import("main.zig"); + +pub const Image = opaque { + pub fn createBitsNoClear( + format: pixman.FormatCode, + width: c_int, + height: c_int, + bits: [*]u32, + stride: c_int, + ) pixman.Error!*Image { + return @ptrCast(?*Image, c.pixman_image_create_bits_no_clear( + @enumToInt(format), + width, + height, + bits, + stride, + )) orelse return pixman.Error.PixmanFailure; + } + + pub fn createSolidFill( + color: pixman.Color, + ) pixman.Error!*Image { + return @ptrCast(?*Image, c.pixman_image_create_solid_fill( + @ptrCast(*const c.pixman_color_t, &color), + )) orelse return pixman.Error.PixmanFailure; + } + + pub fn unref(self: *Image) bool { + return c.pixman_image_unref(@ptrCast(*c.pixman_image_t, self)) == 1; + } + + /// A variant of getDataUnsafe that sets the length of the slice to + /// height * stride. Its possible the buffer is larger but this is the + /// known safe values. If you KNOW the buffer is larger you can use the + /// unsafe variant. + pub fn getData(self: *Image) []u32 { + const height = self.getHeight(); + const stride = self.getStride(); + const ptr = self.getDataUnsafe(); + const len = @intCast(usize, height * stride); + return ptr[0..len]; + } + + pub fn getDataUnsafe(self: *Image) [*]u32 { + return c.pixman_image_get_data(@ptrCast(*c.pixman_image_t, self)); + } + + pub fn getHeight(self: *Image) c_int { + return c.pixman_image_get_height(@ptrCast(*c.pixman_image_t, self)); + } + + pub fn getStride(self: *Image) c_int { + return c.pixman_image_get_stride(@ptrCast(*c.pixman_image_t, self)); + } + + pub fn fillBoxes( + self: *Image, + op: pixman.Op, + color: pixman.Color, + boxes: []const pixman.Box32, + ) pixman.Error!void { + if (c.pixman_image_fill_boxes( + @enumToInt(op), + @ptrCast(*c.pixman_image_t, self), + @ptrCast(*const c.pixman_color_t, &color), + @intCast(c_int, boxes.len), + @ptrCast([*c]const c.pixman_box32_t, boxes.ptr), + ) == 0) return pixman.Error.PixmanFailure; + } + + pub fn fillRectangles( + self: *Image, + op: pixman.Op, + color: pixman.Color, + rects: []const pixman.Rectangle16, + ) pixman.Error!void { + if (c.pixman_image_fill_rectangles( + @enumToInt(op), + @ptrCast(*c.pixman_image_t, self), + @ptrCast(*const c.pixman_color_t, &color), + @intCast(c_int, rects.len), + @ptrCast([*c]const c.pixman_rectangle16_t, rects.ptr), + ) == 0) return pixman.Error.PixmanFailure; + } + + pub fn rasterizeTrapezoid( + self: *Image, + trap: pixman.Trapezoid, + x_off: c_int, + y_off: c_int, + ) void { + c.pixman_rasterize_trapezoid( + @ptrCast(*c.pixman_image_t, self), + @ptrCast(*const c.pixman_trapezoid_t, &trap), + x_off, + y_off, + ); + } + + pub fn composite( + self: *Image, + op: pixman.Op, + src: *Image, + mask: ?*Image, + src_x: i16, + src_y: i16, + mask_x: i16, + mask_y: i16, + dest_x: i16, + dest_y: i16, + width: u16, + height: u16, + ) void { + c.pixman_image_composite( + @enumToInt(op), + @ptrCast(*c.pixman_image_t, src), + @ptrCast(?*c.pixman_image_t, mask), + @ptrCast(*c.pixman_image_t, self), + src_x, + src_y, + mask_x, + mask_y, + dest_x, + dest_y, + width, + height, + ); + } + + pub fn compositeTriangles( + self: *Image, + op: pixman.Op, + src: *Image, + mask_format: pixman.FormatCode, + x_src: c_int, + y_src: c_int, + x_dst: c_int, + y_dst: c_int, + tris: []const pixman.Triangle, + ) void { + c.pixman_composite_triangles( + @enumToInt(op), + @ptrCast(*c.pixman_image_t, src), + @ptrCast(*c.pixman_image_t, self), + @enumToInt(mask_format), + x_src, + y_src, + x_dst, + y_dst, + @intCast(c_int, tris.len), + @ptrCast([*c]const c.pixman_triangle_t, tris.ptr), + ); + } +}; + +test "create and destroy" { + const testing = std.testing; + const alloc = testing.allocator; + + const width = 10; + const height = 10; + const format: pixman.FormatCode = .g1; + const stride = format.strideForWidth(width); + + const len = height * @intCast(usize, stride); + var data = try alloc.alloc(u32, len); + defer alloc.free(data); + std.mem.set(u32, data, 0); + const img = try Image.createBitsNoClear(.g1, width, height, data.ptr, stride); + try testing.expectEqual(@as(c_int, height), img.getHeight()); + try testing.expectEqual(@as(c_int, stride), img.getStride()); + try testing.expect(img.getData().len == height * stride); + try testing.expect(img.unref()); +} + +test "fill boxes a1" { + const testing = std.testing; + const alloc = testing.allocator; + + // Dimensions + const width = 100; + const height = 100; + const format: pixman.FormatCode = .a1; + const stride = format.strideForWidth(width); + + // Image + const len = height * @intCast(usize, stride); + var data = try alloc.alloc(u32, len); + defer alloc.free(data); + std.mem.set(u32, data, 0); + const img = try Image.createBitsNoClear(format, width, height, data.ptr, stride); + defer _ = img.unref(); + + // Fill + const color: pixman.Color = .{ .red = 0xFFFF, .green = 0xFFFF, .blue = 0xFFFF, .alpha = 0xFFFF }; + const boxes = &[_]pixman.Box32{ + .{ + .x1 = 0, + .y1 = 0, + .x2 = width, + .y2 = height, + }, + }; + try img.fillBoxes(.src, color, boxes); +} diff --git a/pkg/pixman/main.zig b/pkg/pixman/main.zig new file mode 100644 index 000000000..864736b2c --- /dev/null +++ b/pkg/pixman/main.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +pub const c = @import("c.zig"); +pub usingnamespace @import("error.zig"); +pub usingnamespace @import("format.zig"); +pub usingnamespace @import("image.zig"); +pub usingnamespace @import("types.zig"); + +test { + std.testing.refAllDecls(@This()); +} diff --git a/pkg/pixman/pixman-version.h b/pkg/pixman/pixman-version.h new file mode 100644 index 000000000..c2342d3d5 --- /dev/null +++ b/pkg/pixman/pixman-version.h @@ -0,0 +1,54 @@ +/* + * Copyright © 2008 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Author: Carl D. Worth + */ + +#ifndef PIXMAN_VERSION_H__ +#define PIXMAN_VERSION_H__ + +#ifndef PIXMAN_H__ +# error pixman-version.h should only be included by pixman.h +#endif + +#define PIXMAN_VERSION_MAJOR 999 +#define PIXMAN_VERSION_MINOR 999 +#define PIXMAN_VERSION_MICRO 999 + +#define PIXMAN_VERSION_STRING "999.999.999" + +#define PIXMAN_VERSION_ENCODE(major, minor, micro) ( \ + ((major) * 10000) \ + + ((minor) * 100) \ + + ((micro) * 1)) + +#define PIXMAN_VERSION PIXMAN_VERSION_ENCODE( \ + PIXMAN_VERSION_MAJOR, \ + PIXMAN_VERSION_MINOR, \ + PIXMAN_VERSION_MICRO) + +#ifndef PIXMAN_API +# define PIXMAN_API +#endif + +#endif /* PIXMAN_VERSION_H__ */ diff --git a/pkg/pixman/types.zig b/pkg/pixman/types.zig new file mode 100644 index 000000000..f1ab9235d --- /dev/null +++ b/pkg/pixman/types.zig @@ -0,0 +1,131 @@ +const std = @import("std"); +const c = @import("c.zig"); +const pixman = @import("main.zig"); + +pub const Op = enum(c_uint) { + clear = 0x00, + src = 0x01, + dst = 0x02, + over = 0x03, + over_reverse = 0x04, + in = 0x05, + in_reverse = 0x06, + out = 0x07, + out_reverse = 0x08, + atop = 0x09, + atop_reverse = 0x0a, + xor = 0x0b, + add = 0x0c, + saturate = 0x0d, + + disjoint_clear = 0x10, + disjoint_src = 0x11, + disjoint_dst = 0x12, + disjoint_over = 0x13, + disjoint_over_reverse = 0x14, + disjoint_in = 0x15, + disjoint_in_reverse = 0x16, + disjoint_out = 0x17, + disjoint_out_reverse = 0x18, + disjoint_atop = 0x19, + disjoint_atop_reverse = 0x1a, + disjoint_xor = 0x1b, + + conjoint_clear = 0x20, + conjoint_src = 0x21, + conjoint_dst = 0x22, + conjoint_over = 0x23, + conjoint_over_reverse = 0x24, + conjoint_in = 0x25, + conjoint_in_reverse = 0x26, + conjoint_out = 0x27, + conjoint_out_reverse = 0x28, + conjoint_atop = 0x29, + conjoint_atop_reverse = 0x2a, + conjoint_xor = 0x2b, + + multiply = 0x30, + screen = 0x31, + overlay = 0x32, + darken = 0x33, + lighten = 0x34, + color_dodge = 0x35, + color_burn = 0x36, + hard_light = 0x37, + soft_light = 0x38, + difference = 0x39, + exclusion = 0x3a, + hsl_hue = 0x3b, + hsl_saturation = 0x3c, + hsl_color = 0x3d, + hsl_luminosity = 0x3e, +}; + +pub const Color = extern struct { + red: u16, + green: u16, + blue: u16, + alpha: u16, +}; + +pub const Fixed = enum(i32) { + _, + + pub fn init(v: anytype) Fixed { + return switch (@TypeOf(v)) { + comptime_int, u32 => @intToEnum(Fixed, v << 16), + f64 => @intToEnum(Fixed, @floatToInt(i32, v * 65536)), + else => { + @compileLog(@TypeOf(v)); + @compileError("unsupported type"); + }, + }; + } +}; + +pub const PointFixed = extern struct { + x: Fixed, + y: Fixed, +}; + +pub const LineFixed = extern struct { + p1: PointFixed, + p2: PointFixed, +}; + +pub const Triangle = extern struct { + p1: PointFixed, + p2: PointFixed, + p3: PointFixed, +}; + +pub const Trapezoid = extern struct { + top: Fixed, + bottom: Fixed, + left: LineFixed, + right: LineFixed, +}; + +pub const Rectangle16 = extern struct { + x: i16, + y: i16, + width: u16, + height: u16, +}; + +pub const Box32 = extern struct { + x1: i32, + y1: i32, + x2: i32, + y2: i32, +}; + +pub const Indexed = extern struct { + color: bool, + rgba: [256]u32, + ent: [32768]u8, +}; + +test { + std.testing.refAllDecls(@This()); +} diff --git a/src/Window.zig b/src/Window.zig index ebe3b67c7..1373e3b58 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -289,6 +289,13 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window { // Pre-calculate our initial cell size ourselves. const cell_size = try renderer.CellSize.init(alloc, font_group); + // Setup our box font + font_group.group.box_font = font.BoxFont{ + .width = @floatToInt(u32, cell_size.width), + .height = @floatToInt(u32, cell_size.height), + .thickness = 2, + }; + // Convert our padding from points to pixels const padding_x = (@intToFloat(f32, config.@"window-padding-x") * x_dpi) / 72; const padding_y = (@intToFloat(f32, config.@"window-padding-y") * y_dpi) / 72; diff --git a/src/font/BoxFont.zig b/src/font/BoxFont.zig new file mode 100644 index 000000000..c0cd7df91 --- /dev/null +++ b/src/font/BoxFont.zig @@ -0,0 +1,2770 @@ +//! 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 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 are primarily around spacing, DPI calculations, +//! and adapting the code to our atlas model. +const BoxFont = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; + +const pixman = @import("pixman"); +const font = @import("main.zig"); +const Atlas = @import("../Atlas.zig"); + +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, + +/// We use alpha-channel-only images for the box font so white causes +/// a pixel to be shown. +const white: pixman.Color = .{ + .red = 0xFFFF, + .green = 0xFFFF, + .blue = 0xFFFF, + .alpha = 0xFFFF, +}; + +/// The thickness of a line. +const Thickness = enum { + 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) { + .light => base, + .heavy => base * 2, + }; + } +}; + +pub fn renderGlyph( + self: BoxFont, + alloc: Allocator, + atlas: *Atlas, + cp: u32, +) !font.Glyph { + assert(atlas.format == .greyscale); + + // Determine the config for our image buffer. The images we draw + // for boxes are always 8bpp + const format: pixman.FormatCode = .a8; + const stride = format.strideForWidth(self.width); + const len = @intCast(usize, stride * @intCast(c_int, self.height)); + + // Allocate our buffer. pixman uses []u32 so we divide our length + // by 4 since u32 / u8 = 4. + var data = try alloc.alloc(u32, len / 4); + defer alloc.free(data); + std.mem.set(u32, data, 0); + + // Create the image we'll draw to + const img = try pixman.Image.createBitsNoClear( + format, + @intCast(c_int, self.width), + @intCast(c_int, self.height), + data.ptr, + stride, + ); + defer _ = img.unref(); + + try self.draw(alloc, img, cp); + + // Reserve our region in the atlas and render the glyph to it. + const region = try atlas.reserve(alloc, self.width, self.height); + if (region.width > 0 and region.height > 0) { + // Convert our []u32 to []u8 since we use 8bpp formats + assert(format.bpp() == 8); + const data_u8 = @alignCast(@alignOf(u8), @ptrCast([*]u8, data.ptr)[0..len]); + + const depth = atlas.format.depth(); + + // We can avoid a buffer copy if our atlas width and bitmap + // width match and the bitmap pitch is just the width (meaning + // the data is tightly packed). + const needs_copy = !(self.width * depth == stride); + + // If we need to copy the data, we copy it into a temporary buffer. + const buffer = if (needs_copy) buffer: { + var temp = try alloc.alloc(u8, self.width * self.height * depth); + var dst_ptr = temp; + var src_ptr = data_u8.ptr; + var i: usize = 0; + while (i < self.height) : (i += 1) { + std.mem.copy(u8, dst_ptr, src_ptr[0 .. self.width * depth]); + dst_ptr = dst_ptr[self.width * depth ..]; + src_ptr += @intCast(usize, stride); + } + break :buffer temp; + } else data_u8[0..(self.width * self.height * depth)]; + defer if (buffer.ptr != data_u8.ptr) alloc.free(buffer); + + // Write the glyph information into the atlas + assert(region.width == self.width); + assert(region.height == self.height); + atlas.set(region, buffer); + } + + // 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 = @intCast(i32, 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 = @intToFloat(f32, self.width), + }; +} + +fn draw(self: BoxFont, alloc: Allocator, img: *pixman.Image, cp: u32) !void { + switch (cp) { + 0x2500 => self.draw_light_horizontal(img), + 0x2501 => self.draw_heavy_horizontal(img), + 0x2502 => self.draw_light_vertical(img), + 0x2503 => self.draw_heavy_vertical(img), + 0x2504 => self.draw_light_triple_dash_horizontal(img), + 0x2505 => self.draw_heavy_triple_dash_horizontal(img), + 0x2506 => self.draw_light_triple_dash_vertical(img), + 0x2507 => self.draw_heavy_triple_dash_vertical(img), + 0x2508 => self.draw_light_quadruple_dash_horizontal(img), + 0x2509 => self.draw_heavy_quadruple_dash_horizontal(img), + 0x250a => self.draw_light_quadruple_dash_vertical(img), + 0x250b => self.draw_heavy_quadruple_dash_vertical(img), + 0x250c => self.draw_light_down_and_right(img), + 0x250d => self.draw_down_light_and_right_heavy(img), + 0x250e => self.draw_down_heavy_and_right_light(img), + 0x250f => self.draw_heavy_down_and_right(img), + + 0x2510 => self.draw_light_down_and_left(img), + 0x2511 => self.draw_down_light_and_left_heavy(img), + 0x2512 => self.draw_down_heavy_and_left_light(img), + 0x2513 => self.draw_heavy_down_and_left(img), + 0x2514 => self.draw_light_up_and_right(img), + 0x2515 => self.draw_up_light_and_right_heavy(img), + 0x2516 => self.draw_up_heavy_and_right_light(img), + 0x2517 => self.draw_heavy_up_and_right(img), + 0x2518 => self.draw_light_up_and_left(img), + 0x2519 => self.draw_up_light_and_left_heavy(img), + 0x251a => self.draw_up_heavy_and_left_light(img), + 0x251b => self.draw_heavy_up_and_left(img), + 0x251c => self.draw_light_vertical_and_right(img), + 0x251d => self.draw_vertical_light_and_right_heavy(img), + 0x251e => self.draw_up_heavy_and_right_down_light(img), + 0x251f => self.draw_down_heavy_and_right_up_light(img), + + 0x2520 => self.draw_vertical_heavy_and_right_light(img), + 0x2521 => self.draw_down_light_and_right_up_heavy(img), + 0x2522 => self.draw_up_light_and_right_down_heavy(img), + 0x2523 => self.draw_heavy_vertical_and_right(img), + 0x2524 => self.draw_light_vertical_and_left(img), + 0x2525 => self.draw_vertical_light_and_left_heavy(img), + 0x2526 => self.draw_up_heavy_and_left_down_light(img), + 0x2527 => self.draw_down_heavy_and_left_up_light(img), + 0x2528 => self.draw_vertical_heavy_and_left_light(img), + 0x2529 => self.draw_down_light_and_left_up_heavy(img), + 0x252a => self.draw_up_light_and_left_down_heavy(img), + 0x252b => self.draw_heavy_vertical_and_left(img), + 0x252c => self.draw_light_down_and_horizontal(img), + 0x252d => self.draw_left_heavy_and_right_down_light(img), + 0x252e => self.draw_right_heavy_and_left_down_light(img), + 0x252f => self.draw_down_light_and_horizontal_heavy(img), + + 0x2530 => self.draw_down_heavy_and_horizontal_light(img), + 0x2531 => self.draw_right_light_and_left_down_heavy(img), + 0x2532 => self.draw_left_light_and_right_down_heavy(img), + 0x2533 => self.draw_heavy_down_and_horizontal(img), + 0x2534 => self.draw_light_up_and_horizontal(img), + 0x2535 => self.draw_left_heavy_and_right_up_light(img), + 0x2536 => self.draw_right_heavy_and_left_up_light(img), + 0x2537 => self.draw_up_light_and_horizontal_heavy(img), + 0x2538 => self.draw_up_heavy_and_horizontal_light(img), + 0x2539 => self.draw_right_light_and_left_up_heavy(img), + 0x253a => self.draw_left_light_and_right_up_heavy(img), + 0x253b => self.draw_heavy_up_and_horizontal(img), + 0x253c => self.draw_light_vertical_and_horizontal(img), + 0x253d => self.draw_left_heavy_and_right_vertical_light(img), + 0x253e => self.draw_right_heavy_and_left_vertical_light(img), + 0x253f => self.draw_vertical_light_and_horizontal_heavy(img), + + 0x2540 => self.draw_up_heavy_and_down_horizontal_light(img), + 0x2541 => self.draw_down_heavy_and_up_horizontal_light(img), + 0x2542 => self.draw_vertical_heavy_and_horizontal_light(img), + 0x2543 => self.draw_left_up_heavy_and_right_down_light(img), + 0x2544 => self.draw_right_up_heavy_and_left_down_light(img), + 0x2545 => self.draw_left_down_heavy_and_right_up_light(img), + 0x2546 => self.draw_right_down_heavy_and_left_up_light(img), + 0x2547 => self.draw_down_light_and_up_horizontal_heavy(img), + 0x2548 => self.draw_up_light_and_down_horizontal_heavy(img), + 0x2549 => self.draw_right_light_and_left_vertical_heavy(img), + 0x254a => self.draw_left_light_and_right_vertical_heavy(img), + 0x254b => self.draw_heavy_vertical_and_horizontal(img), + 0x254c => self.draw_light_double_dash_horizontal(img), + 0x254d => self.draw_heavy_double_dash_horizontal(img), + 0x254e => self.draw_light_double_dash_vertical(img), + 0x254f => self.draw_heavy_double_dash_vertical(img), + + 0x2550 => self.draw_double_horizontal(img), + 0x2551 => self.draw_double_vertical(img), + 0x2552 => self.draw_down_single_and_right_double(img), + 0x2553 => self.draw_down_double_and_right_single(img), + 0x2554 => self.draw_double_down_and_right(img), + 0x2555 => self.draw_down_single_and_left_double(img), + 0x2556 => self.draw_down_double_and_left_single(img), + 0x2557 => self.draw_double_down_and_left(img), + 0x2558 => self.draw_up_single_and_right_double(img), + 0x2559 => self.draw_up_double_and_right_single(img), + 0x255a => self.draw_double_up_and_right(img), + 0x255b => self.draw_up_single_and_left_double(img), + 0x255c => self.draw_up_double_and_left_single(img), + 0x255d => self.draw_double_up_and_left(img), + 0x255e => self.draw_vertical_single_and_right_double(img), + 0x255f => self.draw_vertical_double_and_right_single(img), + + 0x2560 => self.draw_double_vertical_and_right(img), + 0x2561 => self.draw_vertical_single_and_left_double(img), + 0x2562 => self.draw_vertical_double_and_left_single(img), + 0x2563 => self.draw_double_vertical_and_left(img), + 0x2564 => self.draw_down_single_and_horizontal_double(img), + 0x2565 => self.draw_down_double_and_horizontal_single(img), + 0x2566 => self.draw_double_down_and_horizontal(img), + 0x2567 => self.draw_up_single_and_horizontal_double(img), + 0x2568 => self.draw_up_double_and_horizontal_single(img), + 0x2569 => self.draw_double_up_and_horizontal(img), + 0x256a => self.draw_vertical_single_and_horizontal_double(img), + 0x256b => self.draw_vertical_double_and_horizontal_single(img), + 0x256c => self.draw_double_vertical_and_horizontal(img), + 0x256d...0x2570 => try self.draw_light_arc(alloc, img, cp), + + 0x2571 => self.draw_light_diagonal_upper_right_to_lower_left(img), + 0x2572 => self.draw_light_diagonal_upper_left_to_lower_right(img), + 0x2573 => self.draw_light_diagonal_cross(img), + 0x2574 => self.draw_light_left(img), + 0x2575 => self.draw_light_up(img), + 0x2576 => self.draw_light_right(img), + 0x2577 => self.draw_light_down(img), + 0x2578 => self.draw_heavy_left(img), + 0x2579 => self.draw_heavy_up(img), + 0x257a => self.draw_heavy_right(img), + 0x257b => self.draw_heavy_down(img), + 0x257c => self.draw_light_left_and_heavy_right(img), + 0x257d => self.draw_light_up_and_heavy_down(img), + 0x257e => self.draw_heavy_left_and_light_right(img), + 0x257f => self.draw_heavy_up_and_light_down(img), + + 0x2580 => self.draw_upper_half_block(img), + 0x2581 => self.draw_lower_one_eighth_block(img), + 0x2582 => self.draw_lower_one_quarter_block(img), + 0x2583 => self.draw_lower_three_eighths_block(img), + 0x2584 => self.draw_lower_half_block(img), + 0x2585 => self.draw_lower_five_eighths_block(img), + 0x2586 => self.draw_lower_three_quarters_block(img), + 0x2587 => self.draw_lower_seven_eighths_block(img), + 0x2588 => self.draw_full_block(img), + 0x2589 => self.draw_left_seven_eighths_block(img), + 0x258a => self.draw_left_three_quarters_block(img), + 0x258b => self.draw_left_five_eighths_block(img), + 0x258c => self.draw_left_half_block(img), + 0x258d => self.draw_left_three_eighths_block(img), + 0x258e => self.draw_left_one_quarter_block(img), + 0x258f => self.draw_left_one_eighth_block(img), + + 0x2590 => self.draw_right_half_block(img), + 0x2591 => self.draw_light_shade(img), + 0x2592 => self.draw_medium_shade(img), + 0x2593 => self.draw_dark_shade(img), + 0x2594 => self.draw_upper_one_eighth_block(img), + 0x2595 => self.draw_right_one_eighth_block(img), + 0x2596...0x259f => self.draw_quadrant(img, cp), + + 0x2800...0x28FF => self.draw_braille(img, cp), + + 0x1FB00...0x1FB3B => self.draw_sextant(img, cp), + + 0x1FB3C...0x1FB40, + 0x1FB47...0x1FB4B, + 0x1FB57...0x1FB5B, + 0x1FB62...0x1FB66, + 0x1FB6C...0x1FB6F, + => try self.draw_wedge_triangle(img, cp), + + 0x1FB41...0x1FB45, + 0x1FB4C...0x1FB50, + 0x1FB52...0x1FB56, + 0x1FB5D...0x1FB61, + 0x1FB68...0x1FB6B, + => try self.draw_wedge_triangle_inverted(img, cp), + + 0x1FB46, + 0x1FB51, + 0x1FB5C, + 0x1FB67, + => try self.draw_wedge_triangle_and_box(img, cp), + + 0x1FB9A => { + try self.draw_wedge_triangle(img, 0x1fb6d); + try self.draw_wedge_triangle(img, 0x1fb6f); + }, + + 0x1FB9B => { + try self.draw_wedge_triangle(img, 0x1fb6c); + try self.draw_wedge_triangle(img, 0x1fb6e); + }, + + 0x1FB70 => self.draw_vertical_one_eighth_block_n(img, 1), + 0x1FB71 => self.draw_vertical_one_eighth_block_n(img, 2), + 0x1FB72 => self.draw_vertical_one_eighth_block_n(img, 3), + 0x1FB73 => self.draw_vertical_one_eighth_block_n(img, 4), + 0x1FB74 => self.draw_vertical_one_eighth_block_n(img, 5), + 0x1FB75 => self.draw_vertical_one_eighth_block_n(img, 6), + + 0x1FB76 => self.draw_horizontal_one_eighth_block_n(img, 1), + 0x1FB77 => self.draw_horizontal_one_eighth_block_n(img, 2), + 0x1FB78 => self.draw_horizontal_one_eighth_block_n(img, 3), + 0x1FB79 => self.draw_horizontal_one_eighth_block_n(img, 4), + 0x1FB7A => self.draw_horizontal_one_eighth_block_n(img, 5), + 0x1FB7B => self.draw_horizontal_one_eighth_block_n(img, 6), + + 0x1fb82 => self.draw_upper_one_quarter_block(img), + 0x1fb83 => self.draw_upper_three_eighths_block(img), + 0x1fb84 => self.draw_upper_five_eighths_block(img), + 0x1fb85 => self.draw_upper_three_quarters_block(img), + 0x1fb86 => self.draw_upper_seven_eighths_block(img), + + 0x1fb7c => self.draw_left_and_lower_one_eighth_block(img), + 0x1fb7d => self.draw_left_and_upper_one_eighth_block(img), + 0x1fb7e => self.draw_right_and_upper_one_eighth_block(img), + 0x1fb7f => self.draw_right_and_lower_one_eighth_block(img), + 0x1fb80 => self.draw_upper_and_lower_one_eighth_block(img), + 0x1fb81 => self.draw_horizontal_one_eighth_1358_block(img), + + 0x1fb87 => self.draw_right_one_quarter_block(img), + 0x1fb88 => self.draw_right_three_eighths_block(img), + 0x1fb89 => self.draw_right_five_eighths_block(img), + 0x1fb8a => self.draw_right_three_quarters_block(img), + 0x1fb8b => self.draw_right_seven_eighths_block(img), + + else => return error.InvalidCodepoint, + } +} + +fn draw_light_horizontal(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .light); +} + +fn draw_heavy_horizontal(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .heavy); +} + +fn draw_light_vertical(self: BoxFont, img: *pixman.Image) void { + self.vline_middle(img, .light); +} + +fn draw_heavy_vertical(self: BoxFont, img: *pixman.Image) void { + self.vline_middle(img, .heavy); +} + +fn draw_light_triple_dash_horizontal(self: BoxFont, img: *pixman.Image) void { + self.draw_dash_horizontal( + img, + 3, + Thickness.light.height(self.thickness), + @max(4, Thickness.light.height(self.thickness)), + ); +} + +fn draw_heavy_triple_dash_horizontal(self: BoxFont, img: *pixman.Image) void { + self.draw_dash_horizontal( + img, + 3, + Thickness.heavy.height(self.thickness), + @max(4, Thickness.light.height(self.thickness)), + ); +} + +fn draw_light_triple_dash_vertical(self: BoxFont, img: *pixman.Image) void { + self.draw_dash_vertical( + img, + 3, + Thickness.light.height(self.thickness), + @max(4, Thickness.light.height(self.thickness)), + ); +} + +fn draw_heavy_triple_dash_vertical(self: BoxFont, img: *pixman.Image) void { + self.draw_dash_vertical( + img, + 3, + Thickness.heavy.height(self.thickness), + @max(4, Thickness.light.height(self.thickness)), + ); +} + +fn draw_light_quadruple_dash_horizontal(self: BoxFont, img: *pixman.Image) void { + self.draw_dash_horizontal( + img, + 4, + Thickness.light.height(self.thickness), + @max(4, Thickness.light.height(self.thickness)), + ); +} + +fn draw_heavy_quadruple_dash_horizontal(self: BoxFont, img: *pixman.Image) void { + self.draw_dash_horizontal( + img, + 4, + Thickness.heavy.height(self.thickness), + @max(4, Thickness.light.height(self.thickness)), + ); +} + +fn draw_light_quadruple_dash_vertical(self: BoxFont, img: *pixman.Image) void { + self.draw_dash_vertical( + img, + 4, + Thickness.light.height(self.thickness), + @max(4, Thickness.light.height(self.thickness)), + ); +} + +fn draw_heavy_quadruple_dash_vertical(self: BoxFont, img: *pixman.Image) void { + self.draw_dash_vertical( + img, + 4, + Thickness.heavy.height(self.thickness), + @max(4, Thickness.light.height(self.thickness)), + ); +} + +fn draw_light_down_and_right(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .light, .light); + self.vline_middle_down(img, .light, .light); +} + +fn draw_down_light_and_right_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .light, .heavy); + self.vline_middle_down(img, .light, .light); +} + +fn draw_down_heavy_and_right_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .light, .light); + self.vline_middle_down(img, .heavy, .light); +} + +fn draw_heavy_down_and_right(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .heavy, .heavy); + self.vline_middle_down(img, .heavy, .heavy); +} + +fn draw_light_down_and_left(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.vline_middle_down(img, .light, .light); +} + +fn draw_down_light_and_left_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .heavy); + self.vline_middle_down(img, .light, .light); +} + +fn draw_down_heavy_and_left_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.vline_middle_down(img, .heavy, .light); +} + +fn draw_heavy_down_and_left(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .heavy, .heavy); + self.vline_middle_down(img, .heavy, .heavy); +} + +fn draw_light_up_and_right(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .light, .light); + self.vline_middle_up(img, .light, .light); +} + +fn draw_up_light_and_right_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .light, .heavy); + self.vline_middle_up(img, .light, .light); +} + +fn draw_up_heavy_and_right_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .light, .light); + self.vline_middle_up(img, .heavy, .light); +} + +fn draw_heavy_up_and_right(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .heavy, .heavy); + self.vline_middle_up(img, .heavy, .heavy); +} + +fn draw_light_up_and_left(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.vline_middle_up(img, .light, .light); +} + +fn draw_up_light_and_left_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .heavy); + self.vline_middle_up(img, .light, .light); +} + +fn draw_up_heavy_and_left_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.vline_middle_up(img, .heavy, .light); +} + +fn draw_heavy_up_and_left(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .heavy, .heavy); + self.vline_middle_up(img, .heavy, .heavy); +} + +fn draw_light_vertical_and_right(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .light, .light); + self.vline_middle(img, .light); +} + +fn draw_vertical_light_and_right_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .light, .heavy); + self.vline_middle(img, .light); +} + +fn draw_up_heavy_and_right_down_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .light, .light); + self.vline_middle_up(img, .heavy, .light); + self.vline_middle_down(img, .light, .light); +} + +fn draw_down_heavy_and_right_up_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .light, .light); + self.vline_middle_up(img, .light, .light); + self.vline_middle_down(img, .heavy, .light); +} + +fn draw_vertical_heavy_and_right_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .light, .light); + self.vline_middle(img, .heavy); +} + +fn draw_down_light_and_right_up_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .heavy, .heavy); + self.vline_middle_up(img, .heavy, .heavy); + self.vline_middle_down(img, .light, .light); +} + +fn draw_up_light_and_right_down_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .heavy, .heavy); + self.vline_middle_up(img, .light, .light); + self.vline_middle_down(img, .heavy, .heavy); +} + +fn draw_heavy_vertical_and_right(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .heavy, .heavy); + self.vline_middle(img, .heavy); +} + +fn draw_light_vertical_and_left(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.vline_middle(img, .light); +} + +fn draw_vertical_light_and_left_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .heavy); + self.vline_middle(img, .light); +} + +fn draw_up_heavy_and_left_down_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.vline_middle_up(img, .heavy, .light); + self.vline_middle_down(img, .light, .light); +} + +fn draw_down_heavy_and_left_up_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.vline_middle_up(img, .light, .light); + self.vline_middle_down(img, .heavy, .light); +} + +fn draw_vertical_heavy_and_left_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.vline_middle(img, .heavy); +} + +fn draw_down_light_and_left_up_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .heavy, .heavy); + self.vline_middle_up(img, .heavy, .heavy); + self.vline_middle_down(img, .light, .light); +} + +fn draw_up_light_and_left_down_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .heavy, .heavy); + self.vline_middle_up(img, .light, .light); + self.vline_middle_down(img, .heavy, .heavy); +} + +fn draw_heavy_vertical_and_left(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .heavy, .heavy); + self.vline_middle(img, .heavy); +} + +fn draw_light_down_and_horizontal(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .light); + self.vline_middle_down(img, .light, .light); +} + +fn draw_left_heavy_and_right_down_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .heavy); + self.hline_middle_right(img, .light, .light); + self.vline_middle_down(img, .light, .light); +} + +fn draw_right_heavy_and_left_down_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.hline_middle_right(img, .light, .heavy); + self.vline_middle_down(img, .light, .light); +} + +fn draw_down_light_and_horizontal_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .heavy); + self.vline_middle_down(img, .light, .light); +} + +fn draw_down_heavy_and_horizontal_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .light); + self.vline_middle_down(img, .heavy, .light); +} + +fn draw_right_light_and_left_down_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .heavy, .heavy); + self.hline_middle_right(img, .light, .light); + self.vline_middle_down(img, .heavy, .heavy); +} + +fn draw_left_light_and_right_down_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.hline_middle_right(img, .heavy, .heavy); + self.vline_middle_down(img, .heavy, .heavy); +} + +fn draw_heavy_down_and_horizontal(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .heavy); + self.vline_middle_down(img, .heavy, .heavy); +} + +fn draw_light_up_and_horizontal(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .light); + self.vline_middle_up(img, .light, .light); +} + +fn draw_left_heavy_and_right_up_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .heavy); + self.hline_middle_right(img, .light, .light); + self.vline_middle_up(img, .light, .light); +} + +fn draw_right_heavy_and_left_up_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.hline_middle_right(img, .light, .heavy); + self.vline_middle_up(img, .light, .light); +} + +fn draw_up_light_and_horizontal_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .light); + self.vline_middle_up(img, .light, .light); +} + +fn draw_up_heavy_and_horizontal_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .light); + self.vline_middle_up(img, .heavy, .light); +} + +fn draw_right_light_and_left_up_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .heavy, .heavy); + self.hline_middle_right(img, .light, .light); + self.vline_middle_up(img, .heavy, .heavy); +} + +fn draw_left_light_and_right_up_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.hline_middle_right(img, .heavy, .heavy); + self.vline_middle_up(img, .heavy, .heavy); +} + +fn draw_heavy_up_and_horizontal(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .heavy); + self.vline_middle_up(img, .heavy, .heavy); +} + +fn draw_light_vertical_and_horizontal(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .light); + self.vline_middle(img, .light); +} + +fn draw_left_heavy_and_right_vertical_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .heavy); + self.hline_middle_right(img, .light, .light); + self.vline_middle(img, .light); +} + +fn draw_right_heavy_and_left_vertical_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.hline_middle_right(img, .light, .heavy); + self.vline_middle(img, .light); +} + +fn draw_vertical_light_and_horizontal_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .heavy); + self.vline_middle(img, .light); +} + +fn draw_up_heavy_and_down_horizontal_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .light); + self.vline_middle_up(img, .heavy, .heavy); + self.vline_middle_down(img, .light, .light); +} + +fn draw_down_heavy_and_up_horizontal_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .light); + self.vline_middle_up(img, .light, .light); + self.vline_middle_down(img, .heavy, .light); +} + +fn draw_vertical_heavy_and_horizontal_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .light); + self.vline_middle(img, .heavy); +} + +fn draw_left_up_heavy_and_right_down_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .heavy, .heavy); + self.hline_middle_right(img, .light, .light); + self.vline_middle_up(img, .heavy, .heavy); + self.vline_middle_down(img, .light, .light); +} + +fn draw_right_up_heavy_and_left_down_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.hline_middle_right(img, .heavy, .heavy); + self.vline_middle_up(img, .heavy, .heavy); + self.vline_middle_down(img, .light, .light); +} + +fn draw_left_down_heavy_and_right_up_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .heavy, .heavy); + self.hline_middle_right(img, .light, .light); + self.vline_middle_up(img, .light, .light); + self.vline_middle_down(img, .heavy, .heavy); +} + +fn draw_right_down_heavy_and_left_up_light(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.hline_middle_right(img, .heavy, .heavy); + self.vline_middle_up(img, .light, .light); + self.vline_middle_down(img, .heavy, .heavy); +} + +fn draw_down_light_and_up_horizontal_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .heavy); + self.vline_middle_up(img, .heavy, .heavy); + self.vline_middle_down(img, .light, .light); +} + +fn draw_up_light_and_down_horizontal_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .heavy); + self.vline_middle_up(img, .light, .light); + self.vline_middle_down(img, .heavy, .heavy); +} + +fn draw_right_light_and_left_vertical_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .heavy, .heavy); + self.hline_middle_right(img, .light, .light); + self.vline_middle(img, .heavy); +} + +fn draw_left_light_and_right_vertical_heavy(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.hline_middle_right(img, .heavy, .heavy); + self.vline_middle(img, .heavy); +} + +fn draw_heavy_vertical_and_horizontal(self: BoxFont, img: *pixman.Image) void { + self.hline_middle(img, .heavy); + self.vline_middle(img, .heavy); +} + +fn draw_light_double_dash_horizontal(self: BoxFont, img: *pixman.Image) void { + self.draw_dash_horizontal( + img, + 2, + Thickness.light.height(self.thickness), + Thickness.light.height(self.thickness), + ); +} + +fn draw_heavy_double_dash_horizontal(self: BoxFont, img: *pixman.Image) void { + self.draw_dash_horizontal( + img, + 2, + Thickness.heavy.height(self.thickness), + Thickness.heavy.height(self.thickness), + ); +} + +fn draw_light_double_dash_vertical(self: BoxFont, img: *pixman.Image) void { + self.draw_dash_vertical( + img, + 2, + Thickness.light.height(self.thickness), + Thickness.heavy.height(self.thickness), + ); +} + +fn draw_heavy_double_dash_vertical(self: BoxFont, img: *pixman.Image) void { + self.draw_dash_vertical( + img, + 2, + Thickness.heavy.height(self.thickness), + Thickness.heavy.height(self.thickness), + ); +} + +fn draw_double_horizontal(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const mid = (self.height - thick_px * 3) / 2; + self.hline(img, 0, self.width, mid, thick_px); + self.hline(img, 0, self.width, mid + 2 * thick_px, thick_px); +} + +fn draw_double_vertical(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const mid = (self.width - thick_px * 3) / 2; + self.vline(img, 0, self.height, mid, thick_px); + self.vline(img, 0, self.height, mid + 2 * thick_px, thick_px); +} + +fn draw_down_single_and_right_double(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width - thick_px) / 2; + self.vline_middle_down(img, .light, .light); + self.hline(img, vmid, self.width, hmid, thick_px); + self.hline(img, vmid, self.width, hmid + 2 * thick_px, thick_px); +} + +fn draw_down_double_and_right_single(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.hline_middle_right(img, .light, .light); + self.vline(img, hmid, self.height, vmid, thick_px); + self.vline(img, hmid, self.height, vmid + 2 * thick_px, thick_px); +} + +fn draw_double_down_and_right(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.vline(img, hmid, self.height, vmid, thick_px); + self.vline(img, hmid + 2 * thick_px, self.height, vmid + 2 * thick_px, thick_px); + self.hline(img, vmid, self.width, hmid, thick_px); + self.hline(img, vmid + 2 * thick_px, self.width, hmid + 2 * thick_px, thick_px); +} + +fn draw_down_single_and_left_double(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width + thick_px) / 2; + self.vline_middle_down(img, .light, .light); + self.hline(img, 0, vmid, hmid, thick_px); + self.hline(img, 0, vmid, hmid + 2 * thick_px, thick_px); +} + +fn draw_down_double_and_left_single(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.hline_middle_left(img, .light, .light); + self.vline(img, hmid, self.height, vmid, thick_px); + self.vline(img, hmid, self.height, vmid + 2 * thick_px, thick_px); +} + +fn draw_double_down_and_left(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.vline(img, hmid + 2 * thick_px, self.height, vmid, thick_px); + self.vline(img, hmid, self.height, vmid + 2 * thick_px, thick_px); + self.hline(img, 0, vmid + 2 * thick_px, hmid, thick_px); + self.hline(img, 0, vmid, hmid + 2 * thick_px, thick_px); +} + +fn draw_up_single_and_right_double(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width - thick_px) / 2; + self.vline_middle_up(img, .light, .light); + self.hline(img, vmid, self.width, hmid, thick_px); + self.hline(img, vmid, self.width, hmid + 2 * thick_px, thick_px); +} + +fn draw_up_double_and_right_single(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height + thick_px) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.hline_middle_right(img, .light, .light); + self.vline(img, 0, hmid, vmid, thick_px); + self.vline(img, 0, hmid, vmid + 2 * thick_px, thick_px); +} + +fn draw_double_up_and_right(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.vline(img, 0, hmid + 2 * thick_px, vmid, thick_px); + self.vline(img, 0, hmid, vmid + 2 * thick_px, thick_px); + self.hline(img, vmid + 2 * thick_px, self.width, hmid, thick_px); + self.hline(img, vmid, self.width, hmid + 2 * thick_px, thick_px); +} + +fn draw_up_single_and_left_double(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width + thick_px) / 2; + self.vline_middle_up(img, .light, .light); + self.hline(img, 0, vmid, hmid, thick_px); + self.hline(img, 0, vmid, hmid + 2 * thick_px, thick_px); +} + +fn draw_up_double_and_left_single(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height + thick_px) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.hline_middle_left(img, .light, .light); + self.vline(img, 0, hmid, vmid, thick_px); + self.vline(img, 0, hmid, vmid + 2 * thick_px, thick_px); +} + +fn draw_double_up_and_left(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.vline(img, 0, hmid + 0 * thick_px + thick_px, vmid, thick_px); + self.vline(img, 0, hmid + 2 * thick_px + thick_px, vmid + 2 * thick_px, thick_px); + self.hline(img, 0, vmid, hmid, thick_px); + self.hline(img, 0, vmid, hmid + 2 * thick_px, thick_px); +} + +fn draw_vertical_single_and_right_double(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width - thick_px) / 2; + self.vline_middle(img, .light); + self.hline(img, vmid, self.width, hmid, thick_px); + self.hline(img, vmid, self.width, hmid + 2 * thick_px, thick_px); +} + +fn draw_vertical_double_and_right_single(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const vmid = (self.width - thick_px * 3) / 2; + self.hline(img, vmid + 2 * thick_px, self.width, (self.height - thick_px) / 2, thick_px); + self.vline(img, 0, self.height, vmid, thick_px); + self.vline(img, 0, self.height, vmid + 2 * thick_px, thick_px); +} + +fn draw_double_vertical_and_right(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.vline(img, 0, self.height, vmid, thick_px); + self.vline(img, 0, hmid, vmid + 2 * thick_px, thick_px); + self.vline(img, hmid + 2 * thick_px, self.height, vmid + 2 * thick_px, thick_px); + self.hline(img, vmid + 2 * thick_px, self.width, hmid, thick_px); + self.hline(img, vmid + 2 * thick_px, self.width, hmid + 2 * thick_px, thick_px); +} + +fn draw_vertical_single_and_left_double(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width + thick_px) / 2; + self.vline_middle(img, .light); + self.hline(img, 0, vmid, hmid, thick_px); + self.hline(img, 0, vmid, hmid + 2 * thick_px, thick_px); +} + +fn draw_vertical_double_and_left_single(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const vmid = (self.width - thick_px * 3) / 2; + self.hline(img, 0, vmid, (self.height - thick_px) / 2, thick_px); + self.vline(img, 0, self.height, vmid, thick_px); + self.vline(img, 0, self.height, vmid + 2 * thick_px, thick_px); +} + +fn draw_double_vertical_and_left(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.vline(img, 0, self.height, vmid + 2 * thick_px, thick_px); + self.vline(img, 0, hmid, vmid, thick_px); + self.vline(img, hmid + 2 * thick_px, self.height, vmid, thick_px); + self.hline(img, 0, vmid + thick_px, hmid, thick_px); + self.hline(img, 0, vmid, hmid + 2 * thick_px, thick_px); +} + +fn draw_down_single_and_horizontal_double(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + self.vline(img, hmid + 2 * thick_px, self.height, (self.width - thick_px) / 2, thick_px); + self.hline(img, 0, self.width, hmid, thick_px); + self.hline(img, 0, self.width, hmid + 2 * thick_px, thick_px); +} + +fn draw_down_double_and_horizontal_single(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.hline_middle(img, .light); + self.vline(img, hmid, self.height, vmid, thick_px); + self.vline(img, hmid, self.height, vmid + 2 * thick_px, thick_px); +} + +fn draw_double_down_and_horizontal(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.hline(img, 0, self.width, hmid, thick_px); + self.hline(img, 0, vmid, hmid + 2 * thick_px, thick_px); + self.hline(img, vmid + 2 * thick_px, self.width, hmid + 2 * thick_px, thick_px); + self.vline(img, hmid + 2 * thick_px, self.height, vmid, thick_px); + self.vline(img, hmid + 2 * thick_px, self.height, vmid + 2 * thick_px, thick_px); +} + +fn draw_up_single_and_horizontal_double(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width - thick_px) / 2; + self.vline(img, 0, hmid, vmid, thick_px); + self.hline(img, 0, self.width, hmid, thick_px); + self.hline(img, 0, self.width, hmid + 2 * thick_px, thick_px); +} + +fn draw_up_double_and_horizontal_single(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.hline_middle(img, .light); + self.vline(img, 0, hmid, vmid, thick_px); + self.vline(img, 0, hmid, vmid + 2 * thick_px, thick_px); +} + +fn draw_double_up_and_horizontal(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.vline(img, 0, hmid, vmid, thick_px); + self.vline(img, 0, hmid, vmid + 2 * thick_px, thick_px); + self.hline(img, 0, vmid + thick_px, hmid, thick_px); + self.hline(img, vmid + 2 * thick_px, self.width, hmid, thick_px); + self.hline(img, 0, self.width, hmid + 2 * thick_px, thick_px); +} + +fn draw_vertical_single_and_horizontal_double(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + self.vline_middle(img, .light); + self.hline(img, 0, self.width, hmid, thick_px); + self.hline(img, 0, self.width, hmid + 2 * thick_px, thick_px); +} + +fn draw_vertical_double_and_horizontal_single(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const vmid = (self.width - thick_px * 3) / 2; + self.hline_middle(img, .light); + self.vline(img, 0, self.height, vmid, thick_px); + self.vline(img, 0, self.height, vmid + 2 * thick_px, thick_px); +} + +fn draw_double_vertical_and_horizontal(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + const hmid = (self.height - thick_px * 3) / 2; + const vmid = (self.width - thick_px * 3) / 2; + self.hline(img, 0, vmid, hmid, thick_px); + self.hline(img, vmid + 2 * thick_px, self.width, hmid, thick_px); + self.hline(img, 0, vmid, hmid + 2 * thick_px, thick_px); + self.hline(img, vmid + 2 * thick_px, self.width, hmid + 2 * thick_px, thick_px); + self.vline(img, 0, hmid + thick_px, vmid, thick_px); + self.vline(img, 0, hmid, vmid + 2 * thick_px, thick_px); + self.vline(img, hmid + 2 * thick_px, self.height, vmid, thick_px); + self.vline(img, hmid + 2 * thick_px, self.height, vmid + 2 * thick_px, thick_px); +} + +fn draw_light_diagonal_upper_right_to_lower_left(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + img.rasterizeTrapezoid(.{ + .top = pixman.Fixed.init(0), + .bottom = pixman.Fixed.init(self.height), + .left = .{ + .p1 = .{ + .x = pixman.Fixed.init(@intToFloat(f64, self.width) - @intToFloat(f64, thick_px) / 2), + .y = pixman.Fixed.init(0), + }, + + .p2 = .{ + .x = pixman.Fixed.init(0 - @intToFloat(f64, thick_px) / 2), + .y = pixman.Fixed.init(self.height), + }, + }, + .right = .{ + .p1 = .{ + .x = pixman.Fixed.init(@intToFloat(f64, self.width) + @intToFloat(f64, thick_px) / 2), + .y = pixman.Fixed.init(0), + }, + + .p2 = .{ + .x = pixman.Fixed.init(0 + @intToFloat(f64, thick_px) / 2), + .y = pixman.Fixed.init(self.height), + }, + }, + }, 0, 0); +} + +fn draw_light_diagonal_upper_left_to_lower_right(self: BoxFont, img: *pixman.Image) void { + const thick_px = Thickness.light.height(self.thickness); + img.rasterizeTrapezoid(.{ + .top = pixman.Fixed.init(0), + .bottom = pixman.Fixed.init(self.height), + .left = .{ + .p1 = .{ + .x = pixman.Fixed.init(0 - @intToFloat(f64, thick_px) / 2), + .y = pixman.Fixed.init(0), + }, + + .p2 = .{ + .x = pixman.Fixed.init(@intToFloat(f64, self.width) - @intToFloat(f64, thick_px) / 2), + .y = pixman.Fixed.init(self.height), + }, + }, + .right = .{ + .p1 = .{ + .x = pixman.Fixed.init(0 + @intToFloat(f64, thick_px) / 2), + .y = pixman.Fixed.init(0), + }, + + .p2 = .{ + .x = pixman.Fixed.init(@intToFloat(f64, self.width) + @intToFloat(f64, thick_px) / 2), + .y = pixman.Fixed.init(self.height), + }, + }, + }, 0, 0); +} + +fn draw_light_diagonal_cross(self: BoxFont, img: *pixman.Image) void { + self.draw_light_diagonal_upper_right_to_lower_left(img); + self.draw_light_diagonal_upper_left_to_lower_right(img); +} + +fn draw_light_left(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); +} + +fn draw_light_up(self: BoxFont, img: *pixman.Image) void { + self.vline_middle_up(img, .light, .light); +} + +fn draw_light_right(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .light, .light); +} + +fn draw_light_down(self: BoxFont, img: *pixman.Image) void { + self.vline_middle_down(img, .light, .light); +} + +fn draw_heavy_left(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .heavy, .heavy); +} + +fn draw_heavy_up(self: BoxFont, img: *pixman.Image) void { + self.vline_middle_up(img, .heavy, .heavy); +} + +fn draw_heavy_right(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_right(img, .heavy, .heavy); +} + +fn draw_heavy_down(self: BoxFont, img: *pixman.Image) void { + self.vline_middle_down(img, .heavy, .heavy); +} + +fn draw_light_left_and_heavy_right(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .light, .light); + self.hline_middle_right(img, .heavy, .heavy); +} + +fn draw_light_up_and_heavy_down(self: BoxFont, img: *pixman.Image) void { + self.vline_middle_up(img, .light, .light); + self.vline_middle_down(img, .heavy, .heavy); +} + +fn draw_heavy_left_and_light_right(self: BoxFont, img: *pixman.Image) void { + self.hline_middle_left(img, .heavy, .heavy); + self.hline_middle_right(img, .light, .light); +} + +fn draw_heavy_up_and_light_down(self: BoxFont, img: *pixman.Image) void { + self.vline_middle_up(img, .heavy, .heavy); + self.vline_middle_down(img, .light, .light); +} + +fn draw_upper_half_block(self: BoxFont, img: *pixman.Image) void { + self.rect(img, 0, 0, self.width, self.height / 2); +} + +fn draw_lower_one_eighth_block(self: BoxFont, img: *pixman.Image) void { + self.rect(img, 0, self.height - (self.height / 8), self.width, self.height); +} + +fn draw_lower_one_quarter_block(self: BoxFont, img: *pixman.Image) void { + self.rect(img, 0, self.height - (self.height / 4), self.width, self.height); +} + +fn draw_lower_three_eighths_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + self.height - @floatToInt(u32, @round(3 * @intToFloat(f64, self.height) / 8)), + self.width, + self.height, + ); +} + +fn draw_lower_half_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + self.height - @floatToInt(u32, @round(@intToFloat(f64, self.height) / 2)), + self.width, + self.height, + ); +} + +fn draw_lower_five_eighths_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + self.height - @floatToInt(u32, @round(5 * @intToFloat(f64, self.height) / 8)), + self.width, + self.height, + ); +} + +fn draw_lower_three_quarters_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + self.height - @floatToInt(u32, @round(3 * @intToFloat(f64, self.height) / 4)), + self.width, + self.height, + ); +} + +fn draw_lower_seven_eighths_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + self.height - @floatToInt(u32, @round(7 * @intToFloat(f64, self.height) / 8)), + self.width, + self.height, + ); +} + +fn draw_upper_one_quarter_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + 0, + self.width, + @floatToInt(u32, @round(@intToFloat(f64, self.height) / 4)), + ); +} + +fn draw_upper_three_eighths_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + 0, + self.width, + @floatToInt(u32, @round(3 * @intToFloat(f64, self.height) / 8)), + ); +} + +fn draw_upper_five_eighths_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + 0, + self.width, + @floatToInt(u32, @round(5 * @intToFloat(f64, self.height) / 8)), + ); +} + +fn draw_upper_three_quarters_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + 0, + self.width, + @floatToInt(u32, @round(3 * @intToFloat(f64, self.height) / 4)), + ); +} + +fn draw_upper_seven_eighths_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + 0, + self.width, + @floatToInt(u32, @round(7 * @intToFloat(f64, self.height) / 8)), + ); +} + +fn draw_full_block(self: BoxFont, img: *pixman.Image) void { + self.rect(img, 0, 0, self.width, self.height); +} + +fn draw_left_seven_eighths_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + 0, + @floatToInt(u32, @round(7 * @intToFloat(f64, self.width) / 8)), + self.height, + ); +} + +fn draw_left_three_quarters_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + 0, + @floatToInt(u32, @round(3 * @intToFloat(f64, self.width) / 4)), + self.height, + ); +} + +fn draw_left_five_eighths_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + 0, + @floatToInt(u32, @round(5 * @intToFloat(f64, self.width) / 8)), + self.height, + ); +} + +fn draw_left_half_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + 0, + @floatToInt(u32, @round(@intToFloat(f64, self.width) / 2)), + self.height, + ); +} + +fn draw_left_three_eighths_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + 0, + @floatToInt(u32, @round(3 * @intToFloat(f64, self.width) / 8)), + self.height, + ); +} + +fn draw_left_one_quarter_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + 0, + @floatToInt(u32, @round(@intToFloat(f64, self.width) / 4)), + self.height, + ); +} + +fn draw_vertical_one_eighth_block_n(self: BoxFont, img: *pixman.Image, n: u32) void { + const x = @floatToInt(u32, @round(@intToFloat(f64, n) * @intToFloat(f64, self.width) / 8)); + const w = @floatToInt(u32, @round(@intToFloat(f64, self.width) / 8)); + self.rect(img, x, 0, x + w, self.height); +} + +fn draw_left_one_eighth_block(self: BoxFont, img: *pixman.Image) void { + self.draw_vertical_one_eighth_block_n(img, 0); +} + +fn draw_right_half_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + @floatToInt(u32, @round(@intToFloat(f64, self.width) / 2)), + 0, + self.width, + self.height, + ); +} + +fn draw_pixman_shade(self: BoxFont, img: *pixman.Image, v: u16) void { + const rects = &[_]pixman.Rectangle16{ + .{ + .x = 0, + .y = 0, + .width = @intCast(u16, self.width), + .height = @intCast(u16, self.height), + }, + }; + + img.fillRectangles( + .src, + .{ .red = 0, .green = 0, .blue = 0, .alpha = v }, + rects, + ) catch {}; +} + +fn draw_light_shade(self: BoxFont, img: *pixman.Image) void { + self.draw_pixman_shade(img, 0x4000); +} + +fn draw_medium_shade(self: BoxFont, img: *pixman.Image) void { + self.draw_pixman_shade(img, 0x8000); +} + +fn draw_dark_shade(self: BoxFont, img: *pixman.Image) void { + self.draw_pixman_shade(img, 0xc000); +} + +fn draw_horizontal_one_eighth_block_n(self: BoxFont, img: *pixman.Image, n: u32) void { + const y = @floatToInt(u32, @round(@intToFloat(f64, n) * @intToFloat(f64, self.height) / 8)); + const h = @floatToInt(u32, @round(@intToFloat(f64, self.height) / 8)); + self.rect(img, 0, y, self.width, y + h); +} + +fn draw_upper_one_eighth_block(self: BoxFont, img: *pixman.Image) void { + self.draw_horizontal_one_eighth_block_n(img, 0); +} + +fn draw_right_one_eighth_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + self.width - @floatToInt(u32, @round(@intToFloat(f64, self.width) / 8)), + 0, + self.width, + self.height, + ); +} + +fn draw_left_and_lower_one_eighth_block(self: BoxFont, img: *pixman.Image) void { + self.draw_left_one_eighth_block(img); + self.draw_lower_one_eighth_block(img); +} + +fn draw_left_and_upper_one_eighth_block(self: BoxFont, img: *pixman.Image) void { + self.draw_left_one_eighth_block(img); + self.draw_upper_one_eighth_block(img); +} + +fn draw_right_and_upper_one_eighth_block(self: BoxFont, img: *pixman.Image) void { + self.draw_right_one_eighth_block(img); + self.draw_upper_one_eighth_block(img); +} + +fn draw_right_and_lower_one_eighth_block(self: BoxFont, img: *pixman.Image) void { + self.draw_right_one_eighth_block(img); + self.draw_lower_one_eighth_block(img); +} + +fn draw_upper_and_lower_one_eighth_block(self: BoxFont, img: *pixman.Image) void { + self.draw_upper_one_eighth_block(img); + self.draw_lower_one_eighth_block(img); +} + +fn draw_horizontal_one_eighth_1358_block(self: BoxFont, img: *pixman.Image) void { + self.draw_upper_one_eighth_block(img); + self.draw_horizontal_one_eighth_block_n(img, 2); + self.draw_horizontal_one_eighth_block_n(img, 4); + self.draw_lower_one_eighth_block(img); +} + +fn draw_right_one_quarter_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + self.width - @floatToInt(u32, @round(@intToFloat(f64, self.width) / 4)), + 0, + self.width, + self.height, + ); +} + +fn draw_right_three_quarters_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + self.width - @floatToInt(u32, @round(3 * @intToFloat(f64, self.width) / 4)), + 0, + self.width, + self.height, + ); +} + +fn draw_right_three_eighths_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + self.width - @floatToInt(u32, @round(3 * @intToFloat(f64, self.width) / 8)), + 0, + self.width, + self.height, + ); +} + +fn draw_right_five_eighths_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + self.width - @floatToInt(u32, @round(5 * @intToFloat(f64, self.width) / 8)), + 0, + self.width, + self.height, + ); +} + +fn draw_right_seven_eighths_block(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + self.width - @floatToInt(u32, @round(7 * @intToFloat(f64, self.width) / 8)), + 0, + self.width, + self.height, + ); +} + +fn quad_upper_left(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + 0, + @floatToInt(u32, @ceil(@intToFloat(f64, self.width) / 2)), + @floatToInt(u32, @ceil(@intToFloat(f64, self.height) / 2)), + ); +} + +fn quad_upper_right(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + @floatToInt(u32, @floor(@intToFloat(f64, self.width) / 2)), + 0, + self.width, + @floatToInt(u32, @ceil(@intToFloat(f64, self.height) / 2)), + ); +} + +fn quad_lower_left(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + 0, + @floatToInt(u32, @floor(@intToFloat(f64, self.height) / 2)), + @floatToInt(u32, @ceil(@intToFloat(f64, self.width) / 2)), + self.height, + ); +} + +fn quad_lower_right(self: BoxFont, img: *pixman.Image) void { + self.rect( + img, + @floatToInt(u32, @floor(@intToFloat(f64, self.width) / 2)), + @floatToInt(u32, @floor(@intToFloat(f64, self.height) / 2)), + self.width, + self.height, + ); +} + +fn draw_quadrant(self: BoxFont, img: *pixman.Image, cp: u32) void { + const UPPER_LEFT: u8 = 1 << 0; + const UPPER_RIGHT: u8 = 1 << 1; + const LOWER_LEFT: u8 = 1 << 2; + const LOWER_RIGHT: u8 = 1 << 3; + const matrix: [10]u8 = .{ + LOWER_LEFT, + LOWER_RIGHT, + UPPER_LEFT, + UPPER_LEFT | LOWER_LEFT | LOWER_RIGHT, + UPPER_LEFT | LOWER_RIGHT, + UPPER_LEFT | UPPER_RIGHT | LOWER_LEFT, + UPPER_LEFT | UPPER_RIGHT | LOWER_RIGHT, + UPPER_RIGHT, + UPPER_RIGHT | LOWER_LEFT, + UPPER_RIGHT | LOWER_LEFT | LOWER_RIGHT, + }; + + assert(cp >= 0x2596 and cp <= 0x259f); + const idx = cp - 0x2596; + const encoded = matrix[idx]; + + if (encoded & UPPER_LEFT == UPPER_LEFT) self.quad_upper_left(img); + if (encoded & UPPER_RIGHT == UPPER_RIGHT) self.quad_upper_right(img); + if (encoded & LOWER_LEFT == LOWER_LEFT) self.quad_lower_left(img); + if (encoded & LOWER_RIGHT == LOWER_RIGHT) self.quad_lower_right(img); +} + +fn draw_braille(self: BoxFont, img: *pixman.Image, 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(img, x[0], y[0], x[0] + w, y[0] + w); + if (sym & 2 > 0) + self.rect(img, x[0], y[1], x[0] + w, y[1] + w); + if (sym & 4 > 0) + self.rect(img, x[0], y[2], x[0] + w, y[2] + w); + + // Right side + if (sym & 8 > 0) + self.rect(img, x[1], y[0], x[1] + w, y[0] + w); + if (sym & 16 > 0) + self.rect(img, x[1], y[1], x[1] + w, y[1] + w); + if (sym & 32 > 0) + self.rect(img, x[1], y[2], x[1] + w, y[2] + w); + + // 8-dot patterns + if (sym & 64 > 0) + self.rect(img, x[0], y[3], x[0] + w, y[3] + w); + if (sym & 128 > 0) + self.rect(img, x[1], y[3], x[1] + w, y[3] + w); +} + +fn draw_sextant(self: BoxFont, img: *pixman.Image, cp: u32) void { + const UPPER_LEFT: u8 = 1 << 0; + const MIDDLE_LEFT: u8 = 1 << 1; + const LOWER_LEFT: u8 = 1 << 2; + const UPPER_RIGHT: u8 = 1 << 3; + const MIDDLE_RIGHT: u8 = 1 << 4; + const LOWER_RIGHT: u8 = 1 << 5; + + const matrix: [60]u8 = .{ + // U+1fb00 - U+1fb0f + UPPER_LEFT, + UPPER_RIGHT, + UPPER_LEFT | UPPER_RIGHT, + MIDDLE_LEFT, + UPPER_LEFT | MIDDLE_LEFT, + UPPER_RIGHT | MIDDLE_LEFT, + UPPER_LEFT | UPPER_RIGHT | MIDDLE_LEFT, + MIDDLE_RIGHT, + UPPER_LEFT | MIDDLE_RIGHT, + UPPER_RIGHT | MIDDLE_RIGHT, + UPPER_LEFT | UPPER_RIGHT | MIDDLE_RIGHT, + MIDDLE_LEFT | MIDDLE_RIGHT, + UPPER_LEFT | MIDDLE_LEFT | MIDDLE_RIGHT, + UPPER_RIGHT | MIDDLE_LEFT | MIDDLE_RIGHT, + UPPER_LEFT | UPPER_RIGHT | MIDDLE_LEFT | MIDDLE_RIGHT, + LOWER_LEFT, + + // U+1fb10 - U+1fb1f + UPPER_LEFT | LOWER_LEFT, + UPPER_RIGHT | LOWER_LEFT, + UPPER_LEFT | UPPER_RIGHT | LOWER_LEFT, + MIDDLE_LEFT | LOWER_LEFT, + UPPER_RIGHT | MIDDLE_LEFT | LOWER_LEFT, + UPPER_LEFT | UPPER_RIGHT | MIDDLE_LEFT | LOWER_LEFT, + MIDDLE_RIGHT | LOWER_LEFT, + UPPER_LEFT | MIDDLE_RIGHT | LOWER_LEFT, + UPPER_RIGHT | MIDDLE_RIGHT | LOWER_LEFT, + UPPER_LEFT | UPPER_RIGHT | MIDDLE_RIGHT | LOWER_LEFT, + MIDDLE_LEFT | MIDDLE_RIGHT | LOWER_LEFT, + UPPER_LEFT | MIDDLE_LEFT | MIDDLE_RIGHT | LOWER_LEFT, + UPPER_RIGHT | MIDDLE_LEFT | MIDDLE_RIGHT | LOWER_LEFT, + UPPER_LEFT | UPPER_RIGHT | MIDDLE_LEFT | MIDDLE_RIGHT | LOWER_LEFT, + LOWER_RIGHT, + UPPER_LEFT | LOWER_RIGHT, + + // U+1fb20 - U+1fb2f + UPPER_RIGHT | LOWER_RIGHT, + UPPER_LEFT | UPPER_RIGHT | LOWER_RIGHT, + MIDDLE_LEFT | LOWER_RIGHT, + UPPER_LEFT | MIDDLE_LEFT | LOWER_RIGHT, + UPPER_RIGHT | MIDDLE_LEFT | LOWER_RIGHT, + UPPER_LEFT | UPPER_RIGHT | MIDDLE_LEFT | LOWER_RIGHT, + MIDDLE_RIGHT | LOWER_RIGHT, + UPPER_LEFT | MIDDLE_RIGHT | LOWER_RIGHT, + UPPER_LEFT | UPPER_RIGHT | MIDDLE_RIGHT | LOWER_RIGHT, + MIDDLE_LEFT | MIDDLE_RIGHT | LOWER_RIGHT, + UPPER_LEFT | MIDDLE_LEFT | MIDDLE_RIGHT | LOWER_RIGHT, + UPPER_RIGHT | MIDDLE_LEFT | MIDDLE_RIGHT | LOWER_RIGHT, + UPPER_LEFT | UPPER_RIGHT | MIDDLE_LEFT | MIDDLE_RIGHT | LOWER_RIGHT, + LOWER_LEFT | LOWER_RIGHT, + UPPER_LEFT | LOWER_LEFT | LOWER_RIGHT, + UPPER_RIGHT | LOWER_LEFT | LOWER_RIGHT, + + // U+1fb30 - U+1fb3b + UPPER_LEFT | UPPER_RIGHT | LOWER_LEFT | LOWER_RIGHT, + MIDDLE_LEFT | LOWER_LEFT | LOWER_RIGHT, + UPPER_LEFT | MIDDLE_LEFT | LOWER_LEFT | LOWER_RIGHT, + UPPER_RIGHT | MIDDLE_LEFT | LOWER_LEFT | LOWER_RIGHT, + UPPER_LEFT | UPPER_RIGHT | MIDDLE_LEFT | LOWER_LEFT | LOWER_RIGHT, + MIDDLE_RIGHT | LOWER_LEFT | LOWER_RIGHT, + UPPER_LEFT | MIDDLE_RIGHT | LOWER_LEFT | LOWER_RIGHT, + UPPER_RIGHT | MIDDLE_RIGHT | LOWER_LEFT | LOWER_RIGHT, + UPPER_LEFT | UPPER_RIGHT | MIDDLE_RIGHT | LOWER_LEFT | LOWER_RIGHT, + MIDDLE_LEFT | MIDDLE_RIGHT | LOWER_LEFT | LOWER_RIGHT, + UPPER_LEFT | MIDDLE_LEFT | MIDDLE_RIGHT | LOWER_LEFT | LOWER_RIGHT, + UPPER_RIGHT | MIDDLE_LEFT | MIDDLE_RIGHT | LOWER_LEFT | LOWER_RIGHT, + }; + + assert(cp >= 0x1fb00 and cp <= 0x1fb3b); + const idx = cp - 0x1fb00; + const encoded = matrix[idx]; + + const x_halfs = self.xHalfs(); + const y_thirds = self.yThirds(); + + if (encoded & UPPER_LEFT > 0) self.rect(img, 0, 0, x_halfs[0], y_thirds[0]); + if (encoded & MIDDLE_LEFT > 0) self.rect(img, 0, y_thirds[0], x_halfs[0], y_thirds[1]); + if (encoded & LOWER_LEFT > 0) self.rect(img, 0, y_thirds[1], x_halfs[0], self.height); + if (encoded & UPPER_RIGHT > 0) self.rect(img, x_halfs[1], 0, self.width, y_thirds[0]); + if (encoded & MIDDLE_RIGHT > 0) self.rect(img, x_halfs[1], y_thirds[0], self.width, y_thirds[1]); + if (encoded & LOWER_RIGHT > 0) self.rect(img, x_halfs[1], y_thirds[1], self.width, self.height); +} + +fn xHalfs(self: BoxFont) [2]u32 { + return .{ + @floatToInt(u32, @round(@intToFloat(f64, self.width) / 2)), + @floatToInt(u32, @intToFloat(f64, self.width) / 2), + }; +} + +fn yThirds(self: BoxFont) [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_wedge_triangle(self: BoxFont, img: *pixman.Image, cp: u32) !void { + const width = self.width; + const height = self.height; + + const x_halfs = self.xHalfs(); + const y_thirds = self.yThirds(); + const halfs0 = x_halfs[0]; + const halfs1 = x_halfs[1]; + const thirds0 = y_thirds[0]; + const thirds1 = y_thirds[1]; + + var p1_x: u32 = 0; + var p2_x: u32 = 0; + var p3_x: u32 = 0; + var p1_y: u32 = 0; + var p2_y: u32 = 0; + var p3_y: u32 = 0; + + switch (cp) { + 0x1fb3c => { + p3_x = halfs0; + p1_y = thirds1; + p2_y = height; + p3_y = height; + }, + + 0x1fb52 => { + p3_x = halfs0; + p1_y = thirds1; + p2_y = height; + p3_y = height; + }, + + 0x1fb3d => { + p3_x = width; + p1_y = thirds1; + p2_y = height; + p3_y = height; + }, + + 0x1fb53 => { + p3_x = width; + p1_y = thirds1; + p2_y = height; + p3_y = height; + }, + + 0x1fb3e => { + p3_x = halfs0; + p1_y = thirds0; + p2_y = height; + p3_y = height; + }, + + 0x1fb54 => { + p3_x = halfs0; + p1_y = thirds0; + p2_y = height; + p3_y = height; + }, + + 0x1fb3f => { + p3_x = width; + p1_y = thirds0; + p2_y = height; + p3_y = height; + }, + + 0x1fb55 => { + p3_x = width; + p1_y = thirds0; + p2_y = height; + p3_y = height; + }, + + 0x1fb40, 0x1fb56 => { + p3_x = halfs0; + p1_y = 0; + p2_y = height; + p3_y = height; + }, + + 0x1fb47 => { + p1_x = width; + p2_x = width; + p3_x = halfs1; + p1_y = thirds1; + p2_y = height; + p3_y = height; + }, + + 0x1fb5d => { + p1_x = width; + p2_x = width; + p3_x = halfs1; + p1_y = thirds1; + p2_y = height; + p3_y = height; + }, + + 0x1fb48 => { + p1_x = width; + p2_x = width; + p3_x = 0; + p1_y = thirds1; + p2_y = height; + p3_y = height; + }, + + 0x1fb5e => { + p1_x = width; + p2_x = width; + p3_x = 0; + p1_y = thirds1; + p2_y = height; + p3_y = height; + }, + + 0x1fb49 => { + p1_x = width; + p2_x = width; + p3_x = halfs1; + p1_y = thirds0; + p2_y = height; + p3_y = height; + }, + + 0x1fb5f => { + p1_x = width; + p2_x = width; + p3_x = halfs1; + p1_y = thirds0; + p2_y = height; + p3_y = height; + }, + + 0x1fb4a => { + p1_x = width; + p2_x = width; + p3_x = 0; + p1_y = thirds0; + p2_y = height; + p3_y = height; + }, + + 0x1fb60 => { + p1_x = width; + p2_x = width; + p3_x = 0; + p1_y = thirds0; + p2_y = height; + p3_y = height; + }, + + 0x1fb4b, 0x1fb61 => { + p1_x = width; + p2_x = width; + p3_x = halfs1; + p1_y = 0; + p2_y = height; + p3_y = height; + }, + + 0x1fb57 => { + p3_x = halfs0; + p2_y = thirds0; + }, + + 0x1fb41 => { + p3_x = halfs0; + p2_y = thirds0; + }, + + 0x1fb58 => { + p3_x = width; + p2_y = thirds0; + }, + + 0x1fb42 => { + p3_x = width; + p2_y = thirds0; + }, + + 0x1fb59 => { + p3_x = halfs0; + p2_y = thirds1; + }, + + 0x1fb43 => { + p3_x = halfs0; + p2_y = thirds1; + }, + + 0x1fb5a => { + p3_x = width; + p2_y = thirds1; + }, + + 0x1fb44 => { + p3_x = width; + p2_y = thirds1; + }, + + 0x1fb5b, 0x1fb45 => { + p3_x = halfs0; + p2_y = height; + }, + + 0x1fb62 => { + p1_x = width; + p2_x = width; + p3_x = halfs1; + p2_y = thirds0; + }, + + 0x1fb4c => { + p1_x = width; + p2_x = width; + p3_x = halfs1; + p2_y = thirds0; + }, + + 0x1fb63 => { + p1_x = width; + p2_x = width; + p3_x = 0; + p2_y = thirds0; + }, + + 0x1fb4d => { + p1_x = width; + p2_x = width; + p3_x = 0; + p2_y = thirds0; + }, + + 0x1fb64 => { + p1_x = width; + p2_x = width; + p3_x = halfs1; + p2_y = thirds1; + }, + + 0x1fb4e => { + p1_x = width; + p2_x = width; + p3_x = halfs1; + p2_y = thirds1; + }, + + 0x1fb65 => { + p1_x = width; + p2_x = width; + p3_x = 0; + p2_y = thirds1; + }, + + 0x1fb4f => { + p1_x = width; + p2_x = width; + p3_x = 0; + p2_y = thirds1; + }, + + 0x1fb66, 0x1fb50 => { + p1_x = width; + p2_x = width; + p3_x = halfs1; + p2_y = height; + }, + + 0x1fb46 => { + p1_x = 0; + p1_y = thirds1; + p2_x = width; + p2_y = thirds0; + p3_x = width; + p3_y = p1_y; + }, + + 0x1fb51 => { + p1_x = 0; + p1_y = thirds0; + p2_x = 0; + p2_y = thirds1; + p3_x = width; + p3_y = p2_y; + }, + + 0x1fb5c => { + p1_x = 0; + p1_y = thirds0; + p2_x = 0; + p2_y = thirds1; + p3_x = width; + p3_y = p1_y; + }, + + 0x1fb67 => { + p1_x = 0; + p1_y = thirds0; + p2_x = width; + p2_y = p1_y; + p3_x = width; + p3_y = thirds1; + }, + + 0x1fb6c, 0x1fb68 => { + p1_x = 0; + p1_y = 0; + p2_x = halfs0; + p2_y = height / 2; + p3_x = 0; + p3_y = height; + }, + + 0x1fb6d, 0x1fb69 => { + p1_x = 0; + p1_y = 0; + p2_x = halfs1; + p2_y = height / 2; + p3_x = width; + p3_y = 0; + }, + + 0x1fb6e, 0x1fb6a => { + p1_x = width; + p1_y = 0; + p2_x = halfs1; + p2_y = height / 2; + p3_x = width; + p3_y = height; + }, + + 0x1fb6f, 0x1fb6b => { + p1_x = 0; + p1_y = height; + p2_x = halfs1; + p2_y = height / 2; + p3_x = width; + p3_y = height; + }, + + else => unreachable, + } + + const tris = &[_]pixman.Triangle{ + .{ + .p1 = .{ .x = pixman.Fixed.init(p1_x), .y = pixman.Fixed.init(p1_y) }, + .p2 = .{ .x = pixman.Fixed.init(p2_x), .y = pixman.Fixed.init(p2_y) }, + .p3 = .{ .x = pixman.Fixed.init(p3_x), .y = pixman.Fixed.init(p3_y) }, + }, + }; + + const src = try pixman.Image.createSolidFill(white); + defer _ = src.unref(); + img.compositeTriangles(.over, src, .a8, 0, 0, 0, 0, tris); +} + +fn draw_wedge_triangle_inverted(self: BoxFont, img: *pixman.Image, cp: u32) !void { + try self.draw_wedge_triangle(img, cp); + + const src = try pixman.Image.createSolidFill(white); + defer _ = src.unref(); + img.composite( + .out, + src, + null, + 0, + 0, + 0, + 0, + 0, + 0, + @intCast(u16, self.width), + @intCast(u16, self.height), + ); +} + +fn draw_wedge_triangle_and_box(self: BoxFont, img: *pixman.Image, cp: u32) !void { + try self.draw_wedge_triangle(img, cp); + + const y_thirds = self.yThirds(); + const box: pixman.Box32 = switch (cp) { + 0x1fb46, 0x1fb51 => .{ + .x1 = 0, + .y1 = @intCast(i32, y_thirds[1]), + .x2 = @intCast(i32, self.width), + .y2 = @intCast(i32, self.height), + }, + + 0x1fb5c, 0x1fb67 => .{ + .x1 = 0, + .y1 = 0, + .x2 = @intCast(i32, self.width), + .y2 = @intCast(i32, y_thirds[0]), + }, + + else => unreachable, + }; + + const boxes = &[_]pixman.Box32{box}; + img.fillBoxes(.src, white, boxes) catch {}; +} + +fn draw_light_arc( + self: BoxFont, + alloc: Allocator, + img: *pixman.Image, + cp: u32, +) !void { + const supersample = 4; + const height = self.height * supersample; + const width = self.width * supersample; + const stride = pixman.FormatCode.a8.strideForWidth(width); + + // Allocate our buffer + var data = try alloc.alloc(u8, height * @intCast(u32, stride)); + defer alloc.free(data); + std.mem.set(u8, data, 0); + + const height_pixels = self.height; + const width_pixels = self.width; + const thick_pixels = Thickness.light.height(self.thickness); + const thick = thick_pixels * supersample; + + const circle_inner_edge = (@min(width_pixels, height_pixels) - thick_pixels) / 2; + + // We want to draw the quartercircle by filling small circles (with r = + // thickness/2.) whose centers are on its edge. This means to get the + // radius of the quartercircle, we add the exact half thickness to the + // radius of the inner circle. + var c_r: f64 = @intToFloat(f64, circle_inner_edge) + @intToFloat(f64, thick_pixels) / 2; + + // We need to draw short lines from the end of the quartercircle to the + // box-edges, store one endpoint (the other is the edge of the + // quartercircle) in these vars. + var vert_to: u32 = 0; + var hor_to: u32 = 0; + + // Coordinates of the circle-center. + var c_x: u32 = 0; + var c_y: u32 = 0; + + // For a given y there are up to two solutions for the circle-equation. + // Set to -1 for the left, and 1 for the right hemisphere. + var circle_hemisphere: i32 = 0; + + // The quarter circle only has to be evaluated for a small range of + // y-values. + var y_min: u32 = 0; + var y_max: u32 = 0; + + switch (cp) { + '╭' => { + // Don't use supersampled coordinates yet, we want to align actual + // pixels. + // + // pixel-coordinates of the lower edge of the right line and the + // right edge of the bottom line. + const right_bottom_edge = (height_pixels + thick_pixels) / 2; + const bottom_right_edge = (width_pixels + thick_pixels) / 2; + + // find coordinates of circle-center. + c_y = right_bottom_edge + circle_inner_edge; + c_x = bottom_right_edge + circle_inner_edge; + + // we want to render the left, not the right hemisphere of the circle. + circle_hemisphere = -1; + + // don't evaluate beyond c_y, the vertical line is drawn there. + y_min = 0; + y_max = c_y; + + // the vertical line should extend to the bottom of the box, the + // horizontal to the right. + vert_to = height_pixels; + hor_to = width_pixels; + }, + '╮' => { + const left_bottom_edge = (height_pixels + thick_pixels) / 2; + const bottom_left_edge = (width_pixels - thick_pixels) / 2; + + c_y = left_bottom_edge + circle_inner_edge; + c_x = bottom_left_edge - circle_inner_edge; + + circle_hemisphere = 1; + + y_min = 0; + y_max = c_y; + + vert_to = height_pixels; + hor_to = 0; + }, + '╰' => { + const right_top_edge = (height_pixels - thick_pixels) / 2; + const top_right_edge = (width_pixels + thick_pixels) / 2; + + c_y = right_top_edge - circle_inner_edge; + c_x = top_right_edge + circle_inner_edge; + + circle_hemisphere = -1; + + y_min = c_y; + y_max = height_pixels; + + vert_to = 0; + hor_to = width_pixels; + }, + '╯' => { + const left_top_edge = (height_pixels - thick_pixels) / 2; + const top_left_edge = (width_pixels - thick_pixels) / 2; + + c_y = left_top_edge - circle_inner_edge; + c_x = top_left_edge - circle_inner_edge; + + circle_hemisphere = 1; + + y_min = c_y; + y_max = height_pixels; + + vert_to = 0; + hor_to = 0; + }, + + else => {}, + } + + // store for horizontal+vertical line. + const c_x_pixels = c_x; + const c_y_pixels = c_y; + + // Bring coordinates from pixel-grid to supersampled grid. + c_r *= supersample; + c_x *= supersample; + c_y *= supersample; + + y_min *= supersample; + y_max *= supersample; + + const c_r2 = c_r * c_r; + + // To prevent gaps in the circle, each pixel is sampled multiple times. + // As the quartercircle ends (vertically) in the middle of a pixel, an + // uneven number helps hit that exactly. + { + var i: f64 = @intToFloat(f64, y_min) * 16; + while (i <= @intToFloat(f64, y_max) * 16) : (i += 1) { + const y = i / 16; + const x = x: { + // circle_hemisphere * sqrt(c_r2 - (y - c_y) * (y - c_y)) + c_x; + const hemi = @intToFloat(f64, circle_hemisphere); + const y_part = y - @intToFloat(f64, c_y); + const y_squared = y_part * y_part; + const sqrt = @sqrt(c_r2 - y_squared); + const f_c_x = @intToFloat(f64, c_x); + + // We need to detect overflows and just skip this i + const a = hemi * sqrt; + const b = a + f_c_x; + + // If the float math didn't work, ignore. + if (std.math.isNan(b)) continue; + + break :x b; + }; + + const row = @floatToInt(i32, @round(y)); + const col = @floatToInt(i32, @round(x)); + if (col < 0) continue; + + // rectangle big enough to fit entire circle with radius thick/2. + const row1 = row - @intCast(i32, thick / 2 + 1); + const row2 = row + @intCast(i32, thick / 2 + 1); + const col1 = col - @intCast(i32, thick / 2 + 1); + const col2 = col + @intCast(i32, thick / 2 + 1); + + const row_start = @min(row1, row2); + const row_end = @max(row1, row2); + const col_start = @min(col1, col2); + const col_end = @max(col1, col2); + + assert(row_end > row_start); + assert(col_end > col_start); + + // draw circle with radius thick/2 around x,y. + // this is accomplished by rejecting pixels where the distance from + // their center to x,y is greater than thick/2. + var r: i32 = @max(row_start, 0); + const r_end = @max(@min(row_end, @intCast(i32, height)), 0); + while (r < r_end) : (r += 1) { + const r_midpoint = @intToFloat(f64, r) + 0.5; + + var c: i32 = @max(col_start, 0); + const c_end = @max(@min(col_end, @intCast(i32, width)), 0); + while (c < c_end) : (c += 1) { + const c_midpoint = @intToFloat(f64, c) + 0.5; + + // vector from point on quartercircle to midpoint of the current pixel. + const center_midpoint_x = c_midpoint - x; + const center_midpoint_y = r_midpoint - y; + + // distance from current point to circle-center. + const dist = @sqrt(center_midpoint_x * center_midpoint_x + center_midpoint_y * center_midpoint_y); + // skip if midpoint of pixel is outside the circle. + if (dist > @intToFloat(f64, thick) / 2) continue; + + const idx = @intCast(usize, r * stride + c); + data[idx] = 0xff; + } + } + } + } + + // Downsample + { + // We want to convert our []u32 to []u8 since we use an 8bpp format + var data_u32 = img.getData(); + const len_u8 = data_u32.len * 4; + var real_data = @alignCast(@alignOf(u8), @ptrCast([*]u8, data_u32.ptr)[0..len_u8]); + const real_stride = img.getStride(); + + var r: u32 = 0; + while (r < self.height) : (r += 1) { + var c: u32 = 0; + while (c < self.width) : (c += 1) { + var total: u32 = 0; + var i: usize = 0; + while (i < supersample) : (i += 1) { + var j: usize = 0; + while (j < supersample) : (j += 1) { + const idx = (r * supersample + i) * @intCast(usize, stride) + c * supersample + j; + total += data[idx]; + } + } + + const average = @intCast(u8, @min(total / (supersample * supersample), 0xff)); + const idx = r * @intCast(usize, real_stride) + c; + real_data[idx] = average; + } + } + } + + // draw vertical/horizontal lines from quartercircle-edge to box-edge. + self.vline(img, @min(c_y_pixels, vert_to), @max(c_y_pixels, vert_to), (width_pixels - thick_pixels) / 2, thick_pixels); + self.hline(img, @min(c_x_pixels, hor_to), @max(c_x_pixels, hor_to), (height_pixels - thick_pixels) / 2, thick_pixels); +} + +fn draw_dash_horizontal( + self: BoxFont, + img: *pixman.Image, + count: u8, + thick_px: u32, + gap: u32, +) void { + assert(count >= 2 and count <= 4); + + // The number of gaps we have is one less than the number of dashes. + // "- - -" => 2 gaps + const gap_count = count - 1; + + // Determine the width of our dashes + const dash_width = dash_width: { + var gap_i = gap; + var dash_width = (self.width - (gap_count * gap_i)) / count; + while (dash_width <= 0 and gap_i > 1) { + gap_i -= 1; + dash_width = (self.width - (gap_count * gap_i)) / count; + } + + // If we can't fit any dashes then we just render a horizontal line. + if (dash_width <= 0) { + self.hline_middle(img, .light); + return; + } + + break :dash_width dash_width; + }; + + // Our total width should be less than our real width + assert(count * dash_width + gap_count * gap <= self.width); + const remaining = self.width - count * dash_width - gap_count * gap; + + var x: [4]u32 = .{0} ** 4; + var w: [4]u32 = .{dash_width} ** 4; + x[1] = x[0] + w[0] + gap; + if (count == 2) + w[1] = self.width - x[1] + else if (count == 3) + w[1] += remaining + else + w[1] += remaining / 2; + + if (count >= 3) { + x[2] = x[1] + w[1] + gap; + if (count == 3) + w[2] = self.width - x[2] + else + w[2] += remaining - remaining / 2; + } + + if (count >= 4) { + x[3] = x[2] + w[2] + gap; + w[3] = self.width - x[3]; + } + + self.hline(img, x[0], x[0] + w[0], (self.height - thick_px) / 2, thick_px); + self.hline(img, x[1], x[1] + w[1], (self.height - thick_px) / 2, thick_px); + if (count >= 3) + self.hline(img, x[2], x[2] + w[2], (self.height - thick_px) / 2, thick_px); + if (count >= 4) + self.hline(img, x[3], x[3] + w[3], (self.height - thick_px) / 2, thick_px); +} + +fn draw_dash_vertical( + self: BoxFont, + img: *pixman.Image, + count: u8, + thick_px: u32, + gap: u32, +) void { + assert(count >= 2 and count <= 4); + + // The number of gaps we have is one less than the number of dashes. + // "- - -" => 2 gaps + const gap_count = count - 1; + + // Determine the height of our dashes + const dash_height = dash_height: { + var gap_i = gap; + var dash_height = (self.height - (gap_count * gap_i)) / count; + while (dash_height <= 0 and gap_i > 1) { + gap_i -= 1; + dash_height = (self.height - (gap_count * gap_i)) / count; + } + + // If we can't fit any dashes then we just render a horizontal line. + if (dash_height <= 0) { + self.vline_middle(img, .light); + return; + } + + break :dash_height dash_height; + }; + + // Our total height should be less than our real height + assert(count * dash_height + gap_count * gap <= self.height); + const remaining = self.height - count * dash_height - gap_count * gap; + + var y: [4]u32 = .{0} ** 4; + var h: [4]u32 = .{dash_height} ** 4; + y[1] = y[0] + h[0] + gap; + if (count == 2) + h[1] = self.height - y[1] + else if (count == 3) + h[1] += remaining + else + h[1] += remaining / 2; + + if (count >= 3) { + y[2] = y[1] + h[1] + gap; + if (count == 3) + h[2] = self.height - y[2] + else + h[2] += remaining - remaining / 2; + } + + if (count >= 4) { + y[3] = y[2] + h[2] + gap; + h[3] = self.height - y[3]; + } + + self.vline(img, y[0], y[0] + h[0], (self.width - thick_px) / 2, thick_px); + self.vline(img, y[1], y[1] + h[1], (self.width - thick_px) / 2, thick_px); + if (count >= 3) + self.vline(img, y[2], y[2] + h[2], (self.width - thick_px) / 2, thick_px); + if (count >= 4) + self.vline(img, y[3], y[3] + h[3], (self.width - thick_px) / 2, thick_px); +} + +fn vline_middle(self: BoxFont, img: *pixman.Image, thickness: Thickness) void { + const thick_px = thickness.height(self.thickness); + self.vline(img, 0, self.height, (self.width - thick_px) / 2, thick_px); +} + +fn vline_middle_up( + self: BoxFont, + img: *pixman.Image, + vthickness: Thickness, + hthickness: Thickness, +) void { + const hthick_px = hthickness.height(self.thickness); + const vthick_px = vthickness.height(self.thickness); + self.vline( + img, + 0, + (self.height + hthick_px) / 2, + (self.width - vthick_px) / 2, + vthick_px, + ); +} + +fn vline_middle_down( + self: BoxFont, + img: *pixman.Image, + vthickness: Thickness, + hthickness: Thickness, +) void { + const hthick_px = hthickness.height(self.thickness); + const vthick_px = vthickness.height(self.thickness); + self.vline( + img, + (self.height - hthick_px) / 2, + self.height, + (self.width - vthick_px) / 2, + vthick_px, + ); +} + +fn hline_middle(self: BoxFont, img: *pixman.Image, thickness: Thickness) void { + const thick_px = thickness.height(self.thickness); + self.hline(img, 0, self.width, (self.height - thick_px) / 2, thick_px); +} + +fn hline_middle_left( + self: BoxFont, + img: *pixman.Image, + vthickness: Thickness, + hthickness: Thickness, +) void { + const hthick_px = hthickness.height(self.thickness); + const vthick_px = vthickness.height(self.thickness); + self.hline( + img, + 0, + (self.width + vthick_px) / 2, + (self.height - hthick_px) / 2, + hthick_px, + ); +} + +fn hline_middle_right( + self: BoxFont, + img: *pixman.Image, + vthickness: Thickness, + hthickness: Thickness, +) void { + const hthick_px = hthickness.height(self.thickness); + const vthick_px = vthickness.height(self.thickness); + self.hline( + img, + (self.width - vthick_px) / 2, + self.width, + (self.height - hthick_px) / 2, + hthick_px, + ); +} + +fn vline( + self: BoxFont, + img: *pixman.Image, + y1: u32, + y2: u32, + x: u32, + thickness_px: u32, +) void { + const boxes = &[_]pixman.Box32{ + .{ + .x1 = @intCast(i32, @min(@max(x, 0), self.width)), + .x2 = @intCast(i32, @min(@max(x + thickness_px, 0), self.width)), + .y1 = @intCast(i32, @min(@max(y1, 0), self.height)), + .y2 = @intCast(i32, @min(@max(y2, 0), self.height)), + }, + }; + + img.fillBoxes(.src, white, boxes) catch {}; +} + +fn hline( + self: BoxFont, + img: *pixman.Image, + x1: u32, + x2: u32, + y: u32, + thickness_px: u32, +) void { + const boxes = &[_]pixman.Box32{ + .{ + .x1 = @intCast(i32, @min(@max(x1, 0), self.width)), + .x2 = @intCast(i32, @min(@max(x2, 0), self.width)), + .y1 = @intCast(i32, @min(@max(y, 0), self.height)), + .y2 = @intCast(i32, @min(@max(y + thickness_px, 0), self.height)), + }, + }; + + img.fillBoxes(.src, white, boxes) catch {}; +} + +fn rect( + self: BoxFont, + img: *pixman.Image, + x1: u32, + y1: u32, + x2: u32, + y2: u32, +) void { + const boxes = &[_]pixman.Box32{ + .{ + .x1 = @intCast(i32, @min(@max(x1, 0), self.width)), + .y1 = @intCast(i32, @min(@max(y1, 0), self.height)), + .x2 = @intCast(i32, @min(@max(x2, 0), self.width)), + .y2 = @intCast(i32, @min(@max(y2, 0), self.height)), + }, + }; + + img.fillBoxes(.src, white, boxes) catch {}; +} + +test "all" { + const testing = std.testing; + const alloc = testing.allocator; + + var cp: u32 = 0x2500; + const end = 0x2570; + while (cp <= end) : (cp += 1) { + var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale); + defer atlas_greyscale.deinit(alloc); + + const face: BoxFont = .{ .width = 18, .height = 36, .thickness = 2 }; + const glyph = try face.renderGlyph( + alloc, + &atlas_greyscale, + cp, + ); + try testing.expectEqual(@as(u32, face.width), glyph.width); + try testing.expectEqual(@as(u32, face.height), glyph.height); + } +} diff --git a/src/font/Group.zig b/src/font/Group.zig index 1d1f168fc..935af8db1 100644 --- a/src/font/Group.zig +++ b/src/font/Group.zig @@ -4,6 +4,10 @@ //! a codepoint doesn't map cleanly. For example, if a user requests a bold //! char and it doesn't exist we can fallback to a regular non-bold char so //! we show SOMETHING. +//! +//! Note this is made specifically for terminals so it has some features +//! that aren't generally helpful, such as detecting and drawing the terminal +//! box glyphs and requiring cell sizes for such glyphs. const Group = @This(); const std = @import("std"); @@ -43,9 +47,14 @@ size: font.face.DesiredSize, faces: StyleArray, /// If discovery is available, we'll look up fonts where we can't find -/// the codepoint. +/// the codepoint. This can be set after initialization. discover: ?font.Discover = null, +/// Set this to a non-null value to enable box font glyph drawing. If this +/// isn't enabled we'll just fall through to trying to use regular fonts +/// to render box glyphs. +box_font: ?font.BoxFont = null, + pub fn init( alloc: Allocator, lib: Library, @@ -82,7 +91,12 @@ pub fn deinit(self: *Group) void { /// The group takes ownership of the face. The face will be deallocated when /// the group is deallocated. pub fn addFace(self: *Group, alloc: Allocator, style: Style, face: DeferredFace) !void { - try self.faces.getPtr(style).append(alloc, face); + const list = self.faces.getPtr(style); + + // We have some special indexes so we must never pass those. + if (list.items.len >= FontIndex.Special.start - 1) return error.GroupFull; + + try list.append(alloc, face); } /// Resize the fonts to the desired size. @@ -110,14 +124,36 @@ pub const FontIndex = packed struct { const idx_bits = 8 - @typeInfo(@typeInfo(Style).Enum.tag_type).Int.bits; pub const IndexInt = @Type(.{ .Int = .{ .signedness = .unsigned, .bits = idx_bits } }); + /// The special-case fonts that we support. + pub const Special = enum(IndexInt) { + // We start all special fonts at this index so they can be detected. + pub const start = std.math.maxInt(IndexInt); + + /// Box drawing, this is rendered JIT using 2D graphics APIs. + box = start, + }; + style: Style = .regular, idx: IndexInt = 0, + /// Initialize a special font index. + pub fn initSpecial(v: Special) FontIndex { + return .{ .style = .regular, .idx = @enumToInt(v) }; + } + /// Convert to int pub fn int(self: FontIndex) u8 { return @bitCast(u8, self); } + /// Returns true if this is a "special" index which doesn't map to + /// a real font face. We can still render it but there is no face for + /// this font. + pub fn special(self: FontIndex) ?Special { + if (self.idx < Special.start) return null; + return @intToEnum(Special, self.idx); + } + test { // We never want to take up more than a byte since font indexes are // everywhere so if we increase the size of this we'll dramatically @@ -142,6 +178,53 @@ pub fn indexForCodepoint( style: Style, p: ?Presentation, ) ?FontIndex { + // If this is a box drawing glyph, we use the special font index. This + // will force special logic where we'll render this ourselves. If we don't + // have a box font set, then we just try to use regular fonts. + if (self.box_font != null) { + if (switch (cp) { + // "Box Drawing" block + 0x2500...0x257F => true, + + // "Block Elements" block + 0x2580...0x259f => true, + + // "Braille" block + 0x2800...0x28FF => true, + + // "Symbols for Legacy Computing" block + 0x1FB00...0x1FB3B => true, + + 0x1FB3C...0x1FB40, + 0x1FB47...0x1FB4B, + 0x1FB57...0x1FB5B, + 0x1FB62...0x1FB66, + 0x1FB6C...0x1FB6F, + => true, + + 0x1FB41...0x1FB45, + 0x1FB4C...0x1FB50, + 0x1FB52...0x1FB56, + 0x1FB5D...0x1FB61, + 0x1FB68...0x1FB6B, + => true, + + 0x1FB46, + 0x1FB51, + 0x1FB5C, + 0x1FB67, + 0x1FB9A, + 0x1FB9B, + => true, + + 0x1FB70...0x1FB8B => true, + + else => false, + }) { + return FontIndex.initSpecial(.box); + } + } + // If we can find the exact value, then return that. if (self.indexForCodepointExact(cp, style, p)) |value| return value; @@ -188,8 +271,21 @@ fn indexForCodepointExact(self: Group, cp: u32, style: Style, p: ?Presentation) return null; } -/// Return the Face represented by a given FontIndex. +/// Returns the presentation for a specific font index. This is useful for +/// determining what atlas is needed. +pub fn presentationFromIndex(self: Group, index: FontIndex) !font.Presentation { + if (index.special()) |sp| switch (sp) { + .box => return .text, + }; + + const face = try self.faceFromIndex(index); + return face.presentation; +} + +/// Return the Face represented by a given FontIndex. Note that special +/// fonts (i.e. box glyphs) do not have a face. pub fn faceFromIndex(self: Group, index: FontIndex) !Face { + if (index.special() != null) return error.SpecialHasNoFace; const deferred = &self.faces.get(index.style).items[@intCast(usize, index.idx)]; try deferred.load(self.lib, self.size); return deferred.face.?; @@ -214,6 +310,15 @@ pub fn renderGlyph( glyph_index: u32, max_height: ?u16, ) !Glyph { + // Special-case fonts are rendered directly. + if (index.special()) |sp| switch (sp) { + .box => return try self.box_font.?.renderGlyph( + alloc, + atlas, + glyph_index, + ), + }; + const face = &self.faces.get(index.style).items[@intCast(usize, index.idx)]; try face.load(self.lib, self.size); return try face.face.?.renderGlyph(alloc, atlas, glyph_index, max_height); @@ -276,6 +381,43 @@ test { try testing.expectEqual(Style.regular, idx.style); try testing.expectEqual(@as(FontIndex.IndexInt, 1), idx.idx); } + + // Box glyph should be null since we didn't set a box font + { + try testing.expect(group.indexForCodepoint(0x1FB00, .regular, null) == null); + } +} + +test "box glyph" { + const testing = std.testing; + const alloc = testing.allocator; + + var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale); + defer atlas_greyscale.deinit(alloc); + + var lib = try Library.init(); + defer lib.deinit(); + + var group = try init(alloc, lib, .{ .points = 12 }); + defer group.deinit(); + + // Set box font + group.box_font = font.BoxFont{ .width = 18, .height = 36, .thickness = 2 }; + + // Should find a box glyph + const idx = group.indexForCodepoint(0x2500, .regular, null).?; + try testing.expectEqual(Style.regular, idx.style); + try testing.expectEqual(@enumToInt(FontIndex.Special.box), idx.idx); + + // Should render it + const glyph = try group.renderGlyph( + alloc, + &atlas_greyscale, + idx, + 0x2500, + null, + ); + try testing.expectEqual(@as(u32, 36), glyph.height); } test "resize" { diff --git a/src/font/GroupCache.zig b/src/font/GroupCache.zig index 4ed68f6dc..77b488bc8 100644 --- a/src/font/GroupCache.zig +++ b/src/font/GroupCache.zig @@ -132,8 +132,10 @@ pub fn renderGlyph( if (gop.found_existing) return gop.value_ptr.*; // Uncached, render it - const face = try self.group.faceFromIndex(index); - const atlas: *Atlas = if (face.presentation == .emoji) &self.atlas_color else &self.atlas_greyscale; + const atlas: *Atlas = switch (try self.group.presentationFromIndex(index)) { + .text => &self.atlas_greyscale, + .emoji => &self.atlas_color, + }; const glyph = self.group.renderGlyph( alloc, atlas, diff --git a/src/font/Shaper.zig b/src/font/Shaper.zig index dde0a5235..9f2877b2a 100644 --- a/src/font/Shaper.zig +++ b/src/font/Shaper.zig @@ -7,6 +7,7 @@ const Allocator = std.mem.Allocator; const harfbuzz = @import("harfbuzz"); const trace = @import("tracy").trace; const Atlas = @import("../Atlas.zig"); +const font = @import("main.zig"); const Face = @import("main.zig").Face; const DeferredFace = @import("main.zig").DeferredFace; const Group = @import("main.zig").Group; @@ -56,14 +57,18 @@ pub fn shape(self: *Shaper, run: TextRun) ![]Cell { const tracy = trace(@src()); defer tracy.end(); - // TODO: we do not want to hardcode these - const hb_feats = &[_]harfbuzz.Feature{ - harfbuzz.Feature.fromString("dlig").?, - harfbuzz.Feature.fromString("liga").?, - }; + // We only do shaping if the font is not a special-case. For special-case + // fonts, the codepoint == glyph_index so we don't need to run any shaping. + if (run.font_index.special() == null) { + // TODO: we do not want to hardcode these + const hb_feats = &[_]harfbuzz.Feature{ + harfbuzz.Feature.fromString("dlig").?, + harfbuzz.Feature.fromString("liga").?, + }; - const face = try run.group.group.faceFromIndex(run.font_index); - harfbuzz.shape(face.hb_font, self.hb_buf, hb_feats); + const face = try run.group.group.faceFromIndex(run.font_index); + harfbuzz.shape(face.hb_font, self.hb_buf, hb_feats); + } // If our buffer is empty, we short-circuit the rest of the work // return nothing. @@ -569,6 +574,47 @@ test "shape Chinese characters" { try testing.expectEqual(@as(usize, 1), count); } +test "shape box glyphs" { + const testing = std.testing; + const alloc = testing.allocator; + + var testdata = try testShaper(alloc); + defer testdata.deinit(); + + // Setup the box font + testdata.cache.group.box_font = font.BoxFont{ + .width = 18, + .height = 36, + .thickness = 2, + }; + + var buf: [32]u8 = undefined; + var buf_idx: usize = 0; + buf_idx += try std.unicode.utf8Encode(0x2500, buf[buf_idx..]); // horiz line + buf_idx += try std.unicode.utf8Encode(0x2501, buf[buf_idx..]); // + + // Make a screen with some data + var screen = try terminal.Screen.init(alloc, 3, 10, 0); + defer screen.deinit(); + try screen.testWriteString(buf[0..buf_idx]); + + // Get our run iterator + var shaper = testdata.shaper; + var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 })); + var count: usize = 0; + while (try it.next(alloc)) |run| { + count += 1; + try testing.expectEqual(@as(u32, 2), shaper.hb_buf.getLength()); + const cells = try shaper.shape(run); + try testing.expectEqual(@as(usize, 2), cells.len); + try testing.expectEqual(@as(u32, 0x2500), cells[0].glyph_index); + try testing.expectEqual(@as(u16, 0), cells[0].x); + try testing.expectEqual(@as(u32, 0x2501), cells[1].glyph_index); + try testing.expectEqual(@as(u16, 1), cells[1].x); + } + try testing.expectEqual(@as(usize, 1), count); +} + const TestShaper = struct { alloc: Allocator, shaper: Shaper, diff --git a/src/font/main.zig b/src/font/main.zig index 69e6a9431..ed21e2f09 100644 --- a/src/font/main.zig +++ b/src/font/main.zig @@ -1,6 +1,7 @@ const std = @import("std"); const build_options = @import("build_options"); +pub const BoxFont = @import("BoxFont.zig"); pub const discovery = @import("discovery.zig"); pub const face = @import("face.zig"); pub const DeferredFace = @import("DeferredFace.zig"); diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 0868218db..9381d70c9 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -408,6 +408,12 @@ pub fn setFontSize(self: *Metal, size: font.face.DesiredSize) !void { if (std.meta.eql(self.cell_size, new_cell_size)) return; self.cell_size = new_cell_size; + // Set the cell size of the box font + if (self.font_group.group.box_font) |*box| { + box.width = @floatToInt(u32, self.cell_size.width); + box.height = @floatToInt(u32, self.cell_size.height); + } + // Notify the window that the cell size changed. _ = self.window_mailbox.push(.{ .cell_size = new_cell_size, @@ -766,7 +772,7 @@ fn rebuildCells( var iter = self.font_shaper.runIterator(self.font_group, row); while (try iter.next(self.alloc)) |run| { for (try self.font_shaper.shape(run)) |shaper_cell| { - assert(try self.updateCell( + if (self.updateCell( term_selection, screen, row.getCell(shaper_cell.x), @@ -774,7 +780,15 @@ fn rebuildCells( run, shaper_cell.x, y, - )); + )) |update| { + assert(update); + } else |err| { + log.warn("error building cell, will be invalid x={} y={}, err={}", .{ + shaper_cell.x, + y, + err, + }); + } } } @@ -880,8 +894,11 @@ pub fn updateCell( ); // If we're rendering a color font, we use the color atlas - const face = try self.font_group.group.faceFromIndex(shaper_run.font_index); - const mode: GPUCellMode = if (face.presentation == .emoji) .fg_color else .fg; + const presentation = try self.font_group.group.presentationFromIndex(shaper_run.font_index); + const mode: GPUCellMode = switch (presentation) { + .text => .fg, + .emoji => .fg_color, + }; self.cells.appendAssumeCapacity(.{ .mode = mode, diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index db274a42e..c43ac006f 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -501,6 +501,12 @@ pub fn setFontSize(self: *OpenGL, size: font.face.DesiredSize) !void { if (std.meta.eql(self.cell_size, new_cell_size)) return; self.cell_size = new_cell_size; + // Set the cell size of the box font + if (self.font_group.group.box_font) |*box| { + box.width = @floatToInt(u32, self.cell_size.width); + box.height = @floatToInt(u32, self.cell_size.height); + } + // Notify the window that the cell size changed. _ = self.window_mailbox.push(.{ .cell_size = new_cell_size, @@ -763,7 +769,7 @@ pub fn rebuildCells( var iter = self.font_shaper.runIterator(self.font_group, row); while (try iter.next(self.alloc)) |run| { for (try self.font_shaper.shape(run)) |shaper_cell| { - assert(try self.updateCell( + if (self.updateCell( term_selection, screen, row.getCell(shaper_cell.x), @@ -771,7 +777,15 @@ pub fn rebuildCells( run, shaper_cell.x, y, - )); + )) |update| { + assert(update); + } else |err| { + log.warn("error building cell, will be invalid x={} y={}, err={}", .{ + shaper_cell.x, + y, + err, + }); + } } } @@ -952,7 +966,6 @@ pub fn updateCell( // If the cell has a character, draw it if (cell.char > 0) { // Render - const face = try self.font_group.group.faceFromIndex(shaper_run.font_index); const glyph = try self.font_group.renderGlyph( self.alloc, shaper_run.font_index, @@ -961,8 +974,11 @@ pub fn updateCell( ); // If we're rendering a color font, we use the color atlas - var mode: GPUCellMode = .fg; - if (face.presentation == .emoji) mode = .fg_color; + const presentation = try self.font_group.group.presentationFromIndex(shaper_run.font_index); + const mode: GPUCellMode = switch (presentation) { + .text => .fg, + .emoji => .fg_color, + }; self.cells.appendAssumeCapacity(.{ .mode = mode, diff --git a/vendor/pixman b/vendor/pixman new file mode 160000 index 000000000..713077d0a --- /dev/null +++ b/vendor/pixman @@ -0,0 +1 @@ +Subproject commit 713077d0a3c310ca1955bc331d46d55d0ae4a72b