# Saving and Converting Documents

> Convert DOCX, RTF and HTML to PDF with ServerTextControl: batch conversion, Selection.Save, HTML with CSS, smaller PDFs, OmittedContent and ASP.NET Core.

- **Product:** TX Text Control .NET Server
- **Section:** Documentation
- **Kind:** Topic
- **Path:** TX Text Control .NET Server > Document Processing > Saving and Converting Documents
- **6246 words**
- **Web URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/saving-converting/
- **LLMs URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/saving-converting/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/saving-converting/llms-full.txt

---

Converting a DOCX file to PDF takes two lines of code: `Load` in one format and `Save` in another. The quality of the result depends on the details, such as which part of a document you save, how HTML stylesheets and images are written, how large a PDF becomes and which content you leave out. Those details are what you control with `ServerTextControl` and `SaveSettings`, including for batch conversions and web applications.

[Save as DOCX and PDF](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/document-processing/save-docx-pdf/llms-full.txt) covers the `Save` overloads, byte arrays and a controller that returns a file. PDF-specific options are described in [PDF Processing](https://docs.textcontrol.com/tx-text-control-dotnet-server/pdf/llms-full.txt), such as [document metadata](https://docs.textcontrol.com/tx-text-control-dotnet-server/pdf/metadata/llms-full.txt), [PDF/A](https://docs.textcontrol.com/tx-text-control-dotnet-server/pdf/pdf-a/llms-full.txt), passwords, PDF/UA and attachments, and signing in [Digital Signatures](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/llms-full.txt).

### Convert Many Documents

A conversion loads a document in its format and saves it in another. The following program converts every DOCX, RTF, HTML and TX file in the *Documents* folder to PDF and continues with the next file when one can't be converted:

```csharp
using TXTextControl;

// The load format for each file extension
Dictionary<string, StreamType> sourceFormats = new Dictionary<string, StreamType>(StringComparer.OrdinalIgnoreCase)
{
    { ".docx", StreamType.WordprocessingML },
    { ".rtf", StreamType.RichTextFormat },
    { ".html", StreamType.HTMLFormat },
    { ".tx", StreamType.InternalUnicodeFormat }
};

Directory.CreateDirectory("Output");

foreach (string file in Directory.GetFiles("Documents"))
{
    if (!sourceFormats.TryGetValue(Path.GetExtension(file), out StreamType sourceFormat))
    {
        continue;
    }

    string target = Path.Combine("Output", Path.GetFileNameWithoutExtension(file) + ".pdf");

    using (ServerTextControl tx = new ServerTextControl())
    {
        tx.Create();

        try
        {
            tx.Load(file, sourceFormat);
            tx.Save(target, StreamType.AdobePDF);

            Console.WriteLine($"{file} -> {target}");
        }
        catch (FilterException exception)
        {
            Console.WriteLine($"{file} skipped: {exception.Reason}");
        }
    }
}
```

The code works as follows:

- Each file is converted by its own `ServerTextControl` instance, which the `using` statement disposes after the conversion.
- [Save](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/save-method/llms-full.txt) writes the complete document to the target file and overwrites an existing file.
- A [FilterException](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/filterexception/llms-full.txt) with its [Reason](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/filterexception/reason-property/llms-full.txt) reports a file that couldn't be loaded or saved.

Between `Load` and `Save`, the document can be changed, for example with a header or a different page size. For batch processes with hundreds or thousands of documents, Text Control recommends several processes instead of several threads in one process, which can wait for each other at TX Text Control's critical sections; [Performance and Scalability](https://docs.textcontrol.com/tx-text-control-dotnet-server/performance/llms-full.txt) describes the options.

### Save Part of a Document

[Selection.Save](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/selection/save-method/llms-full.txt) saves only the selected text, to a file, a byte array or a string. [Paragraph.Save](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/paragraph/save-method/llms-full.txt), `Line.Save` and `SubTextPart.Save` save a single paragraph, line or subtext part to a byte array or a string:

```csharp
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    tx.Load("Documents/sample.docx", StreamType.WordprocessingML);

    // The first paragraph as an HTML string
    Paragraph paragraph = tx.Paragraphs[1];

    string paragraphHtml;
    paragraph.Save(out paragraphHtml, StringStreamType.HTMLFormat);

    Console.WriteLine(paragraphHtml);

    // Select the same paragraph and save the selection as RTF and as a DOCX file
    tx.Select(paragraph.Start - 1, paragraph.Length);

    string rtf;
    tx.Selection.Save(out rtf, StringStreamType.RichTextFormat);

    tx.Selection.Save("excerpt.docx", StreamType.WordprocessingML);
}
```

Paragraphs are numbered from 1, and [Paragraph.Start](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/paragraph/start-property/llms-full.txt) is one-based, while `Select` expects a position that starts at 0. PDF and PDF/A are the exception: only a complete document can be saved in these formats.

### Save HTML With Stylesheets and Images

HTML output needs two decisions: where the formatting goes and how the images are stored. [SaveSettings.CssSaveMode](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/csssavemode-property/llms-full.txt) sets the formatting:

| Value | The formatting is written |
|---|---|
| `None` | Into the HTML tags of the body |
| `Inline` | As stylesheets in the head of the HTML document |
| `CreateFile` | Into a CSS file with the name of the HTML file, or the name in `CssFileName`; an existing file is kept |
| `OverwriteFile` | Like `CreateFile`, but an existing CSS file is overwritten |

[SaveSettings.ImageSaveMode](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/imagesavemode-property/llms-full.txt) set to `SaveAsData` embeds the images in the HTML, so the result is a single file. The following program writes the same document as an HTML file with a CSS file and as an HTML string with its styles in the head:

```csharp
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    tx.Load("Documents/sample.docx", StreamType.WordprocessingML);

    Directory.CreateDirectory("Output");

    // An HTML file with a stylesheet file of the same name, Output/sample.css
    SaveSettings fileSettings = new SaveSettings()
    {
        CssSaveMode = CssSaveMode.OverwriteFile,
        ImageSaveMode = ImageSaveMode.SaveAsData
    };

    tx.Save("Output/sample.html", StreamType.HTMLFormat, fileSettings);

    // An HTML string can't have a stylesheet file, so the styles go into the head
    SaveSettings stringSettings = new SaveSettings()
    {
        CssSaveMode = CssSaveMode.Inline,
        ImageSaveMode = ImageSaveMode.SaveAsData
    };

    string html;
    tx.Save(out html, StringStreamType.HTMLFormat, stringSettings);

    Console.WriteLine($"{html.Length} characters of HTML");
}
```

`CreateFile` and `OverwriteFile` can't be used when HTML is saved to a string or a byte array. HTML has no pages, so it has no equivalent for the headers, footers, page breaks and page numbers of a document, which is why DOCX and PDF are the formats for documents that are printed or archived.

### Reduce the Size of PDF Files

Photos usually make up most of a PDF file. Two settings reduce their size when a document is saved:

```csharp
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    tx.Load("Documents/sample.docx", StreamType.WordprocessingML);

    SaveSettings saveSettings = new SaveSettings()
    {
        ImageCompressionQuality = 70, // 1 to 100
        ImageMaxResolution = 150      // dpi; larger images are reduced
    };

    byte[] pdf;
    tx.Save(out pdf, BinaryStreamType.AdobePDF, saveSettings);

    Console.WriteLine($"PDF: {pdf.Length} bytes");
}
```

- [ImageCompressionQuality](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/imagecompressionquality-property/llms-full.txt) sets the quality of lossy compression from 1 to 100. In a PDF, a quality of 100 stores images without loss, and lower values store them as JPEG.
- [ImageMaxResolution](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/imagemaxresolution-property/llms-full.txt) reduces images with a higher resolution. The default, 0, keeps the resolution of each image.

The blog article [Best Practices for Image Compression when Exporting to PDF in .NET C#](https://www.textcontrol.com/blog/2025/08/22/best-practices-for-image-compression-when-exporting-to-pdf-in-dotnet-csharp/) recommends starting with a quality of 70, and resolutions of 72 to 150 dpi for documents read on screen and 300 dpi for print. [Images](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/images/#control-how-images-are-saved) lists the other image settings.

### Leave Out Content and Add Properties

[SaveSettings.OmittedContent](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/omittedcontent-property/llms-full.txt) leaves parts of a document out of the saved file, for example comments before a document goes to a customer. It can't be used with PDF and PDF/A:

| Value | Leaves out |
|---|---|
| `Comments`, `Footnotes` | Comments and footnotes |
| `TextFields` | All kinds of text fields |
| `TrackedChanges` | The markup of tracked changes; text and formatting are saved |
| `SubTextParts`, `EditableRegions`, `TablesOfContents` | The markers or data of these elements; text and formatting are saved |
| `Sections` | The section data |

The following program saves a DOCX document without its comments, with a title and two user-defined properties, and prints what was saved:

```csharp
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    tx.Load("Documents/sample.docx", StreamType.WordprocessingML);

    UserDefinedPropertyDictionary properties = new UserDefinedPropertyDictionary();
    properties.Add("InvoiceNumber", "INV-2025-001");
    properties.Add("AmountDue", 299.97);

    SaveSettings saveSettings = new SaveSettings()
    {
        DocumentTitle = "Invoice INV-2025-001",
        UserDefinedDocumentProperties = properties,
        OmittedContent = OmittedContent.Comments
    };

    tx.Save("invoice.docx", StreamType.WordprocessingML, saveSettings);

    Console.WriteLine($"{saveSettings.SavedFile}: {saveSettings.BytesWritten} bytes ({saveSettings.SavedStreamType})");
}
```

- [DocumentTitle](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/documenttitle-property/llms-full.txt) and [UserDefinedDocumentProperties](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/userdefineddocumentproperties-property/llms-full.txt) set the metadata of the saved file, as [Document Metadata](https://docs.textcontrol.com/tx-text-control-dotnet-server/pdf/metadata/llms-full.txt) describes for all formats.
- After saving, [SavedFile](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/savedfile-property/llms-full.txt), [BytesWritten](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/byteswritten-property/llms-full.txt) and [SavedStreamType](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/savedstreamtype-property/llms-full.txt) report the file, its size and its format. `SavedFile` is empty when the document was saved to a byte array or a string.
- With [SaveDocumentBackColor](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/savedocumentbackcolor-property/llms-full.txt) set to `false`, the background color of the document isn't saved.

`SaveSettings` shares the PDF passwords, `EmbeddedFiles` and the other members of its base class [LoadSaveSettingsBase](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/loadsavesettingsbase/llms-full.txt) with `LoadSettings`, as [Control the Import With LoadSettings](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/loading/#control-the-import-with-loadsettings) describes.

### Save to Streams

`Save` writes to a `FileStream` at the stream's current position. For other streams, such as a `MemoryStream` that another library expects, there is no overload: save to a byte array and wrap it in a stream:

```csharp
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    tx.Load("Documents/sample.docx", StreamType.WordprocessingML);

    // A FileStream is written at its current position
    using (FileStream fileStream = new FileStream("sample.rtf", FileMode.Create))
    {
        tx.Save(fileStream, StreamType.RichTextFormat);
    }

    // Other streams: save to a byte array and wrap it
    byte[] pdf;
    tx.Save(out pdf, BinaryStreamType.AdobePDF);

    using (MemoryStream memoryStream = new MemoryStream(pdf))
    {
        Console.WriteLine($"Stream length: {memoryStream.Length}");
    }
}
```

### Return Converted Documents From ASP.NET Core

The following controller action converts a stored document to the format that the request asks for:

```csharp
using Microsoft.AspNetCore.Mvc;
using TXTextControl;

public class ConvertController : Controller
{
    public IActionResult Download(string format)
    {
        using (ServerTextControl tx = new ServerTextControl())
        {
            tx.Create();

            tx.Load("Documents/sample.docx", StreamType.WordprocessingML);

            switch (format)
            {
                case "pdf":
                    byte[] pdf;
                    tx.Save(out pdf, BinaryStreamType.AdobePDF);
                    return File(pdf, "application/pdf", "sample.pdf");

                case "docx":
                    byte[] docx;
                    tx.Save(out docx, BinaryStreamType.WordprocessingML);
                    return File(docx,
                        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                        "sample.docx");

                case "html":
                    string html;
                    tx.Save(out html, StringStreamType.HTMLFormat,
                        new SaveSettings() { CssSaveMode = CssSaveMode.Inline });
                    return Content(html, "text/html");

                default:
                    return BadRequest("Unsupported format.");
            }
        }
    }
}
```

With the default route, `/Convert/Download?format=pdf` returns the PDF file.

### Custom XML Parts of DOCX Files

A DOCX file is a ZIP package, and applications such as MS Word can store data of their own in it as custom XML parts: XML files in the *customXml* folder of the package, such as *customXml/item1.xml*, which the document doesn't show. TX Text Control has no API for these parts, and they aren't kept: a DOCX file loaded with `ServerTextControl` and saved again has no *customXml* folder. Read the parts with `System.IO.Compression` before you load the document:

```csharp
using System.IO.Compression;
using TXTextControl;

// The custom XML parts, read before the document is loaded
Dictionary<string, string> customXmlParts = new Dictionary<string, string>();

using (ZipArchive package = ZipFile.OpenRead("Documents/contract.docx"))
{
    foreach (ZipArchiveEntry entry in package.Entries)
    {
        // customXml/item1.xml, customXml/item2.xml, ...
        if (entry.FullName.StartsWith("customXml/item") && entry.FullName.EndsWith(".xml"))
        {
            using (StreamReader reader = new StreamReader(entry.Open()))
            {
                customXmlParts[entry.FullName] = reader.ReadToEnd();
            }
        }
    }
}

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/contract.docx", StreamType.WordprocessingML);
    tx.Save("contract.pdf", StreamType.AdobePDF);
}

foreach (KeyValuePair<string, string> part in customXmlParts)
{
    Console.WriteLine($"{part.Key}: {part.Value}");
}
```

To write custom XML parts, save the document to a byte array with `BinaryStreamType.WordprocessingML` and add the parts to the package with `ZipArchive` afterward. The blog article [Read and Write Custom XML Parts in MS Word Office Open XML DOCX Files using .NET C#](https://www.textcontrol.com/blog/2024/07/23/read-and-write-custom-xml-parts-in-ms-word-office-open-xml-docx-files-using-net-csharp/) reads and adds the parts this way. For data that travels with a PDF document, PDF/A-3 has embedded files; see [Attachments](https://docs.textcontrol.com/tx-text-control-dotnet-server/pdf/attachments/llms-full.txt).

### What Changes When Converting

Formats differ in what they can store, so a conversion can change a document:

- **DOC** can be loaded and saved only with the Classic engine. [Supported File Formats](https://docs.textcontrol.com/tx-text-control-dotnet-server/overview/file-formats/llms-full.txt) lists the formats of each engine.
- **Plain text** keeps only the text, with a carriage return and a line feed at the end of each paragraph.
- **Fields** of Word documents are only imported as fields when `LoadSettings.ApplicationFieldFormat` is set, as described in [Loading Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/loading/llms-full.txt).
- **Images** are saved in their original format. `SaveSettings.ImageExportFilterIndex` saves them in another format, and when the target format doesn't support that format, they're stored in its default image format. PDF stores vector images such as SVG as vector graphics.
- **Fonts** that aren't available on the server are replaced, which can change line and page breaks. [Creating Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/creating/#make-fonts-available-on-the-server) shows how to find replaced fonts.
- **HTML** has no pages, so headers, footers and page breaks are lost.
- **Custom XML parts** of DOCX files aren't kept, as described in [Custom XML Parts of DOCX Files](#custom-xml-parts-of-docx-files).

[Document Conversion](https://docs.textcontrol.com/tx-text-control-dotnet-server/troubleshooting/conversion/llms-full.txt) in the Troubleshooting section covers conversion problems.

### Next Steps

- [PDF Processing](https://docs.textcontrol.com/tx-text-control-dotnet-server/pdf/llms-full.txt): create PDF/A and PDF/UA documents, protect them and add attachments.
- [Images](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/images/llms-full.txt): control how the images of a document are saved.
- [Loading Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/loading/llms-full.txt): load documents with all their options.
- [Supported File Formats](https://docs.textcontrol.com/tx-text-control-dotnet-server/overview/file-formats/llms-full.txt): the formats you can load and save.

---

## Nearby

- **Previous:** [Loading Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/loading/llms-full.txt)
- **Next:** [Text](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/text/llms-full.txt)
