Compare commits
	
		
			No commits in common. "25e410c0b592a432402b8a8e9c50a493be8e0b6f" and "a7b917d3e96f97f1ebf8ac428a0341b76f8c23d7" have entirely different histories.
		
	
	
		
			25e410c0b5
			...
			a7b917d3e9
		
	
		
					 6 changed files with 71 additions and 550 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -2,4 +2,3 @@ zig-cache/
 | 
				
			||||||
zig-out/
 | 
					zig-out/
 | 
				
			||||||
core
 | 
					core
 | 
				
			||||||
src/images/*
 | 
					src/images/*
 | 
				
			||||||
src/fonts/
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										18
									
								
								build.zig
									
										
									
									
									
								
							
							
						
						
									
										18
									
								
								build.zig
									
										
									
									
									
								
							| 
						 | 
					@ -46,18 +46,12 @@ pub fn build(b: *std.build.Builder) !void {
 | 
				
			||||||
    exe.addIncludePath("lib/i2cdriver");
 | 
					    exe.addIncludePath("lib/i2cdriver");
 | 
				
			||||||
    exe.install();
 | 
					    exe.install();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const exe_fontgen = b.addExecutable(.{
 | 
					    // TODO: I believe we can use runArtifact on a second
 | 
				
			||||||
        .name = "fontgen",
 | 
					    // exe with a different source file for font generation
 | 
				
			||||||
        .root_source_file = .{ .path = "src/fontgen.zig" },
 | 
					    // taking us to a series of 5 byte arrays for each
 | 
				
			||||||
        .target = target,
 | 
					    // character in a font.
 | 
				
			||||||
        .optimize = optimize,
 | 
					    exe.step.dependOn(&AsciiPrintableStep.create(b, .{ .path = "src/images" }).step);
 | 
				
			||||||
    });
 | 
					    // exe.step.dependOn((try fontGeneration(b, target)));
 | 
				
			||||||
    exe_fontgen.linkLibrary(im_dep.artifact("MagickWand"));
 | 
					 | 
				
			||||||
    exe_fontgen.linkLibrary(z_dep.artifact("z"));
 | 
					 | 
				
			||||||
    exe.step.dependOn(&exe_fontgen.run().step);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // If image based characters are needed, uncomment this
 | 
					 | 
				
			||||||
    // exe.step.dependOn(&AsciiPrintableStep.create(b, .{ .path = "src/images" }).step);
 | 
					 | 
				
			||||||
    const run_cmd = exe.run();
 | 
					    const run_cmd = exe.run();
 | 
				
			||||||
    run_cmd.step.dependOn(b.getInstallStep());
 | 
					    run_cmd.step.dependOn(b.getInstallStep());
 | 
				
			||||||
    if (b.args) |args| {
 | 
					    if (b.args) |args| {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
// Image specifications
 | 
					 | 
				
			||||||
pub const WIDTH = 128;
 | 
					 | 
				
			||||||
pub const HEIGHT = 64;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Text specifications
 | 
					 | 
				
			||||||
pub const FONT_WIDTH = 5;
 | 
					 | 
				
			||||||
pub const FONT_HEIGHT = 8;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const CHARS_PER_LINE = 25; // 25 * 5 = 125 so we have 3px left over
 | 
					 | 
				
			||||||
pub const BORDER_LEFT = 1; // 1 empty px left, 2 empty on right
 | 
					 | 
				
			||||||
pub const LINES = 8;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Device specifications
 | 
					 | 
				
			||||||
pub const PAGES = 8;
 | 
					 | 
				
			||||||
							
								
								
									
										250
									
								
								src/fontgen.zig
									
										
									
									
									
								
							
							
						
						
									
										250
									
								
								src/fontgen.zig
									
										
									
									
									
								
							| 
						 | 
					@ -1,250 +0,0 @@
 | 
				
			||||||
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,
 | 
					 | 
				
			||||||
        .intended_io_mode = .blocking,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    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(u8, 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.os.exit(0xff);
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        else => {
 | 
					 | 
				
			||||||
            std.log.err("command failed with: {}", .{result});
 | 
					 | 
				
			||||||
            std.os.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 = @intCast(usize, i) * 8;
 | 
					 | 
				
			||||||
        const packed_byte = pixels[@intCast(usize, 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, @ptrCast(*anyopaque, 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 => {},
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										338
									
								
								src/main.zig
									
										
									
									
									
								
							
							
						
						
									
										338
									
								
								src/main.zig
									
										
									
									
									
								
							| 
						 | 
					@ -1,22 +1,5 @@
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
const display = @import("display.zig");
 | 
					const chars = @import("images/images.zig").chars;
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Disabling the image characters. To re-enabel, switch the import back and
 | 
					 | 
				
			||||||
// adjust build.zig
 | 
					 | 
				
			||||||
const chars = &[_][]const u8{""}; //@import("images/images.zig").chars;
 | 
					 | 
				
			||||||
const fonts = @import("fonts/fonts.zig");
 | 
					 | 
				
			||||||
const unpackBits = @import("fontgen.zig").unpackBits;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const DEFAULT_FONT = "Hack-Regular";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const supported_fonts = @typeInfo(fonts).Struct.decls;
 | 
					 | 
				
			||||||
// Specifying the size here works, but will cause problems later if we want, for example,
 | 
					 | 
				
			||||||
// 2x heigh font size. But...we would need a slice, which is runtime known, and will
 | 
					 | 
				
			||||||
// require an array -> slice conversion and additional allocations, etc, etc.
 | 
					 | 
				
			||||||
// So for now, we'll keep this specified so we can simply use a pointer
 | 
					 | 
				
			||||||
const FontInnerHash = std.AutoHashMap(u21, *const [display.FONT_WIDTH * display.FONT_HEIGHT / 8]u8);
 | 
					 | 
				
			||||||
var font_map: ?std.StringHashMap(?*FontInnerHash) = null;
 | 
					 | 
				
			||||||
var font_arena: ?std.heap.ArenaAllocator = null;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// The package manager will install headers from our dependency in zig's build
 | 
					// 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
 | 
					// cache and include the cache directory as a "-I" option on the build command
 | 
				
			||||||
| 
						 | 
					@ -26,7 +9,22 @@ const c = @cImport({
 | 
				
			||||||
    @cInclude("i2cdriver.h");
 | 
					    @cInclude("i2cdriver.h");
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var lines: [display.LINES]*[:0]u8 = undefined;
 | 
					// Image specifications
 | 
				
			||||||
 | 
					const WIDTH = 128;
 | 
				
			||||||
 | 
					const HEIGHT = 64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Text specifications
 | 
				
			||||||
 | 
					const FONT_WIDTH = 5;
 | 
				
			||||||
 | 
					const FONT_HEIGHT = 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CHARS_PER_LINE = 25; // 25 * 5 = 125 so we have 3px left over
 | 
				
			||||||
 | 
					const BORDER_LEFT = 1; // 1 empty px left, 2 empty on right
 | 
				
			||||||
 | 
					const LINES = 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Device specifications
 | 
				
			||||||
 | 
					const PAGES = 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var lines: [LINES]*[:0]u8 = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn usage(args: [][]u8) !void {
 | 
					fn usage(args: [][]u8) !void {
 | 
				
			||||||
    const writer = std.io.getStdErr().writer();
 | 
					    const writer = std.io.getStdErr().writer();
 | 
				
			||||||
| 
						 | 
					@ -40,7 +38,6 @@ const Options = struct {
 | 
				
			||||||
    device_file: [:0]u8,
 | 
					    device_file: [:0]u8,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
pub fn main() !void {
 | 
					pub fn main() !void {
 | 
				
			||||||
    defer deinit();
 | 
					 | 
				
			||||||
    const alloc = std.heap.c_allocator;
 | 
					    const alloc = std.heap.c_allocator;
 | 
				
			||||||
    //defer alloc.deinit();
 | 
					    //defer alloc.deinit();
 | 
				
			||||||
    const args = try std.process.argsAlloc(alloc);
 | 
					    const args = try std.process.argsAlloc(alloc);
 | 
				
			||||||
| 
						 | 
					@ -55,10 +52,10 @@ pub fn main() !void {
 | 
				
			||||||
    // we take command line options. So cli will overwrite any lines specified
 | 
					    // we take command line options. So cli will overwrite any lines specified
 | 
				
			||||||
    // from the file
 | 
					    // from the file
 | 
				
			||||||
    const stdin_file = std.io.getStdIn();
 | 
					    const stdin_file = std.io.getStdIn();
 | 
				
			||||||
    var stdin_data: [display.WIDTH * display.HEIGHT + 1]u8 = undefined;
 | 
					    var stdin_data: [WIDTH * HEIGHT + 1]u8 = undefined;
 | 
				
			||||||
    // Need this to support deallocation of memory
 | 
					    // Need this to support deallocation of memory
 | 
				
			||||||
    var line_inx: usize = 0;
 | 
					    var line_inx: usize = 0;
 | 
				
			||||||
    var stdin_lines: [display.LINES][:0]u8 = undefined;
 | 
					    var stdin_lines: [LINES][:0]u8 = undefined;
 | 
				
			||||||
    defer {
 | 
					    defer {
 | 
				
			||||||
        for (0..line_inx) |i| {
 | 
					        for (0..line_inx) |i| {
 | 
				
			||||||
            alloc.free(stdin_lines[i]);
 | 
					            alloc.free(stdin_lines[i]);
 | 
				
			||||||
| 
						 | 
					@ -83,12 +80,12 @@ pub fn main() !void {
 | 
				
			||||||
            line_inx += 1;
 | 
					            line_inx += 1;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    std.debug.print("delme: {s}\n", .{lines[0].*});
 | 
				
			||||||
    const opts = try processArgs(alloc, args, &lines);
 | 
					    const opts = try processArgs(alloc, args, &lines);
 | 
				
			||||||
    defer alloc.destroy(opts);
 | 
					    defer alloc.destroy(opts);
 | 
				
			||||||
    if (opts.background_filename.len > 0) try stdout.print("Converting {s}\n", .{opts.background_filename});
 | 
					    if (opts.background_filename.len > 0) try stdout.print("Converting {s}\n", .{opts.background_filename});
 | 
				
			||||||
    var pixels: [display.WIDTH * display.HEIGHT]u8 = undefined;
 | 
					    var pixels: [WIDTH * HEIGHT]u8 = undefined;
 | 
				
			||||||
    try convertImage(opts.background_filename, &pixels, noTextForLine);
 | 
					    try convertImage(opts.background_filename, &pixels, textForLine);
 | 
				
			||||||
    try addTextToImage(alloc, &pixels, &lines);
 | 
					 | 
				
			||||||
    try bw.flush();
 | 
					    try bw.flush();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // We should take the linux device file here, then inspect for ttyUSB vs
 | 
					    // We should take the linux device file here, then inspect for ttyUSB vs
 | 
				
			||||||
| 
						 | 
					@ -98,23 +95,7 @@ pub fn main() !void {
 | 
				
			||||||
    // try stdout.print("Run `zig build test` to run the tests.\n", .{});
 | 
					    // try stdout.print("Run `zig build test` to run the tests.\n", .{});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn deinit() void {
 | 
					fn processArgs(allocator: std.mem.Allocator, args: [][:0]u8, line_array: *[LINES]*const [:0]u8) !*Options {
 | 
				
			||||||
    if (font_map == null or font_arena == null) return;
 | 
					 | 
				
			||||||
    // var it = font_map.?.keyIterator();
 | 
					 | 
				
			||||||
    // while (it.next()) |key| {
 | 
					 | 
				
			||||||
    //     // if (font_map.?.get(key.*).?) |font| {
 | 
					 | 
				
			||||||
    //     //     std.debug.print("delme: {s}: {*}\n", .{ key.*, font });
 | 
					 | 
				
			||||||
    //     //     font.deinit();
 | 
					 | 
				
			||||||
    //     // }
 | 
					 | 
				
			||||||
    // }
 | 
					 | 
				
			||||||
    font_map.?.deinit();
 | 
					 | 
				
			||||||
    font_map = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    font_arena.?.deinit();
 | 
					 | 
				
			||||||
    font_arena = null;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn processArgs(allocator: std.mem.Allocator, args: [][:0]u8, line_array: *[display.LINES]*const [:0]u8) !*Options {
 | 
					 | 
				
			||||||
    if (args.len < 2) try usage(args);
 | 
					    if (args.len < 2) try usage(args);
 | 
				
			||||||
    const prefix = "/dev/ttyUSB";
 | 
					    const prefix = "/dev/ttyUSB";
 | 
				
			||||||
    var opts = try allocator.create(Options);
 | 
					    var opts = try allocator.create(Options);
 | 
				
			||||||
| 
						 | 
					@ -138,10 +119,10 @@ fn processArgs(allocator: std.mem.Allocator, args: [][:0]u8, line_array: *[displ
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (line_number) |line| {
 | 
					        if (line_number) |line| {
 | 
				
			||||||
            if (arg.len > display.CHARS_PER_LINE) {
 | 
					            if (arg.len > CHARS_PER_LINE) {
 | 
				
			||||||
                try std.io.getStdErr().writer().print(
 | 
					                try std.io.getStdErr().writer().print(
 | 
				
			||||||
                    "ERROR: text for line {d} has {d} chars, exceeding maximum length {d}\n",
 | 
					                    "ERROR: text for line {d} has {d} chars, exceeding maximum length {d}\n",
 | 
				
			||||||
                    .{ line, arg.len, display.CHARS_PER_LINE },
 | 
					                    .{ line, arg.len, CHARS_PER_LINE },
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
                std.os.exit(1);
 | 
					                std.os.exit(1);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -161,117 +142,6 @@ fn areDigits(bytes: []u8) bool {
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn addTextToImage(allocator: std.mem.Allocator, pixels: *[display.WIDTH * display.HEIGHT]u8, data: []*[]u8) !void {
 | 
					 | 
				
			||||||
    var maybe_font_data = try getFontData(allocator, DEFAULT_FONT);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (maybe_font_data == null) return error.FontNotFound;
 | 
					 | 
				
			||||||
    var font_data = maybe_font_data.?;
 | 
					 | 
				
			||||||
    for (data, 0..) |line, starting_display_line| {
 | 
					 | 
				
			||||||
        var utf8 = (try std.unicode.Utf8View.init(line.*)).iterator();
 | 
					 | 
				
			||||||
        const starting_display_row = display.HEIGHT / display.LINES * starting_display_line;
 | 
					 | 
				
			||||||
        var starting_display_column: usize = 0;
 | 
					 | 
				
			||||||
        while (utf8.nextCodepoint()) |cp| {
 | 
					 | 
				
			||||||
            var glyph: [display.FONT_WIDTH * display.FONT_HEIGHT]u8 = undefined;
 | 
					 | 
				
			||||||
            std.debug.assert(font_data.get(cp).?.*.len == (display.FONT_WIDTH * display.FONT_HEIGHT / 8));
 | 
					 | 
				
			||||||
            // Is .? appropriate here? this is a hard fail if our codepoint isn't in the font...
 | 
					 | 
				
			||||||
            for (font_data.get(cp).?.*, 0..) |b, i| {
 | 
					 | 
				
			||||||
                glyph[i] = b;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            unpackBits(&glyph); // this will fill the rest of our array
 | 
					 | 
				
			||||||
            // std.log.debug("====" ++ "=" ** display.FONT_WIDTH, .{});
 | 
					 | 
				
			||||||
            // for (0..display.FONT_HEIGHT) |i| {
 | 
					 | 
				
			||||||
            //     std.log.debug(
 | 
					 | 
				
			||||||
            //         "{d:0>2}: {s}",
 | 
					 | 
				
			||||||
            //         .{ i, fmtSliceGreyscaleImage(glyph[(i * display.FONT_WIDTH)..((i + 1) * display.FONT_WIDTH)]) },
 | 
					 | 
				
			||||||
            //     );
 | 
					 | 
				
			||||||
            // }
 | 
					 | 
				
			||||||
            // std.log.debug("====" ++ "=" ** display.FONT_WIDTH ++ "\n", .{});
 | 
					 | 
				
			||||||
            // unpacked - time to ram this in
 | 
					 | 
				
			||||||
            for (glyph, 0..) |b, i| {
 | 
					 | 
				
			||||||
                const column = i % display.FONT_WIDTH + starting_display_column + display.BORDER_LEFT;
 | 
					 | 
				
			||||||
                const row = i / display.FONT_WIDTH + starting_display_row;
 | 
					 | 
				
			||||||
                pixels[(row * display.WIDTH) + column] = b;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            starting_display_column += display.FONT_WIDTH;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn getFontData(allocator: std.mem.Allocator, font_name: []const u8) !?FontInnerHash {
 | 
					 | 
				
			||||||
    if (font_arena == null) {
 | 
					 | 
				
			||||||
        font_arena = std.heap.ArenaAllocator.init(allocator);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    var alloc = font_arena.?.allocator();
 | 
					 | 
				
			||||||
    // The bit lookup will be a bit tricky because we have runtime value to look up
 | 
					 | 
				
			||||||
    // We can use an inline for but compute complexity is a bit crazy. Best to use a
 | 
					 | 
				
			||||||
    // hashmap here
 | 
					 | 
				
			||||||
    //
 | 
					 | 
				
			||||||
    // We aren't generating Unicode at the moment, but to handle it appropriately, let's
 | 
					 | 
				
			||||||
    // assign our key value to u21
 | 
					 | 
				
			||||||
    if (font_map == null) {
 | 
					 | 
				
			||||||
        font_map = std.hash_map.StringHashMap(?*FontInnerHash).init(alloc);
 | 
					 | 
				
			||||||
        try font_map.?.ensureTotalCapacity(supported_fonts.len);
 | 
					 | 
				
			||||||
        inline for (supported_fonts) |font| {
 | 
					 | 
				
			||||||
            font_map.?.putAssumeCapacity(font.name, null);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // defer font_map.deinit();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!font_map.?.contains(font_name)) return null; // this font not in fonts/fonts.zig. This must be addressed in compilation
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const font = font_map.?.get(font_name).?;
 | 
					 | 
				
			||||||
    if (font) |f| return f.*; // Font exists and is fully populated
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Font exists, but has not been populated yet. Build it now
 | 
					 | 
				
			||||||
    // idk if actual map needs to be on the heap here?
 | 
					 | 
				
			||||||
    // I think it does... let's try without and see how we leak
 | 
					 | 
				
			||||||
    inline for (supported_fonts) |supported_font| {
 | 
					 | 
				
			||||||
        if (std.mem.eql(u8, supported_font.name, font_name)) {
 | 
					 | 
				
			||||||
            font_map.?.putAssumeCapacity(supported_font.name, try getFontMap(alloc, supported_font.name));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    std.log.debug(
 | 
					 | 
				
			||||||
        "All fonts added. Outer hash map capacity: {d} count: {d}\n",
 | 
					 | 
				
			||||||
        .{ font_map.?.capacity(), font_map.?.count() },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    std.log.debug(
 | 
					 | 
				
			||||||
        "Inner hash map capacity for default font: {d} count: {d}\n",
 | 
					 | 
				
			||||||
        .{ font_map.?.get(DEFAULT_FONT).?.?.capacity(), font_map.?.get(DEFAULT_FONT).?.?.count() },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    return font_map.?.get(font_name).?.?.*;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
/// gets a font map. All memory owned by caller
 | 
					 | 
				
			||||||
/// The inner hash map is key type u21, value type of pointer to u8 array
 | 
					 | 
				
			||||||
/// The values are used directly, but all keys will be allocated at the time
 | 
					 | 
				
			||||||
/// of call
 | 
					 | 
				
			||||||
fn getFontMap(allocator: std.mem.Allocator, comptime font_name: []const u8) !*FontInnerHash {
 | 
					 | 
				
			||||||
    // supported_font is a comptime value due to inline for
 | 
					 | 
				
			||||||
    // font_name is a runtime value
 | 
					 | 
				
			||||||
    const font_struct = @field(fonts, font_name);
 | 
					 | 
				
			||||||
    const code_points = @typeInfo(@TypeOf(font_struct)).Struct.fields;
 | 
					 | 
				
			||||||
    // found font - populate map and return
 | 
					 | 
				
			||||||
    var map = FontInnerHash.init(allocator);
 | 
					 | 
				
			||||||
    try map.ensureTotalCapacity(code_points.len);
 | 
					 | 
				
			||||||
    inline for (code_points) |point| {
 | 
					 | 
				
			||||||
        if (std.mem.eql(u8, "120", point.name)) {
 | 
					 | 
				
			||||||
            std.log.debug(
 | 
					 | 
				
			||||||
                "CodePoint 120 Added. Data:{s}\n",
 | 
					 | 
				
			||||||
                .{fmtSliceHexLower(@field(font_struct, point.name))},
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        var key_ptr = try allocator.create(u21);
 | 
					 | 
				
			||||||
        key_ptr.* = std.fmt.parseInt(u21, point.name, 10) catch unreachable;
 | 
					 | 
				
			||||||
        map.putAssumeCapacity(
 | 
					 | 
				
			||||||
            key_ptr.*,
 | 
					 | 
				
			||||||
            @field(font_struct, point.name),
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    std.log.debug(
 | 
					 | 
				
			||||||
        "All codepoints added. {*} capacity: {d} count: {d}\n",
 | 
					 | 
				
			||||||
        .{ &map, map.capacity(), map.count() },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    return ↦
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn sendPixels(pixels: []const u8, file: [:0]const u8, device_id: u8) !void {
 | 
					fn sendPixels(pixels: []const u8, file: [:0]const u8, device_id: u8) !void {
 | 
				
			||||||
    if (std.mem.eql(u8, file, "-"))
 | 
					    if (std.mem.eql(u8, file, "-"))
 | 
				
			||||||
        return sendPixelsToStdOut(pixels);
 | 
					        return sendPixelsToStdOut(pixels);
 | 
				
			||||||
| 
						 | 
					@ -292,16 +162,13 @@ fn sendPixelsToStdOut(pixels: []const u8) !void {
 | 
				
			||||||
    var bw = std.io.bufferedWriter(stdout_file);
 | 
					    var bw = std.io.bufferedWriter(stdout_file);
 | 
				
			||||||
    const stdout = bw.writer();
 | 
					    const stdout = bw.writer();
 | 
				
			||||||
    defer bw.flush() catch unreachable; // don't forget to flush!
 | 
					    defer bw.flush() catch unreachable; // don't forget to flush!
 | 
				
			||||||
    for (0..display.HEIGHT) |i| {
 | 
					    for (0..HEIGHT) |i| {
 | 
				
			||||||
        try stdout.print(
 | 
					        try stdout.print("{d:0>2}: {s}\n", .{ i, fmtSliceGreyscaleImage(pixels[(i * WIDTH)..((i + 1) * WIDTH)]) });
 | 
				
			||||||
            "{d:0>2}: {s}\n",
 | 
					 | 
				
			||||||
            .{ i, fmtSliceGreyscaleImage(pixels[(i * display.WIDTH)..((i + 1) * display.WIDTH)]) },
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn sendPixelsThroughI2CDriver(pixels: []const u8, file: [*:0]const u8, device_id: u8) !void {
 | 
					fn sendPixelsThroughI2CDriver(pixels: []const u8, file: [*:0]const u8, device_id: u8) !void {
 | 
				
			||||||
    var pixels_write_command = [_]u8{0x00} ** ((display.WIDTH * display.PAGES) + 1);
 | 
					    var pixels_write_command = [_]u8{0x00} ** ((WIDTH * PAGES) + 1);
 | 
				
			||||||
    pixels_write_command[0] = 0x40;
 | 
					    pixels_write_command[0] = 0x40;
 | 
				
			||||||
    packPixelsToDeviceFormat(pixels, pixels_write_command[1..]);
 | 
					    packPixelsToDeviceFormat(pixels, pixels_write_command[1..]);
 | 
				
			||||||
    var i2c = c.I2CDriver{
 | 
					    var i2c = c.I2CDriver{
 | 
				
			||||||
| 
						 | 
					@ -361,8 +228,8 @@ fn sendPixelsThroughI2CDriver(pixels: []const u8, file: [*:0]const u8, device_id
 | 
				
			||||||
fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void {
 | 
					fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void {
 | 
				
			||||||
    // Each u8 in pixels is a single bit. We need to pack these bits
 | 
					    // Each u8 in pixels is a single bit. We need to pack these bits
 | 
				
			||||||
    for (packed_pixels, 0..) |*b, i| {
 | 
					    for (packed_pixels, 0..) |*b, i| {
 | 
				
			||||||
        const column = i % display.WIDTH;
 | 
					        const column = i % WIDTH;
 | 
				
			||||||
        const page = i / display.WIDTH;
 | 
					        const page = i / WIDTH;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // if (column == 0) std.debug.print("{d}: ", .{page});
 | 
					        // if (column == 0) std.debug.print("{d}: ", .{page});
 | 
				
			||||||
        // pixel array will be 8x as "high" as the data array we are sending to
 | 
					        // pixel array will be 8x as "high" as the data array we are sending to
 | 
				
			||||||
| 
						 | 
					@ -371,7 +238,7 @@ fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void {
 | 
				
			||||||
        //
 | 
					        //
 | 
				
			||||||
        // To convert from the pixel array above, we need to:
 | 
					        // To convert from the pixel array above, we need to:
 | 
				
			||||||
        //   1. convert from device page to a base "row" in the pixel array
 | 
					        //   1. convert from device page to a base "row" in the pixel array
 | 
				
			||||||
        const row = page * display.PAGES;
 | 
					        const row = page * PAGES;
 | 
				
			||||||
        //   2. We will have 8 rows for each base row
 | 
					        //   2. We will have 8 rows for each base row
 | 
				
			||||||
        //   3. Multiple each row by the width to get the index of the start of
 | 
					        //   3. Multiple each row by the width to get the index of the start of
 | 
				
			||||||
        //      the row
 | 
					        //      the row
 | 
				
			||||||
| 
						 | 
					@ -386,14 +253,14 @@ fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void {
 | 
				
			||||||
        //      per source byte
 | 
					        //      per source byte
 | 
				
			||||||
        //   2. Shift that bit into the proper position in our destination byte
 | 
					        //   2. Shift that bit into the proper position in our destination byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        b.* = (pixels[(0 + row) * display.WIDTH + column] & 0x01) << 0 |
 | 
					        b.* = (pixels[(0 + row) * WIDTH + column] & 0x01) << 0 |
 | 
				
			||||||
            (pixels[(1 + row) * display.WIDTH + column] & 0x01) << 1 |
 | 
					            (pixels[(1 + row) * WIDTH + column] & 0x01) << 1 |
 | 
				
			||||||
            (pixels[(2 + row) * display.WIDTH + column] & 0x01) << 2 |
 | 
					            (pixels[(2 + row) * WIDTH + column] & 0x01) << 2 |
 | 
				
			||||||
            (pixels[(3 + row) * display.WIDTH + column] & 0x01) << 3 |
 | 
					            (pixels[(3 + row) * WIDTH + column] & 0x01) << 3 |
 | 
				
			||||||
            (pixels[(4 + row) * display.WIDTH + column] & 0x01) << 4 |
 | 
					            (pixels[(4 + row) * WIDTH + column] & 0x01) << 4 |
 | 
				
			||||||
            (pixels[(5 + row) * display.WIDTH + column] & 0x01) << 5 |
 | 
					            (pixels[(5 + row) * WIDTH + column] & 0x01) << 5 |
 | 
				
			||||||
            (pixels[(6 + row) * display.WIDTH + column] & 0x01) << 6 |
 | 
					            (pixels[(6 + row) * WIDTH + column] & 0x01) << 6 |
 | 
				
			||||||
            (pixels[(7 + row) * display.WIDTH + column] & 0x01) << 7;
 | 
					            (pixels[(7 + row) * WIDTH + column] & 0x01) << 7;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // std.debug.print("{s}", .{std.fmt.fmtSliceHexLower(&[_]u8{b.*})});
 | 
					        // std.debug.print("{s}", .{std.fmt.fmtSliceHexLower(&[_]u8{b.*})});
 | 
				
			||||||
        // if (column == 127) std.debug.print("\n", .{});
 | 
					        // if (column == 127) std.debug.print("\n", .{});
 | 
				
			||||||
| 
						 | 
					@ -409,35 +276,6 @@ fn i2cWrite(i2c: *c.I2CDriver, bytes: []const u8) !void {
 | 
				
			||||||
        return error.BadWrite;
 | 
					        return error.BadWrite;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Case = enum { lower, upper };
 | 
					 | 
				
			||||||
fn formatSliceHexImpl(comptime case: Case) type {
 | 
					 | 
				
			||||||
    const charset = "0123456789" ++ if (case == .upper) "ABCDEF" else "abcdef";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return struct {
 | 
					 | 
				
			||||||
        pub fn formatSliceHexImpl(
 | 
					 | 
				
			||||||
            bytes: []const u8,
 | 
					 | 
				
			||||||
            comptime fmt: []const u8,
 | 
					 | 
				
			||||||
            options: std.fmt.FormatOptions,
 | 
					 | 
				
			||||||
            writer: anytype,
 | 
					 | 
				
			||||||
        ) !void {
 | 
					 | 
				
			||||||
            _ = fmt;
 | 
					 | 
				
			||||||
            _ = options;
 | 
					 | 
				
			||||||
            var buf: [2]u8 = undefined;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for (bytes) |ch| {
 | 
					 | 
				
			||||||
                buf[0] = charset[ch >> 4];
 | 
					 | 
				
			||||||
                buf[1] = charset[ch & 15];
 | 
					 | 
				
			||||||
                try writer.print(" 0x", .{});
 | 
					 | 
				
			||||||
                try writer.writeAll(&buf);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const formatSliceHexLower = formatSliceHexImpl(.lower).formatSliceHexImpl;
 | 
					 | 
				
			||||||
fn fmtSliceHexLower(bytes: []const u8) std.fmt.Formatter(formatSliceHexLower) {
 | 
					 | 
				
			||||||
    return .{ .data = bytes };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
fn fmtSliceGreyscaleImage(bytes: []const u8) std.fmt.Formatter(formatSliceGreyscaleImage) {
 | 
					fn fmtSliceGreyscaleImage(bytes: []const u8) std.fmt.Formatter(formatSliceGreyscaleImage) {
 | 
				
			||||||
    return .{ .data = bytes };
 | 
					    return .{ .data = bytes };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -467,10 +305,7 @@ fn reportMagickError(mw: ?*c.MagickWand) !void {
 | 
				
			||||||
fn textForLine(line: usize) []u8 {
 | 
					fn textForLine(line: usize) []u8 {
 | 
				
			||||||
    return lines[line].*;
 | 
					    return lines[line].*;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
fn noTextForLine(_: usize) []u8 {
 | 
					fn convertImage(filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8, text_fn: *const fn (usize) []u8) !void {
 | 
				
			||||||
    return "";
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
fn convertImage(filename: [:0]u8, pixels: *[display.WIDTH * display.HEIGHT]u8, text_fn: *const fn (usize) []u8) !void {
 | 
					 | 
				
			||||||
    c.MagickWandGenesis();
 | 
					    c.MagickWandGenesis();
 | 
				
			||||||
    defer c.MagickWandTerminus();
 | 
					    defer c.MagickWandTerminus();
 | 
				
			||||||
    var mw = c.NewMagickWand();
 | 
					    var mw = c.NewMagickWand();
 | 
				
			||||||
| 
						 | 
					@ -508,7 +343,7 @@ fn convertImage(filename: [:0]u8, pixels: *[display.WIDTH * display.HEIGHT]u8, t
 | 
				
			||||||
    // This should be 48x64 with our test
 | 
					    // This should be 48x64 with our test
 | 
				
			||||||
    // Command line resize works differently than this. Here we need to find
 | 
					    // Command line resize works differently than this. Here we need to find
 | 
				
			||||||
    // new width and height based on the input aspect ratio ourselves
 | 
					    // new width and height based on the input aspect ratio ourselves
 | 
				
			||||||
    const resize_dimensions = getNewDimensions(w, h, display.WIDTH, display.HEIGHT);
 | 
					    const resize_dimensions = getNewDimensions(w, h, WIDTH, HEIGHT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    std.log.debug("Dimensions for resize: {d}x{d}\n", .{ resize_dimensions.width, resize_dimensions.height });
 | 
					    std.log.debug("Dimensions for resize: {d}x{d}\n", .{ resize_dimensions.width, resize_dimensions.height });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -534,21 +369,21 @@ fn convertImage(filename: [:0]u8, pixels: *[display.WIDTH * display.HEIGHT]u8, t
 | 
				
			||||||
    // around it means that the offset will be negative
 | 
					    // around it means that the offset will be negative
 | 
				
			||||||
    status = c.MagickExtentImage(
 | 
					    status = c.MagickExtentImage(
 | 
				
			||||||
        mw,
 | 
					        mw,
 | 
				
			||||||
        display.WIDTH,
 | 
					        WIDTH,
 | 
				
			||||||
        display.HEIGHT,
 | 
					        HEIGHT,
 | 
				
			||||||
        -@intCast(isize, (display.WIDTH - resize_dimensions.width) / 2),
 | 
					        -@intCast(isize, (WIDTH - resize_dimensions.width) / 2),
 | 
				
			||||||
        -@intCast(isize, (display.HEIGHT - resize_dimensions.height) / 2),
 | 
					        -@intCast(isize, (HEIGHT - resize_dimensions.height) / 2),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (status == c.MagickFalse)
 | 
					    if (status == c.MagickFalse)
 | 
				
			||||||
        return error.CouldNotSetExtent;
 | 
					        return error.CouldNotSetExtent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (0..display.LINES) |i| {
 | 
					    for (0..LINES) |i| {
 | 
				
			||||||
        const text = text_fn(i);
 | 
					        const text = text_fn(i);
 | 
				
			||||||
        if (text.len == 0) continue;
 | 
					        if (text.len == 0) continue;
 | 
				
			||||||
        // We have text!
 | 
					        // We have text!
 | 
				
			||||||
        const y: isize = display.FONT_HEIGHT * @intCast(isize, i);
 | 
					        const y: isize = FONT_HEIGHT * @intCast(isize, i);
 | 
				
			||||||
        var x: isize = display.BORDER_LEFT;
 | 
					        var x: isize = BORDER_LEFT;
 | 
				
			||||||
        var left_spaces: isize = 0;
 | 
					        var left_spaces: isize = 0;
 | 
				
			||||||
        for (text) |ch| {
 | 
					        for (text) |ch| {
 | 
				
			||||||
            if (ch == ' ') {
 | 
					            if (ch == ' ') {
 | 
				
			||||||
| 
						 | 
					@ -557,7 +392,7 @@ fn convertImage(filename: [:0]u8, pixels: *[display.WIDTH * display.HEIGHT]u8, t
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        x += (display.FONT_WIDTH * left_spaces);
 | 
					        x += (FONT_WIDTH * left_spaces);
 | 
				
			||||||
        mw = try drawString(mw, text[@intCast(usize, left_spaces)..], x, y);
 | 
					        mw = try drawString(mw, text[@intCast(usize, left_spaces)..], x, y);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -580,12 +415,12 @@ fn convertImage(filename: [:0]u8, pixels: *[display.WIDTH * display.HEIGHT]u8, t
 | 
				
			||||||
    if (status == c.MagickFalse)
 | 
					    if (status == c.MagickFalse)
 | 
				
			||||||
        return error.CouldNotQuantizeImage;
 | 
					        return error.CouldNotQuantizeImage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    status = c.MagickExportImagePixels(mw, 0, 0, display.WIDTH, display.HEIGHT, "I", c.CharPixel, @ptrCast(*anyopaque, pixels));
 | 
					    status = c.MagickExportImagePixels(mw, 0, 0, WIDTH, HEIGHT, "I", c.CharPixel, @ptrCast(*anyopaque, pixels));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (status == c.MagickFalse)
 | 
					    if (status == c.MagickFalse)
 | 
				
			||||||
        return error.CouldNotExportImage;
 | 
					        return error.CouldNotExportImage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (0..display.WIDTH * display.HEIGHT) |i| {
 | 
					    for (0..WIDTH * HEIGHT) |i| {
 | 
				
			||||||
        switch (pixels[i]) {
 | 
					        switch (pixels[i]) {
 | 
				
			||||||
            0x00 => pixels[i] = 0xFF,
 | 
					            0x00 => pixels[i] = 0xFF,
 | 
				
			||||||
            0xFF => pixels[i] = 0x00,
 | 
					            0xFF => pixels[i] = 0x00,
 | 
				
			||||||
| 
						 | 
					@ -599,7 +434,7 @@ fn drawString(mw: ?*c.MagickWand, str: []const u8, x: isize, y: isize) !?*c.Magi
 | 
				
			||||||
        rc = try drawCharacter(
 | 
					        rc = try drawCharacter(
 | 
				
			||||||
            rc,
 | 
					            rc,
 | 
				
			||||||
            ch,
 | 
					            ch,
 | 
				
			||||||
            -(x + @intCast(isize, display.FONT_WIDTH * i)),
 | 
					            -(x + @intCast(isize, FONT_WIDTH * i)),
 | 
				
			||||||
            -y,
 | 
					            -y,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -641,8 +476,8 @@ fn drawCharacter(mw: ?*c.MagickWand, char: u8, x: isize, y: isize) !?*c.MagickWa
 | 
				
			||||||
        // I think our characters are offset by 6px in the x and 8 in the y
 | 
					        // I think our characters are offset by 6px in the x and 8 in the y
 | 
				
			||||||
        status = c.MagickExtentImage(
 | 
					        status = c.MagickExtentImage(
 | 
				
			||||||
            cw,
 | 
					            cw,
 | 
				
			||||||
            display.WIDTH,
 | 
					            WIDTH,
 | 
				
			||||||
            display.HEIGHT,
 | 
					            HEIGHT,
 | 
				
			||||||
            x,
 | 
					            x,
 | 
				
			||||||
            y,
 | 
					            y,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
| 
						 | 
					@ -681,71 +516,22 @@ fn getNewDimensions(width: usize, height: usize, desired_width: usize, desired_h
 | 
				
			||||||
        .height = @floatToInt(usize, @intToFloat(f64, height) / resize_ratio), // 64,
 | 
					        .height = @floatToInt(usize, @intToFloat(f64, height) / resize_ratio), // 64,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
test "gets proper font data" {
 | 
					 | 
				
			||||||
    // std.testing.log_level = .debug;
 | 
					 | 
				
			||||||
    std.log.debug("\n", .{});
 | 
					 | 
				
			||||||
    defer deinit();
 | 
					 | 
				
			||||||
    var maybe_font_data = try getFontData(std.testing.allocator, DEFAULT_FONT);
 | 
					 | 
				
			||||||
    try std.testing.expect(maybe_font_data != null);
 | 
					 | 
				
			||||||
    var font_data = maybe_font_data.?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try std.testing.expect(font_data.capacity() > 90);
 | 
					 | 
				
			||||||
    try std.testing.expect(font_data.count() > 0);
 | 
					 | 
				
			||||||
    try std.testing.expect(font_data.get(33) != null);
 | 
					 | 
				
			||||||
    try std.testing.expectEqualSlices(u8, &[_]u8{ 0x80, 0x10, 0x42, 0x00, 0x20 }, font_data.get(33).?);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
test "deinit" {
 | 
					 | 
				
			||||||
    defer deinit();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
test "gets correct bytes" {
 | 
					test "gets correct bytes" {
 | 
				
			||||||
    defer deinit();
 | 
					 | 
				
			||||||
    std.testing.log_level = .debug;
 | 
					 | 
				
			||||||
    std.log.debug("\n", .{});
 | 
					 | 
				
			||||||
    const bg_file: [:0]u8 = @constCast("logo:");
 | 
					    const bg_file: [:0]u8 = @constCast("logo:");
 | 
				
			||||||
    const opts = .{ .background_filename = bg_file, .device_file = "-" };
 | 
					    const opts = .{ .background_filename = bg_file, .device_file = "-" };
 | 
				
			||||||
    var empty: [:0]u8 = @constCast("");
 | 
					    const empty: [:0]u8 = @constCast("");
 | 
				
			||||||
    for (&lines) |*line| {
 | 
					    for (&lines) |*line| {
 | 
				
			||||||
        line.* = ∅
 | 
					        line.* = ∅
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    var line: [:0]u8 = @constCast("Hello\\!");
 | 
					    const line: [:0]u8 = @constCast("Hello\\!");
 | 
				
			||||||
    lines[5] = &line;
 | 
					    lines[5] = &line;
 | 
				
			||||||
    var pixels: [display.WIDTH * display.HEIGHT]u8 = undefined;
 | 
					    var pixels: [WIDTH * HEIGHT]u8 = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var expected_pixels: *const [display.WIDTH * display.HEIGHT]u8 = @embedFile("testExpectedBytes.bin");
 | 
					    var expected_pixels: *const [WIDTH * HEIGHT]u8 = @embedFile("testExpectedBytes.bin");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // [_]u8{..,..,..}
 | 
					    // [_]u8{..,..,..}
 | 
				
			||||||
    try convertImage(opts.background_filename, &pixels, noTextForLine);
 | 
					    try convertImage(opts.background_filename, &pixels, textForLine);
 | 
				
			||||||
    try addTextToImage(std.testing.allocator, &pixels, &lines);
 | 
					 | 
				
			||||||
    const fmt = "{d:0>2}: {s}";
 | 
					 | 
				
			||||||
    for (0..display.HEIGHT) |i| {
 | 
					 | 
				
			||||||
        const actual = try std.fmt.allocPrint(
 | 
					 | 
				
			||||||
            std.testing.allocator,
 | 
					 | 
				
			||||||
            fmt,
 | 
					 | 
				
			||||||
            .{ i, fmtSliceGreyscaleImage(pixels[(i * display.WIDTH)..((i + 1) * display.WIDTH)]) },
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        defer std.testing.allocator.free(actual);
 | 
					 | 
				
			||||||
        const expected = try std.fmt.allocPrint(
 | 
					 | 
				
			||||||
            std.testing.allocator,
 | 
					 | 
				
			||||||
            fmt,
 | 
					 | 
				
			||||||
            .{ i, fmtSliceGreyscaleImage(expected_pixels[(i * display.WIDTH)..((i + 1) * display.WIDTH)]) },
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        defer std.testing.allocator.free(expected);
 | 
					 | 
				
			||||||
        std.testing.expectEqualSlices(u8, expected, actual) catch |err| {
 | 
					 | 
				
			||||||
            for (0..display.HEIGHT) |r| {
 | 
					 | 
				
			||||||
                std.log.debug(
 | 
					 | 
				
			||||||
                    fmt,
 | 
					 | 
				
			||||||
                    .{ r, fmtSliceGreyscaleImage(pixels[(r * display.WIDTH)..((r + 1) * display.WIDTH)]) },
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            for (0..display.HEIGHT) |r| {
 | 
					 | 
				
			||||||
                std.log.debug(
 | 
					 | 
				
			||||||
                    fmt,
 | 
					 | 
				
			||||||
                    .{ r, fmtSliceGreyscaleImage(expected_pixels[(r * display.WIDTH)..((r + 1) * display.WIDTH)]) },
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return err;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // try writeBytesToFile("testExpectedBytes.bin", &pixels);
 | 
					    // try writeBytesToFile("testExpectedBytes.bin", &pixels);
 | 
				
			||||||
    try std.testing.expectEqualSlices(u8, expected_pixels, &pixels);
 | 
					    try std.testing.expectEqualSlices(u8, expected_pixels, &pixels);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -763,3 +549,9 @@ fn writeBytesToFile(filename: []const u8, bytes: []u8) !void {
 | 
				
			||||||
    try writer.writeAll(bytes);
 | 
					    try writer.writeAll(bytes);
 | 
				
			||||||
    // try writer.print("pub const chars = &[_][]const u8{{\n", .{});
 | 
					    // try writer.print("pub const chars = &[_][]const u8{{\n", .{});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					test "simple test" {
 | 
				
			||||||
 | 
					    var list = std.ArrayList(i32).init(std.testing.allocator);
 | 
				
			||||||
 | 
					    defer list.deinit(); // try commenting this out and see if zig detects the memory leak!
 | 
				
			||||||
 | 
					    try list.append(42);
 | 
				
			||||||
 | 
					    try std.testing.expectEqual(@as(i32, 42), list.pop());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							
		Loading…
	
	Add table
		
		Reference in a new issue