diff --git a/build.zig b/build.zig index f72422d..ca40a07 100644 --- a/build.zig +++ b/build.zig @@ -15,6 +15,7 @@ pub fn build(b: *std.Build) void { // set a preferred release mode, allowing the user to decide how to optimize. const optimize = b.standardOptimizeOption(.{}); + // Main executable const exe = b.addExecutable(.{ .name = "binfon", .root_source_file = b.path("src/main.zig"), @@ -31,7 +32,6 @@ pub fn build(b: *std.Build) void { run_step.dependOn(&run_cmd.step); // Viewer debug app - const viewer = b.addExecutable(.{ .name = "binfon-viewer", .root_source_file = b.path("src/view.zig"), @@ -51,4 +51,65 @@ pub fn build(b: *std.Build) void { run_cmd.addArgs(args); view_cmd.addArgs(args); } + + // Tests + const bdf_unit_tests = b.addTest(.{ + .root_source_file = b.path("src/bdf.zig"), + .target = target, + .optimize = optimize, + }); + + const run_bdf_unit_tests = b.addRunArtifact(bdf_unit_tests); + + // Similar to creating the run step earlier, this exposes a `test` step to + // the `zig build --help` menu, providing a way for the user to request + // running the unit tests. + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_bdf_unit_tests.step); + + // Module + _ = b.addModule("binfon", .{ + .root_source_file = .{ .path = "src/lib.zig" }, + }); + + // Library + const libbinfon = buildLibrary(b, .{ + .target = target, + .optimize = optimize, + }); + + const libzimalloc_install = b.addInstallArtifact(libbinfon, .{}); + b.getInstallStep().dependOn(&libzimalloc_install.step); +} + +const ModuleOptions = struct { + target: std.Build.ResolvedTarget, + optimize: std.builtin.Mode, + linkage: std.builtin.LinkMode = .dynamic, + pic: ?bool = null, +}; + +fn buildLibrary(b: *std.Build, options: ModuleOptions) *std.Build.Step.Compile { + const version = std.SemanticVersion{ .major = 0, .minor = 1, .patch = 0 }; + + const library = switch (options.linkage) { + .dynamic => b.addSharedLibrary(.{ + .name = "binfon", + .root_source_file = .{ .path = "src/lib.zig" }, + .version = version, + .target = options.target, + .optimize = options.optimize, + .pic = options.pic, + }), + .static => b.addStaticLibrary(.{ + .name = "binfon", + .root_source_file = .{ .path = "src/lib.zig" }, + .version = version, + .target = options.target, + .optimize = options.optimize, + .pic = options.pic, + }), + }; + + return library; } diff --git a/build.zig.zon b/build.zig.zon index b0d4140..58f2806 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,67 +1,12 @@ .{ .name = "binfon", - // This is a [Semantic Version](https://semver.org/). - // In a future version of Zig it will be used for package deduplication. - .version = "0.0.0", - - // This field is optional. - // This is currently advisory only; Zig does not yet do anything - // with this value. - //.minimum_zig_version = "0.11.0", - - // This field is optional. - // Each dependency must either provide a `url` and `hash`, or a `path`. - // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. - // Once all dependencies are fetched, `zig build` no longer requires - // internet connectivity. - .dependencies = .{ - // See `zig fetch --save ` for a command-line interface for adding dependencies. - //.example = .{ - // // When updating this field to a new URL, be sure to delete the corresponding - // // `hash`, otherwise you are communicating that you expect to find the old hash at - // // the new URL. - // .url = "https://example.com/foo.tar.gz", - // - // // This is computed from the file contents of the directory of files that is - // // obtained after fetching `url` and applying the inclusion rules given by - // // `paths`. - // // - // // This field is the source of truth; packages do not come from a `url`; they - // // come from a `hash`. `url` is just one of many possible mirrors for how to - // // obtain a package matching this `hash`. - // // - // // Uses the [multihash](https://multiformats.io/multihash/) format. - // .hash = "...", - // - // // When this is provided, the package is found in a directory relative to the - // // build root. In this case the package's hash is irrelevant and therefore not - // // computed. This field and `url` are mutually exclusive. - // .path = "foo", - - // // When this is set to `true`, a package is declared to be lazily - // // fetched. This makes the dependency only get fetched if it is - // // actually used. - // .lazy = false, - //}, - }, - - // Specifies the set of files and directories that are included in this package. - // Only files and directories listed here are included in the `hash` that - // is computed for this package. - // Paths are relative to the build root. Use the empty string (`""`) to refer to - // the build root itself. - // A directory listed here means that all files within, recursively, are included. + .version = "0.1.0", + .minimum_zig_version = "0.12.0", .paths = .{ - // This makes *all* files, recursively, included in this package. It is generally - // better to explicitly list the files and directories instead, to insure that - // fetching from tarballs, file system paths, and version control all result - // in the same contents hash. - "", - // For example... - //"build.zig", - //"build.zig.zon", - //"src", - //"LICENSE", - //"README.md", + "build.zig", + "build.zig.zon", + "src", + "LICENSE", + "README.md", }, } diff --git a/src/bdf.zig b/src/bdf.zig index bed10de..8f85cc2 100644 --- a/src/bdf.zig +++ b/src/bdf.zig @@ -30,6 +30,58 @@ pub const Font = struct { allocator.free(self.name); self.characters.deinit(); } + + pub fn parse(allocator: std.mem.Allocator, bdf: anytype) !Font { + var font: Font = .{ + .name = undefined, + .height = 0, + .width = 0, + .characters = CharacterMap.init(allocator), + }; + + var bufferedReader = std.io.bufferedReader(bdf); + var reader = bufferedReader.reader(); + + var arenaAllocator = std.heap.ArenaAllocator.init(allocator); + defer arenaAllocator.deinit(); + + var currentName: []u8 = undefined; + var currentCharacter: u16 = 0; + while (true) { + var lineBuffer: [1024]u8 = undefined; + const nextLine = try reader.readUntilDelimiterOrEof(&lineBuffer, '\n') orelse break; + + // Parse line + const line = try parseLine(arenaAllocator.allocator(), nextLine); + switch (line) { + .FONT => |name| { + font.name = try allocator.dupe(u8, name); + std.log.debug("Font: {s}", .{name}); + }, + .FONTBOUNDINGBOX => |box| { + font.width = box.width; + font.height = box.height; + std.log.debug("Bounding box: {d}x{d}", .{ box.width, box.height }); + }, + .STARTCHAR => |name| { + currentName = try allocator.dupe(u8, name); + }, + .ENCODING => |num| { + currentCharacter = num; + }, + .BITMAP => { + const bitmap = try readBitmap(allocator, font.width, font.height, reader); + try font.characters.put(currentCharacter, .{ + .name = currentName, + .bitmap = bitmap, + }); + }, + ._IGNORED => {}, + } + } + + return font; + } }; const Command = enum { @@ -50,58 +102,6 @@ const ParsedCommand = union(Command) { _IGNORED: void, }; -pub fn parse(allocator: std.mem.Allocator, bdf: anytype) !Font { - var font: Font = .{ - .name = undefined, - .height = 0, - .width = 0, - .characters = CharacterMap.init(allocator), - }; - - var bufferedReader = std.io.bufferedReader(bdf); - var reader = bufferedReader.reader(); - - var arenaAllocator = std.heap.ArenaAllocator.init(allocator); - defer arenaAllocator.deinit(); - - var currentName: []u8 = undefined; - var currentCharacter: u16 = 0; - while (true) { - var lineBuffer: [1024]u8 = undefined; - const nextLine = try reader.readUntilDelimiterOrEof(&lineBuffer, '\n') orelse break; - - // Parse line - const line = try parseLine(arenaAllocator.allocator(), nextLine); - switch (line) { - .FONT => |name| { - font.name = try allocator.dupe(u8, name); - std.log.debug("Font: {s}", .{name}); - }, - .FONTBOUNDINGBOX => |box| { - font.width = box.width; - font.height = box.height; - std.log.debug("Bounding box: {d}x{d}", .{ box.width, box.height }); - }, - .STARTCHAR => |name| { - currentName = try allocator.dupe(u8, name); - }, - .ENCODING => |num| { - currentCharacter = num; - }, - .BITMAP => { - const bitmap = try readBitmap(allocator, font.width, font.height, reader); - try font.characters.put(currentCharacter, .{ - .name = currentName, - .bitmap = bitmap, - }); - }, - ._IGNORED => {}, - } - } - - return font; -} - fn parseLine(allocator: std.mem.Allocator, line: []const u8) !ParsedCommand { const firstSpace = std.mem.indexOfScalar(u8, line, ' ') orelse line.len; const command = std.meta.stringToEnum(Command, line[0..firstSpace]) orelse Command._IGNORED; diff --git a/src/lib.zig b/src/lib.zig new file mode 100644 index 0000000..1ce1946 --- /dev/null +++ b/src/lib.zig @@ -0,0 +1,13 @@ +const std = @import("std"); +const bdf = @import("bdf.zig"); +const bin = @import("bin.zig"); + +pub const Font = bdf.Font; +pub const writeGlyphs = bin.writeGlyphs; + +pub fn convert(allocator: std.mem.Allocator, input: anytype, output: anytype, glyphs: []const u16) !void { + var font = try bdf.Font.parse(allocator, input); + defer font.deinit(allocator); + + try bin.writeGlyphs(output, font, glyphs); +} diff --git a/src/main.zig b/src/main.zig index 9a558eb..5b59669 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,5 @@ const std = @import("std"); -const bdf = @import("bdf.zig"); -const bin = @import("bin.zig"); +const lib = @import("lib.zig"); const FontConfig = struct { inputFile: []const u8, @@ -36,14 +35,11 @@ pub fn main() !void { const input = try std.fs.cwd().openFile(fontConfig.value.inputFile, .{}); defer input.close(); - var font = try bdf.parse(allocator, input.reader()); - defer font.deinit(allocator); - // Write output const output = try std.fs.cwd().createFile(fontConfig.value.outputFile, .{}); defer output.close(); - try bin.writeGlyphs(output.writer(), font, fontConfig.value.glyphs); + try lib.convert(allocator, input.reader(), output.writer(), fontConfig.value.glyphs); } fn readConfig(allocator: std.mem.Allocator, input: []u8) !std.json.Parsed(FontConfig) {