This file is indexed.

/usr/share/pyshared/pychart/svgcanvas.py is in python-pychart 1.39-7.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
#
# Copyright (C) 2000-2005 by Yasushi Saito (yasushi.saito@gmail.com)
#
# Jockey is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any
# later version.
#
# Jockey is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
import sys,string,re,math
from xml.dom.minidom import Document,Comment
import theme
import basecanvas
import version
from scaling import *

# Note we flip all y-coords and negate all angles because SVG's coord
# system is inverted wrt postscript/PDF - note it's not enough to
# scale(1,-1) since that turns text into mirror writing with wrong origin

_comment_p = 0                           # whether comment() writes output

def _svgcolor(color):
    """
    Convert a PyChart color object to an SVG rgb() value.
    See color.py.
    """
    return 'rgb(%d,%d,%d)' % tuple(map(lambda x:int(255*x),
                                       [color.r,color.g,color.b]))

def _parse_style_str(s):
    """
    Take an SVG 'style' attribute string like 'stroke:none;fill:black'
    and parse it into a dictionary like {'stroke' : 'none', 'fill' : 'black'}.
    """
    styledict = {}
    if s :
        # parses L -> R so later keys overwrite earlier ones
        for keyval in s.split(';'):
            l = keyval.strip().split(':')
            if l and len(l) == 2: styledict[l[0].strip()] = l[1].strip()
    return styledict

def _make_style_str(styledict):
    """
    Make an SVG style string from the dictionary. See also _parse_style_str also.
    """
    s = ''
    for key in styledict.keys():
        s += "%s:%s;"%(key, styledict[key])
    return s

def _protect_current_children(elt):
    # If elt is a group, check to see whether there are any non-comment
    # children, and if so, create a new group to hold attributes
    # to avoid affecting previous children.  Return either the current
    # elt or the newly generated group.
    if (elt.nodeName == 'g') :
        for kid in elt.childNodes :
            if kid.nodeType != Comment.nodeType:
                g = elt.ownerDocument.createElement('g')
                g.setAttribute('auto','')
                if _comment_p:
                    g.appendChild(g.ownerDocument.createComment
                                  ('auto-generated group'))
                elt.appendChild(g)
                elt = g
                break
    return elt

class T(basecanvas.T):
    def __init__(self, fname):
        basecanvas.T.__init__(self)
        self.__out_fname = fname
        self.__xmin, self.__xmax, self.__ymin, self.__ymax = 0,0,0,0
        self.__doc = Document()
        self.__doc.appendChild(self.__doc.createComment
             ('Created by PyChart ' + version.version + ' ' + version.copyright))
        self.__svg = self.__doc.createElement('svg') # the svg doc
        self.__doc.appendChild(self.__svg)
        self.__defs = self.__doc.createElement('defs') # for clip paths
        self.__svg.appendChild(self.__defs)
        self.__cur_element = self.__svg
        self.gsave()       # create top-level group for dflt styles
        self.__update_style(font_family = theme.default_font_family,
                            font_size = theme.default_font_size,
                            font_style = 'normal',
                            font_weight = 'normal',
                            font_stretch = 'normal',
                            fill = 'none',
                            stroke = 'rgb(0,0,0)', #SVG dflt none, PS dflt blk
                            stroke_width = theme.default_line_width,
                            stroke_linejoin = 'miter',
                            stroke_linecap = 'butt',
                            stroke_dasharray = 'none')

    def __update_style(self, **addstyledict):
        elt = _protect_current_children(self.__cur_element)

        # fetch the current styles for this node
        my_style_dict = _parse_style_str(elt.getAttribute('style'))

        # concat all ancestor style strings to get default styles for this node
        parent, s = elt.parentNode, ''
        while parent.nodeType != Document.nodeType :
            # prepend parent str so later keys will override earlier ones
            s = parent.getAttribute('style') + s
            parent = parent.parentNode
        default_style_dict = _parse_style_str(s)

        # Do some pre-processing on the caller-supplied add'l styles
        # Convert '_' to '-' so caller can specify style tags as python
        # variable names, eg. stroke_width => stroke-width.
        # Also convert all RHS values to strs
        for key in addstyledict.keys():
            k = re.sub('_','-',key)
            addstyledict[k] = str(addstyledict[key]) # all vals => strs
            if (k != key) : del addstyledict[key]

        for k in addstyledict.keys() :
            if (my_style_dict.has_key(k) or # need to overwrite it
                (not default_style_dict.has_key(k)) or # need to set it
                default_style_dict[k] != addstyledict[k]) : # need to override it
                my_style_dict[k] = addstyledict[k]

        s = _make_style_str(my_style_dict)
        if s : elt.setAttribute('style',s)

        self.__cur_element = elt

    ####################################################################
    # methods below define the pychart backend device API

    # First are a set of methods to start, construct and finalize a path

    def newpath(self):                  # Start a new path
        if (self.__cur_element.nodeName != 'g') :
            raise OverflowError, "No containing group for newpath"
        # Just insert a new 'path' element into the document
        p = self.__doc.createElement('path')
        self.__cur_element.appendChild(p)
        self.__cur_element = p

    # This set of methods add data to an existing path element,
    # simply add to the 'd' (data) attribute of the path elt

    def moveto(self, x, y):             #
        if (self.__cur_element.nodeName != 'path') :
            raise OverflowError, "No path for moveto"
        d = ' '.join([self.__cur_element.getAttribute('d'),'M',`x`,`-y`]).strip()
        self.__cur_element.setAttribute('d', d)
    def lineto(self, x, y):
        if (self.__cur_element.nodeName != 'path') :
            raise OverflowError, "No path for lineto"
        d = ' '.join([self.__cur_element.getAttribute('d'),'L',`x`,`-y`]).strip()
        self.__cur_element.setAttribute('d', d)
    def path_arc(self, x, y, radius, ratio, start_angle, end_angle):
        # mimic PS 'arc' given radius, yr/xr (=eccentricity), start and
        # end angles.  PS arc draws from CP (if exists) to arc start,
        # then draws arc in counterclockwise dir from start to end
        # SVG provides an arc command that draws a segment of an
        # ellipse (but not a full circle) given these args:
        # A xr yr rotate majorArcFlag counterclockwiseFlag xe ye
        # We don't use rotate(=0) and flipped axes => all arcs are clockwise

        if (self.__cur_element.nodeName != 'path') :
            raise OverflowError, "No path for path_arc"

        self.comment('x=%g, y=%g, r=%g, :=%g, %g-%g'
                     % (x,y,radius,ratio,start_angle,end_angle))

        xs = x+radius*math.cos(2*math.pi/360.*start_angle)
        ys = y+ratio*radius*math.sin(2*math.pi/360.*start_angle)
        xe = x+radius*math.cos(2*math.pi/360.*end_angle)
        ye = y+ratio*radius*math.sin(2*math.pi/360.*end_angle)
        if (end_angle < start_angle) :  # make end bigger than start
            while end_angle <= start_angle: # '<=' so 360->0 becomes 360->720
                end_angle += 360
        full_circ = (end_angle - start_angle >= 360) # draw a full circle?

        d = self.__cur_element.getAttribute('d')
        d += ' %s %g %g' % (d and 'L' or 'M',xs,-ys) # draw from CP, if exists
        if (radius > 0) : # skip, eg. 0-radius 'rounded' corners which blowup
            if (full_circ) :
                # If we're drawing a full circle, move to the end coord
                # and draw half a circle to the reflected xe,ye
                d += ' M %g %g A %g %g 0 1 0 %g %g'%(xe,-ye,
                                                     radius,radius*ratio,
                                                     2*x-xe,-(2*y-ye))
            # Draw arc from the CP (either reflected xe,ye for full circle else
            # xs,ys) to the end coord - note with full_circ the
            # 'bigArcFlag' value is moot, with exactly 180deg left to draw
            d += ' A %g %g 0 %d 0 %g %g' % (radius,radius*ratio,
                                            end_angle-start_angle>180,
                                            xe,-ye)
        self.__cur_element.setAttribute('d',d.strip())
    def curveto(self, x1,y1,x2,y2,x3,y3):
        # Equivalent of PostScript's x1 y1 x2 y2 x3 y3 curveto which
        # draws a cubic bezier curve from curr pt to x3,y3 with ctrl points
        # x1,y1, and x2,y2
        # In SVG this is just d='[M x0 y0] C x1 y1 x2 y2 x3 y3'
        #! I can't find an example of this being used to test it
        if (self.__cur_element.nodeNode != 'path') :
            raise OverflowError, "No path for curveto"
        d = ' '.join([self.__cur_element.getAttribute('d'),'C',
                      `x1`,`-y1`,`x2`,`-y2`,`x3`,`-y3`,]).strip()
        self.__cur_element.setAttribute('d', d)
    def closepath(self):                # close back to start of path
        if (self.__cur_element.nodeName != 'path') :
            raise OverflowError, "No path for closepath"
        d = ' '.join([self.__cur_element.getAttribute('d'),'Z']).strip()
        self.__cur_element.setAttribute('d', d)

    # Next we have three methods for finalizing a path element,
    # either fill it, clip to it, or draw it (stroke)
    # canvas.polygon() can generate fill/clip cmds with
    # no corresponding path so just ignore them
    def stroke(self):
        if (self.__cur_element.nodeName != 'path') :
            self.comment('No path - ignoring stroke')
            return
        self.__update_style(fill='none')
        self.__cur_element = self.__cur_element.parentNode
    def fill(self):
        if (self.__cur_element.nodeName != 'path') :
            self.comment('No path - ignoring fill')
            return
        self.__update_style(stroke='none')
        self.__cur_element = self.__cur_element.parentNode
    def clip_sub(self):
        if (self.__cur_element.nodeName != 'path') :
            self.comment('No path - ignoring clip')
            return

        # remove the current path from the tree ...
        p = self.__cur_element
        self.__cur_element=p.parentNode
        self.__cur_element.removeChild(p)

        # ... add it to a clipPath elt in the defs section
        clip = self.__doc.createElement('clipPath')
        clipid = 'clip'+`len(self.__defs.childNodes)`
        clip.setAttribute('id',clipid)
        clip.appendChild(p)
        self.__defs.appendChild(clip)

        # ... update the local style to point to it
        self.__update_style(clip_path = 'url(#%s)'%clipid)

    # The text_xxx routines specify the start/end and contents of text
    def text_begin(self):
        if (self.__cur_element.nodeName != 'g') :
            raise ValueError, "No group for text block"
        t = self.__doc.createElement('text')
        self.__cur_element.appendChild(t)
        self.__cur_element = t
    def text_moveto(self, x, y, angle):
        if (self.__cur_element.nodeName != 'text') :
            raise ValueError, "No text for moveto"
        self.__cur_element.setAttribute('x',`x`)
        self.__cur_element.setAttribute('y',`-y`)
        if (angle) :
            self.__cur_element.setAttribute('transform',
                                        'rotate(%g,%g,%g)' % (-angle,x,-y))
    def text_show(self, font_name, size, color, string):
        if (self.__cur_element.nodeName != 'text') :
            raise ValueError, "No text for show"

        # PyChart constructs a postscript font name, for example:
        #
        # Helvetica Helvetica-Bold Helvetica-Oblique Helvetica-BoldOblique
        # Helvetica-Narrow Times-Roman Times-Italic
        # Symbol Palatino-Roman Bookman-Demi Courier AvantGarde-Book
        #
        # We need to deconstruct this to get the font-family (the
        # piece before the '-'), and other characteristics.
        # Note that 'Courier' seems to correspond to SVGs 'CourierNew'
        # and that the SVG Symbol font is Unicode where the ascii text
        # 'Symbol' doesn't create greek characters like 'Sigma ...' -
        # should really pass a unicode string, or provide translation
        #
        # SVG defines:
        # font-style = normal (aka roman) | italic | oblique
        # font-weight = normal | bold (aka demi?)
        # font-stretch = normal | wider | narrower | ultra-condensed |
        #	extra-condensed | condensed | semi-condensed |
        #	semi-expanded | expanded | extra-expanded | ultra-expanded
        # ('narrow' seems to correspond to 'condensed')
        font_name, modifiers = re.match(r'([^-]*)(-.*)?', font_name).groups()
        if font_name == 'Courier' : font_name = 'CourierNew'
        font_style = font_weight = font_stretch = 'normal'
        if modifiers :
            if re.search('Italic',modifiers) : font_style = 'italic'
            elif re.search('Oblique',modifiers) : font_style = 'oblique'
            if re.search('Bold|Demi',modifiers) : font_weight = 'bold'
            if re.search('Narrow',modifiers) : font_stretch = 'condensed'
        #! translate ascii symbol font chars -> unicode (see www.unicode.org)
        #! http://www.unicode.org/Public/MAPPINGS/VENDORS/ADOBE/symbol.txt
        #! but xml Text element writes unicode chars as '?' to XML file...
        string = re.sub(r'\\([()])',r'\1',string) # unescape brackets
        self.__update_style(fill=_svgcolor(color),
                          stroke='none',
                          font_family=font_name,
                          font_size=size,
                          font_style=font_style,
                          font_weight=font_weight,
                          font_stretch=font_stretch)
        self.__cur_element.appendChild(self.__doc.createTextNode(string.encode('utf-8')))
    def text_end(self):
        if (self.__cur_element.nodeName != 'text') :
            raise ValueError, "No text for close"
        self.__cur_element = self.__cur_element.parentNode


    # Three methods that change the local style of elements
    # If applied to a group, they persist until the next grestore,
    # If applied within a path element, they only affect that path -
    # although this may not in general correspond to (say) PostScript
    # behavior, it appears to correspond to reflect mode of use of this API
    def set_fill_color(self, color):
        self.__update_style(fill=_svgcolor(color))
    def set_stroke_color(self, color):
        self.__update_style(stroke=_svgcolor(color))
    def set_line_style(self, style):  # see line_style.py
        linecap = {0:'butt', 1:'round', 2:'square'}
        linejoin = {0:'miter', 1:'round', 2:'bevel'}
        if style.dash: dash = ','.join(map(str,style.dash))
        else : dash = 'none'
        self.__update_style(stroke_width = style.width,
                          stroke = _svgcolor(style.color),
                          stroke_linecap = linecap[style.cap_style],
                          stroke_linejoin = linejoin[style.join_style],
                          stroke_dasharray = dash)

    # gsave & grestore respectively push & pop a new context to hold
    # new style and transform parameters.  push/pop transformation are
    # similar but explicitly specify a coordinate transform at the
    # same time
    def gsave(self):
        if (self.__cur_element.nodeName not in ['g','svg']) :
            raise ValueError, "No group for gsave"
        g = self.__doc.createElement('g')
        self.__cur_element.appendChild(g)
        self.__cur_element = g
    def grestore(self):
        if (self.__cur_element.nodeName != 'g'):
            raise ValueError, "No group for grestore"
        # first pop off any auto-generated groups (see protectCurrentChildren)
        while (self.__cur_element.hasAttribute('auto')) :
            self.__cur_element.removeAttribute('auto')
            self.__cur_element = self.__cur_element.parentNode
        # then pop off the original caller-generated group
        self.__cur_element = self.__cur_element.parentNode

    def push_transformation(self, baseloc, scale, angle, in_text=0):
        #? in_text arg appears to always be ignored

        # In some cases this gets called after newpath, with
        # corresonding pop_transformation called after the path is
        # finalized so we check specifically for that, and generate
        # an enclosing group to hold the incomplete path element
        # We could add the transform directly to the path element
        # (like we do with line-style etc) but that makes it harder
        # to handle the closing 'pop' and might lead to inconsitency
        # with PostScript if the closing pop doesn't come right after
        # the path element

        elt = self.__cur_element
        if elt.nodeName == 'g':
            elt = None
        elif (elt.nodeName == 'path' and not elt.hasAttribute('d')) :
            g = elt.parentNode
            g.removeChild(elt)
            self.__cur_element = g
        else:
            raise ValueError, "Illegal placement of push_transformation"

        t = ''
        if baseloc :
            t += 'translate(%g,%g) '%(baseloc[0],-baseloc[1])
        if angle :
            t += 'rotate(%g) '%-angle
        if scale :
            t += 'scale(%g,%g) '%tuple(scale)

        self.gsave()
        self.__cur_element.setAttribute('transform',t.strip())
        if elt:                         # elt has incomplete 'path' or None
            self.__cur_element.appendChild(elt)
            self.__cur_element = elt

    def pop_transformation(self, in_text=0): #? in_text unused?
        self.grestore()

    # If verbose, add comments to the output stream (helps debugging)
    def comment(self, string):
        if _comment_p :
            self.__cur_element.appendChild(self.__doc.createComment(string))

    # The verbatim method is currently not supported - presumably with
    # the SVG backend the user would require access to the DOM since
    # we're not directly outputting plain text here
    def verbatim(self, string):
        self.__cur_element.appendChild(self.__doc.createComment('verbatim not implemented: ' + string))

    # The close() method finalizes the SVG document and flattens the
    # DOM document to XML text to the specified file (or stdout)
    def close(self):
        basecanvas.T.close(self)
        self.grestore()           # matching the gsave in __init__
        if (self.__cur_element.nodeName != 'svg') :
            raise ValueError, "Incomplete document at close!"

        # Don't bother to output an empty document - this can happen
        # when we get close()d immediately by theme reinit
        if (len(self.__svg.childNodes[-1].childNodes) == 0) :
            return

        fp, need_close = self.open_output(self.__out_fname)
        bbox = theme.adjust_bounding_box([self.__xmin, self.__ymin,
                                          self.__xmax, self.__ymax])
        self.__svg.setAttribute('viewBox','%g %g %g %g'
                                % (xscale(bbox[0]),
                                   -yscale(bbox[3]),
                                   xscale(bbox[2])-xscale(bbox[0]),
                                   yscale(bbox[3])-yscale(bbox[1])))
        self.__svg.setAttribute('xmlns','http://www.w3.org/2000/svg')
        self.__svg.setAttribute('xmlns:xlink','http://www.w3.org/1999/xlink')

        self.__doc.writexml(fp,'','  ','\n')
        if need_close:
            fp.close()