{******************************************}
{ }
{ FastReport VCL }
{ HTML
Export }
{ }
{ Copyright (c) 1998-2021 }
{ by Fast Reports Inc. }
{ }
{******************************************}
unit frxExportHTMLDiv;
{ General advice for using this export.
• Avoid using vertical alignment in memos:
it forces the export to create more
complicated HTML. Leave the alignment vaTop
whenever it's possible.
• Use @-type anchors in TfrxView.URL instead of
#-type, because #-type is much slower to export. }
interface
{$I frx.inc}
uses
{$IFNDEF FPC}
Windows,
{$ENDIF}
SysUtils,
Variants,
Classes,
{$IFDEF FPC}
LCLType, LCLIntf, LCLProc,
{$ENDIF}
{$IFNDEF FPC}
ShellAPI,
{$ENDIF}
Graphics,
frxClass,
frxBaseGraphicsTypes,
frxStorage, // for TCachedStream
frxGradient,
{$IFDEF DELPHI16}
System.UITypes,
{$ENDIF}
frxExportHelpers, frxExportBaseDialog, frxVectorCanvas, frxCSSStyle;
type
{ Measures actual size of a TfrxView.
Example:
Gauge := TfrxBoundsGauge.Create;
Gauge.Obj := MemoView;
Gauge.Bounds.Left; // the leftmost coordinate including the left border
Gauge.Borders.Left; // the left border width }
TfrxBoundsGauge = class
private
FObj: TfrxView;
FBoundsSet: Boolean;
FBounds: TRect;
FBorders: TRect;
FX, FY, FX1, FY1, FDX, FDY: Integer;
FFrameWidth: Integer;
procedure SetObj(Obj: TfrxView);
procedure AddBounds(r: TRect);
function GetInnerWidth: Integer;
function GetInnerHeight: Integer;
protected
procedure BeginDraw;
procedure DrawBackground;
procedure DrawFrame;
procedure DrawLine(x1, y1, x2, y2, w: Integer; Side: TfrxFrameType);
public
property Obj: TfrxView read FObj write SetObj;
property Bounds: TRect read FBounds;
property Borders: TRect read FBorders;
property InnerWidth: Integer read GetInnerWidth;
property InnerHeight: Integer read GetInnerHeight;
end;
{ Represents a HTML tag }
TfrxHTMLItem = class
private
FName: string;
FKeys: TStrings;
FValues: TStrings;
FValue: string;
FRawValue: AnsiString;
FChildren: TObjList;
FLeft, FTop, FWidth, FHeight: Extended;
FLeftSet, FTopSet, FWidthSet, FHeightSet: Boolean;
FStyle: TfrxCSSStyle;
FClass: string;
FRotation: Integer;
FAllowNegativeLeft: Boolean;
FIsTransformMatrix: Boolean;
FTM: array of Extended; // TransformMatrix
procedure SetProp(Index: string; const Value: string);
function GetProp(Index: string): string;
function GetStyle: TfrxCSSStyle;
procedure SetLeft(a: Extended);
procedure SetTop(a: Extended);
procedure SetWidth(a: Extended);
procedure SetHeight(a: Extended);
public
constructor Create(const Name: string);
destructor Destroy; override;
function This: TfrxHTMLItem;
procedure GaudeFrame(Obj: TfrxView);
procedure Gaude(Obj: TfrxView);
procedure WidenBy(Size: Extended);
procedure DoPositive;
class function EscapeAttribute(const s: string): string;
procedure Save(Stream: TStream; Formatted: Boolean);
function Add(const Tag: string): TfrxHTMLItem; overload;
function Add(Item: TfrxHTMLItem): TfrxHTMLItem; overload;
function AddRotated(const Tag: string; ARotation: Integer): TfrxHTMLItem;
function AddTransformed(const Tag: string; ATransformMatrix: array of Extended): TfrxHTMLItem;
procedure AddCSSClass(const s: string);
property Prop[Index: string]: string read GetProp write SetProp; default;
property Value: string write FValue;
property RawValue: AnsiString write FRawValue;
property Name: string write FName;
{ CSS style is created on demand }
property Style: TfrxCSSStyle read GetStyle;
{ These lengths are measured in FR units (pixels) }
property Left: Extended read FLeft write SetLeft;
property Top: Extended read FTop write SetTop;
property Width: Extended read FWidth write SetWidth;
property Height: Extended read FHeight write SetHeight;
property AllowNegativeLeft: Boolean read FAllowNegativeLeft write FAllowNegativeLeft;
end;
{ Queue of automatically serialised HTML tags }
TfrxHTMLItemQueue = class
private
FQueue: array of TfrxHTMLItem;
FUsed: Integer;
FStream: TStream;
FFormatted: Boolean;
protected
procedure Flush;
public
constructor Create(Stream: TStream; Formatted: Boolean);
destructor Destroy; override;
procedure Push(Item: TfrxHTMLItem);
procedure SetQueueLength(n: Integer);
end;
{ HTML export filter }
{$IFDEF DELPHI16}
[ComponentPlatformsAttribute(pidWin32 or pidWin64)]
{$ENDIF}
TfrxHTMLDivExport = class(TExportHTMLDivSVGParent)
private
FTitle: string;
FHTML5: Boolean;
FAllPictures: Boolean;
FPageStyle: TfrxCSSStyle;
FExportAnchors: Boolean;
FPictureTag: Integer;
FQueue: TfrxHTMLItemQueue; // it represents the current page
FUseTemplates: Boolean;
FPrint: Boolean;
FGetNavTemplate: TfrxHTMLExportGetNavTemplate;
FGetMainTemplate: TfrxHTMLExportGetMainTemplate;
FGetToolbarTemplate: TfrxHTMLExportGetToolbarTemplate;
FPreviewOutline: TfrxCustomOutline;
FOutlineTree: TfrxHTMLOutlineNode;
FOutlineTreeNode :TfrxHTMLOutlineNode;
FOnProcessHyperLink: TOnProcessHyperLink;
FParentDetailURL: String;
function GetPageStyle: TfrxCSSStyle;
function GetAnchor(var Page: string; const Name: string): Boolean;
function GetHRef(const URL: string): string;
procedure PutImg(Obj: TfrxView; Pic: TGraphic; WriteSize: Boolean);
procedure EndHTML;
{ Handlers for specific kinds of TfrxView objects.
They return "True" if they succeed to export an object, or "False"
if they want to pass the object further along the handlers chain. }
function ExportTaggedView(Obj: TfrxView): Boolean;
function ExportAllPictures(Obj: TfrxView): Boolean;
function ExportMemo(Obj: TfrxView): Boolean;
function ExportPicture(Obj: TfrxView): Boolean;
function ExportShape(Obj: TfrxView): Boolean;
function ExportLine(Obj: TfrxView): Boolean;
function ExportGradient(Obj: TfrxView): Boolean;
{$IFNDEF FPC}
function ExportViaEMF(Obj: TfrxView): Boolean;
{$ENDIF}
function ExportAsPicture(Obj: TfrxView): Boolean;
protected
{ Creates a new HTML tag and returns it. The tag don't need to be
deleted or serialised to a stream: this is done automatically. }
function AddTag(const Name: string): TfrxHTMLItem;
{ Creates an empty
tag with assigned styles }
function CreateDiv(Obj: TfrxView; Widen: Integer = 0): TfrxHTMLItem;
function CreateFrameDiv(Obj: TfrxView): TfrxHTMLItem;
function CreateFillDiv(Obj: TfrxView): TfrxHTMLItem;
procedure FillGraduienProps(Style: TfrxCSSStyle;
BeginColor, EndColor: TColor; GradientStyle: TfrxGradientStyle);
function FilterHTML(const Text: string): string;
function EscapeText(const s: string): string;
function DoHyperLink(HL: TfrxHyperlink): string;
procedure DoExportAsPicture(Obj: TfrxView; Transparent: Boolean; IsVectorSource: Boolean; IsAlphaSource: Boolean;
TransparentColor: TColor = clNone);
function NavPageNumber(PageNumber: Integer): string;
procedure StartAnchors;
procedure StartNavigator;
procedure CreateCSS; override;
procedure StartHTML;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
class function GetDescription: String; override;
class function ExportDialogClass: TfrxBaseExportDialogClass; override;
{ function ShowModal: TModalResult; override; }
function Start: Boolean; override;
procedure StartPage(Page: TfrxReportPage; Index: Integer); override;
procedure FinishPage(Page: TfrxReportPage; Index: Integer); override;
procedure Finish; override;
{ Report pages are represented as separate
tags. All these tags
are associated with the same CSS style, so its possible to adjust
appearance of pages by modifying this style. Example:
PageStyle['border'] := '1mm solid orange';
PageStyle['margin'] := '5mm';
PageStyle.CSS3Style['box-shadow'] := '3mm 3mm 3mm gray';
PageStyle.cSS3Style['border-radius'] := '3mm'; }
property PageStyle: TfrxCSSStyle read GetPageStyle;
property OnProcessHyperLink: TOnProcessHyperLink read FOnProcessHyperLink write FOnProcessHyperLink;
property ParentDetailURL: String read FParentDetailURL write FParentDetailURL;
property Print: Boolean read FPrint write FPrint;
property UseTemplates: Boolean read FUseTemplates write FUseTemplates;
property OnGetMainTemplate: TfrxHTMLExportGetMainTemplate read FGetMainTemplate
write FGetMainTemplate;
property OnGetToolbarTemplate: TfrxHTMLExportGetToolbarTemplate read FGetToolbarTemplate
write FGetToolbarTemplate;
property OnGetNavTemplate: TfrxHTMLExportGetNavTemplate read FGetNavTemplate
write FGetNavTemplate;
published
property Title: string read FTitle write FTitle;
{ Allows using HTML5 features }
property HTML5: Boolean read FHTML5 write FHTML5;
{ Exports all report components as pictures }
property AllPictures: Boolean read FAllPictures write FAllPictures;
{ Creates anchors based on TfrxView.URL property. This option is useful only if
the URLs begin with the "#" sign. }
property ExportAnchors: Boolean read FExportAnchors write FExportAnchors;
{ If not equals zero, forces the export to save all report components
with this value of the Tag property export as pictures. The format of pictures
is defined by PictureFormat. }
property PictureTag: Integer read FPictureTag write FPictureTag;
end;
{ HTML export that uses modern HTML5 features }
{$IFDEF DELPHI16}
[ComponentPlatformsAttribute(pidWin32 or pidWin64)]
{$ENDIF}
TfrxHTML5DivExport = class(TfrxHTMLDivExport)
public
constructor Create(AOwner: TComponent); override;
class function GetDescription: String; override;
end;
{ HTML export that is compatible with old browsers, like IE6 }
{$IFDEF DELPHI16}
[ComponentPlatformsAttribute(pidWin32 or pidWin64)]
{$ENDIF}
TfrxHTML4DivExport = class(TfrxHTMLDivExport)
public
constructor Create(AOwner: TComponent); override;
class function GetDescription: String; override;
end;
implementation
uses
Contnrs,
frxUtils,
frxRes,
Math,
frxXML, // for access to TfrxPreviewPages.FindAnchor's results
frxPreviewPages,
{$IFNDEF FPC}frxEMFtoSVGExport, frxEMFAbstractExport, {$ENDIF}
frxExportHTMLDivDialog;
const
wbFrame = 1;
wbExport = 2;
{ Utility routines }
function FRLength(a: Extended; AllowNegative: Boolean = False): string;
var
ra: Integer;
begin
ra := Round(a);
Result := IfStr(not AllowNegative and (ra <= 0), '0', IntToStr(ra) + 'px');
end;
function GetLineStyle(s: TfrxFrameStyle): string;
begin
case s of
fsSolid:
Result := 'solid';
fsDash:
Result := 'dashed';
fsDot:
Result := 'dotted';
fsDashDot:
Result := 'dashed';
fsDashDotDot:
Result := 'dotted';
fsDouble:
Result := 'double';
fsAltDot:
Result := 'dotted';
fsSquare:
Result := 'dotted';
else
Result := '';
end;
end;
function GetFrameLineStyle(w: Extended; s: TfrxFrameStyle; c: TColor): string; overload;
begin
if Round(w) = 0 then
Result := ''
else
Result := FRLength(Max(w, 1.0)) + ' ' + GetLineStyle(s) + ' ' + GetColor(c)
end;
function GetFrameLineStyle(Line: TfrxFrameLine): string; overload;
begin
Result := GetFrameLineStyle(Line.Width, Line.Style, Line.Color)
end;
function GetHAlign(Align: TfrxHAlign): string;
begin
case Align of
haRight:
Result := 'right';
haCenter:
Result := 'center';
haBlock:
Result := 'justify';
else
Result := '';
end;
end;
function GetVAlign(Align: TfrxVAlign): string;
begin
case Align of
vaTop:
Result := 'top';
vaBottom:
Result := 'bottom';
vaCenter:
Result := 'middle';
else
Result := '';
end;
end;
function GetFont(Font: TFont): string;
begin
Result := IfStr(fsBold in Font.Style, 'bold ') + IfStr(fsItalic in Font.Style,
'italic ') + IntToStr(Font.Size) + 'pt ' + Font.Name
end;
function GetTextDecoration(s: TFontStyles): string;
begin
Result := IfStr(fsUnderline in s, 'underline ') + IfStr(fsStrikeOut in s,
'line-through')
end;
{ TfrxHTMLDivExport }
function TfrxHTMLDivExport.AddTag(const Name: string): TfrxHTMLItem;
begin
Result := TfrxHTMLItem.Create(Name);
FQueue.Push(Result);
end;
constructor TfrxHTMLDivExport.Create(AOwner: TComponent);
begin
inherited;
DefaultExt := GetStr('HTMLExtension');
FilterDesc := frxGet(9301);
{ LIFO }
AttachHandler(ExportAsPicture);
AttachHandler(ExportPicture);
{$IFNDEF FPC}
AttachHandler(ExportViaEMF);
{$ENDIF}
AttachHandler(ExportGradient);
AttachHandler(ExportLine);
AttachHandler(ExportShape); // Without Fill (except skRectangle)
AttachHandler(ExportMemo);
AttachHandler(ExportAllPictures);
AttachHandler(ExportTaggedView);
Server := False;
FUseTemplates := False;
FPrint := false;
FOnProcessHyperLink := nil;
end;
procedure TfrxHTMLDivExport.CreateCSS;
begin
inherited;
FCSS := TfrxCSSList.Create;
with FCSS do
begin
AddName('div, img, table')['position'] := 'absolute';
AddName('div, td')['overflow'] := 'hidden';
AddName('sub')['font-size'] := '0.67em';
with AddName('sup') do
begin
Style['font-size'] := '0.67em';
Style['vertical-align'] := 'top';
Style['position'] := 'relative';
Style['top'] := '-0.2em';
end;
AddName('svg')['vertical-align'] := 'top';
with AddName('tr, td, table, tbody') do
begin
Style['text-decoration'] := 'inherit';
Style['vertical-align'] := 'inherit';
end;
with AddName('table') do
begin
Style['width'] := '100%';
Style['height'] := '100%';
Style['border-spacing'] := '0';
if not HTML5 then
begin
Style['border'] := '0';
Style['padding'] := '0';
end;
end;
// AddName('img')['z-index'] := '1';
with AddName('.nav') do
begin
Style['font-family'] := 'Courier New, monospace';
Style['font-size'] := '16';
Style['font-weight'] := 'bold';
Style['margin'] := '1em';
end;
with AddName('.nav a') do
begin
Style['text-decoration'] := 'none';
Style['margin-right'] := '1em';
Style['color'] := 'black';
end;
AddName('.nav a:hover')['text-decoration'] := 'underline';
if FPageStyle <> nil then
FPageStyle.AssignTo(AddName('.page'));
if Outline then
AddName(outlineCSS);
end;
end;
function TfrxHTMLDivExport.CreateDiv(Obj: TfrxView; Widen: Integer = 0): TfrxHTMLItem;
var
rBounds: TfrxRect;
begin
Result := AddTag('div');
Result.Gaude(Obj);
Result.DoPositive;
if Widen = wbFrame then
Result.WidenBy(Obj.Frame.Width)
else if Widen = wbExport then
begin
Result.AllowNegativeLeft := True;
rBounds := Obj.GetExportBounds;
Result.Left := rBounds.Left;
Result.Width := rBounds.Right - rBounds.Left;
Result.Top := rBounds.Top;
Result.Height := rBounds.Bottom - rBounds.Top;
if Obj is TfrxCustomMemoView then
with TfrxCustomMemoView(Obj) do
if not Clipped and (Page <> nil) and (Page is TfrxReportPage) then
begin
Result.Left := Min(Result.Left, 0);
Result.Top := Min(Result.Top, 0);
Result.Width := Max(Result.Width, Page.Width);
Result.Height := Max(Result.Height, Page.Height);
end;
end;
if Obj.ShowHint then
Result.Prop['title'] := Obj.Hint;
with Result do
if Obj.URL <> '' then
with Add('a') do
begin
Style['width'] := '100%';
Style['height'] := '100%';
Style['position'] := 'absolute';
Prop['href'] := GetHRef(Obj.URL);
end;
end;
function TfrxHTMLDivExport.CreateFillDiv(Obj: TfrxView): TfrxHTMLItem;
function SVGPatternFill(XLine, YLine, Turn: Boolean; Color: TColor;
Width, Height: Integer; LineWidth: Extended): string;
var
PatternName: string;
begin
PatternName := SVGUniqueID;
with TTextFragment.Create(Formatted) do
begin
Add('');
Result := Text;
Free;
end;
end;
var
Width, Height: Integer;
function PF(LineColor: TColor; XLine, YLine, Turn: Boolean): string;
begin
PF := SVGPatternFill(XLine, YLine, Turn, LineColor, Width, Height, 1.4);
end;
var
x, y, w2, h2: Integer;
begin
Result := nil;
if (Obj.FillType = ftBrush) and
(TfrxBrushFill(Obj.Fill).Style in [bsSolid, bsClear]) and
(TfrxBrushFill(Obj.Fill).BackColor = clNone) { transparent brush } or
(Obj.FillType = ftGlass) and (TfrxGlassFill(Obj.Fill).Color = clNone)
{ transparent glass } then
Exit;
Result := AddTag('div');
Result.Gaude(Obj);
Width := Round(Result.Width);
Height := Round(Result.Height);
case Obj.FillType of
ftBrush:
with TfrxBrushFill(Obj.Fill) do
begin
with TfrxCSSStyle.Create do
begin
Style['background'] := GetColor(BackColor);
Result.AddCSSClass(LockStyle(This));
end;
case Style of
bsHorizontal:
Result.Value := PF(ForeColor, True, False, False);
bsVertical:
Result.Value := PF(ForeColor, False, True, False);
bsFDiagonal:
Result.Value := PF(ForeColor, True, False, True);
bsBDiagonal:
Result.Value := PF(ForeColor, False, True, True);
bsCross:
Result.Value := PF(ForeColor, True, True, False);
bsDiagCross:
Result.Value := PF(ForeColor, True, True, True);
else // bsSolid, bsClear:
end;
end;
ftGradient:
with TfrxGradientFill(Obj.Fill) do
with TfrxCSSStyle.Create do
begin
FillGraduienProps(This, StartColor, EndColor,
TfrxGradientStyle(GradientStyle));
Result.AddCSSClass(LockStyle(This));
end;
ftGlass:
with TfrxGlassFill(Obj.Fill) do
begin
with TfrxCSSStyle.Create do
begin
Style['background'] := frxExportHelpers.GetColor(Color);
Result.AddCSSClass(LockStyle(This));
end;
x := 0;
y := 0;
w2 := Width;
h2 := Height;
CalcGlassRect(Orientation, 0, 0, x, y, w2, h2);
with TTextFragment.Create(Formatted) do
begin
Add('');
Result.Value := Text;
Free;
end;
end;
end;
end;
function TfrxHTMLDivExport.CreateFrameDiv(Obj: TfrxView): TfrxHTMLItem;
procedure FillFrameProps(Style: TfrxCSSStyle; Frame: TfrxFrame);
var
L, T, r, B: string;
begin
L := IfStr(ftLeft in Frame.Typ, GetFrameLineStyle(Frame.LeftLine));
T := IfStr(ftTop in Frame.Typ, GetFrameLineStyle(Frame.TopLine));
r := IfStr(ftRight in Frame.Typ, GetFrameLineStyle(Frame.RightLine));
B := IfStr(ftBottom in Frame.Typ, GetFrameLineStyle(Frame.BottomLine));
if (L = T) and (L = r) and (L = B) then
Style['border'] := L
else
begin
Style['border-left'] := L;
Style['border-top'] := T;
Style['border-right'] := r;
Style['border-bottom'] := B;
end;
if Frame.DropShadow and HTML5 then
with Style do
PrefixStyle['box-shadow'] := FRLength(Frame.ShadowWidth) + ' ' +
FRLength(Frame.ShadowWidth) + ' ' + FRLength(Frame.ShadowWidth) + ' '
+ GetColor(Frame.ShadowColor);
end;
begin
Result := nil;
if (Obj.Frame.Typ = []) and not(Obj.Frame.DropShadow and HTML5) then
Exit;
Result := AddTag('div');
Result.GaudeFrame(Obj);
with TfrxCSSStyle.Create do
begin
FillFrameProps(This, Obj.Frame);
Result.AddCSSClass(LockStyle(This));
end;
end;
destructor TfrxHTMLDivExport.Destroy;
begin
FPageStyle.Free; // it's created by the getter of PageStyle
inherited
end;
procedure TfrxHTMLDivExport.DoExportAsPicture(Obj: TfrxView; Transparent: Boolean; IsVectorSource: Boolean; IsAlphaSource: Boolean; TransparentColor: TColor = clNone);
var
Pic: TfrxPicture;
PF: TfrxPictureFormat;
Correction: Integer;
begin
if Transparent then
PF := pfPNG
else
PF := PictureFormat;
Correction := IfInt(Obj is TfrxShapeView, 2);
{ Some objects can have negative dimensions }
Pic := TfrxPicture.Create(PF, Abs(Round(Obj.AbsLeft + Obj.Width) -
Round(Obj.AbsLeft)) + Correction,
Abs(Round(Obj.AbsTop + Obj.Height) - Round(Obj.AbsTop)) + Correction, Transparent, IsVectorSource, IsAlphaSource);
if Transparent and (TransparentColor <> clNone) then
begin
Pic.SetTransparentColor(TransparentColor);
Pic.FillColor(TransparentColor);
end
else
Pic.FillColor(Obj.Color);
Obj.DrawClipped(Pic.Canvas, 1, 1, -Obj.AbsLeft, -Obj.AbsTop);
PutImg(Obj, Pic.Release, False);
Pic.Free;
end;
function TfrxHTMLDivExport.DoHyperLink(HL: TfrxHyperlink): string;
function FileByAnchor: string;
begin
Result := IntToStr((Report.PreviewPages as TfrxPreviewPages)
.GetAnchorPage(HL.Value)) + DefaultExt;
end;
var
OHV: string;
begin
Result := '';
if (HL <> nil) then
begin
if (@FOnProcessHyperLink <> nil) then
begin
OHV := FOnProcessHyperLink(HL, FParentDetailURL);
if (OHV <> '') then
Result := Format('', [OHV]);
end;
if (OHV = '') then
begin
OHV := TfrxHTMLItem.EscapeAttribute(HL.Value);
if OHV <> '' then
case HL.Kind of
hkURL:
Result := Format('', [OHV]);
hkAnchor:
if MultiPage then
Result := Format('', [FileByAnchor, OHV])
else
Result := Format('', [OHV]);
hkPageNumber:
if MultiPage then
Result := Format('', [OHV + DefaultExt])
else
Result := Format('', [OHV]);
else { hkDetailReport:, hkDetailPage:, hkCustom: }
end;
end;
end;
end;
procedure TfrxHTMLDivExport.EndHTML;
begin
{ ');
end;
if not Server then
begin
Puts('