diff --git a/FastReport.Base/Base.cs b/FastReport.Base/Base.cs index 98b67f7a..f362fd84 100644 --- a/FastReport.Base/Base.cs +++ b/FastReport.Base/Base.cs @@ -18,6 +18,7 @@ namespace FastReport /// Specifies a set of actions that cannot be performed on the object in the design mode. /// [Flags] + [TypeConverter(typeof(FastReport.TypeConverters.FlagConverter))] public enum Restrictions { /// @@ -60,6 +61,7 @@ public enum Restrictions /// Specifies a set of actions that can be performed on the object in the design mode. /// [Flags] + [TypeConverter(typeof(FastReport.TypeConverters.FlagConverter))] public enum Flags { /// diff --git a/FastReport.Base/Gauge/Radial/RadialGauge.cs b/FastReport.Base/Gauge/Radial/RadialGauge.cs index aa6cb76d..2478727c 100644 --- a/FastReport.Base/Gauge/Radial/RadialGauge.cs +++ b/FastReport.Base/Gauge/Radial/RadialGauge.cs @@ -35,6 +35,7 @@ public enum RadialGaugeType /// Radial Gauge position types /// [Flags] + [TypeConverter(typeof(FastReport.TypeConverters.FlagConverter))] public enum RadialGaugePosition { /// diff --git a/FastReport.Base/PictureObject.cs b/FastReport.Base/PictureObject.cs index 141698cf..cd78f293 100644 --- a/FastReport.Base/PictureObject.cs +++ b/FastReport.Base/PictureObject.cs @@ -142,7 +142,6 @@ public int GrayscaleHash set { grayscaleHash = value; } } - /// /// Gets or sets the color of the image that will be treated as transparent. /// @@ -180,8 +179,6 @@ public float Transparency } } - - /// /// Gets or sets a value indicating that the image should be tiled. /// @@ -193,8 +190,6 @@ public bool Tile set { tile = value; } } - - /// /// Gets or sets a value indicating that the image stored in the /// property should be disposed when this object is disposed. @@ -211,15 +206,6 @@ public bool ShouldDisposeImage set { shouldDisposeImage = value; } } - - - - - - - - - /// /// Gets or sets a bitmap transparent image /// @@ -314,6 +300,38 @@ private void UpdateTransparentImage() } } } + +#if MONO + private GraphicsPath GetRoundRectPath(RectangleF rectangleF, float radius) + { + GraphicsPath gp = new GraphicsPath(); + if (radius < 1) + radius = 1; + gp.AddLine(rectangleF.X + radius, rectangleF.Y, rectangleF.Width + rectangleF.X - radius, rectangleF.Y); + gp.AddArc(rectangleF.Width + rectangleF.X - radius - 1, rectangleF.Y, radius + 1, radius + 1, 270, 90); + gp.AddLine(rectangleF.Width + rectangleF.X, rectangleF.Y + radius, rectangleF.Width + rectangleF.X, rectangleF.Height + rectangleF.Y - radius); + gp.AddArc(rectangleF.Width + rectangleF.X - radius - 1, rectangleF.Height + rectangleF.Y - radius - 1, radius + 1, radius + 1, 0, 90); + gp.AddLine(rectangleF.Width + rectangleF.X - radius, rectangleF.Height + rectangleF.Y, rectangleF.X + radius, rectangleF.Height + rectangleF.Y); + gp.AddArc(rectangleF.X, rectangleF.Height + rectangleF.Y - radius - 1, radius + 1, radius + 1, 90, 90); + gp.AddLine(rectangleF.X, rectangleF.Height + rectangleF.Y - radius, rectangleF.X, rectangleF.Y + radius); + gp.AddArc(rectangleF.X, rectangleF.Y, radius, radius, 180, 90); + gp.CloseFigure(); + return gp; + } +#else + private GraphicsPath GetRoundRectPath(RectangleF rectangleF, float radius) + { + GraphicsPath gp = new GraphicsPath(); + if (radius < 1) + radius = 1; + gp.AddArc(rectangleF.Width + rectangleF.X - radius - 1, rectangleF.Y, radius + 1, radius + 1, 270, 90); + gp.AddArc(rectangleF.Width + rectangleF.X - radius - 1, rectangleF.Height + rectangleF.Y - radius - 1, radius + 1, radius + 1, 0, 90); + gp.AddArc(rectangleF.X, rectangleF.Height + rectangleF.Y - radius - 1, radius + 1, radius + 1, 90, 90); + gp.AddArc(rectangleF.X, rectangleF.Y, radius, radius, 180, 90); + gp.CloseFigure(); + return gp; + } +#endif #endregion #region Protected Methods @@ -373,12 +391,16 @@ public override void DrawImage(FRPaintEventArgs e) drawWidth, drawHeight); + GraphicsPath path = new GraphicsPath(); IGraphicsState state = g.Save(); try { //if (Config.IsRunningOnMono) // strange behavior of mono - we need to reset clip before we set new one - g.ResetClip(); - g.SetClip(drawRect); + g.ResetClip(); + + EstablishImageForm(path, drawLeft, drawTop, drawWidth, drawHeight); + + g.SetClip(path, CombineMode.Replace); Report report = Report; if (report != null && report.SmoothGraphics) { @@ -411,6 +433,8 @@ public override void DrawImage(FRPaintEventArgs e) finally { g.Restore(state); + g.ResetClip(); + path.Dispose(); } if (IsPrinting) @@ -552,17 +576,6 @@ public override void Deserialize(FRReader reader) } } - - - - - - //static int number = 0; - - - - - /// /// Loads image /// @@ -602,9 +615,56 @@ protected override void ResetImageIndex() { imageIndex = -1; } + + /// + /// The shape of the image is set using GraphicsPath + /// + /// + /// + /// + /// + /// + public void EstablishImageForm(GraphicsPath path, float drawLeft, float drawTop, float drawWidth, float drawHeight) + { + RectangleF drawRect = new RectangleF( + drawLeft, + drawTop, + drawWidth, + drawHeight); + + switch (Shape) + { + case ShapeKind.Rectangle: + path.AddRectangle(drawRect); + break; + case ShapeKind.RoundRectangle: + float min = Math.Min(drawWidth, drawHeight) / 4; + path.AddPath(GetRoundRectPath(drawRect, min), false); + break; + case ShapeKind.Ellipse: + path.AddEllipse(drawLeft, drawTop, drawWidth, drawHeight); + break; + case ShapeKind.Triangle: + PointF[] triPoints = + { + new PointF(drawLeft + drawWidth, drawTop + drawHeight), new PointF(drawLeft, drawTop + drawHeight), + new PointF(drawLeft + drawWidth / 2, drawTop), new PointF(drawLeft + drawWidth, drawTop + drawHeight) + }; + path.AddPolygon(triPoints); + break; + case ShapeKind.Diamond: + PointF[] diaPoints = + { + new PointF(drawLeft + drawWidth / 2, drawTop), new PointF(drawLeft + drawWidth, drawTop + drawHeight / 2), + new PointF(drawLeft + drawWidth / 2, drawTop + drawHeight), new PointF(drawLeft, drawTop + drawHeight / 2) + }; + path.AddPolygon(diaPoints); + break; + } + } #endregion -#region Report Engine + #region Report Engine /// diff --git a/FastReport.Base/PictureObjectBase.cs b/FastReport.Base/PictureObjectBase.cs index 65406064..81a76e20 100644 --- a/FastReport.Base/PictureObjectBase.cs +++ b/FastReport.Base/PictureObjectBase.cs @@ -92,6 +92,7 @@ public abstract partial class PictureObjectBase : ReportComponentBase private bool showErrorImage; private PictureBoxSizeMode sizeModeInternal; private ImageAlign imageAlign; + private ShapeKind shape; #endregion Private Fields @@ -333,6 +334,17 @@ public ImageAlign ImageAlign set { imageAlign = value; } } + /// + /// Gets or sets a shape kind. + /// + [DefaultValue(ShapeKind.Rectangle)] + [Category("Appearance")] + public ShapeKind Shape + { + get { return shape; } + set { shape = value; } + } + #endregion Public Properties @@ -358,6 +370,7 @@ public ImageAlign ImageAlign public PictureObjectBase() { sizeModeInternal = PictureBoxSizeMode.Zoom; + shape = ShapeKind.Rectangle; padding = new Padding(); imageLocation = ""; dataColumn = ""; @@ -387,6 +400,7 @@ public override void Assign(Base source) Grayscale = src.Grayscale; ShowErrorImage = src.ShowErrorImage; ImageAlign = src.ImageAlign; + Shape = src.Shape; } } @@ -668,8 +682,6 @@ private void UpdateAlign(RectangleF drawRect, ref PointF upperLeft, ref PointF u lowerLeft.Y += offsetY; } - - /// /// Loads image /// @@ -776,6 +788,8 @@ public override void Serialize(FRWriter writer) writer.WriteBool("ShowErrorImage", ShowErrorImage); if (ImageAlign != ImageAlign.None) writer.WriteValue("ImageAlign", ImageAlign); + if (Shape != c.Shape) + writer.WriteValue("Shape", Shape); } #endregion Public Methods diff --git a/FastReport.Base/Preview/PreparedPagePostprocessor.cs b/FastReport.Base/Preview/PreparedPagePostprocessor.cs index ce9b1505..5b0511e8 100644 --- a/FastReport.Base/Preview/PreparedPagePostprocessor.cs +++ b/FastReport.Base/Preview/PreparedPagePostprocessor.cs @@ -1,3 +1,4 @@ +using FastReport.Utils; using System; using System.Collections.Generic; @@ -6,6 +7,7 @@ namespace FastReport.Preview internal class PreparedPagePostprocessor { private Dictionary> duplicates; + private Dictionary> mergedTextObjects; private Dictionary bands; int iBand; @@ -112,6 +114,111 @@ private void CloseDuplicatesMerge(List list) lastObj.Height += delta; } + private void CollectMergedTextObjects(TextObject obj) + { + if (mergedTextObjects.ContainsKey(obj.Band.Name)) + { + List list = mergedTextObjects[obj.Band.Name]; + list.Add(obj); + } + else + { + List list = new List() { obj }; + mergedTextObjects.Add(obj.Band.Name, list); + } + } + + private void MergeTextObjects + () + { + foreach (var band in mergedTextObjects) + { + band.Value.Sort(delegate (TextObject txt, TextObject txt2) + { + if (txt.AbsLeft.CompareTo(txt2.AbsLeft) == 0) + return txt.AbsTop.CompareTo(txt2.AbsTop); + + return txt.AbsLeft.CompareTo(txt2.AbsLeft); + }); + + //Vertical merge + MergeTextObjectsInBand(band.Value); + + //May be horizontal merge + MergeTextObjectsInBand(band.Value); + } + } + + private void MergeTextObjectsInBand(List band) + { + for (int i = 0; i < band.Count; i++) + { + for (int j = i + 1; j < band.Count; j++) + { + if (Merge(band[j], band[i])) + { + TextObject removeObj = band[j]; + band.Remove(removeObj); + removeObj.Dispose(); + if(j > 0) + j--; + } + } + } + } + + private bool Merge(TextObject obj, TextObject obj2) + { + if (obj2.Text != obj.Text) + return false; + + var bounds = obj.AbsBounds; + if (bounds.Width < 0 || bounds.Height < 0) + Validator.NormalizeBounds(ref bounds); + + var bounds2 = obj2.AbsBounds; + if (bounds2.Width < 0 || bounds2.Height < 0) + Validator.NormalizeBounds(ref bounds2); + + if (obj.MergeMode.HasFlag(MergeMode.Vertical) && obj2.MergeMode.HasFlag(MergeMode.Vertical) + && IsEqualWithInaccuracy(bounds2.Width, bounds.Width) && IsEqualWithInaccuracy(bounds2.Left, bounds.Left)) + { + if (IsEqualWithInaccuracy(bounds2.Bottom, bounds.Top)) + { + obj2.Height += bounds.Height; + return true; + } + else if (IsEqualWithInaccuracy(bounds2.Top, bounds.Bottom)) + { + obj2.Height += bounds.Height; + obj2.Top -= bounds.Height; + return true; + } + } + else if (obj.MergeMode.HasFlag(MergeMode.Horizontal) && obj2.MergeMode.HasFlag(MergeMode.Horizontal) + && IsEqualWithInaccuracy(bounds2.Height, bounds.Height) && IsEqualWithInaccuracy(bounds2.Top, bounds.Top)) + { + if (IsEqualWithInaccuracy(bounds2.Right, bounds.Left)) + { + obj2.Width += bounds.Width; + return true; + } + else if (IsEqualWithInaccuracy(bounds2.Left, bounds.Right)) + { + obj2.Width += bounds.Width; + obj2.Left -= bounds.Width; + return true; + } + } + + return false; + } + + private bool IsEqualWithInaccuracy(float value1, float value2) + { + return Math.Abs(value1 - value2) < 0.01; + } + public void Postprocess(ReportPage page) { page.ExtractMacros(); @@ -128,14 +235,19 @@ public void Postprocess(ReportPage page) if (c is TextObjectBase txt && txt.Duplicates != Duplicates.Show) ProcessDuplicates(txt); + + if (c is TextObject text && text.MergeMode != MergeMode.None) + CollectMergedTextObjects(text); } + MergeTextObjects(); CloseDuplicates(); } public PreparedPagePostprocessor() { duplicates = new Dictionary>(); + mergedTextObjects = new Dictionary>(); bands = new Dictionary(); iBand = 0; } @@ -153,6 +265,11 @@ public void PostprocessUnlimited(PreparedPage preparedPage, ReportPage page) ProcessDuplicates(txt); flag = true; //flag for keep in dictionary } + if (c is TextObject text && text.MergeMode != MergeMode.None) + { + CollectMergedTextObjects(text); + flag = true; + } } i++; if (flag) @@ -165,6 +282,8 @@ public void PostprocessUnlimited(PreparedPage preparedPage, ReportPage page) b.Dispose(); } } + + MergeTextObjects(); CloseDuplicates(); } diff --git a/FastReport.Base/ReportComponentBase.cs b/FastReport.Base/ReportComponentBase.cs index 5801fd29..ee96cd4f 100644 --- a/FastReport.Base/ReportComponentBase.cs +++ b/FastReport.Base/ReportComponentBase.cs @@ -34,6 +34,7 @@ public enum ShiftMode /// Specifies where to print an object. /// [Flags] + [TypeConverter(typeof(FastReport.TypeConverters.FlagConverter))] public enum PrintOn { /// diff --git a/FastReport.Base/TextObject.cs b/FastReport.Base/TextObject.cs index 3788fafb..4eacddd3 100644 --- a/FastReport.Base/TextObject.cs +++ b/FastReport.Base/TextObject.cs @@ -245,6 +245,29 @@ public enum AutoShrinkMode FontWidth } + /// + /// Specifies the behavior of the MergeMode feature of TextObject. + /// + [TypeConverter(typeof(FastReport.TypeConverters.FlagConverter))] + [Flags] + public enum MergeMode + { + /// + /// Merge is disabled. + /// + None = 0, + + /// + /// Allows horizontal merging. + /// + Horizontal = 1, + + /// + /// Allows vertical merging. + /// + Vertical = 2, + } + /// /// Represents the Text object that may display one or several text lines. /// @@ -261,6 +284,7 @@ public enum AutoShrinkMode public partial class TextObject : TextObjectBase { #region Fields + private MergeMode mergeMode; private bool autoWidth; private HorzAlign horzAlign; private VertAlign vertAlign; @@ -688,8 +712,6 @@ public TextRenderType TextRenderType set { textRenderType = value; } } - - /// /// Gets or sets the paragraph offset, in pixels. For HtmlParagraph use ParagraphFormat.FirstLineIndent. /// @@ -722,6 +744,18 @@ public InlineImageCache InlineImageCache return inlineImageCache; } } + + /// + /// Gets or sets a value indicating whether the text should be merged with other nearby text objects. + /// + [DefaultValue(MergeMode.None)] + [Category("Behavior")] + [Editor("FastReport.TypeEditors.FlagsEditor, FastReport", typeof(UITypeEditor))] + public MergeMode MergeMode + { + get { return mergeMode; } + set { mergeMode = value; } + } #endregion #region Private Methods @@ -1097,6 +1131,7 @@ public override void Assign(Base source) inlineImageCache = src.inlineImageCache; PreserveLastLineSpace = src.PreserveLastLineSpace; paragraphFormat.Assign(src.paragraphFormat); + MergeMode = src.MergeMode; } /// @@ -1394,6 +1429,8 @@ public override void Serialize(FRWriter writer) writer.WriteFloat("ParagraphOffset", ParagraphOffset); if (ForceJustify != c.ForceJustify) writer.WriteBool("ForceJustify", ForceJustify); + if (MergeMode != c.MergeMode) + writer.WriteValue("MergeMode", MergeMode); if (writer.SerializeTo != SerializeTo.Preview) { if (Style != c.Style) diff --git a/FastReport.Base/TypeConverters/FlagConverter.cs b/FastReport.Base/TypeConverters/FlagConverter.cs new file mode 100644 index 00000000..d4277055 --- /dev/null +++ b/FastReport.Base/TypeConverters/FlagConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel; + +namespace FastReport.TypeConverters +{ + /// + /// Blocks keyboard editing, you need to select a value from the drop-down list for editing + /// + internal class FlagConverter : EnumConverter + { + public FlagConverter(Type type) : base(type) + { + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + return true; + } + } +} \ No newline at end of file diff --git a/FastReport.Base/Utils/GraphicCache.cs b/FastReport.Base/Utils/GraphicCache.cs index 2165195d..d8d6c1cb 100644 --- a/FastReport.Base/Utils/GraphicCache.cs +++ b/FastReport.Base/Utils/GraphicCache.cs @@ -163,6 +163,7 @@ public StringFormat GetStringFormat(StringAlignment align, StringAlignment lineA result.Trimming = trimming; result.FormatFlags = flags; float[] tabStops = new float[64]; + float sumWidth = firstTab; // fixed issue 2823 tabStops[0] = firstTab < 0 ? 0 : firstTab; for (int i = 1; i < 64; i++) @@ -170,9 +171,14 @@ public StringFormat GetStringFormat(StringAlignment align, StringAlignment lineA if (i > tabWidth.Count) { tabStops[i] = defaultTab < 0 ? 0 : defaultTab; + //tab stops have static positions we need to go back to them + if (sumWidth % defaultTab != 0) + tabStops[i] = defaultTab - sumWidth % defaultTab; + sumWidth += tabStops[i]; continue; } tabStops[i] = tabWidth[i - 1] < 0 ? 0 : tabWidth[i - 1]; + sumWidth += tabStops[i]; } result.SetTabStops(0, tabStops); stringFormats[hash] = result; diff --git a/FastReport.Base/Utils/HtmlTextRenderer.cs b/FastReport.Base/Utils/HtmlTextRenderer.cs index df71c61e..62b6c86c 100644 --- a/FastReport.Base/Utils/HtmlTextRenderer.cs +++ b/FastReport.Base/Utils/HtmlTextRenderer.cs @@ -439,7 +439,7 @@ internal void Draw() IGraphicsState state = graphics.Save(); //RectangleF rect = new RectangleF(FDisplayRect.Location, SizeF.Add(FDisplayRect.Size, new SizeF(width_dotnet, 0))); //FGraphics.SetClip(rect, CombineMode.Intersect); - + graphics.SetClip(displayRect, CombineMode.Intersect); // reset alignment //StringAlignment saveAlign = FFormat.Alignment; //StringAlignment saveLineAlign = FFormat.LineAlignment; @@ -695,7 +695,7 @@ private void SplitToParagraphs(string text) Stack elements = new Stack(); SimpleFastReportHtmlReader reader = new SimpleFastReportHtmlReader(this.text); List currentWord = new List(); - float width = paragraphFormat.SkipFirstLineIndent ? 0 : paragraphFormat.FirstLineIndent; + float width = paragraphFormat.SkipFirstLineIndent ? 0 : GetStartPosition(true); Paragraph paragraph = new Paragraph(this); int charIndex = 0; int tabIndex = 0; @@ -763,7 +763,6 @@ private void SplitToParagraphs(string text) word = new Word(this, line, WordType.Tab); - Run tabRun = new RunText(this, word, style, new List(new CharWithIndex[] { reader.Character }), width, charIndex); word.Runs.Add(tabRun); float width2 = GetTabPosition(width); @@ -785,6 +784,13 @@ private void SplitToParagraphs(string text) width2 = GetTabPosition(width, tabIndex); } } + // decrease by (DrawUtils.ScreenDpi / 96f) repeats the work of the Word, if the next tab position is a pixel further than the left indent, + // then the tab stop occurs in the tab position, otherwise the stop will be in the place of the left indentation + if (width < -paragraphFormat.FirstLineIndent && width2 - (DrawUtils.ScreenDpi / 96f) > -paragraphFormat.FirstLineIndent) + { + width2 = -paragraphFormat.FirstLineIndent; + } + tabIndex++; line.Words.Add(word); tabRun.Width = width2 - width; @@ -819,7 +825,7 @@ private void SplitToParagraphs(string text) //word.Runs.Add(runText); line = new Line(this, paragraph, charIndex); word = null; - width = 0; + width = GetStartPosition(); currentWord.Clear(); paragraph.Lines.Add(line); break; @@ -850,7 +856,7 @@ private void SplitToParagraphs(string text) paragraphs.Add(paragraph); line = new Line(this, paragraph, charIndex); word = null; - width = paragraphFormat.FirstLineIndent; + width = GetStartPosition(true); paragraph.Lines.Add(line); break; @@ -1119,7 +1125,7 @@ private Line WrapLine(Paragraph paragraph, Line line, int wordCharIndex, float a paragraph.Lines.Add(line); newWord = new Word(newWord.Renderer, line, newWord.Type); line.Words.Add(newWord); - secondPart.Left = 0; + secondPart.Left = GetStartPosition(); width = secondPart.Width; currentWord = newWord; if (width < availableWidth) @@ -1152,11 +1158,12 @@ private Line WrapLine(Paragraph paragraph, Line line, int wordCharIndex, float a line.Words.RemoveAt(line.Words.Count - 1); Line result = new Line(this, paragraph, wordCharIndex); paragraph.Lines.Add(result); - newWidth = 0; - if (line.Words.Count > 2 && line.Words[line.Words.Count - 2].Type == WordType.Tab) + newWidth = GetStartPosition(); + + if (line.Words.Count > 1 && line.Words[line.Words.Count - 1].Type == WordType.Tab) { - Word tabWord = line.Words[line.Words.Count - 2]; - line.Words.RemoveAt(line.Words.Count - 2); + Word tabWord = line.Words[line.Words.Count - 1]; + line.Words.RemoveAt(line.Words.Count - 1); float width2 = GetTabPosition(newWidth); if (isDifferentTabPositions) { @@ -1167,7 +1174,7 @@ private Line WrapLine(Paragraph paragraph, Line line, int wordCharIndex, float a tabIndex = 1; result.Words.Add(tabWord); tabWord.Line = result; - tabWord.Runs[0].Left = 0; + tabWord.Runs[0].Left = GetStartPosition(); tabWord.Runs[0].Width = width2 - newWidth; newWidth = width2; } @@ -1185,6 +1192,24 @@ private Line WrapLine(Paragraph paragraph, Line line, int wordCharIndex, float a } } + /// + /// Get start position of line. + /// + /// + /// if this parameter is true, the starting position of the line in the new paragraph will be returned + /// + /// + private float GetStartPosition(bool isNewParagraph = false) + { + // if we have a back indent, we take it as zero and shift all the initial positions of the text in the lines by the size of this indent. + if (isNewParagraph && paragraphFormat.FirstLineIndent > 0) + return paragraphFormat.FirstLineIndent; + + if (paragraphFormat.FirstLineIndent < 0 && !isNewParagraph) + return -paragraphFormat.FirstLineIndent; + return 0; + } + #endregion Private Methods #region Public Enums @@ -2236,9 +2261,9 @@ public override void Draw() //#if DEBUG //SizeF size = renderer.graphics.MeasureString(text, font, int.MaxValue, renderer.format); //if (renderer.RightToLeft) - // renderer.graphics.DrawRectangle(Pens.Red, Left - size.Width, Top, size.Width, size.Height); + // renderer.graphics.DrawRectangle(Pens.Red, Left - size.Width, Top, size.Width, size.Height); //else - // renderer.graphics.DrawRectangle(Pens.Red, Left, Top, size.Width, size.Height); + // renderer.graphics.DrawRectangle(Pens.Red, Left, Top, Width, Height); //#endif renderer.graphics.DrawString(text, font, brush, Left, Top, renderer.format); } diff --git a/FastReport.Base/Utils/Validator.cs b/FastReport.Base/Utils/Validator.cs index e6ea0a01..e8063369 100644 --- a/FastReport.Base/Utils/Validator.cs +++ b/FastReport.Base/Utils/Validator.cs @@ -35,7 +35,7 @@ public ValidationError(string name, ErrorLevel level, string message, ReportComp /// public static class Validator { - private static void NormalizeBounds(ref RectangleF bounds) + internal static void NormalizeBounds(ref RectangleF bounds) { if (bounds.Width < 0) {