unit U_PDFView;

interface

uses
  U_Constants, U_BaseConstants, U_LNG, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, siComp, siLngLnk, gtScrollingPanel, gtPDFViewer,
  gtPDFClasses, gtCstPDFDoc, gtExPDFDoc, gtExProPDFDoc, gtPDFDoc, ExtCtrls,
  RzPanel, Buttons, gtViewerTypes, gtPDFConsts, jpeg, Math, RzButton,
  RzRadChk, ImgList;

type
  TF_PDFView = class(TForm)
    siLangLinked1: TsiLangLinked;
    RzPanel1: TRzPanel;
    RzPanel2: TRzPanel;
    gbOkCancel: TRzPanel;
    gtPDFDoc: TgtPDFDocument;
    gtPDFViewer: TgtPDFViewer;
    BtnOpenfile: TSpeedButton;
    BtnActive: TSpeedButton;
    BtnActualSize: TSpeedButton;
    btnFitWidth: TSpeedButton;
    BtnFitPage: TSpeedButton;
    BtnFirstPage: TSpeedButton;
    BtnPriorPage: TSpeedButton;
    lbGoToPage: TLabel;
    EdtGotoPage: TEdit;
    lbPageOf: TLabel;
    BtnNextPage: TSpeedButton;
    BtnLastPage: TSpeedButton;
    BtnZoomIn: TSpeedButton;
    edZoom: TEdit;
    BtnZoomOut: TSpeedButton;
    BtnPrint: TSpeedButton;
    lbPageCount: TLabel;
    TimerGotoPage: TTimer;
    btOK: TRzBitBtn;
    btCancel: TRzBitBtn;
    TimerDocOpenDialog: TTimer;
    cbConsiderScale: TRzCheckBox;
    BtnClkRotate: TSpeedButton;
    BtnResetRotate: TSpeedButton;
    ImageList1: TImageList;
    BtnUnClkRotate: TSpeedButton;
    procedure gtPDFViewerPageChange(Sender: TgtPDFViewer;
      const CurrentPageIndex: TPageIndex);
    procedure BtnOpenfileClick(Sender: TObject);
    procedure BtnActiveClick(Sender: TObject);
    procedure BtnFirstPageClick(Sender: TObject);
    procedure BtnActualSizeClick(Sender: TObject);
    procedure btnFitWidthClick(Sender: TObject);
    procedure BtnFitPageClick(Sender: TObject);
    procedure BtnPriorPageClick(Sender: TObject);
    procedure EdtGotoPageKeyPress(Sender: TObject; var Key: Char);
    procedure TimerGotoPageTimer(Sender: TObject);
    procedure BtnNextPageClick(Sender: TObject);
    procedure BtnLastPageClick(Sender: TObject);
    procedure BtnZoomInClick(Sender: TObject);
    procedure edZoomChange(Sender: TObject);
    procedure edZoomExit(Sender: TObject);
    procedure edZoomKeyPress(Sender: TObject; var Key: Char);
    procedure BtnZoomOutClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure gbOkCancelResize(Sender: TObject);
    procedure TimerDocOpenDialogTimer(Sender: TObject);
    procedure BtnPrintClick(Sender: TObject);
    procedure gtPDFDocPassword(Sender: TObject; var Password: String;
      var Continue: Boolean; const Tries: Integer);
    procedure FormCreate(Sender: TObject);
    procedure BtnClkRotateClick(Sender: TObject);
    procedure BtnResetRotateClick(Sender: TObject);
    procedure BtnUnClkRotateClick(Sender: TObject);

  private
    FOpenDialogOnShow: Boolean;
    FActiveViewAfterOpen: Boolean;
    FLastPassTries: Integer;
    FPassAllTries: Integer;
    FFile: String;

    procedure DefineRatateGlyph;
    function GetRotationAngle: Integer;
    function UpdateButton: string;
    procedure UpdateStatusBar;

    function OpenFileDialog(aActiveView: Boolean): Boolean;
    procedure LoadPDF(const aFile: string);
    procedure PrepareForm;
  public
    function GetDocBitmap(const aTitle: String; aFile: String = ''): TBitmap;
    function GetDocJpeg(const aTitle: String): TJPEGImage;
  end;

  function CreateFPDFView: TF_PDFView;

const
  ActiveBtnHint: array[False..True] of string = (cPDFView_Msg2, cPDFView_Msg1);
  ZoomIncrement = 25;

var
  F_PDFView: TF_PDFView;

implementation

Uses U_BaseCommon, U_Common, USCS_Main; 

{$R *.dfm}


function CreateFPDFView: TF_PDFView;
begin
  if F_PDFView = nil then
  begin
    F_PDFView := TF_PDFView.Create(Application);

    FSCS_Main.SetCursors; //11.07.2013  - восстановить курсоры, которые этот компонент для PDF их херит
  end;
  Result := F_PDFView;
end;

{ TF_PDFView }

function TF_PDFView.OpenFileDialog(aActiveView: Boolean): Boolean;
var
  OpenDialog: TOpenDialog;
begin
  Result := false;
  OpenDialog := TOpenDialog.Create(nil);
  try
    OpenDialog.Title := cPDFView_Msg3+'...';
    OpenDialog.DefaultExt := '*.'+enPdf;
    OpenDialog.Filter := cProgressExp_Msg9_1 +'|'+
                         GetDialogFilter(exdAll, '*');
    OpenDialog.Options := OpenDialog.Options - [ofNoChangeDir] + [ofOverwritePrompt];
    if OpenDialog.Execute then
    begin
      Result := True;
      LoadPDF(OpenDialog.FileName);
    end;
  finally
    FreeAndNil(OpenDialog);
  end;
end;

procedure TF_PDFView.LoadPDF(const aFile: string);
begin

  if gtPDFDoc.IsLoaded then begin
    gtPDFDoc.Reset;
  end;
  
  FLastPassTries := FPassAllTries + 1; // Запоминаем номер последней следующей попытки проверки пароля
  gtPDFViewer.Active := false;
  gtPDFDoc.Filename := aFile;
  if gtPDFDoc.IsLoaded then
  begin
    // 19/07/2017 -- если 200 - пользователь не утруждается усеньшить масштаб, в результете чего при
    // загрузке 5-6 листов получаем OUt OF MEMORY
    //gtPDFViewer.Zoom := 200;
    gtPDFViewer.Zoom := 100;
    //
    BtnPrint.Enabled := True;
    gtPDFViewer.PageNo := 1;
    gtPDFViewer.Active := False;
    gtPDFViewer.RotationAngle := ra0;
    gtPDFViewer.Active := True; //aActiveView;

    UpdateButton;
    UpdateStatusBar;
    EdtGotoPage.SetFocus;
    DefineRatateGlyph;
    //LBmp := TBitmap.Create;
    //ImageList1.GetBitmap(0, LBmp);
    //BtnClkRotate.Glyph.Assign(LBmp);
    //LBmp.Free;
  end;
end;

function TF_PDFView.GetDocBitmap(const aTitle: String; aFile: String = ''): TBitmap;
var
  pSize: TgtPageSize;
  dpi: Integer;
  //Rotation: Integer;
  Rotation: Double;
  aImageProperties: TgtImageProperties;
begin
  Result := nil;
  Caption := aTitle;
  FFile := aFile;
  FOpenDialogOnShow := aFile = '';
  FActiveViewAfterOpen := false;
  PrepareForm;

  if ShowModal = mrOk then
  begin
    if gtPDFDoc.IsLoaded then
    begin
      gtPDFViewer.Active := false;
      Application.ProcessMessages;
      BeginProgress;
      try
        //pSize := gtPDFDoc.GetPageSize(gtPDFViewer.PageNo, muPixels);
        //Result.Width := Trunc(pSize.Width);
        //Result.Height := Trunc(pSize.Height);
        //gtPDFDoc.RenderToCanvas(gtPDFViewer.PageNo, Result.Canvas, tpDefault);
        ////gtPDFDoc.RenderToCanvas(gtPDFViewer.PageNo, Result.Canvas, tpAlignment);

        Rotation := GetRotationAngle; //gtPDFDoc.GetPageRotate(gtPDFViewer.PageNo);

        (*
        aImageProperties := TgtImageProperties.Create;
        aImageProperties.BMPpixelformat := pf16bit;
        gtPDFDoc.SaveAsImage('1', itBitmap, 'c:\', 'ddd', aImageProperties);
        *)
        //AddExceptionToLog('Memory before reading PDF');
        if cbConsiderScale.Checked then
        begin
          try
            //gtPDFDoc.
            dpi := 72;
            dpi := Round(gtPDFViewer.Zoom / 100 * dpi);
            Result := gtPDFDoc.GetBitmap(gtPDFViewer.PageNo, dpi, dpi, 0);
            //Result.SaveToFile('c:\pdf_bitmap_'+IntToStr(dpi)+'.bmp');
          except
            try
              dpi := 72;
              dpi := Round(gtPDFViewer.Zoom / 100 * dpi * 0.75 );
              Result := gtPDFDoc.GetBitmap(gtPDFViewer.PageNo, dpi, dpi, 0);
            except
              try
                dpi := 72;
                dpi := Round(gtPDFViewer.Zoom / 100 * dpi * 0.5 );
                Result := gtPDFDoc.GetBitmap(gtPDFViewer.PageNo, dpi, dpi, 0);
              except
                on E: Exception do AddExceptionToLogEx('F_PDFView.GetDocBitmap', E.Message, true);
              end;
            end;
          end;
        end
        else
        begin
          try
            Result := TBitmap.Create;
            pSize := gtPDFDoc.GetPageSize(gtPDFViewer.PageNo, muPixels);
            Result.Width := Trunc(pSize.Width);
            Result.Height := Trunc(pSize.Height);
            gtPDFDoc.RenderToCanvas(gtPDFViewer.PageNo, Result.Canvas, tpDefault, 0);
          except
            on E: Exception do AddExceptionToLogEx('F_PDFView.GetDocBitmap', E.Message, true);
          end;
        end;

        if Rotation <> 0 then
        try
          RotateBitmap(Result, Rotation, clWhite);
        except
          on E: Exception do AddExceptionToLogEx('RotateBitmap', E.Message, true);
        end;
      finally
        EndProgress;
      end;
      gtPDFDoc.FileName := '';
    end;
  end;
end;

function TF_PDFView.GetDocJpeg(const aTitle: String): TJPEGImage;
var
  Bmp: TBitmap;
begin
  Result := nil;
  Bmp := GetDocBitmap(aTitle);
  if Bmp <> nil then
  begin
    Result := TJPEGImage.Create;
    Result.CompressionQuality := 100;
    Result.Assign(bmp);
    Bmp.Free;
  end;
end;

procedure TF_PDFView.gtPDFViewerPageChange(Sender: TgtPDFViewer;
  const CurrentPageIndex: TPageIndex);
begin
  UpdateButton;
  UpdateStatusBar;
end;

procedure TF_PDFView.DefineRatateGlyph;
var
  LBmp: TBitmap;

begin
  LBmp := TBitmap.Create;
  {if (gtPDFViewer.RotationAngle = ra0) then
  begin
    ImageList1.GetBitmap(0, LBmp);
    BtnClkRotate.Glyph.Assign(LBmp);
  end
  else if (gtPDFViewer.RotationAngle = raCW90) then
  begin
    ImageList1.GetBitmap(1, LBmp);
    BtnClkRotate.Glyph.Assign(LBmp);
  end
  else if (gtPDFViewer.RotationAngle = raCW180) then
  begin
    ImageList1.GetBitmap(2, LBmp);
    BtnClkRotate.Glyph.Assign(LBmp);
  end
  else if (gtPDFViewer.RotationAngle = raCW270) then
  begin
    ImageList1.GetBitmap(3, LBmp);
    BtnClkRotate.Glyph.Assign(LBmp);
  end;}
  ImageList1.GetBitmap(Ord(gtPDFViewer.RotationAngle), LBmp);
  BtnClkRotate.Glyph.Assign(LBmp);
  LBmp.Free;
end;

function TF_PDFView.GetRotationAngle: Integer;
begin
  Result := 0;
  case gtPDFViewer.RotationAngle of
    ra0:
      Result := 0;
    raCW90:
      Result := 90;
    raCW180:
      Result := 180;
    raCW270:
      Result := 270;
    raACW90:
      Result := 270;
    raACW180:
      Result := 180;
    raACW270:
      Result := 90;
  end;
end;

function TF_PDFView.UpdateButton: string;
begin
  BtnFirstPage.Enabled := not gtPDFViewer.IsFirst;
  BtnPriorPage.Enabled := not gtPDFViewer.IsFirst;
  BtnNextPage.Enabled := not gtPDFViewer.IsLast;
  BtnLastPage.Enabled := not gtPDFViewer.IsLast;
  BtnActive.Enabled := True;
  BtnActive.Down := Not gtPDFViewer.Active;
  BtnActive.Hint := ActiveBtnHint[BtnActive.down];
  BtnFitPage.Enabled := True;
  btnFitWidth.Enabled := True;
  BtnActualSize.Enabled := True;
  if round(gtPDFViewer.Zoom) = round(gtPDFViewer.GetStandardZoomValue(sztActualSize)) then
    BtnActualSize.Down := True;
  if round(gtPDFViewer.Zoom) = round(gtPDFViewer.GetStandardZoomValue(sztFitPage)) then
    BtnFitPage.Down := True;
  if round(gtPDFViewer.Zoom) = round(gtPDFViewer.GetStandardZoomValue(sztFitWidth)) then
    btnFitWidth.Down := True;
  //SearchButton.Enabled := True;
  BtnZoomOut.Enabled := True;
  BtnZoomIn.Enabled := True;
  EdtGotoPage.Enabled := True;
  edZoom.Enabled := True;
  BtnClkRotate.Enabled := True;
  BtnResetRotate.Enabled := True;
  EdtGotoPage.Text := IntToStr(gtPDFViewer.PageNo);
  //Label2.Caption := ' of ' + IntToStr(gtPDFViewer1.PDFDocument.PageCount);
  lbPageCount.Caption := IntToStr(gtPDFViewer.PDFDocument.PageCount);
end;

procedure TF_PDFView.UpdateStatusBar;
begin
  edZoom.Text := FormatFloat(GetDisplayFormatForFloatByPrecision(2), gtPDFViewer.Zoom);
  BtnZoomIn.Enabled := (StrToFloat_My(edZoom.Text)) <> 1 ;
end;

procedure TF_PDFView.BtnOpenfileClick(Sender: TObject);
begin
  OpenFileDialog(Not BtnActive.Down);
end;

procedure TF_PDFView.BtnActiveClick(Sender: TObject);
begin
  //BtnActive.Hint := ActiveBtnHint[BtnActive.down];
  gtPDFViewer.Active := Not BtnActive.Down; //not gtPDFViewer.Active;
  BtnPrint.Enabled := gtPDFViewer.Active;
  UpdateButton;
end;

procedure TF_PDFView.BtnActualSizeClick(Sender: TObject);
begin
  gtPDFViewer.Zoom := gtPDFViewer.GetStandardZoomValue(sztActualSize);
  UpdateStatusBar;
end;

procedure TF_PDFView.btnFitWidthClick(Sender: TObject);
begin
  gtPDFViewer.Zoom := gtPDFViewer.GetStandardZoomValue(sztFitWidth);
  UpdateStatusBar;
end;

procedure TF_PDFView.BtnFitPageClick(Sender: TObject);
begin
  gtPDFViewer.Zoom := gtPDFViewer.GetStandardZoomValue(sztFitPage);
  UpdateStatusBar;
end;

procedure TF_PDFView.BtnFirstPageClick(Sender: TObject);
begin
  gtPDFViewer.First;
  UpdateButton;
  UpdateStatusBar;
end;


procedure TF_PDFView.BtnPriorPageClick(Sender: TObject);
begin
  gtPDFViewer.Prior;
  UpdateButton;
  UpdateStatusBar;
end;

procedure TF_PDFView.EdtGotoPageKeyPress(Sender: TObject; var Key: Char);
begin
  if not (Key in ['0'..'9', #13]) and (Ord(Key) <> VK_BACK) then
  begin
    Key := #0;
  end;
  TimerGotoPage.Enabled := true;
end;

procedure TF_PDFView.TimerGotoPageTimer(Sender: TObject);
begin
  TTimer(Sender).Enabled := false;
  gtPDFViewer.GoToPage(StrToIntDef(EdtGotoPage.Text,1));
  UpdateButton;
  UpdateStatusBar;
end;

procedure TF_PDFView.BtnNextPageClick(Sender: TObject);
begin
  gtPDFViewer.Next;
  UpdateButton;
  UpdateStatusBar;
end;

procedure TF_PDFView.BtnLastPageClick(Sender: TObject);
begin
  gtPDFViewer.Last;
  UpdateButton;
  UpdateStatusBar;
end;

procedure TF_PDFView.BtnZoomInClick(Sender: TObject);
var
  Lfactor: Integer;
begin
  if edZoom.Text = '' then
  begin
    gtPDFViewer.Zoom := 1;
    UpdateStatusBar;
  end
  else
  begin
    Lfactor := Floor((gtPDFViewer.Zoom)/ ZoomIncrement);
    if ((gtPDFViewer.Zoom) - (ZoomIncrement * Lfactor)) = 0 then
      gtPDFViewer.Zoom := (ZoomIncrement * (Lfactor-1))
    else
      gtPDFViewer.Zoom := (ZoomIncrement * (Lfactor));
    UpdateStatusBar;
  end;
end;

procedure TF_PDFView.edZoomChange(Sender: TObject);
begin
  if edZoom.Text <> '' then
    gtPDFViewer.Zoom := StrToFloat_My(edZoom.text)
  else
    gtPDFViewer.Zoom := 1;
  UpdateButton;
end;

procedure TF_PDFView.edZoomExit(Sender: TObject);
begin
  if edZoom.Text <> '' then
    gtPDFViewer.Zoom := StrToFloat_My(edZoom.Text);
end;

procedure TF_PDFView.edZoomKeyPress(Sender: TObject; var Key: Char);
begin
  if not (Key in ['0'..'9','.', #13]) and (Ord(Key) <> VK_BACK) then
  begin
    Key := #0;
  end;
end;

procedure TF_PDFView.BtnZoomOutClick(Sender: TObject);
var
  Lfactor: Integer;
begin
  if edZoom.Text = '' then
  begin
    gtPDFViewer.Zoom := ZoomIncrement;
    UpdateStatusBar;
  end
  else
  begin
    Lfactor := Floor(StrToFloat_My(edZoom.text)/ ZoomIncrement);
    gtPDFViewer.Zoom := (ZoomIncrement * (Lfactor+1));
    UpdateStatusBar;
  end;
end;

procedure TF_PDFView.FormShow(Sender: TObject);
begin
  if FFile = '' then
  begin
    if FOpenDialogOnShow then
      TimerDocOpenDialog.Enabled := true;
  end
  else
    LoadPDF(FFile);
end;

procedure TF_PDFView.gbOkCancelResize(Sender: TObject);
begin
  SetMiddleControlChilds(TControl(Sender), TControl(Self));
end;

procedure TF_PDFView.PrepareForm;
begin
  gtPDFViewer.Active := false;
  UpdateButton;
  gbOkCancelResize(gbOkCancel);
end;

procedure TF_PDFView.TimerDocOpenDialogTimer(Sender: TObject);
begin
  TTimer(Sender).Enabled := false;
  OpenFileDialog(FActiveViewAfterOpen);
end;

procedure TF_PDFView.BtnPrintClick(Sender: TObject);
begin
  gtPDFViewer.PrintDoc;
end;

procedure TF_PDFView.gtPDFDocPassword(Sender: TObject;
  var Password: String; var Continue: Boolean; const Tries: Integer);
begin
  FPassAllTries := Tries;
  if Tries = 0 then
    FLastPassTries := 0;

  if (Tries > FLastPassTries) and (Tries < (FLastPassTries + 3)) then
    ShowMessage(cPDFView_Msg4_1 + CRLF + cPDFView_Msg4_2);
  if Tries < (FLastPassTries + 3) then
    Continue := InputPassQuery(cPDFView_Msg4_4, cPDFView_Msg4_5, Password)
  else
  begin
    Continue := False;
    ShowMessage(cPDFView_Msg4_1 + CRLF + cPDFView_Msg4_3);
  end;
end;

procedure TF_PDFView.FormCreate(Sender: TObject);
begin
  FLastPassTries := 0;
  FPassAllTries := -1; //-1 так как номер первой попытки проверки пароля = 0
end;

procedure TF_PDFView.BtnClkRotateClick(Sender: TObject);
var
  LBmp: TBitmap;
begin
  gtPDFViewer.RotateClockwise90;
  DefineRatateGlyph;
end;

procedure TF_PDFView.BtnResetRotateClick(Sender: TObject);
begin
  gtPDFViewer.RotationAngle := ra0;
  DefineRatateGlyph;
end;

procedure TF_PDFView.BtnUnClkRotateClick(Sender: TObject);
begin
  gtPDFViewer.RotateAntiClockwise90;
  //DefineRatateGlyph;
end;

end.
