blob: c9449660dfe00a7e311e1096ff2bc8ca77bfbf66 [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.
*/
package org.apache.fop.render.pdf.pdfbox;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.apache.commons.io.IOUtils;
import org.apache.fontbox.cff.CFFCharset;
import org.apache.fontbox.cff.CFFParser;
import org.apache.fontbox.cff.CFFType1Font;
import org.apache.fontbox.ttf.GlyphData;
import org.apache.fontbox.ttf.TTFParser;
import org.apache.fontbox.ttf.TrueTypeFont;
import org.apache.fontbox.type1.Type1Font;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSInteger;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSObject;
import org.apache.pdfbox.io.RandomAccessReadBuffer;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.font.PDCIDFontType2;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.xmlgraphics.image.loader.ImageException;
import org.apache.xmlgraphics.image.loader.ImageInfo;
import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D;
import org.apache.xmlgraphics.image.loader.util.SoftMapCache;
import org.apache.xmlgraphics.java2d.GeneralGraphics2DImagePainter;
import org.apache.xmlgraphics.java2d.GraphicContext;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontType;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.pdf.PDFAnnotList;
import org.apache.fop.pdf.PDFArray;
import org.apache.fop.pdf.PDFDictionary;
import org.apache.fop.pdf.PDFDocument;
import org.apache.fop.pdf.PDFEncryptionParams;
import org.apache.fop.pdf.PDFFilterList;
import org.apache.fop.pdf.PDFFormXObject;
import org.apache.fop.pdf.PDFGState;
import org.apache.fop.pdf.PDFPage;
import org.apache.fop.pdf.PDFResources;
import org.apache.fop.pdf.PDFStream;
import org.apache.fop.render.pcl.PCLGenerator;
import org.apache.fop.render.pcl.PCLGraphics2D;
import org.apache.fop.render.pdf.PDFContentGenerator;
import org.apache.fop.render.pdf.PDFRenderingContext;
import org.apache.fop.render.ps.PSDocumentHandler;
import org.apache.fop.render.ps.PSImageFormResource;
import org.apache.fop.render.ps.PSRenderingUtil;
public class PDFBoxAdapterTestCase {
protected static final String CFF1 = "2fonts.pdf";
protected static final String CFF2 = "2fonts2.pdf";
protected static final String CFF3 = "simpleh.pdf";
protected static final String CFFSUBRS = "cffsubrs.pdf";
protected static final String CFFSUBRS2 = "cffsubrs2.pdf";
protected static final String CFFSUBRS3 = "cffsubrs3.pdf";
protected static final String CFFSUBRS4 = "cffsubrs4.pdf";
protected static final String TTCID1 = "ttcid1.pdf";
protected static final String TTCID2 = "ttcid2.pdf";
protected static final String TTSubset1 = "ttsubset.pdf";
protected static final String TTSubset2 = "ttsubset2.pdf";
protected static final String TTSubset3 = "ttsubset3.pdf";
protected static final String TTSubset5 = "ttsubset5.pdf";
protected static final String TTSubset6 = "ttsubset6.pdf";
protected static final String TTSubset7 = "ttsubset7.pdf";
protected static final String TTSubset8 = "ttsubset8.pdf";
protected static final String TTSubset9 = "ttsubset9.pdf";
protected static final String TTSubset10 = "ttsubset10.pdf";
protected static final String CFFCID1 = "cffcid1.pdf";
protected static final String CFFCID2 = "cffcid2.pdf";
protected static final String Type1Subset1 = "t1subset.pdf";
protected static final String Type1Subset2 = "t1subset2.pdf";
protected static final String Type1Subset3 = "t1subset3.pdf";
protected static final String Type1Subset4 = "t1subset4.pdf";
protected static final String ROTATE = "rotate.pdf";
protected static final String ANNOT = "annot.pdf";
protected static final String ANNOT2 = "annot2.pdf";
protected static final String ANNOT3 = "annot3.pdf";
protected static final String SHADING = "shading.pdf";
protected static final String LINK = "link.pdf";
protected static final String IMAGE = "image.pdf";
protected static final String HELLOTagged = "taggedWorld.pdf";
protected static final String XFORM = "xform.pdf";
protected static final String LOOP = "loop.pdf";
protected static final String ERROR = "error.pdf";
protected static final String LIBREOFFICE = "libreoffice.pdf";
protected static final String SMASK = "smask.pdf";
protected static final String TYPE0TT = "type0tt.pdf";
protected static final String TYPE0CFF = "type0cff.pdf";
protected static final String ACCESSIBLERADIOBUTTONS = "accessibleradiobuttons.pdf";
protected static final String PATTERN = "pattern.pdf";
protected static final String PATTERN2 = "pattern2.pdf";
protected static final String FORMROTATED = "formrotated.pdf";
protected static final String SOFTMASK = "softmask.pdf";
private static PDFPage getPDFPage(PDFDocument doc) {
final Rectangle2D r = new Rectangle2D.Double();
return new PDFPage(new PDFResources(doc), 0, r, r, r, r);
}
protected static PDFBoxAdapter getPDFBoxAdapter(boolean mergeFonts, boolean formXObject) {
return getPDFBoxAdapter(new PDFDocument(""), mergeFonts, formXObject, false, new HashMap<String, Object>());
}
private static PDFBoxAdapter getPDFBoxAdapter(PDFDocument doc, boolean mergeFonts, boolean formXObject,
boolean mergeFormFields, Map<String, Object> usedFields) {
PDFPage pdfpage = getPDFPage(doc);
doc.setMergeFontsEnabled(mergeFonts);
doc.setFormXObjectEnabled(formXObject);
doc.setMergeFormFieldsEnabled(mergeFormFields);
pdfpage.setDocument(doc);
pdfpage.setObjectNumber(1);
return new PDFBoxAdapter(pdfpage, new HashMap<>(), usedFields, new HashMap<Integer, PDFArray>(),
new HashMap<>());
}
public static PDDocument load(String pdf) throws IOException {
return Loader.loadPDF(new RandomAccessReadBuffer(PDFBoxAdapterTestCase.class.getResourceAsStream(pdf)));
}
@Test
public void testPDFWriter() throws Exception {
FontInfo fi = new FontInfo();
String msg = writeText(fi, CFF3);
Assert.assertTrue(msg, msg.contains("/Myriad_Pro"));
assertEquals(fi.getUsedFonts().size(), 2);
msg = writeText(fi, TTSubset1);
Assert.assertTrue(msg, msg.contains("<74>-0.168 <65>-0.1523 <73>0.1528 <74>277.832"));
msg = writeText(fi, TTSubset2);
Assert.assertTrue(msg, msg.contains("(t)-0.168 (e)-0.1523 (s)0.1528 (t)"));
msg = writeText(fi, TTSubset3);
Assert.assertTrue(msg, msg.contains("[<01>3 <02>-7 <03>] TJ"));
msg = writeText(fi, TTSubset5);
Assert.assertTrue(msg, msg.contains("[(\u0001)2 (\u0002)-7 (\u0003)] TJ"));
msg = writeText(fi, TTCID1);
Assert.assertTrue(msg, msg.contains("<0031001100110011001800120012001300140034>"));
msg = writeText(fi, TTCID2);
Assert.assertTrue(msg, msg.contains("<0031001100110011001800120012001300120034>"));
msg = writeText(fi, CFFCID1);
Assert.assertTrue(msg, msg.contains("/Fm0-1998009062 Do"));
msg = writeText(fi, CFFCID2);
Assert.assertTrue(msg, msg.contains("/Fm0-1997085541 Do"));
msg = writeText(fi, Type1Subset1);
Assert.assertTrue(msg, msg.contains("/Verdana_Type1"));
msg = writeText(fi, Type1Subset2);
Assert.assertTrue(msg, msg.contains("[(2nd example)] TJ"));
msg = writeText(fi, Type1Subset3);
Assert.assertTrue(msg, msg.contains("/URWChanceryL-MediItal_Type1 20 Tf"));
msg = writeText(fi, Type1Subset4);
Assert.assertTrue(msg, msg.contains("/F15_1683747577 40 Tf"));
parseFonts(fi);
}
@Test
public void testMergeTTCFF() throws IOException {
FontInfo fi = new FontInfo();
writeText(fi, TYPE0TT);
writeText(fi, TYPE0CFF);
parseFonts(fi);
}
@Test
public void testMergeTT() throws IOException {
PDDocument doc = load(TYPE0TT);
PDType0Font type0Font = (PDType0Font) doc.getPage(0).getResources().getFont(COSName.getPDFName("C2_0"));
PDCIDFontType2 ttf = (PDCIDFontType2) type0Font.getDescendantFont();
InputStream originalData = ttf.getTrueTypeFont().getOriginalData();
byte[] originalDataBytes = IOUtils.toByteArray(originalData);
doc.close();
MergeTTFonts mergeTTFonts = new MergeTTFonts(null);
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
map.put(0, 0);
mergeTTFonts.readFont(new ByteArrayInputStream(originalDataBytes), null, null, map, true);
byte[] mergedData = mergeTTFonts.getMergedFontSubset();
Assert.assertArrayEquals(mergedData, originalDataBytes);
}
private void parseFonts(FontInfo fi) throws IOException {
for (Typeface font : fi.getUsedFonts().values()) {
InputStream is = ((CustomFont) font).getInputStream();
if (font.getFontType() == FontType.TYPE1C || font.getFontType() == FontType.CIDTYPE0) {
CFFParser p = new CFFParser();
p.parse(new RandomAccessReadBuffer(is));
} else if (font.getFontType() == FontType.TRUETYPE) {
TTFParser parser = new TTFParser();
parser.parse(new RandomAccessReadBuffer(is));
} else if (font.getFontType() == FontType.TYPE0) {
TTFParser parser = new TTFParser(true);
parser.parse(new RandomAccessReadBuffer(is));
} else if (font.getFontType() == FontType.TYPE1) {
Type1Font.createWithPFB(is);
}
Assert.assertTrue(((CustomFont) font).isEmbeddable());
if (font instanceof MultiByteFont) {
Assert.assertTrue(((MultiByteFont) font).getWidthsMap() != null);
} else {
Assert.assertFalse(((CustomFont)font).isSymbolicFont());
}
}
}
protected static String writeText(FontInfo fi, String pdf) throws IOException {
PDDocument doc = load(pdf);
PDPage page = doc.getPage(0);
AffineTransform pageAdjust = new AffineTransform();
String c = (String) getPDFBoxAdapter(true, false)
.createStreamFromPDFBoxPage(doc, page, pdf, pageAdjust, fi, new Rectangle(), new AffineTransform());
doc.close();
return c;
}
@Test
public void testTaggedPDFWriter() throws IOException {
PDFBoxAdapter adapter = getPDFBoxAdapter(false, false);
adapter.setCurrentMCID(5);
PDDocument doc = load(HELLOTagged);
PDPage page = doc.getPage(0);
AffineTransform pageAdjust = new AffineTransform();
Rectangle r = new Rectangle(0, 1650, 842000, 595000);
String stream = (String) adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, r, pageAdjust);
Assert.assertTrue(stream, stream.contains("/P <</MCID 5 >>BDC"));
doc.close();
}
@Test
public void testAnnot() throws Exception {
PDFDocument pdfdoc = new PDFDocument("");
PDFPage pdfpage = getPDFPage(pdfdoc);
pdfpage.setDocument(pdfdoc);
pdfpage.setObjectNumber(1);
PDFBoxAdapter adapter = new PDFBoxAdapter(pdfpage, new HashMap<>(), new HashMap<Integer, PDFArray>());
PDDocument doc = load(ANNOT);
PDPage page = doc.getPage(0);
AffineTransform pageAdjust = new AffineTransform();
Rectangle r = new Rectangle(0, 1650, 842000, 595000);
ByteArrayOutputStream os = new ByteArrayOutputStream();
pdfdoc.output(os);
os.reset();
adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, r, pageAdjust);
pdfdoc.outputTrailer(os);
Assert.assertTrue(os.toString(StandardCharsets.UTF_8.name()).contains("/Fields ["));
doc.close();
}
@Test
public void testAnnot2() throws Exception {
PDFBoxAdapter adapter = getPDFBoxAdapter(false, false);
PDDocument doc = load(ANNOT);
PDPage page = doc.getPage(0);
COSArray annots = (COSArray) page.getCOSObject().getDictionaryObject(COSName.ANNOTS);
COSDictionary dict = (COSDictionary) ((COSObject)annots.get(0)).getObject();
dict.setItem(COSName.PARENT, COSInteger.ONE);
AffineTransform pageAdjust = new AffineTransform();
Rectangle r = new Rectangle(0, 1650, 842000, 595000);
adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, r, pageAdjust);
doc.close();
}
@Test
public void testAnnot3() throws Exception {
PDFDocument pdfdoc = new PDFDocument("");
PDFPage pdfpage = getPDFPage(pdfdoc);
pdfpage.setDocument(pdfdoc);
pdfpage.setObjectNumber(1);
PDFBoxAdapter adapter = new PDFBoxAdapter(pdfpage, new HashMap<>(), new HashMap<Integer, PDFArray>());
PDDocument doc = load(ACCESSIBLERADIOBUTTONS);
PDPage page = doc.getPage(0);
AffineTransform pageAdjust = new AffineTransform();
Rectangle r = new Rectangle(0, 1650, 842000, 595000);
adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, r, pageAdjust);
ByteArrayOutputStream os = new ByteArrayOutputStream();
pdfdoc.output(os);
String out = os.toString(StandardCharsets.UTF_8.name());
Assert.assertTrue(out.contains("/Parent "));
Assert.assertTrue(out.contains("/Kids "));
doc.close();
}
@Test
public void testAnnotFields() throws Exception {
PDFDocument pdfdoc = new PDFDocument("");
PDFPage pdfpage = getPDFPage(pdfdoc);
pdfpage.setDocument(pdfdoc);
pdfpage.setObjectNumber(1);
PDFBoxAdapter adapter = new PDFBoxAdapter(pdfpage, new HashMap<>(), new HashMap<Integer, PDFArray>());
PDDocument doc = load(ACCESSIBLERADIOBUTTONS);
COSArray fields = (COSArray)
doc.getDocumentCatalog().getAcroForm().getCOSObject().getDictionaryObject(COSName.FIELDS);
fields.remove(0);
PDPage page = doc.getPage(0);
AffineTransform pageAdjust = new AffineTransform();
Rectangle r = new Rectangle(0, 1650, 842000, 595000);
adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, r, pageAdjust);
ByteArrayOutputStream os = new ByteArrayOutputStream();
pdfdoc.outputTrailer(os);
Assert.assertTrue(os.toString(StandardCharsets.UTF_8.name()).contains("/Fields []"));
doc.close();
}
@Test
public void testAnnotNoField() throws Exception {
PDFDocument pdfdoc = new PDFDocument("");
PDFPage pdfpage = getPDFPage(pdfdoc);
pdfpage.setDocument(pdfdoc);
pdfpage.setObjectNumber(1);
PDFBoxAdapter adapter = new PDFBoxAdapter(pdfpage, new HashMap<>(), new HashMap<Integer, PDFArray>());
PDDocument doc = load(ACCESSIBLERADIOBUTTONS);
doc.getDocumentCatalog().getAcroForm().getCOSObject().removeItem(COSName.FIELDS);
PDPage page = doc.getPage(0);
AffineTransform pageAdjust = new AffineTransform();
Rectangle r = new Rectangle(0, 1650, 842000, 595000);
adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, r, pageAdjust);
ByteArrayOutputStream os = new ByteArrayOutputStream();
pdfdoc.outputTrailer(os);
Assert.assertTrue(os.toString(StandardCharsets.UTF_8.name()).contains("/Fields []"));
doc.close();
}
@Test
public void testLink() throws Exception {
PDFDocument pdfdoc = new PDFDocument("");
PDFPage pdfpage = getPDFPage(pdfdoc);
pdfpage.setDocument(pdfdoc);
pdfpage.setObjectNumber(1);
Map<Integer, PDFArray> pageNumbers = new HashMap<Integer, PDFArray>();
PDFBoxAdapter adapter = new PDFBoxAdapter(pdfpage, new HashMap<>(), pageNumbers);
PDDocument doc = load(LINK);
PDPage page = doc.getPage(0);
AffineTransform pageAdjust = new AffineTransform();
Rectangle r = new Rectangle(0, 1650, 842000, 595000);
String stream = (String) adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, r, pageAdjust);
Assert.assertTrue(stream.contains("/Link <</MCID 5 >>BDC"));
assertEquals(pageNumbers.size(), 4);
PDFAnnotList annots = (PDFAnnotList) pdfpage.get("Annots");
assertEquals(annots.toPDFString(), "[\n1 0 R\n2 0 R\n]");
doc.close();
}
@Test
public void testXform() throws Exception {
PDFDocument pdfdoc = new PDFDocument("");
pdfdoc.getFilterMap().put(PDFFilterList.DEFAULT_FILTER, Collections.singletonList("null"));
pdfdoc.setMergeFontsEnabled(true);
PDFPage pdfpage = getPDFPage(pdfdoc);
pdfpage.setDocument(pdfdoc);
pdfpage.setObjectNumber(1);
Map<Integer, PDFArray> pageNumbers = new HashMap<Integer, PDFArray>();
PDFBoxAdapter adapter = new PDFBoxAdapter(pdfpage, new HashMap<>(), pageNumbers);
PDDocument doc = load(XFORM);
PDPage page = doc.getPage(0);
AffineTransform pageAdjust = new AffineTransform();
Rectangle r = new Rectangle(0, 1650, 842000, 595000);
adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, new FontInfo(), r, pageAdjust);
doc.close();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
pdfdoc.output(bos);
Assert.assertFalse(bos.toString(StandardCharsets.UTF_8.name()).contains("/W 5 /H 5 /BPC 8 /CS /RGB ID ÿÿÿ"));
}
@Test
public void testPSPDFGraphics2D() throws Exception {
ByteArrayOutputStream stream = pdfToPS(IMAGE);
assertEquals(countString(stream.toString(StandardCharsets.UTF_8.name()), "%AXGBeginBitmap:"), 1);
pdfToPS(CFF1);
pdfToPS(CFF2);
pdfToPS(CFF3);
pdfToPS(TTCID1);
pdfToPS(TTCID2);
pdfToPS(TTSubset1);
pdfToPS(TTSubset2);
pdfToPS(TTSubset3);
pdfToPS(TTSubset5);
stream = pdfToPS(CFFCID1);
assertEquals(countString(stream.toString(StandardCharsets.UTF_8.name()), "%AXGBeginBitmap:"), 2);
pdfToPS(CFFCID2);
pdfToPS(Type1Subset1);
pdfToPS(Type1Subset2);
pdfToPS(Type1Subset3);
pdfToPS(Type1Subset4);
pdfToPS(ROTATE);
pdfToPS(LINK);
pdfToPS(LOOP);
stream = pdfToPS(LIBREOFFICE);
Assert.assertTrue(stream.toString(StandardCharsets.UTF_8.name()).contains("/MaskColor [ 255 255 255 ]"));
}
private int countString(String s, String value) {
return s.split(value).length - 1;
}
@Test
public void testPDFToPDF() throws IOException {
FontInfo fi = new FontInfo();
writeText(fi, CFF1);
writeText(fi, CFF2);
writeText(fi, CFF3);
writeText(fi, CFFCID1);
writeText(fi, CFFCID2);
writeText(fi, IMAGE);
writeText(fi, LINK);
writeText(fi, ROTATE);
writeText(fi, SHADING);
writeText(fi, TTCID1);
writeText(fi, TTCID2);
writeText(fi, TTSubset1);
writeText(fi, TTSubset2);
writeText(fi, TTSubset3);
writeText(fi, TTSubset5);
writeText(fi, Type1Subset1);
writeText(fi, Type1Subset2);
writeText(fi, Type1Subset3);
writeText(fi, Type1Subset4);
writeText(fi, LOOP);
}
protected static ByteArrayOutputStream pdfToPS(String pdf) throws IOException, ImageException {
return pdfToPS(pdf, null);
}
protected static ByteArrayOutputStream pdfToPS(String pdf, Map<String, Object> hints)
throws IOException, ImageException {
ImageConverterPDF2G2D i = new ImageConverterPDF2G2D();
ImageInfo imgi = new ImageInfo(pdf, "b");
try (PDDocument doc = load(pdf)) {
org.apache.xmlgraphics.image.loader.Image img = new ImagePDF(imgi, doc);
ImageGraphics2D ig = (ImageGraphics2D) i.convert(img, hints);
GeneralGraphics2DImagePainter g = (GeneralGraphics2DImagePainter) ig.getGraphics2DImagePainter();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
PSPDFGraphics2D g2d = (PSPDFGraphics2D) g.getGraphics(true, new FOPPSGeneratorImpl(stream));
Rectangle2D rect = new Rectangle2D.Float(0, 0, 100, 100);
GraphicContext gc = new GraphicContext();
g2d.setGraphicContext(gc);
ig.getGraphics2DImagePainter().paint(g2d, rect);
return stream;
}
}
@Test
public void testSmask() throws IOException, ImageException {
ByteArrayOutputStream ps = pdfToPS(SMASK);
Assert.assertTrue(ps.toString(StandardCharsets.UTF_8.name()).contains("/Pattern"));
Assert.assertTrue(ps.toString(StandardCharsets.UTF_8.name()).contains("{<\nf1f1f1"));
}
@Test
public void testPCL() {
UnsupportedOperationException ex = Assert.assertThrows(UnsupportedOperationException.class, () ->
pdfToPCL(SHADING));
Assert.assertTrue(ex.getMessage().contains("Clipping is not supported."));
}
private void pdfToPCL(String pdf) throws IOException, ImageException {
try (PDDocument doc = load(pdf)) {
pdfToPCL(doc, pdf);
}
}
private void pdfToPCL(PDDocument doc, String pdf) throws IOException, ImageException {
ImageConverterPDF2G2D i = new ImageConverterPDF2G2D();
ImageInfo imgi = new ImageInfo(pdf, "b");
org.apache.xmlgraphics.image.loader.Image img = new ImagePDF(imgi, doc);
ImageGraphics2D ig = (ImageGraphics2D) i.convert(img, null);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
PCLGraphics2D g2d = new PCLGraphics2D(new PCLGenerator(stream));
Rectangle2D rect = new Rectangle2D.Float(0, 0, 100, 100);
GraphicContext gc = new GraphicContext();
g2d.setGraphicContext(gc);
ig.getGraphics2DImagePainter().paint(g2d, rect);
}
static class FOPPSGeneratorImpl extends PSGenerator implements PSDocumentHandler.FOPPSGenerator {
public FOPPSGeneratorImpl(OutputStream out) {
super(out);
}
public PSDocumentHandler getHandler() {
PSDocumentHandler handler = mock(PSDocumentHandler.class);
PSRenderingUtil util = mock(PSRenderingUtil.class);
when(util.isOptimizeResources()).thenReturn(false);
when(handler.getPSUtil()).thenReturn(util);
FOUserAgent mockedAgent = mock(FOUserAgent.class);
when(handler.getUserAgent()).thenReturn(mockedAgent);
when(mockedAgent.getTargetResolution()).thenReturn(72f);
when(handler.getFormForImage(any(String.class))).thenReturn(new PSImageFormResource(0, ""));
return handler;
}
public BufferedOutputStream getTempStream(URI uri) throws IOException {
return new BufferedOutputStream(new ByteArrayOutputStream());
}
public Map<Integer, URI> getImages() {
return new HashMap<Integer, URI>();
}
}
private void loadPage(PDFDocument pdfdoc, String src) throws IOException {
PDDocument doc = load(src);
loadPage(pdfdoc, doc, new Rectangle());
}
private PDFPage loadPage(PDFDocument pdfdoc, PDDocument doc, Rectangle destRect) throws IOException {
PDFPage pdfpage = getPDFPage(pdfdoc);
createStreamFromPDFBoxPage(pdfpage, pdfdoc, doc, destRect, new AffineTransform());
return pdfpage;
}
private Object createStreamFromPDFBoxPage(PDFPage pdfpage, PDFDocument pdfdoc, PDDocument doc,
Rectangle destRect, AffineTransform generatorAT) throws IOException {
pdfdoc.assignObjectNumber(pdfpage);
pdfpage.setDocument(pdfdoc);
PDFBoxAdapter adapter = new PDFBoxAdapter(pdfpage, new HashMap<>(), new HashMap<Integer, PDFArray>());
PDPage page = doc.getPage(0);
AffineTransform pageAdjust = new AffineTransform();
Object object = adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, destRect, generatorAT);
doc.close();
return object;
}
@Test
public void testPDFBoxImageHandler() throws Exception {
ImageInfo imgi = new ImageInfo("a", "b");
PDDocument doc = load(SHADING);
ImagePDF img = new ImagePDF(imgi, doc);
PDFDocument pdfdoc = new PDFDocument("");
PDFPage pdfpage = getPDFPage(pdfdoc);
pdfpage.setDocument(pdfdoc);
PDFGState g = new PDFGState();
pdfdoc.assignObjectNumber(g);
pdfpage.addGState(g);
PDFContentGenerator con = new PDFContentGenerator(pdfdoc, null, null);
FOUserAgent mockedAgent = mock(FOUserAgent.class);
when(mockedAgent.isAccessibilityEnabled()).thenReturn(false);
when(mockedAgent.getPDFObjectCache()).thenReturn(new SoftMapCache(true));
PDFRenderingContext c = new PDFRenderingContext(mockedAgent, con, pdfpage, null);
c.setPageNumbers(new HashMap<Integer, PDFArray>());
new PDFBoxImageHandler().handleImage(c, img, new Rectangle(0, 0, 100, 100));
PDFResources res = c.getPage().getPDFResources();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
res.output(bos);
Assert.assertTrue(bos.toString(StandardCharsets.UTF_8.name()).contains("/ExtGState << /GS1"));
}
@Test
public void testPDFCache() throws IOException {
LoadPDFWithCache loadPDFWithCache = new LoadPDFWithCache();
loadPDFWithCache.run(LOOP);
Object item = loadPDFWithCache.pdfCache.values().iterator().next();
assertEquals(item.getClass(), PDFStream.class);
item = loadPDFWithCache.pdfCache.keySet().iterator().next();
assertEquals(item.getClass(), Integer.class);
assertEquals(loadPDFWithCache.pdfCache.size(), 12);
Iterator<Object> iterator = loadPDFWithCache.objectCachePerFile.values().iterator();
iterator.next();
item = iterator.next();
assertEquals(item.getClass(), PDFDictionary.class);
item = loadPDFWithCache.objectCachePerFile.keySet().iterator().next();
assertEquals(item.getClass(), String.class);
assertEquals(loadPDFWithCache.objectCachePerFile.size(), 46);
}
@Test
public void testPDFCache2() throws IOException {
LoadPDFWithCache loadPDFWithCache = new LoadPDFWithCache();
String stream = loadPDFWithCache.run(LOOP);
String cachedStream = (String) loadPDFWithCache.objectCachePerFile.get(LOOP);
Assert.assertTrue(cachedStream.contains("EMC"));
Assert.assertTrue(stream.endsWith(cachedStream));
}
private static class LoadPDFWithCache {
private PDFDocument pdfdoc = new PDFDocument("");
private Map<Object, Object> pdfCache = new LinkedHashMap<Object, Object>();
private Map<Object, Object> objectCachePerFile = new LinkedHashMap<Object, Object>();
private String run(String pdf) throws IOException {
PDFPage pdfpage = getPDFPage(pdfdoc);
pdfdoc.assignObjectNumber(pdfpage);
pdfpage.setDocument(pdfdoc);
PDFBoxAdapter adapter = new PDFBoxAdapter(
pdfpage, objectCachePerFile, null, new HashMap<Integer, PDFArray>(), pdfCache);
PDDocument doc = load(pdf);
PDPage page = doc.getPage(0);
String stream = (String) adapter.createStreamFromPDFBoxPage(
doc, page, pdf, new AffineTransform(), null, new Rectangle(), new AffineTransform());
doc.close();
return stream;
}
}
@Test
public void testPDFSize() throws IOException {
LoadPDFWithCache loadPDFWithCache = new LoadPDFWithCache();
loadPDFWithCache.run(ANNOT);
loadPDFWithCache.run(ANNOT);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
loadPDFWithCache.pdfdoc.output(bos);
assertEquals(loadPDFWithCache.pdfCache.size(), 2);
Assert.assertTrue(bos.size() <= 6418);
}
@Test
public void testErrorMsgToPS() throws IOException {
PDDocument doc = new PDDocument();
PDPage page = new PDPage();
page.setContents(new PDStream(doc, new ByteArrayInputStream("<".getBytes(StandardCharsets.UTF_8))));
doc.addPage(page);
RuntimeException ex = Assert.assertThrows(RuntimeException.class, () -> pdfToPCL(doc, ERROR));
Assert.assertTrue(ex.getMessage().startsWith("Error while painting PDF page: " + ERROR));
}
@Test
public void testErrorMsgToPDF() {
PDFDocument pdfdoc = new PDFDocument("");
PDFContentGenerator contentGenerator = new PDFContentGenerator(pdfdoc, null, null);
PDFRenderingContext context = new PDFRenderingContext(null, contentGenerator, null, null);
ImagePDF imagePDF = new ImagePDF(new ImageInfo(ERROR, null), null);
RuntimeException ex = Assert.assertThrows(RuntimeException.class, () ->
new PDFBoxImageHandler().handleImage(context, imagePDF, null));
Assert.assertTrue(ex.getMessage().startsWith("Error on PDF page: " + ERROR));
}
@Test
public void testNoPageResource() throws IOException {
PDDocument doc = load(CFF1);
PDPage page = doc.getPage(0);
page.setResources(null);
AffineTransform pageAdjust = new AffineTransform();
getPDFBoxAdapter(false, false)
.createStreamFromPDFBoxPage(doc, page, CFF1, pageAdjust, new FontInfo(), new Rectangle(), pageAdjust);
doc.close();
}
@Test
public void testPDFBoxImageHandlerAccessibilityEnabled() throws Exception {
ImageInfo imgi = new ImageInfo("a", "b");
PDDocument doc = load(SHADING);
ImagePDF img = new ImagePDF(imgi, doc);
PDFDocument pdfdoc = new PDFDocument("");
PDFPage pdfpage = getPDFPage(pdfdoc);
pdfpage.setDocument(pdfdoc);
PDFContentGenerator con = new PDFContentGenerator(pdfdoc, null, null);
FOUserAgent mockedAgent = mock(FOUserAgent.class);
when(mockedAgent.isAccessibilityEnabled()).thenReturn(true);
when(mockedAgent.getPDFObjectCache()).thenReturn(new SoftMapCache(true));
PDFRenderingContext c = new PDFRenderingContext(mockedAgent, con, pdfpage, null);
c.setPageNumbers(new HashMap<Integer, PDFArray>());
new PDFBoxImageHandler().handleImage(c, img, new Rectangle());
}
@Test
public void testMergeFontsAndFormXObject() throws IOException {
PDDocument doc = load(IMAGE);
PDPage page = doc.getPage(0);
AffineTransform pageAdjust = new AffineTransform();
RuntimeException ex = Assert.assertThrows(RuntimeException.class, () ->
getPDFBoxAdapter(true, true).createStreamFromPDFBoxPage(
doc, page, IMAGE, pageAdjust, new FontInfo(), new Rectangle(), pageAdjust));
doc.close();
assertEquals(ex.getMessage(), "merge-fonts and form-xobject can't both be enabled");
}
@Test
public void testFormXObject() throws IOException {
PDDocument doc = load(IMAGE);
PDPage page = doc.getPage(0);
AffineTransform pageAdjust = new AffineTransform();
PDFFormXObject formXObject = (PDFFormXObject) getPDFBoxAdapter(false, true)
.createStreamFromPDFBoxPage(doc, page, IMAGE, pageAdjust, new FontInfo(), new Rectangle(), pageAdjust);
doc.close();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
formXObject.output(bos);
Assert.assertTrue(bos.toString(StandardCharsets.UTF_8.name()).contains("/Type /XObject"));
}
@Test
public void testRewriteOfForms() throws IOException {
Assert.assertTrue(getPDFToPDF(ACCESSIBLERADIOBUTTONS).contains("/F15106079 12 Tf"));
}
private String getPDFToPDF(String pdf) throws IOException {
PDFDocument pdfdoc = new PDFDocument("");
PDFPage pdfpage = getPDFPage(pdfdoc);
pdfpage.setDocument(pdfdoc);
pdfpage.setObjectNumber(1);
PDFBoxAdapter adapter = new PDFBoxAdapter(pdfpage, new HashMap<>(), new HashMap<Integer, PDFArray>());
PDDocument doc = load(pdf);
PDPage page = doc.getPage(0);
AffineTransform pageAdjust = new AffineTransform();
Rectangle r = new Rectangle(0, 1650, 842000, 595000);
adapter.createStreamFromPDFBoxPage(doc, page, "key", pageAdjust, null, r, pageAdjust);
ByteArrayOutputStream os = new ByteArrayOutputStream();
Map<String, List<String>> filterMap = new HashMap<String, List<String>>();
List<String> filterList = new ArrayList<String>();
filterList.add("null");
filterMap.put("default", filterList);
pdfdoc.setFilterMap(filterMap);
pdfdoc.output(os);
doc.close();
return os.toString(StandardCharsets.UTF_8.name());
}
@Test
public void testRewriteOfPatternForms() throws IOException {
Assert.assertTrue(getPDFToPDF(PATTERN).contains("/R1106079 gs\n1 1 m"));
}
@Test
public void testDCTEncryption() throws IOException {
PDFDocument pdfdoc = new PDFDocument("");
pdfdoc.setEncryption(new PDFEncryptionParams());
loadPage(pdfdoc, IMAGE);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
pdfdoc.output(bos);
Assert.assertTrue(bos.toString(StandardCharsets.UTF_8.name()).contains("/Filter /DCTDecode"));
}
@Test
public void testCmapLengthInName() throws IOException {
FontInfo fi = new FontInfo();
String msg = writeText(fi, TTSubset3);
Assert.assertTrue(msg, msg.contains("/ArialMT_TrueTypecidcmap1"));
}
@Test
public void testMapChar() throws Exception {
FontInfo fi = new FontInfo();
writeText(fi, TTSubset6);
String msg = writeText(fi, TTSubset7);
Assert.assertTrue(msg, msg.contains("( )Tj"));
}
@Test
public void testReorderGlyphs() throws IOException {
FontInfo fontInfo = new FontInfo();
writeText(fontInfo, TTSubset8);
writeText(fontInfo, TTSubset9);
List<Integer> compositeList = new ArrayList<>();
for (Typeface font : fontInfo.getUsedFonts().values()) {
InputStream inputStream = ((CustomFont) font).getInputStream();
TTFParser parser = new TTFParser(true);
TrueTypeFont trueTypeFont = parser.parse(new RandomAccessReadBuffer(inputStream));
int i = 0;
for (int gid = 0; gid < trueTypeFont.getNumberOfGlyphs(); gid++) {
GlyphData glyphData = trueTypeFont.getGlyph().getGlyph(gid);
if (glyphData != null && glyphData.getDescription().isComposite()) {
compositeList.add(i);
}
i++;
}
}
assertEquals(compositeList, Arrays.asList(18, 19, 39, 42, 62, 63, 29));
}
@Test
public void testFormRotated() throws IOException {
PDFDocument pdfdoc = new PDFDocument("");
loadPage(pdfdoc, FORMROTATED);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
pdfdoc.output(bos);
Assert.assertFalse(bos.toString(StandardCharsets.UTF_8.name()).contains("/R 90"));
}
@Test
public void testCFFSubrs() throws Exception {
FontInfo fontInfo = new FontInfo();
writeText(fontInfo, CFFSUBRS);
writeText(fontInfo, CFFSUBRS2);
InputStream is = null;
for (Typeface font : fontInfo.getUsedFonts().values()) {
if ("AllianzNeo-Bold".equals(font.getEmbedFontName())) {
is = ((CustomFont) font).getInputStream();
}
}
CFFType1Font font = (CFFType1Font) new CFFParser().parse(new RandomAccessReadBuffer(is)).get(0);
byte[][] indexData = (byte[][]) font.getPrivateDict().get("Subrs");
assertEquals(indexData.length, 183);
}
@Test
public void testCFFSubrsCharset() throws Exception {
FontInfo fontInfo = new FontInfo();
writeText(fontInfo, CFFSUBRS4);
writeText(fontInfo, CFFSUBRS3);
CustomFont typeface = (CustomFont) fontInfo.getUsedFonts().get("AllianzNeo-Light_Type1");
InputStream is = typeface.getInputStream();
CFFType1Font font = (CFFType1Font) new CFFParser().parse(new RandomAccessReadBuffer(is)).get(0);
CFFCharset charset = font.getCharset();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append(charset.getNameForGID(i)).append(" ");
}
assertEquals(sb.toString(), ".notdef uni00A0 trademark uni003B uniFB00 ");
}
private String getAnnotationsID(PDFPage page) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
page.getAnnotations().output(os);
return os.toString(StandardCharsets.UTF_8.name()).split("\n")[1];
}
private PDFPage drawAnnot(PDFDocument pdfDoc, Map<String, Object> usedFields, String pdf) throws Exception {
PDFBoxAdapter adapter = getPDFBoxAdapter(pdfDoc, false, false, true, usedFields);
PDDocument input = load(pdf);
PDPage srcPage = input.getPage(0);
AffineTransform at = new AffineTransform();
Rectangle r = new Rectangle(0, 1650, 842000, 595000);
adapter.createStreamFromPDFBoxPage(input, srcPage, "key", at, null, r, at);
input.close();
return adapter.getTargetPage();
}
@Test
public void testMergeAnnotsTree() throws Exception {
PDFDocument pdfDoc = new PDFDocument("");
Map<String, Object> usedFields = new HashMap<>();
PDFPage page1 = drawAnnot(pdfDoc, usedFields, ANNOT2);
PDFPage page2 = drawAnnot(pdfDoc, usedFields, ANNOT3);
ByteArrayOutputStream os = new ByteArrayOutputStream();
pdfDoc.outputHeader(os);
pdfDoc.output(os);
pdfDoc.outputTrailer(os);
PDDocument out = Loader.loadPDF(os.toByteArray());
out.close();
String id1 = "30 0 R";
assertEquals(getAnnotationsID(page1), id1);
String id2 = "33 0 R";
assertEquals(getAnnotationsID(page2), id2);
String outStr = os.toString(StandardCharsets.UTF_8.name()).replaceAll("\\s\\s/", "/");
Assert.assertTrue(outStr.contains("<< /Kids [32 0 R] /T ([Signer1) >>"));
Assert.assertTrue(outStr.contains("<<\n"
+ "/Kids [" + id1 + " " + id2 + "]\n"
+ "/Parent 34 0 R\n"
+ "/T (Fullname1)\n"
+ ">>"));
}
@Test
public void testPreservePropertyNames() throws Exception {
PDDocument doc = load(CFF1);
COSDictionary properties = new COSDictionary();
properties.setItem(COSName.S, COSName.S);
doc.getPage(0).getResources().getCOSObject().setItem(COSName.PROPERTIES, properties);
PDFDocument pdfdoc = new PDFDocument("");
PDFPage page = loadPage(pdfdoc, doc, new Rectangle());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
page.getPDFResources().output(bos);
Assert.assertTrue(bos.toString(StandardCharsets.UTF_8.name()).contains("/Properties << /S /S >>"));
}
@Test
public void testPatternMatrix() throws Exception {
PDDocument doc = load(SHADING);
PDFDocument pdfdoc = new PDFDocument("");
Rectangle destRect = new Rectangle(0, 1650, 274818, 174879);
loadPage(pdfdoc, doc, destRect);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
pdfdoc.output(bos);
String outStr = removeWhiteSpace(bos);
Assert.assertTrue(outStr.contains("<<\n"
+ "/Type /Pattern\n"
+ "/PatternType 2\n"
+ "/Shading 2 0 R\n"
+ "/Matrix [53.8858833313 0 0 -26.4968185425 72.7459411621 -20.8601989746]\n"
+ ">>"));
}
@Test
public void testPatternMatrixWithPageAdjust() throws Exception {
PDDocument doc = load(SHADING);
PDFDocument pdfdoc = new PDFDocument("");
Rectangle destRect = new Rectangle(0, 1650, 274818, 174879);
PDFPage pdfpage = getPDFPage(pdfdoc);
AffineTransform generatorAT = AffineTransform.getTranslateInstance(0, 100);
createStreamFromPDFBoxPage(pdfpage, pdfdoc, doc, destRect, generatorAT);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
pdfdoc.output(bos);
String outStr = removeWhiteSpace(bos);
Assert.assertTrue(outStr.contains("<<\n"
+ "/Type /Pattern\n/PatternType 2\n/Shading 2 0 R\n"
+ "/Matrix [53.8858833313 0 0 -26.4968185425 72.7459411621 -120.8601837158]\n"
+ ">>"));
}
@Test
public void testPatternMatrixFormXObject() throws Exception {
PDDocument doc = load(SHADING);
PDFDocument pdfdoc = new PDFDocument("");
pdfdoc.setFormXObjectEnabled(true);
Rectangle destRect = new Rectangle(0, 1650, 274818, 174879);
loadPage(pdfdoc, doc, destRect);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
pdfdoc.output(bos);
String outStr = removeWhiteSpace(bos);
Assert.assertTrue(outStr.contains("/Pattern << /Pa1 12 0 R /Pa2 13 0 R >>"));
Assert.assertTrue(outStr.contains("<<\n"
+ "/Type /Pattern\n"
+ "/PatternType 2\n"
+ "/Shading 2 0 R\n"
+ "/Matrix [120 0 0 -120 162 705]\n"
+ ">>"));
}
@Test
public void testGetFormXObject() throws Exception {
defaultCreateStreamFromPDFBoxPage(0, 0.083, 0, -100, 0, 0.003, -3.424);
defaultCreateStreamFromPDFBoxPage(90, 0, 0.003, -51.712, -0.083, 0, 49.287);
defaultCreateStreamFromPDFBoxPage(180, -0.083, 0, 1, 0, -0.003, 0.999);
defaultCreateStreamFromPDFBoxPage(270, 0, -0.003, -47.287, 0.083, 0, -51.712);
}
private void defaultCreateStreamFromPDFBoxPage(int rotation, double scaleX, double shearX,
double translateX, double shearY, double scaleY,
double translateY) throws IOException {
PDFDocument pdfdoc = new PDFDocument("");
pdfdoc.setFormXObjectEnabled(true);
PDFPage pdfPage = getPDFPage(pdfdoc);
PDDocument pdDoc = load(SHADING);
PDPage page = pdDoc.getPage(0);
page.setCropBox(new PDRectangle(600, 500, 1200, 800));
page.setRotation(rotation);
PDFFormXObject form = (PDFFormXObject) createStreamFromPDFBoxPage(pdfPage, pdfdoc, pdDoc,
new Rectangle(0, 0, 274818, 174879), new AffineTransform());
AffineTransform at = form.getMatrix();
String message = "Value must be calculated based on the rotation";
assertEquals(message, scaleX, at.getScaleX(), 0.001); //m00
assertEquals(message, shearX, at.getShearX(), 0.001); //m01
assertEquals(message, translateX, at.getTranslateX(), 0.001); //m02
assertEquals(message, shearY, at.getShearY(), 0.001); //m10
assertEquals(message, scaleY, at.getScaleY(), 0.001); //m11
assertEquals(message, translateY, at.getTranslateY(), 0.001); //m12
}
private String removeWhiteSpace(ByteArrayOutputStream bos) throws Exception {
return bos.toString(StandardCharsets.UTF_8.name()).replaceAll("\\s\\s/", "/");
}
@Test
public void testAscenderDoesntMatch() throws IOException {
FontInfo fi = new FontInfo();
writeText(fi, TTSubset6);
String msg = writeText(fi, TTSubset7);
Assert.assertTrue(msg, msg.contains("/C2_0745125721 12 Tf"));
}
@Test
public void testSoftMask() throws Exception {
ByteArrayOutputStream bos = pdfToPS(SOFTMASK);
String output = bos.toString(StandardCharsets.UTF_8.name());
assertEquals(output.split("BeginBitmap").length, 3);
Assert.assertTrue(output.contains("/ImageMatrix [148 0 0 78 0 0]"));
Assert.assertTrue(output.contains("/ImageMatrix [192 0 0 192 0 0]"));
}
@Test
public void testMergeMacFont() throws IOException {
String msg = writeText(new FontInfo(), TTSubset6);
Assert.assertTrue(msg, msg.contains("/Calibri_TrueTypemac"));
}
}