Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/changes/1.3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
- ODPresentation Reader : Fixed a paragraph's line spacing being read out of the margin below it, so that every spacing came back as that margin in centimetres and every mode as points, by [@dkulyk](http://github.com/dkulyk) in [#980](https://github.com/PHPOffice/PHPPresentation/pull/980)
- ODPresentation Reader : Fixed a shape's border being written as a stroke and never read back, so that every shape loaded from a file wore the default border rather than its own, by [@dkulyk](http://github.com/dkulyk) in [#978](https://github.com/PHPOffice/PHPPresentation/pull/978)
- ODPresentation Reader : Fixed the speaker notes of a slide being written and never read back, so that a note written by this library or by LibreOffice was lost on load, by [@dkulyk](http://github.com/dkulyk) fixing [#724](https://github.com/PHPOffice/PHPPresentation/issues/724) in [#1001](https://github.com/PHPOffice/PHPPresentation/pull/1001)
- PowerPoint2007 Reader and ODPresentation Reader : Fixed a line being written by both Writers and read back by neither, so that a deck loaded and saved again keeps the lines it had, by [@dkulyk](http://github.com/dkulyk) fixing [#987](https://github.com/PHPOffice/PHPPresentation/issues/987) in [#988](https://github.com/PHPOffice/PHPPresentation/pull/988)
- PowerPoint2007 Writer : Fixed a line that runs right to left being written mirrored, and one that runs up and to the left being written mirrored on both axes, by naming the axis a line is flipped on rather than always naming the vertical one, by [@dkulyk](http://github.com/dkulyk) fixing [#987](https://github.com/PHPOffice/PHPPresentation/issues/987) in [#988](https://github.com/PHPOffice/PHPPresentation/pull/988)

- PowerPoint97 Reader : Fixed a stream being read one byte past its end, raising `Uninitialized string offset` once per byte for each structure the parser walks off the end of, by [@dkulyk](http://github.com/dkulyk) in [#995](https://github.com/PHPOffice/PHPPresentation/pull/995)
## BC Breaks
Expand Down
3 changes: 2 additions & 1 deletion phpmd.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
<!-- Reader/PowerPoint2007 reads a group, which is a shape holding shapes: the group,
the container it is put in, and the table it can hold are three more classes -->
<!-- Reader/PowerPoint2007 reads the nine kinds of chart, and each kind is a class -->
<!-- Reader/PowerPoint2007 reads a line, which is a shape class of its own -->
<properties>
<property name="maximum" value="47" />
<property name="maximum" value="48" />
</properties>
</rule>
<rule ref="rulesets/design.xml/NumberOfChildren">
Expand Down
29 changes: 29 additions & 0 deletions src/PhpPresentation/Reader/ODPresentation.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use PhpOffice\PhpPresentation\PresentationProperties;
use PhpOffice\PhpPresentation\Shape\Drawing\Base64;
use PhpOffice\PhpPresentation\Shape\Drawing\Gd;
use PhpOffice\PhpPresentation\Shape\Line;
use PhpOffice\PhpPresentation\Shape\RichText;
use PhpOffice\PhpPresentation\Shape\RichText\Field;
use PhpOffice\PhpPresentation\Shape\RichText\Paragraph;
Expand Down Expand Up @@ -857,6 +858,12 @@ protected function loadSlide(DOMElement $nodeSlide): bool
}
}
}
// a line is a shape of the page in its own right, not the content of a frame
foreach ($this->oXMLReader->getElements('draw:line', $nodeSlide) as $oNodeLine) {
if ($oNodeLine instanceof DOMElement) {
$this->loadShapeLine($oNodeLine);
}
}
$this->loadSlideNote($nodeSlide);

return true;
Expand Down Expand Up @@ -980,6 +987,28 @@ protected function applyShapeBorder(AbstractShape $shape, ?Border $border): void
$shape->setBorder($border);
}

/**
* Read a Line.
*
* ODF says a line by the two points it runs between, so there is nothing to flip: a line that
* runs right to left simply has an `svg:x2` smaller than its `svg:x1`, which is the negative
* width the model carries.
*/
protected function loadShapeLine(DOMElement $oNodeLine): void
{
$point = function (string $attribute) use ($oNodeLine): int {
return $oNodeLine->hasAttribute($attribute)
? (int) CommonDrawing::centimetersToPixels((float) substr($oNodeLine->getAttribute($attribute), 0, -2))
: 0;
};

$shape = new Line($point('svg:x1'), $point('svg:y1'), $point('svg:x2'), $point('svg:y2'));
$shape->setDescription($this->loadShapeDescription($oNodeLine));
$shape->setDecorative($this->loadShapeDecorative($oNodeLine));

$this->oPhpPresentation->getActiveSlide()->addShape($shape);
}

/**
* Read Shape RichText.
*
Expand Down
53 changes: 53 additions & 0 deletions src/PhpPresentation/Reader/PowerPoint2007.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
use PhpOffice\PhpPresentation\Shape\Drawing\Gd;
use PhpOffice\PhpPresentation\Shape\Group;
use PhpOffice\PhpPresentation\Shape\Hyperlink;
use PhpOffice\PhpPresentation\Shape\Line;
use PhpOffice\PhpPresentation\Shape\Placeholder;
use PhpOffice\PhpPresentation\Shape\RichText;
use PhpOffice\PhpPresentation\Shape\RichText\Paragraph;
Expand Down Expand Up @@ -1129,6 +1130,54 @@ protected function loadShadow(XMLReader $document, DOMElement $node): ?Shadow
return null;
}

/**
* Read a Line, which is written as a connection shape.
*
* The two ends the shape was given are the offset and the extent of its `a:xfrm`. An extent
* cannot be negative, so a line that runs right to left or bottom to top is written from the
* other end with `flipH` or `flipV` saying so, and the two ends are put back here.
*
* @param AbstractSlide|Note $oSlide
*/
protected function loadShapeLine(XMLReader $document, DOMElement $node, $oSlide, ?ShapeContainerInterface $oContainer = null): void
{
$fromX = $fromY = $width = $height = 0;

$oElement = $document->getElement('p:spPr/a:xfrm/a:off', $node);
if ($oElement instanceof DOMElement) {
$fromX = (int) CommonDrawing::emuToPixels((int) $oElement->getAttribute('x'));
$fromY = (int) CommonDrawing::emuToPixels((int) $oElement->getAttribute('y'));
}

$oElement = $document->getElement('p:spPr/a:xfrm/a:ext', $node);
if ($oElement instanceof DOMElement) {
$width = (int) CommonDrawing::emuToPixels((int) $oElement->getAttribute('cx'));
$height = (int) CommonDrawing::emuToPixels((int) $oElement->getAttribute('cy'));
}

$oElement = $document->getElement('p:spPr/a:xfrm', $node);
if ($oElement instanceof DOMElement) {
if ('1' === $oElement->getAttribute('flipH')) {
$fromX += $width;
$width = -$width;
}
if ('1' === $oElement->getAttribute('flipV')) {
$fromY += $height;
$height = -$height;
}
}

$oShape = new Line($fromX, $fromY, $fromX + $width, $fromY + $height);
($oContainer ?? $oSlide)->addShape($oShape);

$oElement = $document->getElement('p:nvCxnSpPr/p:cNvPr', $node);
if ($oElement instanceof DOMElement) {
$oShape->setName($oElement->hasAttribute('name') ? $oElement->getAttribute('name') : '');
$oShape->setDescription($oElement->hasAttribute('descr') ? $oElement->getAttribute('descr') : '');
$oShape->setDecorative($this->loadShapeDecorative($document, $oElement));
}
}

/**
* @param AbstractSlide|Note $oSlide
*/
Expand Down Expand Up @@ -2239,6 +2288,10 @@ protected function loadSlideShapes(XMLReader $document, $oSlide, DOMNodeList $oE
case 'p:sp':
$this->loadShapeRichText($xmlReader, $oNode, $oSlide, $oContainer);

break;
case 'p:cxnSp':
$this->loadShapeLine($xmlReader, $oNode, $oSlide, $oContainer);

break;
case 'p:grpSp':
$this->loadShapeGroup($xmlReader, $oNode, $oSlide, $xmlReader, $oContainer);
Expand Down
4 changes: 3 additions & 1 deletion src/PhpPresentation/Writer/PowerPoint2007/AbstractSlide.php
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,8 @@ protected function writeShapeLine(XMLWriter $objWriter, Line $shape, int $shapeI
$objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($shape->getHeight()));
$objWriter->endElement();
} elseif ($shape->getWidth() < 0 && $shape->getHeight() < 0) {
$objWriter->writeAttribute('flipH', 1);
$objWriter->writeAttribute('flipV', 1);
// a:off
$objWriter->startElement('a:off');
$objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX() + $shape->getWidth()));
Expand All @@ -848,7 +850,7 @@ protected function writeShapeLine(XMLWriter $objWriter, Line $shape, int $shapeI
$objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu(-$shape->getHeight()));
$objWriter->endElement();
} elseif ($shape->getWidth() < 0) {
$objWriter->writeAttribute('flipV', 1);
$objWriter->writeAttribute('flipH', 1);
// a:off
$objWriter->startElement('a:off');
$objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX() + $shape->getWidth()));
Expand Down
43 changes: 43 additions & 0 deletions tests/PhpPresentation/Tests/Reader/ODPresentationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use PhpOffice\PhpPresentation\PresentationProperties;
use PhpOffice\PhpPresentation\Reader\ODPresentation;
use PhpOffice\PhpPresentation\Shape\Drawing\Gd;
use PhpOffice\PhpPresentation\Shape\Line;
use PhpOffice\PhpPresentation\Shape\RichText;
use PhpOffice\PhpPresentation\Shape\RichText\Field;
use PhpOffice\PhpPresentation\Shape\RichText\Paragraph;
Expand Down Expand Up @@ -1796,4 +1797,46 @@ public function testEmptySlideNoteWrittenByLibreOffice(): void
self::assertCount(0, $oSlide->getNote()->getShapeCollection());
}
}

/**
* A line runs between two points, and a negative width or height is how the model says it
* runs right to left or bottom to top.
*
* @return array<string, array{int, int, int, int}>
*/
public static function dataProviderLine(): array
{
return [
'down and to the right' => [10, 20, 110, 220],
'up and to the right' => [10, 220, 110, 20],
'down and to the left' => [110, 20, 10, 220],
'up and to the left' => [110, 220, 10, 20],
];
}

/**
* @dataProvider dataProviderLine
*/
#[DataProvider('dataProviderLine')]
public function testLineSurvivesTheRoundTrip(int $fromX, int $fromY, int $toX, int $toY): void
{
$oPhpPresentation = new PhpPresentation();
$oShape = $oPhpPresentation->getActiveSlide()->createLineShape($fromX, $fromY, $toX, $toY);
$oShape->setDescription('A line');

$file = tempnam(sys_get_temp_dir(), 'PhpPresentation');
(new ODPresentationWriter($oPhpPresentation))->save($file);
$oPhpPresentationRead = (new ODPresentation())->load($file);
unlink($file);

// a line is a shape of the page, not the content of a frame, and only frames were read
$arrayShape = array_values((array) $oPhpPresentationRead->getActiveSlide()->getShapeCollection());
self::assertCount(1, $arrayShape);
self::assertInstanceOf(Line::class, $arrayShape[0]);
self::assertEquals($fromX, $arrayShape[0]->getOffsetX());
self::assertEquals($fromY, $arrayShape[0]->getOffsetY());
self::assertEquals($toX - $fromX, $arrayShape[0]->getWidth());
self::assertEquals($toY - $fromY, $arrayShape[0]->getHeight());
self::assertEquals('A line', $arrayShape[0]->getDescription());
}
}
43 changes: 43 additions & 0 deletions tests/PhpPresentation/Tests/Reader/PowerPoint2007Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
use PhpOffice\PhpPresentation\Shape\Chart\Type\Scatter;
use PhpOffice\PhpPresentation\Shape\Drawing\Gd;
use PhpOffice\PhpPresentation\Shape\Group;
use PhpOffice\PhpPresentation\Shape\Line as LineShape;
use PhpOffice\PhpPresentation\Shape\Placeholder;
use PhpOffice\PhpPresentation\Shape\RichText;
use PhpOffice\PhpPresentation\Shape\RichText\Field;
Expand Down Expand Up @@ -2037,4 +2038,46 @@ public function testShapeWrapSurvivesTheRoundTrip(): void
// `wrap` is written on `a:bodyPr` beside the insets, and nothing read it back
self::assertEquals(RichText::WRAP_NONE, $arrayShape[0]->getWrap());
}

/**
* A line runs between two points, and a negative width or height is how the model says it
* runs right to left or bottom to top.
*
* @return array<string, array{int, int, int, int}>
*/
public static function dataProviderLine(): array
{
return [
'down and to the right' => [10, 20, 110, 220],
'up and to the right' => [10, 220, 110, 20],
'down and to the left' => [110, 20, 10, 220],
'up and to the left' => [110, 220, 10, 20],
];
}

/**
* @dataProvider dataProviderLine
*/
#[DataProvider('dataProviderLine')]
public function testLineSurvivesTheRoundTrip(int $fromX, int $fromY, int $toX, int $toY): void
{
$oPhpPresentation = new PhpPresentation();
$oShape = $oPhpPresentation->getActiveSlide()->createLineShape($fromX, $fromY, $toX, $toY);
$oShape->setDescription('A line');

$file = tempnam(sys_get_temp_dir(), 'PhpPresentation');
(new PowerPoint2007Writer($oPhpPresentation))->save($file);
$oPhpPresentationRead = (new PowerPoint2007())->load($file);
unlink($file);

// a line was written as a connection shape and read back as nothing at all
$arrayShape = array_values((array) $oPhpPresentationRead->getActiveSlide()->getShapeCollection());
self::assertCount(1, $arrayShape);
self::assertInstanceOf(LineShape::class, $arrayShape[0]);
self::assertEquals($fromX, $arrayShape[0]->getOffsetX());
self::assertEquals($fromY, $arrayShape[0]->getOffsetY());
self::assertEquals($toX - $fromX, $arrayShape[0]->getWidth());
self::assertEquals($toY - $fromY, $arrayShape[0]->getHeight());
self::assertEquals('A line', $arrayShape[0]->getDescription());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,15 @@ public function testLine(): void
$element = '/p:sld/p:cSld/p:spTree/p:cxnSp/p:spPr/a:xfrm/a:off[@x="' . $valEmu10 . '"][@y="' . $valEmu10 . '"]';
$this->assertZipXmlElementExists('ppt/slides/slide1.xml', $element);

$element = '/p:sld/p:cSld/p:spTree/p:cxnSp/p:spPr/a:xfrm[@flipV="1"]/a:off[@x="' . $valEmu10 . '"][@y="' . $valEmu10 . '"]';
// an extent cannot be negative, so a line that runs the other way is written from the
// other end and says which axis it is mirrored on
$element = '/p:sld/p:cSld/p:spTree/p:cxnSp/p:spPr/a:xfrm[@flipV="1"][not(@flipH)]/a:off[@x="' . $valEmu10 . '"][@y="' . $valEmu10 . '"]';
$this->assertZipXmlElementExists('ppt/slides/slide1.xml', $element);

$element = '/p:sld/p:cSld/p:spTree/p:cxnSp/p:spPr/a:xfrm[@flipH="1"][not(@flipV)]/a:off[@x="' . $valEmu10 . '"][@y="' . $valEmu10 . '"]';
$this->assertZipXmlElementExists('ppt/slides/slide1.xml', $element);

$element = '/p:sld/p:cSld/p:spTree/p:cxnSp/p:spPr/a:xfrm[@flipH="1"][@flipV="1"]/a:off[@x="' . $valEmu10 . '"][@y="' . $valEmu10 . '"]';
$this->assertZipXmlElementExists('ppt/slides/slide1.xml', $element);

$this->assertIsSchemaECMA376Valid();
Expand Down
Loading