blob: a41cf30e95db2ccfd35fff8ef8dbcc7d6585e00a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.pdf;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.xml.transform.stream.StreamResult;
import org.junit.Assert;
import org.junit.Test;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.render.intermediate.IFContext;
import org.apache.fop.render.pdf.PDFContentGenerator;
import org.apache.fop.render.pdf.PDFDocumentHandler;
import org.apache.fop.render.pdf.PDFPainter;
public class PDFLinearizationTestCase {
private int objectLeast;
private int[] objects;
@Test
public void testPDF() throws IOException {
PDFDocument doc = new PDFDocument("");
doc.setLinearizationEnabled(true);
PDFResources resources = new PDFResources(doc);
PDFResourceContext context = new PDFResourceContext(resources);
ByteArrayOutputStream out = new ByteArrayOutputStream();
PDFContentGenerator gen = null;
for (int i = 0; i < 2; i++) {
gen = new PDFContentGenerator(doc, out, context);
Rectangle2D.Float f = new Rectangle2D.Float();
PDFPage page = new PDFPage(resources, i, f, f, f, f);
doc.registerObject(page);
doc.registerObject(gen.getStream());
page.setContents(new PDFReference(gen.getStream()));
}
gen.flushPDFDoc();
byte[] data = out.toByteArray();
checkPDF(data);
}
@Test
public void testImage() throws Exception {
String fopxconf = "<fop version=\"1.0\"><renderers>"
+ "<renderer mime=\"application/pdf\">"
+ "<linearization>true</linearization>"
+ "</renderer></renderers></fop>";
FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI(),
new ByteArrayInputStream(fopxconf.getBytes()));
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
IFContext ifContext = new IFContext(foUserAgent);
PDFDocumentHandler documentHandler = new PDFDocumentHandler(ifContext);
documentHandler.getConfigurator().configure(documentHandler);
ByteArrayOutputStream out = new ByteArrayOutputStream();
documentHandler.setFontInfo(new FontInfo());
documentHandler.setResult(new StreamResult(out));
documentHandler.startDocument();
documentHandler.startPage(0, "", "", new Dimension());
PDFPainter pdfPainter = new PDFPainter(documentHandler, null);
pdfPainter.drawImage("test/resources/fop/svg/logo.jpg", new Rectangle());
documentHandler.endPage();
Assert.assertFalse(out.toString().contains("/Subtype /Image"));
documentHandler.endDocument();
Assert.assertTrue(out.toString().contains("/Subtype /Image"));
}
private void checkPDF(byte[] data) throws IOException {
checkHintTable(data);
InputStream is = new ByteArrayInputStream(data);
Map<String, StringBuilder> objs = readObjs(is);
List<String> keys = new ArrayList<String>(objs.keySet());
int start = keys.indexOf("1 0 obj");
Assert.assertTrue(start > 1);
int j = 1;
for (int i = start; i < keys.size(); i++) {
Assert.assertEquals(keys.get(i), j + " 0 obj");
j++;
}
for (int i = 0; i < start; i++) {
Assert.assertEquals(keys.get(i), j + " 0 obj");
j++;
}
checkFirstObj(data);
checkTrailer(data);
String firstObj = objs.values().iterator().next().toString().replace("\n", "");
Assert.assertTrue(firstObj.startsWith("<< /Linearized 1 /L " + data.length));
Assert.assertTrue(firstObj.endsWith("startxref0%%EOF"));
int pageObjNumber = getValue("/O", firstObj);
Assert.assertTrue(objs.get(pageObjNumber + " 0 obj").toString().contains("/Type /Page"));
Assert.assertTrue(objs.get("5 0 obj").toString().contains("/Type /Pages"));
int total = 0;
for (int i : objects) {
total += i;
}
Assert.assertEquals(total, objs.size() - 6);
}
private void checkFirstObj(byte[] data) throws IOException {
int firstObjPos = getValue("/E", getFirstObj(data));
InputStream is = new ByteArrayInputStream(data);
Assert.assertEquals(is.skip(firstObjPos), firstObjPos);
byte[] obj = new byte[10];
Assert.assertEquals(is.read(obj), obj.length);
Assert.assertTrue(new String(obj).startsWith("1 0 obj"));
}
private void checkTrailer(byte[] data) throws IOException {
int trailerPos = getValue("/T", getFirstObj(data));
InputStream is = new ByteArrayInputStream(data);
Assert.assertEquals(is.skip(trailerPos), trailerPos);
byte[] obj = new byte[20];
Assert.assertEquals(is.read(obj), obj.length);
Assert.assertTrue(new String(obj).startsWith("0000000000 65535 f"));
}
private int getValue(String name, String firstObj) throws IOException {
String[] split = firstObj.split(" ");
for (int i = 0; i < split.length; i++) {
if (split[i].equals(name)) {
return Integer.valueOf(split[i + 1].replace(">>", ""));
}
}
throw new IOException(name + " not found " + firstObj);
}
private int[] getArrayValue(String name, String firstObj) throws IOException {
String[] split = firstObj.split(" ");
for (int i = 0; i < split.length; i++) {
if (split[i].equals(name)) {
int[] v = new int[2];
v[0] = Integer.valueOf(split[i + 1].replace("[", ""));
v[1] = Integer.valueOf(split[i + 2].replace("]", ""));
return v;
}
}
throw new IOException(name + " not found " + firstObj);
}
private String getFirstObj(byte[] out) throws IOException {
InputStream data = new ByteArrayInputStream(out);
Map<String, StringBuilder> objs = readObjs(data);
return objs.values().iterator().next().toString().replace("\n", "");
}
private void checkHintTable(byte[] out) throws IOException {
String firstObj = getFirstObj(out);
int hintPos = getArrayValue("/H", firstObj)[0];
int hintLength = getArrayValue("/H", firstObj)[1];
InputStream data = new ByteArrayInputStream(out);
Assert.assertEquals(data.skip(hintPos), hintPos);
byte[] hintTable = new byte[hintLength];
Assert.assertEquals(data.read(hintTable), hintLength);
String hintTableStr = new String(hintTable);
Assert.assertTrue(hintTableStr.contains("/S "));
Assert.assertTrue(hintTableStr.contains("/C "));
Assert.assertTrue(hintTableStr.contains("/E "));
Assert.assertTrue(hintTableStr.contains("/L "));
Assert.assertTrue(hintTableStr.contains("/V "));
Assert.assertTrue(hintTableStr.contains("/O "));
Assert.assertTrue(hintTableStr.contains("/I "));
Assert.assertTrue(hintTableStr.contains("/Length "));
Assert.assertTrue(hintTableStr.contains("stream"));
Assert.assertTrue(hintTableStr.contains("endstream"));
Assert.assertTrue(hintTableStr.endsWith("endobj\n"));
data = new ByteArrayInputStream(hintTable);
readStart(data);
int pages = getValue("/N", firstObj);
readObjectsTable(data, pages);
readSharedObjectsTable(data);
Assert.assertEquals(objectLeast, 1);
}
private void readObjectsTable(InputStream data, int pages)
throws IOException {
objectLeast = read32(data);
read32(data);
int bitsDiffObjects = read16(data);
read32(data);
int bitsDiffPageLength = read16(data);
read32(data);
read16(data);
read32(data);
read16(data);
read16(data);
read16(data);
read16(data);
read16(data);
objects = new int[pages];
for (int i = 0; i < pages; i++) {
objects[i] = objectLeast + readBits(bitsDiffObjects, data);
}
for (int i = 0; i < pages; i++) {
readBits(bitsDiffPageLength, data);
}
for (int i = 0; i < pages; i++) {
readBits(32, data);
}
}
private void readSharedObjectsTable(InputStream str) throws IOException {
readBits(32, str);
readBits(32, str);
readBits(32, str);
int sharedGroups = readBits(32, str);
readBits(16, str);
readBits(32, str);
int bitsDiffGroupLength = readBits(16, str);
for (int i = 0; i < sharedGroups; i++) {
readBits(bitsDiffGroupLength, str);
}
}
private int readBits(int bits, InputStream data) throws IOException {
if (bits == 32) {
return read32(data);
}
if (bits == 16) {
return read16(data);
}
throw new IOException("Wrong bits");
}
private int read32(InputStream data) throws IOException {
int ch1 = data.read();
int ch2 = data.read();
int ch3 = data.read();
int ch4 = data.read();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4));
}
private int read16(InputStream data) throws IOException {
int ch1 = data.read();
int ch2 = data.read();
return (ch1 << 8) + (ch2);
}
private void readStart(InputStream inputStream) throws IOException {
StringBuilder sb = new StringBuilder();
while (inputStream.available() > 0) {
int data = inputStream.read();
if (data == '\n') {
if (sb.toString().equals("stream")) {
return;
}
sb.setLength(0);
} else {
sb.append((char)data);
}
}
}
public static Map<String, StringBuilder> readObjs(InputStream inputStream) throws IOException {
Map<String, StringBuilder> objs = new LinkedHashMap<String, StringBuilder>();
StringBuilder sb = new StringBuilder();
String key = null;
while (inputStream.available() > 0) {
int data = inputStream.read();
if (data == '\n') {
if (sb.toString().endsWith(" 0 obj")) {
key = sb.toString().trim();
objs.put(key, new StringBuilder());
} else if (key != null) {
objs.get(key).append(sb).append("\n");
}
sb.setLength(0);
} else {
sb.append((char)data);
}
}
return objs;
}
}