{$mode objfpc}{$H+}
unit uCICG4Presenters;
interface
uses
  // CC 20260310 LCL port: wrap Windows for cross-compiler safety
  {$IFDEF FPC}
  {$IFDEF MSWINDOWS}Windows,{$ENDIF}
  LCLType, LCLIntf,
  {$ELSE}
  Windows,
  {$ENDIF}
  Graphics, Classes, Dialogs, Forms, Sysutils,
  Globe4, TGClasses, TGObjects, TGSysUtils, TGXML, TGResource, TGPresenters;
type
  TCICG4PointPresenterType = (ppNone, ppFontSymbol, ppImage,
    ppDot, ppCircle, ppSquare, ppDiamond,
    ppTriangleUp, ppTriangleDown, ppTriangleLeft, ppTriangleRight,
    ppArrow);
  {*** CIC derived Point presenters ***}
  TCICG4PointPresenter = class(TPointPresenter)
  private
    FiSymbolOverride : integer;
    FiAngleDeg : SmallInt;
    FPointType : TCICG4PointPresenterType;
  protected
  public
    constructor Create( aParentGlobe : TCustomGlobe4; iID : integer ); override;
    procedure RenderObject( Globe : TCustomGlobe4; Geod : TGlobeObject; State : TGlobeObjectStateSet); override;
  published
    property PointType : TCICG4PointPresenterType read FPointType write FPointType;
    property AngleDeg : SmallInt read FiAngleDeg write FiAngleDeg;
  end;
implementation
{ TCICG4PointPresenter }
constructor TCICG4PointPresenter.Create( aParentGlobe : TCustomGlobe4; iID : integer );
begin
  inherited Create( aParentGlobe, iID );
end;
{-------------------------------------------------------------------------
 TCICG4PointPresenter.RenderObject
-------------------------------------------------------------------------}
{**
  @Param Globe Globe object to render onto.
  @Param Geod Object to Render.
}
procedure TCICG4PointPresenter.RenderObject( Globe : TCustomGlobe4;
  Geod : TGlobeObject; State : TGlobeObjectStateSet );
var
  DC : HDC;
  ptXY : TPoint;
  idx, iChain, iTmp, iTmp2 : integer;
  eSin, eCos : Extended;
  sTmp : string;
  bSelected, bPointRendered : Boolean;
  function SymbolExtent( const Text : string ) : TSize;
  begin
    Result.cx := 0;
    Result.cy := 0;
    GetTextExtentPoint( DC, @Text[1], 1, Result );
  end;
  procedure RenderPoint( const ptLL : TPointLL );
  var
    intDeg: Smallint;
    pntDir: TPoint;
  begin
    bPointRendered := True;
    if Globe.Projection.PointLLToXY( ptLL, ptXY ) then
    begin
      iTmp := Globe.GlobeCanvas.ScaleUnitsToDevice( PointSize, PointUnit );
      if MaxPointSize <> 0 then
        with Globe.GlobeCanvas do // Scale the MaxPointSize to the output canvas
          iTmp := MinVal( iTmp, Round( MaxPointSize * CanvasPixelsPerInch / ScreenPixelsPerInch));
      iTmp := iTmp div 2;
      case PointType of
        ppFontSymbol : { display a font symbol }
          begin
            PointFont.RenderAttribute( Globe.GlobeCanvas, bSelected );
            SinCos( ( -PointFont.FontAngle div 10 ) * LocalPI / 180, eSin, eCos );
            with SymbolExtent( Chr( FiSymbolIndex ) ) do
            begin
              ptXY.X := cx div 2;
              ptXY.Y := cy div 2;
            end;
            sTmp := Chr( FiSymbolIndex );
            // CC 20260310 LCL port: drop Windows. qualifier; LCLIntf exports TextOut
            TextOut( DC, gaPoints^[0].X - Trunc( ptXY.X * eCos - ptXY.Y * eSin ),
              gaPoints^[0].Y - Trunc( ptXY.X * eSin + ptXY.Y * eCos ), PChar( sTmp ), 1 );
          end;
        ppImage : { display a Bitmap or Icon }
          if FImage <> nil then
          begin
            with FImage do
              iTmp2 := Round( ( iTmp / Width ) * Height );
            // CC 20260310 ImageTransparent defaults to True in Lazarus TGlobe4 - manual mask handling may be redundant
            FImage.Graphic.Transparent := FImageTransparent;
            Globe.GlobeCanvas.StretchDraw(
              Rect( ptXY.X - iTmp, ptXY.Y - iTmp2, ptXY.X + iTmp, ptXY.Y + iTmp2 ), FImage.Graphic );
          end;
        ppDot : { display a single pixel }
          // CC 20260310 LCL port: use canvas Pixels property instead of Windows.SetPixel
          Globe.GlobeCanvas.Pixels[ptXY.X, ptXY.Y] := PointPen.PenColor;
        ppCircle : { display a circle }
          // CC 20260310 LCL port: drop Windows. qualifier; LCLIntf exports Ellipse
          Ellipse( DC, ptXY.X - iTmp, ptXY.Y - iTmp, ptXY.X + iTmp, ptXY.Y + iTmp );
        ppTriangleUp : { display an upwards pointing triangle }
          begin
            gaPoints^[0] := Point( ptXY.X + iTmp, ptXY.Y + iTmp );
            gaPoints^[1] := Point( ptXY.X, ptXY.Y - iTmp );
            gaPoints^[2] := Point( ptXY.X - iTmp, ptXY.Y + iTmp );
            // CC 20260310 LCL port: FPC Polygon requires pointer + count + winding; Delphi uses open-array ref
            {$IFDEF FPC}Polygon( DC, @gaPoints^[0], 3, False ){$ELSE}Windows.Polygon( DC, gaPoints^, 3 ){$ENDIF};
          end;
        ppTriangleDown : { display an downwards pointing triangle }
          begin
            gaPoints^[0] := Point( ptXY.X - iTmp, ptXY.Y - iTmp );
            gaPoints^[1] := Point( ptXY.X, ptXY.Y + iTmp );
            gaPoints^[2] := Point( ptXY.X + iTmp, ptXY.Y - iTmp );
            {$IFDEF FPC}Polygon( DC, @gaPoints^[0], 3, False ){$ELSE}Windows.Polygon( DC, gaPoints^, 3 ){$ENDIF};
          end;
        ppTriangleRight : { display an rightwards pointing triangle }
          begin
            gaPoints^[0] := Point( ptXY.X - iTmp, ptXY.Y - iTmp );
            gaPoints^[1] := Point( ptXY.X + iTmp, ptXY.Y );
            gaPoints^[2] := Point( ptXY.X - iTmp, ptXY.Y + iTmp );
            {$IFDEF FPC}Polygon( DC, @gaPoints^[0], 3, False ){$ELSE}Windows.Polygon( DC, gaPoints^, 3 ){$ENDIF};
          end;
        ppTriangleLeft : { display an leftwards pointing triangle }
          begin
            gaPoints^[0] := Point( ptXY.X + iTmp, ptXY.Y + iTmp );
            gaPoints^[1] := Point( ptXY.X - iTmp, ptXY.Y );
            gaPoints^[2] := Point( ptXY.X + iTmp, ptXY.Y - iTmp );
            {$IFDEF FPC}Polygon( DC, @gaPoints^[0], 3, False ){$ELSE}Windows.Polygon( DC, gaPoints^, 3 ){$ENDIF};
          end;
        ppSquare : { display a square }
          // CC 20260310 LCL port: drop Windows. qualifier; LCLIntf exports Rectangle
          Rectangle( DC, ptXY.X - iTmp, ptXY.Y - iTmp, ptXY.X + iTmp, ptXY.Y + iTmp );
        ppDiamond : { display a diamond }
          begin
            gaPoints^[0] := Point( ptXY.X, ptXY.Y - iTmp );
            gaPoints^[1] := Point( ptXY.X + iTmp, ptXY.Y );
            gaPoints^[2] := Point( ptXY.X, ptXY.Y + iTmp );
            gaPoints^[3] := Point( ptXY.X - iTmp, ptXY.Y );
            {$IFDEF FPC}Polygon( DC, @gaPoints^[0], 4, False ){$ELSE}Windows.Polygon( DC, gaPoints^, 4 ){$ENDIF};
          end;
	    ppArrow : { display heading arrow }
          begin
          intDeg := FiAngleDeg;
          pntDir := Point(ptXY.X + Round(iTmp * sin(intDeg * (Pi/180))), ptXY.Y - Round(iTmp * cos(intDeg * (Pi/180))));
      //        Renderer.DrawPoly([pntDir, Point(pntDir.X + Round(iSize * sin((intDeg-135) * (Pi/180))) div 2, pntDir.Y - Round(iSize * cos((intDeg-135) * (Pi/180))) div 2)], False);
      //        Renderer.DrawPoly([pntDir, Point(pntDir.X + Round(iSize * sin((intDeg+135) * (Pi/180))) div 2, pntDir.Y - Round(iSize * cos((intDeg+135) * (Pi/180))) div 2)], True);
          //Renderer.DrawPoly([Point(ptXY.X, ptXY.Y), pntDir], False); // shaft
          gaPoints^[0] := Point(ptXY.X, ptXY.Y);
          gaPoints^[1] := pntDir;
          // CC 20260310 LCL port: shaft drawn as 2-point polygon (line); FPC Polygon signature differs
          {$IFDEF FPC}Polygon( DC, @gaPoints^[0], 2, False ){$ELSE}Windows.Polygon(DC, gaPoints^, 2){$ENDIF};
          //Renderer.DrawPoly([pntDir, Point(pntDir.X + Round(iSize * sin((intDeg-155) * (Pi/180))) div 3, pntDir.Y - Round(iSize * cos((intDeg-155) * (Pi/180))) div 3),
          //           Point(pntDir.X + Round(iSize * sin((intDeg+155) * (Pi/180))) div 3, pntDir.Y - Round(iSize * cos((intDeg+155) * (Pi/180))) div 3), pntDir], True);
          gaPoints^[0] := Point(pntDir.X + Round(iTmp * sin((intDeg-155) * (Pi/180))) div 3, pntDir.Y - Round(iTmp * cos((intDeg-155) * (Pi/180))) div 3);
          gaPoints^[1] := pntDir;
          gaPoints^[2] := Point(pntDir.X + Round(iTmp * sin((intDeg+155) * (Pi/180))) div 3, pntDir.Y - Round(iTmp * cos((intDeg+155) * (Pi/180))) div 3);
          {$IFDEF FPC}Polygon( DC, @gaPoints^[0], 3, False ){$ELSE}Windows.Polygon(DC, gaPoints^, 3){$ENDIF};
          end;
      end;
    end;
  end;
begin
  bSelected := ( osSelected in Geod.ObjectState );
  PointPen.RenderAttribute( Globe.GlobeCanvas, bSelected );
  PointBrush.RenderAttribute( Globe.GlobeCanvas, bSelected );
  DC := Globe.GlobeCanvas.DC;
  bPointRendered := False;
  with Geod as TGeoDataObject do
  begin
    for iChain := 0 to Chains.Count - 1 do
      with Chains[iChain] do
        for idx := 0 to Count - 1 do
          RenderPoint( AsLL[idx] );
    // CC 20260313 only fall back to Centroid for true point objects (Chains.Count=0).
    // Chain-based objects (dot markers etc.) with an empty chain should render nothing.
    if not bPointRendered and (Chains.Count = 0) then
      RenderPoint( Centroid );
  end;
end;

initialization
  RegisterGlobeClasses([TCICG4PointPresenter]);
end.
