def reset(self): self.svg_bounds = None self.svg_dom = xml.dom.minidom.Document() self.svg_dom.doctype = "" self.svg_tag = self.svg_dom.createElement("svg") self.svg_tag.setAttribute("xmlns", "http://www.w3.org/2000/svg") self.svg_tag.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink") self.svg_dom.appendChild(self.svg_tag) def_tag = self.svg_dom.createElement("defs") self.svg_tag.appendChild(def_tag) self.svg_def = def_tag # set of required macros self.required_defs = set() self.style = CascadingStyles() self.style.appendScope() self.style["fill"] = "#fff" self.style["stroke"] = "#000000" self.style["stroke-width"] = "1.000000px" self.svg_graphroot = self.svg_dom.createElement("g") graphic_tag = self.svg_dom.createElement("g") graphic_tag.setAttribute("style", str(self.style)) self.svg_graphroot.appendChild(graphic_tag) self.svg_tag.appendChild(self.svg_graphroot) self.svg_current_layer = graphic_tag
class TestScope(TestCase): def setUp(self): self.cs = CascadingStyles({"font": "arial", "font-size": "12pt"}) def testRemoveScope(self): self.cs.appendScope() self.cs["font"] = "newfont" self.cs["font"] == "newfont"
class TestScope(TestCase): def setUp(self): self.cs = CascadingStyles({"font":"arial","font-size":"12pt"}) def testRemoveScope(self): self.cs.appendScope() self.cs["font"] = "newfont" self.cs["font"] == "newfont"
def reset(self): self.svg_bounds = None self.svg_dom = xml.dom.minidom.Document() self.svg_dom.doctype = "" self.svg_tag = self.svg_dom.createElement("svg") self.svg_tag.setAttribute("xmlns","http://www.w3.org/2000/svg") self.svg_tag.setAttribute("xmlns:xlink","http://www.w3.org/1999/xlink") self.svg_dom.appendChild(self.svg_tag) def_tag = self.svg_dom.createElement("defs") self.svg_tag.appendChild(def_tag) self.svg_def = def_tag # set of required macros self.required_defs = set() self.style = CascadingStyles() self.style.appendScope() self.style["fill"]="#fff" self.style["stroke"]="#000000" self.style["stroke-width"]="1.000000px" self.svg_graphroot = self.svg_dom.createElement("g") graphic_tag = self.svg_dom.createElement("g") graphic_tag.setAttribute("style",str(self.style)) self.svg_graphroot.appendChild(graphic_tag) self.svg_tag.appendChild(self.svg_graphroot) self.svg_current_layer = graphic_tag
class TestDefaults(TestCase): def setUp(self): self.cs = CascadingStyles({"font": "arial", "font-size": "12pt"}) def testNone(self): self.assertEqual(str(self.cs), "") def testRemoveScope(self): self.cs.appendScope() self.cs["font"] = "newfont" self.cs.popScope() self.assertEqual(str(self.cs), "") def testIgnoreDefault(self): self.cs.appendScope() self.cs["font"] = "newfont" self.cs.appendScope() self.cs["font"] = "arial" self.assertEqual(str(self.cs), "")
class TestDefaults(TestCase): def setUp(self): self.cs = CascadingStyles({"font":"arial","font-size":"12pt"}) def testNone(self): self.assertEqual(str(self.cs), "") def testRemoveScope(self): self.cs.appendScope() self.cs["font"] = "newfont" self.cs.popScope() self.assertEqual(str(self.cs), "") def testIgnoreDefault(self): self.cs.appendScope() self.cs["font"] = "newfont" self.cs.appendScope() self.cs["font"] = "arial" self.assertEqual(str(self.cs), "")
class TargetSvg(object): def __init__(self): self.svg_bounds = None self.svg_dom = None self.svg_current_layer = None self.svg_current_font = "" self.svg_tag = None self.svg_graphroot = None self.svg_def = None self.style = None self.reset() def __bb_box(self, minx, miny, w, h): maxx = minx + w maxy = miny + h if not self.svg_bounds: self.svg_bounds = (minx, miny, maxx, maxy) else: (ix, iy, ax, ay) = self.svg_bounds if minx < ix: ix = minx if miny < iy: iy = miny if maxx > ax: ax = maxx if maxy > ay: ay = maxy self.svg_bounds = (ix, iy, ax, ay) def __bb_point(self, px, py): if not self.svg_bounds: self.svg_bounds = (px, py, px, py) else: (ix, iy, ax, ay) = self.svg_bounds if px < ix: ix = px if py < iy: iy = py if px > ax: ax = px if py > ay: ay = py self.svg_bounds = (ix, iy, ax, ay) def reset(self): self.svg_bounds = None self.svg_dom = xml.dom.minidom.Document() self.svg_dom.doctype = "" self.svg_tag = self.svg_dom.createElement("svg") self.svg_tag.setAttribute("xmlns", "http://www.w3.org/2000/svg") self.svg_tag.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink") self.svg_dom.appendChild(self.svg_tag) def_tag = self.svg_dom.createElement("defs") self.svg_tag.appendChild(def_tag) self.svg_def = def_tag # set of required macros self.required_defs = set() self.style = CascadingStyles() self.style.appendScope() self.style["fill"] = "#fff" self.style["stroke"] = "#000000" self.style["stroke-width"] = "1.000000px" self.svg_graphroot = self.svg_dom.createElement("g") graphic_tag = self.svg_dom.createElement("g") graphic_tag.setAttribute("style", str(self.style)) self.svg_graphroot.appendChild(graphic_tag) self.svg_tag.appendChild(self.svg_graphroot) self.svg_current_layer = graphic_tag @property def svg(self): """Return the svg document""" return self.svg_dom.toprettyxml(encoding='utf-8') def mkHex(self, s): # s is a string of a float h = "%02x" % (int(min(float(s) * 256, 255))) return h def extract_colour(self, col): # only gets rgb values (ignores a) return "".join( [self.mkHex(col["r"]), self.mkHex(col["g"]), self.mkHex(col["b"])]) def setGraffleFont(self, font): if font is None: return fontstuffs = [] font_col = "000000" if font.get("Color") is not None: grap_col = font.get("Color") try: font_col = self.extract_colour(grap_col) except: font_col = "000000" fontstuffs.append("fill:#%s" % font_col) fontstuffs.append("stroke:#%s" % font_col) fontstuffs.append("stroke-width:0.1px") fontfam = font.get("Font") if fontfam is not None: if fontfam == "LucidaGrande": fontfam = "Luxi Sans" elif fontfam == "Courier": fontfam = "Courier New" elif fontfam == "GillSans": fontfam = "Arial Narrow" fontstuffs.append("font-family: %s" % fontfam) size = font.get("Size") if size is not None: fontstuffs.append("font-size:%dpx" % int(size)) self.svg_current_font = ";".join(fontstuffs) def addLayer(self, graffleInterpreter, GraphicsList): current_layer = self.svg_current_layer self.style.appendScope() g_emt = self.svg_dom.createElement("g") g_emt.setAttribute("style", str(self.style)) current_layer.appendChild(g_emt) self.svg_current_layer = g_emt graffleInterpreter.iterateGraffleGraphics(GraphicsList) self.style.popScope() self.svg_current_layer = current_layer def addAdjustableArrow(self, bounds, graphic, **opts): x, y, width, height = [float(a) for a in bounds] ratio = float(graphic["ShapeData"]["ratio"]) neck = float(graphic["ShapeData"]["width"]) neck_delta = height * (1 - ratio) / 2 self.addPath([[x, y + neck_delta], [x + width - neck, y + neck_delta], [x + width - neck, y], [x + width, y + height / 2], [x + width - neck, y + height], [x + width - neck, y + height - neck_delta], [x, y + height - neck_delta]], closepath=True, **opts) def addDiamond(self, bounds, **opts): x, y, width, height = [float(a) for a in bounds] xmiddle = x + width / 2 ymiddle = y + height / 2 self.addPath([[x, ymiddle], [xmiddle, y + height], [x + width, ymiddle], [xmiddle, y]], closepath=True, **opts) def addPath(self, pts, **opts): # do geometry mapping here mypts = pts if opts.get("HFlip", False): mypts = geom.h_flip_points(mypts) if opts.get("VFlip", False): mypts = geom.v_flip_points(mypts) if opts.get("Rotation") is not None: mypts = geom.rotate_points(mypts, opts["Rotation"]) for (localx, localy) in mypts: self.__bb_point(localx, localy) ptStrings = [",".join([str(b) for b in a]) for a in mypts] line_string = "M %s" % ptStrings[0] + " ".join(" L %s" % a for a in ptStrings[1:]) if opts.get("closepath", False): line_string = line_string + " z" path_tag = self.svg_dom.createElement("path") path_tag.setAttribute("id", opts.get("id", "")) path_tag.setAttribute("style", str(self.style.scopeStyle())) path_tag.setAttribute("d", line_string) self.svg_current_layer.appendChild(path_tag) def addHorizontalTriangle(self, bounds, **opts): """Graffle has the "HorizontalTriangle" Shape""" x, y, width, height = [float(a) for a in bounds] self.addPath([[x,y],[x+width,y+height/2], [x,y+height]], \ closepath=True, **opts) def addImage(self, bounds, **opts): """SVG viewers should support images - unfortunately many don't :-(""" x, y, width, height = [float(a) for a in bounds] image_tag = self.svg_dom.createElement("image") image_tag.setAttribute("x", str(x)) image_tag.setAttribute("y", str(y)) image_tag.setAttribute("width", str(width)) image_tag.setAttribute("height", str(height)) image_tag.setAttribute("xlink:href", str(opts.get("href", ""))) image_tag.setAttribute("style", str(self.style.scopeStyle())) self.__bb_box(x, y, width, height) self.svg_current_layer.appendChild(image_tag) def addRightTriangle(self, bounds, **opts): """Graffle has the "RightTriangle" Shape""" x, y, width, height = [float(a) for a in bounds] self.addPath( [[x,y],[x+width,y+height], [x,y+height]], \ closepath=True, **opts) def addVerticalTriangle(self, bounds, **opts): """Graffle has the "RightTriangle" Shape""" x, y, width, height = [float(a) for a in bounds] self.addPath( [[x,y],[x+width,y], [x+width/2,y+height]], \ closepath=True, **opts) def addRect(self, **opts): """Add an svg rect""" if opts is None: opts = {} rect_tag = self.svg_dom.createElement("rect") rect_tag.setAttribute("id", opts.get("id", "")) rect_tag.setAttribute("width", str(opts["width"])) rect_tag.setAttribute("height", str(opts["height"])) rect_tag.setAttribute("x", str(opts.get("x", "0"))) rect_tag.setAttribute("y", str(opts.get("y", "0"))) if opts.get("rx") is not None: rect_tag.setAttribute("rx", str(opts["rx"])) rect_tag.setAttribute("ry", str(opts["ry"])) rect_tag.setAttribute("style", str(self.style.scopeStyle())) self.svg_current_layer.appendChild(rect_tag) self.__bb_box(opts.get("x", "0"), opts.get("y", "0"), opts["width"], opts["height"]) def addEllipse(self, bounds, **opts): c = [bounds[i] + (bounds[i + 2] / 2.) for i in [0, 1]] # centre of circle rx = bounds[2] / 2. ry = bounds[3] / 2. circle_tag = self.svg_dom.createElement("ellipse") circle_tag.setAttribute("id", opts.get("id", "")) circle_tag.setAttribute("style", str(self.style.scopeStyle())) circle_tag.setAttribute("cx", str(c[0])) circle_tag.setAttribute("cy", str(c[1])) circle_tag.setAttribute("rx", str(rx)) circle_tag.setAttribute("ry", str(ry)) self.svg_current_layer.appendChild(circle_tag) self.__bb_box(c[0] - rx, c[1] - ry, c[0] + rx, c[1] + ry) def addCloud(self, bounds, **opts): """Add a cloud element""" self.required_defs.add("network_cloud") x, y, dx, dy = bounds cloud_tag = self.svg_dom.createElement("g") cloud_tag.setAttribute("id", opts.get("id", "")) cloud_tag.setAttribute("style", str(self.style.scopeStyle())) cloud_tag.setAttribute( "transform", "translate(%f,%f) scale(%f,%f)" % (x, y, float(dx) / 500.0, float(dy) / 500.0)) p = xml.dom.minidom.parseString( "<use xmlns:xlink='http://www.w3.org/1999/xlink' xlink:href='#network_cloud' />" ) def_node = p.childNodes[0] cloud_tag.appendChild(def_node) self.svg_current_layer.appendChild(cloud_tag) self.__bb_box(x, y, dx, dy) def addText(self, **opts): """Add an svg text element""" text_tag = self.svg_dom.createElement("text") text_tag.setAttribute("id", str(opts.get("id", "")) + "_text") text_tag.setAttribute("x", str(opts.get("x", "0"))) text_tag.setAttribute("y", str(opts.get("y", "0"))) # text_tag.setAttribute("dominant-baseline","mathematical") text_tag.setAttribute("style", self.svg_current_font) self.svg_current_layer.appendChild(text_tag) # Generator lines = extractRTFString(opts["rtftext"]) font_info = opts.get("fontinfo", None) if font_info is not None: font_height = int(font_info.get("Size")) else: font_height = 12 total_height = 0.0 for span in lines: total_height += float(span["style"].get("font-size", "%.1fpx" % font_height)[:-2]) y_diff = float(opts.get( "y", "12.0")) + opts.get("height", 0) / 2 - total_height / 2 line_id = opts["id"] linenb = 0 for span in lines: y_diff += float(span["style"].get("font-size", "%.1fpx" % font_height)[:-2]) linenb += 1 opts["id"] = str(line_id) + "_line" + str(linenb) self.addLine(text_tag,text = span["string"], style = span["style"],\ y_pos=y_diff, line_height =font_height, **opts) self.__bb_box(opts.get("x", "0"), opts.get("y", "0"), opts.get("width", "0"), opts.get("height", "0")) def addLine(self, textnode, **opts): """Add a line of text""" tspan_node = self.svg_dom.createElement("tspan") tspan_node.setAttribute("id", opts.get("id", "")) align = opts.get("style", { "text-align": "left" }).get("text-align", "left") x = opts.get("x", 0) if align == "center": tspan_node.setAttribute("text-anchor", "middle") x += opts.get("width", 0) / 2 elif align == "right": x += opts.get("width", 0) tspan_node.setAttribute("x", str(x)) y_pos = float(opts.get("y_pos", 0)) tspan_node.setAttribute("y", str(y_pos)) if (opts.get("style") is not None) and len(opts.get("style")) > 0: thestyle = opts.get("style") for (k, v) in thestyle.items(): tspan_node.setAttribute(k, v) actual_string = self.svg_dom.createTextNode(opts.get("text", " ")) tspan_node.appendChild(actual_string) textnode.appendChild(tspan_node) def add_document_bounds(self): if self.svg_bounds: (minx, miny, maxx, maxy) = self.svg_bounds # self.svg_tag.setAttribute("width", str(maxx - minx)) # self.svg_tag.setAttribute("height", str(maxy - miny)) self.svg_graphroot.setAttribute('transform', ("translate(%f,%f)" % (-minx, -miny))) def add_requirements(self): for required in self.required_defs: if required.startswith("Arrow1Lend"): (shape, color, width) = required.split("_") scale = 1.0 / ((float(width[0:-2]) - 1.0) / 20.0 + 1.0) p = xml.dom.minidom.parseString(""" <defs><marker orient='auto' refY='0.0' refX='0.0' id='%(id)s' style='overflow:visible;'> <path id='path_%(id)s' d='M -5,0.0 L -5,-2.0 L 0.0,0.0 L -5,2.0 z ' style='fill:#%(color)s;fill-rule:evenodd;stroke:#%(color)s;stroke-width:1.0px;marker-start:none;' transform='scale(%(scale)f)' /> </marker></defs>""" % { "id": required, "color": color, "scale": scale }) def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if required.startswith("Arrow1Lstart"): (shape, color, width) = required.split("_") scale = 1.0 / ((float(width[0:-2]) - 1.0) / 20.0 + 1.0) p = xml.dom.minidom.parseString(""" <defs><marker orient='auto' refY='0.0' refX='0.0' id='%(id)s' style='overflow:visible'> <path id='path_%(id)s' d='M 5,0.0 L 5.0,-2.0 L 0.0,0.0 L 5.0,2.0 z' style='fill:#%(color)s;fill-rule:evenodd;stroke:#%(color)s;stroke-width:1.0px;marker-start:none' transform='scale(%(scale)f)'/> </marker></defs>""" % { "id": required, "color": color, "scale": scale }) def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if "Arrow2Lend" in self.required_defs: # TODO p = xml.dom.minidom.parseString(""" <defs><marker orient='auto' refY='0.0' refX='0.0' id='Arrow2Lend' overflow='visible' stroke='currentColor'> <path id='Arrow2Lend' d='M 10.0,-2.0 L 0.0,0.0 L 10.0,2.0' style='fill:none;stroke:#000000;stroke-width:1.0px;marker-start:none' /> </marker></defs>""") def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if "Arrow2Lstart" in self.required_defs: p = xml.dom.minidom.parseString(""" <defs><marker orient='auto' refY='0.0' refX='0.0' id='Arrow2Lstart' overflow='visible' stroke='currentColor'> <path id='pathStickArrow' d='M -10.0,-2.0 L 0.0,0.0 L -10.0,2.0' style='fill:none;stroke:#000000;stroke-width:1.0px;marker-start:none'/> </marker></defs>""") def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if "DropShadow" in self.required_defs: p = xml.dom.minidom.parseString(""" <defs><filter id='DropShadow' filterRes='100' x='0' y='0'> <feGaussianBlur stdDeviation='3' result='MyBlur'/> <feOffset in='MyBlur' dx='2' dy='4' result='movedBlur'/> <feMerge> <feMergeNode in='movedBlur'/> <feMergeNode in='SourceGraphic'/> </feMerge> </filter></defs>""") def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if "CrowBall" in self.required_defs: p = xml.dom.minidom.parseString(""" <defs><marker refX='0' refY='0' orient='auto' id='mCrowBall' style='overflow:visible'> <path d='M 0.0,2.5 L 7.5,0.0 L 0.0,-2.5' style='stroke:#000;stroke-width:1.0px;marker-start:none;fill:none;' /> <circle cx='10' cy='0' r='2.5' style='stroke-width:1px; stroke: #000; fill:none;'/> </marker></defs>""") def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if "Bar" in self.required_defs: p = xml.dom.minidom.parseString(""" <defs><marker refX='0' refY='0' orient='auto' id='mBar' style='overflow:visible'> <path d='M -7.5,-2.5 L -7.5,2.5' style='stroke:#000;stroke-width:1.0px;marker-start:none;fill:none;' /> </marker></defs>""") def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if "network_cloud" in self.required_defs: ''' cloud shape 500x500 ''' p = xml.dom.minidom.parseString(""" <defs><g id='network_cloud'> <path d='M 127.5 106.25 C 127.5 106.25 126.25 18.7501 231.25 45 C 336.25 71.25 317.5 115 318.75 115 C 320 115 300 61.25 380 51.25 C 452.5 57.5 486.25 71.25 482.5 117.5 C 478.75 163.75 443.75 175 443.75 175 C 443.75 175 507.5 181.25 490 275 C 462.5 340 462.5 333.75 398.75 341.25 C 370 330 368.75 320 368.75 320 C 368.75 320 421.25 368.75 342.5 400 C 253.75 423.75 242.5 402.5 205 391.25 C 168.75 368.75 176.25 341.25 176.25 341.25 C 176.25 341.25 198.75 387.5 122.5 396.25 C 46.25 405 17.5 387.5 3.75 316.25 C 2.08616e-06 262.5 67.5 257.5 67.5 257.5 C 67.5 257.5 26.25 258.75 15 231.25 C 3.75 203.75 -3.75 167.5 30 118.75 C 92.5 60 135 98.75 127.5 106.25 z '/> </g></defs>""") def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) def setGraffleStyle(self, style): if style.get("fill") is not None: fill = style.get("fill") if fill.get("Draws", "") == "NO": # don't display self.style["fill"] = "none" else: grap_col = fill.get("Color") if grap_col is not None: fill_col = self.extract_colour(grap_col) self.style["fill"] = "#%s" % fill_col if style.get("stroke") is not None: stroke = style.get("stroke") if stroke.get("Draws", "") == "NO": self.style["stroke"] = "none" else: grap_col = stroke.get("Color", {"r": 0., "g": 0., "b": 0.}) if grap_col is not None: stroke_col = self.extract_colour(grap_col) self.style["stroke"] = "#%s" % stroke_col if stroke.get("Width") is not None: width = stroke["Width"] self.style["stroke-width"] = "%fpx" % float(width) if stroke.get("HeadArrow") is not None: headarrow = stroke["HeadArrow"] marker_end = "none" headscale = stroke.get("HeadScale", 1.0) if headarrow == "FilledArrow": marker_end = "Arrow1Lend_" + self.style["stroke"][ 1:] + "_" + "%fpx" % (float( self.style["stroke-width"][0:-2]) * headscale) self.style["marker-end"] = "url(#%s)" % marker_end self.required_defs.add(marker_end) elif headarrow == "StickArrow": self.style["marker-end"] = "url(#Arrow2Lend)" self.required_defs.add("Arrow2Lend") elif headarrow == "Bar": #TODO self.style["marker-end"] = "url(#mBar)" self.required_defs.add("Bar") elif headarrow == "0": self.style["marker-end"] = "none" else: print("unknown HeadArrow " + headarrow) self.style["marker-end"] = "url(#Arrow2Lend)" self.required_defs.add("Arrow2Lend") if stroke.get("TailArrow") is not None: tailarrow = stroke["TailArrow"] tailscale = stroke.get("TailScale", 1.0) if tailarrow == "FilledArrow": marker_start = "Arrow1Lstart_" + self.style["stroke"][ 1:] + "_" + "%fpx" % (float( self.style["stroke-width"][0:-2]) * tailscale) self.style["marker-start"] = "url(#%s)" % marker_start self.required_defs.add(marker_start) elif tailarrow == "StickArrow": self.style["marker-end"] = "url(#Arrow2Lstart)" self.required_defs.add("Arrow2Lstart") elif tailarrow == "CrowBall": self.style["marker-start"] = "url(#mCrowBall)" self.required_defs.add("CrowBall") elif tailarrow == "0": self.style["marker-start"] = "none" else: print("unknown TailArrow " + headarrow) self.style["marker-end"] = "url(#Arrow2Lstart)" self.required_defs.add("Arrow2Lstart") if stroke.get("Pattern") is not None: pattern = stroke["Pattern"] if pattern == 1: self.style["stroke-dasharray"] = "3 3" elif pattern == 2: self.style["stroke-dasharray"] = "5 5" else: print("unknown pattern " + str(pattern)) self.style["stroke-dasharray"] = "1 1" if style.get("shadow", {}).get("Draws", "NO") != "NO": # for some reason graffle has a shadow by default self.required_defs.add("DropShadow") self.style["filter"] = "url(#DropShadow)"
def setUp(self): self.cs = CascadingStyles({"font":"arial","font-size":"12pt"})
def setUp(self): self.cs = CascadingStyles({"font": "arial", "font-size": "12pt"})
class TargetSvg(object): def __init__(self): self.svg_bounds = None self.svg_dom = None self.svg_current_layer = None self.svg_current_font = "" self.svg_tag = None self.svg_graphroot = None self.svg_def = None self.style = None self.reset() def __bb_box(self, minx, miny, w, h): maxx = minx + w maxy = miny + h if not self.svg_bounds: self.svg_bounds = (minx, miny, maxx, maxy) else: (ix, iy, ax, ay) = self.svg_bounds if minx < ix: ix = minx if miny < iy: iy = miny if maxx > ax: ax = maxx if maxy > ay: ay = maxy self.svg_bounds = (ix, iy, ax, ay) def __bb_point(self, px, py): if not self.svg_bounds: self.svg_bounds = (px, py, px, py) else: (ix, iy, ax, ay) = self.svg_bounds if px < ix: ix = px if py < iy: iy = py if px > ax: ax = px if py > ay: ay = py self.svg_bounds = (ix, iy, ax, ay) def reset(self): self.svg_bounds = None self.svg_dom = xml.dom.minidom.Document() self.svg_dom.doctype = "" self.svg_tag = self.svg_dom.createElement("svg") self.svg_tag.setAttribute("xmlns","http://www.w3.org/2000/svg") self.svg_tag.setAttribute("xmlns:xlink","http://www.w3.org/1999/xlink") self.svg_dom.appendChild(self.svg_tag) def_tag = self.svg_dom.createElement("defs") self.svg_tag.appendChild(def_tag) self.svg_def = def_tag # set of required macros self.required_defs = set() self.style = CascadingStyles() self.style.appendScope() self.style["fill"]="#fff" self.style["stroke"]="#000000" self.style["stroke-width"]="1.000000px" self.svg_graphroot = self.svg_dom.createElement("g") graphic_tag = self.svg_dom.createElement("g") graphic_tag.setAttribute("style",str(self.style)) self.svg_graphroot.appendChild(graphic_tag) self.svg_tag.appendChild(self.svg_graphroot) self.svg_current_layer = graphic_tag @property def svg(self): """Return the svg document""" return self.svg_dom.toprettyxml(encoding='utf-8') def mkHex(self, s): # s is a string of a float h = "%02x"%(int(min(float(s)*256, 255))) return h def extract_colour(self,col): # only gets rgb values (ignores a) return "".join( [self.mkHex(col["r"]), self.mkHex(col["g"]), self.mkHex(col["b"])] ) def setGraffleFont(self, font): if font is None: return fontstuffs = [] font_col = "000000" if font.get("Color") is not None: grap_col = font.get("Color") try: font_col = self.extract_colour(grap_col) except: font_col = "000000" fontstuffs.append("fill:#%s"%font_col) fontstuffs.append("stroke:#%s"%font_col) fontstuffs.append("stroke-width:0.1px") fontfam = font.get("Font") if fontfam is not None: if fontfam == "LucidaGrande": fontfam = "Luxi Sans" elif fontfam == "Courier": fontfam = "Courier New" elif fontfam == "GillSans": fontfam = "Arial Narrow" fontstuffs.append("font-family: %s"%fontfam) size = font.get("Size") if size is not None: fontstuffs.append("font-size:%dpx"%int(size) ) self.svg_current_font = ";".join(fontstuffs) def addLayer(self, graffleInterpreter, GraphicsList): current_layer = self.svg_current_layer self.style.appendScope() g_emt = self.svg_dom.createElement("g") g_emt.setAttribute("style",str(self.style)) current_layer.appendChild(g_emt) self.svg_current_layer = g_emt graffleInterpreter.iterateGraffleGraphics(GraphicsList) self.style.popScope() self.svg_current_layer = current_layer def addAdjustableArrow(self, bounds, graphic,**opts): x,y,width,height = [float(a) for a in bounds] ratio = float(graphic["ShapeData"]["ratio"]) neck = float(graphic["ShapeData"]["width"]) neck_delta = height*(1-ratio)/2 self.addPath([[x,y+neck_delta], [x+width-neck,y+neck_delta], [x+width-neck,y], [x+width,y+height/2], [x+width-neck,y+height], [x+width-neck,y+height-neck_delta], [x,y+height-neck_delta]],closepath=True,**opts) def addDiamond(self,bounds,**opts): x, y, width, height = [float(a) for a in bounds] xmiddle = x+width/2 ymiddle = y+height/2 self.addPath([[x,ymiddle], [xmiddle,y+height], [x+width,ymiddle], [xmiddle,y]],closepath=True,**opts) def addPath(self, pts, **opts): # do geometry mapping here mypts = pts if opts.get("HFlip",False): mypts = geom.h_flip_points(mypts) if opts.get("VFlip",False): mypts = geom.v_flip_points(mypts) if opts.get("Rotation") is not None: mypts = geom.rotate_points(mypts,opts["Rotation"]) for (localx, localy) in mypts: self.__bb_point(localx, localy) ptStrings = [",".join([str(b) for b in a]) for a in mypts] line_string = "M %s"%ptStrings[0] + " ".join(" L %s"%a for a in ptStrings[1:] ) if opts.get("closepath",False): line_string = line_string + " z" path_tag = self.svg_dom.createElement("path") path_tag.setAttribute("id", opts.get("id","")) path_tag.setAttribute("style", str(self.style.scopeStyle())) path_tag.setAttribute("d", line_string) self.svg_current_layer.appendChild(path_tag) def addHorizontalTriangle(self, bounds, **opts): """Graffle has the "HorizontalTriangle" Shape""" x,y,width,height = [float(a) for a in bounds] self.addPath([[x,y],[x+width,y+height/2], [x,y+height]], \ closepath=True, **opts) def addImage(self, bounds, **opts): """SVG viewers should support images - unfortunately many don't :-(""" x,y,width,height = [float(a) for a in bounds] image_tag = self.svg_dom.createElement("image") image_tag.setAttribute("x", str(x)) image_tag.setAttribute("y", str(y)) image_tag.setAttribute("width", str(width)) image_tag.setAttribute("height", str(height)) image_tag.setAttribute("xlink:href", str(opts.get("href",""))) image_tag.setAttribute("style", str(self.style.scopeStyle())) self.__bb_box(x, y, width, height) self.svg_current_layer.appendChild(image_tag) def addRightTriangle(self, bounds, **opts): """Graffle has the "RightTriangle" Shape""" x,y,width,height = [float(a) for a in bounds] self.addPath( [[x,y],[x+width,y+height], [x,y+height]], \ closepath=True, **opts) def addVerticalTriangle(self, bounds, **opts): """Graffle has the "RightTriangle" Shape""" x,y,width,height = [float(a) for a in bounds] self.addPath( [[x,y],[x+width,y], [x+width/2,y+height]], \ closepath=True, **opts) def addRect(self, **opts): """Add an svg rect""" if opts is None: opts = {} rect_tag = self.svg_dom.createElement("rect") rect_tag.setAttribute("id",opts.get("id","")) rect_tag.setAttribute("width",str(opts["width"])) rect_tag.setAttribute("height",str(opts["height"])) rect_tag.setAttribute("x",str(opts.get("x","0"))) rect_tag.setAttribute("y",str(opts.get("y","0"))) if opts.get("rx") is not None: rect_tag.setAttribute("rx",str(opts["rx"])) rect_tag.setAttribute("ry",str(opts["ry"])) rect_tag.setAttribute("style", str(self.style.scopeStyle())) self.svg_current_layer.appendChild(rect_tag) self.__bb_box(opts.get("x","0"), opts.get("y","0"), opts["width"], opts["height"]) def addEllipse(self, bounds, **opts): c = [bounds[i] + (bounds[i+2]/2.) for i in [0,1]] # centre of circle rx = bounds[2]/2. ry = bounds[3]/2. circle_tag = self.svg_dom.createElement("ellipse") circle_tag.setAttribute("id", opts.get("id","")) circle_tag.setAttribute("style", str(self.style.scopeStyle())) circle_tag.setAttribute("cx", str(c[0])) circle_tag.setAttribute("cy", str(c[1])) circle_tag.setAttribute("rx", str(rx)) circle_tag.setAttribute("ry", str(ry)) self.svg_current_layer.appendChild(circle_tag) self.__bb_box(c[0] - rx, c[1] - ry, c[0] + rx, c[1] + ry) def addCloud(self,bounds,**opts): """Add a cloud element""" self.required_defs.add("network_cloud") x, y, dx, dy = bounds cloud_tag = self.svg_dom.createElement("g") cloud_tag.setAttribute("id", opts.get("id","")) cloud_tag.setAttribute("style", str(self.style.scopeStyle())) cloud_tag.setAttribute("transform","translate(%f,%f) scale(%f,%f)" % (x,y,float(dx)/500.0,float(dy)/500.0)) p = xml.dom.minidom.parseString("<use xmlns:xlink='http://www.w3.org/1999/xlink' xlink:href='#network_cloud' />") def_node = p.childNodes[0] cloud_tag.appendChild(def_node) self.svg_current_layer.appendChild(cloud_tag) self.__bb_box(x, y, dx, dy) def addText(self,**opts): """Add an svg text element""" text_tag = self.svg_dom.createElement("text") text_tag.setAttribute("id",str(opts.get("id",""))+"_text") text_tag.setAttribute("x",str(opts.get("x","0"))) text_tag.setAttribute("y",str(opts.get("y","0"))) # text_tag.setAttribute("dominant-baseline","mathematical") text_tag.setAttribute("style", self.svg_current_font) self.svg_current_layer.appendChild(text_tag) # Generator lines = extractRTFString(opts["rtftext"]) font_info = opts.get("fontinfo",None) if font_info is not None: font_height = int(font_info.get("Size")) else: font_height = 12 total_height = 0.0 for span in lines: total_height += float(span["style"].get("font-size","%.1fpx"%font_height)[:-2]) y_diff = float(opts.get("y", "12.0")) + opts.get("height",0)/2 -total_height/2 line_id=opts["id"] linenb = 0 for span in lines: y_diff+= float(span["style"].get("font-size","%.1fpx"%font_height)[:-2]) linenb+=1 opts["id"]=str(line_id)+"_line"+str(linenb) self.addLine(text_tag,text = span["string"], style = span["style"],\ y_pos=y_diff, line_height =font_height, **opts) self.__bb_box(opts.get("x", "0"), opts.get("y", "0"), opts.get("width", "0"), opts.get("height", "0")) def addLine(self,textnode, **opts): """Add a line of text""" tspan_node = self.svg_dom.createElement("tspan") tspan_node.setAttribute("id",opts.get("id","")) align = opts.get("style", {"text-align":"left"}).get("text-align", "left") x = opts.get("x",0) if align=="center": tspan_node.setAttribute("text-anchor","middle") x+=opts.get("width", 0)/2 elif align == "right": x+=opts.get("width", 0) tspan_node.setAttribute("x",str(x)) y_pos = float(opts.get("y_pos",0)) tspan_node.setAttribute("y",str(y_pos)) if (opts.get("style") is not None) and len(opts.get("style"))>0: thestyle = opts.get("style") for (k,v) in thestyle.items(): tspan_node.setAttribute(k,v) actual_string = self.svg_dom.createTextNode(opts.get("text"," ")) tspan_node.appendChild(actual_string) textnode.appendChild(tspan_node) def add_document_bounds(self): if self.svg_bounds: (minx, miny, maxx, maxy) = self.svg_bounds # self.svg_tag.setAttribute("width", str(maxx - minx)) # self.svg_tag.setAttribute("height", str(maxy - miny)) self.svg_graphroot.setAttribute('transform', ("translate(%f,%f)" % (-minx, -miny))) def add_requirements(self): for required in self.required_defs: if required.startswith("Arrow1Lend"): (shape, color, width) = required.split( "_") scale=1.0/((float(width[0:-2])-1.0)/20.0+1.0) p = xml.dom.minidom.parseString(""" <defs><marker orient='auto' refY='0.0' refX='0.0' id='%(id)s' style='overflow:visible;'> <path id='path_%(id)s' d='M -5,0.0 L -5,-2.0 L 0.0,0.0 L -5,2.0 z ' style='fill:#%(color)s;fill-rule:evenodd;stroke:#%(color)s;stroke-width:1.0px;marker-start:none;' transform='scale(%(scale)f)' /> </marker></defs>"""%{"id":required, "color":color, "scale":scale}) def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if required.startswith("Arrow1Lstart"): (shape, color, width) = required.split( "_") scale=1.0/((float(width[0:-2])-1.0)/20.0+1.0) p = xml.dom.minidom.parseString(""" <defs><marker orient='auto' refY='0.0' refX='0.0' id='%(id)s' style='overflow:visible'> <path id='path_%(id)s' d='M 5,0.0 L 5.0,-2.0 L 0.0,0.0 L 5.0,2.0 z' style='fill:#%(color)s;fill-rule:evenodd;stroke:#%(color)s;stroke-width:1.0px;marker-start:none' transform='scale(%(scale)f)'/> </marker></defs>"""%{"id":required, "color":color, "scale":scale}) def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if "Arrow2Lend" in self.required_defs: # TODO p = xml.dom.minidom.parseString(""" <defs><marker orient='auto' refY='0.0' refX='0.0' id='Arrow2Lend' overflow='visible' stroke='currentColor'> <path id='Arrow2Lend' d='M 10.0,-2.0 L 0.0,0.0 L 10.0,2.0' style='fill:none;stroke:#000000;stroke-width:1.0px;marker-start:none' /> </marker></defs>""") def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if "Arrow2Lstart" in self.required_defs: p = xml.dom.minidom.parseString(""" <defs><marker orient='auto' refY='0.0' refX='0.0' id='Arrow2Lstart' overflow='visible' stroke='currentColor'> <path id='pathStickArrow' d='M -10.0,-2.0 L 0.0,0.0 L -10.0,2.0' style='fill:none;stroke:#000000;stroke-width:1.0px;marker-start:none'/> </marker></defs>""") def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if "DropShadow" in self.required_defs: p = xml.dom.minidom.parseString(""" <defs><filter id='DropShadow' filterRes='100' x='0' y='0'> <feGaussianBlur stdDeviation='3' result='MyBlur'/> <feOffset in='MyBlur' dx='2' dy='4' result='movedBlur'/> <feMerge> <feMergeNode in='movedBlur'/> <feMergeNode in='SourceGraphic'/> </feMerge> </filter></defs>""") def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if "CrowBall" in self.required_defs: p = xml.dom.minidom.parseString(""" <defs><marker refX='0' refY='0' orient='auto' id='mCrowBall' style='overflow:visible'> <path d='M 0.0,2.5 L 7.5,0.0 L 0.0,-2.5' style='stroke:#000;stroke-width:1.0px;marker-start:none;fill:none;' /> <circle cx='10' cy='0' r='2.5' style='stroke-width:1px; stroke: #000; fill:none;'/> </marker></defs>""") def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if "Bar" in self.required_defs: p = xml.dom.minidom.parseString(""" <defs><marker refX='0' refY='0' orient='auto' id='mBar' style='overflow:visible'> <path d='M -7.5,-2.5 L -7.5,2.5' style='stroke:#000;stroke-width:1.0px;marker-start:none;fill:none;' /> </marker></defs>""") def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) if "network_cloud" in self.required_defs: ''' cloud shape 500x500 ''' p = xml.dom.minidom.parseString(""" <defs><g id='network_cloud'> <path d='M 127.5 106.25 C 127.5 106.25 126.25 18.7501 231.25 45 C 336.25 71.25 317.5 115 318.75 115 C 320 115 300 61.25 380 51.25 C 452.5 57.5 486.25 71.25 482.5 117.5 C 478.75 163.75 443.75 175 443.75 175 C 443.75 175 507.5 181.25 490 275 C 462.5 340 462.5 333.75 398.75 341.25 C 370 330 368.75 320 368.75 320 C 368.75 320 421.25 368.75 342.5 400 C 253.75 423.75 242.5 402.5 205 391.25 C 168.75 368.75 176.25 341.25 176.25 341.25 C 176.25 341.25 198.75 387.5 122.5 396.25 C 46.25 405 17.5 387.5 3.75 316.25 C 2.08616e-06 262.5 67.5 257.5 67.5 257.5 C 67.5 257.5 26.25 258.75 15 231.25 C 3.75 203.75 -3.75 167.5 30 118.75 C 92.5 60 135 98.75 127.5 106.25 z '/> </g></defs>""") def_node = p.childNodes[0] for node in def_node.childNodes: self.svg_def.appendChild(node) def setGraffleStyle(self, style): if style.get("fill") is not None: fill = style.get("fill") if fill.get("Draws","") == "NO": # don't display self.style["fill"]="none" else: grap_col = fill.get("Color") if grap_col is not None: fill_col = self.extract_colour(grap_col) self.style["fill"]="#%s"%fill_col if style.get("stroke") is not None: stroke = style.get("stroke") if stroke.get("Draws","") == "NO": self.style["stroke"]="none" else: grap_col = stroke.get("Color",{"r":0.,"g":0.,"b":0.}) if grap_col is not None: stroke_col = self.extract_colour(grap_col) self.style["stroke"]="#%s"%stroke_col if stroke.get("Width") is not None: width = stroke["Width"] self.style["stroke-width"]="%fpx"%float(width) if stroke.get("HeadArrow") is not None: headarrow = stroke["HeadArrow"] marker_end = "none" headscale = stroke.get("HeadScale", 1.0) if headarrow == "FilledArrow": marker_end = "Arrow1Lend_" + self.style["stroke"] [1:] + "_" + "%fpx"%(float(self.style["stroke-width"][0:-2])*headscale) self.style["marker-end"]="url(#%s)"%marker_end self.required_defs.add(marker_end) elif headarrow == "StickArrow": self.style["marker-end"]="url(#Arrow2Lend)" self.required_defs.add("Arrow2Lend") elif headarrow == "Bar": #TODO self.style["marker-end"]="url(#mBar)" self.required_defs.add("Bar") elif headarrow == "0": self.style["marker-end"] = "none" else: print("unknown HeadArrow "+ headarrow) self.style["marker-end"]="url(#Arrow2Lend)" self.required_defs.add("Arrow2Lend") if stroke.get("TailArrow") is not None: tailarrow = stroke["TailArrow"] tailscale = stroke.get("TailScale",1.0) if tailarrow == "FilledArrow": marker_start = "Arrow1Lstart_" + self.style["stroke"] [1:] + "_" + "%fpx"%(float(self.style["stroke-width"][0:-2])*tailscale) self.style["marker-start"]="url(#%s)"%marker_start self.required_defs.add(marker_start) elif tailarrow =="StickArrow": self.style["marker-end"]="url(#Arrow2Lstart)" self.required_defs.add("Arrow2Lstart") elif tailarrow == "CrowBall": self.style["marker-start"] = "url(#mCrowBall)" self.required_defs.add("CrowBall") elif tailarrow == "0": self.style["marker-start"]="none" else: print("unknown TailArrow "+ headarrow) self.style["marker-end"]="url(#Arrow2Lstart)" self.required_defs.add("Arrow2Lstart") if stroke.get("Pattern") is not None: pattern = stroke["Pattern"] if pattern == 1: self.style["stroke-dasharray"]="3 3" elif pattern == 2: self.style["stroke-dasharray"]="5 5" else: print("unknown pattern " + str(pattern)) self.style["stroke-dasharray"]="1 1" if style.get("shadow",{}).get("Draws","NO") != "NO": # for some reason graffle has a shadow by default self.required_defs.add("DropShadow") self.style["filter"]="url(#DropShadow)"
def extractRTFString(s): """Extract a string and some styling info""" bracket_depth = 0 instruction = False inst_code = "" ftable = FontTable() colortable = ColorTable() # The string being generated: std_string = "" style = CascadingStyles() # Want to set these as defaults even if not specified style.appendScope() """TODO: extract styling e.g. {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf460 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural \f0\fs28 \cf0 Next ads are represented in a book-ended carousel on end screen} """ def do_instruction(inst_code, i, std_string): if inst_code == "b": style["font-weight"] = "bold" if inst_code == "ql": style["text-align"] = "left" elif inst_code == "qr": style["text-align"] = "right" elif inst_code == "qj": style["text-align"] = "justify" elif inst_code == "qc": style["text-align"] = "center" elif inst_code == "fonttbl": i = ftable.parseTable(s,i) elif inst_code == "colortbl": i = colortable.parseTable(s, i) elif inst_code[0]=="f" and inst_code[1:].isdigit(): # Font looked up in font table font = ftable.fonts.get(int(inst_code[1:]),{}) for k,v in font.items(): if k != "": style[k] = v elif inst_code[:2]=="fs" and isint(inst_code[2:]): # font size - RTF specifies half pt sizes style["font-size"] = "%.1fpx"%(float(inst_code[2:])/2.0) elif inst_code[:2]=="cf" and isint(inst_code[2:]): # font colour is enytry int(inst_code[2:]) in the colour table style["fill"] = "#" + colortable[int(inst_code[2:])] style["stroke"] = "#" + colortable[int(inst_code[2:])] elif inst_code[0] == "u" and isint(inst_code[1:]): std_string += chr(int(inst_code[1:])) return i, std_string i = -1 result_lines =[] while i < len(s)-1: i += 1 c = s[i] if c == "{": bracket_depth +=1 style.appendScope() elif c == "}": if len(inst_code) > 0: i, std_string = do_instruction(inst_code, i, std_string) instruction=False inst_code="" if std_string != "": result_lines.append({"string":std_string, "style":style.currentStyle()}) std_string = "" style.popScope() bracket_depth -=1 elif c == "\\": if len(inst_code) > 0: i, std_string = do_instruction(inst_code, i, std_string) instruction = True inst_code = "" if instruction: if c == " ": instruction = False i, std_string = do_instruction(inst_code, i, std_string) inst_code="" elif c == "\n": instruction = False if inst_code == "": result_lines.append({"string":std_string, "style":style.currentStyle()}) std_string = "" else: i, std_string = do_instruction(inst_code, i, std_string) inst_code="" elif not c in "\\;": inst_code += c else: if bracket_depth == 1: if not c in "{}\\\n\r": # those characters are escaped std_string += c style.popScope() return result_lines