1 #include "Document.h"
2 
3 #include <algorithm>
4 #include <utility>
5 
6 #include <config.h>
7 
8 #include "pdf/base/XojPdfAction.h"
9 
10 #include "LinkDestination.h"
11 #include "PathUtil.h"
12 #include "Stacktrace.h"
13 #include "Util.h"
14 #include "XojPage.h"
15 #include "filesystem.h"
16 #include "i18n.h"
17 
Document(DocumentHandler * handler)18 Document::Document(DocumentHandler* handler): handler(handler) { g_mutex_init(&this->documentLock); }
19 
~Document()20 Document::~Document() {
21     clearDocument(true);
22     freeTreeContentModel();
23 }
24 
freeTreeContentModel()25 void Document::freeTreeContentModel() {
26     if (this->contentsModel) {
27         gtk_tree_model_foreach(this->contentsModel, reinterpret_cast<GtkTreeModelForeachFunc>(freeTreeContentEntry),
28                                this);
29 
30         g_object_unref(this->contentsModel);
31         this->contentsModel = nullptr;
32     }
33 }
34 
freeTreeContentEntry(GtkTreeModel * treeModel,GtkTreePath * path,GtkTreeIter * iter,Document * doc)35 auto Document::freeTreeContentEntry(GtkTreeModel* treeModel, GtkTreePath* path, GtkTreeIter* iter, Document* doc)
36         -> bool {
37     XojLinkDest* link = nullptr;
38     gtk_tree_model_get(treeModel, iter, DOCUMENT_LINKS_COLUMN_LINK, &link, -1);
39 
40     if (link == nullptr) {
41         return false;
42     }
43 
44     // The dispose function of XojLinkDest is not called, this workaround fixes the Memory Leak
45     delete link->dest;
46     link->dest = nullptr;
47 
48     return false;
49 }
50 
lock()51 void Document::lock() {
52     g_mutex_lock(&this->documentLock);
53 
54     //	if(tryLock()) {
55     //		fprintf(stderr, "Locked by\n");
56     //		Stacktrace::printStracktrace();
57     //		fprintf(stderr, "\n\n\n\n");
58     //	} else {
59     //		g_mutex_lock(&this->documentLock);
60     //	}
61 }
62 
unlock()63 void Document::unlock() {
64     g_mutex_unlock(&this->documentLock);
65 
66     //	fprintf(stderr, "Unlocked by\n");
67     //	Stacktrace::printStracktrace();
68     //	fprintf(stderr, "\n\n\n\n");
69 }
70 
tryLock()71 auto Document::tryLock() -> bool { return g_mutex_trylock(&this->documentLock); }
72 
clearDocument(bool destroy)73 void Document::clearDocument(bool destroy) {
74     if (this->preview) {
75         cairo_surface_destroy(this->preview);
76         this->preview = nullptr;
77     }
78 
79     if (!destroy) {
80         // release lock
81         bool lastLock = tryLock();
82         unlock();
83         this->handler->fireDocumentChanged(DOCUMENT_CHANGE_CLEARED);
84         if (!lastLock)  // document was locked before
85         {
86             lock();
87         }
88     }
89 
90     this->pages.clear();
91     this->pageIndex.reset();
92     freeTreeContentModel();
93 
94     this->filepath = fs::path{};
95     this->pdfFilepath = fs::path{};
96 }
97 
98 /**
99  * Returns the pageCount, this call don't need to be synchronized (if it's not critical, you may get wrong data)
100  */
getPageCount()101 auto Document::getPageCount() -> size_t { return this->pages.size(); }
102 
getPdfPageCount()103 auto Document::getPdfPageCount() -> size_t { return pdfDocument.getPageCount(); }
104 
setFilepath(fs::path filepath)105 void Document::setFilepath(fs::path filepath) { this->filepath = std::move(filepath); }
106 
getFilepath()107 auto Document::getFilepath() -> fs::path { return filepath; }
108 
getPdfFilepath()109 auto Document::getPdfFilepath() -> fs::path { return pdfFilepath; }
110 
createSaveFolder(fs::path lastSavePath)111 auto Document::createSaveFolder(fs::path lastSavePath) -> fs::path {
112     if (!filepath.empty()) {
113         return filepath.parent_path();
114     }
115     if (!pdfFilepath.empty()) {
116         return pdfFilepath.parent_path();
117     }
118 
119 
120     return lastSavePath;
121 }
122 
createSaveFilename(DocumentType type,const string & defaultSaveName)123 auto Document::createSaveFilename(DocumentType type, const string& defaultSaveName) -> fs::path {
124     if (!filepath.empty()) {
125         // This can be any extension
126         fs::path p = filepath.filename();
127         Util::clearExtensions(p);
128         return p;
129     }
130     if (!pdfFilepath.empty()) {
131         fs::path p = pdfFilepath.filename();
132         std::string ext = this->attachPdf ? ".pdf" : "";
133         Util::clearExtensions(p, ext);
134         return p;
135     }
136 
137 
138     time_t curtime = time(nullptr);
139     char stime[128];
140     strftime(stime, sizeof(stime), defaultSaveName.c_str(), localtime(&curtime));
141 
142     // Remove the extension, file format is handled by the filter combo box
143     fs::path p = stime;
144     Util::clearExtensions(p);
145     return p;
146 }
147 
148 
getPreview()149 auto Document::getPreview() -> cairo_surface_t* { return this->preview; }
150 
setPreview(cairo_surface_t * preview)151 void Document::setPreview(cairo_surface_t* preview) {
152     if (this->preview) {
153         cairo_surface_destroy(this->preview);
154     }
155     if (preview) {
156         this->preview = cairo_surface_reference(preview);
157     } else {
158         this->preview = nullptr;
159     }
160 }
161 
getEvMetadataFilename()162 auto Document::getEvMetadataFilename() -> fs::path {
163     if (!this->filepath.empty()) {
164         return this->filepath;
165     }
166     if (!this->pdfFilepath.empty()) {
167         return this->pdfFilepath;
168     }
169     return fs::path{};
170 }
171 
isAttachPdf() const172 auto Document::isAttachPdf() const -> bool { return this->attachPdf; }
173 
findPdfPage(size_t pdfPage)174 auto Document::findPdfPage(size_t pdfPage) -> size_t {
175     // Create a page index if not already indexed.
176     if (!this->pageIndex)
177         indexPdfPages();
178     auto pos = this->pageIndex->find(pdfPage);
179     if (pos == this->pageIndex->end()) {
180         return -1;
181     } else {
182         return pos->second;
183     }
184 }
185 
buildTreeContentsModel(GtkTreeIter * parent,XojPdfBookmarkIterator * iter)186 void Document::buildTreeContentsModel(GtkTreeIter* parent, XojPdfBookmarkIterator* iter) {
187     do {
188         GtkTreeIter treeIter = {0};
189 
190         XojPdfAction* action = iter->getAction();
191         XojLinkDest* link = action->getDestination();
192 
193         if (action->getTitle().empty()) {
194             g_object_unref(link);
195             delete action;
196             continue;
197         }
198 
199         link->dest->setExpand(iter->isOpen());
200 
201         gtk_tree_store_append(GTK_TREE_STORE(contentsModel), &treeIter, parent);
202         char* titleMarkup = g_markup_escape_text(action->getTitle().c_str(), -1);
203 
204         gtk_tree_store_set(GTK_TREE_STORE(contentsModel), &treeIter, DOCUMENT_LINKS_COLUMN_NAME, titleMarkup,
205                            DOCUMENT_LINKS_COLUMN_LINK, link, DOCUMENT_LINKS_COLUMN_PAGE_NUMBER, "", -1);
206 
207         g_free(titleMarkup);
208         g_object_unref(link);
209 
210         XojPdfBookmarkIterator* child = iter->getChildIter();
211         if (child) {
212             buildTreeContentsModel(&treeIter, child);
213             delete child;
214         }
215 
216         delete action;
217 
218     } while (iter->next());
219 }
220 
indexPdfPages()221 void Document::indexPdfPages() {
222     auto index = std::make_unique<PageIndex>();
223     for (size_t i = 0; i < this->pages.size(); ++i) {
224         const auto& p = this->pages[i];
225         if (p->getBackgroundType().isPdfPage()) {
226             index->emplace(p->getPdfPageNr(), i);
227         }
228     }
229     this->pageIndex.swap(index);
230 }
231 
232 
buildContentsModel()233 void Document::buildContentsModel() {
234     freeTreeContentModel();
235 
236     XojPdfBookmarkIterator* iter = pdfDocument.getContentsIter();
237     if (iter == nullptr) {
238         // No Bookmarks
239         return;
240     }
241 
242     this->contentsModel = reinterpret_cast<GtkTreeModel*>(
243             gtk_tree_store_new(4, G_TYPE_STRING, G_TYPE_OBJECT, G_TYPE_BOOLEAN, G_TYPE_STRING));
244     buildTreeContentsModel(nullptr, iter);
245     delete iter;
246 }
247 
getContentsModel()248 auto Document::getContentsModel() -> GtkTreeModel* { return this->contentsModel; }
249 
fillPageLabels(GtkTreeModel * treeModel,GtkTreePath * path,GtkTreeIter * iter,Document * doc)250 auto Document::fillPageLabels(GtkTreeModel* treeModel, GtkTreePath* path, GtkTreeIter* iter, Document* doc) -> bool {
251     XojLinkDest* link = nullptr;
252     gtk_tree_model_get(treeModel, iter, DOCUMENT_LINKS_COLUMN_LINK, &link, -1);
253 
254     if (link == nullptr) {
255         return false;
256     }
257 
258     int page = doc->findPdfPage(link->dest->getPdfPage());
259 
260     gchar* pageLabel = nullptr;
261     if (page != -1) {
262         pageLabel = g_strdup_printf("%i", page + 1);
263     }
264     gtk_tree_store_set(GTK_TREE_STORE(treeModel), iter, DOCUMENT_LINKS_COLUMN_PAGE_NUMBER, pageLabel, -1);
265     g_free(pageLabel);
266 
267     g_object_unref(link);
268     return false;
269 }
270 
updateIndexPageNumbers()271 void Document::updateIndexPageNumbers() {
272     if (this->contentsModel != nullptr) {
273         gtk_tree_model_foreach(this->contentsModel, reinterpret_cast<GtkTreeModelForeachFunc>(fillPageLabels), this);
274     }
275 }
276 
readPdf(const fs::path & filename,bool initPages,bool attachToDocument,gpointer data,gsize length)277 auto Document::readPdf(const fs::path& filename, bool initPages, bool attachToDocument, gpointer data, gsize length)
278         -> bool {
279     GError* popplerError = nullptr;
280 
281     lock();
282 
283     if (data != nullptr) {
284         if (!pdfDocument.load(data, length, password, &popplerError)) {
285             lastError = FS(_F("Document not loaded! ({1}), {2}") % filename.u8string() % popplerError->message);
286             g_error_free(popplerError);
287             unlock();
288 
289             return false;
290         }
291     } else {
292         if (!pdfDocument.load(filename, password, &popplerError)) {
293             if (popplerError) {
294                 lastError = FS(_F("Document not loaded! ({1}), {2}") % filename.u8string() % popplerError->message);
295                 g_error_free(popplerError);
296             } else {
297                 lastError = FS(_F("Document not loaded! ({1}), {2}") % filename.u8string() % "");
298             }
299             unlock();
300             return false;
301         }
302     }
303 
304     this->pdfFilepath = filename;
305     this->attachPdf = attachToDocument;
306     lastError = "";
307 
308     if (initPages) {
309         this->pages.clear();
310     }
311 
312     if (initPages) {
313         for (size_t i = 0; i < pdfDocument.getPageCount(); i++) {
314             XojPdfPageSPtr page = pdfDocument.getPage(i);
315             auto p = std::make_shared<XojPage>(page->getWidth(), page->getHeight());
316             p->setBackgroundPdfPageNr(i);
317             this->pages.emplace_back(std::move(p));
318         }
319     }
320 
321     indexPdfPages();
322     buildContentsModel();
323     updateIndexPageNumbers();
324 
325     unlock();
326 
327     this->handler->fireDocumentChanged(DOCUMENT_CHANGE_PDF_BOOKMARKS);
328 
329     return true;
330 }
331 
setPageSize(PageRef p,double width,double height)332 void Document::setPageSize(PageRef p, double width, double height) { p->setSize(width, height); }
333 
getPageWidth(PageRef p)334 auto Document::getPageWidth(PageRef p) -> double { return p->getWidth(); }
335 
getPageHeight(PageRef p)336 auto Document::getPageHeight(PageRef p) -> double { return p->getHeight(); }
337 
338 /**
339  * @return The last error message to show to the user
340  */
getLastErrorMsg()341 auto Document::getLastErrorMsg() -> string { return lastError; }
342 
deletePage(size_t pNr)343 void Document::deletePage(size_t pNr) {
344     auto it = this->pages.begin() + pNr;
345     this->pages.erase(it);
346 
347     // Reset the page index
348     this->pageIndex.reset();
349     updateIndexPageNumbers();
350 }
351 
insertPage(const PageRef & p,size_t position)352 void Document::insertPage(const PageRef& p, size_t position) {
353     this->pages.insert(this->pages.begin() + position, p);
354 
355     // Reset the page index
356     this->pageIndex.reset();
357     updateIndexPageNumbers();
358 }
359 
addPage(const PageRef & p)360 void Document::addPage(const PageRef& p) {
361     this->pages.push_back(p);
362 
363     // Reset the page index
364     this->pageIndex.reset();
365     updateIndexPageNumbers();
366 }
367 
indexOf(const PageRef & page)368 auto Document::indexOf(const PageRef& page) -> size_t {
369     for (size_t i = 0; i < this->pages.size(); i++) {
370         PageRef pg = this->pages[i];
371         if (pg == page) {
372             return i;
373         }
374     }
375 
376     return npos;
377 }
378 
getPage(size_t page)379 auto Document::getPage(size_t page) -> PageRef {
380     if (getPageCount() <= page) {
381         return nullptr;
382     }
383     if (page == npos) {
384         return nullptr;
385     }
386 
387     return this->pages[page];
388 }
389 
getPdfPage(size_t page)390 auto Document::getPdfPage(size_t page) -> XojPdfPageSPtr { return this->pdfDocument.getPage(page); }
391 
getPdfDocument()392 auto Document::getPdfDocument() -> XojPdfDocument& { return this->pdfDocument; }
393 
operator =(const Document & doc)394 auto Document::operator=(const Document& doc) -> Document& {
395     clearDocument();
396 
397     // Copy PDF Document
398     this->pdfDocument = doc.pdfDocument;
399 
400     this->password = doc.password;
401     this->createBackupOnSave = doc.createBackupOnSave;
402     this->pdfFilepath = doc.pdfFilepath;
403     this->filepath = doc.filepath;
404     this->pages = doc.pages;
405 
406     indexPdfPages();
407     buildContentsModel();
408     updateIndexPageNumbers();
409 
410     bool lastLock = tryLock();
411     unlock();
412     this->handler->fireDocumentChanged(DOCUMENT_CHANGE_COMPLETE);
413     if (!lastLock)  // document was locked before
414     {
415         lock();
416     }
417     return *this;
418 }
419 
setCreateBackupOnSave(bool backup)420 void Document::setCreateBackupOnSave(bool backup) { this->createBackupOnSave = backup; }
421 
shouldCreateBackupOnSave() const422 auto Document::shouldCreateBackupOnSave() const -> bool { return this->createBackupOnSave; }
423