다음을 통해 공유


인쇄 지원 애플리케이션 v3 API 디자인 가이드

이 문서에서는 디바이스에 대한 v3 PSA(인쇄 지원 앱)를 구현하는 프린터 OEM 및 IHV에 대한 지침과 예제를 제공합니다.

용어

기간 정의
PSA (미국) 지원 애플리케이션을 인쇄합니다. 이 문서의 API를 사용하는 UWP 앱입니다.
IPP 인터넷 인쇄 프로토콜입니다. 클라이언트 디바이스에서 프린터와 상호 작용하여 인쇄 기본 설정을 검색하고 설정하고 인쇄할 문서를 보내는 데 사용됩니다.
인쇄 지원과 연관된 프린터 PSA에 연결된 물리적 IPP 프린터입니다.
IPP 프린터 IPP 프로토콜을 지원하는 프린터입니다.
PDL 페이지 설명 언어입니다. 문서를 프린터로 보내는 형식입니다.
연결된 PSA 프린터 PSA 애플리케이션과 연결된 물리적 IPP 프린터입니다.
인쇄 장치 기능 프린터 기능을 정의하기 위한 XML 문서 형식입니다.
인쇄 지원 확장 프린터 제약 조건 확장 기능을 제공하는 PSA 백그라운드 작업입니다.

이 문서에는 인쇄 지원 앱 디자인 가이드 및 Windows.Graphics.Printing.PrintSupport 네임스페이스에 설명된 기존 인쇄 지원 애플리케이션 공용 API에 대한 v3 확장이 포함되어 있습니다. PSA API를 사용하면 프린터 제조업체가 사용자 지정 드라이버를 개발할 필요 없이 내장된 Microsoft IPP 클래스 드라이버를 사용하는 Windows 사용자의 인쇄 경험을 향상시킬 수 있는 하드웨어 지원 앱을 개발할 수 있습니다. 인쇄 구성 요소는 PSA 브로커 프로세스를 통해 PSA 앱과 통신합니다.

자세한 내용은 다음 문서를 참조하세요.

주제 설명
인쇄 지원 앱 디자인 가이드 디바이스에 대한 PSA(인쇄 지원 앱)를 구현하는 프린터 OEM 및 IHV에 대한 지침과 예제를 제공합니다.
인쇄 지원 앱 v4 API 디자인 가이드 디바이스에 대한 v4 PSA(인쇄 지원 앱)를 구현하는 프린터 OEM 및 IHV에 대한 지침과 예제를 제공합니다.
인쇄 지원 가상 프린터용 MSIX 매니페스트 사양 인쇄 지원 가상 프린터를 구현하는 프린터 OEM 및 IHV에 대한 MSIX 매니페스트 지침 및 예제를 제공합니다.
인쇄 지원 앱 연결 PSA(인쇄 지원 앱)를 프린터와 연결하기 위한 지침과 예제를 제공합니다.

API에 대한 중요한 확장은 다음과 같습니다.

  • IPP 압축 - PSA v3 API는 이 기능을 지원하는 IPP 프린터의 인쇄 작업에 IPP 압축 기능을 추가하여 IPP 인쇄를 향상시킵니다. 일부 PSA에는 사용자 지정 압축이 있을 수 있습니다. 즉, IPP 작업에 성능에 영향을 주는 이중 압축이 있습니다. 이를 완화하기 위해 PSA v3 API는 PrintWorkflowJobStartingEventArgs(PSA v1 API) 런타임 클래스에 IsIppCompressionEnabled 속성과 DisableIppCompressionForJob 함수(필요한 경우 현재 작업에 대한 압축을 사용하지 않도록 설정)를 도입합니다.

  • IPP 작업 오류 처리 및 오류 알림 - PSA v3 API는 PrintWorkflowJobBackgroundSession(PSA v1 API) 런타임 클래스에 JobIssueDetected 이벤트를 도입합니다. 이 이벤트는 PSA가 인쇄 작업에서 오류/경고를 감지할 때마다 발생합니다. 그런 다음 PSA는 사용자에게 오류 알림을 표시할 책임이 있습니다. PSA가 이 이벤트에 등록하고 SkipSystemErrorToast 속성을 PrintWorkflowJobIssueDetectedEventArgstrue로 설정하면 인쇄 시스템에 Windows 인쇄 시스템 알림이 표시되지 않도록 지시합니다. 또한 PSA v3 API는 사용자가 토스트 알림과 상호 작용할 때 PSA가 UI를 시작하는 메커니즘을 제공합니다.

  • 사용자 지정 IPP 통신 시간 제한 - PSA v3 API는 PSA가 IPP 시간 제한을 재정의할 수 있는 API를 제공합니다. 또한 IPP 통신 시간 제한을 조작하기 위해 PrintSupportIppCommunicationConfiguration 런타임 클래스가 PrintSupportPrintDeviceCapabilitiesChangedEventArgs에 추가되었습니다. 또한 PSA v3 API는 IPP 통신에 오류가 있을 때 발생하는 이벤트를 도입합니다. IHV가 오류를 조사하고 그에 따라 시간 제한 값을 조정할 수 있도록 이벤트가 도입되었습니다.

  • 지원 IPPFaxOut - PSA v3 API는 IPPFaxOut 프린터 지원을 위해 인쇄 시스템에 기능을 추가합니다. 팩스를 지원하기 위해 PSA는 XPS를 Tiff로 변환하는 렌더링 필터를 지원합니다. PSA는 XPS 콘텐츠를 TIFF로 변환하기 전에 조작할 수 있으므로, PSA가 XPS에서 TIFF로 변환기를 활용할 수 있게 하기 위해 PrintWorkflowPdlConversionType에 XpsToTiff 열거형 값을 제공합니다. 또한 PSA가 표준 프린터와 IPPFaxOut 프린터를 구분할 수 있도록 IsIPPFaxOutPrinter 속성을 IppPrintDevice 런타임 클래스에 제공합니다.

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 앱이 작업 오류를 등록하고, 작업 오류에 대한 알림을 표시하고, 사용자가 알림을 활성화할 때 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가 두 개 있는 경우 사용자에게 혼동을 주므로 이 샘플에서는 PSA가 팩스 UI를 표시하려는 경우 시스템 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;
    }  
}

주석

이 문서의 샘플은 개발자가 PSA API 워크플로에 익숙하다는 가정하에 Print 지원 앱 디자인 가이드 PSA v1 및 v2 API 샘플을 기반으로 작성되었습니다.

이 문서에는 인쇄 지원 앱 디자인 가이드 및 windows.Graphics.Printing.PrintSupport 네임스페이스에 설명된 기존 공용 인쇄 지원 애플리케이션 API에 대한 확장이 포함되어 있습니다. PSA API를 사용하면 프린터 제조업체가 사용자 지정 드라이버를 개발할 필요 없이 기본 제공 Microsoft IPP 클래스 드라이버를 사용하는 동안 Windows 사용자 인쇄 환경을 향상시킬 수 있는 UWP 앱을 개발할 수 있습니다.

인쇄 구성 요소는 PSA 브로커 프로세스를 통해 PSA 앱과 통신합니다.

작업을 위한 IPP 압축 비활성화

Windows 타사 프린터 드라이버에 대한 지원 종료 계획

IsIppCompressionEnabled

IsIPPFaxOutPrinter

IppPrintDevice

작업문제감지

PrintSupportIppCommunicationConfiguration

PrintSupportPrintDeviceCapabilitiesChangedEventArgs

인쇄 워크플로우 작업 배경 세션

PrintWorkflowJobIssueDetectedEventArgs

PrintWorkflowJobStartingEventArgs

printWorkflowPdlConversionType

시스템 오류 알림 건너뛰기

Windows.Graphics.Printing.PrintSupport