From 0e6866d6f0ad1577bf58ef61dfb4a908bc07898c Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 26 Mar 2023 03:19:34 -0700 Subject: [PATCH] Font/Effects/Theme Support for Chart Data Labels and Axis (#3476) * Font and Effects Support for Chart Data Labels and Axis Addresses some remaining issues with 32readwriteLineChart5 (see issue #1797). Font size is covered. So are effects, although the results are a bit odd. For the new spreadsheet 32readwriteLineChart6, the Axis labels have a yellow-ish glow, but reading and writing the spreadsheet in PhpSpreadsheet gives them a purple-ish glow. Nevertheless, the new test shows that the output file uses schemeClr accent4, as does the input file. So the effect is handled correctly, but it seems there is likely to be a difference between theme colors (Writer/Xlsx/Theme appears to write hard-coded color schemes, and, in any case, Reader/Xlsx does not appear to handle schemeClr). Fixing that will be a great deal more difficult, with a large chance of regression, and will need to happen in a separate PR (one that I am not currently investigating, but I will open a new issue). Effects using srgbClr (and probably sysclr) should be okay. * Better Theme Support When reading Xlsx, the theme colors will now also be used for writing. This means that a file can be loaded and saved and its chart colors will now be preserved. If the spreadsheet is created new, Excel 2007-2010 colors are used. The writer is currently hard-coded to use them, so this avoids making this a breaking change. The theme colors can be explicitly changed if desired, and Excel 2013+ colors can be introduced very easily. ```php $spreadsheet->getTheme() ->setThemeColorName(Theme::COLOR_SCHEME_2013_PLUS_NAME); ``` Likewise, if the old behavior of changing to the 2007-2010 scheme rather than using the input values is desired, that is easy to achieve after the load has taken place. ```php $spreadsheet->getTheme() ->setThemeColorName(Theme::COLOR_SCHEME_2007_2010_NAME); ``` The new Theme class introduced by this change can easily be extended to include Fonts and Effects. Unlike Colors, I am unsure what the practical effects of changing those to, say, the 2013+ defaults would be. * Scrutinizer Use an alias in a use statement. * Update Change Log Due to potential behavior change. --- CHANGELOG.md | 1 + samples/Chart/33_Chart_create_area_2.php | 107 ++++++++++++++++++ samples/templates/32readwriteLineChart6.xlsx | Bin 0 -> 13522 bytes src/PhpSpreadsheet/Chart/AxisText.php | 29 ++++- src/PhpSpreadsheet/Chart/Layout.php | 44 ++++++- src/PhpSpreadsheet/Reader/Xlsx.php | 3 + src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 50 +++++++- src/PhpSpreadsheet/Spreadsheet.php | 9 ++ src/PhpSpreadsheet/Style/Font.php | 10 ++ src/PhpSpreadsheet/Theme.php | 76 +++++++++++++ src/PhpSpreadsheet/Writer/Xlsx.php | 2 +- src/PhpSpreadsheet/Writer/Xlsx/Chart.php | 85 +++++++++----- .../Writer/Xlsx/StringTable.php | 7 +- src/PhpSpreadsheet/Writer/Xlsx/Theme.php | 79 +++++-------- .../Chart/LayoutEffectsTest.php | 63 +++++++++++ .../Writer/Xlsx/ThemeColorsTest.php | 54 +++++++++ tests/data/Writer/XLSX/gallerytheme.xlsx | Bin 0 -> 8891 bytes 17 files changed, 519 insertions(+), 100 deletions(-) create mode 100644 samples/Chart/33_Chart_create_area_2.php create mode 100644 samples/templates/32readwriteLineChart6.xlsx create mode 100644 src/PhpSpreadsheet/Theme.php create mode 100644 tests/PhpSpreadsheetTests/Chart/LayoutEffectsTest.php create mode 100644 tests/PhpSpreadsheetTests/Writer/Xlsx/ThemeColorsTest.php create mode 100644 tests/data/Writer/XLSX/gallerytheme.xlsx diff --git a/CHANGELOG.md b/CHANGELOG.md index fa5df9e0f..24a4abd6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Changed +- Xlsx Color schemes read in will be written out (previously Excel 2007-2010 Color scheme was always written); manipulation of those schemes before write, including restoring prior behavior, is provided [PR #3476](https://github.com/PHPOffice/PhpSpreadsheet/pull/3476) - Memory and speed optimisations for Read Filters with Xlsx Files and Shared Formulae. [PR #3474](https://github.com/PHPOffice/PhpSpreadsheet/pull/3474) ### Deprecated diff --git a/samples/Chart/33_Chart_create_area_2.php b/samples/Chart/33_Chart_create_area_2.php new file mode 100644 index 000000000..7761caa63 --- /dev/null +++ b/samples/Chart/33_Chart_create_area_2.php @@ -0,0 +1,107 @@ +getTheme()->setThemeColorName(SpreadsheetTheme::COLOR_SCHEME_2013_PLUS_NAME); +$worksheet = $spreadsheet->getActiveSheet(); +$worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] +); + +// Set the Labels for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 +]; +// Set the X-Axis Labels +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 +]; +// Set the Data values for each data series we want to plot +// Datatype +// Cell reference for data +// Format Code +// Number of datapoints in series +// Data values +// Data Marker +$dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), +]; + +// Build the dataseries +$series = new DataSeries( + DataSeries::TYPE_AREACHART, // plotType + DataSeries::GROUPING_PERCENT_STACKED, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues +); + +// Set the series in the plot area +$plotArea = new PlotArea(null, [$series]); +// Set the chart legend +$legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false); + +$title = new Title('Test %age-Stacked Area Chart'); +$yAxisLabel = new Title('Value ($k)'); + +// Create the chart +$chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel // yAxisLabel +); + +// Set the position where the chart should appear in the worksheet +$chart->setTopLeftPosition('A7'); +$chart->setBottomRightPosition('H20'); + +// Add the chart to the worksheet +$worksheet->addChart($chart); + +// Save Excel 2007 file +$filename = $helper->getFilename(__FILE__); +$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); +$writer->setIncludeCharts(true); +$callStartTime = microtime(true); +$writer->save($filename); +$helper->logWrite($writer, $filename, $callStartTime); diff --git a/samples/templates/32readwriteLineChart6.xlsx b/samples/templates/32readwriteLineChart6.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..95d6dfd90e4e64bf82d91ea34926863591c0f763 GIT binary patch literal 13522 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 r{LgUicj~|U%)e60GW~_x>^~kfillColor = new ChartColor(); + $this->font = new Font(); + $this->font->setSize(null, true); } public function setRotation(?int $rotation): self @@ -30,6 +33,24 @@ class AxisText extends Properties public function getFillColorObject(): ChartColor { - return $this->fillColor; + $fillColor = $this->font->getChartColor(); + if ($fillColor === null) { + $fillColor = new ChartColor(); + $this->font->setChartColorFromObject($fillColor); + } + + return $fillColor; + } + + public function getFont(): Font + { + return $this->font; + } + + public function setFont(Font $font): self + { + $this->font = $font; + + return $this; } } diff --git a/src/PhpSpreadsheet/Chart/Layout.php b/src/PhpSpreadsheet/Chart/Layout.php index 0018d79d7..ac36c25c9 100644 --- a/src/PhpSpreadsheet/Chart/Layout.php +++ b/src/PhpSpreadsheet/Chart/Layout.php @@ -2,6 +2,8 @@ namespace PhpOffice\PhpSpreadsheet\Chart; +use PhpOffice\PhpSpreadsheet\Style\Font; + class Layout { /** @@ -127,8 +129,11 @@ class Layout /** @var ?ChartColor */ private $labelBorderColor; - /** @var ?ChartColor */ - private $labelFontColor; + /** @var ?Font */ + private $labelFont; + + /** @var Properties */ + private $labelEffects; /** * Create a new Layout. @@ -172,7 +177,18 @@ class Layout $this->initBoolean($layout, 'numFmtLinked'); $this->initColor($layout, 'labelFillColor'); $this->initColor($layout, 'labelBorderColor'); - $this->initColor($layout, 'labelFontColor'); + $labelFont = $layout['labelFont'] ?? null; + if ($labelFont instanceof Font) { + $this->labelFont = $labelFont; + } + $labelFontColor = $layout['labelFontColor'] ?? null; + if ($labelFontColor instanceof ChartColor) { + $this->setLabelFontColor($labelFontColor); + } + $labelEffects = $layout['labelEffects'] ?? null; + if ($labelEffects instanceof Properties) { + $this->labelEffects = $labelEffects; + } } private function initBoolean(array $layout, string $name): void @@ -493,14 +509,32 @@ class Layout return $this; } + public function getLabelFont(): ?Font + { + return $this->labelFont; + } + + public function getLabelEffects(): ?Properties + { + return $this->labelEffects; + } + public function getLabelFontColor(): ?ChartColor { - return $this->labelFontColor; + if ($this->labelFont === null) { + return null; + } + + return $this->labelFont->getChartColor(); } public function setLabelFontColor(?ChartColor $chartColor): self { - $this->labelFontColor = $chartColor; + if ($this->labelFont === null) { + $this->labelFont = new Font(); + $this->labelFont->setSize(null, true); + } + $this->labelFont->setChartColorFromObject($chartColor); return $this; } diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 46b7cfabb..50fe8181a 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -455,6 +455,7 @@ class Xlsx extends BaseReader $colourScheme = self::getAttributes($xmlTheme->themeElements->clrScheme); $colourSchemeName = (string) $colourScheme['name']; + $excel->getTheme()->setThemeColorName($colourSchemeName); $colourScheme = $xmlTheme->themeElements->clrScheme->children($drawingNS); $themeColours = []; @@ -466,9 +467,11 @@ class Xlsx extends BaseReader if (isset($xmlColour->sysClr)) { $xmlColourData = self::getAttributes($xmlColour->sysClr); $themeColours[$themePos] = (string) $xmlColourData['lastClr']; + $excel->getTheme()->setThemeColor($k, (string) $xmlColourData['lastClr']); } elseif (isset($xmlColour->srgbClr)) { $xmlColourData = self::getAttributes($xmlColour->srgbClr); $themeColours[$themePos] = (string) $xmlColourData['val']; + $excel->getTheme()->setThemeColor($k, (string) $xmlColourData['val']); } } $theme = new Theme($themeName, $colourSchemeName, $themeColours); diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index 5859ed107..7d33c6eba 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -1143,6 +1143,37 @@ class Chart return $value; } + private function parseFont(SimpleXMLElement $titleDetailPart): ?Font + { + if (!isset($titleDetailPart->pPr->defRPr)) { + return null; + } + $fontArray = []; + $fontArray['size'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer'); + $fontArray['bold'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'b', 'boolean'); + $fontArray['italic'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'i', 'boolean'); + $fontArray['underscore'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'u', 'string'); + $fontArray['strikethrough'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'strike', 'string'); + + if (isset($titleDetailPart->pPr->defRPr->latin)) { + $fontArray['latin'] = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string'); + } + if (isset($titleDetailPart->pPr->defRPr->ea)) { + $fontArray['eastAsian'] = self::getAttribute($titleDetailPart->pPr->defRPr->ea, 'typeface', 'string'); + } + if (isset($titleDetailPart->pPr->defRPr->cs)) { + $fontArray['complexScript'] = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string'); + } + if (isset($titleDetailPart->pPr->defRPr->solidFill)) { + $fontArray['chartColor'] = new ChartColor($this->readColor($titleDetailPart->pPr->defRPr->solidFill)); + } + $font = new Font(); + $font->setSize(null, true); + $font->applyFromArray($fontArray); + + return $font; + } + /** * @param ?SimpleXMLElement $chartDetail */ @@ -1189,8 +1220,13 @@ class Chart } if (isset($chartDetail->dLbls->txPr)) { $txpr = $chartDetail->dLbls->txPr->children($this->aNamespace); - if (isset($txpr->p->pPr->defRPr->solidFill)) { - $plotAttributes['labelFontColor'] = new ChartColor($this->readColor($txpr->p->pPr->defRPr->solidFill)); + if (isset($txpr->p)) { + $plotAttributes['labelFont'] = $this->parseFont($txpr->p); + if (isset($txpr->p->pPr->defRPr->effectLst)) { + $labelEffects = new GridLines(); + $this->readEffects($txpr->p->pPr->defRPr, $labelEffects, false); + $plotAttributes['labelEffects'] = $labelEffects; + } } } } @@ -1489,10 +1525,12 @@ class Chart $addAxisText = true; } } - if (isset($children->p->pPr->defRPr->solidFill)) { - $colorArray = $this->readColor($children->p->pPr->defRPr->solidFill); - $axisText->getFillColorObject()->setColorPropertiesArray($colorArray); - $addAxisText = true; + if (isset($children->p->pPr->defRPr)) { + $font = $this->parseFont($children->p); + if ($font !== null) { + $axisText->setFont($font); + $addAxisText = true; + } } if (isset($children->p->pPr->defRPr->effectLst)) { $this->readEffects($children->p->pPr->defRPr, $axisText, false); diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php index f0744cd27..3c5ecacd8 100644 --- a/src/PhpSpreadsheet/Spreadsheet.php +++ b/src/PhpSpreadsheet/Spreadsheet.php @@ -203,6 +203,14 @@ class Spreadsheet implements JsonSerializable */ private $tabRatio = 600; + /** @var Theme */ + private $theme; + + public function getTheme(): Theme + { + return $this->theme; + } + /** * The workbook has macros ? * @@ -476,6 +484,7 @@ class Spreadsheet implements JsonSerializable { $this->uniqueID = uniqid('', true); $this->calculationEngine = new Calculation($this); + $this->theme = new Theme(); // Initialise worksheet collection and add one worksheet $this->workSheetCollection = []; diff --git a/src/PhpSpreadsheet/Style/Font.php b/src/PhpSpreadsheet/Style/Font.php index 3d7bc1bce..a4d6e99a5 100644 --- a/src/PhpSpreadsheet/Style/Font.php +++ b/src/PhpSpreadsheet/Style/Font.php @@ -231,6 +231,9 @@ class Font extends Supervisor if (isset($styleArray['size'])) { $this->setSize($styleArray['size']); } + if (isset($styleArray['chartColor'])) { + $this->chartColor = $styleArray['chartColor']; + } } return $this; @@ -634,6 +637,13 @@ class Font extends Supervisor return $this; } + public function setChartColorFromObject(?ChartColor $chartColor): self + { + $this->chartColor = $chartColor; + + return $this; + } + /** * Get Underline. * diff --git a/src/PhpSpreadsheet/Theme.php b/src/PhpSpreadsheet/Theme.php new file mode 100644 index 000000000..217b155c6 --- /dev/null +++ b/src/PhpSpreadsheet/Theme.php @@ -0,0 +1,76 @@ + '000000', + 'lt1' => 'FFFFFF', + 'dk2' => '44546A', + 'lt2' => 'E7E6E6', + 'accent1' => '4472C4', + 'accent2' => 'ED7D31', + 'accent3' => 'A5A5A5', + 'accent4' => 'FFC000', + 'accent5' => '5B9BD5', + 'accent6' => '70AD47', + 'hlink' => '0563C1', + 'folHlink' => '954F72', + ]; + + public const COLOR_SCHEME_2007_2010_NAME = 'Office 2007-2010'; + public const COLOR_SCHEME_2007_2010 = [ + 'dk1' => '000000', + 'lt1' => 'FFFFFF', + 'dk2' => '1F497D', + 'lt2' => 'EEECE1', + 'accent1' => '4F81BD', + 'accent2' => 'C0504D', + 'accent3' => '9BBB59', + 'accent4' => '8064A2', + 'accent5' => '4BACC6', + 'accent6' => 'F79646', + 'hlink' => '0000FF', + 'folHlink' => '800080', + ]; + + /** @var string[] */ + private $themeColors = self::COLOR_SCHEME_2007_2010; + + public function getThemeColors(): array + { + return $this->themeColors; + } + + public function setThemeColor(string $key, string $value): self + { + $this->themeColors[$key] = $value; + + return $this; + } + + public function getThemeColorName(): string + { + return $this->themeColorName; + } + + public function setThemeColorName(string $name, ?array $themeColors = null): self + { + $this->themeColorName = $name; + if ($name === self::COLOR_SCHEME_2007_2010_NAME) { + $themeColors = $themeColors ?? self::COLOR_SCHEME_2007_2010; + } elseif ($name === self::COLOR_SCHEME_2013_PLUS_NAME) { + $themeColors = $themeColors ?? self::COLOR_SCHEME_2013_PLUS; + } + if ($themeColors !== null) { + $this->themeColors = $themeColors; + } + + return $this; + } +} diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index 07b790440..3f677b056 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -377,7 +377,7 @@ class Xlsx extends BaseWriter } // Add theme to ZIP file - $zipContent['xl/theme/theme1.xml'] = $this->getWriterPartTheme()->writeTheme(); + $zipContent['xl/theme/theme1.xml'] = $this->getWriterPartTheme()->writeTheme($this->spreadSheet); // Add string table to ZIP file $zipContent['xl/sharedStrings.xml'] = $this->getWriterPartStringTable()->writeStringTable($this->stringTable); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index bea6f4916..6200159a7 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -14,6 +14,7 @@ use PhpOffice\PhpSpreadsheet\Chart\Title; use PhpOffice\PhpSpreadsheet\Chart\TrendLine; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; +use PhpOffice\PhpSpreadsheet\Style\Font; use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; class Chart extends WriterPart @@ -451,8 +452,8 @@ class Chart extends WriterPart } $objWriter->endElement(); // c:spPr } - $fontColor = $chartLayout->getLabelFontColor(); - if ($fontColor && $fontColor->isUsable()) { + $labelFont = $chartLayout->getLabelFont(); + if ($labelFont !== null) { $objWriter->startElement('c:txPr'); $objWriter->startElement('a:bodyPr'); @@ -468,14 +469,7 @@ class Chart extends WriterPart $objWriter->startElement('a:lstStyle'); $objWriter->endElement(); // a:lstStyle - - $objWriter->startElement('a:p'); - $objWriter->startElement('a:pPr'); - $objWriter->startElement('a:defRPr'); - $this->writeColor($objWriter, $fontColor); - $objWriter->endElement(); // a:defRPr - $objWriter->endElement(); // a:pPr - $objWriter->endElement(); // a:p + $this->writeLabelFont($objWriter, $labelFont, $chartLayout->getLabelEffects()); $objWriter->endElement(); // c:txPr } @@ -642,16 +636,7 @@ class Chart extends WriterPart $objWriter->endElement(); // a:bodyPr $objWriter->startElement('a:lstStyle'); $objWriter->endElement(); // a:lstStyle - $objWriter->startElement('a:p'); - $objWriter->startElement('a:pPr'); - $objWriter->startElement('a:defRPr'); - if ($axisText !== null) { - $this->writeColor($objWriter, $axisText->getFillColorObject()); - $this->writeEffects($objWriter, $axisText); - } - $objWriter->endElement(); // a:defRPr - $objWriter->endElement(); // a:pPr - $objWriter->endElement(); // a:p + $this->writeLabelFont($objWriter, ($axisText === null) ? null : $axisText->getFont(), $axisText); $objWriter->endElement(); // c:txPr } @@ -868,16 +853,9 @@ class Chart extends WriterPart $objWriter->endElement(); // a:bodyPr $objWriter->startElement('a:lstStyle'); $objWriter->endElement(); // a:lstStyle - $objWriter->startElement('a:p'); - $objWriter->startElement('a:pPr'); - $objWriter->startElement('a:defRPr'); - if ($axisText !== null) { - $this->writeColor($objWriter, $axisText->getFillColorObject()); - $this->writeEffects($objWriter, $axisText); - } - $objWriter->endElement(); // a:defRPr - $objWriter->endElement(); // a:pPr - $objWriter->endElement(); // a:p + + $this->writeLabelFont($objWriter, ($axisText === null) ? null : $axisText->getFont(), $axisText); + $objWriter->endElement(); // c:txPr } @@ -1807,4 +1785,51 @@ class Chart extends WriterPart } } } + + private function writeLabelFont(XMLWriter $objWriter, ?Font $labelFont, ?Properties $axisText): void + { + $objWriter->startElement('a:p'); + $objWriter->startElement('a:pPr'); + $objWriter->startElement('a:defRPr'); + if ($labelFont !== null) { + $fontSize = $labelFont->getSize(); + if (is_numeric($fontSize)) { + $fontSize *= (($fontSize < 100) ? 100 : 1); + $objWriter->writeAttribute('sz', (string) $fontSize); + } + if ($labelFont->getBold() === true) { + $objWriter->writeAttribute('b', '1'); + } + if ($labelFont->getItalic() === true) { + $objWriter->writeAttribute('i', '1'); + } + $fontColor = $labelFont->getChartColor(); + if ($fontColor !== null) { + $this->writeColor($objWriter, $fontColor); + } + } + if ($axisText !== null) { + $this->writeEffects($objWriter, $axisText); + } + if ($labelFont !== null) { + if (!empty($labelFont->getLatin())) { + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $labelFont->getLatin()); + $objWriter->endElement(); + } + if (!empty($labelFont->getEastAsian())) { + $objWriter->startElement('a:eastAsian'); + $objWriter->writeAttribute('typeface', $labelFont->getEastAsian()); + $objWriter->endElement(); + } + if (!empty($labelFont->getComplexScript())) { + $objWriter->startElement('a:complexScript'); + $objWriter->writeAttribute('typeface', $labelFont->getComplexScript()); + $objWriter->endElement(); + } + } + $objWriter->endElement(); // a:defRPr + $objWriter->endElement(); // a:pPr + $objWriter->endElement(); // a:p + } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php b/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php index 7f623933c..92af57dda 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php @@ -226,9 +226,10 @@ class StringTable extends WriterPart if ($element->getFont() !== null) { // rPr $objWriter->startElement($prefix . 'rPr'); - $size = $element->getFont()->getSize(); - if (is_numeric($size)) { - $objWriter->writeAttribute('sz', (string) (int) ($size * 100)); + $fontSize = $element->getFont()->getSize(); + if (is_numeric($fontSize)) { + $fontSize *= (($fontSize < 100) ? 100 : 1); + $objWriter->writeAttribute('sz', (string) $fontSize); } // Bold diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Theme.php b/src/PhpSpreadsheet/Writer/Xlsx/Theme.php index 9ff29d45d..0e0251dce 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Theme.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Theme.php @@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; +use PhpOffice\PhpSpreadsheet\Spreadsheet; class Theme extends WriterPart { @@ -83,30 +84,12 @@ class Theme extends WriterPart 'Geor' => 'Sylfaen', ]; - /** - * Map of core colours. - * - * @var string[] - */ - private static $colourScheme = [ - 'dk2' => '1F497D', - 'lt2' => 'EEECE1', - 'accent1' => '4F81BD', - 'accent2' => 'C0504D', - 'accent3' => '9BBB59', - 'accent4' => '8064A2', - 'accent5' => '4BACC6', - 'accent6' => 'F79646', - 'hlink' => '0000FF', - 'folHlink' => '800080', - ]; - /** * Write theme to XML format. * * @return string XML Output */ - public function writeTheme() + public function writeTheme(Spreadsheet $spreadsheet) { // Create XML writer $objWriter = null; @@ -129,32 +112,9 @@ class Theme extends WriterPart // a:clrScheme $objWriter->startElement('a:clrScheme'); - $objWriter->writeAttribute('name', 'Office'); + $objWriter->writeAttribute('name', $spreadsheet->getTheme()->getThemeColorName()); - // a:dk1 - $objWriter->startElement('a:dk1'); - - // a:sysClr - $objWriter->startElement('a:sysClr'); - $objWriter->writeAttribute('val', 'windowText'); - $objWriter->writeAttribute('lastClr', '000000'); - $objWriter->endElement(); - - $objWriter->endElement(); - - // a:lt1 - $objWriter->startElement('a:lt1'); - - // a:sysClr - $objWriter->startElement('a:sysClr'); - $objWriter->writeAttribute('val', 'window'); - $objWriter->writeAttribute('lastClr', 'FFFFFF'); - $objWriter->endElement(); - - $objWriter->endElement(); - - // a:dk2 - $this->writeColourScheme($objWriter); + $this->writeColourScheme($objWriter, $spreadsheet); $objWriter->endElement(); @@ -814,16 +774,33 @@ class Theme extends WriterPart /** * Write colour scheme to XML format. */ - private function writeColourScheme(XMLWriter $objWriter): void + private function writeColourScheme(XMLWriter $objWriter, Spreadsheet $spreadsheet): void { - foreach (self::$colourScheme as $colourName => $colourValue) { - $objWriter->startElement('a:' . $colourName); + $themeArray = $spreadsheet->getTheme()->getThemeColors(); + // a:dk1 + $objWriter->startElement('a:dk1'); + $objWriter->startElement('a:sysClr'); + $objWriter->writeAttribute('val', 'windowText'); + $objWriter->writeAttribute('lastClr', $themeArray['dk1'] ?? '000000'); + $objWriter->endElement(); // a:sysClr + $objWriter->endElement(); // a:dk1 - $objWriter->startElement('a:srgbClr'); - $objWriter->writeAttribute('val', $colourValue); - $objWriter->endElement(); + // a:lt1 + $objWriter->startElement('a:lt1'); + $objWriter->startElement('a:sysClr'); + $objWriter->writeAttribute('val', 'window'); + $objWriter->writeAttribute('lastClr', $themeArray['lt1'] ?? 'FFFFFF'); + $objWriter->endElement(); // a:sysClr + $objWriter->endElement(); // a:lt1 - $objWriter->endElement(); + foreach ($themeArray as $colourName => $colourValue) { + if ($colourName !== 'dk1' && $colourName !== 'lt1') { + $objWriter->startElement('a:' . $colourName); + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', $colourValue); + $objWriter->endElement(); // a:srgbClr + $objWriter->endElement(); // a:$colourName + } } } } diff --git a/tests/PhpSpreadsheetTests/Chart/LayoutEffectsTest.php b/tests/PhpSpreadsheetTests/Chart/LayoutEffectsTest.php new file mode 100644 index 000000000..4c1bb9e5d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/LayoutEffectsTest.php @@ -0,0 +1,63 @@ +setIncludeCharts(true); + } + + public function writeCharts(XlsxWriter $writer): void + { + $writer->setIncludeCharts(true); + } + + public function testLegend(): void + { + $reader = new XlsxReader(); + $this->readCharts($reader); + $spreadsheet = $reader->load(self::FILENAME); + + /** @var callable */ + $callableReader = [$this, 'readCharts']; + /** @var callable */ + $callableWriter = [$this, 'writeCharts']; + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $sheet = $reloadedSpreadsheet->getActiveSheet(); + $charts2 = $sheet->getChartCollection(); + self::assertCount(1, $charts2); + $chart2 = $charts2[0]; + self::assertNotNull($chart2); + $yAxis = $chart2->getChartAxisY(); + $yAxisText = $yAxis->getAxisText(); + self::assertNotNull($yAxisText); + self::assertSame(['value' => 'accent4', 'type' => 'schemeClr', 'alpha' => 60], $yAxisText->getGlowProperty('color')); + $plotArea2 = $chart2->getPlotArea(); + self::assertNotNull($plotArea2); + $plotGroup2 = $plotArea2->getPlotGroup()[0]; + $plotIndex2 = $plotGroup2->getPlotLabelByIndex(0); + if ($plotIndex2 === false) { + self::fail('Unexpected false for getPlotLabelByIndex'); + } else { + $layout2 = $plotIndex2->getLabelLayout(); + self::assertNotNull($layout2); + $effects2 = $layout2->getLabelEffects(); + self::assertNotNull($effects2); + $shadows2 = $effects2->getShadowArray(); + self::assertSame('outerShdw', $shadows2['effect']); + self::assertSame(['value' => 'FF0000', 'type' => 'srgbClr', 'alpha' => 70], $shadows2['color']); + } + + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/ThemeColorsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/ThemeColorsTest.php new file mode 100644 index 000000000..7eae3e517 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/ThemeColorsTest.php @@ -0,0 +1,54 @@ +getTheme()->setThemeColorName(SpreadsheetTheme::COLOR_SCHEME_2013_PLUS_NAME); + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + self::assertSame('Office 2013+', $reloadedSpreadsheet->getTheme()->getThemeColorName()); + self::assertSame('FFC000', $reloadedSpreadsheet->getTheme()->getThemeColors()['accent4']); + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function testOffice2007Theme(): void + { + $spreadsheet = new Spreadsheet(); + $spreadsheet->getTheme()->setThemeColorName(SpreadsheetTheme::COLOR_SCHEME_2007_2010_NAME); + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + self::assertSame('Office 2007-2010', $reloadedSpreadsheet->getTheme()->getThemeColorName()); + self::assertSame('8064A2', $reloadedSpreadsheet->getTheme()->getThemeColors()['accent4']); + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function testDefaultTheme(): void + { + $spreadsheet = new Spreadsheet(); + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + self::assertSame('Office', $reloadedSpreadsheet->getTheme()->getThemeColorName()); + self::assertSame('8064A2', $reloadedSpreadsheet->getTheme()->getThemeColors()['accent4']); + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function testGalleryTheme(): void + { + $reader = new XlsxReader(); + $spreadsheet = $reader->load('tests/data/Writer/XLSX/gallerytheme.xlsx'); + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + self::assertSame('Gallery', $reloadedSpreadsheet->getTheme()->getThemeColorName()); + self::assertSame('795FAF', $reloadedSpreadsheet->getTheme()->getThemeColors()['accent4']); + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/data/Writer/XLSX/gallerytheme.xlsx b/tests/data/Writer/XLSX/gallerytheme.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..649ab958cee883d5caadea208de9e6d477671153 GIT binary patch literal 8891 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 literal 0 HcmV?d00001