diff --git a/docs/topics/recipes.md b/docs/topics/recipes.md index f5563ef96a..691d081284 100644 --- a/docs/topics/recipes.md +++ b/docs/topics/recipes.md @@ -479,6 +479,15 @@ row 10. $spreadsheet->getActiveSheet()->setBreak('A10', \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_ROW); ``` +If your print break is inside a defined print area, it may be necessary to add an extra parameter to specify the max column (and this probably won't hurt if the break is not inside a defined print area): + +```php +$spreadsheet->getActiveSheet() + ->setBreak('A10', + \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_ROW, + \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_ROW_MAX_COLUMN); +``` + The following line of code sets a print break on column D: ```php diff --git a/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php b/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php index fb55cb2653..ab57548847 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php @@ -151,8 +151,9 @@ private function pageBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): v private function rowBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void { foreach ($xmlSheet->rowBreaks->brk as $brk) { + $rowBreakMax = isset($brk['max']) ? ((int) $brk['max']) : -1; if ($brk['man']) { - $worksheet->setBreak("A{$brk['id']}", Worksheet::BREAK_ROW); + $worksheet->setBreak("A{$brk['id']}", Worksheet::BREAK_ROW, $rowBreakMax); } } } diff --git a/src/PhpSpreadsheet/Worksheet/PageBreak.php b/src/PhpSpreadsheet/Worksheet/PageBreak.php new file mode 100644 index 0000000000..743cc58d69 --- /dev/null +++ b/src/PhpSpreadsheet/Worksheet/PageBreak.php @@ -0,0 +1,58 @@ +breakType = $breakType; + $this->coordinate = $coordinate; + $this->maxColOrRow = $maxColOrRow; + } + + public function getBreakType(): int + { + return $this->breakType; + } + + public function getCoordinate(): string + { + return $this->coordinate; + } + + public function getMaxColOrRow(): int + { + return $this->maxColOrRow; + } + + public function getColumnInt(): int + { + return Coordinate::indexesFromString($this->coordinate)[0]; + } + + public function getRow(): int + { + return Coordinate::indexesFromString($this->coordinate)[1]; + } + + public function getColumnString(): string + { + return Coordinate::indexesFromString($this->coordinate)[2]; + } +} diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index cd9b8ac884..27ef7735a2 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -35,6 +35,8 @@ class Worksheet implements IComparable public const BREAK_NONE = 0; public const BREAK_ROW = 1; public const BREAK_COLUMN = 2; + // Maximum column for row break + public const BREAK_ROW_MAX_COLUMN = 16383; // Sheet state public const SHEETSTATE_VISIBLE = 'visible'; @@ -188,11 +190,18 @@ class Worksheet implements IComparable private $conditionalStylesCollection = []; /** - * Collection of breaks. + * Collection of row breaks. * - * @var int[] + * @var PageBreak[] */ - private $breaks = []; + private $rowBreaks = []; + + /** + * Collection of column breaks. + * + * @var PageBreak[] + */ + private $columnBreaks = []; /** * Collection of merged cell ranges. @@ -1748,16 +1757,16 @@ public function duplicateConditionalStyle(array $styles, $range = '') * * @return $this */ - public function setBreak($coordinate, $break) + public function setBreak($coordinate, $break, int $max = -1) { $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)); if ($break === self::BREAK_NONE) { - if (isset($this->breaks[$cellAddress])) { - unset($this->breaks[$cellAddress]); - } - } else { - $this->breaks[$cellAddress] = $break; + unset($this->rowBreaks[$cellAddress], $this->columnBreaks[$cellAddress]); + } elseif ($break === self::BREAK_ROW) { + $this->rowBreaks[$cellAddress] = new PageBreak($break, $cellAddress, $max); + } elseif ($break === self::BREAK_COLUMN) { + $this->columnBreaks[$cellAddress] = new PageBreak($break, $cellAddress, $max); } return $this; @@ -1789,7 +1798,35 @@ public function setBreakByColumnAndRow($columnIndex, $row, $break) */ public function getBreaks() { - return $this->breaks; + $breaks = []; + foreach ($this->rowBreaks as $break) { + $breaks[$break->getCoordinate()] = self::BREAK_ROW; + } + foreach ($this->columnBreaks as $break) { + $breaks[$break->getCoordinate()] = self::BREAK_COLUMN; + } + + return $breaks; + } + + /** + * Get row breaks. + * + * @return PageBreak[] + */ + public function getRowBreaks() + { + return $this->rowBreaks; + } + + /** + * Get row breaks. + * + * @return PageBreak[] + */ + public function getColumnBreaks() + { + return $this->columnBreaks; } /** diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index 309168f25b..c30bb30acf 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -1193,7 +1193,7 @@ private function generateRowStart(Worksheet $worksheet, $sheetIndex, $row) { $html = ''; if (count($worksheet->getBreaks()) > 0) { - $breaks = $worksheet->getBreaks(); + $breaks = $worksheet->getRowBreaks(); // check if a break is needed before this row if (isset($breaks['A' . $row])) { diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index ce6a84f206..79ab874c4b 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -2022,27 +2022,15 @@ private function writeBreaks(): void $vbreaks = []; $hbreaks = []; - foreach ($this->phpSheet->getBreaks() as $cell => $breakType) { + foreach ($this->phpSheet->getRowBreaks() as $cell => $break) { // Fetch coordinates $coordinates = Coordinate::coordinateFromString($cell); - - // Decide what to do by the type of break - switch ($breakType) { - case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_COLUMN: - // Add to list of vertical breaks - $vbreaks[] = Coordinate::columnIndexFromString($coordinates[0]) - 1; - - break; - case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_ROW: - // Add to list of horizontal breaks - $hbreaks[] = $coordinates[1]; - - break; - case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_NONE: - default: - // Nothing to do - break; - } + $hbreaks[] = $coordinates[1]; + } + foreach ($this->phpSheet->getColumnBreaks() as $cell => $break) { + // Fetch coordinates + $coordinates = Coordinate::indexesFromString($cell); + $vbreaks[] = $coordinates[0] - 1; } //horizontal page breaks diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index e864ddd175..f4904d2557 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -1006,12 +1006,11 @@ private function writeBreaks(XMLWriter $objWriter, PhpspreadsheetWorksheet $work // Get row and column breaks $aRowBreaks = []; $aColumnBreaks = []; - foreach ($worksheet->getBreaks() as $cell => $breakType) { - if ($breakType == PhpspreadsheetWorksheet::BREAK_ROW) { - $aRowBreaks[] = $cell; - } elseif ($breakType == PhpspreadsheetWorksheet::BREAK_COLUMN) { - $aColumnBreaks[] = $cell; - } + foreach ($worksheet->getRowBreaks() as $cell => $break) { + $aRowBreaks[$cell] = $break; + } + foreach ($worksheet->getColumnBreaks() as $cell => $break) { + $aColumnBreaks[$cell] = $break; } // rowBreaks @@ -1020,12 +1019,16 @@ private function writeBreaks(XMLWriter $objWriter, PhpspreadsheetWorksheet $work $objWriter->writeAttribute('count', (string) count($aRowBreaks)); $objWriter->writeAttribute('manualBreakCount', (string) count($aRowBreaks)); - foreach ($aRowBreaks as $cell) { + foreach ($aRowBreaks as $cell => $break) { $coords = Coordinate::coordinateFromString($cell); $objWriter->startElement('brk'); $objWriter->writeAttribute('id', $coords[1]); $objWriter->writeAttribute('man', '1'); + $rowBreakMax = $break->getMaxColOrRow(); + if ($rowBreakMax >= 0) { + $objWriter->writeAttribute('max', "$rowBreakMax"); + } $objWriter->endElement(); } @@ -1038,11 +1041,11 @@ private function writeBreaks(XMLWriter $objWriter, PhpspreadsheetWorksheet $work $objWriter->writeAttribute('count', (string) count($aColumnBreaks)); $objWriter->writeAttribute('manualBreakCount', (string) count($aColumnBreaks)); - foreach ($aColumnBreaks as $cell) { + foreach ($aColumnBreaks as $cell => $break) { $coords = Coordinate::coordinateFromString($cell); $objWriter->startElement('brk'); - $objWriter->writeAttribute('id', (string) (Coordinate::columnIndexFromString($coords[0]) - 1)); + $objWriter->writeAttribute('id', (string) ((int) $coords[0] - 1)); $objWriter->writeAttribute('man', '1'); $objWriter->endElement(); } diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/RowBreakTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/RowBreakTest.php new file mode 100644 index 0000000000..9bb75f6355 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/RowBreakTest.php @@ -0,0 +1,69 @@ +load($file); + $sheet = $spreadsheet->getActiveSheet(); + $writer = new XlsxWriter($spreadsheet); + $writerWorksheet = new XlsxWriter\Worksheet($writer); + $data = $writerWorksheet->writeWorksheet($sheet, []); + $expected = ''; + self::assertStringContainsString($expected, $data); + $spreadsheet->disconnectWorksheets(); + } + + public function testWriteRowBreakInPrintAreaWithMax(): void + { + // This test specifies max for setBreak and appears correct. + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + for ($row = 1; $row < 60; ++$row) { + for ($column = 'A'; $column !== 'L'; ++$column) { + $cell = $column . $row; + $sheet->getCell($cell)->setValue($cell); + } + } + $sheet->getPageSetup()->setPrintArea('B2:J55'); + $sheet->setBreak('A25', Worksheet::BREAK_ROW, Worksheet::BREAK_ROW_MAX_COLUMN); + $writer = new XlsxWriter($spreadsheet); + $writerWorksheet = new XlsxWriter\Worksheet($writer); + $data = $writerWorksheet->writeWorksheet($sheet, []); + $expected = ''; + self::assertStringContainsString($expected, $data); + $spreadsheet->disconnectWorksheets(); + } + + public function testWriteRowBreakInPrintAreaWithoutMax(): void + { + // This test does not specify max for setBreak, + // and appears incorrect. Probable Excel bug. + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + for ($row = 1; $row < 60; ++$row) { + for ($column = 'A'; $column !== 'L'; ++$column) { + $cell = $column . $row; + $sheet->getCell($cell)->setValue($cell); + } + } + $sheet->getPageSetup()->setPrintArea('B2:J55'); + $sheet->setBreak('A25', Worksheet::BREAK_ROW); + $writer = new XlsxWriter($spreadsheet); + $writerWorksheet = new XlsxWriter\Worksheet($writer); + $data = $writerWorksheet->writeWorksheet($sheet, []); + $expected = ''; + self::assertStringContainsString($expected, $data); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Worksheet/PageBreakTest.php b/tests/PhpSpreadsheetTests/Worksheet/PageBreakTest.php new file mode 100644 index 0000000000..cbeb96d68d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/PageBreakTest.php @@ -0,0 +1,148 @@ +getActiveSheet(); + $sheet->setBreak('A20', Worksheet::BREAK_ROW); + $sheet->setBreak('A40', Worksheet::BREAK_ROW); + $sheet->setBreak('H1', Worksheet::BREAK_COLUMN); + $sheet->setBreak('X1', Worksheet::BREAK_COLUMN); + $breaks1 = $sheet->getBreaks(); + self::assertSame( + [ + 'A20' => Worksheet::BREAK_ROW, + 'A40' => Worksheet::BREAK_ROW, + 'H1' => Worksheet::BREAK_COLUMN, + 'X1' => Worksheet::BREAK_COLUMN, + ], + $breaks1 + ); + $sheet->setBreak('A40', Worksheet::BREAK_NONE); + $sheet->setBreak('H1', Worksheet::BREAK_NONE); + $sheet->setBreak('XX1', Worksheet::BREAK_NONE); + $breaks2 = $sheet->getBreaks(); + self::assertSame( + [ + 'A20' => Worksheet::BREAK_ROW, + 'X1' => Worksheet::BREAK_COLUMN, + ], + $breaks2 + ); + $spreadsheet->disconnectWorksheets(); + } + + public function testBreaksArray(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setBreak([1, 20], Worksheet::BREAK_ROW); + $sheet->setBreak([1, 40], Worksheet::BREAK_ROW); + $sheet->setBreak([8, 1], Worksheet::BREAK_COLUMN); + $sheet->setBreak([24, 1], Worksheet::BREAK_COLUMN); + $breaks1 = $sheet->getBreaks(); + self::assertSame( + [ + 'A20' => Worksheet::BREAK_ROW, + 'A40' => Worksheet::BREAK_ROW, + 'H1' => Worksheet::BREAK_COLUMN, + 'X1' => Worksheet::BREAK_COLUMN, + ], + $breaks1 + ); + $sheet->setBreak([1, 40], Worksheet::BREAK_NONE); + $sheet->setBreak([8, 1], Worksheet::BREAK_NONE); + $sheet->setBreak([50, 1], Worksheet::BREAK_NONE); + $breaks2 = $sheet->getBreaks(); + self::assertSame( + [ + 'A20' => Worksheet::BREAK_ROW, + 'X1' => Worksheet::BREAK_COLUMN, + ], + $breaks2 + ); + $spreadsheet->disconnectWorksheets(); + } + + public function testBreaksCellAddress(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setBreak(new CellAddress('A20'), Worksheet::BREAK_ROW, 16383); + $sheet->setBreak(new CellAddress('A40', $sheet), Worksheet::BREAK_ROW); + $sheet->setBreak(new CellAddress('H1'), Worksheet::BREAK_COLUMN); + $sheet->setBreak(new CellAddress('X1', $sheet), Worksheet::BREAK_COLUMN); + $breaks1 = $sheet->getBreaks(); + self::assertSame( + [ + 'A20' => Worksheet::BREAK_ROW, + 'A40' => Worksheet::BREAK_ROW, + 'H1' => Worksheet::BREAK_COLUMN, + 'X1' => Worksheet::BREAK_COLUMN, + ], + $breaks1 + ); + $sheet->setBreak(new CellAddress('A40'), Worksheet::BREAK_NONE); + $sheet->setBreak(new CellAddress('H1', $sheet), Worksheet::BREAK_NONE); + $sheet->setBreak(new CellAddress('XX1'), Worksheet::BREAK_NONE); + $breaks2 = $sheet->getBreaks(); + self::assertSame( + [ + 'A20' => Worksheet::BREAK_ROW, + 'X1' => Worksheet::BREAK_COLUMN, + ], + $breaks2 + ); + $spreadsheet->disconnectWorksheets(); + } + + public function testBreaksOtherMethods(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setBreak('A20', Worksheet::BREAK_ROW, 16383); + $sheet->setBreak('A40', Worksheet::BREAK_ROW); + $sheet->setBreak('H1', Worksheet::BREAK_COLUMN); + $sheet->setBreak('X1', Worksheet::BREAK_COLUMN); + + $rowBreaks = $sheet->getRowBreaks(); + self::assertCount(2, $rowBreaks); + self::assertSame(Worksheet::BREAK_ROW, $rowBreaks['A20']->getBreakType()); + self::assertSame('A20', $rowBreaks['A20']->getCoordinate()); + self::assertSame(16383, $rowBreaks['A20']->getMaxColOrRow()); + self::assertSame(1, $rowBreaks['A20']->getColumnInt()); + self::assertSame('A', $rowBreaks['A20']->getColumnString()); + self::assertSame(20, $rowBreaks['A20']->getRow()); + self::assertSame(Worksheet::BREAK_ROW, $rowBreaks['A20']->getBreakType()); + self::assertSame('A40', $rowBreaks['A40']->getCoordinate()); + self::assertSame(-1, $rowBreaks['A40']->getMaxColOrRow()); + self::assertSame(1, $rowBreaks['A40']->getColumnInt()); + self::assertSame('A', $rowBreaks['A40']->getColumnString()); + self::assertSame(40, $rowBreaks['A40']->getRow()); + self::assertSame(Worksheet::BREAK_ROW, $rowBreaks['A40']->getBreakType()); + + $columnBreaks = $sheet->getColumnBreaks(); + self::assertCount(2, $columnBreaks); + self::assertSame(Worksheet::BREAK_COLUMN, $columnBreaks['H1']->getBreakType()); + self::assertSame('H1', $columnBreaks['H1']->getCoordinate()); + self::assertSame(8, $columnBreaks['H1']->getColumnInt()); + self::assertSame('H', $columnBreaks['H1']->getColumnString()); + self::assertSame(1, $columnBreaks['H1']->getRow()); + self::assertSame(Worksheet::BREAK_COLUMN, $columnBreaks['H1']->getBreakType()); + self::assertSame('X1', $columnBreaks['X1']->getCoordinate()); + self::assertSame(24, $columnBreaks['X1']->getColumnInt()); + self::assertSame('X', $columnBreaks['X1']->getColumnString()); + self::assertSame(1, $columnBreaks['X1']->getRow()); + self::assertSame(Worksheet::BREAK_COLUMN, $columnBreaks['X1']->getBreakType()); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/data/Reader/XLSX/issue.3143a.xlsx b/tests/data/Reader/XLSX/issue.3143a.xlsx new file mode 100644 index 0000000000..d90eb29f16 Binary files /dev/null and b/tests/data/Reader/XLSX/issue.3143a.xlsx differ