Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.01% covered (success)
94.01%
737 / 784
57.89% covered (warning)
57.89%
11 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
Content
94.01% covered (success)
94.01%
737 / 784
57.89% covered (warning)
57.89%
11 / 19
249.10
0.00% covered (danger)
0.00%
0 / 1
 render
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 writeContent
100.00% covered (success)
100.00%
207 / 207
100.00% covered (success)
100.00%
1 / 1
47
 writeShapeMedia
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
1
 writeShapeDrawing
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
3
 writeShapeTxt
96.63% covered (success)
96.63%
86 / 89
0.00% covered (danger)
0.00%
0 / 1
27
 writeShapeComment
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 writeShapeLine
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 writeShapeTable
100.00% covered (success)
100.00%
51 / 51
100.00% covered (success)
100.00%
1 / 1
14
 writeShapeChart
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 writeShapeGroup
64.71% covered (warning)
64.71%
11 / 17
0.00% covered (danger)
0.00%
0 / 1
10.81
 writeGroupStyle
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
6.73
 writeTxtStyle
96.43% covered (success)
96.43%
54 / 56
0.00% covered (danger)
0.00%
0 / 1
28
 writeDrawingStyle
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 writeLineStyle
76.47% covered (warning)
76.47%
13 / 17
0.00% covered (danger)
0.00%
0 / 1
4.21
 writeTableStyle
62.50% covered (warning)
62.50%
40 / 64
0.00% covered (danger)
0.00%
0 / 1
35.09
 writeSlideNote
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 writeStyleSlide
96.75% covered (success)
96.75%
119 / 123
0.00% covered (danger)
0.00%
0 / 1
55
 writeStylePartFill
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
5.05
 writeStylePartShadow
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
11
1<?php
2/**
3 * This file is part of PHPPresentation - A pure PHP library for reading and writing
4 * presentations documents.
5 *
6 * PHPPresentation is free software distributed under the terms of the GNU Lesser
7 * General Public License version 3 as published by the Free Software Foundation.
8 *
9 * For the full copyright and license information, please read the LICENSE
10 * file that was distributed with this source code. For the full list of
11 * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors.
12 *
13 * @see        https://github.com/PHPOffice/PHPPresentation
14 *
15 * @license     http://www.gnu.org/licenses/lgpl.txt LGPL version 3
16 */
17
18declare(strict_types=1);
19
20namespace PhpOffice\PhpPresentation\Writer\ODPresentation;
21
22use PhpOffice\Common\Adapter\Zip\ZipInterface;
23use PhpOffice\Common\Drawing as CommonDrawing;
24use PhpOffice\Common\Text;
25use PhpOffice\Common\XMLWriter;
26use PhpOffice\PhpPresentation\PresentationProperties;
27use PhpOffice\PhpPresentation\Shape\Chart;
28use PhpOffice\PhpPresentation\Shape\Comment;
29use PhpOffice\PhpPresentation\Shape\Drawing\AbstractDrawingAdapter;
30use PhpOffice\PhpPresentation\Shape\Group;
31use PhpOffice\PhpPresentation\Shape\Line;
32use PhpOffice\PhpPresentation\Shape\Media;
33use PhpOffice\PhpPresentation\Shape\RichText;
34use PhpOffice\PhpPresentation\Shape\RichText\BreakElement;
35use PhpOffice\PhpPresentation\Shape\RichText\Paragraph;
36use PhpOffice\PhpPresentation\Shape\RichText\Run;
37use PhpOffice\PhpPresentation\Shape\RichText\TextElement;
38use PhpOffice\PhpPresentation\Shape\Table;
39use PhpOffice\PhpPresentation\Slide;
40use PhpOffice\PhpPresentation\Slide\Note;
41use PhpOffice\PhpPresentation\Slide\Transition;
42use PhpOffice\PhpPresentation\Style\Alignment;
43use PhpOffice\PhpPresentation\Style\Border;
44use PhpOffice\PhpPresentation\Style\Fill;
45use PhpOffice\PhpPresentation\Style\Font;
46use PhpOffice\PhpPresentation\Style\Shadow;
47
48class Content extends AbstractDecoratorWriter
49{
50    /**
51     * Stores bullet styles for text shapes that include lists.
52     *
53     * @var array<string, array<string, mixed>>
54     */
55    protected $arrStyleBullet = [];
56
57    /**
58     * Stores paragraph information for text shapes.
59     *
60     * @var array<string, Paragraph>
61     */
62    protected $arrStyleParagraph = [];
63
64    /**
65     * Stores font styles for text shapes that include lists.
66     *
67     * @var array<string, Run>
68     */
69    protected $arrStyleTextFont = [];
70
71    /**
72     * Used to track the current shape ID.
73     *
74     * @var int
75     */
76    protected $shapeId;
77
78    public function render(): ZipInterface
79    {
80        $this->getZip()->addFromString('content.xml', $this->writeContent());
81
82        return $this->getZip();
83    }
84
85    /**
86     * Write content file to XML format.
87     *
88     * @return string XML Output
89     */
90    protected function writeContent(): string
91    {
92        // Create XML writer
93        $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
94        $objWriter->startDocument('1.0', 'UTF-8');
95
96        // office:document-content
97        $objWriter->startElement('office:document-content');
98        $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
99        $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0');
100        $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0');
101        $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0');
102        $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0');
103        $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0');
104        $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
105        $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
106        $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
107        $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0');
108        $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0');
109        $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0');
110        $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0');
111        $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0');
112        $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML');
113        $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0');
114        $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0');
115        $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
116        $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer');
117        $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc');
118        $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events');
119        $objWriter->writeAttribute('xmlns:xforms', 'http://www.w3.org/2002/xforms');
120        $objWriter->writeAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema');
121        $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
122        $objWriter->writeAttribute('xmlns:smil', 'urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0');
123        $objWriter->writeAttribute('xmlns:anim', 'urn:oasis:names:tc:opendocument:xmlns:animation:1.0');
124        $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report');
125        $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2');
126        $objWriter->writeAttribute('xmlns:rdfa', 'http://docs.oasis-open.org/opendocument/meta/rdfa#');
127        $objWriter->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0');
128        $objWriter->writeAttribute('xmlns:officeooo', 'http://openoffice.org/2009/office');
129        $objWriter->writeAttribute('xmlns:loext', 'urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0');
130        $objWriter->writeAttribute('office:version', '1.2');
131
132        // office:automatic-styles
133        $objWriter->startElement('office:automatic-styles');
134
135        $this->shapeId = 0;
136        $incSlide = 0;
137        foreach ($this->getPresentation()->getAllSlides() as $pSlide) {
138            // Slides
139            $this->writeStyleSlide($objWriter, $pSlide, $incSlide);
140
141            // Images
142            $shapes = $pSlide->getShapeCollection();
143            foreach ($shapes as $shape) {
144                // Increment $this->shapeId
145                ++$this->shapeId;
146
147                // Check type
148                if ($shape instanceof RichText) {
149                    $this->writeTxtStyle($objWriter, $shape);
150                }
151                if ($shape instanceof AbstractDrawingAdapter) {
152                    $this->writeDrawingStyle($objWriter, $shape);
153                }
154                if ($shape instanceof Line) {
155                    $this->writeLineStyle($objWriter, $shape);
156                }
157                if ($shape instanceof Table) {
158                    $this->writeTableStyle($objWriter, $shape);
159                }
160                if ($shape instanceof Group) {
161                    $this->writeGroupStyle($objWriter, $shape);
162                }
163            }
164
165            ++$incSlide;
166        }
167        // Style : Bullet
168        if (!empty($this->arrStyleBullet)) {
169            foreach ($this->arrStyleBullet as $key => $item) {
170                $oStyle = $item['oStyle'];
171                $arrLevel = explode(';', $item['level']);
172                // style:style
173                $objWriter->startElement('text:list-style');
174                $objWriter->writeAttribute('style:name', 'L_' . $key);
175                foreach ($arrLevel as $level) {
176                    if ('' != $level) {
177                        $oAlign = $item['oAlign_' . $level];
178                        // text:list-level-style-bullet
179                        $objWriter->startElement('text:list-level-style-bullet');
180                        $objWriter->writeAttribute('text:level', (int) $level + 1);
181                        $objWriter->writeAttribute('text:bullet-char', $oStyle->getBulletChar());
182                        // style:list-level-properties
183                        $objWriter->startElement('style:list-level-properties');
184                        if ($oAlign->getIndent() < 0) {
185                            $objWriter->writeAttribute('text:space-before', CommonDrawing::pixelsToCentimeters((int) ($oAlign->getMarginLeft() - (-1 * $oAlign->getIndent()))) . 'cm');
186                            $objWriter->writeAttribute('text:min-label-width', CommonDrawing::pixelsToCentimeters((int) (-1 * $oAlign->getIndent())) . 'cm');
187                        } else {
188                            $objWriter->writeAttribute('text:space-before', (CommonDrawing::pixelsToCentimeters((int) ($oAlign->getMarginLeft() - $oAlign->getIndent()))) . 'cm');
189                            $objWriter->writeAttribute('text:min-label-width', CommonDrawing::pixelsToCentimeters((int) $oAlign->getIndent()) . 'cm');
190                        }
191
192                        $objWriter->endElement();
193                        // style:text-properties
194                        $objWriter->startElement('style:text-properties');
195                        $objWriter->writeAttribute('fo:font-family', $oStyle->getBulletFont());
196                        $objWriter->writeAttribute('style:font-family-generic', 'swiss');
197                        $objWriter->writeAttribute('style:use-window-font-color', 'true');
198                        $objWriter->writeAttribute('fo:font-size', '100%');
199                        $objWriter->endElement();
200                        $objWriter->endElement();
201                    }
202                }
203                $objWriter->endElement();
204            }
205        }
206        // Style : Paragraph
207        if (!empty($this->arrStyleParagraph)) {
208            foreach ($this->arrStyleParagraph as $key => $item) {
209                // style:style
210                $objWriter->startElement('style:style');
211                $objWriter->writeAttribute('style:name', 'P_' . $key);
212                $objWriter->writeAttribute('style:family', 'paragraph');
213                // style:paragraph-properties
214                $objWriter->startElement('style:paragraph-properties');
215                $objWriter->writeAttributeIf(
216                    $item->getLineSpacingMode() === Paragraph::LINE_SPACING_MODE_PERCENT,
217                    'fo:line-height',
218                    $item->getLineSpacing() . '%'
219                );
220                $objWriter->writeAttributeIf(
221                    $item->getLineSpacingMode() === Paragraph::LINE_SPACING_MODE_POINT,
222                    'fo:line-height',
223                    $item->getLineSpacing() . 'pt'
224                );
225                $objWriter->writeAttribute(
226                    'fo:margin-top',
227                    Text::numberFormat(CommonDrawing::pointstoCentimeters($item->getSpacingBefore()), 3) . 'cm'
228                );
229                $objWriter->writeAttribute(
230                    'fo:margin-bottom',
231                    Text::numberFormat(CommonDrawing::pointstoCentimeters($item->getSpacingAfter()), 3) . 'cm'
232                );
233                switch ($item->getAlignment()->getHorizontal()) {
234                    case Alignment::HORIZONTAL_LEFT:
235                        $objWriter->writeAttribute('fo:text-align', 'left');
236
237                        break;
238                    case Alignment::HORIZONTAL_RIGHT:
239                        $objWriter->writeAttribute('fo:text-align', 'right');
240
241                        break;
242                    case Alignment::HORIZONTAL_CENTER:
243                        $objWriter->writeAttribute('fo:text-align', 'center');
244
245                        break;
246                    case Alignment::HORIZONTAL_JUSTIFY:
247                        $objWriter->writeAttribute('fo:text-align', 'justify');
248
249                        break;
250                    case Alignment::HORIZONTAL_DISTRIBUTED:
251                        $objWriter->writeAttribute('fo:text-align', 'justify');
252
253                        break;
254                    default:
255                        $objWriter->writeAttribute('fo:text-align', 'left');
256
257                        break;
258                }
259                $objWriter->writeAttribute(
260                    'style:writing-mode',
261                    $item->getAlignment()->isRTL() ? 'rl-tb' : 'lr-tb'
262                );
263                $objWriter->endElement();
264                $objWriter->endElement();
265            }
266        }
267        // Style : Text : Font
268        if (!empty($this->arrStyleTextFont)) {
269            foreach ($this->arrStyleTextFont as $key => $item) {
270                // style:style
271                $objWriter->startElement('style:style');
272                $objWriter->writeAttribute('style:name', 'T_' . $key);
273                $objWriter->writeAttribute('style:family', 'text');
274
275                // style:style > style:text-properties
276                $objWriter->startElement('style:text-properties');
277                $objWriter->writeAttribute('fo:color', '#' . $item->getFont()->getColor()->getRGB());
278                switch ($item->getFont()->getCapitalization()) {
279                    case Font::CAPITALIZATION_NONE:
280                        $objWriter->writeAttribute('fo:text-transform', 'none');
281
282                        break;
283                    case Font::CAPITALIZATION_ALL:
284                        $objWriter->writeAttribute('fo:text-transform', 'uppercase');
285
286                        break;
287                    case Font::CAPITALIZATION_SMALL:
288                        $objWriter->writeAttribute('fo:text-transform', 'lowercase');
289
290                        break;
291                }
292                switch ($item->getFont()->getFormat()) {
293                    case Font::FORMAT_LATIN:
294                        $objWriter->writeAttribute('fo:font-family', $item->getFont()->getName());
295                        $objWriter->writeAttribute('fo:font-size', $item->getFont()->getSize() . 'pt');
296                        $objWriter->writeAttributeIf($item->getFont()->isBold(), 'fo:font-weight', 'bold');
297                        $objWriter->writeAttribute('fo:language', ($item->getLanguage() ? $item->getLanguage() : 'en'));
298                        $objWriter->writeAttribute('style:script-type', 'latin');
299
300                        break;
301                    case Font::FORMAT_EAST_ASIAN:
302                        $objWriter->writeAttribute('style:font-family-asian', $item->getFont()->getName());
303                        $objWriter->writeAttribute('style:font-size-asian', $item->getFont()->getSize() . 'pt');
304                        $objWriter->writeAttributeIf($item->getFont()->isBold(), 'style:font-weight-asian', 'bold');
305                        $objWriter->writeAttribute('style:language-asian', ($item->getLanguage() ? $item->getLanguage() : 'en'));
306                        $objWriter->writeAttribute('style:script-type', 'asian');
307
308                        break;
309                    case Font::FORMAT_COMPLEX_SCRIPT:
310                        $objWriter->writeAttribute('style:font-family-complex', $item->getFont()->getName());
311                        $objWriter->writeAttribute('style:font-size-complex', $item->getFont()->getSize() . 'pt');
312                        $objWriter->writeAttributeIf($item->getFont()->isBold(), 'style:font-weight-complex', 'bold');
313                        $objWriter->writeAttribute('style:language-complex', ($item->getLanguage() ? $item->getLanguage() : 'en'));
314                        $objWriter->writeAttribute('style:script-type', 'complex');
315
316                        break;
317                }
318
319                // > style:style > style:text-properties
320                $objWriter->endElement();
321                // > style:style
322                $objWriter->endElement();
323            }
324        }
325        $objWriter->endElement();
326
327        //===============================================
328        // Body
329        //===============================================
330        // office:body
331        $objWriter->startElement('office:body');
332        // office:body > office:presentation
333        $objWriter->startElement('office:presentation');
334
335        // Write slides
336        $slideCount = $this->getPresentation()->getSlideCount();
337        $this->shapeId = 0;
338        for ($i = 0; $i < $slideCount; ++$i) {
339            $pSlide = $this->getPresentation()->getSlide($i);
340            $objWriter->startElement('draw:page');
341            $name = $pSlide->getName();
342            if (null !== $name) {
343                $objWriter->writeAttribute('draw:name', $name);
344            }
345            $objWriter->writeAttribute('draw:master-page-name', 'Standard');
346            $objWriter->writeAttribute('draw:style-name', 'stylePage' . $i);
347            // Images
348            $shapes = $pSlide->getShapeCollection();
349            foreach ($shapes as $shape) {
350                // Increment $this->shapeId
351                ++$this->shapeId;
352
353                // Check type
354                if ($shape instanceof RichText) {
355                    $this->writeShapeTxt($objWriter, $shape);
356                } elseif ($shape instanceof Table) {
357                    $this->writeShapeTable($objWriter, $shape);
358                } elseif ($shape instanceof Line) {
359                    $this->writeShapeLine($objWriter, $shape);
360                } elseif ($shape instanceof Chart) {
361                    $this->writeShapeChart($objWriter, $shape);
362                } elseif ($shape instanceof Media) {
363                    $this->writeShapeMedia($objWriter, $shape);
364                } elseif ($shape instanceof AbstractDrawingAdapter) {
365                    $this->writeShapeDrawing($objWriter, $shape);
366                } elseif ($shape instanceof Group) {
367                    $this->writeShapeGroup($objWriter, $shape);
368                } elseif ($shape instanceof Comment) {
369                    $this->writeShapeComment($objWriter, $shape);
370                }
371            }
372            // Slide Note
373            if ($pSlide->getNote() instanceof Note) {
374                $this->writeSlideNote($objWriter, $pSlide->getNote());
375            }
376
377            $objWriter->endElement();
378        }
379
380        // office:document-content > office:body > office:presentation > presentation:settings
381        $objWriter->startElement('presentation:settings');
382        if ($this->getPresentation()->getPresentationProperties()->isLoopContinuouslyUntilEsc()) {
383            $objWriter->writeAttribute('presentation:endless', 'true');
384            $objWriter->writeAttribute('presentation:pause', 'PT0S');
385            $objWriter->writeAttribute('presentation:mouse-visible', 'false');
386        }
387        if ($this->getPresentation()->getPresentationProperties()->getSlideshowType() === PresentationProperties::SLIDESHOW_TYPE_BROWSE) {
388            $objWriter->writeAttribute('presentation:full-screen', 'false');
389        }
390        $objWriter->endElement();
391
392        // > office:presentation
393        $objWriter->endElement();
394        // > office:body
395        $objWriter->endElement();
396        // > office:document-content
397        $objWriter->endElement();
398
399        // Return
400        return $objWriter->getData();
401    }
402
403    /**
404     * Write picture.
405     */
406    protected function writeShapeMedia(XMLWriter $objWriter, Media $shape): void
407    {
408        // draw:frame
409        $objWriter->startElement('draw:frame');
410        $objWriter->writeAttribute('draw:name', $shape->getName());
411        $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getWidth()), 3) . 'cm');
412        $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getHeight()), 3) . 'cm');
413        $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX()), 3) . 'cm');
414        $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY()), 3) . 'cm');
415        $objWriter->writeAttribute('draw:style-name', 'gr' . $this->shapeId);
416        // draw:frame > draw:plugin
417        $objWriter->startElement('draw:plugin');
418        $objWriter->writeAttribute('xlink:href', 'Pictures/' . $shape->getIndexedFilename());
419        $objWriter->writeAttribute('xlink:type', 'simple');
420        $objWriter->writeAttribute('xlink:show', 'embed');
421        $objWriter->writeAttribute('xlink:actuate', 'onLoad');
422        $objWriter->writeAttribute('draw:mime-type', 'application/vnd.sun.star.media');
423
424        $objWriter->startElement('draw:param');
425        $objWriter->writeAttribute('draw:name', 'Loop');
426        $objWriter->writeAttribute('draw:value', 'false');
427        $objWriter->endElement();
428        $objWriter->startElement('draw:param');
429        $objWriter->writeAttribute('draw:name', 'Mute');
430        $objWriter->writeAttribute('draw:value', 'false');
431        $objWriter->endElement();
432        $objWriter->startElement('draw:param');
433        $objWriter->writeAttribute('draw:name', 'VolumeDB');
434        $objWriter->writeAttribute('draw:value', 0);
435        $objWriter->endElement();
436        $objWriter->startElement('draw:param');
437        $objWriter->writeAttribute('draw:name', 'Zoom');
438        $objWriter->writeAttribute('draw:value', 'fit');
439        $objWriter->endElement();
440
441        // draw:frame > ## draw:plugin
442        $objWriter->endElement();
443
444        // ## draw:frame
445        $objWriter->endElement();
446    }
447
448    /**
449     * Write picture.
450     */
451    protected function writeShapeDrawing(XMLWriter $objWriter, AbstractDrawingAdapter $shape): void
452    {
453        // draw:frame
454        $objWriter->startElement('draw:frame');
455        $objWriter->writeAttribute('draw:name', $shape->getName());
456        $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getWidth()), 3) . 'cm');
457        $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getHeight()), 3) . 'cm');
458        $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX()), 3) . 'cm');
459        $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY()), 3) . 'cm');
460        $objWriter->writeAttribute('draw:style-name', 'gr' . $this->shapeId);
461        // draw:image
462        $objWriter->startElement('draw:image');
463        if ($shape instanceof AbstractDrawingAdapter) {
464            $objWriter->writeAttribute('xlink:href', 'Pictures/' . $shape->getIndexedFilename());
465        }
466        $objWriter->writeAttribute('xlink:type', 'simple');
467        $objWriter->writeAttribute('xlink:show', 'embed');
468        $objWriter->writeAttribute('xlink:actuate', 'onLoad');
469        $objWriter->writeAttribute('loext:mime-type', $shape->getMimeType());
470        $objWriter->writeElement('text:p');
471        $objWriter->endElement();
472
473        if ($shape->hasHyperlink()) {
474            // office:event-listeners
475            $objWriter->startElement('office:event-listeners');
476            // presentation:event-listener
477            $objWriter->startElement('presentation:event-listener');
478            $objWriter->writeAttribute('script:event-name', 'dom:click');
479            $objWriter->writeAttribute('presentation:action', 'show');
480            $objWriter->writeAttribute('xlink:href', $shape->getHyperlink()->getUrl());
481            $objWriter->writeAttribute('xlink:type', 'simple');
482            $objWriter->writeAttribute('xlink:show', 'embed');
483            $objWriter->writeAttribute('xlink:actuate', 'onRequest');
484            // > presentation:event-listener
485            $objWriter->endElement();
486            // > office:event-listeners
487            $objWriter->endElement();
488        }
489
490        $objWriter->endElement();
491    }
492
493    /**
494     * Write text.
495     */
496    protected function writeShapeTxt(XMLWriter $objWriter, RichText $shape): void
497    {
498        // draw:frame
499        $objWriter->startElement('draw:frame');
500        $objWriter->writeAttribute('draw:style-name', 'gr' . $this->shapeId);
501        $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getWidth()), 3) . 'cm');
502        $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getHeight()), 3) . 'cm');
503        $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX()), 3) . 'cm');
504        $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY()), 3) . 'cm');
505        // draw:text-box
506        $objWriter->startElement('draw:text-box');
507
508        $paragraphs = $shape->getParagraphs();
509        $paragraphId = 0;
510        $sCstShpLastBullet = '';
511        $iCstShpLastBulletLvl = 0;
512        $bCstShpHasBullet = false;
513
514        foreach ($paragraphs as $paragraph) {
515            // Close the bullet list
516            if ('bullet' != $sCstShpLastBullet && true === $bCstShpHasBullet) {
517                for ($iInc = $iCstShpLastBulletLvl; $iInc >= 0; --$iInc) {
518                    // text:list-item
519                    $objWriter->endElement();
520                    // text:list
521                    $objWriter->endElement();
522                }
523            }
524            //===============================================
525            // Paragraph
526            //===============================================
527            if ('none' == $paragraph->getBulletStyle()->getBulletType()) {
528                ++$paragraphId;
529                // text:p
530                $objWriter->startElement('text:p');
531                $objWriter->writeAttribute('text:style-name', 'P_' . $paragraph->getHashCode());
532
533                // Loop trough rich text elements
534                $richtexts = $paragraph->getRichTextElements();
535                $richtextId = 0;
536                foreach ($richtexts as $richtext) {
537                    ++$richtextId;
538                    if ($richtext instanceof TextElement) {
539                        // text:span
540                        $objWriter->startElement('text:span');
541                        if ($richtext instanceof Run) {
542                            $objWriter->writeAttribute('text:style-name', 'T_' . $richtext->getHashCode());
543                        }
544                        if (true === $richtext->hasHyperlink() && '' != $richtext->getHyperlink()->getUrl()) {
545                            // text:a
546                            $objWriter->startElement('text:a');
547                            $objWriter->writeAttribute('xlink:type', 'simple');
548                            $objWriter->writeAttribute('xlink:href', $richtext->getHyperlink()->getUrl());
549                            $objWriter->text($richtext->getText());
550                            $objWriter->endElement();
551                        } else {
552                            $objWriter->text($richtext->getText());
553                        }
554                        $objWriter->endElement();
555                    } elseif ($richtext instanceof BreakElement) {
556                        // text:span
557                        $objWriter->startElement('text:span');
558                        // text:line-break
559                        $objWriter->startElement('text:line-break');
560                        $objWriter->endElement();
561                        $objWriter->endElement();
562                    }
563                }
564                $objWriter->endElement();
565                //===============================================
566                // Bullet list
567                //===============================================
568            } elseif ('bullet' == $paragraph->getBulletStyle()->getBulletType()) {
569                $bCstShpHasBullet = true;
570                // Open the bullet list
571                if ('bullet' != $sCstShpLastBullet || $iCstShpLastBulletLvl < $paragraph->getAlignment()->getLevel()) {
572                    // text:list
573                    $objWriter->startElement('text:list');
574                    $objWriter->writeAttribute('text:style-name', 'L_' . $paragraph->getBulletStyle()->getHashCode());
575                }
576                if ('bullet' == $sCstShpLastBullet) {
577                    if ($iCstShpLastBulletLvl == $paragraph->getAlignment()->getLevel()) {
578                        // text:list-item
579                        $objWriter->endElement();
580                    } elseif ($iCstShpLastBulletLvl > $paragraph->getAlignment()->getLevel()) {
581                        // text:list-item
582                        $objWriter->endElement();
583                        // text:list
584                        $objWriter->endElement();
585                        // text:list-item
586                        $objWriter->endElement();
587                    }
588                }
589
590                // text:list-item
591                $objWriter->startElement('text:list-item');
592                ++$paragraphId;
593                // text:p
594                $objWriter->startElement('text:p');
595                $objWriter->writeAttribute('text:style-name', 'P_' . $paragraph->getHashCode());
596
597                // Loop trough rich text elements
598                $richtexts = $paragraph->getRichTextElements();
599                $richtextId = 0;
600                foreach ($richtexts as $richtext) {
601                    ++$richtextId;
602                    if ($richtext instanceof TextElement) {
603                        // text:span
604                        $objWriter->startElement('text:span');
605                        if ($richtext instanceof Run) {
606                            $objWriter->writeAttribute('text:style-name', 'T_' . $richtext->getHashCode());
607                        }
608                        if (true === $richtext->hasHyperlink() && '' != $richtext->getHyperlink()->getUrl()) {
609                            // text:a
610                            $objWriter->startElement('text:a');
611                            $objWriter->writeAttribute('xlink:type', 'simple');
612                            $objWriter->writeAttribute('xlink:href', $richtext->getHyperlink()->getUrl());
613                            $objWriter->text($richtext->getText());
614                            $objWriter->endElement();
615                        } else {
616                            $objWriter->text($richtext->getText());
617                        }
618                        $objWriter->endElement();
619                    } elseif ($richtext instanceof BreakElement) {
620                        // text:span
621                        $objWriter->startElement('text:span');
622                        // text:line-break
623                        $objWriter->startElement('text:line-break');
624                        $objWriter->endElement();
625                        $objWriter->endElement();
626                    }
627                }
628                $objWriter->endElement();
629            }
630            $sCstShpLastBullet = $paragraph->getBulletStyle()->getBulletType();
631            $iCstShpLastBulletLvl = $paragraph->getAlignment()->getLevel();
632        }
633
634        // Close the bullet list
635        if ('bullet' == $sCstShpLastBullet && true === $bCstShpHasBullet) {
636            for ($iInc = $iCstShpLastBulletLvl; $iInc >= 0; --$iInc) {
637                // text:list-item
638                $objWriter->endElement();
639                // text:list
640                $objWriter->endElement();
641            }
642        }
643
644        // > draw:text-box
645        $objWriter->endElement();
646        // > draw:frame
647        $objWriter->endElement();
648    }
649
650    /**
651     * Write Comment.
652     */
653    protected function writeShapeComment(XMLWriter $objWriter, Comment $oShape): void
654    {
655        // Note : This element is not valid in the Schema 1.2
656        // officeooo:annotation
657        $objWriter->startElement('officeooo:annotation');
658        $objWriter->writeAttribute('svg:x', number_format(CommonDrawing::pixelsToCentimeters((int) $oShape->getOffsetX()), 2, '.', '') . 'cm');
659        $objWriter->writeAttribute('svg:y', number_format(CommonDrawing::pixelsToCentimeters((int) $oShape->getOffsetY()), 2, '.', '') . 'cm');
660
661        if ($oShape->getAuthor() instanceof Comment\Author) {
662            $objWriter->writeElement('dc:creator', $oShape->getAuthor()->getName());
663        }
664        $objWriter->writeElement('dc:date', date('Y-m-d\TH:i:s', $oShape->getDate()));
665        $objWriter->writeElement('text:p', $oShape->getText());
666
667        // ## officeooo:annotation
668        $objWriter->endElement();
669    }
670
671    protected function writeShapeLine(XMLWriter $objWriter, Line $shape): void
672    {
673        // draw:line
674        $objWriter->startElement('draw:line');
675        $objWriter->writeAttribute('draw:style-name', 'gr' . $this->shapeId);
676        $objWriter->writeAttribute('svg:x1', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX()), 3) . 'cm');
677        $objWriter->writeAttribute('svg:y1', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY()), 3) . 'cm');
678        $objWriter->writeAttribute('svg:x2', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX() + $shape->getWidth()), 3) . 'cm');
679        $objWriter->writeAttribute('svg:y2', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY() + $shape->getHeight()), 3) . 'cm');
680
681        // text:p
682        $objWriter->writeElement('text:p');
683
684        $objWriter->endElement();
685    }
686
687    /**
688     * Write table Shape.
689     */
690    protected function writeShapeTable(XMLWriter $objWriter, Table $shape): void
691    {
692        // draw:frame
693        $objWriter->startElement('draw:frame');
694        $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX()), 3) . 'cm');
695        $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY()), 3) . 'cm');
696        $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getHeight()), 3) . 'cm');
697        $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getWidth()), 3) . 'cm');
698
699        $arrayRows = $shape->getRows();
700        if (!empty($arrayRows)) {
701            $firstRow = reset($arrayRows);
702            $arrayCells = $firstRow->getCells();
703            // table:table
704            $objWriter->startElement('table:table');
705            foreach ($arrayCells as $shapeCell) {
706                $objWriter->startElement('table:table-column');
707                $objWriter->endElement();
708            }
709            foreach ($arrayRows as $keyRow => $shapeRow) {
710                // table:table-row
711                $objWriter->startElement('table:table-row');
712                $objWriter->writeAttribute('table:style-name', 'gr' . $this->shapeId . 'r' . $keyRow);
713                //@todo getFill
714
715                $numColspan = 0;
716                foreach ($shapeRow->getCells() as $keyCell => $shapeCell) {
717                    if (0 == $numColspan) {
718                        // table:table-cell
719                        $objWriter->startElement('table:table-cell');
720                        $objWriter->writeAttribute('table:style-name', 'gr' . $this->shapeId . 'r' . $keyRow . 'c' . $keyCell);
721                        if ($shapeCell->getColspan() > 1) {
722                            $objWriter->writeAttribute('table:number-columns-spanned', $shapeCell->getColspan());
723                            $numColspan = $shapeCell->getColspan() - 1;
724                        }
725
726                        // text:p
727                        $objWriter->startElement('text:p');
728
729                        // text:span
730                        foreach ($shapeCell->getParagraphs() as $shapeParagraph) {
731                            foreach ($shapeParagraph->getRichTextElements() as $shapeRichText) {
732                                if ($shapeRichText instanceof TextElement) {
733                                    // text:span
734                                    $objWriter->startElement('text:span');
735                                    if ($shapeRichText instanceof Run) {
736                                        $objWriter->writeAttribute('text:style-name', 'T_' . $shapeRichText->getHashCode());
737                                    }
738                                    if (true === $shapeRichText->hasHyperlink() && '' !== $shapeRichText->getHyperlink()->getUrl()) {
739                                        // text:a
740                                        $objWriter->startElement('text:a');
741                                        $objWriter->writeAttribute('xlink:type', 'simple');
742                                        $objWriter->writeAttribute('xlink:href', $shapeRichText->getHyperlink()->getUrl());
743                                        $objWriter->text($shapeRichText->getText());
744                                        $objWriter->endElement();
745                                    } else {
746                                        $objWriter->text($shapeRichText->getText());
747                                    }
748                                    $objWriter->endElement();
749                                } elseif ($shapeRichText instanceof BreakElement) {
750                                    // text:span
751                                    $objWriter->startElement('text:span');
752                                    // text:line-break
753                                    $objWriter->startElement('text:line-break');
754                                    $objWriter->endElement();
755                                    $objWriter->endElement();
756                                }
757                            }
758                        }
759
760                        // > text:p
761                        $objWriter->endElement();
762
763                        // > table:table-cell
764                        $objWriter->endElement();
765                    } else {
766                        // table:covered-table-cell
767                        $objWriter->writeElement('table:covered-table-cell');
768                        --$numColspan;
769                    }
770                }
771                // > table:table-row
772                $objWriter->endElement();
773            }
774            // > table:table
775            $objWriter->endElement();
776        }
777        // > draw:frame
778        $objWriter->endElement();
779    }
780
781    /**
782     * Write table Chart.
783     */
784    protected function writeShapeChart(XMLWriter $objWriter, Chart $shape): void
785    {
786        $arrayChart = $this->getArrayChart();
787        $arrayChart[$this->shapeId] = $shape;
788        $this->setArrayChart($arrayChart);
789
790        // draw:frame
791        $objWriter->startElement('draw:frame');
792        $objWriter->writeAttribute('draw:name', $shape->getTitle()->getText());
793        $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX()), 3) . 'cm');
794        $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY()), 3) . 'cm');
795        $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getHeight()), 3) . 'cm');
796        $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getWidth()), 3) . 'cm');
797
798        // draw:object
799        $objWriter->startElement('draw:object');
800        $objWriter->writeAttribute('xlink:href', './Object ' . $this->shapeId);
801        $objWriter->writeAttribute('xlink:type', 'simple');
802        $objWriter->writeAttribute('xlink:show', 'embed');
803
804        // > draw:object
805        $objWriter->endElement();
806        // > draw:frame
807        $objWriter->endElement();
808    }
809
810    /**
811     * Writes a group of shapes.
812     */
813    protected function writeShapeGroup(XMLWriter $objWriter, Group $group): void
814    {
815        // draw:g
816        $objWriter->startElement('draw:g');
817
818        $shapes = $group->getShapeCollection();
819        foreach ($shapes as $shape) {
820            // Increment $this->shapeId
821            ++$this->shapeId;
822
823            // Check type
824            if ($shape instanceof RichText) {
825                $this->writeShapeTxt($objWriter, $shape);
826            } elseif ($shape instanceof Table) {
827                $this->writeShapeTable($objWriter, $shape);
828            } elseif ($shape instanceof Line) {
829                $this->writeShapeLine($objWriter, $shape);
830            } elseif ($shape instanceof Chart) {
831                $this->writeShapeChart($objWriter, $shape);
832            } elseif ($shape instanceof AbstractDrawingAdapter) {
833                $this->writeShapeDrawing($objWriter, $shape);
834            } elseif ($shape instanceof Group) {
835                $this->writeShapeGroup($objWriter, $shape);
836            }
837        }
838
839        $objWriter->endElement(); // draw:g
840    }
841
842    /**
843     * Writes the style information for a group of shapes.
844     */
845    protected function writeGroupStyle(XMLWriter $objWriter, Group $group): void
846    {
847        $shapes = $group->getShapeCollection();
848        foreach ($shapes as $shape) {
849            // Increment $this->shapeId
850            ++$this->shapeId;
851
852            // Check type
853            if ($shape instanceof RichText) {
854                $this->writeTxtStyle($objWriter, $shape);
855            }
856            if ($shape instanceof AbstractDrawingAdapter) {
857                $this->writeDrawingStyle($objWriter, $shape);
858            }
859            if ($shape instanceof Line) {
860                $this->writeLineStyle($objWriter, $shape);
861            }
862            if ($shape instanceof Table) {
863                $this->writeTableStyle($objWriter, $shape);
864            }
865        }
866    }
867
868    /**
869     * Write the default style information for a RichText shape.
870     */
871    protected function writeTxtStyle(XMLWriter $objWriter, RichText $shape): void
872    {
873        // style:style
874        $objWriter->startElement('style:style');
875        $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId);
876        $objWriter->writeAttribute('style:family', 'graphic');
877        $objWriter->writeAttribute('style:parent-style-name', 'standard');
878        // style:graphic-properties
879        $objWriter->startElement('style:graphic-properties');
880        $objWriter->writeAttribute('style:mirror', 'none');
881        $this->writeStylePartShadow($objWriter, $shape->getShadow());
882        if (is_bool($shape->hasAutoShrinkVertical())) {
883            $objWriter->writeAttribute('draw:auto-grow-height', var_export($shape->hasAutoShrinkVertical(), true));
884        }
885        if (is_bool($shape->hasAutoShrinkHorizontal())) {
886            $objWriter->writeAttribute('draw:auto-grow-width', var_export($shape->hasAutoShrinkHorizontal(), true));
887        }
888        // Fill
889        switch ($shape->getFill()->getFillType()) {
890            case Fill::FILL_GRADIENT_LINEAR:
891            case Fill::FILL_GRADIENT_PATH:
892                $objWriter->writeAttribute('draw:fill', 'gradient');
893                $objWriter->writeAttribute('draw:fill-gradient-name', 'gradient_' . $shape->getFill()->getHashCode());
894
895                break;
896            case Fill::FILL_SOLID:
897                $objWriter->writeAttribute('draw:fill', 'solid');
898                $objWriter->writeAttribute('draw:fill-color', '#' . $shape->getFill()->getStartColor()->getRGB());
899
900                break;
901            case Fill::FILL_NONE:
902            default:
903                $objWriter->writeAttribute('draw:fill', 'none');
904                $objWriter->writeAttribute('draw:fill-color', '#' . $shape->getFill()->getStartColor()->getRGB());
905
906                break;
907        }
908        // Border
909        if (Border::LINE_NONE == $shape->getBorder()->getLineStyle()) {
910            $objWriter->writeAttribute('draw:stroke', 'none');
911        } else {
912            $objWriter->writeAttribute('svg:stroke-color', '#' . $shape->getBorder()->getColor()->getRGB());
913            $objWriter->writeAttribute('svg:stroke-width', number_format(CommonDrawing::pointsToCentimeters($shape->getBorder()->getLineWidth()), 3, '.', '') . 'cm');
914            switch ($shape->getBorder()->getDashStyle()) {
915                case Border::DASH_SOLID:
916                    $objWriter->writeAttribute('draw:stroke', 'solid');
917
918                    break;
919                case Border::DASH_DASH:
920                case Border::DASH_DASHDOT:
921                case Border::DASH_DOT:
922                case Border::DASH_LARGEDASH:
923                case Border::DASH_LARGEDASHDOT:
924                case Border::DASH_LARGEDASHDOTDOT:
925                case Border::DASH_SYSDASH:
926                case Border::DASH_SYSDASHDOT:
927                case Border::DASH_SYSDASHDOTDOT:
928                case Border::DASH_SYSDOT:
929                    $objWriter->writeAttribute('draw:stroke', 'dash');
930                    $objWriter->writeAttribute('draw:stroke-dash', 'strokeDash_' . $shape->getBorder()->getDashStyle());
931
932                    break;
933                default:
934                    $objWriter->writeAttribute('draw:stroke', 'none');
935
936                    break;
937            }
938        }
939
940        $objWriter->writeAttribute('fo:wrap-option', 'wrap');
941        // > style:graphic-properties
942        $objWriter->endElement();
943        // > style:style
944        $objWriter->endElement();
945
946        $paragraphs = $shape->getParagraphs();
947        $paragraphId = 0;
948        foreach ($paragraphs as $paragraph) {
949            ++$paragraphId;
950
951            // Style des paragraphes
952            if (!isset($this->arrStyleParagraph[$paragraph->getHashCode()])) {
953                $this->arrStyleParagraph[$paragraph->getHashCode()] = $paragraph;
954            }
955
956            // Style des listes
957            $bulletStyleHashCode = $paragraph->getBulletStyle()->getHashCode();
958            if (!isset($this->arrStyleBullet[$bulletStyleHashCode])) {
959                $this->arrStyleBullet[$bulletStyleHashCode]['oStyle'] = $paragraph->getBulletStyle();
960                $this->arrStyleBullet[$bulletStyleHashCode]['level'] = '';
961            }
962            if (false === strpos($this->arrStyleBullet[$bulletStyleHashCode]['level'], ';' . $paragraph->getAlignment()->getLevel())) {
963                $this->arrStyleBullet[$bulletStyleHashCode]['level'] .= ';' . $paragraph->getAlignment()->getLevel();
964                $this->arrStyleBullet[$bulletStyleHashCode]['oAlign_' . $paragraph->getAlignment()->getLevel()] = $paragraph->getAlignment();
965            }
966
967            $richtexts = $paragraph->getRichTextElements();
968            $richtextId = 0;
969            foreach ($richtexts as $richtext) {
970                ++$richtextId;
971                // Not a line break
972                if ($richtext instanceof Run) {
973                    // Style des font text
974                    if (!isset($this->arrStyleTextFont[$richtext->getHashCode()])) {
975                        $this->arrStyleTextFont[$richtext->getHashCode()] = $richtext;
976                    }
977                }
978            }
979        }
980    }
981
982    /**
983     * Write the default style information for an AbstractDrawingAdapter.
984     */
985    protected function writeDrawingStyle(XMLWriter $objWriter, AbstractDrawingAdapter $shape): void
986    {
987        // style:style
988        $objWriter->startElement('style:style');
989        $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId);
990        $objWriter->writeAttribute('style:family', 'graphic');
991        $objWriter->writeAttribute('style:parent-style-name', 'standard');
992
993        // style:graphic-properties
994        $objWriter->startElement('style:graphic-properties');
995        $objWriter->writeAttribute('draw:stroke', 'none');
996        $objWriter->writeAttribute('style:mirror', 'none');
997        $this->writeStylePartFill($objWriter, $shape->getFill());
998        $this->writeStylePartShadow($objWriter, $shape->getShadow());
999        $objWriter->endElement();
1000
1001        $objWriter->endElement();
1002    }
1003
1004    /**
1005     * Write the default style information for a Line shape.
1006     */
1007    protected function writeLineStyle(XMLWriter $objWriter, Line $shape): void
1008    {
1009        // style:style
1010        $objWriter->startElement('style:style');
1011        $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId);
1012        $objWriter->writeAttribute('style:family', 'graphic');
1013        $objWriter->writeAttribute('style:parent-style-name', 'standard');
1014
1015        // style:graphic-properties
1016        $objWriter->startElement('style:graphic-properties');
1017        $objWriter->writeAttribute('draw:fill', 'none');
1018        switch ($shape->getBorder()->getLineStyle()) {
1019            case Border::LINE_NONE:
1020                $objWriter->writeAttribute('draw:stroke', 'none');
1021
1022                break;
1023            case Border::LINE_SINGLE:
1024                $objWriter->writeAttribute('draw:stroke', 'solid');
1025
1026                break;
1027            default:
1028                $objWriter->writeAttribute('draw:stroke', 'none');
1029
1030                break;
1031        }
1032        $objWriter->writeAttribute('svg:stroke-color', '#' . $shape->getBorder()->getColor()->getRGB());
1033        $objWriter->writeAttribute('svg:stroke-width', Text::numberFormat(CommonDrawing::pointsToCentimeters($shape->getBorder()->getLineWidth()), 3) . 'cm');
1034        $objWriter->endElement();
1035
1036        $objWriter->endElement();
1037    }
1038
1039    /**
1040     * Write the default style information for a Table shape.
1041     */
1042    protected function writeTableStyle(XMLWriter $objWriter, Table $shape): void
1043    {
1044        foreach ($shape->getRows() as $keyRow => $shapeRow) {
1045            // style:style
1046            $objWriter->startElement('style:style');
1047            $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId . 'r' . $keyRow);
1048            $objWriter->writeAttribute('style:family', 'table-row');
1049
1050            // style:table-row-properties
1051            $objWriter->startElement('style:table-row-properties');
1052            $objWriter->writeAttribute('style:row-height', Text::numberFormat(CommonDrawing::pointsToCentimeters($shapeRow->getHeight()), 3) . 'cm');
1053            $objWriter->endElement();
1054
1055            $objWriter->endElement();
1056
1057            foreach ($shapeRow->getCells() as $keyCell => $shapeCell) {
1058                // style:style
1059                $objWriter->startElement('style:style');
1060                $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId . 'r' . $keyRow . 'c' . $keyCell);
1061                $objWriter->writeAttribute('style:family', 'table-cell');
1062
1063                // Note : This element is not valid in the Schema 1.2
1064                // style:graphic-properties
1065                if (Fill::FILL_NONE != $shapeCell->getFill()->getFillType()) {
1066                    $objWriter->startElement('style:graphic-properties');
1067                    if (Fill::FILL_SOLID == $shapeCell->getFill()->getFillType()) {
1068                        $objWriter->writeAttribute('draw:fill', 'solid');
1069                        $objWriter->writeAttribute('draw:fill-color', '#' . $shapeCell->getFill()->getStartColor()->getRGB());
1070                    }
1071                    if (Fill::FILL_GRADIENT_LINEAR == $shapeCell->getFill()->getFillType()) {
1072                        $objWriter->writeAttribute('draw:fill', 'gradient');
1073                        $objWriter->writeAttribute('draw:fill-gradient-name', 'gradient_' . $shapeCell->getFill()->getHashCode());
1074                    }
1075                    $objWriter->endElement();
1076                }
1077                // >style:graphic-properties
1078
1079                // style:paragraph-properties
1080                $objWriter->startElement('style:paragraph-properties');
1081                $cellBorders = $shapeCell->getBorders();
1082                $cellBordersBottomHashCode = $cellBorders->getBottom()->getHashCode();
1083                if ($cellBordersBottomHashCode == $cellBorders->getTop()->getHashCode()
1084                    && $cellBordersBottomHashCode == $cellBorders->getLeft()->getHashCode()
1085                    && $cellBordersBottomHashCode == $cellBorders->getRight()->getHashCode()) {
1086                    $lineStyle = 'none';
1087                    $lineWidth = Text::numberFormat($cellBorders->getBottom()->getLineWidth() / 1.75, 2);
1088                    $lineColor = $cellBorders->getBottom()->getColor()->getRGB();
1089                    switch ($cellBorders->getBottom()->getLineStyle()) {
1090                        case Border::LINE_SINGLE:
1091                            $lineStyle = 'solid';
1092                    }
1093                    $objWriter->writeAttribute('fo:border', $lineWidth . 'pt ' . $lineStyle . ' #' . $lineColor);
1094                } else {
1095                    $lineStyle = 'none';
1096                    $lineWidth = Text::numberFormat($cellBorders->getBottom()->getLineWidth() / 1.75, 2);
1097                    $lineColor = $cellBorders->getBottom()->getColor()->getRGB();
1098                    switch ($cellBorders->getBottom()->getLineStyle()) {
1099                        case Border::LINE_SINGLE:
1100                            $lineStyle = 'solid';
1101                    }
1102                    $objWriter->writeAttribute('fo:border-bottom', $lineWidth . 'pt ' . $lineStyle . ' #' . $lineColor);
1103                    // TOP
1104                    $lineStyle = 'none';
1105                    $lineWidth = Text::numberFormat($cellBorders->getTop()->getLineWidth() / 1.75, 2);
1106                    $lineColor = $cellBorders->getTop()->getColor()->getRGB();
1107                    switch ($cellBorders->getTop()->getLineStyle()) {
1108                        case Border::LINE_SINGLE:
1109                            $lineStyle = 'solid';
1110                    }
1111                    $objWriter->writeAttribute('fo:border-top', $lineWidth . 'pt ' . $lineStyle . ' #' . $lineColor);
1112                    // RIGHT
1113                    $lineStyle = 'none';
1114                    $lineWidth = Text::numberFormat($cellBorders->getRight()->getLineWidth() / 1.75, 2);
1115                    $lineColor = $cellBorders->getRight()->getColor()->getRGB();
1116                    switch ($cellBorders->getRight()->getLineStyle()) {
1117                        case Border::LINE_SINGLE:
1118                            $lineStyle = 'solid';
1119                    }
1120                    $objWriter->writeAttribute('fo:border-right', $lineWidth . 'pt ' . $lineStyle . ' #' . $lineColor);
1121                    // LEFT
1122                    $lineStyle = 'none';
1123                    $lineWidth = Text::numberFormat($cellBorders->getLeft()->getLineWidth() / 1.75, 2);
1124                    $lineColor = $cellBorders->getLeft()->getColor()->getRGB();
1125                    switch ($cellBorders->getLeft()->getLineStyle()) {
1126                        case Border::LINE_SINGLE:
1127                            $lineStyle = 'solid';
1128                    }
1129                    $objWriter->writeAttribute('fo:border-left', $lineWidth . 'pt ' . $lineStyle . ' #' . $lineColor);
1130                }
1131                // >style:paragraph-properties
1132                $objWriter->endElement();
1133                // >style:style
1134                $objWriter->endElement();
1135
1136                foreach ($shapeCell->getParagraphs() as $shapeParagraph) {
1137                    foreach ($shapeParagraph->getRichTextElements() as $shapeRichText) {
1138                        if ($shapeRichText instanceof Run) {
1139                            // Style des font text
1140                            if (!isset($this->arrStyleTextFont[$shapeRichText->getHashCode()])) {
1141                                $this->arrStyleTextFont[$shapeRichText->getHashCode()] = $shapeRichText;
1142                            }
1143                        }
1144                    }
1145                }
1146            }
1147        }
1148    }
1149
1150    /**
1151     * Write the slide note.
1152     */
1153    protected function writeSlideNote(XMLWriter $objWriter, Note $note): void
1154    {
1155        $shapesNote = $note->getShapeCollection();
1156        if (count($shapesNote) > 0) {
1157            $objWriter->startElement('presentation:notes');
1158
1159            foreach ($shapesNote as $shape) {
1160                // Increment $this->shapeId
1161                ++$this->shapeId;
1162
1163                if ($shape instanceof RichText) {
1164                    $this->writeShapeTxt($objWriter, $shape);
1165                }
1166            }
1167
1168            $objWriter->endElement();
1169        }
1170    }
1171
1172    /**
1173     * Write style of a slide.
1174     */
1175    protected function writeStyleSlide(XMLWriter $objWriter, Slide $slide, int $incPage): void
1176    {
1177        // style:style
1178        $objWriter->startElement('style:style');
1179        $objWriter->writeAttribute('style:family', 'drawing-page');
1180        $objWriter->writeAttribute('style:name', 'stylePage' . $incPage);
1181        // style:style/style:drawing-page-properties
1182        $objWriter->startElement('style:drawing-page-properties');
1183        $objWriter->writeAttributeIf(!$slide->isVisible(), 'presentation:visibility', 'hidden');
1184        if (null !== ($oTransition = $slide->getTransition())) {
1185            $objWriter->writeAttribute('presentation:duration', 'PT' . number_format($oTransition->getAdvanceTimeTrigger() / 1000, 6, '.', '') . 'S');
1186            $objWriter->writeAttributeIf($oTransition->hasManualTrigger(), 'presentation:transition-type', 'manual');
1187            $objWriter->writeAttributeIf($oTransition->hasTimeTrigger(), 'presentation:transition-type', 'automatic');
1188            switch ($oTransition->getSpeed()) {
1189                case Transition::SPEED_FAST:
1190                    $objWriter->writeAttribute('presentation:transition-speed', 'fast');
1191
1192                    break;
1193                case Transition::SPEED_MEDIUM:
1194                    $objWriter->writeAttribute('presentation:transition-speed', 'medium');
1195
1196                    break;
1197                case Transition::SPEED_SLOW:
1198                    $objWriter->writeAttribute('presentation:transition-speed', 'slow');
1199
1200                    break;
1201            }
1202
1203            // http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#property-presentation_transition-style
1204            switch ($oTransition->getTransitionType()) {
1205                case Transition::TRANSITION_BLINDS_HORIZONTAL:
1206                    $objWriter->writeAttribute('presentation:transition-style', 'horizontal-stripes');
1207
1208                    break;
1209                case Transition::TRANSITION_BLINDS_VERTICAL:
1210                    $objWriter->writeAttribute('presentation:transition-style', 'vertical-stripes');
1211
1212                    break;
1213                case Transition::TRANSITION_CHECKER_HORIZONTAL:
1214                    $objWriter->writeAttribute('presentation:transition-style', 'horizontal-checkerboard');
1215
1216                    break;
1217                case Transition::TRANSITION_CHECKER_VERTICAL:
1218                    $objWriter->writeAttribute('presentation:transition-style', 'vertical-checkerboard');
1219
1220                    break;
1221                case Transition::TRANSITION_CIRCLE:
1222                    $objWriter->writeAttribute('presentation:transition-style', 'none');
1223
1224                    break;
1225                case Transition::TRANSITION_COMB_HORIZONTAL:
1226                    $objWriter->writeAttribute('presentation:transition-style', 'none');
1227
1228                    break;
1229                case Transition::TRANSITION_COMB_VERTICAL:
1230                    $objWriter->writeAttribute('presentation:transition-style', 'none');
1231
1232                    break;
1233                case Transition::TRANSITION_COVER_DOWN:
1234                    $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-bottom');
1235
1236                    break;
1237                case Transition::TRANSITION_COVER_LEFT:
1238                    $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-left');
1239
1240                    break;
1241                case Transition::TRANSITION_COVER_LEFT_DOWN:
1242                    $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-lowerleft');
1243
1244                    break;
1245                case Transition::TRANSITION_COVER_LEFT_UP:
1246                    $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-upperleft');
1247
1248                    break;
1249                case Transition::TRANSITION_COVER_RIGHT:
1250                    $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-right');
1251
1252                    break;
1253                case Transition::TRANSITION_COVER_RIGHT_DOWN:
1254                    $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-lowerright');
1255
1256                    break;
1257                case Transition::TRANSITION_COVER_RIGHT_UP:
1258                    $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-upperright');
1259
1260                    break;
1261                case Transition::TRANSITION_COVER_UP:
1262                    $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-top');
1263
1264                    break;
1265                case Transition::TRANSITION_CUT:
1266                    $objWriter->writeAttribute('presentation:transition-style', 'none');
1267
1268                    break;
1269                case Transition::TRANSITION_DIAMOND:
1270                    $objWriter->writeAttribute('presentation:transition-style', 'none');
1271
1272                    break;
1273                case Transition::TRANSITION_DISSOLVE:
1274                    $objWriter->writeAttribute('presentation:transition-style', 'dissolve');
1275
1276                    break;
1277                case Transition::TRANSITION_FADE:
1278                    $objWriter->writeAttribute('presentation:transition-style', 'fade-from-center');
1279
1280                    break;
1281                case Transition::TRANSITION_NEWSFLASH:
1282                    $objWriter->writeAttribute('presentation:transition-style', 'none');
1283
1284                    break;
1285                case Transition::TRANSITION_PLUS:
1286                    $objWriter->writeAttribute('presentation:transition-style', 'close');
1287
1288                    break;
1289                case Transition::TRANSITION_PULL_DOWN:
1290                    $objWriter->writeAttribute('presentation:transition-style', 'stretch-from-bottom');
1291
1292                    break;
1293                case Transition::TRANSITION_PULL_LEFT:
1294                    $objWriter->writeAttribute('presentation:transition-style', 'stretch-from-left');
1295
1296                    break;
1297                case Transition::TRANSITION_PULL_RIGHT:
1298                    $objWriter->writeAttribute('presentation:transition-style', 'stretch-from-right');
1299
1300                    break;
1301                case Transition::TRANSITION_PULL_UP:
1302                    $objWriter->writeAttribute('presentation:transition-style', 'stretch-from-top');
1303
1304                    break;
1305                case Transition::TRANSITION_PUSH_DOWN:
1306                    $objWriter->writeAttribute('presentation:transition-style', 'roll-from-bottom');
1307
1308                    break;
1309                case Transition::TRANSITION_PUSH_LEFT:
1310                    $objWriter->writeAttribute('presentation:transition-style', 'roll-from-left');
1311
1312                    break;
1313                case Transition::TRANSITION_PUSH_RIGHT:
1314                    $objWriter->writeAttribute('presentation:transition-style', 'roll-from-right');
1315
1316                    break;
1317                case Transition::TRANSITION_PUSH_UP:
1318                    $objWriter->writeAttribute('presentation:transition-style', 'roll-from-top');
1319
1320                    break;
1321                case Transition::TRANSITION_RANDOM:
1322                    $objWriter->writeAttribute('presentation:transition-style', 'random');
1323
1324                    break;
1325                case Transition::TRANSITION_RANDOMBAR_HORIZONTAL:
1326                    $objWriter->writeAttribute('presentation:transition-style', 'horizontal-lines');
1327
1328                    break;
1329                case Transition::TRANSITION_RANDOMBAR_VERTICAL:
1330                    $objWriter->writeAttribute('presentation:transition-style', 'vertical-lines');
1331
1332                    break;
1333                case Transition::TRANSITION_SPLIT_IN_HORIZONTAL:
1334                    $objWriter->writeAttribute('presentation:transition-style', 'close-horizontal');
1335
1336                    break;
1337                case Transition::TRANSITION_SPLIT_OUT_HORIZONTAL:
1338                    $objWriter->writeAttribute('presentation:transition-style', 'open-horizontal');
1339
1340                    break;
1341                case Transition::TRANSITION_SPLIT_IN_VERTICAL:
1342                    $objWriter->writeAttribute('presentation:transition-style', 'close-vertical');
1343
1344                    break;
1345                case Transition::TRANSITION_SPLIT_OUT_VERTICAL:
1346                    $objWriter->writeAttribute('presentation:transition-style', 'open-vertical');
1347
1348                    break;
1349                case Transition::TRANSITION_STRIPS_LEFT_DOWN:
1350                    $objWriter->writeAttribute('presentation:transition-style', 'none');
1351
1352                    break;
1353                case Transition::TRANSITION_STRIPS_LEFT_UP:
1354                    $objWriter->writeAttribute('presentation:transition-style', 'none');
1355
1356                    break;
1357                case Transition::TRANSITION_STRIPS_RIGHT_DOWN:
1358                    $objWriter->writeAttribute('presentation:transition-style', 'none');
1359
1360                    break;
1361                case Transition::TRANSITION_STRIPS_RIGHT_UP:
1362                    $objWriter->writeAttribute('presentation:transition-style', 'none');
1363
1364                    break;
1365                case Transition::TRANSITION_WEDGE:
1366                    $objWriter->writeAttribute('presentation:transition-style', 'none');
1367
1368                    break;
1369                case Transition::TRANSITION_WIPE_DOWN:
1370                    $objWriter->writeAttribute('presentation:transition-style', 'fade-from-bottom');
1371
1372                    break;
1373                case Transition::TRANSITION_WIPE_LEFT:
1374                    $objWriter->writeAttribute('presentation:transition-style', 'fade-from-left');
1375
1376                    break;
1377                case Transition::TRANSITION_WIPE_RIGHT:
1378                    $objWriter->writeAttribute('presentation:transition-style', 'fade-from-right');
1379
1380                    break;
1381                case Transition::TRANSITION_WIPE_UP:
1382                    $objWriter->writeAttribute('presentation:transition-style', 'fade-from-top');
1383
1384                    break;
1385                case Transition::TRANSITION_ZOOM_IN:
1386                    $objWriter->writeAttribute('presentation:transition-style', 'none');
1387
1388                    break;
1389                case Transition::TRANSITION_ZOOM_OUT:
1390                    $objWriter->writeAttribute('presentation:transition-style', 'none');
1391
1392                    break;
1393            }
1394        }
1395        $oBackground = $slide->getBackground();
1396        if ($oBackground instanceof Slide\AbstractBackground) {
1397            $objWriter->writeAttribute('presentation:background-visible', 'true');
1398            if ($oBackground instanceof Slide\Background\Color) {
1399                $objWriter->writeAttribute('draw:fill', 'solid');
1400                $objWriter->writeAttribute('draw:fill-color', '#' . $oBackground->getColor()->getRGB());
1401            }
1402            if ($oBackground instanceof Slide\Background\Image) {
1403                $objWriter->writeAttribute('draw:fill', 'bitmap');
1404                $objWriter->writeAttribute('draw:fill-image-name', 'background_' . $incPage);
1405                $objWriter->writeAttribute('style:repeat', 'stretch');
1406            }
1407        }
1408        $objWriter->endElement();
1409        // > style:style
1410        $objWriter->endElement();
1411    }
1412
1413    protected function writeStylePartFill(XMLWriter $objWriter, ?Fill $oFill): void
1414    {
1415        if (!$oFill) {
1416            return;
1417        }
1418        switch ($oFill->getFillType()) {
1419            case Fill::FILL_SOLID:
1420                $objWriter->writeAttribute('draw:fill', 'solid');
1421                $objWriter->writeAttribute('draw:fill-color', '#' . $oFill->getStartColor()->getRGB());
1422
1423                break;
1424            case Fill::FILL_NONE:
1425            default:
1426                $objWriter->writeAttribute('draw:fill', 'none');
1427
1428                break;
1429        }
1430    }
1431
1432    /**
1433     * @todo Improve for supporting any direction (https://sinepost.wordpress.com/2012/02/16/theyve-got-atan-you-want-atan2/)
1434     */
1435    protected function writeStylePartShadow(XMLWriter $objWriter, Shadow $oShadow): void
1436    {
1437        if (!$oShadow->isVisible()) {
1438            return;
1439        }
1440        $objWriter->writeAttribute('draw:shadow', 'visible');
1441        $objWriter->writeAttribute('draw:shadow-color', '#' . $oShadow->getColor()->getRGB());
1442
1443        $distanceCms = CommonDrawing::pixelsToCentimeters((int) $oShadow->getDistance());
1444        if (0 == $oShadow->getDirection() || 360 == $oShadow->getDirection()) {
1445            $objWriter->writeAttribute('draw:shadow-offset-x', $distanceCms . 'cm');
1446            $objWriter->writeAttribute('draw:shadow-offset-y', '0cm');
1447        } elseif (45 == $oShadow->getDirection()) {
1448            $objWriter->writeAttribute('draw:shadow-offset-x', $distanceCms . 'cm');
1449            $objWriter->writeAttribute('draw:shadow-offset-y', $distanceCms . 'cm');
1450        } elseif (90 == $oShadow->getDirection()) {
1451            $objWriter->writeAttribute('draw:shadow-offset-x', '0cm');
1452            $objWriter->writeAttribute('draw:shadow-offset-y', $distanceCms . 'cm');
1453        } elseif (135 == $oShadow->getDirection()) {
1454            $objWriter->writeAttribute('draw:shadow-offset-x', '-' . $distanceCms . 'cm');
1455            $objWriter->writeAttribute('draw:shadow-offset-y', $distanceCms . 'cm');
1456        } elseif (180 == $oShadow->getDirection()) {
1457            $objWriter->writeAttribute('draw:shadow-offset-x', '-' . $distanceCms . 'cm');
1458            $objWriter->writeAttribute('draw:shadow-offset-y', '0cm');
1459        } elseif (225 == $oShadow->getDirection()) {
1460            $objWriter->writeAttribute('draw:shadow-offset-x', '-' . $distanceCms . 'cm');
1461            $objWriter->writeAttribute('draw:shadow-offset-y', '-' . $distanceCms . 'cm');
1462        } elseif (270 == $oShadow->getDirection()) {
1463            $objWriter->writeAttribute('draw:shadow-offset-x', '0cm');
1464            $objWriter->writeAttribute('draw:shadow-offset-y', '-' . $distanceCms . 'cm');
1465        } elseif (315 == $oShadow->getDirection()) {
1466            $objWriter->writeAttribute('draw:shadow-offset-x', $distanceCms . 'cm');
1467            $objWriter->writeAttribute('draw:shadow-offset-y', '-' . $distanceCms . 'cm');
1468        }
1469        $objWriter->writeAttribute('draw:shadow-opacity', (100 - $oShadow->getAlpha()) . '%');
1470    }
1471}