Skip to content

Commit 5e02e28

Browse files
committed
Better Handling of Print Area After Column/Row Removal
Fix #2912, another oldie (3+ years). Removal of rows or columns which include part of the print area is not recalculating the print area correctly. This PR will correct that problem, but only if the print area consists of a single range. I think that is by far the most common use case. If there is a demonstrated need to handle multiple ranges, I will respond to a new issue.
1 parent 63d3960 commit 5e02e28

File tree

2 files changed

+179
-6
lines changed

2 files changed

+179
-6
lines changed

src/PhpSpreadsheet/ReferenceHelper.php

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -558,12 +558,7 @@ public function insertNewBefore(
558558
$worksheet->freezePane($splitCell, $topLeftCell);
559559
}
560560

561-
// Page setup
562-
if ($worksheet->getPageSetup()->isPrintAreaSet()) {
563-
$worksheet->getPageSetup()->setPrintArea(
564-
$this->updateCellReference($worksheet->getPageSetup()->getPrintArea())
565-
);
566-
}
561+
$this->updatePrintArea($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
567562

568563
// Update worksheet: drawings
569564
$aDrawings = $worksheet->getDrawingCollection();
@@ -589,6 +584,84 @@ public function insertNewBefore(
589584
$worksheet->garbageCollect();
590585
}
591586

587+
private function updatePrintArea(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
588+
{
589+
$pageSetup = $worksheet->getPageSetup();
590+
if (!$pageSetup->isPrintAreaSet()) {
591+
return;
592+
}
593+
$printArea = $pageSetup->getPrintArea();
594+
$coordinates = Coordinate::indexesFromString($beforeCellAddress);
595+
if (preg_match('/^([A-Z]{1,3})(\d{1,7}):([A-Z]{1,3})(\d{1,7})$/i', $printArea, $matches) === 1) {
596+
$firstRow = (int) $matches[2];
597+
$lastRow = (int) $matches[4];
598+
$firstColumnString = $matches[1];
599+
$lastColumnString = $matches[3];
600+
if ($numberOfRows < 0) {
601+
$affectedRow = $coordinates[1] + $numberOfRows - 1;
602+
$lastAffectedRow = $coordinates[1] - 1;
603+
if ($affectedRow >= $firstRow && $affectedRow <= $lastRow) {
604+
$newLastRow = max($affectedRow, $lastRow + $numberOfRows);
605+
if ($newLastRow >= $firstRow) {
606+
$pageSetup->setPrintArea($matches[1] . $matches[2] . ':' . $matches[3] . $newLastRow);
607+
} else {
608+
$pageSetup->clearPrintArea();
609+
}
610+
611+
return;
612+
}
613+
if ($lastAffectedRow >= $firstRow && $affectedRow <= $lastRow) {
614+
$newFirstRow = $affectedRow + 1;
615+
$newLastRow = $lastRow + $numberOfRows;
616+
if ($newFirstRow >= 1 && $newLastRow >= $newFirstRow) {
617+
$pageSetup->setPrintArea($matches[1] . $newFirstRow . ':' . $matches[3] . $newLastRow);
618+
} else {
619+
$pageSetup->clearPrintArea();
620+
}
621+
622+
return;
623+
}
624+
}
625+
if ($numberOfColumns < 0) {
626+
$firstColumnInt = Coordinate::columnIndexFromString($firstColumnString);
627+
$lastColumnInt = Coordinate::columnIndexFromString($lastColumnString);
628+
$affectedColumn = $coordinates[0] + $numberOfColumns - 1;
629+
$lastAffectedColumn = $coordinates[0] - 1;
630+
//var_dump($affectedColumn, $lastAffectedColumn, $firstColumnInt, $lastColumnInt);
631+
if ($affectedColumn >= $firstColumnInt && $affectedColumn <= $lastColumnInt) {
632+
$newLastColumnInt = max($affectedColumn, $lastColumnInt + $numberOfColumns);
633+
if ($newLastColumnInt >= $firstColumnInt) {
634+
$newLastColumnString = Coordinate::stringFromColumnIndex($newLastColumnInt);
635+
$pageSetup->setPrintArea($matches[1] . $matches[2] . ':' . $newLastColumnString . $matches[4]);
636+
} else {
637+
$pageSetup->clearPrintArea();
638+
}
639+
640+
return;
641+
}
642+
if ($affectedColumn < $firstColumnInt && $lastAffectedColumn > $lastColumnInt) {
643+
$pageSetup->clearPrintArea();
644+
645+
return;
646+
}
647+
if ($lastAffectedColumn >= $firstColumnInt && $lastAffectedColumn <= $lastColumnInt) {
648+
$newFirstColumn = $affectedColumn + 1;
649+
$newLastColumn = $lastColumnInt + $numberOfColumns;
650+
if ($newFirstColumn >= 1 && $newLastColumn >= $newFirstColumn) {
651+
$firstString = Coordinate::stringFromColumnIndex($newFirstColumn);
652+
$lastString = Coordinate::stringFromColumnIndex($newLastColumn);
653+
$pageSetup->setPrintArea($firstString . $matches[2] . ':' . $lastString . $matches[4]);
654+
} else {
655+
$pageSetup->clearPrintArea();
656+
}
657+
658+
return;
659+
}
660+
}
661+
}
662+
$pageSetup->setPrintArea($this->updateCellReference($printArea));
663+
}
664+
592665
private static function matchSheetName(?string $match, string $worksheetName): bool
593666
{
594667
return $match === null || $match === '' || $match === "'\u{fffc}'" || $match === "'\u{fffb}'" || strcasecmp(trim($match, "'"), $worksheetName) === 0;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Worksheet;
6+
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class PrintAreaTest extends TestCase
12+
{
13+
#[DataProvider('removeRowsProvider')]
14+
public function testRemoveRows(string $expected, int $rowNumber, int $numberOfRows): void
15+
{
16+
$spreadsheet = new Spreadsheet();
17+
$sheet = $spreadsheet->getActiveSheet();
18+
$sheet->getPageSetup()->setPrintArea('B5:M41');
19+
$sheet->removeRow($rowNumber, $numberOfRows);
20+
self::assertSame($expected, $sheet->getPageSetup()->getPrintArea());
21+
$spreadsheet->disconnectWorksheets();
22+
}
23+
24+
public static function removeRowsProvider(): array
25+
{
26+
return [
27+
'before beginning of printArea' => ['B3:M39', 3, 2],
28+
'creep into printArea' => ['B3:M37', 3, 4],
29+
'entirely within printArea' => ['B5:M36', 6, 5],
30+
'creep past end of printArea' => ['B5:M34', 35, 8],
31+
'after end of printArea' => ['B5:M41', 55, 8],
32+
'entire printArea' => ['', 5, 37],
33+
'entire printArea+' => ['', 4, 47],
34+
];
35+
}
36+
37+
#[DataProvider('removeColumnsProvider')]
38+
public function testRemoveColumns(string $expected, string $column, int $numberOfColumns): void
39+
{
40+
$spreadsheet = new Spreadsheet();
41+
$sheet = $spreadsheet->getActiveSheet();
42+
$sheet->getPageSetup()->setPrintArea('D5:M41');
43+
$sheet->removeColumn($column, $numberOfColumns);
44+
self::assertSame($expected, $sheet->getPageSetup()->getPrintArea());
45+
$spreadsheet->disconnectWorksheets();
46+
}
47+
48+
public static function removeColumnsProvider(): array
49+
{
50+
return [
51+
'before beginning of printArea' => ['B5:K41', 'B', 2],
52+
'creep into printArea' => ['B5:I41', 'B', 4],
53+
'entirely within printArea' => ['D5:I41', 'E', 4],
54+
'creep past end of printArea' => ['D5:K41', 'L', 3],
55+
'after end of printArea' => ['D5:M41', 'P', 8],
56+
'entire printArea' => ['', 'D', 10],
57+
'entire printArea+' => ['', 'C', 15],
58+
];
59+
}
60+
61+
#[DataProvider('addRowsProvider')]
62+
public function testAddRows(string $expected, int $rowNumber, int $numberOfRows): void
63+
{
64+
$spreadsheet = new Spreadsheet();
65+
$sheet = $spreadsheet->getActiveSheet();
66+
$sheet->getPageSetup()->setPrintArea('D5:M41');
67+
$sheet->insertNewRowBefore($rowNumber, $numberOfRows);
68+
self::assertSame($expected, $sheet->getPageSetup()->getPrintArea());
69+
$spreadsheet->disconnectWorksheets();
70+
}
71+
72+
public static function addRowsProvider(): array
73+
{
74+
return [
75+
'entirely within printArea' => ['D5:M44', 15, 3],
76+
'above printArea' => ['D9:M45', 3, 4],
77+
'below printArea' => ['D5:M41', 48, 4],
78+
];
79+
}
80+
81+
#[DataProvider('addColumnsProvider')]
82+
public function testAddColumns(string $expected, string $column, int $numberOfColumns): void
83+
{
84+
$spreadsheet = new Spreadsheet();
85+
$sheet = $spreadsheet->getActiveSheet();
86+
$sheet->getPageSetup()->setPrintArea('D5:M41');
87+
$sheet->insertNewColumnBefore($column, $numberOfColumns);
88+
self::assertSame($expected, $sheet->getPageSetup()->getPrintArea());
89+
$spreadsheet->disconnectWorksheets();
90+
}
91+
92+
public static function addColumnsProvider(): array
93+
{
94+
return [
95+
'entirely within printArea' => ['D5:P41', 'J', 3],
96+
'left of printArea' => ['H5:Q41', 'C', 4],
97+
'right of printArea' => ['D5:M41', 'Q', 4],
98+
];
99+
}
100+
}

0 commit comments

Comments
 (0)