Skip to content

Instantly share code, notes, and snippets.

@andy0130tw
Last active June 28, 2026 12:08
Show Gist options
  • Select an option

  • Save andy0130tw/ff348b55f494acdf305864ce93ba44f0 to your computer and use it in GitHub Desktop.

Select an option

Save andy0130tw/ff348b55f494acdf305864ce93ba44f0 to your computer and use it in GitHub Desktop.
My first Zig project!
const std = @import("std");
const freetypeWrapper = @import("freetype.zig");
const freetype = freetypeWrapper.freetype;
const ftUnwrap = freetypeWrapper.ftUnwrap;
const shstw = @embedFile("shstw-hinted.ttf");
var ftlib: freetype.FT_Library = null;
var face: freetype.FT_Face = null;
const cFTVec = [*c] const freetype.FT_Vector;
const Context = struct {
writer: *std.Io.Writer,
};
fn appendFmt(user: ?*anyopaque, comptime fmt: []const u8, args: anytype) void {
const ctx: *Context = @ptrCast(@alignCast(user.?));
ctx.*.writer.print(fmt, args) catch |err| {
std.log.err("Write failed {}", .{err});
};
}
fn move_to(to: cFTVec, user: ?*anyopaque) callconv(.c) c_int {
appendFmt(user, "M {},{} ", .{to.*.x, -to.*.y});
return 0;
}
fn line_to(to: cFTVec, user: ?*anyopaque) callconv(.c) c_int {
appendFmt(user, "L {},{} ", .{to.*.x, -to.*.y});
return 0;
}
fn conic_to(c: cFTVec, to: cFTVec, user: ?*anyopaque) callconv(.c) c_int {
appendFmt(user, "Q {},{} {},{} ", .{c.*.x, -c.*.y, to.*.x, -to.*.y});
return 0;
}
fn cubic_to(_: cFTVec, _: cFTVec, _: cFTVec, _: ?*anyopaque) callconv(.c) c_int {
unreachable;
}
fn toSVGPath(outline: freetype.FT_Outline, allocator: std.mem.Allocator) ![]u8 {
var writer = std.Io.Writer.Allocating.init(allocator);
defer writer.deinit();
const outlineFuncs = freetype.FT_Outline_Funcs{
.move_to = move_to,
.line_to = line_to,
.conic_to = conic_to,
.cubic_to = cubic_to,
.shift = 0,
.delta = 0,
};
var ctx = Context {
.writer = &writer.writer,
};
try ftUnwrap(
freetype.FT_Outline_Decompose(
@constCast(&outline),
&outlineFuncs,
&ctx));
_ = try writer.writer.write("Z");
const bigstr = try writer.toOwnedSlice();
return bigstr;
}
pub fn main(_: std.process.Init) !void {
const failpart, const errcode = _initialize();
if (failpart == 1) {
std.log.err("FT_Init_FreeType failed: {}", .{errcode});
return ftUnwrap(errcode);
} else if (failpart == 2) {
std.log.err("FT_New_Memory_Face failed: {}", .{errcode});
return ftUnwrap(errcode);
}
if (failpart != 0) unreachable;
std.log.info("Loaded font: {s}", .{
std.mem.span(face.*.family_name),
});
_ = setPixelSize(21);
_ = loadCodePoint('籲', 0);
const bigstr = try toSVGPath(face.*.glyph.*.outline, std.heap.c_allocator);
std.log.info("output: [{s}]", .{bigstr});
// const bmp = face.*.glyph.*.bitmap;
// const pitch: usize = @intCast(bmp.pitch);
// for (0..@as(usize, @intCast(bmp.rows))) |y| {
// const row = bmp.buffer[y * pitch .. (y + 1) * pitch];
// for (0..@as(usize, @intCast(bmp.width))) |x| {
// const byte = row[x / 8];
// const mask: u8 = @as(u8, 0x80) >> @intCast(x % 8);
// if ((byte & mask) != 0)
// std.debug.print("#", .{})
// else
// std.debug.print(" ", .{});
// }
// std.debug.print("\n", .{});
// }
}
export fn _initialize() @Vector(2, c_int) {
if (ftlib == null) {
const err = freetype.FT_Init_FreeType(&ftlib);
if (err != 0) {
return .{ 1, err };
}
}
if (face == null) {
const err = freetype.FT_New_Memory_Face(
ftlib,
shstw.ptr,
@intCast(shstw.len),
0,
&face);
if (err != 0) {
return .{ 2, err };
// return err;
}
}
return .{ 0, 0 };
}
export fn setPixelSize(size: u32) i32 {
const err = freetype.FT_Set_Pixel_Sizes(face, 0, size);
if (err != 0) {
std.log.err("FT_Set_Pixel_Sizes failed: {}", .{err});
}
return err;
}
export fn loadCodePoint(codepoint: u32, hinttype: u32) i32 {
const glyph_index = freetype.FT_Get_Char_Index(face, codepoint);
if (glyph_index == 0)
return -0xff;
const loadFlags = freetype.FT_LOAD_RENDER | switch (hinttype) {
1 => freetype.FT_LOAD_NO_HINTING,
2 => freetype.FT_LOAD_FORCE_AUTOHINT,
else => 0,
};
ftUnwrap(freetype.FT_Load_Glyph(
face,
glyph_index,
freetype.FT_LOAD_MONOCHROME | loadFlags,
)) catch |err| {
std.log.err("FT_Load_Glyph failed: {}\n", .{err});
return -1;
};
return 0;
}
const ft = @cImport({
@cInclude("ft2build.h");
@cInclude("freetype/freetype.h");
@cInclude("freetype/ftoutln.h");
});
pub const freetype = ft;
pub const FreeTypeError = error {
CannotOpenResource,
UnknownFileFormat,
InvalidFileFormat,
InvalidVersion,
LowerModuleVersion,
InvalidArgument,
UnimplementedFeature,
InvalidTable,
InvalidOffset,
ArrayTooLarge,
MissingModule,
MissingProperty,
InvalidGlyphIndex,
InvalidCharacterCode,
InvalidGlyphFormat,
CannotRenderGlyph,
InvalidOutline,
InvalidComposite,
TooManyHints,
InvalidPixelSize,
InvalidSVGDocument,
InvalidHandle,
InvalidLibraryHandle,
InvalidDriverHandle,
InvalidFaceHandle,
InvalidSizeHandle,
InvalidSlotHandle,
InvalidCharMapHandle,
InvalidCacheHandle,
InvalidStreamHandle,
TooManyDrivers,
TooManyExtensions,
OutOfMemory,
UnlistedObject,
CannotOpenStream,
InvalidStreamSeek,
InvalidStreamSkip,
InvalidStreamRead,
InvalidStreamOperation,
InvalidFrameOperation,
NestedFrameAccess,
InvalidFrameRead,
RasterUninitialized,
RasterCorrupted,
RasterOverflow,
RasterNegativeHeight,
TooManyCaches,
InvalidOpcode,
TooFewArguments,
StackOverflow,
CodeOverflow,
BadArgument,
DivideByZero,
InvalidReference,
DebugOpCode,
ENDFInExecStream,
NestedDEFS,
InvalidCodeRange,
ExecutionTooLong,
TooManyFunctionDefs,
TooManyInstructionDefs,
TableMissing,
HorizHeaderMissing,
LocationsMissing,
NameTableMissing,
CMapTableMissing,
HmtxTableMissing,
PostTableMissing,
InvalidHorizMetrics,
InvalidCharMapFormat,
InvalidPPem,
InvalidVertMetrics,
CouldNotFindContext,
InvalidPostTableFormat,
InvalidPostTable,
DEFInGlyfBytecode,
MissingBitmap,
MissingSVGHooks,
SyntaxError,
StackUnderflow,
Ignore,
NoUnicodeGlyphName,
GlyphTooBig,
MissingStartfontField,
MissingFontField,
MissingSizeField,
MissingFontboundingboxField,
MissingCharsField,
MissingStartcharField,
MissingEncodingField,
MissingBbxField,
BbxTooBig,
CorruptedFontHeader,
CorruptedFontGlyphs,
};
pub fn ftUnwrap(num: c_int) FreeTypeError!void {
return switch (num) {
ft.FT_Err_Ok => {},
ft.FT_Err_Cannot_Open_Resource => FreeTypeError.CannotOpenResource,
ft.FT_Err_Unknown_File_Format => FreeTypeError.UnknownFileFormat,
ft.FT_Err_Invalid_File_Format => FreeTypeError.InvalidFileFormat,
ft.FT_Err_Invalid_Version => FreeTypeError.InvalidVersion,
ft.FT_Err_Lower_Module_Version => FreeTypeError.LowerModuleVersion,
ft.FT_Err_Invalid_Argument => FreeTypeError.InvalidArgument,
ft.FT_Err_Unimplemented_Feature => FreeTypeError.UnimplementedFeature,
ft.FT_Err_Invalid_Table => FreeTypeError.InvalidTable,
ft.FT_Err_Invalid_Offset => FreeTypeError.InvalidOffset,
ft.FT_Err_Array_Too_Large => FreeTypeError.ArrayTooLarge,
ft.FT_Err_Missing_Module => FreeTypeError.MissingModule,
ft.FT_Err_Missing_Property => FreeTypeError.MissingProperty,
ft.FT_Err_Invalid_Glyph_Index => FreeTypeError.InvalidGlyphIndex,
ft.FT_Err_Invalid_Character_Code => FreeTypeError.InvalidCharacterCode,
ft.FT_Err_Invalid_Glyph_Format => FreeTypeError.InvalidGlyphFormat,
ft.FT_Err_Cannot_Render_Glyph => FreeTypeError.CannotRenderGlyph,
ft.FT_Err_Invalid_Outline => FreeTypeError.InvalidOutline,
ft.FT_Err_Invalid_Composite => FreeTypeError.InvalidComposite,
ft.FT_Err_Too_Many_Hints => FreeTypeError.TooManyHints,
ft.FT_Err_Invalid_Pixel_Size => FreeTypeError.InvalidPixelSize,
ft.FT_Err_Invalid_SVG_Document => FreeTypeError.InvalidSVGDocument,
ft.FT_Err_Invalid_Handle => FreeTypeError.InvalidHandle,
ft.FT_Err_Invalid_Library_Handle => FreeTypeError.InvalidLibraryHandle,
ft.FT_Err_Invalid_Driver_Handle => FreeTypeError.InvalidDriverHandle,
ft.FT_Err_Invalid_Face_Handle => FreeTypeError.InvalidFaceHandle,
ft.FT_Err_Invalid_Size_Handle => FreeTypeError.InvalidSizeHandle,
ft.FT_Err_Invalid_Slot_Handle => FreeTypeError.InvalidSlotHandle,
ft.FT_Err_Invalid_CharMap_Handle => FreeTypeError.InvalidCharMapHandle,
ft.FT_Err_Invalid_Cache_Handle => FreeTypeError.InvalidCacheHandle,
ft.FT_Err_Invalid_Stream_Handle => FreeTypeError.InvalidStreamHandle,
ft.FT_Err_Too_Many_Drivers => FreeTypeError.TooManyDrivers,
ft.FT_Err_Too_Many_Extensions => FreeTypeError.TooManyExtensions,
ft.FT_Err_Out_Of_Memory => FreeTypeError.OutOfMemory,
ft.FT_Err_Unlisted_Object => FreeTypeError.UnlistedObject,
ft.FT_Err_Cannot_Open_Stream => FreeTypeError.CannotOpenStream,
ft.FT_Err_Invalid_Stream_Seek => FreeTypeError.InvalidStreamSeek,
ft.FT_Err_Invalid_Stream_Skip => FreeTypeError.InvalidStreamSkip,
ft.FT_Err_Invalid_Stream_Read => FreeTypeError.InvalidStreamRead,
ft.FT_Err_Invalid_Stream_Operation => FreeTypeError.InvalidStreamOperation,
ft.FT_Err_Invalid_Frame_Operation => FreeTypeError.InvalidFrameOperation,
ft.FT_Err_Nested_Frame_Access => FreeTypeError.NestedFrameAccess,
ft.FT_Err_Invalid_Frame_Read => FreeTypeError.InvalidFrameRead,
ft.FT_Err_Raster_Uninitialized => FreeTypeError.RasterUninitialized,
ft.FT_Err_Raster_Corrupted => FreeTypeError.RasterCorrupted,
ft.FT_Err_Raster_Overflow => FreeTypeError.RasterOverflow,
ft.FT_Err_Raster_Negative_Height => FreeTypeError.RasterNegativeHeight,
ft.FT_Err_Too_Many_Caches => FreeTypeError.TooManyCaches,
ft.FT_Err_Invalid_Opcode => FreeTypeError.InvalidOpcode,
ft.FT_Err_Too_Few_Arguments => FreeTypeError.TooFewArguments,
ft.FT_Err_Stack_Overflow => FreeTypeError.StackOverflow,
ft.FT_Err_Code_Overflow => FreeTypeError.CodeOverflow,
ft.FT_Err_Bad_Argument => FreeTypeError.BadArgument,
ft.FT_Err_Divide_By_Zero => FreeTypeError.DivideByZero,
ft.FT_Err_Invalid_Reference => FreeTypeError.InvalidReference,
ft.FT_Err_Debug_OpCode => FreeTypeError.DebugOpCode,
ft.FT_Err_ENDF_In_Exec_Stream => FreeTypeError.ENDFInExecStream,
ft.FT_Err_Nested_DEFS => FreeTypeError.NestedDEFS,
ft.FT_Err_Invalid_CodeRange => FreeTypeError.InvalidCodeRange,
ft.FT_Err_Execution_Too_Long => FreeTypeError.ExecutionTooLong,
ft.FT_Err_Too_Many_Function_Defs => FreeTypeError.TooManyFunctionDefs,
ft.FT_Err_Too_Many_Instruction_Defs => FreeTypeError.TooManyInstructionDefs,
ft.FT_Err_Table_Missing => FreeTypeError.TableMissing,
ft.FT_Err_Horiz_Header_Missing => FreeTypeError.HorizHeaderMissing,
ft.FT_Err_Locations_Missing => FreeTypeError.LocationsMissing,
ft.FT_Err_Name_Table_Missing => FreeTypeError.NameTableMissing,
ft.FT_Err_CMap_Table_Missing => FreeTypeError.CMapTableMissing,
ft.FT_Err_Hmtx_Table_Missing => FreeTypeError.HmtxTableMissing,
ft.FT_Err_Post_Table_Missing => FreeTypeError.PostTableMissing,
ft.FT_Err_Invalid_Horiz_Metrics => FreeTypeError.InvalidHorizMetrics,
ft.FT_Err_Invalid_CharMap_Format => FreeTypeError.InvalidCharMapFormat,
ft.FT_Err_Invalid_PPem => FreeTypeError.InvalidPPem,
ft.FT_Err_Invalid_Vert_Metrics => FreeTypeError.InvalidVertMetrics,
ft.FT_Err_Could_Not_Find_Context => FreeTypeError.CouldNotFindContext,
ft.FT_Err_Invalid_Post_Table_Format => FreeTypeError.InvalidPostTableFormat,
ft.FT_Err_Invalid_Post_Table => FreeTypeError.InvalidPostTable,
ft.FT_Err_DEF_In_Glyf_Bytecode => FreeTypeError.DEFInGlyfBytecode,
ft.FT_Err_Missing_Bitmap => FreeTypeError.MissingBitmap,
ft.FT_Err_Missing_SVG_Hooks => FreeTypeError.MissingSVGHooks,
ft.FT_Err_Syntax_Error => FreeTypeError.SyntaxError,
ft.FT_Err_Stack_Underflow => FreeTypeError.StackUnderflow,
ft.FT_Err_Ignore => FreeTypeError.Ignore,
ft.FT_Err_No_Unicode_Glyph_Name => FreeTypeError.NoUnicodeGlyphName,
ft.FT_Err_Glyph_Too_Big => FreeTypeError.GlyphTooBig,
ft.FT_Err_Missing_Startfont_Field => FreeTypeError.MissingStartfontField,
ft.FT_Err_Missing_Font_Field => FreeTypeError.MissingFontField,
ft.FT_Err_Missing_Size_Field => FreeTypeError.MissingSizeField,
ft.FT_Err_Missing_Fontboundingbox_Field => FreeTypeError.MissingFontboundingboxField,
ft.FT_Err_Missing_Chars_Field => FreeTypeError.MissingCharsField,
ft.FT_Err_Missing_Startchar_Field => FreeTypeError.MissingStartcharField,
ft.FT_Err_Missing_Encoding_Field => FreeTypeError.MissingEncodingField,
ft.FT_Err_Missing_Bbx_Field => FreeTypeError.MissingBbxField,
ft.FT_Err_Bbx_Too_Big => FreeTypeError.BbxTooBig,
ft.FT_Err_Corrupted_Font_Header => FreeTypeError.CorruptedFontHeader,
ft.FT_Err_Corrupted_Font_Glyphs => FreeTypeError.CorruptedFontGlyphs,
else => @panic("Unknown FreeType error"),
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment