lib/filigree/src/font/model.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub const FontError = error{
4 InvalidFont,
5 MissingTable,
6 UnsupportedCmap,
7 InvalidCmap,
8 InvalidKern,
9 InvalidGsub,
10 InvalidGpos,
11 InvalidGdef,
12 InvalidVariations,
13 };
14
15 pub const Tag = u32;
16
17 pub fn tag(comptime name: []const u8) Tag {
18 if (name.len != 4) @compileError("OpenType tags are four bytes");
19 return (@as(u32, name[0]) << 24) |
20 (@as(u32, name[1]) << 16) |
21 (@as(u32, name[2]) << 8) |
22 @as(u32, name[3]);
23 }
24
25 pub const GlyphClass = enum {
26 unknown,
27 base,
28 ligature,
29 mark,
30 component,
31 };
32
33 pub const LigatureCaret = struct {
34 coordinate: i16,
35 synthesized: bool = false,
36 };
37
38 pub const defaultScriptTag: Tag = tag("DFLT");
39
40 pub const FeatureRange = struct {
41 start: u32 = 0,
42 end: u32 = std.math.maxInt(u32),
43 };
44
45 pub const FeatureSetting = struct {
46 tag: Tag,
47 value: u32 = 1,
48 source: ?FeatureRange = null,
49 };
50
51 pub const VariationAxis = struct {
52 tag: Tag,
53 min_value: i32,
54 default_value: i32,
55 max_value: i32,
56 flags: u16 = 0,
57 name_id: u16 = 0,
58 };
59
60 pub const VariationSetting = struct {
61 tag: Tag,
62 value: f32,
63 };
64
65 pub const NormalizedVariation = struct {
66 tag: Tag,
67 value: i16,
68 };
69
70 pub const LayoutSelection = struct {
71 script: Tag = defaultScriptTag,
72 script_tags: []const Tag = &.{},
73 language: ?Tag = null,
74 features: []const FeatureSetting = &.{},
75 variations: []const VariationSetting = &.{},
76 vertical: bool = false,
77 right_to_left: bool = false,
78 };
79
80 pub fn featureEnabled(selection: LayoutSelection, feature_tag: Tag, default_on: bool, source: ?FeatureRange) bool {
81 var enabled = default_on;
82 for (selection.features) |setting| {
83 if (setting.tag != feature_tag) continue;
84 if (!featureSettingApplies(setting, source)) continue;
85 enabled = setting.value != 0;
86 }
87 return enabled;
88 }
89
90 pub fn fixed16ToFloat(value: i32) f32 {
91 return @as(f32, @floatFromInt(value)) / 65536.0;
92 }
93
94 fn featureSettingApplies(setting: FeatureSetting, source: ?FeatureRange) bool {
95 const setting_source = setting.source orelse return true;
96 const lookup_source = source orelse return false;
97 return setting_source.start < lookup_source.end and lookup_source.start < setting_source.end;
98 }
99
100 pub const noGposVariationIndex: u32 = std.math.maxInt(u32);
101
102 pub const GsubSubstitution = struct {
103 glyph_id: u32,
104 component_count: u16,
105 replacement_count: u16 = 1,
106 replacement_glyphs_offset: usize = 0,
107 target_offset: usize = 0,
108 skip_count: usize = 0,
109 };
110
111 pub const GposValueAdjustment = struct {
112 x_placement: i32 = 0,
113 y_placement: i32 = 0,
114 x_advance: i32 = 0,
115 y_advance: i32 = 0,
116 x_placement_variation: u32 = noGposVariationIndex,
117 y_placement_variation: u32 = noGposVariationIndex,
118 x_advance_variation: u32 = noGposVariationIndex,
119 y_advance_variation: u32 = noGposVariationIndex,
120
121 pub fn isZero(self: GposValueAdjustment) bool {
122 return self.x_placement == 0 and
123 self.y_placement == 0 and
124 self.x_advance == 0 and
125 self.y_advance == 0 and
126 self.x_placement_variation == noGposVariationIndex and
127 self.y_placement_variation == noGposVariationIndex and
128 self.x_advance_variation == noGposVariationIndex and
129 self.y_advance_variation == noGposVariationIndex;
130 }
131
132 pub fn withVariations(self: GposValueAdjustment, face: anytype, settings: []const VariationSetting) GposValueAdjustment {
133 return .{
134 .x_placement = face.gposVariationValue(self.x_placement, settings, self.x_placement_variation),
135 .y_placement = face.gposVariationValue(self.y_placement, settings, self.y_placement_variation),
136 .x_advance = face.gposVariationValue(self.x_advance, settings, self.x_advance_variation),
137 .y_advance = face.gposVariationValue(self.y_advance, settings, self.y_advance_variation),
138 };
139 }
140
141 pub fn add(self: *GposValueAdjustment, other: GposValueAdjustment) void {
142 self.x_placement += other.x_placement;
143 self.y_placement += other.y_placement;
144 self.x_advance += other.x_advance;
145 self.y_advance += other.y_advance;
146 self.x_placement_variation = mergeGposVariationIndex(self.x_placement_variation, other.x_placement_variation);
147 self.y_placement_variation = mergeGposVariationIndex(self.y_placement_variation, other.y_placement_variation);
148 self.x_advance_variation = mergeGposVariationIndex(self.x_advance_variation, other.x_advance_variation);
149 self.y_advance_variation = mergeGposVariationIndex(self.y_advance_variation, other.y_advance_variation);
150 }
151 };
152
153 pub const GposPairAdjustment = struct {
154 left: GposValueAdjustment = .{},
155 right: GposValueAdjustment = .{},
156
157 pub fn isZero(self: GposPairAdjustment) bool {
158 return self.left.isZero() and self.right.isZero();
159 }
160
161 pub fn add(self: *GposPairAdjustment, other: GposPairAdjustment) void {
162 self.left.add(other.left);
163 self.right.add(other.right);
164 }
165 };
166
167 pub const GposPairAdjustmentBefore = struct {
168 adjustment: GposPairAdjustment = .{},
169 left_offset: usize = 0,
170
171 pub fn isZero(self: GposPairAdjustmentBefore) bool {
172 return self.adjustment.isZero();
173 }
174 };
175
176 pub const GposContextualPairAdjustment = struct {
177 adjustment: GposPairAdjustment = .{},
178 target_offset: usize = 0,
179 skip_count: usize = 0,
180
181 pub fn isZero(self: GposContextualPairAdjustment) bool {
182 return self.adjustment.isZero();
183 }
184 };
185
186 pub const GposSingleAdjustment = struct {
187 adjustment: GposValueAdjustment = .{},
188 target_offset: usize = 0,
189 skip_count: usize = 0,
190
191 pub fn isZero(self: GposSingleAdjustment) bool {
192 return self.adjustment.isZero();
193 }
194 };
195
196 pub const GposCursiveAttachment = struct {
197 x_anchor_delta: i32 = 0,
198 y_anchor_delta: i32 = 0,
199 };
200
201 fn mergeGposVariationIndex(existing: u32, incoming: u32) u32 {
202 if (incoming == noGposVariationIndex) return existing;
203 if (existing == noGposVariationIndex) return incoming;
204 return existing;
205 }
206
207 test "tag packs OpenType tags" {
208 try std.testing.expectEqual(@as(u32, 0x68656164), tag("head"));
209 }