# Using freetype c in zig

**URL:** <https://ziggit.dev/t/using-freetype-c-in-zig/16611>\
**Category:** Help\
**Tags:** language, c\
**Created:** [July 9, 2026, 5:40pm UTC](https://ziggit.dev/t/using-freetype-c-in-zig/16611 "2026-07-09T17:40:29Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![idk](https://ziggit.dev/user_avatar/ziggit.dev/idk/32/9520_2.png) [@idk](https://ziggit.dev/u/idk)\
**Post date:** [July 9, 2026, 5:40pm UTC](https://ziggit.dev/t/using-freetype-c-in-zig/16611/1 "2026-07-09T17:40:29Z")

</div>

```zig
const std = @import("std");
const Io = std.Io;
const pow = std.math.pow;
const gl = @cImport({
    @cDefine("GLAD_GL_IMPLEMENTATION", "");
    @cInclude("glad/gl.h");
    @cDefine("GLFW_INCLUDE_NONE", "");
    @cInclude("GLFW/glfw3.h");
    @cInclude("GL/gl.h");
});
const ftp = @cImport({
    @cInclude("freetype2/ft2build.h");
    @cInclude("freetype2/freetype/freetype.h");
    @cInclude("freetype2/freetype/config/ftconfig.h");
});

const SCREEN_WIDTH = 1000;
const SCREEN_HEIGHT = 800;
const PI = 3.14159265;

fn getShaderFile(init:std.process.Init,filename:[]const u8) []u8 {
    const gpa = init.gpa;
    const dir = Io.Dir.cwd();

    const file = dir.readFileAlloc(init.io, filename, gpa, .unlimited) catch @panic("shader file not found");

    return file;
}

fn orthoProjection(w: f32, h: f32) [4]@Vector(4, f32) {
    return .{
        .{ 2.0 / w, 0.0, 0.0, -1.0 },
        .{ 0.0, -2.0 / h, 0.0, 1.0 },
        .{ 0.0, 0.0, -1.0, 0.0 },
        .{ 0.0, 0.0, 0.0, 1.0 }
    };
}

pub fn main(init:std.process.Init) !void {
    _ = gl.glfwInit();
    defer gl.glfwTerminate();

    gl.glfwWindowHint(gl.GLFW_CONTEXT_VERSION_MAJOR, 3);
    gl.glfwWindowHint(gl.GLFW_CONTEXT_VERSION_MINOR, 3);
    gl.glfwWindowHint(gl.GLFW_OPENGL_PROFILE, gl.GLFW_OPENGL_CORE_PROFILE);

    const window: *gl.GLFWwindow = gl.glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "amsasjnd", null, null) orelse unreachable;
    defer gl.glfwDestroyWindow(window);

    gl.glfwMakeContextCurrent(window);
    _ = gl.gladLoadGL(gl.glfwGetProcAddress);
    gl.glfwSwapInterval(1);

    gl.glEnable(gl.GL_CULL_FACE);
    gl.glEnable(gl.GL_BLEND);
    gl.glEnable(gl.GL_DEBUG_OUTPUT);
    gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA);

    const vertexSource = getShaderFile(init, "shaders/custom3/text1.vert");
    const fragmentSource = getShaderFile(init, "shaders/custom3/text1.frag");
    defer init.gpa.free(vertexSource);
    defer init.gpa.free(fragmentSource);

    const vertexShader = gl.glCreateShader(gl.GL_VERTEX_SHADER);
    gl.glShaderSource(vertexShader, 1, &vertexSource.ptr, null);
    gl.glCompileShader(vertexShader);
    
    const fragmentShader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER);
    gl.glShaderSource(fragmentShader, 1, &fragmentSource.ptr, null);
    gl.glCompileShader(fragmentShader);

    const shaderProgram = gl.glCreateProgram();
    gl.glAttachShader(shaderProgram, vertexShader);
    gl.glAttachShader(shaderProgram, fragmentShader);
    gl.glLinkProgram(shaderProgram);
    gl.glDeleteShader(fragmentShader);
    gl.glDeleteShader(vertexShader);

    gl.glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

    var ft:ftp.FT_Library = undefined;
    var face:ftp.FT_Face = undefined;
    
    _ = ftp.FT_Init_FreeType(&ft);
    _ = ftp.FT_New_Face(ft, "fonts/MapleMono-Bold.ttf", 0, &face);
    _ = ftp.FT_Set_Pixel_Sizes(face, 0, 48);

    const character = struct {
        texture:c_uint,
        size:@Vector(2, i32),
        bearing:@Vector(2, i32),
        advance:gl.GLuint
    };
    var mapCharacter:std.ArrayList(character) = .empty ;
    defer mapCharacter.deinit(init.gpa);
    defer mapCharacter.clearAndFree(init.gpa);

    gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1);

    for (0..128) |c| {
        _ = ftp.FT_Load_Char(face, c, ftp.FT_LOAD_RENDER);

        // create texture
        // note: * = pointer atau ambil dari memory
        var texture:c_uint = undefined;
        gl.glCreateTextures(gl.GL_TEXTURE_2D, 1, &texture);
        gl.glTextureStorage2D(texture, 1, gl.GL_R8, @intCast(face.*.glyph.*.bitmap.width), @intCast(face.*.glyph.*.bitmap.rows));
        gl.glTextureSubImage2D(texture, 0, 0, 0, @intCast(face.*.glyph.*.bitmap.width), @intCast(face.*.glyph.*.bitmap.rows), gl.GL_RED, gl.GL_UNSIGNED_BYTE, face.*.glyph.*.bitmap.buffer);

        // bind texture
        gl.glBindTexture(gl.GL_TEXTURE_2D, texture);
        gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE);
		gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE);
		gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR);
		gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR);
		// unbind
		// gl.glBindTexture(gl.GL_TEXTURE_2D, 0);

		// append character
		mapCharacter.append(init.gpa, .{
		    .texture = texture,
			.size = @Vector(2, i32){@intCast(face.*.glyph.*.bitmap.width), @intCast(face.*.glyph.*.bitmap.rows)},
			.bearing = @Vector(2, i32){@intCast(face.*.glyph.*.bitmap_left), @intCast(face.*.glyph.*.bitmap_top)},
			.advance = @intCast(face.*.glyph.*.advance.x)
		}) catch @panic("character not appenned");
    }

    _ = ftp.FT_Done_Face(face);
    _ = ftp.FT_Done_FreeType(ft);

    var vao:c_uint, var vbo:c_uint = .{undefined,undefined};
    gl.glCreateVertexArrays(1, &vao);
    gl.glCreateBuffers(1, &vbo);

    gl.glBindVertexArray(vao);

    gl.glNamedBufferData(vbo, @sizeOf(f32) * 24, null, gl.GL_DYNAMIC_DRAW);
    gl.glVertexArrayVertexBuffer(vao, 0, vbo, 0, @sizeOf(f32) * 4);
    gl.glVertexArrayAttribFormat(vao, 0, 4, gl.GL_FLOAT, gl.GL_FALSE, 0);
    gl.glVertexArrayAttribBinding(vao, 0, 0);
	gl.glEnableVertexArrayAttrib(vao, 0);

	const text:[]const u8 = "hell world";

	while (gl.glfwWindowShouldClose(window) == 0) {
	    gl.glClearColor(0.0, 0.0, 0.0, 1.0);
	    gl.glClear(gl.GL_COLOR_BUFFER_BIT);

		var x:f32 = 1.0;
		const y:f32 = 300.0;
		const scale:f32 = 1.0;
		gl.glUseProgram(shaderProgram);
		gl.glBindVertexArray(vao);
		const textLoc = gl.glGetUniformLocation(shaderProgram, "text");
        if(textLoc != -1) gl.glUniform1i(textLoc, 0);
        gl.glActiveTexture(gl.GL_TEXTURE0);

		for (text) |v| {
		    const ch = mapCharacter.items[v];
			const xPos:f32 = x + @as(f32,@floatFromInt(ch.bearing[0])) * scale;
			const yPos:f32 = y - @as(f32,@floatFromInt(ch.size[1] - ch.bearing[1])) * scale;

			const w:f32 = @as(f32,@floatFromInt(ch.size[0])) * scale;
			const h:f32 = @as(f32,@floatFromInt(ch.size[1])) * scale;

			const vertices = [6][4]f32{
			    [4]f32{xPos,yPos+h, 0.0, 0.0},
			    [4]f32{xPos,yPos, 0.0, 1.0},
			    [4]f32{xPos + w,yPos, 1.0, 1.0},

				[4]f32{xPos, yPos + h, 0.0,0.0},
				[4]f32{xPos+w, yPos, 1.0, 1.0},
				[4]f32{xPos+w, yPos+h, 1.0, 0.0}
			};

			gl.glNamedBufferSubData(vbo, 0, @sizeOf(f32) * 6 * 4, &vertices);
			gl.glBindTexture(gl.GL_TEXTURE_2D, ch.texture);
			// gl.glActiveTexture(gl.GL_TEXTURE0);
    		const projection = orthoProjection(SCREEN_WIDTH, SCREEN_HEIGHT);
    		const projectionLoc = gl.glGetUniformLocation(shaderProgram, "projection");
    		const texLoc = gl.glGetUniformLocation(shaderProgram, "textColor");
            if(projectionLoc != -1) gl.glUniformMatrix4fv(projectionLoc, 1, gl.GL_FALSE, @ptrCast(&projection));
            if(texLoc != -1) gl.glUniform3f(texLoc, 0.88, 0.53, 0.03);
			gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6);
			const px = @as(f32,@floatFromInt(ch.advance >> 6));
			x += px * scale;
		}

		gl.glfwSwapBuffers(window);
		gl.glfwPollEvents();
	}
}

```

vertex shader

```zig
#version 330 core
layout (location = 0) in vec4 vertex;
uniform mat4 projection;

out vec2 TexCoords;

void main()
{
    gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
    TexCoords = vertex.zw;
}  

```

fragment shader

```zig
#version 330 core
in vec2 TexCoords;
out vec4 FragColor;
uniform sampler2D text;
uniform vec3 textColor;

void main() {
    float alpha = texture(text, TexCoords).r;
    FragColor = vec4(textColor, alpha);
}

```

i trying making text in opengl with zig and freetype and try follow some example code, this code no error, but the text not show, can anyone tell me whats wrong with my code? , _ **sorry im if my english not very good** _

---

<div class="post-metadata">

**Author:** ![floooh](https://ziggit.dev/user_avatar/ziggit.dev/floooh/32/2989_2.png) [@floooh](https://ziggit.dev/u/floooh)\
**Post date:** [July 9, 2026, 5:48pm UTC](https://ziggit.dev/t/using-freetype-c-in-zig/16611/2 "2026-07-09T17:48:20Z")

</div>

Tbh there can be a thousand reasons why nothing shows up in an OpenGL application. GL is famously bad for this “black screen problem” because it doesn’t have a proper validation layer like all the other 3D APIs.

Your best bet is to inspect the application in a graphics debugger like RenderDoc. This lets you see what data arrives at the GPU, how the GL states are configured, and what the actual shader inputs and outputs are.

Unfortunately, RenderDoc doesn’t support shader debugging in OpenGL, and from googling around it looks like NVIDIA NSight (another graphics debugger) has removed GL shader debugging quite a while ago and only supports Vulkan shader debugging - clear sign that the GL bitrot is real 😕

---

<div class="post-metadata">

**Author:** ![tholmes](https://ziggit.dev/user_avatar/ziggit.dev/tholmes/32/5641_2.png) [@tholmes](https://ziggit.dev/u/tholmes)\
**Post date:** [July 9, 2026, 6:21pm UTC](https://ziggit.dev/t/using-freetype-c-in-zig/16611/3 "2026-07-09T18:21:10Z")

</div>

> [@idk](#):
>
> ```zig
> const vertices = [6][4]f32{
> [4]f32{xPos,yPos+h, 0.0, 0.0},
> [4]f32{xPos,yPos, 0.0, 1.0},
> [4]f32{xPos + w,yPos, 1.0, 1.0},
> 
> [4]f32{xPos, yPos + h, 0.0,0.0},
> [4]f32{xPos+w, yPos, 1.0, 1.0},
> [4]f32{xPos+w, yPos+h, 1.0, 0.0}
> };
> 
> ```

Your vertices are defined in clockwise order. I have (schizophrenically) illustrated this here:

 ![winding_order](https://ziggit.dev/uploads/default/original/2X/e/e50b018abc832f158dc163adf02409cf1c32fe03.png)

However, by default, OpenGL assumes a counter-clockwise winding order, so your triangles are facing away from the viewer, completely invisible.  
You can test whether I’m telling the truth by commenting out this line:  
`gl.glEnable(gl.GL_CULL_FACE);`

---

<div class="post-metadata">

**Author:** ![idk](https://ziggit.dev/user_avatar/ziggit.dev/idk/32/9520_2.png) [@idk](https://ziggit.dev/u/idk)\
**Post date:** [July 10, 2026, 8:23am UTC](https://ziggit.dev/t/using-freetype-c-in-zig/16611/4 "2026-07-10T08:23:15Z")

</div>

![image](https://ziggit.dev/uploads/default/original/2X/3/39c192e57d71905a3ab124fe90d77a381ae6dda1.jpeg)

i try your suggestion earlier, but still same, like this.

thnx btw

---

<div class="post-metadata">

**Author:** ![floooh](https://ziggit.dev/user_avatar/ziggit.dev/floooh/32/2989_2.png) [@floooh](https://ziggit.dev/u/floooh)\
**Post date:** [July 10, 2026, 12:49pm UTC](https://ziggit.dev/t/using-freetype-c-in-zig/16611/5 "2026-07-10T12:49:42Z")

</div>

PS: you should copy-paste your code into an LLM, e.g. Claude Opus comes up with a couple of potential bugs:

- you call `glShaderSource` with a string length parameter of null, meaning it expects the string to be zero terminated, `readFileAlloc` returns a Zig slice though (not zero terminated) - this might be the most obvious problem (also be aware that the length parameter is actually a pointer to an array of integers)
- it also mentioned @tholmes’s suggestion of disabling GL\_CULL\_FACE
- there’s also various more problems if you’re on macOS (basically you’re using too recent GL functions for macOS), but I guess those don’t apply

As I said above, in any decent 3D API you would get proper errors back, but that’s just OpenGL being OpenGL ☹

PPS: it _is_ actually possible to get errors back from glShaderSource, e.g. try this after the glCompileShader calls:

```zig
var ok: gl.GLint = 0;
gl.glGetShaderiv(vertexShader, gl.GL_COMPILE_STATUS, &ok);
if (ok == 0) {
    var log: [1024]u8 = undefined;
    var len: gl.GLsizei = 0;
    gl.glGetShaderInfoLog(vertexShader, log.len, &len, &log);
    std.debug.print("vert: {s}\n", .{log[0..@intCast(len)]});
}

```

If the shader compilation fails, this should print _something_ to the console.

I hope I didn’t violate the Ziggit LLM rules with this post 😉

PPS: also in RenderDoc this shader compilation problem would have shown up as an obvious error.

---

<div class="post-metadata">

**Author:** ![idk](https://ziggit.dev/user_avatar/ziggit.dev/idk/32/9520_2.png) [@idk](https://ziggit.dev/u/idk)\
**Post date:** [July 10, 2026, 1:37pm UTC](https://ziggit.dev/t/using-freetype-c-in-zig/16611/6 "2026-07-10T13:37:21Z")

</div>

oke i’ll try

---

<div class="post-metadata">

**Author:** ![idk](https://ziggit.dev/user_avatar/ziggit.dev/idk/32/9520_2.png) [@idk](https://ziggit.dev/u/idk)\
**Post date:** [July 10, 2026, 4:53pm UTC](https://ziggit.dev/t/using-freetype-c-in-zig/16611/7 "2026-07-10T16:53:24Z")

</div>

the ai doesn’t work for this issue, i think the ortho matrix i created is quite bad, but im still not sure,  
i’ll try again, thanks btw 😃 .

---

<div class="post-metadata">

**Author:** ![idk](https://ziggit.dev/user_avatar/ziggit.dev/idk/32/9520_2.png) [@idk](https://ziggit.dev/u/idk)\
**Post date:** [July 11, 2026, 4:32am UTC](https://ziggit.dev/t/using-freetype-c-in-zig/16611/8 "2026-07-11T04:32:20Z")

</div>

finally i fix this issue, the problem is my orthoprojection

this final fix

```zig
const std = @import("std");
const Io = std.Io;
const pow = std.math.pow;
const gl = @cImport({
    @cDefine("GLAD_GL_IMPLEMENTATION", "");
    @cInclude("glad/gl.h");
    @cDefine("GLFW_INCLUDE_NONE", "");
    @cInclude("GLFW/glfw3.h");
    @cInclude("GL/gl.h");
});
const ftp = @cImport({
    @cInclude("freetype2/ft2build.h");
    @cInclude("freetype2/freetype/freetype.h");
    @cInclude("freetype2/freetype/config/ftconfig.h");
});

const SCREEN_WIDTH = 1920;
const SCREEN_HEIGHT = 1080;
const PI = 3.14159265;

fn getShaderFile(init:std.process.Init,filename:[]const u8) []u8 {
    const gpa = init.gpa;
    const dir = Io.Dir.cwd();

    const file = dir.readFileAlloc(init.io, filename, gpa, .unlimited) catch @panic("shader file not found");

    return file;
}

fn radians(val: f32) f32 {
    return val * (PI / 180.0);
}

fn orthoProjection(left:f32, right:f32, top:f32, bottom:f32) [4]@Vector(4,f32) {
    return .{
        .{ 2 / (right - left), 0.0, 0.0, 0.0 },
        .{ 0.0, 2 / (top - bottom), 0.0, 0.0 },
        .{ 0.0, 0.0, -2.0, 0.0 },
        .{ 0.0, 0.0, 0.0, 1.0 }
    };
}

const Character = struct {
    textureID:c_uint,
    size:@Vector(2, i32),
    bearing:@Vector(2, i32),
    advance:c_uint
};

pub fn main(init:std.process.Init) !void {
    _ = gl.glfwInit();
    defer gl.glfwTerminate();

    gl.glfwWindowHint(gl.GLFW_CONTEXT_VERSION_MAJOR, 3);
    gl.glfwWindowHint(gl.GLFW_CONTEXT_VERSION_MINOR, 3);
    gl.glfwWindowHint(gl.GLFW_OPENGL_PROFILE, gl.GLFW_OPENGL_CORE_PROFILE);

    const window: *gl.GLFWwindow = gl.glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "amsasjnd", null, null) orelse unreachable;
    defer gl.glfwDestroyWindow(window);

    gl.glfwMakeContextCurrent(window);
    _ = gl.gladLoadGL(gl.glfwGetProcAddress);
    gl.glfwSwapInterval(1);

    // gl.glDisable(gl.GL_CULL_FACE);
    gl.glEnable(gl.GL_BLEND);
    // gl.glEnable(gl.GL_DEPTH_TEST);
    gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA);

    var ft:ftp.FT_Library = undefined;
    _ = ftp.FT_Init_FreeType(&ft);
    var face:ftp.FT_Face= undefined;
    _ = ftp.FT_New_Face(ft, "fonts/MapleMono-Bold.ttf", 0, &face);
    _ = ftp.FT_Set_Pixel_Sizes(face, 0, 48);
    gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1);

    const vertexSource = getShaderFile(init, "shaders/custom3/text1.vert");
    const fragmentSource = getShaderFile(init, "shaders/custom3/text1.frag");
    defer init.gpa.free(vertexSource);
    defer init.gpa.free(fragmentSource);

    const vertexShader = gl.glCreateShader(gl.GL_VERTEX_SHADER);
    gl.glShaderSource(vertexShader, 1, &vertexSource.ptr, null);
    gl.glCompileShader(vertexShader);
    
    const fragmentShader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER);
    gl.glShaderSource(fragmentShader, 1, &fragmentSource.ptr, null);
    gl.glCompileShader(fragmentShader);

    const shaderProgram = gl.glCreateProgram();
    gl.glAttachShader(shaderProgram, vertexShader);
    gl.glAttachShader(shaderProgram, fragmentShader);
    gl.glLinkProgram(shaderProgram);
    gl.glDeleteShader(fragmentShader);
    gl.glDeleteShader(vertexShader);

    var VAO:c_uint, var VBO:c_uint = .{undefined, undefined};
    const coordinate = gl.glGetAttribLocation(shaderProgram, "coord");
    const textCoord = gl.glGetAttribLocation(shaderProgram, "text");

    var mapCharacters = std.AutoHashMap(u8, Character).init(init.gpa);
    defer mapCharacters.deinit();
    defer mapCharacters.clearAndFree();

    for(0..128) |n| {
        _ = ftp.FT_Load_Char(face, @intCast(n), ftp.FT_LOAD_RENDER);
        var TEXTURE:c_uint = undefined;
        const glyph:ftp.FT_GlyphSlot = face.*.glyph;
        const width:i32 = @intCast(glyph.*.bitmap.width);
        const height:i32 = @intCast(glyph.*.bitmap.rows);
        const buffer = glyph.*.bitmap.buffer;
        gl.glGenTextures(1, &TEXTURE);
        gl.glBindTexture(gl.GL_TEXTURE_2D, TEXTURE);
        gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_R8, width, height, 0, gl.GL_RED, gl.GL_UNSIGNED_BYTE, buffer);
        gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE);
        gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE);
        gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR);
        gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR);

        mapCharacters.put(@intCast(n), .{
            .textureID = TEXTURE,
            .size = .{width,height},
            .bearing = .{glyph.*.bitmap_left, glyph.*.bitmap_top},
            .advance = @intCast(glyph.*.advance.x)
        }) catch @panic("charater not appenned");
    }
    _ = ftp.FT_Done_Face(face);
    _ = ftp.FT_Done_FreeType(ft);
    
    gl.glGenVertexArrays(1, &VAO);
    gl.glGenBuffers(1, &VBO);

    gl.glBindVertexArray(VAO);
    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, VBO);

    gl.glBufferData(gl.GL_ARRAY_BUFFER, 24 * @sizeOf(f32), null, gl.GL_DYNAMIC_DRAW);
    gl.glEnableVertexAttribArray(@intCast(coordinate));
    gl.glVertexAttribPointer(@intCast(coordinate), 2, gl.GL_FLOAT, gl.GL_FALSE, 4 * @sizeOf(f32), null);
    gl.glEnableVertexAttribArray(@intCast(textCoord));
    gl.glVertexAttribPointer(@intCast(textCoord), 2, gl.GL_FLOAT, gl.GL_FALSE, 4 * @sizeOf(f32), @ptrFromInt(2 * @sizeOf(f32)));

    while (gl.glfwWindowShouldClose(window) == 0) {
        gl.glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

        gl.glClearColor(1.0, 1.0, 1.0, 1.0);
        gl.glClear(gl.GL_COLOR_BUFFER_BIT);

        gl.glUseProgram(shaderProgram);
        const textureLoc = gl.glGetUniformLocation(shaderProgram, "TEXTURE");
        if (textureLoc != -1) gl.glUniform1i(textureLoc, 0);
        gl.glActiveTexture(gl.GL_TEXTURE0);
        gl.glBindVertexArray(VAO);
        const w = @as(f32,@floatFromInt(SCREEN_WIDTH));
        const h = @as(f32,@floatFromInt(SCREEN_HEIGHT));
        const projLoc = gl.glGetUniformLocation(shaderProgram, "projection");
        const projMat = orthoProjection(-w,w,h,-h);
        if(projLoc != -1) gl.glUniformMatrix4fv(projLoc, 1, gl.GL_FALSE, @ptrCast(&projMat));
        const text: []const u8 = "Hello World";
        var x:f32 = 0.0;
        const y:f32 = 0.0;
        for (text) |c| {
            const char = mapCharacters.get(c).?;
            const left:f32 = x + @as(f32,@floatFromInt(char.bearing[0]));
            const top:f32 = y - @as(f32,@floatFromInt(char.size[1] - char.bearing[1]));
            const right:f32 = left + @as(f32,@floatFromInt(char.size[0]));
            const bottom:f32 = top + @as(f32,@floatFromInt(char.size[1]));

            const vertices = [6]@Vector(4, f32){
                .{ left, bottom, 0.0, 0.0 },
                .{ left, top, 0.0, 1.0 },
                .{ right, top, 1.0, 1.0 },
                .{ left, bottom, 0.0, 0.0 },
                .{ right, top, 1.0, 1.0 },
                .{ right, bottom, 1.0, 0.0 },
            };
            gl.glBindTexture(gl.GL_TEXTURE_2D, char.textureID);
            gl.glBindBuffer(gl.GL_ARRAY_BUFFER, VBO);
            gl.glBufferSubData(gl.GL_ARRAY_BUFFER, 0, @sizeOf(@TypeOf(vertices)), &vertices);
            gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6);

            x += @floatFromInt(@as(i32,@intCast(char.advance)) >> 6);
        }

        gl.glfwSwapBuffers(window);
        gl.glfwPollEvents();
    }
}

```

the coordinate texture for the bitmap from the font is not like regular texture,  
if we use texture from image

```zig
-0.5, -0.5, left, top,

```

and if we use texture from the bitmap font

```zig
-0.5, -0.5, left, bottom

```

---

<div class="post-metadata">

**Author:** ![andrewrk](https://ziggit.dev/user_avatar/ziggit.dev/andrewrk/32/7011_2.png) [@andrewrk](https://ziggit.dev/u/andrewrk)\
**Post date:** [July 11, 2026, 4:58am UTC](https://ziggit.dev/t/using-freetype-c-in-zig/16611/9 "2026-07-11T04:58:19Z")

</div>

In case you’re interested there’s a Zig package alternative to FreeType that has no dependencies: [https://codeberg.org/andrewrk/TrueType](https://codeberg.org/andrewrk/TrueType)

---

<div class="post-metadata">

**Author:** ![idk](https://ziggit.dev/user_avatar/ziggit.dev/idk/32/9520_2.png) [@idk](https://ziggit.dev/u/idk)\
**Post date:** [July 11, 2026, 5:22am UTC](https://ziggit.dev/t/using-freetype-c-in-zig/16611/10 "2026-07-11T05:22:00Z")

</div>

ahhh, i just found out that there is an alternative in zig, thank youu
