|
21 | 21 | import java.io.ByteArrayInputStream; |
22 | 22 | import java.io.ByteArrayOutputStream; |
23 | 23 | import java.io.File; |
| 24 | +import java.io.FileInputStream; |
| 25 | +import java.io.InputStream; |
| 26 | +import java.util.Arrays; |
24 | 27 | import java.util.Collection; |
25 | 28 | import java.util.List; |
26 | 29 |
|
|
35 | 38 | import org.xml.sax.helpers.AttributesImpl; |
36 | 39 | import static org.junit.Assert.assertEquals; |
37 | 40 |
|
| 41 | +import org.apache.fop.accessibility.StructureTreeElement; |
38 | 42 | import org.apache.fop.apps.FOUserAgent; |
39 | 43 | import org.apache.fop.apps.Fop; |
40 | 44 | import org.apache.fop.apps.FopFactory; |
41 | 45 | import org.apache.fop.pdf.PDFLinearizationTestCase; |
42 | 46 | import org.apache.fop.pdf.PDFStructElem; |
43 | 47 | 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; |
44 | 61 |
|
45 | 62 | public class PDFStructureTreeTestCase { |
46 | 63 | @Test |
@@ -77,111 +94,137 @@ public void testStaticRegionPerPage() throws Exception { |
77 | 94 | } |
78 | 95 |
|
79 | 96 | @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 | + } |
145 | 101 |
|
| 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) { |
146 | 173 | int count = 0; |
147 | 174 | for (PDFStructElem elem : elems) { |
148 | | - if (elem.getStructureType().equals(StandardStructureTypes.Table.THEAD)) { |
| 175 | + if (elem.getStructureType().equals(elementType)) { |
149 | 176 | count++; |
150 | 177 | } |
151 | 178 | } |
152 | 179 |
|
153 | | - assertEquals("The static region per page conf must apply to static regions only", 1, count); |
| 180 | + return count; |
154 | 181 | } |
155 | 182 |
|
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); |
160 | 190 |
|
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 | + } |
163 | 197 |
|
164 | | - return block.getDocument().getStructureTreeElements(); |
| 198 | + return blockElem.getDocument().getStructureTreeElements(); |
165 | 199 | } |
166 | 200 |
|
167 | 201 | 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()); |
170 | 204 | } |
171 | 205 |
|
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 { |
173 | 209 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
174 | 210 | Fop fop = fopFactory.newFop("application/pdf", userAgent, bos); |
175 | 211 | Transformer transformer = TransformerFactory.newInstance().newTransformer(); |
176 | | - Source src = new StreamSource(new ByteArrayInputStream(fo.getBytes())); |
| 212 | + |
| 213 | + Source src = new StreamSource(inputStream); |
177 | 214 | Result res = new SAXResult(fop.getDefaultHandler()); |
178 | 215 | transformer.transform(src, res); |
179 | 216 | return bos; |
180 | 217 | } |
181 | 218 |
|
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>"; |
185 | 228 | return FopFactory.newInstance(new File(".").toURI(), new ByteArrayInputStream(fopxconf.getBytes())); |
186 | 229 | } |
187 | 230 | } |
0 commit comments