diff --git a/CHANGELOG.md b/CHANGELOG.md index 867eb4d0ca..174293d262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ and this project adheres to [Semantic Versioning](https://semver.org). Thia is a ### Fixed - Google-only formulas exported from Google Sheets. [Issue #1637](https://github.com/PHPOffice/PhpSpreadsheet/issues/1637) [PR #4579](https://github.com/PHPOffice/PhpSpreadsheet/pull/4579) +- Maximum column width. [PR #4581](https://github.com/PHPOffice/PhpSpreadsheet/pull/4581) +- PrintArea after row/column delete. [Issue #2912](https://github.com/PHPOffice/PhpSpreadsheet/issues/2912) [PR #4598](https://github.com/PHPOffice/PhpSpreadsheet/pull/4598) ## 2025-08-10 - 5.0.0 diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index 1abcb84965..9db38160c0 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -558,12 +558,7 @@ public function insertNewBefore( $worksheet->freezePane($splitCell, $topLeftCell); } - // Page setup - if ($worksheet->getPageSetup()->isPrintAreaSet()) { - $worksheet->getPageSetup()->setPrintArea( - $this->updateCellReference($worksheet->getPageSetup()->getPrintArea()) - ); - } + $this->updatePrintAreas($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); // Update worksheet: drawings $aDrawings = $worksheet->getDrawingCollection(); @@ -589,6 +584,93 @@ public function insertNewBefore( $worksheet->garbageCollect(); } + private function updatePrintAreas(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void + { + $pageSetup = $worksheet->getPageSetup(); + if (!$pageSetup->isPrintAreaSet()) { + return; + } + $printAreas = explode(',', $pageSetup->getPrintArea()); + $newPrintAreas = []; + foreach ($printAreas as $printArea) { + $result = $this->updatePrintArea($printArea, $beforeCellAddress, $numberOfColumns, $numberOfRows); + if ($result !== '') { + $newPrintAreas[] = $result; + } + } + $result = implode(',', $newPrintAreas); + if ($result === '') { + $pageSetup->clearPrintArea(); + } else { + $pageSetup->setPrintArea($result); + } + } + + private function updatePrintArea(string $printArea, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): string + { + $coordinates = Coordinate::indexesFromString($beforeCellAddress); + if (preg_match('/^([A-Z]{1,3})(\d{1,7}):([A-Z]{1,3})(\d{1,7})$/i', $printArea, $matches) === 1) { + $firstRow = (int) $matches[2]; + $lastRow = (int) $matches[4]; + $firstColumnString = $matches[1]; + $lastColumnString = $matches[3]; + if ($numberOfRows < 0) { + $affectedRow = $coordinates[1] + $numberOfRows - 1; + $lastAffectedRow = $coordinates[1] - 1; + if ($affectedRow >= $firstRow && $affectedRow <= $lastRow) { + $newLastRow = max($affectedRow, $lastRow + $numberOfRows); + if ($newLastRow >= $firstRow) { + return $matches[1] . $matches[2] . ':' . $matches[3] . $newLastRow; + } + + return ''; + } + if ($lastAffectedRow >= $firstRow && $affectedRow <= $lastRow) { + $newFirstRow = $affectedRow + 1; + $newLastRow = $lastRow + $numberOfRows; + if ($newFirstRow >= 1 && $newLastRow >= $newFirstRow) { + return $matches[1] . $newFirstRow . ':' . $matches[3] . $newLastRow; + } + + return ''; + } + } + if ($numberOfColumns < 0) { + $firstColumnInt = Coordinate::columnIndexFromString($firstColumnString); + $lastColumnInt = Coordinate::columnIndexFromString($lastColumnString); + $affectedColumn = $coordinates[0] + $numberOfColumns - 1; + $lastAffectedColumn = $coordinates[0] - 1; + if ($affectedColumn >= $firstColumnInt && $affectedColumn <= $lastColumnInt) { + $newLastColumnInt = max($affectedColumn, $lastColumnInt + $numberOfColumns); + if ($newLastColumnInt >= $firstColumnInt) { + $newLastColumnString = Coordinate::stringFromColumnIndex($newLastColumnInt); + + return $matches[1] . $matches[2] . ':' . $newLastColumnString . $matches[4]; + } + + return ''; + } + if ($affectedColumn < $firstColumnInt && $lastAffectedColumn > $lastColumnInt) { + return ''; + } + if ($lastAffectedColumn >= $firstColumnInt && $lastAffectedColumn <= $lastColumnInt) { + $newFirstColumn = $affectedColumn + 1; + $newLastColumn = $lastColumnInt + $numberOfColumns; + if ($newFirstColumn >= 1 && $newLastColumn >= $newFirstColumn) { + $firstString = Coordinate::stringFromColumnIndex($newFirstColumn); + $lastString = Coordinate::stringFromColumnIndex($newLastColumn); + + return $firstString . $matches[2] . ':' . $lastString . $matches[4]; + } + + return ''; + } + } + } + + return $this->updateCellReference($printArea); + } + private static function matchSheetName(?string $match, string $worksheetName): bool { return $match === null || $match === '' || $match === "'\u{fffc}'" || $match === "'\u{fffb}'" || strcasecmp(trim($match, "'"), $worksheetName) === 0; diff --git a/tests/PhpSpreadsheetTests/Worksheet/PrintAreaTest.php b/tests/PhpSpreadsheetTests/Worksheet/PrintAreaTest.php new file mode 100644 index 0000000000..c5e63fbc31 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/PrintAreaTest.php @@ -0,0 +1,100 @@ +getActiveSheet(); + $sheet->getPageSetup()->setPrintArea('B5:M41'); + $sheet->removeRow($rowNumber, $numberOfRows); + self::assertSame($expected, $sheet->getPageSetup()->getPrintArea()); + $spreadsheet->disconnectWorksheets(); + } + + public static function removeRowsProvider(): array + { + return [ + 'before beginning of printArea' => ['B3:M39', 3, 2], + 'creep into printArea' => ['B3:M37', 3, 4], + 'entirely within printArea' => ['B5:M36', 6, 5], + 'creep past end of printArea' => ['B5:M34', 35, 8], + 'after end of printArea' => ['B5:M41', 55, 8], + 'entire printArea' => ['', 5, 37], + 'entire printArea+' => ['', 4, 47], + ]; + } + + #[DataProvider('removeColumnsProvider')] + public function testRemoveColumns(string $expected, string $column, int $numberOfColumns): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getPageSetup()->setPrintArea('D5:M41'); + $sheet->removeColumn($column, $numberOfColumns); + self::assertSame($expected, $sheet->getPageSetup()->getPrintArea()); + $spreadsheet->disconnectWorksheets(); + } + + public static function removeColumnsProvider(): array + { + return [ + 'before beginning of printArea' => ['B5:K41', 'B', 2], + 'creep into printArea' => ['B5:I41', 'B', 4], + 'entirely within printArea' => ['D5:I41', 'E', 4], + 'creep past end of printArea' => ['D5:K41', 'L', 3], + 'after end of printArea' => ['D5:M41', 'P', 8], + 'entire printArea' => ['', 'D', 10], + 'entire printArea+' => ['', 'C', 15], + ]; + } + + #[DataProvider('addRowsProvider')] + public function testAddRows(string $expected, int $rowNumber, int $numberOfRows): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getPageSetup()->setPrintArea('D5:M41'); + $sheet->insertNewRowBefore($rowNumber, $numberOfRows); + self::assertSame($expected, $sheet->getPageSetup()->getPrintArea()); + $spreadsheet->disconnectWorksheets(); + } + + public static function addRowsProvider(): array + { + return [ + 'entirely within printArea' => ['D5:M44', 15, 3], + 'above printArea' => ['D9:M45', 3, 4], + 'below printArea' => ['D5:M41', 48, 4], + ]; + } + + #[DataProvider('addColumnsProvider')] + public function testAddColumns(string $expected, string $column, int $numberOfColumns): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getPageSetup()->setPrintArea('D5:M41'); + $sheet->insertNewColumnBefore($column, $numberOfColumns); + self::assertSame($expected, $sheet->getPageSetup()->getPrintArea()); + $spreadsheet->disconnectWorksheets(); + } + + public static function addColumnsProvider(): array + { + return [ + 'entirely within printArea' => ['D5:P41', 'J', 3], + 'left of printArea' => ['H5:Q41', 'C', 4], + 'right of printArea' => ['D5:M41', 'Q', 4], + ]; + } +}