def convertImage(self, node): getAttr = node.getAttribute x, y, width, height = map(getAttr, ('x', 'y', "width", "height")) x, y, width, height = map(self.attrConverter.convertLength, (x, y, width, height)) xlink_href = node.attrib.get('{http://www.w3.org/1999/xlink}href') magic_re = r"^data:image/(jpeg|png);base64" match = re.match(magic_re, xlink_href) if match: img_format = match.groups()[0] image_data = base64.decodestring(xlink_href[(match.span(0)[1] + 1):].encode('ascii')) _, path = tempfile.mkstemp(suffix='.%s' % img_format) with open(path, 'wb') as fh: fh.write(image_data) img = Image(int(x), int(y + height), int(width), int(height), path) # this needs to be removed later, not here... # if exists(path): os.remove(path) else: xlink_href = os.path.join(os.path.dirname(self.svg_source_file), xlink_href) img = Image(int(x), int(y + height), int(width), int(height), xlink_href) try: # this will catch invalid image PDFImage(xlink_href, 0, 0) except IOError: logger.error("Unable to read the image %s. Skipping..." % img.path) return None group = Group(img) group.translate(0, (x + height) * 2) group.scale(1, -1) return group
def renderSvg(self, node, outermost=False): getAttr = node.getAttribute _saved_preserve_space = self.shape_converter.preserve_space self.shape_converter.preserve_space = getAttr("{%s}space" % XML_NS) == 'preserve' group = Group() for child in node.getchildren(): self.renderNode(child, group) self.shape_converter.preserve_space = _saved_preserve_space if not outermost: x, y = map(getAttr, ("x", "y")) x, y = map(self.attrConverter.convertLength, (x, y)) if x or y: group.translate(x or 0, y or 0) view_box = self.get_box(node) if view_box: x_scale, y_scale = 1, 1 width, height = map(getAttr, ("width", "height")) width, height = map(self.attrConverter.convertLength, (width, height)) if view_box.height != height: y_scale = height / view_box.height if view_box.width != width: x_scale = width / view_box.width group.scale(x_scale, y_scale) return group
def img_save(): img_path = PATH.rsplit('.', 1)[0] font = TTFont( PATH) # it would work just as well with fontTools.t1Lib.T1Font glyf = font['glyf'] if not os.path.exists(img_path): os.makedirs(img_path) for glyphName in glyf.keys(): gs = font.getGlyphSet() pen = ReportLabPen(gs, Path(fillColor=colors.black, strokeWidth=1)) g = gs[glyphName] g.draw(pen) # 调整图片大小 w, h = 800, 800 g = Group(pen.path) g.translate(10, 200) g.scale(0.3, 0.3) d = Drawing(w, h) d.add(g) image = renderPM.drawToPIL(d) little_image = image.resize((180, 180)) fromImage = Image.new('RGBA', (360, 360), color=(255, 255, 255)) fromImage.paste(little_image, (0, 0)) # fromImage.show() glyphName = glyphName.replace('uni', '&#x') imageFile = img_path + "/%s.png" % glyphName fromImage.save(imageFile) break
def __init__(self,width=224,height=124,*args,**kw): Drawing.__init__(self,width,height,*args,**kw) points = [122.0, 87.0, 122.0, 88.0, 123.0, 88.0, 123.0, 89.0, 124.0, 89.0, 124.0, 90.0, 126.0, 90.0, 126.0, 89.0, 128.0, 88.0, 128.0, 89.0, 129.0, 89.0, 129.0, 91.0, 128.0, 91.0, 128.0, 92.0, 130.0, 99.0, 130.0, 100.0, 129.0, 100.0, 126.0, 103.0, 125.0, 103.0, 125.0, 104.0, 126.0, 106.0, 130.0, 87.0, 129.0, 87.0, 129.0, 86.0, 126.0, 86.0, 126.0, 87.0] grp = Group(Polygon(points, fillColor=toColor('red'))) grp.scale(1, -1) grp.translate(0, -124) self.add(grp)
def __init__(self,width=224,height=124,*args,**kw): Drawing.__init__(self,width,height,*args,**kw) points = [122.0, 87.0, 122.0, 88.0, 123.0, 88.0, 123.0, 89.0, 124.0, 89.0, 124.0, 90.0, 126.0, 90.0, 126.0, 89.0, 128.0, 88.0, 128.0, 89.0, 129.0, 89.0, 129.0, 91.0, 128.0, 91.0, 128.0, 92.0, 130.0, 99.0, 130.0, 100.0, 129.0, 100.0, 126.0, 103.0, 125.0, 103.0, 125.0, 104.0, 126.0, 106.0, 130.0, 87.0, 129.0, 87.0, 129.0, 86.0, 126.0, 86.0, 126.0, 87.0] grp = Group(Polygon(points, fillColor=toColor('red'))) grp.scale(1, -1) grp.translate(0, -124) self.add(grp)
def generate_pic(glyphname, font: TTFont, size=120, scale=0.1): """ 生成图片 -> 预处理 -> 保存到本地 Args: glyphname: font: file_suffix: size: scale: Returns: """ gs = font.getGlyphSet() pen = ReportLabPen(gs, Path(fillColor=colors.black, strokeWidth=1)) g = gs[glyphname] g.draw(pen) w, h = size, size # Everything is wrapped in a group to allow transformations. g = Group(pen.path) # g.translate(10, 20) g.scale(scale, scale) d = Drawing(w, h) d.add(g) return renderPM.drawToPIL(d)
def draw(self): fillColor = self.fillColor strokeColor = self.strokeColor g = Group() bg = self.background bd = self.border bdw = self.borderWidth shadow = self.shadow x, y = self.x, self.y if bg: if shadow is not None and 0<=shadow<1: shadow = Color(bg.red*shadow,bg.green*shadow,bg.blue*shadow) self._paintLogo(g,dy=-2.5, dx=2,fillColor=shadow) self._paintLogo(g,fillColor=fillColor,strokeColor=strokeColor) g.skew(kx=self.skewX, ky=self.skewY) g.shift(self._dx,self._dy) G = Group() G.add(g) _w, _h = 130, 86 w, h = self.width, self.height if bg or (bd and bdw): G.insert(0,Rect(0,0,_w,_h,fillColor=bg,strokeColor=bd,strokeWidth=bdw)) if w!=_w or h!=_h: G.scale(w/float(_w),h/float(_h)) angle = self.angle if self.angle: w, h = w/2., h/2. G.shift(-w,-h) G.rotate(angle) G.shift(w,h) G.shift(x,y) return G
def ttf_ocr(font, key=None): gs = font.getGlyphSet() keys = [key for key in gs.keys() if key.startswith("uni")] if key is None else [key] c = [] for i, key in enumerate(keys): if key not in gs: logger.info("No this key: %s" % key) c.append("") continue pen = ReportLabPen(gs, Path(fillColor=colors.black, strokeWidth=0.01)) g = gs[key] g.draw(pen) w, h = 50, 50 g = Group(pen.path) g.translate(10, 10) g.scale(0.02, 0.02) d = Drawing(w, h) d.add(g) renderPM.drawToFile(d, png_file.name, fmt="PNG") result = os.popen("tesseract %s stdout -l chi_sim -psm 5" % png_file.name).read().strip().decode("utf-8", "ignore") if len(result) != 1: result = os.popen("tesseract %s stdout -l chi_sim -psm 8" % png_file.name).read().strip().decode("utf-8", "ignore") logger.info("key: %s, result: %s" % (key, result)) c.append(result) if key is not None: return c[0] return c
def convertText(self, node): attrConv = self.attrConverter xml_space = node.getAttribute("{%s}space" % XML_NS) if xml_space: preserve_space = xml_space == 'preserve' else: preserve_space = self.preserve_space gr = Group() frag_lengths = [] dx0, dy0 = 0, 0 x1, y1 = 0, 0 ff = attrConv.findAttr(node, "font-family") or DEFAULT_FONT_NAME ff = attrConv.convertFontFamily(ff) fs = attrConv.findAttr(node, "font-size") or "12" fs = attrConv.convertLength(fs) convertLength = partial(attrConv.convertLength, em_base=fs) x, y = map(node.getAttribute, ('x', 'y')) x, y = map(convertLength, (x, y)) for c in itertools.chain([node], node.getchildren()): has_x, has_y = False, False dx, dy = 0, 0 baseLineShift = 0 if node_name(c) == 'text': text = self.clean_text(c.text, preserve_space) if not text: continue elif node_name(c) == 'tspan': text = self.clean_text(c.text, preserve_space) if not text: continue x1, y1, dx, dy = [c.attrib.get(name, '') for name in ("x", "y", "dx", "dy")] has_x, has_y = (x1 != '', y1 != '') x1, y1, dx, dy = map(convertLength, (x1, y1, dx, dy)) dx0 = dx0 + dx dy0 = dy0 + dy baseLineShift = c.attrib.get("baseline-shift", '0') if baseLineShift in ("sub", "super", "baseline"): baseLineShift = {"sub":-fs/2, "super":fs/2, "baseline":0}[baseLineShift] else: baseLineShift = convertLength(baseLineShift, fs) else: continue frag_lengths.append(stringWidth(text, ff, fs)) new_x = (x1 + dx) if has_x else (x + dx0 + sum(frag_lengths[:-1])) new_y = (y1 + dy) if has_y else (y + dy0) shape = String(new_x, -(new_y - baseLineShift), text) self.applyStyleOnShape(shape, node) if node_name(c) == 'tspan': self.applyStyleOnShape(shape, c) gr.add(shape) gr.scale(1, -1) return gr
def convertText(self, node): attrConv = self.attrConverter x, y = map(node.getAttribute, ('x', 'y')) x, y = map(attrConv.convertLength, (x, y)) xml_space = node.getAttribute("{%s}space" % XML_NS) if xml_space: preserve_space = xml_space == 'preserve' else: preserve_space = self.preserve_space gr = Group() frag_lengths = [] dx0, dy0 = 0, 0 x1, y1 = 0, 0 ff = attrConv.findAttr(node, "font-family") or "Helvetica" ff = attrConv.convertFontFamily(ff) fs = attrConv.findAttr(node, "font-size") or "12" fs = attrConv.convertLength(fs) for c in itertools.chain([node], node.getchildren()): has_x = False dx, dy = 0, 0 baseLineShift = 0 if node_name(c) == 'text': text = self.clean_text(c.text, preserve_space) if not text: continue elif node_name(c) == 'tspan': text = self.clean_text(c.text, preserve_space) if not text: continue x1, y1, dx, dy = [c.attrib.get(name, '') for name in ("x", "y", "dx", "dy")] has_x = x1 != '' x1, y1, dx, dy = map(attrConv.convertLength, (x1, y1, dx, dy)) dx0 = dx0 + dx dy0 = dy0 + dy baseLineShift = c.attrib.get("baseline-shift", '0') if baseLineShift in ("sub", "super", "baseline"): baseLineShift = {"sub":-fs/2, "super":fs/2, "baseline":0}[baseLineShift] else: baseLineShift = attrConv.convertLength(baseLineShift, fs) else: continue frag_lengths.append(stringWidth(text, ff, fs)) new_x = x1 if has_x else sum(frag_lengths[:-1]) shape = String(x + new_x, y - y1 - dy0 + baseLineShift, text) self.applyStyleOnShape(shape, node) if node_name(c) == 'tspan': self.applyStyleOnShape(shape, c) gr.add(shape) gr.scale(1, -1) gr.translate(0, -2*y) return gr
def _borderDraw(self,f): s = self.size # abbreviate as we will use this a lot g = Group() g.add(f) x, y, sW = self.x+self.dx, self.y+self.dy, self.strokeWidth/2. g.insert(0,Rect(-sW, -sW, width=getattr(self,'_width',2*s)+3*sW, height=getattr(self,'_height',s)+2*sW, fillColor = None, strokeColor = self.strokeColor, strokeWidth=sW*2)) g.shift(x,y) g.scale(s/_size, s/_size) return g
def _borderDraw(self,f): s = self.size # abbreviate as we will use this a lot g = Group() g.add(f) x, y, sW = self.x+self.dx, self.y+self.dy, self.strokeWidth/2. g.insert(0,Rect(-sW, -sW, width=getattr(self,'_width',2*s)+3*sW, height=getattr(self,'_height',s)+2*sW, fillColor = None, strokeColor = self.strokeColor, strokeWidth=sW*2)) g.shift(x,y) g.scale(s/_size, s/_size) return g
def draw(self): fillColor = self.fillColor strokeColor = self.strokeColor g = Group() bg = self.background bd = self.border bdw = self.borderWidth shadow = self.shadow x, y = self.x, self.y if bg: if shadow is not None and 0 <= shadow < 1: shadow = Color(bg.red * shadow, bg.green * shadow, bg.blue * shadow) self._paintLogo(g, dy=-2.5, dx=2, fillColor=shadow) self._paintLogo(g, fillColor=fillColor, strokeColor=strokeColor, _ocolors=getattr(self, '_ocolors', None), _pagecolors=getattr(self, '_pagecolors', None)) g.skew(kx=self.skewX, ky=self.skewY) g.shift(self._dx, self._dy) G = Group() G.add(g) _w, _h = 130, 86 w, h = self.width, self.height if bg or (bd and bdw): G.insert( 0, Rect(0, 0, _w, _h, fillColor=bg, strokeColor=bd, strokeWidth=bdw)) if w != _w or h != _h: G.scale(w / float(_w), h / float(_h)) angle = self.angle if self.angle: w, h = w / 2., h / 2. G.shift(-w, -h) G.rotate(angle) G.shift(w, h) xFlip = getattr(self, 'xFlip', 0) and -1 or 0 yFlip = getattr(self, 'yFlip', 0) and -1 or 0 if xFlip or yFlip: sx = xFlip or 1 sy = yFlip or 1 G.shift(sx * x + w * xFlip, sy * y + yFlip * h) G = Group(G, transform=(sx, 0, 0, sy, 0, 0)) else: G.shift(x, y) return G
def draw(self): sx = 0.5 fillColor = self.fillColor strokeColor = self.strokeColor shadow = Color(fillColor.red * sx, fillColor.green * sx, fillColor.blue * sx) g = Group() g2 = Group() g.add( Rect(fillColor=fillColor, strokeColor=fillColor, x=0, y=0, width=self._w, height=self._h)) sx = (self._w - 2) / self._sw() g2.scale(sx, 1) self._addPage(g2, strokeWidth=3, dx=2, dy=-2.5, color=shadow) self._addPage(g2, strokeWidth=3, color=strokeColor) g2.scale(1 / sx, 1) g2.add(self._getText(x=1, y=0, color=shadow)) g2.add(self._getText(x=0, y=1, color=strokeColor)) g2.scale(sx, 1) g2.skew(kx=10, ky=0) g2.shift(0, 38) g.add(g2) g.scale(self.width / self._w, self.height / self._h) g.shift(self.x, self.y) return g
def draw(self): fillColor = self.fillColor strokeColor = self.strokeColor g = Group() bg = self.background bd = self.border bdw = self.borderWidth shadow = self.shadow x, y = self.x, self.y if bg: if shadow is not None and 0 <= shadow < 1: shadow = Color(bg.red * shadow, bg.green * shadow, bg.blue * shadow) self._paintLogo(g, dy=-2.5, dx=2, fillColor=shadow) self._paintLogo( g, fillColor=fillColor, strokeColor=strokeColor, _ocolors=getattr(self, "_ocolors", None), _pagecolors=getattr(self, "_pagecolors", None), ) g.skew(kx=self.skewX, ky=self.skewY) g.shift(self._dx, self._dy) G = Group() G.add(g) _w, _h = 130, 86 w, h = self.width, self.height if bg or (bd and bdw): G.insert(0, Rect(0, 0, _w, _h, fillColor=bg, strokeColor=bd, strokeWidth=bdw)) if w != _w or h != _h: G.scale(w / float(_w), h / float(_h)) angle = self.angle if self.angle: w, h = w / 2.0, h / 2.0 G.shift(-w, -h) G.rotate(angle) G.shift(w, h) xFlip = getattr(self, "xFlip", 0) and -1 or 0 yFlip = getattr(self, "yFlip", 0) and -1 or 0 if xFlip or yFlip: sx = xFlip or 1 sy = yFlip or 1 G.shift(sx * x + w * xFlip, sy * y + yFlip * h) G = Group(G, transform=(sx, 0, 0, sy, 0, 0)) else: G.shift(x, y) return G
def draw_one(self, unicode, font_io): ttf_font = TTFont(font_io) glyphSet = ttf_font.getGlyphSet() if unicode not in glyphSet: return None glyph = glyphSet[unicode] pen = reportLabPen.ReportLabPen( glyphSet, Path(fillColor=colors.black, strokeWidth=1)) glyph.draw(pen) w, h = glyph.width, glyph._glyph.yMax - glyph._glyph.yMin yOffset = -glyph._glyph.yMin * \ self.font_scale + h * (1 - self.font_scale) / 2 glyph = Group(pen.path) glyph.translate(w * (1 - self.font_scale) / 2, yOffset) glyph.scale(self.font_scale, self.font_scale) draw = Drawing(w, h) draw.add(glyph) PIL_image = renderPM.drawToPIL(draw) return PIL_image
def _draw(self): """ :param font_path: 下载的web 字体所在目录 :param image_path: 转换为image后所在目录 :return:返回font 对应的编码列表 """ import os import shutil shutil.rmtree(os.path.join(os.path.abspath('.'), "web_font", "image")) # 能删除该文件夹和文件夹下所有文件 os.mkdir(os.path.join(os.path.abspath('.'), "web_font", "image")) from fontTools.ttLib import TTFont from reportlab.lib import colors font = TTFont( os.path.join(os.path.abspath('.'), "web_font", "stonefont.woff") ) # it would work just as well with fontTools.t1Lib.T1Font gs = font.getGlyphSet() w, h = 40, 40 keys = gs.keys()[2:] for glyphName in keys: pen = FontPen(gs, Path(fillColor=colors.red, strokeWidth=0.01)) imageFile = "%s.png" % glyphName g = gs[glyphName] g.draw(pen) from reportlab.graphics import renderPM from reportlab.graphics.shapes import Group, Drawing g = Group(pen.path) g.translate(10, 13) g.scale(0.02, 0.02) d = Drawing(w, h) d.add(g) renderPM.drawToFile(d, os.path.join(os.path.abspath('.'), "web_font", "image", imageFile), fmt="PNG", dpi=100) return keys
def convertImage(self, node): if not haveImages: logger.warning( "Unable to handle embedded images. Maybe the pillow library is missing?" ) return None getAttr = node.getAttribute x, y, width, height = map(getAttr, ('x', 'y', "width", "height")) x, y, width, height = map(self.attrConverter.convertLength, (x, y, width, height)) image = node._resolved_target is_raster = isinstance(image, str) # A path to a file if is_raster: image = Image(int(x), int(y + height), int(width), int(height), image) group = Group(image) if is_raster: group.translate(0, (y + height) * 2) group.scale(1, -1) return group
def draw_all(self, font_io): ttf_font = TTFont(font_io) glyphSet = ttf_font.getGlyphSet() font_image_dict = {} for glyphName in glyphSet.keys(): if (not glyphName.startswith('uni')): continue pen = reportLabPen.ReportLabPen( glyphSet, Path(fillColor=colors.black, strokeWidth=1)) glyph = glyphSet[glyphName] glyph.draw(pen) w, h = glyph.width, glyph._glyph.yMax - glyph._glyph.yMin yOffset = -glyph._glyph.yMin * \ self.font_scale + h * (1 - self.font_scale) / 2 glyph = Group(pen.path) glyph.translate(w * (1 - self.font_scale) / 2, yOffset) glyph.scale(self.font_scale, self.font_scale) draw = Drawing(w, h) draw.add(glyph) PIL_image = renderPM.drawToPIL(draw) font_image_dict[glyphName] = PIL_image return font_image_dict
def draw(self): sx = 0.5 fillColor = self.fillColor strokeColor = self.strokeColor shadow = Color(fillColor.red*sx,fillColor.green*sx,fillColor.blue*sx) g = Group() g2= Group() g.add(Rect(fillColor=fillColor, strokeColor=fillColor, x=0, y=0, width=self._w, height=self._h)) sx = (self._w-2)/self._sw() g2.scale(sx,1) self._addPage(g2,strokeWidth=3,dx=2,dy=-2.5,color=shadow) self._addPage(g2,strokeWidth=3,color=strokeColor) g2.scale(1/sx,1) g2.add(self._getText(x=1,y=0,color=shadow)) g2.add(self._getText(x=0,y=1,color=strokeColor)) g2.scale(sx,1) g2.skew(kx=10, ky=0) g2.shift(0,38) g.add(g2) g.scale(self.width/self._w,self.height/self._h) g.shift(self.x,self.y) return g
def render(self, node, parent=None): if parent is None: parent = self.mainGroup # ignore if display = none display = node.get('display') if display == "none": return if node.tag == self.SVG_ROOT: self.level += 1 if not self.drawing is None: raise SVGError('drawing already created!') self.root = node # default styles style = { 'color': 'none', 'fill': 'none', 'stroke': 'none', 'font-family': 'Helvetica', 'font-size': '12' } self.styles[self.level] = style # iterate children for child in node: self.render(child, self.mainGroup) # create drawing width = node.get('width', '100%') height = node.get('height', '100%') if node.get("viewBox"): try: minx, miny, width, height = node.get("viewBox").split() except ValueError: raise SVGError("viewBox values not valid") if width.endswith('%') and height.endswith('%'): # handle relative size wscale = parseLength(width) / 100. hscale = parseLength(height) / 100. xL, yL, xH, yH = self.mainGroup.getBounds() self.drawing = Drawing(xH * wscale + xL, yH * hscale + yL) else: self.drawing = Drawing(parseLength(width), parseLength(height)) height = self.drawing.height self.mainGroup.scale(1, -1) self.mainGroup.translate(0, -height) self.drawing.add(self.mainGroup) self.level -= 1 return self.drawing elif node.tag in (self.SVG_G, self.SVG_A): self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() style = self.nodeStyle(node, style) self.styles[self.level] = style group = Group() # iterate children for child in node: self.render(child, group) parent.add(group) transforms = node.get('transform') if transforms: for op in parseTransform.iterparse(transforms): self.applyTransformOnGroup(group, op) self.level -= 1 elif node.tag == self.SVG_USE: self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() style = self.nodeStyle(node, style) self.styles[self.level] = style group = Group() # link id link_id = node.get(self.LINK).lstrip('#') # find linked node in defs or symbol section target = None for defs in self.root.getiterator(self.SVG_DEFS): for element in defs: if element.get('id') == link_id: target = element break if target is None: for defs in self.root.getiterator(self.SVG_SYMBOL): for element in defs: if element.get('id') == link_id: target = element break if target is None: msg = "Could not find use node '%s'" % link_id raise SVGError(msg) self.render(target, group) parent.add(group) # apply transform transforms = node.get('transform') if transforms: for op in parseTransform.iterparse(transforms): self.applyTransformOnGroup(group, op) # apply 'x' and 'y' attribute as translation of defs object if node.get('x') or node.get('y'): dx = parseLength(node.get('x', '0')) dy = parseLength(node.get('y', '0')) self.applyTransformOnGroup(group, ('translate', (dx, dy))) self.level -= 1 elif node.tag == self.SVG_LINE: # get coordinates x1 = parseLength(node.get('x1', '0')) y1 = parseLength(node.get('y1', '0')) x2 = parseLength(node.get('x2', '0')) y2 = parseLength(node.get('y2', '0')) shape = Line(x1, y1, x2, y2) self.addShape(parent, node, shape) elif node.tag == self.SVG_RECT: # get coordinates x = parseLength(node.get('x', '0')) y = parseLength(node.get('y', '0')) width = parseLength(node.get('width')) height = parseLength(node.get('height')) rx = parseLength(node.get('rx', '0')) ry = parseLength(node.get('ry', '0')) shape = Rect(x, y, width, height, rx=rx, ry=ry) self.addShape(parent, node, shape) elif node.tag == self.SVG_CIRCLE: cx = parseLength(node.get('cx', '0')) cy = parseLength(node.get('cy', '0')) r = parseLength(node.get('r')) if r > 0.: shape = Circle(cx, cy, r) self.addShape(parent, node, shape) elif node.tag == self.SVG_ELLIPSE: cx = parseLength(node.get('cx', '0')) cy = parseLength(node.get('cy', '0')) rx = parseLength(node.get('rx')) ry = parseLength(node.get('ry')) if rx > 0. and ry > 0.: shape = Ellipse(cx, cy, rx, ry) self.addShape(parent, node, shape) elif node.tag == self.SVG_POLYLINE: # convert points points = node.get('points').strip() if len(points) == 0: return points = list(map(parseLength, re.split('[ ,]+', points))) # Need to use two shapes, because standard RLG polylines # do not support filling... group = Group() shape = Polygon(points) self.applyStyleToShape(shape, node) shape.strokeColor = None group.add(shape) shape = PolyLine(points) self.applyStyleToShape(shape, node) group.add(shape) self.addShape(parent, node, group) elif node.tag == self.SVG_POLYGON: # convert points points = node.get('points').strip() if len(points) == 0: return points = list(map(parseLength, re.split('[ ,]+', points))) shape = Polygon(points) self.addShape(parent, node, shape) elif node.tag == self.SVG_IMAGE: x = parseLength(node.get('x', '0')) y = parseLength(node.get('y', '0')) width = parseLength(node.get('width', '0')) height = parseLength(node.get('height', '0')) # link id link_id = node.get(self.LINK) filename = os.path.join(os.path.dirname(self.filename), link_id) shape = Image(x, y, width, height, filename) self.addShape(parent, node, shape) elif node.tag == self.SVG_TEXT: # Todo: # - rotation not handled # - baseshift not handled # - embedded span node not handled # def parsePos(node, subnode, name, default='0'): values = subnode.get(name) if values is None: if node is not None: values = node.get(name, default) else: values = default return list(map(parseLength, values.split())) def getPos(values, i, default=None): if i >= len(values): if default is None: return values[-1] else: return default else: return values[i] def handleText(node, subnode, text): # get position variables xs = parsePos(node, subnode, 'x') dxs = parsePos(node, subnode, 'dx') ys = parsePos(node, subnode, 'y') dys = parsePos(node, subnode, 'dy') if sum(map(len, (xs, ys, dxs, dys))) == 4: # single value shape = String(xs[0] + dxs[0], -ys[0] - dys[0], text) self.applyStyleToShape(shape, subnode) group.add(shape) else: # multiple values for i, c in enumerate(text): x = getPos(xs, i) dx = getPos(dxs, i, 0) y = getPos(ys, i) dy = getPos(dys, i, 0) shape = String(x + dx, -y - dy, c) self.applyStyleToShape(shape, subnode) group.add(shape) if node.text and node.text.strip(): group = Group() handleText(None, node, node.text.strip()) group.scale(1, -1) self.addShape(parent, node, group) if len(node) > 0: group = Group() self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() nodestylestyle = self.nodeStyle(node, style) self.styles[self.level] = nodestylestyle for subnode in node: if subnode.tag == self.SVG_TSPAN: handleText(node, subnode, subnode.text.strip()) self.level -= 1 group.scale(1, -1) self.addShape(parent, node, group) elif node.tag == self.SVG_PATH: def convertQuadratic(Q0, Q1, Q2): C1 = (Q0[0] + 2. / 3 * (Q1[0] - Q0[0]), Q0[1] + 2. / 3 * (Q1[1] - Q0[1])) C2 = (C1[0] + 1. / 3 * (Q2[0] - Q0[0]), C1[1] + 1. / 3 * (Q2[1] - Q0[1])) C3 = Q2 return C1[0], C1[1], C2[0], C2[1], C3[0], C3[1] def prevCtrl(lastOp, lastArgs, currentX, currentY): # fetch last controll point if lastOp in 'CScsQqTt': x, y = lastArgs[-2] # mirror about current point return currentX + (currentX - x), currentY + (currentY - y) else: # defaults to current point return currentX, currentY # store sub paths in 'paths' list shape = Path() # keep track of current point and path start point startX, startY = 0., 0. currentX, currentY = 0., 0. # keep track of last operation lastOp = None lastArgs = None # avoid empty path data data = node.get('d') if data is None or len(data) == 0: return for op, args in parsePath.iterparse(data): if op == 'z' or op == 'Z': # close path or subpath shape.closePath() # next sub path starts at begining of current path currentX, currentY = startX, startY elif op == 'M': # moveto absolute if lastOp is not None and lastOp not in ('z', 'Z'): # close sub path shape.closePath() x, y = args[0] shape.moveTo(x, y) startX, startY = x, y # multiple moveto arge result in line for x, y in args[1:]: shape.lineTo(x, y) currentX, currentY = x, y elif op == 'm': if lastOp is not None and lastOp not in ('z', 'Z'): # close sub path shape.closePath() # moveto relative rx, ry = args[0] x, y = currentX + rx, currentY + ry shape.moveTo(x, y) startX, startY = x, y currentX, currentY = x, y # multiple moveto arge result in line for rx, ry in args[1:]: x, y = currentX + rx, currentY + ry shape.lineTo(x, y) currentX, currentY = x, y elif op == 'L': # lineto absolute for x, y in args: shape.lineTo(x, y) currentX, currentY = x, y elif op == 'l': # lineto relative for rx, ry in args: x, y = currentX + rx, currentY + ry shape.lineTo(x, y) currentX, currentY = x, y elif op == 'V': # vertical line absolute for y in args: shape.lineTo(currentX, y) currentY = y elif op == 'v': # vertical line relative for ry in args: y = currentY + ry shape.lineTo(currentX, y) currentY = y elif op == 'H': # horisontal line absolute for x in args: shape.lineTo(x, currentY) currentX = x elif op == 'h': # horisontal line relative for rx in args: x = currentX + rx shape.lineTo(x, currentY) currentX = x elif op == 'C': # cubic bezier absolute for p1, p2, p3 in zip(*([iter(args)] * 3)): shape.curveTo(*(p1 + p2 + p3)) currentX, currentY = p3 elif op == 'c': # cubic bezier relative for pnts in zip(*([iter(args)] * 3)): (x1, y1), (x2, y2), (x3, y3) = pnts pnts = tuple( (p[0] + currentX, p[1] + currentY) for p in pnts) shape.curveTo(*(pnts[0] + pnts[1] + pnts[2])) currentX, currentY = pnts[2] lastOp = op lastArgs = pnts continue elif op == 'S': # shorthand cubic bezier absolute for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY) x2, y2 = p2 x3, y3 = p3 shape.curveTo(x1, y1, x2, y2, x3, y3) lastOp = op lastArgs = (x2, y2), (x3, y3) currentX, currentY = x3, y3 continue elif op == 's': # shorthand cubic bezier relative for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY) x2, y2 = p2 x2, y2 = x2 + currentX, y2 + currentY x3, y3 = p3 x3, y3 = x3 + currentX, y3 + currentY shape.curveTo(x1, y1, x2, y2, x3, y3) currentX, currentY = x3, y3 lastOp = op lastArgs = (x1, y1), (x2, y2), (x3, y3) continue elif op == 'Q': # quadratic bezier absolute for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = currentX, currentY x2, y2 = p2 x3, y3 = p3 ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'q': # quadratic bezier relative for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = currentX, currentY x2, y2 = p2 x2, y2 = x2 + currentX, y2 + currentY x3, y3 = p3 x3, y3 = x3 + currentX, y3 + currentY ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'T': # shorthand quadratic bezier absolute for i in range(len(args)): x1, y1 = currentX, currentY x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY) x3, y3 = args[i] ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 't': # shorthand quadratic bezier relative for i in range(len(args)): x1, y1 = currentX, currentY x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY) x3, y3 = args[i] x3, y3 = x3 + currentX, y3 + currentY ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'A' or op == 'a': # elliptic arc missing continue lastOp = op lastArgs = args # check if fill applies to path fill = None if node.get('fill'): # inline style fill = node.get('fill') else: # try local style if node.get('style'): style = parseStyle.parse(node.get('style')) if 'fill' in style: fill = style['fill'] # try global style if fill is None: style = self.styles[self.level] if 'fill' in style: fill = style['fill'] else: fill = 'none' # hack because RLG has no "semi-closed" paths... if lastOp == 'z' or lastOp == 'Z' or fill == 'none': self.addShape(parent, node, shape) else: group = Group() strokeshape = shape.copy() self.addShape(group, node, strokeshape, fill='none') shape.closePath() self.addShape(group, node, shape, stroke='none') self.addShape(parent, node, group) elif node.tag == self.SVG_TITLE or node.tag == self.SVG_DESC: # Skip non-graphics elements return
glyphName = sys.argv[2] if (len(sys.argv) > 3): imageFile = sys.argv[3] else: imageFile = "%s.png" % glyphName font = TTFont(path) # it would work just as well with fontTools.t1Lib.T1Font gs = font.getGlyphSet() pen = ReportLabPen(gs, Path(fillColor=colors.black, strokeWidth=0)) #pen = ReportLabPen(gs, Path(fillColor=colors.red, strokeWidth=5)) g = gs[glyphName] g.draw(pen) w, h = g.width, 1000 from reportlab.graphics import renderPM from reportlab.graphics.shapes import Group, Drawing, scale # Everything is wrapped in a group to allow transformations. g = Group(pen.path) g.translate(10, 10) #g.scale(0.9, 0.9) #g.scale(0.3, 0.3) g.scale(0.8, 0.8) d = Drawing(w, h) d.add(g) from reportlab.lib.colors import white from reportlab.lib.colors import green renderPM.drawToFile(d, imageFile, fmt="TIFF", configPIL={'transparent':white})
class Renderer: LINK = '{http://www.w3.org/1999/xlink}href' SVG_DEFS = '{http://www.w3.org/2000/svg}defs' SVG_ROOT = '{http://www.w3.org/2000/svg}svg' SVG_A = '{http://www.w3.org/2000/svg}a' SVG_G = '{http://www.w3.org/2000/svg}g' SVG_SYMBOL = '{http://www.w3.org/2000/svg}symbol' SVG_USE = '{http://www.w3.org/2000/svg}use' SVG_RECT = '{http://www.w3.org/2000/svg}rect' SVG_CIRCLE = '{http://www.w3.org/2000/svg}circle' SVG_ELLIPSE = '{http://www.w3.org/2000/svg}ellipse' SVG_LINE = '{http://www.w3.org/2000/svg}line' SVG_POLYLINE = '{http://www.w3.org/2000/svg}polyline' SVG_POLYGON = '{http://www.w3.org/2000/svg}polygon' SVG_PATH = '{http://www.w3.org/2000/svg}path' SVG_TEXT = '{http://www.w3.org/2000/svg}text' SVG_TSPAN = '{http://www.w3.org/2000/svg}tspan' SVG_IMAGE = '{http://www.w3.org/2000/svg}image' SVG_NODES = frozenset((SVG_ROOT, SVG_A, SVG_G, SVG_SYMBOL, SVG_USE, SVG_RECT, SVG_CIRCLE, SVG_ELLIPSE, SVG_LINE, SVG_POLYLINE, SVG_POLYGON, SVG_PATH, SVG_TEXT, SVG_IMAGE)) def __init__(self, filename): self.filename = filename self.level = 0 self.styles = {} self.mainGroup = Group() self.drawing = None self.root = None def render(self, node, parent = None): if parent == None: parent = self.mainGroup # ignore if display = none display = node.get('display') if display == "none": return if node.tag == self.SVG_ROOT: self.level += 1 if not self.drawing is None: raise SVGError('drawing already created!') self.root = node # default styles style = { 'color':'black', 'fill':'black', 'stroke':'none', 'font-family':'Helvetica', 'font-size':'12' } self.styles[self.level] = style # iterate children for child in node: self.render(child, self.mainGroup) # create drawing width = node.get('width', '100%') height = node.get('height', '100%') if node.get("viewBox"): try: minx, miny, width, height = node.get("viewBox").split() except ValueError: raise SVGError("viewBox values not valid") if width.endswith('%') and height.endswith('%'): # handle relative size wscale = parseLength(width) / 100. hscale = parseLength(height) / 100. xL,yL,xH,yH = self.mainGroup.getBounds() self.drawing = Drawing(xH*wscale + xL, yH*hscale + yL) else: self.drawing = Drawing(parseLength(width), parseLength(height)) height = self.drawing.height self.mainGroup.scale(1, -1) self.mainGroup.translate(-float(minx), -height-float(miny)) self.drawing.add(self.mainGroup) self.level -= 1 return self.drawing elif node.tag in (self.SVG_G, self.SVG_A, self.SVG_SYMBOL): self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() style = self.nodeStyle(node, style) self.styles[self.level] = style group = Group() # iterate children for child in node: self.render(child, group) parent.add(group) transforms = node.get('transform') if transforms: for op in parseTransform.iterparse(transforms): self.applyTransformOnGroup(group, op) self.level -= 1 elif node.tag == self.SVG_USE: self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() style = self.nodeStyle(node, style) self.styles[self.level] = style group = Group() # link id link_id = node.get(self.LINK).lstrip('#') # find linked node in defs section target = None for defs in self.root.getiterator(self.SVG_DEFS): for element in defs: if element.get('id') == link_id: target = element break if target is None: raise SVGError("Could not find use node '%s'" % link_id) self.render(target, group) parent.add(group) # apply transform transforms = node.get('transform') if transforms: for op in parseTransform.iterparse(transforms): self.applyTransformOnGroup(group, op) # apply 'x' and 'y' attribute as translation of defs object if node.get('x') or node.get('y'): dx = parseLength(node.get('x','0')) dy = parseLength(node.get('y','0')) self.applyTransformOnGroup(group, ('translate', (dx,dy))) self.level -= 1 elif node.tag == self.SVG_LINE: # get coordinates x1 = parseLength(node.get('x1', '0')) y1 = parseLength(node.get('y1', '0')) x2 = parseLength(node.get('x2', '0')) y2 = parseLength(node.get('y2', '0')) shape = Line(x1, y1, x2, y2) self.addShape(parent, node, shape) elif node.tag == self.SVG_RECT: # get coordinates x = parseLength(node.get('x', '0')) y = parseLength(node.get('y', '0')) width = parseLength(node.get('width')) height = parseLength(node.get('height')) rx = parseLength(node.get('rx', '0')) ry = parseLength(node.get('ry', '0')) shape = Rect(x, y, width, height, rx=rx, ry=ry) self.addShape(parent, node, shape) elif node.tag == self.SVG_CIRCLE: cx = parseLength(node.get('cx', '0')) cy = parseLength(node.get('cy', '0')) r = parseLength(node.get('r')) if r > 0.: shape = Circle(cx, cy, r) self.addShape(parent, node, shape) elif node.tag == self.SVG_ELLIPSE: cx = parseLength(node.get('cx', '0')) cy = parseLength(node.get('cy', '0')) rx = parseLength(node.get('rx')) ry = parseLength(node.get('ry')) if rx > 0. and ry > 0.: shape = Ellipse(cx, cy, rx, ry) self.addShape(parent, node, shape) elif node.tag == self.SVG_POLYLINE: # convert points points = node.get('points').strip() if len(points) == 0: return points = list(map(parseLength, re.split('[ ,]+', points))) # Need to use two shapes, because standard RLG polylines # do not support filling... group = Group() shape = Polygon(points) self.applyStyleToShape(shape, node) shape.strokeColor = None group.add(shape) shape = PolyLine(points) self.applyStyleToShape(shape, node) group.add(shape) self.addShape(parent, node, group) elif node.tag == self.SVG_POLYGON: # convert points points = node.get('points').strip() if len(points) == 0: return points = list(map(parseLength, re.split('[ ,]+', points))) shape = Polygon(points) self.addShape(parent, node, shape) elif node.tag == self.SVG_IMAGE: x = parseLength(node.get('x', '0')) y = parseLength(node.get('y', '0')) width = parseLength(node.get('width', '0')) height = parseLength(node.get('height', '0')) # link id link_id = node.get(self.LINK) filename = os.path.join(os.path.dirname(self.filename), link_id) shape = Image(x, y, width, height, filename) self.addShape(parent, node, shape) elif node.tag == self.SVG_TEXT: # Todo: # - rotation not handled # - baseshift not handled # - embedded span node not handled # def parsePos(node, subnode, name, default = '0'): values = subnode.get(name) if values is None: if not node is None: values = node.get(name, default) else: values = default return list(map(parseLength, values.split())) def getPos(values, i, default = None): if i >= len(values): if default is None: return values[-1] else: return default else: return values[i] def handleText(node, subnode, text): # get position variables xs = parsePos(node, subnode, 'x') dxs = parsePos(node, subnode, 'dx') ys = parsePos(node, subnode,'y') dys = parsePos(node, subnode,'dy') if sum(map(len, (xs,ys,dxs,dys))) == 4: # single value shape = String(xs[0] + dxs[0], -ys[0] - dys[0], text) self.applyStyleToShape(shape, subnode) group.add(shape) else: # multiple values for i,c in enumerate(text): x = getPos(xs, i) dx = getPos(dxs, i, 0) y = getPos(ys, i) dy = getPos(dys, i, 0) shape = String(x + dx, -y -dy, c) self.applyStyleToShape(shape, subnode) group.add(shape) if node.text and node.text.strip(): group = Group() handleText(None, node, node.text.strip()) group.scale(1, -1) self.addShape(parent, node, group) if len(node) > 0: group = Group() self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() nodestylestyle = self.nodeStyle(node, style) self.styles[self.level] = nodestylestyle for subnode in node: if subnode.tag == self.SVG_TSPAN: handleText(node, subnode, subnode.text.strip()) self.level -= 1 group.scale(1, -1) self.addShape(parent, node, group) elif node.tag == self.SVG_PATH: def convertQuadratic(Q0, Q1, Q2): C1 = (Q0[0] + 2./3*(Q1[0] - Q0[0]), Q0[1] + 2./3*(Q1[1] - Q0[1])) C2 = (C1[0] + 1./3*(Q2[0] - Q0[0]), C1[1] + 1./3*(Q2[1] - Q0[1])) C3 = Q2 return C1[0], C1[1], C2[0], C2[1], C3[0], C3[1] def prevCtrl(lastOp, lastArgs, currentX, currentY): # fetch last controll point if lastOp in 'CScsQqTt': x, y = lastArgs[-2] # mirror about current point return currentX + (currentX-x), currentY + (currentY-y) else: # defaults to current point return currentX, currentY # store sub paths in 'paths' list shape = Path() # keep track of current point and path start point startX, startY = 0.,0. currentX, currentY = 0.,0. # keep track of last operation lastOp = None lastArgs = None # avoid empty path data data = node.get('d') if data is None or len(data) == 0: return for op, args in parsePath.iterparse(data): if op == 'z' or op == 'Z': # close path or subpath shape.closePath() # next sub path starts at begining of current path currentX, currentY = startX, startY elif op == 'M': # moveto absolute if not lastOp is None and lastOp not in ('z', 'Z'): # close sub path shape.closePath() x, y = args[0] shape.moveTo(x, y) startX, startY = x, y # multiple moveto arge result in line for x, y in args[1:]: shape.lineTo(x, y) currentX, currentY = x, y elif op == 'm': if not lastOp is None and lastOp not in ('z', 'Z'): # close sub path shape.closePath() # moveto relative rx, ry = args[0] x, y = currentX + rx, currentY + ry shape.moveTo(x, y) startX, startY = x, y currentX, currentY = x, y # multiple moveto arge result in line for rx, ry in args[1:]: x, y = currentX + rx, currentY + ry shape.lineTo(x, y) currentX, currentY = x, y elif op == 'L': # lineto absolute for x,y in args: shape.lineTo(x, y) currentX, currentY = x, y elif op == 'l': # lineto relative for rx,ry in args: x, y = currentX + rx, currentY + ry shape.lineTo(x, y) currentX, currentY = x, y elif op == 'V': # vertical line absolute for y in args: shape.lineTo(currentX, y) currentY = y elif op == 'v': # vertical line relative for ry in args: y = currentY + ry shape.lineTo(currentX, y) currentY = y elif op == 'H': # horisontal line absolute for x in args: shape.lineTo(x, currentY) currentX = x elif op == 'h': # horisontal line relative for rx in args: x = currentX + rx shape.lineTo(x, currentY) currentX = x elif op == 'C': # cubic bezier absolute for i in range(len(args)//3): x1, y1 = args[i*3 + 0] x2, y2 = args[i*3 + 1] x3, y3 = args[i*3 + 2] shape.curveTo(x1, y1, x2, y2, x3, y3) currentX, currentY = x3, y3 elif op == 'c': # cubic bezier relative for i in range(len(args)//3): x1, y1 = args[i*3 + 0] x1, y1 = x1 + currentX, y1 + currentY x2, y2 = args[i*3 + 1] x2, y2 = x2 + currentX, y2 + currentY x3, y3 = args[i*3 + 2] x3, y3 = x3 + currentX, y3 + currentY shape.curveTo(x1, y1, x2, y2, x3, y3) currentX, currentY = x3, y3 lastOp = op lastArgs = (x1, y1), (x2, y2), (x3, y3) continue elif op == 'S': # shorthand cubic bezier absolute for i in range(len(args)//2): x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY) x2, y2 = args[i*2 + 0] x3, y3 = args[i*2 + 1] shape.curveTo(x1, y1, x2, y2, x3, y3) lastOp = op lastArgs = (x2, y2), (x3, y3) currentX, currentY = x3, y3 continue elif op == 's': # shorthand cubic bezier relative for i in range(len(args)//2): x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY) x2, y2 = args[i*2 + 0] x2, y2 = x2 + currentX, y2 + currentY x3, y3 = args[i*2 + 1] x3, y3 = x3 + currentX, y3 + currentY shape.curveTo(x1, y1, x2, y2, x3, y3) currentX, currentY = x3, y3 lastOp = op lastArgs = (x1, y1), (x2, y2), (x3, y3) continue elif op == 'Q': # quadratic bezier absolute for i in range(len(args)//2): x1, y1 = currentX, currentY x2, y2 = args[i*2 + 0] x3, y3 = args[i*2 + 1] ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'q': # quadratic bezier relative for i in range(len(args)//2): x1, y1 = currentX, currentY x2, y2 = args[i*2 + 0] x2, y2 = x2 + currentX, y2 + currentY x3, y3 = args[i*2 + 1] x3, y3 = x3 + currentX, y3 + currentY ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'T': # shorthand quadratic bezier absolute for i in range(len(args)): x1, y1 = currentX, currentY x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY) x3, y3 = args[i] ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 't': # shorthand quadratic bezier relative for i in range(len(args)): x1, y1 = currentX, currentY x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY) x3, y3 = args[i] x3, y3 = x3 + currentX, y3 + currentY ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'A' or op == 'a': # elliptic arc missing continue lastOp = op lastArgs = args # check if fill applies to path fill = None if node.get('fill'): # inline style fill = node.get('fill') else: # try local style if node.get('style'): style = parseStyle.parse(node.get('style')) if 'fill' in style: fill = style['fill'] # try global style if fill is None: style = self.styles[self.level] if 'fill' in style: fill = style['fill'] else: fill = 'none' # hack because RLG has no "semi-closed" paths... if lastOp == 'z' or lastOp == 'Z' or fill == 'none': self.addShape(parent, node, shape) else: group = Group() strokeshape = shape.copy() self.addShape(group, node, strokeshape, fill = 'none') shape.closePath() self.addShape(group, node, shape, stroke = 'none') self.addShape(parent, node, group) def addShape(self, parent, node, shape, **kwargs): self.applyStyleToShape(shape, node, **kwargs) transform = node.get('transform') if transform: # transform can only be applied to a group if isinstance(node, Group): group = node else: group = Group() for op in parseTransform.iterparse(transform): self.applyTransformOnGroup(group, op) if not isinstance(node, Group): group.add(shape) parent.add(group) else: parent.add(shape) return shape def nodeStyle(self, node, style = None): if style is None: style = {} # update with local style if node.get('style'): localstyle = parseStyle.parse(node.get('style')) for name, value in six.iteritems(localstyle): style[name] = value # update with inline style for name in STYLE_NAMES: value = node.get(name) if value: style[name] = value return style def applyStyleToShape(self, shape, node, **kwargs): # fetch global styles for this level globalstyle = self.styles[self.level] # update with node style localstyle = self.nodeStyle(node) for name in STYLES: # avoid text styles if not a String subclass if not isinstance(shape, String) and name in STYLES_FONT: continue value = None if name in kwargs: value = kwargs[name] elif name in localstyle: value = localstyle[name] elif name in globalstyle: value = globalstyle[name] else: continue # handle 'currentColor' if value == 'currentColor': if 'color' in localstyle: value = localstyle['color'] elif 'color' in globalstyle: value = globalstyle['color'] # fetch shape attribute name and converter attrname, converter = STYLES[name] # Sett attribute of shape if hasattr(shape, attrname): setattr(shape, attrname, converter(value)) # defaults if isinstance(shape, String): if shape.fillColor is None: shape.fillColor = colors.black elif isinstance(shape, (Line, PolyLine)): if shape.strokeColor is None: shape.strokeColor = colors.black elif isinstance(shape, (Rect, Circle, Ellipse, Polygon)): if shape.fillColor is None and shape.strokeColor is None: shape.strokeColor = colors.black def applyTransformOnGroup(self, group, transform): op, args = transform if op == 'scale': group.scale(*args) elif op == 'translate': group.translate(*args) elif op == 'rotate': if len(args) == 2: angle, center = args if center: cx, cy = center group.translate(cx, cy) group.rotate(angle) group.translate(-cx, -cy) else: group.rotate(angle) elif op == "skewX": angle = args[0] group.skew(angle, 0) elif op == "skewY": angle = args[0] group.skew(0, angle) elif op == "matrix": group.transform = mmult(group.transform, args)
class Renderer: LINK = '{http://www.w3.org/1999/xlink}href' SVG_DEFS = '{http://www.w3.org/2000/svg}defs' SVG_ROOT = '{http://www.w3.org/2000/svg}svg' SVG_A = '{http://www.w3.org/2000/svg}a' SVG_G = '{http://www.w3.org/2000/svg}g' SVG_SYMBOL = '{http://www.w3.org/2000/svg}symbol' SVG_USE = '{http://www.w3.org/2000/svg}use' SVG_RECT = '{http://www.w3.org/2000/svg}rect' SVG_CIRCLE = '{http://www.w3.org/2000/svg}circle' SVG_ELLIPSE = '{http://www.w3.org/2000/svg}ellipse' SVG_LINE = '{http://www.w3.org/2000/svg}line' SVG_POLYLINE = '{http://www.w3.org/2000/svg}polyline' SVG_POLYGON = '{http://www.w3.org/2000/svg}polygon' SVG_PATH = '{http://www.w3.org/2000/svg}path' SVG_TEXT = '{http://www.w3.org/2000/svg}text' SVG_TSPAN = '{http://www.w3.org/2000/svg}tspan' SVG_IMAGE = '{http://www.w3.org/2000/svg}image' SVG_NODES = frozenset((SVG_ROOT, SVG_A, SVG_G, SVG_SYMBOL, SVG_USE, SVG_RECT, SVG_CIRCLE, SVG_ELLIPSE, SVG_LINE, SVG_POLYLINE, SVG_POLYGON, SVG_PATH, SVG_TEXT, SVG_IMAGE)) def __init__(self, filename): self.filename = filename self.level = 0 self.styles = {} self.mainGroup = Group() self.drawing = None self.root = None def render(self, node, parent = None): if parent == None: parent = self.mainGroup # ignore if display = none display = node.get('display') if display == "none": return if node.tag == self.SVG_ROOT: self.level += 1 if not self.drawing is None: raise SVGError('drawing already created!') self.root = node # default styles style = { 'color':'none', 'fill':'none', 'stroke':'none', 'font-family':'Helvetica', 'font-size':'12' } self.styles[self.level] = style # iterate children for child in node: self.render(child, self.mainGroup) # create drawing width = node.get('width', '100%') height = node.get('height', '100%') if node.get("viewBox"): try: minx, miny, width, height = node.get("viewBox").split() except ValueError: raise SVGError("viewBox values not valid") if width.endswith('%') and height.endswith('%'): # handle relative size wscale = parseLength(width) / 100. hscale = parseLength(height) / 100. xL,yL,xH,yH = self.mainGroup.getBounds() self.drawing = Drawing(xH*wscale + xL, yH*hscale + yL) else: self.drawing = Drawing(parseLength(width), parseLength(height)) height = self.drawing.height self.mainGroup.scale(1, -1) self.mainGroup.translate(0, -height) self.drawing.add(self.mainGroup) self.level -= 1 return self.drawing elif node.tag in (self.SVG_G, self.SVG_A, self.SVG_SYMBOL): self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() style = self.nodeStyle(node, style) self.styles[self.level] = style group = Group() # iterate children for child in node: self.render(child, group) parent.add(group) transforms = node.get('transform') if transforms: for op in parseTransform.iterparse(transforms): self.applyTransformOnGroup(group, op) self.level -= 1 elif node.tag == self.SVG_USE: self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() style = self.nodeStyle(node, style) self.styles[self.level] = style group = Group() # link id link_id = node.get(self.LINK).lstrip('#') # find linked node in defs section target = None for defs in self.root.getiterator(self.SVG_DEFS): for element in defs: if element.get('id') == link_id: target = element break if target is None: raise SVGError("Could not find use node '%s'" % link_id) self.render(target, group) parent.add(group) # apply transform transforms = node.get('transform') if transforms: for op in parseTransform.iterparse(transforms): self.applyTransformOnGroup(group, op) # apply 'x' and 'y' attribute as translation of defs object if node.get('x') or node.get('y'): dx = parseLength(node.get('x','0')) dy = parseLength(node.get('y','0')) self.applyTransformOnGroup(group, ('translate', (dx,dy))) self.level -= 1 elif node.tag == self.SVG_LINE: # get coordinates x1 = parseLength(node.get('x1', '0')) y1 = parseLength(node.get('y1', '0')) x2 = parseLength(node.get('x2', '0')) y2 = parseLength(node.get('y2', '0')) shape = Line(x1, y1, x2, y2) self.addShape(parent, node, shape) elif node.tag == self.SVG_RECT: # get coordinates x = parseLength(node.get('x', '0')) y = parseLength(node.get('y', '0')) width = parseLength(node.get('width')) height = parseLength(node.get('height')) rx = parseLength(node.get('rx', '0')) ry = parseLength(node.get('ry', '0')) shape = Rect(x, y, width, height, rx=rx, ry=ry) self.addShape(parent, node, shape) elif node.tag == self.SVG_CIRCLE: cx = parseLength(node.get('cx', '0')) cy = parseLength(node.get('cy', '0')) r = parseLength(node.get('r')) if r > 0.: shape = Circle(cx, cy, r) self.addShape(parent, node, shape) elif node.tag == self.SVG_ELLIPSE: cx = parseLength(node.get('cx', '0')) cy = parseLength(node.get('cy', '0')) rx = parseLength(node.get('rx')) ry = parseLength(node.get('ry')) if rx > 0. and ry > 0.: shape = Ellipse(cx, cy, rx, ry) self.addShape(parent, node, shape) elif node.tag == self.SVG_POLYLINE: # convert points points = node.get('points').strip() if len(points) == 0: return points = map(parseLength, re.split('[ ,]+', points)) # Need to use two shapes, because standard RLG polylines # do not support filling... group = Group() shape = Polygon(points) self.applyStyleToShape(shape, node) shape.strokeColor = None group.add(shape) shape = PolyLine(points) self.applyStyleToShape(shape, node) group.add(shape) self.addShape(parent, node, group) elif node.tag == self.SVG_POLYGON: # convert points points = node.get('points').strip() if len(points) == 0: return points = map(parseLength, re.split('[ ,]+', points)) shape = Polygon(points) self.addShape(parent, node, shape) elif node.tag == self.SVG_IMAGE: x = parseLength(node.get('x', '0')) y = parseLength(node.get('y', '0')) width = parseLength(node.get('width', '0')) height = parseLength(node.get('height', '0')) # link id link_id = node.get(self.LINK) filename = os.path.join(os.path.dirname(self.filename), link_id) shape = Image(x, y, width, height, filename) self.addShape(parent, node, shape) elif node.tag == self.SVG_TEXT: # Todo: # - rotation not handled # - baseshift not handled # - embedded span node not handled # def parsePos(node, subnode, name, default = '0'): values = subnode.get(name) if values is None: if not node is None: values = node.get(name, default) else: values = default return map(parseLength, values.split()) def getPos(values, i, default = None): if i >= len(values): if default is None: return values[-1] else: return default else: return values[i] def handleText(node, subnode, text): # get position variables xs = parsePos(node, subnode, 'x') dxs = parsePos(node, subnode, 'dx') ys = parsePos(node, subnode,'y') dys = parsePos(node, subnode,'dy') if sum(map(len, (xs,ys,dxs,dys))) == 4: # single value shape = String(xs[0] + dxs[0], -ys[0] - dys[0], text) self.applyStyleToShape(shape, subnode) group.add(shape) else: # multiple values for i,c in enumerate(text): x = getPos(xs, i) dx = getPos(dxs, i, 0) y = getPos(ys, i) dy = getPos(dys, i, 0) shape = String(x + dx, -y -dy, c) self.applyStyleToShape(shape, subnode) group.add(shape) if node.text and node.text.strip(): group = Group() handleText(None, node, node.text.strip()) group.scale(1, -1) self.addShape(parent, node, group) if len(node) > 0: group = Group() self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() nodestylestyle = self.nodeStyle(node, style) self.styles[self.level] = nodestylestyle for subnode in node: if subnode.tag == self.SVG_TSPAN: handleText(node, subnode, subnode.text.strip()) self.level -= 1 group.scale(1, -1) self.addShape(parent, node, group) elif node.tag == self.SVG_PATH: def convertQuadratic(Q0, Q1, Q2): C1 = (Q0[0] + 2./3*(Q1[0] - Q0[0]), Q0[1] + 2./3*(Q1[1] - Q0[1])) C2 = (C1[0] + 1./3*(Q2[0] - Q0[0]), C1[1] + 1./3*(Q2[1] - Q0[1])) C3 = Q2 return C1[0], C1[1], C2[0], C2[1], C3[0], C3[1] def prevCtrl(lastOp, lastArgs, currentX, currentY): # fetch last controll point if lastOp in 'CScsQqTt': x, y = lastArgs[-2] # mirror about current point return currentX + (currentX-x), currentY + (currentY-y) else: # defaults to current point return currentX, currentY # store sub paths in 'paths' list shape = Path() # keep track of current point and path start point startX, startY = 0.,0. currentX, currentY = 0.,0. # keep track of last operation lastOp = None lastArgs = None # avoid empty path data data = node.get('d') if data is None or len(data) == 0: return for op, args in parsePath.iterparse(data): if op == 'z' or op == 'Z': # close path or subpath shape.closePath() # next sub path starts at begining of current path currentX, currentY = startX, startY elif op == 'M': # moveto absolute if not lastOp is None and lastOp not in ('z', 'Z'): # close sub path shape.closePath() x, y = args[0] shape.moveTo(x, y) startX, startY = x, y # multiple moveto arge result in line for x, y in args[1:]: shape.lineTo(x, y) currentX, currentY = x, y elif op == 'm': if not lastOp is None and lastOp not in ('z', 'Z'): # close sub path shape.closePath() # moveto relative rx, ry = args[0] x, y = currentX + rx, currentY + ry shape.moveTo(x, y) startX, startY = x, y currentX, currentY = x, y # multiple moveto arge result in line for rx, ry in args[1:]: x, y = currentX + rx, currentY + ry shape.lineTo(x, y) currentX, currentY = x, y elif op == 'L': # lineto absolute for x,y in args: shape.lineTo(x, y) currentX, currentY = x, y elif op == 'l': # lineto relative for rx,ry in args: x, y = currentX + rx, currentY + ry shape.lineTo(x, y) currentX, currentY = x, y elif op == 'V': # vertical line absolute for y in args: shape.lineTo(currentX, y) currentY = y elif op == 'v': # vertical line relative for ry in args: y = currentY + ry shape.lineTo(currentX, y) currentY = y elif op == 'H': # horisontal line absolute for x in args: shape.lineTo(x, currentY) currentX = x elif op == 'h': # horisontal line relative for rx in args: x = currentX + rx shape.lineTo(x, currentY) currentX = x elif op == 'C': # cubic bezier absolute for i in range(len(args)//3): x1, y1 = args[i*3 + 0] x2, y2 = args[i*3 + 1] x3, y3 = args[i*3 + 2] shape.curveTo(x1, y1, x2, y2, x3, y3) currentX, currentY = x3, y3 elif op == 'c': # cubic bezier relative for i in range(len(args)//3): x1, y1 = args[i*3 + 0] x1, y1 = x1 + currentX, y1 + currentY x2, y2 = args[i*3 + 1] x2, y2 = x2 + currentX, y2 + currentY x3, y3 = args[i*3 + 2] x3, y3 = x3 + currentX, y3 + currentY shape.curveTo(x1, y1, x2, y2, x3, y3) currentX, currentY = x3, y3 lastOp = op lastArgs = (x1, y1), (x2, y2), (x3, y3) continue elif op == 'S': # shorthand cubic bezier absolute for i in range(len(args)//2): x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY) x2, y2 = args[i*2 + 0] x3, y3 = args[i*2 + 1] shape.curveTo(x1, y1, x2, y2, x3, y3) lastOp = op lastArgs = (x2, y2), (x3, y3) currentX, currentY = x3, y3 continue elif op == 's': # shorthand cubic bezier relative for i in range(len(args)//2): x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY) x2, y2 = args[i*2 + 0] x2, y2 = x2 + currentX, y2 + currentY x3, y3 = args[i*2 + 1] x3, y3 = x3 + currentX, y3 + currentY shape.curveTo(x1, y1, x2, y2, x3, y3) currentX, currentY = x3, y3 lastOp = op lastArgs = (x1, y1), (x2, y2), (x3, y3) continue elif op == 'Q': # quadratic bezier absolute for i in range(len(args)//2): x1, y1 = currentX, currentY x2, y2 = args[i*2 + 0] x3, y3 = args[i*2 + 1] ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'q': # quadratic bezier relative for i in range(len(args)//2): x1, y1 = currentX, currentY x2, y2 = args[i*2 + 0] x2, y2 = x2 + currentX, y2 + currentY x3, y3 = args[i*2 + 1] x3, y3 = x3 + currentX, y3 + currentY ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'T': # shorthand quadratic bezier absolute for i in range(len(args)): x1, y1 = currentX, currentY x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY) x3, y3 = args[i] ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 't': # shorthand quadratic bezier relative for i in range(len(args)): x1, y1 = currentX, currentY x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY) x3, y3 = args[i] x3, y3 = x3 + currentX, y3 + currentY ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'A' or op == 'a': # elliptic arc missing continue lastOp = op lastArgs = args # check if fill applies to path fill = None if node.get('fill'): # inline style fill = node.get('fill') else: # try local style if node.get('style'): style = parseStyle.parse(node.get('style')) if 'fill' in style: fill = style['fill'] # try global style if fill is None: style = self.styles[self.level] if 'fill' in style: fill = style['fill'] else: fill = 'none' # hack because RLG has no "semi-closed" paths... if lastOp == 'z' or lastOp == 'Z' or fill == 'none': self.addShape(parent, node, shape) else: group = Group() strokeshape = shape.copy() self.addShape(group, node, strokeshape, fill = 'none') shape.closePath() self.addShape(group, node, shape, stroke = 'none') self.addShape(parent, node, group) def addShape(self, parent, node, shape, **kwargs): self.applyStyleToShape(shape, node, **kwargs) transform = node.get('transform') if transform: # transform can only be applied to a group if isinstance(node, Group): group = node else: group = Group() for op in parseTransform.iterparse(transform): self.applyTransformOnGroup(group, op) if not isinstance(node, Group): group.add(shape) parent.add(group) else: parent.add(shape) return shape def nodeStyle(self, node, style = None): if style is None: style = {} # update with local style if node.get('style'): localstyle = parseStyle.parse(node.get('style')) for name, value in six.iteritems(localstyle): style[name] = value # update with inline style for name in STYLE_NAMES: value = node.get(name) if value: style[name] = value return style def applyStyleToShape(self, shape, node, **kwargs): # fetch global styles for this level globalstyle = self.styles[self.level] # update with node style localstyle = self.nodeStyle(node) for name in STYLES: # avoid text styles if not a String subclass if not isinstance(shape, String) and name in STYLES_FONT: continue value = None if name in kwargs: value = kwargs[name] elif name in localstyle: value = localstyle[name] elif name in globalstyle: value = globalstyle[name] else: continue # handle 'currentColor' if value == 'currentColor': if 'color' in localstyle: value = localstyle['color'] elif 'color' in globalstyle: value = globalstyle['color'] # fetch shape attribute name and converter attrname, converter = STYLES[name] # Sett attribute of shape if hasattr(shape, attrname): setattr(shape, attrname, converter(value)) # defaults if isinstance(shape, String): if shape.fillColor is None: shape.fillColor = colors.black elif isinstance(shape, (Line, PolyLine)): if shape.strokeColor is None: shape.strokeColor = colors.black elif isinstance(shape, (Rect, Circle, Ellipse, Polygon)): if shape.fillColor is None and shape.strokeColor is None: shape.strokeColor = colors.black def applyTransformOnGroup(self, group, transform): op, args = transform if op == 'scale': group.scale(*args) elif op == 'translate': group.translate(*args) elif op == 'rotate': if len(args) == 2: angle, center = args if center: cx, cy = center group.translate(cx, cy) group.rotate(angle) group.translate(-cx, -cy) else: group.rotate(angle) elif op == "skewX": angle = args[0] group.skew(angle, 0) elif op == "skewY": angle = args[0] group.skew(0, angle) elif op == "matrix": group.transform = mmult(group.transform, args)
def render(self, node, parent=None): if parent is None: parent = self.mainGroup # ignore if display = none display = node.get('display') if display == "none": return if node.tag == self.SVG_ROOT: self.level += 1 if not self.drawing is None: raise SVGError('drawing already created!') self.root = node # default styles style = { 'color':'none', 'fill':'none', 'stroke':'none', 'font-family':'Helvetica', 'font-size':'12' } self.styles[self.level] = style # iterate children for child in node: self.render(child, self.mainGroup) # create drawing width = node.get('width', '100%') height = node.get('height', '100%') if node.get("viewBox"): try: minx, miny, width, height = node.get("viewBox").split() except ValueError: raise SVGError("viewBox values not valid") if width.endswith('%') and height.endswith('%'): # handle relative size wscale = parseLength(width) / 100. hscale = parseLength(height) / 100. xL,yL,xH,yH = self.mainGroup.getBounds() self.drawing = Drawing(xH*wscale + xL, yH*hscale + yL) else: self.drawing = Drawing(parseLength(width), parseLength(height)) height = self.drawing.height self.mainGroup.scale(1, -1) self.mainGroup.translate(0, -height) self.drawing.add(self.mainGroup) self.level -= 1 return self.drawing elif node.tag in (self.SVG_G, self.SVG_A): self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() style = self.nodeStyle(node, style) self.styles[self.level] = style group = Group() # iterate children for child in node: self.render(child, group) parent.add(group) transforms = node.get('transform') if transforms: for op in parseTransform.iterparse(transforms): self.applyTransformOnGroup(group, op) self.level -= 1 elif node.tag == self.SVG_USE: self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() style = self.nodeStyle(node, style) self.styles[self.level] = style group = Group() # link id link_id = node.get(self.LINK).lstrip('#') # find linked node in defs or symbol section target = None for defs in self.root.getiterator(self.SVG_DEFS): for element in defs: if element.get('id') == link_id: target = element break if target is None: for defs in self.root.getiterator(self.SVG_SYMBOL): for element in defs: if element.get('id') == link_id: target = element break if target is None: msg = "Could not find use node '%s'" % link_id raise SVGError(msg) self.render(target, group) parent.add(group) # apply transform transforms = node.get('transform') if transforms: for op in parseTransform.iterparse(transforms): self.applyTransformOnGroup(group, op) # apply 'x' and 'y' attribute as translation of defs object if node.get('x') or node.get('y'): dx = parseLength(node.get('x','0')) dy = parseLength(node.get('y','0')) self.applyTransformOnGroup(group, ('translate', (dx,dy))) self.level -= 1 elif node.tag == self.SVG_LINE: # get coordinates x1 = parseLength(node.get('x1', '0')) y1 = parseLength(node.get('y1', '0')) x2 = parseLength(node.get('x2', '0')) y2 = parseLength(node.get('y2', '0')) shape = Line(x1, y1, x2, y2) self.addShape(parent, node, shape) elif node.tag == self.SVG_RECT: # get coordinates x = parseLength(node.get('x', '0')) y = parseLength(node.get('y', '0')) width = parseLength(node.get('width')) height = parseLength(node.get('height')) rx = parseLength(node.get('rx', '0')) ry = parseLength(node.get('ry', '0')) shape = Rect(x, y, width, height, rx=rx, ry=ry) self.addShape(parent, node, shape) elif node.tag == self.SVG_CIRCLE: cx = parseLength(node.get('cx', '0')) cy = parseLength(node.get('cy', '0')) r = parseLength(node.get('r')) if r > 0.: shape = Circle(cx, cy, r) self.addShape(parent, node, shape) elif node.tag == self.SVG_ELLIPSE: cx = parseLength(node.get('cx', '0')) cy = parseLength(node.get('cy', '0')) rx = parseLength(node.get('rx')) ry = parseLength(node.get('ry')) if rx > 0. and ry > 0.: shape = Ellipse(cx, cy, rx, ry) self.addShape(parent, node, shape) elif node.tag == self.SVG_POLYLINE: # convert points points = node.get('points').strip() if len(points) == 0: return points = list(map(parseLength, re.split('[ ,]+', points))) # Need to use two shapes, because standard RLG polylines # do not support filling... group = Group() shape = Polygon(points) self.applyStyleToShape(shape, node) shape.strokeColor = None group.add(shape) shape = PolyLine(points) self.applyStyleToShape(shape, node) group.add(shape) self.addShape(parent, node, group) elif node.tag == self.SVG_POLYGON: # convert points points = node.get('points').strip() if len(points) == 0: return points = list(map(parseLength, re.split('[ ,]+', points))) shape = Polygon(points) self.addShape(parent, node, shape) elif node.tag == self.SVG_IMAGE: x = parseLength(node.get('x', '0')) y = parseLength(node.get('y', '0')) width = parseLength(node.get('width', '0')) height = parseLength(node.get('height', '0')) # link id link_id = node.get(self.LINK) filename = os.path.join(os.path.dirname(self.filename), link_id) shape = Image(x, y, width, height, filename) self.addShape(parent, node, shape) elif node.tag == self.SVG_TEXT: # Todo: # - rotation not handled # - baseshift not handled # - embedded span node not handled # def parsePos(node, subnode, name, default='0'): values = subnode.get(name) if values is None: if node is not None: values = node.get(name, default) else: values = default return list(map(parseLength, values.split())) def getPos(values, i, default=None): if i >= len(values): if default is None: return values[-1] else: return default else: return values[i] def handleText(node, subnode, text): # get position variables xs = parsePos(node, subnode, 'x') dxs = parsePos(node, subnode, 'dx') ys = parsePos(node, subnode,'y') dys = parsePos(node, subnode,'dy') if sum(map(len, (xs,ys,dxs,dys))) == 4: # single value shape = String(xs[0] + dxs[0], -ys[0] - dys[0], text) self.applyStyleToShape(shape, subnode) group.add(shape) else: # multiple values for i, c in enumerate(text): x = getPos(xs, i) dx = getPos(dxs, i, 0) y = getPos(ys, i) dy = getPos(dys, i, 0) shape = String(x + dx, -y -dy, c) self.applyStyleToShape(shape, subnode) group.add(shape) if node.text and node.text.strip(): group = Group() handleText(None, node, node.text.strip()) group.scale(1, -1) self.addShape(parent, node, group) if len(node) > 0: group = Group() self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() nodestylestyle = self.nodeStyle(node, style) self.styles[self.level] = nodestylestyle for subnode in node: if subnode.tag == self.SVG_TSPAN: handleText(node, subnode, subnode.text.strip()) self.level -= 1 group.scale(1, -1) self.addShape(parent, node, group) elif node.tag == self.SVG_PATH: def convertQuadratic(Q0, Q1, Q2): C1 = (Q0[0] + 2./3*(Q1[0] - Q0[0]), Q0[1] + 2./3*(Q1[1] - Q0[1])) C2 = (C1[0] + 1./3*(Q2[0] - Q0[0]), C1[1] + 1./3*(Q2[1] - Q0[1])) C3 = Q2 return C1[0], C1[1], C2[0], C2[1], C3[0], C3[1] def prevCtrl(lastOp, lastArgs, currentX, currentY): # fetch last controll point if lastOp in 'CScsQqTt': x, y = lastArgs[-2] # mirror about current point return currentX + (currentX-x), currentY + (currentY-y) else: # defaults to current point return currentX, currentY # store sub paths in 'paths' list shape = Path() # keep track of current point and path start point startX, startY = 0., 0. currentX, currentY = 0., 0. # keep track of last operation lastOp = None lastArgs = None # avoid empty path data data = node.get('d') if data is None or len(data) == 0: return for op, args in parsePath.iterparse(data): if op == 'z' or op == 'Z': # close path or subpath shape.closePath() # next sub path starts at begining of current path currentX, currentY = startX, startY elif op == 'M': # moveto absolute if lastOp is not None and lastOp not in ('z', 'Z'): # close sub path shape.closePath() x, y = args[0] shape.moveTo(x, y) startX, startY = x, y # multiple moveto arge result in line for x, y in args[1:]: shape.lineTo(x, y) currentX, currentY = x, y elif op == 'm': if lastOp is not None and lastOp not in ('z', 'Z'): # close sub path shape.closePath() # moveto relative rx, ry = args[0] x, y = currentX + rx, currentY + ry shape.moveTo(x, y) startX, startY = x, y currentX, currentY = x, y # multiple moveto arge result in line for rx, ry in args[1:]: x, y = currentX + rx, currentY + ry shape.lineTo(x, y) currentX, currentY = x, y elif op == 'L': # lineto absolute for x, y in args: shape.lineTo(x, y) currentX, currentY = x, y elif op == 'l': # lineto relative for rx, ry in args: x, y = currentX + rx, currentY + ry shape.lineTo(x, y) currentX, currentY = x, y elif op == 'V': # vertical line absolute for y in args: shape.lineTo(currentX, y) currentY = y elif op == 'v': # vertical line relative for ry in args: y = currentY + ry shape.lineTo(currentX, y) currentY = y elif op == 'H': # horisontal line absolute for x in args: shape.lineTo(x, currentY) currentX = x elif op == 'h': # horisontal line relative for rx in args: x = currentX + rx shape.lineTo(x, currentY) currentX = x elif op == 'C': # cubic bezier absolute for p1, p2, p3 in zip(*([iter(args)] * 3)): shape.curveTo(*(p1 + p2 + p3)) currentX, currentY = p3 elif op == 'c': # cubic bezier relative for pnts in zip(*([iter(args)] * 3)): (x1, y1), (x2, y2), (x3, y3) = pnts pnts = tuple((p[0] + currentX, p[1] + currentY) for p in pnts) shape.curveTo(*(pnts[0] + pnts[1] + pnts[2])) currentX, currentY = pnts[2] lastOp = op lastArgs = pnts continue elif op == 'S': # shorthand cubic bezier absolute for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY) x2, y2 = p2 x3, y3 = p3 shape.curveTo(x1, y1, x2, y2, x3, y3) lastOp = op lastArgs = (x2, y2), (x3, y3) currentX, currentY = x3, y3 continue elif op == 's': # shorthand cubic bezier relative for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY) x2, y2 = p2 x2, y2 = x2 + currentX, y2 + currentY x3, y3 = p3 x3, y3 = x3 + currentX, y3 + currentY shape.curveTo(x1, y1, x2, y2, x3, y3) currentX, currentY = x3, y3 lastOp = op lastArgs = (x1, y1), (x2, y2), (x3, y3) continue elif op == 'Q': # quadratic bezier absolute for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = currentX, currentY x2, y2 = p2 x3, y3 = p3 ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'q': # quadratic bezier relative for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = currentX, currentY x2, y2 = p2 x2, y2 = x2 + currentX, y2 + currentY x3, y3 = p3 x3, y3 = x3 + currentX, y3 + currentY ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'T': # shorthand quadratic bezier absolute for i in range(len(args)): x1, y1 = currentX, currentY x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY) x3, y3 = args[i] ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 't': # shorthand quadratic bezier relative for i in range(len(args)): x1, y1 = currentX, currentY x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY) x3, y3 = args[i] x3, y3 = x3 + currentX, y3 + currentY ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'A' or op == 'a': # elliptic arc missing continue lastOp = op lastArgs = args # check if fill applies to path fill = None if node.get('fill'): # inline style fill = node.get('fill') else: # try local style if node.get('style'): style = parseStyle.parse(node.get('style')) if 'fill' in style: fill = style['fill'] # try global style if fill is None: style = self.styles[self.level] if 'fill' in style: fill = style['fill'] else: fill = 'none' # hack because RLG has no "semi-closed" paths... if lastOp == 'z' or lastOp == 'Z' or fill == 'none': self.addShape(parent, node, shape) else: group = Group() strokeshape = shape.copy() self.addShape(group, node, strokeshape, fill='none') shape.closePath() self.addShape(group, node, shape, stroke='none') self.addShape(parent, node, group) elif node.tag == self.SVG_TITLE or node.tag == self.SVG_DESC: # Skip non-graphics elements return
def convert_text(self, node): """ Converts a <text> element """ x, y = self._length_attrs(node, 'x', 'y') preserve_space = utils.node_preserve_space(node, self.preserve_space) gr = Group() frag_lengths = [] dx0, dy0 = 0, 0 x1, y1 = 0, 0 ff = attributes.convert_font_family( attributes.find( node, "font-family")) # default is set inside convert_... fs = attributes.convert_length( attributes.find(node, "font-size") or "12") convert_len = partial(attributes.convert_length, em_base=fs) for c in itertools.chain([node], node.getchildren()): has_x, has_y = False, False dx, dy = 0, 0 baseline_shift = 0 if node_name(c) == 'text': text = self.clean_text(c.text, preserve_space) if not text: continue elif node_name(c) == 'tspan': text = self.clean_text(c.text, preserve_space) if not text: continue x1, y1, dx, dy = [ c.attrib.get(name, '') for name in ("x", "y", "dx", "dy") ] has_x, has_y = (x1 != '', y1 != '') x1, y1, dx, dy = map(convert_len, (x1, y1, dx, dy)) dx0 = dx0 + dx dy0 = dy0 + dy baseline_shift = c.attrib.get("baseline-shift", '0') if baseline_shift in ("sub", "super", "baseline"): baseline_shift = { "sub": -fs / 2, "super": fs / 2, "baseline": 0 }[baseline_shift] else: baseline_shift = convert_len(baseline_shift, fs) else: continue frag_lengths.append(stringWidth(text, ff, fs)) new_x = (x1 + dx) if has_x else (x + dx0 + sum(frag_lengths[:-1])) new_y = (y1 + dy) if has_y else (y + dy0) shape = String(new_x, -(new_y - baseline_shift), text) self.apply_style(to_shape=shape, from_node=node) if node_name(c) == 'tspan': self.apply_style(to_shape=shape, from_node=c) gr.add(shape) gr.scale(1, -1) return gr
from reportlab.lib import colors path = sys.argv[1] glyphName = sys.argv[2] if (len(sys.argv) > 3): imageFile = sys.argv[3] else: imageFile = "%s.png" % glyphName font = TTFont( path ) # it would work just as well with fontemon_blender_addon.fontTools.t1Lib.T1Font gs = font.getGlyphSet() pen = ReportLabPen(gs, Path(fillColor=colors.red, strokeWidth=5)) g = gs[glyphName] g.draw(pen) w, h = g.width, 1000 from reportlab.graphics import renderPM from reportlab.graphics.shapes import Group, Drawing, scale # Everything is wrapped in a group to allow transformations. g = Group(pen.path) g.translate(0, 200) g.scale(0.3, 0.3) d = Drawing(w, h) d.add(g) renderPM.drawToFile(d, imageFile, fmt="PNG")
from fontTools.ttLib import TTFont from reportlab.lib import colors path = sys.argv[1] glyphName = sys.argv[2] if (len(sys.argv) > 3): imageFile = sys.argv[3] else: imageFile = "%s.png" % glyphName font = TTFont(path) # it would work just as well with fontTools.t1Lib.T1Font gs = font.getGlyphSet() pen = ReportLabPen(gs, Path(fillColor=colors.red, strokeWidth=5)) g = gs[glyphName] g.draw(pen) w, h = g.width, 1000 from reportlab.graphics import renderPM from reportlab.graphics.shapes import Group, Drawing, scale # Everything is wrapped in a group to allow transformations. g = Group(pen.path) g.translate(0, 200) g.scale(0.3, 0.3) d = Drawing(w, h) d.add(g) renderPM.drawToFile(d, imageFile, fmt="PNG")