1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
23 // Peter Bartok (pbartok@novell.com)
31 using System.Collections;
35 namespace System.Windows.Forms.RTF {
37 #region Local Variables
38 internal const char EOF = unchecked((char)-1);
39 internal const int NoParam = -1000000;
41 private TokenClass rtf_class;
45 private string encoded_text;
46 private Encoding encoding;
47 private int encoding_code_page = 1252;
48 private StringBuilder text_buffer;
49 private Picture picture;
53 private char pushed_char;
54 //private StringBuilder pushed_text_buffer;
55 private TokenClass pushed_class;
56 private Major pushed_major;
57 private Minor pushed_minor;
58 private int pushed_param;
60 private char prev_char;
61 private bool bump_line;
63 private Font font_list;
65 private Charset cur_charset;
66 private Stack charset_stack;
72 private StreamReader source;
74 private static Hashtable key_table;
75 private static KeyStruct[] Keys = KeysInit.Init();
77 private DestinationCallback destination_callbacks;
78 private ClassCallback class_callbacks;
79 #endregion // Local Variables
83 key_table = new Hashtable(Keys.Length);
84 for (int i = 0; i < Keys.Length; i++) {
85 key_table[Keys[i].Symbol] = Keys[i];
89 public RTF(Stream stream) {
90 source = new StreamReader(stream);
92 text_buffer = new StringBuilder(1024);
93 //pushed_text_buffer = new StringBuilder(1024);
95 rtf_class = TokenClass.None;
96 pushed_class = TokenClass.None;
97 pushed_char = unchecked((char)-1);
101 prev_char = unchecked((char)-1);
104 cur_charset = new Charset();
106 destination_callbacks = new DestinationCallback();
107 class_callbacks = new ClassCallback();
109 destination_callbacks [Minor.OptDest] = new DestinationDelegate (HandleOptDest);
110 destination_callbacks[Minor.FontTbl] = new DestinationDelegate(ReadFontTbl);
111 destination_callbacks[Minor.ColorTbl] = new DestinationDelegate(ReadColorTbl);
112 destination_callbacks[Minor.StyleSheet] = new DestinationDelegate(ReadStyleSheet);
113 destination_callbacks[Minor.Info] = new DestinationDelegate(ReadInfoGroup);
114 destination_callbacks[Minor.Pict] = new DestinationDelegate(ReadPictGroup);
115 destination_callbacks[Minor.Object] = new DestinationDelegate(ReadObjGroup);
117 #endregion // Constructors
120 public TokenClass TokenClass {
122 return this.rtf_class;
126 this.rtf_class = value;
162 return this.text_buffer.ToString();
167 this.text_buffer.Length = 0;
169 this.text_buffer = new StringBuilder(value);
174 public string EncodedText {
175 get { return encoded_text; }
178 public Picture Picture {
179 get { return picture; }
180 set { picture = value; }
183 public Color Colors {
193 public Style Styles {
213 public ClassCallback ClassCallback {
215 return class_callbacks;
219 class_callbacks = value;
223 public DestinationCallback DestinationCallback {
225 return destination_callbacks;
229 destination_callbacks = value;
233 public int LineNumber {
244 #endregion // Properties
247 /// <summary>Set the default font for documents without font table</summary>
248 public void DefaultFont(string name) {
251 font = new Font(this);
256 /// <summary>Read the next character from the input - skip any crlf</summary>
257 private char GetChar ()
259 return GetChar (true);
262 /// <summary>Read the next character from the input</summary>
263 private char GetChar(bool skipCrLf)
269 if ((c = source.Read()) != -1) {
270 this.text_buffer.Append((char) c);
273 if (this.prev_char == EOF) {
274 this.bump_line = true;
277 old_bump_line = bump_line;
283 text_buffer.Length--;
285 } else if (c == '\n') {
287 if (this.prev_char == '\r') {
288 old_bump_line = false;
291 text_buffer.Length--;
302 this.prev_char = (char) c;
306 /// <summary>Parse the RTF stream</summary>
308 while (GetToken() != TokenClass.EOF) {
313 /// <summary>Route a token</summary>
314 public void RouteToken() {
316 if (CheckCM(TokenClass.Control, Major.Destination)) {
317 DestinationDelegate d;
319 d = destination_callbacks[minor];
325 // Invoke class callback if there is one
328 c = class_callbacks[rtf_class];
335 /// <summary>Skip to the end of the next group to start or current group we are in</summary>
336 public void SkipGroup() {
341 while (GetToken() != TokenClass.EOF) {
342 if (rtf_class == TokenClass.Group) {
343 if (this.major == Major.BeginGroup) {
345 } else if (this.major == Major.EndGroup) {
355 /// <summary>Return the next token in the stream</summary>
356 public TokenClass GetToken() {
357 if (pushed_class != TokenClass.None) {
358 this.rtf_class = this.pushed_class;
359 this.major = this.pushed_major;
360 this.minor = this.pushed_minor;
361 this.param = this.pushed_param;
362 this.pushed_class = TokenClass.None;
363 return this.rtf_class;
368 if (this.rtf_class == TokenClass.Text) {
369 this.minor = (Minor)this.cur_charset[(int)this.major];
370 if (encoding == null) {
371 encoding = Encoding.GetEncoding (encoding_code_page);
373 encoded_text = new String (encoding.GetChars (new byte [] { (byte) this.major }));
376 if (this.cur_charset.Flags == CharsetFlags.None) {
377 return this.rtf_class;
380 if (CheckCMM (TokenClass.Control, Major.Unicode, Minor.UnicodeAnsiCodepage)) {
381 encoding_code_page = param;
384 if (((this.cur_charset.Flags & CharsetFlags.Read) != 0) && CheckCM(TokenClass.Control, Major.CharSet)) {
385 this.cur_charset.ReadMap();
386 } else if (((this.cur_charset.Flags & CharsetFlags.Switch) != 0) && CheckCMM(TokenClass.Control, Major.CharAttr, Minor.FontNum)) {
389 fp = Font.GetFont(this.font_list, this.param);
392 if (fp.Name.StartsWith("Symbol")) {
393 this.cur_charset.ID = CharsetType.Symbol;
395 this.cur_charset.ID = CharsetType.General;
397 } else if (((this.cur_charset.Flags & CharsetFlags.Switch) != 0) && (this.rtf_class == TokenClass.Group)) {
399 case Major.BeginGroup: {
400 this.charset_stack.Push(this.cur_charset);
404 case Major.EndGroup: {
405 this.cur_charset = (Charset)this.charset_stack.Pop();
412 return this.rtf_class;
415 private void GetToken2() {
419 this.rtf_class = TokenClass.Unknown;
420 this.param = NoParam;
422 this.text_buffer.Length = 0;
424 if (this.pushed_char != EOF) {
425 c = this.pushed_char;
426 this.text_buffer.Append(c);
427 this.pushed_char = EOF;
428 } else if ((c = GetChar()) == EOF) {
429 this.rtf_class = TokenClass.EOF;
434 this.rtf_class = TokenClass.Group;
435 this.major = Major.BeginGroup;
440 this.rtf_class = TokenClass.Group;
441 this.major = Major.EndGroup;
447 this.rtf_class = TokenClass.Text;
448 this.major = (Major)c; // FIXME - typing?
451 this.rtf_class = TokenClass.Control;
452 this.major = Major.SpecialChar;
453 this.minor = Minor.Tab;
458 if ((c = GetChar()) == EOF) {
463 if (!Char.IsLetter(c)) {
467 if ((c = GetChar()) == EOF) {
471 if ((c2 = GetChar()) == EOF) {
475 this.rtf_class = TokenClass.Text;
476 this.major = (Major)((Char)((Convert.ToByte(c.ToString(), 16) * 16 + Convert.ToByte(c2.ToString(), 16))));
481 if (c == ':' || c == '{' || c == '}' || c == '\\') {
482 this.rtf_class = TokenClass.Text;
483 this.major = (Major)c;
487 Lookup(this.text_buffer.ToString());
491 while (Char.IsLetter(c)) {
492 if ((c = GetChar(false)) == EOF) {
498 this.text_buffer.Length--;
501 Lookup(this.text_buffer.ToString());
504 this.text_buffer.Append(c);
513 if (c != EOF && Char.IsDigit(c) && minor != Minor.PngBlip) {
515 while (Char.IsDigit(c)) {
516 this.param = this.param * 10 + Convert.ToByte(c) - 48;
517 if ((c = GetChar()) == EOF) {
525 if (c != ' ' && c != '\r' && c != '\n') {
526 this.pushed_char = c;
528 this.text_buffer.Length--;
532 public void SetToken(TokenClass cl, Major maj, Minor min, int par, string text) {
537 if (par == NoParam) {
538 this.text_buffer = new StringBuilder(text);
540 this.text_buffer = new StringBuilder(text + par.ToString());
544 public void UngetToken() {
545 if (this.pushed_class != TokenClass.None) {
546 throw new RTFException(this, "Cannot unget more than one token");
549 if (this.rtf_class == TokenClass.None) {
550 throw new RTFException(this, "No token to unget");
553 this.pushed_class = this.rtf_class;
554 this.pushed_major = this.major;
555 this.pushed_minor = this.minor;
556 this.pushed_param = this.param;
557 //this.pushed_text_buffer = new StringBuilder(this.text_buffer.ToString());
560 public TokenClass PeekToken() {
566 public void Lookup(string token) {
570 obj = key_table[token.Substring(1)];
572 rtf_class = TokenClass.Unknown;
578 key = (KeyStruct)obj;
579 this.rtf_class = TokenClass.Control;
580 this.major = key.Major;
581 this.minor = key.Minor;
584 public bool CheckCM(TokenClass rtf_class, Major major) {
585 if ((this.rtf_class == rtf_class) && (this.major == major)) {
592 public bool CheckCMM(TokenClass rtf_class, Major major, Minor minor) {
593 if ((this.rtf_class == rtf_class) && (this.major == major) && (this.minor == minor)) {
600 public bool CheckMM(Major major, Minor minor) {
601 if ((this.major == major) && (this.minor == minor)) {
607 #endregion // Methods
609 #region Default Delegates
611 private void HandleOptDest (RTF rtf)
613 int group_levels = 1;
618 // Here is where we should handle recognised optional
621 // Handle a picture group
623 if (rtf.CheckCMM (TokenClass.Control, Major.Destination, Minor.Pict)) {
628 if (rtf.CheckCM (TokenClass.Group, Major.EndGroup)) {
629 if ((--group_levels) == 0) {
634 if (rtf.CheckCM (TokenClass.Group, Major.BeginGroup)) {
640 private void ReadFontTbl(RTF rtf) {
650 if (rtf.CheckCM(TokenClass.Group, Major.EndGroup)) {
655 if (rtf.CheckCMM(TokenClass.Control, Major.CharAttr, Minor.FontNum)) {
657 } else if (rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) {
660 throw new RTFException(rtf, "Cannot determine format");
665 if (!rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) {
666 throw new RTFException(rtf, "missing \"{\"");
671 font = new Font(rtf);
673 while ((rtf.rtf_class != TokenClass.EOF) && (!rtf.CheckCM(TokenClass.Text, (Major)';')) && (!rtf.CheckCM(TokenClass.Group, Major.EndGroup))) {
674 if (rtf.rtf_class == TokenClass.Control) {
676 case Major.FontFamily: {
677 font.Family = (int)rtf.minor;
681 case Major.CharAttr: {
683 case Minor.FontNum: {
684 font.Num = rtf.param;
690 Console.WriteLine("Got unhandled Control.CharAttr.Minor: " + rtf.minor);
698 case Major.FontAttr: {
700 case Minor.FontCharSet: {
701 font.Charset = (CharsetType)rtf.param;
705 case Minor.FontPitch: {
706 font.Pitch = rtf.param;
710 case Minor.FontCodePage: {
711 font.Codepage = rtf.param;
716 case Minor.FTypeTrueType: {
717 font.Type = rtf.param;
722 Console.WriteLine("Got unhandled Control.FontAttr.Minor: " + rtf.minor);
732 Console.WriteLine("ReadFontTbl: Unknown Control token " + rtf.major);
737 } else if (rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) {
739 } else if (rtf.rtf_class == TokenClass.Text) {
742 sb = new StringBuilder();
744 while ((rtf.rtf_class != TokenClass.EOF) && (!rtf.CheckCM(TokenClass.Text, (Major)';')) && (!rtf.CheckCM(TokenClass.Group, Major.EndGroup)) && (!rtf.CheckCM(TokenClass.Group, Major.BeginGroup))) {
745 sb.Append((char)rtf.major);
749 if (rtf.CheckCM(TokenClass.Group, Major.EndGroup)) {
753 font.Name = sb.ToString();
757 Console.WriteLine("ReadFontTbl: Unknown token " + rtf.text_buffer);
767 if (!rtf.CheckCM(TokenClass.Group, Major.EndGroup)) {
768 throw new RTFException(rtf, "Missing \"}\"");
774 throw new RTFException(rtf, "No font created");
777 if (font.Num == -1) {
778 throw new RTFException(rtf, "Missing font number");
784 private void ReadColorTbl(RTF rtf) {
793 if (rtf.CheckCM(TokenClass.Group, Major.EndGroup)) {
797 color = new Color(rtf);
800 while (rtf.CheckCM(TokenClass.Control, Major.ColorName)) {
803 color.Red = rtf.param;
808 color.Green = rtf.param;
813 color.Blue = rtf.param;
820 if (!rtf.CheckCM(TokenClass.Text, (Major)';')) {
821 throw new RTFException(rtf, "Malformed color entry");
827 private void ReadStyleSheet(RTF rtf) {
831 sb = new StringBuilder();
836 if (rtf.CheckCM(TokenClass.Group, Major.EndGroup)) {
840 style = new Style(rtf);
842 if (!rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) {
843 throw new RTFException(rtf, "Missing \"{\"");
849 if ((rtf.rtf_class == TokenClass.EOF) || rtf.CheckCM(TokenClass.Text, (Major)';')) {
853 if (rtf.rtf_class == TokenClass.Control) {
854 if (rtf.CheckMM(Major.ParAttr, Minor.StyleNum)) {
855 style.Num = rtf.param;
856 style.Type = StyleType.Paragraph;
859 if (rtf.CheckMM(Major.CharAttr, Minor.CharStyleNum)) {
860 style.Num = rtf.param;
861 style.Type = StyleType.Character;
864 if (rtf.CheckMM(Major.StyleAttr, Minor.SectStyleNum)) {
865 style.Num = rtf.param;
866 style.Type = StyleType.Section;
869 if (rtf.CheckMM(Major.StyleAttr, Minor.BasedOn)) {
870 style.BasedOn = rtf.param;
873 if (rtf.CheckMM(Major.StyleAttr, Minor.Additive)) {
874 style.Additive = true;
877 if (rtf.CheckMM(Major.StyleAttr, Minor.Next)) {
878 style.NextPar = rtf.param;
882 new StyleElement(style, rtf.rtf_class, rtf.major, rtf.minor, rtf.param, rtf.text_buffer.ToString());
883 } else if (rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) {
884 // This passes over "{\*\keycode ... }, among other things
886 } else if (rtf.rtf_class == TokenClass.Text) {
887 while (rtf.rtf_class == TokenClass.Text) {
888 if (rtf.major == (Major)';') {
893 sb.Append((char)rtf.major);
897 style.Name = sb.ToString();
900 Console.WriteLine("ReadStyleSheet: Ignored token " + rtf.text_buffer);
906 if (!rtf.CheckCM(TokenClass.Group, Major.EndGroup)) {
907 throw new RTFException(rtf, "Missing EndGroup (\"}\"");
911 if (style.Name == null) {
912 throw new RTFException(rtf, "Style must have name");
916 if (!sb.ToString().StartsWith("Normal") && !sb.ToString().StartsWith("Standard")) {
917 throw new RTFException(rtf, "Missing style number");
920 style.Num = Style.NormalStyleNum;
923 if (style.NextPar == -1) {
924 style.NextPar = style.Num;
931 private void ReadInfoGroup(RTF rtf) {
936 private void ReadPictGroup(RTF rtf)
938 bool read_image_data = false;
940 Picture picture = new Picture ();
944 if (rtf.CheckCM (TokenClass.Group, Major.EndGroup))
949 picture.ImageType = minor;
950 read_image_data = true;
952 case Minor.WinMetafile:
953 picture.ImageType = minor;
954 read_image_data = true;
960 case Minor.PicGoalWid:
961 picture.SetWidthFromTwips (param);
963 case Minor.PicGoalHt:
964 picture.SetHeightFromTwips (param);
968 if (read_image_data && rtf.rtf_class == TokenClass.Text) {
970 picture.Data.Seek (0, SeekOrigin.Begin);
972 //char c = (char) rtf.major;
976 char hexDigit1 = (char) rtf.major;
981 while (hexDigit1 == '\n' || hexDigit1 == '\r') {
982 hexDigit1 = (char) source.Peek ();
983 if (hexDigit1 == '}')
985 hexDigit1 = (char) source.Read ();
988 hexDigit2 = (char) source.Peek ();
989 if (hexDigit2 == '}')
991 hexDigit2 = (char) source.Read ();
992 while (hexDigit2 == '\n' || hexDigit2 == '\r') {
993 hexDigit2 = (char) source.Peek ();
994 if (hexDigit2 == '}')
996 hexDigit2 = (char) source.Read ();
999 if (Char.IsDigit (hexDigit1))
1000 digitValue1 = (uint) (hexDigit1 - '0');
1001 else if (Char.IsLower (hexDigit1))
1002 digitValue1 = (uint) (hexDigit1 - 'a' + 10);
1003 else if (Char.IsUpper (hexDigit1))
1004 digitValue1 = (uint) (hexDigit1 - 'A' + 10);
1005 else if (hexDigit1 == '\n' || hexDigit1 == '\r')
1010 if (Char.IsDigit (hexDigit2))
1011 digitValue2 = (uint) (hexDigit2 - '0');
1012 else if (Char.IsLower (hexDigit2))
1013 digitValue2 = (uint) (hexDigit2 - 'a' + 10);
1014 else if (Char.IsUpper (hexDigit2))
1015 digitValue2 = (uint) (hexDigit2 - 'A' + 10);
1016 else if (hexDigit2 == '\n' || hexDigit2 == '\r')
1021 picture.Data.WriteByte ((byte) checked (digitValue1 * 16 + digitValue2));
1023 // We get the first hex digit at the end, since in the very first
1024 // iteration we use rtf.major as the first hex digit
1025 hexDigit1 = (char) source.Peek ();
1026 if (hexDigit1 == '}')
1028 hexDigit1 = (char) source.Read ();
1032 read_image_data = false;
1037 if (picture.ImageType != Minor.Undefined && !read_image_data) {
1038 this.picture = picture;
1039 SetToken (TokenClass.Control, Major.PictAttr, picture.ImageType, 0, String.Empty);
1043 private void ReadObjGroup(RTF rtf) {
1047 #endregion // Default Delegates