char/iocgn-mono
iosevka variant inspired by Simple Köln-Bonn
git clone https://git.t4t.associates/char/iocgn-mono
2ba32a9
main
1"""Render a weight sweep and compare variable endpoints against static references.""" 2 3import hashlib 4import json 5from pathlib import Path 6 7from fontTools .ttLib import TTFont 8from fontTools .varLib .instancer import instantiateVariableFont 9from PIL import Image ,ImageChops ,ImageDraw ,ImageFont ,ImageStat 10 11from render import ROOT ,code_mask 12 13 14def main (): 15output = ROOT / 'build/variable' 16path = ROOT / 'build/TTF/iocgnMono-Variable.ttf' 17sweep = Image .new ('RGB' , (2200 ,3640 ),'#fafafa' ) 18rows = [('Hno mWw r g 0O1Il| \'"`' ,110 ,165 ), 19 ('comment format com rma minimum WWWW wwww mmmm' ,48 ,255 ), 20 ('const row = `${name}: ${count}`; // + - = * < > ()[]{}' ,42 ,325 ), 21 ('if (value != null && count >= 0) { return "airport"; }' ,42 ,385 ), 22 ('aopqnr mWw 6G9g 8B @#%& \'quoted\' "double" `grave`' ,24 ,455 )] 23report = {'font' : {'path' :str (path .relative_to (ROOT )),'sha256' :hashlib .sha256 (path .read_bytes ()).hexdigest ()}, 24'weights' : [200 ,300 ,400 ,550 ,700 ,750 ,800 ],'em_pixels' : [110 ,48 ,42 ,24 ],'endpoints' : {}} 25with TTFont (path )as variable : 26for i ,weight in enumerate (report ['weights' ]): 27font = instantiateVariableFont (variable , {'wght' :weight ,'ital' :0 },inplace = False ) 28panel = Image .new ('RGB' , (2200 ,520 ),'#fafafa' ) 29panel .paste ('#171c22' , (0 ,0 ),code_mask (font ,rows ,panel .size )) 30ImageDraw .Draw (panel ).text ((38 ,20 ),f'iocgn Mono / wght { weight } ' ,fill = '#58616d' ,font = ImageFont .load_default (size = 28 )) 31sweep .paste (panel , (0 ,i * 520 )) 32font .close () 33for style ,weight in [('ExtraLight' ,200 ), ('Regular' ,400 ), ('Bold' ,700 ), ('ExtraBold' ,800 )]: 34font = instantiateVariableFont (variable , {'wght' :weight ,'ital' :0 },inplace = False ) 35directory = ROOT / 'build/native' 36with TTFont (directory / f'iocgnMono- { style } .ttf' )as static : 37grid = [('' .join (chr (cp )for cp in range (start ,min (start + 13 ,127 ))),256 ,240 + i * 300 ) 38for i ,start in enumerate (range (33 ,127 ,13 ))] 39masks = [code_mask (f ,grid , (1800 ,2450 ))for f in (static ,font )] 40difference = ImageChops .difference (* masks ) 41pixel_error = sum (ImageStat .Stat (difference ).sum ) 42ink = sum (ImageStat .Stat (masks [0 ]).sum ) 43report ['endpoints' ][style ]= {'absolute_raster_difference_over_ink' :pixel_error / ink , 44'em_pixels' :256 } 45assert pixel_error / ink < .025 , (style ,pixel_error / ink ) 46overlay = Image .merge ('RGB' , (masks [1 ],masks [0 ],ImageChops .lighter (* masks ))) 47overlay .save (output / f'endpoint- { style . lower () } .png' ) 48font .close () 49charset = Image .new ('RGB' , (6000 ,1650 ),'#fafafa' ) 50grid = [('' .join (chr (cp )for cp in range (start ,min (start + 13 ,127 ))),160 ,210 + i * 180 ) 51for i ,start in enumerate (range (33 ,127 ,13 ))] 52for column ,weight in enumerate ((200 ,400 ,550 ,700 ,800 )): 53font = instantiateVariableFont (variable , {'wght' :weight ,'ital' :0 },inplace = False ) 54panel = Image .new ('RGB' , (1200 ,1650 ),'#fafafa' ) 55panel .paste ('#171c22' , (0 ,0 ),code_mask (font ,grid ,panel .size )) 56ImageDraw .Draw (panel ).text ((38 ,20 ),f'wght { weight } ' ,fill = '#58616d' ,font = ImageFont .load_default (size = 28 )) 57charset .paste (panel , (column * 1200 ,0 )) 58panel .save (output / f'charset- { weight } .png' ) 59font .close () 60charset .save (output / 'charset.png' ) 61sweep .save (output / 'weight-sweep.png' ) 62template = ROOT / 'tools/variable-demo.html' 63 (output / 'index.html' ).write_bytes (template .read_bytes ()) 64report ['demo_sha256' ]= hashlib .sha256 (template .read_bytes ()).hexdigest () 65report ['renderer_sha256' ]= hashlib .sha256 (Path (__file__ ).read_bytes ()).hexdigest () 66 (output / 'render-manifest.json' ).write_text (json .dumps (report ,indent = 2 )+ '\n' ) 67output / 'weight-sweep.png' ) 68'Endpoint raster differences:' ,report ['endpoints' ]) 69 70 71if __name__ == '__main__' : 72main ()