using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; using System.IO; using FastReport.Utils; using FastReport.Code; using FastReport.RichTextParser; using System.Windows.Forms; using System.Drawing.Design; #if !FRCORE using FastReport.Controls; #endif namespace FastReport { /// /// Represents a RichText object that can display formatted text. /// /// /// Use the property to set the object's text. The text may include /// the RTF formatting tags. /// public partial class RichObject : TextObjectBase, ITranslatable { #region Fields private string dataColumn; private int actualTextStart; private int actualTextLength; private string savedText; private string savedDataColumn; private float translated_height; #if !FRCORE && !MONO private Metafile cachedMetafile; #endif private bool oldBreakStyle; private bool translateObject; private bool allowExpressionFormat; private DiffEventHandler event_handler = null; internal List TransltedObjects = new List(); internal bool localVisibleStorage = false; #endregion #region Properties /// /// Gets or sets the object's text. /// /// /// This property returns the formatted text with rtf tags. /// [Category("Data")] [Editor("FastReport.TypeEditors.TextEditor, FastReport", typeof(UITypeEditor))] public override string Text { get { return base.Text; } set { base.Text = value; dataColumn = ""; #if !FRCORE && !MONO DestroyCachedMetafile(); #endif } } /// /// Gets or sets a name of the data column bound to this control. /// /// /// Value must contain the datasource name, for example: "Datasource.Column". /// [Category("Data")] [Editor("FastReport.TypeEditors.DataColumnEditor, FastReport", typeof(UITypeEditor))] public string DataColumn { get { return dataColumn; } set { if (!String.IsNullOrEmpty(value)) { if (!String.IsNullOrEmpty(Brackets)) { string[] brackets = Brackets.Split(','); Text = brackets[0] + value + brackets[1]; } } dataColumn = value; } } /// /// Gets the actual text start. /// /// /// This property is for internal use only; you should not use it in your code. /// [Browsable(false)] public int ActualTextStart { get { return actualTextStart; } set { actualTextStart = value; } } /// /// Gets the actual text length. /// /// /// This property is for internal use only; you should not use it in your code. /// [Browsable(false)] public int ActualTextLength { get { return actualTextLength; } set { actualTextLength = value; } } /// /// Gets or sets the break style. /// /// /// Set this property to true if you want editable rich text when you edit the prepared report page. /// public bool OldBreakStyle { get { return oldBreakStyle; } set { oldBreakStyle = value; } } /// /// Experimental feature for translation of RichText into report objects /// public bool ConvertRichText { get { #if !FRCORE && !MONO return translateObject; #else return true; #endif } set { translateObject = value; } } /// /// This property not described /// public bool KeepExpressionFormat { get { return allowExpressionFormat; } set { allowExpressionFormat = value; } } #endregion #region Private Methods #if !FRCORE && !MONO private FRRichTextBox CreateRich() { FRRichTextBox rich = new FRRichTextBox(); if (Text != null && Text.StartsWith(@"{\rtf")) rich.Rtf = Text; else rich.Text = Text; Color color = Color.White; if (Fill is SolidFill) color = (Fill as SolidFill).Color; if (color == Color.Transparent) color = Color.White; rich.BackColor = color; rich.DetectUrls = false; return rich; } private Metafile CreateMetafile(FRPaintEventArgs e) { Graphics measureGraphics = Report == null ? e.Graphics.Graphics : Report.PrintSettings.MeasureGraphics == null ? e.Graphics.Graphics : Report.PrintSettings.MeasureGraphics.Graphics; if (measureGraphics == null) measureGraphics = e.Graphics.Graphics; float scaleX = measureGraphics.DpiX / 96f; float scaleY = measureGraphics.DpiY / 96f; IntPtr hdc = measureGraphics.GetHdc(); Metafile emf = new Metafile(hdc, new RectangleF(0, 0, (Width - Padding.Horizontal) * scaleX, (Height - Padding.Vertical) * scaleY), MetafileFrameUnit.Pixel); measureGraphics.ReleaseHdc(hdc); // create metafile canvas and draw on it using (Graphics g = Graphics.FromImage(emf)) using (FRRichTextBox rich = CreateRich()) { int textStart = ActualTextStart; int textLength = ActualTextLength != 0 ? ActualTextLength : rich.TextLength - textStart; rich.FormatRange(g, measureGraphics, new RectangleF(0, 0, Width - Padding.Horizontal, Height - Padding.Vertical), textStart, textStart + textLength, false); } return emf; } private void DestroyCachedMetafile() { if (cachedMetafile != null) { cachedMetafile.Dispose(); cachedMetafile = null; } } private void DrawRich(FRPaintEventArgs e) { // avoid GDI+ errors if (Width < Padding.Horizontal + 1 || Height < Padding.Vertical + 1) return; // draw to emf because we need to zoom the image if (cachedMetafile == null) cachedMetafile = CreateMetafile(e); if (Fill.IsTransparent == false) { e.Graphics.DrawImage(cachedMetafile, new RectangleF((AbsLeft + Padding.Left) * e.ScaleX, (AbsTop + Padding.Top) * e.ScaleY, (Width - Padding.Horizontal) * e.ScaleX, (Height - Padding.Vertical) * e.ScaleY)); } else { int w = (int)Math.Floor((Width - Padding.Horizontal) * e.ScaleX); int h = (int)Math.Floor((Height - Padding.Vertical) * e.ScaleY); using (var target = new Bitmap(w, h)) using (var g = Graphics.FromImage(target)) { g.DrawImage(cachedMetafile, 0, 0, w, h); target.MakeTransparent(Color.White); e.Graphics.DrawImage(target, (AbsLeft + Padding.Left) * e.ScaleX, (AbsTop + Padding.Top) * e.ScaleY); } } if (IsDesigning) DestroyCachedMetafile(); } private void PrintRich(FRPaintEventArgs e) { // avoid GDI+ errors if (Width < Padding.Horizontal + 1 || Height < Padding.Vertical + 1) return; if (ConvertRichText == false) { // FormatRange method uses GDI and does not respect transform settings of GDI+. RectangleF textRect = new RectangleF( (AbsLeft + Padding.Left) + e.Graphics.Transform.OffsetX / e.ScaleX, (AbsTop + Padding.Top) + e.Graphics.Transform.OffsetY / e.ScaleY, (Width - Padding.Horizontal), (Height - Padding.Vertical)); IGraphics measureGraphics = Report == null ? e.Graphics : Report.PrintSettings.MeasureGraphics; if (measureGraphics == null) measureGraphics = e.Graphics; using (FRRichTextBox rich = CreateRich()) { int textStart = ActualTextStart; int textLength = ActualTextLength != 0 ? ActualTextLength : rich.TextLength - textStart; rich.FormatRange(e.Graphics.Graphics, measureGraphics.Graphics, textRect, textStart, textStart + textLength, false); } return; } // draw to emf because we need to zoom the image if (cachedMetafile == null) cachedMetafile = CreateMetafile(e); if (Fill.IsTransparent == false) { e.Graphics.DrawImage(cachedMetafile, new RectangleF((AbsLeft + Padding.Left) * e.ScaleX, (AbsTop + Padding.Top) * e.ScaleY, (Width - Padding.Horizontal) * e.ScaleX, (Height - Padding.Vertical) * e.ScaleY)); } else { int w = (int)Math.Floor((Width - Padding.Horizontal) * e.ScaleX); int h = (int)Math.Floor((Height - Padding.Vertical) * e.ScaleY); using (var target = new Bitmap(w, h)) using (var g = Graphics.FromImage(target)) { g.DrawImage(cachedMetafile, 0, 0, w, h); target.MakeTransparent(Color.White); e.Graphics.DrawImage(target, (AbsLeft + Padding.Left) * e.ScaleX, (AbsTop + Padding.Top) * e.ScaleY); } } } #endif internal float Convert2ReportObjects() { translated_height = this.Height; TransltedObjects.Clear(); #region "Create background-decoration object" TextObject tob = null; #if true if (this.Border.Lines != BorderLines.None || (this.FillColor != Color.White && this.FillColor != Color.Transparent)) { tob = new TextObject(); tob.Border = this.Border.Clone(); tob.BreakTo = this.BreakTo; // tob.Parent = this.Parent; tob.Fill = this.Fill.Clone(); tob.FillColor = this.FillColor; tob.SetName(this.Name + "_0"); // background object must have the height of the original object tob.Height = this.Height; tob.Bounds = this.Bounds; tob.ClientSize = this.ClientSize; tob.Top = 0; // this.Padding.Top; tob.Left = Left; // this.Padding.Left; tob.CanGrow = CanGrow; tob.GrowToBottom = GrowToBottom; tob.ZOrder = 0; tob.SaveState(); TransltedObjects.Add(tob); } #endif using (RichText2ReportObject convertor = new RichText2ReportObject()) using (RTF_DocumentParser parser = new RTF_DocumentParser()) { parser.Load(Text); RichDocument rtf = parser.Document; if (tob != null) // tob.FillColor = parser.GetFillColor(); tob.FillColor = Color.Transparent; TransltedObjects.AddRange(convertor.RichObject2ReportObjects(this, ref rtf, out translated_height)); } if (CanGrow && translated_height > this.Height || CanShrink && translated_height < this.Height) { // shrinking of the background object occurs here if (tob != null) tob.Height = translated_height; } return translated_height; } #endregion #endregion #region Protected Methods /// protected override void Dispose(bool disposing) { #if !FRCORE && !MONO if (disposing) DestroyCachedMetafile(); #endif base.Dispose(disposing); } #endregion #region Public Methods /// public override void Assign(Base source) { base.Assign(source); RichObject src = source as RichObject; DataColumn = src.DataColumn; ActualTextStart = src.ActualTextStart; ActualTextLength = src.ActualTextLength; OldBreakStyle = src.OldBreakStyle; ConvertRichText = src.ConvertRichText; TransltedObjects = src.TransltedObjects; } /// public override void Draw(FRPaintEventArgs e) { base.Draw(e); #if !FRCORE && !MONO try { if (!this.ConvertRichText || IsDesigning) { if (IsPrinting) PrintRich(e); else DrawRich(e); } } catch (Exception ex) { e.Graphics.DrawString(ex.ToString(), DrawUtils.DefaultReportFont, Brushes.Red, new RectangleF(AbsLeft * e.ScaleX, AbsTop * e.ScaleY, Width * e.ScaleX, Height * e.ScaleY)); } #endif #if !FRCORE DrawDesign(e); #endif Border.Draw(e, new RectangleF(AbsLeft, AbsTop, Width, Height)); } #endregion #region Report Engine /// public override void SaveState() { base.SaveState(); savedText = Text; localVisibleStorage = Visible; savedDataColumn = DataColumn; } /// public override void RestoreState() { int count = TransltedObjects.Count; if (ConvertRichText) { Report.Engine.RestoreRich(this); } base.RestoreState(); base.Text = savedText; dataColumn = savedDataColumn; if (count > 0) Visible = true; } /// public override string[] GetExpressions() { List expressions = new List(); expressions.AddRange(base.GetExpressions()); if (AllowExpressions && !String.IsNullOrEmpty(Brackets)) { // collect expressions found in the text string[] brackets = Brackets.Split(','); if (ConvertRichText) { string decoded_text; using (RTF_DocumentParser parser = new RTF_DocumentParser()) { parser.Load(Text); using (RTF_ToTextSaver saver = new RTF_ToTextSaver(parser.Document)) decoded_text = saver.PlainText; } expressions.AddRange(CodeUtils.GetExpressions(decoded_text, brackets[0], brackets[1])); } else { #if !FRCORE && !MONO using (FRRichTextBox rich = CreateRich()) { expressions.AddRange(CodeUtils.GetExpressions(rich.Text, brackets[0], brackets[1])); } #else System.Diagnostics.Trace.WriteLine("RichObject: ConvertRichText poropery must be enabled on Core and Mono systems"); #endif } } if (!String.IsNullOrEmpty(DataColumn)) expressions.Add(DataColumn); return expressions.ToArray(); } /// #if !FRCORE && !MONO public override void GetData() { base.GetData(); #if DIAGNOSTIC System.Diagnostics.Trace.WriteLine(this.Name + " GetData() "); #endif if (!String.IsNullOrEmpty(DataColumn)) { object value = Report.GetColumnValue(DataColumn); if (value is byte[]) { Text = value == null ? "" : System.Text.Encoding.UTF8.GetString(value as byte[]); } else { Text = value == null ? "" : value.ToString(); } } else if (AllowExpressions) { #if false //USE_ORIGINAL_RICH_EXPRESSION_CODE // Prior 20180423 // process expressions if (!String.IsNullOrEmpty(Brackets)) { using (FRRichTextBox rich = CreateRich()) { string[] brackets = Brackets.Split(','); FindTextArgs args = new FindTextArgs(); args.Text = new FastString(rich.Text); args.OpenBracket = brackets[0]; args.CloseBracket = brackets[1]; args.StartIndex = ActualTextStart; int expressionIndex = 0; while (args.StartIndex < args.Text.Length - 2) { string expression = CodeUtils.GetExpression(args, false); if (expression == "") break; string formattedValue = CalcAndFormatExpression(expression, expressionIndex); // strip off the "\r" characters since rich uses only "\n" for new line formattedValue = formattedValue.Replace("\r", ""); args.Text = args.Text.Remove(args.StartIndex, args.EndIndex - args.StartIndex); args.Text = args.Text.Insert(args.StartIndex, formattedValue); // fix for win8.1 and later // because their Selection* properties also includes control symbols { /*rich.SelectionStart = args.StartIndex; rich.SelectionLength = args.EndIndex - args.StartIndex; rich.SelectedText = formattedValue;*/ int richIndex = rich.Find(args.OpenBracket + expression + args.CloseBracket, args.StartIndex, RichTextBoxFinds.None); if (richIndex != -1) { rich.SelectedText = formattedValue; } } args.StartIndex += formattedValue.Length; expressionIndex++; } Text = rich.Rtf; } } #else // functions defined in RichText/RichObject.Ext.cs if (!ConvertRichText) CalculateExpressions(); else MergeRichText(); #endif } } /// public override float CalcHeight() { using (FRRichTextBox rich = CreateRich()) { int textStart = ActualTextStart; int textLength = ActualTextLength != 0 ? ActualTextLength : rich.TextLength - textStart; if (textLength <= 0) return 0; float h = SelectionHeight(rich, textStart, textLength); return h; } } private int SelectionHeight(FRRichTextBox rich, int start, int length) { using (Graphics g = rich.CreateGraphics()) { int n1 = 0; int n2 = 100000; Graphics measureGraphics = Report == null ? g : Report.PrintSettings.MeasureGraphics == null ? g : Report.PrintSettings.MeasureGraphics.Graphics; if (measureGraphics == null) measureGraphics = g; // find the height using halfway point for (int i = 0; i < 20; i++) { int mid = (n1 + n2) / 2; RectangleF textRect = new RectangleF(0, 0, Width - Padding.Horizontal, mid); int fit = rich.FormatRange(g, measureGraphics, textRect, start, start + length, true) - start; if (fit >= length) n2 = mid; else n1 = mid; if (Math.Abs(n1 - n2) < 2) break; } int height = Math.Max(n1, n2); // workaround bug in richtext control (one-line text returns 0 height) if (rich.TextLength > 0 && height <= 2) { RectangleF textRect = new RectangleF(0, 0, Width - Padding.Horizontal, 1000000); rich.FormatRange(g, measureGraphics, textRect, start, start + length, true, out height); } return height + Padding.Vertical; } } /// public override bool Break(BreakableComponent breakTo) { using (FRRichTextBox rich = CreateRich()) using (Graphics g = rich.CreateGraphics()) { // determine number of characters fit in the bounds. Set less height to prevent possible data loss. RectangleF textRect = new RectangleF(0, 0, Width - Padding.Horizontal, Height - Padding.Vertical - 20); Graphics measureGraphics = g; if (Report != null && Report.PrintSettings != null && Report.PrintSettings.MeasureGraphics != null && Report.PrintSettings.MeasureGraphics.Graphics != null) measureGraphics = Report.PrintSettings.MeasureGraphics.Graphics; // prevent page break when height is <= 0 if (textRect.Height <= 0) return false; int textStart = ActualTextStart; int textLength = ActualTextLength != 0 ? ActualTextLength : rich.TextLength - textStart; int charsFit = rich.FormatRange(g, measureGraphics, textRect, textStart, textStart + textLength, true) - textStart; // check the height of the content, because we don't need to split an object that contains half of the image. int tempLength = ActualTextLength; ActualTextLength = charsFit; float height = CalcHeight(); ActualTextLength = tempLength; // if we can print the contents, we can break this object if (charsFit <= 0 || Height < height) return false; // perform break if (breakTo != null) { RichObject richTo = breakTo as RichObject; if (OldBreakStyle) { // copy out-of-bounds rtf to the breakTo rich.SelectionStart = charsFit; rich.SelectionLength = rich.TextLength - charsFit; richTo.Text = rich.SelectedRtf; // leave text that fit in this object rich.SelectedText = ""; Text = rich.Rtf; } else { richTo.Text = Text; richTo.ActualTextStart = textStart + charsFit; ActualTextLength = charsFit; } } return true; } } #else /// public override float CalcHeight() { float bottom = 0; foreach (ReportComponentBase c in TransltedObjects) { bottom = Math.Max(bottom, c.Bottom); } return bottom - this.Top; /* // 20210713 - just calc height, do not translate object here height = Convert2ReportObjects(); if (TransltedObjects.Count == 0) height = Height; else if(this.CanShrink && height < Height) height = Height; #if DEEP_DIAGNOSTIC string decoded_text; using (RTF_DocumentParser parser = new RTF_DocumentParser()) { parser.Load(Text); using (RTF_ToTextSaver saver = new RTF_ToTextSaver(parser.Document)) decoded_text = saver.PlainText; } System.Diagnostics.Trace.WriteLine(decoded_text); System.Diagnostics.Trace.WriteLine(height); #endif return height; */ } /// public override bool Break(BreakableComponent breakTo) { // determine number of characters fit in the bounds. Set less height to prevent possible data loss. RectangleF textRect = new RectangleF(0, 0, Width - Padding.Horizontal, Height - Padding.Vertical - 20); // prevent page break when height is <= 0 if (textRect.Height <= 0) return false; int textStart = ActualTextStart; int charsFit = 0; List overlap = new List(); foreach (ReportComponentBase obj in this.TransltedObjects) { if (obj.Bottom > this.Bottom && obj != TransltedObjects[0]) { if (breakTo != null) { overlap.Add(obj); continue; } break; } charsFit += (obj as TextObject).Text.Length; } //if we can print the contents, we can break this object if (charsFit <= 0) return false; // perform break if (breakTo != null) { RichObject richTo = breakTo as RichObject; breakTo.Clear(); foreach (ReportComponentBase obj in overlap) richTo.TransltedObjects.Add(obj); richTo.ActualTextStart = textStart + charsFit; ActualTextLength = charsFit; } return true; } /// public override void GetData() { base.GetData(); if (!String.IsNullOrEmpty(DataColumn)) { object value = Report.GetColumnValue(DataColumn); if (value is byte[]) { Text = value == null ? "" : System.Text.Encoding.UTF8.GetString(value as byte[]); } else { Text = value == null ? "" : value.ToString(); } } else if (AllowExpressions) { CalculateExpressions(); } } #endif #endregion #region Serialization private void GetDiff(object sender, DiffEventArgs e) { if (Report != null) { if (e.Object is Report) e.DiffObject = Report; else if (e.Object is Base) e.DiffObject = Report.FindObject((e.Object as Base).Name); } } /// public override void Serialize(FRWriter writer) { #if DIAGNOSTIC System.Diagnostics.Trace.WriteLine(this.Name + " Serialize to " + writer.SerializeTo.ToString()); #endif if (this.Text == null) return; base.Serialize(writer); if (writer.DiffObject is RichObject) { RichObject c = writer.DiffObject as RichObject; if (ActualTextStart != c.ActualTextStart) writer.WriteInt("ActualTextStart", ActualTextStart); if (ActualTextLength != c.ActualTextLength) writer.WriteInt("ActualTextLength", ActualTextLength); if (writer.SerializeTo != SerializeTo.Preview) { if (DataColumn != c.DataColumn) writer.WriteStr("DataColumn", DataColumn); } if (OldBreakStyle != c.OldBreakStyle) writer.WriteBool("OldBreakStyle", OldBreakStyle); if (ConvertRichText != c.ConvertRichText) writer.WriteBool("ConvertRichText", ConvertRichText); if (KeepExpressionFormat != c.KeepExpressionFormat) writer.WriteBool("KeepExpressionFormat", KeepExpressionFormat); } } /// public override void Deserialize(FRReader reader) { #if DIAGNOSTIC System.Diagnostics.Trace.WriteLine(this.Name + " Deserialize from " + reader.DeserializeFrom.ToString()); #endif base.SetReport(reader.Report); base.Deserialize(reader); } #region Translated objects list /// public bool CanContain(Base child) { return (child is ReportComponentBase); } /// public void UpdateLayout(float dx, float dy) { } void ITranslatable.ConvertToReportObjects() { Convert2ReportObjects(); SecondStageTranslation(); } #endregion #endregion Serialization /// /// Initializes a new instance of the class with default settings. /// public RichObject() { event_handler = new DiffEventHandler(GetDiff); dataColumn = ""; SetFlags(Flags.HasSmartTag, true); } } }