【.NET 5】【WPF】Edit and print PDF files 1
Tags: csharp, dotnet, wpf, pdfsharpcore
Published: 2020-12-26 14:44:49

## Intro
In this time, I want to edit and print PDF files.

### Environments
* .NET ver.5.0.101
* NLog ver.4.7.6
* (For editing PDF) PdfSharpCore ver.1.2.10

## Print files
For printing files in .NET, I can use "System.Drawing.Printing" and "System.Printing".

* [System.Drawing.Printing Namespace | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/system.drawing.printing?view=dotnet-plat-ext-5.0)
* [System.Printing Namespace | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/system.printing?view=net-5.0)

And I choose later one.

In .NET 5, I couldn't use it in Console applications.
And because it hadn't supported for ASP.NET Core, so I created a WPF application. 

In this sample, the application gets file path from command line arguments, and print it on rendering MainWindow.
After that, it will close automatically.

#### MainWindow.xaml.cs
```cs
using System;
using System.IO;
using System.Printing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media.Imaging;
using NLog;

namespace PdfPrintSample
{
    public partial class MainWindow : Window
    {
        Logger logger = LogManager.GetCurrentClassLogger();
        public MainWindow()
        {
            InitializeComponent();
        }        
        protected override async void OnContentRendered(EventArgs e)
        {
            base.OnContentRendered(e);

            // Get command line arguments
            var args = Environment.GetCommandLineArgs();
            if(args.Length <= 1)
            {
                logger.Error("No arguments");
                return;
            }
            var filePath = args[1];
            // TODO: load file.

            // Get print queue by name
            var printServer = new LocalPrintServer();
            var queue = printServer.GetPrintQueue("Microsoft Print to PDF");
            // Use default printer
            var defaultQueue = LocalPrintServer.GetDefaultPrintQueue();

            var samplePage = new FixedPage();
            var canvas = new Canvas();
            var sampleText = new TextBlock();
            sampleText.Text = "Hello";
            sampleText.FontSize = 32;
            canvas.Children.Add(sampleText);
            samplePage.Children.Add(canvas);

            // Print sample page
            var writer = PrintQueue.CreateXpsDocumentWriter(queue); 
            writer.Write(samplePage);
                    
            // Close this application
            this.Close();
        }
    }
}
```
#### Result
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/n1z5fkyz0wlqm9w9ddtv.png)

## Print PDF file
For printing PDF files, I have to resolve one problem.
This sample only can print XPS files.

Though I can write like this, if the file of "filePath" is JPG or PDF, this code won't work.
```cs
var file = File.ReadAllBytes(filePath);
var printQueue = LocalPrintServer.GetDefaultPrintQueue();

using (var job = printQueue.AddJob())
using (var stream = job.JobStream)
{
    stream.Write(file, 0, file.Length);
}
```

There are some libraries to resolve this, but I choosed using WinRT API.
I can convert from PDF file to "Image".
And I can treat it as XPS. 

From Windows 10 version 1809, WinRT API are able to be called by not only UWP, but also WPF and WinForms.

* [Call Windows Runtime APIs in desktop apps - Windows applications | Microsoft Docs](https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/desktop-to-uwp-enhance)
* [Calling Windows APIs in .NET5 - Windows Developer Blog](https://blogs.windows.com/windowsdeveloper/2020/09/03/calling-windows-apis-in-net5/)
* [Windows.Data.Pdf Namespace - Windows UWP applications | Microsoft Docs](https://docs.microsoft.com/en-us/uwp/api/windows.data.pdf?view=winrt-18362)

And after .NET 5, all I have to do for using WinAPI are only change the "TargetFramework".

#### PdfPrintSample.csproj
```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
  <ItemGroup>
    <Content Update="Nlog.config" CopyToOutputDirectory="PreserveNewest"/>
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="NLog" Version="4.7.6"/>
    <PackageReference Include="PdfSharpCore" Version="1.2.10"/>
  </ItemGroup>
</Project>
```

#### MainWindow.xaml.cs
```cs
        protected override async void OnContentRendered(EventArgs e)
        {
            base.OnContentRendered(e);
             
            var args = Environment.GetCommandLineArgs();
            if(args.Length <= 0)
            {
                 logger.Error("No arguments");
            }
            var filePath = args[1];
            using(var fileStream = new FileStream(filePath, FileMode.Open))
            using(var stream = fileStream.AsRandomAccessStream())
            {
                var document = await Windows.Data.Pdf.PdfDocument.LoadFromStreamAsync(stream);
                using(var page = document.GetPage(0))
                using(var memoryStream = new MemoryStream())
                using(var outputStream = memoryStream.AsRandomAccessStream())
                {
                    await page.RenderToStreamAsync(outputStream);
                    // Get bitmap from stream
                    var bitmap = BitmapDecoder.Create(memoryStream, BitmapCreateOptions.None,    
                            BitmapCacheOption.OnLoad).Frames[0];
                    // Get print queue by name
                    var printServer = new LocalPrintServer();
                    var queue = printServer.GetPrintQueue("Microsoft Print to PDF");
                    
                    var image = new Image();
                    image.Source = bitmap;
                    // Print Image
                    var writer = PrintQueue.CreateXpsDocumentWriter(queue);                        
                    writer.Write(image);
                }
            }            
            this.Close();
         }
...
```

## Edit PDF
There are some libraries to edit PDF in .NET.
But most of them are But most of them are GPL or paid software.

If I use them in my private programming, I don't need warry so much.
But in my work, I have to think carefully.

This time, I try PDFSharpCore.

* [GitHub - ststeiger/PdfSharpCore: Port of the PdfSharp library to .NET Core - largely removed GDI+ (only missing GetFontData - which can be replaced with freetype2)](https://github.com/ststeiger/PdfSharpCore)

### Draw rectangles and text
#### MainWindow.xaml.cs
```cs
...
using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
using PdfSharpCore.Pdf.IO;

namespace PdfPrintSample
{
    public partial class MainWindow : Window
    {
...       
        protected override async void OnContentRendered(EventArgs e)
        {
...
            var contents = Edit(filePath);
            if(contents == null || contents.Length <= 0)
            {
                logger.Error("Failed Editing");
                return;
            }
            using(var memStream = new MemoryStream())
            {
                memStream.Write(contents, 0, contents.Length);
                using(var stream = memStream.AsRandomAccessStream())
                {
                    var document = await Windows.Data.Pdf.PdfDocument.LoadFromStreamAsync(stream);
...                 
                }
            }
            this.Close();
        }
        private byte[] Edit(string filePath)
        { 
            using(var document = PdfReader.Open(filePath, PdfDocumentOpenMode.Modify))
            {
                var graphics = XGraphics.FromPdfPage(document.Pages[0]);
                // Draw rectangle
                var pen = new XPen(XColors.HotPink, Math.PI);
                graphics.DrawRectangle(pen, 30, 20, 100, 60);
                // Draw text
                var font = new XFont( "Meiryo UI" , 32, XFontStyle.Regular);
                graphics.DrawString( "Hello World" , font, XBrushes.BlueViolet,
                        new XRect (70, 60, document.Pages[0].Width, document.Pages[0].Height), 
                        XStringFormats.Center);
                // Save as a file
                // In this sample, to pass the data to print operations without outputting it,
                // I save as a stream.
                //document.Save(@"C:\Users\example\Documents\PdfPrintSample\sample.pdf");
                
                // Save as a stream
                using(MemoryStream memStream = new MemoryStream()) 
                { 
                    document.Save(memStream, true);
                    return memStream.ToArray();
                }
            }
        }
    }
}
```
#### Before
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8pyounvmz0iq5otx9mzl.png)

#### Result
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/z61097owwgrsmztdoydn.png)

* [c# - PDFsharp save to MemoryStream - Stack Overflow](https://stackoverflow.com/questions/1073277/pdfsharp-save-to-memorystream)

### Edit TextField (Failed)
I also can edit TextField values like below.

#### MainWindow.xaml.cs
```cs
...
namespace PdfPrintSample
{
    public partial class MainWindow : Window
    {
...       
        private byte[] Edit(string filePath)
        { 
            using(var document = PdfReader.Open(filePath, PdfDocumentOpenMode.Modify))
            {
                // To display the changed text
                if(document.AcroForm.Elements.ContainsKey("/NeedAppearances") == false)
                {
                    document.AcroForm.Elements.Add("/NeedAppearances", new PdfBoolean(true));
                }
                else
                {
                    document.AcroForm.Elements["/NeedAppearances"] = new PdfBoolean(true);
                }
                // Set value
                document.AcroForm.Fields["SampleField2"].Value = new PdfString("Hello");
                
                // Save as a file
                document.Save(@"C:\Users\example\Documents\PdfPrintSample\sample.pdf");
                
                // Save as a stream
                using(MemoryStream memStream = new MemoryStream()) 
                { 
                    document.Save(memStream, true);
                    return memStream.ToArray();
                }
            }
        }
    }
}
```
* [c# - PDFSharp filling in form fields - Stack Overflow](https://stackoverflow.com/questions/6240585/pdfsharp-filling-in-form-fields)

### Problem
Now, I got a problem.
The text field value was changed in the saved file.
But after printing, the value wasn't changed.

#### Saved file
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/x3k94eqp6qc6mmws9d72.png)

#### Printed data
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/iakvplr6aopew1bihcwn.png)

After saving the file, I open it by Adobe Acrobat DC and save.
After that I try printing it again.

The value is changed.

So shall I do anything to save PDFTextField values?
But I haven't found any informations to resolve it yet.

When I change the value by PDF-LIB (JavaScript library), the problem won't be occurred.

* [PDF-LIB · Create and modify PDF documents in any JavaScript environment.](https://pdf-lib.js.org/)

So I may be able to find hints from that :)