
from pyx import path, canvas, style, deco, text, color, connector, document, \
    svgwriter
from svg.path import parse_path
from subprocess import call
import tempfile, os
from PIL import Image
from io import BytesIO
from cairosvg import svg2png
import xml.etree.ElementTree as ET
import re

class TextArrow:
    """
    Crée un texte sur un fond de couleur de largeur donnée,
    attaché à une flèche
    Les paramètres du constructeur sont :
    @param xt abscisse du centre du texte
    @param yt ordonnée du centre du texte
    @param wt largeur max. de la boîte de texte
    @param x2 abscisse du bout de la flèche
    @param y2 ordonnée du bout de la flèche
    @param atext un texte (unicode)
    @param height_minus (None par défaut); s'il est défini, les ordonnees
        sont calculées comme height_minus - y
    """
    
    def __init__(self, xt, yt, wt, x2, y2, atext, height_minus=None):
        self.xt = float(xt)
        self.yt = float(yt)
        self.wt = float(wt)
        self.x2 = float(x2)
        self.y2 = float(y2)
        self.atext = atext
        if height_minus is not None:
            height_minus = float(height_minus)
            self.yt = height_minus - self.yt
            self.y2 = height_minus - self.y2
        self.attrs = [text.halign.center, text.vshift.middlezero]
        return

    def to_python(self):
        """
        produit la source python pour créer un clone de l'instance
        avec la possibilité de traduire le texte
        """
        return """\
TextArrow({xt:.1f}, {yt:.1f}, {wt:.1f}, {x2:.1f}, {y2:.1f},
    _('''{atext}'''))\
""".format(**self.__dict__)

    def __str__(self):
        return "« {atext} » : position, largeur = ({xt}, {yt}), {wt} ; pointe vers : ({x2}, {y2})".format(**self.__dict__)
    
    @staticmethod
    def from_SVG(source):
        """
        Crée une liste d'instances de TextArrow étant donné
        un fichier SVG
        @param source un nom de fichier, ou un flux de données
        """
        ns = {
            "inkscape": "http://www.inkscape.org/namespaces/inkscape",
            "sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
            "xlink": "http://www.w3.org/1999/xlink",
            "svg": "http://www.w3.org/2000/svg",
        }
        root = ET.parse(source).getroot()
        result=[]
        for group in root.findall(".//svg:g", ns):
            text = group.find("./svg:text", ns)
            has_text = isinstance(text, ET.Element)
            path = group.find("./svg:path", ns)
            has_path = isinstance(path, ET.Element)
            rect = group.find("./svg:rect", ns)
            has_rect = isinstance(rect, ET.Element)
            if has_text and has_path and has_rect:
                letexte = " ".join([t. text for t in text.findall("./svg:tspan", ns)])
                xt = text.attrib.get('x')
                yt = text.attrib.get('y')
                wt = rect.attrib.get('width')
                lapointe = parse_path(path.attrib.get("d"))
                z = lapointe[-1].end
                x2, y2 = z.real, z.imag
                result.append(TextArrow(xt, yt, wt, x2, y2, letexte, height_minus = root.attrib.get("height")))
        return result
        

def annote(img, *textarrows):
    """
    Annote une image "en place" si c'est une instance de PIL.Image.Image,
    mais accepte aussi un nom de fichier.
    @param img un nom de fichier ou une instance de PIL.Image.Image
    @param textarrows une liste d'instances de TextArrow
    @return une instance de PIL.Image.Image
    """
    if not isinstance(img, Image.Image):
        img = Image.open(img)
    img.convert("RGBA")
    # calcul d'échelle pour que les tailles des annotations et des flèches
    # soient raisonnables par rapport à l'image bitmap
    scale = 18/max(img.width, img.height)
    # LaTex devra accepter du texte unicode
    text.set(engine=text.LatexRunner, texenc='utf-8')
    text.preamble(r'\usepackage{ucs}')
    text.preamble(r'\usepackage[utf8x]{inputenc}')
    frame = path.rect(
        0, 0, img.width*scale, img.height*scale)
    c = canvas.canvas()
    c.draw(frame, []) # rectangle englobant, invisible
    backgroundstyle = [style.linewidth.Thick, color.rgb.blue,
                       deco.filled([color.rgb.white])]
    curvestyle = [color.rgb.blue, deco.earrow.normal, style.linewidth.Thick]
    for t in textarrows:
        # création d'objets à dessiner
        textstyle = [text.parbox(t.wt*scale)] + t.attrs
        text_ = text.text(t.xt*scale, t.yt*scale, t.atext, textstyle)
        background = text_.bbox().enlarged(0.1).path()
        curve = connector.curve(
            text_, text.text(t.x2*scale, t.y2*scale, " "), boxdists=[0.1, 0.1])
        # dessin sur le canevas
        c.stroke(background, backgroundstyle)
        c.insert(text_)
        c.stroke(curve, curvestyle)
    # export et conversion au format PNG lisible par PIL.Image
    SVGbuffer = BytesIO()
    PNGbuffer = BytesIO()
    svgwriter.SVGwriter(document.document([document.page(c)]), SVGbuffer)
    SVGbuffer.seek(0)
    svg2png(
        file_obj = SVGbuffer,
        output_width = img.width,
        output_height = img.height,
        write_to = PNGbuffer)
    PNGbuffer.seek(0)
    # récupération des annotations et surimpression sur l'image
    annotations = Image.open(PNGbuffer, formats=["png"])
    img.paste(annotations, (0,0), annotations)
    return img

def annote_par_svg(PNGimage,SVGfilename):
    """
    crée une instance de PIL.Image.Image, à partir d'un fichier PNG
    et d'un fichier SVG ; ce dernier doit contenir des groupes, chacun
    contenant un texte, un rectangle et une flèche. Chaque groupe forme
    une annotation, et la taille du fichier SVG doit correspondre à celle
    de l'image PNG (en pixels)
    @param PNGimage nom d'un fichier d'image au format PNG, ou instance
           de PIL.Image.Image
    @param SVGfilename nom d'un fichier d'image au format SVG
    @return une instance de PIL.Image.Image
    """
    if type(PNGimage) is str:
        PNGimage = Image.open(PNGimage)
    return annote(PNGimage, *TextArrow.from_SVG(SVGfilename))

def to_python_avec_svg(SVGfilename):
    """
    crée une source en Python qui fait en partie le travail de la fonction
    précédente : la liste TextArrow.from_SVG(SVGfilename)
    @param PNGfilename nom d'un fichier d'image au format PNG
    @param SVGfilename nom d'un fichier d'image au format SVG
    @return une instance de PIL.Image.Image
    """
    tas = TextArrow.from_SVG(SVGfilename)
    return "[\n" + ",\n".join(["  "+ ta.to_python() for ta in tas]) + "\n]"

def demo():
    source = Image.open("content/images/fr-fr/cartemembre4.png")
    img = annote_par_svg(source, "annotations/cartemembre4.svg")
    img.save("/tmp/resultat.png")
    print("L'annotation est ajoutée, dans /tmp/resultat.png")
    print("==== un peu de code source Python ====")
    print(to_python_avec_svg("annotations/cartemembre4.svg"))
    
if __name__ == "__main__":
    demo()
    
