C# Code to Print Word Document over .NET 5.0 or .NET Core Frameworks

Hi.

I’m evaluating Aspose.words for a project, but I’ve just struck a rock. I find that the Document.Print() methods are missing from the API, though I can see that they exist in the .NET framework version of the API.

Is it not possible to print documents in .NET 5/Core?

Many thanks

@LukeWebber,

I am afraid, as mentioned in the following page, the Document.Print Method is not available in .NET Core.

Your forum thread has been linked to the appropriate issue (WORDSNET-17369) and you will be notified here as soon as printing in .NET Core environment may be supported in future. Sorry for the inconvenience.

However, the following simple code can be used to print document by using the System.Drawing.Common. For printing, the document is first converted to images and then printed. Please note that in such approach there might be quality degradation because of conversion to images.

DocumentPrinter printer = new DocumentPrinter();
Document doc = new Document(@"C:\Temp\input.docx");
printer.Print(doc);

public class DocumentPrinter
{
    /// <summary>
    /// Prints the specified document using default printer.
    /// </summary>
    public void Print(Document doc)
    {
        mDoc = doc;
        mPageIndex = 0;
        PrintDocument pd = new PrintDocument();
        pd.PrintPage += PrintPage;
        // pd.PrinterSettings.PrinterName = "Microsoft XPS Document Writer";
        // pd.PrinterSettings.PrintToFile = true;
        // pd.PrinterSettings.PrintFileName = "C:\\Temp\\.net core printing.xps";
        pd.Print();
    }

    private void PrintPage(object o, PrintPageEventArgs e)
    {
        try
        {
            using (MemoryStream pageImageStream = new MemoryStream())
            {
                ImageSaveOptions options = new ImageSaveOptions(SaveFormat.Png);

                PageRange pageRange = new PageRange(mPageIndex, mPageIndex); // Specify Range
                options.PageSet = new PageSet(pageRange);

                mDoc.Save(pageImageStream, options);
                pageImageStream.Position = 0;

                using (Image pageImage = Image.FromStream(pageImageStream))
                {
                    e.Graphics.DrawImage(pageImage, new Point(0, 0));
                }

                // Increase page index and check if there are more pages.
                mPageIndex++;
                e.HasMorePages = (mPageIndex < mDoc.PageCount);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            // Stop printing in case of errors.
            e.HasMorePages = false;
        }
    }

    private int mPageIndex;
    private Document mDoc;
}

Thanks for that, Hafeez.

I’ve implemented that, with one slight tweak…

ImageSaveOptions options = new ImageSaveOptions(SaveFormat.Png)
{
    UseAntiAliasing = true,
    UseHighQualityRendering = true,
    Resolution = 300 //TODO: Include in configuration
};

… because the print quality was otherwise awful - 96dpi by default.

However, I’m left with one further problem. The vertical margins on the printed documents are not the same as in the Word document and, as a result, I’m getting two pages rather than one from my test document, and wasted space in the header section at the top. Any help on how I can make the output conform to the way that Word prints the document?

Never mind. I’ve got it, I think. I need to set the PrinterSettings.DefaultPageSettings to match the document’s settings. There are a few bones in this, but I think I’m getting there. I will say, though, that I wish you’d implemented the Print method in .NET before this. It’s coming of for five years since .NET Core version 1, after all.

@LukeWebber,

We will inform you here as soon as WORDSNET-17369 will get resolved in future.

Have you tried the latest (21.6) version of Aspose.Words for .NET on your end? In case the problem still remains, then can you please ZIP and upload a sample Word document you are getting this problem with here for testing? We will then investigate the issue on our end and provide you more information.

Thanks Awais, but I believe I’ve got this sorted out now. The document was trying to print to Letter stationery, and I needed to set every section to A4.

@LukeWebber,

It is great that you were able to resolve the problem on your end. In case you have further inquiries or may need any help in future, please let us know by posting a new thread in Aspose.Words’ forum.

Status quo? Printer Trays!?

Another year has passed. What about printing with .NET Core or netstandard2.x now? I know that Microsoft does not make things easy with its changes to .NET Core, but the platform specific code has been sorted out better with .NET 5/6. So I cannot believe that the leading company for handling Word documents (apart from Microsoft) is not able to do printing almost three years after .NET Core 3.0 was published.

To summarize what I think is the status quo: Using XPS Windows API is fine and sort of abstract, but now obsoleted and discouraged by Microsoft (XPS Print API - Win32 apps | Microsoft Learn). So we have to stick with a page-wise export as image and print that via another Windows API (Print Document Package API - Win32 apps | Microsoft Learn).

Our situation here: We are moving from a VSTO-Word-Add-In to Aspose, and printing is vital for our customers. They use documents with options for printout to different printer trays.

How do we do that with Aspose?

As far as I have seen the printer tray options for a Word document are stored in an OOXML tag such as <w:paperSrc w:first=“4”/> stored along with tags for page margins.

Could you please update the example code such that the print output uses the printer trays the same way Word would do? Do we have to re-invent the wheel?

1 Like

@GEDAT Unfortunately, currently there is no a ready do use solution for printing document in .NET Standard and .NET Core.
However, you can modify the code suggested above to specify paper source. You can use Document.GetPageInfo method to get paper tray specified for the page in the document.
Also, to improve quality of printing you can convert document pages to metafile instead of raster image. For example see the following modified code:

DocumentPrinter printer = new DocumentPrinter();
Document doc = new Document(@"C:\Temp\in.docx");
printer.Print(doc);
public class DocumentPrinter
{
    /// <summary>
    /// Prints the specified document using default printer.
    /// </summary>
    public void Print(Document doc)
    {
        mDoc = doc;
        mPageIndex = 0;
        PrintDocument pd = new PrintDocument();
        pd.PrintPage += PrintPage;
        mPrinterSettings = pd.PrinterSettings;
        // pd.PrinterSettings.PrinterName = "Microsoft XPS Document Writer";
        // pd.PrinterSettings.PrintToFile = true;
        // pd.PrinterSettings.PrintFileName = "C:\\Temp\\.net core printing.xps";
        pd.Print();
    }

    private void PrintPage(object o, PrintPageEventArgs e)
    {
        try
        {
            using (MemoryStream pageImageStream = new MemoryStream())
            {
                ImageSaveOptions options = new ImageSaveOptions(SaveFormat.Emf);

                PageRange pageRange = new PageRange(mPageIndex, mPageIndex); // Specify Range
                options.PageSet = new PageSet(pageRange);

                mDoc.Save(pageImageStream, options);
                pageImageStream.Position = 0;

                // Get paper tray specified in the document.
                PageInfo pageInfo = mDoc.GetPageInfo(mPageIndex);
                e.PageSettings.PaperSource = GetSpecifiedPrinterPaperSource(pageInfo.PaperTray);

                using (Image pageImage = Image.FromStream(pageImageStream))
                {
                    e.Graphics.DrawImage(pageImage, new Point(0, 0));
                }

                // Increase page index and check if there are more pages.
                mPageIndex++;
                e.HasMorePages = (mPageIndex < mDoc.PageCount);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            // Stop printing in case of errors.
            e.HasMorePages = false;
        }
    }

    private PaperSource GetSpecifiedPrinterPaperSource(int paperTray)
    {
        foreach (PaperSource paperSource in mPrinterSettings.PaperSources)
        {
            if (paperSource.RawKind == paperTray)
                return paperSource;
        }

        // If nothing is found, return the default value
        return mPrinterSettings.DefaultPageSettings.PaperSource;
    }

    private int mPageIndex;
    private Document mDoc;
    private PrinterSettings mPrinterSettings;
}

Please let us know if the proposed solution works for you.

1 Like

Thank you @alexey.noskov, you made our day with your instantaneous reply!That was utterly impressive! The code adaptions you provided were almost what we needed to make it work and were really helpful, including the hint about the scalable vector graphics format SaveFormat.Emf. I updated the code to the use of local functions (C#7, to avoid class fields), using statements (C#8), made some necessary changes and added some features I will point out after the snippet:

using System;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
using System.Linq;
using System.Threading;
using Aspose.Words;
using Aspose.Words.Saving;

namespace De.Gedat.Foundation.WordAsposeBackend.UtilsFromAspose
{
    public class DocumentPrinter
    {
        public void Print(
            Document doc,
            string printerName,
            CancellationToken ct,
            string printJobName)
        {
            // Variables used in the local method OnPrintPage:
            using var pd = new PrintDocument();
            var pageIndex = 0;
            Exception exceptionCaughtWhilePrintingPage = null;

            pd.PrintPage += OnPrintPage;
            pd.QueryPageSettings += OnQueryPageSettings;
            pd.PrinterSettings.PrinterName = printerName;
            pd.DocumentName = printJobName;
            pd.Print();
            if (exceptionCaughtWhilePrintingPage != null)
                throw exceptionCaughtWhilePrintingPage;

            void OnPrintPage(object sender, PrintPageEventArgs e)
            {
                try
                {
                    using var pageImageStream = new MemoryStream();
                    var options = new ImageSaveOptions(
                        // vector EMF (Enhanced Meta File) file
                        SaveFormat.Emf)
                    {
                        // Without specifying Resolution it gets grainy,
                        // with Resolution=300 too,
                        // with Resolution=60 fully scalable. ???
                        Resolution = 60
                    };

                    var pageRange = new PageRange(pageIndex, pageIndex); // Specify Range
                    options.PageSet = new PageSet(pageRange);

                    doc.Save(pageImageStream, options);
                    pageImageStream.Position = 0;

                    using (Image pageImage = Image.FromStream(pageImageStream))
                        e.Graphics.DrawImage(pageImage, new Point(0, 0));

                    // Increase page index and check if there are more pages.
                    pageIndex++;
                    e.HasMorePages = pageIndex < doc.PageCount;
                    e.Cancel = ct.IsCancellationRequested;
                }
                catch (Exception ex)
                {
                    exceptionCaughtWhilePrintingPage = ex;
                    // Stop printing in case of errors.
                    e.HasMorePages = false;
                }
            }

            void OnQueryPageSettings(object sender, QueryPageSettingsEventArgs e)
            {
                // Get paper tray specified in the document.
                var pageInfo = doc.GetPageInfo(pageIndex);
                e.PageSettings.PaperSource =
                    GetSpecifiedPrinterPaperSource(pageInfo.PaperTray);
            }

            PaperSource GetSpecifiedPrinterPaperSource(int paperTray)
            {
                PaperSource foundSpecifiedPaperSource =
                    pd.PrinterSettings.PaperSources
                        .Cast<PaperSource>()
                        .FirstOrDefault(
                            paperSource =>
                                paperSource.RawKind == paperTray);
                return foundSpecifiedPaperSource ??
                    // If nothing is found, return the default value
                    pd.PrinterSettings.DefaultPageSettings.PaperSource;
            }
        }
    }
}
  • Changing PageSettings.PaperSource within the PrintPage handler is too late, it has to be done in the QueryPageSettings handler.
  • A “using” for the PrintDocument instance pd was missing.
  • I added cancellation and print job name support.
  • An exception within the PrintPage handler is stored and thrown after printing.
  • Note the code part with the ImageSaveOptions. When I printed to a PDF printer (Cute PDF), the print was grainy when I specified no special ImageSaveOptions as well as when a higher resolution (300) was specified. In contrast to that, explicitly specifying a low resolution (60) led to a real scalable vector output. This is very peculiar.

Thank you again with best regards and wishes to Kharkiv!

@GEDAT It is perfect that you managed to achieve what you need. And thank you for sharing your solution and explanations. This will be definitely useful for other customers.
I will consult regarding the last point regarding image resolution with the responsible developer and let you know our findings.

1 Like

Additional question: Do you have an idea on how to detect empty pages (somewhere within a multipaged document)? Word would not print those, but with our code here, empty pages come out of the printer. So the question for us here would be: How to detect an Image class instance which has nothing on it? Thanks in advance!

@GEDAT You can use the approach suggested here:
https://stackoverflow.com/questions/12684885/checking-to-see-if-an-image-is-blank-in-c-sharp
Also, you can try checking for empty pages using Aspose.Words and Document.ExtractPages method:

Document doc = new Document(@"C:\Temp\in.docx");

TxtSaveOptions options = new TxtSaveOptions();
options.ExportHeadersFootersMode = TxtExportHeadersFootersMode.AllAtEnd;
for (int i = 0; i < doc.PageCount; i++)
{
    Document page = doc.ExtractPages(i, 1);
    // get pages text.
    string pageContent = page.ToString(options).Trim();
    if (string.IsNullOrEmpty(pageContent))
        Console.WriteLine("Page {0} is empty", i);
}
1 Like

Just a remark: “Word would not print empty pages” is not correct, our customers made us think so, but it is not true after all. Anyway, not printing empty pages comes in handy at times.
(I am a colleague of @GEDAT.)

For some documents we need to transfer more page settings to the printer, for example landscape/portrait orientation:

void OnQueryPageSettings(object sender, QueryPageSettingsEventArgs e)
{
    // Get paper tray etc. specified in the document.
    var pageInfo = doc.GetPageInfo(pageIndex);
    e.PageSettings.PaperSource = GetSpecifiedPrinterPaperSource(pageInfo.PaperTray);
    e.PageSettings.Landscape = pageInfo.Landscape;
    e.PageSettings.PaperSize = new System.Drawing.Printing.PaperSize
    {
        Height = PointsToHundredthsOfInches(pageInfo.HeightInPoints),
        Width = PointsToHundredthsOfInches(pageInfo.WidthInPoints)
    };
}

static int PointsToHundredthsOfInches(float pt)
    => (int)(pt / 72.0 * 100.0); // 72pt = 1"

Compared to our first version with just the paper source, we now have added orientation and paper size here. Orientation works fine, but the paper size does not seem to work correctly; the printout of a document with about credit card page size to a real printer now shows a crop by about 1mm. Is there anything wrong in my approach? And what about additional margin settings? Has there anything to be transferred from the document to the printer API, such as e.PageSettings.Margins?

@JSpot Please try using the following method for getting .NET PaperSize:

public System.Drawing.Printing.PaperSize GetDotNetPaperSize(PageInfo info, PrintDocument pd)
{
    PaperKind paperKind = PaperKinds[info.PaperSize];
    if (paperKind != PaperKind.Custom)
    {
        foreach (System.Drawing.Printing.PaperSize paperSize in pd.PrinterSettings.PaperSizes)
        {
            if (paperSize.Kind == paperKind)
                return paperSize;
        }
    }

    return new System.Drawing.Printing.PaperSize("Custom", PointsToHundredthsInch(info.WidthInPoints), PointsToHundredthsInch(info.HeightInPoints));
}

private static int PointsToHundredthsInch(double points)
{
    return (int)(ConvertUtil.PointToInch(points) * 100);
}
       
private Dictionary<Aspose.Words.PaperSize, PaperKind> PaperKinds
{
    get { 
        if (mPaperKinds == null)
        {
            mPaperKinds = new Dictionary<Aspose.Words.PaperSize, PaperKind>();

            mPaperKinds.Add(Aspose.Words.PaperSize.A3,PaperKind.A3);
            mPaperKinds.Add(Aspose.Words.PaperSize.A4,PaperKind.A4);
            mPaperKinds.Add(Aspose.Words.PaperSize.A5,PaperKind.A5);
            mPaperKinds.Add(Aspose.Words.PaperSize.B4,PaperKind.B4);
            mPaperKinds.Add(Aspose.Words.PaperSize.B5,PaperKind.B5);
            mPaperKinds.Add(Aspose.Words.PaperSize.Custom,PaperKind.Custom);
            mPaperKinds.Add(Aspose.Words.PaperSize.EnvelopeDL,PaperKind.DLEnvelope);
            mPaperKinds.Add(Aspose.Words.PaperSize.Executive,PaperKind.Executive);
            mPaperKinds.Add(Aspose.Words.PaperSize.Folio,PaperKind.Folio);
            mPaperKinds.Add(Aspose.Words.PaperSize.Ledger,PaperKind.Ledger);
            mPaperKinds.Add(Aspose.Words.PaperSize.Legal,PaperKind.Legal);
            mPaperKinds.Add(Aspose.Words.PaperSize.Letter,PaperKind.Letter);
            mPaperKinds.Add(Aspose.Words.PaperSize.Paper10x14,PaperKind.Standard10x14);
            mPaperKinds.Add(Aspose.Words.PaperSize.Paper11x17,PaperKind.Standard11x17);
            mPaperKinds.Add(Aspose.Words.PaperSize.Quarto,PaperKind.Quarto);
            mPaperKinds.Add(Aspose.Words.PaperSize.Statement,PaperKind.Statement);
            mPaperKinds.Add(Aspose.Words.PaperSize.Tabloid,PaperKind.Tabloid);
            mPaperKinds.Add(Aspose.Words.PaperSize.Number10Envelope,PaperKind.Number10Envelope);
        }

        return mPaperKinds;
    }
}

private Dictionary<Aspose.Words.PaperSize, PaperKind> mPaperKinds;

Thank you for the code snippet and again for the quick reply! That helped in regard to the translation of the standard paper sizes, that is good. However, in our case with the custom paper size, it changed nothing: the print is cropped a bit. But as it turns out, Word crops here a bit as well, but less, so it was not discovered. I think I will address the fixed paper sizes if supported by the printer, as you suggested, but in case of custom paper sizes, I will stick to the default paper size of the printer (PrintDocument.PrinterSettings.DefaultPageSettings.PaperSize); so no cropping in the print output happens.

@JSpot Please feel free to ask in case of any issues. We are always glad to help you within our capabilities.