diff --git a/freetype/build.zig b/freetype/build.zig index 26e99abb..1bd6ebbd 100644 --- a/freetype/build.zig +++ b/freetype/build.zig @@ -23,6 +23,26 @@ pub fn build(b: *std.build.Builder) !void { const test_step = b.step("test", "Run library tests"); test_step.dependOn(&dedicated_tests.step); test_step.dependOn(&main_tests.step); + + inline for ([_][]const u8{ + "single_glyph", + "glyph_to_svg", + }) |example| { + const example_exe = b.addExecutable("example-" ++ example, "examples/" ++ example ++ ".zig"); + example_exe.setBuildMode(mode); + example_exe.setTarget(target); + example_exe.addPackage(pkg); + link(b, example_exe, .{}); + example_exe.install(); + + const example_compile_step = b.step("example-" ++ example, "Compile '" ++ example ++ "' example"); + example_compile_step.dependOn(b.getInstallStep()); + + const example_run_cmd = example_exe.run(); + example_run_cmd.step.dependOn(b.getInstallStep()); + const example_run_step = b.step("run-example-" ++ example, "Run '" ++ example ++ "' example"); + example_run_step.dependOn(&example_run_cmd.step); + } } pub const Options = struct { diff --git a/freetype/examples/glyph_to_svg.zig b/freetype/examples/glyph_to_svg.zig new file mode 100644 index 00000000..bc488126 --- /dev/null +++ b/freetype/examples/glyph_to_svg.zig @@ -0,0 +1,130 @@ +const std = @import("std"); +const freetype = @import("freetype"); + +const OutlinePrinter = struct { + library: freetype.Library, + face: freetype.Face, + output_file: std.fs.File, + path_stream: std.io.FixedBufferStream([]u8), + + xMin: isize, + yMin: isize, + width: isize, + height: isize, + + const Self = @This(); + + pub fn init(file: std.fs.File) freetype.Error!Self { + var lib = try freetype.Library.init(); + return Self{ + .library = lib, + .face = try lib.newFace("upstream/assets/FiraSans-Regular.ttf", 0), + .output_file = file, + .path_stream = std.io.fixedBufferStream(&std.mem.zeroes([1024 * 10]u8)), + .xMin = 0, + .yMin = 0, + .width = 0, + .height = 0, + }; + } + + pub fn deinit(self: Self) void { + self.library.deinit(); + } + + pub fn outlineExists(self: Self) bool { + const outline = self.face.glyph.outline() orelse return false; + if (outline.numContours() <= 0 or outline.numPoints() <= 0) + return false; + outline.check() catch return false; + return true; + } + + pub fn flipOutline(self: Self) void { + const multiplier = 65536; + const matrix = freetype.Matrix{ + .xx = 1 * multiplier, + .xy = 0 * multiplier, + .yx = 0 * multiplier, + .yy = -1 * multiplier, + }; + self.face.glyph.outline().?.transform(matrix); + } + + pub fn extractOutline(self: *Self) !void { + try self.path_stream.writer().writeAll(""); + } + + pub fn computeViewBox(self: *Self) !void { + const boundingBox = try self.face.glyph.outline().?.bbox(); + self.xMin = boundingBox.xMin; + self.yMin = boundingBox.yMin; + self.width = boundingBox.xMax - boundingBox.xMin; + self.height = boundingBox.yMax - boundingBox.yMin; + } + + pub fn printSVG(self: Self) !void { + try self.output_file.writer().print( + \\ + \\ {s} + \\ + , .{ self.xMin, self.yMin, self.width, self.height, self.path_stream.getWritten() }); + } + + pub fn moveToFunction(to: [*c]const freetype.Vector, self_ptr: ?*anyopaque) callconv(.C) c_int { + var self = @ptrCast(*Self, @alignCast(std.meta.alignment(Self), self_ptr)); + self.path_stream.writer().print("M {d} {d}\t", .{ to.*.x, to.*.y }) catch unreachable; + return 0; + } + + pub fn lineToFunction(to: [*c]const freetype.Vector, self_ptr: ?*anyopaque) callconv(.C) c_int { + var self = @ptrCast(*Self, @alignCast(std.meta.alignment(Self), self_ptr)); + self.path_stream.writer().print("L {d} {d}\t", .{ to.*.x, to.*.y }) catch unreachable; + return 0; + } + + pub fn conicToFunction(control: [*c]const freetype.Vector, to: [*c]const freetype.Vector, self_ptr: ?*anyopaque) callconv(.C) c_int { + var self = @ptrCast(*Self, @alignCast(std.meta.alignment(Self), self_ptr)); + self.path_stream.writer().print("Q {d} {d}, {d} {d}\t", .{ control.*.x, control.*.y, to.*.x, to.*.y }) catch unreachable; + return 0; + } + + pub fn cubicToFunction(control_0: [*c]const freetype.Vector, control_1: [*c]const freetype.Vector, to: [*c]const freetype.Vector, self_ptr: ?*anyopaque) callconv(.C) c_int { + var self = @ptrCast(*Self, @alignCast(std.meta.alignment(Self), self_ptr)); + self.path_stream.writer().print("C {d} {d}, {d} {d}, {d} {d}\t", .{ control_0.*.x, control_0.*.y, control_1.*.x, control_1.*.y, to.*.x, to.*.y }) catch unreachable; + return 0; + } + + pub fn run(self: *Self, symbol: u8) !void { + try self.face.loadChar(symbol, .{ .no_scale = true, .no_bitmap = true }); + + if (!self.outlineExists()) + return error.OutlineDoesntExists; + + self.flipOutline(); + try self.extractOutline(); + try self.computeViewBox(); + try self.printSVG(); + } +}; + +pub fn main() !void { + var file = try std.fs.cwd().createFile("out.svg", .{}); + defer file.close(); + + var outline_printer = try OutlinePrinter.init(file); + defer outline_printer.deinit(); + try outline_printer.run('a'); +}