Categories
Articles

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.

[ad name=”Google Adsense”]

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.

[ad name=”Google Adsense”]

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>