RTF是一种非常流行的文件结构,很多文字编辑器都支持它,VB等开发工具甚至还提供了Richtxtbox的控件。编写通用工具的程序员应该考虑在自己的软件中加入读写RTF文件的功能,这样就需要对RTF的结构有充分的了解。而现在最重要的信息发布手段莫过于WWW了,在编辑软件中提供RTF到HTML的转换也是程序员应该考虑的事情。尽管WORD中已经有这个功能,但不能因此就对您的顾客说:“先用我的程序存成RTF,然后再用WORD......”。
下面将对RTF文件结构分析及其应用进行讨论。
一、RTF文件结构分析
RTF的结构并不复杂,但内容繁多,本文不可能一一说明,只能从总体上讨论一下(如果想阅读详尽的RTF文档,则可在Internet上寻找或与笔者联系。)每个RTF文件都是一个文本文件,显示时由RTF阅读器格式化。文件开始处是{\rtf,它作为RTF文件的标志是必不可少的,RTF阅读器根据它来判断一个文件是否为RTF格式。然后是文件头和正文,文件头包括字体表、文件表、颜色表等几个数据结构,正文中的字体、表格的风格就是根据文件头的信息来格式化的。每个表用一对大括号括起来,当中包含了很多用字符“\”开始的命令。例如,某个颜色表如下:
{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}
开始时用\colortbl标明大括号内是颜色表,接着是\red0\green0\blue0,登记了一种颜色,这种颜色的红绿蓝分量都为0。其它表依此类推。文件头之后是正文,正文由版面格式化命令、文字和各种特殊命令组成。其中只有特殊命令用大括号括起来,而版面格式化命令和文字是“开放式”的,从而把文字和命令分离。文件结束时有一个“}”,和第一个“{”对应。在整个文件中,“}”和“{”必须一一对应。这种格式是RTF阅读器和转换器算法的基础。
RTF格式还有一个特别之处,就是有些字符在命令中有特殊的含义,所以当它们作为文本出现时需要在它们的前面加一个“\”,例如“\”本身就要表示为“\\”。事实上,这种形式在大多数编程语言中是很常见的。
二、算法分析
本节介绍的算法虽然是针对RTF的读写,但也是一般文件过滤器通用的方法,适用于各种格式文件之间的转换。具体来说就是把各种文件都转换成一种中间格式,再根据要求进行显示或转换。其中有个原则是一定要遵守的:程序必须能过滤掉不认识的格式。各种文件都有其特殊的格式,在转换过程中不可避免会出现格式损失的现象,在算法中要考虑这种情况。对于RTF这类格式化文本文件来说,最重要的是要正确地显示或转换文件的大小、颜色、字体等风格。因此,在程序中应该用一个数据结构把这些信息存起来,这个结构就是所谓的中间格式,怎样规定悉听尊便。以下是其流程图:
三、难点分析
在开发过程中我们遇到了不少问题,其中有两个问题特别有意思。
第一个问题是中文的表示方式。在RTF中中文用命令的形式表示:“\'内码”。内码就是汉字机内码。不过,请注意:RTF是文本文件,内码是用ASCII码来储存的,必须把它转换成数字才能使用。例如,“电子与电脑”在RTF中的形式是:
\'b5\'e7\'d7\'d3\'d3\'eb\'b5\'e7\'c4\'d4
第二个是图片的问题,这也是本文的重点。RTF中图片以两种方式存在:第一种方式是直接嵌入,以{\pict开始;第二种方式是作为OLE对象嵌入,这时以{\object开始。当RTF处理器能直接使用OLE时,RTF文件中提供了OLE的数据;否则,文件中直接提供图片的数据,以{\result开始。在使用中最常见的图片格式是内含DIB BITMAP的元文件(METAFILE),这种格式在SDK中没有说明,而且在RTF中是以压缩形式储存的,所以在转换时有一定困难。我们采取了一种比较新的方法:先把META FILE的数据读出来存成一个文件,然后用GetMetafile和PlayMetafile函数把文件中的图形打印在一个内存DC上,最后用抓图的方法把它存成BITMAP文件。以下是具体程序:
C代码
- void WmfToBmp(int width,int height,char *metafile)
- {
- //width为图片宽度,height为图片高度,*metafile为元文件名
- static HMETAFILE hmf;
- char num[4];
- FILE *fh;
- HDC hMemDC;
- HBITMAP hbmp,hold;
- BITMAPFILEHEADER hdr;
- LPBITMAPINFOHEADER lpbmpih;
- HGLOBAL hg;
- BitmapCount++;
- if(BitmapCount==3)
- {
- width=128;
- height=132;
- }
- if(BitmapCount==1)
- {
- width=80;
- height=50;
- }
- ToString10(BitmapCount,num);
- //把字符串转换成数字的函数
- lstrcpy(bmpFile,FilePath);
- lstrcat(bmpFile,num);
- lstrcpy(gifFile,bmpFile);
- lstrcat(bmpFile,".bmp");
- lstrcat(gifFile,".gif");
- hmf=GetMetaFile(metafile);
- hDisplayDC=CreateDC("DISPLAY",NULL,NULL,NULL);
- hMemDC=CreateCompatibleDC(hDisplayDC);
- hg=GlobalAlloc(GHND,sizeof(BITMAPINFOHEADER));
- lpbmpih=(LPBITMAPINFOHEADER)GlobalLock(hg);
- lpbmpih->biSize=sizeof(BITMAPINFOHEADER);
- lpbmpih->biWidth=width;
- lpbmpih->biHeight=height;
- lpbmpih->biPlanes=1;
- lpbmpih->biBitCount=8;
- lpbmpih->biCompression=0;
- hbmp=CreateCompatibleBitmap(hDisplayDC,width,height);
- GlobalUnlock(hg);
- GlobalFree(hg);
- hold=SelectObject(hMemDC,hbmp);
- SetMapMode(hMemDC,MM_ANISOTROPIC);
- SetWindowOrgEx(hMemDC,0,0,NULL);
- SetViewportExtEx(hMemDC,width,height,NULL);
- PlayMetaFile(hMemDC,hmf);
- hbmp=SelectObject(hMemDC,hold);
- if((fh=fopen(bmpFile,"w+b"))==NULL)
- {
- return;
- }
- hdr.bfType=0x4d42;
- hdr.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD)+width*height;
- hdr.bfReserved1=0;
- hdr.bfReserved2=0;
- hdr.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);
- fwrite((LPSTR)&hdr,sizeof(BITMAPFILEHEADER),1,fh);
- hg=GlobalAlloc(GHND,sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD));
- lpbmpih=(LPBITMAPINFOHEADER)GlobalLock(hg);
- lpbmpih->biSize=sizeof(BITMAPINFOHEADER);
- lpbmpih->biWidth=width;
- lpbmpih->biHeight=height;
- lpbmpih->biPlanes=1;
- lpbmpih->biBitCount=8;
- lpbmpih->biCompression=0;
- lpbmpih->biSizeImage=width*height;
- lpbmpih->biClrUsed=256;
- lpbmpih->biClrImportant=0;
- j=GetDIBits(hMemDC,hbmp,0,height,NULL,(BITMAPINFO*)lpbmpih,DIB_RGB_COLORS);
- lpbmpih->biSize=sizeof(BITMAPINFOHEADER);
- fwrite(lpbmpih,sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD),1,fh);
- GetDIBits(hMemDC,hbmp,0,height,(LPSTR)FileBuffer,(BITMAPINFO*)lpbmpih,DIB_RGB_COLS);
- fwrite(FileBuffer,width*height,1,fh);
- GlobalUnlock(hg);
- GlobalFree(hg);
- fclose(fh);
- DeleteDC(hMemDC);
- DeleteDC(hDisplayDC);
- DeleteObject(hbmp);
- DeleteObject(hold);
- DeleteMetaFile(hmf);
- }//end function WmfToBmp()
用这种方法还有一个好处,那就是当你从最底层开始开发RTF阅读器时,只需把PlayMetaFile的参数从内存DC换成屏幕DC就可以显示图片了。
四.RTF格式的扩展
最后讨论一下RTF格式的扩展。RTF格式作为一个标准应该是统一的,但在某种情况下进行扩展是必要的。最明显的例子是微软的WORD,它有自己独有的RTF命令。如果想使自己的软件在技术上占有优势,也可以通过创造新的RTF命令来实现。例如,如果你希望在软件中支持DHTML,则可在RTF中嵌入{\dhtml或{\java之类的命令。由于RTF阅读器有过滤不认识命令的功能,所以这样做不会影响RTF文件的通用性。
补充:
RTF文件格式说明:
1.\ansi Character Set Flag 采用的字符集名称
2.\b Character Formatting Properties Toggle 字符是粗体
3.\b0 Character Formatting Properties Toggle 字符是非粗体
4.\blueNNN Color Table Value 标识蓝色的rgb数值(0-255)
5.\brdrb Paragraph Borders Flag 段落边界底部
6.\brdrcfNNN Paragraph Borders Value 段落边界颜色
7.\brdrs Paragraph Borders Flag 段落边界单线边框
8.\brdrt Paragraph Borders Flag 段落边界顶部
9.\brdrth Paragraph Borders Flag 段落边界双线边框
10.\brdrwNNN Paragraph Borders Value 段落边界线的宽度(单位是堤)
11.\cfNNN Character Formatting Properties Value 字符前景色(0-255)
12.\chpgn Special Characters Symbol 当前页号
13.\colortbl Color Table Destination 颜色表
14.\deffNNN Font Table Value 默认字体(N是字体号)
15.\deftabNNN Document Formatting Properties Value 文档中默认tab宽度(单位是堤,默认值是720堤)
16.\emdash Special Characters Symbol 长说明号——(特殊符号)
17.\endash Special Characters Symbol 短说明号-(特殊符号)
18.\fNNN Character Formatting Properties Value 字符的字体(N是在字体表中的编号)
19.\fonttbl Font Table Destination 字体表
20.\fonttype fontname 字体类型,字体名称
21.\footer Headers and Footers Destination 所有页的页脚。
22.\footeryNNN Section Formatting Properties Value 片断中页脚和底部的距离(单位是堤,默认是720堤)
23.\fsNNN Character Formatting Properties Value 字符在半角状态下的字体大小(默认是24)
24.\greenNNN Color Table Value 标识绿色的rgb值(0-255)
25.\headeryNNN Section Formatting Properties Value 片断中标头和顶部的距离(单位是堤)
26.\i Character Formatting Properties Toggle 字符是斜体
27.\i0 Character Formatting Properties Toggle 字符是非斜体
28.\keepn Paragraph Formatting Properties Flag 段落与下段同页
29.\line Special Characters Symbol 分隔符中的一种:换行符(不是段落分隔符)
30.\li Paragraph Formatting Properties Value 段落的左缩进(默认是0)
31.\margbNNN Document Formatting Properties Value 文档底部空白的高度(单位是堤)
32.\marglNNN Document Formatting Properties Value 文档左部空白的宽度(单位是堤)
33.\margrNNN Document Formatting Properties Value 文档右部空白的宽度(单位是堤)
34.\margtNNN Document Formatting Properties Value 文档顶部空白的高度(单位是堤)
35.\' Special Characters Symbol ‘ 单撇符号
36.\page Special Characters Symbol 分隔符中的一种:分页符
37.\par Special Characters Symbol 新的段落标志
38.\pard Paragraph Formatting Properties Flag 段落中用于对脚注,注释,标头,页脚等特殊信息分成单独的组,用来针对每个组单独指定独立于文本内容的格式。
39.\plain Character Formatting Properties Flag 使字符格式恢复到应用指定的默认格式,相关的字符格式也恢复到应用指定的默认格式。
40.\qc Paragraph Formatting Properties Flag 段落格式居中
41.\qj Paragraph Formatting Properties Flag 段落两端对齐
42.\ql Paragraph Formatting Properties Flag 段落格式左对齐
43.\qr Paragraph Formatting Properties Flag 段落格式右对齐
44.\redNNN Color Table Value 标识红色的rgb值
45.\riNNN Paragraph Formatting Properties Value 段落格式的右缩进值(默认值为0)
46.\rtf RTF Version Destination RTF版本号
47.\slmultNNN Paragraph Formatting Properties Value 段落的多倍行间距0 “最小”或“严格”的行距1 多倍行间距(相对于单倍行间距)
48.\slN Paragraph Formatting Properties Value 段落的指定行间据可以调整或者默认为行的最大高度的字符的高度。
49.\tab Special Characters Symbol TAB字符
50.\titlepg Section Formatting Properties Flag 片断中有特殊格式的第一页
51.\tqr Tabs Flag 右部用TAB填充
52.\tx Tabs Value TAB位置,从左边空白处开始(而不是从左边页边缘开始,单位是堤)
53.\ul Character Formatting Properties Toggle 字符具有连续的下划线(\ul0关闭连续的下划线)
54.\widowctrl Document Formatting Properties Flag 文档的孤行控制