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 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 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 get_glyphs_subset(fontfile, characters): """ Subset a TTF font Reads the named fontfile and restricts the font to the characters. Returns a serialization of the subset font as file-like object. Parameters ---------- symbol : str Path to the font file characters : str Continuous set of characters to include in subset """ options = subset.Options(glyph_names=True, recommended_glyphs=True) # prevent subsetting FontForge Timestamp and other tables options.drop_tables += ['FFTM', 'PfEd', 'BDF'] # if fontfile is a ttc, specify font number if fontfile.endswith(".ttc"): options.font_number = 0 with subset.load_font(fontfile, options) as font: subsetter = subset.Subsetter(options=options) subsetter.populate(text=characters) subsetter.subset(font) fh = BytesIO() font.save(fh, reorderTables=False) return fh
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_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 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 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 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 __init__(self, fontfile, hinting, whitespace_list): self.fontfile = fontfile self.options = Options() self.options.hinting = hinting self.font = load_font(fontfile, self.options, lazy=False) # assert 'glyf' in self.font, 'only support TrueType (quadratic) fonts \ #(eg, not CFF) at this time' self.whitespace_list = whitespace_list
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 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 __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 __init__(self, fontfile, hinting, whitespace_and_ignorable_list): self.fontfile = fontfile self.options = Options() self.options.hinting = hinting # Want the .notdef glyph and outlines. self.options.notdef_glyph = True self.options.notdef_outline = True self.options.desubroutinize = True self.font = load_font(fontfile, self.options, lazy=False) self.whitespace_and_ignorable_list = whitespace_and_ignorable_list
def __init__(self, fontfile, hinting, whitespace_and_ignorable_list): self.fontfile = fontfile self.options = Options() self.options.hinting = hinting # Want the .notdef glyph and outlines. self.options.notdef_glyph = True self.options.notdef_outline = True self.options.decompress = True self.font = load_font(fontfile, self.options, lazy=False) self.whitespace_and_ignorable_list = whitespace_and_ignorable_list
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(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 prune_font(fontfile, out_dir): font = fonttools.load_font(fontfile, fonttools.Options()) # Roundtripping a font trough FontForge adds a GDEF table. This table is not # present in the original version of Calluna, so remove it. It is present in # Inconsolata, but it does not appear to do any harm to remove it, apart # from reducing the file size. if 'GDEF' in font: del font['GDEF'] font.save(os.path.join(out_dir, os.path.basename(fontfile))) 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 initOption(self, **kwargs): # 设置选项 self.options = subset.Options() self.options.flavor = 'woff' for k, v in kwargs.items(): if not hasattr(self.options, k): setattr(self.options, k, v) # 实例化字体 self.font = subset.load_font(self.file_path, self.options) # 设置配置器 self.subsetter = subset.Subsetter(self.options) self.subsetter.populate(text=self.text) self.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. 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 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 _load_font(path): guess = mimetypes.guess_type(path) if guess[0] not in [ "font/ttc", "font/ttf", "font/otf", "font/woff", "application/font-sfnt", "application/font-woff", ]: logging.error("Not a font file: {}".format(path)) logging.error("Guessed mimetype: '{}'".format(guess[0])) logging.error("If this is a text file: do you have Git LFS installed?") sys.exit(1) try: return subset.load_font(path, FONT_TOOLS_OPTIONS, dontLoadGlyphNames=True) except FileNotFoundError as e: # noqa F821 logging.error("Could not load font: {}".format(str(e))) logging.error("You may need to run: `make i18n-download-source-fonts`") sys.exit(1)
def main(args): if not args.filepath: raise AttributeError('Please specify font filepath') if not os.path.exists(args.filepath): raise AttributeError('File: %s not found' % args.filepath) textfile_dir = '_posts' unichars = set() def walk_callback(args, dirname, fnames): for fname in fnames: unichars.update(get_unicodes(os.path.join(dirname, fname))) os.path.walk(textfile_dir, walk_callback, None) unicodes = [ord(c) for c in unichars] cjk_fontfile = args.filepath out_fontdir = 'assets/fonts' out_fontfile = makeOutputFileName(os.path.basename(args.filepath), outputDir=out_fontdir, extension='.woff', overWrite=True) options = subset.Options() dontLoadGlyphNames = not options.glyph_names font = subset.load_font(cjk_fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames) subsetter = subset.Subsetter() subsetter.populate(glyphs=[], gids=[], unicodes=unicodes, text='') subsetter.subset(font) sfnt.USE_ZOPFLI = True font.flavor = 'woff' font.save(out_fontfile, reorderTables=False) print('Input font: % 7d bytes: %s' % (os.path.getsize(cjk_fontfile), cjk_fontfile)) print('Subset font: % 7d bytes: %s' % (os.path.getsize(out_fontfile), out_fontfile))
def stamp(svgfile, fontfile, text, font_family=None, xml_transform=None): options = Options(flavor="woff2") with load_font(fontfile, options) as font: font_family = font_family or font_name(font) font_data = subset_font(font, text, options) with open(svgfile) as f: xml = xmltodict.parse(f.read()) # Embed font in root style tag style = xml["svg"].get("style", {"#text": ""}) if type(style) == str: style = {"#text": style} style["#text"] += f""" <![CDATA[ @font-face {{ font-family: "{font_family}"; src: url("data:font/woff2;base64,{font_data}"); }} ]]> """ xml["svg"]["style"] = style if xml_transform: xml = xml_transform(xml) return unescape(xmltodict.unparse(xml))
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 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 "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 __init__(self, font_path, options=None, featurs=['*']): if not options: options = self.make_options(featurs) self.options = options self.font = load_font(font_path, self.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)
#!/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文件 即可使用
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)
if len(sys.argv) != 5: print("Usage: " + sys.argv[0] + " font-file font-reader dimension-output woff2-output") exit() fname = sys.argv[1] freader = sys.argv[2] doutput = sys.argv[3] 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 = \"")