Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
160 / 160
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
Styles
100.00% covered (success)
100.00%
160 / 160
100.00% covered (success)
100.00%
6 / 6
34
100.00% covered (success)
100.00%
1 / 1
 write
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
5
 writeDefaultStyles
100.00% covered (success)
100.00%
67 / 67
100.00% covered (success)
100.00%
1 / 1
9
 writeMissingTitleStyles
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 writeFontStyle
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
1 / 1
13
 writeParagraphStyle
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
 writeTableStyle
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * This file is part of PHPWord - A pure PHP library for reading and writing
5 * word processing documents.
6 *
7 * PHPWord is free software distributed under the terms of the GNU Lesser
8 * General Public License version 3 as published by the Free Software Foundation.
9 *
10 * For the full copyright and license information, please read the LICENSE
11 * file that was distributed with this source code. For the full list of
12 * contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
13 *
14 * @see         https://github.com/PHPOffice/PHPWord
15 *
16 * @license     http://www.gnu.org/licenses/lgpl.txt LGPL version 3
17 */
18
19namespace PhpOffice\PhpWord\Writer\Word2007\Part;
20
21use PhpOffice\PhpWord\Shared\XMLWriter;
22use PhpOffice\PhpWord\Style;
23use PhpOffice\PhpWord\Style\Font as FontStyle;
24use PhpOffice\PhpWord\Style\Paragraph as ParagraphStyle;
25use PhpOffice\PhpWord\Style\Table as TableStyle;
26use PhpOffice\PhpWord\Writer\Word2007\Style\Font as FontStyleWriter;
27use PhpOffice\PhpWord\Writer\Word2007\Style\Paragraph as ParagraphStyleWriter;
28use PhpOffice\PhpWord\Writer\Word2007\Style\Table as TableStyleWriter;
29
30/**
31 * Word2007 styles part writer: word/styles.xml.
32 *
33 * @todo Do something with the numbering style introduced in 0.10.0
34 *
35 * @SuppressWarnings("PHPMD.UnusedPrivateMethod") For writeFontStyle, writeParagraphStyle, and writeTableStyle
36 */
37class Styles extends AbstractPart
38{
39    /**
40     * Write part.
41     *
42     * @return string
43     */
44    public function write()
45    {
46        $xmlWriter = $this->getXmlWriter();
47
48        $xmlWriter->startDocument('1.0', 'UTF-8', 'yes');
49        $xmlWriter->startElement('w:styles');
50        $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
51        $xmlWriter->writeAttribute('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main');
52
53        // Write default styles
54        $styles = Style::getStyles();
55        $this->writeDefaultStyles($xmlWriter, $styles);
56
57        // Write styles
58        if (count($styles) > 0) {
59            foreach ($styles as $styleName => $style) {
60                if ($styleName == 'Normal') {
61                    continue;
62                }
63
64                // Get style class and execute if the private method exists
65                $styleClass = substr(get_class($style), strrpos(get_class($style), '\\') + 1);
66                $method = "write{$styleClass}Style";
67                if (method_exists($this, $method)) {
68                    $this->$method($xmlWriter, $styleName, $style);
69                }
70            }
71        }
72        $this->writeMissingTitleStyles($xmlWriter, $styles);
73
74        $xmlWriter->endElement(); // w:styles
75
76        return $xmlWriter->getData();
77    }
78
79    /**
80     * Write default font and other default styles.
81     *
82     * @param Style\AbstractStyle[] $styles
83     */
84    private function writeDefaultStyles(XMLWriter $xmlWriter, $styles): void
85    {
86        $phpWord = $this->getParentWriter()->getPhpWord();
87        $fontName = $phpWord->getDefaultFontName();
88        $asianFontName = $phpWord->getDefaultAsianFontName();
89        $fontSize = $phpWord->getDefaultFontSize();
90        $fontColor = $phpWord->getDefaultFontColor();
91        $language = $phpWord->getSettings()->getThemeFontLang();
92        $latinLanguage = ($language == null || $language->getLatin() === null) ? 'en-US' : $language->getLatin();
93
94        // Default font
95        $xmlWriter->startElement('w:docDefaults');
96        $xmlWriter->startElement('w:rPrDefault');
97        $xmlWriter->startElement('w:rPr');
98        $xmlWriter->startElement('w:rFonts');
99        $xmlWriter->writeAttribute('w:ascii', $fontName);
100        $xmlWriter->writeAttribute('w:hAnsi', $fontName);
101        $xmlWriter->writeAttribute('w:eastAsia', $asianFontName);
102        $xmlWriter->writeAttribute('w:cs', $fontName);
103        $xmlWriter->endElement(); // w:rFonts
104        $xmlWriter->startElement('w:color');
105        $xmlWriter->writeAttribute('w:val', $fontColor);
106        $xmlWriter->endElement();
107        $xmlWriter->startElement('w:sz');
108        $xmlWriter->writeAttribute('w:val', $fontSize * 2);
109        $xmlWriter->endElement(); // w:sz
110        $xmlWriter->startElement('w:szCs');
111        $xmlWriter->writeAttribute('w:val', $fontSize * 2);
112        $xmlWriter->endElement(); // w:szCs
113        $xmlWriter->startElement('w:lang');
114        $xmlWriter->writeAttribute('w:val', $latinLanguage);
115        if ($language != null) {
116            $xmlWriter->writeAttributeIf($language->getEastAsia() !== null, 'w:eastAsia', $language->getEastAsia());
117            $xmlWriter->writeAttributeIf($language->getBidirectional() !== null, 'w:bidi', $language->getBidirectional());
118        }
119        $xmlWriter->endElement(); // w:lang
120        $xmlWriter->endElement(); // w:rPr
121        $xmlWriter->endElement(); // w:rPrDefault
122        $xmlWriter->endElement(); // w:docDefaults
123
124        // Normal style
125        $xmlWriter->startElement('w:style');
126        $xmlWriter->writeAttribute('w:type', 'paragraph');
127        $xmlWriter->writeAttribute('w:default', '1');
128        $xmlWriter->writeAttribute('w:styleId', 'Normal');
129        $xmlWriter->startElement('w:name');
130        $xmlWriter->writeAttribute('w:val', 'Normal');
131        $xmlWriter->endElement(); // w:name
132        if (isset($styles['Normal'])) {
133            $normalStyle = $styles['Normal'];
134            // w:pPr
135            if ($normalStyle instanceof FontStyle && $normalStyle->getParagraph() != null) {
136                $styleWriter = new ParagraphStyleWriter($xmlWriter, $normalStyle->getParagraph());
137                $styleWriter->write();
138            } elseif ($normalStyle instanceof ParagraphStyle) {
139                $styleWriter = new ParagraphStyleWriter($xmlWriter, $normalStyle);
140                $styleWriter->write();
141            }
142
143            // w:rPr
144            $styleWriter = new FontStyleWriter($xmlWriter, $normalStyle);
145            $styleWriter->write();
146        }
147        $xmlWriter->endElement(); // w:style
148
149        // FootnoteReference style
150        if (!isset($styles['FootnoteReference'])) {
151            $xmlWriter->startElement('w:style');
152            $xmlWriter->writeAttribute('w:type', 'character');
153            $xmlWriter->writeAttribute('w:styleId', 'FootnoteReference');
154            $xmlWriter->startElement('w:name');
155            $xmlWriter->writeAttribute('w:val', 'Footnote Reference');
156            $xmlWriter->endElement(); // w:name
157            $xmlWriter->writeElement('w:semiHidden');
158            $xmlWriter->writeElement('w:unhideWhenUsed');
159            $xmlWriter->startElement('w:rPr');
160            $xmlWriter->startElement('w:vertAlign');
161            $xmlWriter->writeAttribute('w:val', 'superscript');
162            $xmlWriter->endElement(); // w:vertAlign
163            $xmlWriter->endElement(); // w:rPr
164            $xmlWriter->endElement(); // w:style
165        }
166    }
167
168    /**
169     * Write a style for each title depth used without addTitleStyle(),
170     * so that the w:pStyle of the title resolves and a heading keeps its outline level.
171     *
172     * @param Style\AbstractStyle[] $styles
173     */
174    private function writeMissingTitleStyles(XMLWriter $xmlWriter, array $styles): void
175    {
176        $depths = [];
177        foreach ($this->getParentWriter()->getPhpWord()->getTitles()->getItems() as $title) {
178            $depths[(int) $title->getDepth()] = true;
179        }
180        ksort($depths);
181        foreach (array_keys($depths) as $depth) {
182            $styleName = $depth === 0 ? 'Title' : "Heading_{$depth}";
183            if (!isset($styles[$styleName])) {
184                $this->writeFontStyle($xmlWriter, $styleName, new FontStyle('title'));
185            }
186        }
187    }
188
189    /**
190     * Write font style.
191     *
192     * @param string $styleName
193     */
194    private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $style): void
195    {
196        $paragraphStyle = $style->getParagraph();
197        $styleType = $style->getStyleType();
198        $type = ($styleType == 'title') ? 'paragraph' : 'character';
199        if (null !== $paragraphStyle) {
200            $type = 'paragraph';
201        }
202
203        $xmlWriter->startElement('w:style');
204        $xmlWriter->writeAttribute('w:type', $type);
205
206        // Heading style
207        $outlineLevel = null;
208        if ($styleType == 'title') {
209            $arrStyle = explode('_', $styleName);
210            if (count($arrStyle) > 1) {
211                $styleId = 'Heading' . $arrStyle[1];
212                $styleName = 'heading ' . $arrStyle[1];
213                $styleLink = 'Heading' . $arrStyle[1] . 'Char';
214                // Heading 1 to 9 are outline levels 0 to 8; 9 would be body text
215                $depth = (int) $arrStyle[1];
216                if ($depth >= 1 && $depth <= 9) {
217                    $outlineLevel = $depth - 1;
218                }
219            } else {
220                $styleId = $styleName;
221                $styleLink = $styleName . 'Char';
222            }
223            $xmlWriter->writeAttribute('w:styleId', $styleId);
224
225            $xmlWriter->startElement('w:link');
226            $xmlWriter->writeAttribute('w:val', $styleLink);
227            $xmlWriter->endElement();
228        } elseif (null !== $paragraphStyle) {
229            // if type is 'paragraph' it should have a styleId
230            $xmlWriter->writeAttribute('w:styleId', $styleName);
231        }
232
233        // Style name
234        $xmlWriter->startElement('w:name');
235        $xmlWriter->writeAttribute('w:val', $styleName);
236        $xmlWriter->endElement();
237
238        // Parent style
239        if (null !== $paragraphStyle) {
240            if ($paragraphStyle->getStyleName() != null) {
241                $xmlWriter->writeElementBlock('w:basedOn', 'w:val', $paragraphStyle->getStyleName());
242            } elseif ($paragraphStyle->getBasedOn() != null) {
243                $xmlWriter->writeElementBlock('w:basedOn', 'w:val', $paragraphStyle->getBasedOn());
244            }
245        }
246
247        // w:pPr
248        if (null !== $paragraphStyle || null !== $outlineLevel) {
249            $styleWriter = new ParagraphStyleWriter($xmlWriter, $paragraphStyle ?? new ParagraphStyle());
250            $styleWriter->setOutlineLevel($outlineLevel);
251            $styleWriter->write();
252        }
253
254        // w:rPr
255        $styleWriter = new FontStyleWriter($xmlWriter, $style);
256        $styleWriter->write();
257
258        $xmlWriter->endElement();
259    }
260
261    /**
262     * Write paragraph style.
263     *
264     * @param string $styleName
265     */
266    private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, ParagraphStyle $style): void
267    {
268        $xmlWriter->startElement('w:style');
269        $xmlWriter->writeAttribute('w:type', 'paragraph');
270        $xmlWriter->writeAttribute('w:customStyle', '1');
271        $xmlWriter->writeAttribute('w:styleId', $styleName);
272        $xmlWriter->startElement('w:name');
273        $xmlWriter->writeAttribute('w:val', $styleName);
274        $xmlWriter->endElement();
275
276        // Parent style
277        $basedOn = $style->getBasedOn();
278        $xmlWriter->writeElementIf(null !== $basedOn, 'w:basedOn', 'w:val', $basedOn);
279
280        // Next paragraph style
281        $next = $style->getNext();
282        $xmlWriter->writeElementIf(null !== $next, 'w:next', 'w:val', $next);
283
284        // w:pPr
285        $styleWriter = new ParagraphStyleWriter($xmlWriter, $style);
286        $styleWriter->write();
287
288        $xmlWriter->endElement();
289    }
290
291    /**
292     * Write table style.
293     *
294     * @param string $styleName
295     */
296    private function writeTableStyle(XMLWriter $xmlWriter, $styleName, TableStyle $style): void
297    {
298        $xmlWriter->startElement('w:style');
299        $xmlWriter->writeAttribute('w:type', 'table');
300        $xmlWriter->writeAttribute('w:customStyle', '1');
301        $xmlWriter->writeAttribute('w:styleId', $styleName);
302        $xmlWriter->startElement('w:name');
303        $xmlWriter->writeAttribute('w:val', $styleName);
304        $xmlWriter->endElement();
305        $xmlWriter->startElement('w:uiPriority');
306        $xmlWriter->writeAttribute('w:val', '99');
307        $xmlWriter->endElement();
308
309        $styleWriter = new TableStyleWriter($xmlWriter, $style);
310        $styleWriter->write();
311
312        $xmlWriter->endElement(); // w:style
313    }
314}