Table of Contents

Tutorial: Assemble a board pack

Turn four Word papers and a cover sheet into one board pack. Convert each paper to PDF, merge everything in agenda order, pull out a section for a committee, split and reorder pages, and render a thumbnail of every page. Pages move between PDFs whole: nothing is rasterized or re-encoded.

DocWright 15 minutes

You will learn to:

  • convert several documents with one converter
  • merge, extract, split and reorder pages of existing PDFs with PdfPages
  • render the pages of any PDF, including ones DocWright did not create, to PNG

Everything here is in the main DocWright package. The page tools live in the DocWright.Formats.Pdf namespace.

Step 1: Convert the papers

The papers are ordinary Word documents: a letter, a three-page report, a paper with charts, and a table. The cover sheet is already a PDF.

using DocWright;
using DocWright.Core.Primitives;
using DocWright.Formats.Pdf;
using DocWright.Renderers.Imaging;

// 1. Convert each Word paper to PDF. One converter for all of them.
var converter = new DocWrightConverter();
string[] papers = ["letterhead.docx", "quarterly-report.docx", "charts.docx", "table-styles.docx"];

foreach (string paper in papers)
{
    using FileStream input = File.OpenRead(paper);
    using FileStream output = File.Create(Path.ChangeExtension(paper, ".pdf"));
    converter.Convert(input, output);
}

Step 2: Merge, extract, split and reorder

using DocWright;
using DocWright.Core.Primitives;
using DocWright.Formats.Pdf;
using DocWright.Renderers.Imaging;

// 2. Merge: the cover first, then every paper in agenda order.
PdfAssemblyResult pack = PdfPages.Merge(
    ["cover.pdf", "letterhead.pdf", "quarterly-report.pdf", "charts.pdf", "table-styles.pdf"],
    "board-pack.pdf");
Console.WriteLine($"board-pack.pdf: {pack.PageCount} pages from {pack.SourceCount} files");

// 3. Pull out the financial report (pages 3-5) for the audit committee.
PdfPages.Extract("board-pack.pdf", PageRange.Parse("3-5"), "audit-committee.pdf");

// 4. Split the pack in two: the cover and letter, and the papers.
PdfPages.Split(
    "board-pack.pdf",
    [PageRange.Parse("1-2"), PageRange.Parse("3-")],
    ["pack-part-1.pdf", "pack-part-2.pdf"]);

// 5. Put the two chart pages first, keeping every other page in order.
//    A reorder must name every page exactly once.
int pages = pack.PageCount;
int[] order = [6, 7, .. Enumerable.Range(1, pages).Where(p => p is not (6 or 7))];
PdfPages.Reorder("board-pack.pdf", order, "board-pack-charts-first.pdf");

Output

board-pack.pdf: 8 pages from 5 files
custom-pack.pdf: 5 pages
  outline entries removed: 0, link targets removed: 0, form dropped: False, other entries: [Info]
Method What it produces
PdfPages.Merge(sources, output) All pages of every source, in the order given.
PdfPages.Extract(source, pages, output) Only the pages you name.
PdfPages.Delete(source, pages, output) Every page except the ones you name.
PdfPages.Split(source, parts, outputs) One file per PageRange part.
PdfPages.Reorder(source, order, output) Every page in a new order. The order must name each page exactly once.

Delete, Extract and Reorder also have …InPlace forms that replace the source file safely: the new file is written beside it and swapped in only when the write succeeded. Every method has overloads that take byte[] and Stream instead of paths.

Warning

A PageRange is a set, not a sequence. "5,1" and "1,5" select the same pages, in ascending order. When order matters, use Reorder, or PdfPageAssembly.AppendPage (below).

Step 3: Render page thumbnails

ConvertPdfToImages renders any PDF, not only one DocWright wrote. It takes the same ImageRenderOptions as Word documents:

using DocWright;
using DocWright.Core.Primitives;
using DocWright.Formats.Pdf;
using DocWright.Renderers.Imaging;

// 6. Render a thumbnail of every page of the finished pack.
var thumbnails = new ImageRenderOptions
{
    Width = 160,
    PageEncoded = (page, png) => File.WriteAllBytes($"pack-page-{page}.png", png),
};

using (FileStream packFile = File.OpenRead("board-pack.pdf"))
{
    converter.ConvertPdfToImages(packFile, thumbnails);
}
board-pack.pdf · 8 pages, 160 px thumbnails
Page 1: cover1 · Cover
Page 2: letter2 · Letter
Page 3: report page 13 · Report
Page 4: report page 24 · Report
Page 5: report page 35 · Report
Page 6: charts6 · Charts
Page 7: charts7 · Charts
Page 8: table8 · Table
The merged pack, as rendered by ConvertPdfToImages. Download: board-pack.pdf.

Beyond the five methods: PdfPageAssembly

PdfPages is a convenience layer over PdfPageAssembly. Use the engine directly to interleave pages from several sources, repeat a page, or open password-protected sources:

using DocWright;
using DocWright.Core.Primitives;
using DocWright.Formats.Pdf;
using DocWright.Renderers.Imaging;

using (var assembly = new PdfPageAssembly())
{
    int cover = assembly.AddSource("cover.pdf");
    int report = assembly.AddSource("quarterly-report.pdf");   // or (path, password: "…")

    assembly
        .AppendAll(cover)                                   // every page of the cover
        .AppendRange(report, PageRange.Parse("2-"))         // the report from page 2 on
        .AppendPage(report, 1)                              // then its first page, moved to the end
        .AppendPage(cover, 1);                              // and the cover again as a back page

    PdfAssemblyResult result = assembly.Save("custom-pack.pdf");
    Console.WriteLine($"custom-pack.pdf: {result.PageCount} pages");
    if (result.HasLosses)
    {
        Console.WriteLine($"  outline entries removed: {result.RemovedOutlineEntryCount}, " +
                          $"link targets removed: {result.RemovedDestinationCount}, " +
                          $"form dropped: {result.FormDropped}, " +
                          $"other entries: [{string.Join(", ", result.DroppedDocumentEntries)}]");
    }
}

A repeated page shares one copy of its content, so duplicating a page costs almost nothing. PdfPageAssembly holds its sources open: dispose it, and use one instance per thread.

What you are told about

The custom pack above reports one loss, visible in the output: Info.

PdfAssemblyResult reports what the operation had to change. Check HasLosses before relying on the output:

Property Meaning
PageCount, SourceCount, BytesWritten What was written.
RemovedOutlineEntryCount, RemovedDestinationCount Bookmarks and link targets that pointed at pages left out. Links between pages that were kept are repaired.
FormDropped An interactive form was not carried over.
EncryptionRemoved A source was encrypted. The output is not encrypted.
DroppedDocumentEntries Document-level entries that could not be carried. The most common one is Info, the title and author metadata. When more than one source has it there is no right answer, so none is kept, and the output has no title.
HasLosses Any of the above happened.
Note

Don't confuse these with WordDocument.Merge and WordDocument.Split. Those combine and divide Word documents in the editable model before rendering. PdfPages works on PDF files that already exist.

Next steps