def autohint(self, folder, hintedFolder): print("START") for ft in os.listdir(folder): print(folder, ft) ftpath = os.path.join(folder, ft) hintedFontPath = os.path.join(hintedFolder, ft) if not os.path.exists(hintedFolder): os.makedirs(hintedFolder) try: ttfautohint.ttfautohint(in_file=ftpath, out_file=hintedFontPath) except: print("Not possible to autohint the fonts")
def _autohint(self, otf, fmt): logger.info(f"Autohinting {self.name}.{fmt.value}") if fmt == Format.TTF: from io import BytesIO from ttfautohint import ttfautohint buf = BytesIO() otf.save(buf) otf.close() data = ttfautohint(in_buffer=buf.getvalue(), no_info=True) otf = TTFont(BytesIO(data)) elif fmt == Format.OTF: from tempfile import TemporaryDirectory from psautohint.__main__ import main as psautohint with TemporaryDirectory() as d: path = Path(d) / "tmp.otf" otf.save(path) with TemporaryLogLevel(logging.ERROR): psautohint([str(path)]) otf.close() otf = TTFont(path) return otf
def _autohint(self, otf, fmt): logger.info(f"Autohinting {self.name}.{fmt.value}") if fmt == Format.TTF: from io import BytesIO from ttfautohint import ttfautohint buf = BytesIO() otf.save(buf) otf.close() data = ttfautohint(in_buffer=buf.getvalue(), no_info=True) otf = TTFont(BytesIO(data)) # Set bit 3 on head.flags # https://font-bakery.readthedocs.io/en/latest/fontbakery/profiles/googlefonts.html#com.google.fonts/check/integer_ppem_if_hinted head = otf["head"] head.flags |= 1 << 3 elif fmt == Format.OTF: from tempfile import TemporaryDirectory from psautohint.__main__ import main as psautohint with TemporaryDirectory() as d: path = Path(d) / "tmp.otf" otf.save(path) with TemporaryLogLevel(logging.ERROR): psautohint([str(path)]) otf.close() otf = TTFont(path) return otf
def test_in_and_out_file_paths(self, tmpdir, unhinted): in_file = tmpdir / "unhinted.ttf" out_file = tmpdir / "hinted.ttf" with in_file.open("wb") as f: unhinted.save(f) n = ttfautohint(in_file=str(in_file), out_file=str(out_file)) assert n > 0
def main(args=None): options = ttfautohint.options.parse_args(args) # `parse_args` can return None instead of raising SystemExit on invalid # arguments, when `args` are passed. When it's called with args=None # (e.g. from a console script's `main()`), SystemExit is propagated. if options is None: return 2 logging.basicConfig( level=("DEBUG" if options["debug"] else "INFO" if options["verbose"] else "WARNING"), format="%(name)s: %(levelname)s: %(message)s", ) try: ttfautohint.ttfautohint(**options) except ttfautohint.TAError as e: log.error(e) return e.rv
def hinting_stats(font): """ Return file size differences for a hinted font compared to an dehinted version of same file """ from ttfautohint import ttfautohint, libttfautohint from io import BytesIO from fontTools.ttLib import TTFont from fontTools.subset import main as pyftsubset from fontbakery.profiles.shared_conditions import (is_ttf, is_cff, is_cff2) if is_ttf(TTFont(font)): original_buffer = BytesIO() TTFont(font).save(original_buffer) dehinted_buffer = ttfautohint(in_buffer=original_buffer.getvalue(), dehint=True) dehinted_size = len(dehinted_buffer) version = libttfautohint.version_string elif is_cff(TTFont(font)) or is_cff2(TTFont(font)): ext = os.path.splitext(font)[1] tmp = font.replace(ext, "-tmp-dehinted%s" % ext) args = [font, "--no-hinting", "--glyphs=*", "--ignore-missing-glyphs", "--no-notdef-glyph", "--no-recommended-glyphs", "--no-layout-closure", "--layout-features=*", "--no-desubroutinize", "--name-languages=*", "--glyph-names", "--no-prune-unicode-ranges", "--output-file=%s" % tmp] pyftsubset(args) dehinted_size = os.stat(tmp).st_size version = "" # TODO If there is a way, extract the psautohint version used? os.remove(tmp) else: return None return { "dehinted_size": dehinted_size, "hinted_size": os.stat(font).st_size, "version": version }
def ttfautohint_stats(font): from ttfautohint import ttfautohint, libttfautohint from io import BytesIO from fontTools.ttLib import TTFont from fontbakery.profiles.shared_conditions import is_ttf if not is_ttf(TTFont(font)): return None original_buffer = BytesIO() TTFont(font).save(original_buffer) dehinted_buffer = ttfautohint(in_buffer=original_buffer.getvalue(), dehint=True) return { "dehinted_size": len(dehinted_buffer), "hinted_size": os.stat(font).st_size, "version": libttfautohint.version_string }
def autohint(self, filename): from ttfautohint.options import parse_args as ttfautohint_parse_args from ttfautohint import ttfautohint ttfautohint(**ttfautohint_parse_args([filename, filename]))
def splitFont( outputDirectory=f"RecMono{fontOptions['Family Name']}".replace(" ", ""), newName="Rec Mono", ): # access font as TTFont object varfont = ttLib.TTFont(fontPath) fontFileName = os.path.basename(fontPath) outputSubDir = f"fonts/{outputDirectory}" for instance in fontOptions["Fonts"]: print( "\n--------------------------------------------------------------------------------------\n" + instance) instanceFont = instancer.instantiateVariableFont( varfont, { "wght": fontOptions["Fonts"][instance]["wght"], "CASL": fontOptions["Fonts"][instance]["CASL"], "MONO": fontOptions["Fonts"][instance]["MONO"], "slnt": fontOptions["Fonts"][instance]["slnt"], "CRSV": fontOptions["Fonts"][instance]["CRSV"], }, ) # UPDATE NAME ID 6, postscript name currentPsName = getFontNameID(instanceFont, 6) newPsName = (currentPsName\ .replace("Sans", "")\ .replace(oldName,newName.replace(" ", "") + fontOptions['Family Name'].replace(" ",""))\ .replace("LinearLight", instance.replace(" ", ""))) setFontNameID(instanceFont, 6, newPsName) # UPDATE NAME ID 4, full font name currentFullName = getFontNameID(instanceFont, 4) newFullName = (currentFullName\ .replace("Sans", "")\ .replace(oldName, newName + " " + fontOptions['Family Name'])\ .replace(" Linear Light", instance))\ .replace(" Regular", "") setFontNameID(instanceFont, 4, newFullName) # UPDATE NAME ID 3, unique font ID currentUniqueName = getFontNameID(instanceFont, 3) newUniqueName = (currentUniqueName.replace(currentPsName, newPsName)) setFontNameID(instanceFont, 3, newUniqueName) # ADD name 2, style linking name newStyleLinkingName = instance setFontNameID(instanceFont, 2, newStyleLinkingName) setFontNameID(instanceFont, 17, newStyleLinkingName) # UPDATE NAME ID 1, Font Family name currentFamName = getFontNameID(instanceFont, 1) newFamName = (newFullName.replace(f" {instance}", "")) setFontNameID(instanceFont, 1, newFamName) setFontNameID(instanceFont, 16, newFamName) newFileName = fontFileName\ .replace(oldName, (newName + fontOptions['Family Name']).replace(" ", ""))\ .replace("_VF_", "-" + instance.replace(" ", "") + "-") # make dir for new fonts pathlib.Path(outputSubDir).mkdir(parents=True, exist_ok=True) # ------------------------------------------------------- # save instance font outputPath = f"{outputSubDir}/{newFileName}" # save font instanceFont.save(outputPath) # ------------------------------------------------------- # Code font special stuff in post processing # freeze in rvrn & stylistic set features with pyftfeatfreeze pyftfeatfreeze.main([ f"--features=rvrn,{','.join(fontOptions['Features'])}", outputPath, outputPath ]) if fontOptions['Code Ligatures']: # swap dlig2calt to make code ligatures work in old code editor apps dlig2calt(outputPath, inplace=True) # if casual, merge with casual PL; if linear merge w/ Linear PL if fontOptions["Fonts"][instance]["CASL"] > 0.5: mergePowerlineFont(outputPath, "./font-data/NerdfontsPL-Regular Casual.ttf") else: mergePowerlineFont(outputPath, "./font-data/NerdfontsPL-Regular Linear.ttf") # TODO, maybe: make VF for powerline font, then instantiate specific CASL instance before merging # ------------------------------------------------------- # OpenType Table fixes monoFont = ttLib.TTFont(outputPath) # drop STAT table to allow RIBBI style naming & linking on Windows try: del monoFont["STAT"] except KeyError: print("Font has no STAT table.") # In the post table, isFixedPitched flag must be set in the code fonts monoFont['post'].isFixedPitch = 1 # In the OS/2 table Panose bProportion must be set to 9 monoFont["OS/2"].panose.bProportion = 9 # Also in the OS/2 table, xAvgCharWidth should be set to 600 rather than 612 (612 is an average of glyphs in the "Mono" files which include wide ligatures). monoFont["OS/2"].xAvgCharWidth = 600 # Code to fix fsSelection adapted from: # https://github.com/googlefonts/gftools/blob/a0b516d71f9e7988dfa45af2d0822ec3b6972be4/Lib/gftools/fix.py#L764 old_selection = fs_selection = monoFont["OS/2"].fsSelection # turn off all bits except for bit 7 (USE_TYPO_METRICS) fs_selection &= 1 << 7 if instance == "Italic": monoFont["head"].macStyle = 0b10 # In the OS/2 table Panose bProportion must be set to 11 for "oblique boxed" (this is partially a guess) monoFont["OS/2"].panose.bLetterForm = 11 # set Italic bit fs_selection |= 1 << 0 if instance == "Bold": monoFont['OS/2'].fsSelection = 0b100000 monoFont["head"].macStyle = 0b1 # set Bold bit fs_selection |= 1 << 5 if instance == "Bold Italic": monoFont['OS/2'].fsSelection = 0b100001 monoFont["head"].macStyle = 0b11 # set Italic & Bold bits fs_selection |= 1 << 0 fs_selection |= 1 << 5 monoFont["OS/2"].fsSelection = fs_selection monoFont.save(outputPath) # TTF autohint ttfautohint_options.update(in_file=outputPath, out_file=outputPath, hint_composites=True) ttfautohint.ttfautohint() print(f"\n→ Font saved to '{outputPath}'\n") print('Features are ', fontOptions['Features'])
def makeSFNT(root, outputPath, kind="otf"): """ Generates otf or ttf fonts using the Adobe FDK. This also autohints the generated fonts either with psautohint (cff) or ttfautohint (ttf) *root* is the root to find the source files in *outputPath* is the path to save the generated fonts to *kind* is either 'otf' or 'ttf'. """ if kind == "ttf": source = "ttf" else: source = "ufo" # make sure output dir contains no files files = getFiles(outputPath, kind) if len(files) != 0: for file in files: os.remove(file) print(f"🏗 Initial {kind.upper()} building") files = getFiles(root, source) outputFile = os.path.join(outputPath, "makeotf_output.txt") if os.path.exists(outputFile): os.remove(outputFile) printProgressBar(0, len(files), prefix=' ', suffix='Complete', length=50) for i, file in enumerate(files): # Set the makeotf parameters # -r is release mode # -nshw quiets the "glyph not hinted" warnings, as we # have yet to run the autohinter (we do that after fonts) # are built args = ["makeotf", "-f", file, "-o", outputPath, "-r", "-nshw"] run = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) with open(outputFile, "a") as f: f.write(run.stdout) printProgressBar(i + 1, len(files), prefix=' ', suffix='Complete', length=50) print(f"🏗 {kind.upper()} table fixing") files = getFiles(outputPath, kind) printProgressBar(0, len(files), prefix=' ', suffix='Complete', length=50) for i, file in enumerate(files): font = TTFont(file) nameTableTweak(font) makeDSIG(font) font.save(file) printProgressBar(i + 1, len(files), prefix=' ', suffix='Complete', length=50) print(f"🏗 {kind.upper()} autohinting") files = getFiles(outputPath, kind) outputFile = os.path.join(outputPath, "autohint_output.txt") if os.path.exists(outputFile): os.remove(outputFile) printProgressBar(0, len(files), prefix=' ', suffix='Complete', length=50) for i, file in enumerate(files): if kind is "otf": args = ["psautohint", file] run = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) with open(outputFile, "a") as f: f.write(run.stdout) elif kind is "ttf": ttfautohint_options.update(in_file=file, out_file=file, hint_composites=True) with open(outputFile, "a") as f: with redirect_stdout(f), redirect_stderr(f): ttfautohint.ttfautohint() printProgressBar(i + 1, len(files), prefix=' ', suffix='Complete', length=50)
def autohint_font(ttfont, **options): buf = BytesIO() ttfont.save(buf) data = ttfautohint(in_buffer=buf.getvalue(), **options) return TTFont(BytesIO(data))