#!/usr/bin/env python2
# -*- coding: utf-8 -*-

import argparse
import os
import re
import sys

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.encoding = 'ISO10646'

			# Rename font
			if self.rename_font:
				target_font.familyname += ' for Powerline'
				target_font.fullname += ' for Powerline'
				target_font.fontname += 'ForPowerline'
				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]

			# 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 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[3] + target_bb[1]) - (source_bb[3] + 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)

			# Generate patched font
			target_font.generate('{0}.otf'.format(target_font.fullname))

fp = FontPatcher(args.source_font, args.target_fonts, args.rename_font)
fp.patch()

Add font patcher script