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.
7.0 kB · 226 lines
Zig
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227// 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
//! Scripts: their ISO 15924 codes, the direction each is written in, and the//! OpenType script tags a font may file their lookups under. Ported from//! HarfBuzz's `hb-common.cc` and `hb-ot-tag.cc`.
const std = @import("std");const uucode = @import("uucode");
pub const Script = @import("script_list.zig").Script;
/// The direction text runs in. Only horizontal shaping is implemented; the/// vertical directions exist so that a request for one can be refused.pub const Direction = enum(u3) { ltr = 4, rtl = 5, ttb = 6, btt = 7,
pub fn isHorizontal(d: Direction) bool { return d == .ltr or d == .rtl; }
pub fn isVertical(d: Direction) bool { return d == .ttb or d == .btt; }
/// Whether glyphs run from the end of the text to its start: right to /// left, or bottom to top. pub fn isBackward(d: Direction) bool { return d == .rtl or d == .btt; }
pub fn isForward(d: Direction) bool { return !d.isBackward(); }
pub fn reverse(d: Direction) Direction { return switch (d) { .ltr => .rtl, .rtl => .ltr, .ttb => .btt, .btt => .ttb, }; }
/// `ltr`, `rtl`, `ttb` or `btt`, or as HarfBuzz accepts them, any word /// starting with one of those letters. pub fn parse(s: []const u8) ?Direction { if (s.len == 0) return null; return switch (std.ascii.toLower(s[0])) { 'l' => .ltr, 'r' => .rtl, 't' => .ttb, 'b' => .btt, else => null, }; }};
/// A tag as a big-endian `u32`, which is how HarfBuzz and `Tag.toInt` both/// hold one.pub fn isoTag(comptime s: *const [4]u8) u32 { return std.mem.readInt(u32, s, .big);}
/// The script uucode gives a code point, as a `Script`.pub fn fromUcd(s: uucode.types.Script) Script { return switch (s) { inline else => |t| @field(Script, @tagName(t)), };}
/// The script of a code point.pub fn of(cp: u21) Script { return fromUcd(uucode.get(.script, cp));}
/// `hb_script_from_iso15924_tag`: a script from its ISO 15924 code, in any/// case, with the variant codes folded into the script they vary.pub fn fromIso15924(tag: u32) Script { if (tag == 0) return .invalid; const t = (tag & 0xDFDFDFDF) | 0x00202020; return switch (t) { isoTag("Qaai") => .inherited, isoTag("Qaac") => .coptic, isoTag("Aran") => .arabic, isoTag("Cyrs") => .cyrillic, isoTag("Geok") => .georgian, isoTag("Hans"), isoTag("Hant") => .han, isoTag("Jamo") => .hangul, isoTag("Latf"), isoTag("Latg") => .latin, isoTag("Syre"), isoTag("Syrj"), isoTag("Syrn") => .syriac, else => if (t & 0xE0E0E0E0 == 0x40606060) @fromBackingInt(@intCast(t)) else .unknown, };}
/// `hb_script_from_string`: an ISO 15924 code written out, of which only the/// first four characters count.pub fn fromString(s: []const u8) Script { var buf: [4]u8 = .{ ' ', ' ', ' ', ' ' }; for (s[0..@min(4, s.len)], 0..) |c, i| buf[i] = c; return fromIso15924(std.mem.readInt(u32, &buf, .big));}
/// `hb_script_get_horizontal_direction`: the direction a script is written/// in horizontally, or null for the few written either way.pub fn horizontalDirection(s: Script) ?Direction { return switch (s) { .arabic, .hebrew, .syriac, .thaana, .cypriot, .kharoshthi, .phoenician, .nko, .lydian, .avestan, .imperial_aramaic, .inscriptional_pahlavi, .inscriptional_parthian, .old_south_arabian, .old_turkic, .samaritan, .mandaic, .meroitic_cursive, .meroitic_hieroglyphs, .manichaean, .mende_kikakui, .nabataean, .old_north_arabian, .palmyrene, .psalter_pahlavi, .hatran, .adlam, .hanifi_rohingya, .old_sogdian, .sogdian, .elymaic, .chorasmian, .yezidi, .old_uyghur, .garay, .sidetic, => .rtl, .old_hungarian, .old_italic, .runic, .tifinagh => null, else => .ltr, };}
/// The script tag of the fallback script system, `DFLT`.pub const default_tag = isoTag("DFLT");/// The script tag fonts file mathematical layout under.pub const math_tag = isoTag("math");
fn oldTag(s: Script) u32 { return switch (s) { .invalid => default_tag, .math => math_tag, .hiragana => isoTag("kana"), .lao => isoTag("lao "), .yi => isoTag("yi "), .nko => isoTag("nko "), .vai => isoTag("vai "), else => @backingInt(s) | 0x20000000, };}
fn newTag(s: Script) ?u32 { return switch (s) { .bengali => isoTag("bng2"), .devanagari => isoTag("dev2"), .gujarati => isoTag("gjr2"), .gurmukhi => isoTag("gur2"), .kannada => isoTag("knd2"), .malayalam => isoTag("mlm2"), .oriya => isoTag("ory2"), .tamil => isoTag("tml2"), .telugu => isoTag("tel2"), .myanmar => isoTag("mym2"), else => null, };}
/// `hb_ot_all_tags_from_script`: the OpenType script tags for a script, most/// preferred first. Indic scripts have their version 3 and version 2 tags/// ahead of the original one.pub fn otTags(s: Script, out: *[3]u32) []const u32 { var n: usize = 0; if (newTag(s)) |t| { if (t != isoTag("mym2")) { out[n] = t | '3'; n += 1; } out[n] = t; n += 1; } const old = oldTag(s); if (old != default_tag) { out[n] = old; n += 1; } return out[0..n];}
test "ISO 15924 codes" { try std.testing.expectEqual(Script.arabic, fromString("arab")); try std.testing.expectEqual(Script.arabic, fromString("Aran")); try std.testing.expectEqual(Script.latin, fromString("LATN")); try std.testing.expectEqual(Script.unknown, fromString("12ab")); try std.testing.expectEqual(Script.arabic, of(0x0627)); try std.testing.expectEqual(Script.common, of(' '));}
test "OpenType script tags" { var buf: [3]u32 = undefined; try std.testing.expectEqualSlices(u32, &.{ isoTag("dev3"), isoTag("dev2"), isoTag("deva") }, otTags(.devanagari, &buf)); try std.testing.expectEqualSlices(u32, &.{ isoTag("mym2"), isoTag("mymr") }, otTags(.myanmar, &buf)); try std.testing.expectEqualSlices(u32, &.{isoTag("latn")}, otTags(.latin, &buf)); try std.testing.expectEqualSlices(u32, &.{isoTag("kana")}, otTags(.hiragana, &buf)); try std.testing.expectEqualSlices(u32, &.{}, otTags(.invalid, &buf));}