Add PDF writer.

This commit is contained in:
jmsgrogan 2022-01-01 18:46:31 +00:00
parent c05b7b6315
commit 9c116b1efd
72 changed files with 1819 additions and 114 deletions

View file

@ -0,0 +1,36 @@
list(APPEND publishing_HEADERS
pdf/PdfDocument.h
pdf/PdfDocumentCatalog.h
pdf/PdfDictionary.h
pdf/PdfObject.h
pdf/PdfOutline.h
pdf/PdfPageTree.h
pdf/PdfPage.h
pdf/PdfStream.h
pdf/PdfXRefTable.h
pdf/PdfWriter.h
)
list(APPEND publishing_LIB_INCLUDES
pdf/PdfDocument.cpp
pdf/PdfDocumentCatalog.cpp
pdf/PdfDictionary.cpp
pdf/PdfObject.cpp
pdf/PdfOutline.cpp
pdf/PdfPageTree.cpp
pdf/PdfPage.cpp
pdf/PdfStream.cpp
pdf/PdfXRefTable.cpp
pdf/PdfWriter.cpp
)
add_library(publishing SHARED ${publishing_LIB_INCLUDES} ${publishing_INCLUDES} ${publishing_HEADERS})
target_include_directories(publishing PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/pdf"
)
set_target_properties( publishing PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON )
target_link_libraries( publishing PUBLIC core)
set_property(TARGET publishing PROPERTY FOLDER src)

View file

@ -0,0 +1,45 @@
#include "PdfDictionary.h"
std::string PdfDictionary::ToString() const
{
const auto keys = GetStringKeys();
if (keys.empty())
{
return "";
}
std::string content = " <<";
bool first = true;
auto ending = keys.size() == 1 ? "" : "\n";
for (const auto& key : keys)
{
if (first)
{
content += " /" + key + " " + GetItem(key) + ending;
first = false;
}
else
{
content += " /" + key + " " + GetItem(key) + ending;
}
}
const auto dictKeys = GetDictKeys();
ending = dictKeys.size() == 1 ? "" : "\n";
for (const auto& key : GetDictKeys())
{
auto pdfDict = dynamic_cast<PdfDictionary*>(GetDict(key));
if (first)
{
content += " /" + key + " " + pdfDict->ToString() + ending;
first = false;
}
else
{
content += " /" + key + " " + pdfDict->ToString() + ending;
}
}
content += " >>\n";
return content;
}

View file

@ -0,0 +1,10 @@
#pragma once
#include "Dictionary.h"
class PdfDictionary : public Dictionary
{
public:
std::string ToString() const;
};

View file

@ -0,0 +1,73 @@
#include "PdfDocument.h"
#include "PdfObject.h"
#include "PdfDocumentCatalog.h"
#include "PdfPageTree.h"
#include "PdfPage.h"
#include "PdfOutline.h"
#include "PdfXRefTable.h"
PdfDocument::PdfDocument()
{
mXRefTable = std::make_unique<PdfXRefTable>();
mCatalog = std::make_unique<PdfDocumentCatalog>();
}
PdfDocument::~PdfDocument()
{
}
std::string PdfDocument::ToString()
{
IndexObjects();
auto content = GetHeaderString();
content += GetBodyString();
content += GetXRefString();
content += GetTrailerString();
content += "%%EOF\n";
return content;
}
std::string PdfDocument::GetHeaderString()
{
auto content = "%PDF-" + mVersion + "\n";
mXRefOffset = content.size();
mXRefTable->AddRecord(content.size(), 65535, true);
return content;
}
std::string PdfDocument::GetBodyString()
{
const auto content = mCatalog->ToString(mXRefTable.get());
mXRefOffset += content.size();
return content;
}
std::string PdfDocument::GetXRefString()
{
return mXRefTable->ToString();
}
std::string PdfDocument::GetTrailerString()
{
const auto numObjects = mXRefTable->GetNumEntries();
mTrailer.AddStringItem("Size", std::to_string(numObjects));
mTrailer.AddStringItem("Root", mCatalog->GetRefString());
std::string content = "trailer\n";
content += mTrailer.ToString();
content += "startxref\n";
content += std::to_string(mXRefOffset) + "\n";
return content;
}
void PdfDocument::IndexObjects()
{
mCatalog->IndexObjects(0);
}

View file

@ -0,0 +1,44 @@
#pragma once
#include "PdfDictionary.h"
#include "StringUtils.h"
#include <memory>
#include <string>
#include <vector>
class PdfDocumentCatalog;
using PdfDocumentCatalogPtr = std::unique_ptr<PdfDocumentCatalog>;
class PdfXRefTable;
using PdfXRefTablePtr = std::unique_ptr<PdfXRefTable>;
class PdfDocument
{
public:
PdfDocument();
~PdfDocument();
std::string ToString();
private:
std::string GetHeaderString();
std::string GetTrailerString();
std::string GetBodyString();
std::string GetXRefString();
void IndexObjects();
private:
PdfDictionary mTrailer;
std::string mVersion {"1.7"};
PdfXRefTablePtr mXRefTable;
unsigned mXRefOffset{0};
PdfDocumentCatalogPtr mCatalog;
};
using PdfDocumentPtr = std::unique_ptr<PdfDocument>;

View file

@ -0,0 +1,44 @@
#include "PdfDocumentCatalog.h"
#include "PdfOutline.h"
#include "PdfPageTree.h"
#include "PdfPage.h"
#include "PdfXRefTable.h"
PdfDocumentCatalog::PdfDocumentCatalog()
: PdfObject()
{
mOutlines = std::make_unique<PdfOutlineCollection>();
mPages = std::make_unique<PdfPageTree>();
}
unsigned PdfDocumentCatalog::IndexObjects(unsigned count)
{
auto newCount = count + 1;
mObjectNumber = newCount;
auto objectCount = mOutlines->IndexObjects(mObjectNumber);
objectCount = mPages->IndexObjects(objectCount);
return objectCount;
}
std::string PdfDocumentCatalog::ToString(PdfXRefTable* xRefTable)
{
UpdateDictionary();
auto content = GetStringPrefix();
content += mDictionary.ToString();
content += GetStringSuffix();
xRefTable->AddRecord(content.size(), mGenerationNumber, mIsFree);
content += mOutlines->ToString(xRefTable);
content += mPages->ToString(xRefTable);
return content;
}
void PdfDocumentCatalog::UpdateDictionary()
{
mDictionary.AddStringItem("Type", "/Catalog");
mDictionary.AddStringItem("Outlines", mOutlines->GetRefString());
mDictionary.AddStringItem("Pages", mPages->GetRefString());
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "PdfObject.h"
class PdfPageTree;
class PdfOutlineCollection;
class PdfDocumentCatalog : public PdfObject
{
public:
PdfDocumentCatalog();
unsigned IndexObjects(unsigned count) override;
std::string ToString(PdfXRefTable* xRefTable) override;
private:
void UpdateDictionary() override;
std::unique_ptr<PdfOutlineCollection> mOutlines;
std::unique_ptr<PdfPageTree> mPages;
};

View file

@ -0,0 +1,58 @@
#include "PdfObject.h"
#include "PdfDictionary.h"
#include "PdfXRefTable.h"
std::string PdfObject::ToString(PdfXRefTable* xRefTable)
{
UpdateDictionary();
auto content = GetStringPrefix();
content += mDictionary.ToString();
content += GetStringSuffix();
xRefTable->AddRecord(content.size(), mGenerationNumber, mIsFree);
return content;
}
void PdfObject::UpdateDictionary()
{
}
std::string PdfObject::GetStringSuffix() const
{
return "endobj\n\n";
}
unsigned PdfObject::IndexObjects(unsigned count)
{
const auto newCount = count + 1;
mObjectNumber = newCount;
return newCount;
}
void PdfObject::SetObjectNumber(unsigned num)
{
mObjectNumber = num;
}
void PdfObject::SetGenerationNumber(unsigned num)
{
mGenerationNumber = num;
}
std::string PdfObject::GetStringPrefix() const
{
return std::to_string(mObjectNumber) + " " + std::to_string(mGenerationNumber) + " obj\n";
}
std::string PdfObject::GetRefString() const
{
return std::to_string(mObjectNumber) + " " + std::to_string(mGenerationNumber) + " R";
}
bool PdfObject::IsFree() const
{
return mIsFree;
}

View file

@ -0,0 +1,40 @@
#pragma once
#include "PdfDictionary.h"
#include <memory>
#include <string>
class PdfXRefTable;
class PdfObject
{
public:
virtual ~PdfObject() = default;
std::string GetStringPrefix() const;
std::string GetStringSuffix() const;
std::string GetRefString() const;
virtual unsigned IndexObjects(unsigned count);
bool IsFree() const;
virtual std::string ToString(PdfXRefTable* xRefTable);
void SetObjectNumber(unsigned num);
void SetGenerationNumber(unsigned num);
protected:
virtual void UpdateDictionary();
unsigned mObjectNumber{0};
unsigned mGenerationNumber{0};
PdfDictionary mDictionary;
bool mIsFree {false};
};
using PdfObjectPtr = std::unique_ptr<PdfObject>;

View file

@ -0,0 +1,40 @@
#include "PdfOutline.h"
#include "PdfXRefTable.h"
std::string PdfOutline::ToString(PdfXRefTable* xRefTable)
{
mDictionary.AddStringItem("Type", "/Outline");
auto content = GetStringPrefix();
content += mDictionary.ToString();
content += GetStringSuffix();
xRefTable->AddRecord(content.size(), mGenerationNumber, mIsFree);
return content;
}
unsigned PdfOutlineCollection::IndexObjects(unsigned count)
{
auto newCount = count + 1;
mObjectNumber = newCount;
for (const auto& outline : mOutlines)
{
newCount = outline->IndexObjects(newCount);
}
return newCount;
}
std::string PdfOutlineCollection::ToString(PdfXRefTable* xRefTable)
{
UpdateDictionary();
std::string content = GetStringPrefix();
content += mDictionary.ToString();
content += GetStringSuffix();
xRefTable->AddRecord(content.size(), mGenerationNumber, mIsFree);
return content;
}
void PdfOutlineCollection::UpdateDictionary()
{
mDictionary.AddStringItem("Type", "/Outlines");
mDictionary.AddStringItem("Count", std::to_string(mOutlines.size()));
}

View file

@ -0,0 +1,26 @@
#pragma once
#include "PdfObject.h"
#include <vector>
class PdfOutline : public PdfObject
{
public:
std::string ToString(PdfXRefTable* xRefTable) override;
};
using PdfOutlinePtr = std::unique_ptr<PdfOutline>;
class PdfOutlineCollection : public PdfObject
{
public:
unsigned IndexObjects(unsigned count) override;
std::string ToString(PdfXRefTable* xRefTable) override;
void UpdateDictionary();
private:
std::vector<PdfOutlinePtr> mOutlines;
};

View file

View file

@ -0,0 +1,121 @@
#pragma once
#include "PdfObject.h"
#include "PdfStream.h"
#include "PdfXRefTable.h"
class PdfPageTree;
class PdfProcSet : public PdfObject
{
public:
std::string ToString(PdfXRefTable* xRefTable) override
{
UpdateDictionary();
auto content = GetStringPrefix();
content += mDictionary.ToString();
content += "[/PDF]\n";
content += GetStringSuffix();
xRefTable->AddRecord(content.size(), mGenerationNumber, mIsFree);
return content;
}
};
class PdfFont : public PdfObject
{
public:
std::string ToString(PdfXRefTable* xRefTable) override
{
UpdateDictionary();
auto content = GetStringPrefix();
content += mDictionary.ToString();
content += GetStringSuffix();
xRefTable->AddRecord(content.size(), mGenerationNumber, mIsFree);
return content;
}
void UpdateDictionary()
{
mDictionary.AddStringItem("Type", "/Font");
mDictionary.AddStringItem("Subtype", "/Type1");
mDictionary.AddStringItem("Name", "/F1");
mDictionary.AddStringItem("BaseFont", "/Helvetica");
mDictionary.AddStringItem("Encoding", "/MacRomanEncoding");
}
};
class PdfPage : public PdfObject
{
public:
PdfPage(PdfPageTree* parent)
: mParent(parent)
{
mContent = std::make_unique<PdfStream>();
std::string pageContent = "BT\n";
pageContent += "/F1 24 Tf\n";
pageContent += "100 100 Td\n";
pageContent += "(Hello World) Tj\n";
pageContent += "ET";
mContent->SetContent(pageContent);
mProcSet = std::make_unique<PdfProcSet>();
mDefaultFont = std::make_unique<PdfFont>();
}
unsigned IndexObjects(unsigned count)
{
auto newCount = count + 1;
mObjectNumber = newCount;
newCount = mContent->IndexObjects(newCount);
newCount = mProcSet->IndexObjects(newCount);
newCount = mDefaultFont->IndexObjects(newCount);
return newCount;
}
std::string ToString(PdfXRefTable* xRefTable) override
{
UpdateDictionary();
auto content = GetStringPrefix();
content += mDictionary.ToString();
content += GetStringSuffix();
xRefTable->AddRecord(content.size(), mGenerationNumber, mIsFree);
content += mContent->ToString(xRefTable);
content += mProcSet->ToString(xRefTable);
content += mDefaultFont->ToString(xRefTable);
return content;
}
void UpdateDictionary()
{
std::string mediaBox = "[0 0 " + std::to_string(mWidth) + " " + std::to_string(mHeight) + "]";
mDictionary.AddStringItem("Type", "/Page");
mDictionary.AddStringItem("MediaBox", mediaBox);
mDictionary.AddStringItem("Parent", mParent->GetRefString());
mDictionary.AddStringItem("Contents", mContent->GetRefString());
auto resourcesDict = std::make_unique<PdfDictionary>();
resourcesDict->AddStringItem("ProcSet", mProcSet->GetRefString());
auto fontDict = std::make_unique<PdfDictionary>();
fontDict->AddStringItem("F1", mDefaultFont->GetRefString());
resourcesDict->AddDictItem("Font", std::move(fontDict));
mDictionary.AddDictItem("Resources", std::move(resourcesDict));
}
private:
unsigned mWidth{612};
unsigned mHeight{792};
std::unique_ptr<PdfStream> mContent;
std::unique_ptr<PdfFont> mDefaultFont;
PdfObjectPtr mProcSet;
PdfPageTree* mParent;
};

View file

@ -0,0 +1,37 @@
#include "PdfPageTree.h"
#include "PdfXRefTable.h"
#include "PdfPage.h"
PdfPageTree::PdfPageTree()
{
mRootPage = std::make_unique<PdfPage>(this);
}
unsigned PdfPageTree::IndexObjects(unsigned count)
{
auto newCount = count + 1;
mObjectNumber = newCount;
newCount = mRootPage->IndexObjects(newCount);
return newCount;
}
std::string PdfPageTree::ToString(PdfXRefTable* xRefTable)
{
UpdateDictionary();
std::string content = GetStringPrefix();
content += mDictionary.ToString();
content += GetStringSuffix();
xRefTable->AddRecord(content.size(), mGenerationNumber, mIsFree);
content += mRootPage->ToString(xRefTable);
return content;
}
void PdfPageTree::UpdateDictionary()
{
mDictionary.AddStringItem("Type", "/Pages");
std::string kids = "[" + mRootPage->GetRefString() + "]";
mDictionary.AddStringItem("Kids", kids);
mDictionary.AddStringItem("Count", "1");
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "PdfObject.h"
class PdfPage;
using PdfPagePtr = std::unique_ptr<PdfPage>;
class PdfPageTree : public PdfObject
{
public:
PdfPageTree();
unsigned IndexObjects(unsigned count) override;
std::string ToString(PdfXRefTable* xRefTable) override;
void UpdateDictionary() override;
private:
PdfPagePtr mRootPage;
};

View file

@ -0,0 +1,36 @@
#include "PdfStream.h"
#include "PdfDictionary.h"
#include "PdfXRefTable.h"
PdfStream::PdfStream()
{
mDictionary.AddStringItem("Length", "0");
}
void PdfStream::SetContent(const std::string& content)
{
mContent = content;
Update();
}
void PdfStream::Update()
{
auto length = mContent.size();
mDictionary.AddStringItem("Length", std::to_string(length));
}
std::string PdfStream::ToString(PdfXRefTable* xRefTable)
{
std::string content = GetStringPrefix();
content += mDictionary.ToString();
content += "stream\n";
content += mContent;
content += "\nendstream\n";
content += "endobj\n\n";
xRefTable->AddRecord(content.size(), mGenerationNumber, mIsFree);
return content;
}

View file

@ -0,0 +1,19 @@
#pragma once
#include "PdfObject.h"
class PdfStream : public PdfObject
{
public:
PdfStream();
void Update();
void SetContent(const std::string& content);
std::string ToString(PdfXRefTable* xRefTable) override;
private:
std::string mContent;
};

View file

@ -0,0 +1,12 @@
#include "PdfWriter.h"
#include "PdfDocument.h"
std::string PdfWriter::ToString(const std::unique_ptr<PdfDocument>& document) const
{
std::string content;
content += document->ToString();
return content;
}

View file

@ -0,0 +1,13 @@
#pragma once
#include <memory>
#include <string>
class PdfDocument;
class PdfWriter
{
public:
std::string ToString(const std::unique_ptr<PdfDocument>& document) const;
};

View file

@ -0,0 +1,68 @@
#include "PdfXRefTable.h"
#include "StringUtils.h"
PdfXRefTable::PdfXRefTable()
{
mSections.push_back(TableSubSection());
}
std::string PdfXRefTable::ToString()
{
std::string content;
for (const auto& section : mSections)
{
content += "xref\n" + std::to_string(section.mStartIndex) + " " + std::to_string(section.mRecords.size());
content += "\n";
for (const auto& record : section.mRecords)
{
auto offsetString = StringUtils::ToPaddedString(10, record.mOffsetBytes);
auto generationString = StringUtils::ToPaddedString(5, record.mGenerationNumber);
auto freeString = record.mIsFree ? "f" : "n";
content += offsetString + " " + generationString + " " + freeString + "\n";
}
content += "\n";
}
return content;
}
unsigned PdfXRefTable::GetNextOffset()
{
auto lastNumRecords = mSections[mSections.size() - 1].mRecords.size();
if (lastNumRecords > 0)
{
return mSections[mSections.size() - 1].mRecords[lastNumRecords -1].mOffsetBytes + mLastAddedBytes;
}
else if (mSections.size() > 1)
{
lastNumRecords = mSections[mSections.size() - 2].mRecords.size();
return mSections[mSections.size() - 2].mRecords[lastNumRecords -1].mOffsetBytes + mLastAddedBytes;
}
else
{
return 0;
}
}
void PdfXRefTable::AddRecord(unsigned numBytes, unsigned generation, unsigned isFree)
{
XRefRecord record;
record.mOffsetBytes = GetNextOffset();
record.mGenerationNumber = generation;
record.mIsFree = isFree;
mSections[mSections.size()-1].mRecords.push_back(record);
mLastAddedBytes = numBytes;
}
unsigned PdfXRefTable::GetNumEntries()
{
unsigned count = 0;
for (const auto& section : mSections)
{
count += section.mRecords.size();
}
return count;
}

View file

@ -0,0 +1,37 @@
#pragma once
#include <string>
#include <memory>
#include <vector>
struct XRefRecord
{
unsigned mOffsetBytes{0};
unsigned mGenerationNumber{0};
bool mIsFree{false};
};
struct TableSubSection
{
unsigned mStartIndex{0};
std::vector<XRefRecord> mRecords;
};
class PdfXRefTable
{
public:
PdfXRefTable();
std::string ToString();
unsigned GetNextOffset();
void AddRecord(unsigned numBytes, unsigned generation, unsigned isFree);
unsigned GetNumEntries();
private:
unsigned mLastAddedBytes{0};
std::vector<TableSubSection> mSections;
};