You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

777 lines
16 KiB

#include "PdfExport.h"
#include "PdfRefEntry.h"
#include "PdfUtil.h"
#include "UpdateRef.h"
#include "UpdateRefKey.h"
#include <config.h>
#include <GzHelper.h>
#include <PageRange.h>
#include <stdlib.h>
#include <string.h>
/**
* This class uses some inspiration from FPDF (PHP Class)
*/
PdfExport::PdfExport(Document* doc, ProgressListener* progressListener)
{
XOJ_INIT_TYPE(PdfExport);
this->doc = doc;
this->progressListener = progressListener;
this->xref = new PdfXRef();
this->writer = new PdfWriter(this->xref);
this->objectWriter = new PdfObjectWriter(this->writer, this->xref);
this->dataXrefStart = 0;
this->pageIds = NULL;
this->refListsOther = g_hash_table_new_full((GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, g_free,
(GDestroyNotify) PdfRefList::deletePdfRefList);
this->resources = NULL;
this->xref->addXref(0);
this->xref->addXref(0);
this->outlineRoot = 0;
}
PdfExport::~PdfExport()
{
XOJ_CHECK_TYPE(PdfExport);
for (XojPopplerDocument* doc : this->documents)
{
delete doc;
}
g_list_foreach(this->pageIds, (GFunc) g_free, NULL);
g_list_free(this->pageIds);
this->pageIds = NULL;
this->progressListener = NULL;
g_hash_table_destroy(this->refListsOther);
this->refListsOther = NULL;
delete this->writer;
this->writer = NULL;
delete this->objectWriter;
this->objectWriter = NULL;
delete this->xref;
this->xref = NULL;
XOJ_RELEASE_TYPE(PdfExport);
}
bool PdfExport::writeCatalog()
{
XOJ_CHECK_TYPE(PdfExport);
if (!writer->writeObj())
{
return false;
}
writer->write("<<\n");
writer->write("/Type /Catalog\n");
writer->write("/Pages 1 0 R\n");
writer->write("/OpenAction [3 0 R /FitH null]\n");
// write("/OpenAction [3 0 R /Fit]\n");
// if($this->ZoomMode=='fullpage')
// $this->_out('/OpenAction [3 0 R /Fit]');
// elseif($this->ZoomMode=='fullwidth')
// $this->_out('/OpenAction [3 0 R /FitH null]');
// elseif($this->ZoomMode=='real')
// $this->_out('/OpenAction [3 0 R /XYZ null null 1]');
// elseif(!is_string($this->ZoomMode))
// $this->_out('/OpenAction [3 0 R /XYZ null null '.($this->ZoomMode/100).']');
writer->write("/PageLayout /OneColumn\n");
// if($this->LayoutMode=='single')
// $this->_out('/PageLayout /SinglePage');
// elseif($this->LayoutMode=='continuous')
// $this->_out('/PageLayout /OneColumn');
// elseif($this->LayoutMode=='two')
// $this->_out('/PageLayout /TwoColumnLeft');
if (this->outlineRoot)
{
char* outline = g_strdup_printf("/Outlines %i 0 R\n", this->outlineRoot);
writer->write(outline);
g_free(outline);
writer->write("/PageMode /UseOutlines\n");
}
writer->write(">>\nendobj\n");
return writer->getLastError().empty();
}
bool PdfExport::writeCrossRef()
{
XOJ_CHECK_TYPE(PdfExport);
this->dataXrefStart = this->writer->getDataCount();
this->writer->write("xref\n");
this->writer->write("0 ");
char* tmp = g_strdup_printf("%i", this->writer->getObjectId());
this->writer->write(tmp);
g_free(tmp);
this->writer->write("\n");
this->writer->write("0000000000 65535 f \n");
char buffer[64];
for (int i = 0; i < this->xref->getXrefCount(); i++)
{
sprintf(buffer, "%010d 00000 n \n", this->xref->getXref(i));
this->writer->write(buffer);
}
return this->writer->getLastError().empty();
}
bool PdfExport::writePagesindex()
{
XOJ_CHECK_TYPE(PdfExport);
this->xref->setXref(1, this->writer->getDataCount());
//Pages root
this->writer->write("1 0 obj\n");
this->writer->write("<</Type /Pages\n");
this->writer->write("/Kids [");
int pageCount = 0;
for (GList* l = this->pageIds; l != NULL; l = l->next)
{
int id = *((int*) l->data);
this->writer->write(FORMAT("%i 0 R ", id));
pageCount++;
}
this->writer->write("]\n");
this->writer->write(FORMAT("/Count %i\n", pageCount));
PageRef page = doc->getPage(0);
this->writer->write(FORMAT("/MediaBox [0 0 %.2f %.2f]\n", page->getWidth(), page->getHeight()));
this->writer->write(">>\n");
this->writer->write("endobj\n");
return true;
}
bool PdfExport::writeTrailer()
{
XOJ_CHECK_TYPE(PdfExport);
this->writer->write("trailer\n");
this->writer->write("<<\n");
this->writer->write(FORMAT("/Size %i\n", this->writer->getObjectId()));
this->writer->write(FORMAT("/Root %i 0 R\n", (this->writer->getObjectId() - 1)));
this->writer->write(FORMAT("/Info %i 0 R\n", (this->writer->getObjectId() - 2)));
this->writer->write(">>\n");
this->writer->write("startxref\n");
this->writer->write(FORMAT("%i\n", this->dataXrefStart));
this->writer->write("%%EOF\n");
return this->writer->getLastError().empty();
}
void writeOutRef(char* key, PdfRefList* list, gpointer user_data)
{
list->writeRefList(key);
}
bool PdfExport::writeResourcedict()
{
XOJ_CHECK_TYPE(PdfExport);
this->writer->write("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]\n");
g_hash_table_foreach(this->refListsOther, (GHFunc) writeOutRef, this);
return true;
}
void writeOutRefObj(char* key, PdfRefList* list, gpointer user_data)
{
list->writeObjects();
}
bool PdfExport::writeResources()
{
XOJ_CHECK_TYPE(PdfExport);
g_hash_table_foreach(this->refListsOther, (GHFunc) writeOutRefObj, this);
this->objectWriter->writeCopiedObjects();
//Resource dictionary
this->xref->setXref(2, this->writer->getDataCount());
this->writer->write("2 0 obj\n");
this->writer->write("<<\n");
if (!writeResourcedict())
{
return false;
}
this->writer->write(">>\n");
this->writer->write("endobj\n");
return true;
}
bool PdfExport::writeFooter()
{
XOJ_CHECK_TYPE(PdfExport);
if (!writePagesindex())
{
g_warning("failed to write outlines");
return false;
}
if (!writeResources())
{
g_warning("failed to write resources");
return false;
}
this->bookmarks.writeOutlines(this->doc, this->writer, &this->outlineRoot, this->pageIds);
if (!writer->writeInfo(doc->getFilename().string()))
{
g_warning("failed to write info");
return false;
}
if (!writeCatalog())
{
g_warning("failed to write catalog");
return false;
}
if (!writeCrossRef())
{
g_warning("failed to write cross ref");
return false;
}
if (!writeTrailer())
{
g_warning("failed to write trailer");
return false;
}
return true;
}
void PdfExport::writeGzStream(Stream* str, GList* replacementList)
{
XOJ_CHECK_TYPE(PdfExport);
Object obj1;
str->getDict()->lookup("Length", &obj1);
if (!obj1.isInt())
{
g_error("PDFDoc::writeGzStream, no Length in stream dict");
return;
}
const int length = obj1.getInt();
obj1.free();
char* buffer = new char[length];
str->unfilteredReset();
for (int i = 0; i < length; i++)
{
int c = str->getUnfilteredChar();
buffer[i] = c;
}
string text = GzHelper::gzuncompress(string(buffer, length));
writeStream(text.c_str(), text.length(), replacementList);
delete[] buffer;
str->reset();
}
void PdfExport::writePlainStream(Stream* str, GList* replacementList)
{
XOJ_CHECK_TYPE(PdfExport);
Object obj1;
str->getDict()->lookup("Length", &obj1);
if (!obj1.isInt())
{
g_error("PDFDoc::writePlainStream, no Length in stream dict");
return;
}
const int length = obj1.getInt();
obj1.free();
str->unfilteredReset();
GString* buffer = g_string_sized_new(10240);
for (int i = 0; i < length; i++)
{
int c = str->getUnfilteredChar();
buffer = g_string_append_c(buffer, c);
}
writeStream(buffer->str, buffer->len, replacementList);
g_string_free(buffer, true);
str->reset();
}
void PdfExport::writeStream(const char* str, int len, GList* replacementList)
{
XOJ_CHECK_TYPE(PdfExport);
int lastWritten = 0;
int brackets = 0;
char lastChar = 0;
for (int i = 0; i < len; i++)
{
char c = str[i];
if (c == '(' && lastChar != '\\')
{
brackets++;
}
else if (c == ')' && lastChar != '\\')
{
brackets--;
if (brackets < 0)
{
brackets = 0;
}
}
else if (brackets == 0 && str[i] == '/')
{
this->writer->write(string(str + lastWritten, i - lastWritten));
lastWritten = i++;
char buffer[512];
int u = i;
for (; u < len && (u - i) < 512; u++)
{
buffer[u - i] = str[u];
if (PdfUtil::isWhitespace(str[u]))
{
buffer[u - i] = 0;
break;
}
}
for (GList* l = replacementList; l != NULL; l = l->next)
{
RefReplacement* f = (RefReplacement*) l->data;
if (f->name == buffer)
{
this->writer->write(FORMAT("/%s%i", f->type, f->newId));
f->markAsUsed();
lastWritten = u;
break;
}
}
//printf("->replacement?: %s\n", buffer);
}
lastChar = c;
}
this->writer->write(string(str + lastWritten, len - lastWritten));
}
void PdfExport::addPopplerDocument(XojPopplerDocument doc)
{
XOJ_CHECK_TYPE(PdfExport);
for (XojPopplerDocument* d : this->documents)
{
if (*d == doc)
{
return;
}
}
XojPopplerDocument* d = new XojPopplerDocument();
*d = doc;
this->documents.push_back(d);
}
bool PdfExport::addPopplerPage(XojPopplerPage* pdf, XojPopplerDocument doc)
{
XOJ_CHECK_TYPE(PdfExport);
Page* page = pdf->getPage();
static int otherObjectId = 1;
this->resources = page->getResourceDict();
GList* replacementList = NULL;
Dict* dict = page->getResourceDict();
for (int i = 0; i < dict->getLength(); i++)
{
const char* cDictName = dict->getKey(i);
PdfRefList* refList = (PdfRefList*) g_hash_table_lookup(this->refListsOther, cDictName);
if (!refList)
{
char* indexName = NULL;
if (strcmp(cDictName, "Font") == 0)
{
indexName = g_strdup("F");
}
else if (strcmp(cDictName, "XObject") == 0)
{
indexName = g_strdup("I");
}
else if (strcmp(cDictName, "ExtGState") == 0)
{
indexName = g_strdup("Gs");
}
else if (strcmp(cDictName, "Pattern") == 0)
{
indexName = g_strdup("p");
}
else
{
indexName = g_strdup_printf("o%i-", otherObjectId++);
}
refList = new PdfRefList(this->xref, this->objectWriter, this->writer, indexName);
char* dictName = g_strdup(dict->getKey(i));
// insert the new RefList into the hash table
g_hash_table_insert(this->refListsOther, dictName, refList);
}
refList->parse(dict, i, doc, replacementList);
}
Object* o = new Object();
page->getContents(o);
if (o->getType() == objStream)
{
Dict* dict = o->getStream()->getDict();
Object filter;
dict->lookup("Filter", &filter);
// // this may would be better, but not working...:-/
// Object oDict;
// oDict.initDict(dict);
// Stream * txtStream = stream->addFilters(oDict);
// writePlainStream(txtStream);
if (filter.isNull())
{
writePlainStream(o->getStream(), replacementList);
}
else if (filter.isName("FlateDecode"))
{
writeGzStream(o->getStream(), replacementList);
}
else if (filter.isName())
{
g_warning("Unhandled stream filter: %s\n", filter.getName());
}
}
else if (o->getType() == objArray)
{
int arrLength = o->arrayGetLength();
//g_warning("Array length is %i\n",arrLength);
for (int i = 0; i < arrLength; i++)
{
Object* subObject = new Object;
o->arrayGet(i, subObject);
if (subObject->getType() == objStream)
{
Dict* dict = subObject->getStream()->getDict();
Object filter;
dict->lookup("Filter", &filter);
if (filter.isNull())
{
writePlainStream(subObject->getStream(), replacementList);
}
else if (filter.isName("FlateDecode"))
{
writeGzStream(subObject->getStream(), replacementList);
}
else if (filter.isName())
{
g_warning("Unhandled stream filter: %s\n", filter.getName());
}
}
else
{
g_warning("other poppler type: %i\n", subObject->getType());
}
subObject->free();
delete subObject;
}
}
else
{
g_warning("other poppler type: %i\n", o->getType());
}
for (GList* l = replacementList; l != NULL; l = l->next)
{
RefReplacement* f = (RefReplacement*) l->data;
delete f;
}
g_list_free(replacementList);
o->free();
delete o;
this->resources = NULL;
return true;
}
bool PdfExport::writePage(int pageNr)
{
XOJ_CHECK_TYPE(PdfExport);
PageRef page = doc->getPage(pageNr);
if (!page)
{
return false;
}
int* pageId = (int*) g_malloc(sizeof(int));
*pageId = this->writer->getObjectId();
this->pageIds = g_list_append(this->pageIds, pageId);
this->writer->writeObj();
this->writer->write("<</Type /Page\n");
this->writer->write("/Parent 1 0 R\n");
this->writer->write(FORMAT("/MediaBox [0 0 %.2f %.2f]\n", page->getWidth(), page->getHeight()));
this->writer->write("/Resources 2 0 R\n");
// if (isset($this->PageLinks[$n])) {
// //Links
// $annots = '/Annots [';
//foreach ($this->PageLinks[$n] as $pl)
// {
// $rect=sprintf('%.2F %.2F %.2F %.2F',$pl[0],$pl[1],$pl[0]+$pl[2],$pl[1]-$pl[3]);
// $annots.='<</Type /Annot /Subtype /Link /Rect ['.$rect.'] /Border [0 0 0] ';
// if(is_string($pl[4]))
// $annots.='/A <</S /URI /URI '.$this->_textstring($pl[4]).'>>>>';
// else
// {
// $l=$this->links[$pl[4]];
// $h=isset($this->PageSizes[$l[0]]) ? $this->PageSizes[$l[0]][1] : $hPt;
// $annots.=sprintf('/Dest [%d 0 R /XYZ 0 %.2F null]>>',1+2*$l[0],$h-$l[1]*$this->k);
// }
// }
// $this->_out($annots.']');
//}
this->writer->write(FORMAT("/Contents %i 0 R>>\n", this->writer->getObjectId()));
this->writer->write("endobj\n");
//Page content
this->writer->writeObj();
this->writer->startStream();
addPopplerDocument(doc->getPdfDocument());
currentPdfDoc = doc->getPdfDocument();
if (page->getBackgroundType() == BACKGROUND_TYPE_PDF)
{
XojPopplerPage* pdf = doc->getPdfPage(page->getPdfPageNr());
if (!addPopplerPage(pdf, currentPdfDoc))
{
return false;
}
}
currentPdfDoc = cPdf.getDocument();
if (!addPopplerPage(cPdf.getPage(pageNr), currentPdfDoc))
{
return false;
}
this->writer->endStream();
this->writer->write("endobj\n");
return true;
}
string PdfExport::getLastError()
{
XOJ_CHECK_TYPE(PdfExport);
if (this->lastError.empty())
{
return this->writer->getLastError();
}
return this->lastError;
}
bool PdfExport::createPdf(path file, GList* range)
{
XOJ_CHECK_TYPE(PdfExport);
if (range == NULL)
{
this->lastError = "No pages to export!";
return false;
}
if (!this->writer->openFile(file))
{
return false;
}
this->writer->write("%PDF-1.4\n");
int count = 0;
for (GList* l = range; l != NULL; l = l->next)
{
PageRangeEntry* e = (PageRangeEntry*) l->data;
count += e->getLast() - e->getFirst();
}
if (this->progressListener)
{
this->progressListener->setMaximumState(count * 2);
}
int c = 0;
for (GList* l = range; l != NULL; l = l->next)
{
PageRangeEntry* e = (PageRangeEntry*) l->data;
for (int i = e->getFirst(); i < e->getLast(); i++)
{
PageRef page = doc->getPage(i);
cPdf.drawPage(page);
if (this->progressListener)
{
this->progressListener->setCurrentState(c++);
}
}
}
cPdf.finalize();
addPopplerDocument(cPdf.getDocument());
for (int i = 0; i < count; i++)
{
if (!writePage(i))
{
g_warning("error writing page %i", i + 1);
return false;
}
if (this->progressListener)
{
this->progressListener->setCurrentState(i + count);
}
}
// Write our own footer
if (!writeFooter())
{
g_warning("error writing footer");
return false;
}
this->writer->close();
return true;
}
bool PdfExport::createPdf(path file)
{
XOJ_CHECK_TYPE(PdfExport);
if (doc->getPageCount() < 1)
{
lastError = "No pages to export!";
return false;
}
if (!this->writer->openFile(file))
{
return false;
}
this->writer->write("%PDF-1.4\n");
if (this->progressListener)
{
this->progressListener->setMaximumState(doc->getPageCount() * 2);
}
int count = doc->getPageCount();
for (int i = 0; i < count; i++)
{
PageRef page = doc->getPage(i);
cPdf.drawPage(page);
if (this->progressListener)
{
this->progressListener->setCurrentState(i);
}
}
cPdf.finalize();
addPopplerDocument(cPdf.getDocument());
for (int i = 0; i < count; i++)
{
if (!writePage(i))
{
g_warning("error writing page %i", i + 1);
return false;
}
if (this->progressListener)
{
this->progressListener->setCurrentState(i + count);
}
}
// Write our own footer
if (!writeFooter())
{
g_warning("error writing footer");
return false;
}
this->writer->close();
return true;
}