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
This commit is contained in:
madflow 2016-08-10 22:11:47 +02:00 committed by Adrien Loison
parent b2dc0c3fa9
commit 584121d478
7 changed files with 196 additions and 10 deletions

View File

@ -133,7 +133,7 @@ $writer->setShouldAddBOM(false);
#### Row styling #### 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 ```php
use Box\Spout\Common\Type; use Box\Spout\Common\Type;
@ -146,6 +146,7 @@ $style = (new StyleBuilder())
->setFontSize(15) ->setFontSize(15)
->setFontColor(Color::BLUE) ->setFontColor(Color::BLUE)
->setShouldWrapText() ->setShouldWrapText()
->setBackgroundColor(Color::YELLOW)
->build(); ->build();
$writer = WriterFactory::create(Type::XLSX); $writer = WriterFactory::create(Type::XLSX);

View File

@ -265,6 +265,11 @@ EOD;
$content .= sprintf($borderProperty, implode(' ', $borders)); $content .= sprintf($borderProperty, implode(' ', $borders));
} }
if ($style->shouldApplyBackgroundColor()) {
$content .= sprintf('
<style:table-cell-properties fo:background-color="#%s"/>', $style->getBackgroundColor());
}
$content .= '</style:style>'; $content .= '</style:style>';
return $content; return $content;

View File

@ -71,6 +71,13 @@ class Style
*/ */
protected $shouldApplyBorder = false; protected $shouldApplyBorder = false;
/** @var string Background color */
protected $backgroundColor = null;
/** @var bool */
protected $hasSetBackgroundColor = false;
/** /**
* @return int|null * @return int|null
*/ */
@ -279,6 +286,35 @@ class Style
return $this->shouldApplyFont; 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. * Serializes the style for future comparison with other styles.
* The ID is excluded from the comparison, as we only care about * The ID is excluded from the comparison, as we only care about
@ -341,6 +377,9 @@ class Style
if (!$this->getBorder() && $baseStyle->shouldApplyBorder()) { if (!$this->getBorder() && $baseStyle->shouldApplyBorder()) {
$mergedStyle->setBorder($baseStyle->getBorder()); $mergedStyle->setBorder($baseStyle->getBorder());
} }
if (!$this->hasSetBackgroundColor && $baseStyle->shouldApplyBackgroundColor()) {
$mergedStyle->setBackgroundColor($baseStyle->getBackgroundColor());
}
return $mergedStyle; return $mergedStyle;
} }

View File

@ -133,6 +133,19 @@ class StyleBuilder
return $this; 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. * Returns the configured style. The style is cached and can be reused.
* *

View File

@ -4,6 +4,7 @@ namespace Box\Spout\Writer\XLSX\Helper;
use Box\Spout\Writer\Common\Helper\AbstractStyleHelper; use Box\Spout\Writer\Common\Helper\AbstractStyleHelper;
use Box\Spout\Writer\Style\Color; use Box\Spout\Writer\Style\Color;
use Box\Spout\Writer\Style\Style;
/** /**
* Class StyleHelper * Class StyleHelper
@ -13,6 +14,64 @@ use Box\Spout\Writer\Style\Color;
*/ */
class StyleHelper extends AbstractStyleHelper 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. * Returns the content of the "styles.xml" file, given a list of styles.
* *
@ -84,13 +143,29 @@ EOD;
*/ */
protected function getFillsSectionContent() protected function getFillsSectionContent()
{ {
return <<<EOD // Excel reserves two default fills
<fills count="1"> $fillsCount = count($this->registeredFills) + 2;
<fill> $content = sprintf('<fills count="%d">', $fillsCount);
<patternFill patternType="none"/>
</fill> $content .= '<fill><patternFill patternType="none"/></fill>';
</fills> $content .= '<fill><patternFill patternType="gray125"/></fill>';
EOD;
// 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(
'<fill><patternFill patternType="solid"><fgColor rgb="%s"/></patternFill></fill>',
$backgroundColor
);
}
$content .= '</fills>';
return $content;
} }
/** /**
@ -160,7 +235,11 @@ EOD;
$content = '<cellXfs count="' . count($registeredStyles) . '">'; $content = '<cellXfs count="' . count($registeredStyles) . '">';
foreach ($registeredStyles as $style) { foreach ($registeredStyles as $style) {
$content .= '<xf numFmtId="0" fontId="' . $style->getId() . '" fillId="0" borderId="' . $style->getId() . '" xfId="0"';
$styleId = $style->getId();
$fillId = $this->styleIdToFillMappingTable[$styleId];
$content .= '<xf numFmtId="0" fontId="' . $styleId . '" fillId="' . $fillId . '" borderId="' . $styleId . '" xfId="0"';
if ($style->shouldApplyFont()) { if ($style->shouldApplyFont()) {
$content .= ' applyFont="1"'; $content .= ' applyFont="1"';

View File

@ -118,6 +118,7 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase
->setFontSize(15) ->setFontSize(15)
->setFontColor(Color::RED) ->setFontColor(Color::RED)
->setFontName('Cambria') ->setFontName('Cambria')
->setBackgroundColor(Color::GREEN)
->build(); ->build();
$this->writeToODSFileWithMultipleStyles($dataRows, $fileName, [$style, $style2]); $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('15pt', $customFont2Element, 'text-properties', 'fo:font-size');
$this->assertFirstChildHasAttributeEquals('#' . Color::RED, $customFont2Element, 'text-properties', 'fo:color'); $this->assertFirstChildHasAttributeEquals('#' . Color::RED, $customFont2Element, 'text-properties', 'fo:color');
$this->assertFirstChildHasAttributeEquals('Cambria', $customFont2Element, 'text-properties', 'style:font-name'); $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'); $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 * @return void
*/ */

View File

@ -126,7 +126,6 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase
$fontElements = $fontsDomElement->getElementsByTagName('font'); $fontElements = $fontsDomElement->getElementsByTagName('font');
$this->assertEquals(3, $fontElements->length, 'There should be 3 associated "font" elements, including the default one.'); $this->assertEquals(3, $fontElements->length, 'There should be 3 associated "font" elements, including the default one.');
// First font should be the default one // First font should be the default one
$defaultFontElement = $fontElements->item(0); $defaultFontElement = $fontElements->item(0);
$this->assertChildrenNumEquals(3, $defaultFontElement, 'The default font should only have 3 properties.'); $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'); $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 * @return void
*/ */