unit CairoCanvas;

{$mode objfpc}{$H+}

{$if (FPC_FULLVERSION>=20701)}
{$Packset 1}
{$endif}

{$define pangocairo}
{-$define breaklines}   // disabled as it's not UTF-8 safe
{-$define DebugClip}

interface

uses
  Types, SysUtils, Classes, LCLType, LCLProc, Graphics, math, GraphMath,
  Printers, Cairo
  {$ifdef pangocairo}
  ,Pango, PangoCairo, GLib2
  {$endif}
  ;

type
  TSquaredCorners = set of (scTopLeft,scBottomLeft,scBottomRight,scTopRight);

  { TCairoPrinterCanvas }

  TCairoPrinterCanvas = class(TFilePrinterCanvas)
  strict private
    cr: Pcairo_t;
  private
    FLazClipRect: TRect;
    FUserClipRect: Pcairo_rectangle_t;
    {$ifdef pangocairo}
    fFontDesc: PPangoFontDescription;
    fFontDescStr: string;
    function StylesToStr(Styles: TFontStyles):string;
    procedure UpdatePangoLayout(Layout: PPangoLayout);
    {$endif}
    procedure SelectFontEx(AStyle: TFontStyles; const AName: string;
      ASize: double);
    function SX(x: double): double;
    function SY(y: double): double;
    function SX2(x: double): double;
    function SY2(y: double): double;
    procedure SetSourceColor(Color: TColor);
    procedure SetPenProperties;
    procedure SetBrushProperties;
    procedure SelectFont;
    procedure PolylinePath(Points: PPoint; NumPts: Integer);
    procedure EllipseArcPath(CX, CY, RX, RY: Double; Angle1, Angle2: Double;
      Clockwise, Continuous: Boolean);
    procedure ArcPath(ALeft, ATop, ARight, ABottom, Angle16Deg, Angle16DegLength: double);
    procedure ArcPath(ALeft, ATop, ARight, ABottom, StX, StY, EX, EY: double);
    procedure FillAndStroke;
    procedure FillOnly;
    procedure StrokeOnly;
    procedure TColorToRGB(Color: TColor; out R,G,B: double);
    // debug tools
    procedure DrawPoint(x,y: double; color: TColor);
    procedure DrawRefRect(x,y,awidth,aheight: double; color:TColor);
    procedure DebugSys;
  protected
    ScaleX, ScaleY, FontScale: Double;
    procedure SetLazClipRect(r: TRect);
    procedure DoLineTo(X1,Y1: Integer); override;
    procedure DoMoveTo({%H-}x, {%H-}y: integer); override;

    function CreateCairoHandle: HDC; virtual; abstract;
    procedure DestroyCairoHandle; virtual;
    procedure SetHandle(NewHandle: HDC); override;
    function GetClipRect: TRect; override;
    procedure SetClipRect(const ARect: TRect); override;
    function GetClipping: Boolean; override;
    procedure SetClipping(const AValue: boolean); override;
    //
    procedure CreateBrush; override;
    procedure CreateFont; override;
    procedure CreateHandle; override;
    procedure CreatePen; override;
    procedure CreateRegion; override;
    procedure RealizeAntialiasing; override;
    procedure DestroyHandle;
  public
    SurfaceXDPI, SurfaceYDPI: Integer;
    constructor Create(APrinter : TPrinter); override;
    constructor Create; overload;
    destructor Destroy; override;
    procedure BeginDoc; override;
    procedure EndDoc; override;
    procedure NewPage; override;
    procedure FillRect(const ARect: TRect); override;
    procedure Rectangle(X1,Y1,X2,Y2: Integer); override;
    procedure Polyline(Points: PPoint; NumPts: Integer); override;
    procedure Polygon(Points: PPoint; NumPts: Integer; {%H-}Winding: boolean = False); override;
    procedure FrameRect(const ARect: TRect); override;
    procedure Frame(const ARect: TRect); override;
    procedure RoundRect(X1, Y1, X2, Y2: Integer; RX, RY: Integer); override;
    procedure Ellipse(X1, Y1, X2, Y2: Integer); override;
    procedure Arc(ALeft, ATop, ARight, ABottom, Angle16Deg, Angle16DegLength: Integer); override;
    procedure Arc(ALeft, ATop, ARight, ABottom, StX, StY, EX, EY: Integer); override;
    procedure Chord(X1, Y1, X2, Y2, Angle1, Angle2: Integer); override;
    procedure Chord(X1, Y1, X2, Y2, StX, StY, EX, EY: Integer); override;
    procedure Pie(EllipseX1, EllipseY1, EllipseX2, EllipseY2, StartX, StartY, EndX, EndY: Integer); override;
    procedure RadialPie(Left, Top, Right, Bottom, Angle1, Angle2: Integer); override;
    procedure PolyBezier(Points: PPoint; NumPts: Integer; Filled: boolean = False; Continuous: boolean = False); override;
    procedure TextOut(X,Y: Integer; const Text: String); override;
    procedure TextRect(ARect: TRect; X1, Y1: integer; const Text: string; const Style: TTextStyle); override;
    function TextExtent(const Text: string): TSize; override;
    function GetTextMetrics(out M: TLCLTextMetric): boolean; override;
    procedure StretchDraw(const DestRect: TRect; SrcGraphic: TGraphic); override;
    procedure SetPixel(X,Y: Integer; Value: TColor); override;
  public
    procedure MixedRoundRect(X1, Y1, X2, Y2: Integer; RX, RY: Integer; SquaredCorners: TSquaredCorners);
    procedure DrawSurface(const SourceRect, DestRect: TRect; surface: Pcairo_surface_t);
{  Not implemented
    procedure FloodFill(X, Y: Integer; FillColor: TColor; FillStyle: TFillStyle); override;
    procedure CopyRect(const Dest: TRect; SrcCanvas: TCanvas; const Source: TRect); override;
    procedure Frame3d(var ARect: TRect; const FrameWidth: integer; const Style: TGraphicsBevelCut); override;}
  end;

  { TCairoFileCanvas }

  TCairoFileCanvas = class (TCairoPrinterCanvas)
  protected
    sf: Pcairo_surface_t;
    fStream: TStream;
    procedure DestroyCairoHandle; override;
  public
    procedure UpdatePageSize; virtual;
    property Stream: TStream read fStream write fStream;
  end;

  { TCairoPdfCanvas }

  TCairoPdfCanvas = class(TCairoFileCanvas)
  protected
    function CreateCairoHandle: HDC; override;
  public
    procedure UpdatePageSize; override;
  end;

  { TCairoSvgCanvas }

  TCairoSvgCanvas = class(TCairoFileCanvas)
  protected
    function CreateCairoHandle: HDC; override;
  end;

  { TCairoPngCanvas }

  TCairoPngCanvas = class(TCairoFileCanvas)
  protected
    function CreateCairoHandle: HDC; override;
    procedure DestroyCairoHandle; override;
  public
    constructor Create(APrinter: TPrinter); override;
  end;

  { TCairoPsCanvas }

  TCairoPsCanvas = class(TCairoFileCanvas)
  protected
    function CreateCairoHandle: HDC; override;
  public
    procedure UpdatePageSize; override;
  end;

  function GraphicToARGB32(Source: TGraphic; buf: PByte): Boolean;

implementation

uses
  IntfGraphics, GraphType, FPimage;

const
  Dash_Dash:        array [0..1] of double = (18, 6);             //____ ____
  Dash_Dot:         array [0..1] of double = (3, 3);              //.........
  Dash_DashDot:     array [0..3] of double = (9, 6, 3, 6);        //__ . __ .
  Dash_DashDotDot:  array [0..5] of double = (9, 3, 3, 3, 3, 3);  //__ . . __

function WriteToStream(closure: Pointer; data: PByte; length: LongWord): cairo_status_t; cdecl;
var
  Stream: TStream absolute closure;
begin
  if Stream.Write(data^, Length) = int64(Length) then
    result := CAIRO_STATUS_SUCCESS
  else
    result := CAIRO_STATUS_WRITE_ERROR;
end;

function GraphicToARGB32(Source: TGraphic; buf: PByte): Boolean;
var
  p: PDWord;
  x, y: Integer;
  c: TFPColor;
  Img: TLazIntfImage;
begin
  Img := TRasterImage(Source).CreateIntfImage;
  try
    if Img.DataDescription.Format=ricfNone then begin
      Result := False;
      Exit;
    end;
    p := Pointer(buf);
    for y := 0 to Source.Height-1 do begin
      for x := 0 to Source.Width-1 do begin
        c := Img.Colors[x, y];
        p^ := Hi(c.alpha) shl 24 + Hi(c.red) shl 16 + Hi(c.green) shl 8 + Hi(c.blue);
        inc(p);
      end;
    end;
  finally
    Img.Free;
  end;
  Result := True;
end;


{ TCairoPrinterCanvas }

procedure TCairoPrinterCanvas.SetPenProperties;
  procedure SetDash(d: array of double);
  begin
    cairo_set_dash(cr, @d, High(d)+1, 0);
  end;
var
  cap: cairo_line_cap_t;
   w: double;
begin
  SetSourceColor(Pen.Color);
  case Pen.Mode of
    pmBlack: begin
      SetSourceColor(clBlack);
      cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
    end;
    pmWhite: begin
      SetSourceColor(clWhite);
      cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
    end;
    pmCopy: cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
    pmXor: cairo_set_operator(cr, CAIRO_OPERATOR_XOR);
    pmNotXor: cairo_set_operator(cr, CAIRO_OPERATOR_XOR);
{    pmNop,
    pmNot,
    pmCopy,
    pmNotCopy,
    pmMergePenNot,
    pmMaskPenNot,
    pmMergeNotPen,
    pmMaskNotPen,
    pmMerge,
    pmNotMerge,
    pmMask,
    pmNotMask,}
  else
    cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
  end;
  w := Pen.Width;
  if w = 0 then
    w := 0.5;
  w := w * ScaleY;
  cairo_set_line_width(cr, w); //line_width is diameter of the pen circle

  case Pen.Style of
    psSolid:      cairo_set_dash(cr, nil, 0, 0);
    psDash:       SetDash(Dash_Dash);
    psDot:        SetDash(Dash_Dot);
    psDashDot:    SetDash(Dash_DashDot);
    psDashDotDot: SetDash(Dash_DashDotDot);
  else
    cairo_set_dash(cr, nil, 0, 0);
  end;

  case Pen.EndCap of
    pecRound:   cap := CAIRO_LINE_CAP_ROUND;
    pecSquare:  cap := CAIRO_LINE_CAP_SQUARE;
    pecFlat:    cap := CAIRO_LINE_CAP_BUTT;
  end;

  // dashed patterns do not look ok  combined with round or squared caps
  // make it flat until a solution is found
  case Pen.Style of
    psDash, psDot, psDashDot, psDashDotDot:
      cap := CAIRO_LINE_CAP_BUTT
  end;
  cairo_set_line_cap(cr, cap);

  case Pen.JoinStyle of
    pjsRound: cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
    pjsBevel: cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL);
    pjsMiter: cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER);
  end;
end;

procedure TCairoPrinterCanvas.SetBrushProperties;
begin
  SetSourceColor(Brush.Color);
{  case Brush.Style of
    bsSolid
    bsClear
    bsHorizontal
    bsVertical
    bsFDiagonal
    bsBDiagonal
    bsCross
    bsDiagCross
    bsImage
    bsPattern
  end;}
end;

procedure TCairoPrinterCanvas.DoLineTo(X1, Y1: Integer);
begin
  Changing;
  RequiredState([csHandleValid, csPenValid]);
  SetPenProperties;
  cairo_move_to(cr, SX(PenPos.X), SY(PenPos.Y));
  cairo_line_to(cr, SX(X1), SY(Y1));
  SetInternalPenPos(Point(X1,Y1));
  StrokeOnly;
  Changed;
end;

procedure TCairoPrinterCanvas.DoMoveTo(x, y: integer);
begin
  // should not call inherited DoMoveTo which would end calling
  // interface MoveToEx which breaks things for Qt
end;

procedure TCairoPrinterCanvas.DestroyCairoHandle;
begin
end;

procedure TCairoPrinterCanvas.SetHandle(NewHandle: HDC);
begin
  if NewHandle = {%H-}HDC(cr) then
    exit;

  if (NewHandle=0) and (cr<>nil) then
    DestroyHandle;

  cr := {%H-}Pcairo_t(NewHandle);

  // update state
  inherited SetHandle(NewHandle);
end;

procedure TCairoPrinterCanvas.BeginDoc;
begin
  inherited BeginDoc;
  if assigned(printer) then
    FLazClipRect:=printer.PaperSize.PaperRect.WorkRect;
end;

procedure TCairoPrinterCanvas.EndDoc;
begin
  inherited EndDoc;
  cairo_show_page(cr);
  FLazClipRect := Rect(0, 0, 0, 0);
  //if caller is printer, then at the end destroy cairo handles (flush output)
  //and establishes CreateCairoHandle call on the next print
  Handle := 0;
end;

procedure TCairoPrinterCanvas.NewPage;
begin
  inherited NewPage;
  cairo_show_page(cr);
end;

procedure TCairoPrinterCanvas.CreateBrush;
begin
end;

procedure TCairoPrinterCanvas.CreateFont;
begin
end;

procedure TCairoPrinterCanvas.CreateHandle;
begin
  ScaleX := SurfaceXDPI/XDPI;
  ScaleY := SurfaceYDPI/YDPI;
  Handle := CreateCairoHandle;
end;

procedure TCairoPrinterCanvas.CreatePen;
begin
end;

procedure TCairoPrinterCanvas.CreateRegion;
begin
end;

procedure TCairoPrinterCanvas.RealizeAntialiasing;
begin
end;

procedure TCairoPrinterCanvas.DestroyHandle;
begin
  cairo_destroy(cr);
  cr := nil;
  DestroyCairoHandle;
end;

function TCairoPrinterCanvas.GetClipRect: TRect;
var
  x1,y1,x2,y2: double;
begin
  RequiredState([csHandleValid]);

  // it doesn't matter what the clip is in use, default or user
  // this returns always the current clip

  cairo_clip_extents(cr, @x1, @y1, @x2, @y2);
  result.Left:=round(x1/ScaleX);
  result.Top:=round(y1/ScaleY);
  result.Right:=round(x2/ScaleX);
  result.Bottom:=round(y2/ScaleY);
end;

procedure TCairoPrinterCanvas.SetClipRect(const ARect: TRect);
begin
  RequiredState([csHandleValid]);
  if FUserClipRect=nil then
    New(FUserClipRect);

  fUserClipRect^.x := SX(ARect.Left);
  fUserClipRect^.y := SY(ARect.Top);
  fUserClipRect^.width := SX2(ARect.Right-ARect.Left);
  fUserClipRect^.height:= SY2(ARect.Bottom-ARect.Top);

  cairo_reset_clip(cr);

  {$ifdef DebugClip}
  with fUserClipRect^ do begin
    DrawPoint(x, y, clRed);
    DrawPoint(x+Width, y+Height, clBlue);
    DrawRefRect(x, y, width, height, clAqua);
  end;
  {$endif}

  with fUserClipRect^ do
    cairo_rectangle(cr, x, y, width, Height);

  cairo_Clip(cr);
end;

function TCairoPrinterCanvas.GetClipping: Boolean;
begin
  result := (fUserClipRect<>nil);
end;

procedure TCairoPrinterCanvas.SetClipping(const AValue: boolean);
begin
  RequiredState([csHandleValid]);
  cairo_reset_clip(cr);

  if not AValue then begin
    // free user cliprect if exists
    if fUserClipRect<>nil then
      Dispose(fUserClipRect);
    fUserClipRect := nil;
  end
  else begin
    if fUserClipRect<>nil then
    begin
      with fUserClipRect^ do
      begin
        cairo_rectangle(cr, x, y, width, height);
        cairo_clip(cr);
      end;
    end else
      ; // cairo_reset_clip always clip
  end;
end;

procedure TCairoPrinterCanvas.DrawPoint(x, y: double; color: TColor);
var
  r,g,b: Double;
begin
  TColorToRGB(color, r, g, b);
  cairo_set_source_rgb(cr, r, g, b);
  cairo_rectangle(cr, x-2, y-2, 4, 4);
  cairo_fill(cr);
end;

procedure TCairoPrinterCanvas.DrawRefRect(x, y, awidth, aheight: double;
  color: TColor);
var
  r,g,b: double;
begin
  TColorToRGB(color, r, g, b);
  cairo_set_source_rgb(cr, r, g, b);
  cairo_rectangle(cr, x, y, awidth, aheight);
  cairo_move_to(cr, x, y);
  cairo_line_to(cr, x+awidth, y+aheight);
  cairo_move_to(cr, x+awidth, y);
  cairo_line_to(cr, x, y+aheight);
  cairo_stroke(cr);
end;

procedure TCairoPrinterCanvas.DebugSys;
var
  x,y: double;
  matrix: cairo_matrix_t;
begin
  cairo_get_current_point(cr, @x, @y);
  cairo_get_matrix(cr, @matrix);
  DebugLn('CurPoint:  x=%f y=%f',[x, y]);
  with matrix do
    DebugLn('CurMatrix: xx=%f yx=%f xy=%f yy=%f x0=%f y0=%f',[xx,yx,xy,yy,x0,y0]);
end;

procedure TCairoPrinterCanvas.SetLazClipRect(r: TRect);
begin
  FLazClipRect := r;
end;

constructor TCairoPrinterCanvas.Create(APrinter: TPrinter);
begin
  inherited Create(APrinter);
  ScaleX := 1;
  ScaleY := 1;
  FontScale := 1;
  SurfaceXDPI := 72;
  SurfaceYDPI := 72;
  XDPI := SurfaceXDPI;
  YDPI := SurfaceXDPI;
end;

constructor TCairoPrinterCanvas.Create;
begin
  Create(nil);
end;

destructor TCairoPrinterCanvas.Destroy;
begin
  if fUserClipRect<>nil then
    Dispose(fUserClipRect);
  fUserClipRect := nil;
  {$ifdef pangocairo}
  if fFontDesc<>nil then
    pango_font_description_free(fFontDesc);
  {$endif}
  inherited Destroy;
end;

function TCairoPrinterCanvas.SX(x: double): double;
begin
  Result := ScaleX*(x+FLazClipRect.Left);
end;

function TCairoPrinterCanvas.SY(y: double): double;
begin
  Result := ScaleY*(y+FLazClipRect.Top);
end;

function TCairoPrinterCanvas.SX2(x: double): double;
begin
  Result := ScaleX*x;
end;

function TCairoPrinterCanvas.SY2(y: double): double;
begin
  Result := ScaleY*y;
end;

procedure TCairoPrinterCanvas.SetSourceColor(Color: TColor);
var
  R, G, B: double;
begin
  //TColor je ve formatu BGR
  TColorToRGB(Color, R, G, B);
  cairo_set_source_rgb(cr, R, G, B);
end;

procedure TCairoPrinterCanvas.Rectangle(X1, Y1, X2, Y2: Integer);
begin
  Changing;
  RequiredState([csHandleValid, csBrushValid, csPenValid]);
  SetPenProperties;
  cairo_rectangle(cr, SX(X1), SY(Y1), SX2(X2-X1), SY2(Y2-Y1));
  FillAndStroke;
  Changed;
end;

//1 point rectangle in _Brush_ color
procedure TCairoPrinterCanvas.FrameRect(const ARect: TRect);
begin
  Changing;
  RequiredState([csHandleValid, csBrushValid]);
  cairo_rectangle(cr, SX(ARect.Left), SY(ARect.Top), SX2(ARect.Right-ARect.Left), SY2(ARect.Bottom-ARect.Top));
  SetSourceColor(Brush.Color);
  cairo_set_line_width(cr, 1);
  cairo_stroke(cr); //Don't touch
  Changed;
end;

procedure TCairoPrinterCanvas.Frame(const ARect: TRect);
begin
  Changing;
  RequiredState([csHandleValid, csPenValid]);
  cairo_rectangle(cr, SX(ARect.Left), SY(ARect.Top), SX2(ARect.Right-ARect.Left), SY2(ARect.Bottom-ARect.Top));
  cairo_set_line_width(cr, 1);
  SetSourceColor(Pen.Color);
  cairo_stroke(cr); //Don't touch
  Changed;
end;

//C* - center, R* - halfaxis
procedure TCairoPrinterCanvas.EllipseArcPath(CX, CY, RX, RY: Double; Angle1, Angle2: Double; Clockwise, Continuous: Boolean);
begin
  if (RX=0) or (RY=0) then //cairo_scale do not likes zero params
    Exit;
  cairo_save(cr);
  try
    cairo_translate(cr, SX(CX), SY(CY));
    cairo_scale(cr, SX2(RX), SY2(RY));
    if not Continuous then
      cairo_move_to(cr, cos(Angle1), sin(Angle1)); //Move to arcs starting point
    if Clockwise then
      cairo_arc(cr, 0, 0, 1, Angle1, Angle2)
    else
    cairo_arc_negative(cr, 0, 0, 1, Angle1, Angle2);
  finally
    cairo_restore(cr);
  end;
end;

procedure TCairoPrinterCanvas.FillOnly;
begin
  if Brush.Style <> bsClear then begin
    SetBrushProperties;
    cairo_fill(cr);
  end;
end;

procedure TCairoPrinterCanvas.StrokeOnly;
begin
  if Pen.Style <> psClear then begin
    SetPenProperties;
    cairo_stroke(cr);
  end;
end;

procedure TCairoPrinterCanvas.TColorToRGB(Color: TColor; out R, G, B: double);
begin
  R := (Color and $FF) / 255;
  G := ((Color shr 8) and $FF) / 255;
  B := ((Color shr 16) and $FF) / 255;
end;


{$ifdef pangocairo}
function TCairoPrinterCanvas.StylesToStr(Styles: TFontStyles): string;
begin
  Result := '';
  if fsBold in Styles then
    Result := Result + 'bold ';
  if fsItalic in Styles then
    Result := Result + 'italic ';
end;

procedure TCairoPrinterCanvas.UpdatePangoLayout(Layout: PPangoLayout);
var
  AttrListTemporary: Boolean;
  AttrList: PPangoAttrList;
  Attr: PPangoAttribute;
begin
  if Font.Underline or Font.StrikeThrough then begin

    AttrListTemporary := false;
    AttrList := pango_layout_get_attributes(Layout);
    if (AttrList = nil) then
    begin
      AttrList := pango_attr_list_new();
      AttrListTemporary := True;
    end;
    if Font.Underline then
      Attr := pango_attr_underline_new(PANGO_UNDERLINE_SINGLE)
    else
      Attr := pango_attr_underline_new(PANGO_UNDERLINE_NONE);
    pango_attr_list_change(AttrList, Attr);

    Attr := pango_attr_strikethrough_new(Font.StrikeThrough);
    pango_attr_list_change(AttrList, Attr);

    pango_layout_set_attributes(Layout, AttrList);

    pango_cairo_update_layout(cr, Layout);

    if AttrListTemporary then
      pango_attr_list_unref(AttrList);
  end;
end;

{$endif}

procedure TCairoPrinterCanvas.FillAndStroke;
begin
  if Brush.Style <> bsClear then begin
    SetBrushProperties;
    if Pen.Style = psClear then
      cairo_fill(cr)
    else
      cairo_fill_preserve(cr);
  end;
  if Pen.Style <> psClear then begin
    SetPenProperties;
    cairo_stroke(cr);
  end;
end;

procedure TCairoPrinterCanvas.RoundRect(X1, Y1, X2, Y2: Integer; RX, RY: Integer);
begin
  Changing;
  RequiredState([csHandleValid, csPenValid, csBrushValid]);
  cairo_move_to(cr, SX(X1+RX), SY(Y1));
  cairo_line_to(cr, SX(X2-RX), SY(Y1));
  EllipseArcPath(X2-RX, Y1+RY, RX, RY, -PI/2, 0, True, True);
  cairo_line_to(cr, SX(X2), SY(Y2-RY));
  EllipseArcPath(X2-RX, Y2-RY, RX, RY, 0, PI/2, True, True);
  cairo_line_to(cr, SX(X1+RX), SY(Y2));
  EllipseArcPath(X1+RX, Y2-RY, RX, RY, PI/2, PI, True, True);
  cairo_line_to(cr, SX(X1), SY(Y1+RX));
  EllipseArcPath(X1+RX, Y1+RY, RX, RY, PI, PI*1.5, True, True);
  FillAndStroke;
  Changed;
end;

procedure TCairoPrinterCanvas.MixedRoundRect(X1, Y1, X2, Y2: Integer; RX,
  RY: Integer; SquaredCorners: TSquaredCorners);
begin
  Changing;
  RequiredState([csHandleValid, csPenValid, csBrushValid]);

  cairo_move_to(cr, SX(X1+RX), SY(Y1));
  cairo_line_to(cr, SX(X2-RX), SY(Y1));

  if scTopRight in SquaredCorners then
  begin
    cairo_line_to(cr, SX(X2), SY(Y1));
    cairo_line_to(cr, SX(X2), SY(Y1+RY));
  end else
    EllipseArcPath(X2-RX, Y1+RY, RX, RY, -PI/2, 0, True, True);

  cairo_line_to(cr, SX(X2), SY(Y2-RY));

  if scBottomRight in SquaredCorners then
  begin
    cairo_line_to(cr, SX(X2), SY(Y2));
    cairo_line_to(cr, SX(X2-RX), SY(Y2));
  end else
    EllipseArcPath(X2-RX, Y2-RY, RX, RY, 0, PI/2, True, True);

  cairo_line_to(cr, SX(X1+RX), SY(Y2));

  if scBottomLeft in SquaredCorners then
  begin
    cairo_line_to(cr, SX(X1), SY(Y2));
    cairo_line_to(cr, SX(X1), SY(Y2-RY));
  end else
    EllipseArcPath(X1+RX, Y2-RY, RX, RY, PI/2, PI, True, True);

  cairo_line_to(cr, SX(X1), SY(Y1+RX));

  if scTopLeft in SquaredCorners then
  begin
    cairo_line_to(cr, SX(X1), SY(Y1));
    cairo_line_to(cr, SX(X1+RX), SY(Y1));
  end else
    EllipseArcPath(X1+RX, Y1+RY, RX, RY, PI, PI*1.5, True, True);

  FillAndStroke;
  Changed;
end;

procedure TCairoPrinterCanvas.DrawSurface(const SourceRect, DestRect: TRect;
  surface: Pcairo_surface_t);
var
  SW, SH: Double;
begin
  Changing;
  RequiredState([csHandleValid]);

  cairo_save(cr);
  cairo_translate(cr, SX(DestRect.Left), SY(DestRect.Top));
  SW := (DestRect.Right - DestRect.Left)/(SourceRect.Right-SourceRect.Left);
  SH := (DestRect.Bottom - DestRect.Top)/(SourceRect.Bottom-SourceRect.Top);
  cairo_scale(cr, SX2(SW), SY2(SH));
  cairo_set_source_surface(cr, surface, 0, 0);
  cairo_paint(cr);
  cairo_restore(cr);
  Changed;
end;

procedure TCairoPrinterCanvas.Ellipse(X1, Y1, X2, Y2: Integer);
begin
  Changing;
  RequiredState([csHandleValid, csPenValid, csBrushValid]);
  EllipseArcPath((X2+X1)/2, (Y2+Y1)/2, (X2-X1)/2, (Y2-Y1)/2, 0, 2*PI, True, False);
  FillAndStroke;
  Changed;
end;

procedure TCairoPrinterCanvas.Arc(ALeft, ATop, ARight, ABottom, Angle16Deg, Angle16DegLength: Integer);
begin
  Changing;
  RequiredState([csHandleValid, csPenValid]);
  ArcPath(ALeft, ATop, ARight, ABottom, Angle16Deg, Angle16DegLength);
  StrokeOnly;
  Changed;
end;

procedure TCairoPrinterCanvas.Chord(X1, Y1, X2, Y2, Angle1, Angle2: Integer);
begin
  Changing;
  RequiredState([csHandleValid, csPenValid, csBrushValid]);
  ArcPath(X1, Y1, X2, Y2, Angle1, Angle2);
  cairo_close_path(cr);
  FillAndStroke;
  Changed;
end;

procedure TCairoPrinterCanvas.RadialPie(Left, Top, Right, Bottom, Angle1, Angle2: Integer);
var
  cx, cy: double;
begin
  Changing;
  RequiredState([csHandleValid, csPenValid, csBrushValid]);
  ArcPath(Left, Top, Right, Bottom, Angle1, Angle2);
  cx := (Right+Left)/2;
  cy := (Bottom+Top)/2;
  cairo_line_to(cr, SX(cx), SY(cy));
  cairo_close_path(cr);
  FillAndStroke;
  Changed;
end;

procedure TCairoPrinterCanvas.ArcPath(ALeft, ATop, ARight, ABottom, Angle16Deg, Angle16DegLength: double);
var
  k: Double;
begin
  k := - 2*PI/(360*16);
  EllipseArcPath((ARight+ALeft)/2, (ABottom+ATop)/2, (ARight-ALeft)/2, (ABottom-ATop)/2,
    Angle16Deg*k, Angle16DegLength*k, False, False);
end;

procedure TCairoPrinterCanvas.Arc(ALeft, ATop, ARight, ABottom, StX, StY, EX, EY: Integer);
begin
  Changing;
  RequiredState([csHandleValid, csPenValid]);
  ArcPath(ALeft, ATop, ARight, ABottom, StX, StY, EX, EY);
  StrokeOnly;
  Changed;
end;

procedure TCairoPrinterCanvas.Chord(X1, Y1, X2, Y2, StX, StY, EX, EY: Integer);
begin
  Changing;
  RequiredState([csHandleValid, csPenValid, csBrushValid]);
  ArcPath(X1, Y1, X2, Y2, StX, StY, EX, EY);
  cairo_close_path(cr);
  FillAndStroke;
  Changed;
end;

procedure TCairoPrinterCanvas.Pie(EllipseX1, EllipseY1, EllipseX2, EllipseY2,
  StartX, StartY, EndX, EndY: Integer);
var
  cx, cy: double;
begin
  Changing;
  RequiredState([csHandleValid, csPenValid, csBrushValid]);
  ArcPath(EllipseX1, EllipseY1, EllipseX2, EllipseY2, StartX, StartY, EndX, EndY);
  cx := (EllipseX2+EllipseX1)/2;
  cy := (EllipseY2+EllipseY1)/2;
  cairo_line_to(cr, SX(cx), SY(cy));
  cairo_close_path(cr);
  FillAndStroke;
  Changed;
end;

procedure TCairoPrinterCanvas.ArcPath(ALeft, ATop, ARight, ABottom, StX, StY, EX, EY: double);

  function ATanInt(x, y: double): double;
  begin
    if x <> 0 then begin
      result := ArcTan(y/x);
      if x < 0 then
        result := result + PI;
    end else begin
      if y > 0 then
        result := PI/2
      else
        result := - PI/2;
    end;
  end;

var
  Angle1, Angle2: double;
  cx, cy: double;
begin
  cx := (ARight+ALeft)/2;
  cy := (ABottom+ATop)/2;
  Angle1 := ATanInt(StX-cx, StY-cy);
  Angle2 := ATanInt(EX-cx, EY-cy);
  EllipseArcPath(cx, cy, (ARight-ALeft)/2, (ABottom-ATop)/2, Angle1, Angle2, False, False);
end;

procedure TCairoPrinterCanvas.PolyBezier(Points: PPoint; NumPts: Integer; Filled: boolean; Continuous: boolean);
var
  p, ep: PPoint;
begin
  p := Points;
  ep := Points + NumPts;
  while p < ep do begin
    if (p = Points) or not Continuous then begin //First or non cont.
      cairo_move_to(cr, SX(p^.X), SY(p^.Y));
      inc(p);
    end;
    cairo_curve_to(cr, SX(p^.X), SY(p^.Y), SX((p+1)^.X), SY((p+1)^.Y), SX((p+2)^.X), SY((p+2)^.Y));
    inc(p, 3);
  end;
  if Filled then begin
    cairo_close_path(cr);
    FillAndStroke;
  end else
    StrokeOnly;
end;

//Toy interface
procedure TCairoPrinterCanvas.SelectFont;
begin
  RequiredState([csHandleValid]);
  SelectFontEx(Font.Style, Font.Name, abs(Font.Size));
  SetSourceColor(Font.Color);
end;

procedure TCairoPrinterCanvas.SelectFontEx(AStyle: TFontStyles; const AName: string; ASize: double);
var
  slant: cairo_font_slant_t;
  weight: cairo_font_weight_t;
  {$ifdef pangocairo}
  S: string;
  {$endif}
begin
  if fsBold in Font.Style then
    weight := CAIRO_FONT_WEIGHT_BOLD
  else
    weight := CAIRO_FONT_WEIGHT_NORMAL;
  if fsItalic in Font.Style then
    slant := CAIRO_FONT_SLANT_ITALIC
  else
    slant := CAIRO_FONT_SLANT_NORMAL;
  {$ifdef pangocairo}
  S := format('%s %s %dpx',[AName, StylesToStr(AStyle), round(ASize)]);
  if (fFontDesc=nil) or (S<>fFontDescStr) then
  begin
    if fFontDesc<>nil then
      pango_font_description_free(fFontDesc);
    fFontDesc := pango_font_description_from_string(pchar(s));
  end;
  fFontDescStr := s;
  {$endif}
  cairo_select_font_face(cr, PChar(AName), slant, weight);
  cairo_set_font_size(cr, ASize*FontScale)
end;

procedure TCairoPrinterCanvas.TextOut(X, Y: Integer; const Text: String);
var
  e: cairo_font_extents_t;
  {$ifdef pangocairo}
  Layout: PPangoLayout;
  {$endif}
begin
  Changing;
  RequiredState([csHandleValid, csFontValid, csBrushValid]);
  SelectFont;
  cairo_font_extents(cr, @e);
  cairo_save(cr);
  {$ifdef pangocairo}
  // use absolute font size sintax  (px)
  Layout := Pango_Cairo_Create_Layout(cr);
  pango_layout_set_font_description(layout, fFontDesc);
  UpdatePangoLayout(Layout);
  {$endif}
  if Font.Orientation = 0 then
  begin
    cairo_move_to(cr, SX(X), SY(Y)+e.ascent);
    {$ifdef pangocairo}
    //DebugLn('TextOut ',Text);
    //DebugSys;
    pango_layout_set_text(layout, PChar(Text), -1);
    {$else}
    cairo_show_text(cr, PChar(Text)); //Reference point is on the base line
    {$endif}
  end
  else
  begin
    cairo_move_to(cr, SX(X)+e.ascent, SY(Y));
    cairo_rotate(cr, -gradtorad(Font.Orientation));
    {$ifdef pangocairo}
    pango_layout_set_text(layout, PChar(Text), -1);
    {$else}
    cairo_show_text(cr, PChar(Text)); //Reference point is on the base line
    {$endif}
  end;
  {$ifdef pangocairo}
  pango_cairo_update_layout(cr, layout);
  // get the same text origin as cairo_show_text (baseline left, instead of Pango's top left)
  pango_cairo_show_layout_line (cr, pango_layout_get_line (layout, 0));
  g_object_unref(layout);
  {$endif}
  cairo_restore(cr);
  Changed;
end;

{$ifdef breaklines}
type
  TLine = class
    Start, EndL: Integer;
    Width: Double;
  end;
{$endif}

procedure TCairoPrinterCanvas.TextRect(ARect: TRect; X1, Y1: integer; const Text: string; const Style: TTextStyle);
var
  s: string;
{$ifdef breaklines}
  te: cairo_text_extents_t;
  Lines: TList;
  CurLine: TLine;
  len: integer;
  LastBreakEndL: Integer;
  LastBreakStart: Integer;

  procedure BreakLine(en, st: Integer);
  var
    s1: string;
    te: cairo_text_extents_t;
  begin
    if en>=0 then begin
      //if en>1 then begin
        if en <= len then
          CurLine.EndL := en
        else
          CurLine.EndL := len;
      //end else
        //CurLine.EndL := 1;
      s1 := Copy(s, CurLine.Start, CurLine.EndL-CurLine.Start+1);
      cairo_text_extents(cr, PChar(s1), @te);
      CurLine.Width := te.width;
    end;
    if st > 0 then begin
      CurLine := TLine.Create;
      Lines.Add(CurLine);
      //if st <= len then
        CurLine.Start := st;
      //else
      //  CurLine.Start := len;
      CurLine.EndL := 0;
    end;
    LastBreakEndL := 0;
    LastBreakStart := 0;
  end;
{$endif}

var
  fd: TFontData;
  s1: string;
  i: integer;
  BoxLeft, BoxTop, BoxWidth, BoxHeight: Double;
  StartLeft, StartTop: Double;
  x, y: Double;
  r,b: double;
  {$ifdef pangocairo}
  Layout: PPangoLayout;
  ink,logical: TPangoRectangle;
  {$endif}

  {$ifdef breaklines}
  fe: cairo_font_extents_t;
  BreakBoxWidth: Double;
  j: integer;
  ch: string;
  {$else}
  Lines: TStringList;
  {$endif}
begin
  Changing;
  RequiredState([csHandleValid, csFontValid, csBrushValid]);
  cairo_save(cr);
  try
    s := Text;
    BoxWidth := SX2(ARect.Right-ARect.Left);
    BoxHeight := SY2(ARect.Bottom-ARect.Top);
    BoxLeft := SX(ARect.Left);
    BoxTop := SY(ARect.Top);
    StartLeft := SX(X1);
    StartTop := SY(Y1);
    //DebugLn('Box= l=%f t=%f',[BoxLeft,BoxTop]);
    //DebugLn('     x=%f y=%f',[StartLeft,StartTop]);
    {$ifdef breaklines}
    if Style.Alignment = taLeftJustify then
      BreakBoxWidth := SX(ARect.Right - X1)
    else
      BreakBoxWidth := BoxWidth;
    {$endif}

    if Style.Clipping then begin
      r := BoxWidth+Pen.Width;
      b := BoxHeight+Pen.Width;

      {$ifdef DebugClip}
      DrawPoint(boxLeft, boxTop, clRed);
      DrawPoint(boxLeft+r, boxTop+b, clBlue);
      DrawRefRect(boxLeft, boxTop, r, b, clGreen);
      {$endif}

      cairo_rectangle(cr, BoxLeft, BoxTop, r, b);
      cairo_clip(cr);
    end;

    if (Font.Orientation=900) or (Font.Orientation=2700) then begin
      x := BoxWidth;
      BoxWidth := BoxHeight;
      BoxHeight := x;
    end;

    if Style.ExpandTabs then
      s := StringReplace(s, #9, '        ', [rfReplaceAll])
    else
      s := StringReplace(s, #9, ' ', [rfReplaceAll]);

    if Style.SingleLine then begin
      s := StringReplace(s, #13+#10, ' ', [rfReplaceAll]);
      s := StringReplace(s, #13, ' ', [rfReplaceAll]);
      s := StringReplace(s, #10, ' ', [rfReplaceAll]);
    end;

    if Style.Opaque then begin
      SetSourceColor(Brush.Color);
      cairo_rectangle(cr, BoxLeft, BoxTop, BoxWidth, BoxHeight);
      cairo_fill(cr)
    end;

    if Style.SystemFont and Assigned(OnGetSystemFont) then begin
      fd := GetFontData(OnGetSystemFont());
      SelectFontEx(fd.Style, fd.Name, fd.Height);
      SetSourceColor(clWindowText);
    end else
      SelectFont;

    {$ifdef pangocairo}
    Layout := Pango_Cairo_Create_Layout(cr);
    pango_layout_set_font_description(layout, fFontDesc);
    UpdatePangolayout(Layout);
    {$else}
    cairo_font_extents(cr, @fe);
    {$endif}

    {$ifdef breaklines}
    Lines := TList.Create;
    //Break lines
    len := Length(s);
    BreakLine(-1, 1);
    i := 1;
    while i<=len+1 do begin
      if i<=len then
        ch := s[i]
      else
        ch := '';
      //CR LF breaking
      if ch = #13 then begin
        if (i < len) and (s[i+1] = #10) then begin
          BreakLine(i-1, i+2);
          inc(i, 2);
          Continue;
        end else begin
          BreakLine(i-1, i+1);
          inc(i, 1);
          Continue;
        end;
      end;
      if ch = #10 then begin
        BreakLine(i-1, i+1);
        inc(i, 1);
        Continue;
      end;

      //Word breaking
      if Style.Wordbreak then begin
        if (ch = '') or (ch = ' ') then begin //'' last char
          s1 := Copy(s, CurLine.Start, i-CurLine.Start);
          {$ifdef pangocairo}
          {$else}
          cairo_text_extents(cr, PChar(s1), @te);
          {$endif}
          //skip following break chars
          j := i+1;
          while (j<=len) and (s[j] = ' ') do
            inc(j);
          if (te.width+te.x_bearing) <= BreakBoxWidth then begin
            LastBreakEndL := i-1;
            LastBreakStart := j;
          end else begin //overflow
            if LastBreakEndL<=0 then begin //cannot break
              BreakLine(i-1, j);
              inc(i);
              Continue;
            end else begin
              i := LastBreakStart; //before BreakLine where is LastBreakStart changed
              BreakLine(LastBreakEndL, LastBreakStart);
              Continue;
            end;
          end;
        end;
      end;

      //next char
      inc(i);
    end;
    //Close last CurLine
    BreakLine(Len, -1);

    {$else breaklines}

    Lines := TStringList.Create;
    Lines.Text := s;

    {$endif}

    {$ifdef pangocairo}
    if Style.Wordbreak then begin
      pango_layout_set_width(layout, Round(BoxWidth*PANGO_SCALE));
      pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
      case Style.Alignment of //Works only with pango_layout_set_width
        taLeftJustify:  pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
        taCenter:       pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
        taRightJustify: pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT);
      end;
    end;

    pango_layout_set_text(layout, pchar(s), -1);
    pango_layout_get_extents(Layout, @ink, @logical);
    //Calc start 'box' relative positions
    case Style.Layout of
      tlTop:    y := 0;
      tlCenter: y := BoxHeight/2 - logical.Height/PANGO_SCALE/2;
      tlBottom: y := BoxHeight - logical.height/PANGO_SCALE;
    end;
    {$else}
    //Calc start positions
    case Style.Layout of
      tlTop:    y := 0;
      tlCenter: y := BoxHeight/2 - fe.height*Lines.Count/2;
      tlBottom: y := BoxHeight - fe.height*Lines.Count;
    end;
    {$endif}

    // translate origin
    cairo_translate(cr, StartLeft, StartTop);
    // rotate
    cairo_rotate(cr, -DegToRad(Font.Orientation/10));

    //Text output
    for i := 0 to Lines.Count-1 do begin

      {$ifdef breaklines}
      CurLine := TLine(Lines.Items[i]);
      s1 := Copy(s, CurLine.Start, CurLine.EndL-CurLine.Start+1);
      {$else}
      s1 := Lines[i];
      {$endif}

      //DebugLn('i=%i y=%f s1=%s',[i,y,s1]);
      {$ifdef pangocairo}
      pango_layout_set_text(layout, pchar(s1), -1);
      pango_layout_get_extents(Layout, @ink, @logical);
      x := 0;
      if not Style.Wordbreak then begin
        case Style.Alignment of
          taCenter:       x := BoxWidth/2 - logical.width/PANGO_SCALE/2;
          taRightJustify: x := BoxWidth - logical.Width/PANGO_SCALE;
        end;
      end;
      cairo_move_to(cr, x, y);
      //DebugLn('TextRect ',S1);
      //DebugSys;
      pango_cairo_show_layout(cr, layout);
      y := y + logical.height/PANGO_SCALE;
      {$else}
      case Style.Alignment of
        taLeftJustify: x := StartLeft;
        taCenter: x := BoxLeft + BoxWidth/2 - CurLine.Width/2;
        taRightJustify: x := BoxLeft+BoxWidth - CurLine.Width;
      end;
      cairo_move_to(cr, x, y+fe.ascent);
      cairo_show_text(cr, PChar(s1)); //Reference point is on the base line
      y := y + fe.height;
      {$endif}
    end;
    {$ifdef pangocairo}
    g_object_unref(layout);
    {$endif}

  finally
    cairo_restore(cr);
    {$ifdef breaklines}
    for i := 0 to Lines.Count-1 do
      TLine(Lines.Items[i]).Free;
    {$endif}
    Lines.Free;
  end;
  Changed;
end;

function TCairoPrinterCanvas.TextExtent(const Text: string): TSize;
var
  extents: cairo_text_extents_t;
  {$ifdef pangocairo}
  Layout: PPangoLayout;
  theRect: TPangoRectangle;
  {$endif}
begin
  RequiredState([csHandleValid, csFontValid]);
  SelectFont;
  {$ifdef pangocairo}
  Layout := Pango_Cairo_Create_Layout(cr);
  pango_layout_set_font_description(Layout, fFontDesc);
  cairo_text_extents(cr, PChar(Text), @extents);
  pango_layout_set_text(Layout, pchar(Text), -1);
  pango_layout_get_extents(Layout, nil, @theRect);
  Result.cx := Round((theRect.width/PANGO_SCALE)/ScaleX);
  Result.cy := Round((theRect.height/PANGO_SCALE)/ScaleY);
  g_object_unref(Layout);
  {$else}
  cairo_text_extents(cr, PChar(Text), @extents); //transformation matrix is here ignored
  Result.cx := Round((extents.width)/ScaleX+extents.x_bearing);
  Result.cy := Round((extents.height)/ScaleY-extents.y_bearing);
  {$endif}
end;

function TCairoPrinterCanvas.GetTextMetrics(out M: TLCLTextMetric): boolean;
var
  e: cairo_font_extents_t;
begin
  RequiredState([csHandleValid, csFontValid]);
  SelectFont;
  cairo_font_extents(cr, @e); //transformation matrix is here ignored
  FillChar(M{%H-}, SizeOf(M), 0);
  M.Ascender := Round(e.ascent/ScaleY);
  M.Descender := Round(e.descent/ScaleY);
  M.Height := Round(e.height/ScaleY);
  Result := True;
end;

procedure TCairoPrinterCanvas.StretchDraw(const DestRect: TRect; SrcGraphic: TGraphic);
var
  sf: Pcairo_surface_t;
  buf: PByte;
  W, H: Integer;
  SW, SH: Double;
begin
  if not (SrcGraphic is TRasterImage) then begin
    inherited StretchDraw(DestRect, SrcGraphic);
    Exit;
  end;

  Changing;
  RequiredState([csHandleValid]);
  W := SrcGraphic.Width;
  H := SrcGraphic.Height;

  buf := GetMem(W*H*4);
  try
    cairo_save(cr);
    //FillDWord(buf^, W*H, $00000000);
    if not GraphicToARGB32(SrcGraphic, buf) then
      Exit;

    sf := cairo_image_surface_create_for_data(buf, CAIRO_FORMAT_ARGB32, W, H, W*4);
    cairo_translate(cr, SX(DestRect.Left), SY(DestRect.Top));
    SW := (DestRect.Right - DestRect.Left)/W;
    SH := (DestRect.Bottom - DestRect.Top)/H;
    cairo_scale(cr, SX2(SW), SY2(SH));
    cairo_set_source_surface(cr, sf, 0, 0);
    cairo_paint(cr);
    cairo_surface_destroy(sf);
    cairo_restore(cr);
  finally
    FreeMem(buf);
  end;
  Changed;
end;

procedure TCairoPrinterCanvas.SetPixel(X, Y: Integer; Value: TColor);
begin
  Changing;
  RequiredState([csHandleValid, csPenValid]);
  SetSourceColor(Value);
  cairo_rectangle(cr, SX(X), SY(Y), 1, 1);
  cairo_fill(cr);
  Changed;
end;

procedure TCairoPrinterCanvas.PolylinePath(Points: PPoint; NumPts: Integer);
var
  p: PPoint;
  i: integer;
begin
  p := Points;
  cairo_move_to(cr, SX(p^.X), SY(p^.Y));
  for i := 0 to NumPts-2 do begin
    inc(p);
    cairo_line_to(cr, SX(p^.X), SY(p^.Y));
  end;
end;

procedure TCairoPrinterCanvas.Polyline(Points: PPoint; NumPts: Integer);
begin
  if NumPts <= 0 then
    Exit;
  Changing;
  RequiredState([csHandleValid, csPenValid]);
  PolylinePath(Points, NumPts);
  StrokeOnly;
  Changed;
end;

procedure TCairoPrinterCanvas.Polygon(Points: PPoint; NumPts: Integer; Winding: boolean);
begin
  if NumPts <= 0 then
    Exit;
  Changing;
  RequiredState([csHandleValid, csBrushValid, csPenValid]);
  PolylinePath(Points, NumPts);
  cairo_close_path(cr);
  FillAndStroke;
  Changed;
end;

procedure TCairoPrinterCanvas.FillRect(const ARect: TRect);
begin
  Changing;
  RequiredState([csHandleValid, csBrushValid]);
  cairo_rectangle(cr, SX(ARect.Left), SY(ARect.Top), SX2(ARect.Right-ARect.Left), SY2(ARect.Bottom-ARect.Top));
  FillOnly;
  Changed;
end;

{ TCairoFileCanvas }

procedure TCairoFileCanvas.DestroyCairoHandle;
begin
  cairo_surface_finish(sf);
  cairo_surface_destroy(sf);
  sf := nil;
end;

procedure TCairoFileCanvas.UpdatePageSize;
begin
end;

{ TCairoPdfCanvas }

function TCairoPdfCanvas.CreateCairoHandle: HDC;
begin
  //Sizes are in Points, 72DPI (1pt = 1/72")
  if fStream<>nil then
    sf := cairo_pdf_surface_create_for_stream(@WriteToStream, fStream, PaperWidth*ScaleX, PaperHeight*ScaleY)
  else
    sf := cairo_pdf_surface_create(PChar(FOutputFileName), PaperWidth*ScaleX, PaperHeight*ScaleY);
  result := {%H-}HDC(cairo_create(sf));
end;

procedure TCairoPdfCanvas.UpdatePageSize;
begin
  cairo_pdf_surface_set_size(sf, PaperWidth*ScaleX, PaperHeight*ScaleY);
end;

{ TCairoPsCanvas }

function TCairoPsCanvas.CreateCairoHandle: HDC;
var
  s: string;
  W, H: Double;
  acr: Pcairo_t;
begin
  if Orientation in [poLandscape, poReverseLandscape] then begin
    s := '%%PageOrientation: Landscape';
    W := PaperHeight*ScaleY; //switch H, W
    H := PaperWidth*ScaleX;
  end else begin
    s := '%%PageOrientation: Portait';
    W := PaperWidth*ScaleX;
    H := PaperHeight*ScaleY;
  end;

  //Sizes are in Points, 72DPI (1pt = 1/72")
  if fStream<>nil then
    sf := cairo_ps_surface_create_for_stream(@WriteToStream, fStream, W, H)
  else
    sf := cairo_ps_surface_create(PChar(FOutputFileName), W, H);
  acr := cairo_create(sf);

  cairo_ps_surface_dsc_begin_setup(sf);
  cairo_ps_surface_dsc_comment(sf, PChar(s));

  if (fStream=nil) and not FileExists(FOutputFileName) then
  begin
    DebugLn('Error: unable to write cairo ps to "'+FOutputFileName+'"');
    DestroyCairoHandle;
    exit(0);
  end;

  //rotate and move
  case Orientation of
    poLandscape: begin
      cairo_translate(acr, 0, H);
      cairo_rotate(acr, -PI/2);
    end;
    poReverseLandscape: begin
      cairo_translate(acr, W, 0);
      cairo_rotate(acr, PI/2);
    end;
    poReversePortrait: begin
      cairo_translate(acr, W, H);
      cairo_rotate(acr, PI);
    end;
  end;
  result := {%H-}HDC(acr);
end;

procedure TCairoPsCanvas.UpdatePageSize;
begin
  cairo_ps_surface_set_size(sf, PaperWidth*ScaleX, PaperHeight*ScaleY);
end;

constructor TCairoPngCanvas.Create(APrinter: TPrinter);
begin
  inherited Create(APrinter);
end;

{ TCairoSvgCanvas }

function TCairoSvgCanvas.CreateCairoHandle: HDC;
begin
  //Sizes are in Points, 72DPI (1pt = 1/72")
  sf := cairo_svg_surface_create(PChar(FOutputFileName), PaperWidth*ScaleX, PaperHeight*ScaleY);
  result := {%H-}HDC(cairo_create(sf));
end;


{ TCairoPngCanvas }

function TCairoPngCanvas.CreateCairoHandle: HDC;
var
  acr: Pcairo_t;
begin
  //I do not know how to retrieve DPI of cairo_image_surface
  //It looks like that Cairo uses same DPI as Screen, but how much is it in case of console app???
  //You must set Surface?DPI externally. For example:
  //c := TCairoPngCanvas.Create;
  //c.SurfaceXDPI := GetDeviceCaps(DC, LOGPIXELSX);
  //c.SurfaceYDPI := GetDeviceCaps(DC, LOGPIXELSY);
  sf := cairo_image_surface_create(CAIRO_FORMAT_ARGB32, PaperWidth, PaperHeight);
  acr := cairo_create(sf);
  cairo_scale(acr, 1/ScaleX, 1/ScaleY);
  result := {%H-}HDC(acr);
end;

procedure TCairoPngCanvas.DestroyCairoHandle;
begin
  cairo_surface_write_to_png(sf, PChar(FOutputFileName));
  inherited DestroyCairoHandle;
end;

end.

