Something went wrong. Try again.
An OpenType text shaper for Zig, ported from HarfBuzz and checked against its test suite, for fonts decoded by zig-font.
Something went wrong. Try again.
6.0 kB · 169 lines
Zig
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-FileCopyrightText: © 2009,2010 Red Hat, Inc.// SPDX-FileCopyrightText: © 2011,2012 Google, Inc.// SPDX-License-Identifier: MIT AND MIT-Modern-Variant
//! A feature the caller turns on or off, over the whole text or a range of//! its clusters, and the string syntax HarfBuzz and CSS write one in.
const std = @import("std");
const Feature = @This();
/// The feature tag, as a big-endian `u32`.tag: u32,/// 0 turns the feature off, 1 on; for alternates, which one.value: u32 = 1,/// The clusters it applies to: `start` inclusive, `end` exclusive.start: u32 = global_start,end: u32 = global_end,
pub const global_start: u32 = 0;pub const global_end: u32 = std.math.maxInt(u32);
pub fn isGlobal(f: Feature) bool { return f.start == global_start and f.end == global_end;}
pub fn init(comptime tag: *const [4]u8, value: u32) Feature { return .{ .tag = std.mem.readInt(u32, tag, .big), .value = value };}
const Parser = struct { s: []const u8, i: usize = 0,
fn space(p: *Parser) void { while (p.i < p.s.len and std.ascii.isWhitespace(p.s[p.i])) p.i += 1; }
fn char(p: *Parser, c: u8) bool { p.space(); if (p.i == p.s.len or p.s[p.i] != c) return false; p.i += 1; return true; }
/// As `strtol`: leading space, a sign, digits. A negative number becomes /// a large unsigned one, as in HarfBuzz, so `[3:-1]` runs to the end. fn int(p: *Parser) ?u32 { var j = p.i; while (j < p.s.len and std.ascii.isWhitespace(p.s[j])) j += 1; var neg = false; if (j < p.s.len and (p.s[j] == '-' or p.s[j] == '+')) { neg = p.s[j] == '-'; j += 1; } const digits = j; var v: i64 = 0; while (j < p.s.len and std.ascii.isDigit(p.s[j])) : (j += 1) { v = @min(v * 10 + (p.s[j] - '0'), std.math.maxInt(i32) + 1); } if (j == digits) return null; p.i = j; if (neg) v = -v; const clamped: i32 = @intCast(std.math.clamp(v, std.math.minInt(i32), std.math.maxInt(i32))); return @bitCast(clamped); }
fn boolean(p: *Parser) ?u32 { p.space(); const start = p.i; while (p.i < p.s.len and std.ascii.isAlphabetic(p.s[p.i])) p.i += 1; const w = p.s[start..p.i]; if (std.ascii.eqlIgnoreCase(w, "on")) return 1; if (std.ascii.eqlIgnoreCase(w, "off")) return 0; return null; }
fn tag(p: *Parser) ?u32 { p.space(); var quote: u8 = 0; if (p.i < p.s.len and (p.s[p.i] == '\'' or p.s[p.i] == '"')) { quote = p.s[p.i]; p.i += 1; } const start = p.i; while (p.i < p.s.len and p.s[p.i] != ' ' and p.s[p.i] != '=' and p.s[p.i] != '[' and p.s[p.i] != quote) p.i += 1; const t = p.s[start..p.i]; if (t.len == 0 or t.len > 4) return null; if (quote != 0) { // CSS expects exactly four bytes, and quotes are only allowed for // CSS compatibility. if (t.len != 4) return null; if (p.i == p.s.len or p.s[p.i] != quote) return null; p.i += 1; } var buf: [4]u8 = .{ ' ', ' ', ' ', ' ' }; @memcpy(buf[0..t.len], t); return std.mem.readInt(u32, &buf, .big); }};
/// `hb_feature_from_string`: `kern`, `+kern`, `-kern`, `kern=0`,/// `kern[3:5]`, `aalt=2`, `"liga" off`, and so on.pub fn parse(s: []const u8) ?Feature { var p: Parser = .{ .s = s }; var f: Feature = .{ .tag = 0 }; f.value = if (p.char('-')) 0 else blk: { _ = p.char('+'); break :blk 1; }; f.tag = p.tag() orelse return null;
p.space(); if (p.char('[')) { const start = p.int(); if (start) |v| f.start = v; if (p.char(':') or p.char(';')) { if (p.int()) |v| f.end = v; } else if (start != null) { f.end = f.start +% 1; } if (!p.char(']')) return null; }
const had_equal = p.char('='); const value = p.int() orelse p.boolean(); if (value) |v| f.value = v; if (had_equal and value == null) return null;
p.space(); if (p.i != s.len) return null; return f;}
/// A position on a variable font's axis, in the axis's user units.pub const Variation = struct { tag: u32, value: f32,
/// `hb_variation_from_string`: `wght=700`, or `wght 700`. pub fn parse(s: []const u8) ?Variation { var p: Parser = .{ .s = s }; const tag = p.tag() orelse return null; _ = p.char('='); p.space(); const rest = std.mem.trim(u8, s[p.i..], " \t"); const value = std.fmt.parseFloat(f32, rest) catch return null; return .{ .tag = tag, .value = value }; }};
test "feature strings" { const t = std.testing; const kern = std.mem.readInt(u32, "kern", .big); try t.expectEqual(Feature{ .tag = kern, .value = 1 }, parse("kern").?); try t.expectEqual(Feature{ .tag = kern, .value = 0 }, parse("-kern").?); try t.expectEqual(Feature{ .tag = kern, .value = 0 }, parse("kern=0").?); try t.expectEqual(Feature{ .tag = kern, .value = 1, .start = 3, .end = 5 }, parse("kern[3:5]").?); try t.expectEqual(Feature{ .tag = kern, .value = 1, .start = 3, .end = 4 }, parse("kern[3]").?); try t.expectEqual(Feature{ .tag = kern, .value = 0 }, parse("\"kern\" off").?); try t.expectEqual(Feature{ .tag = std.mem.readInt(u32, "aalt", .big), .value = 2 }, parse("aalt=2").?); try t.expectEqual(Feature{ .tag = std.mem.readInt(u32, "ss1 ", .big), .value = 1 }, parse("ss1").?); try t.expectEqual(@as(?Feature, null), parse("kern=")); try t.expectEqual(@as(?Feature, null), parse("toolong")); const v = Variation.parse("wght=700.5").?; try t.expectEqual(std.mem.readInt(u32, "wght", .big), v.tag); try t.expectEqual(@as(f32, 700.5), v.value);}