1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* $Id$ */ 19 20 package org.apache.fop.complexscripts.layout; 21 22 import java.io.File; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.util.ArrayList; 26 import java.util.Collection; 27 import java.util.HashMap; 28 import java.util.Iterator; 29 import java.util.List; 30 import java.util.Map; 31 32 import javax.xml.parsers.ParserConfigurationException; 33 import javax.xml.transform.Source; 34 import javax.xml.transform.Transformer; 35 import javax.xml.transform.TransformerException; 36 import javax.xml.transform.TransformerFactory; 37 import javax.xml.transform.dom.DOMResult; 38 import javax.xml.transform.dom.DOMSource; 39 import javax.xml.transform.sax.SAXResult; 40 import javax.xml.transform.sax.TransformerHandler; 41 42 import org.junit.BeforeClass; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 import org.junit.runners.Parameterized; 46 import org.junit.runners.Parameterized.Parameters; 47 import org.w3c.dom.Document; 48 import org.w3c.dom.Element; 49 import org.w3c.dom.NamedNodeMap; 50 import org.w3c.dom.Node; 51 import org.w3c.dom.NodeList; 52 import org.xml.sax.ContentHandler; 53 import org.xml.sax.SAXException; 54 55 import static org.junit.Assert.assertEquals; 56 import static org.junit.Assert.assertTrue; 57 import static org.junit.Assert.fail; 58 59 import org.apache.commons.io.FileUtils; 60 import org.apache.commons.io.filefilter.AndFileFilter; 61 import org.apache.commons.io.filefilter.IOFileFilter; 62 import org.apache.commons.io.filefilter.NameFileFilter; 63 import org.apache.commons.io.filefilter.PrefixFileFilter; 64 import org.apache.commons.io.filefilter.SuffixFileFilter; 65 import org.apache.commons.io.filefilter.TrueFileFilter; 66 67 import org.apache.fop.DebugHelper; 68 import org.apache.fop.apps.EnvironmentProfile; 69 import org.apache.fop.apps.EnvironmentalProfileFactory; 70 import org.apache.fop.apps.FOUserAgent; 71 import org.apache.fop.apps.Fop; 72 import org.apache.fop.apps.FopConfBuilder; 73 import org.apache.fop.apps.FopConfParser; 74 import org.apache.fop.apps.FopFactory; 75 import org.apache.fop.apps.FopFactoryBuilder; 76 import org.apache.fop.apps.FormattingResults; 77 import org.apache.fop.apps.MimeConstants; 78 import org.apache.fop.apps.PDFRendererConfBuilder; 79 import org.apache.fop.apps.io.ResourceResolverFactory; 80 import org.apache.fop.area.AreaTreeModel; 81 import org.apache.fop.area.AreaTreeParser; 82 import org.apache.fop.area.RenderPagesModel; 83 import org.apache.fop.events.Event; 84 import org.apache.fop.events.EventListener; 85 import org.apache.fop.events.model.EventSeverity; 86 import org.apache.fop.fonts.FontInfo; 87 import org.apache.fop.intermediate.IFTester; 88 import org.apache.fop.intermediate.TestAssistant; 89 import org.apache.fop.layoutengine.ElementListCollector; 90 import org.apache.fop.layoutengine.LayoutEngineCheck; 91 import org.apache.fop.layoutengine.LayoutEngineChecksFactory; 92 import org.apache.fop.layoutengine.LayoutResult; 93 import org.apache.fop.layoutengine.TestFilesConfiguration; 94 import org.apache.fop.layoutmgr.ElementListObserver; 95 import org.apache.fop.render.Renderer; 96 import org.apache.fop.render.intermediate.IFContext; 97 import org.apache.fop.render.intermediate.IFRenderer; 98 import org.apache.fop.render.intermediate.IFSerializer; 99 import org.apache.fop.render.xml.XMLRenderer; 100 import org.apache.fop.util.ConsoleEventListenerForTests; 101 import org.apache.fop.util.DelegatingContentHandler; 102 103 // CSOFF: LineLengthCheck 104 105 /** 106 * Test complex script layout (end-to-end) functionality. 107 */ 108 @RunWith(Parameterized.class) 109 public class ComplexScriptsLayoutTestCase { 110 111 private static final boolean DEBUG = false; 112 private static final String AREA_TREE_OUTPUT_DIRECTORY = "build/test-results/complexscripts"; 113 private static File areaTreeOutputDir; 114 115 private TestAssistant testAssistant = new TestAssistant(); 116 private LayoutEngineChecksFactory layoutEngineChecksFactory = new LayoutEngineChecksFactory(); 117 private TestFilesConfiguration testConfig; 118 private File testFile; 119 private IFTester ifTester; 120 private TransformerFactory tfactory = TransformerFactory.newInstance(); 121 ComplexScriptsLayoutTestCase(TestFilesConfiguration testConfig, File testFile)122 public ComplexScriptsLayoutTestCase(TestFilesConfiguration testConfig, File testFile) { 123 this.testConfig = testConfig; 124 this.testFile = testFile; 125 this.ifTester = new IFTester(tfactory, areaTreeOutputDir); 126 } 127 128 @Parameters getParameters()129 public static Collection<Object[]> getParameters() throws IOException { 130 return getTestFiles(); 131 } 132 133 @BeforeClass makeDirAndRegisterDebugHelper()134 public static void makeDirAndRegisterDebugHelper() throws IOException { 135 DebugHelper.registerStandardElementListObservers(); 136 areaTreeOutputDir = new File(AREA_TREE_OUTPUT_DIRECTORY); 137 if (!areaTreeOutputDir.mkdirs() && !areaTreeOutputDir.exists()) { 138 throw new IOException("Failed to create the AT output directory at " + AREA_TREE_OUTPUT_DIRECTORY); 139 } 140 } 141 142 @Test runTest()143 public void runTest() throws TransformerException, SAXException, IOException, ParserConfigurationException { 144 DOMResult domres = new DOMResult(); 145 ElementListCollector elCollector = new ElementListCollector(); 146 ElementListObserver.addObserver(elCollector); 147 Fop fop; 148 FopFactory effFactory; 149 EventsChecker eventsChecker = new EventsChecker(new ConsoleEventListenerForTests(testFile.getName(), EventSeverity.WARN)); 150 try { 151 Document testDoc = testAssistant.loadTestCase(testFile); 152 effFactory = getFopFactory(testConfig, testDoc); 153 // Setup Transformer to convert the testcase XML to XSL-FO 154 Transformer transformer = testAssistant.getTestcase2FOStylesheet().newTransformer(); 155 Source src = new DOMSource(testDoc); 156 // Setup Transformer to convert the area tree to a DOM 157 TransformerHandler athandler; 158 athandler = testAssistant.getTransformerFactory().newTransformerHandler(); 159 athandler.setResult(domres); 160 // Setup FOP for area tree rendering 161 FOUserAgent ua = effFactory.newFOUserAgent(); 162 ua.getEventBroadcaster().addEventListener(eventsChecker); 163 XMLRenderer atrenderer = new XMLRenderer(ua); 164 Renderer targetRenderer = ua.getRendererFactory().createRenderer(ua, MimeConstants.MIME_PDF); 165 atrenderer.mimicRenderer(targetRenderer); 166 atrenderer.setContentHandler(athandler); 167 ua.setRendererOverride(atrenderer); 168 fop = effFactory.newFop(ua); 169 SAXResult fores = new SAXResult(fop.getDefaultHandler()); 170 transformer.transform(src, fores); 171 } finally { 172 ElementListObserver.removeObserver(elCollector); 173 } 174 Document doc = (Document)domres.getNode(); 175 if (areaTreeOutputDir != null) { 176 testAssistant.saveDOM(doc, new File(areaTreeOutputDir, testFile.getName() + ".at.xml")); 177 } 178 FormattingResults results = fop.getResults(); 179 LayoutResult result = new LayoutResult(doc, elCollector, results); 180 checkAll(effFactory, testFile, result, eventsChecker); 181 } 182 getFopFactory(TestFilesConfiguration testConfig, Document testDoc)183 private FopFactory getFopFactory(TestFilesConfiguration testConfig, Document testDoc) throws SAXException, IOException { 184 EnvironmentProfile profile = EnvironmentalProfileFactory.createRestrictedIO( 185 testConfig.getTestDirectory().getParentFile().toURI(), 186 ResourceResolverFactory.createDefaultResourceResolver()); 187 InputStream confStream = 188 new FopConfBuilder().setStrictValidation(true) 189 .setFontBaseURI("test/resources/fonts/ttf/") 190 .startRendererConfig(PDFRendererConfBuilder.class) 191 .startFontsConfig() 192 .startFont(null, "DejaVuLGCSerif.ttf") 193 .addTriplet("DejaVu LGC Serif", "normal", "normal") 194 .endFont() 195 .endFontConfig() 196 .endRendererConfig().build(); 197 FopFactoryBuilder builder = 198 new FopConfParser(confStream, new File(".").toURI(), profile).getFopFactoryBuilder(); 199 // builder.setStrictFOValidation(isStrictValidation(testDoc)); 200 // builder.getFontManager().setBase14KerningEnabled(isBase14KerningEnabled(testDoc)); 201 return builder.build(); 202 } 203 checkAll(FopFactory fopFactory, File testFile, LayoutResult result, EventsChecker eventsChecker)204 private void checkAll(FopFactory fopFactory, File testFile, LayoutResult result, EventsChecker eventsChecker) throws TransformerException { 205 Element testRoot = testAssistant.getTestRoot(testFile); 206 NodeList nodes; 207 nodes = testRoot.getElementsByTagName("at-checks"); 208 if (nodes.getLength() > 0) { 209 Element atChecks = (Element)nodes.item(0); 210 doATChecks(atChecks, result); 211 } 212 nodes = testRoot.getElementsByTagName("if-checks"); 213 if (nodes.getLength() > 0) { 214 Element ifChecks = (Element)nodes.item(0); 215 Document ifDocument = createIF(fopFactory, testFile, result.getAreaTree()); 216 ifTester.doIFChecks(testFile.getName(), ifChecks, ifDocument); 217 } 218 nodes = testRoot.getElementsByTagName("event-checks"); 219 if (nodes.getLength() > 0) { 220 Element eventChecks = (Element) nodes.item(0); 221 doEventChecks(eventChecks, eventsChecker); 222 } 223 eventsChecker.emitUncheckedEvents(); 224 } 225 createIF(FopFactory fopFactory, File testFile, Document areaTreeXML)226 private Document createIF(FopFactory fopFactory, File testFile, Document areaTreeXML) throws TransformerException { 227 try { 228 FOUserAgent ua = fopFactory.newFOUserAgent(); 229 ua.getEventBroadcaster().addEventListener(new ConsoleEventListenerForTests(testFile.getName(), EventSeverity.WARN)); 230 IFRenderer ifRenderer = new IFRenderer(ua); 231 IFSerializer serializer = new IFSerializer(new IFContext(ua)); 232 DOMResult result = new DOMResult(); 233 serializer.setResult(result); 234 ifRenderer.setDocumentHandler(serializer); 235 ua.setRendererOverride(ifRenderer); 236 FontInfo fontInfo = new FontInfo(); 237 //Construct the AreaTreeModel that will received the individual pages 238 final AreaTreeModel treeModel = new RenderPagesModel(ua, null, fontInfo, null); 239 //Iterate over all intermediate files 240 AreaTreeParser parser = new AreaTreeParser(); 241 ContentHandler handler = parser.getContentHandler(treeModel, ua); 242 DelegatingContentHandler proxy = new DelegatingContentHandler() { 243 public void endDocument() throws SAXException { 244 super.endDocument(); 245 treeModel.endDocument(); 246 } 247 }; 248 proxy.setDelegateContentHandler(handler); 249 Transformer transformer = tfactory.newTransformer(); 250 transformer.transform(new DOMSource(areaTreeXML), new SAXResult(proxy)); 251 return (Document)result.getNode(); 252 } catch (Exception e) { 253 throw new TransformerException("Error while generating intermediate format file: " + e.getMessage(), e); 254 } 255 } 256 doATChecks(Element checksRoot, LayoutResult result)257 private void doATChecks(Element checksRoot, LayoutResult result) { 258 List<LayoutEngineCheck> checks = layoutEngineChecksFactory.createCheckList(checksRoot); 259 if (checks.size() == 0) { 260 throw new RuntimeException("No available area tree check"); 261 } 262 for (LayoutEngineCheck check : checks) { 263 check.check(result); 264 } 265 } 266 doEventChecks(Element eventChecks, EventsChecker eventsChecker)267 private void doEventChecks(Element eventChecks, EventsChecker eventsChecker) { 268 NodeList events = eventChecks.getElementsByTagName("event"); 269 for (int i = 0; i < events.getLength(); i++) { 270 Element event = (Element) events.item(i); 271 NamedNodeMap attributes = event.getAttributes(); 272 Map<String, String> params = new HashMap<String, String>(); 273 String key = null; 274 for (int j = 0; j < attributes.getLength(); j++) { 275 Node attribute = attributes.item(j); 276 String name = attribute.getNodeName(); 277 String value = attribute.getNodeValue(); 278 if ("key".equals(name)) { 279 key = value; 280 } else { 281 params.put(name, value); 282 } 283 } 284 if (key == null) { 285 throw new RuntimeException("An event element must have a \"key\" attribute"); 286 } 287 eventsChecker.checkEvent(key, params); 288 } 289 } 290 getTestFiles(TestFilesConfiguration testConfig)291 private static Collection<Object[]> getTestFiles(TestFilesConfiguration testConfig) { 292 File mainDir = testConfig.getTestDirectory(); 293 IOFileFilter filter; 294 String single = testConfig.getSingleTest(); 295 String startsWith = testConfig.getStartsWith(); 296 if (single != null) { 297 filter = new NameFileFilter(single); 298 } else if (startsWith != null) { 299 filter = new PrefixFileFilter(startsWith); 300 filter = new AndFileFilter(filter, new SuffixFileFilter(testConfig.getFileSuffix())); 301 } else { 302 filter = new SuffixFileFilter(testConfig.getFileSuffix()); 303 } 304 String testset = testConfig.getTestSet(); 305 Collection<File> files = FileUtils.listFiles(new File(mainDir, testset), filter, TrueFileFilter.INSTANCE); 306 if (testConfig.hasPrivateTests()) { 307 Collection<File> privateFiles = 308 FileUtils.listFiles(new File(mainDir, "private-testcases"), filter, TrueFileFilter.INSTANCE); 309 files.addAll(privateFiles); 310 } 311 Collection<Object[]> parametersForJUnit4 = new ArrayList<Object[]>(); 312 int index = 0; 313 for (File f : files) { 314 parametersForJUnit4.add(new Object[] { testConfig, f }); 315 if (DEBUG) { 316 System.out.println(String.format("%3d %s", index++, f)); 317 } 318 } 319 return parametersForJUnit4; 320 } 321 getTestFiles()322 private static Collection<Object[]> getTestFiles() { 323 String testSet = System.getProperty("fop.complexscripts.testset"); 324 testSet = (testSet != null ? testSet : "standard") + "-testcases"; 325 return getTestFiles(testSet); 326 } 327 getTestFiles(String testSetName)328 private static Collection<Object[]> getTestFiles(String testSetName) { 329 TestFilesConfiguration.Builder builder = new TestFilesConfiguration.Builder(); 330 builder.testDir("test/resources/complexscripts/layout") 331 .singleProperty("fop.complexscripts.single") 332 .startsWithProperty("fop.complexscripts.starts-with") 333 .suffix(".xml") 334 .testSet(testSetName) 335 .privateTestsProperty("fop.complexscripts.private"); 336 return getTestFiles(builder.build()); 337 } 338 339 private static class EventsChecker implements EventListener { 340 341 private final List<Event> events = new ArrayList<Event>(); 342 private final EventListener defaultListener; 343 EventsChecker(EventListener fallbackListener)344 public EventsChecker(EventListener fallbackListener) { 345 this.defaultListener = fallbackListener; 346 } 347 processEvent(Event event)348 public void processEvent(Event event) { 349 events.add(event); 350 } 351 checkEvent(String expectedKey, Map<String, String> expectedParams)352 public void checkEvent(String expectedKey, Map<String, String> expectedParams) { 353 boolean eventFound = false; 354 for (Iterator<Event> iter = events.iterator(); !eventFound && iter.hasNext();) { 355 Event event = iter.next(); 356 if (event.getEventKey().equals(expectedKey)) { 357 eventFound = true; 358 iter.remove(); 359 checkParameters(event, expectedParams); 360 } 361 } 362 if (!eventFound) { 363 fail("Event did not occur but was expected to: " + expectedKey + expectedParams); 364 } 365 } 366 checkParameters(Event event, Map<String, String> expectedParams)367 private void checkParameters(Event event, Map<String, String> expectedParams) { 368 Map<String, Object> actualParams = event.getParams(); 369 for (Map.Entry<String, String> expectedParam : expectedParams.entrySet()) { 370 assertTrue("Event \"" + event.getEventKey() 371 + "\" is missing parameter \"" + expectedParam.getKey() + '"', 372 actualParams.containsKey(expectedParam.getKey())); 373 assertEquals("Event \"" + event.getEventKey() 374 + "\" has wrong value for parameter \"" + expectedParam.getKey() + "\";", 375 actualParams.get(expectedParam.getKey()).toString(), 376 expectedParam.getValue()); 377 } 378 } 379 emitUncheckedEvents()380 public void emitUncheckedEvents() { 381 for (Event event : events) { 382 defaultListener.processEvent(event); 383 } 384 } 385 } 386 387 } 388