| /* |
| * 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; |
| } |
| } |