Skip to content

Commit dc43a8e

Browse files
LodrKumquatyulian-gaponenko
authored andcommitted
Support minMaxWidth counting for span- and special scripts wrapping
DEVSIX-3997
1 parent 2ad0334 commit dc43a8e

File tree

7 files changed

+422
-32
lines changed

7 files changed

+422
-32
lines changed

layout/src/main/java/com/itextpdf/layout/layout/TextLayoutResult.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ public class TextLayoutResult extends MinMaxWidthLayoutResult {
6565

6666
protected boolean lineEndsWithSplitCharacterOrWhiteSpace = false;
6767

68+
protected float leftMinWidth;
69+
70+
protected float rightMinWidth;
71+
6872
/**
6973
* Creates the {@link LayoutResult result of {@link com.itextpdf.layout.renderer.TextRenderer#layout(LayoutContext) layouting}}.
7074
* The {@link LayoutResult#causeOfNothing} will be set as null.
@@ -200,4 +204,54 @@ public TextLayoutResult setLineEndsWithSplitCharacterOrWhiteSpace(boolean lineEn
200204
public boolean isLineEndsWithSplitCharacterOrWhiteSpace() {
201205
return lineEndsWithSplitCharacterOrWhiteSpace;
202206
}
207+
208+
/**
209+
* Sets min width of the leftmost unbreakable part of the TextRenderer#line after layout.
210+
* This value includes left-side additional width, i.e. left margin, border and padding widths.
211+
* In case when entire TextRenderer#line is unbreakable, leftMinWidth also includes right-side additional width.
212+
*
213+
* @param leftMinWidth min width of the leftmost unbreakable part of the TextRenderer#line after layout.
214+
* @return {@link com.itextpdf.layout.layout.TextLayoutResult this layout result} the setting was applied on.
215+
*/
216+
public TextLayoutResult setLeftMinWidth(float leftMinWidth) {
217+
this.leftMinWidth = leftMinWidth;
218+
return this;
219+
}
220+
221+
/**
222+
* Gets min width of the leftmost unbreakable part of the TextRenderer#line after layout.
223+
* This value leftMinWidth includes left-side additional width, i.e. left margin, border and padding widths.
224+
* In case when entire TextRenderer#line is unbreakable, leftMinWidth also includes right-side additional width.
225+
*
226+
* @return min width of the leftmost unbreakable part of the TextRenderer#line after layout.
227+
*/
228+
public float getLeftMinWidth() {
229+
return leftMinWidth;
230+
}
231+
232+
/**
233+
* Sets min width of the rightmost unbreakable part of the TextRenderer#line after layout.
234+
* This value includes right-side additional width, i.e. right margin, border and padding widths.
235+
* In case when entire TextRenderer#line is unbreakable, this value must be -1
236+
* and right-side additional width must be included in leftMinWidth.
237+
*
238+
* @param rightMinWidth min width of the rightmost unbreakable part of the TextRenderer#line after layout.
239+
* @return {@link com.itextpdf.layout.layout.TextLayoutResult this layout result} the setting was applied on.
240+
*/
241+
public TextLayoutResult setRightMinWidth(float rightMinWidth) {
242+
this.rightMinWidth = rightMinWidth;
243+
return this;
244+
}
245+
246+
/**
247+
* Gets min width of the rightmost unbreakable part of the TextRenderer#line after layout.
248+
* This value includes right-side additional width, i.e. right margin, border and padding widths.
249+
* In case when entire TextRenderer#line is unbreakable, this value must be -1
250+
* and right-side additional width must be included in leftMinWidth.
251+
*
252+
* @return min width of the leftmost unbreakable part of the TextRenderer#line after layout.
253+
*/
254+
public float getRightMinWidth() {
255+
return rightMinWidth;
256+
}
203257
}

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

Lines changed: 157 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ && hasChildRendererInHtmlMode()) {
194194
Map<Integer, float[]> textRendererSequenceAscentDescent = new HashMap<>();
195195
float[] ascentDescentTextAscentTextDescentBeforeTextRendererSequence = null;
196196

197+
MinMaxWidthOfTextRendererSequenceHelper minMaxWidthOfTextRendererSequenceHelper = null;
198+
197199
while (childPos < childRenderers.size()) {
198200
IRenderer childRenderer = childRenderers.get(childPos);
199201
LayoutResult childResult = null;
@@ -402,8 +404,10 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
402404
shouldBreakLayouting = textRendererMoveForwardsPostProcessing(moveForwardsSpecialScriptsOverflowX,
403405
moveForwardsTextRenderer, childPos, childRenderer, childResult, wasXOverflowChanged);
404406

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

408412
// it means that we've already increased layout area by MIN_MAX_WIDTH_CORRECTION_EPS
409413
if (childResult instanceof MinMaxWidthLayoutResult && null != childBlockMinMaxWidth) {
@@ -441,6 +445,10 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
441445
textRendererSequenceAscentDescent, childPos, childAscentDescent,
442446
ascentDescentTextAscentTextDescentBeforeTextRendererSequence);
443447

448+
minMaxWidthOfTextRendererSequenceHelper = updateTextRendererSequenceMinMaxWidth(widthHandler, childPos,
449+
minMaxWidthOfTextRendererSequenceHelper, anythingPlaced, textRendererLayoutResults,
450+
specialScriptLayoutResults, lineLayoutContext.getTextIndent());
451+
444452
boolean newLineOccurred = (childResult instanceof TextLayoutResult && ((TextLayoutResult) childResult).isSplitForcedByNewline());
445453
if (!shouldBreakLayouting) {
446454
shouldBreakLayouting = childResult.getStatus() != LayoutResult.FULL || newLineOccurred;
@@ -482,6 +490,10 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
482490
lastFittingChildRendererData.childIndex, specialScriptLayoutResults);
483491
childPos = lastFittingChildRendererData.childIndex;
484492
childResult = lastFittingChildRendererData.childLayoutResult;
493+
minChildWidth = ((MinMaxWidthLayoutResult) childResult).getMinMaxWidth().getMinWidth();
494+
updateMinMaxWidthOfLineRendererAfterTextRendererSequenceProcessing(
495+
noSoftWrap, childPos, childResult, widthHandler,
496+
minMaxWidthOfTextRendererSequenceHelper, specialScriptLayoutResults);
485497
}
486498
} else if (enableSpanWrapping) {
487499
boolean isOverflowFit = wasXOverflowChanged
@@ -509,6 +521,10 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
509521

510522
childPos = lastFittingChildRendererData.childIndex;
511523
childResult = lastFittingChildRendererData.childLayoutResult;
524+
minChildWidth = ((MinMaxWidthLayoutResult) childResult).getMinMaxWidth().getMinWidth();
525+
updateMinMaxWidthOfLineRendererAfterTextRendererSequenceProcessing(
526+
noSoftWrap, childPos, childResult, widthHandler,
527+
minMaxWidthOfTextRendererSequenceHelper, textRendererLayoutResults);
512528
}
513529
}
514530
}
@@ -622,6 +638,27 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
622638
childPos++;
623639
}
624640
}
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+
}
625662
}
626663

627664
if (result == null) {
@@ -1304,22 +1341,46 @@ static boolean isChildFloating(IRenderer childRenderer) {
13041341
&& FloatingHelper.isRendererFloating(childRenderer, kidFloatPropertyVal);
13051342
}
13061343

1307-
static void updateSpecialScriptLayoutResults(Map<Integer, LayoutResult> specialScriptLayoutResults,
1308-
IRenderer childRenderer, int childPos, LayoutResult childResult) {
1344+
void updateSpecialScriptLayoutResults(
1345+
Map<Integer, LayoutResult> specialScriptLayoutResults, IRenderer childRenderer, int childPos,
1346+
LayoutResult childResult, MinMaxWidthOfTextRendererSequenceHelper minMaxWidthOfTextRendererSequenceHelper,
1347+
boolean noSoftWrap, AbstractWidthHandler widthHandler) {
13091348
if ((childRenderer instanceof TextRenderer && ((TextRenderer) childRenderer)
13101349
.textContainsSpecialScriptGlyphs(true))) {
13111350
specialScriptLayoutResults.put(childPos, childResult);
13121351
} else if (!specialScriptLayoutResults.isEmpty() && !isChildFloating(childRenderer)) {
1352+
while (childPos >= 0) {
1353+
if (specialScriptLayoutResults.get(childPos) != null) {
1354+
break;
1355+
} else {
1356+
childPos--;
1357+
}
1358+
}
1359+
childResult = specialScriptLayoutResults.get(childPos);
1360+
updateMinMaxWidthOfLineRendererAfterTextRendererSequenceProcessing(noSoftWrap, childPos, childResult,
1361+
widthHandler, minMaxWidthOfTextRendererSequenceHelper, specialScriptLayoutResults);
13131362
specialScriptLayoutResults.clear();
13141363
}
13151364
}
13161365

1317-
static void updateTextRendererLayoutResults(Map<Integer, LayoutResult> textRendererLayoutResults,
1318-
IRenderer childRenderer, int childPos, LayoutResult childResult) {
1366+
void updateTextRendererLayoutResults(
1367+
Map<Integer, LayoutResult> textRendererLayoutResults, IRenderer childRenderer, int childPos,
1368+
LayoutResult childResult, MinMaxWidthOfTextRendererSequenceHelper minMaxWidthOfTextRendererSequenceHelper,
1369+
boolean noSoftWrap, AbstractWidthHandler widthHandler) {
13191370
if (childRenderer instanceof TextRenderer && !((TextRenderer) childRenderer)
13201371
.textContainsSpecialScriptGlyphs(true)) {
13211372
textRendererLayoutResults.put(childPos, childResult);
13221373
} else if (!textRendererLayoutResults.isEmpty() && !isChildFloating(childRenderer)) {
1374+
while (childPos >= 0) {
1375+
if (textRendererLayoutResults.get(childPos) != null) {
1376+
break;
1377+
} else {
1378+
childPos--;
1379+
}
1380+
}
1381+
childResult = textRendererLayoutResults.get(childPos);
1382+
updateMinMaxWidthOfLineRendererAfterTextRendererSequenceProcessing(noSoftWrap, childPos, childResult,
1383+
widthHandler, minMaxWidthOfTextRendererSequenceHelper, textRendererLayoutResults);
13231384
textRendererLayoutResults.clear();
13241385
}
13251386
}
@@ -1551,6 +1612,82 @@ float[] updateTextRendererSequenceAscentDescent(Map<Integer, float[]> textRender
15511612
return preTextSequenceAscentDescent;
15521613
}
15531614

1615+
MinMaxWidthOfTextRendererSequenceHelper updateTextRendererSequenceMinMaxWidth(
1616+
AbstractWidthHandler widthHandler, int childPos,
1617+
MinMaxWidthOfTextRendererSequenceHelper minMaxWidthOfTextRendererSequenceHelper, boolean anythingPlaced,
1618+
Map<Integer, LayoutResult> textRendererLayoutResults,
1619+
Map<Integer, LayoutResult> specialScriptLayoutResults, float textIndent) {
1620+
IRenderer childRenderer = childRenderers.get(childPos);
1621+
if (isChildFloating(childRenderer)) {
1622+
return minMaxWidthOfTextRendererSequenceHelper;
1623+
} else if (childRenderer instanceof TextRenderer) {
1624+
boolean firstTextRendererWithSpecialScripts =
1625+
((TextRenderer) childRenderer).textContainsSpecialScriptGlyphs(true)
1626+
&& specialScriptLayoutResults.size() == 1;
1627+
boolean firstTextRendererWithoutSpecialScripts =
1628+
!((TextRenderer) childRenderer).textContainsSpecialScriptGlyphs(true)
1629+
&& textRendererLayoutResults.size() == 1;
1630+
if (firstTextRendererWithoutSpecialScripts || firstTextRendererWithSpecialScripts) {
1631+
minMaxWidthOfTextRendererSequenceHelper = new MinMaxWidthOfTextRendererSequenceHelper(
1632+
widthHandler.minMaxWidth.getChildrenMinWidth(), textIndent, anythingPlaced);
1633+
}
1634+
return minMaxWidthOfTextRendererSequenceHelper;
1635+
} else {
1636+
return null;
1637+
}
1638+
}
1639+
1640+
void updateMinMaxWidthOfLineRendererAfterTextRendererSequenceProcessing(
1641+
boolean noSoftWrap, int childPos, LayoutResult layoutResult, AbstractWidthHandler widthHandler,
1642+
MinMaxWidthOfTextRendererSequenceHelper minMaxWidthOfTextRendererSequenceHelper,
1643+
Map<Integer, LayoutResult> textRendererLayoutResults) {
1644+
if (noSoftWrap) {
1645+
return;
1646+
}
1647+
TextLayoutResult currLayoutResult = (TextLayoutResult) layoutResult;
1648+
float leftMinWidthCurrRenderer = currLayoutResult.getLeftMinWidth();
1649+
float generalMinWidthCurrRenderer = currLayoutResult.getMinMaxWidth().getMinWidth();
1650+
float widthOfUnbreakableChunkSplitAcrossRenderers = leftMinWidthCurrRenderer;
1651+
float minWidthOfTextRendererSequence = generalMinWidthCurrRenderer;
1652+
1653+
for (int prevRendererIndex = childPos - 1; prevRendererIndex >= 0; prevRendererIndex--) {
1654+
if (textRendererLayoutResults.get(prevRendererIndex) != null) {
1655+
TextLayoutResult prevLayoutResult = (TextLayoutResult) textRendererLayoutResults.get(prevRendererIndex);
1656+
float leftMinWidthPrevRenderer = prevLayoutResult.getLeftMinWidth();
1657+
float generalMinWidthPrevRenderer = prevLayoutResult.getMinMaxWidth().getMinWidth();
1658+
float rightMinWidthPrevRenderer = prevLayoutResult.getRightMinWidth();
1659+
minWidthOfTextRendererSequence = Math.max(minWidthOfTextRendererSequence, generalMinWidthPrevRenderer);
1660+
1661+
if (!prevLayoutResult.isLineEndsWithSplitCharacterOrWhiteSpace()
1662+
&& !currLayoutResult.isLineStartsWithWhiteSpace()) {
1663+
if (rightMinWidthPrevRenderer > -1f) {
1664+
widthOfUnbreakableChunkSplitAcrossRenderers += rightMinWidthPrevRenderer;
1665+
} else {
1666+
widthOfUnbreakableChunkSplitAcrossRenderers += leftMinWidthPrevRenderer;
1667+
}
1668+
minWidthOfTextRendererSequence = Math.max(minWidthOfTextRendererSequence,
1669+
widthOfUnbreakableChunkSplitAcrossRenderers);
1670+
if (rightMinWidthPrevRenderer > -1f) {
1671+
widthOfUnbreakableChunkSplitAcrossRenderers = leftMinWidthPrevRenderer;
1672+
}
1673+
} else {
1674+
widthOfUnbreakableChunkSplitAcrossRenderers = leftMinWidthPrevRenderer;
1675+
}
1676+
currLayoutResult = prevLayoutResult;
1677+
}
1678+
}
1679+
1680+
if (!minMaxWidthOfTextRendererSequenceHelper.anythingPlacedBeforeTextRendererSequence) {
1681+
widthOfUnbreakableChunkSplitAcrossRenderers += minMaxWidthOfTextRendererSequenceHelper.textIndent;
1682+
minWidthOfTextRendererSequence = Math.max(minWidthOfTextRendererSequence,
1683+
widthOfUnbreakableChunkSplitAcrossRenderers);
1684+
}
1685+
1686+
float lineMinWidth = Math.max(minWidthOfTextRendererSequence,
1687+
minMaxWidthOfTextRendererSequenceHelper.minWidthPreSequence);
1688+
widthHandler.minMaxWidth.setChildrenMinWidth(lineMinWidth);
1689+
}
1690+
15541691
static float getCurWidthSpecialScriptsDecrement(int childPos, int newChildPos,
15551692
Map<Integer, LayoutResult> specialScriptLayoutResults) {
15561693
float decrement = 0.0f;
@@ -2084,6 +2221,20 @@ public LastFittingChildRendererData(int childIndex, LayoutResult childLayoutResu
20842221
}
20852222
}
20862223

2224+
static class MinMaxWidthOfTextRendererSequenceHelper {
2225+
public float minWidthPreSequence;
2226+
public float textIndent;
2227+
public boolean anythingPlacedBeforeTextRendererSequence;
2228+
2229+
public MinMaxWidthOfTextRendererSequenceHelper(float minWidthPreSequence, float textIndent,
2230+
boolean anythingPlacedBeforeTextRendererSequence) {
2231+
this.minWidthPreSequence = minWidthPreSequence;
2232+
this.textIndent = textIndent;
2233+
this.anythingPlacedBeforeTextRendererSequence = anythingPlacedBeforeTextRendererSequence;
2234+
}
2235+
2236+
}
2237+
20872238
static enum SpecialScriptsContainingSequenceStatus {
20882239
MOVE_SEQUENCE_CONTAINING_SPECIAL_SCRIPTS_ON_NEXT_LINE,
20892240
MOVE_TO_PREVIOUS_TEXT_RENDERER_CONTAINING_SPECIAL_SCRIPTS,

0 commit comments

Comments
 (0)