打印支持应用程序 v3 API 设计指南

本文为打印机 OEM 和 IHV 提供了有关在其设备上实现 v3 打印支持应用 (PSA) 的指南和示例。

术语

Term 定义
PSA 打印支持应用程序。 使用此文档中 API 的 UWP 应用程序。
IPP Internet 打印协议。 从客户端设备用于与打印机交互以检索和设置打印首选项,以及发送要打印的文档。
打印支持关联的打印机 连接到 PSA 的物理 IPP 打印机。
IPP 打印机 支持 IPP 协议的打印机。
PDL 页面说明语言。 将文档发送到打印机的格式。
关联的 PSA 打印机 与 PSA 应用程序相关联的物理 IPP 打印机。
打印设备功能 用于定义打印机功能的 XML 文档格式。
PrintSupportExtension 负责提供打印机约束扩展功能的 PSA 后台任务。

本文包含打印支持应用设计指南中所述的现有打印支持应用程序公共 API 的 v3 扩展,Windows.Graphics.Printing.PrintSupport 命名空间。 PSA API 使打印机制造商能够开发硬件支持应用,这些应用可以在使用收件箱Microsoft IPP 类驱动程序时增强 Windows 用户的打印体验,而无需开发自定义驱动程序。 打印组件通过 PSA 代理进程与 PSA 应用程序进行通信。

有关详细信息,请参阅以下文章:

主题 描述
“打印支持应用”设计指南 向在设备上实现打印支持应用 (PSA) 的打印机 OEM 和 IHV 提供相关指南和示例。
打印支持应用 v4 API 设计指南 向在设备上实现 v4 打印支持应用 (PSA) 的打印机 OEM 和 IHV 提供相关指南和示例。
打印支持虚拟打印机的 MSIX Manifest 规范 为正在实现打印支持虚拟打印机的打印机 OEM 和 IHV 提供 MSIX 清单指南和示例。
打印支持应用关联 提供有关将打印支持应用(PSA)与打印机相关联的指导和示例。

API 的重要扩展如下所示:

  • IPP 压缩 - PSA v3 API 增加了一项功能,通过在支持该功能的 IPP 打印机打印作业中引入 IPP 压缩功能,来增强 IPP 打印。 某些 PSA 可能有自定义压缩,这意味着 IPP 作业具有影响性能的双重压缩。 为了缓解此问题,PSA v3 API 在 PrintWorkflowJobStartingEventArgs (PSA v1 API)运行时类中引入了一个属性 IsIppCompressionEnabled,以及一个函数 DisableIppCompressionForJob(用于在需要时禁用当前作业的压缩)。

  • IPP 作业错误处理和错误 Toast - PSA v3 API 在 PrintWorkflowJobBackgroundSession (PSA v1 API) 运行时类中引入了 JobIssueDetected 事件。 每当 PSA 检测到打印作业中出现错误或警告时,就会触发事件。 然后,PSA 负责向用户显示错误 Toast。 当 PSA 注册此事件并在 PrintWorkflowJobIssueDetectedEventArgs 中将 SkipSystemErrorToast 属性设置为 true 时,它会通知打印系统不显示 Windows 打印系统 Toast。 当用户与 Toast 交互时,PSA v3 API 还提供了一种供 PSA 启动 UI 的机制。

  • 自定义 IPP 通信超时 - PSA v3 API 提供了一个 API,PSA 可以通过此 API 替代 IPP 超时。 此外,PrintSupportIppCommunicationConfiguration 运行时类将添加到 PrintSupportPrintDeviceCapabilitiesChangedEventArgs,用于处理 IPP 通信超时。 此外,PSA v3 API 引入了一个事件,该事件在 IPP 通信出错时引发。 引入了该事件,以便 IHV 可以调查失败并相应地调整超时值。

  • 支持 IPPFaxOut - PSA v3 API 向打印系统添加了一项功能以支持 IPPFaxOut 打印机。 为了支持传真,PSA 会提供一个将 XPS 转换成 Tiff 的呈现筛选器。 由于 PSA 在转换为 TIFF 之前可能会操作 XPS 内容,因此它在 PrintWorkflowPdlConversionType 中提供 XpsToTiff 枚举值,以便 PSA 可以访问 XPS 到 TIFF 转换器。 此外,它还向 IppPrintDevice 运行时类提供 IsIPPFaxOutPrinter 属性,以便 PSA 可以区分标准打印机和 IPPFaxOut 打印机。

禁用 IPP 压缩

以下代码示例显示了 IPP 压缩。

public sealed class PrintSupportWorkflowBackgroundTask : IBackgroundTask
{
    public BackgroundTaskDeferral TaskInstanceDeferral { get; set; }
    private PrintWorkflowJobBackgroundSession session;

    public void Run(IBackgroundTaskInstance taskInstance)
    {
        TaskInstanceDeferral = taskInstance.GetDeferral();

        if (taskInstance.TriggerDetails is PrintWorkflowJobTriggerDetails jobDetails)
        {
            session = jobDetails.PrintWorkflowJobSession;
            session.JobStarting += OnJobStarting;
            session.PdlModificationRequested += OnPdlModificationRequested;
            session.JobIssueDetected += OnJobIssueDetected;

            // Make sure to register all the event handlers before PrintWorkflowJobBackgroundSession.Start is called.
            session.Start();
        }
    }

    private void OnJobStarting(PrintWorkflowJobBackgroundSession sender, PrintWorkflowJobStartingEventArgs args)
    {
        using (args.GetDeferral())
        {
            // Skip system rendering.
            args.SetSkipSystemRendering();

            // If Ipp compression is enabled by the system, check to see if PSA does custom compression for the printer
            // and disable system compression if required.
            if (args.IsIppCompressionEnabled)
            {
                if (this.HasCustomCompression(args.Printer))
                {
                    args.DisableIppCompressionForJob();
                }
            }
        }
    }

    bool HasCustomCompression(IppPrintDevice device)
    {
        bool hasCustomCompression = false;
        // Check if the PSA does custom compression for the printer
        return hasCustomCompression;
    } 
} 

IPP 作业错误处理

此示例演示了 PSA 应用如何注册作业错误、显示作业错误的 Toast,以及在用户激活 Toast 时启动 UI。

public sealed class PrintSupportWorkflowBackgroundTask : IBackgroundTask
{
    public BackgroundTaskDeferral TaskInstanceDeferral { get; set; }
    private PrintWorkflowJobBackgroundSession session;

    public void Run(IBackgroundTaskInstance taskInstance)
    {
        TaskInstanceDeferral = taskInstance.GetDeferral();

        if (taskInstance.TriggerDetails is PrintWorkflowJobTriggerDetails jobDetails)
        {
            session = jobDetails.PrintWorkflowJobSession;
            session.JobStarting += OnJobStarting;
            session.PdlModificationRequested += OnPdlModificationRequested;
            session.JobIssueDetected += OnJobIssueDetected;

            // Make sure to register all the event handlers before PrintWorkflowJobBackgroundSession.Start is called.
            session.Start();
        }
    }

    private void OnJobIssueDetected (PrintWorkflowJobBackgroundSession sender, PrintWorkflowJobIssueDetectedEventArgs args)
    {
        // Using a deferral to exclude the background process from being suspended while PSA displays UI.
        Deferral deferral = args.GetDeferral();
        var toast = new ToastNotification(GetErrorToastXml(args.ExtendedError,
                args.JobIssueKind, args.PrinterJob, args.Configuration));
        toast.Activated += async (toastSender, e) =>
        {
            // PSA UI application 
            PrintWorkflowUICompletionStatus uiStatus = await args.UILauncher.LaunchAndCompleteUIAsync();
            // Complete deferral
            deferral.Complete();
        };
        toast.Dismissed += async (toastSender, e) => { deferral.Complete(); };
        toast.Failed += async (toastSender, e) => { deferral.Complete(); };
            
        ToastNotificationManager.CreateToastNotifier().Show(toast);
        args.SkipSystemErrorToast = true;
    }

    private XmlDocument GetErrorToastXml(Exception jobError, PrintWorkflowJobIssueKind issueKind,
        PrintWorkflowPrinterJob printerJob, PrintWorkflowConfiguration workflowConfig )
    {
        var errorToastXml = new XmlDocument();
        // Generate Toast Xml based on error information from Exception and PrintWorkflowPrinterJob.
        return errorToastXml;
    }
}

设置 IPP 通信超时

此示例演示如何设置 IPP 通信超时。

public sealed class PrintSupportExtensionBackGroundTask : IBackgroundTask
{
    public BackgroundTaskDeferral TaskInstanceDeferral { get; set; }
    private PrintSupportExtensionSession session;

    public void Run(IBackgroundTaskInstance taskInstance)
    {
        taskInstance.Canceled += OnTaskInstanceCanceled;
        TaskInstanceDeferral = taskInstance.GetDeferral();

        if (taskInstance.TriggerDetails is PrintSupportExtensionTriggerDetails extensionDetails)
        {
            session = extensionDetails.Session;
            session.PrintTicketValidationRequested += OnSessionPrintTicketValidationRequested;
            session.PrintDeviceCapabilitiesChanged += OnSessionPrintDeviceCapabilitiesChanged;
            session.CommunicationErrorDetected += OnCommunicationErrorDetected;

            // Make sure to register all the event handlers before PrintSupportExtensionSession.Start is called.
            session.Start();
        }
    }

    private void OnTaskInstanceCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
    {
        session = null;
        TaskInstanceDeferral.Complete();
    }
    
    private void OnSessionPrintDeviceCapabilitiesChanged(PrintSupportExtensionSession sender, PrintSupportPrintDeviceCapabilitiesChangedEventArgs args)
    {
        // Using deferral to exclude the background process from being suspended while PSA updates the printer PDC and other configurations.
        using (args.GetDeferral())
        {
            if (args.CommunicationConfiguration.CanModifyTimeouts)
                {
                    this.UpdateAttributeTimeouts(args.CommunicationConfiguration, sender.Printer);
                    this.UpdateJobTimeouts(args.CommunicationConfiguration, sender.Printer);
                }
                // Do other operations, such as Updating PDC, PDR, and so on here.
        }
    }

    void UpdateAttributeTimeouts(PrintSupportIppCommunicationConfiguration config, IppPrintDevice device)
    {
        IppPrinterCommunicationKind communicationKind = config.CommunicationKind;
        PrintSupportIppCommunicationTimeouts currentTimeouts = config.IppAttributeTimeouts;

        // Adjust attribute timeouts based on the printer
        switch (communicationKind)
        {
            case IppPrinterCommunicationKind.Network:
                currentTimeouts.ConnectTimeout = TimeSpan.FromSeconds(10);
                currentTimeouts.SendTimeout = TimeSpan.FromSeconds(10);
                currentTimeouts.ReceiveTimeout = TimeSpan.FromSeconds(10);
                break;
            case IppPrinterCommunicationKind.UniversalPrint:
                // adjust timeout for universal printer
                break;
        }
        
    }

    void UpdateJobTimeouts(
        PrintSupportIppCommunicationConfiguration config, IppPrintDevice device)
    {
        IppPrinterCommunicationKind communicationKind = config.CommunicationKind;
        PrintSupportIppCommunicationTimeouts currentTimeouts = config.IppJobTimeouts;
        // Adjust job timeouts based on the printer and communication type
        switch (communicationKind)
        {
            case IppPrinterCommunicationKind.Network:
                currentTimeouts.ConnectTimeout = TimeSpan.FromSeconds(30);
                currentTimeouts.SendTimeout = TimeSpan.FromSeconds(30);
                currentTimeouts.ReceiveTimeout = TimeSpan.FromSeconds(30);
                break;
            case IppPrinterCommunicationKind.UniversalPrint:
                // adjust timeout for universal printer
                break;
        }
    }
}

处理 IPP 通信错误

此示例演示如何处理 IPP 通信错误。

public sealed class PrintSupportExtensionBackGroundTask : IBackgroundTask
{
    public BackgroundTaskDeferral TaskInstanceDeferral { get; set; }
    private PrintSupportExtensionSession session;
    public void Run(IBackgroundTaskInstance taskInstance)
    {
        taskInstance.Canceled += OnTaskInstanceCanceled;
        TaskInstanceDeferral = taskInstance.GetDeferral();

        if (taskInstance.TriggerDetails is PrintSupportExtensionTriggerDetails extensionDetails)
        {
            session = extensionDetails.Session;
            session.PrintTicketValidationRequested += OnSessionPrintTicketValidationRequested;
            session.PrintDeviceCapabilitiesChanged += OnSessionPrintDeviceCapabilitiesChanged;
            session.CommunicationErrorDetected += OnCommunicationErrorDetected ;

            // Make sure to register all the event handlers before PrintSupportExtensionSession.Start is called.
            session.Start();
        }
    }

    private void OnTaskInstanceCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
    {
        session = null;
        TaskInstanceDeferral.Complete();
    }

    private void OnCommunicationErrorDetected(PrintSupportExtensionSession sender, PrintSupportCommunicationErrorDetectedEventArgs args)
    {
        // Using deferral to exclude the background process from being suspended while PSA updates the timeouts.
        using (args.GetDeferral())
        {

            if (args.ErrorKind == IppCommunicationErrorKind.Timeout)
            {
                PrintSupportIppCommunicationConfiguration ippConfig = args.CommunicationConfiguration;
                IppPrintDevice device = sender.Printer;
                // Update timeout based on the communication error
            }
        }
    }  
}

在 PSA 中打印到 IPP 传真输出打印机

此示例演示如何在 PSA 中打印到 IPPFaxOut 打印机。

public sealed class PrintSupportWorkflowBackgroundTask : IBackgroundTask
{
    public BackgroundTaskDeferral TaskInstanceDeferral { get; set; }
    private PrintWorkflowJobBackgroundSession session;

    public void Run(IBackgroundTaskInstance taskInstance)
    {
        TaskInstanceDeferral = taskInstance.GetDeferral();

        if (taskInstance.TriggerDetails is PrintWorkflowJobTriggerDetails jobDetails)
        {
            session = jobDetails.PrintWorkflowJobSession;
            session.JobStarting += OnJobStarting;
            session.PdlModificationRequested += OnPdlModificationRequested;
            session.JobIssueDetected += OnJobIssueDetected;

            // Make sure to register all the event handlers before PrintWorkflowJobBackgroundSession.Start is called.
            session.Start();
        }
    }

    private async void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession sender, PrintWorkflowPdlModificationRequestedEventArgs args)
    {
        using (args.GetDeferral())
        {
            IppPrintDevice printer = args.PrinterJob.Printer;
            IInputStream xpsContent = args.SourceContent.GetInputStream();
            WorkflowPrintTicket printTicket = args.PrinterJob.GetJobPrintTicket();

            string documentFormat = this.GetPrinterDocumentFormat(printer);
            PrintWorkflowPdlTargetStream targetStream = args.CreateJobOnPrinter(documentFormat);
            IInputStream xpsSourceContent = xpsContent;
            if (printer.IsIPPFaxOutPrinter)
            {                    
                // Add cover page to XPS source document   
                xpsSourceContent = this.AddCoverPageToXpsContent(xpsContent);
            }
            
            PrintWorkflowPdlConverter pdlConverter;
            switch (documentFormat.ToLowerInvariant())
            {
                case "image/pwg-raster":
                    pdlConverter = args.GetPdlConverter(PrintWorkflowPdlConversionType.XpsToPwgr);
                    break;
                case "application/pclm":
                    pdlConverter = args.GetPdlConverter(PrintWorkflowPdlConversionType.XpsToPclm);
                    break;
                case "application/pdf":
                    pdlConverter = args.GetPdlConverter(PrintWorkflowPdlConversionType.XpsToPdf);
                    break;
                case "image/tiff":
                    pdlConverter = args.GetPdlConverter(PrintWorkflowPdlConversionType.XpsToTiff);
                    break;
                default:
                    // This should not happen, aborting workflow if PSA does not identify the supported PDLs
                    args.Configuration.AbortPrintFlow(PrintWorkflowJobAbortReason.JobFailed);
                    return;
                }
                // Use pdlConverter to convert the source XPS stream to printer's document format and send it to the printer using targetStream.
                await pdlConverter.ConvertPdlAsync(printTicket, xpsSourceContent, targetStream.GetOutputStream());

                targetStream.CompleteStreamSubmission(PrintWorkflowSubmittedStatus.Succeeded);
            }
        }

    private IInputStream AddCoverPageToXpsContent(IInputStream xpsStream)
    {
        var coverPageXps = new InMemoryRandomAccessStream();
        // Add cover page to XPS content and write to coverPageXps stream
        return coverPageXps;
    }
}

对 PSA 中的电话号码禁用传真 UI

借助 IPP 传真打印机的支持,此示例显示了一个 UI,用于请求用户在打印到传真打印机时输入传真号。 但 PSA 可能想要显示自己的 UI,其中包含更多信息和选项。 如果有两个传真 UI,用户可能会感到困惑,因此,此示例说明了想要显示传真 UI 时供 PSA 禁用系统 UI 的选项。

以下示例演示 API 用法。

public sealed class PrintSupportWorkflowBackgroundTask : IBackgroundTask
{

    private void OnJobStarting(PrintWorkflowJobBackgroundSession sender, PrintWorkflowJobStartingEventArgs args)
    {
        using (args.GetDeferral())
        {
            // If the job is printing to an Ipp fax printer,
            // check whether PSA has a custom UI and disable system UI for getting the fax number.
            if (args.IsIPPFaxOutPrinter)
            {
                if (this.HasCustomUIForFax(args.Printer))
                {
                    args.SkipSystemFaxUI = true;
                }
            }
        }
    }

    bool HasCustomUIForFax(IppPrintDevice device)
    {
        bool hasCustomUIForFax = false;
        // Check if the PSA does custom UI for the given fax printer.
        return hasCustomUIForFax;
    }  
}

言论

本文中的示例基于 Print 支持应用设计指南中的 PSA v1 和 v2 API 示例构建, 假设开发人员熟悉 PSA API 工作流。

本文包含打印支持应用设计指南中所述的现有公共打印支持应用程序 API 的扩展,以及 Windows.Graphics.Printing.PrintSupport 命名空间。 PSA API 使打印机制造商能够开发 UWP 应用,这些应用可以在使用收件箱Microsoft IPP 类驱动程序时增强 Windows 用户打印体验,而无需开发自定义驱动程序。

打印组件正通过 PSA 代理进程与 PSA 应用通信。

DisableIppCompressionForJob

Windows 上第三方打印机驱动程序的服务终止计划

IsIppCompressionEnabled

IsIPPFaxOutPrinter

IppPrintDevice

JobIssueDetected

PrintSupportIppCommunicationConfiguration

PrintSupportPrintDeviceCapabilitiesChangedEventArgs

PrintWorkflowJobBackgroundSession

PrintWorkflowJobIssueDetectedEventArgs

PrintWorkflowJobStartingEventArgs

PrintWorkflowPdlConversionType

SkipSystemErrorToast

Windows.Graphics.Printing.PrintSupport