Text
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
Startis 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.Startalone keeps the current length. - Element positions start at 1.
Paragraph.Start,Line.StartandTableCell.Startare one-based. Subtract 1 before you pass them toSelect. - 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:
| Property | Returns |
|---|---|
| TextPosition | The text position, starting at 0 |
| Page, PageInSection | The page number in the document and in the section |
| Section | The section number; the first section is 1 |
| Line | The line number on the page; the first line on top of the page is 1 |
| Column | The position in the line; the position in front of the first character is 0 |
| Location | The 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:
| Property | Sets | Default |
|---|---|---|
| Bold, Italic, Strikeout | Bold, italic and struck-out text | false |
| Underline | None, Single, Doubled, SingleWordsOnly or DoubledWordsOnly | None |
| FontName, FontSize | The font and its size in twips | Arial, 200 twips (10 pt) |
| ForeColor, TextBackColor | The text color and the background color of the text, as System.Drawing.Color | System colors |
| Baseline | A shift of the text in twips, from -960 to 960: positive values raise the text, negative values lower it | 0 |
| AutoBaseline | Superscript or subscript text, scaled and shifted with values from the font | None |
| Capitals | Capitals, SmallCapitals or PetiteCapitals | None |
| CharacterSpacing | Twips added to the space of each character; negative values reduce it | 0 |
| CharacterScaling | The character width in percent of the average width | 100 |
| Culture | The language of the text, as a CultureInfo | null, 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:
| Property | Sets | Reset with |
|---|---|---|
| Font | The font of the control, 10 pt Arial by default | ResetFont |
| ForeColor | The foreground color of the control | ResetForeColor |
| TextBackColor | The background color of the text | ResetTextBackColor |
| FontUnderlineStyle | The style of underlined text; whether text is underlined is set by Font | — |
| Baseline | A 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:
| Value | Finds |
|---|---|
MatchCase | Only text with the same uppercase and lowercase letters |
MatchWholeWord | Only whole words |
Reverse | Searches from the end toward the beginning |
NoHighlight, NoMessageBox | Doesn'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.
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
trueif it was added. - Selection.FormattingStyle applies the style to the selected text by its name.
- GetItem returns a style by its name, or
nullif 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:
| Value | Inserts |
|---|---|
LineBreak | A line break within the paragraph |
PageBreak | A page break |
ColumnBreak | A break to the next page column |
Tab | A tab character |
NonBreakingSpace | A space at which a line isn't broken |
NonVisibleHyphen | A 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:
- ServerTextControl.Lines contains the lines of the main text. Each Line has its Text, its Number and the Page it's on.
TextCharscontains the characters of the main text, and the Char property of each is the character.- ServerTextControl.Pages is the number of pages.
- Selection.Save saves the selected text, here as an HTML string. It also saves to files and byte arrays in the other formats.
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 inTextChars. - 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
falsefor 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.