From 584121d47887bdfe063c4dfdee24d490e40466d2 Mon Sep 17 00:00:00 2001 From: madflow Date: Wed, 10 Aug 2016 22:11:47 +0200 Subject: [PATCH] Add background-color to styles (#211) Removed default background color, cosmetics Remove default background color reuse bg colors Cosmetics Moved reusing fills to XLSX StyleHelper Tests and inline doc --- README.md | 3 +- src/Spout/Writer/ODS/Helper/StyleHelper.php | 5 + src/Spout/Writer/Style/Style.php | 39 ++++++++ src/Spout/Writer/Style/StyleBuilder.php | 13 +++ src/Spout/Writer/XLSX/Helper/StyleHelper.php | 95 +++++++++++++++++-- .../Spout/Writer/ODS/WriterWithStyleTest.php | 22 +++++ .../Spout/Writer/XLSX/WriterWithStyleTest.php | 29 +++++- 7 files changed, 196 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7f97168..e48a176 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ $writer->setShouldAddBOM(false); #### Row styling -It is possible to apply some formatting options to a row. Spout supports fonts, borders as well as alignment styles. +It is possible to apply some formatting options to a row. Spout supports fonts, background, borders as well as alignment styles. ```php use Box\Spout\Common\Type; @@ -146,6 +146,7 @@ $style = (new StyleBuilder()) ->setFontSize(15) ->setFontColor(Color::BLUE) ->setShouldWrapText() + ->setBackgroundColor(Color::YELLOW) ->build(); $writer = WriterFactory::create(Type::XLSX); diff --git a/src/Spout/Writer/ODS/Helper/StyleHelper.php b/src/Spout/Writer/ODS/Helper/StyleHelper.php index 7dfb828..9a0eeee 100644 --- a/src/Spout/Writer/ODS/Helper/StyleHelper.php +++ b/src/Spout/Writer/ODS/Helper/StyleHelper.php @@ -265,6 +265,11 @@ EOD; $content .= sprintf($borderProperty, implode(' ', $borders)); } + if ($style->shouldApplyBackgroundColor()) { + $content .= sprintf(' + ', $style->getBackgroundColor()); + } + $content .= ''; return $content; diff --git a/src/Spout/Writer/Style/Style.php b/src/Spout/Writer/Style/Style.php index b595b3a..a8813fb 100644 --- a/src/Spout/Writer/Style/Style.php +++ b/src/Spout/Writer/Style/Style.php @@ -71,6 +71,13 @@ class Style */ protected $shouldApplyBorder = false; + /** @var string Background color */ + protected $backgroundColor = null; + + /** @var bool */ + protected $hasSetBackgroundColor = false; + + /** * @return int|null */ @@ -279,6 +286,35 @@ class Style return $this->shouldApplyFont; } + /** + * Sets the background color + * @param $color ARGB color (@see Color) + * @return Style + */ + public function setBackgroundColor($color) + { + $this->hasSetBackgroundColor = true; + $this->backgroundColor = $color; + return $this; + } + + /** + * @return string + */ + public function getBackgroundColor() + { + return $this->backgroundColor; + } + + /** + * + * @return bool Whether the background color should be applied + */ + public function shouldApplyBackgroundColor() + { + return $this->hasSetBackgroundColor; + } + /** * Serializes the style for future comparison with other styles. * The ID is excluded from the comparison, as we only care about @@ -341,6 +377,9 @@ class Style if (!$this->getBorder() && $baseStyle->shouldApplyBorder()) { $mergedStyle->setBorder($baseStyle->getBorder()); } + if (!$this->hasSetBackgroundColor && $baseStyle->shouldApplyBackgroundColor()) { + $mergedStyle->setBackgroundColor($baseStyle->getBackgroundColor()); + } return $mergedStyle; } diff --git a/src/Spout/Writer/Style/StyleBuilder.php b/src/Spout/Writer/Style/StyleBuilder.php index 6d6239c..d620de4 100644 --- a/src/Spout/Writer/Style/StyleBuilder.php +++ b/src/Spout/Writer/Style/StyleBuilder.php @@ -133,6 +133,19 @@ class StyleBuilder return $this; } + /** + * Sets a background color + * + * @api + * @param string $color ARGB color (@see Color) + * @return StyleBuilder + */ + public function setBackgroundColor($color) + { + $this->style->setBackgroundColor($color); + return $this; + } + /** * Returns the configured style. The style is cached and can be reused. * diff --git a/src/Spout/Writer/XLSX/Helper/StyleHelper.php b/src/Spout/Writer/XLSX/Helper/StyleHelper.php index e13997e..abaa8d9 100644 --- a/src/Spout/Writer/XLSX/Helper/StyleHelper.php +++ b/src/Spout/Writer/XLSX/Helper/StyleHelper.php @@ -4,6 +4,7 @@ namespace Box\Spout\Writer\XLSX\Helper; use Box\Spout\Writer\Common\Helper\AbstractStyleHelper; use Box\Spout\Writer\Style\Color; +use Box\Spout\Writer\Style\Style; /** * Class StyleHelper @@ -13,6 +14,64 @@ use Box\Spout\Writer\Style\Color; */ class StyleHelper extends AbstractStyleHelper { + /** + * @var array + */ + protected $registeredFills = []; + + /** + * @var array [STYLE_ID] => [FILL_ID] maps a style to a fill declaration + */ + protected $styleIdToFillMappingTable = []; + + /** + * Excel preserves two default fills with index 0 and 1 + * Since Excel is the dominant vendor - we play along here + * + * @var int The fill index counter for custom fills. + */ + protected $fillIndex = 2; + + /** + * XLSX specific operations on the registered styles + * + * @param \Box\Spout\Writer\Style\Style $style + * @return \Box\Spout\Writer\Style\Style + */ + public function registerStyle($style) + { + $registeredStyle = parent::registerStyle($style); + $this->registerFill($registeredStyle); + return $registeredStyle; + } + + /** + * Register a fill definition + * + * @param \Box\Spout\Writer\Style\Style $style + */ + protected function registerFill($style) + { + $styleId = $style->getId(); + + // Currently - only solid backgrounds are supported + // so $backgroundColor is a scalar value (RGB Color) + $backgroundColor = $style->getBackgroundColor(); + + // We need to track the already registered background definitions + if (isset($backgroundColor) && !isset($this->registeredFills[$backgroundColor])) { + $this->registeredFills[$backgroundColor] = $styleId; + } + + if (!isset($this->styleIdToFillMappingTable[$styleId])) { + // The fillId maps a style to a fill declaration + // When there is no background color definition - we default to 0 + $fillId = $backgroundColor !== null ? $this->fillIndex++ : 0; + $this->styleIdToFillMappingTable[$styleId] = $fillId; + } + } + + /** * Returns the content of the "styles.xml" file, given a list of styles. * @@ -84,13 +143,29 @@ EOD; */ protected function getFillsSectionContent() { - return << - - - - -EOD; + // Excel reserves two default fills + $fillsCount = count($this->registeredFills) + 2; + $content = sprintf('', $fillsCount); + + $content .= ''; + $content .= ''; + + // The other fills are actually registered by setting a background color + foreach ($this->registeredFills as $styleId) { + + /** @var Style $style */ + $style = $this->styleIdToStyleMappingTable[$styleId]; + + $backgroundColor = $style->getBackgroundColor(); + $content .= sprintf( + '', + $backgroundColor + ); + } + + $content .= ''; + + return $content; } /** @@ -160,7 +235,11 @@ EOD; $content = ''; foreach ($registeredStyles as $style) { - $content .= 'getId(); + $fillId = $this->styleIdToFillMappingTable[$styleId]; + + $content .= 'shouldApplyFont()) { $content .= ' applyFont="1"'; diff --git a/tests/Spout/Writer/ODS/WriterWithStyleTest.php b/tests/Spout/Writer/ODS/WriterWithStyleTest.php index 7a7c190..157b7f0 100644 --- a/tests/Spout/Writer/ODS/WriterWithStyleTest.php +++ b/tests/Spout/Writer/ODS/WriterWithStyleTest.php @@ -118,6 +118,7 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase ->setFontSize(15) ->setFontColor(Color::RED) ->setFontName('Cambria') + ->setBackgroundColor(Color::GREEN) ->build(); $this->writeToODSFileWithMultipleStyles($dataRows, $fileName, [$style, $style2]); @@ -137,6 +138,7 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase $this->assertFirstChildHasAttributeEquals('15pt', $customFont2Element, 'text-properties', 'fo:font-size'); $this->assertFirstChildHasAttributeEquals('#' . Color::RED, $customFont2Element, 'text-properties', 'fo:color'); $this->assertFirstChildHasAttributeEquals('Cambria', $customFont2Element, 'text-properties', 'style:font-name'); + $this->assertFirstChildHasAttributeEquals('#' . Color::GREEN, $customFont2Element, 'table-cell-properties', 'fo:background-color'); } /** @@ -239,6 +241,26 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase $this->assertFirstChildHasAttributeEquals('wrap', $customStyleElement, 'table-cell-properties', 'fo:wrap-option'); } + /** + * @return void + */ + public function testAddBackgroundColor() + { + $fileName = 'test_default_background_style.ods'; + $dataRows = [ + ['defaultBgColor'], + ]; + + $style = (new StyleBuilder())->setBackgroundColor(Color::WHITE)->build(); + $this->writeToODSFile($dataRows, $fileName, $style); + + $styleElements = $this->getCellStyleElementsFromContentXmlFile($fileName); + $this->assertEquals(2, count($styleElements), 'There should be 2 styles (default and custom)'); + + $customStyleElement = $styleElements[1]; + $this->assertFirstChildHasAttributeEquals('#' . Color::WHITE, $customStyleElement, 'table-cell-properties', 'fo:background-color'); + } + /** * @return void */ diff --git a/tests/Spout/Writer/XLSX/WriterWithStyleTest.php b/tests/Spout/Writer/XLSX/WriterWithStyleTest.php index 7596efa..19a11ad 100644 --- a/tests/Spout/Writer/XLSX/WriterWithStyleTest.php +++ b/tests/Spout/Writer/XLSX/WriterWithStyleTest.php @@ -126,7 +126,6 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase $fontElements = $fontsDomElement->getElementsByTagName('font'); $this->assertEquals(3, $fontElements->length, 'There should be 3 associated "font" elements, including the default one.'); - // First font should be the default one $defaultFontElement = $fontElements->item(0); $this->assertChildrenNumEquals(3, $defaultFontElement, 'The default font should only have 3 properties.'); @@ -234,6 +233,34 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase $this->assertFirstChildHasAttributeEquals('1', $xfElement, 'alignment', 'wrapText'); } + /** + * @return void + */ + public function testAddBackgroundColor() + { + $fileName = 'test_add_background_color.xlsx'; + $dataRows = [ + ["BgColor"], + ]; + $style = (new StyleBuilder())->setBackgroundColor(Color::WHITE)->build(); + $this->writeToXLSXFile($dataRows, $fileName, $style); + $fillsDomElement = $this->getXmlSectionFromStylesXmlFile($fileName, 'fills'); + $this->assertEquals(3, $fillsDomElement->getAttribute('count'), 'There should be 3 fills, including the 2 default ones'); + + $fillsElements = $fillsDomElement->getElementsByTagName('fill'); + + $thirdFillElement = $fillsElements->item(2); // Zero based + $fgColor = $thirdFillElement->getElementsByTagName('fgColor')->item(0)->getAttribute('rgb'); + + $this->assertEquals(Color::WHITE, $fgColor, 'The foreground color should equal white'); + + $styleXfsElements = $this->getXmlSectionFromStylesXmlFile($fileName, 'cellXfs'); + $this->assertEquals(2, $styleXfsElements->getAttribute('count'), '2 cell xfs present - a default one and a custom one'); + + $customFillId = $styleXfsElements->lastChild->getAttribute('fillId'); + $this->assertEquals(2, (int)$customFillId, 'The custom fill id should have the index 2'); + } + /** * @return void */