mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-23 11:28:56 +00:00
473 lines
12 KiB
C++
473 lines
12 KiB
C++
#include <qpdf/QPDFFormFieldObjectHelper.hh>
|
|
#include <qpdf/QTC.hh>
|
|
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
|
|
|
|
QPDFFormFieldObjectHelper::Members::~Members()
|
|
{
|
|
}
|
|
|
|
QPDFFormFieldObjectHelper::Members::Members()
|
|
{
|
|
}
|
|
|
|
QPDFFormFieldObjectHelper::QPDFFormFieldObjectHelper(QPDFObjectHandle oh) :
|
|
QPDFObjectHelper(oh),
|
|
m(new Members())
|
|
{
|
|
}
|
|
|
|
QPDFFormFieldObjectHelper::QPDFFormFieldObjectHelper() :
|
|
QPDFObjectHelper(QPDFObjectHandle::newNull()),
|
|
m(new Members())
|
|
{
|
|
}
|
|
|
|
bool
|
|
QPDFFormFieldObjectHelper::isNull()
|
|
{
|
|
return this->oh.isNull();
|
|
}
|
|
|
|
QPDFFormFieldObjectHelper
|
|
QPDFFormFieldObjectHelper::getParent()
|
|
{
|
|
return this->oh.getKey("/Parent"); // may be null
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name)
|
|
{
|
|
QPDFObjectHandle node = this->oh;
|
|
QPDFObjectHandle result(node.getKey(name));
|
|
std::set<QPDFObjGen> seen;
|
|
while (result.isNull() && node.hasKey("/Parent"))
|
|
{
|
|
seen.insert(node.getObjGen());
|
|
node = node.getKey("/Parent");
|
|
if (seen.count(node.getObjGen()))
|
|
{
|
|
break;
|
|
}
|
|
result = node.getKey(name);
|
|
if (! result.isNull())
|
|
{
|
|
QTC::TC("qpdf", "QPDFFormFieldObjectHelper non-trivial inheritance");
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string
|
|
QPDFFormFieldObjectHelper::getInheritableFieldValueAsString(
|
|
std::string const& name)
|
|
{
|
|
QPDFObjectHandle fv = getInheritableFieldValue(name);
|
|
std::string result;
|
|
if (fv.isString())
|
|
{
|
|
result = fv.getUTF8Value();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string
|
|
QPDFFormFieldObjectHelper::getInheritableFieldValueAsName(
|
|
std::string const& name)
|
|
{
|
|
QPDFObjectHandle fv = getInheritableFieldValue(name);
|
|
std::string result;
|
|
if (fv.isName())
|
|
{
|
|
result = fv.getName();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string
|
|
QPDFFormFieldObjectHelper::getFieldType()
|
|
{
|
|
return getInheritableFieldValueAsName("/FT");
|
|
}
|
|
|
|
std::string
|
|
QPDFFormFieldObjectHelper::getFullyQualifiedName()
|
|
{
|
|
std::string result;
|
|
QPDFObjectHandle node = this->oh;
|
|
std::set<QPDFObjGen> seen;
|
|
while ((! node.isNull()) && (seen.count(node.getObjGen()) == 0))
|
|
{
|
|
if (node.getKey("/T").isString())
|
|
{
|
|
if (! result.empty())
|
|
{
|
|
QTC::TC("qpdf", "QPDFFormFieldObjectHelper non-trivial qualified name");
|
|
result = "." + result;
|
|
}
|
|
result = node.getKey("/T").getUTF8Value() + result;
|
|
}
|
|
seen.insert(node.getObjGen());
|
|
node = node.getKey("/Parent");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string
|
|
QPDFFormFieldObjectHelper::getPartialName()
|
|
{
|
|
std::string result;
|
|
if (this->oh.getKey("/T").isString())
|
|
{
|
|
result = this->oh.getKey("/T").getUTF8Value();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string
|
|
QPDFFormFieldObjectHelper::getAlternativeName()
|
|
{
|
|
if (this->oh.getKey("/TU").isString())
|
|
{
|
|
QTC::TC("qpdf", "QPDFFormFieldObjectHelper TU present");
|
|
return this->oh.getKey("/TU").getUTF8Value();
|
|
}
|
|
QTC::TC("qpdf", "QPDFFormFieldObjectHelper TU absent");
|
|
return getFullyQualifiedName();
|
|
}
|
|
|
|
std::string
|
|
QPDFFormFieldObjectHelper::getMappingName()
|
|
{
|
|
if (this->oh.getKey("/TM").isString())
|
|
{
|
|
QTC::TC("qpdf", "QPDFFormFieldObjectHelper TM present");
|
|
return this->oh.getKey("/TM").getUTF8Value();
|
|
}
|
|
QTC::TC("qpdf", "QPDFFormFieldObjectHelper TM absent");
|
|
return getAlternativeName();
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFFormFieldObjectHelper::getValue()
|
|
{
|
|
return getInheritableFieldValue("/V");
|
|
}
|
|
|
|
std::string
|
|
QPDFFormFieldObjectHelper::getValueAsString()
|
|
{
|
|
return getInheritableFieldValueAsString("/V");
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFFormFieldObjectHelper::getDefaultValue()
|
|
{
|
|
return getInheritableFieldValue("/DV");
|
|
}
|
|
|
|
std::string
|
|
QPDFFormFieldObjectHelper::getDefaultValueAsString()
|
|
{
|
|
return getInheritableFieldValueAsString("/DV");
|
|
}
|
|
|
|
std::string
|
|
QPDFFormFieldObjectHelper::getDefaultAppearance()
|
|
{
|
|
return getInheritableFieldValueAsString("/DA");
|
|
}
|
|
|
|
int
|
|
QPDFFormFieldObjectHelper::getQuadding()
|
|
{
|
|
int result = 0;
|
|
QPDFObjectHandle fv = getInheritableFieldValue("/Q");
|
|
if (fv.isInteger())
|
|
{
|
|
QTC::TC("qpdf", "QPDFFormFieldObjectHelper Q present");
|
|
result = static_cast<int>(fv.getIntValue());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int
|
|
QPDFFormFieldObjectHelper::getFlags()
|
|
{
|
|
QPDFObjectHandle f = getInheritableFieldValue("/Ff");
|
|
return f.isInteger() ? f.getIntValue() : 0;
|
|
}
|
|
|
|
bool
|
|
QPDFFormFieldObjectHelper::isText()
|
|
{
|
|
return (getFieldType() == "/Tx");
|
|
}
|
|
|
|
bool
|
|
QPDFFormFieldObjectHelper::isCheckbox()
|
|
{
|
|
return ((getFieldType() == "/Btn") &&
|
|
((getFlags() & (ff_btn_radio | ff_btn_pushbutton)) == 0));
|
|
}
|
|
|
|
bool
|
|
QPDFFormFieldObjectHelper::isRadioButton()
|
|
{
|
|
return ((getFieldType() == "/Btn") &&
|
|
((getFlags() & ff_btn_radio) == ff_btn_radio));
|
|
}
|
|
|
|
bool
|
|
QPDFFormFieldObjectHelper::isPushbutton()
|
|
{
|
|
return ((getFieldType() == "/Btn") &&
|
|
((getFlags() & ff_btn_pushbutton) == ff_btn_pushbutton));
|
|
}
|
|
|
|
bool
|
|
QPDFFormFieldObjectHelper::isChoice()
|
|
{
|
|
return (getFieldType() == "/Ch");
|
|
}
|
|
|
|
std::vector<std::string>
|
|
QPDFFormFieldObjectHelper::getChoices()
|
|
{
|
|
std::vector<std::string> result;
|
|
if (! isChoice())
|
|
{
|
|
return result;
|
|
}
|
|
QPDFObjectHandle opt = getInheritableFieldValue("/Opt");
|
|
if (opt.isArray())
|
|
{
|
|
size_t n = opt.getArrayNItems();
|
|
for (size_t i = 0; i < n; ++i)
|
|
{
|
|
QPDFObjectHandle item = opt.getArrayItem(i);
|
|
if (item.isString())
|
|
{
|
|
result.push_back(item.getUTF8Value());
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void
|
|
QPDFFormFieldObjectHelper::setFieldAttribute(
|
|
std::string const& key, QPDFObjectHandle value)
|
|
{
|
|
this->oh.replaceKey(key, value);
|
|
}
|
|
|
|
void
|
|
QPDFFormFieldObjectHelper::setFieldAttribute(
|
|
std::string const& key, std::string const& utf8_value)
|
|
{
|
|
this->oh.replaceKey(key, QPDFObjectHandle::newUnicodeString(utf8_value));
|
|
}
|
|
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|