Skip to content

Commit ad0c281

Browse files
committed
Add support for inline block elements
DEVSIX-1053
1 parent 70ceb3f commit ad0c281

File tree

12 files changed

+172
-13
lines changed

12 files changed

+172
-13
lines changed

io/src/main/java/com/itextpdf/io/LogMessageConstant.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public final class LogMessageConstant {
7676
public static final String IMAGE_HAS_JPXDECODE_FILTER = "Image cannot be inline if it has JPXDecode filter. It will be added as an ImageXObject";
7777
public static final String IMAGE_SIZE_CANNOT_BE_MORE_4KB = "Inline image size cannot be more than 4KB. It will be added as an ImageXObject";
7878
public static final String INCORRECT_PAGEROTATION = "Encounterd a page rotation that was not a multiple of 90°/ (Pi/2) when generating default appearances for form fields";
79+
public static final String INLINE_BLOCK_ELEMENT_WILL_BE_CLIPPED = "Inline block element does not fit into parent element and will be clipped";
7980
public static final String INPUT_STREAM_CONTENT_IS_LOST_ON_PDFSTREAM_SERIALIZATION = "PdfStream contains not null input stream. It's content will be lost in serialized object.";
8081
public static final String INVALID_INDIRECT_REFERENCE = "Invalid indirect reference {0} {1} R";
8182
public static final String INVALID_KEY_VALUE_KEY_0_HAS_NULL_VALUE = "Invalid key value: key {0} has null value.";

layout/src/main/java/com/itextpdf/layout/element/Paragraph.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ public Paragraph add(ILeafElement element) {
106106
return this;
107107
}
108108

109+
public Paragraph add(IBlockElement element) {
110+
childElements.add(element);
111+
return this;
112+
}
113+
109114
/**
110115
* Adds a {@link java.util.List} of layout elements to the Paragraph.
111116
* @param elements the content to be added, any {@link ILeafElement}

layout/src/main/java/com/itextpdf/layout/margincollapse/MarginsCollapseHandler.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,11 @@ This file is part of the iText (R) project.
4646
import com.itextpdf.layout.IPropertyContainer;
4747
import com.itextpdf.layout.property.FloatPropertyValue;
4848
import com.itextpdf.layout.property.Property;
49+
import com.itextpdf.layout.renderer.AbstractRenderer;
4950
import com.itextpdf.layout.renderer.BlockRenderer;
5051
import com.itextpdf.layout.renderer.CellRenderer;
5152
import com.itextpdf.layout.renderer.IRenderer;
53+
import com.itextpdf.layout.renderer.LineRenderer;
5254
import com.itextpdf.layout.renderer.RootRenderer;
5355
import com.itextpdf.layout.renderer.TableRenderer;
5456

@@ -473,7 +475,9 @@ private static boolean marginsCouldBeSelfCollapsing(IRenderer renderer) {
473475
return !(renderer instanceof TableRenderer)
474476
&& !rendererIsFloated(renderer)
475477
&& !hasBottomBorders(renderer) && !hasTopBorders(renderer)
476-
&& !hasBottomPadding(renderer) && !hasTopPadding(renderer) && !hasPositiveHeight(renderer);
478+
&& !hasBottomPadding(renderer) && !hasTopPadding(renderer) && !hasPositiveHeight(renderer)
479+
// inline block
480+
&& !(isBlockElement(renderer) && renderer instanceof AbstractRenderer && ((AbstractRenderer) renderer).getParent() instanceof LineRenderer);
477481
}
478482

479483
private static boolean firstChildMarginAdjoinedToParent(IRenderer parent) {
@@ -486,7 +490,6 @@ private static boolean lastChildMarginAdjoinedToParent(IRenderer parent) {
486490
&& !rendererIsFloated(parent) && !hasBottomBorders(parent) && !hasBottomPadding(parent) && !hasHeightProp(parent);
487491
}
488492

489-
490493
private static boolean isBlockElement(IRenderer renderer) {
491494
return renderer instanceof BlockRenderer || renderer instanceof TableRenderer;
492495
}

layout/src/main/java/com/itextpdf/layout/renderer/AbstractRenderer.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,22 @@ protected Float getFirstYLineRecursively() {
741741
return ((AbstractRenderer) childRenderers.get(0)).getFirstYLineRecursively();
742742
}
743743

744+
protected Float getLastYLineRecursively() {
745+
if (childRenderers.size() == 0) {
746+
return null;
747+
}
748+
for (int i = childRenderers.size() - 1; i >= 0; i--) {
749+
IRenderer child = childRenderers.get(i);
750+
if (child instanceof AbstractRenderer) {
751+
Float lastYLine = ((AbstractRenderer) child).getLastYLineRecursively();
752+
if (lastYLine != null) {
753+
return lastYLine;
754+
}
755+
}
756+
}
757+
return null;
758+
}
759+
744760
/**
745761
* Applies margins of the renderer on the given rectangle
746762
*

layout/src/main/java/com/itextpdf/layout/renderer/LineRenderer.java

Lines changed: 121 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ This file is part of the iText (R) project.
4343
*/
4444
package com.itextpdf.layout.renderer;
4545

46+
import com.itextpdf.io.LogMessageConstant;
4647
import com.itextpdf.io.font.otf.Glyph;
4748
import com.itextpdf.io.font.otf.GlyphLine;
4849
import com.itextpdf.io.util.ArrayUtil;
@@ -62,6 +63,7 @@ This file is part of the iText (R) project.
6263
import com.itextpdf.layout.property.Property;
6364
import com.itextpdf.layout.property.TabAlignment;
6465
import com.itextpdf.layout.property.UnitValue;
66+
import org.slf4j.LoggerFactory;
6567

6668
import java.util.ArrayList;
6769
import java.util.Arrays;
@@ -74,6 +76,10 @@ public class LineRenderer extends AbstractRenderer {
7476

7577
protected float maxAscent;
7678
protected float maxDescent;
79+
private float maxTextAscent;
80+
private float maxTextDescent;
81+
private float maxBlockAscent;
82+
private float maxBlockDescent;
7783

7884
// bidi levels
7985
protected byte[] levels;
@@ -92,6 +98,10 @@ public LineLayoutResult layout(LayoutContext layoutContext) {
9298
float curWidth = 0;
9399
maxAscent = 0;
94100
maxDescent = 0;
101+
maxTextAscent = 0;
102+
maxTextDescent = 0;
103+
maxBlockAscent = -1e20f;
104+
maxBlockDescent = 1e20f;
95105
int childPos = 0;
96106

97107
MinMaxWidth minMaxWidth = new MinMaxWidth(0, layoutBox.getWidth());
@@ -117,7 +127,7 @@ public LineLayoutResult layout(LayoutContext layoutContext) {
117127

118128
while (childPos < childRenderers.size()) {
119129
IRenderer childRenderer = childRenderers.get(childPos);
120-
LayoutResult childResult;
130+
LayoutResult childResult = null;
121131
Rectangle bbox = new Rectangle(layoutBox.getX() + curWidth, layoutBox.getY(), layoutBox.getWidth() - curWidth, layoutBox.getHeight());
122132

123133
if (childRenderer instanceof TextRenderer) {
@@ -241,7 +251,33 @@ public LineLayoutResult layout(LayoutContext layoutContext) {
241251
continue;
242252
}
243253

244-
childResult = childRenderer.layout(new LayoutContext(new LayoutArea(layoutContext.getArea().getPageNumber(), bbox)));
254+
MinMaxWidth childBlockMinMaxWidth = null;
255+
boolean isInlineBlockChild = isInlineBlockChild(childRenderer);
256+
if (!childWidthWasReplaced) {
257+
if (isInlineBlockChild && childRenderer instanceof AbstractRenderer) {
258+
childBlockMinMaxWidth = ((AbstractRenderer)childRenderer).getMinMaxWidth(layoutContext.getArea().getBBox().getWidth());
259+
// TODO fix eps?
260+
float eps = 0.001f;
261+
float childMaxWidth = childBlockMinMaxWidth.getMaxWidth() + eps;
262+
// Decrease the calculated width by margins, paddings and borders so that even for 100% width the content definitely fits
263+
// TODO DEVSIX-1174 fix depending on box-sizing
264+
if (childBlockMinMaxWidth != null) {
265+
if (childMaxWidth > bbox.getWidth() && bbox.getWidth() != layoutContext.getArea().getBBox().getWidth()) {
266+
childResult = new LineLayoutResult(LayoutResult.NOTHING, null, null, childRenderer, childRenderer);
267+
} else {
268+
if (bbox.getWidth() == layoutContext.getArea().getBBox().getWidth() && childBlockMinMaxWidth.getMinWidth() > layoutContext.getArea().getBBox().getWidth()) {
269+
LoggerFactory.getLogger(LineRenderer.class).warn(LogMessageConstant.INLINE_BLOCK_ELEMENT_WILL_BE_CLIPPED);
270+
childRenderer.setProperty(Property.FORCED_PLACEMENT, true);
271+
}
272+
bbox.setWidth(childMaxWidth);
273+
}
274+
}
275+
}
276+
}
277+
278+
if (childResult == null) {
279+
childResult = childRenderer.layout(new LayoutContext(new LayoutArea(layoutContext.getArea().getPageNumber(), bbox)));
280+
}
245281

246282
// Get back child width so that it's not lost
247283
if (childWidthWasReplaced) {
@@ -259,17 +295,44 @@ public LineLayoutResult layout(LayoutContext layoutContext) {
259295
minChildWidth = ((MinMaxWidthLayoutResult) childResult).getNotNullMinMaxWidth(bbox.getWidth()).getMinWidth();
260296
}
261297
maxChildWidth = ((MinMaxWidthLayoutResult) childResult).getNotNullMinMaxWidth(bbox.getWidth()).getMaxWidth();
298+
} else if (childBlockMinMaxWidth != null) {
299+
minChildWidth = childBlockMinMaxWidth.getMinWidth();
300+
maxChildWidth = childBlockMinMaxWidth.getMaxWidth();
262301
}
263302

264303
float childAscent = 0;
265304
float childDescent = 0;
266-
if (childRenderer instanceof ILeafElementRenderer) {
305+
if (childRenderer instanceof ILeafElementRenderer && childResult.getStatus() != LayoutResult.NOTHING) {
267306
childAscent = ((ILeafElementRenderer) childRenderer).getAscent();
268307
childDescent = ((ILeafElementRenderer) childRenderer).getDescent();
308+
} else if (isInlineBlockChild && childResult.getStatus() != LayoutResult.NOTHING) {
309+
if (childRenderer instanceof AbstractRenderer) {
310+
Float yLine = ((AbstractRenderer)childRenderer).getLastYLineRecursively();
311+
if (yLine == null) {
312+
childAscent = childRenderer.getOccupiedArea().getBBox().getHeight();
313+
} else {
314+
childAscent = childRenderer.getOccupiedArea().getBBox().getTop() - (float)yLine;
315+
childDescent = -((float)yLine - childRenderer.getOccupiedArea().getBBox().getBottom());
316+
}
317+
} else {
318+
childAscent = childRenderer.getOccupiedArea().getBBox().getHeight();
319+
}
269320
}
270321

271322
maxAscent = Math.max(maxAscent, childAscent);
323+
// TODO treat images as blocks
324+
if (childRenderer instanceof TextRenderer || childRenderer instanceof ImageRenderer) {
325+
maxTextAscent = Math.max(maxTextAscent, childAscent);
326+
} else if (!isChildFloating) {
327+
maxBlockAscent = Math.max(maxBlockAscent, childAscent);
328+
}
272329
maxDescent = Math.min(maxDescent, childDescent);
330+
// TODO treat images as blocks
331+
if (childRenderer instanceof TextRenderer || childRenderer instanceof ImageRenderer) {
332+
maxTextDescent = Math.min(maxTextDescent, childDescent);
333+
} else if (!isChildFloating) {
334+
maxBlockDescent = Math.min(maxBlockDescent, childDescent);
335+
}
273336
float maxHeight = maxAscent - maxDescent;
274337

275338
boolean newLineOccurred = (childResult instanceof TextLayoutResult && ((TextLayoutResult) childResult).isSplitForcedByNewline());
@@ -302,7 +365,9 @@ public LineLayoutResult layout(LayoutContext layoutContext) {
302365
widthHandler.updateMaxChildWidth(tabWidth + maxChildWidth);
303366
hangingTabStop = null;
304367
} else if (null == hangingTabStop) {
305-
curWidth += childResult.getOccupiedArea().getBBox().getWidth();
368+
if (childResult.getOccupiedArea() != null && childResult.getOccupiedArea().getBBox() != null) {
369+
curWidth += childResult.getOccupiedArea().getBBox().getWidth();
370+
}
306371
widthHandler.updateMinChildWidth(minChildWidth);
307372
widthHandler.updateMaxChildWidth(maxChildWidth);
308373
}
@@ -325,13 +390,23 @@ public LineLayoutResult layout(LayoutContext layoutContext) {
325390
split[1].childRenderers.add(childRenderer);
326391
split[1].childRenderers.addAll(childRenderers.subList(childPos + 1, childRenderers.size()));
327392
} else {
328-
if (childResult.getStatus() == LayoutResult.PARTIAL || childResult.getStatus() == LayoutResult.FULL) {
393+
boolean forcePlacement = Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT));
394+
boolean isInlineBlockAndFirstOnRootArea = isInlineBlockChild && isFirstOnRootArea();
395+
if (childResult.getStatus() == LayoutResult.PARTIAL && (!isInlineBlockChild || forcePlacement || isInlineBlockAndFirstOnRootArea) || childResult.getStatus() == LayoutResult.FULL) {
329396
split[0].addChild(childResult.getSplitRenderer());
330397
anythingPlaced = true;
331398
}
332399

333400
if (null != childResult.getOverflowRenderer()) {
334-
split[1].childRenderers.add(childResult.getOverflowRenderer());
401+
if (isInlineBlockChild && !forcePlacement && !isInlineBlockAndFirstOnRootArea) {
402+
split[1].childRenderers.add(childRenderer);
403+
} else {
404+
if (isInlineBlockChild && childResult.getOverflowRenderer().getChildRenderers().size() == 0) {
405+
LoggerFactory.getLogger(LineRenderer.class).warn(LogMessageConstant.INLINE_BLOCK_ELEMENT_WILL_BE_CLIPPED);
406+
} else {
407+
split[1].childRenderers.add(childResult.getOverflowRenderer());
408+
}
409+
}
335410
}
336411
split[1].childRenderers.addAll(childRenderers.subList(childPos + 1, childRenderers.size()));
337412
}
@@ -540,9 +615,9 @@ public float getYLine() {
540615
public float getLeadingValue(Leading leading) {
541616
switch (leading.getType()) {
542617
case Leading.FIXED:
543-
return leading.getValue();
618+
return Math.max(leading.getValue(), maxBlockAscent - maxBlockDescent);
544619
case Leading.MULTIPLIED:
545-
return occupiedArea.getBBox().getHeight() * leading.getValue();
620+
return getTopLeadingIndent(leading) + getBottomLeadingIndent(leading);
546621
default:
547622
throw new IllegalStateException();
548623
}
@@ -558,6 +633,11 @@ protected Float getFirstYLineRecursively() {
558633
return getYLine();
559634
}
560635

636+
@Override
637+
protected Float getLastYLineRecursively() {
638+
return getYLine();
639+
}
640+
561641
public void justify(float width) {
562642
float ratio = (float) this.getPropertyAsFloat(Property.SPACING_RATIO);
563643
float freeWidth = occupiedArea.getBBox().getX() + width -
@@ -649,6 +729,10 @@ protected LineRenderer[] split() {
649729
splitRenderer.parent = parent;
650730
splitRenderer.maxAscent = maxAscent;
651731
splitRenderer.maxDescent = maxDescent;
732+
splitRenderer.maxTextAscent = maxTextAscent;
733+
splitRenderer.maxTextDescent = maxTextDescent;
734+
splitRenderer.maxBlockAscent = maxBlockAscent;
735+
splitRenderer.maxBlockDescent = maxBlockDescent;
652736
splitRenderer.levels = levels;
653737
splitRenderer.addAllProperties(getOwnProperties());
654738

@@ -669,7 +753,8 @@ protected LineRenderer adjustChildrenYLine() {
669753
float descent = ((ILeafElementRenderer) renderer).getDescent();
670754
renderer.move(0, actualYLine - renderer.getOccupiedArea().getBBox().getBottom() + descent);
671755
} else {
672-
renderer.move(0, occupiedArea.getBBox().getY() - renderer.getOccupiedArea().getBBox().getBottom());
756+
Float yLine = isInlineBlockChild(renderer) && renderer instanceof AbstractRenderer ? ((AbstractRenderer) renderer).getLastYLineRecursively() : null;
757+
renderer.move(0, actualYLine - (yLine == null ? renderer.getOccupiedArea().getBBox().getBottom() : (float)yLine));
673758
}
674759
}
675760
return this;
@@ -717,11 +802,33 @@ protected MinMaxWidth getMinMaxWidth(float availableWidth) {
717802
}
718803

719804
float getTopLeadingIndent(Leading leading) {
720-
return (getLeadingValue(leading) - occupiedArea.getBBox().getHeight()) / 2;
805+
switch (leading.getType()) {
806+
case Leading.FIXED:
807+
return (Math.max(leading.getValue(), maxBlockAscent - maxBlockDescent) - occupiedArea.getBBox().getHeight()) / 2;
808+
case Leading.MULTIPLIED:
809+
float fontSize = (float)this.getPropertyAsFloat(Property.FONT_SIZE, 0f);
810+
// TODO contains image to be removed
811+
float textAscent = maxTextAscent == 0 && maxTextDescent == 0 && Math.abs(maxAscent) + Math.abs(maxDescent) != 0 && !containsImage() ? fontSize * 0.8f : maxTextAscent;
812+
float textDescent = maxTextAscent == 0 && maxTextDescent == 0 && Math.abs(maxAscent) + Math.abs(maxDescent) != 0 && !containsImage() ? -fontSize * 0.2f : maxTextDescent;
813+
return Math.max(textAscent + ((textAscent - textDescent) * (leading.getValue() - 1)) / 2, maxBlockAscent) - maxAscent;
814+
default:
815+
throw new IllegalStateException();
816+
}
721817
}
722818

723819
float getBottomLeadingIndent(Leading leading) {
724-
return (getLeadingValue(leading) - occupiedArea.getBBox().getHeight()) / 2;
820+
switch (leading.getType()) {
821+
case Leading.FIXED:
822+
return (Math.max(leading.getValue(), maxBlockAscent - maxBlockDescent) - occupiedArea.getBBox().getHeight()) / 2;
823+
case Leading.MULTIPLIED:
824+
float fontSize = (float)this.getPropertyAsFloat(Property.FONT_SIZE, 0f);
825+
// TODO contains image to be removed
826+
float textAscent = maxTextAscent == 0 && maxTextDescent == 0 && !containsImage() ? fontSize * 0.8f : maxTextAscent;
827+
float textDescent = maxTextAscent == 0 && maxTextDescent == 0 && !containsImage() ? -fontSize * 0.2f : maxTextDescent;
828+
return Math.max(-textDescent + ((textAscent - textDescent) * (leading.getValue() - 1)) / 2, -maxBlockDescent) + maxDescent;
829+
default:
830+
throw new IllegalStateException();
831+
}
725832
}
726833

727834
private LineRenderer[] splitNotFittingFloat(int childPos, LayoutResult childResult) {
@@ -966,6 +1073,9 @@ private void resolveChildrenFonts() {
9661073
}
9671074
}
9681075

1076+
private boolean isInlineBlockChild(IRenderer child) {
1077+
return child instanceof BlockRenderer || child instanceof TableRenderer;
1078+
}
9691079

9701080
static class RendererGlyph {
9711081
public RendererGlyph(Glyph glyph, TextRenderer textRenderer) {

layout/src/main/java/com/itextpdf/layout/renderer/ParagraphRenderer.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,20 @@ protected Float getFirstYLineRecursively() {
494494
return lines.get(0).getFirstYLineRecursively();
495495
}
496496

497+
@Override
498+
protected Float getLastYLineRecursively() {
499+
if (lines == null || lines.size() == 0) {
500+
return null;
501+
}
502+
for (int i = lines.size() - 1; i >= 0; i--) {
503+
Float yLine = lines.get(i).getLastYLineRecursively();
504+
if (yLine != null) {
505+
return yLine;
506+
}
507+
}
508+
return null;
509+
}
510+
497511
@Deprecated
498512
protected ParagraphRenderer createOverflowRenderer() {
499513
return (ParagraphRenderer) getNextRenderer();

layout/src/main/java/com/itextpdf/layout/renderer/TableRenderer.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,11 @@ protected MinMaxWidth getMinMaxWidth(float availableWidth) {
11931193
return new MinMaxWidth(additionalWidth, availableWidth, minWidth, maxColTotalWidth);
11941194
}
11951195

1196+
@Override
1197+
protected Float getLastYLineRecursively() {
1198+
return null;
1199+
}
1200+
11961201
private void initializeTableLayoutBorders() {
11971202
bordersHandler = new CollapsedTableBorders(rows, ((Table) getModelElement()).getNumberOfColumns(), getBorders());
11981203
bordersHandler.initializeBorders();

layout/src/main/java/com/itextpdf/layout/renderer/TextRenderer.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,11 @@ protected Float getFirstYLineRecursively() {
10021002
return getYLine();
10031003
}
10041004

1005+
@Override
1006+
protected Float getLastYLineRecursively() {
1007+
return getYLine();
1008+
}
1009+
10051010
/**
10061011
* Returns the length of the {@link com.itextpdf.layout.renderer.TextRenderer#line line} which is the result of the layout call.
10071012
*
Binary file not shown.

0 commit comments

Comments
 (0)