lib/filigree/src/font/math.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const binary = @import("binary.zig");
3 const layout = @import("../layout/root.zig");
4 const model = @import("model.zig");
5
6 const FontError = model.FontError;
7 const Table = binary.Table;
8 const readI16 = binary.readI16;
9 const readU16 = binary.readU16;
10
11 const math_header_size = 10;
12 const math_constants_size = 214;
13 const math_glyph_info_header_size = 8;
14 const math_top_accent_attachment_header_size = 4;
15 const math_variants_header_size = 10;
16 const math_glyph_construction_header_size = 4;
17 const math_glyph_variant_size = 4;
18 const math_glyph_assembly_header_size = 6;
19 const math_glyph_part_size = 10;
20 const math_value_record_size = 4;
21 const glyph_part_extender_flag = 0x0001;
22 const max_assembly_parts = 4096;
23
24 pub const ValueRecord = struct {
25 value: i16,
26 device_offset: u16,
27 };
28
29 pub const Constants = struct {
30 script_percent_scale_down: i16,
31 script_script_percent_scale_down: i16,
32 delimited_sub_formula_min_height: u16,
33 display_operator_min_height: u16,
34 math_leading: ValueRecord,
35 axis_height: ValueRecord,
36 accent_base_height: ValueRecord,
37 flattened_accent_base_height: ValueRecord,
38 subscript_shift_down: ValueRecord,
39 subscript_top_max: ValueRecord,
40 subscript_baseline_drop_min: ValueRecord,
41 superscript_shift_up: ValueRecord,
42 superscript_shift_up_cramped: ValueRecord,
43 superscript_bottom_min: ValueRecord,
44 superscript_baseline_drop_max: ValueRecord,
45 sub_superscript_gap_min: ValueRecord,
46 superscript_bottom_max_with_subscript: ValueRecord,
47 space_after_script: ValueRecord,
48 upper_limit_gap_min: ValueRecord,
49 upper_limit_baseline_rise_min: ValueRecord,
50 lower_limit_gap_min: ValueRecord,
51 lower_limit_baseline_drop_min: ValueRecord,
52 stack_top_shift_up: ValueRecord,
53 stack_top_display_style_shift_up: ValueRecord,
54 stack_bottom_shift_down: ValueRecord,
55 stack_bottom_display_style_shift_down: ValueRecord,
56 stack_gap_min: ValueRecord,
57 stack_display_style_gap_min: ValueRecord,
58 stretch_stack_top_shift_up: ValueRecord,
59 stretch_stack_bottom_shift_down: ValueRecord,
60 stretch_stack_gap_above_min: ValueRecord,
61 stretch_stack_gap_below_min: ValueRecord,
62 fraction_numerator_shift_up: ValueRecord,
63 fraction_numerator_display_style_shift_up: ValueRecord,
64 fraction_denominator_shift_down: ValueRecord,
65 fraction_denominator_display_style_shift_down: ValueRecord,
66 fraction_numerator_gap_min: ValueRecord,
67 fraction_num_display_style_gap_min: ValueRecord,
68 fraction_rule_thickness: ValueRecord,
69 fraction_denominator_gap_min: ValueRecord,
70 fraction_denom_display_style_gap_min: ValueRecord,
71 skewed_fraction_horizontal_gap: ValueRecord,
72 skewed_fraction_vertical_gap: ValueRecord,
73 overbar_vertical_gap: ValueRecord,
74 overbar_rule_thickness: ValueRecord,
75 overbar_extra_ascender: ValueRecord,
76 underbar_vertical_gap: ValueRecord,
77 underbar_rule_thickness: ValueRecord,
78 underbar_extra_descender: ValueRecord,
79 radical_vertical_gap: ValueRecord,
80 radical_display_style_vertical_gap: ValueRecord,
81 radical_rule_thickness: ValueRecord,
82 radical_extra_ascender: ValueRecord,
83 radical_kern_before_degree: ValueRecord,
84 radical_kern_after_degree: ValueRecord,
85 radical_degree_bottom_raise_percent: i16,
86 };
87
88 pub const GlyphVariant = struct {
89 glyph_id: u16,
90 advance_measurement: u16,
91 };
92
93 pub const GlyphAssemblyPart = struct {
94 glyph_id: u16,
95 advance_offset: u16,
96 full_advance: u16,
97 };
98
99 pub const GlyphAssembly = struct {
100 parts: []GlyphAssemblyPart,
101 advance_measurement: u16,
102 italics_correction: ValueRecord,
103
104 pub fn deinit(self: GlyphAssembly, allocator: std.mem.Allocator) void {
105 allocator.free(self.parts);
106 }
107 };
108
109 pub const GlyphInfo = struct {
110 top_accent_attachment: ?TopAccentAttachment,
111
112 pub fn init(data: []const u8, math_table: Table, relative_offset: u16) FontError!GlyphInfo {
113 const glyph_info_offset = try mathChildOffset(math_table, relative_offset, math_glyph_info_header_size);
114 const top_accent_relative = try readU16(data, glyph_info_offset + 2);
115 return .{
116 .top_accent_attachment = if (top_accent_relative == 0) null else try TopAccentAttachment.init(data, math_table, glyph_info_offset, top_accent_relative),
117 };
118 }
119
120 pub fn topAccentAttachment(self: GlyphInfo, data: []const u8, glyph_id: u32) FontError!?ValueRecord {
121 const attachment = self.top_accent_attachment orelse return null;
122 return try attachment.value(data, glyph_id);
123 }
124 };
125
126 const TopAccentAttachment = struct {
127 coverage: layout.Coverage,
128 value_offset: usize,
129 count: u16,
130
131 fn init(data: []const u8, table: Table, glyph_info_offset: usize, relative_offset: u16) FontError!TopAccentAttachment {
132 const offset = try mathChildOffsetFrom(table, glyph_info_offset, relative_offset, math_top_accent_attachment_header_size);
133 const coverage_relative = try readU16(data, offset);
134 const count = try readU16(data, offset + 2);
135 const value_offset = offset + math_top_accent_attachment_header_size;
136 const values_len = @as(usize, count) * math_value_record_size;
137 const offset_relative = offset - table.offset;
138 if (offset_relative > table.len or math_top_accent_attachment_header_size > table.len - offset_relative or values_len > table.len - offset_relative - math_top_accent_attachment_header_size) return error.InvalidFont;
139 const coverage_offset = try mathChildOffsetFrom(table, offset, coverage_relative, 2);
140 const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidFont;
141 return .{
142 .coverage = coverage,
143 .value_offset = value_offset,
144 .count = count,
145 };
146 }
147
148 fn value(self: TopAccentAttachment, data: []const u8, glyph_id: u32) FontError!?ValueRecord {
149 const index = (self.coverage.index(data, glyph_id) catch return error.InvalidFont) orelse return null;
150 if (index >= self.count) return error.InvalidFont;
151 const offset = self.value_offset + @as(usize, index) * math_value_record_size;
152 return .{
153 .value = try readI16(data, offset),
154 .device_offset = try readU16(data, offset + 2),
155 };
156 }
157 };
158
159 pub const Variants = struct {
160 table: Table,
161 variants_offset: usize,
162 min_connector_overlap: u16,
163 vertical_coverage: ?layout.Coverage,
164 horizontal_coverage: ?layout.Coverage,
165 vertical_glyph_count: u16,
166 horizontal_glyph_count: u16,
167 vertical_construction_offsets: usize,
168 horizontal_construction_offsets: usize,
169
170 pub fn init(data: []const u8, math_table: Table, relative_offset: u16) FontError!Variants {
171 const variants_offset = try mathChildOffset(math_table, relative_offset, math_variants_header_size);
172 const min_connector_overlap = try readU16(data, variants_offset);
173 const vertical_coverage_relative = try readU16(data, variants_offset + 2);
174 const horizontal_coverage_relative = try readU16(data, variants_offset + 4);
175 const vertical_glyph_count = try readU16(data, variants_offset + 6);
176 const horizontal_glyph_count = try readU16(data, variants_offset + 8);
177 const vertical_construction_offsets = variants_offset + math_variants_header_size;
178 const horizontal_construction_offsets = vertical_construction_offsets + @as(usize, vertical_glyph_count) * 2;
179 const construction_arrays_len = @as(usize, vertical_glyph_count) * 2 + @as(usize, horizontal_glyph_count) * 2;
180 _ = try mathVariantsChildOffset(math_table, variants_offset, math_variants_header_size, construction_arrays_len);
181 const vertical_coverage = try optionalCoverage(data, math_table, variants_offset, vertical_coverage_relative, vertical_glyph_count);
182 const horizontal_coverage = try optionalCoverage(data, math_table, variants_offset, horizontal_coverage_relative, horizontal_glyph_count);
183 var variants = Variants{
184 .table = math_table,
185 .variants_offset = variants_offset,
186 .min_connector_overlap = min_connector_overlap,
187 .vertical_coverage = vertical_coverage,
188 .horizontal_coverage = horizontal_coverage,
189 .vertical_glyph_count = vertical_glyph_count,
190 .horizontal_glyph_count = horizontal_glyph_count,
191 .vertical_construction_offsets = vertical_construction_offsets,
192 .horizontal_construction_offsets = horizontal_construction_offsets,
193 };
194 try variants.validateConstructions(data, .vertical);
195 try variants.validateConstructions(data, .horizontal);
196 return variants;
197 }
198
199 pub fn verticalVariant(self: Variants, data: []const u8, glyph_id: u32, target_measurement: u16) FontError!?GlyphVariant {
200 return self.variant(data, glyph_id, target_measurement, .vertical);
201 }
202
203 pub fn horizontalVariant(self: Variants, data: []const u8, glyph_id: u32, target_measurement: u16) FontError!?GlyphVariant {
204 return self.variant(data, glyph_id, target_measurement, .horizontal);
205 }
206
207 pub fn verticalAssemblyAlloc(self: Variants, allocator: std.mem.Allocator, data: []const u8, glyph_id: u32, target_measurement: u16) AssemblyError!?GlyphAssembly {
208 return self.assemblyAlloc(allocator, data, glyph_id, target_measurement, .vertical);
209 }
210
211 pub fn horizontalAssemblyAlloc(self: Variants, allocator: std.mem.Allocator, data: []const u8, glyph_id: u32, target_measurement: u16) AssemblyError!?GlyphAssembly {
212 return self.assemblyAlloc(allocator, data, glyph_id, target_measurement, .horizontal);
213 }
214
215 fn variant(self: Variants, data: []const u8, glyph_id: u32, target_measurement: u16, direction: VariantDirection) FontError!?GlyphVariant {
216 const construction_offset = (try self.constructionOffset(data, glyph_id, direction)) orelse return null;
217 return try constructionVariant(data, self.table, construction_offset, target_measurement);
218 }
219
220 fn assemblyAlloc(self: Variants, allocator: std.mem.Allocator, data: []const u8, glyph_id: u32, target_measurement: u16, direction: VariantDirection) AssemblyError!?GlyphAssembly {
221 const construction_offset = (try self.constructionOffset(data, glyph_id, direction)) orelse return null;
222 return try constructionAssemblyAlloc(allocator, data, self.table, construction_offset, self.min_connector_overlap, target_measurement);
223 }
224
225 fn constructionOffset(self: Variants, data: []const u8, glyph_id: u32, direction: VariantDirection) FontError!?usize {
226 const coverage = switch (direction) {
227 .vertical => self.vertical_coverage orelse return null,
228 .horizontal => self.horizontal_coverage orelse return null,
229 };
230 const index = (coverage.index(data, glyph_id) catch return error.InvalidFont) orelse return null;
231 const count = switch (direction) {
232 .vertical => self.vertical_glyph_count,
233 .horizontal => self.horizontal_glyph_count,
234 };
235 if (index >= count) return error.InvalidFont;
236 const offset_array = switch (direction) {
237 .vertical => self.vertical_construction_offsets,
238 .horizontal => self.horizontal_construction_offsets,
239 };
240 const relative = try readU16(data, offset_array + @as(usize, index) * 2);
241 return try mathVariantsChildOffset(self.table, self.variants_offset, relative, math_glyph_construction_header_size);
242 }
243
244 fn validateConstructions(self: Variants, data: []const u8, direction: VariantDirection) FontError!void {
245 const count = switch (direction) {
246 .vertical => self.vertical_glyph_count,
247 .horizontal => self.horizontal_glyph_count,
248 };
249 const offset_array = switch (direction) {
250 .vertical => self.vertical_construction_offsets,
251 .horizontal => self.horizontal_construction_offsets,
252 };
253 for (0..count) |index| {
254 const relative = try readU16(data, offset_array + index * 2);
255 const construction_offset = try mathVariantsChildOffset(self.table, self.variants_offset, relative, math_glyph_construction_header_size);
256 try validateConstruction(data, self.table, construction_offset);
257 }
258 }
259 };
260
261 pub const Math = struct {
262 table: Table,
263 constants_offset: usize,
264 constants: Constants,
265 glyph_info: ?GlyphInfo,
266 variants: ?Variants,
267
268 pub fn init(data: []const u8, math_table: Table) FontError!Math {
269 if (math_table.len < math_header_size) return error.InvalidFont;
270 const major = try readU16(data, math_table.offset);
271 const minor = try readU16(data, math_table.offset + 2);
272 if (major != 1 or minor != 0) return error.InvalidFont;
273
274 const constants_relative = try readU16(data, math_table.offset + 4);
275 const glyph_info_relative = try readU16(data, math_table.offset + 6);
276 const variants_relative = try readU16(data, math_table.offset + 8);
277 if (glyph_info_relative > math_table.len or variants_relative > math_table.len) return error.InvalidFont;
278
279 const constants_offset = try mathChildOffset(math_table, constants_relative, math_constants_size);
280 const glyph_info = if (glyph_info_relative == 0) null else try GlyphInfo.init(data, math_table, glyph_info_relative);
281 const variants = if (variants_relative == 0) null else try Variants.init(data, math_table, variants_relative);
282 return .{
283 .table = math_table,
284 .constants_offset = constants_offset,
285 .constants = try readConstants(data, constants_offset),
286 .glyph_info = glyph_info,
287 .variants = variants,
288 };
289 }
290
291 pub fn topAccentAttachment(self: Math, data: []const u8, glyph_id: u32) FontError!?ValueRecord {
292 const glyph_info = self.glyph_info orelse return null;
293 return try glyph_info.topAccentAttachment(data, glyph_id);
294 }
295
296 pub fn verticalVariant(self: Math, data: []const u8, glyph_id: u32, target_measurement: u16) FontError!?GlyphVariant {
297 const variants = self.variants orelse return null;
298 return variants.verticalVariant(data, glyph_id, target_measurement);
299 }
300
301 pub fn horizontalVariant(self: Math, data: []const u8, glyph_id: u32, target_measurement: u16) FontError!?GlyphVariant {
302 const variants = self.variants orelse return null;
303 return variants.horizontalVariant(data, glyph_id, target_measurement);
304 }
305
306 pub fn verticalAssemblyAlloc(self: Math, allocator: std.mem.Allocator, data: []const u8, glyph_id: u32, target_measurement: u16) AssemblyError!?GlyphAssembly {
307 const variants = self.variants orelse return null;
308 return variants.verticalAssemblyAlloc(allocator, data, glyph_id, target_measurement);
309 }
310
311 pub fn horizontalAssemblyAlloc(self: Math, allocator: std.mem.Allocator, data: []const u8, glyph_id: u32, target_measurement: u16) AssemblyError!?GlyphAssembly {
312 const variants = self.variants orelse return null;
313 return variants.horizontalAssemblyAlloc(allocator, data, glyph_id, target_measurement);
314 }
315 };
316
317 const VariantDirection = enum {
318 vertical,
319 horizontal,
320 };
321
322 const AssemblyError = FontError || std.mem.Allocator.Error;
323
324 const GlyphPartRecord = struct {
325 glyph_id: u16,
326 start_connector_length: u16,
327 end_connector_length: u16,
328 full_advance: u16,
329 flags: u16,
330
331 fn extender(self: GlyphPartRecord) bool {
332 return self.flags & glyph_part_extender_flag != 0;
333 }
334 };
335
336 const AssemblyRange = struct {
337 min_size: i64,
338 max_size: i64,
339 };
340
341 const AssemblyConnection = struct {
342 max_overlap: u16,
343 min_overlap: u16,
344 extra: u16 = 0,
345
346 fn capacity(self: AssemblyConnection) u16 {
347 return self.max_overlap - self.min_overlap;
348 }
349 };
350
351 fn readConstants(data: []const u8, offset: usize) FontError!Constants {
352 var cursor = offset;
353 const script_percent_scale_down = try readI16(data, cursor);
354 cursor += 2;
355 const script_script_percent_scale_down = try readI16(data, cursor);
356 cursor += 2;
357 const delimited_sub_formula_min_height = try readU16(data, cursor);
358 cursor += 2;
359 const display_operator_min_height = try readU16(data, cursor);
360 cursor += 2;
361 return .{
362 .script_percent_scale_down = script_percent_scale_down,
363 .script_script_percent_scale_down = script_script_percent_scale_down,
364 .delimited_sub_formula_min_height = delimited_sub_formula_min_height,
365 .display_operator_min_height = display_operator_min_height,
366 .math_leading = try readValue(data, &cursor),
367 .axis_height = try readValue(data, &cursor),
368 .accent_base_height = try readValue(data, &cursor),
369 .flattened_accent_base_height = try readValue(data, &cursor),
370 .subscript_shift_down = try readValue(data, &cursor),
371 .subscript_top_max = try readValue(data, &cursor),
372 .subscript_baseline_drop_min = try readValue(data, &cursor),
373 .superscript_shift_up = try readValue(data, &cursor),
374 .superscript_shift_up_cramped = try readValue(data, &cursor),
375 .superscript_bottom_min = try readValue(data, &cursor),
376 .superscript_baseline_drop_max = try readValue(data, &cursor),
377 .sub_superscript_gap_min = try readValue(data, &cursor),
378 .superscript_bottom_max_with_subscript = try readValue(data, &cursor),
379 .space_after_script = try readValue(data, &cursor),
380 .upper_limit_gap_min = try readValue(data, &cursor),
381 .upper_limit_baseline_rise_min = try readValue(data, &cursor),
382 .lower_limit_gap_min = try readValue(data, &cursor),
383 .lower_limit_baseline_drop_min = try readValue(data, &cursor),
384 .stack_top_shift_up = try readValue(data, &cursor),
385 .stack_top_display_style_shift_up = try readValue(data, &cursor),
386 .stack_bottom_shift_down = try readValue(data, &cursor),
387 .stack_bottom_display_style_shift_down = try readValue(data, &cursor),
388 .stack_gap_min = try readValue(data, &cursor),
389 .stack_display_style_gap_min = try readValue(data, &cursor),
390 .stretch_stack_top_shift_up = try readValue(data, &cursor),
391 .stretch_stack_bottom_shift_down = try readValue(data, &cursor),
392 .stretch_stack_gap_above_min = try readValue(data, &cursor),
393 .stretch_stack_gap_below_min = try readValue(data, &cursor),
394 .fraction_numerator_shift_up = try readValue(data, &cursor),
395 .fraction_numerator_display_style_shift_up = try readValue(data, &cursor),
396 .fraction_denominator_shift_down = try readValue(data, &cursor),
397 .fraction_denominator_display_style_shift_down = try readValue(data, &cursor),
398 .fraction_numerator_gap_min = try readValue(data, &cursor),
399 .fraction_num_display_style_gap_min = try readValue(data, &cursor),
400 .fraction_rule_thickness = try readValue(data, &cursor),
401 .fraction_denominator_gap_min = try readValue(data, &cursor),
402 .fraction_denom_display_style_gap_min = try readValue(data, &cursor),
403 .skewed_fraction_horizontal_gap = try readValue(data, &cursor),
404 .skewed_fraction_vertical_gap = try readValue(data, &cursor),
405 .overbar_vertical_gap = try readValue(data, &cursor),
406 .overbar_rule_thickness = try readValue(data, &cursor),
407 .overbar_extra_ascender = try readValue(data, &cursor),
408 .underbar_vertical_gap = try readValue(data, &cursor),
409 .underbar_rule_thickness = try readValue(data, &cursor),
410 .underbar_extra_descender = try readValue(data, &cursor),
411 .radical_vertical_gap = try readValue(data, &cursor),
412 .radical_display_style_vertical_gap = try readValue(data, &cursor),
413 .radical_rule_thickness = try readValue(data, &cursor),
414 .radical_extra_ascender = try readValue(data, &cursor),
415 .radical_kern_before_degree = try readValue(data, &cursor),
416 .radical_kern_after_degree = try readValue(data, &cursor),
417 .radical_degree_bottom_raise_percent = try readI16(data, cursor),
418 };
419 }
420
421 fn readValue(data: []const u8, cursor: *usize) FontError!ValueRecord {
422 const result = ValueRecord{
423 .value = try readI16(data, cursor.*),
424 .device_offset = try readU16(data, cursor.* + 2),
425 };
426 cursor.* += 4;
427 return result;
428 }
429
430 fn mathChildOffset(table: Table, relative_offset: u16, min_len: usize) FontError!usize {
431 if (relative_offset == 0) return error.InvalidFont;
432 const offset: usize = relative_offset;
433 if (offset > table.len or min_len > table.len - offset) return error.InvalidFont;
434 return table.offset + offset;
435 }
436
437 fn mathChildOffsetFrom(table: Table, parent_offset: usize, relative_offset: usize, min_len: usize) FontError!usize {
438 if (relative_offset == 0) return error.InvalidFont;
439 if (parent_offset < table.offset) return error.InvalidFont;
440 const parent_relative = parent_offset - table.offset;
441 if (parent_relative > table.len or relative_offset > table.len - parent_relative) return error.InvalidFont;
442 const child_relative = parent_relative + relative_offset;
443 if (min_len > table.len - child_relative) return error.InvalidFont;
444 return table.offset + child_relative;
445 }
446
447 fn mathVariantsChildOffset(table: Table, variants_offset: usize, relative_offset: usize, min_len: usize) FontError!usize {
448 return mathChildOffsetFrom(table, variants_offset, relative_offset, min_len);
449 }
450
451 fn mathConstructionChildOffset(table: Table, construction_offset: usize, relative_offset: usize, min_len: usize) FontError!usize {
452 return mathChildOffsetFrom(table, construction_offset, relative_offset, min_len);
453 }
454
455 fn optionalCoverage(data: []const u8, table: Table, variants_offset: usize, relative_offset: u16, count: u16) FontError!?layout.Coverage {
456 if (count == 0) return null;
457 const coverage_offset = try mathVariantsChildOffset(table, variants_offset, relative_offset, 2);
458 return layout.Coverage.init(data, coverage_offset) catch error.InvalidFont;
459 }
460
461 fn validateConstruction(data: []const u8, table: Table, construction_offset: usize) FontError!void {
462 const construction_relative = construction_offset - table.offset;
463 if (construction_relative > table.len or math_glyph_construction_header_size > table.len - construction_relative) return error.InvalidFont;
464 const assembly_relative = try readU16(data, construction_offset);
465 if (assembly_relative != 0) {
466 const assembly_offset = try mathConstructionChildOffset(table, construction_offset, assembly_relative, math_glyph_assembly_header_size);
467 try validateGlyphAssembly(data, table, assembly_offset);
468 }
469 _ = try constructionVariant(data, table, construction_offset, std.math.maxInt(u16));
470 }
471
472 fn constructionVariant(data: []const u8, table: Table, construction_offset: usize, target_measurement: u16) FontError!?GlyphVariant {
473 const construction_relative = construction_offset - table.offset;
474 if (construction_relative > table.len or math_glyph_construction_header_size > table.len - construction_relative) return error.InvalidFont;
475 const variant_count = try readU16(data, construction_offset + 2);
476 const records_len = @as(usize, variant_count) * math_glyph_variant_size;
477 if (records_len > table.len - construction_relative - math_glyph_construction_header_size) return error.InvalidFont;
478 var fallback: ?GlyphVariant = null;
479 var best: ?GlyphVariant = null;
480 for (0..variant_count) |index| {
481 const record_offset = construction_offset + math_glyph_construction_header_size + index * math_glyph_variant_size;
482 const variant = GlyphVariant{
483 .glyph_id = try readU16(data, record_offset),
484 .advance_measurement = try readU16(data, record_offset + 2),
485 };
486 if (fallback == null or variant.advance_measurement > fallback.?.advance_measurement) fallback = variant;
487 if (variant.advance_measurement >= target_measurement and (best == null or variant.advance_measurement < best.?.advance_measurement)) {
488 best = variant;
489 }
490 }
491 return best orelse fallback;
492 }
493
494 fn constructionAssemblyAlloc(allocator: std.mem.Allocator, data: []const u8, table: Table, construction_offset: usize, min_connector_overlap: u16, target_measurement: u16) AssemblyError!?GlyphAssembly {
495 const assembly_relative = try readU16(data, construction_offset);
496 if (assembly_relative == 0) return null;
497 const assembly_offset = try mathConstructionChildOffset(table, construction_offset, assembly_relative, math_glyph_assembly_header_size);
498 return try glyphAssemblyAlloc(allocator, data, table, assembly_offset, min_connector_overlap, target_measurement);
499 }
500
501 fn validateGlyphAssembly(data: []const u8, table: Table, assembly_offset: usize) FontError!void {
502 const assembly_relative = assembly_offset - table.offset;
503 if (assembly_relative > table.len or math_glyph_assembly_header_size > table.len - assembly_relative) return error.InvalidFont;
504 const part_count = try readU16(data, assembly_offset + 4);
505 const records_len = @as(usize, part_count) * math_glyph_part_size;
506 if (records_len > table.len - assembly_relative - math_glyph_assembly_header_size) return error.InvalidFont;
507 for (0..part_count) |index| {
508 _ = try readGlyphPart(data, assembly_offset + math_glyph_assembly_header_size + index * math_glyph_part_size);
509 }
510 }
511
512 fn glyphAssemblyAlloc(allocator: std.mem.Allocator, data: []const u8, table: Table, assembly_offset: usize, min_connector_overlap: u16, target_measurement: u16) AssemblyError!?GlyphAssembly {
513 try validateGlyphAssembly(data, table, assembly_offset);
514 const italics_correction = ValueRecord{
515 .value = try readI16(data, assembly_offset),
516 .device_offset = try readU16(data, assembly_offset + 2),
517 };
518 const part_count = try readU16(data, assembly_offset + 4);
519 if (part_count == 0) return null;
520 const records = try allocator.alloc(GlyphPartRecord, part_count);
521 defer allocator.free(records);
522 var has_extender = false;
523 for (records, 0..) |*record, index| {
524 record.* = try readGlyphPart(data, assembly_offset + math_glyph_assembly_header_size + index * math_glyph_part_size);
525 has_extender = has_extender or record.extender();
526 }
527
528 var repeat_count: usize = 0;
529 while (true) : (repeat_count += 1) {
530 const sequence = try assemblySequenceAlloc(allocator, records, repeat_count);
531 defer allocator.free(sequence);
532 if (sequence.len == 0) {
533 if (!has_extender) return null;
534 continue;
535 }
536 const range = try assemblyRange(sequence, min_connector_overlap);
537 if (@as(i64, target_measurement) <= range.max_size) {
538 return try buildAssembly(allocator, sequence, min_connector_overlap, target_measurement, italics_correction);
539 }
540 if (!has_extender) return null;
541 if (assemblySequenceLen(records, repeat_count + 1) > max_assembly_parts) return error.InvalidFont;
542 }
543 }
544
545 fn readGlyphPart(data: []const u8, offset: usize) FontError!GlyphPartRecord {
546 const part = GlyphPartRecord{
547 .glyph_id = try readU16(data, offset),
548 .start_connector_length = try readU16(data, offset + 2),
549 .end_connector_length = try readU16(data, offset + 4),
550 .full_advance = try readU16(data, offset + 6),
551 .flags = try readU16(data, offset + 8),
552 };
553 if (part.full_advance == 0) return error.InvalidFont;
554 if (part.start_connector_length > part.full_advance or part.end_connector_length > part.full_advance) return error.InvalidFont;
555 if (part.flags & 0xfffe != 0) return error.InvalidFont;
556 return part;
557 }
558
559 fn assemblySequenceLen(records: []const GlyphPartRecord, repeat_count: usize) usize {
560 var len: usize = 0;
561 for (records) |record| {
562 len += if (record.extender()) repeat_count else 1;
563 }
564 return len;
565 }
566
567 fn assemblySequenceAlloc(allocator: std.mem.Allocator, records: []const GlyphPartRecord, repeat_count: usize) AssemblyError![]GlyphPartRecord {
568 const len = assemblySequenceLen(records, repeat_count);
569 if (len > max_assembly_parts) return error.InvalidFont;
570 const sequence = try allocator.alloc(GlyphPartRecord, len);
571 var index: usize = 0;
572 for (records) |record| {
573 const count = if (record.extender()) repeat_count else 1;
574 for (0..count) |_| {
575 sequence[index] = record;
576 index += 1;
577 }
578 }
579 return sequence;
580 }
581
582 fn assemblyRange(parts: []const GlyphPartRecord, min_connector_overlap: u16) FontError!AssemblyRange {
583 if (parts.len == 0) return error.InvalidFont;
584 var min_size: i64 = 0;
585 var max_size: i64 = 0;
586 for (parts) |part| {
587 min_size += part.full_advance;
588 max_size += part.full_advance;
589 }
590 for (0..parts.len - 1) |index| {
591 const connection = assemblyConnection(parts[index], parts[index + 1], min_connector_overlap);
592 min_size -= connection.max_overlap;
593 max_size -= connection.min_overlap;
594 }
595 if (min_size <= 0 or max_size < min_size) return error.InvalidFont;
596 return .{ .min_size = min_size, .max_size = max_size };
597 }
598
599 fn buildAssembly(allocator: std.mem.Allocator, parts: []const GlyphPartRecord, min_connector_overlap: u16, target_measurement: u16, italics_correction: ValueRecord) AssemblyError!GlyphAssembly {
600 const range = try assemblyRange(parts, min_connector_overlap);
601 const connection_count = parts.len - 1;
602 const connections = try allocator.alloc(AssemblyConnection, connection_count);
603 defer allocator.free(connections);
604 for (connections, 0..) |*connection, index| {
605 connection.* = assemblyConnection(parts[index], parts[index + 1], min_connector_overlap);
606 }
607
608 var requested_extra: i64 = 0;
609 if (@as(i64, target_measurement) > range.min_size) requested_extra = @as(i64, target_measurement) - range.min_size;
610 requested_extra = @min(requested_extra, range.max_size - range.min_size);
611 distributeConnectionExtra(connections, requested_extra);
612
613 const out = try allocator.alloc(GlyphAssemblyPart, parts.len);
614 errdefer allocator.free(out);
615 var advance_offset: i64 = 0;
616 for (parts, 0..) |part, index| {
617 if (advance_offset < 0 or advance_offset > std.math.maxInt(u16)) return error.InvalidFont;
618 out[index] = .{
619 .glyph_id = part.glyph_id,
620 .advance_offset = @intCast(advance_offset),
621 .full_advance = part.full_advance,
622 };
623 if (index < connections.len) {
624 const overlap = @as(i64, connections[index].max_overlap) - connections[index].extra;
625 advance_offset += @as(i64, part.full_advance) - overlap;
626 }
627 }
628 const advance_measurement = advance_offset + parts[parts.len - 1].full_advance;
629 if (advance_measurement <= 0 or advance_measurement > std.math.maxInt(u16)) return error.InvalidFont;
630 return .{
631 .parts = out,
632 .advance_measurement = @intCast(advance_measurement),
633 .italics_correction = italics_correction,
634 };
635 }
636
637 fn assemblyConnection(left: GlyphPartRecord, right: GlyphPartRecord, min_connector_overlap: u16) AssemblyConnection {
638 const max_overlap = @min(left.end_connector_length, right.start_connector_length);
639 return .{
640 .max_overlap = max_overlap,
641 .min_overlap = @min(min_connector_overlap, max_overlap),
642 };
643 }
644
645 fn distributeConnectionExtra(connections: []AssemblyConnection, requested_extra: i64) void {
646 if (connections.len == 0 or requested_extra <= 0) return;
647 var remaining = requested_extra;
648 while (remaining > 0) {
649 var eligible: usize = 0;
650 for (connections) |connection| {
651 if (connection.extra < connection.capacity()) eligible += 1;
652 }
653 if (eligible == 0) return;
654 var step = @divTrunc(remaining, @as(i64, @intCast(eligible)));
655 if (step <= 0) step = 1;
656 for (connections) |*connection| {
657 if (remaining <= 0) return;
658 const capacity = connection.capacity();
659 if (connection.extra >= capacity) continue;
660 const available = capacity - connection.extra;
661 const add: u16 = @intCast(@min(@as(i64, available), step));
662 connection.extra += add;
663 remaining -= add;
664 }
665 }
666 }
667
668 fn writeU16(bytes: []u8, offset: usize, value: u16) void {
669 std.mem.writeInt(u16, bytes[offset..][0..2], value, .big);
670 }
671
672 fn writeI16(bytes: []u8, offset: usize, value: i16) void {
673 std.mem.writeInt(i16, bytes[offset..][0..2], value, .big);
674 }
675
676 fn writeValue(bytes: []u8, constants_offset: usize, field_index: usize, value: i16, device_offset: u16) void {
677 const offset = constants_offset + 8 + (field_index - 1) * 4;
678 writeI16(bytes, offset, value);
679 writeU16(bytes, offset + 2, device_offset);
680 }
681
682 fn writeVariant(bytes: []u8, offset: usize, glyph_id: u16, advance_measurement: u16) void {
683 writeU16(bytes, offset, glyph_id);
684 writeU16(bytes, offset + 2, advance_measurement);
685 }
686
687 fn writePart(bytes: []u8, offset: usize, glyph_id: u16, start_connector_length: u16, end_connector_length: u16, full_advance: u16, flags: u16) void {
688 writeU16(bytes, offset, glyph_id);
689 writeU16(bytes, offset + 2, start_connector_length);
690 writeU16(bytes, offset + 4, end_connector_length);
691 writeU16(bytes, offset + 6, full_advance);
692 writeU16(bytes, offset + 8, flags);
693 }
694
695 test "MATH constants parse script, fraction, and radical metrics" {
696 var bytes = @as([(math_header_size + math_constants_size)]u8, @splat(0));
697 writeU16(&bytes, 0, 1);
698 writeU16(&bytes, 2, 0);
699 writeU16(&bytes, 4, math_header_size);
700 const constants_offset = math_header_size;
701 writeI16(&bytes, constants_offset, 80);
702 writeI16(&bytes, constants_offset + 2, 60);
703 writeU16(&bytes, constants_offset + 4, 1500);
704 writeU16(&bytes, constants_offset + 6, 2000);
705 writeValue(&bytes, constants_offset, 6, 12, 0);
706 writeValue(&bytes, constants_offset, 7, 34, 0);
707 writeValue(&bytes, constants_offset, 11, 9, 0);
708 writeValue(&bytes, constants_offset, 13, 18, 0);
709 writeValue(&bytes, constants_offset, 20, 61, 0);
710 writeValue(&bytes, constants_offset, 22, 62, 0);
711 writeValue(&bytes, constants_offset, 24, 63, 0);
712 writeValue(&bytes, constants_offset, 35, 55, 7);
713 writeValue(&bytes, constants_offset, 41, 44, 0);
714 writeValue(&bytes, constants_offset, 46, 120, 0);
715 writeValue(&bytes, constants_offset, 48, 50, 0);
716 writeValue(&bytes, constants_offset, 51, -90, 0);
717 writeI16(&bytes, constants_offset + math_constants_size - 2, 65);
718
719 const parsed = try Math.init(&bytes, .{ .offset = 0, .len = bytes.len });
720
721 try std.testing.expectEqual(@as(i16, 80), parsed.constants.script_percent_scale_down);
722 try std.testing.expectEqual(@as(i16, 60), parsed.constants.script_script_percent_scale_down);
723 try std.testing.expectEqual(@as(u16, 1500), parsed.constants.delimited_sub_formula_min_height);
724 try std.testing.expectEqual(@as(u16, 2000), parsed.constants.display_operator_min_height);
725 try std.testing.expectEqual(@as(i16, 12), parsed.constants.subscript_top_max.value);
726 try std.testing.expectEqual(@as(i16, 34), parsed.constants.subscript_baseline_drop_min.value);
727 try std.testing.expectEqual(@as(i16, 9), parsed.constants.superscript_baseline_drop_max.value);
728 try std.testing.expectEqual(@as(i16, 18), parsed.constants.superscript_bottom_max_with_subscript.value);
729 try std.testing.expectEqual(@as(i16, 61), parsed.constants.stack_top_display_style_shift_up.value);
730 try std.testing.expectEqual(@as(i16, 62), parsed.constants.stack_bottom_display_style_shift_down.value);
731 try std.testing.expectEqual(@as(i16, 63), parsed.constants.stack_display_style_gap_min.value);
732 try std.testing.expectEqual(@as(i16, 55), parsed.constants.fraction_rule_thickness.value);
733 try std.testing.expectEqual(@as(u16, 7), parsed.constants.fraction_rule_thickness.device_offset);
734 try std.testing.expectEqual(@as(i16, 44), parsed.constants.overbar_rule_thickness.value);
735 try std.testing.expectEqual(@as(i16, 120), parsed.constants.radical_vertical_gap.value);
736 try std.testing.expectEqual(@as(i16, 50), parsed.constants.radical_rule_thickness.value);
737 try std.testing.expectEqual(@as(i16, -90), parsed.constants.radical_kern_after_degree.value);
738 try std.testing.expectEqual(@as(i16, 65), parsed.constants.radical_degree_bottom_raise_percent);
739 }
740
741 test "MATH constants reject truncated table" {
742 var bytes = @as([math_header_size]u8, @splat(0));
743 writeU16(&bytes, 0, 1);
744 writeU16(&bytes, 2, 0);
745 writeU16(&bytes, 4, math_header_size);
746
747 try std.testing.expectError(error.InvalidFont, Math.init(&bytes, .{ .offset = 0, .len = bytes.len }));
748 }
749
750 test "MATH glyph info exposes top accent attachments" {
751 var bytes = @as([(math_header_size + math_constants_size + math_glyph_info_header_size + 32)]u8, @splat(0));
752 writeU16(&bytes, 0, 1);
753 writeU16(&bytes, 2, 0);
754 writeU16(&bytes, 4, math_header_size);
755 const glyph_info_offset = math_header_size + math_constants_size;
756 writeU16(&bytes, 6, glyph_info_offset);
757 writeU16(&bytes, glyph_info_offset + 2, math_glyph_info_header_size);
758 const top_offset = glyph_info_offset + math_glyph_info_header_size;
759 writeU16(&bytes, top_offset, 12);
760 writeU16(&bytes, top_offset + 2, 2);
761 writeI16(&bytes, top_offset + 4, 275);
762 writeU16(&bytes, top_offset + 6, 0);
763 writeI16(&bytes, top_offset + 8, 125);
764 writeU16(&bytes, top_offset + 10, 0);
765 writeU16(&bytes, top_offset + 12, 1);
766 writeU16(&bytes, top_offset + 14, 2);
767 writeU16(&bytes, top_offset + 16, 40);
768 writeU16(&bytes, top_offset + 18, 60);
769
770 const parsed = try Math.init(&bytes, .{ .offset = 0, .len = bytes.len });
771 const first = (try parsed.topAccentAttachment(&bytes, 40)).?;
772 const second = (try parsed.topAccentAttachment(&bytes, 60)).?;
773
774 try std.testing.expectEqual(@as(i16, 275), first.value);
775 try std.testing.expectEqual(@as(i16, 125), second.value);
776 try std.testing.expectEqual(@as(?ValueRecord, null), try parsed.topAccentAttachment(&bytes, 41));
777 }
778
779 test "MATH variants choose ready-made vertical glyph by target measurement" {
780 var bytes = @as([(math_header_size + math_constants_size + 64)]u8, @splat(0));
781 writeU16(&bytes, 0, 1);
782 writeU16(&bytes, 2, 0);
783 writeU16(&bytes, 4, math_header_size);
784 const variants_offset = math_header_size + math_constants_size;
785 writeU16(&bytes, 8, variants_offset);
786 writeU16(&bytes, variants_offset, 5);
787 writeU16(&bytes, variants_offset + 2, 12);
788 writeU16(&bytes, variants_offset + 4, 0);
789 writeU16(&bytes, variants_offset + 6, 1);
790 writeU16(&bytes, variants_offset + 8, 0);
791 writeU16(&bytes, variants_offset + 10, 18);
792 writeU16(&bytes, variants_offset + 12, 1);
793 writeU16(&bytes, variants_offset + 14, 1);
794 writeU16(&bytes, variants_offset + 16, 40);
795 writeU16(&bytes, variants_offset + 18, 0);
796 writeU16(&bytes, variants_offset + 20, 3);
797 writeVariant(&bytes, variants_offset + 22, 41, 700);
798 writeVariant(&bytes, variants_offset + 26, 42, 1000);
799 writeVariant(&bytes, variants_offset + 30, 43, 1300);
800
801 const parsed = try Math.init(&bytes, .{ .offset = 0, .len = bytes.len });
802 const exact = (try parsed.verticalVariant(&bytes, 40, 1000)).?;
803 const middle = (try parsed.verticalVariant(&bytes, 40, 800)).?;
804 const too_large = (try parsed.verticalVariant(&bytes, 40, 2000)).?;
805
806 try std.testing.expectEqual(@as(u16, 42), exact.glyph_id);
807 try std.testing.expectEqual(@as(u16, 1000), exact.advance_measurement);
808 try std.testing.expectEqual(@as(u16, 42), middle.glyph_id);
809 try std.testing.expectEqual(@as(u16, 43), too_large.glyph_id);
810 try std.testing.expectEqual(@as(?GlyphVariant, null), try parsed.verticalVariant(&bytes, 39, 800));
811 }
812
813 test "MATH variants choose ready-made horizontal glyph by target measurement" {
814 var bytes = @as([(math_header_size + math_constants_size + 64)]u8, @splat(0));
815 writeU16(&bytes, 0, 1);
816 writeU16(&bytes, 2, 0);
817 writeU16(&bytes, 4, math_header_size);
818 const variants_offset = math_header_size + math_constants_size;
819 writeU16(&bytes, 8, variants_offset);
820 writeU16(&bytes, variants_offset, 5);
821 writeU16(&bytes, variants_offset + 2, 0);
822 writeU16(&bytes, variants_offset + 4, 12);
823 writeU16(&bytes, variants_offset + 6, 0);
824 writeU16(&bytes, variants_offset + 8, 1);
825 writeU16(&bytes, variants_offset + 10, 18);
826 writeU16(&bytes, variants_offset + 12, 1);
827 writeU16(&bytes, variants_offset + 14, 1);
828 writeU16(&bytes, variants_offset + 16, 40);
829 writeU16(&bytes, variants_offset + 18, 0);
830 writeU16(&bytes, variants_offset + 20, 3);
831 writeVariant(&bytes, variants_offset + 22, 41, 700);
832 writeVariant(&bytes, variants_offset + 26, 42, 1000);
833 writeVariant(&bytes, variants_offset + 30, 43, 1300);
834
835 const parsed = try Math.init(&bytes, .{ .offset = 0, .len = bytes.len });
836 const exact = (try parsed.horizontalVariant(&bytes, 40, 1000)).?;
837 const middle = (try parsed.horizontalVariant(&bytes, 40, 800)).?;
838 const too_large = (try parsed.horizontalVariant(&bytes, 40, 2000)).?;
839
840 try std.testing.expectEqual(@as(u16, 42), exact.glyph_id);
841 try std.testing.expectEqual(@as(u16, 1000), exact.advance_measurement);
842 try std.testing.expectEqual(@as(u16, 42), middle.glyph_id);
843 try std.testing.expectEqual(@as(u16, 43), too_large.glyph_id);
844 try std.testing.expectEqual(@as(?GlyphVariant, null), try parsed.horizontalVariant(&bytes, 39, 800));
845 }
846
847 test "MATH assembly repeats extenders and distributes connector overlap" {
848 var bytes = @as([(math_header_size + math_constants_size + 128)]u8, @splat(0));
849 writeU16(&bytes, 0, 1);
850 writeU16(&bytes, 2, 0);
851 writeU16(&bytes, 4, math_header_size);
852 const variants_offset = math_header_size + math_constants_size;
853 writeU16(&bytes, 8, variants_offset);
854 writeU16(&bytes, variants_offset, 50);
855 writeU16(&bytes, variants_offset + 2, 12);
856 writeU16(&bytes, variants_offset + 4, 0);
857 writeU16(&bytes, variants_offset + 6, 1);
858 writeU16(&bytes, variants_offset + 8, 0);
859 writeU16(&bytes, variants_offset + 10, 18);
860 writeU16(&bytes, variants_offset + 12, 1);
861 writeU16(&bytes, variants_offset + 14, 1);
862 writeU16(&bytes, variants_offset + 16, 40);
863 writeU16(&bytes, variants_offset + 18, 4);
864 writeU16(&bytes, variants_offset + 20, 0);
865 writeI16(&bytes, variants_offset + 22, 0);
866 writeU16(&bytes, variants_offset + 24, 0);
867 writeU16(&bytes, variants_offset + 26, 3);
868 writePart(&bytes, variants_offset + 28, 50, 0, 200, 500, 0);
869 writePart(&bytes, variants_offset + 38, 51, 200, 200, 300, glyph_part_extender_flag);
870 writePart(&bytes, variants_offset + 48, 52, 200, 0, 500, 0);
871
872 const parsed = try Math.init(&bytes, .{ .offset = 0, .len = bytes.len });
873 var assembly = (try parsed.verticalAssemblyAlloc(std.testing.allocator, &bytes, 40, 1300)).?;
874 defer assembly.deinit(std.testing.allocator);
875
876 try std.testing.expectEqual(@as(u16, 1300), assembly.advance_measurement);
877 try std.testing.expectEqual(@as(usize, 4), assembly.parts.len);
878 try std.testing.expectEqual(@as(u16, 50), assembly.parts[0].glyph_id);
879 try std.testing.expectEqual(@as(u16, 0), assembly.parts[0].advance_offset);
880 try std.testing.expectEqual(@as(u16, 51), assembly.parts[1].glyph_id);
881 try std.testing.expectEqual(@as(u16, 400), assembly.parts[1].advance_offset);
882 try std.testing.expectEqual(@as(u16, 51), assembly.parts[2].glyph_id);
883 try std.testing.expectEqual(@as(u16, 600), assembly.parts[2].advance_offset);
884 try std.testing.expectEqual(@as(u16, 52), assembly.parts[3].glyph_id);
885 try std.testing.expectEqual(@as(u16, 800), assembly.parts[3].advance_offset);
886 }
887
888 test "MATH horizontal assembly repeats extenders and distributes connector overlap" {
889 var bytes = @as([(math_header_size + math_constants_size + 128)]u8, @splat(0));
890 writeU16(&bytes, 0, 1);
891 writeU16(&bytes, 2, 0);
892 writeU16(&bytes, 4, math_header_size);
893 const variants_offset = math_header_size + math_constants_size;
894 writeU16(&bytes, 8, variants_offset);
895 writeU16(&bytes, variants_offset, 50);
896 writeU16(&bytes, variants_offset + 2, 0);
897 writeU16(&bytes, variants_offset + 4, 12);
898 writeU16(&bytes, variants_offset + 6, 0);
899 writeU16(&bytes, variants_offset + 8, 1);
900 writeU16(&bytes, variants_offset + 10, 18);
901 writeU16(&bytes, variants_offset + 12, 1);
902 writeU16(&bytes, variants_offset + 14, 1);
903 writeU16(&bytes, variants_offset + 16, 40);
904 writeU16(&bytes, variants_offset + 18, 4);
905 writeU16(&bytes, variants_offset + 20, 0);
906 writeI16(&bytes, variants_offset + 22, 0);
907 writeU16(&bytes, variants_offset + 24, 0);
908 writeU16(&bytes, variants_offset + 26, 3);
909 writePart(&bytes, variants_offset + 28, 50, 0, 200, 500, 0);
910 writePart(&bytes, variants_offset + 38, 51, 200, 200, 300, glyph_part_extender_flag);
911 writePart(&bytes, variants_offset + 48, 52, 200, 0, 500, 0);
912
913 const parsed = try Math.init(&bytes, .{ .offset = 0, .len = bytes.len });
914 var assembly = (try parsed.horizontalAssemblyAlloc(std.testing.allocator, &bytes, 40, 1300)).?;
915 defer assembly.deinit(std.testing.allocator);
916
917 try std.testing.expectEqual(@as(u16, 1300), assembly.advance_measurement);
918 try std.testing.expectEqual(@as(usize, 4), assembly.parts.len);
919 try std.testing.expectEqual(@as(u16, 50), assembly.parts[0].glyph_id);
920 try std.testing.expectEqual(@as(u16, 0), assembly.parts[0].advance_offset);
921 try std.testing.expectEqual(@as(u16, 51), assembly.parts[1].glyph_id);
922 try std.testing.expectEqual(@as(u16, 400), assembly.parts[1].advance_offset);
923 try std.testing.expectEqual(@as(u16, 51), assembly.parts[2].glyph_id);
924 try std.testing.expectEqual(@as(u16, 600), assembly.parts[2].advance_offset);
925 try std.testing.expectEqual(@as(u16, 52), assembly.parts[3].glyph_id);
926 try std.testing.expectEqual(@as(u16, 800), assembly.parts[3].advance_offset);
927 }