-- luadraw_graph.lua (chargé par luadraw_graph2d.lua)
-- date 2026/05/29
-- version 3.1
-- Copyright 2026 Patrick Fradin
-- This work may be distributed and/or modified under the
-- conditions of the LaTeX Project Public License.
-- The latest version of this license is in
--   https://www.ctan.org/license/lppl

-- Tout ce qui touche au dessin de base, sans les axes
local luadraw_calc = require 'luadraw_calc'
local ld = luadraw
require 'luadraw_colors'
local cpx = ld.cpx -- complex
local Z = cpx.Z
local toComplex = cpx.toComplex
local isComplex = cpx.isComplex
local strReal = ld.strReal

local luadraw_graph = {}
setmetatable(luadraw_graph, {__index = luadraw_calc}) -- obligatoire pour l'héritage

--- Constructeur
function luadraw_graph:new(args) -- argument de la forme :
-- {window={x1,x2,y1,y2,xscale,yscale}, margin={left, right, top, bottom}, size={large, haut, ratio}, bg="color", border = true/false, bbox=true/false ,pictureoptions=""}
    local graph = luadraw_calc:new(args) -- obligatoire, on utilise le constructeur de luadraw_calc
    setmetatable(graph, {__index = luadraw_graph})  -- obligatoire, permet d'utiliser self
    
    graph.LF = "\\par" --line feed pour TeX
    graph.advancedexport = {""} -- export en début de graphique
    graph.advanced = false
    graph.currentexport = {""} -- export courant
    graph.deferredexport = {""} -- export en fin de graphique
    graph.deferred = false
    graph.export = graph.currentexport
    graph.pictureoptions = args.pictureoptions or ""
    
    -- styles par défaut
    graph.currentparam = {
            ["viewport"] = {graph.Xmin, graph.Xmax, graph.Ymin, graph.Ymax},  -- définit la vue courante
            ["coordsystem"] = {graph.Xmin, graph.Xmax, graph.Ymin, graph.Ymax},  -- coordonnées de la vue courante
            ["linestyle"] = "solid", -- "noline", ou "solid" ou "dashed" ou "dotted" ou  dash( motif )
            ["linecap"] = "butt", --"butt" ou "round" ou "square"
            ["linejoin"] = "round", -- "miter" ou "round" ou "bevel"
            ["linewidth"]  = 4, -- en dixième de points
            ["linecolor"] = "black", -- couleur de tracé en svgnames ou tout autre nomenclature comprise par tikz
            ["lineopacity"] = 1,  -- opacité du trait
            ["arrows"] = "-", -- fleche, soit "-", soit "->" soit "<-" soit "<->"
            ["fillstyle"] = "none", --ou "full", "bdiag", "fdiag", "hvcross", "diagcross", "horizontal", "vertical", "gradient"
            ["fillcolor"] = "black",
            ["fillopacity"] = 1,
            ["filleo"] = false, -- even odd mode
            ["gradstyle"] = "left color = white, right color = red", -- gradient linéaire de gauche à droite
            ["dotstyle"] = "*", -- style par défaut (point rond)
            ["dotscale"] = 1, -- scale
            ["dotsize"] = Z(2,2), -- le diamètre du point est re + im*linewidth
            ["labelstyle"] = "center", -- position du label "center" ou "N" ou "S", ou "E", etc...
            ["labelcolor"] = "", -- couleur du  document  par défaut
            ["labelsize"] = "", --taille normale par défaut, ou "tiny", ou "small" ou ...
            ["labeldir"] = {}, -- direction de l'écriture (la table vide correspond au sens usuel)
            ["labelangle"] = 0 -- orientation du label
            }
    graph.advancedparam = table.copy(graph.currentparam)
    graph.deferredparam = table.copy(graph.currentparam)
    graph.param = graph.currentparam
    graph.tikzpictureoptions = "line join=round"
    if graph.border or (graph.bg ~= "") then
        graph.tikzpictureoptions = graph.tikzpictureoptions..",background rectangle/.style={"
        if graph.border then 
            graph.tikzpictureoptions = graph.tikzpictureoptions..",draw=black"
        end
        if graph.bg ~= "" then
            graph.tikzpictureoptions = graph.tikzpictureoptions..",fill="..graph.bg
        end
        graph.tikzpictureoptions = graph.tikzpictureoptions.."},show background rectangle"
    end
    if graph.pictureoptions ~= "" then
        graph.tikzpictureoptions = graph.tikzpictureoptions..","..graph.pictureoptions
    end
    graph.matrix = ld.ID -- matrice de transformation [f(0), Lf(1), Lf(i) ], identité par défaut
    
    graph.pile = {} -- pour sauvegarder la fenêtre, les styles et la matrice
    graph.pilematrix = {} -- sauvegarde de la matrice uniquement
    return graph
end

function luadraw_graph:Coord(z)
--  z est un complexe et la fonction renvoie la chaîne "(x,y)" où x et y sont les coordonnées de z pour tikz
    if type(z) == "number" then return self:strCoord(z,0)
    else return self:strCoord(z.re,z.im)
    end
end

function luadraw_graph:Box2d()
-- renvoie la fenêtre 2d courante sous forme d'une ligne polygonale
    local x1,x2,y1,y2 = table.unpack(self.param.coordsystem)
    return {Z(x1,y1),Z(x2,y1),Z(x2,y2),Z(x1,y2)}
end

-- sauvegarde et restauration des paramètres graphiques (fenêtre, styles, matrice)
function luadraw_graph:Saveattr()
    table.insert(self.pile, table.copy(self.matrix))
    table.insert(self.pile, table.copy(self.param))
    self:Writeln("\\begin{scope}")
end

function luadraw_graph:Restoreattr()
    self.param = table.remove(self.pile)
    self.matrix = table.remove(self.pile)
    self:Writeln("\\end{scope}")
end

function luadraw_graph:Defaultattr()
    self:Lineoptions("solid","black",4,"-") -- "noline" ou "solid" ou "dashed" ou "dotted" ou  dash( motif )
    self:Linecap("butt") --"butt" ou "round" ou "square"
    self:Linejoin("round") -- "miter" ou "round" ou "bevel"
    self:Lineopacity(1)  -- opacité du trait
    self:Filloptions("none","black",1,false) --ou "full" "bdiag" "fdiag" "hvcross" "diagcross" "horizontal" "vertical" "gradient"
    self:Gradstyle("left color=white, right color=red") -- gradient linéaire de gauche à droite
    self:Dotstyle("*") -- style par défaut (point rond)
    self:Dotscale(1) -- scale
    self:Labelstyle("center") -- position du label "center" ou "N" ou "S" ou "E" etc...
    self:Labelcolor("") -- couleur du  document  par défaut
    self:Labelsize("") --taille normale par défaut, ou "tiny" ou "small" ou ...
    self:Labelangle(0) -- orientation du label
end

function luadraw_graph:Cleargraph()
    self.advancedexport = {""} -- export en début de graphique
    self.advanced = false
    self.currentexport = {""} -- export courant
    self.deferredexport = {""} -- export en fin de graphique
    self.deferred = false
    self.export = self.currentexport
end

function luadraw_graph:Savematrix()
    table.insert(self.pilematrix, table.copy(self.matrix))
end

function luadraw_graph:Restorematrix()
    self.matrix = table.remove(self.pilematrix)
end

function luadraw_graph:Viewport(x1,x2,y1,y2) -- sélectionne une zone de la fenêtre initiale, il faut faire un g:SaveAttr() avant !
    if (x1 == nil) or (x2 == nil) or (y1 == nil) or (y2 == nil) then return end
    if x1 > x2 then x1, x2 = x2, x1 end
    if y1 > y2 then y1, y2 = y2, y1 end
    self.matrix = ld.ID -- ancienne matrice perdue donc il faut sauver avant
    self.param.viewport = {x1,x2,y1,y2}
    self.param.coordsystem = {x1,x2,y1,y2}
    --self:Writeln("\\clip "..self:strCoord(x1,y1).." -- "..self:strCoord(x2,y1).." -- "..self:strCoord(x2,y2).." -- "..self:strCoord(x1,y2).."-- cycle;") --> clipping donc il faut sauver avant
end

function luadraw_graph:Coordsystem(x1,x2,y1,y2,ortho) -- nouveau système de coordonnées pour la vue courante
    if (x1 == nil) or (x2 == nil) or (y1 == nil) or (y2 == nil) then return end
    if x1 > x2 then x1, x2 = x2, x1 end
    if y1 > y2 then y1, y2 = y2, y1 end    
    local ort = (ortho or false)
    local X1,X2,Y1,Y2 = table.unpack(self.param.viewport) -- vue actuelle
    local G1 = Z((x1+x2)/2, (y1+y2)/2) -- centre du nouveau repérage
    local G2 = Z((X1+X2)/2, (Y1+Y2)/2) -- centre actuel
    local Dx = (X2-X1)/(x2-x1) -- réductions axes
    local Dy = (Y2-Y1)/(y2-y1)
    local D = nil
    if ort then
        if Dx > Dy then D = Dy else D = Dx end
        self:Setmatrix( {G2-G1*D, Z(D,0), Z(0,D) }) -- ancienne matrice perdue donc il faut sauver avant
        local A, B = (Z(X1,Y1)-G2)/D+G1, (Z(X2,Y2)-G2)/D+G1
        self.param.coordsystem = {A.re,B.re,A.im,B.im}
     else
        local M = { G2- Z(G1.re*Dx, G1.im*Dy), Z(Dx,0), Z(0,Dy) }
        self.param.coordsystem = {x1,x2,y1,y2}
        self:Setmatrix(M)
    end
end

function luadraw_graph:Getview()
    return self.param.viewport
end
function luadraw_graph:Xinf()
    return self.param.coordsystem[1]
end

function luadraw_graph:Xsup()
    return self.param.coordsystem[2]
end

function luadraw_graph:Yinf()
    return self.param.coordsystem[3]
end

function luadraw_graph:Ysup()
    return self.param.coordsystem[4]
end

-- écriture dans le document TeX ou dans un fichier pour TeX
function luadraw_graph:Write(str)
-- écrit la chaîne str sans retour à la ligne
    if str ~= "" then
        local n = #self.export
        self.export[n] = self.export[n]..str
    end
end

function luadraw_graph:Writeln(str)
--  écrit la chaîne str avec retour à la ligne
    self:Write(str)
    table.insert(self.export,"") -- on démarre une nouvelle ligne   
end

function luadraw_graph:Begindeferred()
-- pour repousser les instructions en fin de graphique
    self.deferred = true
    self.export = self.deferredexport
    self.param = self.deferredparam
end

function luadraw_graph:Enddeferred()
-- pour arrêter de repousser les instructions en fin de graphique
    self.export = self.currentexport
    self.param = self.currentparam
end

function luadraw_graph:Beginadvanced()
-- pour avancer les instructions en début de graphique
    self.advanced = true
    self.export = self.advancedexport
    self.param = self.advancedparam
end

function luadraw_graph:Endadvanced()
-- pour arrêter d'avancer les instructions en début de graphique
    self.export = self.currentexport
    self.param = self.currentparam
end


--afficher le graphique dans le document TeX  (utilisée par le fichier luadraw.sty pour la méthode Show())
function luadraw_graph:Sendtotex()
    --if #self.deferredexport ~= 0 then self:Defaultattr() end -- pour que les instructions décalées démarrent avec les paramètres par défaut
    local before, after = {""}, {""}
    if self.advanced then before = {"\\end{scope}%","\\begin{scope}%"} end
    if self.deferred then after = {"\\end{scope}%","\\begin{scope}%"} end
    local str = ld.concat(self:beginDraw(),self.advancedexport,before,self.currentexport,after,self.deferredexport,self:endDraw())
    tex.sprint(table.unpack(str))
end

-- sauvegarde dans un fichier texte
function luadraw_graph:Savetofile(nom) 
  -- Ouverture du fichier en écriture seule
  -- Attention, si un fichier existe déjà avec ce nom, il sera écrasé !
    local file = io.open(nom, "w")
    if not file then
        print("I can't open the file "..nom)
        return
    end
    if #self.deferredexport ~= 0 then self:Defaultattr() end
  -- On écrit dans le fichier    
    for _, lg in ipairs(self:beginDraw()) do
        file:write(lg.."%\n")
    end
    for _, lg in ipairs(self.advancedexport) do
        if lg ~= "" then file:write(lg.."%\n") end
    end
    if self.advanced then 
        file:write("\\end{scope}%\n\\begin{scope}%\n") 
    end
    for _, lg in ipairs(self.currentexport) do
        if lg ~= "" then file:write(lg.."%\n") end
    end
    if self.deferred then 
        file:write("\\end{scope}%\n\\begin{scope}%\n")
    end    
    for _, lg in ipairs(self.deferredexport) do
        if lg ~= "" then file:write(lg.."%\n") end
    end
    for _, lg in ipairs(self:endDraw()) do
        file:write(lg.."%\n")
    end
    file:close()
end

function luadraw_graph:beginDraw() -- début du dessin
    local str = {"\\begin{tikzpicture}["..self.tikzpictureoptions.."]"}
     table.insert(str,"\\begin{scope}")
    if self.bbox then
        table.insert(str,"\\clip ("..tostring(-self.Lmargin)..",".. tostring(-self.Bmargin)..") rectangle ("..tostring(self:Graphwidth()+self.Rmargin)..","..tostring(self:Graphheight()+self.Tmargin)..");")
    end
    return str
end

function luadraw_graph:endDraw()  -- fin du dessin
    return {"\\end{scope}","\\end{tikzpicture}"}
end

------------- gestion des attributs de dessin --------------------------

local color2str = function(coul)
-- une couleur peut être soit une chaîne de caractères (un nom de couleur ou "{rgb,1:red,r;green,g;blue,b}" )
-- soit une table {r,g,b}
-- la  fonction renvoie une chaîne
    if coul == nil then return end
    local strCoul
    if type(coul) == "table" then -- c'est une table {r,g,b}
        strCoul = rgb(coul)
    else
        strCoul = coul
        if string.sub(strCoul,1,1) == "{" then -- on retire les accolades
            strCoul = string.sub(strCoul,2,#strCoul-1) 
        end 
    end
    return strCoul
end

function luadraw_graph:Linewidth(ep)
-- ep est une épaisseur en dixième de point ex 4 pour 0.4pt
    if self.param.linewidth ~= ep then
        self:Writeln("\\pgfsetlinewidth{"..tostring(0.1*ep).."pt}")
        self.param.linewidth = ep
    end
end

function luadraw_graph:Linecolor(strCoul) 
-- pour modifier la couleur du tracé (strCoul doit être une chaîne)
    strCoul = color2str(strCoul)
    if self.param.linecolor ~= strCoul then
        self:Writeln("\\pgfsetstrokecolor{"..strCoul.."}")
        self.param.linecolor = strCoul
    end
end

function luadraw_graph:Linecap(style) -- "butt" ou "round" ou "square"
    if self.param.linecap ~= style then
        if style == "butt" then
            self:Writeln("\\pgfsetbuttcap")
            self.param.linecap = style
        else if style == "round" then
                self:Writeln("\\pgfsetroundcap")
                self.param.linecap = style
            else
                if style == "square" then
                    self:Writeln("\\pgfsetrectcap")
                    self.param.linecap = style
                end
            end
        end
    end
end

function luadraw_graph:Linejoin(style) -- "miter" ou "round" ou "bevel"
    if self.param.linejoin ~= style then
        if style == "miter" then
            self:Writeln("\\pgfsetmiterjoin")
            self.param.linejoin = style
        else if style == "round" then
                self:Writeln("\\pgfsetroundjoin")
                self.param.linejoin = style
            else
                if style == "bevel" then
                    self:Writeln("\\pgfsetbeveljoin")
                    self.param.linejoin = style
                end
            end
        end
    end
end

function luadraw_graph:Dash(style) -- transforme par ex {2.5,2} en {2.5pt}{2pt}
    local str = ""
    for _,c in ipairs(style) do
        str = str.."{"..tostring(c).."pt}"
    end
    return str
end    

function luadraw_graph:Linestyle(style)
    if style == self.param.linestyle then return end
    if style == "noline" then
        self.param.linestyle = "noline"
    else
        if style == "solid" then
           self.param.linestyle = "solid" 
           self:Linecap("butt")
           self:Writeln("\\pgfsetdash{}{0pt}")
        else
            if style == "dashed" then
                self.param.linestyle = "dashed" 
                self:Linecap("butt")
                self:Writeln("\\pgfsetdash{{2.5pt}{2pt}}{0pt}")
            else
                if style == "dotted" then
                self.param.linestyle = "dotted"
                self:Linecap("round")
                self:Writeln("\\pgfsetdash{{0pt}{3pt}}{0pt}")
                else -- on attend un motif ex: "{2.5pt}{2pt}"
                    self.param.linestyle = style
                    self:Linecap("butt")
                    self:Writeln("\\pgfsetdash{"..style.."}{0pt}")
                end
            end
        end
    end
end

function luadraw_graph:Lineopacity(x) 
-- opacité du tracé, x est entre 0 et 1
    if x == self.param.lineopacity then return end
    x = (x or 1)
    if (0 <= x) and (x <= 1) then
        self.param.lineopacity = x
        self:Writeln("\\pgfsetstrokeopacity{"..x.."}")
    end
end
    
function luadraw_graph:Arrows(style) -- style = "->", "<-", "<->", nil
    if style == self.param.arrows then return end
    if style == "->" then self:Writeln("\\pgfsetarrows{-to}"); self.param.arrows = style
    else if style == "<-" then self:Writeln("\\pgfsetarrows{to-}"); self.param.arrows = style
        else if style == "<->" then self:Writeln("\\pgfsetarrows{to-to}"); self.param.arrows = style
            else 
                if style == "-" then
                    self:Writeln("\\pgfsetarrows{-}"); self.param.arrows = style
                else self:Writeln("\\pgfsetarrows{"..style.."}"); self.param.arrows = style
                end
            end
        end
    end
end

function luadraw_graph:Lineoptions(style,strCoul,width,arrows)
    if strCoul ~= nil then self:Linecolor(strCoul) end
    if style ~= nil then self:Linestyle(style) end
    if width ~= nil then self:Linewidth(width) end
    if arrows ~= nil then self:Arrows(arrows) end
end

function luadraw_graph:Filloptions(style,strCoul,opacity,evenOdd)
    if opacity ~= nil then self:Fillopacity(opacity) end
    if evenOdd ~= nil then self:Filleo(evenOdd) end
    if style == "gradient" then -- dans ce cas l'argument strCoul contient le style du gradient
        self.param.fillstyle = style
        if strCoul ~= nil then self:Gradstyle(strCoul) end
        return 
    end
    strCoul = color2str(strCoul)
    if strCoul == nil then strCoul = self.param.fillcolor end -- on ne change pas la couleur
    if style == nil then style = self.param.fillstyle end -- on ne change pas le style
    local ok = (self.param.fillcolor == strCoul)
    if not ok then
        self.param.fillcolor = strCoul
    end
    if (style == self.param.fillstyle) and ok then return end
    --if (style == "none") then self.param.fillstyle = style; return end    
    if (style == "none") or (style == "full") then 
        self.param.fillstyle = style 
        if (not ok) or (style == "full") then self:Writeln("\\pgfsetfillcolor{"..self.param.fillcolor.."}") end
        return 
    end
    if style == "bdiag" then self.param.fillstyle = style; self:Writeln("\\pgfsetfillpattern{north east lines}{"..self.param.fillcolor.."}"); return end
    if style == "hvcross" then self.param.fillstyle = style; self:Writeln("\\pgfsetfillpattern{grid}{"..self.param.fillcolor.."}"); return end
    if style == "diagcross" then self.param.fillstyle = style; self:Writeln("\\pgfsetfillpattern{crosshatch}{"..self.param.fillcolor.."}"); return end
    if style == "fdiag" then self.param.fillstyle = style; self:Writeln("\\pgfsetfillpattern{north west lines}{"..self.param.fillcolor.."}"); return end
    if style == "horizontal" then self.param.fillstyle = style; self:Writeln("\\pgfsetfillpattern{horizontal lines}{"..self.param.fillcolor.."}"); return end
    if style == "vertical" then self.param.fillstyle = style; self:Writeln("\\pgfsetfillpattern{vertical lines}{"..self.param.fillcolor.."}"); return end    
    self.param.fillstyle = style  -- en espérant que ce style existe !
    self:Writeln("\\pgfsetfillpattern{"..style.."}{"..self.param.fillcolor.."}")
end

function luadraw_graph:Fillopacity(x) 
-- opacité du tracé, x est entre 0 et 1
    if x == self.param.fillopacity then return end
    x = (x or 1)
    if (0 <= x) and (x <= 1) then
        self.param.fillopacity = x
        self:Writeln("\\pgfsetfillopacity{"..x.."}")
    end
end

function luadraw_graph:Filleo(evenOdd) 
-- opacité du tracé, x est entre 0 et 1
    evenOdd = (evenOdd or false)
    if (self.param.filleo ~= evenOdd) then
        self.param.filleo = evenOdd
        if evenOdd then self:Writeln("\\pgfseteorule") 
        else self:Writeln( "\\pgfsetnonzerorule");
        end
    end
end

function luadraw_graph:Labelstyle(position)
-- position du label par rapport au point d'ancrage
    position = position or ""
    self.param.labelstyle = position
end

function luadraw_graph:Labelcolor(color)
-- couleur du label, color est une chaîne représentant une couleur pour tikz
    color = color or "" -- la chaîne vide représente la couleur courante du document
    --color = color2str(color)
    self.param.labelcolor = color -- utilisée en local seulement
end

function luadraw_graph:Labelsize(size)
-- size est une chaîne : "normalsize" ou "small" ou "tiny" ou "huge" ... etc, la chaîne vide représente normalsize
    size = size or ""
    self.param.labelsize = size
end

function luadraw_graph:Labelangle(angle)
-- angle est en degré, rotation du label autour du point d'ancrage
    angle = angle or 0
    self.param.labelangle = angle
end

function luadraw_graph:Labeldir(dir)
-- dir est une table {dirX, dirY, dep} iniquant e sens de l'écriture, table vide par défaut
    dir = dir or {}
    self.param.labeldir = dir
end

function luadraw_graph:Gradstyle(chaine)
    -- définit le style de gradient, c'est une chaîne transmise telle quelle à \draw
    -- par défaut c'est "left color = white, right color = red"
    if type(chaine) == "string" then
        self.param.gradstyle = chaine
    end
end

function luadraw_graph:Dotstyle(style)
    style = (style or "*")
    if style == nil then return end
    if style ~= self.param.dotstyle then
        self.param.dotstyle = style
    end
end

function luadraw_graph:Dotscale(scale)
    scale = (scale or 1)
    if (scale == nil) or (type(scale) ~= "number") then return end
    if (scale > 0) and (scale ~= self.param.dotscale) then
        self.param.dotscale = scale
    end
end

-- dessins 

------------- ligne polygonale -----------------------------------------

function luadraw_graph:drawcmd(draw_options)
    local commande = ""
    local sep = ""
    draw_options = draw_options or ""
    if ((self.param.linestyle == "noline") and (self.param.fillstyle == "none")) then return end
    if (self.param.linestyle == "noline") then commande = "\\fill["  else commande = "\\draw[" end
    if (self.param.fillstyle ~= "none") and (self.param.linestyle ~= "noline") then commande = commande.."fill"; sep = "," end
    if self.param.fillstyle == "gradient" then commande = commande..sep..self.param.gradstyle; sep = "," end
    if draw_options ~= "" then commande = commande..sep..draw_options.."] "
    else commande = commande.."] "
    end
    return commande
end

function luadraw_graph:Dpolyline(L,close,draw_options,clip) -- close vaut true ou false
-- dessine une ligne polygonale avec les attributs courants
-- L est une liste de complexes ou une liste de listes de complexes
-- clip = {x1,x2,y1,y2} rectangle de clipping, ou nil  pour la fenêtre par défaut
    local clippee
    if (L == nil) or (type(L) ~= "table") or (#L == 0) then return end
    if type(close) == "string" then clip = draw_options; draw_options = close; close = false end
    close = close or false
    draw_options = draw_options or ""
    local commande = self:drawcmd(draw_options)
    if commande == nil then return end
    if (type(L[1]) == "number") or isComplex(L[1]) then L = {L} end
    local X1,X2,Y1,Y2
    if clip == nil then X1,X2,Y1,Y2 = table.unpack(self.param.viewport)
    else X1,X2,Y1,Y2 = table.unpack(clip)
    end
    for _, cp in ipairs(L) do
        clippe = false
        local len = #cp
        cp = {cp}
        if len > 1 then
            if clip ~= nil then
                cp, clippee = ld.clippolyline(cp,X1,X2,Y1,Y2,close)
            end
            if not ld.isID(self.matrix) then
                cp = self:Mtransform(cp)
            end
            --if clip == nil then cp, clippee = ld.clippolyline(cp,X1,X2,Y1,Y2,close) end
            if (clip == nil) and ( (not self.bbox) or (not self.zeromargins) ) then 
                cp, clippee = ld.clippolyline(cp,X1,X2,Y1,Y2,close)
                if (cp[1] ~= nil) and (not clippee) and close then table.remove(cp[1]) end 
            end
            for _, cpp in ipairs(cp) do
                len = #cpp
                if len > 1 then
                    local z = cpp[1]
                    self:Write(commande..self:Coord(z))
                    for k = 2, len do
                        z = cpp[k]
                        self:Write(" -- "..self:Coord(z))
                    end
                    if close then self:Writeln("--cycle;") 
                    else self:Writeln(";")
                    end
                end
            end
        end
    end
end

-- courbe cartésienne
function luadraw_graph:Dcartesian(f,args)
-- dessin d'une courbe cartésienne d'équation y=f(x) dans l'intervalle [x1;x2]
-- args est une table à 5 entrées args = { x = {x1,x2}, nbdots = 50, discont =false, nbdiv = 5, draw_options = "",clip={x1,x2,y1,y2} }
    args = args or {}
    local x = args.x or {self:Xinf(), self:Xsup()}
    local nbdots = args.nbdots or 40
    local discont = args.discont or false
    local nbdiv = args.nbdiv or 4
    local draw_options = args.draw_options or ""
    local x1, x2 = table.unpack(x)
    if x1 > x2 then x1, x2 = x2, x1 end
    local C = ld.cartesian(f,x1,x2,nbdots,discont,nbdiv)
    self:Dpolyline(C,false,draw_options,args.clip)
end

-- courbe paramétrée
function luadraw_graph:Dparametric(p,args)
-- dessin d'une courbe paramétrée par la fonction p:t -> p(t) sur l'intervalle [t1;t2] (à valeurs complexes)
-- args est une table à 5 entrées args = { t = {t1,t2}, nbdots = 40, discont =false, nbdiv = 4, draw_options = "", clip={x1,x2,y1,y2} }
    args = args or {}
    local t = args.t or {self:Xinf(), self:Xsup()}
    local nbdots = args.nbdots or 40
    local discont = args.discont or false
    local nbdiv = args.nbdiv or 4
    local draw_options = args.draw_options or ""
    if draw_options == "" then draw_options = "line join=round"
    else draw_options = "line join=round,"..draw_options end -- jointure arrondie
    local t1, t2 = table.unpack(t)
    if t1 > t2 then t1, t2 = t2, t1 end
    local C = ld.parametric(p,t1,t2,nbdots,discont,nbdiv)
    self:Dpolyline(C,false,draw_options,args.clip)
end

-- courbe polaire
function luadraw_graph:Dpolar(rho,args)
-- dessin d'une courbe polaire parmétrée par la fonction rho:t -> rho(t) sur l'intervalle [t1;t2] (à valeurs réelles)
-- args est une table à 6 entrées args = { t = {t1,t2}, nbdots = 40, discont =false, nbdiv = 4, draw_options = "", clip={x1,x2,y1,y2} } }
    args = args or {}
    local t = args.t or {-math.pi, math.pi}
    local nbdots = args.nbdots or 40
    local discont = args.discont or false
    local nbdiv = args.nbdiv or 4
    local draw_options = args.draw_options or ""
    local t1, t2 = table.unpack(t)
    if t1 > t2 then t1, t2 = t2, t1 end
    local C = ld.polar(rho,t1,t2,nbdots,discont,nbdiv)
    self:Dpolyline(C,false,draw_options,args.clip)
end

-- courbe cartésienne périodique
function luadraw_graph:Dperiodic(f,period,args)
-- f est une fonction x -> f(x) réelle
-- period est une liste {a,b} avec a < b représentant une période
-- args est une table à 5 entrées args = { x = {x1,x2}, nbdots = 40, discont =false, nbdiv = 4, draw_options = "",clip={x1,x2,y1,y2} }
    if (period == nil) or (type(period) ~= "table") or (#period ~= 2) then return end
    args = args or {}
    local x = args.x or {self:Xinf(), self:Xsup()}
    local nbdots = args.nbdots or 40
    local discont = args.discont
    local nbdiv = args.nbdiv or 4
    local draw_options = args.draw_options or ""
    local x1, x2 = table.unpack(x)
    if x1 > x2 then x1, x2 = x2, x1 end
    local C = ld.periodic(f,period,x1,x2,nbdots,discont,nbdiv)
    self:Dpolyline(C,false,draw_options,args.clip)
end

-- courbe d'une fonction en escalier
function luadraw_graph:Dstepfunction(def, args)
-- dessin d'une courbe d'une fonction en escalier
-- def est une table à 2 entrées permettant la définition de la fonction : { {x1,x2,x3,...,xn}, {c1,c2,...} }
--{x1,x2,...,xn} forme une subdivision de l'intervalle [x1,xn] sur lequel est dessinée la courbe
-- sur l'intervalle [x1,x2] la fonction vaut c1, sur [x2,x3] c'est c2, etc
-- args est une table à deux entrées { discont=true/false, draw_options="",clip={x1,x2,y1,y2} }
    if (def == nil) or (type(def) ~= "table") or (#def ~= 2) then return end
    args = args or {}
    local discont = args.discont
    local draw_options = args.draw_options or ""
    local C = ld.stepfunction(def,discont)
    if C ~= nil then
        self:Dpolyline(C,false,draw_options,args.clip)
    end
end

-- courbe d'une fonction affine par morceaux
function luadraw_graph:Daffinebypiece(def, args)
-- dessin d'une courbe d'une fonction affine par morceaux
-- def est une table à 2 entrées permettant la définition de la fonction : { {x1,x2,x3,...,xn}, { {a1,b1}, {a2,b2},...} }
--{x1,x2,...,xn} forme une subdivision de l'intervalle [x1,xn] sur lequel est dessinée la courbe
-- sur l'intervalle [x1,x2] la courbe a pour équation y=a1*x+b1, sur [x2,x3] c'est y=a2*x+b2, etc
-- args est une table à deux entrées { discont=true/false, draw_options="",clip={x1,x2,y1,y2} }
    if (def == nil) or (type(def) ~= "table") or (#def ~= 2) then return end
    args = args or {}
    local discont = args.discont or true
    local draw_options = args.draw_options or ""
    local C = ld.affinebypiece(def,discont)
    if C ~= nil then
        self:Dpolyline(C,false,draw_options,args.clip)
    end
end

function luadraw_graph:DplotXY(X,Y,draw_options,clip)
-- X et Y sont deux listes de complexes avec #X <= #Y
-- la fonction dessine la ligne polygonale constituée des points (X[k],Y[k])
    if #Y < #X then return end
    local L, z = {}
        for k,x in ipairs(X) do
            z = Z(x,Y[k])
            if z ~= nil then table.insert(L, z) end
        end
    self:Dpolyline({L},false,draw_options,clip)
end

-- solution d'équation différentielle
function luadraw_graph:Dodesolve(f,t0,Y0,args)
-- résolution par Runge Kutta 4 dans l'intervalle [t1,t2] (contenant t0) de Y'(t)=f(t,Y(t))
-- où f: (t,Y) -> f(t,Y) dans R^n avec Y(t)={y1(t),...,yn(t)} (liste de réels)
-- t0 et Y0 donnent les conditions initiales avec Y0=Y(t0),
-- la fonction appelée (odeRK4) renvoie une liste M = { {tmin,...,tmax}, {y1(tmin),..., y1(tmax)}, ..., {yn(tmin),..., yn(tmax)} }
-- args est une table à 5 entrées args = { t = {t1,t2}, out = {i1,i2}, nbdots = 50, method = "rkf45"/"rk4", draw_options = "",clip={x1,x2,y1,y2} }
-- l'argument out est une table de deux entiers {i1, i2}, les points dessinés auront pour abscisses les M[i1] et pour ordonnées les M[i2], par défaut i1=1 et i2=2
-- ce qui correspond à la fonction y1 en fonction de t
   args = args or {}
    local t = args.t or {self:Xinf(), self:Xsup()}
    local nbdots = args.nbdots or 50
    local method = args.method or "rkf45"
    local draw_options = args.draw_options or ""
    local t1, t2 = table.unpack(t)
    local out = args.out or {1,2}
    if t1 > t2 then t1, t2 = t2, t1 end
    t0 = (t0 or 0)
    if (t0 < t1) or (t0 > t2) then return end
    local C = ld.odesolve(f,t0,Y0,t1,t2,nbdots,method)
    if C ~= nil then 
        self:DplotXY( C[out[1]], C[out[2]],draw_options,args.clip)
    end
end

function luadraw_graph:Dimplicit(f,args)
-- dessin d'une courbe implicite d'équations f(x,y)=0 dans le pavé [x1,x2]x[y1,y2]
-- args est une table à 3 entrées args = { view = {x1,x2,y1,y2}, grid = {50,50}, draw_options = "" }
-- view détermine la zone de dessin [x1,x2]x[y1,y2] et grid donne le nombre de subdivisions sur x et sur y
    args = args or {}
    local win = args.view or self.param.coordsystem
    local grid = args.grid or {50,50}
    local draw_options = args.draw_options or ""
    local x1, x2, y1, y2 = table.unpack(win)
    if x1 > x2 then x1, x2 = x2, x1 end
    if y1 > y2 then y1, y2 = y2, y1 end
    local C = ld.implicit(f,x1,x2,y1,y2,grid)
    if C ~= nil then self:Dpolyline(C,false,draw_options) end
end

function luadraw_graph:Dcontour(f,z,args)
-- dessin de lignes de niveau de la fonction f: (x,y) -> f(x,y) à valeurs réelles
-- z est la liste des différents niveaux à tracer (équation f(x,y) = z)
-- args est une table à 4 entrées { view = {x1,x2,y1,y2}, grid = {n1,n2}, colors = {"color1", "color2", ...}, draw_options = "" }
    local a = 0
    local F = function(x,y)
            return f(x,y)-a
        end
        
    z = z or {}
    if #z == 0 then return end
    args = args or {}
    local win = args.view or self.param.coordsystem
    local grid = args.grid or {50,50}
    local draw_options = args.draw_options or ""
    local x1, x2, y1, y2 = table.unpack(win)
    if x1 > x2 then x1, x2 = x2, x1 end
    if y1 > y2 then y1, y2 = y2, y1 end
    local colors = args.colors or {}
    local nb = #colors
    local oldC = self.param.linecolor
    for i, k in ipairs(z) do
        if i <= nb then 
            self:Linecolor(colors[i])
        end
        a = k
       local C = ld.implicit(F,x1,x2,y1,y2,grid) 
       if C ~= nil then self:Dpolyline(C,draw_options) end
    end
    self:Linecolor(oldC)
end

function luadraw_graph:Ddomain1(f,args)
-- dessine le contour de la partie du plan comprise entre la courbe de f, l'axe Ox, et les droites x=a, x=b 
-- avec f une fonction x-> f(x) réelle
-- args est une table à 5 entrées { x= {a,b}, nbdots=40, discont=true/false, nbdiv=4, draw_options="" }
   args = (args or {})
    local x = args.x or {self:Xinf(),self:Xsup()}
    local nbdots = (args.nbdots or 40)
    local discont = (args.discont or false)
    local nbdiv = (args.nbdiv or 4)
    local draw_options = args.draw_options or ""
    local a, b = table.unpack(x)
    local C = ld.domain1(f,a,b,nbdots,discont,nbdiv)
    if C ~= nil then
        self:Dpolyline(C,true,draw_options)
    end
end

function luadraw_graph:Ddomain2(f,g,args)
-- dessine le contour de la partie du plan comprise entre la courbe de f, la courbe de g, et les droites x=a, x=b 
-- avec f une fonction x-> f(x) réelle ainsi que g
-- args est une table à 5 entrées { x= {a,b}, nbdots=40, discont=true/false, nbdiv=4, draw_options="" }
    args = (args or {})
    local x = args.x or {self:Xinf(),self:Xsup()}
    local nbdots = (args.nbdots or 40)
    local discont = (args.discont or false)
    local nbdiv = (args.nbdiv or 4)
    local draw_options = args.draw_options or ""
    local a, b = table.unpack(x)
    local C = ld.domain2(f,g,a,b,nbdots,discont,nbdiv)
    if C ~= nil then
        self:Dpolyline(C,true,draw_options)
    end
end

function luadraw_graph:Ddomain3(f,g,args)
-- dessine le contour de la partie du plan comprise entre la courbe de f, la courbe de g, tout en se limitant dans l'intervalle [a;b]
-- avec f une fonction x-> f(x) réelle ainsi que g, supposées définies continues sur [a;b]
-- args est une table à 5 entrées { x= {a,b}, nbdots=40, discont=true/false, nbdiv=4, draw_options="" }
    args = (args or {})
    local x = args.x or {self:Xinf(),self:Xsup()}
    local nbdots = (args.nbdots or 40)
    local discont = (args.discont or false)
    local nbdiv = (args.nbdiv or 4)
    local draw_options = args.draw_options or ""
    local a, b = table.unpack(x)
    local C = ld.domain3(f,g,a,b,nbdots,discont,nbdiv)
    if C ~= nil then
        self:Dpolyline(C,true,draw_options)
    end
end

-- inequalities
local DinequalitiesWithClip = function(self,constraints, args)
    -- constrants = { f1, sg1, f2, sg2, ...} where fi are functions (x->fi(x) real) and sgi = '>' or'<'
    -- draw the outline of the region satisfying the conditions: y>fi(x) or y<fi(x)
    args = args or {}
    local f, sg, C
    local win = args.view or {self:Xinf(), self:Xsup(), self:Yinf(), self:Ysup()}
    local draw_options = args.draw_options or ""
    local x1,x2,y1,y2 =table.unpack(win)
    local P = {Z(x1,y1), Z(x2,y1), Z(x2,y2), Z(x1,y2),Z(x1,y1)}
    local n = #constraints  -- number of arguments
    for i = 1, n-1, 2 do   -- step = 2
        f, sg = constraints[i], constraints[i+1]  -- 2 args
        C = ld.clippolyline(ld.cartesian(f, x1, x2), self:Xinf(), 
                self:Xsup(),self:Yinf(), self:Ysup())[1]
        table.insert(C,1, Z(x1, C[1].im))
        table.insert(C, Z(x2, C[#C].im))
        if sg == ">" then
            ld.insert(C, {Z(x2,y2),Z(x1,y2),C[1]})
        else
            ld.insert(C, {Z(x2,y1),Z(x1,y1),C[1]})
        end
        self:Beginclip( ld.polyline2path(C) )
    end
    self:Dpolyline(P, true, draw_options)
    for i = 1, n-1, 2 do   -- step = 2
        self:Endclip()
    end
end

function luadraw_graph:Dinequalities(constraints, args)
    -- Inequalities( f1, sg1, f2, sg2, ...) where fi are functions (x->fi(x) real) and sgi = '>' or'<'
    -- returns the outline of the region satisfying the conditions: y>fi(x) or y<fi(x)
    args = args or {}
    local clip = args.useclip or false
    if clip then
        DinequalitiesWithClip(self,constraints, args)
    else
        local win = args.view or {self:Xinf(), self:Xsup(), self:Yinf(), self:Ysup()}
        local x1,x2,y1,y2 = table.unpack(win)
        local draw_options = args.draw_options or ""
        local P = ld.inequalities(constraints,x1,x2,y1,y2,self)
        self:Dpolyline(P, true, draw_options)
    end
end

function luadraw_graph:Dimplicit_inequalities(constraints, args)
    -- constrants = { f1, sg1, f2, sg2, ...} where fi are functions (x,y)->fi(x,y) real, and sgi = '>' or'<'
    --can fill the region satisfying the conditions: fi(x,y)>=0 or fi(x,y)<=0
    args = args or {}
    local f, sg, C
    local win = args.view or {self:Xinf(), self:Xsup(), self:Yinf(), self:Ysup()}
    local draw_options = args.draw_options or ""
    local grid = args.grid or {50,50}
    local x1,x2,y1,y2 =table.unpack(win)
    local P = {Z(x1,y1), Z(x2,y1), Z(x2,y2), Z(x1,y2),Z(x1,y1)}
    local n = #constraints  -- number of arguments
    for i = 1, n-1, 2 do   -- step = 2
        f, sg = constraints[i], constraints[i+1]  -- 2 args
        C = ld.implicit_inequality(f, sg, self:Xinf(), self:Xsup(),self:Yinf(), self:Ysup(), grid)
        self:Beginclip( C )
    end
    self:Dpolyline(P, true, draw_options)
    for i = 1, n-1, 2 do   -- step = 2
        self:Endclip()
    end
end

-- segments
function luadraw_graph:Dseg(segm,scale,draw_options) -- Dseg({a,b}, scale, draw_options)
    if type(scale) == "string" then draw_options = scale; scale = 1 end
    if (segm == nil) or (type(segm) ~="table") or (#segm ~= 2) then return end
    local a, b = table.unpack(segm)
    a = toComplex(a)
    b = toComplex(b)
    scale = scale or 1
    if (a == nil) or (b == nil) then return end
    local u = b-a
    if (u.re == 0) and (u.im == 0) then return end
    if not ld.isID(self.matrix) then
        a, b = ld.applymatrix(a,self.matrix), ld.applymatrix(b,self.matrix)
    end
    if scale ~= 1 then
        a,b = table.unpack(ld.seg(a,b,scale))
    end
    local X1,X2,Y1,Y2 = table.unpack(self.param.viewport)
    local res = ld.clipseg(a,b,X1,X2,Y1,Y2)
    if res ~= nil then -- res est une table à deux points
        local commande = self:drawcmd(draw_options)
        if cpx.dot(u,res[2]-res[1]) > 0 then -- le sens est important s'il y a une flèche
            self:Write(commande..self:Coord(res[1]))
            self:Writeln(" -- "..self:Coord(res[2])..";")
        else
            self:Write(commande..self:Coord(res[2]))
            self:Writeln(" -- "..self:Coord(res[1])..";")
        end
    end
end

function luadraw_graph:Dmarkseg(a,b,n,long,espace,angle,draw_options)
-- marque le segment [a,b] avec n petits segments penchés de angle degrés (45° par défaut), 
-- l'espacement est en unité graphique et la longueur en cm.}
    a = toComplex(a); b = toComplex(b)
    if (a == nil) or (b == nil) then return end
    n = n or 1
    long = long or 0.25
    espace = espace or 0.125
    angle = (angle or 45)*math.pi/180
    draw_options = draw_options or ""
    local l = cpx.abs(b-a)
    local v = (b-a)/l
    local u = long/2*cpx.exp(cpx.I*angle)*v 
    local c = a+(l-(n-1)*espace)*v/2
    local pas = espace*v 
    local res = {}
    for k = 1, n do 
        table.insert(res, {c-u, c+u})
        c = c + pas
    end
    self:Dpolyline(res,false,draw_options)
end

function luadraw_graph:Dmarkarc(b,a,c,r,n,long,espace,draw_options)
-- marque l'arc de cercle BAC de n segments
-- l'espacement est en unité graphique et la longueur en cm.}
    a = toComplex(a); b = toComplex(b); c = toComplex(c)
    if (a == nil) or (b == nil) or (c == nil) then return end
    r = math.abs(r or 0.5)
    n = n or 1
    long = (long or 0.25)/2
    espace = (espace or 0.0625)/r
    draw_options = draw_options or ""
    local dep = cpx.arg(b-a) + (cpx.arg((c-a)/(b-a))-(n-1)*espace)/2 
    local res = {}
    for k = 0, n-1 do 
        local p = cpx.exp(cpx.I*(dep+k*espace))
        local v = p/cpx.abs(p)
        table.insert(res, { a+r*p+long*v, a+r*p-long*v})
    end
    self:Dpolyline(res,false,draw_options)
end
    
-- polygone régulier
function luadraw_graph:Dpolyreg(sommet1, sommet2, nbcotes,sens,draw_options) -- ou (centre, sommet, nbcotes,draw_options)
    if type(sens) ~= "number" then draw_options = sens; sens = nil end
    local S = ld.polyreg(sommet1, sommet2, nbcotes, sens)
    self:Dpolyline(S,true,draw_options)
end 

-- carre
function luadraw_graph:Dsquare(a,b,sens,draw_options)
    if type(sens) ~= "number" then draw_options = sens; sens = nil end
    local S = ld.square(a,b,sens or 1)
    self:Dpolyline(S,true,draw_options)
end 

-- rectangle
function luadraw_graph:Drectangle(a,b,c,draw_options)
-- dessine le rectangle ayant comme sommets  consécutifs a et b (complexe) tel que le côté opposé passe par c.
    if type(c) == "string" then draw_options = c; c = nil end
    local S = ld.rectangle(a,b,c)
    self:Dpolyline(S,true,draw_options)
end

-- parallelogram
function luadraw_graph:Dparallelogram(a,u,v,draw_options)
-- dessine le parallélogramme ayant comme sommets consécutifs a, a+u, a+u+v, a+v
    if type(a) == "table" then
        draw_options = u
        a,u,v = table.unpack(a)
    end
    local S = ld.parallelogram(a,u,v)
    self:Dpolyline(S,true,draw_options)
end

-- suite récurrente u_(n+1)=f(u_n)
function luadraw_graph:Dsequence(f,u0,n,draw_options)
-- dessin des escaliers d'une suite récurrente
    local S = ld.sequence(f,u0,n)
    if S ~= nil then self:Dpolyline(S,false,draw_options) end
end    

--courbe de bézier
function luadraw_graph:Dbezier(L,draw_options) -- où L = {A1,c1,c2,A2,c3,c4,A3,...}
-- dessine une série de courbes de Bézier passant par A1, A1,... et ayant comme points de contrôle c1 et c2, puis c3,c4, ...
    if (L == nil) or (type(L) ~= "table") or (#L < 3) then return end
    local a, c1, c2, b
    local i = 1
    if not ld.isID(self.matrix) then
        L = self:Mtransform(L) -- image des points de L par la matrice de transformation courante
        if L == nil then return end
    end
    local first = true
    for k, x in ipairs(L)  do
        if i == 1 then a = x; i = 2
        else
            if i == 2 then 
                c1 = x; i = 3
            else
                if i == 3 then
                    c2 = x; i = 4
                else -- i vaut 4
                    b = x
                    -- tracé
                    if first then
                        local commande = self:drawcmd(draw_options)  -- commande draw avec les options
                        self:Write(commande..self:Coord(a))
                        first = false
                    end
                    self:Write(" .. controls "..self:Coord(c1).." and "..self:Coord(c2).." .. "..self:Coord(b))
                    i = 2
                end
            end
        end
    end
    if not first then self:Writeln(";") end
end

-- spline
function luadraw_graph:Dspline(points,v1,v2,draw_options)
-- dessine une spline passant par les points de la liste points
-- v1 et v2 sont les vecteurs tangents aux extrémités, s'ils sont égaux à nil alors c'est une spline naturelle
    if (points == nil) or (type(points) ~= "table") or (#points < 3) then return end
    self:Dpath( ld.spline(points,v1,v2), draw_options)
end

function luadraw_graph:Dtcurve(L,options)
-- trace une courbe passant par des points donnés avec des tangentes imposées à gauche et à droite.
-- L = {pt1, {t1,a1,t2,a2}, pt2, {t1,a1,t2,a2}, ... }
-- t1 est la norme du vecteur tangent à gauche, a1 est l'angle en degré par rapport à l'horizontale du vecteur tangent à gauche
-- c'est la même chose pour le vecteur tangent à droite avec t2 et a2, mais ceux-ci sont facultatifs
-- lorsque t2 et a2 ne sont pas donnés, alors ils prennent les mêmes valeurs que t1 et a1
-- options = { showdots=false, draw_options=""}
    options = options or {}
    options.showdots = options.showdots or false
    options.draw_options = options.draw_options or ""
    if (L == nil) or (type(L) ~= "table") or (#L < 3) then return end
    local S = ld.tcurve(L)
    if options.showdots then
        local line, dots, ctrl, k = {}, {L[1]}, {}, 0
        for _,z in ipairs(S) do
            k = k+1
            if k%4 == 0 then table.insert(dots,z) 
            elseif k%4 ~= 1 then table.insert(ctrl,z)
            end
            if z ~= "b" then table.insert(line,z) end
        end
        self:Dpolyline(line,"solid,black,line width=0.4pt")
        self:Ddots(ctrl,"color=black,scale=0.75,fill=white,mark=*")
        self:Ddots(dots,"color=black,fill=black,mark=*")
    end
    self:Dpath(S,options.draw_options)
end


-- tracer une droite d = {A,u} définie par un point A (complexe) et un vecteur directeur u (complexe non nul)
function luadraw_graph:Dline(d,B,draw_options,scale)
--trace la droite d (si B=nil) ou bien la droite passant par les points d et B
    local A, u = nil, nil
    if (d == nil) then return end
    if type(B) == "string" then scale = draw_options; draw_options = B; B = nil end
    if (B ~= nil) then 
        B = toComplex(B)
        d = toComplex(d)
        if (B == nil) or (d == nil) then return end
        A = d
        u = B-A
    else
        if (type(d) ~= "table") or (#d ~= 2) then return end
        A = d[1]
        u = d[2]
    end
    A = toComplex(A) ; u = toComplex(u)
    if (A == nil) or (u == nil) or (u.re == 0) and (u.im == 0) then return end
    scale = scale or 1
    if not ld.isID(self.matrix) then
        A, u = ld.applymatrix(A,self.matrix), ld.applyLmatrix(u,self.matrix)
    end
    local X1,X2,Y1,Y2 = table.unpack(self.param.viewport)
    local res = ld.clipline({A,u},X1,X2,Y1,Y2)
    if res ~= nil then
        res = ld.seg(res[1], res[2],scale)
        local commande = self:drawcmd(draw_options)
        if cpx.dot(u,res[2]-res[1]) > 0 then -- le sens est important s'il y a une flèche
            self:Write(commande..self:Coord(res[1]))
            self:Writeln(" -- "..self:Coord(res[2])..";")
        else
            self:Write(commande..self:Coord(res[2]))
            self:Writeln(" -- "..self:Coord(res[1])..";")
        end
    end
end

-- tracer une droite d par une équation cartésienne ax+by+c=0
function luadraw_graph:DlineEq(a, b, c,draw_options, scale)
    local d = ld.lineEq(a,b,c)
    local A = d[1]
    local u = d[2]
    A = toComplex(A)
    u = toComplex(u)
    if (A == nil) or (u == nil) or ((u.re == 0) and (u.im == 0)) then return end
    self:Dline({A,u},draw_options, scale)
end

-- dessiner une demi-droite
function luadraw_graph:Dhline(d,B,draw_options,scale)
--trace la demi-droite [A,A+u) si d={A,u} (si B=nil) ou bien la demi-droite [d,B) si B non nil
    local A, u = nil, nil
    if (d == nil) then return end
    if (type(B) ~= "number") and (not isComplex(B)) then 
        scale = draw_options; draw_options = B; B = nil 
    end
    if (B ~= nil) then 
        B = toComplex(B)
        d = toComplex(d)
        if (B == nil) or (d == nil) then return end
        A = d
        u = B-A
    else
        if (type(d) ~= "table") or (#d ~= 2) then return end
        A = d[1]
        u = d[2]
    end
    A = toComplex(A) ; u = toComplex(u)
    if(A == nil) or (u == nil) or (u.re == 0) and (u.im == 0) then return end 
    if not ld.isID(self.matrix) then
        A, u = ld.applymatrix(A,self.matrix), ld.applyLmatrix(u,self.matrix)
    end
    local X1,X2,Y1,Y2 = table.unpack(self.param.viewport)
    local M = self.matrix  
    self.matrix = ld.ID --la transformation a déjà été faite
    if (A.re < X1) or (A.re > X2) or (A.im < Y1) or (A.im > Y2) then -- A est hors de la vue courante
        self:Dline(A,A+u,draw_options,scale) -- on dessine la droite 
    else -- A est dans la fenêtre
        B = A+((X2-X1)+(Y2-Y1))*u -- on met B est la droite {A,u} mais hors de la vue courante
        self:Dseg({A,B},scale,draw_options) -- on dessine le segment [A,B]
    end
    self.matrix = M -- restitution
end

function luadraw_graph:Dperp(d,A,draw_options,scale)
-- dessine la perpendiculaire à la d passant par A
-- d est soit une droite (un point et un vecteur directeur), soir un vecteur non nul
    self:Dline(ld.perp(d,A),draw_options,scale)
end

function luadraw_graph:Dparallel(d,A,draw_options,scale)
-- dessine la parallèle à la d passant par A
-- d est soit une droite (un point et un vecteur directeur), soir un vecteur non nul
    self:Dline(ld.parallel(d,A),draw_options,scale)
end

function luadraw_graph:Dmed(A,B,draw_options,scale) -- ou Dmed(seg,draw_options)
-- dessine la médiatrice du segment seg ou [A;B]
    if (type(B) ~= "number") and (not isComplex(B)) then 
        scale = draw_options; draw_options = B; B = nil 
    end
    self:Dline(ld.med(A,B),draw_options,scale)
end

function luadraw_graph:Dbissec(B,A,C,interior,draw_options,scale)
-- dessine une bissectrice de l'angle géométrique BAC
    if (type(interior) ~= "boolean") then 
        scale = draw_options; draw_options = interior; interior = nil 
    end
    self:Dline(ld.bissec(B,A,C,interior),draw_options,scale)
end

function luadraw_graph:Dtangent(p,t0,long,draw_options)
-- dessin de la tangente à la courbe paramétrée par t->p(t) (à valeurs complexes)
-- au point de paramètre t0. 
-- si le paramètre long est égal à nil, on trace toute la droite, sinon, un segment de longueur long
    if type(long) ~= "number" then draw_options = long; long = nil end
    local S = ld.tangent(p,t0,long)
    if long == nil then self:Dline(S,draw_options)
    else self:Dseg(S,1,draw_options)
    end
end

function luadraw_graph:DtangentC(f,x0,long,draw_options)
-- dessin de la tangente à la courbe cartésienne d'équation y=f(x)
-- au point d'abscisse x0
-- si le paramètre long est égal à nil, on trace toute la droite, sinon, un segment de longueur long
    if type(long) ~= "number" then draw_options = long; long = nil end
    local S = ld.tangentC(f,x0,long)
    if long == nil then self:Dline(S,draw_options)
    else self:Dseg(S,1,draw_options)
    end
end

function luadraw_graph:DtangentI(f,x0,y0,long,draw_options)
-- dessin de la tangente à la courbe implicite d'équation f(x,y)=0
-- au point (x0,y0) supposé sur la courbe
-- si le paramètre long est égal à nil, on trace toute la droite, sinon, un segment de longueur long
    if type(long) ~= "number" then draw_options = long; long = nil end
    local S = ld.tangentI(f,x0,y0,long)
    if long == nil then self:Dline(S,draw_options)
    else self:Dseg(S,1,draw_options)
    end
end

function luadraw_graph:Dtangent_from(A,p,t1,t2,dp,draw_options,out,scale)
-- dessin des tangentes à la courbe paramétrée par p:t ->p(t), issues du point from A (nombre complexe)
-- t1,t2 :bornes d el'intervalle de recherche
-- dp (optionnel) fonction dérivée de p
-- out doit être une table, elle permet de récupérer les points de contacts
    if type(dp) ~= "function" then 
        scale = out; out = draw_options; draw_options = dp; dp = nil 
    end
    out = out or {}
    local S = ld.tangent_from(A,p,t1,t2,dp)
    for _,B in ipairs(S) do
        table.insert(out,B)
        self:Dline(A,B,draw_options,scale)
    end
end

function luadraw_graph:Dnormal(p,t0,long,draw_options)
-- dessin de la normale à la courbe paramétrée par t->p(t) (à valeurs complexes)
-- au point de paramètre t0. 
-- si le paramètre long est égal à nil, on trace toute la droite, sinon, un segment de longueur long
    if type(long) ~= "number" then draw_options = long; long = nil end
    local S = ld.normal(p,t0,long)
    if long == nil then self:Dline(S,draw_options)
    else self:Dseg(S,1,draw_options)
    end
end

function luadraw_graph:DnormalC(f,x0,long,draw_options)
-- dessin de la normale à la courbe cartésienne d'équation y=f(x)
-- au point d'abscisse x0
-- si le paramètre long est égal à nil, on trace toute la droite, sinon, un segment de longueur long
    if type(long) ~= "number" then draw_options = long; long = nil end
    local S = ld.normalC(f,x0,long)
    if long == nil then self:Dline(S,draw_options)
    else self:Dseg(S,1,draw_options)
    end
end

function luadraw_graph:DnormalI(f,x0,y0,long,draw_options)
-- dessin de la normale à la courbe implicite d'équation f(x,y)=0
-- au point (x0,y0) supposé sur la courbe
-- si le paramètre long est égal à nil, on trace toute la droite, sinon, un segment de longueur long
    if type(long) ~= "number" then draw_options = long; long = nil end
    local S = ld.normalI(f,x0,y0,long)
    if long == nil then self:Dline(S,draw_options)
    else self:Dseg(S,1,draw_options)
    end
end

----- Labels -----------------------------------------------------
function luadraw_graph:Dlabel(...) -- Dlabel(texte,anchor,options, texte,anchor,options, ... )
-- dessiner un label, anchor est un complexe (point), 
-- args est une table à 3 entrées { pos = nil, dist = 0, dir=nil, node_options = "" }
-- pos est "center" ou "N", "NE", "NE", "SE", "S", "SW", "W" ou "NW"
-- dist est la distance en cm du texte par rapport au node, si dist = nil c'est 0 par défaut
-- dir={dirX,dirY,dep} est la direction de l'écriture (nil pour le sens par défaut)
-- node_options est une chaîne passée directement à tikz, ex: "rotate=45, draw, fill=red" (options locales)
   local style = { ["N"] = "above", ["NE"] = "above right", ["NW"] = "above left",
                    ["S"] = "below", ["SE"] = "below right", ["SW"] = "below left",
                    ["W"] = "left", ["E"] = "right" }
    local options, first, sep = "", true, ""
    if self.param.labelcolor ~= "" then options = "color="..self.param.labelcolor; sep = "," end
    local commande = "\\draw[-"..sep..options.."]"
    local texte, anchor, pos, dist, dir = "", nil, self.param.labelstyle, 0, self.param.labeldir
    local node_options = ""
    local args = {}
    local x1,x2,y1,y2 = table.unpack(self:Getview())
    local pt = 0.4/ld.mm -- épaisseur de 0.4pt exprimée en cm
    local epX, epY = pt/self.Xscale, pt/self.Yscale
    
    local alabel=function() -- dessiner un label
        if (texte == nil) or (texte == "") or (anchor == nil) then return end
        if self.param.labelsize ~= "" then texte = "\\"..self.param.labelsize.." "..texte end
        pos = args.pos or pos
        dist = args.dist or dist
        dir = args.dir or dir
        if (type(dir) == "number") or isComplex(dir) then dir = {dir} end
        node_options = args.node_options or node_options
        anchor = toComplex(anchor)
        if (anchor == nil) then return end
        local dir_str = ""
        if (type(dir) == "table") and (#dir > 0) then -- changement de direction de l'écriture
            local u, v, dep = table.unpack(dir)
            u = toComplex(u); 
            if v == nil then v = cpx.I*u else v = toComplex(v) end
            if dep == nil then dep = 0 end
            dep = toComplex(dep)
            if not ld.isID(self.matrix) then
                u, v = table.unpack( self:MLtransform({u,v}) )
            end
            u, v = cpx.normalize(u), cpx.normalize(v)
            dir_str = "cm={"..strReal(u.re)..","..strReal(u.im)..","..strReal(v.re)..","..strReal(v.im)..",("..strReal(dep.re)..","..strReal(dep.im)..")}"
        end
        local tikz_pos = style[pos]
        sep = ""
        if tikz_pos ~= nil then sep = "," else tikz_pos = "" end
        if (dist > 0) and (tikz_pos ~= "") then tikz_pos = tikz_pos.."="..dist.."cm" end
        if not ld.isID(self.matrix) then
            anchor = ld.applymatrix(anchor,self.matrix)
        end
        if (anchor.re < x1-epX) or (anchor.re > x2+epX) or (anchor.im < y1-epY) or (anchor.im > y2+epY) then return end -- point en dehors de la vue courante
        if (self.param.labelangle ~= 0) then tikz_pos = tikz_pos..sep.."rotate="..self.param.labelangle end
        if first then self:Write(commande); first = false end
        local opt_str = tikz_pos
        if (node_options == "") or (tikz_pos == "") then sep = "" else sep = "," end
        opt_str = opt_str..sep..node_options
        if (opt_str == "") or (dir_str == "") then sep = "" else sep = "," end
        opt_str = opt_str..sep..dir_str
        self:Write(" "..self:Coord(anchor).." node["..opt_str.."] {"..texte.."}")
    end
    local n = select("#", ...)  -- Nombre total d'arguments
    for i = 1, n-2, 3 do   -- Pas de 3 (1,4,7...)
        texte, anchor, args = select(i, ...)  -- Récupère les 3 args
        if anchor ~= nil then alabel() 
        else
            print("Warning : the anchor point associated with the text "..texte.." is equal to nil")
        end
    end
    if not first then self:Writeln(";") end
end


------ dots  -------------------------------------------------------
function luadraw_graph:Ddots(L, mark_options)
-- L doit une liste de complexes représentant les points à dessiner (ou liste de listes)
-- mark_options est une chaîne d'options définissant marker options dans tikz
-- exemple "color=green, line width=1.2, scale=0.25"
    L = (L or {})
    if (type(L) == "number") or isComplex(L) then L = {{L}} end
    if (type(L) ~= "table") or (#L == 0) then return end
    if (type(L[1]) == "number") or isComplex(L[1]) then L = {L} end
    mark_options = (mark_options or "")
    local scale = self.param.dotscale
    local r = (self.param.dotsize.re + self.param.dotsize.im*self.param.linewidth/10)/2
    r = r*scale
    local first = true
    local commande = "\\draw[mark="..self.param.dotstyle..",mark size="..r.."pt"
    if mark_options ~= "" then commande = commande..",mark options={"..mark_options.."}" end
    
    local drawdot = function(dots)
        if first then self:Write(commande.."]"); first = false end
        for _,z in ipairs(dots) do
            self:Write(" plot coordinates {"..self:Coord(z).."}")
        end
    end   

    for _, aux in ipairs(L) do
        local len = #aux
        if len > 0 then
            if not ld.isID(self.matrix) then
                aux = self:Mtransform(aux)  --application de la matrice courante
            end
            local X1,X2,Y1,Y2 = table.unpack(self.param.viewport)
            aux = ld.clipdots(aux,X1,X2,Y1,Y2) -- élimination des "trop grands" et conversion reel -> complexe, on récupère une liste de listes
            for _,cp in ipairs(aux) do 
                drawdot(cp) 
            end
        end
    end
    if not first then self:Writeln(";") end
end

function luadraw_graph:Dlabeldot(texte,anchor,args)
-- affiche un texte et le point d'ancrage (dans le style courant), args est identique au cas de Dlabel plus le champ mark_options
    anchor = toComplex(anchor)
    args =  args or {}
    local mark_options = args.mark_options or ""
    self:Dlabel(texte,anchor,args)
    if mark_options == "" then mark_options = "color="..self.param.linecolor
    else mark_options = "color="..self.param.linecolor..","..mark_options
    end
    self:Ddots(anchor,mark_options)
end

----- angle droit, arc, secteur ------------------------------------------------------
function luadraw_graph:Dangle(B,A,C,r,draw_options)
    A, B, C = toComplex(A), toComplex(B), toComplex(C)
    if (A == nil) or (B == nil) or (C == nil) then return end
    if type(r) ~= "number" then draw_optiopns = r; r = 0.25 end
    local D, E, F = ld.angleD(B,A,C,r)
    if (D == nil) or (E == nil) or (F == nil) then return end
    if self.param.fillstyle ~= "none" then
        local oldL = self.param.linestyle
        local oldF = self.param.fillstyle
        self:Linestyle("noline")
        self:Dpolyline({{D,E,F,A}},true,draw_options)
        self:Linestyle(oldL)
        self.param.fillstyle = "none"
        self:Dpolyline({{D,E,F}},false,draw_options)
        self.param.fillstyle = oldF
    else self:Dpolyline({{D,E,F}},false,draw_options)
    end
end

--------- Dessin de chemins --------------------------------------------

-- path
function luadraw_graph:Dpath(L,draw_options,clip) 
-- dessine le chemin contenu dans L, L est une table de complexes et d'instructions
-- ex: Dpath( {-1,2+i,3,"l", 4, "m", -2*i,-3-3*i,"l","cl",...} )
-- "m" pour moveto, "l" pour lineto, "b" pour bézier, "c" pour cercle, "ca" pour arc de cercle, "ea" arc d'ellipse, "e" pour ellipse, "s" pour spline naturelle, "cl" pour close
-- "la" pour line arc (ligne aux coins arrondis), "cla" ligne fermée aux coins arrondis
    clip = clip or false -- indique si  L est un chemin de clipping
    if (L == nil) or (type(L) ~= "table") or (#L < 3) then return end
    draw_options = draw_options or ""
    local commande
    if clip then commande = "\\clip "
    else commande = self:drawcmd(draw_options,clip)
    end
    if commande == nil then return end
    local debut = true
    local res = {} -- résultat
    local crt = {} -- composante courante
    local aux = {} -- lecture en cours
    local last, first = nil, nil -- dernier lu et premier à venir
    local traiter
    local Mcoord = function(z) return self:Coord(z) end
    
    local Tcoord = function(z) -- applique la matrice courante à z
        return self:Coord(ld.applymatrix(z,self.matrix))
    end
    
    if not ld.isID(self.matrix) then Mcoord = Tcoord end -- transformation des points par la matrice courante
    
    local lineto = function() -- traitement du lineto
        -- on relie les points par une ligne
        if debut then self:Write(commande); debut = false end
        if first ~= nil then self:Write(" -- ") end
        self:Write(Mcoord(aux[1]))
        for k = 2, #aux do
            self:Write(" -- "..Mcoord(aux[k]))
        end
        first = last
        aux = {}
    end
    
    local moveto = function() -- traitement du moveto
    -- on démarre une nouvelle composante
            if not debut then self:Write(" ") end
            first = nil
            aux = {last}
    end
    
    local close = function() -- traitement du closepath
        -- en principe il y a eu une instruction avant autre que move, aux doit être vide et pas crt
        self:Write("--cycle")
        aux = {}
    end
    
    local Bezier = function()
        -- aux contient une ou plusieurs courbes de bézier
        local i
        if debut then self:Write(commande); debut = false end
        if first == nil then i = 1 else i = 2 end
        for _, z in ipairs(aux) do
            if i == 1 then self:Write(Mcoord(z)); i = 2
            else
                if i == 2 then self:Write(" .. controls "..Mcoord(z)); i = 3
                else
                    if i == 3 then self:Write(" and "..Mcoord(z)); i = 4
                    else    
                        if i == 4 then self:Write(" .. "..Mcoord(z)); i = 5 
                        else
                            if i == 5 then i = 2 end -- on est sur le caractère "b"
                        end
                    end
                end
            end
        end
        first = last
        aux = {}
    end
    
    local Spline = function ()
            if first ~= nil then 
            table.insert(aux,1,first)
        end
        aux = ld.spline(aux) -- spline naturelle
        if aux ~= nil then
            if first ~= nil then table.remove(aux,1) end -- le premier point est déjà exporté
            Bezier()
        end
        aux = {}
    end
    
    local Circle = function()
    -- il faut un point et le centre
        if first ~= nil then 
            table.insert(aux,1,first)
        end
        local a, c, r = aux[1], nil, nil
        if #aux == 2 then -- on a un point et le centre
            c = aux[2]; r = cpx.abs(a-c)
        else
            if #aux == 3 then -- on a trois points du cercle
                c = ld.interD(ld.med(a,aux[2]), ld.med(a,aux[3]))
                if c == nil then aux = {}; return end
                r = cpx.abs(a - c)
            else aux = {}; return
            end
        end
        aux = ld.arcb(a,c,a,r,1)
        if aux ~= nil then
            if first ~= nil then table.remove(aux,1) end -- le premier point est déjà exporté
            Bezier()
        end
        aux = {}
    end
    
    local Arc = function()
        local n = #aux
        if (n < 3) or (n > 5) then aux = {}; return end
        if first ~= nil then 
            table.insert(aux,1,first)
        end
        aux = ld.arcb(table.unpack(aux))
        if aux ~= nil then
            if first ~= nil then self:Write(" -- "); first = nil end -- pour relier le premier point de l'arc au précédent
            local newfirst = aux[#aux-1]
            Bezier()
            first = newfirst -- dernier point de l'arc
        end
        aux = {}
    end
    
    local Earc = function() -- ellipticarc(b,a,c,rx,ry,sens,inclin)
        local n = #aux
        if (n < 4) or (n > 7) then aux = {}; return end
        if first ~= nil then 
            table.insert(aux,1,first)
        end
        aux = ld.ellipticarcb(table.unpack(aux))
        if aux ~= nil then
            if first ~= nil then self:Write(" -- "); first = nil end -- pour relier le premier point de l'arc au précédent
            local newfirst = aux[#aux-1]
            Bezier()
            first = newfirst -- dernier point de l'arc
        end
        aux = {}
    end    
    
    local Ellipse = function() -- ellipse(p,c,rx,ry,sens,inclin)
        local n = #aux
        if (n < 3) or (n > 5) then aux = {}; return end
        if first ~= nil then 
            table.insert(aux,1,first)
        end
        local p, c, rx, ry, inclin = table.unpack(aux)
        aux = ld.ellipticarcb(p,c,p,rx,ry,1,inclin)
        if aux ~= nil then
            if first ~= nil then self:Write(" -- "); first = nil end -- pour relier le premier point de l'arc au précédent
            local newfirst = aux[#aux-1]
            Bezier()
            first = newfirst -- dernier point de l'arc
        end
        aux = {}
    end 
    local Rline = function(close) --on appelle roundline(L,r)
        local n = #aux
        if (n < 2) then aux = {}; return end
        if first ~= nil then 
            table.insert(aux,1,first)
        end
        local r = table.remove(aux)
        if (type(r) ~= "number") or (r <= 0) then aux = {}; return end
        local C = ld.roundline(aux,r,close,true)
        if C ~= nil then
            if first ~= nil then 
                if not close then table.remove(C,1) -- le premier point est déjà exporté
                end
            end 
            aux = {}
            for _,z in ipairs(C) do
                if (type(z) == "number") or isComplex(z) then table.insert(aux,z); last = z 
                else
                    if type(z) == "string" then traiter[z]() end
                end            
            end
        end
        aux = {}
    end
    
    local cRline = function()
        Rline(true)
    end
-- corps de la fonction dpath
    local clippee
    local aux2 = ld.path(L)
    if not ld.isID(self.matrix) then aux2 = self:Mtransform(aux2) end
    local X1,X2,Y1,Y2 = table.unpack(self.param.viewport)
    clippee = (not clip) and ld.needclip(aux2,X1,X2,Y1,Y2)
    if clippee and self.bbox then
        self:Writeln("\\begin{scope}")
        self:Writeln("\\clip "..self:strCoord(X1,Y1).." rectangle "..self:strCoord(X2,Y2)..";")
    end
    traiter = { ["s"]=Spline, ["l"]=lineto, ["m"]=moveto, ["cl"]=close, ["b"]=Bezier, ["c"]=Circle, ["ca"]=Arc, ["ea"]=Earc, ["e"]=Ellipse, ["la"]=Rline, ["cla"]=cRline } 
    for _, z in ipairs(L) do
        if (type(z) == "number") or isComplex(z) then table.insert(aux,z); last = z 
        else
            if type(z) == "string" then traiter[z]() end
        end
    end
    if not debut then self:Writeln(";") end
    if clippee and self.bbox then self:Writeln("\\end{scope}") end
end

-- clipping avec un chemin
function luadraw_graph:Beginclip(p,inverse) -- p = path
    inverse = inverse or false
    self:Writeln("\\begin{scope}")
    if inverse then
        local chem = self:Box2d()
        --local chem = reverse(self:Box2d())
        local L = ld.path(p)[1]
        local G = cpx.isobar(L)
        local A, B = L[1], L[2]
        if cpx.det(A-B,B-G) >= 0 then chem = ld.reverse(chem) end
        ld.insert(chem,{"l","cl"})
        table.insert(p,2,"m")
        self:Dpath( ld.concat(chem,p),"",true) -- path doit être dans le sens trigonométrique
    else self:Dpath(p,"",true)
    end
end

function luadraw_graph:Endclip()
    self:Writeln("\\end{scope}")
end

-- arc de cercle
function luadraw_graph:Darc(B,A,C,r,sens,draw_options)
-- dessine un arc de cercle de centre A, allant de B à C avec un rayon de r en cm
-- sens = 1 pour sens trigo ou -1 sinon
    local S = ld.arcb(B,A,C,r,sens or 1) -- arc en courbes de Bézier
    if S == nil then return end
    if self.param.fillstyle ~= "none" then
        local oldL = self.param.linestyle
        local oldF = self.param.fillstyle
        self:Linestyle("noline")
        self:Dpath(ld.concat(S,{A,"l","cl"}),draw_options)
        self:Linestyle(oldL)
        self.param.fillstyle = "none"
        self:Dpath(S,draw_options)
        self.param.fillstyle = oldF
    else self:Dpath(S,draw_options)
    end
end

function luadraw_graph:Dwedge(B,A,C,r,sens,draw_options)
-- dessine un secteur angulaire
    self:Dpath( {B,A,C,r,sens,"ca",A,"l","cl"}, draw_options)
end

--cercle
function luadraw_graph:Dcircle(c,r,d,draw_options) -- ou Dcircle({c,r,d},draw_options)
-- dessine le cercle de centre c et de rayon r (si d=nil) ou bien passant par les points c, r et d
    if (type(c) == "table") and (not isComplex(c)) then
        if type(r) ~= "number" then draw_options = r end
        c,r,d = table.unpack(c)
    else
        if (type(d) ~= "number") and (not isComplex(d)) then draw_options = d; d = nil end
    end
    local S = ld.circleb(c,r,d)
    self:Dpath(S,draw_options) -- cercle en courbes de Bézier
end

--ellipse
function luadraw_graph:Dellipse(c,rx,ry,inclin,draw_options)
-- renvoie les points de l'ellipse de centre c et de rayons rx et ry, inclin est l'inclinaison en degrés par rapport à l'horizontale (0 par défaut)
   local S = ld.ellipseb(c,rx,ry,inclin)
   self:Dpath(S,draw_options) -- ellipse en courbes de Bézier
end

-- arc d'ellipse
function luadraw_graph:Dellipticarc(B, A, C, rx, ry, sens, inclin,draw_options)
-- dessine un arc d'ellipse de centre A, et de AB vers AC
-- rx et ry sont en cm
-- sens = +/-1 (1 pour le sens trigo), inclin est l'inclinaison en degrés par rapport à l'horizontale
    if type(inclin) ~= "number" then draw_options = inclin; inclin = nil end
    local S = ld.ellipticarcb(B, A, C, rx, ry, sens or 1, inclin or 0)
    self:Dpath(S,draw_options) -- arc d'ellipse en courbes de Bézier
end   

-- gestion des couleurs ------------------------------------------------

function luadraw_graph:Newcolor(name, color)
-- definit dans l'export tikz une nouvelle couleur
-- name est le nom (chaîne)
-- color est une table de trois composantes: rouge, vert, bleu (entre 0 et 1) ou bien une chaîne définissant une couleur
    if type(color) == "table" then
        self:Writeln("\\definecolor{"..name.."}{rgb}{"..strReal(color[1])..","..strReal(color[2])..","..strReal(color[3]).."}")
    elseif type(color) == "string" then
        self:Writeln("\\colorlet{"..name.."}{"..color.."}")
    end
end

------------- import an image --------------------------
function luadraw_graph:Dimage(file,anchor,options)
-- file = string (full name of the image file)
-- anchor = complex number
-- options = {pos="center", matrix=nil, name="", graphics_options=""}
    local style = { ["N"] = "above", ["NE"] = "above right", ["NW"] = "above left",
                    ["S"] = "below", ["SE"] = "below right", ["SW"] = "below left",
                    ["W"] = "left", ["E"] = "right" }
    options = options or {}
    anchor = toComplex(anchor)
    anchor = ld.applymatrix(anchor,self.matrix)
    local pos = options.pos or "center"
    pos = style[pos]
    local name = options.name or ""
    if name ~= "" then name = "("..name..") " end
    local graphics_options = options.graphics_options or ""
    local mat 
    if ld.isID(self.matrix) then
        mat = options.matrix 
        if mat == nil then mat = ld.ID end
    else
        mat = {Z(0,0), self.matrix[2], self.matrix[3]}
        if options.matrix ~=nil then mat = ld.composematrix(mat, options.matrix) end
    end
    if not ld.isID(mat) then
        local t,u,v = table.unpack( ld.map(toComplex,mat) )
        t = Z(t.re*self.Xscale, t.im*self.Yscale)
        u = Z(u.re*self.Xscale, u.im*self.Xscale)
        v = Z(v.re*self.Xscale, v.im*self.Yscale)
        mat = ",cm={"..strReal(u.re)..","..strReal(u.im)..","..strReal(v.re)..","..strReal(v.im)..",("..strReal(t.re)..","..strReal(t.im)..")}"
    else mat = ""
    end
    if pos ~= nil then pos = ","..pos else pos = "" end
    local cmd = "\\node[line width=0.3pt,inner sep=-0.15pt"..mat..pos.."] "..name.."at ".. self:Coord(anchor).."{\\includegraphics["..graphics_options.."]{"..file.."}};"
    self:Writeln(cmd)
end

function luadraw_graph:Dmapimage(file, parallelo, options)
-- file = string (name of the image)
-- parallelogram = {vertice, vector1, vector2} 
    options = options or {}
    local name = options.name or ""
    local clip = options.clip or false
    local border = options.border_options or nil -- draw_options to draw the border
    local a, u, v = table.unpack( parallelo )
    if clip then self:Beginclip( {a, a+u,a+u+v,a+v,"l","cl"} ) end
        self:Dimage(file, Z(0,0),{pos="NE", name=name, matrix={a,u,v}, 
            graphics_options="width=1cm,height=1cm"})
    if clip then self:Endclip() end
    if border ~= nil then
        self:Dpolyline(ld.parallelogram(a,u,v), true, border)
    end
end

return luadraw_graph
