mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 10:58:58 +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,
|
||||
std::string const& utf8_value);
|
||||
|
||||
// Set /V (field value) to the given value. Optionally set
|
||||
// /NeedAppearances to true. You can explicitly tell this method
|
||||
// not to set /NeedAppearances if you are going to explicitly
|
||||
// generate an appearance stream yourself.
|
||||
// Set /V (field value) to the given value. If need_appearances is
|
||||
// true and the field type is either /Tx (text) or /Ch (choice),
|
||||
// set /NeedAppearances to true. You can explicitly tell this
|
||||
// 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
|
||||
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);
|
||||
|
||||
private:
|
||||
void setRadioButtonValue(QPDFObjectHandle name);
|
||||
void setCheckBoxValue(bool value);
|
||||
|
||||
class Members
|
||||
{
|
||||
friend class QPDFFormFieldObjectHelper;
|
||||
|
@ -272,6 +272,47 @@ 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;
|
||||
}
|
||||
setFieldAttribute("/V", value);
|
||||
if (need_appearances)
|
||||
{
|
||||
@ -294,3 +335,138 @@ QPDFFormFieldObjectHelper::setV(
|
||||
setV(QPDFObjectHandle::newUnicodeString(utf8_value),
|
||||
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
|
||||
QPDFAnnotationObjectHelper forbidden 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',
|
||||
);
|
||||
|
||||
$n_tests += scalar(@form_tests) + 2;
|
||||
$n_tests += scalar(@form_tests) + 6;
|
||||
|
||||
# Many of the form*.pdf files were created by converting the
|
||||
# LibreOffice document storage/form.odt to PDF and then manually
|
||||
@ -216,6 +216,22 @@ $td->runtest("compare files",
|
||||
{$td->FILE => "a.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();
|
||||
# ----------
|
||||
$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;
|
||||
}
|
||||
}
|
||||
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
|
||||
{
|
||||
throw std::runtime_error(std::string("invalid test ") +
|
||||
|
Loading…
Reference in New Issue
Block a user