add dedicated font generation exe for the build
This commit is contained in:
		
							parent
							
								
									f1e635d786
								
							
						
					
					
						commit
						08b30a336f
					
				
					 2 changed files with 258 additions and 0 deletions
				
			
		
							
								
								
									
										10
									
								
								build.zig
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								build.zig
									
										
									
									
									
								
							|  | @ -46,6 +46,16 @@ pub fn build(b: *std.build.Builder) !void { | |||
|     exe.addIncludePath("lib/i2cdriver"); | ||||
|     exe.install(); | ||||
| 
 | ||||
|     const exe_fontgen = b.addExecutable(.{ | ||||
|         .name = "fontgen", | ||||
|         .root_source_file = .{ .path = "src/fontgen.zig" }, | ||||
|         .target = target, | ||||
|         .optimize = optimize, | ||||
|     }); | ||||
|     exe_fontgen.linkLibrary(im_dep.artifact("MagickWand")); | ||||
|     exe_fontgen.linkLibrary(z_dep.artifact("z")); | ||||
|     exe.step.dependOn(&exe_fontgen.run().step); | ||||
| 
 | ||||
|     // TODO: I believe we can use runArtifact on a second | ||||
|     // exe with a different source file for font generation | ||||
|     // taking us to a series of 5 byte arrays for each | ||||
|  |  | |||
							
								
								
									
										248
									
								
								src/fontgen.zig
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								src/fontgen.zig
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,248 @@ | |||
| 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"); | ||||
| }); | ||||
| 
 | ||||
| // 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 = 5; | ||||
| const GLYPH_HEIGHT = 8; | ||||
| 
 | ||||
| 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 => {}, | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		
		Reference in a new issue