Open pdf to specific page via macro

Hi,
I am attempting to convert a VBA macro from Excel to Libreoffice. The function of the macro is fairly simple- it picks up data from the current cell and, using that information, opens a PDF file then advances to a specific page number. However, the code to open the PDF and move to specific page uses windows features to walk through open windows and the code doesn’t work for Libreoffice. I’ve done a bit of research and experimentation but have yet to find any good references or examples to open the PDF to a specific page. Below is the VBA code I am attempting to migrate - any suggested references or examples appreciated.

’ Input Variables:
’ ~~~~~~~~~~~~~~~~
’ sFile : Fully qualified path, filename & extension of the PDF file
’ lPage : Page number to view

’ Usage:
’ ~~~~~~
’ Call ChangePDFPage(“C:\Users\Daniel\Documents\AccessTechniques.pdf”, 65)

’ Revision History:
’ Rev Date(yyyy/mm/dd) Description
’ **************************************************************************************
’ 1 2018-11-25 Initial Release
'---------------------------------------------------------------------------------------
Public Function ChangePDFPage(sFile As String, lPage As Long)

Dim hwndParent&
Dim hwndChild&
Dim sFileName             As String

'Dim sTempWindowText As String
'Dim sTempRetVal As Long

On Error GoTo Error_Handler

sFileName = Right(sFile, Len(sFile) - InStrRev(sFile, "\")) 'Get just the filename from the full path/filename/ext

hwndParent = FnFindWindowLike("*" & sFileName & "*")

If hwndParent = 0 Then
    'This means that we can't find an instance of Adobe with the specified File, so we could use the traditional approach for the first time
    Call OpenPDF(sFile, lPage)

Else
    PostMessage hwndParent, WM_CLOSE, 0, ByVal 0&
    Call OpenPDF(sFile, lPage)
    GoTo skipchild

    hwndChild = GetWindow(hwndParent, GW_CHILD)        'Find the 1st Child window handle - Zoom %
    hwndChild = GetWindow(hwndChild, GW_HWNDNEXT)        'Find the next window handle - Page No.

    'an use PostMessage instead if wanted
    Call SendMessage(hwndChild, WM_SETTEXT, 0&, lPage)        'Enter our page number into the Adobe Reader Page number textbox
    Call SendMessage(hwndChild, WM_KEYDOWN, VK_RETURN, 0) 'Submit an Enter key to actually switch to the input page
    Call SendMessage(hwndChild, WM_KEYUP, VK_RETURN, 0)

skipchild:

End If

Error_Handler_Exit:
On Error Resume Next
Exit Function

Error_Handler:
MsgBox “The following error has occured” & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
“Error Source: ChangePDFPage” & vbCrLf & _
"Error Description: " & Err.Description & _
Switch(Erl = 0, “”, Erl <> 0, vbCrLf & "Line No: " & Erl) _
, vbOKOnly + vbCritical, “An Error has Occured!”
Resume Error_Handler_Exit
End Function

'Purpose : Enumerates all the child windows and returns the handle of a child window with
’ a matching caption or class name.
'Inputs : lParentHwnd The handle to a parent window
’ [sClassName] The class name to search for.
’ [sCaption] The caption to search for.
’ [bSearchChildWindows] Searchs within each child window for a matching window
'Outputs : Returns the handle of child window or zero if not found
'Source : https://visualbasic.happycodings.com/forms/code54.html
Public Function FindChildWindow(ByVal lParentHwnd As Long, Optional ByVal sClassName As String, Optional ByVal sCaption As String, Optional bSearchChildWindows As Boolean = False) As Long
Dim bClassMatches As Boolean, bCaptionMatches As Boolean
Const clMaxName As Long = 255
Const GW_CHILD = 5
Const GW_HWNDNEXT = 2
Const GW_HWNDFIRST = 0

Dim lWinHwnd As Long, sTestName As String * clMaxName, lNumChars As Long

'Ignore case
sClassName = UCase$(sClassName)
sCaption = UCase$(sCaption)

'Find the first child window
lWinHwnd = GetWindow(lParentHwnd, GW_CHILD)
If lWinHwnd = 0 Then
    Exit Function
End If
Do
    bClassMatches = False
    bCaptionMatches = False
    lNumChars = clMaxName

    If Len(sClassName) Then
        'Get the child window classname
        lNumChars = GetClassName(lWinHwnd, sTestName, (clMaxName))
        If UCase(Left$(sTestName, lNumChars)) = sClassName Then
            'Found matching class name
            bClassMatches = True
        End If
    Else
        'No class name specified
        bClassMatches = True
    End If

    If Len(sCaption) Then
        lNumChars = GetWindowText(lWinHwnd, sTestName, (clMaxName))
        If UCase(Left$(sTestName, lNumChars)) = sCaption Then
            'Found matching caption
            bCaptionMatches = True
        End If
    Else
        'No caption specified
        bCaptionMatches = True
    End If

    If bClassMatches And bCaptionMatches Then
        'Found matching dialog, return handle
        FindChildWindow = lWinHwnd
        Exit Do
    ElseIf bSearchChildWindows Then
        'Search Child windows
        FindChildWindow = FindChildWindow(lWinHwnd, sClassName, sCaption, True)
        If FindChildWindow Then
            Exit Do
        End If
    End If

    'Try next next child
    lWinHwnd = GetWindow(lWinHwnd, GW_HWNDNEXT)
    If lWinHwnd = 0 Then
        'No more child windows
        Exit Do
    End If
Loop While lWinHwnd     'Loop until there are no more child windows

End Function

Just try open Reader with key /A "page="

JohnSUN - thanks. I went to the referenced link and it appears to be how to use a command line for Acrobat. My need is to automate starting acrobat from a CALC sheet. Have you seen any references on how to run a command line such a acrobat from a CALC macro?

This is written a little shorter than you wrote in your question - just use Shell:

Sub getParamsAndRunAcrobat(oEvent As Variant)
Dim oData As Variant
	oData = ThisComponent.getCurrentController().getActiveSheet().getCellRangeByName("B1:B2").getDataArray()
	Call OpenPDF(oData(0)(0), oData(1)(0))
End Sub

Sub OpenPDF(sFile As String, lPage As Long)
Const PATH_TO_READER = "C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
	Shell (ConvertToURL(PATH_TO_READER), 3, " /A ""page=" + lPage + """ """ + sFile + """")
End Sub

Demo file - ShellAcrobat.ods

1 Like

JohnSUN - PERFECT! Now the fun starts to get the rest of the VBA macro converted. THANKS!

The solution for Windows using acrobat reader proposed by johnSun, whom I thank for the idea, can be extended to linux-ubuntu with the standard document viewer “evince”

Sub Main
   xEvince ("/ media /...../ test.pdf", 2)
End Sub

sub xEvince (sFileFullPath, iPageNumber)
  Shell ("evince --page-label =" + cstr (iPageNumber) + "  " + sFileFullPath)
end sub

The sub xEvince opens the pdf file requested on the iPageNumber page passed as a parameter

I used as a test a pdf file created with libreoffice writer containing TOC, bookmarks and single hyperlinks to local bookmarks by activating “tagged pdf” and “export bookmark” in the export form.
The pdf exported from writer maintains the TOC with relative automatic links to the headings of the original document and preserves both the bookmarks and the hyperlink functionality

While with ‘evince’ it is possible to open a pdf in correspondence of a page, I have not found any command to open the pdf in correspondence of a bookmark

However, I believe that it is possible to bypass the problem, in the case of pdf exported by writer
By scanning the writer document with a macro to search for bookmarks, it is possible obtain the list of pairs (bookmark-page number) to be used to open the pdf each time in correspondence with the page containing the bookmark

Even when the TOC and hyperlinks imported from libreoffice work in the pdf, and therefore the imported bookmarks work, the bookmarks are not always also visible from evince in the side panel

When it appears, using the command from the terminal:

gvfs-info -a "metadata :: evince :: bookmarks" fileFullPath.ext

all pairs (page number-bookmark name) detected by ‘evince’ are displayed with the format:

metadata :: evince :: bookmarks: [(0, 'bookName0'), (2, 'bookName1')]

Page numbering in this list is 0 based (page 1 corresponds to 0)

1 Like