unit uPdfViewer;
{$I ..\..\..\Lib\PDFiumVcl.inc}

interface

uses
{$IFDEF XE2+}
  Winapi.Windows,
  Winapi.Messages,
  System.SysUtils,
  System.Variants,
  System.Classes,
  Vcl.Graphics,
  Vcl.Controls,
  Vcl.Forms,
  Vcl.Dialogs,
  Vcl.Buttons,
  Vcl.StdCtrls,
  Vcl.ExtCtrls,
  Vcl.XPMan,
  Vcl.ExtDlgs,
  Vcl.ImgList,
  Vcl.ComCtrls,
  Vcl.ToolWin,
  Vcl.Menus,
{$ELSE}
  Windows,
  Messages,
  SysUtils,
  Variants,
  Classes,
  Graphics,
  Controls,
  Forms,
  Dialogs,
  Buttons,
  StdCtrls,
  ExtCtrls,
  XPMan,
  ExtDlgs,
  ImgList,
  ComCtrls,
  ToolWin,
  Menus,
{$ENDIF}
  PDFium;

type
  TFormMain= class(TForm)
    Pdf: TPdf;
    OpenDialog: TOpenDialog;
    PdfView: TPdfView;
    SavePictureDialog: TSavePictureDialog;
    PrintDialog: TPrintDialog;
    TreeViewBookmarks: TTreeView;
    Splitter: TSplitter;
    ProgressBar: TProgressBar;
    PanelCancel: TPanel;
    PanelButtons: TPanel;
    SpeedButtonOpenPdf: TSpeedButton;
    SpeedButtonShowInfo: TSpeedButton;
    SpeedButtonPreviousPage: TSpeedButton;
    SpeedButtonNextPage: TSpeedButton;
    SpeedButtonSaveAs: TSpeedButton;
    SpeedButtonFirstPage: TSpeedButton;
    SpeedButtonLastPage: TSpeedButton;
    SpeedButtonPrint: TSpeedButton;
    Bevel1: TBevel;
    SpeedButtonPageNumber: TSpeedButton;
    Bevel2: TBevel;
    Bevel3: TBevel;
    SpeedButtonZoomOut: TSpeedButton;
    SpeedButtonZoomIn: TSpeedButton;
    SpeedButtonRotateLeft: TSpeedButton;
    SpeedButtonRotateRight: TSpeedButton;
    Bevel4: TBevel;
    Bevel5: TBevel;
    SpeedButtonSettings: TSpeedButton;
    Bevel6: TBevel;
    SpeedButtonSearch: TSpeedButton;
    ComboBoxZoom: TComboBox;
    EditSearch: TEdit;
    Panel: TPanel;
    ButtonCancel: TButton;
    SaveDialog: TSaveDialog;
    SpeedButtonSaveBitmap: TSpeedButton;
    PopupMenuPdfView: TPopupMenu;
    MenuItemCopy: TMenuItem;
    MenuItemSelectAll: TMenuItem;
    MenuItem1: TMenuItem; // separator
    StatusBar: TStatusBar;
    TimerDelayedSelection: TTimer;
    procedure SpeedButtonOpenPdfClick(Sender: TObject);
    procedure PdfViewPageChange(Sender: TObject);
    procedure SpeedButtonShowInfoClick(Sender: TObject);
    procedure SpeedButtonShowTextClick(Sender: TObject);
    procedure SpeedButtonPreviousPageClick(Sender: TObject);
    procedure SpeedButtonNextPageClick(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure SpeedButtonSaveAsClick(Sender: TObject);
    procedure SpeedButtonFirstPageClick(Sender: TObject);
    procedure SpeedButtonLastPageClick(Sender: TObject);
    procedure SpeedButtonPrintClick(Sender: TObject);
    procedure SpeedButtonPageNumberClick(Sender: TObject);
    procedure SpeedButtonZoomOutClick(Sender: TObject);
    procedure SpeedButtonZoomInClick(Sender: TObject);
    procedure SplitterMoved(Sender: TObject);
    procedure SpeedButtonRotateLeftClick(Sender: TObject);
    procedure SpeedButtonRotateRightClick(Sender: TObject);
    procedure ComboBoxZoomChange(Sender: TObject);
    procedure PdfViewMouseDown(
      Sender: TObject;
      Button: TMouseButton;
      Shift : TShiftState;
      X, Y  : Integer);
    procedure PdfViewMouseUp(
      Sender: TObject;
      Button: TMouseButton;
      Shift : TShiftState;
      X, Y  : Integer);
    procedure PdfViewMouseMove(
      Sender: TObject;
      Shift : TShiftState;
      X, Y  : Integer);
    procedure PdfViewPaint(Sender: TObject);
    procedure PdfViewMouseWheel(
      Sender     : TObject;
      Shift      : TShiftState;
      WheelDelta : Integer;
      MousePos   : TPoint;
      var Handled: Boolean);
    procedure SpeedButtonSettingsClick(Sender: TObject);
    procedure FormKeyDown(
      Sender : TObject;
      var Key: Word;
      Shift  : TShiftState);
    procedure SpeedButtonSearchClick(Sender: TObject);
    procedure FormMouseWheel(
      Sender     : TObject;
      Shift      : TShiftState;
      WheelDelta : Integer;
      MousePos   : TPoint;
      var Handled: Boolean);
    procedure TreeViewBookmarksClick(Sender: TObject);
    procedure EditSearchChange(Sender: TObject);
    procedure ButtonCancelClick(Sender: TObject);
    procedure SpeedButtonSaveBitmapClick(Sender: TObject);
    procedure TreeViewBookmarksExpanding(
      Sender            : TObject;
      Node              : TTreeNode;
      var AllowExpansion: Boolean);
    procedure MenuItemCopyClick(Sender: TObject);
    procedure MenuItemSelectAllClick(Sender: TObject);
    procedure PopupMenuPdfViewPopup(Sender: TObject);
    procedure PdfViewDblClick(Sender: TObject);
  private
    { Private declarations }
    Selecting: Boolean;
    SelectionMode: Boolean; // True when in text selection mode
    SelectionStart: Integer;
    SelectionEnd: Integer;
    Cancel: Boolean;
    DisableBookmarks: Boolean;
    SearchStart: Integer;
    SearchEnd: Integer;
    FUpdatingZoom: Boolean; // Flag to prevent recursive DoZoom calls
    FTextSelecting: Boolean; // Flag to indicate text selection in progress

    FLogFile: TextFile; // Log file for debugging
    FLogInitialized: Boolean; // Flag to check if log is initialized
    procedure InitializeLog;
    procedure WriteToLog(const Msg: string);
    procedure DoZoom;
    procedure AddBookmarks(
      Node     : TTreeNode;
      Bookmarks: TBookmarks);
    function AddChildren(Node: TTreeNode): Boolean;
    procedure UpdateStatusBar(const AText: string);
    function FindBookmark(PageNumber: Integer): TTreeNode;
    function GetPreciseCharacterIndex(X, Y: Integer): Integer;
  public
    { Public declarations }
  end;

var
  FormMain: TFormMain;

implementation

uses
{$IFDEF XE2+}
  Vcl.Printers,
  Vcl.ClipBrd,
  System.Types,
  Winapi.ShellApi,
{$ELSE}
  Printers,
  ClipBrd,
  Types,
  ShellApi,
{$ENDIF}
  uSettings;

{$R *.dfm}

procedure Open(const Url: string);
begin
  ShellExecute(0, 'open', PChar(Url), '', '', SW_SHOWNORMAL);
end;

var
  BookmarkPages: array of TTreeNode;

procedure TFormMain.SpeedButtonOpenPdfClick(Sender: TObject);
var
  Password: string;
begin
  if OpenDialog.Execute
  then
  begin
    Selecting:= False;
    SelectionMode:= False;
    SelectionStart:= - 1;
    SelectionEnd:= - 1;
    Caption:= 'PDFium viewer'; // Reset title

    SpeedButtonSaveAs.Enabled:= False;
    SpeedButtonSaveBitmap.Enabled:= False;
    SpeedButtonPrint.Enabled:= False;
    SpeedButtonShowInfo.Enabled:= False;
    SpeedButtonFirstPage.Enabled:= False;
    SpeedButtonPreviousPage.Enabled:= False;
    SpeedButtonPageNumber.Enabled:= False;
    SpeedButtonPageNumber.Caption:= '';
    SpeedButtonNextPage.Enabled:= False;
    SpeedButtonLastPage.Enabled:= False;
    SpeedButtonZoomIn.Enabled:= False;
    SpeedButtonZoomOut.Enabled:= False;
    ComboBoxZoom.Enabled:= False;
    SpeedButtonRotateLeft.Enabled:= False;
    SpeedButtonRotateRight.Enabled:= False;
    SpeedButtonSettings.Enabled:= False;
    SpeedButtonSearch.Enabled:= False;
    EditSearch.Enabled:= False;
    TreeViewBookmarks.Visible:= False;
    Splitter.Visible:= False;
    Pdf.Active:= False;
    Pdf.FileName:= OpenDialog.FileName;
    Pdf.Password:= '';
    Pdf.PageNumber:= 0;
    PdfView.PageNumber:= 1;
    BookmarkPages:= nil;
    try
      PdfView.Active:= True;
    except
      on Error: EPdfError do
        if Error.Message= 'Password required or incorrect password'
        then
        begin
          if not InputQuery('Enter Password', 'Password: ', Password)
          then
            raise;
          Pdf.Password:= Password;
          PdfView.Active:= True;
        end
        else
          raise;
    end;

    TreeViewBookmarks.Items.Clear;
    SetLength(BookmarkPages, Pdf.PageCount);
    AddBookmarks(nil, Pdf.Bookmarks);
    if TreeViewBookmarks.Items.Count> 0
    then
    begin
      Splitter.Visible:= True;
      TreeViewBookmarks.Visible:= True;
    end;

    DoZoom;
  end;
end;

procedure TFormMain.AddBookmarks(
  Node     : TTreeNode;
  Bookmarks: TBookmarks);
var
  ChildNode: TTreeNode;
  I: Integer;
  PageIndex: Integer;
begin
  TreeViewBookmarks.Items.BeginUpdate;
  try
    for I:= 0 to Length(Bookmarks)- 1 do
    begin
      ChildNode:= TreeViewBookmarks.Items.AddChildObject(Node, Bookmarks[I].Title, Bookmarks[I].Handle);
      // Check if PageNumber is within valid range before accessing array
      PageIndex:= Bookmarks[I].PageNumber- 1;
      if (PageIndex>= 0)and (PageIndex< Length(BookmarkPages))
      then
      begin
        if BookmarkPages[PageIndex]= nil
        then
          BookmarkPages[PageIndex]:= ChildNode;
      end;
      ChildNode.HasChildren:= Pdf.HasBookmarkChildren[Bookmarks[I]];
    end;
  finally
    TreeViewBookmarks.Items.EndUpdate;
  end;
end;

function TFormMain.AddChildren(Node: TTreeNode): Boolean;
var
  Bookmark: TBookmark;
  I: Integer;
begin
  Result:= False;
  if Node.HasChildren and (Node.Count= 0)
  then
  begin
    TreeViewBookmarks.Items.BeginUpdate;
    Screen.Cursor:= crHourGlass;
    try
      Bookmark.Handle:= Node.Data;
      AddBookmarks(Node, Pdf.BookmarkChildren[Bookmark]);
      Result:= True;

      for I:= 0 to Node.Count- 1 do
        if I> 0
        then
          Break// optimization
        else
          AddChildren(Node.Item[I]);
    finally
      Screen.Cursor:= crDefault;
      TreeViewBookmarks.Items.EndUpdate;
    end;
  end
end;

function TFormMain.FindBookmark(PageNumber: Integer): TTreeNode;
var
  Index: Integer;
begin
  if (BookmarkPages= nil)or (PageNumber< 1)
  then
    Result:= nil
  else
  begin
    Index:= PageNumber- 1;
    // Ensure Index is within bounds
    if Index>= Length(BookmarkPages)
    then
      Index:= Length(BookmarkPages)- 1;
      
    while (Index> 0)and (BookmarkPages[Index]= nil) do
      Dec(Index);

    Result:= BookmarkPages[Index];

    if Result<> nil
    then
      if AddChildren(Result)
      then
        Result:= FindBookmark(PageNumber);
  end;
end;

function TFormMain.GetPreciseCharacterIndex(X, Y: Integer): Integer;
var
  BaseTolerance: Single;
  ZoomFactor: Single;
  AdjustedTolerance: Single;
  CharIndex: Integer;
begin
  Result:= - 1;

  if not PdfView.Active
  then
    Exit;

  // Calculate dynamic tolerance based on zoom level
  ZoomFactor:= PdfView.Zoom;
  BaseTolerance:= 5.0; // Increased base tolerance

  // Adjust tolerance inversely with zoom - higher zoom needs smaller tolerance
  if ZoomFactor> 1.0
  then
    AdjustedTolerance:= BaseTolerance/ ZoomFactor
  else if ZoomFactor< 1.0
  then
    AdjustedTolerance:= BaseTolerance* (2.0- ZoomFactor)
    // Increase tolerance for smaller zoom
  else
    AdjustedTolerance:= BaseTolerance;

  // Ensure minimum tolerance of 2.0 pixels
  if AdjustedTolerance< 2.0
  then
    AdjustedTolerance:= 2.0;

  // Try to find character with adjusted tolerance
  CharIndex:= PdfView.CharacterIndexAtPos(X, Y, AdjustedTolerance, AdjustedTolerance);

  // If not found with tight tolerance, try with slightly larger tolerance
  if CharIndex< 0
  then
  begin
    AdjustedTolerance:= AdjustedTolerance+ 5.0;
    CharIndex:= PdfView.CharacterIndexAtPos(X, Y, AdjustedTolerance, AdjustedTolerance);
  end;

  // If still not found, try with even larger tolerance for edge cases
  if CharIndex< 0
  then
  begin
    AdjustedTolerance:= AdjustedTolerance+ 10.0;
    CharIndex:= PdfView.CharacterIndexAtPos(X, Y, AdjustedTolerance, AdjustedTolerance);
  end;

  Result:= CharIndex;
end;

procedure TFormMain.PdfViewPageChange(Sender: TObject);
var
  Node: TTreeNode;
begin
  if PdfView.Active
  then
  begin
    SpeedButtonSaveAs.Enabled:= True;
    SpeedButtonSaveBitmap.Enabled:= True;
    SpeedButtonPrint.Enabled:= True;
    SpeedButtonShowInfo.Enabled:= True;
    SpeedButtonFirstPage.Enabled:= PdfView.PageNumber> 1;
    SpeedButtonPreviousPage.Enabled:= PdfView.PageNumber> 1;
    SpeedButtonPageNumber.Enabled:= PdfView.PageCount> 1;
    SpeedButtonPageNumber.Caption:= IntToStr(PdfView.PageNumber)+ ' of '+ IntToStr(PdfView.PageCount);
    SpeedButtonNextPage.Enabled:= PdfView.PageNumber< PdfView.PageCount;
    SpeedButtonLastPage.Enabled:= PdfView.PageNumber< PdfView.PageCount;
    SpeedButtonZoomIn.Enabled:= True;
    SpeedButtonZoomOut.Enabled:= True;
    ComboBoxZoom.Enabled:= True;
    SpeedButtonRotateLeft.Enabled:= True;
    SpeedButtonRotateRight.Enabled:= True;
    SpeedButtonSettings.Enabled:= True;
    SpeedButtonSearch.Enabled:= EditSearch.Text<> '';
    EditSearch.Enabled:= True;
  end
  else
  begin
    SpeedButtonSaveAs.Enabled:= False;
    SpeedButtonSaveBitmap.Enabled:= False;
    SpeedButtonPrint.Enabled:= False;
    SpeedButtonShowInfo.Enabled:= False;
    SpeedButtonFirstPage.Enabled:= False;
    SpeedButtonPreviousPage.Enabled:= False;
    SpeedButtonPageNumber.Enabled:= False;
    SpeedButtonPageNumber.Caption:= '';
    SpeedButtonNextPage.Enabled:= False;
    SpeedButtonLastPage.Enabled:= False;
    SpeedButtonZoomIn.Enabled:= False;
    SpeedButtonZoomOut.Enabled:= False;
    ComboBoxZoom.Enabled:= False;
    SpeedButtonRotateLeft.Enabled:= False;
    SpeedButtonRotateRight.Enabled:= False;
    SpeedButtonSettings.Enabled:= False;
    SpeedButtonSearch.Enabled:= False;
    EditSearch.Enabled:= False;
  end;

  Selecting:= False;
  SelectionStart:= - 1;
  SelectionEnd:= - 1;

  SearchStart:= - 1;
  SearchEnd:= - 1;

  if not FUpdatingZoom
  then
  begin
    DoZoom;
    PdfView.Invalidate;
  end;

  // update bookmark
  if not DisableBookmarks
  then
  begin
    Node:= FindBookmark(PdfView.PageNumber);
    if Node<> nil
    then
      TreeViewBookmarks.Selected:= Node
    else
      TreeViewBookmarks.Selected:= nil;
  end;
end;

procedure TFormMain.SpeedButtonShowInfoClick(Sender: TObject);
const
  NewLine= #13#10;
begin
  ShowMessage('Author: '+ Pdf.Author+ NewLine+ 'Creator: '+ Pdf.Creator+ NewLine+ 'Keywords: '+ Pdf.Keywords+ NewLine+ 'Producer: '+ Pdf.Producer+ NewLine+
    'Subject: '+ Pdf.Subject+ NewLine+ 'Title: '+ Pdf.Title+ NewLine+ 'Creation date: '+ Pdf.CreationDate+ NewLine+ 'Modified date: '+ Pdf.ModifiedDate);
end;

procedure TFormMain.SpeedButtonShowTextClick(Sender: TObject);
begin
  ShowMessage(PdfView.Text);
end;

procedure TFormMain.SpeedButtonFirstPageClick(Sender: TObject);
begin
  PdfView.PageNumber:= 1;
end;

procedure TFormMain.SpeedButtonPreviousPageClick(Sender: TObject);
begin
  PdfView.PageNumber:= PdfView.PageNumber- 1;
end;

procedure TFormMain.SpeedButtonNextPageClick(Sender: TObject);
begin
  PdfView.PageNumber:= PdfView.PageNumber+ 1;
end;

procedure TFormMain.SpeedButtonLastPageClick(Sender: TObject);
begin
  PdfView.PageNumber:= PdfView.PageCount;
end;

procedure TFormMain.DoZoom;
var
  Zoom: Double;
begin
  if PdfView.Active and not FUpdatingZoom
  then
  begin
    FUpdatingZoom:= True;
    try
      // Determine target zoom based on selection
      Zoom:= 1.0;
      case ComboBoxZoom.ItemIndex of
      0:
        Zoom:= 0.1;
      1:
        Zoom:= 0.25;
      2:
        Zoom:= 0.5;
      3:
        Zoom:= 0.75;
      4:
        Zoom:= 1.0;
      5:
        Zoom:= 1.25;
      6:
        Zoom:= 1.5;
      7:
        Zoom:= 2.0;
      8:
        Zoom:= 4.0;
      9:
        Zoom:= 8.0;
      10:
        Zoom:= PdfView.ActualSizeZoom[PdfView.PageNumber];
      11:
        Zoom:= PdfView.PageZoom[PdfView.PageNumber]; // Fit Page
      12:
        Zoom:= PdfView.PageWidthZoom[PdfView.PageNumber]; // Fit Width
      end;

      // Apply zoom to PdfView (SetZoom will Invalidate)
      PdfView.Zoom:= Zoom;

      // Update zoom buttons state
      SpeedButtonZoomIn.Enabled:= ComboBoxZoom.ItemIndex< 9;
      SpeedButtonZoomOut.Enabled:= (ComboBoxZoom.ItemIndex> 0)and (ComboBoxZoom.ItemIndex< 10);
    finally
      FUpdatingZoom:= False;
    end;
  end;
end;

procedure TFormMain.FormResize(Sender: TObject);
begin
  if not FUpdatingZoom and not FTextSelecting
  then
    DoZoom;
end;

procedure TFormMain.SpeedButtonSaveAsClick(Sender: TObject);
begin
  SaveDialog.FileName:= 'document.pdf';
  if SaveDialog.Execute
  then
    Pdf.SaveAs(SaveDialog.FileName);
end;

procedure TFormMain.SpeedButtonSaveBitmapClick(Sender: TObject);
var
  Bitmap: TBitmap;
begin
  if SavePictureDialog.Execute
  then
  begin
    Bitmap:= PdfView.RenderPage(0, 0, PdfView.Width, PdfView.Height, PdfView.Rotation, PdfView.Options);
    try
      Bitmap.SaveToFile(SavePictureDialog.FileName);
    finally
      Bitmap.Free;
    end;
  end;
end;

procedure PrintBitmap(
  Printer: TPrinter;
  Bitmap : TBitmap);
var
  InfoHeaderSize, ImageSize: DWORD;
  InfoHeader: PBitmapInfo;
  Image: Pointer;
begin
  GetDIBSizes(Bitmap.Handle, InfoHeaderSize, ImageSize);

  InfoHeader:= AllocMem(InfoHeaderSize);
  try
    Image:= AllocMem(ImageSize);
    try
      GetDIB(Bitmap.Handle, 0, InfoHeader^, Image^);

      StretchDIBits(Printer.Canvas.Handle, 0, 0, Bitmap.Width, Bitmap.Height, 0, 0, Bitmap.Width, Bitmap.Height, Image, InfoHeader^, DIB_RGB_COLORS, SRCCOPY);
    finally
      FreeMem(Image);
    end;
  finally
    FreeMem(InfoHeader);
  end;
end;

procedure TFormMain.SpeedButtonPrintClick(Sender: TObject);
function GetPrintRotation(Rotation: TRotation): TRotation;
begin
  case Rotation of
  ro90:
    Result:= ro270;
  ro270:
    Result:= ro90;
else
  Result:= Rotation;
  end;
end;

var
  FromPage, ToPage, Page, Copy, CopyCount, CollateCopy, CollateCopyCount: Integer;
  FirstPage: Boolean;
  Bitmap: TBitmap;
begin
  if PdfView.Rotation in [ro0, ro180]
  then
    Printer.Orientation:= poPortrait
  else
    Printer.Orientation:= poLandscape;

  PrintDialog.MinPage:= 1;
  PrintDialog.MaxPage:= Pdf.PageCount;
  PrintDialog.Options:= [poPageNums];
  PrintDialog.FromPage:= PdfView.PageNumber;
  PrintDialog.ToPage:= PdfView.PageNumber;
  PrintDialog.PrintRange:= prPageNums;

  if PrintDialog.Execute
  then
  begin
    if PrintDialog.PrintRange= prPageNums
    then
    begin
      FromPage:= PrintDialog.FromPage;
      ToPage:= PrintDialog.ToPage;
    end
    else
    begin
      FromPage:= 1;
      ToPage:= Pdf.PageCount;
    end;

    if PrintDialog.Collate
    then
    begin
      CollateCopyCount:= PrintDialog.Copies;
      CopyCount:= 1;
    end
    else
    begin
      CollateCopyCount:= 1;
      CopyCount:= PrintDialog.Copies;
    end;

    if Pdf.Title<> ''
    then
      Printer.Title:= Pdf.Title
    else
      Printer.Title:= Caption;

    FirstPage:= True;
    Printer.BeginDoc;
    try
      Cancel:= False;
      ProgressBar.Max:= (ToPage- FromPage+ 1)* PrintDialog.Copies;
      ProgressBar.Position:= 0;
      PanelButtons.Visible:= False;
      PanelCancel.Visible:= True;

      for CollateCopy:= 1 to CollateCopyCount do
        for Page:= FromPage to ToPage do
          for Copy:= 1 to CopyCount do
          begin
            if FirstPage
            then
              FirstPage:= False
            else
              Printer.NewPage;

            ProgressBar.Position:= ProgressBar.Position+ 1;

            Pdf.PageNumber:= Page;
            if Pdf.FormFill
            then
            begin
              Bitmap:= PdfView.RenderPage(0, 0, Printer.PageWidth, Printer.PageHeight, GetPrintRotation(Pdf.PageRotation), [rePrinting]);
              try
                PrintBitmap(Printer, Bitmap);
              finally
                Bitmap.Free;
              end;
            end
            else
              Pdf.RenderPage(Printer.Handle, 0, 0, Printer.PageWidth, Printer.PageHeight, GetPrintRotation(Pdf.PageRotation), [rePrinting]);

            Application.ProcessMessages;
            if Cancel
            then
              Exit;
          end;
    finally
      PanelCancel.Visible:= False;
      PanelButtons.Visible:= True;
      Printer.EndDoc;
    end;
  end;
end;

procedure TFormMain.SpeedButtonPageNumberClick(Sender: TObject);
var
  PageNumber: string;
  NewPageNumber: Integer;
begin
  PageNumber:= IntToStr(PdfView.PageNumber);
  if InputQuery('Select page', 'Page number: ', PageNumber)
  then
  begin
    NewPageNumber:= StrToIntDef(PageNumber, PdfView.PageNumber);
    if (NewPageNumber> 0)and (NewPageNumber<= PdfView.PageCount)
    then
      PdfView.PageNumber:= NewPageNumber;
  end;
end;

procedure TFormMain.SpeedButtonZoomOutClick(Sender: TObject);
begin
  ComboBoxZoom.ItemIndex:= ComboBoxZoom.ItemIndex- 1;
  DoZoom;
end;

procedure TFormMain.SpeedButtonZoomInClick(Sender: TObject);
begin
  ComboBoxZoom.ItemIndex:= ComboBoxZoom.ItemIndex+ 1;
  DoZoom;
end;

procedure TFormMain.SplitterMoved(Sender: TObject);
begin
  if not FUpdatingZoom
  then
    DoZoom;
end;

procedure TFormMain.SpeedButtonRotateLeftClick(Sender: TObject);
begin
  case PdfView.Rotation of
  ro0:
    PdfView.Rotation:= ro270;
  ro90:
    PdfView.Rotation:= ro0;
  ro180:
    PdfView.Rotation:= ro90;
  ro270:
    PdfView.Rotation:= ro180
  end;
  DoZoom;
end;

procedure TFormMain.SpeedButtonRotateRightClick(Sender: TObject);
begin
  case PdfView.Rotation of
  ro0:
    PdfView.Rotation:= ro90;
  ro90:
    PdfView.Rotation:= ro180;
  ro180:
    PdfView.Rotation:= ro270;
  ro270:
    PdfView.Rotation:= ro0;
  end;
  DoZoom;
end;

procedure TFormMain.ComboBoxZoomChange(Sender: TObject);
begin
  DoZoom;
  PdfView.SetFocus;
end;

procedure TFormMain.PdfViewPaint(Sender: TObject);
begin
  // Only paint selections if PdfView is active
  if PdfView.Active
  then
  begin
    PdfView.PaintSelection(SelectionStart, SelectionEnd, $F0C080);
    PdfView.PaintSelection(SearchStart, SearchEnd, $00E000);
  end;
end;

procedure TFormMain.PdfViewMouseWheel(
  Sender     : TObject;
  Shift      : TShiftState;
  WheelDelta : Integer;
  MousePos   : TPoint;
  var Handled: Boolean);
begin
  // Disable scrolling when in text selection mode to prevent interference
  if SelectionMode
  then
  begin
    Handled:= True; // Block scrolling in selection mode
    Exit;
  end;

  // Allow normal scrolling in non-selection mode
  Handled:= False;
end;

procedure TFormMain.SpeedButtonSettingsClick(Sender: TObject);
var
  Options: TRenderOptions;
begin
  with TFormSettings.Create(nil) do
    try
      CheckListBox.Checked[0]:= reAnnotations in PdfView.Options;
      CheckListBox.Checked[1]:= reLcd in PdfView.Options;
      CheckListBox.Checked[2]:= not (reNoNativeText in PdfView.Options);
      CheckListBox.Checked[3]:= reGrayscale in PdfView.Options;
      CheckListBox.Checked[4]:= reLimitCache in PdfView.Options;
      CheckListBox.Checked[5]:= reHalftone in PdfView.Options;
      CheckListBox.Checked[6]:= rePrinting in PdfView.Options;
      CheckListBox.Checked[7]:= reReverseByteOrder in PdfView.Options;
      CheckListBox.Checked[8]:= not (reNoSmoothText in PdfView.Options);
      CheckListBox.Checked[9]:= not (reNoSmoothImage in PdfView.Options);
      CheckListBox.Checked[10]:= not (reNoSmoothPath in PdfView.Options);
      if ShowModal= mrOk
      then
      begin
        Options:= [];
        if CheckListBox.Checked[0]
        then
          Options:= Options+ [reAnnotations];
        if CheckListBox.Checked[1]
        then
          Options:= Options+ [reLcd];
        if not CheckListBox.Checked[2]
        then
          Options:= Options+ [reNoNativeText];
        if CheckListBox.Checked[3]
        then
          Options:= Options+ [reGrayscale];
        if CheckListBox.Checked[4]
        then
          Options:= Options+ [reLimitCache];
        if CheckListBox.Checked[5]
        then
          Options:= Options+ [reHalftone];
        if CheckListBox.Checked[6]
        then
          Options:= Options+ [rePrinting];
        if CheckListBox.Checked[7]
        then
          Options:= Options+ [reReverseByteOrder];
        if not CheckListBox.Checked[8]
        then
          Options:= Options+ [reNoSmoothText];
        if not CheckListBox.Checked[9]
        then
          Options:= Options+ [reNoSmoothImage];
        if not CheckListBox.Checked[10]
        then
          Options:= Options+ [reNoSmoothPath];
        PdfView.Options:= Options;
      end;
    finally
      Free;
    end;
end;

procedure TFormMain.FormKeyDown(
  Sender : TObject;
  var Key: Word;
  Shift  : TShiftState);
begin
  if Key in [VK_LEFT, VK_RIGHT, VK_HOME, VK_END]
  then
    if EditSearch.Focused
    then
      Exit;

  case Key of
  VK_ESCAPE:
    begin
      // Exit selection mode on Escape
      if SelectionMode
      then
      begin
        SelectionMode:= False;
        Selecting:= False;
        SelectionStart:= - 1;
        SelectionEnd:= - 1;
        if PdfView.Active
        then
          PdfView.Invalidate;
        Caption:= 'PDFium viewer'; // Reset title
        Key:= 0;
      end;
    end;

  VK_PRIOR, VK_UP, VK_LEFT:
    begin
      // Disable navigation in selection mode to prevent interference
      if not SelectionMode and SpeedButtonPreviousPage.Enabled
      then
        SpeedButtonPreviousPage.Click;
      Key:= 0;
    end;

  VK_NEXT, VK_DOWN, VK_RIGHT:
    begin
      // Disable navigation in selection mode to prevent interference
      if not SelectionMode and SpeedButtonNextPage.Enabled
      then
        SpeedButtonNextPage.Click;
      Key:= 0;
    end;

  VK_HOME:
    begin
      // Disable navigation in selection mode to prevent interference
      if not SelectionMode and SpeedButtonFirstPage.Enabled
      then
        SpeedButtonFirstPage.Click;
      Key:= 0;
    end;

  VK_END:
    begin
      // Disable navigation in selection mode to prevent interference
      if not SelectionMode and SpeedButtonLastPage.Enabled
      then
        SpeedButtonLastPage.Click;
      Key:= 0;
    end;
  end;
end;

procedure TFormMain.FormMouseWheel(
  Sender     : TObject;
  Shift      : TShiftState;
  WheelDelta : Integer;
  MousePos   : TPoint;
  var Handled: Boolean);
begin
  // Disable scrolling when in text selection mode to prevent interference
  if SelectionMode
  then
  begin
    Handled:= True; // Block scrolling in selection mode
    Exit;
  end;

  // Mouse wheel scrolling is now handled by the PdfView control itself
  // No need for ScrollBox scrolling
  Handled:= False; // Let the PdfView handle the mouse wheel
end;

procedure TFormMain.TreeViewBookmarksClick(Sender: TObject);
var
  Bookmark: TBookmark;
  Node: TTreeNode;
begin
  if PdfView.Active
  then
  begin
    Node:= TreeViewBookmarks.Selected;
    if Node<> nil
    then
    begin
      Bookmark:= Pdf.Bookmark[Node.Text];
      if (Bookmark.PageNumber>= 1)and (Bookmark.PageNumber<= PdfView.PageCount)
      then
        try
          DisableBookmarks:= True;
          PdfView.PageNumber:= Bookmark.PageNumber;
        finally
          DisableBookmarks:= False;
        end;
    end;
  end;

  PdfView.SetFocus;
end;

procedure TFormMain.EditSearchChange(Sender: TObject);
begin
  if SearchStart<> - 1
  then
  begin
    SearchStart:= - 1;
    SearchEnd:= - 1;
    if PdfView.Active
    then
      PdfView.Repaint;
  end;
  SpeedButtonSearch.Enabled:= EditSearch.Text<> '';
end;

procedure TFormMain.SpeedButtonSearchClick(Sender: TObject);
var
  FoundIndex: Integer;
begin
  if EditSearch.Text<> ''
  then
  begin
    Cancel:= False;
    ProgressBar.Max:= Pdf.PageCount- PdfView.PageNumber;
    ProgressBar.Position:= 0;
    PanelButtons.Visible:= False;
    PanelCancel.Visible:= True;

    try
      Pdf.PageNumber:= PdfView.PageNumber;
      if SearchStart= - 1
      then
        FoundIndex:= Pdf.FindFirst(EditSearch.Text, [])
      else
        FoundIndex:= Pdf.FindNext;

      SearchStart:= - 1;
      SearchEnd:= - 1;

      while (FoundIndex= - 1)and (Pdf.PageNumber< Pdf.PageCount) do
      begin
        ProgressBar.Position:= ProgressBar.Position+ 1;
        Application.ProcessMessages;
        if Cancel
        then
          Break;

        Pdf.PageNumber:= Pdf.PageNumber+ 1;
        FoundIndex:= Pdf.FindFirst(EditSearch.Text, [])
      end;

      if FoundIndex<> - 1
      then
      begin
        PdfView.PageNumber:= Pdf.PageNumber;
        SearchStart:= FoundIndex;
        SearchEnd:= FoundIndex+ Length(EditSearch.Text)- 1;
        if PdfView.Active
        then
          PdfView.Invalidate; // repaint
      end
      else
      begin
        if PdfView.Active
        then
          PdfView.Invalidate; // repaint
        if not Cancel
        then
          ShowMessage('Text not found');
      end;
    finally
      PanelCancel.Visible:= False;
      PanelButtons.Visible:= True;
    end;
  end
end;

procedure TFormMain.ButtonCancelClick(Sender: TObject);
begin
  Cancel:= True;
end;

procedure TFormMain.TreeViewBookmarksExpanding(
  Sender            : TObject;
  Node              : TTreeNode;
  var AllowExpansion: Boolean);
var
  Bookmark: TBookmark;
begin
  if Node.Count= 0
  then
  begin
    Bookmark.Handle:= Node.Data;
    AddBookmarks(Node, Pdf.BookmarkChildren[Bookmark]);
  end;
end;

procedure TFormMain.MenuItemCopyClick(Sender: TObject);
var
  Text: WString;
begin
  if not PdfView.Active
  then
    Exit;

  if (SelectionStart>= 0)and (SelectionEnd>= 0)
  then
  begin
    if SelectionEnd< SelectionStart
    then
      Text:= PdfView.Text(SelectionEnd, SelectionStart- SelectionEnd+ 1)
    else
      Text:= PdfView.Text(SelectionStart, SelectionEnd- SelectionStart+ 1);

    Clipboard.AsText:= Text;
  end;
end;

procedure TFormMain.MenuItemSelectAllClick(Sender: TObject);
begin
  if PdfView.Active and (PdfView.CharacterCount> 0)
  then
  begin
    SelectionMode:= True;
    SelectionStart:= 0;
    SelectionEnd:= PdfView.CharacterCount- 1;
    PdfView.Invalidate;
  end;
end;

procedure TFormMain.PopupMenuPdfViewPopup(Sender: TObject);
begin
  // Enable/disable menu items based on current state
  MenuItemCopy.Enabled:= (SelectionStart>= 0)and (SelectionEnd>= 0);
  MenuItemSelectAll.Enabled:= PdfView.Active and (PdfView.CharacterCount> 0);
end;

procedure TFormMain.PdfViewMouseDown(
  Sender: TObject;
  Button: TMouseButton;
  Shift : TShiftState;
  X, Y  : Integer);
var
  SelectedIndex: Integer;
begin
  if not PdfView.Active
  then
    Exit;

  // Initialize log if not already done
  if not FLogInitialized
  then
    InitializeLog;

  // Only handle left mouse button in selection mode
  if (Button= mbLeft)and SelectionMode
  then
  begin
    WriteToLog('MouseDown in selection mode at X: '+ IntToStr(X)+ ', Y: '+ IntToStr(Y));

    // Set selection flags
    Selecting:= True;
    FTextSelecting:= True;

    // Get character at mouse position
    SelectedIndex:= GetPreciseCharacterIndex(X, Y);

    if SelectedIndex>= 0
    then
    begin
      SelectionStart:= SelectedIndex;
      SelectionEnd:= SelectedIndex;
      WriteToLog('Selection started at index: '+ IntToStr(SelectedIndex));
    end
    else
    begin
      // Set fallback position if character not found
      SelectionStart:= 0;
      SelectionEnd:= 0;
      WriteToLog('No character found, using fallback position 0');
    end;

    // Capture mouse to prevent scroll interference
    SetCapture(PdfView.Handle);

    UpdateStatusBar('Dragging to select text...');
    WriteToLog('SetCapture called, FTextSelecting set to True');
  end
  else if not SelectionMode
  then
  begin
    // Normal mode - set hand cursor for panning
    PdfView.Cursor:= crHandPoint;
  end;
end;

procedure TFormMain.PdfViewMouseMove(
  Sender: TObject;
  Shift : TShiftState;
  X, Y  : Integer);
var
  NewEndIndex: Integer;
begin
  if not PdfView.Active
  then
    Exit;

  // Handle text selection dragging
  if SelectionMode and Selecting and FTextSelecting and (ssLeft in Shift)
  then
  begin
    // Get character at current mouse position
    NewEndIndex:= GetPreciseCharacterIndex(X, Y);

    if NewEndIndex>= 0
    then
    begin
      SelectionEnd:= NewEndIndex;

      // Update display if not in zoom update
      if not FUpdatingZoom
      then
        PdfView.Invalidate;
    end;
  end
  else if SelectionMode
  then
  begin
    // In selection mode but not dragging - show I-beam cursor
    PdfView.Cursor:= crIBeam;
  end
  else
  begin
    // Normal mode - show appropriate cursor
    PdfView.Cursor:= crDefault;
  end;
end;

procedure TFormMain.PdfViewMouseUp(
  Sender: TObject;
  Button: TMouseButton;
  Shift : TShiftState;
  X, Y  : Integer);
begin
  if not PdfView.Active
  then
    Exit;

  // Handle left mouse button release in selection mode
  if (Button= mbLeft)and SelectionMode and Selecting
  then
  begin
    if not FLogInitialized
    then
      InitializeLog;

    WriteToLog('MouseUp in selection mode');

    // Release mouse capture
    ReleaseCapture;

    // End selection drag
    Selecting:= False;
    FTextSelecting:= False;

    // Update status based on selection
    if (SelectionStart>= 0)and (SelectionEnd>= 0)and (SelectionStart<> SelectionEnd)
    then
    begin
      UpdateStatusBar('Text selected - Right-click to copy, or press Esc to exit selection mode');
      WriteToLog('Text selection completed from '+ IntToStr(SelectionStart)+ ' to '+ IntToStr(SelectionEnd));
    end
    else
    begin
      UpdateStatusBar('Selection mode - Click and drag to select text, or press Esc to exit');
      WriteToLog('No text selected');
    end;
  end;
end;

procedure TFormMain.PdfViewDblClick(Sender: TObject);
var
  MousePos: TPoint;
  X, Y: Integer;
  SelectedIndex: Integer;
  WasMaximized: Boolean;
begin
  if not PdfView.Active
  then
    Exit;

  // Initialize log if not already done
  if not FLogInitialized
  then
    InitializeLog;

  // Log to confirm double-click event is triggered
  WriteToLog('Double-click event triggered');

  // Get current mouse position relative to PdfView
  MousePos:= PdfView.ScreenToClient(Mouse.CursorPos);
  X:= MousePos.X;
  Y:= MousePos.Y;

  // Log mouse position
  WriteToLog('Mouse Position - X: '+ IntToStr(X)+ ', Y: '+ IntToStr(Y));

  // Check if window was already maximized
  WasMaximized:= (WindowState= wsMaximized);

  // Auto-maximize window if not already maximized for better text selection
  if not WasMaximized
  then
  begin
    WindowState:= wsMaximized;
    // Allow some time for window to resize before proceeding
    Application.ProcessMessages;
    // After maximizing, recalculate mouse position
    MousePos:= PdfView.ScreenToClient(Mouse.CursorPos);
    X:= MousePos.X;
    Y:= MousePos.Y;
    WriteToLog('Window maximized, new Mouse Position - X: '+ IntToStr(X)+ ', Y: '+ IntToStr(Y));
  end;

  // Double click enters selection mode
  SelectionMode:= True;
  Selecting:= False;
  FTextSelecting:= False;

  // Log selection mode status
  WriteToLog('Entered Selection Mode');

  // Find character at double-click position to set initial cursor location
  SelectedIndex:= GetPreciseCharacterIndex(X, Y);

  // Log the result of GetPreciseCharacterIndex
  WriteToLog('Selected Index: '+ IntToStr(SelectedIndex));

  if SelectedIndex>= 0
  then
  begin
    // Set initial selection position at the double-clicked character
    SelectionStart:= SelectedIndex;
    SelectionEnd:= SelectedIndex;
    if not WasMaximized
    then
      UpdateStatusBar('Window maximized for optimal text selection - Click and drag to select text, or press Esc to exit')
    else
      UpdateStatusBar('Selection mode - Click and drag to select text, or press Esc to exit');
    WriteToLog('Selection started at index: '+ IntToStr(SelectedIndex));
  end
  else
  begin
    // No character found at click position, reset selection
    SelectionStart:= - 1;
    SelectionEnd:= - 1;
    if not WasMaximized
    then
      UpdateStatusBar('Window maximized for optimal text selection - Press Esc to exit')
    else
      UpdateStatusBar('Selection mode - Press Esc to exit');
    WriteToLog('No character found at click position');
  end;

  PdfView.Cursor:= crIBeam;
  PdfView.Invalidate;

  // Show a visual indicator that we're in selection mode
  Caption:= 'PDFium viewer - Selection Mode (Press ESC to exit)';

  // Log PDF view properties for debugging
  WriteToLog('PDF View Active: '+ BoolToStr(PdfView.Active, True));
  WriteToLog('PDF View Zoom: '+ FloatToStr(PdfView.Zoom));
  WriteToLog('PDF View Page Count: '+ IntToStr(PdfView.PageCount));
end;

procedure TFormMain.InitializeLog;
begin
  AssignFile(FLogFile, 'PDFViewerDebug.log');
  if FileExists('PDFViewerDebug.log')
  then
    Append(FLogFile)
  else
    Rewrite(FLogFile);
  FLogInitialized:= True;
  WriteToLog('Log initialized at '+ DateTimeToStr(Now));
end;

procedure TFormMain.WriteToLog(const Msg: string);
begin
  if FLogInitialized
  then
  begin
    Writeln(FLogFile, '['+ DateTimeToStr(Now)+ '] '+ Msg);
    Flush(FLogFile);
  end;
end;

procedure TFormMain.UpdateStatusBar(const AText: string);
begin
  if Assigned(StatusBar)
  then
    StatusBar.SimpleText:= AText;
end;

end.
