From 8ad8f7631c9eb93dff11a95cb7334a83888cc782 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 9 Jul 2018 20:12:21 +0100 Subject: [PATCH 1/4] glifLib: writeGlyphToString must include the xml declaration This is a regression from ufoLib v2.1.1, see https://github.com/adobe-type-tools/afdko/pull/462#issuecomment-403577134 https://travis-ci.org/adobe-type-tools/afdko/jobs/401751859#L8427 --- Lib/ufoLib/glifLib.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/ufoLib/glifLib.py b/Lib/ufoLib/glifLib.py index 2949d0e..816570c 100755 --- a/Lib/ufoLib/glifLib.py +++ b/Lib/ufoLib/glifLib.py @@ -628,7 +628,10 @@ def writeGlyphToString(glyphName, glyphObject=None, drawPointsFunc=None, formatV _writeLib(glyphObject, root, validate) # return the text tree = etree.ElementTree(root) - text = etree.tostring(root, encoding=unicode, pretty_print=True) + data = etree.tostring( + root, encoding="utf-8", xml_declaration=True, pretty_print=True + ) + text = data.decode("utf-8") return text From 7bbdf6eef279337438687300f5ea5cd5ec42c846 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 9 Jul 2018 20:14:00 +0100 Subject: [PATCH 2/4] test: skip first line in pyToGLIF helper function --- Lib/ufoLib/test/test_GLIF1.py | 5 +++-- Lib/ufoLib/test/test_GLIF2.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/ufoLib/test/test_GLIF1.py b/Lib/ufoLib/test/test_GLIF1.py index bea7e0d..7c3f0e4 100644 --- a/Lib/ufoLib/test/test_GLIF1.py +++ b/Lib/ufoLib/test/test_GLIF1.py @@ -25,8 +25,9 @@ def pyToGLIF(self, py): glyph = Glyph() exec(py, {"glyph" : glyph, "pointPen" : glyph}) glif = writeGlyphToString(glyph.name, glyphObject=glyph, drawPointsFunc=glyph.drawPoints, formatVersion=1, validate=True) - glif = "\n".join(glif.splitlines()) - return glif + lines = iter(glif.splitlines()) + next(lines) # discard the first line containing the xml declaration + return "\n".join(lines) def glifToPy(self, glif): glif = stripText(glif) diff --git a/Lib/ufoLib/test/test_GLIF2.py b/Lib/ufoLib/test/test_GLIF2.py index a234e57..e26434e 100644 --- a/Lib/ufoLib/test/test_GLIF2.py +++ b/Lib/ufoLib/test/test_GLIF2.py @@ -25,8 +25,9 @@ def pyToGLIF(self, py): glyph = Glyph() exec(py, {"glyph" : glyph, "pointPen" : glyph}) glif = writeGlyphToString(glyph.name, glyphObject=glyph, drawPointsFunc=glyph.drawPoints, formatVersion=2, validate=True) - glif = "\n".join(glif.splitlines()) - return glif + lines = iter(glif.splitlines()) + next(lines) # discard the first line containing the xml declaration + return "\n".join(lines) def glifToPy(self, glif): glif = stripText(glif) From c51ca22a6b41c828fa938c50700c5fd85f3b2db4 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Tue, 10 Jul 2018 12:23:22 +0100 Subject: [PATCH 3/4] test_GLIF{1,2}: use itertools.islice to skip first line with xml declaration --- Lib/ufoLib/test/test_GLIF1.py | 6 +++--- Lib/ufoLib/test/test_GLIF2.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/ufoLib/test/test_GLIF1.py b/Lib/ufoLib/test/test_GLIF1.py index 7c3f0e4..51acff3 100644 --- a/Lib/ufoLib/test/test_GLIF1.py +++ b/Lib/ufoLib/test/test_GLIF1.py @@ -2,6 +2,7 @@ import unittest from ufoLib.glifLib import GlifLibError, readGlyphFromString, writeGlyphToString from ufoLib.test.testSupport import Glyph, stripText +from itertools import islice try: basestring @@ -25,9 +26,8 @@ def pyToGLIF(self, py): glyph = Glyph() exec(py, {"glyph" : glyph, "pointPen" : glyph}) glif = writeGlyphToString(glyph.name, glyphObject=glyph, drawPointsFunc=glyph.drawPoints, formatVersion=1, validate=True) - lines = iter(glif.splitlines()) - next(lines) # discard the first line containing the xml declaration - return "\n".join(lines) + # discard the first line containing the xml declaration + return "\n".join(islice(glif.splitlines(), 1, None)) def glifToPy(self, glif): glif = stripText(glif) diff --git a/Lib/ufoLib/test/test_GLIF2.py b/Lib/ufoLib/test/test_GLIF2.py index e26434e..716c5f3 100644 --- a/Lib/ufoLib/test/test_GLIF2.py +++ b/Lib/ufoLib/test/test_GLIF2.py @@ -2,6 +2,7 @@ import unittest from ufoLib.glifLib import GlifLibError, readGlyphFromString, writeGlyphToString from ufoLib.test.testSupport import Glyph, stripText +from itertools import islice try: basestring @@ -25,9 +26,8 @@ def pyToGLIF(self, py): glyph = Glyph() exec(py, {"glyph" : glyph, "pointPen" : glyph}) glif = writeGlyphToString(glyph.name, glyphObject=glyph, drawPointsFunc=glyph.drawPoints, formatVersion=2, validate=True) - lines = iter(glif.splitlines()) - next(lines) # discard the first line containing the xml declaration - return "\n".join(lines) + # discard the first line containing the xml declaration + return "\n".join(islice(glif.splitlines(), 1, None)) def glifToPy(self, glif): glif = stripText(glif) From 864bb26eb4ce1dc392eee926cb94c104042907ca Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Tue, 10 Jul 2018 12:37:15 +0100 Subject: [PATCH 4/4] glifLib: avoid re-encoding str to utf-8 when writing to file the writeGlyphToString will still return a unicode string, like it's always done. However, in the writeGlyph method we can call a private _writeGlyphToBytes and write UTF-8 bytes directly instead of decoding them, then re-encoding them as we write to the file. --- Lib/ufoLib/glifLib.py | 91 ++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/Lib/ufoLib/glifLib.py b/Lib/ufoLib/glifLib.py index 816570c..252a713 100755 --- a/Lib/ufoLib/glifLib.py +++ b/Lib/ufoLib/glifLib.py @@ -16,7 +16,7 @@ from io import BytesIO, open from warnings import warn from collections import OrderedDict -from fontTools.misc.py23 import tobytes, unicode +from fontTools.misc.py23 import basestring, unicode from ufoLib.plistlib import PlistWriter, readPlist, writePlist from ufoLib.plistFromETree import readPlistFromTree from ufoLib.pointPen import AbstractPointPen, PointToSegmentPen @@ -24,16 +24,6 @@ from ufoLib.validators import isDictEnough, genericTypeValidator, colorValidator,\ guidelinesValidator, anchorsValidator, identifierValidator, imageValidator, glyphLibValidator -try: - basestring -except NameError: - basestring = str - -try: - unicode -except NameError: - unicode = str - from lxml import etree @@ -388,7 +378,7 @@ def writeGlyph(self, glyphName, glyphObject=None, drawPointsFunc=None, formatVer if validate is None: validate = self._validateWrite self._purgeCachedGLIF(glyphName) - data = writeGlyphToString(glyphName, glyphObject, drawPointsFunc, formatVersion=formatVersion, validate=validate) + data = _writeGlyphToBytes(glyphName, glyphObject, drawPointsFunc, formatVersion=formatVersion, validate=validate) fileName = self.contents.get(glyphName) if fileName is None: if self._existingFileNames is None: @@ -407,7 +397,7 @@ def writeGlyph(self, glyphName, glyphObject=None, drawPointsFunc=None, formatVer if data == oldData: return with open(path, "wb") as f: - f.write(tobytes(data, encoding="utf-8")) + f.write(data) def deleteGlyph(self, glyphName): """Permanently delete the glyph from the glyph set on disk. Will @@ -560,34 +550,10 @@ def readGlyphFromString(aString, glyphObject=None, pointPen=None, formatVersions _readGlyphFromTree(tree, glyphObject, pointPen, formatVersions=formatVersions, validate=validate) -def writeGlyphToString(glyphName, glyphObject=None, drawPointsFunc=None, formatVersion=2, validate=True): - """ - Return .glif data for a glyph as a UTF-8 encoded string. - The 'glyphObject' argument can be any kind of object (even None); - the writeGlyphToString() method will attempt to get the following - attributes from it: - "width" the advance width of the glyph - "height" the advance height of the glyph - "unicodes" a list of unicode values for this glyph - "note" a string - "lib" a dictionary containing custom data - "image" a dictionary containing image data - "guidelines" a list of guideline data dictionaries - "anchors" a list of anchor data dictionaries - - All attributes are optional: if 'glyphObject' doesn't - have the attribute, it will simply be skipped. - - To write outline data to the .glif file, writeGlyphToString() needs - a function (any callable object actually) that will take one - argument: an object that conforms to the PointPen protocol. - The function will be called by writeGlyphToString(); it has to call the - proper PointPen methods to transfer the outline to the .glif file. - - The GLIF format version can be specified with the formatVersion argument. - - ``validate`` will validate the written data. It is set to ``True`` by default. - """ +def _writeGlyphToBytes( + glyphName, glyphObject=None, drawPointsFunc=None, writer=None, + formatVersion=2, validate=True): + """Return .glif data for a glyph as a UTF-8 encoded bytes string.""" # start if validate and not isinstance(glyphName, basestring): raise GlifLibError("The glyph name is not properly formatted.") @@ -627,12 +593,49 @@ def writeGlyphToString(glyphName, glyphObject=None, drawPointsFunc=None, formatV if getattr(glyphObject, "lib", None): _writeLib(glyphObject, root, validate) # return the text - tree = etree.ElementTree(root) data = etree.tostring( root, encoding="utf-8", xml_declaration=True, pretty_print=True ) - text = data.decode("utf-8") - return text + return data + + +def writeGlyphToString(glyphName, glyphObject=None, drawPointsFunc=None, formatVersion=2, validate=True): + """ + Return .glif data for a glyph as a Unicode string (`unicode` in py2, `str` + in py3). The XML declaration's encoding is always set to "UTF-8". + The 'glyphObject' argument can be any kind of object (even None); + the writeGlyphToString() method will attempt to get the following + attributes from it: + "width" the advance width of the glyph + "height" the advance height of the glyph + "unicodes" a list of unicode values for this glyph + "note" a string + "lib" a dictionary containing custom data + "image" a dictionary containing image data + "guidelines" a list of guideline data dictionaries + "anchors" a list of anchor data dictionaries + + All attributes are optional: if 'glyphObject' doesn't + have the attribute, it will simply be skipped. + + To write outline data to the .glif file, writeGlyphToString() needs + a function (any callable object actually) that will take one + argument: an object that conforms to the PointPen protocol. + The function will be called by writeGlyphToString(); it has to call the + proper PointPen methods to transfer the outline to the .glif file. + + The GLIF format version can be specified with the formatVersion argument. + + ``validate`` will validate the written data. It is set to ``True`` by default. + """ + data = _writeGlyphToBytes( + glyphName, + glyphObject=glyphObject, + drawPointsFunc=drawPointsFunc, + formatVersion=formatVersion, + validate=validate, + ) + return data.decode("utf-8") def _writeAdvance(glyphObject, element, validate):