RTF文档格式是微软提出的一种用于描述带格式文本的文档格式,上个世纪就提出来了,一直用到现在,而且很多程序都支持这种格式,微软的Office软件家族,Windows写字板软件等等都支持,而且Windows操作系统的剪切板和OLE拖拽操作也支持RTF文档,这样就允许不同的软件通过RTF格式相互交流带格式文本。比如我用的VS.NET2003中的C#代码编辑器,在其中复制了一段代码文本,在MSWord中粘贴所得就是具有高亮度显示的文本。因此RTF格式的作用还是不小的,而且RTF格式是纯文本格式,不是二进制格式,读写都不算难。

RTF文档格式和HTML,XML之类的标记语言有点类似,原理不复杂,但内容还是比较多的,在微软的MSND中就有文章详细的介绍了RTF格式,地址是ms-help://MS.MSDNQTR.2003FEB.2052/dnrtfspec/html/rtfspec.htm,你用记事本打开一个RTF文档,可以发现其中也是纯文本数据,而且一般全是ANSI字符,RTF文档一般采用ASNI字符编码格式进行存储,其中是不能直接保存汉字等编码大于127的字符,要保存得使用转义字符。RTF文档中使用一对花括号"{}"来定义一个组,组可以套嵌定义;用""来开始定义一个指令和转义字符;此外还能包含纯文本数据。所有的指令和转义字符都必须包含在一个组中,一个RTF文档只有一个根组,这点有点类似XML文档只能有一个根节点的规定。

 

我们使用Windows写字板新建一个RTF文档,只输入"Hellow"文本,设置文本颜色为蓝色,然后保存,然后使用记事本打开刚刚保存的RTF文件,此时就能看到一个最简单的RTF文档的内容了,其内容如下。

{     
 tf1ansiansicpg936deff0deflang1033deflangfe2052      
 {fonttbl      
 {f0fmodernfprq6fcharset134'cb'ce'cc'e5;}      
 }      
 {colortbl; ed0green0lue255;}      
 {*generatorMsftedit5.41.15.1507;}      
 viewkind4uc1pardcf1lang2052f0fs20Hellowcf0par      
 }

此处为了便于阅读,对代码进行了缩进处理,实际上RTF文档中空白字符是会影响到显示结果的,一般实际生成RTF文档时不要添加额外的空白字符。

这段RTF代码第一行和最后一行是表示根组的花括号,然后是""开头的指令,指令名称全部由英文字母组成,若指令后面跟着若干个数字,则这些数字就是指令的参数。比如" tf1",这个指令名称是"rtf",参数值是"1";而"ansi"指令名称是"ansi",没有参数。

指令" tf"是每个RTF文档必备的,而且总是第一个指令,因此可以看作RTF文档的文件头标记。若一个RTF文档第一个指令不是"rtf"指令,则可以认为这个RTF文档是不合法的。

指令"ansicpg"就是说明该RTF文档的内容的编码格式,参数就是编码格式编号,例如"ansicpg936"就是指明编码格式为936号字符集,对于C#程序来说,就是库函数System.Text.Encoding.GetEncoding(936)的返回结果,也就是GB2312编码格式。RTF文档本身肯定是使用标准的ANSI格式保存的,此处指明的字符编码格式是用于处理RTF文档中的转义字符的,比如代码中由连续的转义字符'cb'ce'cc'e5,程序解析RTF文档时,应当将这一串转义字符生成一个字节数组,内容为0xcb,0xce,0xcc,0xe5,然后使用第936号编码格式对象的GetString(byte[])函数来还原所存储的字符串,也就是"宋体"两个字。这点比HTML的转义字符处理要麻烦一些,HTML转义字符是一个指令定义一个字符,而RTF中的是一个指令定义一个字节,而汉字是双字节的字符编码,转化前还得设法获得完整的字节序列。

指令"fonttbl"定义了文档中使用的所有的字体的列表,RTF文本内容引用这个字体列表来获得显示文档使用的字体,这和HTML文档中统一定义CSS样式有点类似。"fonttbl"组中由若干个子组,每个子组定义一个字体,字体定义组的第一个指令为"f",带有一个参数指明字体的编号,比如"f0"指明这个字体编号为0,"f1"指明字体编号为1。字体定义组还定义了关于字体的其他信息,其中最重要的就是最后的字体名称了。此演示文档中,字体的名称就是"'cb'ce'cc'e5;",经过编码后就是"宋体;",小心后面还有个分号。注意字体编号可能是不连续的,比如可以存在这样的字体表代码"{f0...}{f1...}{f99...}{f212...}",因此解析RTF字体表时要考虑这点。

指令"colortbl"定义了文档颜色表,RTF文档是统一引用颜色值的,文档内容的文本颜色,背景色等颜色设置都是引用颜色表的,RTF颜色表中只定义了各个颜色的RGB值,没有明确的定义编号,引用时是按照从左到右的顺序引用颜色的,而且颜色值的编号是从“1”开始计算的。此处定义了一个颜色值" ed0green0lue255",也就是纯蓝色。

指令"*generator"是定义了文档的创建者,此处定义指令的方式比较特殊,采用了"*"前缀,个人理解是定义了一种扩展指令,其他的RTF文档处理程序遇到这样的指令可以忽略不计。

后面的指令就是开始描述RTF文档的正文了,比如"pard"开始清除当前段落设置,当前段落设置为默认格式;"f0"表示设置当前字体为字体表中编号为"0"的字体;"fs20"设置字体大小,此处的字体大小为"20",单位是半个点(MSND是这样说的:Fontsizeinhalf-points(thedefaultis24));"cf1"表示当前文本颜色采用第一号颜色,即纯蓝色(RTF颜色表序号从1开始计算);还有纯文本数据"Hellow"就是RTF文档的纯文本内容了。

 

对于英文内容,大部分是可以直接输出到RTF文档中,但对于某些特殊字符需要进行字符转义,比如"","{","}"等等,前面得加上转义前缀"",因此实际上输出的是"","{","}",这类似C语言的转义字符处理。对于制表符,得输出" ab",对于编码大于256的字符,例如汉字,得使用文本内容编码器来编码生成二进制数据,然后使用转义前缀"'"来转义输出一个个字节编码。比如“宋体”,它的GB2312编码生成字节序列0xcb,0xce,0xcc,0xe5,它输出到RTF文档的结果就是“'cb'ce'cc'e5”。

RTF文档中可以嵌入图片,可以使用代码"{pict...}",图片组中包含了图片的二进制数据的16进制编码字符串,MSDN中关于RTF图片格式的说明不多,我对一些图片数据的格式也不清楚,因此如何处理RTF图片也没多少可说的。

关于各种指令的详细说明可参考MSDN中的相关文章,文章地址"ms-help://MS.MSDNQTR.2003FEB.2052/dnrtfspec/html/rtfspec_16.htm#rtfspec_21"。

我们对RTF文档格式有所了解后,就可以开始编程来操作RTF文档了,无非就是按照RTF格式来拼凑字符串而已。比如我的文本编辑器有个功能,能将编辑的内容保存为RTF格式,这时候就需要根据我的文档内容来生成RTF文档。

首先是做一个RTF文档书写器,虽然生成RTF文档的操作可以看作拼凑RTF字符串,但在编程实践中不能真的这么拼凑,得仿造System.Xml.XmlWriter来做一个RTF文档书写器,我编了个名为RTFWriter的RTF文档书写器,该书写器内部实现了基础的RTF文档格式的控制,能保证输出正确的RTF文档,它还提供了比较方便的编程接口,便于其他程序模块调用。这个RTF文档书写器完整的C#代码如下

 

对于英文内容,大部分是可以直接输出到RTF文档中,但对于某些特殊字符需要进行字符转义,比如"","{","}"等等,前面得加上转义前缀"",因此实际上输出的是"","{","}",这类似C语言的转义字符处理。对于制表符,得输出" ab",对于编码大于256的字符,例如汉字,得使用文本内容编码器来编码生成二进制数据,然后使用转义前缀"'"来转义输出一个个字节编码。比如“宋体”,它的GB2312编码生成字节序列0xcb,0xce,0xcc,0xe5,它输出到RTF文档的结果就是“'cb'ce'cc'e5”。

RTF文档中可以嵌入图片,可以使用代码"{pict...}",图片组中包含了图片的二进制数据的16进制编码字符串,MSDN中关于RTF图片格式的说明不多,我对一些图片数据的格式也不清楚,因此如何处理RTF图片也没多少可说的。

关于各种指令的详细说明可参考MSDN中的相关文章,文章地址"ms-help://MS.MSDNQTR.2003FEB.2052/dnrtfspec/html/rtfspec_16.htm#rtfspec_21"。

我们对RTF文档格式有所了解后,就可以开始编程来操作RTF文档了,无非就是按照RTF格式来拼凑字符串而已。比如我的文本编辑器有个功能,能将编辑的内容保存为RTF格式,这时候就需要根据我的文档内容来生成RTF文档。

首先是做一个RTF文档书写器,虽然生成RTF文档的操作可以看作拼凑RTF字符串,但在编程实践中不能真的这么拼凑,得仿造System.Xml.XmlWriter来做一个RTF文档书写器,我编了个名为RTFWriter的RTF文档书写器,该书写器内部实现了基础的RTF文档格式的控制,能保证输出正确的RTF文档,它还提供了比较方便的编程接口,便于其他程序模块调用。这个RTF文档书写器完整的C#代码如下

 

///<summary>
///RTF文档书写器
///</summary>
///<remarks>
///本书写器对生成RTF文档提供了基础的支持
///编制袁永福http://www.xdesigner.cn
///</remarks>
publicclassRTFWriter:System.IDisposable
{
#region测试代码******************************************************
[System.STAThread]
staticvoidMain()
{
TestWriteFile();
TestClipboard();
}
///<summary>
///测试生成RTF文件
///执行这个函数后可以使用MSWord打开文件c:a.rtf
///</summary>
internalstaticvoidTestWriteFile()
{
RTFWriterw=newRTFWriter("c:a.rtf");
TestBuildRTF(w);
w.Close();
System.Windows.Forms.MessageBox.Show("好了,你可以打开文件c:a.rtf了.");
}
///<summary>
///测试生成RTF文档并设置到系统剪切板中
///执行这个函数后就可以在MSWord中使用粘贴操作来显示程序生成的文档了
///</summary>
internalstaticvoidTestClipboard()
{
System.IO.StringWritermyStr=newSystem.IO.StringWriter();
RTFWriterw=newRTFWriter(myStr);
TestBuildRTF(w);
w.Close();
System.Windows.Forms.DataObjectdata=newSystem.Windows.Forms.DataObject();
data.SetData(System.Windows.Forms.DataFormats.Rtf,myStr.ToString());
System.Windows.Forms.Clipboard.SetDataObject(data,true);
System.Windows.Forms.MessageBox.Show("好了,你可以在MSWord中粘贴文本了.");
}
///<summary>
///测试生成RTF文档
///</summary>
///<paramname="w">RTF文档书写器</param>
privatestaticvoidTestBuildRTF(RTFWriterw)
{
w.Encoding=System.Text.Encoding.GetEncoding(936);
//输出文件头
w.WriteStartGroup();
w.WriteKeyword("rtf1");
w.WriteKeyword("ansi");
w.WriteKeyword("ansicpg"+w.Encoding.CodePage);
//输出字体表
w.WriteStartGroup();
w.WriteKeyword("fonttbl");
w.WriteStartGroup();
w.WriteKeyword("f0");
w.WriteText("隶书;");
w.WriteEndGroup();
w.WriteStartGroup();
w.WriteKeyword("f1");
w.WriteText("宋体;");
w.WriteEndGroup();
w.WriteEndGroup();
//输出颜色表
w.WriteStartGroup();
w.WriteKeyword("colortbl");
w.WriteText(";");
w.WriteKeyword("red0");
w.WriteKeyword("green0");
w.WriteKeyword("blue255");
w.WriteText(";");
w.WriteEndGroup();
//输出正文
w.WriteKeyword("qc"); //设置居中对齐
w.WriteKeyword("f0"); //设置字体
w.WriteKeyword("fs30"); //字体大小
w.WriteText("这是第一段文本");
w.WriteKeyword("cf1"); //设置颜色
w.WriteText("隶书");
w.WriteKeyword("cf0"); //设置为默认颜色
w.WriteKeyword("f1"); //设置字体
w.WriteText("居中对齐ABC12345");
w.WriteKeyword("par"); //开始新的段落
w.WriteKeyword("pard"); //清除居中对齐
w.WriteKeyword("f1"); //设置字体
w.WriteKeyword("fs20"); //字体大小
w.WriteKeyword("cf1");
w.WriteText("这是第二段文本宋体左对齐ABC12345");
//结束输出
w.WriteEndGroup();
}
#endregion
///<summary>
///初始化对象
///</summary>
///<paramname="w">文本书写器</param>
publicRTFWriter(System.IO.TextWriterw)
{
myWriter=w;
}
///<summary>
///初始化对象
///</summary>
///<paramname="strFileName">文件名</param>
publicRTFWriter(stringstrFileName)
{
myWriter=newSystem.IO.StreamWriter(
strFileName,
false,
System.Text.Encoding.ASCII);
}
privateSystem.Text.EncodingmyEncoding=System.Text.Encoding.GetEncoding(936);
///<summary>
///字符编码格式
///</summary>
publicSystem.Text.EncodingEncoding
{
get{returnmyEncoding;}
set{myEncoding=value;}
}
///<summary>
///内置的文本书写器
///</summary>
privateSystem.IO.TextWritermyWriter=null;
privateboolbolIndent=false;
///<summary>
///是否使用缩进
///</summary>
///<remarks>
///RTF文档内部不能随便缩进,提供此选项只是用于生成便于阅读的RTF文档,便于程序的调试,
///在开发调试中可以设置该属性为true,方便开发者能直接查看生成的RTF文档,但在生成最终运行的
///程序时应当设置该属性为false.
///</remarks>
publicboolIndent
{
get{returnbolIndent;}
set{bolIndent=value;}
}
privatestringstrIndentString=" ";
///<summary>
///缩进字符串
///</summary>
publicstringIndentString
{
get{returnstrIndentString;}
set{strIndentString=value;}
}
///<summary>
///当前缩进层次
///</summary>
privateintintGroupLevel=0;
///<summary>
///关闭对象
///</summary>
publicvoidClose()
{
if(this.intGroupLevel>0)
thrownewSystem.Exception("还有组未写完");
if(myWriter!=null)
{
myWriter.Close();
myWriter=null;
}
}
///<summary>
///输出一个组
///</summary>
///<paramname="KeyWord">关键字</param>
publicvoidWriteGroup(stringKeyWord)
{
this.WriteStartGroup();
this.WriteKeyword(KeyWord);
this.WriteEndGroup();
}
///<summary>
///开始输出组
///</summary>
publicvoidWriteStartGroup()
{
if(bolIndent)
{
InnerWriteNewLine();
myWriter.Write("{");
}
else
myWriter.Write("{");
intGroupLevel++;
}
///<summary>
///结束输出组
///</summary>
publicvoidWriteEndGroup()
{
intGroupLevel--;
if(intGroupLevel<0)
thrownewSystem.Exception("组不匹配");
if(bolIndent)
{
InnerWriteNewLine();
InnerWrite("}");
}
else
InnerWrite("}");
}
///<summary>
///输出原始文本
///</summary>
///<paramname="txt">文本值</param>
publicvoidWriteRaw(stringtxt)
{
if(txt!=null&&txt.Length>0)
{
InnerWrite(txt);
}
}
///<summary>
///输出关键字
///</summary>
///<paramname="Keyword">关键字值</param>
publicvoidWriteKeyword(stringKeyword)
{
WriteKeyword(Keyword,false);
}
///<summary>
///输出关键字
///</summary>
///<paramname="Keyword">关键字值</param>
///<paramname="Ext">是否是扩展关键字</param>
publicvoidWriteKeyword(stringKeyword,boolExt)
{
if(Keyword==null||Keyword.Length==0)
thrownewSystem.ArgumentNullException("值不得为空");
if(bolIndent==false&&(Keyword=="par"||Keyword=="pard"))
{
//par或pard前可以输出空白行,不影响RTF文档显示
InnerWrite(System.Environment.NewLine);
}
if(this.bolIndent)
{
if(Keyword=="par"||Keyword=="pard")
{
this.InnerWriteNewLine();
}
}
if(Ext)
InnerWrite("*");
else
InnerWrite("");
InnerWrite(Keyword);
}
///<summary>
///内容文本编码格式
///</summary>
privateSystem.Text.EncodingUnicode=System.Text.Encoding.Unicode;
///<summary>
///输出纯文本
///</summary>
///<paramname="Text">文本值</param>
publicvoidWriteText(stringText)
{
if(Text==null||Text.Length==0)
return;
InnerWrite('');
for(intiCount=0;iCount<Text.Length;iCount++)
{
charc=Text[iCount];
if(c==' ')
{
this.WriteKeyword("tab");
InnerWrite('');
}
elseif(c<256)
{
if(c>32&&c<127)
{
//出现特殊字符,需要斜线转义
if(c==''||c=='{'||c=='}')
InnerWrite('');
InnerWrite(c);
}
else
{
InnerWrite("'");
WriteByte((byte)c);
}
}
else
{
byte[]bs=myEncoding.GetBytes(c.ToString());
for(intiCount2=0;iCount2<bs.Length;iCount2++)
{
InnerWrite("'");
WriteByte(bs[iCount2]);
}
}
}//for(intiCount=0;iCount<Text.Length;iCount++)
}
///<summary>
///当前位置
///</summary>
privateintintPosition=0;
///<summary>
///当前行的位置
///</summary>
privateintintLineHead=0;
///<summary>
///16进制字符组
///</summary>
privateconststringHexs="0123456789abcdef";
///<summary>
///输出字节数组
///</summary>
///<paramname="bs">字节数组</param>
publicvoidWriteBytes(byte[]bs)
{
if(bs==null||bs.Length==0)
return;
WriteRaw("");
for(intiCount=0;iCount<bs.Length;iCount++)
{
if((iCount%32)==0)
{
this.WriteRaw(System.Environment.NewLine);
this.WriteIndent();
}
elseif((iCount%8)==0)
{
this.WriteRaw("");
}
byteb=bs[iCount];
inth=(b&0xf0)>>4 ;
intl=b&0xf;
myWriter.Write(Hexs[h]);
myWriter.Write(Hexs[l]);
intPosition+=2;
}
}
///<summary>
///输出一个字节数据
///</summary>
///<paramname="b">字节数据</param>
publicvoidWriteByte(byteb)
{
inth=(b&0xf0)>>4;
intl=b&0xf;
myWriter.Write(Hexs[h]);
myWriter.Write(Hexs[l]);
intPosition+=2;
//FixIndent();
}
#region内部成员******************************************************
privatevoidInnerWrite(charc)
{
intPosition++;
myWriter.Write(c);
}
privatevoidInnerWrite(stringtxt)
{
intPosition+=txt.Length;
myWriter.Write(txt);
}
privatevoidFixIndent()
{
if(this.bolIndent)
{
if(intPosition-intLineHead>100)
InnerWriteNewLine();
}
}
privatevoidInnerWriteNewLine()
{
if(this.bolIndent)
{
if(intPosition>0)
{
InnerWrite(System.Environment.NewLine);
intLineHead=intPosition;
WriteIndent();
}
}
}
privatevoidWriteIndent()
{
if(bolIndent)
{
for(intiCount=0;iCount<intGroupLevel;iCount++)
{
InnerWrite(this.strIndentString);
}
}
}
#endregion
///<summary>
///销毁对象
///</summary>
publicvoidDispose()
{
this.Close();
}
}

你使用VS.NET新建一个C#工程项目后,删除自动生成的Main()函数,然后复制并粘贴这段代码,这样就可以编译运行了。

在这个RTFWriter的基础上,你可以构造自己的RTF应用了,比如将数据库的数据导出到RTF文档中,使用RTF格式向其他程序传递数据。笔者正在开发的XWriter文本编辑器也使用RTFWriter将编辑的文档保存为RTF格式,而且实际上这篇文章是完全使用XWriter编辑的,然后导出为HTML格式,没有使用MSWord,FrontPage等其他文档编辑器,本文中的代码是在VS.NET的C#代码编辑器中直接复制-粘贴而得。

本文只是对操作RTF文档提供了一些比较简单的说明,详细内容可以参考MSDN中关于RTF的说明,网络上的资源更是多如牛毛。RTF文档格式原理简单,但内容却不少,它是一种很古老的技术,却一直到现在还在广泛的使用,而且估计还能用上很长一段时期。其实我们在学习不断出现的新技术的时候,也可以注意那些古老的但经过时间考验的技术。