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;
46 private StringBuilder text_buffer;
50 private char pushed_char;
51 private StringBuilder pushed_text_buffer;
52 private TokenClass pushed_class;
53 private Major pushed_major;
54 private Minor pushed_minor;
55 private int pushed_param;
57 private char prev_char;
58 private bool bump_line;
60 private Font font_list;
61 private Color color_list;
62 private Style style_list;
64 private Charset cur_charset;
65 private Stack charset_stack;
71 private StreamReader source;
73 private static Hashtable key_table;
74 private static KeyStruct[] Keys = KeysInit.Init();
76 private DestinationCallback destination_callbacks;
77 private ClassCallback class_callbacks;
78 #endregion // Local Variables
82 key_table = new Hashtable(Keys.Length);
83 for (int i = 0; i < Keys.Length; i++) {
84 key_table[Keys[i].Symbol] = Keys[i];
88 public RTF(Stream stream) {
89 source = new StreamReader(stream);
91 text_buffer = new StringBuilder(1024);
92 pushed_text_buffer = new StringBuilder(1024);
94 rtf_class = TokenClass.None;
95 pushed_class = TokenClass.None;
96 pushed_char = unchecked((char)-1);
100 prev_char = unchecked((char)-1);
103 cur_charset = new Charset();
105 destination_callbacks = new DestinationCallback();
106 class_callbacks = new ClassCallback();
108 destination_callbacks [Minor.OptDest] = new DestinationDelegate (HandleOptDest);
109 destination_callbacks[Minor.FontTbl] = new DestinationDelegate(ReadFontTbl);
110 destination_callbacks[Minor.ColorTbl] = new DestinationDelegate(ReadColorTbl);
111 destination_callbacks[Minor.StyleSheet] = new DestinationDelegate(ReadStyleSheet);
112 destination_callbacks[Minor.Info] = new DestinationDelegate(ReadInfoGroup);
113 destination_callbacks[Minor.Pict] = new DestinationDelegate(ReadPictGroup);
114 destination_callbacks[Minor.Object] = new DestinationDelegate(ReadObjGroup);
116 #endregion // Constructors
119 public TokenClass TokenClass {
121 return this.rtf_class;
125 this.rtf_class = value;
161 return this.text_buffer.ToString();
166 this.text_buffer.Length = 0;
168 this.text_buffer = new StringBuilder(value);
173 public Color Colors {
183 public Style Styles {
203 public ClassCallback ClassCallback {
205 return class_callbacks;
209 class_callbacks = value;
213 public DestinationCallback DestinationCallback {
215 return destination_callbacks;
219 destination_callbacks = value;
223 public int LineNumber {
234 #endregion // Properties
237 /// <summary>Set the default font for documents without font table</summary>
238 public void DefaultFont(string name) {
241 font = new Font(this);
246 /// <summary>Read the next character from the input</summary>
247 private char GetChar() {
252 if ((c = source.Read()) != -1) {
253 this.text_buffer.Append((char) c);
256 if (this.prev_char == EOF) {
257 this.bump_line = true;
260 old_bump_line = bump_line;
265 text_buffer.Length--;
267 } else if (c == '\n') {
269 if (this.prev_char == '\r') {
270 old_bump_line = false;
272 text_buffer.Length--;
282 this.prev_char = (char) c;
286 /// <summary>Parse the RTF stream</summary>
288 while (GetToken() != TokenClass.EOF) {
293 /// <summary>Route a token</summary>
294 public void RouteToken() {
295 if (CheckCM(TokenClass.Control, Major.Destination)) {
296 DestinationDelegate d;
298 d = destination_callbacks[minor];
304 // Invoke class callback if there is one
307 c = class_callbacks[rtf_class];
314 /// <summary>Skip to the end of the current group</summary>
315 public void SkipGroup() {
320 while (GetToken() != TokenClass.EOF) {
321 if (rtf_class == TokenClass.Group) {
322 if (this.major == Major.BeginGroup) {
324 } else if (this.major == Major.EndGroup) {
334 /// <summary>Return the next token in the stream</summary>
335 public TokenClass GetToken() {
336 if (pushed_class != TokenClass.None) {
337 this.rtf_class = this.pushed_class;
338 this.major = this.pushed_major;
339 this.minor = this.pushed_minor;
340 this.param = this.pushed_param;
341 this.pushed_class = TokenClass.None;
342 return this.rtf_class;
347 if (this.rtf_class == TokenClass.Text) {
348 this.minor = (Minor)this.cur_charset[(int)this.major];
351 if (this.cur_charset.Flags == CharsetFlags.None) {
352 return this.rtf_class;
355 if (((this.cur_charset.Flags & CharsetFlags.Read) != 0) && CheckCM(TokenClass.Control, Major.CharSet)) {
356 this.cur_charset.ReadMap();
357 } else if (((this.cur_charset.Flags & CharsetFlags.Switch) != 0) && CheckCMM(TokenClass.Control, Major.CharAttr, Minor.FontNum)) {
360 fp = Font.GetFont(this.font_list, this.param);
363 if (fp.Name.StartsWith("Symbol")) {
364 this.cur_charset.ID = CharsetType.Symbol;
366 this.cur_charset.ID = CharsetType.General;
368 } else if (((this.cur_charset.Flags & CharsetFlags.Switch) != 0) && (this.rtf_class == TokenClass.Group)) {
370 case Major.BeginGroup: {
371 this.charset_stack.Push(this.cur_charset);
375 case Major.EndGroup: {
376 this.cur_charset = (Charset)this.charset_stack.Pop();
383 return this.rtf_class;
386 private void GetToken2() {
390 this.rtf_class = TokenClass.Unknown;
391 this.param = NoParam;
393 this.text_buffer.Length = 0;
395 if (this.pushed_char != EOF) {
396 c = this.pushed_char;
397 this.text_buffer.Append(c);
398 this.pushed_char = EOF;
399 } else if ((c = GetChar()) == EOF) {
400 this.rtf_class = TokenClass.EOF;
405 this.rtf_class = TokenClass.Group;
406 this.major = Major.BeginGroup;
411 this.rtf_class = TokenClass.Group;
412 this.major = Major.EndGroup;
418 this.rtf_class = TokenClass.Text;
419 this.major = (Major)c; // FIXME - typing?
422 this.rtf_class = TokenClass.Control;
423 this.major = Major.SpecialChar;
424 this.minor = Minor.Tab;
429 if ((c = GetChar()) == EOF) {
434 if (!Char.IsLetter(c)) {
438 if ((c = GetChar()) == EOF) {
442 if ((c2 = GetChar()) == EOF) {
446 this.rtf_class = TokenClass.Text;
447 this.major = (Major)((Char)((Convert.ToByte(c.ToString(), 16) * 16 + Convert.ToByte(c2.ToString(), 16))));
452 if (c == ':' || c == '{' || c == '}' || c == '\\') {
453 this.rtf_class = TokenClass.Text;
454 this.major = (Major)c;
458 Lookup(this.text_buffer.ToString());
462 while (Char.IsLetter(c)) {
463 if ((c = GetChar()) == EOF) {
469 this.text_buffer.Length--;
472 Lookup(this.text_buffer.ToString());
475 this.text_buffer.Append(c);
484 if (c != EOF && Char.IsDigit(c)) {
486 while (Char.IsDigit(c)) {
487 this.param = this.param * 10 + Convert.ToByte(c) - 48;
488 if ((c = GetChar()) == EOF) {
497 this.pushed_char = c;
499 this.text_buffer.Length--;
503 public void SetToken(TokenClass cl, Major maj, Minor min, int par, string text) {
508 if (par == NoParam) {
509 this.text_buffer = new StringBuilder(text);
511 this.text_buffer = new StringBuilder(text + par.ToString());
515 public void UngetToken() {
516 if (this.pushed_class != TokenClass.None) {
517 throw new RTFException(this, "Cannot unget more than one token");
520 if (this.rtf_class == TokenClass.None) {
521 throw new RTFException(this, "No token to unget");
524 this.pushed_class = this.rtf_class;
525 this.pushed_major = this.major;
526 this.pushed_minor = this.minor;
527 this.pushed_param = this.param;
528 this.pushed_text_buffer = new StringBuilder(this.text_buffer.ToString());
531 public TokenClass PeekToken() {
537 public void Lookup(string token) {
541 obj = key_table[token.Substring(1)];
543 rtf_class = TokenClass.Unknown;
549 key = (KeyStruct)obj;
550 this.rtf_class = TokenClass.Control;
551 this.major = key.Major;
552 this.minor = key.Minor;
555 public bool CheckCM(TokenClass rtf_class, Major major) {
556 if ((this.rtf_class == rtf_class) && (this.major == major)) {
563 public bool CheckCMM(TokenClass rtf_class, Major major, Minor minor) {
564 if ((this.rtf_class == rtf_class) && (this.major == major) && (this.minor == minor)) {
571 public bool CheckMM(Major major, Minor minor) {
572 if ((this.major == major) && (this.minor == minor)) {
578 #endregion // Methods
580 #region Default Delegates
582 private void HandleOptDest (RTF rtf)
587 private void ReadFontTbl(RTF rtf) {
597 if (rtf.CheckCM(TokenClass.Group, Major.EndGroup)) {
602 if (rtf.CheckCMM(TokenClass.Control, Major.CharAttr, Minor.FontNum)) {
604 } else if (rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) {
607 throw new RTFException(rtf, "Cannot determine format");
612 if (!rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) {
613 throw new RTFException(rtf, "missing \"{\"");
618 font = new Font(rtf);
620 while ((rtf.rtf_class != TokenClass.EOF) && (!rtf.CheckCM(TokenClass.Text, (Major)';')) && (!rtf.CheckCM(TokenClass.Group, Major.EndGroup))) {
621 if (rtf.rtf_class == TokenClass.Control) {
623 case Major.FontFamily: {
624 font.Family = (int)rtf.minor;
628 case Major.CharAttr: {
630 case Minor.FontNum: {
631 font.Num = rtf.param;
637 Console.WriteLine("Got unhandled Control.CharAttr.Minor: " + rtf.minor);
645 case Major.FontAttr: {
647 case Minor.FontCharSet: {
648 font.Charset = (CharsetType)rtf.param;
652 case Minor.FontPitch: {
653 font.Pitch = rtf.param;
657 case Minor.FontCodePage: {
658 font.Codepage = rtf.param;
663 case Minor.FTypeTrueType: {
664 font.Type = rtf.param;
669 Console.WriteLine("Got unhandled Control.FontAttr.Minor: " + rtf.minor);
679 Console.WriteLine("ReadFontTbl: Unknown Control token " + rtf.major);
684 } else if (rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) {
686 } else if (rtf.rtf_class == TokenClass.Text) {
689 sb = new StringBuilder();
691 while ((rtf.rtf_class != TokenClass.EOF) && (!rtf.CheckCM(TokenClass.Text, (Major)';')) && (!rtf.CheckCM(TokenClass.Group, Major.EndGroup)) && (!rtf.CheckCM(TokenClass.Group, Major.BeginGroup))) {
692 sb.Append((char)rtf.major);
696 if (rtf.CheckCM(TokenClass.Group, Major.EndGroup)) {
700 font.Name = sb.ToString();
704 Console.WriteLine("ReadFontTbl: Unknown token " + rtf.text_buffer);
714 if (!rtf.CheckCM(TokenClass.Group, Major.EndGroup)) {
715 throw new RTFException(rtf, "Missing \"}\"");
721 throw new RTFException(rtf, "No font created");
724 if (font.Num == -1) {
725 throw new RTFException(rtf, "Missing font number");
731 private void ReadColorTbl(RTF rtf) {
740 if (rtf.CheckCM(TokenClass.Group, Major.EndGroup)) {
744 color = new Color(rtf);
747 while (rtf.CheckCM(TokenClass.Control, Major.ColorName)) {
750 color.Red = rtf.param;
755 color.Green = rtf.param;
760 color.Blue = rtf.param;
767 if (!rtf.CheckCM(TokenClass.Text, (Major)';')) {
768 throw new RTFException(rtf, "Malformed color entry");
774 private void ReadStyleSheet(RTF rtf) {
778 sb = new StringBuilder();
783 if (rtf.CheckCM(TokenClass.Group, Major.EndGroup)) {
787 style = new Style(rtf);
789 if (!rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) {
790 throw new RTFException(rtf, "Missing \"{\"");
796 if ((rtf.rtf_class == TokenClass.EOF) || rtf.CheckCM(TokenClass.Text, (Major)';')) {
800 if (rtf.rtf_class == TokenClass.Control) {
801 if (rtf.CheckMM(Major.ParAttr, Minor.StyleNum)) {
802 style.Num = rtf.param;
803 style.Type = StyleType.Paragraph;
806 if (rtf.CheckMM(Major.CharAttr, Minor.CharStyleNum)) {
807 style.Num = rtf.param;
808 style.Type = StyleType.Character;
811 if (rtf.CheckMM(Major.StyleAttr, Minor.SectStyleNum)) {
812 style.Num = rtf.param;
813 style.Type = StyleType.Section;
816 if (rtf.CheckMM(Major.StyleAttr, Minor.BasedOn)) {
817 style.BasedOn = rtf.param;
820 if (rtf.CheckMM(Major.StyleAttr, Minor.Additive)) {
821 style.Additive = true;
824 if (rtf.CheckMM(Major.StyleAttr, Minor.Next)) {
825 style.NextPar = rtf.param;
829 new StyleElement(style, rtf.rtf_class, rtf.major, rtf.minor, rtf.param, rtf.text_buffer.ToString());
830 } else if (rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) {
831 // This passes over "{\*\keycode ... }, among other things
833 } else if (rtf.rtf_class == TokenClass.Text) {
834 while (rtf.rtf_class == TokenClass.Text) {
835 if (rtf.major == (Major)';') {
840 sb.Append((char)rtf.major);
844 style.Name = sb.ToString();
847 Console.WriteLine("ReadStyleSheet: Ignored token " + rtf.text_buffer);
853 if (!rtf.CheckCM(TokenClass.Group, Major.EndGroup)) {
854 throw new RTFException(rtf, "Missing EndGroup (\"}\"");
858 if (style.Name == null) {
859 throw new RTFException(rtf, "Style must have name");
863 if (!sb.ToString().StartsWith("Normal") && !sb.ToString().StartsWith("Standard")) {
864 throw new RTFException(rtf, "Missing style number");
867 style.Num = Style.NormalStyleNum;
870 if (style.NextPar == -1) {
871 style.NextPar = style.Num;
878 private void ReadInfoGroup(RTF rtf) {
883 private void ReadPictGroup(RTF rtf) {
888 private void ReadObjGroup(RTF rtf) {
892 #endregion // Default Delegates