def test_options(self): # https://github.com/behdad/fonttools/issues/413 opt1 = subset.Options() self.assertTrue('Xyz-' not in opt1.layout_features) opt2 = subset.Options() opt2.layout_features.append('Xyz-') self.assertTrue('Xyz-' in opt2.layout_features) self.assertTrue('Xyz-' not in opt1.layout_features)
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 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 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 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 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 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 _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 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 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 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 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 test_no_hinting_TTF(self): _, fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf") subsetpath = self.temp_path(".ttf") subset.main([fontpath, "--no-hinting", "--notdef-outline", "--output-file=%s" % subsetpath, "*"]) subsetfont = TTFont(subsetpath) self.expect_ttx(subsetfont, self.getpath( "expect_no_hinting_TTF.ttx"), ["glyf", "maxp"]) for tag in subset.Options().hinting_tables: self.assertTrue(tag not in subsetfont)
def _get_default_options(): 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 return 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 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 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 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()