class Path(object): ''' id : string, copied from the svg tag's id attribute, or an autogenerated int if there is no id attr. loops : a list of loops. A loop is a list of vertices. A vertex is a pair of floats or ints. color : triple of unsigned bytes, (r, g, b) A Path corresponds to a single SVG path tag. It may contain many independant loops which represent either disjoint polygons or holes. ''' next_id = 1 def __init__(self, tag=None): self.id = None self.loops = [] self.color = (0, 0, 0) self.bounds = Bounds() self.triangles = None if tag: self.parse(tag) def get_id(self, attributes): if 'id' in attributes.keys(): return attributes['id'].value else: self.next_id += 1 return self.next_id - 1 def parse_color(self, color): ''' color : string, eg: '#rrggbb' or 'none' (where rr, gg, bb are hex digits from 00 to ff) returns a triple of unsigned bytes, eg: (0, 128, 255) ''' if color == 'none': return None return ( int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16)) def parse_style(self, style): ''' style : string, eg: fill:#ff2a2a;fill-rule:evenodd;stroke:none;stroke-width:1px; stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1 returns color as a triple of unsigned bytes: (r, g, b), or None ''' style_elements = style.split(';') while style_elements: element = style_elements.pop() if element.startswith('fill:'): return self.parse_color(element[5:]) return None def parse(self, tag): self.id = self.get_id(tag.attributes) if 'style' in tag.attributes.keys(): style_data = tag.attributes['style'].value self.color = self.parse_style(style_data) path_data = tag.attributes['d'].value parser = PathDataParser() path_tuple = parser.to_tuples(path_data) tracer = SvgLoopTracer() self.loops = tracer.to_loops(path_tuple) self.bounds.add_bounds(tracer.bounds) def offset(self, x, y): for loop in self.loops: for idx, vert in enumerate(loop): loop[idx] = (vert[0] + x, vert[1] + y) self.bounds.offset(x, y) def tessellate(self): if self.color: self.triangles = tesselate(self.loops) def _serialise_verts(self): for vert in self.triangles: yield vert[0] yield vert[1] def add_to_batch(self, batch): ''' Adds itself to the given batch, as as single primitive of indexed GL_TRIANGLES. Note that Batch will aggregate all such additions into a single large primitive. ''' if self.triangles: num_verts = len(self.triangles) serial_verts = list(self._serialise_verts()) colors = self.color * num_verts indices = range(num_verts) batch.add_indexed( num_verts, GL_TRIANGLES, None, indices, ('v2f/static', serial_verts), ('c3B/static', colors), )
class SvgBatch(object): ''' Maintains an ordered list of paths, each one corresponding to a path tag from an SVG file. Creates a pylget Batch containing all these paths, for rendering as a single OpenGL GL_TRIANGLES indexed vert primitive. ''' def __init__(self, filename=None): ''' filename: string, absolute or relative filename of an SVG file ''' self.filename = filename self.paths = [] self.path_by_id = {} self.bounds = Bounds() self.batch = None @property def width(self): return self.bounds.width @property def height(self): return self.bounds.height def __getattr__(self, name): if hasattr(self.path_by_id, name): return getattr(self.path_by_id, name) raise AttributeError(name) def __getitem__(self, index): return self.path_by_id[index] def parse_svg(self): ''' Populates self.paths from the <path> tags in the svg file. ''' doc = xml.dom.minidom.parse(self.filename) path_tags = doc.getElementsByTagName('path') for path_tag in path_tags: path = Path(path_tag) self.paths.append(path) self.path_by_id[path.id] = path self.bounds.add_bounds(path.bounds) def center(self): ''' Offset all verts put center of svg at the origin ''' center = self.bounds.get_center() for path in self.paths: path.offset(-center[0], -center[1]) self.bounds.offset(-center[0], -center[1]) def create_batch(self): ''' Returns a new pyglet Batch object populated with indexed GL_TRIANGLES ''' if self.batch is None: self.batch = Batch() self.parse_svg() self.center() for path in self.paths: path.tessellate() path.add_to_batch(self.batch) return self.batch