root/trunk/RBRapier/RBRapier/Formats/SVG/RapierRenderItems.py

Revision 734, 30.3 kB (checked in by sholloway, 4 years ago)

Added support for a precompile step

Line 
1 #!/usr/bin/env python
2 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 ##~ License
4 ##~
5 ##- The RuneBlade Foundation library is intended to ease some
6 ##- aspects of writing intricate Jabber, XML, and User Interface (wxPython, etc.)
7 ##- applications, while providing the flexibility to modularly change the
8 ##- architecture. Enjoy.
9 ##~
10 ##~ Copyright (C) 2002  TechGame Networks, LLC.
11 ##~
12 ##~ This library is free software; you can redistribute it and/or
13 ##~ modify it under the terms of the BSD style License as found in the
14 ##~ LICENSE file included with this distribution.
15 ##~
16 ##~ TechGame Networks, LLC can be reached at:
17 ##~ 3578 E. Hartsel Drive #211
18 ##~ Colorado Springs, Colorado, USA, 80920
19 ##~
20 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21
22 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23 #~ Imports
24 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25
26 from OpenGL import GL, GLU
27 import Numeric
28
29 from RBRapier.Tools import Transformations2d
30 from RBRapier.Tools.Geometry import Curves
31 from RBRapier.Tools.Geometry.gluPolygonTesselation import PolygonTesselator as _PolygonTesselator
32
33 from RapierGeometry import GLGeometryCollector, GLStroke, GLFill
34
35 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36 #~ Constants / Variables / Etc.
37 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
38
39 FillRuleLookup = {'evenodd': GLU.GLU_TESS_WINDING_ODD, 'nonzero': GLU.GLU_TESS_WINDING_NONZERO, None: GLU.GLU_TESS_WINDING_ODD}
40
41 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
42 #~ Definitions
43 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
44
45 class Style(object):
46     def __init__(self, *styles, **kwstyle):
47         self._style = kwstyle
48         map(self._style.update, styles)
49
50     def copy(self):
51         result = self.__class__()
52         result._style = self._style.copy()
53         return result
54
55     def __add__(self, other):
56         return Style(self._style, other._style)
57
58     def __getitem__(self, index):
59         return self._style[index]
60     def get(self, *args):
61         return self._style.get(*args)
62
63     def GetFillColor(self):
64         color = self._style.get('fill', None)
65         if color is None:
66             return None
67         elif color == 'currentColor':
68             color = self._style['color']
69         opacity = int(255*self._style.get('fill-opacity', 1.))
70         return color + (opacity,)
71
72     def GetStrokeColor(self):
73         color = self._style.get('stroke', None)
74         if color is None:
75             return None
76         elif color == 'currentColor':
77             color = self._style['color']
78         opacity = int(255*self._style.get('stroke-opacity', 1.))
79         return color + (opacity,)
80
81 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
82
83 class Transform(Transformations2d.Composite):
84     viewport = (None, None)
85     def GetViewport(self):
86         return self.viewport
87     def SetViewport(self, size):
88         self.viewport = size
89
90     def __mul__(self, other):
91         if isinstance(other, Transform):
92             return Transform(self.collection + other.collection)
93         else:
94             return Transformations2d.Composite.__mul__(self, other)
95
96     def __rmul__(self, other):
97         if isinstance(other, Transform):
98             return Transform(other.collection + self.collection)
99         else:
100             return Transformations2d.Composite.__rmul__(self, other)
101
102     def copy(self):
103         result = self.__class__()
104         result.collection = self.collection[:]
105         result.viewport = self.viewport[:]
106         return result
107
108     def translate(self, x=0.0, y=0.0):
109         self.Add(Transformations2d.Translate((x,y)))
110
111     def scale(self, x=1., y=None):
112         if y is None: y = x
113         self.Add(Transformations2d.Scale((x,y)))
114
115     def rotate(self, angle=0.0, x=0, y=0):
116         if x or y:
117             self.Add(Transformations2d.Translate((x,y)))
118             self.Add(Transformations2d.Rotate(angle))
119             self.Add(Transformations2d.Translate((-x,-y)))
120         else:
121             self.Add(Transformations2d.Rotate(angle, (x,y)))
122
123     def skewx(self, angle=0.0):
124         self.Add(Transformations2d.Skew(xy=angle))
125
126     def skewy(self, angle=0.0):
127         self.Add(Transformations2d.Skew(yx=angle))
128
129     def matrix_byrow(self, xx=1.0, xy=0.0, xh=0.0, yx=0.0, yy=1.0, yh=0.0, hx=0.0, hy=0.0, hh=1.0):
130         """9 component matrix in row format"""
131         self.Add(Transformations2d.Matrix([[xx, xy, xh], [yx, yy, yh], [hx, hy, hh]]))
132
133     def matrix(self, a=1.0, b=0.0, c=0.0, d=1.0, e=0.0, f=0.0):
134         """9 component matrix in svg arrangement -- [[a, c, e],[b, d, f],[0,0,1]]"""
135         self.Add(Transformations2d.Matrix([[a, c, e],[b, d, f],[0,0,1]]))
136
137     def Add(self, *args, **kw):
138         try:
139             del self._cached_matrix
140         except AttributeError: pass
141         return Transformations2d.Composite.Add(self, *args, **kw)
142
143     def asArray3x3(self):
144         try:
145             return self._cached_matrix
146         except AttributeError:
147             matrix = Transformations2d.Composite.asArray3x3(self)
148             self._cached_matrix = matrix
149             return matrix
150
151     def AddSVGTransforms(self, transforms):
152         def AddTransforms(name, *args):
153             transformfn = getattr(self, name.lower(), None)
154             if transformfn is not None:
155                 transformfn(*args)
156
157         transforms.RestoreToEx(onadd=AddTransforms)
158
159     def fromSVGTransforms(klass, transforms):
160         result = klass()
161         result.AddSVGTransforms(transforms)
162         return result
163     fromSVGTransforms = classmethod(fromSVGTransforms)
164
165 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
166
167 class SVGTesselator(_PolygonTesselator):
168     def _OnVertex(self, vertexdata):
169         self.vertexdata.append(vertexdata[:2])
170
171 class SVGPathConnector(object):
172     pos = (0,0)
173     controlpoint = None
174     beziersteps = 16
175     ellipsesteps = 32
176
177     command_table = {
178         'M': 'move',
179         'm': 'move_rel',
180         'Z': 'closepath',
181         'z': 'closepath',
182         'L': 'line',
183         'l': 'line_rel',
184         'H': 'hline',
185         'h': 'hline_rel',
186         'V': 'vline',
187         'v': 'vline_rel',
188         'C': 'cubicbezier',
189         'c': 'cubicbezier_rel',
190         'S': 'smoothcurve',
191         's': 'smoothcurve_rel',
192         'Q': 'quadraticbezier',
193         'q': 'quadraticbezier_rel',
194         'T': 'smoothquadraticbezier',
195         't': 'smoothquadraticbezier_rel',
196         'A': 'ellipticarc',
197         'a': 'ellipticarc_rel'}
198
199     def __init__(self):
200         self.pathlist = []
201
202     def AddSVGPath(self, path):
203         def AddPathElement(commandidx, *args):
204             name = self.command_table.get(commandidx, '')
205             transformfn = getattr(self, name, None)
206             if transformfn is not None:
207                 transformfn(*args)
208         path.RestoreToEx(onadd=AddPathElement)
209
210     def fromSVGPath(klass, path):
211         result = klass()
212         result.AddSVGPath(path)
213         return result
214     fromSVGPath = classmethod(fromSVGPath)
215
216     def _ReduceDuplications(self):
217         def _reduce(path):
218             for idx in range(len(path)-1, 0, -1):
219                 if path[idx] == path[idx-1]:
220                     del path[idx]
221             return path
222         self.pathlist = filter(None, map(_reduce, self.pathlist))
223
224     def GetContours(self):
225         self._ReduceDuplications()
226         return [map(tuple, contour) for contour in self.pathlist]
227
228     def move_rel(self, x, y):
229         dx, dy = self.pos
230         return self.move(x+dx, y+dy)
231     def move(self, x, y):
232         pathlist = self.pathlist
233         if not pathlist or pathlist[-1]:
234             pathlist.append([(x,y)])
235         else:
236             pathlist[-1][:] = [(x,y)]
237         self._savepos((x,y))
238
239     def closepath(self):
240         pathlist = self.pathlist
241         if len(pathlist[-1]) <= 1:
242             pathlist.pop()
243         else:
244             pos = pathlist[-1][0]
245             pathlist[-1].append(pos)
246             pathlist.append([])
247             self._savepos(pos)
248
249     def line_rel(self, x, y):
250         dx, dy = self.pos
251         return self.line(x+dx, y+dy)
252     def line(self, x, y):
253         self.pathlist[-1].append((x,y))
254         self._savepos((x,y))
255
256     def hline_rel(self, x):
257         dx = self.pos[0]
258         return self.hline(x+dx)
259     def hline(self, x):
260         y = self.pos[1]
261         self.pathlist[-1].append((x,y))
262         self._savepos((x,y))
263
264     def vline_rel(self, y):
265         dy = self.pos[1]
266         return self.vline(y+dy)
267     def vline(self, y):
268         x = self.pos[0]
269         self.pathlist[-1].append((x,y))
270         self._savepos((x,y))
271
272     def cubicbezier_rel(self, x1, y1, x2, y2, x, y):
273         dx, dy = self.pos
274         return self.cubicbezier(x1+dx, y1+dy, x2+dx, y2+dy, x+dx, y+dy)
275     def cubicbezier(self, x1, y1, x2, y2, x, y):
276         bezier = Curves.CubicBezier(self.pos, (x1, y1), (x2, y2), (x, y), steps=self.beziersteps)
277         self.pathlist[-1].extend(list(bezier))
278         self._savepos((x,y), (x2, y2))
279
280     def smoothcurve_rel(self, x2, y2, x, y):
281         dx, dy = self.pos
282         return self.smoothcurve(x2+dx, y2+dy, x+dx, y+dy)
283     def smoothcurve(self, x2, y2, x, y):
284         x1r, y1r = self._getControlPoint()
285         return self.cubicbezier(x1r, y1r, x2, y2, x, y)
286
287     def quadraticbezier_rel(self, x1, y1, x, y):
288         dx, dy = self.pos
289         return self.quadraticbezier(x1+dx, y1+dy, x+dx, y+dy)
290     def quadraticbezier(self, x1, y1, x, y):
291         bezier = Curves.QuadraticBezier(self.pos, (x1, y1), (x, y), steps=self.beziersteps)
292         self.pathlist[-1].extend(list(bezier))
293         self._savepos((x,y), (x1,y1))
294
295     def smoothquadraticbezier_rel(self, x, y):
296         dx, dy = self.pos
297         return self.smoothquadraticbezier(x+dx, y+dy)
298     def smoothquadraticbezier(self, x, y):
299         x1r, y1r = self._getControlPoint()
300         return self.quadraticbezier(x1r, y1r, x, y)
301
302     def ellipticarc_rel(self, rx, ry, xrotation, largeArcFlag, sweepFlag, x, y):
303         dx, dy = self.pos
304         return self.ellipticarc(rx, ry, xrotation, largeArcFlag, sweepFlag, x+dx, y+dy)
305     def ellipticarc(self, rx, ry, xrotation, largeArcFlag, sweepFlag, x, y):
306         startpt, endpt = self.pos, (x,y)
307         if startpt == endpt: return
308         ellipse = Curves.Ellipse.fromArc(startpt, endpt, abs(rx), abs(ry), xrotation % 360, bool(largeArcFlag), bool(sweepFlag), inDegrees=True, steps=self.ellipsesteps)
309         self.pathlist[-1].extend(list(ellipse))
310         self._savepos((x,y))
311
312     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
313
314     def _savepos(self, pos, controlpoint=None):
315         self.pos = pos
316         self.controlpoint = controlpoint
317
318     def _getControlPoint(self):
319         if self.controlpoint is None:
320             return self.pos
321         else:
322             x,y = self.pos
323             xcp, ycp = self.controlpoint
324             return 2*x-xcp, 2*y-ycp
325
326 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
327 #~ Render Items
328 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
329
330 class RenderItem(object):
331     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
332     #~ Constants / Variables / Etc.
333     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
334
335     transform = None
336     position = None
337     default_transform = Transform()
338     style = None
339     default_style = Style(stroke=(0,0,0))
340
341     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
342     #~ Definitions
343     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
344
345     def PreCompile(self, ristack=[]):
346         pass
347
348     def Compile(self, style, transform, target, ristack):
349         #style, transform = self._GetStyleAndTransform(style, transform)
350         pass
351
352     #~ SVG Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
353
354     def SetPosition(self, position):
355         self.position = position
356
357     def SetTransform(self, transform):
358         if self.transform is None:
359             self.transform = Transform.fromSVGTransforms(transform)
360         else:
361             self.transform.AddSVGTransforms(transform)
362
363     def SetStyle(self, style):
364         self.style = Style(style)
365
366     def IsGroupRendered(self):
367         return True
368
369     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
370
371     def _GetStyleAndTransform(self, style=None, transform=None):
372         if style is None:
373             if self.style is None:
374                 style = self.default_style.copy()
375             else:
376                 style = self.style.copy()
377         elif self.style is not None:
378             style = style + self.style
379
380         if transform is None:
381             if self.transform is None:
382                 transformout = self.default_transform.copy()
383             else:
384                 transformout = self.transform.copy()
385         elif self.transform is not None:
386             transformout = transform * self.transform
387         else:
388             transformout = transform
389
390         if self.position is not None:
391             if transformout is transform:
392                 transformout = transform.copy()
393             transformout.translate(*self.position)
394         return style, transformout
395
396     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
397
398     def AddProgress(self, target=None):
399         if target is not None:
400             target.AddGeometryProgress()
401
402     def CountGroups(self, allowcache=True):
403         return 0
404     def CountGeometry(self, allowcache=True):
405         return 1
406
407 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
408
409 class GroupRenderItem(RenderItem):
410     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
411     #~ Constants / Variables / Etc.
412     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
413
414     renderChildren = ()
415     otherChildren = ()
416
417     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
418     #~ Public Methods
419     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
420
421     def PreCompile(self, ristack=[]):
422         ristack = ristack + [self]
423         for child in self.renderChildren:
424             child.PreCompile(ristack)
425
426     def Compile(self, style=None, transform=None, target=None, ristack=[]):
427         style, transform = self._GetStyleAndTransform(style, transform)
428         ristack = ristack + [self]
429         for child in self.renderChildren:
430             child.Compile(style, transform, target, ristack)
431             child.AddProgress(target)
432
433     #~ SVG Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
434
435     def SetChildren(self, children):
436         self.renderChildren = []
437         self.otherChildren = []
438         for child in children:
439             if isinstance(child, RenderItem):
440                 if child.IsGroupRendered():
441                     self.renderChildren.append(child)
442                     continue
443             self.otherChildren.append(child)
444
445     #~ Progress Measurements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
446
447     def AddProgress(self, target=None):
448         if target is not None:
449             target.AddGroupProgress()
450
451     def CountGroups(self, allowcache=True):
452         if allowcache:
453             try: return self._cache_countgroups
454             except AttributeError: pass
455         result = sum([child.CountGroups(allowcache) for child in self.renderChildren], 1)
456         self._cache_countgroups = result
457         return result
458
459     def CountGeometry(self, allowcache=True):
460         if allowcache:
461             try: return self._cache_countgeometry
462             except AttributeError: pass
463         result = sum([child.CountGeometry(allowcache) for child in self.renderChildren], 0)
464         self._cache_countgeometry = result
465         return result
466
467 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
468
469 class UseRenderItem(RenderItem):
470     dimensions = (0.,0.)
471
472     def PreCompile(self, ristack=[]):
473         self.useitem.PreCompile(ristack+[self])
474
475     def Compile(self, style=None, transform=None, target=None, ristack=[]):
476         style, transform = self._GetStyleAndTransform(style, transform)
477         self.useitem.Compile(style, transform, target, ristack+[self])
478         self.useitem.AddProgress(target)
479
480     def _GetStyleAndTransform(self, style=None, transform=None):
481         style, transform = RenderItem._GetStyleAndTransform(self, style, transform)
482         transform.SetViewport(self.dimensions)
483         return style, transform
484
485     #~ SVG Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
486
487     def SetDimensions(self, width, height):
488         self.dimensions = width, height
489
490     def SetUseItem(self, useitem):
491         self.useitem = useitem
492
493     #~ Progress Measurements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
494
495     def AddProgress(self, target=None):
496         if target is not None:
497             target.AddGroupProgress()
498
499     def CountGroups(self, allowcache=True):
500         return 1 + self.useitem.CountGroups(allowcache)
501     def CountGeometry(self, allowcache=True):
502         return self.useitem.CountGeometry(allowcache)
503
504 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
505
506 class ContainerGroupRenderItem(GroupRenderItem):
507     viewbox = (None, None, None, None)
508     dimensions = (None, None)
509     alignment = ('xmid', 'ymid', 'meet')
510     position = None
511
512     def _GetStyleAndTransform(self, style=None, transform=None):
513         if transform is None:
514             # This implies that the default OpenGL portal is setup and the
515             # display is [-1,1] wide and [-1,1] tall.  Since I'm trying to
516             # implement this coordinate independent, then the svg dimensions
517             # should be mapped to this [-1,1] range in both directions.  At
518             # this point, we can also flip the coordinate system for OpenGL.
519
520             transform = Transform()
521             if self.position is not None:
522                 ux, uy = self.position
523             else: ux, uy = 0., 0.
524             if self.dimensions != (None, None):
525                 uw, uh = self.dimensions
526             else: uw, uh = 1., 1.
527
528             transform.matrix_byrow(2./uw,0.,-1.-2.*ux/uw, 0., -2./uh, 1.+2.*uy/uh)
529         else:
530             transform = transform.copy()
531             ux, uy = (0.,0.)
532             uw, uh = transform.GetViewport()
533
534             if self.position is not None:
535                 ux, uy = self.position
536             if self.dimensions != (None, None):
537                 uw, uh = self.dimensions
538
539         vx, vy, vw, vh = self.viewbox
540         uw, uh = uw or vw, uh or vh
541
542         if vw > 0. and vh > 0.:
543             vx, vy = vx or 0., vy or 0.
544
545             xalign, yalign, aligntype = [(s or '').lower() for s in self.alignment]
546             xalign, yalign = xalign or 'xmid', yalign or 'ymid'
547             nw, nh = float(uw)/vw, float(uh)/vh
548
549             if aligntype == 'meet': # scale by larger dimension
550                 if nh < nw: # height is greater -- scale by height
551                     sx = sy = uh/vh
552                 else: # width is greater -- scale by width
553                     sx = sy = uw/vw
554             elif aligntype == 'slice': # scale by smaller dimension
555                 if nh > nw: # width is greater -- scale by height
556                     sx = sy = uh/vh
557                 else: # height is greater -- scale by width
558                     sx = sy = uw/vw
559             else: # scale by both width and height
560                 sx, sy = uw/vw, uh/vh
561
562             if aligntype in ['meet', 'slice']:
563                 if xalign == 'xmin': tx = ux - sx*vx + 0.
564                 elif xalign == 'xmid': tx = ux - sx*vx + 0.5*(uw-vw*sx)
565                 elif xalign == 'xmax': tx = ux - sx*vx + (uw-vw*sx)
566                 else: assert False
567
568                 if yalign == 'ymin': ty = uy - sy*vy + 0.
569                 elif yalign == 'ymid': ty = uy - sy*vy + 0.5*(uh-vh*sy)
570                 elif yalign == 'ymax': ty = uy - sy*vy + (uh-vh*sy)
571                 else: assert False
572             else:
573                 tx, ty = ux, uy
574
575             transform.matrix_byrow(sx, 0., tx, 0., sy, ty)
576
577         return GroupRenderItem._GetStyleAndTransform(self, style, transform)
578
579     #~ SVG Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
580
581     def SetDimensions(self, width, height):
582         self.dimensions = width, height
583     def SetViewBox(self, viewbox):
584         if viewbox is not None:
585             self.viewbox = viewbox
586     def SetAlignment(self, xalign, yalign, alignstyle):
587         self.alignment = xalign, yalign, alignstyle
588
589 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
590
591 class SymbolGroup(ContainerGroupRenderItem):
592     def IsGroupRendered(self):
593         return False
594
595 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
596
597 class SVGGroupRenderItem(ContainerGroupRenderItem):
598     target = None
599     def Compile(self, style=None, transform=None, intarget=None, ristack=[]):
600         self.target = GLGeometryCollector(self.CountGroups(), self.CountGeometry())
601         # TODO: implement render options
602
603         ContainerGroupRenderItem.Compile(self, style, transform, self.target, ristack)
604
605         if intarget is not None:
606             result = self.target.Commit(intarget)
607         else: # we're the root... return our goodies
608             result = self.target.GetRenderables()
609
610         del self.target
611         return result
612
613     def CompileProgress(self):
614         if self.target:
615             return self.target.Progress()
616         else: return None
617
618     #~ SVG Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
619
620     def SetRenderOptions(self, text=None, shape=None, image=None):
621         pass # TODO: implement
622
623 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
624 #~ Shapes
625 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
626
627 class Line(RenderItem):
628     def Compile(self, style, transform, target, ristack):
629         style, transform = self._GetStyleAndTransform(style, transform)
630
631         points = transform.TransformPoints(self.points)
632         strokecolor = style.GetStrokeColor()
633         if strokecolor is not None:
634             stroke = GLStroke(points, strokecolor)
635             stroke.AddLines(range(2))
636             stroke.Commit(target)
637
638     #~ SVG Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
639
640     def SetLine(self, c0, c1):
641         self.points = [c0, c1]
642
643 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
644
645 class Rect(RenderItem):
646     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
647     #~ Constants / Variables / Etc.
648     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
649
650     dimensions = (1,1)
651
652     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
653     #~ Public Methods
654     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
655
656     def Compile(self, style, transform, target, ristack):
657         style, transform = self._GetStyleAndTransform(style, transform)
658         x,y = 0., 0.
659         w,h = self.dimensions
660
661         # TODO: compute rounded rectangles
662         points = [(x,y), (x, y+h), (x+w, y+h), (x+w, y)]
663         points = transform.TransformPoints(points)
664
665         fillcolor = style.GetFillColor()
666         if fillcolor is not None:
667             fill = GLFill(points, fillcolor)
668             fill.AddTriangleFan(range(4))
669             fill.Commit(target)
670
671         strokecolor = style.GetStrokeColor()
672         if strokecolor is not None:
673             stroke = GLStroke(points, strokecolor)
674             stroke.AddLineLoop(range(4))
675             stroke.Commit(target)
676
677     #~ SVG Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
678
679     def SetDimensions(self, width, height):
680         self.dimensions = width, height
681
682     def SetRadi(self, radiusx, radiusy):
683         if radiusx or radiusy:
684             warnings.warn('Rounded rectangles are not yet supported')
685         #self.radiusx = radiusx
686         #self.radiusy = radiusy
687
688 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
689
690 class Ellipse(RenderItem):
691     ellipsesteps = 64
692     def Compile(self, style, transform, target, ristack):
693         style, transform = self._GetStyleAndTransform(style, transform)
694
695         ellipse = Curves.Ellipse(self.center, self.radiusx, self.radiusy, steps=self.ellipsesteps)
696         points = transform.TransformPoints(ellipse.asCurve())
697
698         fillcolor = style.GetFillColor()
699         if fillcolor is not None:
700             fillpoints = Numeric.concatenate((transform.TransformPoints([self.center]), points))
701             fill = GLFill(fillpoints, fillcolor)
702             fill.AddTriangleFan(range(len(points))+[1])
703             fill.Commit(target)
704
705         strokecolor = style.GetStrokeColor()
706         if strokecolor is not None:
707             stroke = GLStroke(points, strokecolor)
708             stroke.AddLineLoop(range(len(points)))
709             stroke.Commit(target)
710
711     #~ SVG Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
712
713     def SetCenter(self, center):
714         self.center = center
715     def SetRadius(self, radius):
716         self.SetRadi(radius, radius)
717     def SetRadi(self, radiusx, radiusy):
718         self.radiusx = radiusx
719         self.radiusy = radiusy
720
721 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
722
723 class Circle(Ellipse):
724     # Note: this could be optimized beyond ellipse.
725     # However, we get two implementations for one :)
726     pass
727
728 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
729
730 class Polyline(RenderItem):
731     def Compile(self, style, transform, target, ristack):
732         if len(self.points) < 2: return
733         style, transform = self._GetStyleAndTransform(style, transform)
734
735         fillcolor = style.GetFillColor()
736         if fillcolor is not None and len(self.points) >= 3:
737             windingrule = FillRuleLookup.get(style.get('fill-rule'))
738             if windingrule is None: windingrule = FillRuleLookup[None]
739
740             tesselator = SVGTesselator(normal=(0,0,1))
741             tesselator.open(windingrule=windingrule)
742             glmodedatalist = tesselator.tessellate([self.points])
743             tesselator.close()
744             for glmode, fillpoints in glmodedatalist:
745                 fillpoints = transform.TransformPoints(fillpoints)
746                 fill = GLFill(fillpoints, fillcolor)
747                 fill.Add(glmode, range(len(fillpoints)))
748                 fill.Commit(target)
749
750         strokecolor = style.GetStrokeColor()
751         if strokecolor is not None:
752             points = transform.TransformPoints(self.points)
753             stroke = GLStroke(points, strokecolor)
754             stroke.AddLineStrip(range(len(points)))
755             stroke.Commit(target)
756
757     #~ SVG Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
758
759     def SetPoints(self, points):
760         self.points = points
761
762 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
763
764 class Polygon(Polyline):
765     def Compile(self, style, transform, target, ristack):
766         if len(self.points) < 2: return
767         style, transform = self._GetStyleAndTransform(style, transform)
768
769         windingrule = FillRuleLookup.get(style.get('fill-rule'))
770         if windingrule is None: windingrule = FillRuleLookup[None]
771         tesselator = SVGTesselator(normal=(0,0,1))
772         tesselator.open(windingrule=windingrule)
773
774         fillcolor = style.GetFillColor()
775         if fillcolor is not None:
776             glmodedatalist = tesselator.tessellate([self.points])
777             for glmode, fillpoints in glmodedatalist:
778                 fillpoints = transform.TransformPoints(fillpoints)
779                 fill = GLFill(fillpoints, fillcolor)
780                 fill.Add(glmode, range(len(fillpoints)))
781                 fill.Commit(target)
782
783         strokecolor = style.GetStrokeColor()
784         if strokecolor is not None:
785             glmodedatalist = tesselator.tessellate([self.points])
786             for glmode, strokepoints in glmodedatalist:
787                 strokepoints = transform.TransformPoints(strokepoints)
788                 stroke = GLStroke(strokepoints, strokecolor)
789                 stroke.Add(glmode, range(len(strokepoints)))
790                 stroke.Commit(target)
791         tesselator.close()
792
793     def SetPoints(self, points):
794         if points[0] != points[-1]:
795             self.points = points + [points[0]]
796         else:
797             self.points = points
798         assert len(self.points) >= 3
799
800 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
801
802 class Path(RenderItem):
803     def Compile(self, style, transform, target, ristack):
804         contours = SVGPathConnector.fromSVGPath(self.path).GetContours()
805         if not contours: return # must be a degenerate path... exit gracefully
806
807         style, transform = self._GetStyleAndTransform(style, transform)
808
809         fillcolor = style.GetFillColor()
810         if fillcolor is not None:
811             # paths can only be formed when there is more than two points...
812             contourlist = [contour for contour in contours if len(contour)>2]
813
814             if contourlist:
815                 windingrule = FillRuleLookup.get(style.get('fill-rule'))
816                 if windingrule is None: windingrule = FillRuleLookup[None]
817
818                 tesselator = SVGTesselator(normal=(0,0,1))
819                 tesselator.open(windingrule=windingrule)
820                 glmodedatalist = tesselator.tessellate(contourlist)
821                 tesselator.close()
822                 for glmode, fillpoints in glmodedatalist:
823                     fillpoints = transform.TransformPoints(fillpoints)
824                     fill = GLFill(fillpoints, fillcolor)
825                     fill.Add(glmode, range(len(fillpoints)))
826                     fill.Commit(target)
827
828         strokecolor = style.GetStrokeColor()
829         if strokecolor is not None:
830             points = [pt for contour in contours for pt in contour]
831             points = transform.TransformPoints(points)
832
833             stroke = GLStroke(points, strokecolor)
834             index = 0
835             for count in map(len, contours):
836                 stroke.AddLineStrip(range(index, index+count))
837                 index += count
838             stroke.Commit(target)
839
840     #~ SVG Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
841
842     def SetPath(self, path):
843         self.path = path
844
845 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
846 #~ Images
847 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
848
849 class Image(RenderItem):
850     #~ SVG Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
851
852     def SetDimensions(self, width, height):
853         self.dimensions = width, height
854     def SetImageReference(self, ref):
855         self.imageref = ref
856
857 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
858 #~ Text Content
859 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
860
861 class ContentRenderItem(RenderItem):
862     #~ SVG Settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
863
864     def SetContent(self, content):
865         self.content = content
866
867 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
868 #~ Main mapping
869 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
870
871 class RapierSVGRenderItemFactory(object):
872     mapping = {
873         'svg': SVGGroupRenderItem,
874         'g': GroupRenderItem,
875
876         'line': Line,
877         'rect': Rect,
878         #'image': Image,
879         'path': Path,
880         'polyline': Polyline,
881         'polygon': Polygon,
882         'ellipse': Ellipse,
883         'circle': Circle,
884        
885         'use': UseRenderItem,
886         'symbol': SymbolGroup,
887         #'pattern': None,
888         #'marker': None,
889
890         #'text': None,
891         #'tspan': None,
892         'title': None,
893         'desc': None,
894
895         'defs': None,
896         }
897
898     def __call__(self, renderitem):
899         factory = self.mapping.get(renderitem)
900         if factory is not None:
901             return factory()
902         else:
903             return None
904
Note: See TracBrowser for help on using the browser.