TX Text Control .NET Server

Text

Topic

Replacing placeholders, highlighting amounts and reading what a document says all start with its text. With ServerTextControl, you select text by its position, find and replace it in the main text and in headers, footers and text frames, format characters, define inline styles and read the text line by line.

Create a Document introduces the input position, the Selection class and twips, the unit of font sizes. Alignment, indents, paragraph styles and lists are described in Paragraphs. Each example below is a complete Program.cs of a console application.

How Text Positions Work

ServerTextControl.Text gets or sets the text of the document. To work with part of the text, you select it:

  • Positions start at 0. Selection.Start is the position of the first selected character, and the first position of the text is 0. Selection.Length is the number of selected characters. With a length of 0, nothing is selected and Start is the input position.
  • Select a range. ServerTextControl.Select takes a start position and a length. SelectAll selects the whole text, and SelectWord the word at the input position. Setting Selection.Start alone keeps the current length.
  • Element positions start at 1. Paragraph.Start, Line.Start and TableCell.Start are one-based. Subtract 1 before you pass them to Select.
  • Tables are selected with their own methods, such as TableCell.Select, which Tables describes.

Move and Read the Input Position

ServerTextControl.InputPosition sets the input position, where text, fields and other elements are inserted, and tells where it is. The following program moves it to a word of Documents/sample.docx and prints the page, section, line and column there:

using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/sample.docx", StreamType.WordprocessingML);

    int position = tx.Find("Total");

    if (position != -1)
    {
        // Move the input position to a zero-based text position
        tx.InputPosition = new InputPosition(position);

        InputPosition input = tx.InputPosition;
        Console.WriteLine($"Text position {input.TextPosition}: page {input.Page}, " +
            $"section {input.Section} (page {input.PageInSection} of the section), " +
            $"line {input.Line}, column {input.Column}");
    }
}

The InputPosition object you read has these properties:

PropertyReturns
TextPositionThe text position, starting at 0
Page, PageInSectionThe page number in the document and in the section
SectionThe section number; the first section is 1
LineThe line number on the page; the first line on top of the page is 1
ColumnThe position in the line; the position in front of the first character is 0
LocationThe geometric location in twips, from the top-left corner of the document

The InputPosition constructor has further overloads. new InputPosition(1, 3, 0) stands for page 1, line 3, in front of the first character. new InputPosition(position, TextFieldPosition.OutsideTextField) decides whether a position at the start or end of a field with a doubled input position is inside or outside the field, with a value of TextFieldPosition.

Select and Format Characters

Once text is selected, the properties of ServerTextControl.Selection change its character attributes. The following program formats the first words of a sentence and two amounts that it finds in the text:

using TXTextControl;

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

    tx.Text = "Order Confirmation: The total of 1,250.00 EUR is due on delivery. Regular price: 1,400.00 EUR.";

    // Select 19 characters from position 0: "Order Confirmation:"
    tx.Select(0, 19);
    tx.Selection.Bold = true;
    tx.Selection.FontName = "Arial";
    tx.Selection.FontSize = 280; // 280 twips = 14 pt
    tx.Selection.ForeColor = System.Drawing.Color.DarkBlue;

    // Find a text and format it
    string amount = "1,250.00 EUR";
    int position = tx.Find(amount);

    if (position != -1)
    {
        tx.Select(position, amount.Length);
        tx.Selection.TextBackColor = System.Drawing.Color.Yellow;
        tx.Selection.Underline = FontUnderlineStyle.Doubled;
    }

    string oldPrice = "1,400.00 EUR";
    position = tx.Find(oldPrice);

    if (position != -1)
    {
        tx.Select(position, oldPrice.Length);
        tx.Selection.Strikeout = true;
        tx.Selection.Italic = true;
    }

    tx.Save("formatted-text.docx", StreamType.WordprocessingML);
}

Find returns the position of the text, or -1 if the text isn't found, and Select then selects it for formatting. The character attributes of a Selection:

PropertySetsDefault
Bold, Italic, StrikeoutBold, italic and struck-out textfalse
UnderlineNone, Single, Doubled, SingleWordsOnly or DoubledWordsOnlyNone
FontName, FontSizeThe font and its size in twipsArial, 200 twips (10 pt)
ForeColor, TextBackColorThe text color and the background color of the text, as System.Drawing.ColorSystem colors
BaselineA shift of the text in twips, from -960 to 960: positive values raise the text, negative values lower it0
AutoBaselineSuperscript or subscript text, scaled and shifted with values from the fontNone
CapitalsCapitals, SmallCapitals or PetiteCapitalsNone
CharacterSpacingTwips added to the space of each character; negative values reduce it0
CharacterScalingThe character width in percent of the average width100
CultureThe language of the text, as a CultureInfonull, the neutral culture

When a selection contains different values of an attribute, the property returns its default, and FontName an empty string. IsCommonValueSelected tells whether the whole selection has the same value. It takes a value of Selection.Attribute, such as Bold, FontName, FontSize or ForeColor, and other overloads take the Attribute enumerations of ParagraphFormat, ListFormat, SectionFormat, PageMargins and PageSize:

tx.SelectAll();

// true if all selected text uses one font
if (tx.Selection.IsCommonValueSelected(Selection.Attribute.FontName))
{
    Console.WriteLine($"Font: {tx.Selection.FontName}");
}
else
{
    Console.WriteLine("The selection uses more than one font");
}

A few methods change the selected text in one step: ChangeCapitalization changes the case of letters, GrowFont and ShrinkFont change all font sizes, and RemoveFormatting resets the character attributes to their defaults.

Control-Level Character Properties

ServerTextControl has character properties of its own, besides those of the selection. They belong to the control, not to a selected text:

PropertySetsReset with
FontThe font of the control, 10 pt Arial by defaultResetFont
ForeColorThe foreground color of the controlResetForeColor
TextBackColorThe background color of the textResetTextBackColor
FontUnderlineStyleThe style of underlined text; whether text is underlined is set by Font—
BaselineA baseline shift in twips, from -960 to 960—

To format part of a document, use the properties of Selection shown above.

Insert Formatted Text

When you build text piece by piece, prepare a Selection object with the text and its attributes and assign it to tx.Selection. The text is inserted at the input position with its attributes in one step, which is faster than inserting the text first and formatting it afterward:

using TXTextControl;

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

    // Each Selection object is inserted at the input position
    tx.Selection = new Selection { Text = "Office space: 120 m" };
    tx.Selection = new Selection { Text = "2", AutoBaseline = AutoBaseline.Superscript };
    tx.Selection = new Selection { Text = "\r\n" };

    tx.Selection = new Selection { Text = "Terms and Conditions\r\n", Capitals = Capitals.SmallCapitals };

    // 40 twips added to each character
    tx.Selection = new Selection { Text = "Expanded character spacing\r\n", CharacterSpacing = 40 };

    // 80 percent of the average character width
    tx.Selection = new Selection { Text = "Condensed characters\r\n", CharacterScaling = 80 };

    tx.Selection = new Selection
    {
        Text = "Dieser Absatz ist deutsch.\r\n",
        Culture = new System.Globalization.CultureInfo("de-DE")
    };

    tx.Save("character-attributes.docx", StreamType.WordprocessingML);
}

The blog article Speed Up Document Generation by Factor 2 Using Selection Objects measures the difference.

Find and Replace Text

Find(string) searches the main text from the beginning. The overload Find(string text, int start, FindOptions options) starts at a position, or at the input position with -1, and takes values of FindOptions, which can be combined:

ValueFinds
MatchCaseOnly text with the same uppercase and lowercase letters
MatchWholeWordOnly whole words
ReverseSearches from the end toward the beginning
NoHighlight, NoMessageBoxDoesn't highlight the found text and shows no message boxes

To replace every occurrence of a placeholder, find it, select it, set the new text and continue behind the inserted text:

using TXTextControl;

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

    tx.Text = "%%company_name%% confirms the order. Questions? Contact %%company_name%%.";

    int count = ReplaceAll(tx, "%%company_name%%", "Text Control, LLC");
    Console.WriteLine($"{count} occurrences replaced");

    tx.Save("replaced.docx", StreamType.WordprocessingML);
}

// Replaces every occurrence of searchText in the main text
static int ReplaceAll(ServerTextControl tx, string searchText, string replacementText)
{
    int count = 0;
    int start = 0;
    int position;

    while ((position = tx.Find(searchText, start, FindOptions.MatchCase)) != -1)
    {
        tx.Select(position, searchText.Length);
        tx.Selection.Text = replacementText;

        // Continue behind the inserted text
        start = position + replacementText.Length;
        count++;
    }

    return count;
}

To use a document instead of the sample text, replace the tx.Text line with tx.Load("Documents/sample.docx", StreamType.WordprocessingML);. The blog article High-Performance Text Replacement in Large DOCX Files replaces text in an 80-page document this way.

Placeholder text works for simple cases. For templates that are filled with data, merge fields are the structured alternative; see Merge Fields.

Find Text With Regular Expressions

Find searches for a fixed text. Patterns such as invoice numbers, dates or email addresses are found with a regular expression of .NET in the text of the document. In ServerTextControl.Text, every paragraph ends with a carriage return and a line feed, while a document position counts a paragraph break as one character. Replace each \r\n with \n, and the index of a match is its position in the document:

using System.Drawing;
using System.Text.RegularExpressions;
using TXTextControl;

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

    tx.Text = "Payment reminder\r\n" +
              "Invoice INV-2025-001 of May 27, 2025 is due.\r\n" +
              "Invoice INV-2025-014 of June 3, 2025 is due as well.";

    // One character per paragraph break, as in document positions
    string text = tx.Text.Replace("\r\n", "\n");

    Regex invoiceNumber = new Regex(@"INV-\d{4}-\d{3}");

    foreach (Match match in invoiceNumber.Matches(text))
    {
        tx.Select(match.Index, match.Length);
        tx.Selection.Bold = true;
        tx.Selection.TextBackColor = Color.Yellow;

        Console.WriteLine($"{match.Value} at position {match.Index}");
    }

    tx.Save("reminder.pdf", StreamType.AdobePDF);
}

The program prints the two invoice numbers with their positions and saves them bold on a yellow background. The same works for text in table cells. Formatting keeps the positions valid; if you replace matches with a text of another length, go through the matches from the last to the first. The blog article Advanced Smart Search with Regular Expressions in .NET C# lists patterns for email addresses, URLs, phone numbers and dates.

Replace Text in Headers, Footers and Text Frames

ServerTextControl.Find searches only the main text. Headers, footers and text frames are text parts of their own. ServerTextControl.TextParts contains all of them: the main text first, then the text frames, headers and footers. Each text part implements IFormattedText, which has its own Find method and Selection:

using TXTextControl;

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

    tx.Text = "This contract is made with %%company_name%%.";

    tx.HeadersAndFooters.Add(HeaderFooterType.Header);
    HeaderFooter header = tx.HeadersAndFooters.GetItem(HeaderFooterType.Header);
    header.Selection.Text = "%%company_name%% - Confidential";

    string searchText = "%%company_name%%";
    string replacementText = "Text Control, LLC";
    int count = 0;

    // The main text, text frames, headers and footers
    foreach (IFormattedText textPart in tx.TextParts)
    {
        int start = 0;
        int position;

        while ((position = textPart.Find(searchText, start, FindOptions.MatchCase)) != -1)
        {
            textPart.Selection = new Selection(position, searchText.Length);
            textPart.Selection.Text = replacementText;

            start = position + replacementText.Length;
            count++;
        }
    }

    Console.WriteLine($"{count} occurrences replaced");
    tx.Save("replaced.pdf", StreamType.AdobePDF);
}

The program replaces the placeholder in the main text and in the header, and prints "2 occurrences replaced".

Use Inline Styles

An InlineStyle is a named set of character attributes for words or phrases within a paragraph. When you change the style, all text that uses it changes. The following program defines a style for deadlines, applies it to every occurrence of a date and then adds a background color to the style:

using TXTextControl;

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

    tx.Text = "Please return the signed contract by March 31. Submissions after March 31 are not accepted.";

    // Define an inline style and add it to the document
    InlineStyle deadline = new InlineStyle("Deadline");
    deadline.Bold = true;
    deadline.ForeColor = System.Drawing.Color.DarkRed;
    tx.InlineStyles.Add(deadline);

    // Apply the style to every occurrence of the date
    string date = "March 31";
    int start = 0;
    int position;

    while ((position = tx.Find(date, start, FindOptions.MatchCase)) != -1)
    {
        tx.Select(position, date.Length);
        tx.Selection.FormattingStyle = "Deadline";
        start = position + date.Length;
    }

    // Change the style: all text that uses it changes
    InlineStyle style = tx.InlineStyles.GetItem("Deadline");
    style.TextBackColor = System.Drawing.Color.LightYellow;
    style.Apply();

    tx.Save("inline-style.docx", StreamType.WordprocessingML);
}

The code works as follows:

  • The InlineStyle constructor with a name creates a style based on the root style [Normal]. Another overload takes the name of a base style.
  • InlineStyles.Add adds the style to the document and returns true if it was added.
  • Selection.FormattingStyle applies the style to the selected text by its name.
  • GetItem returns a style by its name, or null if the document has no such style. After changing a style, Apply applies its attributes to the document.

Text with an inline style keeps the attributes of its paragraph style except those that the inline style sets. Selection.RemoveInlineStyles resets the selected text to the attributes of its paragraph style.

Insert Special Characters

\r\n in a text ends a paragraph. TextCharCollection.Add of ServerTextControl.TextChars inserts other control characters, with a value of ControlChars, at the input position or at a text position:

ValueInserts
LineBreakA line break within the paragraph
PageBreakA page break
ColumnBreakA break to the next page column
TabA tab character
NonBreakingSpaceA space at which a line isn't broken
NonVisibleHyphenA hyphen that is shown only when the word is broken at this position

For example, tx.TextChars.Add(ControlChars.LineBreak); inserts a line break at the input position. A heading that must start on a new page is better served by the paragraph attribute PageBreakBefore, described in Paragraphs, because a forced page break can leave an empty page when the text before it changes.

Read the Text of a Document

ServerTextControl gives you the text of a document as a whole, by lines and by characters. The following program prints the text of Documents/sample.docx, its lines with their page numbers and its first line as HTML:

using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/sample.docx", StreamType.WordprocessingML);

    Console.WriteLine(tx.Text);
    Console.WriteLine($"{tx.TextChars.Count} characters, {tx.Lines.Count} lines, {tx.Paragraphs.Count} paragraphs, {tx.Pages} pages");

    foreach (Line line in tx.Lines)
    {
        Console.WriteLine($"Page {line.Page}, line {line.Number}: {line.Text}");
    }

    // Characters and lines are numbered from 1
    Console.WriteLine($"First character: {tx.TextChars[1].Char}");

    // Line.Start is one-based, Select expects a zero-based position
    Line firstLine = tx.Lines[1];
    tx.Select(firstLine.Start - 1, firstLine.Length);

    string html;
    tx.Selection.Save(out html, StringStreamType.HTMLFormat);
    Console.WriteLine(html);
}

The code works as follows:

Positions and Sizes of Lines and Characters

Lines and characters also tell where they are on the page, in twips from the top-left corner of the document. The following lines, added to the program above, print the geometry of the first line and of its first character and save one line as HTML:

// The line at a zero-based text position
Line line = tx.Lines.GetItem(0);
Console.WriteLine($"Baseline {line.Baseline}, bounds {line.TextBounds}");

TextChar character = tx.TextChars[line.Start];
Console.WriteLine($"'{character.Char}' at {character.Bounds}");

// Save a single line without selecting it
string lineHtml;
line.Save(out lineHtml, StringStreamType.HTMLFormat);

What the members return:

  • LineCollection.GetItem returns the line at a zero-based text position, or null. Line.Start is the one-based number of the line's first character, which is also its index in TextChars.
  • Line.Baseline is the vertical position of the line's baseline, and Line.TextBounds the rectangle around its text.
  • TextChar.Bounds is the rectangle around a character, and TextChar.Number its one-based number.
  • Line.Save saves the text of the line as a string or a byte array in a format, like Selection.Save.
  • TextCharCollection.Remove removes a character, also a control character. It returns false for characters other formatting depends on, such as the end of a table cell.

TextCharCollection.GetItem and the System.Drawing.Point overload of LineCollection.GetItem find a character or a line at a geometric position instead. They expect the point in pixels from the top-left corner of the control, not in twips, and GetItem of TextChars returns the nearest character when its second parameter is true.

To get the plain text of a whole document, save it with StringStreamType.PlainText, as Save as DOCX and PDF shows.

Next Steps

  • Paragraphs: align paragraphs, set their spacing and create paragraph styles and lists.
  • Fields: mark text that your code finds and changes later.
  • Creating Documents: build documents from text, documents and other content.
  • Pages: find the page of a text and read the text page by page.
  • Editing Text in the Document Editor: change text from JavaScript.