You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

952 lines
31 KiB

//
// DocumentWriter.cs
//
// Author: Kees van Spelde <sicos2002@hotmail.com>
//
// Copyright (c) 2013-2018 Magic-Sessions. (www.magic-sessions.com)
//
// 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 NON INFRINGEMENT. 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.
//
using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
namespace MsgReader.Rtf
{
/// <summary>
/// RTF document writer
/// </summary>
internal sealed class DocumentWriter
{
#region Fields
private bool _firstParagraph = true;
private DocumentFormatInfo _lastParagraphInfo;
#endregion
#region Properties
// ReSharper disable MemberCanBePrivate.Global
/// <summary>
/// Base writer
/// </summary>
public Writer Writer { get; set; }
/// <summary>
/// Information about this Rtf document
/// </summary>
public Hashtable Info { get; }
/// <summary>
/// Rtf font table
/// </summary>
public Table FontTable { get; }
public ListTable ListTable { get; set; }
public ListOverrideTable ListOverrideTable { get; set; }
/// <summary>
/// Rtf color table
/// </summary>
public ColorTable ColorTable { get; }
/// <summary>
/// System collectiong document's information , maby generating
/// font table and color table , not writting content.
/// </summary>
public bool CollectionInfo { get; set; }
/// <summary>
/// How many nested groups do we have
/// </summary>
public int GroupLevel => Writer.GroupLevel;
/// <summary>
/// When debug mode is turned on, raw information about the Rtf file is written
/// </summary>
public bool DebugMode { get; set; }
// ReSharper restore MemberCanBePrivate.Global
#endregion
#region Constructors
/// <summary>
/// Initialize instance
/// </summary>
public DocumentWriter()
{
Info = new Hashtable();
DebugMode = true;
CollectionInfo = true;
ColorTable = new ColorTable();
ListOverrideTable = new ListOverrideTable();
ListTable = new ListTable();
FontTable = new Table();
ColorTable.CheckValueExistWhenAdd = true;
}
/// <summary>
/// Initialize instance with a text writer
/// </summary>
/// <param name="writer"></param>
public DocumentWriter(TextWriter writer)
{
Info = new Hashtable();
DebugMode = true;
CollectionInfo = true;
ColorTable = new ColorTable();
ListOverrideTable = new ListOverrideTable();
ListTable = new ListTable();
FontTable = new Table();
ColorTable.CheckValueExistWhenAdd = true;
Open(writer);
}
/// <summary>
/// Initialize instance from a file
/// </summary>
/// <param name="fileName"></param>
public DocumentWriter(string fileName)
{
Info = new Hashtable();
DebugMode = true;
CollectionInfo = true;
ColorTable = new ColorTable();
ListOverrideTable = new ListOverrideTable();
ListTable = new ListTable();
FontTable = new Table();
ColorTable.CheckValueExistWhenAdd = true;
// ReSharper disable once DoNotCallOverridableMethodsInConstructor
Open(fileName);
}
/// <summary>
/// Initialize instance from a stream
/// </summary>
/// <param name="stream"></param>
public DocumentWriter(Stream stream)
{
Info = new Hashtable();
DebugMode = true;
CollectionInfo = true;
ColorTable = new ColorTable();
ListOverrideTable = new ListOverrideTable();
ListTable = new ListTable();
FontTable = new Table();
ColorTable.CheckValueExistWhenAdd = true;
var writer = new StreamWriter(stream, Encoding.ASCII);
// ReSharper disable once DoNotCallOverridableMethodsInConstructor
Open(writer);
}
#endregion
#region Open
// ReSharper disable MemberCanBeProtected.Global
/// <summary>
/// Open Rtf file from a textwriter
/// </summary>
/// <param name="writer"></param>
public void Open(TextWriter writer)
{
Writer = new Writer(writer) {Indent = false};
}
/// <summary>
/// Open Rtf file from a file
/// </summary>
/// <param name="fileName"></param>
public void Open(string fileName)
{
Writer = new Writer(fileName) {Indent = false};
}
// ReSharper restore MemberCanBeProtected.Global
#endregion
#region Close
public void Close()
{
Writer.Close();
}
#endregion
#region WriteGroup
public void WriteStartGroup()
{
if (CollectionInfo == false)
Writer.WriteStartGroup();
}
public void WriteEndGroup()
{
if (CollectionInfo == false)
Writer.WriteEndGroup();
}
#endregion
#region WriteKeyword
/// <summary>
/// Write rtf keyword
/// </summary>
/// <param name="keyword">keyword</param>
// ReSharper disable once MemberCanBePrivate.Global
public void WriteKeyword(string keyword)
{
if (CollectionInfo == false)
Writer.WriteKeyword(keyword);
}
/// <summary>
/// Write rtf keyword
/// </summary>
/// <param name="keyWord"></param>
/// <param name="ext">Keyword is external</param>
public void WriteKeyword(string keyWord, bool ext)
{
if (CollectionInfo == false)
Writer.WriteKeyword(keyWord, ext);
}
#endregion
#region WriteRaw
/// <summary>
/// Write raw text
/// </summary>
/// <param name="text"></param>
public void WriteRaw(string text)
{
if (CollectionInfo == false)
{
if (text != null)
Writer.WriteRaw(text);
}
}
#endregion
#region WriteBorderLineDashStyle
/// <summary>
/// Write the style that is used by the borderline
/// </summary>
/// <param name="style"></param>
public void WriteBorderLineDashStyle(DashStyle style)
{
if (CollectionInfo == false)
{
switch (style)
{
case DashStyle.Dot:
WriteKeyword("brdrdot");
break;
case DashStyle.DashDot:
WriteKeyword("brdrdashd");
break;
case DashStyle.DashDotDot:
WriteKeyword("brdrdashdd");
break;
case DashStyle.Dash:
WriteKeyword("brdrdash");
break;
default:
WriteKeyword("brdrs");
break;
}
}
}
#endregion
#region WriteStartEndDocument
/// <summary>
/// Write the start of the document
/// </summary>
public void WriteStartDocument()
{
_lastParagraphInfo = null;
_firstParagraph = true;
if (CollectionInfo)
{
Info.Clear();
FontTable.Clear();
ColorTable.Clear();
FontTable.Add(SystemFonts.DefaultFont.Name);
}
else
{
Writer.WriteStartGroup();
Writer.WriteKeyword(Consts.Rtf);
Writer.WriteKeyword("ansi");
Writer.WriteKeyword("ansicpg" + Writer.Encoding.CodePage);
// Write document information
if (Info.Count > 0)
{
Writer.WriteStartGroup();
Writer.WriteKeyword("info");
foreach (string key in Info.Keys)
{
Writer.WriteStartGroup();
var value = Info[key];
if (value is string)
{
Writer.WriteKeyword(key);
Writer.WriteText((string) value);
}
else if (value is int)
Writer.WriteKeyword(key + value);
else if (value is DateTime)
{
var dateTime = (DateTime) value;
Writer.WriteKeyword(key);
Writer.WriteKeyword("yr" + dateTime.Year);
Writer.WriteKeyword("mo" + dateTime.Month);
Writer.WriteKeyword("dy" + dateTime.Day);
Writer.WriteKeyword("hr" + dateTime.Hour);
Writer.WriteKeyword("min" + dateTime.Minute);
Writer.WriteKeyword("sec" + dateTime.Second);
}
else
Writer.WriteKeyword(key);
Writer.WriteEndGroup();
}
Writer.WriteEndGroup();
}
// Write font table
Writer.WriteStartGroup();
Writer.WriteKeyword(Consts.Fonttbl);
for (var count = 0; count < FontTable.Count; count ++)
{
//string f = myFontTable[ count ] ;
Writer.WriteStartGroup();
Writer.WriteKeyword("f" + count);
var f = FontTable[count];
Writer.WriteText(f.Name);
if (f.Charset != 1)
Writer.WriteKeyword("fcharset" + f.Charset);
Writer.WriteEndGroup();
}
Writer.WriteEndGroup();
// Write color table
Writer.WriteStartGroup();
Writer.WriteKeyword(Consts.Colortbl);
Writer.WriteRaw(";");
for (var count = 0; count < ColorTable.Count; count ++)
{
var colorTable = ColorTable[count];
Writer.WriteKeyword("red" + colorTable.R);
Writer.WriteKeyword("green" + colorTable.G);
Writer.WriteKeyword("blue" + colorTable.B);
Writer.WriteRaw(";");
}
Writer.WriteEndGroup();
// Write list table
if (ListTable != null && ListTable.Count > 0)
{
if (DebugMode)
Writer.WriteRaw(Environment.NewLine);
Writer.WriteStartGroup();
Writer.WriteKeyword("listtable", true);
foreach (var list in ListTable)
{
if (DebugMode)
{
Writer.WriteRaw(Environment.NewLine);
}
Writer.WriteStartGroup();
Writer.WriteKeyword("list");
Writer.WriteKeyword("listtemplateid" + list.ListTemplateId);
if (list.ListHybrid)
Writer.WriteKeyword("listhybrid");
if (DebugMode)
Writer.WriteRaw(Environment.NewLine);
Writer.WriteStartGroup();
Writer.WriteKeyword("listlevel");
Writer.WriteKeyword("levelfollow" + list.LevelFollow);
Writer.WriteKeyword("leveljc" + list.LevelJc);
Writer.WriteKeyword("levelstartat" + list.LevelStartAt);
Writer.WriteKeyword("levelnfc" + Convert.ToInt32(list.LevelNfc));
Writer.WriteKeyword("levelnfcn" + Convert.ToInt32(list.LevelNfc));
Writer.WriteKeyword("leveljc" + list.LevelJc);
if (string.IsNullOrEmpty(list.LevelText) == false)
{
Writer.WriteStartGroup();
Writer.WriteKeyword("leveltext");
Writer.WriteKeyword("'0" + list.LevelText.Length);
if (list.LevelNfc == RtfLevelNumberType.Bullet)
Writer.WriteUnicodeText(list.LevelText);
else
Writer.WriteText(list.LevelText, false);
Writer.WriteEndGroup();
if (list.LevelNfc == RtfLevelNumberType.Bullet)
{
var f = FontTable["Wingdings"];
if (f != null)
Writer.WriteKeyword("f" + f.Index);
}
else
{
Writer.WriteStartGroup();
Writer.WriteKeyword("levelnumbers");
Writer.WriteKeyword("'01");
Writer.WriteEndGroup();
}
}
Writer.WriteEndGroup();
Writer.WriteKeyword("listid" + list.ListId);
Writer.WriteEndGroup();
}
Writer.WriteEndGroup();
}
// Write list overried table
if (ListOverrideTable != null && ListOverrideTable.Count > 0)
{
if (DebugMode)
Writer.WriteRaw(Environment.NewLine);
Writer.WriteStartGroup();
Writer.WriteKeyword("listoverridetable");
foreach (var listOverride in ListOverrideTable)
{
if (DebugMode)
{
Writer.WriteRaw(Environment.NewLine);
}
Writer.WriteStartGroup();
Writer.WriteKeyword("listoverride");
Writer.WriteKeyword("listid" + listOverride.ListId);
Writer.WriteKeyword("listoverridecount" + listOverride.ListOverrideCount);
Writer.WriteKeyword("ls" + listOverride.Id);
Writer.WriteEndGroup();
}
Writer.WriteEndGroup();
}
if (DebugMode)
Writer.WriteRaw(Environment.NewLine);
Writer.WriteKeyword("viewkind1");
}
}
/// <summary>
/// Write the end of the document
/// </summary>
public void WriteEndDocument()
{
if (CollectionInfo == false)
Writer.WriteEndGroup();
Writer.Flush();
}
#endregion
#region WriteStartEndHeader
/// <summary>
/// Write start from header
/// </summary>
public void WriteStartHeader()
{
if (CollectionInfo == false)
{
Writer.WriteStartGroup();
Writer.WriteKeyword("header");
}
}
/// <summary>
/// Write end from header
/// </summary>
public void WriteEndHeader()
{
if (CollectionInfo == false)
{
Writer.WriteEndGroup();
}
}
#endregion
#region WriteStartEndFooter
/// <summary>
/// Write start from footer
/// </summary>
public void WriteStartFooter()
{
if (CollectionInfo == false)
{
Writer.WriteStartGroup();
Writer.WriteKeyword("footer");
}
}
/// <summary>
/// Write end from footer
/// </summary>
public void WriteEndFooter()
{
if (CollectionInfo == false)
Writer.WriteEndGroup();
}
#endregion
#region WriteStartEndParagraph
/// <summary>
/// Write start from paragraph
/// </summary>
public void WriteStartParagraph()
{
WriteStartParagraph(new DocumentFormatInfo());
}
/// <summary>
/// Write end of paragraph
/// </summary>
/// <param name="info">format</param>
// ReSharper disable once MemberCanBePrivate.Global
public void WriteStartParagraph(DocumentFormatInfo info)
{
if (CollectionInfo)
{
//myFontTable.Add("Wingdings");
}
else
{
if (_firstParagraph)
{
_firstParagraph = false;
Writer.WriteRaw(Environment.NewLine);
//myWriter.WriteKeyword("par");
}
else
{
Writer.WriteKeyword("par");
}
if (info.ListId >= 0)
{
Writer.WriteKeyword("pard");
Writer.WriteKeyword("ls" + info.ListId);
if (_lastParagraphInfo != null)
{
if (_lastParagraphInfo.ListId >= 0)
{
Writer.WriteKeyword("pard");
}
}
}
switch (info.Align)
{
case RtfAlignment.Left:
Writer.WriteKeyword("ql");
break;
case RtfAlignment.Center:
Writer.WriteKeyword("qc");
break;
case RtfAlignment.Right:
Writer.WriteKeyword("qr");
break;
case RtfAlignment.Justify:
Writer.WriteKeyword("qj");
break;
}
if (info.ParagraphFirstLineIndent != 0)
{
Writer.WriteKeyword("fi" + Convert.ToInt32(
info.ParagraphFirstLineIndent*400/info.StandTabWidth));
}
else
Writer.WriteKeyword("fi0");
if (info.LeftIndent != 0)
{
Writer.WriteKeyword("li" + Convert.ToInt32(
info.LeftIndent*400/info.StandTabWidth));
}
else
{
Writer.WriteKeyword("li0");
}
Writer.WriteKeyword("plain");
}
_lastParagraphInfo = info;
}
/// <summary>
/// end write paragraph
/// </summary>
public void WriteEndParagraph()
{
}
#endregion
#region WriteText
/// <summary>
/// Write plain text
/// </summary>
/// <param name="text">text</param>
public void WriteText(string text)
{
if (text != null && CollectionInfo == false)
Writer.WriteText(text);
}
#endregion
#region WriteFont
/// <summary>
/// Write font format
/// </summary>
/// <param name="font">font</param>
public void WriteFont(System.Drawing.Font font)
{
if (font == null)
throw new ArgumentNullException(nameof(font));
if (CollectionInfo)
FontTable.Add(font.Name);
else
{
var index = FontTable.IndexOf(font.Name);
if (index >= 0)
Writer.WriteKeyword("f" + index);
if (font.Bold)
Writer.WriteKeyword("b");
if (font.Italic)
Writer.WriteKeyword("i");
if (font.Underline)
Writer.WriteKeyword("ul");
if (font.Strikeout)
Writer.WriteKeyword("strike");
Writer.WriteKeyword("fs" + Convert.ToInt32(font.Size*2));
}
}
#endregion
#region WriteString
/// <summary>
/// Start write of formatted text
/// </summary>
/// <param name="info">format</param>
/// <remarks>
/// This function must assort with WriteEndString
/// </remarks>
// ReSharper disable once MemberCanBePrivate.Global
public void WriteStartString(DocumentFormatInfo info)
{
if (CollectionInfo)
{
FontTable.Add(info.FontName);
ColorTable.Add(info.TextColor);
ColorTable.Add(info.BackColor);
if (info.BorderColor.A != 0)
{
ColorTable.Add(info.BorderColor);
}
return;
}
if (!string.IsNullOrEmpty(info.Link))
{
Writer.WriteStartGroup();
Writer.WriteKeyword("field");
Writer.WriteStartGroup();
Writer.WriteKeyword("fldinst", true);
Writer.WriteStartGroup();
Writer.WriteKeyword("hich");
Writer.WriteText(" HYPERLINK \"" + info.Link + "\"");
Writer.WriteEndGroup();
Writer.WriteEndGroup();
Writer.WriteStartGroup();
Writer.WriteKeyword("fldrslt");
Writer.WriteStartGroup();
}
switch (info.Align)
{
case RtfAlignment.Left:
Writer.WriteKeyword("ql");
break;
case RtfAlignment.Center:
Writer.WriteKeyword("qc");
break;
case RtfAlignment.Right:
Writer.WriteKeyword("qr");
break;
case RtfAlignment.Justify:
Writer.WriteKeyword("qj");
break;
}
Writer.WriteKeyword("plain");
int index = FontTable.IndexOf(info.FontName);
if (index >= 0)
Writer.WriteKeyword("f" + index);
if (info.Bold)
Writer.WriteKeyword("b");
if (info.Italic)
Writer.WriteKeyword("i");
if (info.Underline)
Writer.WriteKeyword("ul");
if (info.Strikeout)
Writer.WriteKeyword("strike");
Writer.WriteKeyword("fs" + Convert.ToInt32(info.FontSize*2));
// Back color
index = ColorTable.IndexOf(info.BackColor);
if (index >= 0)
Writer.WriteKeyword("chcbpat" + Convert.ToString(index + 1));
index = ColorTable.IndexOf(info.TextColor);
if (index >= 0)
Writer.WriteKeyword("cf" + Convert.ToString(index + 1));
if (info.Subscript)
Writer.WriteKeyword("sub");
if (info.Superscript)
Writer.WriteKeyword("super");
if (info.NoWwrap)
Writer.WriteKeyword("nowwrap");
if (info.LeftBorder
|| info.TopBorder
|| info.RightBorder
|| info.BottomBorder)
{
// Border color
if (info.BorderColor.A != 0)
{
Writer.WriteKeyword("chbrdr");
Writer.WriteKeyword("brdrs");
Writer.WriteKeyword("brdrw10");
index = ColorTable.IndexOf(info.BorderColor);
if (index >= 0)
{
Writer.WriteKeyword("brdrcf" + Convert.ToString(index + 1));
}
}
}
}
// ReSharper disable once MemberCanBePrivate.Global
/// <summary>
/// End writing of a formatted string. WriteStartString and WriteString have to be called before this method
/// </summary>
/// <param name="info"></param>
public void WriteEndString(DocumentFormatInfo info)
{
if (CollectionInfo)
{
return;
}
if (info.Subscript)
Writer.WriteKeyword("sub0");
if (info.Superscript)
Writer.WriteKeyword("super0");
if (info.Bold)
Writer.WriteKeyword("b0");
if (info.Italic)
Writer.WriteKeyword("i0");
if (info.Underline)
Writer.WriteKeyword("ul0");
if (info.Strikeout)
Writer.WriteKeyword("strike0");
if (!string.IsNullOrEmpty(info.Link))
{
Writer.WriteEndGroup();
Writer.WriteEndGroup();
Writer.WriteEndGroup();
}
}
/// <summary>
/// Write formatted string. Call WriteStartString before this method
/// </summary>
/// <param name="text">text</param>
/// <param name="info">format</param>
public void WriteString(string text, DocumentFormatInfo info)
{
if (CollectionInfo)
{
FontTable.Add(info.FontName);
ColorTable.Add(info.TextColor);
ColorTable.Add(info.BackColor);
}
else
{
WriteStartString(info);
if (info.Multiline)
{
if (text != null)
{
text = text.Replace("\n", "");
var reader = new StringReader(text);
var strLine = reader.ReadLine();
var count = 0;
while (strLine != null)
{
if (count > 0)
Writer.WriteKeyword("line");
count ++;
Writer.WriteText(strLine);
strLine = reader.ReadLine();
}
reader.Close();
}
}
else
Writer.WriteText(text);
WriteEndString(info);
}
}
/// <summary>
/// End write string
/// </summary>
public void WriteEndString()
{
}
#endregion
#region WriteBookmark
/// <summary>
/// Start write of bookmark
/// </summary>
/// <param name="strName">bookmark name</param>
public void WriteStartBookmark(string strName)
{
if (CollectionInfo == false)
{
Writer.WriteStartGroup();
Writer.WriteKeyword("bkmkstart", true);
Writer.WriteKeyword("f0");
Writer.WriteText(strName);
Writer.WriteEndGroup();
Writer.WriteStartGroup();
Writer.WriteKeyword("bkmkend", true);
Writer.WriteKeyword("f0");
Writer.WriteText(strName);
Writer.WriteEndGroup();
}
}
/// <summary>
/// End write of bookmark
/// </summary>
/// <param name="strName">bookmark name</param>
public void WriteEndBookmark(string strName)
{
}
#endregion
#region WriteLineBreak
/// <summary>
/// Write a line break
/// </summary>
public void WriteLineBreak()
{
if (CollectionInfo == false)
{
Writer.WriteKeyword("line");
}
}
#endregion
#region WriteImage
/// <summary>
/// Write image
/// </summary>
/// <param name="image">Image</param>
/// <param name="width">Pixel width</param>
/// <param name="height">Pixel height</param>
/// <param name="imageData">Image binary data</param>
public void WriteImage(Image image, int width, int height, byte[] imageData)
{
if (imageData == null)
return;
var memoryStream = new MemoryStream();
image.Save(memoryStream, ImageFormat.Jpeg);
memoryStream.Close();
var bs = memoryStream.ToArray();
Writer.WriteStartGroup();
Writer.WriteKeyword("pict");
Writer.WriteKeyword("jpegblip");
Writer.WriteKeyword("picscalex" + Convert.ToInt32(width*100.0/image.Size.Width));
Writer.WriteKeyword("picscaley" + Convert.ToInt32(height*100.0/image.Size.Height));
Writer.WriteKeyword("picwgoal" + Convert.ToString(image.Size.Width*15));
Writer.WriteKeyword("pichgoal" + Convert.ToString(image.Size.Height*15));
Writer.WriteBytes(bs);
Writer.WriteEndGroup();
}
#endregion
}
}