Merge pull request #2447 from fonttools/merge-cff-rebased
Merge CFF rebased
This commit is contained in:
commit
0a7164a452
@ -38,6 +38,85 @@ maxStackLimit = 513
|
|||||||
# maxstack operator has been deprecated. max stack is now always 513.
|
# maxstack operator has been deprecated. max stack is now always 513.
|
||||||
|
|
||||||
|
|
||||||
|
class StopHintCountEvent(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler):
|
||||||
|
stop_hintcount_ops = ("op_hintmask", "op_cntrmask", "op_rmoveto", "op_hmoveto",
|
||||||
|
"op_vmoveto")
|
||||||
|
|
||||||
|
def __init__(self, localSubrs, globalSubrs, private=None):
|
||||||
|
psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs,
|
||||||
|
private)
|
||||||
|
|
||||||
|
def execute(self, charString):
|
||||||
|
self.need_hintcount = True # until proven otherwise
|
||||||
|
for op_name in self.stop_hintcount_ops:
|
||||||
|
setattr(self, op_name, self.stop_hint_count)
|
||||||
|
|
||||||
|
if hasattr(charString, '_desubroutinized'):
|
||||||
|
# If a charstring has already been desubroutinized, we will still
|
||||||
|
# need to execute it if we need to count hints in order to
|
||||||
|
# compute the byte length for mask arguments, and haven't finished
|
||||||
|
# counting hints pairs.
|
||||||
|
if self.need_hintcount and self.callingStack:
|
||||||
|
try:
|
||||||
|
psCharStrings.SimpleT2Decompiler.execute(self, charString)
|
||||||
|
except StopHintCountEvent:
|
||||||
|
del self.callingStack[-1]
|
||||||
|
return
|
||||||
|
|
||||||
|
charString._patches = []
|
||||||
|
psCharStrings.SimpleT2Decompiler.execute(self, charString)
|
||||||
|
desubroutinized = charString.program[:]
|
||||||
|
for idx, expansion in reversed(charString._patches):
|
||||||
|
assert idx >= 2
|
||||||
|
assert desubroutinized[idx - 1] in ['callsubr', 'callgsubr'], desubroutinized[idx - 1]
|
||||||
|
assert type(desubroutinized[idx - 2]) == int
|
||||||
|
if expansion[-1] == 'return':
|
||||||
|
expansion = expansion[:-1]
|
||||||
|
desubroutinized[idx-2:idx] = expansion
|
||||||
|
if not self.private.in_cff2:
|
||||||
|
if 'endchar' in desubroutinized:
|
||||||
|
# Cut off after first endchar
|
||||||
|
desubroutinized = desubroutinized[:desubroutinized.index('endchar') + 1]
|
||||||
|
else:
|
||||||
|
if not len(desubroutinized) or desubroutinized[-1] != 'return':
|
||||||
|
desubroutinized.append('return')
|
||||||
|
|
||||||
|
charString._desubroutinized = desubroutinized
|
||||||
|
del charString._patches
|
||||||
|
|
||||||
|
def op_callsubr(self, index):
|
||||||
|
subr = self.localSubrs[self.operandStack[-1]+self.localBias]
|
||||||
|
psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
|
||||||
|
self.processSubr(index, subr)
|
||||||
|
|
||||||
|
def op_callgsubr(self, index):
|
||||||
|
subr = self.globalSubrs[self.operandStack[-1]+self.globalBias]
|
||||||
|
psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
|
||||||
|
self.processSubr(index, subr)
|
||||||
|
|
||||||
|
def stop_hint_count(self, *args):
|
||||||
|
self.need_hintcount = False
|
||||||
|
for op_name in self.stop_hintcount_ops:
|
||||||
|
setattr(self, op_name, None)
|
||||||
|
cs = self.callingStack[-1]
|
||||||
|
if hasattr(cs, '_desubroutinized'):
|
||||||
|
raise StopHintCountEvent()
|
||||||
|
|
||||||
|
def op_hintmask(self, index):
|
||||||
|
psCharStrings.SimpleT2Decompiler.op_hintmask(self, index)
|
||||||
|
if self.need_hintcount:
|
||||||
|
self.stop_hint_count()
|
||||||
|
|
||||||
|
def processSubr(self, index, subr):
|
||||||
|
cs = self.callingStack[-1]
|
||||||
|
if not hasattr(cs, '_desubroutinized'):
|
||||||
|
cs._patches.append((index, subr._desubroutinized))
|
||||||
|
|
||||||
|
|
||||||
class CFFFontSet(object):
|
class CFFFontSet(object):
|
||||||
"""A CFF font "file" can contain more than one font, although this is
|
"""A CFF font "file" can contain more than one font, although this is
|
||||||
extremely rare (and not allowed within OpenType fonts).
|
extremely rare (and not allowed within OpenType fonts).
|
||||||
@ -368,6 +447,35 @@ class CFFFontSet(object):
|
|||||||
file.seek(0)
|
file.seek(0)
|
||||||
self.decompile(file, otFont, isCFF2=True)
|
self.decompile(file, otFont, isCFF2=True)
|
||||||
|
|
||||||
|
def desubroutinize(self):
|
||||||
|
for fontName in self.fontNames:
|
||||||
|
font = self[fontName]
|
||||||
|
cs = font.CharStrings
|
||||||
|
for g in font.charset:
|
||||||
|
c, _ = cs.getItemAndSelector(g)
|
||||||
|
c.decompile()
|
||||||
|
subrs = getattr(c.private, "Subrs", [])
|
||||||
|
decompiler = _DesubroutinizingT2Decompiler(subrs, c.globalSubrs, c.private)
|
||||||
|
decompiler.execute(c)
|
||||||
|
c.program = c._desubroutinized
|
||||||
|
del c._desubroutinized
|
||||||
|
# Delete all the local subrs
|
||||||
|
if hasattr(font, 'FDArray'):
|
||||||
|
for fd in font.FDArray:
|
||||||
|
pd = fd.Private
|
||||||
|
if hasattr(pd, 'Subrs'):
|
||||||
|
del pd.Subrs
|
||||||
|
if 'Subrs' in pd.rawDict:
|
||||||
|
del pd.rawDict['Subrs']
|
||||||
|
else:
|
||||||
|
pd = font.Private
|
||||||
|
if hasattr(pd, 'Subrs'):
|
||||||
|
del pd.Subrs
|
||||||
|
if 'Subrs' in pd.rawDict:
|
||||||
|
del pd.rawDict['Subrs']
|
||||||
|
# as well as the global subrs
|
||||||
|
self.GlobalSubrs.clear()
|
||||||
|
|
||||||
|
|
||||||
class CFFWriter(object):
|
class CFFWriter(object):
|
||||||
"""Helper class for serializing CFF data to binary. Used by
|
"""Helper class for serializing CFF data to binary. Used by
|
||||||
|
@ -13,6 +13,7 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import operator
|
import operator
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger("fontTools.merge")
|
log = logging.getLogger("fontTools.merge")
|
||||||
@ -200,7 +201,7 @@ ttLib.getTableClass('head').mergeMap = {
|
|||||||
'macStyle': first,
|
'macStyle': first,
|
||||||
'lowestRecPPEM': max,
|
'lowestRecPPEM': max,
|
||||||
'fontDirectionHint': lambda lst: 2,
|
'fontDirectionHint': lambda lst: 2,
|
||||||
'indexToLocFormat': recalculate,
|
'indexToLocFormat': first,
|
||||||
'glyphDataFormat': equal,
|
'glyphDataFormat': equal,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,6 +372,51 @@ ttLib.getTableClass('fpgm').mergeMap = lambda self, lst: first(lst)
|
|||||||
ttLib.getTableClass('cvt ').mergeMap = lambda self, lst: first(lst)
|
ttLib.getTableClass('cvt ').mergeMap = lambda self, lst: first(lst)
|
||||||
ttLib.getTableClass('gasp').mergeMap = lambda self, lst: first(lst) # FIXME? Appears irreconcilable
|
ttLib.getTableClass('gasp').mergeMap = lambda self, lst: first(lst) # FIXME? Appears irreconcilable
|
||||||
|
|
||||||
|
@_add_method(ttLib.getTableClass('CFF '))
|
||||||
|
def merge(self, m, tables):
|
||||||
|
if any(hasattr(table, "FDSelect") for table in tables):
|
||||||
|
raise NotImplementedError(
|
||||||
|
"Merging CID-keyed CFF tables is not supported yet"
|
||||||
|
)
|
||||||
|
|
||||||
|
newcff = tables[0]
|
||||||
|
newfont = newcff.cff[0]
|
||||||
|
private = newfont.Private
|
||||||
|
storedNamesStrings = []
|
||||||
|
glyphOrderStrings = []
|
||||||
|
glyphOrder = set(newfont.getGlyphOrder())
|
||||||
|
for name in newfont.strings.strings:
|
||||||
|
if name not in glyphOrder:
|
||||||
|
storedNamesStrings.append(name)
|
||||||
|
else:
|
||||||
|
glyphOrderStrings.append(name)
|
||||||
|
chrset = list(newfont.charset)
|
||||||
|
newcs = newfont.CharStrings
|
||||||
|
log.debug("FONT 0 CharStrings: %d.", len(newcs))
|
||||||
|
for i, table in enumerate(tables[1:], start=1):
|
||||||
|
font = table.cff[0]
|
||||||
|
font.Private = private
|
||||||
|
fontGlyphOrder = set(font.getGlyphOrder())
|
||||||
|
for name in font.strings.strings:
|
||||||
|
if name in fontGlyphOrder:
|
||||||
|
glyphOrderStrings.append(name)
|
||||||
|
cs = font.CharStrings
|
||||||
|
gs = table.cff.GlobalSubrs
|
||||||
|
log.debug("Font %d CharStrings: %d.", i, len(cs))
|
||||||
|
chrset.extend(font.charset)
|
||||||
|
if newcs.charStringsAreIndexed:
|
||||||
|
for i, name in enumerate(cs.charStrings, start=len(newcs)):
|
||||||
|
newcs.charStrings[name] = i
|
||||||
|
newcs.charStringsIndex.items.append(None)
|
||||||
|
for name in cs.charStrings:
|
||||||
|
newcs[name] = cs[name]
|
||||||
|
|
||||||
|
newfont.charset = chrset
|
||||||
|
newfont.numGlyphs = len(chrset)
|
||||||
|
newfont.strings.strings = glyphOrderStrings + storedNamesStrings
|
||||||
|
|
||||||
|
return newcff
|
||||||
|
|
||||||
def _glyphsAreSame(glyphSet1, glyphSet2, glyph1, glyph2):
|
def _glyphsAreSame(glyphSet1, glyphSet2, glyph1, glyph2):
|
||||||
pen1 = DecomposingRecordingPen(glyphSet1)
|
pen1 = DecomposingRecordingPen(glyphSet1)
|
||||||
pen2 = DecomposingRecordingPen(glyphSet2)
|
pen2 = DecomposingRecordingPen(glyphSet2)
|
||||||
@ -865,9 +911,10 @@ class Options(object):
|
|||||||
op = k[-1]+'=' # Ops is '-=' or '+=' now.
|
op = k[-1]+'=' # Ops is '-=' or '+=' now.
|
||||||
k = k[:-1]
|
k = k[:-1]
|
||||||
v = a[i+1:]
|
v = a[i+1:]
|
||||||
|
ok = k
|
||||||
k = k.replace('-', '_')
|
k = k.replace('-', '_')
|
||||||
if not hasattr(self, k):
|
if not hasattr(self, k):
|
||||||
if ignore_unknown is True or k in ignore_unknown:
|
if ignore_unknown is True or ok in ignore_unknown:
|
||||||
ret.append(orig_a)
|
ret.append(orig_a)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@ -993,20 +1040,35 @@ class Merger(object):
|
|||||||
A :class:`fontTools.ttLib.TTFont` object. Call the ``save`` method on
|
A :class:`fontTools.ttLib.TTFont` object. Call the ``save`` method on
|
||||||
this to write it out to an OTF file.
|
this to write it out to an OTF file.
|
||||||
"""
|
"""
|
||||||
mega = ttLib.TTFont()
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Settle on a mega glyph order.
|
# Settle on a mega glyph order.
|
||||||
#
|
#
|
||||||
fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
|
fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
|
||||||
glyphOrders = [font.getGlyphOrder() for font in fonts]
|
glyphOrders = [font.getGlyphOrder() for font in fonts]
|
||||||
megaGlyphOrder = self._mergeGlyphOrders(glyphOrders)
|
megaGlyphOrder = self._mergeGlyphOrders(glyphOrders)
|
||||||
|
|
||||||
|
# Take first input file sfntVersion
|
||||||
|
sfntVersion = fonts[0].sfntVersion
|
||||||
|
|
||||||
|
cffTables = [None] * len(fonts)
|
||||||
|
if sfntVersion == "OTTO":
|
||||||
|
for i, font in enumerate(fonts):
|
||||||
|
font['CFF '].cff.desubroutinize()
|
||||||
|
cffTables[i] = font['CFF ']
|
||||||
|
|
||||||
# Reload fonts and set new glyph names on them.
|
# Reload fonts and set new glyph names on them.
|
||||||
# TODO Is it necessary to reload font? I think it is. At least
|
# TODO Is it necessary to reload font? I think it is. At least
|
||||||
# it's safer, in case tables were loaded to provide glyph names.
|
# it's safer, in case tables were loaded to provide glyph names.
|
||||||
fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
|
fonts = [ttLib.TTFont(fontfile) for fontfile in fontfiles]
|
||||||
for font,glyphOrder in zip(fonts, glyphOrders):
|
for font, glyphOrder, cffTable in zip(fonts, glyphOrders, cffTables):
|
||||||
font.setGlyphOrder(glyphOrder)
|
font.setGlyphOrder(glyphOrder)
|
||||||
|
if cffTable:
|
||||||
|
# Rename CFF CharStrings to match the new glyphOrder.
|
||||||
|
# Using cffTable from before reloading the fonts, because reasons.
|
||||||
|
self._renameCFFCharStrings(glyphOrder, cffTable)
|
||||||
|
font['CFF '] = cffTable
|
||||||
|
|
||||||
|
mega = ttLib.TTFont(sfntVersion=sfntVersion)
|
||||||
mega.setGlyphOrder(megaGlyphOrder)
|
mega.setGlyphOrder(megaGlyphOrder)
|
||||||
|
|
||||||
for font in fonts:
|
for font in fonts:
|
||||||
@ -1064,6 +1126,15 @@ class Merger(object):
|
|||||||
mega[glyphName] = 1
|
mega[glyphName] = 1
|
||||||
return list(mega.keys())
|
return list(mega.keys())
|
||||||
|
|
||||||
|
def _renameCFFCharStrings(self, glyphOrder, cffTable):
|
||||||
|
"""Rename topDictIndex charStrings based on glyphOrder."""
|
||||||
|
td = cffTable.cff.topDictIndex[0]
|
||||||
|
charStrings = {}
|
||||||
|
for i, v in enumerate(td.CharStrings.charStrings.values()):
|
||||||
|
glyphName = glyphOrder[i]
|
||||||
|
charStrings[glyphName] = v
|
||||||
|
cffTable.cff.topDictIndex[0].CharStrings.charStrings = charStrings
|
||||||
|
|
||||||
def mergeObjects(self, returnTable, logic, tables):
|
def mergeObjects(self, returnTable, logic, tables):
|
||||||
# Right now we don't use self at all. Will use in the future
|
# Right now we don't use self at all. Will use in the future
|
||||||
# for options and logging.
|
# for options and logging.
|
||||||
@ -1182,7 +1253,14 @@ def main(args=None):
|
|||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
|
|
||||||
options = Options()
|
options = Options()
|
||||||
args = options.parse_opts(args)
|
args = options.parse_opts(args, ignore_unknown=['output-file'])
|
||||||
|
outfile = 'merged.ttf'
|
||||||
|
fontfiles = []
|
||||||
|
for g in args:
|
||||||
|
if g.startswith('--output-file='):
|
||||||
|
outfile = g[14:]
|
||||||
|
continue
|
||||||
|
fontfiles.append(g)
|
||||||
|
|
||||||
if len(args) < 1:
|
if len(args) < 1:
|
||||||
print("usage: pyftmerge font...", file=sys.stderr)
|
print("usage: pyftmerge font...", file=sys.stderr)
|
||||||
@ -1195,8 +1273,7 @@ def main(args=None):
|
|||||||
timer.logger.disabled = True
|
timer.logger.disabled = True
|
||||||
|
|
||||||
merger = Merger(options=options)
|
merger = Merger(options=options)
|
||||||
font = merger.merge(args)
|
font = merger.merge(fontfiles)
|
||||||
outfile = 'merged.ttf'
|
|
||||||
with timer("compile and save font"):
|
with timer("compile and save font"):
|
||||||
font.save(outfile)
|
font.save(outfile)
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ from fontTools.misc import psCharStrings
|
|||||||
from fontTools import ttLib
|
from fontTools import ttLib
|
||||||
from fontTools.pens.basePen import NullPen
|
from fontTools.pens.basePen import NullPen
|
||||||
from fontTools.misc.roundTools import otRound
|
from fontTools.misc.roundTools import otRound
|
||||||
|
from fontTools.misc.loggingTools import deprecateFunction
|
||||||
from fontTools.varLib.varStore import VarStoreInstancer
|
from fontTools.varLib.varStore import VarStoreInstancer
|
||||||
from fontTools.subset.util import _add_method, _uniq_sort
|
from fontTools.subset.util import _add_method, _uniq_sort
|
||||||
|
|
||||||
@ -345,86 +346,6 @@ class _DehintingT2Decompiler(psCharStrings.T2WidthExtractor):
|
|||||||
|
|
||||||
hints.status = max(hints.status, subr_hints.status)
|
hints.status = max(hints.status, subr_hints.status)
|
||||||
|
|
||||||
class StopHintCountEvent(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler):
|
|
||||||
stop_hintcount_ops = ("op_hintmask", "op_cntrmask", "op_rmoveto", "op_hmoveto",
|
|
||||||
"op_vmoveto")
|
|
||||||
|
|
||||||
def __init__(self, localSubrs, globalSubrs, private=None):
|
|
||||||
psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs,
|
|
||||||
private)
|
|
||||||
|
|
||||||
def execute(self, charString):
|
|
||||||
self.need_hintcount = True # until proven otherwise
|
|
||||||
for op_name in self.stop_hintcount_ops:
|
|
||||||
setattr(self, op_name, self.stop_hint_count)
|
|
||||||
|
|
||||||
if hasattr(charString, '_desubroutinized'):
|
|
||||||
# If a charstring has already been desubroutinized, we will still
|
|
||||||
# need to execute it if we need to count hints in order to
|
|
||||||
# compute the byte length for mask arguments, and haven't finished
|
|
||||||
# counting hints pairs.
|
|
||||||
if self.need_hintcount and self.callingStack:
|
|
||||||
try:
|
|
||||||
psCharStrings.SimpleT2Decompiler.execute(self, charString)
|
|
||||||
except StopHintCountEvent:
|
|
||||||
del self.callingStack[-1]
|
|
||||||
return
|
|
||||||
|
|
||||||
charString._patches = []
|
|
||||||
psCharStrings.SimpleT2Decompiler.execute(self, charString)
|
|
||||||
desubroutinized = charString.program[:]
|
|
||||||
for idx, expansion in reversed(charString._patches):
|
|
||||||
assert idx >= 2
|
|
||||||
assert desubroutinized[idx - 1] in ['callsubr', 'callgsubr'], desubroutinized[idx - 1]
|
|
||||||
assert type(desubroutinized[idx - 2]) == int
|
|
||||||
if expansion[-1] == 'return':
|
|
||||||
expansion = expansion[:-1]
|
|
||||||
desubroutinized[idx-2:idx] = expansion
|
|
||||||
if not self.private.in_cff2:
|
|
||||||
if 'endchar' in desubroutinized:
|
|
||||||
# Cut off after first endchar
|
|
||||||
desubroutinized = desubroutinized[:desubroutinized.index('endchar') + 1]
|
|
||||||
else:
|
|
||||||
if not len(desubroutinized) or desubroutinized[-1] != 'return':
|
|
||||||
desubroutinized.append('return')
|
|
||||||
|
|
||||||
charString._desubroutinized = desubroutinized
|
|
||||||
del charString._patches
|
|
||||||
|
|
||||||
def op_callsubr(self, index):
|
|
||||||
subr = self.localSubrs[self.operandStack[-1]+self.localBias]
|
|
||||||
psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
|
|
||||||
self.processSubr(index, subr)
|
|
||||||
|
|
||||||
def op_callgsubr(self, index):
|
|
||||||
subr = self.globalSubrs[self.operandStack[-1]+self.globalBias]
|
|
||||||
psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
|
|
||||||
self.processSubr(index, subr)
|
|
||||||
|
|
||||||
def stop_hint_count(self, *args):
|
|
||||||
self.need_hintcount = False
|
|
||||||
for op_name in self.stop_hintcount_ops:
|
|
||||||
setattr(self, op_name, None)
|
|
||||||
cs = self.callingStack[-1]
|
|
||||||
if hasattr(cs, '_desubroutinized'):
|
|
||||||
raise StopHintCountEvent()
|
|
||||||
|
|
||||||
def op_hintmask(self, index):
|
|
||||||
psCharStrings.SimpleT2Decompiler.op_hintmask(self, index)
|
|
||||||
if self.need_hintcount:
|
|
||||||
self.stop_hint_count()
|
|
||||||
|
|
||||||
def processSubr(self, index, subr):
|
|
||||||
cs = self.callingStack[-1]
|
|
||||||
if not hasattr(cs, '_desubroutinized'):
|
|
||||||
cs._patches.append((index, subr._desubroutinized))
|
|
||||||
|
|
||||||
|
|
||||||
@_add_method(ttLib.getTableClass('CFF '))
|
@_add_method(ttLib.getTableClass('CFF '))
|
||||||
def prune_post_subset(self, ttfFont, options):
|
def prune_post_subset(self, ttfFont, options):
|
||||||
@ -444,7 +365,7 @@ def prune_post_subset(self, ttfFont, options):
|
|||||||
|
|
||||||
# Desubroutinize if asked for
|
# Desubroutinize if asked for
|
||||||
if options.desubroutinize:
|
if options.desubroutinize:
|
||||||
self.desubroutinize()
|
cff.desubroutinize()
|
||||||
|
|
||||||
# Drop hints if not needed
|
# Drop hints if not needed
|
||||||
if not options.hinting:
|
if not options.hinting:
|
||||||
@ -460,36 +381,11 @@ def _delete_empty_subrs(private_dict):
|
|||||||
del private_dict.rawDict['Subrs']
|
del private_dict.rawDict['Subrs']
|
||||||
del private_dict.Subrs
|
del private_dict.Subrs
|
||||||
|
|
||||||
|
|
||||||
|
@deprecateFunction("use 'CFFFontSet.desubroutinize()' instead", category=DeprecationWarning)
|
||||||
@_add_method(ttLib.getTableClass('CFF '))
|
@_add_method(ttLib.getTableClass('CFF '))
|
||||||
def desubroutinize(self):
|
def desubroutinize(self):
|
||||||
cff = self.cff
|
self.cff.desubroutinize()
|
||||||
for fontname in cff.keys():
|
|
||||||
font = cff[fontname]
|
|
||||||
cs = font.CharStrings
|
|
||||||
for g in font.charset:
|
|
||||||
c, _ = cs.getItemAndSelector(g)
|
|
||||||
c.decompile()
|
|
||||||
subrs = getattr(c.private, "Subrs", [])
|
|
||||||
decompiler = _DesubroutinizingT2Decompiler(subrs, c.globalSubrs, c.private)
|
|
||||||
decompiler.execute(c)
|
|
||||||
c.program = c._desubroutinized
|
|
||||||
del c._desubroutinized
|
|
||||||
# Delete all the local subrs
|
|
||||||
if hasattr(font, 'FDArray'):
|
|
||||||
for fd in font.FDArray:
|
|
||||||
pd = fd.Private
|
|
||||||
if hasattr(pd, 'Subrs'):
|
|
||||||
del pd.Subrs
|
|
||||||
if 'Subrs' in pd.rawDict:
|
|
||||||
del pd.rawDict['Subrs']
|
|
||||||
else:
|
|
||||||
pd = font.Private
|
|
||||||
if hasattr(pd, 'Subrs'):
|
|
||||||
del pd.Subrs
|
|
||||||
if 'Subrs' in pd.rawDict:
|
|
||||||
del pd.rawDict['Subrs']
|
|
||||||
# as well as the global subrs
|
|
||||||
cff.GlobalSubrs.clear()
|
|
||||||
|
|
||||||
|
|
||||||
@_add_method(ttLib.getTableClass('CFF '))
|
@_add_method(ttLib.getTableClass('CFF '))
|
||||||
|
23445
Tests/merge/data/CFFFont1.ttx
Normal file
23445
Tests/merge/data/CFFFont1.ttx
Normal file
File diff suppressed because it is too large
Load Diff
6682
Tests/merge/data/CFFFont2.ttx
Normal file
6682
Tests/merge/data/CFFFont2.ttx
Normal file
File diff suppressed because it is too large
Load Diff
30064
Tests/merge/data/CFFFont_expected.ttx
Normal file
30064
Tests/merge/data/CFFFont_expected.ttx
Normal file
File diff suppressed because it is too large
Load Diff
@ -3,14 +3,82 @@ import itertools
|
|||||||
from fontTools import ttLib
|
from fontTools import ttLib
|
||||||
from fontTools.ttLib.tables._g_l_y_f import Glyph
|
from fontTools.ttLib.tables._g_l_y_f import Glyph
|
||||||
from fontTools.fontBuilder import FontBuilder
|
from fontTools.fontBuilder import FontBuilder
|
||||||
from fontTools.merge import Merger
|
from fontTools.merge import Merger, main as merge_main
|
||||||
|
import difflib
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
import pathlib
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
class MergeIntegrationTest(unittest.TestCase):
|
class MergeIntegrationTest(unittest.TestCase):
|
||||||
# TODO
|
def setUp(self):
|
||||||
pass
|
self.tempdir = None
|
||||||
|
self.num_tempfiles = 0
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
if self.tempdir:
|
||||||
|
shutil.rmtree(self.tempdir)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getpath(testfile):
|
||||||
|
path, _ = os.path.split(__file__)
|
||||||
|
return os.path.join(path, "data", testfile)
|
||||||
|
|
||||||
|
def temp_path(self, suffix):
|
||||||
|
if not self.tempdir:
|
||||||
|
self.tempdir = tempfile.mkdtemp()
|
||||||
|
self.num_tempfiles += 1
|
||||||
|
return os.path.join(self.tempdir, "tmp%d%s" % (self.num_tempfiles, suffix))
|
||||||
|
|
||||||
|
IGNORED_LINES_RE = re.compile(
|
||||||
|
"^(<ttFont | <(checkSumAdjustment|created|modified) ).*"
|
||||||
|
)
|
||||||
|
def read_ttx(self, path):
|
||||||
|
lines = []
|
||||||
|
with open(path, "r", encoding="utf-8") as ttx:
|
||||||
|
for line in ttx.readlines():
|
||||||
|
# Elide lines with data that often change.
|
||||||
|
if self.IGNORED_LINES_RE.match(line):
|
||||||
|
lines.append("\n")
|
||||||
|
else:
|
||||||
|
lines.append(line.rstrip() + "\n")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def expect_ttx(self, font, expected_ttx, tables=None):
|
||||||
|
path = self.temp_path(suffix=".ttx")
|
||||||
|
font.saveXML(path, tables=tables)
|
||||||
|
actual = self.read_ttx(path)
|
||||||
|
expected = self.read_ttx(expected_ttx)
|
||||||
|
if actual != expected:
|
||||||
|
for line in difflib.unified_diff(
|
||||||
|
expected, actual, fromfile=expected_ttx, tofile=path):
|
||||||
|
sys.stdout.write(line)
|
||||||
|
self.fail("TTX output is different from expected")
|
||||||
|
|
||||||
|
def compile_font(self, path, suffix):
|
||||||
|
savepath = self.temp_path(suffix=suffix)
|
||||||
|
font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
|
||||||
|
font.importXML(path)
|
||||||
|
font.save(savepath, reorderTables=None)
|
||||||
|
return font, savepath
|
||||||
|
|
||||||
|
# -----
|
||||||
|
# Tests
|
||||||
|
# -----
|
||||||
|
|
||||||
|
def test_merge_cff(self):
|
||||||
|
_, fontpath1 = self.compile_font(self.getpath("CFFFont1.ttx"), ".otf")
|
||||||
|
_, fontpath2 = self.compile_font(self.getpath("CFFFont2.ttx"), ".otf")
|
||||||
|
mergedpath = self.temp_path(".otf")
|
||||||
|
merge_main([fontpath1, fontpath2, "--output-file=%s" % mergedpath])
|
||||||
|
mergedfont = ttLib.TTFont(mergedpath)
|
||||||
|
self.expect_ttx(mergedfont, self.getpath("CFFFont_expected.ttx"))
|
||||||
|
|
||||||
|
|
||||||
class gaspMergeUnitTest(unittest.TestCase):
|
class gaspMergeUnitTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
Loading…
x
Reference in New Issue
Block a user