116 lines
4.4 KiB
Python
Executable File
116 lines
4.4 KiB
Python
Executable File
#!/usr/bin/env python2
|
|
# vim:fileencoding=utf-8:noet
|
|
|
|
import argparse
|
|
import sys
|
|
import re
|
|
import os.path
|
|
|
|
try:
|
|
import fontforge
|
|
import psMat
|
|
except ImportError:
|
|
sys.stderr.write('The required FontForge modules could not be loaded.\n\n')
|
|
if sys.version_info.major > 2:
|
|
sys.stderr.write('FontForge only supports Python 2. Please run this script with the Python 2 executable - e.g. "python2 {0}"\n'.format(sys.argv[0]))
|
|
else:
|
|
sys.stderr.write('You need FontForge with Python bindings for this script to work.\n')
|
|
sys.exit(1)
|
|
|
|
# Handle command-line arguments
|
|
parser = argparse.ArgumentParser(description='Font patcher for Powerline. Requires FontForge with Python bindings. Stores the patched font as a new, renamed font file by default.')
|
|
parser.add_argument('target_fonts', help='font files to patch', metavar='font', nargs='+', type=argparse.FileType('rb'))
|
|
parser.add_argument('--no-rename', help='don\'t add " for Powerline" to the font name', default=True, action='store_false', dest='rename_font')
|
|
parser.add_argument('--source-font', help='source symbol font', metavar='font', dest='source_font', default='{0}/fontpatcher-symbols.sfd'.format(sys.path[0]), type=argparse.FileType('rb'))
|
|
args = parser.parse_args()
|
|
|
|
|
|
class FontPatcher(object):
|
|
def __init__(self, source_font, target_fonts, rename_font=True):
|
|
self.source_font = fontforge.open(source_font.name)
|
|
self.target_fonts = (fontforge.open(target_font.name) for target_font in target_fonts)
|
|
self.rename_font = rename_font
|
|
|
|
def patch(self):
|
|
for target_font in self.target_fonts:
|
|
source_font = self.source_font
|
|
target_font_em_original = target_font.em
|
|
target_font.em = 2048
|
|
target_font.encoding = 'ISO10646'
|
|
|
|
# Rename font
|
|
if self.rename_font:
|
|
target_font.familyname += ' for Powerline'
|
|
target_font.fullname += ' for Powerline'
|
|
fontname, style = re.match("^([^-]*)(?:(-.*))?$", target_font.fontname).groups()
|
|
target_font.fontname = fontname + 'ForPowerline'
|
|
if style is not None:
|
|
target_font.fontname += style
|
|
target_font.appendSFNTName('English (US)', 'Preferred Family', target_font.familyname)
|
|
target_font.appendSFNTName('English (US)', 'Compatible Full', target_font.fullname)
|
|
|
|
source_bb = source_font['block'].boundingBox()
|
|
target_bb = [0, 0, 0, 0]
|
|
target_font_width = 0
|
|
|
|
# Find the biggest char width and height in the Latin-1 extended range and the box drawing range
|
|
# This isn't ideal, but it works fairly well - some fonts may need tuning after patching
|
|
for cp in range(0x00, 0x17f) + range(0x2500, 0x2600):
|
|
try:
|
|
bbox = target_font[cp].boundingBox()
|
|
except TypeError:
|
|
continue
|
|
if not target_font_width:
|
|
target_font_width = target_font[cp].width
|
|
if bbox[0] < target_bb[0]:
|
|
target_bb[0] = bbox[0]
|
|
if bbox[1] < target_bb[1]:
|
|
target_bb[1] = bbox[1]
|
|
if bbox[2] > target_bb[2]:
|
|
target_bb[2] = bbox[2]
|
|
if bbox[3] > target_bb[3]:
|
|
target_bb[3] = bbox[3]
|
|
|
|
# Find source and target size difference for scaling
|
|
x_ratio = (target_bb[2] - target_bb[0]) / (source_bb[2] - source_bb[0])
|
|
y_ratio = (target_bb[3] - target_bb[1]) / (source_bb[3] - source_bb[1])
|
|
scale = psMat.scale(x_ratio, y_ratio)
|
|
|
|
# Find source and target midpoints for translating
|
|
x_diff = target_bb[0] - source_bb[0]
|
|
y_diff = target_bb[1] - source_bb[1]
|
|
translate = psMat.translate(x_diff, y_diff)
|
|
transform = psMat.compose(scale, translate)
|
|
|
|
# Create new glyphs from symbol font
|
|
for source_glyph in source_font.glyphs():
|
|
if source_glyph == source_font['block']:
|
|
# Skip the symbol font block glyph
|
|
continue
|
|
|
|
# Select and copy symbol from its encoding point
|
|
source_font.selection.select(source_glyph.encoding)
|
|
source_font.copy()
|
|
|
|
# Select and paste symbol to its unicode code point
|
|
target_font.selection.select(source_glyph.unicode)
|
|
target_font.paste()
|
|
|
|
# Transform the glyph
|
|
target_font.transform(transform)
|
|
|
|
# Reset the font's glyph width so it's still considered monospaced
|
|
target_font[source_glyph.unicode].width = target_font_width
|
|
|
|
target_font.em = target_font_em_original
|
|
|
|
# Generate patched font
|
|
extension = os.path.splitext(target_font.path)[1]
|
|
if extension.lower() not in ['.ttf', '.otf']:
|
|
# Default to OpenType if input is not TrueType/OpenType
|
|
extension = '.otf'
|
|
target_font.generate('{0}{1}'.format(target_font.fullname, extension))
|
|
|
|
fp = FontPatcher(args.source_font, args.target_fonts, args.rename_font)
|
|
fp.patch()
|