Merge branch 'main' into python-3.13

This commit is contained in:
Cosimo Lupo 2024-10-18 15:13:18 +02:00 committed by GitHub
commit b4a276b30f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 177 additions and 15 deletions

View File

@ -1,4 +1,4 @@
sphinx==8.0.2 sphinx==8.1.3
sphinx_rtd_theme==3.0.0 sphinx_rtd_theme==3.0.1
reportlab==4.2.5 reportlab==4.2.5
freetype-py==2.5.1 freetype-py==2.5.1

View File

@ -87,7 +87,11 @@ def ttfGlyphFromSkPath(path: pathops.Path) -> _g_l_y_f.Glyph:
def _charString_from_SkPath( def _charString_from_SkPath(
path: pathops.Path, charString: T2CharString path: pathops.Path, charString: T2CharString
) -> T2CharString: ) -> T2CharString:
t2Pen = T2CharStringPen(width=charString.width, glyphSet=None) if charString.width == charString.private.defaultWidthX:
width = None
else:
width = charString.width - charString.private.nominalWidthX
t2Pen = T2CharStringPen(width=width, glyphSet=None)
path.draw(t2Pen) path.draw(t2Pen)
return t2Pen.getCharString(charString.private, charString.globalSubrs) return t2Pen.getCharString(charString.private, charString.globalSubrs)

View File

@ -713,7 +713,9 @@ class Glyph(object):
else: else:
self.decompileCoordinates(data) self.decompileCoordinates(data)
def compile(self, glyfTable, recalcBBoxes=True, *, boundsDone=None): def compile(
self, glyfTable, recalcBBoxes=True, *, boundsDone=None, optimizeSize=None
):
if hasattr(self, "data"): if hasattr(self, "data"):
if recalcBBoxes: if recalcBBoxes:
# must unpack glyph in order to recalculate bounding box # must unpack glyph in order to recalculate bounding box
@ -730,7 +732,9 @@ class Glyph(object):
if self.isComposite(): if self.isComposite():
data = data + self.compileComponents(glyfTable) data = data + self.compileComponents(glyfTable)
else: else:
data = data + self.compileCoordinates() if optimizeSize is None:
optimizeSize = getattr(glyfTable, "optimizeSize", True)
data = data + self.compileCoordinates(optimizeSize=optimizeSize)
return data return data
def toXML(self, writer, ttFont): def toXML(self, writer, ttFont):
@ -976,7 +980,7 @@ class Glyph(object):
data = data + struct.pack(">h", len(instructions)) + instructions data = data + struct.pack(">h", len(instructions)) + instructions
return data return data
def compileCoordinates(self): def compileCoordinates(self, *, optimizeSize=True):
assert len(self.coordinates) == len(self.flags) assert len(self.coordinates) == len(self.flags)
data = [] data = []
endPtsOfContours = array.array("H", self.endPtsOfContours) endPtsOfContours = array.array("H", self.endPtsOfContours)
@ -991,9 +995,12 @@ class Glyph(object):
deltas.toInt() deltas.toInt()
deltas.absoluteToRelative() deltas.absoluteToRelative()
if optimizeSize:
# TODO(behdad): Add a configuration option for this? # TODO(behdad): Add a configuration option for this?
deltas = self.compileDeltasGreedy(self.flags, deltas) deltas = self.compileDeltasGreedy(self.flags, deltas)
# deltas = self.compileDeltasOptimal(self.flags, deltas) # deltas = self.compileDeltasOptimal(self.flags, deltas)
else:
deltas = self.compileDeltasForSpeed(self.flags, deltas)
data.extend(deltas) data.extend(deltas)
return b"".join(data) return b"".join(data)
@ -1110,6 +1117,63 @@ class Glyph(object):
return (compressedFlags, compressedXs, compressedYs) return (compressedFlags, compressedXs, compressedYs)
def compileDeltasForSpeed(self, flags, deltas):
# uses widest representation needed, for all deltas.
compressedFlags = bytearray()
compressedXs = bytearray()
compressedYs = bytearray()
# Compute the necessary width for each axis
xs = [d[0] for d in deltas]
ys = [d[1] for d in deltas]
minX, minY, maxX, maxY = min(xs), min(ys), max(xs), max(ys)
xZero = minX == 0 and maxX == 0
yZero = minY == 0 and maxY == 0
xShort = -255 <= minX <= maxX <= 255
yShort = -255 <= minY <= maxY <= 255
lastflag = None
repeat = 0
for flag, (x, y) in zip(flags, deltas):
# Oh, the horrors of TrueType
# do x
if xZero:
flag = flag | flagXsame
elif xShort:
flag = flag | flagXShort
if x > 0:
flag = flag | flagXsame
else:
x = -x
compressedXs.append(x)
else:
compressedXs.extend(struct.pack(">h", x))
# do y
if yZero:
flag = flag | flagYsame
elif yShort:
flag = flag | flagYShort
if y > 0:
flag = flag | flagYsame
else:
y = -y
compressedYs.append(y)
else:
compressedYs.extend(struct.pack(">h", y))
# handle repeating flags
if flag == lastflag and repeat != 255:
repeat = repeat + 1
if repeat == 1:
compressedFlags.append(flag)
else:
compressedFlags[-2] = flag | flagRepeat
compressedFlags[-1] = repeat
else:
repeat = 0
compressedFlags.append(flag)
lastflag = flag
return (compressedFlags, compressedXs, compressedYs)
def recalcBounds(self, glyfTable, *, boundsDone=None): def recalcBounds(self, glyfTable, *, boundsDone=None):
"""Recalculates the bounds of the glyph. """Recalculates the bounds of the glyph.
@ -1404,6 +1468,7 @@ class Glyph(object):
pen.addComponent(glyphName, transform) pen.addComponent(glyphName, transform)
return return
self.expand(glyfTable)
coordinates, endPts, flags = self.getCoordinates(glyfTable) coordinates, endPts, flags = self.getCoordinates(glyfTable)
if offset: if offset:
coordinates = coordinates.copy() coordinates = coordinates.copy()

View File

@ -1191,8 +1191,12 @@ def _readGlyphFromTreeFormat1(
haveSeenAdvance = True haveSeenAdvance = True
_readAdvance(glyphObject, element) _readAdvance(glyphObject, element)
elif element.tag == "unicode": elif element.tag == "unicode":
try:
v = element.get("hex") v = element.get("hex")
if v is None:
raise GlifLibError(
"A unicode element is missing its required hex attribute."
)
try:
v = int(v, 16) v = int(v, 16)
if v not in unicodes: if v not in unicodes:
unicodes.append(v) unicodes.append(v)
@ -1254,8 +1258,12 @@ def _readGlyphFromTreeFormat2(
haveSeenAdvance = True haveSeenAdvance = True
_readAdvance(glyphObject, element) _readAdvance(glyphObject, element)
elif element.tag == "unicode": elif element.tag == "unicode":
try:
v = element.get("hex") v = element.get("hex")
if v is None:
raise GlifLibError(
"A unicode element is missing its required hex attribute."
)
try:
v = int(v, 16) v = int(v, 16)
if v not in unicodes: if v not in unicodes:
unicodes.append(v) unicodes.append(v)

Binary file not shown.

View File

@ -1,9 +1,13 @@
import logging import logging
import pytest import pytest
from pathlib import Path
pathops = pytest.importorskip("pathops") pathops = pytest.importorskip("pathops")
from fontTools.ttLib.removeOverlaps import _simplify, _round_path from fontTools.ttLib import TTFont
from fontTools.ttLib.removeOverlaps import removeOverlaps, _simplify, _round_path
DATA_DIR = Path(__file__).parent / "data"
def test_pathops_simplify_bug_workaround(caplog): def test_pathops_simplify_bug_workaround(caplog):
@ -49,3 +53,21 @@ def test_pathops_simplify_bug_workaround(caplog):
expected.close() expected.close()
assert expected == _round_path(result, round=lambda v: round(v, 3)) assert expected == _round_path(result, round=lambda v: round(v, 3))
def test_CFF_CharString_width_nominalWidthX():
font_path = DATA_DIR / "IBMPlexSans-Bold.subset.otf"
font = TTFont(str(font_path))
assert font["hmtx"]["OE"][0] == 998
# calcBounds() has the side effect of setting the width attribute
font["CFF "].cff[0].CharStrings["OE"].calcBounds({})
assert font["CFF "].cff[0].CharStrings["OE"].width == font["hmtx"]["OE"][0]
removeOverlaps(font)
assert font["hmtx"]["OE"][0] == 998
font["CFF "].cff[0].CharStrings["OE"].calcBounds({})
assert font["CFF "].cff[0].CharStrings["OE"].width == font["hmtx"]["OE"][0]

View File

@ -707,6 +707,37 @@ class GlyphComponentTest:
'<component glyphName="a" firstPt="1" secondPt="2" flags="0x0"/>' '<component glyphName="a" firstPt="1" secondPt="2" flags="0x0"/>'
] ]
def test_compile_for_speed(self):
glyph = Glyph()
glyph.numberOfContours = 1
glyph.coordinates = GlyphCoordinates(
[(0, 0), (1, 0), (1, 0), (1, 1), (1, 1), (0, 1), (0, 1)]
)
glyph.flags = array.array("B", [flagOnCurve] + [flagCubic] * 6)
glyph.endPtsOfContours = [6]
glyph.program = ttProgram.Program()
glyph.expand(None)
sizeBytes = glyph.compile(None, optimizeSize=True)
glyph.expand(None)
speedBytes = glyph.compile(None, optimizeSize=False)
assert len(sizeBytes) < len(speedBytes)
for data in sizeBytes, speedBytes:
glyph = Glyph(data)
pen = RecordingPen()
glyph.draw(pen, None)
assert pen.value == [
("moveTo", ((0, 0),)),
("curveTo", ((1, 0), (1, 0), (1.0, 0.5))),
("curveTo", ((1, 1), (1, 1), (0.5, 1.0))),
("curveTo", ((0, 1), (0, 1), (0, 0))),
("closePath", ()),
]
def test_fromXML_reference_points(self): def test_fromXML_reference_points(self):
comp = GlyphComponent() comp = GlyphComponent()
for name, attrs, content in parseXML( for name, attrs, content in parseXML(

View File

@ -300,6 +300,22 @@ class TestGLIF1(unittest.TestCase):
self.assertRaises(GlifLibError, self.pyToGLIF, py) self.assertRaises(GlifLibError, self.pyToGLIF, py)
self.assertRaises(GlifLibError, self.glifToPy, glif) self.assertRaises(GlifLibError, self.glifToPy, glif)
def testUnicodes_hex_present(self):
"""Test that a present <unicode> element must have a
'hex' attribute; by testing that an invalid <unicode>
element raises an appropriate error.
"""
# illegal
glif = """
<glyph name="a" format="1">
<unicode />
<outline>
</outline>
</glyph>
"""
self.assertRaises(GlifLibError, self.glifToPy, glif)
def testNote(self): def testNote(self):
glif = """ glif = """
<glyph name="a" format="1"> <glyph name="a" format="1">

View File

@ -300,6 +300,22 @@ class TestGLIF2(unittest.TestCase):
self.assertRaises(GlifLibError, self.pyToGLIF, py) self.assertRaises(GlifLibError, self.pyToGLIF, py)
self.assertRaises(GlifLibError, self.glifToPy, glif) self.assertRaises(GlifLibError, self.glifToPy, glif)
def testUnicodes_hex_present(self):
"""Test that a present <unicode> element must have a
'hex' attribute; by testing that an invalid <unicode>
element raises an appropriate error.
"""
# illegal
glif = """
<glyph name="a" format="2">
<unicode />
<outline>
</outline>
</glyph>
"""
self.assertRaises(GlifLibError, self.glifToPy, glif)
def testNote(self): def testNote(self):
glif = """ glif = """
<glyph name="a" format="2"> <glyph name="a" format="2">

View File

@ -6,4 +6,4 @@ mypy>=0.782
readme_renderer[md]>=43.0 readme_renderer[md]>=43.0
# Pin black as each version could change formatting, breaking CI randomly. # Pin black as each version could change formatting, breaking CI randomly.
black==24.8.0 black==24.10.0

View File

@ -11,10 +11,10 @@ fs==2.4.16
skia-pathops==0.8.0.post1; platform_python_implementation != "PyPy" skia-pathops==0.8.0.post1; platform_python_implementation != "PyPy"
# this is only required to run Tests/cu2qu/{ufo,cli}_test.py # this is only required to run Tests/cu2qu/{ufo,cli}_test.py
ufoLib2==0.16.0 ufoLib2==0.16.0
ufo2ft==3.3.0 ufo2ft==3.3.1
pyobjc==10.3.1; sys_platform == "darwin" pyobjc==10.3.1; sys_platform == "darwin"
freetype-py==2.5.1 freetype-py==2.5.1
uharfbuzz==0.41.0 uharfbuzz==0.41.0
glyphsLib==6.9.0 # this is only required to run Tests/varLib/interpolatable_test.py glyphsLib==6.9.2 # this is only required to run Tests/varLib/interpolatable_test.py
lxml==5.3.0 lxml==5.3.0
sympy==1.13.3 sympy==1.13.3