[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.transformPen import TransformPen, TransformPointPen
|
||||
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 GlyphComponent
|
||||
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
|
||||
@ -220,9 +220,9 @@ class TTGlyphPen(_TTGlyphBasePen, LoggingPen):
|
||||
super().__init__(glyphSet, handleOverflowingTransforms)
|
||||
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.types.append(onCurve)
|
||||
self.types.append(tp)
|
||||
|
||||
def _popPoint(self) -> None:
|
||||
self.points.pop()
|
||||
@ -234,15 +234,21 @@ class TTGlyphPen(_TTGlyphBasePen, LoggingPen):
|
||||
)
|
||||
|
||||
def lineTo(self, pt: Tuple[float, float]) -> None:
|
||||
self._addPoint(pt, 1)
|
||||
self._addPoint(pt, flagOnCurve)
|
||||
|
||||
def moveTo(self, pt: Tuple[float, float]) -> None:
|
||||
if not self._isClosed():
|
||||
raise PenError('"move"-type point must begin a new contour.')
|
||||
self._addPoint(pt, 1)
|
||||
self._addPoint(pt, flagOnCurve)
|
||||
|
||||
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:
|
||||
assert len(points) >= 1
|
||||
@ -313,9 +319,23 @@ class TTGlyphPointPen(_TTGlyphBasePen, LogMixin, AbstractPointPen):
|
||||
raise PenError("Contour is already closed.")
|
||||
if self._currentContourStartIndex == len(self.points):
|
||||
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._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(
|
||||
self,
|
||||
pt: Tuple[float, float],
|
||||
@ -331,11 +351,13 @@ class TTGlyphPointPen(_TTGlyphBasePen, LogMixin, AbstractPointPen):
|
||||
if self._isClosed():
|
||||
raise PenError("Can't add a point to a closed contour.")
|
||||
if segmentType is None:
|
||||
self.types.append(0) # offcurve
|
||||
elif segmentType in ("qcurve", "line", "move"):
|
||||
self.types.append(1) # oncurve
|
||||
self.types.append(0)
|
||||
elif segmentType in ("line", "move"):
|
||||
self.types.append(flagOnCurve)
|
||||
elif segmentType == "qcurve":
|
||||
self.types.append(flagOnCurve)
|
||||
elif segmentType == "curve":
|
||||
raise NotImplementedError("cubic curves are not supported")
|
||||
self.types.append("curve")
|
||||
else:
|
||||
raise AssertionError(segmentType)
|
||||
|
||||
|
@ -569,10 +569,10 @@ flagRepeat = 0x08
|
||||
flagXsame = 0x10
|
||||
flagYsame = 0x20
|
||||
flagOverlapSimple = 0x40
|
||||
flagReserved = 0x80
|
||||
flagCubic = 0x80
|
||||
|
||||
# These flags are kept for XML output after decompiling the coordinates
|
||||
keepFlags = flagOnCurve + flagOverlapSimple
|
||||
keepFlags = flagOnCurve + flagOverlapSimple + flagCubic
|
||||
|
||||
_flagSignBytes = {
|
||||
0: 2,
|
||||
@ -764,6 +764,8 @@ class Glyph(object):
|
||||
if self.flags[j] & flagOverlapSimple:
|
||||
# Apple's rasterizer uses flagOverlapSimple in the first contour/first pt to flag glyphs that contain overlapping contours
|
||||
attrs.append(("overlap", 1))
|
||||
if self.flags[j] & flagCubic:
|
||||
attrs.append(("cubic", 1))
|
||||
writer.simpletag("pt", attrs)
|
||||
writer.newline()
|
||||
last = self.endPtsOfContours[i] + 1
|
||||
@ -797,6 +799,8 @@ class Glyph(object):
|
||||
flag = bool(safeEval(attrs["on"]))
|
||||
if "overlap" in attrs and bool(safeEval(attrs["overlap"])):
|
||||
flag |= flagOverlapSimple
|
||||
if "cubic" in attrs and bool(safeEval(attrs["cubic"])):
|
||||
flag |= flagCubic
|
||||
flags.append(flag)
|
||||
if not hasattr(self, "coordinates"):
|
||||
self.coordinates = coordinates
|
||||
@ -1416,20 +1420,27 @@ class Glyph(object):
|
||||
end = end + 1
|
||||
contour = coordinates[start:end]
|
||||
cFlags = [flagOnCurve & f for f in flags[start:end]]
|
||||
cuFlags = [flagCubic & f for f in flags[start:end]]
|
||||
start = end
|
||||
if 1 not in cFlags:
|
||||
# There is not a single on-curve point on the curve,
|
||||
# use pen.qCurveTo's special case by specifying None
|
||||
# as the on-curve point.
|
||||
contour.append(None)
|
||||
assert all(cuFlags) or not any(cuFlags)
|
||||
cubic = all(cuFlags)
|
||||
if cubic:
|
||||
pen.curveTo(*contour)
|
||||
else:
|
||||
pen.qCurveTo(*contour)
|
||||
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
|
||||
# the moveTo.
|
||||
firstOnCurve = cFlags.index(1) + 1
|
||||
contour = contour[firstOnCurve:] + contour[:firstOnCurve]
|
||||
cFlags = cFlags[firstOnCurve:] + cFlags[:firstOnCurve]
|
||||
cuFlags = cuFlags[firstOnCurve:] + cuFlags[:firstOnCurve]
|
||||
pen.moveTo(contour[-1])
|
||||
while contour:
|
||||
nextOnCurve = cFlags.index(1) + 1
|
||||
@ -1438,10 +1449,23 @@ class Glyph(object):
|
||||
# pen.closePath()
|
||||
if len(contour) > 1:
|
||||
pen.lineTo(contour[0])
|
||||
else:
|
||||
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:]
|
||||
cFlags = cFlags[nextOnCurve:]
|
||||
cuFlags = cuFlags[nextOnCurve:]
|
||||
pen.closePath()
|
||||
|
||||
def drawPoints(self, pen, glyfTable, offset=0):
|
||||
@ -1474,7 +1498,7 @@ class Glyph(object):
|
||||
segmentType = "line"
|
||||
else:
|
||||
pen.addPoint(pt)
|
||||
segmentType = "qcurve"
|
||||
segmentType = "curve" if cFlags[i] & flagCubic else "qcurve"
|
||||
pen.endPath()
|
||||
|
||||
def __eq__(self, other):
|
||||
|
@ -332,11 +332,11 @@ class TTGlyphPointPenTest(TTGlyphPenTestBase):
|
||||
assert glyph.numberOfContours == 1
|
||||
assert glyph.endPtsOfContours == [3]
|
||||
|
||||
def test_addPoint_errorOnCurve(self):
|
||||
def test_addPoint_noErrorOnCurve(self):
|
||||
pen = TTGlyphPointPen(None)
|
||||
pen.beginPath()
|
||||
with pytest.raises(NotImplementedError):
|
||||
pen.addPoint((0, 0), "curve")
|
||||
pen.endPath()
|
||||
|
||||
def test_beginPath_beginPathOnOpenPath(self):
|
||||
pen = TTGlyphPointPen(None)
|
||||
|
Loading…
x
Reference in New Issue
Block a user