TestCOSIncrement.java
/*
* 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.
*/
package org.apache.pdfbox.cos;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.io.RandomAccessReadBuffer;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationText;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.ThrowingSupplier;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ConcurrentModificationException;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.junit.jupiter.api.BeforeAll;
class TestCOSIncrement
{
@BeforeAll
static void init()
{
new File("target/test-output").mkdirs();
}
// TODO Very basic and primitive test - add in depth testing for all this.
/**
* Create a document from scratch - incrementally making changes - checking results of previous steps.
*/
@Test
void testIncrementallyCreateDocument()
{
byte[] documentData = new byte[0];
// Add page 1.
try (
ByteArrayOutputStream documentOutput = new ByteArrayOutputStream();
PDDocument document = assertDoesNotThrow(
(ThrowingSupplier<PDDocument>) PDDocument::new, "Creating the document failed.")
)
{
document.addPage(new PDPage(new PDRectangle(100, 100)));
document.save(documentOutput);
documentData = documentOutput.toByteArray();
}
catch (IOException e)
{
fail("Closing streams failed.");
}
// Add page 2 and 3.
try (
ByteArrayOutputStream documentOutput = new ByteArrayOutputStream();
PDDocument document = loadDocument(documentData)
)
{
assertEquals(1, document.getNumberOfPages(), "Document should have contained 1 page.");
document.addPage(new PDPage(new PDRectangle(200, 200)));
document.addPage(new PDPage(new PDRectangle(100, 100)));
document.saveIncremental(documentOutput);
documentData = documentOutput.toByteArray();
}
catch (IOException e)
{
fail("Closing streams failed.");
}
// Remove page 2.
try (
ByteArrayOutputStream documentOutput = new ByteArrayOutputStream();
PDDocument document = loadDocument(documentData)
)
{
assertEquals(3, document.getNumberOfPages(), "Document should have contained 3 pages.");
document.removePage(document.getPage(1));
document.saveIncremental(documentOutput);
documentData = documentOutput.toByteArray();
}
catch (IOException e)
{
fail("Closing streams failed.");
}
// Add an image to page 1.
try (
ByteArrayOutputStream documentOutput = new ByteArrayOutputStream();
PDDocument document = loadDocument(documentData)
)
{
assertNotEquals(200, document.getPage(1).getMediaBox().getWidth(), "Page 2 removal failed.");
assertEquals(2, document.getNumberOfPages(), "Document should have contained 2 pages.");
assertFalse(document.getPage(0).hasContents(), "Page 1 should not have had contents.");
assertNull(document.getPage(0).getResources(), "Page 1 should not have contained resources");
assertFalse(document.getPage(1).hasContents(), "Page 2 should not have had contents.");
assertNull(document.getPage(1).getResources(), "Page 2 should not have contained resources");
try (PDPageContentStream contentStream = new PDPageContentStream(document, document.getPage(0)))
{
URL imageResource = TestCOSIncrement.class.getResource("simple.png");
assertNotNull(imageResource, "Image resource not found.");
File image = assertDoesNotThrow(() -> new File(imageResource.toURI()),
"Image file could not be loaded");
contentStream.drawImage(PDImageXObject.createFromFileByExtension(image, document), 15, 20);
}
document.saveIncremental(documentOutput);
documentData = documentOutput.toByteArray();
}
catch (IOException e)
{
fail("Closing streams failed.");
}
// Write a text to page 2.
try (
ByteArrayOutputStream documentOutput = new ByteArrayOutputStream();
PDDocument document = loadDocument(documentData)
)
{
assertTrue(document.getPage(0).hasContents(), "Page 1 should have had contents.");
assertNotNull(document.getPage(0).getResources(), "Page 1 should have contained resources");
assertFalse(document.getPage(0).getResources().getFontNames().iterator().hasNext(),
"Page 1 should not have contained a font");
assertTrue(document.getPage(0).getResources().getXObjectNames().iterator().hasNext(),
"Page 1 should have contained an XObject");
assertFalse(document.getPage(1).hasContents(), "Page 2 should not have had contents.");
assertNull(document.getPage(1).getResources(), "Page 2 should not have contained resources");
try (PDPageContentStream contentStream = new PDPageContentStream(document, document.getPage(1)))
{
contentStream.beginText();
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 20);
contentStream.newLineAtOffset(20, 50);
contentStream.showText("Page 2");
contentStream.endText();
}
document.saveIncremental(documentOutput);
documentData = documentOutput.toByteArray();
}
catch (IOException e)
{
fail("Closing streams failed.");
}
// add an annotation to page 2.
try (
ByteArrayOutputStream documentOutput = new ByteArrayOutputStream();
PDDocument document = loadDocument(documentData)
)
{
assertTrue(document.getPage(0).hasContents(), "Page 1 should have had contents.");
assertNotNull(document.getPage(0).getResources(), "Page 1 should have contained resources");
assertNotNull(document.getPage(1).getResources(), "Page 2 should have contained resources");
assertFalse(document.getPage(1).getAnnotations().size() > 0,
"Page 2 should not have contained an annotation.");
assertTrue(document.getPage(1).hasContents(), "Page 2 should have had contents.");
assertTrue(document.getPage(1).getResources().getFontNames().iterator().hasNext(),
"Page 2 should have contained a font");
assertFalse(document.getPage(1).getResources().getXObjectNames().iterator().hasNext(),
"Page 1 should not have contained an XObject");
PDAnnotationText textAnnotation = new PDAnnotationText();
textAnnotation.setName("text annotation");
textAnnotation.setContents("text annotation");
textAnnotation.setOpen(true);
textAnnotation.setColor(new PDColor(new float[]{1, 0, 0}, PDDeviceRGB.INSTANCE));
textAnnotation.setRectangle(new PDRectangle(4, 5, 10, 10));
textAnnotation.constructAppearances(document);
document.getPage(1).getAnnotations().add(textAnnotation);
document.saveIncremental(documentOutput);
documentData = documentOutput.toByteArray();
}
catch (IOException e)
{
fail("Closing streams failed.");
}
// Do nothing.
try (
ByteArrayOutputStream documentOutput = new ByteArrayOutputStream();
PDDocument document = loadDocument(documentData)
)
{
assertEquals(1, document.getPage(1).getAnnotations().size(), "Page 2 should have contained an annotation.");
document.saveIncremental(documentOutput);
documentData = documentOutput.toByteArray();
}
catch (IOException e)
{
fail("Closing streams failed.");
}
// Check the result.
try (
PDDocument document = loadDocument(documentData)
)
{
assertEquals(2, document.getNumberOfPages(), "Document should have contained 2 pages.");
assertNotNull(document.getPage(0).getResources(), "Page 1 should have contained resources");
assertNotNull(document.getPage(1).getResources(), "Page 2 should have contained resources");
assertTrue(document.getPage(0).hasContents(), "Page 1 should have had contents.");
assertFalse(document.getPage(0).getResources().getFontNames().iterator().hasNext(),
"Page 1 should not have contained a font");
assertTrue(document.getPage(0).getResources().getXObjectNames().iterator().hasNext(),
"Page 1 should have contained an XObject");
assertTrue(document.getPage(1).hasContents(),
"Page 2 should have had contents.");
assertEquals(1, document.getPage(1).getAnnotations().size(),
"Page 2 should have contained an annotation.");
assertTrue(document.getPage(1).getResources().getFontNames().iterator().hasNext(),
"Page 2 should have contained a font");
}
catch (IOException e)
{
fail("Closing streams failed.");
}
// TODO: remove the following - Convenience code - this creates the output file at some path,
// to see and touch it.
/*File outFile = new File("Some/path", "out.pdf");
try (FileOutputStream outputStream = new FileOutputStream(outFile)) {
outputStream.write(documentData);
} catch (IOException ex) {
fail("Writing 'out.pdf' failed.");
}*/
}
/**
* PDFBOX-5263: There was a ConcurrentModificationException with
* YTW2VWJQTDAE67PGJT6GS7QSKW3GNUQR.pdf - test that this issues has been resolved.
*
* @throws IOException
* @throws URISyntaxException
*/
@Test
void testConcurrentModification() throws IOException, URISyntaxException
{
URL pdfLocation =
new URI("https://issues.apache.org/jira/secure/attachment/12891316/YTW2VWJQTDAE67PGJT6GS7QSKW3GNUQR.pdf").toURL();
try (PDDocument document = Loader
.loadPDF(RandomAccessReadBuffer.createBufferFromStream(pdfLocation.openStream())))
{
document.setAllSecurityToBeRemoved(true);
try
{
document.save(new ByteArrayOutputStream());
}
catch (ConcurrentModificationException e)
{
fail("There shouldn't be a ConcurrentModificationException", e.getCause());
}
}
}
private PDDocument loadDocument(byte[] documentData)
{
return assertDoesNotThrow(() -> Loader.loadPDF(documentData), "Loading the document failed.");
}
/**
* Check that subsetting takes place in incremental saving.
*/
@Test
void testSubsetting() throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (PDDocument document = new PDDocument())
{
PDPage page = new PDPage(PDRectangle.A4);
document.addPage(page);
document.save(baos);
}
try (PDDocument document = Loader.loadPDF(baos.toByteArray()))
{
PDPage page = document.getPage(0);
PDFont font = PDType0Font.load(document, TestCOSIncrement.class.getResourceAsStream(
"/org/apache/pdfbox/resources/ttf/LiberationSans-Regular.ttf"));
try (PDPageContentStream contentStream = new PDPageContentStream(document, page))
{
contentStream.beginText();
contentStream.setFont(font, 12);
contentStream.newLineAtOffset(75, 750);
contentStream.showText("Apache PDFBox");
contentStream.endText();
}
COSDictionary catalog = document.getDocumentCatalog().getCOSObject();
catalog.setNeedToBeUpdated(true);
COSDictionary pages = catalog.getCOSDictionary(COSName.PAGES);
pages.setNeedToBeUpdated(true);
page.getCOSObject().setNeedToBeUpdated(true);
document.saveIncremental(new FileOutputStream("target/test-output/PDFBOX-5627.pdf"));
}
try (PDDocument document = Loader.loadPDF(new File("target/test-output/PDFBOX-5627.pdf")))
{
PDPage page = document.getPage(0);
COSName fontName = page.getResources().getFontNames().iterator().next();
PDFont font = page.getResources().getFont(fontName);
assertTrue(font.isEmbedded());
}
}
}