char/iocgn-mono
iosevka variant inspired by Simple Köln-Bonn
git clone https://git.t4t.associates/char/iocgn-mono
2ba32a9
main
1"""Combine the upright and ten-degree oblique into one weight/italic variable font.""" 2 3import hashlib 4import json 5from pathlib import Path 6 7from fontTools .designspaceLib import AxisDescriptor ,DesignSpaceDocument ,InstanceDescriptor ,SourceDescriptor 8from fontTools .otlLib .builder import buildStatTable 9from fontTools .pens .recordingPen import DecomposingRecordingPen 10from fontTools .pens .ttGlyphPen import TTGlyphPen 11from fontTools .ttLib import TTFont 12from fontTools .varLib import build 13from fontTools .varLib .instancer import instantiateVariableFont 14 15from masters import MASTERS ,STATIC_STYLES 16from oblique import name_oblique 17 18ROOT = Path (__file__ ).resolve ().parent .parent 19VERSION = 0.517 20 21 22def main (): 23output = ROOT / 'build/combined' 24output .mkdir (parents = True ,exist_ok = True ) 25designspace = DesignSpaceDocument () 26for name ,tag ,minimum ,default ,maximum in (('Weight' ,'wght' ,200 ,400 ,800 ), 27 ('Italic' ,'ital' ,0 ,0 ,1 )): 28axis = AxisDescriptor () 29axis .name ,axis .tag = name ,tag 30axis .minimum ,axis .default ,axis .maximum = minimum ,default ,maximum 31designspace .addAxis (axis ) 32signatures = None 33order = None 34sources = {} 35for italic ,directory in ((0 ,'variable' ), (1 ,'oblique' )): 36for style ,weight in MASTERS : 37path = ROOT / 'build' / directory / f' { style } .ttf' 38sources [str (path .relative_to (ROOT ))]= hashlib .sha256 (path .read_bytes ()).hexdigest () 39with TTFont (path ,recalcTimestamp = False )as font : 40if order is None : 41order = font .getGlyphOrder () 42if font .getGlyphOrder ()!= order : 43raise ValueError (f' { path } : incompatible glyph order' ) 44glyphs = font .getGlyphSet () 45replacements = {} 46# gvar can vary component offsets, but not their affine transforms. 47for name in order : 48if not font ['glyf' ][name ].isComposite (): 49continue 50recording = DecomposingRecordingPen (glyphs ) 51glyphs [name ].draw (recording ) 52pen = TTGlyphPen (None ) 53recording .replay (pen ) 54glyph = pen .glyph () 55if glyph .numberOfContours : 56glyph .flags [0 ]|= 0x40 # Preserve overlapping accent joins after decomposition. 57replacements [name ]= glyph 58for name ,glyph in replacements .items (): 59font ['glyf' ][name ]= glyph 60glyph .recalcBounds (font ['glyf' ]) 61font ['hmtx' ][name ]= (font ['hmtx' ][name ][0 ],getattr (glyph ,'xMin' ,0 )) 62# Like varLib, which otherwise skips the glyph with only a warning, compare every flag. 63current = {name : (tuple (g .endPtsOfContours ),tuple (g .flags )) 64for name in order if (g := font ['glyf' ][name ]).numberOfContours } 65if signatures is None : 66signatures = current 67elif signatures != current : 68different = [name for name in order if signatures .get (name )!= current .get (name )] 69raise ValueError (f' { path } : incompatible point structures: { different } ' ) 70master = output / f' { style } - { italic } .ttf' 71font ['hhea' ].caretSlopeRise = 1000 72font .save (master ) 73source = SourceDescriptor () 74source .name ,source .path = f' { style } - { italic } ' ,str (master ) 75source .familyName = 'iocgn Mono' 76source .styleName = ('Italic' if style == 'Regular' else f' { style } Italic' )if italic else style 77source .location = {'Weight' :weight ,'Italic' :italic } 78source .copyInfo = source .copyLib = source .copyFeatures = weight == 400 and italic == 0 79designspace .addSource (source ) 80weights = (* MASTERS [:2 ],* STATIC_STYLES ,* MASTERS [2 :]) 81for italic in (0 ,1 ): 82for style ,weight in weights : 83instance = InstanceDescriptor () 84instance .familyName = 'iocgn Mono' 85instance .styleName = ('Italic' if style == 'Regular' else f' { style } Italic' )if italic else style 86instance .postScriptFontName = f'iocgnMono- { instance . styleName . replace ( " " , "" ) } ' 87instance .location = {'Weight' :weight ,'Italic' :italic } 88designspace .addInstance (instance ) 89designspace .write (output / 'iocgn.designspace' ) 90variable ,_ ,_ = build (designspace ,optimize = False ) 91variable ['name' ].removeNames (platformID = 1 ) 92# Approximate IUP can make accented letters' base outlines diverge. 93for name ,variations in variable ['gvar' ].variations .items (): 94coordinates ,controls = variable ['glyf' ]._getCoordinatesAndControls (name ,variable ['hmtx' ].metrics ) 95for variation in variations : 96variation .optimize (coordinates ,controls .endPts ,tolerance = 0 ) 97buildStatTable (variable , [ 98 {'tag' :'wght' ,'name' :'Weight' ,'values' : [ 99 {'name' :style ,'value' :weight ,** ({'flags' :2 ,'linkedValue' :700 }if weight == 400 else {})} 100for style ,weight in weights ]}, 101 {'tag' :'ital' ,'name' :'Italic' ,'values' : [ 102 {'name' :'Roman' ,'value' :0 ,'flags' :2 ,'linkedValue' :1 }, 103 {'name' :'Italic' ,'value' :1 }, 104 ]}, 105 ]) 106variable ['head' ].fontRevision = version = VERSION 107variable ['OS/2' ].recalcUnicodeRanges (variable ) 108variable ['OS/2' ].recalcCodePageRanges (variable ) 109values = {3 :f'iocgnMono-Variable- { version :.3f } ' ,4 :'iocgn Mono' , 1105 :f'Version { version :.3f } ; variable weight and italic' ,6 :'iocgnMono-Variable' ,25 :'iocgnMono' } 111for record in variable ['name' ].names : 112if record .nameID in values : 113variable ['name' ].setName (values [record .nameID ],record .nameID , 114record .platformID ,record .platEncID ,record .langID ) 115for name_id ,value in values .items (): 116variable ['name' ].setName (value ,name_id ,3 ,1 ,0x409 ) 117variable .recalcTimestamp = False 118variable ['head' ].created = variable ['head' ].modified = 2082844800 119destination = ROOT / 'build/TTF/iocgnMono-Variable.ttf' 120destination .parent .mkdir (parents = True ,exist_ok = True ) 121variable .save (destination ) 122static_paths = [] 123# Reload compiled tables: varLib leaves optional GDEF fields unset. 124with TTFont (destination ,recalcTimestamp = False )as compiled : 125for style ,weight in (('Regular' ,400 ),* STATIC_STYLES , ('Bold' ,700 )): 126for italic in (0 ,1 ): 127font = instantiateVariableFont (compiled , {'wght' :weight ,'ital' :italic },updateFontNames = True ) 128label = style 129if italic : 130name_oblique (font ,style ,weight ) 131label = 'Oblique' if style == 'Regular' else f' { style } Oblique' 132path = destination .with_name (f'iocgnMono- { label } .ttf' ) 133font .save (path ) 134font .close () 135static_paths .append (path ) 136f'Built { path . name } : static wght { weight } , ital { italic } .' ) 137report = {'axes' : {'wght' : [200 ,400 ,800 ],'ital' : [0 ,0 ,1 ]},'sources' :sources , 138'builder_sha256' :hashlib .sha256 (Path (__file__ ).read_bytes ()).hexdigest (), 139'static_fonts' : {path .name :hashlib .sha256 (path .read_bytes ()).hexdigest ()for path in static_paths }, 140'font' : {'path' :str (destination .relative_to (ROOT )), 141'sha256' :hashlib .sha256 (destination .read_bytes ()).hexdigest ()}} 142 (output / 'manifest.json' ).write_text (json .dumps (report ,indent = 2 )+ '\n' ) 143f'Built { destination . name } : wght 200–800 and ital 0 upright / 1 ten-degree oblique.' ) 144 145 146if __name__ == '__main__' : 147main ()