{
Implements support for drawing to the LCL TCanvas

License: The same modified LGPL as the Free Pascal RTL
         See the file COPYING.modifiedLGPL for more details

AUTHORS: Felipe Monteiro de Carvalho
}
unit fpvectorial2canvas;

{$ifdef fpc}
  {$mode objfpc}{$h+}
{$endif}

{$define USE_CANVAS_CLIP_REGION}
{.$define DEBUG_CANVAS_CLIP_REGION}
{$define USE_LCL_CANVAS}

{.$define FPVECTORIAL_DEBUG_DIMENSIONS}
{.$define FPVECTORIAL_TOCANVAS_DEBUG}
{.$define FPVECTORIAL_DEBUG_BLOCKS}
{.$define FPVECTORIAL_AUTOFIT_DEBUG}
{.$define FPVECTORIAL_SUPPORT_LAZARUS_1_6}
// visual debugs
{.$define FPVECTORIAL_TOCANVAS_ELLIPSE_VISUALDEBUG}
{.$define FPVECTORIAL_RENDERINFO_VISUALDEBUG}

interface

uses
  Classes, SysUtils, Math, TypInfo, contnrs, Types,
  // FCL-Image
  FPCanvas, FPImage, FPWriteBMP,
  // lazutils
  GraphType, LazUTF8, Laz2_DOM,
  // LCL
  LazRegions, Graphics, LCLIntf, LCLType, intfgraphics, interfacebase,
  // fpvectorial
  fpvutils, fpvectorial;

type

  { TFPVCanvasRenderer }

  TFPVCanvasRenderer = class(TvRenderer)
  public
    procedure BeginRender(var ARenderInfo: TvRenderInfo; ADoDraw: Boolean); override;
    procedure EndRender(var ARenderInfo: TvRenderInfo; ADoDraw: Boolean); override;
    // TPath
    procedure TPath_Render(var ARenderInfo: TvRenderInfo; ADoDraw: Boolean; APath: TPath); override;
  end;

implementation

{ TFPVCanvasRenderer }

procedure TFPVCanvasRenderer.BeginRender(var ARenderInfo: TvRenderInfo;
  ADoDraw: Boolean);
begin

end;

procedure TFPVCanvasRenderer.EndRender(var ARenderInfo: TvRenderInfo;
  ADoDraw: Boolean);
begin

end;

procedure TFPVCanvasRenderer.TPath_Render(var ARenderInfo: TvRenderInfo;
  ADoDraw: Boolean; APath: TPath);
var
  ADest: TFPCustomCanvas absolute ARenderInfo.Canvas;
  ADestX: Integer absolute ARenderInfo.DestX;
  ADestY: Integer absolute ARenderInfo.DestY;
  AMulX: Double absolute ARenderInfo.MulX;
  AMulY: Double absolute ARenderInfo.MulY;
  //
  i: Integer;
  j, n: Integer;
  x1, y1, x2, y2: Integer;
  pts: TPointsArray = nil;
  ACanvas: TCanvas absolute ARenderInfo.Canvas;
  coordX, coordY: Integer;
  curSegment: TPathSegment;
  cur2DSegment: T2DSegment absolute curSegment;
  lRect: TRect;
  gv1, gv2: T2DPoint;
begin
  with APath do begin
  ConvertPathToPolygons(APath, ADestX, ADestY, AMulX, AMulY, FPolyPoints, FPolyStarts);
  x1 := MaxInt;
  y1 := maxInt;
  x2 := -MaxInt;
  y2 := -MaxInt;
  for i := 0 to High(FPolyPoints) do
  begin
    {$ifdef FPVECTORIAL_AUTOFIT_DEBUG}
    if AutoFitDebug <> nil then AutoFitDebug.Add(Format('==[%d=%d]', [FPolyPoints[i].X, FPolyPoints[i].Y]));
    {$endif}
    x1 := min(x1, FPolyPoints[i].X);
    y1 := min(y1, FPolyPoints[i].Y);
    x2 := max(x2, FPolyPoints[i].X);
    y2 := max(y2, FPolyPoints[i].Y);
  end;
  CalcEntityCanvasMinMaxXY_With2Points(ARenderInfo, x1, y1, x2, y2);
  // Boundary rect of shape filled with a gradient
  lRect := Rect(x1, y1, x2, y2);

  if ADoDraw then
  begin
    // (1) draw background only
    ADest.Pen.Style := psClear;
    if (Length(FPolyPoints) > 2) then
      case Brush.Kind of
        bkSimpleBrush:
          if Brush.Style <> bsClear then
          begin
            if (Brush.Style = bsSolid) and (Length(FPolyStarts) > 1) then
              // Non-contiguous polygon (polygon with "holes") --> use special procedure
              // Disadvantage: it can only do solid fills!
              APath.DrawPolygon(ARenderInfo, FPolyPoints, FPolyStarts, lRect)
            else
              {$IFDEF USE_LCL_CANVAS}
              for i := 0 to High(FPolyStarts) do
              begin
                j := FPolyStarts[i];
                if i = High(FPolyStarts) then
                  n := Length(FPolyPoints) - j
                else
                  n := FPolyStarts[i+1] - FPolyStarts[i]; // + 1;
                ACanvas.Polygon(@FPolyPoints[j], n, WindingRule = vcmNonZeroWindingRule);
              end;
              {$ELSE}
              ADest.Polygon(FPolyPoints);
              {$ENDIF}
          end;

        bkHorizontalGradient,
        bkVerticalGradient,
        bkOtherLinearGradient:
          begin
            // calculate gradient vector
            CalcGradientVector(gv1, gv2, lRect, ADestX, ADestY, AMulX, AMulY);
            // Draw the gradient
            DrawPolygonBrushLinearGradient(ARenderInfo, FPolyPoints, FPolyStarts, lRect, gv1, gv2);
          end;

        bkRadialGradient:
          DrawPolygonBrushRadialGradient(ARenderInfo, FPolyPoints, lRect);
      end;  // case Brush.Kind of...

    // (2) draw border, take care of the segments with modified pen
    ADest.Brush.Style := bsClear;               // We will paint no background
    ApplyPenToCanvas(ARenderInfo, Pen);  // Restore pen

    PrepareForSequentialReading;
    for j := 0 to Len - 1 do
    begin
      curSegment := TPathSegment(Next);
      case curSegment.SegmentType of
        stMoveTo:
          begin
            inc(i);
            coordX := CoordToCanvasX(cur2DSegment.X, ADestX, AMulX);
            coordY := CoordToCanvasY(cur2DSegment.Y, ADestY, AMulY);
            ADest.MoveTo(coordX, coordY);
          end;
        st2DLineWithPen, st2DLine, st3DLine:
          begin
            coordX := CoordToCanvasX(cur2DSegment.X, ADestX, AMulX);
            coordY := CoordToCanvasY(cur2DSegment.Y, ADestY, AMulY);
            if curSegment.SegmentType = st2DLineWithPen then
            begin
              ADest.Pen.FPColor := AdjustColorToBackground(T2DSegmentWithPen(Cur2DSegment).Pen.Color, ARenderInfo);
              ADest.Pen.Width := T2DSegmentWithPen(cur2DSegment).Pen.Width;
              ADest.Pen.Style := T2DSegmentWithPen(cur2DSegment).Pen.Style;
              ADest.LineTo(coordX, coordY);
              ApplyPenToCanvas(ARenderInfo, Pen);
            end else
              ADest.LineTo(coordX, coordY);
          end;
        st2DBezier, st3DBezier, st2DEllipticalArc:
          begin
            coordX := CoordToCanvasX(T2DSegment(curSegment.Previous).X, ADestX, AMulX);
            coordY := CoordToCanvasY(T2DSegment(curSegment.Previous).Y, ADestY, AMulY);
            SetLength(pts, 1);
            pts[0] := Point(coordX, coordY);
            curSegment.AddToPoints(ADestX, ADestY, AMulX, AMulY, pts);
            if Length(pts) > 0 then
            begin
              ADest.PolyLine(pts);
              ADest.MoveTo(pts[High(pts)].X, pts[High(pts)].Y);
            end;
          end;
      end;
    end;
  end;
  end;
end;

initialization

RegisterDefaultRenderer(TFPVCanvasRenderer);

end.

