diff --git a/samples/Autofilter/10_Autofilter_selection_1.php b/samples/Autofilter/10_Autofilter_selection_1.php index 9bc47f8b95..ab5aa65a89 100644 --- a/samples/Autofilter/10_Autofilter_selection_1.php +++ b/samples/Autofilter/10_Autofilter_selection_1.php @@ -95,7 +95,7 @@ $spreadsheet->getActiveSheet()->getStyle('D2:D' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2); $spreadsheet->getActiveSheet()->getStyle('E2:F' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_USD_SIMPLE); $spreadsheet->getActiveSheet()->getColumnDimension('F')->setWidth(14); -$spreadsheet->getActiveSheet()->freezePane('A2'); +$spreadsheet->getActiveSheet()->freezePane(0, 1); // Set autofilter range $helper->log('Set autofilter range'); diff --git a/samples/Autofilter/10_Autofilter_selection_2.php b/samples/Autofilter/10_Autofilter_selection_2.php index 9d0afa0f47..a8b29f8fa6 100644 --- a/samples/Autofilter/10_Autofilter_selection_2.php +++ b/samples/Autofilter/10_Autofilter_selection_2.php @@ -95,7 +95,7 @@ $spreadsheet->getActiveSheet()->getStyle('D2:D' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2); $spreadsheet->getActiveSheet()->getStyle('E2:F' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_USD_SIMPLE); $spreadsheet->getActiveSheet()->getColumnDimension('F')->setWidth(14); -$spreadsheet->getActiveSheet()->freezePane('A2'); +$spreadsheet->getActiveSheet()->freezePane(0, 1); // Set autofilter range $helper->log('Set autofilter range'); diff --git a/samples/Autofilter/10_Autofilter_selection_display.php b/samples/Autofilter/10_Autofilter_selection_display.php index 9a6e8c8838..7139da3108 100644 --- a/samples/Autofilter/10_Autofilter_selection_display.php +++ b/samples/Autofilter/10_Autofilter_selection_display.php @@ -95,7 +95,7 @@ $spreadsheet->getActiveSheet()->getStyle('D2:D' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2); $spreadsheet->getActiveSheet()->getStyle('E2:F' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_USD_SIMPLE); $spreadsheet->getActiveSheet()->getColumnDimension('F')->setWidth(14); -$spreadsheet->getActiveSheet()->freezePane('A2'); +$spreadsheet->getActiveSheet()->freezePane(0, 1); // Set autofilter range $helper->log('Set autofilter range'); diff --git a/samples/templates/largeSpreadsheet.php b/samples/templates/largeSpreadsheet.php index 2205bf6f0c..8758a70cc8 100644 --- a/samples/templates/largeSpreadsheet.php +++ b/samples/templates/largeSpreadsheet.php @@ -38,7 +38,7 @@ // Freeze panes $helper->log('Freeze panes'); -$spreadsheet->getActiveSheet()->freezePane('A2'); +$spreadsheet->getActiveSheet()->freezePane(0, 1); // Rows to repeat at top $helper->log('Rows to repeat at top'); diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index ba8ddd2755..8c4d047bb3 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -4488,9 +4488,16 @@ private function readPane() // offset: 2; size: 2; position of horizontal split $py = self::getUInt2d($recordData, 2); + // offset: 4; size: 2; top most visible row in the bottom pane + $rwTop = self::getUInt2d($recordData, 4); + + // offset: 6; size: 2; first visible left column in the right pane + $colLeft = self::getUInt2d($recordData, 6); + if ($this->frozen) { // frozen panes - $this->phpSheet->freezePane(Cell::stringFromColumnIndex($px) . ($py + 1)); + $topLeftCell = Cell::stringFromColumnIndex($colLeft) . ($rwTop + 1); + $this->phpSheet->freezePane($px, $py, $topLeftCell); } // unfrozen panes; split windows; not supported by PhpSpreadsheet core } diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 5d6c574ad6..7cabdad35f 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -720,22 +720,23 @@ public function load($pFilename) $docSheet->setRightToLeft(self::boolean((string) $xmlSheet->sheetViews->sheetView['rightToLeft'])); } if (isset($xmlSheet->sheetViews->sheetView->pane)) { - if (isset($xmlSheet->sheetViews->sheetView->pane['topLeftCell'])) { - $docSheet->freezePane((string) $xmlSheet->sheetViews->sheetView->pane['topLeftCell']); - } else { - $xSplit = 0; - $ySplit = 0; + $xSplit = 0; + $ySplit = 0; + $topLeftCell = null; - if (isset($xmlSheet->sheetViews->sheetView->pane['xSplit'])) { - $xSplit = 1 + (int) ($xmlSheet->sheetViews->sheetView->pane['xSplit']); - } + if (isset($xmlSheet->sheetViews->sheetView->pane['xSplit'])) { + $xSplit = (int) ($xmlSheet->sheetViews->sheetView->pane['xSplit']); + } - if (isset($xmlSheet->sheetViews->sheetView->pane['ySplit'])) { - $ySplit = 1 + (int) ($xmlSheet->sheetViews->sheetView->pane['ySplit']); - } + if (isset($xmlSheet->sheetViews->sheetView->pane['ySplit'])) { + $ySplit = (int) ($xmlSheet->sheetViews->sheetView->pane['ySplit']); + } - $docSheet->freezePaneByColumnAndRow($xSplit, $ySplit); + if (isset($xmlSheet->sheetViews->sheetView->pane['topLeftCell'])) { + $topLeftCell = (string) $xmlSheet->sheetViews->sheetView->pane['topLeftCell']; } + + $docSheet->freezePane($xSplit, $ySplit, $topLeftCell); } if (isset($xmlSheet->sheetViews->sheetView->selection)) { diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index a79447275c..cf9b60acd0 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -583,8 +583,18 @@ public function insertNewBefore($pBefore, $pNumCols, $pNumRows, Worksheet $pShee } // Update worksheet: freeze pane - if ($pSheet->getFreezePane() != '') { - $pSheet->freezePane($this->updateCellReference($pSheet->getFreezePane(), $pBefore, $pNumCols, $pNumRows)); + if ($pSheet->getFreezePane()) { + $splitCell = Cell::stringFromColumnIndex($pSheet->getColSplit()) . ($pSheet->getRowSplit() + 1); + $topLeftCell = $pSheet->getTopLeftCell(); + + $colSplit = $rowSplit = 0; + list($colSplit, $rowSplit) = Cell::coordinateFromString($this->updateCellReference($splitCell, $pBefore, $pNumCols, $pNumRows)); + + $colSplit = Cell::columnIndexFromString($colSplit) - 1; + $rowSplit = $rowSplit - 1; + $topLeftCell = $this->updateCellReference($topLeftCell, $pBefore, $pNumCols, $pNumRows); + + $pSheet->freezePane($colSplit, $rowSplit, $topLeftCell); } // Page setup diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 10a49c30e4..48a7f8bc41 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -198,11 +198,32 @@ class Worksheet implements IComparable private $autoFilter; /** - * Freeze pane. + * Horizontal position of the split. + * + * @var int + */ + private $colSplit = 0; + + /** + * Vertical position of the split. + * + * @var int + */ + private $rowSplit = 0; + + /** + * Default position of the right bottom pane. * * @var string */ - private $freezePane = ''; + private $topLeftCell = ''; + + /** + * Pane frozen ? + * + * @var bool + */ + private $freezePane = false; /** * Show gridlines? @@ -1962,9 +1983,9 @@ public function removeAutoFilter() } /** - * Get Freeze Pane. + * Freeze pane ? * - * @return string + * @return bool */ public function getFreezePane() { @@ -1974,53 +1995,83 @@ public function getFreezePane() /** * Freeze Pane. * - * @param string $pCell Cell (i.e. A2) - * Examples: - * A2 will freeze the rows above cell A2 (i.e row 1) - * B1 will freeze the columns to the left of cell B1 (i.e column A) - * B2 will freeze the rows above and to the left of cell A2 - * (i.e row 1 and column A) + * @param int $colSplit Horizontal position of the split + * @param int $rowSplit Vertical position of the split + * @param string $topLeftCell default position of the right bottom pane * * @throws Exception * * @return Worksheet */ - public function freezePane($pCell) + public function freezePane($colSplit, $rowSplit, $topLeftCell = null) { - // Uppercase coordinate - $pCell = strtoupper($pCell); - if (strpos($pCell, ':') === false && strpos($pCell, ',') === false) { - $this->freezePane = $pCell; - } else { + if (!isset($topLeftCell)) { + $topLeftCell = Cell::stringFromColumnIndex($colSplit) . ($rowSplit + 1); + } + + if (!(strpos($topLeftCell, ':') === false && strpos($topLeftCell, ',') === false)) { throw new Exception('Freeze pane can not be set on a range of cells.'); } + if (!is_int($colSplit) || !is_int($rowSplit)) { + throw new Exception('Split values should be integer to create freeze pane.'); + } + + // If colSplit and rowSplit are equal to zero the freeze pane is removed + if ($colSplit == 0 && $rowSplit == 0) { + $this->freezePane = false; + + return $this; + } + + $this->freezePane = true; + + $this->colSplit = $colSplit; + $this->rowSplit = $rowSplit; + + $this->topLeftCell = $topLeftCell; + return $this; } /** - * Freeze Pane by using numeric cell coordinates. + * Unfreeze Pane. * - * @param int $pColumn Numeric column coordinate of the cell (A = 0) - * @param int $pRow Numeric row coordinate of the cell + * @return Worksheet + */ + public function unfreezePane() + { + return $this->freezePane(0, 0); + } + + /** + * Get horizontal position of the split. * - * @throws Exception + * @return int + */ + public function getColSplit() + { + return $this->colSplit; + } + + /** + * Get vertical position of the split. * - * @return Worksheet + * @return int */ - public function freezePaneByColumnAndRow($pColumn, $pRow) + public function getRowSplit() { - return $this->freezePane(Cell::stringFromColumnIndex($pColumn) . $pRow); + return $this->rowSplit; } /** - * Unfreeze Pane. + * Get the default position of the right bottom pane. * - * @return Worksheet + * @return int */ - public function unfreezePane() + public function getTopLeftCell() { - return $this->freezePane(''); + return $this->topLeftCell; } /** diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index 47b0ff9395..7c972c297e 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -1589,10 +1589,13 @@ private function writeRangeProtection() private function writePanes() { $panes = []; - if ($freezePane = $this->phpSheet->getFreezePane()) { - list($column, $row) = Cell::coordinateFromString($freezePane); - $panes[0] = $row - 1; - $panes[1] = Cell::columnIndexFromString($column) - 1; + if ($this->phpSheet->getFreezePane()) { + $panes[0] = $this->phpSheet->getColSplit(); + $panes[1] = $this->phpSheet->getRowSplit(); + list($leftMostColumn, $topRow) = Cell::coordinateFromString($this->phpSheet->getTopLeftCell()); + //Coordinates are zero-based in xls files + $panes[2] = ($topRow - 1); + $panes[3] = (Cell::columnIndexFromString($leftMostColumn) - 1); } else { // thaw panes return; diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 3a2ceabef2..7e87ebc97e 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -243,31 +243,29 @@ private function writeSheetViews(XMLWriter $objWriter, PhpspreadsheetWorksheet $ // Pane $pane = ''; - $topLeftCell = $pSheet->getFreezePane(); - if (($topLeftCell != '') && ($topLeftCell != 'A1')) { - $activeCell = $topLeftCell; - // Calculate freeze coordinates - $xSplit = $ySplit = 0; + if ($pSheet->getFreezePane()) { + $xSplit = $pSheet->getColSplit(); + $ySplit = $pSheet->getRowSplit(); - list($xSplit, $ySplit) = Cell::coordinateFromString($topLeftCell); - $xSplit = Cell::columnIndexFromString($xSplit); + $topLeftCell = $pSheet->getTopLeftCell(); + $activeCell = $topLeftCell; // pane $pane = 'topRight'; $objWriter->startElement('pane'); - if ($xSplit > 1) { - $objWriter->writeAttribute('xSplit', $xSplit - 1); + if ($xSplit > 0) { + $objWriter->writeAttribute('xSplit', $xSplit); } - if ($ySplit > 1) { - $objWriter->writeAttribute('ySplit', $ySplit - 1); - $pane = ($xSplit > 1) ? 'bottomRight' : 'bottomLeft'; + if ($ySplit > 0) { + $objWriter->writeAttribute('ySplit', $ySplit); + $pane = ($xSplit > 0) ? 'bottomRight' : 'bottomLeft'; } $objWriter->writeAttribute('topLeftCell', $topLeftCell); $objWriter->writeAttribute('activePane', $pane); $objWriter->writeAttribute('state', 'frozen'); $objWriter->endElement(); - if (($xSplit > 1) && ($ySplit > 1)) { + if (($xSplit > 0) && ($ySplit > 0)) { // Write additional selections if more than two panes (ie both an X and a Y split) $objWriter->startElement('selection'); $objWriter->writeAttribute('pane', 'topRight'); diff --git a/tests/PhpSpreadsheetTests/Reader/XlsTest.php b/tests/PhpSpreadsheetTests/Reader/XlsTest.php new file mode 100644 index 0000000000..f397e11ca2 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/XlsTest.php @@ -0,0 +1,40 @@ +getActiveSheet(); + $active->freezePane($colSplit, $rowSplit, $topLeftCell); + + $writer = new WriterXls($spreadsheet); + $writer->save($filename); + + // Read written file + $reader = new ReaderXls(); + $reloadedSpreadsheet = $reader->load($filename); + $reloadedActive = $reloadedSpreadsheet->getActiveSheet(); + $actualColSplit = $reloadedActive->getColSplit(); + $actualRowSplit = $reloadedActive->getRowSplit(); + $actualTopLeftCell = $reloadedActive->getTopLeftCell(); + + self::assertSame($colSplit, $actualColSplit, 'should be able to set horizontal split'); + self::assertSame($rowSplit, $actualRowSplit, 'should be able to set vertical split'); + self::assertSame($topLeftCell, $actualTopLeftCell, 'should be able to set the top left cell'); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php index fd14714dfe..a0f230898b 100644 --- a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php +++ b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php @@ -2,7 +2,10 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader; -use PhpOffice\PhpSpreadsheet\Reader\Xlsx; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx as ReaderXlsx; +use PhpOffice\PhpSpreadsheet\Shared\File; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx as WriterXlsx; use PHPUnit\Framework\TestCase; class XlsxTest extends TestCase @@ -13,7 +16,30 @@ class XlsxTest extends TestCase public function testLoadXlsxWithoutCellReference() { $filename = './data/Reader/XLSX/without_cell_reference.xlsx'; - $reader = new Xlsx(); + $reader = new ReaderXlsx(); $reader->load($filename); } + + public function testFreezePane() + { + $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet'); + $colSplit = 1; + $rowSplit = 1; + $topLeftCell = 'B4'; + $spreadsheet = new Spreadsheet(); + $active = $spreadsheet->getActiveSheet(); + $active->freezePane($colSplit, $rowSplit, $topLeftCell); + $writer = new WriterXlsx($spreadsheet); + $writer->save($filename); + // Read written file + $reader = new ReaderXlsx(); + $reloadedSpreadsheet = $reader->load($filename); + $reloadedActive = $reloadedSpreadsheet->getActiveSheet(); + $actualColSplit = $reloadedActive->getColSplit(); + $actualRowSplit = $reloadedActive->getRowSplit(); + $actualTopLeftCell = $reloadedActive->getTopLeftCell(); + self::assertSame($colSplit, $actualColSplit, 'should be able to set horizontal split'); + self::assertSame($rowSplit, $actualRowSplit, 'should be able to set vertical split'); + self::assertSame($topLeftCell, $actualTopLeftCell, 'should be able to set the top left cell'); + } }