mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-27 11:06:31 +00:00
font/sprite: replace pixman with z2d, extend Box coverage
More complete coverage of the Symbols For Legacy Computing block, including characters from Unicode 16.0. Pixman and the web canvas impl for Canvas have been removed in favor of z2d for drawing, since it has a nicer API with more powerful methods, and is in Zig with no specific platform optimizations so should compile to wasm no problem.
This commit is contained in:
@@ -3,6 +3,7 @@ const canvas = @import("sprite/canvas.zig");
|
||||
pub const Face = @import("sprite/Face.zig");
|
||||
|
||||
pub const Box = canvas.Box;
|
||||
pub const Point = canvas.Point;
|
||||
pub const Canvas = canvas.Canvas;
|
||||
pub const Color = canvas.Color;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -188,29 +188,65 @@ const Kind = enum {
|
||||
=> .box,
|
||||
},
|
||||
|
||||
// Box fonts
|
||||
0x2500...0x257F, // "Box Drawing" block
|
||||
0x2580...0x259F, // "Block Elements" block
|
||||
0x2800...0x28FF, // "Braille" block
|
||||
// == Box fonts ==
|
||||
|
||||
0x1FB00...0x1FB3B, // "Symbols for Legacy Computing" block
|
||||
0x1FB3C...0x1FB40,
|
||||
0x1FB47...0x1FB4B,
|
||||
0x1FB57...0x1FB5B,
|
||||
0x1FB62...0x1FB66,
|
||||
0x1FB6C...0x1FB6F,
|
||||
0x1FB41...0x1FB45,
|
||||
0x1FB4C...0x1FB50,
|
||||
0x1FB52...0x1FB56,
|
||||
0x1FB5D...0x1FB61,
|
||||
0x1FB68...0x1FB6B,
|
||||
0x1FB70...0x1FB8B,
|
||||
0x1FB46,
|
||||
0x1FB51,
|
||||
0x1FB5C,
|
||||
0x1FB67,
|
||||
0x1FB9A,
|
||||
0x1FB9B,
|
||||
// "Box Drawing" block
|
||||
// ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏ ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟ ┠
|
||||
// ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯ ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿ ╀ ╁
|
||||
// ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏ ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟ ╠ ╡ ╢
|
||||
// ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯ ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
|
||||
0x2500...0x257F,
|
||||
|
||||
// "Block Elements" block
|
||||
// ▀ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▉ ▊ ▋ ▌ ▍ ▎ ▏ ▐ ░ ▒ ▓ ▔ ▕ ▖ ▗ ▘ ▙ ▚ ▛ ▜ ▝ ▞ ▟
|
||||
0x2580...0x259F,
|
||||
|
||||
// "Braille" block
|
||||
0x2800...0x28FF,
|
||||
|
||||
// "Symbols for Legacy Computing" block
|
||||
// (Block Mosaics / "Sextants")
|
||||
// 🬀 🬁 🬂 🬃 🬄 🬅 🬆 🬇 🬈 🬉 🬊 🬋 🬌 🬍 🬎 🬏 🬐 🬑 🬒 🬓 🬔 🬕 🬖 🬗 🬘 🬙 🬚 🬛 🬜 🬝 🬞 🬟 🬠
|
||||
// 🬡 🬢 🬣 🬤 🬥 🬦 🬧 🬨 🬩 🬪 🬫 🬬 🬭 🬮 🬯 🬰 🬱 🬲 🬳 🬴 🬵 🬶 🬷 🬸 🬹 🬺 🬻
|
||||
// (Smooth Mosaics)
|
||||
// 🬼 🬽 🬾 🬿 🭀 🭁 🭂 🭃 🭄 🭅 🭆
|
||||
// 🭇 🭈 🭉 🭊 🭋 🭌 🭍 🭎 🭏 🭐 🭑
|
||||
// 🭒 🭓 🭔 🭕 🭖 🭗 🭘 🭙 🭚 🭛 🭜
|
||||
// 🭝 🭞 🭟 🭠 🭡 🭢 🭣 🭤 🭥 🭦 🭧
|
||||
// 🭨 🭩 🭪 🭫 🭬 🭭 🭮 🭯
|
||||
// (Block Elements)
|
||||
// 🭰 🭱 🭲 🭳 🭴 🭵 🭶 🭷 🭸 🭹 🭺 🭻
|
||||
// 🭼 🭽 🭾 🭿 🮀 🮁
|
||||
// 🮂 🮃 🮄 🮅 🮆
|
||||
// 🮇 🮈 🮉 🮊 🮋
|
||||
// (Rectangular Shade Characters)
|
||||
// 🮌 🮍 🮎 🮏 🮐 🮑 🮒
|
||||
0x1FB00...0x1FB92,
|
||||
// (Rectangular Shade Characters)
|
||||
// 🮔
|
||||
// (Fill Characters)
|
||||
// 🮕 🮖 🮗
|
||||
// (Diagonal Fill Characters)
|
||||
// 🮘 🮙
|
||||
// (Smooth Mosaics)
|
||||
// 🮚 🮛
|
||||
// (Triangular Shade Characters)
|
||||
// 🮜 🮝 🮞 🮟
|
||||
// (Character Cell Diagonals)
|
||||
// 🮠 🮡 🮢 🮣 🮤 🮥 🮦 🮧 🮨 🮩 🮪 🮫 🮬 🮭 🮮
|
||||
// (Light Solid Line With Stroke)
|
||||
// 🮯
|
||||
0x1FB94...0x1FBAF,
|
||||
// (Negative Terminal Characters)
|
||||
// 🮽 🮾 🮿
|
||||
0x1FBBD...0x1FBBF,
|
||||
// (Block Elements)
|
||||
//
|
||||
// (Character Cell Diagonals)
|
||||
//
|
||||
// (Geometric Shapes)
|
||||
//
|
||||
0x1FBCE...0x1FBEF,
|
||||
=> .box,
|
||||
|
||||
// Powerline fonts
|
||||
|
||||
@@ -11,7 +11,7 @@ const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const font = @import("../main.zig");
|
||||
const Trapezoid = @import("canvas.zig").Trapezoid;
|
||||
const Quad = @import("canvas.zig").Quad;
|
||||
|
||||
const log = std.log.scoped(.powerline_font);
|
||||
|
||||
@@ -176,10 +176,10 @@ fn draw_wedge_triangle(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !v
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
canvas.triangle(.{
|
||||
.p1 = .{ .x = @as(i32, @intCast(p1_x)), .y = @as(i32, @intCast(p1_y)) },
|
||||
.p2 = .{ .x = @as(i32, @intCast(p2_x)), .y = @as(i32, @intCast(p2_y)) },
|
||||
.p3 = .{ .x = @as(i32, @intCast(p3_x)), .y = @as(i32, @intCast(p3_y)) },
|
||||
try canvas.triangle(.{
|
||||
.p0 = .{ .x = @floatFromInt(p1_x), .y = @floatFromInt(p1_y) },
|
||||
.p1 = .{ .x = @floatFromInt(p2_x), .y = @floatFromInt(p2_y) },
|
||||
.p2 = .{ .x = @floatFromInt(p3_x), .y = @floatFromInt(p3_y) },
|
||||
}, .on);
|
||||
}
|
||||
|
||||
@@ -391,8 +391,8 @@ fn draw_half_circle(self: Powerline, alloc: Allocator, canvas: *font.sprite.Canv
|
||||
const average = @as(u8, @intCast(@min(total / (supersample * supersample), 0xFF)));
|
||||
canvas.rect(
|
||||
.{
|
||||
.x = @intCast(c),
|
||||
.y = @intCast(r),
|
||||
.x = @floatFromInt(c),
|
||||
.y = @floatFromInt(r),
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
},
|
||||
@@ -404,110 +404,86 @@ fn draw_half_circle(self: Powerline, alloc: Allocator, canvas: *font.sprite.Canv
|
||||
}
|
||||
|
||||
fn draw_trapezoid_top_bottom(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !void {
|
||||
const t_top: Trapezoid = if (cp == 0xE0D4)
|
||||
const t_top: Quad = if (cp == 0xE0D4)
|
||||
.{
|
||||
.top = 0,
|
||||
.bottom = @intCast(self.height / 2 - self.height / 20),
|
||||
.left = .{
|
||||
.p1 = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
},
|
||||
.p2 = .{
|
||||
.x = @intCast(self.width - self.width / 3),
|
||||
.y = @intCast(self.height / 2 - self.height / 20),
|
||||
},
|
||||
.p0 = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
},
|
||||
.right = .{
|
||||
.p1 = .{
|
||||
.x = @intCast(self.width),
|
||||
.y = 0,
|
||||
},
|
||||
.p2 = .{
|
||||
.x = @intCast(self.width),
|
||||
.y = @intCast(self.height / 2 - self.height / 20),
|
||||
},
|
||||
.p1 = .{
|
||||
.x = @floatFromInt(self.width - self.width / 3),
|
||||
.y = @floatFromInt(self.height / 2 - self.height / 20),
|
||||
},
|
||||
.p2 = .{
|
||||
.x = @floatFromInt(self.width),
|
||||
.y = @floatFromInt(self.height / 2 - self.height / 20),
|
||||
},
|
||||
.p3 = .{
|
||||
.x = @floatFromInt(self.width),
|
||||
.y = 0,
|
||||
},
|
||||
}
|
||||
else
|
||||
.{
|
||||
.top = 0,
|
||||
.bottom = @intCast(self.height / 2 - self.height / 20),
|
||||
.left = .{
|
||||
.p1 = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
},
|
||||
.p2 = .{
|
||||
.x = 0,
|
||||
.y = @intCast(self.height / 2 - self.height / 20),
|
||||
},
|
||||
.p0 = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
},
|
||||
.right = .{
|
||||
.p1 = .{
|
||||
.x = @intCast(self.width),
|
||||
.y = 0,
|
||||
},
|
||||
.p2 = .{
|
||||
.x = @intCast(self.width / 3),
|
||||
.y = @intCast(self.height / 2 - self.height / 20),
|
||||
},
|
||||
.p1 = .{
|
||||
.x = 0,
|
||||
.y = @floatFromInt(self.height / 2 - self.height / 20),
|
||||
},
|
||||
.p2 = .{
|
||||
.x = @floatFromInt(self.width / 3),
|
||||
.y = @floatFromInt(self.height / 2 - self.height / 20),
|
||||
},
|
||||
.p3 = .{
|
||||
.x = @floatFromInt(self.width),
|
||||
.y = 0,
|
||||
},
|
||||
};
|
||||
|
||||
const t_bottom: Trapezoid = if (cp == 0xE0D4)
|
||||
const t_bottom: Quad = if (cp == 0xE0D4)
|
||||
.{
|
||||
.top = @intCast(self.height / 2 + self.height / 20),
|
||||
.bottom = @intCast(self.height),
|
||||
.left = .{
|
||||
.p1 = .{
|
||||
.x = @intCast(self.width - self.width / 3),
|
||||
.y = @intCast(self.height / 2 + self.height / 20),
|
||||
},
|
||||
.p2 = .{
|
||||
.x = 0,
|
||||
.y = @intCast(self.height),
|
||||
},
|
||||
.p0 = .{
|
||||
.x = @floatFromInt(self.width - self.width / 3),
|
||||
.y = @floatFromInt(self.height / 2 + self.height / 20),
|
||||
},
|
||||
.right = .{
|
||||
.p1 = .{
|
||||
.x = @intCast(self.width),
|
||||
.y = @intCast(self.height / 2 + self.height / 20),
|
||||
},
|
||||
.p2 = .{
|
||||
.x = @intCast(self.width),
|
||||
.y = @intCast(self.height),
|
||||
},
|
||||
.p1 = .{
|
||||
.x = 0,
|
||||
.y = @floatFromInt(self.height),
|
||||
},
|
||||
.p2 = .{
|
||||
.x = @floatFromInt(self.width),
|
||||
.y = @floatFromInt(self.height),
|
||||
},
|
||||
.p3 = .{
|
||||
.x = @floatFromInt(self.width),
|
||||
.y = @floatFromInt(self.height / 2 + self.height / 20),
|
||||
},
|
||||
}
|
||||
else
|
||||
.{
|
||||
.top = @intCast(self.height / 2 + self.height / 20),
|
||||
.bottom = @intCast(self.height),
|
||||
.left = .{
|
||||
.p1 = .{
|
||||
.x = 0,
|
||||
.y = @intCast(self.height / 2 + self.height / 20),
|
||||
},
|
||||
.p2 = .{
|
||||
.x = 0,
|
||||
.y = @intCast(self.height),
|
||||
},
|
||||
.p0 = .{
|
||||
.x = 0,
|
||||
.y = @floatFromInt(self.height / 2 + self.height / 20),
|
||||
},
|
||||
.right = .{
|
||||
.p1 = .{
|
||||
.x = @intCast(self.width / 3),
|
||||
.y = @intCast(self.height / 2 + self.height / 20),
|
||||
},
|
||||
.p2 = .{
|
||||
.x = @intCast(self.width),
|
||||
.y = @intCast(self.height),
|
||||
},
|
||||
.p1 = .{
|
||||
.x = 0,
|
||||
.y = @floatFromInt(self.height),
|
||||
},
|
||||
.p2 = .{
|
||||
.x = @floatFromInt(self.width),
|
||||
.y = @floatFromInt(self.height),
|
||||
},
|
||||
.p3 = .{
|
||||
.x = @floatFromInt(self.width / 3),
|
||||
.y = @floatFromInt(self.height / 2 + self.height / 20),
|
||||
},
|
||||
};
|
||||
|
||||
canvas.trapezoid(t_top);
|
||||
canvas.trapezoid(t_bottom);
|
||||
try canvas.quad(t_top, .on);
|
||||
try canvas.quad(t_bottom, .on);
|
||||
}
|
||||
|
||||
test "all" {
|
||||
|
||||
@@ -3,340 +3,88 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const js = @import("zig-js");
|
||||
const pixman = @import("pixman");
|
||||
const z2d = @import("z2d");
|
||||
const font = @import("../main.zig");
|
||||
|
||||
pub const Point = struct {
|
||||
x: i32,
|
||||
y: i32,
|
||||
x: f64,
|
||||
y: f64,
|
||||
};
|
||||
|
||||
pub const Line = struct {
|
||||
p0: Point,
|
||||
p1: Point,
|
||||
p2: Point,
|
||||
};
|
||||
|
||||
pub const Box = struct {
|
||||
x1: i32,
|
||||
y1: i32,
|
||||
x2: i32,
|
||||
y2: i32,
|
||||
p0: Point,
|
||||
p1: Point,
|
||||
|
||||
pub fn rect(self: Box) Rect {
|
||||
const tl_x = @min(self.x1, self.x2);
|
||||
const tl_y = @min(self.y1, self.y2);
|
||||
const br_x = @max(self.x1, self.x2);
|
||||
const br_y = @max(self.y1, self.y2);
|
||||
const tl_x = @min(self.p0.x, self.p1.x);
|
||||
const tl_y = @min(self.p0.y, self.p1.y);
|
||||
const br_x = @max(self.p0.x, self.p1.x);
|
||||
const br_y = @max(self.p0.y, self.p1.y);
|
||||
return .{
|
||||
.x = tl_x,
|
||||
.y = tl_y,
|
||||
.width = @intCast(br_x - tl_x),
|
||||
.height = @intCast(br_y - tl_y),
|
||||
.width = br_x - tl_x,
|
||||
.height = br_y - tl_y,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Rect = struct {
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
x: f64,
|
||||
y: f64,
|
||||
width: f64,
|
||||
height: f64,
|
||||
};
|
||||
|
||||
pub const Triangle = struct {
|
||||
p0: Point,
|
||||
p1: Point,
|
||||
p2: Point,
|
||||
};
|
||||
|
||||
pub const Quad = struct {
|
||||
p0: Point,
|
||||
p1: Point,
|
||||
p2: Point,
|
||||
p3: Point,
|
||||
};
|
||||
|
||||
pub const Trapezoid = struct {
|
||||
top: i32,
|
||||
bottom: i32,
|
||||
left: Line,
|
||||
right: Line,
|
||||
};
|
||||
|
||||
/// We only use alpha-channel so a pixel can only be "on" or "off".
|
||||
pub const Color = enum(u8) {
|
||||
const CSS_BUF_MAX = 24;
|
||||
|
||||
on = 255,
|
||||
off = 0,
|
||||
_,
|
||||
|
||||
fn pixmanColor(self: Color) pixman.Color {
|
||||
// pixman uses u16 for color while our color value is u8 so we
|
||||
// scale it up proportionally.
|
||||
const max = @as(f32, @floatFromInt(std.math.maxInt(u8)));
|
||||
const max_u16 = @as(f32, @floatFromInt(std.math.maxInt(u16)));
|
||||
const unscaled = @as(f32, @floatFromInt(@intFromEnum(self)));
|
||||
const scaled = @as(u16, @intFromFloat((unscaled * max_u16) / max));
|
||||
return .{ .red = 0, .green = 0, .blue = 0, .alpha = scaled };
|
||||
}
|
||||
|
||||
fn cssColor(self: Color, buf: []u8) ![]u8 {
|
||||
return try std.fmt.bufPrint(buf, "rgba(0, 0, 0, {:.2})", .{
|
||||
@as(f32, @floatFromInt(@intFromEnum(self))) / 255,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/// Composition operations that are supported.
|
||||
pub const CompositionOp = enum {
|
||||
// Note: more can be added here as needed.
|
||||
pub const Canvas = struct {
|
||||
/// The underlying z2d surface.
|
||||
sfc: z2d.Surface,
|
||||
|
||||
source_out,
|
||||
|
||||
fn pixmanOp(self: CompositionOp) pixman.Op {
|
||||
return switch (self) {
|
||||
.source_out => .out,
|
||||
};
|
||||
}
|
||||
|
||||
fn jsOp(self: CompositionOp) js.String {
|
||||
return switch (self) {
|
||||
.source_out => js.string("source-out"),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Canvas = switch (font.options.backend) {
|
||||
.web_canvas => WebCanvasImpl,
|
||||
else => PixmanImpl,
|
||||
};
|
||||
|
||||
const WebCanvasImpl = struct {
|
||||
/// The canvas element that is our final image.
|
||||
canvas: js.Object,
|
||||
|
||||
/// Store the dimensions for easy access later.
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
||||
pub fn init(alloc: Allocator, width: u32, height: u32) !WebCanvasImpl {
|
||||
_ = alloc;
|
||||
|
||||
// Create our canvas that we're going to continue to reuse.
|
||||
const doc = try js.global.get(js.Object, "document");
|
||||
defer doc.deinit();
|
||||
const canvas = try doc.call(js.Object, "createElement", .{js.string("canvas")});
|
||||
errdefer canvas.deinit();
|
||||
|
||||
// Set our dimensions.
|
||||
try canvas.set("width", width);
|
||||
try canvas.set("height", height);
|
||||
|
||||
return WebCanvasImpl{
|
||||
.canvas = canvas,
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *WebCanvasImpl, alloc: Allocator) void {
|
||||
_ = alloc;
|
||||
self.canvas.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn pixel(self: *WebCanvasImpl, x: u32, y: u32, color: Color) void {
|
||||
const ctx = self.context(color) catch return;
|
||||
defer ctx.deinit();
|
||||
ctx.call(void, "fillRect", .{ x, y, 1, 1 }) catch return;
|
||||
}
|
||||
|
||||
pub fn rect(self: *WebCanvasImpl, v: Rect, color: Color) void {
|
||||
const ctx = self.context(color) catch return;
|
||||
defer ctx.deinit();
|
||||
ctx.call(void, "fillRect", .{
|
||||
@as(u32, @intCast(v.x)),
|
||||
@as(u32, @intCast(v.y)),
|
||||
v.width,
|
||||
v.height,
|
||||
}) catch return;
|
||||
}
|
||||
|
||||
pub fn trapezoid(self: *WebCanvasImpl, t: Trapezoid) void {
|
||||
const ctx = self.context(.on) catch return;
|
||||
defer ctx.deinit();
|
||||
|
||||
ctx.call(void, "beginPath", .{}) catch return;
|
||||
ctx.call(void, "moveTo", .{ t.left.p1.x, t.left.p1.y }) catch return;
|
||||
ctx.call(void, "lineTo", .{ t.right.p1.x, t.right.p1.y }) catch return;
|
||||
ctx.call(void, "lineTo", .{ t.right.p2.x, t.right.p2.y }) catch return;
|
||||
ctx.call(void, "lineTo", .{ t.left.p2.x, t.left.p2.y }) catch return;
|
||||
ctx.call(void, "fill", .{}) catch return;
|
||||
}
|
||||
|
||||
pub fn triangle(self: *WebCanvasImpl, t: Triangle, color: Color) void {
|
||||
const ctx = self.context(color) catch return;
|
||||
defer ctx.deinit();
|
||||
|
||||
ctx.call(void, "beginPath", .{}) catch return;
|
||||
ctx.call(void, "moveTo", .{ t.p1.x, t.p1.y }) catch return;
|
||||
ctx.call(void, "lineTo", .{ t.p2.x, t.p2.y }) catch return;
|
||||
ctx.call(void, "lineTo", .{ t.p3.x, t.p3.y }) catch return;
|
||||
ctx.call(void, "fill", .{}) catch return;
|
||||
}
|
||||
|
||||
pub fn composite(
|
||||
self: *WebCanvasImpl,
|
||||
op: CompositionOp,
|
||||
src: *const WebCanvasImpl,
|
||||
dest: Rect,
|
||||
) void {
|
||||
const ctx = self.context(Color.on) catch return;
|
||||
defer ctx.deinit();
|
||||
|
||||
// Set our compositing operation
|
||||
ctx.set("globalCompositeOperation", op.jsOp()) catch return;
|
||||
|
||||
// Composite
|
||||
ctx.call(void, "drawImage", .{
|
||||
src.canvas,
|
||||
dest.x,
|
||||
dest.y,
|
||||
dest.width,
|
||||
dest.height,
|
||||
}) catch return;
|
||||
}
|
||||
|
||||
fn context(self: WebCanvasImpl, fill: ?Color) !js.Object {
|
||||
const ctx = try self.canvas.call(js.Object, "getContext", .{js.string("2d")});
|
||||
errdefer ctx.deinit();
|
||||
|
||||
// Reset our composite operation
|
||||
try ctx.set("globalCompositeOperation", js.string("source-over"));
|
||||
|
||||
// Set our fill color
|
||||
if (fill) |c| {
|
||||
var buf: [Color.CSS_BUF_MAX]u8 = undefined;
|
||||
const color = try c.cssColor(&buf);
|
||||
try ctx.set("fillStyle", js.string(color));
|
||||
}
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
pub fn writeAtlas(self: *WebCanvasImpl, alloc: Allocator, atlas: *font.Atlas) !font.Atlas.Region {
|
||||
assert(atlas.format == .grayscale);
|
||||
|
||||
// Reload our context since we resized the canvas
|
||||
const ctx = try self.context(null);
|
||||
defer ctx.deinit();
|
||||
|
||||
// Set our width/height. Set to vars in case we just query the canvas later.
|
||||
const width = self.width;
|
||||
const height = self.height;
|
||||
|
||||
// Read the image data and get it into a []u8 on our side
|
||||
const bitmap: []u8 = bitmap: {
|
||||
// Read the raw bitmap data and get the "data" value which is a
|
||||
// Uint8ClampedArray.
|
||||
const data = try ctx.call(js.Object, "getImageData", .{ 0, 0, width, height });
|
||||
defer data.deinit();
|
||||
const src_array = try data.get(js.Object, "data");
|
||||
defer src_array.deinit();
|
||||
|
||||
// Allocate our local memory to copy the data to.
|
||||
const len = try src_array.get(u32, "length");
|
||||
const bitmap = try alloc.alloc(u8, @intCast(len));
|
||||
errdefer alloc.free(bitmap);
|
||||
|
||||
// Create our target Uint8Array that we can use to copy from src.
|
||||
const mem_array = mem_array: {
|
||||
// Get our runtime memory
|
||||
const mem = try js.runtime.get(js.Object, "memory");
|
||||
defer mem.deinit();
|
||||
const buf = try mem.get(js.Object, "buffer");
|
||||
defer buf.deinit();
|
||||
|
||||
// Construct our array to peer into our memory
|
||||
const Uint8Array = try js.global.get(js.Object, "Uint8Array");
|
||||
defer Uint8Array.deinit();
|
||||
const mem_array = try Uint8Array.new(.{ buf, bitmap.ptr });
|
||||
errdefer mem_array.deinit();
|
||||
|
||||
break :mem_array mem_array;
|
||||
};
|
||||
defer mem_array.deinit();
|
||||
|
||||
// Copy
|
||||
try mem_array.call(void, "set", .{src_array});
|
||||
|
||||
break :bitmap bitmap;
|
||||
};
|
||||
errdefer alloc.free(bitmap);
|
||||
|
||||
// Convert the format of the bitmap to A8 since the raw canvas data
|
||||
// is in RGBA.
|
||||
// NOTE(mitchellh): do we need a 1px buffer to avoid artifacts?
|
||||
const bitmap_a8: []u8 = a8: {
|
||||
assert(@mod(bitmap.len, 4) == 0);
|
||||
assert(bitmap.len == width * height * 4);
|
||||
var bitmap_a8 = try alloc.alloc(u8, bitmap.len / 4);
|
||||
errdefer alloc.free(bitmap_a8);
|
||||
var i: usize = 0;
|
||||
while (i < bitmap_a8.len) : (i += 1) {
|
||||
bitmap_a8[i] = bitmap[(i * 4) + 3];
|
||||
}
|
||||
|
||||
break :a8 bitmap_a8;
|
||||
};
|
||||
defer alloc.free(bitmap_a8);
|
||||
|
||||
// Write the glyph information into the atlas
|
||||
const region = try atlas.reserve(alloc, width, height);
|
||||
if (region.width > 0 and region.height > 0) {
|
||||
assert(region.width == width);
|
||||
assert(region.height == height);
|
||||
atlas.set(region, bitmap_a8);
|
||||
}
|
||||
|
||||
return region;
|
||||
}
|
||||
};
|
||||
|
||||
const PixmanImpl = struct {
|
||||
/// The underlying image.
|
||||
image: *pixman.Image,
|
||||
|
||||
/// The raw data buffer.
|
||||
data: []u32,
|
||||
alloc: Allocator,
|
||||
|
||||
pub fn init(alloc: Allocator, width: u32, height: u32) !Canvas {
|
||||
// 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(width);
|
||||
const len = @as(usize, @intCast(stride * @as(c_int, @intCast(height))));
|
||||
|
||||
// Allocate our buffer. pixman uses []u32 so we divide our length
|
||||
// by 4 since u32 / u8 = 4.
|
||||
const data = try alloc.alloc(u32, len / 4);
|
||||
errdefer alloc.free(data);
|
||||
@memset(data, 0);
|
||||
|
||||
// Create the image we'll draw to
|
||||
const img = try pixman.Image.createBitsNoClear(
|
||||
format,
|
||||
// Create the surface we'll be using.
|
||||
const sfc = try z2d.Surface.initPixel(
|
||||
.{ .alpha8 = .{ .a = 0 } },
|
||||
alloc,
|
||||
@intCast(width),
|
||||
@intCast(height),
|
||||
data.ptr,
|
||||
stride,
|
||||
);
|
||||
errdefer _ = img.unref();
|
||||
|
||||
return Canvas{
|
||||
.image = img,
|
||||
.data = data,
|
||||
return .{
|
||||
.sfc = sfc,
|
||||
.alloc = alloc,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Canvas, alloc: Allocator) void {
|
||||
alloc.free(self.data);
|
||||
_ = self.image.unref();
|
||||
_ = alloc;
|
||||
self.sfc.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
@@ -344,8 +92,8 @@ const PixmanImpl = struct {
|
||||
pub fn writeAtlas(self: *Canvas, alloc: Allocator, atlas: *font.Atlas) !font.Atlas.Region {
|
||||
assert(atlas.format == .grayscale);
|
||||
|
||||
const width = @as(u32, @intCast(self.image.getWidth()));
|
||||
const height = @as(u32, @intCast(self.image.getHeight()));
|
||||
const width = @as(u32, @intCast(self.sfc.getWidth()));
|
||||
const height = @as(u32, @intCast(self.sfc.getHeight()));
|
||||
|
||||
// Allocate our texture atlas region
|
||||
const region = region: {
|
||||
@@ -372,31 +120,7 @@ const PixmanImpl = struct {
|
||||
};
|
||||
|
||||
if (region.width > 0 and region.height > 0) {
|
||||
const depth = atlas.format.depth();
|
||||
|
||||
// Convert our []u32 to []u8 since we use 8bpp formats
|
||||
const stride = self.image.getStride();
|
||||
const data = @as([*]u8, @ptrCast(self.data.ptr))[0 .. self.data.len * 4];
|
||||
|
||||
// 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 = !(width * depth == stride);
|
||||
|
||||
// If we need to copy the data, we copy it into a temporary buffer.
|
||||
const buffer = if (needs_copy) buffer: {
|
||||
const temp = try alloc.alloc(u8, width * height * depth);
|
||||
var dst_ptr = temp;
|
||||
var src_ptr = data.ptr;
|
||||
var i: usize = 0;
|
||||
while (i < height) : (i += 1) {
|
||||
@memcpy(dst_ptr[0 .. width * depth], src_ptr[0 .. width * depth]);
|
||||
dst_ptr = dst_ptr[width * depth ..];
|
||||
src_ptr += @as(usize, @intCast(stride));
|
||||
}
|
||||
break :buffer temp;
|
||||
} else data[0..(width * height * depth)];
|
||||
defer if (buffer.ptr != data.ptr) alloc.free(buffer);
|
||||
const buffer: []u8 = @ptrCast(self.sfc.image_surface_alpha8.buf);
|
||||
|
||||
// Write the glyph information into the atlas
|
||||
assert(region.width == width);
|
||||
@@ -409,102 +133,105 @@ const PixmanImpl = struct {
|
||||
|
||||
/// Draw and fill a single pixel
|
||||
pub fn pixel(self: *Canvas, x: u32, y: u32, color: Color) void {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
assert(x < self.image.getWidth());
|
||||
assert(y < self.image.getHeight());
|
||||
}
|
||||
|
||||
const boxes = &[_]pixman.Box32{
|
||||
.{
|
||||
.x1 = @intCast(x),
|
||||
.y1 = @intCast(y),
|
||||
.x2 = @intCast(x + 1),
|
||||
.y2 = @intCast(y + 1),
|
||||
},
|
||||
self.sfc.putPixel(
|
||||
@intCast(x),
|
||||
@intCast(y),
|
||||
.{ .alpha8 = .{ .a = @intFromEnum(color) } },
|
||||
) catch {
|
||||
// If we try to set out of range this will fail.
|
||||
// We just silently ignore that.
|
||||
};
|
||||
|
||||
self.image.fillBoxes(.src, color.pixmanColor(), boxes) catch {};
|
||||
}
|
||||
|
||||
/// Draw and fill a rectangle. This is the main primitive for drawing
|
||||
/// lines as well (which are just generally skinny rectangles...)
|
||||
pub fn rect(self: *Canvas, v: Rect, color: Color) void {
|
||||
const boxes = &[_]pixman.Box32{
|
||||
.{
|
||||
.x1 = @intCast(v.x),
|
||||
.y1 = @intCast(v.y),
|
||||
.x2 = @intCast(v.x + @as(i32, @intCast(v.width))),
|
||||
.y2 = @intCast(v.y + @as(i32, @intCast(v.height))),
|
||||
const x0: usize = @intFromFloat(v.x);
|
||||
const x1: usize = @intFromFloat(v.x + v.width);
|
||||
const y0: usize = @intFromFloat(v.y);
|
||||
const y1: usize = @intFromFloat(v.y + v.height);
|
||||
|
||||
for (y0..y1) |y| {
|
||||
for (x0..x1) |x| {
|
||||
self.pixel(
|
||||
@intCast(x),
|
||||
@intCast(y),
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw and fill a quad.
|
||||
pub fn quad(self: *Canvas, q: Quad, color: Color) !void {
|
||||
var ctx: z2d.Context = .{
|
||||
.surface = self.sfc,
|
||||
.pattern = .{
|
||||
.opaque_pattern = .{
|
||||
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
assert(boxes[0].x1 >= 0);
|
||||
assert(boxes[0].y1 >= 0);
|
||||
assert(boxes[0].x2 <= @as(i32, @intCast(self.image.getWidth())));
|
||||
assert(boxes[0].y2 <= @as(i32, @intCast(self.image.getHeight())));
|
||||
}
|
||||
var path = z2d.Path.init(self.alloc);
|
||||
defer path.deinit();
|
||||
|
||||
self.image.fillBoxes(.src, color.pixmanColor(), boxes) catch {};
|
||||
}
|
||||
try path.moveTo(q.p0.x, q.p0.y);
|
||||
try path.lineTo(q.p1.x, q.p1.y);
|
||||
try path.lineTo(q.p2.x, q.p2.y);
|
||||
try path.lineTo(q.p3.x, q.p3.y);
|
||||
try path.close();
|
||||
|
||||
/// Draw and fill a trapezoid.
|
||||
pub fn trapezoid(self: *Canvas, t: Trapezoid) void {
|
||||
self.image.rasterizeTrapezoid(.{
|
||||
.top = pixman.Fixed.init(t.top),
|
||||
.bottom = pixman.Fixed.init(t.bottom),
|
||||
.left = .{
|
||||
.p1 = .{
|
||||
.x = pixman.Fixed.init(t.left.p1.x),
|
||||
.y = pixman.Fixed.init(t.left.p1.y),
|
||||
},
|
||||
.p2 = .{
|
||||
.x = pixman.Fixed.init(t.left.p2.x),
|
||||
.y = pixman.Fixed.init(t.left.p2.y),
|
||||
},
|
||||
},
|
||||
.right = .{
|
||||
.p1 = .{
|
||||
.x = pixman.Fixed.init(t.right.p1.x),
|
||||
.y = pixman.Fixed.init(t.right.p1.y),
|
||||
},
|
||||
.p2 = .{
|
||||
.x = pixman.Fixed.init(t.right.p2.x),
|
||||
.y = pixman.Fixed.init(t.right.p2.y),
|
||||
},
|
||||
},
|
||||
}, 0, 0);
|
||||
try ctx.fill(self.alloc, path);
|
||||
}
|
||||
|
||||
/// Draw and fill a triangle.
|
||||
pub fn triangle(self: *Canvas, t: Triangle, color: Color) void {
|
||||
const tris = &[_]pixman.Triangle{
|
||||
.{
|
||||
.p1 = .{ .x = pixman.Fixed.init(t.p1.x), .y = pixman.Fixed.init(t.p1.y) },
|
||||
.p2 = .{ .x = pixman.Fixed.init(t.p2.x), .y = pixman.Fixed.init(t.p2.y) },
|
||||
.p3 = .{ .x = pixman.Fixed.init(t.p3.x), .y = pixman.Fixed.init(t.p3.y) },
|
||||
pub fn triangle(self: *Canvas, t: Triangle, color: Color) !void {
|
||||
var ctx: z2d.Context = .{
|
||||
.surface = self.sfc,
|
||||
.pattern = .{
|
||||
.opaque_pattern = .{
|
||||
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const src = pixman.Image.createSolidFill(color.pixmanColor()) catch return;
|
||||
defer _ = src.unref();
|
||||
self.image.compositeTriangles(.over, src, .a8, 0, 0, 0, 0, tris);
|
||||
var path = z2d.Path.init(self.alloc);
|
||||
defer path.deinit();
|
||||
|
||||
try path.moveTo(t.p0.x, t.p0.y);
|
||||
try path.lineTo(t.p1.x, t.p1.y);
|
||||
try path.lineTo(t.p2.x, t.p2.y);
|
||||
try path.close();
|
||||
|
||||
try ctx.fill(self.alloc, path);
|
||||
}
|
||||
|
||||
/// Composite one image on another.
|
||||
pub fn composite(self: *Canvas, op: CompositionOp, src: *const Canvas, dest: Rect) void {
|
||||
self.image.composite(
|
||||
op.pixmanOp(),
|
||||
src.image,
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@intCast(dest.x),
|
||||
@intCast(dest.y),
|
||||
@intCast(dest.width),
|
||||
@intCast(dest.height),
|
||||
);
|
||||
/// Stroke a line.
|
||||
pub fn line(self: *Canvas, l: Line, thickness: f64, color: Color) !void {
|
||||
var ctx: z2d.Context = .{
|
||||
.surface = self.sfc,
|
||||
.pattern = .{
|
||||
.opaque_pattern = .{
|
||||
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } },
|
||||
},
|
||||
},
|
||||
.line_width = thickness,
|
||||
.line_cap_mode = .round,
|
||||
};
|
||||
|
||||
var path = z2d.Path.init(self.alloc);
|
||||
defer path.deinit();
|
||||
|
||||
try path.moveTo(l.p0.x, l.p0.y);
|
||||
try path.lineTo(l.p1.x, l.p1.y);
|
||||
|
||||
try ctx.stroke(self.alloc, path);
|
||||
}
|
||||
|
||||
pub fn invert(self: *Canvas) void {
|
||||
for (std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf)) |*v| {
|
||||
v.* = 255 - v.*;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BIN
src/font/sprite/testdata/Box.ppm
vendored
BIN
src/font/sprite/testdata/Box.ppm
vendored
Binary file not shown.
@@ -70,8 +70,8 @@ fn drawSingle(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset {
|
||||
canvas.rect(.{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = width,
|
||||
.height = thickness,
|
||||
.width = @floatFromInt(width),
|
||||
.height = @floatFromInt(thickness),
|
||||
}, .on);
|
||||
|
||||
const offset_y: i32 = 0;
|
||||
@@ -91,15 +91,15 @@ fn drawDouble(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset {
|
||||
canvas.rect(.{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = width,
|
||||
.height = thickness,
|
||||
.width = @floatFromInt(width),
|
||||
.height = @floatFromInt(thickness),
|
||||
}, .on);
|
||||
|
||||
canvas.rect(.{
|
||||
.x = 0,
|
||||
.y = @intCast(thickness + gap),
|
||||
.width = width,
|
||||
.height = thickness,
|
||||
.y = @floatFromInt(thickness * 2),
|
||||
.width = @floatFromInt(width),
|
||||
.height = @floatFromInt(thickness),
|
||||
}, .on);
|
||||
|
||||
const offset_y: i32 = -@as(i32, @intCast(thickness));
|
||||
@@ -121,10 +121,10 @@ fn drawDotted(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset {
|
||||
const x = @min(i * (dot_width + gap_width), width - 1);
|
||||
const rect_width = @min(width - x, dot_width);
|
||||
canvas.rect(.{
|
||||
.x = @intCast(x),
|
||||
.x = @floatFromInt(x),
|
||||
.y = 0,
|
||||
.width = rect_width,
|
||||
.height = thickness,
|
||||
.width = @floatFromInt(rect_width),
|
||||
.height = @floatFromInt(thickness),
|
||||
}, .on);
|
||||
}
|
||||
|
||||
@@ -146,10 +146,10 @@ fn drawDashed(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset {
|
||||
const x = @min(i * dash_width, width - 1);
|
||||
const rect_width = @min(width - x, dash_width);
|
||||
canvas.rect(.{
|
||||
.x = @intCast(x),
|
||||
.x = @floatFromInt(x),
|
||||
.y = 0,
|
||||
.width = rect_width,
|
||||
.height = thickness,
|
||||
.width = @floatFromInt(rect_width),
|
||||
.height = @floatFromInt(thickness),
|
||||
}, .on);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user