Skip to content

Commit bb932d3

Browse files
committed
Support overflow-x for special scripts
DEVSIX-4456
1 parent 032f46e commit bb932d3

File tree

3 files changed

+196
-76
lines changed

3 files changed

+196
-76
lines changed

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

Lines changed: 133 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ This file is part of the iText (R) project.
6565
import com.itextpdf.layout.property.FloatPropertyValue;
6666
import com.itextpdf.layout.property.Leading;
6767
import com.itextpdf.layout.property.OverflowPropertyValue;
68+
import com.itextpdf.layout.property.OverflowWrapPropertyValue;
6869
import com.itextpdf.layout.property.Property;
6970
import com.itextpdf.layout.property.RenderingMode;
7071
import com.itextpdf.layout.property.TabAlignment;
@@ -100,6 +101,9 @@ public class LineRenderer extends AbstractRenderer {
100101

101102
@Override
102103
public LayoutResult layout(LayoutContext layoutContext) {
104+
boolean moveForwardsSpecialScriptsOverflowX = false;
105+
int firstChildToRelayout = -1;
106+
103107
Rectangle layoutBox = layoutContext.getArea().getBBox().clone();
104108
boolean wasParentsHeightClipped = layoutContext.isClippedHeight();
105109
List<Rectangle> floatRendererAreas = layoutContext.getFloatRendererAreas();
@@ -353,20 +357,50 @@ && hasChildRendererInHtmlMode()) {
353357
}
354358
}
355359

360+
361+
boolean shouldBreakLayouting = false;
362+
356363
if (childResult == null) {
357-
if (!wasXOverflowChanged && childPos > 0) {
364+
if (TypographyUtils.isPdfCalligraphAvailable()
365+
&& isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
366+
specialScriptPreLayoutProcessing(childPos);
367+
}
368+
369+
boolean setOverflowFitCausedBySpecialScripts = childRenderer instanceof TextRenderer
370+
&& ((TextRenderer) childRenderer).textContainsSpecialScriptGlyphs(true);
371+
372+
if (!wasXOverflowChanged
373+
&& (childPos > 0 || setOverflowFitCausedBySpecialScripts)
374+
&& !moveForwardsSpecialScriptsOverflowX) {
358375
oldXOverflow = this.<OverflowPropertyValue>getProperty(Property.OVERFLOW_X);
359376
wasXOverflowChanged = true;
360377
setProperty(Property.OVERFLOW_X, OverflowPropertyValue.FIT);
361378
}
362379

363-
if (TypographyUtils.isPdfCalligraphAvailable()
364-
&& isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
365-
specialScriptPreLayoutProcessing(childPos);
380+
if (moveForwardsSpecialScriptsOverflowX) {
381+
int firstPossibleBreakWithinTheRenderer =
382+
((TextRenderer) childRenderer).getSpecialScriptsWordBreakPoints().get(0);
383+
if (firstPossibleBreakWithinTheRenderer != -1) {
384+
((TextRenderer) childRenderer)
385+
.setSpecialScriptFirstNotFittingIndex(firstPossibleBreakWithinTheRenderer);
386+
}
387+
if (wasXOverflowChanged) {
388+
setProperty(Property.OVERFLOW_X, oldXOverflow);
389+
}
366390
}
367391

368392
childResult = childRenderer.layout(new LayoutContext(new LayoutArea(layoutContext.getArea().getPageNumber(), bbox), wasParentsHeightClipped));
369393

394+
if (moveForwardsSpecialScriptsOverflowX) {
395+
if (((TextRenderer) childRenderer).getSpecialScriptFirstNotFittingIndex() > 0) {
396+
shouldBreakLayouting = true;
397+
}
398+
((TextRenderer) childRenderer).setSpecialScriptFirstNotFittingIndex(-1);
399+
if (wasXOverflowChanged) {
400+
setProperty(Property.OVERFLOW_X, OverflowPropertyValue.FIT);
401+
}
402+
}
403+
370404
updateSpecialScriptLayoutResults(specialScriptLayoutResults, childRenderer, childPos, childResult);
371405

372406
// it means that we've already increased layout area by MIN_MAX_WIDTH_CORRECTION_EPS
@@ -425,7 +459,9 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
425459
}
426460

427461
boolean newLineOccurred = (childResult instanceof TextLayoutResult && ((TextLayoutResult) childResult).isSplitForcedByNewline());
428-
boolean shouldBreakLayouting = childResult.getStatus() != LayoutResult.FULL || newLineOccurred;
462+
if (!shouldBreakLayouting) {
463+
shouldBreakLayouting = childResult.getStatus() != LayoutResult.FULL || newLineOccurred;
464+
}
429465

430466
boolean forceOverflowForTextRendererPartialResult = false;
431467

@@ -452,69 +488,80 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
452488
}
453489
}
454490
} else if (shouldBreakLayouting && !newLineOccurred && childRenderers.get(childPos) instanceof TextRenderer
455-
&& ((TextRenderer) childRenderers.get(childPos)).textContainsSpecialScriptGlyphs(true)) {
491+
&& ((TextRenderer) childRenderers.get(childPos)).textContainsSpecialScriptGlyphs(true)
492+
&& !moveForwardsSpecialScriptsOverflowX) {
493+
boolean isOverflowFit = wasXOverflowChanged
494+
? (oldXOverflow == OverflowPropertyValue.FIT)
495+
: isOverflowFit(this.<OverflowPropertyValue>getProperty(Property.OVERFLOW_X));
456496
LastFittingChildRendererData lastFittingChildRendererData =
457-
getIndexAndLayoutResultOfTheLastRendererToRemainOnTheLine
458-
(childPos, specialScriptLayoutResults, wasParentsHeightClipped, floatsOverflowedToNextLine);
459-
460-
curWidth -= getCurWidthSpecialScriptsDecrement(childPos, lastFittingChildRendererData.childIndex,
461-
specialScriptLayoutResults);
497+
getIndexAndLayoutResultOfTheLastRendererToRemainOnTheLine(childPos, specialScriptLayoutResults,
498+
wasParentsHeightClipped, floatsOverflowedToNextLine, isOverflowFit);
462499

463-
childPos = lastFittingChildRendererData.childIndex;
464-
childResult = lastFittingChildRendererData.childLayoutResult;
465-
}
466-
467-
if (!forceOverflowForTextRendererPartialResult) {
468-
maxAscent = Math.max(maxAscent, childAscent);
469-
if (childRenderer instanceof TextRenderer) {
470-
maxTextAscent = Math.max(maxTextAscent, childAscent);
471-
} else if (!isChildFloating) {
472-
maxBlockAscent = Math.max(maxBlockAscent, childAscent);
473-
}
474-
maxDescent = Math.min(maxDescent, childDescent);
475-
if (childRenderer instanceof TextRenderer) {
476-
maxTextDescent = Math.min(maxTextDescent, childDescent);
477-
} else if (!isChildFloating) {
478-
maxBlockDescent = Math.min(maxBlockDescent, childDescent);
500+
if (lastFittingChildRendererData == null) {
501+
moveForwardsSpecialScriptsOverflowX = true;
502+
shouldBreakLayouting = false;
503+
firstChildToRelayout = childPos;
504+
} else {
505+
curWidth -= getCurWidthSpecialScriptsDecrement(childPos, lastFittingChildRendererData.childIndex,
506+
specialScriptLayoutResults);
507+
childPos = lastFittingChildRendererData.childIndex;
508+
childResult = lastFittingChildRendererData.childLayoutResult;
479509
}
480510
}
481-
float maxHeight = maxAscent - maxDescent;
482-
483-
float currChildTextIndent = anythingPlaced ? 0 : lineLayoutContext.getTextIndent();
484-
if (hangingTabStop != null
485-
&& (TabAlignment.LEFT == hangingTabStop.getTabAlignment() || shouldBreakLayouting || childRenderers.size() - 1 == childPos || childRenderers.get(childPos + 1) instanceof TabRenderer)) {
486-
IRenderer tabRenderer = childRenderers.get(lastTabIndex);
487-
List<IRenderer> affectedRenderers = new ArrayList<>();
488-
affectedRenderers.addAll(childRenderers.subList(lastTabIndex + 1, childPos + 1));
489-
float tabWidth = calculateTab(layoutBox, curWidth, hangingTabStop, affectedRenderers, tabRenderer);
490-
491-
tabRenderer.layout(new LayoutContext(new LayoutArea(layoutContext.getArea().getPageNumber(), bbox), wasParentsHeightClipped));
492-
float sumOfAffectedRendererWidths = 0;
493-
for (IRenderer renderer : affectedRenderers) {
494-
renderer.move(tabWidth + sumOfAffectedRendererWidths, 0);
495-
sumOfAffectedRendererWidths += renderer.getOccupiedArea().getBBox().getWidth();
496-
}
497-
if (childResult.getSplitRenderer() != null) {
498-
childResult.getSplitRenderer().move(tabWidth + sumOfAffectedRendererWidths - childResult.getSplitRenderer().getOccupiedArea().getBBox().getWidth(), 0);
511+
512+
if (childPos != firstChildToRelayout) {
513+
if (!forceOverflowForTextRendererPartialResult) {
514+
maxAscent = Math.max(maxAscent, childAscent);
515+
if (childRenderer instanceof TextRenderer) {
516+
maxTextAscent = Math.max(maxTextAscent, childAscent);
517+
} else if (!isChildFloating) {
518+
maxBlockAscent = Math.max(maxBlockAscent, childAscent);
519+
}
520+
maxDescent = Math.min(maxDescent, childDescent);
521+
if (childRenderer instanceof TextRenderer) {
522+
maxTextDescent = Math.min(maxTextDescent, childDescent);
523+
} else if (!isChildFloating) {
524+
maxBlockDescent = Math.min(maxBlockDescent, childDescent);
525+
}
499526
}
500-
float tabAndNextElemWidth = tabWidth + childResult.getOccupiedArea().getBBox().getWidth();
501-
if (hangingTabStop.getTabAlignment() == TabAlignment.RIGHT && curWidth + tabAndNextElemWidth < hangingTabStop.getTabPosition()) {
502-
curWidth = hangingTabStop.getTabPosition();
503-
} else {
504-
curWidth += tabAndNextElemWidth;
527+
float maxHeight = maxAscent - maxDescent;
528+
529+
float currChildTextIndent = anythingPlaced ? 0 : lineLayoutContext.getTextIndent();
530+
if (hangingTabStop != null
531+
&& (TabAlignment.LEFT == hangingTabStop.getTabAlignment() || shouldBreakLayouting || childRenderers.size() - 1 == childPos || childRenderers.get(childPos + 1) instanceof TabRenderer)) {
532+
IRenderer tabRenderer = childRenderers.get(lastTabIndex);
533+
List<IRenderer> affectedRenderers = new ArrayList<>();
534+
affectedRenderers.addAll(childRenderers.subList(lastTabIndex + 1, childPos + 1));
535+
float tabWidth = calculateTab(layoutBox, curWidth, hangingTabStop, affectedRenderers, tabRenderer);
536+
537+
tabRenderer.layout(new LayoutContext(new LayoutArea(layoutContext.getArea().getPageNumber(), bbox), wasParentsHeightClipped));
538+
float sumOfAffectedRendererWidths = 0;
539+
for (IRenderer renderer : affectedRenderers) {
540+
renderer.move(tabWidth + sumOfAffectedRendererWidths, 0);
541+
sumOfAffectedRendererWidths += renderer.getOccupiedArea().getBBox().getWidth();
542+
}
543+
if (childResult.getSplitRenderer() != null) {
544+
childResult.getSplitRenderer().move(tabWidth + sumOfAffectedRendererWidths - childResult.getSplitRenderer().getOccupiedArea().getBBox().getWidth(), 0);
545+
}
546+
float tabAndNextElemWidth = tabWidth + childResult.getOccupiedArea().getBBox().getWidth();
547+
if (hangingTabStop.getTabAlignment() == TabAlignment.RIGHT && curWidth + tabAndNextElemWidth < hangingTabStop.getTabPosition()) {
548+
curWidth = hangingTabStop.getTabPosition();
549+
} else {
550+
curWidth += tabAndNextElemWidth;
551+
}
552+
widthHandler.updateMinChildWidth(minChildWidth + currChildTextIndent);
553+
widthHandler.updateMaxChildWidth(tabWidth + maxChildWidth + currChildTextIndent);
554+
hangingTabStop = null;
555+
} else if (null == hangingTabStop) {
556+
if (childResult.getOccupiedArea() != null && childResult.getOccupiedArea().getBBox() != null) {
557+
curWidth += childResult.getOccupiedArea().getBBox().getWidth();
558+
}
559+
widthHandler.updateMinChildWidth(minChildWidth + currChildTextIndent);
560+
widthHandler.updateMaxChildWidth(maxChildWidth + currChildTextIndent);
505561
}
506-
widthHandler.updateMinChildWidth(minChildWidth + currChildTextIndent);
507-
widthHandler.updateMaxChildWidth(tabWidth + maxChildWidth + currChildTextIndent);
508-
hangingTabStop = null;
509-
} else if (null == hangingTabStop) {
510-
if (childResult.getOccupiedArea() != null && childResult.getOccupiedArea().getBBox() != null) {
511-
curWidth += childResult.getOccupiedArea().getBBox().getWidth();
562+
if (!forceOverflowForTextRendererPartialResult) {
563+
occupiedArea.setBBox(new Rectangle(layoutBox.getX(), layoutBox.getY() + layoutBox.getHeight() - maxHeight, curWidth, maxHeight));
512564
}
513-
widthHandler.updateMinChildWidth(minChildWidth + currChildTextIndent);
514-
widthHandler.updateMaxChildWidth(maxChildWidth + currChildTextIndent);
515-
}
516-
if (!forceOverflowForTextRendererPartialResult) {
517-
occupiedArea.setBBox(new Rectangle(layoutBox.getX(), layoutBox.getY() + layoutBox.getHeight() - maxHeight, curWidth, maxHeight));
518565
}
519566

520567
if (shouldBreakLayouting) {
@@ -575,8 +622,12 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
575622

576623
break;
577624
} else {
578-
anythingPlaced = true;
579-
childPos++;
625+
if (childPos == firstChildToRelayout) {
626+
firstChildToRelayout = -1;
627+
} else {
628+
anythingPlaced = true;
629+
childPos++;
630+
}
580631
}
581632
}
582633

@@ -1378,7 +1429,7 @@ void distributePossibleBreakPointsOverSequentialTextRenderers(
13781429

13791430
LastFittingChildRendererData getIndexAndLayoutResultOfTheLastRendererToRemainOnTheLine
13801431
(int childPos, Map<Integer, LayoutResult> specialScriptLayoutResults, boolean wasParentsHeightClipped,
1381-
List<IRenderer> floatsOverflowedToNextLine) {
1432+
List<IRenderer> floatsOverflowedToNextLine, boolean isOverflowFit) {
13821433
int indexOfRendererContainingLastFullyFittingWord = childPos;
13831434
int splitPosition = 0;
13841435
boolean needToSplitRendererContainingLastFullyFittingWord = false;
@@ -1440,14 +1491,25 @@ && isChildFloating(childRenderers.get(analyzedTextRendererIndex - 1))) {
14401491
SpecialScriptsContainingSequenceStatus status =
14411492
getSpecialScriptsContainingSequenceStatus(analyzedTextRendererIndex);
14421493

1443-
// possible breaks haven't been found, can't move back
1494+
// possible breaks haven't been found, can't move back:
14441495
// forced split on the latter renderer having either Full or Partial result
1496+
// if either OVERFLOW_X is FIT or OVERFLOW_WRAP is either ANYWHERE or BREAK_WORD,
1497+
// otherwise return null as a flag to move forward across this.childRenderers
1498+
// till the end of the unbreakable word
14451499
if (status == SpecialScriptsContainingSequenceStatus.FORCED_SPLIT) {
1446-
if (childPosLayoutResult.getStatus() != LayoutResult.NOTHING) {
1447-
returnLayoutResult = childPosLayoutResult;
1500+
OverflowWrapPropertyValue overflowWrapPropertyValue =
1501+
childRenderers.get(childPos).<OverflowWrapPropertyValue>getProperty(Property.OVERFLOW_WRAP);
1502+
if (overflowWrapPropertyValue == OverflowWrapPropertyValue.ANYWHERE
1503+
|| overflowWrapPropertyValue == OverflowWrapPropertyValue.BREAK_WORD
1504+
|| isOverflowFit) {
1505+
if (childPosLayoutResult.getStatus() != LayoutResult.NOTHING) {
1506+
returnLayoutResult = childPosLayoutResult;
1507+
}
1508+
indexOfRendererContainingLastFullyFittingWord = childPos;
1509+
break;
1510+
} else {
1511+
return null;
14481512
}
1449-
indexOfRendererContainingLastFullyFittingWord = childPos;
1450-
break;
14511513
}
14521514

14531515
// possible breaks haven't been found, can't move back
@@ -1497,8 +1559,8 @@ void updateFloatsOverflowedToNextLine(List<IRenderer> floatsOverflowedToNextLine
14971559
* This method defines how to proceed with a {@link TextRenderer} within which possible breaks haven't been found.
14981560
* Possible scenarios are:
14991561
* - Preceding renderer is also an instance of {@link TextRenderer} and does contain special scripts:
1500-
* {@link LineRenderer#getIndexAndLayoutResultOfTheLastRendererToRemainOnTheLine(int, Map, boolean, List)} will
1501-
* proceed to analyze the preceding {@link TextRenderer} on the subject of possible breaks;
1562+
* {@link LineRenderer#getIndexAndLayoutResultOfTheLastRendererToRemainOnTheLine(int, Map, boolean, List, boolean)}
1563+
* will proceed to analyze the preceding {@link TextRenderer} on the subject of possible breaks;
15021564
* - Preceding renderer is either an instance of {@link TextRenderer} which does not contain special scripts,
15031565
* or an instance of {@link ImageRenderer} or is an inlineBlock child: in this case the entire subsequence of
15041566
* {@link TextRenderer}-s containing special scripts is to be moved to the next line;

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ public LayoutResult layout(LayoutContext layoutContext) {
376376
nonBreakingHyphenRelatedChunkWidth = 0;
377377
}
378378
}
379-
if (firstCharacterWhichExceedsAllowedWidth == -1) {
379+
if (firstCharacterWhichExceedsAllowedWidth == -1 || !isOverflowFit(overflowX)) {
380380
nonBreakablePartWidthWhichDoesNotExceedAllowedWidth += glyphWidth + xAdvance;
381381
}
382382
nonBreakablePartFullWidth += glyphWidth + xAdvance;
@@ -520,7 +520,7 @@ && findPossibleBreaksSplitPosition(specialScriptsWordBreakPoints,
520520
}
521521

522522
boolean specialScriptWordSplit = textContainsSpecialScriptGlyphs(true)
523-
&& !isSplitForcedByNewLine;
523+
&& !isSplitForcedByNewLine && isOverflowFit(overflowX);
524524
if ((nonBreakablePartFullWidth > layoutBox.getWidth() && !anythingPlaced && !hyphenationApplied)
525525
|| forcePartialSplitOnFirstChar
526526
|| -1 != nonBreakingHyphenRelatedChunkStart
@@ -1245,6 +1245,10 @@ void setSpecialScriptFirstNotFittingIndex(int lastFittingIndex) {
12451245
this.specialScriptFirstNotFittingIndex = lastFittingIndex;
12461246
}
12471247

1248+
int getSpecialScriptFirstNotFittingIndex() {
1249+
return specialScriptFirstNotFittingIndex;
1250+
}
1251+
12481252
@Override
12491253
protected Rectangle getBackgroundArea(Rectangle occupiedAreaWithMargins) {
12501254
float textRise = (float) this.getPropertyAsFloat(Property.TEXT_RISE);

0 commit comments

Comments
 (0)