# Sketch - A Python-based interactive drawing program
# Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 by Bernhard Herzog
# Part of the code for Arrow heads and text from Paul Giotta (2002)
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

###Sketch Config
#type = Export
#tk_file_type = ("Scalable Vector Graphics (SVG)", '.svg')
#extensions = '.svg'
format_name = 'SVG'
#unload = 1
###End

from string import join, replace
import re
from math import atan2, hypot, pi

from Sketch import const, Bezier, EmptyPattern, Trafo, Rotation, Translation

def csscolor(color):
    return "#%02x%02x%02x" \
           % (255 * color.red, 255 * color.green, 255 * color.blue)

svg_joins = ('miter', 'round', 'bevel')
svg_caps = (None, 'butt', 'round', 'square')

def escape(data):
    """Escape &, \", ', <, and > in the string data.
    """
    data = replace(data, "&", "&amp;")
    data = replace(data, "<", "&lt;")
    data = replace(data, ">", "&gt;")
    data = replace(data, '"', "&quot;")
    data = replace(data, "'", "&apos;")
    return data


# FIXME: This is the definition of a simple triangular arrowhead It is
# copied directly from the an example in the SVG spec This is a
# prototype only. Do not consider this an acceptable long term solution
arrow_head_def = '''  <defs> 
    <marker id="ArrowEnd" viewBox="0 0 10 10" refX="0" refY="5" 
     markerUnits="strokeWidth" 
     markerWidth="4" 
     markerHeight="3" 
     orient="auto"> 
        <path d="M 0 0 L 10 5 L 0 10 z" /> 
    </marker>
    <marker id="ArrowStart" viewBox="0 0 10 10" refX="10" refY="5" 
     markerUnits="strokeWidth" 
     markerWidth="4" 
     markerHeight="3" 
     orient="auto"> 
        <path d="M 10 0 L 0 5 L 10 10 z" /> 
    </marker> </defs>
'''
  


class SVGSaver:

    def __init__(self, file, filename, document, options):
        self.file = file
        self.filename = filename
        self.document = document
        self.options = options
        self.idcount = 0

    def close(self):
        self.file.close()

    def new_id(self):
        self.idcount = self.idcount + 1
        return self.idcount

    fontMap = {"Times"     : re.compile("Times-Roman.*"),
               "Helvetica" : re.compile("Helvetica.*"),
               "Courier"   : re.compile("Courier.*"),
               }

    def make_style(self, properties, bounding_rect = None):
        style = []
        if properties.line_pattern is not EmptyPattern:
            if properties.line_pattern.is_Solid:
                color = properties.line_pattern.Color()
                style.append("stroke:" + csscolor(color))
            if properties.line_dashes != ():
                #FIXME: This could be much more intelligent, but this
                #quick hack will only produce one style of dashed line
                #for the moment.
                style.append("stroke-dasharray:" + "6,3")
            style.append('stroke-width:' + `properties.line_width`)
            if properties.line_join != const.JoinMiter:
                style.append('stroke-linejoin:'
                             + svg_joins[properties.line_join])
            if properties.line_cap != const.CapButt:
                style.append('stroke-linecap:' + svg_caps[properties.line_cap])

            # FIXME: when arrow heads are implemented properly, change
            # this accordingly
            # FIXME: currently the orientation of the arrow heads is
            # wrong.
            if properties.line_arrow1 <> None:
                style.append('marker-start:url(#ArrowStart)')
            if properties.line_arrow2 <> None:
                style.append('marker-end:url(#ArrowEnd)')
        else:
            style.append("stroke:none")
        if properties.fill_pattern is not EmptyPattern:
            pattern = properties.fill_pattern
            if pattern.is_Solid:
                style.append("fill:" + csscolor(pattern.Color()))
            elif pattern.is_Gradient and bounding_rect:
                if pattern.is_AxialGradient or pattern.is_RadialGradient:
                    gradient_id = self.write_gradient((pattern, bounding_rect),
                                                      style)
                    style.append("fill:url(#%s)" % gradient_id)
                else:
                    style.append("fill:black")
        else:
            style.append("fill:none")

        if properties.font is not None:
            font = properties.font.PostScriptName()
            size = properties.font_size

            for svgfont, pattern in self.fontMap.items():
                if pattern.match(font):
                    font = svgfont
                    break
            style.append("font-family:" + font)
            style.append("font-size:" + str(size))

        return join(style, '; ')

    def write_gradient(self, (pattern, rect), style):
        write = self.file.write
        gradient_id = self.new_id()
        stops = pattern.Gradient().Colors()
        write('<defs>')
        if pattern.is_AxialGradient:
            vx, vy = pattern.Direction()
            angle = atan2(vy, vx) - pi / 2
            center = rect.center()
            rot = Rotation(angle, center)
            left, bottom, right, top = rot(rect)
            height = (top - bottom) * (1.0 - pattern.Border())
            trafo = self.trafo(rot(Translation(center)))
            start = trafo(0, height / 2)
            end = trafo(0, - height / 2)
            write('<linearGradient id="%s" x1="%g" y1="%g" x2="%g" y2="%g"'
                  ' gradientUnits="userSpaceOnUse">\n'
                  % ((gradient_id,) + tuple(start) + tuple(end)))
            tag = 'linearGradient'
        elif pattern.is_RadialGradient:
            cx, cy = pattern.Center()
            cx = cx * rect.right + (1 - cx) * rect.left
            cy = cy * rect.top   + (1 - cy) * rect.bottom
            radius = max(hypot(rect.left - cx, rect.top - cy),
                         hypot(rect.right - cx, rect.top - cy),
                         hypot(rect.right - cx, rect.bottom - cy),
                         hypot(rect.left - cx, rect.bottom - cy))
            radius = radius * (1.0 - pattern.Border())
            cx, cy = self.trafo(cx, cy)
            write('<radialGradient id="%s" cx="%g" cy="%g" r="%g" fx="%g"'
                  ' fy="%g" gradientUnits="userSpaceOnUse">\n'
                  % (gradient_id, cx, cy, radius, cx, cy))
            tag = 'radialGradient'
            stops = stops[:]
            stops.reverse()
            for i in range(len(stops)):
                pos, color = stops[i]
                stops[i] = 1.0 - pos, color
        for pos, color in stops:
            write('<stop offset="%g" style="stop-color:%s"/>\n'
                  % (pos, csscolor(color)))
        write('</%s>\n' % tag)
        write('</defs>')
        return gradient_id

    def PolyBezier(self, paths, properties, bounding_rect):
        style = self.make_style(properties, bounding_rect)
        write = self.file.write
        write('<path style="%s" ' % style)
        data = []
        for path in paths:
            for i in range(path.len):
                type, control, p, cont = path.Segment(i)
                p = self.trafo(p)
                if type == Bezier:
                    p1, p2 = control
                    p1 = self.trafo(p1)
                    p2 = self.trafo(p2)
                    data.append('C %g %g %g %g %g %g' % (p1.x, p1.y,
                                                         p2.x, p2.y,
                                                         p.x, p.y))
                else:
                    if i > 0:
                        data.append('L %g %g' % tuple(p))
                    else:
                        data.append('M %g %g' % tuple(p))
            if path.closed:
                data.append('z')
        write('d="%s"/>\n' % join(data, ''))


    def SimpleText(self, object):
        style = self.make_style(object.Properties(), object.bounding_rect)

        # Char glyphs are inherently upside-down in SVG (compared to
        # sketch) since the y axis points toward the bottom of the page
        # in SVG, but the characters are oriented toward the top of the
        # page. This extra transform inverts them.
        textTrafo = Trafo(1, 0, 0, -1, 0, 0)

        tm = self.trafo(object.Trafo()(textTrafo)).coeff()

        beginText = '<text style="%s" transform="matrix(%g %g %g %g %g %g)">\n'
        endText = '</text >\n'

        self.file.write(beginText %  (( style, ) + tm) )
        self.file.write(escape(object.Text()))
        self.file.write("\n")
        self.file.write(endText)

    def BeginGroup(self):
        self.file.write('<g>\n')

    def EndGroup(self):
        self.file.write('</g>\n')

    def Save(self):
        self.file.write('<?xml version="1.0" encoding="ISO-8859-1" '
                        'standalone="yes"?>\n')

        left, bottom, right, top = self.document.BoundingRect()
        width = right - left
        height = top - bottom
        self.trafo = Trafo(1, 0, 0, -1, -left, top)
        self.file.write('<svg width="%g" height="%g"' % (width, height))
        #self.file.write(' transform="matrix(%g,%g,%g,%g,%g,%g)">\n' % trafo)
        self.file.write('>\n')

        # Put the definition of a simple triangular arrowhead in the file,
        # whether it is used or not
        # FIXME: for a proper solution for arrow heads, we could walk
        # over the object tree, collect all arrow heads actually used
        # and write them out here.
        self.file.write( arrow_head_def )

        for layer in self.document.Layers():
            if not layer.is_SpecialLayer and layer.Printable():
                self.BeginGroup()
                self.save_objects(layer.GetObjects())
                self.EndGroup()
        self.file.write('</svg>')

    def save_objects(self, objects):
        for object in objects:
            if object.is_Compound:
                self.BeginGroup()
                self.save_objects(object.GetObjects())
                self.EndGroup()
            elif object.is_SimpleText:
                self.SimpleText(object)
            #elif object.is_Image:
            #    self.Image(object)
            elif object.is_Bezier or object.is_Rectangle or object.is_Ellipse:
                self.PolyBezier(object.Paths(), object.Properties(),
                                object.bounding_rect)



def save(document, file, filename, options = {}):
    saver = SVGSaver(file, filename, document, options)
    saver.Save()
    saver.close()
