Merge branch 'main' into python-3.13
This commit is contained in:
commit
b4a276b30f
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
|
||||||
# TODO(behdad): Add a configuration option for this?
|
if optimizeSize:
|
||||||
deltas = self.compileDeltasGreedy(self.flags, deltas)
|
# TODO(behdad): Add a configuration option for this?
|
||||||
# deltas = self.compileDeltasOptimal(self.flags, deltas)
|
deltas = self.compileDeltasGreedy(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()
|
||||||
|
@ -1191,8 +1191,12 @@ def _readGlyphFromTreeFormat1(
|
|||||||
haveSeenAdvance = True
|
haveSeenAdvance = True
|
||||||
_readAdvance(glyphObject, element)
|
_readAdvance(glyphObject, element)
|
||||||
elif element.tag == "unicode":
|
elif element.tag == "unicode":
|
||||||
|
v = element.get("hex")
|
||||||
|
if v is None:
|
||||||
|
raise GlifLibError(
|
||||||
|
"A unicode element is missing its required hex attribute."
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
v = element.get("hex")
|
|
||||||
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":
|
||||||
|
v = element.get("hex")
|
||||||
|
if v is None:
|
||||||
|
raise GlifLibError(
|
||||||
|
"A unicode element is missing its required hex attribute."
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
v = element.get("hex")
|
|
||||||
v = int(v, 16)
|
v = int(v, 16)
|
||||||
if v not in unicodes:
|
if v not in unicodes:
|
||||||
unicodes.append(v)
|
unicodes.append(v)
|
||||||
|
BIN
Tests/ttLib/data/IBMPlexSans-Bold.subset.otf
Normal file
BIN
Tests/ttLib/data/IBMPlexSans-Bold.subset.otf
Normal file
Binary file not shown.
@ -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]
|
||||||
|
@ -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(
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user