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

eu.europa.esig.dss.pdf.pdfbox.visible.nativedrawer.NativePdfBoxVisibleSignatureDrawer Maven / Gradle / Ivy

The newest version!
/**
 * DSS - Digital Signature Services
 * Copyright (C) 2015 European Commission, provided under the CEF programme
 * 
 * This file is part of the "DSS - Digital Signature Services" project.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package eu.europa.esig.dss.pdf.pdfbox.visible.nativedrawer;

import eu.europa.esig.dss.model.DSSDocument;
import eu.europa.esig.dss.pades.DSSFileFont;
import eu.europa.esig.dss.pades.DSSFont;
import eu.europa.esig.dss.pades.PAdESUtils;
import eu.europa.esig.dss.pades.SignatureImageParameters;
import eu.europa.esig.dss.pades.SignatureImageTextParameters;
import eu.europa.esig.dss.pdf.pdfbox.PdfBoxUtils;
import eu.europa.esig.dss.pdf.pdfbox.visible.AbstractPdfBoxSignatureDrawer;
import eu.europa.esig.dss.pdf.pdfbox.visible.PdfBoxNativeFont;
import eu.europa.esig.dss.pdf.visible.DSSFontMetrics;
import eu.europa.esig.dss.pdf.visible.ImageRotationUtils;
import eu.europa.esig.dss.pdf.visible.ImageUtils;
import eu.europa.esig.dss.pdf.visible.SignatureFieldDimensionAndPosition;
import eu.europa.esig.dss.signature.resources.DSSResourcesHandler;
import eu.europa.esig.dss.signature.resources.DSSResourcesHandlerBuilder;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
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.SignatureOptions;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.apache.pdfbox.util.Matrix;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

/**
 * The native PDFBox signature drawer.
 * Creates text in the native way.
 */
public class NativePdfBoxVisibleSignatureDrawer extends AbstractPdfBoxSignatureDrawer {

	private static final Logger LOG = LoggerFactory.getLogger(NativePdfBoxVisibleSignatureDrawer.class);

	/** PDFBox font */
	private PDFont pdFont;

	/**
	 * The builder is to be used to create a new {@code DSSResourcesHandler} for visual signature creation,
	 * defining a way working with internal resources (e.g. in memory or by using temporary files).
	 * Default : {@code eu.europa.esig.dss.signature.resources.InMemoryResourcesHandler}
	 */
	private DSSResourcesHandlerBuilder resourcesHandlerBuilder = PAdESUtils.DEFAULT_RESOURCES_HANDLER_BUILDER;

	/**
	 * Default constructor instantiating object with default parameter values
	 */
	public NativePdfBoxVisibleSignatureDrawer() {
		// empty
	}

	/**
	 * Sets {@code DSSResourcesFactoryBuilder} to be used for a {@code DSSResourcesFactory} creation
	 * Default : {@code eu.europa.esig.dss.signature.resources.InMemoryResourcesHandler}. Works with data in memory.
	 *
	 * @param resourcesHandlerBuilder {@link DSSResourcesHandlerBuilder}
	 */
	public void setResourcesHandlerBuilder(DSSResourcesHandlerBuilder resourcesHandlerBuilder) {
		this.resourcesHandlerBuilder = resourcesHandlerBuilder;
	}

	@Override
	public void init(SignatureImageParameters parameters, PDDocument document, SignatureOptions signatureOptions)
			throws IOException {
		super.init(parameters, document, signatureOptions);
		if (!parameters.getTextParameters().isEmpty()) {
			this.pdFont = initFont();
		}
	}

	/**
	 * Method to initialize the specific font for PdfBox {@link PDFont}
	 */
	private PDFont initFont() throws IOException {
		DSSFont dssFont = parameters.getTextParameters().getFont();
		if (dssFont instanceof PdfBoxNativeFont) {
			PdfBoxNativeFont nativeFont = (PdfBoxNativeFont) dssFont;
			return nativeFont.getFont();

		} else if (dssFont instanceof DSSFileFont) {
			DSSFileFont fileFont = (DSSFileFont) dssFont;
			try (InputStream is = fileFont.getInputStream()) {
				return PDType0Font.load(document, is, fileFont.isEmbedFontSubset());
			}

		} else {
			return PdfBoxFontMapper.getPDFont(dssFont.getJavaFont());
		}
	}

	@Override
	protected DSSFontMetrics getDSSFontMetrics() {
		return new PdfBoxDSSFontMetrics(pdFont);
	}

	@Override
	public void draw() throws IOException {
		try (DSSResourcesHandler resourcesHandler = resourcesHandlerBuilder.createResourcesHandler();
			 OutputStream os = resourcesHandler.createOutputStream();
			 PDDocument doc = new PDDocument()) {

			int pageNumber = parameters.getFieldParameters().getPage() - ImageUtils.DEFAULT_FIRST_PAGE;
			PDPage originalPage = document.getPage(pageNumber);
			SignatureFieldDimensionAndPosition dimensionAndPosition = buildSignatureFieldBox();
			// create a new page
			PDPage page = new PDPage(originalPage.getMediaBox());
			doc.addPage(page);
			PDAcroForm acroForm = new PDAcroForm(doc);
			doc.getDocumentCatalog().setAcroForm(acroForm);
			PDSignatureField signatureField = new PDSignatureField(acroForm);
			PDAnnotationWidget widget = signatureField.getWidgets().get(0);
			List acroFormFields = acroForm.getFields();
			acroForm.setSignaturesExist(true);
			acroForm.setAppendOnly(true);
			acroForm.getCOSObject().setDirect(true);
			acroFormFields.add(signatureField);

			PDRectangle rectangle = getPdRectangle(dimensionAndPosition, page);
			widget.setRectangle(rectangle);

			PDAppearanceDictionary appearance = PdfBoxUtils.createSignatureAppearanceDictionary(doc, rectangle);
			widget.setAppearance(appearance);

			PDAppearanceStream appearanceStream = appearance.getNormalAppearance().getAppearanceStream();
			try (PDPageContentStream cs = new PDPageContentStream(doc, appearanceStream)) {
				rotateSignature(cs, rectangle, dimensionAndPosition);
				setFieldBackground(cs, parameters.getBackgroundColor());
				setText(cs, dimensionAndPosition, parameters);
				setImage(cs, doc, dimensionAndPosition, parameters.getImage());
			}

			doc.save(os);

			DSSDocument document = resourcesHandler.writeToDSSDocument();
			try (InputStream is = document.openStream()) {
				signatureOptions.setVisualSignature(is);
			}

		}
	}

	private void rotateSignature(PDPageContentStream cs, PDRectangle rectangle,
			SignatureFieldDimensionAndPosition dimensionAndPosition) throws IOException {
		switch (dimensionAndPosition.getGlobalRotation()) {
		case ImageRotationUtils.ANGLE_90:
			// pdfbox rotates in the opposite way
			cs.transform(Matrix.getRotateInstance(Math.toRadians(ImageRotationUtils.ANGLE_270), 0, 0));
			cs.transform(Matrix.getTranslateInstance(-rectangle.getHeight(), 0));
			break;
		case ImageRotationUtils.ANGLE_180:
			cs.transform(Matrix.getRotateInstance(Math.toRadians(ImageRotationUtils.ANGLE_180), 0, 0));
			cs.transform(Matrix.getTranslateInstance(-rectangle.getWidth(), -rectangle.getHeight()));
			break;
		case ImageRotationUtils.ANGLE_270:
			cs.transform(Matrix.getRotateInstance(Math.toRadians(ImageRotationUtils.ANGLE_90), 0, 0));
			cs.transform(Matrix.getTranslateInstance(0, -rectangle.getWidth()));
			break;
		case ImageRotationUtils.ANGLE_360:
		case ImageRotationUtils.ANGLE_0:
			// do nothing
			break;
		default:
			throw new IllegalStateException(ImageRotationUtils.SUPPORTED_ANGLES_ERROR_MESSAGE);
		}
	}

	/**
	 * Fills a signature field background with the given color
	 * 
	 * @param cs    current {@link PDPageContentStream}
	 * @param color {@link Color} background color
	 * @throws IOException in case of error
	 */
	private void setFieldBackground(PDPageContentStream cs, Color color) throws IOException {
		setBackground(cs, color, new PDRectangle(-5000, -5000, 10000, 10000));
	}

	private void setBackground(PDPageContentStream cs, Color color, PDRectangle rect) throws IOException {
		if (color != null) {
			setAlphaChannel(cs, color);
			setNonStrokingColor(cs, color);
			// fill a whole box with the background color
			cs.addRect(rect.getLowerLeftX(), rect.getLowerLeftY(), rect.getWidth(), rect.getHeight());
			cs.fill();
			cleanTransparency(cs, color);
		}
	}

	/**
	 * Draws the given image with specified dimension and position
	 * 
	 * @param cs                   {@link PDPageContentStream} current stream
	 * @param doc                  {@link PDDocument} to draw the picture on
	 * @param dimensionAndPosition {@link SignatureFieldDimensionAndPosition} size
	 *                             and position to place the picture to
	 * @param image                {@link DSSDocument} image to draw
	 * @throws IOException in case of error
	 */
	private void setImage(PDPageContentStream cs, PDDocument doc,
			SignatureFieldDimensionAndPosition dimensionAndPosition, DSSDocument image) throws IOException {
		if (image != null) {
			try (InputStream is = image.openStream()) {
				cs.saveGraphicsState();
				byte[] bytes = IOUtils.toByteArray(is);
				PDImageXObject imageXObject = PDImageXObject.createFromByteArray(doc, bytes, image.getName());

				float xAxis = dimensionAndPosition.getImageX();
				float yAxis = dimensionAndPosition.getImageY();
				float width = dimensionAndPosition.getImageWidth();
				float height = dimensionAndPosition.getImageHeight();

				cs.drawImage(imageXObject, xAxis, yAxis, width, height);
				cs.transform(Matrix.getRotateInstance(
						((double) 360 - ImageRotationUtils.getRotation(parameters.getFieldParameters().getRotation())), width, height));

				cs.restoreGraphicsState();
			}
		}
	}

	/**
	 * Draws a custom text with the specified parameters
	 * 
	 * @param cs                   {@link PDPageContentStream} current stream
	 * @param dimensionAndPosition {@link SignatureFieldDimensionAndPosition} size
	 *                             and position to place the text to
	 * @param parameters       {@link SignatureImageTextParameters} text to
	 *                             place on the signature field
	 * @throws IOException in case of error
	 */
	private void setText(PDPageContentStream cs, SignatureFieldDimensionAndPosition dimensionAndPosition,
			SignatureImageParameters parameters) throws IOException {
		SignatureImageTextParameters textParameters = parameters.getTextParameters();
		if (!textParameters.isEmpty()) {
			setTextBackground(cs, textParameters, dimensionAndPosition);
			float fontSize = dimensionAndPosition.getTextSize();
			cs.beginText();
			cs.setFont(pdFont, fontSize);
			setNonStrokingColor(cs, textParameters.getTextColor());
			setAlphaChannel(cs, textParameters.getTextColor());

			PdfBoxDSSFontMetrics pdfBoxFontMetrics = new PdfBoxDSSFontMetrics(pdFont);

			String text = dimensionAndPosition.getText();
			String[] strings = pdfBoxFontMetrics.getLines(text);

			float lineHeight = pdfBoxFontMetrics.getHeight(text, dimensionAndPosition.getTextSize());
			cs.setLeading(lineHeight);

			cs.newLineAtOffset(dimensionAndPosition.getTextX(),
					// align vertical position
					 dimensionAndPosition.getTextHeight() + dimensionAndPosition.getTextY() - fontSize);

			float previousOffset = 0;
			for (String str : strings) {
				float stringWidth = pdfBoxFontMetrics.getWidth(str, fontSize);
				float offsetX = 0;
				switch (textParameters.getSignerTextHorizontalAlignment()) {
				case RIGHT:
					offsetX = dimensionAndPosition.getTextWidth() - stringWidth
							- previousOffset;
					break;
				case CENTER:
					offsetX = (dimensionAndPosition.getTextWidth() - stringWidth) / 2
							- previousOffset;
					break;
				default:
					break;
				}
				previousOffset += offsetX;
				cs.newLineAtOffset(offsetX, 0); // relative offset
				cs.showText(str);
				cs.newLine();
			}
			cs.endText();
			cleanTransparency(cs, textParameters.getTextColor());
		}
	}

	private void setTextBackground(PDPageContentStream cs, SignatureImageTextParameters textParameters,
			SignatureFieldDimensionAndPosition dimensionAndPosition) throws IOException {
		if (textParameters.getBackgroundColor() != null) {
			PDRectangle rect = new PDRectangle(
					dimensionAndPosition.getTextBoxX(), dimensionAndPosition.getTextBoxY(),
					dimensionAndPosition.getTextBoxWidth(), dimensionAndPosition.getTextBoxHeight());
			setBackground(cs, textParameters.getBackgroundColor(), rect);
		}
	}

	private void setNonStrokingColor(PDPageContentStream cs, Color color) throws IOException {
		if (color != null) {
			cs.setNonStrokingColor(toPDColor(color));
		}
	}

	private PDColor toPDColor(Color color) {
		float[] components;
		PDColorSpace pdColorSpace;
		if (ImageUtils.isGrayscale(color)) {
			components = new float[] { color.getRed() / 255f };
			pdColorSpace = PDDeviceGray.INSTANCE;
		} else {
			components = new float[] { color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f };
			pdColorSpace = PDDeviceRGB.INSTANCE;
		}
		return new PDColor(components, pdColorSpace);
	}

	/**
	 * Sets alpha channel if needed
	 * 
	 * @param cs    {@link PDPageContentStream} current stream
	 * @param color {@link Color}
	 * @throws IOException in case of error
	 */
	private void setAlphaChannel(PDPageContentStream cs, Color color) throws IOException {
		if (color != null) {
			// if alpha value is less then 255 (is transparent)
			float alpha = color.getAlpha();
			if (alpha < ImageUtils.OPAQUE_VALUE) {
				LOG.warn("Transparency detected and enabled (Be aware: not valid with PDF/A !)");
				setAlpha(cs, alpha);
			}
		}
	}

	private void setAlpha(PDPageContentStream cs, float alpha) throws IOException {
		PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
		gs.setNonStrokingAlphaConstant(alpha / ImageUtils.OPAQUE_VALUE);
		cs.setGraphicsStateParameters(gs);
	}

	/**
	 * Clears alpha channel if needed
	 *
	 * @param cs    {@link PDPageContentStream} current stream
	 * @param color {@link Color}
	 * @throws IOException in case of error
	 */
	private void cleanTransparency(PDPageContentStream cs, Color color) throws IOException {
		if (color != null) {
			// if alpha value is less than 255 (is transparent)
			float alpha = color.getAlpha();
			if (alpha < ImageUtils.OPAQUE_VALUE) {
				setAlpha(cs, ImageUtils.OPAQUE_VALUE);
			}
		}
	}

	/**
	 * Returns {@link PDRectangle} of the widget to place on page
	 *
	 * @param dimensionAndPosition {@link SignatureFieldDimensionAndPosition}
	 *                             specifies widget size and position
	 * @param page                 {@link PDPage} to place the widget on
	 * @return {@link PDRectangle}
	 */
	private PDRectangle getPdRectangle(SignatureFieldDimensionAndPosition dimensionAndPosition, PDPage page) {
		PDRectangle pageRect = page.getMediaBox();
		PDRectangle pdRectangle = new PDRectangle();
		pdRectangle.setLowerLeftX(dimensionAndPosition.getBoxX());
		// because PDF starts to count from bottom
		pdRectangle.setLowerLeftY(
				pageRect.getHeight() - dimensionAndPosition.getBoxY() - dimensionAndPosition.getBoxHeight());
		pdRectangle.setUpperRightX(dimensionAndPosition.getBoxX() + dimensionAndPosition.getBoxWidth());
		pdRectangle.setUpperRightY(pageRect.getHeight() - dimensionAndPosition.getBoxY());
		return pdRectangle;
	}

	@Override
	protected String getExpectedColorSpaceName() throws IOException {
		if (parameters.getImage() != null) {
			try (InputStream is = parameters.getImage().openStream()) {
				byte[] bytes = IOUtils.toByteArray(is);
				PDImageXObject imageXObject = PDImageXObject.createFromByteArray(document, bytes, parameters.getImage().getName());
				PDColorSpace colorSpace = imageXObject.getColorSpace();
				return colorSpace.getName();
			}
		} else {
			return ImageUtils.containRGBColor(parameters) ? COSName.DEVICERGB.getName() : COSName.DEVICEGRAY.getName();
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy