Lon Hosford's Bitbox

Lon (Alonzo) Hosford's Professional Consulting Blog

Adobe AIR NativeProcess Silent Print PDF with BioPDF’s Acrobat Wrapper

By Lon (Alonzo) Hosford
A current Adobe Air Windows OS project I am developing requires printing a Adobe PDF file without user interaction or at the minimum pressing a simple print button without seeing a print dialog window. This is often called silent printing.

Adobe Reader prior to version 8 allowed silent printing from the command line. You find references throughout the internet AcroRd32.exe /N /T PdfFile PrinterName [PrinterDriver[PrinterPort]] However for security this was changed to require the user to finish the printing. If you are developing a kiosk as I am, we want to avoid users having to know how to use printer dialogs or Adobe Reader menus.

Javascript Injection
In researching the alternatives I found the ability to insert Javascript into the PDF file you want to print. This requires Adobe Acrobat Pro and a manual effort of a highly technical nature. I tried this and for a once in while PDF file it is acceptable, but not suited for dynamically third party software generated PDF files or PDF files from multiple sources. Plus it still would require AIR showing the HTMLLoader wrapping the Adobe Reader. You can review this alternative in Adobe Cookbooks.

Other alternatives were command line implementations offered by PDF software companies. These would allow using the NativeProcess in Adobe AIR to execute the printing without user intervention or allow designing a simple user interface like a big print button.

PDFPrint
A decent command line solution was PDFPrint from VeryPDF. Price is $199 (US) per installation. The trial version seemed to work well cobbled by a watermark and page count limitation, but the price forced me to press on.

printto.exe
I came across printto.exe from BioPDF. The printto.exe program uses the default application set by the Windows OS which is Acrobat Reader for PDF. This actually works but it does leaves Acrobat Reader open. I did not try it with other Acrobat Reader clones. Still you can use it, works with any file and it is free free.

GhostScript
Another choice is GhostScript. This works well once you select a “device”. You need to install it and run from the installation directory. I put it on a Windows XP machine and the command line was "C:\Program Files\gs\gs8.71\bin\gswin32c.exe" "@printtestgscmdline.txt" The argument @printtestgscmdline.txt is a way to feed the arguments from a file. The arguments I used are -sDEVICE="printer-device-id" -dNOPAUSE "full-path-to-pdf/your.pdf" -c quit. To get the valid printer device id you can get a list by starting ghostscript interactive mode and typing devicenames ==. I passed on this as I could not get the silencing arguments to work if you copied the installation directory executable files to another directory or machine. I needed something more portable. Also I am not sure of the PDF currency of the software.

Acrobat Wrapper version 2.0.0.23
The one I settled on that is free for non-commercial use. The program file name is acrowrap.exe and called Acrobat Wrapper version 2.0.0.23. You can download and use it without any watermarks or page limits. For commercial use you need to buy PDF Writer at $29 (US) per user with price cuts starting with 2 users; but you do not have to install that. Acrobat Wrapper downloads and installs on its own. Once installed you can take the acrowrap.exe from the installation directory and place it in any folder or computer and it happily runs from the command line. Thus for our commercial use we will buy licenses of PDF Writer for each copy of acrowrap.exe we install on our kiosks. The one drawback is when the printing is delayed the Adobe Reader window appears in a ghostly fashion but not accessible by the user.

You folks at BioPDF should make Acrobat Wrapper a product and simplify so that you do not have to install to get the executable. Also if you can keep Acrobat Reader minimized, that would be sweet.

Using Acrobat Wrapper
The following is a demonstration of how to use Acrobat Wrapper with a NativeProcess Adobe Air application. In this example the user will hit the print button. If your file is generated or downloaded automatically for the user, you can go directly to printing without further user interaction.

I added some options like full screen and always in front for a kiosk type of installation. See code lines 29 and 30 respectively for settings and 46 to 54 for the implementation.

Create an Air project in Flex Builder and paste the code below into the main application mxml.

You need to download and install Acrobat Wrapper on any Windows computer. Then create a folder in your Flex application under the “src” folder and name it the name “native_apps” shown on code line 19 or provide your own folder name. Copy to this folder the acrowrap.exe file from the installation directory that Acrobat Wrapper placed it. For example on a Windows XP computer it was C:\Program Files\bioPDF\Acrobat Wrapper.

Add a sub folder named “in” or a name of your choice on code line 24. The “in” folder will hold your PDF file named on code line 23.

Testing Hints
In testing you will find that if it fails there is no feedback from acrowrap.exe we can pickup from the exit event handler. The exit code is 0 when it prints or fails to print. Thus the best debugging is to create a batch file and mimic what is happening in the AIR application until you find the solution. For example double back slashing and having the “/t” typed as “\t” were problems I encountered.

Also you need Acrobat Reader installed.

Download Flex Builder 4 Project File

<?xml version="1.0" encoding="utf-8"?>
<!--
    Purpose: Demonstrate NativeProcess printing PDF with arcowrap from BioPDF
	Author: Lon Hosford www.lonhosford.com 908 996 3773
	Date: August 12, 2010

-->
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
					   xmlns:s="library://ns.adobe.com/flex/spark"
					   xmlns:mx="library://ns.adobe.com/flex/mx"
					   height = "500"
					   applicationComplete="applicationCompleteHandler(event)"
					   >
	<fx:Script>
		<![CDATA[
			import mx.controls.Alert;
			import mx.events.FlexEvent;
			import mx.managers.CursorManager;
			private const NATIVE_APP_REL_PATH:String = "native_apps"; 	// Relative to applicationDirectory
			private const PDF_PRINT_APP_FILE_NAME:String = "acrowrap.exe";
			private const PDF_PRINT_APP_SWITCH:String = "/t"; 			// Required switch for acrowrap.exe
																		// to close Adobe Reader.
			private const PDF_FILE_NAME:String = "readme.pdf";			// Const for demo only. Var is ok.
			private const PDF_FILE_REL_PATH:String = "in";				// Relative to applicationDirectory
			private const PRINTER_NAME:String = "";						// Blank will use default printer
																		// Network printer double backslash
																		// Ex:\\\\ServerName\\PrinterName

			private var displayFullScreen:Boolean = true;				// Full screen state
			private var displayAlwaysInFront:Boolean = true;			// Always in front state

			// Language localization
			private var lang_Attention:String = "Attention";
			private var lang_NativeNotSupported:String = "NativeProcess not supported. Flash Version: ";
			private var lang_IsWindowsOSOnly:String = " is a Windows OS only program.";
			[Bindable]
			private var lang_PrintPDFBtnLabel:String = "Print PDF";
			[Bindable]
			private var lang_ClearBtnLabel:String = "Clear";
			[Bindable]
			private var lang_ConsoleLabel:String = "Console:";
			protected function applicationCompleteHandler(event:FlexEvent):void
			{
				console(className + ".applicationCompleteHandler(...)");
				// Display full screen
				if (displayFullScreen)
				{
					stage.displayState = StageDisplayState.FULL_SCREEN;
				}
				// Make this application always in front
				if (displayAlwaysInFront)
				{
					this.alwaysInFront = true;
				}
				// NativeProcess not supported
				if (!NativeProcess.isSupported)
				{
					showNativeProcessUnsupported();
				}
			}
			/* ========================================================================
				NativeProcess
			======================================================================== */
			/**
			 * Print the pdf
			 * */
			public function printPDF():void
			{
				var process:NativeProcess;
				var backSlashPattern:RegExp = /\\/g;
				var exeFileName:File;
				var printFileName:String;
				var processArgs:Vector.<String>;

				setUIStateToPrinting();

				// Windows OS
				if (Capabilities.os.toLowerCase().indexOf("win") > -1)
				{
					// Create File object of the application directory
					exeFileName = File.applicationDirectory;
					// Refine the file object to the NativeApps subdirectory of application directory
					exeFileName = exeFileName.resolvePath(NATIVE_APP_REL_PATH);
					// Refine the file object the application file name
					exeFileName = exeFileName.resolvePath(PDF_PRINT_APP_FILE_NAME);
					printFileName = exeFileName.nativePath.substr(0, exeFileName.nativePath.indexOf(exeFileName.name))  +  PDF_FILE_REL_PATH + "\\" + PDF_FILE_NAME;
					printFileName = printFileName.replace(backSlashPattern, "\\\\")	;
					console("Printing " + printFileName);
					processArgs = new Vector.<String>();
					processArgs.push(PDF_PRINT_APP_SWITCH);
					processArgs.push(printFileName);
					processArgs.push(PRINTER_NAME); 

					var nativeProcessStartupInfo:NativeProcessStartupInfo;
					nativeProcessStartupInfo = new NativeProcessStartupInfo();
					nativeProcessStartupInfo.arguments = processArgs ;
					nativeProcessStartupInfo.executable = exeFileName  ;

					console("Executing " + nativeProcessStartupInfo.executable.nativePath);
					console("Arguments " + nativeProcessStartupInfo.arguments.toString());

					// Create NativeProcess, create listeners and start.
					process = new NativeProcess();
					process.addEventListener(NativeProcessExitEvent.EXIT, nativeProcessExitEventHandler);
					process.addEventListener(IOErrorEvent.STANDARD_ERROR_IO_ERROR, ioEventHandler);
					process.start(nativeProcessStartupInfo);
					this.focusManager.showFocus();
				}
				else
				{
					showUnsupportedOS();
				}
			}
			/**
			 * Signals the native process has exited. The exitCode property contains the
			 * value the process returns to the host operating system on exit.
			 * If the AIR application terminates the process by calling the exit()
			 * method of the NativeProcess object, the exitCode property is set to NaN.
			 * */
			public function nativeProcessExitEventHandler(event:NativeProcessExitEvent):void
			{
				console(className + ".nativeProcessExitEventHandler(...) - Process exited with " + event.exitCode);
				setUIStateToReady();
			}
			/**
			 * Signals that reading from the standard error (stderror) stream has failed.
			 * */
			public function ioEventHandler(event:IOErrorEvent):void
			{
				console(className + ".ioEventHandler(...) - IOError - " + event.toString());
				setUIStateToReady();
			}
			/* ========================================================================
				UI
			======================================================================== */
			/**
			 * Set the UI ready state. Not printing.
			 * */
			public function setUIStateToReady():void
			{
				print_btn.enabled = true;
				CursorManager.removeBusyCursor();
			}
			/**
			 * Set the UI printing state.
			 * */
			public function setUIStateToPrinting():void
			{
				print_btn.enabled = false;
				CursorManager.setBusyCursor();
			}
			/**
			 * Show unsupported OS.
			 * */
			public function showUnsupportedOS():void
			{
				Alert.show( PDF_PRINT_APP_FILE_NAME + lang_IsWindowsOSOnly, lang_Attention);
				setUIStateToReady()
			}
			/**
			 * Show native process unsupported.
			 * */
			public function showNativeProcessUnsupported():void
			{
				Alert.show( lang_NativeNotSupported + Capabilities.version, lang_Attention);
			}
			/**
			 * Clear the console
			 * */
			public function clearConsole():void
			{
				console_ta.text = "";
			}
			/**
			 * Append to the console
			 * */
			public function console(msg:String):void
			{
				trace(msg);
				console_ta.text += msg + "\n";
			}
		]]>
	</fx:Script>
	<s:VGroup width="100%" height = "100%" >
		<s:Button id = "print_btn" label="{lang_PrintPDFBtnLabel}" click="printPDF()"/>
		<s:Label text="{lang_ConsoleLabel}" textAlign="left"/>
		<s:TextArea id="console_ta" height = "100%" width="100%"/>
		<s:Button label="{lang_ClearBtnLabel}" click="clearConsole()"/>
	</s:VGroup>
</s:WindowedApplication>
Tagged as: , , , ,

11 Comments

  1. this is just what I needed. I am going to attempt to convert it over to html/javascript, the language my air app is written in. Do you foresee any issues I should look out for?

  2. 1 more thing – is there any way you’ve found to bundle the exe file for distribution? Is there a way to add the installation of the wrapper program into the air install process?

    • Then you export the build release the last screen will give you a choice of folders to include. One should have the native program in this case acrowrap.exe. In the example I simply put acroread.exe in a folder called native_apps and put that folder in the src folder for the Flex Builder 4 project. Based on that it automatically included in the AIR installer exe and the Air installed exe installed the acroread.exe in the folder. I have not tried where the native program is in a folder outside of the project folder.

      • thanks for the info – I’ve been using the command line sdk but I’m sure you can do something similar there.

        I realized though that this solution will only work on windows and my clients are all mac people so it looks like I’m going to have to go with the javascript injection method. Have you found anything that will work with a mac?

        • No but I am also looking for a MAC equal. Let me know if you find some possibilities and I will investigate them.

          This is great opportunity for a c whatever programmer to get noticed by doing one for MAC, WIndows and Linux or for BioPDF to do it.

  3. One more question (maybe)

    Do you know – will either your solution or the javascript injection method work if the pdf links are being pulled in dynamically?

    I need to pull pdfs from a server, create a list on the local machine, then print the list of pdfs as a batch. They will not be compiled into the air app. I am hoping I can dynamically create the html object page and print that

    thanks once again.

    • You can get pdf from anywhere. You just need to shuffle them into the folder you plan to print from or any folder and print from the folder depends on your file housekeeping plan.

      You can get it from a server for ecxample
      public function downloadPDF(url:String):void
      var req:URLRequest = new URLRequest(url);
      downLoadPDFStream = new URLStream();
      downLoadPDFStream.addEventListener(Event.COMPLETE, downLoadPDFStreamCompleteEvent);
      downLoadPDFStream.load(req);

      and land it in a place where you want.
      public function downLoadPDFStreamCompleteEvent(e:Event):void

      var fileData:ByteArray = new ByteArray();
      downLoadPDFStream.readBytes(fileData,0,downLoadPDFStream.bytesAvailable);

      var destinationFile:File = File.documentsDirectory.resolvePath({PATH_TO_FILE AND FILE NAME});

      var fileStream:FileStream = new FileStream();
      fileStream.open(destinationFile, FileMode.WRITE);
      fileStream.writeBytes(fileData,0,fileData.length);
      fileStream.close();

Trackbacks

  1. Flex learner | Blog | Adobe AIR NativeProcess Silent Print PDF with BioPDF's Acrobat ...

Leave a Response

You must be logged in to post a comment.