Skip to content

Commit 02f34d5

Browse files
Separate text sequence reset logic for robustness
DEVSIX-1438
1 parent 2accef7 commit 02f34d5

File tree

4 files changed

+162
-116
lines changed

4 files changed

+162
-116
lines changed

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

Lines changed: 51 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ This file is part of the iText (R) project.
5252
import com.itextpdf.io.util.TextUtil;
5353
import com.itextpdf.kernel.geom.Rectangle;
5454
import com.itextpdf.layout.element.TabStop;
55-
import com.itextpdf.layout.element.Text;
5655
import com.itextpdf.layout.layout.LayoutArea;
5756
import com.itextpdf.layout.layout.LayoutContext;
5857
import com.itextpdf.layout.layout.LayoutResult;
@@ -75,14 +74,11 @@ This file is part of the iText (R) project.
7574
import java.util.ArrayList;
7675
import java.util.Arrays;
7776
import java.util.HashMap;
78-
import java.util.HashSet;
7977
import java.util.LinkedHashMap;
8078
import java.util.List;
8179
import java.util.Map;
8280
import java.util.NavigableMap;
83-
import java.util.Set;
8481

85-
import com.itextpdf.layout.splitting.ISplitCharacters;
8682
import org.slf4j.Logger;
8783
import org.slf4j.LoggerFactory;
8884

@@ -203,6 +199,15 @@ && hasChildRendererInHtmlMode()) {
203199

204200
RenderingMode childRenderingMode = childRenderer.<RenderingMode>getProperty(Property.RENDERING_MODE);
205201

202+
if (isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)
203+
&& TypographyUtils.isPdfCalligraphAvailable()) {
204+
specialScriptPreLayoutProcessing(childPos);
205+
}
206+
resetTextSequenceIfItEnded(specialScriptLayoutResults, true, childRenderer, childPos,
207+
minMaxWidthOfTextRendererSequenceHelper, noSoftWrap, widthHandler);
208+
resetTextSequenceIfItEnded(textRendererLayoutResults, false, childRenderer, childPos,
209+
minMaxWidthOfTextRendererSequenceHelper, noSoftWrap, widthHandler);
210+
206211
if (childRenderer instanceof TextRenderer) {
207212
// Delete these properties in case of relayout. We might have applied them during justify().
208213
childRenderer.deleteOwnProperty(Property.CHARACTER_SPACING);
@@ -376,11 +381,6 @@ && hasChildRendererInHtmlMode()) {
376381
boolean shouldBreakLayouting = false;
377382

378383
if (childResult == null) {
379-
if (TypographyUtils.isPdfCalligraphAvailable()
380-
&& isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
381-
specialScriptPreLayoutProcessing(childPos);
382-
}
383-
384384
boolean setOverflowFitCausedBySpecialScripts = childRenderer instanceof TextRenderer
385385
&& ((TextRenderer) childRenderer).textContainsSpecialScriptGlyphs(true);
386386

@@ -404,10 +404,8 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
404404
shouldBreakLayouting = textRendererMoveForwardsPostProcessing(moveForwardsSpecialScriptsOverflowX,
405405
moveForwardsTextRenderer, childPos, childRenderer, childResult, wasXOverflowChanged);
406406

407-
updateTextRendererLayoutResults(textRendererLayoutResults, childRenderer, childPos, childResult,
408-
minMaxWidthOfTextRendererSequenceHelper, noSoftWrap, widthHandler);
409-
updateSpecialScriptLayoutResults(specialScriptLayoutResults, childRenderer, childPos, childResult,
410-
minMaxWidthOfTextRendererSequenceHelper, noSoftWrap, widthHandler);
407+
updateTextRendererLayoutResults(textRendererLayoutResults, childRenderer, childPos, childResult);
408+
updateSpecialScriptLayoutResults(specialScriptLayoutResults, childRenderer, childPos, childResult);
411409

412410
// it means that we've already increased layout area by MIN_MAX_WIDTH_CORRECTION_EPS
413411
if (childResult instanceof MinMaxWidthLayoutResult && null != childBlockMinMaxWidth) {
@@ -490,10 +488,11 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
490488
lastFittingChildRendererData.childIndex, specialScriptLayoutResults);
491489
childPos = lastFittingChildRendererData.childIndex;
492490
childResult = lastFittingChildRendererData.childLayoutResult;
493-
minChildWidth = ((MinMaxWidthLayoutResult) childResult).getMinMaxWidth().getMinWidth();
494-
updateMinMaxWidthOfLineRendererAfterTextRendererSequenceProcessing(
495-
noSoftWrap, childPos, childResult, widthHandler,
496-
minMaxWidthOfTextRendererSequenceHelper, specialScriptLayoutResults);
491+
specialScriptLayoutResults.put(childPos, childResult);
492+
493+
MinMaxWidth textSequenceElemminMaxWidth = ((MinMaxWidthLayoutResult) childResult).getMinMaxWidth();
494+
minChildWidth = textSequenceElemminMaxWidth.getMinWidth();
495+
maxChildWidth = textSequenceElemminMaxWidth.getMaxWidth();
497496
}
498497
} else if (enableSpanWrapping) {
499498
boolean isOverflowFit = wasXOverflowChanged
@@ -521,10 +520,11 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
521520

522521
childPos = lastFittingChildRendererData.childIndex;
523522
childResult = lastFittingChildRendererData.childLayoutResult;
524-
minChildWidth = ((MinMaxWidthLayoutResult) childResult).getMinMaxWidth().getMinWidth();
525-
updateMinMaxWidthOfLineRendererAfterTextRendererSequenceProcessing(
526-
noSoftWrap, childPos, childResult, widthHandler,
527-
minMaxWidthOfTextRendererSequenceHelper, textRendererLayoutResults);
523+
textRendererLayoutResults.put(childPos, childResult);
524+
525+
MinMaxWidth textSequenceElemminMaxWidth = ((MinMaxWidthLayoutResult) childResult).getMinMaxWidth();
526+
minChildWidth = textSequenceElemminMaxWidth.getMinWidth();
527+
maxChildWidth = textSequenceElemminMaxWidth.getMaxWidth();
528528
}
529529
}
530530
}
@@ -638,29 +638,13 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
638638
childPos++;
639639
}
640640
}
641-
642-
if (childPos == childRenderers.size()
643-
&& (!specialScriptLayoutResults.isEmpty() || !textRendererLayoutResults.isEmpty())) {
644-
int lastTextRenderer = childPos;
645-
boolean nonSpecialScripts = specialScriptLayoutResults.isEmpty();
646-
while (lastTextRenderer >= 0) {
647-
if (nonSpecialScripts
648-
? textRendererLayoutResults.get(lastTextRenderer) != null
649-
: specialScriptLayoutResults.get(lastTextRenderer) != null) {
650-
break;
651-
} else {
652-
lastTextRenderer--;
653-
}
654-
}
655-
LayoutResult lastTextLayoutResult = nonSpecialScripts
656-
? textRendererLayoutResults.get(lastTextRenderer)
657-
: specialScriptLayoutResults.get(lastTextRenderer);
658-
updateMinMaxWidthOfLineRendererAfterTextRendererSequenceProcessing(noSoftWrap, lastTextRenderer,
659-
lastTextLayoutResult, widthHandler, minMaxWidthOfTextRendererSequenceHelper,
660-
nonSpecialScripts ? textRendererLayoutResults : specialScriptLayoutResults);
661-
}
662641
}
663642

643+
resetTextSequenceIfItEnded(specialScriptLayoutResults, true, null, childPos,
644+
minMaxWidthOfTextRendererSequenceHelper, noSoftWrap, widthHandler);
645+
resetTextSequenceIfItEnded(textRendererLayoutResults, false, null, childPos,
646+
minMaxWidthOfTextRendererSequenceHelper, noSoftWrap, widthHandler);
647+
664648
if (result == null) {
665649
boolean noOverflowedFloats = floatsOverflowedToNextLine.isEmpty() && floatsToNextPageOverflowRenderers.isEmpty();
666650
if ((anythingPlaced || floatsPlaced) && noOverflowedFloats || 0 == childRenderers.size()) {
@@ -1332,7 +1316,8 @@ private BaseDirection applyOtf() {
13321316
static boolean isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(IRenderer childRenderer) {
13331317
return childRenderer instanceof TextRenderer
13341318
&& ((TextRenderer) childRenderer).getSpecialScriptsWordBreakPoints() == null
1335-
&& ((TextRenderer) childRenderer).textContainsSpecialScriptGlyphs(false);
1319+
&& ((TextRenderer) childRenderer).textContainsSpecialScriptGlyphs(false)
1320+
&& !isChildFloating(childRenderer);
13361321
}
13371322

13381323
static boolean isChildFloating(IRenderer childRenderer) {
@@ -1343,45 +1328,42 @@ static boolean isChildFloating(IRenderer childRenderer) {
13431328

13441329
void updateSpecialScriptLayoutResults(
13451330
Map<Integer, LayoutResult> specialScriptLayoutResults, IRenderer childRenderer, int childPos,
1346-
LayoutResult childResult, MinMaxWidthOfTextRendererSequenceHelper minMaxWidthOfTextRendererSequenceHelper,
1347-
boolean noSoftWrap, AbstractWidthHandler widthHandler) {
1331+
LayoutResult childResult) {
13481332
if ((childRenderer instanceof TextRenderer
13491333
&& ((TextRenderer) childRenderer).textContainsSpecialScriptGlyphs(true))) {
13501334
specialScriptLayoutResults.put(childPos, childResult);
1351-
} else
1352-
if (!specialScriptLayoutResults.isEmpty()) {
1353-
while (childPos >= 0) {
1354-
if (specialScriptLayoutResults.get(childPos) != null) {
1355-
break;
1356-
} else {
1357-
childPos--;
1358-
}
1359-
}
1360-
childResult = specialScriptLayoutResults.get(childPos);
1361-
updateMinMaxWidthOfLineRendererAfterTextRendererSequenceProcessing(noSoftWrap, childPos, childResult,
1362-
widthHandler, minMaxWidthOfTextRendererSequenceHelper, specialScriptLayoutResults);
1363-
specialScriptLayoutResults.clear();
13641335
}
13651336
}
13661337

1367-
void updateTextRendererLayoutResults(
1368-
Map<Integer, LayoutResult> textRendererLayoutResults, IRenderer childRenderer, int childPos,
1369-
LayoutResult childResult, MinMaxWidthOfTextRendererSequenceHelper minMaxWidthOfTextRendererSequenceHelper,
1370-
boolean noSoftWrap, AbstractWidthHandler widthHandler) {
1338+
void updateTextRendererLayoutResults(Map<Integer, LayoutResult> textRendererLayoutResults,
1339+
IRenderer childRenderer, int childPos, LayoutResult childResult) {
13711340
if (childRenderer instanceof TextRenderer
13721341
&& !((TextRenderer) childRenderer).textContainsSpecialScriptGlyphs(true)) {
13731342
textRendererLayoutResults.put(childPos, childResult);
1374-
} else if (!textRendererLayoutResults.isEmpty()) {
1375-
while (childPos >= 0) {
1376-
if (textRendererLayoutResults.get(childPos) != null) {
1343+
}
1344+
}
1345+
1346+
void resetTextSequenceIfItEnded(Map<Integer, LayoutResult> textRendererLayoutResults, boolean specialScripts,
1347+
IRenderer childRenderer, int childPos,
1348+
MinMaxWidthOfTextRendererSequenceHelper minMaxWidthOfTextRendererSequenceHelper,
1349+
boolean noSoftWrap, AbstractWidthHandler widthHandler) {
1350+
if (childRenderer instanceof TextRenderer
1351+
&& ((TextRenderer) childRenderer).textContainsSpecialScriptGlyphs(true) == specialScripts
1352+
&& !isChildFloating(childRenderer)) {
1353+
return;
1354+
}
1355+
if (!textRendererLayoutResults.isEmpty()) {
1356+
int lastChildInTextSequence = childPos;
1357+
while (lastChildInTextSequence >= 0) {
1358+
if (textRendererLayoutResults.get(lastChildInTextSequence) != null) {
13771359
break;
13781360
} else {
1379-
childPos--;
1361+
lastChildInTextSequence--;
13801362
}
13811363
}
1382-
childResult = textRendererLayoutResults.get(childPos);
1383-
updateMinMaxWidthOfLineRendererAfterTextRendererSequenceProcessing(noSoftWrap, childPos, childResult,
1384-
widthHandler, minMaxWidthOfTextRendererSequenceHelper, textRendererLayoutResults);
1364+
LayoutResult childResult = textRendererLayoutResults.get(lastChildInTextSequence);
1365+
updateMinMaxWidthOfLineRendererAfterTextRendererSequenceProcessing(noSoftWrap, lastChildInTextSequence,
1366+
childResult, widthHandler, minMaxWidthOfTextRendererSequenceHelper, textRendererLayoutResults);
13851367
textRendererLayoutResults.clear();
13861368
}
13871369
}
@@ -1835,7 +1817,6 @@ LastFittingChildRendererData getIndexAndLayoutResultOfTheLastTextRendererWithNoS
18351817
if (textLayoutResult.isContainsPossibleBreak()
18361818
&& textLayoutResult.getStatus() != LayoutResult.NOTHING) {
18371819
textRenderer.setFirstIndexExceedingAvailableWidth(textRenderer.line.end);
1838-
// todo ? relayout in original bBox rather than occupied on the first layout area
18391820
LayoutArea layoutArea = textRenderer.getOccupiedArea().clone();
18401821
layoutArea.getBBox()
18411822
.increaseHeight(0.0001F)

layout/src/test/java/com/itextpdf/layout/renderer/TextRendererIntegrationTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ This file is part of the iText (R) project.
3434
import com.itextpdf.kernel.utils.CompareTool;
3535
import com.itextpdf.layout.ColumnDocumentRenderer;
3636
import com.itextpdf.layout.Document;
37+
import com.itextpdf.layout.borders.DashedBorder;
3738
import com.itextpdf.layout.borders.SolidBorder;
3839
import com.itextpdf.layout.element.Cell;
3940
import com.itextpdf.layout.element.Div;
@@ -659,6 +660,44 @@ public void minMaxWidthWordSplitAcrossMultipleTextRenderers() throws IOException
659660
Assert.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder));
660661
}
661662

663+
@Test
664+
@LogMessages(messages = @LogMessage(messageTemplate = LogMessageConstant.TABLE_WIDTH_IS_MORE_THAN_EXPECTED_DUE_TO_MIN_WIDTH))
665+
public void minWidthForWordInMultipleTextRenderersFollowedByFloatTest() throws IOException, InterruptedException {
666+
String outFileName = destinationFolder + "minWidthForSpanningWordFollowedByFloat.pdf";
667+
String cmpFileName = sourceFolder + "cmp_minWidthForSpanningWordFollowedByFloat.pdf";
668+
PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFileName));
669+
Document doc = new Document(pdfDocument);
670+
doc.setFontSize(40);
671+
672+
// add elements to the table in narrow parent div, so table width would be completely based on min-width
673+
Div narrowDivWithTable = new Div()
674+
.setBorder(new DashedBorder(ColorConstants.DARK_GRAY, 3))
675+
.setWidth(10);
676+
Table table = new Table(1);
677+
table.setBorder(new SolidBorder(ColorConstants.GREEN, 2));
678+
679+
Div floatingDiv = new Div();
680+
floatingDiv.setProperty(Property.FLOAT, FloatPropertyValue.LEFT);
681+
floatingDiv.setWidth(40).setHeight(20).setBackgroundColor(ColorConstants.LIGHT_GRAY);
682+
Paragraph paragraph = new Paragraph()
683+
.add(new Text("s"))
684+
.add(new Text("i"))
685+
.add(new Text("n"))
686+
.add(new Text("g"))
687+
.add(new Text("l"))
688+
.add(new Text("e"))
689+
.add(floatingDiv)
690+
.setBorder(new SolidBorder(1));
691+
paragraph.setProperty(Property.OVERFLOW_X, OverflowPropertyValue.VISIBLE);
692+
paragraph.setProperty(Property.RENDERING_MODE, RenderingMode.HTML_MODE);
693+
694+
table.addCell(paragraph);
695+
narrowDivWithTable.add(table);
696+
doc.add(narrowDivWithTable);
697+
doc.close();
698+
Assert.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder));
699+
}
700+
662701
@Test
663702
public void overflowWrapBreakWordWithOverflowXTest() throws IOException, InterruptedException {
664703
String outFileName = destinationFolder + "overflowWrapBreakWordWithOverflowXTest.pdf";

0 commit comments

Comments
 (0)