Click here to Skip to main content
14,239,722 members

RTF Document Constructor Library

Rate this:
4.95 (54 votes)
Please Sign up or sign in to vote.
4.95 (54 votes)
16 Aug 2010CPOL
Create Rich Text Format documents programatically.


Some time ago, I needed to construct Rich Text Format reports, and I decided to write my own library from scratch as I couldn't find any suitable solution on the web.

The main idea was to create a library that could be easily expanded, so that developers could add any feature from the RTF Specification that is not implemented by default. Look at the second part of this article for details.

Using the Code

The RtfDocument class has a constructor that takes a RtfCodepage enum value as parameter. I tested the library with Windows-1251 Cyrillic encoding, and I'm quite sure it works just as well with others. Unicode is supported too.

RtfDocument rtf = new RtfDocument();

We go on with adding fonts and colors to the specified tables. Later, we will use indexes to refer to them (that's certainly the simplest but not the best implementation). RtfDocument.DefaultFont defines the index of the font used for paragraphs with no FontIndex set explicitly.

rtf.FontTable.Add(new RtfFont("Calibri"));
rtf.FontTable.Add(new RtfFont("Constantia"));
rtf.ColorTable.AddRange(new RtfColor[] {
    new RtfColor(Color.Red),
    new RtfColor(0, 0, 255)

The contents of the document are paragraphs, formatted paragraphs, table rows, and tables. Let's create a header paragraph with centered 16pt text.

RtfFormattedParagraph header = 
  new RtfFormattedParagraph(new RtfParagraphFormatting(16, RtfTextAlign.Center));

Add some text, formatted text, and an empty paragraph to the header:

header.AppendText(new RtfFormattedText(" Bold", RtfCharacterFormatting.Bold));

Add another paragraph with a different formatting. We set FontIndex to 1 and IndentLeft to 6.05cm. Most of the indents and widths are set in twips. TwipConverter converts millimeters, centimeters, and points to twips, and vice versa.

RtfFormattedParagraph p = 
  new RtfFormattedParagraph(new RtfParagraphFormatting(12, RtfTextAlign.Left));
p.Formatting.FontIndex = 1;
p.Formatting.IndentLeft = TwipConverter.ToTwip(6.05F, MetricUnit.Centimeter);
p.AppendText(new RtfFormattedText("Superscript", RtfCharacterFormatting.Superscript));

Here is an example demonstrating inline font size change. The font index -1 to be ignored.

p.AppendParagraph(new RtfFormattedText("Inline", -1, 8));
p.AppendText(new RtfFormattedText(" font size ", -1, 14));
p.AppendText(new RtfFormattedText("change", -1, 8));

Pictures are supported in different output formats. JPEG and PNG cannot be read by WordPad, so it's better to use WMF for compatibility. The conversion to WMF is done with P/Invoke calls, and the credits for this part go to David Bennett.

RtfImage picture = new RtfImage(Properties.Resources.lemon, RtfImageFormat.Wmf);
picture.ScaleX = 50;
picture.ScaleY = 50;


A hyperlink with common formatting:

RtfFormattedText linkText = 
  new RtfFormattedText("View article", RtfCharacterFormatting.Underline, 2);
p.AppendParagraph(new RtfHyperlink("RtfConstructor.aspx", linkText));

A centered table with 2 columns and 3 rows:

RtfTable t1 = new RtfTable(RtfTableAlign.Center, 2, 3);

The cells can be merged both horizontally and vertically:

t1.MergeCellsVertically(1, 0, 2);

Formatting to use within cells:

RtfParagraphFormatting LeftAligned12 = new RtfParagraphFormatting(12, RtfTextAlign.Left);
RtfParagraphFormatting Centered10 = new RtfParagraphFormatting(10, RtfTextAlign.Center);

The table cell class derives from formatted paragraph, and has some additional properties.

t1[0, 0].Definition.Style = 
  new RtfTableCellStyle(RtfBorderSetting.None, LeftAligned12, 
t1[0, 0].AppendText("Bottom");

t1[1, 0].Definition.Style = 
  new RtfTableCellStyle(RtfBorderSetting.Left, Centered10, 
t1[1, 1].Definition.Style = t1[1, 0].Definition.Style;
t1[1, 0].AppendText("Vertical");

We set TextColorIndex of the cell to 1, and add RtfFormattedText with different colors.

t1[0, 1].Formatting = new RtfParagraphFormatting(10, RtfTextAlign.Center);
t1[0, 1].Formatting.TextColorIndex = 1;
t1[0, 1].AppendText(new RtfFormattedText("Black", 0));
t1[0, 1].AppendText(" Red ");
t1[0, 1].AppendText(new RtfFormattedText("Blue", 2));

This part shows an example of bitwise operations on RtfCharacterFormatting:

t1[0, 2].AppendText("Normal");
t1[1, 2].AppendText(new RtfFormattedText("Italic", 
         RtfCharacterFormatting.Caps | RtfCharacterFormatting.Italic));
t1[1, 2].AppendParagraph("+");
t1[1, 2].AppendParagraph(new RtfFormattedText("Caps", 
         RtfCharacterFormatting.Caps | RtfCharacterFormatting.Italic));

Adding the contents to the document:

rtf.Contents.AddRange(new IRtfDocumentPart[] {

When the document is complete, we use RtfWriter to convert it to RTF code:

RtfWriter rtfWriter = new RtfWriter();
TextWriter writer = new StreamWriter("test.rtf");
rtfWriter.Write(writer, rtf);

And voila, the resulting file as seen in Microsoft Word:


And that's the RTF code:

{\fonttbl {\f0\fnil\fcharset1\fprq0 Calibri;}{\f1\fnil\fcharset1\fprq0 Constantia;}}
{\colortbl ;\red255\green0\blue0;\red0\green0\blue255;}
\pard\plain\qc\fi0\li0\ri0\sl0\sb0\sa240\fs32 Calibri 
    {\b Bold}\par\trowd\trrh1134\trqc\clvertalb\cltxlrtb\cellx1701\clvmgf
    intbl\plain\ql\fi0\li0\ri0\sl0\sb0\sa0\fs24 Bottom\cell\pard\intbl
\plain\qc\fi0\li0\ri0\sl0\sb0\sa0\fs20 Vertical\cell\row\
    qc\fi0\li0\ri0\sl0\sb0\sa0\cf1\fs20{\cf0 Black } Red {\cf2 Blue}\cell
\cellx2835\pard\intbl\plain\qc\fi0\li0\ri0\sl0\sb0\sa0\fs20 Normal\
   cell\pard\intbl\plain\qc\fi0\li0\ri0\sl0\sb0\sa0\fs20{\i\caps I
talic}\par +\par{\i\caps Caps}\cell\row\pard\plain\ql\fi0\li3430\
   ri0\sl0\sb120\sa0\f1\fs24 Constantia {\super Superscript}\par{
\fs16 Inline}{\fs28  font size }{\fs16 change}\par{\pict\picscalex50\


\par{\field{\fldinst HYPERLINK "
       cs/RtfConstructor.aspx"}{\fldrslt{\cf2\cb1\ul View article}}}}

The code above has been wrapped to prevent scrolling.

Reflection Part

The conversion of RtfDocument to RTF code is done using Reflection.

Each class representing a control word has a specific attribute which is recognized by the RtfWriter. And that's what makes it easy to expand the library and add your own classes to support more control words. It is somehow similar to what you do when you add System.Xml.Serialization attributes to classes and members for serialization purposes. Except that you will have to study the RTF specification.

It comes as no surprise that the most used attribute is RtfControlWord. If its RtfControlWord.Name property is not set, RtfWriter uses the member name for the control word.

public int Red { get; set; }

Values of int members are appended to control words. RtfWriter ignores members marked with the RtfIndex attribute if their value is -1.

[RtfControlWord("cf"), RtfIndex]
public int TextColorIndex { get; set; }

The enums are special case, and they need the RtfEnumAsControlWord attribute as there are different ways they can be treated.

public enum RtfTableAlign

[RtfEnumAsControlWord(RtfEnumConversion.UseValue, Prefix = "fprq")]
public enum RtfFontPitch
    Default,     // as /fprq0
    Fixed,       // as /fprq1
    Variable     // as /fprq2

public enum RtfDocumentCharacterSet
    ANSI,        // as /ansi
    Mac,         // as /mac
    PC,          // as /pc
    PCa          // as /pca

Some members not marked with RtfControlWord must be included, and RtfInclude tells the RtfWriter to do that.

public class RtfTable
    public RtfTableRowCollection Rows
        get { return _rows; }

Some members should be included only if some conditions are met, and that's where the RtfInclude.ConditionMember property comes in handy.

[RtfControlWord("clbrdrt"), RtfInclude(ConditionMember = "IsTopBorderSet")]
public RtfBorder Top
    get { return top; }

public bool IsTopBorderSet
    get { return Top.Width > 0; }

A bool member marked with RtfControlWord is included only if its value is true.

public bool Bold = false;

Some control words are paired, like /trowd to start a table row, and /row to end it. The RtfControlWordDenotingEnd attribute is used to define the second one.

[RtfControlWord("pard"), RtfControlWordDenotingEnd("cell")]
public class RtfTableCell {

Some members must be ignored, and RtfIgnore is used for that.

public class RtfTableCell
    public RtfTable Table
        get { return RowInternal.Table; }

The RtfEnclosingBraces attribute and its RtfEnclosingBraces.ClosingSemicolon property speak for themselves.

[RtfControlWord("rtf1"), RtfEnclosingBraces]
public class RtfDocument {

[RtfControlWord("f", IsIndexed = true), 
 RtfEnclosingBraces(ClosingSemicolon = true)]
public class RtfFont {

Two attributes left unmentioned are RtfTextData to mark text, and RtfControlGroup which was added for some reason I don't remember. Only font and color tables are marked with it.

Points of Interest

Writing the RtfDocument with a lot of content takes a considerable time, but only once as the information about the types' attributes and members is stored in specific classes. Please take a look at the RtfDocumentInfo, RtfTypeInfo, and RtfAttributeInfo classes.


  • 16.08.10: Code revised, added support for tabs (not shown in the article).
  • 07.08.10: Added support for images, hyperlinks, inline color, and font formatting.
  • 05.08.10: Reflection Part added.
  • 02.08.10: Text revised, RTF code added.
  • 30.07.10: Initial release.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Dima Popov
Software Developer Energoservice
Russian Federation Russian Federation
Dmitry lives in Arkhangelsk, Russia. He has developed C# applications since 2007.

Comments and Discussions

GeneralRe: Opening the generated File via dialog box "Open, Save, Cancel" Pin
Dima Popov7-Dec-12 0:33
memberDima Popov7-Dec-12 0:33 
QuestionHow to add RtfDocument to RichTextBox Pin
rwbta26-Oct-12 4:26
memberrwbta26-Oct-12 4:26 
AnswerRe: How to add RtfDocument to RichTextBox Pin
Dima Popov29-Oct-12 22:53
memberDima Popov29-Oct-12 22:53 
QuestionRichTextBoxes Pin
Member 950558611-Oct-12 7:03
memberMember 950558611-Oct-12 7:03 
AnswerRe: RichTextBoxes Pin
Dima Popov11-Oct-12 9:34
memberDima Popov11-Oct-12 9:34 
QuestionSlow for large documents Pin
lallallalla7-Oct-12 23:09
memberlallallalla7-Oct-12 23:09 
AnswerRe: Slow for large documents Pin
Dima Popov8-Oct-12 3:50
memberDima Popov8-Oct-12 3:50 
SuggestionPaper size and landscape additions Pin
JWhattam2-Aug-12 11:24
memberJWhattam2-Aug-12 11:24 
Hi Dima,
It's me again! Whilst using your library, I found that the paper size always defaulted to the US standard and the orientation to portrait. To get a specific paper size and change the orientation for Word I made the following changes in the RTFDocument class:

public class RtfDocument
private int _MarginTop = (int)TwipConverter.TwipsInInch;
private int _MarginRight = (int)TwipConverter.TwipsInInch;
private int _MarginLeft = (int)TwipConverter.TwipsInInch;
private int _MarginBottom = (int)TwipConverter.TwipsInInch;
private int _PageWidth = TwipConverter.ToTwip(8.3F, MetricUnit.Inch);
private int _PageHeight = TwipConverter.ToTwip(11.7F, MetricUnit.Inch);
private bool _IsLandscape = false;

... The following should be inserted after the ColorTable property

/// <summary> Gets or sets whether the page layout is landscape (true) or not. </summary>
public bool IsLandscape
get { return _IsLandscape; }
set { _IsLandscape = value; }

/// <summary> Includes the \landscape control word when IsLandscape is true. </summary>
[RtfControlWord("landscape"), RtfInclude(ConditionMember = "IsLandscape")]
public string Landscape
get { return ""; }

/// <summary> Gets or sets the page width for the document. </summary>
/// <value> The page width, in twips; default is 8.3 inches in twips. </value>
/// <remarks> Use page size's height when landscape mode selected. </remarks>
public int PageWidth
get { return _PageWidth; }
set { _PageWidth = value; }

/// <summary> Gets or sets the page height for the document. </summary>
/// <value> The page width, in twips; default is 11.7 inches in twips. </value>
/// <remarks> Use page size's width when landscape mode selected. </remarks>
public int PageHeight
get { return _PageHeight; }
set { _PageHeight = value; }

/// <summary> Gets or sets the top margin for the document. </summary>
/// <value> The margin size, in twips; default is 1440 (one inch). </value>
public int MarginTop
get { return _MarginTop; }
set { _MarginTop = value; }

/// <summary> Gets or sets the bottom margin for the document. </summary>
/// <value> The margin size, in twips; default is 1440 (one inch). </value>
public int MarginBottom
get { return _MarginBottom; }
set { _MarginBottom = value; }

/// <summary> Gets or sets the left margin for the document. </summary>
/// <value> The margin size, in twips; default is 1440 (one inch). </value>
public int MarginLeft
get { return _MarginLeft; }
set { _MarginLeft = value; }

/// <summary> Gets or sets the right margin for the document. </summary>
/// <value> The margin size, in twips; default is 1440 (one inch). </value>
public int MarginRight
get { return _MarginRight; }
set { _MarginRight = value; }

Note that it includes the changes suugested by PSU Steve. Also note that the changes should be inserted after the color table property. When this is done, the following is inserted into the RTF document after the color table definition, assuming you have set IsLandscape to true:
{\colortbl ;\red0\green0\blue0;}

I don't know if there is a more elegant way to insert the \landscape code, but the above seems to work. Note that the defaults selected for the paper size are based on the A4 dimensions since this is the default for Australia.

Hope others find this useful.

John. Smile | :)
GeneralRe: Paper size and landscape additions Pin
Dima Popov16-Aug-12 20:25
memberDima Popov16-Aug-12 20:25 
GeneralRe: Paper size and landscape additions Pin
JWhattam19-Aug-12 14:14
memberJWhattam19-Aug-12 14:14 
QuestionImages in table cells Pin
JWhattam4-Apr-12 19:41
memberJWhattam4-Apr-12 19:41 
AnswerRe: Images in table cells Pin
Dima Popov4-Apr-12 20:10
memberDima Popov4-Apr-12 20:10 
GeneralRe: Images in table cells Pin
JWhattam9-Apr-12 16:53
memberJWhattam9-Apr-12 16:53 
QuestionRe: Images in table cells Pin
JWhattam6-Jun-12 19:46
memberJWhattam6-Jun-12 19:46 
AnswerRe: Images in table cells Pin
Dima Popov6-Jun-12 21:36
memberDima Popov6-Jun-12 21:36 
QuestionRe: Images in table cells Pin
JWhattam7-Jun-12 12:16
memberJWhattam7-Jun-12 12:16 
BugRe: Images in table cells Pin
Dima Popov7-Jun-12 21:21
memberDima Popov7-Jun-12 21:21 
GeneralRe: Images in table cells Pin
JWhattam11-Jun-12 13:18
memberJWhattam11-Jun-12 13:18 
GeneralRe: Images in table cells Pin
Dima Popov11-Jun-12 13:45
memberDima Popov11-Jun-12 13:45 
GeneralMy vote of 5 Pin
JWhattam20-Feb-12 12:39
memberJWhattam20-Feb-12 12:39 
GeneralRe: My vote of 5 Pin
Dima Popov15-Mar-12 22:18
memberDima Popov15-Mar-12 22:18 
Questiongenerates large files Pin
FrankinstienCode14-Sep-11 11:27
memberFrankinstienCode14-Sep-11 11:27 
AnswerRe: generates large files Pin
Dima Popov14-Sep-11 20:24
memberDima Popov14-Sep-11 20:24 
QuestionAppending RTF Pin
Andreas Freeman1-Jul-11 16:58
memberAndreas Freeman1-Jul-11 16:58 
AnswerRe: Appending RTF Pin
Dima Popov1-Jul-11 20:35
memberDima Popov1-Jul-11 20:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Posted 30 Jul 2010

Tagged as


104 bookmarked