def makeWeb(args): """If we are building a web version then try to minimise file size""" font = TTFont(args.file, recalcTimestamp=False) # removed compatibility glyphs that of little use on the web ranges = ( (0xfb50, 0xfbb1), (0xfbd3, 0xfd3d), (0xfd50, 0xfdf9), (0xfdfc, 0xfdfc), (0xfe70, 0xfefc), ) cmap = font['cmap'].buildReversed() unicodes = set([min(cmap[c]) for c in cmap]) for r in ranges: unicodes -= set(range(r[0], r[1] + 1)) options = subset.Options() options.set(layout_features='*', name_IDs='*', drop_tables=['DSIG']) subsetter = subset.Subsetter(options=options) subsetter.populate(unicodes=unicodes) subsetter.subset(font) base, ext = os.path.splitext(args.file) for flavor in ("woff", "woff2"): font.flavor = flavor font.save(args.dir + "/" + base + "." + flavor) font.close()
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 _extract_subset(self, options=None): if options is None: options = subset.Options(layout_closure=False) options.drop_tables += ['GPOS', 'GDEF', 'GSUB'] if self.use_raw_gids: # Have to retain GIDs in the Type2 (non-CFF) case, since we don't # have a predefined character set available (i.e. the ROS ordering # param is 'Identity') # This ensures that the PDF operators we output will keep working # with the subsetted font, at the cost of a slight space overhead in # the output. # This is fine, because fonts with a number of glyphs where this # would matter (i.e. large CJK fonts, basically), are subsetted as # CID-keyed CFF fonts anyway (and based on a predetermined charset), # so this subtlety doesn't apply and the space overhead is very # small. # NOTE: we apply the same procedure to string-keyed CFF fonts, # even though this is rather inefficient... XeTeX can subset these # much more compactly, presumably by rewriting the CFF charset data. # TODO look into how that works options.retain_gids = True subsetter: subset.Subsetter = subset.Subsetter(options=options) subsetter.populate(gids=list(self._glyphs.keys())) subsetter.subset(self.tt)
def handle_font(font_name): font = TTFont(font_name) orig_size = os.path.getsize(font_name) if decompress: from fontTools import subset options = subset.Options() options.desubroutinize = True subsetter = subset.Subsetter(options=options) subsetter.populate(glyphs=font.getGlyphOrder()) subsetter.subset(font) if verbose: print("Compressing font through iterative_encode:") out_name = "%s.compressed%s" % os.path.splitext(font_name) compreffor = Compreffor(font, verbose=verbose, **comp_kwargs) compreffor.compress() # save compressed font font.save(out_name) if generate_cff: # save CFF version font["CFF "].cff.compile( open("%s.cff" % os.path.splitext(out_name)[0], "w"), None) comp_size = os.path.getsize(out_name) print("Compressed to %s -- saved %s" % (os.path.basename(out_name), human_size(orig_size - comp_size))) if check: test_compression_integrity(filename, out_name) test_call_depth(out_name)
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 generate(self, version, output): self._update_metadata(version) self._cleanup_glyphs() self._make_over_under_line() self._font.generate(output, flags=("opentype")) font = TTFont(output) # https://github.com/fontforge/fontforge/pull/3235 # fontDirectionHint is deprecated and must be set to 2 font["head"].fontDirectionHint = 2 # unset bits 6..10 font["head"].flags &= ~0x7e0 options = subset.Options() options.set(layout_features='*', name_IDs='*', notdef_outline=True, glyph_names=True, recalc_average_width=True, drop_tables=["FFTM"]) unicodes = font["cmap"].getBestCmap().keys() subsetter = subset.Subsetter(options=options) subsetter.populate(unicodes=unicodes) subsetter.subset(font) font.save(output)
def rename(font): names = set() for layers in font["COLR"].ColorLayers.values(): names |= {l.name for l in layers if LAYER.match(l.name)} del font["COLR"] del font["CPAL"] glyphs = set(font.glyphOrder) - names options = subset.Options() options.set(layout_features='*', name_IDs='*', name_languages='*', notdef_outline=True, glyph_names=True) subsetter = subset.Subsetter(options=options) subsetter.populate(glyphs=glyphs) subsetter.subset(font) for name in font["name"].names: if name.nameID in (1, 3, 4, 6): string = name.toUnicode() if name.nameID in (3, 6): string = string.replace("QuranColored", "Quran") else: string = string.replace("Quran Colored", "Quran") name.string = string.encode(name.getEncoding())
def main(): parser = argparse.ArgumentParser(description="Merge Aref Ruqaa fonts.") parser.add_argument("file1", metavar="FILE", help="input font to process") parser.add_argument("file2", metavar="FILE", help="input font to process") parser.add_argument("--out-file", metavar="FILE", help="output font to write", required=True) args = parser.parse_args() configLogger(level=logging.ERROR) merger = merge.Merger() font = merger.merge([args.file1, args.file2]) # Drop incomplete Greek support. unicodes = set(font.getBestCmap().keys()) - set(range(0x0370, 0x03FF)) options = subset.Options() options.set( layout_features="*", layout_scripts=["arab", "latn", "DFLT"], name_IDs="*", name_languages="*", notdef_outline=True, glyph_names=False, recalc_average_width=True, ) subsetter = subset.Subsetter(options=options) subsetter.populate(unicodes=unicodes) subsetter.subset(font) font.save(args.out_file)
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 test_timing_publishes_parts(self): _, fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf") options = subset.Options() options.timing = True subsetter = subset.Subsetter(options) subsetter.populate(text='ABC') font = TTFont(fontpath) with CapturingLogHandler('fontTools.subset.timer', logging.DEBUG) as captor: captor.logger.propagate = False subsetter.subset(font) logs = captor.records captor.logger.propagate = True self.assertTrue(len(logs) > 5) self.assertEqual( len(logs), len([l for l in logs if 'msg' in l.args and 'time' in l.args])) # Look for a few things we know should happen self.assertTrue(filter(lambda l: l.args['msg'] == "load 'cmap'", logs)) self.assertTrue( filter(lambda l: l.args['msg'] == "subset 'cmap'", logs)) self.assertTrue( filter(lambda l: l.args['msg'] == "subset 'glyf'", logs))
def _prune(self, otf): options = subset.Options() options.set(layout_features='*', name_IDs='*', notdef_outline=True, recalc_average_width=True, recalc_bounds=True) subsetter = subset.Subsetter(options=options) subsetter.populate(unicodes=otf['cmap'].getBestCmap().keys()) subsetter.subset(otf)
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 closeGlyphsOverGSUB(gsub, glyphs): """ Use the FontTools subsetter to perform a closure over the GSUB table given the initial `glyphs` (set of glyph names, str). Update the set in-place adding all the glyph names that can be reached via GSUB substitutions from this initial set. """ subsetter = subset.Subsetter() subsetter.glyphs = glyphs gsub.closure_glyphs(subsetter)
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 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 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 subsetFont(otf, unicodes): from fontTools import subset options = subset.Options() options.set(layout_features='*', name_IDs='*', name_languages='*', notdef_outline=True, glyph_names=True) subsetter = subset.Subsetter(options=options) subsetter.populate(unicodes=unicodes) subsetter.subset(otf) return otf
def subset_masters(designspace, subsetDict): from fontTools import subset subset_options = subset.Options(notdef_outline=True, layout_features='*') for ds_source in designspace.sources: key = tuple(ds_source.location.items()) included = set(subsetDict[key]) ttf_font = ds_source.font subsetter = subset.Subsetter(options=subset_options) subsetter.populate(glyphs=included) subsetter.subset(ttf_font) subset_path = f'{os.path.splitext(ds_source.path)[0]}.subset.otf' logger.progress(f'Saving subset font {subset_path}') ttf_font.save(subset_path) ds_source.font = TTFont(subset_path)
def _get_subset_font(source_file_path, text): """ Given a source file and some text, returns a new, in-memory fontTools Font object that has only the glyphs specified in the set. Note that passing actual text instead of a glyph set to the subsetter allows it to generate appropriate ligatures and other features important for correct rendering. """ if not os.path.exists(source_file_path): logging.error("'{}' not found".format(source_file_path)) font = _load_font(source_file_path) subsetter = subset.Subsetter(options=FONT_TOOLS_OPTIONS) subsetter.populate(text=text) subsetter.subset(font) return font
def subsetFontFT(path, unicodes, quran=False): from fontTools import subset font = TTFont(path, recalcTimestamp=False) options = subset.Options() options.set(layout_features='*', name_IDs='*', name_languages='*', notdef_outline=True, glyph_names=True) subsetter = subset.Subsetter(options=options) subsetter.populate(unicodes=unicodes) subsetter.subset(font) if quran: font["OS/2"].sTypoAscender = font["hhea"].ascent = font["head"].yMax font.save(path)
def test_subset_feature_variations_keep_all(featureVarsTestFont): font = featureVarsTestFont options = subset.Options() subsetter = subset.Subsetter(options) subsetter.populate(unicodes=[ord("f"), ord("$")]) subsetter.subset(font) featureTags = { r.FeatureTag for r in font["GSUB"].table.FeatureList.FeatureRecord } # 'dlig' is discretionary so it is dropped by default assert "dlig" not in featureTags assert "f_f" not in font.getGlyphOrder() # 'rvrn' is required so it is kept by default assert "rvrn" in featureTags assert "dollar.rvrn" in font.getGlyphOrder()
def handle_font(font_name): font = TTFont(font_name) td = font['CFF '].cff.topDictIndex[0] no_subrs = lambda fd: hasattr(fd, 'Subrs') and len(fd.Subrs) > 0 priv_subrs = (hasattr(td, 'FDArray') and any(no_subrs(fd) for fd in td.FDArray)) if len(td.GlobalSubrs) > 0 or priv_subrs: print("Warning: There are subrs in %s" % font_name) orig_size = os.path.getsize(font_name) if decompress: from fontTools import subset options = subset.Options() options.desubroutinize = True subsetter = subset.Subsetter(options=options) subsetter.populate(glyphs=font.getGlyphOrder()) subsetter.subset(font) out_name = "%s.compressed%s" % os.path.splitext(font_name) compreff(font, verbose=verbose, **comp_kwargs) # save compressed font start_time = time.time() font.save(out_name) if verbose: print("Compiled and saved (took %gs)" % (time.time() - start_time)) if generate_cff: # save CFF version font['CFF '].cff.compile( open("%s.cff" % os.path.splitext(out_name)[0], 'w'), None) comp_size = os.path.getsize(out_name) print("Compressed to %s -- saved %s" % (os.path.basename(out_name), human_size(orig_size - comp_size))) if check: test_compression_integrity(filename, out_name) test_call_depth(out_name)
def _extract_subset(self, options=None): options = options or subset.Options(layout_closure=False) if not self.is_cff_font: # Have to retain GIDs in the Type2 (non-CFF) case, since we don't # have a predefined character set available (i.e. the ROS ordering # param is 'Identity') # This ensures that the PDF operators we output will keep working # with the subsetted font, at the cost of a slight space overhead in # the output. # This is fine, because fonts with a number of glyphs where this # would matter (i.e. large CJK fonts, basically), are subsetted as # CFF fonts anyway (and based on a predetermined charset), # so this subtlety doesn't apply and the space overhead is very # small. options.retain_gids = True subsetter: subset.Subsetter = subset.Subsetter(options=options) subsetter.populate(gids=list(self._glyphs.keys())) subsetter.subset(self.tt)
def test_subset_feature_variations_drop_all(featureVarsTestFont): font = featureVarsTestFont options = subset.Options() options.layout_features.remove("rvrn") # drop 'rvrn' subsetter = subset.Subsetter(options) subsetter.populate(unicodes=[ord("f"), ord("$")]) subsetter.subset(font) featureTags = { r.FeatureTag for r in font["GSUB"].table.FeatureList.FeatureRecord } glyphs = set(font.getGlyphOrder()) assert "rvrn" not in featureTags assert glyphs == {".notdef", "f", "dollar"} # all FeatureVariationRecords were dropped assert font["GSUB"].table.FeatureVariations is None assert font["GSUB"].table.Version == 0x00010000
def test_subset_feature_variations(): fb = FontBuilder(unitsPerEm=100) fb.setupGlyphOrder([".notdef", "f", "f_f", "dollar", "dollar.rvrn"]) fb.setupCharacterMap({ord("f"): "f", ord("$"): "dollar"}) fb.setupNameTable({ "familyName": "TestFeatureVars", "styleName": "Regular" }) fb.setupPost() fb.setupFvar(axes=[("wght", 100, 400, 900, "Weight")], instances=[]) fb.addOpenTypeFeatures("""\ feature dlig { sub f f by f_f; } dlig; """) fb.addFeatureVariations([([{ "wght": (0.20886, 1.0) }], { "dollar": "dollar.rvrn" })], featureTag="rvrn") buf = io.BytesIO() fb.save(buf) buf.seek(0) font = TTFont(buf) options = subset.Options() subsetter = subset.Subsetter(options) subsetter.populate(unicodes=[ord("f"), ord("$")]) subsetter.subset(font) featureTags = { r.FeatureTag for r in font["GSUB"].table.FeatureList.FeatureRecord } # 'dlig' is discretionary so it is dropped by default assert "dlig" not in featureTags assert "f_f" not in font.getGlyphOrder() # 'rvrn' is required so it is kept by default assert "rvrn" in featureTags assert "dollar.rvrn" in font.getGlyphOrder()
def decompress(ttFont, **kwargs): """ Use the FontTools Subsetter to desubroutinize the font's CFF table. Any keyword arguments are passed on as options to the Subsetter. Skip if the font contains no subroutines. """ if not has_subrs(ttFont): log.debug('No subroutines found; skip decompress') return from fontTools import subset # The FontTools subsetter modifies many tables by default; here # we only want to desubroutinize, so we run the subsetter on a # temporary copy and extract the resulting CFF table from it make_temp = kwargs.pop('make_temp', True) if make_temp: from io import BytesIO from fontTools.ttLib import TTFont, newTable stream = BytesIO() ttFont.save(stream, reorderTables=None) stream.flush() stream.seek(0) tmpfont = TTFont(stream) else: tmpfont = ttFont # run subsetter on the original font options = subset.Options(**kwargs) options.desubroutinize = True options.notdef_outline = True subsetter = subset.Subsetter(options=options) subsetter.populate(glyphs=tmpfont.getGlyphOrder()) subsetter.subset(tmpfont) if make_temp: # copy modified CFF table to original font data = tmpfont['CFF '].compile(tmpfont) table = newTable('CFF ') table.decompile(data, ttFont) ttFont['CFF '] = table tmpfont.close()
def _desubtoutinize(otf): # Desubroutinize with Subsetter (like compreffor does) from io import BytesIO stream = BytesIO() otf.save(stream, reorderTables=False) stream.flush() stream.seek(0) tmpfont = ttLib.TTFont(stream) options = subset.Options() options.desubroutinize = True options.notdef_outline = True subsetter = subset.Subsetter(options=options) subsetter.populate(glyphs=tmpfont.getGlyphOrder()) subsetter.subset(tmpfont) data = tmpfont['CFF '].compile(tmpfont) table = ttLib.newTable('CFF ') table.decompile(data, otf) otf['CFF '] = table tmpfont.close()
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))