-rw-r--r-- minify_fontawesome.py
1 # -*- coding: utf-8 -*- 2 # 3 # pelican-minify-fontawesome 4 # 5 # Copyright (C) 2019 mcol@posteo.net 6 # 7 # This program is free software: you can redistribute it and/or modify 8 # it under the terms of the GNU General Public License as published by 9 # the Free Software Foundation, either version 3 of the License, or 10 # (at your option) any later version. 11 # 12 # This program is distributed in the hope that it will be useful, 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 # GNU General Public License for more details. 16 # 17 # You should have received a copy of the GNU General Public License 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 19 20 21 import fontforge 22 import os 23 import re 24 25 from docutils import nodes 26 from docutils.parsers.rst import roles 27 from pelican import signals 28 29 30 def copy_glyphs(source, dest, css_blocks): 31 """ 32 Copy only used icons from the font files into the output folder. 33 """ 34 icons = [] 35 for block in css_blocks: 36 match = re.findall('\.fa-(.*?):', block) 37 icons.append(match[0]) 38 39 if not os.path.isdir(dest): 40 os.mkdir(dest) 41 for root, dirs, files in os.walk(source): 42 for f in files: 43 if f.endswith('woff'): 44 font = fontforge.open(os.path.join(root, f)) 45 46 selected = False 47 for icon in icons: 48 try: 49 font.selection.select(("more",), icon) 50 selected = True 51 except ValueError: 52 pass 53 54 if selected: 55 font.selection.invert() 56 font.clear() 57 font.generate(os.path.join(dest, f)) 58 font.close() 59 60 print(f'Font Awesome icons incorporated: {icons}') 61 62 def get_classes(folder, extra_icons): 63 """ 64 Gets a list of font awesome CSS classes defined in all html files within a 65 folder i.e. those beginning with 'fa'. 66 """ 67 fa_classes = extra_icons if extra_icons else [] 68 all_classes = [] 69 70 for root, dirs, files in os.walk(folder): 71 for f in files: 72 if f.endswith('html'): 73 with open(os.path.join(root, f), "r") as fd: 74 matches = re.findall('class=[\'"](.*?)[\'"]', fd.read()) 75 if matches: 76 for match in matches: 77 all_classes.extend(match.split()) 78 79 for cls in set(all_classes): 80 if cls.startswith('fa'): 81 fa_classes.append(cls) 82 83 return fa_classes 84 85 86 def copy_css(output_path, css_file, extra_icons): 87 """ 88 Copy css for only used icons over to output folder. Returns the css blocks 89 corresponding to these icons so we know which icons to copy from the font 90 files. 91 """ 92 with open(css_file, 'r') as fd: 93 contents = fd.read() 94 95 base_css = re.sub('\.fa-[\w-]+:before.*?}', '', contents) 96 for string in ['brands-400', 'regular-400', 'solid-900']: 97 base_css = re.sub( 98 f'src:url\(\.\./webfonts/fa-{string}\.eot\).*?\}}', 99 f'src:url(../webfonts/fa-{string}.woff) format("woff")}}', 100 base_css 101 ) 102 103 css_blocks = [] 104 for cls in get_classes(output_path, extra_icons): 105 match = re.search(f'\.{cls}:before.*?}}', contents) 106 if match: 107 css_blocks.append(match.group(0)) 108 109 css = base_css + ''.join(css_blocks) 110 css_file = os.path.join(output_path, 'theme', 'css', 'fa.css') 111 with open(css_file, 'w') as fd: 112 fd.write(css) 113 114 return css_blocks 115 116 117 def output_font(instance): 118 """ 119 Main function that identifies used icons and copies their CSS and font 120 definitions into the output folder. 121 """ 122 FONT_PATH = instance.settings.get('MINIFY_FONTAWESOME', None) 123 if not FONT_PATH or not os.path.isdir(FONT_PATH): 124 return 125 126 output_path = instance.output_path 127 css_file = os.path.join(FONT_PATH, 'css', 'all.min.css') 128 129 css_blocks = copy_css( 130 output_path, 131 css_file, 132 instance.settings.get('FONTAWESOME_EXTRA', None) 133 ) 134 135 copy_glyphs( 136 os.path.join(FONT_PATH, 'webfonts'), 137 os.path.join(output_path, 'theme', 'webfonts'), 138 css_blocks, 139 ) 140 141 142 def rst_span(name, rawtext, text, lineno, inliner, options={}, content=[]): 143 """ 144 A RST role that replaces inline glyphs e.g. :fas:`cat` with the appropriate 145 HTML span tag. 146 """ 147 return [nodes.raw('', f'<span class="{name} fa-{text}"></span>', format='html')], [] 148 149 150 def register(): 151 # Register main program 152 signals.finalized.connect(output_font) 153 154 # Register font awesome RST roles 155 roles.register_local_role('fas', rst_span) 156 roles.register_local_role('far', rst_span) 157 roles.register_local_role('fab', rst_span) 158