mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 10:58:58 +00:00
Implement helpers for file attachments
This commit is contained in:
parent
bf0e6eb302
commit
ad34b9c278
@ -4,6 +4,12 @@
|
||||
pdf_time_to_qpdf_time, qpdf_time_to_pdf_time,
|
||||
get_current_qpdf_time.
|
||||
|
||||
2021-02-08 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Add helper classes for file attachments:
|
||||
QPDFEmbeddedFileDocumentHelper, QPDFFileSpecObjectHelper,
|
||||
QPDFEFStreamObjectHelper. See their header files for details.
|
||||
|
||||
2021-02-07 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Add new functions QUtil::pipe_file and QUtil::file_provider for
|
||||
|
122
include/qpdf/QPDFEFStreamObjectHelper.hh
Normal file
122
include/qpdf/QPDFEFStreamObjectHelper.hh
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright (c) 2005-2021 Jay Berkenbilt
|
||||
//
|
||||
// This file is part of qpdf.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Versions of qpdf prior to version 7 were released under the terms
|
||||
// of version 2.0 of the Artistic License. At your option, you may
|
||||
// continue to consider qpdf to be licensed under those terms. Please
|
||||
// see the manual for additional information.
|
||||
|
||||
#ifndef QPDFEFSTREAMOBJECTHELPER_HH
|
||||
#define QPDFEFSTREAMOBJECTHELPER_HH
|
||||
|
||||
#include <qpdf/QPDFObjectHelper.hh>
|
||||
|
||||
#include <qpdf/DLL.h>
|
||||
|
||||
#include <qpdf/QPDFObjectHandle.hh>
|
||||
#include <functional>
|
||||
|
||||
// This class provides a higher level interface around Embedded File
|
||||
// Streams, which are discussed in section 7.11.4 of the ISO-32000 PDF
|
||||
// specification.
|
||||
|
||||
class QPDFEFStreamObjectHelper: public QPDFObjectHelper
|
||||
{
|
||||
public:
|
||||
QPDF_DLL
|
||||
QPDFEFStreamObjectHelper(QPDFObjectHandle);
|
||||
QPDF_DLL
|
||||
virtual ~QPDFEFStreamObjectHelper() = default;
|
||||
|
||||
// Date parameters are strings that comform to the PDF spec for
|
||||
// date/time strings, which is "D:yyyymmddhhmmss<z>" where <z> is
|
||||
// either "Z" for UTC or "-hh'mm'" or "+hh'mm'" for timezone
|
||||
// offset. Examples: "D:20210207161528-05'00'",
|
||||
// "D:20210207211528Z". See QUtil::qpdf_time_to_pdf_time.
|
||||
|
||||
QPDF_DLL
|
||||
std::string getCreationDate();
|
||||
QPDF_DLL
|
||||
std::string getModDate();
|
||||
// Get size as reported in the object; return 0 if not present.
|
||||
QPDF_DLL
|
||||
size_t getSize();
|
||||
// Subtype is a mime type such as "text/plain"
|
||||
QPDF_DLL
|
||||
std::string getSubtype();
|
||||
// Return the MD5 checksum as stored in the object as a binary
|
||||
// string. This does not check consistency with the data. If not
|
||||
// present, return an empty string.
|
||||
QPDF_DLL
|
||||
std::string getChecksum();
|
||||
|
||||
// Setters return a reference to this object so that they can be
|
||||
// used as fluent interfaces, e.g.
|
||||
// efsoh.setCreationDate(x).setModDate(y);
|
||||
|
||||
// Create a new embedded file stream with the given stream data,
|
||||
// which can be provided in any of several ways. To get the new
|
||||
// object back, call getObjectHandle() on the returned object. The
|
||||
// checksum and size are computed automatically and stored. Other
|
||||
// parameters may be supplied using setters defined below.
|
||||
QPDF_DLL
|
||||
static QPDFEFStreamObjectHelper
|
||||
createEFStream(QPDF& qpdf, PointerHolder<Buffer> data);
|
||||
QPDF_DLL
|
||||
static QPDFEFStreamObjectHelper
|
||||
createEFStream(QPDF& qpdf, std::string const& data);
|
||||
// The provider function must write the data to the given
|
||||
// pipeline. The function may be called multiple times by the qpdf
|
||||
// library. You can pass QUtil::file_provider(filename) as the
|
||||
// provider to have the qpdf library provide the contents of
|
||||
// filename as a binary.
|
||||
QPDF_DLL
|
||||
static QPDFEFStreamObjectHelper
|
||||
createEFStream(QPDF& qpdf, std::function<void(Pipeline*)> provider);
|
||||
|
||||
// Setters for other parameters
|
||||
QPDF_DLL
|
||||
QPDFEFStreamObjectHelper& setCreationDate(std::string const&);
|
||||
QPDF_DLL
|
||||
QPDFEFStreamObjectHelper& setModDate(std::string const&);
|
||||
|
||||
// Set subtype as a mime-type, e.g. "text/plain" or
|
||||
// "application/pdf".
|
||||
QPDF_DLL
|
||||
QPDFEFStreamObjectHelper& setSubtype(std::string const&);
|
||||
|
||||
private:
|
||||
QPDFObjectHandle getParam(std::string const& pkey);
|
||||
void setParam(std::string const& pkey, QPDFObjectHandle const&);
|
||||
static QPDFEFStreamObjectHelper newFromStream(QPDFObjectHandle stream);
|
||||
|
||||
class Members
|
||||
{
|
||||
friend class QPDFEFStreamObjectHelper;
|
||||
|
||||
public:
|
||||
QPDF_DLL
|
||||
~Members() = default;
|
||||
|
||||
private:
|
||||
Members();
|
||||
Members(Members const&) = delete;
|
||||
};
|
||||
|
||||
PointerHolder<Members> m;
|
||||
};
|
||||
|
||||
#endif // QPDFEFSTREAMOBJECTHELPER_HH
|
97
include/qpdf/QPDFEmbeddedFileDocumentHelper.hh
Normal file
97
include/qpdf/QPDFEmbeddedFileDocumentHelper.hh
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2005-2021 Jay Berkenbilt
|
||||
//
|
||||
// This file is part of qpdf.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Versions of qpdf prior to version 7 were released under the terms
|
||||
// of version 2.0 of the Artistic License. At your option, you may
|
||||
// continue to consider qpdf to be licensed under those terms. Please
|
||||
// see the manual for additional information.
|
||||
|
||||
#ifndef QPDFEMBEDDEDFILEDOCUMENTHELPER_HH
|
||||
#define QPDFEMBEDDEDFILEDOCUMENTHELPER_HH
|
||||
|
||||
#include <qpdf/QPDFDocumentHelper.hh>
|
||||
|
||||
#include <qpdf/QPDF.hh>
|
||||
#include <qpdf/QPDFNameTreeObjectHelper.hh>
|
||||
#include <qpdf/QPDFFileSpecObjectHelper.hh>
|
||||
#include <qpdf/DLL.h>
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
// This class provides a higher level interface around document-level
|
||||
// file attachments, also known as embedded files. These are discussed
|
||||
// in sections 7.7.4 and 7.11 of the ISO-32000 PDF specification.
|
||||
|
||||
class QPDFEmbeddedFileDocumentHelper: public QPDFDocumentHelper
|
||||
{
|
||||
public:
|
||||
QPDF_DLL
|
||||
QPDFEmbeddedFileDocumentHelper(QPDF&);
|
||||
QPDF_DLL
|
||||
virtual ~QPDFEmbeddedFileDocumentHelper() = default;
|
||||
|
||||
QPDF_DLL
|
||||
bool hasEmbeddedFiles() const;
|
||||
|
||||
QPDF_DLL
|
||||
std::map<std::string,
|
||||
std::shared_ptr<QPDFFileSpecObjectHelper>> getEmbeddedFiles();
|
||||
|
||||
// If an embedded file with the given name exists, return a
|
||||
// (shared) pointer to it. Otherwise, return nullptr.
|
||||
QPDF_DLL
|
||||
std::shared_ptr<QPDFFileSpecObjectHelper>
|
||||
getEmbeddedFile(std::string const& name);
|
||||
|
||||
// Add or replace an attachment
|
||||
QPDF_DLL
|
||||
void replaceEmbeddedFile(
|
||||
std::string const& name, QPDFFileSpecObjectHelper const&);
|
||||
|
||||
// Remove an embedded file if present. Return value is true if the
|
||||
// file was present and was removed. This method not only removes
|
||||
// the embedded file from the embedded files name tree but also
|
||||
// nulls out the file specification dictionary. This means that
|
||||
// any references to this file from file attachment annotations
|
||||
// will also stop working. This is the best way to make the
|
||||
// attachment actually disappear from the file and not just from
|
||||
// the list of attachments.
|
||||
QPDF_DLL
|
||||
bool removeEmbeddedFile(std::string const& name);
|
||||
|
||||
private:
|
||||
void initEmbeddedFiles();
|
||||
|
||||
class Members
|
||||
{
|
||||
friend class QPDFEmbeddedFileDocumentHelper;
|
||||
|
||||
public:
|
||||
QPDF_DLL
|
||||
~Members() = default;
|
||||
|
||||
private:
|
||||
Members();
|
||||
Members(Members const&) = delete;
|
||||
|
||||
std::shared_ptr<QPDFNameTreeObjectHelper> embedded_files;
|
||||
};
|
||||
|
||||
PointerHolder<Members> m;
|
||||
};
|
||||
|
||||
#endif // QPDFEMBEDDEDFILEDOCUMENTHELPER_HH
|
126
include/qpdf/QPDFFileSpecObjectHelper.hh
Normal file
126
include/qpdf/QPDFFileSpecObjectHelper.hh
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright (c) 2005-2021 Jay Berkenbilt
|
||||
//
|
||||
// This file is part of qpdf.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Versions of qpdf prior to version 7 were released under the terms
|
||||
// of version 2.0 of the Artistic License. At your option, you may
|
||||
// continue to consider qpdf to be licensed under those terms. Please
|
||||
// see the manual for additional information.
|
||||
|
||||
#ifndef QPDFFILESPECOBJECTHELPER_HH
|
||||
#define QPDFFILESPECOBJECTHELPER_HH
|
||||
|
||||
#include <qpdf/QPDFObjectHelper.hh>
|
||||
|
||||
#include <qpdf/DLL.h>
|
||||
|
||||
#include <qpdf/QPDFObjectHandle.hh>
|
||||
#include <qpdf/QPDFEFStreamObjectHelper.hh>
|
||||
|
||||
// This class provides a higher level interface around File
|
||||
// Specification dictionaries, which are discussed in section 7.11 of
|
||||
// the ISO-32000 PDF specification.
|
||||
|
||||
class QPDFFileSpecObjectHelper: public QPDFObjectHelper
|
||||
{
|
||||
public:
|
||||
QPDF_DLL
|
||||
QPDFFileSpecObjectHelper(QPDFObjectHandle);
|
||||
QPDF_DLL
|
||||
virtual ~QPDFFileSpecObjectHelper() = default;
|
||||
|
||||
QPDF_DLL
|
||||
std::string getDescription();
|
||||
|
||||
// Get the main filename for this file specification. In priority
|
||||
// order, check /UF, /F, /Unix, /DOS, /Mac.
|
||||
QPDF_DLL
|
||||
std::string getFilename();
|
||||
|
||||
// Return any of /UF, /F, /Unix, /DOS, /Mac filename keys that may
|
||||
// be present in the object.
|
||||
QPDF_DLL
|
||||
std::map<std::string, std::string> getFilenames();
|
||||
|
||||
// Get the requested embedded file stream for this file
|
||||
// specification. If key is empty, In priority order, check /UF,
|
||||
// /F, /Unix, /DOS, /Mac. Returns a null object if not found. If
|
||||
// this is an actual embedded file stream, its data is the content
|
||||
// of the attachment. You can also use
|
||||
// QPDFEFStreamObjectHelper for higher level access to
|
||||
// the parameters.
|
||||
QPDF_DLL
|
||||
QPDFObjectHandle getEmbeddedFileStream(std::string const& key = "");
|
||||
|
||||
// Return the /EF key of the file spec, which is a map from file
|
||||
// name key to embedded file stream.
|
||||
QPDF_DLL
|
||||
QPDFObjectHandle getEmbeddedFileStreams();
|
||||
|
||||
// Setters return a reference to this object so that they can be
|
||||
// used as fluent interfaces, e.g.
|
||||
// fsoh.setDescription(x).setFilename(y);
|
||||
|
||||
// Create a new filespec as an indirect object with the given
|
||||
// filename, and attach the contents of the specified file as data
|
||||
// in an embedded file stream.
|
||||
QPDF_DLL
|
||||
static
|
||||
QPDFFileSpecObjectHelper createFileSpec(
|
||||
QPDF& qpdf,
|
||||
std::string const& filename,
|
||||
std::string const& fullpath);
|
||||
|
||||
// Create a new filespec as an indirect object with the given
|
||||
// unicode filename and embedded file stream. The file name will
|
||||
// be used as both /UF and /F. If you need to override, call
|
||||
// setFilename.
|
||||
QPDF_DLL
|
||||
static
|
||||
QPDFFileSpecObjectHelper createFileSpec(
|
||||
QPDF& qpdf,
|
||||
std::string const& filename,
|
||||
QPDFEFStreamObjectHelper);
|
||||
|
||||
QPDF_DLL
|
||||
QPDFFileSpecObjectHelper& setDescription(std::string const&);
|
||||
// setFilename sets /UF to unicode_name. If compat_name is empty,
|
||||
// it is also set to unicode_name. unicode_name should be a UTF-8
|
||||
// encoded string. compat_name is converted to a string
|
||||
// QPDFObjectHandle literally, preserving whatever encoding it
|
||||
// might happen to have.
|
||||
QPDF_DLL
|
||||
QPDFFileSpecObjectHelper& setFilename(
|
||||
std::string const& unicode_name,
|
||||
std::string const& compat_name = "");
|
||||
|
||||
private:
|
||||
class Members
|
||||
{
|
||||
friend class QPDFFileSpecObjectHelper;
|
||||
|
||||
public:
|
||||
QPDF_DLL
|
||||
~Members() = default;
|
||||
|
||||
private:
|
||||
Members();
|
||||
Members(Members const&) = delete;
|
||||
};
|
||||
|
||||
PointerHolder<Members> m;
|
||||
};
|
||||
|
||||
#endif // QPDFFILESPECOBJECTHELPER_HH
|
193
libqpdf/QPDFEFStreamObjectHelper.cc
Normal file
193
libqpdf/QPDFEFStreamObjectHelper.cc
Normal file
@ -0,0 +1,193 @@
|
||||
#include <qpdf/QPDFEFStreamObjectHelper.hh>
|
||||
#include <qpdf/QIntC.hh>
|
||||
#include <qpdf/QUtil.hh>
|
||||
#include <qpdf/Pl_Count.hh>
|
||||
#include <qpdf/Pl_MD5.hh>
|
||||
#include <qpdf/Pl_Discard.hh>
|
||||
|
||||
QPDFEFStreamObjectHelper::QPDFEFStreamObjectHelper(
|
||||
QPDFObjectHandle oh) :
|
||||
QPDFObjectHelper(oh),
|
||||
m(new Members())
|
||||
{
|
||||
}
|
||||
|
||||
QPDFEFStreamObjectHelper::Members::Members()
|
||||
{
|
||||
}
|
||||
|
||||
QPDFObjectHandle
|
||||
QPDFEFStreamObjectHelper::getParam(std::string const& pkey)
|
||||
{
|
||||
auto params = this->oh.getDict().getKey("/Params");
|
||||
if (params.isDictionary())
|
||||
{
|
||||
return params.getKey(pkey);
|
||||
}
|
||||
return QPDFObjectHandle::newNull();
|
||||
}
|
||||
|
||||
void
|
||||
QPDFEFStreamObjectHelper::setParam(
|
||||
std::string const& pkey, QPDFObjectHandle const& pval)
|
||||
{
|
||||
auto params = this->oh.getDict().getKey("/Params");
|
||||
if (! params.isDictionary())
|
||||
{
|
||||
params = QPDFObjectHandle::newDictionary();
|
||||
this->oh.getDict().replaceKey("/Params", params);
|
||||
}
|
||||
params.replaceKey(pkey, pval);
|
||||
}
|
||||
|
||||
std::string
|
||||
QPDFEFStreamObjectHelper::getCreationDate()
|
||||
{
|
||||
auto val = getParam("/CreationDate");
|
||||
if (val.isString())
|
||||
{
|
||||
return val.getUTF8Value();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string
|
||||
QPDFEFStreamObjectHelper::getModDate()
|
||||
{
|
||||
auto val = getParam("/ModDate");
|
||||
if (val.isString())
|
||||
{
|
||||
return val.getUTF8Value();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
size_t
|
||||
QPDFEFStreamObjectHelper::getSize()
|
||||
{
|
||||
auto val = getParam("/Size");
|
||||
if (val.isInteger())
|
||||
{
|
||||
return QIntC::to_size(val.getUIntValueAsUInt());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string
|
||||
QPDFEFStreamObjectHelper::getSubtype()
|
||||
{
|
||||
auto val = getParam("/Subtype");
|
||||
if (val.isName())
|
||||
{
|
||||
auto n = val.getName();
|
||||
if (n.length() > 1)
|
||||
{
|
||||
return n.substr(1);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string
|
||||
QPDFEFStreamObjectHelper::getChecksum()
|
||||
{
|
||||
auto val = getParam("/CheckSum");
|
||||
if (val.isString())
|
||||
{
|
||||
return val.getStringValue();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
QPDFEFStreamObjectHelper
|
||||
QPDFEFStreamObjectHelper::createEFStream(
|
||||
QPDF& qpdf, PointerHolder<Buffer> data)
|
||||
{
|
||||
return newFromStream(QPDFObjectHandle::newStream(&qpdf, data));
|
||||
}
|
||||
|
||||
QPDFEFStreamObjectHelper
|
||||
QPDFEFStreamObjectHelper::createEFStream(
|
||||
QPDF& qpdf, std::string const& data)
|
||||
{
|
||||
return newFromStream(QPDFObjectHandle::newStream(&qpdf, data));
|
||||
}
|
||||
|
||||
namespace QEF
|
||||
{
|
||||
class Provider: public QPDFObjectHandle::StreamDataProvider
|
||||
{
|
||||
public:
|
||||
Provider(std::function<void(Pipeline*)> provider) :
|
||||
StreamDataProvider(false),
|
||||
provider(provider)
|
||||
{
|
||||
}
|
||||
virtual ~Provider() = default;
|
||||
virtual void provideStreamData(int objid, int generation,
|
||||
Pipeline* pipeline) override
|
||||
{
|
||||
this->provider(pipeline);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(Pipeline*)> provider;
|
||||
};
|
||||
};
|
||||
|
||||
QPDFEFStreamObjectHelper
|
||||
QPDFEFStreamObjectHelper::createEFStream(
|
||||
QPDF& qpdf, std::function<void(Pipeline*)> provider)
|
||||
{
|
||||
auto stream = QPDFObjectHandle::newStream(&qpdf);
|
||||
stream.replaceStreamData(new QEF::Provider(provider),
|
||||
QPDFObjectHandle::newNull(),
|
||||
QPDFObjectHandle::newNull());
|
||||
return newFromStream(stream);
|
||||
}
|
||||
|
||||
QPDFEFStreamObjectHelper&
|
||||
QPDFEFStreamObjectHelper::setCreationDate(std::string const& date)
|
||||
{
|
||||
setParam("/CreationDate", QPDFObjectHandle::newString(date));
|
||||
return *this;
|
||||
}
|
||||
|
||||
QPDFEFStreamObjectHelper&
|
||||
QPDFEFStreamObjectHelper::setModDate(std::string const& date)
|
||||
{
|
||||
setParam("/ModDate", QPDFObjectHandle::newString(date));
|
||||
return *this;
|
||||
}
|
||||
|
||||
QPDFEFStreamObjectHelper&
|
||||
QPDFEFStreamObjectHelper::setSubtype(std::string const& subtype)
|
||||
{
|
||||
setParam("/Subtype", QPDFObjectHandle::newName("/" + subtype));
|
||||
return *this;
|
||||
}
|
||||
|
||||
QPDFEFStreamObjectHelper
|
||||
QPDFEFStreamObjectHelper::newFromStream(QPDFObjectHandle stream)
|
||||
{
|
||||
QPDFEFStreamObjectHelper result(stream);
|
||||
stream.getDict().replaceKey(
|
||||
"/Type", QPDFObjectHandle::newName("/EmbeddedFile"));
|
||||
Pl_Discard discard;
|
||||
Pl_MD5 md5("EF md5", &discard);
|
||||
Pl_Count count("EF size", &md5);
|
||||
if (! stream.pipeStreamData(&count, nullptr, 0, qpdf_dl_all))
|
||||
{
|
||||
stream.warnIfPossible(
|
||||
"unable to get stream data for new embedded file stream");
|
||||
}
|
||||
else
|
||||
{
|
||||
result.setParam(
|
||||
"/Size", QPDFObjectHandle::newInteger(count.getCount()));
|
||||
result.setParam(
|
||||
"/CheckSum", QPDFObjectHandle::newString(
|
||||
QUtil::hex_decode(md5.getHexDigest())));
|
||||
}
|
||||
return result;
|
||||
}
|
146
libqpdf/QPDFEmbeddedFileDocumentHelper.cc
Normal file
146
libqpdf/QPDFEmbeddedFileDocumentHelper.cc
Normal file
@ -0,0 +1,146 @@
|
||||
#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
|
||||
|
||||
// File attachments are stored in the /EmbeddedFiles (name tree) key
|
||||
// of the /Names dictionary from the document catalog. Each entry
|
||||
// points to a /FileSpec, which in turn points to one more Embedded
|
||||
// File Streams. Note that file specs can appear in other places as
|
||||
// well, such as file attachment annotations, among others.
|
||||
//
|
||||
// root -> /Names -> /EmbeddedFiles = name tree
|
||||
// filename -> filespec
|
||||
// <<
|
||||
// /Desc ()
|
||||
// /EF <<
|
||||
// /F x 0 R
|
||||
// /UF x 0 R
|
||||
// >>
|
||||
// /F (name)
|
||||
// /UF (name)
|
||||
// /Type /Filespec
|
||||
// >>
|
||||
// x 0 obj
|
||||
// <<
|
||||
// /Type /EmbeddedFile
|
||||
// /DL filesize % not in spec?
|
||||
// /Params <<
|
||||
// /CheckSum <md5>
|
||||
// /CreationDate (D:yyyymmddhhmmss{-hh'mm'|+hh'mm'|Z})
|
||||
// /ModDate (D:yyyymmddhhmmss-hh'mm')
|
||||
// /Size filesize
|
||||
// /Subtype /mime#2ftype
|
||||
// >>
|
||||
// >>
|
||||
|
||||
QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF& qpdf) :
|
||||
QPDFDocumentHelper(qpdf),
|
||||
m(new Members())
|
||||
{
|
||||
auto root = qpdf.getRoot();
|
||||
auto names = root.getKey("/Names");
|
||||
if (names.isDictionary())
|
||||
{
|
||||
auto embedded_files = names.getKey("/EmbeddedFiles");
|
||||
if (embedded_files.isDictionary())
|
||||
{
|
||||
this->m->embedded_files =
|
||||
std::make_shared<QPDFNameTreeObjectHelper>(
|
||||
embedded_files, qpdf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QPDFEmbeddedFileDocumentHelper::Members::Members()
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
QPDFEmbeddedFileDocumentHelper::hasEmbeddedFiles() const
|
||||
{
|
||||
return (this->m->embedded_files.get() != nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
QPDFEmbeddedFileDocumentHelper::initEmbeddedFiles()
|
||||
{
|
||||
if (hasEmbeddedFiles())
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto root = qpdf.getRoot();
|
||||
auto names = root.getKey("/Names");
|
||||
if (! names.isDictionary())
|
||||
{
|
||||
names = QPDFObjectHandle::newDictionary();
|
||||
root.replaceKey("/Names", names);
|
||||
}
|
||||
auto embedded_files = names.getKey("/EmbeddedFiles");
|
||||
if (! embedded_files.isDictionary())
|
||||
{
|
||||
auto nth = QPDFNameTreeObjectHelper::newEmpty(this->qpdf);
|
||||
names.replaceKey("/EmbeddedFiles", nth.getObjectHandle());
|
||||
this->m->embedded_files =
|
||||
std::make_shared<QPDFNameTreeObjectHelper>(nth);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<QPDFFileSpecObjectHelper>
|
||||
QPDFEmbeddedFileDocumentHelper::getEmbeddedFile(std::string const& name)
|
||||
{
|
||||
std::shared_ptr<QPDFFileSpecObjectHelper> result;
|
||||
if (this->m->embedded_files)
|
||||
{
|
||||
auto i = this->m->embedded_files->find(name);
|
||||
if (i != this->m->embedded_files->end())
|
||||
{
|
||||
result = std::make_shared<QPDFFileSpecObjectHelper>(i->second);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::map<std::string, std::shared_ptr<QPDFFileSpecObjectHelper>>
|
||||
QPDFEmbeddedFileDocumentHelper::getEmbeddedFiles()
|
||||
{
|
||||
std::map<std::string,
|
||||
std::shared_ptr<QPDFFileSpecObjectHelper>> result;
|
||||
if (this->m->embedded_files)
|
||||
{
|
||||
for (auto const& i: *(this->m->embedded_files))
|
||||
{
|
||||
result[i.first] = std::make_shared<QPDFFileSpecObjectHelper>(
|
||||
i.second);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
QPDFEmbeddedFileDocumentHelper::replaceEmbeddedFile(
|
||||
std::string const& name, QPDFFileSpecObjectHelper const& fs)
|
||||
{
|
||||
initEmbeddedFiles();
|
||||
this->m->embedded_files->insert(
|
||||
name, fs.getObjectHandle());
|
||||
}
|
||||
|
||||
bool
|
||||
QPDFEmbeddedFileDocumentHelper::removeEmbeddedFile(std::string const& name)
|
||||
{
|
||||
if (! hasEmbeddedFiles())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto iter = this->m->embedded_files->find(name);
|
||||
if (iter == this->m->embedded_files->end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto oh = iter->second;
|
||||
iter.remove();
|
||||
if (oh.isIndirect())
|
||||
{
|
||||
this->qpdf.replaceObject(oh.getObjGen(), QPDFObjectHandle::newNull());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
157
libqpdf/QPDFFileSpecObjectHelper.cc
Normal file
157
libqpdf/QPDFFileSpecObjectHelper.cc
Normal file
@ -0,0 +1,157 @@
|
||||
#include <qpdf/QPDFFileSpecObjectHelper.hh>
|
||||
#include <qpdf/QTC.hh>
|
||||
#include <qpdf/QPDF.hh>
|
||||
#include <qpdf/QUtil.hh>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
QPDFFileSpecObjectHelper::QPDFFileSpecObjectHelper(
|
||||
QPDFObjectHandle oh) :
|
||||
QPDFObjectHelper(oh)
|
||||
{
|
||||
if (! oh.isDictionary())
|
||||
{
|
||||
oh.warnIfPossible("Embedded file object is not a dictionary");
|
||||
return;
|
||||
}
|
||||
auto type = oh.getKey("/Type");
|
||||
if (! (type.isName() && (type.getName() == "/Filespec")))
|
||||
{
|
||||
oh.warnIfPossible("Embedded file object's type is not /Filespec");
|
||||
}
|
||||
}
|
||||
|
||||
QPDFFileSpecObjectHelper::Members::Members()
|
||||
{
|
||||
}
|
||||
|
||||
static std::vector<std::string> name_keys = {
|
||||
"/UF", "/F", "/Unix", "/DOS", "/Mac"};
|
||||
|
||||
std::string
|
||||
QPDFFileSpecObjectHelper::getDescription()
|
||||
{
|
||||
std::string result;
|
||||
auto desc = this->oh.getKey("/Desc");
|
||||
if (desc.isString())
|
||||
{
|
||||
result = desc.getUTF8Value();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string
|
||||
QPDFFileSpecObjectHelper::getFilename()
|
||||
{
|
||||
for (auto const& i: name_keys)
|
||||
{
|
||||
auto k = this->oh.getKey(i);
|
||||
if (k.isString())
|
||||
{
|
||||
return k.getUTF8Value();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::map<std::string, std::string>
|
||||
QPDFFileSpecObjectHelper::getFilenames()
|
||||
{
|
||||
std::map<std::string, std::string> result;
|
||||
for (auto const& i: name_keys)
|
||||
{
|
||||
auto k = this->oh.getKey(i);
|
||||
if (k.isString())
|
||||
{
|
||||
result[i] = k.getUTF8Value();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QPDFObjectHandle
|
||||
QPDFFileSpecObjectHelper::getEmbeddedFileStream(std::string const& key)
|
||||
{
|
||||
auto ef = this->oh.getKey("/EF");
|
||||
if (! ef.isDictionary())
|
||||
{
|
||||
return QPDFObjectHandle::newNull();
|
||||
}
|
||||
if (! key.empty())
|
||||
{
|
||||
return ef.getKey(key);
|
||||
}
|
||||
for (auto const& i: name_keys)
|
||||
{
|
||||
auto k = ef.getKey(i);
|
||||
if (k.isStream())
|
||||
{
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return QPDFObjectHandle::newNull();
|
||||
}
|
||||
|
||||
QPDFObjectHandle
|
||||
QPDFFileSpecObjectHelper::getEmbeddedFileStreams()
|
||||
{
|
||||
return this->oh.getKey("/EF");
|
||||
}
|
||||
|
||||
QPDFFileSpecObjectHelper
|
||||
QPDFFileSpecObjectHelper::createFileSpec(
|
||||
QPDF& qpdf,
|
||||
std::string const& filename,
|
||||
std::string const& fullpath)
|
||||
{
|
||||
return createFileSpec(
|
||||
qpdf, filename,
|
||||
QPDFEFStreamObjectHelper::createEFStream(
|
||||
qpdf,
|
||||
QUtil::file_provider(fullpath)));
|
||||
}
|
||||
|
||||
QPDFFileSpecObjectHelper
|
||||
QPDFFileSpecObjectHelper::createFileSpec(
|
||||
QPDF& qpdf,
|
||||
std::string const& filename,
|
||||
QPDFEFStreamObjectHelper efsoh)
|
||||
{
|
||||
auto oh = qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary());
|
||||
oh.replaceKey("/Type", QPDFObjectHandle::newName("/Filespec"));
|
||||
QPDFFileSpecObjectHelper result(oh);
|
||||
result.setFilename(filename);
|
||||
auto ef = QPDFObjectHandle::newDictionary();
|
||||
ef.replaceKey("/F", efsoh.getObjectHandle());
|
||||
ef.replaceKey("/UF", efsoh.getObjectHandle());
|
||||
oh.replaceKey("/EF", ef);
|
||||
return result;
|
||||
}
|
||||
|
||||
QPDFFileSpecObjectHelper&
|
||||
QPDFFileSpecObjectHelper::setDescription(std::string const& desc)
|
||||
{
|
||||
this->oh.replaceKey("/Desc", QPDFObjectHandle::newUnicodeString(desc));
|
||||
return *this;
|
||||
}
|
||||
|
||||
QPDFFileSpecObjectHelper&
|
||||
QPDFFileSpecObjectHelper::setFilename(
|
||||
std::string const& unicode_name,
|
||||
std::string const& compat_name)
|
||||
{
|
||||
auto uf = QPDFObjectHandle::newUnicodeString(unicode_name);
|
||||
this->oh.replaceKey("/UF", uf);
|
||||
if (compat_name.empty())
|
||||
{
|
||||
QTC::TC("qpdf", "QPDFFileSpecObjectHelper empty compat_name");
|
||||
this->oh.replaceKey("/F", uf);
|
||||
}
|
||||
else
|
||||
{
|
||||
QTC::TC("qpdf", "QPDFFileSpecObjectHelper non-empty compat_name");
|
||||
this->oh.replaceKey("/F", QPDFObjectHandle::newString(compat_name));
|
||||
}
|
||||
return *this;
|
||||
}
|
@ -58,7 +58,10 @@ SRCS_libqpdf = \
|
||||
libqpdf/QPDFAcroFormDocumentHelper.cc \
|
||||
libqpdf/QPDFAnnotationObjectHelper.cc \
|
||||
libqpdf/QPDFCryptoProvider.cc \
|
||||
libqpdf/QPDFEFStreamObjectHelper.cc \
|
||||
libqpdf/QPDFEmbeddedFileDocumentHelper.cc \
|
||||
libqpdf/QPDFExc.cc \
|
||||
libqpdf/QPDFFileSpecObjectHelper.cc \
|
||||
libqpdf/QPDFFormFieldObjectHelper.cc \
|
||||
libqpdf/QPDFMatrix.cc \
|
||||
libqpdf/QPDFNameTreeObjectHelper.cc \
|
||||
|
@ -4949,6 +4949,16 @@ print "\n";
|
||||
working with PDF timestamp strings.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Add new helper classes for supporting file attachments, also
|
||||
known as embedded files. New classes are
|
||||
<classname>QPDFEmbeddedFileDocumentHelper</classname>,
|
||||
<classname>QPDFFileSpecObjectHelper</classname>, and
|
||||
<classname>QPDFEFStreamObjectHelper</classname>. See their
|
||||
respective headers for details.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Add <function>warn</function> to
|
||||
|
@ -569,3 +569,5 @@ QPDFPageObjectHelper unresolved names 0
|
||||
QPDFPageObjectHelper resolving unresolved 0
|
||||
qpdf password stdin 0
|
||||
qpdf password file 0
|
||||
QPDFFileSpecObjectHelper empty compat_name 0
|
||||
QPDFFileSpecObjectHelper non-empty compat_name 0
|
||||
|
@ -520,6 +520,29 @@ $td->runtest("page operations on form xobject",
|
||||
$td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
|
||||
show_ntests();
|
||||
# ----------
|
||||
$td->notify("--- File Attachments ---");
|
||||
$n_tests += 4;
|
||||
|
||||
open(F, ">auto-txt") or die;
|
||||
print F "from file";
|
||||
close(F);
|
||||
$td->runtest("attachments",
|
||||
{$td->COMMAND => "test_driver 76 minimal.pdf auto-txt"},
|
||||
{$td->FILE => "test76.out", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
$td->runtest("check output",
|
||||
{$td->FILE => "a.pdf"},
|
||||
{$td->FILE => "test76.pdf"});
|
||||
$td->runtest("attachments",
|
||||
{$td->COMMAND => "test_driver 77 test76.pdf"},
|
||||
{$td->STRING => "test 77 done\n", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
$td->runtest("check output",
|
||||
{$td->FILE => "a.pdf"},
|
||||
{$td->FILE => "test77.pdf"});
|
||||
|
||||
show_ntests();
|
||||
# ----------
|
||||
$td->notify("--- Stream Replacement Tests ---");
|
||||
|
4
qpdf/qtest/qpdf/test76.out
Normal file
4
qpdf/qtest/qpdf/test76.out
Normal file
@ -0,0 +1,4 @@
|
||||
att1 -> att1.txt
|
||||
att2 -> att2.txt
|
||||
att3 -> π.txt
|
||||
test 76 done
|
233
qpdf/qtest/qpdf/test76.pdf
Normal file
233
qpdf/qtest/qpdf/test76.pdf
Normal file
@ -0,0 +1,233 @@
|
||||
%PDF-1.3
|
||||
%¿÷¢þ
|
||||
%QDF-1.0
|
||||
|
||||
%% Original object ID: 1 0
|
||||
1 0 obj
|
||||
<<
|
||||
/Names <<
|
||||
/EmbeddedFiles 2 0 R
|
||||
>>
|
||||
/Pages 3 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 9 0
|
||||
2 0 obj
|
||||
<<
|
||||
/Names [
|
||||
(att1)
|
||||
4 0 R
|
||||
(att2)
|
||||
5 0 R
|
||||
(att3)
|
||||
6 0 R
|
||||
]
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 2 0
|
||||
3 0 obj
|
||||
<<
|
||||
/Count 1
|
||||
/Kids [
|
||||
7 0 R
|
||||
]
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 8 0
|
||||
4 0 obj
|
||||
<<
|
||||
/Desc (some text)
|
||||
/EF <<
|
||||
/F 8 0 R
|
||||
/UF 8 0 R
|
||||
>>
|
||||
/F (att1.txt)
|
||||
/Type /Filespec
|
||||
/UF (att1.txt)
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 12 0
|
||||
5 0 obj
|
||||
<<
|
||||
/EF <<
|
||||
/F 10 0 R
|
||||
/UF 10 0 R
|
||||
>>
|
||||
/F (att2.txt)
|
||||
/Type /Filespec
|
||||
/UF (att2.txt)
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 13 0
|
||||
6 0 obj
|
||||
<<
|
||||
/EF <<
|
||||
/F 12 0 R
|
||||
/UF 12 0 R
|
||||
>>
|
||||
/F (att3.txt)
|
||||
/Type /Filespec
|
||||
/UF <feff03c0002e007400780074>
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Page 1
|
||||
%% Original object ID: 3 0
|
||||
7 0 obj
|
||||
<<
|
||||
/Contents 14 0 R
|
||||
/MediaBox [
|
||||
0
|
||||
0
|
||||
612
|
||||
792
|
||||
]
|
||||
/Parent 3 0 R
|
||||
/Resources <<
|
||||
/Font <<
|
||||
/F1 16 0 R
|
||||
>>
|
||||
/ProcSet 17 0 R
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 7 0
|
||||
8 0 obj
|
||||
<<
|
||||
/Params <<
|
||||
/CheckSum <2e10f186a4cdf5be438747f4bdc2d4d4>
|
||||
/CreationDate (D:20210207191121-05'00')
|
||||
/ModDate (D:20210208001122Z)
|
||||
/Size 9
|
||||
/Subtype /text#2fplain
|
||||
>>
|
||||
/Type /EmbeddedFile
|
||||
/Length 9 0 R
|
||||
>>
|
||||
stream
|
||||
from file
|
||||
endstream
|
||||
endobj
|
||||
|
||||
%QDF: ignore_newline
|
||||
9 0 obj
|
||||
9
|
||||
endobj
|
||||
|
||||
%% Original object ID: 10 0
|
||||
10 0 obj
|
||||
<<
|
||||
/Params <<
|
||||
/CheckSum <2fce9c8228e360ba9b04a1bd1bf63d6b>
|
||||
/Size 11
|
||||
/Subtype /text#2fplain
|
||||
>>
|
||||
/Type /EmbeddedFile
|
||||
/Length 11 0 R
|
||||
>>
|
||||
stream
|
||||
from string
|
||||
endstream
|
||||
endobj
|
||||
|
||||
%QDF: ignore_newline
|
||||
11 0 obj
|
||||
11
|
||||
endobj
|
||||
|
||||
%% Original object ID: 11 0
|
||||
12 0 obj
|
||||
<<
|
||||
/Params <<
|
||||
/CheckSum <2236c155b1d62b7f00285bba081d4336>
|
||||
/Size 11
|
||||
/Subtype /text#2fplain
|
||||
>>
|
||||
/Type /EmbeddedFile
|
||||
/Length 13 0 R
|
||||
>>
|
||||
stream
|
||||
from buffer
|
||||
endstream
|
||||
endobj
|
||||
|
||||
%QDF: ignore_newline
|
||||
13 0 obj
|
||||
11
|
||||
endobj
|
||||
|
||||
%% Contents for page 1
|
||||
%% Original object ID: 4 0
|
||||
14 0 obj
|
||||
<<
|
||||
/Length 15 0 R
|
||||
>>
|
||||
stream
|
||||
BT
|
||||
/F1 24 Tf
|
||||
72 720 Td
|
||||
(Potato) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
15 0 obj
|
||||
44
|
||||
endobj
|
||||
|
||||
%% Original object ID: 6 0
|
||||
16 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica
|
||||
/Encoding /WinAnsiEncoding
|
||||
/Name /F1
|
||||
/Subtype /Type1
|
||||
/Type /Font
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 5 0
|
||||
17 0 obj
|
||||
[
|
||||
/PDF
|
||||
/Text
|
||||
]
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 18
|
||||
0000000000 65535 f
|
||||
0000000052 00000 n
|
||||
0000000175 00000 n
|
||||
0000000302 00000 n
|
||||
0000000401 00000 n
|
||||
0000000563 00000 n
|
||||
0000000707 00000 n
|
||||
0000000876 00000 n
|
||||
0000001098 00000 n
|
||||
0000001389 00000 n
|
||||
0000001435 00000 n
|
||||
0000001654 00000 n
|
||||
0000001702 00000 n
|
||||
0000001921 00000 n
|
||||
0000001991 00000 n
|
||||
0000002092 00000 n
|
||||
0000002139 00000 n
|
||||
0000002285 00000 n
|
||||
trailer <<
|
||||
/Root 1 0 R
|
||||
/Size 18
|
||||
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
|
||||
>>
|
||||
startxref
|
||||
2321
|
||||
%%EOF
|
194
qpdf/qtest/qpdf/test77.pdf
Normal file
194
qpdf/qtest/qpdf/test77.pdf
Normal file
@ -0,0 +1,194 @@
|
||||
%PDF-1.3
|
||||
%¿÷¢þ
|
||||
%QDF-1.0
|
||||
|
||||
%% Original object ID: 1 0
|
||||
1 0 obj
|
||||
<<
|
||||
/Names <<
|
||||
/EmbeddedFiles 2 0 R
|
||||
>>
|
||||
/Pages 3 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 2 0
|
||||
2 0 obj
|
||||
<<
|
||||
/Names [
|
||||
(att1)
|
||||
4 0 R
|
||||
(att3)
|
||||
5 0 R
|
||||
]
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 3 0
|
||||
3 0 obj
|
||||
<<
|
||||
/Count 1
|
||||
/Kids [
|
||||
6 0 R
|
||||
]
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 4 0
|
||||
4 0 obj
|
||||
<<
|
||||
/Desc (some text)
|
||||
/EF <<
|
||||
/F 7 0 R
|
||||
/UF 7 0 R
|
||||
>>
|
||||
/F (att1.txt)
|
||||
/Type /Filespec
|
||||
/UF (att1.txt)
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 6 0
|
||||
5 0 obj
|
||||
<<
|
||||
/EF <<
|
||||
/F 9 0 R
|
||||
/UF 9 0 R
|
||||
>>
|
||||
/F (att3.txt)
|
||||
/Type /Filespec
|
||||
/UF <feff03c0002e007400780074>
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Page 1
|
||||
%% Original object ID: 7 0
|
||||
6 0 obj
|
||||
<<
|
||||
/Contents 11 0 R
|
||||
/MediaBox [
|
||||
0
|
||||
0
|
||||
612
|
||||
792
|
||||
]
|
||||
/Parent 3 0 R
|
||||
/Resources <<
|
||||
/Font <<
|
||||
/F1 13 0 R
|
||||
>>
|
||||
/ProcSet 14 0 R
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 8 0
|
||||
7 0 obj
|
||||
<<
|
||||
/Params <<
|
||||
/CheckSum <2e10f186a4cdf5be438747f4bdc2d4d4>
|
||||
/CreationDate (D:20210207191121-05'00')
|
||||
/ModDate (D:20210208001122Z)
|
||||
/Size 9
|
||||
/Subtype /text#2fplain
|
||||
>>
|
||||
/Type /EmbeddedFile
|
||||
/Length 8 0 R
|
||||
>>
|
||||
stream
|
||||
from file
|
||||
endstream
|
||||
endobj
|
||||
|
||||
%QDF: ignore_newline
|
||||
8 0 obj
|
||||
9
|
||||
endobj
|
||||
|
||||
%% Original object ID: 12 0
|
||||
9 0 obj
|
||||
<<
|
||||
/Params <<
|
||||
/CheckSum <2236c155b1d62b7f00285bba081d4336>
|
||||
/Size 11
|
||||
/Subtype /text#2fplain
|
||||
>>
|
||||
/Type /EmbeddedFile
|
||||
/Length 10 0 R
|
||||
>>
|
||||
stream
|
||||
from buffer
|
||||
endstream
|
||||
endobj
|
||||
|
||||
%QDF: ignore_newline
|
||||
10 0 obj
|
||||
11
|
||||
endobj
|
||||
|
||||
%% Contents for page 1
|
||||
%% Original object ID: 14 0
|
||||
11 0 obj
|
||||
<<
|
||||
/Length 12 0 R
|
||||
>>
|
||||
stream
|
||||
BT
|
||||
/F1 24 Tf
|
||||
72 720 Td
|
||||
(Potato) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
12 0 obj
|
||||
44
|
||||
endobj
|
||||
|
||||
%% Original object ID: 16 0
|
||||
13 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica
|
||||
/Encoding /WinAnsiEncoding
|
||||
/Name /F1
|
||||
/Subtype /Type1
|
||||
/Type /Font
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Original object ID: 17 0
|
||||
14 0 obj
|
||||
[
|
||||
/PDF
|
||||
/Text
|
||||
]
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 15
|
||||
0000000000 65535 f
|
||||
0000000052 00000 n
|
||||
0000000175 00000 n
|
||||
0000000281 00000 n
|
||||
0000000380 00000 n
|
||||
0000000541 00000 n
|
||||
0000000708 00000 n
|
||||
0000000930 00000 n
|
||||
0000001221 00000 n
|
||||
0000001267 00000 n
|
||||
0000001485 00000 n
|
||||
0000001556 00000 n
|
||||
0000001657 00000 n
|
||||
0000001705 00000 n
|
||||
0000001852 00000 n
|
||||
trailer <<
|
||||
/Root 1 0 R
|
||||
/Size 15
|
||||
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
|
||||
>>
|
||||
startxref
|
||||
1888
|
||||
%%EOF
|
@ -10,6 +10,7 @@
|
||||
#include <qpdf/QPDFNameTreeObjectHelper.hh>
|
||||
#include <qpdf/QPDFPageLabelDocumentHelper.hh>
|
||||
#include <qpdf/QPDFOutlineDocumentHelper.hh>
|
||||
#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
|
||||
#include <qpdf/QUtil.hh>
|
||||
#include <qpdf/QTC.hh>
|
||||
#include <qpdf/Pl_StdioFile.hh>
|
||||
@ -2716,6 +2717,67 @@ void runtest(int n, char const* filename1, char const* arg2)
|
||||
w.setQDFMode(true);
|
||||
w.write();
|
||||
}
|
||||
else if (n == 76)
|
||||
{
|
||||
// Embedded files. arg2 is a file to attach. Hard-code the
|
||||
// mime type and file name for test purposes.
|
||||
QPDFEmbeddedFileDocumentHelper efdh(pdf);
|
||||
auto fs1 = QPDFFileSpecObjectHelper::createFileSpec(
|
||||
pdf, "att1.txt", arg2);
|
||||
fs1.setDescription("some text");
|
||||
auto efs1 = QPDFEFStreamObjectHelper(fs1.getEmbeddedFileStream());
|
||||
efs1.setSubtype("text/plain")
|
||||
.setCreationDate("D:20210207191121-05'00'")
|
||||
.setModDate("D:20210208001122Z");
|
||||
efdh.replaceEmbeddedFile("att1", fs1);
|
||||
auto efs2 = QPDFEFStreamObjectHelper::createEFStream(
|
||||
pdf, "from string");
|
||||
efs2.setSubtype("text/plain");
|
||||
Pl_Buffer p("buffer");
|
||||
p.write(QUtil::unsigned_char_pointer("from buffer"), 11);
|
||||
p.finish();
|
||||
auto efs3 = QPDFEFStreamObjectHelper::createEFStream(
|
||||
pdf, p.getBuffer());
|
||||
efs3.setSubtype("text/plain");
|
||||
efdh.replaceEmbeddedFile(
|
||||
"att2", QPDFFileSpecObjectHelper::createFileSpec(
|
||||
pdf, "att2.txt", efs2));
|
||||
auto fs3 = QPDFFileSpecObjectHelper::createFileSpec(
|
||||
pdf, "att3.txt", efs3);
|
||||
efdh.replaceEmbeddedFile("att3", fs3);
|
||||
fs3.setFilename("\xcf\x80.txt", "att3.txt");
|
||||
|
||||
assert(efs1.getCreationDate() == "D:20210207191121-05'00'");
|
||||
assert(efs1.getModDate() == "D:20210208001122Z");
|
||||
assert(efs2.getSize() == 11);
|
||||
assert(efs2.getSubtype() == "text/plain");
|
||||
assert(QUtil::hex_encode(efs2.getChecksum()) ==
|
||||
"2fce9c8228e360ba9b04a1bd1bf63d6b");
|
||||
|
||||
for (auto iter: efdh.getEmbeddedFiles())
|
||||
{
|
||||
std::cout << iter.first << " -> " << iter.second->getFilename()
|
||||
<< std::endl;
|
||||
}
|
||||
assert(efdh.getEmbeddedFile("att1")->getFilename() == "att1.txt");
|
||||
assert(! efdh.getEmbeddedFile("potato"));
|
||||
|
||||
QPDFWriter w(pdf, "a.pdf");
|
||||
w.setStaticID(true);
|
||||
w.setQDFMode(true);
|
||||
w.write();
|
||||
}
|
||||
else if (n == 77)
|
||||
{
|
||||
QPDFEmbeddedFileDocumentHelper efdh(pdf);
|
||||
assert(efdh.removeEmbeddedFile("att2"));
|
||||
assert(! efdh.removeEmbeddedFile("att2"));
|
||||
|
||||
QPDFWriter w(pdf, "a.pdf");
|
||||
w.setStaticID(true);
|
||||
w.setQDFMode(true);
|
||||
w.write();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(std::string("invalid test ") +
|
||||
|
Loading…
Reference in New Issue
Block a user