def _build_ttf_entries(fpath): """ Given the path to a TTF/TTC file, return a list of :class:`FontEntry` instances. """ entries = [] ext = os.path.splitext(fpath)[-1] try: with open(fpath, "rb") as fp: if ext.lower() == ".ttc": collection = TTCollection(fp) try: for idx, font in enumerate(collection.fonts): entries.append(_ttf_font_property(fpath, font, idx)) except Exception: logger.error(_FONT_ENTRY_ERR_MSG, fpath, exc_info=True) else: font = TTFont(fp) try: entries.append(_ttf_font_property(fpath, font)) except Exception: logger.error(_FONT_ENTRY_ERR_MSG, fpath, exc_info=True) except (RuntimeError, TTLibError): logger.error(f"Could not open font file {fpath}", exc_info=True) except UnicodeError: logger.error(f"Cannot handle unicode file: {fpath}", exc_info=True) return entries
def run(path, options): try: font = TTFont(path, fontNumber=options.face_index) extension = '.ttf' except TTLibError: font = TTCollection(path) extension = '.ttc' if options.output and not os.path.isdir(options.output): output = options.output else: output = makeOutputFileName(path, outputDir=options.output, extension=extension, overWrite=options.overwrite) try: otf_to_ttf(font, post_format=options.post_format, max_err=options.max_error, reverse_direction=options.reverse_direction) except TTLibError as warn: log.warning(f'"{path}" cannot be converted since it is {warn}.') else: font.save(output)
def generate_ttx_dump(font_path, tables=None): try: font = TTFont(font_path) except TTLibError: font = TTCollection(font_path) with font: temp_path = get_temp_file_path() font.saveXML(temp_path, tables=tables) return temp_path
def fix(file): dirname = args.dirname if os.path.exists(args.dirname) \ else os.path.join(os.path.dirname(file), args.dirname) basename = os.path.basename(file) if not os.path.exists(dirname): os.mkdir(dirname) logger.name = basename with open(file, 'rb') as f: header = f.read(4) font = TTCollection(file, **options) if header == b'ttcf' \ else TTFont(file, **options) repair(font) font.save(os.path.join(dirname, basename))
def unpack_ttc(filepath: Path) -> None: try: collection = TTCollection(str(filepath.resolve())) except: print(f"Failed to parse {filepath}, ignore") return for font in collection.fonts: ttf_path = filepath.parent / f"{get_font_name(font)}.ttf" font.save(ttf_path) print(f"{filepath} -> {ttf_path}") filepath.unlink()
def get_ttc_list(filename): #clear font list ttc_names = [] #lazy=True: https://github.com/fonttools/fonttools/issues/2019 ttc = TTCollection(filename, lazy=True) for font in ttc: # single font name in getName(nameID, platformID, platEncID, langID=None), 0x409 make sure all font in English name ttf_name = font["name"].getName(4, 3, 1, 0x409) # add the font name itself instead of the XML representation ttc_names.append(str(ttf_name)) #return array of names return ttc_names
def run(path, options): with open(path, 'rb') as f: header = f.read(4) if header == b'ttcf' and options.face_index == -1: extension = '.ttc' font = TTCollection(path) else: extension = '.ttf' font = TTFont(path, fontNumber=options.face_index) if options.output and not os.path.isdir(options.output): output = options.output else: output = makeOutputFileName(path, outputDir=options.output, extension=extension, overWrite=options.overwrite) otf_to_ttf(font, post_format=options.post_format, max_err=options.max_error, reverse_direction=options.reverse_direction) font.save(output)
def find_glyph(path, glyph): if path.suffix == '.ttc': fonts = TTCollection(path) else: fonts = [TTFont(path)] for n, font in enumerate(fonts): def _inspect(): for table in font['cmap'].tables: if glyph in table.cmap: return (n, (table.platformID, table.platEncID), table.cmap[glyph]) result = _inspect() if result is not None: yield result
def load_font(font_path): """ Read ttc, ttf, otf font file, return a TTFont object """ # ttc is collection of ttf if font_path.endswith('ttc'): ttc = TTCollection(font_path) # assume all ttfs in ttc file have same supported chars return ttc.fonts[0] if font_path.endswith('ttf') or font_path.endswith('TTF') or font_path.endswith('otf'): ttf = TTFont(font_path, 0, allowVID=0, ignoreDecompileErrors=True, fontNumber=-1) return ttf
def check_font(self): font_new = [] for fonts in self.font_list: fontsname = os.path.join(self.font_path, fonts) #print '------------------------------------------' #print fontsname if (fontsname.endswith('ttc')): ttc = TTCollection(fontsname) fonts_load = ttc.fonts[0] elif (fontsname.endswith('ttf') or fontsname.endswith('TTF') or fontsname.endswith('otf')): ttf = TTFont(fontsname, 0, allowVID=0, ignoreDecompileErrors=True, fontNumber=1) fonts_load = ttf else: #print 'the type of fonts is not supported !' #return None continue chars = chain.from_iterable( [y + (Unicode[y[0]], ) for y in x.cmap.items()] for x in fonts_load['cmap'].tables) #print fontsname,' support chars: ',len(chars) chars_int = [] for c in chars: chars_int.append(c[0]) unsupport_chars = [] support_chars = [] for c in self.dict: if ord(c) not in chars_int: unsupport_chars.append(c) #print c else: support_chars.append(c) fonts_load.close() if (len(unsupport_chars) != 0): print fontsname, ' not support all the chars in dic !' else: #print fontsname,' supports all the chars in dic !' font_new.append(fonts) self.font_list = font_new for fonts in self.font_list: print fonts, ' is used .'
def _load_ttfont(self, font_path: str) -> TTFont: """ Read ttc, ttf, otf font file, return a TTFont object """ # ttc is collection of ttf if font_path.endswith("ttc"): ttc = TTCollection(font_path) # assume all ttfs in ttc file have same supported chars return ttc.fonts[0] if (font_path.endswith("ttf") or font_path.endswith("TTF") or font_path.endswith("otf")): ttf = TTFont(font_path, 0, allowVID=0, ignoreDecompileErrors=True, fontNumber=-1) return ttf
def test_lazy_open_path(lazy): ttc_path = TTX_DATA_DIR / "TestTTC.ttc" with TTCollection(ttc_path, lazy=lazy) as collection: assert len(collection) == 2 assert collection[0]["maxp"].numGlyphs == 6 assert collection[1]["maxp"].numGlyphs == 6
all Unicode characters covered by all fonts, e.g. list_ttf_chars.py $ANDROID_HOME/platforms/android-XY/data/fonts/*.ttf Requires FontTools: https://pypi.python.org/pypi/FontTools ''' import sys from fontTools.ttLib import TTFont, TTCollection chars = {} for f in sys.argv[1:]: try: if f.endswith('.ttc'): fonts = TTCollection(f) else: fonts = [TTFont(f)] for font in fonts: bestTable = font.getBestCmap() if bestTable is not None: chars.update(bestTable) else: print('Warning: Font does not have a Unicode cmap:', f, file=sys.stderr) for table in font['cmap'].tables: chars.update(table.cmap) except: print('Could not process arg:', f, file=sys.stderr) raise
import sys from fontTools.ttLib import TTFont, TTCollection import common if __name__ == "__main__": param = common.ParamFromArgument(sys.argv[1]) ttc = TTCollection(param['font'], recalcBBoxes=False) font = ttc[0] del font['vhea'] del font['vmtx'] font_head = font['head'] ref = TTFont(param['reference']) ref_head = ref['head'] scale = font_head.unitsPerEm / ref_head.unitsPerEm font_head.yMin = round(ref_head.yMin * scale) font_head.yMax = round(ref_head.yMax * scale) ttc.save(param['out'])
parser.add_argument( '--delete', action="store_true", default=False, help= 'whether or not to delete font which not full support the chars_file') args, _ = parser.parse_known_args() charset = load_chars(args.chars_file) font_paths = glob.glob(args.font_dir + '/*.*') fonts = {} for p in font_paths: if p.endswith('ttc'): ttc = TTCollection(p) for f in ttc.fonts: fonts["%s_1" % p] = f if p.endswith('ttf') or p.endswith('TTF') or p.endswith('otf'): ttf = TTFont(p, 0, allowVID=0, ignoreDecompileErrors=True, fontNumber=-1) fonts[p] = ttf useful_fonts = [] for k, v in fonts.items(): print("checking font %s" % k)
ppemHw = int(i) + 1 widths = [] for name in font.getGlyphOrder(): width = hmtx_[name][0] widths.append(math.ceil(width / widthHw) * ppemHw) record = bytes([ ppem, max(widths) ] + widths) + pad deviceRecords.append(record) hdmxHeader = sstruct.pack(hdmxHeaderFormat, SimpleNamespace(version = 0, numRecords = len(deviceRecords), recordSize = recordSize)) hdmx_ = DefaultTable('hdmx') hdmx_.data = hdmxHeader + b''.join(deviceRecords) font['hdmx'] = hdmx_ if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('-i', '--input', required = True) parser.add_argument('-o', '--output', required = True) args = parser.parse_args() try: ttc = TTCollection(args.input, recalcBBoxes = False) for font in ttc: BuildRawHdmx(font) ttc.save(args.output) except TTLibError: font = TTFont(args.input, recalcBBoxes = False) BuildRawHdmx(font) font.save(args.output)
def analyzeFonts(fontList, makeDict=False): """ Set makeDict to True to create a dictionary of glyphs supported by each font suitable for JSON export. """ errorList = [] fontDict = {} ## Always update these lines with information on which OS the font files came from ## withOS = OS.objects.get(slug="mac-11") withOSLabel = "Big Sur" for idx, fontFile in enumerate(fontList): isCollection = False if str(fontFile)[-3:] == "ttc": isCollection = True try: if isCollection: fontCollection = TTCollection(fontFile) else: fontCollection = [TTFont(fontFile)] except Exception as e: print("Failed to read", fontFile) print(e) continue fileNameStem = fontFile.stem if makeDict: fontDict[fileNameStem] = [] for font in fontCollection: fontName = font["name"].names[1].toStr() try: fontVersion = font["name"].names[5].toStr( )[:64] # 64 character limit for model charFields except IndexError: fontVersion = "" if "LastResort" in fontName: continue if "©" in fontName or "Copyright" in fontName: fontName = fileNameStem.replace("-", " ") fontName = re.sub(r'([a-z])([A-Z])', r'\1 \2', fontName) try: fontStyle = font["name"].names[2].toStr() except IndexError: fontStyle = "" print("filename: ", fileNameStem) slug = slugify(fontName[:64 - len(fontStyle) - 1] + " " + fontStyle[:64])[:64] print("slug: ", slug) slugDup = False fontDbObj = None print("fontname: ", fontName) print("fontstyle: ", fontStyle) print("fontversion: ", fontVersion) """NOTE! In most recent update, added check for version number. This will end up creating duplicate Font objects in the database since many of the original imports did not include version numbers.""" fontDbObjs = Font.objects.filter(name=fontName.replace( "109uh", "")[:64], style=fontStyle[:64], fileName=fileNameStem, version=fontVersion) if makeDict: fontObj = { "name": fontName.replace("109uh", "")[:64], "style": fontStyle[:64], "fileType": str(fontFile)[-3:], "slug": slug, "version": fontVersion, "glyphs": set() } fontDict[fileNameStem] += [fontObj] if not fontDbObjs.exists() and fontName[0] == ".": fontName = fontName[1:] fontDbObjs = Font.objects.filter(name=fontName.replace( "109uh", "")[:64], style=fontStyle[:64], fileName=fileNameStem, version=fontVersion) if not fontDbObjs.exists(): fontDbObjs = Font.objects.filter(fileName=fileNameStem, version=fontVersion) if fontDbObjs.exists(): fontDbObj = fontDbObjs[0] print(f"Adding to {withOSLabel} font list") fontDbObj.incWithOS.add(withOS) fontDbObj.save() print("Skipping further analysis", fontName, " ", fontStyle) print() continue else: slugDup = Font.objects.filter(slug=slug).exists() if slugDup: slug = slug[:50] + "-" + str(int(random.random() * 10000)) try: fontDbObj = Font(name=fontName.replace("109uh", "")[:64], style=fontStyle[:64], fileName=fileNameStem, fileType=str(fontFile)[-3:], slug=slug, version=fontVersion) print(f"Created font: {fontName} {fontStyle}") print(fontDbObj) fontDbObj.save() except django.db.utils.IntegrityError as e: print("ERROR: ", e) continue for cmap in font['cmap'].tables: if cmap.isUnicode(): for idx, uniChar in enumerate(cmap.cmap): glyphObj, result = Glyph.objects.get_or_create( codePoint=uniChar, defaults={ "officialName": "Private or unassigned", "slug": "%04X" % uniChar, "codePlane": uniChar // 65536 }) if result: print("Created new Glyph object for codePoint ", hex(uniChar).upper()) # except Glyph.DoesNotExist: # print(f"WARNING: In {dictKey}/{fontName}, no glyph found for ", hex(uniChar)) # errorList += [hex(uniChar)] # continue fontDbObj.glyphs.add(glyphObj) if idx % 250 == 0: print('.', end='') if makeDict: fontDict[fileNameStem][-1]["glyphs"].add( f"{hex(uniChar).upper()[2:]}") print(f"Added {fontDbObj.glyphs.count()} glyphs to font") fontDbObj.save() print(f"Adding to {withOSLabel} font list...") fontDbObj.incWithOS.add(withOS) fontDbObj.save() print("Finished ", fontDbObj) print("=====") print() #errorList = list(set(errorList)) #print("Error list first 10 items are ", errorList[:10], f"out of {len(errorList)} total") return fontDict
def createFontList(fontfiles, fontext='ttf'): """ A function to create a font lookup list. The default is to create a list of TrueType fonts. An AFM font list can optionally be created. """ # FIXME: This function is particularly difficult to debug fontlist = [] # Add fonts from list of known font files. seen = {} for fpath in fontfiles: logger.debug("createFontDict %s", fpath) fname = os.path.split(fpath)[1] if fname in seen: continue else: seen[fname] = 1 if fontext == 'afm': try: fh = open(fpath, 'r') except Exception: logger.error("Could not open font file %s", fpath, exc_info=True) continue try: try: font = afm.AFM(fh) finally: fh.close() except RuntimeError: logger.error("Could not parse font file %s", fpath, exc_info=True) continue try: prop = afmFontProperty(fpath, font) except Exception: logger.error("Could not covert font to FontEntry for file %s", fpath, exc_info=True) continue else: _, ext = os.path.splitext(fpath) try: if ext.lower() == ".ttc": collection = TTCollection(six.text_type(fpath)) try: props = [] for font in collection.fonts: props.append(ttfFontProperty(fpath, font)) fontlist.extend(props) continue except Exception: logger.error( "Could not covert font to FontEntry for file %s", fpath, exc_info=True) continue else: font = TTFont(six.text_type(fpath)) except (RuntimeError, TTLibError): logger.error("Could not open font file %s", fpath, exc_info=True) continue except UnicodeError: logger.error("Cannot handle unicode file: %s", fpath, exc_info=True) continue try: prop = ttfFontProperty(fpath, font) except Exception: logger.error("Could not covert font to FontEntry for file %s", fpath, exc_info=True) continue fontlist.append(prop) return fontlist
def test_lazy_open_file(lazy): with (TTX_DATA_DIR / "TestTTC.ttc").open("rb") as file: collection = TTCollection(file, lazy=lazy) assert len(collection) == 2 assert collection[0]["maxp"].numGlyphs == 6 assert collection[1]["maxp"].numGlyphs == 6