Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 86 additions & 77 deletions scripts/powerline-fontpatcher
Original file line number Diff line number Diff line change
Expand Up @@ -28,88 +28,97 @@ 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.target_fonts = ((fontforge.open("{}({})".format(target_font.name, font_name)) for font_name in fontforge.fontsInFile(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
for fonts in self.target_fonts:
new_fonts = []
for target_font in 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

new_fonts.append(target_font)

# 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))
if len(new_fonts) > 1:
# assume the damn thing is a .ttc
new_fonts[0].generateTtc('{0}.ttc'.format(new_fonts[0].familyname), tuple(new_fonts[1:]))
else:
target_font = new_fonts[0]
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()