﻿B4J=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=9.8
@EndOfDesignText@
Sub Class_Globals
	Private myEventName As String
	Private myCallback As Object
	Private fx As JFX
	Private thisDoc As JavaObject
	Private renderer As JavaObject
	Private utils As JavaObject
	Private pneMyContainer As Pane
	Private pneMyPages As Pane
	Private ScrollPanePDF As ScrollPane
	Public myZoom As Float
	Private PagesRects As List
	Private const pageSpace As Float = 6
	Private const LeftRightSpace As Float = 6
	Private myDocName As String
	Private activePage As Int
	Type PDFrectangle (x As Double, y As Double, width As Double, height As Double,index As Int, imv As ImageView, imageIsSet As Boolean)
	Private SpinnerZoom As Spinner
	Private SpinnerPageNumber As Spinner
	Private DocIsLoaded As Boolean
	Public isRenderingPages As Boolean
	Private stopRenderingPages As Boolean
	Public NewZoom As Float
End Sub

'Initializes the object.
'  CallBack:   module that receives events
'  EventName:  event prefix
'  Document:   path and filename to PDF-document to view
'  Pane:       pane for the viewer
'  Layoutname: The layout for the viewer. Must contain a ScrollPane namedm "ScrollPanePDF". Optional is nodes called: "SpinnerZoom", "SpinnerPageNumber" and "LabelFileName"
'  Zoom:       View Zoom of the document (1=100%)
'Returns true if document was succesfull loaded

Public Sub Initialize(CallBack As Object, EventName As String, Document As String, Pane As Pane, Layoutname As String, Zoom As Float) As Boolean
	myEventName = EventName
	myCallback = CallBack
	pneMyContainer = Pane
	pneMyContainer.LoadLayout(Layoutname)
	If SpinnerZoom.IsInitialized Then
		SpinnerZoom.SetListItems(getZoomsList)
	End If
	utils.InitializeStatic("javafx.embed.swing.SwingFXUtils")
	pneMyPages.Initialize("")
	ScrollPanePDF.InnerNode = pneMyPages
	ScrollPanePDF.SetVScrollVisibility("ALWAYS")
	Dim R As Reflector
	R.Target = pneMyPages
	R.AddEventHandler("pneScroll","javafx.scene.input.ScrollEvent.SCROLL")
	If File.Exists("",Document) And LoadDocument(Document, Zoom)  Then
		Return True
	Else
		ResetNodes
		Return False
	End If
End Sub

Private Sub ResetNodes
	If SpinnerPageNumber.IsInitialized Then
		SpinnerPageNumber.Visible = False
	End If
	If SpinnerZoom.IsInitialized Then
		SpinnerZoom.Visible = False
	End If
'	If ButtonRotate.IsInitialized Then
'		ButtonRotate.Visible = False
'	End If
	pneMyPages.RemoveAllNodes
	PagesRects.Initialize
	pneMyPages.PrefHeight = 0
	pneMyPages.PrefWidth = 05
End Sub

'Gives the number of pages in loaded PDF-document, give 0 if no loaded document
public Sub getNumberOfPages() As Int
	If DocIsLoaded Then
		Return thisDoc.RunMethod("getNumberOfPages",Array())
	Else
		Return 0
	End If
End Sub
'Returns a list with string items suitable for spinner node etc. 
'   Use the function StringZoomToFloat to convert one of the strings to a float 
'   suitable as argument for the Repaint sub

Public Sub getZoomsList() As List
	Return Array("10%","20%","30%","40%","50%","60%","70%","80%","90%","100%","110%","120%","130%","140%","150%","160%","170%","180%","190%","200%")
End Sub

'convert 
Public Sub StringZoomToFloat(Zoom As String) As Float
	Return Zoom.Replace("%","")/100
End Sub

Public Sub FloatZoomToString(Zoom As Float) As String
	Return Round2(Zoom * 100,1) & "%"
End Sub

'Open PDF file 
'  Stream:	  inputstream E.g. from GetInputStream methode on HttpJob object
'  Zoom:      View Zoom of the document (1=100%)
'  DescriptiveName: a text to show in viewer E.g. the URL used to get inputstream from HttpJob
'returns true: no error, false: error opening file
Public Sub LoadDocumentFromInputStream(Stream As InputStream, Zoom As Float, DescriptiveName As String) As Boolean
	Try
		Dim PDDocument As JavaObject
		PDDocument.InitializeStatic("org.apache.pdfbox.pdmodel.PDDocument")
		thisDoc = PDDocument.RunMethod("load",Array(Stream))
		renderer.InitializeNewInstance("org.apache.pdfbox.rendering.PDFRenderer",Array(thisDoc))
		myDocName = DescriptiveName
		DocIsLoaded = True
		If SpinnerPageNumber.IsInitialized Then
			SpinnerPageNumber.SetNumericItems(1,getNumberOfPages,1,0)
			SpinnerPageNumber.Visible = True
		End If
		If SpinnerZoom.IsInitialized Then
			SpinnerZoom.Visible = True
		End If
		Repaint(Zoom)
		Return True
	Catch
		DocIsLoaded = False
		ResetNodes
		Return False
	End Try
End Sub

'Open PDF file 
'  name: path + filename
'  Zoom:      View Zoom of the document (1=100%)
'returns true: no error, false: error opening file
public Sub LoadDocument(name As String, Zoom As Float) As Boolean
	Try
		Dim fi As JavaObject
		fi.InitializeNewInstance("java.io.File",Array(name))
		Dim PDDocument As JavaObject
		PDDocument.InitializeStatic("org.apache.pdfbox.pdmodel.PDDocument")
		thisDoc = PDDocument.RunMethod("load",Array(fi))
		renderer.InitializeNewInstance("org.apache.pdfbox.rendering.PDFRenderer",Array(thisDoc))
		myDocName = name
		DocIsLoaded = True
		If SpinnerPageNumber.IsInitialized Then
			SpinnerPageNumber.SetNumericItems(1,getNumberOfPages,1,0)
			SpinnerPageNumber.Visible = True
		End If
		If SpinnerZoom.IsInitialized Then
			SpinnerZoom.Visible = True
		End If
		Repaint(Zoom)
		Return True
	Catch
		DocIsLoaded = False
		ResetNodes
		Return False
	End Try
End Sub

'Unloads an open document
public Sub CloseDocument()
	If thisDoc.IsInitialized Then thisDoc.RunMethod("close",Null)
	DocIsLoaded = False
	ResetNodes
	Repaint(1)
End Sub

'Gives an rendered image of one page in specified Zoom
'   Zoom:    1 is 100% size on stage screen dpi
public Sub renderPage(p As Int, Zoom As Float) As Image
	If DocIsLoaded Then
		Dim scrn As JavaObject
		Dim dpi As Float = scrn.InitializeStatic("javafx.stage.Screen").RunMethodJO("getPrimary",Null).RunMethod("getDpi",Null)
		Dim img As Object = renderer.RunMethod("renderImageWithDPI",Array(p,dpi*Zoom))
		Return utils.RunMethodjo("toFXImage",Array(img,Null))
	Else
		Return Null
	End If
End Sub

'Gives the rotation angle for the specified page in degrees
Public Sub getRotationAngle(page As Int) As Int
	Return thisDoc.RunMethodJO("getPage",Array(page)).RunMethod("getRotation",Null)
End Sub

'Gives the Page size in pixels Zoomd to specified Zoom and with stage screen dpi
'   Zoom:    1 is 100% size on stage screen dpi
Public Sub getPageSizeInPixels(p As Int, Zoom As Float) As PDFrectangle
	Dim scrn As JavaObject
	Dim dpi As Float = scrn.InitializeStatic("javafx.stage.Screen").RunMethodJO("getPrimary",Null).RunMethod("getDpi",Null)
	Dim pg As JavaObject = thisDoc.RunMethodJO("getPage",Array(p))
	Dim r As PDFrectangle
	r.Initialize
	Dim mb As JavaObject = pg.RunMethodJO("getMediaBox",Null)
	If getRotationAngle(p) Mod 180 = 0 Then
		r.height = Floor( mb.RunMethod("getHeight",Null) / 72 * dpi * Zoom)
		r.width = Floor( mb.RunMethod("getWidth",Null) / 72 * dpi * Zoom)
	Else
		r.height = Floor( mb.RunMethod("getWidth",Null) / 72 * dpi * Zoom)
		r.width = Floor( mb.RunMethod("getHeight",Null) / 72 * dpi * Zoom)
	End If
	Return r
End Sub

'Repaint all pages in document on the viewer scrollpane
'This is as Resumable sub that returns controll to the caller when 
'all page sizes is calculated and the first page is rendered.
'The rendering continues one page a time after the message cue is processed,
'until all pages are rendered
'   Zoom:    1 is 100% size on stage screen dpi
Public Sub Repaint(Zoom As Float)
	If DocIsLoaded Then
		If stopRenderingPages Then
			Return 'Repaint is already running and waiting for the pagerendering to stop
		else If isRenderingPages Then
			stopRenderingPages = True
			Wait For RenderingPagesFinished
		End If
		If Round2(Zoom,1) < 0.1 Then
			NewZoom = 0.1
		Else if Round2(Zoom,1) > 2 Then
			NewZoom = 2
		Else
			NewZoom = Round2(Zoom,1)
		End If
		If NewZoom <> myZoom Then
			myZoom = NewZoom
			If SpinnerZoom.IsInitialized And SpinnerZoom.Value <> FloatZoomToString(myZoom) Then
				SpinnerZoom.Value = FloatZoomToString(myZoom)
				Wait For (SpinnerZoom) SpinnerZoom_ValueChanged (Value As Object) 'Prevents the event to make feedback loop
			End If
			CallSubDelayed2(myCallback,myEventName& "_ZoomChanged",myZoom)
		End If
		pneMyPages.RemoveAllNodes
		PagesRects.Initialize
		Dim ypos As Float
		Dim spNative As JavaObject = ScrollPanePDF
		Dim vpWidth As Double = spNative.RunMethodJO("getViewportBounds",Null).RunMethod("getWidth",Null)
		Dim maxwidth As Float = vpWidth
		For i = 0 To getNumberOfPages - 1
			Dim rect As PDFrectangle = getPageSizeInPixels(i,myZoom)
			rect.imv.Initialize("")
			rect.y = ypos
			rect.index = i
			If maxwidth < rect.width + LeftRightSpace * 2 Then maxwidth = rect.width + LeftRightSpace * 2
			ypos = ypos + rect.height + pageSpace
			PagesRects.add(rect)
		Next
		pneMyPages.PrefHeight = Max(ypos,spNative.RunMethodJO("getViewportBounds",Null).RunMethod("getHeight",Null))
		pneMyPages.PrefWidth = maxwidth
		For i = 0 To getNumberOfPages - 1
			rect = PagesRects.Get(i)
			rect.x = (maxwidth - rect.width)/2
			Dim pne As Pane
			pne.Initialize("")
			CSSUtils.SetBackgroundColor(pne,fx.Colors.White)
			pne.AddNode(rect.imv,0,0,-1,-1)
			pneMyPages.AddNode(pne,rect.x,rect.y + pageSpace/2, rect.width,rect.height)
			rect.width = rect.Width + LeftRightSpace * 2
			rect.height = rect.Height + pageSpace
		Next
		isRenderingPages = True
		If isRenderingPages = True Then
			B4XPages.MainPage.LZoom.Text = "  RENDERN..."
			B4XPages.MainPage.LFit.Enabled = False
			Dim x As B4XView = B4XPages.MainPage.LZoom
			x.Color = 0xFF800000
		End If
		For i = 0 To getNumberOfPages - 1
			rect = PagesRects.Get(i)
			If rect.imageIsSet = False Then
				rect.imv.SetImage(renderPage(i,myZoom))
				rect.imageIsSet = True
			End If
			Sleep(0)
			If stopRenderingPages = True Then
				stopRenderingPages = False
				CallSubDelayed(Me,"RenderingPagesFinished")
				Exit
			End If
		Next
		isRenderingPages = False
		If isRenderingPages = False Then
			B4XPages.MainPage.LZoom.Text = "  ZOOM"
			B4XPages.MainPage.LFit.Enabled = True
			Dim x As B4XView = B4XPages.MainPage.LZoom
			x.Color = 0xFF333333
		End If
	End If
'	Log (NewZoom)
End Sub

private Sub pneScroll_Event(e As Event)
	Dim ThisEvent As JavaObject = e
	Dim Moved As Double = ThisEvent.RunMethod("getDeltaY",Null)
	Dim isControlDown As Boolean = ThisEvent.RunMethod("isControlDown",Null)
	Dim x As Double
	Dim y As Double
	Dim MouseDokPos As PDFrectangle
	Dim MouseSPpos As PDFrectangle
	If isControlDown And (Moved > 0 Or (Moved < 0 And Round2(myZoom,1) > 0.1)) Then
		Dim spNative As JavaObject = ScrollPanePDF
		Dim vpWidth As Double = spNative.RunMethodJO("getViewportBounds",Null).RunMethod("getWidth",Null)
		Dim vpHeight As Double = spNative.RunMethodJO("getViewportBounds",Null).RunMethod("getHeight",Null)
		x = ThisEvent.RunMethod("getX",Null)
		y = ThisEvent.RunMethod("getY",Null)
		MouseSPpos.x = x - ScrollPanePDF.HPosition * (pneMyPages.PrefWidth - vpWidth)
		MouseSPpos.y = y - ScrollPanePDF.VPosition * (pneMyPages.PrefHeight - vpHeight)
		For Each r As PDFrectangle In PagesRects
			If y >= r.y And y < r.y + r.height Then
				MouseDokPos.index = r.index
				MouseDokPos.y = (y - r.y) / r.height
				MouseDokPos.x = (x - r.x) / r.width
				Exit
			End If
		Next
		If Moved > 0 Then
			Repaint( myZoom + 0.1)
		Else If Moved < 0 Then
			Repaint( myZoom - 0.1)
		End If
		Dim r As PDFrectangle = PagesRects.Get(MouseDokPos.index)
		CallSubDelayed3(Me,"ScrollToPositionInDocument",MouseDokPos,MouseSPpos)
		e.Consume
	End If
End Sub

'Scrolls the Scrollpane to the specified page
Public Sub ScrollToPage(page As Int)
	If page <= getNumberOfPages Then
		Dim Docpos As PDFrectangle
		Docpos.index = page - 1
		ScrollToPositionInDocument(Docpos,Null)
	End If
End Sub

'Scrolls the scrollpane so the specified position in the specified page reach the specified position on the scrollpane
'    PositionInDocument: 
'          Only this members is used:
'               index: The pagenumber to scroll to
'               x:     Specifies the horizontal position in the PDF-page relative to the MediaBox of the page (0 is left border and 1 is right border of MediaBox)
'               y:     Specifies the vertical position in the PDF-page relative to the MediaBox of the page (0 is top border and 1 is bottom border of MediaBox)
'    PositionInScrollPane:
'          Only this members is used:
'               x:     Specifies the horizontal position in pixels from left to right
'               y:     Specifies the vertical position in pixels from top to bottom
Public Sub ScrollToPositionInDocument(PositionInDocument As PDFrectangle,PositionInScrollPane As PDFrectangle)
	If DocIsLoaded And stopRenderingPages = False Then
		If PositionInScrollPane = Null Then
			Dim psp As PDFrectangle
			PositionInScrollPane = psp
		End If
		Dim spNative As JavaObject = ScrollPanePDF
		Dim vpWidth As Double = spNative.RunMethodJO("getViewportBounds",Null).RunMethod("getWidth",Null)
		Dim vpHeight As Double = spNative.RunMethodJO("getViewportBounds",Null).RunMethod("getHeight",Null)
		Dim r As PDFrectangle = PagesRects.Get(Min(PositionInDocument.index,getNumberOfPages-1))
		If pneMyPages.PrefWidth > vpWidth Then ScrollPanePDF.HPosition = (r.x + PositionInDocument.x * r.width - PositionInScrollPane.x) / (pneMyPages.PrefWidth - vpWidth)
		If pneMyPages.PrefHeight > vpHeight Then ScrollPanePDF.VPosition = (r.y + PositionInDocument.y * r.height - PositionInScrollPane.y) / (pneMyPages.PrefHeight - vpHeight)
	End If
End Sub

'Gives the path and filename to loaded document
Public Sub getDocName As String
	Return myDocName
End Sub

private Sub ScrollPanePDF_VScrollChanged (Position As Double)
	Dim spNative As JavaObject = ScrollPanePDF
	Dim vpHeight As Double = spNative.RunMethodJO("getViewportBounds",Null).RunMethod("getHeight",Null)
	Dim y1 As Double = ScrollPanePDF.VPosition * (pneMyPages.PrefHeight - vpHeight)
	Dim y2 As Double = y1 + vpHeight
	Dim bestPage As Int
	Dim bestPageVisible As Double
	For Each r As PDFrectangle In PagesRects
		Dim Visible As Double = (Min(y2,r.y + r.height) - Max(y1,r.y))
		If Visible > bestPageVisible Then
			bestPageVisible = Visible
			bestPage = r.index
		End If
	Next
	If activePage <> bestPage Then
		activePage = bestPage
		If SpinnerPageNumber.IsInitialized Then
			If activePage + 1 <> SpinnerPageNumber.Value Then
				SpinnerPageNumber.Value = activePage + 1
			End If
			Wait For (SpinnerPageNumber) SpinnerPageNumber_ValueChanged (Value As Object) 'Prevents the event to make feedback loop
		End If
		CallSubDelayed2(myCallback,myEventName & "_Page",activePage+1)
	End If
End Sub
'
Private Sub SpinnerPageNumber_ValueChanged (Value As Object)
	ScrollToPage(Value)
End Sub

Private Sub SpinnerZoom_ValueChanged (Value As Object)
	Repaint(StringZoomToFloat(Value))
End Sub

Sub ScrollPanePDF_Resize (Width As Double, Height As Double)
	If DocIsLoaded Then
		Dim ap As Int = activePage
		Repaint(myZoom)
		ScrollToPage(ap+1)
	End If
End Sub
