Compare commits
6 Commits
a7b917d3e9
...
25e410c0b5
Author | SHA1 | Date | |
---|---|---|---|
25e410c0b5 | |||
b05ac7bae7 | |||
8c9df71080 | |||
b07c75ca9a | |||
08b30a336f | |||
f1e635d786 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@ zig-cache/
|
||||||
zig-out/
|
zig-out/
|
||||||
core
|
core
|
||||||
src/images/*
|
src/images/*
|
||||||
|
src/fonts/
|
||||||
|
|
18
build.zig
18
build.zig
|
@ -46,12 +46,18 @@ pub fn build(b: *std.build.Builder) !void {
|
||||||
exe.addIncludePath("lib/i2cdriver");
|
exe.addIncludePath("lib/i2cdriver");
|
||||||
exe.install();
|
exe.install();
|
||||||
|
|
||||||
// TODO: I believe we can use runArtifact on a second
|
const exe_fontgen = b.addExecutable(.{
|
||||||
// exe with a different source file for font generation
|
.name = "fontgen",
|
||||||
// taking us to a series of 5 byte arrays for each
|
.root_source_file = .{ .path = "src/fontgen.zig" },
|
||||||
// character in a font.
|
.target = target,
|
||||||
exe.step.dependOn(&AsciiPrintableStep.create(b, .{ .path = "src/images" }).step);
|
.optimize = optimize,
|
||||||
// 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| {
|
||||||
|
|
14
src/display.zig
Normal file
14
src/display.zig
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// 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
Normal file
250
src/fontgen.zig
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
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,5 +1,22 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const chars = @import("images/images.zig").chars;
|
const display = @import("display.zig");
|
||||||
|
|
||||||
|
// 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
|
||||||
|
@ -9,22 +26,7 @@ const c = @cImport({
|
||||||
@cInclude("i2cdriver.h");
|
@cInclude("i2cdriver.h");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Image specifications
|
var lines: [display.LINES]*[:0]u8 = undefined;
|
||||||
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();
|
||||||
|
@ -38,6 +40,7 @@ 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);
|
||||||
|
@ -52,10 +55,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: [WIDTH * HEIGHT + 1]u8 = undefined;
|
var stdin_data: [display.WIDTH * display.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: [LINES][:0]u8 = undefined;
|
var stdin_lines: [display.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]);
|
||||||
|
@ -80,12 +83,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: [WIDTH * HEIGHT]u8 = undefined;
|
var pixels: [display.WIDTH * display.HEIGHT]u8 = undefined;
|
||||||
try convertImage(opts.background_filename, &pixels, textForLine);
|
try convertImage(opts.background_filename, &pixels, noTextForLine);
|
||||||
|
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
|
||||||
|
@ -95,7 +98,23 @@ 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 processArgs(allocator: std.mem.Allocator, args: [][:0]u8, line_array: *[LINES]*const [:0]u8) !*Options {
|
fn deinit() void {
|
||||||
|
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);
|
||||||
|
@ -119,10 +138,10 @@ fn processArgs(allocator: std.mem.Allocator, args: [][:0]u8, line_array: *[LINES
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (line_number) |line| {
|
if (line_number) |line| {
|
||||||
if (arg.len > CHARS_PER_LINE) {
|
if (arg.len > display.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, CHARS_PER_LINE },
|
.{ line, arg.len, display.CHARS_PER_LINE },
|
||||||
);
|
);
|
||||||
std.os.exit(1);
|
std.os.exit(1);
|
||||||
}
|
}
|
||||||
|
@ -142,6 +161,117 @@ 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);
|
||||||
|
@ -162,13 +292,16 @@ 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..HEIGHT) |i| {
|
for (0..display.HEIGHT) |i| {
|
||||||
try stdout.print("{d:0>2}: {s}\n", .{ i, fmtSliceGreyscaleImage(pixels[(i * WIDTH)..((i + 1) * WIDTH)]) });
|
try stdout.print(
|
||||||
|
"{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} ** ((WIDTH * PAGES) + 1);
|
var pixels_write_command = [_]u8{0x00} ** ((display.WIDTH * display.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{
|
||||||
|
@ -228,8 +361,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 % WIDTH;
|
const column = i % display.WIDTH;
|
||||||
const page = i / WIDTH;
|
const page = i / display.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
|
||||||
|
@ -238,7 +371,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 * PAGES;
|
const row = page * display.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
|
||||||
|
@ -253,14 +386,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) * WIDTH + column] & 0x01) << 0 |
|
b.* = (pixels[(0 + row) * display.WIDTH + column] & 0x01) << 0 |
|
||||||
(pixels[(1 + row) * WIDTH + column] & 0x01) << 1 |
|
(pixels[(1 + row) * display.WIDTH + column] & 0x01) << 1 |
|
||||||
(pixels[(2 + row) * WIDTH + column] & 0x01) << 2 |
|
(pixels[(2 + row) * display.WIDTH + column] & 0x01) << 2 |
|
||||||
(pixels[(3 + row) * WIDTH + column] & 0x01) << 3 |
|
(pixels[(3 + row) * display.WIDTH + column] & 0x01) << 3 |
|
||||||
(pixels[(4 + row) * WIDTH + column] & 0x01) << 4 |
|
(pixels[(4 + row) * display.WIDTH + column] & 0x01) << 4 |
|
||||||
(pixels[(5 + row) * WIDTH + column] & 0x01) << 5 |
|
(pixels[(5 + row) * display.WIDTH + column] & 0x01) << 5 |
|
||||||
(pixels[(6 + row) * WIDTH + column] & 0x01) << 6 |
|
(pixels[(6 + row) * display.WIDTH + column] & 0x01) << 6 |
|
||||||
(pixels[(7 + row) * WIDTH + column] & 0x01) << 7;
|
(pixels[(7 + row) * display.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", .{});
|
||||||
|
@ -276,6 +409,35 @@ 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 };
|
||||||
}
|
}
|
||||||
|
@ -305,7 +467,10 @@ fn reportMagickError(mw: ?*c.MagickWand) !void {
|
||||||
fn textForLine(line: usize) []u8 {
|
fn textForLine(line: usize) []u8 {
|
||||||
return lines[line].*;
|
return lines[line].*;
|
||||||
}
|
}
|
||||||
fn convertImage(filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8, text_fn: *const fn (usize) []u8) !void {
|
fn noTextForLine(_: usize) []u8 {
|
||||||
|
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();
|
||||||
|
@ -343,7 +508,7 @@ fn convertImage(filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8, text_fn: *const f
|
||||||
// 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, WIDTH, HEIGHT);
|
const resize_dimensions = getNewDimensions(w, h, display.WIDTH, display.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 });
|
||||||
|
|
||||||
|
@ -369,21 +534,21 @@ fn convertImage(filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8, text_fn: *const f
|
||||||
// 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,
|
||||||
WIDTH,
|
display.WIDTH,
|
||||||
HEIGHT,
|
display.HEIGHT,
|
||||||
-@intCast(isize, (WIDTH - resize_dimensions.width) / 2),
|
-@intCast(isize, (display.WIDTH - resize_dimensions.width) / 2),
|
||||||
-@intCast(isize, (HEIGHT - resize_dimensions.height) / 2),
|
-@intCast(isize, (display.HEIGHT - resize_dimensions.height) / 2),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (status == c.MagickFalse)
|
if (status == c.MagickFalse)
|
||||||
return error.CouldNotSetExtent;
|
return error.CouldNotSetExtent;
|
||||||
|
|
||||||
for (0..LINES) |i| {
|
for (0..display.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 = FONT_HEIGHT * @intCast(isize, i);
|
const y: isize = display.FONT_HEIGHT * @intCast(isize, i);
|
||||||
var x: isize = BORDER_LEFT;
|
var x: isize = display.BORDER_LEFT;
|
||||||
var left_spaces: isize = 0;
|
var left_spaces: isize = 0;
|
||||||
for (text) |ch| {
|
for (text) |ch| {
|
||||||
if (ch == ' ') {
|
if (ch == ' ') {
|
||||||
|
@ -392,7 +557,7 @@ fn convertImage(filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8, text_fn: *const f
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
x += (FONT_WIDTH * left_spaces);
|
x += (display.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,12 +580,12 @@ fn convertImage(filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8, text_fn: *const f
|
||||||
if (status == c.MagickFalse)
|
if (status == c.MagickFalse)
|
||||||
return error.CouldNotQuantizeImage;
|
return error.CouldNotQuantizeImage;
|
||||||
|
|
||||||
status = c.MagickExportImagePixels(mw, 0, 0, WIDTH, HEIGHT, "I", c.CharPixel, @ptrCast(*anyopaque, pixels));
|
status = c.MagickExportImagePixels(mw, 0, 0, display.WIDTH, display.HEIGHT, "I", c.CharPixel, @ptrCast(*anyopaque, pixels));
|
||||||
|
|
||||||
if (status == c.MagickFalse)
|
if (status == c.MagickFalse)
|
||||||
return error.CouldNotExportImage;
|
return error.CouldNotExportImage;
|
||||||
|
|
||||||
for (0..WIDTH * HEIGHT) |i| {
|
for (0..display.WIDTH * display.HEIGHT) |i| {
|
||||||
switch (pixels[i]) {
|
switch (pixels[i]) {
|
||||||
0x00 => pixels[i] = 0xFF,
|
0x00 => pixels[i] = 0xFF,
|
||||||
0xFF => pixels[i] = 0x00,
|
0xFF => pixels[i] = 0x00,
|
||||||
|
@ -434,7 +599,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, FONT_WIDTH * i)),
|
-(x + @intCast(isize, display.FONT_WIDTH * i)),
|
||||||
-y,
|
-y,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -476,8 +641,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,
|
||||||
WIDTH,
|
display.WIDTH,
|
||||||
HEIGHT,
|
display.HEIGHT,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
);
|
);
|
||||||
|
@ -516,22 +681,71 @@ 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 = "-" };
|
||||||
const empty: [:0]u8 = @constCast("");
|
var empty: [:0]u8 = @constCast("");
|
||||||
for (&lines) |*line| {
|
for (&lines) |*line| {
|
||||||
line.* = ∅
|
line.* = ∅
|
||||||
}
|
}
|
||||||
const line: [:0]u8 = @constCast("Hello\\!");
|
var line: [:0]u8 = @constCast("Hello\\!");
|
||||||
lines[5] = &line;
|
lines[5] = &line;
|
||||||
var pixels: [WIDTH * HEIGHT]u8 = undefined;
|
var pixels: [display.WIDTH * display.HEIGHT]u8 = undefined;
|
||||||
|
|
||||||
var expected_pixels: *const [WIDTH * HEIGHT]u8 = @embedFile("testExpectedBytes.bin");
|
var expected_pixels: *const [display.WIDTH * display.HEIGHT]u8 = @embedFile("testExpectedBytes.bin");
|
||||||
|
|
||||||
// [_]u8{..,..,..}
|
// [_]u8{..,..,..}
|
||||||
try convertImage(opts.background_filename, &pixels, textForLine);
|
try convertImage(opts.background_filename, &pixels, noTextForLine);
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
@ -549,9 +763,3 @@ 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…
Reference in New Issue
Block a user