Last active
June 28, 2026 12:08
-
-
Save andy0130tw/ff348b55f494acdf305864ce93ba44f0 to your computer and use it in GitHub Desktop.
My first Zig project!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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