// Copyright (C) 2003-2026, Foxit Software Inc..
// All Rights Reserved.
//
// http://www.foxitsoftware.com
//
// The following code is copyrighted and contains proprietary information and trade secrets of Foxit Software Inc..
// You cannot distribute any part of Foxit PDF SDK to any third party or general public,
// unless there is a separate license agreement with Foxit Software Inc. which explicitly grants you such rights.
//
// bouncycastle
// Copyright (c) 2000 - 2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// This file contains an example to demonstrate how to use Foxit PDF SDK to add, sign and
// verify paging seal signature in PDF document.

import com.foxit.sdk.PDFException;
import com.foxit.sdk.common.DateTime;
import com.foxit.sdk.common.Image;
import com.foxit.sdk.common.Library;
import com.foxit.sdk.common.fxcrt.FileReaderCallback;
import com.foxit.sdk.common.fxcrt.StreamCallback;
import com.foxit.sdk.common.fxcrt.PauseCallback;
import com.foxit.sdk.common.fxcrt.RectF;
import com.foxit.sdk.pdf.*;
import com.foxit.sdk.pdf.annots.Widget;
import com.foxit.sdk.common.Range;

import static com.foxit.sdk.common.Constants.e_ErrSuccess;
import static com.foxit.sdk.pdf.Signature.*;

import java.util.*;
import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.util.Calendar;
import java.util.Enumeration;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1OutputStream;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.*;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;

class CryptNoRestrict {
	static X509Certificate[] certificates = null;
    static PrivateKey privateKey = null;
    static BouncyCastleProvider bouncy_castle_provider = null;

    static public char[] convertByteArrayToCharArray(byte[] bytes_array) {
        Charset utf8_charset = Charset.forName("UTF-8");
        ByteBuffer byte_buffer = ByteBuffer.allocate(bytes_array.length);
        byte_buffer.put(bytes_array).flip();
        CharBuffer char_buffer = utf8_charset.decode(byte_buffer);
        return char_buffer.array();
    }

    static public byte[] SignMsg(final byte[] to_be_signed_data, final String key_file,
            final byte[] password) throws Exception {
        bouncy_castle_provider = new BouncyCastleProvider();
        Security.addProvider(bouncy_castle_provider);
        KeyStore key_store = KeyStore.getInstance("PKCS12");
        FileInputStream fis = new FileInputStream(key_file);
        key_store.load(fis, convertByteArrayToCharArray(password));
        fis.close();

        Enumeration aliases_enumeration = key_store.aliases();
        String key_alias = null;     
        if (aliases_enumeration != null && aliases_enumeration.hasMoreElements()) {
            key_alias = (String)aliases_enumeration.nextElement();
        }        
        Certificate[] certs = key_store.getCertificateChain(key_alias);
        Certificate cert = key_store.getCertificate(key_alias);
        privateKey = (PrivateKey) key_store.getKey(key_alias, convertByteArrayToCharArray(password));
        X509Certificate cerx509 = (X509Certificate) cert;
        List<X509Certificate> cert_list = new ArrayList<X509Certificate>();
        cert_list.add(cerx509);
        Store certs_store = new JcaCertStore(cert_list);
        JcaContentSignerBuilder content_signer_builder = new JcaContentSignerBuilder("SHA1WithRSAEncryption");
        ContentSigner content_signer = content_signer_builder.setProvider(bouncy_castle_provider).build(privateKey);
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        JcaSignerInfoGeneratorBuilder jcg_signer_info_generator_builder = new JcaSignerInfoGeneratorBuilder(
                new JcaDigestCalculatorProviderBuilder().setProvider(bouncy_castle_provider).build());
        gen.addSignerInfoGenerator(
                jcg_signer_info_generator_builder.setDirectSignature(true).build(content_signer, cerx509));
        gen.addCertificates(certs_store);
        CMSTypedData cms_typed_data = new CMSProcessableByteArray(to_be_signed_data);
        CMSSignedData signed_data = gen.generate(cms_typed_data, true);
        ByteArrayOutputStream byte_array_output_stream = new ByteArrayOutputStream();
        ASN1InputStream asn1_input_stream = new ASN1InputStream(signed_data.getEncoded());
        ASN1OutputStream ans1_output_stream = ASN1OutputStream.create(byte_array_output_stream);
        ans1_output_stream.writeObject(asn1_input_stream.readObject());
        return byte_array_output_stream.toByteArray();
    }

    static public boolean VerifyMsg(byte[] plain_text, byte[] to_be_verified,
            String cert_file) throws Exception {
        CMSProcessable content = new CMSProcessableByteArray(plain_text);
        CMSSignedData cms_signed_data = new CMSSignedData(content, to_be_verified);
        Store cert_store = cms_signed_data.getCertificates();

        SignerInformationStore signer_info_store = cms_signed_data.getSignerInfos();
        Iterator<?> iterator = signer_info_store.getSigners().iterator();
        int verified = 0, size = 0;

        while (iterator.hasNext()) {
            SignerInformation signer_info = (SignerInformation) iterator.next();
            @SuppressWarnings("unchecked")
            Collection<?> cert_collection = cert_store.getMatches(signer_info.getSID());
            Iterator<?> cert_iterator = cert_collection.iterator();
            X509CertificateHolder cert = (X509CertificateHolder) cert_iterator.next();
            if (!signer_info
                    .verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(bouncy_castle_provider).build(cert))) {
                return false;
            }
        }
        return true;
    }
}

//Used for implementing SignatureCallback.
class DigestContext {
	public FileReaderCallback file_read_callback_;
	public int[] byte_range_array_;
	public int byte_range_array_size_;

	DigestContext(FileReaderCallback file_read_callback,
			int[] byte_range_array, int byte_range_array_size) {
		this.file_read_callback_ = file_read_callback;
		this.byte_range_array_ = byte_range_array;
		this.byte_range_array_size_ = byte_range_array_size;
	}
};

//Implementation of SignatureCallback
class SignatureCallbackImpl extends SignatureCallback {
    private DigestContext digest_context_ = null;
    protected MessageDigest message_digest_sha1_ = null;
    byte[] digest_ = null;
    private CryptNoRestrict cryptNoRestrict = new CryptNoRestrict();

    SignatureCallbackImpl() {
    }

    @Override
    public void release() {
    }

    @Override
    public boolean startCalcDigest(FileReaderCallback file, int[] byte_range_array,
            com.foxit.sdk.pdf.Signature signature, Object client_data) {
        digest_context_ = new DigestContext(file, byte_range_array, byte_range_array.length);
        try {
            message_digest_sha1_ = MessageDigest.getInstance("SHA-1");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return true;
    }

    @Override
    public int continueCalcDigest(Object client_data, PauseCallback pause) {
        try {
            FileReaderCallback filehandler = digest_context_.file_read_callback_;
            byte[] arr1 = new byte[digest_context_.byte_range_array_[1]];
            filehandler.readBlock(arr1,
                    digest_context_.byte_range_array_[0],
                    digest_context_.byte_range_array_[1]);
            byte[] arr2 = new byte[digest_context_.byte_range_array_[3]];
            filehandler.readBlock(arr2,
                    digest_context_.byte_range_array_[2],
                    digest_context_.byte_range_array_[3]);
            byte[] arrall = new byte[(int) digest_context_.byte_range_array_[1]
                    + (int) digest_context_.byte_range_array_[3]];
            System.arraycopy(arr1, 0, arrall, 0, arr1.length);
            System.arraycopy(arr2, 0, arrall, arr1.length, arr2.length);
            digest_ = message_digest_sha1_.digest(arrall);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return com.foxit.sdk.common.Progressive.e_Finished;
    }

    @Override
    public byte[] getDigest(Object client_data) {
        return digest_;
    }

    @Override
    public byte[] sign(byte[] digest, String cert_path, byte[] cert_password, int digest_algorithm,
            java.lang.Object client_data) {
        try {
            byte[] encryptStr = cryptNoRestrict.SignMsg(digest, cert_path, cert_password);
            return encryptStr;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public byte[] sign(byte[] digest, StreamCallback cert_path, byte[] cert_password, int digest_algorithm,
            java.lang.Object client_data) {
        return null;
    }

    @Override
    public int verifySigState(byte[] var1, byte[] var2, Object var3) {
        boolean verify_state = false;
        try {
            verify_state = cryptNoRestrict.VerifyMsg(var1, var2,
                    paging_seal_signature.input_path + "foxit.cer");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return verify_state ? com.foxit.sdk.pdf.Signature.e_StateVerifyNoChange
                : com.foxit.sdk.pdf.Signature.e_StateVerifyChange;
    }

    @Override
    public boolean isNeedPadData() {
        return false;
    }

    @Override
    public int checkCertificateValidity(String cert_path, byte[] cert_password, java.lang.Object client_data) {
        // User can check the validity of input certificate here.
        // If no need to check, just return e_CertValid.
        return com.foxit.sdk.pdf.SignatureCallback.e_CertValid;
    }
}

public class paging_seal_signature {
	public static String sn = "nkyvo37UxrKdu5Peq294Eb+g2ihRldgPQEP6MFtAM4LwtxXXJ+T7MQ==";
	public static String key = "8f3g1sFttW8NAgfSNv+BvQJm2vDtTY+wGQ/1PcmGtqKTwvvI8mLE4KvuHocWH+XZnP3U+Vh1BnqpgQIjO0jwRz7evDdlijTdutpBEsozaXEvFpLbixmsSGvtznflVsuEmsu5ytLwVJiTap2VcCiiSbyuvQwQu88I8BxJ+XXv3LPXN5v/+00zDrZ+V5yLefi+bk7JX5FRE2jfSnOJIqEaVkHOpCIjwf9aszl01YoY7x0EAehg0ILal+v2QLLo7HnkAVt/TA31rddZexhq/2hwAwfy4OfvkJPnESppD7D+SAsX5CfXoU55P1vmyRe+lKBM6pOcarUbuaN4Vjj62SD0sTDbJZ2Ypyp+kcIxZ9wcD+Mg8aSimr5+hg8wZJT0u0mIAdZhTJ++A9sq9OmVAfL2QQ6bh2ISyKPGJPv29u738bAokRjqzZy9bi30mYfGLEwKChEd2R2NBDKZTDOVxFwpzD1IbF0/wN/DDbMO2gS0xUvgSbJyuXfA3ALtmoUH9I9iScaBVon5oYRRy6OTnFMgDgVM+nFTzWbaGKZfO+PMBnreaQ/Ezr9wH7YKJ+LrM4r6xVQjcHDUyx4dlVLe3zQlDRKGGChhyvymVDBuZR8B0/M9Y/DKr1sQAzp/W1Wsz+LZb+JUIxLBrJzgRLtOliuLF+JOTy036W0shWcXYYc2bwLdPWP/BXygmnopzUTCtuo/QLVTRNdENFqciYYvSHEZxgV1sUFpuBqIMUEigWR/2gMlMDiTVq0udEqn8g9bkw1SImVy50v2gwEbUHkEa243BP/3/Dae0YhH03NDUlTSVBKX6w1UVQK6/7aDL3A4EEg47YmKgh6NsABzRsgSvLBhBQNqzOoUvnKaf7YMlw0ns06BdFswP6DaY1KmuRR2Xn6MtDTshGNtT9+azHPqaAWaZs1rHz3fGYb5pfJHptJlx+r9609bY+ZhCERLYG+0lTNWCFnDxdI8Vj1BG4iGWaDBkIC5qdJI3qDLVDABdtIk43fMhgfr+TUqds+G6KBDapS9p32nKZc6lBqvlNN12Pv1AJCC1rwfECzmNQi3MXogQCxzi5pHAgd/BGyGwo4RWhFrKFN869H0whvDVC8NJM2upFrEEJET4fecQyMLWHuTCWvjRZclLC/GGOj9PAXHOcEdFkq4UkloMulXLGsQzkzHB3Elvd2NnxZ2+3FK2e0xNx+rBMru/i/HHLG6WZvz6Jz7mjmibNfBExydtEI5lWUZ1Wsy0r17kyvlME9Bot7XXPkx/+cKTcxfNWSb3kaPROYIgSpGjrXxfrphBnr1mioJPq3WrtP9E5lZBeMd8jxXT4NlX3T0frsci6Xy1ChB/BytU7pvn6bMfddTnEDd/QpkW+6WYOnHHrIYVk2ngKjUBl5nGOkp37wpgUpWp5tcICjpbjfNdY43VfTyHRf4dGRR";

	public static String output_path = "../output_files/";
	public static String input_path = "../input_files/";
	public static String input_file = input_path + "AboutFoxit.pdf";
	public static String output_directory = output_path + "paging_seal_signature/";

 // You can also use System.load("filename") instead. The filename argument must be an absolute path name.
 static {
     String os = System.getProperty("os.name").toLowerCase();
     String lib = "fsdk_java_";
     if (os.startsWith("win")) {
         lib += "win";
     } else if (os.startsWith("mac")) {
         lib += "mac";
     } else {
         lib += "linux";
   	}
        
       if (System.getProperty("sun.arch.data.model").equals("64")) {
        if(System.getProperty("os.arch").equals("aarch64")){
          if(os.startsWith("mac")){
             lib += "arm";
           }
           else{
             lib += "armv8";
           }
         }
         else{
            lib += "64";
         }
        } else {
         if(System.getProperty("os.arch").equals("arm")){
            lib += "armv7";
         }
         else{
            lib += "32";
         }
       }
     System.loadLibrary(lib);
 }

	static String TransformSignatureStateToString(int sig_state) {
		if (0 == sig_state)
			return "Unknown";
		String state_str = "";
		if ((sig_state & e_StateNoSignData) > 0)
			state_str += "NoSignData";
		if ((sig_state & e_StateUnsigned) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "Unsigned";
		}
		if ((sig_state & e_StateSigned) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "Signed";
		}
		if ((sig_state & e_StateVerifyValid) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerfiyValid";
		}
		if ((sig_state & e_StateVerifyInvalid) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyInvalid";
		}
		if ((sig_state & e_StateVerifyErrorData) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyErrorData";
		}
		if ((sig_state & e_StateVerifyNoSupportWay) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyNoSupportWay";
		}
		if ((sig_state & e_StateVerifyErrorByteRange) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyErrorByteRange";
		}
		if ((sig_state & e_StateVerifyChange) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyChange";
		}
		if ((sig_state & e_StateVerifyNoChange) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyNoChange";
		}
		if ((sig_state & e_StateVerifyIncredible) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyIncredible";
		}
		if ((sig_state & e_StateVerifyIssueValid) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyIssueValid";
		}
		if ((sig_state & e_StateVerifyIssueUnknown) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyIssueUnknown";
		}
		if ((sig_state & e_StateVerifyIssueRevoke) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyIssueRevoke";
		}
		if ((sig_state & e_StateVerifyIssueExpire) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyIssueExpire";
		}
		if ((sig_state & e_StateVerifyIssueUncheck) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyIssueUncheck";
		}
		if ((sig_state & e_StateVerifyIssueCurrent) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyIssueCurrent";
		}
		if ((sig_state & e_StateVerifyTimestampNone) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyTimestampNone";
		}
		if ((sig_state & e_StateVerifyTimestampDoc) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyTimestampDoc";
		}
		if ((sig_state & e_StateVerifyTimestampValid) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyTimestampValid";
		}
		if ((sig_state & e_StateVerifyTimestampInvalid) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyTimestampInvalid";
		}
		if ((sig_state & e_StateVerifyTimestampExpire) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyTimestampExpire";
		}
		if ((sig_state & e_StateVerifyTimestampIssueUnknown) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyTimestampIssueUnknown";
		}
		if ((sig_state & e_StateVerifyTimestampIssueValid) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyTimestampIssueValid";
		}
		if ((sig_state & e_StateVerifyTimestampTimeBefore) > 0) {
			if (state_str.length() > 0)
				state_str += "|";
			state_str += "VerifyTimestampTimeBefore";
		}
		return state_str;
	}

	static String DateTimeToString(DateTime datetime) {
		return String.format("%d/%d/%d-%d:%d:%d %s%d:%d", datetime.getYear(),
				datetime.getMonth(), datetime.getDay(), datetime.getHour(),
				datetime.getMinute(), datetime.getSecond(),
				datetime.getUtc_hour_offset() > 0 ? "+" : "-",
				datetime.getUtc_hour_offset(), datetime.getUtc_minute_offset());
	}

	static DateTime GetLocalDateTime() {
		Calendar c = Calendar.getInstance();

		DateTime datetime = new DateTime();
		datetime.setYear(c.get(Calendar.YEAR));
		datetime.setMonth(c.get(Calendar.MONTH) + 1);
		datetime.setDay(c.get(Calendar.DATE));
		datetime.setHour(c.get(Calendar.HOUR));
		datetime.setMinute(c.get(Calendar.MINUTE));
		datetime.setSecond(c.get(Calendar.SECOND));
		
		java.util.TimeZone timeZone = c.getTimeZone();
		int offset = timeZone.getRawOffset();
		int gmt = offset/(3600*1000);
		
		datetime.setUtc_hour_offset((short)gmt);
		datetime.setUtc_minute_offset(offset%(3600*1000) / 60);
		return datetime;
	}

	static com.foxit.sdk.pdf.PagingSealSignature AddPagingSealSignature(PDFDoc pdf_doc,
			String sub_filter, boolean is_on_perforation) throws Exception {
		PDFPage pdf_page = pdf_doc.getPage(0);
		float page_height = pdf_page.getHeight();
		float page_width = pdf_page.getWidth();
		float sig_width =  page_width * (float)0.4;
		float sig_hegiht = page_height * (float)0.1;
		// Add new paging seal signature to document.
		Range range = new Range(1,pdf_doc.getPageCount(), Range.e_All);
		com.foxit.sdk.pdf.PagingSealSignature new_sig = pdf_doc.addPagingSealSignature(range, sig_width, sig_hegiht, true);
		if (new_sig.isEmpty()) {
			throw new Exception("Add signature failed!");
		}
		PagingSealConfig paging_seal_config = new PagingSealConfig(PagingSealConfig.e_PagingSealPositionLeft, (float)page_width/2, 0.5f, is_on_perforation, PagingSealConfig.e_PagingSealStyleMultipleSignatures, false, 0);
		new_sig.setPagingSealConfig(paging_seal_config);

		// Set values for the new paging seal signature.
		new_sig.setKeyValue(e_KeyNameSigner, "Foxit PDF SDK");
		String new_value = String.format(String.format(
				"As a sample for subfilter \"%s\"", sub_filter));
		new_sig.setKeyValue(e_KeyNameReason, String.format(new_value));
		new_sig.setKeyValue(e_KeyNameContactInfo, "support@foxitsoftware.com");
		new_sig.setKeyValue(e_KeyNameDN, "CN=CN,MAIL=MAIL@MAIL.COM");
		new_sig.setKeyValue(e_KeyNameLocation, "Fuzhou, China");
		new_value = String.format(String.format(
				"As a sample for subfilter \"%s\"", sub_filter));
		new_sig.setKeyValue(e_KeyNameText, String.format(new_value));
		DateTime sign_time = GetLocalDateTime();
		new_sig.setSignTime(sign_time);
		String image_file_path = input_path + "FoxitLogo.jpg";
		Image image = new Image(image_file_path);
		new_sig.setImage(image, 0);
		// Set appearance flags to decide which content would be used in appearance.
		int ap_flags = e_APFlagLabel | e_APFlagSigner | e_APFlagReason
				| e_APFlagDN | e_APFlagLocation | e_APFlagText
				| e_APFlagSigningTime | e_APFlagBitmap;
		new_sig.setAppearanceFlags(ap_flags);
		boolean gaResult = new_sig.generateAppearance();

		return new_sig;
	}

	static void AdobePPKLitePagingSealSignature(PDFDoc pdf_doc, boolean use_default) throws Exception {
		String filter = "Adobe.PPKLite";
		String sub_filter = "adbe.pkcs7.detached";

		if (!use_default) {
			sub_filter = "adbe.pkcs7.sha1";
			SignatureCallbackImpl sig_callback = new SignatureCallbackImpl();
			Library.registerSignatureCallback(filter, sub_filter, sig_callback);
		}
		
		System.out.println(String.format("Use signature callback object for filter \"%s\" and sub-filter \"%s\"",filter, sub_filter));	
		// Add a new paging seal signature to document.						
		boolean is_on_perforation = true;
		if (use_default) is_on_perforation = false;
		com.foxit.sdk.pdf.PagingSealSignature new_paging_seal_signature = AddPagingSealSignature(pdf_doc, sub_filter, is_on_perforation);		
		// Set filter and subfilter for the new paging seal signature.
		new_paging_seal_signature.setFilter(filter);
		new_paging_seal_signature.setSubFilter(sub_filter);
		boolean is_signed = new_paging_seal_signature.isSigned();
		int sig_state = new_paging_seal_signature.getState();
		System.out.println(String.format(
				"[Before signing] Signed?:%s\t State:%s", is_signed ? "true"
						: "false", TransformSignatureStateToString(sig_state)));

		// Sign the new signature.
		String signed_pdf_path = output_directory + "signed_middle_paging_seal_signature.pdf";
		if (use_default)
			signed_pdf_path = output_directory + "signed_left_paging_seal_signature_default_handler.pdf";
		String cert_file_path = input_path + "foxit_all.pfx";
		String cert_file_password = "123456";

		// Cert file path will be passed back to application through callback function SignatureCallback::Sign().
		// In this demo, the cert file path will be used for signing in callback function SignatureCallback::Sign().
		new_paging_seal_signature.startSign(cert_file_path, cert_file_password.getBytes(),
				e_DigestSHA1, signed_pdf_path, null, null);

		System.out.println("[Sign] Finished!");
		is_signed = new_paging_seal_signature.isSigned();
		sig_state = new_paging_seal_signature.getState();
		System.out.println(String.format(
				"[After signing] Signed?:%s\tState:%s", is_signed ? "true"
						: "false", TransformSignatureStateToString(sig_state)));

		// Open the signed document and verify the newly added paging seal signature (which is the last one).
		System.out.println(String.format("Signed PDF file: %s",
				signed_pdf_path));
		PDFDoc signed_pdf_doc = new PDFDoc(signed_pdf_path);
		int error_code = signed_pdf_doc.load(null);
		if (e_ErrSuccess != error_code) {
			System.out.println("Fail to open the signed PDF file.");
			return;
		}
		// Get the last signature which is just added and signed.
		int sig_count = signed_pdf_doc.getSignatureCount();
		com.foxit.sdk.pdf.Signature signed_signature = signed_pdf_doc
				.getSignature(sig_count - 1);
		// Verify the intergirity of signature.
		signed_signature.startVerify(null, null);
		System.out.println("[Verify] Finished!");
		is_signed = signed_signature.isSigned();
		sig_state = signed_signature.getState();
		System.out.println(String.format(
				"[After verifying] Signed?:%s\tState:%s",
				is_signed ? "true" : "false",
				TransformSignatureStateToString(sig_state)));
	}

	static void CheckPagingSealSignatureInfo(PDFDoc pdf_doc) throws PDFException {
		int sig_count = pdf_doc.getSignatureCount();
		if (sig_count < 1) {
	        System.out.println("No signature in current PDF file.\r\n");
		    return;
		}
		for (int i = 0; i < sig_count; i++) {
			System.out.println(String.format("Signature index: %d\r\n", i));
		    com.foxit.sdk.pdf.Signature signature = pdf_doc.getSignature(i);
		    

		    if (signature.isEmpty()) continue;
			if (signature.getSignatureType() != com.foxit.sdk.pdf.Signature.e_SignatureTypePagingSeal) continue;
		    boolean is_signed = signature.isSigned();
		    System.out.println(String.format("Signed?:%s", is_signed ? "true": "false"));
		    int sig_state = signature.getState();
		    System.out.println(String.format("State:%s", TransformSignatureStateToString(sig_state)));
		    Widget widget_annot = signature.getControl(0).getWidget();
			System.out.println(String.format("Object number %d of related widget annotation's dictionary", widget_annot.getDict().getObjNum()));

			String filter = signature.getFilter();
			System.out.println(String.format("Filter:%s", filter));
			String sub_filter = signature.getSubFilter();
			System.out.println(String.format("Sub-filter:%s", sub_filter));
		    if (is_signed) {
				DateTime sign_time = signature.getSignTime();
				String time_str = DateTimeToString(sign_time);
			    System.out.println(String.format("Sign Time:%s", time_str));
		    }
		    String key_value;
			key_value = signature.getKeyValue(e_KeyNameSigner);
			System.out.println(String.format("Signer:%s", key_value));
			key_value = signature.getKeyValue(e_KeyNameLocation);
			System.out.println(String.format("Location:%s", key_value));
			key_value = signature.getKeyValue(e_KeyNameReason);
			System.out.println(String.format("Reason:%s", key_value));
			key_value = signature.getKeyValue(e_KeyNameContactInfo);
			System.out.println(String.format("Contact Info:%s", key_value));
			key_value = signature.getKeyValue(e_KeyNameDN);
			System.out.println(String.format("DN:%s", key_value));
			key_value = signature.getKeyValue(e_KeyNameText);
			System.out.println(String.format("Text:%s", key_value));
		}
	}

	private static void createResultFolder(String output_path) {
		File myPath = new File(output_path);
		if (!myPath.exists()) {
			myPath.mkdir();
		}
	}

	public static void main(String[] args) throws Exception {
		createResultFolder(output_directory);
		// Initialize library.
		int error_code = Library.initialize(sn, key);
		if (error_code != e_ErrSuccess) {
			System.out.println(String.format("Library Initialize Error: %d\n",
					error_code));
			return;
		}
		System.out.println(String.format("Input file path: %s\r\n", input_file));
				
		for (int i = 0; i < 2; i++) {		
			PDFDoc pdf_doc = new PDFDoc(input_file);
			pdf_doc.startLoad(null, false, null);

			// Check information of existed signature(s) in PDF file if there's any paging seal signature in the PDF file.
			CheckPagingSealSignatureInfo(pdf_doc);
			// Add paging seal signature, sign it and verify it with filter "Adobe.PPKLite" and different subfilter.
	    
			AdobePPKLitePagingSealSignature(pdf_doc, i > 0 ? true:false);
			String signed_pdf_path = output_directory + "signed_middle_paging_seal_signature.pdf";
			if (i > 0)
				signed_pdf_path = output_directory + "signed_left_paging_seal_signature_default_handler.pdf";
			PDFDoc pdf_doc1 = new PDFDoc(signed_pdf_path);
			pdf_doc1.startLoad(null, false, null);
			CheckPagingSealSignatureInfo(pdf_doc1);
		}

		Library.release();
	}

}

