Named destinations

Informations

Author: Jan Slabon
License: FPDF

Description

This extension allows to use and define named destinations in a PDF document.

If the $link parameter of SetLink() is prefixed with a "#", it will be registered as a named destination.

If a $link parameter of Link(), Cell(), Write(),... is prefixed with a "#", it will be treated as a named destination target.

Source

<?php
require_once('fpdf.php');

class PDF_NamedDestinations extends FPDF
{
    protected $namedDestinations = array();
    protected $n_namedDestinations;

    function SetLink($link, $y = 0, $page = -1)
    {
        if (strpos($link, '#') !== 0) {
            parent::SetLink($link);
        } else {
            // Set destination of internal link
            if ($y == -1)
                $y = $this->y;
            if ($page == -1)
                $page = $this->page;
            $this->namedDestinations[substr($link, 1)] = array($page, $y);
        }
    }

    function _putnamedDestinations()
    {
        if ($this->DefOrientation == 'P') {
            $hPt = $this->DefPageSize[1] * $this->k;
        } else {
            $hPt = $this->DefPageSize[0] * $this->k;
        }
        
        $names = array();
        foreach ($this->namedDestinations as $name => $namedDestinations) {
            $h = isset($this->PageInfo[$namedDestinations[0]]['size']) ? $this->PageInfo[$namedDestinations[0]]['size'][1] : $hPt;
            $this->_newobj();
            $this->_put(sprintf('[%d 0 R /XYZ 0 %.2F null]', $this->PageInfo[$namedDestinations[0]]['n'], $h - $namedDestinations[1] * $this->k));
            $this->_put('endobj');

            $names[$name] = $this->n;
        }
        ksort($names, SORT_STRING);

        $this->_newobj();
        $this->n_namedDestinations = $this->n;
        $this->_put('<<');
        $s = array();
        foreach ($names as $name => $n)
            $s[] = $this->_textstring((string)$name) . ' ' . $n . ' 0 R';
        $this->_put('/Names [' . implode(' ', $s) . ']');
        $this->_put('>>');
        $this->_put('endobj');
    }

    function _putresources()
    {
        parent::_putresources();
        if(!empty($this->namedDestinations))
            $this->_putnamedDestinations();
    }

    function _putcatalog()
    {
        parent::_putcatalog();
        if(!empty($this->namedDestinations))
            $this->_put('/Names <</Dests '.$this->n_namedDestinations.' 0 R>>');
    }

    protected function _putpage($n)
    {
        $this->_newobj();
        $this->_put('<</Type /Page');
        $this->_put('/Parent 1 0 R');
        if(isset($this->PageInfo[$n]['size']))
            $this->_put(sprintf('/MediaBox [0 0 %.2F %.2F]',$this->PageInfo[$n]['size'][0],$this->PageInfo[$n]['size'][1]));
        if(isset($this->PageInfo[$n]['rotation']))
            $this->_put('/Rotate '.$this->PageInfo[$n]['rotation']);
        $this->_put('/Resources 2 0 R');
        if(!empty($this->PageLinks[$n]))
        {
            $s = '/Annots [';
            foreach($this->PageLinks[$n] as $pl)
                $s .= $pl[5].' 0 R ';
            $s .= ']';
            $this->_put($s);
        }
        if($this->WithAlpha)
            $this->_put('/Group <</Type /Group /S /Transparency /CS /DeviceRGB>>');
        $this->_put('/Contents '.($this->n+1).' 0 R>>');
        $this->_put('endobj');
        // Page content
        if(!empty($this->AliasNbPages))
            $this->pages[$n] = str_replace($this->AliasNbPages,$this->page,$this->pages[$n]);
        $this->_putstreamobject($this->pages[$n]);
        // Annotations
        foreach($this->PageLinks[$n] as $pl)
        {
            $this->_newobj();
            $rect = sprintf('%.2F %.2F %.2F %.2F',$pl[0],$pl[1],$pl[0]+$pl[2],$pl[1]-$pl[3]);
            $s = '<</Type /Annot /Subtype /Link /Rect ['.$rect.'] /Border [0 0 0] ';
            if(is_string($pl[4])) {
                if (strpos($pl[4], '#') === 0) {
                    $s .= '/A <</S /GoTo /D ' . $this->_textstring(substr($pl[4], 1)) . '>>>>';
                } else {
                    $s .= '/A <</S /URI /URI ' . $this->_textstring($pl[4]) . '>>>>';
                }
            } else {
                $l = $this->links[$pl[4]];
                if(isset($this->PageInfo[$l[0]]['size']))
                    $h = $this->PageInfo[$l[0]]['size'][1];
                else
                    $h = ($this->DefOrientation=='P') ? $this->DefPageSize[1]*$this->k : $this->DefPageSize[0]*$this->k;
                $s .= sprintf('/Dest [%d 0 R /XYZ 0 %.2F null]>>',$this->PageInfo[$l[0]]['n'],$h-$l[1]*$this->k);
            }
            $this->_put($s);
            $this->_put('endobj');
        }
    }
}

Example

<?php
require_once('named_destinations.php');

$pdf = new PDF_NamedDestinations();

$pdf->AddPage();
$pdf->SetFont('Arial', '', 14);
$pdf->SetLink('#page-1');
$pdf->Write(10, 'Link to page 2', '#page-2');

$pdf->AddPage();
$pdf->SetLink('#page-2');
$pdf->Write(10, 'Link to page 1', '#page-1');

$pdf->Output();
?>
View the result here.

Download

ZIP | TGZ