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