PDFWriter.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it). 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package org.openspcoop2.utils.pdf;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationFileAttachment;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDXFAResource;
import org.openspcoop2.utils.Utilities;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.mime.MimeTypes;
import org.openspcoop2.utils.resources.FileSystemUtilities;

/**
* PDFWriter
*
* @author Andrea Poli (apoli@link.it)
* @author $Author$
* @version $Rev$, $Date$
*/
public class PDFWriter extends AbstractPDFCore {
	
	private static final String CONTENT_UNDEFINED = "content undefined";
	private static final String FILENAME_UNDEFINED = "fileName undefined";
	
	public static void writeTo(PDDocument doc, OutputStream os) throws UtilsException {
		PDFWriter writer = new PDFWriter(doc);
		writer.writeTo(os);
	}
	public static void writeTo(PDDocument doc, File file) throws UtilsException {
		PDFWriter writer = new PDFWriter(doc);
		writer.writeTo(file);
	}
	
	public PDFWriter(PDDocument doc) throws UtilsException {
		super(doc);
	}
	public PDFWriter(byte[] content) throws UtilsException {
		super(content, false);
	}
	public PDFWriter(File doc) throws UtilsException {
		super(doc, false);
	}
	public PDFWriter(InputStream is) throws UtilsException {
		super(is, false);
	}

	private void checkFile(File file) throws UtilsException {
		if(file==null) {
			throw new UtilsException("File undefined");
		}
	}
	
	public void writeTo(OutputStream os) throws UtilsException {
		if(os==null) {
			throw new UtilsException("OutputStream undefined");
		}
		try {
			this.document.save(os);
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	public void writeTo(File file) throws UtilsException {
		if(file==null) {
			throw new UtilsException("OutputFile undefined");
		}
		try {
			this.document.save(file);
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	
	public void addEmbeddedFile(File file) throws UtilsException {
		addEmbeddedFile(file, null);
	}
	public void addEmbeddedFile(File file, String mediaType) throws UtilsException {
		checkFile(file);
		try(FileInputStream fin = new FileInputStream(file)) {
			this.addEmbeddedFile(file.getName(), fin, (int) file.length(), mediaType);
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	public void addEmbeddedFile(String fileName, byte[] content) throws UtilsException {
		addEmbeddedFile(fileName, content, null);
	}
	public void addEmbeddedFile(String fileName, byte[] content, String mediaType) throws UtilsException {
		if(content==null) {
			throw new UtilsException(CONTENT_UNDEFINED);
		}
		try(ByteArrayInputStream bin = new ByteArrayInputStream(content)) {
			this.addEmbeddedFile(fileName, bin, content.length, mediaType);
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	public void addEmbeddedFile(String fileName, InputStream content) throws UtilsException {
		addEmbeddedFile(fileName, content, -1);
	}
	public void addEmbeddedFile(String fileName, InputStream content, int size) throws UtilsException {
		addEmbeddedFile(fileName, content, size, null);
	}
	public void addEmbeddedFile(String fileName, InputStream content, String mediaType) throws UtilsException {
		addEmbeddedFile(fileName, content, -1, mediaType);
	}
	public void addEmbeddedFile(String fileName, InputStream content, int size, String mediaType) throws UtilsException {
		
		if(fileName==null) {
			throw new UtilsException(FILENAME_UNDEFINED);
		}
		if(content==null) {
			throw new UtilsException(CONTENT_UNDEFINED);
		}
		
		try {
			if(mediaType==null) {
				mediaType = MimeTypes.getInstance().getMimeType(new File(fileName));
			}
			
			checkDocumentCatalog();
			
			PDDocumentNameDictionary names = this.document.getDocumentCatalog().getNames();
			if(names==null) {
				names = new PDDocumentNameDictionary( this.document.getDocumentCatalog() );
				this.document.getDocumentCatalog().setNames( names );
			}
			
			PDEmbeddedFilesNameTreeNode efTree = names.getEmbeddedFiles();
			if(efTree==null) {
				efTree = new PDEmbeddedFilesNameTreeNode();
				names.setEmbeddedFiles( efTree );
			}
	
			PDComplexFileSpecification fs = new PDComplexFileSpecification();
			fs.setFile(fileName);
			PDEmbeddedFile ef = new PDEmbeddedFile(this.document, content );
			ef.setSubtype( mediaType );
			if(size>0) {
				ef.setSize( size );
			}
			ef.setCreationDate( new GregorianCalendar() );
			fs.setEmbeddedFile( ef );

			Map<String,PDComplexFileSpecification> efMapOrig = efTree.getNames();
			Map<String,PDComplexFileSpecification> efMap = new HashMap<>();
			if(efMapOrig!=null && !efMapOrig.isEmpty()) {
				for (Map.Entry<String,PDComplexFileSpecification> entry : efMapOrig.entrySet()) {
					String fName = entry.getKey();
					PDComplexFileSpecification orig = entry.getValue();
					efMap.put(fName, orig);
				}
			}
			efMap.put( fileName, fs );
			efTree.setNames( efMap );
			
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	
	
	
	public void addEmbeddedFileAsKid(File file) throws UtilsException {
		addEmbeddedFileAsKid(file, null);
	}
	public void addEmbeddedFileAsKid(File file, String mediaType) throws UtilsException {
		checkFile(file);
		try(FileInputStream fin = new FileInputStream(file)) {
			this.addEmbeddedFileAsKid(file.getName(), fin, (int) file.length(), mediaType);
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	public void addEmbeddedFileAsKid(String fileName, byte[] content) throws UtilsException {
		addEmbeddedFileAsKid(fileName, content, null);
	}
	public void addEmbeddedFileAsKid(String fileName, byte[] content, String mediaType) throws UtilsException {
		if(content==null) {
			throw new UtilsException(CONTENT_UNDEFINED);
		}
		try(ByteArrayInputStream bin = new ByteArrayInputStream(content)) {
			this.addEmbeddedFileAsKid(fileName, bin, content.length, mediaType);
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	public void addEmbeddedFileAsKid(String fileName, InputStream content) throws UtilsException {
		addEmbeddedFileAsKid(fileName, content, -1);
	}
	public void addEmbeddedFileAsKid(String fileName, InputStream content, int size) throws UtilsException {
		addEmbeddedFileAsKid(fileName, content, size, null);
	}
	public void addEmbeddedFileAsKid(String fileName, InputStream content, String mediaType) throws UtilsException {
		addEmbeddedFileAsKid(fileName, content, -1, mediaType);
	}
	public void addEmbeddedFileAsKid(String fileName, InputStream content, int size, String mediaType) throws UtilsException {
		
		if(fileName==null) {
			throw new UtilsException(FILENAME_UNDEFINED);
		}
		if(content==null) {
			throw new UtilsException(CONTENT_UNDEFINED);
		}
		
		try {
			if(mediaType==null) {
				mediaType = MimeTypes.getInstance().getMimeType(new File(fileName));
			}
			
			checkDocumentCatalog();
			
			PDDocumentNameDictionary names = this.document.getDocumentCatalog().getNames();
			if(names==null) {
				names = new PDDocumentNameDictionary( this.document.getDocumentCatalog() );
				this.document.getDocumentCatalog().setNames( names );
			}
			
			PDEmbeddedFilesNameTreeNode efTree = names.getEmbeddedFiles();
			if(efTree==null) {
				efTree = new PDEmbeddedFilesNameTreeNode();
				names.setEmbeddedFiles( efTree );
			}
	
			List<PDNameTreeNode<PDComplexFileSpecification>> kids = efTree.getKids();
			boolean kidsEmpty = false;
			if(kids==null) {
				kids = new ArrayList<>();
				kidsEmpty = true;
			}
			
			PDComplexFileSpecification fs = new PDComplexFileSpecification();
			fs.setFile(fileName);
			PDEmbeddedFile ef = new PDEmbeddedFile(this.document, content );
			ef.setSubtype( mediaType );
			if(size>0) {
				ef.setSize( size );
			}
			ef.setCreationDate( new GregorianCalendar() );
			fs.setEmbeddedFile( ef );
	
			PDEmbeddedFilesNameTreeNode pdNameTreeeNodeKids = new PDEmbeddedFilesNameTreeNode();
			Map<String,PDComplexFileSpecification> efMap = new HashMap<>();
			efMap.put( fileName, fs );
			pdNameTreeeNodeKids.setNames(efMap);
			kids.add(pdNameTreeeNodeKids);
			
			if(kidsEmpty) {
				efTree.setKids(kids);
			}
			
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	
	
	
	
	
	public void addAnnotationFile(File file) throws UtilsException {
		addAnnotationFile(file, null);
	}
	public void addAnnotationFile(File file, String mediaType) throws UtilsException {
		checkFile(file);
		try(FileInputStream fin = new FileInputStream(file)) {
			this.addAnnotationFile(file.getName(), fin, (int) file.length(), mediaType);
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	public void addAnnotationFile(String fileName, byte[] content) throws UtilsException {
		addAnnotationFile(fileName, content, null);
	}
	public void addAnnotationFile(String fileName, byte[] content, String mediaType) throws UtilsException {
		if(content==null) {
			throw new UtilsException(CONTENT_UNDEFINED);
		}
		try(ByteArrayInputStream bin = new ByteArrayInputStream(content)) {
			this.addAnnotationFile(fileName, bin, content.length, mediaType);
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	public void addAnnotationFile(String fileName, InputStream content) throws UtilsException {
		addAnnotationFile(fileName, content, -1);
	}
	public void addAnnotationFile(String fileName, InputStream content, int size) throws UtilsException {
		addAnnotationFile(fileName, content, size, null);
	}
	public void addAnnotationFile(String fileName, InputStream content, String mediaType) throws UtilsException {
		addAnnotationFile(fileName, content, -1, mediaType);
	}
	public void addAnnotationFile(String fileName, InputStream content, int size, String mediaType) throws UtilsException {
		
		if(fileName==null) {
			throw new UtilsException(FILENAME_UNDEFINED);
		}
		if(content==null) {
			throw new UtilsException(CONTENT_UNDEFINED);
		}
		
		try {
			if(mediaType==null) {
				mediaType = MimeTypes.getInstance().getMimeType(new File(fileName));
			}
			
			checkDocumentCatalog();
			
			if(this.document.getPages()==null || this.document.getNumberOfPages()<=0) {
				throw new UtilsException("DocumentPage not found");
			}
			PDPage page = this.document.getPage(0);
			if(page==null) {
				throw new UtilsException("FirstDocumentPage undefined");
			}
			List<PDAnnotation> annotations = page.getAnnotations();
			if(annotations==null) {
				annotations = new ArrayList<>();
			}
			
			PDComplexFileSpecification fs = new PDComplexFileSpecification();
			fs.setFile(fileName);
			PDEmbeddedFile ef = new PDEmbeddedFile(this.document, content );
			ef.setSubtype( mediaType );
			if(size>0) {
				ef.setSize( size );
			}
			ef.setCreationDate( new GregorianCalendar() );
			fs.setEmbeddedFile( ef );
	
			PDAnnotationFileAttachment fa = new PDAnnotationFileAttachment();
			fa.setFile(fs);
			
			annotations.add(fa);
			
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	
	
	
	
	
	public void setXFAFile(File file, boolean addXfaDatasets) throws UtilsException {
		byte[]content = null;
		try {
			content = FileSystemUtilities.readBytesFromFile(file);
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
		this.setXFAFile(content, addXfaDatasets);
	}
	public void setXFAFile(InputStream content, boolean addXfaDatasets) throws UtilsException {
		this.setXFAFile(Utilities.getAsByteArray(content), addXfaDatasets);
	}
	public void setXFAFile(byte[] content, boolean addXfaDatasets) throws UtilsException {
		
		if(content==null) {
			throw new UtilsException(CONTENT_UNDEFINED);
		}
		
		try {
			
			checkDocumentCatalog();
			
			PDAcroForm pdAcroForm = this.document.getDocumentCatalog().getAcroForm();
			if(pdAcroForm==null) {
				pdAcroForm = new PDAcroForm(this.document);
				this.document.getDocumentCatalog().setAcroForm(pdAcroForm);
			}
			
			if(addXfaDatasets) {
				content = XFADocument.addXfaDatasets(content);
			}

			COSStream base = new COSStream();
			try(OutputStream os = base.createOutputStream()){
				os.write(content);
				os.flush();
			}
			PDXFAResource xfa = new PDXFAResource(base);
			pdAcroForm.setXFA(xfa);
										
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
}