250 lines
8.6 KiB
Zig
250 lines
8.6 KiB
Zig
const std = @import("std");
|
|
|
|
// The package manager will install headers from our dependency in zig's build
|
|
// cache and include the cache directory as a "-I" option on the build command
|
|
// automatically.
|
|
const c = @cImport({
|
|
@cInclude("MagickWand/MagickWand.h");
|
|
});
|
|
|
|
const display = @import("display.zig");
|
|
|
|
// This is set in two places. If this needs adjustment be sure to change the
|
|
// magick CLI command (where it is a string)
|
|
const GLYPH_WIDTH = display.FONT_WIDTH;
|
|
const GLYPH_HEIGHT = display.FONT_HEIGHT;
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const alloc = gpa.allocator();
|
|
//
|
|
// var env = try std.process.getEnvMap(alloc);
|
|
// defer env.deinit();
|
|
// var env_iterator = env.iterator();
|
|
// std.debug.print("\n", .{});
|
|
// while (env_iterator.next()) |entry| {
|
|
// std.debug.print("{s}={s}\n", .{ entry.key_ptr.*, entry.value_ptr.* });
|
|
// }
|
|
// cwd is the root of the project - yay!
|
|
const proj_path = std.fs.cwd(); //.realpath(".", &path_buf);
|
|
|
|
// We will assume we own the src/fonts dir in entirety
|
|
proj_path.makeDir("src/fonts/") catch {};
|
|
|
|
if (!std.meta.isError(proj_path.statFile("src/fonts/fonts.zig"))) return;
|
|
|
|
const generated_file = try proj_path.createFile("src/fonts/fonts.zig", .{
|
|
.read = false,
|
|
.truncate = true,
|
|
.lock = .exclusive,
|
|
.lock_nonblocking = false,
|
|
.mode = 0o666,
|
|
});
|
|
defer generated_file.close();
|
|
|
|
// We need a temp file for the glyph bmp
|
|
var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
|
|
|
const temp_file = try std.fs.path.joinZ(alloc, &[_][]const u8{ try proj_path.realpath("src/fonts/", &path_buf), ".tmp.bmp" });
|
|
defer alloc.free(temp_file);
|
|
defer std.fs.deleteFileAbsolute(temp_file) catch {};
|
|
|
|
const file_writer = generated_file.writer();
|
|
var buffered_writer = std.io.bufferedWriter(file_writer);
|
|
defer buffered_writer.flush() catch unreachable;
|
|
const writer = buffered_writer.writer();
|
|
// std.debug.print("cwd: {s}", .{try std.fs.cwd().realpath(".", &path_buf)});
|
|
|
|
// const args = try std.process.argsAlloc(alloc);
|
|
// defer std.process.argsFree(alloc, args);
|
|
//
|
|
// const stdout_file = std.io.getStdOut().writer();
|
|
// var bw = std.io.bufferedWriter(stdout_file);
|
|
// const stdout = bw.writer();
|
|
// defer bw.flush() catch unreachable; // don't forget to flush!
|
|
//
|
|
// // try stdout.print("Run `zig build test` to run the tests.\n", .{});
|
|
var pixels: [GLYPH_WIDTH * GLYPH_HEIGHT]u8 = undefined;
|
|
try writer.print("pub const @\"{s}\" = .{{\n", .{"Hack-Regular"});
|
|
// TODO: Read and cache
|
|
for (32..127) |i| {
|
|
// if (i == 32) {
|
|
// try writer.print(" \"\",\n", .{});
|
|
// continue;
|
|
// }
|
|
const char_str = [_]u8{@intCast(i)};
|
|
// Need to escape the following chars: 32 (' ') 92 ('\')
|
|
const label_param = parm: {
|
|
switch (i) {
|
|
32 => break :parm "label:\\ ",
|
|
92 => break :parm "label:\\\\",
|
|
else => break :parm "label:" ++ char_str,
|
|
}
|
|
};
|
|
|
|
// generate the file
|
|
// 36 ($) and 81 (Q) are widest and only 9 wide
|
|
// We are chopping the right pixel
|
|
try run(alloc, &[_][]const u8{
|
|
"magick",
|
|
"-background",
|
|
"white",
|
|
"-fill",
|
|
"black",
|
|
"-font",
|
|
"Hack-Regular",
|
|
"-density",
|
|
"72",
|
|
"-pointsize",
|
|
"8",
|
|
label_param,
|
|
"-extent",
|
|
"5x8",
|
|
temp_file,
|
|
});
|
|
|
|
// Grab pixels from the file
|
|
try convertImage(temp_file, &pixels);
|
|
|
|
packBits(&pixels);
|
|
// unpackBits(&pixels);
|
|
|
|
try writer.print(" .@\"{d}\" = &[_]u8{{ ", .{i});
|
|
var first = true;
|
|
for (pixels[0..(GLYPH_WIDTH * GLYPH_HEIGHT / 8)]) |byte| {
|
|
// for (pixels) |byte| { // unpacked only
|
|
if (!first) try writer.print(", ", .{});
|
|
try writer.print("0x{s}", .{std.fmt.bytesToHex(&[_]u8{byte}, .lower)});
|
|
first = false;
|
|
}
|
|
try writer.print(" }},\n", .{});
|
|
}
|
|
try writer.print("}};\n", .{});
|
|
}
|
|
const hi = .{
|
|
.there = &[_]u8{0xff},
|
|
};
|
|
|
|
fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void {
|
|
var env_map = try std.process.getEnvMap(allocator);
|
|
defer env_map.deinit();
|
|
|
|
var child = std.ChildProcess.init(argv, allocator);
|
|
|
|
child.stdin_behavior = .Ignore;
|
|
child.stdout_behavior = .Inherit;
|
|
child.stderr_behavior = .Inherit;
|
|
child.cwd = null; //std.fs.cwd();
|
|
child.env_map = &env_map;
|
|
|
|
try child.spawn();
|
|
const result = try child.wait();
|
|
switch (result) {
|
|
.Exited => |code| if (code != 0) {
|
|
std.log.err("command failed with exit code {}", .{code});
|
|
{
|
|
var msg = std.ArrayList(u8).init(allocator);
|
|
defer msg.deinit();
|
|
const writer = msg.writer();
|
|
var prefix: []const u8 = "";
|
|
for (argv) |arg| {
|
|
try writer.print("{s}\"{s}\"", .{ prefix, arg });
|
|
prefix = " ";
|
|
}
|
|
std.log.debug("[RUN] {s}", .{msg.items});
|
|
}
|
|
std.posix.exit(0xff);
|
|
},
|
|
else => {
|
|
std.log.err("command failed with: {}", .{result});
|
|
std.posix.exit(0xff);
|
|
},
|
|
}
|
|
}
|
|
pub fn unpackBits(pixels: *[GLYPH_WIDTH * GLYPH_HEIGHT]u8) void {
|
|
// bits packed: 0000 0001
|
|
// ^
|
|
// \- most significant bit
|
|
|
|
// Need to start at the end and work forward to avoid
|
|
// overwrites
|
|
var i: isize = (GLYPH_WIDTH * GLYPH_HEIGHT / 8 - 1);
|
|
while (i >= 0) {
|
|
const start = @as(usize, @intCast(i)) * 8;
|
|
const packed_byte = pixels[@intCast(i)];
|
|
pixels[start + 7] = ((packed_byte & 0b10000000) >> 7) * 0xFF;
|
|
pixels[start + 6] = ((packed_byte & 0b01000000) >> 6) * 0xFF;
|
|
pixels[start + 5] = ((packed_byte & 0b00100000) >> 5) * 0xFF;
|
|
pixels[start + 4] = ((packed_byte & 0b00010000) >> 4) * 0xFF;
|
|
pixels[start + 3] = ((packed_byte & 0b00001000) >> 3) * 0xFF;
|
|
pixels[start + 2] = ((packed_byte & 0b00000100) >> 2) * 0xFF;
|
|
pixels[start + 1] = ((packed_byte & 0b00000010) >> 1) * 0xFF;
|
|
pixels[start + 0] = ((packed_byte & 0b00000001) >> 0) * 0xFF;
|
|
i -= 1;
|
|
}
|
|
}
|
|
|
|
fn packBits(pixels: *[GLYPH_WIDTH * GLYPH_HEIGHT]u8) void {
|
|
for (0..(GLYPH_WIDTH * GLYPH_HEIGHT / 8)) |i| {
|
|
const start = i * 8;
|
|
pixels[i] = (pixels[start] & 0b00000001) |
|
|
(pixels[(start + 1)] & 0b00000010) |
|
|
(pixels[(start + 2)] & 0b00000100) |
|
|
(pixels[(start + 3)] & 0b00001000) |
|
|
(pixels[(start + 4)] & 0b00010000) |
|
|
(pixels[(start + 5)] & 0b00100000) |
|
|
(pixels[(start + 6)] & 0b01000000) |
|
|
(pixels[(start + 7)] & 0b10000000);
|
|
}
|
|
}
|
|
|
|
fn convertImage(filename: [:0]u8, pixels: *[GLYPH_WIDTH * GLYPH_HEIGHT]u8) !void {
|
|
c.MagickWandGenesis();
|
|
defer c.MagickWandTerminus();
|
|
var mw = c.NewMagickWand();
|
|
defer {
|
|
if (mw) |w| mw = c.DestroyMagickWand(w);
|
|
}
|
|
|
|
// Reading an image into ImageMagick is problematic if it isn't a bmp
|
|
// as the library needs a bunch of dependencies available
|
|
var status = c.MagickReadImage(mw, filename);
|
|
if (status == c.MagickFalse) {
|
|
// try reportMagickError(mw);
|
|
return error.CouldNotReadImage;
|
|
}
|
|
|
|
// We make the image monochrome by quantizing the image with 2 colors in the
|
|
// gray colorspace. See:
|
|
// https://www.imagemagick.org/Usage/quantize/#monochrome
|
|
// and
|
|
// https://stackoverflow.com/questions/18267432/using-the-c-api-for-imagemagick-on-iphone-to-convert-to-monochrome
|
|
//
|
|
// We do this at the end so we have pure black and white. Otherwise the
|
|
// resizing oprations will generate some greyscale that we don't want
|
|
status = c.MagickQuantizeImage(mw, // MagickWand
|
|
2, // Target number colors
|
|
c.GRAYColorspace, // Colorspace
|
|
1, // Optimal depth
|
|
c.MagickTrue, // Dither
|
|
c.MagickFalse // Quantization error
|
|
);
|
|
|
|
if (status == c.MagickFalse)
|
|
return error.CouldNotQuantizeImage;
|
|
|
|
status = c.MagickExportImagePixels(mw, 0, 0, GLYPH_WIDTH, GLYPH_HEIGHT, "I", c.CharPixel, @as(*anyopaque, @ptrCast(pixels)));
|
|
|
|
if (status == c.MagickFalse)
|
|
return error.CouldNotExportImage;
|
|
|
|
for (0..GLYPH_WIDTH * GLYPH_HEIGHT) |i| {
|
|
switch (pixels[i]) {
|
|
0x00 => pixels[i] = 0xFF,
|
|
0xFF => pixels[i] = 0x00,
|
|
else => {},
|
|
}
|
|
}
|
|
}
|