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 · 81 lines
Zig
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! Text to glyphs without shaping: a stand-in until there is a shaper.//!//! Each codepoint becomes the glyph its `cmap` maps it to, placed one after//! another along the baseline by `hmtx` advances and legacy `kern` pairs.//! There is no `GSUB` and no `GPOS`, so no ligatures, no mark positioning,//! no contextual forms, and no right-to-left: Latin, Greek, Cyrillic and CJK//! come out readable, and scripts that need shaping do not. A shaping//! library replaces this by producing `Run`s itself.
const std = @import("std");const Allocator = std.mem.Allocator;const Face = @import("Face.zig");const run = @import("run.zig");
pub const Layout = run.Placed;
/// Lay `text` out in `face` at `size` pixels per em. A codepoint the face/// has no glyph for becomes glyph 0, `.notdef`, which is how a font draws/// a missing character; ill-formed UTF-8 becomes U+FFFD. The glyphs are/// owned by the caller.pub fn layout(gpa: Allocator, face: *const Face, size: f32, text: []const u8) Allocator.Error!Layout { var ids: std.ArrayList(u16) = .empty; defer ids.deinit(gpa); var it: Utf8Iterator = .{ .bytes = text }; while (it.next()) |cp| try ids.append(gpa, face.glyphIndex(cp) orelse 0);
const positions = try gpa.alloc(run.Position, ids.items.len); defer gpa.free(positions); for (positions, ids.items, 0..) |*p, id, i| { p.* = .{ .x_advance = face.advance(id) }; if (i + 1 < ids.items.len) p.x_advance += face.kerning(id, ids.items[i + 1]); } return run.place(gpa, face, size, ids.items, positions);}
/// Decodes UTF-8, giving U+FFFD for each maximal ill-formed subsequence/// as the Unicode standard recommends, rather than failing.pub const Utf8Iterator = struct { bytes: []const u8, i: usize = 0,
pub fn next(it: *Utf8Iterator) ?u21 { if (it.i >= it.bytes.len) return null; const b0 = it.bytes[it.i]; const len = std.unicode.utf8ByteSequenceLength(b0) catch { it.i += 1; return 0xfffd; }; if (it.i + len > it.bytes.len) { it.i += validPrefix(it.bytes[it.i..]); return 0xfffd; } const seq = it.bytes[it.i..][0..len]; const cp = std.unicode.utf8Decode(seq) catch { it.i += validPrefix(seq); return 0xfffd; }; it.i += len; return cp; }
/// How many bytes of a broken sequence to consume: the lead byte and /// the continuation bytes after it, at least one. fn validPrefix(s: []const u8) usize { var n: usize = 1; while (n < s.len and n < 4 and s[n] & 0xc0 == 0x80) n += 1; return n; }};
test "ill-formed UTF-8 becomes replacement characters" { var it: Utf8Iterator = .{ .bytes = "a\xffb\xe2\x82" }; try std.testing.expectEqual(@as(?u21, 'a'), it.next()); try std.testing.expectEqual(@as(?u21, 0xfffd), it.next()); try std.testing.expectEqual(@as(?u21, 'b'), it.next()); try std.testing.expectEqual(@as(?u21, 0xfffd), it.next()); try std.testing.expectEqual(@as(?u21, null), it.next());}