Merge branch 'main' into python-3.13
This commit is contained in:
commit
b4a276b30f
@ -1,4 +1,4 @@
|
||||
sphinx==8.0.2
|
||||
sphinx_rtd_theme==3.0.0
|
||||
sphinx==8.1.3
|
||||
sphinx_rtd_theme==3.0.1
|
||||
reportlab==4.2.5
|
||||
freetype-py==2.5.1
|
||||
|
@ -87,7 +87,11 @@ def ttfGlyphFromSkPath(path: pathops.Path) -> _g_l_y_f.Glyph:
|
||||
def _charString_from_SkPath(
|
||||
path: pathops.Path, charString: 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)
|
||||
return t2Pen.getCharString(charString.private, charString.globalSubrs)
|
||||
|
||||
|
@ -713,7 +713,9 @@ class Glyph(object):
|
||||
else:
|
||||
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 recalcBBoxes:
|
||||
# must unpack glyph in order to recalculate bounding box
|
||||
@ -730,7 +732,9 @@ class Glyph(object):
|
||||
if self.isComposite():
|
||||
data = data + self.compileComponents(glyfTable)
|
||||
else:
|
||||
data = data + self.compileCoordinates()
|
||||
if optimizeSize is None:
|
||||
optimizeSize = getattr(glyfTable, "optimizeSize", True)
|
||||
data = data + self.compileCoordinates(optimizeSize=optimizeSize)
|
||||
return data
|
||||
|
||||
def toXML(self, writer, ttFont):
|
||||
@ -976,7 +980,7 @@ class Glyph(object):
|
||||
data = data + struct.pack(">h", len(instructions)) + instructions
|
||||
return data
|
||||
|
||||
def compileCoordinates(self):
|
||||
def compileCoordinates(self, *, optimizeSize=True):
|
||||
assert len(self.coordinates) == len(self.flags)
|
||||
data = []
|
||||
endPtsOfContours = array.array("H", self.endPtsOfContours)
|
||||
@ -991,9 +995,12 @@ class Glyph(object):
|
||||
deltas.toInt()
|
||||
deltas.absoluteToRelative()
|
||||
|
||||
if optimizeSize:
|
||||
# TODO(behdad): Add a configuration option for this?
|
||||
deltas = self.compileDeltasGreedy(self.flags, deltas)
|
||||
# deltas = self.compileDeltasOptimal(self.flags, deltas)
|
||||
else:
|
||||
deltas = self.compileDeltasForSpeed(self.flags, deltas)
|
||||
|
||||
data.extend(deltas)
|
||||
return b"".join(data)
|
||||
@ -1110,6 +1117,63 @@ class Glyph(object):
|
||||
|
||||
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):
|
||||
"""Recalculates the bounds of the glyph.
|
||||
|
||||
@ -1404,6 +1468,7 @@ class Glyph(object):
|
||||
pen.addComponent(glyphName, transform)
|
||||
return
|
||||
|
||||
self.expand(glyfTable)
|
||||
coordinates, endPts, flags = self.getCoordinates(glyfTable)
|
||||
if offset:
|
||||
coordinates = coordinates.copy()
|
||||
|
@ -1191,8 +1191,12 @@ def _readGlyphFromTreeFormat1(
|
||||
haveSeenAdvance = True
|
||||
_readAdvance(glyphObject, element)
|
||||
elif element.tag == "unicode":
|
||||
try:
|
||||
v = element.get("hex")
|
||||
if v is None:
|
||||
raise GlifLibError(
|
||||
"A unicode element is missing its required hex attribute."
|
||||
)
|
||||
try:
|
||||
v = int(v, 16)
|
||||
if v not in unicodes:
|
||||
unicodes.append(v)
|
||||
@ -1254,8 +1258,12 @@ def _readGlyphFromTreeFormat2(
|
||||
haveSeenAdvance = True
|
||||
_readAdvance(glyphObject, element)
|
||||
elif element.tag == "unicode":
|
||||
try:
|
||||
v = element.get("hex")
|
||||
if v is None:
|
||||
raise GlifLibError(
|
||||
"A unicode element is missing its required hex attribute."
|
||||
)
|
||||
try:
|
||||
v = int(v, 16)
|
||||
if v not in unicodes:
|
||||
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 pytest
|
||||
from pathlib import Path
|
||||
|
||||
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):
|
||||
@ -49,3 +53,21 @@ def test_pathops_simplify_bug_workaround(caplog):
|
||||
expected.close()
|
||||
|
||||
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"/>'
|
||||
]
|
||||
|
||||
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):
|
||||
comp = GlyphComponent()
|
||||
for name, attrs, content in parseXML(
|
||||
|
@ -300,6 +300,22 @@ class TestGLIF1(unittest.TestCase):
|
||||
self.assertRaises(GlifLibError, self.pyToGLIF, py)
|
||||
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):
|
||||
glif = """
|
||||
<glyph name="a" format="1">
|
||||
|
@ -300,6 +300,22 @@ class TestGLIF2(unittest.TestCase):
|
||||
self.assertRaises(GlifLibError, self.pyToGLIF, py)
|
||||
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):
|
||||
glif = """
|
||||
<glyph name="a" format="2">
|
||||
|
@ -6,4 +6,4 @@ mypy>=0.782
|
||||
readme_renderer[md]>=43.0
|
||||
|
||||
# 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"
|
||||
# this is only required to run Tests/cu2qu/{ufo,cli}_test.py
|
||||
ufoLib2==0.16.0
|
||||
ufo2ft==3.3.0
|
||||
ufo2ft==3.3.1
|
||||
pyobjc==10.3.1; sys_platform == "darwin"
|
||||
freetype-py==2.5.1
|
||||
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
|
||||
sympy==1.13.3
|
||||
|
Loading…
x
Reference in New Issue
Block a user