Open a PDF to a Named Destination via DDE in C/C++

We switched to using PDF as the online help format for our new Visual Nature Studio 3 product, which is a switch I greatly welcome. Previously we used non-paginated basic HTML, broken up into over a hundred "chapters", one for each major topic or window in the program. When the user invoked the help menu or F1 key, the software would determine what window was active, and map that to an HTML filename. I would then use ShellExecute to launch the users preferred web browser to view the file in question. It was portable, supported decent rich media, and worked well. The downsides were that we had to keep track of lots of files, they weren't very elegant to print, and the Table of Contents, Index and Searching were cumbersome. PDF is a single file, with great onscreen and print quality, is easily searchable and has a good table of contents. PDF even offers "named destinations" which are internal named bookmarks that can be jumped to, regardless of what page they end up on as the document grows or shrinks.

However, when you want to display a particular section in a PDF file, from within your application, things get a little more difficult. I don't want to embed a PDF viewer in my application or anything, I just want to launch the user's preferred PDF viewer (whatever it is -- Ghostscript, Foxit, Adobe Reader or something else). We don't like to impose our choice of viewer.

However, while launching a PDF into the preferred viewer is easy, launching a PDF document TO a particular page or section is not so easy. Adobe Reader (nee Acrobat Reader) offers some command line arguments, including page=pagenum and nameddest=name for opening a PDF directly to a specified page or named destination (section). Done deal, right? Wrong.

See, the problem is that if Adobe Reader already HAS the document open, and you try to invoke it again with the same command-line argument method to specify a new named destination, it completely ignores you. Apparently it notices the document is already open and decides it doesn't need to do anything, ignoring that you're requesting a potentially different page be displayed.

Ok, back to the drawing board. next we look at the Acrobat Interapplication Communication Overview. This introduces some DDE commands, including DocGoToNameDest. Perfect. We'll just launch the document the old fashioned way, then see if we can talk via DDE and issue a DocGoToNameDest.

Well, it's not that easy. First, apparently you need to become an Adobe developer to get the sample code to do this:
http://article.gmane.org/gmane.comp.tex.live/8228

However, it seems that the TeX guys have already had to deal with this problem, when previewing PDFs created from TeX authoring tools They provided some sample source here:
http://magic.aladdin.cs.cmu.edu/2005/07/15/pdfopen-and-pdfclose/
http://tug.org/texlive/devsrc/Build/source/texk/contrib/pdfdde.c

PDFDDE has the ability to open and close PDF files. So, I grabbed this source and checked it out. The license seems somewhat vague:


DDEOpen.c

This file is furnished to you by Adobe Systems Incorporated
under the terms of the Acrobat (r) Plug-ins Software
Development Kit License Agreement.

Copyright (C) 1995-1999, Adobe Systems Inc. All Rights Reserved.

I figured if it's ok for TeX to use it, then it's ok for me to use it. So, I renamed it to pdfdde.cpp (my source is all C++ and I can't mix C code into it easily) and compiled it. It worked pretty well, but didn't support the DocGoToNameDest command I needed. I commented out all the code I didn't need (for cleanliness) and created my own TellReaderToJumpToNamedDestViaDDE() function to do what I needed to do.

TellReaderToJumpToNamedDestViaDDE turned out to be more complicated that I had hoped. According to page 44 of the IACOverview document: You must first open a document using the DocOpen DDE message in order to be able to use other DDE messages on it. You cannot use DDE messages to close a document that a user opened manually. Hmm. This could be problematic. Basically, empirical testing indicates you can only use DDE commands on a document you opened via DDE. Hrm. DDE won't open a new document if the same file is already open, and it says we can't close documents via DDE unless we ourselves opened them. Here's a pickle.

What I ended up doing is below:


// Adobe Reader DDE commands only work on documents opened by DDE.
// Documents already opened do not obey commands like DocGoToNameDest
// So, we must CLOSE the document first (if open) THEN reopen it
// so we are granted access to it

bool TellReaderToJumpToNamedDestViaDDE(const char *FullFilePath, const char *NamedDest)
{
bool Success = false;
// this will start Reader (or whatever PDF host is available) if not started
OpenConversation(EVAL_TOPIC);

// Komrade! Use largest hammer to beat Adobe Reader into submission!
ExecuteCommand("[DocClose(\"%s\")]", FullFilePath);
ExecuteCommand("[DocOpen(\"%s\")]", FullFilePath);
ExecuteCommand("[DocOpen(\"%s\")]", FullFilePath); /* acrord32 ver8 bug */
ExecuteCommand("[FileOpen(\"%s\")]", FullFilePath);

Success = ExecuteCommand("[DocGoToNameDest(\"%s\", %s)]", FullFilePath, NamedDest);
CloseConversation();

return(Success);
} // TellReaderToJumpToNamedDestViaDDE

Adobe Reader DOES respond to our DocClose. Then, mimicking the code from pdfopen() in the original pdfdde.c, I issue two DocOpen commands to work around an Adobe Reader 8 bug followed by FileOpen. Finally, once it is open (and now under our control, whereas it wasn't prior to closing and reopening) we can now pull DocGoToNameDest and have a respectable chance of it obeying. And, it seems to, more often than not. The only downside is that if the document were really complex, or not stored locally, perhaps closing and reopening it would have some load/parse cost.

Code is attached. I am relinquishing all copyright to it, because I'm unclear what the original license restrictions were. I just want my insights to be made available to anyone out there who might benefit from it, and save them hassle. If anyone has any suggestions, please contact me (see the About/Contact page, link on the right side of this page).

Unfortunately, neither Ghostscript nor Foxit support this DDE technique. I have a request in with the Foxit guys (Mantis ID 0005714) to add this functionality, and I hope they follow through with it. Drop them a note support@foxitsoftware.com if you want to let them know you need it too.

AttachmentSize
pdfdde.cpp_.txt16.5 KB