diff --git a/itext.tests/itext.layout.tests/itext/layout/renderer/ListRendererUnitTest.cs b/itext.tests/itext.layout.tests/itext/layout/renderer/ListRendererUnitTest.cs index d8e29e105..524bbdfe0 100644 --- a/itext.tests/itext.layout.tests/itext/layout/renderer/ListRendererUnitTest.cs +++ b/itext.tests/itext.layout.tests/itext/layout/renderer/ListRendererUnitTest.cs @@ -180,6 +180,40 @@ public virtual void IncorrectChildTypeDoesntThrowTest() { } } + [NUnit.Framework.Test] + public virtual void NestedListDirectChildDoesNotThrowOnLayoutTest() { + using (Document document = CreateDummyDocument()) { + List nestedList = new List(ListNumberingType.ENGLISH_LOWER).Add("nested-1").Add("nested-2"); + List parentList = new List(ListNumberingType.DECIMAL); + parentList.Add(new ListItem("parent-1")); + parentList.Add(nestedList); + parentList.Add(new ListItem("parent-2")); + NUnit.Framework.Assert.DoesNotThrow(() => document.Add(parentList)); + } + } + + [NUnit.Framework.Test] + public virtual void NestedListIndentAppliedToDirectNestedListRendererTest() { + List nestedList = new List(ListNumberingType.ENGLISH_LOWER).SetMarginLeft(7).Add("nested-1"); + List parentList = new List(ListNumberingType.DECIMAL).SetListIndent(15); + parentList.Add(new ListItem("parent-1")); + parentList.Add(nestedList); + parentList.Add(new ListItem("parent-2")); + ListRenderer parentRenderer = (ListRenderer)parentList.CreateRendererSubTree(); + using (Document document = CreateDummyDocument()) { + parentRenderer.SetParent(document.GetRenderer()); + LayoutResult result = parentRenderer.Layout(CreateLayoutContext(400, 400)); + NUnit.Framework.Assert.AreNotEqual(LayoutResult.NOTHING, result.GetStatus()); + } + NUnit.Framework.Assert.AreEqual(3, parentRenderer.GetChildRenderers().Count); + IRenderer nestedRenderer = parentRenderer.GetChildRenderers()[1]; + NUnit.Framework.Assert.IsTrue(nestedRenderer is ListRenderer); + UnitValue nestedMarginLeft = nestedRenderer.GetProperty(Property.MARGIN_LEFT); + NUnit.Framework.Assert.IsNotNull(nestedMarginLeft); + NUnit.Framework.Assert.IsTrue(nestedMarginLeft.IsPointValue()); + NUnit.Framework.Assert.AreEqual(22f, nestedMarginLeft.GetValue(), 0.0001f); + } + private static ListRenderer CreateInsideListRenderer() { List list = new List(); list.SetListSymbol(new iText.Layout.Element.Text("*")); diff --git a/itext/itext.layout/itext/layout/element/List.cs b/itext/itext.layout/itext/layout/element/List.cs index 1ea26b4cd..fe79d22fc 100644 --- a/itext/itext.layout/itext/layout/element/List.cs +++ b/itext/itext.layout/itext/layout/element/List.cs @@ -97,6 +97,30 @@ public virtual iText.Layout.Element.List Add(ListItem listItem) { return this; } + /// + /// Adds a new + /// + /// (nested list) to the bottom of the List. + /// + /// a nested list + /// this list. + public virtual iText.Layout.Element.List Add(iText.Layout.Element.List list) { + childElements.Add(list); + return this; + } + + /// + /// Adds a new + /// + /// to the bottom of the List. + /// + /// a block element (e.g. Paragraph) + /// this list. + public virtual iText.Layout.Element.List Add(IBlockElement element) { + childElements.Add(element); + return this; + } + /// /// Adds a new /// @@ -234,6 +258,24 @@ public virtual iText.Layout.Element.List SetSymbolIndent(float symbolIndent) { return this; } + /// + /// Gets the indent (left margin) applied to nested lists that are direct children of this list. + /// + /// the nested list indent as a float, or null if not set. + public virtual float? GetListIndent() { + return this.GetProperty(Property.LIST_INDENT); + } + + /// + /// Sets the indent (left margin) applied to nested lists that are direct children of this list. + /// + /// the indent offset for nested lists. + /// this list. + public virtual iText.Layout.Element.List SetListIndent(float listIndent) { + SetProperty(Property.LIST_INDENT, listIndent); + return this; + } + /// /// Gets the piece of text that is added after the /// diff --git a/itext/itext.layout/itext/layout/properties/Property.cs b/itext/itext.layout/itext/layout/properties/Property.cs index e0309b5c3..ffaf00136 100644 --- a/itext/itext.layout/itext/layout/properties/Property.cs +++ b/itext/itext.layout/itext/layout/properties/Property.cs @@ -437,6 +437,11 @@ public sealed class Property { private const int MAX_INHERITED_PROPERTY_ID = 165; + /// + /// The indent (left margin) applied to nested lists that are direct children of a parent list. + /// + public const int LIST_INDENT = 166; + static Property() { INHERITED_PROPERTIES = new bool[MAX_INHERITED_PROPERTY_ID + 1]; INHERITED_PROPERTIES[iText.Layout.Properties.Property.APPEARANCE_STREAM_LAYOUT] = true; diff --git a/itext/itext.layout/itext/layout/renderer/ListRenderer.cs b/itext/itext.layout/itext/layout/renderer/ListRenderer.cs index ed6e2eb6e..af683acd4 100644 --- a/itext/itext.layout/itext/layout/renderer/ListRenderer.cs +++ b/itext/itext.layout/itext/layout/renderer/ListRenderer.cs @@ -322,6 +322,11 @@ private LayoutResult CorrectListSplitting(IRenderer splitRenderer, IRenderer ove // Notice that placed item is a son of the first ListItemRenderer (otherwise there would be now // FORCED_PLACEMENT applied) IRenderer firstListItemRenderer = splitRenderer.GetChildRenderers()[0]; + if (!(firstListItemRenderer is ListItemRenderer) && firstListItemRenderer is BlockRenderer) { + // If the first child is not a ListItemRenderer, fall back to default behaviour + return new LayoutResult(null == overflowRenderer ? LayoutResult.FULL : LayoutResult.PARTIAL, occupiedArea, + splitRenderer, overflowRenderer, this); + } iText.Layout.Renderer.ListRenderer newOverflowRenderer = (iText.Layout.Renderer.ListRenderer)CreateOverflowRenderer (LayoutResult.PARTIAL); newOverflowRenderer.DeleteOwnProperty(Property.FORCED_PLACEMENT); @@ -366,6 +371,11 @@ private LayoutResult InitializeListSymbols(LayoutContext layoutContext) { IList symbolRenderers = new List(); int listItemNum = (int)this.GetProperty(Property.LIST_START, 1); foreach (IRenderer renderer in childRenderers) { + if (!(renderer is ListItemRenderer) && (renderer is ParagraphRenderer || renderer is iText.Layout.Renderer.ListRenderer)) { + // Non-ListItem children (e.g. Paragraph, nested List) do not get list symbols + symbolRenderers.Add(null); + continue; + } renderer.SetParent(this); listItemNum = (renderer.GetProperty(Property.LIST_SYMBOL_ORDINAL_VALUE) != null) ? (int)renderer.GetProperty (Property.LIST_SYMBOL_ORDINAL_VALUE) : listItemNum; @@ -406,8 +416,30 @@ private LayoutResult InitializeListSymbols(LayoutContext layoutContext) { } } float? symbolIndent = this.GetPropertyAsFloat(Property.LIST_SYMBOL_INDENT); + float? nestedListIndent = this.GetPropertyAsFloat(Property.LIST_INDENT); listItemNum = 0; foreach (IRenderer childRenderer in childRenderers) { + IRenderer symbolRenderer = symbolRenderers[listItemNum++]; + if (!(childRenderer is ListItemRenderer)) { + // Non-ListItem children (e.g. Paragraph, nested List) do not get list symbols + // Apply indent to nested lists so they are visually offset from the parent list + if (childRenderer is iText.Layout.Renderer.ListRenderer) { + if (nestedListIndent != null) { + bool isRtlNested = BaseDirection.RIGHT_TO_LEFT == childRenderer.GetProperty(Property.BASE_DIRECTION); + int nestedMarginToSet = isRtlNested ? Property.MARGIN_RIGHT : Property.MARGIN_LEFT; + UnitValue existingMargin = childRenderer.GetProperty(nestedMarginToSet, UnitValue.CreatePointValue(0f)); + float nestedCalculatedMargin = existingMargin.IsPointValue() ? existingMargin.GetValue() : 0f; + nestedCalculatedMargin += (float)nestedListIndent; + childRenderer.SetProperty(nestedMarginToSet, UnitValue.CreatePointValue(nestedCalculatedMargin)); + } + continue; + } + else if (childRenderer is ParagraphRenderer) { + // Paragraphs - no list symbol + continue; + } + // Other non-ListItem types (e.g. DivRenderer) will fall through to the throw below + } // Symbol indent's value should be summed with the margin's value bool isRtl = BaseDirection.RIGHT_TO_LEFT == childRenderer.GetProperty(Property.BASE_DIRECTION ); @@ -426,7 +458,6 @@ private LayoutResult InitializeListSymbols(LayoutContext layoutContext) { calculatedMargin += maxSymbolWidth + (float)(symbolIndent != null ? symbolIndent : 0f); } childRenderer.SetProperty(marginToSet, UnitValue.CreatePointValue(calculatedMargin)); - IRenderer symbolRenderer = symbolRenderers[listItemNum++]; if (childRenderer is ListItemRenderer) { ((ListItemRenderer)childRenderer).AddSymbolRenderer(symbolRenderer, maxSymbolWidth); }