def bounding_box(self): ''' returns a tuple describing the bounding box: (min_x, min_y, max_x, max_y) ''' if not self._bounding_box: self._bounding_box = BoundingBox() if self.triangles: self._bounding_box.expand(self.triangles) if self.outlines: for o in self.outlines: self._bounding_box.expand(o) return self._bounding_box.extents()
class SVGPath(SVGRenderableElement): """ Represents a single SVG path. This is usually a distinct shape with a fill pattern, an outline, or both. """ def __init__(self, svg, element, parent): SVGRenderableElement.__init__(self, svg, element, parent) #: The original SVG file self.svg = svg if not self.is_pattern_part: self.config = svg.config else: self.config = svg.config.super_detailed() #: The actual path elements, as a list of vertices self.outlines = None #: The triangles that comprise the inner fill self.triangles = None #: The base shape. Possible values: path, rect, circle, ellipse, line, polygon, polyline self.shape = None #: The bounding box self._bounding_box = None self.marker_start = element.get('marker-start', None) self.marker_mid = element.get('marker-mid', None) self.marker_end = element.get('marker-end', None) if self.marker_start: self.marker_start = self.marker_start[5:-1] if self.marker_mid: self.marker_mid = self.marker_mid[5:-1] if self.marker_end: self.marker_end = self.marker_end[5:-1] path_builder = SVGPathBuilder() path_builder.read_xml_svg_element( self, element, self.config) self.outlines = path_builder.path self.triangles = path_builder.polygon self.display_list = None def _render_stroke(self): stroke = self.style.stroke stroke_width = self.style.stroke_width is_miter = self.style.stroke_linejoin == 'miter' miter_limit = self.style.stroke_miterlimit if is_miter else 0 for loop in self.outlines: self.svg.n_lines += len(loop) - 1 loop_plus = [] for i in xrange(len(loop) - 1): loop_plus += [loop[i], loop[i+1]] if isinstance(stroke, str): g = self.svg._gradients[stroke] strokes = [g.sample(x, self) for x in loop_plus] else: strokes = [stroke for x in loop_plus] if len(loop_plus) == 0: continue if len(self.style.stroke_dasharray): ls = lines.split_line_by_pattern(loop_plus, self.style.stroke_dasharray) if ls[0][0] == ls[-1][-1]: #if the last line end point equals the first line start point, #this is a "closed" line, so combine the first and the last line combined_line = ls[-1] + ls[0] ls[0] = combined_line del ls[-1] for l in ls: lines.draw_polyline( l, stroke_width, color=strokes[0], line_cap=self.style.stroke_linecap, join_type=self.style.stroke_linejoin, miter_limit=miter_limit) if self.marker_start: end_point = vec2(loop_plus[0]) almost_end_point = vec2(loop_plus[1]) marker = self.svg.defs[self.marker_start] self._render_marker(end_point, almost_end_point, marker, True) if self.marker_end: end_point = vec2(loop_plus[-1]) almost_end_point = vec2(loop_plus[-2]) marker = self.svg.defs[self.marker_end] self._render_marker(end_point, almost_end_point, marker) else: lines.draw_polyline( loop_plus, stroke_width, color=strokes[0], line_cap=self.style.stroke_linecap, join_type=self.style.stroke_linejoin, miter_limit=miter_limit) if self.marker_start: end_point = vec2(loop_plus[0]) almost_end_point = vec2(loop_plus[1]) marker = self.svg.defs[self.marker_start] self._render_marker(end_point, almost_end_point, marker, True) if self.marker_end: end_point = vec2(loop_plus[-1]) almost_end_point = vec2(loop_plus[-2]) marker = self.svg.defs[self.marker_end] self._render_marker(end_point, almost_end_point, marker) def _render_marker(self, a, b, marker, reverse=False): if marker.orient == 'auto': angle = (a - b).angle() else: angle = marker.orient if reverse: angle += math.pi sx = (marker.marker_width / marker.vb_w) * self.style.stroke_width sy = (marker.marker_height / marker.vb_h) * self.style.stroke_width rx = marker.ref_x ry = marker.ref_y with Matrix.transform(a.x, a.y, theta=angle): with Matrix.scale(sx, sy): with Matrix.translation(-rx, -ry): marker.render() def _render_gradient_fill(self): fill = self.style.fill tris = self.triangles self.svg.n_tris += len(tris) / 3 g = None if isinstance(fill, str): g = self.svg._gradients[fill] fills = [g.sample(x, self) for x in tris] else: fills = [fill] * len(tris) # for x in tris] if g: g.apply_shader(self, self.transform, self.style.opacity * self.style.fill_opacity) graphics.draw_colored_triangles( flatten_list(tris), flatten_list(fills) ) if g: g.unapply_shader() def bounding_box(self): ''' returns a tuple describing the bounding box: (min_x, min_y, max_x, max_y) ''' if not self._bounding_box: self._bounding_box = BoundingBox() if self.triangles: self._bounding_box.expand(self.triangles) if self.outlines: for o in self.outlines: self._bounding_box.expand(o) return self._bounding_box.extents() def _render_pattern_fill(self): fill = self.style.fill tris = self.triangles pattern = None if fill in self.svg.patterns: pattern = self.svg.patterns[fill] pattern.bind_texture() min_x, min_y, max_x, max_y = self.bounding_box() tex_coords = [] for vtx in tris: tex_coords.append((vtx[0]-min_x)/(max_x-min_x)/pattern.width) tex_coords.append((vtx[1]-min_y)/(max_y-min_y)/pattern.width) graphics.draw_textured_triangles( flatten_list(tris), tex_coords ) if pattern: pattern.unbind_texture() def on_render(self): """Render immediately to screen (no display list). Slow! Consider using SVG.draw(...) instead.""" gl.glClear(gl.GL_DEPTH_BUFFER_BIT) gl.glEnable(gl.GL_DEPTH_TEST) if self.style.stroke and self.outlines: self._render_stroke() gl.glPushMatrix() gl.glTranslatef(0, 0, -0.1) if self.triangles: try: if isinstance(self.style.fill, str) and self.style.fill in self.svg.patterns: self._render_pattern_fill() else: self._render_gradient_fill() except Exception as exception: traceback.print_exc(exception) gl.glPopMatrix() gl.glDisable(gl.GL_DEPTH_TEST) def __repr__(self): return "<SVGPath id=%s title='%s' description='%s' transform=%s>" % ( self.id, self.title, self.description, self.transform )