def test_stringifyattrs(self): writer = XMLWriter(BytesIO()) expected = ' attr="0"' self.assertEqual(expected, writer.stringifyattrs(attr=0)) self.assertEqual(expected, writer.stringifyattrs(attr=b'0')) self.assertEqual(expected, writer.stringifyattrs(attr='0')) self.assertEqual(expected, writer.stringifyattrs(attr=u'0'))
def test_sparse_mmotf(tmpdir): base = "%s/sparse_masters" % DATA_DIR paths = sorted(glob.glob(base + "/*.otf")) # the reference font is modified in-place, make a temp copy first # MasterSet_Kanji-w0.00.otf has to be the reference font. reference = make_temp_copy(tmpdir, paths[0]) inpaths = paths[1:] outpaths = [str(tmpdir / basename(p)) + ".out" for p in inpaths] options = Options(reference, inpaths, outpaths) options.allow_no_blues = True hintFiles(options) refs = [p + ".ref" for p in paths] for ref, out in zip(refs, [reference] + outpaths): for path in (ref, out): font = TTFont(path) assert "CFF " in font writer = XMLWriter(str(tmpdir / basename(path)) + ".xml") font["CFF "].toXML(writer, font) writer.close() assert differ([ str(tmpdir / basename(ref)) + ".xml", str(tmpdir / basename(out)) + ".xml" ])
def test_dumphex(self): writer = XMLWriter(BytesIO()) writer.dumphex("Type is a beautiful group of letters, not a group of beautiful letters.") self.assertEqual(HEADER + bytesjoin([ "54797065 20697320 61206265 61757469", "66756c20 67726f75 70206f66 206c6574", "74657273 2c206e6f 74206120 67726f75", "70206f66 20626561 75746966 756c206c", "65747465 72732e ", ""], joiner=linesep), writer.file.getvalue())
def getXML(func, ttFont=None): """Call the passed toXML function and return the written content as string. Result is stripped of XML declaration and OS-specific newline characters. """ writer = XMLWriter(BytesIO()) # don't write OS-specific new lines writer.newlinestr = writer.totype('') # erase XML declaration writer.file.seek(0) writer.file.truncate() func(writer, ttFont) xml = writer.file.getvalue().decode("utf-8") return xml
def getXML(obj, ttFont): """Call the object's toXML() method and return the writer's content as string. Result is stripped of XML declaration and OS-specific newline characters. """ writer = XMLWriter(BytesIO()) # don't write OS-specific new lines writer.newlinestr = writer.totype('') # erase XML declaration writer.file.seek(0) writer.file.truncate() obj.toXML(writer, ttFont) xml = writer.file.getvalue().decode("utf-8") return xml
def _newPage(self, width, height): if hasattr(self, "_svgContext"): self._svgContext.endtag("svg") self.reset() self.size(width, height) self._svgData = self._svgFileClass() self._pages.append(self._svgData) self._svgContext = XMLWriter(self._svgData, encoding="utf-8", indentwhite=self.indentation) self._svgContext.width = self.width self._svgContext.height = self.height attrs = [('width', self.width), ('height', self.height), ('viewBox', f"0 0 {self.width} {self.height}")] self._svgContext.begintag("svg", attrs + self._svgTagArguments) self._svgContext.newline() self._state.transformMatrix = self._state.transformMatrix.scale(1, -1).translate(0, -self.height)
def test_otf(otf, tmpdir): out = str(tmpdir / basename(otf)) + ".out" options = Options(otf, out) hintFiles(options) for path in (otf, out): font = TTFont(path) assert "CFF " in font writer = XMLWriter(str(tmpdir / basename(path)) + ".xml") font["CFF "].toXML(writer, font) writer.close() assert differ([str(tmpdir / basename(otf)) + ".xml", str(tmpdir / basename(out)) + ".xml"])
def test_begintag_endtag(self): writer = XMLWriter(BytesIO()) writer.begintag("tag", attr="value") writer.write("content") writer.endtag("tag") self.assertEqual(HEADER + b'<tag attr="value">content</tag>', writer.file.getvalue())
def test_cff(cff, tmpdir): out = str(tmpdir / basename(cff)) + ".out" options = Options(cff, out) hintFiles(options) for path in (cff, out): font = CFFFontSet() writer = XMLWriter(str(tmpdir / basename(path)) + ".xml") with open(path, "rb") as fp: font.decompile(fp, None) font.toXML(writer) writer.close() assert differ([str(tmpdir / basename(cff)) + ".xml", str(tmpdir / basename(out)) + ".xml"])
def test_newlinestr(self): header = b'<?xml version="1.0" encoding="UTF-8"?>' for nls in (None, '\n', '\r\n', '\r', ''): writer = XMLWriter(BytesIO(), newlinestr=nls) writer.write("hello") writer.newline() writer.write("world") writer.newline() linesep = tobytes(os.linesep) if nls is None else tobytes(nls) self.assertEqual( header + linesep + b"hello" + linesep + b"world" + linesep, writer.file.getvalue())
def test_vfotf(otf, tmpdir): out = str(tmpdir / basename(otf)) + ".out" options = Options(None, [otf], [out]) options.allow_no_blues = True hintFiles(options) for path in (otf, out): font = TTFont(path) assert "CFF2" in font writer = XMLWriter(str(tmpdir / basename(path)) + ".xml") font["CFF2"].toXML(writer, font) writer.close() assert differ([ str(tmpdir / basename(otf)) + ".xml", str(tmpdir / basename(out)) + ".xml" ])
def __init__(self, metaDataPath, fontPath): self.writer = XMLWriter(metaDataPath, encoding="UTF-8") self.font = Font(fontPath) self.beginMetaData() self.uniqueId() self.vendor() self.credits() self.description() self.license() self.copyright() self.trademark() self.endMetaData() self.writer.close()
def makeXMLWriter(newlinestr='\n'): # don't write OS-specific new lines writer = XMLWriter(BytesIO(), newlinestr=newlinestr) # erase XML declaration writer.file.seek(0) writer.file.truncate() return writer
def test_decimals_otf(tmpdir): otf = "%s/dummy/decimals.otf" % DATA_DIR out = str(tmpdir / basename(otf)) + ".out" options = Options(otf, out) options.round_coords = False hintFiles(options) for path in (otf, out): font = TTFont(path) assert "CFF " in font writer = XMLWriter(str(tmpdir / basename(path)) + ".xml") font["CFF "].toXML(writer, font) writer.close() assert differ([str(tmpdir / basename(otf)) + ".xml", str(tmpdir / basename(out)) + ".xml"])
def test_toXML(self): table = table__m_e_t_a() table.data["TEST"] = b"\xCA\xFE\xBE\xEF" writer = XMLWriter(BytesIO()) table.toXML(writer, {"meta": table}) xml = writer.file.getvalue().decode("utf-8") self.assertEqual(['<hexdata tag="TEST">', 'cafebeef', '</hexdata>'], [line.strip() for line in xml.splitlines()][1:])
def test_toXML_text(self): table = table__m_e_t_a() table.data["dlng"] = u"Latn,Grek,Cyrl" writer = XMLWriter(BytesIO()) table.toXML(writer, {"meta": table}) xml = writer.file.getvalue().decode("utf-8") self.assertEqual(['<text tag="dlng">', 'Latn,Grek,Cyrl', '</text>'], [line.strip() for line in xml.splitlines()][1:])
def test_toXML_allDeltasNone(self): writer = XMLWriter(BytesIO()) axes = {"wght": (0.0, 1.0, 1.0)} g = GlyphVariation(axes, [None] * 5) g.toXML(writer, ["wght", "wdth"]) self.assertEqual([ '<tuple>', '<coord axis="wght" value="1.0"/>', '<!-- no deltas -->', '</tuple>' ], GlyphVariationTest.xml_lines(writer))
def test_toXML2(self): writer = XMLWriter(StringIO()) table = otTables.MultipleSubst() table.mapping = {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]} table.toXML2(writer, self.font) self.assertEqual(writer.file.getvalue().splitlines()[1:], [ '<Substitution in="c_t" out="c,t"/>', '<Substitution in="f_f_i" out="f,f,i"/>', ])
def test_toXML_ascii_data(self): table = table__m_e_t_a() table.data["TEST"] = b"Hello!" writer = XMLWriter(BytesIO()) table.toXML(writer, {"meta": table}) xml = writer.file.getvalue().decode("utf-8") self.assertEqual([ '<hexdata tag="TEST">', '<!-- ascii: Hello! -->', '48656c6c 6f21', '</hexdata>' ], [line.strip() for line in xml.splitlines()][1:])
def test_toXML2(self): writer = XMLWriter(StringIO()) table = otTables.SingleSubst() table.mapping = {"A": "a", "B": "b", "C": "c"} table.toXML2(writer, self.font) self.assertEqual(writer.file.getvalue().splitlines()[1:], [ '<Substitution in="A" out="a"/>', '<Substitution in="B" out="b"/>', '<Substitution in="C" out="c"/>', ])
def test_toXML(self): avar = table__a_v_a_r() avar.segments["opsz"] = {-1.0: -1.0, 0.0: 0.0, 0.3: 0.8, 1.0: 1.0} writer = XMLWriter(StringIO()) avar.toXML(writer, self.makeFont(["opsz"])) self.assertEqual([ '<segment axis="opsz">', '<mapping from="-1.0" to="-1.0"/>', '<mapping from="0.0" to="0.0"/>', '<mapping from="0.3" to="0.8"/>', '<mapping from="1.0" to="1.0"/>', '</segment>' ], self.xml_lines(writer))
def test_toXML(self): writer = XMLWriter(StringIO()) table = table__l_t_a_g() table.decompile(self.DATA_, ttFont=None) table.toXML(writer, ttFont=None) expected = "\n".join([ '<?xml version="1.0" encoding="UTF-8"?>', '<version value="1"/>', '<flags value="0"/>', '<LanguageTag tag="en"/>', '<LanguageTag tag="zh-Hant"/>', '<LanguageTag tag="zh"/>' ]) + "\n" self.assertEquals(expected.encode("utf_8"), writer.file.getvalue())
def test_toXML(self): writer = XMLWriter(BytesIO()) table = newTable("ltag") table.decompile(self.DATA_, ttFont=None) table.toXML(writer, ttFont=None) expected = os.linesep.join([ '<?xml version="1.0" encoding="UTF-8"?>', '<version value="1"/>', '<flags value="0"/>', '<LanguageTag tag="en"/>', '<LanguageTag tag="zh-Hant"/>', '<LanguageTag tag="zh"/>' ]) + os.linesep self.assertEqual(expected.encode("utf_8"), writer.file.getvalue())
def test_toXML2(self): writer = XMLWriter(StringIO()) table = otTables.AlternateSubst() table.alternates = {"G": ["G.alt2", "G.alt1"], "Z": ["Z.fina"]} table.toXML2(writer, self.font) self.assertEqual(writer.file.getvalue().splitlines()[1:], [ '<AlternateSet glyph="G">', ' <Alternate glyph="G.alt2"/>', ' <Alternate glyph="G.alt1"/>', '</AlternateSet>', '<AlternateSet glyph="Z">', ' <Alternate glyph="Z.fina"/>', '</AlternateSet>' ])
def test_toXML_constants(self): writer = XMLWriter(BytesIO()) g = TupleVariation(AXES, [42, None, 23, 0, -17, None]) g.toXML(writer, ["wdth", "wght", "opsz"]) self.assertEqual([ '<tuple>', '<coord axis="wdth" max="0.5" min="0.3" value="0.4"/>', '<coord axis="wght" value="1.0"/>', '<coord axis="opsz" value="-0.7"/>', '<delta cvt="0" value="42"/>', '<delta cvt="2" value="23"/>', '<delta cvt="3" value="0"/>', '<delta cvt="4" value="-17"/>', '</tuple>' ], TupleVariationTest.xml_lines(writer))
def test_toXML(self): font = MakeFont() writer = XMLWriter(BytesIO()) font["fvar"].toXML(writer, font) xml = writer.file.getvalue().decode("utf-8") self.assertEqual(2, xml.count("<Axis>")) self.assertTrue("<AxisTag>wght</AxisTag>" in xml) self.assertTrue("<AxisTag>wdth</AxisTag>" in xml) self.assertEqual(2, xml.count("<NamedInstance ")) self.assertTrue("<!-- Light -->" in xml) self.assertTrue("<!-- Light Condensed -->" in xml)
def test_toXML_points(self): writer = XMLWriter(BytesIO()) g = TupleVariation(AXES, [(9, 8), None, (7, 6), (0, 0), (-1, -2), None]) g.toXML(writer, ["wdth", "wght", "opsz"]) self.assertEqual([ '<tuple>', '<coord axis="wdth" max="0.5" min="0.3" value="0.4"/>', '<coord axis="wght" value="1.0"/>', '<coord axis="opsz" value="-0.7"/>', '<delta pt="0" x="9" y="8"/>', '<delta pt="2" x="7" y="6"/>', '<delta pt="3" x="0" y="0"/>', '<delta pt="4" x="-1" y="-2"/>', '</tuple>' ], TupleVariationTest.xml_lines(writer))
def test_toXML_badDeltaFormat(self): writer = XMLWriter(BytesIO()) g = TupleVariation(AXES, ["String"]) with CapturingLogHandler(log, "ERROR") as captor: g.toXML(writer, ["wdth"]) self.assertIn("bad delta format", [r.msg for r in captor.records]) self.assertEqual([ '<tuple>', '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>', '<!-- bad delta #0 -->', '</tuple>', ], TupleVariationTest.xml_lines(writer))
def test_xml_indentation(self): with open(TTPROGRAM_TTX, 'r', encoding='utf-8') as f: ttProgramXML = f.read() p = Program() p.fromBytecode(BYTECODE) ttfont = TestFont() buf = StringIO() writer = XMLWriter(buf) try: p.toXML(writer, ttfont) finally: output_string = buf.getvalue() assert output_string == ttProgramXML
def _newPage(self, width, height): if hasattr(self, "_svgContext"): self._svgContext.endtag("svg") self.reset() self.size(width, height) self._svgData = self._svgFileClass() self._pages.append(self._svgData) self._svgContext = XMLWriter(self._svgData, encoding="utf-8", indentwhite=self.indentation) self._svgContext.width = self.width self._svgContext.height = self.height self._svgContext.begintag("svg", width=self.width, height=self.height, **self._svgTagArguments) self._svgContext.newline() self._state.transformMatrix = self._state.transformMatrix.scale(1, -1).translate(0, -self.height)
def test_toXML(self): font = MakeFont() inst = NamedInstance() inst.nameID = AddName(font, "Light Condensed").nameID inst.coordinates = {"wght": 0.7, "wdth": 0.5} writer = XMLWriter(StringIO()) inst.toXML(writer, font) self.assertEqual([ '', '<!-- Light Condensed -->', '<NamedInstance nameID="%s">' % inst.nameID, '<coord axis="wght" value="0.7"/>', '<coord axis="wdth" value="0.5"/>', '</NamedInstance>' ], xml_lines(writer))
class SVGContext(BaseContext): _graphicsStateClass = SVGGraphicsState _shadowClass = SVGShadow _colorClass = SVGColor _gradientClass = SVGGradient _svgFileClass = SVGFile _svgTagArguments = { "version": "1.1", "xmlns": "http://www.w3.org/2000/svg", } _svgLineJoinStylesMap = { AppKit.NSMiterLineJoinStyle: "miter", AppKit.NSRoundLineJoinStyle: "round", AppKit.NSBevelLineJoinStyle: "bevel" } _svgLineCapStylesMap = { AppKit.NSButtLineCapStyle: "butt", AppKit.NSSquareLineCapStyle: "square", AppKit.NSRoundLineCapStyle: "round", } indentation = " " fileExtensions = ["svg"] def __init__(self): super(SVGContext, self).__init__() self._pages = [] # not supported in a svg context def cmykFill(self, c, m, y, k, a=1): warnings.warn("cmykFill is not supported in a svg context") def cmykStroke(self, c, m, y, k, a=1): warnings.warn("cmykStroke is not supported in a svg context") def cmykLinearGradient(self, startPoint=None, endPoint=None, colors=None, locations=None): warnings.warn("cmykLinearGradient is not supported in a svg context") def cmykRadialGradient(self, startPoint=None, endPoint=None, colors=None, locations=None, startRadius=0, endRadius=100): warnings.warn("cmykRadialGradient is not supported in a svg context") def cmykShadow(self, offset, blur, color): warnings.warn("cmykShadow is not supported in a svg context") # svg overwrites def shadow(self, offset, blur, color): super(SVGContext, self).shadow(offset, blur, color) if self._state.shadow is not None: self._state.shadow.writeDefs(self._svgContext) def linearGradient(self, startPoint=None, endPoint=None, colors=None, locations=None): super(SVGContext, self).linearGradient(startPoint, endPoint, colors, locations) if self._state.gradient is not None: self._state.gradient.writeDefs(self._svgContext) def radialGradient(self, startPoint=None, endPoint=None, colors=None, locations=None, startRadius=0, endRadius=100): super(SVGContext, self).radialGradient(startPoint, endPoint, colors, locations, startRadius, endRadius) if startRadius != 0: warnings.warn("radialGradient will clip the startRadius to '0' in a svg context.") if self._state.gradient is not None: self._state.gradient.writeDefs(self._svgContext) # svg def _reset(self, other=None): self._embeddedFonts = set() def _newPage(self, width, height): if hasattr(self, "_svgContext"): self._svgContext.endtag("svg") self.reset() self.size(width, height) self._svgData = self._svgFileClass() self._pages.append(self._svgData) self._svgContext = XMLWriter(self._svgData, encoding="utf-8", indentwhite=self.indentation) self._svgContext.width = self.width self._svgContext.height = self.height self._svgContext.begintag("svg", width=self.width, height=self.height, **self._svgTagArguments) self._svgContext.newline() self._state.transformMatrix = self._state.transformMatrix.scale(1, -1).translate(0, -self.height) def _saveImage(self, path, multipage): if multipage is None: multipage = False self._svgContext.endtag("svg") fileName, fileExt = os.path.splitext(path) firstPage = 0 pageCount = len(self._pages) pathAdd = "_1" if not multipage: firstPage = pageCount - 1 pathAdd = "" for index in range(firstPage, pageCount): page = self._pages[index] svgPath = fileName + pathAdd + fileExt page.writeToFile(svgPath) pathAdd = "_%s" % (index + 2) def _save(self): pass def _restore(self): pass def _drawPath(self): if self._state.path: self._svgBeginClipPath() data = self._svgDrawingAttributes() data["d"] = self._svgPath(self._state.path) data["transform"] = self._svgTransform(self._state.transformMatrix) if self._state.shadow is not None: data["filter"] = "url(#%s)" % self._state.shadow.tagID if self._state.gradient is not None: data["fill"] = "url(#%s)" % self._state.gradient.tagID self._svgContext.simpletag("path", **data) self._svgContext.newline() self._svgEndClipPath() def _clipPath(self): uniqueID = self._getUniqueID() self._svgContext.begintag("clipPath", id=uniqueID) self._svgContext.newline() data = dict() data["d"] = self._svgPath(self._state.path) data["transform"] = self._svgTransform(self._state.transformMatrix) data["clip-rule"] = "evenodd" self._svgContext.simpletag("path", **data) self._svgContext.newline() self._svgContext.endtag("clipPath") self._svgContext.newline() self._state.clipPathID = uniqueID def _textBox(self, txt, box, align): path, (x, y) = self._getPathForFrameSetter(box) canDoGradients = True if align == "justified": warnings.warn("justified text is not supported in a svg context") attrString = self.attributedString(txt, align=align) if self._state.hyphenation: attrString = self.hyphenateAttributedString(attrString, path) txt = attrString.string() setter = CoreText.CTFramesetterCreateWithAttributedString(attrString) box = CoreText.CTFramesetterCreateFrame(setter, (0, 0), path, None) self._svgBeginClipPath() defaultData = self._svgDrawingAttributes() data = { "text-anchor": "start", "transform": self._svgTransform(self._state.transformMatrix.translate(x, y + self.height).scale(1, -1)) } if self._state.shadow is not None: data["filter"] = "url(#%s_flipped)" % self._state.shadow.tagID self._svgContext.begintag("text", **data) self._svgContext.newline() ctLines = CoreText.CTFrameGetLines(box) origins = CoreText.CTFrameGetLineOrigins(box, (0, len(ctLines)), None) for i, (originX, originY) in enumerate(origins): ctLine = ctLines[i] # bounds = CoreText.CTLineGetImageBounds(ctLine, self._pdfContext) # if bounds.size.width == 0: # continue ctRuns = CoreText.CTLineGetGlyphRuns(ctLine) for ctRun in ctRuns: stringRange = CoreText.CTRunGetStringRange(ctRun) attributes = CoreText.CTRunGetAttributes(ctRun) font = attributes.get(AppKit.NSFontAttributeName) fontAttributes = font.fontDescriptor().fontAttributes() fillColor = attributes.get(AppKit.NSForegroundColorAttributeName) strokeColor = attributes.get(AppKit.NSStrokeColorAttributeName) strokeWidth = attributes.get(AppKit.NSStrokeWidthAttributeName, self._state.strokeWidth) baselineShift = attributes.get(AppKit.NSBaselineOffsetAttributeName, 0) openTypeFeatures = fontAttributes.get(CoreText.NSFontFeatureSettingsAttribute) fontName = font.fontName() fontSize = font.pointSize() spanData = dict(defaultData) fill = self._colorClass(fillColor).svgColor() if fill: spanData["fill"] = fill stroke = self._colorClass(strokeColor).svgColor() if stroke: spanData["stroke"] = stroke spanData["stroke-width"] = formatNumber(abs(strokeWidth)) spanData["font-family"] = fontName spanData["font-size"] = formatNumber(fontSize) if openTypeFeatures: featureTags = getFeatureTagsForFontAttributes(openTypeFeatures) spanData["style"] = self._svgStyle(**{ "font-feature-settings": self._svgStyleOpenTypeFeatures(featureTags) } ) if canDoGradients and self._state.gradient is not None: spanData["fill"] = "url(#%s_flipped)" % self._state.gradient.tagID self._save() runTxt = txt.substringWithRange_((stringRange.location, stringRange.length)) while runTxt and runTxt[-1] == " ": runTxt = runTxt[:-1] runTxt = runTxt.replace("\n", "") runTxt = runTxt.encode("utf-8") runPos = CoreText.CTRunGetPositions(ctRun, (0, 1), None) runX = runY = 0 if runPos: runX = runPos[0].x runY = runPos[0].y spanData["x"] = formatNumber(originX + runX) spanData["y"] = formatNumber(self.height - originY - runY + baselineShift) self._svgContext.begintag("tspan", **spanData) self._svgContext.newline() self._svgContext.write(runTxt) self._svgContext.newline() self._svgContext.endtag("tspan") self._svgContext.newline() self._restore() self._svgContext.endtag("text") self._svgContext.newline() self._svgEndClipPath() def _image(self, path, (x, y), alpha, pageNumber): # todo: # support embedding of images when the source is not a path but # a nsimage or a pdf / gif with a pageNumber self._svgBeginClipPath() if path.startswith("http"): url = AppKit.NSURL.URLWithString_(path) else: url = AppKit.NSURL.fileURLWithPath_(path) im = AppKit.NSImage.alloc().initByReferencingURL_(url) w, h = im.size() data = dict() data["x"] = 0 data["y"] = 0 data["width"] = w data["height"] = h data["opacity"] = alpha data["transform"] = self._svgTransform(self._state.transformMatrix.translate(x, y + h).scale(1, -1)) data["xlink:href"] = path self._svgContext.simpletag("image", **data) self._svgContext.newline() self._svgEndClipPath()
class SVGContext(BaseContext): _graphicsStateClass = SVGGraphicsState _shadowClass = SVGShadow _colorClass = SVGColor _gradientClass = SVGGradient _clipPathIDGenerator = _UniqueIDGenerator("clip") _svgFileClass = SVGFile _svgTagArguments = { "version": "1.1", "xmlns": "http://www.w3.org/2000/svg", "xmlns:xlink": "http://www.w3.org/1999/xlink" } _svgLineJoinStylesMap = { AppKit.NSMiterLineJoinStyle: "miter", AppKit.NSRoundLineJoinStyle: "round", AppKit.NSBevelLineJoinStyle: "bevel" } _svgLineCapStylesMap = { AppKit.NSButtLineCapStyle: "butt", AppKit.NSSquareLineCapStyle: "square", AppKit.NSRoundLineCapStyle: "round", } indentation = " " fileExtensions = ["svg"] saveImageOptions = [ ("multipage", "Output a numbered svg file for each page or frame in the document."), ] def __init__(self): super(SVGContext, self).__init__() self._pages = [] # not supported in a svg context def cmykFill(self, c, m, y, k, a=1): warnings.warn("cmykFill is not supported in a svg context") def cmykStroke(self, c, m, y, k, a=1): warnings.warn("cmykStroke is not supported in a svg context") def cmykLinearGradient(self, startPoint=None, endPoint=None, colors=None, locations=None): warnings.warn("cmykLinearGradient is not supported in a svg context") def cmykRadialGradient(self, startPoint=None, endPoint=None, colors=None, locations=None, startRadius=0, endRadius=100): warnings.warn("cmykRadialGradient is not supported in a svg context") def cmykShadow(self, offset, blur, color): warnings.warn("cmykShadow is not supported in a svg context") # svg overwrites def shadow(self, offset, blur, color): super(SVGContext, self).shadow(offset, blur, color) if self._state.shadow is not None: self._state.shadow.writeDefs(self._svgContext) def linearGradient(self, startPoint=None, endPoint=None, colors=None, locations=None): super(SVGContext, self).linearGradient(startPoint, endPoint, colors, locations) if self._state.gradient is not None: self._state.gradient.writeDefs(self._svgContext) def radialGradient(self, startPoint=None, endPoint=None, colors=None, locations=None, startRadius=0, endRadius=100): super(SVGContext, self).radialGradient(startPoint, endPoint, colors, locations, startRadius, endRadius) if startRadius != 0: warnings.warn("radialGradient will clip the startRadius to '0' in a svg context.") if self._state.gradient is not None: self._state.gradient.writeDefs(self._svgContext) # svg def _reset(self, other=None): self._embeddedFonts = set() self._embeddedImages = dict() def _newPage(self, width, height): if hasattr(self, "_svgContext"): self._svgContext.endtag("svg") self.reset() self.size(width, height) self._svgData = self._svgFileClass() self._pages.append(self._svgData) self._svgContext = XMLWriter(self._svgData, encoding="utf-8", indentwhite=self.indentation) self._svgContext.width = self.width self._svgContext.height = self.height self._svgContext.begintag("svg", width=self.width, height=self.height, **self._svgTagArguments) self._svgContext.newline() self._state.transformMatrix = self._state.transformMatrix.scale(1, -1).translate(0, -self.height) def _saveImage(self, path, options): multipage = options.get("multipage") if multipage is None: multipage = False self._svgContext.endtag("svg") fileName, fileExt = os.path.splitext(path) firstPage = 0 pageCount = len(self._pages) pathAdd = "_1" if not multipage: firstPage = pageCount - 1 pathAdd = "" for index in range(firstPage, pageCount): page = self._pages[index] svgPath = fileName + pathAdd + fileExt page.writeToFile(svgPath) pathAdd = "_%s" % (index + 2) def _save(self): pass def _restore(self): pass def _drawPath(self): if self._state.path: self._svgBeginClipPath() data = self._svgDrawingAttributes() data["d"] = self._svgPath(self._state.path) data["transform"] = self._svgTransform(self._state.transformMatrix) if self._state.shadow is not None: data["filter"] = "url(#%s)" % self._state.shadow.tagID if self._state.gradient is not None: data["fill"] = "url(#%s)" % self._state.gradient.tagID self._svgContext.simpletag("path", **data) self._svgContext.newline() self._svgEndClipPath() def _clipPath(self): uniqueID = self._clipPathIDGenerator.gen() self._svgContext.begintag("clipPath", id=uniqueID) self._svgContext.newline() data = dict() data["d"] = self._svgPath(self._state.path) data["transform"] = self._svgTransform(self._state.transformMatrix) data["clip-rule"] = "evenodd" self._svgContext.simpletag("path", **data) self._svgContext.newline() self._svgContext.endtag("clipPath") self._svgContext.newline() self._state.clipPathID = uniqueID def _textBox(self, txt, box, align): path, (x, y) = self._getPathForFrameSetter(box) canDoGradients = True if align == "justified": warnings.warn("justified text is not supported in a svg context") attrString = self.attributedString(txt, align=align) if self._state.hyphenation: attrString = self.hyphenateAttributedString(attrString, path) txt = attrString.string() setter = CoreText.CTFramesetterCreateWithAttributedString(attrString) box = CoreText.CTFramesetterCreateFrame(setter, (0, 0), path, None) self._svgBeginClipPath() defaultData = self._svgDrawingAttributes() data = { "text-anchor": "start", "transform": self._svgTransform(self._state.transformMatrix.translate(x, y + self.height).scale(1, -1)) } if self._state.shadow is not None: data["filter"] = "url(#%s_flipped)" % self._state.shadow.tagID self._svgContext.begintag("text", **data) self._svgContext.newline() ctLines = CoreText.CTFrameGetLines(box) origins = CoreText.CTFrameGetLineOrigins(box, (0, len(ctLines)), None) for i, (originX, originY) in enumerate(origins): ctLine = ctLines[i] # bounds = CoreText.CTLineGetImageBounds(ctLine, self._pdfContext) # if bounds.size.width == 0: # continue ctRuns = CoreText.CTLineGetGlyphRuns(ctLine) for ctRun in ctRuns: stringRange = CoreText.CTRunGetStringRange(ctRun) attributes = CoreText.CTRunGetAttributes(ctRun) font = attributes.get(AppKit.NSFontAttributeName) fontAttributes = font.fontDescriptor().fontAttributes() fillColor = attributes.get(AppKit.NSForegroundColorAttributeName) strokeColor = attributes.get(AppKit.NSStrokeColorAttributeName) strokeWidth = attributes.get(AppKit.NSStrokeWidthAttributeName, self._state.strokeWidth) baselineShift = attributes.get(AppKit.NSBaselineOffsetAttributeName, 0) openTypeFeatures = fontAttributes.get(CoreText.NSFontFeatureSettingsAttribute) fontName = font.fontName() fontSize = font.pointSize() spanData = dict(defaultData) fill = self._colorClass(fillColor).svgColor() if fill: c, a = fill spanData["fill"] = c if a != 1: spanData["fill-opacity"] = a stroke = self._colorClass(strokeColor).svgColor() if stroke: c, a = stroke spanData["stroke"] = c if a != 1: spanData["stroke-opacity"] = a spanData["stroke-width"] = formatNumber(abs(strokeWidth) * .5) spanData["font-family"] = fontName spanData["font-size"] = formatNumber(fontSize) if openTypeFeatures: featureTags = getFeatureTagsForFontAttributes(openTypeFeatures) spanData["style"] = self._svgStyle(**{ "font-feature-settings": self._svgStyleOpenTypeFeatures(featureTags) } ) if canDoGradients and self._state.gradient is not None: spanData["fill"] = "url(#%s_flipped)" % self._state.gradient.tagID self._save() runTxt = txt.substringWithRange_((stringRange.location, stringRange.length)) while runTxt and runTxt[-1] == " ": runTxt = runTxt[:-1] runTxt = runTxt.replace("\n", "") runTxt = runTxt.encode("utf-8") runPos = CoreText.CTRunGetPositions(ctRun, (0, 1), None) runX = runY = 0 if runPos: runX = runPos[0].x runY = runPos[0].y spanData["x"] = formatNumber(originX + runX) spanData["y"] = formatNumber(self.height - originY - runY + baselineShift) self._svgContext.begintag("tspan", **spanData) self._svgContext.newline() self._svgContext.write(runTxt) self._svgContext.newline() self._svgContext.endtag("tspan") self._svgContext.newline() self._restore() self._svgContext.endtag("text") self._svgContext.newline() self._svgEndClipPath() def _image(self, path, xy, alpha, pageNumber): # todo: # support embedding of images when the source is not a path but # a nsimage or a pdf / gif with a pageNumber x, y = xy self._svgBeginClipPath() if path.startswith("http"): url = AppKit.NSURL.URLWithString_(path) else: url = AppKit.NSURL.fileURLWithPath_(path) image = AppKit.NSImage.alloc().initByReferencingURL_(url) width, height = image.size() if path not in self._embeddedImages: # get a unique id for the image imageID = "image_%s" % (len(self._embeddedImages) + 1) # store it self._embeddedImages[path] = imageID _, ext = os.path.splitext(path) mimeSubtype = ext[1:].lower() # remove the dot, make lowercase if mimeSubtype == "jpg": mimeSubtype = "jpeg" if mimeSubtype not in ("png", "jpeg"): # the image is not a png or a jpeg # convert it to a png mimeSubtype = "png" imageRep = _makeBitmapImageRep(image) imageData = imageRep.representationUsingType_properties_(AppKit.NSPNGFileType, None) else: imageData = AppKit.NSData.dataWithContentsOfURL_(url).bytes() defData = [ ("id", imageID), ("width", width), ("height", height), ("xlink:href", "data:image/%s;base64,%s" % (mimeSubtype, base64.b64encode(imageData).decode("ascii"))) ] self._svgContext.begintag("defs") self._svgContext.newline() self._svgContext.simpletag("image", defData) self._svgContext.newline() self._svgContext.endtag("defs") self._svgContext.newline() else: imageID = self._embeddedImages[path] data = [ ("x", 0), ("y", 0), ("opacity", alpha), ("transform", self._svgTransform(self._state.transformMatrix.translate(x, y + height).scale(1, -1))), ("xlink:href", "#%s" % imageID) ] self._svgContext.simpletag("use", data) self._svgContext.newline() self._svgEndClipPath() def _transform(self, transform): self._state.transformMatrix = self._state.transformMatrix.transform(transform) # helpers def _svgTransform(self, transform): return "matrix(%s)" % (",".join([repr(s) for s in transform])) def _svgPath(self, path, transformMatrix=None): path = path.getNSBezierPath() if transformMatrix: path = path.copy() aT = AppKit.NSAffineTransform.transform() aT.setTransformStruct_(transformMatrix[:]) path.transformUsingAffineTransform_(aT) svg = "" for i in range(path.elementCount()): instruction, points = path.elementAtIndex_associatedPoints_(i) if instruction == AppKit.NSMoveToBezierPathElement: svg += "M%s,%s " % (formatNumber(points[0].x), formatNumber(points[0].y)) previousPoint = points[-1] elif instruction == AppKit.NSLineToBezierPathElement: x = points[0].x - previousPoint.x y = points[0].y - previousPoint.y svg += "l%s,%s " % (formatNumber(x), formatNumber(y)) previousPoint = points[-1] elif instruction == AppKit.NSCurveToBezierPathElement: offx1 = points[0].x - previousPoint.x offy1 = points[0].y - previousPoint.y offx2 = points[1].x - previousPoint.x offy2 = points[1].y - previousPoint.y x = points[2].x - previousPoint.x y = points[2].y - previousPoint.y svg += "c%s,%s,%s,%s,%s,%s " % (formatNumber(offx1), formatNumber(offy1), formatNumber(offx2), formatNumber(offy2), formatNumber(x), formatNumber(y)) previousPoint = points[-1] elif instruction == AppKit.NSClosePathBezierPathElement: svg += "Z " return svg.strip() def _svgBeginClipPath(self): if self._state.clipPathID: data = dict() data["clip-path"] = "url(#%s)" % self._state.clipPathID self._svgContext.begintag("g", **data) self._svgContext.newline() def _svgEndClipPath(self): if self._state.clipPathID: self._svgContext.endtag("g") self._svgContext.newline() def _svgDrawingAttributes(self): data = dict() fill = self._svgFillColor() if fill: c, a = fill data["fill"] = c if a != 1: data["fill-opacity"] = a else: data["fill"] = "none" stroke = self._svgStrokeColor() if stroke: c, a = stroke data["stroke"] = c if a != 1: data["stroke-opacity"] = a data["stroke-width"] = formatNumber(abs(self._state.strokeWidth)) if self._state.lineDash: data["stroke-dasharray"] = ",".join([str(i) for i in self._state.lineDash]) if self._state.lineJoin in self._svgLineJoinStylesMap: data["stroke-linejoin"] = self._svgLineJoinStylesMap[self._state.lineJoin] if self._state.lineCap in self._svgLineCapStylesMap: data["stroke-linecap"] = self._svgLineCapStylesMap[self._state.lineCap] return data def _svgFillColor(self): if self._state.fillColor: return self._state.fillColor.svgColor() return None def _svgStrokeColor(self): if self._state.strokeColor: return self._state.strokeColor.svgColor() return None def _svgStyleOpenTypeFeatures(self, featureTags): return ", ".join(["'%s'" % tag for tag in featureTags]) def _svgStyle(self, **kwargs): style = [] if self._state.blendMode is not None: style.append("mix-blend-mode: %s;" % self._state.blendMode) for key, value in kwargs.items(): style.append("%s: %s;" % (key, value)) return " ".join(style) def installFont(self, path): success, error = super(self.__class__, self).installFont(path) # if path not in self._embeddedFonts: # warnings.warn("Your font will be embedded and accessibele") # self._embeddedFonts.add(path) # f = open(path, "r") # fontData = f.read() # f.close() # fontName = self._fontNameForPath(path) # ctx = self._svgContext # ctx.begintag("defs") # ctx.newline() # ctx.begintag("style", type="text/css") # ctx.newline() # ctx.write("@font-face {") # ctx.newline() # ctx.indent() # ctx.write("font-family: %s;" % fontName) # ctx.newline() # if path.startswith("http"): # ctx.write("src: url(%s');" % path) # else: # ctx.write("src: url('data:application/font-woff;charset=utf-8;base64,%s');" % base64.b64encode(fontData)) # ctx.newline() # ctx.dedent() # ctx.write("}") # ctx.newline() # ctx.endtag("style") # ctx.newline() # ctx.endtag("defs") # ctx.newline() return success, error
def writeGlyphToString(glyphName, glyphObject=None, drawPointsFunc=None, writer=None): """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 with of the glyph "unicodes" a list of unicode values for this glyph "note" a string "lib" a dictionary containing custom data 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. """ if writer is None: try: # try the newer location first from fontTools.misc.xmlWriter import XMLWriter except ImportError: from xmlWriter import XMLWriter aFile = StringIO() writer = XMLWriter(aFile, encoding="UTF-8") else: aFile = None writer.begintag("glyph", [("name", glyphName), ("format", "1")]) writer.newline() width = getattr(glyphObject, "width", None) if width is not None: if not isinstance(width, (int, float)): raise GlifLibError, "width attribute must be int or float" writer.simpletag("advance", width=repr(width)) writer.newline() unicodes = getattr(glyphObject, "unicodes", None) if unicodes: if isinstance(unicodes, int): unicodes = [unicodes] for code in unicodes: if not isinstance(code, int): raise GlifLibError, "unicode values must be int" hexCode = hex(code)[2:].upper() if len(hexCode) < 4: hexCode = "0" * (4 - len(hexCode)) + hexCode writer.simpletag("unicode", hex=hexCode) writer.newline() note = getattr(glyphObject, "note", None) if note is not None: if not isinstance(note, (str, unicode)): raise GlifLibError, "note attribute must be str or unicode" note = note.encode('utf-8') writer.begintag("note") writer.newline() for line in note.splitlines(): writer.write(line.strip()) writer.newline() writer.endtag("note") writer.newline() if drawPointsFunc is not None: writer.begintag("outline") writer.newline() pen = GLIFPointPen(writer) drawPointsFunc(pen) writer.endtag("outline") writer.newline() lib = getattr(glyphObject, "lib", None) if lib: from robofab.plistlib import PlistWriter if not isinstance(lib, dict): lib = dict(lib) writer.begintag("lib") writer.newline() plistWriter = PlistWriter(writer.file, indentLevel=writer.indentlevel, indent=writer.indentwhite, writeHeader=False) plistWriter.writeValue(lib) writer.endtag("lib") writer.newline() writer.endtag("glyph") writer.newline() if aFile is not None: return aFile.getvalue() else: return None
def test_simpletag(self): writer = XMLWriter(BytesIO()) writer.simpletag("tag", a="1", b="2") self.assertEqual(HEADER + b'<tag a="1" b="2"/>', writer.file.getvalue())
def __init__(self): self._file = StringIO() self._writer = XMLWriter(self._file, encoding="utf-8")
def test_writecdata(self): writer = XMLWriter(BytesIO()) writer.writecdata("foo&bar") self.assertEqual(HEADER + b"<![CDATA[foo&bar]]>", writer.file.getvalue())
def test_write(self): writer = XMLWriter(BytesIO()) writer.write("foo&bar") self.assertEqual(HEADER + b"foo&bar", writer.file.getvalue())
def test_comment_multiline(self): writer = XMLWriter(BytesIO()) writer.comment("Hello world\nHow are you?") self.assertEqual(HEADER + b"<!-- Hello world" + linesep + b" How are you? -->", writer.file.getvalue())
def test_comment_escaped(self): writer = XMLWriter(BytesIO()) writer.comment("This&that are <comments>") self.assertEqual(HEADER + b"<!-- This&that are <comments> -->", writer.file.getvalue())
def test_carriage_return_escaped(self): writer = XMLWriter(BytesIO()) writer.write("two lines\r\nseparated by Windows line endings") self.assertEqual( HEADER + b'two lines \nseparated by Windows line endings', writer.file.getvalue())
class Logger(object): def __init__(self): self._file = StringIO() self._writer = XMLWriter(self._file, encoding="utf-8") def __del__(self): self._writer = None self._file.close() def logStart(self): self._writer.begintag("xml") def logEnd(self): self._writer.endtag("xml") def logMainSettings(self, glyphNames, script, langSys): self._writer.begintag("initialSettings") self._writer.newline() self._writer.simpletag("string", value=" ".join(glyphNames)) self._writer.newline() self._writer.simpletag("script", value=script) self._writer.newline() self._writer.simpletag("langSys", value=langSys) self._writer.newline() self._writer.endtag("initialSettings") self._writer.newline() def logTableStart(self, table): name = table.__class__.__name__ self._writer.begintag("table", name=name) self._writer.newline() self.logTableFeatureStates(table) def logTableEnd(self): self._writer.endtag("table") def logTableFeatureStates(self, table): self._writer.begintag("featureStates") self._writer.newline() for tag in sorted(table.getFeatureList()): state = table.getFeatureState(tag) self._writer.simpletag("feature", name=tag, state=int(state)) self._writer.newline() self._writer.endtag("featureStates") self._writer.newline() def logApplicableLookups(self, table, lookups): self._writer.begintag("applicableLookups") self._writer.newline() if lookups: order = [] last = None for tag, lookup in lookups: if tag != last: if order: self._logLookupList(last, order) order = [] last = tag index = table.LookupList.Lookup.index(lookup) order.append(index) self._logLookupList(last, order) self._writer.endtag("applicableLookups") self._writer.newline() def _logLookupList(self, tag, lookups): lookups = " ".join([str(i) for i in lookups]) self._writer.simpletag("lookups", feature=tag, indices=lookups) self._writer.newline() def logProcessingStart(self): self._writer.begintag("processing") self._writer.newline() def logProcessingEnd(self): self._writer.endtag("processing") self._writer.newline() def logLookupStart(self, table, tag, lookup): index = table.LookupList.Lookup.index(lookup) self._writer.begintag("lookup", feature=tag, index=index) self._writer.newline() def logLookupEnd(self): self._writer.endtag("lookup") self._writer.newline() def logSubTableStart(self, lookup, subtable): index = lookup.SubTable.index(subtable) lookupType = subtable.__class__.__name__ self._writer.begintag("subTable", index=index, type=lookupType) self._writer.newline() def logSubTableEnd(self): self._writer.endtag("subTable") self._writer.newline() def logGlyphRecords(self, glyphRecords): for r in glyphRecords: self._writer.simpletag("glyphRecord", name=r.glyphName, xPlacement=r.xPlacement, yPlacement=r.yPlacement, xAdvance=r.xAdvance, yAdvance=r.yAdvance) self._writer.newline() def logInput(self, processed, unprocessed): self._writer.begintag("input") self._writer.newline() self._writer.begintag("processed") self._writer.newline() self.logGlyphRecords(processed) self._writer.endtag("processed") self._writer.newline() self._writer.begintag("unprocessed") self._writer.newline() self.logGlyphRecords(unprocessed) self._writer.endtag("unprocessed") self._writer.newline() self._writer.endtag("input") self._writer.newline() def logOutput(self, processed, unprocessed): self._writer.begintag("output") self._writer.newline() self._writer.begintag("processed") self._writer.newline() self.logGlyphRecords(processed) self._writer.endtag("processed") self._writer.newline() self._writer.begintag("unprocessed") self._writer.newline() self.logGlyphRecords(unprocessed) self._writer.endtag("unprocessed") self._writer.newline() self._writer.endtag("output") self._writer.newline() def logResults(self, processed): self._writer.begintag("results") self._writer.newline() self.logGlyphRecords(processed) self._writer.endtag("results") self._writer.newline() def getText(self): return self._file.getvalue()
class WoffMetaDataWriter(object): def __init__(self, metaDataPath, fontPath): self.writer = XMLWriter(metaDataPath, encoding="UTF-8") self.font = Font(fontPath) self.beginMetaData() self.uniqueId() self.vendor() self.credits() self.description() self.license() self.copyright() self.trademark() self.endMetaData() self.writer.close() def beginMetaData(self): self.writer.begintag("metadata") self.writer.newline() def uniqueId(self): url = self.font.info.get("openTypeNameManufacturerURL") if not url: return reverseUrl = reverseDomain(url) name = "%s%s" % (self.font.info.familyName, self.font.info.styleName) name = name.replace(" ", "") self.writer.simpletag("uniqueid", id="%s.%s" % (reverseUrl, name)) self.writer.newline() def vendor(self): name = self.font.info.get("openTypeNameManufacturer") url = self.font.info.get("openTypeNameManufacturerURL") if not name or not url: return self.writer.simpletag("vendor", name=name, url=url) self.writer.newline() def credits(self): manufacturerName = self.font.info.get("openTypeNameManufacturer") manufacturerUrl = self.font.info.get("openTypeNameManufacturerURL") designerName = self.font.info.get("openTypeNameDesigner") designerUrl = self.font.info.get("openTypeNameDesignerURL") if not manufacturerName and not manufacturerUrl and not designerName and not designerUrl: return self.writer.begintag("credits") self.writer.newline() if manufacturerName and manufacturerUrl: manufacturerName = manufacturerName.encode("utf-8") self.writer.simpletag("credit", name=manufacturerName, url=manufacturerUrl, role="Foundry") self.writer.newline() if designerName and designerUrl: designerName = designerName.encode("utf-8") self.writer.simpletag("credit", name=designerName, url=designerUrl, role="Designer") self.writer.newline() self.writer.endtag("credits") self.writer.newline() def _addData(self, tag, infoAttr, extra=dict()): data = self.font.info.get(infoAttr) if not data: return data = data.encode("utf-8") self.writer.begintag(tag, **extra) self.writer.newline() self.writer.begintag("text", lang="en") self.writer.newline() self.writer.write(data) self.writer.endtag("text") self.writer.newline() self.writer.endtag(tag) self.writer.newline() def description(self): self._addData("description", "openTypeNameDescription") def license(self): extra = dict() licenseUrl = self.font.info.get("openTypeNameLicenseURL") if licenseUrl: extra["url"] = licenseUrl self._addData("license", "openTypeNameLicense", extra) def copyright(self): self._addData("copyright", "copyright") def trademark(self): self._addData("trademark", "trademark") def endMetaData(self): self.writer.endtag("metadata") self.writer.newline()
def check_mti_file(self, name, tableTag=None): xml_expected_path = self.getpath("%s.ttx" % name + ('.'+tableTag if tableTag is not None else '')) with open(xml_expected_path, 'rt', encoding="utf-8") as xml_expected_file: xml_expected = xml_expected_file.read() font = self.create_font() with open(self.getpath("%s.txt" % name), 'rt', encoding="utf-8") as f: table = mtiLib.build(f, font, tableTag=tableTag) if tableTag is not None: self.assertEqual(tableTag, table.tableTag) tableTag = table.tableTag # Make sure it compiles. blob = table.compile(font) # Make sure it decompiles. decompiled = table.__class__() decompiled.decompile(blob, font) # XML from built object. writer = XMLWriter(StringIO(), newlinestr='\n') writer.begintag(tableTag); writer.newline() table.toXML(writer, font) writer.endtag(tableTag); writer.newline() xml_built = writer.file.getvalue() # XML from decompiled object. writer = XMLWriter(StringIO(), newlinestr='\n') writer.begintag(tableTag); writer.newline() decompiled.toXML(writer, font) writer.endtag(tableTag); writer.newline() xml_binary = writer.file.getvalue() self.expect_ttx(xml_binary, xml_built, fromfile='decompiled', tofile='built') self.expect_ttx(xml_expected, xml_built, fromfile=xml_expected_path, tofile='built') from fontTools.misc import xmlReader f = StringIO() f.write(xml_expected) f.seek(0) font2 = TTFont() font2.setGlyphOrder(font.getGlyphOrder()) reader = xmlReader.XMLReader(f, font2) reader.read(rootless=True) # XML from object read from XML. writer = XMLWriter(StringIO(), newlinestr='\n') writer.begintag(tableTag); writer.newline() font2[tableTag].toXML(writer, font) writer.endtag(tableTag); writer.newline() xml_fromxml = writer.file.getvalue() self.expect_ttx(xml_expected, xml_fromxml, fromfile=xml_expected_path, tofile='fromxml')
def test_indent_dedent(self): writer = XMLWriter(BytesIO()) writer.write("foo") writer.newline() writer.indent() writer.write("bar") writer.newline() writer.dedent() writer.write("baz") self.assertEqual(HEADER + bytesjoin(["foo", " bar", "baz"], linesep), writer.file.getvalue())