@@ -39,15 +39,19 @@ class GlyphSetBuilder:
39
39
glyphs : dict [str , GSGlyph ]
40
40
41
41
def __init__ (self ):
42
- self .glyphs = {}
42
+ self .font = GSFont ()
43
+ self .font .format_version = 3
43
44
44
45
def build (self ) -> dict [str , GSGlyph ]:
45
- return self .glyphs
46
+ return { g . name : g for g in self .font . glyphs }
46
47
47
48
def add_glyph (self , name : str , build_fn : Callable [["GlyphBuilder" ], None ]) -> Self :
48
49
glyph = GlyphBuilder (name )
49
50
build_fn (glyph )
50
- self .glyphs [name ] = glyph .build ()
51
+ # this inserts the glyph in the font and sets the glyph.parent attribute to it;
52
+ # GSLayer._is_bracket_layer()/_bracket_axis_rules() require this to check the
53
+ # glyph.parent.format_version to determine where to look for the axis rules
54
+ self .font .glyphs .append (glyph .build ())
51
55
return self
52
56
53
57
@@ -60,6 +64,7 @@ def __init__(self, name: str):
60
64
glyph .category = info .category
61
65
glyph .subCategory = info .subCategory
62
66
self .num_masters = 0
67
+ self .num_alternates = 0
63
68
self .add_master_layer ()
64
69
65
70
def build (self ) -> GSGlyph :
@@ -71,6 +76,7 @@ def add_master_layer(self) -> Self:
71
76
f"master-{ self .num_masters } "
72
77
)
73
78
self .num_masters += 1
79
+ self .num_alternates = 0
74
80
self .glyph .layers .append (layer )
75
81
self .current_layer = layer
76
82
return self
@@ -85,6 +91,20 @@ def add_backup_layer(self, associated_master_idx=0):
85
91
self .current_layer = layer
86
92
return self
87
93
94
+ def add_bracket_layer (self , axis_rules ):
95
+ assert self .current_layer ._is_master_layer
96
+ associated_master_id = self .current_layer .layerId
97
+ master_layer = self .glyph .layers [associated_master_id ]
98
+ layer = GSLayer ()
99
+ layer .name = f"{ master_layer .name } bracket-{ self .num_alternates } "
100
+ layer .layerId = str (uuid .uuid4 ()).upper ()
101
+ layer .associatedMasterId = associated_master_id
102
+ layer .attributes ["axisRules" ] = axis_rules
103
+ self .num_alternates += 1
104
+ self .glyph .layers .append (layer )
105
+ self .current_layer = layer
106
+ return self
107
+
88
108
def set_category (self , category : str ) -> Self :
89
109
self .glyph .category = category
90
110
return self
@@ -362,31 +382,37 @@ def test_digraphs_arent_ligatures():
362
382
363
383
364
384
def test_propagate_across_layers (caplog ):
365
- # derived from the observed behaviour of glyphs 3.2.2 (3259 )
385
+ # derived from the observed behaviour of glyphs 3.4 (3414 )
366
386
glyphs = (
367
387
GlyphSetBuilder ()
368
388
.add_glyph (
369
389
"A" ,
370
390
lambda glyph : (
371
- glyph .add_anchor ("bottom" , (290 , 10 ))
372
- .add_anchor ("ogonek" , (490 , 3 ))
373
- .add_anchor ("top" , (290 , 690 ))
391
+ glyph .add_anchor ("bottom" , (206 , 16 ))
392
+ .add_anchor ("ogonek" , (360 , 13 ))
393
+ .add_anchor ("top" , (212 , 724 ))
394
+ .add_bracket_layer ([{"min" : 500 }])
395
+ .add_anchor ("bottom" , (206 , 16 ))
396
+ .add_anchor ("top" , (212 , 724 ))
374
397
.add_master_layer ()
375
- .add_anchor ("bottom" , (300 , 0 ))
376
- .add_anchor ("ogonek" , (540 , 10 ))
377
- .add_anchor ("top" , (300 , 700 ))
398
+ .add_anchor ("bottom" , (278 , 12 ))
399
+ .add_anchor ("ogonek" , (464 , 13 ))
400
+ .add_anchor ("top" , (281 , 758 ))
401
+ .add_bracket_layer ([{"min" : 500 }])
402
+ .add_anchor ("bottom" , (278 , 12 ))
403
+ .add_anchor ("top" , (281 , 758 ))
378
404
.add_backup_layer ()
379
405
.add_anchor ("top" , (290 , 690 ))
380
406
),
381
407
)
382
408
.add_glyph (
383
409
"acutecomb" ,
384
410
lambda glyph : (
385
- glyph .add_anchor ("_top" , (335 , 502 ))
386
- .add_anchor ("top" , (353 , 721 ))
411
+ glyph .add_anchor ("_top" , (150 , 580 ))
412
+ .add_anchor ("top" , (170 , 792 ))
387
413
.add_master_layer ()
388
- .add_anchor ("_top" , (366 , 500 ))
389
- .add_anchor ("top" , (366 , 765 ))
414
+ .add_anchor ("_top" , (167 , 580 ))
415
+ .add_anchor ("top" , (170 , 792 ))
390
416
.add_backup_layer ()
391
417
.add_anchor ("_top" , (335 , 502 ))
392
418
),
@@ -395,10 +421,16 @@ def test_propagate_across_layers(caplog):
395
421
"Aacute" ,
396
422
lambda glyph : (
397
423
glyph .add_component ("A" , (0 , 0 ))
398
- .add_component ("acutecomb" , (- 45 , 188 ))
424
+ .add_component ("acutecomb" , (62 , 144 ))
425
+ .add_bracket_layer ([{"min" : 500 }])
426
+ .add_component ("A" , (20 , 0 ))
427
+ .add_component ("acutecomb" , (82 , 144 ))
399
428
.add_master_layer ()
400
429
.add_component ("A" , (0 , 0 ))
401
- .add_component ("acutecomb" , (- 66 , 200 ))
430
+ .add_component ("acutecomb" , (114 , 178 ))
431
+ .add_bracket_layer ([{"min" : 500 }])
432
+ .add_component ("A" , (30 , 0 ))
433
+ .add_component ("acutecomb" , (144 , 178 ))
402
434
.add_backup_layer ()
403
435
.add_component ("A" , (0 , 0 ))
404
436
.add_component ("acutecomb" , (- 45 , 188 ))
@@ -433,26 +465,45 @@ def test_propagate_across_layers(caplog):
433
465
434
466
new_glyph = glyphs ["Aacute" ]
435
467
assert_anchors (
436
- new_glyph .layers [0 ].anchors ,
468
+ new_glyph .layers ["master-0" ].anchors ,
437
469
[
438
- ("bottom" , (290 , 10 )),
439
- ("ogonek" , (490 , 3 )),
440
- ("top" , (308 , 909 )),
470
+ ("bottom" , (206 , 16 )),
471
+ ("ogonek" , (360 , 13 )),
472
+ ("top" , (232 , 936 )),
441
473
],
442
474
)
443
475
444
476
assert_anchors (
445
- new_glyph .layers [1 ].anchors ,
477
+ new_glyph .layers ["master-1" ].anchors ,
478
+ [
479
+ ("bottom" , (278 , 12 )),
480
+ ("ogonek" , (464 , 13 )),
481
+ ("top" , (284 , 970 )),
482
+ ],
483
+ )
484
+
485
+ # alternate 'bracket' layers should work as well
486
+ alternate_layers = [l for l in new_glyph .layers if l ._is_bracket_layer ()]
487
+ assert len (alternate_layers ) == 2
488
+ assert_anchors (
489
+ alternate_layers [0 ].anchors ,
490
+ [
491
+ ("bottom" , (226 , 16 )),
492
+ ("top" , (252 , 936 )),
493
+ ],
494
+ )
495
+ assert_anchors (
496
+ alternate_layers [1 ].anchors ,
446
497
[
447
- ("bottom" , (300 , 0 )),
448
- ("ogonek" , (540 , 10 )),
449
- ("top" , (300 , 965 )),
498
+ ("bottom" , (308 , 12 )),
499
+ ("top" , (314 , 970 )),
450
500
],
451
501
)
452
502
453
503
# non-master (e.g. backup) layers are silently skipped
454
- assert not new_glyph .layers [2 ]._is_master_layer
455
- assert_anchors (new_glyph .layers [2 ].anchors , [])
504
+ assert not new_glyph .layers [- 1 ]._is_master_layer
505
+ assert not new_glyph .layers [- 1 ]._is_bracket_layer ()
506
+ assert_anchors (new_glyph .layers [- 1 ].anchors , [])
456
507
457
508
assert len (caplog .records ) == 0
458
509
@@ -730,6 +781,9 @@ def test_real_files():
730
781
for g1 , g2 in zip (font .glyphs , expected .glyphs ):
731
782
assert len (g1 .layers ) == len (g2 .layers )
732
783
for l1 , l2 in zip (g1 .layers , g2 .layers ):
733
- assert [(a .name , tuple (a .position )) for a in l1 .anchors ] == [
784
+ # seems like the latest Glyphs.app insists on sorting anchors alphabetically
785
+ # whereas we keep the original order; but what matters is their name and
786
+ # position
787
+ assert sorted ((a .name , tuple (a .position )) for a in l1 .anchors ) == sorted (
734
788
(a .name , tuple (a .position )) for a in l2 .anchors
735
- ]
789
+ )
0 commit comments