TextLayout - Inconsistent behavior with Split and ObjectRects

Posted by: Alberto on 3 March 2021, 3:54 am EST

    • Post Options:
    • Link

    Posted 3 March 2021, 3:54 am EST - Updated 29 September 2022, 11:10 pm EST

    Hello,

    I found an inconsistent behavior of the TextLayout, when Split is not used.

    When Split is used then it works as espected. The text is split to the remaining new TextLayout. See Page 1 and Page 4.

    When Split and ObjectRects are not used then the Text is drawed outside the LayoutRectangle (red) and the resulting ContentRectangle (green) is much bigger, till the end of the Page. See Page 2.

    When Split is not used but ObjectsRects is defined (without adding any Object), so it should have no effect at all, then the Text is not drawed ouside the LayoutRectangle but the ContentRectangle is still bigger than the LayoutRectangle. See Page 3.

    What is incorrect in my code?

    using System;
    
    namespace Messerli.LayouterOne.Infrastructure.BasicPdf.Test.BugReport
    {
        /// <summary>
        /// I found an inconsistent behavior of the TextLayout, when Split is not used. 
        /// When Split is used then it works as espected.The text is split to the remaining new TextLayout.See Page 1 and Page 4.
        /// When Split and ObjectRects are not used then the Text is drawed outside the LayoutRectangle(red) and the 
        /// resulting ContentRectangle(green) is much bigger, till the end of the Page.See Page 2.
        /// When Split is not used but ObjectsRects is defined(without adding any Object), so it should have no effect at all, 
        /// then the Text is not drawed ouside the LayoutRectangle but the ContentRectangle is still bigger than the LayoutRectangle. See Page 3.
        /// </summary>
        public class TextLayoutOutsideRectangle
        {
            public const float Resolution = 72f;
            public const float Point = 1f;
            public const float Inch = Point * Resolution;
            public const float CM = Inch / 2.54f;
            public const float MM = CM / 10;
    
            GrapeCity.Documents.Pdf.GcPdfDocument Document;
            GrapeCity.Documents.Text.TextFormat TextFormat1;
            GrapeCity.Documents.Pdf.GcPdfGraphics Graphics;
            GrapeCity.Documents.Pdf.Page Page;
            System.Drawing.SizeF PageSize;
            System.Drawing.RectangleF TextRect;
    
            public static void ShowIssue(Object doc)
            {
                new TextLayoutOutsideRectangle((GrapeCity.Documents.Pdf.GcPdfDocument)doc).DrawPages();
            }
    
            TextLayoutOutsideRectangle(GrapeCity.Documents.Pdf.GcPdfDocument doc) 
            {
                Document = doc;
                TextFormat1 = new GrapeCity.Documents.Text.TextFormat()
                {
                    Font = GrapeCity.Documents.Text.FontCollection.SystemFonts.FindFamilyName("Segoe UI", false, true),
                    FontSize = 48,
                    ForeColor = System.Drawing.Color.Black,
                    UseTypoMetrics = true, // cross platform independent
                    EnableFontHinting = true, // is default true
                    FontSizeInGraphicUnits = false, // typographic units
                };
            }
    
            void AddPage()
            {
                Page = Document.NewPage();
                Page.PaperKind = GrapeCity.Documents.Common.PaperKind.A4;
                Page.Landscape = false;
                PageSize = Page.GetRenderSize(Resolution, Resolution);
                Graphics = Page.Graphics;
                Graphics.Resolution = Resolution;
                TextRect = new System.Drawing.RectangleF(0, 0, PageSize.Width, PageSize.Height);
                TextRect.Inflate(-2.5f * CM, -2.7f * CM);
                Graphics.DrawRectangle(TextRect, new GrapeCity.Documents.Drawing.Pen(System.Drawing.Color.Red, 2f * MM));
            }
    
            void DrawPage(bool useSplit, bool useObjectRect)
            {
                AddPage();
    
                // create TextLayout
                var rect = TextRect;
                var text = new GrapeCity.Documents.Text.TextLayout(Graphics.Resolution)
                {
                    MarginLeft = rect.X,
                    MarginTop = rect.Y,
                    MarginRight = 0,
                    MarginBottom = 0,
                    MaxWidth = rect.Right,
                    MaxHeight = rect.Bottom,
                    EllipsisCharCode = '.',
                    ParagraphAlignment = GrapeCity.Documents.Text.ParagraphAlignment.Near,
                    TextAlignment = GrapeCity.Documents.Text.TextAlignment.Leading,
                    //AlignmentDelayToSplit = useSplit,
                    //LastLineIsEndOfParagraph = true,
                    //HonorLastLineSpacing = true,
                    //AllowOverhangingWords = true,
                    //TrimmingGranularity = GrapeCity.Documents.Text.TrimmingGranularity.Character,
                };
                string lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer sodales iaculis dictum. Nam in porta dolor. Pellentesque neque orci, maximus in accumsan eget, tincidunt nec eros. Aenean vitae nisi vel purus maximus dignissim. Proin sagittis elementum pharetra. Mauris eu tellus pretium, luctus dui eget, pharetra velit. Nunc fermentum tortor ante, eget sodales sapien imperdiet ut.";
                text.Append(string.Format("Page: {0}, Split: {1}, ObjectRect: {2}. ", Page.Index+1, useSplit, useObjectRect) + lorem, TextFormat1);
                text.RecalculateGlyphs();
    
                // Add Optional ObjectRectangles
                if (useObjectRect)
                {
                    // add object list. But without objects. It should have no effect.
                    text.ObjectRects = new System.Collections.Generic.List<GrapeCity.Documents.Text.ObjectRect>();
                }
                // Perform Layout
                text.PerformLayout(false);
                // Split if requested but ignore the resulting text.
                if (useSplit)
                {
                    text.Split(new GrapeCity.Documents.Text.TextSplitOptions(), out GrapeCity.Documents.Text.TextLayout _);
                }
                // Draw TextLayout
                Graphics.DrawTextLayout(text, System.Drawing.PointF.Empty);
                //  Draw Rectangle of the resulting ContentRectangle
                Graphics.DrawRectangle(text.ContentRectangle, new GrapeCity.Documents.Drawing.Pen(System.Drawing.Color.LightGreen, 1f * MM));
            }
    
            void DrawPages()
            {
                DrawPage(true, false);
                DrawPage(false, false);
                DrawPage(false, true);
                DrawPage(true, true);
            }
        }
    }
    
    

    Thank You

    Alberto

  • Posted 3 March 2021, 4:19 am EST

    To test the code:

                var pdf = new GrapeCity.Documents.Pdf.GcPdfDocument();
                TextLayoutOutsideRectangle.ShowIssue(pdf);
                pdf.Save("test.pdf");
                System.Diagnostics.Process.Start("explorer", "test.pdf");
    
    

    Is this behaviour a Bug or is it by Design?

    How can I not use the Split feature to draw a simple text, getting a correct ContentRectangle and avoiding that the text is drawed outside the LayoutRectangle?

  • Posted 3 March 2021, 4:31 am EST

    Using always Split to avoid this issue, has the limitation that ParagraphAlignment is forced to be GrapeCity.Documents.Text.ParagraphAlignment.Near. It is not possible to align the Text Vertically in the Center or at the Bottom (Far or Justified or Distributed). Well this limitation is there also if ObjectRects is used…

  • Posted 3 March 2021, 11:42 pm EST - Updated 29 September 2022, 11:11 pm EST

    Another inconsistent behaviour. If I use Softhypen, Split and ObjectRect then the ContentRectangle is incorrect, it uses the whole LayoutRectangle instead of the drawed area. If ObjectRect is not used then the ContentRectangle is correct.

    Page 1 correct.

    Page 4 incorrect.

    string lorem = "Lorem ip\x00ADsum dolor sit amet, consectetur adipiscing elit. Inte\x00ADger so\u00ADdales iaculis dic\x00ADtum. Nam in porta dolor. Pel\x00ADlentesque neque orci, maximus in accumsan eget, tin\x00ADcidunt nec eros. Aenean vitae nisi vel purus maximus di\x00ADgnis\x00ADsim. Proin sag\x00ADit\x00ADtis elementum pharetra. Mauris eu tellus pretium, luctus dui eget, pharetra velit. Nunc fermentum tortor ante, eget sodales sapien imperdiet ut.";
    

  • Posted 4 March 2021, 10:00 pm EST

    Hello,

    We are discussing this with the developers and will get back to you with the updates soon.

    [Internal Tracking ID: 2834]

    Regards,

    Prabhat Sharma.

  • Posted 8 March 2021, 9:32 pm EST

    Thank you.

    To summarize the only correct behaviour is Page 1, with Split=True, ObjectRect=false. The ContentRectangle equals to the printed area of the text.

    Page 2: Split=false, ObjectRect=false → It prints outside the LayoutRectangle and ContentRectangle is till the end of the page.

    Page 3: Split=false, ObjetRect=true → It prints correctly but ContentRectangle ist wrong, bigger than LayoutRectangle but smaller than the end of the page.

    Page 4: Split=True, ObjectRect=true → It prints correctly but ContentRectangle ist wrong, it equals to LayoutRectangle.

  • Posted 10 March 2021, 9:11 pm EST

    Hi,

    As Per the developers, inconsistencies are between the two pages with Split==false. This is because the text does not fit in the provided bounds. If after TextLayout.PerformLayout TextLayout.ContentHeightFitsInBounds is false (i.e. not all text fits in the provided bounds), your code must call TextLayout.Split, otherwise, the results of the PerformLayout call are basically undefined. This is by design due to performance considerations (the Split parameters may affect the layout of the portion that fits on the 1st page).

    Regards,

    Prabhat Sharma.

  • Posted 30 March 2021, 11:35 pm EST

    Hello,

    I saw only now the answer, didn’t got an e-mail notification.

    I understand. If TextLayout.ContentHeight/WidthFitsInBounds is false then the results of the PerformLayout call is undefined behaviour.

    Then I suppose the correct sequence is this here:

    text.PerformLayout(false);
    if (!text.ContentWidthFitsInBounds || !text.ContentHeightFitsInBounds) {
        text.Split(new GrapeCity.Documents.Text.TextSplitOptions(), out text2);
        text.PerformLayout(false);
    } 
    Graphics.DrawTextLayout(text, System.Drawing.PointF.Empty);
    
    

    If the text doesn’t fit then split is called and then again PerformLayout. Only after that I can call DrawTextLayout else the Text could be written outside the Bounds and the results of PerformLayout is incorrect.

    Is that correct?

    It should be written somewhere in the Documentation.

    In this case there is still an issue if Softhypen is used. With Softhypen active, Split=true and ObjectRect=true, the resulting ContentRect is the whole Bounds instead of the printed area. It is wrong.

    If ObjectRect=false then the resulting ContentRect is the expected correct one.

    See the last two examples Page 1 and Page 4 where both Split=true.

    Thank you

  • Posted 31 March 2021, 7:06 pm EST

    Hello,

    We are passing your comments to the developers and will let you know as soon as we get the update from their end.

    Regards,

    Prabhat Sharma.

  • Posted 27 April 2021, 12:28 am EST

    Hello,

    A month passed. Any new about this issue?

    If in a Text Layout I use Object Rectangles, Soft Hypenation and the Split Function (which is mandatory else by specification the Content Rectangle will be undefined), then the resulting Content Rectangle is not the used area but the whole Layout.

    The Content Rectangle is useless. This is a bug.

    Thank you,

  • Posted 27 April 2021, 6:13 am EST

    Hi Alberto,

    As per the devs, the call:

    text.Split(new GrapeCity.Documents.Text.TextSplitOptions(), out text2);

    creates and uses a TextSplitOptions that is not properly initialized (no width/height). To automatically initialize it, you may call

    text.Split(null, out text2);

    or you need to manually initialize it with reasonable values.

    Regards,

    Prabhat Sharma.

  • Posted 27 April 2021, 7:50 pm EST

    Using a value of null in text.Split(null, out text2) then I get the same result as with the default values new GrapeCity.Documents.Text.TextSplitOptions().

  • Posted 27 April 2021, 7:51 pm EST - Updated 29 September 2022, 11:11 pm EST

    This here is the new code, almost all members of TextSplitOptions are set.

    Page 1: no Object Rectangles: All Ok.

    Page 3: Object Rectangles yes: Content Rectangle of first page uses the whole Layout (WRONG).

    using System;
    
    namespace Messerli.LayouterOne.Pdf.BasicPdf.Test.BugReport
    {
        /// <summary>
        /// I found an inconsistent behavior of the TextLayout, when Object Rect is used with Soft Hypenation. 
        /// In that case the the resulting ContentRectangle(green) on page 3 is bigger than the printed area.
        /// It uses the whole Layout Rectangle.
        /// </summary>
        public class TextLayoutOutsideRectangle
        {
            public const float Resolution = 72f;
            public const float Point = 1f;
            public const float Inch = Point * Resolution;
            public const float CM = Inch / 2.54f;
            public const float MM = CM / 10;
    
            GrapeCity.Documents.Pdf.GcPdfDocument Document;
            GrapeCity.Documents.Text.TextFormat TextFormat1;
            GrapeCity.Documents.Pdf.GcPdfGraphics Graphics;
            GrapeCity.Documents.Pdf.Page Page;
            System.Drawing.SizeF PageSize;
            System.Drawing.RectangleF TextRect;
    
            public static void ShowIssue(Object doc)
            {
                new TextLayoutOutsideRectangle((GrapeCity.Documents.Pdf.GcPdfDocument)doc).DrawPages();
            }
    
            TextLayoutOutsideRectangle(GrapeCity.Documents.Pdf.GcPdfDocument doc) 
            {
                Document = doc;
                TextFormat1 = new GrapeCity.Documents.Text.TextFormat()
                {
                    Font = GrapeCity.Documents.Text.FontCollection.SystemFonts.FindFamilyName("Segoe UI", false, true),
                    FontSize = 48,
                    ForeColor = System.Drawing.Color.Black,
                    UseTypoMetrics = true, // cross platform independent
                    EnableFontHinting = true, // is default true
                    FontSizeInGraphicUnits = false, // typographic units
                };
            }
    
            void AddPage()
            {
                Page = Document.NewPage();
                Page.PaperKind = GrapeCity.Documents.Common.PaperKind.A4;
                Page.Landscape = false;
                PageSize = Page.GetRenderSize(Resolution, Resolution);
                Graphics = Page.Graphics;
                Graphics.Resolution = Resolution;
                TextRect = new System.Drawing.RectangleF(0, 0, PageSize.Width, PageSize.Height);
                TextRect.Inflate(-2.5f * CM, -2.7f * CM);
                Graphics.DrawRectangle(TextRect, new GrapeCity.Documents.Drawing.Pen(System.Drawing.Color.Red, 2f * MM));
            }
    
            void DrawPage(bool useObjectRect)
            {
                AddPage();
    
                // create TextLayout
                var rect = TextRect;
                GrapeCity.Documents.Text.TextLayout text2 = null;
                var text = new GrapeCity.Documents.Text.TextLayout(Graphics.Resolution)
                {
                    MarginLeft = rect.X,
                    MarginTop = rect.Y,
                    MarginRight = 0,
                    MarginBottom = 0,
                    MaxWidth = rect.Right,
                    MaxHeight = rect.Bottom,
                    ParagraphAlignment = GrapeCity.Documents.Text.ParagraphAlignment.Near,
                    TextAlignment = GrapeCity.Documents.Text.TextAlignment.Leading,
                    //AlignmentDelayToSplit = useSplit,
                    //LastLineIsEndOfParagraph = true,
                    //HonorLastLineSpacing = true,
                    //AllowOverhangingWords = true,
                    TrimmingGranularity = GrapeCity.Documents.Text.TrimmingGranularity.Word,
                    EllipsisCharCode = '.',
                    WrapMode = GrapeCity.Documents.Text.WrapMode.WordWrap,
                    //SoftHyphenReplacementCharCode = 0x2D,
                };
                string lorem = "Lorem ip\xADsum dolor sit amet, consectetur adipiscing elit. Inte\xADger so\u00ADdales iaculis dic\xADtum. Nam in porta dolor. Pel\xADlentesque neque orci, maximus in accumsan eget, tin\x00ADcidunt nec eros. Aenean vitae nisi vel purus maximus di\x00ADgnis\xADsim. Proin sag\xADit\xADtis elementum pharetra. Mauris eu tellus pretium, luctus dui eget, pharetra velit. Nunc fermentum tortor ante, eget sodales sapien imperdiet ut.";
                text.Append(string.Format("Page: {0}, ObjectRect: {1}.\n", Page.Index+1, useObjectRect) + lorem, TextFormat1);
                text.RecalculateGlyphs();
    
                // Add Optional ObjectRectangles
                if (useObjectRect)
                {
                    // add object list. But without objects. It should have no effect.
                    text.ObjectRects = new System.Collections.Generic.List<GrapeCity.Documents.Text.ObjectRect>();
                }
                // Perform Layout
                text.PerformLayout(false);
                // Split if requested
                if (!text.ContentWidthFitsInBounds || !text.ContentHeightFitsInBounds)
                {
                    var textSplitOptions = new GrapeCity.Documents.Text.TextSplitOptions()
                    {
                        AddSpacingAfterLastLine = false,
                        KeepParagraphLinesTogether = false,
                        LineGapBeforeFirstLine = false,
                        MinLinesInFirstParagraph = 1,
                        MinLinesInLastParagraph = 1,
                        RestMarginLeft = rect.X,
                        RestMarginTop = rect.Y,
                        RestMarginRight = 0,
                        RestMarginBottom = 0,
                        RestMaxWidth = rect.Right,
                        RestMaxHeight = rect.Bottom,
                    };
                    if (useObjectRect)
                    {
                        // add object list. But without objects. It should have no effect.
                        textSplitOptions.RestObjectRects = new System.Collections.Generic.List<GrapeCity.Documents.Text.ObjectRect>();
                    }
                    text.Split(textSplitOptions, out text2);
                    text.PerformLayout(false);
                }
    
                // Draw TextLayout
                Graphics.DrawTextLayout(text, System.Drawing.PointF.Empty);
    
                //  Draw Rectangle of the resulting ContentRectangle
                Graphics.DrawRectangle(text.ContentRectangle, new GrapeCity.Documents.Drawing.Pen(System.Drawing.Color.LightGreen, 1f * MM));
    
                // should we draw the next page?
                if (text2 != null)
                {
                    AddPage();
                    text2.ParagraphAlignment = GrapeCity.Documents.Text.ParagraphAlignment.Near;
                    text2.TextAlignment = GrapeCity.Documents.Text.TextAlignment.Leading;
                    text2.PerformLayout(false);
                    Graphics.DrawTextLayout(text2, System.Drawing.PointF.Empty);
                    //  Draw Rectangle of the resulting ContentRectangle
                    Graphics.DrawRectangle(text2.ContentRectangle, new GrapeCity.Documents.Drawing.Pen(System.Drawing.Color.DarkOliveGreen, 1f * MM));
                }
            }
    
            void DrawPages()
            {
                // Page 1: no Object Rectangles: ALL OK.
                DrawPage(false);
                // Page 3: Object Rectangles: Content Rectangle uses the whole Layout (WRONG).
                DrawPage(true);
            }
        }
    }
    
    

  • Posted 28 April 2021, 10:40 pm EST

    Hello,

    We have forwarded your comments further to the development team and will let you know as soon as we get the update from their end.

    Regards,

    Prabhat Sharma.

  • Posted 5 May 2021, 4:27 pm EST

    Hello,

    We have created a sample with your given code but it is throwing lots of error so can you please share a stripped-down version of your code showing the issue.

    Regards,

    Prabhat Sharma.

  • Posted 5 May 2021, 4:27 pm EST

  • Posted 5 May 2021, 8:24 pm EST

    Here the working solution. It’s the same code as initially posted. For some reasons when you created the project additional characters where included in each line.

    DemoRect.zip

  • Posted 6 May 2021, 2:00 pm EST

    Hello,

    Thank you for the file. We have forwarded it further for investigation.

    Regards,

    Prabhat Sharma.

  • Posted 20 May 2021, 7:38 pm EST

    Hello Alberto,

    The issue with the TextLayout has been fixed in the latest version of the GcPdf 4.1.0.660, you can update it in your project to resolve this issue:

    https://www.nuget.org/packages/GrapeCity.Documents.Pdf/

    If you have any other query then please let us know.

    Regards,

    Prabhat Sharma.

Need extra support?

Upgrade your support plan and get personal unlimited phone support with our customer engagement team

Learn More

Forum Channels