source: orange/Orange/orng/orngMisc.py @ 10654:cd73789785b5

Revision 10654:cd73789785b5, 27.1 KB checked in by markotoplak, 2 years ago (diff)

Moved selection from misc to utils.

Line 
1from __future__ import with_statement
2import random, types
3import os.path
4
5from Orange.utils.environ import install_dir
6## -> Orange.misc
7
8def getobjectname(x, default=""):
9    if type(x) == types.StringType:
10        return x
11
12    for i in ["name", "shortDescription", "description", "func_doc", "func_name"]:
13        if getattr(x, i, ""):
14            return getattr(x, i)
15
16    if hasattr(x, "__class__"):
17        r = repr(x.__class__)
18        if r[1:5] == "type":
19            return str(x.__class__)[7:-2]
20        elif r[1:6] == "class":
21            return str(x.__class__)[8:-2]
22
23    return default
24
25
26def demangleExamples(x):
27    if type(x) == types.TupleType:
28        return x
29    else:
30        return x, 0
31
32
33## -> Orange.misc.counters
34
35class BooleanCounter:
36  def __init__(self, bits):
37    self.bits = bits
38    self.state = None
39
40  def __iter__(self):
41    if self.state:
42        return self
43    else:
44        return BooleanCounter(self.bits)
45
46  def next(self):
47    if self.state:
48      for bit in range(self.bits - 1, -1, -1):
49        self.state[bit] = (self.state[bit] + 1) % 2
50        if self.state[bit]:
51          break
52      else:
53        self.state = None
54    else:
55      self.state = [0] * self.bits
56
57    if not self.state:
58        raise StopIteration, "BooleanCounter: counting finished"
59
60    return self.state
61
62
63class LimitedCounter:
64  def __init__(self, limits):
65    self.limits = limits
66    self.state = None
67
68  def __iter__(self):
69    if self.state:
70        return self
71    else:
72        return LimitedCounter(self.limits)
73
74  def next(self):
75    if self.state:
76      i = len(self.limits) - 1
77      while (i >= 0) and (self.state[i] == self.limits[i] - 1):
78        self.state[i] = 0
79        i -= 1
80      if i == -1:
81        self.state = None
82      else:
83        self.state[i] += 1
84    else:
85      self.state = [0] * len(self.limits)
86
87    if not self.state:
88      raise StopIteration, "LimitedCounter: counting finished"
89
90    return self.state
91
92
93class MofNCounter:
94    def __init__(self, m, n):
95        if m > n:
96            raise TypeError, "Number of selected items exceeds the number of items"
97
98        self.state = None
99        self.m = m
100        self.n = n
101
102    def __iter__(self):
103        if self.state:
104            return self
105        else:
106            return MofNCounter(self.m, self.n)
107
108    def next(self):
109        if self.state:
110            m, n, state = self.m, self.n, self.state
111            for place in range(m - 1, -1, -1):
112                if state[place] + m - 1 - place < n - 1:
113                    state[place] += 1
114                    for place in range(place + 1, m):
115                        state[place] = state[place - 1] + 1
116                    break
117            else:
118                self.state = None
119                raise StopIteration, "MofNCounter: counting finished"
120        else:
121            self.state = range(self.m)
122
123        return self.state[:]
124
125class NondecreasingCounter:
126  def __init__(self, places):
127    self.state = None
128    self.subcounter = None
129    self.places = places
130
131  def __iter__(self):
132    if self.state:
133        return self
134    else:
135        return NondecreasingCounter(self.places)
136
137  def next(self):
138    if not self.subcounter:
139      self.subcounter = BooleanCounter(self.places - 1)
140    if self.subcounter.next():
141      self.state = [0]
142      for add_one in self.subcounter.state:
143        self.state.append(self.state[-1] + add_one)
144    else:
145      self.state = None
146
147    if not self.state:
148      raise StopIteration, "NondecreasingCounter: counting finished"
149
150    return self.state
151
152
153class CanonicFuncCounter:
154  def __init__(self, places):
155    self.places = places
156    self.state = None
157
158  def __iter__(self):
159    if self.state:
160        return self
161    else:
162        return CanonicFuncCounter(self.places)
163
164  def next(self):
165    if self.state:
166      i = self.places - 1
167      while (i > 0) and (self.state[i] == max(self.state[:i]) + 1):
168        self.state[i] = 0
169        i -= 1
170      if i:
171        self.state[i] += 1
172      else:
173        self.state = None
174    else:
175      self.state = [0] * self.places
176
177    if not self.state:
178      raise StopIteration, "CanonicFuncCounter: counting finished"
179
180    return self.state
181
182
183## Orange.utils.selection
184
185import random
186
187class BestOnTheFly:
188    def __init__(self, compare=cmp, seed=0, callCompareOn1st=False):
189        self.randomGenerator = random.Random(seed)
190        self.compare = compare
191        self.wins = 0
192        self.bestIndex, self.index = -1, -1
193        self.best = None
194        self.callCompareOn1st = callCompareOn1st
195
196    def candidate(self, x):
197        self.index += 1
198        if not self.wins:
199            self.best = x
200            self.wins = 1
201            self.bestIndex = self.index
202            return 1
203        else:
204            if self.callCompareOn1st:
205                cmpr = self.compare(x[0], self.best[0])
206            else:
207                cmpr = self.compare(x, self.best)
208            if cmpr > 0:
209                self.best = x
210                self.wins = 1
211                self.bestIndex = self.index
212                return 1
213            elif cmpr == 0:
214                self.wins = self.wins + 1
215                if not self.randomGenerator.randint(0, self.wins - 1):
216                    self.best = x
217                    self.bestIndex = self.index
218                    return 1
219        return 0
220
221    def winner(self):
222        return self.best
223
224    def winnerIndex(self):
225        if self.best is not None:
226            return self.bestIndex
227        else:
228            return None
229
230def selectBest(x, compare=cmp, seed=0, callCompareOn1st=False):
231    bs = BestOnTheFly(compare, seed, callCompareOn1st)
232    for i in x:
233        bs.candidate(i)
234    return bs.winner()
235
236def selectBestIndex(x, compare=cmp, seed=0, callCompareOn1st=False):
237    bs = BestOnTheFly(compare, seed, callCompareOn1st)
238    for i in x:
239        bs.candidate(i)
240    return bs.winnerIndex()
241
242def compare2_firstBigger(x, y):
243    return cmp(x[0], y[0])
244
245def compare2_firstSmaller(x, y):
246    return -cmp(x[0], y[0])
247
248def compare2_lastBigger(x, y):
249    return cmp(x[-1], y[-1])
250
251def compare2_lastSmaller(x, y):
252    return -cmp(x[-1], y[-1])
253
254def compare2_bigger(x, y):
255    return cmp(x, y)
256
257def compare2_smaller(x, y):
258    return -cmp(x, y)
259
260
261# Orange.misc
262
263def frange(*argw):
264    start, stop, step = 0.0, 1.0, 0.1
265    if len(argw) == 1:
266        start = step = argw[0]
267    elif len(argw) == 2:
268        stop, step = argw
269    elif len(argw) == 3:
270        start, stop, step = argw
271    elif len(argw) > 3:
272        raise AttributeError, "1-3 arguments expected"
273
274    stop += 1e-10
275    i = 0
276    res = []
277    while 1:
278        f = start + i * step
279        if f > stop:
280            break
281        res.append(f)
282        i += 1
283    return res
284
285
286# Orange.misc
287
288verbose = 0
289
290def printVerbose(text, *verb):
291    if len(verb) and verb[0] or verbose:
292        print text
293
294import sys
295
296class ConsoleProgressBar(object):
297    def __init__(self, title="", charwidth=40, step=1, output=sys.stderr):
298        self.title = title + " "
299        self.charwidth = charwidth
300        self.step = step
301        self.currstring = ""
302        self.state = 0
303        self.output = output
304
305    def clear(self, i= -1):
306        try:
307            if hasattr(self.output, "isatty") and self.output.isatty():
308                self.output.write("\b" * (i if i != -1 else len(self.currstring)))
309            else:
310                self.output.seek(-i if i != -1 else -len(self.currstring), 2)
311        except Exception: ## If for some reason we failed
312            self.output.write("\n")
313
314    def getstring(self):
315        progchar = int(round(float(self.state) * (self.charwidth - 5) / 100.0))
316        return self.title + "=" * (progchar) + ">" + " " * (self.charwidth - 5 - progchar) + "%3i" % int(round(self.state)) + "%"
317
318    def printline(self, string):
319        try:
320            self.clear()
321            self.output.write(string)
322            self.output.flush()
323        except Exception:
324            pass
325        self.currstring = string
326
327    def __call__(self, newstate=None):
328        if newstate == None:
329            newstate = self.state + self.step
330        if int(newstate) != int(self.state):
331            self.state = newstate
332            self.printline(self.getstring())
333        else:
334            self.state = newstate
335
336    def finish(self):
337        self.__call__(100)
338        self.output.write("\n")
339
340def progressBarMilestones(count, iterations=100):
341    return set([int(i * count / float(iterations)) for i in range(iterations)])
342
343
344## Odkrij, kdo to uporablja
345
346class ColorPalette(object):
347    def __init__(self, colors, gamma=None, overflow=(255, 255, 255), underflow=(255, 255, 255), unknown=(0, 0, 0)):
348        self.colors = colors
349        self.gammaFunc = lambda x, gamma:((math.exp(gamma * math.log(2 * x - 1)) if x > 0.5 else -math.exp(gamma * math.log(-2 * x + 1)) if x != 0.5 else 0.0) + 1) / 2.0
350        self.gamma = gamma
351        self.overflow = overflow
352        self.underflow = underflow
353        self.unknown = unknown
354
355    def get_rgb(self, val, gamma=None):
356        if val is None:
357            return self.unknown
358        gamma = self.gamma if gamma is None else gamma
359        index = int(val * (len(self.colors) - 1))
360        if val < 0.0:
361            return self.underflow
362        elif val > 1.0:
363            return self.overflow
364        elif index == len(self.colors) - 1:
365            return tuple(self.colors[-1][i] for i in range(3)) # self.colors[-1].green(), self.colors[-1].blue())
366        else:
367            red1, green1, blue1 = [self.colors[index][i] for i in range(3)] #, self.colors[index].green(), self.colors[index].blue()
368            red2, green2, blue2 = [self.colors[index + 1][i] for i in range(3)] #, self.colors[index + 1].green(), self.colors[index + 1].blue()
369            x = val * (len(self.colors) - 1) - index
370            if gamma is not None:
371                x = self.gammaFunc(x, gamma)
372            return [(c2 - c1) * x + c1 for c1, c2 in [(red1, red2), (green1, green2), (blue1, blue2)]]
373
374    def __call__(self, val, gamma=None):
375        return self.get_rgb(val, gamma)
376
377import math
378
379#from ColorPalette import ColorPalette
380
381class GeneratorContextManager(object):
382   def __init__(self, gen):
383       self.gen = gen
384   def __enter__(self):
385       try:
386           return self.gen.next()
387       except StopIteration:
388           raise RuntimeError("generator didn't yield")
389   def __exit__(self, type, value, traceback):
390       if type is None:
391           try:
392               self.gen.next()
393           except StopIteration:
394               return
395           else:
396               raise RuntimeError("generator didn't stop")
397       else:
398           try:
399               self.gen.throw(type, value, traceback)
400               raise RuntimeError("generator didn't stop after throw()")
401           except StopIteration:
402               return True
403           except:
404               # only re-raise if it's *not* the exception that was
405               # passed to throw(), because __exit__() must not raise
406               # an exception unless __exit__() itself failed.  But
407               # throw() has to raise the exception to signal
408               # propagation, so this fixes the impedance mismatch
409               # between the throw() protocol and the __exit__()
410               # protocol.
411               #
412               if sys.exc_info()[1] is not value:
413                   raise
414
415def contextmanager(func):
416    def helper(*args, **kwds):
417        return GeneratorContextManager(func(*args, **kwds))
418    return helper
419
420from functools import wraps
421def with_state(func):
422    @wraps(func)
423    def wrap(self, *args, **kwargs):
424        with self.state(**kwargs):
425            r = func(self, *args)
426        return r
427    return wrap
428
429def with_gc_disabled(func):
430    import gc
431    def disabler():
432        gc.disable()
433        try:
434            yield
435        finally:
436            gc.enable()
437    @wraps(func)
438    def wrapper(*args, **kwargs):
439        with contextmanager(disabler)():
440            return func(*args, **kwargs)
441    return wrapper
442
443
444## -> Orange.misc.render
445
446import numpy
447
448class Renderer(object):
449    render_state_attributes = ["font", "stroke_color", "fill_color", "render_hints", "transform", "gradient", "text_alignment"]
450
451    ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER = range(3)
452
453    def __init__(self, width, height):
454        self.width = width
455        self.height = height
456        self.render_state = {}
457        self.render_state["font"] = ("Times-Roman", 10)
458        self.render_state["fill_color"] = (0, 0, 0)
459        self.render_state["gradient"] = {}
460        self.render_state["stroke_color"] = (0, 0, 0)
461        self.render_state["stroke_width"] = 1
462        self.render_state["text_alignment"] = self.ALIGN_LEFT
463        self.render_state["transform"] = numpy.matrix(numpy.eye(3))
464        self.render_state["view_transform"] = numpy.matrix(numpy.eye(3))
465        self.render_state["render_hints"] = {}
466        self.render_state_stack = []
467
468    def font(self):
469        return self.render_state["font"]
470
471    def set_font(self, family, size):
472        self.render_state["font"] = family, size
473
474    def fill_color(self):
475        return self.render_state["fill_color"]
476
477    def set_fill_color(self, color):
478        self.render_state["fill_color"] = color
479
480    def set_gradient(self, gradient):
481        self.render_state["gradient"] = gradient
482
483    def gradient(self):
484        return self.render_state["gradient"]
485
486    def stroke_color(self):
487        return self.render_state["stroke_color"]
488
489    def set_stroke_color(self, color):
490        self.render_state["stroke_color"] = color
491
492    def stroke_width(self):
493        return self.render_state["stroke_width"]
494
495    def set_stroke_width(self, width):
496        self.render_state["stroke_width"] = width
497
498    def set_text_alignment(self, align):
499        self.render_state["text_alignment"] = align
500
501    def text_alignment(self):
502        return self.render_state["text_alignment"]
503
504    def transform(self):
505        return self.render_state["transform"]
506
507    def set_transform(self, transform):
508        self.render_state["transform"] = transform
509
510    def render_hints(self):
511        return self.render_state["render_hints"]
512
513    def set_render_hints(self, hints):
514        self.render_state["render_hints"].update(hints)
515
516    def save_render_state(self):
517        import copy
518        self.render_state_stack.append(copy.deepcopy(self.render_state))
519
520    def restore_render_state(self):
521        self.render_state = self.render_state_stack.pop(-1)
522
523    def apply_transform(self, transform):
524        self.render_state["transform"] = self.render_state["transform"] * transform
525
526    def translate(self, x, y):
527        transform = numpy.eye(3)
528        transform[:, 2] = x, y, 1
529        self.apply_transform(transform)
530
531    def rotate(self, angle):
532        angle *= 2 * math.pi / 360.0
533        transform = numpy.eye(3)
534        transform[:2, :2] = [[math.cos(angle), -math.sin(angle)], [math.sin(angle), math.cos(angle)]]
535        self.apply_transform(transform)
536
537    def scale(self, sx, sy):
538        transform = numpy.eye(3)
539        transform[(0, 1), (0, 1)] = sx, sy
540        self.apply_transform(transform)
541
542    def skew(self, sx, sy):
543        transform = numpy.eye(3)
544        transform[(1, 0), (0, 1)] = numpy.array([sx, sy]) * 2 * math.pi / 360.0
545        self.apply_transform(transform)
546
547    def draw_line(self, sx, sy, ex, ey, **kwargs):
548        raise NotImplemented
549
550    def draw_lines(self, points, **kwargs):
551        raise NotImplemented
552
553    def draw_rect(self, x, y, w, h, **kwargs):
554        raise NotImplemented
555
556    def draw_polygon(self, vertices, **kwargs):
557        raise NotImplemented
558
559    def draw_arch(self, something, **kwargs):
560        raise NotImplemented
561
562    def draw_text(self, x, y, text, **kwargs):
563        raise NotImplemented
564
565    def string_size_hint(self, text, **kwargs):
566        raise NotImpemented
567
568    @contextmanager
569    def state(self, **kwargs):
570        self.save_render_state()
571        for key, value in kwargs.items():
572            if key in ["translate", "rotate", "scale", "skew"]:
573                getattr(self, key)(*value)
574            else:
575                getattr(self, "set_" + key)(value)
576        try:
577            yield
578        finally:
579            self.restore_render_state()
580
581    def save(self):
582        raise NotImplemented
583
584    def close(self, file):
585        pass
586
587class EPSRenderer(Renderer):
588    EPS_DRAW_RECT = """/draw_rect
589{/h exch def /w exch def
590 /y exch def /x exch def
591 newpath
592 x y moveto
593 w 0 rlineto
594 0 h neg rlineto
595 w neg 0 rlineto
596 closepath
597} def"""
598
599    EPS_SET_GRADIENT = """<< /PatternType 2
600 /Shading
601   << /ShadingType 2
602      /ColorSpace /DeviceRGB
603      /Coords [%f %f %f %f]
604      /Function
605      << /FunctionType 0
606         /Domain [0 1]
607         /Range [0 1 0 1 0 1]
608         /BitsPerSample 8
609         /Size [%i]
610         /DataSource <%s>
611      >>
612   >>
613>>
614matrix
615makepattern
616/mypattern exch def
617/Pattern setcolorspace
618mypattern setcolor
619
620"""
621
622    EPS_SHOW_FUNCTIONS = """/center_align_show
623{ dup stringwidth pop
624  2 div
625  neg
626  0 rmoveto
627  show } def
628 
629/right_align_show
630{ dup stringwidth pop
631  neg
632  0 rmoveto
633  show } def
634"""
635    def __init__(self, width, height):
636        Renderer.__init__(self, width, height)
637        from StringIO import StringIO
638        self._eps = StringIO()
639        self._eps.write("%%!PS-Adobe-3.0 EPSF-3.0\n%%%%BoundingBox: 0 0 %i %i\n" % (width, height))
640        self._eps.write(self.EPS_SHOW_FUNCTIONS)
641        self._eps.write("%f %f translate\n" % (0, self.height))
642        self.set_font(*self.render_state["font"])
643        self._inline_func = dict(stroke_color=lambda color: "%f %f %f setrgbcolor" % tuple(255.0 / c for c in color),
644                                 fill_color=lambda color:"%f %f %f setrgbcolor" % tuple(255.0 / c for c in color),
645                                 stroke_width=lambda w: "%f setlinewidth" % w)
646
647    def set_font(self, family, size):
648        Renderer.set_font(self, family, size)
649        self._eps.write("/%s findfont %f scalefont setfont\n" % self.font())
650
651    def set_fill_color(self, color):
652        Renderer.set_fill_color(self, color)
653        self._eps.write("%f %f %f setrgbcolor\n" % tuple(c / 255.0 for c in color))
654
655    def set_gradient(self, gradient):
656        Renderer.set_gradient(self, gradient)
657        (x1, y1, x2, y2), samples = gradient
658        binary = "".join([chr(int(c)) for p, s in samples for c in s])
659        import binascii
660        self._eps.write(self.EPS_SET_GRADIENT % (x1, y1, x2, y2, len(samples), binascii.hexlify(binary)))
661
662    def set_stroke_color(self, color):
663        Renderer.set_stroke_color(self, color)
664        self._eps.write("%f %f %f setrgbcolor\n" % tuple(c / 255.0 for c in color))
665
666    def set_stroke_width(self, width):
667        Renderer.set_stroke_width(self, width)
668        self._eps.write("%f setlinewidth\n" % width)
669
670    def set_render_hints(self, hints):
671        Renderer.set_render_hints(self, hints)
672        if hints.get("linecap", None):
673            map = {"butt":0, "round":1, "rect":2}
674            self._eps.write("%i setlinecap\n" % (map.get(hints.get("linecap"), 0)))
675
676    @with_state
677    def draw_line(self, sx, sy, ex, ey, **kwargs):
678        self._eps.write("newpath\n%f %f moveto %f %f lineto\nstroke\n" % (sx, -sy, ex, -ey))
679
680    @with_state
681    def draw_rect(self, x, y, w, h, **kwargs):
682        self._eps.write("newpath\n%(x)f %(y)f moveto %(w)f 0 rlineto\n0 %(h)f rlineto %(w)f neg 0 rlineto\nclosepath\n" % dict(x=x, y= -y, w=w, h= -h))
683        self._eps.write("gsave\n")
684        if self.gradient():
685            self.set_gradient(self.gradient())
686        else:
687            self.set_fill_color(self.fill_color())
688        self._eps.write("fill\ngrestore\n")
689        self.set_stroke_color(self.stroke_color())
690        self._eps.write("stroke\n")
691
692    @with_state
693    def draw_polygon(self, vertices, **kwargs):
694        self._eps.write("newpath\n%f %f moveto\n" % vertices[0])
695        for x, y in vertices[1:]:
696            self._eps.write("%f %f lineto\n" % (x, y))
697        self._eps.write("closepath\n")
698        self._eps.write("gsave\n")
699        self.set_fill_color(self.fill_color())
700        self._eps.write("fill\ngrestore\n")
701        self.set_stroke_color(self.stroke_color())
702        self._eps.write("stroke\n")
703
704    @with_state
705    def draw_text(self, x, y, text, **kwargs):
706        show = ["show", "right_align_show", "center_align_show"][self.text_alignment()]
707        self._eps.write("%f %f moveto (%s) %s\n" % (x, -y, text, show))
708
709    def save_render_state(self):
710        Renderer.save_render_state(self)
711        self._eps.write("gsave\n")
712
713    def restore_render_state(self):
714        Renderer.restore_render_state(self)
715        self._eps.write("grestore\n")
716
717    def translate(self, dx, dy):
718        Renderer.translate(self, dx, dy)
719        self._eps.write("%f %f translate\n" % (dx, -dy))
720
721    def rotate(self, angle):
722        Renderer.rotate(self, angle)
723        self._eps.write("%f rotate\n" % -angle)
724
725    def scale(self, sx, sy):
726        Renderer.scale(self, sx, sy)
727        self._eps.write("%f %f scale\n" % (sx, sy))
728
729    def skew(self, sx, sy):
730        Renderer.skew(self, sx, sy)
731        self._eps.write("%f %f skew\n" % (sx, sy))
732
733    def save(self, filename):
734#        self._eps.close()
735        open(filename, "wb").write(self._eps.getvalue())
736
737    def string_size_hint(self, text, **kwargs):
738        import warnings
739        warnings.warn("EpsRenderer class does not suport exact string width estimation", stacklevel=2)
740        return len(text) * self.font()[1]
741
742
743class PILRenderer(Renderer):
744    def __init__(self, width, height):
745        Renderer.__init__(self, width, height)
746        import Image, ImageDraw, ImageFont
747        self._pil_image = Image.new("RGB", (width, height), (255, 255, 255))
748        self._draw = ImageDraw.Draw(self._pil_image, "RGB")
749        self._pil_font = ImageFont.load_default()
750
751    def _transform(self, x, y):
752        p = self.transform() * [[x], [y], [1]]
753        return p[0, 0], p[1, 0]
754
755    def set_font(self, family, size):
756        Renderer.set_font(self, family, size)
757        import ImageFont
758        try:
759            font_file = os.path.join(install_dir, "utils", family + ".ttf")
760            if os.path.exists(font_file):
761                self._pil_font = ImageFont.truetype(font_file, int(size))
762            else:
763                self._pil_font = ImageFont.truetype(family + ".ttf", int(size))
764        except Exception:
765            import warnings
766            warnings.warn("Could not load %s.ttf font!", stacklevel=2)
767            try:
768                self._pil_font = ImageFont.truetype("cour.ttf", int(size))
769            except Exception:
770                warnings.warn("Could not load the cour.ttf font!! Loading the default", stacklevel=2)
771                self._pil_font = ImageFont.load_default()
772
773    @with_state
774    def draw_line(self, sx, sy, ex, ey, **kwargs):
775        sx, sy = self._transform(sx, sy)
776        ex, ey = self._transform(ex, ey)
777        self._draw.line((sx, sy, ex, ey), fill=self.stroke_color(), width=int(self.stroke_width()))
778
779    @with_state
780    def draw_rect(self, x, y, w, h, **kwargs):
781        x1, y1 = self._transform(x, y)
782        x2, y2 = self._transform(x + w, y + h)
783        self._draw.rectangle((x1, y1, x2 , y2), fill=self.fill_color(), outline=self.stroke_color())
784
785    @with_state
786    def draw_text(self, x, y, text, **kwargs):
787        x, y = self._transform(x, y - self.font()[1])
788        self._draw.text((x, y), text, font=self._pil_font, fill=self.stroke_color())
789
790    def save(self, file):
791        self._pil_image.save(file)
792
793    def string_size_hint(self, text, **kwargs):
794        return self._pil_font.getsize(text)[1]
795
796class SVGRenderer(Renderer):
797    SVG_HEADER = """<?xml version="1.0" ?>
798<svg height="%f" version="1.0" width="%f" xmlns="http://www.w3.org/2000/svg">
799<defs>
800    %s
801</defs>
802    %s
803</svg>
804"""
805    def __init__(self, width, height):
806        Renderer.__init__(self, width, height)
807        self.transform_count_stack = [0]
808        import StringIO
809        self._svg = StringIO.StringIO()
810        self._defs = StringIO.StringIO()
811        self._gradients = {}
812
813    def set_gradient(self, gradient):
814        Renderer.set_gradient(self, gradient)
815        if gradient not in self._gradients.items():
816            id = "grad%i" % len(self._gradients)
817            self._gradients[id] = gradient
818            (x1, y1, x2, y2), stops = gradient
819            (x1, y1, x2, y2) = (0, 0, 100, 0)
820
821            self._defs.write('<linearGradient id="%s" x1="%f%%" y1="%f%%" x2="%f%%" y2="%f%%">\n' % (id, x1, y1, x2, y2))
822            for offset, color in stops:
823                self._defs.write('<stop offset="%f" style="stop-color:rgb(%i, %i, %i); stop-opacity:1"/>\n' % ((offset,) + color))
824            self._defs.write('</linearGradient>\n')
825
826    def get_fill(self):
827        if self.render_state["gradient"]:
828            return 'style="fill:url(#%s)"' % ([key for key, gr in self._gradients.items() if gr == self.render_state["gradient"]][0])
829        else:
830            return 'fill="rgb(%i %i %i)"' % self.fill_color()
831
832    def get_stroke(self):
833#        if self.render_state["gradient"]:
834#            return ""
835#        else:
836            return 'stroke="rgb(%i, %i, %i)"' % self.stroke_color() + ' stroke-width="%f"' % self.stroke_width()
837
838    def get_text_alignment(self):
839        return 'text-anchor="%s"' % (["start", "end", "middle"][self.text_alignment()])
840
841    def get_linecap(self):
842        return 'stroke-linecap="%s"' % self.render_hints().get("linecap", "butt")
843
844    @with_state
845    def draw_line(self, sx, sy, ex, ey):
846        self._svg.write('<line x1="%f" y1="%f" x2="%f" y2="%f" %s %s/>\n' % ((sx, sy, ex, ey) + (self.get_stroke(), self.get_linecap())))
847
848#    @with_state
849#    def draw_lines(self):
850
851    @with_state
852    def draw_rect(self, x, y, w, h):
853        self._svg.write('<rect x="%f" y="%f" width="%f" height="%f" %s %s/>\n' % ((x, y, w, h) + (self.get_fill(),) + (self.get_stroke(),)))
854
855    @with_state
856    def draw_polygon(self, vertices, **kwargs):
857        path = "M %f %f L " % vertices[0]
858        path += " L ".join("%f %f" % vert for vert in vertices[1:])
859        path += " z"
860        self._svg.write('<path d="%s" %s/>' % ((path,) + (self.get_stroke(),)))
861
862    @with_state
863    def draw_text(self, x, y, text):
864        self._svg.write('<text x="%f" y="%f" font-family="%s" font-size="%f" %s>%s</text>\n' % ((x, y) + self.font() + (self.get_text_alignment(), text)))
865
866    def translate(self, x, y):
867        self._svg.write('<g transform="translate(%f,%f)">\n' % (x, y))
868        self.transform_count_stack[-1] = self.transform_count_stack[-1] + 1
869
870    def rotate(self, angle):
871        self._svg.write('<g transform="rotate(%f)">\n' % angle)
872        self.transform_count_stack[-1] = self.transform_count_stack[-1] + 1
873
874    def scale(self, sx, sy):
875        self._svg.write('<g transform="scale(%f,%f)">\n' % (sx, sy))
876        self.transform_count_stack[-1] = self.transform_count_stack[-1] + 1
877
878    def skew(self, sx, sy):
879        self._svg.write('<g transform="skewX(%f)">' % sx)
880        self._svg.write('<g transform="skewY(%f)">' % sy)
881        self.transform_count_stack[-1] = self.transform_count_stack[-1] + 2
882
883    def save_render_state(self):
884        Renderer.save_render_state(self)
885        self.transform_count_stack.append(0)
886
887    def restore_render_state(self):
888        Renderer.restore_render_state(self)
889        count = self.transform_count_stack.pop(-1)
890        self._svg.write('</g>\n' * count)
891
892    def save(self, filename):
893        open(filename, "wb").write(self.SVG_HEADER % (self.height, self.width, self._defs.getvalue(), self._svg.getvalue()))
894
895class CairoRenderer(Renderer):
896    def __init__(self, width, height):
897        Renderer.__init__(self, width, height)
898
Note: See TracBrowser for help on using the repository browser.