diff --git a/src/PhpPresentation/Reader/ODPresentation.php b/src/PhpPresentation/Reader/ODPresentation.php index ddf750dfc..77f29a207 100644 --- a/src/PhpPresentation/Reader/ODPresentation.php +++ b/src/PhpPresentation/Reader/ODPresentation.php @@ -31,6 +31,7 @@ use PhpOffice\PhpPresentation\PresentationProperties; use PhpOffice\PhpPresentation\Shape\Drawing\Base64; use PhpOffice\PhpPresentation\Shape\Drawing\Gd; +use PhpOffice\PhpPresentation\Shape\Media; use PhpOffice\PhpPresentation\Shape\RichText; use PhpOffice\PhpPresentation\Shape\RichText\Paragraph; use PhpOffice\PhpPresentation\Slide\Background\Color as BackgroundColor; @@ -276,12 +277,14 @@ protected function loadStyle(DOMElement $nodeStyle): bool if ('bitmap' == $nodeDrawingPageProps->getAttribute('draw:fill') && $nodeDrawingPageProps->hasAttribute('draw:fill-image-name')) { $nameStyle = $nodeDrawingPageProps->getAttribute('draw:fill-image-name'); if (!empty($this->arrayCommonStyles[$nameStyle]) && 'image' == $this->arrayCommonStyles[$nameStyle]['type'] && !empty($this->arrayCommonStyles[$nameStyle]['path'])) { - $tmpBkgImg = tempnam(sys_get_temp_dir(), 'PhpPresentationReaderODPBkg'); $contentImg = $this->oZip->getFromName($this->arrayCommonStyles[$nameStyle]['path']); - file_put_contents($tmpBkgImg, $contentImg); + if ($contentImg) { + $tmpFile = new Gd(); + $tmpFile->loadFromContent($contentImg, basename($this->arrayCommonStyles[$nameStyle]['path'])); - $oBackground = new Image(); - $oBackground->setPath($tmpBkgImg); + $oBackground = new Image(); + $oBackground->setImage($tmpFile); + } } } } @@ -547,6 +550,11 @@ protected function loadSlide(DOMElement $nodeSlide): bool if ($this->oXMLReader->getElement('draw:text-box', $oNodeFrame)) { $this->loadShapeRichText($oNodeFrame); + continue; + } + if ($this->oXMLReader->getElement('draw:plugin', $oNodeFrame)) { + $this->loadShapeMedia($oNodeFrame); + continue; } } @@ -585,7 +593,7 @@ protected function loadShapeDrawing(DOMElement $oNodeFrame): void // Contents of file if (empty($mimetype)) { $shape = new Gd(); - $shape->setImageResource(imagecreatefromstring($imageFile)); + $shape->loadFromContent($imageFile, basename($sFilename)); } else { $shape = new Base64(); $shape->setData('data:' . $mimetype . ';base64,' . base64_encode($imageFile)); @@ -643,6 +651,60 @@ protected function loadShapeRichText(DOMElement $oNodeFrame): void } } + /** + * Read Shape Media. + */ + protected function loadShapeMedia(DOMElement $oNodeFrame): void + { + $oNodePlugin = $this->oXMLReader->getElement('draw:plugin', $oNodeFrame); + if (!($oNodePlugin instanceof DOMElement)) { + return; + } + + $mediaFile = null; + $filePath = null; + if ($oNodePlugin->hasAttribute('xlink:href')) { + $filePath = $oNodePlugin->getAttribute('xlink:href'); + if (!$filePath) { + return; + } + + $filePathParts = explode('/', $filePath); + if ($filePathParts[0] !== 'Media') { + return; + } + + $mediaFile = $this->oZip->getFromName($filePath); + } + + if (!$mediaFile) { + return; + } + + $shape = new Media(); + $shape->loadFromContent($mediaFile, basename($filePath)); + + $shape->getShadow()->setVisible(false); + $shape->setName($oNodeFrame->hasAttribute('draw:name') ? $oNodeFrame->getAttribute('draw:name') : ''); + $shape->setDescription($oNodeFrame->hasAttribute('draw:name') ? $oNodeFrame->getAttribute('draw:name') : ''); + $shape->setResizeProportional(false); + $shape->setWidth($oNodeFrame->hasAttribute('svg:width') ? CommonDrawing::centimetersToPixels((float) substr($oNodeFrame->getAttribute('svg:width'), 0, -2)) : 0); + $shape->setHeight($oNodeFrame->hasAttribute('svg:height') ? CommonDrawing::centimetersToPixels((float) substr($oNodeFrame->getAttribute('svg:height'), 0, -2)) : 0); + $shape->setResizeProportional(true); + $shape->setOffsetX($oNodeFrame->hasAttribute('svg:x') ? CommonDrawing::centimetersToPixels((float) substr($oNodeFrame->getAttribute('svg:x'), 0, -2)) : 0); + $shape->setOffsetY($oNodeFrame->hasAttribute('svg:y') ? CommonDrawing::centimetersToPixels((float) substr($oNodeFrame->getAttribute('svg:y'), 0, -2)) : 0); + + if ($oNodeFrame->hasAttribute('draw:style-name')) { + $keyStyle = $oNodeFrame->getAttribute('draw:style-name'); + if (isset($this->arrayStyles[$keyStyle])) { + $shape->setShadow($this->arrayStyles[$keyStyle]['shadow']); + $shape->setFill($this->arrayStyles[$keyStyle]['fill']); + } + } + + $this->oPhpPresentation->getActiveSlide()->addShape($shape); + } + /** * Read Paragraph. */ diff --git a/src/PhpPresentation/Reader/PowerPoint2007.php b/src/PhpPresentation/Reader/PowerPoint2007.php index d93a19168..773973dce 100644 --- a/src/PhpPresentation/Reader/PowerPoint2007.php +++ b/src/PhpPresentation/Reader/PowerPoint2007.php @@ -34,9 +34,11 @@ use PhpOffice\PhpPresentation\PhpPresentation; use PhpOffice\PhpPresentation\PresentationProperties; use PhpOffice\PhpPresentation\Shape\Chart; +use PhpOffice\PhpPresentation\Shape\Drawing\AbstractDrawingAdapter; use PhpOffice\PhpPresentation\Shape\Drawing\Base64; use PhpOffice\PhpPresentation\Shape\Drawing\Gd; use PhpOffice\PhpPresentation\Shape\Hyperlink; +use PhpOffice\PhpPresentation\Shape\Media; use PhpOffice\PhpPresentation\Shape\Placeholder; use PhpOffice\PhpPresentation\Shape\RichText; use PhpOffice\PhpPresentation\Shape\RichText\Paragraph; @@ -448,7 +450,6 @@ protected function loadSlide(string $sPart, string $baseFile): void $oSlide = $this->oPhpPresentation->createSlide(); $this->oPhpPresentation->setActiveSlideIndex($this->oPhpPresentation->getSlideCount() - 1); $oSlide->setRelsIndex('ppt/slides/_rels/' . $baseFile . '.rels'); - // Background $oElement = $xmlReader->getElement('/p:sld/p:cSld/p:bg/p:bgPr'); if ($oElement instanceof DOMElement) { @@ -490,25 +491,26 @@ protected function loadSlide(string $sPart, string $baseFile): void } $pathImage = implode('/', $pathImage); $contentImg = $this->oZip->getFromName($pathImage); + $fileName = basename($pathImage); + + $tmpFile = new Gd(); + $tmpFile->loadFromContent($contentImg, $fileName); - $tmpBkgImg = tempnam(sys_get_temp_dir(), 'PhpPresentationReaderPpt2007Bkg'); - file_put_contents($tmpBkgImg, $contentImg); // Background $oBackground = new Slide\Background\Image(); $oBackground - ->setPath($tmpBkgImg) - ->setExtension(pathinfo($pathImage, PATHINFO_EXTENSION)); + ->setImage($tmpFile) + ->setExtension(pathinfo($pathImage, PATHINFO_EXTENSION));; + // Slide Background $oSlide = $this->oPhpPresentation->getActiveSlide(); $oSlide->setBackground($oBackground); } } } - // Shapes $arrayElements = $xmlReader->getElements('/p:sld/p:cSld/p:spTree/*'); $this->loadSlideShapes($xmlReader, $oSlide, $arrayElements, $xmlReader); - // Layout $oSlide = $this->oPhpPresentation->getActiveSlide(); foreach ($this->arrayRels['ppt/slides/_rels/' . $baseFile . '.rels'] as $valueRel) { @@ -517,7 +519,6 @@ protected function loadSlide(string $sPart, string $baseFile): void if (array_key_exists($layoutBasename, $this->arraySlideLayouts)) { $oSlide->setSlideLayout($this->arraySlideLayouts[$layoutBasename]); } - break; } } @@ -565,7 +566,6 @@ protected function loadMasterSlide(string $sPart, string $baseFile): void continue; } $oRTParagraph = new Paragraph(); - if ('a:defPPr' == $oElementLvl->nodeName) { $level = 0; } else { @@ -573,7 +573,6 @@ protected function loadMasterSlide(string $sPart, string $baseFile): void $level = str_replace('pPr', '', $level); $level = (int) $level; } - if ($oElementLvl->hasAttribute('algn')) { $oRTParagraph->getAlignment()->setHorizontal($oElementLvl->getAttribute('algn')); } @@ -612,7 +611,6 @@ protected function loadMasterSlide(string $sPart, string $baseFile): void $oRTParagraph->getFont()->setColor($oSchemeColor); } } - switch ($oElementTxStyle->nodeName) { case 'p:bodyStyle': $oSlideMaster->getTextStyles()->setBodyStyleAtLvl($oRTParagraph, $level); @@ -637,7 +635,6 @@ protected function loadMasterSlide(string $sPart, string $baseFile): void if (false !== $pptTheme) { $this->loadTheme($pptTheme, $oSlideMaster); } - break; } } @@ -702,7 +699,6 @@ protected function loadLayoutSlide(string $sPart, string $baseFile, SlideMaster return $oSlideLayout; } - // @phpstan-ignore-next-line return null; } @@ -774,12 +770,15 @@ protected function loadSlideBackground(XMLReader $xmlReader, DOMElement $oElemen } $pathImage = implode('/', $pathImage); $contentImg = $this->oZip->getFromName($pathImage); + $fileName = basename($pathImage); + + $tmpFile = new Gd(); + $tmpFile->loadFromContent($contentImg, $fileName); - $tmpBkgImg = tempnam(sys_get_temp_dir(), 'PhpPresentationReaderPpt2007Bkg'); - file_put_contents($tmpBkgImg, $contentImg); // Background $oBackground = new Slide\Background\Image(); - $oBackground->setPath($tmpBkgImg); + $oBackground->setImage($tmpFile); + // Slide Background $oSlide->setBackground($oBackground); } @@ -793,7 +792,6 @@ protected function loadSlideNote(string $baseFile, Slide $oSlide): void // @phpstan-ignore-next-line if ($xmlReader->getDomFromString($sPart)) { $oNote = $oSlide->getNote(); - $arrayElements = $xmlReader->getElements('/p:notes/p:cSld/p:spTree/*'); $this->loadSlideShapes($xmlReader, $oNote, $arrayElements, $xmlReader); } @@ -803,7 +801,11 @@ protected function loadShapeDrawing(XMLReader $document, DOMElement $node, Abstr { // Core $document->registerNamespace('asvg', 'http://schemas.microsoft.com/office/drawing/2016/SVG/main'); - if ($document->getElement('p:blipFill/a:blip/a:extLst/a:ext/asvg:svgBlip', $node)) { + $embedNode = $document->getElements("p:nvPicPr/p:nvPr//*[local-name()='media']", $node); + $embedNode = $embedNode ? $embedNode->item(0) : false; + if ($embedNode) { + $oShape = new Media(); + } elseif ($document->getElement('p:blipFill/a:blip/a:extLst/a:ext/asvg:svgBlip', $node)) { $oShape = new Base64(); } else { $oShape = new Gd(); @@ -811,12 +813,10 @@ protected function loadShapeDrawing(XMLReader $document, DOMElement $node, Abstr $oShape->getShadow()->setVisible(false); // Variables $fileRels = $oSlide->getRelsIndex(); - $oElement = $document->getElement('p:nvPicPr/p:cNvPr', $node); if ($oElement instanceof DOMElement) { $oShape->setName($oElement->hasAttribute('name') ? $oElement->getAttribute('name') : ''); $oShape->setDescription($oElement->hasAttribute('descr') ? $oElement->getAttribute('descr') : ''); - // Hyperlink $oElementHlinkClick = $document->getElement('a:hlinkClick', $oElement); if (is_object($oElementHlinkClick)) { @@ -825,52 +825,22 @@ protected function loadShapeDrawing(XMLReader $document, DOMElement $node, Abstr ); } } - - $oElement = $document->getElement('p:blipFill/a:blip', $node); - if ($oElement instanceof DOMElement) { - if ($oElement->hasAttribute('r:embed') && isset($this->arrayRels[$fileRels][$oElement->getAttribute('r:embed')]['Target'])) { - $pathImage = 'ppt/slides/' . $this->arrayRels[$fileRels][$oElement->getAttribute('r:embed')]['Target']; - $pathImage = explode('/', $pathImage); - foreach ($pathImage as $key => $partPath) { - if ('..' == $partPath) { - unset($pathImage[$key - 1], $pathImage[$key]); - } - } - $pathImage = implode('/', $pathImage); - $imageFile = $this->oZip->getFromName($pathImage); - if (!empty($imageFile)) { - if ($oShape instanceof Gd) { - $info = getimagesizefromstring($imageFile); - if (!$info) { - return; - } - $oShape->setMimeType($info['mime']); - $oShape->setRenderingFunction(str_replace('/', '', $info['mime'])); - $image = @imagecreatefromstring($imageFile); - if (!$image) { - return; - } - $oShape->setImageResource($image); - } elseif ($oShape instanceof Base64) { - $oShape->setData('data:image/svg+xml;base64,' . base64_encode($imageFile)); - } - } - } + if ($oShape instanceof Media) { + $oShape = $this->loadShapeDrawingEmbed($embedNode, $fileRels, $oShape); + } else { + $oShape = $this->loadShapeDrawingImage($document, $node, $fileRels, $oShape); } - $oElement = $document->getElement('p:spPr', $node); if ($oElement instanceof DOMElement) { $oFill = $this->loadStyleFill($document, $oElement); $oShape->setFill($oFill); } - $oElement = $document->getElement('p:spPr/a:xfrm', $node); if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('rot')) { $oShape->setRotation((int) CommonDrawing::angleToDegrees((int) $oElement->getAttribute('rot'))); } } - $oElement = $document->getElement('p:spPr/a:xfrm/a:off', $node); if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('x')) { @@ -880,7 +850,6 @@ protected function loadShapeDrawing(XMLReader $document, DOMElement $node, Abstr $oShape->setOffsetY((int) CommonDrawing::emuToPixels((int) $oElement->getAttribute('y'))); } } - $oElement = $document->getElement('p:spPr/a:xfrm/a:ext', $node); if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('cx')) { @@ -900,6 +869,70 @@ protected function loadShapeDrawing(XMLReader $document, DOMElement $node, Abstr $oSlide->addShape($oShape); } + protected function loadShapeDrawingEmbed(DOMElement $oElement, string $fileRels, Media $oShape): Media + { + if (!$oElement->hasAttribute('r:embed')) { + return $oShape; + } + if (!isset($this->arrayRels[$fileRels][$oElement->getAttribute('r:embed')]['Target'])) { + return $oShape; + } + + $embedPath = $this->arrayRels[$fileRels][$oElement->getAttribute('r:embed')]['Target']; + $pathEmbed = "ppt/slides/{$embedPath}"; + + $pathEmbed = explode('/', $pathEmbed); + foreach ($pathEmbed as $key => $partPath) { + if ('..' == $partPath) { + unset($pathEmbed[$key - 1], $pathEmbed[$key]); + } + } + $pathEmbed = implode('/', $pathEmbed); + $contentEmbed = $this->oZip->getFromName($pathEmbed); + $fileName = basename($embedPath); + + $oShape->loadFromContent($contentEmbed, $fileName); + + return $oShape; + } + + protected function loadShapeDrawingImage(XMLReader $document, DOMElement $node, string $fileRels, AbstractDrawingAdapter $oShape): AbstractDrawingAdapter + { + $oElement = $document->getElement('p:blipFill/a:blip', $node); + if (!($oElement instanceof DOMElement)) { + return $oShape; + } + if (!$oElement->hasAttribute('r:embed')) { + return $oShape; + } + if (!isset($this->arrayRels[$fileRels][$oElement->getAttribute('r:embed')]['Target'])) { + return $oShape; + } + + $pathImage = 'ppt/slides/' . $this->arrayRels[$fileRels][$oElement->getAttribute('r:embed')]['Target']; + $pathImage = explode('/', $pathImage); + foreach ($pathImage as $key => $partPath) { + if ('..' == $partPath) { + unset($pathImage[$key - 1], $pathImage[$key]); + } + } + $pathImage = implode('/', $pathImage); + $imageFile = $this->oZip->getFromName($pathImage); + $fileName = basename($pathImage); + + if (!$imageFile) { + return $oShape; + } + + if ($oShape instanceof Gd) { + $oShape->loadFromContent($imageFile, $fileName); + } elseif ($oShape instanceof Base64) { + $oShape->setData('data:image/svg+xml;base64,' . base64_encode($imageFile)); + } + + return $oShape; + } + /** * Load Shadow for shape or paragraph. */ @@ -1050,7 +1083,6 @@ protected function loadShapeRichText(XMLReader $document, DOMElement $node, $oSl protected function loadShapeTable(XMLReader $document, DOMElement $node, AbstractSlide $oSlide): void { $this->fileRels = $oSlide->getRelsIndex(); - $oShape = $oSlide->createTableShape(); $oElement = $document->getElement('p:cNvPr', $node); @@ -1501,6 +1533,7 @@ protected function loadParagraph(XMLReader $document, DOMElement $oElement, $oSh $oParagraph->getBulletStyle()->setBulletColor($oColor); } } + $arraySubElements = $document->getElements('(a:r|a:br)', $oElement); foreach ($arraySubElements as $oSubElement) { if (!($oSubElement instanceof DOMElement)) { @@ -1513,7 +1546,6 @@ protected function loadParagraph(XMLReader $document, DOMElement $oElement, $oSh $oElementrPr = $document->getElement('a:rPr', $oSubElement); if (is_object($oElementrPr)) { $oText = $oParagraph->createTextRun(); - if ($oElementrPr->hasAttribute('b')) { $att = $oElementrPr->getAttribute('b'); $oText->getFont()->setBold('true' == $att || '1' == $att ? true : false); @@ -1591,7 +1623,6 @@ protected function loadParagraph(XMLReader $document, DOMElement $oElement, $oSh $oText->getFont()->setCharset((int) $oElementFont->getAttribute('charset')); } } - $oSubSubElement = $document->getElement('a:t', $oSubElement); $oText->setText($oSubSubElement->nodeValue); } diff --git a/src/PhpPresentation/Shape/Drawing/AbstractDrawingAdapter.php b/src/PhpPresentation/Shape/Drawing/AbstractDrawingAdapter.php index 31aef9e14..8f3192828 100644 --- a/src/PhpPresentation/Shape/Drawing/AbstractDrawingAdapter.php +++ b/src/PhpPresentation/Shape/Drawing/AbstractDrawingAdapter.php @@ -35,7 +35,41 @@ abstract public function getMimeType(): string; abstract public function getPath(): string; /** + * @param string $path File path * @return self */ abstract public function setPath(string $path); + + /** + * Set whether this is a temporary file that should be cleaned up + * + * @param bool $isTemporary + * @return self + */ + abstract public function setIsTemporaryFile(bool $isTemporary); + + /** + * Load content into this object using a temporary file + * + * @param string $content Binary content + * @param string $fileName Optional fileName for reference + * @param string $prefix Prefix for the temporary file + * @return self + */ + public function loadFromContent(string $content, string $fileName = '', string $prefix = 'PhpPresentation'): self + { + $tmpFile = tempnam(sys_get_temp_dir(), $prefix); + file_put_contents($tmpFile, $content); + + // Set path and mark as temporary for automatic cleanup + $this->setPath($tmpFile); + $this->setIsTemporaryFile(true); + + // Set filename if provided + if (!empty($fileName)) { + $this->setName($fileName); + } + + return $this; + } } diff --git a/src/PhpPresentation/Shape/Drawing/File.php b/src/PhpPresentation/Shape/Drawing/File.php index 7fd894ac3..cbb62d74c 100644 --- a/src/PhpPresentation/Shape/Drawing/File.php +++ b/src/PhpPresentation/Shape/Drawing/File.php @@ -30,6 +30,16 @@ class File extends AbstractDrawingAdapter */ protected $path = ''; + /** + * @var string Name of the file + */ + protected $fileName = ''; + + /** + * @var bool Flag indicating if this is a temporary file that should be cleaned up + */ + protected $isTemporaryFile = false; + /** * Get Path. */ @@ -62,6 +72,40 @@ public function setPath(string $pValue = '', bool $pVerifyFile = true): self return $this; } + /** + * Set whether this is a temporary file that should be cleaned up + * + * @param bool $isTemporary + * @return self + */ + public function setIsTemporaryFile(bool $isTemporary): self + { + $this->isTemporaryFile = $isTemporary; + return $this; + } + + /** + * Check if this is a temporary file that should be cleaned up + * + * @return bool + */ + public function isTemporaryFile(): bool + { + return $this->isTemporaryFile; + } + + public function getFileName(): string + { + return $this->fileName; + } + + public function setFileName(string $fileName): self + { + $this->fileName = $fileName; + + return $this; + } + public function getContents(): string { return CommonFile::fileGetContents($this->getPath()); @@ -95,4 +139,36 @@ public function getIndexedFilename(): string return $output; } + + /** + * {@inheritDoc} + */ + public function loadFromContent(string $content, string $fileName = '', string $prefix = 'PhpPresentation'): AbstractDrawingAdapter + { + // Create temporary file + $tmpFile = tempnam(sys_get_temp_dir(), $prefix); + file_put_contents($tmpFile, $content); + + // Set path and mark as temporary + $this->setPath($tmpFile); + $this->setIsTemporaryFile(true); + + // Set filename if provided + if (!empty($fileName)) { + $this->setFileName($fileName); + } + + return $this; + } + + /** + * Clean up resources when object is destroyed + */ + public function __destruct() + { + // Remove temporary file if needed + if ($this->isTemporaryFile() && $this->path && file_exists($this->path)) { + @unlink($this->path); + } + } } diff --git a/src/PhpPresentation/Shape/Drawing/Gd.php b/src/PhpPresentation/Shape/Drawing/Gd.php index bbc278a84..1149ff014 100644 --- a/src/PhpPresentation/Shape/Drawing/Gd.php +++ b/src/PhpPresentation/Shape/Drawing/Gd.php @@ -39,7 +39,7 @@ class Gd extends AbstractDrawingAdapter /** * Image resource. * - * @var resource + * @var GdImage|resource|null */ protected $imageResource; @@ -64,6 +64,11 @@ class Gd extends AbstractDrawingAdapter */ protected $uniqueName; + /** + * @var bool Flag indicating if this is a temporary file that should be cleaned up + */ + protected $isTemporaryFile = false; + /** * Gd constructor. */ @@ -76,10 +81,48 @@ public function __construct() /** * Get image resource. * - * @return resource + * @param bool $isTransient Avoid the image resource being stored in memory to avoid OOM + * @return ?GdImage|?resource */ - public function getImageResource() + public function getImageResource(bool $isTransient = false) { + // Lazy load image resource if not already loaded + if (!$this->imageResource) { + $imageString = file_get_contents($this->getPath()); + if ($imageString === false) { + return null; // Failed to read file + } + + $image = imagecreatefromstring($imageString); + if ($image === false) { + return null; // Failed to create image resource + } + + $this->setImageResource($image); + } + + if ($isTransient) { + // Create a new image resource and copy the original + $width = imagesx($this->imageResource); + $height = imagesy($this->imageResource); + $imageCopy = imagecreatetruecolor($width, $height); + + // Preserve transparency for PNG/GIF images + if (imageistruecolor($this->imageResource)) { + imagealphablending($imageCopy, false); + imagesavealpha($imageCopy, true); + } + + // Copy the image data + imagecopy($imageCopy, $this->imageResource, 0, 0, 0, 0, $width, $height); + + // Destroy the original resource to free memory + imagedestroy($this->imageResource); + $this->imageResource = null; + + return $imageCopy; + } + return $this->imageResource; } @@ -93,14 +136,26 @@ public function getImageResource() public function setImageResource($value = null) { $this->imageResource = $value; + if (!$this->imageResource) { + return $this; + } + + $this->getDimensions(); - if (null !== $this->imageResource && false !== $this->imageResource) { - // Get width/height + return $this; + } + + public function getDimensions(): array + { + // Lazy load dimensions + if (!$this->width) { $this->width = imagesx($this->imageResource); + } + if (!$this->height) { $this->height = imagesy($this->imageResource); } - return $this; + return [$this->width, $this->height]; } /** @@ -190,9 +245,86 @@ public function getPath(): string return $this->path; } + /** + * Set Path. + * + * @param string $path File path + * @return self + */ public function setPath(string $path): self { $this->path = $path; + return $this; + } + + /** + * Set whether this is a temporary file that should be cleaned up + * + * @param bool $isTemporary + * @return self + */ + public function setIsTemporaryFile(bool $isTemporary): self + { + $this->isTemporaryFile = $isTemporary; + return $this; + } + + /** + * Check if this is a temporary file that should be cleaned up + * + * @return bool + */ + public function isTemporaryFile(): bool + { + return $this->isTemporaryFile; + } + + /** + * Clean up resources when object is destroyed + */ + public function __destruct() + { + // Free GD image resource if it exists + if ($this->imageResource) { + imagedestroy($this->imageResource); + $this->imageResource = null; + } + + // Remove temporary file if needed + if ($this->isTemporaryFile && !empty($this->path) && file_exists($this->path)) { + @unlink($this->path); + } + } + + /** + * {@inheritDoc} + */ + public function loadFromContent(string $content, string $fileName = '', string $prefix = 'PhpPresentationGd'): AbstractDrawingAdapter + { + // Check if the content is a valid image + $image = @imagecreatefromstring($content); + if ($image === false) { + return $this; + } + // Clean up the image resource to avoid memory leaks + @imagedestroy($image); + + $tmpFile = tempnam(sys_get_temp_dir(), $prefix); + file_put_contents($tmpFile, $content); + + // Set path and mark as temporary for automatic cleanup + $this->setPath($tmpFile); + $this->setIsTemporaryFile(true); + + if (!empty($fileName)) { + $this->setName($fileName); + } + + $info = getimagesizefromstring($content); + if (isset($info['mime'])) { + $this->setMimeType($info['mime']); + $this->setRenderingFunction(str_replace('/', '', $info['mime'])); + } return $this; } diff --git a/src/PhpPresentation/Slide/Background/Image.php b/src/PhpPresentation/Slide/Background/Image.php index 801464dc0..9e7995182 100644 --- a/src/PhpPresentation/Slide/Background/Image.php +++ b/src/PhpPresentation/Slide/Background/Image.php @@ -21,6 +21,7 @@ namespace PhpOffice\PhpPresentation\Slide\Background; use PhpOffice\PhpPresentation\Exception\FileNotFoundException; +use PhpOffice\PhpPresentation\Shape\Drawing\AbstractDrawingAdapter; use PhpOffice\PhpPresentation\Slide\AbstractBackground; class Image extends AbstractBackground @@ -47,6 +48,11 @@ class Image extends AbstractBackground */ protected $width; + /** + * @var AbstractDrawingAdapter|null + */ + protected $image; + /** * @var string */ @@ -68,7 +74,7 @@ public function getPath(): ?string * * @return self */ - public function setPath(string $pValue = '', bool $pVerifyFile = true) + public function setPath(string $pValue = '', bool $pVerifyFile = true): Image { if ($pVerifyFile) { if (!file_exists($pValue)) { @@ -85,6 +91,20 @@ public function setPath(string $pValue = '', bool $pVerifyFile = true) return $this; } + /** + * Set the image using a drawing adapter (keeps a reference to the object to manage file lifecycle) + * + * @param AbstractDrawingAdapter $image Drawing adapter containing image data + * @return self + * @throws FileNotFoundException + */ + public function setImage(AbstractDrawingAdapter $image): self + { + $this->image = $image; + $this->setPath($image->getPath()); + return $this; + } + /** * Set Extension. * @@ -125,7 +145,7 @@ public function getExtension(): string * * @return string */ - public function getIndexedFilename($numSlide) + public function getIndexedFilename($numSlide): string { return 'background_' . $numSlide . '.' . $this->getExtension(); }