Forked from miguelsousa/convert_type1_to_opentype-cff.py
Created
November 29, 2025 14:43
-
-
Save palopezv/b234e07945c06ba001981566517f4de6 to your computer and use it in GitHub Desktop.
Converts a Type 1 font into a OpenType-CFF font
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copyright 2023 Adobe, Inc. All Rights Reserved. | |
| # Author: Miguel Sousa | |
| """ | |
| Converts a Type 1 font into a OpenType-CFF font. | |
| """ | |
| import argparse | |
| from fontTools.agl import toUnicode | |
| from fontTools.fontBuilder import FontBuilder | |
| from fontTools.misc.cliTools import makeOutputFileName | |
| from fontTools.pens.basePen import NullPen | |
| from fontTools.pens.t2CharStringPen import T2CharStringPen | |
| from fontTools.t1Lib import T1Font | |
| def convert_font(input_path): | |
| t1 = T1Font(input_path) | |
| t1.parse() | |
| # Collect 'name' table strings | |
| font_name = t1.font.get('FontName', '') | |
| font_info = t1.font.get('FontInfo', {}) | |
| full_name = font_info.get('FullName', '') | |
| font_version = font_info.get('version', '') | |
| name_strings = dict( | |
| familyName=font_info.get('FamilyName', ''), | |
| styleName=font_info.get('Weight', ''), | |
| fullName=full_name, | |
| psName=font_name, | |
| version=f"Version {font_version}", | |
| uniqueFontIdentifier=f"{font_version};{font_name}", | |
| ) | |
| # Collect glyph names | |
| encoding = t1.font.get('Encoding') | |
| char_names = t1.font.get('CharStrings') | |
| gnames = [] | |
| for gname in encoding: | |
| if gname not in gnames: | |
| gnames.append(gname) | |
| for gname in char_names: | |
| if gname not in gnames: | |
| gnames.append(gname) | |
| # Infer 'cmap' table values from glyph names | |
| cmap = {} | |
| for gname in gnames: | |
| char = toUnicode(gname) | |
| if char: | |
| cmap[ord(char)] = gname | |
| # Collect charstrings and advance widths | |
| char_strings = {} | |
| adv_widths = {} | |
| gset = t1.getGlyphSet() | |
| npen = NullPen() | |
| for gname in gnames: | |
| glyph = gset[gname] | |
| glyph.draw(npen) | |
| gwidth = round(glyph.width) | |
| adv_widths[gname] = gwidth | |
| t2pen = T2CharStringPen(gwidth, None) | |
| glyph.draw(t2pen) | |
| cs = t2pen.getCharString() | |
| char_strings[gname] = cs | |
| fb = FontBuilder(1000, isTTF=False) | |
| fb.setupGlyphOrder(gnames) | |
| fb.setupCharacterMap(cmap) | |
| fb.setupCFF(font_name, {"FullName": full_name}, char_strings, {}) | |
| lsb = {} | |
| for gname, cs in char_strings.items(): | |
| xmin = 0 | |
| gbbox = cs.calcBounds(None) | |
| if gbbox: | |
| xmin, *_ = gbbox | |
| lsb[gname] = xmin | |
| metrics = { | |
| gname: (adv_width, lsb[gname]) | |
| for gname, adv_width in adv_widths.items() | |
| } | |
| fb.setupHorizontalMetrics(metrics) | |
| fb.setupHorizontalHeader(ascent=824, descent=-200) | |
| fb.setupNameTable(name_strings, mac=False) | |
| fb.setupOS2(sTypoAscender=824, usWinAscent=824, usWinDescent=200) | |
| fb.setupPost() | |
| save_path = makeOutputFileName(input_path, extension='.otf') | |
| fb.save(save_path) | |
| def main(args=None): | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument('input_path', help='path to Type 1 font file') | |
| opts = parser.parse_args(args) | |
| convert_font(opts.input_path) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment