Something went wrong. Try again.
A Zig library that finds fonts as fontconfig does, without it: the same configuration, OpenType and bitmap fonts, and the same answers.
Something went wrong. Try again.
16 kB · 440 lines
Zig
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-FileCopyrightText: © 2000 Keith Packard// SPDX-License-Identifier: MIT AND HPND-sell-variant
//! Pattern objects and the named constants, from `fcobjs.h`, `fcname.c`//! and `fc-const/fcconst.list`.
const std = @import("std");const str = @import("str.zig");
/// The types a pattern value can have, `FcType`.pub const Type = enum { unknown, void, integer, double, string, bool, matrix, charset, langset, range,};
/// The objects fontconfig defines, in its order: the order patterns keep/// their elements in, and the order `FcNameUnparse` writes them.pub const Builtin = enum(u8) { family = 1, familylang, style, stylelang, fullname, fullnamelang, slant, weight, width, size, aspect, pixelsize, spacing, foundry, antialias, hintstyle, hinting, verticallayout, autohint, globaladvance, file, index, rasterizer, outline, scalable, dpi, rgba, scale, minspace, charwidth, charheight, matrix, charset, lang, fontversion, capability, fontformat, embolden, embeddedbitmap, decorative, lcdfilter, namelang, fontfeatures, prgname, hash, postscriptname, color, symbol, fontvariations, variable, fonthashint, order, desktop, namedinstance, fontwrapper, genericfamily,
/// The type a value of this object should have. pub fn valueType(b: Builtin) Type { return switch (b) { .family, .familylang, .style, .stylelang, .fullname, .fullnamelang => .string, .slant => .integer, .weight, .width, .size => .range, .aspect, .pixelsize => .double, .spacing => .integer, .foundry => .string, .antialias => .bool, .hintstyle => .integer, .hinting, .verticallayout, .autohint, .globaladvance => .bool, .file => .string, .index => .integer, .rasterizer => .string, .outline, .scalable => .bool, .dpi => .double, .rgba => .integer, .scale => .double, .minspace => .bool, .charwidth, .charheight => .integer, .matrix => .matrix, .charset => .charset, .lang => .langset, .fontversion => .integer, .capability, .fontformat => .string, .embolden, .embeddedbitmap, .decorative => .bool, .lcdfilter => .integer, .namelang, .fontfeatures, .prgname, .hash, .postscriptname => .string, .color, .symbol => .bool, .fontvariations => .string, .variable, .fonthashint => .bool, .order => .integer, .desktop => .string, .namedinstance => .bool, .fontwrapper => .string, .genericfamily => .integer, }; }
pub fn fromName(text: []const u8) ?Builtin { return std.meta.stringToEnum(Builtin, text); }};
/// A pattern object: one of fontconfig's own, or any other name a/// configuration or an application cares to use. The others have no type,/// so any value goes.pub const Object = union(enum) { builtin: Builtin, custom: []const u8,
pub fn fromName(text: []const u8) Object { if (Builtin.fromName(text)) |b| return .{ .builtin = b }; return .{ .custom = text }; }
pub fn name(o: Object) []const u8 { return switch (o) { .builtin => |b| @tagName(b), .custom => |n| n, }; }
pub fn eql(a: Object, b: Object) bool { return switch (a) { .builtin => |x| switch (b) { .builtin => |y| x == y, .custom => false, }, .custom => |x| switch (b) { .builtin => false, .custom => |y| std.mem.eql(u8, x, y), }, }; }
pub fn is(o: Object, b: Builtin) bool { return switch (o) { .builtin => |x| x == b, .custom => false, }; }
pub fn valueType(o: Object) Type { return switch (o) { .builtin => |b| b.valueType(), .custom => .unknown, }; }
/// `FcObjectValidType`: may a value of type `t` be stored in this /// object? pub fn acceptsType(o: Object, t: Type) bool { const want = o.valueType(); return switch (want) { .unknown => true, .double, .integer => t == .double or t == .integer, .langset => t == .langset or t == .string, .range => t == .range or t == .double or t == .integer, else => t == want, }; }
/// Orders objects as fontconfig orders a pattern's elements: its own /// objects by number, then the others. pub fn order(a: Object, b: Object) std.math.Order { return switch (a) { .builtin => |x| switch (b) { .builtin => |y| std.math.order(@backingInt(x), @backingInt(y)), .custom => .lt, }, .custom => |x| switch (b) { .builtin => .gt, .custom => |y| std.mem.order(u8, x, y), }, }; }
pub fn format(o: Object, w: *std.Io.Writer) std.Io.Writer.Error!void { try w.writeAll(o.name()); }};
pub const weight = struct { pub const thin = 0; pub const extralight = 40; pub const light = 50; pub const demilight = 55; pub const book = 75; pub const regular = 80; pub const medium = 100; pub const demibold = 180; pub const bold = 200; pub const extrabold = 205; pub const black = 210; pub const extrablack = 215;};
pub const slant = struct { pub const roman = 0; pub const italic = 100; pub const oblique = 110;};
pub const width = struct { pub const ultracondensed = 50; pub const extracondensed = 63; pub const condensed = 75; pub const semicondensed = 87; pub const normal = 100; pub const semiexpanded = 113; pub const expanded = 125; pub const extraexpanded = 150; pub const ultraexpanded = 200;};
pub const spacing = struct { pub const proportional = 0; pub const dual = 90; pub const mono = 100; pub const charcell = 110;};
pub const rgba = struct { pub const unknown = 0; pub const rgb = 1; pub const bgr = 2; pub const vrgb = 3; pub const vbgr = 4; pub const none = 5;};
pub const hint = struct { pub const none = 0; pub const slight = 1; pub const medium = 2; pub const full = 3;};
pub const lcd = struct { pub const none = 0; pub const default = 1; pub const light = 2; pub const legacy = 3;};
pub const generic_family = struct { pub const unknown = 0; pub const serif = 1; pub const sans = 2; pub const mono = 3; pub const cursive = 4; pub const fantasy = 5; pub const system_ui = 6; pub const ui_serif = 7; pub const ui_sans = 8; pub const ui_mono = 9; pub const ui_rounded = 10; pub const emoji = 11; pub const math = 12; pub const fangsong = 13;};
/// A named constant: `bold` is the weight 200.pub const Constant = struct { name: []const u8, object: Builtin, value: i32,};
/// `fc-const/fcconst.list`, sorted as `fc-const.py` sorts it: by name,/// ignoring ASCII case, then by object.pub const constants = blk: { @setEvalBranchQuota(100_000); var list = [_]Constant{ .{ .name = "antialias", .object = .antialias, .value = 1 }, .{ .name = "autohint", .object = .autohint, .value = 1 }, .{ .name = "bgr", .object = .rgba, .value = rgba.bgr }, .{ .name = "black", .object = .weight, .value = weight.black }, .{ .name = "bold", .object = .weight, .value = weight.bold }, .{ .name = "book", .object = .weight, .value = weight.book }, .{ .name = "charcell", .object = .spacing, .value = spacing.charcell }, .{ .name = "condensed", .object = .width, .value = width.condensed }, .{ .name = "cursive", .object = .genericfamily, .value = generic_family.cursive }, .{ .name = "decorative", .object = .decorative, .value = 1 }, .{ .name = "demibold", .object = .weight, .value = weight.demibold }, .{ .name = "demilight", .object = .weight, .value = weight.demilight }, .{ .name = "dual", .object = .spacing, .value = spacing.dual }, .{ .name = "embeddedbitmap", .object = .embeddedbitmap, .value = 1 }, .{ .name = "embolden", .object = .embolden, .value = 1 }, .{ .name = "emoji", .object = .genericfamily, .value = generic_family.emoji }, .{ .name = "expanded", .object = .width, .value = width.expanded }, .{ .name = "extrablack", .object = .weight, .value = weight.extrablack }, .{ .name = "extrabold", .object = .weight, .value = weight.extrabold }, .{ .name = "extracondensed", .object = .width, .value = width.extracondensed }, .{ .name = "extraexpanded", .object = .width, .value = width.extraexpanded }, .{ .name = "extralight", .object = .weight, .value = weight.extralight }, .{ .name = "fangsong", .object = .genericfamily, .value = generic_family.fangsong }, .{ .name = "fantasy", .object = .genericfamily, .value = generic_family.fantasy }, .{ .name = "globaladvance", .object = .globaladvance, .value = 1 }, .{ .name = "heavy", .object = .weight, .value = weight.black }, .{ .name = "hintfull", .object = .hintstyle, .value = hint.full }, .{ .name = "hinting", .object = .hinting, .value = 1 }, .{ .name = "hintmedium", .object = .hintstyle, .value = hint.medium }, .{ .name = "hintnone", .object = .hintstyle, .value = hint.none }, .{ .name = "hintslight", .object = .hintstyle, .value = hint.slight }, .{ .name = "italic", .object = .slant, .value = slant.italic }, .{ .name = "lcddefault", .object = .lcdfilter, .value = lcd.default }, .{ .name = "lcdlegacy", .object = .lcdfilter, .value = lcd.legacy }, .{ .name = "lcdlight", .object = .lcdfilter, .value = lcd.light }, .{ .name = "lcdnone", .object = .lcdfilter, .value = lcd.none }, .{ .name = "light", .object = .weight, .value = weight.light }, .{ .name = "math", .object = .genericfamily, .value = generic_family.math }, .{ .name = "medium", .object = .weight, .value = weight.medium }, .{ .name = "minspace", .object = .minspace, .value = 1 }, .{ .name = "mono", .object = .spacing, .value = spacing.mono }, .{ .name = "monospace", .object = .genericfamily, .value = generic_family.mono }, .{ .name = "none", .object = .rgba, .value = rgba.none }, .{ .name = "normal", .object = .weight, .value = weight.regular }, .{ .name = "normal", .object = .width, .value = width.normal }, .{ .name = "oblique", .object = .slant, .value = slant.oblique }, .{ .name = "outline", .object = .outline, .value = 1 }, .{ .name = "proportional", .object = .spacing, .value = spacing.proportional }, .{ .name = "regular", .object = .weight, .value = weight.regular }, .{ .name = "rgb", .object = .rgba, .value = rgba.rgb }, .{ .name = "roman", .object = .slant, .value = slant.roman }, .{ .name = "sans-serif", .object = .genericfamily, .value = generic_family.sans }, .{ .name = "scalable", .object = .scalable, .value = 1 }, .{ .name = "semibold", .object = .weight, .value = weight.demibold }, .{ .name = "semicondensed", .object = .width, .value = width.semicondensed }, .{ .name = "semiexpanded", .object = .width, .value = width.semiexpanded }, .{ .name = "semilight", .object = .weight, .value = weight.demilight }, .{ .name = "serif", .object = .genericfamily, .value = generic_family.serif }, .{ .name = "system-ui", .object = .genericfamily, .value = generic_family.system_ui }, .{ .name = "thin", .object = .weight, .value = weight.thin }, .{ .name = "ui-monospace", .object = .genericfamily, .value = generic_family.ui_mono }, .{ .name = "ui-rounded", .object = .genericfamily, .value = generic_family.ui_rounded }, .{ .name = "ui-sans-serif", .object = .genericfamily, .value = generic_family.ui_sans }, .{ .name = "ui-serif", .object = .genericfamily, .value = generic_family.ui_serif }, .{ .name = "ultrablack", .object = .weight, .value = weight.extrablack }, .{ .name = "ultrabold", .object = .weight, .value = weight.extrabold }, .{ .name = "ultracondensed", .object = .width, .value = width.ultracondensed }, .{ .name = "ultraexpanded", .object = .width, .value = width.ultraexpanded }, .{ .name = "ultralight", .object = .weight, .value = weight.extralight }, .{ .name = "unknown", .object = .rgba, .value = rgba.unknown }, .{ .name = "vbgr", .object = .rgba, .value = rgba.vbgr }, .{ .name = "verticallayout", .object = .verticallayout, .value = 1 }, .{ .name = "vrgb", .object = .rgba, .value = rgba.vrgb }, }; std.mem.sort(Constant, &list, {}, struct { fn lessThan(_: void, a: Constant, b: Constant) bool { return switch (std.ascii.orderIgnoreCase(a.name, b.name)) { .lt => true, .gt => false, .eq => @backingInt(a.object) < @backingInt(b.object), }; } }.lessThan); break :blk list;};
/// `FcNameGetConstant`: a constant by name alone. A name that belongs to/// more than one object — `normal` is both a weight and a width — is/// ambiguous and finds nothing.pub fn constant(text: []const u8) ?Constant { var found: ?Constant = null; for (constants) |c| { if (str.eqlIgnoreCase(c.name, text)) { if (found != null) return null; found = c; } } return found;}
/// `FcNameGetConstantFor`: a constant by name, for one object only.pub fn constantFor(text: []const u8, object: Object) ?Constant { const b = switch (object) { .builtin => |b| b, .custom => return null, }; for (constants) |c| { if (c.object == b and str.eqlIgnoreCase(c.name, text)) return c; } return null;}
/// `FcNameGetConstantNameFromObject`: the first name for a value of an/// object.pub fn constantName(object: Builtin, value: i32) ?[]const u8 { for (constants) |c| { if (c.object == object and c.value == value) return c.name; } return null;}
const testing = std.testing;
test "objects" { try testing.expectEqual(Type.range, Object.fromName("weight").valueType()); try testing.expectEqualStrings("pixelsize", Object.fromName("pixelsize").name()); try testing.expect(Object.fromName("frobnicate") == .custom); try testing.expect(Object.fromName("weight").acceptsType(.integer)); try testing.expect(!Object.fromName("family").acceptsType(.integer)); try testing.expect(Object.fromName("lang").acceptsType(.string));}
test "constants" { try testing.expectEqual(@as(i32, 200), constant("Bold").?.value); try testing.expectEqual(Builtin.slant, constant("italic").?.object); try testing.expectEqual(@as(?Constant, null), constant("normal")); try testing.expectEqual(@as(i32, 100), constantFor("normal", .{ .builtin = .width }).?.value); try testing.expectEqual(@as(?Constant, null), constantFor("bold", .{ .builtin = .slant })); try testing.expectEqualStrings("bold", constantName(.weight, 200).?);}