Browse Source

Intermediate changes

robot-piglet 9 months ago
parent
commit
b52a8ab5cd

+ 11 - 1
contrib/python/fonttools/.dist-info/METADATA

@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: fonttools
-Version: 4.52.4
+Version: 4.53.0
 Summary: Tools to manipulate font files
 Home-page: http://github.com/fonttools/fonttools
 Author: Just van Rossum
@@ -375,6 +375,16 @@ Have fun!
 Changelog
 ~~~~~~~~~
 
+4.53.0 (released 2024-05-31)
+----------------------------
+
+- [ttLib.removeOverlaps] Support CFF table to aid in downconverting CFF2 fonts (#3528)
+- [avar] Fix crash when accessing not-yet-existing attribute (#3550)
+- [docs] Add buildMathTable to otlLib.builder documentation (#3540)
+- [feaLib] Allow UTF-8 with BOM when reading features (#3495)
+- [SVGPathPen] Revert rounding coordinates to two decimal places by default (#3543)
+- [varLib.instancer] Refix output filename decision-making  (#3545, #3544, #3548)
+
 4.52.4 (released 2024-05-27)
 ----------------------------
 

+ 1 - 1
contrib/python/fonttools/fontTools/__init__.py

@@ -3,6 +3,6 @@ from fontTools.misc.loggingTools import configLogger
 
 log = logging.getLogger(__name__)
 
-version = __version__ = "4.52.4"
+version = __version__ = "4.53.0"
 
 __all__ = ["version", "log", "configLogger"]

+ 1 - 1
contrib/python/fonttools/fontTools/feaLib/lexer.py

@@ -269,7 +269,7 @@ class IncludingLexer(object):
             fileobj, closing = file_or_path, False
         else:
             filename, closing = file_or_path, True
-            fileobj = open(filename, "r", encoding="utf-8")
+            fileobj = open(filename, "r", encoding="utf-8-sig")
         data = fileobj.read()
         filename = getattr(fileobj, "name", None)
         if closing:

+ 2 - 8
contrib/python/fonttools/fontTools/pens/svgPathPen.py

@@ -2,7 +2,7 @@ from typing import Callable
 from fontTools.pens.basePen import BasePen
 
 
-def pointToString(pt, ntos):
+def pointToString(pt, ntos=str):
     return " ".join(ntos(i) for i in pt)
 
 
@@ -37,13 +37,7 @@ class SVGPathPen(BasePen):
             print(tpen.getCommands())
     """
 
-    def __init__(
-        self,
-        glyphSet,
-        ntos: Callable[[float], str] = (
-            lambda x: ("%.2f" % x) if x != int(x) else str(int(x))
-        ),
-    ):
+    def __init__(self, glyphSet, ntos: Callable[[float], str] = str):
         BasePen.__init__(self, glyphSet)
         self._commands = []
         self._lastCommand = None

+ 189 - 52
contrib/python/fonttools/fontTools/ttLib/removeOverlaps.py

@@ -7,11 +7,14 @@ import itertools
 import logging
 from typing import Callable, Iterable, Optional, Mapping
 
-from fontTools.misc.roundTools import otRound
+from fontTools.cffLib import CFFFontSet
 from fontTools.ttLib import ttFont
 from fontTools.ttLib.tables import _g_l_y_f
 from fontTools.ttLib.tables import _h_m_t_x
+from fontTools.misc.psCharStrings import T2CharString
+from fontTools.misc.roundTools import otRound, noRound
 from fontTools.pens.ttGlyphPen import TTGlyphPen
+from fontTools.pens.t2CharStringPen import T2CharStringPen
 
 import pathops
 
@@ -81,6 +84,14 @@ def ttfGlyphFromSkPath(path: pathops.Path) -> _g_l_y_f.Glyph:
     return glyph
 
 
+def _charString_from_SkPath(
+    path: pathops.Path, charString: T2CharString
+) -> T2CharString:
+    t2Pen = T2CharStringPen(width=charString.width, glyphSet=None)
+    path.draw(t2Pen)
+    return t2Pen.getCharString(charString.private, charString.globalSubrs)
+
+
 def _round_path(
     path: pathops.Path, round: Callable[[float], float] = otRound
 ) -> pathops.Path:
@@ -90,7 +101,12 @@ def _round_path(
     return rounded_path
 
 
-def _simplify(path: pathops.Path, debugGlyphName: str) -> pathops.Path:
+def _simplify(
+    path: pathops.Path,
+    debugGlyphName: str,
+    *,
+    round: Callable[[float], float] = otRound,
+) -> pathops.Path:
     # skia-pathops has a bug where it sometimes fails to simplify paths when there
     # are float coordinates and control points are very close to one another.
     # Rounding coordinates to integers works around the bug.
@@ -105,7 +121,7 @@ def _simplify(path: pathops.Path, debugGlyphName: str) -> pathops.Path:
     except pathops.PathOpsError:
         pass
 
-    path = _round_path(path)
+    path = _round_path(path, round=round)
     try:
         path = pathops.simplify(path, clockwise=path.clockwise)
         log.debug(
@@ -124,6 +140,10 @@ def _simplify(path: pathops.Path, debugGlyphName: str) -> pathops.Path:
     raise AssertionError("Unreachable")
 
 
+def _same_path(path1: pathops.Path, path2: pathops.Path) -> bool:
+    return {tuple(c) for c in path1.contours} == {tuple(c) for c in path2.contours}
+
+
 def removeTTGlyphOverlaps(
     glyphName: str,
     glyphSet: _TTGlyphMapping,
@@ -144,7 +164,7 @@ def removeTTGlyphOverlaps(
         path2 = _simplify(path, glyphName)
 
         # replace TTGlyph if simplified path is different (ignoring contour order)
-        if {tuple(c) for c in path.contours} != {tuple(c) for c in path2.contours}:
+        if not _same_path(path, path2):
             glyfTable[glyphName] = glyph = ttfGlyphFromSkPath(path2)
             # simplified glyph is always unhinted
             assert not glyph.program
@@ -159,42 +179,16 @@ def removeTTGlyphOverlaps(
     return False
 
 
-def removeOverlaps(
+def _remove_glyf_overlaps(
+    *,
     font: ttFont.TTFont,
-    glyphNames: Optional[Iterable[str]] = None,
-    removeHinting: bool = True,
-    ignoreErrors=False,
+    glyphNames: Iterable[str],
+    glyphSet: _TTGlyphMapping,
+    removeHinting: bool,
+    ignoreErrors: bool,
 ) -> None:
-    """Simplify glyphs in TTFont by merging overlapping contours.
-
-    Overlapping components are first decomposed to simple contours, then merged.
-
-    Currently this only works with TrueType fonts with 'glyf' table.
-    Raises NotImplementedError if 'glyf' table is absent.
-
-    Note that removing overlaps invalidates the hinting. By default we drop hinting
-    from all glyphs whether or not overlaps are removed from a given one, as it would
-    look weird if only some glyphs are left (un)hinted.
-
-    Args:
-        font: input TTFont object, modified in place.
-        glyphNames: optional iterable of glyph names (str) to remove overlaps from.
-            By default, all glyphs in the font are processed.
-        removeHinting (bool): set to False to keep hinting for unmodified glyphs.
-        ignoreErrors (bool): set to True to ignore errors while removing overlaps,
-            thus keeping the tricky glyphs unchanged (fonttools/fonttools#2363).
-    """
-    try:
-        glyfTable = font["glyf"]
-    except KeyError:
-        raise NotImplementedError("removeOverlaps currently only works with TTFs")
-
+    glyfTable = font["glyf"]
     hmtxTable = font["hmtx"]
-    # wraps the underlying glyf Glyphs, takes care of interfacing with drawing pens
-    glyphSet = font.getGlyphSet()
-
-    if glyphNames is None:
-        glyphNames = font.getGlyphOrder()
 
     # process all simple glyphs first, then composites with increasing component depth,
     # so that by the time we test for component intersections the respective base glyphs
@@ -225,27 +219,170 @@ def removeOverlaps(
     log.debug("Removed overlaps for %s glyphs:\n%s", len(modified), " ".join(modified))
 
 
-def main(args=None):
-    """Simplify glyphs in TTFont by merging overlapping contours."""
+def _remove_charstring_overlaps(
+    *,
+    glyphName: str,
+    glyphSet: _TTGlyphMapping,
+    cffFontSet: CFFFontSet,
+) -> bool:
+    path = skPathFromGlyph(glyphName, glyphSet)
+
+    # remove overlaps
+    path2 = _simplify(path, glyphName, round=noRound)
+
+    # replace TTGlyph if simplified path is different (ignoring contour order)
+    if not _same_path(path, path2):
+        charStrings = cffFontSet[0].CharStrings
+        charStrings[glyphName] = _charString_from_SkPath(path2, charStrings[glyphName])
+        return True
 
-    import sys
+    return False
 
-    if args is None:
-        args = sys.argv[1:]
 
-    if len(args) < 2:
-        print(
-            f"usage: fonttools ttLib.removeOverlaps INPUT.ttf OUTPUT.ttf [GLYPHS ...]"
+def _remove_cff_overlaps(
+    *,
+    font: ttFont.TTFont,
+    glyphNames: Iterable[str],
+    glyphSet: _TTGlyphMapping,
+    removeHinting: bool,
+    ignoreErrors: bool,
+    removeUnusedSubroutines: bool = True,
+) -> None:
+    cffFontSet = font["CFF "].cff
+    modified = set()
+    for glyphName in glyphNames:
+        try:
+            if _remove_charstring_overlaps(
+                glyphName=glyphName,
+                glyphSet=glyphSet,
+                cffFontSet=cffFontSet,
+            ):
+                modified.add(glyphName)
+        except RemoveOverlapsError:
+            if not ignoreErrors:
+                raise
+            log.error("Failed to remove overlaps for '%s'", glyphName)
+
+    if not modified:
+        log.debug("No overlaps found in the specified CFF glyphs")
+        return
+
+    if removeHinting:
+        cffFontSet.remove_hints()
+
+    if removeUnusedSubroutines:
+        cffFontSet.remove_unused_subroutines()
+
+    log.debug("Removed overlaps for %s glyphs:\n%s", len(modified), " ".join(modified))
+
+
+def removeOverlaps(
+    font: ttFont.TTFont,
+    glyphNames: Optional[Iterable[str]] = None,
+    removeHinting: bool = True,
+    ignoreErrors: bool = False,
+    *,
+    removeUnusedSubroutines: bool = True,
+) -> None:
+    """Simplify glyphs in TTFont by merging overlapping contours.
+
+    Overlapping components are first decomposed to simple contours, then merged.
+
+    Currently this only works for fonts with 'glyf' or 'CFF ' tables.
+    Raises NotImplementedError if 'glyf' or 'CFF ' tables are absent.
+
+    Note that removing overlaps invalidates the hinting. By default we drop hinting
+    from all glyphs whether or not overlaps are removed from a given one, as it would
+    look weird if only some glyphs are left (un)hinted.
+
+    Args:
+        font: input TTFont object, modified in place.
+        glyphNames: optional iterable of glyph names (str) to remove overlaps from.
+            By default, all glyphs in the font are processed.
+        removeHinting (bool): set to False to keep hinting for unmodified glyphs.
+        ignoreErrors (bool): set to True to ignore errors while removing overlaps,
+            thus keeping the tricky glyphs unchanged (fonttools/fonttools#2363).
+        removeUnusedSubroutines (bool): set to False to keep unused subroutines
+            in CFF table after removing overlaps. Default is to remove them if
+            any glyphs are modified.
+    """
+
+    if "glyf" not in font and "CFF " not in font:
+        raise NotImplementedError(
+            "No outline data found in the font: missing 'glyf' or 'CFF ' table"
         )
-        sys.exit(1)
 
-    src = args[0]
-    dst = args[1]
-    glyphNames = args[2:] or None
+    if glyphNames is None:
+        glyphNames = font.getGlyphOrder()
+
+    # Wraps the underlying glyphs, takes care of interfacing with drawing pens
+    glyphSet = font.getGlyphSet()
+
+    if "glyf" in font:
+        _remove_glyf_overlaps(
+            font=font,
+            glyphNames=glyphNames,
+            glyphSet=glyphSet,
+            removeHinting=removeHinting,
+            ignoreErrors=ignoreErrors,
+        )
+
+    if "CFF " in font:
+        _remove_cff_overlaps(
+            font=font,
+            glyphNames=glyphNames,
+            glyphSet=glyphSet,
+            removeHinting=removeHinting,
+            ignoreErrors=ignoreErrors,
+            removeUnusedSubroutines=removeUnusedSubroutines,
+        )
 
-    with ttFont.TTFont(src) as f:
-        removeOverlaps(f, glyphNames)
-        f.save(dst)
+
+def main(args=None):
+    """Simplify glyphs in TTFont by merging overlapping contours."""
+
+    import argparse
+
+    parser = argparse.ArgumentParser(
+        "fonttools ttLib.removeOverlaps", description=__doc__
+    )
+
+    parser.add_argument("input", metavar="INPUT.ttf", help="Input font file")
+    parser.add_argument("output", metavar="OUTPUT.ttf", help="Output font file")
+    parser.add_argument(
+        "glyphs",
+        metavar="GLYPHS",
+        nargs="*",
+        help="Optional list of glyph names to remove overlaps from",
+    )
+    parser.add_argument(
+        "--keep-hinting",
+        action="store_true",
+        help="Keep hinting for unmodified glyphs, default is to drop hinting",
+    )
+    parser.add_argument(
+        "--ignore-errors",
+        action="store_true",
+        help="ignore errors while removing overlaps, "
+        "thus keeping the tricky glyphs unchanged",
+    )
+    parser.add_argument(
+        "--keep-unused-subroutines",
+        action="store_true",
+        help="Keep unused subroutines in CFF table after removing overlaps, "
+        "default is to remove them if any glyphs are modified",
+    )
+    args = parser.parse_args(args)
+
+    with ttFont.TTFont(args.input) as font:
+        removeOverlaps(
+            font=font,
+            glyphNames=args.glyphs or None,
+            removeHinting=not args.keep_hinting,
+            ignoreErrors=args.ignore_errors,
+            removeUnusedSubroutines=not args.keep_unused_subroutines,
+        )
+        font.save(args.output)
 
 
 if __name__ == "__main__":

+ 4 - 2
contrib/python/fonttools/fontTools/ttLib/tables/_a_v_a_r.py

@@ -143,7 +143,9 @@ class table__a_v_a_r(BaseTTXConverter):
 
     def renormalizeLocation(self, location, font):
 
-        if self.majorVersion not in (1, 2):
+        majorVersion = getattr(self, "majorVersion", 1)
+
+        if majorVersion not in (1, 2):
             raise NotImplementedError("Unknown avar table version")
 
         avarSegments = self.segments
@@ -154,7 +156,7 @@ class table__a_v_a_r(BaseTTXConverter):
                 value = piecewiseLinearMap(value, avarMapping)
             mappedLocation[axisTag] = value
 
-        if self.majorVersion < 2:
+        if majorVersion < 2:
             return mappedLocation
 
         # Version 2

+ 3 - 1
contrib/python/fonttools/fontTools/varLib/instancer/__init__.py

@@ -1880,7 +1880,9 @@ def main(args=None):
     )
 
     isFullInstance = {
-        axisTag for axisTag, limit in axisLimits.items() if limit[0] == limit[2]
+        axisTag
+        for axisTag, limit in axisLimits.items()
+        if limit is None or limit[0] == limit[2]
     }.issuperset(axis.axisTag for axis in varfont["fvar"].axes)
 
     instantiateVariableFont(

+ 1 - 1
contrib/python/fonttools/ya.make

@@ -2,7 +2,7 @@
 
 PY3_LIBRARY()
 
-VERSION(4.52.4)
+VERSION(4.53.0)
 
 LICENSE(MIT)
 

+ 8 - 7
contrib/python/ipython/py3/.dist-info/METADATA

@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: ipython
-Version: 8.24.0
+Version: 8.25.0
 Summary: IPython: Productive Interactive Computing
 Author: The IPython Development Team
 Author-email: ipython-dev@python.org
@@ -43,17 +43,18 @@ Requires-Dist: ipython[test,test_extra] ; extra == 'all'
 Provides-Extra: black
 Requires-Dist: black ; extra == 'black'
 Provides-Extra: doc
+Requires-Dist: docrepr ; extra == 'doc'
+Requires-Dist: exceptiongroup ; extra == 'doc'
+Requires-Dist: intersphinx-registry ; extra == 'doc'
 Requires-Dist: ipykernel ; extra == 'doc'
+Requires-Dist: ipython[test] ; extra == 'doc'
+Requires-Dist: matplotlib ; extra == 'doc'
 Requires-Dist: setuptools >=18.5 ; extra == 'doc'
-Requires-Dist: sphinx >=1.3 ; extra == 'doc'
 Requires-Dist: sphinx-rtd-theme ; extra == 'doc'
+Requires-Dist: sphinx >=1.3 ; extra == 'doc'
 Requires-Dist: sphinxcontrib-jquery ; extra == 'doc'
-Requires-Dist: docrepr ; extra == 'doc'
-Requires-Dist: matplotlib ; extra == 'doc'
-Requires-Dist: stack-data ; extra == 'doc'
 Requires-Dist: typing-extensions ; extra == 'doc'
-Requires-Dist: exceptiongroup ; extra == 'doc'
-Requires-Dist: ipython[test] ; extra == 'doc'
+Requires-Dist: tomli ; (python_version < "3.11") and extra == 'doc'
 Provides-Extra: kernel
 Requires-Dist: ipykernel ; extra == 'kernel'
 Provides-Extra: matplotlib

+ 7 - 2
contrib/python/ipython/py3/IPython/core/completerlib.py

@@ -199,9 +199,14 @@ def get_root_modules():
     return rootmodules
 
 
-def is_importable(module, attr, only_modules):
+def is_importable(module, attr: str, only_modules) -> bool:
     if only_modules:
-        return inspect.ismodule(getattr(module, attr))
+        try:
+            mod = getattr(module, attr)
+        except ModuleNotFoundError:
+            # See gh-14434
+            return False
+        return inspect.ismodule(mod)
     else:
         return not(attr[:2] == '__' and attr[-2:] == '__')
 

Some files were not shown because too many files changed in this diff