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: from xmlWriter import XMLWriter except ImportError: # try the other location from fontTools.misc.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
class SVGContext(BaseContext): _graphicsStateClass = SVGGraphicsState _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", } fileExtensions = ["svg"] def shadow(self, offset, blur, color): warnings.warn("shadow is not supported in a svg context") def cmykShadow(self, offset, blur, color): warnings.warn("shadow is not supported in a svg context") def linearGradient(self, startPoint=None, endPoint=None, colors=None, locations=None): warnings.warn("linearGradient is not supported in a svg context") def radialGradient(self, startPoint=None, endPoint=None, colors=None, locations=None, startRadius=0, endRadius=100): warnings.warn("radialGradient is not supported in a svg context") def _newPage(self, width, height): self.reset() self.size(width, height) self._svgData = self._svgFileClass() self._svgContext = XMLWriter(self._svgData) 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): self._svgContext.endtag("svg") self._svgData.writeToFile(path) 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) 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, (x, y, w, h), align): attrString = self.attributedString(txt, align=align) setter = CoreText.CTFramesetterCreateWithAttributedString(attrString) path = CoreText.CGPathCreateMutable() CoreText.CGPathAddRect(path, None, CoreText.CGRectMake(x, y, w, h)) box = CoreText.CTFramesetterCreateFrame(setter, (0, 0), path, None) self._save() self._svgBeginClipPath() data = self._svgDrawingAttributes() data["font-family"] = self._state.text.fontName data["font-size"] = self._state.text.fontSize data["transform"] = self._svgTransform(self._state.transformMatrix.translate(x, y + h).scale(1, -1)) data["text-anchor"] = "start" lines = [] ctLines = CoreText.CTFrameGetLines(box) for ctLine in ctLines: r = CoreText.CTLineGetStringRange(ctLine) line = txt[r.location:r.location+r.length] while line and line[-1] == " ": line = line[:-1] lines.append(line.replace("\n", "")) self._svgContext.begintag("text", **data) self._svgContext.newline() txt = [] origins = CoreText.CTFrameGetLineOrigins(box, (0, len(ctLines)), None) for i, (originX, originY) in enumerate(origins): line = lines[i] self._svgContext.begintag("tspan", x=originX, y=h-originY) self._svgContext.newline() self._svgContext.write(line) self._svgContext.newline() self._svgContext.endtag("tspan") self._svgContext.newline() self._svgContext.endtag("text") self._svgContext.newline() self._svgEndClipPath()
class SVGContext(BaseContext): _graphicsStateClass = SVGGraphicsState _shadowClass = SVGShadow _colorClass = SVGColor _gradientClass = SVGGradient _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", } fileExtensions = ["svg"] def __init__(self): super(SVGContext, self).__init__() self._pages = [] # not supported in a svg context def openTypeFeatures(self, *args, **features): warnings.warn("openTypeFeatures is 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): 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") 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, (x, y, w, h), align): canDoGradients = not isinstance(txt, FormattedString) if align == "justified": warnings.warn("justified text is not supported in a svg context") attrString = self.attributedString(txt, align=align) if self._state.text.hyphenation: attrString = self.hyphenateAttributedString(attrString, w) txt = attrString.string() setter = CoreText.CTFramesetterCreateWithAttributedString(attrString) path = CoreText.CGPathCreateMutable() CoreText.CGPathAddRect(path, None, CoreText.CGRectMake(x, y, w, h)) 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: attributes = CoreText.CTRunGetAttributes(ctRun) font = attributes.get(AppKit.NSFontAttributeName) fillColor = attributes.get(AppKit.NSForegroundColorAttributeName) strokeColor = attributes.get(AppKit.NSStrokeColorAttributeName) strokeWidth = attributes.get(AppKit.NSStrokeWidthAttributeName, self._state.strokeWidth) baselineShift = attributes.get(AppKit.NSBaselineOffsetAttributeName, 0) fontName = font.fontName() fontSize = font.pointSize() spanData = dict(defaultData) spanData["fill"] = self._colorClass(fillColor).svgColor() spanData["stroke"] = self._colorClass(strokeColor).svgColor() spanData["stroke-width"] = strokeWidth spanData["font-family"] = fontName spanData["font-size"] = fontSize if canDoGradients and self._state.gradient is not None: spanData["fill"] = "url(#%s_flipped)" % self._state.gradient.tagID self._save() r = CoreText.CTRunGetStringRange(ctRun) runTxt = txt.substringWithRange_((r.location, r.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"] = originX + runX spanData["y"] = 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()
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 TestSuite(metaTestItem): ''' Collection of all TestBlocks ''' def __init__(self, TheOutPath='', TheFontList=[]): metaTestItem.__init__(self, 'fontQA', 'fontQA') if TheOutPath != '': self.OutPath = TheOutPath else: self.OutPath = os.path.join(fl.path, 'fontQA_report.xml') self.suite = self self.showReport = True if TheFontList == [] and len(fl) > 0: for i in range(len(fl)): self.testFontList.append(fl[i]) else: self.testFontList = TheFontList self.testBlockList = self._childs def addBlock(self, TheName, TheTag): m = re.search(r"\W", TheTag) if m: raise ValueError, 'Second parameter has to contain only alphanumeric characters ( \w or [a-zA-Z0-9_] ) .' result = TestBlock(TheName, TheTag) self.append(result) return result def showDialog(self): self.dialog = SuiteDialog(self) result = self.dialog.Run() if result == 1: for anyBlock in self.testBlockList: anyBlock.isSelected = getattr(self.dialog, anyBlock.tag) self.OutPath = self.dialog.OutPath self.showReport = self.dialog.openInBrowser return result def doTest(self): go_on = False if len(fl) > 0: if self.OutPath == '': if self.showDialog == OK: go_on = True else: if os.path.isdir(os.path.dirname(self.OutPath)): go_on = True if go_on: fl.BeginProgress('checking fonts', len(self)) self.XMLwriter = XMLWriter(self.OutPath, self.indentStr) procInst = '<?xml-stylesheet type="text/xsl" href="%s"?>' % XSL_FileName self.XMLwriter.writeraw(procInst) self.XMLwriter.newline() timeStr = time.strftime('%Y.%m.%d-%H:%M:%S', time.localtime()) self.XMLwriter.begintag(self.name, RunDateTime=timeStr) self.XMLwriter.newline() self.XMLwriter.begintag('FontList') self.XMLwriter.newline() for anyFont in self.testFontList: self.XMLwriter.simpletag('Font', FullName=anyFont.full_name, Path=anyFont.file_name) self.XMLwriter.newline() self.XMLwriter.endtag('FontList') self.XMLwriter.newline() self.XMLwriter.begintag('TestSuite') self.XMLwriter.newline() counter = 1 for anyBlock in self.testBlockList: if anyBlock.isSelected: anyBlock.XMLwriter = self.XMLwriter anyBlock._doTest() fl.TickProgress(counter) counter += 1 self.XMLwriter.endtag('TestSuite') self.XMLwriter.newline() self.XMLwriter.endtag(self.name) self.XMLwriter.newline() self.XMLwriter.close() fl.EndProgress() styleSheetTargetPath = os.path.join(os.path.dirname(self.OutPath), XSL_FileName) if os.path.isfile(styleSheetTargetPath): os.remove(styleSheetTargetPath) shutil.copy2(XSL_FilePath, styleSheetTargetPath) if self.showReport: webbrowser.open(self.OutPath)
class SVGContext(BaseContext): _graphicsStateClass = SVGGraphicsState _shadowClass = SVGShadow _colorClass = SVGColor _gradientClass = SVGGradient _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", } fileExtensions = ["svg"] def __init__(self): super(SVGContext, self).__init__() self._pages = [] # not supported in a svg context def openTypeFeatures(self, *args, **features): warnings.warn("openTypeFeatures is 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 _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") 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, (x, y, w, h), align): canDoGradients = not isinstance(txt, FormattedString) if align == "justified": warnings.warn("justified text is not supported in a svg context") attrString = self.attributedString(txt, align=align) if self._state.text.hyphenation: attrString = self.hyphenateAttributedString(attrString, w) txt = attrString.string() setter = CoreText.CTFramesetterCreateWithAttributedString(attrString) path = CoreText.CGPathCreateMutable() CoreText.CGPathAddRect(path, None, CoreText.CGRectMake(x, y, w, h)) box = CoreText.CTFramesetterCreateFrame(setter, (0, 0), path, None) self._svgBeginClipPath() defaultData = self._svgDrawingAttributes() data = {"text-anchor": "start"} 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: attributes = CoreText.CTRunGetAttributes(ctRun) font = attributes.get(AppKit.NSFontAttributeName) fillColor = attributes.get( AppKit.NSForegroundColorAttributeName) strokeColor = attributes.get(AppKit.NSStrokeColorAttributeName) strokeWidth = attributes.get(AppKit.NSStrokeWidthAttributeName, self._state.strokeWidth) fontName = font.fontName() fontSize = font.pointSize() spanData = dict(defaultData) spanData["fill"] = self._colorClass(fillColor).svgColor() spanData["stroke"] = self._colorClass(strokeColor).svgColor() spanData["stroke-width"] = strokeWidth spanData["font-family"] = fontName spanData["font-size"] = fontSize if canDoGradients and self._state.gradient is not None: spanData[ "fill"] = "url(#%s_flipped)" % self._state.gradient.tagID self._save() r = CoreText.CTRunGetStringRange(ctRun) runTxt = txt.substringWithRange_((r.location, r.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"] = originX + runX + x spanData["y"] = self.height - y - originY - runY 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()