Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace OpenSpout\Writer\ODS\Manager\Style;
6
 
7
use OpenSpout\Common\Entity\Style\Border;
8
use OpenSpout\Common\Entity\Style\BorderPart;
9
use OpenSpout\Common\Entity\Style\CellAlignment;
10
use OpenSpout\Common\Entity\Style\CellVerticalAlignment;
11
use OpenSpout\Common\Entity\Style\Style;
12
use OpenSpout\Writer\Common\AbstractOptions;
13
use OpenSpout\Writer\Common\ColumnWidth;
14
use OpenSpout\Writer\Common\Entity\Worksheet;
15
use OpenSpout\Writer\Common\Manager\Style\AbstractStyleManager as CommonStyleManager;
16
use OpenSpout\Writer\ODS\Helper\BorderHelper;
17
 
18
/**
19
 * @internal
20
 *
21
 * @property StyleRegistry $styleRegistry
22
 */
23
final class StyleManager extends CommonStyleManager
24
{
25
    private readonly AbstractOptions $options;
26
 
27
    public function __construct(StyleRegistry $styleRegistry, AbstractOptions $options)
28
    {
29
        parent::__construct($styleRegistry);
30
        $this->options = $options;
31
    }
32
 
33
    /**
34
     * Returns the content of the "styles.xml" file, given a list of styles.
35
     *
36
     * @param int $numWorksheets Number of worksheets created
37
     */
38
    public function getStylesXMLFileContent(int $numWorksheets): string
39
    {
40
        $content = <<<'EOD'
41
            <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
42
            <office:document-styles office:version="1.2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:msoxl="http://schemas.microsoft.com/office/excel/formula" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
43
            EOD;
44
 
45
        $content .= $this->getFontFaceSectionContent();
46
        $content .= $this->getStylesSectionContent();
47
        $content .= $this->getAutomaticStylesSectionContent($numWorksheets);
48
        $content .= $this->getMasterStylesSectionContent($numWorksheets);
49
 
50
        $content .= <<<'EOD'
51
            </office:document-styles>
52
            EOD;
53
 
54
        return $content;
55
    }
56
 
57
    /**
58
     * Returns the contents of the "<office:font-face-decls>" section, inside "content.xml" file.
59
     */
60
    public function getContentXmlFontFaceSectionContent(): string
61
    {
62
        $content = '<office:font-face-decls>';
63
        foreach ($this->styleRegistry->getUsedFonts() as $fontName) {
64
            $content .= '<style:font-face style:name="'.$fontName.'" svg:font-family="'.$fontName.'"/>';
65
        }
66
        $content .= '</office:font-face-decls>';
67
 
68
        return $content;
69
    }
70
 
71
    /**
72
     * Returns the contents of the "<office:automatic-styles>" section, inside "content.xml" file.
73
     *
74
     * @param Worksheet[] $worksheets
75
     */
76
    public function getContentXmlAutomaticStylesSectionContent(array $worksheets): string
77
    {
78
        $content = '<office:automatic-styles>';
79
 
80
        foreach ($this->styleRegistry->getRegisteredStyles() as $style) {
81
            $content .= $this->getStyleSectionContent($style);
82
        }
83
 
84
        $useOptimalRowHeight = null === $this->options->DEFAULT_ROW_HEIGHT ? 'true' : 'false';
85
        $defaultRowHeight = null === $this->options->DEFAULT_ROW_HEIGHT ? '15pt' : "{$this->options->DEFAULT_ROW_HEIGHT}pt";
86
        $defaultColumnWidth = null === $this->options->DEFAULT_COLUMN_WIDTH ? '' : "style:column-width=\"{$this->options->DEFAULT_COLUMN_WIDTH}pt\"";
87
 
88
        $content .= <<<EOD
89
            <style:style style:family="table-column" style:name="default-column-style">
90
                <style:table-column-properties fo:break-before="auto" {$defaultColumnWidth}/>
91
            </style:style>
92
            <style:style style:family="table-row" style:name="ro1">
93
                <style:table-row-properties fo:break-before="auto" style:row-height="{$defaultRowHeight}" style:use-optimal-row-height="{$useOptimalRowHeight}"/>
94
            </style:style>
95
            EOD;
96
 
97
        foreach ($worksheets as $worksheet) {
98
            $worksheetId = $worksheet->getId();
99
            $isSheetVisible = $worksheet->getExternalSheet()->isVisible() ? 'true' : 'false';
100
 
101
            $content .= <<<EOD
102
                <style:style style:family="table" style:master-page-name="mp{$worksheetId}" style:name="ta{$worksheetId}">
103
                    <style:table-properties style:writing-mode="lr-tb" table:display="{$isSheetVisible}"/>
104
                </style:style>
105
                EOD;
106
        }
107
 
108
        // Sort column widths since ODS cares about order
109
        $columnWidths = $this->options->getColumnWidths();
110
        usort($columnWidths, static function (ColumnWidth $a, ColumnWidth $b): int {
111
            return $a->start <=> $b->start;
112
        });
113
        $content .= $this->getTableColumnStylesXMLContent();
114
 
115
        $content .= '</office:automatic-styles>';
116
 
117
        return $content;
118
    }
119
 
120
    public function getTableColumnStylesXMLContent(): string
121
    {
122
        if ([] === $this->options->getColumnWidths()) {
123
            return '';
124
        }
125
 
126
        $content = '';
127
        foreach ($this->options->getColumnWidths() as $styleIndex => $columnWidth) {
128
            $content .= <<<EOD
129
                <style:style style:family="table-column" style:name="co{$styleIndex}">
130
                    <style:table-column-properties fo:break-before="auto" style:use-optimal-column-width="false" style:column-width="{$columnWidth->width}pt"/>
131
                </style:style>
132
                EOD;
133
        }
134
 
135
        return $content;
136
    }
137
 
138
    public function getStyledTableColumnXMLContent(int $maxNumColumns): string
139
    {
140
        if ([] === $this->options->getColumnWidths()) {
141
            return '';
142
        }
143
 
144
        $content = '';
145
        foreach ($this->options->getColumnWidths() as $styleIndex => $columnWidth) {
146
            $numCols = $columnWidth->end - $columnWidth->start + 1;
147
            $content .= <<<EOD
148
                <table:table-column table:default-cell-style-name='Default' table:style-name="co{$styleIndex}" table:number-columns-repeated="{$numCols}"/>
149
                EOD;
150
        }
151
        \assert(isset($columnWidth));
152
        // Note: This assumes the column widths are contiguous and default width is
153
        // only applied to columns after the last custom column with a custom width
154
        $content .= '<table:table-column table:default-cell-style-name="ce1" table:style-name="default-column-style" table:number-columns-repeated="'.($maxNumColumns - $columnWidth->end).'"/>';
155
 
156
        return $content;
157
    }
158
 
159
    /**
160
     * Returns the content of the "<office:styles>" section, inside "styles.xml" file.
161
     */
162
    private function getStylesSectionContent(): string
163
    {
164
        $defaultStyle = $this->getDefaultStyle();
165
 
166
        return <<<EOD
167
            <office:styles>
168
                <number:number-style style:name="N0">
169
                    <number:number number:min-integer-digits="1"/>
170
                </number:number-style>
171
                <style:style style:data-style-name="N0" style:family="table-cell" style:name="Default">
172
                    <style:table-cell-properties fo:background-color="transparent" style:vertical-align="automatic"/>
173
                    <style:text-properties fo:color="#{$defaultStyle->getFontColor()}"
174
                                           fo:font-size="{$defaultStyle->getFontSize()}pt" style:font-size-asian="{$defaultStyle->getFontSize()}pt" style:font-size-complex="{$defaultStyle->getFontSize()}pt"
175
                                           style:font-name="{$defaultStyle->getFontName()}" style:font-name-asian="{$defaultStyle->getFontName()}" style:font-name-complex="{$defaultStyle->getFontName()}"/>
176
                </style:style>
177
            </office:styles>
178
            EOD;
179
    }
180
 
181
    /**
182
     * Returns the content of the "<office:master-styles>" section, inside "styles.xml" file.
183
     *
184
     * @param int $numWorksheets Number of worksheets created
185
     */
186
    private function getMasterStylesSectionContent(int $numWorksheets): string
187
    {
188
        $content = '<office:master-styles>';
189
 
190
        for ($i = 1; $i <= $numWorksheets; ++$i) {
191
            $content .= <<<EOD
192
                <style:master-page style:name="mp{$i}" style:page-layout-name="pm{$i}">
193
                    <style:header/>
194
                    <style:header-left style:display="false"/>
195
                    <style:footer/>
196
                    <style:footer-left style:display="false"/>
197
                </style:master-page>
198
                EOD;
199
        }
200
 
201
        $content .= '</office:master-styles>';
202
 
203
        return $content;
204
    }
205
 
206
    /**
207
     * Returns the content of the "<office:font-face-decls>" section, inside "styles.xml" file.
208
     */
209
    private function getFontFaceSectionContent(): string
210
    {
211
        $content = '<office:font-face-decls>';
212
        foreach ($this->styleRegistry->getUsedFonts() as $fontName) {
213
            $content .= '<style:font-face style:name="'.$fontName.'" svg:font-family="'.$fontName.'"/>';
214
        }
215
        $content .= '</office:font-face-decls>';
216
 
217
        return $content;
218
    }
219
 
220
    /**
221
     * Returns the content of the "<office:automatic-styles>" section, inside "styles.xml" file.
222
     *
223
     * @param int $numWorksheets Number of worksheets created
224
     */
225
    private function getAutomaticStylesSectionContent(int $numWorksheets): string
226
    {
227
        $content = '<office:automatic-styles>';
228
 
229
        for ($i = 1; $i <= $numWorksheets; ++$i) {
230
            $content .= <<<EOD
231
                <style:page-layout style:name="pm{$i}">
232
                    <style:page-layout-properties style:first-page-number="continue" style:print="objects charts drawings" style:table-centering="none"/>
233
                    <style:header-style/>
234
                    <style:footer-style/>
235
                </style:page-layout>
236
                EOD;
237
        }
238
 
239
        $content .= '</office:automatic-styles>';
240
 
241
        return $content;
242
    }
243
 
244
    /**
245
     * Returns the contents of the "<style:style>" section, inside "<office:automatic-styles>" section.
246
     */
247
    private function getStyleSectionContent(Style $style): string
248
    {
249
        $styleIndex = $style->getId() + 1; // 1-based
250
 
251
        $content = '<style:style style:data-style-name="N0" style:family="table-cell" style:name="ce'.$styleIndex.'" style:parent-style-name="Default">';
252
 
253
        $content .= $this->getTextPropertiesSectionContent($style);
254
        $content .= $this->getParagraphPropertiesSectionContent($style);
255
        $content .= $this->getTableCellPropertiesSectionContent($style);
256
 
257
        $content .= '</style:style>';
258
 
259
        return $content;
260
    }
261
 
262
    /**
263
     * Returns the contents of the "<style:text-properties>" section, inside "<style:style>" section.
264
     */
265
    private function getTextPropertiesSectionContent(Style $style): string
266
    {
267
        if (!$style->shouldApplyFont()) {
268
            return '';
269
        }
270
 
271
        return '<style:text-properties '
272
            .$this->getFontSectionContent($style)
273
            .'/>';
274
    }
275
 
276
    /**
277
     * Returns the contents of the fonts definition section, inside "<style:text-properties>" section.
278
     */
279
    private function getFontSectionContent(Style $style): string
280
    {
281
        $defaultStyle = $this->getDefaultStyle();
282
        $content = '';
283
 
284
        $fontColor = $style->getFontColor();
285
        if ($fontColor !== $defaultStyle->getFontColor()) {
286
            $content .= ' fo:color="#'.$fontColor.'"';
287
        }
288
 
289
        $fontName = $style->getFontName();
290
        if ($fontName !== $defaultStyle->getFontName()) {
291
            $content .= ' style:font-name="'.$fontName.'" style:font-name-asian="'.$fontName.'" style:font-name-complex="'.$fontName.'"';
292
        }
293
 
294
        $fontSize = $style->getFontSize();
295
        if ($fontSize !== $defaultStyle->getFontSize()) {
296
            $content .= ' fo:font-size="'.$fontSize.'pt" style:font-size-asian="'.$fontSize.'pt" style:font-size-complex="'.$fontSize.'pt"';
297
        }
298
 
299
        if ($style->isFontBold()) {
300
            $content .= ' fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"';
301
        }
302
        if ($style->isFontItalic()) {
303
            $content .= ' fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic"';
304
        }
305
        if ($style->isFontUnderline()) {
306
            $content .= ' style:text-underline-style="solid" style:text-underline-type="single"';
307
        }
308
        if ($style->isFontStrikethrough()) {
309
            $content .= ' style:text-line-through-style="solid"';
310
        }
311
 
312
        return $content;
313
    }
314
 
315
    /**
316
     * Returns the contents of the "<style:paragraph-properties>" section, inside "<style:style>" section.
317
     */
318
    private function getParagraphPropertiesSectionContent(Style $style): string
319
    {
320
        if (!$style->shouldApplyCellAlignment() && !$style->shouldApplyCellVerticalAlignment()) {
321
            return '';
322
        }
323
 
324
        return '<style:paragraph-properties '
325
            .$this->getCellAlignmentSectionContent($style)
326
            .$this->getCellVerticalAlignmentSectionContent($style)
327
            .'/>';
328
    }
329
 
330
    /**
331
     * Returns the contents of the cell alignment definition for the "<style:paragraph-properties>" section.
332
     */
333
    private function getCellAlignmentSectionContent(Style $style): string
334
    {
335
        if (!$style->hasSetCellAlignment()) {
336
            return '';
337
        }
338
 
339
        return sprintf(
340
            ' fo:text-align="%s" ',
341
            $this->transformCellAlignment($style->getCellAlignment())
342
        );
343
    }
344
 
345
    /**
346
     * Returns the contents of the cell vertical alignment definition for the "<style:paragraph-properties>" section.
347
     */
348
    private function getCellVerticalAlignmentSectionContent(Style $style): string
349
    {
350
        if (!$style->hasSetCellVerticalAlignment()) {
351
            return '';
352
        }
353
 
354
        return sprintf(
355
            ' fo:vertical-align="%s" ',
356
            $this->transformCellVerticalAlignment($style->getCellVerticalAlignment())
357
        );
358
    }
359
 
360
    /**
361
     * Even though "left" and "right" alignments are part of the spec, and interpreted
362
     * respectively as "start" and "end", using the recommended values increase compatibility
363
     * with software that will read the created ODS file.
364
     */
365
    private function transformCellAlignment(string $cellAlignment): string
366
    {
367
        return match ($cellAlignment) {
368
            CellAlignment::LEFT => 'start',
369
            CellAlignment::RIGHT => 'end',
370
            default => $cellAlignment,
371
        };
372
    }
373
 
374
    /**
375
     * Spec uses 'middle' rather than 'center'
376
     * http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1420236_253892949.
377
     */
378
    private function transformCellVerticalAlignment(string $cellVerticalAlignment): string
379
    {
380
        return (CellVerticalAlignment::CENTER === $cellVerticalAlignment)
381
            ? 'middle'
382
            : $cellVerticalAlignment;
383
    }
384
 
385
    /**
386
     * Returns the contents of the "<style:table-cell-properties>" section, inside "<style:style>" section.
387
     */
388
    private function getTableCellPropertiesSectionContent(Style $style): string
389
    {
390
        $content = '<style:table-cell-properties ';
391
 
392
        if ($style->hasSetWrapText()) {
393
            $content .= $this->getWrapTextXMLContent($style->shouldWrapText());
394
        }
395
 
396
        if (null !== ($border = $style->getBorder())) {
397
            $content .= $this->getBorderXMLContent($border);
398
        }
399
 
400
        if (null !== ($bgColor = $style->getBackgroundColor())) {
401
            $content .= $this->getBackgroundColorXMLContent($bgColor);
402
        }
403
 
404
        $content .= '/>';
405
 
406
        return $content;
407
    }
408
 
409
    /**
410
     * Returns the contents of the wrap text definition for the "<style:table-cell-properties>" section.
411
     */
412
    private function getWrapTextXMLContent(bool $shouldWrapText): string
413
    {
414
        return ' fo:wrap-option="'.($shouldWrapText ? '' : 'no-').'wrap" style:vertical-align="automatic" ';
415
    }
416
 
417
    /**
418
     * Returns the contents of the borders definition for the "<style:table-cell-properties>" section.
419
     */
420
    private function getBorderXMLContent(Border $border): string
421
    {
422
        $borders = array_map(static function (BorderPart $borderPart) {
423
            return BorderHelper::serializeBorderPart($borderPart);
424
        }, $border->getParts());
425
 
426
        return sprintf(' %s ', implode(' ', $borders));
427
    }
428
 
429
    /**
430
     * Returns the contents of the background color definition for the "<style:table-cell-properties>" section.
431
     */
432
    private function getBackgroundColorXMLContent(string $bgColor): string
433
    {
434
        return sprintf(' fo:background-color="#%s" ', $bgColor);
435
    }
436
}