PHPOffice/PHPPresentation

View on GitHub
src/PhpPresentation/Reader/PowerPoint97.php

Summary

Maintainability
F
1 mo
Test Coverage
B
80%
<?php
/**
 * This file is part of PHPPresentation - A pure PHP library for reading and writing
 * presentations documents.
 *
 * PHPPresentation is free software distributed under the terms of the GNU Lesser
 * General Public License version 3 as published by the Free Software Foundation.
 *
 * For the full copyright and license information, please read the LICENSE
 * file that was distributed with this source code. For the full list of
 * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors.
 *
 * @see        https://github.com/PHPOffice/PHPPresentation
 *
 * @license     http://www.gnu.org/licenses/lgpl.txt LGPL version 3
 */

declare(strict_types=1);

namespace PhpOffice\PhpPresentation\Reader;

use Exception;
use PhpOffice\Common\Microsoft\OLERead;
use PhpOffice\Common\Text;
use PhpOffice\PhpPresentation\AbstractShape;
use PhpOffice\PhpPresentation\Exception\FeatureNotImplementedException;
use PhpOffice\PhpPresentation\Exception\FileNotFoundException;
use PhpOffice\PhpPresentation\Exception\InvalidFileFormatException;
use PhpOffice\PhpPresentation\PhpPresentation;
use PhpOffice\PhpPresentation\Shape;
use PhpOffice\PhpPresentation\Shape\Drawing;
use PhpOffice\PhpPresentation\Shape\Group;
use PhpOffice\PhpPresentation\Shape\Hyperlink;
use PhpOffice\PhpPresentation\Shape\Line;
use PhpOffice\PhpPresentation\Shape\RichText;
use PhpOffice\PhpPresentation\Style\Alignment;
use PhpOffice\PhpPresentation\Style\Bullet;
use PhpOffice\PhpPresentation\Style\Color;
use PhpOffice\PhpPresentation\Style\Font;

/**
 * Serialized format reader.
 */
class PowerPoint97 implements ReaderInterface
{
    public const OFFICEARTBLIPEMF = 0xF01A;
    public const OFFICEARTBLIPWMF = 0xF01B;
    public const OFFICEARTBLIPPICT = 0xF01C;
    public const OFFICEARTBLIPJPG = 0xF01D;
    public const OFFICEARTBLIPPNG = 0xF01E;
    public const OFFICEARTBLIPDIB = 0xF01F;
    public const OFFICEARTBLIPTIFF = 0xF029;
    public const OFFICEARTBLIPJPEG = 0xF02A;

    /**
     * @see http://msdn.microsoft.com/en-us/library/dd945336(v=office.12).aspx
     */
    public const RT_ANIMATIONINFO = 0x1014;
    public const RT_ANIMATIONINFOATOM = 0x0FF1;
    public const RT_BINARYTAGDATABLOB = 0x138B;
    public const RT_BLIPCOLLECTION9 = 0x07F8;
    public const RT_BLIPENTITY9ATOM = 0x07F9;
    public const RT_BOOKMARKCOLLECTION = 0x07E3;
    public const RT_BOOKMARKENTITYATOM = 0x0FD0;
    public const RT_BOOKMARKSEEDATOM = 0x07E9;
    public const RT_BROADCASTDOCINFO9 = 0x177E;
    public const RT_BROADCASTDOCINFO9ATOM = 0x177F;
    public const RT_BUILDATOM = 0x2B03;
    public const RT_BUILDLIST = 0x2B02;
    public const RT_CHARTBUILD = 0x2B04;
    public const RT_CHARTBUILDATOM = 0x2B05;
    public const RT_COLORSCHEMEATOM = 0x07F0;
    public const RT_COMMENT10 = 0x2EE0;
    public const RT_COMMENT10ATOM = 0x2EE1;
    public const RT_COMMENTINDEX10 = 0x2EE4;
    public const RT_COMMENTINDEX10ATOM = 0x2EE5;
    public const RT_CRYPTSESSION10CONTAINER = 0x2F14;
    public const RT_CURRENTUSERATOM = 0x0FF6;
    public const RT_CSTRING = 0x0FBA;
    public const RT_DATETIMEMETACHARATOM = 0x0FF7;
    public const RT_DEFAULTRULERATOM = 0x0FAB;
    public const RT_DOCROUTINGSLIPATOM = 0x0406;
    public const RT_DIAGRAMBUILD = 0x2B06;
    public const RT_DIAGRAMBUILDATOM = 0x2B07;
    public const RT_DIFF10 = 0x2EED;
    public const RT_DIFF10ATOM = 0x2EEE;
    public const RT_DIFFTREE10 = 0x2EEC;
    public const RT_DOCTOOLBARSTATES10ATOM = 0x36B1;
    public const RT_DOCUMENT = 0x03E8;
    public const RT_DOCUMENTATOM = 0x03E9;
    public const RT_DRAWING = 0x040C;
    public const RT_DRAWINGGROUP = 0x040B;
    public const RT_ENDDOCUMENTATOM = 0x03EA;
    public const RT_EXTERNALAVIMOVIE = 0x1006;
    public const RT_EXTERNALCDAUDIO = 0x100E;
    public const RT_EXTERNALCDAUDIOATOM = 0x1012;
    public const RT_EXTERNALHYPERLINK = 0x0FD7;
    public const RT_EXTERNALHYPERLINK9 = 0x0FE4;
    public const RT_EXTERNALHYPERLINKATOM = 0x0FD3;
    public const RT_EXTERNALHYPERLINKFLAGSATOM = 0x1018;
    public const RT_EXTERNALMCIMOVIE = 0x1007;
    public const RT_EXTERNALMEDIAATOM = 0x1004;
    public const RT_EXTERNALMIDIAUDIO = 0x100D;
    public const RT_EXTERNALOBJECTLIST = 0x0409;
    public const RT_EXTERNALOBJECTLISTATOM = 0x040A;
    public const RT_EXTERNALOBJECTREFATOM = 0x0BC1;
    public const RT_EXTERNALOLECONTROL = 0x0FEE;
    public const RT_EXTERNALOLECONTROLATOM = 0x0FFB;
    public const RT_EXTERNALOLEEMBED = 0x0FCC;
    public const RT_EXTERNALOLEEMBEDATOM = 0x0FCD;
    public const RT_EXTERNALOLELINK = 0x0FCE;
    public const RT_EXTERNALOLELINKATOM = 0x0FD1;
    public const RT_EXTERNALOLEOBJECTATOM = 0x0FC3;
    public const RT_EXTERNALOLEOBJECTSTG = 0x1011;
    public const RT_EXTERNALVIDEO = 0x1005;
    public const RT_EXTERNALWAVAUDIOEMBEDDED = 0x100F;
    public const RT_EXTERNALWAVAUDIOEMBEDDEDATOM = 0x1013;
    public const RT_EXTERNALWAVAUDIOLINK = 0x1010;
    public const RT_ENVELOPEDATA9ATOM = 0x1785;
    public const RT_ENVELOPEFLAGS9ATOM = 0x1784;
    public const RT_ENVIRONMENT = 0x03F2;
    public const RT_FONTCOLLECTION = 0x07D5;
    public const RT_FONTCOLLECTION10 = 0x07D6;
    public const RT_FONTEMBEDDATABLOB = 0x0FB8;
    public const RT_FONTEMBEDFLAGS10ATOM = 0x32C8;
    public const RT_FILTERPRIVACYFLAGS10ATOM = 0x36B0;
    public const RT_FONTENTITYATOM = 0x0FB7;
    public const RT_FOOTERMETACHARATOM = 0x0FFA;
    public const RT_GENERICDATEMETACHARATOM = 0x0FF8;
    public const RT_GRIDSPACING10ATOM = 0x040D;
    public const RT_GUIDEATOM = 0x03FB;
    public const RT_HANDOUT = 0x0FC9;
    public const RT_HASHCODEATOM = 0x2B00;
    public const RT_HEADERSFOOTERS = 0x0FD9;
    public const RT_HEADERSFOOTERSATOM = 0x0FDA;
    public const RT_HEADERMETACHARATOM = 0x0FF9;
    public const RT_HTMLDOCINFO9ATOM = 0x177B;
    public const RT_HTMLPUBLISHINFOATOM = 0x177C;
    public const RT_HTMLPUBLISHINFO9 = 0x177D;
    public const RT_INTERACTIVEINFO = 0x0FF2;
    public const RT_INTERACTIVEINFOATOM = 0x0FF3;
    public const RT_KINSOKU = 0x0FC8;
    public const RT_KINSOKUATOM = 0x0FD2;
    public const RT_LEVELINFOATOM = 0x2B0A;
    public const RT_LINKEDSHAPE10ATOM = 0x2EE6;
    public const RT_LINKEDSLIDE10ATOM = 0x2EE7;
    public const RT_LIST = 0x07D0;
    public const RT_MAINMASTER = 0x03F8;
    public const RT_MASTERTEXTPROPATOM = 0x0FA2;
    public const RT_METAFILE = 0x0FC1;
    public const RT_NAMEDSHOW = 0x0411;
    public const RT_NAMEDSHOWS = 0x0410;
    public const RT_NAMEDSHOWSLIDESATOM = 0x0412;
    public const RT_NORMALVIEWSETINFO9 = 0x0414;
    public const RT_NORMALVIEWSETINFO9ATOM = 0x0415;
    public const RT_NOTES = 0x03F0;
    public const RT_NOTESATOM = 0x03F1;
    public const RT_NOTESTEXTVIEWINFO9 = 0x0413;
    public const RT_OUTLINETEXTPROPS9 = 0x0FAE;
    public const RT_OUTLINETEXTPROPS10 = 0x0FB3;
    public const RT_OUTLINETEXTPROPS11 = 0x0FB5;
    public const RT_OUTLINETEXTPROPSHEADER9ATOM = 0x0FAF;
    public const RT_OUTLINETEXTREFATOM = 0x0F9E;
    public const RT_OUTLINEVIEWINFO = 0x0407;
    public const RT_PERSISTDIRECTORYATOM = 0x1772;
    public const RT_PARABUILD = 0x2B08;
    public const RT_PARABUILDATOM = 0x2B09;
    public const RT_PHOTOALBUMINFO10ATOM = 0x36B2;
    public const RT_PLACEHOLDERATOM = 0x0BC3;
    public const RT_PRESENTATIONADVISORFLAGS9ATOM = 0x177A;
    public const RT_PRINTOPTIONSATOM = 0x1770;
    public const RT_PROGBINARYTAG = 0x138A;
    public const RT_PROGSTRINGTAG = 0x1389;
    public const RT_PROGTAGS = 0x1388;
    public const RT_RECOLORINFOATOM = 0x0FE7;
    public const RT_RTFDATETIMEMETACHARATOM = 0x1015;
    public const RT_ROUNDTRIPANIMATIONATOM12ATOM = 0x2B0B;
    public const RT_ROUNDTRIPANIMATIONHASHATOM12ATOM = 0x2B0D;
    public const RT_ROUNDTRIPCOLORMAPPING12ATOM = 0x040F;
    public const RT_ROUNDTRIPCOMPOSITEMASTERID12ATOM = 0x041D;
    public const RT_ROUNDTRIPCONTENTMASTERID12ATOM = 0x0422;
    public const RT_ROUNDTRIPCONTENTMASTERINFO12ATOM = 0x041E;
    public const RT_ROUNDTRIPCUSTOMTABLESTYLES12ATOM = 0x0428;
    public const RT_ROUNDTRIPDOCFLAGS12ATOM = 0x0425;
    public const RT_ROUNDTRIPHEADERFOOTERDEFAULTS12ATOM = 0x0424;
    public const RT_ROUNDTRIPHFPLACEHOLDER12ATOM = 0x0420;
    public const RT_ROUNDTRIPNEWPLACEHOLDERID12ATOM = 0x0BDD;
    public const RT_ROUNDTRIPNOTESMASTERTEXTSTYLES12ATOM = 0x0427;
    public const RT_ROUNDTRIPOARTTEXTSTYLES12ATOM = 0x0423;
    public const RT_ROUNDTRIPORIGINALMAINMASTERID12ATOM = 0x041C;
    public const RT_ROUNDTRIPSHAPECHECKSUMFORCL12ATOM = 0x0426;
    public const RT_ROUNDTRIPSHAPEID12ATOM = 0x041F;
    public const RT_ROUNDTRIPSLIDESYNCINFO12 = 0x3714;
    public const RT_ROUNDTRIPSLIDESYNCINFOATOM12 = 0x3715;
    public const RT_ROUNDTRIPTHEME12ATOM = 0x040E;
    public const RT_SHAPEATOM = 0x0BDB;
    public const RT_SHAPEFLAGS10ATOM = 0x0BDC;
    public const RT_SLIDE = 0x03EE;
    public const RT_SLIDEATOM = 0x03EF;
    public const RT_SLIDEFLAGS10ATOM = 0x2EEA;
    public const RT_SLIDELISTENTRY10ATOM = 0x2EF0;
    public const RT_SLIDELISTTABLE10 = 0x2EF1;
    public const RT_SLIDELISTWITHTEXT = 0x0FF0;
    public const RT_SLIDELISTTABLESIZE10ATOM = 0x2EEF;
    public const RT_SLIDENUMBERMETACHARATOM = 0x0FD8;
    public const RT_SLIDEPERSISTATOM = 0x03F3;
    public const RT_SLIDESHOWDOCINFOATOM = 0x0401;
    public const RT_SLIDESHOWSLIDEINFOATOM = 0x03F9;
    public const RT_SLIDETIME10ATOM = 0x2EEB;
    public const RT_SLIDEVIEWINFO = 0x03FA;
    public const RT_SLIDEVIEWINFOATOM = 0x03FE;
    public const RT_SMARTTAGSTORE11CONTAINER = 0x36B3;
    public const RT_SOUND = 0x07E6;
    public const RT_SOUNDCOLLECTION = 0x07E4;
    public const RT_SOUNDCOLLECTIONATOM = 0x07E5;
    public const RT_SOUNDDATABLOB = 0x07E7;
    public const RT_SORTERVIEWINFO = 0x0408;
    public const RT_STYLETEXTPROPATOM = 0x0FA1;
    public const RT_STYLETEXTPROP10ATOM = 0x0FB1;
    public const RT_STYLETEXTPROP11ATOM = 0x0FB6;
    public const RT_STYLETEXTPROP9ATOM = 0x0FAC;
    public const RT_SUMMARY = 0x0402;
    public const RT_TEXTBOOKMARKATOM = 0x0FA7;
    public const RT_TEXTBYTESATOM = 0x0FA8;
    public const RT_TEXTCHARFORMATEXCEPTIONATOM = 0x0FA4;
    public const RT_TEXTCHARSATOM = 0x0FA0;
    public const RT_TEXTDEFAULTS10ATOM = 0x0FB4;
    public const RT_TEXTDEFAULTS9ATOM = 0x0FB0;
    public const RT_TEXTHEADERATOM = 0x0F9F;
    public const RT_TEXTINTERACTIVEINFOATOM = 0x0FDF;
    public const RT_TEXTMASTERSTYLEATOM = 0x0FA3;
    public const RT_TEXTMASTERSTYLE10ATOM = 0x0FB2;
    public const RT_TEXTMASTERSTYLE9ATOM = 0x0FAD;
    public const RT_TEXTPARAGRAPHFORMATEXCEPTIONATOM = 0x0FA5;
    public const RT_TEXTRULERATOM = 0x0FA6;
    public const RT_TEXTSPECIALINFOATOM = 0x0FAA;
    public const RT_TEXTSPECIALINFODEFAULTATOM = 0x0FA9;
    public const RT_TIMEANIMATEBEHAVIOR = 0xF134;
    public const RT_TIMEANIMATEBEHAVIORCONTAINER = 0xF12B;
    public const RT_TIMEANIMATIONVALUE = 0xF143;
    public const RT_TIMEANIMATIONVALUELIST = 0xF13F;
    public const RT_TIMEBEHAVIOR = 0xF133;
    public const RT_TIMEBEHAVIORCONTAINER = 0xF12A;
    public const RT_TIMECOLORBEHAVIOR = 0xF135;
    public const RT_TIMECOLORBEHAVIORCONTAINER = 0xF12C;
    public const RT_TIMECLIENTVISUALELEMENT = 0xF13C;
    public const RT_TIMECOMMANDBEHAVIOR = 0xF13B;
    public const RT_TIMECOMMANDBEHAVIORCONTAINER = 0xF132;
    public const RT_TIMECONDITION = 0xF128;
    public const RT_TIMECONDITIONCONTAINER = 0xF125;
    public const RT_TIMEEFFECTBEHAVIOR = 0xF136;
    public const RT_TIMEEFFECTBEHAVIORCONTAINER = 0xF12D;
    public const RT_TIMEEXTTIMENODECONTAINER = 0xF144;
    public const RT_TIMEITERATEDATA = 0xF140;
    public const RT_TIMEMODIFIER = 0xF129;
    public const RT_TIMEMOTIONBEHAVIOR = 0xF137;
    public const RT_TIMEMOTIONBEHAVIORCONTAINER = 0xF12E;
    public const RT_TIMENODE = 0xF127;
    public const RT_TIMEPROPERTYLIST = 0xF13D;
    public const RT_TIMEROTATIONBEHAVIOR = 0xF138;
    public const RT_TIMEROTATIONBEHAVIORCONTAINER = 0xF12F;
    public const RT_TIMESCALEBEHAVIOR = 0xF139;
    public const RT_TIMESCALEBEHAVIORCONTAINER = 0xF130;
    public const RT_TIMESEQUENCEDATA = 0xF141;
    public const RT_TIMESETBEHAVIOR = 0xF13A;
    public const RT_TIMESETBEHAVIORCONTAINER = 0xF131;
    public const RT_TIMESUBEFFECTCONTAINER = 0xF145;
    public const RT_TIMEVARIANT = 0xF142;
    public const RT_TIMEVARIANTLIST = 0xF13E;
    public const RT_USEREDITATOM = 0x0FF5;
    public const RT_VBAINFO = 0x03FF;
    public const RT_VBAINFOATOM = 0x0400;
    public const RT_VIEWINFOATOM = 0x03FD;
    public const RT_VISUALPAGEATOM = 0x2B01;
    public const RT_VISUALSHAPEATOM = 0x2AFB;

    /**
     * @see http://msdn.microsoft.com/en-us/library/dd926394(v=office.12).aspx
     */
    public const SL_BIGOBJECT = 0x0000000F;
    public const SL_BLANK = 0x00000010;
    public const SL_COLUMNTWOROWS = 0x0000000A;
    public const SL_FOUROBJECTS = 0x0000000E;
    public const SL_MASTERTITLE = 0x00000002;
    public const SL_TITLEBODY = 0x00000001;
    public const SL_TITLEONLY = 0x00000007;
    public const SL_TITLESLIDE = 0x00000000;
    public const SL_TWOCOLUMNS = 0x00000008;
    public const SL_TWOCOLUMNSROW = 0x0000000D;
    public const SL_TWOROWS = 0x00000009;
    public const SL_TWOROWSCOLUMN = 0x0000000B;
    public const SL_VERTICALTITLEBODY = 0x00000011;
    public const SL_VERTICALTWOROWS = 0x00000012;

    /**
     * Array with Fonts.
     *
     * @var array<int, string>
     */
    private $arrayFonts = [];

    /**
     * Array with Hyperlinks.
     *
     * @var array<int, array<string, string>>
     */
    private $arrayHyperlinks = [];

    /**
     * Array with Notes.
     *
     * @var array<int, int>
     */
    private $arrayNotes = [];

    /**
     * Array with Pictures.
     *
     * @var array<int, string>
     */
    private $arrayPictures = [];

    /**
     * Offset (in bytes) from the beginning of the PowerPoint Document Stream to the UserEditAtom record for the most recent user edit.
     *
     * @var int
     */
    private $offsetToCurrentEdit;

    /**
     * A structure that specifies a compressed table of sequential persist object identifiers and stream offsets to associated persist objects.
     *
     * @var array<int, int>
     */
    private $rgPersistDirEntry;

    /**
     * Offset (in bytes) from the beginning of the PowerPoint Document Stream to the PersistDirectoryAtom record for this user edit.
     *
     * @var int
     */
    private $offsetPersistDirectory;

    /**
     * Output Object.
     *
     * @var PhpPresentation
     */
    private $oPhpPresentation;

    /**
     * @var null|Group
     */
    private $oCurrentGroup;

    /**
     * @var bool
     */
    private $bFirstShapeGroup = false;

    /**
     * Stream "Powerpoint Document".
     *
     * @var string
     */
    private $streamPowerpointDocument;

    /**
     * Stream "Current User".
     *
     * @var string
     */
    private $streamCurrentUser;

    /**
     * Stream "Pictures".
     *
     * @var string
     */
    private $streamPictures;

    /**
     * @var int
     */
    private $inMainType;

    /**
     * @var null|int
     */
    private $currentNote;

    /**
     * @var null|string
     */
    private $filename;

    /**
     * Can the current \PhpOffice\PhpPresentation\Reader\ReaderInterface read the file?
     */
    public function canRead(string $pFilename): bool
    {
        return $this->fileSupportsUnserializePhpPresentation($pFilename);
    }

    /**
     * Does a file support UnserializePhpPresentation ?
     */
    public function fileSupportsUnserializePhpPresentation(string $pFilename = ''): bool
    {
        // Check if file exists
        if (!file_exists($pFilename)) {
            throw new FileNotFoundException($pFilename);
        }

        try {
            // Use ParseXL for the hard work.
            $ole = new OLERead();
            // get excel data
            $ole->read($pFilename);

            return true;
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * Loads PhpPresentation Serialized file.
     */
    public function load(string $pFilename): PhpPresentation
    {
        // Unserialize... First make sure the file supports it!
        if (!$this->fileSupportsUnserializePhpPresentation($pFilename)) {
            throw new InvalidFileFormatException($pFilename, self::class);
        }

        $this->filename = $pFilename;

        return $this->loadFile();
    }

    /**
     * Load PhpPresentation Serialized file.
     */
    private function loadFile(): PhpPresentation
    {
        $this->oPhpPresentation = new PhpPresentation();
        $this->oPhpPresentation->removeSlideByIndex();

        // Read OLE Blocks
        $this->loadOLE();
        // Read pictures in the Pictures Stream
        $this->loadPicturesStream();
        // Read information in the Current User Stream
        $this->loadCurrentUserStream();
        // Read information in the PowerPoint Document Stream
        $this->loadPowerpointDocumentStream();

        return $this->oPhpPresentation;
    }

    /**
     * Read OLE Part.
     */
    private function loadOLE(): void
    {
        // OLE reader
        $oOLE = new OLERead();
        $oOLE->read($this->filename);

        // PowerPoint Document Stream
        $this->streamPowerpointDocument = $oOLE->getStream($oOLE->powerpointDocument);

        // Current User Stream
        $this->streamCurrentUser = $oOLE->getStream($oOLE->currentUser);

        // Get pictures data
        $this->streamPictures = $oOLE->getStream($oOLE->pictures);
    }

    /**
     * Stream Pictures.
     *
     * @see http://msdn.microsoft.com/en-us/library/dd920746(v=office.12).aspx
     */
    private function loadPicturesStream(): void
    {
        $stream = $this->streamPictures;

        $pos = 0;
        do {
            $arrayRH = $this->loadRecordHeader($stream, $pos);
            $pos += 8;
            $readSuccess = false;
            if (0x00 == $arrayRH['recVer'] && (0xF007 == $arrayRH['recType'] || ($arrayRH['recType'] >= 0xF018 && $arrayRH['recType'] <= 0xF117))) {
                //@link : http://msdn.microsoft.com/en-us/library/dd950560(v=office.12).aspx
                if (0xF007 == $arrayRH['recType']) {
                    // OfficeArtFBSE
                    throw new FeatureNotImplementedException();
                }
                // $arrayRH['recType'] >= 0xF018 && $arrayRH['recType'] <= 0xF117
                $arrayRecord = $this->readRecordOfficeArtBlip($stream, $pos - 8);
                if ($arrayRecord['length'] > 0) {
                    $pos += $arrayRecord['length'];
                    $this->arrayPictures[] = $arrayRecord['picture'];
                }
                $readSuccess = true;
            }
        } while (true === $readSuccess);
    }

    /**
     * Stream Current User.
     *
     * @see http://msdn.microsoft.com/en-us/library/dd908567(v=office.12).aspx
     */
    private function loadCurrentUserStream(): void
    {
        $pos = 0;

        /**
         * CurrentUserAtom : http://msdn.microsoft.com/en-us/library/dd948895(v=office.12).aspx.
         */
        // RecordHeader : http://msdn.microsoft.com/en-us/library/dd926377(v=office.12).aspx
        $rHeader = $this->loadRecordHeader($this->streamCurrentUser, $pos);
        $pos += 8;
        if (0x0 != $rHeader['recVer'] || 0x000 != $rHeader['recInstance'] || self::RT_CURRENTUSERATOM != $rHeader['recType']) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > RecordHeader');
        }

        // Size
        $size = self::getInt4d($this->streamCurrentUser, $pos);
        $pos += 4;
        if (0x00000014 != $size) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > Size');
        }

        // headerToken
        $headerToken = self::getInt4d($this->streamCurrentUser, $pos);
        $pos += 4;
        if (0xF3D1C4DF == $headerToken) {
            // Encrypted file
            throw new FeatureNotImplementedException();
        }

        // offsetToCurrentEdit
        $this->offsetToCurrentEdit = self::getInt4d($this->streamCurrentUser, $pos);
        $pos += 4;

        // lenUserName
        $lenUserName = self::getInt2d($this->streamCurrentUser, $pos);
        $pos += 2;
        if ($lenUserName > 255) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > lenUserName');
        }

        // docFileVersion
        $docFileVersion = self::getInt2d($this->streamCurrentUser, $pos);
        $pos += 2;
        if (0x03F4 != $docFileVersion) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > docFileVersion');
        }

        // majorVersion
        $majorVersion = self::getInt1d($this->streamCurrentUser, $pos);
        ++$pos;
        if (0x03 != $majorVersion) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > majorVersion');
        }

        // minorVersion
        $minorVersion = self::getInt1d($this->streamCurrentUser, $pos);
        ++$pos;
        if (0x00 != $minorVersion) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > minorVersion');
        }

        // unused
        $pos += 2;

        // ansiUserName
        // $ansiUserName = '';
        do {
            $char = self::getInt1d($this->streamCurrentUser, $pos);
            if (($char >= 0x00 && $char <= 0x1F) || ($char >= 0x7F && $char <= 0x9F)) {
                $char = false;
            } else {
                // $ansiUserName .= chr($char);
                ++$pos;
            }
        } while (false !== $char);

        // relVersion
        $relVersion = self::getInt4d($this->streamCurrentUser, $pos);
        $pos += 4;
        if (0x00000008 != $relVersion && 0x00000009 != $relVersion) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > relVersion');
        }

        // unicodeUserName
        // $unicodeUserName = '';
        for ($inc = 0; $inc < $lenUserName; ++$inc) {
            $char = self::getInt2d($this->streamCurrentUser, $pos);
            if (($char >= 0x00 && $char <= 0x1F) || ($char >= 0x7F && $char <= 0x9F)) {
                break;
            }
            // $unicodeUserName .= chr($char);
            $pos += 2;
        }
    }

    /**
     * Stream Powerpoint Document.
     *
     * @see http://msdn.microsoft.com/en-us/library/dd921564(v=office.12).aspx
     */
    private function loadPowerpointDocumentStream(): void
    {
        $this->readRecordUserEditAtom($this->streamPowerpointDocument, $this->offsetToCurrentEdit);

        $this->readRecordPersistDirectoryAtom($this->streamPowerpointDocument, $this->offsetPersistDirectory);

        foreach ($this->rgPersistDirEntry as $offsetDir) {
            $pos = $offsetDir;

            $rHeader = $this->loadRecordHeader($this->streamPowerpointDocument, $pos);
            $pos += 8;
            $this->inMainType = $rHeader['recType'];
            $this->currentNote = null;
            switch ($rHeader['recType']) {
                case self::RT_DOCUMENT:
                    $this->readRecordDocumentContainer($this->streamPowerpointDocument, $pos);

                    break;
                case self::RT_NOTES:
                    $this->readRecordNotesContainer($this->streamPowerpointDocument, $pos);

                    break;
                case self::RT_SLIDE:
                    $this->readRecordSlideContainer($this->streamPowerpointDocument, $pos);

                    break;
                default:
                    break;
            }
        }
    }

    /**
     * Read a record header.
     *
     * @return array<string, int>
     */
    private function loadRecordHeader(string $stream, int $pos): array
    {
        $rec = self::getInt2d($stream, $pos);
        $recType = self::getInt2d($stream, $pos + 2);
        $recLen = self::getInt4d($stream, $pos + 4);

        return [
            'recVer' => ($rec >> 0) & bindec('1111'),
            'recInstance' => ($rec >> 4) & bindec('111111111111'),
            'recType' => $recType,
            'recLen' => $recLen,
        ];
    }

    /**
     * Read 8-bit unsigned integer.
     */
    public static function getInt1d(string $data, int $pos): int
    {
        return ord($data[$pos]);
    }

    /**
     * Read 16-bit unsigned integer.
     */
    public static function getInt2d(string $data, int $pos): int
    {
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
    }

    /**
     * Read 32-bit signed integer.
     */
    public static function getInt4d(string $data, int $pos): int
    {
        // FIX: represent numbers correctly on 64-bit system
        // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
        // Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
        $or24 = ord($data[$pos + 3]);

        $ord24 = ($or24 & 127) << 24;
        if ($or24 >= 128) {
            // negative number
            $ord24 = -abs((256 - $or24) << 24);
        }

        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $ord24;
    }

    /**
     * A container record that specifies the animation and sound information for a shape.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd772900(v=office.12).aspx
     */
    private function readRecordAnimationInfoContainer(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ANIMATIONINFO == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;

            // animationAtom
            // animationSound
            throw new FeatureNotImplementedException();
        }

        return $arrayReturn;
    }

    /**
     * A container record that specifies information about the document.
     *
     * @see http://msdn.microsoft.com/en-us/library/dd947357(v=office.12).aspx
     */
    private function readRecordDocumentContainer(string $stream, int $pos): void
    {
        $documentAtom = $this->loadRecordHeader($stream, $pos);
        $pos += 8;
        if (0x1 != $documentAtom['recVer'] || 0x000 != $documentAtom['recInstance'] || self::RT_DOCUMENTATOM != $documentAtom['recType']) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : RTDocument > DocumentAtom');
        }
        $pos += $documentAtom['recLen'];

        $exObjList = $this->loadRecordHeader($stream, $pos);
        if (0xF == $exObjList['recVer'] && 0x000 == $exObjList['recInstance'] && self::RT_EXTERNALOBJECTLIST == $exObjList['recType']) {
            $pos += 8;
            // exObjListAtom > rh
            $exObjListAtom = $this->loadRecordHeader($stream, $pos);
            if (0x0 != $exObjListAtom['recVer'] || 0x000 != $exObjListAtom['recInstance'] || self::RT_EXTERNALOBJECTLISTATOM != $exObjListAtom['recType'] || 0x00000004 != $exObjListAtom['recLen']) {
                throw new InvalidFileFormatException($this->filename, self::class, 'Location : RTDocument > DocumentAtom > exObjList > exObjListAtom');
            }
            $pos += 8;
            // exObjListAtom > exObjIdSeed
            $pos += 4;
            // rgChildRec
            $exObjList['recLen'] -= 12;
            do {
                $childRec = $this->loadRecordHeader($stream, $pos);
                $pos += 8;
                $exObjList['recLen'] -= 8;
                switch ($childRec['recType']) {
                    case self::RT_EXTERNALHYPERLINK:
                        //@link : http://msdn.microsoft.com/en-us/library/dd944995(v=office.12).aspx
                        // exHyperlinkAtom > rh
                        $exHyperlinkAtom = $this->loadRecordHeader($stream, $pos);
                        if (0x0 != $exHyperlinkAtom['recVer'] || 0x000 != $exHyperlinkAtom['recInstance'] || self::RT_EXTERNALHYPERLINKATOM != $exHyperlinkAtom['recType'] || 0x00000004 != $exHyperlinkAtom['recLen']) {
                            throw new InvalidFileFormatException($this->filename, self::class, 'Location : RTDocument > DocumentAtom > exObjList > rgChildRec > RT_ExternalHyperlink');
                        }
                        $pos += 8;
                        $exObjList['recLen'] -= 8;
                        // exHyperlinkAtom > exHyperlinkId
                        $exHyperlinkId = self::getInt4d($stream, $pos);
                        $pos += 4;
                        $exObjList['recLen'] -= 4;

                        $this->arrayHyperlinks[$exHyperlinkId] = [];
                        // friendlyNameAtom
                        $friendlyNameAtom = $this->loadRecordHeader($stream, $pos);
                        if (0x0 == $friendlyNameAtom['recVer'] && 0x000 == $friendlyNameAtom['recInstance'] && self::RT_CSTRING == $friendlyNameAtom['recType'] && $friendlyNameAtom['recLen'] % 2 == 0) {
                            $pos += 8;
                            $exObjList['recLen'] -= 8;
                            $this->arrayHyperlinks[$exHyperlinkId]['text'] = '';
                            for ($inc = 0; $inc < ($friendlyNameAtom['recLen'] / 2); ++$inc) {
                                $char = self::getInt2d($stream, $pos);
                                $pos += 2;
                                $exObjList['recLen'] -= 2;
                                $this->arrayHyperlinks[$exHyperlinkId]['text'] .= chr($char);
                            }
                        }
                        // targetAtom
                        $targetAtom = $this->loadRecordHeader($stream, $pos);
                        if (0x0 == $targetAtom['recVer'] && 0x001 == $targetAtom['recInstance'] && self::RT_CSTRING == $targetAtom['recType'] && $targetAtom['recLen'] % 2 == 0) {
                            $pos += 8;
                            $exObjList['recLen'] -= 8;
                            $this->arrayHyperlinks[$exHyperlinkId]['url'] = '';
                            for ($inc = 0; $inc < ($targetAtom['recLen'] / 2); ++$inc) {
                                $char = self::getInt2d($stream, $pos);
                                $pos += 2;
                                $exObjList['recLen'] -= 2;
                                $this->arrayHyperlinks[$exHyperlinkId]['url'] .= chr($char);
                            }
                        }
                        // locationAtom
                        $locationAtom = $this->loadRecordHeader($stream, $pos);
                        if (0x0 == $locationAtom['recVer'] && 0x003 == $locationAtom['recInstance'] && self::RT_CSTRING == $locationAtom['recType'] && $locationAtom['recLen'] % 2 == 0) {
                            $pos += 8;
                            $exObjList['recLen'] -= 8;
                            $string = '';
                            for ($inc = 0; $inc < ($locationAtom['recLen'] / 2); ++$inc) {
                                $char = self::getInt2d($stream, $pos);
                                $pos += 2;
                                $exObjList['recLen'] -= 2;
                                $string .= chr($char);
                            }
                        }

                        break;
                    default:
                        // var_dump(dechex((int) $childRec['recType']));
                        throw new FeatureNotImplementedException();
                }
            } while ($exObjList['recLen'] > 0);
        }

        //@link : http://msdn.microsoft.com/en-us/library/dd907813(v=office.12).aspx
        $documentTextInfo = $this->loadRecordHeader($stream, $pos);
        if (0xF == $documentTextInfo['recVer'] && 0x000 == $documentTextInfo['recInstance'] && self::RT_ENVIRONMENT == $documentTextInfo['recType']) {
            $pos += 8;
            //@link : http://msdn.microsoft.com/en-us/library/dd952717(v=office.12).aspx
            $kinsoku = $this->loadRecordHeader($stream, $pos);
            if (0xF == $kinsoku['recVer'] && 0x002 == $kinsoku['recInstance'] && self::RT_KINSOKU == $kinsoku['recType']) {
                $pos += 8;
                $pos += $kinsoku['recLen'];
            }

            //@link : http://msdn.microsoft.com/en-us/library/dd948152(v=office.12).aspx
            $fontCollection = $this->loadRecordHeader($stream, $pos);
            if (0xF == $fontCollection['recVer'] && 0x000 == $fontCollection['recInstance'] && self::RT_FONTCOLLECTION == $fontCollection['recType']) {
                $pos += 8;
                do {
                    $fontEntityAtom = $this->loadRecordHeader($stream, $pos);
                    $pos += 8;
                    $fontCollection['recLen'] -= 8;
                    if (0x0 != $fontEntityAtom['recVer'] || $fontEntityAtom['recInstance'] > 128 || self::RT_FONTENTITYATOM != $fontEntityAtom['recType']) {
                        throw new InvalidFileFormatException($this->filename, self::class, 'Location : RTDocument > RT_Environment > RT_FontCollection > RT_FontEntityAtom');
                    }
                    $string = '';
                    for ($inc = 0; $inc < 32; ++$inc) {
                        $char = self::getInt2d($stream, $pos);
                        $pos += 2;
                        $fontCollection['recLen'] -= 2;
                        $string .= chr($char);
                    }
                    $this->arrayFonts[] = $string;

                    // lfCharSet (1 byte)
                    ++$pos;
                    --$fontCollection['recLen'];

                    // fEmbedSubsetted (1 bit)
                    // unused (7 bits)
                    ++$pos;
                    --$fontCollection['recLen'];

                    // rasterFontType (1 bit)
                    // deviceFontType (1 bit)
                    // truetypeFontType (1 bit)
                    // fNoFontSubstitution (1 bit)
                    // reserved (4 bits)
                    ++$pos;
                    --$fontCollection['recLen'];

                    // lfPitchAndFamily (1 byte)
                    ++$pos;
                    --$fontCollection['recLen'];

                    $fontEmbedData1 = $this->loadRecordHeader($stream, $pos);
                    if (0x0 == $fontEmbedData1['recVer'] && $fontEmbedData1['recInstance'] >= 0x000 && $fontEmbedData1['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData1['recType']) {
                        $pos += 8;
                        $fontCollection['recLen'] -= 8;
                        $pos += $fontEmbedData1['recLen'];
                        $fontCollection['recLen'] -= $fontEmbedData1['recLen'];
                    }

                    $fontEmbedData2 = $this->loadRecordHeader($stream, $pos);
                    if (0x0 == $fontEmbedData2['recVer'] && $fontEmbedData2['recInstance'] >= 0x000 && $fontEmbedData2['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData2['recType']) {
                        $pos += 8;
                        $fontCollection['recLen'] -= 8;
                        $pos += $fontEmbedData2['recLen'];
                        $fontCollection['recLen'] -= $fontEmbedData2['recLen'];
                    }

                    $fontEmbedData3 = $this->loadRecordHeader($stream, $pos);
                    if (0x0 == $fontEmbedData3['recVer'] && $fontEmbedData3['recInstance'] >= 0x000 && $fontEmbedData3['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData3['recType']) {
                        $pos += 8;
                        $fontCollection['recLen'] -= 8;
                        $pos += $fontEmbedData3['recLen'];
                        $fontCollection['recLen'] -= $fontEmbedData3['recLen'];
                    }

                    $fontEmbedData4 = $this->loadRecordHeader($stream, $pos);
                    if (0x0 == $fontEmbedData4['recVer'] && $fontEmbedData4['recInstance'] >= 0x000 && $fontEmbedData4['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData4['recType']) {
                        $pos += 8;
                        $fontCollection['recLen'] -= 8;
                        $pos += $fontEmbedData4['recLen'];
                        $fontCollection['recLen'] -= $fontEmbedData4['recLen'];
                    }
                } while ($fontCollection['recLen'] > 0);
            }

            $textCFDefaultsAtom = $this->loadRecordHeader($stream, $pos);
            if (0x0 == $textCFDefaultsAtom['recVer'] && 0x000 == $textCFDefaultsAtom['recInstance'] && self::RT_TEXTCHARFORMATEXCEPTIONATOM == $textCFDefaultsAtom['recType']) {
                $pos += 8;
                $pos += $textCFDefaultsAtom['recLen'];
            }

            $textPFDefaultsAtom = $this->loadRecordHeader($stream, $pos);
            if (0x0 == $textPFDefaultsAtom['recVer'] && 0x000 == $textPFDefaultsAtom['recInstance'] && self::RT_TEXTPARAGRAPHFORMATEXCEPTIONATOM == $textPFDefaultsAtom['recType']) {
                $pos += 8;
                $pos += $textPFDefaultsAtom['recLen'];
            }

            $defaultRulerAtom = $this->loadRecordHeader($stream, $pos);
            if (0x0 == $defaultRulerAtom['recVer'] && 0x000 == $defaultRulerAtom['recInstance'] && self::RT_DEFAULTRULERATOM == $defaultRulerAtom['recType']) {
                $pos += 8;
                $pos += $defaultRulerAtom['recLen'];
            }

            $textSIDefaultsAtom = $this->loadRecordHeader($stream, $pos);
            if (0x0 == $textSIDefaultsAtom['recVer'] && 0x000 == $textSIDefaultsAtom['recInstance'] && self::RT_TEXTSPECIALINFODEFAULTATOM == $textSIDefaultsAtom['recType']) {
                $pos += 8;
                $pos += $textSIDefaultsAtom['recLen'];
            }

            $textMasterStyleAtom = $this->loadRecordHeader($stream, $pos);
            if (0x0 == $textMasterStyleAtom['recVer'] && self::RT_TEXTMASTERSTYLEATOM == $textMasterStyleAtom['recType']) {
                $pos += 8;
                $pos += $textMasterStyleAtom['recLen'];
            }
        }

        $soundCollection = $this->loadRecordHeader($stream, $pos);
        if (0xF == $soundCollection['recVer'] && 0x005 == $soundCollection['recInstance'] && self::RT_SOUNDCOLLECTION == $soundCollection['recType']) {
            $pos += 8;
            $pos += $soundCollection['recLen'];
        }

        $drawingGroup = $this->loadRecordHeader($stream, $pos);
        if (0xF == $drawingGroup['recVer'] && 0x000 == $drawingGroup['recInstance'] && self::RT_DRAWINGGROUP == $drawingGroup['recType']) {
            $drawing = $this->readRecordDrawingGroupContainer($stream, $pos);
            $pos += 8;
            $pos += $drawing['length'];
        }

        $masterList = $this->loadRecordHeader($stream, $pos);
        if (0xF == $masterList['recVer'] && 0x001 == $masterList['recInstance'] && self::RT_SLIDELISTWITHTEXT == $masterList['recType']) {
            $pos += 8;
            $pos += $masterList['recLen'];
        }

        $docInfoList = $this->loadRecordHeader($stream, $pos);
        if (0xF == $docInfoList['recVer'] && 0x000 == $docInfoList['recInstance'] && self::RT_LIST == $docInfoList['recType']) {
            $pos += 8;
            $pos += $docInfoList['recLen'];
        }

        $slideHF = $this->loadRecordHeader($stream, $pos);
        if (0xF == $slideHF['recVer'] && 0x003 == $slideHF['recInstance'] && self::RT_HEADERSFOOTERS == $slideHF['recType']) {
            $pos += 8;
            $pos += $slideHF['recLen'];
        }

        $notesHF = $this->loadRecordHeader($stream, $pos);
        if (0xF == $notesHF['recVer'] && 0x004 == $notesHF['recInstance'] && self::RT_HEADERSFOOTERS == $notesHF['recType']) {
            $pos += 8;
            $pos += $notesHF['recLen'];
        }

        // SlideListWithTextContainer
        $slideList = $this->loadRecordHeader($stream, $pos);
        if (0xF == $slideList['recVer'] && 0x000 == $slideList['recInstance'] && self::RT_SLIDELISTWITHTEXT == $slideList['recType']) {
            $pos += 8;
            do {
                // SlideListWithTextSubContainerOrAtom
                $rhSlideList = $this->loadRecordHeader($stream, $pos);
                if (0x0 == $rhSlideList['recVer'] && 0x000 == $rhSlideList['recInstance'] && self::RT_SLIDEPERSISTATOM == $rhSlideList['recType'] && 0x00000014 == $rhSlideList['recLen']) {
                    $pos += 8;
                    $slideList['recLen'] -= 8;
                    // persistIdRef
                    $pos += 4;
                    $slideList['recLen'] -= 4;
                    // reserved1 - fShouldCollapse - fNonOutlineData - reserved2
                    $pos += 4;
                    $slideList['recLen'] -= 4;
                    // cTexts
                    $pos += 4;
                    $slideList['recLen'] -= 4;
                    // slideId
                    $slideId = self::getInt4d($stream, $pos);
                    if (-2147483648 == $slideId) {
                        $slideId = 0;
                    }
                    if ($slideId > 0) {
                        $this->arrayNotes[$this->oPhpPresentation->getActiveSlideIndex()] = $slideId;
                    }
                    $pos += 4;
                    $slideList['recLen'] -= 4;
                    // reserved3
                    $pos += 4;
                    $slideList['recLen'] -= 4;
                }
            } while ($slideList['recLen'] > 0);
        }
    }

    /**
     * An atom record that specifies information about a slide.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx
     */
    private function readRecordDrawingContainer(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_DRAWING == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;

            $officeArtDg = $this->readRecordOfficeArtDgContainer($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $officeArtDg['length'];
        }

        return $arrayReturn;
    }

    /**
     * @return array<string, int>
     */
    private function readRecordDrawingGroupContainer(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_DRAWINGGROUP == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies a reference to an external object.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd910388(v=office.12).aspx
     */
    private function readRecordExObjRefAtom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_EXTERNALOBJECTREFATOM == $data['recType'] && 0x00000004 == $data['recLen']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Datas
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies a type of action to be performed.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd953300(v=office.12).aspx
     */
    private function readRecordInteractiveInfoAtom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_INTERACTIVEINFOATOM == $data['recType'] && 0x00000010 == $data['recLen']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // soundIdRef
            $arrayReturn['length'] += 4;
            // exHyperlinkIdRef
            $arrayReturn['exHyperlinkIdRef'] = self::getInt4d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 4;
            // action
            ++$arrayReturn['length'];
            // oleVerb
            ++$arrayReturn['length'];
            // jump
            ++$arrayReturn['length'];
            // fAnimated (1 bit)
            // fStopSound (1 bit)
            // fCustomShowReturn (1 bit)
            // fVisited (1 bit)
            // reserved (4 bits)
            ++$arrayReturn['length'];
            // hyperlinkType
            ++$arrayReturn['length'];
            // unused
            $arrayReturn['length'] += 3;
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies the name of a macro, a file name, or a named show.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd925121(v=office.12).aspx
     */
    private function readRecordMacroNameAtom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x002 == $data['recInstance'] && self::RT_CSTRING == $data['recType'] && $data['recLen'] % 2 == 0) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Datas
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * A container record that specifies what actions to perform when interacting with an object by means of a mouse click.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd952348(v=office.12).aspx
     */
    private function readRecordMouseClickInteractiveInfoContainer(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_INTERACTIVEINFO == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // interactiveInfoAtom
            $interactiveInfoAtom = $this->readRecordInteractiveInfoAtom($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $interactiveInfoAtom['length'];
            if ($interactiveInfoAtom['length'] > 0) {
                $arrayReturn['exHyperlinkIdRef'] = $interactiveInfoAtom['exHyperlinkIdRef'];
            }
            // macroNameAtom
            $macroNameAtom = $this->readRecordMacroNameAtom($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $macroNameAtom['length'];
        }

        return $arrayReturn;
    }

    /**
     * A container record that specifies what actions to perform when interacting with an object by moving the mouse cursor over it.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd925811(v=office.12).aspx
     */
    private function readRecordMouseOverInteractiveInfoContainer(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x001 == $data['recInstance'] && self::RT_INTERACTIVEINFO == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;

            // interactiveInfoAtom
            // macroNameAtom
            throw new FeatureNotImplementedException();
        }

        return $arrayReturn;
    }

    /**
     * The OfficeArtBlip record specifies BLIP file data.
     *
     * @return array{'length': int, 'picture': null|string}
     *
     * @see https://msdn.microsoft.com/en-us/library/dd910081(v=office.12).aspx
     */
    private function readRecordOfficeArtBlip(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
            'picture' => null,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && ($data['recType'] >= 0xF018 && $data['recType'] <= 0xF117)) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Datas
            switch ($data['recType']) {
                case self::OFFICEARTBLIPJPG:
                case self::OFFICEARTBLIPPNG:
                    // rgbUid1
                    $arrayReturn['length'] += 16;
                    $data['recLen'] -= 16;
                    if (0x6E1 == $data['recInstance']) {
                        // rgbUid2
                        $arrayReturn['length'] += 16;
                        $data['recLen'] -= 16;
                    }
                    // tag
                    ++$arrayReturn['length'];
                    --$data['recLen'];
                    // BLIPFileData
                    $arrayReturn['picture'] = substr($this->streamPictures, $pos + $arrayReturn['length'], $data['recLen']);
                    $arrayReturn['length'] += $data['recLen'];

                    break;
                default:
                    // var_dump(dechex((int) $data['recType']))
                    throw new FeatureNotImplementedException();
            }
        }

        return $arrayReturn;
    }

    /**
     * The OfficeArtChildAnchor record specifies four signed integers that specify the anchor for the shape that contains this record.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd922720(v=office.12).aspx
     */
    private function readRecordOfficeArtChildAnchor(string $stream, int $pos)
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF00F == $data['recType'] && 0x00000010 == $data['recLen']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Datas
            $arrayReturn['left'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 4;
            $arrayReturn['top'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 4;
            $arrayReturn['width'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']) - $arrayReturn['left'];
            $arrayReturn['length'] += 4;
            $arrayReturn['height'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']) - $arrayReturn['top'];
            $arrayReturn['length'] += 4;
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies the location of a shape.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd922797(v=office.12).aspx
     */
    private function readRecordOfficeArtClientAnchor(string $stream, int $pos)
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF010 == $data['recType'] && (0x00000008 == $data['recLen'] || 0x00000010 == $data['recLen'])) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Datas
            switch ($data['recLen']) {
                case 0x00000008:
                    $arrayReturn['top'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6);
                    $arrayReturn['length'] += 2;
                    $arrayReturn['left'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6);
                    $arrayReturn['length'] += 2;
                    $arrayReturn['width'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6) - $arrayReturn['left'];
                    $arrayReturn['length'] += 2;
                    $arrayReturn['height'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6) - $arrayReturn['left'];
                    $arrayReturn['length'] += 2;
                    $pos += 8;

                    break;
                case 0x00000010:
                    // record OfficeArtClientAnchor (0x00000010)
                    throw new FeatureNotImplementedException();
            }
        }

        return $arrayReturn;
    }

    /**
     * A container record that specifies text related data for a shape.
     *
     * @return array{'length': int, 'alignH': null|string, 'text': string, 'numParts': int, 'numTexts': int, 'hyperlink': array<int, array<string, int>>, 'part': array{'length': int, 'strLenRT': int, 'partLength': float|int, 'bold': bool, 'italic': bool, 'underline': bool, 'fontName': string, 'fontSize': int, 'color': Color}}
     *
     * @see https://msdn.microsoft.com/en-us/library/dd910958(v=office.12).aspx
     */
    private function readRecordOfficeArtClientTextbox(string $stream, int $pos)
    {
        $arrayReturn = [
            'length' => 0,
            'text' => '',
            'numParts' => 0,
            'numTexts' => 0,
            'hyperlink' => [],
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        // recVer 0xF
        // Doc : 0x0    https://msdn.microsoft.com/en-us/library/dd910958(v=office.12).aspx
        // Sample : 0xF https://msdn.microsoft.com/en-us/library/dd953497(v=office.12).aspx
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF00D == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Datas
            $strLen = 0;
            do {
                $rhChild = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']);
                // @link : https://msdn.microsoft.com/en-us/library/dd947039(v=office.12).aspx
                // echo dechex($rhChild['recType']).'-'.$rhChild['recType'].EOL;
                switch ($rhChild['recType']) {
                    case self::RT_INTERACTIVEINFO:
                        //@link : http://msdn.microsoft.com/en-us/library/dd948623(v=office.12).aspx
                        if (0x0000 == $rhChild['recInstance']) {
                            $mouseClickInfo = $this->readRecordMouseClickInteractiveInfoContainer($stream, $pos + $arrayReturn['length']);
                            $arrayReturn['length'] += $mouseClickInfo['length'];
                            $arrayReturn['hyperlink'][]['id'] = $mouseClickInfo['exHyperlinkIdRef'];
                        }
                        if (0x0001 == $rhChild['recInstance']) {
                            $mouseOverInfo = $this->readRecordMouseOverInteractiveInfoContainer($stream, $pos + $arrayReturn['length']);
                            $arrayReturn['length'] += $mouseOverInfo['length'];
                        }

                        break;
                    case self::RT_STYLETEXTPROPATOM:
                        $arrayReturn['length'] += 8;
                        // @link : http://msdn.microsoft.com/en-us/library/dd950647(v=office.12).aspx
                        // rgTextPFRun
                        $strLenRT = $strLen + 1;
                        do {
                            $strucTextPFRun = $this->readStructureTextPFRun($stream, $pos + $arrayReturn['length'], $strLenRT);
                            ++$arrayReturn['numTexts'];
                            $arrayReturn['text' . $arrayReturn['numTexts']] = $strucTextPFRun;
                            if (isset($strucTextPFRun['alignH'])) {
                                $arrayReturn['alignH'] = $strucTextPFRun['alignH'];
                            }
                            $strLenRT = $strucTextPFRun['strLenRT'];
                            $arrayReturn['length'] += $strucTextPFRun['length'];
                        } while ($strLenRT > 0);
                        // rgTextCFRun
                        $strLenRT = $strLen + 1;
                        do {
                            $strucTextCFRun = $this->readStructureTextCFRun($stream, $pos + $arrayReturn['length'], $strLenRT);
                            ++$arrayReturn['numParts'];
                            $arrayReturn['part' . $arrayReturn['numParts']] = $strucTextCFRun;
                            $strLenRT = $strucTextCFRun['strLenRT'];
                            $arrayReturn['length'] += $strucTextCFRun['length'];
                        } while ($strLenRT > 0);

                        break;
                    case self::RT_TEXTBYTESATOM:
                        $arrayReturn['length'] += 8;
                        // @link : https://msdn.microsoft.com/en-us/library/dd947905(v=office.12).aspx
                        $strLen = (int) $rhChild['recLen'];
                        for ($inc = 0; $inc < $strLen; ++$inc) {
                            $char = self::getInt1d($stream, $pos + $arrayReturn['length']);
                            if (0x0B == $char) {
                                $char = 0x20;
                            }
                            $arrayReturn['text'] .= Text::chr($char);
                            ++$arrayReturn['length'];
                        }

                        break;
                    case self::RT_TEXTCHARSATOM:
                        $arrayReturn['length'] += 8;
                        // @link : http://msdn.microsoft.com/en-us/library/dd772921(v=office.12).aspx
                        $strLen = (int) ($rhChild['recLen'] / 2);
                        for ($inc = 0; $inc < $strLen; ++$inc) {
                            $char = self::getInt2d($stream, $pos + $arrayReturn['length']);
                            if (0x0B == $char) {
                                $char = 0x20;
                            }
                            $arrayReturn['text'] .= Text::chr($char);
                            $arrayReturn['length'] += 2;
                        }

                        break;
                    case self::RT_TEXTHEADERATOM:
                        $arrayReturn['length'] += 8;
                        // @link : http://msdn.microsoft.com/en-us/library/dd905272(v=office.12).aspx
                        // textType
                        $arrayReturn['length'] += 4;

                        break;
                    case self::RT_TEXTINTERACTIVEINFOATOM:
                        $arrayReturn['length'] += 8;
                        //@link : http://msdn.microsoft.com/en-us/library/dd947973(v=office.12).aspx
                        if (0x0000 == $rhChild['recInstance']) {
                            //@todo : MouseClickTextInteractiveInfoAtom
                            $arrayReturn['hyperlink'][count($arrayReturn['hyperlink']) - 1]['start'] = self::getInt4d($stream, $pos + +$arrayReturn['length']);
                            $arrayReturn['length'] += 4;

                            $arrayReturn['hyperlink'][count($arrayReturn['hyperlink']) - 1]['end'] = self::getInt4d($stream, $pos + +$arrayReturn['length']);
                            $arrayReturn['length'] += 4;
                        }
                        if (0x0001 == $rhChild['recInstance']) {
                            throw new FeatureNotImplementedException();
                        }

                        break;
                    case self::RT_TEXTSPECIALINFOATOM:
                        $arrayReturn['length'] += 8;
                        // @link : http://msdn.microsoft.com/en-us/library/dd945296(v=office.12).aspx
                        $strLenRT = $strLen + 1;
                        do {
                            $structTextSIRun = $this->readStructureTextSIRun($stream, $pos + $arrayReturn['length'], $strLenRT);
                            $strLenRT = $structTextSIRun['strLenRT'];
                            $arrayReturn['length'] += $structTextSIRun['length'];
                        } while ($strLenRT > 0);

                        break;
                    case self::RT_TEXTRULERATOM:
                        $arrayReturn['length'] += 8;
                        // @link : http://msdn.microsoft.com/en-us/library/dd953212(v=office.12).aspx
                        $structRuler = $this->readStructureTextRuler($stream, $pos + $arrayReturn['length']);
                        $arrayReturn['length'] += $structRuler['length'];

                        break;
                    case self::RT_SLIDENUMBERMETACHARATOM:
                        $datasRecord = $this->readRecordSlideNumberMCAtom($stream, $pos + $arrayReturn['length']);
                        $arrayReturn['length'] += $datasRecord['length'];

                        break;
                    default:
                        $arrayReturn['length'] += 8;
                        $arrayReturn['length'] += $rhChild['recLen'];
                }
            } while (($data['recLen'] - $arrayReturn['length']) > 0);
        }

        return $arrayReturn;
    }

    /**
     * The OfficeArtSpContainer record specifies a shape container.
     *
     * @return array{'length': int, 'shape': null|AbstractShape}
     *
     * @see https://msdn.microsoft.com/en-us/library/dd943794(v=office.12).aspx
     */
    private function readRecordOfficeArtSpContainer(string $stream, int $pos)
    {
        $arrayReturn = [
            'length' => 0,
            'shape' => null,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF004 == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // shapeGroup
            $shapeGroup = $this->readRecordOfficeArtFSPGR($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $shapeGroup['length'];

            // shapeProp
            $shapeProp = $this->readRecordOfficeArtFSP($stream, $pos + $arrayReturn['length']);
            if (0 == $shapeProp['length']) {
                throw new InvalidFileFormatException($this->filename, self::class);
            }
            $arrayReturn['length'] += $shapeProp['length'];

            if (0x1 == $shapeProp['fDeleted'] && 0x0 == $shapeProp['fChild']) {
                // deletedShape
                $deletedShape = $this->readRecordOfficeArtFPSPL($stream, $pos + $arrayReturn['length']);
                $arrayReturn['length'] += $deletedShape['length'];
            }

            // shapePrimaryOptions
            $shpPrimaryOptions = $this->readRecordOfficeArtFOPT($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $shpPrimaryOptions['length'];

            // shapeSecondaryOptions1
            $shpSecondaryOptions1 = $this->readRecordOfficeArtSecondaryFOPT($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $shpSecondaryOptions1['length'];

            // shapeTertiaryOptions1
            $shpTertiaryOptions1 = $this->readRecordOfficeArtTertiaryFOPT($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $shpTertiaryOptions1['length'];

            // childAnchor
            $childAnchor = $this->readRecordOfficeArtChildAnchor($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $childAnchor['length'];

            // clientAnchor
            $clientAnchor = $this->readRecordOfficeArtClientAnchor($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $clientAnchor['length'];

            // clientData
            $clientData = $this->readRecordOfficeArtClientData($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $clientData['length'];

            // clientTextbox
            $clientTextbox = $this->readRecordOfficeArtClientTextbox($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $clientTextbox['length'];

            // shapeSecondaryOptions2
            if (0 == $shpSecondaryOptions1['length']) {
                $shpSecondaryOptions2 = $this->readRecordOfficeArtSecondaryFOPT($stream, $pos + $arrayReturn['length']);
                $arrayReturn['length'] += $shpSecondaryOptions2['length'];
            }

            // shapeTertiaryOptions2
            if (0 == $shpTertiaryOptions1['length']) {
                $shpTertiaryOptions2 = $this->readRecordOfficeArtTertiaryFOPT($stream, $pos + $arrayReturn['length']);
                $arrayReturn['length'] += $shpTertiaryOptions2['length'];
            }

            // Core : Shape
            // Informations about group are not defined
            $arrayDimensions = [];
            $bIsGroup = false;
            if (is_object($this->oCurrentGroup)) {
                if (!$this->bFirstShapeGroup) {
                    if ($clientAnchor['length'] > 0) {
                        // $this->oCurrentGroup->setOffsetX($clientAnchor['left']);
                        // $this->oCurrentGroup->setOffsetY($clientAnchor['top']);
                        // $this->oCurrentGroup->setHeight($clientAnchor['height']);
                        // $this->oCurrentGroup->setWidth($clientAnchor['width']);
                    }
                    $bIsGroup = true;
                    $this->bFirstShapeGroup = true;
                } else {
                    if ($childAnchor['length'] > 0) {
                        $arrayDimensions = $childAnchor;
                    }
                }
            } else {
                if ($clientAnchor['length'] > 0) {
                    $arrayDimensions = $clientAnchor;
                }
            }
            if (!$bIsGroup) {
                // *** Shape ***
                if (isset($shpPrimaryOptions['pib'])) {
                    // isDrawing
                    $drawingPib = $shpPrimaryOptions['pib'];
                    if (isset($this->arrayPictures[$drawingPib - 1])) {
                        $gdImage = imagecreatefromstring($this->arrayPictures[$drawingPib - 1]);
                        $arrayReturn['shape'] = new Drawing\Gd();
                        $arrayReturn['shape']->setImageResource($gdImage);
                    }
                } elseif (isset($shpPrimaryOptions['line']) && $shpPrimaryOptions['line']) {
                    // isLine
                    $arrayReturn['shape'] = new Line(0, 0, 0, 0);
                } elseif ($clientTextbox['length'] > 0) {
                    $arrayReturn['shape'] = new RichText();
                    if (isset($clientTextbox['alignH'])) {
                        $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setHorizontal($clientTextbox['alignH']);
                    }

                    $start = 0;
                    $lastLevel = -1;
                    $lastMarginLeft = 0;
                    // @phpstan-ignore-next-line
                    for ($inc = 1; $inc <= $clientTextbox['numParts']; ++$inc) {
                        if ($clientTextbox['numParts'] == $clientTextbox['numTexts'] && isset($clientTextbox['text' . $inc])) {
                            if (isset($clientTextbox['text' . $inc]['bulletChar'])) {
                                $arrayReturn['shape']->getActiveParagraph()->getBulletStyle()->setBulletType(Bullet::TYPE_BULLET);
                                $arrayReturn['shape']->getActiveParagraph()->getBulletStyle()->setBulletChar($clientTextbox['text' . $inc]['bulletChar']);
                            }
                            // Indent
                            $indent = 0;
                            if (isset($clientTextbox['text' . $inc]['indent'])) {
                                $indent = $clientTextbox['text' . $inc]['indent'];
                            }
                            if (isset($clientTextbox['text' . $inc]['leftMargin'])) {
                                if ($lastMarginLeft > $clientTextbox['text' . $inc]['leftMargin']) {
                                    --$lastLevel;
                                }
                                if ($lastMarginLeft < $clientTextbox['text' . $inc]['leftMargin']) {
                                    ++$lastLevel;
                                }
                                $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setLevel($lastLevel);
                                $lastMarginLeft = $clientTextbox['text' . $inc]['leftMargin'];

                                $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setMarginLeft($clientTextbox['text' . $inc]['leftMargin']);
                                $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setIndent($indent - $clientTextbox['text' . $inc]['leftMargin']);
                            }
                        }
                        // Texte
                        $sText = substr($clientTextbox['text'] ?? '', $start, $clientTextbox['part' . $inc]['partLength']);
                        $sHyperlinkURL = '';
                        if (empty($sText)) {
                            // Is there a hyperlink ?
                            if (!empty($clientTextbox['hyperlink'])) {
                                foreach ($clientTextbox['hyperlink'] as $itmHyperlink) {
                                    if ($itmHyperlink['start'] == $start && ($itmHyperlink['end'] - $itmHyperlink['start']) == (float) $clientTextbox['part' . $inc]['partLength']) {
                                        $sText = $this->arrayHyperlinks[$itmHyperlink['id']]['text'];
                                        $sHyperlinkURL = $this->arrayHyperlinks[$itmHyperlink['id']]['url'];

                                        break;
                                    }
                                }
                            }
                        }
                        // New paragraph
                        $bCreateParagraph = false;
                        if (false !== strpos($sText, "\r")) {
                            $bCreateParagraph = true;
                            $sText = str_replace("\r", '', $sText);
                        }
                        // TextRun
                        $txtRun = $arrayReturn['shape']->createTextRun($sText);
                        if (isset($clientTextbox['part' . $inc]['bold'])) {
                            $txtRun->getFont()->setBold($clientTextbox['part' . $inc]['bold']);
                        }
                        if (isset($clientTextbox['part' . $inc]['italic'])) {
                            $txtRun->getFont()->setItalic($clientTextbox['part' . $inc]['italic']);
                        }
                        if (isset($clientTextbox['part' . $inc]['underline'])) {
                            $txtRun->getFont()->setUnderline(
                                $clientTextbox['part' . $inc]['underline'] ? Font::UNDERLINE_SINGLE : Font::UNDERLINE_NONE
                            );
                        }
                        if (isset($clientTextbox['part' . $inc]['fontName'])) {
                            $txtRun->getFont()->setName($clientTextbox['part' . $inc]['fontName']);
                        }
                        if (isset($clientTextbox['part' . $inc]['fontSize'])) {
                            $txtRun->getFont()->setSize($clientTextbox['part' . $inc]['fontSize']);
                        }
                        if (isset($clientTextbox['part' . $inc]['color'])) {
                            $txtRun->getFont()->setColor($clientTextbox['part' . $inc]['color']);
                        }
                        // Hyperlink
                        if (!empty($sHyperlinkURL)) {
                            $txtRun->setHyperlink(new Hyperlink($sHyperlinkURL));
                        }

                        $start += $clientTextbox['part' . $inc]['partLength'];
                        if ($bCreateParagraph) {
                            $arrayReturn['shape']->createParagraph();
                        }
                    }
                }

                // *** Properties ***
                // Dimensions
                if ($arrayReturn['shape'] instanceof AbstractShape) {
                    if (!empty($arrayDimensions)) {
                        $arrayReturn['shape']->setOffsetX($arrayDimensions['left']);
                        $arrayReturn['shape']->setOffsetY($arrayDimensions['top']);
                        $arrayReturn['shape']->setHeight($arrayDimensions['height']);
                        $arrayReturn['shape']->setWidth($arrayDimensions['width']);
                    }
                    // Rotation
                    if (isset($shpPrimaryOptions['rotation'])) {
                        $rotation = $shpPrimaryOptions['rotation'];
                        $arrayReturn['shape']->setRotation($rotation);
                    }
                    // Shadow
                    if (isset($shpPrimaryOptions['shadowOffsetX'], $shpPrimaryOptions['shadowOffsetY'])) {
                        $shadowOffsetX = $shpPrimaryOptions['shadowOffsetX'];
                        $shadowOffsetY = $shpPrimaryOptions['shadowOffsetY'];
                        if (0 != $shadowOffsetX && 0 != $shadowOffsetX) {
                            $arrayReturn['shape']->getShadow()->setVisible(true);
                            if ($shadowOffsetX > 0 && $shadowOffsetX == $shadowOffsetY) {
                                $arrayReturn['shape']->getShadow()->setDistance($shadowOffsetX)->setDirection(45);
                            }
                        }
                    }
                    // Specific Line
                    if ($arrayReturn['shape'] instanceof Line) {
                        if (isset($shpPrimaryOptions['lineColor'])) {
                            $arrayReturn['shape']->getBorder()->getColor()->setARGB('FF' . $shpPrimaryOptions['lineColor']);
                        }
                        if (isset($shpPrimaryOptions['lineWidth'])) {
                            $arrayReturn['shape']->setHeight($shpPrimaryOptions['lineWidth']);
                        }
                    }
                    // Specific RichText
                    if ($arrayReturn['shape'] instanceof RichText) {
                        if (isset($shpPrimaryOptions['insetBottom'])) {
                            $arrayReturn['shape']->setInsetBottom($shpPrimaryOptions['insetBottom']);
                        }
                        if (isset($shpPrimaryOptions['insetLeft'])) {
                            $arrayReturn['shape']->setInsetLeft($shpPrimaryOptions['insetLeft']);
                        }
                        if (isset($shpPrimaryOptions['insetRight'])) {
                            $arrayReturn['shape']->setInsetRight($shpPrimaryOptions['insetRight']);
                        }
                        if (isset($shpPrimaryOptions['insetTop'])) {
                            $arrayReturn['shape']->setInsetTop($shpPrimaryOptions['insetTop']);
                        }
                    }
                }
            } else {
                // Rotation
                if (isset($shpPrimaryOptions['rotation'])) {
                    $rotation = $shpPrimaryOptions['rotation'];
                    $this->oCurrentGroup->setRotation($rotation);
                }
            }
        }

        return $arrayReturn;
    }

    /**
     * The OfficeArtSpgrContainer record specifies a container for groups of shapes.
     *
     * @param string $stream
     * @param int $pos
     * @param bool $bInGroup
     *
     * @return array<string, int>
     *
     * @see : https://msdn.microsoft.com/en-us/library/dd910416(v=office.12).aspx
     */
    private function readRecordOfficeArtSpgrContainer($stream, $pos, $bInGroup = false)
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF003 == $data['recType']) {
            $arrayReturn['length'] += 8;

            do {
                $rhFileBlock = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']);
                if (!(0xF == $rhFileBlock['recVer'] && 0x0000 == $rhFileBlock['recInstance'] && (0xF003 == $rhFileBlock['recType'] || 0xF004 == $rhFileBlock['recType']))) {
                    throw new InvalidFileFormatException($this->filename, self::class);
                }

                switch ($rhFileBlock['recType']) {
                    case 0xF003:
                        // Core
                        $this->oCurrentGroup = $this->oPhpPresentation->getActiveSlide()->createGroup();
                        $this->bFirstShapeGroup = false;
                        // OfficeArtSpgrContainer
                        $fileBlock = $this->readRecordOfficeArtSpgrContainer($stream, $pos + $arrayReturn['length'], true);
                        $arrayReturn['length'] += $fileBlock['length'];
                        $data['recLen'] -= $fileBlock['length'];

                        break;
                    case 0xF004:
                        // Core
                        if (!$bInGroup) {
                            $this->oCurrentGroup = null;
                        }
                        // OfficeArtSpContainer
                        $fileBlock = $this->readRecordOfficeArtSpContainer($stream, $pos + $arrayReturn['length']);
                        $arrayReturn['length'] += $fileBlock['length'];
                        $data['recLen'] -= $fileBlock['length'];
                        // Core
                        //@todo
                        if (null !== $fileBlock['shape']) {
                            switch ($this->inMainType) {
                                case self::RT_NOTES:
                                    $arrayIdxSlide = array_flip($this->arrayNotes);
                                    if ($this->currentNote > 0 && isset($arrayIdxSlide[$this->currentNote])) {
                                        $oSlide = $this->oPhpPresentation->getSlide($arrayIdxSlide[$this->currentNote]);
                                        if (0 == count($oSlide->getNote()->getShapeCollection())) {
                                            $oSlide->getNote()->addShape($fileBlock['shape']);
                                        }
                                    }

                                    break;
                                case self::RT_SLIDE:
                                    if ($bInGroup) {
                                        $this->oCurrentGroup->addShape($fileBlock['shape']);
                                    } else {
                                        $this->oPhpPresentation->getActiveSlide()->addShape($fileBlock['shape']);
                                    }

                                    break;
                            }
                        }

                        break;
                }
            } while ($data['recLen'] > 0);
        }

        return $arrayReturn;
    }

    /**
     * The OfficeArtTertiaryFOPT record specifies a table of OfficeArtRGFOPTE records,.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd950206(v=office.12).aspx
     */
    private function readRecordOfficeArtTertiaryFOPT(string $stream, int $pos)
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x3 == $data['recVer'] && 0xF122 == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;

            $officeArtFOPTE = [];
            for ($inc = 0; $inc < $data['recInstance']; ++$inc) {
                $opid = self::getInt2d($this->streamPowerpointDocument, $pos + $arrayReturn['length']);
                $arrayReturn['length'] += 2;
                $optOp = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']);
                $arrayReturn['length'] += 4;
                $officeArtFOPTE[] = [
                    'opid' => ($opid >> 0) & bindec('11111111111111'),
                    'fBid' => ($opid >> 14) & bindec('1'),
                    'fComplex' => ($opid >> 15) & bindec('1'),
                    'op' => $optOp,
                ];
            }
            //@link : http://code.metager.de/source/xref/kde/calligra/filters/libmso/OPID
            foreach ($officeArtFOPTE as $opt) {
                switch ($opt['opid']) {
                    case 0x039F:
                        // Table properties
                        //@link : https://msdn.microsoft.com/en-us/library/dd922773(v=office.12).aspx
                        break;
                    case 0x03A0:
                        // Table Row Properties
                        //@link : https://msdn.microsoft.com/en-us/library/dd923419(v=office.12).aspx
                        if (0x1 == $opt['fComplex']) {
                            $arrayReturn['length'] += $opt['op'];
                        }

                        break;
                    case 0x03A9:
                        // GroupShape : metroBlob
                        //@link : https://msdn.microsoft.com/en-us/library/dd943388(v=office.12).aspx
                        if (0x1 == $opt['fComplex']) {
                            $arrayReturn['length'] += $opt['op'];
                        }

                        break;
                    case 0x01FF:
                        // Line Style Boolean
                        //@link : https://msdn.microsoft.com/en-us/library/dd951605(v=office.12).aspx
                        break;
                    default:
                        // var_dump('0x' . dechex($opt['opid']));
                        throw new FeatureNotImplementedException();
                }
            }
        }

        return $arrayReturn;
    }

    /**
     * The OfficeArtDgContainer record specifies the container for all the file records for the objects in a drawing.
     *
     * @return array<string, int>
     *
     * @see : https://msdn.microsoft.com/en-us/library/dd924455(v=office.12).aspx
     */
    private function readRecordOfficeArtDgContainer(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF002 == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // drawingData
            $drawingData = $this->readRecordOfficeArtFDG($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $drawingData['length'];
            // regroupItems
            //@todo
            // groupShape
            $groupShape = $this->readRecordOfficeArtSpgrContainer($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $groupShape['length'];
            // shape
            $shape = $this->readRecordOfficeArtSpContainer($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $shape['length'];
            // solvers1
            //@todo
            // deletedShapes
            //@todo
            // solvers1
            //@todo
        }

        return $arrayReturn;
    }

    /**
     * The OfficeArtFDG record specifies the number of shapes, the drawing identifier, and the shape identifier of the last shape in a drawing.
     *
     * @return array<string, int>
     *
     * @see : https://msdn.microsoft.com/en-us/library/dd946757(v=office.12).aspx
     */
    private function readRecordOfficeArtFDG(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && $data['recInstance'] <= 0xFFE && 0xF008 == $data['recType'] && 0x00000008 == $data['recLen']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Length
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * The OfficeArtFOPT record specifies a table of OfficeArtRGFOPTE records.
     *
     * @return array<string, bool|int|string>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd943404(v=office.12).aspx
     */
    private function readRecordOfficeArtFOPT(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x3 == $data['recVer'] && 0xF00B == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;

            //@link : http://msdn.microsoft.com/en-us/library/dd906086(v=office.12).aspx
            $officeArtFOPTE = [];
            for ($inc = 0; $inc < $data['recInstance']; ++$inc) {
                $opid = self::getInt2d($this->streamPowerpointDocument, $pos + $arrayReturn['length']);
                $arrayReturn['length'] += 2;
                $data['recLen'] -= 2;
                $optOp = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']);
                $arrayReturn['length'] += 4;
                $data['recLen'] -= 4;
                $officeArtFOPTE[] = [
                    'opid' => ($opid >> 0) & bindec('11111111111111'),
                    'fBid' => ($opid >> 14) & bindec('1'),
                    'fComplex' => ($opid >> 15) & bindec('1'),
                    'op' => $optOp,
                ];
            }
            //@link : http://code.metager.de/source/xref/kde/calligra/filters/libmso/OPID
            foreach ($officeArtFOPTE as $opt) {
                // echo $opt['opid'].'-0x'.dechex($opt['opid']).EOL;
                switch ($opt['opid']) {
                    case 0x0004:
                        // Transform : rotation
                        //@link : https://msdn.microsoft.com/en-us/library/dd949750(v=office.12).aspx
                        $arrayReturn['rotation'] = $opt['op'];

                        break;
                    case 0x007F:
                        // Transform : Protection Boolean Properties
                        //@link : http://msdn.microsoft.com/en-us/library/dd909131(v=office.12).aspx
                        break;
                    case 0x0080:
                        // Text : ltxid
                        //@link : http://msdn.microsoft.com/en-us/library/dd947446(v=office.12).aspx
                        break;
                    case 0x0081:
                        // Text : dxTextLeft
                        //@link : http://msdn.microsoft.com/en-us/library/dd953234(v=office.12).aspx
                        $arrayReturn['insetLeft'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);

                        break;
                    case 0x0082:
                        // Text : dyTextTop
                        //@link : http://msdn.microsoft.com/en-us/library/dd925068(v=office.12).aspx
                        $arrayReturn['insetTop'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);

                        break;
                    case 0x0083:
                        // Text : dxTextRight
                        //@link : http://msdn.microsoft.com/en-us/library/dd906782(v=office.12).aspx
                        $arrayReturn['insetRight'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);

                        break;
                    case 0x0084:
                        // Text : dyTextBottom
                        //@link : http://msdn.microsoft.com/en-us/library/dd772858(v=office.12).aspx
                        $arrayReturn['insetBottom'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);

                        break;
                    case 0x0085:
                        // Text : WrapText
                        //@link : http://msdn.microsoft.com/en-us/library/dd924770(v=office.12).aspx
                        break;
                    case 0x0087:
                        // Text : anchorText
                        //@link : http://msdn.microsoft.com/en-us/library/dd948575(v=office.12).aspx
                        break;
                    case 0x00BF:
                        // Text : Text Boolean Properties
                        //@link : http://msdn.microsoft.com/en-us/library/dd950905(v=office.12).aspx
                        break;
                    case 0x0104:
                        // Blip : pib
                        //@link : http://msdn.microsoft.com/en-us/library/dd772837(v=office.12).aspx
                        if (0 == $opt['fComplex']) {
                            $arrayReturn['pib'] = $opt['op'];
                            $data['recLen'] -= $opt['op'];
                        }
                        // pib Complex

                        break;
                    case 0x13F:
                        // Blip Boolean Properties
                        //@link : https://msdn.microsoft.com/en-us/library/dd944215(v=office.12).aspx
                        break;
                    case 0x140:
                        // Geometry : geoLeft
                        //@link : http://msdn.microsoft.com/en-us/library/dd947489(v=office.12).aspx
                        // print_r('geoLeft : '.$opt['op'].EOL);
                        break;
                    case 0x141:
                        // Geometry : geoTop
                        //@link : http://msdn.microsoft.com/en-us/library/dd949459(v=office.12).aspx
                        // print_r('geoTop : '.$opt['op'].EOL);
                        break;
                    case 0x142:
                        // Geometry : geoRight
                        //@link : http://msdn.microsoft.com/en-us/library/dd947117(v=office.12).aspx
                        // print_r('geoRight : '.$opt['op'].EOL);
                        break;
                    case 0x143:
                        // Geometry : geoBottom
                        //@link : http://msdn.microsoft.com/en-us/library/dd948602(v=office.12).aspx
                        // print_r('geoBottom : '.$opt['op'].EOL);
                        break;
                    case 0x144:
                        // Geometry : shapePath
                        //@link : http://msdn.microsoft.com/en-us/library/dd945249(v=office.12).aspx
                        $arrayReturn['line'] = true;

                        break;
                    case 0x145:
                        // Geometry : pVertices
                        //@link : http://msdn.microsoft.com/en-us/library/dd949814(v=office.12).aspx
                        if (1 == $opt['fComplex']) {
                            $arrayReturn['length'] += $opt['op'];
                            $data['recLen'] -= $opt['op'];
                        }

                        break;
                    case 0x146:
                        // Geometry : pSegmentInfo
                        //@link : http://msdn.microsoft.com/en-us/library/dd905742(v=office.12).aspx
                        if (1 == $opt['fComplex']) {
                            $arrayReturn['length'] += $opt['op'];
                            $data['recLen'] -= $opt['op'];
                        }

                        break;
                    case 0x155:
                        // Geometry : pAdjustHandles
                        //@link : http://msdn.microsoft.com/en-us/library/dd905890(v=office.12).aspx
                        if (1 == $opt['fComplex']) {
                            $arrayReturn['length'] += $opt['op'];
                            $data['recLen'] -= $opt['op'];
                        }

                        break;
                    case 0x156:
                        // Geometry : pGuides
                        //@link : http://msdn.microsoft.com/en-us/library/dd910801(v=office.12).aspx
                        if (1 == $opt['fComplex']) {
                            $arrayReturn['length'] += $opt['op'];
                            $data['recLen'] -= $opt['op'];
                        }

                        break;
                    case 0x157:
                        // Geometry : pInscribe
                        //@link : http://msdn.microsoft.com/en-us/library/dd904889(v=office.12).aspx
                        if (1 == $opt['fComplex']) {
                            $arrayReturn['length'] += $opt['op'];
                            $data['recLen'] -= $opt['op'];
                        }

                        break;
                    case 0x17F:
                        // Geometry Boolean Properties
                        //@link : http://msdn.microsoft.com/en-us/library/dd944968(v=office.12).aspx
                        break;
                    case 0x0180:
                        // Fill : fillType
                        //@link : http://msdn.microsoft.com/en-us/library/dd947909(v=office.12).aspx
                        break;
                    case 0x0181:
                        // Fill : fillColor
                        //@link : http://msdn.microsoft.com/en-us/library/dd921332(v=office.12).aspx
                        $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
                        $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
                        $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, '0', STR_PAD_LEFT);

                        // echo 'fillColor  : '.$strColor.EOL;
                        break;
                    case 0x0183:
                        // Fill : fillBackColor
                        //@link : http://msdn.microsoft.com/en-us/library/dd950634(v=office.12).aspx
                        $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
                        $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
                        $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, '0', STR_PAD_LEFT);

                        // echo 'fillBackColor  : '.$strColor.EOL;
                        break;
                    case 0x0193:
                        // Fill : fillRectRight
                        //@link : http://msdn.microsoft.com/en-us/library/dd951294(v=office.12).aspx
                        // echo 'fillRectRight  : '.\PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']).EOL;
                        break;
                    case 0x0194:
                        // Fill : fillRectBottom
                        //@link : http://msdn.microsoft.com/en-us/library/dd910194(v=office.12).aspx
                        // echo 'fillRectBottom   : '.\PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']).EOL;
                        break;
                    case 0x01BF:
                        // Fill : Fill Style Boolean Properties
                        //@link : http://msdn.microsoft.com/en-us/library/dd909380(v=office.12).aspx
                        break;
                    case 0x01C0:
                        // Line Style : lineColor
                        //@link : http://msdn.microsoft.com/en-us/library/dd920397(v=office.12).aspx
                        $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
                        $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
                        $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
                        $arrayReturn['lineColor'] = $strColor;

                        break;
                    case 0x01C1:
                        // Line Style : lineOpacity
                        //@link : http://msdn.microsoft.com/en-us/library/dd923433(v=office.12).aspx
                        // echo 'lineOpacity : '.dechex($opt['op']).EOL;
                        break;
                    case 0x01C2:
                        // Line Style : lineBackColor
                        //@link : http://msdn.microsoft.com/en-us/library/dd947669(v=office.12).aspx
                        break;
                    case 0x01CB:
                        // Line Style : lineWidth
                        //@link : http://msdn.microsoft.com/en-us/library/dd926964(v=office.12).aspx
                        $arrayReturn['lineWidth'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);

                        break;
                    case 0x01D6:
                        // Line Style : lineJoinStyle
                        //@link : http://msdn.microsoft.com/en-us/library/dd909643(v=office.12).aspx
                        break;
                    case 0x01D7:
                        // Line Style : lineEndCapStyle
                        //@link : http://msdn.microsoft.com/en-us/library/dd925071(v=office.12).aspx
                        break;
                    case 0x01FF:
                        // Line Style : Line Style Boolean Properties
                        //@link : http://msdn.microsoft.com/en-us/library/dd951605(v=office.12).aspx
                        break;
                    case 0x0201:
                        // Shadow Style : shadowColor
                        //@link : http://msdn.microsoft.com/en-us/library/dd923454(v=office.12).aspx
                        break;
                    case 0x0204:
                        // Shadow Style : shadowOpacity
                        //@link : http://msdn.microsoft.com/en-us/library/dd920720(v=office.12).aspx
                        break;
                    case 0x0205:
                        // Shadow Style : shadowOffsetX
                        //@link : http://msdn.microsoft.com/en-us/library/dd945280(v=office.12).aspx
                        $arrayReturn['shadowOffsetX'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);

                        break;
                    case 0x0206:
                        // Shadow Style : shadowOffsetY
                        //@link : http://msdn.microsoft.com/en-us/library/dd907855(v=office.12).aspx
                        $arrayReturn['shadowOffsetY'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);

                        break;
                    case 0x023F:
                        // Shadow Style : Shadow Style Boolean Properties
                        //@link : http://msdn.microsoft.com/en-us/library/dd947887(v=office.12).aspx
                        break;
                    case 0x0304:
                        // Shape : bWMode
                        //@link : http://msdn.microsoft.com/en-us/library/dd947659(v=office.12).aspx
                        break;
                    case 0x033F:
                        // Shape Boolean Properties
                        //@link : http://msdn.microsoft.com/en-us/library/dd951345(v=office.12).aspx
                        break;
                    case 0x0380:
                        // Group Shape Property Set : wzName
                        //@link : http://msdn.microsoft.com/en-us/library/dd950681(v=office.12).aspx
                        if (1 == $opt['fComplex']) {
                            $arrayReturn['length'] += $opt['op'];
                            $data['recLen'] -= $opt['op'];
                        }

                        break;
                    case 0x03BF:
                        // Group Shape Property Set : Group Shape Boolean Properties
                        //@link : http://msdn.microsoft.com/en-us/library/dd949807(v=office.12).aspx
                        break;
                    default:
                }
            }
            if ($data['recLen'] > 0) {
                $arrayReturn['length'] += $data['recLen'];
            }
        }

        return $arrayReturn;
    }

    /**
     * The OfficeArtFPSPL record specifies the former hierarchical position of the containing object that is either a shape or a group of shapes.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd947479(v=office.12).aspx
     */
    private function readRecordOfficeArtFPSPL(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF11D == $data['recType'] && 0x00000004 == $data['recLen']) {
            $arrayReturn['length'] += 8;
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * The OfficeArtFSP record specifies an instance of a shape.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd925898(v=office.12).aspx
     */
    private function readRecordOfficeArtFSP(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x2 == $data['recVer'] && 0xF00A == $data['recType'] && 0x00000008 == $data['recLen']) {
            $arrayReturn['length'] += 8;
            // spid
            $arrayReturn['length'] += 4;
            // data
            $data = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 4;
            $arrayReturn['fGroup'] = ($data >> 0) & bindec('1');
            $arrayReturn['fChild'] = ($data >> 1) & bindec('1');
            $arrayReturn['fPatriarch'] = ($data >> 2) & bindec('1');
            $arrayReturn['fDeleted'] = ($data >> 3) & bindec('1');
        }

        return $arrayReturn;
    }

    /**
     * The OfficeArtFSPGR record specifies the coordinate system of the group shape that the anchors of the child shape are expressed in.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd925381(v=office.12).aspx
     */
    private function readRecordOfficeArtFSPGR(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x1 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF009 == $data['recType'] && 0x00000010 == $data['recLen']) {
            $arrayReturn['length'] += 8;
            //$arrShapeGroup['xLeft'] = self::getInt4d($this->streamPowerpointDocument, $pos);
            $arrayReturn['length'] += 4;
            //$arrShapeGroup['yTop'] = self::getInt4d($this->streamPowerpointDocument, $pos);
            $arrayReturn['length'] += 4;
            //$arrShapeGroup['xRight'] = self::getInt4d($this->streamPowerpointDocument, $pos);
            $arrayReturn['length'] += 4;
            //$arrShapeGroup['yBottom'] = self::getInt4d($this->streamPowerpointDocument, $pos);
            $arrayReturn['length'] += 4;
        }

        return $arrayReturn;
    }

    /**
     * The OfficeArtSecondaryFOPT record specifies a table of OfficeArtRGFOPTE records.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd950259(v=office.12).aspx
     */
    private function readRecordOfficeArtSecondaryFOPT(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x3 == $data['recVer'] && 0xF121 == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Length
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * A container record that specifies information about a shape.
     *
     * @return array<string, int>
     *
     * @see : https://msdn.microsoft.com/en-us/library/dd950927(v=office.12).aspx
     */
    private function readRecordOfficeArtClientData(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF011 == $data['recType']) {
            $arrayReturn['length'] += 8;
            // shapeFlagsAtom (9 bytes)
            $dataShapeFlagsAtom = $this->readRecordShapeFlagsAtom($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $dataShapeFlagsAtom['length'];

            // shapeFlags10Atom (9 bytes)
            $dataShapeFlags10Atom = $this->readRecordShapeFlags10Atom($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $dataShapeFlags10Atom['length'];

            // exObjRefAtom (12 bytes)
            $dataExObjRefAtom = $this->readRecordExObjRefAtom($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $dataExObjRefAtom['length'];

            // animationInfo (variable)
            $dataAnimationInfo = $this->readRecordAnimationInfoContainer($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $dataAnimationInfo['length'];

            // mouseClickInteractiveInfo (variable)
            $mouseClickInfo = $this->readRecordMouseClickInteractiveInfoContainer($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $mouseClickInfo['length'];

            // mouseOverInteractiveInfo (variable)
            $mouseOverInfo = $this->readRecordMouseOverInteractiveInfoContainer($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $mouseOverInfo['length'];

            // placeholderAtom (16 bytes)
            $dataPlaceholderAtom = $this->readRecordPlaceholderAtom($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $dataPlaceholderAtom['length'];

            // recolorInfoAtom (variable)
            $dataRecolorInfo = $this->readRecordRecolorInfoAtom($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += $dataRecolorInfo['length'];

            // rgShapeClientRoundtripData (variable)
            $array = [
                self::RT_PROGTAGS,
                self::RT_ROUNDTRIPNEWPLACEHOLDERID12ATOM,
                self::RT_ROUNDTRIPSHAPEID12ATOM,
                self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM,
                self::RT_ROUNDTRIPSHAPECHECKSUMFORCL12ATOM,
            ];
            do {
                $dataHeaderRG = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']);
                if (in_array($dataHeaderRG['recType'], $array)) {
                    switch ($dataHeaderRG['recType']) {
                        case self::RT_PROGTAGS:
                            $dataRG = $this->readRecordShapeProgTagsContainer($stream, $pos + $arrayReturn['length']);
                            $arrayReturn['length'] += $dataRG['length'];

                            break;
                        case self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM:
                            $dataRG = $this->readRecordRoundTripHFPlaceholder12Atom($stream, $pos + $arrayReturn['length']);
                            $arrayReturn['length'] += $dataRG['length'];

                            break;
                        case self::RT_ROUNDTRIPSHAPEID12ATOM:
                            $dataRG = $this->readRecordRoundTripShapeId12Atom($stream, $pos + $arrayReturn['length']);
                            $arrayReturn['length'] += $dataRG['length'];

                            break;
                        default:
                            // var_dump('0x' . dechex($dataHeaderRG['recType']));
                            throw new FeatureNotImplementedException();
                    }
                }
            } while (in_array($dataHeaderRG['recType'], $array));
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies a persist object directory. Each persist object identifier specified MUST be unique in that persist object directory.
     *
     * @see http://msdn.microsoft.com/en-us/library/dd952680(v=office.12).aspx
     */
    private function readRecordPersistDirectoryAtom(string $stream, int $pos): void
    {
        $rHeader = $this->loadRecordHeader($stream, $pos);
        $pos += 8;
        if (0x0 != $rHeader['recVer'] || 0x000 != $rHeader['recInstance'] || self::RT_PERSISTDIRECTORYATOM != $rHeader['recType']) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : PersistDirectoryAtom > RecordHeader');
        }
        // rgPersistDirEntry
        // @link : http://msdn.microsoft.com/en-us/library/dd947347(v=office.12).aspx
        do {
            $data = self::getInt4d($stream, $pos);
            $pos += 4;
            $rHeader['recLen'] -= 4;
            //$persistId  = ($data >> 0) & bindec('11111111111111111111');
            $cPersist = ($data >> 20) & bindec('111111111111');

            $rgPersistOffset = [];
            for ($inc = 0; $inc < $cPersist; ++$inc) {
                $rgPersistOffset[] = self::getInt4d($stream, $pos);
                $pos += 4;
                $rHeader['recLen'] -= 4;
            }
        } while ($rHeader['recLen'] > 0);
        $this->rgPersistDirEntry = $rgPersistOffset;
    }

    /**
     * A container record that specifies information about the headers (1) and footers within a slide.
     *
     * @see https://msdn.microsoft.com/en-us/library/dd904856(v=office.12).aspx
     *
     * @return array<string, int>
     */
    private function readRecordPerSlideHeadersFootersContainer(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_HEADERSFOOTERS == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Length
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies whether a shape is a placeholder shape.
     *
     * @see https://msdn.microsoft.com/en-us/library/dd923930(v=office.12).aspx
     *
     * @return array<string, int>
     */
    private function readRecordPlaceholderAtom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PLACEHOLDERATOM == $data['recType'] && 0x00000008 == $data['recLen']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Datas
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies a collection of re-color mappings for a metafile ([MS-WMF]).
     *
     * @see https://msdn.microsoft.com/en-us/library/dd904899(v=office.12).aspx
     *
     * @return array<string, int>
     */
    private function readRecordRecolorInfoAtom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_RECOLORINFOATOM == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Datas
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies that a shape is a header or footerplaceholder shape.
     *
     * @see https://msdn.microsoft.com/en-us/library/dd910800(v=office.12).aspx
     *
     * @return array<string, int>
     */
    private function readRecordRoundTripHFPlaceholder12Atom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM == $data['recType'] && 0x00000001 == $data['recLen']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Datas
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies a shape identifier.
     *
     * @see https://msdn.microsoft.com/en-us/library/dd772926(v=office.12).aspx
     *
     * @return array<string, int>
     */
    private function readRecordRoundTripShapeId12Atom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ROUNDTRIPSHAPEID12ATOM == $data['recType'] && 0x00000004 == $data['recLen']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Length
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * A container record that specifies information about a slide that synchronizes to a slide in a slide library.
     *
     * @see https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx
     *
     * @return array<string, int>
     */
    private function readRecordRoundTripSlideSyncInfo12Container(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ROUNDTRIPSLIDESYNCINFO12 == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Length
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies shape-level Boolean flags.
     *
     * @see https://msdn.microsoft.com/en-us/library/dd908949(v=office.12).aspx
     *
     * @return array<string, int>
     */
    private function readRecordShapeFlags10Atom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SHAPEFLAGS10ATOM == $data['recType'] && 0x00000001 == $data['recLen']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Datas
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies shape-level Boolean flags.
     *
     * @see https://msdn.microsoft.com/en-us/library/dd925824(v=office.12).aspx
     *
     * @return array<string, int>
     */
    private function readRecordShapeFlagsAtom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SHAPEATOM == $data['recType'] && 0x00000001 == $data['recLen']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Datas
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * A container record that specifies programmable tags with additional binary shape data.
     *
     * @see https://msdn.microsoft.com/en-us/library/dd911033(v=office.12).aspx
     *
     * @return array<string, int>
     */
    private function readRecordShapeProgBinaryTagContainer(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PROGBINARYTAG == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Datas
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * A container record that specifies programmable tags with additional shape data.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd911266(v=office.12).aspx
     */
    private function readRecordShapeProgTagsContainer(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PROGTAGS == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;

            $length = 0;
            do {
                $dataHeaderRG = $this->loadRecordHeader($stream, $pos + $arrayReturn['length'] + $length);
                switch ($dataHeaderRG['recType']) {
                    case self::RT_PROGBINARYTAG:
                        $dataRG = $this->readRecordShapeProgBinaryTagContainer($stream, $pos + $arrayReturn['length'] + $length);
                        $length += $dataRG['length'];

                        break;
                        //case self::RT_PROGSTRINGTAG:
                    default:
                        // var_dump('0x' . dechex($dataHeaderRG['recType']));
                        throw new FeatureNotImplementedException();
                }
            } while ($length < $data['recLen']);
            // Datas
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies information about a slide.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx
     */
    private function readRecordSlideAtom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x2 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SLIDEATOM == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // slideAtom > geom
            $arrayReturn['length'] += 4;
            // slideAtom > rgPlaceholderTypes
            $rgPlaceholderTypes = [];
            for ($inc = 0; $inc < 8; ++$inc) {
                $rgPlaceholderTypes[] = self::getInt1d($this->streamPowerpointDocument, $pos);
                ++$arrayReturn['length'];
            }

            // slideAtom > masterIdRef
            $arrayReturn['length'] += 4;
            // slideAtom > notesIdRef
            $arrayReturn['length'] += 4;
            // slideAtom > slideFlags
            $arrayReturn['length'] += 2;
            // slideAtom > unused;
            $arrayReturn['length'] += 2;
        }

        return $arrayReturn;
    }

    /**
     * A container record that specifies a presentation slide or title master slide.
     *
     * @see http://msdn.microsoft.com/en-us/library/dd946323(v=office.12).aspx
     */
    private function readRecordSlideContainer(string $stream, int $pos): void
    {
        // Core
        $this->oPhpPresentation->createSlide();
        $this->oPhpPresentation->setActiveSlideIndex($this->oPhpPresentation->getSlideCount() - 1);

        // *** slideAtom (32 bytes)
        $slideAtom = $this->readRecordSlideAtom($stream, $pos);
        if (0 == $slideAtom['length']) {
            throw new InvalidFileFormatException($this->filename, self::class);
        }
        $pos += $slideAtom['length'];

        // *** slideShowSlideInfoAtom (24 bytes)
        $slideShowInfoAtom = $this->readRecordSlideShowSlideInfoAtom($stream, $pos);
        $pos += $slideShowInfoAtom['length'];

        // *** perSlideHFContainer (variable) : optional
        $perSlideHFContainer = $this->readRecordPerSlideHeadersFootersContainer($stream, $pos);
        $pos += $perSlideHFContainer['length'];

        // *** rtSlideSyncInfo12 (variable) : optional
        $rtSlideSyncInfo12 = $this->readRecordRoundTripSlideSyncInfo12Container($stream, $pos);
        $pos += $rtSlideSyncInfo12['length'];

        // *** drawing (variable)
        $drawing = $this->readRecordDrawingContainer($stream, $pos);
        $pos += $drawing['length'];

        // *** slideSchemeColorSchemeAtom (40 bytes)
        $slideSchemeColorAtom = $this->readRecordSlideSchemeColorSchemeAtom($stream, $pos);
        if (0 == $slideSchemeColorAtom['length']) {
            // Record SlideSchemeColorSchemeAtom
            throw new InvalidFileFormatException($this->filename, self::class);
        }
        $pos += $slideSchemeColorAtom['length'];

        // *** slideNameAtom (variable)
        $slideNameAtom = $this->readRecordSlideNameAtom($stream, $pos);
        $pos += $slideNameAtom['length'];

        // *** slideProgTagsContainer (variable).
        $slideProgTags = $this->readRecordSlideProgTagsContainer($stream, $pos);
        $pos += $slideProgTags['length'];

        // *** rgRoundTripSlide (variable)
    }

    /**
     * An atom record that specifies the name of a slide.
     *
     * @see https://msdn.microsoft.com/en-us/library/dd906297(v=office.12).aspx
     *
     * @return array{'length': int, 'slideName': string}
     */
    private function readRecordSlideNameAtom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
            'slideName' => '',
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x003 == $data['recInstance'] && self::RT_CSTRING == $data['recType'] && $data['recLen'] % 2 == 0) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Length
            $strLen = ($data['recLen'] / 2);
            for ($inc = 0; $inc < $strLen; ++$inc) {
                $char = self::getInt2d($stream, $pos + $arrayReturn['length']);
                $arrayReturn['length'] += 2;
                $arrayReturn['slideName'] .= Text::chr($char);
            }
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies a slide number metacharacter.
     *
     * @see https://msdn.microsoft.com/en-us/library/dd945703(v=office.12).aspx
     *
     * @return array<string, int>
     */
    private function readRecordSlideNumberMCAtom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SLIDENUMBERMETACHARATOM == $data['recType'] && 0x00000004 == $data['recLen']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Datas
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * A container record that specifies programmable tags with additional slide data.
     *
     * @see https://msdn.microsoft.com/en-us/library/dd951946(v=office.12).aspx
     *
     * @return array<string, int>
     */
    private function readRecordSlideProgTagsContainer(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PROGTAGS == $data['recType']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Length
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * A container record that specifies the color scheme used by a slide.
     *
     * @see https://msdn.microsoft.com/en-us/library/dd949420(v=office.12).aspx
     *
     * @return array<string, int>
     */
    private function readRecordSlideSchemeColorSchemeAtom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x001 == $data['recInstance'] && self::RT_COLORSCHEMEATOM == $data['recType'] && 0x00000020 == $data['recLen']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Length
            $rgSchemeColor = [];
            for ($inc = 0; $inc <= 7; ++$inc) {
                $rgSchemeColor[] = [
                    'red' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4),
                    'green' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4 + 1),
                    'blue' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4 + 2),
                ];
            }
            $arrayReturn['length'] += (8 * 4);
        }

        return $arrayReturn;
    }

    /**
     * An atom record that specifies what transition effect to perform during a slide show, and how to advance to the next presentation slide.
     *
     * @see https://msdn.microsoft.com/en-us/library/dd943408(v=office.12).aspx
     *
     * @return array<string, int>
     */
    private function readRecordSlideShowSlideInfoAtom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SLIDESHOWSLIDEINFOATOM == $data['recType'] && 0x00000010 == $data['recLen']) {
            // Record Header
            $arrayReturn['length'] += 8;
            // Length;
            $arrayReturn['length'] += $data['recLen'];
        }

        return $arrayReturn;
    }

    /**
     * UserEditAtom.
     *
     * @see http://msdn.microsoft.com/en-us/library/dd945746(v=office.12).aspx
     */
    private function readRecordUserEditAtom(string $stream, int $pos): void
    {
        $rHeader = $this->loadRecordHeader($stream, $pos);
        $pos += 8;
        if (0x0 != $rHeader['recVer'] || 0x000 != $rHeader['recInstance'] || self::RT_USEREDITATOM != $rHeader['recType'] || (0x0000001C != $rHeader['recLen'] && 0x00000020 != $rHeader['recLen'])) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : UserEditAtom > RecordHeader');
        }

        // lastSlideIdRef
        $pos += 4;
        // version
        $pos += 2;

        // minorVersion
        $minorVersion = self::getInt1d($stream, $pos);
        ++$pos;
        if (0x00 != $minorVersion) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : UserEditAtom > minorVersion');
        }

        // majorVersion
        $majorVersion = self::getInt1d($stream, $pos);
        ++$pos;
        if (0x03 != $majorVersion) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : UserEditAtom > majorVersion');
        }

        // offsetLastEdit
        $pos += 4;
        // offsetPersistDirectory
        $this->offsetPersistDirectory = self::getInt4d($stream, $pos);
        $pos += 4;

        // docPersistIdRef
        $docPersistIdRef = self::getInt4d($stream, $pos);
        $pos += 4;
        if (0x00000001 != $docPersistIdRef) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : UserEditAtom > docPersistIdRef');
        }

        // persistIdSeed
        $pos += 4;
        // lastView
        $pos += 2;
        // unused
        $pos += 2;
    }

    /**
     * A structure that specifies the character-level formatting of a run of text.
     *
     * @return array{'length': int, 'strLenRT': int, 'partLength': int, 'bold': bool, 'italic': bool, 'underline': bool, 'fontName': string, 'fontSize': int, 'color': Color}
     *
     * @see https://msdn.microsoft.com/en-us/library/dd945870(v=office.12).aspx
     */
    private function readStructureTextCFRun(string $stream, int $pos, int $strLenRT): array
    {
        $arrayReturn = [
            'length' => 0,
            'strLenRT' => $strLenRT,
        ];

        // rgTextCFRun
        $countRgTextCFRun = self::getInt4d($stream, $pos + $arrayReturn['length']);
        $arrayReturn['strLenRT'] -= $countRgTextCFRun;
        $arrayReturn['length'] += 4;
        $arrayReturn['partLength'] = $countRgTextCFRun;

        $masks = self::getInt4d($stream, $pos + $arrayReturn['length']);
        $arrayReturn['length'] += 4;

        $masksData = [];
        $masksData['bold'] = ($masks >> 0) & bindec('1');
        $masksData['italic'] = ($masks >> 1) & bindec('1');
        $masksData['underline'] = ($masks >> 2) & bindec('1');
        $masksData['unused1'] = ($masks >> 3) & bindec('1');
        $masksData['shadow'] = ($masks >> 4) & bindec('1');
        $masksData['fehint'] = ($masks >> 5) & bindec('1');
        $masksData['unused2'] = ($masks >> 6) & bindec('1');
        $masksData['kumi'] = ($masks >> 7) & bindec('1');
        $masksData['unused3'] = ($masks >> 8) & bindec('1');
        $masksData['emboss'] = ($masks >> 9) & bindec('1');
        $masksData['fHasStyle'] = ($masks >> 10) & bindec('1111');
        $masksData['unused4'] = ($masks >> 14) & bindec('11');
        $masksData['typeface'] = ($masks >> 16) & bindec('1');
        $masksData['size'] = ($masks >> 17) & bindec('1');
        $masksData['color'] = ($masks >> 18) & bindec('1');
        $masksData['position'] = ($masks >> 19) & bindec('1');
        $masksData['pp10ext'] = ($masks >> 20) & bindec('1');
        $masksData['oldEATypeface'] = ($masks >> 21) & bindec('1');
        $masksData['ansiTypeface'] = ($masks >> 22) & bindec('1');
        $masksData['symbolTypeface'] = ($masks >> 23) & bindec('1');
        $masksData['newEATypeface'] = ($masks >> 24) & bindec('1');
        $masksData['csTypeface'] = ($masks >> 25) & bindec('1');
        $masksData['pp11ext'] = ($masks >> 26) & bindec('1');
        if (1 == $masksData['bold'] || 1 == $masksData['italic'] || 1 == $masksData['underline'] || 1 == $masksData['shadow'] || 1 == $masksData['fehint'] || 1 == $masksData['kumi'] || 1 == $masksData['emboss'] || 1 == $masksData['fHasStyle']) {
            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;

            $fontStyleFlags = [];
            $fontStyleFlags['bold'] = ($data >> 0) & bindec('1');
            $fontStyleFlags['italic'] = ($data >> 1) & bindec('1');
            $fontStyleFlags['underline'] = ($data >> 2) & bindec('1');
            $fontStyleFlags['unused1'] = ($data >> 3) & bindec('1');
            $fontStyleFlags['shadow'] = ($data >> 4) & bindec('1');
            $fontStyleFlags['fehint'] = ($data >> 5) & bindec('1');
            $fontStyleFlags['unused2'] = ($data >> 6) & bindec('1');
            $fontStyleFlags['kumi'] = ($data >> 7) & bindec('1');
            $fontStyleFlags['unused3'] = ($data >> 8) & bindec('1');
            $fontStyleFlags['emboss'] = ($data >> 9) & bindec('1');
            $fontStyleFlags['pp9rt'] = ($data >> 10) & bindec('1111');
            $fontStyleFlags['unused4'] = ($data >> 14) & bindec('11');

            $arrayReturn['bold'] = (1 == $fontStyleFlags['bold']) ? true : false;
            $arrayReturn['italic'] = (1 == $fontStyleFlags['italic']) ? true : false;
            $arrayReturn['underline'] = (1 == $fontStyleFlags['underline']) ? true : false;
        }
        if (1 == $masksData['typeface']) {
            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
            $arrayReturn['fontName'] = $this->arrayFonts[$data] ?? '';
        }
        if (1 == $masksData['oldEATypeface']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['ansiTypeface']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['symbolTypeface']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['size']) {
            $arrayReturn['fontSize'] = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['color']) {
            $red = self::getInt1d($stream, $pos + $arrayReturn['length']);
            ++$arrayReturn['length'];
            $green = self::getInt1d($stream, $pos + $arrayReturn['length']);
            ++$arrayReturn['length'];
            $blue = self::getInt1d($stream, $pos + $arrayReturn['length']);
            ++$arrayReturn['length'];
            $index = self::getInt1d($stream, $pos + $arrayReturn['length']);
            ++$arrayReturn['length'];

            if (0xFE == $index) {
                $strColor = str_pad(dechex($red), 2, '0', STR_PAD_LEFT);
                $strColor .= str_pad(dechex($green), 2, '0', STR_PAD_LEFT);
                $strColor .= str_pad(dechex($blue), 2, '0', STR_PAD_LEFT);

                $arrayReturn['color'] = new Color('FF' . $strColor);
            }
        }
        if (1 == $masksData['position']) {
            throw new FeatureNotImplementedException();
        }

        return $arrayReturn;
    }

    /**
     * A structure that specifies the paragraph-level formatting of a run of text.
     *
     * @return array{'length': int, 'strLenRT': int, 'alignH': null|string, 'bulletChar': string, 'leftMargin': int, 'indent': int}
     *
     * @see https://msdn.microsoft.com/en-us/library/dd923535(v=office.12).aspx
     */
    private function readStructureTextPFRun(string $stream, int $pos, int $strLenRT): array
    {
        $arrayReturn = [
            'length' => 0,
            'strLenRT' => $strLenRT,
        ];

        // rgTextPFRun
        $countRgTextPFRun = self::getInt4d($stream, $pos + $arrayReturn['length']);
        $arrayReturn['strLenRT'] -= $countRgTextPFRun;
        $arrayReturn['length'] += 4;

        // indent
        $arrayReturn['length'] += 2;

        $masks = self::getInt4d($stream, $pos + $arrayReturn['length']);
        $arrayReturn['length'] += 4;

        $masksData = [];
        $masksData['hasBullet'] = ($masks >> 0) & bindec('1');
        $masksData['bulletHasFont'] = ($masks >> 1) & bindec('1');
        $masksData['bulletHasColor'] = ($masks >> 2) & bindec('1');
        $masksData['bulletHasSize'] = ($masks >> 3) & bindec('1');
        $masksData['bulletFont'] = ($masks >> 4) & bindec('1');
        $masksData['bulletColor'] = ($masks >> 5) & bindec('1');
        $masksData['bulletSize'] = ($masks >> 6) & bindec('1');
        $masksData['bulletChar'] = ($masks >> 7) & bindec('1');
        $masksData['leftMargin'] = ($masks >> 8) & bindec('1');
        $masksData['unused'] = ($masks >> 9) & bindec('1');
        $masksData['indent'] = ($masks >> 10) & bindec('1');
        $masksData['align'] = ($masks >> 11) & bindec('1');
        $masksData['lineSpacing'] = ($masks >> 12) & bindec('1');
        $masksData['spaceBefore'] = ($masks >> 13) & bindec('1');
        $masksData['spaceAfter'] = ($masks >> 14) & bindec('1');
        $masksData['defaultTabSize'] = ($masks >> 15) & bindec('1');
        $masksData['fontAlign'] = ($masks >> 16) & bindec('1');
        $masksData['charWrap'] = ($masks >> 17) & bindec('1');
        $masksData['wordWrap'] = ($masks >> 18) & bindec('1');
        $masksData['overflow'] = ($masks >> 19) & bindec('1');
        $masksData['tabStops'] = ($masks >> 20) & bindec('1');
        $masksData['textDirection'] = ($masks >> 21) & bindec('1');
        $masksData['reserved1'] = ($masks >> 22) & bindec('1');
        $masksData['bulletBlip'] = ($masks >> 23) & bindec('1');
        $masksData['bulletScheme'] = ($masks >> 24) & bindec('1');
        $masksData['bulletHasScheme'] = ($masks >> 25) & bindec('1');

        $bulletFlags = [];
        if (1 == $masksData['hasBullet'] || 1 == $masksData['bulletHasFont'] || 1 == $masksData['bulletHasColor'] || 1 == $masksData['bulletHasSize']) {
            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;

            $bulletFlags['fHasBullet'] = ($data >> 0) & bindec('1');
            $bulletFlags['fBulletHasFont'] = ($data >> 1) & bindec('1');
            $bulletFlags['fBulletHasColor'] = ($data >> 2) & bindec('1');
            $bulletFlags['fBulletHasSize'] = ($data >> 3) & bindec('1');
        }
        if (1 == $masksData['bulletChar']) {
            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
            $arrayReturn['bulletChar'] = chr($data);
        }
        if (1 == $masksData['bulletFont']) {
            // $data = self::getInt2d($stream, $pos);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['bulletSize']) {
            // $data = self::getInt2d($stream, $pos);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['bulletColor']) {
            // $red = self::getInt1d($stream, $pos + $arrayReturn['length']);
            ++$arrayReturn['length'];
            // $green = self::getInt1d($stream, $pos + $arrayReturn['length']);
            ++$arrayReturn['length'];
            // $blue = self::getInt1d($stream, $pos + $arrayReturn['length']);
            ++$arrayReturn['length'];
            $index = self::getInt1d($stream, $pos + $arrayReturn['length']);
            ++$arrayReturn['length'];

            if (0xFE == $index) {
                // $strColor = str_pad(dechex($red), 2, '0', STR_PAD_LEFT);
                // $strColor .= str_pad(dechex($green), 2, '0', STR_PAD_LEFT);
                // $strColor .= str_pad(dechex($blue), 2, '0', STR_PAD_LEFT);
            }
        }
        if (1 == $masksData['align']) {
            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
            switch ($data) {
                case 0x0000:
                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_LEFT;

                    break;
                case 0x0001:
                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_CENTER;

                    break;
                case 0x0002:
                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_RIGHT;

                    break;
                case 0x0003:
                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_JUSTIFY;

                    break;
                case 0x0004:
                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_DISTRIBUTED;

                    break;
                case 0x0005:
                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_DISTRIBUTED;

                    break;
                case 0x0006:
                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_JUSTIFY;

                    break;
                default:
                    break;
            }
        }
        if (1 == $masksData['lineSpacing']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['spaceBefore']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['spaceAfter']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['leftMargin']) {
            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
            $arrayReturn['leftMargin'] = (int) round($data / 6);
        }
        if (1 == $masksData['indent']) {
            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
            $arrayReturn['indent'] = (int) round($data / 6);
        }
        if (1 == $masksData['defaultTabSize']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['tabStops']) {
            throw new FeatureNotImplementedException();
        }
        if (1 == $masksData['fontAlign']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['charWrap'] || 1 == $masksData['wordWrap'] || 1 == $masksData['overflow']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['textDirection']) {
            throw new FeatureNotImplementedException();
        }

        return $arrayReturn;
    }

    /**
     * A structure that specifies language and spelling information for a run of text.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd909603(v=office.12).aspx
     */
    private function readStructureTextSIRun(string $stream, int $pos, int $strLenRT): array
    {
        $arrayReturn = [
            'length' => 0,
            'strLenRT' => $strLenRT,
        ];

        $arrayReturn['strLenRT'] -= self::getInt4d($stream, $pos + $arrayReturn['length']);
        $arrayReturn['length'] += 4;

        $data = self::getInt4d($stream, $pos + $arrayReturn['length']);
        $arrayReturn['length'] += 4;
        $masksData = [];
        $masksData['spell'] = ($data >> 0) & bindec('1');
        $masksData['lang'] = ($data >> 1) & bindec('1');
        $masksData['altLang'] = ($data >> 2) & bindec('1');
        $masksData['unused1'] = ($data >> 3) & bindec('1');
        $masksData['unused2'] = ($data >> 4) & bindec('1');
        $masksData['fPp10ext'] = ($data >> 5) & bindec('1');
        $masksData['fBidi'] = ($data >> 6) & bindec('1');
        $masksData['unused3'] = ($data >> 7) & bindec('1');
        $masksData['reserved1'] = ($data >> 8) & bindec('1');
        $masksData['smartTag'] = ($data >> 9) & bindec('1');

        if (1 == $masksData['spell']) {
            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
            $masksSpell = [];
            $masksSpell['error'] = ($data >> 0) & bindec('1');
            $masksSpell['clean'] = ($data >> 1) & bindec('1');
            $masksSpell['grammar'] = ($data >> 2) & bindec('1');
        }
        if (1 == $masksData['lang']) {
            // $data = self::getInt2d($stream, $pos);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['altLang']) {
            // $data = self::getInt2d($stream, $pos);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['fBidi']) {
            throw new FeatureNotImplementedException();
        }
        if (1 == $masksData['fPp10ext']) {
            throw new FeatureNotImplementedException();
        }
        if (1 == $masksData['smartTag']) {
            throw new FeatureNotImplementedException();
        }

        return $arrayReturn;
    }

    /**
     * A structure that specifies tabbing, margins, and indentation for text.
     *
     * @return array<string, int>
     *
     * @see https://msdn.microsoft.com/en-us/library/dd922749(v=office.12).aspx
     */
    private function readStructureTextRuler(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = self::getInt4d($stream, $pos + $arrayReturn['length']);
        $arrayReturn['length'] += 4;

        $masksData = [];
        $masksData['fDefaultTabSize'] = ($data >> 0) & bindec('1');
        $masksData['fCLevels'] = ($data >> 1) & bindec('1');
        $masksData['fTabStops'] = ($data >> 2) & bindec('1');
        $masksData['fLeftMargin1'] = ($data >> 3) & bindec('1');
        $masksData['fLeftMargin2'] = ($data >> 4) & bindec('1');
        $masksData['fLeftMargin3'] = ($data >> 5) & bindec('1');
        $masksData['fLeftMargin4'] = ($data >> 6) & bindec('1');
        $masksData['fLeftMargin5'] = ($data >> 7) & bindec('1');
        $masksData['fIndent1'] = ($data >> 8) & bindec('1');
        $masksData['fIndent2'] = ($data >> 9) & bindec('1');
        $masksData['fIndent3'] = ($data >> 10) & bindec('1');
        $masksData['fIndent4'] = ($data >> 11) & bindec('1');
        $masksData['fIndent5'] = ($data >> 12) & bindec('1');

        if (1 == $masksData['fCLevels']) {
            throw new FeatureNotImplementedException();
        }
        if (1 == $masksData['fDefaultTabSize']) {
            throw new FeatureNotImplementedException();
        }
        if (1 == $masksData['fTabStops']) {
            $count = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
            $arrayTabStops = [];
            for ($inc = 0; $inc < $count; ++$inc) {
                $position = self::getInt2d($stream, $pos + $arrayReturn['length']);
                $arrayReturn['length'] += 2;
                $type = self::getInt2d($stream, $pos + $arrayReturn['length']);
                $arrayReturn['length'] += 2;
                $arrayTabStops[] = [
                    'position' => $position,
                    'type' => $type,
                ];
            }
        }
        if (1 == $masksData['fLeftMargin1']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['fIndent1']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['fLeftMargin2']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['fIndent2']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['fLeftMargin3']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['fIndent3']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['fLeftMargin4']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['fIndent4']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['fLeftMargin5']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }
        if (1 == $masksData['fIndent5']) {
            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
            $arrayReturn['length'] += 2;
        }

        return $arrayReturn;
    }

    private function readRecordNotesContainer(string $stream, int $pos): void
    {
        // notesAtom
        $notesAtom = $this->readRecordNotesAtom($stream, $pos);
        $pos += $notesAtom['length'];

        // drawing
        $drawing = $this->readRecordDrawingContainer($stream, $pos);
        $pos += $drawing['length'];

        // slideSchemeColorSchemeAtom
        // slideNameAtom
        // slideProgTagsContainer
        // rgNotesRoundTripAtom
    }

    /**
     * @return array<string, int>
     */
    private function readRecordNotesAtom(string $stream, int $pos): array
    {
        $arrayReturn = [
            'length' => 0,
        ];

        $data = $this->loadRecordHeader($stream, $pos);
        if (0x1 != $data['recVer'] || 0x000 != $data['recInstance'] || self::RT_NOTESATOM != $data['recType'] || 0x00000008 != $data['recLen']) {
            throw new InvalidFileFormatException($this->filename, self::class, 'Location : NotesAtom > RecordHeader)');
        }
        // Record Header
        $arrayReturn['length'] += 8;
        // NotesAtom > slideIdRef
        $notesIdRef = self::getInt4d($stream, $pos + $arrayReturn['length']);
        if (-2147483648 == $notesIdRef) {
            $notesIdRef = 0;
        }
        $this->currentNote = $notesIdRef;
        $arrayReturn['length'] += 4;

        // NotesAtom > slideFlags
        $arrayReturn['length'] += 2;
        // NotesAtom > unused
        $arrayReturn['length'] += 2;

        return $arrayReturn;
    }
}