"""
    etiquettes.views, un module pour SLM
    - rendu des pages web à l'aide de modèles

    Copyright (C) 2023-2024 Georges Khaznadar <georgesk@debian.org>

    This program 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 3 of the License, or
    (at your option) any later version.

    This program 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.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
import re
import io

import labels
from reportlab.graphics import shapes
from reportlab.graphics.barcode import createBarcodeDrawing
from reportlab.lib.colors import toColor
from reportlab.graphics import renderSVG
from datetime import datetime
    
from .version import this_version
from .models import MIN_NO_CAUTION
from manuels.settings import BASE_DIR

import reportlab.rl_config

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import platform
import os.path
reportlab.rl_config.warnOnMissingFontGlyphs = 0

if platform.system() != "Windows":
    pdfmetrics.registerFont(TTFont('Times-Roman',
                                   '/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf'))
    pdfmetrics.registerFont(TTFont('Times-Bold',
                                   '/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf'))
    pdfmetrics.registerFont(TTFont('Times-Italic',
                                   '/usr/share/fonts/truetype/liberation/LiberationSerif-Italic.ttf'))
    pdfmetrics.registerFont(TTFont('Times-BoldItalic',
                                   '/usr/share/fonts/truetype/liberation/LiberationSerif-BoldItalic.ttf'))

    pdfmetrics.registerFont(TTFont('Helvetica',
                                   '/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf'))
    pdfmetrics.registerFont(TTFont('Helvetica-Bold',
                                   '/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf'))
    pdfmetrics.registerFont(TTFont('Helvetica-Italic',
                                   '/usr/share/fonts/truetype/liberation/LiberationSans-Italic.ttf'))
    pdfmetrics.registerFont(TTFont('Helvetica-BoldItalic',
                                   '/usr/share/fonts/truetype/liberation/LiberationSans-BoldItalic.ttf'))

PADDING = 1
BORDER = False # trace les limites des étiquettes ou pas

def pt2mm(long):
    """
    Convertit une longueur donnée en points vers des millimètres
    """
    return 0.3528 * long

def mm2pt(long):
    """
    Convertit une longueur donnée en millimètres vers des points
    """
    return long / 0.3528

def transform2labelPos(transform, specs):
    """
    Déduit d'une matrice de transformation, la position d'une
    étiquette dans une page.
    @param transform l'attribut d'un élément SVG "g", contenant une étiquette
    @param specs une instance de labels.Specification
    @return un dictionnaire qui donne la ligne et la colonne, numérotées
       à partir de zéro
    """
    pattern = re.compile(r" *matrix\(.*,([.\d]+),([.\d]+)\)")
    m = pattern.match(transform)
    x_mm = pt2mm(float(m.group(1)))
    y_mm = pt2mm(float(m.group(2)))
    col = round((x_mm - float(specs.left_margin)) /
                float(specs.label_width + specs.column_gap))
    row = round((float(
        specs.sheet_height - specs.top_margin - specs.label_height) - y_mm) /
                float(specs.label_height + specs.row_gap))
    return {"row": row, "col": col}

class EtiquetteBase:
    """
    Structure de données commune à toutes les impressions sur des
    planches d'étiquettes, ou microperforées.
    """
    
    """
    @var specs une instance de labels.Specification, ajustée pour le format
    "Avery J8159", vendu aussi sous le nom "A12" autrement.
    Voir https://www.avery.fr/produit/etiquette-pour-timbres-j8159-10
    """
    specs = labels.Specification(
        210, 297,                              # format A4
        3, 8,                                  # 3 colonnes, 9 lignes
        63.5, 33.9,                            # largeur, hauteur de l'étiquette
        corner_radius=2,                       # arrondi
        left_margin=7, right_margin=7,         # marges gauche et droite
        top_margin=12.9,                         # marge en haut
        left_padding=PADDING,                  # espacement intérieur
        right_padding=6.5,                     # coquetterie de GemaSCO ?
        top_padding=PADDING,                   #
        bottom_padding=PADDING,                #
        row_gap=0                              # espace entre lignes
    )

    def __init__(self):
        pass
    
    def adjustXY(self, drawable, x, y):
        """
        Ajuste l'origine d'une instance dessinable de reportlab.
        L'ajustement ne se fait que si l'origine initiale est (0,0)
        @param drawable une instace dessinable de reportlab
        @param x abscisse du début en mm, ou "center"
        @param y ordonnée du début en mm, ou "center"
        """
        x1, y1, x2, y2 = drawable.getBounds()
        w = x2 - x1
        h = y2 - y1
        if x1 == 0 and y1 <= 0:
            x = (self.w - w)/2 if x=="center" else mm2pt(x)
            y = (self.h - h)/2 if y=="center" else mm2pt(y)
            if isinstance(drawable, shapes.Drawing):
                drawable.translate(x, y)
            elif isinstance(drawable, shapes.String):
                drawable.x = x
                drawable.y = y
            elif isinstance(drawable, shapes.Image):
                drawable.x = x
                drawable.y = y
        return

    def addParagraph(self,p, x, y, lineheight, maxLines,
                     fontSize = 10, join = ""):
        """
        Ajoute un paragraphe sur le pylabel
        @param p un paragraphe qui peut contenir des retours à la ligne "/n",
                 et qui sont censées être courtes (tenir sur la lageur de
                 l'étiquette pour chaque ligne)
        @param x abscisse où commence le paragraphe
        @para y ordonnée où commence le paragraphe
        @param lineheight hauteur d'un passage à la ligne
        @param maxLines nombre de lignes à ne pas dépasser
        @param fontSize taille de fonte en pt (10 par défaut)
        @param join (vide par défaut) ; s'il n'est pas vide, c'est une chaîne
                    courte qui peut servir à rassembler des morceaux du
                    paragraphe sur une même ligne tant qu'on ne dépasse pas.
        """
        width = self.specs.label_width - \
            (self.specs.left_padding + self.specs.right_padding)
        lignes = [li.strip() for li in p.split("\n") if li.strip()]
        index = 0
        n_ligne=0
        while index < len(lignes):
            printable = lignes[index]
            s = self.string(printable, fontSize=fontSize)
            x1 ,y1, x2, y2 = s.getBounds()
            if x + pt2mm(x2 - x1) >= width:
                self.adjustXY(s, x, y + n_ligne * lineheight)
                self.pylabel.add(s)
                n_ligne +=1
                if n_ligne > maxLines:
                    print("Error: too many lines")
                    return
                index +=1
                continue
            else:
                accu = printable
                while join and index < len(lignes)-1:
                    accu += join + lignes[index+1]
                    s = self.string(accu, fontSize=fontSize)
                    x1 ,y1, x2, y2 = s.getBounds()
                    if x + pt2mm(x2 - x1) >= width:
                        break
                    else:
                        printable = accu
                        index +=1
                # on a fini d'ajouter des morceaux, il est temps
                # d'écrire sur le label
                s = self.string(printable, fontSize=fontSize)
                self.adjustXY(s, x, y - n_ligne * lineheight)
                self.pylabel.add(s)
                n_ligne +=1
                index += 1
                if n_ligne > maxLines:
                    print("Error: too many lines")
                    return
        return

    def drawBarcode(self, width = 72, height = 6, x = 'center', y = 10):
        """
        Inscrit un code barre dans un pylabel ;
        @param width largeur du code-barre, en mm (47 mm par défaut)
        @param height hauteur du code-barre, en mm (6 mm par défaut)
        @param x abscisse du début ("center" par défaut)
        @param y ordonnée du début (10 mm par défaut)
        """
        bc = createBarcodeDrawing(
            'Code128', value="{code:9d}".format(**self.__dict__),
            width = mm2pt(width), height = mm2pt(height))
        dx = -12 # ce décalage a été réglé expérientalement, au pif
        bc.translate(dx, mm2pt(10))
        self.pylabel.add(bc)
        return

    def string(self, s, fontName="Helvetica", fontSize=6):
        """
        Crée une instance de shapes.String avec x,y = 0,0
        @param s la chaîne à afficher
        @param fontname la police de caractères, Helvetica par défaut
        @param la taille de la police, 6 pt par défaut
        """
        return shapes.String(0, 0, s, fontName=fontName, fontSize=fontSize)

    def image(self, path, width, height):
        """
        Crée une instance de shapes.Image avec x,y = 0,0
        @param path chemin de l'image
        @param width largeur
        @param height hauteur
        """
        if path[0] != "/":
            path = os.path.join(BASE_DIR, path)
        return shapes.Image(0, 0, width, height, path)

    @staticmethod
    def draw_etiquette(label, w, h, instance):
        """
        Fonction de rappel qui crée effectivement le dessin d'une étiquette
        @param label une instance de reportlab.graphics.shapes.Drawing qui
           est gérée automatiquement lors du rappel
        @param w la largeur disponible dans l'objet généré automatiquement
        @param h la hauteur disponible dans l'objet généré automatiquement
        @param instance une instance d'Etiquette
        """
        instance.make_label(label, w, h)
        return

class EtiquetteBoutique(EtiquetteBase):
    """
    Structure de données pour les étiquettes à mettre sur les étagères, afin
    de faciliter le passage des élèves en mode "boutique". Les paramètres
    du constructeur sont :
    @param code ce qu'il faut encoder dans le QR-code à gauche
    @param abrege un mot court pour désigner un manuel
    """
    def __init__(self, code, abrege = "abrégé manquant"):
        self.code = code
        self.abrege = abrege
        self.tempfiles = []
        return

    def qr(self, code, size = 85, x = 0, y = 6):
        """
        inscrit un QR-code dans une image
        @param code le code à inscrire
        @param size largeur et hauteur du QR-code (30 mm par défaut)
        @param x abscisse du début (2mm par défaut)
        @param y ordonnée du début (2mm par défaut)
        """
        import qrcode
        import tempfile
        from qrcode.image.styledpil import StyledPilImage
        from qrcode.image.styles.moduledrawers.pil import RoundedModuleDrawer

        qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_H)
        qr.add_data(code)
        img = qr.make_image(
            image_factory=StyledPilImage,
            module_drawer=RoundedModuleDrawer()
        )
        tmp = tempfile.NamedTemporaryFile(prefix="etiq-"+code, suffix=".png")
        img.save(tmp.name, format="PNG")
        self.tempfiles.append(tmp) # so the tempfile lives as long as self
        return shapes.Image(x, y, size, size, tmp.name)

    def drawQR(self):
        img = self.qr(self.code)
        self.pylabel.add(img)
        s = self.string(self.code, fontSize=6)
        self.pylabel.add(s)
        return

    def drawAbrege(self):
        mots = re.split(r"[ -]+", self.abrege)
        p = "\n".join(mots)
        self.addParagraph(
            p, 31, 15, lineheight=6, maxLines=3, fontSize=14, join=" ")
        return

    def make_label(self, label, w, h):
        """
        Enchaîne toutes les étapes de création de l'étiquette, pour
        la bibliothèque pylabels.
        """
        self.pylabel, self.w, self.h = label, w, h
        self.drawQR()
        self.drawAbrege()
        return

class EtiquetteJeanBart(EtiquetteBase):
    """
    Implémentation de la structure de données spécifique aux étiquettes
    pour les manuels de la coopérative du lycée Jean Bart. Les paramètres
    du constructeur sont :
    @param code ce qu'il faut encoder dans le code-barre
    @param titre le titre d'un manuel
    @param abrege un mot court, par exemple "ESP" pour un manuel d'Espagnol
    @param date la date officielle de création de l'étiquette
    @param ets le nom d'établissement (par défaut, "Lycée Jean Bart")
    @param loc la localisation de l'établissement (par défaut, "Dunkerque")
    @param log le nom du logiciel générateur (par défaut, "SLM")
    @param ver la version du logiciel (par défaut, None, ce qui fait
       rechercher dans le sous-module version)
    """
    specs = labels.Specification(
        210, 297,                              # format A4
        3, 8,                                  # 3 colonnes, 9 lignes
        63.5, 33.9,                            # largeur, hauteur de l'étiquette
        corner_radius=2,                       # arrondi
        left_margin=7, right_margin=7,         # marges gauche et droite
        top_margin=12.9,                         # marge en haut
        left_padding=PADDING,                  # espacement intérieur
        right_padding=6.5,                     # coquetterie de GemaSCO ?
        top_padding=PADDING,                   #
        bottom_padding=PADDING,                #
        row_gap=0                              # espace entre lignes
    )

    def __init__(self, code, titre, abrege, adate,
                 ets = "Lycée Jean Bart", loc = "Dunkerque",
                 log = "SLM", ver = None):
        EtiquetteBase.__init__(self)
        self.code = code
        self.titre = titre
        self.abrege = abrege
        self.date = adate
        self.ets = ets
        self.loc = loc
        self.log = log
        if ver:
            self.ver = ver
        else:
            self.ver = this_version
        # propriétés qui seront utilisées pour l'écriture sur un pylabel
        self.pylabel = None # instance de pylabel
        self.w = None       # sa largeur, en points
        self.h = None       # sa hauteur, en points
        return

    def drawEts(self, x='center', y=23):
        """
        Inscrit le nom de l'établissement dans un pylabel ;
        attention, ici l'unité est le point typographique ; 1pt = 0.3528 mm
        @param x l'abscisse de la chaîne à tracer ("center" par défaut)
        @param y l'ordonnée de la chaîne à tracer (23 mm par défaut)
        """
        s = self.string("{ets} - {loc}".format(**self.__dict__), fontSize=6)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        return

    def drawTitle(self, x="center", y = 20):
        """
        Inscrit le titre : date de création à l'inventaire et titre du livre
        @param x l'abscisse de la chaîne à tracer ("center" par défaut)
        @param y l'ordonnée de la chaîne à tracer (20 mm par défaut)
        """
        s = self.string("{date} - {titre}".format(**self.__dict__), fontSize=4)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        return

    def drawAbrege(self, x="center", y=5):
        """
        Inscrit l'abrégé sous le code-barre : abrégé et code
        @param x l'abscisse de la chaîne à tracer ("center" par défaut)
        @param y l'ordonnée de la chaîne à tracer (5 mm par défaut)
        """
        s = self.string("{abrege} - {code}".format(**self.__dict__), fontSize=6)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        ## REMARQUE : on définit ci-dessous un encadrement autour de l'abrégé
        ## mais celui-ci n'est pas centré, comme dans Gemasco à l'origine ;)
        p = shapes.PolyLine(
            [mm2pt(v) for v in [2, 3.5, 56, 3.5, 56, 8.5, 2, 8.5, 2, 3.5]],
            strokeWidth = 0.05,
            strokeColor = toColor("rgb(200,200,200)"),
        )
        self.pylabel.add(p)
        return

    def repeatCode(self):
        """
        Répète deux fois le code numérique sur l'étiquette
        """
        s = self.string("{code}".format(**self.__dict__),
                        fontName="Helvetica-Bold", fontSize=4)
        g = shapes.Group(s)
        g.translate(mm2pt(4), mm2pt(23))
        g.rotate(-90)
        self.pylabel.add(g)
        s = s.copy()
        self.adjustXY(s, 45, 17)
        self.pylabel.add(s)
        return

    def drawSignature(self):
        """
        Donne le nom du logiciel générateur et sa version
        """
        s = self.string(
            "{log} version {ver}".\
            format(**self.__dict__),
            fontSize=4)
        g = shapes.Group(s)
        g.translate(mm2pt(54), mm2pt(23))
        g.rotate(-90)
        self.pylabel.add(g)
        return
    
    def make_label(self,label, w, h):
        """
        Enchaîne toutes les étapes de création de l'étiquette, pour
        la bibliothèque pylabels.
        """
        self.pylabel, self.w, self.h = label, w, h
        self.drawBarcode()
        self.drawEts()
        self.drawTitle()
        self.drawAbrege()
        self.repeatCode()
        self.drawSignature()
        return

class CartesMembre(EtiquetteBase):
    """
    Implémentation de la structure de données spécifique aux cartes de membre
    pour la coopérative du lycée Jean Bart. Les paramètres
    du constructeur sont :
    @param eleve une instance de la classe models.Eleves
    """
    specs = labels.Specification(
        210, 297,                              # format A4
        2, 3,                                  # 2 colonnes, 3 lignes
        105, 99,                               # largeur, hauteur de l'étiquette
        corner_radius=2,                       # arrondi
        left_margin=0, right_margin=0,         # marges gauche et droite
        top_margin=0,                          # marge en haut
        left_padding=8,                        # espacement intérieur
        right_padding=8,                       # 
        top_padding=8,                         #
        bottom_padding=8,                      #
        row_gap=0                              # espace entre lignes
    )

    def __init__(self, eleve):
        EtiquetteBase.__init__(self)
        self.eleve = eleve
        self.pylabel = None # instance de pylabel
        self.w = None       # sa largeur, en points
        self.h = None       # sa hauteur, en points
        return

    def drawEts(self, x='center', y=75):
        """
        Inscrit le nom de l'établissement dans un pylabel ;
        attention, ici l'unité est le point typographique ; 1pt = 0.3528 mm
        @param x l'abscisse de la chaîne à tracer ("center" par défaut)
        @param y l'ordonnée de la chaîne à tracer (75 mm par défaut)
        """
        s = self.string("Lycée Jean Bart -- Dunkerque", fontSize=19)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        return

    def drawTitle(self, x="center", y = 70):
        """
        Inscrit le titre 
        @param x l'abscisse de la chaîne à tracer ("center" par défaut)
        @param y l'ordonnée de la chaîne à tracer (70 mm par défaut)
        """
        classe = self.eleve.Lib_Structure
        if classe.startswith("PAS"):
            classe = ".........."
        s = self.string(
            "Coopérative Scolaire      Classe : {classe}".format(classe=classe),
            fontSize=12)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        return

    def drawMembre(self, x="center", y=60):
        """
        Inscrit les données du membre
        @param x l'abscisse de la chaîne à tracer ("center" par défaut)
        @param y l'ordonnée de la chaîne à tracer (60 mm par défaut)
        """
        no = ".........."
        if self.eleve.no_caution >= MIN_NO_CAUTION:
            no = self.eleve.no_caution
        s = self.string("Membre Actif N° : {no}".format(no=no), fontSize=19)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        return

    def drawNomPrenom(self, x="center", y=50):
        """
        Donne le nom du logiciel générateur et sa version. La taille
        de la fonte sera 20 tant que la longueur de NomPrenomEleve ne
        dépasse pas 20 caractères, et elle diminue après ça.
        @param x l'abscisse de la chaîne à tracer ("center" par défaut)
        @param y l'ordonnée de la chaîne à tracer (50 mm par défaut)
        """
        fontSize = 20
        e = self.eleve
        if len(e.NomPrenomEleve) > 20:
            fontSize = round(20*20/len(e.NomPrenomEleve))
        s = self.string("{e.NomPrenomEleve}".format(e=e),
                        fontSize=fontSize)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        return
    
    def drawDate(self, x="center", y=45):
        """
        affiche la date
        """
        if self.eleve.date_inscription_coop is not None:
            thedate = self.eleve.date_inscription_coop
        else:
            thedate = datetime.now().date()
            self.eleve.date_inscription_coop = thedate
            self.eleve.save()
        s = self.string("Inscription : le {date}".\
            format(date=thedate.strftime("%d/%m/%Y")),
                        fontSize=12)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        return

    def drawSuivi(self, x=5, y=40):
        p = shapes.PolyLine(
            [mm2pt(v) for v in
             [x, y, x+80, y]],
            strokeWidth = 0.2,
            strokeColor = toColor("rgb(0,0,0)"),
        )
        self.pylabel.add(p)
        s = self.string(
            "Livres non rendus : " + \
            ".........................................................",
            fontSize=12)
        self.adjustXY(s, x, y-6)
        self.pylabel.add(s)
        s = self.string(
            ".........................................................................",
            fontSize=12)
        self.adjustXY(s, x, y-12)
        self.pylabel.add(s)
        if self.eleve.sans_caution:
            s = self.string(
                "(s.c.) ..........................",
                fontSize=12)
        else:
            s = self.string(
                "Caution rendue : 70 € ☐ .........",
                fontSize=12)
        self.adjustXY(s, x, y-35)
        self.pylabel.add(s)
        return
    
    def drawSignature(self, x=65, y=20):
        s = self.string(
            "Le trésorier :",
            fontSize=8)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        p = shapes.PolyLine(
            [mm2pt(v) for v in
             [x-1, y+5, x+21, y+5, x+21, y-15, x-1, y-15, x-1, y+5]],
            strokeWidth = 0.1,
            strokeColor = toColor("rgb(0,0,0)"),
        )
        self.pylabel.add(p)
        return

    def make_label(self,label, w, h):
        """
        Enchaîne toutes les étapes de création de l'étiquette, pour
        la bibliothèque pylabels.
        """
        self.pylabel, self.w, self.h = label, w, h
        self.drawEts()
        self.drawTitle()
        self.drawMembre()
        self.drawNomPrenom()
        self.drawDate()
        self.drawSuivi()
        self.drawSignature()
        return

class Suivi(EtiquetteBase):
    """
    Implémentation de la structure de données spécifique aux cartes de membre
    pour la coopérative du lycée Jean Bart. Les paramètres
    du constructeur sont :
    @param eleve une instance de la classe models.Eleves
    """
    specs = labels.Specification(
        297,210,                               # format A4 paysage
        2, 1,                                  # 2 colonnes, 1 ligne
        120, 190,                              # largeur, hauteur de l'étiquette
        corner_radius=2,                       # arrondi
        left_margin=12, right_margin=12,       # marges gauche et droite
        top_margin=0,                          # marge en haut
        left_padding=8,                        # espacement intérieur
        right_padding=8,                       # 
        top_padding=8,                         #
        bottom_padding=8,                      #
        row_gap=0                              # espace entre lignes
    )

    def __init__(self, eleve, annee="0000/00", non_rendus=[]):
        EtiquetteBase.__init__(self)
        self.eleve = eleve
        self.annee = annee
        self.non_rendu = non_rendus
        self.pylabel = None # instance de pylabel
        self.w = None       # sa largeur, en points
        self.h = None       # sa hauteur, en points
        return

    def drawAnnee(self, x="center", y=165):
        """
        Inscrit l'année
        """
        s = self.string("Location {annee}".format(annee=self.annee),
                        fontSize=28, fontName="Helvetica-Bold")
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        p = shapes.PolyLine(
            [mm2pt(v) for v in [0, y-2, 125, y-2]],
            strokeWidth = 0.5,
            strokeColor = toColor("rgb(0,0,0)"),
        )
        self.pylabel.add(p)
        return
    
    def drawEts(self, x=5, y=152):
        """
        Inscrit le nom de l'établissement
        """
        s = self.string("Lycée Jean Bart", fontSize=19,
                        fontName="Helvetica-Bold")
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        s = self.string("COOPÉRATIVE SCOLAIRE", fontSize=11.7)
        self.adjustXY(s, x, y-6)
        self.pylabel.add(s)
        return

    def drawClasseNumero(self, x=65, y=152):
        classe = self.eleve.Lib_Structure
        if classe.startswith("PAS"):
            classe = ".........."
        s = self.string("Classe : {classe}".format(classe=classe), fontSize=11)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        if self.eleve.no_caution >= MIN_NO_CAUTION:
            no = self.eleve.no_caution
            if self.eleve.sans_caution:
                no = f"s.c.({self.eleve.no_caution})"
        else:
            no = ".........."
        s = self.string("N° COOP : {no}".format(no=no),
                        fontSize=11)
        self.adjustXY(s, x, y-6)
        self.pylabel.add(s)
        return

    def drawNomPrenom(self, x=0, y=132):
        s = self.string("NOM :", fontSize=16)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        s = self.string(self.eleve.Nom_de_famille, fontSize=16,
                        fontName="Helvetica-Bold")
        self.adjustXY(s, x+25, y)
        self.pylabel.add(s)
        s = self.string("Prénom :", fontSize=14)
        self.adjustXY(s, x, y-5)
        self.pylabel.add(s)
        s = self.string(self.eleve.Prenom, fontSize=14,
                        fontName="Helvetica-Bold")
        self.adjustXY(s, x+25, y-5)
        self.pylabel.add(s)
        return

    def drawOptions(self, x=0, y=120):
        opt = "Option/Spés : " + \
            (self.eleve.Lib_Mat_Enseignee_1 or "")+ "\n" + \
            (self.eleve.Lib_Mat_Enseignee_2 or "") + "\n" + \
            (self.eleve.Lib_Mat_Enseignee_3 or "") + "\n" + \
            (self.eleve.Lib_Mat_Enseignee_4 or "") + "\n" + \
            (self.eleve.Lib_Mat_Enseignee_5 or "")+ "\n" + \
            (self.eleve.Lib_Mat_Enseignee_6 or "")+ "\n" + \
            (self.eleve.Lib_Mat_Enseignee_7 or "")+ "\n" + \
            (self.eleve.Lib_Mat_Enseignee_8 or "")+ "\n" + \
            (self.eleve.Lib_Mat_Enseignee_9 or "")+ "\n" + \
            (self.eleve.Lib_Mat_Enseignee_10 or "")
        # adresse multi-ligne, 4 mm par ligne, 2 lignes au plus
        # on joint les morcreaux par des points-virgules.
        self.addParagraph(opt, x, y, 4, 2, join=" ; ")
        return

    
    def drawAdresse(self, x=0, y=108):
        if self.eleve.Adresse_Repr_Leg and self.eleve.Adresse_Repr_Leg.split():
            adr = "ADRESSE : " + self.eleve.Adresse_Repr_Leg
        else:
            adr = """\
ADRESSE : .....................................................................
..............................................................................
.............................................................................."""
        # adresse multi-ligne, 4 mm par ligne, 4 lignes au plus
        # on joint les morcreaux par des points-virgules.
        self.addParagraph(adr, x, y, 4, 4, join=" ; ")
        return

    def drawTelephones(self, x = 0, y = 90):
        s = self.string(
            "Tél. Repr. Lég. : {t1} Tél. Autre Repr. : {t2}".format(
                t1 = self.eleve.Tel_Portable_Repr_Leg or "..................",
                t2 = self.eleve.Tel_Portable_Autre_Repr_Leg or "..................",
            ),
            fontSize=10)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        s = self.string(
            "Tél. Élève : {t3}".format(
                t3 = self.eleve.Tel_Portable or ".................."
            ),
            fontSize=10)
        self.adjustXY(s, x, y-4)
        self.pylabel.add(s)
        return

    def drawTitreCompta(self, x = 'center', y = 73):
        s = self.string("Détails comptabilité", fontSize=16,
                        fontName="Helvetica-Bold")
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        p = shapes.PolyLine(
            [mm2pt(v) for v in [0, y-2, 125, y-2]],
            strokeWidth = 0.5,
            strokeColor = toColor("rgb(0,0,0)"),
        )
        self.pylabel.add(p)
        return

    def drawLocation(self, x=0, y= 65):
        s = self.string("Location : 55 €", fontSize=12) 
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        s = self.string(
            "Esp. ☐ ; Chq. ☐ ; HDF N°........................",
            fontSize=12)
        self.adjustXY(s, x + 36, y)
        self.pylabel.add(s)
        s = self.string("Masse : 10 €", fontSize=12)
        self.adjustXY(s, x, y-6)
        self.pylabel.add(s)
        s = self.string("Esp. ☐  Chq. ☐", fontSize=12)
        self.adjustXY(s, x + 36, y-6)
        self.pylabel.add(s)
        s = self.string("Caution : 70 €", fontSize=12)
        self.adjustXY(s, x, y-12)
        self.pylabel.add(s)
        s = self.string("Esp. ☐ ; Chq. ☐", fontSize=12)
        self.adjustXY(s, x + 36, y-12)
        self.pylabel.add(s)
        s = self.string(
            "Chq. banque : .........  Titulaire du compte : ....................",
            fontSize=12)
        self.adjustXY(s, x, y-18)
        self.pylabel.add(s)
        return
    
    def drawObservations(self, x=0, y= 38):
        l1 = "........................................................" + \
            "..............................."
        if self.non_rendu:
            # on commence à marquer la liste des livres non rendus
            ll = len(l1)
            l1 = ",".join((str(nr) for nr in self.non_rendu)) + l1
            l1 = l1[:ll]
            s = self.string("Observations : ", fontSize=12)
            self.adjustXY(s, x, y)
            self.pylabel.add(s)
            img = self.image("gestion/static/img/danger.png", 14, 12)
            self.adjustXY(img, x+27, y)
            self.pylabel.add(img)
            s = self.string(l1, fontSize=5)
            self.adjustXY(s, x+33, y)
            self.pylabel.add(s)
        else:
            s = self.string("Observations : " + l1, fontSize=12)
            self.adjustXY(s, x, y)
            self.pylabel.add(s)
        s = self.string(
            "........................................................" + \
            "........................................................"
                        , fontSize=12)
        self.adjustXY(s, x, y-6)
        self.pylabel.add(s)
        s = self.string(
            "........................................................" + \
            "........................................................"
                        , fontSize=12)
        self.adjustXY(s, x, y-12)
        self.pylabel.add(s)
        return
    
    def drawEnregistrement(self, x='center', y= 10):
        s = self.string("Enregistrement comptable", fontSize=16,
                        fontName="Helvetica-Bold")
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        p = shapes.PolyLine(
            [mm2pt(v) for v in [0, y-2, 125, y-2]],
            strokeWidth = 0.5,
            strokeColor = toColor("rgb(0,0,0)"),
        )
        self.pylabel.add(p)
        s = self.string("Livre N° : ....                      Page : ....."
                        , fontSize=12)
        self.adjustXY(s, x, y-8)
        self.pylabel.add(s)
       
        return
    
    def make_label(self,label, w, h):
        """
        Enchaîne toutes les étapes de création de l'étiquette, pour
        la bibliothèque pylabels.
        """
        self.pylabel, self.w, self.h = label, w, h
        self.drawAnnee()
        self.drawEts()
        self.drawClasseNumero()
        self.drawNomPrenom()
        self.drawOptions()
        self.drawAdresse()
        self.drawTelephones()
        self.drawTitreCompta()
        self.drawLocation()
        self.drawObservations()
        self.drawEnregistrement()
        return

def previewSVG(data, tabou):
    """
    Produit un fichier SVG de planches d'étiquettes ; ce fichier est
    enrichi par rapport à ce que donne Reportlab afin d'attribuer un
    propriété d'interaction à chaque étiquette
    @param data une liste d'instances d'EtiquetteJeanBart
    @param tabou une liste de positions où on ne doit pas imprimer
       d'étiquette ; chaque position est donnée par un dictionnaire
       "row" : entier, "col": entier (numéros débutant à un)
    @return un triplet :
       un rendu de la première page au format SVG,
       le nombre d'étiquettes et de pages
    """
    sheet = labels.Sheet(
        EtiquetteJeanBart.specs, EtiquetteJeanBart.draw_etiquette, border=True)
    sheet.partial_page(1,[(int(pos["row"]), int(pos["col"])) for pos in tabou])
    for etiquette in data:
        sheet.add_label(etiquette)
    svg = renderSVG.drawToString(sheet._pages[0])
    # on dégraisse le document pour ne garder que le nœud svg
    from lxml import etree as ET
    def istag(tagname, elt):
        """
        Vérifie un tag indépendemment de l'espace de nom pour lxml
        """
        return re.sub(r"\{.*\}", "", elt.tag) == tagname
    def firstTag(tagname, elt):
        """
        Trouve le premier descendant possédant un tag parmi les
        fils d'un élément
        """
        found = [e for e in elt if istag(tagname, e)]
        return found[0] if found else None
    def children(tagname, elt):
        """
        Renvoie les éléments descendant de elt qui on un tag donné
        """
        return [e for e in elt if istag(tagname, e)]
    
    doc = ET.fromstring(svg.encode())
    title = firstTag("title", doc)
    title.text = "Cliquer sur les étiquettes pour activer/désactiver l'impression"
    # On "descend de deux groupes" dans le document
    # et à ce niveau, il y a une collection de groupes, chacun avec
    # une étiquette.
    etiquettes = children("g",firstTag("g", firstTag("g", doc)))
    for e in etiquettes:
        pos = transform2labelPos(e.attrib["transform"], EtiquetteJeanBart.specs)
        row = pos["row"]
        col = pos["col"]
        # rend l'objet cliquable
        e.attrib["onclick"] = "toggleLabel(this);"
        e.attrib["style"] = "cursor: pointer"
        rect = ET.Element("rect")
        rect.attrib["fill"] = "rgba(200,255,200,0.3)"
        rect.attrib["class"] = "ok"
        rect.attrib["title"] = "Cliquer pour désactiver cette position"
        rect.attrib["width"] = "{}".format(
            mm2pt(float(EtiquetteJeanBart.specs.label_width)))
        rect.attrib["height"] = "{}".format(
            mm2pt(float(EtiquetteJeanBart.specs.label_height)))
        # ajout de données ligne, colonne
        rect.attrib["data-col"] = str(col+1)
        rect.attrib["data-row"] = str(row+1)
        e.append(rect)
    return ET.tostring(doc).decode(), sheet.label_count, sheet.page_count

class CartesMembre_verso(EtiquetteBase):
    """
    Implémentation de la structure de données des versos de cartes de membre
    pour la coopérative du lycée Jean Bart.
    """
    specs = labels.Specification(
        210, 297,                              # format A4
        2, 3,                                  # 2 colonnes, 3 lignes
        105, 99,                               # largeur, hauteur de l'étiquette
        corner_radius=2,                       # arrondi
        left_margin=0, right_margin=0,         # marges gauche et droite
        top_margin=0,                          # marge en haut
        left_padding=8,                        # espacement intérieur
        right_padding=8,                       # 
        top_padding=8,                         #
        bottom_padding=8,                      #
        row_gap=0                              # espace entre lignes
    )

    def __init__(self):
        EtiquetteBase.__init__(self)
        return

    def drawRendu(self, x=5, y=75):
        """
        affiche la ligne de rendu
        """
        s = self.string(
            "Classe : ..........  Caution rendue le : ..../..../....",
            fontSize=12)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        s = self.string(
            "CNI ☐ C. Bus ☐ Badge ☐ Autre ☐",
            fontSize=12)
        self.adjustXY(s, x, y-6)
        self.pylabel.add(s)
        s = self.string(
            "Chèque au guichet ☐ Chèque par courrier ☐",
            fontSize=12)
        self.adjustXY(s, x, y-12)
        self.pylabel.add(s)
        s = self.string(
            "Ordre : ........................................................",
            fontSize=12)
        self.adjustXY(s, x, y-18)
        self.pylabel.add(s)
        return

    def drawCaution(self, x=5, y=40):
        s = self.string(
            "Cette carte vaut pour une caution de 70 €, remboursable",
            fontSize=8)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        s = self.string(
            "dans les 3 mois suivant la sortie de la coopérative.",
            fontSize=8)
        self.adjustXY(s, x, y-4)
        self.pylabel.add(s)
        return

    def drawSignature(self, x=5, y=30):
        s = self.string(
            "Signature :",
            fontSize=8)
        self.adjustXY(s, x, y)
        self.pylabel.add(s)
        p = shapes.PolyLine(
            [mm2pt(v) for v in
             [x-1, y+5, x+80, y+5, x+80, y-15, x-1, y-15, x-1, y+5]],
            strokeWidth = 0.1,
            strokeColor = toColor("rgb(0,0,0)"),
        )
        self.pylabel.add(p)
        return
        
    
    def make_label(self,label, w, h):
        """
        Enchaîne toutes les étapes de création de l'étiquette, pour
        la bibliothèque pylabels.
        """
        self.pylabel, self.w, self.h = label, w, h
        self.drawRendu()
        self.drawCaution()
        self.drawSignature()
        return
    
class EtiquetteAdresse(EtiquetteBase):
    """
    Structure de données pour les étiquettes d'adresse concernant un élève :
    @param e une instance d'Eleves
    """
    def __init__(self, e):
        self.eleve = e
        return

    def drawNom(self):
        self.addParagraph(
            self.eleve.NomPrenomEleve, 2, 25, lineheight=6,
            maxLines=1, fontSize=10, join=" ")
        return

    def drawAdresse(self):
        debut, code = self.eleve.adresse_codepostal()
        self.addParagraph(
            debut, 2, 21, lineheight=4,
            maxLines=5, fontSize=7.5, join=" ")
        self.addParagraph(
            code, 2, 8, lineheight=4,
            maxLines=2, fontSize=10, join=" ")
        return
    
    def make_label(self, label, w, h):
        """
        Enchaîne toutes les étapes de création de l'étiquette, pour
        la bibliothèque pylabels.
        """
        self.pylabel, self.w, self.h = label, w, h
        self.drawNom()
        self.drawAdresse()
        return

def makePDF(data, tabou):
    """
    Produit un fichier PDF de planches d'étiquettes
    @param data une liste d'instances d'EtiquetteJeanBart
    @param tabou une liste de positions où on ne doit pas imprimer
       d'étiquette ; chaque position est donnée par un dictionnaire
       "row" : entier, "col": entier (numéros débutant à un)
    @return un pseudo-fichier, ouvert en lecteur, initialisé à son début
    """
    sheet = labels.Sheet(
        EtiquetteJeanBart.specs, EtiquetteJeanBart.draw_etiquette,
        border=BORDER)
    sheet.partial_page(1,[(int(pos["row"]), int(pos["col"])) for pos in tabou])
    for etiquette in data:
        sheet.add_label(etiquette)
    buffer = io.BytesIO()
    sheet.save(buffer)
    buffer.seek(0)
    return buffer

def make_carte_membre_PDF(data, tabou):
    """
    Produit un fichier PDF de cartes de membre
    @param data une liste d'instances de CartesMembre
    @param tabou une liste de positions où on ne doit pas imprimer
       d'étiquette ; chaque position est donnée par un dictionnaire
       "row" : entier, "col": entier (numéros débutant à un)
    @return un pseudo-fichier, ouvert en lecteur, initialisé à son début
    """
    sheet = labels.Sheet(CartesMembre.specs, CartesMembre.draw_etiquette,
                         border=False)
    sheet.partial_page(1,[(int(pos["row"]), int(pos["col"])) for pos in tabou])
    for etiquette in data:
        sheet.add_label(etiquette)
    buffer = io.BytesIO()
    sheet.save(buffer)
    buffer.seek(0)
    return buffer

def make_suivi_PDF(data):
    """
    Produit un fichier PDF de fiches de suivi
    @param data une liste d'instances de Suivi
    @param tabou une liste de positions où on ne doit pas imprimer
       d'étiquette ; chaque position est donnée par un dictionnaire
       "row" : entier, "col": entier (numéros débutant à un)
    @return un pseudo-fichier, ouvert en lecteur, initialisé à son début
    """
    sheet = labels.Sheet(Suivi.specs, Suivi.draw_etiquette,
                         border=False)
    # dans le cas où le nombre de fiches de suivi est impair, on imprime
    # seulement à la deuxième position, afin de pouvoir placer une feuille
    # A5 comme première feuille à imprimer
    if len(data) %2 == 1:
        sheet.partial_page(1,[(1, 1)])
    for etiquette in data:
        sheet.add_label(etiquette)
    buffer = io.BytesIO()
    sheet.save(buffer)
    buffer.seek(0)
    return buffer

def make_carte_membre_verso_PDF(data):
    """
    Produit un fichier PDF de cartes de membre
    @param data une liste d'instances de CartesMembre
    @return un pseudo-fichier, ouvert en lecteur, initialisé à son début
    """
    sheet = labels.Sheet(CartesMembre.specs, CartesMembre.draw_etiquette,
                         border=False)
    for etiquette in data:
        sheet.add_label(etiquette)
    buffer = io.BytesIO()
    sheet.save(buffer)
    buffer.seek(0)
    return buffer

def demo():
    # démonstration !
    # création des feuilles à imprimer
    sheet = labels.Sheet(
        EtiquetteJeanBart.specs, EtiquetteJeanBart.draw_etiquette, border=True)

    # Ajout de quelques étiquettes
    for n in range(21365, 21370):
        e = EtiquetteJeanBart(
            n,                           # numéro pour le code-barre
            "Hispamundo Term G et STMG", # titre
            'ESP',                       # abrégé
            "14/03/2023",                # date
        )
        sheet.add_label(e)


    # Enregistrement du fichier et message
    sheet.save('essai.pdf')
    print("{0:d} étiquette(s) imprimées, sur {1:d} page(s).".format(
        sheet.label_count, sheet.page_count))
    return
    

if __name__ == "__main__":
    demo()
