Search code examples
c++qtqmakeqpdf

check checkBox using ffh.getFieldType() == "/Btn" with qpdf lib


I use the qpdf it works when I use /Tx for filling text field. But it doesn't work for checking a checkBox :

Lib header : https://github.com/qpdf/qpdf/blob/aa602fd107a622a3f12e6530220bb0303b95b520/include/qpdf/QPDFFormFieldObjectHelper.hh

My code : if (ffh.getFieldType() == "/Btn")

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <stdlib.h>
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFPageDocumentHelper.hh>
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
#include <qpdf/QPDFWriter.hh>
#include <qpdf/QUtil.hh>
#include <QFileDialog>
#include <QDebug>
#include "qtfillpdf.h"
#include "ui_qtfillpdf.h"

qtfillpdf::qtfillpdf(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::qtfillpdf)
{
    ui->setupUi(this);
}

qtfillpdf::~qtfillpdf()
{
    delete ui;
}

int  qtfillpdf::qtfillTESTREPORTpdf( char const* infilename  , char const* outfilename,char const* value )
{

    qDebug() << "qtfillTESTREPORTpdf : 1 " << value;
    // This is a contrived example that just goes through a file page
    // by page and sets the value of any text fields it finds to a
    // fixed value as given on the command line. The purpose here is
    // to illustrate use of the helper classes around interactive
    // forms.

    QPDF qpdf;
    qDebug() << "qtfillTESTREPORTpdf : 1.1 " << value;
    qpdf.processFile(infilename);
    std::string nm;
    nm= qpdf.getFilename();
    qDebug() << "qtfillTESTREPORTpdf : 2 " << nm.c_str() ;
    // We will iterate through form fields by starting at the page
    // level and looking at each field for each page. We could
    // also called QPDFAcroFormDocumentHelper::getFormFields to
    // iterate at the field level, but doing it as below
    // illustrates how we can map from annotations to fields.

    QPDFAcroFormDocumentHelper afdh(qpdf);
    QPDFPageDocumentHelper pdh(qpdf);
    std::vector<QPDFPageObjectHelper> pages = pdh.getAllPages();
    qDebug() << "qtfillTESTREPORTpdf : 3 "  ;
    for (std::vector<QPDFPageObjectHelper>::iterator page_iter =
         pages.begin();
         page_iter != pages.end(); ++page_iter)
    {
        // Get all widget annotations for each page. Widget
        // annotations are the ones that contain the details of
        // what's in a form field.
        std::vector<QPDFAnnotationObjectHelper> annotations =
                afdh.getWidgetAnnotationsForPage(*page_iter);

        char const* checkPASS="true";
        char const* checkFAIL="false";

        for (std::vector<QPDFAnnotationObjectHelper>::iterator annot_iter = annotations.begin();
             annot_iter != annotations.end(); ++annot_iter)
        {
            // For each annotation, find its associated field. If
            // it's a text field, set its value. This will
            // automatically update the document to indicate that
            // appearance streams need to be regenerated. At the
            // time of this writing, qpdf doesn't have any helper
            // code to assist with appearance stream generation,
            // though there's nothing that prevents it from being
            // possible.
            //qDebug() << "qtfillTESTREPORTpdf : 4 "  << annot_iter->getAppearanceState().c_str() ;


            QPDFFormFieldObjectHelper ffh =
                    afdh.getFieldForAnnotation(*annot_iter);

            if (ffh.getFieldType() == "/Tx")
            {
                //qDebug() << "qtfillTESTREPORTpdf : 5 "  <<ffh.getChoices().front().c_str();
                // Set the value. This will automatically set
                // /NeedAppearances to true. If you don't want to
                // do that, pass false as the second argument. For
                // details see comments in
                // QPDFFormFieldObjectHelper.hh.
                ffh.setV(value);
                qDebug() << "OK6" << value << "  ffh.getValueAsString() : " <<ffh.getValueAsString().c_str();
            }

            else if (ffh.getFieldType() == "/Btn")
            {
                //qDebug() << "qtfillTESTREPORTpdf : 5 "  <<ffh.getChoices().front().c_str();
                // Set the value. This will automatically set
                // /NeedAppearances to true. If you don't want to
                // do that, pass false as the second argument. For
                // details see comments in
                // QPDFFormFieldObjectHelper.hh.
                ffh.setV(checkPASS);
                qDebug() << "OK7" << checkPASS << "  ffh.getValueAsString() : " << ffh.getValueAsString().c_str() << endl;
                qDebug() << "OK7- 2 " << checkPASS << "  ffh.getValue().getName() : " <<ffh.getValue().getName().c_str() << endl;
            }

        }
    }

    //  Write out a new file
    QPDFWriter w(qpdf, outfilename);
    w.setStaticID(true); // for testing only
    w.write();
    return 3;
}

void qtfillpdf::on_Browse_clicked()
{

    QString s_configFileName;
    QString outfilename = "OUTTESTREPORTWithCheckedBoxes.pdf";
    QString value="OkeyMenIfillAutomaticallyYourSheet";

    s_configFileName = QFileDialog::getOpenFileName(
                this,
                tr("Selectionner le fichier de configuration [xlsm]"),
                "",
                tr("pdf Files (*.pdf)")
                );

    qDebug() << "File Name : " << s_configFileName ;


    qtfillTESTREPORTpdf(s_configFileName.toStdString().c_str(), outfilename.toStdString().c_str(), value.toStdString().c_str());

    qDebug()<< " END_BROWSE :" << s_configFileName ;
}

Who understand why it doesn't work for Btn although it does work for /Tx

Logs for /Tx and i see it on the outputPDF

OK6 OkeyMenIfillAutomaticallyYourSheet   ffh.getValueAsString() :  OkeyMenIfillAutomaticallyYourSheet

Logs for Btn : Nothing on the outputPDF

WARNING:  object stream 1979, object 1129 0 at offset 41539 -> dictionary key /V: operation for name attempted on object of type null: returning dummy name
OK7- 2  true   ffh.getValue().getName() :  /QPDFFakeName

I understand that the qpdf don't see /Yes or /Off in my pdf but there is no /Tx neither and in the qpdf exemple ther is no /Yes or /Off fileds name

Need help to get the good idea, thanks you

intersting part of the lib source code :

void
QPDFFormFieldObjectHelper::setV(
    QPDFObjectHandle value, bool need_appearances)
{
    if (getFieldType() == "/Btn")
    {
        if (isCheckbox())
        {
            bool okay = false;
            if (value.isName())
            {
                std::string name = value.getName();
                if ((name == "/Yes") || (name == "/Off"))
                {
                    okay = true;
                    setCheckBoxValue((name == "/Yes"));
                }
            }
            if (! okay)
            {
                this->oh.warnIfPossible(
                    "ignoring attempt to set a checkbox field to a"
                    " value of other than /Yes or /Off");
            }
        }
        else if (isRadioButton())
        {
            if (value.isName())
            {
                setRadioButtonValue(value);
            }
            else
            {
                this->oh.warnIfPossible(
                    "ignoring attempt to set a radio button field to"
                    " an object that is not a name");
            }
        }
        else if (isPushbutton())
        {
            this->oh.warnIfPossible(
                "ignoring attempt set the value of a pushbutton field");
        }
        return;
    }
    if (value.isString())
    {
        setFieldAttribute(
            "/V", QPDFObjectHandle::newUnicodeString(value.getUTF8Value()));
    }
    else
    {
        setFieldAttribute("/V", value);
    }
    if (need_appearances)
    {
        QPDF* qpdf = this->oh.getOwningQPDF();
        if (! qpdf)
        {
            throw std::logic_error(
                "QPDFFormFieldObjectHelper::setV called with"
                " need_appearances = true on an object that is"
                " not associated with an owning QPDF");
        }
        QPDFAcroFormDocumentHelper(*qpdf).setNeedAppearances(true);
    }
}

void
QPDFFormFieldObjectHelper::setV(
    std::string const& utf8_value, bool need_appearances)
{
    setV(QPDFObjectHandle::newUnicodeString(utf8_value),
         need_appearances);
}

SourceCode of qpdf .dll :

https://github.com/qpdf/qpdf/blob/2d32f4db8fd125f2481ecf767d9f6506e80481f6/libqpdf/QPDFFormFieldObjectHelper.cc

I added some clarification elements on the answare

Thank you in advance for help!!!!


Solution

  • SOLVED : EVERY THING IS WORKING WELL WITH MY CODE BELLOW : HAPPINESSE

    int  qtfillpdf::qtfillTESTpdf( char const* infilename  , char const* outfilename,char const* value )
    {
    
        qDebug() << "qtfillTESTREPORTpdf : 1 " << value;
        // This is a contrived example that just goes through a file page
        // by page and sets the value of any text fields it finds to a
        // fixed value as given on the command line. The purpose here is
        // to illustrate use of the helper classes around interactive
        // forms.
    
        QPDF qpdf;
        qDebug() << "qtfillTESTREPORTpdf : 1.1 " << value;
        qpdf.processFile(infilename);
        std::string nm;
        nm= qpdf.getFilename();
        qDebug() << "qtfillTESTREPORTpdf : 2 " << nm.c_str() ;
        // We will iterate through form fields by starting at the page
        // level and looking at each field for each page. We could
        // also called QPDFAcroFormDocumentHelper::getFormFields to
        // iterate at the field level, but doing it as below
        // illustrates how we can map from annotations to fields.
    
        QPDFAcroFormDocumentHelper afdh(qpdf);
        QPDFPageDocumentHelper pdh(qpdf);
    
        QPDFObjectHandle checkPASS  = QPDFObjectHandle::newName("/Yes");
    
    
        std::vector<QPDFPageObjectHelper> pages = pdh.getAllPages();
        qDebug() << "qtfillTESTREPORTpdf : 3 "  ;
        for (std::vector<QPDFPageObjectHelper>::iterator page_iter = pages.begin();
             page_iter != pages.end(); ++page_iter)
        {
            // Get all widget annotations for each page. Widget
            // annotations are the ones that contain the details of
            // what's in a form field.
            std::vector<QPDFAnnotationObjectHelper> annotations =
                    afdh.getWidgetAnnotationsForPage(*page_iter);
    
            //char const* checkPASS="/Yes";
            char const* checkFAIL="/Off";
            for (std::vector<QPDFAnnotationObjectHelper>::iterator annot_iter = annotations.begin();
                 annot_iter != annotations.end(); ++annot_iter)
            {
                // For each annotation, find its associated field. If
                // it's a text field, set its value. This will
                // automatically update the document to indicate that
                // appearance streams need to be regenerated. At the
                // time of this writing, qpdf doesn't have any helper
                // code to assist with appearance stream generation,
                // though there's nothing that prevents it from being
                // possible.
                //qDebug() << "qtfillTESTREPORTpdf : 4 "  << annot_iter->getAppearanceState().c_str() ;
    
    
                QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(*annot_iter);
    
                //qDebug() << " OK 5 qtfillTESTREPORTpdf : "  <<ffh.getFieldType().c_str() ;
    
                qDebug() << " OK 5-1 ffh.getFieldType().c_str(): "  <<ffh.getFieldType().c_str() << " ffh.getFullyQualifiedName() : "  <<ffh.getFullyQualifiedName().c_str() << endl;
                bool isname = checkPASS.isName();
    
                qDebug()<< " OK 5-2 isName() : " <<isname <<  " getName() :"<<  checkPASS.getName().c_str();
    
                //qDebug() << ffh.setFieldAttribute();
    
                if (ffh.getFieldType() == "/Tx")
                {
                    // Set the value. This will automatically set
                    // /NeedAppearances to true. If you don't want to
                    // do that, pass false as the second argument. For
                    // details see comments in
                    // QPDFFormFieldObjectHelper.hh.
                    ffh.setV(value, true);
                    qDebug() << "OK 6 : " << value ;
                }
                else if (ffh.getFieldType() == "/Btn")
                {
                    //qDebug() << "qtfillTESTREPORTpdf : 5 "  <<ffh.getChoices().front().c_str();
                    // Set the value. This will automatically set
                    // /NeedAppearances to true. If you don't want to
                    // do that, pass false as the second argument. For
                    // details see comments in
                    // QPDFFormFieldObjectHelper.hh.
                    ffh.setV(checkPASS,true);
                    qDebug() << "OK7-1" << checkPASS.isNumber() << "ffh.getValueAsString() : " << ffh.getValueAsString().c_str() << endl;
                    qDebug() << "OK7-2 " << checkPASS.isString() << "ffh.getValue().getName() : " <<ffh.getValue().getName().c_str() << endl;
    
    
                    //                ffh.setV(checkPASS);
                    //                qDebug() << "OK7- 1" << checkPASS << "  ffh.getValueAsString() : " << ffh.getValueAsString().c_str() << endl;
                    //                qDebug() << "OK7- 2 " << checkPASS << "  ffh.getValue().getName() : " <<ffh.getValue().getName().c_str() << endl;
                    //                                qDebug() << "OK7- 2 " << checkPASS << "  ffh.getValue().getName() : " <<ffh << endl;
    
                    //                ffh.setV(checkFAIL);
                    //                qDebug() << "OK7- 1" << checkFAIL << "  ffh.getValueAsString() : " << ffh.getValueAsString().c_str() << endl;
                    //                qDebug() << "OK7- 2 " << checkFAIL << "  ffh.getValue().getName() : " <<ffh.getValue().getName().c_str() << endl;
                }
            }
        }
    
        //  Write out a new file
        QPDFWriter w(qpdf, outfilename);
        w.setStaticID(true); // for testing only
        w.write();
        return 3;
    }
    

    If Using pdf acrobat reader you will have to change the exportation value to "Yes" and not "On" any more exportation value is a checkbox proprety accessible from acrobate reader