Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
158 / 158
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
Styles
100.00% covered (success)
100.00%
158 / 158
100.00% covered (success)
100.00%
7 / 7
31
100.00% covered (success)
100.00%
1 / 1
 write
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 writeDefault
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
8
 writeNamed
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 cvttwiptostr
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 writePageLayout
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 writePageLayoutIndiv
100.00% covered (success)
100.00%
62 / 62
100.00% covered (success)
100.00%
1 / 1
5
 writeMaster
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
8
1<?php
2/**
3 * This file is part of PHPWord - A pure PHP library for reading and writing
4 * word processing documents.
5 *
6 * PHPWord 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/PHPWord/contributors.
12 *
13 * @see         https://github.com/PHPOffice/PHPWord
14 *
15 * @license     http://www.gnu.org/licenses/lgpl.txt LGPL version 3
16 */
17
18namespace PhpOffice\PhpWord\Writer\ODText\Part;
19
20use PhpOffice\PhpWord\Settings;
21use PhpOffice\PhpWord\Shared\Converter;
22use PhpOffice\PhpWord\Shared\XMLWriter;
23use PhpOffice\PhpWord\Style;
24
25/**
26 * ODText styles part writer: styles.xml.
27 */
28class Styles extends AbstractPart
29{
30    /**
31     * Write part.
32     *
33     * @return string
34     */
35    public function write()
36    {
37        $xmlWriter = $this->getXmlWriter();
38
39        // XML header
40        $xmlWriter->startDocument('1.0', 'UTF-8');
41        $xmlWriter->startElement('office:document-styles');
42        $this->writeCommonRootAttributes($xmlWriter);
43
44        // Font declarations
45        $this->writeFontFaces($xmlWriter);
46
47        // Office styles
48        $xmlWriter->startElement('office:styles');
49        $this->writeDefault($xmlWriter);
50        $this->writeNamed($xmlWriter);
51        $xmlWriter->endElement();
52
53        // Automatic styles
54        $xmlWriter->startElement('office:automatic-styles');
55        $this->writePageLayout($xmlWriter);
56        $xmlWriter->endElement(); // office:automatic-styles
57
58        // Master style
59        $this->writeMaster($xmlWriter);
60
61        $xmlWriter->endElement(); // office:document-styles
62
63        return $xmlWriter->getData();
64    }
65
66    /**
67     * Write default styles.
68     */
69    private function writeDefault(XMLWriter $xmlWriter): void
70    {
71        $xmlWriter->startElement('style:default-style');
72        $xmlWriter->writeAttribute('style:family', 'paragraph');
73
74        // Paragraph
75        $xmlWriter->startElement('style:paragraph-properties');
76        $xmlWriter->writeAttribute('fo:hyphenation-ladder-count', 'no-limit');
77        $xmlWriter->writeAttribute('style:text-autospace', 'ideograph-alpha');
78        $xmlWriter->writeAttribute('style:punctuation-wrap', 'hanging');
79        $xmlWriter->writeAttribute('style:line-break', 'strict');
80        $xmlWriter->writeAttribute('style:tab-stop-distance', '1.249cm');
81        $xmlWriter->writeAttribute('style:writing-mode', 'page');
82        $xmlWriter->endElement(); // style:paragraph-properties
83
84        $language = $this->getParentWriter()->getPhpWord()->getSettings()->getThemeFontLang();
85        $latinLang = $language != null && is_string($language->getLatin()) ? explode('-', $language->getLatin()) : ['fr', 'FR'];
86        $asianLang = $language != null && is_string($language->getEastAsia()) ? explode('-', $language->getEastAsia()) : ['zh', 'CN'];
87        $complexLang = $language != null && is_string($language->getBidirectional()) ? explode('-', $language->getBidirectional()) : ['hi', 'IN'];
88        if ($this->getParentWriter()->getPhpWord()->getSettings()->hasHideGrammaticalErrors()) {
89            $latinLang = $asianLang = $complexLang = ['zxx', 'none'];
90        }
91
92        // Font
93        $xmlWriter->startElement('style:text-properties');
94        $xmlWriter->writeAttribute('style:use-window-font-color', 'true');
95        $xmlWriter->writeAttribute('style:font-name', Settings::getDefaultFontName());
96        $xmlWriter->writeAttribute('fo:font-size', Settings::getDefaultFontSize() . 'pt');
97        $xmlWriter->writeAttribute('fo:language', $latinLang[0]);
98        $xmlWriter->writeAttribute('fo:country', $latinLang[1]);
99        $xmlWriter->writeAttribute('style:letter-kerning', 'true');
100        $xmlWriter->writeAttribute('style:font-name-asian', Settings::getDefaultFontName() . '2');
101        $xmlWriter->writeAttribute('style:font-size-asian', Settings::getDefaultFontSize() . 'pt');
102        $xmlWriter->writeAttribute('style:language-asian', $asianLang[0]);
103        $xmlWriter->writeAttribute('style:country-asian', $asianLang[1]);
104        $xmlWriter->writeAttribute('style:font-name-complex', Settings::getDefaultFontName() . '2');
105        $xmlWriter->writeAttribute('style:font-size-complex', Settings::getDefaultFontSize() . 'pt');
106        $xmlWriter->writeAttribute('style:language-complex', $complexLang[0]);
107        $xmlWriter->writeAttribute('style:country-complex', $complexLang[1]);
108        $xmlWriter->writeAttribute('fo:hyphenate', 'false');
109        $xmlWriter->writeAttribute('fo:hyphenation-remain-char-count', '2');
110        $xmlWriter->writeAttribute('fo:hyphenation-push-char-count', '2');
111        $xmlWriter->endElement(); // style:text-properties
112
113        $xmlWriter->endElement(); // style:default-style
114    }
115
116    /**
117     * Write named styles.
118     */
119    private function writeNamed(XMLWriter $xmlWriter): void
120    {
121        $styles = Style::getStyles();
122        if (count($styles) > 0) {
123            foreach ($styles as $style) {
124                if ($style->isAuto() === false) {
125                    $styleClass = str_replace('\\Style\\', '\\Writer\\ODText\\Style\\', get_class($style));
126                    if (class_exists($styleClass)) {
127                        /** @var \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle $styleWriter Type hint */
128                        $styleWriter = new $styleClass($xmlWriter, $style);
129                        $styleWriter->write();
130                    }
131                }
132            }
133        }
134    }
135
136    /**
137     * Convert int in twips to inches/cm then to string and append unit.
138     *
139     * @param float|int $twips
140     * @param float $factor
141     * return string
142     */
143    private static function cvttwiptostr($twips, $factor = 1.0)
144    {
145        $ins = (string) ($twips * $factor / Converter::INCH_TO_TWIP) . 'in';
146        $cms = (string) ($twips * $factor * Converter::INCH_TO_CM / Converter::INCH_TO_TWIP) . 'cm';
147
148        return (strlen($ins) < strlen($cms)) ? $ins : $cms;
149    }
150
151    /**
152     * call writePageLayoutIndiv to write page layout styles for each page.
153     */
154    private function writePageLayout(XMLWriter $xmlWriter): void
155    {
156        $sections = $this->getParentWriter()->getPhpWord()->getSections();
157        $countsects = count($sections);
158        for ($i = 0; $i < $countsects; ++$i) {
159            $this->writePageLayoutIndiv($xmlWriter, $sections[$i], $i + 1);
160        }
161    }
162
163    /**
164     * Write page layout styles.
165     *
166     * @param \PhpOffice\PhpWord\Element\Section $section
167     * @param int $sectionNbr
168     */
169    private function writePageLayoutIndiv(XMLWriter $xmlWriter, $section, $sectionNbr): void
170    {
171        $sty = $section->getStyle();
172        if (count($section->getHeaders()) > 0) {
173            $topfactor = 0.5;
174        } else {
175            $topfactor = 1.0;
176        }
177        if (count($section->getFooters()) > 0) {
178            $botfactor = 0.5;
179        } else {
180            $botfactor = 1.0;
181        }
182        $orient = $sty->getOrientation();
183        $pwidth = self::cvttwiptostr($sty->getPageSizeW());
184        $pheight = self::cvttwiptostr($sty->getPageSizeH());
185        $mtop = self::cvttwiptostr($sty->getMarginTop(), $topfactor);
186        $mbottom = self::cvttwiptostr($sty->getMarginBottom(), $botfactor);
187        $mleft = self::cvttwiptostr($sty->getMarginRight());
188        $mright = self::cvttwiptostr($sty->getMarginLeft());
189
190        $xmlWriter->startElement('style:page-layout');
191        $xmlWriter->writeAttribute('style:name', "Mpm$sectionNbr");
192
193        $xmlWriter->startElement('style:page-layout-properties');
194        $xmlWriter->writeAttribute('fo:page-width', $pwidth);
195        $xmlWriter->writeAttribute('fo:page-height', $pheight);
196        $xmlWriter->writeAttribute('style:num-format', '1');
197        $xmlWriter->writeAttribute('style:print-orientation', $orient);
198        $xmlWriter->writeAttribute('fo:margin-top', $mtop);
199        $xmlWriter->writeAttribute('fo:margin-bottom', $mbottom);
200        $xmlWriter->writeAttribute('fo:margin-left', $mleft);
201        $xmlWriter->writeAttribute('fo:margin-right', $mright);
202        $xmlWriter->writeAttribute('style:writing-mode', 'lr-tb');
203        $xmlWriter->writeAttribute('style:layout-grid-color', '#c0c0c0');
204        $xmlWriter->writeAttribute('style:layout-grid-lines', '25199');
205        $xmlWriter->writeAttribute('style:layout-grid-base-height', '0.423cm');
206        $xmlWriter->writeAttribute('style:layout-grid-ruby-height', '0cm');
207        $xmlWriter->writeAttribute('style:layout-grid-mode', 'none');
208        $xmlWriter->writeAttribute('style:layout-grid-ruby-below', 'false');
209        $xmlWriter->writeAttribute('style:layout-grid-print', 'false');
210        $xmlWriter->writeAttribute('style:layout-grid-display', 'false');
211        $xmlWriter->writeAttribute('style:layout-grid-base-width', '0.37cm');
212        $xmlWriter->writeAttribute('style:layout-grid-snap-to', 'true');
213        $xmlWriter->writeAttribute('style:footnote-max-height', '0cm');
214
215        $xmlWriter->startElement('style:footnote-sep');
216        $xmlWriter->writeAttribute('style:width', '0.018cm');
217        $xmlWriter->writeAttribute('style:line-style', 'solid');
218        $xmlWriter->writeAttribute('style:adjustment', 'left');
219        $xmlWriter->writeAttribute('style:rel-width', '25%');
220        $xmlWriter->writeAttribute('style:color', '#000000');
221        $xmlWriter->endElement(); //style:footnote-sep
222
223        $xmlWriter->endElement(); // style:page-layout-properties
224
225        $xmlWriter->startElement('style:header-style');
226        if ($topfactor < 1.0) {
227            $xmlWriter->startElement('style:header-footer-properties');
228            $xmlWriter->writeAttribute('fo:min-height', $mtop);
229            $xmlWriter->writeAttribute('fo:margin-bottom', $mtop);
230            $xmlWriter->writeAttribute('style:dynamic-spacing', 'true');
231            $xmlWriter->endElement(); // style:header-footer-properties
232        }
233        $xmlWriter->endElement(); // style:header-style
234
235        $xmlWriter->startElement('style:footer-style');
236        if ($botfactor < 1.0) {
237            $xmlWriter->startElement('style:header-footer-properties');
238            $xmlWriter->writeAttribute('fo:min-height', $mbottom);
239            $xmlWriter->writeAttribute('fo:margin-top', $mbottom);
240            $xmlWriter->writeAttribute('style:dynamic-spacing', 'true');
241            $xmlWriter->endElement(); // style:header-footer-properties
242        }
243        $xmlWriter->endElement(); // style:footer-style
244
245        $xmlWriter->endElement(); // style:page-layout
246    }
247
248    /**
249     * Write master style.
250     */
251    private function writeMaster(XMLWriter $xmlWriter): void
252    {
253        $xmlWriter->startElement('office:master-styles');
254
255        $sections = $this->getParentWriter()->getPhpWord()->getSections();
256        $countsects = count($sections);
257        for ($i = 0; $i < $countsects; ++$i) {
258            $iplus1 = $i + 1;
259            $xmlWriter->startElement('style:master-page');
260            $xmlWriter->writeAttribute('style:name', "Standard$iplus1");
261            $xmlWriter->writeAttribute('style:page-layout-name', "Mpm$iplus1");
262            // Multiple headers and footers probably not supported,
263            //   and, even if they are, I'm not sure how,
264            //   so quit after generating one.
265            foreach ($sections[$i]->getHeaders() as $hdr) {
266                $xmlWriter->startElement('style:header');
267                foreach ($hdr->getElements() as $elem) {
268                    $cl1 = get_class($elem);
269                    $cl2 = str_replace('\\Element\\', '\\Writer\\ODText\\Element\\', $cl1);
270                    if (class_exists($cl2)) {
271                        $wtr = new $cl2($xmlWriter, $elem);
272                        $wtr->write();
273                    }
274                }
275                $xmlWriter->endElement(); // style:header
276
277                break;
278            }
279            foreach ($sections[$i]->getFooters() as $hdr) {
280                $xmlWriter->startElement('style:footer');
281                foreach ($hdr->getElements() as $elem) {
282                    $cl1 = get_class($elem);
283                    $cl2 = str_replace('\\Element\\', '\\Writer\\ODText\\Element\\', $cl1);
284                    if (class_exists($cl2)) {
285                        $wtr = new $cl2($xmlWriter, $elem);
286                        $wtr->write();
287                    }
288                }
289                $xmlWriter->endElement(); // style:footer
290
291                break;
292            }
293            $xmlWriter->endElement(); // style:master-page
294        }
295        $xmlWriter->endElement(); // office:master-styles
296    }
297}