Compare commits

..

2 commits

Author SHA1 Message Date
65c0b9445f
add lib 2024-05-08 11:52:55 +02:00
c4adef4d12
build note 2024-05-08 11:01:32 +02:00
6 changed files with 145 additions and 122 deletions

View file

@ -1,9 +1,17 @@
# Absolutely Worthless Binfon # Absolutely Worthless Binfon
Takes all your precious fonts and puts them in the BIN! Takes all your precious fonts and puts them in BINs!
Allows embedding of bitmap fonts into easily embeddable binary blobs. Allows embedding of bitmap fonts into easily embeddable binary blobs.
## Building
`zig build` builds two targets:
- `binfon` is the actual executable that runs the bdf to bin transformation (see Usage below)
- `binfon-viewer` takes a .bin and prints its glyphs to console
## Usage
The inputs must be a .bdf file and a .json configuration file. The JSON configuration file must be formatted as follows: The inputs must be a .bdf file and a .json configuration file. The JSON configuration file must be formatted as follows:
```json ```json

View file

@ -15,6 +15,7 @@ pub fn build(b: *std.Build) void {
// set a preferred release mode, allowing the user to decide how to optimize. // set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
// Main executable
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "binfon", .name = "binfon",
.root_source_file = b.path("src/main.zig"), .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); run_step.dependOn(&run_cmd.step);
// Viewer debug app // Viewer debug app
const viewer = b.addExecutable(.{ const viewer = b.addExecutable(.{
.name = "binfon-viewer", .name = "binfon-viewer",
.root_source_file = b.path("src/view.zig"), .root_source_file = b.path("src/view.zig"),
@ -51,4 +51,65 @@ pub fn build(b: *std.Build) void {
run_cmd.addArgs(args); run_cmd.addArgs(args);
view_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;
} }

View file

@ -1,67 +1,12 @@
.{ .{
.name = "binfon", .name = "binfon",
// This is a [Semantic Version](https://semver.org/). .version = "0.1.0",
// In a future version of Zig it will be used for package deduplication. .minimum_zig_version = "0.12.0",
.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 <url>` 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.
.paths = .{ .paths = .{
// This makes *all* files, recursively, included in this package. It is generally "build.zig",
// better to explicitly list the files and directories instead, to insure that "build.zig.zon",
// fetching from tarballs, file system paths, and version control all result "src",
// in the same contents hash. "LICENSE",
"", "README.md",
// For example...
//"build.zig",
//"build.zig.zon",
//"src",
//"LICENSE",
//"README.md",
}, },
} }

View file

@ -30,6 +30,58 @@ pub const Font = struct {
allocator.free(self.name); allocator.free(self.name);
self.characters.deinit(); 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 { const Command = enum {
@ -50,58 +102,6 @@ const ParsedCommand = union(Command) {
_IGNORED: void, _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 { fn parseLine(allocator: std.mem.Allocator, line: []const u8) !ParsedCommand {
const firstSpace = std.mem.indexOfScalar(u8, line, ' ') orelse line.len; const firstSpace = std.mem.indexOfScalar(u8, line, ' ') orelse line.len;
const command = std.meta.stringToEnum(Command, line[0..firstSpace]) orelse Command._IGNORED; const command = std.meta.stringToEnum(Command, line[0..firstSpace]) orelse Command._IGNORED;

13
src/lib.zig Normal file
View file

@ -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);
}

View file

@ -1,6 +1,5 @@
const std = @import("std"); const std = @import("std");
const bdf = @import("bdf.zig"); const lib = @import("lib.zig");
const bin = @import("bin.zig");
const FontConfig = struct { const FontConfig = struct {
inputFile: []const u8, inputFile: []const u8,
@ -36,14 +35,11 @@ pub fn main() !void {
const input = try std.fs.cwd().openFile(fontConfig.value.inputFile, .{}); const input = try std.fs.cwd().openFile(fontConfig.value.inputFile, .{});
defer input.close(); defer input.close();
var font = try bdf.parse(allocator, input.reader());
defer font.deinit(allocator);
// Write output // Write output
const output = try std.fs.cwd().createFile(fontConfig.value.outputFile, .{}); const output = try std.fs.cwd().createFile(fontConfig.value.outputFile, .{});
defer output.close(); 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) { fn readConfig(allocator: std.mem.Allocator, input: []u8) !std.json.Parsed(FontConfig) {