def subset_font_cmap(srcname, dstname, exclude=None, include=None, bump_version=True): opt = _DEFAULT_OPTIONS font = subset.load_font(srcname, opt) target_charset = set(font_data.get_cmap(font).keys()) if include is not None: target_charset &= include if exclude is not None: target_charset -= exclude subsetter = subset.Subsetter(options=opt) subsetter.populate(unicodes=target_charset) subsetter.subset(font) if bump_version: # assume version string has 'uh' if unhinted, else hinted. revision, version_string = swat_license.get_bumped_version(font) font['head'].fontRevision = revision font_data.set_name_record(font, _VERSION_ID, version_string) subset.save_font(font, dstname, opt)
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_otf_from_ufo(self, otf_path, ufo): """Subset a font using export flags set by glyphsLib.""" keep_glyphs = set(ufo.lib.get(GLYPHS_PREFIX + 'Keep Glyphs', [])) include = [] for old_name, new_name in zip( ufo.lib[PUBLIC_PREFIX + 'glyphOrder'], TTFont(otf_path).getGlyphOrder()): glyph = ufo[old_name] if ((keep_glyphs and old_name not in keep_glyphs) or not glyph.lib.get(GLYPHS_PREFIX + 'Glyphs.Export', True)): continue include.append(new_name) # copied from nototools.subset opt = subset.Options() opt.name_IDs = ['*'] opt.name_legacy = True opt.name_languages = ['*'] opt.layout_features = ['*'] opt.notdef_outline = True opt.recalc_bounds = True opt.recalc_timestamp = True opt.canonical_order = True opt.glyph_names = True font = subset.load_font(otf_path, opt, lazy=False) subsetter = subset.Subsetter(options=opt) subsetter.populate(glyphs=include) subsetter.subset(font) subset.save_font(font, otf_path, opt)
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 woffCreate(self, text=None, No='2'): mergeTools = merge.Merger() woffFile = "static/font/" + No + ".woff" saveFilename = str(Path(self.cwd) / woffFile) filename = [self.fontFile, saveFilename] textLuck = [] baseUrl = 'https://www.font.cn/preview/getFont?font_id=303&format=ttf&vers=v1.0&words=' urlEnd = '&hex=1' if text is None: return collectText = {t for t in text} text = ''.join(collectText) for t in text: if t not in self.name: hexT = str(hex(ord(t))) textLuck.append(hexT[2:]) # print(len(textLuck)) if len(textLuck) != 0: mergeUrl = ','.join(textLuck) aimUrl = baseUrl + mergeUrl + urlEnd fontDown = self.downloadWoff(aimUrl, saveFilename) if fontDown != None: mergeFont = mergeTools.merge(filename) mergeFont.save(self.fontFile) # 拆分合并后的字体文件成2.woff options = subset.Options() fontMerge = subset.load_font(self.fontFile, options) subsetter = subset.Subsetter(options) subsetter.populate(text=text) subsetter.subset(fontMerge) options.flavor = 'woff' subset.save_font(fontMerge, saveFilename, options)
def makeKit(font_path): # put the result into a directory named file_name.kit dest_dir = os.path.splitext(font_path)[0] + '.kit' if os.path.isdir(dest_dir): print 'FAILURE: dest %s already exists' % dest_dir return False os.makedirs(dest_dir) print 'Making a kit for %s in %s' % (font_path, dest_dir) # crack open the font # equivalent pyftsubset /tmp/Lobster-Regular.ttf --unicodes='*' --obfuscate_names options = subset.Options() with contextlib.closing(subset.load_font(font_path, options)) as font: unicodes = [] for t in font['cmap'].tables: if t.isUnicode(): unicodes.extend(t.cmap.keys()) options.unicodes = unicodes # mangle 'name' so the font can't be installed options.obfuscate_names # apply our subsetting, most notably trashing 'name' subsetter = subset.Subsetter(options=options) subsetter.populate() # write [ot]tf, woff, and woff2 editions with 'name' mangled font_name_noext = os.path.splitext(os.path.basename(font_path))[0] font_ext = os.path.splitext(os.path.basename(font_path))[1] for fmt in [font_ext, '.woff', '.woff2']: dest_file = os.path.join(dest_dir, font_name_noext + fmt) options.flavor = None if fmt.startswith('.woff'): options.flavor = fmt[1:] print 'Writing %s' % dest_file with open(dest_file, 'wb') as f: subset.save_font(font, f, options) # write a sample somewhat (no early Android, IE) bulletproof css dest_file = os.path.join(dest_dir, 'bulletproof.css') os2 = font['OS/2'] font_style = 'normal' if os2.fsSelection & 1: font_style = 'italic' with open(dest_file, 'w') as f: f.write("@font-face {\n") f.write(" font-family: '%s';\n" % font_name_noext) f.write(" font-style: %s;\n" % font_style) f.write(" font-weight: %d;\n" % os2.usWeightClass) f.write(" src:\n") f.write(" url('./%s.woff2') format('woff2'),\n" % font_name_noext) f.write(" url('./%s.woff') format('woff'),\n" % font_name_noext) if font_ext == '.otf': f.write(" url('./%s.otf') format('opentype')" % font_name_noext) else: f.write(" url('./%s.ttf') format('truetype')" % font_name_noext) f.write(";\n") f.write("}\n") return True
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 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 subset_ttf_font(filepath: str) -> dict: options = subset.Options() font = subset.load_font(f'{filepath}.ttf', options) options.flavor = 'woff' subset.save_font(font, f'{filepath}.woff', options) options.flavor = 'woff2' subset.save_font(font, f'{filepath}.woff2', options) return {'woff': f'{filepath}.woff', 'woff2': f'{filepath}.woff2'}
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 subSetFont(ff, tt): options = subset.Options() # dir(options) font = subset.load_font(ff, options) subsetter = subset.Subsetter(options) subsetter.populate(text=tt) subsetter.subset(font) # options.flavor = 'woff' subset.save_font(font, 'font.ttf', options) modFont()
def subset_font(source_file, target_file, include=None, exclude=None, options=None): """Subsets a font file. Subsets a font file based on a specified character set. If only include is specified, only characters from that set would be included in the output font. If only exclude is specified, all characters except those in that set will be included. If neither is specified, the character set will remain the same, but inaccessible glyphs will be removed. Args: source_file: Input file name. target_file: Output file name include: The list of characters to include from the source font. exclude: The list of characters to exclude from the source font. options: A dictionary listing which options should be different from the default. Raises: NotImplementedError: Both include and exclude were specified. """ opt = subset.Options() opt.name_IDs = ["*"] opt.name_legacy = True opt.name_languages = ["*"] opt.layout_features = ["*"] opt.notdef_outline = True opt.recalc_bounds = True opt.recalc_timestamp = True opt.canonical_order = True opt.drop_tables = ["+TTFA"] if options is not None: for name, value in options.items(): setattr(opt, name, value) if include is not None: if exclude is not None: raise NotImplementedError( "Subset cannot include and exclude a set at the same time.") target_charset = include else: if exclude is None: exclude = [] source_charset = coverage.character_set(source_file) target_charset = source_charset - set(exclude) font = subset.load_font(source_file, opt) subsetter = subset.Subsetter(options=opt) subsetter.populate(unicodes=target_charset) subsetter.subset(font) subset.save_font(font, target_file, opt)
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_otf_from_ufo(self, otf_path, ufo): """Subset a font using export flags set by glyphsLib. There are two more settings that can change export behavior: "Export Glyphs" and "Remove Glyphs", which are currently not supported for complexity reasons. See https://github.com/googlei18n/glyphsLib/issues/295. """ from fontTools import subset # ufo2ft always inserts a ".notdef" glyph as the first glyph ufo_order = makeOfficialGlyphOrder(ufo) if ".notdef" not in ufo_order: ufo_order.insert(0, ".notdef") ot_order = TTFont(otf_path).getGlyphOrder() assert ot_order[0] == ".notdef" assert len(ufo_order) == len(ot_order) for key in (KEEP_GLYPHS_NEW_KEY, KEEP_GLYPHS_OLD_KEY): keep_glyphs_list = ufo.lib.get(key) if keep_glyphs_list is not None: keep_glyphs = set(keep_glyphs_list) break else: keep_glyphs = None include = [] for source_name, binary_name in zip(ufo_order, ot_order): if keep_glyphs and source_name not in keep_glyphs: continue if source_name in ufo: exported = ufo[source_name].lib.get(GLYPH_EXPORT_KEY, True) if not exported: continue include.append(binary_name) # copied from nototools.subset opt = subset.Options() opt.name_IDs = ['*'] opt.name_legacy = True opt.name_languages = ['*'] opt.layout_features = ['*'] opt.notdef_outline = True opt.recalc_bounds = True opt.recalc_timestamp = True opt.canonical_order = True opt.glyph_names = True font = subset.load_font(otf_path, opt, lazy=False) subsetter = subset.Subsetter(options=opt) subsetter.populate(glyphs=include) subsetter.subset(font) subset.save_font(font, otf_path, opt)
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 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 subset_font(source_file, target_file, include=None, exclude=None, options=None): """Subsets a font file. Subsets a font file based on a specified character set. If only include is specified, only characters from that set would be included in the output font. If only exclude is specified, all characters except those in that set will be included. If neither is specified, the character set will remain the same, but inaccessible glyphs will be removed. Args: source_file: Input file name. target_file: Output file name include: The list of characters to include from the source font. exclude: The list of characters to exclude from the source font. options: A dictionary listing which options should be different from the default. Raises: NotImplementedError: Both include and exclude were specified. """ opt = subset.Options() opt.name_IDs = ['*'] opt.name_legacy = True opt.name_languages = ['*'] opt.layout_features = ['*'] opt.notdef_outline = True opt.recalc_bounds = True opt.recalc_timestamp = True opt.canonical_order = True opt.drop_tables = ['+TTFA'] if options is not None: for name, value in options.iteritems(): setattr(opt, name, value) if include is not None: if exclude is not None: raise NotImplementedError( 'Subset cannot include and exclude a set at the same time.') target_charset = include else: if exclude is None: exclude = [] source_charset = coverage.character_set(source_file) target_charset = source_charset - set(exclude) font = subset.load_font(source_file, opt) subsetter = subset.Subsetter(options=opt) subsetter.populate(unicodes=target_charset) subsetter.subset(font) subset.save_font(font, target_file, opt)
def subset(self, font_bytes, codepoints): # pylint: disable=no-self-use """Computes a subset of font_bytes to the given codepoints.""" options = subset.Options() subsetter = subset.Subsetter(options=options) with io.BytesIO(font_bytes) as font_io, \ subset.load_font(font_io, options) as font: subsetter.populate(unicodes=codepoints) subsetter.subset(font) with io.BytesIO() as output: subset.save_font(font, output, options) return output.getvalue()
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 convertFont(fontPath, fontType): options = Options() tmpOutputTtf = tmpFileName(".ttf") tmpOutputWoff = tmpFileName(".woff") font = TTFont(fontPath) ttfOptions = Options() # export the font as woff for web use woffOptions = Options() woffOptions.with_zopfli = True woffOptions.flavor = "woff" if fontType == "otf": # convert the font to ttf ttfFont = otf_to_ttf(font) # save font can also convert to woff! save_font(ttfFont, tmpOutputTtf, ttfOptions) save_font(ttfFont, tmpOutputWoff, woffOptions) elif fontType == "ttf": save_font(font, tmpOutputTtf, ttfOptions) save_font(font, tmpOutputWoff, woffOptions) else: print "wrong type" ttfBase64 = "data:;base64," + toBase64(tmpOutputTtf) woffBase64 = "data:;base64," + toBase64(tmpOutputWoff) #cleanup files cleanUp([tmpOutputWoff, tmpOutputTtf]) print woffBase64.replace("\n", "") print ttfBase64.replace("\n", "")
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 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 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 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 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 subset_font_cmap( srcname, dstname, exclude=None, include=None, bump_version=True): opt = _DEFAULT_OPTIONS font = subset.load_font(srcname, opt) target_charset = set(font_data.get_cmap(font).keys()) if include is not None: target_charset &= include if exclude is not None: target_charset -= exclude subsetter = subset.Subsetter(options=opt) subsetter.populate(unicodes=target_charset) subsetter.subset(font) if bump_version: # assume version string has 'uh' if unhinted, else hinted. revision, version_string = swat_license.get_bumped_version(font) font['head'].fontRevision = revision font_data.set_name_record(font, _VERSION_ID, version_string) subset.save_font(font, dstname, opt)
def subset_otf_from_ufo(self, otf_path, ufo): """Subset a font using export flags set by glyphs2ufo.""" font_lib_prefix = 'com.schriftgestaltung.' glyph_lib_prefix = font_lib_prefix + 'Glyphs.' keep_glyphs = set(ufo.lib.get(font_lib_prefix + 'Keep Glyphs', [])) include = [] glyph_order = ufo.lib['public.glyphOrder'] for glyph_name in glyph_order: glyph = ufo[glyph_name] if ((keep_glyphs and glyph_name not in keep_glyphs) or not glyph.lib.get(glyph_lib_prefix + 'Export', True)): continue include.append(glyph_name) # copied from nototools.subset opt = subset.Options() opt.name_IDs = ['*'] opt.name_legacy = True opt.name_languages = ['*'] opt.layout_features = ['*'] opt.notdef_outline = True opt.recalc_bounds = True opt.recalc_timestamp = True opt.canonical_order = True opt.glyph_names = ufo.lib.get( font_lib_prefix + "Don't use Production Names") font = subset.load_font(otf_path, opt, lazy=False) subsetter = subset.Subsetter(options=opt) subsetter.populate(glyphs=include) subsetter.subset(font) subset.save_font(font, otf_path, opt)
def convertFont(base64, type): options = Options() # tmp file names tmpInputFontName = tmpFileName("." + type) tmpOutputTtf = tmpFileName(".ttf") tmpOutputWoff = tmpFileName(".woff") # remove data header from base64 # now we have a clean input source fontbase64 = base64.split(",")[1] with open(tmpInputFontName, "wb") as f: fontinput = f.write(fontbase64.decode('base64')) f.close() # we always work from a TTFont Object (also takes OTF) font = TTFont(tmpInputFontName) ttfOptions = Options() # export the font as woff for web use woffOptions = Options() woffOptions.with_zopfli = True woffOptions.flavor = "woff" if type == 'otf': # convert the font to ttf ttfFont = otf_to_ttf(font) # save font can also convert to woff! save_font(ttfFont, tmpOutputTtf, ttfOptions) save_font(ttfFont, tmpOutputWoff, woffOptions) elif type == 'ttf': save_font(font, tmpOutputTtf, ttfOptions) save_font(font, tmpOutputWoff, woffOptions) else: return {'error': 'please give a valid type'} ttfBase64 = toBase64(tmpOutputTtf) woffBase64 = toBase64(tmpOutputWoff) #cleanup files cleanUp([tmpInputFontName, tmpOutputWoff, tmpOutputTtf]) return {'woff': woffBase64, 'ttf': ttfBase64}
woutput = sys.argv[4] options = ftss.Options() options.flavor = 'woff2' subsetter = ftss.Subsetter(options=options) font = ftss.load_font(fname, options) subsetter.populate(unicodes=glyphs) subsetter.subset(font) out = io.BytesIO() ftss.save_font(font, out, options) font.close() # save the woff2 file fw = open(woutput, "w") fw.write("package serif\n") fw.write("var Woff2Blob = \"") fw.write(base64.b64encode(out.getvalue()).decode("ascii")) fw.write("\"") fw.close() # save the dimensions file fd = open(doutput, "w", 1) subargs = [freader, fname, "serif"] subargs.extend(map(lambda x: str(x), glyphs))
def startCut(self): subset.save_font(self.font, self.output_path, self.options)
from fontTools import subset options = subset.Options() # dir(options) font = subset.load_font('shuibo.ttf', options) subsetter = subset.Subsetter(options) subsetter.populate(text = 'Google') subsetter.subset(font) options.flavor = 'woff' subset.save_font(font, 'font.woff', options)
#!/usr/bin/python import sys from fontTools import subset # 测试使用 # sys.argv = ['字体拆分.py', '.\data\Funkster.ttf'] sys.argv = ['字体拆分.py', '.\data\HARLOWSI.TTF'] if len(sys.argv) < 2: print("Usage : 字体拆分.py in.ttx") sys.exit(1) ttx_file = sys.argv[1] del sys.argv ops = subset.Options() font = subset.load_font(ttx_file, ops) sub = subset.Subsetter(ops) sub.populate(text='Google') sub.subset(font) ops.flavor = 'woff' subset.save_font(font, '.\data\sub.woff', ops) font.close() # sub.woff 在 windows 上利用 FontCreator 软件导出成ttf文件 即可使用
def subset_otf_from_ufo(self, otf_path, ufo): """Subset a font using "Keep Glyphs" custom parameter and export flags as set by glyphsLib. "Export Glyphs" and "Remove Glyphs" are currently not supported: https://github.com/googlei18n/glyphsLib/issues/295. """ from fontTools import subset # we must exclude from the final UFO glyphOrder all the glyphs that were not # exported to OTF because included in 'public.skipExportGlyphs' skip_export_glyphs = set(ufo.lib.get("public.skipExportGlyphs", ())) exported_glyphs = dict.fromkeys(g for g in ufo.keys() if g not in skip_export_glyphs) ufo_order = makeOfficialGlyphOrder(exported_glyphs, glyphOrder=ufo.glyphOrder) # ufo2ft always inserts a ".notdef" glyph as the first glyph if ".notdef" not in exported_glyphs: ufo_order.insert(0, ".notdef") ot_order = TTFont(otf_path).getGlyphOrder() assert ot_order[0] == ".notdef" assert len(ufo_order) == len(ot_order) for key in (KEEP_GLYPHS_NEW_KEY, KEEP_GLYPHS_OLD_KEY): keep_glyphs_list = ufo.lib.get(key) if keep_glyphs_list is not None: keep_glyphs = set(keep_glyphs_list) break else: keep_glyphs = None include = [] for source_name, binary_name in zip(ufo_order, ot_order): if keep_glyphs and source_name not in keep_glyphs: continue if source_name in ufo: exported = ufo[source_name].lib.get(GLYPH_EXPORT_KEY, True) if not exported: continue include.append(binary_name) # copied from nototools.subset opt = subset.Options() opt.name_IDs = ["*"] opt.name_legacy = True opt.name_languages = ["*"] opt.layout_features = ["*"] opt.notdef_outline = True opt.recalc_bounds = True opt.recalc_timestamp = True opt.canonical_order = True opt.glyph_names = True font = subset.load_font(otf_path, opt, lazy=False) subsetter = subset.Subsetter(options=opt) subsetter.populate(glyphs=include) subsetter.subset(font) subset.save_font(font, otf_path, opt)
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 save(self, outputfile): save_font(self.font, outputfile, self.options)