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.
4.8 kB · 124 lines
Zig
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! Drawing positioned glyphs onto a z2d surface.
const std = @import("std");const Allocator = std.mem.Allocator;const z2d = @import("z2d");const Face = @import("Face.zig");const GlyphCache = @import("GlyphCache.zig");const raster = @import("raster.zig");const Run = @import("run.zig").Run;
pub const Options = struct { /// The text color, pre-multiplied as all of z2d's pixels are. Outline /// glyphs are painted in it, and color glyphs use it where their font /// asks for the foreground. color: z2d.Pixel = .{ .rgba = .{ .r = 0, .g = 0, .b = 0, .a = 255 } }, /// How glyphs are composited onto the surface. operator: z2d.compositor.Operator = .src_over, anti_aliasing_mode: z2d.options.AntiAliasMode = .default, /// Draw `COLR` and `SVG ` glyphs in color rather than as plain outlines. color_glyphs: bool = true, /// The `CPAL` palette color glyphs use. palette: u16 = 0, /// Where rendered glyphs are kept between draws. Without one, every /// glyph is rendered afresh, which gives the same pixels slower. cache: ?*GlyphCache = null,};
pub const Error = Allocator.Error;
/// Draw `run` onto `target` with the run's origin — the start of its/// baseline — at `(x, y)` in pixels.////// Each glyph is placed to the nearest quarter pixel, rendered once into an/// image of its own at that offset (or taken from `options.cache`), and/// composited. For drawing under a rotation or any other transformation,/// see `outline.appendRun`.pub fn drawRun( gpa: Allocator, target: *z2d.Surface, run: Run, x: f32, y: f32, options: Options,) Error!void { const foreground = z2d.pixel.RGBA.fromPixel(options.color); for (run.glyphs) |g| { const gx = quarter(x + g.x) orelse continue; const gy = quarter(y + g.y) orelse continue; const color = options.color_glyphs and run.face.colorKind(g.id) != .none; const key: GlyphCache.Key = .{ .face = @intFromPtr(run.face), .glyph_id = g.id, .size = @bitCast(run.size), .sub_x = gx.sub, .sub_y = gy.sub, .color = color, .anti_aliasing = @backingInt(options.anti_aliasing_mode), .palette = if (color) options.palette else 0, .foreground = if (color) @bitCast(foreground) else 0, };
if (options.cache) |cache| { if (cache.get(key)) |hit| { if (hit) |img| composite(target, img, gx.whole, gy.whole, options); continue; } } var rendered = try raster.render( gpa, run.face, g.id, run.size, @as(f32, @floatFromInt(gx.sub)) / 4, @as(f32, @floatFromInt(gy.sub)) / 4, .{ .anti_aliasing_mode = options.anti_aliasing_mode, .color = color, .palette = options.palette, .foreground = foreground, }, ); if (rendered) |*img| composite(target, img, gx.whole, gy.whole, options); if (options.cache) |cache| { try cache.put(key, rendered); } else if (rendered) |*img| { img.deinit(gpa); } }}
const Quarter = struct { whole: i32, sub: u2 };
/// `v` to the nearest quarter, as a whole part and a number of quarters./// Null for a position too far off any surface to matter.fn quarter(v: f32) ?Quarter { if (!std.math.isFinite(v) or @abs(v) > 1 << 28) return null; const q: i32 = @intFromFloat(@round(v * 4)); return .{ .whole = @divFloor(q, 4), .sub = @intCast(@mod(q, 4)) };}
/// Composite a rendered glyph whose origin is in pixel `(x, y)`.fn composite(target: *z2d.Surface, img: *const raster.Image, x: i32, y: i32, options: Options) void { const dx = x +| img.left; const dy = y +| img.top; if (img.isColor()) { target.composite(&img.surface, options.operator, dx, dy, .{}); return; } // The text color through the mask, then onto the surface. z2d.compositor.SurfaceCompositor.run(target, dx, dy, 2, .{ .{ .operator = .dst_in, .dst = .{ .pixel = options.color }, .src = .{ .surface = &img.surface } }, .{ .operator = options.operator }, }, .{});}
test "positions round to the nearest quarter pixel" { try std.testing.expectEqual(Quarter{ .whole = 3, .sub = 1 }, quarter(3.3).?); try std.testing.expectEqual(Quarter{ .whole = -1, .sub = 3 }, quarter(-0.2).?); try std.testing.expectEqual(Quarter{ .whole = 5, .sub = 0 }, quarter(4.9).?); try std.testing.expectEqual(@as(?Quarter, null), quarter(std.math.inf(f32)));}