1# Convert Font Awesome, Fork Awesome, Google Material Design, Material Design Icons, Kenney Game and Ionicons 2# icon font parameters to C89, C++11 and C# compatible formats. 3# 4#------------------------------------------------------------------------------ 5# 1 - Source material 6# 7# 1.1 - Font Awesome 8# 1.1.1 - version 4 9# https://raw.githubusercontent.com/FortAwesome/Font-Awesome/fa-4/src/icons.yml 10# https://github.com/FortAwesome/Font-Awesome/blob/fa-4/fonts/fontawesome-webfont.ttf 11# 1.1.2 - version 5 12# https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/advanced-options/metadata/icons.yml 13# https://github.com/FortAwesome/Font-Awesome/blob/master/web-fonts-with-css/webfonts/fa-brands-400.ttf 14# https://github.com/FortAwesome/Font-Awesome/blob/master/web-fonts-with-css/webfonts/fa-regular-400.ttf 15# https://github.com/FortAwesome/Font-Awesome/blob/master/web-fonts-with-css/webfonts/fa-solid-900.ttf 16# 1.2 - Fork Awesome 17# https://raw.githubusercontent.com/ForkAwesome/Fork-Awesome/master/src/icons/icons.yml 18# https://github.com/ForkAwesome/Fork-Awesome/blob/master/fonts/forkawesome-webfont.ttf 19# 1.3 - Google Material Design 20# https://raw.githubusercontent.com/google/material-design-icons/master/iconfont/codepoints 21# https://github.com/google/material-design-icons/blob/master/iconfont/MaterialIcons-Regular.ttf 22# 1.4 - Material Design Icons 23# https://raw.githubusercontent.com/Templarian/MaterialDesign-Webfont/master/css/materialdesignicons.css 24# https://github.com/Templarian/MaterialDesign-Webfont/blob/master/fonts/materialdesignicons-webfont.ttf 25# 1.5 - Kenney Game icons 26# https://raw.githubusercontent.com/nicodinh/kenney-icon-font/master/css/kenney-icons.css 27# https://github.com/nicodinh/kenney-icon-font/blob/master/fonts/kenney-icon-font.ttf 28# 1.6 - Ionicons 29# https://raw.githubusercontent.com/ionic-team/ionicons/master/src/docs/archived/v2/css/ionicons.css 30# https://github.com/ionic-team/ionicons/blob/master/src/docs/archived/v2/fonts/ionicons.ttf 31# 32#------------------------------------------------------------------------------ 33# 2 - Data sample 34# 35# Font Awesome example: 36# - input: music: 37# changes: 38# - '1' 39# - 5.0.0 40# label: Music 41# search: 42# terms: 43# - note 44# - sound 45# styles: 46# - solid 47# unicode: f001 48# - output C++11: #define ICON_FA_MUSIC u8"\uf001" 49# - output C89: #define ICON_FA_MUSIC "\xEF\x80\x81" 50# - output C#: public const string Music = "\uf001"; 51# 52# All fonts have computed min and max unicode fonts ICON_MIN and ICON_MAX 53# - output C89, C++11: #define ICON_MIN_FA 0xf000 54# #define ICON_MAX_FA 0xf2e0 55# - output C#: public const int IconMin = 0xf000; 56# public const int IconMax = 0xf2e0; 57# 58#------------------------------------------------------------------------------ 59# 3 - Script dependencies 60# 61# 3.1 - Fonts source material online 62# 3.2 - Python 2.7 - https://www.python.org/download/releases/2.7/ 63# 3.3 - Requests - http://docs.python-requests.org/ 64# 3.4 - PyYAML - http://pyyaml.org/ 65# 66#------------------------------------------------------------------------------ 67# 4 - References 68# 69# GitHub repository: https://github.com/juliettef/IconFontCppHeaders/ 70# 71#------------------------------------------------------------------------------ 72 73 74import requests 75import yaml 76 77 78# Fonts 79 80class Font: 81 font_name = '[ ERROR - missing font name ]' 82 font_abbr = '[ ERROR - missing font abbreviation ]' 83 font_url_data = '[ ERROR - missing font data url ]' 84 font_url_ttf = '[ ERROR - missing ttf file url ]' 85 font_file_name_ttf = '[ ERROR - missing ttf file name ]' 86 87 @classmethod 88 def get_icons( cls, input ): 89 # intermediate representation of the fonts data, identify the min and max 90 print( '[ ERROR - missing implementation of class method get_icons for {!s} ]'.format( cls.font_name )) 91 icons_data = {} 92 icons_data.update({ 'font_min' : '[ ERROR - missing font min ]', 93 'font_max' : '[ ERROR - missing font max ]', 94 'icons' : '[ ERROR - missing list of pairs [ font icon name, code ]]' }) 95 return icons_data 96 97 @classmethod 98 def download( cls ): 99 input_raw = '' 100 response = requests.get( cls.font_url_data, timeout = 2 ) 101 if response.status_code == 200: 102 input_raw = response.content 103 print( 'Downloaded - ' + cls.font_name ) 104 else: 105 raise Exception( 'Download failed - ' + cls.font_name ) 106 return input_raw 107 108 @classmethod 109 def get_intermediate_representation( cls ): 110 font_ir = {} 111 input_raw = cls.download() 112 if input_raw: 113 icons_data = cls.get_icons( input_raw ) 114 font_ir.update( icons_data ) 115 font_ir.update({ 'font_url_ttf' : cls.font_url_ttf, 116 'font_url_data' : cls.font_url_data, 117 'font_file_name_ttf' : cls.font_file_name_ttf, 118 'font_name' : cls.font_name, 119 'font_abbr' : cls.font_abbr }) 120 print( 'Generated intermediate data - ' + cls.font_name ) 121 return font_ir 122 123 124class FontFA4( Font ): # legacy Font Awesome version 4 125 font_name = 'Font Awesome 4' 126 font_abbr = 'FA' 127 font_url_data = 'https://raw.githubusercontent.com/FortAwesome/Font-Awesome/fa-4/src/icons.yml' 128 font_url_ttf = 'https://github.com/FortAwesome/Font-Awesome/blob/fa-4/fonts/fontawesome-webfont.ttf' 129 font_file_name_ttf = [[ font_abbr, font_url_ttf[ font_url_ttf.rfind('/')+1: ]]] 130 131 @classmethod 132 def get_icons( self, input ): 133 icons_data = { } 134 data = yaml.safe_load(input) 135 font_min = 'ffff' 136 font_max = '0' 137 icons = [] 138 for item in data[ 'icons' ]: 139 if item[ 'unicode' ] < font_min: 140 font_min = item[ 'unicode' ] 141 if item[ 'unicode' ] >= font_max: 142 font_max = item[ 'unicode' ] 143 icons.append([ item[ 'id' ], item[ 'unicode' ]]) 144 icons_data.update({ 'font_min' : font_min, 145 'font_max' : font_max, 146 'icons' : icons }) 147 return icons_data 148 149 150class FontFK( FontFA4 ): # Fork Awesome, based on Font Awesome 4 151 font_name = 'Fork Awesome' 152 font_abbr = 'FK' 153 font_url_data = 'https://raw.githubusercontent.com/ForkAwesome/Fork-Awesome/master/src/icons/icons.yml' 154 font_url_ttf = 'https://github.com/ForkAwesome/Fork-Awesome/blob/master/fonts/forkawesome-webfont.ttf' 155 156 157class FontFA5( Font ): # Font Awesome version 5. Solid and Regular styles (Regular is a subset of Solid). 158 font_name = 'Font Awesome 5' 159 font_abbr = 'FA' 160 font_url_data = 'https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/advanced-options/metadata/icons.yml' 161 font_url_ttf = 'https://github.com/FortAwesome/Font-Awesome/blob/master/web-fonts-with-css/webfonts/fa-solid-900.ttf, ' +\ 162 'https://github.com/FortAwesome/Font-Awesome/blob/master/web-fonts-with-css/webfonts/fa-regular-400.ttf, ' 163 font_file_name_ttf = [[ 'FAS', 'fa-solid-900.ttf' ], [ 'FAR', 'fa-regular-400.ttf' ]] 164 font_fa_style = [ 'solid', 'regular' ] 165 166 @classmethod 167 def get_icons( self, input ): 168 icons_data = { } 169 data = yaml.safe_load(input) 170 if data: 171 font_min = 'ffff' 172 font_max = '0' 173 icons = [] 174 for key in data: 175 item = data[ key ] 176 for style in item[ 'styles' ]: 177 if style in self.font_fa_style: 178 if [ key, item[ 'unicode' ]] not in icons: 179 if item[ 'unicode' ] < font_min: 180 font_min = item[ 'unicode' ] 181 if item[ 'unicode' ] >= font_max: 182 font_max = item[ 'unicode' ] 183 icons.append([ key, item[ 'unicode' ] ]) 184 icons_data.update({ 'font_min':font_min, 'font_max':font_max, 'icons':icons }) 185 return icons_data 186 187 188class FontFA5Brands( FontFA5 ): # Font Awesome version 5, Brand styles. 189 font_name = 'Font Awesome 5 Brands' 190 font_abbr = 'FAB' 191 font_url_ttf = 'https://github.com/FortAwesome/Font-Awesome/blob/master/web-fonts-with-css/webfonts/fa-brands-400.ttf' 192 font_file_name_ttf = [[ font_abbr, font_url_ttf[ font_url_ttf.rfind('/') + 1: ]]] 193 font_fa_style = [ 'brands' ] 194 195 @classmethod 196 def get_icons( self, input ): 197 icons_data = { } 198 data = yaml.safe_load(input) 199 if data: 200 font_min = 'ffff' 201 font_max = '0' 202 icons = [ ] 203 for key in data: 204 item = data[ key ] 205 for style in item[ 'styles' ]: 206 if style in self.font_fa_style: 207 if item[ 'unicode' ] < font_min: 208 font_min = item[ 'unicode' ] 209 if item[ 'unicode' ] >= font_max: 210 font_max = item[ 'unicode' ] 211 icons.append([ key, item[ 'unicode' ]]) 212 icons_data.update({ 'font_min':font_min, 'font_max':font_max, 'icons':icons }) 213 return icons_data 214 215 216class FontMD( Font ): # Material Design 217 font_name = 'Material Design' 218 font_abbr = 'MD' 219 font_url_data = 'https://raw.githubusercontent.com/google/material-design-icons/master/iconfont/codepoints' 220 font_url_ttf = 'https://github.com/google/material-design-icons/blob/master/iconfont/MaterialIcons-Regular.ttf' 221 font_file_name_ttf = [[ font_abbr, font_url_ttf[ font_url_ttf.rfind('/')+1: ]]] 222 223 @classmethod 224 def get_icons( self, input ): 225 icons_data = {} 226 lines = str.split( input, '\n' ) 227 if lines: 228 font_min = 'ffff' 229 font_max = '0' 230 icons = [] 231 for line in lines : 232 words = str.split(line) 233 if words and len( words ) >= 2: 234 if words[ 1 ] < font_min: 235 font_min = words[ 1 ] 236 if words[ 1 ] >= font_max: 237 font_max = words[ 1 ] 238 icons.append( words ) 239 icons_data.update({ 'font_min' : font_min, 240 'font_max' : font_max, 241 'icons' : icons }) 242 return icons_data 243 244 245class FontMDI( Font ): # Material Design Icons 246 font_name = 'Material Design Icons' 247 font_abbr = 'MDI' 248 font_url_data = 'https://raw.githubusercontent.com/Templarian/MaterialDesign-Webfont/master/css/materialdesignicons.css' 249 font_url_ttf = 'https://github.com/Templarian/MaterialDesign-Webfont/blob/master/fonts/materialdesignicons-webfont.ttf' 250 font_file_name_ttf = [[ font_abbr, font_url_ttf[ font_url_ttf.rfind('/')+1: ]]] 251 252 @classmethod 253 def get_icons( self, input ): 254 icons_data = {} 255 input_trimmed = input[ input.find( '-moz-osx-font-smoothing: grayscale;\n}\n\n' ) + len( '-moz-osx-font-smoothing: grayscale;\n}\n\n' ) : input.find( '.mdi-18px.mdi-set,' )] 256 lines = str.split( input_trimmed, '}\n\n' ) 257 if lines: 258 font_min = 'ffff' 259 font_max = '0' 260 icons = [] 261 for line in lines : 262 if '.mdi-' in line: 263 words = str.split(line) 264 if words and '.mdi-' in words[ 0 ]: 265 font_id = words[ 0 ].partition( '.mdi-' )[2].partition( ':before' )[0] 266 font_code = words[ 3 ].partition( '"\\' )[2].partition( '";' )[0] 267 if font_code < font_min: 268 font_min = font_code 269 if font_code >= font_max: 270 font_max = font_code 271 icons.append([ font_id, font_code ]) 272 icons_data.update({ 'font_min' : font_min, 273 'font_max' : font_max, 274 'icons' : icons }) 275 return icons_data 276 277 278class FontKI( Font ): # Kenney Game icons 279 font_name = 'Kenney' 280 font_abbr = 'KI' 281 font_url_data = 'https://raw.githubusercontent.com/nicodinh/kenney-icon-font/master/css/kenney-icons.css' 282 font_url_ttf = 'https://github.com/nicodinh/kenney-icon-font/blob/master/fonts/kenney-icon-font.ttf' 283 font_file_name_ttf = [[ font_abbr, font_url_ttf[ font_url_ttf.rfind('/')+1: ]]] 284 285 @classmethod 286 def get_icons( self, input ): 287 icons_data = {} 288 lines = str.split( input, '\n' ) 289 if lines: 290 font_min = 'ffff' 291 font_max = '0' 292 icons = [] 293 for line in lines : 294 if '.ki-' in line: 295 words = str.split(line) 296 if words and '.ki-' in words[ 0 ]: 297 font_id = words[ 0 ].partition( '.ki-' )[2].partition( ':before' )[0] 298 font_code = words[ 2 ].partition( '"\\' )[2].partition( '";' )[0] 299 if font_code < font_min: 300 font_min = font_code 301 if font_code >= font_max: 302 font_max = font_code 303 icons.append([ font_id, font_code ]) 304 icons_data.update({ 'font_min' : font_min, 305 'font_max' : font_max, 306 'icons' : icons }) 307 return icons_data 308 309 310class FontII( Font ): # Ionicons 311 font_name = 'Ionicons' 312 font_abbr = 'II' 313 font_url_data = 'https://raw.githubusercontent.com/ionic-team/ionicons/master/src/docs/archived/v2/css/ionicons.css' 314 font_url_ttf = 'https://github.com/ionic-team/ionicons/blob/master/src/docs/archived/v2/fonts/ionicons.ttf' 315 font_file_name_ttf = [[ font_abbr, font_url_ttf[ font_url_ttf.rfind('/') + 1: ]]] 316 317 @classmethod 318 def get_icons( self, input ): 319 icons_data = {} 320 lines = str.split( input, '\n' ) 321 if lines: 322 font_min = 'ffff' 323 font_max = '0' 324 icons = [] 325 for line in lines : 326 if ( '.ion-' and 'content:' ) in line: 327 words = str.split(line) 328 if words and '.ion-' in words[ 0 ]: 329 font_id = words[ 0 ].partition( '.ion-' )[2].partition( ':before' )[0] 330 font_code = words[ 3 ].partition( '"\\' )[2].partition( '";' )[0] 331 if font_code < font_min: 332 font_min = font_code 333 if font_code >= font_max: 334 font_max = font_code 335 icons.append([ font_id, font_code ]) 336 icons_data.update({ 'font_min' : font_min, 337 'font_max' : font_max, 338 'icons' : icons }) 339 return icons_data 340 341 342# Languages 343 344 345class Language: 346 language_name = '[ ERROR - missing language name ]' 347 file_name = '[ ERROR - missing file name ]' 348 intermediate = {} 349 350 def __init__( self, intermediate ): 351 self.intermediate = intermediate 352 353 @classmethod 354 def prelude( cls ): 355 print('[ ERROR - missing implementation of class method prelude for {!s} ]'.format( cls.language_name )) 356 result = '[ ERROR - missing prelude ]' 357 return result 358 359 @classmethod 360 def lines_minmax( cls ): 361 print('[ ERROR - missing implementation of class method lines_minmax for {!s} ]'.format( cls.language_name )) 362 result = '[ ERROR - missing min and max ]' 363 return result 364 365 @classmethod 366 def line_icon( cls, icon ): 367 print('[ ERROR - missing implementation of class method line_icon for {!s} ]'.format( cls.language_name )) 368 result = '[ ERROR - missing icon line ]' 369 return result 370 371 @classmethod 372 def epilogue( cls ): 373 return '' 374 375 @classmethod 376 def convert( cls ): 377 result = cls.prelude() + cls.lines_minmax() 378 for icon in cls.intermediate.get( 'icons' ): 379 line_icon = cls.line_icon( icon ) 380 result += line_icon 381 result += cls.epilogue() 382 print ( 'Converted - {!s} for {!s}' ).format( cls.intermediate.get( 'font_name' ), cls.language_name ) 383 return result 384 385 @classmethod 386 def save_to_file( cls ): 387 filename = cls.file_name.format( name = str.lower(cls.intermediate.get( 'font_name' )).replace( ' ', '_' )) 388 converted = cls.convert() 389 with open( filename, 'w' ) as f: 390 f.write( converted ) 391 print( 'Saved - {!s}' ).format( filename ) 392 393 394class LanguageC89( Language ): 395 language_name = 'C89' 396 file_name = 'icons_{name}.h' 397 398 @classmethod 399 def prelude( cls ): 400 tmpl_prelude = '// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py for language {lang}\n' + \ 401 '// from {url_data}\n' + \ 402 '// for use with {url_ttf}\n' + \ 403 '#pragma once\n\n' 404 result = tmpl_prelude.format(lang = cls.language_name, 405 url_data = cls.intermediate.get( 'font_url_data' ), 406 url_ttf = cls.intermediate.get( 'font_url_ttf' )) 407 tmpl_prelude_define_file_name = '#define FONT_ICON_FILE_NAME_{font_abbr} "{file_name_ttf}"\n' 408 file_names_ttf = cls.intermediate.get( 'font_file_name_ttf' ) 409 for file_name_ttf in file_names_ttf: 410 result += tmpl_prelude_define_file_name.format( font_abbr = file_name_ttf[ 0 ], file_name_ttf = file_name_ttf[ 1 ]) 411 return result + '\n' 412 413 @classmethod 414 def lines_minmax( cls ): 415 tmpl_line_minmax = '#define ICON_{minmax}_{abbr} 0x{val}\n' 416 result = tmpl_line_minmax.format(minmax = 'MIN', 417 abbr = cls.intermediate.get( 'font_abbr' ), 418 val = cls.intermediate.get( 'font_min' )) + \ 419 tmpl_line_minmax.format(minmax = 'MAX', 420 abbr = cls.intermediate.get( 'font_abbr' ), 421 val = cls.intermediate.get( 'font_max' )) 422 return result 423 424 @classmethod 425 def line_icon( cls, icon ): 426 tmpl_line_icon = '#define ICON_{abbr}_{icon} "{code}"\n' 427 icon_name = str.upper( icon[ 0 ]).replace( '-', '_' ) 428 code_base = ''.join([ '{0:x}'.format( ord( x )) for x in unichr( int( icon[ 1 ], 16 )).encode( 'utf-8' )]).upper() 429 icon_code = '\\x' + code_base[ :2 ] + '\\x' + code_base[ 2:4 ] + '\\x' + code_base[ 4: ] 430 result = tmpl_line_icon.format( abbr = cls.intermediate.get( 'font_abbr' ), 431 icon = icon_name, 432 code = icon_code ) 433 return result 434 435 436class LanguageCpp11( LanguageC89 ): 437 language_name = 'C++11' 438 file_name = 'Icons{name}.h' 439 440 @classmethod 441 def line_icon( cls, icon ): 442 tmpl_line_icon = '#define ICON_{abbr}_{icon} u8"\u{code}"\n' 443 icon_name = str.upper( icon[ 0 ]).replace( '-', '_' ) 444 icon_code = icon[ 1 ] 445 result = tmpl_line_icon.format( abbr = cls.intermediate.get( 'font_abbr' ), 446 icon = icon_name, 447 code = icon_code) 448 return result 449 450 451class LanguageCSharp( Language ): 452 language_name = "C#" 453 file_name = 'Icons{name}.cs' 454 455 @classmethod 456 def prelude( cls ): 457 tmpl_prelude = '// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py for language {lang}\n' + \ 458 '// from {url_data}\n' + \ 459 '// for use with {url_ttf}\n' + \ 460 'namespace IconFonts\n' + \ 461 '{{\n' + \ 462 ' public class {font_name}\n' + \ 463 ' {{\n' 464 465 result = tmpl_prelude.format(lang = cls.language_name, 466 url_data = cls.intermediate.get( 'font_url_data' ), 467 url_ttf = cls.intermediate.get( 'font_url_ttf' ), 468 font_name = cls.intermediate.get( 'font_name' ).replace( ' ', '' ) 469 ) 470 tmpl_prelude_define_file_name = ' public const string FontIconFileName = "{file_name_ttf}";\n' 471 file_names_ttf = cls.intermediate.get( 'font_file_name_ttf' ) 472 for file_name_ttf in file_names_ttf: 473 result += tmpl_prelude_define_file_name.format( file_name_ttf = file_name_ttf[ 1 ]) 474 return result + '\n' 475 476 @classmethod 477 def epilogue( cls ): 478 return ' }\n' + \ 479 '}\n' 480 481 @classmethod 482 def lines_minmax( cls ): 483 tmpl_line_minmax = ' public const int Icon{minmax} = 0x{val};\n' 484 result = tmpl_line_minmax.format(minmax = 'Min', 485 val = cls.intermediate.get( 'font_min' )) + \ 486 tmpl_line_minmax.format(minmax = 'Max', 487 val = cls.intermediate.get( 'font_max' )) 488 return result 489 490 @classmethod 491 def line_icon( cls, icon ): 492 493 tmpl_line_icon = ' public const string {icon} = "\u{code}";\n' 494 icon_name = cls.to_camelcase(icon[ 0 ]) 495 icon_code = icon[ 1 ] 496 497 if icon_name[ 0 ].isdigit(): 498 # Variable may not start with a digit 499 icon_name = 'The' + icon_name 500 501 if icon_name == cls.intermediate.get( 'font_name' ).replace( ' ', '' ): 502 # Member may not have same name as enclosing class 503 icon_name += 'Icon' 504 505 result = tmpl_line_icon.format( icon = icon_name, 506 code = icon_code) 507 return result 508 509 @classmethod 510 def to_camelcase( cls, text ): 511 parts = text.split( '-' ) 512 for i in range( len( parts ) ): 513 p = parts[i] 514 parts[ i ] = p[ 0 ].upper() + p[ 1: ].lower() 515 return ''.join( parts ) 516 517 518# Main 519fonts = [ FontFA4, FontFA5, FontFA5Brands, FontFK, FontMD, FontMDI, FontKI, FontII ] 520languages = [ LanguageC89 ] 521 522intermediates = [] 523for font in fonts: 524 try: 525 font_intermediate = font.get_intermediate_representation() 526 intermediates.append( font_intermediate ) 527 except Exception as e: 528 print( '[ ERROR: {!s} ]'.format( e )) 529for interm in intermediates: 530 Language.intermediate = interm 531 for lang in languages: 532 lang.save_to_file() 533