Skip to content

Commit 47012ba

Browse files
committed
Hidden Rows and Columns - Tcpdf and Mpdf
Tcpdf issues warnings when processing hidden rows. Some time ago, Mpdf was having problems in the same situation; this was resolved by not writing out hidden rows in the html which Mpdf uses to generate its file. The same solution can be easily applied to Tcpdf. Neither Tcpdf nor Mpdf handles hidden columns. Dompdf doesn't have a problem with either rows or columns. At any rate, the solution for Tcpdf/Mpdf is to not write out the data in the hidden columns. It is more difficult to implement this for columns than for rows, but this PR should do the trick. Html writer generally uses `display:none` for the data in suppressed columns. This works, but is technically incorrect - the "approved" method is `visibility:collapse` on the col element. However, Firefox doesn't handle that correctly (open bug was filed over a decade ago), and, since all browsers seem to handle the existing implementation, it is left alone. Among other considerations, this PR is a necessary precursor for supporting printArea in Html/Pdf should we decide to do that (issue #3941). Writer/Html protected property `$isMPdf` is deprecated with this PR in favor of testing for `instanceof Mpdf`.
1 parent 9a94aea commit 47012ba

File tree

6 files changed

+186
-7
lines changed

6 files changed

+186
-7
lines changed

src/PhpSpreadsheet/Writer/Html.php

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ class Html extends BaseWriter
103103

104104
/**
105105
* Is the current writer creating mPDF?
106+
*
107+
* @deprecated 2.0.1 use instanceof Mpdf instead
106108
*/
107109
protected bool $isMPdf = false;
108110

@@ -454,7 +456,7 @@ public function generateSheetData(): string
454456

455457
// Get worksheet dimension
456458
[$min, $max] = explode(':', $sheet->calculateWorksheetDataDimension());
457-
[$minCol, $minRow] = Coordinate::indexesFromString($min);
459+
[$minCol, $minRow, $minColString] = Coordinate::indexesFromString($min);
458460
[$maxCol, $maxRow] = Coordinate::indexesFromString($max);
459461
$this->extendRowsAndColumns($sheet, $maxCol, $maxRow);
460462

@@ -467,16 +469,20 @@ public function generateSheetData(): string
467469
$html .= $startTag;
468470

469471
// Write row if there are HTML table cells in it
470-
$mpdfInvisible = $this->isMPdf && !$sheet->isRowVisible($row);
471-
if (!$mpdfInvisible && !isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) {
472+
if ($this->shouldGenerateRow($sheet, $row) && !isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) {
472473
// Start a new rowData
473474
$rowData = [];
474475
// Loop through columns
475476
$column = $minCol;
477+
$colStr = $minColString;
476478
while ($column <= $maxCol) {
477479
// Cell exists?
478480
$cellAddress = Coordinate::stringFromColumnIndex($column) . $row;
479-
$rowData[$column++] = ($sheet->getCellCollection()->has($cellAddress)) ? $cellAddress : '';
481+
if ($this->shouldGenerateColumn($sheet, $colStr)) {
482+
$rowData[$column] = ($sheet->getCellCollection()->has($cellAddress)) ? $cellAddress : '';
483+
}
484+
++$column;
485+
++$colStr;
480486
}
481487
$html .= $this->generateRow($sheet, $rowData, $row - 1, $cellType);
482488
}
@@ -610,7 +616,7 @@ private function writeImageInCell(string $coordinates): string
610616
$filename = htmlspecialchars($filename, Settings::htmlEntityFlags());
611617

612618
$html .= PHP_EOL;
613-
$imageData = self::winFileToUrl($filename, $this->isMPdf);
619+
$imageData = self::winFileToUrl($filename, $this instanceof Pdf\Mpdf);
614620

615621
if ($this->embedImages || str_starts_with($imageData, 'zip://')) {
616622
$picture = @file_get_contents($filename);
@@ -822,9 +828,13 @@ private function buildCssPerSheet(Worksheet $sheet, array &$css): void
822828
// col elements, initialize
823829
$highestColumnIndex = Coordinate::columnIndexFromString($sheet->getHighestColumn()) - 1;
824830
$column = -1;
831+
$colStr = 'A';
825832
while ($column++ < $highestColumnIndex) {
826833
$this->columnWidths[$sheetIndex][$column] = self::DEFAULT_CELL_WIDTH_POINTS; // approximation
827-
$css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = self::DEFAULT_CELL_WIDTH_POINTS . 'pt';
834+
if ($this->shouldGenerateColumn($sheet, $colStr)) {
835+
$css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = self::DEFAULT_CELL_WIDTH_POINTS . 'pt';
836+
}
837+
++$colStr;
828838
}
829839

830840
// col elements, loop through columnDimensions and set width
@@ -834,6 +844,9 @@ private function buildCssPerSheet(Worksheet $sheet, array &$css): void
834844
$width = SharedDrawing::pixelsToPoints($width);
835845
if ($columnDimension->getVisible() === false) {
836846
$css['table.sheet' . $sheetIndex . ' .column' . $column]['display'] = 'none';
847+
// This would be better but Firefox has an 11-year-old bug.
848+
// https://bugzilla.mozilla.org/show_bug.cgi?id=819045
849+
//$css['table.sheet' . $sheetIndex . ' col.col' . $column]['visibility'] = 'collapse';
837850
}
838851
if ($width >= 0) {
839852
$this->columnWidths[$sheetIndex][$column] = $width;
@@ -991,7 +1004,7 @@ private function createCSSStyleAlignment(Alignment $alignment): array
9911004
}
9921005
$rotation = $alignment->getTextRotation();
9931006
if ($rotation !== 0 && $rotation !== Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) {
994-
if ($this->isMPdf) {
1007+
if ($this instanceof Pdf\Mpdf) {
9951008
$css['text-rotate'] = "$rotation";
9961009
} else {
9971010
$css['transform'] = "rotate({$rotation}deg)";
@@ -1782,4 +1795,25 @@ private function generatePageDeclarations(bool $generateSurroundingHTML): string
17821795

17831796
return $htmlPage;
17841797
}
1798+
1799+
private function shouldGenerateRow(Worksheet $sheet, int $row): bool
1800+
{
1801+
if (!($this instanceof Pdf\Mpdf || $this instanceof Pdf\Tcpdf)) {
1802+
return true;
1803+
}
1804+
1805+
return $sheet->isRowVisible($row);
1806+
}
1807+
1808+
private function shouldGenerateColumn(Worksheet $sheet, string $colStr): bool
1809+
{
1810+
if (!($this instanceof Pdf\Mpdf || $this instanceof Pdf\Tcpdf)) {
1811+
return true;
1812+
}
1813+
if (!$sheet->columnDimensionExists($colStr)) {
1814+
return true;
1815+
}
1816+
1817+
return $sheet->getColumnDimension($colStr)->getVisible();
1818+
}
17851819
}

src/PhpSpreadsheet/Writer/Pdf/Mpdf.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ class Mpdf extends Pdf
1010
public const SIMULATED_BODY_START = '<!-- simulated body start -->';
1111
private const BODY_TAG = '<body>';
1212

13+
/**
14+
* Is the current writer creating mPDF?
15+
*
16+
* @deprecated 2.0.1 use instanceof Mpdf instead
17+
*/
1318
protected bool $isMPdf = true;
1419

1520
/**
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Dompdf;
6+
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class HideTest extends TestCase
12+
{
13+
public function testHide(): void
14+
{
15+
$spreadsheet = new Spreadsheet();
16+
$sheet = $spreadsheet->getActiveSheet();
17+
$sheet->fromArray([
18+
['a1', 'b1', 'c1', 'd1', 'e1', 'f1'],
19+
['a2', 'b2', 'c2', 'd2', 'e2', 'f2'],
20+
['a3', 'b3', 'c3', 'd3', 'e3', 'f3'],
21+
['a4', 'b4', 'c4', 'd4', 'e4', 'f4'],
22+
['a5', 'b5', 'c5', 'd5', 'e5', 'f5'],
23+
['a6', 'b6', 'c6', 'd6', 'e6', 'f6'],
24+
]);
25+
$sheet->getColumnDimension('B')->setVisible(false);
26+
$sheet->getRowDimension(3)->setVisible(false);
27+
$writer = new Dompdf($spreadsheet);
28+
$html = $writer->generateHtmlAll();
29+
self::assertStringContainsString('table.sheet0 .column1 { display:none }', $html);
30+
self::assertStringContainsString('table.sheet0 tr.row2 { display:none; visibility:hidden }', $html);
31+
self::assertStringContainsString('.navigation {display: none;}', $html);
32+
$count = substr_count($html, 'display:none');
33+
self::assertSame(3, $count);
34+
$spreadsheet->disconnectWorksheets();
35+
}
36+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Html;
6+
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PhpOffice\PhpSpreadsheet\Writer\Html;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class HideTest extends TestCase
12+
{
13+
public function testHide(): void
14+
{
15+
$spreadsheet = new Spreadsheet();
16+
$sheet = $spreadsheet->getActiveSheet();
17+
$sheet->fromArray([
18+
['a1', 'b1', 'c1', 'd1', 'e1', 'f1'],
19+
['a2', 'b2', 'c2', 'd2', 'e2', 'f2'],
20+
['a3', 'b3', 'c3', 'd3', 'e3', 'f3'],
21+
['a4', 'b4', 'c4', 'd4', 'e4', 'f4'],
22+
['a5', 'b5', 'c5', 'd5', 'e5', 'f5'],
23+
['a6', 'b6', 'c6', 'd6', 'e6', 'f6'],
24+
]);
25+
$sheet->getColumnDimension('B')->setVisible(false);
26+
$sheet->getRowDimension(3)->setVisible(false);
27+
$writer = new Html($spreadsheet);
28+
$html = $writer->generateHtmlAll();
29+
self::assertStringContainsString('table.sheet0 .column1 { display:none }', $html);
30+
self::assertStringContainsString('table.sheet0 tr.row2 { display:none; visibility:hidden }', $html);
31+
self::assertStringContainsString('.navigation {display: none;}', $html);
32+
$count = substr_count($html, 'display:none');
33+
self::assertSame(3, $count);
34+
$spreadsheet->disconnectWorksheets();
35+
}
36+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Mpdf;
6+
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class HideTest extends TestCase
12+
{
13+
public function testHide(): void
14+
{
15+
$spreadsheet = new Spreadsheet();
16+
$sheet = $spreadsheet->getActiveSheet();
17+
$sheet->fromArray([
18+
['a1', 'b1', 'c1', 'd1', 'e1', 'f1'],
19+
['a2', 'b2', 'c2', 'd2', 'e2', 'f2'],
20+
['a3', 'b3', 'c3', 'd3', 'e3', 'f3'],
21+
['a4', 'b4', 'c4', 'd4', 'e4', 'f4'],
22+
['a5', 'b5', 'c5', 'd5', 'e5', 'f5'],
23+
['a6', 'b6', 'c6', 'd6', 'e6', 'f6'],
24+
]);
25+
$sheet->getColumnDimension('B')->setVisible(false);
26+
$sheet->getRowDimension(3)->setVisible(false);
27+
$writer = new Mpdf($spreadsheet);
28+
$html = $writer->generateHtmlAll();
29+
self::assertStringNotContainsString('a3', $html);
30+
self::assertStringNotContainsString('b1', $html);
31+
self::assertStringContainsString('a1', $html);
32+
$spreadsheet->disconnectWorksheets();
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Tcpdf;
6+
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Tcpdf;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class HideTest extends TestCase
12+
{
13+
public function testHide(): void
14+
{
15+
$spreadsheet = new Spreadsheet();
16+
$sheet = $spreadsheet->getActiveSheet();
17+
$sheet->fromArray([
18+
['a1', 'b1', 'c1', 'd1', 'e1', 'f1'],
19+
['a2', 'b2', 'c2', 'd2', 'e2', 'f2'],
20+
['a3', 'b3', 'c3', 'd3', 'e3', 'f3'],
21+
['a4', 'b4', 'c4', 'd4', 'e4', 'f4'],
22+
['a5', 'b5', 'c5', 'd5', 'e5', 'f5'],
23+
['a6', 'b6', 'c6', 'd6', 'e6', 'f6'],
24+
]);
25+
$sheet->getColumnDimension('B')->setVisible(false);
26+
$sheet->getRowDimension(3)->setVisible(false);
27+
$writer = new Tcpdf($spreadsheet);
28+
$html = $writer->generateHtmlAll();
29+
self::assertStringNotContainsString('a3', $html);
30+
self::assertStringNotContainsString('b1', $html);
31+
self::assertStringContainsString('a1', $html);
32+
$spreadsheet->disconnectWorksheets();
33+
}
34+
}

0 commit comments

Comments
 (0)