Пример #1
0
class SVGHandler(saxlib.HandlerBase):

    dispatch_start = {
        'svg': 'initsvg',
        'g': 'begin_group',
        'circle': 'circle',
        'ellipse': 'ellipse',
        'rect': 'rect',
        'polyline': 'polyline',
        'polygon': 'polygon',
        'path': 'begin_path',
        'text': 'begin_text',
        'image': 'image',
        'data': 'data',
        'use': 'use',
        'defs': 'begin_defs',
    }
    dispatch_end = {
        'g': 'end_group',
        'path': 'end_path',
        'defs': 'end_defs',
        'text': 'end_text'
    }

    def __init__(self, loader):
        self.loader = loader
        self.trafo = self.basetrafo = Trafo()
        self.state_stack = ()
        self.style = loader.style.Copy()
        self.style.line_pattern = EmptyPattern
        self.style.fill_pattern = SolidPattern(StandardColors.black)
        self.current_text = ""
        self.style.font = GetFont("Times-Roman")
        self.style.font_size = 12
        self.halign = text.ALIGN_LEFT
        self.named_objects = {}
        self.in_defs = 0
        self.paths = None
        self.path = None
        self.depth = 0
        self.indent = '    '

    def _print(self, *args):
        return
        if args:
            print self.depth * self.indent + args[0],
        for s in args[1:]:
            print s,
        print

    def parse_transform(self, trafo_string):
        trafo = self.trafo
        #print trafo
        trafo_string = as_latin1(trafo_string)
        while trafo_string:
            #print trafo_string
            match = rx_trafo.match(trafo_string)
            if match:
                function = match.group(1)
                args = string.translate(match.group(2), commatospace)
                args = map(float, split(args))
                trafo_string = trafo_string[match.end(0):]
                if function == 'matrix':
                    trafo = trafo(apply(Trafo, tuple(args)))
                elif function == 'scale':
                    trafo = trafo(Scale(args[0]))
                elif function == 'translate':
                    dx, dy = args
                    trafo = trafo(Translation(dx, dy))
                elif function == 'rotate':
                    trafo = trafo(Rotation(args[0] * degrees))
                elif function == 'skewX':
                    trafo = trafo(Trafo(1, 0, tan(args[0] * degrees), 1, 0, 0))
                elif function == 'skewY':
                    trafo = trafo(Trafo(1, tan(args[0] * degrees), 0, 1, 0, 0))
            else:
                trafo_string = ''
        #print trafo
        self.trafo = trafo

    def startElement(self, name, attrs):
        self._print('(', name)
        for key, value in attrs.items():
            self._print('  -', key, ` value `)
        self.depth = self.depth + 1
        self.push_state()
        if attrs.has_key('transform'):
            self.parse_transform(attrs['transform'])
        self._print("applied transormation", self.trafo)
        method = self.dispatch_start.get(name)
        if method is not None:
            getattr(self, method)(attrs)

    def endElement(self, name):
        self.depth = self.depth - 1
        self._print(')', name)
        method = self.dispatch_end.get(name)
        if method is not None:
            getattr(self, method)()
        self.pop_state()

    def characters(self, ch, start, length):
        self.current_text = self.current_text + as_latin1(ch)

    def error(self, exception):
        print 'error', exception

    def fatalError(self, exception):
        print 'fatalError', exception

    def warning(self, exception):
        print 'warning', exception

    def initsvg(self, attrs):
        width = self.user_length(attrs.get('width', '100%'))
        height = self.user_length(attrs.get('height', '100%'))
        self._print('initsvg', width, height)
        self.trafo = Trafo(1, 0, 0, -1, 0, height)
        self.basetrafo = self.trafo
        # evaluate viewBox
        # FIXME: Handle preserveAspectRatio as well
        viewbox = attrs.get("viewBox", "")
        if viewbox:
            vx, vy, vwidth, vheight = map(float, split(viewbox))
            t = Scale(width / vwidth, height / vheight)
            t = t(Translation(-vx, -vy))
            self.trafo = self.trafo(t)
        self._print("basetrafo", self.basetrafo)

    def parse_style(self, style):
        parts = filter(None, map(strip, split(style, ';')))
        for part in parts:
            key, val = map(strip, split(part, ':', 1))
            self._print('style', key, val)

            # only try to parse the value if it's not empty
            if val:
                self.try_add_style(key, val)
            else:
                # FIXME: we should probably print a message or something
                pass

    def try_add_style(self, key, val):
        if key == 'fill':
            if val == 'none':
                self.style.fill_pattern = EmptyPattern
            else:
                color = csscolor(val)
                self._print('fill', color)
                self.style.fill_pattern = SolidPattern(color)
        elif key == 'stroke':
            if val == 'none':
                self.style.line_pattern = EmptyPattern
            else:
                color = csscolor(val)
                self._print('stroke', color)
                self.style.line_pattern = SolidPattern(color)
        elif key == 'stroke-width':
            width = self.user_length(val)
            # Multiply the width with a value taken from the
            # transformation matrix because so far transforming an
            # object in Sketch does not affect the stroke width in any
            # way. Thus we have to do that explicitly here.
            # FIXME: using m11 is not really the best approach but in
            # many cases better than using the width as is.
            width = self.trafo.m11 * width
            self._print('width', width)
            self.style.line_width = width
        elif key == 'stroke-linejoin':
            self.style.line_join = join[val]
        elif key == 'stroke-linecap':
            self.style.line_cap = cap[val]
        elif key == 'font-family':
            try:
                # convert val to 8bit string.
                self.style.font = GetFont(str(val))
            except UnicodeError:
                # If it's not ASCII we probably won't have the font, so
                # use the default one.
                # FIXME: Give a warning
                pass
        elif key == 'font-size':
            self.style.font_size = self.user_length(val)
            ####self.style.font_size = float(val)
        elif key == 'text-anchor':
            if val == 'start':
                self.halign = text.ALIGN_LEFT
            elif val == 'middle':
                self.halign = text.ALIGN_CENTER
            elif val == 'end':
                self.halign = text.ALIGN_RIGHT

    def set_loader_style(self, allow_font=0):
        # Copy self.style to loader.
        # If allow_font is false (the default) do not copy the font
        # properties.
        property_types = properties.property_types
        style = self.style.Copy()
        if not allow_font:
            for name in style.__dict__.keys():
                if property_types.get(name) == properties.FontProperty:
                    delattr(style, name)
        self.loader.style = style

    def push_state(self):
        self.state_stack = self.style, self.trafo, self.state_stack
        self.style = self.style.Copy()

    def pop_state(self):
        self.style, self.trafo, self.state_stack = self.state_stack

    def user_length(self, str):
        # interpret string as a length and return the appropriate value
        # user coordinates
        str = strip(str)
        factor = factors.get(str[-2:])
        if factor is not None:
            str = str[:-2]
        elif str[-1] == '%':
            # FIXME: this case depends on the width/height attrs of the
            # SVG element
            str = str[:-1]
            factor = 1.0
        else:
            factor = 1.0
        return float(str) * factor

    def user_point(self, x, y):
        # Return the point described by the SVG coordinates x and y as
        # an SKPoint object in user coordinates. x and y are expected to
        # be strings.
        x = strip(x)
        y = strip(y)

        # extract the units from the coordinate values if any and
        # determine the appropriate factor to convert those units to
        # user space units.
        xunit = x[-2:]
        factor = factors.get(xunit)
        if factor is not None:
            x = x[:-2]
        elif x[-1] == '%':
            # XXX this is wrong
            x = x[:-1]
            xunit = '%'
            factor = 1
        else:
            xunit = ''
            factor = 1.0
        x = float(x) * factor

        yunit = y[-2:]
        factor = factors.get(yunit)
        if factor is not None:
            y = y[:-2]
        elif y[-1] == '%':
            y = y[:-1]
            yunit = '%'
            factor = 1.0
        else:
            yunit = ''
            factor = 1.0
        y = float(y) * factor

        return Point(x, y)

    def point(self, x, y, relative=0):
        # Return the point described by the SVG coordinates x and y as
        # an SKPoint object in absolute, i.e. document coordinates. x
        # and y are expected to be strings. If relative is true, they're
        # relative coordinates.
        x, y = self.user_point(x, y)

        if relative:
            p = self.trafo.DTransform(x, y)
        else:
            p = self.trafo(x, y)
        return p

    def circle(self, attrs):
        if self.in_defs:
            id = attrs.get('id', '')
            if id:
                self.named_objects[id] = ('object', 'circle', attrs)
            return
        if attrs.has_key('cx'):
            x = attrs['cx']
        else:
            x = '0'
        if attrs.has_key('cy'):
            y = attrs['cy']
        else:
            y = '0'
        x, y = self.point(x, y)
        r = self.point(attrs['r'], '0', relative=1).x
        t = Trafo(r, 0, 0, r, x, y)
        self._print('circle', t)
        style = attrs.get('style', '')
        if style:
            self.parse_style(style)
        self.set_loader_style()
        apply(self.loader.ellipse, t.coeff())

    def ellipse(self, attrs):
        if self.in_defs:
            id = attrs.get('id', '')
            if id:
                self.named_objects[id] = ('object', 'ellipse', attrs)
            return
        if attrs.has_key('cx'):
            x = attrs['cx']
        else:
            x = '0'
        if attrs.has_key('cy'):
            y = attrs['cy']
        else:
            y = '0'
        x, y = self.point(x, y)
        rx, ry = self.point(attrs['rx'], attrs['ry'], relative=1)
        t = Trafo(rx, 0, 0, ry, x, y)
        self._print('ellipse', t)
        style = attrs.get('style', '')
        if style:
            self.parse_style(style)
        self.set_loader_style()
        apply(self.loader.ellipse, t.coeff())

    def rect(self, attrs):
        #print 'rect', attrs.map
        if self.in_defs:
            id = attrs.get('id', '')
            if id:
                self.named_objects[id] = ('object', 'rect', attrs)
            return
        if attrs.has_key('x'):
            x = attrs['x']
        else:
            x = '0'
        if attrs.has_key('y'):
            y = attrs['y']
        else:
            y = '0'
        x, y = self.point(x, y)
        wx, wy = self.point(attrs['width'], "0", relative=1)
        hx, hy = self.point("0", attrs['height'], relative=1)
        t = Trafo(wx, wy, hx, hy, x, y)
        self._print('rect', t)
        style = attrs.get('style', '')
        if style:
            self.parse_style(style)
        self.set_loader_style()
        apply(self.loader.rectangle, t.coeff())

    def polyline(self, attrs):
        if self.in_defs:
            id = attrs.get('id', '')
            if id:
                self.named_objects[id] = ('object', 'polyline', attrs)
            return
        points = as_latin1(attrs['points'])
        points = string.translate(points, commatospace)
        points = split(points)
        path = CreatePath()
        point = self.point
        for i in range(0, len(points), 2):
            path.AppendLine(point(points[i], points[i + 1]))
        style = attrs.get('style', '')
        if style:
            self.parse_style(style)
        self.set_loader_style()
        self.loader.bezier(paths=(path, ))

    def polygon(self, attrs):
        if self.in_defs:
            id = attrs.get('id', '')
            if id:
                self.named_objects[id] = ('object', 'polygon', attrs)
            return
        points = as_latin1(attrs['points'])
        points = string.translate(points, commatospace)
        points = split(points)
        path = CreatePath()
        point = self.point
        for i in range(0, len(points), 2):
            path.AppendLine(point(points[i], points[i + 1]))
        path.AppendLine(path.Node(0))
        path.ClosePath()
        style = attrs.get('style', '')
        if style:
            self.parse_style(style)
        self.set_loader_style()
        self.loader.bezier(paths=(path, ))

    def parse_path(self, str):
        paths = self.paths
        path = self.path
        trafo = self.trafo
        str = strip(string.translate(as_latin1(str), commatospace))
        last_quad = None
        last_cmd = cmd = None
        f13 = 1.0 / 3.0
        f23 = 2.0 / 3.0
        #print '*', str
        while 1:
            match = rx_command.match(str)
            #print match
            if match:
                last_cmd = cmd
                cmd = str[0]
                str = str[match.end():]
                #print '*', str
                points = match.group(1)
                #print '**', points
                if points:
                    # use tokenize_line to parse the arguments so that
                    # we deal with signed numbers following another
                    # number without intervening whitespace other
                    # characters properls.
                    # FIXME: tokenize_line works but is not the best way
                    # to do it because it accepts input that wouldn't be
                    # valid here.
                    points = filter(operator.isNumberType,
                                    skread.tokenize_line(points))
                #print cmd, points
                if cmd in 'mM':
                    path = CreatePath()
                    paths.append(path)
                    if cmd == 'M' or len(paths) == 1:
                        path.AppendLine(trafo(points[0], points[1]))
                    else:
                        p = trafo.DTransform(points[0], points[1])
                        path.AppendLine(paths[-2].Node(-1) + p)
                    if len(points) > 2:
                        if cmd == 'm':
                            for i in range(2, len(points), 2):
                                p = trafo.DTransform(points[i], points[i + 1])
                                path.AppendLine(path.Node(-1) + p)
                        else:
                            for i in range(2, len(points), 2):
                                path.AppendLine(trafo(points[i],
                                                      points[i + 1]))
                elif cmd == 'l':
                    for i in range(0, len(points), 2):
                        p = trafo.DTransform(points[i], points[i + 1])
                        path.AppendLine(path.Node(-1) + p)
                elif cmd == 'L':
                    for i in range(0, len(points), 2):
                        path.AppendLine(trafo(points[i], points[i + 1]))
                elif cmd == 'H':
                    for num in points:
                        path.AppendLine(Point(num, path.Node(-1).y))
                elif cmd == 'h':
                    for num in points:
                        x, y = path.Node(-1)
                        dx, dy = trafo.DTransform(num, 0)
                        path.AppendLine(Point(x + dx, y + dy))
                elif cmd == 'V':
                    for num in points:
                        path.AppendLine(Point(path.Node(-1).x, num))
                elif cmd == 'v':
                    for num in points:
                        x, y = path.Node(-1)
                        dx, dy = trafo.DTransform(0, num)
                        path.AppendLine(Point(x + dx, y + dy))
                elif cmd == 'C':
                    if len(points) % 6 != 0:
                        self.loader.add_message("number of parameters of 'C'"\
                                                "must be multiple of 6")
                    else:
                        for i in range(0, len(points), 6):
                            p1 = trafo(points[i], points[i + 1])
                            p2 = trafo(points[i + 2], points[i + 3])
                            p3 = trafo(points[i + 4], points[i + 5])
                            path.AppendBezier(p1, p2, p3)
                elif cmd == 'c':
                    if len(points) % 6 != 0:
                        self.loader.add_message("number of parameters of 'c'"\
                                                "must be multiple of 6")
                    else:
                        for i in range(0, len(points), 6):
                            p = path.Node(-1)
                            p1 = p + trafo.DTransform(points[i], points[i + 1])
                            p2 = p + trafo.DTransform(points[i + 2],
                                                      points[i + 3])
                            p3 = p + trafo.DTransform(points[i + 4],
                                                      points[i + 5])
                            path.AppendBezier(p1, p2, p3)
                elif cmd == 'S':
                    if len(points) % 4 != 0:
                        self.loader.add_message("number of parameters of 'S'"\
                                                "must be multiple of 4")
                    else:
                        for i in range(0, len(points), 4):
                            type, controls, p, cont = path.Segment(-1)
                            if type == Bezier:
                                q = controls[1]
                            else:
                                q = p
                            p1 = 2 * p - q
                            p2 = trafo(points[i], points[i + 1])
                            p3 = trafo(points[i + 2], points[i + 3])
                            path.AppendBezier(p1, p2, p3)
                elif cmd == 's':
                    if len(points) % 4 != 0:
                        self.loader.add_message("number of parameters of 's'"\
                                                "must be multiple of 4")
                    else:
                        for i in range(0, len(points), 4):
                            type, controls, p, cont = path.Segment(-1)
                            if type == Bezier:
                                q = controls[1]
                            else:
                                q = p
                            p1 = 2 * p - q
                            p2 = p + trafo.DTransform(points[i], points[i + 1])
                            p3 = p + trafo.DTransform(points[i + 2],
                                                      points[i + 3])
                            path.AppendBezier(p1, p2, p3)
                elif cmd == 'Q':
                    if len(points) % 4 != 0:
                        self.loader.add_message("number of parameters of 'Q'"\
                                                "must be multiple of 4")
                    else:
                        for i in range(0, len(points), 4):
                            q = trafo(points[i], points[i + 1])
                            p3 = trafo(points[i + 2], points[i + 3])
                            p1 = f13 * path.Node(-1) + f23 * q
                            p2 = f13 * p3 + f23 * q
                            path.AppendBezier(p1, p2, p3)
                            last_quad = q
                elif cmd == 'q':
                    if len(points) % 4 != 0:
                        self.loader.add_message("number of parameters of 'q'"\
                                                "must be multiple of 4")
                    else:
                        for i in range(0, len(points), 4):
                            p = path.Node(-1)
                            q = p + trafo.DTransform(points[i], points[i + 1])
                            p3 = p + trafo.DTransform(points[i + 2],
                                                      points[i + 3])
                            p1 = f13 * p + f23 * q
                            p2 = f13 * p3 + f23 * q
                            path.AppendBezier(p1, p2, p3)
                            last_quad = q
                elif cmd == 'T':
                    if len(points) % 2 != 0:
                        self.loader.add_message("number of parameters of 'T'"\
                                                "must be multiple of 4")
                    else:
                        if last_cmd not in 'QqTt' or last_quad is None:
                            last_quad = path.Node(-1)
                        for i in range(0, len(points), 2):
                            p = path.Node(-1)
                            q = 2 * p - last_quad
                            p3 = trafo(points[i], points[i + 1])
                            p1 = f13 * p + f23 * q
                            p2 = f13 * p3 + f23 * q
                            path.AppendBezier(p1, p2, p3)
                            last_quad = q
                elif cmd == 't':
                    if len(points) % 2 != 0:
                        self.loader.add_message("number of parameters of 't'"\
                                                "must be multiple of 4")
                    else:
                        if last_cmd not in 'QqTt' or last_quad is None:
                            last_quad = path.Node(-1)
                        for i in range(0, len(points), 2):
                            p = path.Node(-1)
                            q = 2 * p - last_quad
                            p3 = p + trafo.DTransform(points[i], points[i + 1])
                            p1 = f13 * p + f23 * q
                            p2 = f13 * p3 + f23 * q
                            path.AppendBezier(p1, p2, p3)
                            last_quad = q

                elif cmd in 'zZ':
                    path.AppendLine(path.Node(0))
                    path.ClosePath()
            else:
                break
        self.path = path

    def begin_path(self, attrs):
        if self.in_defs:
            id = attrs.get('id', '')
            if id:
                self.named_objects[id] = ('object', 'path', attrs)
            return
        self.paths = []
        self.path = None
        self.parse_path(attrs['d'])
        style = attrs.get('style', '')
        if style:
            self.parse_style(style)
        self.set_loader_style()

    def end_path(self):
        if self.in_defs:
            return
        self.loader.bezier(paths=tuple(self.paths))
        self.paths = None

    def image(self, attrs):
        if self.in_defs:
            id = attrs.get('id', '')
            if id:
                self.named_objects[id] = ('object', 'image', attrs)
            return
        href = as_latin1(attrs['xlink:href'])
        image = load_image(os.path.join(self.loader.directory, href)).image
        if attrs.has_key('x'):
            x = attrs['x']
        else:
            x = '0'
        if attrs.has_key('y'):
            y = attrs['y']
        else:
            y = '0'
        x, y = self.user_point(x, y)

        width = self.user_length(attrs['width'])
        scalex = width / image.size[0]

        height = self.user_length(attrs['height'])
        scaley = -height / image.size[1]

        style = attrs.get('style', '')
        if style:
            self.parse_style(style)
        self.set_loader_style()
        t = self.trafo(Trafo(scalex, 0, 0, scaley, x, y + height))
        self._print('image', t)
        self.loader.image(image, t)

    def begin_text(self, attrs):
        if self.in_defs:
            id = attrs.get('id', '')
            if id:
                self.named_objects[id] = ('object', 'text', attrs)
            return

        # parse the presentation attributes if any.
        # FIXME: this has to be implemented for the other elements that
        # can have presentation attributes as well.
        for key, value in attrs.items():
            self.try_add_style(key, value)

        if attrs.has_key('x'):
            x = attrs['x']
        else:
            x = '0'
        if attrs.has_key('y'):
            y = attrs['y']
        else:
            y = '0'
        x, y = self.user_point(x, y)
        self.text_trafo = self.trafo(Trafo(1, 0, 0, -1, x, y))
        self._print('text', self.text_trafo)
        style = attrs.get('style', '')
        if style:
            self.parse_style(style)
        self.set_loader_style(allow_font=1)
        self.current_text = ''

    def end_text(self):
        self.loader.simple_text(strip(self.current_text),
                                self.text_trafo,
                                halign=self.halign)

    def data(self, attrs):
        pass

    def begin_group(self, attrs):
        style = attrs.get('style', '')
        if style:
            self.parse_style(style)
        self.loader.begin_group()

    def end_group(self):
        try:
            self.loader.end_group()
        except EmptyCompositeError:
            pass

    def use(self, attrs):
        #print 'use', attrs.map
        if attrs.has_key('xlink:href'):
            name = attrs['xlink:href']
        else:
            name = attrs.get('href', '<none>')
        if name:
            data = self.named_objects.get(name[1:])
            if data[0] == 'object':
                if attrs.has_key('style'):
                    self.parse_style(attrs['style'])
                self.startElement(data[1], data[2])
                self.endElement(data[1])

    def begin_defs(self, attrs):
        self.in_defs = 1

    def end_defs(self):
        self.in_defs = 0
Пример #2
0
class XFigLoader(SimplifiedLoader):

    format_name = format_name

    functions = [('define_color', 0), ('read_ellipse', 1),
                 ('read_polyline', 2), ('read_spline', 3), ('read_text', 4),
                 ('read_arc', 5), ('begin_compound', 6), ('end_compound', -6)]

    def __init__(self, file, filename, match):
        SimplifiedLoader.__init__(self, file, filename, match)
        self.layout = None
        self.format_version = atof(match.group('version'))
        self.trafo = Trafo(1.0, 0.0, 0.0, -1.0, 0.0, 800)
        self.colors = std_colors + [StandardColors.black] * 512
        self.depths = {}  # map object ids to depth
        self.guess_cont()

    def readline(self):
        line = SimplifiedLoader.readline(self)
        while line[:1] == '#':
            line = SimplifiedLoader.readline(self)
        return line

    def get_compiled(self):
        funclist = {}
        for name, rx in self.functions:
            funclist[rx] = getattr(self, name)
        return funclist

    def set_depth(self, depth):
        self.depths[id(self.object)] = -depth

    def define_color(self, line):
        idx, color = split(line, None, 1)
        self.colors[atoi(idx)] = XRGBColor(color)

    def get_pattern(self, color, style=None):
        if style == -1:
            return EmptyPattern
        rgb = self.colors[color]
        if style is not None:
            if color in (BLACK, DEFAULT_COLOR):
                if style > 0 and style <= 20:
                    rgb = Blend(self.colors[WHITE], rgb, (20 - style) / 20.0)
                elif style == 0:
                    rgb = self.colors[WHITE]
            else:
                if style >= 0 and style < 20:
                    rgb = Blend(self.colors[BLACK], rgb, (20 - style) / 20.0)
                elif style > 20 and style <= 40:
                    rgb = Blend(self.colors[WHITE], rgb, (style - 20) / 20.0)
        return SolidPattern(rgb)

    def line(self, color, width, join, cap, style=0, style_val=0):
        if width:
            val = style_val / width
            width = width * 72.0 / 80.0
            pattern = self.get_pattern(color)
            dashes = ()
            if style == 1:
                # dashed
                dashes = (val, val)
            elif style == 2:
                # dotted
                dashes = (1 / width, val)
            elif style == 3:
                # dash-dot
                dashes = (val, 0.5 * val, 1 / width, 0.5 * val)
            elif style == 4:
                # dash-dot-dot
                dashes = (val, 0.45 * val, 1 / width, 0.333 * val, 1 / width,
                          0.45 * val)
            elif style == 5:
                # dash-dot-dot-dot
                dashes = (val, 0.4 * val, 1 / width, 0.333 * val, 1 / width,
                          0.333 * val, 1 / width, 0.4 * val)
            try:
                self.set_properties(line_pattern=pattern,
                                    line_width=width,
                                    line_join=xfig_join[join],
                                    line_cap=xfig_cap[cap],
                                    line_dashes=dashes)
            except:
                raise SketchLoadError("can't assign line style: %s:%s" %
                                      sys.exc_info()[:2])
        else:
            self.empty_line()

    def fill(self, color, style):
        pattern = self.get_pattern(color, style)
        try:
            self.set_properties(fill_pattern=pattern)
        except:
            raise SketchLoadError("can't assign fill style: %s:%s" %
                                  sys.exc_info()[:2])

    def font(self, font, size, flags):
        if flags & 4:
            # A PostScript font
            name = psfonts[font]
        else:
            # A TeX font. map to psfont
            name = texfonts[font]
            self.add_message(
                _("PostScript font `%(ps)s' substituted for "
                  "TeX-font `%(tex)s'") % {
                      'ps': name,
                      'tex': tex_font_names[font]
                  })

        self.set_properties(font=GetFont(name), font_size=size)

    def read_tokens(self, num):
        # read NUM tokens from the input file. return an empty list if
        # eof met before num items are read
        readline = self.readline
        tokenize = skread.tokenize_line
        tokens = []
        while len(tokens) < num:
            line = readline()
            if not line:
                return []
            tokens = tokens + tokenize(line)
        return tokens

    def read_arc(self, line):
        readline = self.readline
        tokenize = skread.tokenize_line
        args = tokenize(line)
        if len(args) != 21:
            raise SketchLoadError('Invalid Arc specification')
        sub_type, line_style, thickness, pen_color, fill_color, depth, \
                pen_style, area_fill, style, cap, direction, \
                forward_arrow, backward_arrow, \
                cx, cy, x1, y1, x2, y2, x3, y3 = args
        self.fill(fill_color, area_fill)
        self.line(pen_color, thickness, const.JoinMiter, cap, line_style,
                  style)

        if forward_arrow: readline()  # XXX: implement this
        if backward_arrow: readline()  # XXX: implement this

        trafo = self.trafo
        center = trafo(cx, cy)
        start = trafo(x1, y1)
        end = trafo(x3, y3)
        radius = abs(start - center)
        start_angle = atan2(start.y - center.y, start.x - center.x)
        end_angle = atan2(end.y - center.y, end.x - center.x)
        if direction == 0:
            start_angle, end_angle = end_angle, start_angle
        if sub_type == 1:
            sub_type = const.ArcArc
        else:
            sub_type = const.ArcPieSlice
        self.ellipse(radius, 0, 0, radius, center.x, center.y, start_angle,
                     end_angle, sub_type)
        self.set_depth(depth)

    def read_ellipse(self, line):
        readline = self.readline
        tokenize = skread.tokenize_line
        args = tokenize(line)
        if len(args) != 19:
            raise SketchLoadError('Invalid Ellipse specification')
        sub_type, line_style, thickness, pen_color, fill_color, depth, \
                pen_style, area_fill, style, direction, angle, \
                cx, cy, rx, ry, sx, sy, ex, ey = args
        self.fill(fill_color, area_fill)
        self.line(pen_color, thickness, const.JoinMiter, const.CapButt,
                  line_style, style)

        center = self.trafo(cx, cy)
        radius = self.trafo.DTransform(rx, ry)
        trafo = Trafo(radius.x, 0, 0, radius.y)
        trafo = Rotation(angle)(trafo)
        trafo = Translation(center)(trafo)
        apply(self.ellipse, trafo.coeff())
        self.set_depth(depth)

    def read_polyline(self, line):
        readline = self.readline
        tokenize = skread.tokenize_line
        args = tokenize(line)
        if len(args) != 15:
            raise SketchLoadError('Invalid PolyLine specification')
        sub_type, line_style, thickness, pen_color, fill_color, depth, \
                pen_style, area_fill, style, join, cap, \
                radius, forward_arrow, backward_arrow, npoints = args
        self.fill(fill_color, area_fill)
        self.line(pen_color, thickness, join, cap, line_style, style)

        if forward_arrow: readline()  # XXX: implement this
        if backward_arrow: readline()  # XXX: implement this
        if sub_type == 5: readline()  # imported picture

        path = CreatePath()
        ncoords = npoints * 2
        pts = self.read_tokens(ncoords)
        if not pts:
            raise SketchLoadError('Missing points for polyline')
        if len(pts) > ncoords:
            del pts[ncoords:]
        map(path.AppendLine, coords_to_points(pts, self.trafo))
        if sub_type in (2, 3, 4):
            path.load_close(1)
        self.bezier(paths=path)
        self.set_depth(depth)

    def read_spline(self, line):
        readline = self.readline
        tokenize = skread.tokenize_line
        args = tokenize(line)
        if len(args) != 13:
            raise SketchLoadError('Invalid Spline specification')
        sub_type, line_style, thickness, pen_color, fill_color, depth, \
                pen_style, area_fill, style, cap, \
                forward_arrow, backward_arrow, npoints = args
        closed = sub_type & 1
        if forward_arrow: readline()
        if backward_arrow: readline()

        # in 3.2 all splines are stored as x-splines...
        if self.format_version == 3.2:
            if sub_type in (0, 2):
                sub_type = 4
            else:
                sub_type = 5

        self.fill(fill_color, area_fill)
        self.line(pen_color, thickness, 0, cap, line_style, style)

        ncoords = npoints * 2
        pts = self.read_tokens(ncoords)
        if not pts:
            raise SketchLoadError('Missing points for spline')
        if len(pts) > ncoords:
            del pts[ncoords:]
        pts = coords_to_points(pts, self.trafo)

        path = CreatePath()
        if sub_type in (2, 3):
            # interpolated spline, read 2 control points for each node
            ncontrols = 4 * npoints
            controls = self.read_tokens(ncontrols)
            if not controls:
                raise SketchLoadError('Missing control points for spline')
            if len(controls) > ncontrols:
                del controls[ncontrols:]
            controls = coords_to_points(controls[2:-2], self.trafo)
            path.AppendLine(pts[0])
            ncontrols = 2 * (npoints - 1)
            controls = [controls] * (npoints - 1)
            map(path.AppendBezier,
                map(getitem, controls, range(0, ncontrols, 2)),
                map(getitem, controls, range(1, ncontrols, 2)), pts[1:])
        elif sub_type in (0, 1):
            # approximated spline
            f13 = 1.0 / 3.0
            f23 = 2.0 / 3.0
            curve = path.AppendBezier
            straight = path.AppendLine
            last = pts[0]
            cur = pts[1]
            start = node = (last + cur) / 2
            if closed:
                straight(node)
            else:
                straight(last)
                straight(node)
            last = cur
            for cur in pts[2:]:
                c1 = f13 * node + f23 * last
                node = (last + cur) / 2
                c2 = f13 * node + f23 * last
                curve(c1, c2, node)
                last = cur
            if closed:
                curve(f13 * node + f23 * last, f13 * start + f23 * last, start)
            else:
                straight(last)
        elif sub_type in (4, 5):
            # An X-spline. Treat it like a polyline for now.
            # read and discard the control info
            self.read_tokens(npoints)
            self.add_message(_("X-Spline treated as PolyLine"))
            map(path.AppendLine, pts)
            if closed:
                path.AppendLine(path.Node(0))
        if closed:
            path.load_close(1)
        self.bezier(paths=path)
        self.set_depth(depth)

    def read_text(self, line):
        args = tokenize(line, 12)  # don't tokenize the text itself
        if len(args) != 13:  # including the unparsed rest of the line
            raise SketchLoadError('Invalid text specification')
        sub_type, color, depth, pen_style, font, size, angle, flags, \
                height, length, x, y, rest = args
        self.fill(color, None)
        self.font(font, size * 0.9, flags)

        if len(rest) > 2:  #at least a space and a newline
            # read the actual text. This implementation may fail in
            # certain cases!
            string = rest[1:]
            while string[-5:] != '\\001\n':
                line = self.readline()
                if not line:
                    raise SketchLoadError('Premature end of string')
                string = string + line
            globals = {'__builtins__': {}}
            try:
                # using eval here might be a security hole!
                string = eval('"""' + string[:-5] + '"""', globals)
            except:
                string = eval("'''" + string[:-5] + "'''", globals)
        else:
            raise SketchLoadError('Invalid text string')

        trafo = Translation(self.trafo(x, y))(Rotation(angle))
        self.simple_text(string, trafo=trafo, halign=align[sub_type])
        self.set_depth(depth)

    def begin_compound(self, line):
        self.begin_group()

    def end_compound(self, line):
        try:
            self.end_group()
        except EmptyCompositeError:
            pass

    def end_composite(self):
        # sort composite_items by their depth
        items = self.composite_items
        depths = map(self.depths.get, map(id, items), [-10000] * len(items))
        depths = map(None, depths, range(len(items)), items)
        depths.sort()
        self.composite_items = map(getitem, depths, [2] * len(items))
        SimplifiedLoader.end_composite(self)

    def read_header(self):
        format = orientation = None
        if self.format_version >= 3.0:
            line = strip(self.readline())
            if line:
                # portrait/landscape
                if lower(line) == 'landscape':
                    orientation = pagelayout.Landscape
                else:
                    orientation = pagelayout.Portrait
            else:
                raise SketchLoadError('No format specification')
            line = strip(self.readline())
            if line:
                # centering
                line = lower(line)
                if line == 'center' or line == 'flush left':
                    line = lower(strip(self.readline()))
            if not line:
                raise SketchLoadError(
                    'No Center/Flushleft or Units specification')
            if line == 'metric':
                # ignore for now
                pass
            if self.format_version >= 3.2:
                self.readline()  # papersize
                self.readline()  # magnification
                self.readline()  # pages
                self.readline()  # transparent color
        line = strip(self.readline())
        if line:
            try:
                ppi, coord = map(atoi, split(line))
            except:
                raise SketchLoadError('Invalid Resolution specification')
            self.trafo = self.trafo(Scale(72.0 / ppi))

    def Load(self):
        file = self.file
        funclist = self.get_compiled()
        # binding frequently used functions to local variables speeds up
        # the process considerably...
        readline = file.readline
        tokenize = skread.tokenize_line
        self.document()
        self.layer(_("Layer 1"))
        try:
            self.read_header()
            line = self.readline()
            while line:
                tokens = tokenize(line, 1)
                if len(tokens) > 1:
                    function, rest = tokens
                else:
                    function = tokens[0]
                    rest = ''
                if type(function) == type(0):
                    function = funclist.get(function)
                    if function:
                        function(rest)
                line = self.readline()

        except SketchLoadError, value:
            warn_tb(INTERNAL)
            raise SketchLoadError('%d:%s' % (self.lineno, str(value))), None,\
                  sys.exc_traceback
        except: