{********************************************}
{ HTML5 Canvas Export                        }
{ Copyright (c) 2012-2020 by Steema Software }
{        All Rights Reserved                 }
{********************************************}
unit TeeHTML5Canvas;
{$I TeeDefs.inc}

interface

uses
  {$IFNDEF LINUX}
  {$IFNDEF FMX}
  Windows,
  {$ENDIF}
  {$ENDIF}

  SysUtils, Classes,
  {$IFDEF D9}
  Types,
  {$ENDIF}

  {$IFDEF FPC}
  Types,
  {$ENDIF}

  {$IFDEF D16}
  System.UITypes,
  {$ENDIF}

  {$IFDEF FMX}
  FMX.Types, FMX.Platform,
  {$IFDEF D19}
  FMX.Graphics,
  {$ENDIF}

  {$ELSE}
  Graphics, Clipbrd,
  {$ENDIF}

  {$IFDEF FMX}
  FMXTee.Procs, FMXTee.Canvas, FMXTee.Editor.Export
  {$ELSE}
  TeeProcs, TeeExport, TeCanvas
  {$ENDIF};

type
  THTML5Canvas=class(TTeeNeutralCanvas)
  private
    FX           : TCoordinate;
    FY           : TCoordinate;
    FStrings     : TStrings;

    IGradientCount : Integer;
    IOwnsStrings   : Boolean;

    OldDecimal   : Char;

    procedure Add(Const S:String); {$IFDEF D9}inline;{$ENDIF}
    procedure AddBrush;
    procedure AddDashedLine;
    procedure AddGradient(const AGradient:TTeeGradient; const Rect:TRect);
    procedure AddPen;
    function GetStrings:TStrings;
    function HTML5Color(const AColor:TColor):String; overload;
    function HTML5Color(const AColor:TColor; const ATransp:Single):String; overload;
    procedure InitStrings;

  protected
    procedure SmoothShadow(const Shadow:TTeeShadow; const Rect:TRect;
                           Ellipse:Boolean; RoundSize:Integer;
                           const P:Array of TPoint; DonutPercent:Integer=0;
                           const StartAngle:Double=0;
                           const EndAngle:Double=360); override;
    {$IFNDEF FMX}
    function CanvasStyle:TCanvasStyle; override;
    {$ENDIF}
  public
    Constructor CreateText(const AStrings:TStrings);
    Destructor Destroy; override;

    Function InitWindow( const DestCanvas:TCanvas;
                         const A3DOptions:TView3DOptions;
                         ABackColor:TColor;
                         Is3D:Boolean;
                         Const UserRect:TRect):TRect; override;

    procedure AssignVisiblePenColor(const APen:TTeePen; AColor:TColor); override;

    procedure Arc(const Left, Top, Right, Bottom, StartX, StartY, EndX, EndY: Integer); override;
    procedure Draw(X, Y: Integer; const Graphic: TGraphic); override;
    procedure FillRect(const Rect: TRect); override;
    procedure Ellipse(const X1, Y1, X2, Y2: TCoordinate); override;
    procedure LineTo(const X,Y:TCoordinate); override;
    procedure MoveTo(const X,Y:TCoordinate); override;
    procedure Pie(const R:TRect; const StartAngle,EndAngle:Double); override;
    procedure Pie(X1, Y1, X2, Y2, X3, Y3, X4, Y4: Integer); override;
    procedure Rectangle(const X0,Y0,X1,Y1:TCoordinate); override;
    procedure RoundRect(X1, Y1, X2, Y2, X3, Y3: Integer); override;
    procedure StretchDraw(const Rect: TRect; const Graphic: TGraphic); override;
    Procedure TextOut({$IFDEF FMX}const{$ENDIF} X,Y:TCoordinate; const Text:String); override;

    procedure ClipEllipse(Const Rect:TRect; Inverted:Boolean=False); override;
    procedure ClipRectangle(Const Rect:TRect); override;
    Procedure ClipRectangle(Const Rect:TRect; RoundX,RoundY:Integer); override;
    Procedure ClipPolygon(const Points:Array of TPoint; NumPoints:Integer;
                          DiffRegion:Boolean=False); override;

    procedure UnClipRectangle; override;

    Procedure GradientFill( Const Rect:TRect;
                            StartColor,EndColor:TColor;
                            Direction:TGradientDirection;
                            Balance:Integer=50;
                            RadialX:Integer=0;
                            RadialY:Integer=0); override;

    procedure RotateLabel(x,y:Integer; Const St:String; RotDegree:Double; AllowHTML:Boolean=False); override;
    procedure RotateLabel3D(x,y,z:Integer;
                            const St:String; RotDegree:Double;
                            AllowHTML:Boolean=False); override;
    Procedure Polygon({$IFDEF D6}const{$ENDIF} Points:Array of TPoint); override;
    Procedure Polyline({$IFDEF D6}const{$ENDIF} Points:Array of TPoint); override;

    { 3d }
    Procedure ShowImage(const DestCanvas,DefaultCanvas:TCanvas; Const UserRect:TRect); override;
    Procedure TextOut3D(X,Y,Z:Integer; const Text:String; AllowHTML:Boolean=False); override;

    // Properties
    property Strings:TStrings read GetStrings;
  end;

  THTML5ExportFormat=class(TTeeExportFormat)
  protected
    Procedure DoCopyToClipboard; override;
  public
    Destructor Destroy; override;

    function Description:String; override;
    function FileExtension:String; override;
    class function FileFilter:String; override;
    Function HTML5:TStringList;
    Procedure SaveToStream(Stream:TStream); override;
  end;

procedure TeeSaveToHTML5File( const APanel:TCustomTeePanel; FileName: String;
                              AWidth:Integer=0; AHeight: Integer=0);

implementation

uses {$IFDEF FMX}
     FMXTee.Constants,
     {$ELSE}
     TeeConst,
     {$ENDIF}
     Math;

{$IFDEF D15}
{$DEFINE TEEFORMAT}
{$ENDIF}

{$IFDEF LCL}
{$DEFINE TEEFORMAT}
{$ENDIF}

{ THTML5Canvas }

{$IFNDEF FMX}
function THTML5Canvas.CanvasStyle:TCanvasStyle;
begin
  result:=csHTML5;
end;
{$ENDIF}

function THTML5Canvas.GetStrings:TStrings;
begin
  if not Assigned(FStrings) then
  begin
    FStrings:=TStringList.Create;
    IOwnsStrings:=True;

    InitStrings;
  end;

  result:=FStrings;
end;

procedure THTML5Canvas.Add(const S: String);
begin
  GetStrings.Add(S);
end;

function ToFloat(const Value:Double):String;
begin
  result:=FormatFloat('0.###',Value);
end;

function HTMLPoint(const X,Y:Double):String; overload;
begin
  result:=ToFloat(X)+','+ToFloat(Y);
end;

{$IFNDEF FMX}
function HTMLPoint(const P:TPointFloat):String; overload;
begin
  result:=ToFloat(P.X)+','+ToFloat(P.Y);
end;
{$ENDIF}

function HTMLPoint(X,Y:Integer):String; overload;
begin
  result:=TeeStr(X)+','+TeeStr(Y);
end;

function HTMLPoint(const P:TPoint):String; overload;
begin
  {$IFDEF FMX}
  result:=HTMLPoint(P.X,P.Y);
  {$ELSE}
  result:=TeeStr(P.X)+','+TeeStr(P.Y);
  {$ENDIF}
end;

procedure THTML5Canvas.Arc(const Left, Top, Right, Bottom, StartX, StartY,
  EndX, EndY: Integer);
begin
  if Pen.Style<>psClear then
  begin
    Add(' ctx.beginPath();');
    Add(' ctx.moveTo('+HTMLPoint(FX,FY)+');');
    Add(' ctx.arcTo('+HTMLPoint(Left,Top)+','+HTMLPoint(Right,Bottom)+','+
          ToFloat(0.5*(Right-Left))+');');

    Add(' ctx.stroke();');
  end;
end;

procedure THTML5Canvas.AssignVisiblePenColor(const APen: TTeePen; AColor: TColor);
begin
  inherited;
  AddPen;
end;

procedure THTML5Canvas.ClipEllipse(const Rect: TRect; Inverted: Boolean);
begin
end;

procedure THTML5Canvas.ClipPolygon(const Points: array of TPoint;
  NumPoints: Integer; DiffRegion: Boolean);
begin
end;

procedure THTML5Canvas.ClipRectangle(const Rect: TRect; RoundX,
  RoundY: Integer);
begin
end;

procedure THTML5Canvas.ClipRectangle(const Rect: TRect);
begin
  Add(' ctx.save();');
  Add(' ctx.beginPath();');
  Add(' ctx.moveTo('+HTMLPoint(Rect.Left,Rect.Top)+');');
  Add(' ctx.lineTo('+HTMLPoint(Rect.Right,Rect.Top)+');');
  Add(' ctx.lineTo('+HTMLPoint(Rect.Right,Rect.Bottom)+');');
  Add(' ctx.lineTo('+HTMLPoint(Rect.Left,Rect.Bottom)+');');
  Add(' ctx.lineTo('+HTMLPoint(Rect.Left,Rect.Top)+');');

  Add(' ctx.clip();');
end;

procedure THTML5Canvas.AddDashedLine;
begin
  Add('// Copyright David Owens: http://davidowens.wordpress.com/2010/09/07/html-5-canvas-and-dashed-lines/');
  Add('CanvasRenderingContext2D.prototype.dashedLineTo = function (fromX, fromY, toX, toY, pattern) {');
  Add('  // Our growth rate for our line can be one of the following:');
  Add('  //   (+,+), (+,-), (-,+), (-,-)');
  Add('  // Because of this, our algorithm needs to understand if the x-coord and');
  Add('  // y-coord should be getting smaller or larger and properly cap the values');
  Add('  // based on (x,y).');
  Add('  var lt = function (a, b) { return a <= b; };');
  Add('  var gt = function (a, b) { return a >= b; };');
  Add('  var capmin = function (a, b) { return Math.min(a, b); };');
  Add('  var capmax = function (a, b) { return Math.max(a, b); };');
  Add('');
  Add('  var checkX = { thereYet: gt, cap: capmin };');
  Add('  var checkY = { thereYet: gt, cap: capmin };');
  Add('');
  Add('  if (fromY - toY > 0) {');
  Add('    checkY.thereYet = lt;');
  Add('    checkY.cap = capmax;');
  Add('  }');
  Add('  if (fromX - toX > 0) {');
  Add('    checkX.thereYet = lt;');
  Add('    checkX.cap = capmax;');
  Add('  }');
  Add('');
  Add('');
  Add('  var offsetX = fromX;');
  Add('  var offsetY = fromY;');
  Add('  var idx = 0, dash = true;');
  Add('  while (!(checkX.thereYet(offsetX, toX) && checkY.thereYet(offsetY, toY))) {');
  Add('    var ang = Math.atan2(toY - fromY, toX - fromX);');
  Add('    var len = pattern[idx];');
  Add('');
  Add('    offsetX = checkX.cap(toX, offsetX + (Math.cos(ang) * len));');
  Add('    offsetY = checkY.cap(toY, offsetY + (Math.sin(ang) * len));');
  Add('');
  Add('    if (dash) this.lineTo(offsetX, offsetY);');
  Add('    else this.moveTo(offsetX, offsetY);');
  Add('');
  Add('    idx = (idx + 1) % pattern.length;');
  Add('    dash = !dash;');
  Add('  }');
  Add('};');
end;

Constructor THTML5Canvas.CreateText(const AStrings: TStrings);
begin
  Create;

  FStrings:=AStrings;
  UseBuffer:=False;
  FSupportsAlpha:=True;

  InitStrings;
end;

Destructor THTML5Canvas.Destroy;
begin
  if IOwnsStrings then
     FreeAndNil(FStrings);

  inherited;
end;

procedure THTML5Canvas.InitStrings;
begin
  Add('<html>');
  Add(' <head>');
  Add('  <script type="application/javascript">');

  AddDashedLine;

  Add('function draw() {');
  Add(' var canvas = document.getElementById("canvas");');
  Add(' var ctx = canvas.getContext("2d");');
end;

procedure THTML5Canvas.Draw(X, Y: Integer; const Graphic: TGraphic);
begin
end;

procedure THTML5Canvas.Ellipse(const X1, Y1, X2, Y2: TCoordinate);
begin
  if X2<>X1 then
  begin
    if Gradient.Visible then
       AddGradient(Gradient,
                     TeeRect(-(X2-X1) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF},
                             -(Y2-Y1) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF},
                             (X2-X1) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF},
                             (Y2-Y1) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF}))
    else
       AddBrush;

    Add(' ctx.save();');
    Add(' ctx.translate('+HTMLPoint((X2+X1) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF},
                                    (Y2+Y1) {$IFDEF FMX}*0.5{$ELSE}div 2{$ENDIF})+');');

    if (Y2-Y1)<>(X2-X1) then
       Add(' ctx.scale(1,'+ToFloat((Y2-Y1)/(X2-X1))+');');

    Add(' ctx.beginPath();');
    Add(' ctx.arc(0,0,'+ToFloat(0.5*(X2-X1))+','+'0,Math.PI*2,true);');

    if Pen.Style<>psClear then
       Add(' ctx.stroke();');

    if Gradient.Visible or (Brush.Style<>bsClear) then
       Add(' ctx.fill();');

    Add(' ctx.restore();');
  end;
end;

function THTML5Canvas.HTML5Color(const AColor:TColor; const ATransp:Single):String;
begin
  if ATransp=1 then
     result:='rgb('+TeeStr(TRGBA(AColor).Red)+','+
                    TeeStr(TRGBA(AColor).Green)+','+
                    TeeStr(TRGBA(AColor).Blue)+')'
  else
     result:='rgba('+TeeStr(TRGBA(AColor).Red)+','+
                     TeeStr(TRGBA(AColor).Green)+','+
                     TeeStr(TRGBA(AColor).Blue)+','+
                     ToFloat(ATransp)+')';
end;

function THTML5Canvas.HTML5Color(const AColor:TColor):String;
begin
  result:=HTML5Color(AColor,ITransp);
end;

procedure THTML5Canvas.AddBrush;
begin
  if Brush.Style<>bsClear then
     Add(' ctx.fillStyle = "'+HTML5Color(Brush.Color)+'";');
end;

function ToStr(const Value:TCoordinate):String;
begin
  {$IFDEF FMX}
  result:=IntToStr(Round(Value));
  {$ELSE}
  result:=TeeStr(Value);
  {$ENDIF}
end;

procedure THTML5Canvas.AddPen;
begin
  if Pen.Style<>psClear then
  begin
    Add(' ctx.strokeStyle = "'+HTML5Color(Pen.Color)+'";');
    Add(' ctx.lineWidth = '+ToStr(Pen.Width)+';');
    // lineCap
    // lineJoin
  end;
end;

function HTMLRect(const R:TRect):String; overload;
begin
  with R do
    result:=ToStr(Left)+', '+ToStr(Top)+', '+ToStr(Right-Left)+', '+
            ToStr(Bottom-Top);
end;

function HTMLRect(Left,Top,Right,Bottom:Integer):String; overload;
begin
  result:=ToStr(Left)+', '+ToStr(Top)+', '+ToStr(Right-Left)+', '+
          ToStr(Bottom-Top);
end;

function HTMLBounds(const Left,Top,Right,Bottom:TCoordinate):String; overload;
begin
  result:=ToStr(Left)+', '+ToStr(Top)+', '+ToStr(Right)+', '+ToStr(Bottom);
end;

procedure THTML5Canvas.FillRect(const Rect: TRect);
begin
  if Gradient.Visible then
     AddGradient(Gradient,Rect)
  else
  if Brush.Style<>bsClear then
     AddBrush;

  if Gradient.Visible or (Brush.Style<>bsClear) then
     Add(' ctx.fillRect('+HTMLRect(Rect)+');');
end;


procedure THTML5Canvas.AddGradient(const AGradient:TTeeGradient; const Rect:TRect);
var tmp : String;

  procedure AddLinearGradient(const x1,y1,x2,y2:TCoordinate; Color1,Color2:TColor);
  begin
    Inc(IGradientCount);
    tmp:='gradient'+TeeStr(IGradientCount);
    Add(' var '+tmp+' = ctx.createLinearGradient('+HTMLBounds(x1,y1,x2,y2)+');');

    Add(' '+tmp+'.addColorStop(0,"'+HTML5Color(Color1)+'");');
    Add(' '+tmp+'.addColorStop(1,"'+HTML5Color(Color2)+'");');
  end;

begin
  with AGradient do
  if Direction<>gdRadial then
  begin
    // Linear

    case Direction of
      gdTopBottom: AddLinearGradient(0,Rect.Top,0,Rect.Bottom,EndColor,StartColor);
      gdBottomTop: AddLinearGradient(0,Rect.Top,0,Rect.Bottom,StartColor,EndColor);
      gdRightLeft: AddLinearGradient(Rect.Left,0,Rect.Right,0,StartColor,EndColor);
      gdLeftRight: AddLinearGradient(Rect.Left,0,Rect.Right,0,EndColor,StartColor);
     gdDiagonalUp: AddLinearGradient(Rect.Left,Rect.Top,Rect.Right,Rect.Bottom,EndColor,StartColor);
   gdDiagonalDown: AddLinearGradient(Rect.Left,Rect.Top,Rect.Right,Rect.Bottom,StartColor,EndColor);
    else
       AddLinearGradient(0,0,0,Rect.Bottom-Rect.Top,StartColor,EndColor);
    end;
  end;

  Add(' ctx.fillStyle='+tmp+';');
end;

procedure THTML5Canvas.GradientFill(const Rect: TRect; StartColor,
  EndColor: TColor; Direction: TGradientDirection; Balance, RadialX,
  RadialY: Integer);
var tmp : TTeeGradient;
begin
  tmp:=TTeeGradient.Create(nil);
  try
    tmp.StartColor:=StartColor;
    tmp.EndColor:=EndColor;
    tmp.Direction:=Direction;
    tmp.Balance:=Balance;
    tmp.RadialX:=RadialX;
    tmp.RadialY:=RadialY;

    AddGradient(tmp,Rect);
  finally
    tmp.Free;
  end;

  Add(' ctx.fillRect('+HTMLRect(Rect)+');');
end;

function THTML5Canvas.InitWindow(const DestCanvas: TCanvas;
  const A3DOptions: TView3DOptions; ABackColor: TColor; Is3D: Boolean;
  const UserRect: TRect): TRect;
begin
  result:=inherited InitWindow(DestCanvas,A3DOptions,ABackColor,Is3D,UserRect);

  OldDecimal:={$IFDEF TEEFORMAT}FormatSettings.{$ENDIF}DecimalSeparator;
  {$IFDEF TEEFORMAT}FormatSettings.{$ENDIF}DecimalSeparator:='.';
end;

procedure THTML5Canvas.LineTo(const X, Y: TCoordinate);

  procedure AddDash(const Pattern:String);
  begin
    Add(' ctx.dashedLineTo('+HTMLPoint(FX,FY)+','+HTMLPoint(X,Y)+',['+Pattern+']);');
  end;

begin
  Add(' ctx.beginPath();');
  Add(' ctx.moveTo('+HTMLPoint(FX,FY)+');');

  if Pen.SmallDots then
     AddDash('1,1')
  else
    case Pen.Style of
     psDash : AddDash('4,1');
     psDot  : AddDash('2,1');
     psDashDot: AddDash('4,1,2,1');
     psDashDotDot:  AddDash('4,1,2,1,2,1');
    else
      Add(' ctx.lineTo('+HTMLPoint(X,Y)+');')
    end;

  Add(' ctx.stroke();');
end;

procedure THTML5Canvas.MoveTo(const X, Y: TCoordinate);
begin
  FX:=X;
  FY:=Y;
end;

procedure THTML5Canvas.Pie(const R:TRect; const StartAngle,EndAngle:Double);
var tmp : {$IFDEF FMX}TPoint{$ELSE}TPointFloat{$ENDIF};
    tmpSin,
    tmpCos : Extended;
    tmpR   : Double;
begin
  if R.Left<>R.Right then
  begin
    AddBrush;

    Add(' ctx.beginPath();');

    with R do
    begin
      tmp.X:=(0.5*(Left+Right));
      tmp.Y:=(0.5*(Top+Bottom));
      tmpR:=(Right-Left)*0.5;
    end;

    Add(' ctx.save();');
    Add(' ctx.translate('+HTMLPoint(tmp)+');');

    if (R.Bottom-R.Top)<>(R.Right-R.Left) then
       Add(' ctx.scale(1,'+ToFloat((R.Bottom-R.Top)/(R.Right-R.Left))+');');

    Add(' ctx.moveTo(0,0);');

    SinCos(-StartAngle*TeePiStep,tmpSin,tmpCos);
    Add(' ctx.lineTo('+HTMLPoint(tmpR*tmpCos,tmpR*tmpSin)+');');

    Add(' ctx.arc(0,0,'+ToFloat(tmpR)+','+
          ToFloat(-StartAngle*TeePiStep)+','+ToFloat(-EndAngle*TeePiStep)+
          ',true);');

    Add(' ctx.closePath();');

    if Pen.Style<>psClear then
       Add(' ctx.stroke();');

    if Brush.Style<>bsClear then
       Add(' ctx.fill();');

    Add(' ctx.restore();');
  end;
end;

procedure THTML5Canvas.Pie(X1, Y1, X2, Y2, X3, Y3, X4, Y4: Integer);
var tmp0,
    tmp1 : Single;
begin
  CalcPieAngles(X1,Y1,X2,Y2,X3,Y3,X4,Y4,tmp0,tmp1);
  Pie(TeeRect(X1,Y1,X2,Y2),tmp0,tmp1);
end;

procedure THTML5Canvas.Polygon({$IFDEF D6}const{$ENDIF} Points: array of TPoint);
var t : Integer;
begin
  if Length(Points)>0 then
  begin
    AddBrush;

    Add(' ctx.beginPath();');
    Add(' ctx.moveTo('+HTMLPoint(Points[0])+');');

    for t:=1 to Length(Points)-1 do
        Add(' ctx.lineTo('+HTMLPoint(Points[t])+');');

    Add(' ctx.lineTo('+HTMLPoint(Points[0])+');');

    if Brush.Style<>bsClear then
       Add(' ctx.fill();');

    if Pen.Style<>psClear then
       Add(' ctx.stroke();');
  end;
end;

Procedure THTML5Canvas.Polyline({$IFDEF D6}const{$ENDIF} Points:Array of TPoint);
var t : Integer;
begin
  if Length(Points)>0 then
  begin
    AddPen;

    Add(' ctx.beginPath();');
    Add(' ctx.moveTo('+HTMLPoint(Points[0])+');');

    for t:=1 to Length(Points)-1 do
        Add(' ctx.lineTo('+HTMLPoint(Points[t])+');');

    Add(' ctx.stroke();');
  end;
end;

procedure THTML5Canvas.Rectangle(const X0, Y0, X1, Y1:  TCoordinate);
var tmp : TRect;
begin
  tmp:=TeeRect(X0,Y0,X1,Y1);

  if Pen.Style<>psClear then
     Add(' ctx.strokeRect('+HTMLRect(tmp)+');');

  FillRect(tmp);
end;

procedure THTML5Canvas.RotateLabel(x, y: Integer; const St: String;
                                   RotDegree: Double; AllowHTML:Boolean=False);
var tmp : TSize;
begin
  if RotDegree=0 then
     TextOut(x,y,St)
  else
  begin
    Add(' ctx.save();');

    {$IFDEF FMX}
    tmp.cx:=TextWidth(St);
    tmp.cy:=TextHeight(St);
    {$ELSE}
    {$IFDEF D6}
    tmp:=TextExtent(St);
    {$ELSE}
    tmp.cx:=TextWidth(St);
    tmp.cy:=TextHeight(St);
    {$ENDIF}
    {$ENDIF}

    Add(' ctx.translate('+HTMLPoint(x+(tmp.cx div 2),y+(tmp.cy div 2))+');');

    Add(' ctx.rotate('+ToFloat((360-RotDegree)*TeePiStep)+');');

    TextOut(-(tmp.cx div 2),-(tmp.cy div 2),St);
    Add(' ctx.restore();');
  end;
end;

procedure THTML5Canvas.RotateLabel3D(x, y, z: Integer; const St: String;
                                     RotDegree: Double; AllowHTML:Boolean=False);
begin
  Calc3DPos(X,Y,Z);
  RotateLabel(X,Y,St,RotDegree,AllowHTML);
end;

procedure THTML5Canvas.RoundRect(X1, Y1, X2, Y2, X3, Y3: Integer);
begin
  AddBrush;

  Add(' ctx.beginPath();');
  Add(' ctx.moveTo('+HTMLPoint(x1,y1+y3)+');');
  Add(' ctx.lineTo('+HTMLPoint(x1,y2-y3)+');');
  Add(' ctx.quadraticCurveTo('+TeeStr(x1)+','+TeeStr(y2)+','+TeeStr(x1+x3)+','+TeeStr(y2)+');');
  Add(' ctx.lineTo('+HTMLPoint(x2-x3,y2)+');');
  Add(' ctx.quadraticCurveTo('+TeeStr(x2)+','+TeeStr(y2)+','+TeeStr(x2)+','+TeeStr(y2-y3)+');');
  Add(' ctx.lineTo('+HTMLPoint(x2,y1+y3)+');');
  Add(' ctx.quadraticCurveTo('+TeeStr(x2)+','+TeeStr(y1)+','+TeeStr(x2-x3)+','+TeeStr(y1)+');');
  Add(' ctx.lineTo('+HTMLPoint(x1+x3,y1)+');');
  Add(' ctx.quadraticCurveTo('+TeeStr(x1)+','+TeeStr(y1)+','+TeeStr(x1)+','+TeeStr(y1+y3)+');');

  if Pen.Style<>psClear then
     Add(' ctx.stroke();');

  if Brush.Style<>bsClear then
     Add(' ctx.fill();');
end;

procedure THTML5Canvas.ShowImage(const DestCanvas, DefaultCanvas: TCanvas;
  const UserRect: TRect);
begin
  Add('}');
  Add('  </script>');
  Add(' </head>');
  Add(' <body onload="draw()">');
  Add('   <canvas id="canvas" width="'+ToStr(UserRect.Right-UserRect.Left)+
                          '" height="'+ToStr(UserRect.Bottom-UserRect.Top)+
      '">This browser does not support HTML5 Canvas</canvas>');
  Add(' </body>');
  Add('</html>');

  {$IFDEF TEEFORMAT}FormatSettings.{$ENDIF}DecimalSeparator:=OldDecimal;
end;

procedure THTML5Canvas.SmoothShadow(const Shadow:TTeeShadow; const Rect:TRect;
                            Ellipse:Boolean; RoundSize:Integer;
                            const P:Array of TPoint; DonutPercent:Integer=0;
                            const StartAngle:Double=0;
                            const EndAngle:Double=360);
begin
  Add(' ctx.shadowColor="'+HTML5Color(Shadow.Color,Shadow.Transparency)+'";');
  Add(' ctx.shadowBlur='+TeeStr(4+Shadow.SmoothBlur)+';');

  Brush.Style:=bsSolid;
  Brush.Color:=Shadow.Color;
  Pen.Style:=psClear;

  if Ellipse then
     //if EndAngle-StartAngle=360 then
        Self.Ellipse(Rect)
     //else
     //   Self.Donut();
  else
  if Length(P)>0 then
     Polygon(P)
  else
  if RoundSize<>0 then
     RoundRect(Rect,RoundSize,RoundSize)
  else
     FillRect(Rect);

  Add(' ctx.shadowBlur=0;');
end;

procedure THTML5Canvas.StretchDraw(const Rect: TRect; const Graphic: TGraphic);
begin
  // img.src=""
  // ctx.drawImage
end;

procedure THTML5Canvas.TextOut({$IFDEF FMX}const{$ENDIF} X, Y: TCoordinate;
                               const Text: String);

  function FontStyle:String;
  begin
    result:='';

    if {$IFDEF FMX}TFontStyle.{$ENDIF}fsBold in Font.Style then
       result:=result+'bold ';

    if {$IFDEF FMX}TFontStyle.{$ENDIF}fsItalic in Font.Style then
       result:=result+'italic ';
  end;

begin
  Add(' ctx.fillStyle = "'+HTML5Color({$IFDEF FMX}Brush{$ELSE}Font{$ENDIF}.Color)+'";');

  case TextAlign of
    TA_LEFT: Add(' ctx.textAlign = "left";');
  TA_CENTER: Add(' ctx.textAlign = "center";');
  else
    Add(' ctx.textAlign = "right";');
  end;

  Add(' ctx.textBaseline = "top";');

  Add(' ctx.font = "'+FontStyle+' '+
      ToStr({$IFDEF FMX}Font.Size{$ELSE}-Font.Height{$ENDIF})+'px '+
      Font.{$IFDEF FMX}Family{$ELSE}Name{$ENDIF}+'";');

  // ITeeFont.OutLine.Visible -->  strokeText

  Add(' ctx.fillText("'+Text+'",'+HTMLPoint(X,Y+2)+');');
end;

procedure THTML5Canvas.TextOut3D(X, Y, Z: Integer; const Text: String; AllowHTML:Boolean=False);
begin
  Calc3DPos(X,Y,Z);
  TextOut(X,Y,Text);
end;

procedure THTML5Canvas.UnClipRectangle;
begin
  Add(' ctx.restore();');  // only narrowing clip works, this not: ClipRectangle(Bounds);
end;

{ TSVGExportFormat }

function THTML5ExportFormat.Description: String;
begin
  result:=TeeMsg_HTML5Canvas;
end;

Destructor THTML5ExportFormat.Destroy;
begin
  //FProperties.Free;
  inherited;
end;

procedure THTML5ExportFormat.DoCopyToClipboard;
begin
  with HTML5 do
  try
    TeeCopyClipboard(Text);
  finally
    Free;
  end;
end;

function THTML5ExportFormat.FileExtension: String;
begin
  result:='HTM';
end;

class function THTML5ExportFormat.FileFilter: String;
begin
  result:=TeeMsg_HTML5CanvasFilter;
end;

procedure THTML5ExportFormat.SaveToStream(Stream: TStream);
begin
  with HTML5 do
  try
    SaveToStream(Stream);
  finally
    Free;
  end;
end;

type TTeePanelAccess=class(TCustomTeePanel);

function THTML5ExportFormat.HTML5: TStringList;
var tmp : TCanvas3D;
begin
  CheckSize;

  result:=TStringList.Create;
  Panel.AutoRepaint:=False;
  try

    tmp:=Panel.Canvas;

    TTeePanelAccess(Panel).InternalCanvas:=nil;

    //CheckProperties;

    Panel.Canvas:=THTML5Canvas.CreateText(result);
    try
      Panel.Canvas.Assign(tmp);

      if not Assigned(Panel.Parent) then
         Panel.BufferedDisplay:=True;

      //THTML5Canvas(Panel.Canvas).Antialias:=FProperties.CBAntiAlias.Checked;

      Panel.Draw(Panel.Canvas.ReferenceCanvas,TeeRect(Width,Height));
    finally
      Panel.Canvas:=tmp;
    end;

  finally
    Panel.AutoRepaint:=True;
  end;
end;

procedure TeeSaveToHTML5File( const APanel:TCustomTeePanel; FileName: String;
                              AWidth:Integer=0; AHeight: Integer=0);
begin
  with THTML5ExportFormat.Create do
  try
    Panel:=APanel;
    Height:=AHeight;
    Width:=AWidth;

    if ExtractFileExt(FileName)='' then
       FileName:=FileName+'.'+FileExtension;

    SaveToFile(FileName);
  finally
    Free;
  end;
end;

initialization
  {$IFDEF FMX}
  TTeeExportFormats.RegisterFormat(THTML5ExportFormat);
  {$ELSE}
  RegisterTeeExportFormat(THTML5ExportFormat);
  {$ENDIF}
finalization
  {$IFDEF FMX}
  TTeeExportFormats.UnRegisterFormat(THTML5ExportFormat);
  {$ELSE}
  UnRegisterTeeExportFormat(THTML5ExportFormat);
  {$ENDIF}
end.
