// Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) // // Authors: // Peter Bartok (pbartok@novell.com) // // COMPLETE #undef RTF_DEBUG using System; using System.Collections; using System.IO; using System.Text; namespace System.Windows.Forms.RTF { internal class RTF { #region Local Variables internal const char EOF = unchecked((char)-1); internal const int NoParam = -1000000; private TokenClass rtf_class; private Major major; private Minor minor; private int param; private string encoded_text; private Encoding encoding; private int encoding_code_page = 1252; private StringBuilder text_buffer; private Picture picture; private int line_num; private int line_pos; private char pushed_char; //private StringBuilder pushed_text_buffer; private TokenClass pushed_class; private Major pushed_major; private Minor pushed_minor; private int pushed_param; private char prev_char; private bool bump_line; private Font font_list; private Charset cur_charset; private Stack charset_stack; private Style styles; private Color colors; private Font fonts; private StreamReader source; private static Hashtable key_table; private static KeyStruct[] Keys = KeysInit.Init(); private DestinationCallback destination_callbacks; private ClassCallback class_callbacks; #endregion // Local Variables #region Constructors static RTF() { key_table = new Hashtable(Keys.Length); for (int i = 0; i < Keys.Length; i++) { key_table[Keys[i].Symbol] = Keys[i]; } } public RTF(Stream stream) { source = new StreamReader(stream); text_buffer = new StringBuilder(1024); //pushed_text_buffer = new StringBuilder(1024); rtf_class = TokenClass.None; pushed_class = TokenClass.None; pushed_char = unchecked((char)-1); line_num = 0; line_pos = 0; prev_char = unchecked((char)-1); bump_line = false; cur_charset = new Charset(); destination_callbacks = new DestinationCallback(); class_callbacks = new ClassCallback(); destination_callbacks [Minor.OptDest] = new DestinationDelegate (HandleOptDest); destination_callbacks[Minor.FontTbl] = new DestinationDelegate(ReadFontTbl); destination_callbacks[Minor.ColorTbl] = new DestinationDelegate(ReadColorTbl); destination_callbacks[Minor.StyleSheet] = new DestinationDelegate(ReadStyleSheet); destination_callbacks[Minor.Info] = new DestinationDelegate(ReadInfoGroup); destination_callbacks[Minor.Pict] = new DestinationDelegate(ReadPictGroup); destination_callbacks[Minor.Object] = new DestinationDelegate(ReadObjGroup); } #endregion // Constructors #region Properties public TokenClass TokenClass { get { return this.rtf_class; } set { this.rtf_class = value; } } public Major Major { get { return this.major; } set { this.major = value; } } public Minor Minor { get { return this.minor; } set { this.minor = value; } } public int Param { get { return this.param; } set { this.param = value; } } public string Text { get { return this.text_buffer.ToString(); } set { if (value == null) { this.text_buffer.Length = 0; } else { this.text_buffer = new StringBuilder(value); } } } public string EncodedText { get { return encoded_text; } } public Picture Picture { get { return picture; } set { picture = value; } } public Color Colors { get { return colors; } set { colors = value; } } public Style Styles { get { return styles; } set { styles = value; } } public Font Fonts { get { return fonts; } set { fonts = value; } } public ClassCallback ClassCallback { get { return class_callbacks; } set { class_callbacks = value; } } public DestinationCallback DestinationCallback { get { return destination_callbacks; } set { destination_callbacks = value; } } public int LineNumber { get { return line_num; } } public int LinePos { get { return line_pos; } } #endregion // Properties #region Methods /// Set the default font for documents without font table public void DefaultFont(string name) { Font font; font = new Font(this); font.Num = 0; font.Name = name; } /// Read the next character from the input private char GetChar() { int c; bool old_bump_line; SkipCRLF: if ((c = source.Read()) != -1) { this.text_buffer.Append((char) c); } if (this.prev_char == EOF) { this.bump_line = true; } old_bump_line = bump_line; bump_line = false; if (c == '\r') { bump_line = true; text_buffer.Length--; goto SkipCRLF; } else if (c == '\n') { bump_line = true; if (this.prev_char == '\r') { old_bump_line = false; } text_buffer.Length--; goto SkipCRLF; } this.line_pos ++; if (old_bump_line) { this.line_num++; this.line_pos = 1; } this.prev_char = (char) c; return (char) c; } /// Parse the RTF stream public void Read() { while (GetToken() != TokenClass.EOF) { RouteToken(); } } /// Route a token public void RouteToken() { if (CheckCM(TokenClass.Control, Major.Destination)) { DestinationDelegate d; d = destination_callbacks[minor]; if (d != null) { d(this); } } // Invoke class callback if there is one ClassDelegate c; c = class_callbacks[rtf_class]; if (c != null) { c(this); } } /// Skip to the end of the current group public void SkipGroup() { int level; level = 1; while (GetToken() != TokenClass.EOF) { if (rtf_class == TokenClass.Group) { if (this.major == Major.BeginGroup) { level++; } else if (this.major == Major.EndGroup) { level--; if (level < 1) { break; } } } } } /// Return the next token in the stream public TokenClass GetToken() { if (pushed_class != TokenClass.None) { this.rtf_class = this.pushed_class; this.major = this.pushed_major; this.minor = this.pushed_minor; this.param = this.pushed_param; this.pushed_class = TokenClass.None; return this.rtf_class; } GetToken2(); if (this.rtf_class == TokenClass.Text) { this.minor = (Minor)this.cur_charset[(int)this.major]; if (encoding == null) { encoding = Encoding.GetEncoding (encoding_code_page); } encoded_text = new String (encoding.GetChars (new byte [] { (byte) this.major })); } if (this.cur_charset.Flags == CharsetFlags.None) { return this.rtf_class; } if (CheckCMM (TokenClass.Control, Major.Unicode, Minor.UnicodeAnsiCodepage)) { encoding_code_page = param; } if (((this.cur_charset.Flags & CharsetFlags.Read) != 0) && CheckCM(TokenClass.Control, Major.CharSet)) { this.cur_charset.ReadMap(); } else if (((this.cur_charset.Flags & CharsetFlags.Switch) != 0) && CheckCMM(TokenClass.Control, Major.CharAttr, Minor.FontNum)) { Font fp; fp = Font.GetFont(this.font_list, this.param); if (fp != null) { if (fp.Name.StartsWith("Symbol")) { this.cur_charset.ID = CharsetType.Symbol; } else { this.cur_charset.ID = CharsetType.General; } } else if (((this.cur_charset.Flags & CharsetFlags.Switch) != 0) && (this.rtf_class == TokenClass.Group)) { switch(this.major) { case Major.BeginGroup: { this.charset_stack.Push(this.cur_charset); break; } case Major.EndGroup: { this.cur_charset = (Charset)this.charset_stack.Pop(); break; } } } } return this.rtf_class; } private void GetToken2() { char c; int sign; this.rtf_class = TokenClass.Unknown; this.param = NoParam; this.text_buffer.Length = 0; if (this.pushed_char != EOF) { c = this.pushed_char; this.text_buffer.Append(c); this.pushed_char = EOF; } else if ((c = GetChar()) == EOF) { this.rtf_class = TokenClass.EOF; return; } if (c == '{') { this.rtf_class = TokenClass.Group; this.major = Major.BeginGroup; return; } if (c == '}') { this.rtf_class = TokenClass.Group; this.major = Major.EndGroup; return; } if (c != '\\') { if (c != '\t') { this.rtf_class = TokenClass.Text; this.major = (Major)c; // FIXME - typing? return; } else { this.rtf_class = TokenClass.Control; this.major = Major.SpecialChar; this.minor = Minor.Tab; return; } } if ((c = GetChar()) == EOF) { // Not so good return; } if (!Char.IsLetter(c)) { if (c == '\'') { char c2; if ((c = GetChar()) == EOF) { return; } if ((c2 = GetChar()) == EOF) { return; } this.rtf_class = TokenClass.Text; this.major = (Major)((Char)((Convert.ToByte(c.ToString(), 16) * 16 + Convert.ToByte(c2.ToString(), 16)))); return; } // Escaped char if (c == ':' || c == '{' || c == '}' || c == '\\') { this.rtf_class = TokenClass.Text; this.major = (Major)c; return; } Lookup(this.text_buffer.ToString()); return; } while (Char.IsLetter(c)) { if ((c = GetChar()) == EOF) { break; } } if (c != EOF) { this.text_buffer.Length--; } Lookup(this.text_buffer.ToString()); if (c != EOF) { this.text_buffer.Append(c); } sign = 1; if (c == '-') { sign = -1; c = GetChar(); } if (c != EOF && Char.IsDigit(c) && minor != Minor.PngBlip) { this.param = 0; while (Char.IsDigit(c)) { this.param = this.param * 10 + Convert.ToByte(c) - 48; if ((c = GetChar()) == EOF) { break; } } this.param *= sign; } if (c != EOF) { if (c != ' ') { this.pushed_char = c; } this.text_buffer.Length--; } } public void SetToken(TokenClass cl, Major maj, Minor min, int par, string text) { this.rtf_class = cl; this.major = maj; this.minor = min; this.param = par; if (par == NoParam) { this.text_buffer = new StringBuilder(text); } else { this.text_buffer = new StringBuilder(text + par.ToString()); } } public void UngetToken() { if (this.pushed_class != TokenClass.None) { throw new RTFException(this, "Cannot unget more than one token"); } if (this.rtf_class == TokenClass.None) { throw new RTFException(this, "No token to unget"); } this.pushed_class = this.rtf_class; this.pushed_major = this.major; this.pushed_minor = this.minor; this.pushed_param = this.param; //this.pushed_text_buffer = new StringBuilder(this.text_buffer.ToString()); } public TokenClass PeekToken() { GetToken(); UngetToken(); return rtf_class; } public void Lookup(string token) { Object obj; KeyStruct key; obj = key_table[token.Substring(1)]; if (obj == null) { rtf_class = TokenClass.Unknown; major = (Major) -1; minor = (Minor) -1; return; } key = (KeyStruct)obj; this.rtf_class = TokenClass.Control; this.major = key.Major; this.minor = key.Minor; } public bool CheckCM(TokenClass rtf_class, Major major) { if ((this.rtf_class == rtf_class) && (this.major == major)) { return true; } return false; } public bool CheckCMM(TokenClass rtf_class, Major major, Minor minor) { if ((this.rtf_class == rtf_class) && (this.major == major) && (this.minor == minor)) { return true; } return false; } public bool CheckMM(Major major, Minor minor) { if ((this.major == major) && (this.minor == minor)) { return true; } return false; } #endregion // Methods #region Default Delegates private void HandleOptDest (RTF rtf) { while (true) { GetToken (); if (rtf.CheckCM (TokenClass.Group, Major.EndGroup)) break; // // We don't want to skip all optional dests // if (rtf.CheckCMM (TokenClass.Control, Major.Destination, Minor.Pict)) { ReadPictGroup (rtf); return; } } } private void ReadFontTbl(RTF rtf) { int old; Font font; old = -1; font = null; while (true) { rtf.GetToken(); if (rtf.CheckCM(TokenClass.Group, Major.EndGroup)) { break; } if (old < 0) { if (rtf.CheckCMM(TokenClass.Control, Major.CharAttr, Minor.FontNum)) { old = 1; } else if (rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) { old = 0; } else { throw new RTFException(rtf, "Cannot determine format"); } } if (old == 0) { if (!rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) { throw new RTFException(rtf, "missing \"{\""); } rtf.GetToken(); } font = new Font(rtf); while ((rtf.rtf_class != TokenClass.EOF) && (!rtf.CheckCM(TokenClass.Text, (Major)';')) && (!rtf.CheckCM(TokenClass.Group, Major.EndGroup))) { if (rtf.rtf_class == TokenClass.Control) { switch(rtf.major) { case Major.FontFamily: { font.Family = (int)rtf.minor; break; } case Major.CharAttr: { switch(rtf.minor) { case Minor.FontNum: { font.Num = rtf.param; break; } default: { #if RTF_DEBUG Console.WriteLine("Got unhandled Control.CharAttr.Minor: " + rtf.minor); #endif break; } } break; } case Major.FontAttr: { switch (rtf.minor) { case Minor.FontCharSet: { font.Charset = (CharsetType)rtf.param; break; } case Minor.FontPitch: { font.Pitch = rtf.param; break; } case Minor.FontCodePage: { font.Codepage = rtf.param; break; } case Minor.FTypeNil: case Minor.FTypeTrueType: { font.Type = rtf.param; break; } default: { #if RTF_DEBUG Console.WriteLine("Got unhandled Control.FontAttr.Minor: " + rtf.minor); #endif break; } } break; } default: { #if RTF_DEBUG Console.WriteLine("ReadFontTbl: Unknown Control token " + rtf.major); #endif break; } } } else if (rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) { rtf.SkipGroup(); } else if (rtf.rtf_class == TokenClass.Text) { StringBuilder sb; sb = new StringBuilder(); while ((rtf.rtf_class != TokenClass.EOF) && (!rtf.CheckCM(TokenClass.Text, (Major)';')) && (!rtf.CheckCM(TokenClass.Group, Major.EndGroup)) && (!rtf.CheckCM(TokenClass.Group, Major.BeginGroup))) { sb.Append((char)rtf.major); rtf.GetToken(); } if (rtf.CheckCM(TokenClass.Group, Major.EndGroup)) { rtf.UngetToken(); } font.Name = sb.ToString(); continue; #if RTF_DEBUG } else { Console.WriteLine("ReadFontTbl: Unknown token " + rtf.text_buffer); #endif } rtf.GetToken(); } if (old == 0) { rtf.GetToken(); if (!rtf.CheckCM(TokenClass.Group, Major.EndGroup)) { throw new RTFException(rtf, "Missing \"}\""); } } } if (font == null) { throw new RTFException(rtf, "No font created"); } if (font.Num == -1) { throw new RTFException(rtf, "Missing font number"); } rtf.RouteToken(); } private void ReadColorTbl(RTF rtf) { Color color; int num; num = 0; while (true) { rtf.GetToken(); if (rtf.CheckCM(TokenClass.Group, Major.EndGroup)) { break; } color = new Color(rtf); color.Num = num++; while (rtf.CheckCM(TokenClass.Control, Major.ColorName)) { switch (rtf.minor) { case Minor.Red: { color.Red = rtf.param; break; } case Minor.Green: { color.Green = rtf.param; break; } case Minor.Blue: { color.Blue = rtf.param; break; } } rtf.GetToken(); } if (!rtf.CheckCM(TokenClass.Text, (Major)';')) { throw new RTFException(rtf, "Malformed color entry"); } } rtf.RouteToken(); } private void ReadStyleSheet(RTF rtf) { Style style; StringBuilder sb; sb = new StringBuilder(); while (true) { rtf.GetToken(); if (rtf.CheckCM(TokenClass.Group, Major.EndGroup)) { break; } style = new Style(rtf); if (!rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) { throw new RTFException(rtf, "Missing \"{\""); } while (true) { rtf.GetToken(); if ((rtf.rtf_class == TokenClass.EOF) || rtf.CheckCM(TokenClass.Text, (Major)';')) { break; } if (rtf.rtf_class == TokenClass.Control) { if (rtf.CheckMM(Major.ParAttr, Minor.StyleNum)) { style.Num = rtf.param; style.Type = StyleType.Paragraph; continue; } if (rtf.CheckMM(Major.CharAttr, Minor.CharStyleNum)) { style.Num = rtf.param; style.Type = StyleType.Character; continue; } if (rtf.CheckMM(Major.StyleAttr, Minor.SectStyleNum)) { style.Num = rtf.param; style.Type = StyleType.Section; continue; } if (rtf.CheckMM(Major.StyleAttr, Minor.BasedOn)) { style.BasedOn = rtf.param; continue; } if (rtf.CheckMM(Major.StyleAttr, Minor.Additive)) { style.Additive = true; continue; } if (rtf.CheckMM(Major.StyleAttr, Minor.Next)) { style.NextPar = rtf.param; continue; } new StyleElement(style, rtf.rtf_class, rtf.major, rtf.minor, rtf.param, rtf.text_buffer.ToString()); } else if (rtf.CheckCM(TokenClass.Group, Major.BeginGroup)) { // This passes over "{\*\keycode ... }, among other things rtf.SkipGroup(); } else if (rtf.rtf_class == TokenClass.Text) { while (rtf.rtf_class == TokenClass.Text) { if (rtf.major == (Major)';') { rtf.UngetToken(); break; } sb.Append((char)rtf.major); rtf.GetToken(); } style.Name = sb.ToString(); #if RTF_DEBUG } else { Console.WriteLine("ReadStyleSheet: Ignored token " + rtf.text_buffer); #endif } } rtf.GetToken(); if (!rtf.CheckCM(TokenClass.Group, Major.EndGroup)) { throw new RTFException(rtf, "Missing EndGroup (\"}\""); } // Sanity checks if (style.Name == null) { throw new RTFException(rtf, "Style must have name"); } if (style.Num < 0) { if (!sb.ToString().StartsWith("Normal") && !sb.ToString().StartsWith("Standard")) { throw new RTFException(rtf, "Missing style number"); } style.Num = Style.NormalStyleNum; } if (style.NextPar == -1) { style.NextPar = style.Num; } } rtf.RouteToken(); } private void ReadInfoGroup(RTF rtf) { rtf.SkipGroup(); rtf.RouteToken(); } private void ReadPictGroup(RTF rtf) { bool read_image_data = false; Picture picture = new Picture (); while (true) { rtf.GetToken (); if (rtf.CheckCM (TokenClass.Group, Major.EndGroup)) break; switch (minor) { case Minor.PngBlip: picture.ImageType = minor; read_image_data = true; break; case Minor.WinMetafile: picture.ImageType = minor; read_image_data = true; continue; case Minor.PicWid: continue; case Minor.PicHt: continue; case Minor.PicGoalWid: picture.SetWidthFromTwips (param); continue; case Minor.PicGoalHt: picture.SetHeightFromTwips (param); continue; } if (read_image_data && rtf.rtf_class == TokenClass.Text) { picture.Data.Seek (0, SeekOrigin.Begin); //char c = (char) rtf.major; uint digitValue1; uint digitValue2; char hexDigit1 = (char) rtf.major; char hexDigit2; while (true) { while (hexDigit1 == '\n' || hexDigit1 == '\r') { hexDigit1 = (char) source.Peek (); if (hexDigit1 == '}') break; hexDigit1 = (char) source.Read (); } hexDigit2 = (char) source.Peek (); if (hexDigit2 == '}') break; hexDigit2 = (char) source.Read (); while (hexDigit2 == '\n' || hexDigit2 == '\r') { hexDigit2 = (char) source.Peek (); if (hexDigit2 == '}') break; hexDigit2 = (char) source.Read (); } if (Char.IsDigit (hexDigit1)) digitValue1 = (uint) (hexDigit1 - '0'); else if (Char.IsLower (hexDigit1)) digitValue1 = (uint) (hexDigit1 - 'a' + 10); else if (Char.IsUpper (hexDigit1)) digitValue1 = (uint) (hexDigit1 - 'A' + 10); else if (hexDigit1 == '\n' || hexDigit1 == '\r') continue; else break; if (Char.IsDigit (hexDigit2)) digitValue2 = (uint) (hexDigit2 - '0'); else if (Char.IsLower (hexDigit2)) digitValue2 = (uint) (hexDigit2 - 'a' + 10); else if (Char.IsUpper (hexDigit2)) digitValue2 = (uint) (hexDigit2 - 'A' + 10); else if (hexDigit2 == '\n' || hexDigit2 == '\r') continue; else break; picture.Data.WriteByte ((byte) checked (digitValue1 * 16 + digitValue2)); // We get the first hex digit at the end, since in the very first // iteration we use rtf.major as the first hex digit hexDigit1 = (char) source.Peek (); if (hexDigit1 == '}') break; hexDigit1 = (char) source.Read (); } read_image_data = false; break; } } if (picture.ImageType != Minor.Undefined && !read_image_data) { this.picture = picture; SetToken (TokenClass.Control, Major.PictAttr, picture.ImageType, 0, String.Empty); } } private void ReadObjGroup(RTF rtf) { rtf.SkipGroup(); rtf.RouteToken(); } #endregion // Default Delegates } }