[glyf] Support cubic curves
https://github.com/harfbuzz/boring-expansion-spec/issues/41
This commit is contained in:
parent
085b489012
commit
82e0536beb
@ -7,7 +7,7 @@ from fontTools.misc.roundTools import otRound
|
|||||||
from fontTools.pens.basePen import LoggingPen, PenError
|
from fontTools.pens.basePen import LoggingPen, PenError
|
||||||
from fontTools.pens.transformPen import TransformPen, TransformPointPen
|
from fontTools.pens.transformPen import TransformPen, TransformPointPen
|
||||||
from fontTools.ttLib.tables import ttProgram
|
from fontTools.ttLib.tables import ttProgram
|
||||||
from fontTools.ttLib.tables._g_l_y_f import flagOnCurve
|
from fontTools.ttLib.tables._g_l_y_f import flagOnCurve, flagCubic
|
||||||
from fontTools.ttLib.tables._g_l_y_f import Glyph
|
from fontTools.ttLib.tables._g_l_y_f import Glyph
|
||||||
from fontTools.ttLib.tables._g_l_y_f import GlyphComponent
|
from fontTools.ttLib.tables._g_l_y_f import GlyphComponent
|
||||||
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
|
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
|
||||||
@ -220,9 +220,9 @@ class TTGlyphPen(_TTGlyphBasePen, LoggingPen):
|
|||||||
super().__init__(glyphSet, handleOverflowingTransforms)
|
super().__init__(glyphSet, handleOverflowingTransforms)
|
||||||
self.outputImpliedClosingLine = outputImpliedClosingLine
|
self.outputImpliedClosingLine = outputImpliedClosingLine
|
||||||
|
|
||||||
def _addPoint(self, pt: Tuple[float, float], onCurve: int) -> None:
|
def _addPoint(self, pt: Tuple[float, float], tp: int) -> None:
|
||||||
self.points.append(pt)
|
self.points.append(pt)
|
||||||
self.types.append(onCurve)
|
self.types.append(tp)
|
||||||
|
|
||||||
def _popPoint(self) -> None:
|
def _popPoint(self) -> None:
|
||||||
self.points.pop()
|
self.points.pop()
|
||||||
@ -234,15 +234,21 @@ class TTGlyphPen(_TTGlyphBasePen, LoggingPen):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def lineTo(self, pt: Tuple[float, float]) -> None:
|
def lineTo(self, pt: Tuple[float, float]) -> None:
|
||||||
self._addPoint(pt, 1)
|
self._addPoint(pt, flagOnCurve)
|
||||||
|
|
||||||
def moveTo(self, pt: Tuple[float, float]) -> None:
|
def moveTo(self, pt: Tuple[float, float]) -> None:
|
||||||
if not self._isClosed():
|
if not self._isClosed():
|
||||||
raise PenError('"move"-type point must begin a new contour.')
|
raise PenError('"move"-type point must begin a new contour.')
|
||||||
self._addPoint(pt, 1)
|
self._addPoint(pt, flagOnCurve)
|
||||||
|
|
||||||
def curveTo(self, *points) -> None:
|
def curveTo(self, *points) -> None:
|
||||||
raise NotImplementedError
|
assert len(points) % 2 == 1
|
||||||
|
for pt in points[:-1]:
|
||||||
|
self._addPoint(pt, flagCubic)
|
||||||
|
|
||||||
|
# last point is None if there are no on-curve points
|
||||||
|
if points[-1] is not None:
|
||||||
|
self._addPoint(points[-1], 1)
|
||||||
|
|
||||||
def qCurveTo(self, *points) -> None:
|
def qCurveTo(self, *points) -> None:
|
||||||
assert len(points) >= 1
|
assert len(points) >= 1
|
||||||
@ -313,9 +319,23 @@ class TTGlyphPointPen(_TTGlyphBasePen, LogMixin, AbstractPointPen):
|
|||||||
raise PenError("Contour is already closed.")
|
raise PenError("Contour is already closed.")
|
||||||
if self._currentContourStartIndex == len(self.points):
|
if self._currentContourStartIndex == len(self.points):
|
||||||
raise PenError("Tried to end an empty contour.")
|
raise PenError("Tried to end an empty contour.")
|
||||||
|
|
||||||
|
contourStart = self.endPts[-1] + 1 if self.endPts else 0
|
||||||
self.endPts.append(len(self.points) - 1)
|
self.endPts.append(len(self.points) - 1)
|
||||||
self._currentContourStartIndex = None
|
self._currentContourStartIndex = None
|
||||||
|
|
||||||
|
# Resolve types for any cubic segments
|
||||||
|
flags = self.types
|
||||||
|
for i in range(contourStart, len(flags)):
|
||||||
|
if flags[i] == "curve":
|
||||||
|
j = i - 1
|
||||||
|
if j < contourStart:
|
||||||
|
j = len(flags) - 1
|
||||||
|
while flags[j] == 0:
|
||||||
|
flags[j] = flagCubic
|
||||||
|
j -= 1
|
||||||
|
flags[i] = flagOnCurve
|
||||||
|
|
||||||
def addPoint(
|
def addPoint(
|
||||||
self,
|
self,
|
||||||
pt: Tuple[float, float],
|
pt: Tuple[float, float],
|
||||||
@ -331,11 +351,13 @@ class TTGlyphPointPen(_TTGlyphBasePen, LogMixin, AbstractPointPen):
|
|||||||
if self._isClosed():
|
if self._isClosed():
|
||||||
raise PenError("Can't add a point to a closed contour.")
|
raise PenError("Can't add a point to a closed contour.")
|
||||||
if segmentType is None:
|
if segmentType is None:
|
||||||
self.types.append(0) # offcurve
|
self.types.append(0)
|
||||||
elif segmentType in ("qcurve", "line", "move"):
|
elif segmentType in ("line", "move"):
|
||||||
self.types.append(1) # oncurve
|
self.types.append(flagOnCurve)
|
||||||
|
elif segmentType == "qcurve":
|
||||||
|
self.types.append(flagOnCurve)
|
||||||
elif segmentType == "curve":
|
elif segmentType == "curve":
|
||||||
raise NotImplementedError("cubic curves are not supported")
|
self.types.append("curve")
|
||||||
else:
|
else:
|
||||||
raise AssertionError(segmentType)
|
raise AssertionError(segmentType)
|
||||||
|
|
||||||
|
@ -569,10 +569,10 @@ flagRepeat = 0x08
|
|||||||
flagXsame = 0x10
|
flagXsame = 0x10
|
||||||
flagYsame = 0x20
|
flagYsame = 0x20
|
||||||
flagOverlapSimple = 0x40
|
flagOverlapSimple = 0x40
|
||||||
flagReserved = 0x80
|
flagCubic = 0x80
|
||||||
|
|
||||||
# These flags are kept for XML output after decompiling the coordinates
|
# These flags are kept for XML output after decompiling the coordinates
|
||||||
keepFlags = flagOnCurve + flagOverlapSimple
|
keepFlags = flagOnCurve + flagOverlapSimple + flagCubic
|
||||||
|
|
||||||
_flagSignBytes = {
|
_flagSignBytes = {
|
||||||
0: 2,
|
0: 2,
|
||||||
@ -764,6 +764,8 @@ class Glyph(object):
|
|||||||
if self.flags[j] & flagOverlapSimple:
|
if self.flags[j] & flagOverlapSimple:
|
||||||
# Apple's rasterizer uses flagOverlapSimple in the first contour/first pt to flag glyphs that contain overlapping contours
|
# Apple's rasterizer uses flagOverlapSimple in the first contour/first pt to flag glyphs that contain overlapping contours
|
||||||
attrs.append(("overlap", 1))
|
attrs.append(("overlap", 1))
|
||||||
|
if self.flags[j] & flagCubic:
|
||||||
|
attrs.append(("cubic", 1))
|
||||||
writer.simpletag("pt", attrs)
|
writer.simpletag("pt", attrs)
|
||||||
writer.newline()
|
writer.newline()
|
||||||
last = self.endPtsOfContours[i] + 1
|
last = self.endPtsOfContours[i] + 1
|
||||||
@ -797,6 +799,8 @@ class Glyph(object):
|
|||||||
flag = bool(safeEval(attrs["on"]))
|
flag = bool(safeEval(attrs["on"]))
|
||||||
if "overlap" in attrs and bool(safeEval(attrs["overlap"])):
|
if "overlap" in attrs and bool(safeEval(attrs["overlap"])):
|
||||||
flag |= flagOverlapSimple
|
flag |= flagOverlapSimple
|
||||||
|
if "cubic" in attrs and bool(safeEval(attrs["cubic"])):
|
||||||
|
flag |= flagCubic
|
||||||
flags.append(flag)
|
flags.append(flag)
|
||||||
if not hasattr(self, "coordinates"):
|
if not hasattr(self, "coordinates"):
|
||||||
self.coordinates = coordinates
|
self.coordinates = coordinates
|
||||||
@ -1416,20 +1420,27 @@ class Glyph(object):
|
|||||||
end = end + 1
|
end = end + 1
|
||||||
contour = coordinates[start:end]
|
contour = coordinates[start:end]
|
||||||
cFlags = [flagOnCurve & f for f in flags[start:end]]
|
cFlags = [flagOnCurve & f for f in flags[start:end]]
|
||||||
|
cuFlags = [flagCubic & f for f in flags[start:end]]
|
||||||
start = end
|
start = end
|
||||||
if 1 not in cFlags:
|
if 1 not in cFlags:
|
||||||
# There is not a single on-curve point on the curve,
|
# There is not a single on-curve point on the curve,
|
||||||
# use pen.qCurveTo's special case by specifying None
|
# use pen.qCurveTo's special case by specifying None
|
||||||
# as the on-curve point.
|
# as the on-curve point.
|
||||||
contour.append(None)
|
contour.append(None)
|
||||||
pen.qCurveTo(*contour)
|
assert all(cuFlags) or not any(cuFlags)
|
||||||
|
cubic = all(cuFlags)
|
||||||
|
if cubic:
|
||||||
|
pen.curveTo(*contour)
|
||||||
|
else:
|
||||||
|
pen.qCurveTo(*contour)
|
||||||
else:
|
else:
|
||||||
# Shuffle the points so that contour the is guaranteed
|
# Shuffle the points so that the contour is guaranteed
|
||||||
# to *end* in an on-curve point, which we'll use for
|
# to *end* in an on-curve point, which we'll use for
|
||||||
# the moveTo.
|
# the moveTo.
|
||||||
firstOnCurve = cFlags.index(1) + 1
|
firstOnCurve = cFlags.index(1) + 1
|
||||||
contour = contour[firstOnCurve:] + contour[:firstOnCurve]
|
contour = contour[firstOnCurve:] + contour[:firstOnCurve]
|
||||||
cFlags = cFlags[firstOnCurve:] + cFlags[:firstOnCurve]
|
cFlags = cFlags[firstOnCurve:] + cFlags[:firstOnCurve]
|
||||||
|
cuFlags = cuFlags[firstOnCurve:] + cuFlags[:firstOnCurve]
|
||||||
pen.moveTo(contour[-1])
|
pen.moveTo(contour[-1])
|
||||||
while contour:
|
while contour:
|
||||||
nextOnCurve = cFlags.index(1) + 1
|
nextOnCurve = cFlags.index(1) + 1
|
||||||
@ -1439,9 +1450,22 @@ class Glyph(object):
|
|||||||
if len(contour) > 1:
|
if len(contour) > 1:
|
||||||
pen.lineTo(contour[0])
|
pen.lineTo(contour[0])
|
||||||
else:
|
else:
|
||||||
pen.qCurveTo(*contour[:nextOnCurve])
|
cubicFlags = [f for f in cuFlags[: nextOnCurve - 1]]
|
||||||
|
assert all(cubicFlags) or not any(cubicFlags)
|
||||||
|
cubic = any(cubicFlags)
|
||||||
|
if cubic:
|
||||||
|
assert all(
|
||||||
|
cubicFlags
|
||||||
|
), "Mixed segments not currently supported"
|
||||||
|
assert (
|
||||||
|
len(cubicFlags) == 2
|
||||||
|
), "Cubic multi-segments not currently supported"
|
||||||
|
pen.curveTo(*contour[:nextOnCurve])
|
||||||
|
else:
|
||||||
|
pen.qCurveTo(*contour[:nextOnCurve])
|
||||||
contour = contour[nextOnCurve:]
|
contour = contour[nextOnCurve:]
|
||||||
cFlags = cFlags[nextOnCurve:]
|
cFlags = cFlags[nextOnCurve:]
|
||||||
|
cuFlags = cuFlags[nextOnCurve:]
|
||||||
pen.closePath()
|
pen.closePath()
|
||||||
|
|
||||||
def drawPoints(self, pen, glyfTable, offset=0):
|
def drawPoints(self, pen, glyfTable, offset=0):
|
||||||
@ -1474,7 +1498,7 @@ class Glyph(object):
|
|||||||
segmentType = "line"
|
segmentType = "line"
|
||||||
else:
|
else:
|
||||||
pen.addPoint(pt)
|
pen.addPoint(pt)
|
||||||
segmentType = "qcurve"
|
segmentType = "curve" if cFlags[i] & flagCubic else "qcurve"
|
||||||
pen.endPath()
|
pen.endPath()
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
@ -332,11 +332,11 @@ class TTGlyphPointPenTest(TTGlyphPenTestBase):
|
|||||||
assert glyph.numberOfContours == 1
|
assert glyph.numberOfContours == 1
|
||||||
assert glyph.endPtsOfContours == [3]
|
assert glyph.endPtsOfContours == [3]
|
||||||
|
|
||||||
def test_addPoint_errorOnCurve(self):
|
def test_addPoint_noErrorOnCurve(self):
|
||||||
pen = TTGlyphPointPen(None)
|
pen = TTGlyphPointPen(None)
|
||||||
pen.beginPath()
|
pen.beginPath()
|
||||||
with pytest.raises(NotImplementedError):
|
pen.addPoint((0, 0), "curve")
|
||||||
pen.addPoint((0, 0), "curve")
|
pen.endPath()
|
||||||
|
|
||||||
def test_beginPath_beginPathOnOpenPath(self):
|
def test_beginPath_beginPathOnOpenPath(self):
|
||||||
pen = TTGlyphPointPen(None)
|
pen = TTGlyphPointPen(None)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user