Attachments
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.
| Property | Contains |
|---|---|
| FileName | The name of the file, which can include a relative or absolute path; read only |
| Data | The content of the file, a string or a byte array; read only |
| MIMEType | The MIME type, such as application/json; without one, application/octet-stream is used |
| Relationship | How the file relates to the document; written to PDF/A only |
| Description | A description of the file |
| CreationDate, LastModificationDate | The 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:
- TextControl.DocumentSettings
.EmbeddedFiles are the attachments of the document in the editor. A loaded PDF fills them, the sidebar panel shows and changes them, and every save as PDF embeds them. The value isnullwhen the document has none. - SaveSettings.EmbeddedFiles sets the files of one save and takes the place of those in
DocumentSettings. - LoadSettings.EmbeddedFiles returns the files of a loaded PDF. They are loaded when PDFImportSettings contains
LoadEmbeddedFiles, which is part of the default.
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:
| Relationship | Use it for |
|---|---|
Source | The original document the PDF was created from, such as a DOCX file |
Data | Machine-readable data, such as XML, JSON or CSV |
Alternative | Another representation of the document's content, such as the XML of a ZUGFeRD or Factur-X invoice |
Supplement | Additional information, such as appendices |
Unspecified | Anything 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.
EmbeddedFilestakes 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 totextControl1.DocumentSettings.EmbeddedFilesinstead.
PDF readers list the attachments of a document in a panel of their own. Adobe Acrobat shows them with their names and descriptions:

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.

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:
- GetFromFile takes the complete path of a PDF file, and GetFromData the PDF as a byte array. Both return an array of DocumentServer.PDF.EmbeddedFile objects.
- The properties of this class are read only, and its MIME type is named MimeType.
- HasBinaryData and HasStringData tell whether the content is in BinaryData or in StringData.
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
- PDF/A: create archivable PDF/A-3 documents.
- Document Metadata: write and read the title, author and keywords.
- Signing PDF Documents: protect a PDF and its attachments with a signature.
- Sidebar and Panels: add the sidebar that shows the DocumentSettings panel.