# PDVisibleSignDesigner.java

    /*
     * 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.BufferedInputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;

    import javax.imageio.ImageIO;

    import org.apache.pdfbox.pdmodel.PDDocument;
    import org.apache.pdfbox.Loader;
    import org.apache.pdfbox.io.MemoryUsageSetting;
    import org.apache.pdfbox.pdmodel.PDPage;
    import org.apache.pdfbox.pdmodel.common.PDRectangle;

    /**
     * Class for visible signature design properties. Setters use param() instead of setParam() to allow
     * chaining.
     *
     * @author Vakhtang Koroghlishvili
     */
    public class PDVisibleSignDesigner
    {
        private Float imageWidth;
        private Float imageHeight;
        private float xAxis;
        private float yAxis;
        private float pageHeight;
        private float pageWidth;
        private BufferedImage image;
        private String signatureFieldName = "sig";
        private int[] formatterRectangleParameters = { 0, 0, 100, 50 };
        private AffineTransform affineTransform = new AffineTransform();
        private float imageSizeInPercents;
        private int rotation = 0;

        /**
         * Constructor.
         *
         * @param filename Path of the PDF file
         * @param imageStream image as a stream
         * @param page The 1-based page number for which the page size should be calculated.
         * @throws IOException
         */
        public PDVisibleSignDesigner(String filename, InputStream imageStream, int page)
                throws IOException
        {
            this(filename, imageStream, page, MemoryUsageSetting.setupMainMemoryOnly());
        }

        /**
         * Constructor.
         *
         * @param filename Path of the PDF file
         * @param imageStream image as a stream
         * @param page The 1-based page number for which the page size should be calculated.
         * @param memoryUsageSetting if the file to be signed is big, use this to manage memory consumption
         * @throws IOException
         */
        public PDVisibleSignDesigner(String filename, InputStream imageStream, int page,
        		MemoryUsageSetting memoryUsageSetting)
                throws IOException
        {
            // set visible signature image Input stream
            readImageStream(imageStream);

            // calculate height and width of document page
            calculatePageSizeFromFile(filename, page, memoryUsageSetting);
        }

        /**
         * Constructor.
         *
         * @param documentStream Original PDF document as stream
         * @param imageStream Image as a stream
         * @param page The 1-based page number for which the page size should be calculated.
         * @throws IOException
         */
        public PDVisibleSignDesigner(InputStream documentStream, InputStream imageStream, int page)
                throws IOException
        {
            // set visible signature image Input stream
            readImageStream(imageStream);

            // calculate height and width of document page
            calculatePageSizeFromStream(documentStream, page);
        }

        /**
         * Constructor.
         *
         * @param document Already created PDDocument of your PDF document.
         * @param imageStream Image as a stream.
         * @param page The 1-based page number for which the page size should be calculated.
         * @throws IOException If we can't read, flush, or can't close stream.
         */
        public PDVisibleSignDesigner(PDDocument document, InputStream imageStream, int page) throws IOException
        {
            readImageStream(imageStream);
            calculatePageSize(document, page);
        }

        /**
         * Constructor.
         *
         * @param filename Path of the PDF file
         * @param image
         * @param page The 1-based page number for which the page size should be calculated.
         * @throws IOException
         */
        public PDVisibleSignDesigner(String filename, BufferedImage image, int page)
                throws IOException
        {
            // set visible signature image
            setImage(image);

            // calculate height and width of document page
            calculatePageSizeFromFile(filename, page);
        }

        /**
         * Constructor.
         *
         * @param documentStream Original PDF document as stream
         * @param image
         * @param page The 1-based page number for which the page size should be calculated.
         * @throws IOException
         */
        public PDVisibleSignDesigner(InputStream documentStream, BufferedImage image, int page)
                throws IOException
        {
            // set visible signature image
            setImage(image);

            // calculate height and width of document page
            calculatePageSizeFromStream(documentStream, page);
        }

        /**
         * Constructor.
         *
         * @param document Already created PDDocument of your PDF document.
         * @param image
         * @param page The 1-based page number for which the page size should be calculated.
         */
        public PDVisibleSignDesigner(PDDocument document, BufferedImage image, int page)
        {
            setImage(image);
            calculatePageSize(document, page);
        }

        /**
         * Constructor usable for signing existing signature fields.
         *
         * @param imageStream image as a stream
         * @throws IOException
         */
        public PDVisibleSignDesigner(InputStream imageStream) throws IOException
        {
            // set visible signature image Input stream
            readImageStream(imageStream);
        }

        private void calculatePageSizeFromFile(String filename, int page) throws IOException
        {
            MemoryUsageSetting memoryUsageSetting = MemoryUsageSetting.setupMainMemoryOnly();
            calculatePageSizeFromFile(filename, page, memoryUsageSetting);
        }

        private void calculatePageSizeFromFile(String filename, int page,
                MemoryUsageSetting memoryUsageSetting) throws IOException
        {
            try (PDDocument document = Loader.loadPDF(new File(filename), memoryUsageSetting))
            {
                // calculate height and width of document page
                calculatePageSize(document, page);
            }
        }

        private void calculatePageSizeFromStream(InputStream documentStream, int page) throws IOException
        {
            try (PDDocument document = Loader.loadPDF(documentStream))
            {
                // calculate height and width of document page
                calculatePageSize(document, page);
            }
        }

        /**
         * Each page of document can be different sizes. This method calculates the page size based on
         * the page media box.
         *
         * @param document
         * @param page The 1-based page number for which the page size should be calculated.
         * @throws IllegalArgumentException if the page argument is lower than 0.
         */
        private void calculatePageSize(PDDocument document, int page)
        {
            if (page < 1)
            {
                throw new IllegalArgumentException("First page of pdf is 1, not " + page);
            }

            PDPage firstPage = document.getPage(page - 1);
            PDRectangle mediaBox = firstPage.getMediaBox();
            pageHeight(mediaBox.getHeight());
            pageWidth = mediaBox.getWidth();
            imageSizeInPercents = 100;
            rotation = firstPage.getRotation() % 360;
        }

        /**
         * Adjust signature for page rotation. This is optional, call this after all x and y coordinates
         * have been set if you want the signature to be positioned regardless of page orientation.
         *
         * @return Visible Signature Configuration Object
         */
        public PDVisibleSignDesigner adjustForRotation()
        {
            switch (rotation)
            {
                case 90:
                    // https://stackoverflow.com/a/34359956/535646
                    float temp = yAxis;
                    yAxis = pageHeight - xAxis - imageWidth;
                    xAxis = temp;

                    affineTransform = new AffineTransform(
                            0, imageHeight / imageWidth, -imageWidth / imageHeight, 0, imageWidth, 0);

                    temp = imageHeight;
                    imageHeight = imageWidth;
                    imageWidth = temp;
                    break;

                case 180:
                    float newX = pageWidth - xAxis - imageWidth;
                    float newY = pageHeight - yAxis - imageHeight;
                    xAxis = newX;
                    yAxis = newY;

                    affineTransform = new AffineTransform(-1, 0, 0, -1, imageWidth, imageHeight);
                    break;

                case 270:
                    temp = xAxis;
                    xAxis = pageWidth - yAxis - imageHeight;
                    yAxis = temp;

                    affineTransform = new AffineTransform(
                            0, -imageHeight / imageWidth, imageWidth / imageHeight, 0, 0, imageHeight);

                    temp = imageHeight;
                    imageHeight = imageWidth;
                    imageWidth = temp;
                    break;

                case 0:
                default:
                    break;
            }
            return this;
        }

        /**
         * Set the image for the signature.
         *
         * @param path Path of the image file.
         * @return Visible Signature Configuration Object
         * @throws IOException
         */
        public PDVisibleSignDesigner signatureImage(String path) throws IOException
        {
            try (InputStream in = new BufferedInputStream(new FileInputStream(path)))
            {
                readImageStream(in);
            }
            return this;
        }

        /**
         * Zoom signature image with some percent.
         *
         * @param percent increase (positive value) or decrease (negative value) image with x percent.
         * @return Visible Signature Configuration Object
         */
        public PDVisibleSignDesigner zoom(float percent)
        {
            imageHeight += (imageHeight * percent) / 100;
            imageWidth += (imageWidth * percent) / 100;
            formatterRectangleParameters[2] = (int) imageWidth.floatValue();
            formatterRectangleParameters[3] = (int) imageHeight.floatValue();
            return this;
        }

        /**
         *
         * @param x - x coordinate
         * @param y - y coordinate
         * @return Visible Signature Configuration Object
         */
        public PDVisibleSignDesigner coordinates(float x, float y)
        {
            xAxis(x);
            yAxis(y);
            return this;
        }

        /**
         *
         * @return xAxis - gets x coordinates
         */
        public float getxAxis()
        {
            return xAxis;
        }

        /**
         *
         * @param xAxis  - x coordinate
         * @return Visible Signature Configuration Object
         */
        public PDVisibleSignDesigner xAxis(float xAxis)
        {
            this.xAxis = xAxis;
            return this;
        }

        /**
         *
         * @return yAxis
         */
        public float getyAxis()
        {
            return yAxis;
        }

        /**
         *
         * @param yAxis
         * @return Visible Signature Configuration Object
         */
        public PDVisibleSignDesigner yAxis(float yAxis)
        {
            this.yAxis = yAxis;
            return this;
        }

        /**
         *
         * @return signature image width
         */
        public float getWidth()
        {
            return imageWidth;
        }

        /**
         *
         * @param width signature image width
         * @return Visible Signature Configuration Object
         */
        public PDVisibleSignDesigner width(float width)
        {
            this.imageWidth = width;
            this.formatterRectangleParameters[2] = (int) width;
            return this;
        }

        /**
         *
         * @return signature image height
         */
        public float getHeight()
        {
            return imageHeight;
        }

        /**
         *
         * @param height signature image height
         * @return Visible Signature Configuration Object
         */
        public PDVisibleSignDesigner height(float height)
        {
            this.imageHeight = height;
            this.formatterRectangleParameters[3] = (int) height;
            return this;
        }

        /**
         *
         * @return template height
         */
        protected float getTemplateHeight()
        {
            return getPageHeight();
        }

        /**
         *
         * @param templateHeight
         * @return Visible Signature Configuration Object
         */
        private PDVisibleSignDesigner pageHeight(float templateHeight)
        {
            this.pageHeight = templateHeight;
            return this;
        }

        /**
         *
         * @return signature field name
         */
        public String getSignatureFieldName()
        {
            return signatureFieldName;
        }

        /**
         *
         * @param signatureFieldName
         * @return Visible Signature Configuration Object
         */
        public PDVisibleSignDesigner signatureFieldName(String signatureFieldName)
        {
            this.signatureFieldName = signatureFieldName;
            return this;
        }

        /**
         *
         * @return image Image
         */
        public BufferedImage getImage()
        {
            return image;
        }

        /**
         * Read the image stream of the signature and set height and width.
         *
         * @param stream stream of your visible signature image
         * @throws IOException If we can't read, flush, or close stream of image
         */
        private void readImageStream(InputStream stream) throws IOException
        {
            ImageIO.setUseCache(false);
            setImage(ImageIO.read(stream));
        }

        /**
         * Set image and its height and width.
         *
         * @param image
         */
        private void setImage(BufferedImage image)
        {
            this.image = image;
            imageHeight = (float) image.getHeight();
            imageWidth = (float) image.getWidth();
            formatterRectangleParameters[2] = image.getWidth();
            formatterRectangleParameters[3] = image.getHeight();
        }

        /**
         * @return Affine Transform parameters for PDF Matrix
         */
        public AffineTransform getTransform()
        {
            return affineTransform;
        }

        /**
         *
         * @param affineTransform
         * @return Visible Signature Configuration Object
         */
        public PDVisibleSignDesigner transform(AffineTransform affineTransform)
        {
            this.affineTransform = new AffineTransform(affineTransform);
            return this;
        }

        /**
         *
         * @return formatter PDRectangle parameters
         */
        public int[] getFormatterRectangleParameters()
        {
            return formatterRectangleParameters;
        }

        /**
         * Sets formatter PDRectangle
         *
         * @param formatterRectangleParameters
         * @return Visible Signature Configuration Object
         */
        public PDVisibleSignDesigner formatterRectangleParameters(int[] formatterRectangleParameters)
        {
            this.formatterRectangleParameters = formatterRectangleParameters;
            return this;
        }

        /**
         *
         * @return page width
         */
        public float getPageWidth()
        {
            return pageWidth;
        }

        /**
         *
         * @param pageWidth pageWidth
         * @return Visible Signature Configuration Object
         */
        public PDVisibleSignDesigner pageWidth(float pageWidth)
        {
            this.pageWidth = pageWidth;
            return this;
        }

        /**
         *
         * @return page height
         */
        public float getPageHeight()
        {
            return pageHeight;
        }

        /**
         * get image size in percents
         * @return the image size in percent
         */
        public float getImageSizeInPercents()
        {
            return imageSizeInPercents;
        }

       /**
        *
        * @param imageSizeInPercents
        */
        public void imageSizeInPercents(float imageSizeInPercents)
        {
            this.imageSizeInPercents = imageSizeInPercents;
        }

        /**
         * returns visible signature text
         * @return the visible signature's text
         */
        public String getSignatureText()
        {
            throw new UnsupportedOperationException("That method is not yet implemented");
        }

        /**
         *
         * @param signatureText - adds the text on visible signature
         * @return the signature design
         */
        public PDVisibleSignDesigner signatureText(String signatureText)
        {
            throw new UnsupportedOperationException("That method is not yet implemented");
        }
    }