All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.itextpdf.signatures.PdfSignatureAppearance Maven / Gradle / Ivy

There is a newer version: 9.0.0
Show newest version
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2023 Apryse Group NV
    Authors: Apryse Software.

    This program is offered under a commercial and under the AGPL license.
    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

    AGPL licensing:
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    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 Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see .
 */
package com.itextpdf.signatures;

import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.io.image.ImageData;
import com.itextpdf.kernel.colors.Color;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfStream;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.layout.LayoutArea;
import com.itextpdf.layout.layout.LayoutContext;
import com.itextpdf.layout.layout.LayoutResult;
import com.itextpdf.layout.renderer.IRenderer;

import java.io.IOException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Calendar;

/**
 * Provides convenient methods to make a signature appearance. Use it in conjunction with {@link PdfSigner}.
 */
public class PdfSignatureAppearance {

    /**
     * Extra space at the top.
     */
    private static final float TOP_SECTION = 0.3f;

    /**
     * Margin for the content inside the signature rectangle.
     */
    private static final float MARGIN = 2;

    /**
     * The document to be signed.
     */
    private PdfDocument document;

    /**
     * The page where the signature will appear.
     */
    private int page = 1;

    /**
     * The coordinates of the rectangle for a visible signature,
     * or a zero-width, zero-height rectangle for an invisible signature.
     */
    private Rectangle rect;

    /**
     * Rectangle that represent the position and dimension of the signature in the page.
     */
    private Rectangle pageRect;

    /**
     * Zero level of the signature appearance.
     */
    private PdfFormXObject n0;

    /**
     * Second level of the signature appearance.
     */
    private PdfFormXObject n2;

    /**
     * Form containing all layers drawn on top of each other.
     */
    private PdfFormXObject topLayer;

    /**
     * The rendering mode chosen for visible signatures.
     */
    private RenderingMode renderingMode = RenderingMode.DESCRIPTION;

    /**
     * The reason for signing.
     */
    private String reason = "";

    /**
     * The caption for the reason for signing.
     */
    private String reasonCaption = "Reason: ";

    /**
     * Holds value of property location.
     */
    private String location = "";

    /**
     * The caption for the location of signing.
     */
    private String locationCaption = "Location: ";

    /**
     * Holds value of the application that creates the signature.
     */
    private String signatureCreator = "";

    /**
     * The contact name of the signer.
     */
    private String contact = "";

    /**
     * Holds value of property signDate.
     */
    private Calendar signDate;

    /**
     * The signing certificate.
     */
    private Certificate signCertificate;

    /**
     * The image that needs to be used for a visible signature.
     */
    private ImageData signatureGraphic = null;

    /**
     * A background image for the text in layer 2.
     */
    private ImageData image;

    /**
     * The scaling to be applied to the background image.
     */
    private float imageScale;

    /**
     * The text that goes in Layer 2 of the signature appearance.
     */
    private String layer2Text;

    /**
     * Font for the text in Layer 2.
     */
    private PdfFont layer2Font;

    /**
     * Font size for the font of Layer 2.
     */
    private float layer2FontSize = 0;

    /**
     * Font color for the font of Layer 2.
     */
    private Color layer2FontColor;

    /**
     * Indicates the field to be signed if it is already presented in the document
     * (signing existing field). Required for {@link #reuseAppearance} option.
     */
    private String fieldName;

    /**
     * Indicates if we need to reuse the existing appearance as layer 0.
     */
    private boolean reuseAppearance = false;

    /**
     * Creates a PdfSignatureAppearance.
     *
     * @param document PdfDocument
     * @param pageRect Rectangle of the appearance
     * @param pageNumber Number of the page the appearance should be on
     */
    protected PdfSignatureAppearance(PdfDocument document, Rectangle pageRect, int pageNumber) {
        this.document = document;
        this.pageRect = new Rectangle(pageRect);
        this.rect = new Rectangle(pageRect.getWidth(), pageRect.getHeight());
        this.page = pageNumber;
    }

    /**
     * Provides the page number of the signature field which this signature
     * appearance is associated with.
     *
     * @return The page number of the signature field which this signature
     * appearance is associated with.
     */
    public int getPageNumber() {
        return page;
    }

    /**
     * Sets the page number of the signature field which this signature
     * appearance is associated with. Implicitly calls {@link PdfSignatureAppearance#setPageRect}
     * which considers page number to process the rectangle correctly.
     *
     * @param pageNumber The page number of the signature field which
     *                   this signature appearance is associated with.
     *
     * @return this instance to support fluent interface
     */
    public PdfSignatureAppearance setPageNumber(int pageNumber) {
        this.page = pageNumber;
        setPageRect(pageRect);
        return this;
    }

    /**
     * Provides the rectangle that represent the position and dimension
     * of the signature field in the page.
     *
     * @return the rectangle that represent the position and dimension
     * of the signature field in the page
     */
    public Rectangle getPageRect() {
        return pageRect;
    }

    /**
     * Sets the rectangle that represent the position and dimension of
     * the signature field in the page.
     *
     * @param pageRect The rectangle that represents the position and
     *                 dimension of the signature field in the page.
     *
     * @return this instance to support fluent interface
     */
    public PdfSignatureAppearance setPageRect(Rectangle pageRect) {
        this.pageRect = new Rectangle(pageRect);
        this.rect = new Rectangle(pageRect.getWidth(), pageRect.getHeight());
        return this;
    }

    /**
     * Get Layer 0 of the appearance.
     *
     * 

* The size of the layer is determined by the rectangle set via * {@link PdfSignatureAppearance#setPageRect(Rectangle)} * * @return layer 0 */ public PdfFormXObject getLayer0() { if (n0 == null) { n0 = new PdfFormXObject(rect); n0.makeIndirect(document); } return n0; } /** * Get Layer 2 of the appearance. * *

* The size of the layer is determined by the rectangle set via * {@link PdfSignatureAppearance#setPageRect(Rectangle)} * * @return layer 2 */ public PdfFormXObject getLayer2() { if (n2 == null) { n2 = new PdfFormXObject(rect); n2.makeIndirect(document); } return n2; } /** * Gets the rendering mode for this signature. * * @return the rendering mode for this signature */ public RenderingMode getRenderingMode() { return renderingMode; } /** * Sets the rendering mode for this signature. * * @param renderingMode the rendering mode * * @return this instance to support fluent interface */ public PdfSignatureAppearance setRenderingMode(RenderingMode renderingMode) { this.renderingMode = renderingMode; return this; } /** * Returns the signing reason. * * @return reason for signing */ public String getReason() { return this.reason; } /** * Sets the signing reason. * * @param reason signing reason. * * @return this instance to support fluent interface */ public PdfSignatureAppearance setReason(String reason) { this.reason = reason; return this; } /** * Sets the caption for the signing reason. * * @param reasonCaption A new signing reason caption * * @return this instance to support fluent interface */ public PdfSignatureAppearance setReasonCaption(String reasonCaption) { this.reasonCaption = reasonCaption; return this; } /** * Returns the signing location. * * @return signing location */ public String getLocation() { return this.location; } /** * Sets the signing location. * * @param location A new signing location * * @return this instance to support fluent interface */ public PdfSignatureAppearance setLocation(String location) { this.location = location; return this; } /** * Sets the caption for the signing location. * * @param locationCaption A new signing location caption * * @return this instance to support fluent interface */ public PdfSignatureAppearance setLocationCaption(String locationCaption) { this.locationCaption = locationCaption; return this; } /** * Returns the signature creator. * * @return The signature creator */ public String getSignatureCreator(){ return signatureCreator; } /** * Sets the name of the application used to create the signature. * * @param signatureCreator A new name of the application signing a document * * @return this instance to support fluent interface */ public PdfSignatureAppearance setSignatureCreator(String signatureCreator){ this.signatureCreator = signatureCreator; return this; } /** * Returns the signing contact. * * @return The signing contact */ public String getContact() { return this.contact; } /** * Sets the signing contact. * * @param contact A new signing contact * * @return this instance to support fluent interface */ public PdfSignatureAppearance setContact(String contact) { this.contact = contact; return this; } /** * Sets the certificate used to provide the text in the appearance. * This certificate doesn't take part in the actual signing process. * * @param signCertificate the certificate * * @return this instance to support fluent interface */ public PdfSignatureAppearance setCertificate(Certificate signCertificate) { this.signCertificate = signCertificate; return this; } /** * Get the signing certificate. * * @return the signing certificate */ public Certificate getCertificate() { return signCertificate; } /** * Gets the Image object to render. * * @return the image */ public ImageData getSignatureGraphic() { return signatureGraphic; } /** * Sets the Image object to render when Render is set to RenderingMode.GRAPHIC or RenderingMode.GRAPHIC_AND_DESCRIPTION. * * @param signatureGraphic image rendered. If null the mode is defaulted to RenderingMode.DESCRIPTION * * @return this instance to support fluent interface */ public PdfSignatureAppearance setSignatureGraphic(ImageData signatureGraphic) { this.signatureGraphic = signatureGraphic; return this; } /** * Indicates that the existing appearances needs to be reused as layer 0. * * @param reuseAppearance is an appearances reusing flag value to set * * @return this instance to support fluent interface */ public PdfSignatureAppearance setReuseAppearance(boolean reuseAppearance) { this.reuseAppearance = reuseAppearance; return this; } // layer 2 /** * Gets the background image for the layer 2. * * @return the background image for the layer 2 */ public ImageData getImage() { return this.image; } /** * Sets the background image for the layer 2. * * @param image the background image for the layer 2 * * @return this instance to support fluent interface */ public PdfSignatureAppearance setImage(ImageData image) { this.image = image; return this; } /** * Gets the scaling to be applied to the background image. * * @return the scaling to be applied to the background image */ public float getImageScale() { return this.imageScale; } /** * Sets the scaling to be applied to the background image. If it's zero the image * will fully fill the rectangle. If it's less than zero the image will fill the rectangle but * will keep the proportions. If it's greater than zero that scaling will be applied. * In any of the cases the image will always be centered. It's zero by default. * * @param imageScale the scaling to be applied to the background image * * @return this instance to support fluent interface */ public PdfSignatureAppearance setImageScale(float imageScale) { this.imageScale = imageScale; return this; } /** * Sets the signature text identifying the signer. * * @param text the signature text identifying the signer. If null or not set * a standard description will be used * * @return this instance to support fluent interface */ public PdfSignatureAppearance setLayer2Text(String text) { layer2Text = text; return this; } /** * Gets the signature text identifying the signer if set by setLayer2Text(). * * @return the signature text identifying the signer */ public String getLayer2Text() { return layer2Text; } /** * Gets the n2 and n4 layer font. * * @return the n2 and n4 layer font */ public PdfFont getLayer2Font() { return this.layer2Font; } /** * Sets the n2 and n4 layer font. If the font size is zero, auto-fit will be used. * * @param layer2Font the n2 and n4 font * * @return this instance to support fluent interface */ public PdfSignatureAppearance setLayer2Font(PdfFont layer2Font) { this.layer2Font = layer2Font; return this; } /** * Sets the n2 and n4 layer font size. * * @param fontSize font size * * @return this instance to support fluent interface */ public PdfSignatureAppearance setLayer2FontSize(float fontSize) { this.layer2FontSize = fontSize; return this; } /** * Gets the n2 and n4 layer font size. * * @return the n2 and n4 layer font size */ public float getLayer2FontSize() { return layer2FontSize; } /** * Sets the n2 and n4 layer font color. * * @param color font color * * @return this instance to support fluent interface */ public PdfSignatureAppearance setLayer2FontColor(Color color) { this.layer2FontColor = color; return this; } /** * Gets the n2 and n4 layer font color. * * @return the n2 and n4 layer font color */ public Color getLayer2FontColor() { return layer2FontColor; } /** * Gets the visibility status of the signature. * * @return the visibility status of the signature */ public boolean isInvisible() { return rect == null || rect.getWidth() == 0 || rect.getHeight() == 0; } /** * Constructs appearance (top-level) for a signature. * * @return a top-level signature appearance * @throws IOException if font cannot be created * @see Adobe Pdf Digital * Signature Appearances */ protected PdfFormXObject getAppearance() throws IOException { PdfCanvas canvas; if (isInvisible()) { PdfFormXObject appearance = new PdfFormXObject(new Rectangle(0, 0)); appearance.makeIndirect(document); return appearance; } if (n0 == null && !reuseAppearance) { createBlankN0(); } if (n2 == null) { n2 = new PdfFormXObject(rect); n2.makeIndirect(document); canvas = new PdfCanvas(n2, document); int rotation = document.getPage(page).getRotation(); if (rotation == 90) { canvas.concatMatrix(0, 1, -1, 0, rect.getWidth(), 0); } else if (rotation == 180) { canvas.concatMatrix(-1, 0, 0, -1, rect.getWidth(), rect.getHeight()); } else if (rotation == 270) { canvas.concatMatrix(0, -1, 1, 0, 0, rect.getHeight()); } Rectangle rotatedRect = rotateRectangle(this.rect, document.getPage(page).getRotation()); String text = layer2Text; if (null == text) { text = generateLayer2Text(); } if (image != null) { if (imageScale == 0) { canvas = new PdfCanvas(n2, document); canvas.addImageWithTransformationMatrix(image, rotatedRect.getWidth(), 0, 0, rotatedRect.getHeight(), 0, 0); } else { float usableScale = imageScale; if (imageScale < 0) { usableScale = Math.min(rotatedRect.getWidth() / image.getWidth(), rotatedRect.getHeight() / image.getHeight()); } float w = image.getWidth() * usableScale; float h = image.getHeight() * usableScale; float x = (rotatedRect.getWidth() - w) / 2; float y = (rotatedRect.getHeight() - h) / 2; canvas = new PdfCanvas(n2, document); canvas.addImageWithTransformationMatrix(image, w, 0, 0, h, x, y); } } PdfFont font; if (layer2Font == null) { font = PdfFontFactory.createFont(); } else { font = layer2Font; } Rectangle dataRect = null; Rectangle signatureRect = null; if (renderingMode == RenderingMode.NAME_AND_DESCRIPTION || renderingMode == RenderingMode.GRAPHIC_AND_DESCRIPTION && this.signatureGraphic != null) { if (rotatedRect.getHeight() > rotatedRect.getWidth()) { signatureRect = new Rectangle( MARGIN, rotatedRect.getHeight() / 2, rotatedRect.getWidth() - 2 * MARGIN, rotatedRect.getHeight() / 2); dataRect = new Rectangle( MARGIN, MARGIN, rotatedRect.getWidth() - 2 * MARGIN, rotatedRect.getHeight() / 2 - 2 * MARGIN); } else { // origin is the bottom-left signatureRect = new Rectangle( MARGIN, MARGIN, rotatedRect.getWidth() / 2 - 2 * MARGIN, rotatedRect.getHeight() - 2 * MARGIN); dataRect = new Rectangle( rotatedRect.getWidth() / 2 + MARGIN / 2, MARGIN, rotatedRect.getWidth() / 2 - MARGIN, rotatedRect.getHeight() - 2 * MARGIN); } } else if (renderingMode == RenderingMode.GRAPHIC) { if (signatureGraphic == null) { throw new IllegalStateException("A signature image must be present when rendering mode is graphic. Use setSignatureGraphic()"); } signatureRect = new Rectangle( MARGIN, MARGIN, rotatedRect.getWidth() - 2 * MARGIN, // take all space available rotatedRect.getHeight() - 2 * MARGIN); } else { dataRect = new Rectangle( MARGIN, MARGIN, rotatedRect.getWidth() - 2 * MARGIN, rotatedRect.getHeight() * (1 - TOP_SECTION) - 2 * MARGIN); } switch (renderingMode) { case NAME_AND_DESCRIPTION: String signedBy = CertificateInfo.getSubjectFields((X509Certificate) signCertificate).getField("CN"); if (signedBy == null) { signedBy = CertificateInfo.getSubjectFields((X509Certificate) signCertificate).getField("E"); } if (signedBy == null) { signedBy = ""; } addTextToCanvas(signedBy, font, signatureRect); break; case GRAPHIC_AND_DESCRIPTION: { if (signatureGraphic == null) { throw new IllegalStateException("A signature image must be present when rendering mode is graphic and description. Use setSignatureGraphic()"); } float imgWidth = signatureGraphic.getWidth(); if (imgWidth == 0) { imgWidth = signatureRect.getWidth(); } float imgHeight = signatureGraphic.getHeight(); if (imgHeight == 0) { imgHeight = signatureRect.getHeight(); } float multiplierH = signatureRect.getWidth() / signatureGraphic.getWidth(); float multiplierW = signatureRect.getHeight() / signatureGraphic.getHeight(); float multiplier = Math.min(multiplierH, multiplierW); imgWidth *= multiplier; imgHeight *= multiplier; float x = signatureRect.getRight() - imgWidth; float y = signatureRect.getBottom() + (signatureRect.getHeight() - imgHeight) / 2; canvas = new PdfCanvas(n2, document); canvas.addImageWithTransformationMatrix(signatureGraphic, imgWidth, 0, 0, imgHeight, x, y); break; } case GRAPHIC: float imgWidth = signatureGraphic.getWidth(); if (imgWidth == 0) { imgWidth = signatureRect.getWidth(); } float imgHeight = signatureGraphic.getHeight(); if (imgHeight == 0) { imgHeight = signatureRect.getHeight(); } float multiplierH = signatureRect.getWidth() / signatureGraphic.getWidth(); float multiplierW = signatureRect.getHeight() / signatureGraphic.getHeight(); float multiplier = Math.min(multiplierH, multiplierW); imgWidth *= multiplier; imgHeight *= multiplier; float x = signatureRect.getLeft() + (signatureRect.getWidth() - imgWidth) / 2; float y = signatureRect.getBottom() + (signatureRect.getHeight() - imgHeight) / 2; canvas = new PdfCanvas(n2, document); canvas.addImageWithTransformationMatrix(signatureGraphic, imgWidth, 0, 0, imgHeight, x, y); break; } if (renderingMode != RenderingMode.GRAPHIC) { addTextToCanvas(text, font, dataRect); } } Rectangle rotated = new Rectangle(rect); if (topLayer == null) { topLayer = new PdfFormXObject(rotated); topLayer.makeIndirect(document); if (reuseAppearance) { PdfAcroForm acroForm = PdfAcroForm.getAcroForm(document, true); PdfFormField field = acroForm.getField(fieldName); PdfStream stream = field.getWidgets().get(0).getAppearanceDictionary().getAsStream(PdfName.N); PdfFormXObject xobj = new PdfFormXObject(stream); if (stream != null) { topLayer.getResources().addForm(xobj, new PdfName("n0")); PdfCanvas canvas1 = new PdfCanvas(topLayer, document); canvas1.addXObjectWithTransformationMatrix(xobj, 1, 0, 0, 1, 0, 0); } else { reuseAppearance = false; if (n0 == null) { createBlankN0(); } } } if (!reuseAppearance) { topLayer.getResources().addForm(n0, new PdfName("n0")); PdfCanvas canvas1 = new PdfCanvas(topLayer, document); canvas1.addXObjectWithTransformationMatrix(n0, 1, 0, 0, 1, 0, 0); } topLayer.getResources().addForm(n2, new PdfName("n2")); PdfCanvas canvas1 = new PdfCanvas(topLayer, document); canvas1.addXObjectWithTransformationMatrix(n2, 1, 0, 0, 1, 0, 0); } PdfFormXObject napp = new PdfFormXObject(rotated); napp.makeIndirect(document); napp.getResources().addForm(topLayer, new PdfName("FRM")); canvas = new PdfCanvas(napp, document); canvas.addXObjectAt(topLayer, topLayer.getBBox().getAsNumber(0).floatValue(), topLayer.getBBox().getAsNumber(1).floatValue()); return napp; } /** * Returns the signature date. * * @return the signature date */ protected java.util.Calendar getSignDate() { return signDate; } /** * Sets the signature date. * * @param signDate A new signature date * * @return this instance to support fluent interface */ protected PdfSignatureAppearance setSignDate(java.util.Calendar signDate) { this.signDate = signDate; return this; } /** * Set the field name of the appearance. * * @param fieldName name of the field * * @return this instance to support fluent interface */ protected PdfSignatureAppearance setFieldName(String fieldName) { this.fieldName = fieldName; return this; } private static Rectangle rotateRectangle(Rectangle rect, int angle) { if (0 == (angle / 90) % 2) { return new Rectangle(rect.getWidth(), rect.getHeight()); } else { return new Rectangle(rect.getHeight(), rect.getWidth()); } } private void createBlankN0() { n0 = new PdfFormXObject(new Rectangle(100, 100)); n0.makeIndirect(document); PdfCanvas canvas = new PdfCanvas(n0, document); canvas.writeLiteral("% DSBlank\n"); } private void addTextToCanvas(String text, PdfFont font, Rectangle dataRect) { PdfCanvas canvas; canvas = new PdfCanvas(n2, document); Paragraph paragraph = new Paragraph(text).setFont(font).setMargin(0).setMultipliedLeading(0.9f); Canvas layoutCanvas = new Canvas(canvas, dataRect); paragraph.setFontColor(layer2FontColor); if (layer2FontSize == 0) { applyCopyFittingFontSize(paragraph, dataRect, layoutCanvas.getRenderer()); } else { paragraph.setFontSize(layer2FontSize); } layoutCanvas.add(paragraph); } private void applyCopyFittingFontSize(Paragraph paragraph, Rectangle rect, IRenderer parentRenderer) { IRenderer renderer = paragraph.createRendererSubTree().setParent(parentRenderer); LayoutContext layoutContext = new LayoutContext(new LayoutArea(1, rect)); float lFontSize = 0.1f, rFontSize = 100; int numberOfIterations = 15; // 15 iterations with lFontSize = 0.1 and rFontSize = 100 should result in ~0.003 precision for (int i = 0; i < numberOfIterations; i++) { float mFontSize = (lFontSize + rFontSize) / 2; paragraph.setFontSize(mFontSize); LayoutResult result = renderer.layout(layoutContext); if (result.getStatus() == LayoutResult.FULL) { lFontSize = mFontSize; } else { rFontSize = mFontSize; } } paragraph.setFontSize(lFontSize); } String generateLayer2Text() { StringBuilder buf = new StringBuilder(); buf.append("Digitally signed by "); String name = null; CertificateInfo.X500Name x500name = CertificateInfo.getSubjectFields((X509Certificate)signCertificate); if (x500name != null) { name = x500name.getField("CN"); if (name == null) { name = x500name.getField("E"); } } if (name == null) { name = ""; } buf.append(name).append('\n'); buf.append("Date: ").append(SignUtils.dateToString(signDate)); if (reason != null) { buf.append('\n').append(reasonCaption).append(reason); } if (location != null) { buf.append('\n').append(locationCaption).append(location); } return buf.toString(); } /** * Signature rendering modes. */ public enum RenderingMode { /** * The rendering mode is just the description. */ DESCRIPTION, /** * The rendering mode is the name of the signer and the description. */ NAME_AND_DESCRIPTION, /** * The rendering mode is an image and the description. */ GRAPHIC_AND_DESCRIPTION, /** * The rendering mode is just an image. */ GRAPHIC } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy