/*
* 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.pdmodel.interactive.digitalsignature.visible;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
/**
* Implementation of {@link PDFTemplateBuilder}. This builds the signature PDF but doesn't keep the
* elements, these are kept in its PDF template structure.
*
* @author Vakhtang Koroghlishvili
*/
public class PDVisibleSigBuilder implements PDFTemplateBuilder
{
private final PDFTemplateStructure pdfStructure;
private static final Log LOG = LogFactory.getLog(PDVisibleSigBuilder.class);
/**
* Constructor, creates PDF template structure.
*/
public PDVisibleSigBuilder()
{
pdfStructure = new PDFTemplateStructure();
LOG.info("PDF Structure has been created");
}
@Override
public void createPage(PDVisibleSignDesigner properties)
{
PDPage page = new PDPage(new PDRectangle(properties.getPageWidth(),
properties.getPageHeight()));
pdfStructure.setPage(page);
LOG.info("PDF page has been created");
}
/**
* Creates a PDDocument and adds the page parameter to it and keeps this as a template in the
* PDF template Structure.
*
* @param page
* @throws IOException
*/
@Override
public void createTemplate(PDPage page) throws IOException
{
PDDocument template = new PDDocument();
template.addPage(page);
pdfStructure.setTemplate(template);
}
@Override
public void createAcroForm(PDDocument template)
{
PDAcroForm theAcroForm = new PDAcroForm(template);
template.getDocumentCatalog().setAcroForm(theAcroForm);
pdfStructure.setAcroForm(theAcroForm);
LOG.info("AcroForm has been created");
}
@Override
public PDFTemplateStructure getStructure()
{
return pdfStructure;
}
@Override
public void createSignatureField(PDAcroForm acroForm) throws IOException
{
PDSignatureField sf = new PDSignatureField(acroForm);
pdfStructure.setSignatureField(sf);
LOG.info("Signature field has been created");
}
@Override
public void createSignature(PDSignatureField pdSignatureField, PDPage page, String signerName)
throws IOException
{
PDSignature pdSignature = new PDSignature();
PDAnnotationWidget widget = pdSignatureField.getWidgets().get(0);
pdSignatureField.setValue(pdSignature);
widget.setPage(page);
page.getAnnotations().add(widget);
if (!signerName.isEmpty())
{
pdSignature.setName(signerName);
}
pdfStructure.setPdSignature(pdSignature);
LOG.info("PDSignature has been created");
}
@Override
public void createAcroFormDictionary(PDAcroForm acroForm, PDSignatureField signatureField)
throws IOException
{
@SuppressWarnings("unchecked")
List<PDField> acroFormFields = acroForm.getFields();
COSDictionary acroFormDict = acroForm.getCOSObject();
acroForm.setSignaturesExist(true);
acroForm.setAppendOnly(true);
acroFormDict.setDirect(true);
acroFormFields.add(signatureField);
acroForm.setDefaultAppearance("/sylfaen 0 Tf 0 g");
pdfStructure.setAcroFormFields(acroFormFields);
pdfStructure.setAcroFormDictionary(acroFormDict);
LOG.info("AcroForm dictionary has been created");
}
@Override
public void createSignatureRectangle(PDSignatureField signatureField,
PDVisibleSignDesigner properties) throws IOException
{
PDRectangle rect = new PDRectangle();
rect.setUpperRightX(properties.getxAxis() + properties.getWidth());
rect.setUpperRightY(properties.getTemplateHeight() - properties.getyAxis());
rect.setLowerLeftY(properties.getTemplateHeight() - properties.getyAxis() -
properties.getHeight());
rect.setLowerLeftX(properties.getxAxis());
signatureField.getWidgets().get(0).setRectangle(rect);
pdfStructure.setSignatureRectangle(rect);
LOG.info("Signature rectangle has been created");
}
/**
* {@inheritDoc }
*
* @deprecated use {@link #createAffineTransform(java.awt.geom.AffineTransform) }
*/
@Override
@Deprecated
public void createAffineTransform(byte[] params)
{
AffineTransform transform = new AffineTransform(params[0], params[1], params[2],
params[3], params[4], params[5]);
pdfStructure.setAffineTransform(transform);
LOG.info("Matrix has been added");
}
@Override
public void createAffineTransform(AffineTransform affineTransform)
{
pdfStructure.setAffineTransform(affineTransform);
LOG.info("Matrix has been added");
}
@Override
public void createProcSetArray()
{
COSArray procSetArr = new COSArray();
procSetArr.add(COSName.getPDFName("PDF"));
procSetArr.add(COSName.getPDFName("Text"));
procSetArr.add(COSName.getPDFName("ImageB"));
procSetArr.add(COSName.getPDFName("ImageC"));
procSetArr.add(COSName.getPDFName("ImageI"));
pdfStructure.setProcSet(procSetArr);
LOG.info("ProcSet array has been created");
}
@Override
public void createSignatureImage(PDDocument template, BufferedImage image) throws IOException
{
pdfStructure.setImage(LosslessFactory.createFromImage(template, image));
LOG.info("Visible Signature Image has been created");
}
@Override
public void createFormatterRectangle(byte[] params)
{
PDRectangle formatterRectangle = new PDRectangle();
formatterRectangle.setUpperRightX(params[0]);
formatterRectangle.setUpperRightY(params[1]);
formatterRectangle.setLowerLeftX(params[2]);
formatterRectangle.setLowerLeftY(params[3]);
pdfStructure.setFormatterRectangle(formatterRectangle);
LOG.info("Formatter rectangle has been created");
}
@Override
public void createHolderFormStream(PDDocument template)
{
PDStream holderForm = new PDStream(template);
pdfStructure.setHolderFormStream(holderForm);
LOG.info("Holder form stream has been created");
}
@Override
public void createHolderFormResources()
{
PDResources holderFormResources = new PDResources();
pdfStructure.setHolderFormResources(holderFormResources);
LOG.info("Holder form resources have been created");
}
@Override
public void createHolderForm(PDResources holderFormResources, PDStream holderFormStream,
PDRectangle formrect)
{
PDFormXObject holderForm = new PDFormXObject(holderFormStream);
holderForm.setResources(holderFormResources);
holderForm.setBBox(formrect);
holderForm.setFormType(1);
pdfStructure.setHolderForm(holderForm);
LOG.info("Holder form has been created");
}
@Override
public void createAppearanceDictionary(PDFormXObject holderForml,
PDSignatureField signatureField) throws IOException
{
PDAppearanceDictionary appearance = new PDAppearanceDictionary();
appearance.getCOSObject().setDirect(true);
PDAppearanceStream appearanceStream = new PDAppearanceStream(holderForml.getCOSObject());
appearance.setNormalAppearance(appearanceStream);
signatureField.getWidgets().get(0).setAppearance(appearance);
pdfStructure.setAppearanceDictionary(appearance);
LOG.info("PDF appearance dictionary has been created");
}
@Override
public void createInnerFormStream(PDDocument template)
{
PDStream innerFormStream = new PDStream(template);
pdfStructure.setInnterFormStream(innerFormStream);
LOG.info("Stream of another form (inner form - it will be inside holder form) " +
"has been created");
}
@Override
public void createInnerFormResource()
{
PDResources innerFormResources = new PDResources();
pdfStructure.setInnerFormResources(innerFormResources);
LOG.info("Resources of another form (inner form - it will be inside holder form)" +
"have been created");
}
@Override
public void createInnerForm(PDResources innerFormResources, PDStream innerFormStream,
PDRectangle formrect)
{
PDFormXObject innerForm = new PDFormXObject(innerFormStream);
innerForm.setResources(innerFormResources);
innerForm.setBBox(formrect);
innerForm.setFormType(1);
pdfStructure.setInnerForm(innerForm);
LOG.info("Another form (inner form - it will be inside holder form) has been created");
}
@Override
public void insertInnerFormToHolderResources(PDFormXObject innerForm,
PDResources holderFormResources)
{
COSName innerFormName = holderFormResources.add(innerForm, "FRM");
pdfStructure.setInnerFormName(innerFormName);
LOG.info("Now inserted inner form inside holder form");
}
@Override
public void createImageFormStream(PDDocument template)
{
PDStream imageFormStream = new PDStream(template);
pdfStructure.setImageFormStream(imageFormStream);
LOG.info("Created image form stream");
}
@Override
public void createImageFormResources()
{
PDResources imageFormResources = new PDResources();
pdfStructure.setImageFormResources(imageFormResources);
LOG.info("Created image form resources");
}
@Override
public void createImageForm(PDResources imageFormResources, PDResources innerFormResource,
PDStream imageFormStream, PDRectangle formrect, AffineTransform at,
PDImageXObject img) throws IOException
{
PDFormXObject imageForm = new PDFormXObject(imageFormStream);
imageForm.setBBox(formrect);
imageForm.setMatrix(at);
imageForm.setResources(imageFormResources);
imageForm.setFormType(1);
imageFormResources.getCOSObject().setDirect(true);
COSName imageFormName = innerFormResource.add(imageForm, "n");
COSName imageName = imageFormResources.add(img, "img");
pdfStructure.setImageForm(imageForm);
pdfStructure.setImageFormName(imageFormName);
pdfStructure.setImageName(imageName);
LOG.info("Created image form");
}
@Override
public void injectProcSetArray(PDFormXObject innerForm, PDPage page,
PDResources innerFormResources, PDResources imageFormResources,
PDResources holderFormResources, COSArray procSet)
{
innerForm.getResources().getCOSObject().setItem(COSName.PROC_SET, procSet);
page.getCOSObject().setItem(COSName.PROC_SET, procSet);
innerFormResources.getCOSObject().setItem(COSName.PROC_SET, procSet);
imageFormResources.getCOSObject().setItem(COSName.PROC_SET, procSet);
holderFormResources.getCOSObject().setItem(COSName.PROC_SET, procSet);
LOG.info("Inserted ProcSet to PDF");
}
@Override
public void injectAppearanceStreams(PDStream holderFormStream, PDStream innerFormStream,
PDStream imageFormStream, COSName imageObjectName,
COSName imageName, COSName innerFormName,
PDVisibleSignDesigner properties) throws IOException
{
// 100 means that document width is 100% via the rectangle. if rectangle
// is 500px, images 100% is 500px.
// String imgFormContent = "q "+imageWidthSize+ " 0 0 50 0 0 cm /" +
// imageName + " Do Q\n" + builder.toString();
String imgFormContent = "q " + 100 + " 0 0 50 0 0 cm /" + imageName.getName() + " Do Q\n";
String holderFormContent = "q 1 0 0 1 0 0 cm /" + innerFormName.getName() + " Do Q\n";
String innerFormContent = "q 1 0 0 1 0 0 cm /" + imageObjectName.getName() + " Do Q\n";
appendRawCommands(pdfStructure.getHolderFormStream().createOutputStream(), holderFormContent);
appendRawCommands(pdfStructure.getInnerFormStream().createOutputStream(), innerFormContent);
appendRawCommands(pdfStructure.getImageFormStream().createOutputStream(), imgFormContent);
LOG.info("Injected appearance stream to pdf");
}
public void appendRawCommands(OutputStream os, String commands) throws IOException
{
os.write(commands.getBytes("UTF-8"));
os.close();
}
@Override
public void createVisualSignature(PDDocument template)
{
pdfStructure.setVisualSignature(template.getDocument());
LOG.info("Visible signature has been created");
}
@Override
public void createWidgetDictionary(PDSignatureField signatureField,
PDResources holderFormResources) throws IOException
{
COSDictionary widgetDict = signatureField.getWidgets().get(0).getCOSObject();
widgetDict.setNeedToBeUpdated(true);
widgetDict.setItem(COSName.DR, holderFormResources.getCOSObject());
pdfStructure.setWidgetDictionary(widgetDict);
LOG.info("WidgetDictionary has been created");
}
@Override
public void closeTemplate(PDDocument template) throws IOException
{
template.close();
pdfStructure.getTemplate().close();
}
}