TX Text Control .NET for Windows Forms

Attachments

Topic

A PDF file can carry other files: the XML of an electronic invoice next to its printable pages, the JSON data a letter was merged from or a spreadsheet with the details of a contract. In a Windows Forms application, such attachments belong to the document in the TextControl. Your code adds them before it saves the document as PDF, users manage them in a sidebar panel, and the attachments of a PDF document that the editor loads are kept when it is saved again. Read on for the classes involved, the relationship and MIME type PDF/A-3 requires, electronic invoices and extracting attachments from PDF files you receive.

Saving the editor's document as PDF and PDF/A is introduced in Create a PDF in Getting Started.

How Attachments Work

An attachment is an EmbeddedFile object: a file name, the data of the file and optional properties. Embedded files are supported for PDF and PDF/A only: they are written when the document is saved with StreamType.AdobePDF or StreamType.AdobePDFA, and other formats leave them out.

PropertyContains
FileNameThe name of the file, which can include a relative or absolute path; read only
DataThe content of the file, a string or a byte array; read only
MIMETypeThe MIME type, such as application/json; without one, application/octet-stream is used
RelationshipHow the file relates to the document; written to PDF/A only
DescriptionA description of the file
CreationDate, LastModificationDateThe dates of the file; when they are null, the dates of the PDF document are used

The EmbeddedFile constructor takes the file name, the data as a byte array or a string, and XMP metadata, which can be null. The metadata consists of rdf:Description elements that are added to the metadata of the PDF document, as electronic invoices require. A third overload takes a SafeFileHandle and reads the file when the document is saved.

Three properties hold attachments:

Choose the Relationship and MIME Type

PDF/A-3 requires a MIME type and a relationship for every embedded file. Relationship is a string, and these values are predefined:

RelationshipUse it for
SourceThe original document the PDF was created from, such as a DOCX file
DataMachine-readable data, such as XML, JSON or CSV
AlternativeAnother representation of the document's content, such as the XML of a ZUGFeRD or Factur-X invoice
SupplementAdditional information, such as appendices
UnspecifiedAnything else

Common MIME types are application/xml, application/json, text/csv, image/png and image/jpeg. The blog article Why Defining MIME Types for PDF/A Attachments Is Essential explains why validators reject PDF/A documents without them.

Attach a File When Saving

The following handler in Form1.cs attaches the JSON data of an invoice to the document in textControl1 and saves it as PDF/A. It needs using System;, using System.IO; and using TXTextControl; at the top of the file, and data.json in the folder of the application:

private void attachDataButton_Click(object sender, EventArgs e)
{
    // PDF/A requires fonts that can be embedded
    textControl1.FontSettings.EmbeddableFontsOnly = true;

    EmbeddedFile data = new EmbeddedFile("data.json", File.ReadAllBytes("data.json"), null)
    {
        Description = "Invoice data",
        MIMEType = "application/json",
        Relationship = "Data",
        CreationDate = DateTime.Now
    };

    SaveSettings saveSettings = new SaveSettings()
    {
        EmbeddedFiles = new EmbeddedFile[] { data }
    };

    textControl1.Save("invoice.pdf", StreamType.AdobePDFA, saveSettings);
}

The code works as follows:

  • FontSettings.EmbeddableFontsOnly restricts the document to fonts that can be embedded, which PDF/A requires. PDF/A explains the rules of the format.
  • EmbeddedFiles takes an array, so one save embeds any number of files.
  • Because the array is passed in SaveSettings, the attachment goes into this file only. To make it part of the document, so that every later save embeds it, assign the array to textControl1.DocumentSettings.EmbeddedFiles instead.

PDF readers list the attachments of a document in a panel of their own. Adobe Acrobat shows them with their names and descriptions:

Adobe Acrobat showing a contract PDF with the Attachments panel open, which lists agreement.docx, data.json, data.xlsx and thumbnail.jpg.

Let Users Manage Attachments in the Sidebar

The Sidebar has a DocumentSettings panel for the properties of the document. Its Embedded Files in PDF category lists the attachments of DocumentSettings.EmbeddedFiles. With its buttons, users add a file (Add...), write an attachment to a file (Save...), delete it (Remove) and change its description, relationship and MIME type (Edit...). When the user adds a file, the panel sets a MIME type that matches the file name extension.

The Document Settings sidebar of TX Words beside the editor, with the Embedded Files in PDF category listing two files and the Edit Embedded File dialog box offering the relationships Source, Alternative, Data, Supplement and Unspecified.

To show the panel, set the ContentLayout of a sidebar connected to textControl1 to DocumentSettings, in its smart tag or in code. The following handler opens it in a sidebar named sidebarLeft. It needs using TXTextControl.Windows.Forms;:

private void documentSettingsButton_Click(object sender, EventArgs e)
{
    sidebarLeft.ContentLayout = Sidebar.SidebarContentLayout.DocumentSettings;
    sidebarLeft.IsShown = true;
}

The panel also edits the title, author, subject, keywords and custom properties of the document, as Document Metadata describes. If your application doesn't offer attachments, hide their category with FindItem and a value of DocumentSettingsItem:

// Hide the Embedded Files in PDF category of the panel
Control embeddedFiles = sidebarLeft.FindItem(Sidebar.DocumentSettingsItem.TXITEM_EmbeddedFilesPanel);

if (embeddedFiles != null)
{
    embeddedFiles.Visible = false;
}

How a sidebar is added in the designer and connected to the ribbon is shown in Sidebar and Panels.

Create an Electronic Invoice

ZUGFeRD and its French counterpart Factur-X are hybrid invoices: a PDF/A-3 document for people to read, with the invoice as XML attached for software to process. Since ZUGFeRD 2.1, XRechnung XML can be embedded in a PDF the same way. TX Text Control creates the readable invoice and embeds the XML; the XML itself comes from your ERP system or an e-invoice library such as ZUGFeRD-csharp.

The following handler embeds factur-x.xml in the invoice shown in the editor, together with the XMP metadata in metadata.xml that declares the standard, and saves a PDF/A-3 document:

private void createEInvoiceButton_Click(object sender, EventArgs e)
{
    // PDF/A requires fonts that can be embedded
    textControl1.FontSettings.EmbeddableFontsOnly = true;

    // The XML invoice, created by your ERP system or an e-invoice library
    byte[] invoiceXml = File.ReadAllBytes("factur-x.xml");

    // rdf:Description elements that declare the invoice standard
    string metadata = File.ReadAllText("metadata.xml");

    EmbeddedFile invoice = new EmbeddedFile("factur-x.xml", invoiceXml, metadata)
    {
        Description = "factur-x",
        MIMEType = "application/xml",
        Relationship = "Alternative",
        LastModificationDate = DateTime.Now
    };

    SaveSettings saveSettings = new SaveSettings()
    {
        EmbeddedFiles = new EmbeddedFile[] { invoice }
    };

    textControl1.Save("e-invoice.pdf", StreamType.AdobePDFA, saveSettings);
}

metadata.xml contains the XMP extension schema of ZUGFeRD with the conformance level, the document type, the version and the name of the embedded file. The blog article Creating ZUGFeRD 2.3 (XRechnung, Factur-X) Documents with .NET C# shows its complete content and creates the XML with ZUGFeRD-csharp. Check the result with a validation service for ZUGFeRD and Factur-X, and sign the PDF to make later changes to the invoice detectable, as Digital Signatures describes.

Extract Attachments From a PDF

To read the attachments of a PDF without loading it into the editor, use the static class Attachments of the TXTextControl.DocumentServer.PDF namespace. That namespace has an EmbeddedFile class of its own, which clashes with TXTextControl.EmbeddedFile in a file that imports both namespaces. An alias avoids the clash: add using Pdf = TXTextControl.DocumentServer.PDF; at the top of Form1.cs.

The following handler lets the user choose a PDF file and a folder and writes every attachment of the file to the folder:

private void extractAttachmentsButton_Click(object sender, EventArgs e)
{
    using (OpenFileDialog openDialog = new OpenFileDialog() { Filter = "PDF documents (*.pdf)|*.pdf" })
    using (FolderBrowserDialog folderDialog = new FolderBrowserDialog())
    {
        if (openDialog.ShowDialog(this) != DialogResult.OK ||
            folderDialog.ShowDialog(this) != DialogResult.OK)
        {
            return;
        }

        Pdf.EmbeddedFile[] files = Pdf.Attachments.GetFromFile(openDialog.FileName)
            ?? Array.Empty<Pdf.EmbeddedFile>();

        foreach (Pdf.EmbeddedFile file in files)
        {
            // The file name can contain a path
            string path = Path.Combine(folderDialog.SelectedPath, Path.GetFileName(file.FileName));

            if (file.HasBinaryData)
            {
                File.WriteAllBytes(path, file.BinaryData);
            }
            else if (file.HasStringData)
            {
                File.WriteAllText(path, file.StringData);
            }
        }

        MessageBox.Show($"{files.Length} attachments saved to {folderDialog.SelectedPath}");
    }
}

The code works as follows:

To pick the XML of an e-invoice, look for the attachment whose Relationship is Alternative and whose MimeType ends with xml: older invoices use text/xml, newer ones application/xml.

Keep, Add and Replace Attachments

When textControl1 loads a PDF, its attachments go into DocumentSettings.EmbeddedFiles, and a save as PDF writes them again, so a PDF that the user opens, changes and saves keeps its attachments. SaveSettings.EmbeddedFiles replaces them for one save: the array you pass is what the file contains. To add a file and keep the existing ones, extend the array of DocumentSettings; the handler needs using System.Collections.Generic;:

private void addAttachmentButton_Click(object sender, EventArgs e)
{
    // The attachments of the loaded PDF, or none
    List<EmbeddedFile> files = new List<EmbeddedFile>(
        textControl1.DocumentSettings.EmbeddedFiles ?? Array.Empty<EmbeddedFile>());

    files.Add(new EmbeddedFile("line-items.csv", File.ReadAllBytes("line-items.csv"), null)
    {
        Description = "Line items",
        MIMEType = "text/csv"
    });

    // Saved with the document from now on
    textControl1.DocumentSettings.EmbeddedFiles = files.ToArray();

    textControl1.Save("invoice-with-line-items.pdf", StreamType.AdobePDF);
}

The attachments of a PDF are loaded only when LoadEmbeddedFiles is part of LoadSettings.PDFImportSettings. A value you set yourself, such as PDFImportSettings.GenerateLines for the text only, has to include it, as Loading Documents describes for the other settings. The saved PDF is a new document built from the content of the editor, with the same attachments.

Next Steps