Merge pull request #56 from mitchellh/pixman

Procedurally generate and support box-drawing glyphs
This commit is contained in:
Mitchell Hashimoto
2022-11-25 15:39:03 -08:00
committed by GitHub
21 changed files with 3802 additions and 21 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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, .{});

99
conformance/blocks.zig Normal file
View File

@ -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", .{});
}
}
}

View File

@ -31,6 +31,7 @@
, libXi
, libXinerama
, libXrandr
, pixman
, zlib
}:
let
@ -87,6 +88,7 @@ in mkShell rec {
harfbuzz
libpng
libuv
pixman
zlib
libX11

141
pkg/pixman/build.zig Normal file
View File

@ -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",
};

3
pkg/pixman/c.zig Normal file
View File

@ -0,0 +1,3 @@
pub usingnamespace @cImport({
@cInclude("pixman.h");
});

4
pkg/pixman/error.zig Normal file
View File

@ -0,0 +1,4 @@
pub const Error = error{
// Pixman doesn't really have errors so we just have a single error.
PixmanFailure,
};

118
pkg/pixman/format.zig Normal file
View File

@ -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));
}

207
pkg/pixman/image.zig Normal file
View File

@ -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);
}

10
pkg/pixman/main.zig Normal file
View File

@ -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());
}

View File

@ -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 <cworth@cworth.org>
*/
#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__ */

131
pkg/pixman/types.zig Normal file
View File

@ -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());
}

View File

@ -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;

2770
src/font/BoxFont.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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" {

View File

@ -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,

View File

@ -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,

View File

@ -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");

View File

@ -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,

View File

@ -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,

1
vendor/pixman vendored Submodule

@ -0,0 +1 @@
Subproject commit 713077d0a3c310ca1955bc331d46d55d0ae4a72b