From 545bd7ed0f45846916f135aad78200d6f70a96e9 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Thu, 28 May 2015 20:08:24 +0000 Subject: [PATCH] fpspreadsheet: Major reconstructor of color management: no more palettes now, use direct rgb colors instead. May break existing code - sorry! Update all demos and unit tests (passed). git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@4156 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../examples/other/demo_recursive_calc.pas | 2 +- .../read_write/excel5demo/excel5write.lpr | 17 +- .../read_write/excel8demo/excel8write.lpr | 1 - .../wikitabledemo/wikitablewrite.lpr | 4 +- .../examples/visual/spready/mainform.lfm | 23 +- .../examples/visual/spready/mainform.pas | 71 ++- .../examples/visual/spready/spready.lpi | 2 +- .../examples/visual/wikitablemaker/wtmain.lfm | 1 + .../examples/visual/wikitablemaker/wtmain.pas | 30 +- components/fpspreadsheet/fpsactions.pas | 8 +- .../fpspreadsheet/fpsheaderfooterparser.pas | 6 +- components/fpspreadsheet/fpsopendocument.pas | 96 ++-- components/fpspreadsheet/fpspalette.pas | 414 ++++++++++++++++++ components/fpspreadsheet/fpspreadsheet.pas | 211 ++------- .../fpspreadsheet/fpspreadsheetctrls.pas | 69 +-- .../fpspreadsheet/fpspreadsheetgrid.pas | 16 +- components/fpspreadsheet/fpsreaderwriter.pas | 6 +- components/fpspreadsheet/fpsstrings.pas | 60 +++ components/fpspreadsheet/fpstypes.pas | 215 +++++---- components/fpspreadsheet/fpsutils.pas | 177 ++++++-- components/fpspreadsheet/fpsvisualutils.pas | 10 +- .../fpspreadsheet/laz_fpspreadsheet.lpk | 6 +- .../fpspreadsheet/laz_fpspreadsheet.pas | 2 +- components/fpspreadsheet/tests/colortests.pas | 250 ++++++----- components/fpspreadsheet/tests/errortests.pas | 57 ++- .../fpspreadsheet/tests/formattests.pas | 137 +++--- .../fpspreadsheet/tests/manualtests.pas | 81 +++- components/fpspreadsheet/wikitable.pas | 12 +- components/fpspreadsheet/xlsbiff2.pas | 14 +- components/fpspreadsheet/xlsbiff5.pas | 132 +++--- components/fpspreadsheet/xlsbiff8.pas | 213 +++++---- components/fpspreadsheet/xlscommon.pas | 168 +++++-- components/fpspreadsheet/xlsxooxml.pas | 210 +++------ 33 files changed, 1696 insertions(+), 1025 deletions(-) create mode 100644 components/fpspreadsheet/fpspalette.pas diff --git a/components/fpspreadsheet/examples/other/demo_recursive_calc.pas b/components/fpspreadsheet/examples/other/demo_recursive_calc.pas index ce565af72..f53f2aa20 100644 --- a/components/fpspreadsheet/examples/other/demo_recursive_calc.pas +++ b/components/fpspreadsheet/examples/other/demo_recursive_calc.pas @@ -49,7 +49,7 @@ begin writeln('Finished.'); writeln; writeln('Please open "'+OutputFile+'" in "fpsgrid".'); - writeLn('It should show calculation results in cells B1 and B2.'); + writeLn('It must show correct calculation results in cells B1 and B2.'); finally workbook.Free; end; diff --git a/components/fpspreadsheet/examples/read_write/excel5demo/excel5write.lpr b/components/fpspreadsheet/examples/read_write/excel5demo/excel5write.lpr index 92805ca70..646c16c06 100644 --- a/components/fpspreadsheet/examples/read_write/excel5demo/excel5write.lpr +++ b/components/fpspreadsheet/examples/read_write/excel5demo/excel5write.lpr @@ -10,7 +10,7 @@ program excel5write; {$mode delphi}{$H+} uses - Classes, SysUtils, fpsTypes, fpspreadsheet, xlsbiff5; + Classes, SysUtils, fpsTypes, fpSpreadsheet, fpsPalette, fpsUtils, xlsbiff5; const Str_First = 'First'; @@ -28,6 +28,7 @@ var i, r: Integer; number: Double; fmt: string; + palette: TsPalette; begin MyDir := ExtractFilePath(ParamStr(0)); @@ -359,10 +360,16 @@ begin // Creates a new worksheet MyWorksheet := MyWorkbook.AddWorksheet('Colors'); - for i:=0 to MyWorkbook.GetPaletteSize-1 do begin - MyWorksheet.WriteBlank(i, 0); - Myworksheet.WriteBackgroundColor(i, 0, TsColor(i)); - MyWorksheet.WriteUTF8Text(i, 1, MyWorkbook.GetColorName(i)); + palette := TsPalette.Create; + try + palette.UseColors(PALETTE_BIFF5); // This stores the colors of BIFF5 files in the local palette + for i:=0 to palette.Count-1 do begin + MyWorksheet.WriteBlank(i, 0); + Myworksheet.WriteBackgroundColor(i, 0, palette[i]); + MyWorksheet.WriteUTF8Text(i, 1, GetColorName(palette[i])); + end; + finally + palette.Free; end; // Save the spreadsheet to a file diff --git a/components/fpspreadsheet/examples/read_write/excel8demo/excel8write.lpr b/components/fpspreadsheet/examples/read_write/excel8demo/excel8write.lpr index 96da10b56..2a2ab14ec 100644 --- a/components/fpspreadsheet/examples/read_write/excel8demo/excel8write.lpr +++ b/components/fpspreadsheet/examples/read_write/excel8demo/excel8write.lpr @@ -36,7 +36,6 @@ begin // Create the spreadsheet MyWorkbook := TsWorkbook.Create; MyWorkbook.SetDefaultFont('Calibri', 9); - MyWorkbook.UsePalette(@PALETTE_BIFF8, Length(PALETTE_BIFF8)); MyWorkbook.FormatSettings.CurrencyFormat := 2; MyWorkbook.FormatSettings.NegCurrFormat := 14; MyWorkbook.Options := MyWorkbook.Options + [boCalcBeforeSaving]; diff --git a/components/fpspreadsheet/examples/read_write/wikitabledemo/wikitablewrite.lpr b/components/fpspreadsheet/examples/read_write/wikitabledemo/wikitablewrite.lpr index 9af2dcd12..ce3940f06 100644 --- a/components/fpspreadsheet/examples/read_write/wikitabledemo/wikitablewrite.lpr +++ b/components/fpspreadsheet/examples/read_write/wikitabledemo/wikitablewrite.lpr @@ -135,8 +135,8 @@ begin MyWorksheet.WriteNumber(row, 0, row); MyWorksheet.WriteUTF8Text(row, 1, 'RGB background color:'); - MyWorksheet.WriteUTF8Text(row, 2, 'color #FF77C3'); - MyWorksheet.WriteBackgroundColor(row, 2, MyWorkbook.AddColorToPalette($C377FF)); + MyWorksheet.WriteUTF8Text(row, 2, 'color #FF77C3'); // HTML colors are big-endian + MyWorksheet.WriteBackgroundColor(row, 2, $C377FF); // fps colors are little-endian inc(row); MyWorksheet.WriteNumber(row, 0, row); diff --git a/components/fpspreadsheet/examples/visual/spready/mainform.lfm b/components/fpspreadsheet/examples/visual/spready/mainform.lfm index 1f53e80ec..04ef93744 100644 --- a/components/fpspreadsheet/examples/visual/spready/mainform.lfm +++ b/components/fpspreadsheet/examples/visual/spready/mainform.lfm @@ -9,6 +9,7 @@ object MainFrm: TMainFrm Menu = MainMenu OnActivate = FormActivate OnCreate = FormCreate + OnDestroy = FormDestroy ShowHint = True LCLVersion = '1.5' object Panel1: TPanel @@ -425,7 +426,7 @@ object MainFrm: TMainFrm end end object InspectorSplitter: TSplitter - Left = 639 + Left = 591 Height = 453 Top = 84 Width = 5 @@ -433,10 +434,10 @@ object MainFrm: TMainFrm ResizeAnchor = akRight end object InspectorPageControl: TPageControl - Left = 644 + Left = 596 Height = 453 Top = 84 - Width = 241 + Width = 289 ActivePage = PgCellValue Align = alRight TabIndex = 0 @@ -445,18 +446,20 @@ object MainFrm: TMainFrm object PgCellValue: TTabSheet Caption = 'Cell value' ClientHeight = 425 - ClientWidth = 233 + ClientWidth = 281 object CellInspector: TValueListEditor Left = 0 Height = 425 Top = 0 - Width = 233 + Width = 281 Align = alClient FixedCols = 0 + MouseWheelOption = mwGrid RowCount = 15 TabOrder = 0 TitleStyle = tsNative - DisplayOptions = [doColumnTitles, doAutoColResize] + DisplayOptions = [doColumnTitles] + Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goColSizing, goAlwaysShowEditor, goThumbTracking] Strings.Strings = ( 'Row=' 'Column=' @@ -478,8 +481,8 @@ object MainFrm: TMainFrm '' ) ColWidths = ( - 114 - 115 + 138 + 139 ) end end @@ -494,7 +497,7 @@ object MainFrm: TMainFrm Left = 0 Height = 453 Top = 84 - Width = 639 + Width = 591 OnChange = TabControlChange Align = alClient TabOrder = 3 @@ -502,7 +505,7 @@ object MainFrm: TMainFrm Left = 2 Height = 448 Top = 3 - Width = 635 + Width = 587 FrozenCols = 0 FrozenRows = 0 ReadFormulas = False diff --git a/components/fpspreadsheet/examples/visual/spready/mainform.pas b/components/fpspreadsheet/examples/visual/spready/mainform.pas index e182f585c..5f91945bf 100644 --- a/components/fpspreadsheet/examples/visual/spready/mainform.pas +++ b/components/fpspreadsheet/examples/visual/spready/mainform.pas @@ -8,7 +8,7 @@ uses Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls, Menus, ExtCtrls, ComCtrls, ActnList, Spin, Grids, ColorBox, ValEdit, - fpstypes, fpspreadsheetgrid, fpspreadsheet, + fpstypes, fpspalette, fpspreadsheetgrid, fpspreadsheet, {%H-}fpsallformats; type @@ -325,6 +325,7 @@ type procedure FontSizeComboBoxSelect(Sender: TObject); procedure FormActivate(Sender: TObject); procedure FormCreate(Sender: TObject); + procedure FormDestroy(Sender: TObject); procedure InspectorPageControlChange(Sender: TObject); procedure MemoFormulaEditingDone(Sender: TObject); procedure TabControlChange(Sender: TObject); @@ -334,6 +335,7 @@ type private FCopiedFormat: TCell; + FPalette: TsPalette; function EditComment(ACaption: String; var AText: String): Boolean; procedure LoadFile(const AFileName: String); @@ -928,9 +930,9 @@ begin if WorksheetGrid.Workbook <> nil then begin Items.Clear; Items.AddObject('no fill', TObject(PtrInt(clNone))); - for i:=0 to WorksheetGrid.Workbook.GetPaletteSize-1 do begin - clr := WorksheetGrid.Workbook.GetPaletteColor(i); - clrName := WorksheetGrid.Workbook.GetColorName(i); + for i:=0 to FPalette.Count-1 do begin + clr := FPalette[i]; + clrName := GetColorName(clr); Items.AddObject(Format('%d: %s', [i, clrName]), TObject(PtrInt(clr))); end; end; @@ -947,7 +949,7 @@ begin if CbBackgroundColor.ItemIndex <= 0 then with WorksheetGrid do BackgroundColors[Selection] := scNotDefined else - with WorksheetGrid do BackgroundColors[Selection] := CbBackgroundColor.ItemIndex - 1; + with WorksheetGrid do BackgroundColors[Selection] := PtrInt(CbBackgroundColor.Items.Objects[CbBackgroundColor.ItemIndex]); end; procedure TMainFrm.CbHeaderStyleChange(Sender: TObject); @@ -1087,12 +1089,20 @@ begin FontSizeCombobox.DropDownCount := DROPDOWN_COUNT; CbBackgroundColor.DropDownCount := DROPDOWN_COUNT; + FPalette := TsPalette.Create; + FPalette.AddExcelColors; + // Initialize a new empty workbook AcNewExecute(nil); ActiveControl := WorksheetGrid; end; +procedure TMainFrm.FormDestroy(Sender: TObject); +begin + FPalette.Free; +end; + procedure TMainFrm.InspectorPageControlChange(Sender: TObject); begin CellInspector.Parent := InspectorPageControl.ActivePage; @@ -1184,13 +1194,14 @@ end; procedure TMainFrm.UpdateBackgroundColorIndex; var - sClr: TsColor; + clr: TsColor; begin - with WorksheetGrid do sClr := BackgroundColors[Selection]; - if sClr = scNotDefined then + with WorksheetGrid do + clr := BackgroundColors[Selection]; + if (clr = scNotDefined) or (clr = scTransparent) then CbBackgroundColor.ItemIndex := 0 // no fill else - CbBackgroundColor.ItemIndex := sClr + 1; + CbBackgroundColor.ItemIndex := CbBackgroundColor.Items.IndexOfObject(TObject(PtrInt(clr))); end; procedure TMainFrm.UpdateHorAlignmentActions; @@ -1214,6 +1225,7 @@ var cb: TsCellBorder; r1,r2,c1,c2: Cardinal; fmt: TsCellFormat; + nfparams: TsNumFormatParams; begin with CellInspector do begin @@ -1223,10 +1235,10 @@ begin if InspectorPageControl.ActivePage = PgCellValue then begin if ACell=nil - then Strings.Add('Row=') + then Strings.Add(Format('Row=%d', [WorksheetGrid.GetWorksheetRow(WorksheetGrid.Row)])) else Strings.Add(Format('Row=%d', [ACell^.Row])); if ACell=nil - then Strings.Add('Column=') + then Strings.Add(Format('Column=%d', [WorksheetGrid.GetWorksheetCol(WorksheetGrid.Col)])) else Strings.Add(Format('Column=%d', [ACell^.Col])); if ACell=nil then Strings.Add('ContentType=') @@ -1308,29 +1320,40 @@ begin else Strings.Add(Format('BorderStyles[%s]=%s, %s', [ GetEnumName(TypeInfo(TsCellBorder), ord(cb)), - GetEnumName(TypeInfo(TsLineStyle), ord(fmt.BorderStyles[cbEast].LineStyle)), - WorksheetGrid.Workbook.GetColorName(fmt.BorderStyles[cbEast].Color) + GetEnumName(TypeInfo(TsLineStyle), ord(fmt.BorderStyles[cb].LineStyle)), + GetColorName(fmt.BorderStyles[cb].Color) ])); if (ACell=nil) or not (uffBackground in fmt.UsedformattingFields) then Strings.Add('BackgroundColor=') - else Strings.Add(Format('BackgroundColor=%d (%s)', [ + else Strings.Add(Format('BackgroundColor=$%8x (%s)', [ fmt.Background.BgColor, - WorksheetGrid.Workbook.GetColorName(fmt.Background.BgColor) + GetColorName(fmt.Background.BgColor) ])); if (ACell=nil) or not (uffNumberFormat in fmt.UsedFormattingFields) then Strings.Add('NumberFormat=') - else Strings.Add(Format('NumberFormat=%s', [GetEnumName(TypeInfo(TsNumberFormat), ord(fmt.NumberFormat))])); + else begin + nfparams := WorksheetGrid.Workbook.GetNumberFormat(fmt.NumberFormatIndex); + if nfparams = nil then + begin + Strings.Add('NumberFormat=General'); + Strings.Add('NumberFormatStr='); + end else + begin + Strings.Add(Format('NumberFormat=%s', [GetEnumName(TypeInfo(TsNumberFormat), ord(nfparams.NumFormat))])); + Strings.Add(Format('NumberFormatStr=%s', [nfparams.NumFormatStr])); + end; + end; + { if (ACell=nil) or not (uffNumberFormat in fmt.UsedFormattingFields) then Strings.Add('NumberFormatStr=') else Strings.Add('NumberFormatStr=' + fmt.NumberFormatStr); - if not WorksheetGrid.Worksheet.IsMerged(ACell) then - Strings.Add('Merged range=') - else - begin - WorksheetGrid.Worksheet.FindMergedRange(ACell, r1, c1, r2, c2); - Strings.Add('Merged range=' + GetCellRangeString(r1, c1, r2, c2)); - end; - + } + if not WorksheetGrid.Worksheet.IsMerged(ACell) + then Strings.Add('Merged range=') + else begin + WorksheetGrid.Worksheet.FindMergedRange(ACell, r1, c1, r2, c2); + Strings.Add('Merged range=' + GetCellRangeString(r1, c1, r2, c2)); + end; end; end; end; diff --git a/components/fpspreadsheet/examples/visual/spready/spready.lpi b/components/fpspreadsheet/examples/visual/spready/spready.lpi index 10e09fd9f..9e0af4b47 100644 --- a/components/fpspreadsheet/examples/visual/spready/spready.lpi +++ b/components/fpspreadsheet/examples/visual/spready/spready.lpi @@ -103,7 +103,6 @@ - @@ -128,6 +127,7 @@ + diff --git a/components/fpspreadsheet/examples/visual/wikitablemaker/wtmain.lfm b/components/fpspreadsheet/examples/visual/wikitablemaker/wtmain.lfm index 6777607f1..fecd89c90 100644 --- a/components/fpspreadsheet/examples/visual/wikitablemaker/wtmain.lfm +++ b/components/fpspreadsheet/examples/visual/wikitablemaker/wtmain.lfm @@ -9,6 +9,7 @@ object MainFrm: TMainFrm Menu = MainMenu OnActivate = FormActivate OnCreate = FormCreate + OnDestroy = FormDestroy ShowHint = True LCLVersion = '1.5' object MainToolBar: TToolBar diff --git a/components/fpspreadsheet/examples/visual/wikitablemaker/wtmain.pas b/components/fpspreadsheet/examples/visual/wikitablemaker/wtmain.pas index 02e33ba66..0399d6d86 100644 --- a/components/fpspreadsheet/examples/visual/wikitablemaker/wtmain.pas +++ b/components/fpspreadsheet/examples/visual/wikitablemaker/wtmain.pas @@ -9,7 +9,7 @@ uses StdCtrls, Menus, ExtCtrls, ComCtrls, ActnList, Grids, ColorBox, SynEdit, SynEditHighlighter, SynHighlighterHTML, SynHighlighterMulti, SynHighlighterCss, SynGutterCodeFolding, fpspreadsheetgrid, - fpstypes, fpspreadsheet, fpsallformats; + fpstypes, fpspalette, fpspreadsheet, fpsallformats; type @@ -187,6 +187,7 @@ type procedure FontSizeComboBoxSelect(Sender: TObject); procedure FormActivate(Sender: TObject); procedure FormCreate(Sender: TObject); + procedure FormDestroy(Sender: TObject); procedure PageControlChange(Sender: TObject); procedure TabControlChange(Sender: TObject); procedure WorksheetGridSelection(Sender: TObject; aCol, aRow: Integer); @@ -194,6 +195,7 @@ type WorksheetGrid: TsWorksheetGrid; FCopiedFormat: TCell; FHighlighter: TSynCustomHighlighter; + FPalette: TsPalette; procedure LoadFile(const AFileName: String); procedure SetupBackgroundColorBox; procedure UpdateBackgroundColorIndex; @@ -543,20 +545,22 @@ begin if (WorksheetGrid <> nil) and (WorksheetGrid.Workbook <> nil) then begin Items.Clear; Items.AddObject('no fill', TObject(PtrInt(clNone))); - for i:=0 to WorksheetGrid.Workbook.GetPaletteSize-1 do begin - clr := WorksheetGrid.Workbook.GetPaletteColor(i); - clrName := WorksheetGrid.Workbook.GetColorName(i); + for i:=0 to FPalette.Count-1 do begin + clr := FPalette[i]; + clrName := GetColorName(clr); Items.AddObject(Format('%d: %s', [i, clrName]), TObject(PtrInt(clr))); end; end; end; procedure TMainFrm.CbBackgroundColorSelect(Sender: TObject); +var + clr: TsColor; begin if CbBackgroundColor.ItemIndex <= 0 then with WorksheetGrid do BackgroundColors[Selection] := scNotDefined else - with WorksheetGrid do BackgroundColors[Selection] := CbBackgroundColor.ItemIndex - 1; + with WorksheetGrid do BackgroundColors[Selection] := PtrInt(CbBackgroundColor.Items.Objects[CbBackgroundColor.ItemIndex]); end; procedure TMainFrm.FontComboBoxSelect(Sender: TObject); @@ -637,6 +641,9 @@ begin CbBackgroundColor.ColorRectWidth := CbBackgroundColor.ItemHeight - 6; // to get a square box... {$ENDIF} + FPalette := TsPalette.Create; + FPalette.AddExcelColors; + // Initialize a new empty workbook AcNewExecute(nil); @@ -645,6 +652,11 @@ begin ActiveControl := WorksheetGrid; end; +procedure TMainFrm.FormDestroy(Sender: TObject); +begin + FPalette.Free; +end; + procedure TMainFrm.PageControlChange(Sender: TObject); var stream: TMemoryStream; @@ -726,13 +738,13 @@ end; procedure TMainFrm.UpdateBackgroundColorIndex; var - sClr: TsColor; + clr: TsColor; begin - with WorksheetGrid do sClr := BackgroundColors[Selection]; - if sClr = scNotDefined then + with WorksheetGrid do clr := BackgroundColors[Selection]; + if (clr = scNotDefined) or (clr = scTransparent) then CbBackgroundColor.ItemIndex := 0 // no fill else - CbBackgroundColor.ItemIndex := sClr + 1; + CbBackgroundColor.ItemIndex := CbBackgroundColor.Items.IndexOfObject(TObject(PtrInt(clr))); end; procedure TMainFrm.UpdateHorAlignmentActions; diff --git a/components/fpspreadsheet/fpsactions.pas b/components/fpspreadsheet/fpsactions.pas index 0dc403637..7a3a5f40d 100644 --- a/components/fpspreadsheet/fpsactions.pas +++ b/components/fpspreadsheet/fpsactions.pas @@ -1078,14 +1078,14 @@ procedure TsActionBorder.ApplyStyle(AWorkbook: TsWorkbook; out ABorderStyle: TsCellBorderStyle); begin ABorderStyle.LineStyle := FLineStyle; - ABorderStyle.Color := AWorkbook.GetPaletteColor(ABorderStyle.Color); + ABorderStyle.Color := ABorderStyle.Color and $00FFFFFF; end; procedure TsActionBorder.ExtractStyle(AWorkbook: TsWorkbook; ABorderStyle: TsCellBorderStyle); begin FLineStyle := ABorderStyle.LineStyle; - Color := AWorkbook.AddColorToPalette(ABorderStyle.Color); + Color := ColorToRGB(ABorderStyle.Color); end; constructor TsActionBorders.Create; @@ -1575,14 +1575,14 @@ end; procedure TsBackgroundColorDialogAction.DoAccept; begin - FBackgroundColor := Workbook.AddColorToPalette(TsColorValue(Dialog.Color)); + FBackgroundColor := ColorToRgb(Dialog.Color); inherited; end; procedure TsBackgroundColorDialogAction.DoBeforeExecute; begin inherited; - Dialog.Color := Workbook.GetPaletteColor(FBackgroundColor); + Dialog.Color := FBackgroundColor and $00FFFFFF; end; procedure TsBackgroundColorDialogAction.ExtractFromCell(ACell: PCell); diff --git a/components/fpspreadsheet/fpsheaderfooterparser.pas b/components/fpspreadsheet/fpsheaderfooterparser.pas index 50468334a..8a967a208 100644 --- a/components/fpspreadsheet/fpsheaderfooterparser.pas +++ b/components/fpspreadsheet/fpsheaderfooterparser.pas @@ -20,11 +20,11 @@ type FontName: String; Size: Double; Style: TsHeaderFooterFontStyles; - Color: TsColorValue; + Color: TsColor; constructor Create; overload; constructor Create(AFont: TsFont); overload; constructor Create(AFontName: String; ASize: Double; - AStyle: TsHeaderFooterFontStyles; AColor: TsColorValue); overload; + AStyle: TsHeaderFooterFontStyles; AColor: TsColor); overload; procedure Assign(AFont: TObject); end; @@ -99,7 +99,7 @@ begin end; constructor TsHeaderFooterFont.Create(AFontName: String; ASize: Double; - AStyle: TsHeaderFooterFontStyles; AColor: TsColorValue); + AStyle: TsHeaderFooterFontStyles; AColor: TsColor); begin FontName := AFontName; Size := ASize; diff --git a/components/fpspreadsheet/fpsopendocument.pas b/components/fpspreadsheet/fpsopendocument.pas index 726506987..a5c62f0b7 100755 --- a/components/fpspreadsheet/fpsopendocument.pas +++ b/components/fpspreadsheet/fpsopendocument.pas @@ -108,7 +108,7 @@ type function ReadFont(ANode: TDOMnode; APreferredIndex: Integer = -1): Integer; procedure ReadHeaderFooterFont(ANode: TDOMNode; var AFontName: String; var AFontSize: Double; var AFontStyle: TsHeaderFooterFontStyles; - var AFontColor: TsColorValue); + var AFontColor: TsColor); function ReadHeaderFooterText(ANode: TDOMNode): String; procedure ReadRowsAndCells(ATableNode: TDOMNode); procedure ReadRowStyle(AStyleNode: TDOMNode); @@ -561,7 +561,7 @@ var n: Integer; el, nEl: Integer; ns: Integer; - clr: TsColorvalue; + clr: TsColor; mask: String; timeIntervalStr: String; styleMapStr: String; @@ -607,7 +607,7 @@ begin case Elements[el].Token of nftColor: begin - clr := FWorkbook.GetPaletteColor(Elements[el].IntValue); + clr := TsColor(Elements[el].IntValue); Result := Result + ''; end; @@ -679,7 +679,9 @@ begin // Mixed fraction if nfkFraction in Kind then begin - int := Elements[el].IntValue; + if Elements[el].Token = nftIntOptDigit + then int := 0 + else int := Elements[el].IntValue; inc(el); while (el < nel) and not (Elements[el].Token in [nftFracNumZeroDigit, nftFracNumOptDigit, nftFracNumSpaceDigit]) @@ -874,8 +876,6 @@ begin FMasterPageList := TFPList.Create; FHeaderFooterFontList := TObjectList.Create; // frees objects - // Set up the default palette in order to have the default color names correct. - Workbook.UseDefaultPalette; // Initial base date in case it won't be read from file FDateMode := dm1899; end; @@ -1129,7 +1129,7 @@ var fntName: String; fntSize: Double; fntStyle: TsHeaderFooterFontStyles; - fntColor: TsColorValue; + fntColor: TsColor; begin if not Assigned(AStylesNode) then exit; @@ -1683,7 +1683,7 @@ begin s := GetAttrValue(ANode, 'fo:color'); if s <> '' then - fntColor := FWorkbook.AddColorToPalette(HTMLColorStrToColor(s)) + fntColor := HTMLColorStrToColor(s) else fntColor := FWorkbook.GetDefaultFont.Color; @@ -1694,10 +1694,6 @@ begin end else if (APreferredIndex > -1) then begin - { --- wp: No more missing font #4 now !!! - if (APreferredIndex = 4) then - raise Exception.Create('Cannot replace font #4'); - } FWorkbook.ReplaceFont(APreferredIndex, fntName, fntSize, fntStyles, fntColor); Result := APreferredIndex; end else @@ -1938,7 +1934,7 @@ end; procedure TsSpreadOpenDocReader.ReadHeaderFooterFont(ANode: TDOMNode; var AFontName: String; var AFontSize: Double; - var AFontStyle: TsHeaderFooterFontStyles; var AFontColor: TsColorValue); + var AFontStyle: TsHeaderFooterFontStyles; var AFontColor: TsColor); var s: String; begin @@ -2241,7 +2237,7 @@ procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode); grouping: Boolean; nex: Integer; cs: String; - color: TsColorValue; + color: TsColor; hasColor: Boolean; idx: Integer; begin @@ -2280,14 +2276,14 @@ procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode); begin nf := nfFraction; s := GetAttrValue(node, 'number:min-integer-digits'); - if s <> '' then fracInt := StrToInt(s) else fracInt := 0; + if s <> '' then fracInt := StrToInt(s) else fracInt := -1; s := GetAttrValue(node, 'number:min-numerator-digits'); if s <> '' then fracNum := StrToInt(s) else fracNum := 0; s := GetAttrValue(node, 'number:min-denominator-digits'); if s <> '' then fracDenom := StrToInt(s) else fracDenom := 0; s := GetAttrValue(node, 'number:denominator-value'); if s <> '' then fracDenom := -StrToInt(s); - nfs := nfs + BuildFractionFormatString(fracInt > 0, fracNum, fracDenom); + nfs := nfs + BuildFractionFormatString(fracInt > -1, fracNum, fracDenom); end else if nodeName = 'number:scientific-number' then begin @@ -2324,14 +2320,12 @@ procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode); if s <> '' then begin hasColor := true; - // { // currently not needed color := HTMLColorStrToColor(s); - idx := FWorkbook.AddColorToPalette(color); - if idx < 8 then - nfs := Format('[%s]%s', [FWorkbook.GetColorName(idx), nfs]) - else - nfs := Format('[Color%d]%s', [idx, nfs]); - // } + case color of + scBlack, scWhite, scRed, scGreen, + scBlue, scYellow, scMagenta, scCyan: + nfs := Format('[%s]%s', [GetColorName(color), nfs]); + end; end; end; node := node.NextSibling; @@ -2913,7 +2907,7 @@ var numFmtStr: String; numFmtIndex: Integer; numFmtParams: TsNumFormatParams; - clr: TsColorValue; + clr: TsColor; s: String; procedure SetBorderStyle(ABorder: TsCellBorder; AStyleValue: String); @@ -2925,7 +2919,7 @@ var s: String; wid: Double; linestyle: String; - rgb: TsColorValue; + rgb: TsColor; p: Integer; begin L := TStringList.Create; @@ -2934,7 +2928,7 @@ var L.StrictDelimiter := true; L.DelimitedText := AStyleValue; wid := 0; - rgb := TsColorValue(-1); + rgb := scNotDefined; linestyle := ''; for i:=0 to L.Count-1 do begin @@ -2981,8 +2975,7 @@ var else if (linestyle = 'double') then fmt.BorderStyles[ABorder].LineStyle := lsDouble; - fmt.BorderStyles[ABorder].Color := IfThen(rgb = TsColorValue(-1), - scBlack, Workbook.AddColorToPalette(rgb)); + fmt.BorderStyles[ABorder].Color := IfThen(rgb = scNotDefined, scBlack, rgb); finally L.Free; end; @@ -3048,10 +3041,6 @@ begin fmt.FontIndex := ReadFont(styleChildNode, HYPERLINK_FONTINDEX) else fmt.FontIndex := ReadFont(styleChildNode); - { - if fmt.FontIndex = BOLD_FONTINDEX then - Include(fmt.UsedFormattingFields, uffBold) - else } if fmt.FontIndex > 0 then Include(fmt.UsedFormattingFields, uffFont); end else @@ -3062,8 +3051,7 @@ begin if (s <> '') and (s <> 'transparent') then begin clr := HTMLColorStrToColor(s); // ODS does not support background fill patterns! - fmt.Background.FgColor := IfThen(clr = TsColorValue(-1), - scTransparent, Workbook.AddColorToPalette(clr)); + fmt.Background.FgColor := IfThen(clr = scNotDefined, scTransparent, clr); fmt.Background.BgColor := fmt.Background.FgColor; if (fmt.Background.BgColor <> scTransparent) then begin @@ -4444,8 +4432,6 @@ end; -------------------------------------------------------------------------------} function TsSpreadOpenDocWriter.WriteBackgroundColorStyleXMLAsString( const AFormat: TsCellFormat): String; -type - TRgb = record r,g,b,a: byte; end; const // fraction of pattern color in fill pattern FRACTION: array[TsFillStyle] of Double = ( 0.0, 1.0, 0.75, 0.50, 0.25, 0.125, 0.0625, // fsNoFill..fsGray6 @@ -4453,8 +4439,8 @@ const // fraction of pattern color in fill pattern 0.25, 0.25, 0.25, 0.25, // fsThinStripeHor..fsThinStripeDiagDown 0.5, 6.0/16, 0.75, 7.0/16); // fsHatchDiag..fsThinHatchHor var - fc,bc: TsColorValue; - mix: TRgb; + fc,bc: TsColor; + mix: TRgba; fraction_fc, fraction_bc: Double; begin Result := ''; @@ -4463,22 +4449,22 @@ begin exit; // Foreground and background colors - fc := Workbook.GetPaletteColor(AFormat.Background.FgColor); + fc := AFormat.Background.FgColor; if Aformat.Background.BgColor = scTransparent then - bc := Workbook.GetPaletteColor(scWhite) + bc := scWhite else - bc := Workbook.GetPaletteColor(AFormat.Background.BgColor); + bc := AFormat.Background.BgColor; + // Mixing fraction fraction_fc := FRACTION[AFormat.Background.Style]; fraction_bc := 1.0 - fraction_fc; - // Mixed color - mix.r := Min(round(fraction_fc*TRgb(fc).r + fraction_bc*TRgb(bc).r), 255); - mix.g := Min(round(fraction_fc*TRgb(fc).g + fraction_bc*TRgb(bc).g), 255); - mix.b := Min(round(fraction_fc*TRgb(fc).b + fraction_bc*TRgb(bc).b), 255); - Result := Format('fo:background-color="%s" ', [ - ColorToHTMLColorStr(TsColorValue(mix)) - ]); + // Mixed color + mix.r := Min(round(fraction_fc*TRgba(fc).r + fraction_bc*TRgba(bc).r), 255); + mix.g := Min(round(fraction_fc*TRgba(fc).g + fraction_bc*TRgba(bc).g), 255); + mix.b := Min(round(fraction_fc*TRgba(fc).b + fraction_bc*TRgba(bc).b), 255); + + Result := Format('fo:background-color="%s" ', [ColorToHTMLColorStr(TsColor(mix))]); end; {@@ ---------------------------------------------------------------------------- @@ -4499,7 +4485,7 @@ begin Result := Result + Format('fo:border-bottom="%s %s %s" ', [ BORDER_LINEWIDTHS[AFormat.BorderStyles[cbSouth].LineStyle], BORDER_LINESTYLES[AFormat.BorderStyles[cbSouth].LineStyle], - Workbook.GetPaletteColorAsHTMLStr(AFormat.BorderStyles[cbSouth].Color) + ColorToHTMLColorStr(AFormat.BorderStyles[cbSouth].Color) ]); if AFormat.BorderStyles[cbSouth].LineStyle = lsDouble then Result := Result + 'style:border-linewidth-bottom="0.002cm 0.035cm 0.002cm" '; @@ -4512,7 +4498,7 @@ begin Result := Result + Format('fo:border-left="%s %s %s" ', [ BORDER_LINEWIDTHS[AFormat.BorderStyles[cbWest].LineStyle], BORDER_LINESTYLES[AFormat.BorderStyles[cbWest].LineStyle], - Workbook.GetPaletteColorAsHTMLStr(AFormat.BorderStyles[cbWest].Color) + ColorToHTMLColorStr(AFormat.BorderStyles[cbWest].Color) ]); if AFormat.BorderStyles[cbWest].LineStyle = lsDouble then Result := Result + 'style:border-linewidth-left="0.002cm 0.035cm 0.002cm" '; @@ -4525,7 +4511,7 @@ begin Result := Result + Format('fo:border-right="%s %s %s" ', [ BORDER_LINEWIDTHS[AFormat.BorderStyles[cbEast].LineStyle], BORDER_LINESTYLES[AFormat.BorderStyles[cbEast].LineStyle], - Workbook.GetPaletteColorAsHTMLStr(AFormat.BorderStyles[cbEast].Color) + ColorToHTMLColorStr(AFormat.BorderStyles[cbEast].Color) ]); if AFormat.BorderStyles[cbSouth].LineStyle = lsDouble then Result := Result + 'style:border-linewidth-right="0.002cm 0.035cm 0.002cm" '; @@ -4538,7 +4524,7 @@ begin Result := Result + Format('fo:border-top="%s %s %s" ', [ BORDER_LINEWIDTHS[AFormat.BorderStyles[cbNorth].LineStyle], BORDER_LINESTYLES[AFormat.BorderStyles[cbNorth].LineStyle], - Workbook.GetPaletteColorAsHTMLStr(AFormat.BorderStyles[cbNorth].Color) + ColorToHTMLColorStr(AFormat.BorderStyles[cbNorth].Color) ]); if AFormat.BorderStyles[cbSouth].LineStyle = lsDouble then Result := Result + 'style:border-linewidth-top="0.002cm 0.035cm 0.002cm" '; @@ -4550,7 +4536,7 @@ begin Result := Result + Format('style:diagonal-bl-tr="%s %s %s" ', [ BORDER_LINEWIDTHS[AFormat.BorderStyles[cbDiagUp].LineStyle], BORDER_LINESTYLES[AFormat.BorderStyles[cbDiagUp].LineStyle], - Workbook.GetPaletteColorAsHTMLStr(AFormat.BorderStyles[cbDiagUp].Color) + ColorToHTMLColorStr(AFormat.BorderStyles[cbDiagUp].Color) ]); end; @@ -4559,7 +4545,7 @@ begin Result := Result + Format('style:diagonal-tl-br="%s %s %s" ', [ BORDER_LINEWIDTHS[AFormat.BorderStyles[cbDiagDown].LineStyle], BORDER_LINESTYLES[AFormat.BorderStyles[cbDiagDown].LineStyle], - Workbook.GetPaletteColorAsHTMLStr(AFormat.BorderStyles[cbDiagDown].Color) + ColorToHTMLColorStr(AFormat.BorderStyles[cbDiagDown].Color) ]); end; end; @@ -4613,7 +4599,7 @@ begin Result := Result + 'style:text-line-through-style="solid" '; if AFont.Color <> defFnt.Color then - Result := Result + Format('fo:color="%s" ', [Workbook.GetPaletteColorAsHTMLStr(AFont.Color)]); + Result := Result + Format('fo:color="%s" ', [ColorToHTMLColorStr(AFont.Color)]); end; function TsSpreadOpenDocWriter.WriteFontStyleXMLAsString( diff --git a/components/fpspreadsheet/fpspalette.pas b/components/fpspreadsheet/fpspalette.pas new file mode 100644 index 000000000..984b7e787 --- /dev/null +++ b/components/fpspreadsheet/fpspalette.pas @@ -0,0 +1,414 @@ +{ fpsPalette } + +{@@ ---------------------------------------------------------------------------- + Palette support for fpspreadsheet file formats + + AUTHORS: Werner Pamler, Felipe Monteiro de Carvalho, Reinier Olislagers + + LICENSE: See the file COPYING.modifiedLGPL.txt, included in the Lazarus + distribution, for details about the license. +-------------------------------------------------------------------------------} + +unit fpsPalette; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, fpstypes, fpspreadsheet; + +type + + { TsPalette } + TsPalette = class + private + FColors: array of TsColor; + function GetColor(AIndex: Integer): TsColor; + procedure SetColor(AIndex: Integer; AColor: TsColor); + public + constructor Create; + procedure AddBuiltinColors; virtual; + function AddColor(AColor: TsColor; ABigEndian: Boolean = false): Integer; + procedure AddExcelColors; + function AddUniqueColor(AColor: TsColor; ABigEndian: Boolean = false): Integer; + procedure Clear; + procedure CollectFromWorkbook(AWorkbook: TsWorkbook); + function ColorUsedInWorkbook(APaletteIndex: Integer; AWorkbook: TsWorkbook): Boolean; + function FindClosestColorIndex(AColor: TsColor; AMaxPaletteCount: Integer = -1): Integer; + function FindColor(AColor: TsColor; AMaxPaletteCount: Integer = -1): Integer; + function Count: Integer; + procedure Trim(AMaxSize: Integer); + procedure UseColors(const AColors: array of TsColor; ABigEndian: Boolean = false); + property Colors[AIndex: Integer]: TsColor read GetColor write SetColor; default; + end; + + procedure MakeLEPalette(var AColors: array of TsColor); + + +implementation + +uses + fpsutils; + +{@@ ---------------------------------------------------------------------------- + If a palette is coded as big-endian (e.g. by copying the rgb values from + the OpenOffice documentation) the palette values can be converted by means + of this procedure to little-endian which is required by fpspreadsheet. + + @param AColors Color array to be converted. + After conversion, its color values are replaced. +-------------------------------------------------------------------------------} +procedure MakeLEPalette(var AColors: array of TsColor); +var + i: Integer; +begin + for i := 0 to High(AColors) do + AColors[i] := LongRGBToExcelPhysical(AColors[i]) +end; + + +{@@ ---------------------------------------------------------------------------- + Constructor of the palette: initializes the color array +-------------------------------------------------------------------------------} +constructor TsPalette.Create; +begin + inherited; + SetLength(FColors, 0); +end; + +{@@ ---------------------------------------------------------------------------- + Adds an rgb color value to the palette and returns the palette index + of the new color. + + Existing colors are not checked. + + If ABigEndian is TRUE then the rgb values are assumed to be in big endian + order (r = high byte). + By default, rgb is in little-endian order (r = low byte) +-------------------------------------------------------------------------------} +function TsPalette.AddColor(AColor: TsColor; ABigEndian: Boolean = false): Integer; +begin + if ABigEndian then + AColor := LongRGBToExcelPhysical(AColor); + + SetLength(FColors, Length(FColors) + 1); + FColors[High(FColors)] := AColor; +end; + +{@@ ---------------------------------------------------------------------------- + Adds the built-in colors +-------------------------------------------------------------------------------} +procedure TsPalette.AddBuiltinColors; +begin + AddColor(scBlack); // 0 + AddColor(scWhite); // 1 + AddColor(scRed); // 2 + AddColor(scGreen); // 3 + AddColor(scBlue); // 4 + AddColor(scYellow); // 5 + AddColor(scMagenta); // 6 + AddColor(scCyan); // 7 +end; + +{@@ ---------------------------------------------------------------------------- + Adds the standard palette of Excel 8 + + NOTE: To get the full Excel8 palette call this after AddBuiltinColors +-------------------------------------------------------------------------------} +procedure TsPalette.AddExcelColors; +begin + AddColor($000000, true); // $08: EGA black + AddColor($FFFFFF, true); // $09: EGA white + AddColor($FF0000, true); // $0A: EGA red + AddColor($00FF00, true); // $0B: EGA green + AddColor($0000FF, true); // $0C: EGA blue + AddColor($FFFF00, true); // $0D: EGA yellow + AddColor($FF00FF, true); // $0E: EGA magenta + AddColor($00FFFF, true); // $0F: EGA cyan + + AddColor($800000, true); // $10: EGA dark red + AddColor($008000, true); // $11: EGA dark green + AddColor($000080, true); // $12: EGA dark blue + AddColor($808000, true); // $13: EGA olive + AddColor($800080, true); // $14: EGA purple + AddColor($008080, true); // $15: EGA teal + AddColor($C0C0C0, true); // $16: EGA silver + AddColor($808080, true); // $17: EGA gray + + AddColor($9999FF, true); // $18: + AddColor($993366, true); // $19: + AddColor($FFFFCC, true); // $1A: + AddColor($CCFFFF, true); // $1B: + AddColor($660066, true); // $1C: + AddColor($FF8080, true); // $1D: + AddColor($0066CC, true); // $1E: + AddColor($CCCCFF, true); // $1F: + + AddColor($000080, true); // $20: + AddColor($FF00FF, true); // $21: + AddColor($FFFF00, true); // $22: + AddColor($00FFFF, true); // $23: + AddColor($800080, true); // $24: + AddColor($800000, true); // $25: + AddColor($008080, true); // $26: + AddColor($0000FF, true); // $27: + AddColor($00CCFF, true); // $28: + AddColor($CCFFFF, true); // $29: + AddColor($CCFFCC, true); // $2A: + AddColor($FFFF99, true); // $2B: + AddColor($99CCFF, true); // $2C: + AddColor($FF99CC, true); // $2D: + AddColor($CC99FF, true); // $2E: + AddColor($FFCC99, true); // $2F: + + AddColor($3366FF, true); // $30: + AddColor($33CCCC, true); // $31: + AddColor($99CC00, true); // $32: + AddColor($FFCC00, true); // $33: + AddColor($FF9900, true); // $34: + AddColor($FF6600, true); // $35: + AddColor($666699, true); // $36: + AddColor($969696, true); // $37: + AddColor($003366, true); // $38: + AddColor($339966, true); // $39: + AddColor($003300, true); // $3A: + AddColor($333300, true); // $3B: + AddColor($993300, true); // $3C: + AddColor($993366, true); // $3D: + AddColor($333399, true); // $3E: + AddColor($333333, true); // $3F: +end; + +{@@ ---------------------------------------------------------------------------- + Adds the specified color to the palette if it does not yet exist. + + Returns the palette index of the new or existing color +-------------------------------------------------------------------------------} +function TsPalette.AddUniqueColor(AColor: TsColor; + ABigEndian: Boolean = false): Integer; +begin + if ABigEndian then + AColor := LongRGBToExcelPhysical(AColor); + + Result := FindColor(AColor); + if Result = -1 then result := AddColor(AColor); +end; + +{@@ ---------------------------------------------------------------------------- + Clears the palette +-------------------------------------------------------------------------------} +procedure TsPalette.Clear; +begin + SetLength(FColors, 0); +end; + +{@@ ---------------------------------------------------------------------------- + Collects the colors used in the specified workbook +-------------------------------------------------------------------------------} +procedure TsPalette.CollectFromWorkbook(AWorkbook: TsWorkbook); +var + i: Integer; + sheet: TsWorksheet; + cell: PCell; + fmt: TsCellFormat; + fnt: TsFont; + cb: TsCellBorder; +begin + for i:=0 to AWorkbook.GetWorksheetCount-1 do + begin + sheet := AWorkbook.GetWorksheetByIndex(i); + for cell in sheet.Cells do begin + fmt := sheet.ReadCellFormat(cell); + if (uffBackground in fmt.UsedFormattingFields) then + begin + AddUniqueColor(fmt.Background.BgColor); + AddUniqueColor(fmt.Background.FgColor); + end; + if (uffFont in fmt.UsedFormattingFields) then + begin + fnt := AWorkbook.GetFont(fmt.FontIndex); + AddUniqueColor(fnt.Color); + end; + if (uffBorder in fmt.UsedFormattingFields) then + begin + for cb in TsCellBorder do + if (cb in fmt.Border) then + AddUniqueColor(fmt.BorderStyles[cb].Color); + end; + end; + end; +end; + +{@@ ---------------------------------------------------------------------------- + Checks whether a given color is used somewhere within the entire workbook + + @param APaletteIndex Palette index of the color + @result True if the color is used by at least one cell, false if not. +-------------------------------------------------------------------------------} +function TsPalette.ColorUsedInWorkbook(APaletteIndex: Integer; + AWorkbook: TsWorkbook): Boolean; +var + sheet: TsWorksheet; + cell: PCell; + i: Integer; + fnt: TsFont; + b: TsCellBorder; + fmt: PsCellFormat; + color: TsColor; +begin + color := GetColor(APaletteIndex); + if (color = scNotDefined) or (AWorkbook = nil) then + exit(false); + + Result := true; + for i:=0 to AWorkbook.GetWorksheetCount-1 do + begin + sheet := AWorkbook.GetWorksheetByIndex(i); + for cell in sheet.Cells do + begin + fmt := AWorkbook.GetPointerToCellFormat(cell^.FormatIndex); + if (uffBackground in fmt^.UsedFormattingFields) then + begin + if fmt^.Background.BgColor = color then exit; + if fmt^.Background.FgColor = color then exit; + end; + if (uffBorder in fmt^.UsedFormattingFields) then + for b in TsCellBorders do + if (b in fmt^.Border) and (fmt^.BorderStyles[b].Color = color) then + exit; + if (uffFont in fmt^.UsedFormattingFields) then + begin + fnt := AWorkbook.GetFont(fmt^.FontIndex); + if fnt.Color = color then + exit; + end; + end; + end; + Result := false; +end; + +{@@ ---------------------------------------------------------------------------- + Finds the palette color index which points to a color that is "closest" to a + given color. "Close" means here smallest length of the rgb-difference vector. + + @param AColor Rgb color value to be considered + @param AMaxPaletteCount Number of palette entries considered. Example: + BIFF5/BIFF8 can write only 64 colors, i.e + AMaxPaletteCount = 64 + @return Palette index of the color closest to AColor +-------------------------------------------------------------------------------} +function TsPalette.FindClosestColorIndex(AColor: TsColor; + AMaxPaletteCount: Integer = -1): Integer; +type + TRGBA = record r,g,b,a: Byte end; +var + rgb: TRGBA; + rgb0: TRGBA absolute AColor; + dist: Double; + minDist: Double; + i: Integer; + n: Integer; +begin + Result := -1; + minDist := 1E108; + n := Length(FColors); + if AMaxPaletteCount > n then n := AMaxPaletteCount; + for i := 0 to n - 1 do + begin + rgb := TRGBA(GetColor(i)); + dist := sqr(rgb.r - rgb0.r) + sqr(rgb.g - rgb0.g) + sqr(rgb.b - rgb0.b); + if dist < minDist then + begin + Result := i; + minDist := dist; + end; + end; +end; + +{@@ ---------------------------------------------------------------------------- + Finds the palette color index which belongs to the specified color. + Returns -1 if the color is not contained in the palette. + + @param AColor Rgb color value to be considered + @param AMaxPaletteCount Number of palette entries considered. Example: + BIFF5/BIFF8 can write only 64 colors, i.e + AMaxPaletteCount = 64 + @return Palette index of AColor +-------------------------------------------------------------------------------} +function TsPalette.FindColor(AColor: TsColor; + AMaxPaletteCount: Integer = -1): Integer; +var + n: Integer; +begin + n := Length(FColors); + if AMaxPaletteCount > n then n := AMaxPaletteCount; + for Result := 0 to n - 1 do + if GetColor(Result) = AColor then + exit; + Result := -1; +end; + +{@@ ---------------------------------------------------------------------------- + Reads the rgb color for the given index from the palette. + Can be type-cast to TColor for usage in GUI applications. + + @param AIndex Index of the color considered + @return A number containing the rgb components in little-endian notation. +-------------------------------------------------------------------------------} +function TsPalette.GetColor(AIndex: Integer): TsColor; +begin + if (AIndex >= 0) and (AIndex < Length(FColors)) then + Result := FColors[AIndex] + else + Result := scNotDefined; +end; + +{@@ ---------------------------------------------------------------------------- + Returns the number of palette colors +-------------------------------------------------------------------------------} +function TsPalette.Count: Integer; +begin + Result := Length(FColors); +end; + +{@@ ---------------------------------------------------------------------------- + Replaces a color value of the palette by a new value. + The color must be given in little-endian notation (ABGR, with A=0). + + @param AIndex Palette index of the color to be replaced + @param AColor Number containing the rgb components of the new color +-------------------------------------------------------------------------------} +procedure TsPalette.SetColor(AIndex: Integer; AColor: TsColor); +begin + if (AIndex >= 0) and (AIndex < Length(FColors)) then + FColors[AIndex] := AColor; +end; + +{@@ ---------------------------------------------------------------------------- + Trims the size of the palette +-------------------------------------------------------------------------------} +procedure TsPalette.Trim(AMaxSize: Integer); +begin + if Length(FColors) > AMaxSize then + SetLength(FColors, AMaxSize); +end; + +{@@ ---------------------------------------------------------------------------- + Uses the color array to with "APalette" points in the palette. + If ABigEndian is true it is assumed that the input colors are specified in + big-endian notation, i.e. "blue" in the low-value byte. +-------------------------------------------------------------------------------} +procedure TsPalette.UseColors(const AColors: array of TsColor; ABigEndian: Boolean = false); +var + i: Integer; +begin + SetLength(FColors, High(AColors)+1); + if ABigEndian then + for i:=0 to High(AColors) do FColors[i] := LongRGBToExcelPhysical(AColors[i]) + else + for i:=0 to High(AColors) do FColors[i] := AColors[i]; +end; + + +end. diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index 7460ce42f..bedc66915 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -549,7 +549,7 @@ type FWorksheets: TFPList; FFormat: TsSpreadsheetFormat; FBuiltinFontCount: Integer; - FPalette: array of TsColorValue; + //FPalette: array of TsColorValue; FVirtualColCount: Cardinal; FVirtualRowCount: Cardinal; FReadWriteFlag: TsReadWriteFlag; @@ -565,7 +565,7 @@ type FOnRemoveWorksheet: TsRemoveWorksheetEvent; FOnRemovingWorksheet: TsWorksheetEvent; FOnSelectWorksheet: TsWorksheetEvent; - FOnChangePalette: TNotifyEvent; +// FOnChangePalette: TNotifyEvent; FFileName: String; FLockCount: Integer; FLog: TStringList; @@ -668,11 +668,8 @@ type function AddNumberFormat(AFormatStr: String): Integer; function GetNumberFormat(AIndex: Integer): TsNumFormatParams; function GetNumberFormatCount: Integer; - + (* { Color handling } - function AddColorToPalette(AColorValue: TsColorValue): TsColor; - function FindClosestColor(AColorValue: TsColorValue; - AMaxPaletteCount: Integer = -1): TsColor; function FPSColorToHexString(AColor: TsColor; ARGBColor: TFPColor): String; function GetColorName(AColorIndex: TsColor): string; overload; procedure GetColorName(AColorValue: TsColorValue; out AName: String); overload; @@ -684,6 +681,7 @@ type procedure UsePalette(APalette: PsPalette; APaletteCount: Word; ABigEndian: Boolean = false); function UsesColor(AColorIndex: TsColor): Boolean; + *) { Utilities } procedure UpdateCaches; @@ -708,7 +706,7 @@ type {@@ This event fires whenever a new worksheet is added } property OnAddWorksheet: TsWorksheetEvent read FOnAddWorksheet write FOnAddWorksheet; {@@ This event fires whenever the workbook palette changes. } - property OnChangePalette: TNotifyEvent read FOnChangePalette write FOnChangePalette; +// property OnChangePalette: TNotifyEvent read FOnChangePalette write FOnChangePalette; {@@ This event fires whenever a worksheet is changed } property OnChangeWorksheet: TsWorksheetEvent read FOnChangeWorksheet write FOnChangeWorksheet; {@@ This event fires whenever a workbook is loaded } @@ -778,7 +776,6 @@ type procedure CopyCellFormat(AFromCell, AToCell: PCell); procedure CopyCellValue(AFromCell, AToCell: PCell); -procedure MakeLEPalette(APalette: PsPalette; APaletteSize: Integer); //function SameCellBorders(ACell1, ACell2: PCell): Boolean; overload; function SameCellBorders(AFormat1, AFormat2: PsCellFormat): Boolean; //overload; @@ -826,7 +823,7 @@ const DEF_CHART_NEUTRAL_COLORVALUE = $FFFFFF; DEF_TOOLTIP_TEXT_COLORVALUE = $000000; DEF_FONT_AUTOMATIC_COLORVALUE = $000000; - + (* var {@@ RGB colors RGB in "big-endian" notation (red at left). The values are inverted at initialization to be little-endian at run-time! @@ -883,26 +880,7 @@ var 'beige', // $15 'wheat' // $16 ); - -{@@ ---------------------------------------------------------------------------- - If a palette is coded as big-endian (e.g. by copying the rgb values from - the OpenOffice documentation) the palette values can be converted by means - of this procedure to little-endian which is required internally by TsWorkbook. - - @param APalette Pointer to the palette to be converted. After conversion, - its color values are replaced. - @param APaletteSize Number of colors contained in the palette --------------------------------------------------------------------------------} -procedure MakeLEPalette(APalette: PsPalette; APaletteSize: Integer); -var - i: Integer; -begin - {$PUSH}{$R-} - for i := 0 to APaletteSize-1 do - APalette^[i] := LongRGBToExcelPhysical(APalette^[i]) - {$POP} -end; - + *) {@@ ---------------------------------------------------------------------------- Copies the format of a cell to another one. @@ -916,7 +894,6 @@ var numFmtParams: TsNumFormatParams; nfs: String; font: TsFont; - clr: TsColorvalue; cb: TsCellBorder; begin Assert(AFromCell <> nil); @@ -929,6 +906,7 @@ begin begin fmt := sourceSheet.ReadCellFormat(AFromCell); //destSheet.WriteCellFormat(AToCell, fmt); + { if (uffBackground in fmt.UsedFormattingFields) then begin clr := sourceSheet.Workbook.GetPaletteColor(fmt.Background.BgColor); @@ -936,21 +914,26 @@ begin clr := sourceSheet.Workbook.GetPaletteColor(fmt.Background.FgColor); fmt.Background.FgColor := destSheet.Workbook.AddColorToPalette(clr); end; + } if (uffFont in fmt.UsedFormattingFields) then begin font := sourceSheet.ReadCellFont(AFromCell); + { clr := sourceSheet.Workbook.GetPaletteColor(font.Color); font.Color := destSheet.Workbook.AddColorToPalette(clr); + } fmt.FontIndex := destSheet.Workbook.FindFont(font.FontName, font.Size, font.Style, font.Color); if fmt.FontIndex = -1 then fmt.FontIndex := destSheet.Workbook.AddFont(font.FontName, font.Size, font.Style, font.Color); end; + { if (uffBorder in fmt.UsedFormattingFields) then for cb in fmt.Border do begin clr := sourceSheet.Workbook.GetPaletteColor(fmt.BorderStyles[cb].Color); fmt.BorderStyles[cb].Color := destSheet.Workbook.AddColorToPalette(clr); end; + } if (uffNumberformat in fmt.UsedFormattingFields) then begin numFmtParams := sourceSheet.Workbook.GetNumberFormat(fmt.NumberFormatIndex); @@ -1086,7 +1069,8 @@ begin IfThen(fssItalic in fnt.Style, 'i', '.'), IfThen(fssUnderline in fnt.Style, 'u', '.'), IfThen(fssStrikeOut in fnt.Style, 's', '.'), - AWorkbook.GetPaletteColorAsHTMLStr(fnt.Color) + ColorToHTMLColorStr(fnt.Color) + //AWorkbook.GetPaletteColorAsHTMLStr(fnt.Color) ])); end; L.SaveToFile(AFileName); @@ -2847,10 +2831,10 @@ begin end; {@@ ---------------------------------------------------------------------------- - Returns the background color of a cell as index into the workbook's color palette. + Returns the background color of a cell as rbg value - @param ACell Pointer to the cell - @return Index of the cell background color into the workbook's color palette + @param ACell Pointer to the cell + @return Value containing the rgb bytes in little-endian order -------------------------------------------------------------------------------} function TsWorksheet.ReadBackgroundColor(ACell: PCell): TsColor; var @@ -4857,8 +4841,7 @@ end; @param ARow The row of the cell @param ACol The column of the cell - @param AFontColor Index into the workbook's color palette identifying the - new text color. + @param AFontColor RGB value of the new text color @return Index of the font in the workbook's font list. -------------------------------------------------------------------------------} function TsWorksheet.WriteFontColor(ARow, ACol: Cardinal; AFontColor: TsColor): Integer; @@ -4872,8 +4855,7 @@ end; is created. Returns the index of this font in the font list. @param ACell Pointer to the cell - @param AFontColor Index into the workbook's color palette identifying the - new text color. + @param AFontColor RGB value of the new text color @return Index of the font in the workbook's font list. -------------------------------------------------------------------------------} function TsWorksheet.WriteFontColor(ACell: PCell; AFontColor: TsColor): Integer; @@ -5093,8 +5075,8 @@ end; @param ARow Row index of the cell @param ACol Column index of the cell @param AFillStyle Fill style to be used - see TsFillStyle - @param APatternColor Palette index of the pattern color - @param ABackgroundColor Palette index of the background color + @param APatternColor RGB value of the pattern color + @param ABackgroundColor RGB value of the background color @return Pointer to cell @NOTE Is replaced by uniform fill if WriteBackgroundColor is called later. @@ -5111,8 +5093,8 @@ end; @param ACell Pointer to the cell @param AStyle Fill style ("pattern") to be used - see TsFillStyle - @param APatternColor Palette index of the pattern color - @param ABackgroundColor Palette index of the background color + @param APatternColor RGB value of the pattern color + @param ABackgroundColor RGB value of the background color @NOTE Is replaced by uniform fill if WriteBackgroundColor is called later. -------------------------------------------------------------------------------} @@ -5147,9 +5129,9 @@ end; @param ARow Row index of the cell @param ACol Column index of the cell - @param AColor Index of the new background color into the workbook's - color palette. Use the color index scTransparent to - erase an existing background color. + @param AColor RGB value of the new background color. + Use the value "scTransparent" to clear an existing + background color. @return Pointer to cell -------------------------------------------------------------------------------} function TsWorksheet.WriteBackgroundColor(ARow, ACol: Cardinal; @@ -5163,9 +5145,9 @@ end; Sets a uniform background color of a cell. @param ACell Pointer to cell - @param AColor Index of the new background color into the workbook's - color palette. Use the color index scTransparent to - erase an existing background color. + @param AColor RGB value of the new background color. + Use the value "scTransparent" to clear an existing + background color. -------------------------------------------------------------------------------} procedure TsWorksheet.WriteBackgroundColor(ACell: PCell; AColor: TsColor); begin @@ -5185,8 +5167,7 @@ end; @param ACol Column index of the cell @param ABorder Indicates to which border (left/top etc) this color is to be applied - @param AColor Index of the new border color into the workbook's - color palette. + @param AColor RGB value of the new border color @return Pointer to cell -------------------------------------------------------------------------------} function TsWorksheet.WriteBorderColor(ARow, ACol: Cardinal; @@ -5203,8 +5184,7 @@ end; @param ACell Pointer to cell @param ABorder Indicates to which border (left/top etc) this color is to be applied - @param AColor Index of the new border color into the workbook's - color palette. + @param AColor RGB value of the new border color -------------------------------------------------------------------------------} procedure TsWorksheet.WriteBorderColor(ACell: PCell; ABorder: TsCellBorder; AColor: TsColor); @@ -5355,7 +5335,7 @@ end; @param ACol Column index of the considered cell @param ABorder Identifier of the border to be modified @param ALineStyle Identifier for the new line style of the border - @param AColor Palette index for the color of the border line + @param AColor RGB value of the border line color @return Pointer to cell @see WriteBorderStyles @@ -5374,7 +5354,7 @@ end; @param ACell Pointer to cell @param ABorder Identifier of the border to be modified @param ALineStyle Identifier for the new line style of the border - @param AColor Palette index for the color of the border line + @param AColor RGB value of the color of the border line @see WriteBorderStyles -------------------------------------------------------------------------------} @@ -6302,8 +6282,6 @@ begin FormatSettings.ShortDateFormat := MakeShortDateFormat(FormatSettings.ShortDateFormat); FormatSettings.LongDateFormat := MakeLongDateFormat(FormatSettings.ShortDateFormat); - UseDefaultPalette; - FFontList := TFPList.Create; SetDefaultFont(DEFAULT_FONTNAME, DEFAULT_FONTSIZE); InitFonts; @@ -7249,7 +7227,7 @@ end; @param AFontName Name of the font (like 'Arial') @param ASize Size of the font in points @param AStyle Style of the font, a combination of TsFontStyle elements - @param AColor Color of the font, given by its index into the workbook's palette. + @param AColor RGB valoe of the font color @return Index of the font in the workbook's font list -------------------------------------------------------------------------------} function TsWorkbook.AddFont(const AFontName: String; ASize: Single; @@ -7301,11 +7279,13 @@ end; @param AFontName Name of the font (like 'Arial') @param ASize Size of the font in points @param AStyle Style of the font, a combination of TsFontStyle elements - @param AColor Color of the font, given by its index into the workbook's palette. + @param AColor RGB value of the font color @return Index of the font in the font list, or -1 if not found. -------------------------------------------------------------------------------} function TsWorkbook.FindFont(const AFontName: String; ASize: Single; AStyle: TsFontStyles; AColor: TsColor): Integer; +const + EPS = 1e-3; var fnt: TsFont; begin @@ -7314,9 +7294,9 @@ begin fnt := TsFont(FFontList.Items[Result]); if (fnt <> nil) and SameText(AFontName, fnt.FontName) and - (abs(ASize - fnt.Size) < 0.001) and // careful when comparing floating point numbers + SameValue(ASize, fnt.Size, EPS) and // careful when comparing floating point numbers (AStyle = fnt.Style) and - (AColor = fnt.Color) // Take care of limited palette size! + (AColor = fnt.Color) then exit; end; @@ -7520,7 +7500,7 @@ function TsWorkbook.GetNumberFormatCount: Integer; begin Result := FNumFormatList.Count; end; - + (* {@@ ---------------------------------------------------------------------------- Adds a color to the palette and returns its palette index, but only if the color does not already exist - in this case, it returns the index of the @@ -7602,7 +7582,7 @@ begin if Assigned(FOnChangePalette) then FOnChangePalette(self); end; - + *) {@@ ---------------------------------------------------------------------------- Adds a (simple) error message to an internal list @@ -7639,47 +7619,7 @@ function TsWorkbook.GetErrorMsg: String; begin Result := FLog.Text; end; - -{@@ ---------------------------------------------------------------------------- - Finds the palette color index which points to a color that is closest to a - given color. "Close" means here smallest length of the rgb-difference vector. - - @param AColorValue Rgb color value to be considered - @param AMaxPaletteCount Number of palette entries considered. Example: - BIFF5/BIFF8 can write only 64 colors, i.e - AMaxPaletteCount = 64 - @return Palette index of the color closest to AColorValue --------------------------------------------------------------------------------} -function TsWorkbook.FindClosestColor(AColorValue: TsColorValue; - AMaxPaletteCount: Integer = -1): TsColor; -type - TRGBA = record r,g,b, a: Byte end; -var - rgb: TRGBA; - rgb0: TRGBA absolute AColorValue; - dist: Double; - minDist: Double; - i: Integer; - n: Integer; -begin - Result := scNotDefined; - minDist := 1E108; - if AMaxPaletteCount = -1 then - n := Length(FPalette) - else - n := Min(Length(FPalette), AMaxPaletteCount); - for i:=0 to n-1 do - begin - rgb := TRGBA(GetPaletteColor(i)); - dist := sqr(rgb.r - rgb0.r) + sqr(rgb.g - rgb0.g) + sqr(rgb.b - rgb0.b); - if dist < minDist then - begin - Result := i; - minDist := dist; - end; - end; -end; - + (* {@@ ---------------------------------------------------------------------------- Converts a fpspreadsheet color into into a string RRGGBB. Note that colors are written to xls files as ABGR (where A is 0). @@ -7757,26 +7697,6 @@ begin AName := Format('%.2x%.2x%.2x', [R, G, B]); end; -{@@ ---------------------------------------------------------------------------- - Reads the rgb color for the given index from the current palette. Can be - type-cast to TColor for usage in GUI applications. - - @param AColorIndex Index of the color considered - @return A number containing the rgb components in little-endian notation. --------------------------------------------------------------------------------} -function TsWorkbook.GetPaletteColor(AColorIndex: TsColor): TsColorValue; -begin - if (AColorIndex >= 0) and (AColorIndex < GetPaletteSize) then - begin - if ((FPalette = nil) or (Length(FPalette) = 0)) then - Result := DEFAULT_PALETTE[AColorIndex] - else - Result := FPalette[AColorIndex]; - end - else - Result := $000000; // "black" as default -end; - {@@ ---------------------------------------------------------------------------- Converts the palette color of the given index to a string that can be used in HTML code. For ODS. @@ -7790,36 +7710,6 @@ begin Result := ColorToHTMLColorStr(GetPaletteColor(AColorIndex)); end; -{@@ ---------------------------------------------------------------------------- - Replaces a color value of the current palette by a new value. The color must - be given as ABGR (little-endian), with A=0). - - @param AColorIndex Palette index of the color to be replaced - @param AColorValue Number containing the rgb components of the new color --------------------------------------------------------------------------------} -procedure TsWorkbook.SetPaletteColor(AColorIndex: TsColor; - AColorValue: TsColorValue); -begin - if (AColorIndex >= 0) and (AColorIndex < GetPaletteSize) then - begin - if ((FPalette = nil) or (Length(FPalette) = 0)) then - DEFAULT_PALETTE[AColorIndex] := AColorValue - else - FPalette[AColorIndex] := AColorValue; - end; -end; - -{@@ ---------------------------------------------------------------------------- - Returns the count of palette colors --------------------------------------------------------------------------------} -function TsWorkbook.GetPaletteSize: Integer; -begin - if (FPalette = nil) or (Length(FPalette) = 0) then - Result := High(DEFAULT_PALETTE) + 1 - else - Result := Length(FPalette); -end; - {@@ ---------------------------------------------------------------------------- Instructs the workbook to take colors from the default palette. Is called from ODS reader because ODS does not have a palette. Without a palette the @@ -7919,7 +7809,7 @@ begin end; Result := false; end; - + *) {******************************************************************************* * TsBasicSpreadReaderWriter * @@ -7963,7 +7853,6 @@ end; procedure TsBasicSpreadWriter.CheckLimitations; var lastCol, lastRow: Cardinal; - i, n: Integer; begin Workbook.GetLastRowColIndex(lastRow, lastCol); @@ -7974,22 +7863,10 @@ begin // Check column count if lastCol >= FLimitations.MaxColCount then Workbook.AddErrorMsg(rsMaxColsExceeded, [lastCol+1, FLimitations.MaxColCount]); - - // Check color count. - n := Workbook.GetPaletteSize; - if n > FLimitations.MaxPaletteSize then - for i:= FLimitations.MaxPaletteSize to n-1 do - if Workbook.UsesColor(i) then - begin - Workbook.AddErrorMsg(rsTooManyPaletteColors, [n, FLimitations.MaxPaletteSize]); - break; - end; end; initialization - // Default palette - MakeLEPalette(@DEFAULT_PALETTE, Length(DEFAULT_PALETTE)); finalization SetLength(GsSpreadFormats, 0); diff --git a/components/fpspreadsheet/fpspreadsheetctrls.pas b/components/fpspreadsheet/fpspreadsheetctrls.pas index 9c86b0d36..e99f4cee6 100644 --- a/components/fpspreadsheet/fpspreadsheetctrls.pas +++ b/components/fpspreadsheet/fpspreadsheetctrls.pas @@ -28,7 +28,7 @@ interface uses Classes, Graphics, SysUtils, Controls, StdCtrls, ComCtrls, ValEdit, ActnList, LResources, - fpstypes, fpspreadsheet, {%H-}fpsAllFormats; + fpstypes, fpspalette, fpspreadsheet, {%H-}fpsAllFormats; type {@@ Event handler procedure for displaying a message if an error or @@ -42,7 +42,7 @@ type TsNotificationItem = (lniWorkbook, lniWorksheet, lniWorksheetAdd, lniWorksheetRemoving, lniWorksheetRemove, lniWorksheetRename, - lniCell, lniSelection, lniAbortSelection, lniRow, lniPalette); + lniCell, lniSelection, lniAbortSelection, lniRow); //, lniPalette); {@@ This set accompanies the notification between WorkbookSource and visual controls and describes which items have changed in the spreadsheet. } TsNotificationItems = set of TsNotificationItem; @@ -78,7 +78,7 @@ type AFormat: TsSpreadsheetFormat; AWorksheetIndex: Integer = 0); procedure SetFileName(const AFileName: TFileName); procedure SetOptions(AValue: TsWorkbookOptions); - procedure WorkbookChangedPaletteHandler(Sender: TObject); +// procedure WorkbookChangedPaletteHandler(Sender: TObject); procedure WorkbookOpenedHandler(Sender: TObject); procedure WorksheetAddedHandler(Sender: TObject; ASheet: TsWorksheet); procedure WorksheetChangedHandler(Sender: TObject; ASheet: TsWorksheet); @@ -436,6 +436,9 @@ type property FixedCols default 0; end; +var + ComboColors: TsPalette = nil; + procedure Register; @@ -445,7 +448,6 @@ uses Types, Math, TypInfo, LCLType, LCLProc, Dialogs, Forms, fpsStrings, fpsUtils; - {@@ ---------------------------------------------------------------------------- Registers the spreadsheet components in the Lazarus component palette, page "FPSpreadsheet". @@ -797,7 +799,7 @@ begin FWorkbook.OnRemovingWorksheet := @WorksheetRemovingHandler; FWorkbook.OnRenameWorksheet := @WorksheetRenamedHandler; FWorkbook.OnSelectWorksheet := @WorksheetSelectedHandler; - FWorkbook.OnChangePalette := @WorkbookChangedPaletteHandler; +// FWorkbook.OnChangePalette := @WorkbookChangedPaletteHandler; // Pass options to workbook SetOptions(FOptions); end; @@ -1240,7 +1242,7 @@ begin EnableControls; end; end; - + (* {@@ ---------------------------------------------------------------------------- Event handler called whenever the palette of the workbook is changed. -------------------------------------------------------------------------------} @@ -1248,7 +1250,7 @@ procedure TsWorkbookSource.WorkbookChangedPaletteHandler(Sender: TObject); begin Unused(Sender); NotifyListeners([lniPalette]); -end; +end; *) {@@ ---------------------------------------------------------------------------- Event handler called whenever a new workbook is opened. @@ -1970,7 +1972,7 @@ begin Brush.Style := bsClear; end else begin - Brush.Color := Workbook.GetPaletteColor(clr); + Brush.Color := clr and $00FFFFFF; Brush.Style := bsSolid; end; Pen.Color := clBlack; @@ -2010,6 +2012,7 @@ procedure TsCellCombobox.ExtractFromCell(ACell: PCell); var fnt: TsFont; clr: TsColor; + idx: Integer; begin case FFormatItem of cfiFontName: @@ -2091,7 +2094,7 @@ var begin Unused(AData); if (Worksheet = nil) or - ([lniCell, lniSelection, lniPalette]*AChangedItems = []) + ([lniCell, lniSelection]*AChangedItems = []) then exit; @@ -2100,11 +2103,9 @@ begin (lniSelection in AChangedItems) then ExtractFromCell(activeCell); - - if (FFormatItem in [cfiFontColor, cfiBorderColor, cfiBackgroundColor]) and - (lniPalette in AChangedItems) - then - Populate; + { + if (FFormatItem in [cfiFontColor, cfiBorderColor, cfiBackgroundColor]) then + Populate; } end; {@@ ---------------------------------------------------------------------------- @@ -2136,6 +2137,7 @@ end; procedure TsCellCombobox.Populate; var i: Integer; + clr: TsColor; begin if Workbook = nil then exit; @@ -2145,18 +2147,19 @@ begin Items.Assign(Screen.Fonts); cfiFontSize: Items.CommaText := '8,9,10,11,12,13,14,16,18,20,22,24,26,28,32,36,48,72'; - cfiFontColor: - for i:=0 to Workbook.GetPaletteSize-1 do - Items.AddObject(Workbook.GetColorName(i), TObject(PtrInt(i))); - cfiBackgroundColor: - begin - Items.AddObject('(none)', TObject(scTransparent)); - for i:=0 to Workbook.GetPaletteSize-1 do - Items.AddObject(Workbook.GetColorName(i), TObject(PtrInt(i))); - end; + cfiBackgroundColor, + cfiFontColor, cfiBorderColor: - for i:=0 to Workbook.GetPaletteSize-1 do - Items.AddObject(Workbook.GetColorName(i), TObject(PtrInt(i))); + begin + Items.Clear; + if FFormatItem = cfiBackgroundColor then + Items.AddObject('(none)', TObject(scTransparent)); + for i:=0 to ComboColors.Count-1 do + begin + clr := ComboColors[i]; + Items.AddObject(GetColorName(clr), TObject(PtrInt(clr))); + end; + end; else raise Exception.Create('[TsCellCombobox.Populate] Unknown cell format item.'); end; @@ -2673,8 +2676,8 @@ begin else AStrings.Add(Format('BorderStyles[%s]=%s, %s', [ GetEnumName(TypeInfo(TsCellBorder), ord(cb)), - GetEnumName(TypeInfo(TsLineStyle), ord(fmt.BorderStyles[cbEast].LineStyle)), - Workbook.GetColorName(fmt.BorderStyles[cbEast].Color)])); + GetEnumName(TypeInfo(TsLineStyle), ord(fmt.BorderStyles[cb].LineStyle)), + GetColorName(fmt.BorderStyles[cb].Color)])); if (ACell = nil) or not (uffBackground in fmt.UsedformattingFields) then begin @@ -2685,10 +2688,10 @@ begin begin AStrings.Add(Format('Style=%s', [ GetEnumName(TypeInfo(TsFillStyle), ord(fmt.Background.Style))])); - AStrings.Add(Format('PatternColor=%d (%s)', [ - fmt.Background.FgColor, Workbook.GetColorName(fmt.Background.FgColor)])); - AStrings.Add(Format('BackgroundColor=%d (%s)', [ - fmt.Background.BgColor, Workbook.GetColorName(fmt.Background.BgColor)])); + AStrings.Add(Format('PatternColor=$%.8x (%s)', [ + fmt.Background.FgColor, GetColorName(fmt.Background.FgColor)])); + AStrings.Add(Format('BackgroundColor=$%.8x (%s)', [ + fmt.Background.BgColor, GetColorName(fmt.Background.BgColor)])); end; if (ACell = nil) or not (uffNumberFormat in fmt.UsedFormattingFields) then @@ -2949,12 +2952,16 @@ initialization CellClipboard := TsCellList.Create; + ComboColors := TsPalette.Create; + ComboColors.AddExcelColors; + RegisterPropertyToSkip(TsSpreadsheetInspector, 'RowHeights', 'For compatibility with older Laz versions.', ''); RegisterPropertyToSkip(TsSpreadsheetInspector, 'ColWidths', 'For compatibility with older Laz versions.', ''); finalization CellClipboard.Free; + if ComboColors <> nil then ComboColors.Free; end. diff --git a/components/fpspreadsheet/fpspreadsheetgrid.pas b/components/fpspreadsheet/fpspreadsheetgrid.pas index eb0c7b10b..642b4114d 100644 --- a/components/fpspreadsheet/fpspreadsheetgrid.pas +++ b/components/fpspreadsheet/fpspreadsheetgrid.pas @@ -247,7 +247,6 @@ type { Utilities related to Workbooks } procedure Convert_sFont_to_Font(sFont: TsFont; AFont: TFont); procedure Convert_Font_to_sFont(AFont: TFont; sFont: TsFont); - function FindNearestPaletteIndex(AColor: TColor): TsColor; { Interfacing with WorkbookSource} procedure ListenerNotification(AChangedItems: TsNotificationItems; @@ -1392,15 +1391,15 @@ begin fsSolidFill: begin Canvas.Brush.Style := bsSolid; - Canvas.Brush.Color := Workbook.GetPaletteColor(fmt^.Background.FgColor); + Canvas.Brush.Color := fmt^.Background.FgColor and $00FFFFFF; end; else if fmt^.Background.BgColor = scTransparent then bgcolor := Color - else bgcolor := Workbook.GetPaletteColor(fmt^.Background.BgColor); + else bgcolor := fmt^.Background.BgColor and $00FFFFFF; if fmt^.Background.FgColor = scTransparent then fgcolor := Color - else fgcolor := Workbook.GetPaletteColor(fmt^.Background.FgColor); + else fgcolor := fmt^.Background.FgColor and $00FFFFFF; CreateFillPattern(FillPatternBitmap, fmt^.Background.Style, fgColor, bgColor); Canvas.Brush.Style := bsImage; Canvas.Brush.Bitmap := FillPatternBitmap; @@ -1424,7 +1423,7 @@ begin begin Canvas.Font.Name := fnt.FontName; Canvas.Font.Size := round(fnt.Size); - Canvas.Font.Color := Workbook.GetPaletteColor(fnt.Color); + Canvas.Font.Color := fnt.Color and $00FFFFFF; style := []; if fssBold in fnt.Style then Include(style, fsBold); if fssItalic in fnt.Style then Include(style, fsItalic); @@ -1444,7 +1443,7 @@ begin if (nfkHasColor in numFmt.Sections[sidx].Kind) then begin clr := numFmt.Sections[sidx].Color; - Canvas.Font.Color := Workbook.GetPaletteColor(clr); + Canvas.Font.Color := clr and $00FFFFFF; end; end; // Wordwrap, text alignment and text rotation are handled by "DrawTextInCell". @@ -1569,7 +1568,7 @@ const begin Canvas.Pen.Style := PEN_STYLES[ABorderStyle.LineStyle]; Canvas.Pen.Width := PEN_WIDTHS[ABorderStyle.LineStyle]; - Canvas.Pen.Color := Workbook.GetPaletteColor(ABorderStyle.Color); + Canvas.Pen.Color := ABorderStyle.Color and $00FFFFFF; Canvas.Pen.EndCap := pecSquare; width3 := (ABorderStyle.LineStyle in [lsThick, lsDouble]); @@ -2282,6 +2281,7 @@ begin end; end; +(* {@@ ---------------------------------------------------------------------------- The "colors" used by the spreadsheet are indexes into the workbook's color palette. If the user wants to set a color to a particular RGB value this is @@ -2294,7 +2294,7 @@ function TsCustomWorksheetGrid.FindNearestPaletteIndex(AColor: TColor): TsColor; begin Result := fpsVisualUtils.FindNearestPaletteIndex(Workbook, AColor); end; - + *) (* {@@ ---------------------------------------------------------------------------- Notification by the workbook link that a cell has been modified. --> Repaint. diff --git a/components/fpspreadsheet/fpsreaderwriter.pas b/components/fpspreadsheet/fpsreaderwriter.pas index 1314e29bf..35dd59999 100644 --- a/components/fpspreadsheet/fpsreaderwriter.pas +++ b/components/fpspreadsheet/fpsreaderwriter.pas @@ -95,7 +95,7 @@ type procedure AddBuiltinNumFormats; virtual; function FindNumFormatInList(ANumFormatStr: String): Integer; - function FixColor(AColor: TsColor): TsColor; virtual; +// function FixColor(AColor: TsColor): TsColor; virtual; procedure FixFormat(ACell: PCell); virtual; procedure GetSheetDimensions(AWorksheet: TsWorksheet; out AFirstRow, ALastRow, AFirstCol, ALastCol: Cardinal); virtual; @@ -404,7 +404,7 @@ begin exit; Result := -1; end; - + (* {@@ ---------------------------------------------------------------------------- If a color index is greater then the maximum palette color count this color is replaced by the closest palette color. @@ -420,7 +420,7 @@ function TsCustomSpreadWriter.FixColor(AColor: TsColor): TsColor; begin Result := AColor; end; - + *) {@@ ---------------------------------------------------------------------------- If formatting features of a cell are not supported by the destination file format of the writer, here is the place to apply replacements. diff --git a/components/fpspreadsheet/fpsstrings.pas b/components/fpspreadsheet/fpsstrings.pas index 1efafed55..41892224f 100644 --- a/components/fpspreadsheet/fpsstrings.pas +++ b/components/fpspreadsheet/fpsstrings.pas @@ -67,6 +67,66 @@ resourcestring rsCannotSortMerged = 'The cell range cannot be sorted because it contains merged cells.'; + // Colors + rsAqua = 'aqua'; + rsBeige = 'beige'; + rsBlack = 'black'; + rsBlue = 'blue'; + rsBlueGray = 'blue gray'; + rsBrown = 'brown'; + rsCoral = 'coral'; + rsCyan = 'cyan'; + rsDarkBlue = 'dark blue'; + rsDarkGreen = 'dark green'; + rsDarkPurple = 'dark purple'; + rsDarkRed = 'dark red'; + rsDarkTeal = 'dark teal'; + rsGold = 'gold'; + rsGray = 'gray'; + rsGray10pct = '10% gray'; + rsGray20pct = '20% gray'; + rsGray25pct = '25% gray'; + rsGray40pct = '40% gray'; + rsGray50pct = '50% gray'; + rsGray80pct = '80% gray'; + rsGreen = 'green'; + rsIceBlue = 'ice blue'; + rsIndigo = 'indigo'; + rsIvory = 'ivory'; + rsLavander = 'lavander'; + rsLightBlue = 'light blue'; + rsLightGreen = 'light green'; + rsLightOrange = 'light orange'; + rsLightTurquoise = 'light turquoise'; + rsLightYellow = 'light yellow'; + rsLime = 'lime'; + rsMagenta = 'magenta'; + rsNavy = 'navy'; + rsOceanBlue = 'ocean blue'; + rsOlive = 'olive'; + rsOliveGreen = 'olive green'; + rsOrange = 'orange'; + rsPaleBlue = 'pale blue'; + rsPeriwinkle = 'periwinkle'; + rsPink = 'pink'; + rsPlum = 'plum'; + rsPurple = 'purple'; + rsRed = 'red'; + rsRose = 'rose'; + rsSeaGreen = 'sea green'; + rsSilver = 'silver'; + rsSkyBlue = 'sky blue'; + rsTan = 'tan'; + rsTeal = 'teal'; + rsVeryDarkGreen = 'very dark green'; + rsViolet = 'violet'; + rsWheat = 'wheat'; + rsWhite = 'white'; + rsYellow = 'yellow'; + + rsNotDefined = 'not defined'; + rsTransparent = 'transparent'; + rsTRUE = 'TRUE'; // wp: Do we really want to translate these strings? rsFALSE = 'FALSE'; rsErrEmptyIntersection = '#NULL!'; diff --git a/components/fpspreadsheet/fpstypes.pas b/components/fpspreadsheet/fpstypes.pas index 6f8798f06..03698ae2d 100644 --- a/components/fpspreadsheet/fpstypes.pas +++ b/components/fpspreadsheet/fpstypes.pas @@ -264,92 +264,147 @@ type {@@ Indicates vertical text alignment in cells } TsVertAlignment = (vaDefault, vaTop, vaCenter, vaBottom); - {@@ - Colors in fpspreadsheet are given as indices into a palette. - Use the workbook's GetPaletteColor to determine the color rgb value as - little-endian (with "r" being the low-value byte, in agreement with TColor). - The data type for rgb values is TsColorValue. } - TsColor = Word; + {@@ Colors in fpspreadsheet are given as rgb values in little-endian notation + (i.e. "r" is the low-value byte). The highest-value byte, if not zero, + indicates special colors. } + TsColor = DWord; -{@@ - These are some constants for color indices into the default palette. - Note, however, that if a different palette is used there may be more colors, - and the names of the color constants may no longer be correct. -} const - {@@ Index of black color in the standard color palettes } - scBlack = $00; - {@@ Index of white color in the standard color palettes } - scWhite = $01; - {@@ Index of red color in the standard color palettes } - scRed = $02; - {@@ Index of green color in the standard color palettes } - scGreen = $03; - {@@ Index of blue color in the standard color palettes } - scBlue = $04; - {@@ Index of yellow color in the standard color palettes } - scYellow = $05; - {@@ Index of magenta color in the standard color palettes } - scMagenta = $06; - {@@ Index of cyan color in the standard color palettes } - scCyan = $07; - {@@ Index of dark red color in the standard color palettes } - scDarkRed = $08; - {@@ Index of dark green color in the standard color palettes } - scDarkGreen = $09; - {@@ Index of dark blue color in the standard color palettes } - scDarkBlue = $0A; - {@@ Index of "navy" color (dark blue) in the standard color palettes } - scNavy = $0A; - {@@ Index of olive color in the standard color palettes } - scOlive = $0B; - {@@ Index of purple color in the standard color palettes } - scPurple = $0C; - {@@ Index of teal color in the standard color palettes } - scTeal = $0D; - {@@ Index of silver color in the standard color palettes } - scSilver = $0E; - {@@ Index of grey color in the standard color palettes } - scGrey = $0F; - {@@ Index of gray color in the standard color palettes } - scGray = $0F; // redefine to allow different spelling - {@@ Index of a 10% grey color in the standard color palettes } - scGrey10pct = $10; - {@@ Index of a 10% gray color in the standard color palettes } - scGray10pct = $10; - {@@ Index of a 20% grey color in the standard color palettes } - scGrey20pct = $11; - {@@ Index of a 20% gray color in the standard color palettes } - scGray20pct = $11; - {@@ Index of orange color in the standard color palettes } - scOrange = $12; - {@@ Index of dark brown color in the standard color palettes } - scDarkbrown = $13; - {@@ Index of brown color in the standard color palettes } - scBrown = $14; - {@@ Index of beige color in the standard color palettes } - scBeige = $15; - {@@ Index of "wheat" color (yellow-orange) in the standard color palettes } - scWheat = $16; + {@@ These are some important rgb color volues. + } + {@@ rgb value of black color, BIFF2 palette index 0, BIFF8 index 8} + scBlack = $00000000; + {@@ rgb value of white color, BIFF2 palette index 1, BIFF8 index 9 } + scWhite = $00FFFFFF; + {@@ rgb value of red color, BIFF2 palette index 2, BIFF8 index 10 } + scRed = $000000FF; + {@@ rgb value of green color, BIFF2 palette index 3, BIFF8 index 11 } + scGreen = $0000FF00; + {@@ rgb value of blue color, BIFF2 palette index 4, BIFF8 indexes 12 and 39} + scBlue = $00FF0000; + {@@ rgb value of yellow color, BIFF2 palette index 5, BIFF8 indexes 13 and 34} + scYellow = $0000FFFF; + {@@ rgb value of magenta color, BIFF2 palette index 6, BIFF8 index 14 and 33} + scMagenta = $00FF00FF; + scPink = $00FE00FE; + {@@ rgb value of cyan color, BIFF2 palette index 7, BIFF8 indexes 15} + scCyan = $00FFFF00; + scTurquoise = scCyan; + {@@ rgb value of dark red color, BIFF8 indexes 16 and 35} + scDarkRed = $00000080; + {@@ rgb value of dark green color, BIFF8 index 17 } + scDarkGreen = $00008000; + {@@ rgb value of dark blue color } + scDarkBlue = $008B0000; + {@@ rgb value of "navy" color, BIFF8 palette indexes 18 and 32 } + scNavy = $00800000; + {@@ rgb value of olive color } + scOlive = $00008080; + {@@ rgb value of purple color, BIFF8 palette indexes 20 and 36 } + scPurple = $00800080; + {@@ rgb value of teal color, BIFF8 palette index 21 and 38 } + scTeal = $00808000; + {@@ rgb value of silver color } + scSilver = $00C0C0C0; + scGray25pct = scSilver; + {@@ rgb value of grey color } + scGray = $00808080; + {@@ rgb value of gray color } + scGrey = scGray; // redefine to allow different spelling + scGray50pct = scGray; + {@@ rgb value of a 10% grey color } + scGray10pct = $00E6E6E6; + {@@ rgb value of a 10% gray color } + scGrey10pct = scGray10pct; + {@@ rgb value of a 20% grey color } + scGray20pct = $00CCCCCC; + {@@ rgb value of a 20% gray color } + scGrey20pct = scGray20pct; + {@@ rgb value of periwinkle color, BIFF8 palette index 24 } + scPeriwinkle = $00FF9999; + {@@ rgb value of plum color, BIFF8 palette indexes 25 and 61 } + scPlum = $00663399; + {@@ rgb value of ivory color, BIFF8 palette index 26 } + scIvory = $00CCFFFF; + {@@ rgb value of light turquoise color, BIFF8 palette indexes 27 and 41 } + scLightTurquoise = $00FFFFCC; + {@@ rgb value of dark purple color, BIFF8 palette index 28 } + scDarkPurple = $00660066; + {@@ rgb value of coral color, BIFF8 palette index 29 } + scCoral = $008080FF; + {@@ rgb value of ocean blue color, BIFF8 palette index 30 } + scOceanBlue = $00CC6600; + {@@ rgb value of ice blue color, BIFF8 palette index 31 } + scIceBlue = $00FFCCCC; + {@@ rgb value of sky blue color, BIFF8 palette index 40 } + scSkyBlue = $00FFCC00; + {@@ rgb value of light green color, BIFF8 palette index 42 } + scLightGreen = $00CCFFCC; + {@@ rgb value of light yellow color, BIFF8 palette index 43 } + scLightYellow = $0099FFFF; + {@@ rgb value of pale blue color, BIFF8 palette index 44 } + scPaleBlue = $00FFCC99; + {@@ rgb value of rose color, BIFF8 palette index 45 } + scRose = $00CC99FF; + {@@ rgb value of lavander color, BIFF8 palette index 46 } + scLavander = $00FF99CC; + {@@ rgb value of tan color, BIFF8 palette index 47 } + scTan = $0099CCFF; + {@@ rgb value of light blue color, BIFF8 palette index 48 } + scLightBlue = $00FF6633; + {@@ rgb value of aqua color, BIFF8 palette index 49 } + scAqua = $00CCCC33; + {@@ rgb value of lime color, BIFF8 palette index 50 } + scLime = $0000CC99; + {@@ rgb value of golden color, BIFF8 palette index 51 } + scGold = $0000CCFF; + {@@ rgb value of light orange color, BIFF8 palette index 52 } + scLightOrange = $000099FF; + {@@ rgb value of orange color, BIFF8 palette index 53 } + scOrange = $000066FF; + {@@ rgb value of blue gray, BIFF8 palette index 54 } + scBlueGray = $00996666; + scBlueGrey = scBlueGray; + {@@ rgb value of gray 40%, BIFF8 palette index 55 } + scGray40pct = $00969696; + {@@ rgb value of dark teal, BIFF8 palette index 56 } + scDarkTeal = $00663300; + {@@ rgb value of sea green, BIFF8 palette index 57 } + scSeaGreen = $00669933; + {@@ rgb value of very dark green, BIFF8 palette index 58 } + scVeryDarkGreen = $00003300; + {@@ rgb value of olive green color, BIFF8 palette index 59 } + scOliveGreen = $00003333; + {@@ rgb value of brown color, BIFF8 palette index 60 } + scBrown = $00003399; + {@@ rgb value of indigo color, BIFF8 palette index 62 } + scIndigo = $00993333; + {@@ rgb value of 80% gray, BIFF8 palette index 63 } + scGray80pct = $00333333; + scGrey80pct = scGray80pct; - // not sure - but I think the mechanism with scRGBColor is not working... - // Will be removed sooner or later... - scRGBColor = $FFFD; +// {@@ rgb value of orange color } +// scOrange = $0000A5FF; + {@@ rgb value of dark brown color } + scDarkBrown = $002D52A0; + +// {@@ rgb value of brown color } +// scBrown = $003F85CD; + {@@ rgb value of beige color } + scBeige = $00DCF5F5; + {@@ rgb value of "wheat" color (yellow-orange) } + scWheat = $00B3DEF5; - {@@ Identifier for transparent color } - scTransparent = $FFFE; {@@ Identifier for not-defined color } - scNotDefined = $FFFF; + scNotDefined = $40000000; + {@@ Identifier for transparent color } + scTransparent = $20000000; + {@@ Identifier for palette index encoded into the TsColor } + scPaletteIndexMask = $80000000; + {@@ Mask for the rgb components contained in the TsColor } + scRGBMask = $00FFFFFF; type - {@@ Data type for rgb color values } - TsColorValue = DWord; - - {@@ Palette of color values. A "color value" is a DWord value containing - rgb colors. } - TsPalette = array[0..0] of TsColorValue; - PsPalette = ^TsPalette; - {@@ Font style (redefined to avoid usage of "Graphics" } TsFontStyle = (fssBold, fssItalic, fssStrikeOut, fssUnderline); @@ -365,7 +420,7 @@ type Size: Single; // in "points" {@@ Font style, such as bold, italics etc. - see TsFontStyle} Style: TsFontStyles; - {@@ Text color given by the index into the workbook's color palette } + {@@ Text color given as rgb value } Color: TsColor; end; diff --git a/components/fpspreadsheet/fpsutils.pas b/components/fpspreadsheet/fpsutils.pas index 7181bb1a3..6c0758565 100644 --- a/components/fpspreadsheet/fpsutils.pas +++ b/components/fpspreadsheet/fpsutils.pas @@ -28,6 +28,9 @@ type {@@ Set of characters } TsDecsChars = set of char; + {@@ Color value, composed of r(ed), g(reen) and b(lue) components } + TRGBA = record r, g, b, a: byte end; + const {@@ Date formatting string for unambiguous date/time display as strings Can be used for text output when date/time cell support is not available } @@ -51,8 +54,6 @@ function WordLEtoN(AValue: Word): Word; function DWordLEtoN(AValue: Cardinal): Cardinal; function WideStringLEToN(const AValue: WideString): WideString; -function LongRGBToExcelPhysical(const RGB: DWord): DWord; - // Other routines function ParseIntervalString(const AStr: string; out AFirstCellRow, AFirstCellCol, ACount: Cardinal; @@ -140,13 +141,18 @@ function pxToPts(AValue, AScreenPixelsPerInch: Integer): Double; inline; function PtsToPx(AValue: Double; AScreenPixelsPerInch: Integer): Integer; inline; function HTMLLengthStrToPts(AValue: String; DefaultUnits: String = 'pt'): Double; -function HTMLColorStrToColor(AValue: String): TsColorValue; -function ColorToHTMLColorStr(AValue: TsColorValue; AExcelDialect: Boolean = false): String; function UTF8TextToXMLText(AText: ansistring): ansistring; function ValidXMLText(var AText: ansistring; ReplaceSpecialChars: Boolean = true): Boolean; -function TintedColor(AColor: TsColorValue; tint: Double): TsColorValue; -function HighContrastColor(AColorValue: TsColorValue): TsColor; +function ColorToHTMLColorStr(AValue: TsColor; AExcelDialect: Boolean = false): String; +function HTMLColorStrToColor(AValue: String): TsColor; + +function GetColorName(AColor: TsColor): String; +function HighContrastColor(AColor: TsColor): TsColor; +function IsPaletteIndex(AColor: TsColor): Boolean; +function LongRGBToExcelPhysical(const RGB: DWord): DWord; +function SetAsPaletteIndex(AIndex: Integer): TsColor; +function TintedColor(AColor: TsColor; tint: Double): TsColor; function AnalyzeCompareStr(AString: String; out ACompareOp: TsCompareOperation): String; @@ -183,9 +189,6 @@ implementation uses Math, lazutf8, fpsStrings; -type - TRGBA = record r, g, b, a: byte end; - const POS_CURR_FMT: array[0..3] of string = ( // Format parameter 0 is "value", parameter 1 is "currency symbol" @@ -356,29 +359,6 @@ begin {$ENDIF} end; -{@@ ---------------------------------------------------------------------------- - Converts the RGB part of a LongRGB logical structure to its physical representation. - In other words: RGBA (where A is 0 and omitted in the function call) => ABGR - Needed for conversion of palette colors. - - @param RGB DWord value containing RGBA bytes in big endian byte-order - @return DWord containing RGB bytes in little-endian byte-order (A = 0) --------------------------------------------------------------------------------} -function LongRGBToExcelPhysical(const RGB: DWord): DWord; -begin - {$IFDEF FPC} - {$IFDEF ENDIAN_LITTLE} - result := RGB shl 8; //tags $00 at end for the A byte - result := SwapEndian(result); //flip byte order - {$ELSE} - //Big endian - result := RGB; //leave value as is //todo: verify if this turns out ok - {$ENDIF} - {$ELSE} - // messed up result - {$ENDIF} -end; - {@@ ---------------------------------------------------------------------------- Parses strings like A5:A10 into an selection interval information @@ -1968,13 +1948,83 @@ begin end; {@@ ---------------------------------------------------------------------------- - Converts a HTML color string to a TsColorValue. Need for the ODS file format. + Determines the name of a color from its rgb value +-------------------------------------------------------------------------------} +function GetColorName(AColor: TsColor): string; +var + rgba: TRGBA absolute AColor; +begin + case AColor of + scAqua : Result := rsAqua; + scBeige : Result := rsBeige; + scBlack : Result := rsBlack; + scBlue : Result := rsBlue; + scBlueGray : Result := rsBlueGray; + scBrown : Result := rsBrown; + scCoral : Result := rsCoral; + scCyan : Result := rsCyan; + scDarkBlue : Result := rsDarkBlue; + scDarkGreen : Result := rsDarkGreen; + scDarkPurple : Result := rsDarkPurple; + scDarkRed : Result := rsDarkRed; + scDarkTeal : Result := rsDarkTeal; + scGold : Result := rsGold; + scGray : Result := rsGray; + scGray10pct : Result := rsGray10pct; + scGray20pct : Result := rsGray20pct; + scGray40pct : Result := rsGray40pct; + scGray80pct : Result := rsGray80pct; + scGreen : Result := rsGreen; + scIceBlue : Result := rsIceBlue; + scIndigo : Result := rsIndigo; + scIvory : Result := rsIvory; + scLavander : Result := rsLavander; + scLightBlue : Result := rsLightBlue; + scLightGreen : Result := rsLightGreen; + scLightOrange: Result := rsLightOrange; + scLightTurquoise: Result := rsLightTurquoise; + scLightYellow: Result := rsLightYellow; + scLime : Result := rsLime; + scMagenta : Result := rsMagenta; + scNavy : Result := rsNavy; + scOceanBlue : Result := rsOceanBlue; + scOlive : Result := rsOlive; + scOliveGreen : Result := rsOliveGreen; + scOrange : Result := rsOrange; + scPaleBlue : Result := rsPaleBlue; + scPeriwinkle : Result := rsPeriwinkle; + scPink : Result := rsPink; + scPlum : Result := rsPlum; + scPurple : Result := rsPurple; + scRed : Result := rsRed; + scRose : Result := rsRose; + scSeaGreen : Result := rsSeaGreen; + scSilver : Result := rsSilver; + scSkyBlue : Result := rsSkyBlue; + scTan : Result := rsTan; + scTeal : Result := rsTeal; + scVeryDarkGreen: Result := rsVeryDarkGreen; +// scViolet : Result := rsViolet; + scWheat : Result := rsWheat; + scWhite : Result := rsWhite; + scYellow : Result := rsYellow; + scTransparent: Result := rsTransparent; + scNotDefined : Result := rsNotDefined; + else if rgba.a = 0 then + Result := Format('r%d g%d b%d', [rgba.r, rgba.g, rgba.b]) + else + Result := ''; + end; +end; + +{@@ ---------------------------------------------------------------------------- + Converts a HTML color string to a TsColor alue. Needed for the ODS file format. @param AValue HTML color string, such as '#FF0000' @return rgb color value in little endian byte-sequence. This value is compatible with the TColor data type of the graphics unit. -------------------------------------------------------------------------------} -function HTMLColorStrToColor(AValue: String): TsColorValue; +function HTMLColorStrToColor(AValue: String): TsColor; begin if AValue = '' then Result := scNotDefined @@ -2022,13 +2072,11 @@ end; i.e. in AARRGGBB notation, like '00FF0000' for "red" @return HTML-compatible string, like '#FF0000' (AExcelDialect = false) -------------------------------------------------------------------------------} -function ColorToHTMLColorStr(AValue: TsColorValue; AExcelDialect: Boolean = false): String; -type - TRGB = record r,g,b,a: Byte end; +function ColorToHTMLColorStr(AValue: TsColor; + AExcelDialect: Boolean = false): String; var - rgb: TRGB; + rgb: TRGBA absolute AValue; begin - rgb := TRGB(AValue); if AExcelDialect then Result := Format('00%.2x%.2x%.2x', [rgb.r, rgb.g, rgb.b]) else @@ -3069,6 +3117,23 @@ begin end; end; +{@@ ---------------------------------------------------------------------------- + Constructs a TsColor from a palette index. It has bit 15 in the high-order + byte set. +-------------------------------------------------------------------------------} +function SetAsPaletteIndex(AIndex: Integer): TsColor; +begin + Result := (DWord(AIndex) and scRGBMask) or scPaletteIndexMask; +end; + +{@@ ---------------------------------------------------------------------------- + Checks whether the specified TsColor represents a palette index +-------------------------------------------------------------------------------} +function IsPaletteIndex(AColor: TsColor): Boolean; +begin + Result := AColor and scPaletteIndexMask = scPaletteIndexMask; +end; + {@@ ---------------------------------------------------------------------------- Excel defines theme colors and applies a "tint" factor (-1...+1) to darken or brighten them. @@ -3082,7 +3147,7 @@ end; @param tint Factor (-1...+1) to be used for the operation @return Modified color -------------------------------------------------------------------------------} -function TintedColor(AColor: TsColorValue; tint: Double): TsColorValue; +function TintedColor(AColor: TsColor; tint: Double): TsColor; const HLSMAX = 255; var @@ -3090,7 +3155,7 @@ var h, l, s: Byte; lum: Double; begin - if tint = 0 then begin + if (tint = 0) or (TRGBA(AColor).a <> 0) then begin Result := AColor; exit; end; @@ -3119,18 +3184,42 @@ end; Returns the color index for black or white depending on a color being "bright" or "dark". - @param AColorValue rgb color to be analyzed + @param AColor rgb color to be analyzed @return The color index for black (scBlack) if AColorValue is a "bright" color, or white (scWhite) if AColorValue is a "dark" color. -------------------------------------------------------------------------------} -function HighContrastColor(AColorValue: TsColorvalue): TsColor; +function HighContrastColor(AColor: TsColor): TsColor; begin - if TRGBA(AColorValue).r + TRGBA(AColorValue).g + TRGBA(AColorValue).b < 3*128 then + if TRGBA(AColor).r + TRGBA(AColor).g + TRGBA(AColor).b < 3*128 then Result := scWhite else Result := scBlack; end; +{@@ ---------------------------------------------------------------------------- + Converts the RGB part of a LongRGB logical structure to its physical representation. + In other words: RGBA (where A is 0 and omitted in the function call) => ABGR + Needed for conversion of palette colors. + + @param RGB DWord value containing RGBA bytes in big endian byte-order + @return DWord containing RGB bytes in little-endian byte-order (A = 0) +-------------------------------------------------------------------------------} +function LongRGBToExcelPhysical(const RGB: DWord): DWord; +begin + {$IFDEF FPC} + {$IFDEF ENDIAN_LITTLE} + result := RGB shl 8; //tags $00 at end for the A byte + result := SwapEndian(result); //flip byte order + {$ELSE} + //Big endian + result := RGB; //leave value as is //todo: verify if this turns out ok + {$ENDIF} + {$ELSE} + // messed up result + {$ENDIF} +end; + + {$PUSH}{$HINTS OFF} {@@ Silence warnings due to an unused parameter } procedure Unused(const A1); diff --git a/components/fpspreadsheet/fpsvisualutils.pas b/components/fpspreadsheet/fpsvisualutils.pas index 56108ddb1..953a357f3 100644 --- a/components/fpspreadsheet/fpsvisualutils.pas +++ b/components/fpspreadsheet/fpsvisualutils.pas @@ -10,7 +10,7 @@ uses procedure Convert_sFont_to_Font(AWorkbook: TsWorkbook; sFont: TsFont; AFont: TFont); procedure Convert_Font_to_sFont(AWorkbook: TsWorkbook; AFont: TFont; sFont: TsFont); -function FindNearestPaletteIndex(AWorkbook: TsWorkbook; AColor: TColor): TsColor; +//function FindNearestPaletteIndex(AWorkbook: TsWorkbook; AColor: TColor): TsColor; function WrapText(ACanvas: TCanvas; const AText: string; AMaxWidth: integer): string; @@ -36,7 +36,7 @@ begin if fssItalic in sFont.Style then AFont.Style := AFont.Style + [fsItalic]; if fssUnderline in sFont.Style then AFont.Style := AFont.Style + [fsUnderline]; if fssStrikeout in sFont.Style then AFont.Style := AFont.Style + [fsStrikeout]; - AFont.Color := AWorkbook.GetPaletteColor(sFont.Color); + AFont.Color := TColor(sFont.Color and $00FFFFFF); end; end; @@ -56,10 +56,10 @@ begin if fsItalic in AFont.Style then Include(sFont.Style, fssItalic); if fsUnderline in AFont.Style then Include(sFont.Style, fssUnderline); if fsStrikeout in AFont.Style then Include(sFont.Style, fssStrikeout); - sFont.Color := FindNearestPaletteIndex(AWorkbook, AFont.Color); + sFont.Color := ColorToRGB(AFont.Color); end; end; - + (* function FindNearestPaletteIndex(AWorkbook: TsWorkbook; AColor: TColor): TsColor; procedure ColorToHSL(RGB: TColor; out H, S, L : double); @@ -148,7 +148,7 @@ begin end; end; end; - + *) {@@ ---------------------------------------------------------------------------- Wraps text by inserting line ending characters so that the lines are not longer than AMaxWidth. diff --git a/components/fpspreadsheet/laz_fpspreadsheet.lpk b/components/fpspreadsheet/laz_fpspreadsheet.lpk index 695f264a8..b2e39f87f 100644 --- a/components/fpspreadsheet/laz_fpspreadsheet.lpk +++ b/components/fpspreadsheet/laz_fpspreadsheet.lpk @@ -28,7 +28,7 @@ This package is all you need if you don't want graphical components (like grids and charts)."/> - + @@ -165,6 +165,10 @@ This package is all you need if you don't want graphical components (like grids + + + + diff --git a/components/fpspreadsheet/laz_fpspreadsheet.pas b/components/fpspreadsheet/laz_fpspreadsheet.pas index 9bfbb97f5..f1a5d79b3 100644 --- a/components/fpspreadsheet/laz_fpspreadsheet.pas +++ b/components/fpspreadsheet/laz_fpspreadsheet.pas @@ -13,7 +13,7 @@ uses uvirtuallayer_ole_helpers, uvirtuallayer_ole_types, uvirtuallayer_stream, fpolebasic, wikitable, fpsNumFormatParser, fpsfunc, fpsRPN, fpsStrings, fpscsv, fpsCsvDocument, fpspatches, fpsTypes, xlsEscher, fpsReaderWriter, - fpsNumFormat, fpsclasses, fpsHeaderFooterParser; + fpsNumFormat, fpsclasses, fpsHeaderFooterParser, fpsPalette; implementation diff --git a/components/fpspreadsheet/tests/colortests.pas b/components/fpspreadsheet/tests/colortests.pas index 0e7ebba9e..31a7253b7 100644 --- a/components/fpspreadsheet/tests/colortests.pas +++ b/components/fpspreadsheet/tests/colortests.pas @@ -80,8 +80,12 @@ type procedure TestWriteRead_OOXML_Font_RandomPal; // palette 64, top 56 entries random end; + implementation +uses + fpsPalette; + const ColorsSheet = 'Colors'; @@ -111,79 +115,76 @@ var row, col: Integer; MyCell: PCell; TempFile: string; //write xls/xml to this file and read back from it - color: TsColor; expectedRGB: DWord; currentRGB: DWord; - pal: Array of TsColorValue; + palette: TsPalette; i: Integer; begin TempFile:=GetTempFileName; - MyWorkbook := TsWorkbook.Create; + // Define palette + palette := TsPalette.Create; try - MyWorkSheet:= MyWorkBook.AddWorksheet(ColorsSheet); - - // Define palette case whichPalette of - 5: MyWorkbook.UsePalette(@PALETTE_BIFF5, Length(PALETTE_BIFF5)); - 8: MyWorkbook.UsePalette(@PALETTE_BIFF8, Length(PALETTE_BIFF8)); - 999: begin // Random palette: testing of color replacement - MyWorkbook.UsePalette(@PALETTE_BIFF8, Length(PALETTE_BIFF8)); - for i:=8 to 63 do // first 8 colors cannot be changed - MyWorkbook.SetPaletteColor(i, random(256) + random(256) shr 8 + random(256) shr 16); + 5: palette.UseColors(PALETTE_BIFF5); + 8: palette.UseColors(PALETTE_BIFF8); + 999: begin // random palette: testing of color replacement + palette.UseColors(PALETTE_BIFF8); + for i:=8 to 63 do // first 8 colors must not be changed in Excel + palette[i] := random(256) + random(256) shr 8 + random(256) shr 16; end; - // else use default palette + else palette.AddBuiltinColors; end; - // Remember all colors because ODS does not have a palette in the file; therefore - // we do not know which colors to expect. - SetLength(pal, MyWorkbook.GetPaletteSize); - for i:=0 to High(pal) do - pal[i] := MyWorkbook.GetPaletteColor(i); + MyWorkbook := TsWorkbook.Create; + try + MyWorkSheet:= MyWorkBook.AddWorksheet(ColorsSheet); - // Write out all colors - row := 0; - col := 0; - for color := 0 to MyWorkbook.GetPaletteSize-1 do begin - MyWorksheet.WriteUTF8Text(row, col, CELLTEXT); - MyWorksheet.WriteBackgroundColor(row, col, color); - MyCell := MyWorksheet.FindCell(row, col); - if MyCell = nil then - fail('Error in test code. Failed to get cell.'); - currentRGB := MyWorkbook.GetPaletteColor(MyWorksheet.ReadBackgroundColor(MyCell)); - expectedRGB := MyWorkbook.GetPaletteColor(color); - CheckEquals(expectedRGB, currentRGB, - 'Test unsaved background color, cell ' + CellNotation(MyWorksheet,0,0)); - inc(row); + // Write out all colors + row := 0; + col := 0; + for i := 0 to palette.Count-1 do begin + MyWorksheet.WriteUTF8Text(row, col, CELLTEXT); + MyCell := MyWorksheet.WriteBackgroundColor(row, col, palette[i]); + if MyCell = nil then + fail('Error in test code. Failed to get cell.'); + currentRGB := MyWorksheet.ReadBackgroundColor(MyCell); + expectedRGB := palette[i]; + CheckEquals(expectedRGB, currentRGB, + 'Test unsaved background color, cell ' + CellNotation(MyWorksheet,0,0)); + inc(row); + end; + MyWorkBook.WriteToFile(TempFile, AFormat, true); + finally + MyWorkbook.Free; end; - MyWorkBook.WriteToFile(TempFile, AFormat, true); + + // Open the spreadsheet + MyWorkbook := TsWorkbook.Create; + try + MyWorkbook.ReadFromFile(TempFile, AFormat); + if AFormat = sfExcel2 then + MyWorksheet := MyWorkbook.GetFirstWorksheet + else + MyWorksheet := GetWorksheetByName(MyWorkBook, ColorsSheet); + if MyWorksheet=nil then + fail('Error in test code. Failed to get named worksheet'); + for row := 0 to MyWorksheet.GetLastRowIndex do begin + MyCell := MyWorksheet.FindCell(row, col); + if MyCell = nil then + fail('Error in test code. Failed to get cell.'); + currentRGB := MyWorksheet.ReadBackgroundColor(MyCell); + expectedRGB := palette[row]; + CheckEquals(expectedRGB, currentRGB, + 'Test saved background color, cell '+CellNotation(MyWorksheet,Row,Col)); + end; + finally + MyWorkbook.Free; + DeleteFile(TempFile); + end; + finally - MyWorkbook.Free; - end; - - // Open the spreadsheet - MyWorkbook := TsWorkbook.Create; - try - MyWorkbook.ReadFromFile(TempFile, AFormat); - if AFormat = sfExcel2 then - MyWorksheet := MyWorkbook.GetFirstWorksheet - else - MyWorksheet := GetWorksheetByName(MyWorkBook, ColorsSheet); - if MyWorksheet=nil then - fail('Error in test code. Failed to get named worksheet'); - for row := 0 to MyWorksheet.GetLastRowIndex do begin - MyCell := MyWorksheet.FindCell(row, col); - if MyCell = nil then - fail('Error in test code. Failed to get cell.'); - color := TsColor(row); - currentRGB := MyWorkbook.GetPaletteColor(MyWorksheet.ReadBackgroundColor(MyCell)); - expectedRGB := pal[color]; - CheckEquals(expectedRGB, currentRGB, - 'Test saved background color, cell '+CellNotation(MyWorksheet,Row,Col)); - end; - finally - MyWorkbook.Free; - DeleteFile(TempFile); + palette.Free end; end; @@ -201,87 +202,82 @@ var row, col: Integer; MyCell: PCell; TempFile: string; //write xls/xml to this file and read back from it - color, colorInFile: TsColor; expectedRGB, currentRGB: DWord; - pal: Array of TsColorValue; + palette: TsPalette; i: Integer; begin TempFile:=GetTempFileName; - MyWorkbook := TsWorkbook.Create; + // Define palette + palette := TsPalette.Create; try - MyWorkSheet:= MyWorkBook.AddWorksheet(ColorsSheet); - - // Define palette case whichPalette of - 5: MyWorkbook.UsePalette(@PALETTE_BIFF5, High(PALETTE_BIFF5)+1); - 8: MyWorkbook.UsePalette(@PALETTE_BIFF8, High(PALETTE_BIFF8)+1); - 999: begin // Random palette: testing of color replacement - MyWorkbook.UsePalette(@PALETTE_BIFF8, Length(PALETTE_BIFF8)); - for i:=8 to 63 do // first 8 colors cannot be changed - MyWorkbook.SetPaletteColor(i, random(256) + random(256) shr 8 + random(256) shr 16); - end; - // else use default palette + 5: palette.UseColors(PALETTE_BIFF5); + 8: palette.UseColors(PALETTE_BIFF8); + 999: begin // random palette: testing of color replacement + palette.UseColors(PALETTE_BIFF8); + for i:=8 to 63 do // first 8 colors must not be changed in Excel + palette[i] := random(256) + random(256) shr 8 + random(256) shr 16; + end; + else palette.AddBuiltinColors; end; - // Remember all colors because ODS does not have a palette in the file; - // therefore we do not know which colors to expect. - SetLength(pal, MyWorkbook.GetPaletteSize); - for color:=0 to High(pal) do - pal[color] := MyWorkbook.GetPaletteColor(color); + MyWorkbook := TsWorkbook.Create; + try + MyWorkSheet:= MyWorkBook.AddWorksheet(ColorsSheet); - // Write out all colors - row := 0; - col := 0; - for color := 0 to MyWorkbook.GetPaletteSize-1 do begin - MyWorksheet.WriteUTF8Text(row, col, CELLTEXT); - MyWorksheet.WriteFontColor(row, col, color); - MyCell := MyWorksheet.FindCell(row, col); - if MyCell = nil then - fail('Error in test code. Failed to get cell.'); - colorInFile := MyWorksheet.ReadCellFont(MyCell).Color; - currentRGB := MyWorkbook.GetPaletteColor(colorInFile); - expectedRGB := MyWorkbook.GetPaletteColor(color); - CheckEquals(expectedRGB, currentRGB, - 'Test unsaved font color, cell ' + CellNotation(MyWorksheet,row, col)); - inc(row); - end; - MyWorkBook.WriteToFile(TempFile, AFormat, true); - finally - MyWorkbook.Free; - end; - - // Open the spreadsheet - MyWorkbook := TsWorkbook.Create; - try - MyWorkbook.ReadFromFile(TempFile, AFormat); - if AFormat = sfExcel2 then - MyWorksheet := MyWorkbook.GetFirstWorksheet - else - MyWorksheet := GetWorksheetByName(MyWorkBook, ColorsSheet); - if MyWorksheet=nil then - fail('Error in test code. Failed to get named worksheet'); - for row := 0 to MyWorksheet.GetLastRowIndex do begin - MyCell := MyWorksheet.FindCell(row, col); - if MyCell = nil then - fail('Error in test code. Failed to get cell.'); - color := TsColor(row); - expectedRGB := pal[color]; - colorInFile := MyWorksheet.ReadCellFont(MyCell).Color; - currentRGB := MyWorkbook.GetPaletteColor(colorInFile); - - // Excel2 cannot write the entire palette. The writer had called "FixColor". - // We simulate that here to get the color correct. - if (AFormat = sfExcel2) and (color >= BIFF2_MAX_PALETTE_SIZE) then begin - color := MyWorkbook.FindClosestColor(expectedRGB, BIFF2_MAX_PALETTE_SIZE); - expectedRGB := MyWorkbook.GetPaletteColor(color); + // Write out all colors + row := 0; + col := 0; + for i := 0 to palette.Count-1 do begin + MyWorksheet.WriteUTF8Text(row, col, CELLTEXT); + MyWorksheet.WriteFontColor(row, col, palette[i]); + MyCell := MyWorksheet.FindCell(row, col); + if MyCell = nil then + fail('Error in test code. Failed to get cell.'); + currentRGB := MyWorksheet.ReadCellFont(MyCell).Color; + expectedRGB := palette[i]; + CheckEquals(expectedRGB, currentRGB, + 'Test unsaved font color, cell ' + CellNotation(MyWorksheet,row, col)); + inc(row); end; - CheckEquals(expectedRGB, currentRGB, - 'Test saved font color, cell '+CellNotation(MyWorksheet,Row,Col)); + MyWorkBook.WriteToFile(TempFile, AFormat, true); + finally + MyWorkbook.Free; end; + + // Open the spreadsheet + MyWorkbook := TsWorkbook.Create; + try + MyWorkbook.ReadFromFile(TempFile, AFormat); + if AFormat = sfExcel2 then + MyWorksheet := MyWorkbook.GetFirstWorksheet + else + MyWorksheet := GetWorksheetByName(MyWorkBook, ColorsSheet); + if MyWorksheet=nil then + fail('Error in test code. Failed to get named worksheet'); + col := 0; + for row := 0 to MyWorksheet.GetLastRowIndex do begin + MyCell := MyWorksheet.FindCell(row, col); + if MyCell = nil then + fail('Error in test code. Failed to get cell.'); + expectedRGB := palette[row]; + currentRGB := MyWorksheet.ReadCellFont(MyCell).Color; + + // Excel2 cannot write the entire palette. We have to look for the + // closest color. + if (AFormat = sfExcel2) then + expectedRGB := palette[palette.FindClosestColorIndex(expectedRGB, BIFF2_MAX_PALETTE_SIZE)]; + CheckEquals(expectedRGB, currentRGB, + 'Test saved font color, cell '+CellNotation(MyWorksheet,Row,Col)); + end; + finally + MyWorkbook.Free; + DeleteFile(TempFile); + end; + finally - MyWorkbook.Free; - DeleteFile(TempFile); + palette.Free; end; end; diff --git a/components/fpspreadsheet/tests/errortests.pas b/components/fpspreadsheet/tests/errortests.pas index 84c512f61..8bcebc503 100644 --- a/components/fpspreadsheet/tests/errortests.pas +++ b/components/fpspreadsheet/tests/errortests.pas @@ -35,7 +35,7 @@ type implementation uses - StrUtils, fpsRPN, xlsbiff5; + StrUtils, fpsPalette, fpsRPN, xlsbiff5; const ERROR_SHEET = 'ErrorTest'; //worksheet name @@ -67,6 +67,8 @@ var ErrList: TStringList; newColor: TsColor; expected: integer; + palette: TsPalette; + i: Integer; begin formula := '=A1'; @@ -122,25 +124,44 @@ begin MyWorkbook := TsWorkbook.Create; try // Prepare a full palette - MyWorkbook.UsePalette(@PALETTE_BIFF5[0], Length(PALETTE_BIFF5)); - // Add 1 more color - this is one too many for BIFF5 and 8, and a lot - // too many for BIFF2 ! - newColor := MyWorkbook.AddColorToPalette($FF7878); + palette := TsPalette.Create; + try + // Create random palette of 65 unique entries - 1 too many for Excel5/8 + // and a lot too many for BIFF2 + palette.AddBuiltinColors; + for i:=8 to 65 do + begin + repeat + newColor := random(256) + random(256) shl 8 + random(256) shl 16; + until palette.FindColor(newColor) = -1; + palette.AddColor(newColor); + end; - MyWorkSheet:= MyWorkBook.AddWorksheet(ERROR_SHEET); - MyWorksheet.WriteUTF8Text(0, 0, s); - MyWorksheet.WriteFontColor(0, 0, newColor); + MyWorkSheet:= MyWorkBook.AddWorksheet(ERROR_SHEET); + + // Use all colors in order to have them in the palette to be written + // to file. + for row := 0 to palette.Count-1 do + begin + MyWorksheet.WriteUTF8Text(row, 0, s); + MyWorksheet.WriteFontColor(row, 0, palette[row]); + end; + + TempFile:=NewTempFile; + MyWorkBook.WriteToFile(TempFile, AFormat, true); + ErrList.Text := MyWorkbook.ErrorMsg; + // Palette usage in biff --> expecting error due to too large palette + if (TTestFormat(AFormat) in [sfExcel2, sfExcel5, sfExcel8]) then + expected := 1 + else + // no palette in xml --> no error expected + expected := 0; + CheckEquals(expected, ErrList.Count, 'Error count mismatch in test 3'); + + finally + palette.Free; + end; - TempFile:=NewTempFile; - MyWorkBook.WriteToFile(TempFile, AFormat, true); - ErrList.Text := MyWorkbook.ErrorMsg; - // Palette usage in biff --> expecting error due to too large palette - if (TTestFormat(AFormat) in [sfExcel2, sfExcel5, sfExcel8]) then - expected := 1 - else - // no palette in xml --> no error expected - expected := 0; - CheckEquals(expected, ErrList.Count, 'Error count mismatch in test 3'); finally MyWorkbook.Free; DeleteFile(TempFile); diff --git a/components/fpspreadsheet/tests/formattests.pas b/components/fpspreadsheet/tests/formattests.pas index 6d50e5641..dcf954cc9 100644 --- a/components/fpspreadsheet/tests/formattests.pas +++ b/components/fpspreadsheet/tests/formattests.pas @@ -156,7 +156,7 @@ type implementation uses - TypInfo, fpsPatches, fpsutils, fpsnumformat, fpscsv; + TypInfo, fpsPatches, fpsutils, fpsnumformat, fpspalette, fpscsv; const FmtNumbersSheet = 'NumbersFormat'; //let's distinguish it from the regular numbers sheet @@ -311,7 +311,7 @@ begin SollBorderLineStyles[5] := lsDouble; SollBorderLineStyles[6] := lsHair; - SollBorderColors[0] := scBlue; + SollBorderColors[0] := scBlack; SollBorderColors[1] := scRed; SollBorderColors[2] := scBlue; SollBorderColors[3] := scGray; @@ -728,7 +728,6 @@ begin // Write out all test values MyWorkbook := TsWorkbook.Create; try - MyWorkbook.UsePalette(@PALETTE_BIFF8, Length(PALETTE_BIFF8)); MyWorkSheet:= MyWorkBook.AddWorksheet(BackgroundSheet); for style in TsFillStyle do begin row := ord(style); @@ -752,8 +751,7 @@ begin MyWorksheet := GetWorksheetByName(MyWorkBook, BackgroundSheet); if MyWorksheet=nil then fail('Error in test code. Failed to get named worksheet'); - - for style in TsFillStyle do begin + for style in TsFillStyle do begin row := ord(style); // Column B has BK_COLOR as backgroundcolor of the patterns @@ -770,13 +768,13 @@ begin begin if PATTERN_COLOR <> patt.FgColor then CheckEquals( - MyWorkbook.GetColorName(PATTERN_COLOR), - MyWorkbook.GetColorName(patt.FgColor), + GetColorName(PATTERN_COLOR), + GetColorName(patt.FgColor), 'Test saved fill pattern color mismatch, cell ' + CellNotation(MyWorksheet, row, col)); if BK_COLOR <> patt.BgColor then CheckEquals( - MyWorkbook.GetColorName(BK_COLOR), - MyWorkbook.GetColorName(patt.BgColor), + GetColorName(BK_COLOR), + GetColorName(patt.BgColor), 'Test saved fill background color mismatch, cell ' + CellNotation(MyWorksheet, row, col)); end; @@ -794,20 +792,20 @@ begin begin if PATTERN_COLOR <> patt.FgColor then CheckEquals( - MyWorkbook.GetColorName(PATTERN_COLOR), - MyWorkbook.GetColorName(patt.FgColor), + GetColorName(PATTERN_COLOR), + GetColorName(patt.FgColor), 'Test saved fill pattern color mismatch, cell ' + CellNotation(MyWorksheet, row, col)); // SolidFill is a special case: here the background color is always equal // to the pattern color - the cell layout does not know this... if style = fsSolidFill then CheckEquals( - MyWorkbook.GetColorName(PATTERN_COLOR), - MyWorkbook.GetColorName(patt.BgColor), + GetColorName(PATTERN_COLOR), + GetColorName(patt.BgColor), 'Test saved fill pattern color mismatch, cell ' + CellNotation(MyWorksheet, row, col)) else CheckEquals( - MyWorkbook.GetColorName(scTransparent), - MyWorkbook.GetColorName(patt.BgColor), + GetColorName(scTransparent), + GetColorName(patt.BgColor), 'Test saved fill background color mismatch, cell ' + CellNotation(MyWorksheet, row, col)); end; end; @@ -983,11 +981,11 @@ begin begin for col := 1 to 10 do begin - MyWorksheet.WriteBorders(row*2, col*2, borders); + MyWorksheet.WriteBorders(row*2-1, col*2-1, borders); for b in borders do begin - MyWorksheet.WriteBorderLineStyle(row*2, col*2, b, SollBorderLineStyles[ls]); - MyWorksheet.WriteBorderColor(row*2, col*2, b, SollBorderColors[c]); + MyWorksheet.WriteBorderLineStyle(row*2-1, col*2-1, b, SollBorderLineStyles[ls]); + MyWorksheet.WriteBorderColor(row*2-1, col*2-1, b, SollBorderColors[c]); inc(ls); if ls > High(SollBorderLineStyles) then begin @@ -1021,7 +1019,7 @@ begin begin for col := 1 to 10 do begin - MyCell := MyWorksheet.FindCell(row*2, col*2); + MyCell := MyWorksheet.FindCell(row*2-1, col*2-1); if myCell = nil then fail('Error in test code. Failed to get cell.'); for b in borders do @@ -1034,8 +1032,8 @@ begin expected := ord(SollBorderLineStyles[ls]); if AFormat in [sfExcel8, sfOOXML] then case b of - cbDiagUp : diagUp_ls := expected; - cbDiagDown: expected := diagUp_ls; + cbDiagUp : diagUp_ls := expected; + cbDiagDown : expected := diagUp_ls; end; CheckEquals(expected, current, 'Test saved border line style mismatch, cell ' + CellNotation(MyWorksheet, row*2, col*2)); @@ -1046,8 +1044,8 @@ begin // in the "diagonal-down" case. if AFormat in [sfExcel8, sfOOXML] then case b of - cbDiagUp : diagUp_clr := expected; - cbDiagDown: expected := diagUp_clr; + cbDiagUp : diagUp_clr := expected; + cbDiagDown : expected := diagUp_clr; end; CheckEquals(expected, current, 'Test saved border color mismatch, cell ' + CellNotation(MyWorksheet, row*2, col*2)); @@ -1591,55 +1589,64 @@ var fnt: TsFont; actual, expected: String; i: Integer; + palette: TsPalette; begin - MyWorkbook := TsWorkbook.Create; + palette := TsPalette.Create; try - MyWorksheet:= MyWorkBook.AddWorksheet(SHEETNAME); - for r := 0 to 7 do // each row has a different font size - for c := 0 to 7 do // each column has a different font color - begin - MyWorksheet.WriteNumber(r, c, 123); - MyWorksheet.WriteBackgroundColor(r, c, 0); - MyWorksheet.WriteFont(r, c, 'Times New Roman', FontSizes[r], [], c); // Biff2 has only 8 colors --> re-use the black! - // --> in total 64 combinations - end; - TempFile:=NewTempFile; - MyWorkBook.WriteToFile(TempFile, AFormat, true); - finally - MyWorkbook.Free; - end; + palette.AddBuiltinColors; - // Open the spreadsheet - MyWorkbook := TsWorkbook.Create; - try - MyWorkbook.ReadFromFile(TempFile, AFormat); + MyWorkbook := TsWorkbook.Create; + try + MyWorksheet:= MyWorkBook.AddWorksheet(SHEETNAME); + for r := 0 to 7 do // each row has a different font size + for c := 0 to 7 do // each column has a different font color + begin + MyWorksheet.WriteNumber(r, c, 123); + MyWorksheet.WriteBackgroundColor(r, c, 0); + MyWorksheet.WriteFont(r, c, 'Times New Roman', FontSizes[r], [], palette[c]); // Biff2 has only 8 colors --> re-use the black! + // --> in total 64 combinations + end; + TempFile:=NewTempFile; + MyWorkBook.WriteToFile(TempFile, AFormat, true); + finally + MyWorkbook.Free; + end; - // 1st sheet: merged cells with text - if AFormat = sfExcel2 then - MyWorksheet := MyWorkbook.GetFirstWorksheet - else - MyWorksheet := GetWorksheetByName(MyWorkBook, SHEETNAME); - if MyWorksheet=nil then - fail('Error in test code. Failed to get named worksheet ' + SHEETNAME); + // Open the spreadsheet + MyWorkbook := TsWorkbook.Create; + try + MyWorkbook.ReadFromFile(TempFile, AFormat); - for r:=0 to MyWorksheet.GetLastRowIndex do - for c := 0 to MyWorksheet.GetLastColIndex do - begin - cell := MyWorksheet.FindCell(r, c); - fnt := MyWorksheet.ReadCellFont(cell); - expected := FloatToStr(FontSizes[r]); - actual := FloatToStr(fnt.Size); - CheckEquals(expected, actual, - 'Font size mismatch, cell '+ CellNotation(MyWorksheet, r, c)); - expected := IntToStr(c); - actual := IntToStr(fnt.Color); - CheckEquals(expected, actual, - 'Font color mismatch, cell '+ CellNotation(MyWorksheet, r, c)); - end; + // 1st sheet: merged cells with text + if AFormat = sfExcel2 then + MyWorksheet := MyWorkbook.GetFirstWorksheet + else + MyWorksheet := GetWorksheetByName(MyWorkBook, SHEETNAME); + if MyWorksheet=nil then + fail('Error in test code. Failed to get named worksheet ' + SHEETNAME); + + for r:=0 to MyWorksheet.GetLastRowIndex do + for c := 0 to MyWorksheet.GetLastColIndex do + begin + cell := MyWorksheet.FindCell(r, c); + fnt := MyWorksheet.ReadCellFont(cell); + expected := FloatToStr(FontSizes[r]); + actual := FloatToStr(fnt.Size); + CheckEquals(expected, actual, + 'Font size mismatch, cell '+ CellNotation(MyWorksheet, r, c)); + expected := IntToStr(palette[c]); + actual := IntToStr(fnt.Color); + CheckEquals(expected, actual, + 'Font color mismatch, cell '+ CellNotation(MyWorksheet, r, c)); + end; + + finally + MyWorkbook.Free; + DeleteFile(TempFile); + end; finally - MyWorkbook.Free; - DeleteFile(TempFile); + palette.Free; end; end; diff --git a/components/fpspreadsheet/tests/manualtests.pas b/components/fpspreadsheet/tests/manualtests.pas index 83a1104c6..be0f196f9 100644 --- a/components/fpspreadsheet/tests/manualtests.pas +++ b/components/fpspreadsheet/tests/manualtests.pas @@ -63,17 +63,20 @@ type {$ENDIF} // For BIFF8 format, writes all background colors in A1..A16 procedure TestBiff8CellBackgroundColor; + + procedure TestNumberFormats; end; implementation uses - fpstypes, fpsUtils, rpnFormulaUnit; + fpstypes, fpsUtils, fpsPalette, rpnFormulaUnit; const COLORSHEETNAME='color_sheet'; //for background color tests RPNSHEETNAME='rpn_formula_sheet'; //for rpn formula tests FORMULASHEETNAME='formula_sheet'; // for string formula tests + NUMBERFORMATSHEETNAME='number format sheet'; // for number format tests OUTPUT_FORMAT = sfExcel8; //change manually if you want to test different formats. To do: automatically output all formats var @@ -184,6 +187,7 @@ var Cell : PCell; i: cardinal; RowOffset: cardinal; + palette: TsPalette; begin if OUTPUT_FORMAT <> sfExcel8 then Ignore('This test only applies to BIFF8 XLS output format.'); @@ -196,17 +200,76 @@ begin if Workbook = nil then Workbook := TsWorkbook.Create; - Worksheet := Workbook.AddWorksheet(COLORSHEETNAME); - WorkSheet.WriteUTF8Text(0,1,'TSpreadManualTests.TestBiff8CellBackgroundColor'); - RowOffset := 1; - for i:=0 to Workbook.GetPaletteSize-1 do begin - WorkSheet.WriteUTF8Text(i+RowOffset,0,'BACKGROUND COLOR TEST'); - Cell := Worksheet.GetCell(i+RowOffset, 0); - Worksheet.WriteBackgroundColor(Cell, TsColor(i)); - WorkSheet.WriteUTF8Text(i+RowOffset,1,'Cell to the left should be '+Workbook.GetColorName(i)+'. Please check.'); + palette := TsPalette.Create; + try + palette.AddBuiltinColors; + palette.AddExcelColors; + + Worksheet := Workbook.AddWorksheet(COLORSHEETNAME); + WorkSheet.WriteUTF8Text(0, 1, 'TSpreadManualTests.TestBiff8CellBackgroundColor'); + RowOffset := 1; + for i:=0 to palette.Count-1 do begin + cell := WorkSheet.WriteUTF8Text(i+RowOffset,0,'BACKGROUND COLOR TEST'); + Worksheet.WriteBackgroundColor(Cell, palette[i]); + Worksheet.WriteFontColor(cell, HighContrastColor(palette[i])); + WorkSheet.WriteUTF8Text(i+RowOffset,1,'Cell to the left should be '+GetColorName(palette[i])+'. Please check.'); + end; + Worksheet.WriteColWidth(0, 30); + Worksheet.WriteColWidth(1, 60); + finally + palette.Free; end; end; +procedure TSpreadManualTests.TestNumberFormats(); +// source: forum post +// http://forum.lazarus.freepascal.org/index.php/topic,19887.msg134114.html#msg134114 +// possible fix for values there too +const + Values: Array[0..4] of Double = (12000.34, -12000.34, 0.0001234, -0.0001234, 0.0); + FormatStrings: array[0..24] of String = ( + 'General', + '0', '0.00', '0.0000', + '#,##0', '#,##0.00', '#,##0.0000', + '0%', '0.00%', '0.0000%', + '0,', '0.00,', '0.0000,', + '0E+00', '0.00E+00', '0.0000E+00', + '0E-00', '0.00E-00', '0.0000E-00', + '# ?/?', '# ??/??', '# ????/????', + '?/?', '??/??', '????/????' + ); +var + Worksheet: TsWorksheet; + Cell : PCell; + i: cardinal; + r, c: Cardinal; + palette: TsPalette; + nfs: String; +begin + if OUTPUT_FORMAT <> sfExcel8 then + Ignore('This test only applies to BIFF8 XLS output format.'); + + // No worksheets in BIFF2. Since main interest is here in formulas we just jump + // off here - need to change this in the future... + if OUTPUT_FORMAT = sfExcel2 then + Ignore('BIFF2 does not support worksheets. Ignoring manual tests for now'); + + if Workbook = nil then + Workbook := TsWorkbook.Create; + + Worksheet := Workbook.AddWorksheet(NUMBERFORMATSHEETNAME); + WorkSheet.WriteUTF8Text(0, 1, 'Number format tests'); + + for r:=0 to High(FormatStrings) do + begin + Worksheet.WriteUTF8Text(r+2, 0, FormatStrings[r]); + for c:=0 to High(Values) do + Worksheet.WriteNumber(r+2, c+1, values[c], nfCustom, FormatStrings[r]); + end; + + Worksheet.WriteColWidth(0, 20); +end; + {$IFDEF FPSPREAD_HAS_NEWRPNSUPPORT} // As described in bug 25718: Feature request & patch: Implementation of writing more functions procedure TSpreadManualTests.TestRPNFormula; diff --git a/components/fpspreadsheet/wikitable.pas b/components/fpspreadsheet/wikitable.pas index f63306c89..ac3b6ea53 100644 --- a/components/fpspreadsheet/wikitable.pas +++ b/components/fpspreadsheet/wikitable.pas @@ -270,7 +270,8 @@ begin if copy(lFormatstr, 1, 6) = 'color:' then begin lColorstr := Copy(lFormatstr, 7, Length(lFormatStr)); - lCurBackgroundColor := FWorkbook.AddColorToPalette(HTMLColorStrToColor(lColorStr)); + lCurBackgroundColor := HTMLColorStrToColor(lColorStr); +// lCurBackgroundColor := FWorkbook.AddColorToPalette(HTMLColorStrToColor(lColorStr)); lUseBackgroundColor := True; lFormatStr := ''; end; @@ -381,7 +382,8 @@ procedure TsWikiTableWriter.WriteToStrings_WikiMedia(AStrings: TStrings); clr := fmt^.BorderStyles[ABorder].Color; Result := Format('border-%s:%s', [BORDERNAMES[ABorder], LINESTYLES[ls]]); if clr <> scBlack then - Result := Result + ' ' + FWorkbook.GetPaletteColorAsHTMLStr(clr) + '; '; + Result := Result + ' ' + ColorToHTMLColorStr(clr) + '; '; +// Result := Result + ' ' + FWorkbook.GetPaletteColorAsHTMLStr(clr) + '; '; end; const @@ -499,8 +501,10 @@ begin begin lCurColor := FWorksheet.ReadBackgroundColor(lCell); lStyleStr := Format('background-color:%s;color:%s;', [ - FWorkbook.GetPaletteColorAsHTMLStr(lCurColor), - FWorkbook.GetPaletteColorAsHTMLStr(lFont.Color) + ColorToHTMLColorStr(lCurColor), + ColorToHTMLColorStr(lFont.Color) +// FWorkbook.GetPaletteColorAsHTMLStr(lCurColor), +// FWorkbook.GetPaletteColorAsHTMLStr(lFont.Color) ]); end; diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas index f5af49cd6..4a9bed11e 100755 --- a/components/fpspreadsheet/xlsbiff2.pas +++ b/components/fpspreadsheet/xlsbiff2.pas @@ -144,7 +144,7 @@ var var { the palette of the default BIFF2 colors as "big-endian color" values } - PALETTE_BIFF2: array[$0..$07] of TsColorValue = ( + PALETTE_BIFF2: array[$0..$07] of TsColor = ( $000000, // $00: black $FFFFFF, // $01: white $FF0000, // $02: red @@ -159,7 +159,7 @@ var implementation uses - Math, fpsStrings, fpsReaderWriter; + Math, fpsStrings, fpsReaderWriter, fpsPalette; const { Excel record IDs } @@ -419,8 +419,12 @@ begin end; procedure TsSpreadBIFF2Reader.ReadFONTCOLOR(AStream: TStream); +var + lColor: Word; begin - FFont.Color := WordLEToN(AStream.ReadWord); + lColor := WordLEToN(AStream.ReadWord); // Palette index + FFont.Color := IfThen(lColor = SYS_DEFAULT_WINDOW_TEXT_COLOR, + scBlack, FPalette[lColor]); end; {@@ ---------------------------------------------------------------------------- @@ -1533,7 +1537,7 @@ begin AStream.WriteWord(WordToLE(2)); { Font color index, only first 8 palette entries allowed! } - AStream.WriteWord(WordToLE(word(FixColor(font.Color)))); + AStream.WriteWord(WordToLE(PaletteIndex(font.Color))); end; {@@ ---------------------------------------------------------------------------- @@ -1987,6 +1991,6 @@ initialization {$ENDIF} RegisterSpreadFormat(TsSpreadBIFF2Reader, TsSpreadBIFF2Writer, sfExcel2); - MakeLEPalette(@PALETTE_BIFF2, Length(PALETTE_BIFF2)); + MakeLEPalette(PALETTE_BIFF2); end. diff --git a/components/fpspreadsheet/xlsbiff5.pas b/components/fpspreadsheet/xlsbiff5.pas index d004f75aa..40dc84665 100755 --- a/components/fpspreadsheet/xlsbiff5.pas +++ b/components/fpspreadsheet/xlsbiff5.pas @@ -77,6 +77,7 @@ type FWorksheetNames: TStringList; FCurrentWorksheet: Integer; protected + procedure PopulatePalette; override; { Record writing methods } procedure ReadBoundsheet(AStream: TStream); procedure ReadFONT(const AStream: TStream); @@ -137,7 +138,7 @@ var var // the palette of the default BIFF5 colors as "big-endian color" values - PALETTE_BIFF5: array[$00..$3F] of TsColorValue = ( + PALETTE_BIFF5: array[$00..$3F] of TsColor = ( $000000, // $00: black $FFFFFF, // $01: white $FF0000, // $02: red @@ -213,7 +214,7 @@ var implementation uses - fpsStrings, fpsStreams, fpsReaderWriter; + Math, fpsStrings, fpsStreams, fpsReaderWriter, fpsPalette; const { Excel record IDs } @@ -337,6 +338,15 @@ type { TsSpreadBIFF5Reader } +{@@ ---------------------------------------------------------------------------- + Populates the reader's default palette using the BIFF5 default colors. +-------------------------------------------------------------------------------} +procedure TsSpreadBIFF5Reader.PopulatePalette; +begin + FPalette.Clear; + FPalette.UseColors(PALETTE_BIFF5); +end; + procedure TsSpreadBIFF5Reader.ReadWorkbookGlobals(AStream: TStream); var SectionEOF: Boolean = False; @@ -352,14 +362,14 @@ begin CurStreamPos := AStream.Position; case RecordType of - INT_EXCEL_ID_BOF : ; - INT_EXCEL_ID_BOUNDSHEET : ReadBoundSheet(AStream); - INT_EXCEL_ID_CODEPAGE : ReadCodePage(AStream); - INT_EXCEL_ID_FONT : ReadFont(AStream); - INT_EXCEL_ID_FORMAT : ReadFormat(AStream); - INT_EXCEL_ID_XF : ReadXF(AStream); - INT_EXCEL_ID_PALETTE : ReadPalette(AStream); - INT_EXCEL_ID_EOF : SectionEOF := True; + INT_EXCEL_ID_BOF : ; + INT_EXCEL_ID_BOUNDSHEET : ReadBoundSheet(AStream); + INT_EXCEL_ID_CODEPAGE : ReadCodePage(AStream); + INT_EXCEL_ID_FONT : ReadFont(AStream); + INT_EXCEL_ID_FORMAT : ReadFormat(AStream); + INT_EXCEL_ID_XF : ReadXF(AStream); + INT_EXCEL_ID_PALETTE : ReadPalette(AStream); + INT_EXCEL_ID_EOF : SectionEOF := True; else // nothing end; @@ -370,6 +380,9 @@ begin // Check for the end of the file if AStream.Position >= AStream.Size then SectionEOF := True; end; + + // Convert palette indexes to rgb colors + FixColors; end; procedure TsSpreadBIFF5Reader.ReadWorksheet(AStream: TStream); @@ -606,8 +619,7 @@ procedure TsSpreadBIFF5Reader.ReadXF(AStream: TStream); var rec: TBIFF5_XFRecord; fmt: TsCellFormat; -// nfidx: Integer; - i: Integer; + i, cidx: Integer; nfparams: TsNumFormatParams; nfs: String; b: Byte; @@ -646,28 +658,7 @@ begin Include(fmt.UsedFormattingFields, uffNumberFormat); end; end; -{ - // Number format index - nfparams := Workbook.GetNumberFormat(rec.NumFormatIndex); - nfs := nfParams.NumFormatStr[nfdDefault]; - if nfs <> '' then begin - fmt.NumberFormatIndex := Workbook.AddNumberFormat(nfs); - fmt.NumberFormat := nfParams.NumFormat; - fmt.NumberFormatStr := nfs; - Include(fmt.UsedFormattingFields, uffNumberFormat); - end; - } - { - nfidx := WordLEToN(rec.NumFormatIndex); - i := NumFormatList.FindByIndex(nfidx); - if i > -1 then begin - nfdata := NumFormatList.Items[i]; - fmt.NumberFormat := nfdata.NumFormat; - fmt.NumberFormatStr := nfdata.FormatString; - if nfdata.NumFormat <> nfGeneral then - Include(fmt.UsedFormattingFields, uffNumberFormat); - end; - } + // Horizontal text alignment b := rec.Align_TextBreak AND MASK_XF_HOR_ALIGN; if (b <= ord(High(TsHorAlignment))) then @@ -742,10 +733,17 @@ begin end; // Border line colors - fmt.BorderStyles[cbWest].Color := (rec.Border_BkGr2 and MASK_XF_BORDER_LEFT_COLOR) shr 16; - fmt.BorderStyles[cbEast].Color := (rec.Border_BkGr2 and MASK_XF_BORDER_RIGHT_COLOR) shr 23; - fmt.BorderStyles[cbNorth].Color := (rec.Border_BkGr2 and MASK_XF_BORDER_TOP_COLOR) shr 9; - fmt.BorderStyles[cbSouth].Color := (rec.Border_BkGr1 and MASK_XF_BORDER_BOTTOM_COLOR) shr 25; + // NOTE: It is possible that the palette is not yet known at this moment. + // Therefore we store the palette index encoded into the colors. + // They will be converted to rgb in "FixColors". + cidx := (rec.Border_BkGr2 and MASK_XF_BORDER_LEFT_COLOR) shr 16; + fmt.BorderStyles[cbWest].Color := IfThen(cidx >= 64, scBlack, SetAsPaletteIndex(cidx)); + cidx := (rec.Border_BkGr2 and MASK_XF_BORDER_RIGHT_COLOR) shr 23; + fmt.BorderStyles[cbEast].Color := IfThen(cidx >= 64, scBlack, SetAsPaletteIndex(cidx)); + cidx := (rec.Border_BkGr2 and MASK_XF_BORDER_TOP_COLOR) shr 9; + fmt.BorderStyles[cbNorth].Color := IfThen(cidx >= 64, scBlack, SetAsPaletteIndex(cidx)); + cidx := (rec.Border_BkGr1 and MASK_XF_BORDER_BOTTOM_COLOR) shr 25; + fmt.BorderStyles[cbSouth].Color := IfThen(cidx >= 64, scBlack, SetAsPaletteIndex(cidx)); // Background fill := (rec.Border_BkGr1 and MASK_XF_BKGR_FILLPATTERN) shr 16; @@ -758,12 +756,12 @@ begin // Fill style fmt.Background.Style := fs; // Pattern color - fmt.Background.FgColor := rec.Border_BkGr1 and MASK_XF_BKGR_PATTERN_COLOR; - if fmt.Background.FgColor = SYS_DEFAULT_FOREGROUND_COLOR then - fmt.Background.FgColor := scBlack; - fmt.Background.BgColor := (rec.Border_BkGr1 and MASK_XF_BKGR_BACKGROUND_COLOR) shr 7; - if fmt.Background.BgColor = SYS_DEFAULT_BACKGROUND_COLOR then - fmt.Background.BgColor := scTransparent; + cidx := rec.Border_BkGr1 and MASK_XF_BKGR_PATTERN_COLOR; // Palette index + fmt.Background.FgColor := IfThen(cidx = SYS_DEFAULT_FOREGROUND_COLOR, + scBlack, SetAsPaletteIndex(cidx)); + cidx := (rec.Border_BkGr1 and MASK_XF_BKGR_BACKGROUND_COLOR) shr 7; + fmt.Background.BgColor := IfThen(cidx = SYS_DEFAULT_BACKGROUND_COLOR, + scTransparent, SetAsPaletteIndex(cidx)); Include(fmt.UsedFormattingFields, uffBackground); break; end; @@ -785,14 +783,12 @@ begin BIFF5EOF := False; { Read workbook globals } - ReadWorkbookGlobals(AStream); - // Check for the end of the file + { Check for the end of the file } if AStream.Position >= AStream.Size then BIFF5EOF := True; { Now read all worksheets } - while (not BIFF5EOF) do begin ReadWorksheet(AStream); @@ -807,11 +803,7 @@ begin // at the end of the file. end; - if not FPaletteFound then - FWorkbook.UsePalette(@PALETTE_BIFF5, Length(PALETTE_BIFF5)); - - { Finalizations } - + { Finalization } FWorksheetNames.Free; end; @@ -840,10 +832,21 @@ begin if lOptions and $0004 <> 0 then Include(font.Style, fssUnderline); if lOptions and $0008 <> 0 then Include(font.Style, fssStrikeout); - { Colour index } + { Color index } + // The problem is that the palette is loaded after the font list; therefore + // we do not know the rgb color of the font here. We store the palette index + // ("SetAsPaletteIndex") and replace it by the rgb color at the end of the + // workbook globals records. As an indicator that the font does not yet + // contain an rgb color a control bit is set in the high-byte of the TsColor. lColor := WordLEToN(AStream.ReadWord); - //font.Color := TsColor(lColor - 8); // Palette colors have an offset 8 - font.Color := tsColor(lColor); + if lColor < 8 then + // Use built-in colors directly otherwise the Workbook's FindFont would not find the font in ReadXF + font.Color := FPalette[lColor] + else + if lColor = SYS_DEFAULT_WINDOW_TEXT_COLOR then + font.Color := scBlack + else + font.Color := SetAsPaletteIndex(lColor); { Font weight } lWeight := WordLEToN(AStream.ReadWord); @@ -1197,6 +1200,7 @@ procedure TsSpreadBIFF5Writer.WriteFont(AStream: TStream; AFont: TsFont); var Len: Byte; optn: Word; + cidx: Integer; begin if AFont = nil then // this happens for FONT4 in case of BIFF exit; @@ -1222,8 +1226,8 @@ begin if fssStrikeout in AFont.Style then optn := optn or $0008; AStream.WriteWord(WordToLE(optn)); - { Colour index } - AStream.WriteWord(WordToLE(ord(FixColor(AFont.Color)))); + { Color index } + AStream.WriteWord(WordToLE(PaletteIndex(AFont.Color))); { Font weight } if fssBold in AFont.Style then @@ -1574,19 +1578,19 @@ begin begin if (AFormatRecord^.Background.FgColor = scTransparent) then dw1 := dw1 or (SYS_DEFAULT_FOREGROUND_COLOR and $0000007F) - else dw1 := dw1 or (FixColor(AFormatRecord^.Background.FgColor) and $0000007F); + else dw1 := dw1 or (PaletteIndex(AFormatRecord^.Background.FgColor) and $0000007F); if AFormatRecord^.Background.BgColor = scTransparent then dw1 := dw1 or (SYS_DEFAULT_BACKGROUND_COLOR shl 7) - else dw1 := dw1 or (FixColor(AFormatRecord^.Background.BgColor) shl 7); + else dw1 := dw1 or (PaletteIndex(AFormatRecord^.Background.BgColor) shl 7); dw1 := dw1 or (MASK_XF_FILL_PATT[AFormatRecord^.Background.Style] shl 16); end; // Border lines if (uffBorder in AFormatRecord^.UsedFormattingFields) then begin - dw1 := dw1 or (AFormatRecord^.BorderStyles[cbSouth].Color shl 25); // Bottom line color - dw2 := (FixColor(AFormatRecord^.BorderStyles[cbNorth].Color) shl 9) or // Top line color - (FixColor(AFormatRecord^.BorderStyles[cbWest].Color) shl 16) or // Left line color - (FixColor(AFormatRecord^.BorderStyles[cbEast].Color) shl 23); // Right line color + dw1 := dw1 or PaletteIndex(AFormatRecord^.BorderStyles[cbSouth].Color) shl 25; // Bottom line color + dw2 := (PaletteIndex(AFormatRecord^.BorderStyles[cbNorth].Color) shl 9) or // Top line color + (PaletteIndex(AFormatRecord^.BorderStyles[cbWest].Color) shl 16) or // Left line color + (PaletteIndex(AFormatRecord^.BorderStyles[cbEast].Color) shl 23); // Right line color if cbSouth in AFormatRecord^.Border then dw1 := dw1 or ((DWord(AFormatRecord^.BorderStyles[cbSouth].LineStyle)+1) shl 22); if cbNorth in AFormatRecord^.Border then @@ -1612,7 +1616,7 @@ initialization {$ENDIF} RegisterSpreadFormat(TsSpreadBIFF5Reader, TsSpreadBIFF5Writer, sfExcel5); - MakeLEPalette(@PALETTE_BIFF5, Length(PALETTE_BIFF5)); + MakeLEPalette(PALETTE_BIFF5); end. diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index cfff9a43c..c91160227 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -84,6 +84,7 @@ type procedure ReadBoundsheet(AStream: TStream); function ReadString(const AStream: TStream; const ALength: WORD): String; protected + procedure PopulatePalette; override; procedure ReadCONTINUE(const AStream: TStream); procedure ReadFONT(const AStream: TStream); procedure ReadFORMAT(AStream: TStream); override; @@ -181,7 +182,7 @@ var var // the palette of the 64 default BIFF8 colors as "big-endian color" values - PALETTE_BIFF8: array[$00..$3F] of TsColorValue = ( + PALETTE_BIFF8: array[$00..$3F] of TsColor = ( $000000, // $00: black // 8 built-in default colors $FFFFFF, // $01: white $FF0000, // $02: red @@ -191,72 +192,73 @@ var $FF00FF, // $06: magenta $00FFFF, // $07: cyan - $000000, // $08: EGA black - $FFFFFF, // $09: EGA white - $FF0000, // $0A: EGA red - $00FF00, // $0B: EGA green - $0000FF, // $0C: EGA blue - $FFFF00, // $0D: EGA yellow - $FF00FF, // $0E: EGA magenta - $00FFFF, // $0F: EGA cyan + $000000, // $08: EGA black 1 + $FFFFFF, // $09: EGA white 2 + $FF0000, // $0A: EGA red 3 + $00FF00, // $0B: EGA green 4 + $0000FF, // $0C: EGA blue 5 + $FFFF00, // $0D: EGA yellow 6 + $FF00FF, // $0E: EGA magenta 7 pink + $00FFFF, // $0F: EGA cyan 8 turqoise - $800000, // $10: EGA dark red - $008000, // $11: EGA dark green - $000080, // $12: EGA dark blue - $808000, // $13: EGA olive - $800080, // $14: EGA purple - $008080, // $15: EGA teal - $C0C0C0, // $16: EGA silver - $808080, // $17: EGA gray - $9999FF, // $18: - $993366, // $19: - $FFFFCC, // $1A: - $CCFFFF, // $1B: - $660066, // $1C: - $FF8080, // $1D: - $0066CC, // $1E: - $CCCCFF, // $1F: + $800000, // $10=16: EGA dark red 9 + $008000, // $11=17: EGA dark green 10 + $000080, // $12=18: EGA dark blue 11 + $808000, // $13=19: EGA olive 12 dark yellow + $800080, // $14=20: EGA purple 13 violet + $008080, // $15=21: EGA teal 14 + $C0C0C0, // $16=22: EGA silver 15 gray 25% + $808080, // $17=23: EGA gray 16 gray 50% + $9999FF, // $18=24: Periwinkle + $993366, // $19=25: Plum + $FFFFCC, // $1A=26: Ivory + $CCFFFF, // $1B=27: Light turquoise + $660066, // $1C=28: Dark purple + $FF8080, // $1D=29: Coral + $0066CC, // $1E=30: Ocean blue + $CCCCFF, // $1F=31: Ice blue - $000080, // $20: - $FF00FF, // $21: - $FFFF00, // $22: - $00FFFF, // $23: - $800080, // $24: - $800000, // $25: - $008080, // $26: - $0000FF, // $27: - $00CCFF, // $28: - $CCFFFF, // $29: - $CCFFCC, // $2A: - $FFFF99, // $2B: - $99CCFF, // $2C: - $FF99CC, // $2D: - $CC99FF, // $2E: - $FFCC99, // $2F: + $000080, // $20=32: Navy (repeated) + $FF00FF, // $21=33: Pink (magenta repeated) + $FFFF00, // $22=34: Yellow (repeated) + $00FFFF, // $23=35: Turqoise (=cyan repeated) + $800080, // $24=36: Purple (repeated) + $800000, // $25=37: Dark red (repeated) + $008080, // $26=38: Teal (repeated) + $0000FF, // $27=39: Blue (repeated) + $00CCFF, // $28=40: Sky blue + $CCFFFF, // $29=41: Light turquoise (repeated) + $CCFFCC, // $2A=42: Light green + $FFFF99, // $2B=43: Light yellow + $99CCFF, // $2C=44: Pale blue + $FF99CC, // $2D=45: rose + $CC99FF, // $2E=46: lavander + $FFCC99, // $2F=47: tan - $3366FF, // $30: - $33CCCC, // $31: - $99CC00, // $32: - $FFCC00, // $33: - $FF9900, // $34: - $FF6600, // $35: - $666699, // $36: - $969696, // $37: - $003366, // $38: - $339966, // $39: - $003300, // $3A: - $333300, // $3B: - $993300, // $3C: - $993366, // $3D: - $333399, // $3E: - $333333 // $3F: + $3366FF, // $30=48: Light blue + $33CCCC, // $31=49: Aqua + $99CC00, // $32=50: Lime + $FFCC00, // $33=51: Gold + $FF9900, // $34=52: Light orange + $FF6600, // $35=53: Orange + $666699, // $36=54: Blue gray + $969696, // $37=55: Gray 40% + $003366, // $38=56: Dark teal + $339966, // $39=57: Sea green + $003300, // $3A=58: very dark green + $333300, // $3B=59: olive green + $993300, // $3C=60: brown + $993366, // $3D=61: plum + $333399, // $3E=62: indigo + $333333 // $3F=63: gray 80% ); + // color names according to http://dmcritchie.mvps.org/EXCEL/COLORS.HTM implementation uses Math, lconvencoding, LazFileUtils, URIParser, - fpsStrings, fpsStreams, fpsReaderWriter, fpsExprParser, xlsEscher; + fpsStrings, fpsStreams, fpsReaderWriter, fpsPalette, fpsExprParser, xlsEscher; const { Excel record IDs } @@ -429,10 +431,21 @@ begin inherited; end; -{ Reads a CONTINUE record. If the Flag "FCommentPending" is active then this +{@@ ---------------------------------------------------------------------------- + Populates the reader's default palette using the BIFF8 default colors. +-------------------------------------------------------------------------------} +procedure TsSpreadBIFF8Reader.PopulatePalette; +begin + FPalette.Clear; + FPalette.UseColors(PALETTE_BIFF8); +end; + +{@@ ---------------------------------------------------------------------------- + Reads a CONTINUE record. If the Flag "FCommentPending" is active then this record contains the text of a comment assigned to a cell. The length of the string is taken from the preceeding TXO record, and the ID of the comment is - extracted in another preceeding record, an OBJ record. } + extracted in another preceeding record, an OBJ record. +-------------------------------------------------------------------------------} procedure TsSpreadBIFF8Reader.ReadCONTINUE(const AStream: TStream); var commentStr: String; @@ -657,6 +670,9 @@ begin // Check for the end of the file if AStream.Position >= AStream.Size then SectionEOF := True; end; + + // Convert palette indexes to rgb colors + FixColors; end; procedure TsSpreadBIFF8Reader.ReadWorksheet(AStream: TStream); @@ -839,9 +855,6 @@ begin // at the end of the file. end; - if not FPaletteFound then - FWorkbook.UsePalette(@PALETTE_BIFF8, Length(PALETTE_BIFF8)); - { Finalizations } FWorksheetNames.Free; end; @@ -1206,6 +1219,7 @@ var nfs: String; nfParams: TsNumFormatParams; i: Integer; + iclr: Integer; fnt: TsFont; begin InitFormatRecord(fmt); @@ -1326,29 +1340,39 @@ begin end; // Border line colors - fmt.BorderStyles[cbWest].Color := (rec.Border_BkGr1 and MASK_XF_BORDER_LEFT_COLOR) shr 16; - fmt.BorderStyles[cbEast].Color := (rec.Border_BkGr1 and MASK_XF_BORDER_RIGHT_COLOR) shr 23; - fmt.BorderStyles[cbNorth].Color := (rec.Border_BkGr2 and MASK_XF_BORDER_TOP_COLOR); - fmt.BorderStyles[cbSouth].Color := (rec.Border_BkGr2 and MASK_XF_BORDER_BOTTOM_COLOR) shr 7; - fmt.BorderStyles[cbDiagUp].Color := (rec.Border_BkGr2 and MASK_XF_BORDER_DIAGONAL_COLOR) shr 14; + // NOTE: It is possible that the palette is not yet known at this moment. + // Therefore we store the palette index encoded into the colorx. + // They will be converted to rgb in "FixColors". + iclr := (rec.Border_BkGr1 and MASK_XF_BORDER_LEFT_COLOR) shr 16; + fmt.BorderStyles[cbWest].Color := IfThen(iclr >= 64, scBlack, SetAsPaletteIndex(iclr)); + iclr := (rec.Border_BkGr1 and MASK_XF_BORDER_RIGHT_COLOR) shr 23; + fmt.BorderStyles[cbEast].Color := IfThen(iclr >= 64, scBlack, SetAsPaletteIndex(iclr)); + iclr := (rec.Border_BkGr2 and MASK_XF_BORDER_TOP_COLOR); + fmt.BorderStyles[cbNorth].Color := IfThen(iclr >= 64, scBlack, SetAsPaletteIndex(iclr)); + iclr := (rec.Border_BkGr2 and MASK_XF_BORDER_BOTTOM_COLOR) shr 7; + fmt.BorderStyles[cbSouth].Color := IfThen(iclr >= 64, scBlack, SetAsPaletteIndex(iclr)); + iclr := (rec.Border_BkGr2 and MASK_XF_BORDER_DIAGONAL_COLOR) shr 14; + fmt.BorderStyles[cbDiagUp].Color := IfThen(iclr >= 64, scBlack, SetAsPaletteIndex(iclr)); fmt.BorderStyles[cbDiagDown].Color := fmt.BorderStyles[cbDiagUp].Color; // Background fill pattern and color fill := (rec.Border_BkGr2 and MASK_XF_BACKGROUND_PATTERN) shr 26; if fill <> MASK_XF_FILL_PATT_EMPTY then begin + rec.BkGr3 := DWordLEToN(rec.BkGr3); for fs in TsFillStyle do if fill = MASK_XF_FILL_PATT[fs] then begin - rec.BkGr3 := DWordLEToN(rec.BkGr3); // Pattern color - fmt.Background.FgColor := rec.BkGr3 and $007F; - if fmt.Background.FgColor = SYS_DEFAULT_FOREGROUND_COLOR then - fmt.Background.FgColor := scBlack; + iclr := rec.BkGr3 and $007F; + fmt.Background.FgColor := IfThen(iclr = SYS_DEFAULT_FOREGROUND_COLOR, + scBlack, SetAsPaletteIndex(iclr)); + // Background color - fmt.Background.BgColor := (rec.BkGr3 and $3F80) shr 7; - if fmt.Background.BgColor = SYS_DEFAULT_BACKGROUND_COLOR then - fmt.Background.BgColor := scTransparent; + iclr := (rec.BkGr3 and $3F80) shr 7; + fmt.Background.BgColor := IfThen(iclr = SYS_DEFAULT_BACKGROUND_COLOR, + scTransparent, SetAsPaletteIndex(iclr)); + // Fill style fmt.Background.Style := fs; Include(fmt.UsedFormattingFields, uffBackground); @@ -1374,7 +1398,7 @@ begin font := TsFont.Create; { Height of the font in twips = 1/20 of a point } - lHeight := WordLEToN(AStream.ReadWord); // WordToLE(200) + lHeight := WordLEToN(AStream.ReadWord); font.Size := lHeight/20; { Option flags } @@ -1385,10 +1409,21 @@ begin if lOptions and $0004 <> 0 then Include(font.Style, fssUnderline); if lOptions and $0008 <> 0 then Include(font.Style, fssStrikeout); - { Colour index } + { Color index } + // The problem is that the palette is loaded after the font list; therefore + // we do not know the rgb color of the font here. We store the palette index + // ("SetAsPaletteIndex") and replace it by the rgb color at the end of the + // workbook globals records. As an indicator that the font does not yet + // contain an rgb color a control bit is set in the high-byte of the TsColor. lColor := WordLEToN(AStream.ReadWord); - //font.Color := TsColor(lColor - 8); // Palette colors have an offset 8 - font.Color := tsColor(lColor); + if lColor < 8 then + // Use built-in colors directly otherwise the Workbook's FindFont would not find the font in ReadXF + font.Color := FPalette[lColor] + else + if lColor = SYS_DEFAULT_WINDOW_TEXT_COLOR then + font.Color := scBlack + else + font.Color := SetAsPaletteIndex(lColor); { Font weight } lWeight := WordLEToN(AStream.ReadWord); @@ -1983,8 +2018,8 @@ begin if fssStrikeout in AFont.Style then optn := optn or $0008; AStream.WriteWord(WordToLE(optn)); - { Colour index } - AStream.WriteWord(WordToLE(ord(FixColor(AFont.Color)))); + { Color index } + AStream.WriteWord(WordToLE(PaletteIndex(AFont.Color))); { Font weight } if fssBold in AFont.Style then @@ -3005,8 +3040,8 @@ begin if (AFormatRecord <> nil) and (uffBorder in AFormatRecord^.UsedFormattingFields) then begin // Left and right line colors - dw1 := AFormatRecord^.BorderStyles[cbWest].Color shl 16 + - AFormatRecord^.BorderStyles[cbEast].Color shl 23; + dw1 := PaletteIndex(AFormatRecord^.BorderStyles[cbWest].Color) shl 16 + + PaletteIndex(AFormatRecord^.BorderStyles[cbEast].Color) shl 23; // Border line styles if cbWest in AFormatRecord^.Border then dw1 := dw1 or (DWord(AFormatRecord^.BorderStyles[cbWest].LineStyle)+1); @@ -3022,9 +3057,9 @@ begin dw1 := dw1 or $80000000; // Top, bottom and diagonal line colors - dw2 := FixColor(AFormatRecord^.BorderStyles[cbNorth].Color) + - FixColor(AFormatRecord^.BorderStyles[cbSouth].Color) shl 7 + - FixColor(AFormatRecord^.BorderStyles[cbDiagUp].Color) shl 14; + dw2 := PaletteIndex(AFormatRecord^.BorderStyles[cbNorth].Color) + + PaletteIndex(AFormatRecord^.BorderStyles[cbSouth].Color) shl 7 + + PaletteIndex(AFormatRecord^.BorderStyles[cbDiagUp].Color) shl 14; // In BIFF8 both diagonals have the same color - we use the color of the up-diagonal. // Diagonal line style @@ -3041,11 +3076,11 @@ begin // Pattern color if AFormatRecord^.Background.FgColor = scTransparent then w3 := w3 or SYS_DEFAULT_FOREGROUND_COLOR - else w3 := w3 or FixColor(AFormatRecord^.Background.FgColor); + else w3 := w3 or PaletteIndex(AFormatRecord^.Background.FgColor); // Background color if AFormatRecord^.Background.BgColor = scTransparent then w3 := w3 or SYS_DEFAULT_BACKGROUND_COLOR shl 7 - else w3 := w3 or (FixColor(AFormatRecord^.Background.BgColor) shl 7); + else w3 := w3 or (PaletteIndex(AFormatRecord^.Background.BgColor) shl 7); end; rec.Border_BkGr1 := DWordToLE(dw1); @@ -3063,7 +3098,7 @@ initialization RegisterSpreadFormat(TsSpreadBIFF8Reader, TsSpreadBIFF8Writer, sfExcel8); // Converts the palette to litte-endian - MakeLEPalette(@PALETTE_BIFF8, Length(PALETTE_BIFF8)); + MakeLEPalette(PALETTE_BIFF8); end. diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index de9deac8e..aba75881c 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -11,7 +11,7 @@ interface uses Classes, SysUtils, DateUtils, lconvencoding, - fpsTypes, fpSpreadsheet, fpsUtils, fpsNumFormatParser, + fpsTypes, fpSpreadsheet, fpsUtils, fpsNumFormatParser, fpsPalette, fpsReaderWriter; const @@ -211,7 +211,7 @@ const { System colors, for BIFF5-BIFF8 } SYS_DEFAULT_FOREGROUND_COLOR = $0040; SYS_DEFAULT_BACKGROUND_COLOR = $0041; - + SYS_DEFAULT_WINDOW_TEXT_COLOR = $7FFF; { Error codes } ERR_INTERSECTION_EMPTY = $00; // #NULL! @@ -348,11 +348,11 @@ type RecordSize: Word; FCodepage: string; // in a format prepared for lconvencoding.ConvertEncoding FDateMode: TDateMode; - FPaletteFound: Boolean; FIncompleteCell: PCell; FIncompleteNote: String; FIncompleteNoteLength: Word; FFirstNumFormatIndexInFile: Integer; + FPalette: TsPalette; procedure AddBuiltinNumFormats; override; procedure ApplyCellFormatting(ACell: PCell; XFIndex: Word); virtual; //overload; // Extracts a number out of an RK value @@ -360,9 +360,12 @@ type // Returns the numberformat for a given XF record procedure ExtractNumberFormat(AXFIndex: WORD; out ANumberFormat: TsNumberFormat; out ANumberFormatStr: String); virtual; + procedure FixColors; // Tries to find if a number cell is actually a date/datetime/time cell and retrieves the value function IsDateTime(Number: Double; ANumberFormat: TsNumberFormat; ANumberFormatStr: String; out ADateTime: TDateTime): Boolean; + procedure PopulatePalette; virtual; + // Here we can add reading of records which didn't change across BIFF5-8 versions // Read a blank cell procedure ReadBlank(AStream: TStream); override; @@ -433,6 +436,7 @@ type public constructor Create(AWorkbook: TsWorkbook); override; + destructor Destroy; override; end; @@ -443,12 +447,13 @@ type FDateMode: TDateMode; FCodePage: String; // in a format prepared for lconvencoding.ConvertEncoding FFirstNumFormatIndexInFile: Integer; + FPalette: TsPalette; procedure AddBuiltinNumFormats; override; function FindXFIndex(ACell: PCell): Integer; virtual; - function FixColor(AColor: TsColor): TsColor; override; function GetLastRowIndex(AWorksheet: TsWorksheet): Integer; function GetLastColIndex(AWorksheet: TsWorksheet): Word; function GetPrintOptions: Word; virtual; + function PaletteIndex(AColor: TsColor): Word; // Helper function for writing the BIFF header procedure WriteBIFFHeader(AStream: TStream; ARecID, ARecSize: Word); @@ -548,6 +553,8 @@ type public constructor Create(AWorkbook: TsWorkbook); override; + destructor Destroy; override; + procedure CheckLimitations; override; end; procedure AddBuiltinBiffFormats(AList: TStringList; @@ -773,16 +780,31 @@ end; constructor TsSpreadBIFFReader.Create(AWorkbook: TsWorkbook); begin inherited Create(AWorkbook); + + FPalette := TsPalette.Create; + PopulatePalette; + FCellFormatList := TsCellFormatList.Create(true); - // Allow duplicates! XF indexes get out of sync if not all format records are in list + // true = allow duplicates! XF indexes get out of sync if not all format records are in list + // Initial base date in case it won't be read from file FDateMode := dm1900; + // Limitations of BIFF5 and BIFF8 file format FLimitations.MaxColCount := 256; FLimitations.MaxRowCount := 65536; FLimitations.MaxPaletteSize := 64; end; +{@@ ---------------------------------------------------------------------------- + Destructor of the reader class +-------------------------------------------------------------------------------} +destructor TsSpreadBIFFReader.Destroy; +begin + FPalette.Free; + inherited Destroy; +end; + {@@ ---------------------------------------------------------------------------- Adds the built-in number formats to the NumFormatList. Valid for BIFF5...BIFF8. Needs to be overridden for BIFF2. @@ -814,7 +836,6 @@ begin end; end; - {@@ ---------------------------------------------------------------------------- Extracts a number out of an RK value. Valid since BIFF3. @@ -871,6 +892,47 @@ begin end; end; +{@@ ---------------------------------------------------------------------------- + It is a problem of the biff file structure that the font is loaded before the + palette. Therefore, when reading the font, we cannot determine its rgb color. + We had stored temporarily the palette index in the font color member and + are replacing it here by the corresponding rgb color. This is possible because + FixFontColors is called at the end of the workbook globals records when + everything is known. +-------------------------------------------------------------------------------} +procedure TsSpreadBIFFReader.FixColors; +var + i: Integer; + fnt: TsFont; + fmt: PsCellFormat; + + procedure FixColor(var AColor: TsColor); + begin + if IsPaletteIndex(AColor) then + AColor := FPalette[AColor and $00FFFFFF]; + end; + +begin + for i:=0 to FWorkbook.GetFontCount - 1 do + begin + fnt := FWorkbook.GetFont(i); + FixColor(fnt.Color); + end; + + for i:=0 to FCellFormatList.Count-1 do + begin + fmt := FCellFormatList[i]; + FixColor(fmt^.Background.BgColor); + FixColor(fmt^.Background.FgColor); + FixColor(fmt^.BorderStyles[cbEast].Color); + FixColor(fmt^.BorderStyles[cbWest].Color); + FixColor(fmt^.BorderStyles[cbNorth].Color); + FixColor(fmt^.BorderStyles[cbSouth].Color); + FixColor(fmt^.BorderStyles[cbDiagUp].Color); + FixColor(fmt^.BorderStyles[cbDiagDown].Color); + end; +end; + {@@ ---------------------------------------------------------------------------- Converts the number to a date/time and return that if it is -------------------------------------------------------------------------------} @@ -1463,17 +1525,15 @@ end; -------------------------------------------------------------------------------} procedure TsSpreadBIFFReader.ReadPalette(AStream: TStream); var - i, n: Word; - pal: Array of TsColorValue; + n: Word; begin + // Read palette size n := WordLEToN(AStream.ReadWord) + 8; - SetLength(pal, n); - for i:=0 to 7 do - pal[i] := Workbook.GetPaletteColor(i); - for i:=8 to n-1 do - pal[i] := DWordLEToN(AStream.ReadDWord); - Workbook.UsePalette(@pal[0], n, false); - FPaletteFound := true; + FPalette.Clear; + FPalette.AddBuiltinColors; + // Read palette colors and add them to the palette + while FPalette.Count < n do + FPalette.AddColor(DWordLEToN(AStream.ReadDWord)); end; {@@ ---------------------------------------------------------------------------- @@ -2129,6 +2189,15 @@ begin FWorksheet.Options := FWorksheet.Options - [soHasFrozenPanes]; end; +{@@ ---------------------------------------------------------------------------- + Populates the reader's palette by default colors. Will be overwritten if the + file contains a palette on its own +-------------------------------------------------------------------------------} +procedure TsSpreadBIFFReader.PopulatePalette; +begin + FPalette.AddBuiltinColors; +end; + {------------------------------------------------------------------------------} { TsSpreadBIFFWriter } @@ -2142,14 +2211,25 @@ constructor TsSpreadBIFFWriter.Create(AWorkbook: TsWorkbook); begin inherited Create(AWorkbook); - // Initial base date in case it won't be set otherwise. - // Use 1900 to get a bit more range between 1900..1904. - FDateMode := dm1900; - // Limitations of BIFF5 and BIFF8 file formats FLimitations.MaxColCount := 256; FLimitations.MaxRowCount := 65536; FLimitations.MaxPaletteSize := 64; + + // Initial base date in case it won't be set otherwise. + // Use 1900 to get a bit more range between 1900..1904. + FDateMode := dm1900; + + // Color palette + FPalette := TsPalette.Create; + FPalette.AddBuiltinColors; + FPalette.CollectFromWorkbook(AWorkbook); +end; + +destructor TsSpreadBIFFWriter.Destroy; +begin + FPalette.Free; + inherited Destroy; end; {@@ ---------------------------------------------------------------------------- @@ -2164,6 +2244,21 @@ begin ); end; +{@@ ---------------------------------------------------------------------------- + Checks limitations of the file format. Overridden to take care of the + color palette which can only contain a given number of entries. +-------------------------------------------------------------------------------} +procedure TsSpreadBIFFWriter.CheckLimitations; +begin + inherited CheckLimitations; + // Check color count. + if FPalette.Count > FLimitations.MaxPaletteSize then + begin + Workbook.AddErrorMsg(rsTooManyPaletteColors, [FPalette.Count, FLimitations.MaxPaletteSize]); + FPalette.Trim(FLimitations.MaxPaletteSize); + end; +end; + {@@ ---------------------------------------------------------------------------- Determines the index of the XF record, according to formatting of the given cell @@ -2173,17 +2268,6 @@ begin Result := LAST_BUILTIN_XF + ACell^.FormatIndex; end; -function TsSpreadBIFFWriter.FixColor(AColor: TsColor): TsColor; -var - rgb: TsColorValue; -begin - if AColor >= Limitations.MaxPaletteSize then begin - rgb := Workbook.GetPaletteColor(AColor); - Result := Workbook.FindClosestColor(rgb, FLimitations.MaxPaletteSize); - end else - Result := AColor; -end; - function TsSpreadBIFFWriter.GetLastRowIndex(AWorksheet: TsWorksheet): Integer; begin Result := AWorksheet.GetLastRowIndex; @@ -2234,6 +2318,20 @@ begin Result := Result or $0080; end; +{@@ ---------------------------------------------------------------------------- + Determines the index of the specified color in the writer's palette, or, if + not found, gets the index of the "closest" color. +-------------------------------------------------------------------------------} +function TsSpreadBIFFWriter.PaletteIndex(AColor: TsColor): Word; +var + idx: Integer; +begin + idx := FPalette.FindColor(AColor, Limitations.MaxPaletteSize); + if idx = -1 then + idx := FPalette.FindClosestColorIndex(AColor, Limitations.MaxPaletteSize); + Result := word(idx); +end; + {@@ ---------------------------------------------------------------------------- Writes the BIFF record header consisting of the record ID and the size of data to be written immediately afterwards. @@ -2695,14 +2793,14 @@ end; {@@ ---------------------------------------------------------------------------- Writes the PALETTE record for the color palette. - Valid for BIFF3-BIFF8. BIFF2 has no palette in file. + Valid for BIFF3-BIFF8. BIFF2 has no palette in the file. -------------------------------------------------------------------------------} procedure TsSpreadBIFFWriter.WritePalette(AStream: TStream); const NUM_COLORS = 56; var i, n: Integer; - rgb: TsColorValue; + rgb: TsColor; begin { BIFF Record header } WriteBIFFHeader(AStream, INT_EXCEL_ID_PALETTE, 2 + 4*NUM_COLORS); @@ -2710,13 +2808,13 @@ begin { Number of colors } AStream.WriteWord(WordToLE(NUM_COLORS)); - { Take the colors from the palette of the Worksheet } - n := Workbook.GetPaletteSize; + { Take the colors from the internal palette of the writer } + n := FPalette.Count; { Skip the first 8 entries - they are hard-coded into Excel } for i := 8 to 8 + NUM_COLORS - 1 do begin - rgb := Math.IfThen(i < n, Workbook.GetPaletteColor(i), $FFFFFF); + rgb := Math.IfThen(i < n, FPalette[i], $FFFFFF); AStream.WriteDWord(DWordToLE(rgb)) end; end; diff --git a/components/fpspreadsheet/xlsxooxml.pas b/components/fpspreadsheet/xlsxooxml.pas index 262deb09e..8f9fdf387 100755 --- a/components/fpspreadsheet/xlsxooxml.pas +++ b/components/fpspreadsheet/xlsxooxml.pas @@ -43,7 +43,7 @@ uses {$ELSE} fpszipper, {$ENDIF} - fpsTypes, fpSpreadsheet, fpsUtils, fpsReaderWriter, fpsNumFormat, + fpsTypes, fpSpreadsheet, fpsUtils, fpsReaderWriter, fpsNumFormat, fpsPalette, fpsxmlcommon, xlsCommon; type @@ -59,7 +59,8 @@ type FBorderList: TFPList; FHyperlinkList: TFPList; FSharedFormulaBaseList: TFPList; - FThemeColors: array of TsColorValue; + FPalette: TsPalette; + FThemeColors: array of TsColor; FWrittenByFPS: Boolean; procedure ApplyCellFormatting(ACell: PCell; XfIndex: Integer); procedure ApplyHyperlinks(AWorksheet: TsWorksheet); @@ -255,81 +256,7 @@ const MIME_COMMENTS = MIME_SPREADML + '.comments+xml'; MIME_VMLDRAWING = MIME_OFFICEDOCUMENT + '.vmlDrawing'; - LAST_PALETTE_COLOR = $3F; // 63 - -var - // the palette of the 64 default colors as "big-endian color" values - // (identical to BIFF8) - PALETTE_OOXML: array[$00..LAST_PALETTE_COLOR] of TsColorValue = ( - $000000, // $00: black // 8 built-in default colors - $FFFFFF, // $01: white - $FF0000, // $02: red - $00FF00, // $03: green - $0000FF, // $04: blue - $FFFF00, // $05: yellow - $FF00FF, // $06: magenta - $00FFFF, // $07: cyan - - $000000, // $08: EGA black - $FFFFFF, // $09: EGA white - $FF0000, // $0A: EGA red - $00FF00, // $0B: EGA green - $0000FF, // $0C: EGA blue - $FFFF00, // $0D: EGA yellow - $FF00FF, // $0E: EGA magenta - $00FFFF, // $0F: EGA cyan - - $800000, // $10: EGA dark red - $008000, // $11: EGA dark green - $000080, // $12: EGA dark blue - $808000, // $13: EGA olive - $800080, // $14: EGA purple - $008080, // $15: EGA teal - $C0C0C0, // $16: EGA silver - $808080, // $17: EGA gray - $9999FF, // $18: - $993366, // $19: - $FFFFCC, // $1A: - $CCFFFF, // $1B: - $660066, // $1C: - $FF8080, // $1D: - $0066CC, // $1E: - $CCCCFF, // $1F: - - $000080, // $20: - $FF00FF, // $21: - $FFFF00, // $22: - $00FFFF, // $23: - $800080, // $24: - $800000, // $25: - $008080, // $26: - $0000FF, // $27: - $00CCFF, // $28: - $CCFFFF, // $29: - $CCFFCC, // $2A: - $FFFF99, // $2B: - $99CCFF, // $2C: - $FF99CC, // $2D: - $CC99FF, // $2E: - $FFCC99, // $2F: - - $3366FF, // $30: - $33CCCC, // $31: - $99CC00, // $32: - $FFCC00, // $33: - $FF9900, // $34: - $FF6600, // $35: - $666699, // $36: - $969696, // $37: - $003366, // $38: - $339966, // $39: - $003300, // $3A: - $333300, // $3B: - $993300, // $3C: - $993366, // $3D: - $333399, // $3E: - $333333 // $3F: - ); + LAST_PALETTE_INDEX = 63; type TFillListData = class @@ -384,8 +311,6 @@ constructor TsSpreadOOXMLReader.Create(AWorkbook: TsWorkbook); begin inherited Create(AWorkbook); FDateMode := XlsxSettings.DateMode; - // Set up the default palette in order to have the default color names correct. - Workbook.UseDefaultPalette; FSharedStrings := TStringList.Create; FFillList := TFPList.Create; @@ -395,6 +320,8 @@ begin // Allow duplicates because xf indexes used in cell records cannot be found any more. FSharedFormulaBaseList := TFPList.Create; + FPalette := TsPalette.Create; + FPointSeparatorSettings := DefaultFormatSettings; FPointSeparatorSettings.DecimalSeparator := '.'; end; @@ -417,6 +344,7 @@ begin // FCellFormatList, FNumFormatList and FFontList are destroyed by ancestor + FPalette.Free; inherited Destroy; end; @@ -894,7 +822,7 @@ end; function TsSpreadOOXMLReader.ReadColor(ANode: TDOMNode): TsColor; var s: String; - rgb: TsColorValue; + rgb: TsColor; idx: Integer; tint: Double; n: Integer; @@ -912,16 +840,19 @@ begin s := GetAttrValue(ANode, 'rgb'); if s <> '' then begin - Result := FWorkbook.AddColorToPalette(HTMLColorStrToColor('#' + s)); + Result := HTMLColorStrToColor('#' + s); exit; end; s := GetAttrValue(ANode, 'indexed'); if s <> '' then begin Result := StrToInt(s); - n := FWorkbook.GetPaletteSize; - if (Result <= LAST_PALETTE_COLOR) and (Result < n) then + n := FPalette.Count; + if (Result <= LAST_PALETTE_INDEX) and (Result < n) then + begin + Result := FPalette[Result]; exit; + end; // System colors // taken from OpenOffice docs case Result of @@ -956,7 +887,7 @@ begin tint := StrToFloat(s, FPointSeparatorSettings); rgb := TintedColor(rgb, tint); end; - Result := FWorkBook.AddColorToPalette(rgb); + Result := rgb; exit; end; end; @@ -1465,36 +1396,42 @@ var node, colornode: TDOMNode; nodename: String; s: string; - clr: TsColor; - rgb: TsColorValue; + cidx: Integer; // color index + rgb: TsColor; n: Integer; begin // OOXML sometimes specifies color by index even if a palette ("indexedColors") // is not loaeded. Therefore, we use the BIFF8 palette as default because // the default indexedColors are identical to it. - n := Length(PALETTE_OOXML); - FWorkbook.UsePalette(@PALETTE_OOXML, n); + FPalette.Clear; + FPalette.AddBuiltinColors; // This adds the BIFF2 colors 0..7 + FPalette.AddExcelColors; // This adds the BIFF8 colors 8..63 + n := FPalette.Count; + if ANode = nil then exit; - clr := 0; + cidx := 0; node := ANode.FirstChild; - while Assigned(node) do begin + while Assigned(node) do + begin nodename := node.NodeName; - if nodename = 'indexedColors' then begin + if nodename = 'indexedColors' then + begin colornode := node.FirstChild; - while Assigned(colornode) do begin + while Assigned(colornode) do + begin nodename := colornode.NodeName; if nodename = 'rgbColor' then begin s := GetAttrValue(colornode, 'rgb'); if s <> '' then begin rgb := HTMLColorStrToColor('#' + s); - if clr < n then begin - FWorkbook.SetPaletteColor(clr, rgb); - inc(clr); + if cidx < n then begin + FPalette[cidx] := rgb; + inc(cidx); end else - FWorkbook.AddColorToPalette(rgb); + FPalette.AddColor(rgb); end; end; colornode := colorNode.NextSibling; @@ -2078,20 +2015,19 @@ const "slantDashDot", "mediumDashDot", "mediumDashed", "medium", "thick", "double" } var styleName: String; - colorName: String; - rgb: TsColorValue; + colorStr: String; + rgb: TsColor; begin if (ABorder in AFormatRecord^.Border) then begin // Line style styleName := LINESTYLE_NAME[AFormatRecord^.BorderStyles[ABorder].LineStyle]; // Border color - rgb := Workbook.GetPaletteColor(AFormatRecord^.BorderStyles[ABorder].Color); - //rgb := Workbook.GetPaletteColor(ACell^.BorderStyles[ABorder].Color); - colorName := ColorToHTMLColorStr(rgb, true); + rgb := AFormatRecord^.BorderStyles[ABorder].Color; + colorStr := ColorToHTMLColorStr(rgb, true); AppendToStream(AStream, Format( '<%s style="%s">', - [ABorderName, styleName, colorName, ABorderName] + [ABorderName, styleName, colorStr, ABorderName] )); end else AppendToStream(AStream, Format( @@ -2255,11 +2191,11 @@ begin if FFillList[i]^.Background.FgColor = scTransparent then fc := 'auto="1"' else - fc := Format('rgb="%s"', [Copy(Workbook.GetPaletteColorAsHTMLStr(FFillList[i]^.Background.FgColor), 2, 255)]); + fc := Format('rgb="%s"', [Copy(ColorToHTMLColorStr(FFillList[i]^.Background.FgColor), 2, MaxInt)]); if FFillList[i]^.Background.BgColor = scTransparent then bc := 'auto="1"' else - bc := Format('rgb="%s"', [Copy(Workbook.GetPaletteColorAsHTMLStr(FFillList[i]^.Background.BgColor), 2, 255)]); + bc := Format('rgb="%s"', [Copy(ColorToHTMLColorStr(FFillList[i]^.Background.BgColor), 2, MaxInt)]); AppendToStream(AStream, ''); AppendToStream(AStream, Format( @@ -2283,39 +2219,24 @@ var i: Integer; font: TsFont; s: String; - rgb: TsColorValue; begin AppendToStream(FSStyles, Format( '', [Workbook.GetFontCount])); for i:=0 to Workbook.GetFontCount-1 do begin font := Workbook.GetFont(i); - { - if font = 4 then -// if font = nil then - AppendToStream(AStream, '') - // Font #4 is missing in fpspreadsheet due to BIFF compatibility. We write - // an empty node to keep the numbers in sync with the stored font index. - else begin} - s := Format('', [font.Size, font.FontName], FPointSeparatorSettings); - if (fssBold in font.Style) then - s := s + ''; - if (fssItalic in font.Style) then - s := s + ''; - if (fssUnderline in font.Style) then - s := s + ''; - if (fssStrikeout in font.Style) then - s := s + ''; - if font.Color <> scBlack then begin - if font.Color < 64 then - s := s + Format('', [font.Color]) - else begin - rgb := Workbook.GetPaletteColor(font.Color); - s := s + Format('', [Copy(ColorToHTMLColorStr(rgb), 2, 255)]); - end; - end; - AppendToStream(AStream, - '', s, ''); -// end; + s := Format('', [font.Size, font.FontName], FPointSeparatorSettings); + if (fssBold in font.Style) then + s := s + ''; + if (fssItalic in font.Style) then + s := s + ''; + if (fssUnderline in font.Style) then + s := s + ''; + if (fssStrikeout in font.Style) then + s := s + ''; + if font.Color <> scBlack then + s := s + Format('', [Copy(ColorToHTMLColorStr(font.Color), 2, MaxInt)]); + AppendToStream(AStream, + '', s, ''); end; AppendToStream(AStream, ''); @@ -2481,27 +2402,11 @@ begin ); end; -{ Writes the workbook's color palette to the file } +{ In older versions, the workbook had a color palette which was written here. + Now there is no palette any more. } procedure TsSpreadOOXMLWriter.WritePalette(AStream: TStream); -var - rgb: TsColorValue; - i: Integer; begin - AppendToStream(AStream, - '' + - ''); - - // There must not be more than 64 palette entries because the next colors - // are system colors. - for i:=0 to Min(LAST_PALETTE_COLOR, Workbook.GetPaletteSize-1) do begin - rgb := Workbook.GetPaletteColor(i); - AppendToStream(AStream, - ''); - end; - - AppendToStream(AStream, - '' + - ''); + // just keep it here in case we'd need it later... end; procedure TsSpreadOOXMLWriter.WritePageMargins(AStream: TStream; @@ -3636,8 +3541,5 @@ initialization // Registers this reader / writer on fpSpreadsheet RegisterSpreadFormat(TsSpreadOOXMLReader, TsSpreadOOXMLWriter, sfOOXML); - // Create color palette for OOXML file format - MakeLEPalette(@PALETTE_OOXML, Length(PALETTE_OOXML)); - end.