Skip to content

Commit 32930c7

Browse files
committed
FOP-3316 Fix reading order when using the static-region-per-page conf
1 parent 58b4c79 commit 32930c7

7 files changed

Lines changed: 304 additions & 93 deletions

File tree

fop-core/src/main/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ void addAreas(PositionIterator parentIter, LayoutContext layoutContext) {
465465
addHeaderFooterAreas(headerElements, tableLM.getTable().getTableHeader(), painter,
466466
false);
467467
if (!ancestorTreatAsArtifact) {
468-
headerIsBeingRepeated = true;
468+
headerIsBeingRepeated = !tableLM.getFObj().getUserAgent().isStaticRegionsPerPageForAccessibility();
469469
}
470470
layoutContext.setTreatAsArtifact(ancestorTreatAsArtifact);
471471
}
@@ -487,7 +487,8 @@ void addAreas(PositionIterator parentIter, LayoutContext layoutContext) {
487487

488488
if (footerElements != null && !footerElements.isEmpty()) {
489489
boolean ancestorTreatAsArtifact = layoutContext.treatAsArtifact();
490-
layoutContext.setTreatAsArtifact(treatFooterAsArtifact);
490+
layoutContext.setTreatAsArtifact(treatFooterAsArtifact
491+
&& !tableLM.getFObj().getUserAgent().isStaticRegionsPerPageForAccessibility());
491492
//Positions for footers are simply added at the end
492493
addHeaderFooterAreas(footerElements, tableLM.getTable().getTableFooter(), painter, true);
493494
if (lastPos instanceof TableHFPenaltyPosition && !tableLM.getFooterFootnotes().isEmpty()) {

fop-core/src/main/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,6 @@ public PDFStructElem build(StructureHierarchyMember parent, Attributes attribute
355355

356356
private PDFStructElem rootStructureElement;
357357

358-
private boolean staticContent;
359-
360358
void setPdfFactory(PDFFactory pdfFactory) {
361359
this.pdfFactory = pdfFactory;
362360
}
@@ -403,9 +401,6 @@ public void endPageSequence() {
403401
}
404402

405403
public StructureTreeElement startNode(String name, Attributes attributes, StructureTreeElement parent) {
406-
if ("static-content".equals(name)) {
407-
staticContent = true;
408-
}
409404
if (!isPDFA1Safe(name)) {
410405
return null;
411406
}
@@ -416,7 +411,7 @@ public StructureTreeElement startNode(String name, Attributes attributes, Struct
416411
parentElem = parent;
417412
}
418413
StructureTreeElement structElem;
419-
if (staticContent && pdfFactory.getDocument().isStaticRegionsPerPageForAccessibility()) {
414+
if (pdfFactory.getDocument().isStaticRegionsPerPageForAccessibility()) {
420415
structElem = new Factory(name, parentElem, attributes);
421416
} else {
422417
structElem = createStructureElement(
@@ -464,10 +459,6 @@ public PDFStructElem createStructureElement(int pageNumber) {
464459
}
465460

466461
public void endNode(String name) {
467-
if ("static-content".equals(name)) {
468-
staticContent = false;
469-
}
470-
471462
if (isPDFA1Safe(name)) {
472463
ancestors.removeFirst();
473464
}

fop-core/src/test/java/org/apache/fop/render/pdf/PDFStructureTreeTestCase.java

Lines changed: 124 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
import java.io.ByteArrayInputStream;
2222
import java.io.ByteArrayOutputStream;
2323
import java.io.File;
24+
import java.io.FileInputStream;
25+
import java.io.InputStream;
26+
import java.util.Arrays;
2427
import java.util.Collection;
2528
import java.util.List;
2629

@@ -35,12 +38,26 @@
3538
import org.xml.sax.helpers.AttributesImpl;
3639
import static org.junit.Assert.assertEquals;
3740

41+
import org.apache.fop.accessibility.StructureTreeElement;
3842
import org.apache.fop.apps.FOUserAgent;
3943
import org.apache.fop.apps.Fop;
4044
import org.apache.fop.apps.FopFactory;
4145
import org.apache.fop.pdf.PDFLinearizationTestCase;
4246
import org.apache.fop.pdf.PDFStructElem;
4347
import org.apache.fop.pdf.StandardStructureTypes;
48+
import org.apache.fop.pdf.StructureType;
49+
import static org.apache.fop.pdf.StandardStructureTypes.Grouping.DIV;
50+
import static org.apache.fop.pdf.StandardStructureTypes.Grouping.DOCUMENT;
51+
import static org.apache.fop.pdf.StandardStructureTypes.Grouping.PART;
52+
import static org.apache.fop.pdf.StandardStructureTypes.Grouping.SECT;
53+
import static org.apache.fop.pdf.StandardStructureTypes.Paragraphlike.P;
54+
import static org.apache.fop.pdf.StandardStructureTypes.Table.TABLE;
55+
import static org.apache.fop.pdf.StandardStructureTypes.Table.TBODY;
56+
import static org.apache.fop.pdf.StandardStructureTypes.Table.TD;
57+
import static org.apache.fop.pdf.StandardStructureTypes.Table.TFOOT;
58+
import static org.apache.fop.pdf.StandardStructureTypes.Table.TH;
59+
import static org.apache.fop.pdf.StandardStructureTypes.Table.THEAD;
60+
import static org.apache.fop.pdf.StandardStructureTypes.Table.TR;
4461

4562
public class PDFStructureTreeTestCase {
4663
@Test
@@ -77,111 +94,137 @@ public void testStaticRegionPerPage() throws Exception {
7794
}
7895

7996
@Test
80-
public void testTableHeaderDuplicatedIfStaticRegionsPerPageTrue() throws Exception {
81-
List<PDFStructElem> elems = getPDFStructElems("<fo:root xmlns:fo=\"http://www.w3.org/1999/XSL/Format\" "
82-
+ "font-family=\"arial\" font-size=\"16pt\" xml:lang=\"en\">\n"
83-
+ " <fo:layout-master-set>\n"
84-
+ " <fo:simple-page-master master-name=\"A5-Page\" page-width=\"148mm\" page-height=\"210mm\">\n"
85-
+ " <fo:region-body background-color=\"#efefef\" margin=\"10mm\"/>\n"
86-
+ " </fo:simple-page-master>\n"
87-
+ " <fo:page-sequence-master master-name=\"A5\">\n"
88-
+ " <fo:repeatable-page-master-alternatives>\n"
89-
+ " <fo:conditional-page-master-reference master-reference=\"A5-Page\"/>\n"
90-
+ " </fo:repeatable-page-master-alternatives>\n"
91-
+ " </fo:page-sequence-master>\n"
92-
+ " </fo:layout-master-set>\n"
93-
+ " <fo:page-sequence master-reference=\"A5\">\n"
94-
+ " <fo:flow flow-name=\"xsl-region-body\">\n"
95-
+ " <fo:block>\n"
96-
+ " <fo:block padding-bottom=\"160mm\">Table overflow</fo:block>\n"
97-
+ " <fo:table table-layout=\"fixed\" width=\"100%\">\n"
98-
+ " <fo:table-header>\n"
99-
+ " <fo:table-row>\n"
100-
+ " <fo:table-cell number-columns-spanned=\"2\">\n"
101-
+ " <fo:block font-weight=\"bold\" font-style=\"italic\" text-align=\"left\" "
102-
+ "text-align-last=\"center\"> Table Title </fo:block>\n"
103-
+ " </fo:table-cell>\n"
104-
+ " </fo:table-row>\n"
105-
+ " </fo:table-header>\n"
106-
+ " <fo:table-body>\n"
107-
+ " <fo:table-row>\n"
108-
+ " <fo:table-cell>\n"
109-
+ " <fo:block>Row 1 Column A</fo:block>\n"
110-
+ " </fo:table-cell>\n"
111-
+ " <fo:table-cell>\n"
112-
+ " <fo:block>Row 1 Column B</fo:block>\n"
113-
+ " </fo:table-cell>\n"
114-
+ " </fo:table-row>\n"
115-
+ " <fo:table-row>\n"
116-
+ " <fo:table-cell>\n"
117-
+ " <fo:block>Row 2 Column A</fo:block>\n"
118-
+ " </fo:table-cell>\n"
119-
+ " <fo:table-cell>\n"
120-
+ " <fo:block>Row 2 Column B</fo:block>\n"
121-
+ " </fo:table-cell>\n"
122-
+ " </fo:table-row>\n"
123-
+ " <fo:table-row>\n"
124-
+ " <fo:table-cell>\n"
125-
+ " <fo:block>Row 2 Column A</fo:block>\n"
126-
+ " </fo:table-cell>\n"
127-
+ " <fo:table-cell>\n"
128-
+ " <fo:block>Row 2 Column B</fo:block>\n"
129-
+ " </fo:table-cell>\n"
130-
+ " </fo:table-row>\n"
131-
+ " <fo:table-row>\n"
132-
+ " <fo:table-cell>\n"
133-
+ " <fo:block>Row 2 Column A</fo:block>\n"
134-
+ " </fo:table-cell>\n"
135-
+ " <fo:table-cell>\n"
136-
+ " <fo:block>Row 2 Column B</fo:block>\n"
137-
+ " </fo:table-cell>\n"
138-
+ " </fo:table-row>\n"
139-
+ " </fo:table-body>\n"
140-
+ " </fo:table>\n"
141-
+ " </fo:block>\n"
142-
+ " </fo:flow>\n"
143-
+ " </fo:page-sequence>\n"
144-
+ "</fo:root>");
97+
public void testReadingOrder() throws Exception {
98+
checkReadingOrder(Arrays.asList(DOCUMENT, PART, SECT, DIV, P, P, DIV, P, DIV, P, P, DIV, P),
99+
"test/fo/reading_order.fo");
100+
}
145101

102+
@Test
103+
public void testReadingOrderWithTableInHeader() throws Exception {
104+
checkReadingOrder(Arrays.asList(DOCUMENT, PART, SECT, DIV, P, TABLE, THEAD, TR, TH, P, TBODY, TR, TD, P, TFOOT,
105+
TR, TD, P, P, DIV, P, DIV, P, TABLE, THEAD, TR, TH, P, TBODY, TR, TD, P, TFOOT,
106+
TR, TD, P, P, DIV, P),
107+
"test/fo/reading_order_table_in_header.fo");
108+
}
109+
110+
@Test
111+
public void testReadingOrderBlockSpannedOverPage() throws Exception {
112+
checkReadingOrder(Arrays.asList(DOCUMENT, PART, SECT, DIV, P, P, DIV, P, DIV, P, P, DIV, P, DIV, P, P, DIV, P),
113+
"test/fo/reading_order_block_spanned_over_page.fo");
114+
}
115+
116+
@Test
117+
public void testReadingOrderTableInBody() throws Exception {
118+
checkReadingOrder(Arrays.asList(DOCUMENT, PART, SECT, DIV, P, P, TABLE, THEAD, TR, TH, P, TBODY, TR, TD, P, TD,
119+
P, TFOOT, TR, TD, P, DIV, P, DIV, P, P, TABLE, THEAD, TR, TH, P, TBODY, TR, TD, P, TD, P, TFOOT,
120+
TR, TD, P, DIV, P),
121+
"test/fo/reading_order_table_in_body.fo");
122+
}
123+
124+
private void checkReadingOrder(List<StructureType> orderedTypes, String filePath) throws Exception {
125+
List<PDFStructElem> elems = getPDFStructElems(filePath, true);
126+
127+
int index = 0;
128+
for (PDFStructElem elem : elems) {
129+
assertEquals("Reading order must be preserved when static-region-per-page is true",
130+
orderedTypes.get(index), elem.getStructureType());
131+
index++;
132+
}
133+
134+
assertEquals("Must verify all the PDFStructElements", orderedTypes.size(), elems.size());
135+
}
136+
137+
@Test
138+
public void testTableDuplicatedIfStaticRegionsPerPageTrue() throws Exception {
139+
checkTableBodyCount(true, "test/fo/reading_order_table_in_body.fo",
140+
"A table element should only have one respective structure element", 2);
141+
}
142+
143+
@Test
144+
public void testTableBodyNotDuplicatedIfStaticRegionsPerPageFalse() throws Exception {
145+
checkTableBodyCount(false, "test/fo/reading_order_table_in_body.fo",
146+
"A table element should only have one respective structure element", 1);
147+
}
148+
149+
@Test
150+
public void testTableBodyDuplicatedIfInsideStaticContent() throws Exception {
151+
checkTableBodyCount(true, "test/fo/reading_order_table_in_header.fo",
152+
"If the conf is set to true, a table element must be duplicated like any other fo element", 2);
153+
}
154+
155+
private void checkTableBodyCount(boolean staticRegionPerPage, String filePath, String assertionMessage,
156+
int expectedCount) throws Exception {
157+
List<PDFStructElem> elems = getPDFStructElems(filePath, staticRegionPerPage);
158+
159+
int count = countTableElements(elems, TABLE);
160+
assertEquals(assertionMessage, expectedCount, count);
161+
162+
count = countTableElements(elems, StandardStructureTypes.Table.TBODY);
163+
assertEquals(assertionMessage, expectedCount, count);
164+
165+
count = countTableElements(elems, THEAD);
166+
assertEquals(assertionMessage, expectedCount, count);
167+
168+
count = countTableElements(elems, StandardStructureTypes.Table.TFOOT);
169+
assertEquals(assertionMessage, expectedCount, count);
170+
}
171+
172+
private int countTableElements(List<PDFStructElem> elems, StructureType elementType) {
146173
int count = 0;
147174
for (PDFStructElem elem : elems) {
148-
if (elem.getStructureType().equals(StandardStructureTypes.Table.THEAD)) {
175+
if (elem.getStructureType().equals(elementType)) {
149176
count++;
150177
}
151178
}
152179

153-
assertEquals("The static region per page conf must apply to static regions only", 1, count);
180+
return count;
154181
}
155182

156-
private List<PDFStructElem> getPDFStructElems(String fo) throws Exception {
157-
FopFactory fopFactory = getFopFactory();
158-
FOUserAgent userAgent = fopFactory.newFOUserAgent();
159-
foToOutput(fo, fopFactory, userAgent);
183+
private List<PDFStructElem> getPDFStructElems(String foFileName, boolean staticRegionPerPage) throws Exception {
184+
FopFactory fopFactory = getFopFactory(staticRegionPerPage, true);
185+
FOUserAgent userAgent = fopFactory.newFOUserAgent();
186+
foToOutput(new FileInputStream(foFileName), fopFactory, userAgent);
187+
188+
StructureTreeElement block = userAgent.getStructureTreeEventHandler()
189+
.startNode("#PCDATA", new AttributesImpl(), null);
160190

161-
PDFStructElem block = (PDFStructElem) userAgent
162-
.getStructureTreeEventHandler().startNode("block", new AttributesImpl(), null);
191+
PDFStructElem blockElem;
192+
if (block instanceof PDFStructElem) {
193+
blockElem = (PDFStructElem) block;
194+
} else {
195+
blockElem = ((PDFStructureTreeBuilder.Factory) block).createStructureElement(1);
196+
}
163197

164-
return block.getDocument().getStructureTreeElements();
198+
return blockElem.getDocument().getStructureTreeElements();
165199
}
166200

167201
private ByteArrayOutputStream foToOutput(String fo) throws Exception {
168-
FopFactory fopFactory = getFopFactory();
169-
return foToOutput(fo, fopFactory, fopFactory.newFOUserAgent());
202+
FopFactory fopFactory = getFopFactory(true, false);
203+
return foToOutput(new ByteArrayInputStream(fo.getBytes()), fopFactory, fopFactory.newFOUserAgent());
170204
}
171205

172-
private ByteArrayOutputStream foToOutput(String fo, FopFactory fopFactory, FOUserAgent userAgent) throws Exception {
206+
207+
private ByteArrayOutputStream foToOutput(InputStream inputStream, FopFactory fopFactory, FOUserAgent userAgent)
208+
throws Exception {
173209
ByteArrayOutputStream bos = new ByteArrayOutputStream();
174210
Fop fop = fopFactory.newFop("application/pdf", userAgent, bos);
175211
Transformer transformer = TransformerFactory.newInstance().newTransformer();
176-
Source src = new StreamSource(new ByteArrayInputStream(fo.getBytes()));
212+
213+
Source src = new StreamSource(inputStream);
177214
Result res = new SAXResult(fop.getDefaultHandler());
178215
transformer.transform(src, res);
179216
return bos;
180217
}
181218

182-
private FopFactory getFopFactory() throws Exception {
183-
String fopxconf =
184-
"<fop version=\"1.0\"><accessibility static-region-per-page=\"true\">true</accessibility></fop>";
219+
private FopFactory getFopFactory(boolean staticRegionPerPage, boolean useObjectsStreams) throws Exception {
220+
String fopxconf = "<fop version=\"1.0\">"
221+
+ " <accessibility static-region-per-page=\"" + staticRegionPerPage + "\">true</accessibility>"
222+
+ " <renderers>\n"
223+
+ " <renderer mime=\"application/pdf\">\n"
224+
+ " <use-object-streams>" + useObjectsStreams + "</use-object-streams>"
225+
+ " </renderer>\n"
226+
+ " </renderers>\n"
227+
+ "</fop>";
185228
return FopFactory.newInstance(new File(".").toURI(), new ByteArrayInputStream(fopxconf.getBytes()));
186229
}
187230
}

fop/test/fo/reading_order.fo

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
3+
<fo:layout-master-set>
4+
<fo:simple-page-master master-name="Pages" page-height="297mm" page-width="210mm">
5+
<fo:region-body region-name="Body"/>
6+
<fo:region-before region-name="Header" extent="70mm"/>
7+
<fo:region-after region-name="Footer" extent="27mm"/>
8+
</fo:simple-page-master>
9+
</fo:layout-master-set>
10+
<fo:page-sequence format="1" id="th_default_sequence1" master-reference="Pages">
11+
<fo:static-content flow-name="Header">
12+
<fo:block-container height="27mm" width="68mm" top="5mm" left="14.5mm" absolute-position="fixed">
13+
<fo:block margin-top="5mm">
14+
rest header
15+
</fo:block>
16+
</fo:block-container>
17+
</fo:static-content>
18+
<fo:static-content flow-name="Footer">
19+
<fo:block-container height="27mm" width="68mm" top="100mm" left="14.5mm" absolute-position="fixed">
20+
<fo:block margin-top="5mm">
21+
rest footer
22+
</fo:block>
23+
</fo:block-container>
24+
</fo:static-content>
25+
<fo:flow flow-name="Body">
26+
<fo:block padding-bottom="27cm" page-break-after="always">test</fo:block>
27+
<fo:block padding-bottom="27cm" page-break-after="always">test1</fo:block>
28+
</fo:flow>
29+
</fo:page-sequence>
30+
</fo:root>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
3+
<fo:layout-master-set>
4+
<fo:simple-page-master master-name="Pages" page-height="297mm" page-width="210mm">
5+
<fo:region-body region-name="Body"/>
6+
<fo:region-before region-name="Header" extent="70mm"/>
7+
<fo:region-after region-name="Footer" extent="27mm"/>
8+
</fo:simple-page-master>
9+
</fo:layout-master-set>
10+
<fo:page-sequence format="1" id="th_default_sequence1" master-reference="Pages">
11+
<fo:static-content flow-name="Header">
12+
<fo:block-container height="27mm" width="68mm" top="5mm" left="14.5mm" absolute-position="fixed">
13+
<fo:block margin-top="5mm">
14+
rest header
15+
</fo:block>
16+
</fo:block-container>
17+
</fo:static-content>
18+
<fo:static-content flow-name="Footer">
19+
<fo:block-container height="27mm" width="68mm" top="100mm" left="14.5mm" absolute-position="fixed">
20+
<fo:block margin-top="5mm">
21+
rest footer
22+
</fo:block>
23+
</fo:block-container>
24+
</fo:static-content>
25+
<fo:flow flow-name="Body">
26+
<fo:block padding-bottom="27cm">test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test </fo:block>
27+
<fo:block padding-bottom="27cm" page-break-after="always">test1</fo:block>
28+
</fo:flow>
29+
</fo:page-sequence>
30+
</fo:root>

0 commit comments

Comments
 (0)