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
示例#2
0
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()
示例#3
0
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
示例#4
0
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()
示例#5
0
 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())
示例#6
0
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",
    }

    _svgUnderlineStylesMap = {
        AppKit.NSUnderlineStyleSingle: "",
        AppKit.NSUnderlineStyleThick: "",
        AppKit.NSUnderlineStyleDouble: "double",
    }

    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
        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 _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)
            if self._state.path.svgID:
                data["id"] = self._state.path.svgID
            if self._state.path.svgClass:
                data["class"] = self._state.path.svgClass
            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
            if self._state.path.svgLink:
                self._svgContext.begintag("a", **{"xlink:href": self._state.path.svgLink})
                self._svgContext.newline()
            self._svgContext.simpletag("path", **data)
            self._svgContext.newline()
            if self._state.path.svgLink:
                self._svgContext.endtag("a")
                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, rawTxt, 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(rawTxt, 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
        if isinstance(rawTxt, FormattedString):
            if rawTxt.svgID:
                data["id"] = rawTxt.svgID
            if rawTxt.svgClass:
                data["class"] = rawTxt.svgClass
            if rawTxt.svgLink:
                self._svgContext.begintag("a", **{"xlink:href": rawTxt.svgLink})
                self._svgContext.newline()
        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)
                fontDescriptor = font.fontDescriptor()
                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 = attributes.get("drawbot.openTypeFeatures")
                underline = attributes.get(AppKit.NSUnderlineStyleAttributeName)
                url = attributes.get(AppKit.NSLinkAttributeName)

                fontName = font.fontName()
                fontSize = font.pointSize()
                fontFallbacks = [fallbackFont.postscriptName() for fallbackFont in fontDescriptor.get(CoreText.NSFontCascadeListAttribute, [])]
                fontNames = ", ".join([fontName] + fontFallbacks)

                style = dict()
                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"] = fontNames
                spanData["font-size"] = formatNumber(fontSize)

                if openTypeFeatures:
                    style["font-feature-settings"] = self._svgStyleOpenTypeFeatures(openTypeFeatures)

                if canDoGradients and self._state.gradient is not None:
                    spanData["fill"] = "url(#%s_flipped)" % self._state.gradient.tagID

                if underline is not None:
                    style["text-decoration"] = "underline"
                    underlineStyle = self._svgUnderlineStylesMap.get(underline)
                    if underlineStyle:
                        style["text-decoration-style"] = underlineStyle

                if style:
                    spanData["style"] = self._svgStyle(**style)
                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)
                if url is not None:
                    self._svgContext.begintag("a", href=url.absoluteString())
                    self._svgContext.newline()
                self._svgContext.begintag("tspan", **spanData)
                self._svgContext.newline()
                self._svgContext.write(runTxt)
                self._svgContext.newline()
                self._svgContext.endtag("tspan")
                self._svgContext.newline()
                if url is not None:
                    self._svgContext.endtag("a")
                    self._svgContext.newline()
                self._restore()

        self._svgContext.endtag("text")
        self._svgContext.newline()
        if isinstance(rawTxt, FormattedString) and rawTxt.svgLink:
            self._svgContext.endtag("a")
            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' %s" % (tag, int(value)) for tag, value in featureTags.items()])

    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 sorted(kwargs.items()):
            style.append("%s: %s;" % (key, value))
        return " ".join(style)

    def _linkURL(self, url, xywh):
        x, y, w, h = xywh
        rectData = dict(
            x=x,
            y=self.height-y-h,
            width=w,
            height=h,
            fill="transparent",
        )
        self._svgContext.begintag("a", href=url)
        self._svgContext.newline()
        self._svgContext.simpletag('rect', **rectData)
        self._svgContext.newline()
        self._svgContext.endtag("a")
        self._svgContext.newline()
示例#7
0
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:
                    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)
        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()

    def _transform(self, transform):
        self._state.transformMatrix = self._state.transformMatrix.transform(
            transform)

    # helpers

    def _getUniqueID(self):
        return uuid.uuid4().hex

    def _svgTransform(self, transform):
        return "matrix(%s)" % (",".join([str(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
示例#8
0
	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())
示例#9
0
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()
示例#10
0
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()