From c302dc2ca9bc39c411a76abc7b1f3a288ac98b05 Mon Sep 17 00:00:00 2001 From: eisbaer66 Date: Wed, 10 Apr 2019 01:52:19 +0200 Subject: [PATCH] rtf --- RtfWriter.Standard/RtfAbstract.cs | 72 ++ RtfWriter.Standard/RtfBasics.cs | 702 +++++++++++ RtfWriter.Standard/RtfBlockList.cs | 165 +++ RtfWriter.Standard/RtfCharFormat.cs | 318 +++++ RtfWriter.Standard/RtfDocument.cs | 217 ++++ RtfWriter.Standard/RtfFieldControlWord.cs | 44 + RtfWriter.Standard/RtfFootnote.cs | 43 + RtfWriter.Standard/RtfHeaderFooter.cs | 45 + RtfWriter.Standard/RtfParagraph.cs | 536 ++++++++ RtfWriter.Standard/RtfSection.cs | 106 ++ RtfWriter.Standard/RtfSectionFooter.cs | 30 + RtfWriter.Standard/RtfTable.cs | 629 ++++++++++ RtfWriter.Standard/RtfTableCell.cs | 209 ++++ RtfWriter.Standard/RtfUtility.cs | 151 +++ RtfWriter.Standard/RtfWriter.Standard.csproj | 7 + .../ConverterTests.cs | 21 +- .../Tf2Rebalance.CreateSummary.Tests.csproj | 6 + .../higps_summary.rtf | 1082 +++++++++++++++++ ...f2rebalance_attributes.example_summary.rtf | 68 ++ Tf2Rebalance.CreateSummary.sln | 10 +- .../Formater/IRebalanceInfoFormater.cs | 10 + .../RebalanceInfoFormaterBase.cs} | 46 +- .../Formater/RebalanceInfoRtfFormater.cs | 56 + .../Formater/RebalanceInfoTextFormater.cs | 41 + Tf2Rebalance.CreateSummary/Program.cs | 5 +- .../Properties/launchSettings.json | 2 +- .../Tf2Rebalance.CreateSummary.csproj | 1 + 27 files changed, 4570 insertions(+), 52 deletions(-) create mode 100644 RtfWriter.Standard/RtfAbstract.cs create mode 100644 RtfWriter.Standard/RtfBasics.cs create mode 100644 RtfWriter.Standard/RtfBlockList.cs create mode 100644 RtfWriter.Standard/RtfCharFormat.cs create mode 100644 RtfWriter.Standard/RtfDocument.cs create mode 100644 RtfWriter.Standard/RtfFieldControlWord.cs create mode 100644 RtfWriter.Standard/RtfFootnote.cs create mode 100644 RtfWriter.Standard/RtfHeaderFooter.cs create mode 100644 RtfWriter.Standard/RtfParagraph.cs create mode 100644 RtfWriter.Standard/RtfSection.cs create mode 100644 RtfWriter.Standard/RtfSectionFooter.cs create mode 100644 RtfWriter.Standard/RtfTable.cs create mode 100644 RtfWriter.Standard/RtfTableCell.cs create mode 100644 RtfWriter.Standard/RtfUtility.cs create mode 100644 RtfWriter.Standard/RtfWriter.Standard.csproj create mode 100644 Tf2Rebalance.CreateSummary.Tests/higps_summary.rtf create mode 100644 Tf2Rebalance.CreateSummary.Tests/tf2rebalance_attributes.example_summary.rtf create mode 100644 Tf2Rebalance.CreateSummary/Formater/IRebalanceInfoFormater.cs rename Tf2Rebalance.CreateSummary/{IRebalanceInfoFormater.cs => Formater/RebalanceInfoFormaterBase.cs} (73%) create mode 100644 Tf2Rebalance.CreateSummary/Formater/RebalanceInfoRtfFormater.cs create mode 100644 Tf2Rebalance.CreateSummary/Formater/RebalanceInfoTextFormater.cs diff --git a/RtfWriter.Standard/RtfAbstract.cs b/RtfWriter.Standard/RtfAbstract.cs new file mode 100644 index 0000000..918f54a --- /dev/null +++ b/RtfWriter.Standard/RtfAbstract.cs @@ -0,0 +1,72 @@ +namespace RtfWriter.Standard +{ + /// + /// Internal use only. + /// Objects that are renderable can emit RTF code. + /// + abstract public class RtfRenderable + { + /// + /// Internal use only. + /// Emit RTF code. + /// + /// RTF code + abstract public string render(); + } + + /// + /// Internal use only. + /// RtfBlock is a content block that cannot contain other blocks. + /// For example, an image is an RtfBlock because it cannot contain + /// other content block such as another image, a paragraph, a table, + /// etc. + /// + abstract public class RtfBlock : RtfRenderable + { + /// + /// How this block is aligned in its containing block. + /// + abstract public Align Alignment { get; set; } + /// + /// By what distance this block is separated from others in + /// the containing block. + /// + abstract public Margins Margins { get; } + /// + /// Default character formats. + /// + abstract public RtfCharFormat DefaultCharFormat { get; } + /// + /// When set to true, this block will be arranged in the beginning + /// of a new page. + /// + abstract public bool StartNewPage { get; set; } + /// + /// Internal use only. + /// Beginning RTF control words for this block. + /// + abstract internal string BlockHead { set; } + /// + /// Internal use only. + /// Ending RTF control word for this block. + /// + abstract internal string BlockTail { set; } + + protected string AlignmentCode() + { + switch (Alignment) + { + case Align.Left: + return @"\ql"; + case Align.Right: + return @"\qr"; + case Align.Center: + return @"\qc"; + case Align.FullyJustify: + return @"\qj"; + default: + return @"\qd"; + } + } + } +} diff --git a/RtfWriter.Standard/RtfBasics.cs b/RtfWriter.Standard/RtfBasics.cs new file mode 100644 index 0000000..26d99c0 --- /dev/null +++ b/RtfWriter.Standard/RtfBasics.cs @@ -0,0 +1,702 @@ +using System; + +namespace RtfWriter.Standard +{ + /// + /// Horizontal alignment. + /// + public enum Align + { + None = 0, + Left, + Right, + Center, + FullyJustify, + Distributed, + } + + /// + /// Vertical alignment. + /// + public enum AlignVertical + { + Top = 1, + Middle, + Bottom, + } + + /// + /// Top, bottom, left, and right. + /// + public enum Direction + { + Top = 0, + Right, + Bottom, + Left, + } + + /// + /// Types of paper sizes. + /// + public enum PaperSize + { + Letter = 1, + A4, + A3, + } + + /// + /// Types of paper orientaion. + /// + public enum PaperOrientation + { + Portrait = 1, + Landscape, + } + + /// + /// Types of locality. + /// + public enum Lcid + { + TraditionalChinese = 1028, + English = 1033, + French = 1036, + German = 1031, + Italian = 1040, + Japanese = 1041, + Korean = 1042, + SimplifiedChinese = 2052, + Spanish = 3082, + } + + /// + /// Types of font styles. + /// + public enum FontStyleFlag + { + Bold = 0x01, + Italic = 0x02, + Underline = 0x04, + Super = 0x08, + Sub = 0x10, + Scaps = 0x20, + Strike = 0x40, + } + + /// + /// Types of image files. + /// + public enum ImageFileType + { + Jpg = 1, + Gif, + Png + } + + /// + /// Types of border styles. + /// + public enum BorderStyle + { + None = 0, + Single, + Dotted, + Dashed, + Double, + } + + /// + /// Types of two-in-one style quoting symbols. + /// (For Far East character formatting.) + /// + public enum TwoInOneStyle + { + NotEnabled = 0, + None, + Parentheses, + SquareBrackets, + AngledBrackets, + Braces, + } + + /// + /// Internal use only. + /// Specify whether a RtfHeaderFooter object is header or footer. + /// + internal enum HeaderFooterType + { + Header = 1, + Footer, + } + + /// + /// Specify whether an RtfSection is of type Start or End + /// + public enum SectionStartEnd + { + Start, + End + } + + /// + /// Container for a set of font styles. It is helpful when more than + /// one of the font styles (e.g., both bold and italic) are associated with + /// some characters. + /// + public class FontStyle + { + private UInt32 _styleAdd, _styleRemove; + + /// + /// Internal use only. + /// Constructor that initializes as containing none of the styles. + /// + internal FontStyle() + { + _styleAdd = _styleRemove = 0; + } + + /// + /// Internal use only. + /// Copy constructor. + /// + /// + internal FontStyle(FontStyle src) + { + _styleAdd = src._styleAdd; + _styleRemove = src._styleRemove; + } + + /// + /// Add a font style to the set. Adding a font style + /// that is already in the set has no effect. + /// + /// Font style to be added. + public void addStyle(FontStyleFlag sty) + { + _styleAdd |= (UInt32) sty; + _styleRemove &= ~( (UInt32) sty ); + } + + /// + /// Remove a font style from the set. Removing a font style + /// that is already not in the set has no effect. + /// + /// Font style to be removed. + public void removeStyle(FontStyleFlag sty) + { + _styleAdd &= ~( (UInt32) sty ); + _styleRemove |= (UInt32) sty; + } + + /// + /// Test whether a font style is in the set. + /// + /// Font style to be tested. + /// True if the font style is in the set; false otherwise. + public bool containsStyleAdd( FontStyleFlag sty ) + { + if ((_styleAdd & (UInt32) sty) > 0) { + return true; + } + return false; + } + + public bool containsStyleRemove(FontStyleFlag sty) + { + if ((_styleRemove & (UInt32) sty) > 0) { + return true; + } + return false; + } + + /// + /// Indicate whether the set is empty. + /// + public bool IsEmpty + { + get + { + return _styleAdd == 0 && _styleRemove == 0; + } + } + } + + /// + /// A descriptor for a font. Fonts are assigned as descriptors, + /// not names (e.g., Times New Roman). + /// + public class FontDescriptor + { + private int _descr; + + /// + /// Internal use only. + /// Constructor. + /// + /// Internal representative integer of the font. + internal FontDescriptor(int descr) + { + _descr = descr; + } + + /// + /// Internal use only. + /// Get internal representative integer of the font. + /// + internal int Value + { + get + { + return _descr; + } + } + } + + /// + /// A descriptor for a color. Colors are assigned as descriptors, + /// not names (e.g., #ff0000, or RED). + /// + public class ColorDescriptor + { + private int _descr; + + /// + /// Internal use only. + /// Constructor. + /// + /// Internal representative integer for the color. + internal ColorDescriptor(int descr) + { + _descr = descr; + } + + /// + /// Internal use only. + /// Get internal representative integer for the color. + /// + internal int Value + { + get + { + return _descr; + } + } + } + + /// + /// Margin settings for a content block, containing four margin values. + /// + public class Margins + { + private float[] _margins; + + /// + /// Internal use only. + /// Constructor that initializes all four margins as -1. + /// + internal Margins() + { + _margins = new float[4]; + } + + /// + /// Internal use only. + /// Constructor that gives initial values for all four margins. + /// + /// Top margin size in points. + /// Right margin size in points. + /// Bottom margin size in points. + /// Left margin size in points. + internal Margins(float t, float r, float b, float l) + : this() + { + _margins[(int) Direction.Top] = t; + _margins[(int) Direction.Right] = r; + _margins[(int) Direction.Bottom] = b; + _margins[(int) Direction.Left] = l; + } + + /// + /// Indexer that allows getting and setting of one of the four margin values. + /// + /// The direction at which the margin locates. One of top, + /// right, bottom, left. + /// Margin size in points. + public float this[Direction d] + { + get + { + int i = (int) d; + if (i >= 0 && i < _margins.Length) { + return _margins[i]; + } + throw new Exception("Not a valid direction."); + } + set + { + int i = (int)d; + if (i >= 0 && i < _margins.Length) { + _margins[i] = value; + } else { + throw new Exception("Not a valid direction."); + } + } + } + + public bool equals( Margins margins ) + { + return ( margins._margins[(int) Direction.Bottom] == _margins[(int) Direction.Bottom] ) && + ( margins._margins[(int) Direction.Left] == _margins[(int) Direction.Left] ) && + ( margins._margins[(int) Direction.Right] == _margins[(int) Direction.Right] ) && + ( margins._margins[(int) Direction.Top] == _margins[(int) Direction.Top] ); + } + } + + /// + /// Border attributes for table cells. + /// + public class Border + { + private BorderStyle _style; + private float _width; + private ColorDescriptor _colorDesc; + + /// + /// Internal use only. + /// Default constructor that sets border style to None. + /// + internal Border() + { + _style = BorderStyle.None; + _width = 0.5F; + _colorDesc = new ColorDescriptor(0); + } + + /// + /// Indirect use only. + /// See if two borders are equal. + /// + /// Border object to be compared with. + /// True if the two borders are equal; false otherwise. + public override bool Equals(object obj) + { + Border bdr = (Border) obj; + return (this.Style == bdr.Style && this.Width == bdr.Width); + } + + /// + /// Indirect use only. + /// Differentiate borders. + /// + /// A hash code representing different sets of border attributes. + public override int GetHashCode() + { + return _width.GetHashCode() * 1000 + (int) _style; + } + + /// + /// Get or set the border style. + /// + public BorderStyle Style + { + get + { + return _style; + } + set + { + _style = value; + } + } + + /// + /// Get or set the width of the border line. + /// + public float Width + { + get + { + return _width; + } + set + { + _width = value; + } + } + + /// + /// Get or set the border color. + /// + public ColorDescriptor Color + { + get + { + return _colorDesc; + } + set + { + _colorDesc = value; + } + } + } + + /// + /// Border settings for a table cell, containing four sets of border attributes. + /// + public class Borders + { + private Border[] _borders; + + /// + /// Internal use only. + /// Default constructor that sets all border style to None. + /// + internal Borders() + { + _borders = new Border[4]; + for (int i = 0; i < _borders.Length; i++) { + _borders[i] = new Border(); + } + } + + /// + /// Indexer that gets border attributes for borders in any of the four + /// direction. + /// + /// The direction at which the border locates. One of top + /// right, bottom, left. + /// The border attributes. + public Border this[Direction d] + { + get + { + int i = (int)d; + if (i >= 0 && i < _borders.Length) { + return _borders[i]; + } + throw new Exception("Not a valid direction."); + } + } + } + + /// + /// Colors to be applied in the document. Note that objects of this class + /// cannot be assigned to document directly. Instead, they work through + /// ColorDescriptor objects. + /// + public class RtfColor + { + private int _color; + + /// + /// Default constructor that initialized as black color. + /// + public RtfColor() + { + _color = 0; + } + + /// + /// Constructor that initializes using RGB values. + /// + /// Red component of the color. + /// Green component of the color. + /// Blue component of the color. + public RtfColor(byte red, byte green, byte blue) + { + _color = (red << 16) + (green << 8) + blue; + } + + /// + /// Constructor that initializes using a string representation of + /// a hexadecimal value. + /// + /// String representation of a hexadecimal value, such + /// as "FF0000" or "00AB12". + public RtfColor(string hex) + { + if (hex == null || hex.Length != 6) { + throw new Exception("String parameter hex should be of length 6."); + } + hex = hex.ToUpper(); + for (int i = 0; i < hex.Length; i++) { + if (!Char.IsDigit(hex[i]) && (hex[i] < 'A' || hex[i] > 'F')) { + throw new Exception("Characters of parameter hex should be in [0-9,A-F,a-f]"); + } + } + byte red = Convert.ToByte(hex.Substring(0, 2), 16); + byte green = Convert.ToByte(hex.Substring(2, 2), 16); + byte blue = Convert.ToByte(hex.Substring(4, 2), 16); + _color = (red << 16) + (green << 8) + blue; + } + + /// + /// Constructor that initializes using System Drawing colour + /// + /// System Drawing Colour + public RtfColor(System.Drawing.Color color) + { + _color = ( color.R << 16 ) + ( color.G << 8 ) + color.B; + } + + /// + /// Indirect use only. + /// See if two colors are the same. + /// + /// Color object to be compared with. + /// True if two colors are identical; false otherwise. + public override bool Equals(object obj) + { + RtfColor b = (RtfColor) obj; + return (b._color == this._color); + } + + /// + /// Indirect use only. + /// Differentiate colors. + /// + /// A hash code used to differentiate colors. + public override int GetHashCode() + { + return _color; + } + + /// + /// Get or set the red component of the color. + /// + internal string Red + { + get + { + return ((_color >> 16) % 256).ToString(); + } + } + + /// + /// Get or set the green component of the color. + /// + internal string Green + { + get + { + return ((_color >> 8) % 256).ToString(); + } + } + + /// + /// Get or set the blue component of the color. + /// + internal string Blue + { + get + { + return (_color % 256).ToString(); + } + } + } + + /// + /// Internal use only. + /// A collection of cell merging information associated with each table cell being merged. + /// + internal class CellMergeInfo + { + private int _rowSpan; + private int _colSpan; + private int _rowIndex; + private int _colIndex; + private RtfTableCell _representative; + + /// + /// Internal use only. + /// Constructor. + /// + /// Representative cell for the cell. + /// (Usually the one located at top left corner of the group of merged cell.) + /// Number of rows that this group of merged cells spans. + /// Number of columns that this group of merged cells spans. + /// The relative row index of the cell within this group + /// of merged cells. + /// The relative column index of the cell within this group + /// of merged cells. + internal CellMergeInfo(RtfTableCell representative, int rowSpan, int colSpan, + int rowIndex, int colIndex) + { + _representative = representative; + _rowSpan = rowSpan; + _colSpan = colSpan; + _rowIndex = rowIndex; + _colIndex = colIndex; + } + + /// + /// Get the number of rows that this group of merged cells spans. + /// + internal int RowSpan + { + get + { + return _rowSpan; + } + } + + /// + /// Get the number of columns that this group of merged cells spans. + /// + internal int ColSpan + { + get + { + return _colSpan; + } + } + + /// + /// Get the relative row index of the cell within this group of merged cells. + /// + internal int RowIndex + { + get + { + return _rowIndex; + } + } + + /// + /// Get the relative column index of the cell within this group of merged cells. + /// + internal int ColIndex + { + get + { + return _colIndex; + } + } + + /// + /// Get the representative cell of the cell. + /// + internal RtfTableCell Representative + { + get + { + return _representative; + } + } + } + + /// + /// Internal use only. + /// Constant values for default document settings. + /// + internal static class DefaultValue + { + public static int FontSize = 12; + public static string Font = "Times New Roman"; + public static float MarginLarge = 50; // used for long edges of A4 (was 90) + public static float MarginSmall = 50; // used for short edges of A4 (was 72) + } +} diff --git a/RtfWriter.Standard/RtfBlockList.cs b/RtfWriter.Standard/RtfBlockList.cs new file mode 100644 index 0000000..4ed5071 --- /dev/null +++ b/RtfWriter.Standard/RtfBlockList.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace RtfWriter.Standard +{ + /// + /// A container for an array of content blocks. For example, a footnote + /// is a RtfBlockList because it may contains a paragraph and an image. + /// + public class RtfBlockList : RtfRenderable + { + /// + /// Storage for array of content blocks. + /// + protected List _blocks; + /// + /// Default character formats within this container. + /// + protected RtfCharFormat _defaultCharFormat; + + private bool _allowParagraph; + private bool _allowFootnote; + private bool _allowControlWord; + private bool _allowImage; + private bool _allowTable; + + /// + /// Internal use only. + /// Default constructor that allows containing all types of content blocks. + /// + internal RtfBlockList() + : this(true, true, true, true, true) + { + } + + /// + /// Internal use only. + /// Constructor specifying allowed content blocks to be contained. + /// + /// Whether an RtfParagraph is allowed. + /// Whether RtfTable is allowed. + internal RtfBlockList(bool allowParagraph, bool allowTable) + : this(allowParagraph, true, true, true, allowTable) + { + } + + /// + /// Internal use only. + /// Constructor specifying allowed content blocks to be contained. + /// + /// Whether an RtfParagraph is allowed. + /// Whether an RtfFootnote is allowed in contained RtfParagraph. + /// Whether an field control word is allowed in contained + /// RtfParagraph. + /// Whether RtfImage is allowed. + /// Whether RtfTable is allowed. + internal RtfBlockList(bool allowParagraph, bool allowFootnote, bool allowControlWord, + bool allowImage, bool allowTable) + { + _blocks = new List(); + _allowParagraph = allowParagraph; + _allowFootnote = allowFootnote; + _allowControlWord = allowControlWord; + _allowImage = allowImage; + _allowTable = allowTable; + _defaultCharFormat = null; + } + + /// + /// Get default character formats within this container. + /// + public RtfCharFormat DefaultCharFormat + { + get + { + if (_defaultCharFormat == null) { + _defaultCharFormat = new RtfCharFormat(-1, -1, 1); + } + return _defaultCharFormat; + } + } + + private void addBlock(RtfBlock block) + { + if (block != null) { + _blocks.Add(block); + } + } + + /// + /// Add a paragraph to this container. + /// + /// Paragraph being added. + public RtfParagraph addParagraph() + { + if (!_allowParagraph) { + throw new Exception("Paragraph is not allowed."); + } + RtfParagraph block = new RtfParagraph(_allowFootnote, _allowControlWord); + addBlock(block); + return block; + } + + /// + /// Add a section to this container + /// + public RtfSection addSection(SectionStartEnd type, RtfDocument doc) + { + var block = new RtfSection(type, doc); + addBlock(block); + return block; + } + + /// + /// Add a table to this container. + /// + /// Number of rows in the table. + /// Number of columns in the table. + /// Horizontabl width (in points) of the table. + /// The size of font used in this table. This is used to calculate margins. + /// Table begin added. + public RtfTable addTable(int rowCount, int colCount, float horizontalWidth, float fontSize) + { + if (!_allowTable) { + throw new Exception("Table is not allowed."); + } + RtfTable block = new RtfTable(rowCount, colCount, horizontalWidth, fontSize); + addBlock(block); + return block; + } + + /// + /// Internal use only. + /// Transfer all content blocks to another RtfBlockList object. + /// + /// Target RtfBlockList object to transfer to. + internal void transferBlocksTo(RtfBlockList target) + { + for (int i = 0; i < _blocks.Count; i++) { + target.addBlock(_blocks[i]); + } + _blocks.Clear(); + } + + /// + /// Internal use only. + /// Emit RTF code. + /// + /// Resulting RTF code for this object. + public override string render() + { + StringBuilder result = new StringBuilder(); + + result.AppendLine(); + for (int i = 0; i < _blocks.Count; i++) { + if (_defaultCharFormat != null && _blocks[i].DefaultCharFormat != null) { + _blocks[i].DefaultCharFormat.copyFrom(_defaultCharFormat); + } + result.AppendLine(_blocks[i].render()); + } + return result.ToString(); + } + } +} diff --git a/RtfWriter.Standard/RtfCharFormat.cs b/RtfWriter.Standard/RtfCharFormat.cs new file mode 100644 index 0000000..f1d137f --- /dev/null +++ b/RtfWriter.Standard/RtfCharFormat.cs @@ -0,0 +1,318 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace RtfWriter.Standard +{ + /// + /// Summary description for RtfCharFormat + /// + public class RtfCharFormat + { + private int _begin; + private int _end; + private FontDescriptor _font; + private FontDescriptor _ansiFont; + private float _fontSize; + private FontStyle _fontStyle; + private ColorDescriptor _bgColor; + private ColorDescriptor _fgColor; + private TwoInOneStyle _twoInOneStyle; + private string _bookmark; + private string _localHyperlink; + private string _localHyperlinkTip; + + + internal RtfCharFormat(int begin, int end, int textLength) + { + // Note: + // In the condition that ``_begin == _end == -1'', + // the character formatting is applied to the whole paragraph. + _begin = -1; + _end = -1; + _font = null; // do not specify font (use default one) + _ansiFont = null; // do not specify font (use default one) + _fontSize = -1; // do not specify font size (use default one) + _fontStyle = new FontStyle(); + _bgColor = null; + _fgColor = null; + _twoInOneStyle = TwoInOneStyle.NotEnabled; + _bookmark = ""; + setRange(begin, end, textLength); + } + + internal void copyFrom(RtfCharFormat src) + { + if (src == null) { + return; + } + _begin = src._begin; + _end = src._end; + if (_font == null && src._font != null) { + _font = new FontDescriptor(src._font.Value); + } + if (_ansiFont == null && src._ansiFont != null) { + _ansiFont = new FontDescriptor(src._ansiFont.Value); + } + if (_fontSize < 0 && src._fontSize >= 0) { + _fontSize = src._fontSize; + } + if (_fontStyle.IsEmpty && !src._fontStyle.IsEmpty) { + _fontStyle = new FontStyle(src._fontStyle); + } + if (_bgColor == null && src._bgColor != null) { + _bgColor = new ColorDescriptor(src._bgColor.Value); + } + if (_fgColor == null && src._fgColor != null) { + _fgColor = new ColorDescriptor(src._fgColor.Value); + } + } + + private void setRange(int begin, int end, int textLength) + { + if (begin > end) { + throw new Exception("Invalid range: (" + begin + ", " + end + ")"); + } else if (begin < 0 || end < 0) { + if (begin != -1 || end != -1) { + throw new Exception("Invalid range: (" + begin + ", " + end + ")"); + } + } + if (end >= textLength) { + throw new Exception("Range ending out of range: " + end); + } + _begin = begin; + _end = end; + } + + internal int Begin + { + get + { + return _begin; + } + } + + internal int End + { + get + { + return _end; + } + } + + public string Bookmark + { + get + { + return _bookmark; + } + set + { + _bookmark = value; + } + } + + public string LocalHyperlink + { + get + { + return _localHyperlink; + } + set + { + _localHyperlink = value; + } + } + + public string LocalHyperlinkTip + { + get + { + return _localHyperlinkTip; + } + set + { + _localHyperlinkTip = value; + } + } + + public FontDescriptor Font + { + get + { + return _font; + } + set + { + _font = value; + } + } + + public FontDescriptor AnsiFont + { + get + { + return _ansiFont; + } + set + { + _ansiFont = value; + } + } + + public float FontSize + { + get + { + return _fontSize; + } + set + { + _fontSize = value; + } + } + + public FontStyle FontStyle + { + get + { + return _fontStyle; + } + } + + public ColorDescriptor FgColor + { + get + { + return _fgColor; + } + set + { + _fgColor = value; + } + } + + public ColorDescriptor BgColor + { + get + { + return _bgColor; + } + set + { + _bgColor = value; + } + } + + public TwoInOneStyle TwoInOneStyle + { + get + { + return _twoInOneStyle; + } + set + { + _twoInOneStyle = value; + } + } + + internal string renderHead() + { + StringBuilder result = new StringBuilder("{"); + + if (!string.IsNullOrEmpty(_localHyperlink)) { + result.Append(@"{\field{\*\fldinst HYPERLINK \\l "); + result.Append("\"" + _localHyperlink + "\""); + if (!string.IsNullOrEmpty(_localHyperlinkTip)) result.Append(" \\\\o \"" + _localHyperlinkTip + "\""); + result.Append(@"}{\fldrslt{"); + } + + + if (_font != null || _ansiFont != null) { + if (_font == null) { + result.Append(@"\f" + _ansiFont.Value); + } else if (_ansiFont == null) { + result.Append(@"\f" + _font.Value); + } else { + result.Append(@"\loch\af" + _ansiFont.Value + @"\hich\af" + _ansiFont.Value + + @"\dbch\af" + _font.Value); + } + } + if (_fontSize > 0) { + result.Append(@"\fs" + RtfUtility.pt2HalfPt(_fontSize)); + } + if (_fgColor != null) { + result.Append(@"\cf" + _fgColor.Value); + } + if (_bgColor != null) { + result.Append(@"\chshdng0\chcbpat" + _bgColor.Value + @"\cb" + _bgColor.Value); + } + + foreach(var fontStyle in _fontStyleMap) + { + if (FontStyle.containsStyleAdd(fontStyle.Key)) { + result.Append(@"\" + fontStyle.Value); + } else if(FontStyle.containsStyleRemove(fontStyle.Key)) { + result.Append(@"\" + fontStyle.Value + "0"); + } + } + if (_twoInOneStyle != TwoInOneStyle.NotEnabled) { + result.Append(@"\twoinone"); + switch (_twoInOneStyle) { + case TwoInOneStyle.None: + result.Append("0"); + break; + case TwoInOneStyle.Parentheses: + result.Append("1"); + break; + case TwoInOneStyle.SquareBrackets: + result.Append("2"); + break; + case TwoInOneStyle.AngledBrackets: + result.Append("3"); + break; + case TwoInOneStyle.Braces: + result.Append("4"); + break; + } + } + + if (result.ToString().Contains(@"\")) { + result.Append(" "); + } + + if (!string.IsNullOrEmpty(_bookmark)) { + result.Append(@"{\*\bkmkstart " + _bookmark + "}"); + } + + return result.ToString(); + } + + internal string renderTail() + { + StringBuilder result = new StringBuilder(""); + + if (!string.IsNullOrEmpty(_bookmark)) { + result.Append(@"{\*\bkmkend " + _bookmark + "}"); + } + + if (!string.IsNullOrEmpty(_localHyperlink)) { + result.Append(@"}}}"); + } + + result.Append("}"); + return result.ToString(); + } + + private static IDictionary _fontStyleMap = new Dictionary + { + {FontStyleFlag.Bold, "b"}, + {FontStyleFlag.Italic, "i"}, + {FontStyleFlag.Scaps, "scaps"}, + {FontStyleFlag.Strike, "strike"}, + {FontStyleFlag.Sub, "sub"}, + {FontStyleFlag.Super, "super"}, + {FontStyleFlag.Underline, "ul"} + }; + } +} diff --git a/RtfWriter.Standard/RtfDocument.cs b/RtfWriter.Standard/RtfDocument.cs new file mode 100644 index 0000000..92964b9 --- /dev/null +++ b/RtfWriter.Standard/RtfDocument.cs @@ -0,0 +1,217 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace RtfWriter.Standard +{ + /// + /// Summary description for RtfDocument + /// + public class RtfDocument : RtfBlockList + { + private PaperSize _paper; + private PaperOrientation _orientation; + private Margins _margins; + private Lcid _lcid; + private List _fontTable; + private List _colorTable; + private RtfHeaderFooter _header; + private RtfHeaderFooter _footer; + + public RtfDocument(PaperSize paper, PaperOrientation orientation, Lcid lcid) + { + _paper = paper; + _orientation = orientation; + _margins = new Margins(); + if (_orientation == PaperOrientation.Portrait) { + _margins[Direction.Top] = DefaultValue.MarginSmall; + _margins[Direction.Right] = DefaultValue.MarginLarge; + _margins[Direction.Bottom] = DefaultValue.MarginSmall; + _margins[Direction.Left] = DefaultValue.MarginLarge; + } else { // landscape + _margins[Direction.Top] = DefaultValue.MarginLarge; + _margins[Direction.Right] = DefaultValue.MarginSmall; + _margins[Direction.Bottom] = DefaultValue.MarginLarge; + _margins[Direction.Left] = DefaultValue.MarginSmall; + } + _lcid = lcid; + _fontTable = new List(); + _fontTable.Add(DefaultValue.Font); // default font + _colorTable = new List(); + _colorTable.Add(new RtfColor()); // default color + _header = null; + _footer = null; + } + + public Margins Margins + { + get + { + return _margins; + } + set + { + _margins = value; + } + } + + public RtfHeaderFooter Header + { + get + { + if (_header == null) { + _header = new RtfHeaderFooter(HeaderFooterType.Header); + } + return _header; + } + } + + public RtfHeaderFooter Footer + { + get + { + if (_footer == null) { + _footer = new RtfHeaderFooter(HeaderFooterType.Footer); + } + return _footer; + } + } + + public ColorDescriptor DefaultColor + { + get + { + return new ColorDescriptor(0); + } + } + + public FontDescriptor DefaultFont + { + get + { + return new FontDescriptor(0); + } + } + + public void setDefaultFont(string fontName) + { + _fontTable[0] = fontName; + } + + public FontDescriptor createFont(string fontName) + { + if (_fontTable.Contains(fontName)) { + return new FontDescriptor(_fontTable.IndexOf(fontName)); + } + _fontTable.Add(fontName); + return new FontDescriptor(_fontTable.IndexOf(fontName)); + } + + public ColorDescriptor createColor(RtfColor color) + { + if (_colorTable.Contains(color)) { + return new ColorDescriptor(_colorTable.IndexOf(color)); + } + _colorTable.Add(color); + return new ColorDescriptor(_colorTable.IndexOf(color)); + } + + public ColorDescriptor createColor(System.Drawing.Color color) + { + var rtfColor = new RtfColor(color.R, color.G, color.B); + return createColor(rtfColor); + } + + public RtfTable addTable(int rowCount, int colCount, float fontSize) + { + float horizontalWidth; + + if (_orientation == PaperOrientation.Portrait) { + horizontalWidth = RtfUtility.paperWidthInPt(_paper, _orientation) + - _margins[Direction.Left] - _margins[Direction.Right]; + } else { + horizontalWidth = RtfUtility.paperHeightInPt(_paper, _orientation) + - _margins[Direction.Left] - _margins[Direction.Right]; + } + return base.addTable(rowCount, colCount, horizontalWidth, fontSize); + } + + public override string render() + { + StringBuilder rtf = new StringBuilder(); + + // --------------------------------------------------- + // Prologue + // --------------------------------------------------- + rtf.AppendLine(@"{\rtf1\ansi\deff0"); + rtf.AppendLine(); + + // --------------------------------------------------- + // Insert font table + // --------------------------------------------------- + rtf.AppendLine(@"{\fonttbl"); + for (int i = 0; i < _fontTable.Count; i++) { + rtf.AppendLine(@"{\f" + i + " " + RtfUtility.unicodeEncode(_fontTable[i].ToString()) + ";}"); + } + rtf.AppendLine("}"); + rtf.AppendLine(); + + // --------------------------------------------------- + // Insert color table + // --------------------------------------------------- + rtf.AppendLine(@"{\colortbl"); + rtf.AppendLine(";"); + for (int i = 1; i < _colorTable.Count; i++) { + RtfColor c = _colorTable[i]; + rtf.AppendLine(@"\red" + c.Red + @"\green" + c.Green + @"\blue" + c.Blue + ";"); + } + rtf.AppendLine("}"); + rtf.AppendLine(); + + // --------------------------------------------------- + // Preliminary + // --------------------------------------------------- + rtf.AppendLine(@"\deflang" + (int)_lcid + @"\plain\fs" + + RtfUtility.pt2HalfPt(DefaultValue.FontSize) + @"\widowctrl\hyphauto\ftnbj"); + // page size + rtf.AppendLine(@"\paperw" + RtfUtility.paperWidthInTwip(_paper, _orientation) + + @"\paperh" + RtfUtility.paperHeightInTwip(_paper, _orientation)); + // page margin + rtf.AppendLine(@"\margt" + RtfUtility.pt2Twip(_margins[Direction.Top])); + rtf.AppendLine(@"\margr" + RtfUtility.pt2Twip(_margins[Direction.Right])); + rtf.AppendLine(@"\margb" + RtfUtility.pt2Twip(_margins[Direction.Bottom])); + rtf.AppendLine(@"\margl" + RtfUtility.pt2Twip(_margins[Direction.Left])); + // orientation + if (_orientation == PaperOrientation.Landscape) { + rtf.AppendLine(@"\landscape"); + } + // header/footer + if (_header != null) { + rtf.Append(_header.render()); + } + if (_footer != null) { + rtf.Append(_footer.render()); + } + rtf.AppendLine(); + + // --------------------------------------------------- + // Document body + // --------------------------------------------------- + rtf.Append(base.render()); + + // --------------------------------------------------- + // Ending + // --------------------------------------------------- + rtf.AppendLine("}"); + + return rtf.ToString(); + } + + public void save(string fname) + { + StreamWriter w = new StreamWriter(fname); + w.Write(render()); + w.Close(); + } + } +} diff --git a/RtfWriter.Standard/RtfFieldControlWord.cs b/RtfWriter.Standard/RtfFieldControlWord.cs new file mode 100644 index 0000000..f99de7a --- /dev/null +++ b/RtfWriter.Standard/RtfFieldControlWord.cs @@ -0,0 +1,44 @@ +namespace RtfWriter.Standard +{ + public class RtfFieldControlWord : RtfRenderable + { + public enum FieldType + { + None = 0, + Page, + NumPages, + Date, + Time, + } + + private static string[] ControlWordPool = new string[] { + // correspond with FiledControlWords enum + "", + @"{\field{\*\fldinst PAGE }}", + @"{\field{\*\fldinst NUMPAGES }}", + @"{\field{\*\fldinst DATE }}", + @"{\field{\*\fldinst TIME }}" + }; + + private int _position; + private FieldType _type; + + internal RtfFieldControlWord(int position, FieldType type) + { + _position = position; + _type = type; + } + + internal int Position + { + get { + return _position; + } + } + + public override string render() + { + return ControlWordPool[(int)_type]; + } + } +} diff --git a/RtfWriter.Standard/RtfFootnote.cs b/RtfWriter.Standard/RtfFootnote.cs new file mode 100644 index 0000000..9645d8f --- /dev/null +++ b/RtfWriter.Standard/RtfFootnote.cs @@ -0,0 +1,43 @@ +using System; +using System.Text; + +namespace RtfWriter.Standard +{ + /// + /// Summary description for RtfFootnote + /// + public class RtfFootnote : RtfBlockList + { + private int _position; + + internal RtfFootnote(int position, int textLength) + : base(true, false, false, true, false) + { + if (position < 0 || position >= textLength) { + throw new Exception("Invalid footnote position: " + position + + " (text length=" + textLength + ")"); + } + _position = position; + } + + internal int Position + { + get + { + return _position; + } + } + + public override string render() + { + StringBuilder result = new StringBuilder(); + + result.AppendLine(@"{\super\chftn}"); + result.AppendLine(@"{\footnote\plain\chftn"); + ((RtfBlock)base._blocks[base._blocks.Count - 1]).BlockTail = "}"; + result.Append(base.render()); + result.AppendLine("}"); + return result.ToString(); + } + } +} diff --git a/RtfWriter.Standard/RtfHeaderFooter.cs b/RtfWriter.Standard/RtfHeaderFooter.cs new file mode 100644 index 0000000..963889e --- /dev/null +++ b/RtfWriter.Standard/RtfHeaderFooter.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections; +using System.Text; + +namespace RtfWriter.Standard +{ + /// + /// Summary description for RtfHeaderFooter + /// + public class RtfHeaderFooter : RtfBlockList + { + private Hashtable _magicWords; + private HeaderFooterType _type; + + internal RtfHeaderFooter(HeaderFooterType type) + : base(true, false, true, true, false) + { + _magicWords = new Hashtable(); + _type = type; + } + + public override string render() + { + StringBuilder result = new StringBuilder(); + + if (_type == HeaderFooterType.Header) { + result.AppendLine(@"{\header"); + } else if (_type == HeaderFooterType.Footer) { + result.AppendLine(@"{\footer"); + } else { + throw new Exception("Invalid HeaderFooterType"); + } + result.AppendLine(); + for (int i = 0; i < base._blocks.Count; i++) { + if (base._defaultCharFormat != null + && ((RtfBlock)base._blocks[i]).DefaultCharFormat != null) { + ((RtfBlock)base._blocks[i]).DefaultCharFormat.copyFrom(base._defaultCharFormat); + } + result.AppendLine(((RtfBlock)_blocks[i]).render()); + } + result.AppendLine("}"); + return result.ToString(); + } + } +} diff --git a/RtfWriter.Standard/RtfParagraph.cs b/RtfWriter.Standard/RtfParagraph.cs new file mode 100644 index 0000000..969b5ef --- /dev/null +++ b/RtfWriter.Standard/RtfParagraph.cs @@ -0,0 +1,536 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace RtfWriter.Standard +{ + /// + /// Summary description for RtfParagraph + /// + public class RtfParagraph : RtfBlock + { + private StringBuilder _text; + private float _linespacing; + private Margins _margins; + private Align _align; + private List _charFormats; + protected bool _allowFootnote; + protected bool _allowControlWord; + private List _footnotes; + private List _controlWords; + private string _blockHead; + private string _blockTail; + private bool _startNewPage; + private float _firstLineIndent; + private RtfCharFormat _defaultCharFormat; + + protected struct Token + { + public string text; + public bool isControl; + } + + private class DisjointRange + { + public DisjointRange() + { + head = -1; + tail = -1; + format = null; + } + public int head; + public int tail; + public RtfCharFormat format; + } + + public RtfParagraph() + : this(false, false) + { + } + + public RtfParagraph(bool allowFootnote, bool allowControlWord) + { + _text = new StringBuilder(); + _linespacing = -1; + _margins = new Margins(); + _align = Align.Left; //Changed default to .Left as .None was spreading text accross page. + _charFormats = new List(); + _allowFootnote = allowFootnote; + _allowControlWord = allowControlWord; + _footnotes = new List(); + _controlWords = new List(); + _blockHead = @"{\pard"; + _blockTail = @"\par}"; + _startNewPage = false; + _firstLineIndent = 0; + _defaultCharFormat = null; + } + + public StringBuilder Text + { + get + { + return _text; + } + } + + public float LineSpacing + { + get + { + return _linespacing; + } + set + { + _linespacing = value; + } + } + + public float FirstLineIndent + { + get + { + return _firstLineIndent; + } + set + { + _firstLineIndent = value; + } + } + + public void setText(string text) + { + _text = new StringBuilder(text); + } + + public override RtfCharFormat DefaultCharFormat + { + get + { + if (_defaultCharFormat == null) { + _defaultCharFormat = new RtfCharFormat(-1, -1, _text.Length); + } + return _defaultCharFormat; + } + } + + public override bool StartNewPage + { + get + { + return _startNewPage; + } + set + { + _startNewPage = value; + } + } + + public override Align Alignment + { + get + { + return _align; + } + set + { + _align = value; + } + } + + public override Margins Margins + { + get + { + return _margins; + } + } + + internal override string BlockHead + { + set + { + _blockHead = value; + } + } + + internal override string BlockTail + { + set + { + _blockTail = value; + } + } + + /// + /// Add a character formatting to a range in this paragraph. + /// To specify the whole paragraph as the range, set begin = end = -1. + /// Format that is added latter will override the former, if their + /// range overlays each other. + /// + /// Beginning of the range + /// End of the range + public RtfCharFormat addCharFormat(int begin, int end) + { + RtfCharFormat fmt = new RtfCharFormat(begin, end, _text.Length); + _charFormats.Add(fmt); + return fmt; + } + + public RtfCharFormat addCharFormat() + { + return addCharFormat(-1, -1); + } + + public RtfFootnote addFootnote(int position) + { + if (!_allowFootnote) { + throw new Exception("Footnote is not allowed."); + } + RtfFootnote fnt = new RtfFootnote(position, _text.Length); + _footnotes.Add(fnt); + return fnt; + } + + public void addControlWord(int position, RtfFieldControlWord.FieldType type) + { + if (!_allowControlWord) { + throw new Exception("ControlWord is not allowed."); + } + RtfFieldControlWord w = new RtfFieldControlWord(position, type); + for (int i = 0; i < _controlWords.Count; i++) { + if (_controlWords[i].Position == w.Position) { + _controlWords[i] = w; + return; + } + } + _controlWords.Add(w); + } + + protected LinkedList buildTokenList() + { + int count; + Token token; + LinkedList tokList = new LinkedList(); + LinkedListNode node; + List dranges = new List(); + + #region Build head[] and tail[] from char format range for later use. + // -------------------------------------------------- + // Transform possibly overlapped character format ranges into + // disjoint ranges. + // -------------------------------------------------- + for (int i = 0; i < _charFormats.Count; i++) { + RtfCharFormat fmt = _charFormats[i]; + DisjointRange range = null; + if (fmt.Begin == -1 && fmt.End == -1) { + range = new DisjointRange(); + range.head = 0; + range.tail = _text.Length - 1; + range.format = fmt; + } else if (fmt.Begin <= fmt.End) { + range = new DisjointRange(); + range.head = fmt.Begin; + range.tail = fmt.End; + range.format = fmt; + } else { + continue; + } + if (range.tail >= _text.Length) { + range.tail = _text.Length - 1; + if (range.head > range.tail) { + continue; + } + } + // make the ranges disjoint from each other. + List delList = new List(); + List addList = new List(); + List addAnchorList = new List(); + for (int j = 0; j < dranges.Count; j++) { + DisjointRange r = dranges[j]; + if (range.head <= r.head && range.tail >= r.tail) { + // former range is totally covered by the later + // |--------| r + // |-----------------| range + delList.Add(r); + } else if (range.head <= r.head && range.tail >= r.head && range.tail < r.tail) { + // former range is partially covered + // |------------------| r + // |-----------------| range + r.head = range.tail + 1; + } else if (range.head > r.head && range.head <= r.tail && range.tail >= r.tail) { + // former range is partially covered + // |------------------| r + // |-----------------| range + r.tail = range.head - 1; + } else if (range.head > r.head && range.tail < r.tail) { + // later range is totally covered by the former + // |----------------------| r + // |---------| range + DisjointRange newRange = new DisjointRange(); + newRange.head = range.tail + 1; + newRange.tail = r.tail; + newRange.format = r.format; + r.tail = range.head - 1; + addList.Add(newRange); + addAnchorList.Add(r); + } + } + dranges.Add(range); + for (int j = 0; j < delList.Count; j++) { + dranges.Remove(delList[j]); + } + for (int j = 0; j < addList.Count; j++) { + int index = dranges.IndexOf(addAnchorList[j]); + if (index < 0) { + continue; + } + dranges.Insert(index, addList[j]); + } + } + #endregion + token = new Token(); + token.text = _text.ToString(); + token.isControl = false; + tokList.AddLast(token); + #region Build token list from head[] and tail[]. + // -------------------------------------------------- + // Build token list from head[] and tail[]. + // -------------------------------------------------- + for (int i = 0; i < dranges.Count; i++) { + DisjointRange r = dranges[i]; + count = 0; + // process head[i] + if (r.head == 0) { + Token newTok = new Token(); + newTok.isControl = true; + newTok.text = r.format.renderHead(); + tokList.AddFirst(newTok); + } else { + node = tokList.First; + while (node != null) { + Token tok = node.Value; + + if (!tok.isControl) { + count += tok.text.Length; + if (count == r.head) { + Token newTok = new Token(); + newTok.isControl = true; + newTok.text = r.format.renderHead(); + while (node.Next != null && node.Next.Value.isControl) { + node = node.Next; + } + tokList.AddAfter(node, newTok); + break; + } else if (count > r.head) { + LinkedListNode newNode; + Token newTok1 = new Token(); + newTok1.isControl = false; + newTok1.text = tok.text.Substring(0, tok.text.Length - (count - r.head)); + newNode = tokList.AddAfter(node, newTok1); + Token newTok2 = new Token(); + newTok2.isControl = true; + newTok2.text = r.format.renderHead(); + newNode = tokList.AddAfter(newNode, newTok2); + Token newTok3 = new Token(); + newTok3.isControl = false; + newTok3.text = tok.text.Substring(tok.text.Length - (count - r.head)); + newNode = tokList.AddAfter(newNode, newTok3); + tokList.Remove(node); + break; + } + } + node = node.Next; + } + } + // process tail[i] + count = 0; + node = tokList.First; + while (node != null) { + Token tok = node.Value; + + if (!tok.isControl) { + count += tok.text.Length; + if (count - 1 == r.tail) { + Token newTok = new Token(); + newTok.isControl = true; + newTok.text = r.format.renderTail(); + tokList.AddAfter(node, newTok); + break; + } else if (count - 1 > r.tail) { + LinkedListNode newNode; + Token newTok1 = new Token(); + newTok1.isControl = false; + newTok1.text = tok.text.Substring(0, tok.text.Length - (count - r.tail) + 1); + newNode = tokList.AddAfter(node, newTok1); + Token newTok2 = new Token(); + newTok2.isControl = true; + newTok2.text = r.format.renderTail(); + newNode = tokList.AddAfter(newNode, newTok2); + Token newTok3 = new Token(); + newTok3.isControl = false; + newTok3.text = tok.text.Substring(tok.text.Length - (count - r.tail) + 1); + newNode = tokList.AddAfter(newNode, newTok3); + tokList.Remove(node); + break; + } + } + node = node.Next; + } + } // end for each char format + #endregion + #region Insert footnote into token list. + // -------------------------------------------------- + // Insert footnote into token list. + // -------------------------------------------------- + for (int i = 0; i < _footnotes.Count; i++) { + int pos = _footnotes[i].Position; + if (pos >= _text.Length) { + continue; + } + + count = 0; + node = tokList.First; + while (node != null) { + Token tok = node.Value; + + if (!tok.isControl) { + count += tok.text.Length; + if (count - 1 == pos) { + Token newTok = new Token(); + newTok.isControl = true; + newTok.text = _footnotes[i].render(); + tokList.AddAfter(node, newTok); + break; + } else if (count - 1 > pos) { + LinkedListNode newNode; + Token newTok1 = new Token(); + newTok1.isControl = false; + newTok1.text = tok.text.Substring(0, tok.text.Length - (count - pos) + 1); + newNode = tokList.AddAfter(node, newTok1); + Token newTok2 = new Token(); + newTok2.isControl = true; + newTok2.text = _footnotes[i].render(); + newNode = tokList.AddAfter(newNode, newTok2); + Token newTok3 = new Token(); + newTok3.isControl = false; + newTok3.text = tok.text.Substring(tok.text.Length - (count - pos) + 1); + newNode = tokList.AddAfter(newNode, newTok3); + tokList.Remove(node); + break; + } + } + node = node.Next; + } + } + #endregion + #region Insert control words into token list. + // -------------------------------------------------- + // Insert control words into token list. + // -------------------------------------------------- + for (int i = 0; i < _controlWords.Count; i++) { + int pos = _controlWords[i].Position; + if (pos >= _text.Length) { + continue; + } + + count = 0; + node = tokList.First; + while (node != null) { + Token tok = node.Value; + + if (!tok.isControl) { + count += tok.text.Length; + if (count - 1 == pos) { + Token newTok = new Token(); + newTok.isControl = true; + newTok.text = _controlWords[i].render(); + tokList.AddAfter(node, newTok); + break; + } else if (count - 1 > pos) { + LinkedListNode newNode; + Token newTok1 = new Token(); + newTok1.isControl = false; + newTok1.text = tok.text.Substring(0, tok.text.Length - (count - pos) + 1); + newNode = tokList.AddAfter(node, newTok1); + Token newTok2 = new Token(); + newTok2.isControl = true; + newTok2.text = _controlWords[i].render(); + newNode = tokList.AddAfter(newNode, newTok2); + Token newTok3 = new Token(); + newTok3.isControl = false; + newTok3.text = tok.text.Substring(tok.text.Length - (count - pos) + 1); + newNode = tokList.AddAfter(newNode, newTok3); + tokList.Remove(node); + break; + } + } + node = node.Next; + } + } + #endregion + + return tokList; + } + + protected string extractTokenList(LinkedList tokList) + { + LinkedListNode node; + StringBuilder result = new StringBuilder(); + + node = tokList.First; + while (node != null) { + if (node.Value.isControl) { + result.Append(node.Value.text); + } else { + result.Append(RtfUtility.unicodeEncode(node.Value.text)); + } + node = node.Next; + } + return result.ToString(); + } + + public override string render() + { + LinkedList tokList = buildTokenList(); + StringBuilder result = new StringBuilder(_blockHead); + + if (_startNewPage) { + result.Append(@"\pagebb"); + } + + if (_linespacing >= 0) { + result.Append(@"\sl-" + RtfUtility.pt2Twip(_linespacing) + @"\slmult0"); + } + if (_margins[Direction.Top] > 0) { + result.Append(@"\sb" + RtfUtility.pt2Twip(_margins[Direction.Top])); + } + if (_margins[Direction.Bottom] > 0) { + result.Append(@"\sa" + RtfUtility.pt2Twip(_margins[Direction.Bottom])); + } + if (_margins[Direction.Left] > 0) { + result.Append(@"\li" + RtfUtility.pt2Twip(_margins[Direction.Left])); + } + if (_margins[Direction.Right] > 0) { + result.Append(@"\ri" + RtfUtility.pt2Twip(_margins[Direction.Right])); + } + //if (_firstLineIndent != 0) { + result.Append(@"\fi" + RtfUtility.pt2Twip(_firstLineIndent)); + //} + result.Append(AlignmentCode()); + result.AppendLine(); + + // insert default char format intto the 1st position of _charFormats + if (_defaultCharFormat != null) { + result.AppendLine(_defaultCharFormat.renderHead()); + } + result.AppendLine(extractTokenList(tokList)); + if (_defaultCharFormat != null) { + result.Append(_defaultCharFormat.renderTail()); + } + + result.AppendLine(_blockTail); + return result.ToString(); + } + } +} diff --git a/RtfWriter.Standard/RtfSection.cs b/RtfWriter.Standard/RtfSection.cs new file mode 100644 index 0000000..4a7adc7 --- /dev/null +++ b/RtfWriter.Standard/RtfSection.cs @@ -0,0 +1,106 @@ +using System; +using System.Text; + +namespace RtfWriter.Standard +{ + public class RtfSection : RtfBlock + { + private Align _align; + private RtfSectionFooter _sectionFooter; + private readonly Margins _margins; + + internal RtfSection(SectionStartEnd startEnd, RtfDocument doc) + { + ParentDocument = doc; + _align = Align.None; + PageOrientation = PaperOrientation.Portrait; + StartEnd = startEnd; + FooterPositionFromPageBottom = 720; + _sectionFooter = null; + _margins = new Margins(); + } + + public override bool StartNewPage + { + get; + set; + } + + public override Align Alignment + { + get + { + return _align; + } + set + { + _align = value; + } + } + + public SectionStartEnd StartEnd { get; private set; } + public PaperOrientation PageOrientation { get; set; } + public RtfSectionFooter SectionFooter { get { return _sectionFooter ?? (_sectionFooter = new RtfSectionFooter(this)); } } + private int FooterPositionFromPageBottom { get; set; } + + /// + /// Pagewidth in twips + /// + public int PageWidth { get; set; } + + /// + /// Page height in twips + /// + public int PageHeight { get; set; } + + private RtfDocument ParentDocument { get; set; } + + public override string render() + { + StringBuilder result = new StringBuilder(); + if (StartEnd == SectionStartEnd.Start) + { + result.AppendLine(string.Format(@"{{\sectd\ltrsect\footery{0}\sectdefaultcl\sftnbj{1} ", FooterPositionFromPageBottom, AlignmentCode())); + if (PageOrientation == PaperOrientation.Landscape) + { + result.Append(@"\lndscpsxn "); + } + result.Append(string.Format(@"\pgwsxn{0}\pghsxn{1} ",PageWidth, PageHeight)); + if (!ParentDocument.Margins.Equals(Margins)) + { + result.Append(string.Format(@"\marglsxn{0}\margrsxn{1}\margtsxn{2}\margbsxn{3} ", + Margins[Direction.Left], Margins[Direction.Right], Margins[Direction.Top], Margins[Direction.Bottom])); + } + if( SectionFooter != null ) + { + result.AppendLine( SectionFooter.render() ); + } + } + else + { + result.AppendLine(string.Format(@"\sect }}")); + } + return result.ToString(); + } + + public override Margins Margins + { + get { return _margins; } + } + + public override RtfCharFormat DefaultCharFormat + { + get { throw new Exception("BlockHead is not supported for sections."); } + } + + internal override string BlockHead + { + set { throw new Exception("BlockHead is not supported for sections."); } + } + + internal override string BlockTail + { + set { throw new Exception("BlockHead is not supported for sections."); } + } + } +} diff --git a/RtfWriter.Standard/RtfSectionFooter.cs b/RtfWriter.Standard/RtfSectionFooter.cs new file mode 100644 index 0000000..405ea16 --- /dev/null +++ b/RtfWriter.Standard/RtfSectionFooter.cs @@ -0,0 +1,30 @@ +using System; +using System.Text; + +namespace RtfWriter.Standard +{ + public class RtfSectionFooter : RtfBlockList + { + internal RtfSectionFooter(RtfSection parent) + : base(true, true, true, true, true) + { + if(parent == null) + { + throw new Exception("Section footer can only be placed within a section "); + } + } + + public override string render() + { + StringBuilder result = new StringBuilder(); + + result.AppendLine(@"{\footerr \ltrpar \pard\plain"); + result.AppendLine(@"\par "); + result.Append(base.render()); + result.AppendLine(@"\par"); + result.AppendLine(@"}"); + + return result.ToString(); + } + } +} diff --git a/RtfWriter.Standard/RtfTable.cs b/RtfWriter.Standard/RtfTable.cs new file mode 100644 index 0000000..4e71fd8 --- /dev/null +++ b/RtfWriter.Standard/RtfTable.cs @@ -0,0 +1,629 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace RtfWriter.Standard +{ + /// + /// Summary description for RtfTable + /// + public class RtfTable : RtfBlock + { + private Align _alignment; + private Margins _margins; + private int _rowCount; + private int _colCount; + private RtfTableCell[][] _cells; + private float _defaultCellWidth; + private List _representativeList; + private bool _startNewPage; + private float[] _rowHeight; + private bool[] _rowKeepInSamePage; + private int _titleRowCount; + private readonly float _fontSize; + private RtfCharFormat _defaultCharFormat; + private Margins[] _cellPadding; + + public RtfTable(int rowCount, int colCount, float horizontalWidth, float fontSize) + { + _fontSize = fontSize; + _alignment = Align.None; + _margins = new Margins(); + _rowCount = rowCount; + _colCount = colCount; + _representativeList = new List(); + _startNewPage = false; + _titleRowCount = 0; + _cellPadding = new Margins[_rowCount]; + if (_rowCount < 1 || _colCount < 1) { + throw new Exception("The number of rows or columns is less than 1."); + } + + HeaderBackgroundColour = null; + RowBackgroundColour = null; + RowAltBackgroundColour = null; + + // Set cell default width according to paper width + _defaultCellWidth = horizontalWidth / (float)colCount; + _cells = new RtfTableCell[_rowCount][]; + _rowHeight = new float[_rowCount]; + _rowKeepInSamePage = new bool[_rowCount]; + for (int i = 0; i < _rowCount; i++) { + _cells[i] = new RtfTableCell[_colCount]; + _rowHeight[i] = 0F; + _rowKeepInSamePage[i] = false; + _cellPadding[i] = new Margins(); + for (int j = 0; j < _colCount; j++) { + _cells[i][j] = new RtfTableCell(_defaultCellWidth, i, j, this); + } + } + } + + public ColorDescriptor HeaderBackgroundColour { get; set; } + public ColorDescriptor RowBackgroundColour { get; set; } + public ColorDescriptor RowAltBackgroundColour { get; set; } + + public override Align Alignment + { + get + { + return _alignment; + } + set + { + _alignment = value; + } + } + + public override Margins Margins + { + get + { + return _margins; + } + } + + public override RtfCharFormat DefaultCharFormat + { + get + { + if (_defaultCharFormat == null) { + _defaultCharFormat = new RtfCharFormat(-1, -1, 1); + } + return _defaultCharFormat; + } + } + + public override bool StartNewPage + { + get + { + return _startNewPage; + } + set + { + _startNewPage = value; + } + } + + public int RowCount + { + get + { + return _rowCount; + } + } + + public int ColCount + { + get + { + return _colCount; + } + } + + /// + /// Title row will be displayed in every page on which the table appears. + /// + public int TitleRowCount + { + get + { + return _titleRowCount; + } + set + { + _titleRowCount = value; + } + } + + public Margins[] CellPadding + { + get + { + return _cellPadding; + } + } + + internal override string BlockHead + { + set + { + throw new Exception("BlockHead is not supported for tables."); + } + } + + internal override string BlockTail + { + set + { + throw new Exception("BlockHead is not supported for tables."); + } + } + + public RtfTableCell cell(int row, int col) + { + if (_cells[row][col].IsMerged) { + return _cells[row][col].MergeInfo.Representative; + } + return _cells[row][col]; + } + + public void setColWidth(int col, float width) + { + if (col < 0 || col >= _colCount) { + throw new Exception("Column index out of range"); + } + for (int i = 0; i < _rowCount; i++) { + if (_cells[i][col].IsMerged) { + throw new Exception("Column width cannot be set " + + "because some cell in this column has been merged."); + } + } + for (int i = 0; i < _rowCount; i++) { + _cells[i][col].Width = width; + } + } + + public void setRowHeight(int row, float height) + { + if (row < 0 || row >= _rowCount) { + throw new Exception("Row index out of range"); + } + for (int i = 0; i < _colCount; i++) { + if (_cells[row][i].IsMerged + && _cells[row][i].MergeInfo.Representative.MergeInfo.RowSpan > 1) { + throw new Exception("Row height cannot be set " + + "because some cell in this row has been merged."); + } + } + _rowHeight[row] = height; + } + + public void setRowKeepInSamePage(int row, bool allow) + { + if (row < 0 || row >= _rowCount) { + throw new Exception("Row index out of range"); + } + _rowKeepInSamePage[row] = allow; + } + + public RtfTableCell merge(int topRow, int leftCol, int rowSpan, int colSpan) + { + if (topRow < 0 || topRow >= _rowCount) { + throw new Exception("Row index out of range"); + } + if (leftCol < 0 || leftCol >= _colCount) { + throw new Exception("Column index out of range"); + } + if (rowSpan < 1 || topRow + rowSpan - 1 >= _rowCount) { + throw new Exception("Row span out of range."); + } + if (colSpan < 1 || leftCol + colSpan - 1 >= _colCount) { + throw new Exception("Column span out of range."); + } + if (colSpan == 1 && rowSpan == 1) { + return cell(topRow, leftCol); + } + // Check if the cell has been merged before. + for (int i = 0; i < rowSpan; i++) { + for (int j = 0; j < colSpan; j++) { + if (_cells[topRow + i][leftCol + j].IsMerged) { + throw new Exception("Cannot merge cells because some of the cells has been merged."); + } + } + } + + float width = 0; + for (int i = 0; i < rowSpan; i++) { + for (int j = 0; j < colSpan; j++) { + // Sum up the column widths in the first row. + if (i == 0) { + width += _cells[topRow][leftCol + j].Width; + } + // Set merge info for each cell. + // Note: The representatives of all cells are set to the (topRow, leftCol) cell. + _cells[topRow + i][leftCol + j].MergeInfo + = new CellMergeInfo(_cells[topRow][leftCol], rowSpan, colSpan, i, j); + if (i != 0 || j != 0) { + // Transfer the blocks (contents) of each cell to their representative cell. + _cells[topRow + i][leftCol + j].transferBlocksTo( + _cells[topRow + i][leftCol + j].MergeInfo.Representative); + } + } + } + // Set cell width in the representative cell. + _cells[topRow][leftCol].Width = width; + _representativeList.Add(_cells[topRow][leftCol]); + return _cells[topRow][leftCol]; + } + + private void validateAllMergedCellBorders() + { + for (int i = 0; i < _representativeList.Count; i++) { + validateMergedCellBorders(_representativeList[i]); + } + } + + private void validateMergedCellBorders(RtfTableCell representative) + { + if (!representative.IsMerged) { + throw new Exception("Invalid representative (cell is not merged)."); + } + validateMergedCellBorder(representative, Direction.Top); + validateMergedCellBorder(representative, Direction.Right); + validateMergedCellBorder(representative, Direction.Bottom); + validateMergedCellBorder(representative, Direction.Left); + } + + private void validateMergedCellBorder(RtfTableCell representative, Direction dir) + { + if (!representative.IsMerged) { + throw new Exception("Invalid representative (cell is not merged)."); + } + Dictionary stat = new Dictionary(); + Border majorityBorder; + int majorityCount; + int limit = (dir == Direction.Top || dir == Direction.Bottom) ? + representative.MergeInfo.ColSpan : representative.MergeInfo.RowSpan; + + for (int i = 0; i < limit; i++) { + int r, c; + Border bdr; + if (dir == Direction.Top || dir == Direction.Bottom) { + if (dir == Direction.Top) { + r = 0; + } else { // dir == bottom + r = representative.MergeInfo.RowSpan - 1; + } + c = i; + } else { // dir == right || left + if (dir == Direction.Right) { + c = representative.MergeInfo.ColSpan - 1; + } else { // dir == left + c = 0; + } + r = i; + } + bdr = _cells[representative.RowIndex + r][representative.ColIndex + c].Borders[dir]; + if (stat.ContainsKey(bdr)) { + stat[bdr] = (int)stat[bdr] + 1; + } else { + stat[bdr] = 1; + } + } + majorityCount = -1; + majorityBorder = representative.Borders[dir]; + foreach(KeyValuePair de in stat) { + if(de.Value > majorityCount) { + majorityCount = de.Value; + majorityBorder.Style = de.Key.Style; + majorityBorder.Width = de.Key.Width; + majorityBorder.Color = de.Key.Color; + } + } + } + + /// + /// Set ALL inner borders (colour will be set to default) + /// + /// + /// + public void setInnerBorder(BorderStyle style, float width) + { + setInnerBorder(style, width, new ColorDescriptor(0)); + } + + /// + /// Sets ALL inner borders as specified + /// + /// + /// + /// + public void setInnerBorder(BorderStyle style, float width, ColorDescriptor color) + { + for (int i = 0; i < _rowCount; i++) { + for (int j = 0; j < _colCount; j++) { + if (i == 0) { + // The first row + _cells[i][j].Borders[Direction.Bottom].Style = style; + _cells[i][j].Borders[Direction.Bottom].Width = width; + _cells[i][j].Borders[Direction.Bottom].Color = color; + } else if (i == _rowCount - 1) { + // The last row + _cells[i][j].Borders[Direction.Top].Style = style; + _cells[i][j].Borders[Direction.Top].Width = width; + _cells[i][j].Borders[Direction.Top].Color = color; + } else { + _cells[i][j].Borders[Direction.Top].Style = style; + _cells[i][j].Borders[Direction.Top].Width = width; + _cells[i][j].Borders[Direction.Top].Color = color; + _cells[i][j].Borders[Direction.Bottom].Style = style; + _cells[i][j].Borders[Direction.Bottom].Color = color; + _cells[i][j].Borders[Direction.Bottom].Width = width; + } + if (j == 0) { + // The first column + _cells[i][j].Borders[Direction.Right].Style = style; + _cells[i][j].Borders[Direction.Right].Width = width; + _cells[i][j].Borders[Direction.Right].Color = color; + } else if (j == _colCount - 1) { + // The last column + _cells[i][j].Borders[Direction.Left].Style = style; + _cells[i][j].Borders[Direction.Left].Width = width; + _cells[i][j].Borders[Direction.Left].Color = color; + } else { + _cells[i][j].Borders[Direction.Right].Style = style; + _cells[i][j].Borders[Direction.Right].Width = width; + _cells[i][j].Borders[Direction.Right].Color = color; + _cells[i][j].Borders[Direction.Left].Style = style; + _cells[i][j].Borders[Direction.Left].Width = width; + _cells[i][j].Borders[Direction.Left].Color = color; + } + } + } + } + + /// + /// Set ALL outer borders (colour will be set to default) + /// + /// + /// + public void setOuterBorder(BorderStyle style, float width) + { + setOuterBorder(style, width, new ColorDescriptor(0)); + } + + /// + /// Sets ALL outer borders as specified + /// + /// + /// + /// + public void setOuterBorder(BorderStyle style, float width, ColorDescriptor color) + { + for (int i = 0; i < _colCount; i++) { + _cells[0][i].Borders[Direction.Top].Style = style; + _cells[0][i].Borders[Direction.Top].Width = width; + _cells[0][i].Borders[Direction.Top].Color = color; + _cells[_rowCount - 1][i].Borders[Direction.Bottom].Style = style; + _cells[_rowCount - 1][i].Borders[Direction.Bottom].Width = width; + _cells[_rowCount - 1][i].Borders[Direction.Bottom].Color = color; + } + for (int i = 0; i < _rowCount; i++) { + _cells[i][0].Borders[Direction.Left].Style = style; + _cells[i][0].Borders[Direction.Left].Width = width; + _cells[i][0].Borders[Direction.Left].Color = color; + _cells[i][_colCount - 1].Borders[Direction.Right].Style = style; + _cells[i][_colCount - 1].Borders[Direction.Right].Width = width; + _cells[i][_colCount - 1].Borders[Direction.Right].Color = color; + } + } + + public void setHeaderBorderColors(ColorDescriptor colorOuter, ColorDescriptor colorInner) + { + for (int j = 0; j < _colCount; j++) + { + _cells[0][j].Borders[Direction.Top].Color = colorOuter; + _cells[0][j].Borders[Direction.Bottom].Color = colorInner; + if (j == 0) + { + // The first column + _cells[0][j].Borders[Direction.Right].Color = colorInner; + _cells[0][j].Borders[Direction.Left].Color = colorOuter; + + } + else if (j == _colCount - 1) + { + // The last column + _cells[0][j].Borders[Direction.Right].Color = colorOuter; + _cells[0][j].Borders[Direction.Left].Color = colorInner; + + } + else + { + _cells[0][j].Borders[Direction.Right].Color = colorInner; + _cells[0][j].Borders[Direction.Left].Color = colorInner; + } + } + } + + public override string render() + { + StringBuilder result = new StringBuilder(); + + // validate borders for each cell. + // (borders may be changed because of cell merging) + validateAllMergedCellBorders(); + // set default char format for each cell. + if (_defaultCharFormat != null) { + for (int i = 0; i < _rowCount; i++) { + for (int j = 0; j < _colCount; j++) { + if (_cells[i][j].IsMerged + && _cells[i][j].MergeInfo.Representative != _cells[i][j]) { + continue; + } + if (_cells[i][j].DefaultCharFormat != null) { + _cells[i][j].DefaultCharFormat.copyFrom(_defaultCharFormat); + } + } + } + } + + float topMargin = _margins[Direction.Top] - _fontSize; + + if(_startNewPage || topMargin > 0) { + result.Append(@"{\pard"); + if (_startNewPage) { + result.Append(@"\pagebb"); + } + if (_margins[Direction.Top] >= 0) { + result.Append(@"\sl-" + RtfUtility.pt2Twip(topMargin)); + } else { + result.Append(@"\sl-1"); + } + result.AppendLine(@"\slmult0\par}"); + } + + int colAcc; + + for (int i = 0; i < _rowCount; i++) + { + colAcc = 0; + result.Append(@"{\trowd\trgaph" + + string.Format(@"\trpaddl{0}\trpaddt{1}\trpaddr{2}\trpaddb{3}", + RtfUtility.pt2Twip(CellPadding[i][Direction.Left]), + RtfUtility.pt2Twip(CellPadding[i][Direction.Top]), + RtfUtility.pt2Twip(CellPadding[i][Direction.Right]), + RtfUtility.pt2Twip(CellPadding[i][Direction.Bottom]))); + switch (_alignment) { + case Align.Left: + result.Append(@"\trql"); + break; + case Align.Right: + result.Append(@"\trqr"); + break; + case Align.Center: + result.Append(@"\trqc"); + break; + case Align.FullyJustify: + result.Append(@"\trqj"); + break; + } + result.AppendLine(); + if (_margins[Direction.Left] >= 0) { + result.AppendLine(@"\trleft" + RtfUtility.pt2Twip(_margins[Direction.Left])); + colAcc = RtfUtility.pt2Twip(_margins[Direction.Left]); + } + if (_rowHeight[i] > 0) { + result.Append(@"\trrh" + RtfUtility.pt2Twip(_rowHeight[i])); + } + if (_rowKeepInSamePage[i]) { + result.Append(@"\trkeep"); + } + if (i < _titleRowCount) { + result.Append(@"\trhdr"); + } + result.AppendLine(); + + for (int j = 0; j < _colCount; j++) + { + if (_cells[i][j].IsMerged && !_cells[i][j].IsBeginOfColSpan) { + continue; + } + float nextCellLeftBorderClearance = j < _colCount - 1 ? cell(i, j + 1).OuterLeftBorderClearance : 0; + colAcc += RtfUtility.pt2Twip(cell(i, j).Width); + int colRightPos = colAcc; + if(nextCellLeftBorderClearance < 0) + { + colRightPos += RtfUtility.pt2Twip(nextCellLeftBorderClearance); + colRightPos = colRightPos == 0 ? 1 : colRightPos; + } + + // Borders + for (Direction d = Direction.Top; d <= Direction.Left; d++) { + Border bdr = cell(i, j).Borders[d]; + if (bdr.Style != BorderStyle.None) { + result.Append(@"\clbrdr"); + switch (d) { + case Direction.Top: + result.Append("t"); + break; + case Direction.Right: + result.Append("r"); + break; + case Direction.Bottom: + result.Append("b"); + break; + case Direction.Left: + result.Append("l"); + break; + } + result.Append(@"\brdrw" + RtfUtility.pt2Twip(bdr.Width)); + result.Append(@"\brdr"); + switch (bdr.Style) { + case BorderStyle.Single: + result.Append("s"); + break; + case BorderStyle.Dotted: + result.Append("dot"); + break; + case BorderStyle.Dashed: + result.Append("dash"); + break; + case BorderStyle.Double: + result.Append("db"); + break; + default: + throw new Exception("Unkown border style"); + } + result.Append(@"\brdrcf" + bdr.Color.Value); + } + } + + // Cell background colour + if (cell(i, j).BackgroundColour != null) result.Append(string.Format(@"\clcbpat{0}", cell(i, j).BackgroundColour.Value)); // cell.BackGroundColor overrides others + else if (i == 0 && HeaderBackgroundColour != null) result.Append(string.Format(@"\clcbpat{0}", HeaderBackgroundColour.Value)); // header + else if (RowBackgroundColour != null && (RowAltBackgroundColour == null || i % 2 == 0)) result.Append(string.Format(@"\clcbpat{0}", RowBackgroundColour.Value)); // row colour + else if (RowBackgroundColour != null && RowAltBackgroundColour != null && i % 2 != 0) result.Append(string.Format(@"\clcbpat{0}", RowAltBackgroundColour.Value)); // alt row colour + + if (_cells[i][j].IsMerged && _cells[i][j].MergeInfo.RowSpan > 1) { + if (_cells[i][j].IsBeginOfRowSpan) { + result.Append(@"\clvmgf"); + } else { + result.Append(@"\clvmrg"); + } + } + switch (_cells[i][j].AlignmentVertical) + { + case AlignVertical.Top: + result.Append(@"\clvertalt"); + break; + case AlignVertical.Middle: + result.Append(@"\clvertalc"); + break; + case AlignVertical.Bottom: + result.Append(@"\clvertalb"); + break; + } + result.AppendLine(@"\cellx" + colRightPos); + } + + for (int j = 0; j < _colCount; j++) + { + if (!_cells[i][j].IsMerged || _cells[i][j].IsBeginOfColSpan) { + result.Append(_cells[i][j].render()); + } + } + + result.AppendLine(@"\row}"); + } + + if (_margins[Direction.Bottom] >= 0) { + result.Append(@"\sl-" + RtfUtility.pt2Twip(_margins[Direction.Bottom]) + @"\slmult"); + } + + return result.ToString(); + } + } +} diff --git a/RtfWriter.Standard/RtfTableCell.cs b/RtfWriter.Standard/RtfTableCell.cs new file mode 100644 index 0000000..4a2228a --- /dev/null +++ b/RtfWriter.Standard/RtfTableCell.cs @@ -0,0 +1,209 @@ +using System.Text; + +namespace RtfWriter.Standard +{ + /// + /// Summary description for RtfTableCell + /// + public class RtfTableCell : RtfBlockList + { + private float _width; + private Align _halign; + private AlignVertical _valign; + private Borders _borders; + private CellMergeInfo _mergeInfo; + private int _rowIndex; + private int _colIndex; + + internal RtfTableCell(float width, int rowIndex, int colIndex, RtfTable parentTable) + : base(true, false) + { + _width = width; + _halign = Align.None; + _valign = AlignVertical.Top; + _borders = new Borders(); + _mergeInfo = null; + _rowIndex = rowIndex; + _colIndex = colIndex; + BackgroundColour = null; + ParentTable = parentTable; + } + + internal bool IsBeginOfColSpan + { + get + { + if (_mergeInfo == null) { + return false; + } + return (_mergeInfo.ColIndex == 0); + } + } + + internal bool IsBeginOfRowSpan + { + get + { + if (_mergeInfo == null) { + return false; + } + return (_mergeInfo.RowIndex == 0); + } + } + + public bool IsMerged + { + get + { + if (_mergeInfo == null) { + return false; + } + return true; + } + } + + internal CellMergeInfo MergeInfo + { + get + { + return _mergeInfo; + } + set + { + _mergeInfo = value; + } + } + + public float Width + { + get + { + return _width; + } + set + { + _width = value; + } + } + + public Borders Borders + { + get + { + return _borders; + } + } + + public RtfTable ParentTable { get; private set; } + + public ColorDescriptor BackgroundColour { get; set; } + + public Align Alignment + { + get + { + return _halign; + } + set + { + _halign = value; + } + } + + public AlignVertical AlignmentVertical + { + get + { + return _valign; + } + set + { + _valign = value; + } + } + + public int RowIndex + { + get + { + return _rowIndex; + } + } + + public int ColIndex + { + get + { + return _colIndex; + } + } + + public float OuterLeftBorderClearance { get; set; } + + public void setBorderColor(ColorDescriptor color) + { + this.Borders[Direction.Top].Color = color; + this.Borders[Direction.Bottom].Color = color; + this.Borders[Direction.Left].Color = color; + this.Borders[Direction.Right].Color = color; + } + + public override string render() + { + StringBuilder result = new StringBuilder(); + string align = ""; + + switch (_halign) { + case Align.Left: + align = @"\ql"; + break; + case Align.Right: + align = @"\qr"; + break; + case Align.Center: + align = @"\qc"; + break; + case Align.FullyJustify: + align = @"\qj"; + break; + case Align.Distributed: + align = @"\qd"; + break; + } + + + if (base._blocks.Count <= 0) { + result.AppendLine(@"\pard\intbl"); + } else { + for (int i = 0; i < base._blocks.Count; i++) { + RtfBlock block = (RtfBlock) base._blocks[i]; + if (_defaultCharFormat != null && block.DefaultCharFormat != null) { + block.DefaultCharFormat.copyFrom(_defaultCharFormat); + } + if (block.Margins[Direction.Top] < 0) { + block.Margins[Direction.Top] = 0; + } + if (block.Margins[Direction.Right] < 0) { + block.Margins[Direction.Right] = 0; + } + if (block.Margins[Direction.Bottom] < 0) { + block.Margins[Direction.Bottom] = 0; + } + if (block.Margins[Direction.Left] < 0) { + block.Margins[Direction.Left] = 0; + } + if (i == 0) { + block.BlockHead = @"\pard\intbl" + align; + } else { + block.BlockHead = @"\par" + align; + } + block.BlockTail = ""; + result.AppendLine(block.render()); + } + } + + result.AppendLine(@"\cell"); + return result.ToString(); + } + } +} diff --git a/RtfWriter.Standard/RtfUtility.cs b/RtfWriter.Standard/RtfUtility.cs new file mode 100644 index 0000000..fee6164 --- /dev/null +++ b/RtfWriter.Standard/RtfUtility.cs @@ -0,0 +1,151 @@ +using System; +using System.Text; + +namespace RtfWriter.Standard +{ + /// + /// Summary description for RtfUtility + /// + public static class RtfUtility + { + public static float mm2Points( float mm ) + { + return mm * (float) 2.836; + } + + public static int mm2Twips( float mm ) + { + var inches = mm * 0.0393700787; + return Convert.ToInt32( inches * 1440 ); + } + + public static int pt2Twip(float pt) + { + return !float.IsNaN( pt ) ? Convert.ToInt32( pt * 20 ) : 0; + } + + public static int pt2HalfPt(float pt) + { + return Convert.ToInt32(pt * 2); + } + + private static int[] paperDimensions(PaperSize paperSize) + { + switch(paperSize) { + case PaperSize.A4: + return new int[] { 11906, 16838 }; + case PaperSize.Letter: + return new int[] { 15840, 12240 }; + case PaperSize.A3: + return new int[] { 16838, 23811 }; + default: + throw new Exception("Unknow paper size."); + } + } + + public static int paperWidthInTwip(PaperSize paperSize, PaperOrientation orientation) + { + int[] d = paperDimensions(paperSize); + if (orientation == PaperOrientation.Portrait) { + if (d[0] < d[1]) { + return d[0]; + } else { + return d[1]; + } + } else { // landscape + if (d[0] < d[1]) { + return d[1]; + } else { + return d[0]; + } + } + } + + public static int paperHeightInTwip(PaperSize paperSize, PaperOrientation orientation) + { + int[] d = paperDimensions(paperSize); + if (orientation == PaperOrientation.Portrait) { + if (d[0] < d[1]) { + return d[1]; + } else { + return d[0]; + } + } else { // landscape + if (d[0] < d[1]) { + return d[0]; + } else { + return d[1]; + } + } + } + + public static float paperWidthInPt(PaperSize paperSize, PaperOrientation orientation) + { + return (float) paperWidthInTwip(paperSize, orientation) / 20.0F; + } + + public static float paperHeightInPt(PaperSize paperSize, PaperOrientation orientation) + { + return (float)paperHeightInTwip(paperSize, orientation) / 20.0F; + } + + public static string unicodeEncode(string str) + { + StringBuilder result = new StringBuilder(); + int unicode; + + for (int i = 0; i < str.Length; i++) { + unicode = (int)str[i]; + if (str[i] == '\n') { + result.AppendLine(@"\line"); + } else if (str[i] == '\r') { + // ignore '\r' + } else if (str[i] == '\t') { + result.Append(@"\tab "); + } else if (unicode <= 0xff) { + if (unicode == 0x5c || unicode == 0x7b || unicode == 0x7d) { + result.Append(@"\'" + string.Format("{0:x2}", unicode)); + } else if (0x00 <= unicode && unicode < 0x20) { + result.Append(@"\'" + string.Format("{0:x2}", unicode)); + } else if (0x20 <= unicode && unicode < 0x80) { + result.Append(str[i]); + } else { // 0x80 <= unicode <= 0xff + result.Append(@"\'" + string.Format("{0:x2}", unicode)); + } + } else if (0xff < unicode && unicode <= 0x8000) { + result.Append(@"\uc1\u" + unicode + "*"); + } else if (0x8000 < unicode && unicode <= 0xffff) { + result.Append(@"\uc1\u" + (unicode - 0x10000) + "*"); + } else { + result.Append(@"\uc1\u9633*"); + } + } + return result.ToString(); + } + + /// + /// big5 encoding (preserve this function for failure restoration) + /// + /// string to be encoded + /// encoded string + public static string big5Encode(string str) + { + string result = ""; + Encoding big5 = Encoding.GetEncoding(950); + Encoding ascii = Encoding.ASCII; + Byte[] buf = big5.GetBytes(str); + Byte c; + + for (int i = 0; i < buf.Length; i++) { + c = buf[i]; + if ((0x00 <= c && c < 0x20) || (0x80 <= c && c <= 0xff) + || c == 0x5c || c == 0x7b || c == 0x7d) { + result += string.Format(@"\'{0:x2}", c); + } else { + result += ascii.GetChars(new byte[] { c })[0]; + } + } + return result; + } + } +} diff --git a/RtfWriter.Standard/RtfWriter.Standard.csproj b/RtfWriter.Standard/RtfWriter.Standard.csproj new file mode 100644 index 0000000..9f5c4f4 --- /dev/null +++ b/RtfWriter.Standard/RtfWriter.Standard.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/Tf2Rebalance.CreateSummary.Tests/ConverterTests.cs b/Tf2Rebalance.CreateSummary.Tests/ConverterTests.cs index 0ba93a2..1534436 100644 --- a/Tf2Rebalance.CreateSummary.Tests/ConverterTests.cs +++ b/Tf2Rebalance.CreateSummary.Tests/ConverterTests.cs @@ -27,13 +27,30 @@ public void TestInitialize() [DataRow("tf2rebalance_attributes.example.txt", "tf2rebalance_attributes.example_summary.txt")] [DataRow("higps.txt", "higps_summary.txt")] [DataRow("higps_withoutClasses.txt", "higps_withoutClasses_summary.txt")] - public void TestMethod1(string inputFilename, string expectedOutputFilename) + public void Text(string inputFilename, string expectedOutputFilename) { string input = File.ReadAllText(inputFilename); string expectedOutput = File.ReadAllText(expectedOutputFilename); Converter converter = new Converter(_itemInfos); - RebalanceInfoTextFormater formater = new RebalanceInfoTextFormater(); + IRebalanceInfoFormater formater = new RebalanceInfoTextFormater(); + + IEnumerable rebalanceInfos = converter.Execute(input); + string output = formater.Create(rebalanceInfos); + + Assert.AreEqual(expectedOutput, output); + } + + [TestMethod] + [DataRow("tf2rebalance_attributes.example.txt", "tf2rebalance_attributes.example_summary.rtf")] + [DataRow("higps.txt", "higps_summary.rtf")] + public void Rtf(string inputFilename, string expectedOutputFilename) + { + string input = File.ReadAllText(inputFilename); + string expectedOutput = File.ReadAllText(expectedOutputFilename); + + Converter converter = new Converter(_itemInfos); + IRebalanceInfoFormater formater = new RebalanceInfoRtfFormater(); IEnumerable rebalanceInfos = converter.Execute(input); string output = formater.Create(rebalanceInfos); diff --git a/Tf2Rebalance.CreateSummary.Tests/Tf2Rebalance.CreateSummary.Tests.csproj b/Tf2Rebalance.CreateSummary.Tests/Tf2Rebalance.CreateSummary.Tests.csproj index 222b118..08ce85b 100644 --- a/Tf2Rebalance.CreateSummary.Tests/Tf2Rebalance.CreateSummary.Tests.csproj +++ b/Tf2Rebalance.CreateSummary.Tests/Tf2Rebalance.CreateSummary.Tests.csproj @@ -21,6 +21,9 @@ + + PreserveNewest + PreserveNewest @@ -36,6 +39,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Tf2Rebalance.CreateSummary.Tests/higps_summary.rtf b/Tf2Rebalance.CreateSummary.Tests/higps_summary.rtf new file mode 100644 index 0000000..d649844 --- /dev/null +++ b/Tf2Rebalance.CreateSummary.Tests/higps_summary.rtf @@ -0,0 +1,1082 @@ +{\rtf1\ansi\deff0 + +{\fonttbl +{\f0 Times New Roman;} +} + +{\colortbl +; +} + +\deflang1033\plain\fs24\widowctrl\hyphauto\ftnbj +\paperw11906\paperh16838 +\margt1000 +\margr1000 +\margb1000 +\margl1000 + + +{\pard\fi0\ql +{\b\i\ul Classes\line +} +\par} + +{\pard\fi0\ql +{\b Engineer\line +} +\par} + +{\pard\fi0\ql +{Reduced build and upgrade cost of teleporters by 50%\line +\line +} +\par} + +{\pard\fi0\ql +{\b\i\ul unknown\line +} +\par} + +{\pard\fi0\ql +{\b 972 30474\line +} +\par} + +{\pard\fi0\ql +{pyro\line +\line +} +\par} + +{\pard\fi0\ql +{\b\i\ul Weapons\line +} +\par} + +{\pard\fi0\ql +{\b\ul Demoman\line +} +\par} + +{\pard\fi0\ql +{\i Melee \line +} +\par} + +{\pard\fi0\ql +{\b The Eyelander, Festive Eyelander, Nessie's Nine Iron, Horseless Headless Horsemann's Headtaker, The Scotsman's Skullcutter, The Claidheamh M\'f2r, The Persian Persuader, The Half-Zatoichi\line +} +\par} + +{\pard\fi0\ql +{Longer deploy and holster time removed\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Pain Train\line +} +\par} + +{\pard\fi0\ql +{You now capture at 3x\line +\line +} +\par} + +{\pard\fi0\ql +{\b Ullapool Caber\line +} +\par} + +{\pard\fi0\ql +{Always crits and kills yourself\line +\line +} +\par} + +{\pard\fi0\ql +{\i Primary \line +} +\par} + +{\pard\fi0\ql +{\b Ali Baba's Wee Booties, The Bootlegger\line +} +\par} + +{\pard\fi0\ql +{More health, faster moving speed and better turn control and better refill\line +\line +} +\par} + +{\pard\fi0\ql +{\b Autumn, Macabre Web, Rainbow, Sweet Dreams, Coffin Nail, Top Shelf, Warhawk, Butcher Bird, Grenade Launcher, Grenade Launcher (Renamed/Strange), Festive Grenade Launcher\line +} +\par} + +{\pard\fi0\ql +{Has 5 in clip\line +\line +} +\par} + +{\pard\fi0\ql +{\b The B.A.S.E. Jumper\line +} +\par} + +{\pard\fi0\ql +{Jump height increased by 200%\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Iron Bomber\line +} +\par} + +{\pard\fi0\ql +{Slightly shorter fuse time\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Loch-n-Load\line +} +\par} + +{\pard\fi0\ql +{Only has 2 clip, shoots 2 at the time, no longer shatters\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Loose Cannon\line +} +\par} + +{\pard\fi0\ql +{Increased pushback slightly\line +\line +} +\par} + +{\pard\fi0\ql +{\i Secondary \line +} +\par} + +{\pard\fi0\ql +{\b Sticky Jumper\line +} +\par} + +{\pard\fi0\ql +{Faster firing speed, more bombs out, faster reload\line +\line +} +\par} + +{\pard\fi0\ql +{\b Sudden Flurry, Carpet Bomber, Blasted Bombardier, Rooftop Wrangler, Liquid Asset, Pink Elephant, Autumn, Pumpkin Patch, Macabre Web, Sweet Dreams, Coffin Nail, Dressed to Kill, Blitzkrieg, Festive Stickybomb Launcher, Silver Botkiller Stickybomb Launcher Mk.I, Gold Botkiller Stickybomb Launcher Mk.I, Rust Botkiller Stickybomb Launcher Mk.I, Blood Botkiller Stickybomb Launcher Mk.I, Carbonado Botkiller Stickybomb Launcher Mk.I, Diamond Botkiller Stickybomb Launcher Mk.I, Silver Botkiller Stickybomb Launcher Mk.II, Gold Botkiller Stickybomb Launcher Mk.II, Stickybomb Launcher, Stickybomb Launcher (Renamed/Strange)\line +} +\par} + +{\pard\fi0\ql +{Can have 24 bombs out\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Chargin' Targe, Festive Targe\line +} +\par} + +{\pard\fi0\ql +{3 seconds Longer charge duration\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Quickiebomb Launcher\line +} +\par} + +{\pard\fi0\ql +{Instant arm time\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Scottish Resistance\line +} +\par} + +{\pard\fi0\ql +{Can have 40 bombs out\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Splendid Screen\line +} +\par} + +{\pard\fi0\ql +{6 seconds longer charge time\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Tide Turner\line +} +\par} + +{\pard\fi0\ql +{9 seconds longer charge time, taking damage no longer reduces charge time\line +\line +} +\par} + +{\pard\fi0\ql +{\b\ul Engineer\line +} +\par} + +{\pard\fi0\ql +{\i Primary \line +} +\par} + +{\pard\fi0\ql +{\b Panic Attack\line +} +\par} + +{\pard\fi0\ql +{Less damage, faster shooting, always accurate\line +\line +} +\par} + +{\pard\fi0\ql +{\b\ul Heavy\line +} +\par} + +{\pard\fi0\ql +{\i Melee \line +} +\par} + +{\pard\fi0\ql +{\b Fists, Fists (Renamed/Strange), Apoco-Fists\line +} +\par} + +{\pard\fi0\ql +{20% faster weapon switch\line +\line +} +\par} + +{\pard\fi0\ql +{\b Gloves of Running Urgently, The Bread Bite\line +} +\par} + +{\pard\fi0\ql +{Run faster and drain health faster\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Eviction Notice\line +} +\par} + +{\pard\fi0\ql +{On hit gain 15 second speed bost\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Holiday Punch\line +} +\par} + +{\pard\fi0\ql +{\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Killing Gloves of Boxing\line +} +\par} + +{\pard\fi0\ql +{Crits last 15 seconds longer\line +\line +} +\par} + +{\pard\fi0\ql +{\b Warrior's Spirit\line +} +\par} + +{\pard\fi0\ql +{Stronger punches makes enemies go bye bye\line +\line +} +\par} + +{\pard\fi0\ql +{\i Primary \line +} +\par} + +{\pard\fi0\ql +{\b Minigun, Minigun (Renamed/Strange), Festive Minigun, Silver Botkiller Minigun Mk.I, Gold Botkiller Minigun Mk.I, Rust Botkiller Minigun Mk.I, Blood Botkiller Minigun Mk.I, Carbonado Botkiller Minigun Mk.I, Diamond Botkiller Minigun Mk.I, Silver Botkiller Minigun Mk.II, Gold Botkiller Minigun Mk.II, Iron Curtain, King of the Jungle, Iron Wood, Antique Annihilator, War Room, Citizen Pain, Brick House, Macabre Web, Pumpkin Patch, Nutcracker, Brain Candy, Mister Cuddles, Coffin Nail, Dressed to Kill, Top Shelf, Butcher Bird\line +} +\par} + +{\pard\fi0\ql +{now heals on hit\line +\line +} +\par} + +{\pard\fi0\ql +{\b Natascha\line +} +\par} + +{\pard\fi0\ql +{slows the enemy more\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Brass Beast\line +} +\par} + +{\pard\fi0\ql +{more bullets, more spread\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Huo-Long Heater, The Huo-Long Heater (Genuine)\line +} +\par} + +{\pard\fi0\ql +{crits against dudeson fire\line +\line +} +\par} + +{\pard\fi0\ql +{\b Tomislav\line +} +\par} + +{\pard\fi0\ql +{more accurate and more faster\line +\line +} +\par} + +{\pard\fi0\ql +{\i Secondary \line +} +\par} + +{\pard\fi0\ql +{\b Panic Attack\line +} +\par} + +{\pard\fi0\ql +{Less damage, faster shooting, always accurate\line +\line +} +\par} + +{\pard\fi0\ql +{\b Sandvich, Robo-Sandvich, Festive Sandvich\line +} +\par} + +{\pard\fi0\ql +{Healing increased while eating\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Buffalo Steak Sandvich\line +} +\par} + +{\pard\fi0\ql +{instead of taking more dmg, you take like 70% less\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Dalokohs Bar, Fishcake, Second Banana\line +} +\par} + +{\pard\fi0\ql +{now can switch to and throw faster\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Family Business\line +} +\par} + +{\pard\fi0\ql +{Is now scattergun and fat scout, you give up your primary for this\line +\line +} +\par} + +{\pard\fi0\ql +{\b\ul Medic\line +} +\par} + +{\pard\fi0\ql +{\i Melee \line +} +\par} + +{\pard\fi0\ql +{\b Bonesaw, Bonesaw (Renamed/Strange), Festive Bonesaw\line +} +\par} + +{\pard\fi0\ql +{Now adds 10 seconds of bleed\line +\line +} +\par} + +{\pard\fi0\ql +{\i Secondary \line +} +\par} + +{\pard\fi0\ql +{\b Coffin Nail\line +} +\par} + +{\pard\fi0\ql +{now heals on hit\line +\line +} +\par} + +{\pard\fi0\ql +{\b\ul Pyro\line +} +\par} + +{\pard\fi0\ql +{\i Melee \line +} +\par} + +{\pard\fi0\ql +{\b Fire Axe, Fire Axe (Renamed/Strange)\line +} +\par} + +{\pard\fi0\ql +{Added:Reveals disguised victim on hit\line +10 max health added\line +\line +} +\par} + +{\pard\fi0\ql +{\i Primary \line +} +\par} + +{\pard\fi0\ql +{\b Flame Thrower, Flame Thrower (Renamed/Strange), Festive Flame Thrower, Silver Botkiller Flame Thrower Mk.I, Gold Botkiller Flame Thrower Mk.I, Rust Botkiller Flame Thrower Mk.I, Blood Botkiller Flame Thrower Mk.I, Carbonado Botkiller Flame Thrower Mk.I, Diamond Botkiller Flame Thrower Mk.I, Silver Botkiller Flame Thrower Mk.II\line +} +\par} + +{\pard\fi0\ql +{pyro\line +\line +} +\par} + +{\pard\fi0\ql +{\i Secondary \line +} +\par} + +{\pard\fi0\ql +{\b Gas Passer\line +} +\par} + +{\pard\fi0\ql +{sniper has to piss a lot so recharge is fast and throws fast too\line +\line +} +\par} + +{\pard\fi0\ql +{\b Panic Attack\line +} +\par} + +{\pard\fi0\ql +{Less damage, faster shooting, always accurate\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Reserve Shooter\line +} +\par} + +{\pard\fi0\ql +{Is now a anti aircraft rifle, crits instead of minicrit, 100% accurate, 1 clip size\line +\line +} +\par} + +{\pard\fi0\ql +{\b\ul Scout\line +} +\par} + +{\pard\fi0\ql +{\i Melee \line +} +\par} + +{\pard\fi0\ql +{\b Sun-on-a-Stick\line +} +\par} + +{\pard\fi0\ql +{increased dmg to people on fire by 100%\line +\line +} +\par} + +{\pard\fi0\ql +{\i Primary \line +} +\par} + +{\pard\fi0\ql +{\b Baby Face's Blaster\line +} +\par} + +{\pard\fi0\ql +{No longer lose hype, build hype by jumping and keep it forever\line +\line +} +\par} + +{\pard\fi0\ql +{\b Force-A-Nature\line +} +\par} + +{\pard\fi0\ql +{wowzers\line +\line +} +\par} + +{\pard\fi0\ql +{\b Scattergun, Scattergun (Renamed/Strange), Festive Scattergun, Silver Botkiller Scattergun Mk.I, Gold Botkiller Scattergun Mk.I, Rust Botkiller Scattergun Mk.I, Blood Botkiller Scattergun Mk.I, Carbonado Botkiller Scattergun Mk.I, Diamond Botkiller Scattergun Mk.I, Silver Botkiller Scattergun Mk.II, Gold Botkiller Scattergun Mk.II, Night Terror, Tartan Torpedo, Country Crusher, Backcountry Blaster, Spruce Deuce, Current Event, Macabre Web, Nutcracker, Blue Mew, Flower Power, Shot to Hell, Killer Bee, Corsair, Coffin Nail\line +} +\par} + +{\pard\fi0\ql +{Speedboost on hit!\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Back Scatter\line +} +\par} + +{\pard\fi0\ql +{Crits when it would minicrit\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Shortstop\line +} +\par} + +{\pard\fi0\ql +{Increased damage, but take slightly more pushback\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Soda Popper\line +} +\par} + +{\pard\fi0\ql +{Less Clip, faster reload\line +\line +} +\par} + +{\pard\fi0\ql +{\i Secondary \line +} +\par} + +{\pard\fi0\ql +{\b Mad Milk, Mutated Milk\line +} +\par} + +{\pard\fi0\ql +{sniper has to piss a lot so recharge is fast and throws fast too\line +\line +} +\par} + +{\pard\fi0\ql +{\b\ul Sniper\line +} +\par} + +{\pard\fi0\ql +{\i Melee \line +} +\par} + +{\pard\fi0\ql +{\b The Bushwacka\line +} +\par} + +{\pard\fi0\ql +{speed boost on kill so its easier to chain\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Shahanshah\line +} +\par} + +{\pard\fi0\ql +{90% less dmg while half alife, 100% dmg bonus while half dead\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Tribalman's Shiv\line +} +\par} + +{\pard\fi0\ql +{on hit bleed for 30 seconds ya bloody bleeder!\line +\line +} +\par} + +{\pard\fi0\ql +{\i Primary \line +} +\par} + +{\pard\fi0\ql +{\b Sniper Rifle, Sniper Rifle (Renamed/Strange), Festive Sniper Rifle, Silver Botkiller Sniper Rifle Mk.I, Gold Botkiller Sniper Rifle Mk.I, The AWPer Hand, Rust Botkiller Sniper Rifle Mk.I, Blood Botkiller Sniper Rifle Mk.I, Carbonado Botkiller Sniper Rifle Mk.I, Diamond Botkiller Sniper Rifle Mk.I, Silver Botkiller Sniper Rifle Mk.II, Gold Botkiller Sniper Rifle Mk.II, The Classic, Night Owl, Purple Range, Lumber From Down Under, Shot in the Dark, Bogtrotter, Thunderbolt, Pumpkin Patch, Boneyard, Wildwood, Balloonicorn, Rainbow, Coffin Nail, Dressed to Kill, Airwolf, Shooting Star\line +} +\par} + +{\pard\fi0\ql +{now shoots rockets instead of bullets\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Bazaar Bargain\line +} +\par} + +{\pard\fi0\ql +{added 30% more headshot damage\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Hitman's Heatmaker\line +} +\par} + +{\pard\fi0\ql +{Now drops arrows and you can jump real high and run real fast, giving Sniper an alternative playstyle\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Huntsman, Festive Huntsman, The Fortified Compound\line +} +\par} + +{\pard\fi0\ql +{arrows so fast they are bullets\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Machina, Shooting Star\line +} +\par} + +{\pard\fi0\ql +{Increased fully charged damage bonus a little bit, but careful as it now HEALS ENEMY BUILDINGS!\line +Because NoFungineer figured out how to get heal with bullets\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Sydney Sleeper\line +} +\par} + +{\pard\fi0\ql +{All shots penetrates\line +\line +} +\par} + +{\pard\fi0\ql +{\i Secondary \line +} +\par} + +{\pard\fi0\ql +{\b Cozy Camper\line +} +\par} + +{\pard\fi0\ql +{self heal increased to 25 pr second\line +\line +} +\par} + +{\pard\fi0\ql +{\b Darwin's Danger Shield\line +} +\par} + +{\pard\fi0\ql +{100% resistance to afterburn\line +\line +} +\par} + +{\pard\fi0\ql +{\b Jarate, Festive Jarate, The Self-Aware Beauty Mark\line +} +\par} + +{\pard\fi0\ql +{sniper has to piss a lot so recharge is fast and throws fast too\line +\line +} +\par} + +{\pard\fi0\ql +{\b SMG, Festive SMG, SMG (Renamed/Strange), Woodsy Widowmaker, Plaid Potshotter, Treadplate Tormenter, Team Sprayer, Low Profile, Wildwood, Blue Mew, High Roller's, Blitzkrieg\line +} +\par} + +{\pard\fi0\ql +{is now the minigun\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Cleaner's Carbine\line +} +\par} + +{\pard\fi0\ql +{increased minicrit duration to 60 seconds\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Razorback\line +} +\par} + +{\pard\fi0\ql +{can't get killed by backstabs unless the spy uses the fast knife\line +\line +} +\par} + +{\pard\fi0\ql +{\b\ul Soldier\line +} +\par} + +{\pard\fi0\ql +{\i Melee \line +} +\par} + +{\pard\fi0\ql +{\b Shovel, Shovel (Renamed/Strange)\line +} +\par} + +{\pard\fi0\ql +{Added: Mini-crits from the back\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Disciplinary Action\line +} +\par} + +{\pard\fi0\ql +{Faster whips, less dmg\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Equalizer\line +} +\par} + +{\pard\fi0\ql +{You can get even more overheal as soldier!\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Escape Plan\line +} +\par} + +{\pard\fi0\ql +{4 times the healing from medics when active!\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Half-Zatoichi\line +} +\par} + +{\pard\fi0\ql +{Longer deploy and holster time removed\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Market Gardener\line +} +\par} + +{\pard\fi0\ql +{Melee Range Increased a bit\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Pain Train\line +} +\par} + +{\pard\fi0\ql +{You now capture at 3x\line +\line +} +\par} + +{\pard\fi0\ql +{\i Primary \line +} +\par} + +{\pard\fi0\ql +{\b Rocket Launcher, Rocket Launcher (Renamed/Strange), The Direct Hit, The Black Box, Rocket Jumper, The Liberty Launcher, The Cow Mangler 5000, The Original, Festive Rocket Launcher, The Beggar's Bazooka, Silver Botkiller Rocket Launcher Mk.I, Gold Botkiller Rocket Launcher Mk.I, Rust Botkiller Rocket Launcher Mk.I, Blood Botkiller Rocket Launcher Mk.I, Carbonado Botkiller Rocket Launcher Mk.I, Diamond Botkiller Rocket Launcher Mk.I, Silver Botkiller Rocket Launcher Mk.II, Gold Botkiller Rocket Launcher Mk.II, Festive Black Box, The Air Strike, Woodland Warrior, Sand Cannon, American Pastoral, Smalltown Bringdown, Shell Shocker, Aqua Marine, Autumn, Blue Mew, Brain Candy, Coffin Nail, High Roller's, Warhawk\line +} +\par} + +{\pard\fi0\ql +{You can now rocketjump higher\line +\line +} +\par} + +{\pard\fi0\ql +{\i Secondary \line +} +\par} + +{\pard\fi0\ql +{\b Gunboats\line +} +\par} + +{\pard\fi0\ql +{25% Faster move speed for faster and longer rocket jumps!\line +\line +} +\par} + +{\pard\fi0\ql +{\b Panic Attack\line +} +\par} + +{\pard\fi0\ql +{Less damage, faster shooting, always accurate\line +\line +} +\par} + +{\pard\fi0\ql +{\b The B.A.S.E. Jumper\line +} +\par} + +{\pard\fi0\ql +{Jump height increased by 200%\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Buff Banner, Festive Buff Banner, The Battalion's Backup, The Concheror\line +} +\par} + +{\pard\fi0\ql +{Now lasts 3 as long\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Mantreads\line +} +\par} + +{\pard\fi0\ql +{Better air control\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Reserve Shooter\line +} +\par} + +{\pard\fi0\ql +{Is now a anti aircraft rifle, crits instead of minicrit, 100% accurate, 1 clip size\line +\line +} +\par} + +{\pard\fi0\ql +{\b The Righteous Bison\line +} +\par} + +{\pard\fi0\ql +{Faster lazers\line +\line +} +\par} + +{\pard\fi0\ql +{\b\ul Spy\line +} +\par} + +{\pard\fi0\ql +{\i Secondary \line +} +\par} + +{\pard\fi0\ql +{\b Coffin Nail\line +} +\par} + +{\pard\fi0\ql +{You can now rocketjump higher\line +\line +} +\par} + +} diff --git a/Tf2Rebalance.CreateSummary.Tests/tf2rebalance_attributes.example_summary.rtf b/Tf2Rebalance.CreateSummary.Tests/tf2rebalance_attributes.example_summary.rtf new file mode 100644 index 0000000..18f6b99 --- /dev/null +++ b/Tf2Rebalance.CreateSummary.Tests/tf2rebalance_attributes.example_summary.rtf @@ -0,0 +1,68 @@ +{\rtf1\ansi\deff0 + +{\fonttbl +{\f0 Times New Roman;} +} + +{\colortbl +; +} + +\deflang1033\plain\fs24\widowctrl\hyphauto\ftnbj +\paperw11906\paperh16838 +\margt1000 +\margr1000 +\margb1000 +\margl1000 + + +{\pard\fi0\ql +{\b\i\ul Weapons\line +} +\par} + +{\pard\fi0\ql +{\b\ul Demoman\line +} +\par} + +{\pard\fi0\ql +{\i Melee \line +} +\par} + +{\pard\fi0\ql +{\b The Eyelander, Festive Eyelander, Nessie's Nine Iron, Horseless Headless Horsemann's Headtaker\line +} +\par} + +{\pard\fi0\ql +{Longer deploy and holster time removed\line +\line +} +\par} + +{\pard\fi0\ql +{\b\ul Soldier\line +} +\par} + +{\pard\fi0\ql +{\i Primary \line +} +\par} + +{\pard\fi0\ql +{\b The Direct Hit\line +} +\par} + +{\pard\fi0\ql +{Direct Hit:\line +- 25% jump height.\line +- -50% less clip size.\line +\line +} +\par} + +} diff --git a/Tf2Rebalance.CreateSummary.sln b/Tf2Rebalance.CreateSummary.sln index 1b0a539..67893d5 100644 --- a/Tf2Rebalance.CreateSummary.sln +++ b/Tf2Rebalance.CreateSummary.sln @@ -3,14 +3,16 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.28307.106 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tf2Rebalance.CreateSummary", "Tf2Rebalance.CreateSummary\Tf2Rebalance.CreateSummary.csproj", "{3DEFE1BC-DD4C-49C1-94FC-F929F1CC02D2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tf2Rebalance.CreateSummary", "Tf2Rebalance.CreateSummary\Tf2Rebalance.CreateSummary.csproj", "{3DEFE1BC-DD4C-49C1-94FC-F929F1CC02D2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tf2Rebalance.CreateSummary.Tests", "Tf2Rebalance.CreateSummary.Tests\Tf2Rebalance.CreateSummary.Tests.csproj", "{28026CF3-AC82-4CE0-9638-EFA66F552A09}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tf2Rebalance.CreateSummary.Tests", "Tf2Rebalance.CreateSummary.Tests\Tf2Rebalance.CreateSummary.Tests.csproj", "{28026CF3-AC82-4CE0-9638-EFA66F552A09}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValveFormat.Superpower", "ValveFormat.Superpower\ValveFormat.Superpower.csproj", "{71B4A6D0-15DA-465B-A44C-CED84A6CF429}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValveFormat.Superpower.Tests", "ValveFormat.Superpower.Tests\ValveFormat.Superpower.Tests.csproj", "{6924341B-023D-4001-B27A-BF3700095058}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RtfWriter.Standard", "RtfWriter.Standard\RtfWriter.Standard.csproj", "{4A1F7183-B8EA-463A-BE1F-D7617F5BDD21}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {6924341B-023D-4001-B27A-BF3700095058}.Debug|Any CPU.Build.0 = Debug|Any CPU {6924341B-023D-4001-B27A-BF3700095058}.Release|Any CPU.ActiveCfg = Release|Any CPU {6924341B-023D-4001-B27A-BF3700095058}.Release|Any CPU.Build.0 = Release|Any CPU + {4A1F7183-B8EA-463A-BE1F-D7617F5BDD21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A1F7183-B8EA-463A-BE1F-D7617F5BDD21}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A1F7183-B8EA-463A-BE1F-D7617F5BDD21}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A1F7183-B8EA-463A-BE1F-D7617F5BDD21}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Tf2Rebalance.CreateSummary/Formater/IRebalanceInfoFormater.cs b/Tf2Rebalance.CreateSummary/Formater/IRebalanceInfoFormater.cs new file mode 100644 index 0000000..f1f9fdf --- /dev/null +++ b/Tf2Rebalance.CreateSummary/Formater/IRebalanceInfoFormater.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace Tf2Rebalance.CreateSummary +{ + public interface IRebalanceInfoFormater + { + string Create(IEnumerable infos); + } +} \ No newline at end of file diff --git a/Tf2Rebalance.CreateSummary/IRebalanceInfoFormater.cs b/Tf2Rebalance.CreateSummary/Formater/RebalanceInfoFormaterBase.cs similarity index 73% rename from Tf2Rebalance.CreateSummary/IRebalanceInfoFormater.cs rename to Tf2Rebalance.CreateSummary/Formater/RebalanceInfoFormaterBase.cs index 53cf3ff..38eff52 100644 --- a/Tf2Rebalance.CreateSummary/IRebalanceInfoFormater.cs +++ b/Tf2Rebalance.CreateSummary/Formater/RebalanceInfoFormaterBase.cs @@ -1,16 +1,9 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.RegularExpressions; namespace Tf2Rebalance.CreateSummary { - public interface IRebalanceInfoFormater - { - string Create(IEnumerable infos); - } - public abstract class RebalanceInfoFormaterBase : IRebalanceInfoFormater { private string _slotPattern = @"\[Slot (\d)\]"; @@ -85,41 +78,4 @@ private string GetSlot(string slot) return match.Groups[1].Captures[0].Value; } } - - public class RebalanceInfoTextFormater : RebalanceInfoFormaterBase - { - private StringBuilder _builder; - - protected override void Init() - { - _builder = new StringBuilder(); - } - - protected override void WriteCategory(string text) - { - _builder.AppendLine(text); - } - - protected override void WriteClass(string text) - { - _builder.AppendLine(text); - } - - protected override void WriteSlot(string text) - { - _builder.AppendLine(text); - } - - protected override void Write(RebalanceInfo weapon) - { - _builder.AppendLine(weapon.name); - _builder.AppendLine(weapon.info); - _builder.AppendLine(); - } - - protected override string Finalize() - { - return _builder.ToString(); - } - } } \ No newline at end of file diff --git a/Tf2Rebalance.CreateSummary/Formater/RebalanceInfoRtfFormater.cs b/Tf2Rebalance.CreateSummary/Formater/RebalanceInfoRtfFormater.cs new file mode 100644 index 0000000..11eead1 --- /dev/null +++ b/Tf2Rebalance.CreateSummary/Formater/RebalanceInfoRtfFormater.cs @@ -0,0 +1,56 @@ +using RtfWriter.Standard; + +namespace Tf2Rebalance.CreateSummary +{ + public class RebalanceInfoRtfFormater : RebalanceInfoFormaterBase + { + private RtfDocument _document; + + protected override void Init() + { + _document = new RtfDocument(PaperSize.A4, PaperOrientation.Portrait, Lcid.English); + } + + protected override void WriteCategory(string text) + { + RtfParagraph paragraph = _document.addParagraph(); + RtfCharFormat format = paragraph.addCharFormat(); + format.FontStyle.addStyle(FontStyleFlag.Bold | FontStyleFlag.Italic | FontStyleFlag.Underline); + paragraph.Text.AppendLine(text); + } + + protected override void WriteClass(string text) + { + RtfParagraph paragraph = _document.addParagraph(); + RtfCharFormat format = paragraph.addCharFormat(); + format.FontStyle.addStyle(FontStyleFlag.Bold | FontStyleFlag.Underline); + paragraph.Text.AppendLine(text); + } + + protected override void WriteSlot(string text) + { + RtfParagraph paragraph = _document.addParagraph(); + RtfCharFormat format = paragraph.addCharFormat(); + format.FontStyle.addStyle(FontStyleFlag.Italic); + paragraph.Text.AppendLine(text); + } + + protected override void Write(RebalanceInfo weapon) + { + RtfParagraph paragraphWeaponName = _document.addParagraph(); + RtfCharFormat formatWeaponName = paragraphWeaponName.addCharFormat(); + formatWeaponName.FontStyle.addStyle(FontStyleFlag.Bold); + paragraphWeaponName.Text.AppendLine(weapon.name); + + RtfParagraph paragraph = _document.addParagraph(); + RtfCharFormat format = paragraph.addCharFormat(); + paragraph.Text.AppendLine(weapon.info); + paragraph.Text.AppendLine(); + } + + protected override string Finalize() + { + return _document.render(); + } + } +} \ No newline at end of file diff --git a/Tf2Rebalance.CreateSummary/Formater/RebalanceInfoTextFormater.cs b/Tf2Rebalance.CreateSummary/Formater/RebalanceInfoTextFormater.cs new file mode 100644 index 0000000..ad45a67 --- /dev/null +++ b/Tf2Rebalance.CreateSummary/Formater/RebalanceInfoTextFormater.cs @@ -0,0 +1,41 @@ +using System.Text; + +namespace Tf2Rebalance.CreateSummary +{ + public class RebalanceInfoTextFormater : RebalanceInfoFormaterBase + { + private StringBuilder _builder; + + protected override void Init() + { + _builder = new StringBuilder(); + } + + protected override void WriteCategory(string text) + { + _builder.AppendLine(text); + } + + protected override void WriteClass(string text) + { + _builder.AppendLine(text); + } + + protected override void WriteSlot(string text) + { + _builder.AppendLine(text); + } + + protected override void Write(RebalanceInfo weapon) + { + _builder.AppendLine(weapon.name); + _builder.AppendLine(weapon.info); + _builder.AppendLine(); + } + + protected override string Finalize() + { + return _builder.ToString(); + } + } +} \ No newline at end of file diff --git a/Tf2Rebalance.CreateSummary/Program.cs b/Tf2Rebalance.CreateSummary/Program.cs index 724f1e7..2c9f49a 100644 --- a/Tf2Rebalance.CreateSummary/Program.cs +++ b/Tf2Rebalance.CreateSummary/Program.cs @@ -36,7 +36,7 @@ static void Main(string[] args) IDictionary> weaponNames = AlliedModsWiki.GetItemInfos(); Converter converter = new Converter(weaponNames); - IRebalanceInfoFormater formater = new RebalanceInfoTextFormater(); + IRebalanceInfoFormater formater = new RebalanceInfoRtfFormater(); foreach (string filename in args) { @@ -78,7 +78,8 @@ private static void CreateSummary(string filename, Converter converter, IRebalan return; } - string outputFilename = filename.Replace(Path.GetFileNameWithoutExtension(filename), Path.GetFileNameWithoutExtension(filename) + "_summary"); + //string outputFilename = filename.Replace(Path.GetFileNameWithoutExtension(filename), Path.GetFileNameWithoutExtension(filename) + "_summary"); + string outputFilename = Path.GetFileNameWithoutExtension(filename) + "_summary.rtf"; Log.Information("writing summary to {SummaryFileName}", outputFilename); File.WriteAllText(outputFilename, output); diff --git a/Tf2Rebalance.CreateSummary/Properties/launchSettings.json b/Tf2Rebalance.CreateSummary/Properties/launchSettings.json index 264dcd8..e2b5ba6 100644 --- a/Tf2Rebalance.CreateSummary/Properties/launchSettings.json +++ b/Tf2Rebalance.CreateSummary/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Tf2Rebalance.CreateSummary": { "commandName": "Project", - "commandLineArgs": "higps.txt" + "commandLineArgs": "tf2rebalance_attributes.example.txt" } } } \ No newline at end of file diff --git a/Tf2Rebalance.CreateSummary/Tf2Rebalance.CreateSummary.csproj b/Tf2Rebalance.CreateSummary/Tf2Rebalance.CreateSummary.csproj index 99252c8..2db9cb7 100644 --- a/Tf2Rebalance.CreateSummary/Tf2Rebalance.CreateSummary.csproj +++ b/Tf2Rebalance.CreateSummary/Tf2Rebalance.CreateSummary.csproj @@ -15,6 +15,7 @@ +