def run(self, filename, pipedata): if 'optimize' in pipedata and not pipedata['optimize']: return self.bakery.logging_raw('### Optimize TTF {}'.format(filename)) # copied from https://code.google.com/p/noto/source/browse/nototools/subset.py from fontTools.subset import Options, Subsetter, load_font, save_font options = Options() options.layout_features = "*" options.name_IDs = "*" options.hinting = True options.notdef_outline = True font = load_font(op.join(self.builddir, filename), options) subsetter = Subsetter(options=options) subsetter.populate(glyphs=font.getGlyphOrder()) subsetter.subset(font) save_font(font, op.join(self.builddir, filename + '.opt'), options) newsize = op.getsize(op.join(self.builddir, filename + '.opt')) origsize = op.getsize(op.join(self.builddir, filename)) # compare filesizes TODO print analysis of this :) comment = "# look at the size savings of that subset process" self.bakery.logging_cmd("ls -l '%s'* %s" % (filename, comment)) statusmessage = "{0}.opt: {1} bytes\n{0}: {2} bytes\n" self.bakery.logging_raw(statusmessage.format(filename, newsize, origsize)) # move ttx files to src shutil.move(op.join(self.builddir, filename + '.opt'), op.join(self.builddir, filename), log=self.bakery.logger)
def execute_pyftsubset(self, pipedata, subsetname, name, glyphs="", args=""): from fontTools.subset import Subsetter, Options, load_font, save_font target_file = '{0}.{1}'.format(op.join(self.builddir, name)[:-4], subsetname) options = Options() cmd_options = '' if pipedata.get('pyftsubset'): cmd_options = pipedata['pyftsubset'] options.parse_opts(pipedata['pyftsubset'].split()) if pipedata.get('pyftsubset.%s' % subsetname): cmd_options = pipedata['pyftsubset.%s' % subsetname] options.parse_opts(pipedata['pyftsubset.%s' % subsetname].split()) font = load_font(op.join(self.builddir, name), options) unicodes = re_range([int(g.replace('U+', ''), 16) for g in glyphs.split()]) self.bakery.logging_cmd('pyftsubset --unicodes="{0}" {2} {1}'.format(unicodes, name, cmd_options)) subsetter = Subsetter(options=options) subsetter.populate(unicodes=[int(g.replace('U+', ''), 16) for g in glyphs.split()]) subsetter.subset(font) self.bakery.logging_cmd('mv {0}.subset {1}'.format(name, target_file)) save_font(font, target_file, options)
def main(args): """Subset a font (useful for making small test fonts). Args: args: list, arguments the user typed. """ parser = argparse.ArgumentParser() parser.add_argument('fontfile', help='Input font file') parser.add_argument('--subset_postfix', default='', help='Postfix to the subset extension') parser.add_argument('--text', default='', help='Text to include in the subset') parser.add_argument('--unicodes', default='', help='Comma separated list of Unicode codepoints (hex) ' 'to include in the subset; eg, "e7,0xe8,U+00e9"') parser.add_argument('--glyphs', default='', help='Comma separated list of glyph IDs (decimal) to ' 'include in the subset; eg, "1,27"') parser.add_argument('--hinting', default=False, action='store_true', help='Enable hinting if specified, no hinting if not ' 'present') cmd_args = parser.parse_args(args) options = Options() # Definitely want the .notdef glyph and outlines. options.notdef_glyph = True options.notdef_outline = True # Get the item. to keep in the subset. text = cmd_args.text unicodes_str = cmd_args.unicodes.lower().replace('0x', '').replace('u+', '') # TODO(bstell) replace this whole files by using the new subset.py code unicodes_input = [c for c in unicodes_str.split(',') if c] unicodes = [] for c in unicodes_input: if '-' in c: uni_range = c.split('-') uni_range_expanded = range(int(uni_range[0], 16), int(uni_range[1], 16) + 1) unicodes.extend(uni_range_expanded) else: unicodes.append(int(c, 16)) #unicodes = [int(c, 16) for c in unicodes_input_expanded] glyphs = [int(c) for c in cmd_args.glyphs.split(',') if c] fontfile = cmd_args.fontfile options.hinting = cmd_args.hinting # False => no hinting options.hinting = True # hint stripping for CFF is currently broken dirname = os.path.dirname(fontfile) basename = os.path.basename(fontfile) filename, extension = os.path.splitext(basename) subset_postfix = cmd_args.subset_postfix output_file = dirname + '/' + filename + '_subset' + subset_postfix + extension print "output_file =", output_file font = load_font(fontfile, options, lazy=False) subsetter = Subsetter(options) subsetter.populate(text=text, unicodes=unicodes, glyphs=glyphs) subsetter.subset(font) save_font(font, output_file, options)
def clean(self): """Remove glyphs that should have outlines but do not. """ rcmap = reverse_cmap(self.font) names = set(rcmap.keys()) names.difference_update(self._invalid_glyphs(names, rcmap)) subsetter = Subsetter(options=self.options) subsetter.populate(glyphs=names) subsetter.subset(self.font)
def subset(fontfile, outfile_basename, glyphs): options = Options() # Fonttools has this feature that if you enable 'dlig', it will also give # you glyphs that you did not ask for, but if you do not enable 'dlig', # then discretionary ligatures do not render properly. # See https://github.com/behdad/fonttools/issues/43. # As a workaround, only enable 'dlig' if there are glyphs for discretionary # ligatures. # TODO: This should be fixed, consider upgrading. # https://github.com/fonttools/fonttools/commit/022536212be4cf022a2cb9a286fec8be1931d19b. dligs = set(glyphs).intersection(['c_b', 'c_h', 'c_k', 'c_p', 'ct', 'g_i', 'q_u', 's_b', 's_h', 's_k', 's_p', 'st']) if len(dligs) > 0: options.layout_features.append('dlig') else: # Due to a bug in Fonttools, options are actually global, so the # remnants of the previous instance are visible here. # See https://github.com/behdad/fonttools/issues/413. if 'dlig' in options.layout_features: options.layout_features.remove('dlig') # Same for small caps, it needs to be enabled explicitly. Luckily, only the # glyphs in the list get included, no extra ones. if any(g.endswith('.smcp') for g in glyphs): options.layout_features.append('smcp') options.layout_features.append('c2sc') else: if 'smcp' in options.layout_features: options.layout_features.remove('smcp') if 'c2sc' in options.layout_features: options.layout_features.remove('c2sc') # Fonts that went through the FontForge roundtrip will have subroutinized # programs in the CFF table. This presumably reduces file size for full # fonts, but on subsetted fonts it hurts file size and compressability, so # desubroutinize. options.desubroutinize = True font = load_font(fontfile, options) subsetter = Subsetter(options = options) subsetter.populate(glyphs = glyphs) subsetter.subset(font) prune_cmaps(font) options.flavor = "woff" save_font(font, outfile_basename + ".woff", options) options.flavor = "woff2" save_font(font, outfile_basename + ".woff2", options) font.close()
def closure(self): """Takes closure of glyphs specified by glyph_names and glyph_codes. """ options = Options() options.notdef_glyph = False subsetter = Subsetter(options=options) subsetter.populate(glyphs=self.glyph_names, unicodes=self.glyph_codes) subsetter._closure_glyphs(self.font) gids = sorted(self.glyph_name_to_id[gg] for gg in subsetter.glyphs_all if gg != '.notdef') return gids
def subset(fontfile, outfile_basename, glyphs): options = Options() # Fonttools has this "feature" that if you enable 'dlig', it will also give # you glyphs that you did not ask for, but if you do not enable 'dlig', # then discretionary ligatures do not render properly. # See https://github.com/behdad/fonttools/issues/43. # As a workaround, only enable 'dlig' if there are glyphs for discretionary # ligatures. dligs = set(glyphs).intersection(["c_b", "c_h", "c_k", "c_p", "ct", "g_i", "q_u", "s_b", "s_h", "s_k", "s_p", "st"]) if len(dligs) > 0: options.layout_features.append("dlig") else: # Due to a bug in Fonttools, options are actually global, so the # remnants of the previous instance are visible here. # See https://github.com/behdad/fonttools/issues/413. if "dlig" in options.layout_features: options.layout_features.remove("dlig") # Same for small caps, it needs to be enabled explicitly. Luckily, only the # glyphs in the list get included, no extra ones. if any(g.endswith(".smcp") for g in glyphs): options.layout_features.append("smcp") options.layout_features.append("c2sc") else: if "smcp" in options.layout_features: options.layout_features.remove("smcp") if "c2sc" in options.layout_features: options.layout_features.remove("c2sc") # Fonts that went through the FontForge roundtrip will have subroutinized # programs in the CFF table. This presumably reduces file size for full # fonts, but on subsetted fonts it hurts file size and compressability, so # desubroutinize. options.desubroutinize = True font = load_font(fontfile, options) subsetter = Subsetter(options=options) subsetter.populate(glyphs=glyphs) subsetter.subset(font) prune_cmaps(font) options.flavor = "woff" save_font(font, outfile_basename + ".woff", options) options.flavor = "woff2" save_font(font, outfile_basename + ".woff2", options) font.close()
def run(self, pipedata): if 'optimize' in pipedata and not pipedata['optimize']: return from bakery_cli.utils import ProcessedFile filename = ProcessedFile() self.bakery.logging_raw('### Optimize TTF {}'.format(filename)) # copied from https://code.google.com/p/noto/source/browse/nototools/subset.py from fontTools.subset import Options, Subsetter, load_font, save_font options = Options() options.layout_features = ["*"] options.name_IDs = ["*"] options.hinting = True options.legacy_kern = True options.notdef_outline = True options.no_subset_tables += ['DSIG'] options.drop_tables = list(set(options._drop_tables_default) - set(['DSIG'])) cmd_options = ('--glyphs=*' ' --layout-features=*' ' --name-IDs=*' ' --hinting' ' --legacy-kern --notdef-outline' ' --no-subset-tables+=DSIG' ' --drop-tables-=DSIG') font = load_font(op.join(self.builddir, filename), options) cmdline = 'pyftsubset {1} {0}'.format(cmd_options, op.join(self.builddir, filename)) self.bakery.logging_cmd(cmdline) subsetter = Subsetter(options=options) subsetter.populate(glyphs=font.getGlyphOrder()) subsetter.subset(font) save_font(font, op.join(self.builddir, filename + '.fix'), options) # compare filesizes TODO print analysis of this :) comment = "# look at the size savings of that subset process" self.bakery.logging_cmd(comment) run(u"ls -la {0} {0}.fix | awk '{{ print $5 \"\t\" $9 }}'".format(unicode(op.join(self.builddir, filename)))) comment = "# copy back optimized ttf to original filename" self.bakery.logging_cmd(comment) shutil.move(op.join(self.builddir, filename + '.fix'), op.join(self.builddir, filename))
def clean(self, verbose): """Remove glyphs that should have outlines but do not. """ if verbose: print('reverse_cmap') rcmap = reverse_cmap(self.font) names = set(rcmap.keys()) if verbose: print('names.difference_update') names.difference_update(self._invalid_glyphs(names, rcmap)) subsetter = Subsetter(options=self.options) if verbose: print('populate') subsetter.populate(glyphs=names) if verbose: print('subset') subsetter.subset(self.font)
def main(args): """Subset a font (useful for making small test fonts). Args: args: list, arguments the user typed. """ parser = argparse.ArgumentParser() parser.add_argument('fontfile', help='Input font file') parser.add_argument('--text', default='', help='Text to include in the subset') parser.add_argument('--unicodes', default='', help='Comma separated list of Unicode codepoints (hex) ' 'to include in the subset; eg, "e7,0xe8,U+00e9"') parser.add_argument('--glyphs', default='', help='Comma separated list of glyph IDs (decimal) to ' 'include in the subset; eg, "1,27"') parser.add_argument('--hinting', default=False, action='store_true', help='Enable hinting if specified, no hinting if not ' 'present') cmd_args = parser.parse_args(args) options = Options() # Definitely want the .notdef glyph and outlines. options.notdef_glyph = True options.notdef_outline = True # Get the item. to keep in the subset. text = cmd_args.text unicodes_str = cmd_args.unicodes.lower().replace('0x', '').replace('u+', '') unicodes = [int(c, 16) for c in unicodes_str.split(',') if c] glyphs = [int(c) for c in cmd_args.glyphs.split(',') if c] fontfile = cmd_args.fontfile options.hinting = cmd_args.hinting # False => no hinting dirname = os.path.dirname(fontfile) basename = os.path.basename(fontfile) filename, extension = os.path.splitext(basename) output_file = dirname + '/' + filename + '_subset' + extension font = load_font(fontfile, options, lazy=False) subsetter = Subsetter(options) subsetter.populate(text=text, unicodes=unicodes, glyphs=glyphs) subsetter.subset(font) save_font(font, output_file, options)
def shrink(self, freezeFeatures = [], removeFeatures = [], glyphs = [], replaceNames = '', suffix = ''): # Freeze features from pyftfeatfreeze.pyftfeatfreeze import RemapByOTL class FreezeOptions(object): pass options = FreezeOptions() options.inpath = '' options.outpath = '' options.features = ','.join(freezeFeatures) # comma-separated list of OpenType feature tags, e.g. 'smcp,c2sc,onum' options.script = 'latn' # OpenType script tag, e.g. 'cyrl' (default: '%(default)s') options.lang = None # OpenType language tag, e.g. 'SRB ' (optional) options.zapnames = False # zap glyphnames from the font ('post' table version 3, .ttf only) options.rename = True if suffix else False # add a suffix to the font menu names (by default, the suffix will be constructed from the OpenType feature tags) options.usesuffix = suffix # use a custom suffix when -S is provided options.replacenames = replaceNames # search for strings in the font naming tables and replace them, format is 'search1/replace1,search2/replace2,...' options.info = True # update font version string options.report = False # report languages, scripts and features in font options.names = False # output names of remapped glyphs during processing options.verbose = True remapByOTL = RemapByOTL(options) remapByOTL.ttx = self.TTFont remapByOTL.remapByOTL() remapByOTL.renameFont() # Subset from fontTools.subset import Subsetter, Options # features = list(set(self.features()) - (set(freezeFeatures) & set(removeFeatures))) features = list(set(self.features()) - set(removeFeatures)) # print 'target features', features options = Options(layout_features = features, name_IDs = '*', glyph_names = True, name_legacy = True, name_languages = '*') subsetter = Subsetter(options = options) # populate with unicodes unicodes = [] for t in self.TTFont['cmap'].tables: if t.isUnicode(): unicodes.extend(list(t.cmap.keys())) subsetter.populate(unicodes = unicodes) subsetter.subset(self.TTFont)
def subsetFont(base64, subset): # tmp file names tmpInputFontName = tmpFileName(".ttf") tmpOutputFontName = tmpFileName(".woff") # remove data header from base64 fontbase64 = base64.split(",")[1] with open(tmpInputFontName, "wb") as f: fontinput = f.write(fontbase64.decode('base64')) f.close() # open the font with fontTools font = TTFont(tmpInputFontName) options = Options() options.desubroutinize = True # export the font as woff for web use options.with_zopfli = True options.flavor = "woff" subsetter = Subsetter(options=options) subsetter.populate(text=subset) subsetter.subset(font) save_font(font, tmpOutputFontName, options) subsettedFont = open(tmpOutputFontName, "rb").read().encode("base64") os.unlink(tmpOutputFontName) os.unlink(tmpInputFontName) return {'subset': subsettedFont}
def generate_subset(unicode_range, flavor, font_file, output_dir): """ Generate font subset. You can do the same with the following command. $ pyftsubset YOUR_FONT.otf \ --unicodes=U+943a-943b \ --layout-features='*' \ --flavor=woff \ --name-IDs='*' \ --output-file=style/font-subsets/YOUR_FONT-subset-1.woff """ args = ["--layout-features='*'", "--flavor=%s" % flavor] options = Options() options.parse_opts(args) subsetter = Subsetter(options) font = load_font(font_file, options) subsetter.populate(unicodes=parse_unicodes(unicode_range)) subsetter.subset(font) font_path = Path(font_file) name = font_path.stem unicode_range_hash = _get_unicode_range_hash(unicode_range) outfile = "%s/%s/%s-subset-%s.%s" % ( output_dir, FONT_DIR, name, unicode_range_hash, flavor, ) save_font(font, outfile, options) font.close()
def generateWoff2(self, verbosity=0): woff2_list = [] os.makedirs(self.assetdir, exist_ok=True) for subname, (subrange, unicodes) in self.urdict.items(): if verbosity == 2: print("Processing", subname) subs = Subsetter() font = TTFont(self.fontfile) subs.populate(unicodes=unicodes) subs.subset(font) cmap = font.getBestCmap() glyphcount = len(font.getGlyphOrder()) - 1 if cmap: outfile = os.path.join(self.assetdir, self.basename + "." + subname + ".woff2") font.flavor = 'woff2' font.save(outfile) woff2_list.append((outfile, subrange)) if verbosity == 1: print("Generated", outfile) elif verbosity == 2: print(" Generated", outfile) print(" Found", glyphcount, "glyphs for", len(cmap), "out of", len(unicodes), "unicodes") else: if verbosity == 2: print(" Found no glyphs for any of", len(unicodes), "unicodes") font.close() return woff2_list
def execute_pyftsubset(self, pipedata, subsetname, name, glyphs="", args=""): from fontTools.subset import Subsetter, Options, load_font, save_font target_file = '{0}.{1}'.format( op.join(self.builddir, name)[:-4], subsetname) options = Options() cmd_options = '' if pipedata.get('pyftsubset'): cmd_options = pipedata['pyftsubset'] options.parse_opts(pipedata['pyftsubset'].split()) if pipedata.get('pyftsubset.%s' % subsetname): cmd_options = pipedata['pyftsubset.%s' % subsetname] options.parse_opts(pipedata['pyftsubset.%s' % subsetname].split()) font = load_font(op.join(self.builddir, name), options) unicodes = re_range( [int(g.replace('U+', ''), 16) for g in glyphs.split()]) self.bakery.logging_cmd('pyftsubset --unicodes="{0}" {2} {1}'.format( unicodes, name, cmd_options)) subsetter = Subsetter(options=options) subsetter.populate( unicodes=[int(g.replace('U+', ''), 16) for g in glyphs.split()]) subsetter.subset(font) self.bakery.logging_cmd('mv {0}.subset {1}'.format(name, target_file)) save_font(font, target_file, options)
def subset_font(font, text, options): subsetter = Subsetter(options) subsetter.populate(text=text) subsetter.subset(font) with BytesIO() as outfile: save_font(font, outfile, options) outfile.seek(0) out = b64encode(outfile.read()) return out.decode()
def __generateForWeight(self, weight: str, font: Font) -> None: package = self.__core.package base_dir = self.__core.directories.webfonts output_dir = base_dir.joinpath(f"./{package.version}/{weight}") font_path = self.__core.findFontfilePath(font) output_dir.mkdir(parents=True, exist_ok=True) metadata = self.__generateMetadata() fake = Faker() fake.seed(package.id) subset_fontname: str = fake.name() options = Options() options.font_number = font.number options.hinting = False options.desubroutinize = True options.drop_tables += [ 'FFTM', 'PfEd', 'TeX', 'BDF', 'cvt', 'fpgm', 'prep', 'gasp', 'VORG', 'CBDT', 'CBLC', 'sbix' ] for ignored in ['rvrn', 'locl']: options.layout_features.remove(ignored) for unicodes_file in FILE_DIR.UNICODE_TEXT.glob('./**/*.txt'): idx = unicodes_file.stem unicodes: List[str] = [] with open(unicodes_file, 'r') as unicode_read_io: for line in unicode_read_io.readlines(): unicodes.extend(parse_unicodes(line.split('#')[0])) with load_font(font_path, options) as ttfont: subsetter = Subsetter(options=options) subsetter.populate(unicodes=unicodes) subsetter.subset(ttfont) for record in ttfont['name'].names: if record.nameID == NAME_ID.COPYRIGHT: record.string = '\n'.join(package.copyrights) elif record.nameID in FAMILY_RELATED_NAME_ID: record.string = subset_fontname woff_file = output_dir.joinpath(f"{idx}.woff") with open(woff_file, 'wb') as woff_write_io: options.flavor = 'woff' ttfont.flavorData = WOFFFlavorData() ttfont.flavorData.metaData = metadata save_font(ttfont, woff_write_io, options) woff2_file = output_dir.joinpath(f"{idx}.woff2") with open(woff2_file, 'wb') as woff2_write_io: options.flavor = 'woff2' ttfont.flavorData = WOFF2FlavorData() ttfont.flavorData.metaData = metadata save_font(ttfont, woff2_write_io, options)
def subset(fontfile, outfile_basename, glyphs): options = Options() # Fonttools has this "feature" that if you enable 'dlig', it will also give # you glyphs that you did not ask for, but if you do not enable 'dlig', # then discretionary ligatures do not render properly. # See https://github.com/behdad/fonttools/issues/43. # As a workaround, only enable 'dlig' if there are glyphs for discretionary # ligatures. dligs = set(glyphs).intersection([ 'c_b', 'c_h', 'c_k', 'c_p', 'ct', 'g_i', 'q_u', 's_b', 's_h', 's_k', 's_p', 'st' ]) if len(dligs) > 0: options.layout_features.append('dlig') else: # Due to a bug in Fonttools, options are actually global, so the # remnants of the previous instance are visible here. # See https://github.com/behdad/fonttools/issues/413. if 'dlig' in options.layout_features: options.layout_features.remove('dlig') # Same for small caps, it needs to be enabled explicitly. Luckily, only the # glyphs in the list get included, no extra ones. if any(g.endswith('.smcp') for g in glyphs): options.layout_features.append('smcp') options.layout_features.append('c2sc') else: if 'smcp' in options.layout_features: options.layout_features.remove('smcp') if 'c2sc' in options.layout_features: options.layout_features.remove('c2sc') # Fonts that went through the FontForge roundtrip will have subroutinized # programs in the CFF table. This presumably reduces file size for full # fonts, but on subsetted fonts it hurts file size and compressability, so # desubroutinize. options.desubroutinize = True font = load_font(fontfile, options) subsetter = Subsetter(options=options) subsetter.populate(glyphs=glyphs) subsetter.subset(font) prune_cmaps(font) options.flavor = "woff" save_font(font, outfile_basename + ".woff", options) options.flavor = "woff2" save_font(font, outfile_basename + ".woff2", options) font.close()
def run(self, pipedata): if 'optimize' in pipedata and not pipedata['optimize']: return from bakery_cli.utils import ProcessedFile filename = ProcessedFile() self.bakery.logging_raw('### Optimize TTF {}'.format(filename)) # copied from https://code.google.com/p/noto/source/browse/nototools/subset.py from fontTools.subset import Options, Subsetter, load_font, save_font options = Options() options.layout_features = ["*"] options.name_IDs = ["*"] options.hinting = True options.legacy_kern = True options.notdef_outline = True options.no_subset_tables += ['DSIG'] options.drop_tables = list( set(options._drop_tables_default) - set(['DSIG'])) cmd_options = ('--glyphs=*' ' --layout-features=*' ' --name-IDs=*' ' --hinting' ' --legacy-kern --notdef-outline' ' --no-subset-tables+=DSIG' ' --drop-tables-=DSIG') font = load_font(op.join(self.builddir, filename), options) cmdline = 'pyftsubset {1} {0}'.format(cmd_options, op.join(self.builddir, filename)) self.bakery.logging_cmd(cmdline) subsetter = Subsetter(options=options) subsetter.populate(glyphs=font.getGlyphOrder()) subsetter.subset(font) save_font(font, op.join(self.builddir, filename + '.fix'), options) # compare filesizes TODO print analysis of this :) comment = "# look at the size savings of that subset process" self.bakery.logging_cmd(comment) run(u"ls -la {0} {0}.fix | awk '{{ print $5 \"\t\" $9 }}'".format( unicode(op.join(self.builddir, filename)))) comment = "# copy back optimized ttf to original filename" self.bakery.logging_cmd(comment) shutil.move(op.join(self.builddir, filename + '.fix'), op.join(self.builddir, filename))
def subset_font(basefile_path, buff, text): options = Options() options.name_IDs = [] options.obfuscate_names = True options.flavor = 'woff' font = load_font(basefile_path, options) subsetter = Subsetter(options=options) subsetter.populate(text=text) subsetter.subset(font) save_font(font, buff, options) font.close() buff.seek(0) return
def customSubsetting(self, font, text): options = Options() options.layout_features = "*" # keep all GSUB/GPOS features options.glyph_names = False # keep post glyph names options.legacy_cmap = True # keep non-Unicode cmaps options.name_legacy = True # keep non-Unicode names options.name_IDs = ["*"] # keep all nameIDs options.name_languages = ["*"] # keep all name languages options.notdef_outline = True options.ignore_missing_glyphs = False options.recommended_glyphs = True options.prune_unicode_ranges = True subsetter = Subsetter(options=options) subsetter.populate(text=text, unicodes=[0, 13, 32]) subsetter.subset(font) return font
def _generate_font(self, filename, data): name = data['name-slug'] self._append_log(_('Generating fonts for %s:' % data['name']), bold=True) if self.ranges: for range, unicodes in self.ranges.items(): font = TTFont(filename) subs = Subsetter() subs.populate(unicodes=parse_unicodes(unicodes)) subs.subset(font) self._write_font(font, data, range=range) font.close() del font else: font = TTFont(filename) self._write_font(font, data) font.close()
def run(self, filename, pipedata): if 'optimize' in pipedata and not pipedata['optimize']: return self.bakery.logging_raw('### Optimize TTF {}'.format(filename)) # copied from https://code.google.com/p/noto/source/browse/nototools/subset.py from fontTools.subset import Options, Subsetter, load_font, save_font options = Options() options.layout_features = ["*"] options.name_IDs = ["*"] options.hinting = True options.legacy_kern = True options.notdef_outline = True options.no_subset_tables += ['DSIG'] options.drop_tables = list( set(options._drop_tables_default) - set(['DSIG'])) font = load_font(op.join(self.builddir, filename), options) self.bakery.logging_raw('Before: {}'.format(font.keys())) self.bakery.logging_raw('{}'.format(options.__dict__)) subsetter = Subsetter(options=options) subsetter.populate(glyphs=font.getGlyphOrder()) subsetter.subset(font) save_font(font, op.join(self.builddir, filename + '.opt'), options) newsize = op.getsize(op.join(self.builddir, filename + '.opt')) origsize = op.getsize(op.join(self.builddir, filename)) # compare filesizes TODO print analysis of this :) comment = "# look at the size savings of that subset process" self.bakery.logging_cmd("ls -l '%s'* %s" % (filename, comment)) statusmessage = "{0}.opt: {1} bytes\n{0}: {2} bytes\n" self.bakery.logging_raw( statusmessage.format(filename, newsize, origsize)) self.bakery.logging_raw('Now: {}'.format(font.keys())) # move ttx files to src shutil.move(op.join(self.builddir, filename + '.opt'), op.join(self.builddir, filename), log=self.bakery.logger)
def _subset(self, otf, fmt): from fontTools.subset import Options, Subsetter for name, subset in self.subsets.items(): logger.info(f"Creating {name}.{fmt.value} subset") new = deepcopy(otf) options = Options() options.name_IDs = ["*"] options.name_legacy = True options.name_languages = ["*"] options.recommended_glyphs = True options.layout_features = ["*"] options.notdef_outline = True options.notdef_glyph = True options.glyph_names = True options.hinting = True options.legacy_kern = True options.symbol_cmap = True options.layout_closure = False options.prune_unicode_ranges = False options.passthrough_tables = False options.recalc_average_width = True options.ignore_missing_glyphs = True options.layout_scripts = subset["langsys"] options.drop_tables.remove("DSIG") options.no_subset_tables += ["DSIG"] subsetter = Subsetter(options=options) subsetter.populate(subset["glyphlist"]) with TemporaryLogLevel(logging.WARNING): subsetter.subset(new) new = self._optimize(new, name, fmt) names = subset.get("names") if names: logger.info( f"Adding name entries to {name}.{fmt.value} susbet") self._setnames(new, names) self._buildwoff(new, name, fmt) self._save(new, name, fmt)
def subsetter(self, font, subset): """ use the noto fonts glyphsnames to subset fonts with premade subsettings """ options = Options() options.layout_features = "*" # keep all GSUB/GPOS features # options.no_layout_closure = True # TESTING options.glyph_names = False # keep post glyph names options.legacy_cmap = True # keep non-Unicode cmaps options.name_legacy = True # keep non-Unicode names options.name_IDs = ["*"] # keep all nameIDs options.name_languages = ["*"] # keep all name languages options.notdef_outline = False options.ignore_missing_glyphs = True options.prune_unicode_ranges = True options.recommended_glyphs = True subsetter = Subsetter(options=options) subsetter.populate(glyphs=subset) subsetter.subset(font) return font
def subsetFont(fontPath, subset): tmpOutputFontName = os.path.dirname( os.path.abspath(__file__)) + "/tmp/" + str(uuid.uuid4()) + ".woff" font = TTFont(fontPath) options = Options() options.desubroutinize = True options.with_zopfli = True options.flavor = "woff" subsetter = Subsetter(options=options) subsetter.populate(text=subset) subsetter.subset(font) save_font(font, tmpOutputFontName, options) subsettedFont = 'data:;base64,' + open(tmpOutputFontName, "rb").read().encode("base64") cleanUp([tmpOutputFontName]) print subsettedFont.replace('\n', '')
def main(args): """Subset a font (useful for making small test fonts). Args: args: list, arguments the user typed. """ parser = argparse.ArgumentParser() parser.add_argument('fontfile', help='Input font file') parser.add_argument('--subset_postfix', default='', help='Postfix to the subset extension') parser.add_argument('--text', default='', help='Text to include in the subset') parser.add_argument( '--unicodes', default='', help='Comma separated list of Unicode codepoints (hex) ' 'to include in the subset; eg, "e7,0xe8,U+00e9"') parser.add_argument('--glyphs', default='', help='Comma separated list of glyph IDs (decimal) to ' 'include in the subset; eg, "1,27"') parser.add_argument('--hinting', default=False, action='store_true', help='Enable hinting if specified, no hinting if not ' 'present') cmd_args = parser.parse_args(args) options = Options() # Definitely want the .notdef glyph and outlines. options.notdef_glyph = True options.notdef_outline = True # Get the item. to keep in the subset. text = cmd_args.text unicodes_str = cmd_args.unicodes.lower().replace('0x', '').replace('u+', '') # TODO(bstell) replace this whole files by using the new subset.py code unicodes_input = [c for c in unicodes_str.split(',') if c] unicodes = [] for c in unicodes_input: if '-' in c: uni_range = c.split('-') uni_range_expanded = range(int(uni_range[0], 16), int(uni_range[1], 16) + 1) unicodes.extend(uni_range_expanded) else: unicodes.append(int(c, 16)) #unicodes = [int(c, 16) for c in unicodes_input_expanded] glyphs = [int(c) for c in cmd_args.glyphs.split(',') if c] fontfile = cmd_args.fontfile options.hinting = cmd_args.hinting # False => no hinting options.hinting = True # hint stripping for CFF is currently broken dirname = os.path.dirname(fontfile) basename = os.path.basename(fontfile) filename, extension = os.path.splitext(basename) subset_postfix = cmd_args.subset_postfix output_file = dirname + '/' + filename + '_subset' + subset_postfix + extension print "output_file =", output_file font = load_font(fontfile, options, lazy=False) subsetter = Subsetter(options) subsetter.populate(text=text, unicodes=unicodes, glyphs=glyphs) subsetter.subset(font) save_font(font, output_file, options)
# We round-trip through a buffer to workaround PIL not # resetting the dpi value. data = im.tobytes() im = Image.frombytes(im.mode, im.size, data) if dst.exists(): dstIm = Image.open(dst) if data == dstIm.tobytes(): # Don't save when the image data is the same, some # meta data may still have changed, making us do # unwanted commits. print("-- same image, skipping", dst) continue im.save(dst) print("Subsetting fonts...") for src in sorted(docsSourceFonts.glob("*.woff2")): dst = docsFonts / src.name font = TTFont(src) subsetter = Subsetter() unicodes = set(ord(c) for c in markdownSource) subsetter.populate(unicodes=unicodes) subsetter.subset(font) if dst.exists(): existing = TTFont(dst, lazy=True) if sorted(font.getBestCmap()) == sorted(existing.getBestCmap()): print("-- same cmap, skipping", dst) continue font.flavor = "woff2" font.save(dst)
def subsetFonts(family, writingSystem, flavor=["ttf"], familyNewName=" ", jsonpath=" ", keepFea=True): print(familyNewName) # if len(flavor) == 0: flavor = ["ttf"] latinProCodePageRange = [0, 1, 4, 7, 8] cyrProCodePageRange = [2] greekProCodePageRange = [3] ASCII = [0, 1] SecureSet = [0] coreArabicCodePageRange = [0, 6] unicodePageRangeDict = { "Cyrillic": latinProCodePageRange, "CyrillicPro": latinProCodePageRange, "Greek": greekProCodePageRange, "Latin": latinProCodePageRange, "ASCII": ASCII, "SecureSet": SecureSet, "Core_Arabic": coreArabicCodePageRange } pageRangeToApply = [] subsetFolder = "" for i in writingSystem: subsetFolder += i formats = ["ttf", "woff", "woff2"] toKeep = list() folder = getFolder(family) folderFonts = os.path.join(folder, "fonts") options = Options() options.layout_features = '*' # keep all GSUB/GPOS features # options.legacy_kern = True # keep kern table options.glyph_names = False # keep post glyph names options.legacy_cmap = True # keep non-Unicode cmaps options.symbol_cmap = True # keep Symbol cmaps options.name_legacy = True # keep non-Unicode names options.name_IDs = ['*'] # keep all nameIDs options.name_languages = ['*'] # keep all name languages options.notdef_outline = True # keep outline of .notdef options.ignore_missing_glyphs = True options.prune_unicode_ranges = True keep = [] if family in pan_european_fonts: jsonpath = "subsets/lgc_glyphset.json" elif family in arabic_fonts: jsonpath = "subsets/arabic_glyphset.json" keep, pageRangeToApply = readJsonStoredSubset(jsonpath, writingSystem) for i in flavor: if not os.path.exists(os.path.join(folderFonts, i.upper())): print(">> Make {} fonts.".format(family)) designSpace2Instances(family, i, secureSet=False) for i in flavor: fontspath = [os.path.join(folderFonts, i.upper(), font) \ for font in os.listdir(folder + "/fonts/" + i.upper())] for f in fontspath: if f.split(".")[-1] in formats: newfont = TTFont(f) for namerecord in newfont['name'].names: namerecord.string = namerecord.toUnicode() if namerecord.nameID == 2: WeightName = namerecord.string if namerecord.nameID == 17: WeightName = "".join(namerecord.string.split(" ")) # print(family, WeightName) subsetter = Subsetter(options=options) subsetter.populate(glyphs=keep) subsetter.subset(newfont) destination = os.path.join(folder, "fonts", subsetFolder + "_subset", "fonts") if not os.path.exists(os.path.join(destination, i.upper())): os.makedirs(os.path.join(destination, i.upper())) subsetName = family + subsetFolder + "-" + WeightName + "." + i newfont.save(os.path.join(destination, i.upper(), subsetName)) folder = family + "/fonts/" + subsetFolder + "_subset" if familyNewName != " ": renameFonts(folder, familyNewName, codePageRange=pageRangeToApply) else: familyNewName = family + subsetFolder renameFonts(folder, familyNewName, codePageRange=pageRangeToApply) shutil.rmtree(destination)
def subset(self, text): subsetter = Subsetter(self.options) subsetter.populate(text=text) subsetter.subset(self.font)