1057 lines
23 KiB
C#
Raw Normal View History

// 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;
internal const int DefaultEncodingCodePage = 1252;
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 = DefaultEncodingCodePage;
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;
font_list = null;
charset_stack = null;
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
/// <summary>Set the default font for documents without font table</summary>
public void DefaultFont(string name) {
Font font;
font = new Font(this);
font.Num = 0;
font.Name = name;
}
/// <summary>Read the next character from the input - skip any crlf</summary>
private char GetChar ()
{
return GetChar (true);
}
/// <summary>Read the next character from the input</summary>
private char GetChar(bool skipCrLf)
{
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 (skipCrLf) {
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;
}
/// <summary>Parse the RTF stream</summary>
public void Read() {
while (GetToken() != TokenClass.EOF) {
RouteToken();
}
}
/// <summary>Route a token</summary>
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);
}
}
/// <summary>Skip to the end of the next group to start or current group we are in</summary>
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;
}
}
}
}
}
/// <summary>Return the next token in the stream</summary>
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;
// fallback to the default one in case we have an invalid value
if (encoding_code_page < 0 || encoding_code_page > 65535)
encoding_code_page = DefaultEncodingCodePage;
}
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(false)) == 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 != ' ' && c != '\r' && c != '\n') {
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)
{
int group_levels = 1;
while (true) {
GetToken ();
// Here is where we should handle recognised optional
// destinations.
//
// Handle a picture group
//
if (rtf.CheckCMM (TokenClass.Control, Major.Destination, Minor.Pict)) {
ReadPictGroup (rtf);
return;
}
if (rtf.CheckCM (TokenClass.Group, Major.EndGroup)) {
if ((--group_levels) == 0) {
break;
}
}
if (rtf.CheckCM (TokenClass.Group, Major.BeginGroup)) {
group_levels++;
}
}
}
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
}
}