Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sheet Background Images #3795

Merged
merged 3 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org).
- Support for Conditional Formatting Color Scale. [PR #3738](https://github.com/PHPOffice/PhpSpreadsheet/pull/3738)
- Support Additional Tags in Helper/Html. [Issue #3751](https://github.com/PHPOffice/PhpSpreadsheet/issues/3751) [PR #3752](https://github.com/PHPOffice/PhpSpreadsheet/pull/3752)
- Writer ODS : Write Border Style for cells [Issue #3690](https://github.com/PHPOffice/PhpSpreadsheet/issues/3690) [PR #3693](https://github.com/PHPOffice/PhpSpreadsheet/pull/3693)
- Sheet Background Images [Issue #1649](https://github.com/PHPOffice/PhpSpreadsheet/issues/1649) [PR #3795](https://github.com/PHPOffice/PhpSpreadsheet/pull/3795)
- Check if Coordinate is Inside Range [PR #3779](https://github.com/PHPOffice/PhpSpreadsheet/pull/3779)

### Changed

Expand Down Expand Up @@ -72,6 +74,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
- Theme File Missing but Referenced in Spreadsheet. [Issue #3770](https://github.com/PHPOffice/PhpSpreadsheet/issues/3770) [PR #3772](https://github.com/PHPOffice/PhpSpreadsheet/pull/3772)
- Slk Shared Formulas. [Issue #2267](https://github.com/PHPOffice/PhpSpreadsheet/issues/2267) [PR #3776](https://github.com/PHPOffice/PhpSpreadsheet/pull/3776)
- Html omitting some charts. [Issue #3767](https://github.com/PHPOffice/PhpSpreadsheet/issues/3767) [PR #3771](https://github.com/PHPOffice/PhpSpreadsheet/pull/3771)
- Case Insensitive Comparison for Sheet Names [PR #3791](https://github.com/PHPOffice/PhpSpreadsheet/pull/3791)

## 1.29.0 - 2023-06-15

Expand Down
22 changes: 22 additions & 0 deletions src/PhpSpreadsheet/Reader/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet

if ($this->readDataOnly === false) {
$this->readAutoFilter($xmlSheetNS, $docSheet);
$this->readBackgroundImage($xmlSheetNS, $docSheet, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels');
}

$this->readTables($xmlSheetNS, $docSheet, $dir, $fileWorksheet, $zip, $mainNS);
Expand Down Expand Up @@ -2201,6 +2202,27 @@ private function readAutoFilter(
}
}

private function readBackgroundImage(
SimpleXMLElement $xmlSheet,
Worksheet $docSheet,
string $relsName
): void {
if ($xmlSheet && $xmlSheet->picture) {
$id = (string) self::getArrayItem(self::getAttributes($xmlSheet->picture, Namespaces::SCHEMA_OFFICE_DOCUMENT), 'id');
$rels = $this->loadZip($relsName);
foreach ($rels->Relationship as $rel) {
$attrs = $rel->attributes() ?? [];
$rid = (string) ($attrs['Id'] ?? '');
$target = (string) ($attrs['Target'] ?? '');
if ($rid === $id && substr($target, 0, 2) === '..') {
$target = 'xl' . substr($target, 2);
$content = $this->getFromZipArchive($this->zip, $target);
$docSheet->setBackgroundImage($content);
}
}
}
}

private function readTables(
SimpleXMLElement $xmlSheet,
Worksheet $docSheet,
Expand Down
43 changes: 43 additions & 0 deletions src/PhpSpreadsheet/Worksheet/Worksheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -3879,4 +3879,47 @@ private function getXfIndex(string $coordinate): ?int

return $xfIndex;
}

private string $backgroundImage = '';

private string $backgroundMime = '';

private string $backgroundExtension = '';

public function getBackgroundImage(): string
{
return $this->backgroundImage;
}

public function getBackgroundMime(): string
{
return $this->backgroundMime;
}

public function getBackgroundExtension(): string
{
return $this->backgroundExtension;
}

/**
* Set background image.
* Used on read/write for Xlsx.
* Used on write for Html.
*
* @param string $backgroundImage Image represented as a string, e.g. results of file_get_contents
*/
public function setBackgroundImage(string $backgroundImage): self
{
$imageArray = getimagesizefromstring($backgroundImage) ?: ['mime' => ''];
$mime = $imageArray['mime'];
if ($mime !== '') {
$extension = explode('/', $mime);
$extension = $extension[1];
$this->backgroundImage = $backgroundImage;
$this->backgroundMime = $mime;
$this->backgroundExtension = $extension;
}

return $this;
}
}
5 changes: 5 additions & 0 deletions src/PhpSpreadsheet/Writer/Html.php
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,11 @@ private function buildCssPerSheet(Worksheet $sheet, array &$css): void
$css["table.sheet$sheetIndex"]['page-break-inside'] = 'avoid';
$css["table.sheet$sheetIndex"]['break-inside'] = 'avoid';
}
$picture = $sheet->getBackgroundImage();
if ($picture !== '') {
$base64 = base64_encode($picture);
$css["table.sheet$sheetIndex"]['background-image'] = 'url(data:' . $sheet->getBackgroundMime() . ';base64,' . $base64 . ')';
}

// Build styles
// Calculate column widths
Expand Down
2 changes: 1 addition & 1 deletion src/PhpSpreadsheet/Writer/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ public function save($filename, int $flags = 0): void
// Add worksheet relationships (drawings, ...)
for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
// Add relationships
$zipContent['xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts, $tableRef1);
$zipContent['xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts, $tableRef1, $zipContent);

// Add unparsedLoadedData
$sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName();
Expand Down
7 changes: 7 additions & 0 deletions src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = fal
}
}
}

$bgImage = $spreadsheet->getSheet($i)->getBackgroundImage();
$mimeType = $spreadsheet->getSheet($i)->getBackgroundMime();
$extension = $spreadsheet->getSheet($i)->getBackgroundExtension();
if ($bgImage !== '' && !isset($aMediaContentTypes[$mimeType])) {
$this->writeDefaultContentType($objWriter, $extension, $mimeType);
}
}

// unparsed defaults
Expand Down
16 changes: 15 additions & 1 deletion src/PhpSpreadsheet/Writer/Xlsx/Rels.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public function writeWorkbookRelationships(Spreadsheet $spreadsheet)
*
* @return string XML Output
*/
public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, $worksheetId = 1, $includeCharts = false, $tableRef = 1)
public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, $worksheetId = 1, $includeCharts = false, $tableRef = 1, array &$zipContent = [])
{
// Create XML writer
$objWriter = null;
Expand Down Expand Up @@ -221,6 +221,20 @@ public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\
);
}

$backgroundImage = $worksheet->getBackgroundImage();
if ($backgroundImage !== '') {
$rId = 'Bg';
$uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999));
$relPath = "../media/$uniqueName." . $worksheet->getBackgroundExtension();
$this->writeRelationship(
$objWriter,
$rId,
Namespaces::IMAGE,
$relPath
);
$zipContent["xl/media/$uniqueName." . $worksheet->getBackgroundExtension()] = $backgroundImage;
}

// Write hyperlink relationships?
$i = 1;
foreach ($worksheet->getHyperlinkCollection() as $hyperlink) {
Expand Down
44 changes: 37 additions & 7 deletions src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, array $string
// IgnoredErrors
$this->writeIgnoredErrors($objWriter);

// BackgroundImage must come after ignored, before table
$this->writeBackgroundImage($objWriter, $worksheet);

// Table
$this->writeTable($objWriter, $worksheet);

Expand Down Expand Up @@ -1042,6 +1045,9 @@ private function writeAutoFilter(XMLWriter $objWriter, PhpspreadsheetWorksheet $
private function writeTable(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
{
$tableCount = $worksheet->getTableCollection()->count();
if ($tableCount === 0) {
return;
}

$objWriter->startElement('tableParts');
$objWriter->writeAttribute('count', (string) $tableCount);
Expand All @@ -1055,6 +1061,18 @@ private function writeTable(XMLWriter $objWriter, PhpspreadsheetWorksheet $works
$objWriter->endElement();
}

/**
* Write Background Image.
*/
private function writeBackgroundImage(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
{
if ($worksheet->getBackgroundImage() !== '') {
$objWriter->startElement('picture');
$objWriter->writeAttribute('r:id', 'rIdBg');
$objWriter->endElement();
}
}

/**
* Write PageSetup.
*/
Expand Down Expand Up @@ -1098,19 +1116,31 @@ private function writePageSetup(XMLWriter $objWriter, PhpspreadsheetWorksheet $w
private function writeHeaderFooter(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
{
// headerFooter
$headerFooter = $worksheet->getHeaderFooter();
$oddHeader = $headerFooter->getOddHeader();
$oddFooter = $headerFooter->getOddFooter();
$evenHeader = $headerFooter->getEvenHeader();
$evenFooter = $headerFooter->getEvenFooter();
$firstHeader = $headerFooter->getFirstHeader();
$firstFooter = $headerFooter->getFirstFooter();
if ("$oddHeader$oddFooter$evenHeader$evenFooter$firstHeader$firstFooter" === '') {
return;
}

$objWriter->startElement('headerFooter');
$objWriter->writeAttribute('differentOddEven', ($worksheet->getHeaderFooter()->getDifferentOddEven() ? 'true' : 'false'));
$objWriter->writeAttribute('differentFirst', ($worksheet->getHeaderFooter()->getDifferentFirst() ? 'true' : 'false'));
$objWriter->writeAttribute('scaleWithDoc', ($worksheet->getHeaderFooter()->getScaleWithDocument() ? 'true' : 'false'));
$objWriter->writeAttribute('alignWithMargins', ($worksheet->getHeaderFooter()->getAlignWithMargins() ? 'true' : 'false'));

$objWriter->writeElement('oddHeader', $worksheet->getHeaderFooter()->getOddHeader());
$objWriter->writeElement('oddFooter', $worksheet->getHeaderFooter()->getOddFooter());
$objWriter->writeElement('evenHeader', $worksheet->getHeaderFooter()->getEvenHeader());
$objWriter->writeElement('evenFooter', $worksheet->getHeaderFooter()->getEvenFooter());
$objWriter->writeElement('firstHeader', $worksheet->getHeaderFooter()->getFirstHeader());
$objWriter->writeElement('firstFooter', $worksheet->getHeaderFooter()->getFirstFooter());
$objWriter->endElement();
self::writeElementIf($objWriter, $oddHeader !== '', 'oddHeader', $oddHeader);
self::writeElementIf($objWriter, $oddFooter !== '', 'oddFooter', $oddFooter);
self::writeElementIf($objWriter, $evenHeader !== '', 'evenHeader', $evenHeader);
self::writeElementIf($objWriter, $evenFooter !== '', 'evenFooter', $evenFooter);
self::writeElementIf($objWriter, $firstHeader !== '', 'firstHeader', $firstHeader);
self::writeElementIf($objWriter, $firstFooter !== '', 'firstFooter', $firstFooter);

$objWriter->endElement(); // headerFooter
}

/**
Expand Down
31 changes: 31 additions & 0 deletions tests/PhpSpreadsheetTests/Writer/Html/BackgroundImageTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests\Writer\Html;

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Html;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;

class BackgroundImageTest extends AbstractFunctional
{
public function testBackgroundImage(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->getCell('A1')->setValue(1);
$sheet->getCell('B1')->setValue(2);
$sheet->getCell('A2')->setValue(3);
$sheet->getCell('B2')->setValue(4);
$imageFile = 'tests/data/Writer/XLSX/backgroundtest.png';
$image = (string) file_get_contents($imageFile);
$sheet->setBackgroundImage($image);
self::assertSame('image/png', $sheet->getBackgroundMime());
self::assertSame('png', $sheet->getBackgroundExtension());
$writer = new Html($spreadsheet);
$header = $writer->generateHTMLHeader(true);
self::assertStringContainsString('table.sheet0 { background-image:url(data:image/png;base64,', $header);
$spreadsheet->disconnectWorksheets();
}
}
50 changes: 50 additions & 0 deletions tests/PhpSpreadsheetTests/Writer/Xlsx/BackgroundImageTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;

class BackgroundImageTest extends AbstractFunctional
{
public function testBackgroundImage(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->getCell('A1')->setValue(1);
$sheet->getCell('B1')->setValue(2);
$sheet->getCell('A2')->setValue(3);
$sheet->getCell('B2')->setValue(4);
$imageFile = 'tests/data/Writer/XLSX/backgroundtest.png';
$image = (string) file_get_contents($imageFile);
$sheet->setBackgroundImage($image);
self::assertSame('image/png', $sheet->getBackgroundMime());
self::assertSame('png', $sheet->getBackgroundExtension());

$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
$spreadsheet->disconnectWorksheets();
$reloadedWorksheet = $reloadedSpreadsheet->getActiveSheet();
self::assertSame($image, $reloadedWorksheet->getBackgroundImage());
self::assertSame('image/png', $reloadedWorksheet->getBackgroundMime());
self::assertSame('png', $reloadedWorksheet->getBackgroundExtension());
self::assertSame(2, $reloadedWorksheet->getCell('B1')->getValue());
$reloadedSpreadsheet->disconnectWorksheets();
}

public function testInvalidImage(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->getCell('A1')->setValue(1);
$imageFile = __FILE__;
$image = (string) file_get_contents($imageFile);
self::assertNotSame('', $image);
$sheet->setBackgroundImage($image);
self::assertSame('', $sheet->getBackgroundImage());
self::assertSame('', $sheet->getBackgroundMime());
self::assertSame('', $sheet->getBackgroundExtension());
$spreadsheet->disconnectWorksheets();
}
}
Binary file added tests/data/Writer/XLSX/backgroundtest.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.