// // DocumentWriter.cs // // Author: Kees van Spelde // // 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 { /// /// RTF document writer /// internal sealed class DocumentWriter { #region Fields private bool _firstParagraph = true; private DocumentFormatInfo _lastParagraphInfo; #endregion #region Properties // ReSharper disable MemberCanBePrivate.Global /// /// Base writer /// public Writer Writer { get; set; } /// /// Information about this Rtf document /// public Hashtable Info { get; } /// /// Rtf font table /// public Table FontTable { get; } public ListTable ListTable { get; set; } public ListOverrideTable ListOverrideTable { get; set; } /// /// Rtf color table /// public ColorTable ColorTable { get; } /// /// System collectiong document's information , maby generating /// font table and color table , not writting content. /// public bool CollectionInfo { get; set; } /// /// How many nested groups do we have /// public int GroupLevel => Writer.GroupLevel; /// /// When debug mode is turned on, raw information about the Rtf file is written /// public bool DebugMode { get; set; } // ReSharper restore MemberCanBePrivate.Global #endregion #region Constructors /// /// Initialize instance /// public DocumentWriter() { Info = new Hashtable(); DebugMode = true; CollectionInfo = true; ColorTable = new ColorTable(); ListOverrideTable = new ListOverrideTable(); ListTable = new ListTable(); FontTable = new Table(); ColorTable.CheckValueExistWhenAdd = true; } /// /// Initialize instance with a text writer /// /// 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); } /// /// Initialize instance from a file /// /// 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); } /// /// Initialize instance from a stream /// /// 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 /// /// Open Rtf file from a textwriter /// /// public void Open(TextWriter writer) { Writer = new Writer(writer) {Indent = false}; } /// /// Open Rtf file from a file /// /// 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 /// /// Write rtf keyword /// /// keyword // ReSharper disable once MemberCanBePrivate.Global public void WriteKeyword(string keyword) { if (CollectionInfo == false) Writer.WriteKeyword(keyword); } /// /// Write rtf keyword /// /// /// Keyword is external public void WriteKeyword(string keyWord, bool ext) { if (CollectionInfo == false) Writer.WriteKeyword(keyWord, ext); } #endregion #region WriteRaw /// /// Write raw text /// /// public void WriteRaw(string text) { if (CollectionInfo == false) { if (text != null) Writer.WriteRaw(text); } } #endregion #region WriteBorderLineDashStyle /// /// Write the style that is used by the borderline /// /// 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 /// /// Write the start of the document /// 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"); } } /// /// Write the end of the document /// public void WriteEndDocument() { if (CollectionInfo == false) Writer.WriteEndGroup(); Writer.Flush(); } #endregion #region WriteStartEndHeader /// /// Write start from header /// public void WriteStartHeader() { if (CollectionInfo == false) { Writer.WriteStartGroup(); Writer.WriteKeyword("header"); } } /// /// Write end from header /// public void WriteEndHeader() { if (CollectionInfo == false) { Writer.WriteEndGroup(); } } #endregion #region WriteStartEndFooter /// /// Write start from footer /// public void WriteStartFooter() { if (CollectionInfo == false) { Writer.WriteStartGroup(); Writer.WriteKeyword("footer"); } } /// /// Write end from footer /// public void WriteEndFooter() { if (CollectionInfo == false) Writer.WriteEndGroup(); } #endregion #region WriteStartEndParagraph /// /// Write start from paragraph /// public void WriteStartParagraph() { WriteStartParagraph(new DocumentFormatInfo()); } /// /// Write end of paragraph /// /// format // 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; } /// /// end write paragraph /// public void WriteEndParagraph() { } #endregion #region WriteText /// /// Write plain text /// /// text public void WriteText(string text) { if (text != null && CollectionInfo == false) Writer.WriteText(text); } #endregion #region WriteFont /// /// Write font format /// /// font 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 /// /// Start write of formatted text /// /// format /// /// This function must assort with WriteEndString /// // 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 /// /// End writing of a formatted string. WriteStartString and WriteString have to be called before this method /// /// 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(); } } /// /// Write formatted string. Call WriteStartString before this method /// /// text /// format 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); } } /// /// End write string /// public void WriteEndString() { } #endregion #region WriteBookmark /// /// Start write of bookmark /// /// bookmark name 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(); } } /// /// End write of bookmark /// /// bookmark name public void WriteEndBookmark(string strName) { } #endregion #region WriteLineBreak /// /// Write a line break /// public void WriteLineBreak() { if (CollectionInfo == false) { Writer.WriteKeyword("line"); } } #endregion #region WriteImage /// /// Write image /// /// Image /// Pixel width /// Pixel height /// Image binary data 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 } }