Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.35% covered (warning)
81.35%
1239 / 1523
42.62% covered (danger)
42.62%
26 / 61
CRAP
0.00% covered (danger)
0.00%
0 / 1
PowerPoint97
81.35% covered (warning)
81.35%
1239 / 1523
42.62% covered (danger)
42.62%
26 / 61
2838.50
0.00% covered (danger)
0.00%
0 / 1
 canRead
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fileSupportsUnserializePhpPresentation
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 load
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 loadFile
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 loadOLE
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 loadPicturesStream
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
7.02
 loadCurrentUserStream
80.43% covered (warning)
80.43%
37 / 46
0.00% covered (danger)
0.00%
0 / 1
24.30
 loadPowerpointDocumentStream
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
6
 loadRecordHeader
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 getInt1d
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInt2d
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInt4d
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 readRecordAnimationInfoContainer
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
4.25
 readRecordDocumentContainer
65.03% covered (warning)
65.03%
119 / 183
0.00% covered (danger)
0.00%
0 / 1
547.03
 readRecordDrawingContainer
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 readRecordDrawingGroupContainer
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 readRecordExObjRefAtom
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.39
 readRecordInteractiveInfoAtom
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
5
 readRecordMacroNameAtom
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.39
 readRecordMouseClickInteractiveInfoContainer
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 readRecordMouseOverInteractiveInfoContainer
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
4.25
 readRecordOfficeArtBlip
85.00% covered (warning)
85.00%
17 / 20
0.00% covered (danger)
0.00%
0 / 1
8.22
 readRecordOfficeArtChildAnchor
40.00% covered (danger)
40.00%
6 / 15
0.00% covered (danger)
0.00%
0 / 1
10.40
 readRecordOfficeArtClientAnchor
90.48% covered (success)
90.48%
19 / 21
0.00% covered (danger)
0.00%
0 / 1
8.06
 readRecordOfficeArtClientTextbox
86.21% covered (warning)
86.21%
75 / 87
0.00% covered (danger)
0.00%
0 / 1
24.39
 readRecordOfficeArtSpContainer
90.48% covered (success)
90.48%
133 / 147
0.00% covered (danger)
0.00%
0 / 1
64.21
 readRecordOfficeArtSpgrContainer
71.05% covered (warning)
71.05%
27 / 38
0.00% covered (danger)
0.00%
0 / 1
25.86
 readRecordOfficeArtTertiaryFOPT
17.65% covered (danger)
17.65%
6 / 34
0.00% covered (danger)
0.00%
0 / 1
92.43
 readRecordOfficeArtDgContainer
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 readRecordOfficeArtFDG
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 readRecordOfficeArtFOPT
94.84% covered (success)
94.84%
147 / 155
0.00% covered (danger)
0.00%
0 / 1
59.48
 readRecordOfficeArtFPSPL
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 readRecordOfficeArtFSP
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 readRecordOfficeArtFSPGR
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 readRecordOfficeArtSecondaryFOPT
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
3.14
 readRecordOfficeArtClientData
75.00% covered (warning)
75.00%
33 / 44
0.00% covered (danger)
0.00%
0 / 1
10.27
 readRecordPersistDirectoryAtom
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
5.01
 readRecordPerSlideHeadersFootersContainer
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 readRecordPlaceholderAtom
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 readRecordRecolorInfoAtom
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
4.25
 readRecordRoundTripHFPlaceholder12Atom
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 readRecordRoundTripShapeId12Atom
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 readRecordRoundTripSlideSyncInfo12Container
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
4.25
 readRecordShapeFlags10Atom
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.39
 readRecordShapeFlagsAtom
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.39
 readRecordShapeProgBinaryTagContainer
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 readRecordShapeProgTagsContainer
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
42
 readRecordSlideAtom
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
5
 readRecordSlideContainer
90.91% covered (success)
90.91%
20 / 22
0.00% covered (danger)
0.00%
0 / 1
3.01
 readRecordSlideNameAtom
53.85% covered (warning)
53.85%
7 / 13
0.00% covered (danger)
0.00%
0 / 1
9.54
 readRecordSlideNumberMCAtom
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 readRecordSlideProgTagsContainer
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 readRecordSlideSchemeColorSchemeAtom
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 readRecordSlideShowSlideInfoAtom
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 readRecordUserEditAtom
83.33% covered (warning)
83.33%
20 / 24
0.00% covered (danger)
0.00%
0 / 1
9.37
 readStructureTextCFRun
96.39% covered (success)
96.39%
80 / 83
0.00% covered (danger)
0.00%
0 / 1
20
 readStructureTextPFRun
84.55% covered (warning)
84.55%
93 / 110
0.00% covered (danger)
0.00%
0 / 1
34.55
 readStructureTextSIRun
91.89% covered (success)
91.89%
34 / 37
0.00% covered (danger)
0.00%
0 / 1
7.03
 readStructureTextRuler
85.96% covered (warning)
85.96%
49 / 57
0.00% covered (danger)
0.00%
0 / 1
15.62
 readRecordNotesContainer
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 readRecordNotesAtom
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
6.09
1<?php
2/**
3 * This file is part of PHPPresentation - A pure PHP library for reading and writing
4 * presentations documents.
5 *
6 * PHPPresentation is free software distributed under the terms of the GNU Lesser
7 * General Public License version 3 as published by the Free Software Foundation.
8 *
9 * For the full copyright and license information, please read the LICENSE
10 * file that was distributed with this source code. For the full list of
11 * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors.
12 *
13 * @see        https://github.com/PHPOffice/PHPPresentation
14 *
15 * @license     http://www.gnu.org/licenses/lgpl.txt LGPL version 3
16 */
17
18declare(strict_types=1);
19
20namespace PhpOffice\PhpPresentation\Reader;
21
22use Exception;
23use PhpOffice\Common\Microsoft\OLERead;
24use PhpOffice\Common\Text;
25use PhpOffice\PhpPresentation\AbstractShape;
26use PhpOffice\PhpPresentation\Exception\FeatureNotImplementedException;
27use PhpOffice\PhpPresentation\Exception\FileNotFoundException;
28use PhpOffice\PhpPresentation\Exception\InvalidFileFormatException;
29use PhpOffice\PhpPresentation\PhpPresentation;
30use PhpOffice\PhpPresentation\Shape;
31use PhpOffice\PhpPresentation\Shape\Drawing;
32use PhpOffice\PhpPresentation\Shape\Group;
33use PhpOffice\PhpPresentation\Shape\Hyperlink;
34use PhpOffice\PhpPresentation\Shape\Line;
35use PhpOffice\PhpPresentation\Shape\RichText;
36use PhpOffice\PhpPresentation\Style\Alignment;
37use PhpOffice\PhpPresentation\Style\Bullet;
38use PhpOffice\PhpPresentation\Style\Color;
39use PhpOffice\PhpPresentation\Style\Font;
40
41/**
42 * Serialized format reader.
43 */
44class PowerPoint97 implements ReaderInterface
45{
46    public const OFFICEARTBLIPEMF = 0xF01A;
47    public const OFFICEARTBLIPWMF = 0xF01B;
48    public const OFFICEARTBLIPPICT = 0xF01C;
49    public const OFFICEARTBLIPJPG = 0xF01D;
50    public const OFFICEARTBLIPPNG = 0xF01E;
51    public const OFFICEARTBLIPDIB = 0xF01F;
52    public const OFFICEARTBLIPTIFF = 0xF029;
53    public const OFFICEARTBLIPJPEG = 0xF02A;
54
55    /**
56     * @see http://msdn.microsoft.com/en-us/library/dd945336(v=office.12).aspx
57     */
58    public const RT_ANIMATIONINFO = 0x1014;
59    public const RT_ANIMATIONINFOATOM = 0x0FF1;
60    public const RT_BINARYTAGDATABLOB = 0x138B;
61    public const RT_BLIPCOLLECTION9 = 0x07F8;
62    public const RT_BLIPENTITY9ATOM = 0x07F9;
63    public const RT_BOOKMARKCOLLECTION = 0x07E3;
64    public const RT_BOOKMARKENTITYATOM = 0x0FD0;
65    public const RT_BOOKMARKSEEDATOM = 0x07E9;
66    public const RT_BROADCASTDOCINFO9 = 0x177E;
67    public const RT_BROADCASTDOCINFO9ATOM = 0x177F;
68    public const RT_BUILDATOM = 0x2B03;
69    public const RT_BUILDLIST = 0x2B02;
70    public const RT_CHARTBUILD = 0x2B04;
71    public const RT_CHARTBUILDATOM = 0x2B05;
72    public const RT_COLORSCHEMEATOM = 0x07F0;
73    public const RT_COMMENT10 = 0x2EE0;
74    public const RT_COMMENT10ATOM = 0x2EE1;
75    public const RT_COMMENTINDEX10 = 0x2EE4;
76    public const RT_COMMENTINDEX10ATOM = 0x2EE5;
77    public const RT_CRYPTSESSION10CONTAINER = 0x2F14;
78    public const RT_CURRENTUSERATOM = 0x0FF6;
79    public const RT_CSTRING = 0x0FBA;
80    public const RT_DATETIMEMETACHARATOM = 0x0FF7;
81    public const RT_DEFAULTRULERATOM = 0x0FAB;
82    public const RT_DOCROUTINGSLIPATOM = 0x0406;
83    public const RT_DIAGRAMBUILD = 0x2B06;
84    public const RT_DIAGRAMBUILDATOM = 0x2B07;
85    public const RT_DIFF10 = 0x2EED;
86    public const RT_DIFF10ATOM = 0x2EEE;
87    public const RT_DIFFTREE10 = 0x2EEC;
88    public const RT_DOCTOOLBARSTATES10ATOM = 0x36B1;
89    public const RT_DOCUMENT = 0x03E8;
90    public const RT_DOCUMENTATOM = 0x03E9;
91    public const RT_DRAWING = 0x040C;
92    public const RT_DRAWINGGROUP = 0x040B;
93    public const RT_ENDDOCUMENTATOM = 0x03EA;
94    public const RT_EXTERNALAVIMOVIE = 0x1006;
95    public const RT_EXTERNALCDAUDIO = 0x100E;
96    public const RT_EXTERNALCDAUDIOATOM = 0x1012;
97    public const RT_EXTERNALHYPERLINK = 0x0FD7;
98    public const RT_EXTERNALHYPERLINK9 = 0x0FE4;
99    public const RT_EXTERNALHYPERLINKATOM = 0x0FD3;
100    public const RT_EXTERNALHYPERLINKFLAGSATOM = 0x1018;
101    public const RT_EXTERNALMCIMOVIE = 0x1007;
102    public const RT_EXTERNALMEDIAATOM = 0x1004;
103    public const RT_EXTERNALMIDIAUDIO = 0x100D;
104    public const RT_EXTERNALOBJECTLIST = 0x0409;
105    public const RT_EXTERNALOBJECTLISTATOM = 0x040A;
106    public const RT_EXTERNALOBJECTREFATOM = 0x0BC1;
107    public const RT_EXTERNALOLECONTROL = 0x0FEE;
108    public const RT_EXTERNALOLECONTROLATOM = 0x0FFB;
109    public const RT_EXTERNALOLEEMBED = 0x0FCC;
110    public const RT_EXTERNALOLEEMBEDATOM = 0x0FCD;
111    public const RT_EXTERNALOLELINK = 0x0FCE;
112    public const RT_EXTERNALOLELINKATOM = 0x0FD1;
113    public const RT_EXTERNALOLEOBJECTATOM = 0x0FC3;
114    public const RT_EXTERNALOLEOBJECTSTG = 0x1011;
115    public const RT_EXTERNALVIDEO = 0x1005;
116    public const RT_EXTERNALWAVAUDIOEMBEDDED = 0x100F;
117    public const RT_EXTERNALWAVAUDIOEMBEDDEDATOM = 0x1013;
118    public const RT_EXTERNALWAVAUDIOLINK = 0x1010;
119    public const RT_ENVELOPEDATA9ATOM = 0x1785;
120    public const RT_ENVELOPEFLAGS9ATOM = 0x1784;
121    public const RT_ENVIRONMENT = 0x03F2;
122    public const RT_FONTCOLLECTION = 0x07D5;
123    public const RT_FONTCOLLECTION10 = 0x07D6;
124    public const RT_FONTEMBEDDATABLOB = 0x0FB8;
125    public const RT_FONTEMBEDFLAGS10ATOM = 0x32C8;
126    public const RT_FILTERPRIVACYFLAGS10ATOM = 0x36B0;
127    public const RT_FONTENTITYATOM = 0x0FB7;
128    public const RT_FOOTERMETACHARATOM = 0x0FFA;
129    public const RT_GENERICDATEMETACHARATOM = 0x0FF8;
130    public const RT_GRIDSPACING10ATOM = 0x040D;
131    public const RT_GUIDEATOM = 0x03FB;
132    public const RT_HANDOUT = 0x0FC9;
133    public const RT_HASHCODEATOM = 0x2B00;
134    public const RT_HEADERSFOOTERS = 0x0FD9;
135    public const RT_HEADERSFOOTERSATOM = 0x0FDA;
136    public const RT_HEADERMETACHARATOM = 0x0FF9;
137    public const RT_HTMLDOCINFO9ATOM = 0x177B;
138    public const RT_HTMLPUBLISHINFOATOM = 0x177C;
139    public const RT_HTMLPUBLISHINFO9 = 0x177D;
140    public const RT_INTERACTIVEINFO = 0x0FF2;
141    public const RT_INTERACTIVEINFOATOM = 0x0FF3;
142    public const RT_KINSOKU = 0x0FC8;
143    public const RT_KINSOKUATOM = 0x0FD2;
144    public const RT_LEVELINFOATOM = 0x2B0A;
145    public const RT_LINKEDSHAPE10ATOM = 0x2EE6;
146    public const RT_LINKEDSLIDE10ATOM = 0x2EE7;
147    public const RT_LIST = 0x07D0;
148    public const RT_MAINMASTER = 0x03F8;
149    public const RT_MASTERTEXTPROPATOM = 0x0FA2;
150    public const RT_METAFILE = 0x0FC1;
151    public const RT_NAMEDSHOW = 0x0411;
152    public const RT_NAMEDSHOWS = 0x0410;
153    public const RT_NAMEDSHOWSLIDESATOM = 0x0412;
154    public const RT_NORMALVIEWSETINFO9 = 0x0414;
155    public const RT_NORMALVIEWSETINFO9ATOM = 0x0415;
156    public const RT_NOTES = 0x03F0;
157    public const RT_NOTESATOM = 0x03F1;
158    public const RT_NOTESTEXTVIEWINFO9 = 0x0413;
159    public const RT_OUTLINETEXTPROPS9 = 0x0FAE;
160    public const RT_OUTLINETEXTPROPS10 = 0x0FB3;
161    public const RT_OUTLINETEXTPROPS11 = 0x0FB5;
162    public const RT_OUTLINETEXTPROPSHEADER9ATOM = 0x0FAF;
163    public const RT_OUTLINETEXTREFATOM = 0x0F9E;
164    public const RT_OUTLINEVIEWINFO = 0x0407;
165    public const RT_PERSISTDIRECTORYATOM = 0x1772;
166    public const RT_PARABUILD = 0x2B08;
167    public const RT_PARABUILDATOM = 0x2B09;
168    public const RT_PHOTOALBUMINFO10ATOM = 0x36B2;
169    public const RT_PLACEHOLDERATOM = 0x0BC3;
170    public const RT_PRESENTATIONADVISORFLAGS9ATOM = 0x177A;
171    public const RT_PRINTOPTIONSATOM = 0x1770;
172    public const RT_PROGBINARYTAG = 0x138A;
173    public const RT_PROGSTRINGTAG = 0x1389;
174    public const RT_PROGTAGS = 0x1388;
175    public const RT_RECOLORINFOATOM = 0x0FE7;
176    public const RT_RTFDATETIMEMETACHARATOM = 0x1015;
177    public const RT_ROUNDTRIPANIMATIONATOM12ATOM = 0x2B0B;
178    public const RT_ROUNDTRIPANIMATIONHASHATOM12ATOM = 0x2B0D;
179    public const RT_ROUNDTRIPCOLORMAPPING12ATOM = 0x040F;
180    public const RT_ROUNDTRIPCOMPOSITEMASTERID12ATOM = 0x041D;
181    public const RT_ROUNDTRIPCONTENTMASTERID12ATOM = 0x0422;
182    public const RT_ROUNDTRIPCONTENTMASTERINFO12ATOM = 0x041E;
183    public const RT_ROUNDTRIPCUSTOMTABLESTYLES12ATOM = 0x0428;
184    public const RT_ROUNDTRIPDOCFLAGS12ATOM = 0x0425;
185    public const RT_ROUNDTRIPHEADERFOOTERDEFAULTS12ATOM = 0x0424;
186    public const RT_ROUNDTRIPHFPLACEHOLDER12ATOM = 0x0420;
187    public const RT_ROUNDTRIPNEWPLACEHOLDERID12ATOM = 0x0BDD;
188    public const RT_ROUNDTRIPNOTESMASTERTEXTSTYLES12ATOM = 0x0427;
189    public const RT_ROUNDTRIPOARTTEXTSTYLES12ATOM = 0x0423;
190    public const RT_ROUNDTRIPORIGINALMAINMASTERID12ATOM = 0x041C;
191    public const RT_ROUNDTRIPSHAPECHECKSUMFORCL12ATOM = 0x0426;
192    public const RT_ROUNDTRIPSHAPEID12ATOM = 0x041F;
193    public const RT_ROUNDTRIPSLIDESYNCINFO12 = 0x3714;
194    public const RT_ROUNDTRIPSLIDESYNCINFOATOM12 = 0x3715;
195    public const RT_ROUNDTRIPTHEME12ATOM = 0x040E;
196    public const RT_SHAPEATOM = 0x0BDB;
197    public const RT_SHAPEFLAGS10ATOM = 0x0BDC;
198    public const RT_SLIDE = 0x03EE;
199    public const RT_SLIDEATOM = 0x03EF;
200    public const RT_SLIDEFLAGS10ATOM = 0x2EEA;
201    public const RT_SLIDELISTENTRY10ATOM = 0x2EF0;
202    public const RT_SLIDELISTTABLE10 = 0x2EF1;
203    public const RT_SLIDELISTWITHTEXT = 0x0FF0;
204    public const RT_SLIDELISTTABLESIZE10ATOM = 0x2EEF;
205    public const RT_SLIDENUMBERMETACHARATOM = 0x0FD8;
206    public const RT_SLIDEPERSISTATOM = 0x03F3;
207    public const RT_SLIDESHOWDOCINFOATOM = 0x0401;
208    public const RT_SLIDESHOWSLIDEINFOATOM = 0x03F9;
209    public const RT_SLIDETIME10ATOM = 0x2EEB;
210    public const RT_SLIDEVIEWINFO = 0x03FA;
211    public const RT_SLIDEVIEWINFOATOM = 0x03FE;
212    public const RT_SMARTTAGSTORE11CONTAINER = 0x36B3;
213    public const RT_SOUND = 0x07E6;
214    public const RT_SOUNDCOLLECTION = 0x07E4;
215    public const RT_SOUNDCOLLECTIONATOM = 0x07E5;
216    public const RT_SOUNDDATABLOB = 0x07E7;
217    public const RT_SORTERVIEWINFO = 0x0408;
218    public const RT_STYLETEXTPROPATOM = 0x0FA1;
219    public const RT_STYLETEXTPROP10ATOM = 0x0FB1;
220    public const RT_STYLETEXTPROP11ATOM = 0x0FB6;
221    public const RT_STYLETEXTPROP9ATOM = 0x0FAC;
222    public const RT_SUMMARY = 0x0402;
223    public const RT_TEXTBOOKMARKATOM = 0x0FA7;
224    public const RT_TEXTBYTESATOM = 0x0FA8;
225    public const RT_TEXTCHARFORMATEXCEPTIONATOM = 0x0FA4;
226    public const RT_TEXTCHARSATOM = 0x0FA0;
227    public const RT_TEXTDEFAULTS10ATOM = 0x0FB4;
228    public const RT_TEXTDEFAULTS9ATOM = 0x0FB0;
229    public const RT_TEXTHEADERATOM = 0x0F9F;
230    public const RT_TEXTINTERACTIVEINFOATOM = 0x0FDF;
231    public const RT_TEXTMASTERSTYLEATOM = 0x0FA3;
232    public const RT_TEXTMASTERSTYLE10ATOM = 0x0FB2;
233    public const RT_TEXTMASTERSTYLE9ATOM = 0x0FAD;
234    public const RT_TEXTPARAGRAPHFORMATEXCEPTIONATOM = 0x0FA5;
235    public const RT_TEXTRULERATOM = 0x0FA6;
236    public const RT_TEXTSPECIALINFOATOM = 0x0FAA;
237    public const RT_TEXTSPECIALINFODEFAULTATOM = 0x0FA9;
238    public const RT_TIMEANIMATEBEHAVIOR = 0xF134;
239    public const RT_TIMEANIMATEBEHAVIORCONTAINER = 0xF12B;
240    public const RT_TIMEANIMATIONVALUE = 0xF143;
241    public const RT_TIMEANIMATIONVALUELIST = 0xF13F;
242    public const RT_TIMEBEHAVIOR = 0xF133;
243    public const RT_TIMEBEHAVIORCONTAINER = 0xF12A;
244    public const RT_TIMECOLORBEHAVIOR = 0xF135;
245    public const RT_TIMECOLORBEHAVIORCONTAINER = 0xF12C;
246    public const RT_TIMECLIENTVISUALELEMENT = 0xF13C;
247    public const RT_TIMECOMMANDBEHAVIOR = 0xF13B;
248    public const RT_TIMECOMMANDBEHAVIORCONTAINER = 0xF132;
249    public const RT_TIMECONDITION = 0xF128;
250    public const RT_TIMECONDITIONCONTAINER = 0xF125;
251    public const RT_TIMEEFFECTBEHAVIOR = 0xF136;
252    public const RT_TIMEEFFECTBEHAVIORCONTAINER = 0xF12D;
253    public const RT_TIMEEXTTIMENODECONTAINER = 0xF144;
254    public const RT_TIMEITERATEDATA = 0xF140;
255    public const RT_TIMEMODIFIER = 0xF129;
256    public const RT_TIMEMOTIONBEHAVIOR = 0xF137;
257    public const RT_TIMEMOTIONBEHAVIORCONTAINER = 0xF12E;
258    public const RT_TIMENODE = 0xF127;
259    public const RT_TIMEPROPERTYLIST = 0xF13D;
260    public const RT_TIMEROTATIONBEHAVIOR = 0xF138;
261    public const RT_TIMEROTATIONBEHAVIORCONTAINER = 0xF12F;
262    public const RT_TIMESCALEBEHAVIOR = 0xF139;
263    public const RT_TIMESCALEBEHAVIORCONTAINER = 0xF130;
264    public const RT_TIMESEQUENCEDATA = 0xF141;
265    public const RT_TIMESETBEHAVIOR = 0xF13A;
266    public const RT_TIMESETBEHAVIORCONTAINER = 0xF131;
267    public const RT_TIMESUBEFFECTCONTAINER = 0xF145;
268    public const RT_TIMEVARIANT = 0xF142;
269    public const RT_TIMEVARIANTLIST = 0xF13E;
270    public const RT_USEREDITATOM = 0x0FF5;
271    public const RT_VBAINFO = 0x03FF;
272    public const RT_VBAINFOATOM = 0x0400;
273    public const RT_VIEWINFOATOM = 0x03FD;
274    public const RT_VISUALPAGEATOM = 0x2B01;
275    public const RT_VISUALSHAPEATOM = 0x2AFB;
276
277    /**
278     * @see http://msdn.microsoft.com/en-us/library/dd926394(v=office.12).aspx
279     */
280    public const SL_BIGOBJECT = 0x0000000F;
281    public const SL_BLANK = 0x00000010;
282    public const SL_COLUMNTWOROWS = 0x0000000A;
283    public const SL_FOUROBJECTS = 0x0000000E;
284    public const SL_MASTERTITLE = 0x00000002;
285    public const SL_TITLEBODY = 0x00000001;
286    public const SL_TITLEONLY = 0x00000007;
287    public const SL_TITLESLIDE = 0x00000000;
288    public const SL_TWOCOLUMNS = 0x00000008;
289    public const SL_TWOCOLUMNSROW = 0x0000000D;
290    public const SL_TWOROWS = 0x00000009;
291    public const SL_TWOROWSCOLUMN = 0x0000000B;
292    public const SL_VERTICALTITLEBODY = 0x00000011;
293    public const SL_VERTICALTWOROWS = 0x00000012;
294
295    /**
296     * Array with Fonts.
297     *
298     * @var array<int, string>
299     */
300    private $arrayFonts = [];
301
302    /**
303     * Array with Hyperlinks.
304     *
305     * @var array<int, array<string, string>>
306     */
307    private $arrayHyperlinks = [];
308
309    /**
310     * Array with Notes.
311     *
312     * @var array<int, int>
313     */
314    private $arrayNotes = [];
315
316    /**
317     * Array with Pictures.
318     *
319     * @var array<int, string>
320     */
321    private $arrayPictures = [];
322
323    /**
324     * Offset (in bytes) from the beginning of the PowerPoint Document Stream to the UserEditAtom record for the most recent user edit.
325     *
326     * @var int
327     */
328    private $offsetToCurrentEdit;
329
330    /**
331     * A structure that specifies a compressed table of sequential persist object identifiers and stream offsets to associated persist objects.
332     *
333     * @var array<int, int>
334     */
335    private $rgPersistDirEntry;
336
337    /**
338     * Offset (in bytes) from the beginning of the PowerPoint Document Stream to the PersistDirectoryAtom record for this user edit.
339     *
340     * @var int
341     */
342    private $offsetPersistDirectory;
343
344    /**
345     * Output Object.
346     *
347     * @var PhpPresentation
348     */
349    private $oPhpPresentation;
350
351    /**
352     * @var null|Group
353     */
354    private $oCurrentGroup;
355
356    /**
357     * @var bool
358     */
359    private $bFirstShapeGroup = false;
360
361    /**
362     * Stream "Powerpoint Document".
363     *
364     * @var string
365     */
366    private $streamPowerpointDocument;
367
368    /**
369     * Stream "Current User".
370     *
371     * @var string
372     */
373    private $streamCurrentUser;
374
375    /**
376     * Stream "Pictures".
377     *
378     * @var string
379     */
380    private $streamPictures;
381
382    /**
383     * @var int
384     */
385    private $inMainType;
386
387    /**
388     * @var null|int
389     */
390    private $currentNote;
391
392    /**
393     * @var null|string
394     */
395    private $filename;
396
397    /**
398     * Can the current \PhpOffice\PhpPresentation\Reader\ReaderInterface read the file?
399     */
400    public function canRead(string $pFilename): bool
401    {
402        return $this->fileSupportsUnserializePhpPresentation($pFilename);
403    }
404
405    /**
406     * Does a file support UnserializePhpPresentation ?
407     */
408    public function fileSupportsUnserializePhpPresentation(string $pFilename = ''): bool
409    {
410        // Check if file exists
411        if (!file_exists($pFilename)) {
412            throw new FileNotFoundException($pFilename);
413        }
414
415        try {
416            // Use ParseXL for the hard work.
417            $ole = new OLERead();
418            // get excel data
419            $ole->read($pFilename);
420
421            return true;
422        } catch (Exception $e) {
423            return false;
424        }
425    }
426
427    /**
428     * Loads PhpPresentation Serialized file.
429     */
430    public function load(string $pFilename): PhpPresentation
431    {
432        // Unserialize... First make sure the file supports it!
433        if (!$this->fileSupportsUnserializePhpPresentation($pFilename)) {
434            throw new InvalidFileFormatException($pFilename, self::class);
435        }
436
437        $this->filename = $pFilename;
438
439        return $this->loadFile();
440    }
441
442    /**
443     * Load PhpPresentation Serialized file.
444     */
445    private function loadFile(): PhpPresentation
446    {
447        $this->oPhpPresentation = new PhpPresentation();
448        $this->oPhpPresentation->removeSlideByIndex();
449
450        // Read OLE Blocks
451        $this->loadOLE();
452        // Read pictures in the Pictures Stream
453        $this->loadPicturesStream();
454        // Read information in the Current User Stream
455        $this->loadCurrentUserStream();
456        // Read information in the PowerPoint Document Stream
457        $this->loadPowerpointDocumentStream();
458
459        return $this->oPhpPresentation;
460    }
461
462    /**
463     * Read OLE Part.
464     */
465    private function loadOLE(): void
466    {
467        // OLE reader
468        $oOLE = new OLERead();
469        $oOLE->read($this->filename);
470
471        // PowerPoint Document Stream
472        $this->streamPowerpointDocument = $oOLE->getStream($oOLE->powerpointDocument);
473
474        // Current User Stream
475        $this->streamCurrentUser = $oOLE->getStream($oOLE->currentUser);
476
477        // Get pictures data
478        $this->streamPictures = $oOLE->getStream($oOLE->pictures);
479    }
480
481    /**
482     * Stream Pictures.
483     *
484     * @see http://msdn.microsoft.com/en-us/library/dd920746(v=office.12).aspx
485     */
486    private function loadPicturesStream(): void
487    {
488        $stream = $this->streamPictures;
489
490        $pos = 0;
491        do {
492            $arrayRH = $this->loadRecordHeader($stream, $pos);
493            $pos += 8;
494            $readSuccess = false;
495            if (0x00 == $arrayRH['recVer'] && (0xF007 == $arrayRH['recType'] || ($arrayRH['recType'] >= 0xF018 && $arrayRH['recType'] <= 0xF117))) {
496                //@link : http://msdn.microsoft.com/en-us/library/dd950560(v=office.12).aspx
497                if (0xF007 == $arrayRH['recType']) {
498                    // OfficeArtFBSE
499                    throw new FeatureNotImplementedException();
500                }
501                // $arrayRH['recType'] >= 0xF018 && $arrayRH['recType'] <= 0xF117
502                $arrayRecord = $this->readRecordOfficeArtBlip($stream, $pos - 8);
503                if ($arrayRecord['length'] > 0) {
504                    $pos += $arrayRecord['length'];
505                    $this->arrayPictures[] = $arrayRecord['picture'];
506                }
507                $readSuccess = true;
508            }
509        } while (true === $readSuccess);
510    }
511
512    /**
513     * Stream Current User.
514     *
515     * @see http://msdn.microsoft.com/en-us/library/dd908567(v=office.12).aspx
516     */
517    private function loadCurrentUserStream(): void
518    {
519        $pos = 0;
520
521        /**
522         * CurrentUserAtom : http://msdn.microsoft.com/en-us/library/dd948895(v=office.12).aspx.
523         */
524        // RecordHeader : http://msdn.microsoft.com/en-us/library/dd926377(v=office.12).aspx
525        $rHeader = $this->loadRecordHeader($this->streamCurrentUser, $pos);
526        $pos += 8;
527        if (0x0 != $rHeader['recVer'] || 0x000 != $rHeader['recInstance'] || self::RT_CURRENTUSERATOM != $rHeader['recType']) {
528            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > RecordHeader');
529        }
530
531        // Size
532        $size = self::getInt4d($this->streamCurrentUser, $pos);
533        $pos += 4;
534        if (0x00000014 != $size) {
535            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > Size');
536        }
537
538        // headerToken
539        $headerToken = self::getInt4d($this->streamCurrentUser, $pos);
540        $pos += 4;
541        if (0xF3D1C4DF == $headerToken) {
542            // Encrypted file
543            throw new FeatureNotImplementedException();
544        }
545
546        // offsetToCurrentEdit
547        $this->offsetToCurrentEdit = self::getInt4d($this->streamCurrentUser, $pos);
548        $pos += 4;
549
550        // lenUserName
551        $lenUserName = self::getInt2d($this->streamCurrentUser, $pos);
552        $pos += 2;
553        if ($lenUserName > 255) {
554            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > lenUserName');
555        }
556
557        // docFileVersion
558        $docFileVersion = self::getInt2d($this->streamCurrentUser, $pos);
559        $pos += 2;
560        if (0x03F4 != $docFileVersion) {
561            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > docFileVersion');
562        }
563
564        // majorVersion
565        $majorVersion = self::getInt1d($this->streamCurrentUser, $pos);
566        ++$pos;
567        if (0x03 != $majorVersion) {
568            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > majorVersion');
569        }
570
571        // minorVersion
572        $minorVersion = self::getInt1d($this->streamCurrentUser, $pos);
573        ++$pos;
574        if (0x00 != $minorVersion) {
575            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > minorVersion');
576        }
577
578        // unused
579        $pos += 2;
580
581        // ansiUserName
582        // $ansiUserName = '';
583        do {
584            $char = self::getInt1d($this->streamCurrentUser, $pos);
585            if (($char >= 0x00 && $char <= 0x1F) || ($char >= 0x7F && $char <= 0x9F)) {
586                $char = false;
587            } else {
588                // $ansiUserName .= chr($char);
589                ++$pos;
590            }
591        } while (false !== $char);
592
593        // relVersion
594        $relVersion = self::getInt4d($this->streamCurrentUser, $pos);
595        $pos += 4;
596        if (0x00000008 != $relVersion && 0x00000009 != $relVersion) {
597            throw new InvalidFileFormatException($this->filename, self::class, 'Location : CurrentUserAtom > relVersion');
598        }
599
600        // unicodeUserName
601        // $unicodeUserName = '';
602        for ($inc = 0; $inc < $lenUserName; ++$inc) {
603            $char = self::getInt2d($this->streamCurrentUser, $pos);
604            if (($char >= 0x00 && $char <= 0x1F) || ($char >= 0x7F && $char <= 0x9F)) {
605                break;
606            }
607            // $unicodeUserName .= chr($char);
608            $pos += 2;
609        }
610    }
611
612    /**
613     * Stream Powerpoint Document.
614     *
615     * @see http://msdn.microsoft.com/en-us/library/dd921564(v=office.12).aspx
616     */
617    private function loadPowerpointDocumentStream(): void
618    {
619        $this->readRecordUserEditAtom($this->streamPowerpointDocument, $this->offsetToCurrentEdit);
620
621        $this->readRecordPersistDirectoryAtom($this->streamPowerpointDocument, $this->offsetPersistDirectory);
622
623        foreach ($this->rgPersistDirEntry as $offsetDir) {
624            $pos = $offsetDir;
625
626            $rHeader = $this->loadRecordHeader($this->streamPowerpointDocument, $pos);
627            $pos += 8;
628            $this->inMainType = $rHeader['recType'];
629            $this->currentNote = null;
630            switch ($rHeader['recType']) {
631                case self::RT_DOCUMENT:
632                    $this->readRecordDocumentContainer($this->streamPowerpointDocument, $pos);
633
634                    break;
635                case self::RT_NOTES:
636                    $this->readRecordNotesContainer($this->streamPowerpointDocument, $pos);
637
638                    break;
639                case self::RT_SLIDE:
640                    $this->readRecordSlideContainer($this->streamPowerpointDocument, $pos);
641
642                    break;
643                default:
644                    break;
645            }
646        }
647    }
648
649    /**
650     * Read a record header.
651     *
652     * @return array<string, int>
653     */
654    private function loadRecordHeader(string $stream, int $pos): array
655    {
656        $rec = self::getInt2d($stream, $pos);
657        $recType = self::getInt2d($stream, $pos + 2);
658        $recLen = self::getInt4d($stream, $pos + 4);
659
660        return [
661            'recVer' => ($rec >> 0) & bindec('1111'),
662            'recInstance' => ($rec >> 4) & bindec('111111111111'),
663            'recType' => $recType,
664            'recLen' => $recLen,
665        ];
666    }
667
668    /**
669     * Read 8-bit unsigned integer.
670     */
671    public static function getInt1d(string $data, int $pos): int
672    {
673        return ord($data[$pos]);
674    }
675
676    /**
677     * Read 16-bit unsigned integer.
678     */
679    public static function getInt2d(string $data, int $pos): int
680    {
681        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
682    }
683
684    /**
685     * Read 32-bit signed integer.
686     */
687    public static function getInt4d(string $data, int $pos): int
688    {
689        // FIX: represent numbers correctly on 64-bit system
690        // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
691        // Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
692        $or24 = ord($data[$pos + 3]);
693
694        $ord24 = ($or24 & 127) << 24;
695        if ($or24 >= 128) {
696            // negative number
697            $ord24 = -abs((256 - $or24) << 24);
698        }
699
700        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $ord24;
701    }
702
703    /**
704     * A container record that specifies the animation and sound information for a shape.
705     *
706     * @return array<string, int>
707     *
708     * @see https://msdn.microsoft.com/en-us/library/dd772900(v=office.12).aspx
709     */
710    private function readRecordAnimationInfoContainer(string $stream, int $pos): array
711    {
712        $arrayReturn = [
713            'length' => 0,
714        ];
715
716        $data = $this->loadRecordHeader($stream, $pos);
717        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ANIMATIONINFO == $data['recType']) {
718            // Record Header
719            $arrayReturn['length'] += 8;
720
721            // animationAtom
722            // animationSound
723            throw new FeatureNotImplementedException();
724        }
725
726        return $arrayReturn;
727    }
728
729    /**
730     * A container record that specifies information about the document.
731     *
732     * @see http://msdn.microsoft.com/en-us/library/dd947357(v=office.12).aspx
733     */
734    private function readRecordDocumentContainer(string $stream, int $pos): void
735    {
736        $documentAtom = $this->loadRecordHeader($stream, $pos);
737        $pos += 8;
738        if (0x1 != $documentAtom['recVer'] || 0x000 != $documentAtom['recInstance'] || self::RT_DOCUMENTATOM != $documentAtom['recType']) {
739            throw new InvalidFileFormatException($this->filename, self::class, 'Location : RTDocument > DocumentAtom');
740        }
741        $pos += $documentAtom['recLen'];
742
743        $exObjList = $this->loadRecordHeader($stream, $pos);
744        if (0xF == $exObjList['recVer'] && 0x000 == $exObjList['recInstance'] && self::RT_EXTERNALOBJECTLIST == $exObjList['recType']) {
745            $pos += 8;
746            // exObjListAtom > rh
747            $exObjListAtom = $this->loadRecordHeader($stream, $pos);
748            if (0x0 != $exObjListAtom['recVer'] || 0x000 != $exObjListAtom['recInstance'] || self::RT_EXTERNALOBJECTLISTATOM != $exObjListAtom['recType'] || 0x00000004 != $exObjListAtom['recLen']) {
749                throw new InvalidFileFormatException($this->filename, self::class, 'Location : RTDocument > DocumentAtom > exObjList > exObjListAtom');
750            }
751            $pos += 8;
752            // exObjListAtom > exObjIdSeed
753            $pos += 4;
754            // rgChildRec
755            $exObjList['recLen'] -= 12;
756            do {
757                $childRec = $this->loadRecordHeader($stream, $pos);
758                $pos += 8;
759                $exObjList['recLen'] -= 8;
760                switch ($childRec['recType']) {
761                    case self::RT_EXTERNALHYPERLINK:
762                        //@link : http://msdn.microsoft.com/en-us/library/dd944995(v=office.12).aspx
763                        // exHyperlinkAtom > rh
764                        $exHyperlinkAtom = $this->loadRecordHeader($stream, $pos);
765                        if (0x0 != $exHyperlinkAtom['recVer'] || 0x000 != $exHyperlinkAtom['recInstance'] || self::RT_EXTERNALHYPERLINKATOM != $exHyperlinkAtom['recType'] || 0x00000004 != $exHyperlinkAtom['recLen']) {
766                            throw new InvalidFileFormatException($this->filename, self::class, 'Location : RTDocument > DocumentAtom > exObjList > rgChildRec > RT_ExternalHyperlink');
767                        }
768                        $pos += 8;
769                        $exObjList['recLen'] -= 8;
770                        // exHyperlinkAtom > exHyperlinkId
771                        $exHyperlinkId = self::getInt4d($stream, $pos);
772                        $pos += 4;
773                        $exObjList['recLen'] -= 4;
774
775                        $this->arrayHyperlinks[$exHyperlinkId] = [];
776                        // friendlyNameAtom
777                        $friendlyNameAtom = $this->loadRecordHeader($stream, $pos);
778                        if (0x0 == $friendlyNameAtom['recVer'] && 0x000 == $friendlyNameAtom['recInstance'] && self::RT_CSTRING == $friendlyNameAtom['recType'] && $friendlyNameAtom['recLen'] % 2 == 0) {
779                            $pos += 8;
780                            $exObjList['recLen'] -= 8;
781                            $this->arrayHyperlinks[$exHyperlinkId]['text'] = '';
782                            for ($inc = 0; $inc < ($friendlyNameAtom['recLen'] / 2); ++$inc) {
783                                $char = self::getInt2d($stream, $pos);
784                                $pos += 2;
785                                $exObjList['recLen'] -= 2;
786                                $this->arrayHyperlinks[$exHyperlinkId]['text'] .= chr($char);
787                            }
788                        }
789                        // targetAtom
790                        $targetAtom = $this->loadRecordHeader($stream, $pos);
791                        if (0x0 == $targetAtom['recVer'] && 0x001 == $targetAtom['recInstance'] && self::RT_CSTRING == $targetAtom['recType'] && $targetAtom['recLen'] % 2 == 0) {
792                            $pos += 8;
793                            $exObjList['recLen'] -= 8;
794                            $this->arrayHyperlinks[$exHyperlinkId]['url'] = '';
795                            for ($inc = 0; $inc < ($targetAtom['recLen'] / 2); ++$inc) {
796                                $char = self::getInt2d($stream, $pos);
797                                $pos += 2;
798                                $exObjList['recLen'] -= 2;
799                                $this->arrayHyperlinks[$exHyperlinkId]['url'] .= chr($char);
800                            }
801                        }
802                        // locationAtom
803                        $locationAtom = $this->loadRecordHeader($stream, $pos);
804                        if (0x0 == $locationAtom['recVer'] && 0x003 == $locationAtom['recInstance'] && self::RT_CSTRING == $locationAtom['recType'] && $locationAtom['recLen'] % 2 == 0) {
805                            $pos += 8;
806                            $exObjList['recLen'] -= 8;
807                            $string = '';
808                            for ($inc = 0; $inc < ($locationAtom['recLen'] / 2); ++$inc) {
809                                $char = self::getInt2d($stream, $pos);
810                                $pos += 2;
811                                $exObjList['recLen'] -= 2;
812                                $string .= chr($char);
813                            }
814                        }
815
816                        break;
817                    default:
818                        // var_dump(dechex((int) $childRec['recType']));
819                        throw new FeatureNotImplementedException();
820                }
821            } while ($exObjList['recLen'] > 0);
822        }
823
824        //@link : http://msdn.microsoft.com/en-us/library/dd907813(v=office.12).aspx
825        $documentTextInfo = $this->loadRecordHeader($stream, $pos);
826        if (0xF == $documentTextInfo['recVer'] && 0x000 == $documentTextInfo['recInstance'] && self::RT_ENVIRONMENT == $documentTextInfo['recType']) {
827            $pos += 8;
828            //@link : http://msdn.microsoft.com/en-us/library/dd952717(v=office.12).aspx
829            $kinsoku = $this->loadRecordHeader($stream, $pos);
830            if (0xF == $kinsoku['recVer'] && 0x002 == $kinsoku['recInstance'] && self::RT_KINSOKU == $kinsoku['recType']) {
831                $pos += 8;
832                $pos += $kinsoku['recLen'];
833            }
834
835            //@link : http://msdn.microsoft.com/en-us/library/dd948152(v=office.12).aspx
836            $fontCollection = $this->loadRecordHeader($stream, $pos);
837            if (0xF == $fontCollection['recVer'] && 0x000 == $fontCollection['recInstance'] && self::RT_FONTCOLLECTION == $fontCollection['recType']) {
838                $pos += 8;
839                do {
840                    $fontEntityAtom = $this->loadRecordHeader($stream, $pos);
841                    $pos += 8;
842                    $fontCollection['recLen'] -= 8;
843                    if (0x0 != $fontEntityAtom['recVer'] || $fontEntityAtom['recInstance'] > 128 || self::RT_FONTENTITYATOM != $fontEntityAtom['recType']) {
844                        throw new InvalidFileFormatException($this->filename, self::class, 'Location : RTDocument > RT_Environment > RT_FontCollection > RT_FontEntityAtom');
845                    }
846                    $string = '';
847                    for ($inc = 0; $inc < 32; ++$inc) {
848                        $char = self::getInt2d($stream, $pos);
849                        $pos += 2;
850                        $fontCollection['recLen'] -= 2;
851                        $string .= chr($char);
852                    }
853                    $this->arrayFonts[] = $string;
854
855                    // lfCharSet (1 byte)
856                    ++$pos;
857                    --$fontCollection['recLen'];
858
859                    // fEmbedSubsetted (1 bit)
860                    // unused (7 bits)
861                    ++$pos;
862                    --$fontCollection['recLen'];
863
864                    // rasterFontType (1 bit)
865                    // deviceFontType (1 bit)
866                    // truetypeFontType (1 bit)
867                    // fNoFontSubstitution (1 bit)
868                    // reserved (4 bits)
869                    ++$pos;
870                    --$fontCollection['recLen'];
871
872                    // lfPitchAndFamily (1 byte)
873                    ++$pos;
874                    --$fontCollection['recLen'];
875
876                    $fontEmbedData1 = $this->loadRecordHeader($stream, $pos);
877                    if (0x0 == $fontEmbedData1['recVer'] && $fontEmbedData1['recInstance'] >= 0x000 && $fontEmbedData1['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData1['recType']) {
878                        $pos += 8;
879                        $fontCollection['recLen'] -= 8;
880                        $pos += $fontEmbedData1['recLen'];
881                        $fontCollection['recLen'] -= $fontEmbedData1['recLen'];
882                    }
883
884                    $fontEmbedData2 = $this->loadRecordHeader($stream, $pos);
885                    if (0x0 == $fontEmbedData2['recVer'] && $fontEmbedData2['recInstance'] >= 0x000 && $fontEmbedData2['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData2['recType']) {
886                        $pos += 8;
887                        $fontCollection['recLen'] -= 8;
888                        $pos += $fontEmbedData2['recLen'];
889                        $fontCollection['recLen'] -= $fontEmbedData2['recLen'];
890                    }
891
892                    $fontEmbedData3 = $this->loadRecordHeader($stream, $pos);
893                    if (0x0 == $fontEmbedData3['recVer'] && $fontEmbedData3['recInstance'] >= 0x000 && $fontEmbedData3['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData3['recType']) {
894                        $pos += 8;
895                        $fontCollection['recLen'] -= 8;
896                        $pos += $fontEmbedData3['recLen'];
897                        $fontCollection['recLen'] -= $fontEmbedData3['recLen'];
898                    }
899
900                    $fontEmbedData4 = $this->loadRecordHeader($stream, $pos);
901                    if (0x0 == $fontEmbedData4['recVer'] && $fontEmbedData4['recInstance'] >= 0x000 && $fontEmbedData4['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData4['recType']) {
902                        $pos += 8;
903                        $fontCollection['recLen'] -= 8;
904                        $pos += $fontEmbedData4['recLen'];
905                        $fontCollection['recLen'] -= $fontEmbedData4['recLen'];
906                    }
907                } while ($fontCollection['recLen'] > 0);
908            }
909
910            $textCFDefaultsAtom = $this->loadRecordHeader($stream, $pos);
911            if (0x0 == $textCFDefaultsAtom['recVer'] && 0x000 == $textCFDefaultsAtom['recInstance'] && self::RT_TEXTCHARFORMATEXCEPTIONATOM == $textCFDefaultsAtom['recType']) {
912                $pos += 8;
913                $pos += $textCFDefaultsAtom['recLen'];
914            }
915
916            $textPFDefaultsAtom = $this->loadRecordHeader($stream, $pos);
917            if (0x0 == $textPFDefaultsAtom['recVer'] && 0x000 == $textPFDefaultsAtom['recInstance'] && self::RT_TEXTPARAGRAPHFORMATEXCEPTIONATOM == $textPFDefaultsAtom['recType']) {
918                $pos += 8;
919                $pos += $textPFDefaultsAtom['recLen'];
920            }
921
922            $defaultRulerAtom = $this->loadRecordHeader($stream, $pos);
923            if (0x0 == $defaultRulerAtom['recVer'] && 0x000 == $defaultRulerAtom['recInstance'] && self::RT_DEFAULTRULERATOM == $defaultRulerAtom['recType']) {
924                $pos += 8;
925                $pos += $defaultRulerAtom['recLen'];
926            }
927
928            $textSIDefaultsAtom = $this->loadRecordHeader($stream, $pos);
929            if (0x0 == $textSIDefaultsAtom['recVer'] && 0x000 == $textSIDefaultsAtom['recInstance'] && self::RT_TEXTSPECIALINFODEFAULTATOM == $textSIDefaultsAtom['recType']) {
930                $pos += 8;
931                $pos += $textSIDefaultsAtom['recLen'];
932            }
933
934            $textMasterStyleAtom = $this->loadRecordHeader($stream, $pos);
935            if (0x0 == $textMasterStyleAtom['recVer'] && self::RT_TEXTMASTERSTYLEATOM == $textMasterStyleAtom['recType']) {
936                $pos += 8;
937                $pos += $textMasterStyleAtom['recLen'];
938            }
939        }
940
941        $soundCollection = $this->loadRecordHeader($stream, $pos);
942        if (0xF == $soundCollection['recVer'] && 0x005 == $soundCollection['recInstance'] && self::RT_SOUNDCOLLECTION == $soundCollection['recType']) {
943            $pos += 8;
944            $pos += $soundCollection['recLen'];
945        }
946
947        $drawingGroup = $this->loadRecordHeader($stream, $pos);
948        if (0xF == $drawingGroup['recVer'] && 0x000 == $drawingGroup['recInstance'] && self::RT_DRAWINGGROUP == $drawingGroup['recType']) {
949            $drawing = $this->readRecordDrawingGroupContainer($stream, $pos);
950            $pos += 8;
951            $pos += $drawing['length'];
952        }
953
954        $masterList = $this->loadRecordHeader($stream, $pos);
955        if (0xF == $masterList['recVer'] && 0x001 == $masterList['recInstance'] && self::RT_SLIDELISTWITHTEXT == $masterList['recType']) {
956            $pos += 8;
957            $pos += $masterList['recLen'];
958        }
959
960        $docInfoList = $this->loadRecordHeader($stream, $pos);
961        if (0xF == $docInfoList['recVer'] && 0x000 == $docInfoList['recInstance'] && self::RT_LIST == $docInfoList['recType']) {
962            $pos += 8;
963            $pos += $docInfoList['recLen'];
964        }
965
966        $slideHF = $this->loadRecordHeader($stream, $pos);
967        if (0xF == $slideHF['recVer'] && 0x003 == $slideHF['recInstance'] && self::RT_HEADERSFOOTERS == $slideHF['recType']) {
968            $pos += 8;
969            $pos += $slideHF['recLen'];
970        }
971
972        $notesHF = $this->loadRecordHeader($stream, $pos);
973        if (0xF == $notesHF['recVer'] && 0x004 == $notesHF['recInstance'] && self::RT_HEADERSFOOTERS == $notesHF['recType']) {
974            $pos += 8;
975            $pos += $notesHF['recLen'];
976        }
977
978        // SlideListWithTextContainer
979        $slideList = $this->loadRecordHeader($stream, $pos);
980        if (0xF == $slideList['recVer'] && 0x000 == $slideList['recInstance'] && self::RT_SLIDELISTWITHTEXT == $slideList['recType']) {
981            $pos += 8;
982            do {
983                // SlideListWithTextSubContainerOrAtom
984                $rhSlideList = $this->loadRecordHeader($stream, $pos);
985                if (0x0 == $rhSlideList['recVer'] && 0x000 == $rhSlideList['recInstance'] && self::RT_SLIDEPERSISTATOM == $rhSlideList['recType'] && 0x00000014 == $rhSlideList['recLen']) {
986                    $pos += 8;
987                    $slideList['recLen'] -= 8;
988                    // persistIdRef
989                    $pos += 4;
990                    $slideList['recLen'] -= 4;
991                    // reserved1 - fShouldCollapse - fNonOutlineData - reserved2
992                    $pos += 4;
993                    $slideList['recLen'] -= 4;
994                    // cTexts
995                    $pos += 4;
996                    $slideList['recLen'] -= 4;
997                    // slideId
998                    $slideId = self::getInt4d($stream, $pos);
999                    if (-2147483648 == $slideId) {
1000                        $slideId = 0;
1001                    }
1002                    if ($slideId > 0) {
1003                        $this->arrayNotes[$this->oPhpPresentation->getActiveSlideIndex()] = $slideId;
1004                    }
1005                    $pos += 4;
1006                    $slideList['recLen'] -= 4;
1007                    // reserved3
1008                    $pos += 4;
1009                    $slideList['recLen'] -= 4;
1010                }
1011            } while ($slideList['recLen'] > 0);
1012        }
1013    }
1014
1015    /**
1016     * An atom record that specifies information about a slide.
1017     *
1018     * @return array<string, int>
1019     *
1020     * @see https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx
1021     */
1022    private function readRecordDrawingContainer(string $stream, int $pos): array
1023    {
1024        $arrayReturn = [
1025            'length' => 0,
1026        ];
1027
1028        $data = $this->loadRecordHeader($stream, $pos);
1029        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_DRAWING == $data['recType']) {
1030            // Record Header
1031            $arrayReturn['length'] += 8;
1032
1033            $officeArtDg = $this->readRecordOfficeArtDgContainer($stream, $pos + $arrayReturn['length']);
1034            $arrayReturn['length'] += $officeArtDg['length'];
1035        }
1036
1037        return $arrayReturn;
1038    }
1039
1040    /**
1041     * @return array<string, int>
1042     */
1043    private function readRecordDrawingGroupContainer(string $stream, int $pos): array
1044    {
1045        $arrayReturn = [
1046            'length' => 0,
1047        ];
1048
1049        $data = $this->loadRecordHeader($stream, $pos);
1050        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_DRAWINGGROUP == $data['recType']) {
1051            // Record Header
1052            $arrayReturn['length'] += 8;
1053            $arrayReturn['length'] += $data['recLen'];
1054        }
1055
1056        return $arrayReturn;
1057    }
1058
1059    /**
1060     * An atom record that specifies a reference to an external object.
1061     *
1062     * @return array<string, int>
1063     *
1064     * @see https://msdn.microsoft.com/en-us/library/dd910388(v=office.12).aspx
1065     */
1066    private function readRecordExObjRefAtom(string $stream, int $pos): array
1067    {
1068        $arrayReturn = [
1069            'length' => 0,
1070        ];
1071
1072        $data = $this->loadRecordHeader($stream, $pos);
1073        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_EXTERNALOBJECTREFATOM == $data['recType'] && 0x00000004 == $data['recLen']) {
1074            // Record Header
1075            $arrayReturn['length'] += 8;
1076            // Datas
1077            $arrayReturn['length'] += $data['recLen'];
1078        }
1079
1080        return $arrayReturn;
1081    }
1082
1083    /**
1084     * An atom record that specifies a type of action to be performed.
1085     *
1086     * @return array<string, int>
1087     *
1088     * @see https://msdn.microsoft.com/en-us/library/dd953300(v=office.12).aspx
1089     */
1090    private function readRecordInteractiveInfoAtom(string $stream, int $pos): array
1091    {
1092        $arrayReturn = [
1093            'length' => 0,
1094        ];
1095
1096        $data = $this->loadRecordHeader($stream, $pos);
1097        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_INTERACTIVEINFOATOM == $data['recType'] && 0x00000010 == $data['recLen']) {
1098            // Record Header
1099            $arrayReturn['length'] += 8;
1100            // soundIdRef
1101            $arrayReturn['length'] += 4;
1102            // exHyperlinkIdRef
1103            $arrayReturn['exHyperlinkIdRef'] = self::getInt4d($stream, $pos + $arrayReturn['length']);
1104            $arrayReturn['length'] += 4;
1105            // action
1106            ++$arrayReturn['length'];
1107            // oleVerb
1108            ++$arrayReturn['length'];
1109            // jump
1110            ++$arrayReturn['length'];
1111            // fAnimated (1 bit)
1112            // fStopSound (1 bit)
1113            // fCustomShowReturn (1 bit)
1114            // fVisited (1 bit)
1115            // reserved (4 bits)
1116            ++$arrayReturn['length'];
1117            // hyperlinkType
1118            ++$arrayReturn['length'];
1119            // unused
1120            $arrayReturn['length'] += 3;
1121        }
1122
1123        return $arrayReturn;
1124    }
1125
1126    /**
1127     * An atom record that specifies the name of a macro, a file name, or a named show.
1128     *
1129     * @return array<string, int>
1130     *
1131     * @see https://msdn.microsoft.com/en-us/library/dd925121(v=office.12).aspx
1132     */
1133    private function readRecordMacroNameAtom(string $stream, int $pos): array
1134    {
1135        $arrayReturn = [
1136            'length' => 0,
1137        ];
1138
1139        $data = $this->loadRecordHeader($stream, $pos);
1140        if (0x0 == $data['recVer'] && 0x002 == $data['recInstance'] && self::RT_CSTRING == $data['recType'] && $data['recLen'] % 2 == 0) {
1141            // Record Header
1142            $arrayReturn['length'] += 8;
1143            // Datas
1144            $arrayReturn['length'] += $data['recLen'];
1145        }
1146
1147        return $arrayReturn;
1148    }
1149
1150    /**
1151     * A container record that specifies what actions to perform when interacting with an object by means of a mouse click.
1152     *
1153     * @return array<string, int>
1154     *
1155     * @see https://msdn.microsoft.com/en-us/library/dd952348(v=office.12).aspx
1156     */
1157    private function readRecordMouseClickInteractiveInfoContainer(string $stream, int $pos): array
1158    {
1159        $arrayReturn = [
1160            'length' => 0,
1161        ];
1162
1163        $data = $this->loadRecordHeader($stream, $pos);
1164        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_INTERACTIVEINFO == $data['recType']) {
1165            // Record Header
1166            $arrayReturn['length'] += 8;
1167            // interactiveInfoAtom
1168            $interactiveInfoAtom = $this->readRecordInteractiveInfoAtom($stream, $pos + $arrayReturn['length']);
1169            $arrayReturn['length'] += $interactiveInfoAtom['length'];
1170            if ($interactiveInfoAtom['length'] > 0) {
1171                $arrayReturn['exHyperlinkIdRef'] = $interactiveInfoAtom['exHyperlinkIdRef'];
1172            }
1173            // macroNameAtom
1174            $macroNameAtom = $this->readRecordMacroNameAtom($stream, $pos + $arrayReturn['length']);
1175            $arrayReturn['length'] += $macroNameAtom['length'];
1176        }
1177
1178        return $arrayReturn;
1179    }
1180
1181    /**
1182     * A container record that specifies what actions to perform when interacting with an object by moving the mouse cursor over it.
1183     *
1184     * @return array<string, int>
1185     *
1186     * @see https://msdn.microsoft.com/en-us/library/dd925811(v=office.12).aspx
1187     */
1188    private function readRecordMouseOverInteractiveInfoContainer(string $stream, int $pos): array
1189    {
1190        $arrayReturn = [
1191            'length' => 0,
1192        ];
1193
1194        $data = $this->loadRecordHeader($stream, $pos);
1195        if (0xF == $data['recVer'] && 0x001 == $data['recInstance'] && self::RT_INTERACTIVEINFO == $data['recType']) {
1196            // Record Header
1197            $arrayReturn['length'] += 8;
1198
1199            // interactiveInfoAtom
1200            // macroNameAtom
1201            throw new FeatureNotImplementedException();
1202        }
1203
1204        return $arrayReturn;
1205    }
1206
1207    /**
1208     * The OfficeArtBlip record specifies BLIP file data.
1209     *
1210     * @return array{'length': int, 'picture': null|string}
1211     *
1212     * @see https://msdn.microsoft.com/en-us/library/dd910081(v=office.12).aspx
1213     */
1214    private function readRecordOfficeArtBlip(string $stream, int $pos): array
1215    {
1216        $arrayReturn = [
1217            'length' => 0,
1218            'picture' => null,
1219        ];
1220
1221        $data = $this->loadRecordHeader($stream, $pos);
1222        if (0x0 == $data['recVer'] && ($data['recType'] >= 0xF018 && $data['recType'] <= 0xF117)) {
1223            // Record Header
1224            $arrayReturn['length'] += 8;
1225            // Datas
1226            switch ($data['recType']) {
1227                case self::OFFICEARTBLIPJPG:
1228                case self::OFFICEARTBLIPPNG:
1229                    // rgbUid1
1230                    $arrayReturn['length'] += 16;
1231                    $data['recLen'] -= 16;
1232                    if (0x6E1 == $data['recInstance']) {
1233                        // rgbUid2
1234                        $arrayReturn['length'] += 16;
1235                        $data['recLen'] -= 16;
1236                    }
1237                    // tag
1238                    ++$arrayReturn['length'];
1239                    --$data['recLen'];
1240                    // BLIPFileData
1241                    $arrayReturn['picture'] = substr($this->streamPictures, $pos + $arrayReturn['length'], $data['recLen']);
1242                    $arrayReturn['length'] += $data['recLen'];
1243
1244                    break;
1245                default:
1246                    // var_dump(dechex((int) $data['recType']))
1247                    throw new FeatureNotImplementedException();
1248            }
1249        }
1250
1251        return $arrayReturn;
1252    }
1253
1254    /**
1255     * The OfficeArtChildAnchor record specifies four signed integers that specify the anchor for the shape that contains this record.
1256     *
1257     * @return array<string, int>
1258     *
1259     * @see https://msdn.microsoft.com/en-us/library/dd922720(v=office.12).aspx
1260     */
1261    private function readRecordOfficeArtChildAnchor(string $stream, int $pos)
1262    {
1263        $arrayReturn = [
1264            'length' => 0,
1265        ];
1266
1267        $data = $this->loadRecordHeader($stream, $pos);
1268        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF00F == $data['recType'] && 0x00000010 == $data['recLen']) {
1269            // Record Header
1270            $arrayReturn['length'] += 8;
1271            // Datas
1272            $arrayReturn['left'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']);
1273            $arrayReturn['length'] += 4;
1274            $arrayReturn['top'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']);
1275            $arrayReturn['length'] += 4;
1276            $arrayReturn['width'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']) - $arrayReturn['left'];
1277            $arrayReturn['length'] += 4;
1278            $arrayReturn['height'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']) - $arrayReturn['top'];
1279            $arrayReturn['length'] += 4;
1280        }
1281
1282        return $arrayReturn;
1283    }
1284
1285    /**
1286     * An atom record that specifies the location of a shape.
1287     *
1288     * @return array<string, int>
1289     *
1290     * @see https://msdn.microsoft.com/en-us/library/dd922797(v=office.12).aspx
1291     */
1292    private function readRecordOfficeArtClientAnchor(string $stream, int $pos)
1293    {
1294        $arrayReturn = [
1295            'length' => 0,
1296        ];
1297
1298        $data = $this->loadRecordHeader($stream, $pos);
1299        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF010 == $data['recType'] && (0x00000008 == $data['recLen'] || 0x00000010 == $data['recLen'])) {
1300            // Record Header
1301            $arrayReturn['length'] += 8;
1302            // Datas
1303            switch ($data['recLen']) {
1304                case 0x00000008:
1305                    $arrayReturn['top'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6);
1306                    $arrayReturn['length'] += 2;
1307                    $arrayReturn['left'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6);
1308                    $arrayReturn['length'] += 2;
1309                    $arrayReturn['width'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6) - $arrayReturn['left'];
1310                    $arrayReturn['length'] += 2;
1311                    $arrayReturn['height'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6) - $arrayReturn['left'];
1312                    $arrayReturn['length'] += 2;
1313                    $pos += 8;
1314
1315                    break;
1316                case 0x00000010:
1317                    // record OfficeArtClientAnchor (0x00000010)
1318                    throw new FeatureNotImplementedException();
1319            }
1320        }
1321
1322        return $arrayReturn;
1323    }
1324
1325    /**
1326     * A container record that specifies text related data for a shape.
1327     *
1328     * @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}}
1329     *
1330     * @see https://msdn.microsoft.com/en-us/library/dd910958(v=office.12).aspx
1331     */
1332    private function readRecordOfficeArtClientTextbox(string $stream, int $pos)
1333    {
1334        $arrayReturn = [
1335            'length' => 0,
1336            'text' => '',
1337            'numParts' => 0,
1338            'numTexts' => 0,
1339            'hyperlink' => [],
1340        ];
1341
1342        $data = $this->loadRecordHeader($stream, $pos);
1343        // recVer 0xF
1344        // Doc : 0x0    https://msdn.microsoft.com/en-us/library/dd910958(v=office.12).aspx
1345        // Sample : 0xF https://msdn.microsoft.com/en-us/library/dd953497(v=office.12).aspx
1346        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF00D == $data['recType']) {
1347            // Record Header
1348            $arrayReturn['length'] += 8;
1349            // Datas
1350            $strLen = 0;
1351            do {
1352                $rhChild = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']);
1353                // @link : https://msdn.microsoft.com/en-us/library/dd947039(v=office.12).aspx
1354                // echo dechex($rhChild['recType']).'-'.$rhChild['recType'].EOL;
1355                switch ($rhChild['recType']) {
1356                    case self::RT_INTERACTIVEINFO:
1357                        //@link : http://msdn.microsoft.com/en-us/library/dd948623(v=office.12).aspx
1358                        if (0x0000 == $rhChild['recInstance']) {
1359                            $mouseClickInfo = $this->readRecordMouseClickInteractiveInfoContainer($stream, $pos + $arrayReturn['length']);
1360                            $arrayReturn['length'] += $mouseClickInfo['length'];
1361                            $arrayReturn['hyperlink'][]['id'] = $mouseClickInfo['exHyperlinkIdRef'];
1362                        }
1363                        if (0x0001 == $rhChild['recInstance']) {
1364                            $mouseOverInfo = $this->readRecordMouseOverInteractiveInfoContainer($stream, $pos + $arrayReturn['length']);
1365                            $arrayReturn['length'] += $mouseOverInfo['length'];
1366                        }
1367
1368                        break;
1369                    case self::RT_STYLETEXTPROPATOM:
1370                        $arrayReturn['length'] += 8;
1371                        // @link : http://msdn.microsoft.com/en-us/library/dd950647(v=office.12).aspx
1372                        // rgTextPFRun
1373                        $strLenRT = $strLen + 1;
1374                        do {
1375                            $strucTextPFRun = $this->readStructureTextPFRun($stream, $pos + $arrayReturn['length'], $strLenRT);
1376                            ++$arrayReturn['numTexts'];
1377                            $arrayReturn['text' . $arrayReturn['numTexts']] = $strucTextPFRun;
1378                            if (isset($strucTextPFRun['alignH'])) {
1379                                $arrayReturn['alignH'] = $strucTextPFRun['alignH'];
1380                            }
1381                            $strLenRT = $strucTextPFRun['strLenRT'];
1382                            $arrayReturn['length'] += $strucTextPFRun['length'];
1383                        } while ($strLenRT > 0);
1384                        // rgTextCFRun
1385                        $strLenRT = $strLen + 1;
1386                        do {
1387                            $strucTextCFRun = $this->readStructureTextCFRun($stream, $pos + $arrayReturn['length'], $strLenRT);
1388                            ++$arrayReturn['numParts'];
1389                            $arrayReturn['part' . $arrayReturn['numParts']] = $strucTextCFRun;
1390                            $strLenRT = $strucTextCFRun['strLenRT'];
1391                            $arrayReturn['length'] += $strucTextCFRun['length'];
1392                        } while ($strLenRT > 0);
1393
1394                        break;
1395                    case self::RT_TEXTBYTESATOM:
1396                        $arrayReturn['length'] += 8;
1397                        // @link : https://msdn.microsoft.com/en-us/library/dd947905(v=office.12).aspx
1398                        $strLen = (int) $rhChild['recLen'];
1399                        for ($inc = 0; $inc < $strLen; ++$inc) {
1400                            $char = self::getInt1d($stream, $pos + $arrayReturn['length']);
1401                            if (0x0B == $char) {
1402                                $char = 0x20;
1403                            }
1404                            $arrayReturn['text'] .= Text::chr($char);
1405                            ++$arrayReturn['length'];
1406                        }
1407
1408                        break;
1409                    case self::RT_TEXTCHARSATOM:
1410                        $arrayReturn['length'] += 8;
1411                        // @link : http://msdn.microsoft.com/en-us/library/dd772921(v=office.12).aspx
1412                        $strLen = (int) ($rhChild['recLen'] / 2);
1413                        for ($inc = 0; $inc < $strLen; ++$inc) {
1414                            $char = self::getInt2d($stream, $pos + $arrayReturn['length']);
1415                            if (0x0B == $char) {
1416                                $char = 0x20;
1417                            }
1418                            $arrayReturn['text'] .= Text::chr($char);
1419                            $arrayReturn['length'] += 2;
1420                        }
1421
1422                        break;
1423                    case self::RT_TEXTHEADERATOM:
1424                        $arrayReturn['length'] += 8;
1425                        // @link : http://msdn.microsoft.com/en-us/library/dd905272(v=office.12).aspx
1426                        // textType
1427                        $arrayReturn['length'] += 4;
1428
1429                        break;
1430                    case self::RT_TEXTINTERACTIVEINFOATOM:
1431                        $arrayReturn['length'] += 8;
1432                        //@link : http://msdn.microsoft.com/en-us/library/dd947973(v=office.12).aspx
1433                        if (0x0000 == $rhChild['recInstance']) {
1434                            //@todo : MouseClickTextInteractiveInfoAtom
1435                            $arrayReturn['hyperlink'][count($arrayReturn['hyperlink']) - 1]['start'] = self::getInt4d($stream, $pos + +$arrayReturn['length']);
1436                            $arrayReturn['length'] += 4;
1437
1438                            $arrayReturn['hyperlink'][count($arrayReturn['hyperlink']) - 1]['end'] = self::getInt4d($stream, $pos + +$arrayReturn['length']);
1439                            $arrayReturn['length'] += 4;
1440                        }
1441                        if (0x0001 == $rhChild['recInstance']) {
1442                            throw new FeatureNotImplementedException();
1443                        }
1444
1445                        break;
1446                    case self::RT_TEXTSPECIALINFOATOM:
1447                        $arrayReturn['length'] += 8;
1448                        // @link : http://msdn.microsoft.com/en-us/library/dd945296(v=office.12).aspx
1449                        $strLenRT = $strLen + 1;
1450                        do {
1451                            $structTextSIRun = $this->readStructureTextSIRun($stream, $pos + $arrayReturn['length'], $strLenRT);
1452                            $strLenRT = $structTextSIRun['strLenRT'];
1453                            $arrayReturn['length'] += $structTextSIRun['length'];
1454                        } while ($strLenRT > 0);
1455
1456                        break;
1457                    case self::RT_TEXTRULERATOM:
1458                        $arrayReturn['length'] += 8;
1459                        // @link : http://msdn.microsoft.com/en-us/library/dd953212(v=office.12).aspx
1460                        $structRuler = $this->readStructureTextRuler($stream, $pos + $arrayReturn['length']);
1461                        $arrayReturn['length'] += $structRuler['length'];
1462
1463                        break;
1464                    case self::RT_SLIDENUMBERMETACHARATOM:
1465                        $datasRecord = $this->readRecordSlideNumberMCAtom($stream, $pos + $arrayReturn['length']);
1466                        $arrayReturn['length'] += $datasRecord['length'];
1467
1468                        break;
1469                    default:
1470                        $arrayReturn['length'] += 8;
1471                        $arrayReturn['length'] += $rhChild['recLen'];
1472                }
1473            } while (($data['recLen'] - $arrayReturn['length']) > 0);
1474        }
1475
1476        return $arrayReturn;
1477    }
1478
1479    /**
1480     * The OfficeArtSpContainer record specifies a shape container.
1481     *
1482     * @return array{'length': int, 'shape': null|AbstractShape}
1483     *
1484     * @see https://msdn.microsoft.com/en-us/library/dd943794(v=office.12).aspx
1485     */
1486    private function readRecordOfficeArtSpContainer(string $stream, int $pos)
1487    {
1488        $arrayReturn = [
1489            'length' => 0,
1490            'shape' => null,
1491        ];
1492
1493        $data = $this->loadRecordHeader($stream, $pos);
1494        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF004 == $data['recType']) {
1495            // Record Header
1496            $arrayReturn['length'] += 8;
1497            // shapeGroup
1498            $shapeGroup = $this->readRecordOfficeArtFSPGR($stream, $pos + $arrayReturn['length']);
1499            $arrayReturn['length'] += $shapeGroup['length'];
1500
1501            // shapeProp
1502            $shapeProp = $this->readRecordOfficeArtFSP($stream, $pos + $arrayReturn['length']);
1503            if (0 == $shapeProp['length']) {
1504                throw new InvalidFileFormatException($this->filename, self::class);
1505            }
1506            $arrayReturn['length'] += $shapeProp['length'];
1507
1508            if (0x1 == $shapeProp['fDeleted'] && 0x0 == $shapeProp['fChild']) {
1509                // deletedShape
1510                $deletedShape = $this->readRecordOfficeArtFPSPL($stream, $pos + $arrayReturn['length']);
1511                $arrayReturn['length'] += $deletedShape['length'];
1512            }
1513
1514            // shapePrimaryOptions
1515            $shpPrimaryOptions = $this->readRecordOfficeArtFOPT($stream, $pos + $arrayReturn['length']);
1516            $arrayReturn['length'] += $shpPrimaryOptions['length'];
1517
1518            // shapeSecondaryOptions1
1519            $shpSecondaryOptions1 = $this->readRecordOfficeArtSecondaryFOPT($stream, $pos + $arrayReturn['length']);
1520            $arrayReturn['length'] += $shpSecondaryOptions1['length'];
1521
1522            // shapeTertiaryOptions1
1523            $shpTertiaryOptions1 = $this->readRecordOfficeArtTertiaryFOPT($stream, $pos + $arrayReturn['length']);
1524            $arrayReturn['length'] += $shpTertiaryOptions1['length'];
1525
1526            // childAnchor
1527            $childAnchor = $this->readRecordOfficeArtChildAnchor($stream, $pos + $arrayReturn['length']);
1528            $arrayReturn['length'] += $childAnchor['length'];
1529
1530            // clientAnchor
1531            $clientAnchor = $this->readRecordOfficeArtClientAnchor($stream, $pos + $arrayReturn['length']);
1532            $arrayReturn['length'] += $clientAnchor['length'];
1533
1534            // clientData
1535            $clientData = $this->readRecordOfficeArtClientData($stream, $pos + $arrayReturn['length']);
1536            $arrayReturn['length'] += $clientData['length'];
1537
1538            // clientTextbox
1539            $clientTextbox = $this->readRecordOfficeArtClientTextbox($stream, $pos + $arrayReturn['length']);
1540            $arrayReturn['length'] += $clientTextbox['length'];
1541
1542            // shapeSecondaryOptions2
1543            if (0 == $shpSecondaryOptions1['length']) {
1544                $shpSecondaryOptions2 = $this->readRecordOfficeArtSecondaryFOPT($stream, $pos + $arrayReturn['length']);
1545                $arrayReturn['length'] += $shpSecondaryOptions2['length'];
1546            }
1547
1548            // shapeTertiaryOptions2
1549            if (0 == $shpTertiaryOptions1['length']) {
1550                $shpTertiaryOptions2 = $this->readRecordOfficeArtTertiaryFOPT($stream, $pos + $arrayReturn['length']);
1551                $arrayReturn['length'] += $shpTertiaryOptions2['length'];
1552            }
1553
1554            // Core : Shape
1555            // Informations about group are not defined
1556            $arrayDimensions = [];
1557            $bIsGroup = false;
1558            if (is_object($this->oCurrentGroup)) {
1559                if (!$this->bFirstShapeGroup) {
1560                    if ($clientAnchor['length'] > 0) {
1561                        // $this->oCurrentGroup->setOffsetX($clientAnchor['left']);
1562                        // $this->oCurrentGroup->setOffsetY($clientAnchor['top']);
1563                        // $this->oCurrentGroup->setHeight($clientAnchor['height']);
1564                        // $this->oCurrentGroup->setWidth($clientAnchor['width']);
1565                    }
1566                    $bIsGroup = true;
1567                    $this->bFirstShapeGroup = true;
1568                } else {
1569                    if ($childAnchor['length'] > 0) {
1570                        $arrayDimensions = $childAnchor;
1571                    }
1572                }
1573            } else {
1574                if ($clientAnchor['length'] > 0) {
1575                    $arrayDimensions = $clientAnchor;
1576                }
1577            }
1578            if (!$bIsGroup) {
1579                // *** Shape ***
1580                if (isset($shpPrimaryOptions['pib'])) {
1581                    // isDrawing
1582                    $drawingPib = $shpPrimaryOptions['pib'];
1583                    if (isset($this->arrayPictures[$drawingPib - 1])) {
1584                        $gdImage = imagecreatefromstring($this->arrayPictures[$drawingPib - 1]);
1585                        $arrayReturn['shape'] = new Drawing\Gd();
1586                        $arrayReturn['shape']->setImageResource($gdImage);
1587                    }
1588                } elseif (isset($shpPrimaryOptions['line']) && $shpPrimaryOptions['line']) {
1589                    // isLine
1590                    $arrayReturn['shape'] = new Line(0, 0, 0, 0);
1591                } elseif ($clientTextbox['length'] > 0) {
1592                    $arrayReturn['shape'] = new RichText();
1593                    if (isset($clientTextbox['alignH'])) {
1594                        $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setHorizontal($clientTextbox['alignH']);
1595                    }
1596
1597                    $start = 0;
1598                    $lastLevel = -1;
1599                    $lastMarginLeft = 0;
1600                    // @phpstan-ignore-next-line
1601                    for ($inc = 1; $inc <= $clientTextbox['numParts']; ++$inc) {
1602                        if ($clientTextbox['numParts'] == $clientTextbox['numTexts'] && isset($clientTextbox['text' . $inc])) {
1603                            if (isset($clientTextbox['text' . $inc]['bulletChar'])) {
1604                                $arrayReturn['shape']->getActiveParagraph()->getBulletStyle()->setBulletType(Bullet::TYPE_BULLET);
1605                                $arrayReturn['shape']->getActiveParagraph()->getBulletStyle()->setBulletChar($clientTextbox['text' . $inc]['bulletChar']);
1606                            }
1607                            // Indent
1608                            $indent = 0;
1609                            if (isset($clientTextbox['text' . $inc]['indent'])) {
1610                                $indent = $clientTextbox['text' . $inc]['indent'];
1611                            }
1612                            if (isset($clientTextbox['text' . $inc]['leftMargin'])) {
1613                                if ($lastMarginLeft > $clientTextbox['text' . $inc]['leftMargin']) {
1614                                    --$lastLevel;
1615                                }
1616                                if ($lastMarginLeft < $clientTextbox['text' . $inc]['leftMargin']) {
1617                                    ++$lastLevel;
1618                                }
1619                                $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setLevel($lastLevel);
1620                                $lastMarginLeft = $clientTextbox['text' . $inc]['leftMargin'];
1621
1622                                $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setMarginLeft($clientTextbox['text' . $inc]['leftMargin']);
1623                                $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setIndent($indent - $clientTextbox['text' . $inc]['leftMargin']);
1624                            }
1625                        }
1626                        // Texte
1627                        $sText = substr($clientTextbox['text'] ?? '', $start, $clientTextbox['part' . $inc]['partLength']);
1628                        $sHyperlinkURL = '';
1629                        if (empty($sText)) {
1630                            // Is there a hyperlink ?
1631                            if (!empty($clientTextbox['hyperlink'])) {
1632                                foreach ($clientTextbox['hyperlink'] as $itmHyperlink) {
1633                                    if ($itmHyperlink['start'] == $start && ($itmHyperlink['end'] - $itmHyperlink['start']) == (float) $clientTextbox['part' . $inc]['partLength']) {
1634                                        $sText = $this->arrayHyperlinks[$itmHyperlink['id']]['text'];
1635                                        $sHyperlinkURL = $this->arrayHyperlinks[$itmHyperlink['id']]['url'];
1636
1637                                        break;
1638                                    }
1639                                }
1640                            }
1641                        }
1642                        // New paragraph
1643                        $bCreateParagraph = false;
1644                        if (false !== strpos($sText, "\r")) {
1645                            $bCreateParagraph = true;
1646                            $sText = str_replace("\r", '', $sText);
1647                        }
1648                        // TextRun
1649                        $txtRun = $arrayReturn['shape']->createTextRun($sText);
1650                        if (isset($clientTextbox['part' . $inc]['bold'])) {
1651                            $txtRun->getFont()->setBold($clientTextbox['part' . $inc]['bold']);
1652                        }
1653                        if (isset($clientTextbox['part' . $inc]['italic'])) {
1654                            $txtRun->getFont()->setItalic($clientTextbox['part' . $inc]['italic']);
1655                        }
1656                        if (isset($clientTextbox['part' . $inc]['underline'])) {
1657                            $txtRun->getFont()->setUnderline(
1658                                $clientTextbox['part' . $inc]['underline'] ? Font::UNDERLINE_SINGLE : Font::UNDERLINE_NONE
1659                            );
1660                        }
1661                        if (isset($clientTextbox['part' . $inc]['fontName'])) {
1662                            $txtRun->getFont()->setName($clientTextbox['part' . $inc]['fontName']);
1663                        }
1664                        if (isset($clientTextbox['part' . $inc]['fontSize'])) {
1665                            $txtRun->getFont()->setSize($clientTextbox['part' . $inc]['fontSize']);
1666                        }
1667                        if (isset($clientTextbox['part' . $inc]['color'])) {
1668                            $txtRun->getFont()->setColor($clientTextbox['part' . $inc]['color']);
1669                        }
1670                        // Hyperlink
1671                        if (!empty($sHyperlinkURL)) {
1672                            $txtRun->setHyperlink(new Hyperlink($sHyperlinkURL));
1673                        }
1674
1675                        $start += $clientTextbox['part' . $inc]['partLength'];
1676                        if ($bCreateParagraph) {
1677                            $arrayReturn['shape']->createParagraph();
1678                        }
1679                    }
1680                }
1681
1682                // *** Properties ***
1683                // Dimensions
1684                if ($arrayReturn['shape'] instanceof AbstractShape) {
1685                    if (!empty($arrayDimensions)) {
1686                        $arrayReturn['shape']->setOffsetX($arrayDimensions['left']);
1687                        $arrayReturn['shape']->setOffsetY($arrayDimensions['top']);
1688                        $arrayReturn['shape']->setHeight($arrayDimensions['height']);
1689                        $arrayReturn['shape']->setWidth($arrayDimensions['width']);
1690                    }
1691                    // Rotation
1692                    if (isset($shpPrimaryOptions['rotation'])) {
1693                        $rotation = $shpPrimaryOptions['rotation'];
1694                        $arrayReturn['shape']->setRotation($rotation);
1695                    }
1696                    // Shadow
1697                    if (isset($shpPrimaryOptions['shadowOffsetX'], $shpPrimaryOptions['shadowOffsetY'])) {
1698                        $shadowOffsetX = $shpPrimaryOptions['shadowOffsetX'];
1699                        $shadowOffsetY = $shpPrimaryOptions['shadowOffsetY'];
1700                        if (0 != $shadowOffsetX && 0 != $shadowOffsetX) {
1701                            $arrayReturn['shape']->getShadow()->setVisible(true);
1702                            if ($shadowOffsetX > 0 && $shadowOffsetX == $shadowOffsetY) {
1703                                $arrayReturn['shape']->getShadow()->setDistance($shadowOffsetX)->setDirection(45);
1704                            }
1705                        }
1706                    }
1707                    // Specific Line
1708                    if ($arrayReturn['shape'] instanceof Line) {
1709                        if (isset($shpPrimaryOptions['lineColor'])) {
1710                            $arrayReturn['shape']->getBorder()->getColor()->setARGB('FF' . $shpPrimaryOptions['lineColor']);
1711                        }
1712                        if (isset($shpPrimaryOptions['lineWidth'])) {
1713                            $arrayReturn['shape']->setHeight($shpPrimaryOptions['lineWidth']);
1714                        }
1715                    }
1716                    // Specific RichText
1717                    if ($arrayReturn['shape'] instanceof RichText) {
1718                        if (isset($shpPrimaryOptions['insetBottom'])) {
1719                            $arrayReturn['shape']->setInsetBottom($shpPrimaryOptions['insetBottom']);
1720                        }
1721                        if (isset($shpPrimaryOptions['insetLeft'])) {
1722                            $arrayReturn['shape']->setInsetLeft($shpPrimaryOptions['insetLeft']);
1723                        }
1724                        if (isset($shpPrimaryOptions['insetRight'])) {
1725                            $arrayReturn['shape']->setInsetRight($shpPrimaryOptions['insetRight']);
1726                        }
1727                        if (isset($shpPrimaryOptions['insetTop'])) {
1728                            $arrayReturn['shape']->setInsetTop($shpPrimaryOptions['insetTop']);
1729                        }
1730                    }
1731                }
1732            } else {
1733                // Rotation
1734                if (isset($shpPrimaryOptions['rotation'])) {
1735                    $rotation = $shpPrimaryOptions['rotation'];
1736                    $this->oCurrentGroup->setRotation($rotation);
1737                }
1738            }
1739        }
1740
1741        return $arrayReturn;
1742    }
1743
1744    /**
1745     * The OfficeArtSpgrContainer record specifies a container for groups of shapes.
1746     *
1747     * @param string $stream
1748     * @param int $pos
1749     * @param bool $bInGroup
1750     *
1751     * @return array<string, int>
1752     *
1753     * @see : https://msdn.microsoft.com/en-us/library/dd910416(v=office.12).aspx
1754     */
1755    private function readRecordOfficeArtSpgrContainer($stream, $pos, $bInGroup = false)
1756    {
1757        $arrayReturn = [
1758            'length' => 0,
1759        ];
1760
1761        $data = $this->loadRecordHeader($stream, $pos);
1762        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF003 == $data['recType']) {
1763            $arrayReturn['length'] += 8;
1764
1765            do {
1766                $rhFileBlock = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']);
1767                if (!(0xF == $rhFileBlock['recVer'] && 0x0000 == $rhFileBlock['recInstance'] && (0xF003 == $rhFileBlock['recType'] || 0xF004 == $rhFileBlock['recType']))) {
1768                    throw new InvalidFileFormatException($this->filename, self::class);
1769                }
1770
1771                switch ($rhFileBlock['recType']) {
1772                    case 0xF003:
1773                        // Core
1774                        $this->oCurrentGroup = $this->oPhpPresentation->getActiveSlide()->createGroup();
1775                        $this->bFirstShapeGroup = false;
1776                        // OfficeArtSpgrContainer
1777                        $fileBlock = $this->readRecordOfficeArtSpgrContainer($stream, $pos + $arrayReturn['length'], true);
1778                        $arrayReturn['length'] += $fileBlock['length'];
1779                        $data['recLen'] -= $fileBlock['length'];
1780
1781                        break;
1782                    case 0xF004:
1783                        // Core
1784                        if (!$bInGroup) {
1785                            $this->oCurrentGroup = null;
1786                        }
1787                        // OfficeArtSpContainer
1788                        $fileBlock = $this->readRecordOfficeArtSpContainer($stream, $pos + $arrayReturn['length']);
1789                        $arrayReturn['length'] += $fileBlock['length'];
1790                        $data['recLen'] -= $fileBlock['length'];
1791                        // Core
1792                        //@todo
1793                        if (null !== $fileBlock['shape']) {
1794                            switch ($this->inMainType) {
1795                                case self::RT_NOTES:
1796                                    $arrayIdxSlide = array_flip($this->arrayNotes);
1797                                    if ($this->currentNote > 0 && isset($arrayIdxSlide[$this->currentNote])) {
1798                                        $oSlide = $this->oPhpPresentation->getSlide($arrayIdxSlide[$this->currentNote]);
1799                                        if (0 == count($oSlide->getNote()->getShapeCollection())) {
1800                                            $oSlide->getNote()->addShape($fileBlock['shape']);
1801                                        }
1802                                    }
1803
1804                                    break;
1805                                case self::RT_SLIDE:
1806                                    if ($bInGroup) {
1807                                        $this->oCurrentGroup->addShape($fileBlock['shape']);
1808                                    } else {
1809                                        $this->oPhpPresentation->getActiveSlide()->addShape($fileBlock['shape']);
1810                                    }
1811
1812                                    break;
1813                            }
1814                        }
1815
1816                        break;
1817                }
1818            } while ($data['recLen'] > 0);
1819        }
1820
1821        return $arrayReturn;
1822    }
1823
1824    /**
1825     * The OfficeArtTertiaryFOPT record specifies a table of OfficeArtRGFOPTE records,.
1826     *
1827     * @return array<string, int>
1828     *
1829     * @see https://msdn.microsoft.com/en-us/library/dd950206(v=office.12).aspx
1830     */
1831    private function readRecordOfficeArtTertiaryFOPT(string $stream, int $pos)
1832    {
1833        $arrayReturn = [
1834            'length' => 0,
1835        ];
1836
1837        $data = $this->loadRecordHeader($stream, $pos);
1838        if (0x3 == $data['recVer'] && 0xF122 == $data['recType']) {
1839            // Record Header
1840            $arrayReturn['length'] += 8;
1841
1842            $officeArtFOPTE = [];
1843            for ($inc = 0; $inc < $data['recInstance']; ++$inc) {
1844                $opid = self::getInt2d($this->streamPowerpointDocument, $pos + $arrayReturn['length']);
1845                $arrayReturn['length'] += 2;
1846                $optOp = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']);
1847                $arrayReturn['length'] += 4;
1848                $officeArtFOPTE[] = [
1849                    'opid' => ($opid >> 0) & bindec('11111111111111'),
1850                    'fBid' => ($opid >> 14) & bindec('1'),
1851                    'fComplex' => ($opid >> 15) & bindec('1'),
1852                    'op' => $optOp,
1853                ];
1854            }
1855            //@link : http://code.metager.de/source/xref/kde/calligra/filters/libmso/OPID
1856            foreach ($officeArtFOPTE as $opt) {
1857                switch ($opt['opid']) {
1858                    case 0x039F:
1859                        // Table properties
1860                        //@link : https://msdn.microsoft.com/en-us/library/dd922773(v=office.12).aspx
1861                        break;
1862                    case 0x03A0:
1863                        // Table Row Properties
1864                        //@link : https://msdn.microsoft.com/en-us/library/dd923419(v=office.12).aspx
1865                        if (0x1 == $opt['fComplex']) {
1866                            $arrayReturn['length'] += $opt['op'];
1867                        }
1868
1869                        break;
1870                    case 0x03A9:
1871                        // GroupShape : metroBlob
1872                        //@link : https://msdn.microsoft.com/en-us/library/dd943388(v=office.12).aspx
1873                        if (0x1 == $opt['fComplex']) {
1874                            $arrayReturn['length'] += $opt['op'];
1875                        }
1876
1877                        break;
1878                    case 0x01FF:
1879                        // Line Style Boolean
1880                        //@link : https://msdn.microsoft.com/en-us/library/dd951605(v=office.12).aspx
1881                        break;
1882                    default:
1883                        // var_dump('0x' . dechex($opt['opid']));
1884                        throw new FeatureNotImplementedException();
1885                }
1886            }
1887        }
1888
1889        return $arrayReturn;
1890    }
1891
1892    /**
1893     * The OfficeArtDgContainer record specifies the container for all the file records for the objects in a drawing.
1894     *
1895     * @return array<string, int>
1896     *
1897     * @see : https://msdn.microsoft.com/en-us/library/dd924455(v=office.12).aspx
1898     */
1899    private function readRecordOfficeArtDgContainer(string $stream, int $pos): array
1900    {
1901        $arrayReturn = [
1902            'length' => 0,
1903        ];
1904
1905        $data = $this->loadRecordHeader($stream, $pos);
1906        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF002 == $data['recType']) {
1907            // Record Header
1908            $arrayReturn['length'] += 8;
1909            // drawingData
1910            $drawingData = $this->readRecordOfficeArtFDG($stream, $pos + $arrayReturn['length']);
1911            $arrayReturn['length'] += $drawingData['length'];
1912            // regroupItems
1913            //@todo
1914            // groupShape
1915            $groupShape = $this->readRecordOfficeArtSpgrContainer($stream, $pos + $arrayReturn['length']);
1916            $arrayReturn['length'] += $groupShape['length'];
1917            // shape
1918            $shape = $this->readRecordOfficeArtSpContainer($stream, $pos + $arrayReturn['length']);
1919            $arrayReturn['length'] += $shape['length'];
1920            // solvers1
1921            //@todo
1922            // deletedShapes
1923            //@todo
1924            // solvers1
1925            //@todo
1926        }
1927
1928        return $arrayReturn;
1929    }
1930
1931    /**
1932     * The OfficeArtFDG record specifies the number of shapes, the drawing identifier, and the shape identifier of the last shape in a drawing.
1933     *
1934     * @return array<string, int>
1935     *
1936     * @see : https://msdn.microsoft.com/en-us/library/dd946757(v=office.12).aspx
1937     */
1938    private function readRecordOfficeArtFDG(string $stream, int $pos): array
1939    {
1940        $arrayReturn = [
1941            'length' => 0,
1942        ];
1943
1944        $data = $this->loadRecordHeader($stream, $pos);
1945        if (0x0 == $data['recVer'] && $data['recInstance'] <= 0xFFE && 0xF008 == $data['recType'] && 0x00000008 == $data['recLen']) {
1946            // Record Header
1947            $arrayReturn['length'] += 8;
1948            // Length
1949            $arrayReturn['length'] += $data['recLen'];
1950        }
1951
1952        return $arrayReturn;
1953    }
1954
1955    /**
1956     * The OfficeArtFOPT record specifies a table of OfficeArtRGFOPTE records.
1957     *
1958     * @return array<string, bool|int|string>
1959     *
1960     * @see https://msdn.microsoft.com/en-us/library/dd943404(v=office.12).aspx
1961     */
1962    private function readRecordOfficeArtFOPT(string $stream, int $pos): array
1963    {
1964        $arrayReturn = [
1965            'length' => 0,
1966        ];
1967
1968        $data = $this->loadRecordHeader($stream, $pos);
1969        if (0x3 == $data['recVer'] && 0xF00B == $data['recType']) {
1970            // Record Header
1971            $arrayReturn['length'] += 8;
1972
1973            //@link : http://msdn.microsoft.com/en-us/library/dd906086(v=office.12).aspx
1974            $officeArtFOPTE = [];
1975            for ($inc = 0; $inc < $data['recInstance']; ++$inc) {
1976                $opid = self::getInt2d($this->streamPowerpointDocument, $pos + $arrayReturn['length']);
1977                $arrayReturn['length'] += 2;
1978                $data['recLen'] -= 2;
1979                $optOp = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']);
1980                $arrayReturn['length'] += 4;
1981                $data['recLen'] -= 4;
1982                $officeArtFOPTE[] = [
1983                    'opid' => ($opid >> 0) & bindec('11111111111111'),
1984                    'fBid' => ($opid >> 14) & bindec('1'),
1985                    'fComplex' => ($opid >> 15) & bindec('1'),
1986                    'op' => $optOp,
1987                ];
1988            }
1989            //@link : http://code.metager.de/source/xref/kde/calligra/filters/libmso/OPID
1990            foreach ($officeArtFOPTE as $opt) {
1991                // echo $opt['opid'].'-0x'.dechex($opt['opid']).EOL;
1992                switch ($opt['opid']) {
1993                    case 0x0004:
1994                        // Transform : rotation
1995                        //@link : https://msdn.microsoft.com/en-us/library/dd949750(v=office.12).aspx
1996                        $arrayReturn['rotation'] = $opt['op'];
1997
1998                        break;
1999                    case 0x007F:
2000                        // Transform : Protection Boolean Properties
2001                        //@link : http://msdn.microsoft.com/en-us/library/dd909131(v=office.12).aspx
2002                        break;
2003                    case 0x0080:
2004                        // Text : ltxid
2005                        //@link : http://msdn.microsoft.com/en-us/library/dd947446(v=office.12).aspx
2006                        break;
2007                    case 0x0081:
2008                        // Text : dxTextLeft
2009                        //@link : http://msdn.microsoft.com/en-us/library/dd953234(v=office.12).aspx
2010                        $arrayReturn['insetLeft'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);
2011
2012                        break;
2013                    case 0x0082:
2014                        // Text : dyTextTop
2015                        //@link : http://msdn.microsoft.com/en-us/library/dd925068(v=office.12).aspx
2016                        $arrayReturn['insetTop'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);
2017
2018                        break;
2019                    case 0x0083:
2020                        // Text : dxTextRight
2021                        //@link : http://msdn.microsoft.com/en-us/library/dd906782(v=office.12).aspx
2022                        $arrayReturn['insetRight'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);
2023
2024                        break;
2025                    case 0x0084:
2026                        // Text : dyTextBottom
2027                        //@link : http://msdn.microsoft.com/en-us/library/dd772858(v=office.12).aspx
2028                        $arrayReturn['insetBottom'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);
2029
2030                        break;
2031                    case 0x0085:
2032                        // Text : WrapText
2033                        //@link : http://msdn.microsoft.com/en-us/library/dd924770(v=office.12).aspx
2034                        break;
2035                    case 0x0087:
2036                        // Text : anchorText
2037                        //@link : http://msdn.microsoft.com/en-us/library/dd948575(v=office.12).aspx
2038                        break;
2039                    case 0x00BF:
2040                        // Text : Text Boolean Properties
2041                        //@link : http://msdn.microsoft.com/en-us/library/dd950905(v=office.12).aspx
2042                        break;
2043                    case 0x0104:
2044                        // Blip : pib
2045                        //@link : http://msdn.microsoft.com/en-us/library/dd772837(v=office.12).aspx
2046                        if (0 == $opt['fComplex']) {
2047                            $arrayReturn['pib'] = $opt['op'];
2048                            $data['recLen'] -= $opt['op'];
2049                        }
2050                        // pib Complex
2051
2052                        break;
2053                    case 0x13F:
2054                        // Blip Boolean Properties
2055                        //@link : https://msdn.microsoft.com/en-us/library/dd944215(v=office.12).aspx
2056                        break;
2057                    case 0x140:
2058                        // Geometry : geoLeft
2059                        //@link : http://msdn.microsoft.com/en-us/library/dd947489(v=office.12).aspx
2060                        // print_r('geoLeft : '.$opt['op'].EOL);
2061                        break;
2062                    case 0x141:
2063                        // Geometry : geoTop
2064                        //@link : http://msdn.microsoft.com/en-us/library/dd949459(v=office.12).aspx
2065                        // print_r('geoTop : '.$opt['op'].EOL);
2066                        break;
2067                    case 0x142:
2068                        // Geometry : geoRight
2069                        //@link : http://msdn.microsoft.com/en-us/library/dd947117(v=office.12).aspx
2070                        // print_r('geoRight : '.$opt['op'].EOL);
2071                        break;
2072                    case 0x143:
2073                        // Geometry : geoBottom
2074                        //@link : http://msdn.microsoft.com/en-us/library/dd948602(v=office.12).aspx
2075                        // print_r('geoBottom : '.$opt['op'].EOL);
2076                        break;
2077                    case 0x144:
2078                        // Geometry : shapePath
2079                        //@link : http://msdn.microsoft.com/en-us/library/dd945249(v=office.12).aspx
2080                        $arrayReturn['line'] = true;
2081
2082                        break;
2083                    case 0x145:
2084                        // Geometry : pVertices
2085                        //@link : http://msdn.microsoft.com/en-us/library/dd949814(v=office.12).aspx
2086                        if (1 == $opt['fComplex']) {
2087                            $arrayReturn['length'] += $opt['op'];
2088                            $data['recLen'] -= $opt['op'];
2089                        }
2090
2091                        break;
2092                    case 0x146:
2093                        // Geometry : pSegmentInfo
2094                        //@link : http://msdn.microsoft.com/en-us/library/dd905742(v=office.12).aspx
2095                        if (1 == $opt['fComplex']) {
2096                            $arrayReturn['length'] += $opt['op'];
2097                            $data['recLen'] -= $opt['op'];
2098                        }
2099
2100                        break;
2101                    case 0x155:
2102                        // Geometry : pAdjustHandles
2103                        //@link : http://msdn.microsoft.com/en-us/library/dd905890(v=office.12).aspx
2104                        if (1 == $opt['fComplex']) {
2105                            $arrayReturn['length'] += $opt['op'];
2106                            $data['recLen'] -= $opt['op'];
2107                        }
2108
2109                        break;
2110                    case 0x156:
2111                        // Geometry : pGuides
2112                        //@link : http://msdn.microsoft.com/en-us/library/dd910801(v=office.12).aspx
2113                        if (1 == $opt['fComplex']) {
2114                            $arrayReturn['length'] += $opt['op'];
2115                            $data['recLen'] -= $opt['op'];
2116                        }
2117
2118                        break;
2119                    case 0x157:
2120                        // Geometry : pInscribe
2121                        //@link : http://msdn.microsoft.com/en-us/library/dd904889(v=office.12).aspx
2122                        if (1 == $opt['fComplex']) {
2123                            $arrayReturn['length'] += $opt['op'];
2124                            $data['recLen'] -= $opt['op'];
2125                        }
2126
2127                        break;
2128                    case 0x17F:
2129                        // Geometry Boolean Properties
2130                        //@link : http://msdn.microsoft.com/en-us/library/dd944968(v=office.12).aspx
2131                        break;
2132                    case 0x0180:
2133                        // Fill : fillType
2134                        //@link : http://msdn.microsoft.com/en-us/library/dd947909(v=office.12).aspx
2135                        break;
2136                    case 0x0181:
2137                        // Fill : fillColor
2138                        //@link : http://msdn.microsoft.com/en-us/library/dd921332(v=office.12).aspx
2139                        $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
2140                        $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
2141                        $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
2142
2143                        // echo 'fillColor  : '.$strColor.EOL;
2144                        break;
2145                    case 0x0183:
2146                        // Fill : fillBackColor
2147                        //@link : http://msdn.microsoft.com/en-us/library/dd950634(v=office.12).aspx
2148                        $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
2149                        $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
2150                        $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
2151
2152                        // echo 'fillBackColor  : '.$strColor.EOL;
2153                        break;
2154                    case 0x0193:
2155                        // Fill : fillRectRight
2156                        //@link : http://msdn.microsoft.com/en-us/library/dd951294(v=office.12).aspx
2157                        // echo 'fillRectRight  : '.\PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']).EOL;
2158                        break;
2159                    case 0x0194:
2160                        // Fill : fillRectBottom
2161                        //@link : http://msdn.microsoft.com/en-us/library/dd910194(v=office.12).aspx
2162                        // echo 'fillRectBottom   : '.\PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']).EOL;
2163                        break;
2164                    case 0x01BF:
2165                        // Fill : Fill Style Boolean Properties
2166                        //@link : http://msdn.microsoft.com/en-us/library/dd909380(v=office.12).aspx
2167                        break;
2168                    case 0x01C0:
2169                        // Line Style : lineColor
2170                        //@link : http://msdn.microsoft.com/en-us/library/dd920397(v=office.12).aspx
2171                        $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
2172                        $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
2173                        $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, '0', STR_PAD_LEFT);
2174                        $arrayReturn['lineColor'] = $strColor;
2175
2176                        break;
2177                    case 0x01C1:
2178                        // Line Style : lineOpacity
2179                        //@link : http://msdn.microsoft.com/en-us/library/dd923433(v=office.12).aspx
2180                        // echo 'lineOpacity : '.dechex($opt['op']).EOL;
2181                        break;
2182                    case 0x01C2:
2183                        // Line Style : lineBackColor
2184                        //@link : http://msdn.microsoft.com/en-us/library/dd947669(v=office.12).aspx
2185                        break;
2186                    case 0x01CB:
2187                        // Line Style : lineWidth
2188                        //@link : http://msdn.microsoft.com/en-us/library/dd926964(v=office.12).aspx
2189                        $arrayReturn['lineWidth'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);
2190
2191                        break;
2192                    case 0x01D6:
2193                        // Line Style : lineJoinStyle
2194                        //@link : http://msdn.microsoft.com/en-us/library/dd909643(v=office.12).aspx
2195                        break;
2196                    case 0x01D7:
2197                        // Line Style : lineEndCapStyle
2198                        //@link : http://msdn.microsoft.com/en-us/library/dd925071(v=office.12).aspx
2199                        break;
2200                    case 0x01FF:
2201                        // Line Style : Line Style Boolean Properties
2202                        //@link : http://msdn.microsoft.com/en-us/library/dd951605(v=office.12).aspx
2203                        break;
2204                    case 0x0201:
2205                        // Shadow Style : shadowColor
2206                        //@link : http://msdn.microsoft.com/en-us/library/dd923454(v=office.12).aspx
2207                        break;
2208                    case 0x0204:
2209                        // Shadow Style : shadowOpacity
2210                        //@link : http://msdn.microsoft.com/en-us/library/dd920720(v=office.12).aspx
2211                        break;
2212                    case 0x0205:
2213                        // Shadow Style : shadowOffsetX
2214                        //@link : http://msdn.microsoft.com/en-us/library/dd945280(v=office.12).aspx
2215                        $arrayReturn['shadowOffsetX'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);
2216
2217                        break;
2218                    case 0x0206:
2219                        // Shadow Style : shadowOffsetY
2220                        //@link : http://msdn.microsoft.com/en-us/library/dd907855(v=office.12).aspx
2221                        $arrayReturn['shadowOffsetY'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']);
2222
2223                        break;
2224                    case 0x023F:
2225                        // Shadow Style : Shadow Style Boolean Properties
2226                        //@link : http://msdn.microsoft.com/en-us/library/dd947887(v=office.12).aspx
2227                        break;
2228                    case 0x0304:
2229                        // Shape : bWMode
2230                        //@link : http://msdn.microsoft.com/en-us/library/dd947659(v=office.12).aspx
2231                        break;
2232                    case 0x033F:
2233                        // Shape Boolean Properties
2234                        //@link : http://msdn.microsoft.com/en-us/library/dd951345(v=office.12).aspx
2235                        break;
2236                    case 0x0380:
2237                        // Group Shape Property Set : wzName
2238                        //@link : http://msdn.microsoft.com/en-us/library/dd950681(v=office.12).aspx
2239                        if (1 == $opt['fComplex']) {
2240                            $arrayReturn['length'] += $opt['op'];
2241                            $data['recLen'] -= $opt['op'];
2242                        }
2243
2244                        break;
2245                    case 0x03BF:
2246                        // Group Shape Property Set : Group Shape Boolean Properties
2247                        //@link : http://msdn.microsoft.com/en-us/library/dd949807(v=office.12).aspx
2248                        break;
2249                    default:
2250                }
2251            }
2252            if ($data['recLen'] > 0) {
2253                $arrayReturn['length'] += $data['recLen'];
2254            }
2255        }
2256
2257        return $arrayReturn;
2258    }
2259
2260    /**
2261     * The OfficeArtFPSPL record specifies the former hierarchical position of the containing object that is either a shape or a group of shapes.
2262     *
2263     * @return array<string, int>
2264     *
2265     * @see https://msdn.microsoft.com/en-us/library/dd947479(v=office.12).aspx
2266     */
2267    private function readRecordOfficeArtFPSPL(string $stream, int $pos): array
2268    {
2269        $arrayReturn = [
2270            'length' => 0,
2271        ];
2272
2273        $data = $this->loadRecordHeader($stream, $pos);
2274        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF11D == $data['recType'] && 0x00000004 == $data['recLen']) {
2275            $arrayReturn['length'] += 8;
2276            $arrayReturn['length'] += $data['recLen'];
2277        }
2278
2279        return $arrayReturn;
2280    }
2281
2282    /**
2283     * The OfficeArtFSP record specifies an instance of a shape.
2284     *
2285     * @return array<string, int>
2286     *
2287     * @see https://msdn.microsoft.com/en-us/library/dd925898(v=office.12).aspx
2288     */
2289    private function readRecordOfficeArtFSP(string $stream, int $pos): array
2290    {
2291        $arrayReturn = [
2292            'length' => 0,
2293        ];
2294
2295        $data = $this->loadRecordHeader($stream, $pos);
2296        if (0x2 == $data['recVer'] && 0xF00A == $data['recType'] && 0x00000008 == $data['recLen']) {
2297            $arrayReturn['length'] += 8;
2298            // spid
2299            $arrayReturn['length'] += 4;
2300            // data
2301            $data = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']);
2302            $arrayReturn['length'] += 4;
2303            $arrayReturn['fGroup'] = ($data >> 0) & bindec('1');
2304            $arrayReturn['fChild'] = ($data >> 1) & bindec('1');
2305            $arrayReturn['fPatriarch'] = ($data >> 2) & bindec('1');
2306            $arrayReturn['fDeleted'] = ($data >> 3) & bindec('1');
2307        }
2308
2309        return $arrayReturn;
2310    }
2311
2312    /**
2313     * The OfficeArtFSPGR record specifies the coordinate system of the group shape that the anchors of the child shape are expressed in.
2314     *
2315     * @return array<string, int>
2316     *
2317     * @see https://msdn.microsoft.com/en-us/library/dd925381(v=office.12).aspx
2318     */
2319    private function readRecordOfficeArtFSPGR(string $stream, int $pos): array
2320    {
2321        $arrayReturn = [
2322            'length' => 0,
2323        ];
2324
2325        $data = $this->loadRecordHeader($stream, $pos);
2326        if (0x1 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF009 == $data['recType'] && 0x00000010 == $data['recLen']) {
2327            $arrayReturn['length'] += 8;
2328            //$arrShapeGroup['xLeft'] = self::getInt4d($this->streamPowerpointDocument, $pos);
2329            $arrayReturn['length'] += 4;
2330            //$arrShapeGroup['yTop'] = self::getInt4d($this->streamPowerpointDocument, $pos);
2331            $arrayReturn['length'] += 4;
2332            //$arrShapeGroup['xRight'] = self::getInt4d($this->streamPowerpointDocument, $pos);
2333            $arrayReturn['length'] += 4;
2334            //$arrShapeGroup['yBottom'] = self::getInt4d($this->streamPowerpointDocument, $pos);
2335            $arrayReturn['length'] += 4;
2336        }
2337
2338        return $arrayReturn;
2339    }
2340
2341    /**
2342     * The OfficeArtSecondaryFOPT record specifies a table of OfficeArtRGFOPTE records.
2343     *
2344     * @return array<string, int>
2345     *
2346     * @see https://msdn.microsoft.com/en-us/library/dd950259(v=office.12).aspx
2347     */
2348    private function readRecordOfficeArtSecondaryFOPT(string $stream, int $pos): array
2349    {
2350        $arrayReturn = [
2351            'length' => 0,
2352        ];
2353
2354        $data = $this->loadRecordHeader($stream, $pos);
2355        if (0x3 == $data['recVer'] && 0xF121 == $data['recType']) {
2356            // Record Header
2357            $arrayReturn['length'] += 8;
2358            // Length
2359            $arrayReturn['length'] += $data['recLen'];
2360        }
2361
2362        return $arrayReturn;
2363    }
2364
2365    /**
2366     * A container record that specifies information about a shape.
2367     *
2368     * @return array<string, int>
2369     *
2370     * @see : https://msdn.microsoft.com/en-us/library/dd950927(v=office.12).aspx
2371     */
2372    private function readRecordOfficeArtClientData(string $stream, int $pos): array
2373    {
2374        $arrayReturn = [
2375            'length' => 0,
2376        ];
2377
2378        $data = $this->loadRecordHeader($stream, $pos);
2379        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF011 == $data['recType']) {
2380            $arrayReturn['length'] += 8;
2381            // shapeFlagsAtom (9 bytes)
2382            $dataShapeFlagsAtom = $this->readRecordShapeFlagsAtom($stream, $pos + $arrayReturn['length']);
2383            $arrayReturn['length'] += $dataShapeFlagsAtom['length'];
2384
2385            // shapeFlags10Atom (9 bytes)
2386            $dataShapeFlags10Atom = $this->readRecordShapeFlags10Atom($stream, $pos + $arrayReturn['length']);
2387            $arrayReturn['length'] += $dataShapeFlags10Atom['length'];
2388
2389            // exObjRefAtom (12 bytes)
2390            $dataExObjRefAtom = $this->readRecordExObjRefAtom($stream, $pos + $arrayReturn['length']);
2391            $arrayReturn['length'] += $dataExObjRefAtom['length'];
2392
2393            // animationInfo (variable)
2394            $dataAnimationInfo = $this->readRecordAnimationInfoContainer($stream, $pos + $arrayReturn['length']);
2395            $arrayReturn['length'] += $dataAnimationInfo['length'];
2396
2397            // mouseClickInteractiveInfo (variable)
2398            $mouseClickInfo = $this->readRecordMouseClickInteractiveInfoContainer($stream, $pos + $arrayReturn['length']);
2399            $arrayReturn['length'] += $mouseClickInfo['length'];
2400
2401            // mouseOverInteractiveInfo (variable)
2402            $mouseOverInfo = $this->readRecordMouseOverInteractiveInfoContainer($stream, $pos + $arrayReturn['length']);
2403            $arrayReturn['length'] += $mouseOverInfo['length'];
2404
2405            // placeholderAtom (16 bytes)
2406            $dataPlaceholderAtom = $this->readRecordPlaceholderAtom($stream, $pos + $arrayReturn['length']);
2407            $arrayReturn['length'] += $dataPlaceholderAtom['length'];
2408
2409            // recolorInfoAtom (variable)
2410            $dataRecolorInfo = $this->readRecordRecolorInfoAtom($stream, $pos + $arrayReturn['length']);
2411            $arrayReturn['length'] += $dataRecolorInfo['length'];
2412
2413            // rgShapeClientRoundtripData (variable)
2414            $array = [
2415                self::RT_PROGTAGS,
2416                self::RT_ROUNDTRIPNEWPLACEHOLDERID12ATOM,
2417                self::RT_ROUNDTRIPSHAPEID12ATOM,
2418                self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM,
2419                self::RT_ROUNDTRIPSHAPECHECKSUMFORCL12ATOM,
2420            ];
2421            do {
2422                $dataHeaderRG = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']);
2423                if (in_array($dataHeaderRG['recType'], $array)) {
2424                    switch ($dataHeaderRG['recType']) {
2425                        case self::RT_PROGTAGS:
2426                            $dataRG = $this->readRecordShapeProgTagsContainer($stream, $pos + $arrayReturn['length']);
2427                            $arrayReturn['length'] += $dataRG['length'];
2428
2429                            break;
2430                        case self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM:
2431                            $dataRG = $this->readRecordRoundTripHFPlaceholder12Atom($stream, $pos + $arrayReturn['length']);
2432                            $arrayReturn['length'] += $dataRG['length'];
2433
2434                            break;
2435                        case self::RT_ROUNDTRIPSHAPEID12ATOM:
2436                            $dataRG = $this->readRecordRoundTripShapeId12Atom($stream, $pos + $arrayReturn['length']);
2437                            $arrayReturn['length'] += $dataRG['length'];
2438
2439                            break;
2440                        default:
2441                            // var_dump('0x' . dechex($dataHeaderRG['recType']));
2442                            throw new FeatureNotImplementedException();
2443                    }
2444                }
2445            } while (in_array($dataHeaderRG['recType'], $array));
2446        }
2447
2448        return $arrayReturn;
2449    }
2450
2451    /**
2452     * An atom record that specifies a persist object directory. Each persist object identifier specified MUST be unique in that persist object directory.
2453     *
2454     * @see http://msdn.microsoft.com/en-us/library/dd952680(v=office.12).aspx
2455     */
2456    private function readRecordPersistDirectoryAtom(string $stream, int $pos): void
2457    {
2458        $rHeader = $this->loadRecordHeader($stream, $pos);
2459        $pos += 8;
2460        if (0x0 != $rHeader['recVer'] || 0x000 != $rHeader['recInstance'] || self::RT_PERSISTDIRECTORYATOM != $rHeader['recType']) {
2461            throw new InvalidFileFormatException($this->filename, self::class, 'Location : PersistDirectoryAtom > RecordHeader');
2462        }
2463        // rgPersistDirEntry
2464        // @link : http://msdn.microsoft.com/en-us/library/dd947347(v=office.12).aspx
2465        do {
2466            $data = self::getInt4d($stream, $pos);
2467            $pos += 4;
2468            $rHeader['recLen'] -= 4;
2469            //$persistId  = ($data >> 0) & bindec('11111111111111111111');
2470            $cPersist = ($data >> 20) & bindec('111111111111');
2471
2472            $rgPersistOffset = [];
2473            for ($inc = 0; $inc < $cPersist; ++$inc) {
2474                $rgPersistOffset[] = self::getInt4d($stream, $pos);
2475                $pos += 4;
2476                $rHeader['recLen'] -= 4;
2477            }
2478        } while ($rHeader['recLen'] > 0);
2479        $this->rgPersistDirEntry = $rgPersistOffset;
2480    }
2481
2482    /**
2483     * A container record that specifies information about the headers (1) and footers within a slide.
2484     *
2485     * @see https://msdn.microsoft.com/en-us/library/dd904856(v=office.12).aspx
2486     *
2487     * @return array<string, int>
2488     */
2489    private function readRecordPerSlideHeadersFootersContainer(string $stream, int $pos): array
2490    {
2491        $arrayReturn = [
2492            'length' => 0,
2493        ];
2494
2495        $data = $this->loadRecordHeader($stream, $pos);
2496        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_HEADERSFOOTERS == $data['recType']) {
2497            // Record Header
2498            $arrayReturn['length'] += 8;
2499            // Length
2500            $arrayReturn['length'] += $data['recLen'];
2501        }
2502
2503        return $arrayReturn;
2504    }
2505
2506    /**
2507     * An atom record that specifies whether a shape is a placeholder shape.
2508     *
2509     * @see https://msdn.microsoft.com/en-us/library/dd923930(v=office.12).aspx
2510     *
2511     * @return array<string, int>
2512     */
2513    private function readRecordPlaceholderAtom(string $stream, int $pos): array
2514    {
2515        $arrayReturn = [
2516            'length' => 0,
2517        ];
2518
2519        $data = $this->loadRecordHeader($stream, $pos);
2520        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PLACEHOLDERATOM == $data['recType'] && 0x00000008 == $data['recLen']) {
2521            // Record Header
2522            $arrayReturn['length'] += 8;
2523            // Datas
2524            $arrayReturn['length'] += $data['recLen'];
2525        }
2526
2527        return $arrayReturn;
2528    }
2529
2530    /**
2531     * An atom record that specifies a collection of re-color mappings for a metafile ([MS-WMF]).
2532     *
2533     * @see https://msdn.microsoft.com/en-us/library/dd904899(v=office.12).aspx
2534     *
2535     * @return array<string, int>
2536     */
2537    private function readRecordRecolorInfoAtom(string $stream, int $pos): array
2538    {
2539        $arrayReturn = [
2540            'length' => 0,
2541        ];
2542
2543        $data = $this->loadRecordHeader($stream, $pos);
2544        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_RECOLORINFOATOM == $data['recType']) {
2545            // Record Header
2546            $arrayReturn['length'] += 8;
2547            // Datas
2548            $arrayReturn['length'] += $data['recLen'];
2549        }
2550
2551        return $arrayReturn;
2552    }
2553
2554    /**
2555     * An atom record that specifies that a shape is a header or footerplaceholder shape.
2556     *
2557     * @see https://msdn.microsoft.com/en-us/library/dd910800(v=office.12).aspx
2558     *
2559     * @return array<string, int>
2560     */
2561    private function readRecordRoundTripHFPlaceholder12Atom(string $stream, int $pos): array
2562    {
2563        $arrayReturn = [
2564            'length' => 0,
2565        ];
2566
2567        $data = $this->loadRecordHeader($stream, $pos);
2568        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM == $data['recType'] && 0x00000001 == $data['recLen']) {
2569            // Record Header
2570            $arrayReturn['length'] += 8;
2571            // Datas
2572            $arrayReturn['length'] += $data['recLen'];
2573        }
2574
2575        return $arrayReturn;
2576    }
2577
2578    /**
2579     * An atom record that specifies a shape identifier.
2580     *
2581     * @see https://msdn.microsoft.com/en-us/library/dd772926(v=office.12).aspx
2582     *
2583     * @return array<string, int>
2584     */
2585    private function readRecordRoundTripShapeId12Atom(string $stream, int $pos): array
2586    {
2587        $arrayReturn = [
2588            'length' => 0,
2589        ];
2590
2591        $data = $this->loadRecordHeader($stream, $pos);
2592        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ROUNDTRIPSHAPEID12ATOM == $data['recType'] && 0x00000004 == $data['recLen']) {
2593            // Record Header
2594            $arrayReturn['length'] += 8;
2595            // Length
2596            $arrayReturn['length'] += $data['recLen'];
2597        }
2598
2599        return $arrayReturn;
2600    }
2601
2602    /**
2603     * A container record that specifies information about a slide that synchronizes to a slide in a slide library.
2604     *
2605     * @see https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx
2606     *
2607     * @return array<string, int>
2608     */
2609    private function readRecordRoundTripSlideSyncInfo12Container(string $stream, int $pos): array
2610    {
2611        $arrayReturn = [
2612            'length' => 0,
2613        ];
2614
2615        $data = $this->loadRecordHeader($stream, $pos);
2616        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ROUNDTRIPSLIDESYNCINFO12 == $data['recType']) {
2617            // Record Header
2618            $arrayReturn['length'] += 8;
2619            // Length
2620            $arrayReturn['length'] += $data['recLen'];
2621        }
2622
2623        return $arrayReturn;
2624    }
2625
2626    /**
2627     * An atom record that specifies shape-level Boolean flags.
2628     *
2629     * @see https://msdn.microsoft.com/en-us/library/dd908949(v=office.12).aspx
2630     *
2631     * @return array<string, int>
2632     */
2633    private function readRecordShapeFlags10Atom(string $stream, int $pos): array
2634    {
2635        $arrayReturn = [
2636            'length' => 0,
2637        ];
2638
2639        $data = $this->loadRecordHeader($stream, $pos);
2640        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SHAPEFLAGS10ATOM == $data['recType'] && 0x00000001 == $data['recLen']) {
2641            // Record Header
2642            $arrayReturn['length'] += 8;
2643            // Datas
2644            $arrayReturn['length'] += $data['recLen'];
2645        }
2646
2647        return $arrayReturn;
2648    }
2649
2650    /**
2651     * An atom record that specifies shape-level Boolean flags.
2652     *
2653     * @see https://msdn.microsoft.com/en-us/library/dd925824(v=office.12).aspx
2654     *
2655     * @return array<string, int>
2656     */
2657    private function readRecordShapeFlagsAtom(string $stream, int $pos): array
2658    {
2659        $arrayReturn = [
2660            'length' => 0,
2661        ];
2662
2663        $data = $this->loadRecordHeader($stream, $pos);
2664        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SHAPEATOM == $data['recType'] && 0x00000001 == $data['recLen']) {
2665            // Record Header
2666            $arrayReturn['length'] += 8;
2667            // Datas
2668            $arrayReturn['length'] += $data['recLen'];
2669        }
2670
2671        return $arrayReturn;
2672    }
2673
2674    /**
2675     * A container record that specifies programmable tags with additional binary shape data.
2676     *
2677     * @see https://msdn.microsoft.com/en-us/library/dd911033(v=office.12).aspx
2678     *
2679     * @return array<string, int>
2680     */
2681    private function readRecordShapeProgBinaryTagContainer(string $stream, int $pos): array
2682    {
2683        $arrayReturn = [
2684            'length' => 0,
2685        ];
2686
2687        $data = $this->loadRecordHeader($stream, $pos);
2688        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PROGBINARYTAG == $data['recType']) {
2689            // Record Header
2690            $arrayReturn['length'] += 8;
2691            // Datas
2692            $arrayReturn['length'] += $data['recLen'];
2693        }
2694
2695        return $arrayReturn;
2696    }
2697
2698    /**
2699     * A container record that specifies programmable tags with additional shape data.
2700     *
2701     * @return array<string, int>
2702     *
2703     * @see https://msdn.microsoft.com/en-us/library/dd911266(v=office.12).aspx
2704     */
2705    private function readRecordShapeProgTagsContainer(string $stream, int $pos): array
2706    {
2707        $arrayReturn = [
2708            'length' => 0,
2709        ];
2710
2711        $data = $this->loadRecordHeader($stream, $pos);
2712        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PROGTAGS == $data['recType']) {
2713            // Record Header
2714            $arrayReturn['length'] += 8;
2715
2716            $length = 0;
2717            do {
2718                $dataHeaderRG = $this->loadRecordHeader($stream, $pos + $arrayReturn['length'] + $length);
2719                switch ($dataHeaderRG['recType']) {
2720                    case self::RT_PROGBINARYTAG:
2721                        $dataRG = $this->readRecordShapeProgBinaryTagContainer($stream, $pos + $arrayReturn['length'] + $length);
2722                        $length += $dataRG['length'];
2723
2724                        break;
2725                        //case self::RT_PROGSTRINGTAG:
2726                    default:
2727                        // var_dump('0x' . dechex($dataHeaderRG['recType']));
2728                        throw new FeatureNotImplementedException();
2729                }
2730            } while ($length < $data['recLen']);
2731            // Datas
2732            $arrayReturn['length'] += $data['recLen'];
2733        }
2734
2735        return $arrayReturn;
2736    }
2737
2738    /**
2739     * An atom record that specifies information about a slide.
2740     *
2741     * @return array<string, int>
2742     *
2743     * @see https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx
2744     */
2745    private function readRecordSlideAtom(string $stream, int $pos): array
2746    {
2747        $arrayReturn = [
2748            'length' => 0,
2749        ];
2750
2751        $data = $this->loadRecordHeader($stream, $pos);
2752        if (0x2 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SLIDEATOM == $data['recType']) {
2753            // Record Header
2754            $arrayReturn['length'] += 8;
2755            // slideAtom > geom
2756            $arrayReturn['length'] += 4;
2757            // slideAtom > rgPlaceholderTypes
2758            $rgPlaceholderTypes = [];
2759            for ($inc = 0; $inc < 8; ++$inc) {
2760                $rgPlaceholderTypes[] = self::getInt1d($this->streamPowerpointDocument, $pos);
2761                ++$arrayReturn['length'];
2762            }
2763
2764            // slideAtom > masterIdRef
2765            $arrayReturn['length'] += 4;
2766            // slideAtom > notesIdRef
2767            $arrayReturn['length'] += 4;
2768            // slideAtom > slideFlags
2769            $arrayReturn['length'] += 2;
2770            // slideAtom > unused;
2771            $arrayReturn['length'] += 2;
2772        }
2773
2774        return $arrayReturn;
2775    }
2776
2777    /**
2778     * A container record that specifies a presentation slide or title master slide.
2779     *
2780     * @see http://msdn.microsoft.com/en-us/library/dd946323(v=office.12).aspx
2781     */
2782    private function readRecordSlideContainer(string $stream, int $pos): void
2783    {
2784        // Core
2785        $this->oPhpPresentation->createSlide();
2786        $this->oPhpPresentation->setActiveSlideIndex($this->oPhpPresentation->getSlideCount() - 1);
2787
2788        // *** slideAtom (32 bytes)
2789        $slideAtom = $this->readRecordSlideAtom($stream, $pos);
2790        if (0 == $slideAtom['length']) {
2791            throw new InvalidFileFormatException($this->filename, self::class);
2792        }
2793        $pos += $slideAtom['length'];
2794
2795        // *** slideShowSlideInfoAtom (24 bytes)
2796        $slideShowInfoAtom = $this->readRecordSlideShowSlideInfoAtom($stream, $pos);
2797        $pos += $slideShowInfoAtom['length'];
2798
2799        // *** perSlideHFContainer (variable) : optional
2800        $perSlideHFContainer = $this->readRecordPerSlideHeadersFootersContainer($stream, $pos);
2801        $pos += $perSlideHFContainer['length'];
2802
2803        // *** rtSlideSyncInfo12 (variable) : optional
2804        $rtSlideSyncInfo12 = $this->readRecordRoundTripSlideSyncInfo12Container($stream, $pos);
2805        $pos += $rtSlideSyncInfo12['length'];
2806
2807        // *** drawing (variable)
2808        $drawing = $this->readRecordDrawingContainer($stream, $pos);
2809        $pos += $drawing['length'];
2810
2811        // *** slideSchemeColorSchemeAtom (40 bytes)
2812        $slideSchemeColorAtom = $this->readRecordSlideSchemeColorSchemeAtom($stream, $pos);
2813        if (0 == $slideSchemeColorAtom['length']) {
2814            // Record SlideSchemeColorSchemeAtom
2815            throw new InvalidFileFormatException($this->filename, self::class);
2816        }
2817        $pos += $slideSchemeColorAtom['length'];
2818
2819        // *** slideNameAtom (variable)
2820        $slideNameAtom = $this->readRecordSlideNameAtom($stream, $pos);
2821        $pos += $slideNameAtom['length'];
2822
2823        // *** slideProgTagsContainer (variable).
2824        $slideProgTags = $this->readRecordSlideProgTagsContainer($stream, $pos);
2825        $pos += $slideProgTags['length'];
2826
2827        // *** rgRoundTripSlide (variable)
2828    }
2829
2830    /**
2831     * An atom record that specifies the name of a slide.
2832     *
2833     * @see https://msdn.microsoft.com/en-us/library/dd906297(v=office.12).aspx
2834     *
2835     * @return array{'length': int, 'slideName': string}
2836     */
2837    private function readRecordSlideNameAtom(string $stream, int $pos): array
2838    {
2839        $arrayReturn = [
2840            'length' => 0,
2841            'slideName' => '',
2842        ];
2843
2844        $data = $this->loadRecordHeader($stream, $pos);
2845        if (0x0 == $data['recVer'] && 0x003 == $data['recInstance'] && self::RT_CSTRING == $data['recType'] && $data['recLen'] % 2 == 0) {
2846            // Record Header
2847            $arrayReturn['length'] += 8;
2848            // Length
2849            $strLen = ($data['recLen'] / 2);
2850            for ($inc = 0; $inc < $strLen; ++$inc) {
2851                $char = self::getInt2d($stream, $pos + $arrayReturn['length']);
2852                $arrayReturn['length'] += 2;
2853                $arrayReturn['slideName'] .= Text::chr($char);
2854            }
2855        }
2856
2857        return $arrayReturn;
2858    }
2859
2860    /**
2861     * An atom record that specifies a slide number metacharacter.
2862     *
2863     * @see https://msdn.microsoft.com/en-us/library/dd945703(v=office.12).aspx
2864     *
2865     * @return array<string, int>
2866     */
2867    private function readRecordSlideNumberMCAtom(string $stream, int $pos): array
2868    {
2869        $arrayReturn = [
2870            'length' => 0,
2871        ];
2872
2873        $data = $this->loadRecordHeader($stream, $pos);
2874        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SLIDENUMBERMETACHARATOM == $data['recType'] && 0x00000004 == $data['recLen']) {
2875            // Record Header
2876            $arrayReturn['length'] += 8;
2877            // Datas
2878            $arrayReturn['length'] += $data['recLen'];
2879        }
2880
2881        return $arrayReturn;
2882    }
2883
2884    /**
2885     * A container record that specifies programmable tags with additional slide data.
2886     *
2887     * @see https://msdn.microsoft.com/en-us/library/dd951946(v=office.12).aspx
2888     *
2889     * @return array<string, int>
2890     */
2891    private function readRecordSlideProgTagsContainer(string $stream, int $pos): array
2892    {
2893        $arrayReturn = [
2894            'length' => 0,
2895        ];
2896
2897        $data = $this->loadRecordHeader($stream, $pos);
2898        if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PROGTAGS == $data['recType']) {
2899            // Record Header
2900            $arrayReturn['length'] += 8;
2901            // Length
2902            $arrayReturn['length'] += $data['recLen'];
2903        }
2904
2905        return $arrayReturn;
2906    }
2907
2908    /**
2909     * A container record that specifies the color scheme used by a slide.
2910     *
2911     * @see https://msdn.microsoft.com/en-us/library/dd949420(v=office.12).aspx
2912     *
2913     * @return array<string, int>
2914     */
2915    private function readRecordSlideSchemeColorSchemeAtom(string $stream, int $pos): array
2916    {
2917        $arrayReturn = [
2918            'length' => 0,
2919        ];
2920
2921        $data = $this->loadRecordHeader($stream, $pos);
2922        if (0x0 == $data['recVer'] && 0x001 == $data['recInstance'] && self::RT_COLORSCHEMEATOM == $data['recType'] && 0x00000020 == $data['recLen']) {
2923            // Record Header
2924            $arrayReturn['length'] += 8;
2925            // Length
2926            $rgSchemeColor = [];
2927            for ($inc = 0; $inc <= 7; ++$inc) {
2928                $rgSchemeColor[] = [
2929                    'red' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4),
2930                    'green' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4 + 1),
2931                    'blue' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4 + 2),
2932                ];
2933            }
2934            $arrayReturn['length'] += (8 * 4);
2935        }
2936
2937        return $arrayReturn;
2938    }
2939
2940    /**
2941     * An atom record that specifies what transition effect to perform during a slide show, and how to advance to the next presentation slide.
2942     *
2943     * @see https://msdn.microsoft.com/en-us/library/dd943408(v=office.12).aspx
2944     *
2945     * @return array<string, int>
2946     */
2947    private function readRecordSlideShowSlideInfoAtom(string $stream, int $pos): array
2948    {
2949        $arrayReturn = [
2950            'length' => 0,
2951        ];
2952
2953        $data = $this->loadRecordHeader($stream, $pos);
2954        if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SLIDESHOWSLIDEINFOATOM == $data['recType'] && 0x00000010 == $data['recLen']) {
2955            // Record Header
2956            $arrayReturn['length'] += 8;
2957            // Length;
2958            $arrayReturn['length'] += $data['recLen'];
2959        }
2960
2961        return $arrayReturn;
2962    }
2963
2964    /**
2965     * UserEditAtom.
2966     *
2967     * @see http://msdn.microsoft.com/en-us/library/dd945746(v=office.12).aspx
2968     */
2969    private function readRecordUserEditAtom(string $stream, int $pos): void
2970    {
2971        $rHeader = $this->loadRecordHeader($stream, $pos);
2972        $pos += 8;
2973        if (0x0 != $rHeader['recVer'] || 0x000 != $rHeader['recInstance'] || self::RT_USEREDITATOM != $rHeader['recType'] || (0x0000001C != $rHeader['recLen'] && 0x00000020 != $rHeader['recLen'])) {
2974            throw new InvalidFileFormatException($this->filename, self::class, 'Location : UserEditAtom > RecordHeader');
2975        }
2976
2977        // lastSlideIdRef
2978        $pos += 4;
2979        // version
2980        $pos += 2;
2981
2982        // minorVersion
2983        $minorVersion = self::getInt1d($stream, $pos);
2984        ++$pos;
2985        if (0x00 != $minorVersion) {
2986            throw new InvalidFileFormatException($this->filename, self::class, 'Location : UserEditAtom > minorVersion');
2987        }
2988
2989        // majorVersion
2990        $majorVersion = self::getInt1d($stream, $pos);
2991        ++$pos;
2992        if (0x03 != $majorVersion) {
2993            throw new InvalidFileFormatException($this->filename, self::class, 'Location : UserEditAtom > majorVersion');
2994        }
2995
2996        // offsetLastEdit
2997        $pos += 4;
2998        // offsetPersistDirectory
2999        $this->offsetPersistDirectory = self::getInt4d($stream, $pos);
3000        $pos += 4;
3001
3002        // docPersistIdRef
3003        $docPersistIdRef = self::getInt4d($stream, $pos);
3004        $pos += 4;
3005        if (0x00000001 != $docPersistIdRef) {
3006            throw new InvalidFileFormatException($this->filename, self::class, 'Location : UserEditAtom > docPersistIdRef');
3007        }
3008
3009        // persistIdSeed
3010        $pos += 4;
3011        // lastView
3012        $pos += 2;
3013        // unused
3014        $pos += 2;
3015    }
3016
3017    /**
3018     * A structure that specifies the character-level formatting of a run of text.
3019     *
3020     * @return array{'length': int, 'strLenRT': int, 'partLength': int, 'bold': bool, 'italic': bool, 'underline': bool, 'fontName': string, 'fontSize': int, 'color': Color}
3021     *
3022     * @see https://msdn.microsoft.com/en-us/library/dd945870(v=office.12).aspx
3023     */
3024    private function readStructureTextCFRun(string $stream, int $pos, int $strLenRT): array
3025    {
3026        $arrayReturn = [
3027            'length' => 0,
3028            'strLenRT' => $strLenRT,
3029        ];
3030
3031        // rgTextCFRun
3032        $countRgTextCFRun = self::getInt4d($stream, $pos + $arrayReturn['length']);
3033        $arrayReturn['strLenRT'] -= $countRgTextCFRun;
3034        $arrayReturn['length'] += 4;
3035        $arrayReturn['partLength'] = $countRgTextCFRun;
3036
3037        $masks = self::getInt4d($stream, $pos + $arrayReturn['length']);
3038        $arrayReturn['length'] += 4;
3039
3040        $masksData = [];
3041        $masksData['bold'] = ($masks >> 0) & bindec('1');
3042        $masksData['italic'] = ($masks >> 1) & bindec('1');
3043        $masksData['underline'] = ($masks >> 2) & bindec('1');
3044        $masksData['unused1'] = ($masks >> 3) & bindec('1');
3045        $masksData['shadow'] = ($masks >> 4) & bindec('1');
3046        $masksData['fehint'] = ($masks >> 5) & bindec('1');
3047        $masksData['unused2'] = ($masks >> 6) & bindec('1');
3048        $masksData['kumi'] = ($masks >> 7) & bindec('1');
3049        $masksData['unused3'] = ($masks >> 8) & bindec('1');
3050        $masksData['emboss'] = ($masks >> 9) & bindec('1');
3051        $masksData['fHasStyle'] = ($masks >> 10) & bindec('1111');
3052        $masksData['unused4'] = ($masks >> 14) & bindec('11');
3053        $masksData['typeface'] = ($masks >> 16) & bindec('1');
3054        $masksData['size'] = ($masks >> 17) & bindec('1');
3055        $masksData['color'] = ($masks >> 18) & bindec('1');
3056        $masksData['position'] = ($masks >> 19) & bindec('1');
3057        $masksData['pp10ext'] = ($masks >> 20) & bindec('1');
3058        $masksData['oldEATypeface'] = ($masks >> 21) & bindec('1');
3059        $masksData['ansiTypeface'] = ($masks >> 22) & bindec('1');
3060        $masksData['symbolTypeface'] = ($masks >> 23) & bindec('1');
3061        $masksData['newEATypeface'] = ($masks >> 24) & bindec('1');
3062        $masksData['csTypeface'] = ($masks >> 25) & bindec('1');
3063        $masksData['pp11ext'] = ($masks >> 26) & bindec('1');
3064        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']) {
3065            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3066            $arrayReturn['length'] += 2;
3067
3068            $fontStyleFlags = [];
3069            $fontStyleFlags['bold'] = ($data >> 0) & bindec('1');
3070            $fontStyleFlags['italic'] = ($data >> 1) & bindec('1');
3071            $fontStyleFlags['underline'] = ($data >> 2) & bindec('1');
3072            $fontStyleFlags['unused1'] = ($data >> 3) & bindec('1');
3073            $fontStyleFlags['shadow'] = ($data >> 4) & bindec('1');
3074            $fontStyleFlags['fehint'] = ($data >> 5) & bindec('1');
3075            $fontStyleFlags['unused2'] = ($data >> 6) & bindec('1');
3076            $fontStyleFlags['kumi'] = ($data >> 7) & bindec('1');
3077            $fontStyleFlags['unused3'] = ($data >> 8) & bindec('1');
3078            $fontStyleFlags['emboss'] = ($data >> 9) & bindec('1');
3079            $fontStyleFlags['pp9rt'] = ($data >> 10) & bindec('1111');
3080            $fontStyleFlags['unused4'] = ($data >> 14) & bindec('11');
3081
3082            $arrayReturn['bold'] = (1 == $fontStyleFlags['bold']) ? true : false;
3083            $arrayReturn['italic'] = (1 == $fontStyleFlags['italic']) ? true : false;
3084            $arrayReturn['underline'] = (1 == $fontStyleFlags['underline']) ? true : false;
3085        }
3086        if (1 == $masksData['typeface']) {
3087            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3088            $arrayReturn['length'] += 2;
3089            $arrayReturn['fontName'] = $this->arrayFonts[$data] ?? '';
3090        }
3091        if (1 == $masksData['oldEATypeface']) {
3092            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3093            $arrayReturn['length'] += 2;
3094        }
3095        if (1 == $masksData['ansiTypeface']) {
3096            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3097            $arrayReturn['length'] += 2;
3098        }
3099        if (1 == $masksData['symbolTypeface']) {
3100            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3101            $arrayReturn['length'] += 2;
3102        }
3103        if (1 == $masksData['size']) {
3104            $arrayReturn['fontSize'] = self::getInt2d($stream, $pos + $arrayReturn['length']);
3105            $arrayReturn['length'] += 2;
3106        }
3107        if (1 == $masksData['color']) {
3108            $red = self::getInt1d($stream, $pos + $arrayReturn['length']);
3109            ++$arrayReturn['length'];
3110            $green = self::getInt1d($stream, $pos + $arrayReturn['length']);
3111            ++$arrayReturn['length'];
3112            $blue = self::getInt1d($stream, $pos + $arrayReturn['length']);
3113            ++$arrayReturn['length'];
3114            $index = self::getInt1d($stream, $pos + $arrayReturn['length']);
3115            ++$arrayReturn['length'];
3116
3117            if (0xFE == $index) {
3118                $strColor = str_pad(dechex($red), 2, '0', STR_PAD_LEFT);
3119                $strColor .= str_pad(dechex($green), 2, '0', STR_PAD_LEFT);
3120                $strColor .= str_pad(dechex($blue), 2, '0', STR_PAD_LEFT);
3121
3122                $arrayReturn['color'] = new Color('FF' . $strColor);
3123            }
3124        }
3125        if (1 == $masksData['position']) {
3126            throw new FeatureNotImplementedException();
3127        }
3128
3129        return $arrayReturn;
3130    }
3131
3132    /**
3133     * A structure that specifies the paragraph-level formatting of a run of text.
3134     *
3135     * @return array{'length': int, 'strLenRT': int, 'alignH': null|string, 'bulletChar': string, 'leftMargin': int, 'indent': int}
3136     *
3137     * @see https://msdn.microsoft.com/en-us/library/dd923535(v=office.12).aspx
3138     */
3139    private function readStructureTextPFRun(string $stream, int $pos, int $strLenRT): array
3140    {
3141        $arrayReturn = [
3142            'length' => 0,
3143            'strLenRT' => $strLenRT,
3144        ];
3145
3146        // rgTextPFRun
3147        $countRgTextPFRun = self::getInt4d($stream, $pos + $arrayReturn['length']);
3148        $arrayReturn['strLenRT'] -= $countRgTextPFRun;
3149        $arrayReturn['length'] += 4;
3150
3151        // indent
3152        $arrayReturn['length'] += 2;
3153
3154        $masks = self::getInt4d($stream, $pos + $arrayReturn['length']);
3155        $arrayReturn['length'] += 4;
3156
3157        $masksData = [];
3158        $masksData['hasBullet'] = ($masks >> 0) & bindec('1');
3159        $masksData['bulletHasFont'] = ($masks >> 1) & bindec('1');
3160        $masksData['bulletHasColor'] = ($masks >> 2) & bindec('1');
3161        $masksData['bulletHasSize'] = ($masks >> 3) & bindec('1');
3162        $masksData['bulletFont'] = ($masks >> 4) & bindec('1');
3163        $masksData['bulletColor'] = ($masks >> 5) & bindec('1');
3164        $masksData['bulletSize'] = ($masks >> 6) & bindec('1');
3165        $masksData['bulletChar'] = ($masks >> 7) & bindec('1');
3166        $masksData['leftMargin'] = ($masks >> 8) & bindec('1');
3167        $masksData['unused'] = ($masks >> 9) & bindec('1');
3168        $masksData['indent'] = ($masks >> 10) & bindec('1');
3169        $masksData['align'] = ($masks >> 11) & bindec('1');
3170        $masksData['lineSpacing'] = ($masks >> 12) & bindec('1');
3171        $masksData['spaceBefore'] = ($masks >> 13) & bindec('1');
3172        $masksData['spaceAfter'] = ($masks >> 14) & bindec('1');
3173        $masksData['defaultTabSize'] = ($masks >> 15) & bindec('1');
3174        $masksData['fontAlign'] = ($masks >> 16) & bindec('1');
3175        $masksData['charWrap'] = ($masks >> 17) & bindec('1');
3176        $masksData['wordWrap'] = ($masks >> 18) & bindec('1');
3177        $masksData['overflow'] = ($masks >> 19) & bindec('1');
3178        $masksData['tabStops'] = ($masks >> 20) & bindec('1');
3179        $masksData['textDirection'] = ($masks >> 21) & bindec('1');
3180        $masksData['reserved1'] = ($masks >> 22) & bindec('1');
3181        $masksData['bulletBlip'] = ($masks >> 23) & bindec('1');
3182        $masksData['bulletScheme'] = ($masks >> 24) & bindec('1');
3183        $masksData['bulletHasScheme'] = ($masks >> 25) & bindec('1');
3184
3185        $bulletFlags = [];
3186        if (1 == $masksData['hasBullet'] || 1 == $masksData['bulletHasFont'] || 1 == $masksData['bulletHasColor'] || 1 == $masksData['bulletHasSize']) {
3187            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3188            $arrayReturn['length'] += 2;
3189
3190            $bulletFlags['fHasBullet'] = ($data >> 0) & bindec('1');
3191            $bulletFlags['fBulletHasFont'] = ($data >> 1) & bindec('1');
3192            $bulletFlags['fBulletHasColor'] = ($data >> 2) & bindec('1');
3193            $bulletFlags['fBulletHasSize'] = ($data >> 3) & bindec('1');
3194        }
3195        if (1 == $masksData['bulletChar']) {
3196            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3197            $arrayReturn['length'] += 2;
3198            $arrayReturn['bulletChar'] = chr($data);
3199        }
3200        if (1 == $masksData['bulletFont']) {
3201            // $data = self::getInt2d($stream, $pos);
3202            $arrayReturn['length'] += 2;
3203        }
3204        if (1 == $masksData['bulletSize']) {
3205            // $data = self::getInt2d($stream, $pos);
3206            $arrayReturn['length'] += 2;
3207        }
3208        if (1 == $masksData['bulletColor']) {
3209            // $red = self::getInt1d($stream, $pos + $arrayReturn['length']);
3210            ++$arrayReturn['length'];
3211            // $green = self::getInt1d($stream, $pos + $arrayReturn['length']);
3212            ++$arrayReturn['length'];
3213            // $blue = self::getInt1d($stream, $pos + $arrayReturn['length']);
3214            ++$arrayReturn['length'];
3215            $index = self::getInt1d($stream, $pos + $arrayReturn['length']);
3216            ++$arrayReturn['length'];
3217
3218            if (0xFE == $index) {
3219                // $strColor = str_pad(dechex($red), 2, '0', STR_PAD_LEFT);
3220                // $strColor .= str_pad(dechex($green), 2, '0', STR_PAD_LEFT);
3221                // $strColor .= str_pad(dechex($blue), 2, '0', STR_PAD_LEFT);
3222            }
3223        }
3224        if (1 == $masksData['align']) {
3225            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3226            $arrayReturn['length'] += 2;
3227            switch ($data) {
3228                case 0x0000:
3229                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_LEFT;
3230
3231                    break;
3232                case 0x0001:
3233                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_CENTER;
3234
3235                    break;
3236                case 0x0002:
3237                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_RIGHT;
3238
3239                    break;
3240                case 0x0003:
3241                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_JUSTIFY;
3242
3243                    break;
3244                case 0x0004:
3245                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_DISTRIBUTED;
3246
3247                    break;
3248                case 0x0005:
3249                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_DISTRIBUTED;
3250
3251                    break;
3252                case 0x0006:
3253                    $arrayReturn['alignH'] = Alignment::HORIZONTAL_JUSTIFY;
3254
3255                    break;
3256                default:
3257                    break;
3258            }
3259        }
3260        if (1 == $masksData['lineSpacing']) {
3261            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3262            $arrayReturn['length'] += 2;
3263        }
3264        if (1 == $masksData['spaceBefore']) {
3265            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3266            $arrayReturn['length'] += 2;
3267        }
3268        if (1 == $masksData['spaceAfter']) {
3269            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3270            $arrayReturn['length'] += 2;
3271        }
3272        if (1 == $masksData['leftMargin']) {
3273            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3274            $arrayReturn['length'] += 2;
3275            $arrayReturn['leftMargin'] = (int) round($data / 6);
3276        }
3277        if (1 == $masksData['indent']) {
3278            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3279            $arrayReturn['length'] += 2;
3280            $arrayReturn['indent'] = (int) round($data / 6);
3281        }
3282        if (1 == $masksData['defaultTabSize']) {
3283            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3284            $arrayReturn['length'] += 2;
3285        }
3286        if (1 == $masksData['tabStops']) {
3287            throw new FeatureNotImplementedException();
3288        }
3289        if (1 == $masksData['fontAlign']) {
3290            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3291            $arrayReturn['length'] += 2;
3292        }
3293        if (1 == $masksData['charWrap'] || 1 == $masksData['wordWrap'] || 1 == $masksData['overflow']) {
3294            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3295            $arrayReturn['length'] += 2;
3296        }
3297        if (1 == $masksData['textDirection']) {
3298            throw new FeatureNotImplementedException();
3299        }
3300
3301        return $arrayReturn;
3302    }
3303
3304    /**
3305     * A structure that specifies language and spelling information for a run of text.
3306     *
3307     * @return array<string, int>
3308     *
3309     * @see https://msdn.microsoft.com/en-us/library/dd909603(v=office.12).aspx
3310     */
3311    private function readStructureTextSIRun(string $stream, int $pos, int $strLenRT): array
3312    {
3313        $arrayReturn = [
3314            'length' => 0,
3315            'strLenRT' => $strLenRT,
3316        ];
3317
3318        $arrayReturn['strLenRT'] -= self::getInt4d($stream, $pos + $arrayReturn['length']);
3319        $arrayReturn['length'] += 4;
3320
3321        $data = self::getInt4d($stream, $pos + $arrayReturn['length']);
3322        $arrayReturn['length'] += 4;
3323        $masksData = [];
3324        $masksData['spell'] = ($data >> 0) & bindec('1');
3325        $masksData['lang'] = ($data >> 1) & bindec('1');
3326        $masksData['altLang'] = ($data >> 2) & bindec('1');
3327        $masksData['unused1'] = ($data >> 3) & bindec('1');
3328        $masksData['unused2'] = ($data >> 4) & bindec('1');
3329        $masksData['fPp10ext'] = ($data >> 5) & bindec('1');
3330        $masksData['fBidi'] = ($data >> 6) & bindec('1');
3331        $masksData['unused3'] = ($data >> 7) & bindec('1');
3332        $masksData['reserved1'] = ($data >> 8) & bindec('1');
3333        $masksData['smartTag'] = ($data >> 9) & bindec('1');
3334
3335        if (1 == $masksData['spell']) {
3336            $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3337            $arrayReturn['length'] += 2;
3338            $masksSpell = [];
3339            $masksSpell['error'] = ($data >> 0) & bindec('1');
3340            $masksSpell['clean'] = ($data >> 1) & bindec('1');
3341            $masksSpell['grammar'] = ($data >> 2) & bindec('1');
3342        }
3343        if (1 == $masksData['lang']) {
3344            // $data = self::getInt2d($stream, $pos);
3345            $arrayReturn['length'] += 2;
3346        }
3347        if (1 == $masksData['altLang']) {
3348            // $data = self::getInt2d($stream, $pos);
3349            $arrayReturn['length'] += 2;
3350        }
3351        if (1 == $masksData['fBidi']) {
3352            throw new FeatureNotImplementedException();
3353        }
3354        if (1 == $masksData['fPp10ext']) {
3355            throw new FeatureNotImplementedException();
3356        }
3357        if (1 == $masksData['smartTag']) {
3358            throw new FeatureNotImplementedException();
3359        }
3360
3361        return $arrayReturn;
3362    }
3363
3364    /**
3365     * A structure that specifies tabbing, margins, and indentation for text.
3366     *
3367     * @return array<string, int>
3368     *
3369     * @see https://msdn.microsoft.com/en-us/library/dd922749(v=office.12).aspx
3370     */
3371    private function readStructureTextRuler(string $stream, int $pos): array
3372    {
3373        $arrayReturn = [
3374            'length' => 0,
3375        ];
3376
3377        $data = self::getInt4d($stream, $pos + $arrayReturn['length']);
3378        $arrayReturn['length'] += 4;
3379
3380        $masksData = [];
3381        $masksData['fDefaultTabSize'] = ($data >> 0) & bindec('1');
3382        $masksData['fCLevels'] = ($data >> 1) & bindec('1');
3383        $masksData['fTabStops'] = ($data >> 2) & bindec('1');
3384        $masksData['fLeftMargin1'] = ($data >> 3) & bindec('1');
3385        $masksData['fLeftMargin2'] = ($data >> 4) & bindec('1');
3386        $masksData['fLeftMargin3'] = ($data >> 5) & bindec('1');
3387        $masksData['fLeftMargin4'] = ($data >> 6) & bindec('1');
3388        $masksData['fLeftMargin5'] = ($data >> 7) & bindec('1');
3389        $masksData['fIndent1'] = ($data >> 8) & bindec('1');
3390        $masksData['fIndent2'] = ($data >> 9) & bindec('1');
3391        $masksData['fIndent3'] = ($data >> 10) & bindec('1');
3392        $masksData['fIndent4'] = ($data >> 11) & bindec('1');
3393        $masksData['fIndent5'] = ($data >> 12) & bindec('1');
3394
3395        if (1 == $masksData['fCLevels']) {
3396            throw new FeatureNotImplementedException();
3397        }
3398        if (1 == $masksData['fDefaultTabSize']) {
3399            throw new FeatureNotImplementedException();
3400        }
3401        if (1 == $masksData['fTabStops']) {
3402            $count = self::getInt2d($stream, $pos + $arrayReturn['length']);
3403            $arrayReturn['length'] += 2;
3404            $arrayTabStops = [];
3405            for ($inc = 0; $inc < $count; ++$inc) {
3406                $position = self::getInt2d($stream, $pos + $arrayReturn['length']);
3407                $arrayReturn['length'] += 2;
3408                $type = self::getInt2d($stream, $pos + $arrayReturn['length']);
3409                $arrayReturn['length'] += 2;
3410                $arrayTabStops[] = [
3411                    'position' => $position,
3412                    'type' => $type,
3413                ];
3414            }
3415        }
3416        if (1 == $masksData['fLeftMargin1']) {
3417            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3418            $arrayReturn['length'] += 2;
3419        }
3420        if (1 == $masksData['fIndent1']) {
3421            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3422            $arrayReturn['length'] += 2;
3423        }
3424        if (1 == $masksData['fLeftMargin2']) {
3425            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3426            $arrayReturn['length'] += 2;
3427        }
3428        if (1 == $masksData['fIndent2']) {
3429            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3430            $arrayReturn['length'] += 2;
3431        }
3432        if (1 == $masksData['fLeftMargin3']) {
3433            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3434            $arrayReturn['length'] += 2;
3435        }
3436        if (1 == $masksData['fIndent3']) {
3437            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3438            $arrayReturn['length'] += 2;
3439        }
3440        if (1 == $masksData['fLeftMargin4']) {
3441            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3442            $arrayReturn['length'] += 2;
3443        }
3444        if (1 == $masksData['fIndent4']) {
3445            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3446            $arrayReturn['length'] += 2;
3447        }
3448        if (1 == $masksData['fLeftMargin5']) {
3449            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3450            $arrayReturn['length'] += 2;
3451        }
3452        if (1 == $masksData['fIndent5']) {
3453            // $data = self::getInt2d($stream, $pos + $arrayReturn['length']);
3454            $arrayReturn['length'] += 2;
3455        }
3456
3457        return $arrayReturn;
3458    }
3459
3460    private function readRecordNotesContainer(string $stream, int $pos): void
3461    {
3462        // notesAtom
3463        $notesAtom = $this->readRecordNotesAtom($stream, $pos);
3464        $pos += $notesAtom['length'];
3465
3466        // drawing
3467        $drawing = $this->readRecordDrawingContainer($stream, $pos);
3468        $pos += $drawing['length'];
3469
3470        // slideSchemeColorSchemeAtom
3471        // slideNameAtom
3472        // slideProgTagsContainer
3473        // rgNotesRoundTripAtom
3474    }
3475
3476    /**
3477     * @return array<string, int>
3478     */
3479    private function readRecordNotesAtom(string $stream, int $pos): array
3480    {
3481        $arrayReturn = [
3482            'length' => 0,
3483        ];
3484
3485        $data = $this->loadRecordHeader($stream, $pos);
3486        if (0x1 != $data['recVer'] || 0x000 != $data['recInstance'] || self::RT_NOTESATOM != $data['recType'] || 0x00000008 != $data['recLen']) {
3487            throw new InvalidFileFormatException($this->filename, self::class, 'Location : NotesAtom > RecordHeader)');
3488        }
3489        // Record Header
3490        $arrayReturn['length'] += 8;
3491        // NotesAtom > slideIdRef
3492        $notesIdRef = self::getInt4d($stream, $pos + $arrayReturn['length']);
3493        if (-2147483648 == $notesIdRef) {
3494            $notesIdRef = 0;
3495        }
3496        $this->currentNote = $notesIdRef;
3497        $arrayReturn['length'] += 4;
3498
3499        // NotesAtom > slideFlags
3500        $arrayReturn['length'] += 2;
3501        // NotesAtom > unused
3502        $arrayReturn['length'] += 2;
3503
3504        return $arrayReturn;
3505    }
3506}