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 package org.apache.fop.pdf; 20 21 import java.awt.Dimension; 22 import java.awt.Rectangle; 23 import java.awt.geom.Rectangle2D; 24 import java.io.ByteArrayInputStream; 25 import java.io.ByteArrayOutputStream; 26 import java.io.File; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.util.ArrayList; 30 import java.util.LinkedHashMap; 31 import java.util.List; 32 import java.util.Map; 33 34 import javax.xml.transform.stream.StreamResult; 35 36 import org.junit.Assert; 37 import org.junit.Test; 38 39 import org.apache.fop.apps.FOUserAgent; 40 import org.apache.fop.apps.FopFactory; 41 import org.apache.fop.fonts.FontInfo; 42 import org.apache.fop.render.intermediate.IFContext; 43 import org.apache.fop.render.pdf.PDFContentGenerator; 44 import org.apache.fop.render.pdf.PDFDocumentHandler; 45 import org.apache.fop.render.pdf.PDFPainter; 46 47 public class PDFLinearizationTestCase { 48 private int objectLeast; 49 private int[] objects; 50 51 @Test testPDF()52 public void testPDF() throws IOException { 53 PDFDocument doc = new PDFDocument(""); 54 doc.setLinearizationEnabled(true); 55 PDFResources resources = new PDFResources(doc); 56 PDFResourceContext context = new PDFResourceContext(resources); 57 ByteArrayOutputStream out = new ByteArrayOutputStream(); 58 PDFContentGenerator gen = null; 59 for (int i = 0; i < 2; i++) { 60 gen = new PDFContentGenerator(doc, out, context); 61 Rectangle2D.Float f = new Rectangle2D.Float(); 62 PDFPage page = new PDFPage(resources, i, f, f, f, f); 63 doc.registerObject(page); 64 doc.registerObject(gen.getStream()); 65 page.setContents(new PDFReference(gen.getStream())); 66 } 67 gen.flushPDFDoc(); 68 byte[] data = out.toByteArray(); 69 checkPDF(data); 70 } 71 72 @Test testImage()73 public void testImage() throws Exception { 74 String fopxconf = "<fop version=\"1.0\"><renderers>" 75 + "<renderer mime=\"application/pdf\">" 76 + "<linearization>true</linearization>" 77 + "</renderer></renderers></fop>"; 78 FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI(), 79 new ByteArrayInputStream(fopxconf.getBytes())); 80 FOUserAgent foUserAgent = fopFactory.newFOUserAgent(); 81 IFContext ifContext = new IFContext(foUserAgent); 82 PDFDocumentHandler documentHandler = new PDFDocumentHandler(ifContext); 83 documentHandler.getConfigurator().configure(documentHandler); 84 ByteArrayOutputStream out = new ByteArrayOutputStream(); 85 documentHandler.setFontInfo(new FontInfo()); 86 documentHandler.setResult(new StreamResult(out)); 87 documentHandler.startDocument(); 88 documentHandler.startPage(0, "", "", new Dimension()); 89 PDFPainter pdfPainter = new PDFPainter(documentHandler, null); 90 pdfPainter.drawImage("test/resources/fop/svg/logo.jpg", new Rectangle()); 91 documentHandler.endPage(); 92 Assert.assertFalse(out.toString().contains("/Subtype /Image")); 93 documentHandler.endDocument(); 94 Assert.assertTrue(out.toString().contains("/Subtype /Image")); 95 } 96 checkPDF(byte[] data)97 private void checkPDF(byte[] data) throws IOException { 98 checkHintTable(data); 99 InputStream is = new ByteArrayInputStream(data); 100 Map<String, StringBuilder> objs = readObjs(is); 101 102 List<String> keys = new ArrayList<String>(objs.keySet()); 103 int start = keys.indexOf("1 0 obj"); 104 Assert.assertTrue(start > 1); 105 int j = 1; 106 for (int i = start; i < keys.size(); i++) { 107 Assert.assertEquals(keys.get(i), j + " 0 obj"); 108 j++; 109 } 110 for (int i = 0; i < start; i++) { 111 Assert.assertEquals(keys.get(i), j + " 0 obj"); 112 j++; 113 } 114 115 checkFirstObj(data); 116 checkTrailer(data); 117 118 String firstObj = objs.values().iterator().next().toString().replace("\n", ""); 119 Assert.assertTrue(firstObj.startsWith("<< /Linearized 1 /L " + data.length)); 120 Assert.assertTrue(firstObj.endsWith("startxref0%%EOF")); 121 int pageObjNumber = getValue("/O", firstObj); 122 Assert.assertTrue(objs.get(pageObjNumber + " 0 obj").toString().contains("/Type /Page")); 123 Assert.assertTrue(objs.get("5 0 obj").toString().contains("/Type /Pages")); 124 125 int total = 0; 126 for (int i : objects) { 127 total += i; 128 } 129 Assert.assertEquals(total, objs.size() - 6); 130 } 131 checkFirstObj(byte[] data)132 private void checkFirstObj(byte[] data) throws IOException { 133 int firstObjPos = getValue("/E", getFirstObj(data)); 134 InputStream is = new ByteArrayInputStream(data); 135 Assert.assertEquals(is.skip(firstObjPos), firstObjPos); 136 byte[] obj = new byte[10]; 137 Assert.assertEquals(is.read(obj), obj.length); 138 Assert.assertTrue(new String(obj).startsWith("1 0 obj")); 139 } 140 checkTrailer(byte[] data)141 private void checkTrailer(byte[] data) throws IOException { 142 int trailerPos = getValue("/T", getFirstObj(data)); 143 InputStream is = new ByteArrayInputStream(data); 144 Assert.assertEquals(is.skip(trailerPos), trailerPos); 145 byte[] obj = new byte[20]; 146 Assert.assertEquals(is.read(obj), obj.length); 147 Assert.assertTrue(new String(obj).startsWith("0000000000 65535 f")); 148 } 149 getValue(String name, String firstObj)150 private int getValue(String name, String firstObj) throws IOException { 151 String[] split = firstObj.split(" "); 152 for (int i = 0; i < split.length; i++) { 153 if (split[i].equals(name)) { 154 return Integer.valueOf(split[i + 1].replace(">>", "")); 155 } 156 } 157 throw new IOException(name + " not found " + firstObj); 158 } 159 getArrayValue(String name, String firstObj)160 private int[] getArrayValue(String name, String firstObj) throws IOException { 161 String[] split = firstObj.split(" "); 162 for (int i = 0; i < split.length; i++) { 163 if (split[i].equals(name)) { 164 int[] v = new int[2]; 165 v[0] = Integer.valueOf(split[i + 1].replace("[", "")); 166 v[1] = Integer.valueOf(split[i + 2].replace("]", "")); 167 return v; 168 } 169 } 170 throw new IOException(name + " not found " + firstObj); 171 } 172 getFirstObj(byte[] out)173 private String getFirstObj(byte[] out) throws IOException { 174 InputStream data = new ByteArrayInputStream(out); 175 Map<String, StringBuilder> objs = readObjs(data); 176 return objs.values().iterator().next().toString().replace("\n", ""); 177 } 178 checkHintTable(byte[] out)179 private void checkHintTable(byte[] out) throws IOException { 180 String firstObj = getFirstObj(out); 181 int hintPos = getArrayValue("/H", firstObj)[0]; 182 int hintLength = getArrayValue("/H", firstObj)[1]; 183 184 InputStream data = new ByteArrayInputStream(out); 185 Assert.assertEquals(data.skip(hintPos), hintPos); 186 187 byte[] hintTable = new byte[hintLength]; 188 Assert.assertEquals(data.read(hintTable), hintLength); 189 String hintTableStr = new String(hintTable); 190 191 Assert.assertTrue(hintTableStr.contains("/S ")); 192 Assert.assertTrue(hintTableStr.contains("/C ")); 193 Assert.assertTrue(hintTableStr.contains("/E ")); 194 Assert.assertTrue(hintTableStr.contains("/L ")); 195 Assert.assertTrue(hintTableStr.contains("/V ")); 196 Assert.assertTrue(hintTableStr.contains("/O ")); 197 Assert.assertTrue(hintTableStr.contains("/I ")); 198 Assert.assertTrue(hintTableStr.contains("/Length ")); 199 Assert.assertTrue(hintTableStr.contains("stream")); 200 Assert.assertTrue(hintTableStr.contains("endstream")); 201 Assert.assertTrue(hintTableStr.endsWith("endobj\n")); 202 203 data = new ByteArrayInputStream(hintTable); 204 readStart(data); 205 int pages = getValue("/N", firstObj); 206 readObjectsTable(data, pages); 207 readSharedObjectsTable(data); 208 Assert.assertEquals(objectLeast, 1); 209 } 210 readObjectsTable(InputStream data, int pages)211 private void readObjectsTable(InputStream data, int pages) 212 throws IOException { 213 objectLeast = read32(data); 214 read32(data); 215 int bitsDiffObjects = read16(data); 216 read32(data); 217 int bitsDiffPageLength = read16(data); 218 read32(data); 219 read16(data); 220 read32(data); 221 read16(data); 222 read16(data); 223 read16(data); 224 read16(data); 225 read16(data); 226 227 objects = new int[pages]; 228 for (int i = 0; i < pages; i++) { 229 objects[i] = objectLeast + readBits(bitsDiffObjects, data); 230 } 231 for (int i = 0; i < pages; i++) { 232 readBits(bitsDiffPageLength, data); 233 } 234 for (int i = 0; i < pages; i++) { 235 readBits(32, data); 236 } 237 } 238 readSharedObjectsTable(InputStream str)239 private void readSharedObjectsTable(InputStream str) throws IOException { 240 readBits(32, str); 241 readBits(32, str); 242 readBits(32, str); 243 int sharedGroups = readBits(32, str); 244 readBits(16, str); 245 readBits(32, str); 246 int bitsDiffGroupLength = readBits(16, str); 247 for (int i = 0; i < sharedGroups; i++) { 248 readBits(bitsDiffGroupLength, str); 249 } 250 } 251 readBits(int bits, InputStream data)252 private int readBits(int bits, InputStream data) throws IOException { 253 if (bits == 32) { 254 return read32(data); 255 } 256 if (bits == 16) { 257 return read16(data); 258 } 259 throw new IOException("Wrong bits"); 260 } 261 read32(InputStream data)262 private int read32(InputStream data) throws IOException { 263 int ch1 = data.read(); 264 int ch2 = data.read(); 265 int ch3 = data.read(); 266 int ch4 = data.read(); 267 return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4)); 268 } 269 read16(InputStream data)270 private int read16(InputStream data) throws IOException { 271 int ch1 = data.read(); 272 int ch2 = data.read(); 273 return (ch1 << 8) + (ch2); 274 } 275 readStart(InputStream inputStream)276 private void readStart(InputStream inputStream) throws IOException { 277 StringBuilder sb = new StringBuilder(); 278 while (inputStream.available() > 0) { 279 int data = inputStream.read(); 280 if (data == '\n') { 281 if (sb.toString().equals("stream")) { 282 return; 283 } 284 sb.setLength(0); 285 } else { 286 sb.append((char)data); 287 } 288 } 289 } 290 readObjs(InputStream inputStream)291 public static Map<String, StringBuilder> readObjs(InputStream inputStream) throws IOException { 292 Map<String, StringBuilder> objs = new LinkedHashMap<String, StringBuilder>(); 293 StringBuilder sb = new StringBuilder(); 294 String key = null; 295 while (inputStream.available() > 0) { 296 int data = inputStream.read(); 297 if (data == '\n') { 298 if (sb.toString().endsWith(" 0 obj")) { 299 key = sb.toString().trim(); 300 objs.put(key, new StringBuilder()); 301 } else if (key != null) { 302 objs.get(key).append(sb).append("\n"); 303 } 304 sb.setLength(0); 305 } else { 306 sb.append((char)data); 307 } 308 } 309 return objs; 310 } 311 } 312