Несколько esign с использованием pdfbox 2.0.12 Java? - PullRequest
0 голосов
/ 16 октября 2018

Я пытался добавить несколько подписей в одном файле PDF на штампе.Я могу добавить несколько штампов.В моем случае на одном я получал сообщение об ошибке

, по крайней мере, одна подпись недействительна.

Я хочу добавить несколько допустимых знаков в одном PDF.Пожалуйста, помогите мне.На изображении допустим только один знак, другие знаки недействительны, поэтому позвольте мне сказать, что я делаю неправильно

Мой снимок кода ниже

public void getSignOnPdf(Map<Integer, byte[]> PdfSigneture1, List<Long> documentIds, List<String> calTimeStamp,
        String originalPdfReadServerPath, String tickImagePath, int serverTime, int pageNumberToInsertStamp,
        String name, String location, String reasonForSign, int xCo_ordinates, int yCo_ordinates,
        int signatureWidth, int signatureHeight, String pdfPassword, String internal_outputFinalPdfPath)
        throws Exception {
    String pdfReadServerPath = null;
    String l_slash = new String();
    String originalPDFPath = new String(originalPdfReadServerPath.trim());

    boolean isCorrectPDFOutputPath = false;
    String aspOutputPdfServerPath = null;
    synchronized (this) {
        if ((internal_outputFinalPdfPath != null) && (!internal_outputFinalPdfPath.trim().isEmpty())) {
            System.out.println("[" + EsignCommonFuntion.generateTimeStampForLog()
                    + "] :1-->  outputFinalPdfPath is: " + internal_outputFinalPdfPath);
            if (!(new File(internal_outputFinalPdfPath)).isFile()) {
                isCorrectPDFOutputPath = true;
                aspOutputPdfServerPath = internal_outputFinalPdfPath;
            } else {
                System.out.println("1--> Please provide directory path for outputFinalPdfPath: "
                        .concat(String.valueOf(internal_outputFinalPdfPath)));
            }
        } else {
            System.out.println(" 1--> outputFinalPdfPath is empty or null: "
                    .concat(String.valueOf(internal_outputFinalPdfPath)));
        }
    }
    boolean isPasswordPresent = false;
    String pdfPasswordForEncryption;
    synchronized (this) {
        if ((pdfPassword != null) && (!pdfPassword.trim().isEmpty())) {
            pdfPasswordForEncryption = pdfPassword.trim();
            isPasswordPresent = true;
        } else {
            pdfPasswordForEncryption = null;
        }
        String pdfOriginalName = (new File(originalPDFPath)).getName();
        String pdfAbsolutePath = originalPDFPath.substring(0, originalPDFPath.lastIndexOf(l_slash));
        if (isPasswordPresent) {
            pdfAbsolutePath = getEncryptedPdfName(originalPDFPath, pdfAbsolutePath + l_slash,
                    pdfPasswordForEncryption, pdfOriginalName);
            pdfReadServerPath = new String(pdfAbsolutePath);
        } else {
            pdfReadServerPath = originalPDFPath;
        }
    }
    ArrayList<String> unSignedFilesList = new ArrayList<String>();

    Map<Integer, byte[]> l_PdfSigneture = PdfSigneture1;

    int actual_pageNumForStamp = 1;

    String pdfFileName = (new File(pdfReadServerPath)).getName();

    FileOutputStream fos = null;

    String nameToShowInSignature = name;
    String locationToShowInSignature = location;
    String reasonForSignatureSign = reasonForSign;

    PDDocument documentFinal = null;
    try {
        pdfReadServerPath = pdfReadServerPath.substring(0, pdfReadServerPath.lastIndexOf(l_slash));
        System.out.println("inside getSignOnMethod pdfAbsolutePath:".concat(String.valueOf(pdfReadServerPath)));
        unSignedFilesList.add(pdfFileName);
        System.out.println("inside getSignOnMethod pdfFileName:".concat(String.valueOf(pdfFileName)));

        String PDFpath = pdfReadServerPath + l_slash + (String) (unSignedFilesList).get(0);

        System.out.println("Inside for PDFpath: ".concat(String.valueOf(PDFpath)));

        String finalOutputPdfName = ((String) (unSignedFilesList).get(0)).substring(0,
                ((String) (unSignedFilesList).get(0)).lastIndexOf(".")) + "_signedFinal.pdf";

        File outFile2 = null;

        if (isCorrectPDFOutputPath) {
            System.out.println("if condition Final signed PDF ouptut Path: " + aspOutputPdfServerPath + l_slash
                    + finalOutputPdfName);
            outFile2 = new File(aspOutputPdfServerPath + l_slash + finalOutputPdfName);
            fos = new FileOutputStream(outFile2);
        } else {
            outFile2 = new File(pdfReadServerPath + l_slash + outFile2);
            fos = new FileOutputStream(outFile2);
        }

        documentFinal = PDDocument.load(new File(PDFpath));

        for (int i = 1; i < 4; i++) {
            FileInputStream image2 = new FileInputStream(tickImagePath);

            PDSignature pdsignature = new PDSignature();
            pdsignature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            pdsignature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);

            Calendar cal = GregorianCalendar.getInstance();
            SimpleDateFormat l_simpleDateFormater = new SimpleDateFormat("yyyyMMdd_HHmmss");
            String timeStamp = (String) calTimeStamp.get(i - 1);

            try {
                cal.setTime(l_simpleDateFormater.parse(timeStamp));
            } catch (ParseException ex) {
                ex.printStackTrace();
            }

            cal.add(12, serverTime);
            pdsignature.setSignDate(cal);
            documentFinal.setDocumentId((Long) documentIds.get(i - 1));

            String dateToShowInSignature = cal.getTime().toString();

            Float saveIncrementalObj1 = null;
            saveIncrementalObj1 = new Float((float) xCo_ordinates, (float) yCo_ordinates, (float) signatureWidth,
                    (float) signatureHeight);

            PDRectangle rect = getPDRectangle(documentFinal, saveIncrementalObj1, i);
            PDVisibleSignDesigner visibleSig;
            (visibleSig = new PDVisibleSignDesigner(documentFinal, image2, i)).xAxis(xCo_ordinates)
                    .yAxis(yCo_ordinates).zoom(-95.0F).signatureFieldName("signature");

            PDVisibleSigProperties visibleSignatureProp = new PDVisibleSigProperties();

            visibleSignatureProp.signerName("name").signerLocation("location").signatureReason("Security")
                    .preferredSize(0).page(i - 1).visualSignEnabled(true).setPdVisibleSignature(visibleSig)
                    .buildSignature();
            try {
                PdfSigneture = new TreeMap<>();
                // PdfSigneture.clear();
                PdfSigneture = l_PdfSigneture;

                if (visibleSignatureProp.isVisualSignEnabled()) {
                    this.options = new SignatureOptions();
                    this.options.setVisualSignature(visibleSignatureProp);
                    this.options.setPage(visibleSignatureProp.getPage());
                    this.options.setVisualSignature(
                            getInputStream(documentFinal, i, rect, tickImagePath, nameToShowInSignature,
                                    locationToShowInSignature, dateToShowInSignature, reasonForSignatureSign));
                    documentFinal.addSignature(pdsignature, this, this.options);
                } else {
                    documentFinal.addSignature(pdsignature, this);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        synchronized (this) {
            SaveIncrementalSignObject saveIncrementalSignObject;
            (saveIncrementalSignObject = new SaveIncrementalSignObject()).setFos(fos);
            saveIncrementalSignObject.setPDDocumentFromFile(documentFinal);

            saveIncrementalForSign(saveIncrementalSignObject);
        }
    } catch (Exception localException2) {
        System.out.println("Insidemethod -- Exception block" + localException2.getMessage());
        return;
    } finally {
        fos.flush();
        if (fos != null) {
            fos.close();
        }
        documentFinal.close();
    }
}

public static synchronized void saveIncrementalForSign(SaveIncrementalSignObject p_SaveIncrementalObj) {
    PDDocument documentFinal = null;
    try {
        (documentFinal = p_SaveIncrementalObj.getPDDocumentFromFile())
                .saveIncremental(p_SaveIncrementalObj.getFos());
    } catch (Exception e) {
        e.printStackTrace();
        try {
        //              documentFinal.close();
            return;
        } catch (Exception eX) {
            eX.printStackTrace();
            return;
        }
    }
}

1 Ответ

0 голосов
/ 16 октября 2018

В комментарии вы уточнили, чего хотите достичь:

Я пытался применить одну подпись в нескольких местах.

Как обсуждалось в первом разделе ниже,это не то, что делает ваш код: ваш код пытается применить несколько сигнатур к одному месту в одной ревизии , что невозможно, как объяснено там.

Применение единственной подписи к нескольким местам в единственной ревизии , с другой стороны, нежелательно для команды спецификации PDF и некоторых подходовчтобы реализовать это, было сделано недействительным по спецификации, но это возможно, как объяснено во втором разделе ниже.

Ваш подход и почему он не может работать

Вы, кажется, пытаетесь применить несколькоподписи за один проход:

if (isPasswordPresent) {
    documentFinal = PDDocument.load(new File(PDFpath), pdfPasswordForEncryption);
} else {
    documentFinal = PDDocument.load(new File(PDFpath));
}

for (int i = 1; i < 4; i++) {
    FileInputStream image2 = new FileInputStream(tickImagePath);

    PDSignature pdsignature = new PDSignature();

    [...]

    try {
        [...]

        if (visibleSignatureProp.isVisualSignEnabled()) {
            [...]
            documentFinal.addSignature(pdsignature, this, this.options);
        } else {
            documentFinal.addSignature(pdsignature, this);
        }
    } catch (Exception e) {
        System.out.println("Inside getSignOnPdf sub exception block at addSignature:" + e + "error :" + e.getMessage());
        e.printStackTrace();
    }
}

synchronized (this) {
    [...]
    saveIncrementalForSign(saveIncrementalSignObject);
}

Это не может работать.

В PDF-файлах несколько подписей применяются одна за другой в отдельных редакциях PDF, а не все параллельно вта же ревизия:

image

Вы можете найти некоторые фоны в этом ответе и документах, на которые есть ссылки.

Таким образом, в псевдокодевместо этого вам нужно сделать следующее:

for (int i = 1; i < 4; i++) {
    load current version of the PDF;
    apply the i'th signature;
    save and sign as new current version of the PDF;
}

Имя метода PDDocument.addSignature может немного ввести в заблуждение, поскольку можно предположить, что можно добавить несколько подписей.Это не вариант;все подписи будут созданы как поля подписи со своими виджетами, но только поле последнего добавленного PDSignature будет подписано, поэтому только это последнее добавленное поле подписи будет иметь разумное значение.

@ Tilman -PDDocument.addSignature, вероятно, должен быть тест, генерирующий исключение, если подпись уже была добавлена ​​с момента загрузки документа.

Обсуждение вашей фактической задачи

Путь объектов PDF изВизуализация подписи на странице PDF для фактической подписи (контейнер подписи CMS в случае субфильтров на основе CMS) не является немедленной.Вместо этого у нас есть

  • страница PDF в ее аннотациях, ссылающаяся на
  • виджет поля подписи (визуализация подписи), принадлежащий
  • поле подписи, ссылающееся на
  • словарь значений подписи, в который встроен контейнер подписи CMS.

Для реализации фактической задачи

применение одной подписи к нескольким местам,

, следовательно, существует несколько вариантов получения с нескольких страниц с отображением подписи в контейнере с единой подписью:

  1. Все страницы с визуализациями подписи, указывающими на одно и то жеаннотация одного виджета поля с одной подписью со словарем значений, содержащим контейнер для подписи.
  2. Каждая страница с визуализациями подписей, указывающими на их собственный виджет, но все виджеты, принадлежащие к одному полю с одной подписью, со словарем значений, содержащимКонтейнер для подписи.
  3. Eкаждая страница с визуализациями подписи, указывающими на собственный виджет, каждый виджет принадлежит отдельному полю подписи, но все они указывают на один и тот же словарь значений, содержащий контейнер подписи.

Давайте теперь посмотрим на PDFспецификация ISO 32000-2.Прежде всего, он предостерегает от использования единой подписи с несколькими визуализациями:

Расположение подписи в документе может иметь отношение к ее юридическому значению.[...]

Если с подписью связано более одного местоположения, значение может стать неоднозначным.

(ISO 32000-2, раздел 12.7.5.5«Поля подписи»)

Соответственно, в спецификации делается попытка запретить отдельные подписи с несколькими визуализациями:

На данный словарь аннотаций следует ссылаться из Annots массив только одной страницы.

(ISO 32000-2, раздел 12.5.2 «Словари аннотаций»)

Это запрещает вышеуказанный вариант 1.

поля подписи никогда не должны ссылатьсяк более чем одной аннотации

(ISO 32000-2, раздел 12.7.5.5 «Поля подписи»)

Это запрещает вариант 2.

Очевидно, что вариант 3 явно не запрещен.Для общих полей формы совместное использование объекта значения даже явно разрешено, поскольку значение поля формы является наследуемым!

Таким образом, строго говоря, создание подписей с несколькими визуализациями возможно с использованием опции 3.

Обратите внимание,хотя команда разработчиков спецификаций PDF явно не собиралась разрешать их, скорее всего, это был недосмотр.Таким образом, вы должны учитывать, что некоторые будущие исправления к спецификации в конечном итоге также будут запрещать вариант 3.

Если вы все же хотите попробовать, должна быть возможность настроить или исправить PDFBox для создания отдельных подписей с несколькими визуализациямииспользуя подход варианта 3.

Это уже доказано возможным, например, для iText, ср. этот ответ .

Более того, образец документа, которым вы поделились, использует эту опцию.

Подтверждение концепции

Как оказалось, ондовольно просто создать мульти-визуализацию PDF-подписи с помощью PDFBox в соответствии с вариантом 3. В частности, это проще, чем делать это с помощью iText, ср. ответ, указанный выше , потому что словарь значений сигнатур здесь - это объект, который человек создает и обрабатывает самостоятельно, тогда как в iText он создается под капотом и как раз вовремя.

Все, что нужно сделатьсостоит в том, чтобы создать один объект PDSignature и сгенерировать с ним одну сигнатуру (используя PDDocument.addSignature), а затем добавить столько других полей сигнатур, сколько нужно, установив свойства значения подписи этих полей в один объект PDSignature, созданный вначало.

Например, вы можете использовать такой метод, чтобы добавить дополнительные поля подписи:

void addSignatureField(PDDocument pdDocument, PDPage pdPage, PDRectangle rectangle, PDSignature signature) throws IOException {
    PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
    List<PDField> acroFormFields = acroForm.getFields();

    PDSignatureField signatureField = new PDSignatureField(acroForm);
    signatureField.setSignature(signature);
    PDAnnotationWidget widget = signatureField.getWidgets().get(0);
    acroFormFields.add(signatureField);

    widget.setRectangle(rectangle);
    widget.setPage(pdPage);

    // from PDVisualSigBuilder.createHolderForm()
    PDStream stream = new PDStream(pdDocument);
    PDFormXObject form = new PDFormXObject(stream);
    PDResources res = new PDResources();
    form.setResources(res);
    form.setFormType(1);
    PDRectangle bbox = new PDRectangle(rectangle.getWidth(), rectangle.getHeight());
    float height = bbox.getHeight();

    form.setBBox(bbox);
    PDFont font = PDType1Font.HELVETICA_BOLD;

    // from PDVisualSigBuilder.createAppearanceDictionary()
    PDAppearanceDictionary appearance = new PDAppearanceDictionary();
    appearance.getCOSObject().setDirect(true);
    PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());
    appearance.setNormalAppearance(appearanceStream);
    widget.setAppearance(appearance);

    try (PDPageContentStream cs = new PDPageContentStream(pdDocument, appearanceStream))
    {
        // show background (just for debugging, to see the rect size + position)
        cs.setNonStrokingColor(Color.yellow);
        cs.addRect(-5000, -5000, 10000, 10000);
        cs.fill();

        float fontSize = 10;
        float leading = fontSize * 1.5f;
        cs.beginText();
        cs.setFont(font, fontSize);
        cs.setNonStrokingColor(Color.black);
        cs.newLineAtOffset(fontSize, height - leading);
        cs.setLeading(leading);
        cs.showText("Signature text");
        cs.newLine();
        cs.showText("some additional Information");
        cs.newLine();
        cs.showText("let's keep talking");
        cs.endText();
    }

    pdPage.getAnnotations().add(widget);

    COSDictionary pageTreeObject = pdPage.getCOSObject(); 
    while (pageTreeObject != null) {
        pageTreeObject.setNeedToBeUpdated(true);
        pageTreeObject = (COSDictionary) pageTreeObject.getDictionaryObject(COSName.PARENT);
    }
}

( CreateMultipleVisualizations вспомогательный метод)

(на самом деле этот метод основан на методе CreateVisibleSignature2.createVisualSignatureTemplate из артефакта примеров pdfbox, но сильно упрощен и теперь используется для создания полей действительной подписи, а не просто шаблона для копирования.)

Используется следующим образом:

try (   InputStream resource = PDF_SOURCE_STREAM;
        OutputStream result = PDF_TARGET_STREAM;
        PDDocument pdDocument = PDDocument.load(resource)   )
{
    PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
    if (acroForm == null) {
        pdDocument.getDocumentCatalog().setAcroForm(acroForm = new PDAcroForm(pdDocument));
    }
    acroForm.setSignaturesExist(true);
    acroForm.setAppendOnly(true);
    acroForm.getCOSObject().setDirect(true);

    PDRectangle rectangle = new PDRectangle(100, 600, 300, 100);
    PDSignature signature = new PDSignature();
    signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
    signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
    signature.setName("Example User");
    signature.setLocation("Los Angeles, CA");
    signature.setReason("Testing");
    signature.setSignDate(Calendar.getInstance());
    pdDocument.addSignature(signature, this);

    for (PDPage pdPage : pdDocument.getPages()) {
        addSignatureField(pdDocument, pdPage, rectangle, signature);
    }

    pdDocument.saveIncremental(result);
}

( CreateMultipleVisualizations test testCreateSignatureWithMultipleVisualizations)

каждый получает PDFс визуализацией подписи на каждой странице итогового документа (и дополнительной невидимой, потому что я немного ленив), но только с одним фактическим значением подписи (учитывая, что this реализует SignatureInterface с помощью метода byte[] sign(InputStream)).

Осторожно, хотя:

  • Метод PDSignatureField setSignature в PDFBox 3.0.0-SNAPSHOT устарел.В конечном итоге вам, возможно, придется внедрить объект PDSignature, используя более низкоуровневые методы.
  • Этот вид мульти-визуализации не нужен командам спецификации PDF.Скорее всего, в конечном итоге они будут запрещены.
...