Merge several documents to single PDF document in .NET

Blog category: PDF

November 25, 2020

This article contains C# code that allows to merge several input documents (PDF, TIFF, PNG, DOCX, ...) to single PDF document. Each input image is added as image-only PDF page, each document (PDF, DOCX) page is converted to PDF page. Created PDF document contains bookmark for each created PDF page. Also the PDF document includes a content page with clickable bookmarks for all PDF pages.

Here is C# code that allows to merge several input documents (PDF, TIFF, PNG, DOCX, ...) to single PDF document:
/// <summary>
/// Merges several documents (PDF, TIFF, PNG, DOCX, ...) to single PDF document.
/// </summary>
/// <param name="outputFilename">The output PDF filename.</param>
/// <param name="inputFilenames">The filenames of input documents.</param>
public static void MergeDocuments(string outputFilename, params string[] inputFilenames)
{
    if (inputFilenames == null || inputFilenames.Length < 1)
        throw new System.ArgumentOutOfRangeException("inputFilenames");

    // define style of index page
    string indexPageFontName = "Arial";
    float indexPageFontSize = 14;
    System.Drawing.PointF indexPagePadding = new System.Drawing.PointF(indexPageFontSize * 2, indexPageFontSize * 4);
    float indexPageLineSpacing = indexPageFontSize * 0.8f;
    System.Drawing.Color linkFontColor = System.Drawing.Color.Blue;
    System.Drawing.Color titleFontColor = System.Drawing.Color.Black;

    // create output PDF document
    using (Vintasoft.Imaging.Pdf.PdfDocument document = 
        new Vintasoft.Imaging.Pdf.PdfDocument(outputFilename, Vintasoft.Imaging.Pdf.PdfFormat.Pdf_17))
    {
        // create bookmark collection
        document.Bookmarks = new Vintasoft.Imaging.Pdf.Tree.PdfBookmarkCollection(document);

        // create index page
        Vintasoft.Imaging.Pdf.Tree.PdfPage indexPage =
            new Vintasoft.Imaging.Pdf.Tree.PdfPage(document, Vintasoft.Imaging.PaperSizeKind.A4);
        // add index page to PDF document
        document.Pages.Add(indexPage);
        // create annotation collection for index page
        indexPage.Annotations = new Vintasoft.Imaging.Pdf.Tree.Annotations.PdfAnnotationList(document);

        // create font and text brush
        Vintasoft.Imaging.Pdf.Tree.Fonts.PdfFont font;
        using (System.IO.Stream fontProgramStream = document.FontProgramsController.GetTrueTypeFontProgram(indexPageFontName))
            font = document.FontManager.CreateCIDFontFromTrueTypeFont(fontProgramStream);
        Vintasoft.Imaging.Pdf.Drawing.PdfBrush fontBrush = 
            new Vintasoft.Imaging.Pdf.Drawing.PdfBrush(linkFontColor);

        // open PdfGraphics on index page
        using (Vintasoft.Imaging.Pdf.Drawing.PdfGraphics indexGraphics = indexPage.GetGraphics())
        {
            // start location in index page
            System.Drawing.PointF indexPageTextLocation = indexPage.MediaBox.Location;
            indexPageTextLocation.X += indexPagePadding.X;
            indexPageTextLocation.Y += indexPage.MediaBox.Height - indexPagePadding.Y - indexPageFontSize * 2;

            // draw index title
            System.Drawing.RectangleF titleRect = new System.Drawing.RectangleF(
                indexPageTextLocation.X,
                indexPageTextLocation.Y,
                indexPage.MediaBox.Width - indexPageTextLocation.X * 2,
                indexPageFontSize * 2 * 2);
            indexGraphics.DrawString(
                "Example: Create PDF document from several documents.",
                font, indexPageFontSize * 2, new Vintasoft.Imaging.Pdf.Drawing.PdfBrush(titleFontColor), 
                titleRect, Vintasoft.Imaging.Pdf.Drawing.PdfContentAlignment.Center, true);

            indexPageTextLocation.Y -= indexPageLineSpacing * 4;

            // location of page number
            float indexPageNumberLocationX = indexPage.MediaBox.X + indexPage.MediaBox.Width - indexPagePadding.X;

            // for each input file
            foreach (string inputFilename in inputFilenames)
            {
                int firstPageIndex = document.Pages.Count;

                // create bookmark
                Vintasoft.Imaging.Pdf.Tree.PdfBookmark fileBookmark = 
                    new Vintasoft.Imaging.Pdf.Tree.PdfBookmark(document, System.IO.Path.GetFileName(inputFilename));
                fileBookmark.Destination = new Vintasoft.Imaging.Pdf.Tree.PdfDestinationFit(document, firstPageIndex);
                document.Bookmarks.Add(fileBookmark);

                // if input image file is PDF document
                if (System.IO.Path.GetExtension(inputFilename).ToUpper() == ".PDF")
                {
                    // open input PDF document
                    using (Vintasoft.Imaging.Pdf.PdfDocument inputDoc = new Vintasoft.Imaging.Pdf.PdfDocument(inputFilename))
                    {
                        // add pages from input PDF document to the output document, also add bookmark tree from input PDF document to the 'fileBookmark'
                        document.AddDocument(inputDoc, fileBookmark.ChildBookmarks);
                    }
                }
                else
                {
                    // add images from input image file to the output document
                    Vintasoft.Imaging.ImageCollection images = new Vintasoft.Imaging.ImageCollection();
                    images.Add(inputFilename);
                    // if input file is vector document (DOCX, ...)
                    if (images[0].IsVectorImage)
                    {
                        // create temp stream
                        using (System.IO.MemoryStream tempStream = new System.IO.MemoryStream())
                        {
                            // create PDF encoder
                            using (Vintasoft.Imaging.Codecs.Encoders.PdfEncoder encoder = new Vintasoft.Imaging.Codecs.Encoders.PdfEncoder())
                            {
                                // convert input file to temporary PDF document
                                images.SaveSync(tempStream, encoder);
                                // add pages from temp PDF document to the output document
                                using (Vintasoft.Imaging.Pdf.PdfDocument tempDoc = new Vintasoft.Imaging.Pdf.PdfDocument(tempStream))
                                    document.AddDocument(tempDoc, fileBookmark.ChildBookmarks);
                            }
                        }
                    }
                    else
                    {
                        // encode input file as image-only PDF pages
                        foreach (Vintasoft.Imaging.VintasoftImage image in images)
                            AddImageToPdfDocument(document, image);
                    }
                    images.ClearAndDisposeItems();
                }

                // if 'fileBookmark' does not have children bookmarks AND added document is multipage document, i.e.
                // input document is PDF document without bookmarks OR input document is NOT PDF document
                if (fileBookmark.ChildBookmarks.Count == 0 && (document.Pages.Count - firstPageIndex) > 1)
                {
                    // for each page in input document
                    for (int i = firstPageIndex; i < document.Pages.Count; i++)
                    {
                        // create bookmark for page
                        Vintasoft.Imaging.Pdf.Tree.PdfBookmark pageBookmark =
                            new Vintasoft.Imaging.Pdf.Tree.PdfBookmark(document, string.Format("Page {0}", i - firstPageIndex + 1));
                        // set destination for page bookmark
                        pageBookmark.Destination = new Vintasoft.Imaging.Pdf.Tree.PdfDestinationFit(document, i);
                        // add page bookmark to the file bookmark
                        fileBookmark.ChildBookmarks.Add(pageBookmark);
                    }
                }

                // draw link text on index page
                System.Drawing.RectangleF indexRect = DrawIndexString(
                    indexGraphics, System.IO.Path.GetFileName(inputFilename), font, indexPageFontSize, 
                    fontBrush, indexPageTextLocation, firstPageIndex + 1, indexPageNumberLocationX);

                // add link annotation on link text
                Vintasoft.Imaging.Pdf.Tree.Annotations.PdfLinkAnnotation linkAnnotation =
                    new Vintasoft.Imaging.Pdf.Tree.Annotations.PdfLinkAnnotation(document.Pages[firstPageIndex], fileBookmark.Destination);
                linkAnnotation.Rectangle = indexRect;
                linkAnnotation.HighlightingMode = Vintasoft.Imaging.Pdf.Tree.Annotations.PdfAnnotationHighlightingMode.Invert;
                indexPage.Annotations.Add(linkAnnotation);

                // move to next link
                indexPageTextLocation.Y -= indexRect.Height + indexPageLineSpacing;
            }
        }
        
        // subset font that is used for text drawing
        document.FontManager.PackFont(font);

        // save chages in the output document
        document.SaveChanges();
    }
}

/// <summary>
/// Draws the index string.
/// </summary>
/// <param name="graphics">The graphics.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="fontSize">Size of the font.</param>
/// <param name="brush">The brush.</param>
/// <param name="textLocation">The text location.</param>
/// <param name="pageNumber">The page number.</param>
/// <param name="pageNumberLocationX">The page number location at X-axis.</param>
/// <returns>Rectangle, where text was drawn.</returns>
private static System.Drawing.RectangleF DrawIndexString(
    Vintasoft.Imaging.Pdf.Drawing.PdfGraphics graphics, 
    string text,
    Vintasoft.Imaging.Pdf.Tree.Fonts.PdfFont font, 
    float fontSize,
    Vintasoft.Imaging.Pdf.Drawing.PdfBrush brush,
    System.Drawing.PointF textLocation, 
    int pageNumber, 
    float pageNumberLocationX)
{
    // draw text
    float width, height;
    graphics.DrawString(text, font, fontSize, brush, textLocation);
    graphics.MeasureString(text, font, fontSize, out width, out height);
    
    // draw dots
    float lineWidth = 1;
    System.Drawing.PointF pageNumberLocation = new System.Drawing.PointF(pageNumberLocationX, textLocation.Y);
    using (Vintasoft.Imaging.Pdf.Drawing.PdfPen pen = new Vintasoft.Imaging.Pdf.Drawing.PdfPen(brush.Color, lineWidth))
    {
        pen.DashPattern = new float[] { lineWidth, lineWidth * 2 };
        graphics.DrawLine(pen, new System.Drawing.PointF(textLocation.X + width, textLocation.Y), pageNumberLocation);
    }
    
    // draw page number
    graphics.DrawString(pageNumber.ToString(), font, fontSize, brush, pageNumberLocation);
    graphics.MeasureString(pageNumber.ToString(), font, fontSize, out width, out height);

    return new System.Drawing.RectangleF(textLocation.X, textLocation.Y, pageNumberLocation.X - textLocation.X + width, height);
}

/// <summary>
/// Adds the image to PDF document.
/// </summary>
/// <param name="document">The document.</param>
/// <param name="image">The image.</param>
private static void AddImageToPdfDocument(
    Vintasoft.Imaging.Pdf.PdfDocument document, 
    Vintasoft.Imaging.VintasoftImage image)
{
    // select compression
    Vintasoft.Imaging.Pdf.PdfCompression compression;
    Vintasoft.Imaging.Pdf.PdfCompressionSettings compressionSettings;
    if (image.BitsPerPixel == 1)
        compression = Vintasoft.Imaging.Pdf.PdfCompression.CcittFax;
    else if (image.PixelFormat == Vintasoft.Imaging.PixelFormat.Indexed8)
        compression = Vintasoft.Imaging.Pdf.PdfCompression.Zip;
    else
        compression = Vintasoft.Imaging.Pdf.PdfCompression.Jpeg;
    
    // create compression settings
    compressionSettings = new Vintasoft.Imaging.Pdf.PdfCompressionSettings();
    compressionSettings.JpegQuality = 75;
    
    // add page
    document.Pages.Add(image, compression, compressionSettings);
}

Share this article