Skip to content

Commit 92a67eb

Browse files
authored
Merge pull request #4 from seanlynchwv/fix/upgrade-phpspreadsheet
Fix/upgrade phpspreadsheet
2 parents 9d155be + 1a1a0c1 commit 92a67eb

4 files changed

Lines changed: 130 additions & 6 deletions

File tree

src/PhpPresentation/Shape/AutoShape.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,4 +273,31 @@ public function setOutline(Outline $outline): self
273273

274274
return $this;
275275
}
276+
277+
private ?int $roundRectAdj = null;
278+
279+
public function getRoundRectAdj(): ?int
280+
{
281+
return $this->roundRectAdj;
282+
}
283+
284+
/**
285+
* Set corner radius in pixels. We map px to the OOXML 'adj' 0..50000 scale.
286+
* adj is relative to (min(width,height)/2).
287+
*/
288+
public function setRoundRectCorner(int $px): self
289+
{
290+
$minHalf = (int) floor(min($this->width, $this->height) / 2);
291+
if ($minHalf > 0) {
292+
$this->roundRectAdj = max(0, min(50000, (int) round($px / $minHalf * 50000)));
293+
}
294+
return $this;
295+
}
296+
297+
// override the hash so radius works
298+
public function getHashCode(): string
299+
{
300+
// parent::getHashCode() already includes geometry/fill/shadow, etc.
301+
return md5(parent::getHashCode() . $this->type . $this->text . (string) $this->roundRectAdj . __CLASS__);
302+
}
276303
}

src/PhpPresentation/Writer/PowerPoint2007/AbstractSlide.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,9 +1187,21 @@ protected function writeShapeAutoShape(XMLWriter $objWriter, AutoShape $shape, i
11871187
// p:sp\p:spPr\a:prstGeom
11881188
$objWriter->startElement('a:prstGeom');
11891189
$objWriter->writeAttribute('prst', $shape->getType());
1190-
// p:sp\p:spPr\a:prstGeom\a:avLst
1191-
$objWriter->writeElement('a:avLst');
1192-
// p:sp\p:spPr\a:prstGeom\
1190+
1191+
// a:avLst (+ optional adj for roundRect)
1192+
$needsAdj = ($shape->getType() === \PhpOffice\PhpPresentation\Shape\AutoShape::TYPE_ROUNDED_RECTANGLE);
1193+
$adj = $shape->getRoundRectAdj();
1194+
1195+
if ($needsAdj && $adj !== null) {
1196+
$objWriter->startElement('a:avLst');
1197+
$objWriter->startElement('a:gd');
1198+
$objWriter->writeAttribute('name', 'adj');
1199+
$objWriter->writeAttribute('fmla', 'val ' . (string) $adj); // 0..50000
1200+
$objWriter->endElement(); // a:gd
1201+
$objWriter->endElement(); // a:avLst
1202+
} else {
1203+
$objWriter->writeElement('a:avLst');
1204+
}
11931205
$objWriter->endElement();
11941206
// Fill
11951207
$this->writeFill($objWriter, $shape->getFill());

src/PhpPresentation/Writer/PowerPoint2007/ContentTypes.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ public function render(): ZipInterface
4949
// XML
5050
$this->writeDefaultContentType($objWriter, 'xml', 'application/xml');
5151

52-
// SVG
53-
$this->writeDefaultContentType($objWriter, 'svg', 'image/svg+xml');
52+
// SVG will pre-register it in $aMediaContentTypes
5453

5554
// Presentation
5655
$this->writeOverrideContentType($objWriter, '/ppt/presentation.xml', 'application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml');
@@ -106,11 +105,12 @@ public function render(): ZipInterface
106105
// Add media content-types
107106
$aMediaContentTypes = [];
108107

109-
// GIF, JPEG, PNG
108+
// GIF, JPEG, PNG, SVG
110109
$aMediaContentTypes['gif'] = 'image/gif';
111110
$aMediaContentTypes['jpg'] = 'image/jpeg';
112111
$aMediaContentTypes['jpeg'] = 'image/jpeg';
113112
$aMediaContentTypes['png'] = 'image/png';
113+
$aMediaContentTypes['svg'] = 'image/svg+xml';
114114
foreach ($aMediaContentTypes as $key => $value) {
115115
$this->writeDefaultContentType($objWriter, $key, $value);
116116
}
@@ -133,6 +133,11 @@ public function render(): ZipInterface
133133
$extension = strtolower($shapeIndex->getExtension());
134134
$mimeType = $shapeIndex->getMimeType();
135135

136+
// Normalize any odd returns (some environments report "image/svg")
137+
if ($extension === 'svg') {
138+
$mimeType = 'image/svg+xml';
139+
}
140+
136141
if (!isset($aMediaContentTypes[$extension])) {
137142
$aMediaContentTypes[$extension] = $mimeType;
138143

tests/PhpPresentation/Tests/Shape/AutoShapeTest.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@
2020

2121
namespace PhpOffice\PhpPresentation\Tests\Shape;
2222

23+
use PhpOffice\PhpPresentation\PhpPresentation;
2324
use PhpOffice\PhpPresentation\Shape\AutoShape;
25+
use PhpOffice\PhpPresentation\Style\Color;
26+
use PhpOffice\PhpPresentation\Style\Fill;
2427
use PhpOffice\PhpPresentation\Style\Outline;
28+
use PhpOffice\PhpPresentation\Writer\PowerPoint2007;
2529
use PHPUnit\Framework\TestCase;
30+
use ZipArchive;
2631

2732
class AutoShapeTest extends TestCase
2833
{
@@ -64,4 +69,79 @@ public function testType(): void
6469
self::assertInstanceOf(AutoShape::class, $object->setType(AutoShape::TYPE_HEXAGON));
6570
self::assertEquals(AutoShape::TYPE_HEXAGON, $object->getType());
6671
}
72+
73+
public function testPixelSetterComputesAdjAndAffectsHash(): void
74+
{
75+
$w = 200; // px
76+
$h = 100; // px
77+
$px1 = 5; // softer radius
78+
$px2 = 10; // larger radius
79+
80+
$s1 = (new AutoShape())
81+
->setType(AutoShape::TYPE_ROUNDED_RECTANGLE)
82+
->setWidth($w)->setHeight($h)
83+
->setRoundRectCorner($px1);
84+
85+
$s2 = (clone $s1)->setRoundRectCorner($px2);
86+
87+
// adj expected: round(px / (min(w,h)/2) * 50000)
88+
$minHalf = (int) floor(min($w, $h) / 2); // 50
89+
$expectedAdj1 = (int) round($px1 / $minHalf * 50000); // 5/50 * 50000 = 5000
90+
$expectedAdj2 = (int) round($px2 / $minHalf * 50000); // 10/50 * 50000 = 10000
91+
92+
self::assertSame($expectedAdj1, $s1->getRoundRectAdj());
93+
self::assertSame($expectedAdj2, $s2->getRoundRectAdj());
94+
95+
// Hash must differ when radius differs
96+
self::assertNotSame($s1->getHashCode(), $s2->getHashCode());
97+
}
98+
99+
public function testNoRadiusByDefaultIsNull(): void
100+
{
101+
$shape = new AutoShape();
102+
self::assertNull($shape->getRoundRectAdj());
103+
}
104+
105+
public function testWriterEmitsAdjGuideForRoundRect(): void
106+
{
107+
$ppt = new PhpPresentation();
108+
$slide = $ppt->getActiveSlide();
109+
110+
$width = 200;
111+
$height = 100;
112+
$padding = 5;
113+
$minHalf = (int) floor(min($width, $height) / 2);
114+
$expectedAdj = (int) round($padding / $minHalf * 50000); // 5000
115+
116+
$shape = (new AutoShape())
117+
->setType(AutoShape::TYPE_ROUNDED_RECTANGLE)
118+
->setWidth($width)->setHeight($height)
119+
->setRoundRectCorner($padding);
120+
121+
// Give it a fill so it's an obvious shape
122+
$shape->getFill()->setFillType(Fill::FILL_SOLID)->setStartColor(new Color('FFFFFFFF'));
123+
$slide->addShape($shape);
124+
125+
$tmpFile = tempnam(sys_get_temp_dir(), 'pptx_');
126+
$writer = new PowerPoint2007($ppt);
127+
$writer->save($tmpFile);
128+
129+
// Open the pptx and read slide1.xml
130+
$zip = new ZipArchive();
131+
$this->assertTrue($zip->open($tmpFile) === true, 'Failed to open pptx zip');
132+
$xml = $zip->getFromName('ppt/slides/slide1.xml');
133+
$zip->close();
134+
@unlink($tmpFile);
135+
136+
$this->assertIsString($xml);
137+
138+
// Must contain roundRect geometry and the adj guide with expected value
139+
$this->assertStringContainsString('<a:prstGeom prst="roundRect">', $xml);
140+
141+
// fmla="val N" (there is a space after 'val' in writer)
142+
$this->assertMatchesRegularExpression(
143+
sprintf('/<a:gd[^>]+name="adj"[^>]+fmla="val %d"/', $expectedAdj),
144+
$xml
145+
);
146+
}
67147
}

0 commit comments

Comments
 (0)