Skip to content

Commit 26f2925

Browse files
committed
Move bidirectional reordering from text level to line level
When multiple text instances are added to a paragraph, the correct way is to apply reordering to a line as relative order of text blocks might be affected by the reordering DEVSIX-461
1 parent 167bf3e commit 26f2925

File tree

4 files changed

+307
-146
lines changed

4 files changed

+307
-146
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ public AbstractRenderer(IPropertyContainer modelElement) {
5959
this.modelElement = modelElement;
6060
}
6161

62+
protected AbstractRenderer(AbstractRenderer other) {
63+
this.childRenderers = other.childRenderers;
64+
this.positionedRenderers = other.positionedRenderers;
65+
this.modelElement = other.modelElement;
66+
this.flushed = other.flushed;
67+
this.occupiedArea = other.occupiedArea.clone();
68+
this.parent = other.parent;
69+
this.properties = other.properties;
70+
this.isLastRendererForModelElement = other.isLastRendererForModelElement;
71+
}
72+
6273
@Override
6374
public void addChild(IRenderer renderer) {
6475
// https://www.webkit.org/blog/116/webcore-rendering-iii-layout-basics

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

Lines changed: 114 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.itextpdf.layout.renderer;
22

3+
import com.itextpdf.io.font.otf.Glyph;
4+
import com.itextpdf.io.font.otf.GlyphLine;
5+
import com.itextpdf.io.util.Utilities;
36
import com.itextpdf.kernel.geom.Rectangle;
47
import com.itextpdf.layout.Property;
58
import com.itextpdf.layout.element.TabStop;
@@ -11,6 +14,7 @@
1114

1215
import java.util.ArrayList;
1316
import java.util.Iterator;
17+
import java.util.List;
1418
import java.util.Map;
1519
import java.util.NavigableMap;
1620

@@ -19,6 +23,8 @@ public class LineRenderer extends AbstractRenderer {
1923
protected float maxAscent;
2024
protected float maxDescent;
2125

26+
protected byte[] levels;
27+
2228
@Override
2329
public LineLayoutResult layout(LayoutContext layoutContext) {
2430
Rectangle layoutBox = layoutContext.getArea().getBBox().clone();
@@ -29,9 +35,34 @@ public LineLayoutResult layout(LayoutContext layoutContext) {
2935
maxDescent = 0;
3036
int childPos = 0;
3137

38+
Property.BaseDirection baseDirection = getProperty(Property.BASE_DIRECTION);
39+
if (levels == null && baseDirection != Property.BaseDirection.NO_BIDI) {
40+
for (IRenderer renderer : childRenderers) {
41+
if (renderer instanceof TextRenderer) {
42+
((TextRenderer) renderer).applyOtf();
43+
}
44+
}
45+
List<Integer> unicodeIdsLst = new ArrayList<>();
46+
for (IRenderer child : childRenderers) {
47+
if (child instanceof TextRenderer) {
48+
GlyphLine text = ((TextRenderer) child).getText();
49+
for (int i = text.start; i < text.end; i++) {
50+
assert text.glyphs.get(i).getChars().length > 0;
51+
// we assume all the chars will have the same bidi group
52+
// we also assume pairing symbols won't get merged with other ones
53+
int unicode = text.glyphs.get(i).getChars()[0];
54+
unicodeIdsLst.add(unicode);
55+
}
56+
}
57+
}
58+
levels = TypographyUtils.getBidiLevels(baseDirection, Utilities.toArray(unicodeIdsLst));
59+
}
60+
3261
boolean anythingPlaced = false;
3362
TabStop nextTabStop = null;
3463

64+
LineLayoutResult result = null;
65+
3566
while (childPos < childRenderers.size()) {
3667
IRenderer childRenderer = childRenderers.get(childPos);
3768
LayoutResult childResult;
@@ -125,23 +156,86 @@ public LineLayoutResult layout(LayoutContext layoutContext) {
125156
split[1].childRenderers.addAll(childRenderers.subList(childPos + 1, childRenderers.size()));
126157
}
127158

128-
split[0].adjustChildrenYLine().trimLast();
129-
LineLayoutResult result = new LineLayoutResult(anythingPlaced ? LayoutResult.PARTIAL : LayoutResult.NOTHING, occupiedArea, split[0], split[1]);
159+
result = new LineLayoutResult(anythingPlaced ? LayoutResult.PARTIAL : LayoutResult.NOTHING, occupiedArea, split[0], split[1]);
130160
if (childResult.getStatus() == LayoutResult.PARTIAL && childResult instanceof TextLayoutResult && ((TextLayoutResult) childResult).isSplitForcedByNewline())
131161
result.setSplitForcedByNewline(true);
132-
return result;
162+
break;
133163
} else {
134164
anythingPlaced = true;
135165
childPos++;
136166
}
137167
}
138168

169+
if (result == null) {
170+
if (anythingPlaced) {
171+
result = new LineLayoutResult(LayoutResult.FULL, occupiedArea, null, null);
172+
} else {
173+
result = new LineLayoutResult(LayoutResult.NOTHING, occupiedArea, null, this);
174+
}
175+
}
176+
177+
// Consider for now that all the children have the same font, and that after reordering text pieces
178+
// can be reordered, but cannot be split.
179+
if (baseDirection != Property.BaseDirection.NO_BIDI) {
180+
List<IRenderer> children = null;
181+
if (result.getStatus() == LayoutResult.PARTIAL) {
182+
children = result.getSplitRenderer().getChildRenderers();
183+
} else if (result.getStatus() == LayoutResult.FULL) {
184+
children = getChildRenderers();
185+
}
186+
187+
if (children != null) {
188+
List<RendererGlyph> lineGlyphs = new ArrayList<>();
189+
for (IRenderer child : children) {
190+
if (child instanceof TextRenderer) {
191+
GlyphLine childLine = ((TextRenderer) child).line;
192+
for (int i = childLine.start; i < childLine.end; i++) {
193+
lineGlyphs.add(new RendererGlyph(childLine.get(i), (TextRenderer) child));
194+
}
195+
}
196+
}
197+
byte[] lineLevels = new byte[lineGlyphs.size()];
198+
System.arraycopy(levels, 0, lineLevels, 0, lineGlyphs.size());
199+
List<RendererGlyph> reorderedLine = TypographyUtils.reoderLine(lineGlyphs, lineLevels, levels);
200+
201+
if (reorderedLine != null) {
202+
children.clear();
203+
int pos = 0;
204+
while (pos < reorderedLine.size()) {
205+
IRenderer renderer = reorderedLine.get(pos).renderer;
206+
children.add(new TextRenderer((TextRenderer) renderer));
207+
((TextRenderer) children.get(children.size() - 1)).line = new GlyphLine(((TextRenderer) children.get(children.size() - 1)).line);
208+
GlyphLine gl = ((TextRenderer) children.get(children.size() - 1)).line;
209+
gl.end = gl.start;
210+
while (pos < reorderedLine.size() && reorderedLine.get(pos).renderer == renderer) {
211+
gl.set(gl.end, reorderedLine.get(pos).glyph);
212+
gl.end++;
213+
pos++;
214+
}
215+
}
216+
217+
float currentXPos = layoutContext.getArea().getBBox().getLeft();
218+
for (IRenderer child : children) {
219+
float currentWidth = ((TextRenderer) child).calculateLineWidth();
220+
((TextRenderer) child).occupiedArea.getBBox().setX(currentXPos).setWidth(currentWidth);
221+
currentXPos += currentWidth;
222+
}
223+
}
224+
225+
if (result.getStatus() == LayoutResult.PARTIAL) {
226+
LineRenderer overflow = (LineRenderer) result.getOverflowRenderer();
227+
overflow.levels = new byte[levels.length - lineLevels.length];
228+
System.arraycopy(levels, lineLevels.length, overflow.levels, 0, overflow.levels.length);
229+
}
230+
}
231+
}
232+
139233
if (anythingPlaced) {
140-
adjustChildrenYLine().trimLast();
141-
return new LineLayoutResult(LayoutResult.FULL, occupiedArea, null, null);
142-
} else {
143-
return new LineLayoutResult(LayoutResult.NOTHING, occupiedArea, null, this);
234+
LineRenderer processed = result.getStatus() == LayoutResult.FULL ? this : (LineRenderer) result.getSplitRenderer();
235+
processed.adjustChildrenYLine().trimLast();
144236
}
237+
238+
return result;
145239
}
146240

147241
public float getMaxAscent() {
@@ -268,9 +362,11 @@ protected LineRenderer[] split() {
268362
splitRenderer.parent = parent;
269363
splitRenderer.maxAscent = maxAscent;
270364
splitRenderer.maxDescent = maxDescent;
365+
splitRenderer.levels = levels;
271366

272367
LineRenderer overflowRenderer = createOverflowRenderer();
273368
overflowRenderer.parent = parent;
369+
overflowRenderer.levels = levels;
274370

275371
return new LineRenderer[] {splitRenderer, overflowRenderer};
276372
}
@@ -355,7 +451,6 @@ private TabStop calculateTab(IRenderer childRenderer, float curWidth, float line
355451
* Returns resulting width of the tab.
356452
*/
357453
private float calculateTab(Rectangle layoutBox, float curWidth, TabStop tabStop, IRenderer nextElementRenderer, LayoutResult nextElementResult, IRenderer tabRenderer) {
358-
359454
float childWidth = 0;
360455
if (nextElementRenderer != null)
361456
childWidth = nextElementRenderer.getOccupiedArea().getBBox().getWidth();
@@ -394,4 +489,15 @@ private void processDefaultTab(IRenderer tabRenderer, float curWidth, float line
394489
tabRenderer.setProperty(Property.WIDTH, Property.UnitValue.createPointValue(tabWidth));
395490
tabRenderer.setProperty(Property.HEIGHT, maxAscent - maxDescent);
396491
}
492+
493+
static class RendererGlyph {
494+
public RendererGlyph(Glyph glyph, TextRenderer textRenderer) {
495+
this.glyph = glyph;
496+
this.renderer = textRenderer;
497+
}
498+
499+
public Glyph glyph;
500+
public TextRenderer renderer;
501+
}
502+
397503
}

0 commit comments

Comments
 (0)