// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie // 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).?); }