Something went wrong. Try again.
Draws glyphs from zig-font onto z2d surfaces from positioned glyph runs, with fontconfig-based font loading and fallback.
Something went wrong. Try again.
3.2 kB · 97 lines
Zig
at main
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! Positioned glyphs: what a shaper produces and what the renderer draws.//!//! The renderer never looks at text. Turning text into glyphs — choosing//! fonts, applying `GSUB` and `GPOS`, reordering, bidi — is a shaper's job,//! and what it hands over is a `Run`: one face at one size, and the glyphs//! to draw from it with where each one goes.
const std = @import("std");const Allocator = std.mem.Allocator;const Face = @import("Face.zig");
/// One glyph and where its origin goes, in pixels relative to the run's/// origin, y down. Fractional positions are kept: the renderer places/// glyphs to a quarter of a pixel.pub const Glyph = struct { id: u16, x: f32, y: f32,};
/// Glyphs from one face at one size.pub const Run = struct { face: *const Face, /// Pixels per em. size: f32, glyphs: []const Glyph,};
/// How a shaper positions one glyph, in font units, y up: the offset of the/// glyph from the pen, and how far the pen then moves. These are HarfBuzz's/// `hb_glyph_position_t`, at a font scale of the face's units per em.pub const Position = struct { x_advance: f32 = 0, y_advance: f32 = 0, x_offset: f32 = 0, y_offset: f32 = 0,};
/// Glyphs placed from a shaper's advances and offsets, by walking a pen/// along them from the origin. Also gives where the pen ended up, which is/// where a following run starts. The glyphs are owned by the caller.pub const Placed = struct { glyphs: []Glyph, /// The pen's position after the last glyph, in pixels, y down. end_x: f32, end_y: f32,
pub fn deinit(p: *Placed, gpa: Allocator) void { gpa.free(p.glyphs); p.* = undefined; }};
pub fn place( gpa: Allocator, face: *const Face, size: f32, ids: []const u16, positions: []const Position,) Allocator.Error!Placed { std.debug.assert(ids.len == positions.len); const s = face.scale(size); const glyphs = try gpa.alloc(Glyph, ids.len); var pen_x: f32 = 0; var pen_y: f32 = 0; for (glyphs, ids, positions) |*g, id, p| { g.* = .{ .id = id, .x = (pen_x + p.x_offset) * s, .y = -(pen_y + p.y_offset) * s }; pen_x += p.x_advance; pen_y += p.y_advance; } return .{ .glyphs = glyphs, .end_x = pen_x * s, .end_y = -pen_y * s };}
test "place walks the pen and flips y" { var f: @import("font").Font = undefined; f.fvar = null; f.head.units_per_em = 1000; var face = try Face.init(std.testing.allocator, &f, .{}); defer face.deinit(std.testing.allocator);
var p = try place(std.testing.allocator, &face, 10, &.{ 3, 4, 5 }, &.{ .{ .x_advance = 500 }, .{ .x_advance = 600, .y_offset = 100 }, .{ .x_advance = 0, .x_offset = -250, .y_advance = 50 }, }); defer p.deinit(std.testing.allocator); try std.testing.expectEqualSlices(Glyph, &.{ .{ .id = 3, .x = 0, .y = 0 }, .{ .id = 4, .x = 5, .y = -1 }, .{ .id = 5, .x = 8.5, .y = 0 }, }, p.glyphs); try std.testing.expectEqual(@as(f32, 11), p.end_x); try std.testing.expectEqual(@as(f32, -0.5), p.end_y);}