mirror of
https://github.com/qpdf/qpdf.git
synced 2025-01-03 15:17:29 +00:00
Add special case setV code for button fields
This commit is contained in:
parent
1342612308
commit
b55567a0fa
@ -163,10 +163,13 @@ class QPDFFormFieldObjectHelper: public QPDFObjectHelper
|
|||||||
void setFieldAttribute(std::string const& key,
|
void setFieldAttribute(std::string const& key,
|
||||||
std::string const& utf8_value);
|
std::string const& utf8_value);
|
||||||
|
|
||||||
// Set /V (field value) to the given value. Optionally set
|
// Set /V (field value) to the given value. If need_appearances is
|
||||||
// /NeedAppearances to true. You can explicitly tell this method
|
// true and the field type is either /Tx (text) or /Ch (choice),
|
||||||
// not to set /NeedAppearances if you are going to explicitly
|
// set /NeedAppearances to true. You can explicitly tell this
|
||||||
// generate an appearance stream yourself.
|
// method not to set /NeedAppearances if you are going to generate
|
||||||
|
// an appearance stream yourself. Starting with qpdf 8.3.0, this
|
||||||
|
// method handles fields of type /Btn (checkboxes, radio buttons,
|
||||||
|
// pushbuttons) specially.
|
||||||
QPDF_DLL
|
QPDF_DLL
|
||||||
void setV(QPDFObjectHandle value, bool need_appearances = true);
|
void setV(QPDFObjectHandle value, bool need_appearances = true);
|
||||||
|
|
||||||
@ -177,6 +180,9 @@ class QPDFFormFieldObjectHelper: public QPDFObjectHelper
|
|||||||
void setV(std::string const& utf8_value, bool need_appearances = true);
|
void setV(std::string const& utf8_value, bool need_appearances = true);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void setRadioButtonValue(QPDFObjectHandle name);
|
||||||
|
void setCheckBoxValue(bool value);
|
||||||
|
|
||||||
class Members
|
class Members
|
||||||
{
|
{
|
||||||
friend class QPDFFormFieldObjectHelper;
|
friend class QPDFFormFieldObjectHelper;
|
||||||
|
@ -272,6 +272,47 @@ void
|
|||||||
QPDFFormFieldObjectHelper::setV(
|
QPDFFormFieldObjectHelper::setV(
|
||||||
QPDFObjectHandle value, bool need_appearances)
|
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;
|
||||||
|
}
|
||||||
setFieldAttribute("/V", value);
|
setFieldAttribute("/V", value);
|
||||||
if (need_appearances)
|
if (need_appearances)
|
||||||
{
|
{
|
||||||
@ -294,3 +335,138 @@ QPDFFormFieldObjectHelper::setV(
|
|||||||
setV(QPDFObjectHandle::newUnicodeString(utf8_value),
|
setV(QPDFObjectHandle::newUnicodeString(utf8_value),
|
||||||
need_appearances);
|
need_appearances);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFFormFieldObjectHelper::setRadioButtonValue(QPDFObjectHandle name)
|
||||||
|
{
|
||||||
|
// Set the value of a radio button field. This has the following
|
||||||
|
// specific behavior:
|
||||||
|
// * If this is a radio button field that has a parent that is
|
||||||
|
// also a radio button field and has no explicit /V, call itself
|
||||||
|
// on the parent
|
||||||
|
// * If this is a radio button field with childen, set /V to the
|
||||||
|
// given value. Then, for each child, if the child has the
|
||||||
|
// specified value as one of its keys in the /N subdictionary of
|
||||||
|
// its /AP (i.e. its normal appearance stream dictionary), set
|
||||||
|
// /AS to name; otherwise, if /Off is a member, set /AS to /Off.
|
||||||
|
// Note that we never turn on /NeedAppearances when setting a
|
||||||
|
// radio button field.
|
||||||
|
QPDFObjectHandle parent = this->oh.getKey("/Parent");
|
||||||
|
if (parent.isDictionary() && parent.getKey("/Parent").isNull())
|
||||||
|
{
|
||||||
|
QPDFFormFieldObjectHelper ph(parent);
|
||||||
|
if (ph.isRadioButton())
|
||||||
|
{
|
||||||
|
// This is most likely one of the individual buttons. Try
|
||||||
|
// calling on the parent.
|
||||||
|
QTC::TC("qpdf", "QPDFFormFieldObjectHelper set parent radio button");
|
||||||
|
ph.setRadioButtonValue(name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QPDFObjectHandle kids = this->oh.getKey("/Kids");
|
||||||
|
if (! (isRadioButton() && parent.isNull() && kids.isArray()))
|
||||||
|
{
|
||||||
|
this->oh.warnIfPossible("don't know how to set the value"
|
||||||
|
" of this field as a radio button");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setFieldAttribute("/V", name);
|
||||||
|
int nkids = kids.getArrayNItems();
|
||||||
|
for (int i = 0; i < nkids; ++i)
|
||||||
|
{
|
||||||
|
QPDFObjectHandle kid = kids.getArrayItem(i);
|
||||||
|
QPDFObjectHandle AP = kid.getKey("/AP");
|
||||||
|
QPDFObjectHandle annot;
|
||||||
|
if (AP.isNull())
|
||||||
|
{
|
||||||
|
// The widget may be below. If there is more than one,
|
||||||
|
// just find the first one.
|
||||||
|
QPDFObjectHandle grandkids = kid.getKey("/Kids");
|
||||||
|
if (grandkids.isArray())
|
||||||
|
{
|
||||||
|
int ngrandkids = grandkids.getArrayNItems();
|
||||||
|
for (int j = 0; j < ngrandkids; ++j)
|
||||||
|
{
|
||||||
|
QPDFObjectHandle grandkid = grandkids.getArrayItem(j);
|
||||||
|
AP = grandkid.getKey("/AP");
|
||||||
|
if (! AP.isNull())
|
||||||
|
{
|
||||||
|
QTC::TC("qpdf", "QPDFFormFieldObjectHelper radio button grandkid widget");
|
||||||
|
annot = grandkid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
annot = kid;
|
||||||
|
}
|
||||||
|
if (! annot.isInitialized())
|
||||||
|
{
|
||||||
|
QTC::TC("qpdf", "QPDFObjectHandle broken radio button");
|
||||||
|
this->oh.warnIfPossible(
|
||||||
|
"unable to set the value of this radio button");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (AP.isDictionary() &&
|
||||||
|
AP.getKey("/N").isDictionary() &&
|
||||||
|
AP.getKey("/N").hasKey(name.getName()))
|
||||||
|
{
|
||||||
|
QTC::TC("qpdf", "QPDFFormFieldObjectHelper turn on radio button");
|
||||||
|
annot.replaceKey("/AS", name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QTC::TC("qpdf", "QPDFFormFieldObjectHelper turn off radio button");
|
||||||
|
annot.replaceKey("/AS", QPDFObjectHandle::newName("/Off"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFFormFieldObjectHelper::setCheckBoxValue(bool value)
|
||||||
|
{
|
||||||
|
// Set /AS to /Yes or /Off in addition to setting /V.
|
||||||
|
QPDFObjectHandle name =
|
||||||
|
QPDFObjectHandle::newName(value ? "/Yes" : "/Off");
|
||||||
|
setFieldAttribute("/V", name);
|
||||||
|
QPDFObjectHandle AP = this->oh.getKey("/AP");
|
||||||
|
QPDFObjectHandle annot;
|
||||||
|
if (AP.isNull())
|
||||||
|
{
|
||||||
|
// The widget may be below. If there is more than one, just
|
||||||
|
// find the first one.
|
||||||
|
QPDFObjectHandle kids = this->oh.getKey("/Kids");
|
||||||
|
if (kids.isArray())
|
||||||
|
{
|
||||||
|
int nkids = kids.getArrayNItems();
|
||||||
|
for (int i = 0; i < nkids; ++i)
|
||||||
|
{
|
||||||
|
QPDFObjectHandle kid = kids.getArrayItem(i);
|
||||||
|
AP = kid.getKey("/AP");
|
||||||
|
if (! AP.isNull())
|
||||||
|
{
|
||||||
|
QTC::TC("qpdf", "QPDFFormFieldObjectHelper checkbox kid widget");
|
||||||
|
annot = kid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
annot = this->oh;
|
||||||
|
}
|
||||||
|
if (! annot.isInitialized())
|
||||||
|
{
|
||||||
|
QTC::TC("qpdf", "QPDFObjectHandle broken checkbox");
|
||||||
|
this->oh.warnIfPossible(
|
||||||
|
"unable to set the value of this checkbox");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QTC::TC("qpdf", "QPDFFormFieldObjectHelper set checkbox AS");
|
||||||
|
annot.replaceKey("/AS", name);
|
||||||
|
}
|
||||||
|
@ -390,3 +390,11 @@ QPDFObjectHandle replace with copy 0
|
|||||||
QPDFPageDocumentHelper indirect as resources 0
|
QPDFPageDocumentHelper indirect as resources 0
|
||||||
QPDFAnnotationObjectHelper forbidden flags 0
|
QPDFAnnotationObjectHelper forbidden flags 0
|
||||||
QPDFAnnotationObjectHelper missing required flags 0
|
QPDFAnnotationObjectHelper missing required flags 0
|
||||||
|
QPDFFormFieldObjectHelper set parent radio button 0
|
||||||
|
QPDFFormFieldObjectHelper radio button grandkid widget 0
|
||||||
|
QPDFFormFieldObjectHelper turn on radio button 0
|
||||||
|
QPDFFormFieldObjectHelper turn off radio button 0
|
||||||
|
QPDFFormFieldObjectHelper checkbox kid widget 0
|
||||||
|
QPDFObjectHandle broken radio button 0
|
||||||
|
QPDFFormFieldObjectHelper set checkbox AS 0
|
||||||
|
QPDFObjectHandle broken checkbox 0
|
||||||
|
@ -188,7 +188,7 @@ my @form_tests = (
|
|||||||
'form-errors',
|
'form-errors',
|
||||||
);
|
);
|
||||||
|
|
||||||
$n_tests += scalar(@form_tests) + 2;
|
$n_tests += scalar(@form_tests) + 6;
|
||||||
|
|
||||||
# Many of the form*.pdf files were created by converting the
|
# Many of the form*.pdf files were created by converting the
|
||||||
# LibreOffice document storage/form.odt to PDF and then manually
|
# LibreOffice document storage/form.odt to PDF and then manually
|
||||||
@ -216,6 +216,22 @@ $td->runtest("compare files",
|
|||||||
{$td->FILE => "a.pdf"},
|
{$td->FILE => "a.pdf"},
|
||||||
{$td->FILE => "form-no-need-appearances-filled.pdf"});
|
{$td->FILE => "form-no-need-appearances-filled.pdf"});
|
||||||
|
|
||||||
|
$td->runtest("button fields",
|
||||||
|
{$td->COMMAND => "test_driver 51 button-set.pdf"},
|
||||||
|
{$td->FILE => "button-set.out", $td->EXIT_STATUS => 0},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
$td->runtest("compare files",
|
||||||
|
{$td->FILE => "a.pdf"},
|
||||||
|
{$td->FILE => "button-set-out.pdf"});
|
||||||
|
|
||||||
|
$td->runtest("broken button fields",
|
||||||
|
{$td->COMMAND => "test_driver 51 button-set-broken.pdf"},
|
||||||
|
{$td->FILE => "button-set-broken.out", $td->EXIT_STATUS => 0},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
$td->runtest("compare files",
|
||||||
|
{$td->FILE => "a.pdf"},
|
||||||
|
{$td->FILE => "button-set-broken-out.pdf"});
|
||||||
|
|
||||||
show_ntests();
|
show_ntests();
|
||||||
# ----------
|
# ----------
|
||||||
$td->notify("--- Stream Replacement Tests ---");
|
$td->notify("--- Stream Replacement Tests ---");
|
||||||
|
3883
qpdf/qtest/qpdf/button-set-broken-out.pdf
Normal file
3883
qpdf/qtest/qpdf/button-set-broken-out.pdf
Normal file
File diff suppressed because it is too large
Load Diff
7
qpdf/qtest/qpdf/button-set-broken.out
Normal file
7
qpdf/qtest/qpdf/button-set-broken.out
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
setting r1 via parent
|
||||||
|
WARNING: button-set-broken.pdf, object 5 0 at offset 995: unable to set the value of this radio button
|
||||||
|
turning checkbox1 on
|
||||||
|
turning checkbox2 off
|
||||||
|
WARNING: button-set-broken.pdf, object 7 0 at offset 1354: unable to set the value of this checkbox
|
||||||
|
setting r2 via child
|
||||||
|
test 51 done
|
3710
qpdf/qtest/qpdf/button-set-broken.pdf
Normal file
3710
qpdf/qtest/qpdf/button-set-broken.pdf
Normal file
File diff suppressed because it is too large
Load Diff
3883
qpdf/qtest/qpdf/button-set-out.pdf
Normal file
3883
qpdf/qtest/qpdf/button-set-out.pdf
Normal file
File diff suppressed because it is too large
Load Diff
5
qpdf/qtest/qpdf/button-set.out
Normal file
5
qpdf/qtest/qpdf/button-set.out
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
setting r1 via parent
|
||||||
|
turning checkbox1 on
|
||||||
|
turning checkbox2 off
|
||||||
|
setting r2 via child
|
||||||
|
test 51 done
|
3710
qpdf/qtest/qpdf/button-set.pdf
Normal file
3710
qpdf/qtest/qpdf/button-set.pdf
Normal file
File diff suppressed because it is too large
Load Diff
@ -1771,6 +1771,55 @@ void runtest(int n, char const* filename1, char const* arg2)
|
|||||||
std::cout << *iter << std::endl;
|
std::cout << *iter << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (n == 51)
|
||||||
|
{
|
||||||
|
// Test radio button and checkbox field setting. The input
|
||||||
|
// files must have radios button called r1 and r2 and
|
||||||
|
// checkboxes called checkbox1 and checkbox2. The files
|
||||||
|
// button-set*.pdf are designed for this test case.
|
||||||
|
QPDFObjectHandle acroform = pdf.getRoot().getKey("/AcroForm");
|
||||||
|
QPDFObjectHandle fields = acroform.getKey("/Fields");
|
||||||
|
int n = fields.getArrayNItems();
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
QPDFObjectHandle field = fields.getArrayItem(i);
|
||||||
|
QPDFObjectHandle T = field.getKey("/T");
|
||||||
|
if (! T.isString())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string Tval = T.getUTF8Value();
|
||||||
|
if (Tval == "r1")
|
||||||
|
{
|
||||||
|
std::cout << "setting r1 via parent\n";
|
||||||
|
QPDFFormFieldObjectHelper foh(field);
|
||||||
|
foh.setV(QPDFObjectHandle::newName("/2"));
|
||||||
|
}
|
||||||
|
else if (Tval == "r2")
|
||||||
|
{
|
||||||
|
std::cout << "setting r2 via child\n";
|
||||||
|
field = field.getKey("/Kids").getArrayItem(1);
|
||||||
|
QPDFFormFieldObjectHelper foh(field);
|
||||||
|
foh.setV(QPDFObjectHandle::newName("/3"));
|
||||||
|
}
|
||||||
|
else if (Tval == "checkbox1")
|
||||||
|
{
|
||||||
|
std::cout << "turning checkbox1 on\n";
|
||||||
|
QPDFFormFieldObjectHelper foh(field);
|
||||||
|
foh.setV(QPDFObjectHandle::newName("/Yes"));
|
||||||
|
}
|
||||||
|
else if (Tval == "checkbox2")
|
||||||
|
{
|
||||||
|
std::cout << "turning checkbox2 off\n";
|
||||||
|
QPDFFormFieldObjectHelper foh(field);
|
||||||
|
foh.setV(QPDFObjectHandle::newName("/Off"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QPDFWriter w(pdf, "a.pdf");
|
||||||
|
w.setQDFMode(true);
|
||||||
|
w.setStaticID(true);
|
||||||
|
w.write();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw std::runtime_error(std::string("invalid test ") +
|
throw std::runtime_error(std::string("invalid test ") +
|
||||||
|
Loading…
Reference in New Issue
Block a user