# PyDia SVG Renderer # Copyright (c) 2003, 2004 Hans Breuer # # A full blown SVG(Z) renderer. As of this writing less bugs in the output # than the Dia SVG renderer written in C # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import sys, string, dia class SvgRenderer : def __init__ (self) : self.f = None self.line_caps = 0 self.line_join = 0 self.line_style = 0 self.dash_length = 0 def _open(self, filename) : self.f = open(filename, "w") def begin_render (self, data, filename) : self._open (filename) r = data.extents xofs = - r[0] yofs = - r[1] self.f.write(''' ''' % (r.right - r.left, r.bottom - r.top, r[0], r[1], r[2], r[3])) #self.f.write("\n" % (str(data.extents))) #self.f.write("\n" % (data.active_layer.name)) def end_render (self) : self.f.write('') self.f.close() def set_linewidth (self, width) : if width < 0.001 : # zero line width is invisble ? self.line_width = 0.001 else : self.line_width = width def set_linecaps (self, mode) : self.line_caps = mode def set_linejoin (self, mode) : self.line_join = mode def set_linestyle (self, style) : self.line_style = style def set_dashlength (self, length) : self.dash_length = length def set_fillstyle (self, style) : # currently only 'solid' so not used anywhere else self.fill_style = style def set_font (self, font, size) : self.font = font self.font_size = size def draw_line (self, start, end, color) : self.f.write('\n' \ % (start.x, start.y, end.x, end.y, self._rgb(color), self.line_width, self._stroke_style())) def draw_polyline (self, points, color) : self.f.write('\n') def draw_polygon (self, points, color) : self.f.write('\n') def fill_polygon (self, points, color) : self.f.write('\n') def draw_rect (self, rect, color) : self.f.write('\n' \ % ( rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, self._rgb(color), self.line_width, self._stroke_style())) def fill_rect (self, rect, color) : self.f.write('\n' \ % ( rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, self._rgb(color))) def _arc (self, center, width, height, angle1, angle2, color, fill=None) : # not in the renderer interface import math mPi180 = math.pi / 180.0 rx = width / 2.0 ry = height / 2.0 sx = center.x + rx * math.cos(mPi180 * angle1) sy = center.y - ry * math.sin(mPi180 * angle1) ex = center.x + rx * math.cos(mPi180 * angle2) ey = center.y - ry * math.sin(mPi180 * angle2) largearc = (angle2 - angle1 >= 180) sweep = 0 # always draw in negative direction if not fill : self.f.write('\n') def draw_arc (self, center, width, height, angle1, angle2, color) : self._arc(center, width, height, angle1, angle2, color) def fill_arc (self, center, width, height, angle1, angle2, color) : self._arc(center, width, height, angle1, angle2, color, 1) def draw_ellipse (self, center, width, height, color) : self.f.write('' \ % (center.x, center.y, width / 2, height / 2, self._rgb(color), self.line_width, self._stroke_style())) def fill_ellipse (self, center, width, height, color) : self.f.write('' \ % (center.x, center.y, width / 2, height / 2, self._rgb(color))) def draw_bezier (self, bezpoints, color) : self.f.write('\n') def fill_bezier (self, bezpoints, color) : self.f.write('\n') def draw_string (self, text, pos, alignment, color) : if len(text) < 1 : return # shouldn'this be done at the higher level talign = ('start', 'middle', 'end') [alignment] fstyle = ('normal', 'italic', 'oblique') [self.font.style & 0x03] fweight = (400, 200, 300, 500, 600, 700, 800, 900) [(self.font.style >> 4) & 0x7] self.f.write('\n' \ % (pos.x, pos.y, self._rgb(color), talign, self.font_size, self.font.family, fstyle, fweight)) # avoid writing XML special characters (ampersand must be first to not break the rest) for rep in [('&', '&'), ('<', '<'), ('>', '>'), ('"', '"'), ("'", ''')] : text = string.replace (text, rep[0], rep[1]) self.f.write(text) self.f.write('\n') def draw_image (self, point, width, height, image) : #FIXME : do something better than absolute pathes ? self.f.write('\n' \ % (point.x, point.y, width, height, image.uri)) # Helpers, not in the DiaRenderer interface def _rgb(self, color) : # given a dia color convert to svg color string rgb = "#%02X%02X%02X" % (int(255 * color.red), int(color.green * 255), int(color.blue * 255)) return rgb def _stroke_style(self) : # return the current line style as svg string dashlen =self.dash_length # dashlen/style interpretation like the DiaGdkRenderer dotlen = dashlen * 0.1 caps = self.line_caps join = self.line_join style = self.line_style st = "" if style == 0 : # LINESTYLE_SOLID pass elif style == 1 : # DASHED st = 'stroke-dasharray="%.2f,%.2f"' % (dashlen, dashlen) elif style == 2 : # DASH_DOT, gaplen = (dashlen - dotlen) / 2.0 st = 'stroke-dasharray="%.2f,%.2f,%.2f,%.2f"' % (dashlen, gaplen, dotlen, gaplen) elif style == 3 : # DASH_DOT_DOT, gaplen = (dashlen - dotlen) / 3.0 st = 'stroke-dasharray="%.2f,%.2f,%.2f,%.2f,%.2f,%.2f"' % (dashlen, gaplen, dotlen, gaplen, dotlen, gaplen) elif style == 4 : # DOTTED st = 'stroke-dasharray="%.2f,%.2f"' % (dotlen, dotlen) if join == 0 : # MITER pass # st = st + ' stroke-linejoin="bevel"' elif join == 1 : # ROUND st = st + ' stroke-linejoin="round"' elif join == 2 : # BEVEL st = st + ' stroke-linejoin="bevel"' if caps == 0 : # BUTT pass # default stroke-linecap="butt" elif caps == 1 : # ROUND st = st + ' stroke-linecap="round"' elif caps == 2 : # PROJECTING st = st + ' stroke-linecap="square"' # is this the same ? return st class SvgzRenderer(SvgRenderer) : def _open(self, filename) : # There is some (here) not wanted behaviour in gzip.open/GzipFile : # the filename with path is not only used to adress the file but also # completely stored in the file itself. Correct it here. import os, os.path, gzip path, name = os.path.split(filename) os.chdir(path) self.f = gzip.open (name, "wb") # dia-python keeps a reference to the renderer class and uses it on demand dia.register_export ("SVG plain", "svg", SvgRenderer()) dia.register_export ("SVG compressed", "svgz", SvgzRenderer())