From 2d29205b576ba1e3ec9de5be7633d91d89b7bf12 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Wed, 24 Jul 2019 15:50:43 +0000 Subject: [PATCH] fpsrpeadsheet: Excel xls format (BIFF8, BIFF5 and BIFF2) supports PageBreaks now. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7071 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../fpspreadsheet/source/common/xlsbiff2.pas | 4 + .../fpspreadsheet/source/common/xlsbiff5.pas | 7 +- .../fpspreadsheet/source/common/xlsbiff8.pas | 4 + .../fpspreadsheet/source/common/xlscommon.pas | 163 ++++++++++++++++-- .../fpspreadsheet/tests/colrowtests.pas | 113 ++++++++++++ 5 files changed, 274 insertions(+), 17 deletions(-) diff --git a/components/fpspreadsheet/source/common/xlsbiff2.pas b/components/fpspreadsheet/source/common/xlsbiff2.pas index ee9411b9a..12bbfe37a 100644 --- a/components/fpspreadsheet/source/common/xlsbiff2.pas +++ b/components/fpspreadsheet/source/common/xlsbiff2.pas @@ -669,6 +669,7 @@ begin INT_EXCEL_ID_FORMAT : ReadFormat(AStream); INT_EXCEL_ID_FORMULA : ReadFormula(AStream); INT_EXCEL_ID_HEADER : ReadHeaderFooter(AStream, true); + INT_EXCEL_ID_HORZPAGEBREAK : ReadHorizontalPageBreaks(AStream, FWorksheet); INT_EXCEL_ID_INTEGER : ReadInteger(AStream); INT_EXCEL_ID_IXFE : ReadIXFE(AStream); INT_EXCEL_ID_LABEL : ReadLabel(AStream); @@ -687,6 +688,7 @@ begin INT_EXCEL_ID_STRING : ReadStringRecord(AStream); INT_EXCEL_ID_TOPMARGIN : ReadMargin(AStream, 2); INT_EXCEL_ID_DEFROWHEIGHT : ReadDefRowHeight(AStream); + INT_EXCEL_ID_VERTPAGEBREAK : ReadVerticalPageBreaks(AStream, FWorksheet); INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream); INT_EXCEL_ID_WINDOWPROTECT : ReadWindowProtect(AStream); INT_EXCEL_ID_XF : ReadXF(AStream); @@ -1660,6 +1662,8 @@ begin WritePrintHeaders(AStream); WritePrintGridLines(AStream); WriteDefaultRowHeight(AStream, FWorksheet); + WriteHorizontalPageBreaks(AStream, FWorksheet); + WriteVerticalPageBreaks(AStream, FWorksheet); WriteFonts(AStream); // Page settings block diff --git a/components/fpspreadsheet/source/common/xlsbiff5.pas b/components/fpspreadsheet/source/common/xlsbiff5.pas index 56cb9c963..4818c0c2a 100644 --- a/components/fpspreadsheet/source/common/xlsbiff5.pas +++ b/components/fpspreadsheet/source/common/xlsbiff5.pas @@ -558,6 +558,7 @@ begin INT_EXCEL_ID_FOOTER : ReadHeaderFooter(AStream, false); INT_EXCEL_ID_FORMULA : ReadFormula(AStream); INT_EXCEL_ID_HEADER : ReadHeaderFooter(AStream, true); + INT_EXCEL_ID_HORZPAGEBREAK : ReadHorizontalPageBreaks(AStream, FWorksheet); INT_EXCEL_ID_HCENTER : ReadHCENTER(AStream); INT_EXCEL_ID_LABEL : ReadLabel(AStream); INT_EXCEL_ID_LEFTMARGIN : ReadMargin(AStream, 0); @@ -584,6 +585,7 @@ begin INT_EXCEL_ID_SCL : ReadSCLRecord(AStream); INT_EXCEL_ID_STRING : ReadStringRecord(AStream); INT_EXCEL_ID_VCENTER : ReadVCENTER(AStream); + INT_EXCEL_ID_VERTPAGEBREAK : ReadVerticalPageBreaks(AStream, FWorksheet); INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream); {$IFDEF FPSPREADDEBUG} // Only write out if debugging @@ -1272,6 +1274,8 @@ begin WriteSheetPR(AStream); // Page settings block + WriteHorizontalPageBreaks(AStream, FWorksheet); + WriteVerticalPageBreaks(AStream, FWorksheet); WriteHeaderFooter(AStream, true); WriteHeaderFooter(AStream, false); WriteHCenter(AStream); @@ -1298,7 +1302,7 @@ begin WriteDimensions(AStream, FWorksheet); WriteWindow2(AStream, FWorksheet); WriteSCLRecord(AStream, FWorksheet); - WritePane(AStream, FWorksheet, true, pane); // true for "is BIFF5 or BIFF8" + WritePane(AStream, FWorksheet, true, pane); // true: "is BIFF5 or BIFF8" WriteSelection(AStream, FWorksheet, pane); //WriteRows(AStream, sheet); @@ -1313,7 +1317,6 @@ begin end; { Cleanup } - SetLength(sheetPos, 0); end; diff --git a/components/fpspreadsheet/source/common/xlsbiff8.pas b/components/fpspreadsheet/source/common/xlsbiff8.pas index 34c231ca3..cb812f95f 100644 --- a/components/fpspreadsheet/source/common/xlsbiff8.pas +++ b/components/fpspreadsheet/source/common/xlsbiff8.pas @@ -1218,6 +1218,7 @@ begin INT_EXCEL_ID_FORMULA : ReadFormula(AStream); INT_EXCEL_ID_HCENTER : ReadHCENTER(AStream); INT_EXCEL_ID_HEADER : ReadHeaderFooter(AStream, true); + INT_EXCEL_ID_HORZPAGEBREAK : ReadHorizontalPageBreaks(AStream, FWorksheet); INT_EXCEL_ID_HLINKTOOLTIP : ReadHyperlinkToolTip(AStream); INT_EXCEL_ID_HYPERLINK : ReadHyperlink(AStream); INT_EXCEL_ID_LABEL : ReadLabel(AStream); @@ -1258,6 +1259,7 @@ begin INT_EXCEL_ID_TOPMARGIN : ReadMargin(AStream, 2); INT_EXCEL_ID_TXO : ReadTXO(AStream); INT_EXCEL_ID_VCENTER : ReadVCENTER(AStream); + INT_EXCEL_ID_VERTPAGEBREAK : ReadVerticalPageBreaks(AStream, FWorksheet); INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream); else // nothing @@ -2802,6 +2804,8 @@ begin WriteSheetPR(AStream); // Page setting block + WriteHorizontalPageBreaks(AStream, FWorksheet); + WriteVerticalPageBreaks(AStream, FWorksheet); WriteHeaderFooter(AStream, true); WriteHeaderFooter(AStream, false); WriteHCenter(AStream); diff --git a/components/fpspreadsheet/source/common/xlscommon.pas b/components/fpspreadsheet/source/common/xlscommon.pas index baf7d1f68..ee4061d0a 100644 --- a/components/fpspreadsheet/source/common/xlscommon.pas +++ b/components/fpspreadsheet/source/common/xlscommon.pas @@ -25,6 +25,8 @@ const INT_EXCEL_ID_FOOTER = $0015; INT_EXCEL_ID_EXTERNSHEET = $0017; INT_EXCEL_ID_WINDOWPROTECT = $0019; + INT_EXCEL_ID_VERTPAGEBREAK = $001A; + INT_EXCEL_ID_HORZPAGEBREAK = $001B; INT_EXCEL_ID_NOTE = $001C; INT_EXCEL_ID_SELECTION = $001D; INT_EXCEL_ID_DATEMODE = $0022; @@ -491,6 +493,7 @@ type procedure ReadFormula(AStream: TStream); override; procedure ReadHCENTER(AStream: TStream); procedure ReadHeaderFooter(AStream: TStream; AIsHeader: Boolean); virtual; + procedure ReadHorizontalPageBreaks(AStream: TStream; AWorksheet: TsBasicWorksheet); procedure ReadMargin(AStream: TStream; AMargin: Integer); // Read multiple blank cells procedure ReadMulBlank(AStream: TStream); @@ -548,12 +551,12 @@ type procedure ReadSELECTION(AStream: TStream); procedure ReadSharedFormula(AStream: TStream); procedure ReadSHEETPR(AStream: TStream); - // Helper function for reading a string with 8-bit length function ReadString_8bitLen(AStream: TStream): String; virtual; // Read STRING record (result of string formula) procedure ReadStringRecord(AStream: TStream); virtual; procedure ReadVCENTER(AStream: TStream); + procedure ReadVerticalPageBreaks(AStream: TStream; AWorksheet: TsBasicWorksheet); // Read WINDOW2 record (gridlines, sheet headers) procedure ReadWindow2(AStream: TStream); virtual; // Read WINDOWPROTECT record @@ -642,6 +645,7 @@ type ACell: PCell); override; procedure WriteHCenter(AStream: TStream); procedure WriteHeaderFooter(AStream: TStream; AIsHeader: Boolean); virtual; + procedure WriteHorizontalPageBreaks(AStream: TStream; ASheet: TsBasicWorksheet); // Writes out page margin for printing procedure WriteMARGIN(AStream: TStream; AMargin: Integer); // Writes out all FORMAT records @@ -703,6 +707,7 @@ type procedure WriteSheetPR(AStream: TStream); procedure WriteSTRINGRecord(AStream: TStream; AString: String); virtual; procedure WriteVCenter(AStream: TStream); + procedure WriteVerticalPageBreaks(AStream: TStream; ASheet: TsBasicWorksheet); // Writes cell content received by workbook in OnNeedCellData event procedure WriteVirtualCells(AStream: TStream; ASheet: TsBasicWorksheet); // Writes out a WINDOW1 record @@ -1704,13 +1709,6 @@ begin end else lCol.FormatIndex := 0; - { Get current value of column options to keep already set PageBreak option } - col := TsWorksheet(FWorksheet).FindCol(c); - if col <> nil then - lCol.Options := col^.Options - else - lCol.Options := []; - { Read column visibility } flags := WordLEToN(AStream.ReadWord); if flags and $0001 = $0001 then @@ -1719,9 +1717,18 @@ begin Exclude(lCol.Options, croHidden); { Assign width and format to columns, but only if different from defaults } - if (lCol.FormatIndex > 0) or (lCol.ColWidthType = cwtCustom) or (lCol.Options <> []) then - for c := c1 to c2 do + if (lCol.FormatIndex > 0) or (lCol.ColWidthType = cwtCustom) or (croHidden in lCol.Options) then + for c := c1 to c2 do begin + // PageBreak flag is stored in the Column options, but BIFF has read it + // earlier from the VERTICALPAGEBREAKS record. We check whether a column + // record already exists and copy PageBreak flag from it. + col := sheet.FindCol(c); + Exclude(lCol.Options, croPageBreak); + if (col <> nil) and (croPageBreak in col^.Options) then + Include(lCol.Options, croPageBreak); + // Apply column record to worksheet sheet.WriteColInfo(c, lCol); + end; end; {@@ ---------------------------------------------------------------------------- @@ -2066,6 +2073,33 @@ begin end; end; +{@@ ---------------------------------------------------------------------------- + Reads the HORIZONALPAGEBREAKS record. It contains the row indexes above which + a manual page break is executed during printing. +-------------------------------------------------------------------------------} +procedure TsSpreadBIFFReader.ReadHorizontalPageBreaks(AStream: TStream; + AWorksheet: TsBasicWorksheet); +type + TRowBreakRec = packed record + RowIndex: Word; // Index to first row below the page break + FirstCol: Word; // Index to first column of this page break + LastCol: Word; // Index to last column of this page break + end; +var + n, r: Word; + i: Integer; + rec: TRowBreakRec; +begin + // Number of following row indexes + n := WordLEToN(AStream.ReadWord); + + for i := 1 to n do begin + AStream.ReadBuffer(rec, SizeOf(rec)); + r := WordLEToN(rec.RowIndex); + TsWorksheet(AWorksheet).AddPageBreakToRow(r); + end; +end; + {@@ ---------------------------------------------------------------------------- Reads a page margin of the current worksheet (for printing). The margin is identified by the parameter "AMargin" (0=left, 1=right, 2=top, 3=bottom) @@ -2597,12 +2631,13 @@ begin // Find the format with ID xf lRow.FormatIndex := XFToFormatIndex(xf); - { Get current value of row Options to keep Pagebreak already written } + lRow.Options := []; + { Get current value of row Options to keep PageBreak already read + from HORIZONTALPAGEBREAKS record } row := TsWorksheet(FWorksheet).FindRow(rowRec.RowIndex); - if row <> nil then - lRow.Options := row^.Options - else - lRow.Options := []; + if (row <> nil) and (croPageBreak in row^.Options) then + Include(lRow.Options, croPageBreak); + { Row visibility } if rowRec.Flags and $00000020 <> 0 then Include(lRow.Options, croHidden) @@ -3437,6 +3472,33 @@ begin Options := Options + [poVertCentered]; end; +{@@ ---------------------------------------------------------------------------- + Reads the VERTICALPAGEBREAKS record. It contains the column indexes before + which a manual page break is executed during printing. +-------------------------------------------------------------------------------} +procedure TsSpreadBIFFReader.ReadVerticalPageBreaks(AStream: TStream; + AWorksheet: TsBasicWorksheet); +type + TColBreakRec = packed record + ColIndex: Word; // Index to first column following the page break + FirstRow: Word; // Index to first row of this page break + LastRow: Word; // Index to last row of this page break + end; +var + n, c: Word; + i: Integer; + rec: TColBreakRec; +begin + // Number of following column index structures + n := WordLEToN(AStream.ReadWord); + + for i := 1 to n do begin + AStream.ReadBuffer(rec, SizeOf(rec)); + c := WordLEToN(rec.ColIndex); + TsWorksheet(AWorksheet).AddPageBreakToCol(c); + end; +end; + {@@ ---------------------------------------------------------------------------- Reads the WINDOW2 record containing information like "show grid lines", "show sheet headers", "panes are frozen", etc. @@ -4491,6 +4553,41 @@ begin AStream.WriteBuffer(s[1], len * SizeOf(AnsiChar)); end; +{@@ ---------------------------------------------------------------------------- + Writes an Excel HORIZONTALPAGEBREAKS record which contains the indexes of the + rows before which a manual page break is executed during printing +-------------------------------------------------------------------------------} +procedure TsSpreadBIFFWriter.WriteHorizontalPageBreaks(AStream: TStream; + ASheet: TsBasicWorksheet); +var + i, n: Integer; + row: PRow; + sheet: TsWorksheet absolute ASheet; +begin + n := 0; + for i := 0 to sheet.Rows.Count - 1 do + if (croPageBreak in PRow(sheet.Rows[i])^.Options) then inc(n); + if n = 0 then + exit; + + // Biff header + WriteBiffHeader(AStream, INT_EXCEL_ID_HORZPAGEBREAK, SizeOf(Word)*(1 + 3*n)); + + // Number of following row indexes + AStream.WriteWord(WordToLE(n)); + + for i := 0 to sheet.Rows.Count - 1 do begin + row := PRow(sheet.Rows[i]); + if (croPageBreak in row^.Options) then begin + // Index to first row below the page break + aStream.WriteWord(WordToLE(row^.Row)); + // Index to first column of this page break + AStream.WriteWord(0); + // Index to last column of this page break + AStream.WriteWord(WordToLE($00FF)); + end; + end; +end; {@@ ---------------------------------------------------------------------------- Writes a 64-bit floating point NUMBER record. @@ -5654,6 +5751,42 @@ begin AStream.WriteWord(WordToLE(w)); end; +{@@ ---------------------------------------------------------------------------- + Writes an Excel VERTICALPAGEBREAKS record which contains the indexed of the + columns before which a manual page break is executed during printing +-------------------------------------------------------------------------------} +procedure TsSpreadBIFFWriter.WriteVerticalPageBreaks(AStream: TStream; + ASheet: TsBasicWorksheet); +var + i, n: Integer; + col: PCol; + sheet: TsWorksheet absolute ASheet; +begin + n := 0; + for i := 0 to sheet.Cols.Count - 1 do + if (croPageBreak in PCol(sheet.Cols[i])^.Options) then inc(n); + if n = 0 then + exit; + + // BIFF header + WriteBiffHeader(AStream, INT_EXCEL_ID_VERTPAGEBREAK, SizeOf(Word)*(1 + 3*n)); + + // Number of following column indexes + AStream.WriteWord(WordToLE(n)); + + for i := 0 to sheet.Cols.Count - 1 do begin + col := PCol(sheet.Cols[i]); + if (croPageBreak in col^.Options) then begin + // Index to first column following the page break + AStream.WriteWord(WordToLE(col^.Col)); + // Index to first row of this page break + AStream.WriteWord(0); + // Index to last row of this page break + AStream.WriteWord(WordToLE($FFFF)); + end; + end; +end; + procedure TsSpreadBIFFWriter.WriteVirtualCells(AStream: TStream; ASheet: TsBasicWorksheet); var diff --git a/components/fpspreadsheet/tests/colrowtests.pas b/components/fpspreadsheet/tests/colrowtests.pas index d729c3af2..c9342eb05 100644 --- a/components/fpspreadsheet/tests/colrowtests.pas +++ b/components/fpspreadsheet/tests/colrowtests.pas @@ -283,6 +283,19 @@ type procedure TestWriteRead_ShowRow_ODS; // Add a page break column + procedure TestWriteRead_AddPageBreak_Col_BIFF2; + procedure TestWriteRead_AddPageBreak_Row_BIFF2; + + procedure TestWriteRead_AddPageBreak_Col_BIFF5; + procedure TestWriteRead_AddPageBreak_ColHidden_BIFF5; + procedure TestWriteRead_AddPageBreak_Row_BIFF5; + procedure TestWriteRead_AddPageBreak_RowHidden_BIFF5; + + procedure TestWriteRead_AddPageBreak_Col_BIFF8; + procedure TestWriteRead_AddPageBreak_ColHidden_BIFF8; + procedure TestWriteRead_AddPageBreak_Row_BIFF8; + procedure TestWriteRead_AddPageBreak_RowHidden_BIFF8; + procedure TestWriteRead_AddPageBreak_Col_OOXML; procedure TestWriteRead_AddPageBreak_ColHidden_OOXML; procedure TestWriteRead_AddPageBreak_Row_OOXML; @@ -294,6 +307,19 @@ type procedure TestWriteRead_AddPageBreak_RowHidden_XML; // Remove a page break column + procedure TestWriteRead_RemovePageBreak_Col_BIFF2; + procedure TestWriteRead_RemovePageBreak_Row_BIFF2; + + procedure TestWriteRead_RemovePageBreak_Col_BIFF5; + procedure TestWriteRead_RemovePageBreak_ColHidden_BIFF5; + procedure TestWriteRead_RemovePageBreak_Row_BIFF5; + procedure TestWriteRead_RemovePageBreak_RowHidden_BIFF5; + + procedure TestWriteRead_RemovePageBreak_Col_BIFF8; + procedure TestWriteRead_RemovePageBreak_ColHidden_BIFF8; + procedure TestWriteRead_RemovePageBreak_Row_BIFF8; + procedure TestWriteRead_RemovePageBreak_RowHidden_BIFF8; + procedure TestWriteRead_RemovePageBreak_Col_OOXML; procedure TestWriteRead_RemovePageBreak_ColHidden_OOXML; procedure TestWriteRead_RemovePageBreak_Row_OOXML; @@ -2142,6 +2168,7 @@ begin book := TsWorkbook.Create; try sheet := book.AddWorksheet('Test'); + sheet.WriteNumber(0, 0, 1.0); sheet.AddPageBreakToCol(COL_INDEX); if Hidden then @@ -2230,6 +2257,49 @@ begin end; end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_AddPageBreak_Col_BIFF2; +begin + TestWriteRead_AddPageBreak_Col(false, sfExcel2); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_AddPageBreak_Row_BIFF2; +begin + TestWriteRead_AddPageBreak_Row(false, sfExcel2); +end; + +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_AddPageBreak_Col_BIFF5; +begin + TestWriteRead_AddPageBreak_Col(false, sfExcel5); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_AddPageBreak_Row_BIFF5; +begin + TestWriteRead_AddPageBreak_Row(false, sfExcel5); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_AddPageBreak_ColHidden_BIFF5; +begin + TestWriteRead_AddPageBreak_Col(true, sfExcel5); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_AddPageBreak_RowHidden_BIFF5; +begin + TestWriteRead_AddPageBreak_Row(true, sfExcel5); +end; + +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_AddPageBreak_Col_BIFF8; +begin + TestWriteRead_AddPageBreak_Col(false, sfExcel8); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_AddPageBreak_Row_BIFF8; +begin + TestWriteRead_AddPageBreak_Row(false, sfExcel8); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_AddPageBreak_ColHidden_BIFF8; +begin + TestWriteRead_AddPageBreak_Col(true, sfExcel8); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_AddPageBreak_RowHidden_BIFF8; +begin + TestWriteRead_AddPageBreak_Row(true, sfExcel8); +end; + procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_AddPageBreak_Col_OOXML; begin TestWriteRead_AddPageBreak_Col(false, sfOOXML); @@ -2414,6 +2484,49 @@ begin end; end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_RemovePageBreak_Col_BIFF2; +begin + TestWriteRead_RemovePageBreak_Col(false, sfExcel2); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_RemovePageBreak_Row_BIFF2; +begin + TestWriteRead_RemovePageBreak_Row(false, sfExcel2); +end; + +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_RemovePageBreak_Col_BIFF5; +begin + TestWriteRead_RemovePageBreak_Col(false, sfExcel5); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_RemovePageBreak_Row_BIFF5; +begin + TestWriteRead_RemovePageBreak_Row(false, sfExcel5); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_RemovePageBreak_ColHidden_BIFF5; +begin + TestWriteRead_RemovePageBreak_Col(true, sfExcel5); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_RemovePageBreak_RowHidden_BIFF5; +begin + TestWriteRead_RemovePageBreak_Row(true, sfExcel5); +end; + +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_RemovePageBreak_Col_BIFF8; +begin + TestWriteRead_RemovePageBreak_Col(false, sfExcel8); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_RemovePageBreak_Row_BIFF8; +begin + TestWriteRead_RemovePageBreak_Row(false, sfExcel8); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_RemovePageBreak_ColHidden_BIFF8; +begin + TestWriteRead_RemovePageBreak_Col(true, sfExcel8); +end; +procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_RemovePageBreak_RowHidden_BIFF8; +begin + TestWriteRead_RemovePageBreak_Row(true, sfExcel8); +end; + procedure TSpreadWriteRead_ColRow_Tests.TestWriteRead_RemovePageBreak_Col_OOXML; begin TestWriteRead_RemovePageBreak_Col(false, sfOOXML);