You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			249 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			249 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
# Copyright 2013 Google, Inc. All Rights Reserved.
 | 
						|
#
 | 
						|
# Google Author(s): Behdad Esfahbod, Roozbeh Pournader
 | 
						|
 | 
						|
from fontTools import ttLib
 | 
						|
import fontTools.merge.base
 | 
						|
from fontTools.merge.cmap import (
 | 
						|
    computeMegaGlyphOrder,
 | 
						|
    computeMegaCmap,
 | 
						|
    renameCFFCharStrings,
 | 
						|
)
 | 
						|
from fontTools.merge.layout import layoutPreMerge, layoutPostMerge
 | 
						|
from fontTools.merge.options import Options
 | 
						|
import fontTools.merge.tables
 | 
						|
from fontTools.misc.loggingTools import Timer
 | 
						|
from functools import reduce
 | 
						|
import sys
 | 
						|
import logging
 | 
						|
 | 
						|
 | 
						|
log = logging.getLogger("fontTools.merge")
 | 
						|
timer = Timer(logger=logging.getLogger(__name__ + ".timer"), level=logging.INFO)
 | 
						|
 | 
						|
 | 
						|
class Merger(object):
 | 
						|
    """Font merger.
 | 
						|
 | 
						|
    This class merges multiple files into a single OpenType font, taking into
 | 
						|
    account complexities such as OpenType layout (``GSUB``/``GPOS``) tables and
 | 
						|
    cross-font metrics (for example ``hhea.ascent`` is set to the maximum value
 | 
						|
    across all the fonts).
 | 
						|
 | 
						|
    If multiple glyphs map to the same Unicode value, and the glyphs are considered
 | 
						|
    sufficiently different (that is, they differ in any of paths, widths, or
 | 
						|
    height), then subsequent glyphs are renamed and a lookup in the ``locl``
 | 
						|
    feature will be created to disambiguate them. For example, if the arguments
 | 
						|
    are an Arabic font and a Latin font and both contain a set of parentheses,
 | 
						|
    the Latin glyphs will be renamed to ``parenleft.1`` and ``parenright.1``,
 | 
						|
    and a lookup will be inserted into the to ``locl`` feature (creating it if
 | 
						|
    necessary) under the ``latn`` script to substitute ``parenleft`` with
 | 
						|
    ``parenleft.1`` etc.
 | 
						|
 | 
						|
    Restrictions:
 | 
						|
 | 
						|
    - All fonts must have the same units per em.
 | 
						|
    - If duplicate glyph disambiguation takes place as described above then the
 | 
						|
      fonts must have a ``GSUB`` table.
 | 
						|
 | 
						|
    Attributes:
 | 
						|
            options: Currently unused.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, options=None):
 | 
						|
        if not options:
 | 
						|
            options = Options()
 | 
						|
 | 
						|
        self.options = options
 | 
						|
 | 
						|
    def _openFonts(self, fontfiles):
 | 
						|
        fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
 | 
						|
        for font, fontfile in zip(fonts, fontfiles):
 | 
						|
            font._merger__fontfile = fontfile
 | 
						|
            font._merger__name = font["name"].getDebugName(4)
 | 
						|
        return fonts
 | 
						|
 | 
						|
    def merge(self, fontfiles):
 | 
						|
        """Merges fonts together.
 | 
						|
 | 
						|
        Args:
 | 
						|
                fontfiles: A list of file names to be merged
 | 
						|
 | 
						|
        Returns:
 | 
						|
                A :class:`fontTools.ttLib.TTFont` object. Call the ``save`` method on
 | 
						|
                this to write it out to an OTF file.
 | 
						|
        """
 | 
						|
        #
 | 
						|
        # Settle on a mega glyph order.
 | 
						|
        #
 | 
						|
        fonts = self._openFonts(fontfiles)
 | 
						|
        glyphOrders = [list(font.getGlyphOrder()) for font in fonts]
 | 
						|
        computeMegaGlyphOrder(self, glyphOrders)
 | 
						|
 | 
						|
        # Take first input file sfntVersion
 | 
						|
        sfntVersion = fonts[0].sfntVersion
 | 
						|
 | 
						|
        # Reload fonts and set new glyph names on them.
 | 
						|
        fonts = self._openFonts(fontfiles)
 | 
						|
        for font, glyphOrder in zip(fonts, glyphOrders):
 | 
						|
            font.setGlyphOrder(glyphOrder)
 | 
						|
            if "CFF " in font:
 | 
						|
                renameCFFCharStrings(self, glyphOrder, font["CFF "])
 | 
						|
 | 
						|
        cmaps = [font["cmap"] for font in fonts]
 | 
						|
        self.duplicateGlyphsPerFont = [{} for _ in fonts]
 | 
						|
        computeMegaCmap(self, cmaps)
 | 
						|
 | 
						|
        mega = ttLib.TTFont(sfntVersion=sfntVersion)
 | 
						|
        mega.setGlyphOrder(self.glyphOrder)
 | 
						|
 | 
						|
        for font in fonts:
 | 
						|
            self._preMerge(font)
 | 
						|
 | 
						|
        self.fonts = fonts
 | 
						|
 | 
						|
        allTags = reduce(set.union, (list(font.keys()) for font in fonts), set())
 | 
						|
        allTags.remove("GlyphOrder")
 | 
						|
 | 
						|
        for tag in sorted(allTags):
 | 
						|
            if tag in self.options.drop_tables:
 | 
						|
                continue
 | 
						|
 | 
						|
            with timer("merge '%s'" % tag):
 | 
						|
                tables = [font.get(tag, NotImplemented) for font in fonts]
 | 
						|
 | 
						|
                log.info("Merging '%s'.", tag)
 | 
						|
                clazz = ttLib.getTableClass(tag)
 | 
						|
                table = clazz(tag).merge(self, tables)
 | 
						|
                # XXX Clean this up and use:  table = mergeObjects(tables)
 | 
						|
 | 
						|
                if table is not NotImplemented and table is not False:
 | 
						|
                    mega[tag] = table
 | 
						|
                    log.info("Merged '%s'.", tag)
 | 
						|
                else:
 | 
						|
                    log.info("Dropped '%s'.", tag)
 | 
						|
 | 
						|
        del self.duplicateGlyphsPerFont
 | 
						|
        del self.fonts
 | 
						|
 | 
						|
        self._postMerge(mega)
 | 
						|
 | 
						|
        return mega
 | 
						|
 | 
						|
    def mergeObjects(self, returnTable, logic, tables):
 | 
						|
        # Right now we don't use self at all.  Will use in the future
 | 
						|
        # for options and logging.
 | 
						|
 | 
						|
        allKeys = set.union(
 | 
						|
            set(),
 | 
						|
            *(vars(table).keys() for table in tables if table is not NotImplemented),
 | 
						|
        )
 | 
						|
        for key in allKeys:
 | 
						|
            log.info(" %s", key)
 | 
						|
            try:
 | 
						|
                mergeLogic = logic[key]
 | 
						|
            except KeyError:
 | 
						|
                try:
 | 
						|
                    mergeLogic = logic["*"]
 | 
						|
                except KeyError:
 | 
						|
                    raise Exception(
 | 
						|
                        "Don't know how to merge key %s of class %s"
 | 
						|
                        % (key, returnTable.__class__.__name__)
 | 
						|
                    )
 | 
						|
            if mergeLogic is NotImplemented:
 | 
						|
                continue
 | 
						|
            value = mergeLogic(getattr(table, key, NotImplemented) for table in tables)
 | 
						|
            if value is not NotImplemented:
 | 
						|
                setattr(returnTable, key, value)
 | 
						|
 | 
						|
        return returnTable
 | 
						|
 | 
						|
    def _preMerge(self, font):
 | 
						|
        layoutPreMerge(font)
 | 
						|
 | 
						|
    def _postMerge(self, font):
 | 
						|
        layoutPostMerge(font)
 | 
						|
 | 
						|
        if "OS/2" in font:
 | 
						|
            # https://github.com/fonttools/fonttools/issues/2538
 | 
						|
            # TODO: Add an option to disable this?
 | 
						|
            font["OS/2"].recalcAvgCharWidth(font)
 | 
						|
 | 
						|
 | 
						|
__all__ = ["Options", "Merger", "main"]
 | 
						|
 | 
						|
 | 
						|
@timer("make one with everything (TOTAL TIME)")
 | 
						|
def main(args=None):
 | 
						|
    """Merge multiple fonts into one"""
 | 
						|
    from fontTools import configLogger
 | 
						|
 | 
						|
    if args is None:
 | 
						|
        args = sys.argv[1:]
 | 
						|
 | 
						|
    options = Options()
 | 
						|
    args = options.parse_opts(args)
 | 
						|
    fontfiles = []
 | 
						|
    if options.input_file:
 | 
						|
        with open(options.input_file) as inputfile:
 | 
						|
            fontfiles = [
 | 
						|
                line.strip()
 | 
						|
                for line in inputfile.readlines()
 | 
						|
                if not line.lstrip().startswith("#")
 | 
						|
            ]
 | 
						|
    for g in args:
 | 
						|
        fontfiles.append(g)
 | 
						|
 | 
						|
    if len(fontfiles) < 1:
 | 
						|
        print(
 | 
						|
            "usage: fonttools merge [font1 ... fontN] [--input-file=filelist.txt] [--output-file=merged.ttf] [--import-file=tables.ttx]",
 | 
						|
            file=sys.stderr,
 | 
						|
        )
 | 
						|
        print(
 | 
						|
            "                                   [--drop-tables=tags] [--verbose] [--timing]",
 | 
						|
            file=sys.stderr,
 | 
						|
        )
 | 
						|
        print("", file=sys.stderr)
 | 
						|
        print(" font1 ... fontN              Files to merge.", file=sys.stderr)
 | 
						|
        print(
 | 
						|
            " --input-file=<filename>      Read files to merge from a text file, each path new line. # Comment lines allowed.",
 | 
						|
            file=sys.stderr,
 | 
						|
        )
 | 
						|
        print(
 | 
						|
            " --output-file=<filename>     Specify output file name (default: merged.ttf).",
 | 
						|
            file=sys.stderr,
 | 
						|
        )
 | 
						|
        print(
 | 
						|
            " --import-file=<filename>     TTX file to import after merging. This can be used to set metadata.",
 | 
						|
            file=sys.stderr,
 | 
						|
        )
 | 
						|
        print(
 | 
						|
            " --drop-tables=<table tags>   Comma separated list of table tags to skip, case sensitive.",
 | 
						|
            file=sys.stderr,
 | 
						|
        )
 | 
						|
        print(
 | 
						|
            " --verbose                    Output progress information.",
 | 
						|
            file=sys.stderr,
 | 
						|
        )
 | 
						|
        print(" --timing                     Output progress timing.", file=sys.stderr)
 | 
						|
        return 1
 | 
						|
 | 
						|
    configLogger(level=logging.INFO if options.verbose else logging.WARNING)
 | 
						|
    if options.timing:
 | 
						|
        timer.logger.setLevel(logging.DEBUG)
 | 
						|
    else:
 | 
						|
        timer.logger.disabled = True
 | 
						|
 | 
						|
    merger = Merger(options=options)
 | 
						|
    font = merger.merge(fontfiles)
 | 
						|
 | 
						|
    if options.import_file:
 | 
						|
        font.importXML(options.import_file)
 | 
						|
 | 
						|
    with timer("compile and save font"):
 | 
						|
        font.save(options.output_file)
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    sys.exit(main())
 |