Skip to content

Insert WMF Image File in PDF Document Using PHP FPDF Library.

fpdf-wmf.php

<?php
/****************************************************************************
* Software: FPDF_WMF                                                        *
* Version:  0.5                                                             *
* Date:     2019-08-10                                                      *
* Author:   Martin HALL-MAY                                                 *
* License:  FPDF                                                            *
****************************************************************************/

require('fpdf.php');

class FPDF_WMF extends FPDF
{
    protected $formobjects=array(); // array of Form Objects
    protected $gdiObjectArray;      // array of GDI objects (pens, brushes, etc.)

    // Play back a WMF file
    // Placement and sizing of the image works as with Image()
    function ImageWMF($file, $x, $y, $w=0, $h=0, $link='')
    {
        // Put a WMF image on the page
        if(!isset($this->formobjects[$file]))
        {
            // First use of image, get info
            $info=$this->_parsewmf($file);
            $info['i']=count($this->formobjects)+1;
            $this->formobjects[$file]=$info;
        }
        else
        {
            $info=$this->formobjects[$file];
        }

        // Automatic dimensions if necessary
        // WMF units are twips (1/20pt)
        // divide by 20 to get points
        // divide by k to get user units
        if($w==0 && $h==0)
        {
            $w = abs($info['w'])/(20*$this->k);
            $h = abs($info['h'])/(20*$this->k);
        }
        if($w==0)
            $w = abs($h*$info['w']/$info['h']);
        if($h==0)
            $h = abs($w*$info['h']/$info['w']);

        $sx = $w*$this->k / $info['w'];
        $sy = -$h*$this->k / $info['h'];
        $this->_out(sprintf('q %F 0 0 %F %F %F cm /FO%d Do Q', $sx, $sy, $x*$this->k-$sx*$info['x'], (($this->h-$y)*$this->k)-$sy*$info['y'], $info['i']));

        if($link)
            $this->Link($x,$y,$w,$h,$link);
    }

    function _parsewmf($file)
    {
        $this->gdiObjectArray = array();

        $a=unpack('stest',"\1\0");
        if ($a['test']!=1)
            $this->Error('Big-endian architectures are not supported');

        $f=fopen($file,'rb');
        if(!$f)
            $this->Error('Can\'t open image file: '.$file);

        // check for Aldus placeable metafile header
        $key = unpack('Lmagic', fread($f, 4));
        $headSize = 18 - 4; // WMF header minus four bytes already read
        if ($key['magic'] == (int)0x9AC6CDD7)
            $headSize += 22; // Aldus header

        // strip headers
        fread($f, $headSize);

        // define some state variables
        $wo=null; // window origin
        $we=null; // window extent
        $polyFillMode = 0;
        $nullPen = false;
        $nullBrush = false;

        $endRecord = false;

        $data = '';

        // read the records
        while (!feof($f) && !$endRecord)
        {
            $recordInfo = unpack('Lsize/Sfunc', fread($f, 6));

            // size of record given in WORDs (= 2 bytes)
            $size = $recordInfo['size'];

            // func is number of GDI function
            $func = $recordInfo['func'];

            // parameters are read as one block and processed
            // as necessary by the case statement below.
            // the data are stored in little-endian format and are unpacked using:
            // s - signed 16-bit int
            // S - unsigned 16-bit int (or WORD)
            // L - unsigned 32-bit int (or DWORD)
            // NB. parameters to GDI functions are stored in reverse order
            // however structures are not reversed,
            // e.g. POINT { int x, int y } where x=3000 (0x0BB8) and y=-1200 (0xFB50)
            // is stored as B8 0B 50 FB
            if ($size > 3)
            {
                $parms = fread($f, 2*($size-3));
            }

            // process each record.
            // function numbers are defined in wingdi.h
            switch ($func)
            {
                case 0x020b:  // SetWindowOrg
                    // do not allow window origin to be changed
                    // after drawing has begun
                    if (!$data)
                        $wo = array_reverse(unpack('s2', $parms));
                    break;

                case 0x020c:  // SetWindowExt
                    // do not allow window extent to be changed
                    // after drawing has begun
                    if (!$data)
                        $we = array_reverse(unpack('s2', $parms));
                    break;

                case 0x02fc:  // CreateBrushIndirect
                    $brush = unpack('sstyle/Cr/Cg/Cb/Ca/Shatch', $parms);
                    $brush['type'] = 'B';
                    $this->_AddGDIObject($brush);
                    break;

                case 0x02fa:  // CreatePenIndirect
                    $pen = unpack('Sstyle/swidth/sdummy/Cr/Cg/Cb/Ca', $parms);

                    // convert width from twips to user unit
                    $pen['width'] /= (20 * $this->k);
                    $pen['type'] = 'P';
                    $this->_AddGDIObject($pen);
                    break;

                // MUST create other GDI objects even if we don't handle them
                // otherwise object numbering will get out of sequence
                case 0x06fe: // CreateBitmap
                case 0x02fd: // CreateBitmapIndirect
                case 0x00f8: // CreateBrush
                case 0x02fb: // CreateFontIndirect
                case 0x00f7: // CreatePalette
                case 0x01f9: // CreatePatternBrush
                case 0x06ff: // CreateRegion
                case 0x0142: // DibCreatePatternBrush
                    $dummyObject = array('type'=>'D');
                    $this->_AddGDIObject($dummyObject);
                    break;

                case 0x0106:  // SetPolyFillMode
                    $polyFillMode = unpack('smode', $parms);
                    $polyFillMode = $polyFillMode['mode'];
                    break;

                case 0x01f0:  // DeleteObject
                    $idx = unpack('Sidx', $parms);
                    $idx = $idx['idx'];
                    $this->_DeleteGDIObject($idx);
                    break;

                case 0x012d:  // SelectObject
                    $idx = unpack('Sidx', $parms);
                    $idx = $idx['idx'];
                    $obj = $this->_GetGDIObject($idx);

                    switch ($obj['type'])
                    {
                        case 'B':
                            $nullBrush = false;

                            if ($obj['style'] == 1) // BS_NULL, BS_HOLLOW
                            {
                                $nullBrush = true;
                            }
                            else
                            {
                                $data .= sprintf("%.3F %.3F %.3F rg\n",$obj['r']/255,$obj['g']/255,$obj['b']/255);
                            }
                            break;

                        case 'P':
                            $nullPen = false;
                            $dashArray = array(); 

                            // dash parameters are my own - feel free to change them
                            switch ($obj['style'])
                            {
                                case 0: // PS_SOLID
                                    break;
                                case 1: // PS_DASH
                                    $dashArray = array(3,1);
                                    break;
                                case 2: // PS_DOT
                                    $dashArray = array(0.5,0.5);
                                    break;
                                case 3: // PS_DASHDOT
                                    $dashArray = array(2,1,0.5,1);
                                    break;
                                case 4: // PS_DASHDOTDOT
                                    $dashArray = array(2,1,0.5,1,0.5,1);
                                    break;
                                case 5: // PS_NULL
                                    $nullPen = true;
                                    break;
                            }

                            if (!$nullPen)
                            {
                                $data .= sprintf("%.3F %.3F %.3F RG\n",$obj['r']/255,$obj['g']/255,$obj['b']/255);
                                $data .= sprintf("%.2F w\n",$obj['width']*$this->k);
                            }

                            if (!empty($dashArray))
                            {
                                $s = '[';
                                for ($i=0; $i<count($dashArray);$i++)
                                {
                                    $s .= $dashArray[$i] * $this->k;
                                    if ($i != count($dashArray)-1)
                                        $s .= ' ';
                                }
                                $s .= '] 0 d';
                                $data .= $s."\n";
                            }

                            break;
                    }
                    break;

                case 0x0325: // Polyline
                case 0x0324: // Polygon
                    $coords = unpack('s'.($size-3), $parms);
                    $numpoints = $coords[1];

                    for ($i = $numpoints; $i > 0; $i--)
                    {
                        $px = $coords[2*$i];
                        $py = $coords[2*$i+1];

                        if ($i < $numpoints)
                            $data .= $this->LineTo($px, $py);
                        else
                            $data .= $this->MoveTo($px, $py);
                    }

                    if ($func == 0x0325)
                    {
                        $op = 's';
                    }
                    else if ($func == 0x0324)
                    {
                        if ($nullPen)
                        {
                            if ($nullBrush)
                                $op = 'n';  // no op
                            else
                                $op = 'f';  // fill
                        }
                        else
                        {
                            if ($nullBrush)
                                $op = 's';  // stroke
                            else
                                $op = 'b';  // stroke and fill
                        }

                        if ($polyFillMode==1 && ($op=='b' || $op=='f')) 
                            $op .= '*';  // use even-odd fill rule
                    }

                    $data .= $op."\n";
                    break;

                case 0x0538: // PolyPolygon
                    $coords = unpack('s'.($size-3), $parms);

                    $numpolygons = $coords[1];

                    $adjustment = $numpolygons;

                    for ($j = 1; $j <= $numpolygons; $j++)
                    {
                        $numpoints = $coords[$j + 1];

                        for ($i = $numpoints; $i > 0; $i--)
                        {
                            $px = $coords[2*$i   + $adjustment];
                            $py = $coords[2*$i+1 + $adjustment];

                            if ($i == $numpoints)
                                $data .= $this->MoveTo($px, $py);
                            else
                                $data .= $this->LineTo($px, $py);
                        }

                        $adjustment += $numpoints * 2;
                    }

                    if ($nullPen)
                    {
                        if ($nullBrush)
                            $op = 'n';  // no op
                        else
                            $op = 'f';  // fill
                    }
                    else
                    {
                        if ($nullBrush)
                            $op = 's';  // stroke
                        else
                            $op = 'b';  // stroke and fill
                    }

                    if ($polyFillMode==1 && ($op=='b' || $op=='f')) 
                        $op .= '*';  // use even-odd fill rule

                    $data .= $op."\n";

                    break;

                case 0x0000:
                    $endRecord = true;
                    break;
            }
        }

        fclose($f);
        return array('x'=>$wo[0],'y'=>$wo[1],'w'=>$we[0],'h'=>$we[1],'data'=>$data);
    }

    function MoveTo($x, $y)
    {
        return "$x $y m\n";
    }

    // a line must have been started using MoveTo() first
    function LineTo($x, $y)
    {
        return "$x $y l\n";
    }

    function _AddGDIObject($obj)
    {
        // find next available slot
        $idx = 0;
        if (!empty($this->gdiObjectArray))
        {
            $empty = false;
            $i = 0;

            while (!$empty)
            {
                $empty = !isset($this->gdiObjectArray[$i]);
                $i++;
            }
            $idx = $i-1;
        }

        $this->gdiObjectArray[$idx] = $obj;
    }

    function _GetGDIObject($idx)
    {
        return $this->gdiObjectArray[$idx];
    }

    function _DeleteGDIObject($idx)
    {
        unset($this->gdiObjectArray[$idx]);
    }

    function _putformobjects()
    {
        foreach($this->formobjects as $file=>$info)
        {
            $this->_newobj();
            $this->formobjects[$file]['n']=$this->n;
            $this->_put('<</Type /XObject');
            $this->_put('/Subtype /Form');
            $this->_put('/BBox ['.$info['x'].' '.$info['y'].' '.($info['w']+$info['x']).' '.($info['h']+$info['y']).']');
            if ($this->compress)
                $this->_put('/Filter /FlateDecode');
            $data=($this->compress) ? gzcompress($info['data']) : $info['data'];
            $this->_put('/Length '.strlen($data).'>>');
            $this->_putstream($data);
            unset($this->formobjects[$file]['data']);
            $this->_put('endobj');
        }
    }

    function _putxobjectdict()
    {
        parent::_putxobjectdict();
        foreach($this->formobjects as $formobject)
            $this->_put('/FO'.$formobject['i'].' '.$formobject['n'].' 0 R');
    }

    function _putresources()
    {
        $this->_putformobjects();
        parent::_putresources();
    }
}

?>

index.php

<?php
require('fpdf-wmf.php');

$pdf = new FPDF_WMF();
$pdf->AddPage();
$pdf->ImageWMF('ringmaster.wmf', 55, 10, 110);
$pdf->Output();
?>