Share via


인쇄 지원 앱 디자인 가이드

이 문서에서는 여러 가지 방법으로 Windows 사용자의 인쇄 환경을 향상시킬 수 있는 인쇄 지원 앱(PSA)을 개발하기 위한 프린터 OEM 및 IHV에 대한 지침과 예제를 제공합니다.

Important

Windows 11 SDK(22000.1) 릴리스부터 PSA(Print Support Apps)는 프린터용 UWP 앱을 개발하는 데 권장되는 방법입니다. 인쇄 장치에 대한 인쇄 지원 앱을 개발하려면 대상으로 하는 Windows 버전용 Windows 11 SDK를 다운로드하여 설치합니다.

Important

이 항목에는 Windows 11 버전 22H2부터 사용할 수 있는 PSA 기능을 설명하는 섹션이 포함되어 있습니다. 해당 섹션에는 해당 버전에 적용된다는 메모가 포함되어 있습니다.

일부 프린터 기능은 제조업체 앱의 도움이 올바르게 구성되어야 하는 특수 기능인 Windows에서 표시하는 인쇄 대화 상자에 표시되지 않습니다. 프린터의 기본 기능에 제공되지 않는 기능일 수도 있습니다.

프린터 관련 기능은 사용자가 옵션을 쉽게 선택하고 해당 시나리오에 관련된 모든 기능이 자동으로 올바른 값으로 설정되도록 신뢰할 수 있는 방식으로 그룹화할 수 있습니다. 예를 들어 잉크 저장기, 종이 저장기 및 최고 품질 모드 중에서 사용자가 선택한 항목에 따라 다양한 인쇄 기능을 자동으로 조작할 수 있는 옵션을 선택할 수 있습니다. Windows는 모든 프린터 모델의 모든 사용자 지정 기능을 이해해야 하므로 자동으로 그룹화할 수 없습니다.

사용자 지정 인쇄 기본 설정을 표시하기 위한 요구 사항은 모든 Windows 인쇄 대화 상자 및 Windows에서 제공하는 API를 사용하는 사용자 지정 인쇄 대화 상자에서 사용자가 활성화할 수 있는 선택적 UWP 확장 계약을 사용하여 이 API에서 해결됩니다. 제조업체는 사용자가 소유한 특정 프린터에 최상의 인쇄 환경을 제공하도록 UI를 조정할 수 있습니다.

프린터 제조업체가 개선하고 차별화할 수 있는 또 다른 영역은 인쇄 품질입니다. 제조업체는 특정 프린터의 콘텐츠를 최적화하여 렌더링 후 인쇄 품질을 향상시킬 수 있습니다. 또한 프린터 특정 기능을 고려할 수 있으므로 최종 출력을 더 잘 나타내는 고품질 미리 보기를 표시할 수 있습니다.

print support app print timeline

용어

기간 정의
PSA 지원 애플리케이션을 인쇄합니다. 이 문서에 설명된 API를 사용하는 UWP 앱입니다.
Mpd 최신 인쇄 대화 상자. 앱이 Windows.Graphics.Printing API를 사용하여 인쇄할 때 사용자에게 표시됩니다.
Cpd 일반 인쇄 대화 상자. 앱이 Win32 API를 사용하여 인쇄할 때 사용자에게 표시됩니다. 인쇄 미리 보기를 표시해야 하는 앱은 이 대화 상자를 트리거하지 않고 대화의 버전을 직접 구현하지 않습니다. Office 앱 대표적인 예입니다.
IPP 인터넷 인쇄 프로토콜입니다. 클라이언트 디바이스에서 프린터와 상호 작용하여 인쇄 기본 설정을 검색하고 설정하고 인쇄할 문서를 보내는 데 사용됩니다.
인쇄 지원 관련 프린터 PSA에 연결된 프린터입니다.
IPP 프린터 IPP 프로토콜을 지원하는 프린터입니다.
추가 설정 MPD에서 파트너 제공 앱 UI를 여는 링크입니다. 기본값은 PSA가 설치되어 있지 않은 경우 기본 제공 인쇄 기본 설정 UI를 여는 것입니다.
프린터 기본 설정 UI 인쇄 시 적용되는 기본 프린터 옵션을 설정하는 데 사용되는 대화 상자입니다. 예를 들어 방향, 용지 크기, 색, 양쪽 인쇄 등입니다.
Pdl 페이지 설명 언어입니다. 문서를 프린터로 보내는 형식입니다.
연결된 PSA 프린터 PSA 애플리케이션과 연결된 물리적 IPP 프린터입니다.
PrintDeviceCapabilities 프린터 기능을 정의하기 위한 XML 문서 형식입니다. 자세한 내용은 인쇄 티켓 및 인쇄 기능 기술을 참조 하세요.
Printticket 지정된 인쇄 작업에 대한 사용자의 의도를 캡처하는 데 사용되는 다양한 인쇄 관련 기능 및 해당 값의 컬렉션입니다.
PrintSupportExtension 프린터 제약 조건 확장 기능을 제공하는 PSA 백그라운드 작업입니다.

이러한 샘플은 다음과 같이 정의된 printsupport 네임스페이스를 참조합니다.

    xmlns:printsupport="http://schemas.microsoft.com/appx/manifest/printsupport/windows10"

사용자가 문서를 인쇄하려고 할 때 문서를 인쇄할 기본 설정을 지정하는 경우가 많습니다. 예를 들어 가로 방향으로 문서를 인쇄하도록 선택할 수 있습니다. 또한 프린터에서 지원하는 사용자 지정 기능을 활용할 수도 있습니다. Windows는 사용자 지정 기본 설정을 표시하는 기본 UI를 제공하지만 적절한 아이콘이나 설명이 없으므로 사용자가 이를 이해하지 못할 수 있습니다. Windows에서 잘못된 UI 컨트롤을 사용하여 표시할 수도 있습니다. 이러한 사용자 지정 기능은 기능을 완전히 이해하는 앱에서 제공하는 것이 가장 좋습니다. 이는 프린터 제조업체가 만드는 다양한 프린터 모델에 맞게 조정된 앱을 만들 수 있는 API를 제공하는 동기부여입니다.

windows.printSupport설정UI라는 새 범주를 사용하여 새 UAP 확장 계약이 만들어집니다. 이 계약으로 활성화된 앱은 PrintSupport설정UI라는 새 ActivationKind를 받습니다. 이 계약에는 새로운 기능이 필요하지 않습니다.

<Extensions>
    <printsupport:Extension Category="windows.printSupportSettingsUI" 
        EntryPoint="PsaSample.PsaSettingsUISample"/>
</Extensions>

이 계약은 사용자가 MPD 또는 CPD의 기본 설정에서 추가 설정 선택할 때 호출됩니다. 이 계약은 설정 앱의 인쇄 기본 설정에서 호출할 수도 있습니다. 계약이 활성화되면 앱은 현재 PrintTicketPrintDevice 개체를 가져오는 데 사용할 수 있는 PrintSupport설정UISession 개체를 받습니다. PrintDevice 개체는 프린터와 통신하여 프린터 및 작업 특성을 수신하는 데 사용할 수 있습니다. 그러면 앱에서 사용자에게 프린터의 적절한 옵션을 사용하여 UI를 표시할 수 있습니다. 사용자가 선택을 하고 확인을 선택하면 애플리케이션에서 인쇄 티켓을 수정하고 유효성을 검사한 다음 PrintSupportPrintTicketTarget 개체를 사용하여 다시 제출할 수 있습니다. 사용자가 기본 설정 창을 취소하도록 선택하는 경우 변경 내용을 해제해야 하며카드 PrintSupport설정UISession 개체에서 가져온 지연을 완료하여 애플리케이션을 종료해야 합니다.

인쇄 지원 앱은 여러 인쇄 작업에 대해 여러 동시 활성화를 처리해야 하므로 이러한 앱은 package.appxmanifest 파일의 SupportsMultipleInstances 요소를 사용하여 여러 인스턴스를 지원해야 합니다. 이렇게 하지 않으면 한 인쇄 작업의 기본 설정을 확인하면 열려 있는 다른 기본 설정 창이 닫히게 될 수 있습니다. 사용자는 해당 기본 설정 창을 다시 열어야 합니다.

다음 시퀀스 다이어그램은 설정 UI 인쇄 티켓 조작의 개념을 나타냅니다.

sequence diagram of settings U I print ticket manipulation

설정 UI에서 PrintTicket 변경

인쇄 대화 상자(MPD/CPD 또는 사용자 지정 인쇄 대화 상자) 또는 시스템 설정에서 시작할 때 설정 UI를 활성화하기 위한 C# 샘플 코드:

namespace PsaSampleApp
{
    sealed partial class App : Application
    {
        Deferral settingsDeferral;
        protected override void OnActivated(IActivatedEventArgs args)
        {
            if (args.Kind == ActivationKind.PrintSupportSettingsUI)
           {
                // Get the activation arguments
                var settingsEventArgs = args as PrintSupportSettingsActivatedEventArgs;
                PrintSupportSettingsUISession settingsSession = settingsEventArgs.Session;
                // Take deferral
                this.settingsDeferral = settingsEventArgs.GetDeferral();

                // Create root frame
                var rootFrame = new Frame();
                
        // Choose the page to be shown based upon where the application is being launched from
                switch (settingsSession.LaunchKind)
                {
                    case SettingsLaunchKind.UserDefaultPrintTicket:
                    {
                        // Show settings page when launched for default printer settings
                        rootFrame.Navigate(typeof(DefaultSettingsView), settingsSession);
                    }
                    break;
                    case SettingsLaunchKind.JobPrintTicket:
                    {
               // Show settings page when launched from printing app
                       rootFrame.Navigate(typeof(JobSettingsView), settingsSession);
                    }
                    break;
                }
                
   
                Window.Current.Content = rootFrame; 
            }
        }

        internal void ExitSettings()
        {
            settingsDeferral.Complete();
        } 
    }
}

기본값용 XAML설정View 클래스:

<Page
    x:Class="PsaSampleApp.DefaultSettingsView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PsaSampleApp"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0"  Orientation="Vertical" Margin="30,50,0,0">
           <ComboBox x:Name="OrientationOptions" ItemsSource="{x:Bind OrientationFeatureOptions}" SelectedItem="{x:Bind SelectedOrientationOption, Mode=TwoWay}" DisplayMemberPath="DisplayName" HorizontalAlignment="Left" Height="Auto" Width="Auto" VerticalAlignment="Top"/>
       </StackPanel>

        <StackPanel Grid.Row="1" Orientation="Horizontal">
            <Button x:Name="Ok" Content="Ok" HorizontalAlignment="Left" Margin="50,0,0,0" VerticalAlignment="Top" Click="OkClicked"/>
            <Button x:Name="Cancel" Content="Cancel" HorizontalAlignment="Left" Margin="20,0,0,0" VerticalAlignment="Top" Click="CancelClicked"/>
        </StackPanel>
    </Grid>
</Page>

UI를 표시하고 PrintTicket를 변경하기 위한 C# 샘플 코드:

namespace PsaSampleApp
{
    /// <summary>
    /// Class for showing print settings to the user and allow user to modify it
    /// </summary>
    public sealed partial class DefaultSettingsView: Page
    {
        private IppPrintDevice printer;
        private PrintSupportSettingsUISession uiSession;
        private WorkflowPrintTicket printTicket;
        private App application;
        // Bound to XAML combo box
        public ObservableCollection<PrintTicketOption> OrientationFeatureOptions { get; } = new ObservableCollection<PrintTicketOption>();
        public PrintTicketOption SelectedOrientationOption { get; set; }  

        public SettingsView()
        {
            this.InitializeComponent();
            this.application = Application.Current as App;
            this.orientationFeatureOptions = new ObservableCollection<PrintTicketOption>();
        }

        internal void OnNavigatedTo(NavigationEventArgs e)
        {
            this.uiSession = = e.Parameter as PrintSupportSettingsUISession;
            this.printer = session.SessionInfo.Printer;
            this.printTicket = session.SessionPrintTicket;
            
            PrintTicketCapabilities printTicketCapabilities = this.printTicket.GetCapabilities();

            // Read orientation feature from PrintTicket capabilities
            PrintTicketFeature feature = printTicketCapabilities.PageOrientationFeature;
            // Populate XAML combo box with orientation feature options
            this.PopulateOrientationOptionComboBox(feature.Options); 

            PrintTicketOption printTicketOrientationOption = printTicket.PageOrientationFeature.GetSelectedOption();
            // Update orientation option in XAML combo box
            this.SelectedOrientationOption = this.orientationFeatureOptions.Single((option)=> (option.Name == printTicketOrientationOption.Name && option.XmlNamespace == printTicketOrientationOption.XmlNamespace));
        }

        private async void OkClicked(object sender, RoutedEventArgs e)
        {
            // Disable Ok button while the print ticket is being submitted
            this.Ok.IsEnabled = false;

            // Set selected orientation option in the PrintTicket and submit it
            PrintTicketFeature orientationFeature = this.printTicket.PageOrientationFeature;
            orientationFeature.SetSelectedOption(this.SelectedOrientationOption);
            // Validate and submit PrintTicket
            WorkflowPrintTicketValidationResult result = await printTicket.ValidateAsync();
            if (result.Validated)
            {
                // PrintTicket validated successfully – submit and exit
                this.uiSession.UpdatePrintTicket(printTicket);
                this.application.ExitSettings();
            }
            else
            {
                this.Ok.IsEnabled = true;
                // PrintTicket is not valid – show error
                this.ShowInvalidPrintTicketError(result.ExtendedError);
            }
        }

        private void CancelClicked(object sender, RoutedEventArgs e)
        {
            this.application.ExitSettings();
        }
    }
}

프린터 장치에서 프린터 특성 가져오기

IPP 프린터에서 get-printer-attributes 쿼리로의 WireShark 응답:

wireshark response from an I P P printer to a get printer attributes query

프린터에서 잉크 이름 및 잉크 수준을 가져오기 위한 C# 샘플 코드:

namespace PsaSampleApp
{
    /// <summary>
    /// Class for showing print settings to the user
    /// </summary>
    public sealed partial class SettingsView : Page
    { 
       IList<string> inkNames;
       IList<int> inkLevels;
        
        private async void GetPrinterAttributes()
        {
            // Read ink names and levels, along with loaded media-sizes
            var attributes = new List<string>();
            attributes.Add("marker-names");
            attributes.Add("marker-levels");
            attributes.Add("media-col-ready");
            IDictionary<string, IppAttributeValue> printerAttributes = this.printer.GetPrinterAttributes(attributes);

            IppAttributeValue inkNamesValue = printerAttributes["marker-names"];
            CheckValueType(inkNamesValue, IppAttributeValueKind.Keyword);
            this.inkNames = inkNamesValue.GetKeywordArray();
            
            IppAttributeValue inkLevelsValue = printerAttributes["marker-levels"];
            CheckValueType(inkLevelsValue, IppAttributeValueKind.Integer);
            this.inkLevels = inkLevelsValue.GetIntegerArray();
    
            // Read loaded print media sizes
        IppAttributeValue mediaReadyCollectionsValue = printerAttributes["media-col-ready"];
            foreach (var mediaReadyCollection in mediaReadyCollectionsValue.GetCollectionArray())
            {
                IppAttributeValue mediaSizeCollection;
                if (mediaReadyCollection.TryGetValue("media-size", out mediaSizeCollection))
                {
                    var xDimensionValue = mediaSizeCollection.GetCollectionArray().First()["x-dimension"];
                    var yDimensionValue = mediaSizeCollection.GetCollectionArray().First()["y-dimension"];
                    CheckValueType(xDimensionValue, IppAttributeValueKind.Integer);
                    CheckValueType(yDimensionValue, IppAttributeValueKind.Integer);
                    int xDimension = xDimensionValue.GetIntegerArray().First();
                    int yDimension = yDimensionValue.GetIntegerArray().First();
                    this.AddMediaSize(xDimension, yDimension);
                }
            }
        }

        private void CheckValueType(IppAttributeValue value, IppAttributeValueKind expectedKind)
        {
            if (value.Kind != expectedKind)
            {
                throw new Exception(string.Format("Non conformant type found: {0}, expected: {1}", value.Kind, expectedKind));
            }
        }
    }
}

프린터에서 프린터 특성 설정

프린터 특성을 설정하기 위한 C# 샘플 코드:

int defaultResolutionX = 1200;
int defaultResolutionY = 1200;
string pdlFormat = "image/pwg-raster";
private async void SetPrinterAttributes()
{
    var attributes = new Dictionary<string, IppAttributeValue>();
    attributes.Add("document-format-default", IppAttributeValue.CreateKeyword(this.pdlFormat));
    var resolution = new IppResolution(this.defaultResolutionX, this.defaultResolutionY, IppResolutionUnit.DotsPerInch);
    attributes.Add("printer-resolution-default", IppAttributeValue.CreateResolution(resolution));
            
    var result = this.printer.SetPrinterAttributes(attributes);
    if (!result.Succeeded)
    {
        foreach (var attributeError in result.AttributeErrors)
        {
            var attributeName = attributeError.Key;
            switch (attributeError.Value.Reason)
            {
            case IppAttributeErrorReason.AttributeValuesNotSupported:
                var values = attributeError.Value.GetUnsupportedValues().First();
                this.LogUnSupportedValues(attributeName, values);
                break;
            case IppAttributeErrorReason.AttributeNotSettable:
                this.LogAttributeNotSettable(attributeName);
                break;
            case IppAttributeErrorReason.AttributeNotSupported:
                this.LogAttributeNotSupported(attributeName);
                break;
            case IppAttributeErrorReason.RequestEntityTooLarge:
                this.LogAttributeNotEntityTooLarge(attributeName);
                break;
            case IppAttributeErrorReason. ConflictingAttributes:
                this.LogConflictingAttributes(attributeName);
                break;
            }
        }
    }
}

프린터 제약 조건 확장

인쇄 지원 앱은 사용자 지정 PrintTicket 유효성 검사 및 기본 PrintTicket 정의를 지원합니다. 이 섹션에서는 이러한 기능을 지원하는 방법을 설명합니다.

프린터 확장 제약 조건을 지원하기 위해 새 백그라운드 작업 유형인 PrintSupportExtension이 구현되었습니다. Package.appxmanifest에는 다음과 같이 인쇄 지원 확장에 대한 확장성 항목이 있습니다.

<Extensions>
    <printsupport:Extension Category="windows.printSupportExtension" 
        EntryPoint="PsaBackgroundTasks.PrintSupportExtension"/>
</Extensions>

이 서비스는 연결된 IPP 프린터의 인쇄 작업에서 언제든지 실행할 수 있습니다. Run(IBackgroundTaskInstance taskInstance) 함수를 통해 인쇄 지원 확장이 활성화되면 PrintSupportExtensionTriggerDetails 런타임 클래스에 대한 액세스를 제공하기 위해 IBackgroundTaskInstance 인스턴스가 PrintSupportExtensionSession에 제공됩니다. 이 클래스는 PrintSupportExtensionSession을 속성으로 내부적으로 제공합니다. 그러면 PrintSupportExtension 백그라운드 클래스는 세션 개체를 사용하여 사용자 지정 기능을 제공하려는 이벤트에 등록할 수 있습니다.

  1. event Windows.Foundation.TypedEventHandler<PrintSupportExtensionSession, PrintSupportPrintTicketValidationRequestedEventArgs>; PrintTicketValidationRequested;

    인쇄 지원 확장에서 자체 PrintTicket 유효성 검사 메커니즘을 제공하는 경우 이 이벤트에 등록할 수 있습니다. PrintTicket의 유효성을 검사해야 할 때마다 인쇄 시스템에서 이 이벤트가 발생합니다. 그러면 PrintSupportExtension은 EventArgs 내에서 유효성을 검사해야 하는 현재 PrintTicket를 가져옵니다. 그런 다음 PrintSupportExtension 백그라운드 클래스는 유효성을 위해 PrintTicket를 검사 충돌을 해결하기 위해 수정할 수 있습니다. 그러면 PrintSupportExtension 백그라운드 클래스는 SetPrintTicketResult 함수를 사용하여 유효성 검사 결과를 설정하여 PrintTicket가 해결되었거나 충돌이 있거나 잘못된지 여부를 나타냅니다. 이 이벤트는 인쇄 작업의 수명 동안 언제든지 발생할 수 있습니다. PrintSupportExtension 클래스가 이 이벤트에 등록되지 않은 경우 인쇄 시스템은 PrintTicket에 대한 자체 유효성 검사를 수행합니다.

  2. event Windows.Foundation.TypedEventHandler<PrintSupportExtensionSession, PrintSupportPrintDeviceCapabilitiesChangedEventArgs>; PrintDeviceCapabilitiesChanged;

    이 이벤트는 인쇄 시스템이 연결된 IPP 프린터의 캐시된 PrintDeviceCapabilities를 업데이트한 후에 발생합니다. 이 이벤트가 발생하면 PrintSupportExtension 백그라운드 클래스가 변경된 PrintDeviceCapabilities를 검사하고 수정할 수 있습니다.

인쇄 티켓의 사용자 지정 유효성 검사

PrintTicket 유효성 검사 서비스를 제공하기 위한 C# 샘플 코드:

public void Run(IBackgroundTaskInstance taskInstance)
{
    // Take task deferral
    this.taskDeferral = taskInstance.GetDeferral();
    // Associate a cancellation handler with the background task
    taskInstance.Canceled += OnTaskCanceled;

    var psaTriggerDetails = taskInstance.TriggerDetails as PrintSupportExtensionTriggerDetails;

    var serviceSession = psaTriggerDetails.Session as PrintSupportExtensionSession;

    this.ippPrintDevice = serviceSession.Printer;
    serviceSession.PrintTicketValidationRequested += this.OnPrintTicketValidationRequested;
    serviceSession.PrinterDeviceCapabilitiesChanged += this.OnPdcChanged;
    serviceSession.Start();
}

private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
    // Complete the deferral
    this.taskDeferral.Complete();
}

private void OnPrintTicketValidationRequested(PrintSupportExtensionSession session, PrintSupportPrintTicketValidationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        // Get PrintTicket that needs needs to be validated and resolved   
        var printTicket = args.PrintTicket;
                
        // Validate and resolve PrintTicket
        WorkflowPrintTicketValidationStatus validationStatus = this.ValidateAndResolvePrintTicket(printTicket);
        args.SetPrintTicketValidationStatus(validationStatus);
    }
}

PrintDeviceCapabilities 업데이트

private void OnPdcChanged(PrintSupportExtensionSession session, PrintSupportPrintDeviceCapabilitiesChangedEventArgs args)
{
    using (args.GetDeferral())
    {
        var pdc = args.GetCurrentPrintDeviceCapabilities();

        // Check current PDC and make changes according to printer device capabilities
        XmlDocument newPdc = this.CheckAndUpdatePrintDeviceCapabilities(pdc);
        args.UpdatePrintDeviceCapabilities(newPdc);
    }
}

인쇄 품질 향상

사용자가 인쇄 대화 상자에서 인쇄 단추를 눌러 인쇄를 커밋하면 인쇄할 문서가 인쇄 중인 앱에서 인쇄 스택으로 전송됩니다. 그런 다음 이 문서에서는 대상 프린터에 적합하도록 변환(PDL로 렌더링)을 진행합니다. Windows는 프린터에서 쿼리된 특성에 따라 선택할 변환을 결정합니다. 그러면 변환된 문서가 프린터로 전송됩니다. 대부분의 프린터에서 잘 작동하지만 파트너 앱이 변환에 참여할 수 있도록 하여 인쇄 품질을 향상시킬 수 있는 경우가 있습니다. 이를 용이하게 하기 위해 현재 인쇄 워크플로 API는 인쇄 스택의 추가 지점에서 앱에 대한 호출을 포함하도록 확장됩니다. 이 API는 PSA 앱이 등록할 수 있는 두 개의 새 이벤트를 지원합니다. 다음은 PSA API 표면의 유일한 진입점입니다.

  1. JobStarting

    • 이 이벤트는 모든 애플리케이션에서 인쇄 작업을 시작할 때 발생합니다. 이벤트가 발생하면 PrintWorkflowJobStartingEventArgs에서 SetSkipSystemRendering을 호출하여 인쇄 지원 앱에서 시스템 렌더링을 건너뛰도록 선택할 수 있습니다. 시스템 렌더링 건너뛰기를 선택하면 인쇄 시스템에서 XPS 문서를 프린터에 필요한 PDL 형식으로 변환하지 않습니다. 대신, 인쇄 애플리케이션에서 생성된 XPS는 PSA에 직접 제공된 다음 XPS를 PDL 형식으로 변환하는 작업을 담당합니다.
  2. PdlModificationRequested

    • 이 이벤트는 Windows에서 XPS 스트림을 프린터로 표시된 PDL 형식으로 변환하기 시작할 때 발생합니다. 런타임 클래스 PrintWorkflowPdlModificationRequestedEventArgs는 이 이벤트에 대한 인수로 제공됩니다. 이 이벤트 클래스는 인쇄 작업 콘텐츠를 읽고 쓰기 위한 PDL 원본 및 대상 개체를 제공합니다. 앱에서 사용자 입력이 필요하다고 판단되면 EventArgs에서 PrintWorkflowUI시작 관리자를 사용하여 UI를 시작할 수 있습니다. 이 API는 Tester-Doer 패턴을 사용합니다. Function IsUILaunchEnabled가 false를 반환하면 PrintWorkflowUI시작 관리자 UI를 호출할 수 없습니다. PSA 세션이 자동 모드(헤드리스 또는 키오스크 모드)에서 실행되는 경우 이 함수는 false를 반환합니다. 함수가 false를 반환하는 경우 인쇄 지원 앱에서 UI를 시작하려고 하면 안 됩니다.

    OutputStream은 GetStreamTargetAsync 함수에서 반환되는 PrintWorkflowPdlTargetStream의 일부로 사용할 수 있습니다. 대상 OutputStream에 기록된 콘텐츠는 문서 콘텐츠로 프린터에 전달됩니다.

PDL 수정 이벤트에 대한 시퀀스 다이어그램:

sequence diagram for the source stream P D L modification event

PSA 전경 애플리케이션은 PSA 백그라운드 작업이 UI 시작을 요청할 때 시작됩니다. PSA는 포그라운드 계약을 사용하여 사용자 입력을 받거나 미리 보기 인쇄 미리 보기를 사용자에게 표시할 수 있습니다.

printSupportWorkflow 백그라운드 작업 유형이 정의되었습니다. Package.appxmanifest에는 PrintSupportWorkflow 계약에 대해 다음과 같은 확장성 항목이 있습니다.

<Extensions>
    <printsupport:Extension Category="windows.printSupportWorkflow" 
        EntryPoint="PsaBackgroundTasks.PrintSupportWorkflowSample"/>
</Extensions>

계약을 활성화하면 PrintWorkflowJobTriggerDetails가 IBackgroundTaskInstance-TriggerDetails>제공됩니다. PrintWorkflowJobTriggerDetails는 내부적으로 해당 속성의 일부로 PrintWorkflowJobBackgroundSession을 제공합니다. 앱은 PrintWorkflowJobBackgroundSession을 사용하여 인쇄 작업 워크플로의 다양한 삽입 지점과 관련된 이벤트를 등록할 수 있습니다. 이벤트 등록이 완료되면 앱은 PrintWorkflowJobBackgroundSession::Start를 호출하여 인쇄 시스템에서 다양한 삽입 지점과 관련된 이벤트 발생을 시작해야 합니다.

PrintSupportJobUI라는 ActivationKind가 정의됩니다. 새 기능이 필요하지는 않습니다.

<Extensions>
    <printsupport:Extension Category="windows.printSupportJobUI" 
        EntryPoint="PsaSample.PrintSupportJobUISample"/>
</Extensions>

인쇄 지원 워크플로 백그라운드 계약 또는 사용자가 인쇄 작업 오류 알림을 선택할 때 시작할 수 있는 UI 계약입니다. 활성화 시 PrintWorkflowJobActivatedEventArgs가 제공되며 PrintWorkflowJobUISession 개체가 있습니다. PrintWorkflowJobUISession을 사용하면 PDL 데이터에 액세스하려는 경우 포그라운드 애플리케이션이 PdlDataAvailable 이벤트에 등록해야 합니다. 포그라운드 애플리케이션이 작업 중에 발생할 수 있는 오류에 대한 사용자 지정 오류 메시지를 표시하려는 경우 JobNotification 이벤트에 등록해야 합니다. 이벤트가 등록되면 애플리케이션은 인쇄 시스템이 이벤트 발생을 시작하도록 PrintWorkflowJobUISession::Start 함수를 호출해야 합니다.

시스템 렌더링 건너뛰기

namespace PsaBackground
{
    class PrintSupportWorkflowBackgroundTask : IBackgroundTask
    {
        BackgroundTaskDeferral taskDeferral;
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            // Take Task Deferral            
            taskDeferral = taskInstance.GetDeferral();

            var jobTriggerDetails = taskInstance.TriggerDetails as PrintWorkflowJobTriggerDetails;

            var workflowBackgroundSession = jobTriggerDetails.PrintWorkflowJobSession as PrintWorkflowJobBackgroundSession;
            // Register for events
            workflowBackgroundSession.JobStarting += this.OnJobStarting;
            workflowBackgroundSession.PdlModificationRequested += this.OnPdlModificationRequested;
            // Start Firing events
            workflowBackgroundSession.Start();
        }
    
        private void OnJobStarting(PrintWorkflowJobBackgroundSession session, PrintWorkflowJobStartingEventArgs args)
        {
            using (args.GetDeferral())
            {
                // Call SetSkipSystemRendering to skip conversion for XPS to PDL, so that PSA can directly manipulate the XPS file.
                args.SetSkipSystemRendering();
            }
        }
     }
}

PDL 수정 이벤트

PDL 수정 이벤트에 대한 시퀀스 다이어그램:

sequence diagram for the input stream P D L modification event

인쇄 지원 작업 모니터 읽기 및 쓰기 인쇄 작업 콘텐츠에 대한 C# 샘플 코드:

private void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        IInputStream pdlContent = args.SourceContent.GetInputStream();
        // Specify the Content type of stream that will be written to target that is passed to printer accordingly.
        PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinter(args.SourceStream.ContentType);
        IOutputStream outputStream = streamTarget.GetOutputStream();

        using (var inputReader = new Windows.Storage.Streams.DataReader(pdlContent))
        {
            inputReader.InputStreamOptions = InputStreamOptions.Partial;
            using (var outputWriter = new Windows.Storage.Streams.DataWriter(outputStream))
            {
                // Write the updated Print stream from input stream to the output stream
                uint chunkSizeInBytes = 256 * 1024; // 256K chunks
                
                uint lastAllocSize = 0;
                byte[] contentData = new byte[chunkSize];
                while(this.ReadChunk(inputReader, ref contentData))
                {
                    
                    // Make any changes required to the input data
                    // ...                        
                    // Write out the modified content
                    outputWriter.WriteBytes(contentData);
                    await outputWriter.StoreAsync();
                }
            }
        }
        streamTarget.CompleteStreamSubmission(PrintWorkflowSubmittedStatus.Succeeded);
        this.taskDeferral.Complete();
        }
    }
}

워크플로 백그라운드에서 UI 시작

PSA PDL 수정 요청 이벤트 계약에서 인쇄 지원 작업 UI를 시작하기 위한 C# 샘플 코드:

private async void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    IInputStream pdlContent = args.SourceContent.GetInputStream();
    WorkflowPrintTicket printTicket = args.PrinterJob.GetJobPrintTicket();

    bool uiRequired = this.IsUIRequired(pdlContent, printTicket);
    if (!uiRequired)
    {
        // Specify the Content type of content that will be written to target that is passed to printer accordingly.
        PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinter (args.SourceStream.ContentType);
        // Process content directly if UI is not required
        this.ProcessContent(pdlContent, streamTarget);
    }
    else if (args.UILauncher.IsUILaunchEnabled())
    {
        // LaunchAndCompleteUIAsync will launch the UI and wait for it to complete before returning 
        PrintWorkflowUICompletionStatus status = await args.UILauncher.LaunchAndCompleteUIAsync();
        if (status == PrintWorkflowUICompletionStatus.Completed)
        {
            PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinter(args.SourceStream.ContentType);
            this.ProcessContent(pdlContent, streamTarget);
        }
        else
        {
            if (status == PrintWorkflowUICompletionStatus.UserCanceled)
            {
                // Log user cancellation and cleanup here.
                this.taskDeferral.Complete();
            }
            else
            {
                // UI launch failed, abort print job.
                args.Configuration.AbortPrintFlow(PrintWorkflowAbortReason.JobFailed);
                this.taskDeferral.Complete();
            }
        }
    }
    else
    {
        // PSA requires to show UI, but launching UI is not supported at this point because of user selection.
        args.Configuration.AbortPrintFlow(PrintWorkflowAbortReason.JobFailed);
        this.taskDeferral.Complete();
    }
}

PDLDataAvailable 이벤트에 대한 워크플로 작업 UI 활성화

PdlDataAvailable 이벤트에 대한 인쇄 작업 UI 활성화를 위한 시퀀스 다이어그램:

sequence diagram for print job U I activation for the P D L data available event

PSA 작업 UI 활성화 계약에 대한 C# 샘플 코드:

namespace PsaSampleApp
{
    sealed partial class App : Application
    {
        protected override void OnActivated(IActivatedEventArgs args)
        {
            if (args.Kind == ActivationKind.PrintSupportJobUI)
            {
                var rootFrame = new Frame();
        
                rootFrame.Navigate(typeof(JobUIPage));
                Window.Current.Content = rootFrame;
        
                var jobUI = rootFrame.Content as JobUIPage;

                // Get the activation arguments
                var workflowJobUIEventArgs = args as PrintWorkflowJobActivatedEventArgs;

                PrintWorkflowJobUISession session = workflowJobUIEventArgs.Session;
                session.PdlDataAvailable += jobUI.OnPdlDataAvailable;
                session.JobNotification += jobUI.OnJobNotification;
                // Start firing events
                session.Start(); 
            }
        }
    }
}

namespace PsaSampleApp
{
    public sealed partial class JobUIPage : Page    
    {
        public JobUIPage()
        {
            this.InitializeComponent();
        }

        public string WorkflowHeadingLabel;

        public void OnPdlDataAvailable(PrintWorkflowJobUISession session, PrintWorkflowPdlDataAvailableEventArgs args)
        {
            using (args.GetDeferral())
            {
                string jobTitle = args.Configuration.JobTitle;
                string sourceApplicationName = args.Configuration.SourceAppDisplayName;            
                string printerName = args.Printer.PrinterName;
                this.WorkflowHeadingLabel = string.Format(this.formatHeading, jobTitle, sourceApplicationName, printerName);

                // Get pdl stream and content type
                IInputStream pdlContent = args.SourceContent.GetInputStream();
                string contentType = args.SourceContent.ContentType;
                this.ShowPrintPreview(pdlContent, contentType);
            }
        }
    }
}

프린터 작업 특성 가져오기

인쇄 작업의 작업 특성을 가져오기 위한 C# 샘플 코드:

namespace PsaBackground
{
    class PrintSupportWorkflowBackgroundTask : IBackgroundTask
    {
        private async void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, 
                             PrintWorkflowPdlModificationRequestedEventArgs args)
        {
            using (args.GetDeferral())
            {
                string colorMode = this.GetJobColorMode(args.PrinterJob);
                if (colorMode != "monochrome")
                {
                    this.SetJobColorModeToMonochrome(args.PrinterJob);
                } 
            }
        }

        private string GetJobColorMode(PrintWorkflowPrinterJob printerJob)
        {
            var attributes = new List<string>();
            attributes.Add("print-color-mode");
             // Gets the IPP attributes from the current print job
            IDictionary<string, IppAttributeValue> printerAttributes = printerJob.GetJobAttributes(attributes);

            var colorModeValue =  printerAttributes["print-color-mode"];
            this.CheckValueType(colorModeValue, IppAttributeValueKind.Keyword);

            return colorModeValue.GetKeywordArray().First();
        }
    }
} 

프린터 작업 특성 설정

C# 샘플 코드는 위의 프린터 작업 특성 가져오기 섹션에서 작업 특성 설정을 보여 줌을 계속합니다.

private async void SetJobColorModeToMonochrome(PrintWorkflowPrinterJob printerJob)
{
    var attributes = new Dictionary<string, IppAttributeValue>();
    attributes.Add("print-color-mode", IppAttributeValue.CreateKeyword("monochrome"));

    var result = PrinterJob.SetJobAttributes(attributes);
    if (!result.Succeeded)
    {
        this.LogSetAttributeError(result.AttributeErrors);
    }
}

일부 IPP 프린터는 작업을 만든 후 작업 특성 가져오기/설정을 지원하지 않습니다. 이러한 프린터의 경우 PrintJob에는 JobId 속성이 "0"으로 설정되어 있으며 GetJobAttributes SetJobAttributes/는 예외를 제외하고 즉시 실패합니다.

PDL 콘텐츠에 대한 스토리지 파일 액세스 제공

PDF와 같은 일부 PDL 형식은 처리를 시작할 수 있는 전체 스트림이 필요합니다. 따라서 원본 콘텐츠의 StorageFile을 반환하는 PrintWorkflowPdlSourceContent 클래스에 GetContentFileAsync라는 새 메서드가 제공됩니다.

public sealed partial class JobUIPage : Page
{
    public async void OnPdlDataAvailable(PrintWorkflowJobUISession session, PrintWorkflowPdlDataAvailableEventArgs args)
    {
        using (args.GetDeferral())
        {
            if (String.Equals(args.SourceContent.ContentType, "application/pdf", StringComparison.OrdinalIgnoreCase))
            {
                // Wait for all PDL data to be available
                StorageFile sourceFile == await args.SourceContent.GetContentFileAsync();
                IRandomAccessStream sourceStream = await sourceFile.OpenReadAsync();

                PdfDocument pdfDocument = await PdfDocument.LoadFromStreamAsync(sourceStream);

                for (uint i = 0; i < pdfDocument.PageCount; i++)
                {
                    PdfPage page = pdfDocument.GetPage(i);
                    var pageImage = new InMemoryRandomAccessStream();
                    await page.RenderToStreamAsync(pageImage);
                    this.AddImageToPreviewImageList(pageImage);
                }
            }
        }
    }
}    

XPS를 PDF로 PDL 변환

XPS에서 PDF로의 PDL 변환을 보여 주는 C# 샘플 코드:

private async void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        if (String.Equals(args.SourceContent.ContentType, "application/oxps", StringComparison.OrdinalIgnoreCase))
        {
            var xpsContent = args.SourceContent.GetInputStream();

            var printTicket = args.PrinterJob.GetJobPrintTicket();
            PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinter("application/pdf");

            // Modify XPS stream here to make the needed changes 
            // for example adding a watermark

            PrintWorkflowPdlConverter pdlConverter = args.GetPdlConverter(PrintWorkflowPdlConversionType.XpsToPdf);
            await pdlConverter.ConvertPdlAsync(printTicket, xpsContent, streamTarget.GetOutputStream());

            streamTarget.CompleteStreamSubmission(PrintWorkflowSubmittedStatus.Succeeded);
        }
        else
        {
            // We except source content to be XPS in this case, abort the session if it is not XPS.
            args.Configuration.AbortPrintFlow(PrintWorkflowAbortReason.JobFailed);
        }
    }
    this.taskDeferral.Complete();
}

작업 알림 이벤트

작업 알림 이벤트에 대한 시퀀스 다이어그램:

sequence diagram for the job notification event

위의 PDLDataAvailable 이벤트 섹션에 대한 워크플로 작업 UI 활성화에서 계속되는 C# 샘플 코드는 작업 알림에 오류를 표시합니다.

public sealed partial class JobUIPage : Page    
{
    public void OnJobNotification(PrintWorkflowJobUISession session, PrintWorkflowJobNotificationEventArgs args)
    {
        using (args.GetDeferral())
        {
            PrintWorkflowPrinterJobStatus jobStatus = args.PrintJob.GetJobStatus();

            switch (jobStatus)
            {
                case PrintWorkflowPrinterJobStatus::Error:
                    // Show print job error to the user
                    Frame->Navigate(JobErrorPage::typeid, this);
                break;
                case PrintWorkflowPrinterJobStatus::Abort:
                    // Show message that print job has been aborted.
                    Frame->Navigate(JobAbortPage::typeid, this);
                break;
                case PrintWorkflowPrinterJobStatus::Completed:
                    // Show job successfully completed message to the user.
                    Frame->Navigate(JobCompletedPage::typeid, this);
                break;
            }
        }
    }    
}

초기 작업 특성을 사용하여 작업 만들기

현재 일부 IPP 프린터는 set-attribute 작업을 지원하지 않습니다. 이 문제를 완화하기 위해 PrintWorkflowPdlDataAvailableEventArgs의 CreateJobOnPrinterWithAttributes 함수 및 CreateJobOnPrinterWithAttributesBuffer 함수가 제공됩니다. PSA 개발자는 이러한 API를 사용하여 프린터에서 작업을 만들 때 프린터에 전달되는 작업 특성을 제공할 수 있습니다.

public sealed partial class JobUIPage : Page
{
    public async void OnPdlDataAvailable(PrintWorkflowJobUISession session, PrintWorkflowPdlDataAvailableEventArgs args)
    {
       var attributes = new Dictionary<string, IppAttributeValue>();
       attributes.Add("print-color-mode", IppAttributeValue.CreateKeyword("monochrome"));
       // Create job on printer with initial job attributes
       PrintWorkflowPdlTargetStream streamTarget = args.CreateJobOnPrinterWithAttributes(attributes, "application/pdf");
        // Write data to target stream
    }
}

순차 XPS 처리

스풀링이 완료되기 전에 순차적으로 XPS를 처리하기 위한 C++/Winrt 샘플 코드입니다.

namespace winrt
{
    struct WorkflowReceiver : public winrt::implements<WorkflowReceiver, IPrintWorkflowXpsReceiver2>
    {
        STDMETHODIMP SetDocumentSequencePrintTicket(_In_ IStream* documentSequencePrintTicket) noexcept override
        {
            // process document sequence print ticket
            return S_OK;
        }

        STDMETHODIMP SetDocumentSequenceUri(PCWSTR documentSequenceUri) noexcept override
        {
            // process document sequence URI
        }

        STDMETHODIMP AddDocumentData(UINT32 documentId, _In_ IStream* documentPrintTicket,
            PCWSTR documentUri) noexcept override
        {
            // process document URI and print ticket
            return S_OK;
        }

        STDMETHODIMP AddPage(UINT32 documentId, UINT32 pageId,
            _In_ IXpsOMPageReference* pageReference, PCWSTR pageUri)  noexcept override
        {
            // process XPS page
            return S_OK;
        }

        STDMETHODIMP Close() noexcept override
        {
            // XPS processing finished
            return S_OK;
        }

        STDMETHODIMP Failed(HRESULT XpsError) noexcept override
        {
            // XPS processing failed, log error and exit
            return S_OK;
        }
    };

    void PsaBackgroundTask::OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session,
        PrintWorkflowPdlModificationRequestedEventArgs args)
    {
    auto contentType = args.SourceContent().ContentType();
        if (contentType == L"application/oxps")
        {
                    auto xpsContent = args.SourceContent().GetInputStream();
                    PrintWorkflowObjectModelSourceFileContent xpsContentObjectModel(xpsContent);
                    com_ptr<IPrintWorkflowObjectModelSourceFileContentNative> xpsContentObjectModelNative;
                    check_hresult(winrt::get_unknown(xpsContentObjectModel)->QueryInterface( 
                                                        IID_PPV_ARGS(xpsContentObjectModelNative.put())));
        
                    auto xpsreceiver = make_self<WorkflowReceiver>();
                    check_hresult(xpsContentObjectModelNative->StartXpsOMGeneration(xpsreceiver.get()));
        }
    }
}

표시 이름 지역화 및 PDL 통과 API 통합

Important

이 섹션에서는 Windows 11 버전 22H2부터 사용할 수 있는 PSA 기능에 대해 설명합니다.

이 시나리오에서 PSA는 PDC(인쇄 디바이스 기능)를 사용자 지정하고 문자열 지역화를 위한 PDR(인쇄 디바이스 리소스)을 제공합니다.

또한 PSA는 지원되는 PDL 통과 API 콘텐츠 형식(PDL 형식)도 설정합니다. PSA가 이벤트를 구독하지 않거나 SetSupportedPdlPassthroughContentTypes를 명시적으로 호출하지 않으면 이 PSA 앱과 연결된 프린터에 대해 PDL 통과가 비활성화됩니다.

// Event handler called every time PrintSystem updates PDC or BindPrinter is called
 private void OnPdcChanged(PrintSupportExtensionSession session, PrintSupportPrintDeviceCapabilitiesChangedEventArgs args)
{
    using (args.GetDeferral())
    {
        XmlDocument pdc = args.GetCurrentPrintDeviceCapabilities();
        XmlDocument pdr = args.GetCurrentPrintDeviceResources();
        
        // Check current PDC and make changes according to printer device capabilities 
        XmlDocument newPdc = this.CheckAndUpdatePrintDeviceCapabilities(pdc);
        // Get updated printer devices resources, corresponding to the new PDC 
        XmlDocument newPdr = this.GetPrintDeviceResourcesInfo(newPdc, pdr, args.ResourceLanguage);

        // Update supported PDL formats 
        args.SetSupportedPdlPassthroughContentTypes(GetSupportedPdlContentTypes());
        
        args.UpdatePrintDeviceCapabilities(newPdc);
        args.UpdatePrintDeviceResources(newPdr);
    }
}

페이지 수준 기능 지원 및 작업 특성

Important

이 섹션에서는 Windows 11 버전 22H2부터 사용할 수 있는 PSA 기능에 대해 설명합니다.

페이지 수준 기능 지원 및 작업 특성 시나리오는 샘플 코드에서 동일한 위치에서 변경하여 처리되므로 그룹화됩니다.

  • 페이지 수준 기능 지원: 이 시나리오에서 PSA 애플리케이션은 PrintTicket에서 구문 분석된 IPP 특성으로 재정의해서는 안 되는 페이지 수준 특성을 지정합니다.

  • 작업 특성 지원(PIN 인쇄)에 대한 별도의 컬렉션: 이 시나리오에서 PSA 애플리케이션은 사용자 지정 IPP 작업 특성(예: PIN)을 지정합니다.

다음 C# 샘플 코드는 작업 특성 시나리오에 대한 페이지 수준 기능 지원별도의 컬렉션에 필요한 변경 내용을 보여 줍니다.

private void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession session, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        IInputStream pdlContent = args.SourceContent.GetInputStream();
    
        // Custom job attributes to add to the printJob
        IDictionary<string, IppAttributeValue> jobAttributes = LocalStorageUtil.GetCustomIppJobAttributes();
        // Custom operation attributes to add to printJob
        IDictionary<string, IppAttributeValue> operationAttributes = LocalStorageUtil.GetCustomIppOperationAttributes();
        
        // PSA has an option to select preferred PDL format
        string documentFormat = GetDocumentFormat(args.PrinterJob.Printer);
    
        // Create PrintJob with specified PDL and custom attributes
        PrintWorkflowPdlTargetStream targetStream = args.CreateJobOnPrinterWithAttributes(jobAttributes, documentFormat  , operationAttributes,
           PrintWorkflowAttributesMergePolicy  .DoNotMergeWithPrintTicket /*jobAttributesMergePolicy*/, PrintWorkflowAttributesMergePolicy.MergePreferPsaOnConflict /*operationAttributesMergePolicy*/);
    
        // Adding a watermark to the output(targetStream) if source payload type is XPS
        this.ModifyPayloadIfNeeded(targetStream, args, documentFormat, deferral);
    
        // Marking the stream submission as Succeeded.
        targetStream.CompleteStreamSubmission(PrintWorkflowSubmittedStatus.Succeeded);
    
        this.taskDeferral.Complete();
    }
}

PSA를 사용하여 인쇄 대화 상자 개선

Important

이 섹션에서는 Windows 11 버전 22H2부터 사용할 수 있는 PSA 기능에 대해 설명합니다.

이 시나리오에서는 PSA 통합과 함께 인쇄 대화 상자를 사용하면 다음 작업을 수행할 수 있습니다.

  • MPD에서 선택이 PSA와 연결된 프린터로 변경될 때 콜백 가져오기

  • openUrl 작업을 지원하는 하나의 AdaptiveCard 표시

  • 인쇄 대화 상자에서 사용자 지정 기능 및 매개 변수 표시

  • PrintTicket을 수정하여 인쇄 대화 상자에 표시된 기능 옵션의 선택을 변경합니다.

  • 인쇄 앱의 Windows.ApplicationModel.AppInfo 를 가져와서 인쇄 대화 상자를 엽니다.

다음 C# 샘플에서는 이러한 인쇄 대화 상자의 향상된 기능을 보여 줍니다.

public BackgroundTaskDeferral TaskInstanceDeferral { get; set; }

public void Run(IBackgroundTaskInstance taskInstance)
{
    // Take task deferral 
    TaskInstanceDeferral   = taskInstance.GetDeferral();
    // Associate a cancellation handler with the background task 
    taskInstance.Canceled += OnTaskCanceled;

    if (taskInstance.TriggerDetails is PrintSupportExtensionTriggerDetails extensionDetails)
    {
         PrintSupportExtensionSession session = extensionDetails.Session;
         session.PrintTicketValidationRequested += OnSessionPrintTicketValidationRequested;
         session.PrintDeviceCapabilitiesChanged += OnSessionPrintDeviceCapabilitiesChanged;
         session.PrinterSelected += this.OnPrinterSelected;
    }
}

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

// Event handler called when the PSA Associated printer is selected in Print Dialog
private void OnPrinterSelected(PrintSupportExtensionSession session, PrintSupportPrinterSelectedEventArgs args)
{
    using (args.GetDeferral())
    {
        // Show adaptive card in the Print Dialog (generated based on Printer and Printing App) 
        args.SetAdaptiveCard  (GetCustomAdaptiveCard(session.Printer, args.SourceAppInfo));

        // Request to show Features and Parameters in the Print Dialog if not shown already
        const string xmlNamespace = "\"http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords\"";
        var additionalFeatures= new List<PrintSupportPrintTicketElement> { new PrintSupportPrintTicketElement { LocalName = "PageMediaType", NamespaceUri = xmlNamespace } };                  
        var additionalParameters = new List<PrintSupportPrintTicketElement> { new PrintSupportPrintTicketElement { LocalName = "JobCopiesAllDocuments", NamespaceUri = xmlNamespace } };

        if ((featuresToShow.Count + parametersToShow.Count) <= args.AllowedCustomFeaturesAndParametersCount)
        {
            args.SetAdditionalFeatures(additionalFeatures);
            args.SetAdditionalParameter(additionalParameters);
        }
        else
        {
            // Cannot show that many additional features and parameters, consider reducing the number
            // of additional features and parameters by selecting only the most important ones
        }
    }
}

// Create simple AdaptiveCard to show in MPD
public IAdaptiveCard GetCustomAdaptiveCard(IppPrintDevice ippPrinter, AppInfo appInfo)
{
    return AdaptiveCardBuilder.CreateAdaptiveCardFromJson($@"
        {{""body"": [
                {{ 
                    ""type"": ""TextBlock"",
                    ""text"": ""Hello {appInfo.DisplayInfo.DisplayName} from {ippPrinter.PrinterName}!""
                }}
              ],
              ""$schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"",
            ""type"": ""AdaptiveCard"",
            ""version"": ""1.0""
        }}");
}

호스트 기반 처리 플래그를 사용하여 PDL 변환

Important

이 섹션에서는 Windows 11 버전 22H2부터 사용할 수 있는 PSA 기능에 대해 설명합니다.

현재 PDL 변환 API인 PrintWorkflowPdlConverter.ConvertPdlAsync는 기본적으로 호스트 기반 처리를 수행합니다. 즉, 프린터에서 이러한 작업을 수행할 필요가 없도록 호스트/인쇄 컴퓨터에서 회전, 페이지 순서 등을 수행합니다. 그러나 프린터 IHV는 프린터가 이 작업을 더 잘 수행할 수 있으므로 호스트 기반 처리 없이 PDL 변환을 원할 수 있습니다. ConvertPdlAsync 함수는 이 요구 사항을 해결하기 위해 호스트 기반 처리 플래그를 사용합니다. PSA는 이 플래그를 사용하여 모든 호스트 기반 처리 또는 특정 호스트 기반 처리 작업을 건너뛸 수 있습니다.

class HostBaseProcessingRequirements
{
    public bool CopiesNeedsHostBasedProcessing = false;
    public bool PageOrderingNeedsHostBasedProcessing = false;
    public bool PageRotationNeedsHostBasedProcessing = false;
    public bool BlankPageInsertionNeedsHostBasedProcessing = false;
}

private async void OnPdlModificationRequested(PrintWorkflowJobBackgroundSession sender, PrintWorkflowPdlModificationRequestedEventArgs args)
{
    using (args.GetDeferral())
    {
        var targetStream = args.CreateJobOnPrinter("application/pdf");
        var pdlConverter = args.GetPdlConverter(PrintWorkflowPdlConversionType.XpsToPdf);

        var hostBasedRequirements = this.ReadHostBasedProcessingRequirements(args.PrinterJob.Printer);
            
        PdlConversionHostBasedProcessingOperations hostBasedProcessing = PdlConversionHostBasedProcessingOperations.None;
        if (hostBasedRequirements.CopiesNeedsHostBasedProcessing)
        {
            hostBasedProcessing |= PdlConversionHostBasedProcessingOperations.Copies;
        }

        if (hostBasedRequirements.PageOrderingNeedsHostBasedProcessing)
        {
            hostBasedProcessing |= PdlConversionHostBasedProcessingOperations.PageOrdering;
        }

        if (hostBasedRequirements.PageRotationNeedsHostBasedProcessing)
        {
            hostBasedProcessing |= PdlConversionHostBasedProcessingOperations.PageRotation;
        }

        if (hostBasedRequirements.BlankPageInsertionNeedsHostBasedProcessing)
        {
            hostBasedProcessing |= PdlConversionHostBasedProcessingOperations.BlankPageInsertion;
        }

        await pdlConverter.ConvertPdlAsync(args.PrinterJob.GetJobPrintTicket(), args.SourceContent.GetInputStream(), targetStream.GetOutputStream(), hostBasedProcessing);
    }
}

private HostBaseProcessingRequirements ReadHostBasedProcessingRequirements(IppPrintDevice printDevice)
{
    // Read Host based processing requirements for the printer
}

PDC(인쇄 장치 기능) 업데이트 정책 설정

Important

이 섹션에서는 Windows 11 버전 22H2부터 사용할 수 있는 PSA 기능에 대해 설명합니다.

프린터 IHV는 PDC(인쇄 장치 기능)를 업데이트해야 하는 경우에 대해 서로 다른 요구 사항을 가질 수 있습니다. 이러한 요구 사항을 해결하기 위해 PrintSupportPrintDeviceCapabilitiesUpdatePolicy 는 PDC에 대한 업데이트 정책을 설정할 수 있습니다. PSA는 이 API를 사용하여 시간 또는 인쇄 작업 수에 따라 PDC 업데이트 정책을 설정할 수 있습니다.

작업 수에 따라 PDC 업데이트 정책 설정

// Event handler called every time PrintSystem updates PDC
private void OnPdcChanged(PrintSupportExtensionSession session, PrintSupportPrintDeviceCapabilitiesChangedEventArgs args)
{
    using (args.GetDeferral())
    {
        // Set update policy to update the PDC on bind printer of every print job.
        var updatePolicy = PrintSupportPrintDeviceCapabilitiesUpdatePolicy.CreatePrintJobRefresh(1);
        args.SetPrintDeviceCapabilitiesUpdatePolicy(updatePolicy);      
    }
}

TimeOut에 따라 PDC 업데이트 정책 설정

// Event handler called every time PrintSystem updates PDC
private void OnPdcChanged(PrintSupportExtensionSession session, PrintSupportPrintDeviceCapabilitiesChangedEventArgs args)
{
    using (args.GetDeferral())
    {
        // Set update policy to update the PDC on bind printer of every print job.
        var updatePolicy = PrintSupportPrintDeviceCapabilitiesUpdatePolicy.CreatePrintJobRefresh(1);
        args.SetPrintDeviceCapabilitiesUpdatePolicy(updatePolicy);      
    }
}

PSA(일반 인쇄 지원 앱) 디자인 지침

인쇄 지원 앱을 디자인할 때는 다음 측면을 디자인에 포함해야 합니다.

  • 포그라운드 및 백그라운드 계약은 모두 여러 인스턴스를 지원하는 것으로 표시되어야 합니다. 예를 들어 SupportsMultipleInstance 는 패키지 매니페스트에 있어야 합니다. 이는 여러 동시 작업에 대해 계약의 수명을 안정적으로 관리할 수 있도록 하기 위한 것입니다.

  • PDL 수정을 위한 시작 UI를 선택적 단계로 처리합니다. UI 시작이 허용되지 않은 경우에도 인쇄 작업을 성공적으로 완료하기 위해 최선을 다합니다. PDL을 수정하는 동안 사용자 입력 없이 작업을 성공적으로 완료할 방법이 없는 경우에만 인쇄 작업을 중단해야 합니다. 이러한 경우 수정되지 않은 PDL을 보내는 것이 좋습니다.

  • PDL 수정을 위해 UI를 시작할 때 LaunchAndCompleteUIAsync를 호출하기 전에 IsUILaunchEnabled를 호출합니다. 이는 현재 시간에 UI를 표시할 수 없는 시나리오가 계속 올바르게 인쇄되도록 하기 위한 것입니다. 이러한 시나리오는 헤드리스 디바이스 또는 현재 키오스크 모드이거나 방해 모드를 방해하지 않는 디바이스에 있을 수 있습니다.

지원 앱 연결 인쇄

Windows.Devices.Printers

Windows.Graphics.Printing.PrintSupport

Windows.Graphics.Printing.Workflow

IPP(인터넷 인쇄 프로토콜) 사양