1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
|
//! This structure contains a set of SharedGrid structures keyed by
//! unique font configuration.
//!
//! Most terminals (surfaces) will share the same font configuration.
//! This structure allows expensive font information such as
//! the font atlas, glyph cache, font faces, etc. to be shared.
//!
//! This structure is thread-safe when the operations are documented
//! as thread-safe.
const SharedGridSet = @This();
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const font = @import("main.zig");
const CodepointResolver = font.CodepointResolver;
const Collection = font.Collection;
const Discover = font.Discover;
const Style = font.Style;
const Library = font.Library;
const Metrics = font.Metrics;
const CodepointMap = font.CodepointMap;
const DesiredSize = font.face.DesiredSize;
const Face = font.Face;
const SharedGrid = font.SharedGrid;
const discovery = @import("discovery.zig");
const configpkg = @import("../config.zig");
const Config = configpkg.Config;
const log = std.log.scoped(.font_shared_grid_set);
/// The allocator to use for all heap allocations.
alloc: Allocator,
/// The map of font configurations to SharedGrid instances.
map: Map = .{},
/// The font library that is used for all font groups.
font_lib: Library,
/// Font discovery mechanism.
font_discover: ?Discover = null,
/// Lock to protect multi-threaded access to the map.
lock: std.Thread.Mutex = .{},
pub const InitError = Library.InitError;
/// Initialize a new SharedGridSet.
pub fn init(alloc: Allocator) InitError!SharedGridSet {
var font_lib = try Library.init(alloc);
errdefer font_lib.deinit();
return .{
.alloc = alloc,
.map = .{},
.font_lib = font_lib,
};
}
pub fn deinit(self: *SharedGridSet) void {
var it = self.map.iterator();
while (it.next()) |entry| {
entry.key_ptr.deinit();
const v = entry.value_ptr.*;
v.grid.deinit(self.alloc);
self.alloc.destroy(v.grid);
}
self.map.deinit(self.alloc);
if (comptime Discover != void) {
if (self.font_discover) |*v| v.deinit();
}
self.font_lib.deinit();
}
/// Returns the number of cached grids.
pub fn count(self: *SharedGridSet) usize {
self.lock.lock();
defer self.lock.unlock();
return self.map.count();
}
/// Initialize a SharedGrid for the given font configuration. If the
/// SharedGrid is not present it will be initialized with a ref count of
/// 1. If it is present, the ref count will be incremented.
///
/// This is NOT thread-safe.
///
/// The returned data (key and grid) should never be freed. The memory is
/// owned by the set and will be freed when the ref count reaches zero.
pub fn ref(
self: *SharedGridSet,
config: *const DerivedConfig,
font_size: DesiredSize,
) !struct { Key, *SharedGrid } {
var key = try Key.init(self.alloc, config, font_size);
errdefer key.deinit();
self.lock.lock();
defer self.lock.unlock();
const gop = try self.map.getOrPut(self.alloc, key);
if (gop.found_existing) {
log.debug("found cached grid for font config", .{});
// We can deinit the key because we found a cached value.
key.deinit();
// Increment our ref count and return the cache
gop.value_ptr.ref += 1;
return .{ gop.key_ptr.*, gop.value_ptr.grid };
}
errdefer self.map.removeByPtr(gop.key_ptr);
log.debug("initializing new grid for font config", .{});
// A new font config, initialize the cache.
const grid = try self.alloc.create(SharedGrid);
errdefer self.alloc.destroy(grid);
gop.value_ptr.* = .{
.grid = grid,
.ref = 1,
};
grid.* = try .init(self.alloc, resolver: {
// Build our collection. This is the expensive operation that
// involves finding fonts, loading them (maybe, some are deferred),
// etc.
var c = try self.collection(&key, font_size, config);
errdefer c.deinit(self.alloc);
// Setup our enabled/disabled styles
var styles = CodepointResolver.StyleStatus.initFill(true);
styles.set(.bold, config.@"font-style-bold" != .false);
styles.set(.italic, config.@"font-style-italic" != .false);
styles.set(.bold_italic, config.@"font-style-bold-italic" != .false);
// Init our resolver which just requires setting fields.
break :resolver .{
.collection = c,
.styles = styles,
.discover = try self.discover(),
.codepoint_map = key.codepoint_map,
};
});
errdefer grid.deinit(self.alloc);
return .{ gop.key_ptr.*, gop.value_ptr.grid };
}
/// Builds the Collection for the given configuration key and
/// initial font size.
fn collection(
self: *SharedGridSet,
key: *const Key,
size: DesiredSize,
config: *const DerivedConfig,
) !Collection {
// A quick note on memory management:
// - font_lib is owned by the SharedGridSet
// - metric_modifiers is owned by the key which is freed only when
// the ref count for this grid reaches zero.
const load_options: Collection.LoadOptions = .{
.library = self.font_lib,
.size = size,
.freetype_load_flags = key.freetype_load_flags,
};
var c = Collection.init();
errdefer c.deinit(self.alloc);
c.load_options = load_options;
c.metric_modifiers = key.metric_modifiers;
// Search for fonts
if (Discover != void) discover: {
const disco = try self.discover() orelse {
log.warn(
"font discovery not available, cannot search for fonts",
.{},
);
break :discover;
};
// A buffer we use to store the font names for logging.
var name_buf: [256]u8 = undefined;
inline for (@typeInfo(Style).@"enum".fields) |field| {
const style = @field(Style, field.name);
for (key.descriptorsForStyle(style)) |desc| {
{
var disco_it = try disco.discover(self.alloc, desc);
defer disco_it.deinit();
if (try disco_it.next()) |face| {
log.info("font {s}: {s}", .{
field.name,
try face.name(&name_buf),
});
_ = try c.addDeferred(self.alloc, face, .{
.style = style,
.fallback = false,
// No size adjustment for primary fonts.
.size_adjustment = .none,
});
continue;
}
}
// If there are variation configurations and we didn't find
// the font, then we retry the discovery with all stylistic
// bits set to false. This is because some fonts may not
// set the stylistic bit in their table but still support
// axes to mimic the style. At the time of writing, Berkeley
// Mono Variable is like this. See #2140.
if (style != .regular and desc.variations.len > 0) {
var disco_it = try disco.discover(self.alloc, desc: {
var copy = desc;
copy.bold = false;
copy.italic = false;
break :desc copy;
});
defer disco_it.deinit();
if (try disco_it.next()) |face| {
log.info("font {s}: {s}", .{
field.name,
try face.name(&name_buf),
});
_ = try c.addDeferred(self.alloc, face, .{
.style = style,
.fallback = false,
// No size adjustment for primary fonts.
.size_adjustment = .none,
});
continue;
}
}
log.warn("font-family {s} not found: {s}", .{
field.name,
desc.family.?,
});
}
}
}
// Complete our styles to ensure we have something to satisfy every
// possible style request. We do this before adding our built-in font
// because we want to ensure our built-in styles are fallbacks to
// the configured styles.
try c.completeStyles(self.alloc, config.@"font-synthetic-style");
// Our built-in font will be used as a backup
_ = try c.add(
self.alloc,
try .init(
self.font_lib,
font.embedded.variable,
load_options.faceOptions(),
),
.{
.style = .regular,
.fallback = true,
.size_adjustment = font.default_fallback_adjustment,
},
);
try (try c.getFace(try c.add(
self.alloc,
try .init(
self.font_lib,
font.embedded.variable,
load_options.faceOptions(),
),
.{
.style = .bold,
.fallback = true,
.size_adjustment = font.default_fallback_adjustment,
},
))).setVariations(
&.{.{ .id = .init("wght"), .value = 700 }},
load_options.faceOptions(),
);
_ = try c.add(
self.alloc,
try .init(
self.font_lib,
font.embedded.variable_italic,
load_options.faceOptions(),
),
.{
.style = .italic,
.fallback = true,
.size_adjustment = font.default_fallback_adjustment,
},
);
try (try c.getFace(try c.add(
self.alloc,
try .init(
self.font_lib,
font.embedded.variable_italic,
load_options.faceOptions(),
),
.{
.style = .bold_italic,
.fallback = true,
.size_adjustment = font.default_fallback_adjustment,
},
))).setVariations(
&.{.{ .id = .init("wght"), .value = 700 }},
load_options.faceOptions(),
);
// Nerd-font symbols fallback.
_ = try c.add(
self.alloc,
try .init(
self.font_lib,
font.embedded.symbols_nerd_font,
load_options.faceOptions(),
),
.{
.style = .regular,
.fallback = true,
// No size adjustment for the symbols font.
.size_adjustment = .none,
},
);
// On macOS, always search for and add the Apple Emoji font
// as our preferred emoji font for fallback. We do this in case
// people add other emoji fonts to their system, we always want to
// prefer the official one. Users can override this by explicitly
// specifying a font-family for emoji.
if (comptime builtin.target.os.tag.isDarwin() and Discover != void) apple_emoji: {
const disco = try self.discover() orelse break :apple_emoji;
var disco_it = try disco.discover(self.alloc, .{
.family = "Apple Color Emoji",
});
defer disco_it.deinit();
if (try disco_it.next()) |face| {
_ = try c.addDeferred(self.alloc, face, .{
.style = .regular,
.fallback = true,
// No size adjustment for emojis.
.size_adjustment = .none,
});
}
}
// Emoji fallback. We don't include this on Mac since Mac is expected
// to always have the Apple Emoji available on the system.
if (comptime !builtin.target.os.tag.isDarwin() or Discover == void) {
_ = try c.add(
self.alloc,
try .init(
self.font_lib,
font.embedded.emoji,
load_options.faceOptions(),
),
.{
.style = .regular,
.fallback = true,
// No size adjustment for emojis.
.size_adjustment = .none,
},
);
_ = try c.add(
self.alloc,
try .init(
self.font_lib,
font.embedded.emoji_text,
load_options.faceOptions(),
),
.{
.style = .regular,
.fallback = true,
// No size adjustment for emojis.
.size_adjustment = .none,
},
);
}
return c;
}
/// Decrement the ref count for the given key. If the ref count is zero,
/// the grid will be deinitialized and removed from the map.j:w
pub fn deref(self: *SharedGridSet, key: Key) void {
self.lock.lock();
defer self.lock.unlock();
const entry = self.map.getEntry(key) orelse return;
assert(entry.value_ptr.ref >= 1);
// If we have more than one reference, decrement and return.
if (entry.value_ptr.ref > 1) {
entry.value_ptr.ref -= 1;
return;
}
// We are at a zero ref count so deinit the group and remove.
entry.key_ptr.deinit();
entry.value_ptr.grid.deinit(self.alloc);
self.alloc.destroy(entry.value_ptr.grid);
self.map.removeByPtr(entry.key_ptr);
}
/// Map of font configurations to grid instances. The grid
/// instances are pointers that are heap allocated so that they're
/// stable pointers across hash map resizes.
pub const Map = std.HashMapUnmanaged(
Key,
ReffedGrid,
struct {
const KeyType = Key;
pub fn hash(ctx: @This(), k: KeyType) u64 {
_ = ctx;
return k.hashcode();
}
pub fn eql(ctx: @This(), a: KeyType, b: KeyType) bool {
return ctx.hash(a) == ctx.hash(b);
}
},
std.hash_map.default_max_load_percentage,
);
/// Initialize once and return the font discovery mechanism. This remains
/// initialized throughout the lifetime of the application because some
/// font discovery mechanisms (i.e. fontconfig) are unsafe to reinit.
fn discover(self: *SharedGridSet) !?*Discover {
// If we're built without a font discovery mechanism, return null
if (comptime Discover == void) return null;
// If we initialized, use it
if (self.font_discover) |*v| return v;
self.font_discover = .init();
return &self.font_discover.?;
}
/// Ref-counted SharedGrid.
const ReffedGrid = struct {
grid: *SharedGrid,
ref: u32 = 0,
};
/// This is the configuration required to create a key without having
/// to keep the full Ghostty configuration around.
pub const DerivedConfig = struct {
arena: ArenaAllocator,
@"font-family": configpkg.RepeatableString,
@"font-family-bold": configpkg.RepeatableString,
@"font-family-italic": configpkg.RepeatableString,
@"font-family-bold-italic": configpkg.RepeatableString,
@"font-style": configpkg.FontStyle,
@"font-style-bold": configpkg.FontStyle,
@"font-style-italic": configpkg.FontStyle,
@"font-style-bold-italic": configpkg.FontStyle,
@"font-variation": configpkg.RepeatableFontVariation,
@"font-variation-bold": configpkg.RepeatableFontVariation,
@"font-variation-italic": configpkg.RepeatableFontVariation,
@"font-variation-bold-italic": configpkg.RepeatableFontVariation,
@"font-codepoint-map": configpkg.RepeatableCodepointMap,
@"font-synthetic-style": configpkg.FontSyntheticStyle,
@"adjust-cell-width": ?Metrics.Modifier,
@"adjust-cell-height": ?Metrics.Modifier,
@"adjust-font-baseline": ?Metrics.Modifier,
@"adjust-underline-position": ?Metrics.Modifier,
@"adjust-underline-thickness": ?Metrics.Modifier,
@"adjust-strikethrough-position": ?Metrics.Modifier,
@"adjust-strikethrough-thickness": ?Metrics.Modifier,
@"adjust-overline-position": ?Metrics.Modifier,
@"adjust-overline-thickness": ?Metrics.Modifier,
@"adjust-cursor-thickness": ?Metrics.Modifier,
@"adjust-cursor-height": ?Metrics.Modifier,
@"adjust-box-thickness": ?Metrics.Modifier,
@"adjust-icon-height": ?Metrics.Modifier,
@"freetype-load-flags": font.face.FreetypeLoadFlags,
/// Initialize a DerivedConfig. The config should be either a
/// config.Config or another DerivedConfig to clone from.
pub fn init(
alloc_gpa: Allocator,
config: anytype,
) Allocator.Error!DerivedConfig {
var arena = ArenaAllocator.init(alloc_gpa);
errdefer arena.deinit();
const alloc = arena.allocator();
return .{
.@"font-family" = try config.@"font-family".clone(alloc),
.@"font-family-bold" = try config.@"font-family-bold".clone(alloc),
.@"font-family-italic" = try config.@"font-family-italic".clone(alloc),
.@"font-family-bold-italic" = try config.@"font-family-bold-italic".clone(alloc),
.@"font-style" = try config.@"font-style".clone(alloc),
.@"font-style-bold" = try config.@"font-style-bold".clone(alloc),
.@"font-style-italic" = try config.@"font-style-italic".clone(alloc),
.@"font-style-bold-italic" = try config.@"font-style-bold-italic".clone(alloc),
.@"font-variation" = try config.@"font-variation".clone(alloc),
.@"font-variation-bold" = try config.@"font-variation-bold".clone(alloc),
.@"font-variation-italic" = try config.@"font-variation-italic".clone(alloc),
.@"font-variation-bold-italic" = try config.@"font-variation-bold-italic".clone(alloc),
.@"font-codepoint-map" = try config.@"font-codepoint-map".clone(alloc),
.@"font-synthetic-style" = config.@"font-synthetic-style",
.@"adjust-cell-width" = config.@"adjust-cell-width",
.@"adjust-cell-height" = config.@"adjust-cell-height",
.@"adjust-font-baseline" = config.@"adjust-font-baseline",
.@"adjust-underline-position" = config.@"adjust-underline-position",
.@"adjust-underline-thickness" = config.@"adjust-underline-thickness",
.@"adjust-strikethrough-position" = config.@"adjust-strikethrough-position",
.@"adjust-strikethrough-thickness" = config.@"adjust-strikethrough-thickness",
.@"adjust-overline-position" = config.@"adjust-overline-position",
.@"adjust-overline-thickness" = config.@"adjust-overline-thickness",
.@"adjust-cursor-thickness" = config.@"adjust-cursor-thickness",
.@"adjust-cursor-height" = config.@"adjust-cursor-height",
.@"adjust-box-thickness" = config.@"adjust-box-thickness",
.@"adjust-icon-height" = config.@"adjust-icon-height",
.@"freetype-load-flags" = if (font.face.FreetypeLoadFlags != void) config.@"freetype-load-flags" else {},
// This must be last so the arena contains all our allocations
// from above since Zig does assignment in order.
.arena = arena,
};
}
pub fn deinit(self: *DerivedConfig) void {
self.arena.deinit();
}
};
/// The key used to uniquely identify a font configuration.
pub const Key = struct {
arena: ArenaAllocator,
/// The descriptors used for all the fonts added to the
/// initial group, including all styles. This is hashed
/// in order so the order matters. All users of the struct
/// should ensure that the order is consistent.
descriptors: []const discovery.Descriptor = &.{},
/// These are the offsets into the descriptors array for
/// each style. For example, bold is from
/// offsets[@intFromEnum(.bold) - 1] to
/// offsets[@intFromEnum(.bold)].
style_offsets: StyleOffsets = @splat(0),
/// The codepoint map configuration.
codepoint_map: CodepointMap = .{},
/// The metric modifier set configuration.
metric_modifiers: Metrics.ModifierSet = .{},
/// The configured font size for this key. We don't use this
/// directly but it is used as part of the hash for the
/// font grid.
font_size: DesiredSize = .{ .points = 12 },
/// The freetype load flags configuration, only non-void if the
/// freetype backend is enabled.
freetype_load_flags: font.face.FreetypeLoadFlags = font.face.freetype_load_flags_default,
const style_offsets_len = std.enums.directEnumArrayLen(Style, 0);
const StyleOffsets = [style_offsets_len]usize;
comptime {
// We assume this throughout this structure. If this changes
// we may need to change this structure.
assert(@intFromEnum(Style.regular) == 0);
assert(@intFromEnum(Style.bold) == 1);
assert(@intFromEnum(Style.italic) == 2);
assert(@intFromEnum(Style.bold_italic) == 3);
}
pub fn init(
alloc_gpa: Allocator,
config_src: *const DerivedConfig,
font_size: DesiredSize,
) Allocator.Error!Key {
var arena = ArenaAllocator.init(alloc_gpa);
errdefer arena.deinit();
const alloc = arena.allocator();
// Clone our configuration. We need to do this because the lifetime
// of the derived config is usually shorter than that of a key
// and we use pointers into the derived config for the key. We
// can remove this if we wanted by dupe-ing the memory we use
// from DerivedConfig below.
var config = try DerivedConfig.init(alloc, config_src);
var descriptors: std.ArrayList(discovery.Descriptor) = .empty;
defer descriptors.deinit(alloc);
for (config.@"font-family".list.items) |family| {
try descriptors.append(alloc, .{
.family = family,
.style = config.@"font-style".nameValue(),
.size = font_size.points,
.variations = config.@"font-variation".list.items,
});
}
// In all the styled cases below, we prefer to specify an exact
// style via the `font-style` configuration. If a style is not
// specified, we use the discovery mechanism to search for a
// style category such as bold, italic, etc. We can't specify both
// because the latter will restrict the search to only that. If
// a user says `font-style = italic` for the bold face for example,
// no results would be found if we restrict to ALSO searching for
// italic.
for (config.@"font-family-bold".list.items) |family| {
const style = config.@"font-style-bold".nameValue();
try descriptors.append(alloc, .{
.family = family,
.style = style,
.size = font_size.points,
.bold = style == null,
.variations = config.@"font-variation-bold".list.items,
});
}
for (config.@"font-family-italic".list.items) |family| {
const style = config.@"font-style-italic".nameValue();
try descriptors.append(alloc, .{
.family = family,
.style = style,
.size = font_size.points,
.italic = style == null,
.variations = config.@"font-variation-italic".list.items,
});
}
for (config.@"font-family-bold-italic".list.items) |family| {
const style = config.@"font-style-bold-italic".nameValue();
try descriptors.append(alloc, .{
.family = family,
.style = style,
.size = font_size.points,
.bold = style == null,
.italic = style == null,
.variations = config.@"font-variation-bold-italic".list.items,
});
}
// Setup the codepoint map
const codepoint_map: CodepointMap = map: {
const map = config.@"font-codepoint-map";
if (map.map.list.len == 0) break :map .{};
const clone = try config.@"font-codepoint-map".clone(alloc);
break :map clone.map;
};
// Metric modifiers
const metric_modifiers: Metrics.ModifierSet = set: {
var set: Metrics.ModifierSet = .{};
if (config.@"adjust-cell-width") |m| try set.put(alloc, .cell_width, m);
if (config.@"adjust-cell-height") |m| try set.put(alloc, .cell_height, m);
if (config.@"adjust-font-baseline") |m| try set.put(alloc, .cell_baseline, m);
if (config.@"adjust-underline-position") |m| try set.put(alloc, .underline_position, m);
if (config.@"adjust-underline-thickness") |m| try set.put(alloc, .underline_thickness, m);
if (config.@"adjust-strikethrough-position") |m| try set.put(alloc, .strikethrough_position, m);
if (config.@"adjust-strikethrough-thickness") |m| try set.put(alloc, .strikethrough_thickness, m);
if (config.@"adjust-overline-position") |m| try set.put(alloc, .overline_position, m);
if (config.@"adjust-overline-thickness") |m| try set.put(alloc, .overline_thickness, m);
if (config.@"adjust-cursor-thickness") |m| try set.put(alloc, .cursor_thickness, m);
if (config.@"adjust-cursor-height") |m| try set.put(alloc, .cursor_height, m);
if (config.@"adjust-box-thickness") |m| try set.put(alloc, .box_thickness, m);
if (config.@"adjust-icon-height") |m| try set.put(alloc, .icon_height, m);
break :set set;
};
const regular_offset = config.@"font-family".list.items.len;
const bold_offset = regular_offset + config.@"font-family-bold".list.items.len;
const italic_offset = bold_offset + config.@"font-family-italic".list.items.len;
const bold_italic_offset = italic_offset + config.@"font-family-bold-italic".list.items.len;
return .{
.arena = arena,
.descriptors = try descriptors.toOwnedSlice(alloc),
.style_offsets = .{
regular_offset,
bold_offset,
italic_offset,
bold_italic_offset,
},
.codepoint_map = codepoint_map,
.metric_modifiers = metric_modifiers,
.font_size = font_size,
.freetype_load_flags = if (font.face.FreetypeLoadFlags != void)
config.@"freetype-load-flags"
else
font.face.freetype_load_flags_default,
};
}
pub fn deinit(self: *Key) void {
self.arena.deinit();
}
/// Get the descriptors for the given font style that can be
/// used with discovery.
pub fn descriptorsForStyle(
self: Key,
style: Style,
) []const discovery.Descriptor {
const idx = @intFromEnum(style);
const start: usize = if (idx == 0) 0 else self.style_offsets[idx - 1];
const end = self.style_offsets[idx];
return self.descriptors[start..end];
}
/// Hash the key with the given hasher.
pub fn hash(self: Key, hasher: anytype) void {
const autoHash = std.hash.autoHash;
autoHash(hasher, @as(u32, @bitCast(self.font_size.points)));
autoHash(hasher, self.font_size.xdpi);
autoHash(hasher, self.font_size.ydpi);
autoHash(hasher, self.descriptors.len);
for (self.descriptors) |d| d.hash(hasher);
self.codepoint_map.hash(hasher);
autoHash(hasher, self.metric_modifiers.count());
autoHash(hasher, self.freetype_load_flags);
if (self.metric_modifiers.count() > 0) {
inline for (@typeInfo(Metrics.Key).@"enum".fields) |field| {
const key = @field(Metrics.Key, field.name);
if (self.metric_modifiers.get(key)) |value| {
autoHash(hasher, key);
value.hash(hasher);
}
}
}
}
/// Returns a hash code that can be used to uniquely identify this
/// action.
pub fn hashcode(self: Key) u64 {
var hasher = std.hash.Wyhash.init(0);
self.hash(&hasher);
return hasher.final();
}
};
test "Key" {
const testing = std.testing;
const alloc = testing.allocator;
var cfg = try Config.default(alloc);
defer cfg.deinit();
var keycfg = try DerivedConfig.init(alloc, &cfg);
defer keycfg.deinit();
var k = try Key.init(alloc, &keycfg, .{ .points = 12 });
defer k.deinit();
var k2 = try Key.init(alloc, &keycfg, .{ .points = 12 });
defer k2.deinit();
try testing.expect(k.hashcode() > 0);
try testing.expectEqual(k.hashcode(), k2.hashcode());
}
test "Key different font points" {
const testing = std.testing;
const alloc = testing.allocator;
var cfg = try Config.default(alloc);
defer cfg.deinit();
var keycfg = try DerivedConfig.init(alloc, &cfg);
defer keycfg.deinit();
var k = try Key.init(alloc, &keycfg, .{ .points = 12 });
defer k.deinit();
var k2 = try Key.init(alloc, &keycfg, .{ .points = 16 });
defer k2.deinit();
try testing.expect(k.hashcode() != k2.hashcode());
}
test "Key different font DPI" {
const testing = std.testing;
const alloc = testing.allocator;
var cfg = try Config.default(alloc);
defer cfg.deinit();
var keycfg = try DerivedConfig.init(alloc, &cfg);
defer keycfg.deinit();
var k = try Key.init(alloc, &keycfg, .{ .points = 12, .xdpi = 1 });
defer k.deinit();
var k2 = try Key.init(alloc, &keycfg, .{ .points = 12, .xdpi = 2 });
defer k2.deinit();
try testing.expect(k.hashcode() != k2.hashcode());
}
test SharedGridSet {
const testing = std.testing;
const alloc = testing.allocator;
var set = try SharedGridSet.init(alloc);
defer set.deinit();
var cfg = try Config.default(alloc);
defer cfg.deinit();
var keycfg = try DerivedConfig.init(alloc, &cfg);
defer keycfg.deinit();
// Get a grid for the given config
const key1, const grid1 = try set.ref(&keycfg, .{ .points = 12 });
try testing.expectEqual(@as(usize, 1), set.count());
// Get another
const key2, const grid2 = try set.ref(&keycfg, .{ .points = 12 });
try testing.expectEqual(@as(usize, 1), set.count());
// They should be pointer equivalent
try testing.expectEqual(@intFromPtr(grid1), @intFromPtr(grid2));
// If I deref grid2 then we should still have a count of 1
set.deref(key2);
try testing.expectEqual(@as(usize, 1), set.count());
// If I deref grid1 then we should have a count of 0
set.deref(key1);
try testing.expectEqual(@as(usize, 0), set.count());
}
|