Заполнена акроформа PDFBOX, но при открытии в Acrobat reader значения исчезают

#java #pdfbox #adobe-reader #acrofields

#java #pdfbox #adobe-reader #acrofields

Вопрос:

У меня есть форма PDF, я пытаюсь заполнить ее с помощью PDFBOX. Это работает, форма заполнена, и я открываю ее с помощью другого устройства чтения или браузера, я вижу значения, однако, когда я пытаюсь открыть в Adobe Reader, значения исчезают, я перепробовал все возможные способы выяснить, почему, но значения не видны.

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

Я не уверен, связано ли это с моим кодом или Adobe Reader.

Ссылка на форму PDF, которую мне нужно заполнить

вот мой код для заполнения формы.

 
abstract class AbstractPDFFormFiller<T> {

    private val logger = LogManager.getLogger()
    private val merge = PDFMergerUtility()

    private val PAGE_SIZE = 12

    fun fillForm(templatePath: String, data: List<T>, headerParam: Map<String, String>): PDDocument {
        val chunks = getDataChunks(data)
        val totalPages = chunks.size

        if (totalPages == 1) {
            val sourceDocument = getTemplate(templatePath)
            val form = sourceDocument.documentCatalog.acroForm
            fillHeader(form, headerParam, totalPages, totalPages, data)
            fillData(form, data, totalPages)
            return sourceDocument
        } else {
            val resultDocument = PDDocument()
            chunks.forEachIndexed { currentPage, it ->
                val sourceDocument = getTemplate(templatePath)
                val form = sourceDocument.documentCatalog.acroForm
                fillHeader(form, headerParam, currentPage, totalPages, it)
                fillData(form, it, currentPage)
//                mergePDFForm(resultDocument, sourceDocument)
                sourceDocument.save("C:\Users\\Documents\Downloads\$currentPage.pdf")
                sourceDocument.close()
            }
            mergeFromDisk(File("C:\Users\Documents\Downloads"),resultDocument)
            return resultDocument
        }
    }

    fun mergeFromDisk(folderPath:File, resultDoc:PDDocument){
        folderPath.listFiles()?.forEach {
            mergePDFForm(resultDoc, PDDocument.load(it))
        }
    }

    private fun mergePDFForm(destination: PDDocument, source: PDDocument) {
        try {
            source.documentCatalog.acroForm.flatten()
            merge.acroFormMergeMode = PDFMergerUtility.AcroFormMergeMode.JOIN_FORM_FIELDS_MODE
            merge.appendDocument(destination, source)
            merge.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly())
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    fun getTemplate(templatePath: String): PDDocument {
        val s = javaClass.classLoader?.getResource(templatePath)?.openStream()
        return PDDocument.load(s)
    }

    private fun getDataChunks(data: List<T>): List<List<T>> {
        return data.chunked(PAGE_SIZE)
    }

    fun setValue(
        form: PDAcroForm,
        fullyQualifiedName: String,
        value: String,
        rename: Boolean = false,
        reNameTo: String?
    ) {
        val field = form.getField(fullyQualifiedName)
        field.setValue(value)
        if (rename)
            renameField(
                form = form,
                fullyQualifiedName = fullyQualifiedName,
                newName = "${field.fullyQualifiedName}_$reNameTo"
            )
    }

    fun renameField(form: PDAcroForm, fullyQualifiedName: String, newName: String) {
        val field = form.getField(fullyQualifiedName)
            ?: throw IllegalArgumentException("Field with $fullyQualifiedName not found")
        if (field.actions != null amp;amp; field.actions.f != null) field.actions.f = null
        try {
            field.partialName = newName
        } catch (e: Exception) {
            logger.fatal("Cannot rename to PDF form name {} to new name {}", fullyQualifiedName, newName)
        }
    }


    abstract fun fillHeader(
        form: PDAcroForm,
        map: Map<String, String>,
        currentPage: Int,
        totalPage: Int,
        data: List<T>
    )

    abstract fun fillData(form: PDAcroForm, data: List<T>, currentPage: Int)
}


class DiversionDataFormFiller : AbstractPDFFormFiller<DiversionData>() {
    private val logger = LogManager.getLogger()

    override fun fillHeader(
        form: PDAcroForm,
        map: Map<String, String>,
        currentPage: Int,
        totalPage: Int,
        data: List<DiversionData>
    ) {
        form.getField("Product Type").setValue(map["Product Type"])
        form.getField("Type of Schedule").setValue(map["Type of Schedule"])
        form.getField("LA Revenue Account Number").setValue(map["LA Revenue Account Number"])
        form.getField("Company Name").setValue(map["Company Name"])
        form.getField("Filling Period").setValue(map["Filling Period"])
        form.getField("Page").setValue((currentPage   1).toString())
        form.getField("of").setValue(totalPage.toString())
        form.getField("Total").setValue(data.stream().mapToDouble(DiversionData::quantity).sum().toString())
        renameField(form, "Product Type", "Product Type_$currentPage")
        renameField(form, "Type of Schedule", "Type of Schedule_$currentPage")
        renameField(form, "LA Revenue Account Number", "LA Revenue Account Number_$currentPage")
        renameField(form, "Company Name", "Company Name_$currentPage")
        renameField(form, "Filling Period", "Filling Period_$currentPage")
        renameField(form, "Total", "Total_$currentPage")
        renameField(form, "Page", "Page_$currentPage")
        renameField(form, "of", "of_$currentPage")
    }

    override fun fillData(form: PDAcroForm, data: List<DiversionData>, currentPage: Int) {
        val fieldTree = form.fieldTree
        data.forEachIndexed { i, element ->
            fieldTree.forEach {
                if (it.fieldType == "Tx") {
                    try {
                        if (it.fullyQualifiedName.startsWith("Date") amp;amp; it.partialName == i.toString()) {
                            logger.info(
                                "renaming {} to {}, {}",
                                it.fullyQualifiedName,
                                "${it.fullyQualifiedName}_$currentPage",
                                it.partialName
                            )
/*                            it.setValue(Util.dateToStr(element.date, "MM/dd/yy"))
                            renameField(form, it.fullyQualifiedName, "${it.partialName}_$currentPage")*/
                            setValue(
                                form,
                                it.fullyQualifiedName,
                                Util.dateToStr(element.date, "MM/dd/yy"),
                                true,
                                "$currentPage"
                            )
                        } else if (it.fullyQualifiedName.startsWith("Name2") amp;amp; it.partialName == i.toString()
                        ) {
                            it.setValue(element.shipperTaxPayerNumber)
                            renameField(form, it.fullyQualifiedName, "${it.fullyQualifiedName}_$currentPage")
                        } else if (it.fullyQualifiedName.startsWith("Name") amp;amp; it.partialName == i.toString()
                        ) {
                            it.setValue(element.supplierTaxPayerNumber)
                            renameField(form, it.fullyQualifiedName, "${it.fullyQualifiedName}_$currentPage")
                        } else if (it.fullyQualifiedName.startsWith("Diversion Number") amp;amp; it.partialName == i.toString()
                        ) {
                            it.setValue(element.importNumber)
                            renameField(form, it.fullyQualifiedName, "${it.fullyQualifiedName}_$currentPage")
                        } else if (it.fullyQualifiedName.startsWith("FEIN2") amp;amp; it.partialName == i.toString()
                        ) {
                            it.setValue(element.shipperTaxPayerNumber)
                            renameField(form, it.fullyQualifiedName, "${it.fullyQualifiedName}_$currentPage")
                        } else if (it.fullyQualifiedName.startsWith("FEIN") amp;amp; it.partialName == i.toString()
                        ) {
                            it.setValue(element.supplierTaxPayerNumber)
                            renameField(form, it.fullyQualifiedName, "${it.fullyQualifiedName}_$currentPage")
                        } else if (it.fullyQualifiedName.startsWith("Mode") amp;amp; it.partialName == i.toString()
                        ) {
                            it.setValue("J")
                            renameField(form, it.fullyQualifiedName, "${it.fullyQualifiedName}_$currentPage")
                        } else if (it.fullyQualifiedName.startsWith("Manifest") amp;amp; it.partialName == i.toString()
                        ) {
                            it.setValue(element.billOfLading)
                            renameField(form, it.fullyQualifiedName, "${it.fullyQualifiedName}_$currentPage")
                        } else if (it.fullyQualifiedName.startsWith("Doc. Number") amp;amp; it.partialName == i.toString()
                        ) {
                            it.setValue(element.billOfLading)
                            renameField(form, it.fullyQualifiedName, "${it.fullyQualifiedName}_$currentPage")
                        } else if (it.fullyQualifiedName.startsWith("Net gallons") amp;amp; it.partialName == i.toString()
                        ) {
                            it.setValue(element.quantity.toString())
                            renameField(form, it.fullyQualifiedName, "${it.fullyQualifiedName}_$currentPage")
                        } else if (it.fullyQualifiedName.startsWith("New") amp;amp; it.partialName == i.toString()
                        ) {
                            it.setValue(element.revisedDestination)
                            renameField(form, it.fullyQualifiedName, "${it.fullyQualifiedName}_$currentPage")
                        }
                    } catch (ex: IOException) {
                        ex.printStackTrace()
                    }
                }
            }
        }
    }
}

 

Определение класса данных

 data class DiversionData(
    val terminalIRSCode: String,
    val fuelType: String,
    val supplierTaxPayerNumber: String,
    val shipperTaxPayerNumber: String,
    val quantity: Double,
    val originalDestination: String,
    val revisedDestination: String,
    val importNumber: String?,
    val date: LocalDate,
    val billOfLading: String,
)
 

тест для заполнения формы

    @Test
    public void fillFormUsingNewKotlinClass() throws IOException {
        List<DiversionData> diversionData = new ArrayList<>();

        for (int i = 0; i < 20; i  ) {
            DiversionData d = new DiversionData(
                "terminal_Code"   i,
                "Regular"   i,
                "Supplier tax"   i,
                "shipper tax"   i,
                1000   i,
                "TX",
                "LA",
                "0000"   i,
                LocalDate.now().plusDays(i),
                "123456"   i
            );
            diversionData.add(d);
        }
        //E:/repo/gasjobber-docker/gasjobber-api/src/main/resources/
        String path = "templates/taxFormsPDF/LA/5402(7_06)F.pdf";
        DiversionDataFormFiller filler = new DiversionDataFormFiller();
        Map<String, String> param = new HashMap<>();
        param.put("Product Type","065");
        param.put("Type of Schedule","22");
        param.put("LA Revenue Account Number","3264660001");
        param.put("Company Name","Test CO.");
        param.put("Filling Period","2020-12");
        PDDocument document =  filler.fillForm(path,diversionData,param);
        document.save(new File("C:\Users\Documents\Downloads\testpdf.pdf"));
        document.close();
    }
 

Комментарии:

1. Какую версию PDFBox вы используете? Можете ли вы создать короткий код, который работает самостоятельно?

2. Вы создаете внешний вид? Для меня это звучит как пропущенные появления…

3. @TilmanHausherr Я использую версию 2.0.21, это код, его полный набор, включая тест и его работу

4. PDFBox находится на 2.0.22, пожалуйста, повторите попытку. Чтобы выяснить причину, идеальным тестом было бы то, где вы задаете одно поле (но, возможно, строки «param.put» должны быть в порядке, чтобы указать, где это происходит)

5. Я могу воспроизвести эффект, используя SetFields.java пример с «Типом продукта» и 605. Поле видно в PDFBox, но не в Adobe Reader, я подозреваю, что Javascript что-то делает.

Ответ №1:

Запись страницы /AA/O («Действие, которое должно выполняться при открытии страницы») имеет следующее:

 if (!bReset)
{
    this.resetForm();
    bReset = true;
}
 

Таким образом, форма сбрасывается.

Это происходит, даже если форма заполнена вручную и сохранена, закрыта и снова открыта. Возможно, это демонстрационная версия «formupack», которая делает это специально.

Вы можете предотвратить это, удалив запись page / AA следующим образом

 document.getPage(0).setActions(null);
 

или просто удалите запись / O

 document.getPage(0).getActions().setO(null);
 

Комментарии:

1. Отличная находка — для меня это первый таймер!