FastReport_2022_VCL/Source/ExportPack/frxBiffConverter.pas
2024-01-01 16:13:08 +01:00

1671 lines
38 KiB
ObjectPascal

{******************************************}
{ }
{ FastReport VCL }
{ Converter from TfrxIEMatrix to BIFF }
{ }
{ Copyright (c) 1998-2021 }
{ by Fast Reports Inc. }
{ }
{******************************************}
unit frxBiffConverter;
{$I frx.inc}
interface
uses
Graphics,
Windows,
Classes,
{$IFDEF DELPHI16}
System.UITypes,
{$ENDIF}
frxClass,
frxExportMatrix,
frxBIFF,
frxProgress,
frxGraphicUtils;
{ Printers.TPrinterOrientation
This type is not defined in Delphi4, therefore
I have to define these constants manually. }
const
frxPoPortrait = 0; // poPortrait
frxPoLandscape = 1; // poLandscape
type
TfrxPrintOrient = LongInt; // Printers.TPrinterOrientation
{ Fields of this structure are
copies of corresponding fields
from TfrxReportPage. }
TfrxPageInfo = record
PaperWidth: Extended;
PaperHeight: Extended;
LeftMargin: Extended;
RightMargin: Extended;
TopMargin: Extended;
BottomMargin: Extended;
Orientation: TfrxPrintOrient;
PageCount: LongInt;
PaperSize: LongInt;
Name: string;
end;
{ - Matrix - the exported data matrix
- Sheet - the resulting Excel sheet
- Workbook- a workbook that will contain the sheet
- Page - the current page
- PageId - index to the page
- Size - count of columns and rows in the mapped rectangle area
- Source - the top-left cell in TfrxIEMatrix
- Dest - the top-left cell in the Excel sheet
- Images - list of indexes to TfrxIEMObject with pictures
- Surr - if set, empty cells will be added for padding
- Pictures - if set, pictures are exported
- PageBreaks - if set, pages breaks are added to Excel sheet
- FitPages - adjust page dimensions to fit to a print page
- TSW - with of a tab offset
- ZCW - width of zero character
- GridLines - if set, the sheet will contain grid lines }
TfrxBiffPageOptions = record
Matrix: TfrxIEMatrix;
Sheet: TBiffSheet;
Page: TfrxPageInfo;
PageId: LongInt;
Source: TPoint;
Dest: TPoint;
Size: TPoint;
Images: TList;
Surr: Boolean;
Pictures: Boolean;
PageBreaks: Boolean;
WorkBook: TBiffWorkbook;
FitPages: Boolean;
TSW: Integer;
ZCW: Integer;
GridLines: Boolean;
RHScale: Extended;
Formulas: Boolean;
end;
{ Any object in the matrix has a style.
This style must be converted to XF record
that can be written to a BIFF document.
The conversion is slow, so it need be cached. }
TfrxBiffStyles = class
private
FXFi: array of Integer; // XF indexes
FFonti: array of Integer; // Font indexes
FFonts: array of TFont; // TFont objects
FWorkbook: TBiffWorkbook;
FTSW: Integer; // the width of the tab character
{ Creates an XF record and returns an index to this record.
Excel defines XF records of two types: cell XFs and style XFs.
This function creates a cell XF record. }
function CreateStyle(s: TfrxIEMStyle; BgPattern: Boolean;
RTL: Boolean): LongInt;
{ Returns an index to an entry in FXFi that corresponds
to the given arguments. }
function GetEntryIndex(StyleIndex: Integer; Background, RTL: Boolean): Integer;
{ Returns True if the style index is valid }
function IsValidStyleIndex(StyleIndex: Integer): Boolean;
public
{ StylesCount - the maximum number of styles that will be added via AddStyle
Workbook - the workbook that will keep all added styles and fonts
TSW - the width of the tab character }
constructor Create(StylesCount: Integer; Workbook: TBiffWorkbook; TSW: Integer);
{ Creates an XF record from the given style object and
two boolean constants. The created XF record is associated
with the given style index. The routine returns an index
to the created XF record. }
function AddStyle(StyleIndex: Integer; Style: TfrxIEMStyle;
Background, RTL: Boolean): Integer;
{ Returns an index to a previously created XF record }
function GetStyle(StyleIndex: Integer; Background, RTL: Boolean): Integer;
{ Creates a FONT record, adds it to a workbook and
returns an index to the added FONT record. }
function CreateFont(f: TFont; ss: TSubStyle = ssNormal): LongInt;
{ Creates a FONT recor from the given font object.
The result is an index to the created FONT record. }
function AddFont(Font: TFont): Integer;
{ Returns an index to a FONT record previously
created via AddFont. If AddFont hasn't been
called for this font object, then -1 is returned. }
function GetFont(Font: TFont): Integer;
end;
TfrxBiffConverter = class
private
po: TfrxBiffPageOptions;
FShowprogress: Boolean;
FProgressBar: TfrxProgress;
procedure Convert(Sheet: TBiffSheet; BiffMaxRow_: Longint = BiffMaxRow);
procedure InitProgressBar(Steps: Integer; Text: string);
procedure StepProgressBarIf(Condition: Boolean);
procedure FreeProgressBar;
procedure BreakIfTerminated;
//
// todo: any cell text seems to end with a line break
// even no line breaks are written in the designer.
// This will suppress the ending system symbols.
//
procedure SuppressEndingTrash(var s: WideString);
function CreateFormulaCell(Obj: TfrxIEMObject): TBiffCell;
function CreateHTMLCell(obj: TfrxIEMObject; Styles: TfrxBiffStyles): TBiffTextCell;
function CreateTextCell(obj: TfrxIEMObject): TBiffCell;
function CreateNumberCell(obj: TfrxIEMObject): TBiffCell;
function CreateDateCell(obj: TfrxIEMObject): TBiffCell;
function CreateCell(r, c: LongInt; obj: TfrxIEMObject; Styles: TfrxBiffStyles): TBiffCell;
function IsFormula(Obj: TfrxIEMObject): Boolean;
procedure SetColWidths(s: TBiffSheet);
procedure SetRowHeights(s: TBiffSheet);
procedure SetMargin(var m: TBiffMargin); // page margin in inches
procedure SetPageSetup(ps: TBiffPageSetup);
procedure AddImage(Sheet: TBiffSheet; ObjId: LongInt);
procedure MergeCells(Sheet: TBiffSheet; ObjId: Integer);
{ Ratio of a length unit of an Excel column width to a pixel.
Example:
w := 300; // 300 pixels
cw := w * GetColWidthFactor; // excel column's width is 300 pixels }
function GetColWidthFactor: Double;
{ Ratio of a length unit of an Excel row height to a pixel.
Example:
h := 200; // 200 pixels
rh := h * GetRowHeightFactor; // excel row's height is 200 pixels }
function GetRowHeightFactor: Double;
{ Returns the width of a column in units
defined in [MS-XLS] }
function GetColWidth(Col: LongInt): Double;
{ Returns the height of a row in units
defines in [MS-XLS] }
function GetRowHeight(Row: LongInt): Double;
public
function GetSheet(BiffMaxRow_: Longint = BiffMaxRow): TBiffSheet;
property Options: TfrxBiffPageOptions write po;
property ShowProgress: Boolean read FShowProgress write FShowProgress;
end;
ETerminated = class(TObject); // exporting terminated
EInvalidFRFormat = class(TObject); // invalid FR number format
function frxConvertMatrixToBiffSheet(po: TfrxBiffPageOptions;
ShowProgress: Boolean;
BiffMaxRow_: Longint = BiffMaxRow): TBiffSheet;
implementation
uses
frxRes,
frxImageConverter,
frxEscher,
SysUtils;
function ResStr(Tag: string): string;
begin
Result := frxResources.Get(Tag)
end;
function GetSystemDecimalSeparator: Char;
begin
{$IFDEF Delphi15}
Result := FormatSettings.DecimalSeparator
{$ELSE}
Result := SysUtils.DecimalSeparator
{$ENDIF}
end;
function GetSystemThousandSeparator: Char;
begin
{$IFDEF Delphi15}
Result := FormatSettings.ThousandSeparator
{$ELSE}
Result := SysUtils.ThousandSeparator
{$ENDIF}
end;
function frxConvertMatrixToBiffSheet(po: TfrxBiffPageOptions; ShowProgress: Boolean; BiffMaxRow_: Longint = BiffMaxRow): TBiffSheet;
var
Conv: TfrxBiffConverter;
begin
Conv := TfrxBiffConverter.Create;
try
Conv.ShowProgress := ShowProgress;
Conv.Options := po;
Result := Conv.GetSheet(BiffMaxRow_);
finally
Conv.Free;
end;
end;
function DupChar(c: Char; n: Integer): string;
var
i: Integer;
begin
SetLength(Result, n);
for i := 1 to n do
Result[i] := c;
end;
{ TfrxBiffConverter }
procedure TfrxBiffConverter.SuppressEndingTrash(var s: WideString);
begin
if Copy(s, Length(s) - 1, 2) = #13#10 then
s := Copy(s, 1, Length(s) - 2)
end;
function TfrxBiffConverter.CreateHTMLCell(obj: TfrxIEMObject;
Styles: TfrxBiffStyles): TBiffTextCell;
var
orig: WideString; // Cell text
text: WideString; // Buffer
ucs: TBiffUCS; // BIFF8 string
textLen: LongInt; // Length(text)
curTag: TfrxHTMLTag; // Currently opened HTML tag
curStr: WideString;
curLen: LongInt;
function Eq(a, b: TfrxHTMLTag): Boolean;
begin
if (a = nil) and (b = nil) then
Result := True
else if (a = nil) or (b = nil) then
Result := False
else
Result :=
(a.Style = b.Style) and
(a.Color = b.Color) and
(a.SubType = b.SubType);
end;
procedure PushChar(Pos: LongInt);
var
c: WideChar;
begin
c := orig[Pos];
if c = #13 then Exit;
curStr := curStr + c;
Inc(curLen);
end;
procedure ResetCurTag;
begin
curTag := nil;
curStr := '';
curLen := 0;
end;
//
// Creates a FONT record and returns its index.
//
function TagToFont(tag: TfrxHTMLTag): LongInt;
var
font: TFont;
begin
font := TFont.Create;
font.Assign(obj.Style.Font);
with font do
begin
Color := tag.Color;
Style := tag.Style;
end;
Result := Styles.CreateFont(font, tag.SubType);
font.Free;
end;
//
// Adds a formatting run defined by the current tag (cur).
//
procedure AddFormat;
begin
if curTag = nil then Exit;
ucs.AddFormat(textLen, TagToFont(curTag));
text := text + curStr;
Inc(textLen, curLen);
end;
var
list: TfrxHTMLTagsList;
tags: TfrxHTMLTags;
tag: TfrxHTMLTag;
i, j, sst: LongInt;
begin
text := obj.Memo.Text;
textLen := Length(text);
list := TfrxHTMLTagsList.Create;
list.AllowTags := True;
with obj.Style.Font do
list.SetDefaults(Color, 0, Style);
orig := text;
list.ExpandHTMLTags(text);
text := '';
textLen := 0;
ResetCurTag;
ucs := TBiffUCS.Create;
for i := 0 to list.Count - 1 do
begin
tags := list[i];
for j := 0 to tags.Count - 1 do
begin
tag := tags[j];
if not Eq(tag, curTag) then
begin
AddFormat;
ResetCurTag;
curTag := tag;
end;
PushChar(tag.Position);
end;
end;
AddFormat;
list.Free;
SuppressEndingTrash(text);
with ucs do
begin
Data := text;
Len16 := True;
end;
sst := po.WorkBook.AddString(ucs);
Result := TBiffTextCell.Create(sst);
end;
function TfrxBiffConverter.CreateTextCell(obj: TfrxIEMObject): TBiffCell;
var
sst: LongInt;
text: WideString;
begin
text := obj.Memo.Text;
SuppressEndingTrash(text);
if text = '' then
begin
Result := TBiffCell.Create;
Exit;
end;
ValidateLineBreaks(text);
sst := po.WorkBook.AddString(text);
Result := TBiffTextCell.Create(sst)
end;
{$HINTS OFF}
function TfrxBiffConverter.CreateFormulaCell(Obj: TfrxIEMObject): TBiffCell;
var
Compiler: TBiffFormulaCompiler;
Stream: TStream;
begin
Compiler := TBiffFormulaCompiler.Create;
try
try
Compiler.LinkTable := po.WorkBook.LinkTable;
with Obj.Memo do
if Copy(Text, 1, 1) = '=' then
Compiler.Formula := Copy(Text, 2, Length(Text) - 1)
else
Compiler.Formula := Text;
Stream := TMemoryStream.Create;
try
Compiler.SaveToStream(Stream);
except
Stream.Free;
Result := nil;
raise
end;
Result := TBiffFormulaCell.Create(Stream);
finally
Compiler.Free;
end;
except
Result := CreateTextCell(Obj);
end;
end;
{$HINTS ON}
function TfrxBiffConverter.CreateNumberCell(obj: TfrxIEMObject): TBiffCell;
function Clean(const s: AnsiString): AnsiString;
var
i, j: Integer;
begin
SetLength(Result, Length(s));
j := 1;
for i := 1 to Length(s) do
if s[i] in ['0'..'9'] then
begin
Result[j] := s[i];
Inc(j);
end;
SetLength(Result, j - 1);
end;
function CleanText(const s: AnsiString; Sep: AnsiChar): AnsiString;
var
i, j: Integer;
begin
SetLength(Result, Length(s));
j := 1;
for i := 1 to Length(s) do
if s[i] in ['0'..'9', Sep] then
begin
Result[j] := s[i];
Inc(j);
end;
SetLength(Result, j - 1);
end;
function CleanExp(const s: AnsiString; Sep: AnsiChar): AnsiString;
var
i, j: Integer;
Exp, ExpSign: Boolean;
begin
SetLength(Result, Length(s));
j := 1;
Exp := False;
ExpSign := False;
for i := 1 to Length(s) do
if s[i] in ['-', '+', '0'..'9', 'e', 'E', Sep] then
begin
if s[i] in ['e', 'E'] then
begin
if not Exp then Exp := True else Continue;
end;
if Exp then
begin
if s[i] in ['-', '+'] then if not ExpSign then ExpSign := True else Continue;
end
else
if s[i] = '+' then Continue;
if s[i] = Sep then
Result[j] := AnsiChar(GetSystemDecimalSeparator)
else
Result[j] := s[i];
Inc(j);
end;
SetLength(Result, j - 1);
end;
function Pos(const s: AnsiString; c: AnsiChar): Integer;
begin
for Result := 1 to Length(s) do
if s[Result] = c then
Exit;
Result := 0;
end;
function FracPart(const s: AnsiString): Extended;
var
i: Integer;
begin
Result := 0;
for i := Length(s) downto 1 do
Result := (Result + Ord(s[i]) - Ord('0'))*0.1
end;
function IntPart(const s: AnsiString): Extended;
var
i: Integer;
begin
Result := 0;
for i := 1 to Length(s) do
Result := Result*10 + Ord(s[i]) - Ord('0')
end;
var
Text: AnsiString;
Sep: AnsiChar;
ip, fp: Extended;
Sign: Extended;
i: Integer;
begin
Result := nil;
Text := AnsiString(obj.Memo.Text);
if Text = '' then
Exit;
with obj.Style.DisplayFormat do
if DecimalSeparator <> '' then
Sep := AnsiChar(DecimalSeparator[1])
else
Sep := AnsiChar(GetSystemDecimalSeparator);
if (Text[1] = '-') or (Text[1] = '(') then
Sign := -1
else
Sign := +1;
if (Pos(Text, 'e') <> 0) or (Pos(Text, 'E') <> 0) then
begin
try
Result := TBiffNumberCell.Create(StrToFloat(string(CleanExp(Text, Sep))));
except
Result := TBiffNumberCell.Create(StrToFloat(string(CleanText(Text, Sep))));
end
end
else
begin
i := Pos(Text, Sep);
if i = 0 then
begin
ip := IntPart(Clean(Text));
fp := 0;
end
else
begin
ip := IntPart(Clean(Copy(Text, 1, i - 1)));
fp := FracPart(Clean(Copy(Text, i + 1, Length(Text) - i)));
end;
Result := TBiffNumberCell.Create(Sign*(ip + fp));
end;
end;
function TfrxBiffConverter.CreateDateCell(obj: TfrxIEMObject): TBiffCell;
var
{$IFDEF DELPHI12}
Text: String;
{$ELSE}
Text: AnsiString;
{$ENDIF}
sformat: String;
D: TDateTime;
FS: TFormatSettings;
procedure SwapChart(ch: char);
begin
Text := StringReplace(Text, ch, '.', [rfReplaceAll]);
sformat := StringReplace(sformat, ch, '.', [rfReplaceAll]);
end;
begin
Result := nil;
{$IFDEF DELPHI12}
Text := String(obj.Memo.Text);
{$ELSE}
Text := AnsiString(obj.Memo.Text);
{$ENDIF}
if Text = '' then
Exit;
GetLocaleFormatSettings(GetUserDefaultLCID, FS);
sformat := obj.Style.DisplayFormat.FormatStr;
SwapChart('/');
SwapChart('-');
SwapChart('\');
FS.DateSeparator := '.';
FS.TimeSeparator := ':';
FS.ShortDateFormat := sformat;
FS.ShortTimeFormat := 'hh:nn:ss';
//D := StrToDateTimeDef(Text, Now, FS);
try
D := StrToDateTime(Trim(Text), FS);
Result := TBiffNumberCell.Create(d);
except
Result := nil;
end;
//Result := TBiffNumberCell.Create(d);
end;
function TfrxBiffConverter.CreateCell(r, c: LongInt; obj: TfrxIEMObject;
Styles: TfrxBiffStyles): TBiffCell;
var
cell: TBiffCell;
img: Boolean;
begin
if obj = nil then
begin
Result := TBiffCell.Create;
with Result do
begin
Row := r;
Col := c;
XF := 15;
end;
Exit;
end;
cell := nil;
if obj.Counter > 1 then
cell := TBiffCell.Create // Blank cell (cell without text)
else if IsFormula(obj) then
cell := CreateFormulaCell(obj)
else if obj.HTMLTags then
cell := CreateHTMLCell(obj, Styles) // Text cell with formatting runs
else if obj.Style.DisplayFormat.Kind = fkNumeric then
cell := CreateNumberCell(obj)
else if (obj.Style.DisplayFormat.Kind = fkDateTime) then //added support for datetime
cell := CreateDateCell(obj);
if cell = nil then
cell := CreateTextCell(obj); // Cell with plain text (without HTML formatting)
img := (obj.Image <> nil) and
(obj.Image.Width <> 0) and (obj.Image.Height <> 0);
with cell do
begin
Row := r;
Col := c;
XF := Styles.AddStyle(obj.StyleIndex, obj.Style, not img, obj.RTL);
end;
Result := cell;
end;
function TfrxBiffConverter.GetColWidthFactor: Double;
begin
Result := 96 * 256 / (72 * po.ZCW);
end;
function TfrxBiffConverter.GetRowHeightFactor: Double;
begin
Result := 72 * 20 / 96;
end;
function TfrxBiffConverter.GetColWidth(Col: LongInt): Double;
begin
with po.Matrix do
begin
if Col < Width - 1 then
Result := GetXPosById(Col + 1)
else with po.Page do
Result := (PaperWidth - RightMargin) / 25.4 * 96;
Result := Round(Result - GetXPosById(Col));
Result := Result * GetColWidthFactor;
if Result < 0 then Result := 0;
end;
end;
function TfrxBiffConverter.GetRowHeight(Row: LongInt): Double;
begin
with po.Matrix do
begin
{ todo: There's a bug somewhere in TfrxIEMatrix.
GetXPosById(0) returns a distance between the left
side of the page and the left side of the leftmost
object. GetYPosById(0) returns a distance between
the top side of the page and the top side of the
topmost object MINUS the top margin. }
if Row < Height - 1 then
Result := GetYPosById(Row + 1)
else with po.Page do
Result := (PaperHeight - BottomMargin - TopMargin) / 25.4 * 96;
Result := Round(Result - GetYPosById(Row));
Result := Result * GetRowHeightFactor;
if Result < 0 then Result := 0;
end;
end;
procedure TfrxBiffConverter.SetColWidths(s: TBiffSheet);
const
ColBlock: Integer = 100;
var
i: LongInt;
w: Double;
begin
if po.Size.X = 0 then Exit;
{ Calculate the scaling factor w. }
if not po.FitPages then
w := 1.0
else
begin
try
with po.Matrix do
w := GetXPosById(po.Source.X + po.Size.X) - GetXPosById(po.Source.X);
except
w := 2.09;
end;
w := w + po.Size.X; // inaccuracy that can occur
w := w / 96 * 25.4; // convert to millimeters
with po.Page do
if w > 0.0 then
w := (PaperWidth - (LeftMargin + RightMargin)) / w
else
w := 1.0;
if w > 1.0 then w := 1.0;
end;
InitProgressBar(po.Size.X div ColBlock, ResStr('BiffCol'));
try
for i := 0 to po.Size.X - 1 do
begin
s.ColWidth[po.Dest.X + i] := Round(w * GetColWidth(po.Source.X + i));
StepProgressBarIf(i mod ColBlock = 0);
BreakIfTerminated;
end;
finally
FreeProgressBar;
end;
end;
procedure TfrxBiffConverter.SetRowHeights(s: TBiffSheet);
const
RowBlock: Integer = 100;
var
i: LongInt;
h: Extended;
begin
if po.Size.Y = 0 then Exit;
{ h = a ratio of the paper height to the summary height
of all cells in the exported matrix.
Normally, h >= 1.0. But if the original report is bad
aligned, the formed matrix can be larger than the paper
and h < 1.0. This means that all cells should be shrinked
to fit to the paper. }
if not po.FitPages or po.PageBreaks then
h := 1.0
else
begin
with po.Matrix do
h := GetYPosById(po.Source.Y + po.Size.Y) - GetYPosById(po.Source.Y);
h := h + 2 + po.Size.Y; // inaccuracy that can occur
h := h / 96 * 25.4; // convert to millimeters
with po.Page do
if h > 0.0 then
h := (PaperHeight - (TopMargin + BottomMargin)) / h
else
h := 0.0;
if h > 1.0 then h := 1.0;
end;
InitProgressBar(po.Size.Y div RowBlock, ResStr('BiffRow'));
try
for i := 0 to po.Size.Y - 1 do
begin
s.RowHeight[po.Dest.Y + i] := Round(po.RHScale * h * GetRowHeight(po.Source.Y + i));
StepProgressBarIf(i mod RowBlock = 0);
BreakIfTerminated;
end;
finally
FreeProgressBar;
end;
end;
procedure TfrxBiffConverter.SetMargin(var m: TBiffMargin);
function f2i(x: Extended): Double;
begin
Result := x / 25.4;
end;
begin
with po.Page do
begin
m.Left := f2i(LeftMargin);
m.Top := f2i(TopMargin);
m.Right := f2i(RightMargin);
m.Bottom := f2i(BottomMargin);
end;
end;
procedure TfrxBiffConverter.SetPageSetup(ps: TBiffPageSetup);
function GetOrientation(o: TfrxPrintOrient): TBiffPageOrientation;
begin
Result := bpoPortrait;
if LongInt(o) = frxPoLandscape then
Result := bpoLandscape;
end;
function GetPaperSize(s: LongInt): Word;
begin
if (s = BiffPsUnknown) or (s >= BiffPsReservedMin) and
(s <= BiffPsReservedMax) or (s >= BiffPsCustomMin) then
Result := BiffPsA4
else
Result := s;
end;
begin
ps.Orient := GetOrientation(po.Page.Orientation);
ps.Size := GetPaperSize(po.Page.PaperSize);
ps.Copies := po.Page.PageCount;
end;
procedure TfrxBiffConverter.AddImage(Sheet: TBiffSheet; ObjId: LongInt);
var
LeftCol, TopRow, Cols, Rows: Integer; // object's bounds
Obj: TfrxIEMObject;
Stream: TStream;
begin
Obj := po.Matrix.GetObjectById(ObjId);
if (Obj = nil) or
(Obj.Image = nil) or
(Obj.Image.Width = 0) or
(Obj.Image.Height = 0) then
Exit;
po.Matrix.GetObjectPos(ObjId, LeftCol, TopRow, Cols, Rows);
if (LeftCol < po.Dest.X) or
(TopRow < po.Dest.Y) or
(LeftCol + Cols > po.Dest.X + po.Size.X) or
(TopRow + Rows > po.Dest.Y + po.Size.Y) then
Exit;
with Sheet.AddDrawing do
begin
Stream := TMemoryStream.Create;
try
SaveGraphicAs(Obj.Image, Stream, gpPNG);
Image := po.WorkBook.AddBitmap(EscherBkPNG, Stream);
finally
Stream.Free;
end;
with Pos do
begin
Left := LeftCol;
Top := TopRow;
Right := LeftCol + Cols;
Bottom := TopRow + Rows;
end;
end;
end;
procedure TfrxBiffConverter.Convert(Sheet: TBiffSheet; BiffMaxRow_: Longint = BiffMaxRow);
var
r, c: Integer;
obj: TfrxIEMObject;
id: LongInt;
CurPage: Integer;
Styles: TfrxBiffStyles;
begin
{ If the entire report is exported to a single
sheet, it's needed to add page breaks. CurPage
is the current page index. }
CurPage := po.PageId;
if po.Page.Name = '' then
Sheet.Name := frxGet(9154) + ' ' + IntToStr(po.PageId)
else
Sheet.Name := po.Page.Name;
if not po.GridLines then
Sheet.View.Options := Sheet.View.Options and not BiffWoGridLines;
SetMargin(Sheet.Margin);
SetPageSetup(Sheet.PageSetup);
{ Correct the mapped cells area }
with po do
begin
with Source do
begin
if X < 0 then X := 0;
if Y < 0 then Y := 0;
end;
with Dest do
begin
if X < 0 then X := 0;
if Y < 0 then Y := 0;
if Surr then
begin
Inc(X);
Inc(Y);
end;
end;
with Size do
begin
if X = 0 then X := BiffMaxCol + 1;
if Y = 0 then Y := BiffMaxRow_ + 1;
if Source.X + X > Matrix.Width - 1 then
X := Matrix.Width - 1 - Source.X;
if Source.Y + Y > Matrix.Height - 1 then
Y := Matrix.Height - 1 - Source.Y;
if Source.X + X > BiffMaxCol + 1 then
X := BiffMaxCol + 1 - Source.X;
if Source.Y + Y > BiffMaxRow_ + 1 then
Y := BiffMaxRow_ + 1 - Source.Y;
if X < 0 then
X := 0;
if Y < 0 then
Y := 0;
end;
end;
{ Export cells }
Styles := TfrxBiffStyles.Create(po.Matrix.GetStylesCount, po.WorkBook, po.TSW);
InitProgressBar(po.Size.Y div 128, ResStr('BiffCell'));
try
with po.Matrix do
for r := 0 to po.Size.Y - 1 do
begin
with po.Source do
if po.PageBreaks and (GetCellYPos(Y + r) > GetPageBreak(CurPage)) and (CurPage < 1025) then
begin
Sheet.AddPageBreak(po.Dest.Y + r);
Inc(CurPage);
end;
for c := 0 to po.Size.X - 1 do
begin
with po.Source do
id := GetCell(X + c, Y + r);
if id < 0 then
Continue;
obj := GetObjectById(id);
obj.Counter := obj.Counter + 1;
with po.Dest do
Sheet.AddCell(CreateCell(Y + r, X + c, obj, Styles));
if obj.Counter = 1 then
MergeCells(Sheet, id);
BreakIfTerminated;
end;
StepProgressBarIf(r mod 128 = 0);
end;
finally
Styles.Free;
FreeProgressBar;
end;
{ Set cell sizes }
SetColWidths(Sheet);
SetRowHeights(Sheet);
{ Export pictures }
if po.Pictures then
if po.Images = nil then
with po.Matrix do
try
InitProgressBar(ObjectsCount div 128, ResStr('BiffImg'));
for id := 0 to GetObjectsCount - 1 do
begin
AddImage(Sheet, id);
StepProgressBarIf(id mod 128 = 0);
BreakIfTerminated;
end;
finally
FreeProgressBar
end
else
with po.Images do
try
InitProgressBar(Count div 128, ResStr('BiffImg'));
for id := 0 to Count - 1 do
begin
AddImage(Sheet, LongInt(Items[id]));
StepProgressBarIf(id mod 128 = 0);
BreakIfTerminated;
end;
finally
FreeProgressBar
end;
end;
function TfrxBiffConverter.GetSheet(BiffMaxRow_: Longint = BiffMaxRow): TBiffSheet;
begin
Result := TBiffSheet.Create(po.WorkBook);
try
Convert(Result, BiffMaxRow_)
except
on ETerminated do
{ do nothing }
else
begin
Result.Free;
raise;
end
end
end;
procedure TfrxBiffConverter.InitProgressBar(Steps: Integer; Text: string);
begin
if ShowProgress then
begin
FProgressBar := TfrxProgress.Create(nil);
FProgressBar.Execute(Steps, frxResources.Get('ProgressWait'), True, True);
FProgressBar.Message := Text;
end;
end;
function TfrxBiffConverter.IsFormula(Obj: TfrxIEMObject): Boolean;
begin
Result := po.Formulas and (Copy(Obj.Memo.Text, 1, 1) = '=')
end;
procedure TfrxBiffConverter.MergeCells(Sheet: TBiffSheet; ObjId: Integer);
var
x, y, dx, dy: Integer;
begin
po.Matrix.GetObjectPos(ObjId, x, y, dx, dy);
Sheet.MergeCells(Rect(x, y, x + dx - 1, y + dy - 1));
end;
procedure TfrxBiffConverter.StepProgressBarIf(Condition: Boolean);
begin
if ShowProgress and Condition then
FProgressBar.Tick
end;
procedure TfrxBiffConverter.FreeProgressBar;
begin
FProgressBar.Free
end;
procedure TfrxBiffConverter.BreakIfTerminated;
begin
if ShowProgress and FProgressBar.Terminated then
raise ETerminated.Create
end;
{ TfrxBiffStyles }
constructor TfrxBiffStyles.Create(StylesCount: Integer; Workbook: TBiffWorkbook;
TSW: Integer);
var
i: Integer;
begin
FTSW := TSW;
SetLength(FXFi, 4 * StylesCount);
FWorkbook := Workbook;
for i := 0 to Length(FXFi) - 1 do
FXFi[i] := -1;
end;
function TfrxBiffStyles.IsValidStyleIndex(StyleIndex: Integer): Boolean;
begin
Result := (StyleIndex >= 0) and (StyleIndex * 4 < Length(FXFi));
end;
function TfrxBiffStyles.GetEntryIndex(StyleIndex: Integer;
Background, RTL: Boolean): Integer;
begin
Result := StyleIndex * 4;
if Background then
Inc(Result);
if RTL then
Inc(Result, 2);
end;
function TfrxBiffStyles.AddStyle(StyleIndex: Integer; Style: TfrxIEMStyle;
Background, RTL: Boolean): Integer;
var
i: Integer;
begin
Result := GetStyle(StyleIndex, Background, RTL);
if Result >= 0 then
Exit;
Result := CreateStyle(Style, Background, RTL);
i := GetEntryIndex(StyleIndex, Background, RTL);
FXFi[i] := Result;
end;
function TfrxBiffStyles.GetStyle(StyleIndex: Integer; Background, RTL: Boolean): Integer;
var
i: Integer;
begin
if not IsValidStyleIndex(StyleIndex) then
raise Exception.Create('StyleIndex is out of bounds');
i := GetEntryIndex(StyleIndex, Background, RTL);
Result := FXFi[i];
end;
function TfrxBiffStyles.AddFont(Font: TFont): Integer;
var
n: Integer;
begin
Result := GetFont(Font);
if Result >= 0 then
Exit;
Result := CreateFont(Font);
n := Length(FFonts);
SetLength(FFonts, n + 1);
SetLength(FFonti, n + 1);
FFonts[n] := Font;
FFonti[n] := Result;
end;
function TfrxBiffStyles.GetFont(Font: TFont): Integer;
var
i: Integer;
begin
for i := 0 to Length(FFonts) - 1 do
if FFonts[i] = Font then
begin
Result := FFonti[i];
Exit;
end;
Result := -1;
end;
function TfrxBiffStyles.CreateFont(f: TFont; ss: TSubStyle): LongInt;
var
font: TBiffFont;
procedure AddOption(opt: TBiffFontOptions);
begin
font.Data.Options := font.Data.Options or Word(opt);
end;
function GetWeight: Word;
begin
Result := Word(fwNormal);
if fsBold in f.Style then
Result := Word(fwBold);
end;
function GetUnderline: TBiffFontUnderline;
begin
Result := fuNone;
if fsUnderline in f.Style then
Result := fuSingle;
end;
function GetFamily: TBiffFontFamily;
begin
Result := ffNone;
if fpFixed = f.Pitch then
Result := ffModern;
end;
begin
Result := 0; // Default font
if f = nil then Exit;
font := TBiffFont.Create;
with font.Data do
begin
Height := -MulDiv(f.Height, 1440, f.PixelsPerInch);
if fsItalic in f.Style then
AddOption(foItalic);
if fsStrikeOut in f.Style then
AddOption(foStruckOut);
case ss of
ssSuperscript: Font.Data.Esc := feSuperScript;
ssSubscript: Font.Data.Esc := feSubScript;
end;
Color := FWorkBook.AddColor(LongWord(f.Color));
Weight := GetWeight;
Underline := GetUnderline;
Family := GetFamily;
Charset := f.Charset;
end;
font.Name := f.Name;
Result := FWorkBook.AddFont(font);
end;
function TfrxBiffStyles.CreateStyle(s: TfrxIEMStyle; BgPattern, RTL: Boolean): LongInt;
//
// Adds a number format for the current cell and
// returns an index to it.
//
function GetFormat: LongInt;
var
Fmt, DecSep, ThSep, DecFmt: string;
p, DecPlaces: Integer;
begin
DecSep := '.';
ThSep := ',';
Fmt := s.DisplayFormat.FormatStr;
if Fmt = '' then
case s.DisplayFormat.Kind of
fkText: Result := BiffFmtGeneral;
fkNumeric: Result := BiffFmtFixedPoint;
fkDateTime: Result := BiffFmtDateTime;
fkBoolean: Result := BiffFmtGeneral;
else Result := BiffFmtGeneral
end
else if Fmt[1] <> '%' then
begin
Fmt := StringReplace(Fmt, '%', '\%', [rfReplaceAll]);
Result := FWorkBook.AddFormat(Fmt);
end
else
try
p := Pos('.', Fmt);
if p = 0 then
DecPlaces := 0
else
DecPlaces := StrToInt(Copy(Fmt, p + 1, Length(Fmt) - p - 1));
if DecPlaces <> 0 then
DecFmt := '0' + DecSep + DupChar('0', DecPlaces)
else
DecFmt := '0';
case Fmt[Length(Fmt)] of
'n': Result := FWorkBook.AddFormat('#' + ThSep + '##' + DecFmt);
'm'://Guillaume
begin
Fmt:='#' + ThSep + '##' + DecFmt;
{$IFDEF DELPHI16}
case FormatSettings.CurrencyFormat of
0:Fmt:=FormatSettings.CurrencyString+Fmt; //$1
1:Fmt:=Fmt+FormatSettings.CurrencyString; //1$
2:Fmt:=FormatSettings.CurrencyString+' '+Fmt; //$ 1
3:Fmt:=Fmt+' '+FormatSettings.CurrencyString; //1 $
end;
{$ELSE}
case CurrencyFormat of
0:Fmt:=CurrencyString+Fmt; //$1
1:Fmt:=Fmt+CurrencyString; //1$
2:Fmt:=CurrencyString+' '+Fmt; //$ 1
3:Fmt:=Fmt+' '+CurrencyString; //1 $
end;
{$ENDIF}
Result := FWorkBook.AddFormat(Fmt);
end;
'f': Result := FWorkBook.AddFormat(DecFmt);
'd': Result := FWorkBook.AddFormat('#' + DecSep + DupChar('#', DecPlaces));
'g': Result := BiffFmtGeneral;
else raise EInvalidFRFormat.Create;
end;
except
//
// If the format is not "FR-like" then
// it's assumed to be "Excel-like" and
// is added to the list of formats.
//
Result := FWorkBook.AddFormat(Fmt)
end;
end;
function XFRotation(a: LongInt): Byte;
begin
Result := 0;
if a = 0 then Exit;
a := a mod 360;
if a < 0 then Inc(a, 360);
if a > 180 then Dec(a, 360);
if (a > 0) and (a <= 90) then
Result := a;
if (a < 0) and (a >= -90) then
Result := -a + 90;
end;
{ XF cell allows only a few border styles,
but FastReport's cells allows more kinds
of cells, so there's not a one-to-one conrrespondence
between XF border styles and FastReport border styles.
todo: FR designer allows to specify different borders on
the left side, the right side and so on, but TfrxIEMStyle
does not provide access to these properties }
procedure XFBorder(ft: TfrxFrameType; var b: TBiffLine);
var
w: Single;
color: TColor;
style: TfrxFrameStyle;
procedure SWR(ls: TBiffLineStyle; const min, max: Single);
begin
if ((min < 0) or (w >= min)) and ((max < 0) or (w <= max)) then
b.Style := ls;
end;
begin
color := clNone;
style := fsSolid;
case ft of
ftLeft:
begin
w := s.LeftLine.Width;
color := s.LeftLine.Color;
style := s.LeftLine.Style;
end;
ftTop:
begin
w := s.TopLine.Width;
color := s.TopLine.Color;
style := s.TopLine.Style;
end;
ftRight:
begin
w := s.RightLine.Width;
color := s.RightLine.Color;
style := s.RightLine.Style;
end;
ftBottom:
begin
w := s.BottomLine.Width;
color := s.BottomLine.Color;
style := s.BottomLine.Style;
end;
end;
with FWorkBook do
b.Color := AddColor(color);
b.Style := lsNone;
if w > 1e-6 then
case style of
fsSolid:
begin
SWR(lsThin, -1, 1.5);
SWR(lsMedium, 1.5, 2.5);
SWR(lsThick, 2.5, -1);
end;
fsDash:
begin
SWR(lsDashed, -1, 1.5);
SWR(lsMediumDashed, 1.5, -1);
end;
fsDot:
SWR(lsDotted, -1, -1);
fsDashDot:
begin
SWR(lsThinDashDotted, -1, 1.5);
SWR(lsMediumDashDotted, 1.5, -1);
end;
fsDashDotDot:
begin
SWR(lsThinDashDotDotted, -1, 1.5);
SWR(lsMediumDashDotDotted, 1.5, -1);
end;
fsDouble: SWR(lsDouble, -1, -1);
fsAltDot: SWR(lsHair, -1, -1);
fsSquare: SWR(lsThin, -1, -1);
end;
end;
procedure SetBg(x: TBiffXF; p: TBiffPatternStyle);
begin
with x.Data do
if p = psSolid then
begin
Patt := p;
PattBgColor := $41;
PattColor := FWorkBook.AddColor(LongWord(s.Color));
end
else
begin
Patt := p;
PattBgColor := FWorkBook.AddColor(LongWord(s.Color));
PattColor := FWorkBook.AddColor($000000);
end;
end;
function GetIndent(gap: Extended): Byte;
begin
Result := 0;
if gap <= 1.0 then Exit;
gap := gap / 25.4 * 96 / FTSW;
Result := Round(gap);
Result := Result and 7;
end;
function GetDirection(RTL: Boolean): TBiffXFTextDir;
begin
if RTL then
Result := xftdRTL
else
Result := xftdLTR;
end;
var
XF: TBiffXF;
begin
Result := 15; // Default cell XF
if s = nil then Exit;
XF := TBiffXF.Create;
with XF.Data do
begin
Parent := 0; // Must be zero for cell XFs
if s.Rotation = 90 then
begin
case s.HAlign of
haLeft: VAlign := xfvaBottom;
haRight: VAlign := xfvaTop;
haCenter: VAlign := xfvaCentered;
haBlock: VAlign := xfvaJustified;
end;
case s.VAlign of
vaTop: HAlign := xfhaLeft;
vaBottom: HAlign := xfhaRight;
vaCenter: HAlign := xfhaCentered;
end;
end
else
if s.Rotation = 270 then
begin
case s.HAlign of
haLeft: VAlign := xfvaTop;
haRight: VAlign := xfvaBottom;
haCenter: VAlign := xfvaCentered;
haBlock: VAlign := xfvaJustified;
end;
case s.VAlign of
vaTop: HAlign := xfhaRight;
vaBottom: HAlign := xfhaLeft;
vaCenter: HAlign := xfhaCentered;
end;
end
else
begin
case s.HAlign of
haLeft: HAlign := xfhaLeft;
haRight: HAlign := xfhaRight;
haCenter: HAlign := xfhaCentered;
haBlock: HAlign := xfhaJustified;
end;
case s.VAlign of
vaTop: VAlign := xfvaTop;
vaBottom: VAlign := xfvaBottom;
vaCenter: VAlign := xfvaCentered;
end;
end;
Direction := GetDirection(RTL);
WordWrap := s.WordWrap;
Rotation := XFRotation(s.Rotation);
{ todo: suppose that a memo has the right text alignment
and has a paragraph gap 20 (i.e. not zero). Should there
be an indent from the right memo border or not ?
FR designer doesn't make an indent, MS Excel does, but
I do in the same way as FR designer does. }
if s.HAlign = haLeft then
Indent := GetIndent(s.ParagraphGap);
if ftLeft in s.FrameTyp then XFBorder(ftLeft, L);
if ftTop in s.FrameTyp then XFBorder(ftTop, T);
if ftRight in s.FrameTyp then XFBorder(ftRight, R);
if ftBottom in s.FrameTyp then XFBorder(ftBottom, B);
if not BgPattern or (s.Color = clNone) then
SetBg(XF, psNone)
else
case s.BrushStyle of
bsSolid: SetBg(XF, psSolid);
bsClear: SetBg(XF, psSolid);
bsHorizontal: SetBg(XF, psHor);
bsVertical: SetBg(XF, psVer);
bsBDiagonal: SetBg(XF, psDiag);
bsFDiagonal: SetBg(XF, psDiagBack);
bsCross: SetBg(XF, psCross);
bsDiagCross: SetBg(XF, psCrossDiag);
end;
Format := GetFormat;
Font := CreateFont(s.Font);
UsedAttrs := BiffXfuaAll;
end;
with FWorkBook do
Result := AddXF(XF);
end;
end.