aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/fonttools/fontTools/cffLib/transforms.py
diff options
context:
space:
mode:
authorrobot-piglet <robot-piglet@yandex-team.com>2024-06-08 08:51:43 +0300
committerrobot-piglet <robot-piglet@yandex-team.com>2024-06-08 09:02:27 +0300
commitc3763910c6f878b17102c94e0b62cab57c9954b8 (patch)
tree19de35706beeb1adc158f13ca87110e110f1a753 /contrib/python/fonttools/fontTools/cffLib/transforms.py
parent63965219325c32d9b72b466b89fc613295746336 (diff)
downloadydb-c3763910c6f878b17102c94e0b62cab57c9954b8.tar.gz
Intermediate changes
Diffstat (limited to 'contrib/python/fonttools/fontTools/cffLib/transforms.py')
-rw-r--r--contrib/python/fonttools/fontTools/cffLib/transforms.py482
1 files changed, 482 insertions, 0 deletions
diff --git a/contrib/python/fonttools/fontTools/cffLib/transforms.py b/contrib/python/fonttools/fontTools/cffLib/transforms.py
new file mode 100644
index 0000000000..0772d85e21
--- /dev/null
+++ b/contrib/python/fonttools/fontTools/cffLib/transforms.py
@@ -0,0 +1,482 @@
+from fontTools.misc.psCharStrings import (
+ SimpleT2Decompiler,
+ T2WidthExtractor,
+ calcSubrBias,
+)
+
+
+def _uniq_sort(l):
+ return sorted(set(l))
+
+
+class StopHintCountEvent(Exception):
+ pass
+
+
+class _DesubroutinizingT2Decompiler(SimpleT2Decompiler):
+ stop_hintcount_ops = (
+ "op_hintmask",
+ "op_cntrmask",
+ "op_rmoveto",
+ "op_hmoveto",
+ "op_vmoveto",
+ )
+
+ def __init__(self, localSubrs, globalSubrs, private=None):
+ 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:
+ SimpleT2Decompiler.execute(self, charString)
+ except StopHintCountEvent:
+ del self.callingStack[-1]
+ return
+
+ charString._patches = []
+ 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
+ ]
+
+ charString._desubroutinized = desubroutinized
+ del charString._patches
+
+ def op_callsubr(self, index):
+ subr = self.localSubrs[self.operandStack[-1] + self.localBias]
+ SimpleT2Decompiler.op_callsubr(self, index)
+ self.processSubr(index, subr)
+
+ def op_callgsubr(self, index):
+ subr = self.globalSubrs[self.operandStack[-1] + self.globalBias]
+ 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):
+ 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))
+
+
+def desubroutinize(cff):
+ for fontName in cff.fontNames:
+ font = cff[fontName]
+ cs = font.CharStrings
+ for c in cs.values():
+ 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()
+
+
+class _MarkingT2Decompiler(SimpleT2Decompiler):
+ def __init__(self, localSubrs, globalSubrs, private):
+ SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, private)
+ for subrs in [localSubrs, globalSubrs]:
+ if subrs and not hasattr(subrs, "_used"):
+ subrs._used = set()
+
+ def op_callsubr(self, index):
+ self.localSubrs._used.add(self.operandStack[-1] + self.localBias)
+ SimpleT2Decompiler.op_callsubr(self, index)
+
+ def op_callgsubr(self, index):
+ self.globalSubrs._used.add(self.operandStack[-1] + self.globalBias)
+ SimpleT2Decompiler.op_callgsubr(self, index)
+
+
+class _DehintingT2Decompiler(T2WidthExtractor):
+ class Hints(object):
+ def __init__(self):
+ # Whether calling this charstring produces any hint stems
+ # Note that if a charstring starts with hintmask, it will
+ # have has_hint set to True, because it *might* produce an
+ # implicit vstem if called under certain conditions.
+ self.has_hint = False
+ # Index to start at to drop all hints
+ self.last_hint = 0
+ # Index up to which we know more hints are possible.
+ # Only relevant if status is 0 or 1.
+ self.last_checked = 0
+ # The status means:
+ # 0: after dropping hints, this charstring is empty
+ # 1: after dropping hints, there may be more hints
+ # continuing after this, or there might be
+ # other things. Not clear yet.
+ # 2: no more hints possible after this charstring
+ self.status = 0
+ # Has hintmask instructions; not recursive
+ self.has_hintmask = False
+ # List of indices of calls to empty subroutines to remove.
+ self.deletions = []
+
+ pass
+
+ def __init__(
+ self, css, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private=None
+ ):
+ self._css = css
+ T2WidthExtractor.__init__(
+ self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX
+ )
+ self.private = private
+
+ def execute(self, charString):
+ old_hints = charString._hints if hasattr(charString, "_hints") else None
+ charString._hints = self.Hints()
+
+ T2WidthExtractor.execute(self, charString)
+
+ hints = charString._hints
+
+ if hints.has_hint or hints.has_hintmask:
+ self._css.add(charString)
+
+ if hints.status != 2:
+ # Check from last_check, make sure we didn't have any operators.
+ for i in range(hints.last_checked, len(charString.program) - 1):
+ if isinstance(charString.program[i], str):
+ hints.status = 2
+ break
+ else:
+ hints.status = 1 # There's *something* here
+ hints.last_checked = len(charString.program)
+
+ if old_hints:
+ assert hints.__dict__ == old_hints.__dict__
+
+ def op_callsubr(self, index):
+ subr = self.localSubrs[self.operandStack[-1] + self.localBias]
+ T2WidthExtractor.op_callsubr(self, index)
+ self.processSubr(index, subr)
+
+ def op_callgsubr(self, index):
+ subr = self.globalSubrs[self.operandStack[-1] + self.globalBias]
+ T2WidthExtractor.op_callgsubr(self, index)
+ self.processSubr(index, subr)
+
+ def op_hstem(self, index):
+ T2WidthExtractor.op_hstem(self, index)
+ self.processHint(index)
+
+ def op_vstem(self, index):
+ T2WidthExtractor.op_vstem(self, index)
+ self.processHint(index)
+
+ def op_hstemhm(self, index):
+ T2WidthExtractor.op_hstemhm(self, index)
+ self.processHint(index)
+
+ def op_vstemhm(self, index):
+ T2WidthExtractor.op_vstemhm(self, index)
+ self.processHint(index)
+
+ def op_hintmask(self, index):
+ rv = T2WidthExtractor.op_hintmask(self, index)
+ self.processHintmask(index)
+ return rv
+
+ def op_cntrmask(self, index):
+ rv = T2WidthExtractor.op_cntrmask(self, index)
+ self.processHintmask(index)
+ return rv
+
+ def processHintmask(self, index):
+ cs = self.callingStack[-1]
+ hints = cs._hints
+ hints.has_hintmask = True
+ if hints.status != 2:
+ # Check from last_check, see if we may be an implicit vstem
+ for i in range(hints.last_checked, index - 1):
+ if isinstance(cs.program[i], str):
+ hints.status = 2
+ break
+ else:
+ # We are an implicit vstem
+ hints.has_hint = True
+ hints.last_hint = index + 1
+ hints.status = 0
+ hints.last_checked = index + 1
+
+ def processHint(self, index):
+ cs = self.callingStack[-1]
+ hints = cs._hints
+ hints.has_hint = True
+ hints.last_hint = index
+ hints.last_checked = index
+
+ def processSubr(self, index, subr):
+ cs = self.callingStack[-1]
+ hints = cs._hints
+ subr_hints = subr._hints
+
+ # Check from last_check, make sure we didn't have
+ # any operators.
+ if hints.status != 2:
+ for i in range(hints.last_checked, index - 1):
+ if isinstance(cs.program[i], str):
+ hints.status = 2
+ break
+ hints.last_checked = index
+
+ if hints.status != 2:
+ if subr_hints.has_hint:
+ hints.has_hint = True
+
+ # Decide where to chop off from
+ if subr_hints.status == 0:
+ hints.last_hint = index
+ else:
+ hints.last_hint = index - 2 # Leave the subr call in
+
+ elif subr_hints.status == 0:
+ hints.deletions.append(index)
+
+ hints.status = max(hints.status, subr_hints.status)
+
+
+def _cs_subset_subroutines(charstring, subrs, gsubrs):
+ p = charstring.program
+ for i in range(1, len(p)):
+ if p[i] == "callsubr":
+ assert isinstance(p[i - 1], int)
+ p[i - 1] = subrs._used.index(p[i - 1] + subrs._old_bias) - subrs._new_bias
+ elif p[i] == "callgsubr":
+ assert isinstance(p[i - 1], int)
+ p[i - 1] = (
+ gsubrs._used.index(p[i - 1] + gsubrs._old_bias) - gsubrs._new_bias
+ )
+
+
+def _cs_drop_hints(charstring):
+ hints = charstring._hints
+
+ if hints.deletions:
+ p = charstring.program
+ for idx in reversed(hints.deletions):
+ del p[idx - 2 : idx]
+
+ if hints.has_hint:
+ assert not hints.deletions or hints.last_hint <= hints.deletions[0]
+ charstring.program = charstring.program[hints.last_hint :]
+ if not charstring.program:
+ # TODO CFF2 no need for endchar.
+ charstring.program.append("endchar")
+ if hasattr(charstring, "width"):
+ # Insert width back if needed
+ if charstring.width != charstring.private.defaultWidthX:
+ # For CFF2 charstrings, this should never happen
+ assert (
+ charstring.private.defaultWidthX is not None
+ ), "CFF2 CharStrings must not have an initial width value"
+ charstring.program.insert(
+ 0, charstring.width - charstring.private.nominalWidthX
+ )
+
+ if hints.has_hintmask:
+ i = 0
+ p = charstring.program
+ while i < len(p):
+ if p[i] in ["hintmask", "cntrmask"]:
+ assert i + 1 <= len(p)
+ del p[i : i + 2]
+ continue
+ i += 1
+
+ assert len(charstring.program)
+
+ del charstring._hints
+
+
+def remove_hints(cff):
+ for fontname in cff.keys():
+ font = cff[fontname]
+ cs = font.CharStrings
+ # This can be tricky, but doesn't have to. What we do is:
+ #
+ # - Run all used glyph charstrings and recurse into subroutines,
+ # - For each charstring (including subroutines), if it has any
+ # of the hint stem operators, we mark it as such.
+ # Upon returning, for each charstring we note all the
+ # subroutine calls it makes that (recursively) contain a stem,
+ # - Dropping hinting then consists of the following two ops:
+ # * Drop the piece of the program in each charstring before the
+ # last call to a stem op or a stem-calling subroutine,
+ # * Drop all hintmask operations.
+ # - It's trickier... A hintmask right after hints and a few numbers
+ # will act as an implicit vstemhm. As such, we track whether
+ # we have seen any non-hint operators so far and do the right
+ # thing, recursively... Good luck understanding that :(
+ css = set()
+ for c in cs.values():
+ c.decompile()
+ subrs = getattr(c.private, "Subrs", [])
+ decompiler = _DehintingT2Decompiler(
+ css,
+ subrs,
+ c.globalSubrs,
+ c.private.nominalWidthX,
+ c.private.defaultWidthX,
+ c.private,
+ )
+ decompiler.execute(c)
+ c.width = decompiler.width
+ for charstring in css:
+ _cs_drop_hints(charstring)
+ del css
+
+ # Drop font-wide hinting values
+ all_privs = []
+ if hasattr(font, "FDArray"):
+ all_privs.extend(fd.Private for fd in font.FDArray)
+ else:
+ all_privs.append(font.Private)
+ for priv in all_privs:
+ for k in [
+ "BlueValues",
+ "OtherBlues",
+ "FamilyBlues",
+ "FamilyOtherBlues",
+ "BlueScale",
+ "BlueShift",
+ "BlueFuzz",
+ "StemSnapH",
+ "StemSnapV",
+ "StdHW",
+ "StdVW",
+ "ForceBold",
+ "LanguageGroup",
+ "ExpansionFactor",
+ ]:
+ if hasattr(priv, k):
+ setattr(priv, k, None)
+ remove_unused_subroutines(cff)
+
+
+def _pd_delete_empty_subrs(private_dict):
+ if hasattr(private_dict, "Subrs") and not private_dict.Subrs:
+ if "Subrs" in private_dict.rawDict:
+ del private_dict.rawDict["Subrs"]
+ del private_dict.Subrs
+
+
+def remove_unused_subroutines(cff):
+ for fontname in cff.keys():
+ font = cff[fontname]
+ cs = font.CharStrings
+ # Renumber subroutines to remove unused ones
+
+ # Mark all used subroutines
+ for c in cs.values():
+ subrs = getattr(c.private, "Subrs", [])
+ decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs, c.private)
+ decompiler.execute(c)
+
+ all_subrs = [font.GlobalSubrs]
+ if hasattr(font, "FDArray"):
+ all_subrs.extend(
+ fd.Private.Subrs
+ for fd in font.FDArray
+ if hasattr(fd.Private, "Subrs") and fd.Private.Subrs
+ )
+ elif hasattr(font.Private, "Subrs") and font.Private.Subrs:
+ all_subrs.append(font.Private.Subrs)
+
+ subrs = set(subrs) # Remove duplicates
+
+ # Prepare
+ for subrs in all_subrs:
+ if not hasattr(subrs, "_used"):
+ subrs._used = set()
+ subrs._used = _uniq_sort(subrs._used)
+ subrs._old_bias = calcSubrBias(subrs)
+ subrs._new_bias = calcSubrBias(subrs._used)
+
+ # Renumber glyph charstrings
+ for c in cs.values():
+ subrs = getattr(c.private, "Subrs", None)
+ _cs_subset_subroutines(c, subrs, font.GlobalSubrs)
+
+ # Renumber subroutines themselves
+ for subrs in all_subrs:
+ if subrs == font.GlobalSubrs:
+ if not hasattr(font, "FDArray") and hasattr(font.Private, "Subrs"):
+ local_subrs = font.Private.Subrs
+ else:
+ local_subrs = None
+ else:
+ local_subrs = subrs
+
+ subrs.items = [subrs.items[i] for i in subrs._used]
+ if hasattr(subrs, "file"):
+ del subrs.file
+ if hasattr(subrs, "offsets"):
+ del subrs.offsets
+
+ for subr in subrs.items:
+ _cs_subset_subroutines(subr, local_subrs, font.GlobalSubrs)
+
+ # Delete local SubrsIndex if empty
+ if hasattr(font, "FDArray"):
+ for fd in font.FDArray:
+ _pd_delete_empty_subrs(fd.Private)
+ else:
+ _pd_delete_empty_subrs(font.Private)
+
+ # Cleanup
+ for subrs in all_subrs:
+ del subrs._used, subrs._old_bias, subrs._new_bias