<?php
/**
 * Html2Pdf Library - myPdf class
 *
 * HTML => PDF converter
 * distributed under the OSL-3.0 License
 *
 * @package   Html2pdf
 * @author    Laurent MINGUET <webmaster@html2pdf.fr>
 * @copyright 2025 Laurent MINGUET
 */

namespace Spipu\Html2Pdf;

use Spipu\Html2Pdf\Exception\HtmlParsingException;
use TCPDF;

class MyPdf extends TCPDF
{
    protected $_footerParam = array();
    protected $_transf      = array();
    protected $_myLastPageGroup = null;
    protected $_myLastPageGroupNb = 0;

    // used to make a radius with bezier : (4/3 * (sqrt(2) - 1))
    const MY_ARC = 0.5522847498;

    // nb of segment to build an arc with bezier curv
    const ARC_NB_SEGMENT = 8;

    /**
     * @var float
     */
    protected $ws = 0.;

    /**
     * class constructor
     *
     * @param string  $orientation page orientation, same as TCPDF
     * @param string  $unit        User measure unit, same as TCPDF
     * @param mixed   $format      The format used for pages, same as TCPDF
     * @param boolean $unicode     TRUE means that the input text is unicode (default = true)
     * @param string  $encoding    charset encoding; default is UTF-8
     * @param boolean $diskcache   if TRUE reduce the RAM memory usage by caching temporary data on filesystem (slower).
     * @param false|int $pdfa        If TRUE set the document to PDF/A mode.
     * @access public
     */
    public function __construct(
        $orientation = 'P',
        $unit = 'mm',
        $format = 'A4',
        $unicode = true,
        $encoding = 'UTF-8',
        $diskcache = false,
        $pdfa = false
    ) {
        // call the parent constructor
        parent::__construct($orientation, $unit, $format, $unicode, $encoding, $diskcache, $pdfa);

        // init the specific parameters used by Html2Pdf
        $this->SetCreator(PDF_CREATOR);
        $this->SetAutoPageBreak(false, 0);
        $this->linestyleCap = '2 J';
        $this->setPrintHeader(false);
        $this->jpeg_quality = 90;

        // prepare the automatic footer
        $this->SetMyFooter();

        $this->setCellPaddings(0, 0, 0, 0);
        $this->setCellMargins(0, 0, 0, 0);
    }

    /**
     * Set the parameters for the automatic footer
     *
     * @param boolean $page display the page number
     * @param boolean $date display the date
     * @param boolean $time display the time
     * @param boolean $form display a warning abour forms
     * @access public
     */
    public function SetMyFooter($page = false, $date = false, $time = false, $form = false)
    {
        $page    = ($page ? true : false);
        $date    = ($date ? true : false);
        $time    = ($time ? true : false);
        $form    = ($form ? true : false);

        $this->_footerParam = array('page' => $page, 'date' => $date, 'time' => $time, 'form' => $form);
    }

    /**
     * This function is call automatically by TCPDF at the end of a page
     * It takes no parameters
     *
     * @access public
     */
    public function Footer()
    {
        // prepare the text from the tranlated text
        $txt = '';
        if ($this->_footerParam['form']) {
            $txt = Locale::get('pdf05');
        }
        if ($this->_footerParam['date'] && $this->_footerParam['time']) {
            $txt.= ($txt ? ' - ' : '').Locale::get('pdf03');
        }
        if ($this->_footerParam['date'] && !$this->_footerParam['time']) {
            $txt.= ($txt ? ' - ' : '').Locale::get('pdf01');
        }
        if (!$this->_footerParam['date'] && $this->_footerParam['time']) {
            $txt.= ($txt ? ' - ' : '').Locale::get('pdf02');
        }
        if ($this->_footerParam['page']) {
            $txt.= ($txt ? ' - ' : '').Locale::get('pdf04');
        }

        if (strlen($txt)>0) {
            // replace some values
            $toReplace = array(
                '[[date_d]]'  => date('d'),
                '[[date_m]]'  => date('m'),
                '[[date_y]]'  => date('Y'),
                '[[date_h]]'  => date('H'),
                '[[date_i]]'  => date('i'),
                '[[date_s]]'  => date('s'),
                '[[page_cu]]' => $this->getMyNumPage(),
                '[[page_nb]]' => $this->getMyAliasNbPages(),
            );
            $txt = str_replace(array_keys($toReplace), array_values($toReplace), $txt);

            // draw the footer
            parent::SetY(-11);
            $this->SetFont('helvetica', 'I', 8);
            $this->Cell(0, 10, $txt, 0, 0, 'R');
        }
    }

    /**
     * after cloning an object, we do not want to clone all the front information
     * because it takes a lot a time and a lot of memory => we use reference
     *
     * @param myPdf &$pdf
     * @access public
     */
    public function cloneFontFrom(&$pdf)
    {
        $this->n                = &$pdf->getN();
        $this->fonts            = &$pdf->getFonts();
        $this->FontFiles        = &$pdf->getFontFiles();
        $this->diffs            = &$pdf->getDiffs();
        $this->fontlist         = &$pdf->getFontList();
        $this->numfonts         = &$pdf->getNumFonts();
        $this->fontkeys         = &$pdf->getFontKeys();
        $this->font_obj_ids     = &$pdf->getFontObjIds();
        $this->annotation_fonts = &$pdf->getAnnotFonts();
    }

    /**
     * multiple public accessor for some private attributs
     * used only by cloneFontFrom
     *
     * @return array
     * @access public
     */
    public function &getFonts()
    {
        return $this->fonts;
    }
    public function &getFontFiles()
    {
        return $this->FontFiles;
    }
    public function &getDiffs()
    {
        return $this->diffs;
    }
    public function &getFontList()
    {
        return $this->fontlist;
    }
    public function &getNumFonts()
    {
        return $this->numfonts;
    }
    public function &getFontKeys()
    {
        return $this->fontkeys;
    }
    public function &getFontObjIds()
    {
        return $this->font_obj_ids;
    }
    public function &getAnnotFonts()
    {
        return $this->annotation_fonts;
    }
    public function &getN()
    {
        return $this->n;
    }

    /**
     * Verify that a Font is already loaded
     *
     * @param string $fontKey Font Key
     * @return boolean
     * @access public
     */
    public function isLoadedFont($fontKey)
    {
        if (isset($this->fonts[$fontKey])) {
            return true;
        }

        if (isset($this->CoreFonts[$fontKey])) {
            return true;
        }

        return false;
    }

    /**
     * Get the Word Spacing
     *
     * @access public
     * @return float word spacing
     */
    public function getWordSpacing()
    {
        return $this->ws;
    }

    /**
     * set the Word Spacing
     *
     * @param float $ws word spacing
     * @access public
     */
    public function setWordSpacing($ws = 0.)
    {
        $this->ws = $ws;
        $this->_out(sprintf('%.3F Tw', $ws*$this->k));
    }

    /**
     * start to use a rectangular Cliping Path with radius corners
     *
     * @param float $x (top left corner)
     * @param float $y (top left corner)
     * @param float $w (x+w  = botom rigth corner)
     * @param float $h (y+h = botom rigth corner)
     * @param array $cornerTL radius of the Top Left corner
     * @param array $cornerTR radius of the Top Right corner
     * @param array $cornerBL radius of the Bottom Left corner
     * @param array $cornerBR radius of the Bottom Right corner
     * @access public
     */
    public function clippingPathStart(
        $x = null,
        $y = null,
        $w = null,
        $h = null,
        $cornerTL = null,
        $cornerTR = null,
        $cornerBL = null,
        $cornerBR = null
    ) {

        // init the path
        $path = '';

        // if we have the position and the size of the rectangle, we can proceed
        if ($x !== null && $y !== null && $w !== null && $h !== null) {
            // the positions of the rectangle's corners
            $x1 = $x*$this->k;
            $y1 = ($this->h-$y)*$this->k;

            $x2 = ($x+$w)*$this->k;
            $y2 = ($this->h-$y)*$this->k;

            $x3 = ($x+$w)*$this->k;
            $y3 = ($this->h-$y-$h)*$this->k;

            $x4 = $x*$this->k;
            $y4 = ($this->h-$y-$h)*$this->k;

            // if we have at least one radius corner, then we proceed to a specific path, else it is just a rectangle
            if ($cornerTL || $cornerTR || $cornerBL || $cornerBR) {
                // prepare the radius values
                if ($cornerTL) {
                    $cornerTL[0] = $cornerTL[0]*$this->k;
                    $cornerTL[1] =-$cornerTL[1]*$this->k;
                }
                if ($cornerTR) {
                    $cornerTR[0] = $cornerTR[0]*$this->k;
                    $cornerTR[1] =-$cornerTR[1]*$this->k;
                }
                if ($cornerBL) {
                    $cornerBL[0] = $cornerBL[0]*$this->k;
                    $cornerBL[1] =-$cornerBL[1]*$this->k;
                }
                if ($cornerBR) {
                    $cornerBR[0] = $cornerBR[0]*$this->k;
                    $cornerBR[1] =-$cornerBR[1]*$this->k;
                }

                // if TL radius then specific start else (X1,Y1)
                if ($cornerTL) {
                    $path.= sprintf('%.2F %.2F m ', $x1+$cornerTL[0], $y1);
                } else {
                    $path.= sprintf('%.2F %.2F m ', $x1, $y1);
                }

                // if TR radius then line + arc, else line to (X2,Y2)
                if ($cornerTR) {
                    $xt1 = ($x2-$cornerTR[0])+$cornerTR[0]*self::MY_ARC;
                    $yt1 = ($y2+$cornerTR[1])-$cornerTR[1];
                    $xt2 = ($x2-$cornerTR[0])+$cornerTR[0];
                    $yt2 = ($y2+$cornerTR[1])-$cornerTR[1]*self::MY_ARC;

                    $path.= sprintf('%.2F %.2F l ', $x2-$cornerTR[0], $y2);
                    $path.= sprintf('%.2F %.2F %.2F %.2F %.2F %.2F c ', $xt1, $yt1, $xt2, $yt2, $x2, $y2+$cornerTR[1]);
                } else {
                    $path.= sprintf('%.2F %.2F l ', $x2, $y2);
                }

                // if BR radius then line + arc, else line to (X3, Y3)
                if ($cornerBR) {
                    $xt1 = ($x3-$cornerBR[0])+$cornerBR[0];
                    $yt1 = ($y3-$cornerBR[1])+$cornerBR[1]*self::MY_ARC;
                    $xt2 = ($x3-$cornerBR[0])+$cornerBR[0]*self::MY_ARC;
                    $yt2 = ($y3-$cornerBR[1])+$cornerBR[1];

                    $path.= sprintf('%.2F %.2F l ', $x3, $y3-$cornerBR[1]);
                    $path.= sprintf('%.2F %.2F %.2F %.2F %.2F %.2F c ', $xt1, $yt1, $xt2, $yt2, $x3-$cornerBR[0], $y3);
                } else {
                    $path.= sprintf('%.2F %.2F l ', $x3, $y3);
                }

                // if BL radius then line + arc, else line to (X4, Y4)
                if ($cornerBL) {
                    $xt1 = ($x4+$cornerBL[0])-$cornerBL[0]*self::MY_ARC;
                    $yt1 = ($y4-$cornerBL[1])+$cornerBL[1];
                    $xt2 = ($x4+$cornerBL[0])-$cornerBL[0];
                    $yt2 = ($y4-$cornerBL[1])+$cornerBL[1]*self::MY_ARC;

                    $path.= sprintf('%.2F %.2F l ', $x4+$cornerBL[0], $y4);
                    $path.= sprintf('%.2F %.2F %.2F %.2F %.2F %.2F c ', $xt1, $yt1, $xt2, $yt2, $x4, $y4-$cornerBL[1]);
                } else {
                    $path.= sprintf('%.2F %.2F l ', $x4, $y4);
                }

                // if RL radius then line + arc
                if ($cornerTL) {
                    $xt1 = ($x1+$cornerTL[0])-$cornerTL[0];
                    $yt1 = ($y1+$cornerTL[1])-$cornerTL[1]*self::MY_ARC;
                    $xt2 = ($x1+$cornerTL[0])-$cornerTL[0]*self::MY_ARC;
                    $yt2 = ($y1+$cornerTL[1])-$cornerTL[1];

                    $path.= sprintf('%.2F %.2F l ', $x1, $y1+$cornerTL[1]);
                    $path.= sprintf('%.2F %.2F %.2F %.2F %.2F %.2F c ', $xt1, $yt1, $xt2, $yt2, $x1+$cornerTL[0], $y1);
                }
            } else {
                $path.= sprintf('%.2F %.2F m ', $x1, $y1);
                $path.= sprintf('%.2F %.2F l ', $x2, $y2);
                $path.= sprintf('%.2F %.2F l ', $x3, $y3);
                $path.= sprintf('%.2F %.2F l ', $x4, $y4);
            }

            // close the path
            $path.= ' h W n';
        }

        // using the path as a clipping path
        $this->_out('q '.$path.' ');
    }

    /**
     * stop to use the Cliping Path
     *
     * @access public
     */
    public function clippingPathStop()
    {
        $this->_out(' Q');
    }

    /**
     * draw a filled corner of a border with a external and a internal radius
     *         /--------+ ext2
     *        /         |
     *       /  /-------+ int2
     *      /  /
     *      | /
     *      | |
     *      | |
     * ext1 +-+ int1        + cen
     *
     * @param float $ext1X
     * @param float $ext1Y
     * @param float $ext2X
     * @param float $ext2Y
     * @param float $int1X
     * @param float $int1Y
     * @param float $int2X
     * @param float $int2Y
     * @param float $cenX
     * @param float $cenY
     * @access public
     */
    public function drawCurve($ext1X, $ext1Y, $ext2X, $ext2Y, $int1X, $int1Y, $int2X, $int2Y, $cenX, $cenY)
    {
        // prepare the coordinates
        $ext1X = $ext1X*$this->k;
        $ext2X = $ext2X*$this->k;
        $int1X = $int1X*$this->k;
        $int2X = $int2X*$this->k;
        $cenX  = $cenX*$this->k;

        $ext1Y = ($this->h-$ext1Y)*$this->k;
        $ext2Y = ($this->h-$ext2Y)*$this->k;
        $int1Y = ($this->h-$int1Y)*$this->k;
        $int2Y = ($this->h-$int2Y)*$this->k;
        $cenY  = ($this->h-$cenY) *$this->k;

        // init the curve
        $path = '';

        if ($ext1X-$cenX !=0) {
            $xt1 = $cenX+($ext1X-$cenX);
            $yt1 = $cenY+($ext2Y-$cenY)*self::MY_ARC;
            $xt2 = $cenX+($ext1X-$cenX)*self::MY_ARC;
            $yt2 = $cenY+($ext2Y-$cenY);
        } else {
            $xt1 = $cenX+($ext2X-$cenX)*self::MY_ARC;
            $yt1 = $cenY+($ext1Y-$cenY);
            $xt2 = $cenX+($ext2X-$cenX);
            $yt2 = $cenY+($ext1Y-$cenY)*self::MY_ARC;
        }
        $path.= sprintf('%.2F %.2F m ', $ext1X, $ext1Y);
        $path.= sprintf('%.2F %.2F %.2F %.2F %.2F %.2F c ', $xt1, $yt1, $xt2, $yt2, $ext2X, $ext2Y);

        if ($int1X-$cenX !=0) {
            $xt1 = $cenX+($int1X-$cenX)*self::MY_ARC;
            $yt1 = $cenY+($int2Y-$cenY);
            $xt2 = $cenX+($int1X-$cenX);
            $yt2 = $cenY+($int2Y-$cenY)*self::MY_ARC;
        } else {
            $xt1 = $cenX+($int2X-$cenX);
            $yt1 = $cenY+($int1Y-$cenY)*self::MY_ARC;
            $xt2 = $cenX+($int2X-$cenX)*self::MY_ARC;
            $yt2 = $cenY+($int1Y-$cenY);
        }
        $path.= sprintf('%.2F %.2F l ', $int2X, $int2Y);
        $path.= sprintf('%.2F %.2F %.2F %.2F %.2F %.2F c ', $xt1, $yt1, $xt2, $yt2, $int1X, $int1Y);

        // draw the curve
        $this->_out($path . 'f');
    }

    /**
     * draw a filled corner of a border with only a external radius
     *         /--+ ext2
     *        /   |
     *       /    |
     *      /     |
     *      |     |
     *      |     |
     *      |     |
     * ext1 +-----+ int      + cen
     *
     * @param float $ext1X
     * @param float $ext1Y
     * @param float $ext2X
     * @param float $ext2Y
     * @param float $intX
     * @param float $intY
     * @param float $cenX
     * @param float $cenY
     * @access public
     */
    public function drawCorner($ext1X, $ext1Y, $ext2X, $ext2Y, $intX, $intY, $cenX, $cenY)
    {
        // prepare the coordinates
        $ext1X = $ext1X*$this->k;
        $ext2X = $ext2X*$this->k;
        $intX  = $intX*$this->k;
        $cenX  = $cenX*$this->k;

        $ext1Y = ($this->h-$ext1Y)*$this->k;
        $ext2Y = ($this->h-$ext2Y)*$this->k;
        $intY  = ($this->h-$intY)*$this->k;
        $cenY  = ($this->h-$cenY)*$this->k;

        // init the curve
        $path = '';

        if ($ext1X-$cenX !=0) {
            $xt1 = $cenX+($ext1X-$cenX);
            $yt1 = $cenY+($ext2Y-$cenY)*self::MY_ARC;
            $xt2 = $cenX+($ext1X-$cenX)*self::MY_ARC;
            $yt2 = $cenY+($ext2Y-$cenY);
        } else {
            $xt1 = $cenX+($ext2X-$cenX)*self::MY_ARC;
            $yt1 = $cenY+($ext1Y-$cenY);
            $xt2 = $cenX+($ext2X-$cenX);
            $yt2 = $cenY+($ext1Y-$cenY)*self::MY_ARC;
        }
        $path.= sprintf('%.2F %.2F m ', $ext1X, $ext1Y);
        $path.= sprintf('%.2F %.2F %.2F %.2F %.2F %.2F c ', $xt1, $yt1, $xt2, $yt2, $ext2X, $ext2Y);
        $path.= sprintf('%.2F %.2F l ', $intX, $intY);
        $path.= sprintf('%.2F %.2F l ', $ext1X, $ext1Y);

        // draw the curve
        $this->_out($path . 'f');
    }

    /**
     * Start a transformation
     *
     * @access public
     */
    public function startTransform()
    {
        $this->_out('q');
    }

    /**
     * Stop a transformation
     *
     * @access public
     */
    public function stopTransform()
    {
        $this->_out('Q');
    }

    /**
     * add a Translate transformation
     *
     * @param $xT
     * @param $yT
     * @access public
     */
    public function setTranslate($xT, $yT)
    {
        // Matrix for Translate
        $tm[0]=1;
        $tm[1]=0;
        $tm[2]=0;
        $tm[3]=1;
        $tm[4]=$xT*$this->k;
        $tm[5]=-$yT*$this->k;

        // apply the Transform Matric
        $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
    }

    /**
     * add a Rotate transformation
     *
     * @param float $angle
     * @param float|null $xC
     * @param float|null $yC
     * @access public
     */
    public function setRotation($angle, $xC = null, $yC = null)
    {
        // if no center, rotate around the current posiition
        if ($xC === null) {
            $xC=$this->x;
        }
        if ($yC === null) {
            $yC=$this->y;
        }

        // prepare the coordinate
        $yC=($this->h-$yC)*$this->k;
        $xC*=$this->k;

        // Matrix for Rotate
        $tm[0]=cos(deg2rad($angle));
        $tm[1]=sin(deg2rad($angle));
        $tm[2]=-$tm[1];
        $tm[3]=$tm[0];
        $tm[4]=$xC+$tm[1]*$yC-$tm[0]*$xC;
        $tm[5]=$yC-$tm[0]*$yC-$tm[1]*$xC;

        // apply the Transform Matric
        $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
    }

    /**
     * we redefine the original SetX method, because we don't want the automatic treatment.
     * It is Html2Pdf that make the treatment.
     * If language is RTL direction this method will call to parent (TCPDF class).
     *
     * @param float   $x
     * @param boolean $rtloff
     * @access public
     */
    public function SetX($x, $rtloff = false)
    {
        if (!$rtloff && $this->rtl) {
            parent::SetX($x, $rtloff);
            return;
        }

        $this->x=$x;
    }

    /**
     * we redefine the original SetY method, because we don't want the automatic treatment.
     * It is Html2Pdf that make the treatment.
     * If language is RTL direction this method will call to parent (TCPDF class).
     *
     * @param float   $y
     * @param boolean $resetx Reset the X position
     * @param boolean $rtloff
     * @access public
     */
    public function SetY($y, $resetx = true, $rtloff = false)
    {
        if (!$rtloff && $this->rtl) {
            parent::SetY($y, $resetx, $rtloff);
            return;
        }

        if ($resetx) {
            $this->x=$this->lMargin;
        }

        $this->y=$y;
    }

    /**
     * we redefine the original SetXY method, because we don't want the automatic treatment.
     * It is Html2Pdf that make the treatment.
     * If language is RTL direction this method will call to parent (TCPDF class).
     *
     * @param integer $x
     * @param integer $y
     * @param boolean $rtloff
     * @access public
     */
    public function SetXY($x, $y, $rtloff = false)
    {
        if (!$rtloff && $this->rtl) {
            parent::SetXY($x, $y, $rtloff);
            return;
        }

        $this->x=$x;
        $this->y=$y;
    }

    /**
     * multiple public accessor because Html2Pdf need to use TCPDF without being a extend of it
     *
     * @return mixed
     * @access public
     */
    public function getK()
    {
        return $this->k;
    }
    public function getW()
    {
        return $this->w;
    }
    public function getH()
    {
        return $this->h;
    }
    public function getlMargin()
    {
        return $this->lMargin;
    }
    public function getrMargin()
    {
        return $this->rMargin;
    }
    public function gettMargin()
    {
        return $this->tMargin;
    }
    public function getbMargin()
    {
        return $this->bMargin;
    }
    public function setbMargin($v)
    {
        $this->bMargin=$v;
    }

    /**
     * SVG - Convert a SVG Style in PDF Style
     *
     * @param  array  $styles SVG Style
     * @return string PDF style
     * @access public
     */
    public function svgSetStyle($styles)
    {
        // init the PDF style
        $style = '';

        // Style : fill
        if ($styles['fill']) {
            $this->SetFillColorArray($styles['fill']);
            $style.= 'F';
        }

        // Style : stroke
        if ($styles['stroke'] && $styles['stroke-width']) {
            $this->SetDrawColorArray($styles['stroke']);
            $this->SetLineWidth($styles['stroke-width']);
            $style.= 'D';
        }

        // Style : opacity
        if ($styles['fill-opacity']) {
            $this->SetAlpha($styles['fill-opacity']);
        }

        return $style;
    }

    /**
     * SVG - make a Rectangle
     *
     * @param float $x
     * @param float $y
     * @param float $w
     * @param float $h
     * @param string $style PDF Style
     * @access public
     */
    public function svgRect($x, $y, $w, $h, $style)
    {
        // prepare the 4 corners
        $x1=$x;
        $x2=$x+$w;
        $x3=$x+$w;
        $x4=$x;

        $y1=$y;
        $y2=$y;
        $y3=$y+$h;
        $y4=$y+$h;

        // get the Closing operator from the PDF Style
        if ($style === 'F') {
            $op='f';
        } elseif ($style === 'FD' || $style === 'DF') {
            $op='B';
        } else {
            $op='S';
        }

        // drawing
        $this->_Point($x1, $y1, true);
        $this->_Line($x2, $y2, true);
        $this->_Line($x3, $y3, true);
        $this->_Line($x4, $y4, true);
        $this->_Line($x1, $y1, true);
        $this->_out($op);
    }

    /**
     * SVG - make a Line
     *
     * @param float $x1
     * @param float $y1
     * @param float $x2
     * @param float $y2
     * @access public
     */
    public function svgLine($x1, $y1, $x2, $y2)
    {
        // get the Closing operator
        $op='S';

        // drawing
        $this->_Point($x1, $y1, true);
        $this->_Line($x2, $y2, true);
        $this->_out($op);
    }

    /**
     * SVG - make a Ellipse
     *
     * @param float  $x0 x Center
     * @param float  $y0 y Center
     * @param float  $rx x radius
     * @param float  $ry y radius
     * @param string $style PDF Style
     * @access public
     */
    public function svgEllipse($x0, $y0, $rx, $ry, $style)
    {
        // get the Closing operator from the PDF Style
        if ($style === 'F') {
            $op='f';
        } elseif ($style === 'FD' || $style === 'DF') {
            $op='B';
        } else {
            $op='S';
        }

        // drawing
        $this->_Arc($x0, $y0, $rx, $ry, 0, 2*M_PI, true, true, true);
        $this->_out($op);
    }

    /**
     * SVG - make a Advanced Polygone
     *
     * @param array  $actions list of actions
     * @param string $style PDF Style
     * @access public
     */
    public function svgPolygone($actions, $style)
    {
        // get the Closing operator from the PDF Style
        if ($style === 'F') {
            $op='f';
        } elseif ($style === 'FD' || $style === 'DF') {
            $op='B';
        } else {
            $op='S';
        }

        // To save the First action and the last point
        $first = array('', 0, 0);
        $last = array(0, 0, 0, 0);

        foreach ($actions as $action) {
            switch ($action[0]) {
                // Start the Path - move - absolute
                case 'M':
                    $first = $action;
                    $x = $action[1];
                    $y = $action[2];
                    $xc = $x;
                    $yc = $y;
                    $this->_Point($x, $y, true);
                    break;

                // Start the Path - move - relative
                case 'm':
                    $first = $action;
                    $x = $last[0]+$action[1];
                    $y = $last[1]+$action[2];
                    $xc = $x;
                    $yc = $y;
                    $this->_Point($x, $y, true);
                    break;

                // Close the Path
                case 'Z':
                case 'z':
                    $x = $first[1];
                    $y = $first[2];
                    $xc = $x;
                    $yc = $y;
                    $this->_Line($x, $y, true);
                    break;

                // Make a Line (new point)
                case 'L':
                    $x = $action[1];
                    $y = $action[2];
                    $xc = $x;
                    $yc = $y;
                    $this->_Line($x, $y, true);
                    break;

                // Make a Line (vector from last point)
                case 'l':
                    $x = $last[0]+$action[1];
                    $y = $last[1]+$action[2];
                    $xc = $x;
                    $yc = $y;
                    $this->_Line($x, $y, true);
                    break;

                // Make a Horizontal Line (new point)
                case 'H':
                    $x = $action[1];
                    $y = $last[1];
                    $xc = $x;
                    $yc = $y;
                    $this->_Line($x, $y, true);
                    break;

                // Make a Horisontal Line (vector from last point)
                case 'h':
                    $x = $last[0]+$action[1];
                    $y = $last[1];
                    $xc = $x;
                    $yc = $y;
                    $this->_Line($x, $y, true);
                    break;

                // Make a Vertical Line (new point)
                case 'V':
                    $x = $last[0];
                    $y = $action[1];
                    $xc = $x;
                    $yc = $y;
                    $this->_Line($x, $y, true);
                    break;

                // Make a Vertical Line (vector from last point)
                case 'v':
                    $x = $last[0];
                    $y = $last[1]+$action[1];
                    $xc = $x;
                    $yc = $y;
                    $this->_Line($x, $y, true);
                    break;

                // Make a Arc (new point)
                case 'A':
                    $rx = $action[1];   // rx
                    $ry = $action[2];   // ry
                    $a = $action[3];    // deviation angle of the axis X
                    $l = $action[4];    // large-arc-flag
                    $s = $action[5];    // sweep-flag
                    $x1 = $last[0];     // begin x
                    $y1 = $last[1];     // begin y
                    $x2 = $action[6];   // final x
                    $y2 = $action[7];   // final y

                    $this->_Arc2($x1, $y1, $x2, $y2, $rx, $ry, $a, $l, $s, true);
                    $x = $x2;
                    $y = $y2;
                    $xc = $x;
                    $yc = $y;
                    break;

                // Make a Arc (vector from last point)
                case 'a':
                    $rx = $action[1];   // rx
                    $ry = $action[2];   // ry
                    $a = $action[3];    // deviation angle of the axis X
                    $l = $action[4];    // large-arc-flag
                    $s = $action[5];    // sweep-flag
                    $x1 = $last[0];     // begin x
                    $y1 = $last[1];     // begin y
                    $x2 = $last[0]+$action[6]; // final x
                    $y2 = $last[1]+$action[7]; // final y

                    $this->_Arc2($x1, $y1, $x2, $y2, $rx, $ry, $a, $l, $s, true);
                    $x = $x2;
                    $y = $y2;
                    $xc = $x;
                    $yc = $y;
                    break;

                // Make a Bezier Curve (new point)
                case 'C':
                    $x1 = $action[1];
                    $y1 = $action[2];
                    $x2 = $action[3];
                    $y2 = $action[4];
                    $xf = $action[5];
                    $yf = $action[6];
                    $this->_Curve($x1, $y1, $x2, $y2, $xf, $yf, true);
                    $x = $xf;
                    $y = $yf;
                    $xc = $x2;
                    $yc = $y2;
                    break;

                // Make a Bezier Curve (vector from last point)
                case 'c':
                    $x1 = $last[0]+$action[1];
                    $y1 = $last[1]+$action[2];
                    $x2 = $last[0]+$action[3];
                    $y2 = $last[1]+$action[4];
                    $xf = $last[0]+$action[5];
                    $yf = $last[1]+$action[6];
                    $this->_Curve($x1, $y1, $x2, $y2, $xf, $yf, true);
                    $x = $xf;
                    $y = $yf;
                    $xc = $x2;
                    $yc = $y2;
                    break;

                // Unknown Path
                default:
                    $e = new HtmlParsingException('SVG Path Error : ['.$action[0].'] is unknown');
                    $e->setInvalidTag('POLYGON');
                    throw $e;
            }

            // save the last point
            $last = array($x, $y, $xc, $yc);
        }

        // finish the path
        $this->_out($op);
    }

    /**
     * SVG - go to a point
     *
     * @param float   $x
     * @param float   $y
     * @param boolean $trans apply transformation
     * @access protected
     */
    protected function _Point($x, $y, $trans = false)
    {
        if ($trans) {
            $this->ptTransform($x, $y);
        }

        $this->_out(sprintf('%.2F %.2F m', $x, $y));
    }

    /**
     * SVG - make a line from the last point to (x,y)
     *
     * @param float   $x
     * @param float   $y
     * @param boolean $trans apply transformation
     * @access protected
     */
    protected function _Line($x, $y, $trans = false)
    {
        if ($trans) {
            $this->ptTransform($x, $y);
        }

        $this->_out(sprintf('%.2F %.2F l', $x, $y));
    }

    /**
     * SVG - make a bezier curve from the last point to (xf,yf), with the 2 direction points (x1,y1) and (x2,y2)
     *
     * @param float $x1
     * @param float $y1
     * @param float $x2
     * @param float $y2
     * @param float $xf
     * @param float $yf
     * @param boolean $trans apply transformation
     * @access protected
     */
    protected function _Curve($x1, $y1, $x2, $y2, $xf, $yf, $trans = false)
    {
        if ($trans) {
            $this->ptTransform($x1, $y1);
            $this->ptTransform($x2, $y2);
            $this->ptTransform($xf, $yf);
        }
        $this->_out(sprintf('%.2F %.2F %.2F %.2F %.2F %.2F c', $x1, $y1, $x2, $y2, $xf, $yf));
    }

    /**
     * SVG - make a arc with Center, Radius, from angleBegin to angleEnd
     *
     * @param float $xc
     * @param float $yc
     * @param float $rx
     * @param float $ry
     * @param float $angleBegin in radians
     * @param float $angleEnd in radians
     * @param boolean $direction
     * @param boolean $drawFirst, true => add the first point
     * @param boolean $trans apply transformation
     * @access protected
     */
    protected function _Arc(
        $xc,
        $yc,
        $rx,
        $ry,
        $angleBegin,
        $angleEnd,
        $direction = true,
        $drawFirst = true,
        $trans = false
    ) {

        // if we want the no trigo direction : add 2PI to the begin angle, to invert the direction
        if (!$direction) {
            $angleBegin+= M_PI*2.;
        }

        // cut in segment to convert in berize curv
        $dt = ($angleEnd - $angleBegin)/self::ARC_NB_SEGMENT;
        $dtm = $dt/3;

        // center of the arc
        $x0 = $xc;
        $y0 = $yc;

        // calculing the first point
        $t1 = $angleBegin;
        $a0 = $x0 + ($rx * cos($t1));
        $b0 = $y0 + ($ry * sin($t1));
        $c0 = -$rx * sin($t1);
        $d0 = $ry * cos($t1);

        // if drawFirst => draw the first point
        if ($drawFirst) {
            $this->_Point($a0, $b0, $trans);
        }

        // foreach segment
        for ($i = 1; $i <= self::ARC_NB_SEGMENT; $i++) {
            // calculing the next point
            $t1 = ($i * $dt)+$angleBegin;
            $a1 = $x0 + ($rx * cos($t1));
            $b1 = $y0 + ($ry * sin($t1));
            $c1 = -$rx * sin($t1);
            $d1 = $ry * cos($t1);

            // make the bezier curv
            $this->_Curve(
                $a0 + ($c0 * $dtm),
                $b0 + ($d0 * $dtm),
                $a1 - ($c1 * $dtm),
                $b1 - ($d1 * $dtm),
                $a1,
                $b1,
                $trans
            );

            // save the point
            $a0 = $a1;
            $b0 = $b1;
            $c0 = $c1;
            $d0 = $d1;
        }
    }

    /**
     * SVG - make a arc from Pt1 to Pt2, with Radius
     *
     * @param float $x1
     * @param float $y1
     * @param float $x2
     * @param float $y2
     * @param float $rx
     * @param float $ry
     * @param float $angle deviation angle of the axis X
     * @param boolean $l large-arc-flag
     * @param boolean $s sweep-flag
     * @param boolean $trans apply transformation
     * @access protected
     */
    protected function _Arc2($x1, $y1, $x2, $y2, $rx, $ry, $angle = 0., $l = 0, $s = 0, $trans = false)
    {
        // array to stock the parameters
        $v = array();

        // the original values
        $v['x1'] = $x1;
        $v['y1'] = $y1;
        $v['x2'] = $x2;
        $v['y2'] = $y2;
        $v['rx'] = $rx;
        $v['ry'] = $ry;

        // rotate with the deviation angle of the axis X
        $v['xr1'] = $v['x1']*cos($angle) - $v['y1']*sin($angle);
        $v['yr1'] = $v['x1']*sin($angle) + $v['y1']*cos($angle);
        $v['xr2'] = $v['x2']*cos($angle) - $v['y2']*sin($angle);
        $v['yr2'] = $v['x2']*sin($angle) + $v['y2']*cos($angle);

        // the normalized vector
        $v['Xr1'] = $v['xr1']/$v['rx'];
        $v['Yr1'] = $v['yr1']/$v['ry'];
        $v['Xr2'] = $v['xr2']/$v['rx'];
        $v['Yr2'] = $v['yr2']/$v['ry'];
        $v['dXr'] = $v['Xr2']-$v['Xr1'];
        $v['dYr'] = $v['Yr2']-$v['Yr1'];
        $v['D'] = $v['dXr']*$v['dXr'] + $v['dYr']*$v['dYr'];

        // if |vector| is Null, or if |vector| > 2 : impossible to make a arc => Line
        if ($v['D'] == 0 || $v['D']>4) {
            $this->_Line($x2, $y2, $trans);
            return;
        }

        // convert paramters for make a arc with Center, Radius, from angleBegin to angleEnd
        $v['s1'] = array();
        $v['s1']['t'] = sqrt((4.-$v['D'])/$v['D']);
        $v['s1']['Xr'] = ($v['Xr1']+$v['Xr2'])/2. + $v['s1']['t']*($v['Yr2']-$v['Yr1'])/2.;
        $v['s1']['Yr'] = ($v['Yr1']+$v['Yr2'])/2. + $v['s1']['t']*($v['Xr1']-$v['Xr2'])/2.;
        $v['s1']['xr'] = $v['s1']['Xr']*$v['rx'];
        $v['s1']['yr'] = $v['s1']['Yr']*$v['ry'];
        $v['s1']['x'] = $v['s1']['xr']*cos($angle)+$v['s1']['yr']*sin($angle);
        $v['s1']['y'] =-$v['s1']['xr']*sin($angle)+$v['s1']['yr']*cos($angle);
        $v['s1']['a1'] = atan2($v['y1']-$v['s1']['y'], $v['x1']-$v['s1']['x']);
        $v['s1']['a2'] = atan2($v['y2']-$v['s1']['y'], $v['x2']-$v['s1']['x']);
        if ($v['s1']['a1']>$v['s1']['a2']) {
            $v['s1']['a1']-=2*M_PI;
        }

        $v['s2'] = array();
        $v['s2']['t'] = -$v['s1']['t'];
        $v['s2']['Xr'] = ($v['Xr1']+$v['Xr2'])/2. + $v['s2']['t']*($v['Yr2']-$v['Yr1'])/2.;
        $v['s2']['Yr'] = ($v['Yr1']+$v['Yr2'])/2. + $v['s2']['t']*($v['Xr1']-$v['Xr2'])/2.;
        $v['s2']['xr'] = $v['s2']['Xr']*$v['rx'];
        $v['s2']['yr'] = $v['s2']['Yr']*$v['ry'];
        $v['s2']['x'] = $v['s2']['xr']*cos($angle)+$v['s2']['yr']*sin($angle);
        $v['s2']['y'] =-$v['s2']['xr']*sin($angle)+$v['s2']['yr']*cos($angle);
        $v['s2']['a1'] = atan2($v['y1']-$v['s2']['y'], $v['x1']-$v['s2']['x']);
        $v['s2']['a2'] = atan2($v['y2']-$v['s2']['y'], $v['x2']-$v['s2']['x']);
        if ($v['s2']['a1']>$v['s2']['a2']) {
            $v['s2']['a1']-=2*M_PI;
        }

        if (!$l) {
            if ($s) {
                $xc = $v['s2']['x'];
                $yc = $v['s2']['y'];
                $a1 = $v['s2']['a1'];
                $a2 = $v['s2']['a2'];
                $this->_Arc($xc, $yc, $rx, $ry, $a1, $a2, true, false, $trans);
            } else {
                $xc = $v['s1']['x'];
                $yc = $v['s1']['y'];
                $a1 = $v['s1']['a1'];
                $a2 = $v['s1']['a2'];
                $this->_Arc($xc, $yc, $rx, $ry, $a1, $a2, false, false, $trans);
            }
        } else {
            if ($s) {
                $xc = $v['s1']['x'];
                $yc = $v['s1']['y'];
                $a1 = $v['s1']['a1'];
                $a2 = $v['s1']['a2'];
                $this->_Arc($xc, $yc, $rx, $ry, $a1, $a2, true, false, $trans);
            } else {
                $xc = $v['s2']['x'];
                $yc = $v['s2']['y'];
                $a1 = $v['s2']['a1'];
                $a2 = $v['s2']['a2'];
                $this->_Arc($xc, $yc, $rx, $ry, $a1, $a2, false, false, $trans);
            }
        }
    }

    /**
     * SVG - transform the point (reference)
     *
     * @param float &$x
     * @param float &$y
     * @param boolean $trans true => convert into PDF unit
     * @return boolean
     * @access public
     */
    public function ptTransform(&$x, &$y, $trans = true)
    {
        // load the last Transfomation Matrix
        $nb = count($this->_transf);
        if ($nb) {
            $m = $this->_transf[$nb-1];
        } else {
            $m = array(1,0,0,1,0,0);
        }

        // apply the Transformation Matrix
        list($x, $y) = array(($x*$m[0]+$y*$m[2]+$m[4]),($x*$m[1]+$y*$m[3]+$m[5]));

        // if true => convert into PDF unit
        if ($trans) {
            $x = $x*$this->k;
            $y = ($this->h-$y)*$this->k;
        }

        return true;
    }

    /**
     * SVG - add a transformation Matric
     *
     * @param array $n matrix
     * @access public
     */
    public function doTransform($n = null)
    {
        // get the last Transformation Matrix
        $nb = count($this->_transf);
        if ($nb) {
            $m = $this->_transf[$nb-1];
        } else {
            $m = array(1,0,0,1,0,0);
        }

        // if no transform, get the Identity Matrix
        if (!$n) {
            $n = array(1,0,0,1,0,0);
        }

        // create the new Transformation Matrix
        $this->_transf[] = array(
            $m[0]*$n[0]+$m[2]*$n[1],
            $m[1]*$n[0]+$m[3]*$n[1],
            $m[0]*$n[2]+$m[2]*$n[3],
            $m[1]*$n[2]+$m[3]*$n[3],
            $m[0]*$n[4]+$m[2]*$n[5]+$m[4],
            $m[1]*$n[4]+$m[3]*$n[5]+$m[5]
        );
    }

    /**
     * SVG - remove a transformation Matric
     *
     * @access public
     */
    public function undoTransform()
    {
        array_pop($this->_transf);
    }

    /**
     * Convert a Html2Pdf barcode in a TCPDF barcode
     *
     * @param string $code code to print
     * @param string $type type of barcode (see tcpdf/barcodes.php for supported formats)
     * @param int $x x position in user units
     * @param int $y y position in user units
     * @param int $w width in user units
     * @param int $h height in user units
     * @param int $labelFontsize of the Test Label. If false : no Label
     * @param array $color color of the foreground
     * @param string $dimension 1D or 2D
     * @access public
     */
    public function myBarcode($code, $type, $x, $y, $w, $h, $labelFontsize, $color, $dimension = '1D')
    {
        // the style of the barcode
        $style = array(
            'position' => 'S',
            'text' => ($labelFontsize ? true : false),
            'fgcolor' => $color,
            'bgcolor' => false,
        );

        // build the barcode
        if ($dimension === '2D') {
            // PDF417, DATAMATRIX ...
            $this->write2DBarcode($code, $type, $x, $y, $w, $h, $style, 'N');
        } else {
            $this->write1DBarcode($code, $type, $x, $y, $w, $h, '', $style, 'N');
        }

        // it Label => add the FontSize to the height
        if ($labelFontsize) {
            $h+= $labelFontsize;
        }

        // return the size of the barcode
        return array($w, $h);
    }

    /**
     * create a automatic Index on a page
     *
     * @param html2pdf $obj           parent object
     * @param string   $titre         Title of the Index Page
     * @param integer  $sizeTitle     Font size for hthe Title
     * @param integer  $sizeBookmark  Font size for the bookmarks
     * @param boolean  $bookmarkTitle Bookmark the Title
     * @param boolean  $displayPage   Display the page number for each bookmark
     * @param integer  $page draw the automatic Index on a specific Page. if null => add a page at the end
     * @param string   $fontName      FontName to use
     * @access public
     */
    public function createIndex(
        &$obj,
        $titre = 'Index',
        $sizeTitle = 20,
        $sizeBookmark = 15,
        $bookmarkTitle = true,
        $displayPage = true,
        $page = null,
        $fontName = 'helvetica'
    ) {

        // bookmark the Title if wanted
        if ($bookmarkTitle) {
            $this->Bookmark($titre, 0, -1);
        }

        // display the Title with the good Font size
        $this->SetFont($fontName, '', $sizeTitle);
        $this->Cell(0, 5, $titre, 0, 1, 'C');

        // set the good Font size for the bookmarks
        $this->SetFont($fontName, '', $sizeBookmark);
        $this->Ln(10);

        // get the number of bookmarks
        $size=sizeof($this->outlines);

        // get the size of the "P. xx" cell
        $pageCellSize=$this->GetStringWidth('p. '.$this->outlines[$size-1]['p'])+2;

        // Foreach bookmark
        for ($i=0; $i<$size; $i++) {
            // if we need a new page => add a new page
            if ($this->getY()+$this->FontSize>=($this->h - $this->bMargin)) {
                $obj->_INDEX_NewPage($page);
                $this->SetFont($fontName, '', $sizeBookmark);
            }

            // Offset of the current level
            $level=$this->outlines[$i]['l'];
            if ($level>0) {
                $this->Cell($level*8);
            }

            // Caption (cut to fit on the width page)
            $str=$this->outlines[$i]['t'];
            $strsize=$this->GetStringWidth($str);
            $availableSize=$this->w-$this->lMargin-$this->rMargin-$pageCellSize-($level*8)-4;
            while ($strsize>=$availableSize) {
                $str=substr($str, 0, -1);
                $strsize=$this->GetStringWidth($str);
            }

            // if we want to display the page nmber
            if ($displayPage) {
                // display the Bookmark Caption
                $this->Cell($strsize+2, $this->FontSize+2, $str);

                //Filling dots
                $w=$this->w-$this->lMargin-$this->rMargin-$pageCellSize-($level*8)-($strsize+2);
                $nb = (int) ($w/$this->GetStringWidth('.'));
                $dots=str_repeat('.', $nb);
                $this->Cell($w, $this->FontSize+2, $dots, 0, 0, 'R');

                //Page number
                $this->Cell($pageCellSize, $this->FontSize+2, 'p. '.$this->outlines[$i]['p'], 0, 1, 'R');
            } else {
                // display the Bookmark Caption
                $this->Cell($strsize+2, $this->FontSize+2, $str, 0, 1);
            }
        }
    }

    /**
     * Returns the string alias used for the total number of pages.
     *
     * @access public
     * @return string;
     * @see TCPDF::getAliasNbPages(), TCPDF::getPageGroupAlias()
     */
    public function getMyAliasNbPages()
    {
        if ($this->_myLastPageGroupNb == 0) {
            return $this->getAliasNbPages();
        } else {
            $old = $this->currpagegroup;
            $this->currpagegroup = '{nb'.$this->_myLastPageGroupNb.'}';
            $new = $this->getPageGroupAlias();
            $this->currpagegroup = $old;

            return $new;
        }
    }

    /**
     * Returns the current page number.
     *
     * @access public
     * @param  integer $page
     * @return integer;
     */
    public function getMyNumPage($page = null)
    {
        if ($page === null) {
            $page = $this->page;
        }

        if ($this->_myLastPageGroupNb == 0) {
            return $page;
        } else {
            return $page-$this->_myLastPageGroup;
        }
    }

    /**
     * Start a new group of pages
     *
     * @access public
     * @return void
     * @see tcpdf::startPageGroup
     */
    public function myStartPageGroup()
    {
        $this->_myLastPageGroup = $this->page-1;
        $this->_myLastPageGroupNb++;
    }

    /**
     * get $_myLastPageGroup;
     *
     * @access public
     * @return integer $_myLastPageGroup;
     */
    public function getMyLastPageGroup()
    {
        return $this->_myLastPageGroup;
    }

    /**
     * set $_myLastPageGroup;
     *
     * @access public
     * @param integer $myLastPageGroup;
     */
    public function setMyLastPageGroup($myLastPageGroup)
    {
        $this->_myLastPageGroup = $myLastPageGroup;
    }

    /**
     * get $_myLastPageGroupNb;
     *
     * @access public
     * @return integer $_myLastPageGroupNb;
     */
    public function getMyLastPageGroupNb()
    {
        return $this->_myLastPageGroupNb;
    }

    /**
     * set $_myLastPageGroupNb;
     *
     * @access public
     * @param integer $myLastPageGroupNb;
     */
    public function setMyLastPageGroupNb($myLastPageGroupNb)
    {
        $this->_myLastPageGroupNb = $myLastPageGroupNb;
    }
}
