summaryrefslogtreecommitdiff
path: root/misc/fontbuildlib/builder.py
diff options
context:
space:
mode:
authorRasmus Andersson <rasmus@notion.se>2019-10-23 03:00:58 +0300
committerRasmus Andersson <rasmus@notion.se>2019-10-23 03:00:58 +0300
commitaa7ad2d7a0e255d4052c4999ec62d88c699586ac (patch)
tree54e8c771f6b0c5c09cedc3da4d9173c48968e580 /misc/fontbuildlib/builder.py
parent9c444dededcb0787d562072a08c909ec463ea4b7 (diff)
downloadinter-aa7ad2d7a0e255d4052c4999ec62d88c699586ac.tar.xz
fontbuild: remove use of fontmake, simplifying things.
Diffstat (limited to 'misc/fontbuildlib/builder.py')
-rw-r--r--misc/fontbuildlib/builder.py149
1 files changed, 149 insertions, 0 deletions
diff --git a/misc/fontbuildlib/builder.py b/misc/fontbuildlib/builder.py
new file mode 100644
index 000000000..98166d5ac
--- /dev/null
+++ b/misc/fontbuildlib/builder.py
@@ -0,0 +1,149 @@
+import logging
+import ufo2ft
+from defcon import Font
+from ufo2ft.util import _LazyFontName
+from ufo2ft.filters.removeOverlaps import RemoveOverlapsFilter
+from fontTools.designspaceLib import DesignSpaceDocument
+from .name import getFamilyName, setFullName
+from .info import updateFontVersion
+from .glyph import findGlyphDirectives, composedGlyphIsTrivial, decomposeGlyphs
+
+log = logging.getLogger(__name__)
+
+
+class FontBuilder:
+ # def __init__(self, *args, **kwargs)
+
+ def buildStatic(self,
+ ufo, # input UFO as filename string or defcon.Font object
+ outputFilename, # output filename string
+ cff=True, # true = makes CFF outlines. false = makes TTF outlines.
+ **kwargs, # passed along to ufo2ft.compile*()
+ ):
+ if isinstance(ufo, str):
+ ufo = Font(ufo)
+
+ # update version to actual, real version. Must come after any call to setFontInfo.
+ updateFontVersion(ufo, dummy=False, isVF=False)
+
+ compilerOptions = dict(
+ useProductionNames=True,
+ inplace=True, # avoid extra copy
+ removeOverlaps=True,
+ overlapsBackend='pathops', # use Skia's pathops
+ )
+
+ log.info("compiling %s -> %s (%s)", _LazyFontName(ufo), outputFilename,
+ "OTF/CFF-2" if cff else "TTF")
+
+ if cff:
+ font = ufo2ft.compileOTF(ufo, **compilerOptions)
+ else: # ttf
+ font = ufo2ft.compileTTF(ufo, **compilerOptions)
+
+ log.debug("writing %s", outputFilename)
+ font.save(outputFilename)
+
+
+
+ def buildVariable(self,
+ designspace, # designspace filename string or DesignSpaceDocument object
+ outputFilename, # output filename string
+ cff=False, # if true, builds CFF-2 font, else TTF
+ **kwargs, # passed along to ufo2ft.compileVariable*()
+ ):
+ designspace = self._loadDesignspace(designspace)
+
+ # check in the designspace's <lib> element if user supplied a custom featureWriters
+ # configuration; if so, use that for all the UFOs built from this designspace.
+ featureWriters = None
+ if ufo2ft.featureWriters.FEATURE_WRITERS_KEY in designspace.lib:
+ featureWriters = ufo2ft.featureWriters.loadFeatureWriters(designspace)
+
+ compilerOptions = dict(
+ useProductionNames=True,
+ featureWriters=featureWriters,
+ inplace=True, # avoid extra copy
+ **kwargs
+ )
+
+ if log.isEnabledFor(logging.INFO):
+ log.info("compiling %s -> %s (%s)", designspace.path, outputFilename,
+ "OTF/CFF-2" if cff else "TTF")
+
+ if cff:
+ font = ufo2ft.compileVariableCFF2(designspace, **compilerOptions)
+ else:
+ font = ufo2ft.compileVariableTTF(designspace, **compilerOptions)
+
+ # Rename fullName record to familyName (VF only).
+ # Note: Even though we set openTypeNameCompatibleFullName it seems that the fullName
+ # record is still computed by fonttools, so we override it here.
+ setFullName(font, getFamilyName(font))
+
+ log.debug("writing %s", outputFilename)
+ font.save(outputFilename)
+
+
+
+ @staticmethod
+ def _loadDesignspace(designspace):
+ log.info("loading designspace sources")
+ if isinstance(designspace, str):
+ designspace = DesignSpaceDocument.fromfile(designspace)
+ else:
+ # copy that we can mess with
+ designspace = DesignSpaceDocument.fromfile(designspace.path)
+
+ masters = designspace.loadSourceFonts(opener=Font)
+ # masters = [s.font for s in designspace.sources] # list of UFO font objects
+
+ # Update the default source's full name to not include style name
+ defaultFont = designspace.default.font
+ defaultFont.info.openTypeNameCompatibleFullName = defaultFont.info.familyName
+
+ for ufo in masters:
+ # update font version
+ updateFontVersion(ufo, dummy=False, isVF=True)
+
+ log.info("Preprocessing glyphs")
+ # find glyphs subject to decomposition and/or overlap removal
+ # TODO: Find out why this loop is SO DAMN SLOW. It might just be so that defcon is
+ # really slow when reading glyphs. Perhaps we can sidestep defcon and just
+ # read & parse the .glif files ourselves.
+ glyphNamesToDecompose = set() # glyph names
+ glyphsToRemoveOverlaps = set() # glyph objects
+ for ufo in masters:
+ for g in ufo:
+ if g.components and not composedGlyphIsTrivial(g):
+ glyphNamesToDecompose.add(g.name)
+ if 'removeoverlap' in findGlyphDirectives(g.note):
+ if g.components and len(g.components) > 0:
+ glyphNamesToDecompose.add(g.name)
+ glyphsToRemoveOverlaps.add(g)
+
+ # decompose
+ if glyphNamesToDecompose:
+ if log.isEnabledFor(logging.DEBUG):
+ log.debug('Decomposing glyphs:\n %s', "\n ".join(glyphNamesToDecompose))
+ elif log.isEnabledFor(logging.INFO):
+ log.info('Decomposing %d glyphs', len(glyphNamesToDecompose))
+ decomposeGlyphs(masters, glyphNamesToDecompose)
+
+ # remove overlaps
+ if glyphsToRemoveOverlaps:
+ rmoverlapFilter = RemoveOverlapsFilter(backend='pathops')
+ rmoverlapFilter.start()
+ if log.isEnabledFor(logging.DEBUG):
+ log.debug(
+ 'Removing overlaps in glyphs:\n %s',
+ "\n ".join(set([g.name for g in glyphsToRemoveOverlaps])),
+ )
+ elif log.isEnabledFor(logging.INFO):
+ log.info('Removing overlaps in %d glyphs', len(glyphsToRemoveOverlaps))
+ for g in glyphsToRemoveOverlaps:
+ rmoverlapFilter.filter(g)
+
+ # handle control back to fontmake
+ return designspace
+