Generating code

This commit is contained in:
Jakub Melka
2020-03-21 16:36:27 +01:00
parent 96d2e33692
commit bc6ca3fc46
15 changed files with 966 additions and 9 deletions

View File

@@ -232,6 +232,11 @@ PDFObjectReference PDFObjectStorage::addObject(PDFObject object)
return reference;
}
void PDFObjectStorage::setObject(PDFObjectReference reference, PDFObject object)
{
m_objects[reference.objectNumber] = Entry(reference.generation, qMove(object));
}
QByteArray PDFDocumentDataLoaderDecorator::readName(const PDFObject& object)
{
const PDFObject& dereferencedObject = m_document->getObject(object);

View File

@@ -84,6 +84,11 @@ public:
/// \returns Reference to new object
PDFObjectReference addObject(PDFObject object);
/// Sets object to object storage. Reference must exist.
/// \param reference Reference to object
/// \param object New value of object
void setObject(PDFObjectReference reference, PDFObject object);
private:
PDFObjects m_objects;
PDFObject m_trailerDictionary;

View File

@@ -16,6 +16,7 @@
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
#include "pdfdocumentbuilder.h"
#include "pdfencoding.h"
namespace pdf
{
@@ -62,6 +63,82 @@ void PDFObjectFactory::endDictionaryItem()
std::get<PDFDictionary>(dictionaryItem.object).addEntry(qMove(topItem.itemName), qMove(std::get<PDFObject>(topItem.object)));
}
PDFObjectFactory& PDFObjectFactory::operator<<(QString textString)
{
if (!PDFEncoding::canConvertToEncoding(textString, PDFEncoding::Encoding::PDFDoc))
{
// Use unicode encoding
QByteArray ba;
{
QTextStream textStream(&ba, QIODevice::WriteOnly);
textStream.setCodec("UTF-16BE");
textStream.setGenerateByteOrderMark(true);
textStream << textString;
}
addObject(PDFObject::createString(std::make_shared<PDFString>(qMove(ba))));
}
else
{
// Use PDF document encoding
addObject(PDFObject::createString(std::make_shared<PDFString>(PDFEncoding::convertToEncoding(textString, PDFEncoding::Encoding::PDFDoc))));
}
return *this;
}
PDFObjectFactory& PDFObjectFactory::operator<<(WrapAnnotationColor color)
{
if (color.color.isValid())
{
// Jakub Melka: we will decide, if we have gray/rgb/cmyk color
QColor value = color.color;
if (value.spec() == QColor::Cmyk)
{
*this << std::initializer_list<PDFReal>{ value.cyanF(), value.magentaF(), value.yellowF(), value.blackF() };
}
else if (qIsGray(value.rgb()))
{
*this << std::initializer_list<PDFReal>{ value.redF() };
}
else
{
*this << std::initializer_list<PDFReal>{ value.redF(), value.greenF(), value.blueF() };
}
}
else
{
addObject(PDFObject::createNull());
}
return *this;
}
PDFObjectFactory& PDFObjectFactory::operator<<(WrapCurrentDateTime)
{
addObject(PDFObject::createString(std::make_shared<PDFString>(PDFEncoding::converDateTimeToString(QDateTime::currentDateTime()))));
return *this;
}
PDFObjectFactory& PDFObjectFactory::operator<<(const QRectF& value)
{
*this << std::initializer_list<PDFReal>{ value.left(), value.top(), value.right(), value.bottom() };
return *this;
}
PDFObjectFactory& PDFObjectFactory::operator<<(int value)
{
*this << PDFInteger(value);
return *this;
}
PDFObjectFactory& PDFObjectFactory::operator<<(WrapName wrapName)
{
addObject(PDFObject::createName(std::make_shared<PDFString>(qMove(wrapName.name))));
return *this;
}
PDFObject PDFObjectFactory::takeObject()
{
Q_ASSERT(m_items.size() == 1);
@@ -154,8 +231,111 @@ PDFDocument PDFDocumentBuilder::build() const
return PDFDocument(PDFObjectStorage(m_storage), m_version);
}
PDFObjectReference PDFDocumentBuilder::addObject(PDFObject object)
{
return m_storage.addObject(PDFObjectManipulator::removeNullObjects(object));
}
void PDFDocumentBuilder::mergeTo(PDFObjectReference reference, PDFObject object)
{
m_storage.setObject(reference, PDFObjectManipulator::merge(m_storage.getObject(reference), qMove(object), PDFObjectManipulator::RemoveNullObjects));
}
QRectF PDFDocumentBuilder::getPopupWindowRect(const QRectF& rectangle) const
{
return rectangle.translated(rectangle.width() * 1.25, 0);
}
/* START GENERATED CODE */
PDFObjectReference PDFDocumentBuilder::createAnnotationSquare(PDFObjectReference page,
QRectF rectangle,
PDFReal borderWidth,
QColor fillColor,
QColor strokeColor,
QString title,
QString subject,
QString contents)
{
PDFObjectFactory objectBuilder;
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Subtype");
objectBuilder << WrapName("Square");
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Rect");
objectBuilder << rectangle;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("F");
objectBuilder << 4;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("P");
objectBuilder << page;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("M");
objectBuilder << WrapCurrentDateTime();
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("CreationDate");
objectBuilder << WrapCurrentDateTime();
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Border");
objectBuilder << std::initializer_list<PDFReal>{ 0.0, 0.0, borderWidth };
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("C");
objectBuilder << WrapAnnotationColor(strokeColor);
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("IC");
objectBuilder << WrapAnnotationColor(fillColor);
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("T");
objectBuilder << title;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Contents");
objectBuilder << contents;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Subj");
objectBuilder << subject;
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObjectReference annotationObject = addObject(objectBuilder.takeObject());
PDFObjectReference popupAnnotation = createAnnotationPopup(page, annotationObject, getPopupWindowRect(rectangle), false);
objectBuilder.beginDictionaryItem("Popup");
objectBuilder << popupAnnotation;
objectBuilder.endDictionaryItem();
PDFObject updateAnnotationPopup = objectBuilder.takeObject();
mergeTo(annotationObject, updateAnnotationPopup);
return PDFObjectReference();
}
PDFObjectReference PDFDocumentBuilder::createAnnotationPopup(PDFObjectReference page,
PDFObjectReference parentAnnotation,
QRectF rectangle,
bool opened)
{
PDFObjectFactory objectBuilder;
objectBuilder.beginDictionary();
objectBuilder.beginDictionaryItem("Subtype");
objectBuilder << WrapName("Popup");
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Rect");
objectBuilder << rectangle;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("P");
objectBuilder << page;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Parent");
objectBuilder << parentAnnotation;
objectBuilder.endDictionaryItem();
objectBuilder.beginDictionaryItem("Open");
objectBuilder << opened;
objectBuilder.endDictionaryItem();
objectBuilder.endDictionary();
PDFObjectReference popupAnnotation = addObject(objectBuilder.takeObject());
return popupAnnotation;
}
/* END GENERATED CODE */
} // namespace pdf

View File

@@ -24,6 +24,30 @@
namespace pdf
{
struct WrapName
{
WrapName(const char* name) :
name(name)
{
}
QByteArray name;
};
struct WrapAnnotationColor
{
WrapAnnotationColor(QColor color) :
color(color)
{
}
QColor color;
};
struct WrapCurrentDateTime { };
/// Factory for creating various PDF objects, such as simple objects,
/// dictionaries, arrays etc.
class PDFObjectFactory
@@ -45,6 +69,12 @@ public:
PDFObjectFactory& operator<<(PDFReal value);
PDFObjectFactory& operator<<(PDFInteger value);
PDFObjectFactory& operator<<(PDFObjectReference value);
PDFObjectFactory& operator<<(WrapName wrapName);
PDFObjectFactory& operator<<(int value);
PDFObjectFactory& operator<<(const QRectF& value);
PDFObjectFactory& operator<<(WrapCurrentDateTime);
PDFObjectFactory& operator<<(WrapAnnotationColor color);
PDFObjectFactory& operator<<(QString textString);
/// Treat containers - write them as array
template<typename Container, typename ValueType = decltype(*std::begin(std::declval<Container>()))>
@@ -119,11 +149,52 @@ public:
PDFDocument build() const;
/* START GENERATED CODE */
/* START GENERATED CODE */
/* END GENERATED CODE */
/// Square annotation displays rectangle (or square). When opened, they display pop-up window
/// containing the text of associated note (and window title). Square border/fill color can be defined,
/// along with border width.
/// \param page Page to which is annotation added
/// \param rectangle Area in which is rectangle displayed
/// \param borderWidth Width of the border line of rectangle
/// \param fillColor Fill color of rectangle (interior color). If you do not want to have area color filled,
/// then use invalid QColor.
/// \param strokeColor Stroke color (color of the rectangle border). If you do not want to have a
/// border, then use invalid QColor.
/// \param title Title (it is displayed as title of popup window)
/// \param subject Subject (short description of the subject being adressed by the annotation)
/// \param contents Contents (text displayed, for example, in the marked annotation dialog)
PDFObjectReference createAnnotationSquare(PDFObjectReference page,
QRectF rectangle,
PDFReal borderWidth,
QColor fillColor,
QColor strokeColor,
QString title,
QString subject,
QString contents);
/// Creates a new popup annotation on the page. Popup annotation is represented usually by floating
/// window, which can be opened, or closed. Popup annotation is associated with parent annotation,
/// which can be usually markup annotation. Popup annotation displays parent annotation's texts, for
/// example, title, comment, date etc.
/// \param page Page to which is annotation added
/// \param parentAnnotation Parent annotation (for which is popup window displayed)
/// \param rectangle Area on the page, where popup window appears
/// \param opened Is the window opened?
PDFObjectReference createAnnotationPopup(PDFObjectReference page,
PDFObjectReference parentAnnotation,
QRectF rectangle,
bool opened);
/* END GENERATED CODE */
private:
PDFObjectReference addObject(PDFObject object);
void mergeTo(PDFObjectReference reference, PDFObject object);
QRectF getPopupWindowRect(const QRectF& rectangle) const;
PDFObjectStorage m_storage;
PDFVersion m_version;
};

View File

@@ -2149,6 +2149,7 @@ QByteArray PDFEncoding::convertToEncoding(const QString& string, PDFEncoding::En
if (unicode == (*table)[static_cast<unsigned char>(i)])
{
converted = i;
break;
}
}
@@ -2158,6 +2159,34 @@ QByteArray PDFEncoding::convertToEncoding(const QString& string, PDFEncoding::En
return result;
}
bool PDFEncoding::canConvertToEncoding(const QString& string, PDFEncoding::Encoding encoding)
{
const encoding::EncodingTable* table = getTableForEncoding(encoding);
Q_ASSERT(table);
for (QChar character : string)
{
ushort unicode = character.unicode();
bool converted = false;
for (int i = 0; i < table->size(); ++i)
{
if (unicode == (*table)[static_cast<unsigned char>(i)])
{
converted = true;
break;
}
}
if (!converted)
{
return false;
}
}
return true;
}
QString PDFEncoding::convertTextString(const QByteArray& stream)
{
if (hasUnicodeLeadMarkings(stream))
@@ -2259,6 +2288,13 @@ QDateTime PDFEncoding::convertToDateTime(const QByteArray& stream)
return QDateTime();
}
QByteArray PDFEncoding::converDateTimeToString(QDateTime dateTime)
{
QDateTime utcDateTime = dateTime.toUTC();
QString convertedDateTime = QString("D:%1").arg(utcDateTime.toString("yyyyMMddhhmmss"));
return convertedDateTime.toLatin1();
}
const encoding::EncodingTable* PDFEncoding::getTableForEncoding(Encoding encoding)
{
switch (encoding)

View File

@@ -70,6 +70,13 @@ public:
/// \sa convert
static QByteArray convertToEncoding(const QString& string, Encoding encoding);
/// Verifies, if string with given unicode characters can be converted using
/// the specified encoding (so, all unicode characters present in the string
/// are also present in given encoding).
/// \param string String to be tested
/// \param encoding Encoding used in verification of conversion
static bool canConvertToEncoding(const QString& string, Encoding encoding);
/// Convert text string to the unicode string, using either PDFDocEncoding,
/// or UTF-16BE encoding. Please see PDF Reference 1.7, Chapter 3.8.1. If
/// UTF-16BE encoding is used, then leading bytes should be 0xFE and 0xFF
@@ -88,6 +95,11 @@ public:
/// \param stream Stream, from which date/time is read
static QDateTime convertToDateTime(const QByteArray& stream);
/// Convert date/time to string according to PDF Reference 1.7, Chapter 3.8.1.
/// If date is invalid, empty byte array is returned.
/// \param dateTime Date and time to be converted
static QByteArray converDateTimeToString(QDateTime dateTime);
/// Returns conversion table for particular encoding
/// \param encoding Encoding
static const encoding::EncodingTable* getTableForEncoding(Encoding encoding);

View File

@@ -204,6 +204,25 @@ const PDFObject& PDFDictionary::get(const char* key) const
}
}
void PDFDictionary::setEntry(const QByteArray& key, PDFObject&& value)
{
auto it = find(key);
if (it != m_dictionary.end())
{
it->second = qMove(value);
}
else
{
addEntry(QByteArray(key), qMove(value));
}
}
void PDFDictionary::removeNullObjects()
{
m_dictionary.erase(std::remove_if(m_dictionary.begin(), m_dictionary.end(), [](const DictionaryEntry& entry) { return entry.second.isNull(); }), m_dictionary.end());
m_dictionary.shrink_to_fit();
}
void PDFDictionary::optimize()
{
m_dictionary.shrink_to_fit();
@@ -219,6 +238,11 @@ std::vector<PDFDictionary::DictionaryEntry>::const_iterator PDFDictionary::find(
return std::find_if(m_dictionary.cbegin(), m_dictionary.cend(), [&key](const DictionaryEntry& entry) { return entry.first == key; });
}
std::vector<PDFDictionary::DictionaryEntry>::iterator PDFDictionary::find(const QByteArray& key)
{
return std::find_if(m_dictionary.begin(), m_dictionary.end(), [&key](const DictionaryEntry& entry) { return entry.first == key; });
}
std::vector<PDFDictionary::DictionaryEntry>::const_iterator PDFDictionary::find(const char* key) const
{
return std::find_if(m_dictionary.cbegin(), m_dictionary.cend(), [&key](const DictionaryEntry& entry) { return entry.first == key; });
@@ -231,4 +255,59 @@ bool PDFStream::equals(const PDFObjectContent* other) const
return m_dictionary.equals(&otherStream->m_dictionary) && m_content == otherStream->m_content;
}
PDFObject PDFObjectManipulator::merge(PDFObject left, PDFObject right, MergeFlags flags)
{
if (left.getType() != right.getType())
{
return right;
}
if (left.isDictionary())
{
Q_ASSERT(right.isDictionary());
PDFDictionary targetDictionary = *left.getDictionary();
const PDFDictionary& sourceDictionary = *right.getDictionary();
for (size_t i = 0, count = sourceDictionary.getCount(); i < count; ++i)
{
const QByteArray& key = sourceDictionary.getKey(i);
PDFObject value = merge(targetDictionary.get(key), sourceDictionary.getValue(i), flags);
targetDictionary.setEntry(key, qMove(value));
}
if (flags.testFlag(RemoveNullObjects))
{
targetDictionary.removeNullObjects();
}
return PDFObject::createDictionary(std::make_shared<PDFDictionary>(qMove(targetDictionary)));
}
else if (left.isArray() && flags.testFlag(ConcatenateArrays))
{
// Concatenate arrays
const PDFArray* leftArray = left.getArray();
const PDFArray* rightArray = right.getArray();
std::vector<PDFObject> objects;
objects.reserve(leftArray->getCount() + rightArray->getCount());
for (size_t i = 0, count = leftArray->getCount(); i < count; ++i)
{
objects.emplace_back(leftArray->getItem(i));
}
for (size_t i = 0, count = rightArray->getCount(); i < count; ++i)
{
objects.emplace_back(rightArray->getItem(i));
}
return PDFObject::createArray(std::make_shared<PDFArray>(qMove(objects)));
}
return right;
}
PDFObject PDFObjectManipulator::removeNullObjects(PDFObject object)
{
return merge(object, object, RemoveNullObjects);
}
} // namespace pdf

View File

@@ -95,6 +95,8 @@ public:
constexpr inline PDFObject& operator=(const PDFObject&) = default;
constexpr inline PDFObject& operator=(PDFObject&&) = default;
inline Type getType() const { return m_type; }
// Test operators
inline bool isNull() const { return m_type == Type::Null; }
inline bool isBool() const { return m_type == Type::Bool; }
@@ -260,6 +262,12 @@ public:
/// \param value Value
void addEntry(QByteArray&& key, PDFObject&& value) { m_dictionary.emplace_back(std::move(key), std::move(value)); }
/// Sets entry value. If entry with given key doesn't exist,
/// then it is created.
/// \param key Key
/// \param value Value
void setEntry(const QByteArray& key, PDFObject&& value);
/// Returns count of items in the dictionary
size_t getCount() const { return m_dictionary.size(); }
@@ -274,6 +282,9 @@ public:
/// \param index Zero-based index of value in the dictionary
const PDFObject& getValue(size_t index) const { return m_dictionary[index].second; }
/// Removes null objects from dictionary
void removeNullObjects();
/// Optimizes the dictionary for memory consumption
virtual void optimize() override;
@@ -283,6 +294,11 @@ private:
/// \param key Key to be found
std::vector<DictionaryEntry>::const_iterator find(const QByteArray& key) const;
/// Finds an item in the dictionary array, if the item is not in the dictionary,
/// then end iterator is returned.
/// \param key Key to be found
std::vector<DictionaryEntry>::iterator find(const QByteArray& key);
/// Finds an item in the dictionary array, if the item is not in the dictionary,
/// then end iterator is returned.
/// \param key Key to be found
@@ -322,6 +338,34 @@ private:
QByteArray m_content;
};
class PDFObjectManipulator
{
public:
explicit PDFObjectManipulator() = delete;
enum MergeFlag
{
RemoveNullObjects = 0x0001, ///< Remove null object from dictionaries
ConcatenateArrays = 0x0002, ///< Concatenate arrays instead of replace
};
Q_DECLARE_FLAGS(MergeFlags, MergeFlag)
/// Merges two objects. If object type is different, then object from right is used.
/// If both objects are dictionaries, then their content is merged, object \p right
/// has precedence over object \p left. If both objects are arrays, and concatenating
/// flag is turned on, then they are concatenated instead of replacing left array
/// by right array. If remove null objects flag is turend on, then null objects
/// are removed from dictionaries.
/// \param left Left, 'slave' object
/// \param right Right 'master' object, has priority over left
/// \param flags Merge flags
static PDFObject merge(PDFObject left, PDFObject right, MergeFlags flags);
/// Remove null objects from all dictionaries
/// \param object Object
static PDFObject removeNullObjects(PDFObject object);
};
} // namespace pdf
#endif // PDFOBJECT_H

View File

@@ -68,7 +68,6 @@ private:
PDFObjectReference m_reference;
};
void PDFDecryptObjectVisitor::visitNull()
{
m_objectStack.push_back(PDFObject::createNull());