diff --git a/components/fpspreadsheet/examples/excel8demo/excel8read.lpi b/components/fpspreadsheet/examples/excel8demo/excel8read.lpi index 43f419d9e..6dea16dd6 100644 --- a/components/fpspreadsheet/examples/excel8demo/excel8read.lpi +++ b/components/fpspreadsheet/examples/excel8demo/excel8read.lpi @@ -1,4 +1,4 @@ - + @@ -43,7 +43,7 @@ - + diff --git a/components/fpspreadsheet/examples/excel8demo/excel8write.lpr b/components/fpspreadsheet/examples/excel8demo/excel8write.lpr index ca5b5348f..a651b2de6 100644 --- a/components/fpspreadsheet/examples/excel8demo/excel8write.lpr +++ b/components/fpspreadsheet/examples/excel8demo/excel8write.lpr @@ -141,13 +141,13 @@ begin MyWorksheet.WriteDateTime(37, 1, number, nfTimeInterval); // Set width of columns 1 and 5 - lCol.Width := 100; //mm + lCol.Width := 30; MyWorksheet.WriteColInfo(1, lCol); - lCol.Width := 50; + lCol.Width := 5; MyWorksheet.WriteColInfo(5, lCol); // Set height of rows 5 and 6 - lRow.Height := 10; // mm + lRow.Height := 10; MyWorksheet.WriteRowInfo(5, lRow); lRow.Height := 5; MyWorksheet.WriteRowInfo(6, lRow); diff --git a/components/fpspreadsheet/examples/excel8demo/test.xls b/components/fpspreadsheet/examples/excel8demo/test.xls index a9d03e207..301e0ee97 100644 Binary files a/components/fpspreadsheet/examples/excel8demo/test.xls and b/components/fpspreadsheet/examples/excel8demo/test.xls differ diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index 2e461cb39..362826acd 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -247,7 +247,7 @@ type TCol = record Col: Byte; - Width: Single; // in milimeters + Width: Single; // in "characters". Excel uses the with of char "0" in 1st font end; PCol = ^TCol; @@ -309,6 +309,7 @@ type procedure WriteColInfo(ACol: Cardinal; AData: TCol); { Properties } property Cells: TAVLTree read FCells; + property Cols: TIndexedAVLTree read FCols; end; { TsWorkbook } @@ -1325,6 +1326,7 @@ begin inherited Destroy; end; + {@@ Helper method for determining the spreadsheet type from the file type extension diff --git a/components/fpspreadsheet/tests/formattests.pas b/components/fpspreadsheet/tests/formattests.pas index b7f5f0fdc..ada997335 100644 --- a/components/fpspreadsheet/tests/formattests.pas +++ b/components/fpspreadsheet/tests/formattests.pas @@ -28,6 +28,8 @@ var SollDateTimeFormats: array[0..9] of TsNumberFormat; SollDateTimeFormatStrings: array[0..9] of String; + SollColWidths: array[0..1] of Single; + procedure InitSollFmtData; type @@ -45,6 +47,8 @@ type procedure TestWriteReadNumberFormats; // Repeat with date/times procedure TestWriteReadDateTimeFormats; + // Test column width + procedure TestWriteReadColWidths; end; implementation @@ -125,6 +129,10 @@ begin SollDateTimeStrings[i, 8] := FormatDateTime('nn:ss', SollDateTimes[i]); SollDateTimeStrings[i, 9] := TimeIntervalToString(SollDateTimes[i]); end; + + // Column width + SollColWidths[0] := 20; // characters based on width of "0" + SollColWidths[1] := 40; end; { TSpreadWriteReadFormatTests } @@ -222,6 +230,51 @@ begin DeleteFile(TempFile); end; +procedure TSpreadWriteReadFormatTests.TestWriteReadColWidths; +var + MyWorksheet: TsWorksheet; + MyWorkbook: TsWorkbook; + ActualColWidth: Single; + Col: Integer; + lpCol: PCol; + lCol: TCol; + TempFile: string; //write xls/xml to this file and read back from it +begin + TempFile:=GetTempFileName; + {// Not needed: use workbook.writetofile with overwrite=true + if fileexists(TempFile) then + DeleteFile(TempFile); + } + // Write out all test values + MyWorkbook := TsWorkbook.Create; + MyWorkSheet:= MyWorkBook.AddWorksheet(FmtNumbersSheet); + for Col := Low(SollColWidths) to High(SollColWidths) do begin + lCol.Width := SollColWidths[Col]; + MyWorksheet.WriteColInfo(Col, lCol); + end; + MyWorkBook.WriteToFile(TempFile,sfExcel8,true); + MyWorkbook.Free; + + // Open the spreadsheet, as biff8 + MyWorkbook := TsWorkbook.Create; + MyWorkbook.ReadFromFile(TempFile, sfExcel8); + MyWorksheet:=GetWorksheetByName(MyWorkBook, FmtNumbersSheet); + if MyWorksheet=nil then + fail('Error in test code. Failed to get named worksheet'); + for Col := Low(SollColWidths) to High(SollColWidths) do begin + lpCol := MyWorksheet.GetCol(Col); + if lpCol = nil then + fail('Error in test code. Failed to return saved column width'); + ActualColWidth := lpCol^.Width; + CheckEquals(SollColWidths[Col], ActualColWidth, 'Test saved colwidth mismatch column '+ColNotation(MyWorkSheet,Col)); + end; + // Finalization + MyWorkbook.Free; + + DeleteFile(TempFile); +end; + + initialization RegisterTest(TSpreadWriteReadFormatTests); InitSollFmtData; diff --git a/components/fpspreadsheet/tests/spreadtestgui.lpi b/components/fpspreadsheet/tests/spreadtestgui.lpi index 50e93ecce..84b9f1466 100644 --- a/components/fpspreadsheet/tests/spreadtestgui.lpi +++ b/components/fpspreadsheet/tests/spreadtestgui.lpi @@ -142,7 +142,7 @@ - + @@ -157,8 +157,10 @@ - + + + diff --git a/components/fpspreadsheet/tests/testsutility.pas b/components/fpspreadsheet/tests/testsutility.pas index c592a7869..e564bf198 100644 --- a/components/fpspreadsheet/tests/testsutility.pas +++ b/components/fpspreadsheet/tests/testsutility.pas @@ -25,6 +25,9 @@ const // Returns an A.. notation based on sheet, row, optional column (e.g. A1). function CellNotation(WorkSheet: TsWorksheet; Row: integer; Column: integer=0): string; +// Returns an A notation of column based on sheed and column +function ColNotation(WorkSheet: TsWorksheet; Column:Integer): String; + // Note: using this function instead of GetWorkSheetByName for compatibility with // older fpspreadsheet versions that don't have that function function GetWorksheetByName(AWorkBook: TsWorkBook; AName: String): TsWorksheet; @@ -70,6 +73,25 @@ begin result:=WorkSheet.Name+'!'+inttostr(Column+1)+':'+inttostr(Row+1) end; +function ColNotation(WorkSheet: TsWorksheet; Column:Integer): String; +begin + if not Assigned(Worksheet) then + Result := 'ColNotation: error getting worksheet.' + else begin + if Column < 26 then + Result := Worksheet.Name + '!' + char(Column+65) + else + if Column < 26*26 then + Result := Worksheet.Name + '!' + char(Column div 26 + 65) + char(Column mod 26 + 65) + else + if Column < 26*26*26 then + Result := Worksheet.Name + '!' + char(Column div (26*26) + 65) + + char(Column mod (26*26) div 26 + 65) + + char(Column mod (26*26*26) + 65) + else + Result := 'ColNotation: At most three digits supported.'; + end; +end; end. diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index 12995d471..530e01e25 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -117,6 +117,8 @@ type // procedure ReadCodepage in xlscommon // procedure ReadDateMode in xlscommon procedure ReadFont(const AStream: TStream); + // Read col info + procedure ReadColInfo(const AStream: TStream); public constructor Create; override; destructor Destroy; override; @@ -140,6 +142,7 @@ type procedure WriteXFFieldsForFormattingStyles(AStream: TStream); protected procedure AddDefaultFormats(); override; + procedure WriteColInfo(AStream: TStream; ASheet: TsWorksheet; ACol: PCol); public // constructor Create; // destructor Destroy; override; @@ -176,6 +179,7 @@ const { Excel record IDs } INT_EXCEL_ID_BOF = $0809; INT_EXCEL_ID_BOUNDSHEET = $0085; // Renamed to SHEET in the latest OpenOffice docs + INT_EXCEL_ID_COLINFO = $007D; INT_EXCEL_ID_COUNTRY = $008C; INT_EXCEL_ID_EOF = $000A; INT_EXCEL_ID_DIMENSIONS = $0200; @@ -554,7 +558,9 @@ var MyData: TMemoryStream; CurrentPos: Int64; Boundsheets: array of Int64; - i, len: Integer; + sheet: TsWorksheet; + i, j, len: Integer; + col: PCol; begin { Write workbook globals } @@ -643,6 +649,8 @@ begin for i := 0 to AData.GetWorksheetCount - 1 do begin + sheet := AData.GetWorksheetByIndex(i); + { First goes back and writes the position of the BOF of the sheet on the respective BOUNDSHEET record } CurrentPos := AStream.Position; @@ -654,11 +662,16 @@ begin WriteIndex(AStream); - WriteDimensions(AStream, AData.GetWorksheetByIndex(i)); + for j := 0 to sheet.Cols.Count-1 do begin + col := PCol(sheet.Cols[j]); + WriteColInfo(AStream, sheet, col); + end; + + WriteDimensions(AStream, sheet); WriteWindow2(AStream, True); - WriteCellsToStream(AStream, AData.GetWorksheetByIndex(i).Cells); + WriteCellsToStream(AStream, sheet.Cells); WriteEOF(AStream); end; @@ -747,6 +760,32 @@ begin AStream.WriteBuffer(WideStringToLE(WideSheetName)[1], Len * Sizeof(WideChar)); end; +{******************************************************************* +* TsSpreadBIFF8Writer.WriteColumn () +* +* DESCRIPTION: Writes a COLINFO record +*******************************************************************} +procedure TsSpreadBIFF8Writer.WriteColInfo(AStream: TStream; + ASheet: TsWorkSheet; ACol: PCol); +var + w: Integer; +begin + if Assigned(ACol) and Assigned(ASheet) then begin + { BIFF Record header } + AStream.WriteWord(WordToLE(INT_EXCEL_ID_COLINFO)); // BIFF record header + AStream.WriteWord(WordToLE(12)); // Record size + AStream.WriteWord(WordToLE(ACol^.Col)); // start column + AStream.WriteWord(WordToLE(ACol^.Col)); // end column + { calculate width to be in units of 1/256 of pixel width of character "0" } + w := round(ACol^.Width * 256); + AStream.WriteWord(WordToLE(w)); // write width + AStream.WriteWord(15); // XF record, ignored + AStream.WriteWord(0); // option flags, ignored + AStream.WriteWord(0); // "not used" + end; +end; + + {******************************************************************* * TsSpreadBIFF8Writer.WriteDateTime () * @@ -1868,6 +1907,7 @@ begin INT_EXCEL_ID_RK: ReadRKValue(AStream); INT_EXCEL_ID_MULRK: ReadMulRKValues(AStream); INT_EXCEL_ID_LABELSST:ReadLabelSST(AStream); //BIFF8 only + INT_EXCEL_ID_COLINFO: ReadColInfo(AStream); INT_EXCEL_ID_BOF: ; INT_EXCEL_ID_EOF: SectionEOF := True; else @@ -2384,6 +2424,25 @@ begin lFontName := ReadString(AStream, Len); end; +procedure TsSpreadBiff8Reader.ReadColInfo(const AStream: TStream); +var + c, c1, c2: Cardinal; + w: Word; + col: TCol; +begin + // read column start and end index of column range + c1 := WordLEToN(AStream.ReadWord); + c2 := WordLEToN(AStream.ReadWord); + // read col width in 1/256 of the width of "0" character + w := WordLEToN(AStream.ReadWord); + // calculate width in units of "characters" + col.Width := w / 256; + // assign width to columns + for c := c1 to c2 do + FWorksheet.WriteColInfo(c, col); +end; + + {******************************************************************* * Initialization section *