using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Collections;
using System.IO;
namespace ConvertPDF
{
/// <summary>
/// Create by : TaGoH
/// URL of the last version: http://www.codeproject.com/KB/cs/GhostScriptUseWithCSharp.aspx
/// Description:
/// Class to convert a pdf to an image using GhostScript DLL
/// A big Credit for this code go to:Rangel Avulso
/// I mainly create a better interface and refactor it to made it ready to use!
/// </summary>
/// <see cref="http://www.codeproject.com/KB/cs/GhostScriptUseWithCSharp.aspx"/>
/// <seealso cref="http://www.hrangel.com.br/index.php/2006/12/04/converter-pdf-para-imagem-jpeg-em-c/"/>
public class PDFConvert
{
#region Windows Import
/// <summary>Needed to copy memory from one location to another, used to fill the struct</summary>
/// <param name="Destination"></param>
/// <param name="Source"></param>
/// <param name="Length"></param>
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);
#endregion
#region GhostScript Import
/// <summary>Create a new instance of Ghostscript. This instance is passed to most other gsapi functions. The caller_handle will be provided to callback functions.
/// At this stage, Ghostscript supports only one instance. </summary>
/// <param name="pinstance"></param>
/// <param name="caller_handle"></param>
/// <returns></returns>
[DllImport("gsdll32.dll", EntryPoint="gsapi_new_instance")]
private static extern int gsapi_new_instance (out IntPtr pinstance, IntPtr caller_handle);
/// <summary>This is the important function that will perform the conversion</summary>
/// <param name="instance"></param>
/// <param name="argc"></param>
/// <param name="argv"></param>
/// <returns></returns>
[DllImport("gsdll32.dll", EntryPoint="gsapi_init_with_args")]
private static extern int gsapi_init_with_args (IntPtr instance, int argc, IntPtr argv);
/// <summary>
/// Exit the interpreter. This must be called on shutdown if gsapi_init_with_args() has been called, and just before gsapi_delete_instance().
/// </summary>
/// <param name="instance"></param>
/// <returns></returns>
[DllImport("gsdll32.dll", EntryPoint="gsapi_exit")]
private static extern int gsapi_exit (IntPtr instance);
/// <summary>
/// Destroy an instance of Ghostscript. Before you call this, Ghostscript must have finished. If Ghostscript has been initialised, you must call gsapi_exit before gsapi_delete_instance.
/// </summary>
/// <param name="instance"></param>
[DllImport("gsdll32.dll", EntryPoint="gsapi_delete_instance")]
private static extern void gsapi_delete_instance (IntPtr instance);
/// <summary>Get info about the version of Ghostscript i'm using</summary>
/// <param name="pGSRevisionInfo"></param>
/// <param name="intLen"></param>
/// <returns></returns>
[DllImport("gsdll32.dll", EntryPoint="gsapi_revision")]
private static extern int gsapi_revision (ref GS_Revision pGSRevisionInfo , int intLen );
/// <summary>Use a different I/O</summary>
/// <param name="lngGSInstance"></param>
/// <param name="gsdll_stdin">Function that menage the Standard INPUT</param>
/// <param name="gsdll_stdout">Function that menage the Standard OUTPUT</param>
/// <param name="gsdll_stderr">Function that menage the Standard ERROR output</param>
/// <returns></returns>
[DllImport("gsdll32.dll", EntryPoint = "gsapi_set_stdio")]
private static extern int gsapi_set_stdio(IntPtr lngGSInstance, StdioCallBack gsdll_stdin, StdioCallBack gsdll_stdout, StdioCallBack gsdll_stderr);
#endregion
#region Const
const int e_Quit=-101;
const int e_NeedInput = -106;
#endregion
#region Variables
private string _sDeviceFormat;
private string _sParametersUsed;
private int _iWidth;
private int _iHeight;
private int _iResolutionX;
private int _iResolutionY;
private int _iJPEGQuality;
/// <summary>The first page to convert in image</summary>
private int _iFirstPageToConvert = -1;
/// <summary>The last page to conver in an image</summary>
private int _iLastPageToConvert = -1;
private Boolean _bFitPage;
private Boolean _bThrowOnlyException = false;
private bool _bRedirectIO = false;
private IntPtr _objHandle;
private System.Diagnostics.Process myProcess;
public StringBuilder output;
//public string output;
//private List<byte> outputBytes;
//public string error;
#endregion
#region Proprieties
/// <summary>
/// What format to use to convert
/// is suggested to use png256 instead of jpeg for document!
/// they are smaller and better suited!
/// </summary>
/// <see cref="http://pages.cs.wisc.edu/~ghost/doc/cvs/Devices.htm"/>
public string OutputFormat
{
get { return _sDeviceFormat; }
set { _sDeviceFormat = value; }
}
public string ParametersUsed
{
get { return _sParametersUsed; }
set { _sParametersUsed = value; }
}
public int Width
{
get { return _iWidth; }
set { _iWidth = value; }
}
public int Height
{
get { return _iHeight; }
set { _iHeight = value; }
}
public int ResolutionX
{
get { return _iResolutionX; }
set { _iResolutionX = value; }
}
public int ResolutionY
{
get { return _iResolutionY; }
set { _iResolutionY = value; }
}
public Boolean FitPage
{
get { return _bFitPage; }
set { _bFitPage = value; }
}
/// <summary>Quality of compression of JPG</summary>
public int JPEGQuality
{
get { return _iJPEGQuality; }
set { _iJPEGQuality = value; }
}
/// <summary>The first page to convert in image</summary>
public int FirstPageToConvert
{
get { return _iFirstPageToConvert; }
set { _iFirstPageToConvert = value; }
}
/// <summary>The last page to conver in an image</summary>
public int LastPageToConvert
{
get { return _iLastPageToConvert; }
set { _iLastPageToConvert = value; }
}
/// <summary>Set to True if u want the program to never display Messagebox
/// but otherwise throw exception</summary>
public Boolean ThrowOnlyException
{
get { return _bThrowOnlyException; }
set { _bThrowOnlyException = value; }
}
/// <summary>If i should redirect the Output of Ghostscript library somewhere</summary>
public bool RedirectIO
{
get { return _bRedirectIO; }
set { _bRedirectIO = value; }
}
#endregion
#region Init
public PDFConvert(IntPtr objHandle)
{
_objHandle = objHandle;
}
public PDFConvert()
{
_objHandle = IntPtr.Zero;
}
#endregion
#region Convert
/// <summary>Convert a single file!</summary>
/// <param name="inputFile">The file PDf to convert</param>
/// <param name="outputFile">The image file that will be created</param>
/// <remarks>You must pass all the parameter for the conversion
/// as Proprieties of this class</remarks>
/// <returns>True if the conversion succed!</returns>
public bool Convert(string inputFile, string outputFile)
{
return Convert(inputFile, outputFile, _bThrowOnlyException);
}
//public System.Drawing.Image Convert(string inputFile)
//{
// _bRedirectIO = true;
// if (Convert(inputFile, "%stdout", _bThrowOnlyException))
// {
// if ((output != null) && (output.Length > 0))
// {
// //StringReader sr = new StringReader(output.ToString());
// //MemoryStream ms = new MemoryStream(UTF8Encoding.Default.GetBytes(output.ToString()));
// System.Drawing.Image returnImage = System.Drawing.Image.FromStream(myProcess.StandardOutput.BaseStream).Clone() as System.Drawing.Image;
// //ms.Close();
// return returnImage;
// }
// }
// return null;
//}
/// <summary>Convert a single file!</summary>
/// <param name="inputFile">The file PDf to convert</param>
/// <param name="outputFile">The image file that will be created</param>
/// <param name="throwException">if the function should throw an exception
/// or display a message box</param>
/// <remarks>You must pass all the parameter for the conversion
/// as Proprieties of this class</remarks>
/// <returns>True if the conversion succed!</returns>
public bool Convert(string inputFile, string outputFile,bool throwException)
{
#region Check Input
//Avoid to work when the file doesn't exist
if (string.IsNullOrEmpty(inputFile))
{
if (throwException)
throw new ArgumentNullException("inputFile");
else
{
System.Windows.Forms.MessageBox.Show("The inputfile is missing");
return false;
}
}
if (!System.IO.File.Exists(inputFile))
{
if (throwException)
throw new ArgumentException(string.Format("The file :'{0}' doesn't exist", inputFile), "inputFile");
else
{
System.Windows.Forms.MessageBox.Show(string.Format("The file :'{0}' doesn't exist", inputFile));
return false;
}
}
if (string.IsNullOrEmpty(_sDeviceFormat))
{
if (throwException)
throw new ArgumentNullException("Device");
else
{
System.Windows.Forms.MessageBox.Show("You didn't provide a device for the conversion");
return false;
}
}
#endregion
#region Variables
int intReturn, intCounter, intElementCount;
//The pointer to the current istance of the dll
IntPtr intGSInstanceHandle;
object[] aAnsiArgs;
IntPtr[] aPtrArgs;
GCHandle[] aGCHandle;
IntPtr callerHandle, intptrArgs;
GCHandle gchandleArgs;
#endregion
//Generate the list of the parameters i need to pass to the dll
string[] sArgs = GetGeneratedArgs(inputFile, outputFile);
#region Convert Unicode strings to null terminated ANSI byte arrays
// Convert the Unicode strings to null terminated ANSI byte arrays
// then get pointers to the byte arrays.
intElementCount = sArgs.Length;
aAnsiArgs = new object[intElementCount];
aPtrArgs = new IntPtr[intElementCount];
aGCHandle = new GCHandle[intElementCount];
//Convert the parameters
for (intCounter = 0; intCounter < intElementCount; intCounter++)
{
aAnsiArgs[intCounter] = StringToAnsiZ(sArgs[intCounter]);
aGCHandle[intCounter] = GCHandle.Alloc(aAnsiArgs[intCounter], GCHandleType.Pinned);
aPtrArgs[intCounter] = aGCHandle[intCounter].AddrOfPinnedObject();
}
gchandleArgs = GCHandle.Alloc(aPtrArgs, GCHandleType.Pinned);
intptrArgs = gchandleArgs.AddrOfPinnedObject();
#endregion
#region Create a new istance of the library!
intReturn = -1;
try
{
intReturn = gsapi_new_instance(out intGSInstanceHandle, _objHandle);
//Be sure that we create an istance!
if (intReturn < 0)
{
for (intCounter = 0; intCounter < intReturn; intCounter++)
aGCHandle[intCounter].Free();
gchandleArgs.Free();
if (throwException)
throw new ApplicationException("I can't create a new istance of Ghostscript please verify no other istance are running!");
else
{
System.Windows.Forms.MessageBox.Show("I can't create a new istance of Ghostscript please verify no other istance are running!");
return false;
}
}
}
catch (DllNotFoundException ex)
{//in this case the dll we r using isn't the dll we expect
for (intCounter = 0; intCounter < intReturn; intCounter++)
aGCHandle[intCounter].Free();
gchandleArgs.Free();
if (throwException)
throw new ApplicationException("The gs32dll.dll in the program directory doesn't "+
"expose the methods i need!\nplease download the version 8.63 from the original website!");
else
{
System.Windows.Forms.MessageBox.Show("The gs32dll.dll in the program directory"+
" doesn't expose the methods i need!\nPlease download the version 8.63 "+
"from the original website!");
return false;
}
}
callerHandle = IntPtr.Zero;//remove unwanter handler
#endregion
#region Capture the I/O
if (_bRedirectIO)
{
StdioCallBack stdinCallback = new StdioCallBack(gsdll_stdin);
StdioCallBack stdoutCallback = new StdioCallBack(gsdll_stdout);
StdioCallBack stderrCallback = new StdioCallBack(gsdll_stderr);
intReturn = gsapi_set_stdio(intGSInstanceHandle, stdinCallback, stdoutCallback, stderrCallback);
if (output == null) output = new StringBuilder();
else output.Remove(0, output.Length);
myProcess = System.Diagnostics.Process.GetCurrentProcess();
myProcess.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(SaveOutputToImage);
}
#endregion
intReturn = -1;//if nothing change it there is an error!
//Ok now is time to call the interesting method
try { intReturn = gsapi_init_with_args(intGSInstanceHandle, intElementCount, intptrArgs); }
catch (Exception ex)
{
if (throwException)
throw new ApplicationException(ex.Message, ex);
else
System.Windows.Forms.MessageBox.Show(ex.Message);
}
finally//No matter what happen i MUST close the istance!
{ //free all the memory
for (intCounter = 0; intCounter < intReturn; intCounter++)
{
aGCHandle[intCounter].Free();
}
gchandleArgs.Free();
gsapi_exit(intGSInstanceHandle);//Close the istance
gsapi_delete_instance(intGSInstanceHandle);//delete it
//In case i was looking for output now stop
if (myProcess != null) myProcess.OutputDataReceived -= new System.Diagnostics.DataReceivedEventHandler(SaveOutputToImage);
}
//Conversion was successfull if return code was 0 or e_Quit
return (intReturn == 0) | (intReturn == e_Quit);//e_Quit = -101
}
void SaveOutputToImage(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
output.Append(e.Data);
}
#endregion
#region Accessory Functions
/// <summary>This function create the list of parameters to pass to the dll</summary>
/// <param name="inputFile">the file to convert</param>
/// <param name="outputFile">where to write the image</param>
/// <returns>the list of the arguments</returns>
private string[] GetGeneratedArgs(string inputFile, string outputFile)
{
// Count how many extra args are need - HRangel - 11/29/2006, 3:13:43 PM
ArrayList lstExtraArgs = new ArrayList();
if ( _sDeviceFormat=="jpeg" && _iJPEGQuality > 0 && _iJPEGQuality < 101)
lstExtraArgs.Add("-dJPEGQ=" + _iJPEGQuality);
if (_iWidth > 0 && _iHeight > 0)
lstExtraArgs.Add("-g" + _iWidth + "x" + _iHeight);
if (_bFitPage)
lstExtraArgs.Add("-dPDFFitPage");
if (_iResolutionX > 0)
{
if (_iResolutionY > 0)
lstExtraArgs.Add("-r" + _iResolutionX + "x" + _iResolutionY);
else
lstExtraArgs.Add("-r" + _iResolutionX);
}
if (_iFirstPageToConvert > 0)
lstExtraArgs.Add("-dFirstPage=" + _iFirstPageToConvert);
if (_iLastPageToConvert > 0)
lstExtraArgs.Add("-dLastPage=" + _iLastPageToConvert);
//If i want to redirect write it to the standard output!
if (_bRedirectIO)
{
outputFile = "-";
//In this case you must also use the -q switch to prevent Ghostscript
//from writing messages to standard output which become
//mixed with the intended output stream.
lstExtraArgs.Add("-q");
}
int iFixedCount = 7;
int iExtraArgsCount = lstExtraArgs.Count;
string[] args = new string[iFixedCount + lstExtraArgs.Count];
args[0]="pdf2img";//this parameter have little real use
args[1]="-dNOPAUSE";//I don't want interruptions
args[2]="-dBATCH";//stop after
args[3]="-dSAFER";
args[4]="-sDEVICE="+_sDeviceFormat;//what kind of export format i should provide
//For a complete list watch here:
//http://pages.cs.wisc.edu/~ghost/doc/cvs/Devices.htm
//Fill the remaining parameters
for (int i=0; i < iExtraArgsCount; i++)
{
args[5+i] = (string) lstExtraArgs[i];
}
//Fill outputfile and inputfile
args[5 + iExtraArgsCount] = string.Format("-sOutputFile={0}",outputFile);
args[6 + iExtraArgsCount] = string.Format("{0}",inputFile);
//Ok now save them to be shown 4 debug use
_sParametersUsed = "";
foreach (string arg in args)
_sParametersUsed += " " + arg;
return args;
}
/// <summary>
/// Convert a Unicode string to a null terminated Ansi string for Ghostscript.
/// The result is stored in a byte array
/// </summary>
/// <param name="str">The parameter i want to convert</param>
/// <returns>the byte array that contain the string</returns>
private static byte[] StringToAnsiZ(string str)
{
// Later you will need to convert
// this byte array to a pointer with
// GCHandle.Alloc(XXXX, GCHandleType.Pinned)
// and GSHandle.AddrOfPinnedObject()
//int intElementCount,intCounter;
if (str == null) str = String.Empty;
byte[] testVector = new byte[str.Length + 1];
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
encoding.GetBytes(str,0,str.Length,testVector,0);
testVector[str.Length] = 0;
return testVector;
}
/// <summary>Convert a Pointer to a string to a real string</summary>
/// <param name="strz"></param>
/// <returns></returns>
public static string AnsiZtoString(IntPtr strz)
{
return Marshal.PtrToStringAnsi(strz);
}
#endregion
#region Menage Standard Input & Standard Output
public int gsdll_stdin(IntPtr intGSInstanceHandle, IntPtr strz, int intBytes)
{
// This is dumb code that reads one byte at a time
// Ghostscript doesn't mind this, it is just very slow
if (intBytes == 0)
return 0;
else
{
int ich = Console.Read();
if (ich == -1)
return 0; // EOF
else
{
byte bch = (byte)ich;
GCHandle gcByte = GCHandle.Alloc(bch, GCHandleType.Pinned);
IntPtr ptrByte = gcByte.AddrOfPinnedObject();
CopyMemory(strz, ptrByte, 1);
ptrByte = IntPtr.Zero;
gcByte.Free();
return 1;
}
}
}
public int gsdll_stdout(IntPtr intGSInstanceHandle, IntPtr strz , int intBytes)
{
if (intBytes > 0)
{
Console.Write(Marshal.PtrToStringAnsi(strz));
}
return 0;
}
public int gsdll_stderr(IntPtr intGSInstanceHandle, IntPtr strz, int intBytes )
{
//return gsdll_stdout(intGSInstanceHandle, strz, intBytes);
Console.Write(Marshal.PtrToStringAnsi(strz));
return intBytes;
}
#endregion
#region Menage Revision
public GhostScriptRevision GetRevision()
{
// Check revision number of Ghostscript
int intReturn;
GS_Revision udtGSRevInfo = new GS_Revision();
GhostScriptRevision output;
GCHandle gcRevision;
gcRevision = GCHandle.Alloc(udtGSRevInfo, GCHandleType.Pinned);
intReturn = gsapi_revision(ref udtGSRevInfo, 16);
output.intRevision = udtGSRevInfo.intRevision;
output.intRevisionDate = udtGSRevInfo.intRevisionDate;
output.ProductInformation = AnsiZtoString(udtGSRevInfo.strProduct);
output.CopyrightInformations = AnsiZtoString(udtGSRevInfo.strCopyright);
gcRevision.Free();
return output;
}
#endregion
}
/// <summary>Delegate used by Ghostscript to perform I/O operations</summary>
/// <param name="handle"></param>
/// <param name="strptr"></param>
/// <param name="count"></param>
/// <returns></returns>
public delegate int StdioCallBack(IntPtr handle, IntPtr strptr, int count);
/// <summary>This struct is filled with the information of the version of this ghostscript</summary>
/// <remarks>Have the layout defined cuz i will fill it with a kernel copy memory</remarks>
[StructLayout(LayoutKind.Sequential)]
struct GS_Revision
{
public IntPtr strProduct;
public IntPtr strCopyright;
public int intRevision;
public int intRevisionDate;
}
public struct GhostScriptRevision
{
public string ProductInformation;
public string CopyrightInformations;
public int intRevision;
public int intRevisionDate;
}
}