*/ private $arrayFonts = []; /** * Array with Hyperlinks. * * @var array> */ private $arrayHyperlinks = []; /** * Array with Notes. * * @var array */ private $arrayNotes = []; /** * Array with Pictures. * * @var array */ private $arrayPictures = []; /** * Offset (in bytes) from the beginning of the PowerPoint Document Stream to the UserEditAtom record for the most recent user edit. * * @var int */ private $offsetToCurrentEdit; /** * A structure that specifies a compressed table of sequential persist object identifiers and stream offsets to associated persist objects. * * @var array */ private $rgPersistDirEntry; /** * Offset (in bytes) from the beginning of the PowerPoint Document Stream to the PersistDirectoryAtom record for this user edit. * * @var int */ private $offsetPersistDirectory; /** * Output Object. * * @var PhpPresentation */ private $oPhpPresentation; /** * @var Group|null */ private $oCurrentGroup; /** * @var bool */ private $bFirstShapeGroup = false; /** * Stream "Powerpoint Document". * * @var string */ private $streamPowerpointDocument; /** * Stream "Current User". * * @var string */ private $streamCurrentUser; /** * Stream "Pictures". * * @var string */ private $streamPictures; /** * @var int */ private $inMainType; /** * @var int|null */ private $currentNote; /** * @var string|null */ private $filename; /** * Can the current \PhpOffice\PhpPresentation\Reader\ReaderInterface read the file? */ public function canRead(string $pFilename): bool { return $this->fileSupportsUnserializePhpPresentation($pFilename); } /** * Does a file support UnserializePhpPresentation ? * * @throws FileNotFoundException */ public function fileSupportsUnserializePhpPresentation(string $pFilename = ''): bool { // Check if file exists if (!\file_exists($pFilename)) { throw new FileNotFoundException($pFilename); } /* try { // Use ParseXL for the hard work. $ole = new OLERead(); // get excel data $ole->read($pFilename); return true; } catch (\Exception $e) { return false; } */ $ole = new OLERead(); return $ole->read($pFilename); } /** * Loads PhpPresentation Serialized file. * * @throws InvalidFileFormatException */ public function load(string $pFilename): PhpPresentation { // Unserialize... First make sure the file supports it! if (!$this->fileSupportsUnserializePhpPresentation($pFilename)) { throw new InvalidFileFormatException($pFilename, PowerPoint97::class); } $this->filename = $pFilename; return $this->loadFile(); } /** * Load PhpPresentation Serialized file */ private function loadFile(): PhpPresentation { $this->oPhpPresentation = new PhpPresentation(); $this->oPhpPresentation->removeSlideByIndex(); // Read OLE Blocks $this->loadOLE(); // Read pictures in the Pictures Stream $this->loadPicturesStream(); // Read information in the Current User Stream $this->loadCurrentUserStream(); // Read information in the PowerPoint Document Stream $this->loadPowerpointDocumentStream(); return $this->oPhpPresentation; } /** * Read OLE Part */ private function loadOLE(): void { // OLE reader $oOLE = new OLERead(); $oOLE->read($this->filename); // PowerPoint Document Stream $this->streamPowerpointDocument = $oOLE->getStream($oOLE->powerpointDocument); // Current User Stream $this->streamCurrentUser = $oOLE->getStream($oOLE->currentUser); // Get pictures data $this->streamPictures = $oOLE->getStream($oOLE->pictures); } /** * Stream Pictures. * * @throws FeatureNotImplementedException * * @see http://msdn.microsoft.com/en-us/library/dd920746(v=office.12).aspx */ private function loadPicturesStream(): void { $stream = $this->streamPictures; $pos = 0; do { $arrayRH = $this->loadRecordHeader($stream, $pos); $pos += 8; $readSuccess = false; if (0x00 == $arrayRH['recVer'] && (0xF007 == $arrayRH['recType'] || ($arrayRH['recType'] >= 0xF018 && $arrayRH['recType'] <= 0xF117))) { //@link : http://msdn.microsoft.com/en-us/library/dd950560(v=office.12).aspx if (0xF007 == $arrayRH['recType']) { // OfficeArtFBSE throw new FeatureNotImplementedException(); } if ($arrayRH['recType'] >= 0xF018 && $arrayRH['recType'] <= 0xF117) { $arrayRecord = $this->readRecordOfficeArtBlip($stream, $pos - 8); if ($arrayRecord['length'] > 0) { $pos += $arrayRecord['length']; $this->arrayPictures[] = $arrayRecord['picture']; } } $readSuccess = true; } } while (true === $readSuccess); } /** * Stream Current User. * * @throws FeatureNotImplementedException * @throws InvalidFileFormatException * * @see http://msdn.microsoft.com/en-us/library/dd908567(v=office.12).aspx */ private function loadCurrentUserStream(): void { $pos = 0; /** * CurrentUserAtom : http://msdn.microsoft.com/en-us/library/dd948895(v=office.12).aspx. */ // RecordHeader : http://msdn.microsoft.com/en-us/library/dd926377(v=office.12).aspx $rHeader = $this->loadRecordHeader($this->streamCurrentUser, $pos); $pos += 8; if (0x0 != $rHeader['recVer'] || 0x000 != $rHeader['recInstance'] || self::RT_CURRENTUSERATOM != $rHeader['recType']) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > RecordHeader'); } // Size $size = self::getInt4d($this->streamCurrentUser, $pos); $pos += 4; if (0x00000014 != $size) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > Size'); } // headerToken $headerToken = self::getInt4d($this->streamCurrentUser, $pos); $pos += 4; if (0xF3D1C4DF == $headerToken && 0xE391C05F != $headerToken) { // Encrypted file throw new FeatureNotImplementedException(); } // offsetToCurrentEdit $this->offsetToCurrentEdit = self::getInt4d($this->streamCurrentUser, $pos); $pos += 4; // lenUserName $lenUserName = self::getInt2d($this->streamCurrentUser, $pos); $pos += 2; if ($lenUserName > 255) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > lenUserName'); } // docFileVersion $docFileVersion = self::getInt2d($this->streamCurrentUser, $pos); $pos += 2; if (0x03F4 != $docFileVersion) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > docFileVersion'); } // majorVersion $majorVersion = self::getInt1d($this->streamCurrentUser, $pos); ++$pos; if (0x03 != $majorVersion) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > majorVersion'); } // minorVersion $minorVersion = self::getInt1d($this->streamCurrentUser, $pos); ++$pos; if (0x00 != $minorVersion) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > minorVersion'); } // unused $pos += 2; // ansiUserName // $ansiUserName = ''; do { $char = self::getInt1d($this->streamCurrentUser, $pos); if (($char >= 0x00 && $char <= 0x1F) || ($char >= 0x7F && $char <= 0x9F)) { $char = false; } else { // $ansiUserName .= chr($char); ++$pos; } } while (false !== $char); // relVersion $relVersion = self::getInt4d($this->streamCurrentUser, $pos); $pos += 4; if (0x00000008 != $relVersion && 0x00000009 != $relVersion) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > relVersion'); } // unicodeUserName // $unicodeUserName = ''; for ($inc = 0; $inc < $lenUserName; ++$inc) { $char = self::getInt2d($this->streamCurrentUser, $pos); if (($char >= 0x00 && $char <= 0x1F) || ($char >= 0x7F && $char <= 0x9F)) { break; } // $unicodeUserName .= chr($char); $pos += 2; } } /** * Stream Powerpoint Document. * * @see http://msdn.microsoft.com/en-us/library/dd921564(v=office.12).aspx */ private function loadPowerpointDocumentStream(): void { $this->readRecordUserEditAtom($this->streamPowerpointDocument, $this->offsetToCurrentEdit); $this->readRecordPersistDirectoryAtom($this->streamPowerpointDocument, $this->offsetPersistDirectory); foreach ($this->rgPersistDirEntry as $offsetDir) { $pos = $offsetDir; $rHeader = $this->loadRecordHeader($this->streamPowerpointDocument, $pos); $pos += 8; $this->inMainType = $rHeader['recType']; $this->currentNote = null; switch ($rHeader['recType']) { case self::RT_DOCUMENT: $this->readRecordDocumentContainer($this->streamPowerpointDocument, $pos); break; case self::RT_NOTES: $this->readRecordNotesContainer($this->streamPowerpointDocument, $pos); break; case self::RT_SLIDE: $this->readRecordSlideContainer($this->streamPowerpointDocument, $pos); break; default: break; } } } /** * Read a record header. * * @return array */ private function loadRecordHeader(string $stream, int $pos): array { $rec = self::getInt2d($stream, $pos); $recType = self::getInt2d($stream, $pos + 2); $recLen = self::getInt4d($stream, $pos + 4); return [ 'recVer' => ($rec >> 0) & \bindec('1111'), 'recInstance' => ($rec >> 4) & \bindec('111111111111'), 'recType' => $recType, 'recLen' => $recLen, ]; } /** * Read 8-bit unsigned integer. */ public static function getInt1d(string $data, int $pos): int { return \ord($data[$pos]); } /** * Read 16-bit unsigned integer. */ public static function getInt2d(string $data, int $pos): int { return \ord($data[$pos]) | (\ord($data[$pos + 1]) << 8); } /** * Read 32-bit signed integer. */ public static function getInt4d(string $data, int $pos): int { // FIX: represent numbers correctly on 64-bit system // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334 // Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems $or24 = \ord($data[$pos + 3]); $ord24 = ($or24 & 127) << 24; if ($or24 >= 128) { // negative number $ord24 = -\abs((256 - $or24) << 24); } return \ord($data[$pos]) | (\ord($data[$pos + 1]) << 8) | (\ord($data[$pos + 2]) << 16) | $ord24; } /** * A container record that specifies the animation and sound information for a shape. * * @return array * * @throws FeatureNotImplementedException * * @see https://msdn.microsoft.com/en-us/library/dd772900(v=office.12).aspx */ private function readRecordAnimationInfoContainer(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ANIMATIONINFO == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // animationAtom // animationSound throw new FeatureNotImplementedException(); } return $arrayReturn; } /** * A container record that specifies information about the document. * * @throws FeatureNotImplementedException * @throws InvalidFileFormatException * * @see http://msdn.microsoft.com/en-us/library/dd947357(v=office.12).aspx */ private function readRecordDocumentContainer(string $stream, int $pos): void { $documentAtom = $this->loadRecordHeader($stream, $pos); $pos += 8; if (0x1 != $documentAtom['recVer'] || 0x000 != $documentAtom['recInstance'] || self::RT_DOCUMENTATOM != $documentAtom['recType']) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : RTDocument > DocumentAtom'); } $pos += $documentAtom['recLen']; $exObjList = $this->loadRecordHeader($stream, $pos); if (0xF == $exObjList['recVer'] && 0x000 == $exObjList['recInstance'] && self::RT_EXTERNALOBJECTLIST == $exObjList['recType']) { $pos += 8; // exObjListAtom > rh $exObjListAtom = $this->loadRecordHeader($stream, $pos); if (0x0 != $exObjListAtom['recVer'] || 0x000 != $exObjListAtom['recInstance'] || self::RT_EXTERNALOBJECTLISTATOM != $exObjListAtom['recType'] || 0x00000004 != $exObjListAtom['recLen']) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : RTDocument > DocumentAtom > exObjList > exObjListAtom'); } $pos += 8; // exObjListAtom > exObjIdSeed $pos += 4; // rgChildRec $exObjList['recLen'] -= 12; do { $childRec = $this->loadRecordHeader($stream, $pos); $pos += 8; $exObjList['recLen'] -= 8; switch ($childRec['recType']) { case self::RT_EXTERNALHYPERLINK: //@link : http://msdn.microsoft.com/en-us/library/dd944995(v=office.12).aspx // exHyperlinkAtom > rh $exHyperlinkAtom = $this->loadRecordHeader($stream, $pos); if (0x0 != $exHyperlinkAtom['recVer'] || 0x000 != $exHyperlinkAtom['recInstance'] || self::RT_EXTERNALHYPERLINKATOM != $exHyperlinkAtom['recType'] || 0x00000004 != $exObjListAtom['recLen']) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : RTDocument > DocumentAtom > exObjList > rgChildRec > RT_ExternalHyperlink'); } $pos += 8; $exObjList['recLen'] -= 8; // exHyperlinkAtom > exHyperlinkId $exHyperlinkId = self::getInt4d($stream, $pos); $pos += 4; $exObjList['recLen'] -= 4; $this->arrayHyperlinks[$exHyperlinkId] = []; // friendlyNameAtom $friendlyNameAtom = $this->loadRecordHeader($stream, $pos); if (0x0 == $friendlyNameAtom['recVer'] && 0x000 == $friendlyNameAtom['recInstance'] && self::RT_CSTRING == $friendlyNameAtom['recType'] && $friendlyNameAtom['recLen'] % 2 == 0) { $pos += 8; $exObjList['recLen'] -= 8; $this->arrayHyperlinks[$exHyperlinkId]['text'] = ''; for ($inc = 0; $inc < ($friendlyNameAtom['recLen'] / 2); ++$inc) { $char = self::getInt2d($stream, $pos); $pos += 2; $exObjList['recLen'] -= 2; $this->arrayHyperlinks[$exHyperlinkId]['text'] .= \chr($char); } } // targetAtom $targetAtom = $this->loadRecordHeader($stream, $pos); if (0x0 == $targetAtom['recVer'] && 0x001 == $targetAtom['recInstance'] && self::RT_CSTRING == $targetAtom['recType'] && $targetAtom['recLen'] % 2 == 0) { $pos += 8; $exObjList['recLen'] -= 8; $this->arrayHyperlinks[$exHyperlinkId]['url'] = ''; for ($inc = 0; $inc < ($targetAtom['recLen'] / 2); ++$inc) { $char = self::getInt2d($stream, $pos); $pos += 2; $exObjList['recLen'] -= 2; $this->arrayHyperlinks[$exHyperlinkId]['url'] .= \chr($char); } } // locationAtom $locationAtom = $this->loadRecordHeader($stream, $pos); if (0x0 == $locationAtom['recVer'] && 0x003 == $locationAtom['recInstance'] && self::RT_CSTRING == $locationAtom['recType'] && $locationAtom['recLen'] % 2 == 0) { $pos += 8; $exObjList['recLen'] -= 8; $string = ''; for ($inc = 0; $inc < ($locationAtom['recLen'] / 2); ++$inc) { $char = self::getInt2d($stream, $pos); $pos += 2; $exObjList['recLen'] -= 2; $string .= \chr($char); } } break; default: // var_dump(dechex((int) $childRec['recType'])); throw new FeatureNotImplementedException(); } } while ($exObjList['recLen'] > 0); } //@link : http://msdn.microsoft.com/en-us/library/dd907813(v=office.12).aspx $documentTextInfo = $this->loadRecordHeader($stream, $pos); if (0xF == $documentTextInfo['recVer'] && 0x000 == $documentTextInfo['recInstance'] && self::RT_ENVIRONMENT == $documentTextInfo['recType']) { $pos += 8; //@link : http://msdn.microsoft.com/en-us/library/dd952717(v=office.12).aspx $kinsoku = $this->loadRecordHeader($stream, $pos); if (0xF == $kinsoku['recVer'] && 0x002 == $kinsoku['recInstance'] && self::RT_KINSOKU == $kinsoku['recType']) { $pos += 8; $pos += $kinsoku['recLen']; } //@link : http://msdn.microsoft.com/en-us/library/dd948152(v=office.12).aspx $fontCollection = $this->loadRecordHeader($stream, $pos); if (0xF == $fontCollection['recVer'] && 0x000 == $fontCollection['recInstance'] && self::RT_FONTCOLLECTION == $fontCollection['recType']) { $pos += 8; do { $fontEntityAtom = $this->loadRecordHeader($stream, $pos); $pos += 8; $fontCollection['recLen'] -= 8; if (0x0 != $fontEntityAtom['recVer'] || $fontEntityAtom['recInstance'] > 128 || self::RT_FONTENTITYATOM != $fontEntityAtom['recType']) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : RTDocument > RT_Environment > RT_FontCollection > RT_FontEntityAtom'); } $string = ''; for ($inc = 0; $inc < 32; ++$inc) { $char = self::getInt2d($stream, $pos); $pos += 2; $fontCollection['recLen'] -= 2; $string .= \chr($char); } $this->arrayFonts[] = $string; // lfCharSet (1 byte) ++$pos; --$fontCollection['recLen']; // fEmbedSubsetted (1 bit) // unused (7 bits) ++$pos; --$fontCollection['recLen']; // rasterFontType (1 bit) // deviceFontType (1 bit) // truetypeFontType (1 bit) // fNoFontSubstitution (1 bit) // reserved (4 bits) ++$pos; --$fontCollection['recLen']; // lfPitchAndFamily (1 byte) ++$pos; --$fontCollection['recLen']; $fontEmbedData1 = $this->loadRecordHeader($stream, $pos); if (0x0 == $fontEmbedData1['recVer'] && $fontEmbedData1['recInstance'] >= 0x000 && $fontEmbedData1['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData1['recType']) { $pos += 8; $fontCollection['recLen'] -= 8; $pos += $fontEmbedData1['recLen']; $fontCollection['recLen'] -= $fontEmbedData1['recLen']; } $fontEmbedData2 = $this->loadRecordHeader($stream, $pos); if (0x0 == $fontEmbedData2['recVer'] && $fontEmbedData2['recInstance'] >= 0x000 && $fontEmbedData2['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData2['recType']) { $pos += 8; $fontCollection['recLen'] -= 8; $pos += $fontEmbedData2['recLen']; $fontCollection['recLen'] -= $fontEmbedData2['recLen']; } $fontEmbedData3 = $this->loadRecordHeader($stream, $pos); if (0x0 == $fontEmbedData3['recVer'] && $fontEmbedData3['recInstance'] >= 0x000 && $fontEmbedData3['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData3['recType']) { $pos += 8; $fontCollection['recLen'] -= 8; $pos += $fontEmbedData3['recLen']; $fontCollection['recLen'] -= $fontEmbedData3['recLen']; } $fontEmbedData4 = $this->loadRecordHeader($stream, $pos); if (0x0 == $fontEmbedData4['recVer'] && $fontEmbedData4['recInstance'] >= 0x000 && $fontEmbedData4['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData4['recType']) { $pos += 8; $fontCollection['recLen'] -= 8; $pos += $fontEmbedData4['recLen']; $fontCollection['recLen'] -= $fontEmbedData4['recLen']; } } while ($fontCollection['recLen'] > 0); } $textCFDefaultsAtom = $this->loadRecordHeader($stream, $pos); if (0x0 == $textCFDefaultsAtom['recVer'] && 0x000 == $textCFDefaultsAtom['recInstance'] && self::RT_TEXTCHARFORMATEXCEPTIONATOM == $textCFDefaultsAtom['recType']) { $pos += 8; $pos += $textCFDefaultsAtom['recLen']; } $textPFDefaultsAtom = $this->loadRecordHeader($stream, $pos); if (0x0 == $textPFDefaultsAtom['recVer'] && 0x000 == $textPFDefaultsAtom['recInstance'] && self::RT_TEXTPARAGRAPHFORMATEXCEPTIONATOM == $textPFDefaultsAtom['recType']) { $pos += 8; $pos += $textPFDefaultsAtom['recLen']; } $defaultRulerAtom = $this->loadRecordHeader($stream, $pos); if (0x0 == $defaultRulerAtom['recVer'] && 0x000 == $defaultRulerAtom['recInstance'] && self::RT_DEFAULTRULERATOM == $defaultRulerAtom['recType']) { $pos += 8; $pos += $defaultRulerAtom['recLen']; } $textSIDefaultsAtom = $this->loadRecordHeader($stream, $pos); if (0x0 == $textSIDefaultsAtom['recVer'] && 0x000 == $textSIDefaultsAtom['recInstance'] && self::RT_TEXTSPECIALINFODEFAULTATOM == $textSIDefaultsAtom['recType']) { $pos += 8; $pos += $textSIDefaultsAtom['recLen']; } $textMasterStyleAtom = $this->loadRecordHeader($stream, $pos); if (0x0 == $textMasterStyleAtom['recVer'] && self::RT_TEXTMASTERSTYLEATOM == $textMasterStyleAtom['recType']) { $pos += 8; $pos += $textMasterStyleAtom['recLen']; } } $soundCollection = $this->loadRecordHeader($stream, $pos); if (0xF == $soundCollection['recVer'] && 0x005 == $soundCollection['recInstance'] && self::RT_SOUNDCOLLECTION == $soundCollection['recType']) { $pos += 8; $pos += $soundCollection['recLen']; } $drawingGroup = $this->loadRecordHeader($stream, $pos); if (0xF == $drawingGroup['recVer'] && 0x000 == $drawingGroup['recInstance'] && self::RT_DRAWINGGROUP == $drawingGroup['recType']) { $drawing = $this->readRecordDrawingGroupContainer($stream, $pos); $pos += 8; $pos += $drawing['length']; } $masterList = $this->loadRecordHeader($stream, $pos); if (0xF == $masterList['recVer'] && 0x001 == $masterList['recInstance'] && self::RT_SLIDELISTWITHTEXT == $masterList['recType']) { $pos += 8; $pos += $masterList['recLen']; } $docInfoList = $this->loadRecordHeader($stream, $pos); if (0xF == $docInfoList['recVer'] && 0x000 == $docInfoList['recInstance'] && self::RT_LIST == $docInfoList['recType']) { $pos += 8; $pos += $docInfoList['recLen']; } $slideHF = $this->loadRecordHeader($stream, $pos); if (0xF == $slideHF['recVer'] && 0x003 == $slideHF['recInstance'] && self::RT_HEADERSFOOTERS == $slideHF['recType']) { $pos += 8; $pos += $slideHF['recLen']; } $notesHF = $this->loadRecordHeader($stream, $pos); if (0xF == $notesHF['recVer'] && 0x004 == $notesHF['recInstance'] && self::RT_HEADERSFOOTERS == $notesHF['recType']) { $pos += 8; $pos += $notesHF['recLen']; } // SlideListWithTextContainer $slideList = $this->loadRecordHeader($stream, $pos); if (0xF == $slideList['recVer'] && 0x000 == $slideList['recInstance'] && self::RT_SLIDELISTWITHTEXT == $slideList['recType']) { $pos += 8; do { // SlideListWithTextSubContainerOrAtom $rhSlideList = $this->loadRecordHeader($stream, $pos); if (0x0 == $rhSlideList['recVer'] && 0x000 == $rhSlideList['recInstance'] && self::RT_SLIDEPERSISTATOM == $rhSlideList['recType'] && 0x00000014 == $rhSlideList['recLen']) { $pos += 8; $slideList['recLen'] -= 8; // persistIdRef $pos += 4; $slideList['recLen'] -= 4; // reserved1 - fShouldCollapse - fNonOutlineData - reserved2 $pos += 4; $slideList['recLen'] -= 4; // cTexts $pos += 4; $slideList['recLen'] -= 4; // slideId $slideId = self::getInt4d($stream, $pos); if (-2147483648 == $slideId) { $slideId = 0; } if ($slideId > 0) { $this->arrayNotes[$this->oPhpPresentation->getActiveSlideIndex()] = $slideId; } $pos += 4; $slideList['recLen'] -= 4; // reserved3 $pos += 4; $slideList['recLen'] -= 4; } } while ($slideList['recLen'] > 0); } } /** * An atom record that specifies information about a slide. * * @return array * * @see https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx */ private function readRecordDrawingContainer(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_DRAWING == $data['recType']) { // Record Header $arrayReturn['length'] += 8; $officeArtDg = $this->readRecordOfficeArtDgContainer($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $officeArtDg['length']; } return $arrayReturn; } /** * @return array */ private function readRecordDrawingGroupContainer(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_DRAWINGGROUP == $data['recType']) { // Record Header $arrayReturn['length'] += 8; $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * An atom record that specifies a reference to an external object. * * @return array * * @see https://msdn.microsoft.com/en-us/library/dd910388(v=office.12).aspx */ private function readRecordExObjRefAtom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_EXTERNALOBJECTREFATOM == $data['recType'] && 0x00000004 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * An atom record that specifies a type of action to be performed. * * @return array * * @see https://msdn.microsoft.com/en-us/library/dd953300(v=office.12).aspx */ private function readRecordInteractiveInfoAtom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_INTERACTIVEINFOATOM == $data['recType'] && 0x00000010 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // soundIdRef $arrayReturn['length'] += 4; // exHyperlinkIdRef $arrayReturn['exHyperlinkIdRef'] = self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; // action ++$arrayReturn['length']; // oleVerb ++$arrayReturn['length']; // jump ++$arrayReturn['length']; // fAnimated (1 bit) // fStopSound (1 bit) // fCustomShowReturn (1 bit) // fVisited (1 bit) // reserved (4 bits) ++$arrayReturn['length']; // hyperlinkType ++$arrayReturn['length']; // unused $arrayReturn['length'] += 3; } return $arrayReturn; } /** * An atom record that specifies the name of a macro, a file name, or a named show. * * @return array * * @see https://msdn.microsoft.com/en-us/library/dd925121(v=office.12).aspx */ private function readRecordMacroNameAtom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x002 == $data['recInstance'] && self::RT_CSTRING == $data['recType'] && $data['recLen'] % 2 == 0) { // Record Header $arrayReturn['length'] += 8; // Datas $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * A container record that specifies what actions to perform when interacting with an object by means of a mouse click. * * @return array * * @see https://msdn.microsoft.com/en-us/library/dd952348(v=office.12).aspx */ private function readRecordMouseClickInteractiveInfoContainer(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_INTERACTIVEINFO == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // interactiveInfoAtom $interactiveInfoAtom = $this->readRecordInteractiveInfoAtom($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $interactiveInfoAtom['length']; if ($interactiveInfoAtom['length'] > 0) { $arrayReturn['exHyperlinkIdRef'] = $interactiveInfoAtom['exHyperlinkIdRef']; } // macroNameAtom $macroNameAtom = $this->readRecordMacroNameAtom($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $macroNameAtom['length']; } return $arrayReturn; } /** * A container record that specifies what actions to perform when interacting with an object by moving the mouse cursor over it. * * @return array * * @throws FeatureNotImplementedException * * @see https://msdn.microsoft.com/en-us/library/dd925811(v=office.12).aspx */ private function readRecordMouseOverInteractiveInfoContainer(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x001 == $data['recInstance'] && self::RT_INTERACTIVEINFO == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // interactiveInfoAtom // macroNameAtom throw new FeatureNotImplementedException(); } return $arrayReturn; } /** * The OfficeArtBlip record specifies BLIP file data. * * @return array{'length': int, 'picture': null|string} * * @throws FeatureNotImplementedException * * @see https://msdn.microsoft.com/en-us/library/dd910081(v=office.12).aspx */ private function readRecordOfficeArtBlip(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, 'picture' => null, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && ($data['recType'] >= 0xF018 && $data['recType'] <= 0xF117)) { // Record Header $arrayReturn['length'] += 8; // Datas switch ($data['recType']) { case self::OFFICEARTBLIPJPG: case self::OFFICEARTBLIPPNG: // rgbUid1 $arrayReturn['length'] += 16; $data['recLen'] -= 16; if (0x6E1 == $data['recInstance']) { // rgbUid2 $arrayReturn['length'] += 16; $data['recLen'] -= 16; } // tag ++$arrayReturn['length']; --$data['recLen']; // BLIPFileData $arrayReturn['picture'] = \substr($this->streamPictures, $pos + $arrayReturn['length'], $data['recLen']); $arrayReturn['length'] += $data['recLen']; break; default: // var_dump(dechex((int) $data['recType'])) throw new FeatureNotImplementedException(); } } return $arrayReturn; } /** * The OfficeArtChildAnchor record specifies four signed integers that specify the anchor for the shape that contains this record. * * @return array * * @see https://msdn.microsoft.com/en-us/library/dd922720(v=office.12).aspx */ private function readRecordOfficeArtChildAnchor(string $stream, int $pos) { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF00F == $data['recType'] && 0x00000010 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas $arrayReturn['left'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; $arrayReturn['top'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; $arrayReturn['width'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']) - $arrayReturn['left']; $arrayReturn['length'] += 4; $arrayReturn['height'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']) - $arrayReturn['top']; $arrayReturn['length'] += 4; } return $arrayReturn; } /** * An atom record that specifies the location of a shape. * * @return array * * @throws FeatureNotImplementedException * * @see https://msdn.microsoft.com/en-us/library/dd922797(v=office.12).aspx */ private function readRecordOfficeArtClientAnchor(string $stream, int $pos) { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF010 == $data['recType'] && (0x00000008 == $data['recLen'] || 0x00000010 == $data['recLen'])) { // Record Header $arrayReturn['length'] += 8; // Datas switch ($data['recLen']) { case 0x00000008: $arrayReturn['top'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6); $arrayReturn['length'] += 2; $arrayReturn['left'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6); $arrayReturn['length'] += 2; $arrayReturn['width'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6) - $arrayReturn['left']; $arrayReturn['length'] += 2; $arrayReturn['height'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6) - $arrayReturn['left']; $arrayReturn['length'] += 2; $pos += 8; break; case 0x00000010: // record OfficeArtClientAnchor (0x00000010) throw new FeatureNotImplementedException(); } } return $arrayReturn; } /** * A container record that specifies text related data for a shape. * * @return array{'length': int, 'alignH': string|null, 'text': string, 'numParts': int, 'numTexts': int, 'hyperlink': array>, 'part': array{'length': int, 'strLenRT': int, 'partLength': int|float, 'bold': bool, 'italic': bool, 'underline': bool, 'fontName': string, 'fontSize': int, 'color': Color}} * * @throws FeatureNotImplementedException * * @see https://msdn.microsoft.com/en-us/library/dd910958(v=office.12).aspx */ private function readRecordOfficeArtClientTextbox(string $stream, int $pos) { $arrayReturn = [ 'length' => 0, 'text' => '', 'numParts' => 0, 'numTexts' => 0, 'hyperlink' => [], ]; $data = $this->loadRecordHeader($stream, $pos); // recVer 0xF // Doc : 0x0 https://msdn.microsoft.com/en-us/library/dd910958(v=office.12).aspx // Sample : 0xF https://msdn.microsoft.com/en-us/library/dd953497(v=office.12).aspx if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF00D == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Datas $strLen = 0; do { $rhChild = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']); /* * @link : https://msdn.microsoft.com/en-us/library/dd947039(v=office.12).aspx */ // echo dechex($rhChild['recType']).'-'.$rhChild['recType'].EOL; switch ($rhChild['recType']) { case self::RT_INTERACTIVEINFO: //@link : http://msdn.microsoft.com/en-us/library/dd948623(v=office.12).aspx if (0x0000 == $rhChild['recInstance']) { $mouseClickInfo = $this->readRecordMouseClickInteractiveInfoContainer($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $mouseClickInfo['length']; $arrayReturn['hyperlink'][]['id'] = $mouseClickInfo['exHyperlinkIdRef']; } if (0x0001 == $rhChild['recInstance']) { $mouseOverInfo = $this->readRecordMouseOverInteractiveInfoContainer($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $mouseOverInfo['length']; } break; case self::RT_STYLETEXTPROPATOM: $arrayReturn['length'] += 8; // @link : http://msdn.microsoft.com/en-us/library/dd950647(v=office.12).aspx // rgTextPFRun $strLenRT = $strLen + 1; do { $strucTextPFRun = $this->readStructureTextPFRun($stream, $pos + $arrayReturn['length'], $strLenRT); ++$arrayReturn['numTexts']; $arrayReturn['text' . $arrayReturn['numTexts']] = $strucTextPFRun; if (isset($strucTextPFRun['alignH'])) { $arrayReturn['alignH'] = $strucTextPFRun['alignH']; } $strLenRT = $strucTextPFRun['strLenRT']; $arrayReturn['length'] += $strucTextPFRun['length']; } while ($strLenRT > 0); // rgTextCFRun $strLenRT = $strLen + 1; do { $strucTextCFRun = $this->readStructureTextCFRun($stream, $pos + $arrayReturn['length'], $strLenRT); ++$arrayReturn['numParts']; $arrayReturn['part' . $arrayReturn['numParts']] = $strucTextCFRun; $strLenRT = $strucTextCFRun['strLenRT']; $arrayReturn['length'] += $strucTextCFRun['length']; } while ($strLenRT > 0); break; case self::RT_TEXTBYTESATOM: $arrayReturn['length'] += 8; // @link : https://msdn.microsoft.com/en-us/library/dd947905(v=office.12).aspx $strLen = (int) $rhChild['recLen']; for ($inc = 0; $inc < $strLen; ++$inc) { $char = self::getInt1d($stream, $pos + $arrayReturn['length']); if (0x0B == $char) { $char = 0x20; } $arrayReturn['text'] .= Text::chr($char); ++$arrayReturn['length']; } break; case self::RT_TEXTCHARSATOM: $arrayReturn['length'] += 8; // @link : http://msdn.microsoft.com/en-us/library/dd772921(v=office.12).aspx $strLen = (int) ($rhChild['recLen'] / 2); for ($inc = 0; $inc < $strLen; ++$inc) { $char = self::getInt2d($stream, $pos + $arrayReturn['length']); if (0x0B == $char) { $char = 0x20; } $arrayReturn['text'] .= Text::chr($char); $arrayReturn['length'] += 2; } break; case self::RT_TEXTHEADERATOM: $arrayReturn['length'] += 8; // @link : http://msdn.microsoft.com/en-us/library/dd905272(v=office.12).aspx // textType $arrayReturn['length'] += 4; break; case self::RT_TEXTINTERACTIVEINFOATOM: $arrayReturn['length'] += 8; //@link : http://msdn.microsoft.com/en-us/library/dd947973(v=office.12).aspx if (0x0000 == $rhChild['recInstance']) { //@todo : MouseClickTextInteractiveInfoAtom $arrayReturn['hyperlink'][\count($arrayReturn['hyperlink']) - 1]['start'] = self::getInt4d($stream, $pos + +$arrayReturn['length']); $arrayReturn['length'] += 4; $arrayReturn['hyperlink'][\count($arrayReturn['hyperlink']) - 1]['end'] = self::getInt4d($stream, $pos + +$arrayReturn['length']); $arrayReturn['length'] += 4; } if (0x0001 == $rhChild['recInstance']) { throw new FeatureNotImplementedException(); } break; case self::RT_TEXTSPECIALINFOATOM: $arrayReturn['length'] += 8; // @link : http://msdn.microsoft.com/en-us/library/dd945296(v=office.12).aspx $strLenRT = $strLen + 1; do { $structTextSIRun = $this->readStructureTextSIRun($stream, $pos + $arrayReturn['length'], $strLenRT); $strLenRT = $structTextSIRun['strLenRT']; $arrayReturn['length'] += $structTextSIRun['length']; } while ($strLenRT > 0); break; case self::RT_TEXTRULERATOM: $arrayReturn['length'] += 8; // @link : http://msdn.microsoft.com/en-us/library/dd953212(v=office.12).aspx $structRuler = $this->readStructureTextRuler($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $structRuler['length']; break; case self::RT_SLIDENUMBERMETACHARATOM: $datasRecord = $this->readRecordSlideNumberMCAtom($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $datasRecord['length']; break; default: $arrayReturn['length'] += 8; $arrayReturn['length'] += $rhChild['recLen']; } } while (($data['recLen'] - $arrayReturn['length']) > 0); } return $arrayReturn; } /** * The OfficeArtSpContainer record specifies a shape container. * * @return array{'length': int, 'shape': null|AbstractShape} * * @throws InvalidFileFormatException * * @see https://msdn.microsoft.com/en-us/library/dd943794(v=office.12).aspx */ private function readRecordOfficeArtSpContainer(string $stream, int $pos) { $arrayReturn = [ 'length' => 0, 'shape' => null, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF004 == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // shapeGroup $shapeGroup = $this->readRecordOfficeArtFSPGR($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $shapeGroup['length']; // shapeProp $shapeProp = $this->readRecordOfficeArtFSP($stream, $pos + $arrayReturn['length']); if (0 == $shapeProp['length']) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class); } $arrayReturn['length'] += $shapeProp['length']; if (0x1 == $shapeProp['fDeleted'] && 0x0 == $shapeProp['fChild']) { // deletedShape $deletedShape = $this->readRecordOfficeArtFPSPL($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $deletedShape['length']; } // shapePrimaryOptions $shpPrimaryOptions = $this->readRecordOfficeArtFOPT($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $shpPrimaryOptions['length']; // shapeSecondaryOptions1 $shpSecondaryOptions1 = $this->readRecordOfficeArtSecondaryFOPT($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $shpSecondaryOptions1['length']; // shapeTertiaryOptions1 $shpTertiaryOptions1 = $this->readRecordOfficeArtTertiaryFOPT($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $shpTertiaryOptions1['length']; // childAnchor $childAnchor = $this->readRecordOfficeArtChildAnchor($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $childAnchor['length']; // clientAnchor $clientAnchor = $this->readRecordOfficeArtClientAnchor($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $clientAnchor['length']; // clientData $clientData = $this->readRecordOfficeArtClientData($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $clientData['length']; // clientTextbox $clientTextbox = $this->readRecordOfficeArtClientTextbox($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $clientTextbox['length']; // shapeSecondaryOptions2 if (0 == $shpSecondaryOptions1['length']) { $shpSecondaryOptions2 = $this->readRecordOfficeArtSecondaryFOPT($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $shpSecondaryOptions2['length']; } // shapeTertiaryOptions2 if (0 == $shpTertiaryOptions1['length']) { $shpTertiaryOptions2 = $this->readRecordOfficeArtTertiaryFOPT($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $shpTertiaryOptions2['length']; } // Core : Shape // Informations about group are not defined $arrayDimensions = []; $bIsGroup = false; if (\is_object($this->oCurrentGroup)) { if (!$this->bFirstShapeGroup) { if ($clientAnchor['length'] > 0) { // $this->oCurrentGroup->setOffsetX($clientAnchor['left']); // $this->oCurrentGroup->setOffsetY($clientAnchor['top']); // $this->oCurrentGroup->setHeight($clientAnchor['height']); // $this->oCurrentGroup->setWidth($clientAnchor['width']); } $bIsGroup = true; $this->bFirstShapeGroup = true; } else { if ($childAnchor['length'] > 0) { $arrayDimensions = $childAnchor; } } } else { if ($clientAnchor['length'] > 0) { $arrayDimensions = $clientAnchor; } } if (!$bIsGroup) { // *** Shape *** if (isset($shpPrimaryOptions['pib'])) { // isDrawing $drawingPib = $shpPrimaryOptions['pib']; if (isset($this->arrayPictures[$drawingPib - 1])) { $gdImage = \imagecreatefromstring($this->arrayPictures[$drawingPib - 1]); $arrayReturn['shape'] = new Drawing\Gd(); $arrayReturn['shape']->setImageResource($gdImage); } } elseif (isset($shpPrimaryOptions['line']) && $shpPrimaryOptions['line']) { // isLine $arrayReturn['shape'] = new Line(0, 0, 0, 0); } elseif ($clientTextbox['length'] > 0) { $arrayReturn['shape'] = new RichText(); if (isset($clientTextbox['alignH'])) { $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setHorizontal($clientTextbox['alignH']); } $start = 0; $lastLevel = -1; $lastMarginLeft = 0; /* @phpstan-ignore-next-line */ for ($inc = 1; $inc <= $clientTextbox['numParts']; ++$inc) { if ($clientTextbox['numParts'] == $clientTextbox['numTexts'] && isset($clientTextbox['text' . $inc])) { if (isset($clientTextbox['text' . $inc]['bulletChar'])) { $arrayReturn['shape']->getActiveParagraph()->getBulletStyle()->setBulletType(Bullet::TYPE_BULLET); $arrayReturn['shape']->getActiveParagraph()->getBulletStyle()->setBulletChar($clientTextbox['text' . $inc]['bulletChar']); } // Indent $indent = 0; if (isset($clientTextbox['text' . $inc]['indent'])) { $indent = $clientTextbox['text' . $inc]['indent']; } if (isset($clientTextbox['text' . $inc]['leftMargin'])) { if ($lastMarginLeft > $clientTextbox['text' . $inc]['leftMargin']) { --$lastLevel; } if ($lastMarginLeft < $clientTextbox['text' . $inc]['leftMargin']) { ++$lastLevel; } $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setLevel($lastLevel); $lastMarginLeft = $clientTextbox['text' . $inc]['leftMargin']; $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setMarginLeft($clientTextbox['text' . $inc]['leftMargin']); $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setIndent($indent - $clientTextbox['text' . $inc]['leftMargin']); } } // Texte $sText = \substr(isset($clientTextbox['text']) ? $clientTextbox['text'] : '', $start, $clientTextbox['part' . $inc]['partLength']); $sHyperlinkURL = ''; if (empty($sText)) { // Is there a hyperlink ? if (!empty($clientTextbox['hyperlink'])) { foreach ($clientTextbox['hyperlink'] as $itmHyperlink) { if ($itmHyperlink['start'] == $start && ($itmHyperlink['end'] - $itmHyperlink['start']) == (float) $clientTextbox['part' . $inc]['partLength']) { $sText = $this->arrayHyperlinks[$itmHyperlink['id']]['text']; $sHyperlinkURL = $this->arrayHyperlinks[$itmHyperlink['id']]['url']; break; } } } } // New paragraph $bCreateParagraph = false; if (false !== \strpos($sText, "\r")) { $bCreateParagraph = true; $sText = \str_replace("\r", '', $sText); } // TextRun $txtRun = $arrayReturn['shape']->createTextRun($sText); if (isset($clientTextbox['part' . $inc]['bold'])) { $txtRun->getFont()->setBold($clientTextbox['part' . $inc]['bold']); } if (isset($clientTextbox['part' . $inc]['italic'])) { $txtRun->getFont()->setItalic($clientTextbox['part' . $inc]['italic']); } if (isset($clientTextbox['part' . $inc]['underline'])) { $txtRun->getFont()->setUnderline( $clientTextbox['part' . $inc]['underline'] ? Font::UNDERLINE_SINGLE : Font::UNDERLINE_NONE ); } if (isset($clientTextbox['part' . $inc]['fontName'])) { $txtRun->getFont()->setName($clientTextbox['part' . $inc]['fontName']); } if (isset($clientTextbox['part' . $inc]['fontSize'])) { $txtRun->getFont()->setSize($clientTextbox['part' . $inc]['fontSize']); } if (isset($clientTextbox['part' . $inc]['color'])) { $txtRun->getFont()->setColor($clientTextbox['part' . $inc]['color']); } // Hyperlink if (!empty($sHyperlinkURL)) { $txtRun->setHyperlink(new Hyperlink($sHyperlinkURL)); } $start += $clientTextbox['part' . $inc]['partLength']; if ($bCreateParagraph) { $arrayReturn['shape']->createParagraph(); } } } // *** Properties *** // Dimensions if ($arrayReturn['shape'] instanceof AbstractShape) { if (!empty($arrayDimensions)) { $arrayReturn['shape']->setOffsetX($arrayDimensions['left']); $arrayReturn['shape']->setOffsetY($arrayDimensions['top']); $arrayReturn['shape']->setHeight($arrayDimensions['height']); $arrayReturn['shape']->setWidth($arrayDimensions['width']); } // Rotation if (isset($shpPrimaryOptions['rotation'])) { $rotation = $shpPrimaryOptions['rotation']; $arrayReturn['shape']->setRotation($rotation); } // Shadow if (isset($shpPrimaryOptions['shadowOffsetX'], $shpPrimaryOptions['shadowOffsetY'])) { $shadowOffsetX = $shpPrimaryOptions['shadowOffsetX']; $shadowOffsetY = $shpPrimaryOptions['shadowOffsetY']; if (0 != $shadowOffsetX && 0 != $shadowOffsetX) { $arrayReturn['shape']->getShadow()->setVisible(true); if ($shadowOffsetX > 0 && $shadowOffsetX == $shadowOffsetY) { $arrayReturn['shape']->getShadow()->setDistance($shadowOffsetX)->setDirection(45); } } } // Specific Line if ($arrayReturn['shape'] instanceof Line) { if (isset($shpPrimaryOptions['lineColor'])) { $arrayReturn['shape']->getBorder()->getColor()->setARGB('FF' . $shpPrimaryOptions['lineColor']); } if (isset($shpPrimaryOptions['lineWidth'])) { $arrayReturn['shape']->setHeight($shpPrimaryOptions['lineWidth']); } } // Specific RichText if ($arrayReturn['shape'] instanceof RichText) { if (isset($shpPrimaryOptions['insetBottom'])) { $arrayReturn['shape']->setInsetBottom($shpPrimaryOptions['insetBottom']); } if (isset($shpPrimaryOptions['insetLeft'])) { $arrayReturn['shape']->setInsetLeft($shpPrimaryOptions['insetLeft']); } if (isset($shpPrimaryOptions['insetRight'])) { $arrayReturn['shape']->setInsetRight($shpPrimaryOptions['insetRight']); } if (isset($shpPrimaryOptions['insetTop'])) { $arrayReturn['shape']->setInsetTop($shpPrimaryOptions['insetTop']); } } } } else { // Rotation if (isset($shpPrimaryOptions['rotation'])) { $rotation = $shpPrimaryOptions['rotation']; $this->oCurrentGroup->setRotation($rotation); } } } return $arrayReturn; } /** * The OfficeArtSpgrContainer record specifies a container for groups of shapes. * * @param string $stream * @param int $pos * @param bool $bInGroup * * @return array * * @throws InvalidFileFormatException * * @see : https://msdn.microsoft.com/en-us/library/dd910416(v=office.12).aspx */ private function readRecordOfficeArtSpgrContainer($stream, $pos, $bInGroup = false) { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF003 == $data['recType']) { $arrayReturn['length'] += 8; do { $rhFileBlock = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']); if (!(0xF == $rhFileBlock['recVer'] && 0x0000 == $rhFileBlock['recInstance'] && (0xF003 == $rhFileBlock['recType'] || 0xF004 == $rhFileBlock['recType']))) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class); } switch ($rhFileBlock['recType']) { case 0xF003: // Core $this->oCurrentGroup = $this->oPhpPresentation->getActiveSlide()->createGroup(); $this->bFirstShapeGroup = false; // OfficeArtSpgrContainer $fileBlock = $this->readRecordOfficeArtSpgrContainer($stream, $pos + $arrayReturn['length'], true); $arrayReturn['length'] += $fileBlock['length']; $data['recLen'] -= $fileBlock['length']; break; case 0xF004: // Core if (!$bInGroup) { $this->oCurrentGroup = null; } // OfficeArtSpContainer $fileBlock = $this->readRecordOfficeArtSpContainer($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $fileBlock['length']; $data['recLen'] -= $fileBlock['length']; // Core //@todo if (!\is_null($fileBlock['shape'])) { switch ($this->inMainType) { case self::RT_NOTES: $arrayIdxSlide = \array_flip($this->arrayNotes); if ($this->currentNote > 0 && isset($arrayIdxSlide[$this->currentNote])) { $oSlide = $this->oPhpPresentation->getSlide($arrayIdxSlide[$this->currentNote]); if (0 == $oSlide->getNote()->getShapeCollection()->count()) { $oSlide->getNote()->addShape($fileBlock['shape']); } } break; case self::RT_SLIDE: if ($bInGroup) { $this->oCurrentGroup->addShape($fileBlock['shape']); } else { $this->oPhpPresentation->getActiveSlide()->addShape($fileBlock['shape']); } break; } } break; } } while ($data['recLen'] > 0); } return $arrayReturn; } /** * The OfficeArtTertiaryFOPT record specifies a table of OfficeArtRGFOPTE records,. * * @return array * * @throws FeatureNotImplementedException * * @see https://msdn.microsoft.com/en-us/library/dd950206(v=office.12).aspx */ private function readRecordOfficeArtTertiaryFOPT(string $stream, int $pos) { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x3 == $data['recVer'] && 0xF122 == $data['recType']) { // Record Header $arrayReturn['length'] += 8; $officeArtFOPTE = []; for ($inc = 0; $inc < $data['recInstance']; ++$inc) { $opid = self::getInt2d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $optOp = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; $officeArtFOPTE[] = [ 'opid' => ($opid >> 0) & \bindec('11111111111111'), 'fBid' => ($opid >> 14) & \bindec('1'), 'fComplex' => ($opid >> 15) & \bindec('1'), 'op' => $optOp, ]; } //@link : http://code.metager.de/source/xref/kde/calligra/filters/libmso/OPID foreach ($officeArtFOPTE as $opt) { switch ($opt['opid']) { case 0x039F: // Table properties //@link : https://msdn.microsoft.com/en-us/library/dd922773(v=office.12).aspx break; case 0x03A0: // Table Row Properties //@link : https://msdn.microsoft.com/en-us/library/dd923419(v=office.12).aspx if (0x1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; } break; case 0x03A9: // GroupShape : metroBlob //@link : https://msdn.microsoft.com/en-us/library/dd943388(v=office.12).aspx if (0x1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; } break; case 0x01FF: // Line Style Boolean //@link : https://msdn.microsoft.com/en-us/library/dd951605(v=office.12).aspx break; default: // var_dump('0x' . dechex($opt['opid'])); throw new FeatureNotImplementedException(); } } } return $arrayReturn; } /** * The OfficeArtDgContainer record specifies the container for all the file records for the objects in a drawing. * * @return array * * @see : https://msdn.microsoft.com/en-us/library/dd924455(v=office.12).aspx */ private function readRecordOfficeArtDgContainer(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF002 == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // drawingData $drawingData = $this->readRecordOfficeArtFDG($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $drawingData['length']; // regroupItems //@todo // groupShape $groupShape = $this->readRecordOfficeArtSpgrContainer($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $groupShape['length']; // shape $shape = $this->readRecordOfficeArtSpContainer($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $shape['length']; // solvers1 //@todo // deletedShapes //@todo // solvers1 //@todo } return $arrayReturn; } /** * The OfficeArtFDG record specifies the number of shapes, the drawing identifier, and the shape identifier of the last shape in a drawing. * * @return array * * @see : https://msdn.microsoft.com/en-us/library/dd946757(v=office.12).aspx */ private function readRecordOfficeArtFDG(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && $data['recInstance'] <= 0xFFE && 0xF008 == $data['recType'] && 0x00000008 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Length $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * The OfficeArtFOPT record specifies a table of OfficeArtRGFOPTE records. * * @return array * * @see https://msdn.microsoft.com/en-us/library/dd943404(v=office.12).aspx */ private function readRecordOfficeArtFOPT(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x3 == $data['recVer'] && 0xF00B == $data['recType']) { // Record Header $arrayReturn['length'] += 8; //@link : http://msdn.microsoft.com/en-us/library/dd906086(v=office.12).aspx $officeArtFOPTE = []; for ($inc = 0; $inc < $data['recInstance']; ++$inc) { $opid = self::getInt2d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $data['recLen'] -= 2; $optOp = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; $data['recLen'] -= 4; $officeArtFOPTE[] = [ 'opid' => ($opid >> 0) & \bindec('11111111111111'), 'fBid' => ($opid >> 14) & \bindec('1'), 'fComplex' => ($opid >> 15) & \bindec('1'), 'op' => $optOp, ]; } //@link : http://code.metager.de/source/xref/kde/calligra/filters/libmso/OPID foreach ($officeArtFOPTE as $opt) { // echo $opt['opid'].'-0x'.dechex($opt['opid']).EOL; switch ($opt['opid']) { case 0x0004: // Transform : rotation //@link : https://msdn.microsoft.com/en-us/library/dd949750(v=office.12).aspx $arrayReturn['rotation'] = $opt['op']; break; case 0x007F: // Transform : Protection Boolean Properties //@link : http://msdn.microsoft.com/en-us/library/dd909131(v=office.12).aspx break; case 0x0080: // Text : ltxid //@link : http://msdn.microsoft.com/en-us/library/dd947446(v=office.12).aspx break; case 0x0081: // Text : dxTextLeft //@link : http://msdn.microsoft.com/en-us/library/dd953234(v=office.12).aspx $arrayReturn['insetLeft'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x0082: // Text : dyTextTop //@link : http://msdn.microsoft.com/en-us/library/dd925068(v=office.12).aspx $arrayReturn['insetTop'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x0083: // Text : dxTextRight //@link : http://msdn.microsoft.com/en-us/library/dd906782(v=office.12).aspx $arrayReturn['insetRight'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x0084: // Text : dyTextBottom //@link : http://msdn.microsoft.com/en-us/library/dd772858(v=office.12).aspx $arrayReturn['insetBottom'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x0085: // Text : WrapText //@link : http://msdn.microsoft.com/en-us/library/dd924770(v=office.12).aspx break; case 0x0087: // Text : anchorText //@link : http://msdn.microsoft.com/en-us/library/dd948575(v=office.12).aspx break; case 0x00BF: // Text : Text Boolean Properties //@link : http://msdn.microsoft.com/en-us/library/dd950905(v=office.12).aspx break; case 0x0104: // Blip : pib //@link : http://msdn.microsoft.com/en-us/library/dd772837(v=office.12).aspx if (0 == $opt['fComplex']) { $arrayReturn['pib'] = $opt['op']; $data['recLen'] -= $opt['op']; } else { // pib Complex } break; case 0x13F: // Blip Boolean Properties //@link : https://msdn.microsoft.com/en-us/library/dd944215(v=office.12).aspx break; case 0x140: // Geometry : geoLeft //@link : http://msdn.microsoft.com/en-us/library/dd947489(v=office.12).aspx // print_r('geoLeft : '.$opt['op'].EOL); break; case 0x141: // Geometry : geoTop //@link : http://msdn.microsoft.com/en-us/library/dd949459(v=office.12).aspx // print_r('geoTop : '.$opt['op'].EOL); break; case 0x142: // Geometry : geoRight //@link : http://msdn.microsoft.com/en-us/library/dd947117(v=office.12).aspx // print_r('geoRight : '.$opt['op'].EOL); break; case 0x143: // Geometry : geoBottom //@link : http://msdn.microsoft.com/en-us/library/dd948602(v=office.12).aspx // print_r('geoBottom : '.$opt['op'].EOL); break; case 0x144: // Geometry : shapePath //@link : http://msdn.microsoft.com/en-us/library/dd945249(v=office.12).aspx $arrayReturn['line'] = true; break; case 0x145: // Geometry : pVertices //@link : http://msdn.microsoft.com/en-us/library/dd949814(v=office.12).aspx if (1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; $data['recLen'] -= $opt['op']; } break; case 0x146: // Geometry : pSegmentInfo //@link : http://msdn.microsoft.com/en-us/library/dd905742(v=office.12).aspx if (1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; $data['recLen'] -= $opt['op']; } break; case 0x155: // Geometry : pAdjustHandles //@link : http://msdn.microsoft.com/en-us/library/dd905890(v=office.12).aspx if (1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; $data['recLen'] -= $opt['op']; } break; case 0x156: // Geometry : pGuides //@link : http://msdn.microsoft.com/en-us/library/dd910801(v=office.12).aspx if (1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; $data['recLen'] -= $opt['op']; } break; case 0x157: // Geometry : pInscribe //@link : http://msdn.microsoft.com/en-us/library/dd904889(v=office.12).aspx if (1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; $data['recLen'] -= $opt['op']; } break; case 0x17F: // Geometry Boolean Properties //@link : http://msdn.microsoft.com/en-us/library/dd944968(v=office.12).aspx break; case 0x0180: // Fill : fillType //@link : http://msdn.microsoft.com/en-us/library/dd947909(v=office.12).aspx break; case 0x0181: // Fill : fillColor //@link : http://msdn.microsoft.com/en-us/library/dd921332(v=office.12).aspx $strColor = \str_pad(\dechex(($opt['op'] >> 0) & \bindec('11111111')), 2, '0', STR_PAD_LEFT); $strColor .= \str_pad(\dechex(($opt['op'] >> 8) & \bindec('11111111')), 2, '0', STR_PAD_LEFT); $strColor .= \str_pad(\dechex(($opt['op'] >> 16) & \bindec('11111111')), 2, '0', STR_PAD_LEFT); // echo 'fillColor : '.$strColor.EOL; break; case 0x0183: // Fill : fillBackColor //@link : http://msdn.microsoft.com/en-us/library/dd950634(v=office.12).aspx $strColor = \str_pad(\dechex(($opt['op'] >> 0) & \bindec('11111111')), 2, '0', STR_PAD_LEFT); $strColor .= \str_pad(\dechex(($opt['op'] >> 8) & \bindec('11111111')), 2, '0', STR_PAD_LEFT); $strColor .= \str_pad(\dechex(($opt['op'] >> 16) & \bindec('11111111')), 2, '0', STR_PAD_LEFT); // echo 'fillBackColor : '.$strColor.EOL; break; case 0x0193: // Fill : fillRectRight //@link : http://msdn.microsoft.com/en-us/library/dd951294(v=office.12).aspx // echo 'fillRectRight : '.\PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']).EOL; break; case 0x0194: // Fill : fillRectBottom //@link : http://msdn.microsoft.com/en-us/library/dd910194(v=office.12).aspx // echo 'fillRectBottom : '.\PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']).EOL; break; case 0x01BF: // Fill : Fill Style Boolean Properties //@link : http://msdn.microsoft.com/en-us/library/dd909380(v=office.12).aspx break; case 0x01C0: // Line Style : lineColor //@link : http://msdn.microsoft.com/en-us/library/dd920397(v=office.12).aspx $strColor = \str_pad(\dechex(($opt['op'] >> 0) & \bindec('11111111')), 2, '0', STR_PAD_LEFT); $strColor .= \str_pad(\dechex(($opt['op'] >> 8) & \bindec('11111111')), 2, '0', STR_PAD_LEFT); $strColor .= \str_pad(\dechex(($opt['op'] >> 16) & \bindec('11111111')), 2, '0', STR_PAD_LEFT); $arrayReturn['lineColor'] = $strColor; break; case 0x01C1: // Line Style : lineOpacity //@link : http://msdn.microsoft.com/en-us/library/dd923433(v=office.12).aspx // echo 'lineOpacity : '.dechex($opt['op']).EOL; break; case 0x01C2: // Line Style : lineBackColor //@link : http://msdn.microsoft.com/en-us/library/dd947669(v=office.12).aspx break; case 0x01CB: // Line Style : lineWidth //@link : http://msdn.microsoft.com/en-us/library/dd926964(v=office.12).aspx $arrayReturn['lineWidth'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x01D6: // Line Style : lineJoinStyle //@link : http://msdn.microsoft.com/en-us/library/dd909643(v=office.12).aspx break; case 0x01D7: // Line Style : lineEndCapStyle //@link : http://msdn.microsoft.com/en-us/library/dd925071(v=office.12).aspx break; case 0x01FF: // Line Style : Line Style Boolean Properties //@link : http://msdn.microsoft.com/en-us/library/dd951605(v=office.12).aspx break; case 0x0201: // Shadow Style : shadowColor //@link : http://msdn.microsoft.com/en-us/library/dd923454(v=office.12).aspx break; case 0x0204: // Shadow Style : shadowOpacity //@link : http://msdn.microsoft.com/en-us/library/dd920720(v=office.12).aspx break; case 0x0205: // Shadow Style : shadowOffsetX //@link : http://msdn.microsoft.com/en-us/library/dd945280(v=office.12).aspx $arrayReturn['shadowOffsetX'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x0206: // Shadow Style : shadowOffsetY //@link : http://msdn.microsoft.com/en-us/library/dd907855(v=office.12).aspx $arrayReturn['shadowOffsetY'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x023F: // Shadow Style : Shadow Style Boolean Properties //@link : http://msdn.microsoft.com/en-us/library/dd947887(v=office.12).aspx break; case 0x0304: // Shape : bWMode //@link : http://msdn.microsoft.com/en-us/library/dd947659(v=office.12).aspx break; case 0x033F: // Shape Boolean Properties //@link : http://msdn.microsoft.com/en-us/library/dd951345(v=office.12).aspx break; case 0x0380: // Group Shape Property Set : wzName //@link : http://msdn.microsoft.com/en-us/library/dd950681(v=office.12).aspx if (1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; $data['recLen'] -= $opt['op']; } break; case 0x03BF: // Group Shape Property Set : Group Shape Boolean Properties //@link : http://msdn.microsoft.com/en-us/library/dd949807(v=office.12).aspx break; default: } } if ($data['recLen'] > 0) { $arrayReturn['length'] += $data['recLen']; } } return $arrayReturn; } /** * The OfficeArtFPSPL record specifies the former hierarchical position of the containing object that is either a shape or a group of shapes. * * @return array * * @see https://msdn.microsoft.com/en-us/library/dd947479(v=office.12).aspx */ private function readRecordOfficeArtFPSPL(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF11D == $data['recType'] && 0x00000004 == $data['recLen']) { $arrayReturn['length'] += 8; $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * The OfficeArtFSP record specifies an instance of a shape. * * @return array * * @see https://msdn.microsoft.com/en-us/library/dd925898(v=office.12).aspx */ private function readRecordOfficeArtFSP(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x2 == $data['recVer'] && 0xF00A == $data['recType'] && 0x00000008 == $data['recLen']) { $arrayReturn['length'] += 8; // spid $arrayReturn['length'] += 4; // data $data = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; $arrayReturn['fGroup'] = ($data >> 0) & \bindec('1'); $arrayReturn['fChild'] = ($data >> 1) & \bindec('1'); $arrayReturn['fPatriarch'] = ($data >> 2) & \bindec('1'); $arrayReturn['fDeleted'] = ($data >> 3) & \bindec('1'); } return $arrayReturn; } /** * The OfficeArtFSPGR record specifies the coordinate system of the group shape that the anchors of the child shape are expressed in. * * @return array * * @see https://msdn.microsoft.com/en-us/library/dd925381(v=office.12).aspx */ private function readRecordOfficeArtFSPGR(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x1 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF009 == $data['recType'] && 0x00000010 == $data['recLen']) { $arrayReturn['length'] += 8; //$arrShapeGroup['xLeft'] = self::getInt4d($this->streamPowerpointDocument, $pos); $arrayReturn['length'] += 4; //$arrShapeGroup['yTop'] = self::getInt4d($this->streamPowerpointDocument, $pos); $arrayReturn['length'] += 4; //$arrShapeGroup['xRight'] = self::getInt4d($this->streamPowerpointDocument, $pos); $arrayReturn['length'] += 4; //$arrShapeGroup['yBottom'] = self::getInt4d($this->streamPowerpointDocument, $pos); $arrayReturn['length'] += 4; } return $arrayReturn; } /** * The OfficeArtSecondaryFOPT record specifies a table of OfficeArtRGFOPTE records. * * @return array * * @see https://msdn.microsoft.com/en-us/library/dd950259(v=office.12).aspx */ private function readRecordOfficeArtSecondaryFOPT(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x3 == $data['recVer'] && 0xF121 == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Length $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * A container record that specifies information about a shape. * * @return array * * @throws FeatureNotImplementedException * * @see : https://msdn.microsoft.com/en-us/library/dd950927(v=office.12).aspx */ private function readRecordOfficeArtClientData(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF011 == $data['recType']) { $arrayReturn['length'] += 8; // shapeFlagsAtom (9 bytes) $dataShapeFlagsAtom = $this->readRecordShapeFlagsAtom($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $dataShapeFlagsAtom['length']; // shapeFlags10Atom (9 bytes) $dataShapeFlags10Atom = $this->readRecordShapeFlags10Atom($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $dataShapeFlags10Atom['length']; // exObjRefAtom (12 bytes) $dataExObjRefAtom = $this->readRecordExObjRefAtom($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $dataExObjRefAtom['length']; // animationInfo (variable) $dataAnimationInfo = $this->readRecordAnimationInfoContainer($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $dataAnimationInfo['length']; // mouseClickInteractiveInfo (variable) $mouseClickInfo = $this->readRecordMouseClickInteractiveInfoContainer($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $mouseClickInfo['length']; // mouseOverInteractiveInfo (variable) $mouseOverInfo = $this->readRecordMouseOverInteractiveInfoContainer($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $mouseOverInfo['length']; // placeholderAtom (16 bytes) $dataPlaceholderAtom = $this->readRecordPlaceholderAtom($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $dataPlaceholderAtom['length']; // recolorInfoAtom (variable) $dataRecolorInfo = $this->readRecordRecolorInfoAtom($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $dataRecolorInfo['length']; // rgShapeClientRoundtripData (variable) $array = [ self::RT_PROGTAGS, self::RT_ROUNDTRIPNEWPLACEHOLDERID12ATOM, self::RT_ROUNDTRIPSHAPEID12ATOM, self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM, self::RT_ROUNDTRIPSHAPECHECKSUMFORCL12ATOM, ]; do { $dataHeaderRG = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']); if (\in_array($dataHeaderRG['recType'], $array)) { switch ($dataHeaderRG['recType']) { case self::RT_PROGTAGS: $dataRG = $this->readRecordShapeProgTagsContainer($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $dataRG['length']; break; case self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM: $dataRG = $this->readRecordRoundTripHFPlaceholder12Atom($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $dataRG['length']; break; case self::RT_ROUNDTRIPSHAPEID12ATOM: $dataRG = $this->readRecordRoundTripShapeId12Atom($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $dataRG['length']; break; default: // var_dump('0x' . dechex($dataHeaderRG['recType'])); throw new FeatureNotImplementedException(); } } } while (\in_array($dataHeaderRG['recType'], $array)); } return $arrayReturn; } /** * An atom record that specifies a persist object directory. Each persist object identifier specified MUST be unique in that persist object directory. * * @see http://msdn.microsoft.com/en-us/library/dd952680(v=office.12).aspx * * @throws InvalidFileFormatException */ private function readRecordPersistDirectoryAtom(string $stream, int $pos): void { $rHeader = $this->loadRecordHeader($stream, $pos); $pos += 8; if (0x0 != $rHeader['recVer'] || 0x000 != $rHeader['recInstance'] || self::RT_PERSISTDIRECTORYATOM != $rHeader['recType']) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : PersistDirectoryAtom > RecordHeader'); } // rgPersistDirEntry // @link : http://msdn.microsoft.com/en-us/library/dd947347(v=office.12).aspx do { $data = self::getInt4d($stream, $pos); $pos += 4; $rHeader['recLen'] -= 4; //$persistId = ($data >> 0) & bindec('11111111111111111111'); $cPersist = ($data >> 20) & \bindec('111111111111'); $rgPersistOffset = []; for ($inc = 0; $inc < $cPersist; ++$inc) { $rgPersistOffset[] = self::getInt4d($stream, $pos); $pos += 4; $rHeader['recLen'] -= 4; } } while ($rHeader['recLen'] > 0); $this->rgPersistDirEntry = $rgPersistOffset; } /** * A container record that specifies information about the headers (1) and footers within a slide. * * @see https://msdn.microsoft.com/en-us/library/dd904856(v=office.12).aspx * * @return array */ private function readRecordPerSlideHeadersFootersContainer(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_HEADERSFOOTERS == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Length $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * An atom record that specifies whether a shape is a placeholder shape. * * @see https://msdn.microsoft.com/en-us/library/dd923930(v=office.12).aspx * * @return array */ private function readRecordPlaceholderAtom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PLACEHOLDERATOM == $data['recType'] && 0x00000008 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * An atom record that specifies a collection of re-color mappings for a metafile ([MS-WMF]). * * @see https://msdn.microsoft.com/en-us/library/dd904899(v=office.12).aspx * * @return array */ private function readRecordRecolorInfoAtom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_RECOLORINFOATOM == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Datas $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * An atom record that specifies that a shape is a header or footerplaceholder shape. * * @see https://msdn.microsoft.com/en-us/library/dd910800(v=office.12).aspx * * @return array */ private function readRecordRoundTripHFPlaceholder12Atom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM == $data['recType'] && 0x00000001 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * An atom record that specifies a shape identifier. * * @see https://msdn.microsoft.com/en-us/library/dd772926(v=office.12).aspx * * @return array */ private function readRecordRoundTripShapeId12Atom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ROUNDTRIPSHAPEID12ATOM == $data['recType'] && 0x00000004 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Length $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * A container record that specifies information about a slide that synchronizes to a slide in a slide library. * * @see https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx * * @return array */ private function readRecordRoundTripSlideSyncInfo12Container(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ROUNDTRIPSLIDESYNCINFO12 == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Length $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * An atom record that specifies shape-level Boolean flags. * * @see https://msdn.microsoft.com/en-us/library/dd908949(v=office.12).aspx * * @return array */ private function readRecordShapeFlags10Atom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SHAPEFLAGS10ATOM == $data['recType'] && 0x00000001 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * An atom record that specifies shape-level Boolean flags. * * @see https://msdn.microsoft.com/en-us/library/dd925824(v=office.12).aspx * * @return array */ private function readRecordShapeFlagsAtom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SHAPEATOM == $data['recType'] && 0x00000001 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * A container record that specifies programmable tags with additional binary shape data. * * @see https://msdn.microsoft.com/en-us/library/dd911033(v=office.12).aspx * * @return array */ private function readRecordShapeProgBinaryTagContainer(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PROGBINARYTAG == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Datas $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * A container record that specifies programmable tags with additional shape data. * * @return array * * @throws FeatureNotImplementedException * * @see https://msdn.microsoft.com/en-us/library/dd911266(v=office.12).aspx */ private function readRecordShapeProgTagsContainer(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PROGTAGS == $data['recType']) { // Record Header $arrayReturn['length'] += 8; $length = 0; do { $dataHeaderRG = $this->loadRecordHeader($stream, $pos + $arrayReturn['length'] + $length); switch ($dataHeaderRG['recType']) { case self::RT_PROGBINARYTAG: $dataRG = $this->readRecordShapeProgBinaryTagContainer($stream, $pos + $arrayReturn['length'] + $length); $length += $dataRG['length']; break; //case self::RT_PROGSTRINGTAG: default: // var_dump('0x' . dechex($dataHeaderRG['recType'])); throw new FeatureNotImplementedException(); } } while ($length < $data['recLen']); // Datas $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * An atom record that specifies information about a slide. * * @return array * * @see https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx */ private function readRecordSlideAtom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x2 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SLIDEATOM == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // slideAtom > geom $arrayReturn['length'] += 4; // slideAtom > rgPlaceholderTypes $rgPlaceholderTypes = []; for ($inc = 0; $inc < 8; ++$inc) { $rgPlaceholderTypes[] = self::getInt1d($this->streamPowerpointDocument, $pos); ++$arrayReturn['length']; } // slideAtom > masterIdRef $arrayReturn['length'] += 4; // slideAtom > notesIdRef $arrayReturn['length'] += 4; // slideAtom > slideFlags $arrayReturn['length'] += 2; // slideAtom > unused; $arrayReturn['length'] += 2; } return $arrayReturn; } /** * A container record that specifies a presentation slide or title master slide. * * @throws InvalidFileFormatException * * @see http://msdn.microsoft.com/en-us/library/dd946323(v=office.12).aspx */ private function readRecordSlideContainer(string $stream, int $pos): void { // Core $this->oPhpPresentation->createSlide(); $this->oPhpPresentation->setActiveSlideIndex($this->oPhpPresentation->getSlideCount() - 1); // *** slideAtom (32 bytes) $slideAtom = $this->readRecordSlideAtom($stream, $pos); if (0 == $slideAtom['length']) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class); } $pos += $slideAtom['length']; // *** slideShowSlideInfoAtom (24 bytes) $slideShowInfoAtom = $this->readRecordSlideShowSlideInfoAtom($stream, $pos); $pos += $slideShowInfoAtom['length']; // *** perSlideHFContainer (variable) : optional $perSlideHFContainer = $this->readRecordPerSlideHeadersFootersContainer($stream, $pos); $pos += $perSlideHFContainer['length']; // *** rtSlideSyncInfo12 (variable) : optional $rtSlideSyncInfo12 = $this->readRecordRoundTripSlideSyncInfo12Container($stream, $pos); $pos += $rtSlideSyncInfo12['length']; // *** drawing (variable) $drawing = $this->readRecordDrawingContainer($stream, $pos); $pos += $drawing['length']; // *** slideSchemeColorSchemeAtom (40 bytes) $slideSchemeColorAtom = $this->readRecordSlideSchemeColorSchemeAtom($stream, $pos); if (0 == $slideSchemeColorAtom['length']) { // Record SlideSchemeColorSchemeAtom throw new InvalidFileFormatException($this->filename, PowerPoint97::class); } $pos += $slideSchemeColorAtom['length']; // *** slideNameAtom (variable) $slideNameAtom = $this->readRecordSlideNameAtom($stream, $pos); $pos += $slideNameAtom['length']; // *** slideProgTagsContainer (variable). $slideProgTags = $this->readRecordSlideProgTagsContainer($stream, $pos); $pos += $slideProgTags['length']; // *** rgRoundTripSlide (variable) } /** * An atom record that specifies the name of a slide. * * @see https://msdn.microsoft.com/en-us/library/dd906297(v=office.12).aspx * * @return array{'length': int, 'slideName': string} */ private function readRecordSlideNameAtom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, 'slideName' => '', ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x003 == $data['recInstance'] && self::RT_CSTRING == $data['recType'] && $data['recLen'] % 2 == 0) { // Record Header $arrayReturn['length'] += 8; // Length $strLen = ($data['recLen'] / 2); for ($inc = 0; $inc < $strLen; ++$inc) { $char = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $arrayReturn['slideName'] .= Text::chr($char); } } return $arrayReturn; } /** * An atom record that specifies a slide number metacharacter. * * @see https://msdn.microsoft.com/en-us/library/dd945703(v=office.12).aspx * * @return array */ private function readRecordSlideNumberMCAtom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SLIDENUMBERMETACHARATOM == $data['recType'] && 0x00000004 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * A container record that specifies programmable tags with additional slide data. * * @see https://msdn.microsoft.com/en-us/library/dd951946(v=office.12).aspx * * @return array */ private function readRecordSlideProgTagsContainer(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PROGTAGS == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Length $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * A container record that specifies the color scheme used by a slide. * * @see https://msdn.microsoft.com/en-us/library/dd949420(v=office.12).aspx * * @return array */ private function readRecordSlideSchemeColorSchemeAtom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x001 == $data['recInstance'] && self::RT_COLORSCHEMEATOM == $data['recType'] && 0x00000020 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Length $rgSchemeColor = []; for ($inc = 0; $inc <= 7; ++$inc) { $rgSchemeColor[] = [ 'red' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4), 'green' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4 + 1), 'blue' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4 + 2), ]; } $arrayReturn['length'] += (8 * 4); } return $arrayReturn; } /** * An atom record that specifies what transition effect to perform during a slide show, and how to advance to the next presentation slide. * * @see https://msdn.microsoft.com/en-us/library/dd943408(v=office.12).aspx * * @return array */ private function readRecordSlideShowSlideInfoAtom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SLIDESHOWSLIDEINFOATOM == $data['recType'] && 0x00000010 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Length; $arrayReturn['length'] += $data['recLen']; } return $arrayReturn; } /** * UserEditAtom. * * @see http://msdn.microsoft.com/en-us/library/dd945746(v=office.12).aspx * * @throws InvalidFileFormatException */ private function readRecordUserEditAtom(string $stream, int $pos): void { $rHeader = $this->loadRecordHeader($stream, $pos); $pos += 8; if (0x0 != $rHeader['recVer'] || 0x000 != $rHeader['recInstance'] || self::RT_USEREDITATOM != $rHeader['recType'] || (0x0000001C != $rHeader['recLen'] && 0x00000020 != $rHeader['recLen'])) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : UserEditAtom > RecordHeader'); } // lastSlideIdRef $pos += 4; // version $pos += 2; // minorVersion $minorVersion = self::getInt1d($stream, $pos); ++$pos; if (0x00 != $minorVersion) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : UserEditAtom > minorVersion'); } // majorVersion $majorVersion = self::getInt1d($stream, $pos); ++$pos; if (0x03 != $majorVersion) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : UserEditAtom > majorVersion'); } // offsetLastEdit $pos += 4; // offsetPersistDirectory $this->offsetPersistDirectory = self::getInt4d($stream, $pos); $pos += 4; // docPersistIdRef $docPersistIdRef = self::getInt4d($stream, $pos); $pos += 4; if (0x00000001 != $docPersistIdRef) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : UserEditAtom > docPersistIdRef'); } // persistIdSeed $pos += 4; // lastView $pos += 2; // unused $pos += 2; } /** * A structure that specifies the character-level formatting of a run of text. * * @return array{'length': int, 'strLenRT': int, 'partLength': int, 'bold': bool, 'italic': bool, 'underline': bool, 'fontName': string, 'fontSize': int, 'color': Color} * * @throws FeatureNotImplementedException * * @see https://msdn.microsoft.com/en-us/library/dd945870(v=office.12).aspx */ private function readStructureTextCFRun(string $stream, int $pos, int $strLenRT): array { $arrayReturn = [ 'length' => 0, 'strLenRT' => $strLenRT, ]; // rgTextCFRun $countRgTextCFRun = self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['strLenRT'] -= $countRgTextCFRun; $arrayReturn['length'] += 4; $arrayReturn['partLength'] = $countRgTextCFRun; $masks = self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; $masksData = []; $masksData['bold'] = ($masks >> 0) & \bindec('1'); $masksData['italic'] = ($masks >> 1) & \bindec('1'); $masksData['underline'] = ($masks >> 2) & \bindec('1'); $masksData['unused1'] = ($masks >> 3) & \bindec('1'); $masksData['shadow'] = ($masks >> 4) & \bindec('1'); $masksData['fehint'] = ($masks >> 5) & \bindec('1'); $masksData['unused2'] = ($masks >> 6) & \bindec('1'); $masksData['kumi'] = ($masks >> 7) & \bindec('1'); $masksData['unused3'] = ($masks >> 8) & \bindec('1'); $masksData['emboss'] = ($masks >> 9) & \bindec('1'); $masksData['fHasStyle'] = ($masks >> 10) & \bindec('1111'); $masksData['unused4'] = ($masks >> 14) & \bindec('11'); $masksData['typeface'] = ($masks >> 16) & \bindec('1'); $masksData['size'] = ($masks >> 17) & \bindec('1'); $masksData['color'] = ($masks >> 18) & \bindec('1'); $masksData['position'] = ($masks >> 19) & \bindec('1'); $masksData['pp10ext'] = ($masks >> 20) & \bindec('1'); $masksData['oldEATypeface'] = ($masks >> 21) & \bindec('1'); $masksData['ansiTypeface'] = ($masks >> 22) & \bindec('1'); $masksData['symbolTypeface'] = ($masks >> 23) & \bindec('1'); $masksData['newEATypeface'] = ($masks >> 24) & \bindec('1'); $masksData['csTypeface'] = ($masks >> 25) & \bindec('1'); $masksData['pp11ext'] = ($masks >> 26) & \bindec('1'); if (1 == $masksData['bold'] || 1 == $masksData['italic'] || 1 == $masksData['underline'] || 1 == $masksData['shadow'] || 1 == $masksData['fehint'] || 1 == $masksData['kumi'] || 1 == $masksData['emboss'] || 1 == $masksData['fHasStyle']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $fontStyleFlags = []; $fontStyleFlags['bold'] = ($data >> 0) & \bindec('1'); $fontStyleFlags['italic'] = ($data >> 1) & \bindec('1'); $fontStyleFlags['underline'] = ($data >> 2) & \bindec('1'); $fontStyleFlags['unused1'] = ($data >> 3) & \bindec('1'); $fontStyleFlags['shadow'] = ($data >> 4) & \bindec('1'); $fontStyleFlags['fehint'] = ($data >> 5) & \bindec('1'); $fontStyleFlags['unused2'] = ($data >> 6) & \bindec('1'); $fontStyleFlags['kumi'] = ($data >> 7) & \bindec('1'); $fontStyleFlags['unused3'] = ($data >> 8) & \bindec('1'); $fontStyleFlags['emboss'] = ($data >> 9) & \bindec('1'); $fontStyleFlags['pp9rt'] = ($data >> 10) & \bindec('1111'); $fontStyleFlags['unused4'] = ($data >> 14) & \bindec('11'); $arrayReturn['bold'] = (1 == $fontStyleFlags['bold']) ? true : false; $arrayReturn['italic'] = (1 == $fontStyleFlags['italic']) ? true : false; $arrayReturn['underline'] = (1 == $fontStyleFlags['underline']) ? true : false; } if (1 == $masksData['typeface']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $arrayReturn['fontName'] = isset($this->arrayFonts[$data]) ? $this->arrayFonts[$data] : ''; } if (1 == $masksData['oldEATypeface']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['ansiTypeface']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['symbolTypeface']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['size']) { $arrayReturn['fontSize'] = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['color']) { $red = self::getInt1d($stream, $pos + $arrayReturn['length']); ++$arrayReturn['length']; $green = self::getInt1d($stream, $pos + $arrayReturn['length']); ++$arrayReturn['length']; $blue = self::getInt1d($stream, $pos + $arrayReturn['length']); ++$arrayReturn['length']; $index = self::getInt1d($stream, $pos + $arrayReturn['length']); ++$arrayReturn['length']; if (0xFE == $index) { $strColor = \str_pad(\dechex($red), 2, '0', STR_PAD_LEFT); $strColor .= \str_pad(\dechex($green), 2, '0', STR_PAD_LEFT); $strColor .= \str_pad(\dechex($blue), 2, '0', STR_PAD_LEFT); $arrayReturn['color'] = new Color('FF' . $strColor); } } if (1 == $masksData['position']) { throw new FeatureNotImplementedException(); } return $arrayReturn; } /** * A structure that specifies the paragraph-level formatting of a run of text. * * @return array{'length': int, 'strLenRT': int, 'alignH': string|null, 'bulletChar': string, 'leftMargin': int, 'indent': int} * * @throws FeatureNotImplementedException * * @see https://msdn.microsoft.com/en-us/library/dd923535(v=office.12).aspx */ private function readStructureTextPFRun(string $stream, int $pos, int $strLenRT): array { $arrayReturn = [ 'length' => 0, 'strLenRT' => $strLenRT, ]; // rgTextPFRun $countRgTextPFRun = self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['strLenRT'] -= $countRgTextPFRun; $arrayReturn['length'] += 4; // indent $arrayReturn['length'] += 2; $masks = self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; $masksData = []; $masksData['hasBullet'] = ($masks >> 0) & \bindec('1'); $masksData['bulletHasFont'] = ($masks >> 1) & \bindec('1'); $masksData['bulletHasColor'] = ($masks >> 2) & \bindec('1'); $masksData['bulletHasSize'] = ($masks >> 3) & \bindec('1'); $masksData['bulletFont'] = ($masks >> 4) & \bindec('1'); $masksData['bulletColor'] = ($masks >> 5) & \bindec('1'); $masksData['bulletSize'] = ($masks >> 6) & \bindec('1'); $masksData['bulletChar'] = ($masks >> 7) & \bindec('1'); $masksData['leftMargin'] = ($masks >> 8) & \bindec('1'); $masksData['unused'] = ($masks >> 9) & \bindec('1'); $masksData['indent'] = ($masks >> 10) & \bindec('1'); $masksData['align'] = ($masks >> 11) & \bindec('1'); $masksData['lineSpacing'] = ($masks >> 12) & \bindec('1'); $masksData['spaceBefore'] = ($masks >> 13) & \bindec('1'); $masksData['spaceAfter'] = ($masks >> 14) & \bindec('1'); $masksData['defaultTabSize'] = ($masks >> 15) & \bindec('1'); $masksData['fontAlign'] = ($masks >> 16) & \bindec('1'); $masksData['charWrap'] = ($masks >> 17) & \bindec('1'); $masksData['wordWrap'] = ($masks >> 18) & \bindec('1'); $masksData['overflow'] = ($masks >> 19) & \bindec('1'); $masksData['tabStops'] = ($masks >> 20) & \bindec('1'); $masksData['textDirection'] = ($masks >> 21) & \bindec('1'); $masksData['reserved1'] = ($masks >> 22) & \bindec('1'); $masksData['bulletBlip'] = ($masks >> 23) & \bindec('1'); $masksData['bulletScheme'] = ($masks >> 24) & \bindec('1'); $masksData['bulletHasScheme'] = ($masks >> 25) & \bindec('1'); $bulletFlags = []; if (1 == $masksData['hasBullet'] || 1 == $masksData['bulletHasFont'] || 1 == $masksData['bulletHasColor'] || 1 == $masksData['bulletHasSize']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $bulletFlags['fHasBullet'] = ($data >> 0) & \bindec('1'); $bulletFlags['fBulletHasFont'] = ($data >> 1) & \bindec('1'); $bulletFlags['fBulletHasColor'] = ($data >> 2) & \bindec('1'); $bulletFlags['fBulletHasSize'] = ($data >> 3) & \bindec('1'); } if (1 == $masksData['bulletChar']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $arrayReturn['bulletChar'] = \chr($data); } if (1 == $masksData['bulletFont']) { // $data = self::getInt2d($stream, $pos); $arrayReturn['length'] += 2; } if (1 == $masksData['bulletSize']) { // $data = self::getInt2d($stream, $pos); $arrayReturn['length'] += 2; } if (1 == $masksData['bulletColor']) { // $red = self::getInt1d($stream, $pos + $arrayReturn['length']); ++$arrayReturn['length']; // $green = self::getInt1d($stream, $pos + $arrayReturn['length']); ++$arrayReturn['length']; // $blue = self::getInt1d($stream, $pos + $arrayReturn['length']); ++$arrayReturn['length']; $index = self::getInt1d($stream, $pos + $arrayReturn['length']); ++$arrayReturn['length']; if (0xFE == $index) { // $strColor = str_pad(dechex($red), 2, '0', STR_PAD_LEFT); // $strColor .= str_pad(dechex($green), 2, '0', STR_PAD_LEFT); // $strColor .= str_pad(dechex($blue), 2, '0', STR_PAD_LEFT); } } if (1 == $masksData['align']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; switch ($data) { case 0x0000: $arrayReturn['alignH'] = Alignment::HORIZONTAL_LEFT; break; case 0x0001: $arrayReturn['alignH'] = Alignment::HORIZONTAL_CENTER; break; case 0x0002: $arrayReturn['alignH'] = Alignment::HORIZONTAL_RIGHT; break; case 0x0003: $arrayReturn['alignH'] = Alignment::HORIZONTAL_JUSTIFY; break; case 0x0004: $arrayReturn['alignH'] = Alignment::HORIZONTAL_DISTRIBUTED; break; case 0x0005: $arrayReturn['alignH'] = Alignment::HORIZONTAL_DISTRIBUTED; break; case 0x0006: $arrayReturn['alignH'] = Alignment::HORIZONTAL_JUSTIFY; break; default: break; } } if (1 == $masksData['lineSpacing']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['spaceBefore']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['spaceAfter']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['leftMargin']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $arrayReturn['leftMargin'] = (int) \round($data / 6); } if (1 == $masksData['indent']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $arrayReturn['indent'] = (int) \round($data / 6); } if (1 == $masksData['defaultTabSize']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['tabStops']) { throw new FeatureNotImplementedException(); } if (1 == $masksData['fontAlign']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['charWrap'] || 1 == $masksData['wordWrap'] || 1 == $masksData['overflow']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['textDirection']) { throw new FeatureNotImplementedException(); } return $arrayReturn; } /** * A structure that specifies language and spelling information for a run of text. * * @return array * * @throws FeatureNotImplementedException * * @see https://msdn.microsoft.com/en-us/library/dd909603(v=office.12).aspx */ private function readStructureTextSIRun(string $stream, int $pos, int $strLenRT): array { $arrayReturn = [ 'length' => 0, 'strLenRT' => $strLenRT, ]; $arrayReturn['strLenRT'] -= self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; $data = self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; $masksData = []; $masksData['spell'] = ($data >> 0) & \bindec('1'); $masksData['lang'] = ($data >> 1) & \bindec('1'); $masksData['altLang'] = ($data >> 2) & \bindec('1'); $masksData['unused1'] = ($data >> 3) & \bindec('1'); $masksData['unused2'] = ($data >> 4) & \bindec('1'); $masksData['fPp10ext'] = ($data >> 5) & \bindec('1'); $masksData['fBidi'] = ($data >> 6) & \bindec('1'); $masksData['unused3'] = ($data >> 7) & \bindec('1'); $masksData['reserved1'] = ($data >> 8) & \bindec('1'); $masksData['smartTag'] = ($data >> 9) & \bindec('1'); if (1 == $masksData['spell']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $masksSpell = []; $masksSpell['error'] = ($data >> 0) & \bindec('1'); $masksSpell['clean'] = ($data >> 1) & \bindec('1'); $masksSpell['grammar'] = ($data >> 2) & \bindec('1'); } if (1 == $masksData['lang']) { // $data = self::getInt2d($stream, $pos); $arrayReturn['length'] += 2; } if (1 == $masksData['altLang']) { // $data = self::getInt2d($stream, $pos); $arrayReturn['length'] += 2; } if (1 == $masksData['fBidi']) { throw new FeatureNotImplementedException(); } if (1 == $masksData['fPp10ext']) { throw new FeatureNotImplementedException(); } if (1 == $masksData['smartTag']) { throw new FeatureNotImplementedException(); } return $arrayReturn; } /** * A structure that specifies tabbing, margins, and indentation for text. * * @return array * * @throws FeatureNotImplementedException * * @see https://msdn.microsoft.com/en-us/library/dd922749(v=office.12).aspx */ private function readStructureTextRuler(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; $masksData = []; $masksData['fDefaultTabSize'] = ($data >> 0) & \bindec('1'); $masksData['fCLevels'] = ($data >> 1) & \bindec('1'); $masksData['fTabStops'] = ($data >> 2) & \bindec('1'); $masksData['fLeftMargin1'] = ($data >> 3) & \bindec('1'); $masksData['fLeftMargin2'] = ($data >> 4) & \bindec('1'); $masksData['fLeftMargin3'] = ($data >> 5) & \bindec('1'); $masksData['fLeftMargin4'] = ($data >> 6) & \bindec('1'); $masksData['fLeftMargin5'] = ($data >> 7) & \bindec('1'); $masksData['fIndent1'] = ($data >> 8) & \bindec('1'); $masksData['fIndent2'] = ($data >> 9) & \bindec('1'); $masksData['fIndent3'] = ($data >> 10) & \bindec('1'); $masksData['fIndent4'] = ($data >> 11) & \bindec('1'); $masksData['fIndent5'] = ($data >> 12) & \bindec('1'); if (1 == $masksData['fCLevels']) { throw new FeatureNotImplementedException(); } if (1 == $masksData['fDefaultTabSize']) { throw new FeatureNotImplementedException(); } if (1 == $masksData['fTabStops']) { $count = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $arrayTabStops = []; for ($inc = 0; $inc < $count; ++$inc) { $position = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $type = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $arrayTabStops[] = [ 'position' => $position, 'type' => $type, ]; } } if (1 == $masksData['fLeftMargin1']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['fIndent1']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['fLeftMargin2']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['fIndent2']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['fLeftMargin3']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['fIndent3']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['fLeftMargin4']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['fIndent4']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['fLeftMargin5']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } if (1 == $masksData['fIndent5']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } return $arrayReturn; } private function readRecordNotesContainer(string $stream, int $pos): void { // notesAtom $notesAtom = $this->readRecordNotesAtom($stream, $pos); $pos += $notesAtom['length']; // drawing $drawing = $this->readRecordDrawingContainer($stream, $pos); $pos += $drawing['length']; // slideSchemeColorSchemeAtom // slideNameAtom // slideProgTagsContainer // rgNotesRoundTripAtom } /** * @return array * * @throws InvalidFileFormatException */ private function readRecordNotesAtom(string $stream, int $pos): array { $arrayReturn = [ 'length' => 0, ]; $data = $this->loadRecordHeader($stream, $pos); if (0x1 != $data['recVer'] || 0x000 != $data['recInstance'] || self::RT_NOTESATOM != $data['recType'] || 0x00000008 != $data['recLen']) { throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : NotesAtom > RecordHeader)'); } // Record Header $arrayReturn['length'] += 8; // NotesAtom > slideIdRef $notesIdRef = self::getInt4d($stream, $pos + $arrayReturn['length']); if (-2147483648 == $notesIdRef) { $notesIdRef = 0; } $this->currentNote = $notesIdRef; $arrayReturn['length'] += 4; // NotesAtom > slideFlags $arrayReturn['length'] += 2; // NotesAtom > unused $arrayReturn['length'] += 2; return $arrayReturn; } }