65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 PDF995 和 FreePDF_XP 免费打印机进行自动化 PDF 转换

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (9投票s)

2008年10月6日

BSD

7分钟阅读

viewsIcon

133040

downloadIcon

3144

一个面向对象的类,使用免费工具自动创建 PDF 文件。

为了使用演示软件将文件转换为 PDF,您应该拥有免费的 PDF 打印机之一或 Adobe Acrobat。如果您没有其中任何一个,文件将被发送到您的默认打印机。

引言

如今,PDF 是许多报告工具中使用的标准文件格式。我用 VB.NET 开发的应用程序需要在工作中将大量的 Microsoft Project 和 Excel 文件转换为 PDF 文件,并将它们放在自定义指定的目录中,而无需任何用户交互。办公室里的几乎每个人都安装了免费的 PDF 打印机而不是 Adobe Acrobat——要么是 PDF995,要么是 FreePDF。它们都是非常好的转换器,但我的问题是它们总是弹出(自然地)另存为对话框。我阅读了两个打印机的文档,在互联网上搜索了一段时间,实际上找到了许多关于如何自动化打印的示例,但没有一个是用 VB.NET 编写的。所以,我想,VB.NET 已经是一个不错的 OOP 语言,为什么不把它用来创建一个易于使用的类,以便轻松地将文件转换为 PDF 呢?

背景

如果您有 Adobe Acrobat,您也会有一个PDF 打印机。Acrobat 在 Acrobat SDK 中也提供了非常方便且易于理解的示例,说明如何使用 VB.NET 静默地将任何文件转换为 PDF。在它们的示例中,它们还展示了如何找到每个已知文件扩展名的默认应用程序。对于这个示例,这并不需要,因为 Windows 中大多数文件扩展名已经有了一个关联的应用程序,所以我们只需要启动一个带有 print 动词的进程。首先,我们需要知道动词是什么。根据 Microsoft 的说法,动词是

"文件关联使用动词作为对Shell命名空间中的项(包括文件和文件夹)所执行操作的简写。动词与 Shell 快捷菜单密切相关,其中的每个菜单项都与一个动词相关联。IContextMenuShellExecute支持动词的规范名称;规范动词名称在不同平台或语言下保持不变,这使得开发人员无需了解Shell命名空间项的详细信息即可调用已知的规范动词。例如,ShellExecute可以对 Microsoft Word 文档调用Print动词,这会请求已安装的处理程序打印文档,而无需知道是 Word、Microsoft WordPad 还是其他应用程序执行打印。"

还有

"文件关联通常有一个首选操作,当用户双击某种类型的文件时会执行该操作。此首选操作与称为主动词的动词相关联。主选择由shell键的默认值指定,或者如果shell键没有默认值,则由open键指定。最常见的主选择是open。然而,在媒体文件中,最常见的主选择是play。主选择也称为默认选择。

...

"print:应用程序打印文件内容并退出,显示尽可能少的信息以完成任务。"

有关动词和文件关联的更详细信息可以在此处找到。

Using the Code

现在我们知道了动词是什么,我们将用它来打印我们的文件。打印现有文件的一种方法是启动一个进程,将文件名和动词“PRINT”提供给 StartInfo 结构。Windows 会找到关联的应用程序,使用关联的应用程序打开文件,将其发送到默认打印机,然后关闭应用程序。

在开始解释 PDF995 和 FreePDF XP 的细节之前,我们将简要讨论启动进程。

.NET Framework 拥有 System.Diagnostics 命名空间,其中提供了 Process 类等。有了它,程序员的生活变得非常轻松。为了启动一个进程,使其窗口不可见,并等待进程准备就绪(打印文件时可以做到这一点),我们编写

'Define properties for the print process
Dim procStartInfo As ProcessStartInfo = Nothing

procStartInfo = New ProcessStartInfo
procStartInfo.FileName = "c:\file.xls" ' e.g. an excel file
procStartInfo.Verb = "Print" ' print please 

'Make process invisible
procStartInfo.CreateNoWindow = True
procStartInfo.WindowStyle = ProcessWindowStyle.Hidden

'start print process for the file with/from the associated application
Dim procPrint As Process = Process.Start(procStartInfo)

If Not procPrint Is Nothing Then
    procPrint.WaitForExit()
End If

有关 Process 类的更多信息可以在这里找到:.NET Framework Process 类。现在,让我们看看上面提到的 PDF 打印机是如何工作的。

PDF995 打印机使用 INI 文件存储其设置。INI 文件位于打印机安装路径的 res 子目录中。进一步阅读开发人员的 FAQ 说,为了绕过“另存为”对话框并将 PDF 文件放置到自定义目录中,我们只需要覆盖以下 INI 文件选项:

  1. OutputFile – 将导致文件以该名称保存在给定文件夹中。
  2. AutoLaunch – 将其设置为零将阻止每次创建 PDF 文件后打开 PDF 阅读器应用程序。

anyfiletopdf1.gif

另一方面,FreePDF XP 打印机使用 Windows 注册表存储其设置,并且首先将文件转换为 PostScript,然后将 PS 文件转换为 PDF 文件。要将 PDF 文件放入用户指定的目录,我们需要暂时将 psDir 注册表值设置为所需的目录字符串。然后,管理员手册说,为了使用 freepdf.exe 应用程序从命令行转换 PDF 文件,我们需要以下选项:

  1. “/3 delps, end” - 意思是转换文件,删除 PS 文件,然后退出 FreePDF 应用程序。
  2. 所需的 PDF 文件名以及我们从中创建 PDF 的 PS 文件名。我们将一次只转换一个文件,FreePDF PS 文件始终具有相同的名称格式:当前用户名000001.ps。如果我们排队了多个文件,我们将有 000002、000003 等,但由于我们总是转换一个文件并等待打印机完成,这对我们来说应该可以正常工作。

anyfiletopdf2.gif

总结我们需要采取的行动,我们得到以下内容:

PDF995 将其设置存储在 INI 文件中,因此我们需要:

  1. 通过注册表检索打印机安装路径
  2. 保存安装路径 /res 目录中的原始 INI 文件
  3. 创建一个自定义 INI
  4. 转换为 PDF
  5. 恢复原始 INI 文件

FreePDF XP 将其设置存储在注册表中(完整路径:HKEY_LOCAL_MACHINE\SOFTWARE\shbox\FreePdfXP)并使用 PS 中间文件,因此我们需要:

  1. 保存原始注册表值
  2. 写入自定义值
  3. 创建 PS
  4. 将 PS 转换为 PDF
  5. 恢复注册表值

现在我们知道了如何设计我们的基类打印机。我们绝对需要一个函数来保存原始打印机设置,一个函数来恢复原始打印机设置,一个变量来保存要打印的所需文件的完整路径,以及一个 print 函数。由于两个打印机的设置方法不同,因此将此函数声明为 abstract 是有意义的,并且由于创建打印过程始终相同,因此重写 print 函数是有意义的。为了清晰起见,这只是部分代码。完整的源代码可以在 zip 文件中找到。

Imports System.IO

'we want an abstract base class
Public MustInherit Class CPrinter

    Protected _strFileToPrint As String = ""
    'save original settings
    Protected MustOverride Sub pushSettings()
    'restore original settings
    Protected MustOverride Sub popSettings()
    'start printing process (adopted from Adobe PDF SDK examples)
    Public Overridable Sub PrintFile()

        'Define properties for the print process
        Dim procStartInfo As ProcessStartInfo = Nothing

        If System.IO.File.Exists(_strFileToPrint) Then

            procStartInfo = New ProcessStartInfo
            procStartInfo.FileName = _strFileToPrint
            procStartInfo.Verb = "Print"

            'Make process invisible
            procStartInfo.CreateNoWindow = True
            procStartInfo.WindowStyle = ProcessWindowStyle.Hidden

            'start print process for the file with/from the associated application
            Dim procPrint As Process = Process.Start(procStartInfo)
            'give the system some time
            System.Threading.Thread.Sleep(2500)

            If Not procPrint Is Nothing Then
                procPrint.WaitForExit()
            End If

        End If

    End Sub

End Class

现在我们有了一个 abstract 基类,我们需要继承它。目前,这些类会将 PDF 文件放置在与原始文件相同的目录中。

PDF995 打印机类

Imports Microsoft.Win32
Imports System.IO

Public Class CPrinterPDF995 : Inherits CPrinter

    'the INI that we want to change in oder to "silently" use the PDF printer
    Private Const strOriginalIniFile As String = "pdf995.ini"
    'we want to recover the original INI file after every print
    Private Const strOriginalIniSave As String = "pdf995.ini_ORIGINAL"
    'used to locate PDF995 installation directory
    Private Const strRegPath As String = _ 
    "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pdf995"

    Protected Overrides Sub pushSettings()

        Dim strIniPath As String = RetrieveIniPath()

        'save original INI file first
        FileSystem.Rename(strIniPath + strOriginalIniFile, _
			strIniPath + strOriginalIniSave)

        Me.createCustomIni()

    End Sub

    Protected Overrides Sub popSettings()

        Dim strIniPath As String = RetrieveIniPath()

        'restore original INI file
        File.Delete(strIniPath + strOriginalIniFile)
        FileSystem.Rename(strIniPath + strOriginalIniSave, _
			strIniPath + strOriginalIniFile)

    End Sub

    Private Sub createIni()

        Dim strFileToPrint As String = MyBase.strFileToPrint
        Dim strIniPath As String = RetrieveIniPath()
        Dim fi As System.IO.FileInfo = New System.IO.FileInfo(strFileToPrint)
        Dim strPDFOutputDirectory As String = fi.DirectoryName + "\"
        Dim oWriter As StreamWriter = Nothing

        fi = Nothing

        'write INI file, create PDF from "strFileToPrint" and save it to "directory"
        oWriter = New StreamWriter(strIniPath + "pdf995.ini", False, _ 
        System.Text.Encoding.Unicode)

        oWriter.WriteLine("[Parameters]")
        oWriter.WriteLine("Install = 0")
        oWriter.WriteLine("Quiet = 1")
        oWriter.WriteLine("AutoLaunch = 0")
        oWriter.WriteLine("Document Name = " + strFileToPrint)
        oWriter.WriteLine("User File = " + strFileToPrint + ".pdf")
        oWriter.WriteLine("Output File = " + strFileToPrint + ".pdf")
        oWriter.WriteLine("Initial Dir = " + strPDFOutputDirectory)
        oWriter.WriteLine("Use GPL Ghostcript = 1")

        oWriter.Close()

        oWriter = Nothing

        System.Threading.Thread.Sleep(1000) ' give Pdf995 some time

    End Sub

    Public Overrides Sub printFile()

        Me.pushSettings()
        MyBase.PrintFile()
        Me.popSettings()

    End Sub

End Class

FreePDF 打印机类

Imports Microsoft.Win32
Imports System.IO

Public Class CPrinterXFreePDF : Inherits CPrinter

    'freepdf.exe full path
    Private strFreePDFExe As String = "" 
    'PS temp directory, needed for silent ps to pdf creation
    Private strPSTempDir As String = "" 
    'FreePDF path in registry
    Private Const strRegPath As String = "SOFTWARE\shbox\FreePdfXP"

    Public Overrides Sub printFile()

        Dim fi As System.IO.FileInfo = New System.IO.FileInfo(fileToPrint)
        Dim strFileToPrintDir As String = fi.DirectoryName + "\"
        fi = Nothing

        Me.pushSettings()

        Dim strCmd As String = strFreePDFExe + " /3 delps,end ""eBook"" """ + _ 
        fileToPrint + ".pdf"" """ + _
        strFileToPrintDir + Environment.UserName + _
        "000001.ps"""

        'create the PS file
        MyBase.PrintFile()
        'create PDF from PS file, can also be started with System.Diagnostics.Process
        Shell(strCmd, AppWinStyle.Hide, True)

        Me.popSettings()

    End Sub

    Protected Overrides Sub pushSettings()

        Dim regKey As RegistryKey = Nothing
        Dim fi As System.IO.FileInfo = New System.IO.FileInfo(MyBase.strFileToPrint)
        Dim strPSPath As String = fi.DirectoryName + "\"
        fi = Nothing

        regKey = Registry.LocalMachine.OpenSubKey(strRegPath, True)

        'fpDir = freepdf.exe directory
        strFreePDFExe = regKey.GetValue("fpDir") + "freepdf.exe"
        'psDir = location for temp PS files, save it
        strPSTempDir = regKey.GetValue("psDir")

        'set currently new PS files location
        If Not String.IsNullOrEmpty(strPSPath) Then
            regKey.SetValue("psDir", strPSPath)
        End If

        regKey.Close()

        regKey = Nothing

    End Sub

    Protected Overrides Sub popSettings()

        Dim regKey As RegistryKey = Nothing

        regKey = Registry.LocalMachine.OpenSubKey(strRegPath, True)

        'restore original psDir
        regKey.SetValue("psDir", strPSTempDir)

        regKey.Close()

        regKey = Nothing

    End Sub

您可以随时从其中一个类实例化一个对象并像这样转换文件:

Dim oPrinter As CPrinterPDF995 = New CPrinterPDF995

oPrinter.fileToPrint = "c:\temp\report.xls"
oPrinter.printFile()

oPrinter = Nothing

我希望注释掉的代码能够自明,并能帮助您在家或工作节省一些时间。如果您在理解代码时遇到任何困难,请提问,我很乐意为您提供帮助。另外,不要忘记通过控制面板或在代码中将所需的打印机设置为系统默认打印机。

anyfiletopdf3.gif

祝您转换愉快! :-)

历史

  • 2008 年 10 月 5 日:v1.00 - 初始版本
  • 2008 年 10 月 13 日:添加了 C# 源代码
© . All rights reserved.