def test_nestedComponents_interpolatable(self, FontClass): ufos = [ FontClass(getpath("NestedComponents-Regular.ufo")), FontClass(getpath("NestedComponents-Bold.ufo")), ] ttfs = compileInterpolatableTTFs(ufos) for ttf in ttfs: assert ttf["maxp"].maxComponentDepth != 1 ttfs = compileInterpolatableTTFs(ufos, flattenComponents=True) for ttf in ttfs: assert ttf["maxp"].maxComponentDepth == 1
def test_custom_layer_compilation_interpolatable(layertestrgufo, layertestbdufo): ufo1 = layertestrgufo ufo2 = layertestbdufo master_ttfs = list( compileInterpolatableTTFs([ufo1, ufo1, ufo2], layerNames=[None, "Medium", None])) assert master_ttfs[0].getGlyphOrder() == [ ".notdef", "a", "e", "s", "dotabovecomb", "edotabove", ] assert master_ttfs[1].getGlyphOrder() == [".notdef", "e"] assert master_ttfs[2].getGlyphOrder() == [ ".notdef", "a", "e", "s", "dotabovecomb", "edotabove", ] sparse_tables = [ tag for tag in master_ttfs[1].keys() if tag != "GlyphOrder" ] assert SPARSE_TTF_MASTER_TABLES.issuperset(sparse_tables)
def test_skip_export_glyphs_multi_ufo(self, FontClass): # Bold has a public.skipExportGlyphs lib key excluding "b", "d" and "f". ufo1 = FontClass(getpath("IncompatibleMasters/NewFont-Regular.ufo")) ufo2 = FontClass(getpath("IncompatibleMasters/NewFont-Bold.ufo")) fonts = ufo2ft.compileInterpolatableTTFs([ufo1, ufo2], inplace=True) for font in fonts: assert set(font.getGlyphOrder()) == {".notdef", "a", "c", "e"} gpos_table = font["GPOS"].table assert gpos_table.LookupList.Lookup[0].SubTable[0].Coverage.glyphs == ["a"] glyphs = font["glyf"].glyphs for g in glyphs.values(): g.expand(font["glyf"]) assert glyphs["a"].numberOfContours == 1 assert not hasattr(glyphs["a"], "components") assert glyphs["c"].numberOfContours == 6 assert not hasattr(glyphs["c"], "components") assert glyphs["e"].numberOfContours == 13 assert not hasattr(glyphs["e"], "components")
def test_compileInterpolatableTTFs(self, FontClass): ufos = [ FontClass(getpath("NestedComponents-Regular.ufo")), FontClass(getpath("NestedComponents-Bold.ufo")), ] filters = [TransformationsFilter(OffsetY=10)] ttfs = compileInterpolatableTTFs(ufos, filters=filters) for i, ttf in enumerate(ttfs): glyph = ufos[i]["a"] pen1 = BoundsPen(ufos[i]) glyph.draw(pen1) glyphSet = ttf.getGlyphSet() tt_glyph = glyphSet["uni0061"] pen2 = BoundsPen(glyphSet) tt_glyph.draw(pen2) assert pen1.bounds[0] == pen2.bounds[0] assert pen1.bounds[1] + 10 == pen2.bounds[1] assert pen1.bounds[2] == pen2.bounds[2] assert pen1.bounds[3] + 10 == pen2.bounds[3]
def test_interpolatableTTFs_lazy(self, FontClass): # two same UFOs **must** be interpolatable ufos = [FontClass(getpath("TestFont.ufo")) for _ in range(2)] ttfs = list(compileInterpolatableTTFs(ufos)) expectTTX(ttfs[0], "TestFont.ttx") expectTTX(ttfs[1], "TestFont.ttx")
def save_otfs( self, ufos, ttf=False, is_instance=False, interpolatable=False, autohint=None, subset=None, use_production_names=None, subroutinize=None, # deprecated optimize_cff=CFFOptimization.NONE, cff_round_tolerance=None, remove_overlaps=True, overlaps_backend=None, reverse_direction=True, conversion_error=None, feature_writers=None, interpolate_layout_from=None, interpolate_layout_dir=None, output_path=None, output_dir=None, inplace=True, ): """Build OpenType binaries from UFOs. Args: ufos: Font objects to compile. ttf: If True, build fonts with TrueType outlines and .ttf extension. is_instance: If output fonts are instances, for generating paths. interpolatable: If output is interpolatable, for generating paths. autohint: Parameters to provide to ttfautohint. If not provided, the autohinting step is skipped. subset: Whether to subset the output according to data in the UFOs. If not provided, also determined by flags in the UFOs. use_production_names: Whether to use production glyph names in the output. If not provided, determined by flags in the UFOs. subroutinize: If True, subroutinize CFF outlines in output. cff_round_tolerance (float): controls the rounding of point coordinates in CFF table. It is defined as the maximum absolute difference between the original float and the rounded integer value. By default, all floats are rounded to integer (tolerance 0.5); a value of 0 completely disables rounding; values in between only round floats which are close to their integral part within the tolerated range. Ignored if ttf=True. remove_overlaps: If True, remove overlaps in glyph shapes. overlaps_backend: name of the library to remove overlaps. Can be either "booleanOperations" (default) or "pathops". reverse_direction: If True, reverse contour directions when compiling TrueType outlines. conversion_error: Error to allow when converting cubic CFF contours to quadratic TrueType contours. feature_writers: list of ufo2ft-compatible feature writer classes or pre-initialized objects that are passed on to ufo2ft feature compiler to generate automatic feature code. The default value (None) means that ufo2ft will use its built-in default feature writers (for kern, mark, mkmk, etc.). An empty list ([]) will skip any automatic feature generation. interpolate_layout_from: A DesignSpaceDocument object to give varLib for interpolating layout tables to use in output. interpolate_layout_dir: Directory containing the compiled master fonts to use for interpolating binary layout tables. output_path: output font file path. Only works when the input 'ufos' list contains a single font. output_dir: directory where to save output files. Mutually exclusive with 'output_path' argument. """ assert not (output_path and output_dir), "mutually exclusive args" if output_path is not None and len(ufos) > 1: raise ValueError("output_path requires a single input") if subroutinize is not None: import warnings warnings.warn( "the 'subroutinize' argument is deprecated, use 'optimize_cff'", UserWarning, ) if subroutinize: optimize_cff = CFFOptimization.SUBROUTINIZE else: # for b/w compatibility, we still run the charstring specializer # even when --no-subroutinize is used. Use the new --optimize-cff # option to disable both specilization and subroutinization optimize_cff = CFFOptimization.SPECIALIZE ext = "ttf" if ttf else "otf" if interpolate_layout_from is not None: if interpolate_layout_dir is None: interpolate_layout_dir = self._output_dir( ext, is_instance=False, interpolatable=interpolatable) finder = partial(_varLib_finder, directory=interpolate_layout_dir, ext=ext) # no need to generate automatic features in ufo2ft, since here we # are interpolating precompiled GPOS table with fontTools.varLib. # An empty 'featureWriters' list tells ufo2ft to not generate any # automatic features. # TODO: Add an argument to ufo2ft.compileOTF/compileTTF to # completely skip compiling features into OTL tables feature_writers = [] compiler_options = dict( useProductionNames=use_production_names, reverseDirection=reverse_direction, cubicConversionError=conversion_error, featureWriters=feature_writers, inplace=True, # avoid extra copy ) if interpolatable: if not ttf: raise NotImplementedError( "interpolatable CFF not supported yet") logger.info("Building interpolation-compatible TTFs") fonts = ufo2ft.compileInterpolatableTTFs(ufos, **compiler_options) else: fonts = self._iter_compile( ufos, ttf, removeOverlaps=remove_overlaps, overlapsBackend=overlaps_backend, optimizeCFF=optimize_cff, roundTolerance=cff_round_tolerance, **compiler_options, ) do_autohint = ttf and autohint is not None for font, ufo in zip(fonts, ufos): if interpolate_layout_from is not None: master_locations, instance_locations = self._designspace_locations( interpolate_layout_from) loc = instance_locations[_normpath(ufo.path)] gpos_src = interpolate_layout(interpolate_layout_from, loc, finder, mapped=True) font["GPOS"] = gpos_src["GPOS"] gsub_src = TTFont( finder(self._closest_location(master_locations, loc))) if "GDEF" in gsub_src: font["GDEF"] = gsub_src["GDEF"] if "GSUB" in gsub_src: font["GSUB"] = gsub_src["GSUB"] if do_autohint: # if we are autohinting, we save the unhinted font to a # temporary path, and the hinted one to the final destination fd, otf_path = tempfile.mkstemp("." + ext) os.close(fd) elif output_path is None: otf_path = self._output_path(ufo, ext, is_instance, interpolatable, output_dir=output_dir) else: otf_path = output_path logger.info("Saving %s", otf_path) font.save(otf_path) # 'subset' is an Optional[bool], can be None, True or False. # When False, we never subset; when True, we always do; when # None (default), we check the presence of custom parameters if subset is False: pass elif subset is True or ( (KEEP_GLYPHS_OLD_KEY in ufo.lib or KEEP_GLYPHS_NEW_KEY in ufo.lib) or any( glyph.lib.get(GLYPH_EXPORT_KEY, True) is False for glyph in ufo)): self.subset_otf_from_ufo(otf_path, ufo) if not do_autohint: continue if output_path is not None: hinted_otf_path = output_path else: hinted_otf_path = self._output_path( ufo, ext, is_instance, interpolatable, autohinted=True, output_dir=output_dir, ) try: ttfautohint(otf_path, hinted_otf_path, args=autohint) except TTFAError: # copy unhinted font to destination before re-raising error shutil.copyfile(otf_path, hinted_otf_path) raise finally: # must clean up temp file os.remove(otf_path)
def test_interpolatableTTFs_lazy(self): # two same UFOs **must** be interpolatable ufos = [loadUFO("TestFont.ufo") for _ in range(2)] ttfs = list(compileInterpolatableTTFs(ufos)) self.expectTTX(ttfs[0], "TestFont.ttx") self.expectTTX(ttfs[1], "TestFont.ttx")