1""" 2Hotfix a set of 'upstream' Noto CJK fonts to comply with fonts.google.com 3requirements. The following hotfixes are made: 4 5* Reconstruct fvar/STAT tables to remove any weights which do not 6 comply to the fonts.google.com expectations. 7* Rewrite the license description within the fonts to be the standard OFL 8 text used in other fonts. 9* Replace the copyright sign © in the name table with ASCII '(c)'. 10* Rename the variable fonts using the GF Family[axes].ttf format. 11* Run various other source fixes using gftools.fix. 12* Restore the correct hhea ascent = 1160 and descent = -288. 13""" 14 15from gftools.util.google_fonts import _KNOWN_WEIGHTS 16from gftools.fix import fix_font 17from axisregistry import build_name_table, build_fvar_instances, build_stat 18from fontTools.ttLib import TTFont 19from fontTools.ttLib.tables._f_v_a_r import NamedInstance 20from gftools.constants import ( 21 NAMEID_FONT_SUBFAMILY_NAME, 22 NAMEID_TYPOGRAPHIC_SUBFAMILY_NAME, 23) 24from fontbakery.profiles.googlefonts import com_google_fonts_check_font_names 25from fontbakery.profiles.googlefonts_conditions import expected_font_names 26import argparse 27import re 28import os 29 30HOTFIX_NUMBER = 2 31 32OFL_DESCRIPTION = "This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: https://scripts.sil.org/OFL" 33 34parser = argparse.ArgumentParser( 35 description="Hotfix Noto CJK VF TTFs to Google Fonts standard" 36) 37parser.add_argument("--output-dir", default=".", help="output directory") 38parser.add_argument("fonts", metavar="N", type=str, nargs="+", help="fonts to fix") 39 40args = parser.parse_args() 41 42if not os.path.exists(args.output_dir): 43 os.makedirs(args.output_dir) 44 45if "" in _KNOWN_WEIGHTS: 46 del _KNOWN_WEIGHTS[""] 47if "Hairline" in _KNOWN_WEIGHTS: 48 del _KNOWN_WEIGHTS["Hairline"] 49 50 51def scratch_font(ttfont): 52 # Trash all the names from STAT/fvar and start again. 53 deletable_names = set() 54 if "STAT" in ttfont: 55 stat = ttfont["STAT"].table 56 deletable_names.add(stat.ElidedFallbackNameID) 57 for av in stat.AxisValueArray.AxisValue: 58 deletable_names.add(av.ValueNameID) 59 for axis in stat.DesignAxisRecord.Axis: 60 deletable_names.add(axis.AxisNameID) 61 del ttfont["STAT"] 62 63 fvar = ttfont["fvar"] 64 for axis in fvar.axes: 65 deletable_names.remove(axis.axisNameID) 66 for instance in fvar.instances: 67 deletable_names.add(instance.subfamilyNameID) 68 deletable_names.add(instance.postscriptNameID) 69 for name_id in deletable_names: 70 ttfont["name"].removeNames(name_id) 71 fvar.instances = [] 72 73 # Drop 0x411 font names 74 ttfont["name"].names = [x for x in ttfont["name"].names if x.langID == 0x409] 75 76 77def fix_copyright(ttfont): 78 ttfont["name"].setName( 79 ttfont["name"].getName(0, 3, 1).toUnicode().replace("©", "(c)"), 0, 3, 1, 0x409 80 ) 81 ttfont["name"].setName(OFL_DESCRIPTION, 13, 3, 1, 0x409) 82 83 84def fix_version(ttfont): 85 ttfont["name"].setName( 86 ttfont["name"] 87 .getName(5, 3, 1) 88 .toUnicode() 89 .replace(";hotconv", f"-H{HOTFIX_NUMBER};hotconv"), 90 5, 91 3, 92 1, 93 0x409, 94 ) 95 96 97def fix_hhea_ascent(ttfont, sans=True): 98 if sans: 99 ttfont["hhea"].ascender = 1160 100 ttfont["hhea"].descent = -288 101 else: 102 ttfont["hhea"].ascender = 1151 103 ttfont["hhea"].descent = -286 104 105 106for font in args.fonts: 107 ttfont = TTFont(font) 108 scratch_font(ttfont) 109 fix_copyright(ttfont) 110 fix_version(ttfont) 111 fix_hhea_ascent(ttfont, "Sans" in font) 112 fix_font(ttfont, include_source_fixes=True) 113 build_name_table(ttfont, siblings=[]) 114 build_fvar_instances(ttfont) 115 build_stat(ttfont, []) 116 if "DSIG" in ttfont: 117 del ttfont["DSIG"] 118 119 axis_tags = sorted([ax.axisTag for ax in ttfont["fvar"].axes]) 120 axis_tags = ",".join(axis_tags) 121 newname = os.path.basename(font).replace("-VF.ttf", "[%s].ttf" % axis_tags) 122 newname = re.sub("CJK(..)", lambda x: x[0][-2:].upper(), newname) 123 ttfont.save(os.path.join(args.output_dir, newname)) 124